diff --git a/chrome/manifest.base.json b/chrome/manifest.base.json new file mode 100644 index 0000000..3ce5e9e --- /dev/null +++ b/chrome/manifest.base.json @@ -0,0 +1,35 @@ +{ + "version": "0.0.10", + "name": "Chess Browser Extension", + "manifest_version": 2, + "description": "Customize your Chess.com Experience", + "browser_action": { + "default_title": "Chess.com Browser Extension", + "default_popup": "popup.html" + }, + "icons": { + "16": "img/icon-16.png", + "48": "img/icon-48.png", + "128": "img/icon-128.png" + }, + "web_accesible_resources": [ + "inject.html", + "content.js" + ], + "content_scripts": [ + { + "matches": ["https://www.chess.com/*"], + "js": ["inject.js"], + "all_frames": true + } + ], + "background": { + "page": "background.html" + }, + "permissions": [ + "contextMenus", + "tabs", + "storage", + "https://www.chess.com/*" + ] +} diff --git a/chrome/manifest.build.json b/chrome/manifest.build.json index 4c87d06..a52eacc 100644 --- a/chrome/manifest.build.json +++ b/chrome/manifest.build.json @@ -1,31 +1,40 @@ { - "version": "0.0.10", - "name": "Chess Browser Extension", - "manifest_version": 2, - "description": "Customize your Chess.com Experience", - "browser_action": { - "default_title": "Chess.com Browser Extension", - "default_popup": "popup.html" - }, - "icons": { - "16": "img/icon-16.png", - "48": "img/icon-48.png", - "128": "img/icon-128.png" - }, - "web_accessible_resources": [ - "inject.html", - "content.js" - ], - "content_scripts": [ - { - "matches": ["https://www.chess.com/*"], - "js": ["inject.js"], - "all_frames": true - } - ], - "background": { - "page": "background.html" - }, - "permissions": [ "contextMenus", "tabs", "storage", "https://www.chess.com/*" ], - "content_security_policy": "default-src 'self'; script-src 'self'; connect-src https://www.chess.com; style-src * 'unsafe-inline'; img-src 'self' https://images.chesscomfiles.com data:; font-src 'self' data:;" + "version": "0.0.10", + "name": "Chess Browser Extension", + "manifest_version": 2, + "description": "Customize your Chess.com Experience", + "browser_action": { + "default_title": "Chess.com Browser Extension", + "default_popup": "popup.html" + }, + "icons": { + "16": "img/icon-16.png", + "48": "img/icon-48.png", + "128": "img/icon-128.png" + }, + "web_accesible_resources": [ + "inject.html", + "content.js" + ], + "content_scripts": [ + { + "matches": [ + "https://www.chess.com/*" + ], + "js": [ + "inject.js" + ], + "all_frames": true + } + ], + "background": { + "page": "background.html" + }, + "permissions": [ + "contextMenus", + "tabs", + "storage", + "https://www.chess.com/*" + ], + "content_security_policy": "default-src 'self'; script-src 'self'; connect-src https://www.chess.com; style-src * 'unsafe-inline'; img-src 'self' https://images.chesscomfiles.com data:; font-src 'self' data:;" } diff --git a/chrome/manifest.buildFirefox.json b/chrome/manifest.buildFirefox.json index 9c0e690..5e5e68d 100644 --- a/chrome/manifest.buildFirefox.json +++ b/chrome/manifest.buildFirefox.json @@ -1,39 +1,48 @@ { - "version": "0.0.10", - "name": "Chess Browser Extension", - "manifest_version": 2, - "description": "Customize your Chess.com Experience", - "browser_action": { - "default_title": "Chess.com Browser Extension", - "default_popup": "popup.html", - "default_icon": { - "48": "img/icon-48.png" + "version": "0.0.10", + "name": "Chess Browser Extension", + "manifest_version": 2, + "description": "Customize your Chess.com Experience", + "browser_action": { + "default_title": "Chess.com Browser Extension", + "default_popup": "popup.html", + "default_icon": { + "48": "img/icon-48.png" + } + }, + "icons": { + "16": "img/icon-16.png", + "48": "img/icon-48.png", + "128": "img/icon-128.png" + }, + "web_accesible_resources": [ + "inject.html", + "content.js" + ], + "content_scripts": [ + { + "matches": [ + "https://www.chess.com/*" + ], + "js": [ + "inject.js" + ], + "all_frames": true + } + ], + "background": { + "page": "background.html" + }, + "permissions": [ + "contextMenus", + "tabs", + "storage", + "https://www.chess.com/*" + ], + "content_security_policy": "default-src 'self'; script-src 'self'; connect-src https://www.chess.com; style-src * 'unsafe-inline'; img-src 'self' https://images.chesscomfiles.com data:; font-src 'self' data:;", + "applications": { + "gecko": { + "id": "chesscom@gmail.com" + } } - }, - "icons": { - "16": "img/icon-16.png", - "48": "img/icon-48.png", - "128": "img/icon-128.png" - }, - "applications": { - "gecko": { - "id": "chesscom@gmail.com" - } - }, - "web_accessible_resources": [ - "inject.html", - "content.js" - ], - "content_scripts": [ - { - "matches": ["https://www.chess.com/*"], - "js": ["inject.js"], - "all_frames": true - } - ], - "background": { - "page": "background.html" - }, - "permissions": [ "contextMenus", "tabs", "storage", "https://www.chess.com/*" ], - "content_security_policy": "default-src 'self'; script-src 'self'; connect-src https://www.chess.com; style-src * 'unsafe-inline'; img-src 'self' https://images.chesscomfiles.com data:; font-src 'self' data:;" } diff --git a/chrome/manifest.dev.json b/chrome/manifest.dev.json index 177934d..091d0f9 100644 --- a/chrome/manifest.dev.json +++ b/chrome/manifest.dev.json @@ -1,41 +1,44 @@ { - "version": "0.0.10", - "name": "Chess Browser Extension", - "manifest_version": 2, - "description": "Customize your Chess.com Experience", - "browser_action": { - "default_title": "Chess.com Browser Extension", - "default_popup": "popup.html" - }, - "icons": { - "128": "img/icon-128.png" - }, - "web_accessible_resources": [ - "inject.html", - "img/*", - "fonts/*", - "content.js" - ], - "content_scripts": [ - { - "matches": [ + "version": "0.0.10", + "name": "Chess Browser Extension", + "manifest_version": 2, + "description": "Customize your Chess.com Experience", + "browser_action": { + "default_title": "Chess.com Browser Extension", + "default_popup": "popup.html" + }, + "icons": { + "16": "img/icon-16.png", + "48": "img/icon-48.png", + "128": "img/icon-128.png" + }, + "web_accesible_resources": [ + "inject.html", + "content.js" + ], + "content_scripts": [ + { + "matches": [ + "https://www.chess.com/*" + ], + "js": [ + "inject.js" + ], + "all_frames": true + } + ], + "background": { + "page": "background.html" + }, + "permissions": [ + "contextMenus", + "tabs", + "storage", "https://www.chess.com/*" - ], - "js": [ - "inject.js" - ], - "all_frames": true - } - ], - "background": { - "page": "background.html" - }, - "permissions": [ - "contextMenus", - "management", - "tabs", - "storage", - "https://www.chess.com/*" - ], - "content_security_policy": "default-src 'self'; script-src 'self' https://localhost:3000 'unsafe-eval'; connect-src https://localhost:3000 https://www.chess.com; style-src * 'unsafe-inline' 'self' blob:; img-src 'self' https://images.chesscomfiles.com data:; font-src 'self' data:;" + ], + "web_accessible_resources": [ + "img/*", + "fonts/*" + ], + "content_security_policy": "default-src 'self'; script-src 'self' https://localhost:3000 'unsafe-eval'; object-src 'self'; connect-src https://localhost:3000 https://www.chess.com; style-src 'self' blob:; img-src 'self' https://images.chesscomfiles.com data:; font-src 'self' data:;" } diff --git a/chrome/manifest.devFirefox.json b/chrome/manifest.devFirefox.json index 20268c6..45adf60 100644 --- a/chrome/manifest.devFirefox.json +++ b/chrome/manifest.devFirefox.json @@ -1,49 +1,52 @@ { - "version": "0.0.10", - "name": "Chess Browser Extension", - "manifest_version": 2, - "description": "Customize your Chess.com Experience", - "browser_action": { - "default_title": "Chess.com Browser Extension", - "default_popup": "popup.html", - "default_icon": { - "48": "img/icon-48.png" - } - }, - "icons": { - "128": "img/icon-128.png" - }, - "applications": { - "gecko": { - "id": "chesscom@gmail.com" - } - }, - "web_accessible_resources": [ - "inject.html", - "img/*", - "fonts/*", - "content.js" - ], - "content_scripts": [ - { - "matches": [ + "version": "0.0.10", + "name": "Chess Browser Extension", + "manifest_version": 2, + "description": "Customize your Chess.com Experience", + "browser_action": { + "default_title": "Chess.com Browser Extension", + "default_popup": "popup.html", + "default_icon": { + "48": "img/icon-48.png" + } + }, + "icons": { + "16": "img/icon-16.png", + "48": "img/icon-48.png", + "128": "img/icon-128.png" + }, + "web_accesible_resources": [ + "inject.html", + "content.js" + ], + "content_scripts": [ + { + "matches": [ + "https://www.chess.com/*" + ], + "js": [ + "inject.js" + ], + "all_frames": true + } + ], + "background": { + "page": "background.html" + }, + "permissions": [ + "contextMenus", + "tabs", + "storage", "https://www.chess.com/*" - ], - "js": [ - "inject.js" - ], - "all_frames": true + ], + "web_accessible_resources": [ + "img/*", + "fonts/*" + ], + "content_security_policy": "default-src 'self'; script-src 'self' https://localhost:3000 'unsafe-eval'; object-src 'self'; connect-src https://localhost:3000 https://www.chess.com; style-src 'self' blob:; img-src 'self' https://images.chesscomfiles.com data:; font-src 'self' data:;", + "applications": { + "gecko": { + "id": "chesscom@gmail.com" + } } - ], - "background": { - "page": "background.html" - }, - "permissions": [ - "contextMenus", - "management", - "tabs", - "storage", - "https://www.chess.com/*" - ], - "content_security_policy": "default-src 'self'; script-src 'self' https://localhost:3000 'unsafe-eval'; object-src 'self'; connect-src https://localhost:3000 https://www.chess.com; style-src 'self' blob:; img-src 'self' https://images.chesscomfiles.com data:; font-src 'self' data:;" } diff --git a/chrome/manifest.specific.chrome.json b/chrome/manifest.specific.chrome.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/chrome/manifest.specific.chrome.json @@ -0,0 +1 @@ +{} diff --git a/chrome/manifest.specific.dev.json b/chrome/manifest.specific.dev.json new file mode 100644 index 0000000..ba94fa4 --- /dev/null +++ b/chrome/manifest.specific.dev.json @@ -0,0 +1,7 @@ +{ + "web_accessible_resources": [ + "img/*", + "fonts/*" + ], + "content_security_policy": "default-src 'self'; script-src 'self' https://localhost:3000 'unsafe-eval'; object-src 'self'; connect-src https://localhost:3000 https://www.chess.com; style-src 'self' blob:; img-src 'self' https://images.chesscomfiles.com data:; font-src 'self' data:;" +} diff --git a/chrome/manifest.specific.firefox.json b/chrome/manifest.specific.firefox.json new file mode 100644 index 0000000..a1b49a5 --- /dev/null +++ b/chrome/manifest.specific.firefox.json @@ -0,0 +1,12 @@ +{ + "browser_action": { + "default_icon": { + "48": "img/icon-48.png" + } + }, + "applications": { + "gecko": { + "id": "chesscom@gmail.com" + } + } +} diff --git a/chrome/manifest.specific.prod.json b/chrome/manifest.specific.prod.json new file mode 100644 index 0000000..adeee2b --- /dev/null +++ b/chrome/manifest.specific.prod.json @@ -0,0 +1,3 @@ +{ + "content_security_policy": "default-src 'self'; script-src 'self'; connect-src https://www.chess.com; style-src * 'unsafe-inline'; img-src 'self' https://images.chesscomfiles.com data:; font-src 'self' data:;" +} diff --git a/scripts/build.js b/scripts/build.js index e475b57..69e6b13 100644 --- a/scripts/build.js +++ b/scripts/build.js @@ -1,4 +1,6 @@ +/* eslint-disable import/no-extraneous-dependencies */ const tasks = require('./tasks'); +const shell = require('shelljs'); tasks.replaceWebpack(); console.log('[Copy assets]'); @@ -7,4 +9,4 @@ tasks.copyAssets('build'); console.log('[Webpack Build]'); console.log('-'.repeat(80)); -exec('webpack --config webpack/prod.config.js --progress --profile --colors'); +shell.exec('webpack --config webpack/prod.config.js --progress --profile --colors'); diff --git a/scripts/buildFirefox.js b/scripts/buildFirefox.js index 38f1191..ff4e2b7 100644 --- a/scripts/buildFirefox.js +++ b/scripts/buildFirefox.js @@ -1,4 +1,6 @@ +/* eslint-disable import/no-extraneous-dependencies */ const tasks = require('./tasks'); +const shell = require('shelljs'); tasks.replaceWebpack(); console.log('[Copy assets]'); @@ -7,4 +9,4 @@ tasks.copyAssets('buildFirefox'); console.log('[Webpack Build]'); console.log('-'.repeat(80)); -exec('webpack --config webpack/prodFirefox.config.js --progress --profile --colors'); +shell.exec('webpack --config webpack/prodFirefox.config.js --progress --profile --colors'); diff --git a/scripts/tasks.js b/scripts/tasks.js index 4636d49..3006192 100644 --- a/scripts/tasks.js +++ b/scripts/tasks.js @@ -1,5 +1,7 @@ /* eslint import/no-extraneous-dependencies: 0 */ -require('shelljs/global'); +const shell = require('shelljs'); +const fs = require('fs'); +const _ = require('lodash'); exports.replaceWebpack = () => { const replaceTasks = [{ @@ -10,9 +12,128 @@ exports.replaceWebpack = () => { to: 'node_modules/webpack/hot/log-apply-result.js' }]; - replaceTasks.forEach(task => cp(task.from, task.to)); + replaceTasks.forEach(task => shell.cp(task.from, task.to)); }; + +// The following implementation of a merge is written purely to be bug +// free not to be optimized performance wise as this is not needed in this +// case as it's just a dev script and the manifest files are very small +// (relative to computation abilities) json files + +function isObject(candidate) { + return (typeof candidate) === 'object' && candidate !== null; +} + +function isArray(candidate) { + return candidate.constructor === Array; +} + +function mergeError(msg) { + throw new Error(`mergeManifests Error: ${msg}`); +} + +function mergeArrays(passedArrA, passedArrB) { + // Deep copy both arrays just to be safe + const arrA = JSON.parse(JSON.stringify(passedArrA)); + const arrB = JSON.parse(JSON.stringify(passedArrB)); + // We never recurse on arrays as we assume each object + // in an array is self-contained and would not be broken up + // So don't assume this when modularizing the manifest files + const ret = _.uniq(arrA.concat(arrB)); + // We use _.uniq which will remove duplicate strings and duplicate + // numbers but keep all objects as it would compare just their pointers + return ret; +} + +function mergeObjects(passedObjA, passedObjB, manifestTypeA, manifestTypeB) { + // Prepare error origin text dependent on whether or not + // objects or strings were passed for original manifest merge + let originErrorText = ''; + if ((typeof manifestTypeA) === 'string') { + originErrorText += ` in ${manifestTypeA}`; + } + if ((typeof manifestTypeB) === 'string') { + if (originErrorText) { + originErrorText += ` and ${manifestTypeB}`; + } else { + originErrorText += ` in ${manifestTypeB}`; + } + } + + // Deep copy both objects just to be safe + const objA = JSON.parse(JSON.stringify(passedObjA)); + const ret = JSON.parse(JSON.stringify(passedObjB)); + // Merge A into B + // Disabling eslint rule for code clarity, either key exists + // in both objects or it doesn't, 2 different cases with subcases + /* eslint-disable no-lonely-if */ + _.forEach(objA, (value, key) => { + if (!(key in ret)) { + ret[key] = value; + } else { + // Both objects contain the key + + // It's important we check for Array before Object as Array + // is a subset of Object + if (isArray(value)) { + if (!isArray(ret[key])) { + mergeError(`types of key values for key '${key}' \ +don't match when merging${originErrorText}`); + } + ret[key] = mergeArrays(value, ret[key]); + } else if (isObject(value)) { + if (!isObject(ret[key]) || isArray(ret[key])) { + mergeError(`types of key values for key '${key}' \ +don't match when merging${originErrorText}`); + } + ret[key] = mergeObjects(value, ret[key], manifestTypeA, manifestTypeB); + } else if (key === 'content_security_policy') { + // Special case for a special value + if ((typeof value) !== 'string' || (typeof ret[key]) !== 'string') { + mergeError(`value of content_security_policy was not a string${originErrorText}`); + } + + // We first check if there are any duplicate values + value.split(';').forEach(rule => { + const trimmed = rule.trim(); + const property = trimmed.split(' ')[0].trim(); + if (property && ret[key].indexOf(property) >= 0) { + mergeError(`Duplicate properties attempting to be \ +merged in content_security_policy${originErrorText}`); + } + }); + ret[key] += ` ${value}`; + } else { + mergeError(`Trying to merge non-objects for key '${key}' \ +in ${manifestTypeA} and ${manifestTypeB}`); + } + } + }); + /* eslint-enable no-lonely-if */ + return ret; +} + + +function mergeManifests(manifestTypeA, manifestTypeB) { + let jsonA; + let jsonB; + if ((typeof manifestTypeA) === 'string') { + jsonA = JSON.parse(fs.readFileSync(`${__dirname}/../chrome/manifest.${manifestTypeA}.json`)); + } else { + // Deep copy to be on the safe side + jsonA = JSON.parse(JSON.stringify(manifestTypeA)); + } + + if ((typeof manifestTypeB) === 'string') { + jsonB = JSON.parse(fs.readFileSync(`${__dirname}/../chrome/manifest.${manifestTypeB}.json`)); + } else { + // Deep copy to be on the safe side + jsonB = JSON.parse(JSON.stringify(manifestTypeB)); + } + return mergeObjects(jsonA, jsonB, manifestTypeA, manifestTypeB); +} + exports.copyAssets = type => { let env = type; @@ -20,10 +141,29 @@ exports.copyAssets = type => { env = 'prod'; } - rm('-rf', type); - mkdir(type); - cp(`chrome/manifest.${type}.json`, `${type}/manifest.json`); - cp('-R', 'chrome/assets/*', type); - cp('-R', 'chrome/*.js', type); - exec(`jade -O "{ env: '${env}' }" -o ${type} chrome/views/`); + // Rebuild modularized manifest + let outputManifest; + if (type.indexOf('build') >= 0) { + outputManifest = mergeManifests('specific.prod', 'base'); + } else { + outputManifest = mergeManifests('specific.dev', 'base'); + } + + if (type.indexOf('Firefox') >= 0) { + outputManifest = mergeManifests('specific.firefox', outputManifest); + } else { + outputManifest = mergeManifests('specific.chrome', outputManifest); + } + + shell.rm('-rf', type); + shell.mkdir(type); + // eslint-disable-next-line prefer-template + fs.writeFileSync(`${type}/manifest.json`, JSON.stringify(outputManifest, null, 4) + '\n'); + // The line just below is just temporary to test that we didn't modify the manifests + // and should be removed along with the files it creates before any PR is merged + // eslint-disable-next-line prefer-template + fs.writeFileSync(`chrome/manifest.${type}.json`, JSON.stringify(outputManifest, null, 4) + '\n'); + shell.cp('-R', 'chrome/assets/*', type); + shell.cp('-R', 'chrome/*.js', type); + shell.exec(`jade -O "{ env: '${env}' }" -o ${type} chrome/views/`); };