browser-extension-manager 1.3.1 → 1.3.3

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/CHANGELOG.md CHANGED
@@ -18,16 +18,19 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
18
18
  ## [1.3.0] - 2025-12-16
19
19
 
20
20
  ### Added
21
- - Multi-target builds for Chromium and Firefox with automatic manifest adjustments
21
+ - Multi-target builds for Chromium, Firefox, and Opera with automatic manifest adjustments
22
22
  - Chromium build uses `background.service_worker`, Firefox build uses `background.scripts`
23
- - Edge loading instructions in BUILD_INSTRUCTIONS.md
23
+ - Opera build auto-resolves `__MSG_*__` placeholders in `short_name` (Opera enforces 12-char limit including placeholder text)
24
+ - Browser-specific `TARGETS` config object with `adjustManifest()` functions for each target
25
+ - Opera loading instructions in BUILD_INSTRUCTIONS.md
24
26
 
25
27
  ### Changed
26
- - Packaged output structure changed from `packaged/raw/` to `packaged/{chromium,firefox}/raw/`
27
- - Extension zip moved from `packaged/extension.zip` to `packaged/{chromium,firefox}/extension.zip`
28
- - `package.js` now creates separate builds for each browser target
29
- - `publish.js` uses target-specific paths (chromium zip for Chrome/Edge, firefox raw for Firefox)
28
+ - Packaged output structure changed from `packaged/raw/` to `packaged/{chromium,firefox,opera}/raw/`
29
+ - Extension zip moved from `packaged/extension.zip` to `packaged/{chromium,firefox,opera}/extension.zip`
30
+ - `package.js` now creates separate builds for each browser target using configurable `TARGETS` object
31
+ - `publish.js` uses target-specific paths (chromium zip for Chrome/Edge, firefox raw for Firefox, opera zip for Opera)
30
32
  - `audit.js` audits chromium build (code is identical between targets)
33
+ - Manifest compilation now uses 3-step process: apply defaults → target adjustments → cleanup
31
34
 
32
35
  ## [1.1.13] - 2025-11-26
33
36
  ### Added
@@ -304,7 +304,7 @@ class Manager {
304
304
  if (user) {
305
305
  // User is signed in - get current stored state to preserve token
306
306
  const result = await new Promise(resolve =>
307
- this.extension.storage.get('bxm:authState', resolve)
307
+ this.extension.storage.local.get('bxm:authState', resolve)
308
308
  );
309
309
  const currentState = result['bxm:authState'] || {};
310
310
 
@@ -321,11 +321,11 @@ class Manager {
321
321
  timestamp: Date.now(),
322
322
  };
323
323
 
324
- await this.extension.storage.set({ 'bxm:authState': authState });
324
+ await this.extension.storage.local.set({ 'bxm:authState': authState });
325
325
  this.logger.log('[AUTH] Auth state synced to storage');
326
326
  } else {
327
327
  // User is signed out - clear storage
328
- await this.extension.storage.remove('bxm:authState');
328
+ await this.extension.storage.local.remove('bxm:authState');
329
329
  this.logger.log('[AUTH] Auth state cleared from storage');
330
330
  }
331
331
  }
