From 46254730bd5ff98af2c9b2d976c8c7250734e6b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomek=20W=C3=B3jcik?= Date: Mon, 15 Sep 2025 16:28:17 +0000 Subject: [PATCH] BTHLABS-52: Firefox Desktop Extension --- .../hotpocket_backend/apps/ui/constants.py | 1 + .../apps/ui/templatetags/ui.py | 15 ++--- services/extension/.gitignore | 1 + services/extension/package.json | 5 +- services/extension/rollup.config.js | 6 ++ services/extension/src/background/firefox.js | 6 ++ services/extension/src/content/firefox.js | 6 ++ services/extension/src/manifest/firefox.json | 27 +++++++++ services/extension/tasks.py | 55 ++++++++++++++----- .../hotpocket_common/constants/accounts.py | 1 + .../soa/hotpocket_soa/dto/accounts.py | 3 + 11 files changed, 103 insertions(+), 23 deletions(-) create mode 100644 services/extension/src/background/firefox.js create mode 100644 services/extension/src/content/firefox.js create mode 100644 services/extension/src/manifest/firefox.json diff --git a/services/backend/hotpocket_backend/apps/ui/constants.py b/services/backend/hotpocket_backend/apps/ui/constants.py index 206b1aa..b4994bb 100644 --- a/services/backend/hotpocket_backend/apps/ui/constants.py +++ b/services/backend/hotpocket_backend/apps/ui/constants.py @@ -22,3 +22,4 @@ class StarUnstarAssociationViewMode(enum.Enum): class UIAccessTokenOriginApp(enum.Enum): SAFARI_WEB_EXTENSION = _('Safari Web Extension') CHROME_EXTENSION = _('Chrome Extension') + FIREFOX_EXTENSION = _('Firefox Extension') diff --git a/services/backend/hotpocket_backend/apps/ui/templatetags/ui.py b/services/backend/hotpocket_backend/apps/ui/templatetags/ui.py index 392b5b8..6d52976 100644 --- a/services/backend/hotpocket_backend/apps/ui/templatetags/ui.py +++ b/services/backend/hotpocket_backend/apps/ui/templatetags/ui.py @@ -132,14 +132,15 @@ def render_access_token_app(access_token: AccessTokenOut) -> str: variant = 'secondary' origin_app = access_token.get_origin_app() - match origin_app: - case AccessTokenOriginApp.SAFARI_WEB_EXTENSION: - app = UIAccessTokenOriginApp[origin_app.value].value - variant = 'info' - case AccessTokenOriginApp.CHROME_EXTENSION: - app = UIAccessTokenOriginApp[origin_app.value].value - variant = 'info' + extension_origin_apps = ( + AccessTokenOriginApp.SAFARI_WEB_EXTENSION, + AccessTokenOriginApp.CHROME_EXTENSION, + AccessTokenOriginApp.FIREFOX_EXTENSION, + ) + if origin_app in extension_origin_apps: + app = UIAccessTokenOriginApp[origin_app.value].value + variant = 'info' return format_html( '{}', diff --git a/services/extension/.gitignore b/services/extension/.gitignore index a9ef9e1..c52a6f3 100644 --- a/services/extension/.gitignore +++ b/services/extension/.gitignore @@ -1,3 +1,4 @@ node_modules/ dist/ +secrets/*.json secrets/*.pem diff --git a/services/extension/package.json b/services/extension/package.json index 04f0aa5..58b28e2 100644 --- a/services/extension/package.json +++ b/services/extension/package.json @@ -15,7 +15,10 @@ "watch:safari": "HOTPOCKET_EXTENSION_TARGET=safari npx rollup -c rollup.config.js -w", "build:chrome": "NODE_ENV=production HOTPOCKET_EXTENSION_TARGET=chrome npx rollup -c rollup.config.js", "dev:chrome": "HOTPOCKET_EXTENSION_TARGET=chrome npx rollup -c rollup.config.js", - "watch:chrome": "HOTPOCKET_EXTENSION_TARGET=chrome npx rollup -c rollup.config.js -w" + "watch:chrome": "HOTPOCKET_EXTENSION_TARGET=chrome npx rollup -c rollup.config.js -w", + "build:firefox": "NODE_ENV=production HOTPOCKET_EXTENSION_TARGET=firefox npx rollup -c rollup.config.js", + "dev:firefox": "HOTPOCKET_EXTENSION_TARGET=firefox npx rollup -c rollup.config.js", + "watch:firefox": "HOTPOCKET_EXTENSION_TARGET=firefox npx rollup -c rollup.config.js -w" }, "devDependencies": { "@eslint/js": "9.33.0", diff --git a/services/extension/rollup.config.js b/services/extension/rollup.config.js index cc2377b..95ea0b3 100644 --- a/services/extension/rollup.config.js +++ b/services/extension/rollup.config.js @@ -5,6 +5,7 @@ import {string} from 'rollup-plugin-string'; import packageJSON from './package.json' with {type: 'json'}; import manifestChrome from './src/manifest/chrome.json' with {type: 'json'}; import manifestCommon from './src/manifest/common.json' with {type: 'json'}; +import manifestFirefox from './src/manifest/firefox.json' with {type: 'json'}; import manifestSafari from './src/manifest/safari.json' with {type: 'json'}; const BANNER = `/*! @@ -68,6 +69,11 @@ const manifestJsonOutputPlugin = () => { ...result, ...manifestChrome, }; + } else if (TARGET == 'firefox') { + result = { + ...result, + ...manifestFirefox, + }; } result.version = packageJSON.version; diff --git a/services/extension/src/background/firefox.js b/services/extension/src/background/firefox.js new file mode 100644 index 0000000..463b58b --- /dev/null +++ b/services/extension/src/background/firefox.js @@ -0,0 +1,6 @@ +import main from './main'; + +main({ + platform: 'Firefox', + api: browser, +}); diff --git a/services/extension/src/content/firefox.js b/services/extension/src/content/firefox.js new file mode 100644 index 0000000..463b58b --- /dev/null +++ b/services/extension/src/content/firefox.js @@ -0,0 +1,6 @@ +import main from './main'; + +main({ + platform: 'Firefox', + api: browser, +}); diff --git a/services/extension/src/manifest/firefox.json b/services/extension/src/manifest/firefox.json new file mode 100644 index 0000000..ef916fe --- /dev/null +++ b/services/extension/src/manifest/firefox.json @@ -0,0 +1,27 @@ +{ + "action": { + "default_title": "__MSG_extension_name__", + "default_icon": { + "16": "images/toolbar-icon-16.png", + "32": "images/toolbar-icon-32.png" + } + }, + "background": { + "scripts": [ + "background-bundle.js" + ], + "type": "module" + }, + "browser_specific_settings": { + "gecko": { + "id": "@Extension.HotPocket.BTHLabs", + "strict_min_version": "142.0", + "data_collection_permissions": { + "required": [ + "websiteActivity", + "technicalAndInteraction" + ] + } + } + } +} diff --git a/services/extension/tasks.py b/services/extension/tasks.py index e7dbf01..5e05f32 100644 --- a/services/extension/tasks.py +++ b/services/extension/tasks.py @@ -3,7 +3,9 @@ from __future__ import annotations -from invoke import task +import json + +from invoke import Context, task from invoke.exceptions import UnexpectedExit from hotpocket_workspace_tools import get_workspace_mode @@ -12,18 +14,18 @@ WORKSPACE_MODE = get_workspace_mode() @task -def clean(ctx): +def clean(ctx: Context): ctx.run('rm -rf dist/') @task -def test(ctx): +def test(ctx: Context): # ctx.run('pytest -v --disable-warnings') print('NOOP') @task -def flake8(ctx): +def flake8(ctx: Context): ctx.run('flake8') @@ -45,12 +47,12 @@ def isort(ctx, check=False, diff=False): @task -def eslint(ctx): +def eslint(ctx: Context): ctx.run('yarn run eslint') @task -def lint(ctx): +def lint(ctx: Context): ihazsuccess = True try: @@ -73,17 +75,17 @@ def lint(ctx): @task -def typecheck(ctx): +def typecheck(ctx: Context): ctx.run('mypy .') @task -def django_shell(ctx): +def django_shell(ctx: Context): raise NotImplementedError() @task -def ci(ctx): +def ci(ctx: Context): ihazsuccess = True ci_tasks = [test, lint, typecheck] @@ -98,35 +100,58 @@ def ci(ctx): @task -def setup(ctx): +def setup(ctx: Context): print('NOOP') @task -def start_web(ctx): +def start_web(ctx: Context): raise NotImplementedError() @task -def start_safari(ctx): +def start_safari(ctx: Context): ctx.run('yarn watch:safari') @task(pre=[clean]) -def start_chrome(ctx): +def start_chrome(ctx: Context): ctx.run('yarn watch:chrome') +@task(pre=[clean]) +def start_firefox(ctx: Context): + ctx.run('yarn watch:firefox') + + @task -def build_safari(ctx): +def build_safari(ctx: Context): ctx.run('yarn build:safari') @task(pre=[clean]) -def build_chrome(ctx): +def build_chrome(ctx: Context): ctx.run('yarn build:chrome') ctx.run(' '.join([ r'/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome', '--pack-extension=dist/chrome-production/', '--pack-extension-key=secrets/chrome.pem', ])) + + +@task(pre=[clean]) +def build_firefox(ctx: Context): + ctx.run('yarn build:firefox') + + firefox_secrets = None + with open('secrets/firefox.json', 'r', encoding='utf-8') as firefox_secrets_f: + firefox_secrets = json.load(firefox_secrets_f) + + with ctx.cd('dist/firefox-production'): + ctx.run(' '.join([ + 'web-ext', + 'sign', + '--channel=unlisted', + f'--api-key={firefox_secrets["api_key"]}', + f'--api-secret={firefox_secrets["api_secret"]}', + ])) diff --git a/services/packages/common/hotpocket_common/constants/accounts.py b/services/packages/common/hotpocket_common/constants/accounts.py index cbd7ee5..c919265 100644 --- a/services/packages/common/hotpocket_common/constants/accounts.py +++ b/services/packages/common/hotpocket_common/constants/accounts.py @@ -8,3 +8,4 @@ class AccessTokenOriginApp(enum.Enum): UNKNOWN = 'UNKNOWN' SAFARI_WEB_EXTENSION = 'SAFARI_WEB_EXTENSION' CHROME_EXTENSION = 'CHROME_EXTENSION' + FIREFOX_EXTENSION = 'FIREFOX_EXTENSION' diff --git a/services/packages/soa/hotpocket_soa/dto/accounts.py b/services/packages/soa/hotpocket_soa/dto/accounts.py index 3aef33a..58f3437 100644 --- a/services/packages/soa/hotpocket_soa/dto/accounts.py +++ b/services/packages/soa/hotpocket_soa/dto/accounts.py @@ -29,6 +29,9 @@ class AccessTokenOut(ModelOut): case 'chrome-extension': return AccessTokenOriginApp.CHROME_EXTENSION + case 'moz-extension': + return AccessTokenOriginApp.FIREFOX_EXTENSION + case _: return None