From 10f044a918d11863fc4c3cb4be0dabce7b87ecf2 Mon Sep 17 00:00:00 2001 From: Yaroslav Date: Thu, 13 Jun 2024 22:16:47 +0200 Subject: [PATCH] Chromium build (init) --- .gitignore | 1 + Makefile | 12 +++ assets/_locales/en/messages.json | 11 ++ assets/chromium/_locales | 1 + assets/chromium/icons | 1 + bin/manifest.ts | 17 ++++ package.json | 4 + src/manifest.spec.ts | 167 +++++++++++++++++++++++++++++++ src/manifest.ts | 91 +++++++++++++++++ src/service_worker.ts | 44 ++++++++ tsconfig.json | 29 ++++++ 11 files changed, 378 insertions(+) create mode 100644 assets/_locales/en/messages.json create mode 120000 assets/chromium/_locales create mode 120000 assets/chromium/icons create mode 100644 bin/manifest.ts create mode 100644 src/manifest.spec.ts create mode 100644 src/manifest.ts create mode 100644 src/service_worker.ts create mode 100644 tsconfig.json diff --git a/.gitignore b/.gitignore index 1779972..31f89f3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ node_modules/ **/*.mmdb **/*.d.?ts +assets/chromium/ lib/**/index.mjs diff --git a/Makefile b/Makefile index 8240cce..d95a39b 100644 --- a/Makefile +++ b/Makefile @@ -20,6 +20,18 @@ assets/public/maxmind/GeoLite2-Country.mmdb : curl -vL "https://download.maxmind.com/app/geoip_download?edition_id=GeoLite2-Country&license_key=${MAXMIND_LICENSE_KEY}&suffix=tar.gz" | tar --strip-components 1 -xzv -C `dirname $@` ls -lAh `dirname $@` +assets/chromium/manifest.json : package.json + mkdir -p `dirname $@` + bun bin/manifest.ts chromium > $@ + +assets/chromium/service_worker.js : src/service_worker.ts + mkdir -p `dirname $@` + bun build --entrypoints src/service_worker.ts --outdir assets/chromium + +.PHONY : clean +clean : + -rm assets/chromium/{manifest.json,service_worker.js} + .PHONY : distclean distclean : -rm assets/public/maxmind/GeoLite2-Country.mmdb diff --git a/assets/_locales/en/messages.json b/assets/_locales/en/messages.json new file mode 100644 index 0000000..9a55aef --- /dev/null +++ b/assets/_locales/en/messages.json @@ -0,0 +1,11 @@ +{ + "ext_description": { + "message": "Capture The Flag — browser extension." + }, + "ext_name": { + "message": "Capture The Flag" + }, + "ext_short_name": { + "message": "CTF" + } +} diff --git a/assets/chromium/_locales b/assets/chromium/_locales new file mode 120000 index 0000000..dcdefe5 --- /dev/null +++ b/assets/chromium/_locales @@ -0,0 +1 @@ +../_locales \ No newline at end of file diff --git a/assets/chromium/icons b/assets/chromium/icons new file mode 120000 index 0000000..3cfd4cb --- /dev/null +++ b/assets/chromium/icons @@ -0,0 +1 @@ +../icons \ No newline at end of file diff --git a/bin/manifest.ts b/bin/manifest.ts new file mode 100644 index 0000000..479442c --- /dev/null +++ b/bin/manifest.ts @@ -0,0 +1,17 @@ +import { argv } from 'node:process'; + +import _ from 'lodash-es'; + +import pkg from '../package.json' with { type: 'json' }; +import Manifest from '../src/manifest.ts'; + +if (import.meta.main) { + void main(); +} + +function main(args = argv.slice(2)) { + const manifest = new Manifest(process.env, pkg); + const target = _.first(args) as 'firefox' | 'chromium'; + + console.log(manifest.render(target)); +} diff --git a/package.json b/package.json index 5ab7266..c54a438 100644 --- a/package.json +++ b/package.json @@ -6,14 +6,18 @@ "license": "MPL-2.0", "type": "module", "scripts": { + "t": "bun test --watch", "postinstall": "make lib/maxmind" }, "dependencies": { "buffer": "^6.0.3", + "lodash-es": "^4.17.21", "mmdb-lib": "^2.1.1" }, "devDependencies": { "@types/bun": "latest", + "@types/lodash-es": "^4.17.12", + "chrome-types": "latest", "eslint-config-nilfalse": "github:nilfalse/eslint", "prettier": "^3.3", "webpack-cli": "^5.1.4" diff --git a/src/manifest.spec.ts b/src/manifest.spec.ts new file mode 100644 index 0000000..8ab04e9 --- /dev/null +++ b/src/manifest.spec.ts @@ -0,0 +1,167 @@ +import { describe, expect, it } from 'bun:test'; + +import Manifest from './manifest'; + +describe('manifest', () => { + describe('Firefox', () => { + it('should create manifest v2', async () => { + const manifest = new Manifest( + {}, + { author: 'nilfalse.com', version: '1.0.0' }, + ); + + expect(manifest.render('firefox').split('\n')).toStrictEqual([ + '{', + ' "manifest_version": 2,', + ' "name": "__MSG_ext_name__",', + ' "short_name": "__MSG_ext_short_name__",', + ' "version": "1.0.0",', + ' "default_locale": "en",', + ' "description": "__MSG_ext_description__",', + ' "icons": {', + ' "32": "icons/icon_32px.png",', + ' "48": "icons/icon_48px.png",', + ' "128": "icons/icon_128px.png",', + ' "256": "icons/icon_256px.png",', + ' "512": "icons/icon_512px.png"', + ' },', + ' "page_action": {', + ' "default_icon": {', + ' "32": "icons/icon_32px.png",', + ' "48": "icons/icon_48px.png",', + ' "128": "icons/icon_128px.png",', + ' "256": "icons/icon_256px.png",', + ' "512": "icons/icon_512px.png"', + ' },', + ' "default_popup": "popup.html",', + ' "show_matches": [', + ' ""', + ' ]', + ' },', + ' "background": {},', + ' "permissions": [', + ' "dns",', + ' "webRequest",', + ' "storage"', + ' ],', + ' "host_permissions": [', + ' ""', + ' ],', + ' "author": "nilfalse.com",', + ' "browser_specific_settings": {', + ' "gecko": {', + ' "id": "@ctf"', + ' }', + ' }', + '}', + ]); + }); + + it('should use commit hash for version when it is available', async () => { + const manifest = new Manifest( + { GITHUB_SHA: '666e666' }, + { author: 'nilfalse.com', version: '1.0.0' }, + ); + + expect(manifest.render('firefox').split('\n')).toStrictEqual([ + '{', + ' "manifest_version": 2,', + ' "name": "__MSG_ext_name__",', + ' "short_name": "__MSG_ext_short_name__",', + ' "version": "1.0.0",', + ' "version_name": "1.0.0 (666e666)",', + ' "default_locale": "en",', + ' "description": "__MSG_ext_description__",', + ' "icons": {', + ' "32": "icons/icon_32px.png",', + ' "48": "icons/icon_48px.png",', + ' "128": "icons/icon_128px.png",', + ' "256": "icons/icon_256px.png",', + ' "512": "icons/icon_512px.png"', + ' },', + ' "page_action": {', + ' "default_icon": {', + ' "32": "icons/icon_32px.png",', + ' "48": "icons/icon_48px.png",', + ' "128": "icons/icon_128px.png",', + ' "256": "icons/icon_256px.png",', + ' "512": "icons/icon_512px.png"', + ' },', + ' "default_popup": "popup.html",', + ' "show_matches": [', + ' ""', + ' ]', + ' },', + ' "background": {},', + ' "permissions": [', + ' "dns",', + ' "webRequest",', + ' "storage"', + ' ],', + ' "host_permissions": [', + ' ""', + ' ],', + ' "author": "nilfalse.com",', + ' "browser_specific_settings": {', + ' "gecko": {', + ' "id": "@ctf"', + ' }', + ' }', + '}', + ]); + }); + }); + + describe('Chromium', () => { + it('should create manifest v3', async () => { + const manifest = new Manifest( + { GITHUB_SHA: '666e666' }, + { author: 'nilfalse.com', version: '1.0.0' }, + ); + + expect(manifest.render('chromium').split('\n')).toStrictEqual([ + '{', + ' "manifest_version": 3,', + ' "name": "__MSG_ext_name__",', + ' "short_name": "__MSG_ext_short_name__",', + ' "version": "1.0.0",', + ' "version_name": "1.0.0 (666e666)",', + ' "default_locale": "en",', + ' "description": "__MSG_ext_description__",', + ' "icons": {', + ' "32": "icons/icon_32px.png",', + ' "48": "icons/icon_48px.png",', + ' "128": "icons/icon_128px.png",', + ' "256": "icons/icon_256px.png",', + ' "512": "icons/icon_512px.png"', + ' },', + ' "action": {', + ' "default_icon": {', + ' "32": "icons/icon_32px.png",', + ' "48": "icons/icon_48px.png",', + ' "128": "icons/icon_128px.png",', + ' "256": "icons/icon_256px.png",', + ' "512": "icons/icon_512px.png"', + ' },', + ' "default_popup": "popup.html",', + ' "show_matches": [', + ' ""', + ' ]', + ' },', + ' "background": {', + ' "service_worker": "service_worker.js"', + ' },', + ' "permissions": [', + ' "webRequest",', + ' "storage",', + ' "offscreen"', + ' ],', + ' "host_permissions": [', + ' ""', + ' ],', + ' "author": "nilfalse.com"', + '}', + ]); + }); + }); +}); diff --git a/src/manifest.ts b/src/manifest.ts new file mode 100644 index 0000000..98e995a --- /dev/null +++ b/src/manifest.ts @@ -0,0 +1,91 @@ +import _ from 'lodash-es'; + +export default class Manifest { + constructor( + private readonly env: Record, + private readonly pkg: Record, + ) {} + + render(target: 'firefox' | 'chromium') { + const { GITHUB_SHA } = this.env; + const { author, version } = this.pkg; + + const icons = { + 32: 'icons/icon_32px.png', + 48: 'icons/icon_48px.png', + 128: 'icons/icon_128px.png', + 256: 'icons/icon_256px.png', + 512: 'icons/icon_512px.png', + }; + + const action = { + default_icon: icons, + default_popup: 'popup.html', + show_matches: [''], + }; + + const manifest = { + manifest_version: undefined, + + name: '__MSG_ext_name__', + short_name: '__MSG_ext_short_name__', + version, + version_name: + GITHUB_SHA && this.pkg['version'] + ` (${GITHUB_SHA.substring(0, 8)})`, + + default_locale: 'en', + description: '__MSG_ext_description__', + + icons, + + action: undefined, + page_action: undefined, + + options_ui: undefined, + // { + // page: 'options.html', + // open_in_tab: false, + // }, + + background: { + background: undefined, + service_worker: undefined, + }, + + permissions: ['webRequest', 'storage'], + host_permissions: [''], + author, + + browser_specific_settings: undefined, + }; + + switch (target) { + case 'firefox': + _.set(manifest, 'manifest_version', 2); + _.set(manifest, 'page_action', action); + _.set(manifest, 'browser_specific_settings', { + gecko: { + id: '@ctf', + }, + }); + + manifest.permissions.unshift('dns'); + + break; + case 'chromium': + _.set(manifest, 'manifest_version', 3); + _.set(manifest, 'background.service_worker', 'service_worker.js'); + _.set(manifest, 'action', action); + + manifest.permissions.push('offscreen'); + + break; + default: + throw new Error( + `Unknown manifest: ${target}, expected 'firefox' or 'chromium'`, + ); + } + + return JSON.stringify(manifest, null, 2); + } +} diff --git a/src/service_worker.ts b/src/service_worker.ts new file mode 100644 index 0000000..e86f844 --- /dev/null +++ b/src/service_worker.ts @@ -0,0 +1,44 @@ +/// +/// + +import * as maxmind from '../lib/maxmind/index.mjs'; + +chrome.runtime.onInstalled.addListener((details) => { + console.log('Chrome Installing', details.reason); + + if (details.reason !== 'install' && details.reason !== 'update') { + return; + } +}); + +const MAXMIND_URL = 'https://ctf.pages.dev/maxmind/GeoLite2-Country.mmdb'; + +self.addEventListener('activate', (event) => { + console.log('Activating'); + + event.waitUntil( + caches + .match(MAXMIND_URL) + .then( + (r) => + r || + caches + .open('v1') + .then((cache) => cache.addAll([MAXMIND_URL])) + .then(() => caches.match(MAXMIND_URL)), + ) + .then((r) => { + console.log(r); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + return r!; + }) + .then(maxmind.init) + .then((reader) => { + console.log('Reader', reader); + + console.log(reader.get('185.51.76.136')); + }), + ); +}); + +declare const self: ServiceWorkerGlobalScope; diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..8db307c --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,29 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + + "compilerOptions": { + // Enable latest features + "target": "ESNext", + "module": "ESNext", + "moduleDetection": "force", + "lib": ["ESNext", "DOM", "WebWorker"], + "jsx": "react-jsx", + "allowJs": true, + + // Bundler mode + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "noEmit": true, + + // Best practices + "strict": true, + "skipLibCheck": true, + "noFallthroughCasesInSwitch": true, + + // Some stricter flags (disabled by default) + "noUnusedLocals": true, + "noUnusedParameters": true, + "noPropertyAccessFromIndexSignature": true + } +}