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 32e0dca..8591886 100644 Binary files a/src/img/bowery_icon.png and b/src/img/bowery_icon.png differ diff --git a/src/img/bowery_icon_disabled.png b/src/img/bowery_icon_disabled.png new file mode 100644 index 0000000..795e9bf Binary files /dev/null and b/src/img/bowery_icon_disabled.png differ diff --git a/src/js/background.js b/src/js/background.js index 951fab1..ab93475 100644 --- a/src/js/background.js +++ b/src/js/background.js @@ -1,40 +1,42 @@ import '../img/bowery_icon.png'; +import '../img/bowery_icon_disabled.png'; import 'chrome-extension-async'; -import get from 'lodash/get'; -import { validateToken } from '@lib/api'; -import { BOWERY_APP_DOMAIN } from 'secrets'; +import AuthService from '../services/AuthService' +import TrackingService from '../services/TrackingService' +import { EVENTS } from '../lib/constants' -async function getBoweryToken() { - const valid = await validateToken() - if (valid) { - return; - } - - const window = await chrome.windows.create({ - url: BOWERY_APP_DOMAIN, - type: 'popup', - focused: false, - }); - const tabId = get(window, 'tabs[0].id'); - const [jwToken] = await chrome.tabs.executeScript(tabId, { code: "localStorage.getItem('jwToken')" }); - await chrome.windows.remove(window.id); - await chrome.storage.local.set({ token: jwToken }); - if (!jwToken) { - throw new Error('You have to be logged in to the Bowery application.'); - } +async function activationHandler({ tabId }) { + const tab = await chrome.tabs.get(tabId); + const url = tab.url || tab.pendingUrl; + if (url.match(/https:\/\/streeteasy.com\/building\/|https:\/\/streeteasy.com\/rental\//)) { + await chrome.browserAction.setIcon({ path: 'bowery_icon.png' }); + chrome.browserAction.enable(); + } else { + await chrome.browserAction.setIcon({ path: 'bowery_icon_disabled.png' }); + chrome.browserAction.disable(); + } } -chrome.extension.onRequest.addListener(async ({ type, data }) => { - 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/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/src/services/AuthService.js b/src/services/AuthService.js new file mode 100644 index 0000000..6a2a2b0 --- /dev/null +++ b/src/services/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/services/TrackingService.js b/src/services/TrackingService.js new file mode 100644 index 0000000..42a91ce --- /dev/null +++ b/src/services/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/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`) }, ] } })