@@ -349,11 +349,11 @@ class Manager {
349
349
 
350
350
  // Save token to storage (user state will be synced by onAuthStateChanged)
351
351
  const result = await new Promise(resolve =>
352
- this.extension.storage.get('bxm:authState', resolve)
352
+ this.extension.storage.local.get('bxm:authState', resolve)
353
353
  );
354
354
  const currentState = result['bxm:authState'] || {};
355
355
 
356
- await this.extension.storage.set({
356
+ await this.extension.storage.local.set({
357
357
  'bxm:authState': {
358
358
  ...currentState,
359
359
  token: token,
@@ -2,6 +2,8 @@
2
2
  // Basic info
3
3
  manifest_version: 3,
4
4
  name: '__MSG_appName__',
5
+ // NOTE: Opera enforces a 12-char limit on short_name INCLUDING the placeholder text
6
+ // '__MSG_appNameShort__' is 19 chars, so use a static value for Opera compatibility
5
7
  short_name: '__MSG_appNameShort__',
6
8
  description: '__MSG_appDescription__',
7
9
 
@@ -1,22 +1,26 @@
1
1
  # ========== Default Values ==========
2
+ # Github Token
3
+ # Get token at: https://github.com/settings/tokens
4
+ GH_TOKEN=""
5
+
2
6
  # Chrome Web Store Publishing Credentials
3
7
  # Get credentials at: https://developer.chrome.com/docs/webstore/using_webstore_api/
4
- CHROME_EXTENSION_ID="your-extension-id"
5
- CHROME_CLIENT_ID="your-client-id"
6
- CHROME_CLIENT_SECRET="your-client-secret"
7
- CHROME_REFRESH_TOKEN="your-refresh-token"
8
+ CHROME_EXTENSION_ID=""
9
+ CHROME_CLIENT_ID=""
10
+ CHROME_CLIENT_SECRET=""
11
+ CHROME_REFRESH_TOKEN=""
8
12
 
9
13
  # Firefox Add-ons Publishing Credentials
10
14
  # Get credentials at: https://addons.mozilla.org/developers/addon/api/key/
11
- FIREFOX_EXTENSION_ID="your-extension-id"
12
- FIREFOX_API_KEY="your-api-key"
13
- FIREFOX_API_SECRET="your-api-secret"
15
+ FIREFOX_EXTENSION_ID=""
16
+ FIREFOX_API_KEY=""
17
+ FIREFOX_API_SECRET=""
14
18
 
15
19
  # Microsoft Edge Add-ons Publishing Credentials
16
20
  # Get credentials at: https://learn.microsoft.com/en-us/microsoft-edge/extensions-chromium/publish/api/using-addons-api
17
- EDGE_PRODUCT_ID="your-product-id"
18
- EDGE_CLIENT_ID="your-client-id"
19
- EDGE_API_KEY="your-api-key"
21
+ EDGE_PRODUCT_ID=""
22
+ EDGE_CLIENT_ID=""
23
+ EDGE_API_KEY=""
20
24
 
21
25
  # Opera Add-ons
22
26
  # NOTE: Opera does not have a publishing API. Extensions must be submitted manually at:
@@ -152,8 +152,67 @@ function getPackageVersion(packageName) {
152
152
  }
153
153
  }
154
154
 
155
- // Build targets
156
- const TARGETS = ['chromium', 'firefox'];
155
+ // Build targets with browser-specific configurations
156
+ const TARGETS = {
157
+ chromium: {
158
+ // Chrome, Edge, Brave, etc. - uses service_worker
159
+ adjustManifest: (manifest) => {
160
+ if (manifest.background) {
161
+ delete manifest.background.scripts;
162
+ }
163
+ return manifest;
164
+ },
165
+ },
166
+ firefox: {
167
+ // Firefox - uses scripts array, no service_worker
168
+ adjustManifest: (manifest) => {
169
+ if (manifest.background) {
170
+ if (manifest.background.service_worker) {
171
+ if (!manifest.background.scripts) {
172
+ manifest.background.scripts = [manifest.background.service_worker];
173
+ }
174
+ delete manifest.background.service_worker;
175
+ }
176
+ }
177
+ return manifest;
178
+ },
179
+ },
180
+ opera: {
181
+ // Opera - like chromium but with stricter requirements
182
+ // Opera enforces 12-char limit on short_name INCLUDING placeholder text
183
+ adjustManifest: (manifest) => {
184
+ // Same as chromium for background
185
+ if (manifest.background) {
186
+ delete manifest.background.scripts;
187
+ }
188
+
189
+ // Opera requires static short_name (12 char limit includes placeholder text)
190
+ // __MSG_appNameShort__ is 19 chars, so we must use actual value
191
+ if (manifest.short_name && manifest.short_name.startsWith('__MSG_')) {
192
+ // Try to get the value from default locale
193
+ const localesDir = path.join('dist', '_locales');
194
+ const defaultLocale = manifest.default_locale || 'en';
195
+ const messagesPath = path.join(localesDir, defaultLocale, 'messages.json');
196
+
197
+ if (jetpack.exists(messagesPath)) {
198
+ try {
199
+ const messages = JSON5.parse(jetpack.read(messagesPath));
200
+ // Extract key from __MSG_keyName__
201
+ const key = manifest.short_name.replace(/^__MSG_/, '').replace(/__$/, '');
202
+ if (messages[key]?.message) {
203
+ manifest.short_name = messages[key].message;
204
+ logger.log(`[opera] Resolved short_name to "${manifest.short_name}"`);
205
+ }
206
+ } catch (e) {
207
+ logger.warn(`[opera] Could not resolve short_name from locale: ${e.message}`);
208
+ }
209
+ }
210
+ }
211
+
212
+ return manifest;
213
+ },
214
+ },
215
+ };
157
216
 
158
217
  // Recursively remove empty arrays and objects from an object
159
218
  function removeEmptyValues(obj) {
@@ -193,10 +252,12 @@ async function compileManifest(outputDir, target) {
193
252
  const configPath = path.join(rootPathPackage, 'dist', 'config', 'manifest.json');
194
253
 
195
254
  // Read and parse using JSON5
196
- const manifest = JSON5.parse(jetpack.read(manifestPath));
255
+ let manifest = JSON5.parse(jetpack.read(manifestPath));
197
256
  const defaultConfig = JSON5.parse(jetpack.read(configPath));
198
257
 
199
- // Apply defaults
258
+ // ═══════════════════════════════════════════════════════════════════════════
259
+ // STEP 1: Apply defaults (shared across all targets)
260
+ // ═══════════════════════════════════════════════════════════════════════════
200
261
  getKeys(defaultConfig).forEach(key => {
201
262
  const defaultValue = key.split('.').reduce((o, k) => (o || {})[k], defaultConfig);
202
263
  const userValue = key.split('.').reduce((o, k) => (o || {})[k], manifest);
@@ -222,23 +283,17 @@ async function compileManifest(outputDir, target) {
222
283
  // Add package version to manifest
223
284
  manifest.version = project.version;
224
285
 
225
- // Apply target-specific manifest adjustments
226
- if (manifest.background) {
227
- if (target === 'chromium') {
228
- // Chrome/Edge uses service_worker only
229
- delete manifest.background.scripts;
230
- } else if (target === 'firefox') {
231
- // Firefox uses scripts array only
232
- if (manifest.background.service_worker) {
233
- // Ensure scripts array exists (derive from service_worker if needed)
234
- if (!manifest.background.scripts) {
235
- manifest.background.scripts = [manifest.background.service_worker];
236
- }
237
- delete manifest.background.service_worker;
238
- }
239
- }
286
+ // ═══════════════════════════════════════════════════════════════════════════
287
+ // STEP 2: Apply target-specific adjustments
288
+ // ═══════════════════════════════════════════════════════════════════════════
289
+ const targetConfig = TARGETS[target];
290
+ if (targetConfig?.adjustManifest) {
291
+ manifest = targetConfig.adjustManifest(manifest);
240
292
  }
241
293
 
294
+ // ═══════════════════════════════════════════════════════════════════════════
295
+ // STEP 3: Clean up (shared across all targets)
296
+ // ═══════════════════════════════════════════════════════════════════════════
242
297
  // Remove empty arrays and objects from manifest
243
298
  const cleanedManifest = removeEmptyValues(manifest);
244
299
 
@@ -278,14 +333,14 @@ async function compileLocales(outputDir) {
278
333
  }
279
334
  }
280
335
 
281
- // Package Task for raw - creates both chromium and firefox builds
336
+ // Package Task for raw - creates builds for all browser targets
282
337
  async function packageRaw() {
283
338
  // Log
284
339
  logger.log(`Starting raw packaging...`);
285
340
 
286
341
  try {
287
342
  // Build for each target
288
- for (const target of TARGETS) {
343
+ for (const target of Object.keys(TARGETS)) {
289
344
  await packageRawForTarget(target);
290
345
  }
291
346
 
@@ -353,7 +408,7 @@ async function packageZip() {
353
408
 
354
409
  try {
355
410
  // Create zip for each target
356
- for (const target of TARGETS) {
411
+ for (const target of Object.keys(TARGETS)) {
357
412
  const inputDir = `packaged/${target}/raw`;
358
413
  const zipPath = `packaged/${target}/extension.zip`;
359
414
 
@@ -19,6 +19,10 @@ const PATHS = {
19
19
  zip: path.join(process.cwd(), 'packaged', 'firefox', 'extension.zip'),
20
20
  raw: path.join(process.cwd(), 'packaged', 'firefox', 'raw'),
21
21
  },
22
+ opera: {
23
+ zip: path.join(process.cwd(), 'packaged', 'opera', 'extension.zip'),
24
+ raw: path.join(process.cwd(), 'packaged', 'opera', 'raw'),
25
+ },
22
26
  };
23
27
 
24
28
  // Helper to check if a credential is valid (not empty or placeholder)
@@ -26,17 +26,12 @@ npm install
26
26
  npm run build
27
27
  ```
28
28
  5. The built extensions will be in `packaged/` directory:
29
- - **Firefox:** `packaged/firefox/raw/` (unpacked) and `packaged/firefox/extension.zip`
30
29
  - **Chrome/Edge:** `packaged/chromium/raw/` (unpacked) and `packaged/chromium/extension.zip`
30
+ - **Firefox:** `packaged/firefox/raw/` (unpacked) and `packaged/firefox/extension.zip`
31
+ - **Opera:** `packaged/opera/raw/` (unpacked) and `packaged/opera/extension.zip`
31
32
 
32
33
  ## Loading the Extension
33
34
 
34
- ### Firefox
35
- 1. Go to `about:debugging`
36
- 2. Click "This Firefox"
37
- 3. Click "Load Temporary Add-on"
38
- 4. Select `packaged/firefox/raw/manifest.json`
39
-
40
35
  ### Chrome
41
36
  1. Go to `chrome://extensions`
42
37
  2. Enable "Developer mode"
@@ -49,6 +44,18 @@ npm run build
49
44
  3. Click "Load unpacked"
50
45
  4. Select the `packaged/chromium/raw/` directory
51
46
 
47
+ ### Firefox
48
+ 1. Go to `about:debugging`
49
+ 2. Click "This Firefox"
50
+ 3. Click "Load Temporary Add-on"
51
+ 4. Select `packaged/firefox/raw/manifest.json`
52
+
53
+ ### Opera
54
+ 1. Go to `opera://extensions`
55
+ 2. Enable "Developer mode"
56
+ 3. Click "Load unpacked"
57
+ 4. Select the `packaged/opera/raw/` directory
58
+
52
59
  ## Questions
53
60
 
54
61
  If you have any questions about the build process, please contact the developer.
@@ -112,7 +112,7 @@ Affiliatizer.get = function () {
112
112
  Affiliatizer.initialize = async function (Manager) {
113
113
  // Shortcuts
114
114
  const { extension, logger } = Manager;
115
- const { storage } = extension;
115
+ const storage = extension.storage.local;
116
116
 
117
117
  // Parse the URL
118
118
  const url = new URL(window.location.href);
@@ -29,7 +29,7 @@ async function signInWithStoredToken(context, authState) {
29
29
  // If token is invalid/expired, clear the auth state
30
30
  if (error.code === 'auth/invalid-custom-token' || error.code === 'auth/custom-token-expired') {
31
31
  logger.log('[AUTH-SYNC] Token expired, clearing auth state');
32
- context.extension.storage.remove('bxm:authState');
32
+ context.extension.storage.local.remove('bxm:authState');
33
33
  }
34
34
  }
35
35
  }
@@ -43,7 +43,7 @@ export function setupAuthStorageListener(context) {
43
43
  const { extension, webManager, logger } = context;
44
44
 
45
45
  // Check existing auth state on load and sign in
46
- extension.storage.get('bxm:authState', (result) => {
46
+ extension.storage.local.get('bxm:authState', (result) => {
47
47
  const authState = result['bxm:authState'];
48
48
 
49
49
  if (authState?.token) {
@@ -58,7 +58,7 @@ export function setupAuthStorageListener(context) {
58
58
  if (!state.user) {
59
59
  // User signed out - clear storage so all contexts sync
60
60
  logger.log('[AUTH-SYNC] WM auth signed out, clearing storage...');
61
- extension.storage.remove('bxm:authState');
61
+ extension.storage.local.remove('bxm:authState');
62
62
  }
63
63
  });
64
64
 
@@ -111,15 +111,6 @@ function Extension () {
111
111
  }
112
112
  } catch (e) {}
113
113
 
114
- // Fix storage
115
- if (self.storage) {
116
- if (self.storage.sync) {
117
- self.storage = self.storage.sync
118
- } else if (self.storage.local) {
119
- self.storage = self.storage.local
120
- }
121
- }
122
-
123
114
  // Return the object
124
115
  return self;
125
116
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "browser-extension-manager",
3
- "version": "1.3.1",
3
+ "version": "1.3.3",
4
4
  "description": "Browser Extension Manager dependency manager",
5
5
  "main": "dist/index.js",
6
6
  "exports": {