browser-extension-manager 1.6.0 → 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +268 -0
- package/README.md +4 -0
- package/dist/commands/install.js +2 -2
- package/dist/commands/setup.js +2 -1
- package/dist/defaults/.github/workflows/publish.yml +4 -1
- package/dist/lib/safe-install.js +13 -0
- package/docs/audit.md +67 -0
- package/docs/auth.md +147 -0
- package/docs/build-system.md +131 -0
- package/docs/cdp-debugging.md +45 -0
- package/docs/cli.md +70 -0
- package/docs/common-mistakes.md +10 -0
- package/docs/components.md +87 -0
- package/docs/css.md +91 -0
- package/docs/defaults.md +54 -0
- package/docs/environment-detection.md +79 -0
- package/docs/extension.md +94 -0
- package/docs/hooks.md +101 -0
- package/docs/icons.md +47 -0
- package/docs/logging.md +33 -0
- package/docs/managers.md +100 -0
- package/docs/offscreen.md +57 -0
- package/docs/publishing.md +175 -0
- package/docs/templating.md +66 -0
- package/docs/test-boot-layer.md +141 -0
- package/docs/test-framework.md +404 -0
- package/docs/themes.md +77 -0
- package/docs/translations.md +72 -0
- package/docs/xss-prevention.md +95 -0
- package/package.json +13 -9
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# Cross-Browser API Wrapper (`lib/extension.js`)
|
|
2
|
+
|
|
3
|
+
A singleton that normalizes the `chrome.*` / `browser.*` / `window.*` extension API surface so consumers write their extension once and it works on Chrome, Firefox, Edge, and other Chromium-based browsers.
|
|
4
|
+
|
|
5
|
+
## Import
|
|
6
|
+
|
|
7
|
+
```js
|
|
8
|
+
const ext = require('browser-extension-manager/lib/extension');
|
|
9
|
+
|
|
10
|
+
// or as a Manager property:
|
|
11
|
+
const Manager = new (require('browser-extension-manager/popup'));
|
|
12
|
+
await Manager.initialize();
|
|
13
|
+
const { extension } = Manager; // same singleton
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## How it works
|
|
17
|
+
|
|
18
|
+
For each known extension API, the wrapper tries in order:
|
|
19
|
+
1. `chrome.<api>` (Chrome, Edge, Opera, Brave)
|
|
20
|
+
2. `window.<api>` (some content-script contexts where chrome is exposed under window)
|
|
21
|
+
3. `browser.<api>` (Firefox)
|
|
22
|
+
4. `browser.extension.<api>` (legacy fallback)
|
|
23
|
+
|
|
24
|
+
The first one that resolves becomes the singleton's `<api>` property. Each lookup is wrapped in try/catch so unknown globals don't throw at module-load time — that's what makes the wrapper safe to import from Node contexts too (where none of these globals exist).
|
|
25
|
+
|
|
26
|
+
## Supported APIs
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
action, alarms, bookmarks, browsingData, browserAction,
|
|
30
|
+
certificateProvider, commands, contentSettings, contextMenus,
|
|
31
|
+
cookies, debugger, declarativeContent, declarativeNetRequest,
|
|
32
|
+
devtools, dns, documentScan, downloads, enterprise, events,
|
|
33
|
+
extension, extensionTypes, fileBrowserHandler, fileSystemProvider,
|
|
34
|
+
fontSettings, gcm, history, i18n, identity, idle, input, instanceID,
|
|
35
|
+
management, notifications, offscreen, omnibox, pageAction,
|
|
36
|
+
permissions, platformKeys, power, printerProvider, privacy, proxy,
|
|
37
|
+
runtime, scripting, search, sessions, sidePanel, storage, tabGroups,
|
|
38
|
+
tabs, topSites, tts, ttsEngine, userScripts, vpnProvider, wallpaper,
|
|
39
|
+
webNavigation, webRequest, windows
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
If a browser doesn't expose a given API, the property is `null` (not undefined), so consumers can branch on `if (extension.sidePanel) { ... }`.
|
|
43
|
+
|
|
44
|
+
## Usage
|
|
45
|
+
|
|
46
|
+
```js
|
|
47
|
+
const Manager = new (require('browser-extension-manager/popup'));
|
|
48
|
+
await Manager.initialize();
|
|
49
|
+
const { extension } = Manager;
|
|
50
|
+
|
|
51
|
+
// Works on Chrome, Firefox, Edge — identical call sites
|
|
52
|
+
extension.tabs.query({ active: true, currentWindow: true }, (tabs) => {
|
|
53
|
+
console.log('Active tab:', tabs[0]);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
extension.storage.get('key', (result) => { /* ... */ });
|
|
57
|
+
extension.runtime.sendMessage({ type: 'hello' });
|
|
58
|
+
extension.notifications.create({ /* ... */ });
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Storage normalization
|
|
62
|
+
|
|
63
|
+
The wrapper auto-resolves `storage` to `storage.sync` when available (preferred — synced across the user's Chrome sign-in), falling back to `storage.local` when sync isn't available (e.g. some Firefox MV2 contexts).
|
|
64
|
+
|
|
65
|
+
```js
|
|
66
|
+
extension.storage.set({ foo: 'bar' });
|
|
67
|
+
extension.storage.get('foo', (result) => console.log(result.foo));
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
If you specifically need local-only storage (per-machine, larger quota), use `chrome.storage.local.*` directly — bypass the wrapper.
|
|
71
|
+
|
|
72
|
+
## Node-safe by design
|
|
73
|
+
|
|
74
|
+
The wrapper imports cleanly from Node (build-time scripts, tests, gulp tasks) — every property is `null` in that context because none of the browser globals exist. This is what makes BXM's build-layer tests possible:
|
|
75
|
+
|
|
76
|
+
```js
|
|
77
|
+
// build-layer test (Node)
|
|
78
|
+
const ext = require('browser-extension-manager/lib/extension');
|
|
79
|
+
ctx.expect(ext.runtime).toBeNull(); // no chrome global in Node
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Why not `webextension-polyfill`?
|
|
83
|
+
|
|
84
|
+
`webextension-polyfill` shims Firefox's promise-returning APIs onto Chrome's callback-based APIs. Useful, but:
|
|
85
|
+
- Adds an npm dep that has to be loaded as a content-script before user code
|
|
86
|
+
- Doesn't address the case where APIs simply don't exist on a browser (e.g. `sidePanel` on Firefox)
|
|
87
|
+
- Slow boot in SW context
|
|
88
|
+
|
|
89
|
+
BXM's wrapper is simpler: detect what's there, expose null when it isn't, let user code branch. Combine with the promisification Chrome added in MV3 (callbacks return promises when omitted) for a polyfill-free async-friendly API.
|
|
90
|
+
|
|
91
|
+
## See also
|
|
92
|
+
|
|
93
|
+
- [managers.md](managers.md) — every Manager exposes `extension` after `initialize()`
|
|
94
|
+
- [components.md](components.md) — which API surface is available in which context
|
package/docs/hooks.md
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# Build Hooks
|
|
2
|
+
|
|
3
|
+
Two lifecycle hooks let consumers run custom logic during the build pipeline.
|
|
4
|
+
|
|
5
|
+
## Hook files
|
|
6
|
+
|
|
7
|
+
| Hook | Source | When |
|
|
8
|
+
|---|---|---|
|
|
9
|
+
| `build:pre` | `hooks/build:pre.js` (in consumer project) | Before packaging — after `dist/` is built but before `packaged/` is assembled |
|
|
10
|
+
| `build:post` | `hooks/build:post.js` | After packaging — `packaged/<browser>/raw/` and `.zip` exist |
|
|
11
|
+
|
|
12
|
+
Hooks are optional — `gulp/tasks/package.js` checks for the file's presence and runs it if present.
|
|
13
|
+
|
|
14
|
+
## Hook shape
|
|
15
|
+
|
|
16
|
+
```js
|
|
17
|
+
// hooks/build:pre.js
|
|
18
|
+
module.exports = async function (index) {
|
|
19
|
+
// index contains build info — package, manifest, config, paths
|
|
20
|
+
console.log('Pre-build hook running for', index.brand.name);
|
|
21
|
+
|
|
22
|
+
// Mutate files, generate assets, validate, anything you want.
|
|
23
|
+
// Return a Promise (or use async fn) to make the build wait.
|
|
24
|
+
};
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## The `index` argument
|
|
28
|
+
|
|
29
|
+
The hook receives a build-info object with:
|
|
30
|
+
|
|
31
|
+
- `index.package` — parsed `package.json`
|
|
32
|
+
- `index.manifest` — parsed `src/manifest.json` (JSON5)
|
|
33
|
+
- `index.config` — parsed `config/browser-extension-manager.json`
|
|
34
|
+
- `index.brand` — shorthand for `index.config.brand`
|
|
35
|
+
- `index.paths` — `{ root, src, dist, packaged }` absolute paths
|
|
36
|
+
- `index.env` — `'production'` or `'development'`
|
|
37
|
+
- `index.browsers` — array of browser targets being built (`['chromium', 'firefox', 'opera']`)
|
|
38
|
+
|
|
39
|
+
## Common uses
|
|
40
|
+
|
|
41
|
+
### Sync a CHANGELOG version into the manifest
|
|
42
|
+
|
|
43
|
+
```js
|
|
44
|
+
// hooks/build:pre.js
|
|
45
|
+
const fs = require('fs');
|
|
46
|
+
const path = require('path');
|
|
47
|
+
|
|
48
|
+
module.exports = async function (index) {
|
|
49
|
+
const changelog = fs.readFileSync(path.join(index.paths.root, 'CHANGELOG.md'), 'utf8');
|
|
50
|
+
const latestVersion = changelog.match(/## \[([\d.]+)\]/)?.[1];
|
|
51
|
+
if (latestVersion && latestVersion !== index.manifest.version) {
|
|
52
|
+
console.warn(`Manifest version (${index.manifest.version}) doesn't match CHANGELOG latest (${latestVersion})`);
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Inject a build timestamp
|
|
58
|
+
|
|
59
|
+
```js
|
|
60
|
+
// hooks/build:pre.js
|
|
61
|
+
const fs = require('fs');
|
|
62
|
+
const path = require('path');
|
|
63
|
+
|
|
64
|
+
module.exports = async function (index) {
|
|
65
|
+
const buildInfo = {
|
|
66
|
+
builtAt: new Date().toISOString(),
|
|
67
|
+
gitSha: require('child_process').execSync('git rev-parse HEAD').toString().trim(),
|
|
68
|
+
};
|
|
69
|
+
fs.writeFileSync(path.join(index.paths.dist, 'build-info.json'), JSON.stringify(buildInfo, null, 2));
|
|
70
|
+
};
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Trigger a post-publish webhook
|
|
74
|
+
|
|
75
|
+
```js
|
|
76
|
+
// hooks/build:post.js
|
|
77
|
+
module.exports = async function (index) {
|
|
78
|
+
if (process.env.BXM_IS_PUBLISH !== 'true') return; // only after real publish
|
|
79
|
+
await fetch('https://api.myservice.com/extension-released', {
|
|
80
|
+
method: 'POST',
|
|
81
|
+
body: JSON.stringify({ version: index.manifest.version }),
|
|
82
|
+
});
|
|
83
|
+
};
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Async by default
|
|
87
|
+
|
|
88
|
+
Hooks are awaited — the build waits for them to resolve before continuing. Throw or reject to fail the build (and abort packaging / publishing).
|
|
89
|
+
|
|
90
|
+
## Where hooks are invoked
|
|
91
|
+
|
|
92
|
+
[src/gulp/tasks/package.js](../src/gulp/tasks/package.js) loads and runs them. Search for `runHook` in that file to see the exact wiring.
|
|
93
|
+
|
|
94
|
+
## Why not just edit gulp tasks?
|
|
95
|
+
|
|
96
|
+
You COULD fork BXM's gulp tasks for any custom build behavior. Hooks exist so consumers don't need to. Hooks are stable contract (the `index` object shape doesn't change), survive BXM upgrades, and live in the consumer's repo (where build-specific concerns belong).
|
|
97
|
+
|
|
98
|
+
## See also
|
|
99
|
+
|
|
100
|
+
- [build-system.md](build-system.md) — gulp pipeline details
|
|
101
|
+
- [publishing.md](publishing.md) — store auto-publishing happens AFTER `build:post`
|
package/docs/icons.md
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# Icons
|
|
2
|
+
|
|
3
|
+
BXM generates every extension icon size from ONE consumer-supplied source image — drop a single file, the build derives the rest.
|
|
4
|
+
|
|
5
|
+
## Layout
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
<consumer>/config/icon.png ← your ONE source icon (png or svg)
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
That's it. The `icons` gulp task picks up `config/icon.{png,svg}` and generates:
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
dist/assets/images/icons/icon.png (original, converted to png)
|
|
15
|
+
dist/assets/images/icons/icon-1024x.png
|
|
16
|
+
dist/assets/images/icons/icon-512x.png
|
|
17
|
+
dist/assets/images/icons/icon-256x.png
|
|
18
|
+
dist/assets/images/icons/icon-128x.png
|
|
19
|
+
dist/assets/images/icons/icon-64x.png
|
|
20
|
+
dist/assets/images/icons/icon-48x.png
|
|
21
|
+
dist/assets/images/icons/icon-32x.png
|
|
22
|
+
dist/assets/images/icons/icon-24x.png
|
|
23
|
+
dist/assets/images/icons/icon-16x.png
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Where the sizes are used
|
|
27
|
+
|
|
28
|
+
- **Manifest** — BXM's manifest template wires the `icons` map to the generated paths (`assets/images/icons/icon-<size>x.png`), covering Chrome's required 16/32/48/128 plus the larger store sizes.
|
|
29
|
+
- **Store packaging** — the `package` task copies `icon-128x.png` into the store-assets directory for listing uploads.
|
|
30
|
+
|
|
31
|
+
## Sizes — ship ONE large source
|
|
32
|
+
|
|
33
|
+
Supply the source at the largest size you have (1024×1024 recommended) — generation resizes DOWN with quality 100 and strips metadata. Small sources are enlarged (`withoutEnlargement: false`), but upscaled icons look soft; start big.
|
|
34
|
+
|
|
35
|
+
## Watch behavior
|
|
36
|
+
|
|
37
|
+
In dev (`npm start`) the task watches `config/icon.*` and regenerates on change. In build mode the watcher is skipped — generation runs once in the pipeline.
|
|
38
|
+
|
|
39
|
+
## Source files
|
|
40
|
+
|
|
41
|
+
- [src/gulp/tasks/icons.js](../src/gulp/tasks/icons.js) — generation task (gulp-responsive-modern)
|
|
42
|
+
- [src/config/manifest.json](../src/config/manifest.json) — framework manifest wiring the `icons` map
|
|
43
|
+
|
|
44
|
+
## See also
|
|
45
|
+
|
|
46
|
+
- [build-system.md](build-system.md) — where the icons task runs in the pipeline
|
|
47
|
+
- [extension.md](extension.md) — cross-browser API + manifest reference
|
package/docs/logging.md
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Logging
|
|
2
|
+
|
|
3
|
+
BXM tees every line of CLI/pipeline output to log files in the consumer project root, so you can `tail -f` or `grep` a run instead of scrolling terminal scrollback. Runtime extension logs live in the browser's own consoles (service-worker console, popup/options DevTools) — this doc covers the file logs BXM itself writes.
|
|
4
|
+
|
|
5
|
+
## Log files
|
|
6
|
+
|
|
7
|
+
All in `<projectRoot>/logs/`:
|
|
8
|
+
|
|
9
|
+
| File | Source | Lifetime |
|
|
10
|
+
|---|---|---|
|
|
11
|
+
| `dev.log` | Gulp pipeline output on `npm start` | Truncated each run |
|
|
12
|
+
| `build.log` | Gulp pipeline output on `npm run build` (`BXM_BUILD_MODE=true`) | Truncated each run |
|
|
13
|
+
| `test.log` | `npx mgr test` runner output (suite names, pass/fail states, timings) | Truncated each run |
|
|
14
|
+
|
|
15
|
+
`dev.log` and `build.log` are the same gulp tee — which one it writes is chosen by `BXM_BUILD_MODE`, so they never both fill up in one run.
|
|
16
|
+
|
|
17
|
+
## What gets captured
|
|
18
|
+
|
|
19
|
+
Everything that flows through stdout/stderr: `Manager.logger(...)` output, raw `console.log` calls, gulp task names, webpack/sass output, the works. ANSI color codes are stripped from the file (grep-friendly); the terminal continues to receive colored output unchanged.
|
|
20
|
+
|
|
21
|
+
## Controls
|
|
22
|
+
|
|
23
|
+
| Var | Effect |
|
|
24
|
+
|---|---|
|
|
25
|
+
| `BXM_LOG_FILE=false` | Disable the tee entirely |
|
|
26
|
+
| `BXM_LOG_FILE=<path>` | Override the log file path |
|
|
27
|
+
|
|
28
|
+
Implementation: [src/utils/attach-log-file.js](../src/utils/attach-log-file.js), attached at the top of [src/gulp/main.js](../src/gulp/main.js) — same pattern as EM's `dev.log`/`build.log` and UJM's.
|
|
29
|
+
|
|
30
|
+
## See also
|
|
31
|
+
|
|
32
|
+
- [build-system.md](build-system.md) — the gulp pipeline that feeds `dev.log`/`build.log`
|
|
33
|
+
- [test-framework.md](test-framework.md) — the test runner that feeds `test.log`
|
package/docs/managers.md
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# Manager Classes
|
|
2
|
+
|
|
3
|
+
Each component context has its own Manager class — a one-line import + `initialize()` bootstrap that wires up `extension` (the cross-browser API wrapper), `logger`, `webManager` (Firebase auth), `messenger`, and cross-context helpers.
|
|
4
|
+
|
|
5
|
+
## Import paths
|
|
6
|
+
|
|
7
|
+
| Context | Import | Source |
|
|
8
|
+
|---|---|---|
|
|
9
|
+
| Build-time (Node) | `require('browser-extension-manager/build')` | [src/build.js](../src/build.js) |
|
|
10
|
+
| Background SW | `require('browser-extension-manager/background')` | [src/background.js](../src/background.js) |
|
|
11
|
+
| Popup | `require('browser-extension-manager/popup')` | [src/popup.js](../src/popup.js) |
|
|
12
|
+
| Options | `require('browser-extension-manager/options')` | [src/options.js](../src/options.js) |
|
|
13
|
+
| Sidepanel | `require('browser-extension-manager/sidepanel')` | [src/sidepanel.js](../src/sidepanel.js) |
|
|
14
|
+
| Pages (custom) | `require('browser-extension-manager/page')` | [src/page.js](../src/page.js) |
|
|
15
|
+
| Content script | `require('browser-extension-manager/content')` | [src/content.js](../src/content.js) |
|
|
16
|
+
| Offscreen | `require('browser-extension-manager/offscreen')` | [src/offscreen.js](../src/offscreen.js) |
|
|
17
|
+
|
|
18
|
+
## One-line bootstrap
|
|
19
|
+
|
|
20
|
+
Every consumer-side context entry is the same shape:
|
|
21
|
+
|
|
22
|
+
```js
|
|
23
|
+
// src/assets/js/components/popup/index.js
|
|
24
|
+
import Manager from 'browser-extension-manager/popup';
|
|
25
|
+
|
|
26
|
+
const manager = new Manager();
|
|
27
|
+
await manager.initialize();
|
|
28
|
+
|
|
29
|
+
// Manager now exposes:
|
|
30
|
+
// manager.extension — cross-browser chrome.*/browser.* API wrapper (see docs/extension.md)
|
|
31
|
+
// manager.logger — LoggerLite('popup') with timestamped output
|
|
32
|
+
// manager.webManager — Web Manager (Firebase, auth, analytics, bindings)
|
|
33
|
+
// manager.messenger — wired automatically; chrome.runtime.onMessage listener installed
|
|
34
|
+
// manager.isDevelopment() / isProduction() / isTesting() / getVersion() (cross-context helpers)
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
The contexts that include `web-manager` (popup / options / sidepanel / page) also run the auth sync handshake automatically — see [auth.md](auth.md).
|
|
38
|
+
|
|
39
|
+
## Build-time Manager
|
|
40
|
+
|
|
41
|
+
`browser-extension-manager/build` is the build-time Manager — used in gulp tasks, CLI commands, and tests. Different surface from the runtime Managers:
|
|
42
|
+
|
|
43
|
+
```js
|
|
44
|
+
const Manager = require('browser-extension-manager/build');
|
|
45
|
+
|
|
46
|
+
Manager.getConfig(); // → parsed config/browser-extension-manager.json
|
|
47
|
+
Manager.getManifest(); // → parsed src/manifest.json (JSON5)
|
|
48
|
+
Manager.getPackage('project'); // → cwd's package.json
|
|
49
|
+
Manager.getPackage('main'); // → BXM's own package.json
|
|
50
|
+
Manager.getRootPath('project'); // → process.cwd()
|
|
51
|
+
Manager.getRootPath('main'); // → path to BXM's dist
|
|
52
|
+
Manager.getEnvironment(); // → 'production' if BXM_BUILD_MODE=true else 'development'
|
|
53
|
+
Manager.getLiveReloadPort(); // → 35729 by default
|
|
54
|
+
Manager.isBuildMode(); // → boolean
|
|
55
|
+
Manager.actLikeProduction(); // → buildMode || UJ_AUDIT_FORCE
|
|
56
|
+
Manager.require(name); // → borrow any of BXM's bundled deps (json5, fs-jetpack, etc.)
|
|
57
|
+
Manager.logger(name); // → new logger('name') instance
|
|
58
|
+
Manager.reportBuildError(e); // → notifly + log
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Cross-context helpers (`isTesting/isDevelopment/isProduction/getVersion`) are available on `Manager` as static methods AND on instances. See [environment-detection.md](environment-detection.md).
|
|
62
|
+
|
|
63
|
+
## Boot flow inside `initialize()`
|
|
64
|
+
|
|
65
|
+
Each Manager's `initialize()`:
|
|
66
|
+
|
|
67
|
+
1. **Read configuration** — from `window.BXM_BUILD_JSON?.config` (injected by webpack at build time)
|
|
68
|
+
2. **Wire `extension`** — singleton from [src/lib/extension.js](../src/lib/extension.js), normalized chrome.*/browser.* API
|
|
69
|
+
3. **Construct `logger`** — `new LoggerLite('<context>')` from [src/lib/logger-lite.js](../src/lib/logger-lite.js)
|
|
70
|
+
4. **Initialize `webManager`** (popup/options/sidepanel/page only) — `webManager.initialize(config)`
|
|
71
|
+
5. **Set up auth listener** — `webManager.auth().listen((state) => { /* ... */ })`
|
|
72
|
+
6. **Sync with background** — call `syncWithBackground(this)` from [src/lib/auth-helpers.js](../src/lib/auth-helpers.js); see [auth.md](auth.md)
|
|
73
|
+
7. **Install broadcast / sign-out / event listeners** — handles signin-from-other-context, signout propagation
|
|
74
|
+
8. **Return the manager instance**
|
|
75
|
+
|
|
76
|
+
Background.js is more involved — it owns the auth source of truth, listens for `bxm:syncAuth` from other contexts, handles the website token flow. See [auth.md](auth.md).
|
|
77
|
+
|
|
78
|
+
## Auth-related shortcuts (popup / options / sidepanel / page)
|
|
79
|
+
|
|
80
|
+
These Manager classes expose `manager.openAuthPage(options)` — opens the website's `/token` page (with `authSourceTabId` so tab restoration works after sign-in). See [auth.md](auth.md) for the full flow.
|
|
81
|
+
|
|
82
|
+
## Mixin: cross-context helpers
|
|
83
|
+
|
|
84
|
+
Every Manager file imports `attachTo` from [src/utils/mode-helpers.js](../src/utils/mode-helpers.js) and calls it at the bottom of the file:
|
|
85
|
+
|
|
86
|
+
```js
|
|
87
|
+
import { attachTo as attachModeHelpers } from './utils/mode-helpers.js';
|
|
88
|
+
// ... class Manager { ... }
|
|
89
|
+
attachModeHelpers(Manager);
|
|
90
|
+
export default Manager;
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
That gives every Manager `isDevelopment` / `isProduction` / `isTesting` / `getVersion` on both the prototype and the constructor. See [environment-detection.md](environment-detection.md).
|
|
94
|
+
|
|
95
|
+
## See also
|
|
96
|
+
|
|
97
|
+
- [components.md](components.md) — the seven component contexts
|
|
98
|
+
- [extension.md](extension.md) — the cross-browser chrome.* API wrapper
|
|
99
|
+
- [auth.md](auth.md) — how background.js coordinates auth across contexts
|
|
100
|
+
- [environment-detection.md](environment-detection.md) — `isTesting / isDevelopment / getVersion`
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# Offscreen Component
|
|
2
|
+
|
|
3
|
+
Offscreen documents persist in the background (unlike service workers, they don't sleep). Use for WebSocket connections, long-running tasks, audio, clipboard — anything an MV3 service worker can't hold.
|
|
4
|
+
|
|
5
|
+
**Key facts:**
|
|
6
|
+
|
|
7
|
+
- Only one offscreen document can exist per extension.
|
|
8
|
+
- Invisible — no UI, no styling needed.
|
|
9
|
+
- Lightweight Manager (no WebManager, no auth, no theme) — see [managers.md](managers.md).
|
|
10
|
+
- Must be created programmatically from the background service worker.
|
|
11
|
+
- Requires the `offscreen` permission in the manifest (opt-in — see [components.md](components.md)).
|
|
12
|
+
|
|
13
|
+
## Creating from Background
|
|
14
|
+
|
|
15
|
+
```javascript
|
|
16
|
+
const OFFSCREEN_PATH = 'views/offscreen/index.html';
|
|
17
|
+
|
|
18
|
+
async function hasOffscreenDocument() {
|
|
19
|
+
const contexts = await chrome.runtime.getContexts({
|
|
20
|
+
contextTypes: ['OFFSCREEN_DOCUMENT'],
|
|
21
|
+
documentUrls: [chrome.runtime.getURL(OFFSCREEN_PATH)],
|
|
22
|
+
});
|
|
23
|
+
return contexts.length > 0;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async function setupOffscreenDocument() {
|
|
27
|
+
if (await hasOffscreenDocument()) return;
|
|
28
|
+
await chrome.offscreen.createDocument({
|
|
29
|
+
url: OFFSCREEN_PATH,
|
|
30
|
+
reasons: ['WEB_RTC'],
|
|
31
|
+
justification: 'Maintain persistent connection',
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Offscreen ↔ Background Communication
|
|
37
|
+
|
|
38
|
+
```javascript
|
|
39
|
+
// From offscreen → background
|
|
40
|
+
chrome.runtime.sendMessage({ action: 'myAction', data: {...} }, (response) => {
|
|
41
|
+
// handle response
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// From background, listen for offscreen messages
|
|
45
|
+
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
|
46
|
+
if (message.action === 'myAction') {
|
|
47
|
+
doSomething(message.data).then(sendResponse);
|
|
48
|
+
return true; // keep channel open for async response
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## See also
|
|
54
|
+
|
|
55
|
+
- [components.md](components.md) — the seven component contexts + manifest wiring
|
|
56
|
+
- [managers.md](managers.md) — lightweight vs full Manager per context
|
|
57
|
+
- [extension.md](extension.md) — cross-browser API wrapper
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
# Publishing
|
|
2
|
+
|
|
3
|
+
`BXM_IS_PUBLISH=true npm run build` packages AND uploads to extension stores in one step.
|
|
4
|
+
|
|
5
|
+
## Supported stores
|
|
6
|
+
|
|
7
|
+
- **Chrome Web Store** (Chrome / Edge / Brave / Opera users)
|
|
8
|
+
- **Firefox Add-ons** (AMO)
|
|
9
|
+
- **Microsoft Edge Add-ons** (separate from Chrome Web Store — Edge Insiders + corporate environments)
|
|
10
|
+
|
|
11
|
+
Only stores with configured credentials get published to. Unconfigured stores are silently skipped.
|
|
12
|
+
|
|
13
|
+
## Setup — `.env`
|
|
14
|
+
|
|
15
|
+
Add store credentials to your project's `.env` (gitignored):
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
# Chrome Web Store
|
|
19
|
+
CHROME_EXTENSION_ID="..."
|
|
20
|
+
CHROME_CLIENT_ID="..."
|
|
21
|
+
CHROME_CLIENT_SECRET="..."
|
|
22
|
+
CHROME_REFRESH_TOKEN="..."
|
|
23
|
+
|
|
24
|
+
# Firefox Add-ons
|
|
25
|
+
FIREFOX_EXTENSION_ID="..."
|
|
26
|
+
FIREFOX_API_KEY="..."
|
|
27
|
+
FIREFOX_API_SECRET="..."
|
|
28
|
+
|
|
29
|
+
# Microsoft Edge Add-ons
|
|
30
|
+
EDGE_PRODUCT_ID="..."
|
|
31
|
+
EDGE_CLIENT_ID="..."
|
|
32
|
+
EDGE_API_KEY="..."
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Getting credentials
|
|
36
|
+
|
|
37
|
+
### Chrome Web Store
|
|
38
|
+
|
|
39
|
+
1. Create an OAuth client via [Google Cloud Console](https://console.cloud.google.com/) (Web application type)
|
|
40
|
+
2. Enable the [Chrome Web Store API](https://developer.chrome.com/docs/webstore/using_webstore_api/)
|
|
41
|
+
3. Generate a refresh token via the OAuth flow (one-time)
|
|
42
|
+
4. `CHROME_EXTENSION_ID` is in your Chrome Web Store Developer Dashboard URL: `chrome.google.com/webstore/devconsole/<id>`
|
|
43
|
+
|
|
44
|
+
### Firefox Add-ons
|
|
45
|
+
|
|
46
|
+
1. Sign in to [Add-on Developer Hub](https://addons.mozilla.org/developers/)
|
|
47
|
+
2. Generate JWT credentials at "Manage API Keys"
|
|
48
|
+
3. `FIREFOX_EXTENSION_ID` matches the `id` field in your manifest's `browser_specific_settings.gecko.id`
|
|
49
|
+
|
|
50
|
+
### Microsoft Edge Add-ons
|
|
51
|
+
|
|
52
|
+
1. Sign up for the [Microsoft Edge Add-ons Partner Center](https://partner.microsoft.com/dashboard/microsoftedge)
|
|
53
|
+
2. Generate API credentials in the "API publishing" section
|
|
54
|
+
3. `EDGE_PRODUCT_ID` is the GUID assigned to your extension by Microsoft
|
|
55
|
+
|
|
56
|
+
## Publish flow
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
BXM_IS_PUBLISH=true npm run build
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
What happens:
|
|
63
|
+
|
|
64
|
+
1. `npm run build` runs the full gulp pipeline (build → package → packaged/<browser>/raw + zip)
|
|
65
|
+
2. `gulp/tasks/package.js` detects `BXM_IS_PUBLISH=true`
|
|
66
|
+
3. For each browser, reads the store credentials from `.env`
|
|
67
|
+
4. If credentials are present, uploads the `.zip` via the store's API
|
|
68
|
+
5. Logs success / failure per store; exits non-zero if any upload fails
|
|
69
|
+
|
|
70
|
+
## Manual upload
|
|
71
|
+
|
|
72
|
+
Run `npm run build` without `BXM_IS_PUBLISH=true` — you get unsigned `.zip` files per browser under `packaged/`:
|
|
73
|
+
|
|
74
|
+
```
|
|
75
|
+
packaged/
|
|
76
|
+
├── chromium/<ExtensionName>.zip
|
|
77
|
+
├── firefox/<ExtensionName>.zip
|
|
78
|
+
└── opera/<ExtensionName>.zip
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Upload these manually via each store's web dashboard.
|
|
82
|
+
|
|
83
|
+
## CI / GitHub Actions
|
|
84
|
+
|
|
85
|
+
For automated releases, store credentials as encrypted GitHub Actions secrets. A typical workflow:
|
|
86
|
+
|
|
87
|
+
```yaml
|
|
88
|
+
- name: Build + publish
|
|
89
|
+
env:
|
|
90
|
+
BXM_IS_PUBLISH: true
|
|
91
|
+
CHROME_EXTENSION_ID: ${{ secrets.CHROME_EXTENSION_ID }}
|
|
92
|
+
CHROME_CLIENT_ID: ${{ secrets.CHROME_CLIENT_ID }}
|
|
93
|
+
CHROME_CLIENT_SECRET: ${{ secrets.CHROME_CLIENT_SECRET }}
|
|
94
|
+
CHROME_REFRESH_TOKEN: ${{ secrets.CHROME_REFRESH_TOKEN }}
|
|
95
|
+
# ...same for FIREFOX_*, EDGE_*
|
|
96
|
+
run: npm run build
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Same pattern EM uses for its desktop apps. Each store does its own review afterward (Chrome / Firefox: hours to a few days; Edge: typically same day).
|
|
100
|
+
|
|
101
|
+
## What about Safari?
|
|
102
|
+
|
|
103
|
+
Safari (Apple) uses a different extension model and requires Xcode-based packaging via `safari-web-extension-converter`. Not currently in scope for BXM's auto-publish. Manual conversion + App Store Connect upload is the route.
|
|
104
|
+
|
|
105
|
+
## Store listing description (`config/description.md`)
|
|
106
|
+
|
|
107
|
+
`config/description.md` is the Chrome Web Store listing description. When writing or rewriting it, first read `config/browser-extension-manager.json` (brand), `config/messages.json` (extension name + short description), `src/manifest.json` (permissions/features), and the component JS under `src/assets/js/components/` to understand what the extension actually does — be specific about real features, not generic copy.
|
|
108
|
+
|
|
109
|
+
**Format:**
|
|
110
|
+
|
|
111
|
+
```
|
|
112
|
+
[emoji] [Key Feature Headline 1]
|
|
113
|
+
[emoji] [Key Feature Headline 2]
|
|
114
|
+
[emoji] [Key Feature Headline 3]
|
|
115
|
+
[emoji] [Key Feature Headline 4]
|
|
116
|
+
[emoji] [Key Feature Headline 5]
|
|
117
|
+
|
|
118
|
+
[Extension Name] is [compelling one-sentence pitch]. [Pain point 1]. [Pain point 2]. [Pain point 3].
|
|
119
|
+
If you [target audience description] — [Extension Name] was made for you.
|
|
120
|
+
|
|
121
|
+
[emoji] How it works
|
|
122
|
+
[Extension Name] [brief mechanism description].
|
|
123
|
+
|
|
124
|
+
[emoji] [Feature 1]: [One-line description]
|
|
125
|
+
[emoji] [Feature 2]: [One-line description]
|
|
126
|
+
[emoji] [Feature 3]: [One-line description]
|
|
127
|
+
[emoji] [Feature 4]: [One-line description]
|
|
128
|
+
|
|
129
|
+
[Brief usage instructions — 2-3 sentences].
|
|
130
|
+
No complicated setup. No learning curve. Just [core value proposition].
|
|
131
|
+
|
|
132
|
+
[emoji] Why [Extension Name] is a game changer
|
|
133
|
+
|
|
134
|
+
[Benefit 1 title]: [Description].
|
|
135
|
+
[Benefit 2 title]: [Description].
|
|
136
|
+
[Benefit 3 title]: [Description].
|
|
137
|
+
[Benefit 4 title]: [Description].
|
|
138
|
+
[Benefit 5 title]: [Description].
|
|
139
|
+
|
|
140
|
+
[emoji] The [Extension Name] Difference
|
|
141
|
+
Most people either:
|
|
142
|
+
|
|
143
|
+
[Alternative 1 that's worse]
|
|
144
|
+
[Alternative 2 that's worse]
|
|
145
|
+
|
|
146
|
+
[Extension Name] gives you a [better option description].
|
|
147
|
+
[Catchy metaphor with emoji]
|
|
148
|
+
[Closing sentence about who benefits].
|
|
149
|
+
|
|
150
|
+
[emoji] Install [Extension Name] now and [call to action].
|
|
151
|
+
[Short imperative sentence].
|
|
152
|
+
|
|
153
|
+
:money_with_wings: Bonus:
|
|
154
|
+
While you're browsing, the extension also finds and applies shopping deals from top partners like Amazon, Capital One, and NordVPN. Get discounts and bonuses without lifting a finger. When you buy through links on our extension, we may earn an affiliate commission.
|
|
155
|
+
|
|
156
|
+
:locked_with_key: Your privacy is respected — we do not sell or misuse your data. By using our extension, you agree to our terms of service and privacy policy.
|
|
157
|
+
When you buy through links on our extension, we may earn an affiliate commission.
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
**Rules:**
|
|
161
|
+
|
|
162
|
+
- **Keep the Bonus section and Privacy section EXACTLY as shown** — do not modify these
|
|
163
|
+
- Use the extension's actual name from `config/messages.json` — do NOT use `{{ brand.name }}` template variables
|
|
164
|
+
- Be specific about features — reference what the code actually does
|
|
165
|
+
- Tone: enthusiastic, conversational, persuasive; emojis for section headers and feature bullets
|
|
166
|
+
- Feature headlines short and punchy (under 50 characters)
|
|
167
|
+
- 300-500 words (excluding the Bonus and Privacy sections)
|
|
168
|
+
|
|
169
|
+
**After rewriting**, clear stale cached translations — delete `.cache/translations/description/` and the `description` key from `.cache/translate.json` — then have the user run `npm run build` to regenerate translations (see [translations.md](translations.md)).
|
|
170
|
+
|
|
171
|
+
## See also
|
|
172
|
+
|
|
173
|
+
- [build-system.md](build-system.md) — packaging pipeline that produces the per-browser zips
|
|
174
|
+
- [hooks.md](hooks.md) — `build:pre` and `build:post` hooks run before/after publish
|
|
175
|
+
- [cli.md](cli.md) — env var conventions
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# Templating
|
|
2
|
+
|
|
3
|
+
HTML views go through a two-step `{{ }}` token replacement during the `gulp/html` task. Same convention as EM and UJM.
|
|
4
|
+
|
|
5
|
+
## How it works
|
|
6
|
+
|
|
7
|
+
1. Your view file (`src/views/<component>/index.html`) is templated first — `{{ brand.name }}` etc. resolve against the page-vars object.
|
|
8
|
+
2. The result is injected into [src/config/page-template.html](../src/config/page-template.html) (the framework's outer HTML shell).
|
|
9
|
+
3. The outer template is processed again with the same vars.
|
|
10
|
+
4. Output written to `dist/views/<component>/index.html`.
|
|
11
|
+
|
|
12
|
+
## Available variables
|
|
13
|
+
|
|
14
|
+
| Token | Source | Example |
|
|
15
|
+
|---|---|---|
|
|
16
|
+
| `{{ brand.name }}` | `config/browser-extension-manager.json` → `brand.name` | `Tabblar` |
|
|
17
|
+
| `{{ brand.url }}` | `config/browser-extension-manager.json` → `brand.url` | `https://tabblar.com` |
|
|
18
|
+
| `{{ brand.id }}` | `config/browser-extension-manager.json` → `brand.id` | `tabblar` |
|
|
19
|
+
| `{{ page.name }}` | Component name (e.g. `popup`, `pages/dashboard`) | `popup` |
|
|
20
|
+
| `{{ page.path }}` | Full view path | `views/popup/index.html` |
|
|
21
|
+
| `{{ page.title }}` | Page title — defaults to brand name | `Tabblar` |
|
|
22
|
+
| `{{ theme.appearance }}` | Theme appearance: `dark` or `light` | `dark` |
|
|
23
|
+
| `{{ cacheBust }}` | Cache-busting timestamp (build time) | `1715568000` |
|
|
24
|
+
| `{{ version }}` | `package.json#version` | `1.1.10` |
|
|
25
|
+
| `{{ environment }}` | `'production'` or `'development'` | `production` |
|
|
26
|
+
|
|
27
|
+
## Example usage
|
|
28
|
+
|
|
29
|
+
```html
|
|
30
|
+
<!-- src/views/popup/index.html -->
|
|
31
|
+
<!doctype html>
|
|
32
|
+
<html>
|
|
33
|
+
<head><title>{{ page.title }}</title></head>
|
|
34
|
+
<body>
|
|
35
|
+
<h1>Welcome to {{ brand.name }}</h1>
|
|
36
|
+
<a href="{{ brand.url }}/pricing?ref=ext">Upgrade to Premium</a>
|
|
37
|
+
<p>Version {{ version }}</p>
|
|
38
|
+
</body>
|
|
39
|
+
</html>
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Page template
|
|
43
|
+
|
|
44
|
+
[src/config/page-template.html](../src/config/page-template.html) is the outer shell that wraps every view. It contains:
|
|
45
|
+
- `<!doctype html>` + `<html>` boilerplate
|
|
46
|
+
- `<meta>` tags (viewport, charset)
|
|
47
|
+
- A `{{ content }}` slot where the per-component HTML is injected
|
|
48
|
+
- `<link rel="stylesheet">` for the component's `.bundle.css`
|
|
49
|
+
- `<script>` for the component's `.bundle.js`
|
|
50
|
+
- Cache-busting query params via `{{ cacheBust }}`
|
|
51
|
+
|
|
52
|
+
Consumers don't author this — BXM owns it. If you need to customize per-view, override directly in `src/views/<component>/index.html` (BXM detects when a view provides its own full `<html>` and skips wrapping).
|
|
53
|
+
|
|
54
|
+
## Customizing page vars
|
|
55
|
+
|
|
56
|
+
The page-vars object is built by [src/gulp/tasks/html.js](../src/gulp/tasks/html.js). To add a new variable for consumer templates, edit the `buildPageVars()` helper there. Vars added are available in BOTH the view file AND the outer page-template.html.
|
|
57
|
+
|
|
58
|
+
## Why two passes?
|
|
59
|
+
|
|
60
|
+
Pass 1 lets a view interpolate something into the outer template. For example, a view can do `<!-- bxm:page-title --> Custom Title <!-- /bxm:page-title -->` (a future feature) and have the value flow into `{{ page.title }}` for the outer template's `<title>`. The pattern keeps simple cases simple (`{{ brand.name }}` Just Works) while leaving room for view → shell metadata flow.
|
|
61
|
+
|
|
62
|
+
## See also
|
|
63
|
+
|
|
64
|
+
- [build-system.md](build-system.md) — gulp pipeline including the html task
|
|
65
|
+
- [css.md](css.md) — how the page template references compiled CSS bundles
|
|
66
|
+
- [themes.md](themes.md) — `{{ theme.appearance }}` derivation
|