From f69dadea4afeb745cbe2f4754cfd4d2e16a11f16 Mon Sep 17 00:00:00 2001 From: Sergiusz Date: Wed, 24 Jan 2024 16:14:54 +0100 Subject: [PATCH 1/2] Enable extensions on Firefox --- ManifestBuilder.js | 74 ++++--- manifest.chrome.json | 6 + manifest.firefox.json | 5 + manifest.json | 379 +++++++++++++++++++++------------- package.json | 3 +- scripts/background.firefox.js | 115 +++++++++++ webpack.config.js | 253 ++++++++++++----------- 7 files changed, 541 insertions(+), 294 deletions(-) create mode 100644 manifest.chrome.json create mode 100644 manifest.firefox.json create mode 100644 scripts/background.firefox.js diff --git a/ManifestBuilder.js b/ManifestBuilder.js index b3fada3..7d30891 100644 --- a/ManifestBuilder.js +++ b/ManifestBuilder.js @@ -1,51 +1,49 @@ "use strict"; -const integrations = require('./integrationList.json'); +const integrations = require("./integrationList.json"); module.exports = { - build: function (isDebug = false) { - if (isDebug) { - integrations.push({ - "matches": ["*://localhost:*/*"], - "script": ["for-development-only.js"] - }) - } + build: function (isDebug = false) { + if (isDebug) { + integrations.push({ + matches: ["http://localhost/*"], + script: ["for-development-only.js"], + }); + } - let contentScripts = []; - for (const integration of integrations) { - if (!isDebug && integration.isActiveInProd !== true) { - continue; - } + let contentScripts = []; + for (const integration of integrations) { + if (!isDebug && integration.isActiveInProd !== true) { + continue; + } - contentScripts.push({ - 'matches': [ - ...this.defaults.matches, - ...integration.matches - ], - 'exclude_matches': [ - ...this.defaults.exclude_matches, - ...(integration.exclude_matches ? integration.exclude_matches : []) - ], - 'css': this.defaults.css, - 'js': [ - ...this.defaults.js, - 'scripts-2.0/third_party/' + integration.script, - ], - }); - } + const excludeMatches = [ + ...this.defaults.exclude_matches, + ...(integration.exclude_matches ? integration.exclude_matches : []), + ]; - return contentScripts; - }, - defaults: { - matches: [], - exclude_matches: [], - css: [ - 'scripts-2.0/style/main.min.css', - ], + const excludeMatchesOptions = + excludeMatches.length > 0 ? { exclude_matches: excludeMatches } : {}; + + contentScripts.push({ + matches: [...this.defaults.matches, ...integration.matches], + ...excludeMatchesOptions, + css: this.defaults.css, js: [ - 'scripts-2.0/common.js', + ...this.defaults.js, + "scripts-2.0/third_party/" + integration.script, ], + }); } + + return contentScripts; + }, + defaults: { + matches: [], + exclude_matches: [], + css: ["scripts-2.0/style/main.min.css"], + js: ["scripts-2.0/common.js"], + }, }; //Add this entires to integrations.json when custom domain feature will be done 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..c3e9049 --- /dev/null +++ b/manifest.firefox.json @@ -0,0 +1,5 @@ +{ + "background": { + "scripts": ["scripts/background.firefox.js"] + } +} diff --git a/manifest.json b/manifest.json index 010a4b3..a253e07 100644 --- a/manifest.json +++ b/manifest.json @@ -1,145 +1,246 @@ { - "name": "__MSG_NAME__", - "version": "0", - "manifest_version": 3, - "description": "__MSG_DESCRIPTION__", - "externally_connectable": { - "matches": ["*://*.timecamp.com/*"] + "name": "__MSG_NAME__", + "version": "0", + "manifest_version": 3, + "description": "__MSG_DESCRIPTION__", + "externally_connectable": { + "matches": ["*://*.timecamp.com/*"] + }, + "icons": { + "14": "images/icon-14.png", + "16": "images/icon-16.png", + "48": "images/icon-48.png", + "128": "images/icon-128.png" + }, + "default_locale": "en", + "action": { + "default_icon": { + "19": "images/icon-19-gray.png", + "38": "images/icon-38-gray.png" }, - "icons": { - "14": "images/icon-14.png", - "16": "images/icon-16.png", - "48": "images/icon-48.png", - "128": "images/icon-128.png" + "default_title": "__MSG_TITLE__", + "default_popup": "popup.html" + }, + "content_scripts": [ + { + "matches": ["*://*.podio.com/*"], + "exclude_matches": [ + "*://*.podio.com/", + "*://podio.com/site/*", + "*://podio.com/login*" + ], + "css": [ + "styles/common.css", + "styles/font-awesome.css", + "styles/bootstrap.min.css", + "styles/jquery-ui.structure.min.css", + "styles/jquery-ui.theme.min.css", + "styles/sites/podio.css" + ], + "js": [ + "scripts/lib/jquery-2.1.1.min.js", + "scripts/lib/jquery-ui.min.js", + "scripts/lib/moment.min.js", + "scripts/lib/moment-range.min.js", + "scripts/config.js", + "scripts/TokenManager.js", + "scripts/ApiService.js", + "scripts/common.js", + "scripts/TimerBase.js", + "scripts/TimerButton.js", + "scripts/third_party/podio.js" + ] }, - "default_locale": "en", - "action": { - "default_icon": { - "19": "images/icon-19-gray.png", - "38": "images/icon-38-gray.png" - }, - "default_title": "__MSG_TITLE__", - "default_popup": "popup.html" + { + "matches": ["*://*.teamwork.com/*"], + "exclude_matches": [ + "*://www.teamwork.com/*", + "*://happines.teamwork.com/*", + "*://developer.teamwork.com/*" + ], + "css": [ + "styles/common.css", + "styles/font-awesome.css", + "styles/bootstrap.min.css", + "styles/jquery-ui.structure.min.css", + "styles/jquery-ui.theme.min.css", + "styles/sites/teamwork.css" + ], + "js": [ + "scripts/lib/jquery-2.1.1.min.js", + "scripts/lib/jquery-ui.min.js", + "scripts/lib/moment.min.js", + "scripts/lib/moment-range.min.js", + "scripts/config.js", + "scripts/TokenManager.js", + "scripts/ApiService.js", + "scripts/common.js", + "scripts/TimerBase.js", + "scripts/TimerButton.js", + "scripts/third_party/teamwork.js" + ] }, - "content_scripts": [ - { - "matches": [ - "*://*.podio.com/*" - ], - "exclude_matches" : [ - "*://*.podio.com/", - "*://podio.com/site/*", - "*://podio.com/login*" - ], - "css": ["styles/common.css", "styles/font-awesome.css", "styles/bootstrap.min.css", "styles/jquery-ui.structure.min.css", "styles/jquery-ui.theme.min.css", - "styles/sites/podio.css"], - "js": ["scripts/lib/jquery-2.1.1.min.js", "scripts/lib/jquery-ui.min.js", "scripts/lib/moment.min.js", "scripts/lib/moment-range.min.js", - "scripts/config.js", "scripts/TokenManager.js", "scripts/ApiService.js", "scripts/common.js", "scripts/TimerBase.js","scripts/TimerButton.js", - "scripts/third_party/podio.js"] - }, - { - "matches": [ - "*://*.teamwork.com/*" - ], - "exclude_matches" : [ - "*://www.teamwork.com/*", - "*://happines.teamwork.com/*", - "*://developer.teamwork.com/*" - ], - "css": ["styles/common.css", "styles/font-awesome.css", "styles/bootstrap.min.css", "styles/jquery-ui.structure.min.css", "styles/jquery-ui.theme.min.css", - "styles/sites/teamwork.css"], - "js": ["scripts/lib/jquery-2.1.1.min.js", "scripts/lib/jquery-ui.min.js", "scripts/lib/moment.min.js", "scripts/lib/moment-range.min.js", - "scripts/config.js", "scripts/TokenManager.js", "scripts/ApiService.js", "scripts/common.js", "scripts/TimerBase.js","scripts/TimerButton.js", - "scripts/third_party/teamwork.js"] - }, - { - "matches": [ - "*://*.insightly.com/*" - ], - "exclude_matches" : [ - "*://api.insight.ly/*", - "*://support.insight.ly/*" - ], - "css": ["styles/common.css", "styles/font-awesome.css", "styles/bootstrap.min.css", "styles/jquery-ui.structure.min.css", "styles/jquery-ui.theme.min.css", - "styles/sites/insightly.css"], - "js": ["scripts/lib/jquery-2.1.1.min.js", "scripts/lib/jquery-ui.min.js", "scripts/lib/moment.min.js", "scripts/lib/moment-range.min.js", - "scripts/config.js", "scripts/TokenManager.js", "scripts/ApiService.js", "scripts/common.js", "scripts/TimerBase.js","scripts/TimerButton.js", - "scripts/third_party/insightly.js"] - }, - { - "matches": [ - "*://*.insight.ly/*" - ], - "exclude_matches" : [ - "*://api.insight.ly/*", - "*://support.insight.ly/*" - ], - "css": ["styles/common.css", "styles/font-awesome.css", "styles/bootstrap.min.css", "styles/jquery-ui.structure.min.css", "styles/jquery-ui.theme.min.css", - "styles/sites/insightly.css"], - "js": ["scripts/lib/jquery-2.1.1.min.js", "scripts/lib/jquery-ui.min.js", "scripts/lib/moment.min.js", "scripts/lib/moment-range.min.js", - "scripts/config.js", "scripts/TokenManager.js", "scripts/ApiService.js", "scripts/common.js", "scripts/TimerBase.js","scripts/TimerButton.js", - "scripts/third_party/insight.ly.js"] - }, - { - "matches": [ - "*://*.todoist.com/app*" - ], - "css": ["styles/common.css", "styles/font-awesome.css", "styles/bootstrap.min.css", "styles/jquery-ui.structure.min.css", "styles/jquery-ui.theme.min.css", - "styles/sites/todoist.css"], - "js": ["scripts/lib/jquery-2.1.1.min.js", "scripts/lib/jquery-ui.min.js", "scripts/lib/moment.min.js", "scripts/lib/moment-range.min.js", - "scripts/config.js", "scripts/TokenManager.js", "scripts/ApiService.js", "scripts/common.js", "scripts/TimerBase.js","scripts/TimerButton.js", - "scripts/third_party/todoist.js"] - }, - { - "matches": [ - "*://*.wrike.com/workspace.htm*" - ], - "css": ["styles/common.css", "styles/font-awesome.css", "styles/bootstrap.min.css", "styles/jquery-ui.structure.min.css", "styles/jquery-ui.theme.min.css", - "styles/sites/wrike.css"], - "js": ["scripts/lib/jquery-2.1.1.min.js", "scripts/lib/jquery-ui.min.js", "scripts/lib/moment.min.js", "scripts/lib/moment-range.min.js", - "scripts/config.js", "scripts/TokenManager.js", "scripts/ApiService.js", "scripts/common.js", "scripts/TimerBase.js","scripts/TimerButton.js", - "scripts/third_party/wrike.js"] - }, - { - "matches": [ - "*://app.hey.space/projects/*" - ], - "css": ["styles/common.css", "styles/font-awesome.css", "styles/bootstrap.min.css", "styles/jquery-ui.structure.min.css", "styles/jquery-ui.theme.min.css", - "styles/sites/heyspace.css"], - "js": ["scripts/lib/jquery-2.1.1.min.js", "scripts/lib/jquery-ui.min.js", "scripts/lib/moment.min.js", "scripts/lib/moment-range.min.js", - "scripts/config.js", "scripts/TokenManager.js", "scripts/ApiService.js", "scripts/common.js", "scripts/TimerBase.js","scripts/TimerButton.js", - "scripts/third_party/heyspace.js"] - }, - { - "matches": [ - "*://app.clickup.com/*" - ], - "css": ["styles/common.css", "styles/font-awesome.css", "styles/bootstrap.min.css", "styles/jquery-ui.structure.min.css", "styles/jquery-ui.theme.min.css", - "styles/sites/clickup.css"], - "js": ["scripts/lib/jquery-2.1.1.min.js", "scripts/lib/jquery-ui.min.js", "scripts/lib/moment.min.js", "scripts/lib/moment-range.min.js", - "scripts/config.js", "scripts/TokenManager.js", "scripts/ApiService.js", "scripts/common.js", "scripts/TimerBase.js","scripts/TimerButton.js", - "scripts/third_party/clickup.js"] - } - ], - "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" + { + "matches": ["*://*.insightly.com/*"], + "exclude_matches": ["*://api.insight.ly/*", "*://support.insight.ly/*"], + "css": [ + "styles/common.css", + "styles/font-awesome.css", + "styles/bootstrap.min.css", + "styles/jquery-ui.structure.min.css", + "styles/jquery-ui.theme.min.css", + "styles/sites/insightly.css" + ], + "js": [ + "scripts/lib/jquery-2.1.1.min.js", + "scripts/lib/jquery-ui.min.js", + "scripts/lib/moment.min.js", + "scripts/lib/moment-range.min.js", + "scripts/config.js", + "scripts/TokenManager.js", + "scripts/ApiService.js", + "scripts/common.js", + "scripts/TimerBase.js", + "scripts/TimerButton.js", + "scripts/third_party/insightly.js" + ] }, - "web_accessible_resources": [{ - "resources":[ - "images/*.gif", - "images/*.png", - "scripts/interceptHistoryChanges.js", - "templates/*", - "fonts/*" - ], - "matches": [""] - }] + { + "matches": ["*://*.insight.ly/*"], + "exclude_matches": ["*://api.insight.ly/*", "*://support.insight.ly/*"], + "css": [ + "styles/common.css", + "styles/font-awesome.css", + "styles/bootstrap.min.css", + "styles/jquery-ui.structure.min.css", + "styles/jquery-ui.theme.min.css", + "styles/sites/insightly.css" + ], + "js": [ + "scripts/lib/jquery-2.1.1.min.js", + "scripts/lib/jquery-ui.min.js", + "scripts/lib/moment.min.js", + "scripts/lib/moment-range.min.js", + "scripts/config.js", + "scripts/TokenManager.js", + "scripts/ApiService.js", + "scripts/common.js", + "scripts/TimerBase.js", + "scripts/TimerButton.js", + "scripts/third_party/insight.ly.js" + ] + }, + { + "matches": ["*://*.todoist.com/app*"], + "css": [ + "styles/common.css", + "styles/font-awesome.css", + "styles/bootstrap.min.css", + "styles/jquery-ui.structure.min.css", + "styles/jquery-ui.theme.min.css", + "styles/sites/todoist.css" + ], + "js": [ + "scripts/lib/jquery-2.1.1.min.js", + "scripts/lib/jquery-ui.min.js", + "scripts/lib/moment.min.js", + "scripts/lib/moment-range.min.js", + "scripts/config.js", + "scripts/TokenManager.js", + "scripts/ApiService.js", + "scripts/common.js", + "scripts/TimerBase.js", + "scripts/TimerButton.js", + "scripts/third_party/todoist.js" + ] + }, + { + "matches": ["*://*.wrike.com/workspace.htm*"], + "css": [ + "styles/common.css", + "styles/font-awesome.css", + "styles/bootstrap.min.css", + "styles/jquery-ui.structure.min.css", + "styles/jquery-ui.theme.min.css", + "styles/sites/wrike.css" + ], + "js": [ + "scripts/lib/jquery-2.1.1.min.js", + "scripts/lib/jquery-ui.min.js", + "scripts/lib/moment.min.js", + "scripts/lib/moment-range.min.js", + "scripts/config.js", + "scripts/TokenManager.js", + "scripts/ApiService.js", + "scripts/common.js", + "scripts/TimerBase.js", + "scripts/TimerButton.js", + "scripts/third_party/wrike.js" + ] + }, + { + "matches": ["*://app.hey.space/projects/*"], + "css": [ + "styles/common.css", + "styles/font-awesome.css", + "styles/bootstrap.min.css", + "styles/jquery-ui.structure.min.css", + "styles/jquery-ui.theme.min.css", + "styles/sites/heyspace.css" + ], + "js": [ + "scripts/lib/jquery-2.1.1.min.js", + "scripts/lib/jquery-ui.min.js", + "scripts/lib/moment.min.js", + "scripts/lib/moment-range.min.js", + "scripts/config.js", + "scripts/TokenManager.js", + "scripts/ApiService.js", + "scripts/common.js", + "scripts/TimerBase.js", + "scripts/TimerButton.js", + "scripts/third_party/heyspace.js" + ] + }, + { + "matches": ["*://app.clickup.com/*"], + "css": [ + "styles/common.css", + "styles/font-awesome.css", + "styles/bootstrap.min.css", + "styles/jquery-ui.structure.min.css", + "styles/jquery-ui.theme.min.css", + "styles/sites/clickup.css" + ], + "js": [ + "scripts/lib/jquery-2.1.1.min.js", + "scripts/lib/jquery-ui.min.js", + "scripts/lib/moment.min.js", + "scripts/lib/moment-range.min.js", + "scripts/config.js", + "scripts/TokenManager.js", + "scripts/ApiService.js", + "scripts/common.js", + "scripts/TimerBase.js", + "scripts/TimerButton.js", + "scripts/third_party/clickup.js" + ] + } + ], + "homepage_url": "http://www.timecamp.com/", + "incognito": "spanning", + "permissions": ["tabs", "storage"], + "host_permissions": ["https://*.timecamp.com/*"], + "web_accessible_resources": [ + { + "resources": [ + "images/*.gif", + "images/*.png", + "scripts/interceptHistoryChanges.js", + "templates/*", + "fonts/*" + ], + "matches": [""] + } + ] } diff --git a/package.json b/package.json index c95592b..fba6d88 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": { @@ -9,6 +9,7 @@ "start": "webpack-cli --mode development --watch", "build-prod": "bash -c \"if [ ! -z \"$SERVER_DOMAIN\" ]; then echo 'Server domain must be empty in production build'; else npm run build-prod-unsafe; fi\"", "build-dev": "webpack-cli --mode development && npm run merge-background-files", + "build-dev-firefox": "BROWSER=firefox 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\"" diff --git a/scripts/background.firefox.js b/scripts/background.firefox.js new file mode 100644 index 0000000..37f38f6 --- /dev/null +++ b/scripts/background.firefox.js @@ -0,0 +1,115 @@ +import browser from "webextension-polyfill"; + +const objectToFormdata = (obj) => { + const data = new URLSearchParams(); + for (const [key, value] of Object.entries(obj)) { + data.append(key, value); + } + return data.toString(); +}; +browser.runtime.onMessage.addListener((request, sender, sendResponse) => { + if (request.id === "apiService") { + const { data, headers, type, url } = request.params; + const params = { + method: type, + headers: { + ...headers, + Accept: "application/json", + "content-type": "application/x-www-form-urlencoded", + }, + body: objectToFormdata(data), + }; + fetch(url, params) + .then((response) => response.json()) + .then((data) => sendResponse({ resolve: true, data: data })) + .catch((error) => { + if (error.status && error.status === 403 && !request.isRetry) { + TokenManager.obtainNewToken() + .then((token) => { + ApiService.setToken(token); + sendResponse({ resolve: false, data: { retry: request } }); + }) + .catch(() => { + sendResponse({ resolve: false, data: error }); + }); + } else { + sendResponse({ resolve: false, data: error }); + } + }); + return true; + } + + if (request.id === "errorLog") { + fetch(serverUrl + "ajax/send_error_log", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + type: "chrome-plugin-start-timer", + details: request.response, + }), + }); + return true; + } + + if (request.id === "canTrack") { + const { data, headers, type, url } = request.params; + const params = { + method: type, + headers: { + ...headers, + Accept: "application/json", + "content-type": "application/x-www-form-urlencoded", + }, + body: objectToFormdata(data), + }; + fetch(url, params) + .then((response) => { + sendResponse({ resolve: true, data: response }); + }) + .catch(() => { + sendResponse({ resolve: false, data: null }); + }); + return true; + } + + if (request.id === "token") { + fetch(tokenUrl, { + method: "POST", + body: request.params.data, + }) + .then((response) => response.text()) + .then((token) => { + if (token.toUpperCase() !== "NO_SESSION" && token.length <= 50) { + TokenManager.storeToken(token); + browser.runtime.sendMessage({ + popup: { + loggedIn: null, + statusText: browser.i18n.getMessage("STATUS_SUCCESS"), + }, + }); + setTimeout(() => { + window.parent.close(); + }, 3000); + } else { + browser.runtime.sendMessage({ + popup: { + loggedIn: null, + statusText: browser.i18n.getMessage("LOG_IN_ERROR"), + statusFlag: "error", + }, + }); + } + }) + .catch(() => { + browser.runtime.sendMessage({ + popup: { + loggedIn: null, + statusText: browser.i18n.getMessage("LOG_IN_ERROR"), + statusFlag: "error", + }, + }); + }); + } +}); diff --git a/webpack.config.js b/webpack.config.js index a740d6e..a79aa82 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,68 +1,68 @@ -const fs = require('fs'); -const path = require('path'); -const CopyPlugin = require('copy-webpack-plugin'); -const FileManagerPlugin = require('filemanager-webpack-plugin'); -const HtmlWebpackPlugin = require('html-webpack-plugin'); -const pkg = require('./package.json'); -const { CleanWebpackPlugin: CleanPlugin } = require('clean-webpack-plugin'); -const { EnvironmentPlugin } = require('webpack'); -const ManifestBuilder = require('./ManifestBuilder.js'); +const fs = require("fs"); +const path = require("path"); +const CopyPlugin = require("copy-webpack-plugin"); +const FileManagerPlugin = require("filemanager-webpack-plugin"); +const HtmlWebpackPlugin = require("html-webpack-plugin"); +const pkg = require("./package.json"); +const { CleanWebpackPlugin: CleanPlugin } = require("clean-webpack-plugin"); +const { EnvironmentPlugin } = require("webpack"); +const ManifestBuilder = require("./ManifestBuilder.js"); const ENV = { - SERVER_PROTOCOL: process.env.SERVER_PROTOCOL || 'https', - SERVER_DOMAIN: process.env.SERVER_DOMAIN || 'app.timecamp.com', + 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', - GOOGLE_ANALYTICS_ID: 'UA-4525089-16' + MARKETING_PAGE_DOMAIN: "www.timecamp.com", + GOOGLE_ANALYTICS_ID: "UA-4525089-16", }; const CUSTOM_DOMAINS = { - 73065: 'https://enterprise.timecamp.com/', + 73065: "https://enterprise.timecamp.com/", }; module.exports = (env, argv) => { let version = pkg.version; let development = false; let production = false; - if (argv.mode === 'production') { + if (argv.mode === "production") { production = true; } - if (argv.mode === 'development') { + if (argv.mode === "development") { development = true; } console.log({ - 'development': development, - 'production': production + development: development, + production: production, }); return { - target: 'web', + target: "web", context: path.resolve(__dirname), - devtool: 'source-map', + devtool: "source-map", entry: { - ...entry('main', 'scss', '/style/'), - ...entry('common'), - ...entry('ApiService'), - ...entry('PathService'), - ...entry('TimeFormatter'), - ...entry('popup'), - ...entry('background', 'js', '', '', 'background-2.0'), - ...entryThirdPartyScripts() + ...entry("main", "scss", "/style/"), + ...entry("common"), + ...entry("ApiService"), + ...entry("PathService"), + ...entry("TimeFormatter"), + ...entry("popup"), + ...entry("background", "js", "", "", "background-2.0"), + ...entryThirdPartyScripts(), }, output: { - publicPath: '', - path: path.resolve(__dirname, 'dist'), - filename: '[name].js', + publicPath: "", + path: path.resolve(__dirname, "dist"), + filename: "[name].js", }, optimization: { - minimize: production + minimize: production, }, resolve: { - extensions: ['.tsx', '.ts', '.js', '.jsx', '.scss'] + extensions: [".tsx", ".ts", ".js", ".jsx", ".scss"], }, node: { - global: false + global: false, }, module: { @@ -70,21 +70,21 @@ module.exports = (env, argv) => { { test: /\.(gif)$/i, exclude: /node_modules/, - loader: 'file-loader', + loader: "file-loader", }, { test: /\.tsx?$/, exclude: /node_modules/, - use: 'ts-loader' + use: "ts-loader", }, { test: /\.jsx?$/, exclude: /node_modules/, - use: 'babel-loader', + use: "babel-loader", }, { test: /\.svg$/, - loader: 'svg-url-loader' + loader: "svg-url-loader", }, { test: /\.s[ac]ss$/i, @@ -96,20 +96,23 @@ module.exports = (env, argv) => { "css-loader", // Compiles Sass to CSS "sass-loader", - ] + ], }, { test: /main\.scss/, exclude: /node_modules/, use: [ { - loader: 'file-loader', - options: { outputPath: 'plugin/scripts-2.0/style/', name: '[name].min.css'} + loader: "file-loader", + options: { + outputPath: "plugin/scripts-2.0/style/", + name: "[name].min.css", + }, }, - "sass-loader" - ] + "sass-loader", + ], }, - ] + ], }, plugins: [ new HtmlWebpackPlugin(), @@ -120,120 +123,138 @@ module.exports = (env, argv) => { DEBUG: development, CUSTOM_DOMAINS: CUSTOM_DOMAINS, MARKETING_PAGE_DOMAIN: ENV.MARKETING_PAGE_DOMAIN, - GOOGLE_ANALYTICS_ID: development ? '' : ENV.GOOGLE_ANALYTICS_ID + GOOGLE_ANALYTICS_ID: development ? "" : ENV.GOOGLE_ANALYTICS_ID, }), new CleanPlugin(), - new CopyPlugin({'patterns': [ - ...copy({ - from: '_locales/', - to: '_locales/' - }), - ...copy({ - from: 'fonts/', - to: 'fonts/' - }), - ...copy({ - from: 'images/', - to: 'images/' - }), - ...copy({ - from: 'styles/', - to: 'styles/' - }), - ...copy({ - from: 'scripts/', - to: '../plugin/scripts/', - transform: transformOldConfig() - }), - ...copy({ - from: 'popup.html', - to: '../plugin/' - }), - { - from: 'manifest.json', - to: 'plugin/manifest.json', - transform: transformManifest(development) - } - ]}, { copyUnmodified: true }), + new CopyPlugin( + { + patterns: [ + ...copy({ + from: "_locales/", + to: "_locales/", + }), + ...copy({ + from: "fonts/", + to: "fonts/", + }), + ...copy({ + from: "images/", + to: "images/", + }), + ...copy({ + from: "styles/", + to: "styles/", + }), + ...copy({ + from: "scripts/", + to: "../plugin/scripts/", + transform: transformOldConfig(), + }), + ...copy({ + from: "popup.html", + to: "../plugin/", + }), + { + from: "manifest.json", + to: "plugin/manifest.json", + transform: transformManifest(development), + }, + ], + }, + { copyUnmodified: true } + ), new FileManagerPlugin({ events: { onEnd: { ...(production && { - delete: [ - 'dist/**/*.js.map' - ], - }) - } - } - }) - ].filter(Boolean) - };}; - -function entry ( - name, - ext = 'js', - from = '', - to = '', - destName = name -) { + delete: ["dist/**/*.js.map"], + }), + }, + }, + }), + ].filter(Boolean), + }; +}; + +function entry(name, ext = "js", from = "", to = "", destName = name) { return { - [`plugin/scripts-2.0/${to}${destName}`]: `./scripts-2.0/${from}${name}.${ext}` + [`plugin/scripts-2.0/${to}${destName}`]: `./scripts-2.0/${from}${name}.${ext}`, }; } function entryThirdPartyScripts() { - const contentScriptFiles = fs.readdirSync('./scripts-2.0/third_party/'); + const contentScriptFiles = fs.readdirSync("./scripts-2.0/third_party/"); return contentScriptFiles.reduce((entries, file) => { - const name = file.replace('.js', ''); - return Object.assign(entries, entry(`${name}`, 'js', 'third_party/', 'third_party/')); + const name = file.replace(".js", ""); + return Object.assign( + entries, + entry(`${name}`, "js", "third_party/", "third_party/") + ); }, {}); } -function copy (o) { +function copy(o) { return [ { ...o, - to: `plugin/${o.to}` - } + to: `plugin/${o.to}`, + }, ]; } function transformOldConfig() { - return function (content, filePath) { - let PATH_SEPARATOR = process.platform === 'win32' ? '\\' : '/'; - - if (filePath.split(PATH_SEPARATOR).pop() === 'config.js') { - let serverUrl = 'var serverUrl="' + ENV.SERVER_PROTOCOL + '://' + ENV.SERVER_DOMAIN + '/' + '";'; - let customDomain = 'var customDomain=' + JSON.stringify(CUSTOM_DOMAINS) + ';'; + return function (content, filePath) { + let PATH_SEPARATOR = process.platform === "win32" ? "\\" : "/"; - return Buffer.from(serverUrl + customDomain + content.toString()); - } + if (filePath.split(PATH_SEPARATOR).pop() === "config.js") { + let serverUrl = + 'var serverUrl="' + + ENV.SERVER_PROTOCOL + + "://" + + ENV.SERVER_DOMAIN + + "/" + + '";'; + let customDomain = + "var customDomain=" + JSON.stringify(CUSTOM_DOMAINS) + ";"; - return Buffer.from(content.toString()); + return Buffer.from(serverUrl + customDomain + content.toString()); } + + return Buffer.from(content.toString()); + }; } 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.homepage_url = + ENV.SERVER_PROTOCOL + "://" + ENV.SERVER_DOMAIN + "/"; manifest.host_permissions = [ ...manifest.host_permissions, - ENV.SERVER_PROTOCOL + '://*.' + ENV.SERVER_DOMAIN + '/*' + ENV.SERVER_PROTOCOL + "://*." + ENV.SERVER_DOMAIN + "/*", ]; manifest.externally_connectable.matches = [ ...manifest.externally_connectable.matches, - ENV.SERVER_PROTOCOL + '://' + ENV.SERVER_DOMAIN + '/*' + ENV.SERVER_PROTOCOL + "://" + ENV.SERVER_DOMAIN + "/*", ]; manifest.content_scripts = [ ...manifest.content_scripts, - ...ManifestBuilder.build(isDebug) + ...ManifestBuilder.build(isDebug), ]; manifest.version = pkg.version; - return Buffer.from(JSON.stringify(manifest, undefined, 2)); + const browser = process.env.BROWSER === "firefox" ? "firefox" : "chrome"; + + 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)); }; } From 12cf043bd37643c299df0df0e09c6ca7f7ace65e Mon Sep 17 00:00:00 2001 From: bpDevTc <104766829+bpDevTc@users.noreply.github.com> Date: Mon, 15 Jul 2024 17:18:27 +0200 Subject: [PATCH 2/2] [BP-22] Firefox changes --- ManifestBuilder.js | 85 ++-- README.md | 4 + manifest.firefox.json | 9 +- manifest.json | 379 +++++++----------- package.json | 3 +- scripts-2.0/ApiService.js | 30 +- scripts-2.0/StorageManager.js | 12 +- scripts-2.0/background.js | 14 +- .../third_party/timecamp-firefox-helper.js | 8 + scripts/background.firefox.js | 115 ------ webpack.config.js | 245 ++++++----- 11 files changed, 356 insertions(+), 548 deletions(-) create mode 100644 scripts-2.0/third_party/timecamp-firefox-helper.js delete mode 100644 scripts/background.firefox.js diff --git a/ManifestBuilder.js b/ManifestBuilder.js index 7d30891..b923402 100644 --- a/ManifestBuilder.js +++ b/ManifestBuilder.js @@ -1,49 +1,64 @@ "use strict"; -const integrations = require("./integrationList.json"); +const integrations = require('./integrationList.json'); module.exports = { - build: function (isDebug = false) { - if (isDebug) { - integrations.push({ - matches: ["http://localhost/*"], - script: ["for-development-only.js"], - }); - } + build: function (serverProtocol, serverDomain, browser, isDebug = false) { + if (isDebug) { + integrations.push({ + "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; + } - let contentScripts = []; - for (const integration of integrations) { - if (!isDebug && integration.isActiveInProd !== true) { - continue; - } + let item = { + 'matches': [ + ...this.defaults.matches, + ...integration.matches + ], + 'exclude_matches': [ + ...this.defaults.exclude_matches, + ...(integration.exclude_matches ? integration.exclude_matches : []) + ], + 'css': this.defaults.css, + 'js': [ + ...this.defaults.js, + 'scripts-2.0/third_party/' + integration.script, + ], + }; - const excludeMatches = [ - ...this.defaults.exclude_matches, - ...(integration.exclude_matches ? integration.exclude_matches : []), - ]; + if (item.exclude_matches.length === 0) { + delete item.exclude_matches; + } - const excludeMatchesOptions = - excludeMatches.length > 0 ? { exclude_matches: excludeMatches } : {}; + contentScripts.push(item); + } - contentScripts.push({ - matches: [...this.defaults.matches, ...integration.matches], - ...excludeMatchesOptions, - css: this.defaults.css, + return contentScripts; + }, + defaults: { + matches: [], + exclude_matches: [], + css: [ + 'scripts-2.0/style/main.min.css', + ], js: [ - ...this.defaults.js, - "scripts-2.0/third_party/" + integration.script, + 'scripts-2.0/common.js', ], - }); } - - return contentScripts; - }, - defaults: { - matches: [], - exclude_matches: [], - css: ["scripts-2.0/style/main.min.css"], - js: ["scripts-2.0/common.js"], - }, }; //Add this entires to integrations.json when custom domain feature will be done 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.firefox.json b/manifest.firefox.json index c3e9049..9914c05 100644 --- a/manifest.firefox.json +++ b/manifest.firefox.json @@ -1,5 +1,10 @@ { "background": { - "scripts": ["scripts/background.firefox.js"] + "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 a253e07..a950498 100644 --- a/manifest.json +++ b/manifest.json @@ -1,246 +1,141 @@ { - "name": "__MSG_NAME__", - "version": "0", - "manifest_version": 3, - "description": "__MSG_DESCRIPTION__", - "externally_connectable": { - "matches": ["*://*.timecamp.com/*"] - }, - "icons": { - "14": "images/icon-14.png", - "16": "images/icon-16.png", - "48": "images/icon-48.png", - "128": "images/icon-128.png" - }, - "default_locale": "en", - "action": { - "default_icon": { - "19": "images/icon-19-gray.png", - "38": "images/icon-38-gray.png" + "name": "__MSG_NAME__", + "version": "0", + "manifest_version": 3, + "description": "__MSG_DESCRIPTION__", + "externally_connectable": { + "matches": ["*://*.timecamp.com/*"] }, - "default_title": "__MSG_TITLE__", - "default_popup": "popup.html" - }, - "content_scripts": [ - { - "matches": ["*://*.podio.com/*"], - "exclude_matches": [ - "*://*.podio.com/", - "*://podio.com/site/*", - "*://podio.com/login*" - ], - "css": [ - "styles/common.css", - "styles/font-awesome.css", - "styles/bootstrap.min.css", - "styles/jquery-ui.structure.min.css", - "styles/jquery-ui.theme.min.css", - "styles/sites/podio.css" - ], - "js": [ - "scripts/lib/jquery-2.1.1.min.js", - "scripts/lib/jquery-ui.min.js", - "scripts/lib/moment.min.js", - "scripts/lib/moment-range.min.js", - "scripts/config.js", - "scripts/TokenManager.js", - "scripts/ApiService.js", - "scripts/common.js", - "scripts/TimerBase.js", - "scripts/TimerButton.js", - "scripts/third_party/podio.js" - ] + "icons": { + "14": "images/icon-14.png", + "16": "images/icon-16.png", + "48": "images/icon-48.png", + "128": "images/icon-128.png" }, - { - "matches": ["*://*.teamwork.com/*"], - "exclude_matches": [ - "*://www.teamwork.com/*", - "*://happines.teamwork.com/*", - "*://developer.teamwork.com/*" - ], - "css": [ - "styles/common.css", - "styles/font-awesome.css", - "styles/bootstrap.min.css", - "styles/jquery-ui.structure.min.css", - "styles/jquery-ui.theme.min.css", - "styles/sites/teamwork.css" - ], - "js": [ - "scripts/lib/jquery-2.1.1.min.js", - "scripts/lib/jquery-ui.min.js", - "scripts/lib/moment.min.js", - "scripts/lib/moment-range.min.js", - "scripts/config.js", - "scripts/TokenManager.js", - "scripts/ApiService.js", - "scripts/common.js", - "scripts/TimerBase.js", - "scripts/TimerButton.js", - "scripts/third_party/teamwork.js" - ] + "default_locale": "en", + "action": { + "default_icon": { + "19": "images/icon-19-gray.png", + "38": "images/icon-38-gray.png" + }, + "default_title": "__MSG_TITLE__", + "default_popup": "popup.html" }, - { - "matches": ["*://*.insightly.com/*"], - "exclude_matches": ["*://api.insight.ly/*", "*://support.insight.ly/*"], - "css": [ - "styles/common.css", - "styles/font-awesome.css", - "styles/bootstrap.min.css", - "styles/jquery-ui.structure.min.css", - "styles/jquery-ui.theme.min.css", - "styles/sites/insightly.css" - ], - "js": [ - "scripts/lib/jquery-2.1.1.min.js", - "scripts/lib/jquery-ui.min.js", - "scripts/lib/moment.min.js", - "scripts/lib/moment-range.min.js", - "scripts/config.js", - "scripts/TokenManager.js", - "scripts/ApiService.js", - "scripts/common.js", - "scripts/TimerBase.js", - "scripts/TimerButton.js", - "scripts/third_party/insightly.js" - ] - }, - { - "matches": ["*://*.insight.ly/*"], - "exclude_matches": ["*://api.insight.ly/*", "*://support.insight.ly/*"], - "css": [ - "styles/common.css", - "styles/font-awesome.css", - "styles/bootstrap.min.css", - "styles/jquery-ui.structure.min.css", - "styles/jquery-ui.theme.min.css", - "styles/sites/insightly.css" - ], - "js": [ - "scripts/lib/jquery-2.1.1.min.js", - "scripts/lib/jquery-ui.min.js", - "scripts/lib/moment.min.js", - "scripts/lib/moment-range.min.js", - "scripts/config.js", - "scripts/TokenManager.js", - "scripts/ApiService.js", - "scripts/common.js", - "scripts/TimerBase.js", - "scripts/TimerButton.js", - "scripts/third_party/insight.ly.js" - ] - }, - { - "matches": ["*://*.todoist.com/app*"], - "css": [ - "styles/common.css", - "styles/font-awesome.css", - "styles/bootstrap.min.css", - "styles/jquery-ui.structure.min.css", - "styles/jquery-ui.theme.min.css", - "styles/sites/todoist.css" - ], - "js": [ - "scripts/lib/jquery-2.1.1.min.js", - "scripts/lib/jquery-ui.min.js", - "scripts/lib/moment.min.js", - "scripts/lib/moment-range.min.js", - "scripts/config.js", - "scripts/TokenManager.js", - "scripts/ApiService.js", - "scripts/common.js", - "scripts/TimerBase.js", - "scripts/TimerButton.js", - "scripts/third_party/todoist.js" - ] - }, - { - "matches": ["*://*.wrike.com/workspace.htm*"], - "css": [ - "styles/common.css", - "styles/font-awesome.css", - "styles/bootstrap.min.css", - "styles/jquery-ui.structure.min.css", - "styles/jquery-ui.theme.min.css", - "styles/sites/wrike.css" - ], - "js": [ - "scripts/lib/jquery-2.1.1.min.js", - "scripts/lib/jquery-ui.min.js", - "scripts/lib/moment.min.js", - "scripts/lib/moment-range.min.js", - "scripts/config.js", - "scripts/TokenManager.js", - "scripts/ApiService.js", - "scripts/common.js", - "scripts/TimerBase.js", - "scripts/TimerButton.js", - "scripts/third_party/wrike.js" - ] - }, - { - "matches": ["*://app.hey.space/projects/*"], - "css": [ - "styles/common.css", - "styles/font-awesome.css", - "styles/bootstrap.min.css", - "styles/jquery-ui.structure.min.css", - "styles/jquery-ui.theme.min.css", - "styles/sites/heyspace.css" - ], - "js": [ - "scripts/lib/jquery-2.1.1.min.js", - "scripts/lib/jquery-ui.min.js", - "scripts/lib/moment.min.js", - "scripts/lib/moment-range.min.js", - "scripts/config.js", - "scripts/TokenManager.js", - "scripts/ApiService.js", - "scripts/common.js", - "scripts/TimerBase.js", - "scripts/TimerButton.js", - "scripts/third_party/heyspace.js" - ] - }, - { - "matches": ["*://app.clickup.com/*"], - "css": [ - "styles/common.css", - "styles/font-awesome.css", - "styles/bootstrap.min.css", - "styles/jquery-ui.structure.min.css", - "styles/jquery-ui.theme.min.css", - "styles/sites/clickup.css" - ], - "js": [ - "scripts/lib/jquery-2.1.1.min.js", - "scripts/lib/jquery-ui.min.js", - "scripts/lib/moment.min.js", - "scripts/lib/moment-range.min.js", - "scripts/config.js", - "scripts/TokenManager.js", - "scripts/ApiService.js", - "scripts/common.js", - "scripts/TimerBase.js", - "scripts/TimerButton.js", - "scripts/third_party/clickup.js" - ] - } - ], - "homepage_url": "http://www.timecamp.com/", - "incognito": "spanning", - "permissions": ["tabs", "storage"], - "host_permissions": ["https://*.timecamp.com/*"], - "web_accessible_resources": [ - { - "resources": [ - "images/*.gif", - "images/*.png", - "scripts/interceptHistoryChanges.js", - "templates/*", - "fonts/*" - ], - "matches": [""] - } - ] -} + "content_scripts": [ + { + "matches": [ + "*://*.podio.com/*" + ], + "exclude_matches" : [ + "*://*.podio.com/", + "*://podio.com/site/*", + "*://podio.com/login*" + ], + "css": ["styles/common.css", "styles/font-awesome.css", "styles/bootstrap.min.css", "styles/jquery-ui.structure.min.css", "styles/jquery-ui.theme.min.css", + "styles/sites/podio.css"], + "js": ["scripts/lib/jquery-2.1.1.min.js", "scripts/lib/jquery-ui.min.js", "scripts/lib/moment.min.js", "scripts/lib/moment-range.min.js", + "scripts/config.js", "scripts/TokenManager.js", "scripts/ApiService.js", "scripts/common.js", "scripts/TimerBase.js","scripts/TimerButton.js", + "scripts/third_party/podio.js"] + }, + { + "matches": [ + "*://*.teamwork.com/*" + ], + "exclude_matches" : [ + "*://www.teamwork.com/*", + "*://happines.teamwork.com/*", + "*://developer.teamwork.com/*" + ], + "css": ["styles/common.css", "styles/font-awesome.css", "styles/bootstrap.min.css", "styles/jquery-ui.structure.min.css", "styles/jquery-ui.theme.min.css", + "styles/sites/teamwork.css"], + "js": ["scripts/lib/jquery-2.1.1.min.js", "scripts/lib/jquery-ui.min.js", "scripts/lib/moment.min.js", "scripts/lib/moment-range.min.js", + "scripts/config.js", "scripts/TokenManager.js", "scripts/ApiService.js", "scripts/common.js", "scripts/TimerBase.js","scripts/TimerButton.js", + "scripts/third_party/teamwork.js"] + }, + { + "matches": [ + "*://*.insightly.com/*" + ], + "exclude_matches" : [ + "*://api.insight.ly/*", + "*://support.insight.ly/*" + ], + "css": ["styles/common.css", "styles/font-awesome.css", "styles/bootstrap.min.css", "styles/jquery-ui.structure.min.css", "styles/jquery-ui.theme.min.css", + "styles/sites/insightly.css"], + "js": ["scripts/lib/jquery-2.1.1.min.js", "scripts/lib/jquery-ui.min.js", "scripts/lib/moment.min.js", "scripts/lib/moment-range.min.js", + "scripts/config.js", "scripts/TokenManager.js", "scripts/ApiService.js", "scripts/common.js", "scripts/TimerBase.js","scripts/TimerButton.js", + "scripts/third_party/insightly.js"] + }, + { + "matches": [ + "*://*.insight.ly/*" + ], + "exclude_matches" : [ + "*://api.insight.ly/*", + "*://support.insight.ly/*" + ], + "css": ["styles/common.css", "styles/font-awesome.css", "styles/bootstrap.min.css", "styles/jquery-ui.structure.min.css", "styles/jquery-ui.theme.min.css", + "styles/sites/insightly.css"], + "js": ["scripts/lib/jquery-2.1.1.min.js", "scripts/lib/jquery-ui.min.js", "scripts/lib/moment.min.js", "scripts/lib/moment-range.min.js", + "scripts/config.js", "scripts/TokenManager.js", "scripts/ApiService.js", "scripts/common.js", "scripts/TimerBase.js","scripts/TimerButton.js", + "scripts/third_party/insight.ly.js"] + }, + { + "matches": [ + "*://*.todoist.com/app*" + ], + "css": ["styles/common.css", "styles/font-awesome.css", "styles/bootstrap.min.css", "styles/jquery-ui.structure.min.css", "styles/jquery-ui.theme.min.css", + "styles/sites/todoist.css"], + "js": ["scripts/lib/jquery-2.1.1.min.js", "scripts/lib/jquery-ui.min.js", "scripts/lib/moment.min.js", "scripts/lib/moment-range.min.js", + "scripts/config.js", "scripts/TokenManager.js", "scripts/ApiService.js", "scripts/common.js", "scripts/TimerBase.js","scripts/TimerButton.js", + "scripts/third_party/todoist.js"] + }, + { + "matches": [ + "*://*.wrike.com/workspace.htm*" + ], + "css": ["styles/common.css", "styles/font-awesome.css", "styles/bootstrap.min.css", "styles/jquery-ui.structure.min.css", "styles/jquery-ui.theme.min.css", + "styles/sites/wrike.css"], + "js": ["scripts/lib/jquery-2.1.1.min.js", "scripts/lib/jquery-ui.min.js", "scripts/lib/moment.min.js", "scripts/lib/moment-range.min.js", + "scripts/config.js", "scripts/TokenManager.js", "scripts/ApiService.js", "scripts/common.js", "scripts/TimerBase.js","scripts/TimerButton.js", + "scripts/third_party/wrike.js"] + }, + { + "matches": [ + "*://app.hey.space/projects/*" + ], + "css": ["styles/common.css", "styles/font-awesome.css", "styles/bootstrap.min.css", "styles/jquery-ui.structure.min.css", "styles/jquery-ui.theme.min.css", + "styles/sites/heyspace.css"], + "js": ["scripts/lib/jquery-2.1.1.min.js", "scripts/lib/jquery-ui.min.js", "scripts/lib/moment.min.js", "scripts/lib/moment-range.min.js", + "scripts/config.js", "scripts/TokenManager.js", "scripts/ApiService.js", "scripts/common.js", "scripts/TimerBase.js","scripts/TimerButton.js", + "scripts/third_party/heyspace.js"] + }, + { + "matches": [ + "*://app.clickup.com/*" + ], + "css": ["styles/common.css", "styles/font-awesome.css", "styles/bootstrap.min.css", "styles/jquery-ui.structure.min.css", "styles/jquery-ui.theme.min.css", + "styles/sites/clickup.css"], + "js": ["scripts/lib/jquery-2.1.1.min.js", "scripts/lib/jquery-ui.min.js", "scripts/lib/moment.min.js", "scripts/lib/moment-range.min.js", + "scripts/config.js", "scripts/TokenManager.js", "scripts/ApiService.js", "scripts/common.js", "scripts/TimerBase.js","scripts/TimerButton.js", + "scripts/third_party/clickup.js"] + } + ], + "homepage_url": "http://www.timecamp.com/", + "incognito": "spanning", + "permissions": [ + "tabs", + "storage" + ], + "host_permissions": ["https://*.timecamp.com/*"], + "web_accessible_resources": [{ + "resources":[ + "images/*.gif", + "images/*.png", + "scripts/interceptHistoryChanges.js", + "templates/*", + "fonts/*" + ], + "matches": [""] + }] +} \ No newline at end of file diff --git a/package.json b/package.json index fba6d88..e5352c9 100644 --- a/package.json +++ b/package.json @@ -9,10 +9,9 @@ "start": "webpack-cli --mode development --watch", "build-prod": "bash -c \"if [ ! -z \"$SERVER_DOMAIN\" ]; then echo 'Server domain must be empty in production build'; else npm run build-prod-unsafe; fi\"", "build-dev": "webpack-cli --mode development && npm run merge-background-files", - "build-dev-firefox": "BROWSER=firefox 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/scripts/background.firefox.js b/scripts/background.firefox.js deleted file mode 100644 index 37f38f6..0000000 --- a/scripts/background.firefox.js +++ /dev/null @@ -1,115 +0,0 @@ -import browser from "webextension-polyfill"; - -const objectToFormdata = (obj) => { - const data = new URLSearchParams(); - for (const [key, value] of Object.entries(obj)) { - data.append(key, value); - } - return data.toString(); -}; -browser.runtime.onMessage.addListener((request, sender, sendResponse) => { - if (request.id === "apiService") { - const { data, headers, type, url } = request.params; - const params = { - method: type, - headers: { - ...headers, - Accept: "application/json", - "content-type": "application/x-www-form-urlencoded", - }, - body: objectToFormdata(data), - }; - fetch(url, params) - .then((response) => response.json()) - .then((data) => sendResponse({ resolve: true, data: data })) - .catch((error) => { - if (error.status && error.status === 403 && !request.isRetry) { - TokenManager.obtainNewToken() - .then((token) => { - ApiService.setToken(token); - sendResponse({ resolve: false, data: { retry: request } }); - }) - .catch(() => { - sendResponse({ resolve: false, data: error }); - }); - } else { - sendResponse({ resolve: false, data: error }); - } - }); - return true; - } - - if (request.id === "errorLog") { - fetch(serverUrl + "ajax/send_error_log", { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - type: "chrome-plugin-start-timer", - details: request.response, - }), - }); - return true; - } - - if (request.id === "canTrack") { - const { data, headers, type, url } = request.params; - const params = { - method: type, - headers: { - ...headers, - Accept: "application/json", - "content-type": "application/x-www-form-urlencoded", - }, - body: objectToFormdata(data), - }; - fetch(url, params) - .then((response) => { - sendResponse({ resolve: true, data: response }); - }) - .catch(() => { - sendResponse({ resolve: false, data: null }); - }); - return true; - } - - if (request.id === "token") { - fetch(tokenUrl, { - method: "POST", - body: request.params.data, - }) - .then((response) => response.text()) - .then((token) => { - if (token.toUpperCase() !== "NO_SESSION" && token.length <= 50) { - TokenManager.storeToken(token); - browser.runtime.sendMessage({ - popup: { - loggedIn: null, - statusText: browser.i18n.getMessage("STATUS_SUCCESS"), - }, - }); - setTimeout(() => { - window.parent.close(); - }, 3000); - } else { - browser.runtime.sendMessage({ - popup: { - loggedIn: null, - statusText: browser.i18n.getMessage("LOG_IN_ERROR"), - statusFlag: "error", - }, - }); - } - }) - .catch(() => { - browser.runtime.sendMessage({ - popup: { - loggedIn: null, - statusText: browser.i18n.getMessage("LOG_IN_ERROR"), - statusFlag: "error", - }, - }); - }); - } -}); diff --git a/webpack.config.js b/webpack.config.js index a79aa82..d9f5b39 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,68 +1,68 @@ -const fs = require("fs"); -const path = require("path"); -const CopyPlugin = require("copy-webpack-plugin"); -const FileManagerPlugin = require("filemanager-webpack-plugin"); -const HtmlWebpackPlugin = require("html-webpack-plugin"); -const pkg = require("./package.json"); -const { CleanWebpackPlugin: CleanPlugin } = require("clean-webpack-plugin"); -const { EnvironmentPlugin } = require("webpack"); -const ManifestBuilder = require("./ManifestBuilder.js"); +const fs = require('fs'); +const path = require('path'); +const CopyPlugin = require('copy-webpack-plugin'); +const FileManagerPlugin = require('filemanager-webpack-plugin'); +const HtmlWebpackPlugin = require('html-webpack-plugin'); +const pkg = require('./package.json'); +const { CleanWebpackPlugin: CleanPlugin } = require('clean-webpack-plugin'); +const { EnvironmentPlugin } = require('webpack'); +const ManifestBuilder = require('./ManifestBuilder.js'); const ENV = { - SERVER_PROTOCOL: process.env.SERVER_PROTOCOL || "https", - SERVER_DOMAIN: process.env.SERVER_DOMAIN || "app.timecamp.com", + 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", - GOOGLE_ANALYTICS_ID: "UA-4525089-16", + MARKETING_PAGE_DOMAIN: 'www.timecamp.com', + GOOGLE_ANALYTICS_ID: 'UA-4525089-16' }; const CUSTOM_DOMAINS = { - 73065: "https://enterprise.timecamp.com/", + 73065: 'https://enterprise.timecamp.com/', }; module.exports = (env, argv) => { let version = pkg.version; let development = false; let production = false; - if (argv.mode === "production") { + if (argv.mode === 'production') { production = true; } - if (argv.mode === "development") { + if (argv.mode === 'development') { development = true; } console.log({ - development: development, - production: production, + 'development': development, + 'production': production }); return { - target: "web", + target: 'web', context: path.resolve(__dirname), - devtool: "source-map", + devtool: 'source-map', entry: { - ...entry("main", "scss", "/style/"), - ...entry("common"), - ...entry("ApiService"), - ...entry("PathService"), - ...entry("TimeFormatter"), - ...entry("popup"), - ...entry("background", "js", "", "", "background-2.0"), - ...entryThirdPartyScripts(), + ...entry('main', 'scss', '/style/'), + ...entry('common'), + ...entry('ApiService'), + ...entry('PathService'), + ...entry('TimeFormatter'), + ...entry('popup'), + ...entry('background', 'js', '', '', 'background-2.0'), + ...entryThirdPartyScripts() }, output: { - publicPath: "", - path: path.resolve(__dirname, "dist"), - filename: "[name].js", + publicPath: '', + path: path.resolve(__dirname, 'dist'), + filename: '[name].js', }, optimization: { - minimize: production, + minimize: production }, resolve: { - extensions: [".tsx", ".ts", ".js", ".jsx", ".scss"], + extensions: ['.tsx', '.ts', '.js', '.jsx', '.scss'] }, node: { - global: false, + global: false }, module: { @@ -70,21 +70,21 @@ module.exports = (env, argv) => { { test: /\.(gif)$/i, exclude: /node_modules/, - loader: "file-loader", + loader: 'file-loader', }, { test: /\.tsx?$/, exclude: /node_modules/, - use: "ts-loader", + use: 'ts-loader' }, { test: /\.jsx?$/, exclude: /node_modules/, - use: "babel-loader", + use: 'babel-loader', }, { test: /\.svg$/, - loader: "svg-url-loader", + loader: 'svg-url-loader' }, { test: /\.s[ac]ss$/i, @@ -96,23 +96,20 @@ module.exports = (env, argv) => { "css-loader", // Compiles Sass to CSS "sass-loader", - ], + ] }, { test: /main\.scss/, exclude: /node_modules/, use: [ { - loader: "file-loader", - options: { - outputPath: "plugin/scripts-2.0/style/", - name: "[name].min.css", - }, + loader: 'file-loader', + options: { outputPath: 'plugin/scripts-2.0/style/', name: '[name].min.css'} }, - "sass-loader", - ], + "sass-loader" + ] }, - ], + ] }, plugins: [ new HtmlWebpackPlugin(), @@ -123,132 +120,122 @@ module.exports = (env, argv) => { DEBUG: development, CUSTOM_DOMAINS: CUSTOM_DOMAINS, MARKETING_PAGE_DOMAIN: ENV.MARKETING_PAGE_DOMAIN, - GOOGLE_ANALYTICS_ID: development ? "" : ENV.GOOGLE_ANALYTICS_ID, + GOOGLE_ANALYTICS_ID: development ? '' : ENV.GOOGLE_ANALYTICS_ID }), new CleanPlugin(), - new CopyPlugin( - { - patterns: [ - ...copy({ - from: "_locales/", - to: "_locales/", - }), - ...copy({ - from: "fonts/", - to: "fonts/", - }), - ...copy({ - from: "images/", - to: "images/", - }), - ...copy({ - from: "styles/", - to: "styles/", - }), - ...copy({ - from: "scripts/", - to: "../plugin/scripts/", - transform: transformOldConfig(), - }), - ...copy({ - from: "popup.html", - to: "../plugin/", - }), - { - from: "manifest.json", - to: "plugin/manifest.json", - transform: transformManifest(development), - }, - ], - }, - { copyUnmodified: true } - ), + new CopyPlugin({'patterns': [ + ...copy({ + from: '_locales/', + to: '_locales/' + }), + ...copy({ + from: 'fonts/', + to: 'fonts/' + }), + ...copy({ + from: 'images/', + to: 'images/' + }), + ...copy({ + from: 'styles/', + to: 'styles/' + }), + ...copy({ + from: 'scripts/', + to: '../plugin/scripts/', + transform: transformOldConfig() + }), + ...copy({ + from: 'popup.html', + to: '../plugin/' + }), + { + from: 'manifest.json', + to: 'plugin/manifest.json', + transform: transformManifest(development) + } + ]}, { copyUnmodified: true }), new FileManagerPlugin({ events: { onEnd: { ...(production && { - delete: ["dist/**/*.js.map"], - }), - }, - }, - }), - ].filter(Boolean), - }; -}; - -function entry(name, ext = "js", from = "", to = "", destName = name) { + delete: [ + 'dist/**/*.js.map' + ], + }) + } + } + }) + ].filter(Boolean) + };}; + +function entry ( + name, + ext = 'js', + from = '', + to = '', + destName = name +) { return { - [`plugin/scripts-2.0/${to}${destName}`]: `./scripts-2.0/${from}${name}.${ext}`, + [`plugin/scripts-2.0/${to}${destName}`]: `./scripts-2.0/${from}${name}.${ext}` }; } function entryThirdPartyScripts() { - const contentScriptFiles = fs.readdirSync("./scripts-2.0/third_party/"); + const contentScriptFiles = fs.readdirSync('./scripts-2.0/third_party/'); return contentScriptFiles.reduce((entries, file) => { - const name = file.replace(".js", ""); - return Object.assign( - entries, - entry(`${name}`, "js", "third_party/", "third_party/") - ); + const name = file.replace('.js', ''); + return Object.assign(entries, entry(`${name}`, 'js', 'third_party/', 'third_party/')); }, {}); } -function copy(o) { +function copy (o) { return [ { ...o, - to: `plugin/${o.to}`, - }, + to: `plugin/${o.to}` + } ]; } function transformOldConfig() { - return function (content, filePath) { - let PATH_SEPARATOR = process.platform === "win32" ? "\\" : "/"; + return function (content, filePath) { + let PATH_SEPARATOR = process.platform === 'win32' ? '\\' : '/'; - if (filePath.split(PATH_SEPARATOR).pop() === "config.js") { - let serverUrl = - 'var serverUrl="' + - ENV.SERVER_PROTOCOL + - "://" + - ENV.SERVER_DOMAIN + - "/" + - '";'; - let customDomain = - "var customDomain=" + JSON.stringify(CUSTOM_DOMAINS) + ";"; + if (filePath.split(PATH_SEPARATOR).pop() === 'config.js') { + let serverUrl = 'var serverUrl="' + ENV.SERVER_PROTOCOL + '://' + ENV.SERVER_DOMAIN + '/' + '";'; + let customDomain = 'var customDomain=' + JSON.stringify(CUSTOM_DOMAINS) + ';'; - return Buffer.from(serverUrl + customDomain + content.toString()); - } + return Buffer.from(serverUrl + customDomain + content.toString()); + } - return Buffer.from(content.toString()); - }; + return Buffer.from(content.toString()); + } } function transformManifest(isDebug = false) { return async function (content) { const manifest = JSON.parse(content.toString()); - - manifest.homepage_url = - ENV.SERVER_PROTOCOL + "://" + ENV.SERVER_DOMAIN + "/"; + manifest.homepage_url = ENV.SERVER_PROTOCOL + '://' + ENV.SERVER_DOMAIN + '/'; manifest.host_permissions = [ ...manifest.host_permissions, - ENV.SERVER_PROTOCOL + "://*." + ENV.SERVER_DOMAIN + "/*", + ENV.SERVER_PROTOCOL + '://*.' + ENV.SERVER_DOMAIN + '/*' ]; 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; - const browser = process.env.BROWSER === "firefox" ? "firefox" : "chrome"; - const browserSpecificManifestPath = `manifest.${browser}.json`; const file = await fs.promises.readFile(browserSpecificManifestPath);