From 1de3da216391ca302e2979d1e510c987cb07134a Mon Sep 17 00:00:00 2001 From: Artsemi Sinitsa Date: Tue, 24 Dec 2019 11:51:51 +0300 Subject: [PATCH 1/2] Add metrics, refactor some code --- package-lock.json | 64 ++++++++----- package.json | 3 +- src/img/bowery_icon.png | Bin 2795 -> 2441 bytes src/img/bowery_icon_disabled.png | Bin 0 -> 1826 bytes src/js/background.js | 68 +++++++------- src/js/parse-comp.js | 151 +++++++++++++++---------------- src/js/popup.js | 1 - src/lib/AuthService.js | 49 ++++++++++ src/lib/TrackingService.js | 27 ++++++ src/lib/api.js | 73 +++++++-------- src/lib/constants.js | 6 ++ src/lib/utils.js | 77 ++++++++-------- webpack.config.js | 5 +- 13 files changed, 308 insertions(+), 216 deletions(-) create mode 100644 src/img/bowery_icon_disabled.png create mode 100644 src/lib/AuthService.js create mode 100644 src/lib/TrackingService.js diff --git a/package-lock.json b/package-lock.json index d883f84..b66ec0f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,9 +1,14 @@ { "name": "bowery-chrome-extension", - "version": "1.0.0", + "version": "1.0.1", "lockfileVersion": 1, "requires": true, "dependencies": { + "@amplitude/ua-parser-js": { + "version": "0.7.20", + "resolved": "https://registry.npmjs.org/@amplitude/ua-parser-js/-/ua-parser-js-0.7.20.tgz", + "integrity": "sha512-bmW++BLt1Hg+4HCExLXP+0Jhgy2eTsEevqkVc5o4yYbgwdP/gV3gEQXzyVrMVlWWNLgph/tFIkf5PVlSpCELEg==" + }, "@material/animation": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/@material/animation/-/animation-3.1.0.tgz", @@ -1205,6 +1210,28 @@ "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=", "dev": true }, + "amplitude-js": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/amplitude-js/-/amplitude-js-5.8.0.tgz", + "integrity": "sha512-eqlDsAQu/5CQQ6C6ePA9gicjgNHlw/C+DIOFMqsq5uIBQEN8EG4O3YVgQe/R22RxQqFkyBGZSNrBCQwEw42ghQ==", + "requires": { + "@amplitude/ua-parser-js": "0.7.20", + "blueimp-md5": "^2.10.0", + "query-string": "5" + }, + "dependencies": { + "query-string": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/query-string/-/query-string-5.1.1.tgz", + "integrity": "sha512-gjWOsm2SoGlgLEdAGt7a6slVOk9mGiXmPFMqrEhLQ68rhQuBnpfs3+EmlvqKyxnCo9/PPlF+9MtY02S1aFg+Jw==", + "requires": { + "decode-uri-component": "^0.2.0", + "object-assign": "^4.1.0", + "strict-uri-encode": "^1.0.0" + } + } + } + }, "ansi-colors": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.4.tgz", @@ -1642,6 +1669,11 @@ "integrity": "sha512-aBQ1FxIa7kSWCcmKHlcHFlT2jt6J/l4FzC7KcPELkOJOsPOb/bccdhmIrKDfXhwFrmc7vDoDrrepFvGqjyXGJg==", "dev": true }, + "blueimp-md5": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/blueimp-md5/-/blueimp-md5-2.12.0.tgz", + "integrity": "sha512-zo+HIdIhzojv6F1siQPqPFROyVy7C50KzHv/k/Iz+BtvtVzSHXiMXOpq2wCfNkeBqdCv+V8XOV96tsEt2W/3rQ==" + }, "bn.js": { "version": "4.11.8", "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz", @@ -3934,13 +3966,11 @@ }, "balanced-match": { "version": "1.0.0", - "bundled": true, - "optional": true + "bundled": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, - "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -3953,18 +3983,15 @@ }, "code-point-at": { "version": "1.1.0", - "bundled": true, - "optional": true + "bundled": true }, "concat-map": { "version": "0.0.1", - "bundled": true, - "optional": true + "bundled": true }, "console-control-strings": { "version": "1.1.0", - "bundled": true, - "optional": true + "bundled": true }, "core-util-is": { "version": "1.0.2", @@ -4067,8 +4094,7 @@ }, "inherits": { "version": "2.0.3", - "bundled": true, - "optional": true + "bundled": true }, "ini": { "version": "1.3.5", @@ -4078,7 +4104,6 @@ "is-fullwidth-code-point": { "version": "1.0.0", "bundled": true, - "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -4091,7 +4116,6 @@ "minimatch": { "version": "3.0.4", "bundled": true, - "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -4103,7 +4127,6 @@ "minipass": { "version": "2.3.5", "bundled": true, - "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -4192,8 +4215,7 @@ }, "number-is-nan": { "version": "1.0.1", - "bundled": true, - "optional": true + "bundled": true }, "object-assign": { "version": "4.1.1", @@ -4203,7 +4225,6 @@ "once": { "version": "1.4.0", "bundled": true, - "optional": true, "requires": { "wrappy": "1" } @@ -4309,7 +4330,6 @@ "string-width": { "version": "1.0.2", "bundled": true, - "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -6337,8 +6357,7 @@ "object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", - "dev": true + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" }, "object-copy": { "version": "0.1.0", @@ -8603,8 +8622,7 @@ "strict-uri-encode": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz", - "integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=", - "dev": true + "integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=" }, "string-width": { "version": "2.1.1", diff --git a/package.json b/package.json index 54f052b..056030a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "bowery-chrome-extension", - "version": "1.0.1", + "version": "1.1.0", "description": "Bowery chrome extension for Streeteasy", "scripts": { "build": "node utils/build.js", @@ -29,6 +29,7 @@ "svelte-select": "^3.1.1" }, "dependencies": { + "amplitude-js": "^5.8.0", "axios": "^0.19.0", "chrome-extension-async": "^3.3.2", "filemanager-webpack-plugin": "^2.0.5", diff --git a/src/img/bowery_icon.png b/src/img/bowery_icon.png index 32e0dca1fb4eaa6fccf4a4de70298d39bdcb1ef4..8591886290e2495089bb6d955985086213ff8297 100644 GIT binary patch literal 2441 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 E4 { - if (type === 'popup-opened') { - try { - const [activeTab] = await chrome.tabs.query({ active: true, currentWindow: true }); - if (!activeTab.url.match(/https:\/\/streeteasy.com\/building\/|https:\/\/streeteasy.com\/rental\//)) { - throw new Error('The extension pulls data only from Streeteasy.'); - } - await getBoweryToken(); - await chrome.tabs.executeScript({ file: 'parse-comp.bundle.js' }); - } catch (error) { - chrome.extension.sendRequest({ error: error.message }); - } +chrome.tabs.onActivated.addListener(activationHandler); +chrome.webNavigation.onBeforeNavigate.addListener(activationHandler); + +chrome.runtime.onMessage.addListener(async ({ type }) => { + try { + switch (type) { + case EVENTS.INITIALIZE: + const authInfo = await AuthService.authenticate(); + const user = authInfo.user; + TrackingService.identify(user) + TrackingService.logEvent('Chrome Extension Clicked'); + chrome.tabs.executeScript({ file: 'parse-comp.bundle.js' }); + break; + case EVENTS.COMP_ADDED: + TrackingService.logEvent('Chrome Extension Comp Added'); + break; + default: + break; } + } catch (error) { + chrome.runtime.sendMessage({ error: error }); + } }); diff --git a/src/js/parse-comp.js b/src/js/parse-comp.js index 9f81a7a..1b791f1 100644 --- a/src/js/parse-comp.js +++ b/src/js/parse-comp.js @@ -4,98 +4,97 @@ import intersection from 'lodash/intersection'; import words from 'lodash/words'; import { geocodeByAddress } from '@lib/api'; import $ from 'jquery'; -import { UNIT_AMENITIES_LIST, STREET_EASY_AMENITIES_MAP, GEOGRAPHY_OPTIONS, GOOGLE_ADDRESS_BOROUGH } from '@lib/constants'; +import { UNIT_AMENITIES_LIST, STREET_EASY_AMENITIES_MAP, GEOGRAPHY_OPTIONS, GOOGLE_ADDRESS_BOROUGH, EVENTS } from '@lib/constants'; const getListsOfAmenities = amenitiesList => { - const unitAmenities = intersection(Object.keys(STREET_EASY_AMENITIES_MAP), amenitiesList); - return unitAmenities.map(amenity => - UNIT_AMENITIES_LIST.find(pair => pair.value === STREET_EASY_AMENITIES_MAP[amenity]), - ); + const unitAmenities = intersection(Object.keys(STREET_EASY_AMENITIES_MAP), amenitiesList); + return unitAmenities.map(amenity => + UNIT_AMENITIES_LIST.find(pair => pair.value === STREET_EASY_AMENITIES_MAP[amenity]), + ); }; const getLocationInfoFromAddress = async ({ address, zip }) => { - const addressInfo = await geocodeByAddress({ address, zip }); - const location = {}; + const addressInfo = await geocodeByAddress({ address, zip }); + const location = {}; - const addressComponents = get(addressInfo, 'address_components') || []; - for (const part of addressComponents) { - part.types.forEach(type => { - location[type] = { short: part.short_name, long: part.long_name }; - }); - } - let borough = {} + const addressComponents = get(addressInfo, 'address_components') || []; + for (const part of addressComponents) { + part.types.forEach(type => { + location[type] = { short: part.short_name, long: part.long_name }; + }); + } + let borough = {} - const state = get(location, 'administrative_area_level_1.short'); - let city = location.locality || addressInfo.sublocality || addressInfo.neighborhood; + const state = get(location, 'administrative_area_level_1.short'); + let city = location.locality || addressInfo.sublocality || addressInfo.neighborhood; - if (state === 'NJ') { - city = get(location, 'administrative_area_level_3') || get(location, 'locality'); - } else if (state === 'NY') { - city = location.sublocality || location.locality || {}; - borough = { - short: GOOGLE_ADDRESS_BOROUGH[city.short], - long: GOOGLE_ADDRESS_BOROUGH[city.long], - } + if (state === 'NJ') { + city = get(location, 'administrative_area_level_3') || get(location, 'locality'); + } else if (state === 'NY') { + city = location.sublocality || location.locality || {}; + borough = { + short: GOOGLE_ADDRESS_BOROUGH[city.short], + long: GOOGLE_ADDRESS_BOROUGH[city.long], } + } - let locationIdentifier = GEOGRAPHY_OPTIONS[state] || GEOGRAPHY_OPTIONS.OTHER + let locationIdentifier = GEOGRAPHY_OPTIONS[state] || GEOGRAPHY_OPTIONS.OTHER - if (state === 'NY' && !borough.long) { - locationIdentifier = GEOGRAPHY_OPTIONS.OTHER - } - const coords = { - longitude: get(addressInfo, 'geometry.location.lng'), - latitude: get(addressInfo, 'geometry.location.lat'), - }; + if (state === 'NY' && !borough.long) { + locationIdentifier = GEOGRAPHY_OPTIONS.OTHER + } + const coords = { + longitude: get(addressInfo, 'geometry.location.lng'), + latitude: get(addressInfo, 'geometry.location.lat'), + }; - return { - address:`${get(location, 'street_number.long')} ${get(location, 'route.long')}`, - city: city ? city.short : '', - zip: location.postal_code ? location.postal_code.short : '', - state, - locationIdentifier, - coords, - }; + return { + address: `${get(location, 'street_number.long')} ${get(location, 'route.long')}`, + city: city ? city.short : '', + zip: location.postal_code ? location.postal_code.short : '', + state, + locationIdentifier, + coords, + }; }; const getTextContent = selector => { - const text = $(selector).text(); - return words(text).join(' '); + const text = $(selector).text(); + return words(text).join(' '); }; (async function parseComp() { - const [, data = '[]'] = document.body.textContent.match(/dataLayer = (\[.*\]);/) || []; - const [compData] = JSON.parse(data); - - const amenitiesList = get(compData, 'listAmen', '').split('|'); - const buildingTitle = $('.building-title .incognito').text(); - const [, , unitNumber] = buildingTitle.match(/(.*) #(.*)/); - const dateOfValue = $('.DetailsPage-priceHistory .Table tr:first-child .Table-cell--priceHistoryDate .Text') - .text() - .trim(); - const zip = get(compData, 'listZip'); - const address = getTextContent('.backend_data.BuildingInfo-item'); - const location = await getLocationInfoFromAddress({ zip, address }); - const amenities = getListsOfAmenities(amenitiesList) - const result = { - state: location.state, - dateOfValue: new Date(dateOfValue).toISOString(), - coords: location.coords, - city: location.city, - unitNumber, - address: location.address, - locationIdentifier: location.locationIdentifier, - zip, - rooms: get(compData, 'listRoom'), - bedrooms: get(compData, 'listBed'), - bathrooms: get(compData, 'listBath'), - sqft: get(compData, 'listSqFt', ''), - rent: get(compData, 'listPrice', ''), - amenities: isEmpty(amenities) ? null : amenities, - sourceOfInformation: 'externalDatabase', - sourceUrl: document.location.toString(), - sourceName: 'StreetEasy', - }; + const [, data = '[]'] = document.body.textContent.match(/dataLayer = (\[.*\]);/) || []; + const [compData] = JSON.parse(data); - chrome.extension.sendRequest({ type: 'comp-parsed', data: result, key: buildingTitle }); -})(); + const amenitiesList = get(compData, 'listAmen', '').split('|'); + const buildingTitle = $('.building-title .incognito').text(); + const [, , unitNumber] = buildingTitle.match(/(.*) #(.*)/); + const dateOfValue = $('.DetailsPage-priceHistory .Table tr:first-child .Table-cell--priceHistoryDate .Text') + .text() + .trim(); + const zip = get(compData, 'listZip'); + const address = getTextContent('.backend_data.BuildingInfo-item'); + const location = await getLocationInfoFromAddress({ zip, address }); + const amenities = getListsOfAmenities(amenitiesList) + const result = { + state: location.state, + dateOfValue: new Date(dateOfValue).toISOString(), + coords: location.coords, + city: location.city, + unitNumber, + address: location.address, + locationIdentifier: location.locationIdentifier, + zip, + rooms: get(compData, 'listRoom'), + bedrooms: get(compData, 'listBed'), + bathrooms: get(compData, 'listBath'), + sqft: get(compData, 'listSqFt', ''), + rent: get(compData, 'listPrice', ''), + amenities: isEmpty(amenities) ? null : amenities, + sourceOfInformation: 'externalDatabase', + sourceUrl: document.location.toString(), + sourceName: 'StreetEasy', + }; + chrome.runtime.sendMessage({ type: EVENTS.COMP_PARSED, data: result }); +})() \ No newline at end of file diff --git a/src/js/popup.js b/src/js/popup.js index 69de413..2582c72 100644 --- a/src/js/popup.js +++ b/src/js/popup.js @@ -1,6 +1,5 @@ import App from '../svelte/App.svelte'; - const app = new App({ target: document.body, }); diff --git a/src/lib/AuthService.js b/src/lib/AuthService.js new file mode 100644 index 0000000..6a2a2b0 --- /dev/null +++ b/src/lib/AuthService.js @@ -0,0 +1,49 @@ +import 'chrome-extension-async'; +import axios from 'axios'; +import get from 'lodash/get'; +import 'chrome-extension-async'; +import { BOWERY_APP_DOMAIN } from 'secrets'; + +class AuthService { + static async authenticate({ obtainFreshToken = false } = {}) { + try { + const { token } = obtainFreshToken + ? await AuthService._obtainToken() + : await chrome.storage.local.get('token'); + const response = await axios.get(`${BOWERY_APP_DOMAIN}/user/authenticated-user`, { + headers: { Authorization: token ? `Bearer ${token}` : '' }, + }); + + const user = AuthService._mapUser(response.data) + return { user }; + } catch (error) { + if (!obtainFreshToken) { + return await AuthService.authenticate({ obtainFreshToken: true }); + } else { + throw error; + } + } + } + + static async _obtainToken() { + const tab = await chrome.tabs.create({ url: BOWERY_APP_DOMAIN, active: false }); + const [jwToken] = await chrome.tabs.executeScript(tab.id, { code: "localStorage.getItem('jwToken')" }); + await chrome.tabs.remove(tab.id); + await chrome.storage.local.set({ token: jwToken }); + return { token: jwToken }; + } + + static _mapUser(data) { + const user = { + id: get(data, 'id'), + name: get(data, 'fullName'), + first_name: get(data, 'name.first'), + last_name: get(data, 'name.last'), + position: get(data, 'position'), + email: get(data, 'username'), + }; + return user + } +} + +export default AuthService; diff --git a/src/lib/TrackingService.js b/src/lib/TrackingService.js new file mode 100644 index 0000000..42a91ce --- /dev/null +++ b/src/lib/TrackingService.js @@ -0,0 +1,27 @@ +import Amplitude from 'amplitude-js'; +import { AMPLITUDE_API_KEY } from 'secrets' + +class TrackingService { + constructor() { + this.client = Amplitude.getInstance(); + this.client.init(AMPLITUDE_API_KEY); + } + + identify(user) { + this.client.setUserId(user.id) + const identify = new Amplitude.Identify() + .set('name', user.name) + .set('first_name', user.first_name) + .set('last_name', user.last_name) + .set('email', user.email) + .set('position', user.position); + + this.client.identify(identify); + } + + logEvent(name, properties = {}) { + this.client.logEvent(name, { ...properties, version: process.env.VERSION }); + } +} + +export default new TrackingService() diff --git a/src/lib/api.js b/src/lib/api.js index 93a8a0a..64c6e51 100644 --- a/src/lib/api.js +++ b/src/lib/api.js @@ -1,63 +1,52 @@ import axios from 'axios'; import get from 'lodash/get'; import 'chrome-extension-async'; -import { GOOGLE_API } from './constants'; +import { GOOGLE_API, EVENTS } from './constants'; import { GOOGLE_API_KEY, BOWERY_APP_DOMAIN } from 'secrets'; const getAuthHeaders = async () => { - const { token } = await chrome.storage.local.get('token'); - return { Authorization: token ? `Bearer ${token}` : '' }; + const { token } = await chrome.storage.local.get('token'); + return { Authorization: token ? `Bearer ${token}` : '' }; }; export const geocodeByAddress = async ({ address, zip }) => { - const response = await axios.get(GOOGLE_API, { - params: { address, zip, key: GOOGLE_API_KEY }, - }); - return get(response, 'data.results.0'); + const response = await axios.get(GOOGLE_API, { + params: { address, zip, key: GOOGLE_API_KEY }, + }); + return get(response, 'data.results.0'); }; export const fetchReport = async url => { - if (!url) { - return null; - } - const match = url.match(/((\d|\w){24})/); - if (!match) { - throw new Error('Not a valid URL'); - } - const [id] = match; - const headers = await getAuthHeaders(); - const response = await axios.get(`${BOWERY_APP_DOMAIN}/report/${id}`, { - headers: headers, - }); + if (!url) { + return null; + } + const match = url.match(/((\d|\w){24})/); + if (!match) { + throw new Error('Not a valid URL'); + } + const [id] = match; + const headers = await getAuthHeaders(); + const response = await axios.get(`${BOWERY_APP_DOMAIN}/report/${id}`, { + headers: headers, + }); - return response.data; + return response.data; }; export const addUnitComp = async (url, unitComp) => { - const [id] = url.match(/((\d|\w){24})/); - const headers = await getAuthHeaders(); - await axios.post(`${BOWERY_APP_DOMAIN}/report/${id}/addUnitComp`, unitComp, { - headers: headers, - }); -}; - -export const validateToken = async () => { - try { - const headers = await getAuthHeaders(); - const response = await axios.get(`${BOWERY_APP_DOMAIN}/user/authenticated-user`, { - headers: headers, - }); - return response.status === 200; - } catch (error) { - return false; - } + const [id] = url.match(/((\d|\w){24})/); + const headers = await getAuthHeaders(); + await axios.post(`${BOWERY_APP_DOMAIN}/report/${id}/addUnitComp`, unitComp, { + headers: headers, + }); + chrome.runtime.sendMessage({ type: EVENTS.COMP_ADDED }); }; export const fetchProperty = async params => { const headers = await getAuthHeaders(); - const response = await axios.get(`${BOWERY_APP_DOMAIN}/api/propertySearch/ny/address`, { - params, - headers, - }); - return get(response, 'data.0', {}); + const response = await axios.get(`${BOWERY_APP_DOMAIN}/api/propertySearch/ny/address`, { + params, + headers, + }); + return get(response, 'data.0', {}); }; diff --git a/src/lib/constants.js b/src/lib/constants.js index 95b1cdb..f157ff4 100644 --- a/src/lib/constants.js +++ b/src/lib/constants.js @@ -38,4 +38,10 @@ export const GOOGLE_ADDRESS_BOROUGH = { 'The Bronx': 'Bronx', Queens: 'Queens', 'Staten Island': 'Staten Island', +} + +export const EVENTS = { + INITIALIZE: 'INITIALIZE', + COMP_PARSED: 'COMP_PARSED', + COMP_ADDED: 'COMP_ADDED' } \ No newline at end of file diff --git a/src/lib/utils.js b/src/lib/utils.js index 0272653..0ccf4f2 100644 --- a/src/lib/utils.js +++ b/src/lib/utils.js @@ -1,52 +1,53 @@ import uniqBy from 'lodash/uniqBy'; import { BOWERY_APP_DOMAIN } from 'secrets'; import { fetchProperty } from '@lib/api'; - +import { EVENTS } from '../lib/constants' const normalizeReportUrl = (url = '') => { - const [reportUrl] = url.match(/((\d|\w){24})/); - return `${BOWERY_APP_DOMAIN}/report/${reportUrl}`; + const [reportUrl] = url.match(/((\d|\w){24})/); + return `${BOWERY_APP_DOMAIN}/report/${reportUrl}`; }; export const getLastVisitedReports = async () => { - const history = await chrome.history.search({ - text: `${BOWERY_APP_DOMAIN}/report/`, - startTime: Date.now() - 1008000000, - }); - const reportsVisited = history.filter( - page => - page.url.match(/(\/report\/(\d|\w){24})/) && - page.title !== 'Bowery' && - !BOWERY_APP_DOMAIN.includes(page.title), - ); + const history = await chrome.history.search({ + text: `${BOWERY_APP_DOMAIN}/report/`, + startTime: Date.now() - 1008000000, + }); + const reportsVisited = history.filter( + page => + page.url.match(/(\/report\/(\d|\w){24})/) && + page.title !== 'Bowery' && + !BOWERY_APP_DOMAIN.includes(page.title), + ); - const reports = reportsVisited.map(page => ({ - value: normalizeReportUrl(page.url), - address: page.title, - })); + const reports = reportsVisited.map(page => ({ + value: normalizeReportUrl(page.url), + address: page.title, + })); - return uniqBy(reports, 'value').slice(0, 5); + return uniqBy(reports, 'value').slice(0, 5); }; + export const getInitialRentCompValues = () => - new Promise((resolve, reject) => { - function extensionListener({ type, data, error }) { - if (error) { - reject(new Error(error)); - } + new Promise((resolve, reject) => { + function extensionListener({ type, data, error }) { + if (error) { + reject(new Error(error)); + } - if (type === 'comp-parsed') { - fetchProperty({ address: data.address, city: data.city, zip: data.zip }) - .then(property => { - resolve({ ...data, block: property.block, lot: property.lot, borough: property.borough }); - }) - .catch((err) => { - console.log(err) - resolve(data); - }); + if (type === EVENTS.COMP_PARSED) { + fetchProperty({ address: data.address, city: data.city, zip: data.zip }) + .then(property => { + resolve({ ...data, block: property.block, lot: property.lot, borough: property.borough }); + }) + .catch((err) => { + console.log(err) + resolve(data); + }); - chrome.extension.onRequest.removeListener(extensionListener); - } - } - chrome.extension.onRequest.addListener(extensionListener); - chrome.extension.sendRequest({ type: 'popup-opened' }); - }); + chrome.runtime.onMessage.removeListener(extensionListener); + } + } + chrome.runtime.onMessage.addListener(extensionListener); + chrome.runtime.sendMessage({ type: EVENTS.INITIALIZE }); + }); diff --git a/webpack.config.js b/webpack.config.js index 00d397d..bee0f37 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -85,7 +85,8 @@ var options = { new CleanWebpackPlugin(["build"]), // expose and write the allowed env vars on the compiled bundle new webpack.DefinePlugin({ - "process.env.NODE_ENV": JSON.stringify(env.NODE_ENV) + "process.env.NODE_ENV": JSON.stringify(env.NODE_ENV), + "process.env.VERSION": JSON.stringify(process.env.npm_package_version) }), new CopyWebpackPlugin([{ from: "src/manifest.json", @@ -129,7 +130,7 @@ var options = { new FileManagerPlugin({ onEnd: { archive: [ - { source: path.join(__dirname, "build"), destination: path.join(__dirname, "packages", `${env.NODE_ENV}-v${packageInfo.version}.zip`) }, + { source: path.join(__dirname, "build"), destination: path.join(__dirname, "packages", `${env.NODE_ENV}-v${process.env.npm_package_version}.zip`) }, ] } }) From 681d5c54254255b7d9dae3bd167fa0caa8ca66fd Mon Sep 17 00:00:00 2001 From: Artsemi Sinitsa Date: Tue, 24 Dec 2019 11:53:07 +0300 Subject: [PATCH 2/2] Clean up --- src/js/background.js | 4 ++-- src/{lib => services}/AuthService.js | 0 src/{lib => services}/TrackingService.js | 0 3 files changed, 2 insertions(+), 2 deletions(-) rename src/{lib => services}/AuthService.js (100%) rename src/{lib => services}/TrackingService.js (100%) diff --git a/src/js/background.js b/src/js/background.js index 108a1eb..ab93475 100644 --- a/src/js/background.js +++ b/src/js/background.js @@ -1,8 +1,8 @@ import '../img/bowery_icon.png'; import '../img/bowery_icon_disabled.png'; import 'chrome-extension-async'; -import AuthService from '../lib/AuthService' -import TrackingService from '../lib/TrackingService' +import AuthService from '../services/AuthService' +import TrackingService from '../services/TrackingService' import { EVENTS } from '../lib/constants' async function activationHandler({ tabId }) { diff --git a/src/lib/AuthService.js b/src/services/AuthService.js similarity index 100% rename from src/lib/AuthService.js rename to src/services/AuthService.js diff --git a/src/lib/TrackingService.js b/src/services/TrackingService.js similarity index 100% rename from src/lib/TrackingService.js rename to src/services/TrackingService.js