diff --git a/ManifestBuilder.js b/ManifestBuilder.js index b3fada3..b923402 100644 --- a/ManifestBuilder.js +++ b/ManifestBuilder.js @@ -3,21 +3,28 @@ const integrations = require('./integrationList.json'); module.exports = { - build: function (isDebug = false) { + build: function (serverProtocol, serverDomain, browser, isDebug = false) { if (isDebug) { integrations.push({ - "matches": ["*://localhost:*/*"], + "matches": ["*://localhost/*/*"], "script": ["for-development-only.js"] }) } + if (browser === "firefox") { + integrations.push({ + "matches": [serverProtocol + '://' + serverDomain + '/*'], + "script": ["timecamp-firefox-helper.js"] + }) + } + let contentScripts = []; for (const integration of integrations) { if (!isDebug && integration.isActiveInProd !== true) { continue; } - contentScripts.push({ + let item = { 'matches': [ ...this.defaults.matches, ...integration.matches @@ -31,7 +38,13 @@ module.exports = { ...this.defaults.js, 'scripts-2.0/third_party/' + integration.script, ], - }); + }; + + if (item.exclude_matches.length === 0) { + delete item.exclude_matches; + } + + contentScripts.push(item); } return contentScripts; diff --git a/README.md b/README.md index 2be0b3c..b83a5da 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,10 @@ TimeCamp Browser Plugin Extension npm install --legacy-peer-deps ``` +## Firefox +To build for Firefox just run: `export BROWSER=firefox` +To switch back to Chrome run: `unset BROWSER` + ## Build for production 1. `unset SERVER_DOMAIN` or revert domain changes - just in case if you changed for development 2. `npm run-script build-prod` - it creates archive `dist/timecamp-browser-extension-2.x.x.zip` and directory `dist/plugin` with same unzipped files diff --git a/manifest.chrome.json b/manifest.chrome.json new file mode 100644 index 0000000..a7374cd --- /dev/null +++ b/manifest.chrome.json @@ -0,0 +1,6 @@ +{ + "background": { + "service_worker": "scripts/background.js" + }, + "offline_enabled": false +} diff --git a/manifest.firefox.json b/manifest.firefox.json new file mode 100644 index 0000000..9914c05 --- /dev/null +++ b/manifest.firefox.json @@ -0,0 +1,10 @@ +{ + "background": { + "scripts": ["scripts/background.js"] + }, + "browser_specific_settings": { + "gecko": { + "id": "dev@timecamp.com" + } + } +} \ No newline at end of file diff --git a/manifest.json b/manifest.json index 010a4b3..a950498 100644 --- a/manifest.json +++ b/manifest.json @@ -123,15 +123,11 @@ ], "homepage_url": "http://www.timecamp.com/", "incognito": "spanning", - "offline_enabled": false, "permissions": [ "tabs", "storage" ], "host_permissions": ["https://*.timecamp.com/*"], - "background": { - "service_worker": "scripts/background.js" - }, "web_accessible_resources": [{ "resources":[ "images/*.gif", @@ -142,4 +138,4 @@ ], "matches": [""] }] -} +} \ No newline at end of file diff --git a/package.json b/package.json index c95592b..e5352c9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "timecamp-chrome-extension", - "version": "2.85.2", + "version": "2.85.22", "description": "TimeCamp Google Chrome Time Tracking Extension", "main": "index.js", "scripts": { @@ -11,7 +11,7 @@ "build-dev": "webpack-cli --mode development && npm run merge-background-files", "build-prod-unsafe": "webpack-cli --mode production && npm run merge-background-files && npm run zip", "merge-background-files": "cat 'dist/plugin/scripts-2.0/background-2.0.js' >> 'dist/plugin/scripts/background.js'", - "zip": "bash -c \"pushd dist/plugin && zip -rq ../timecamp-browser-extension-`node -p \"require('./package.json').version\"`.zip * && popd\"" + "zip": "bash -c \" pushd dist/plugin && zip -rq ../timecamp-browser-extension-`node -p \"require('./package.json').version\"`-`if [ $BROWSER == \"firefox\" ]; then echo \"firefox\"; else echo \"chromium\"; fi`.zip * && popd\"" }, "babel": { "presets": [ diff --git a/scripts-2.0/ApiService.js b/scripts-2.0/ApiService.js index 5957dfc..30889c2 100644 --- a/scripts-2.0/ApiService.js +++ b/scripts-2.0/ApiService.js @@ -863,9 +863,9 @@ export default class ApiService { removeStoredToken() { return new Promise((resolve, reject) => { - chrome.storage.sync.clear(); - chrome.storage.sync.set({ 'removed': true }, function () { - if (chrome.runtime.lastError) { + browser.storage.sync.clear(); + browser.storage.sync.set({ 'removed': true }).then(() => { + if (browser.runtime.lastError) { reject(); } else { resolve(true); @@ -876,10 +876,10 @@ export default class ApiService { storeToken(token) { return new Promise((resolve, reject) => { - chrome.storage.sync.set({ 'token': token, 'removed': false }, function () { + browser.storage.sync.set({ 'token': token, 'removed': false }).then(() => { resolve(); - if (chrome.runtime.lastError) { - logger.error(chrome.runtime.lastError.message); + if (browser.runtime.lastError) { + logger.error(browser.runtime.lastError.message); } }); }); @@ -887,10 +887,10 @@ export default class ApiService { storeNextToken(token) { return new Promise((resolve, reject) => { - chrome.storage.sync.set({ 'next_token': token, 'removed': false }, function () { + browser.storage.sync.set({ 'next_token': token, 'removed': false }).then(() => { resolve(); - if (chrome.runtime.lastError) { - logger.error(chrome.runtime.lastError.message); + if (browser.runtime.lastError) { + logger.error(browser.runtime.lastError.message); } }); }); @@ -898,9 +898,9 @@ export default class ApiService { getStoredToken() { return new Promise((resolve, reject) => { - chrome.storage.sync.get('token', function (items) { + browser.storage.sync.get('token').then((items) => { var token = items['token']; - if (token && !chrome.runtime.lastError) { + if (token && !browser.runtime.lastError) { resolve(token); } else { reject(); @@ -911,9 +911,9 @@ export default class ApiService { getStoredNextToken() { return new Promise((resolve, reject) => { - chrome.storage.sync.get('next_token', function (items) { + browser.storage.sync.get('next_token').then((items) => { var token = items['next_token']; - if (token && !chrome.runtime.lastError) { + if (token && !browser.runtime.lastError) { resolve(token); } else { reject(); @@ -924,8 +924,8 @@ export default class ApiService { getLoggedOutFlag() { return new Promise((resolve, reject) => { - chrome.storage.sync.get('removed', function (items) { - if (chrome.runtime.lastError) { + browser.storage.sync.get('removed').then((items) => { + if (browser.runtime.lastError) { reject(); return; } diff --git a/scripts-2.0/StorageManager.js b/scripts-2.0/StorageManager.js index a486700..304035f 100644 --- a/scripts-2.0/StorageManager.js +++ b/scripts-2.0/StorageManager.js @@ -19,11 +19,11 @@ export default class StorageManager { browser.storage.sync.get(key).then((items) => { let data = items[key]; - if (browser.runtime.lastError === undefined) { + if (!browser.runtime.lastError) { resolve(data); } else { - logger.error(browser.runtime.lastError.message); - reject(browser.runtime.lastError.message); + logger.error(browser.runtime.lastError?.error); + reject(browser.runtime.lastError?.error); } }).catch((e) => { reject(e); @@ -34,11 +34,11 @@ export default class StorageManager { set(key, data) { return new Promise((resolve, reject) => { browser.storage.sync.set({[key]: data}).then(() => { - if (browser.runtime.lastError === undefined) { + if (!browser.runtime.lastError) { resolve(); } else { - logger.error(browser.runtime.lastError.message); - reject(browser.runtime.lastError.message); + logger.error(browser.runtime.lastError?.error); + reject(browser.runtime.lastError?.error); } }).catch((e) => { reject(e); diff --git a/scripts-2.0/background.js b/scripts-2.0/background.js index 27cb2df..109df6d 100644 --- a/scripts-2.0/background.js +++ b/scripts-2.0/background.js @@ -369,6 +369,9 @@ const TcButton = { reject(error); }); break; + case 'tokenUpdate': + TcButton.login(request.token); + break; } } catch (e) { logger.error(e); @@ -377,13 +380,13 @@ const TcButton = { }); }, + //used only by chrome newMessageExternal: (request, sender, sendResponse) => { return new Promise((resolve, reject) => { try { switch (request.type) { case 'tokenUpdate': - apiService.storeToken(request.token); - TcButton.doAfterLogin(); + TcButton.login(request.token); break; } } catch (e) { @@ -457,6 +460,11 @@ const TcButton = { }); }, + login: (token) => { + apiService.storeToken(token); + TcButton.doAfterLogin(); + }, + doAfterLogin: () => { return new Promise((resolve, reject) => { TcButton.isUserLogged = true; @@ -723,6 +731,8 @@ browser.runtime.onMessage.addListener(function (request, sender, sendResponse) { //tracking return analyticsService.logEvent(request, sender, sendResponse) }); + +//used only by chrome browser.runtime.onMessageExternal.addListener(TcButton.newMessageExternal); setInterval(() => { const promise = TcButton.updateCurrentEntry(); diff --git a/scripts-2.0/third_party/timecamp-firefox-helper.js b/scripts-2.0/third_party/timecamp-firefox-helper.js new file mode 100644 index 0000000..57463b6 --- /dev/null +++ b/scripts-2.0/third_party/timecamp-firefox-helper.js @@ -0,0 +1,8 @@ +'use strict'; + +//firefox helper for pass data from website to background script +window.addEventListener("message", (event) => { + if (event.data.type === 'tokenUpdate') { + browser.runtime.sendMessage(event.data); + } +}, false); \ No newline at end of file diff --git a/webpack.config.js b/webpack.config.js index a740d6e..d9f5b39 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -12,7 +12,7 @@ const ENV = { SERVER_PROTOCOL: process.env.SERVER_PROTOCOL || 'https', SERVER_DOMAIN: process.env.SERVER_DOMAIN || 'app.timecamp.com', NEXT_SERVER_DOMAIN: "v4.api.timecamp.com", - MARKETING_PAGE_DOMAIN: 'www.timecamp.com', + MARKETING_PAGE_DOMAIN: 'www.timecamp.com', GOOGLE_ANALYTICS_ID: 'UA-4525089-16' }; @@ -214,7 +214,7 @@ function transformOldConfig() { } function transformManifest(isDebug = false) { - return function (content) { + return async function (content) { const manifest = JSON.parse(content.toString()); manifest.homepage_url = ENV.SERVER_PROTOCOL + '://' + ENV.SERVER_DOMAIN + '/'; manifest.host_permissions = [ @@ -224,16 +224,24 @@ function transformManifest(isDebug = false) { manifest.externally_connectable.matches = [ ...manifest.externally_connectable.matches, - ENV.SERVER_PROTOCOL + '://' + ENV.SERVER_DOMAIN + '/*' + ENV.SERVER_PROTOCOL + '://' + ENV.SERVER_DOMAIN + '/*', ]; + const browser = process.env.BROWSER === "firefox" ? "firefox" : "chrome"; + manifest.content_scripts = [ ...manifest.content_scripts, - ...ManifestBuilder.build(isDebug) + ...ManifestBuilder.build(ENV.SERVER_PROTOCOL, ENV.SERVER_DOMAIN, browser, isDebug) ]; manifest.version = pkg.version; - return Buffer.from(JSON.stringify(manifest, undefined, 2)); + const browserSpecificManifestPath = `manifest.${browser}.json`; + + const file = await fs.promises.readFile(browserSpecificManifestPath); + const fileContent = JSON.parse(file.toString()); + const manifestResult = Object.assign(manifest, fileContent); + + return Buffer.from(JSON.stringify(manifestResult, undefined, 2)); }; }