browser-extension-manager 1.3.15 → 1.3.16

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.
@@ -68,6 +68,9 @@ class Manager {
68
68
  // Setup auth storage listener (detect sign-out from pages)
69
69
  this.setupAuthStorageListener();
70
70
 
71
+ // Restore auth state from storage on startup
72
+ this.restoreAuthState();
73
+
71
74
  // Setup livereload
72
75
  this.setupLiveReload();
73
76
 
@@ -120,10 +123,42 @@ class Manager {
120
123
  }
121
124
  });
122
125
 
126
+ // Listen for runtime messages (from popup, options, pages, etc.)
127
+ this.extension.runtime.onMessage.addListener((message, _sender, sendResponse) => {
128
+ // Handle auth ID token requests from other contexts
129
+ // Other contexts use storage listener to know WHEN to request, this just provides the token
130
+ if (message.command === 'bxm:getIdToken') {
131
+ this.handleGetIdToken(sendResponse);
132
+ return true; // Keep channel open for async response
133
+ }
134
+ });
135
+
123
136
  // Log
124
137
  this.logger.log('Set up message handlers');
125
138
  }
126
139
 
140
+ // Handle ID token request from other contexts
141
+ // Called when other contexts detect auth state in storage and need a fresh token to sign in
142
+ async handleGetIdToken(sendResponse) {
143
+ try {
144
+ // Check if Firebase auth is initialized and user is signed in
145
+ if (!this.libraries.firebaseAuth?.currentUser) {
146
+ this.logger.log('[AUTH] getIdToken: No user signed in');
147
+ sendResponse({ success: false, error: 'No user signed in' });
148
+ return;
149
+ }
150
+
151
+ // Get fresh ID token (Firebase auto-refreshes if needed)
152
+ const idToken = await this.libraries.firebaseAuth.currentUser.getIdToken(true);
153
+
154
+ this.logger.log('[AUTH] getIdToken: Providing fresh ID token');
155
+ sendResponse({ success: true, idToken: idToken });
156
+ } catch (error) {
157
+ this.logger.error('[AUTH] getIdToken error:', error.message);
158
+ sendResponse({ success: false, error: error.message });
159
+ }
160
+ }
161
+
127
162
  // Initialize Firebase
128
163
  initializeFirebase() {
129
164
  // Get Firebase config
@@ -266,6 +301,58 @@ class Manager {
266
301
  this.logger.log('[AUTH] Auth storage listener set up');
267
302
  }
268
303
 
304
+ // Restore auth state on startup
305
+ // Firebase Auth persists sessions in IndexedDB - we just need to initialize it
306
+ // and onAuthStateChanged will fire if there's a persisted session
307
+ async restoreAuthState() {
308
+ try {
309
+ // Check for existing auth state in storage (for logging purposes)
310
+ const result = await new Promise(resolve =>
311
+ this.extension.storage.local.get('bxm:authState', resolve)
312
+ );
313
+ const authState = result['bxm:authState'];
314
+
315
+ // Log existing storage state
316
+ if (authState) {
317
+ this.logger.log('[AUTH] Found existing auth state in storage on startup:', {
318
+ user: authState.user?.email || 'unknown',
319
+ timestamp: authState.timestamp,
320
+ age: authState.timestamp ? `${Math.round((Date.now() - authState.timestamp) / 1000 / 60)} minutes ago` : 'unknown',
321
+ });
322
+ } else {
323
+ this.logger.log('[AUTH] No existing auth state found in storage on startup');
324
+ }
325
+
326
+ // Get Firebase config
327
+ const firebaseConfig = this.config?.firebase?.app?.config;
328
+ if (!firebaseConfig) {
329
+ this.logger.log('[AUTH] Firebase config not available, skipping auth restore');
330
+ return;
331
+ }
332
+
333
+ // Initialize Firebase auth - it will auto-restore from IndexedDB if session exists
334
+ // onAuthStateChanged (set up in getFirebaseAuth) will fire and sync to storage
335
+ this.logger.log('[AUTH] Initializing Firebase Auth (will restore persisted session if any)...');
336
+ const auth = this.getFirebaseAuth();
337
+
338
+ // Check if already signed in (Firebase restored from IndexedDB)
339
+ if (auth.currentUser) {
340
+ this.logger.log('[AUTH] Firebase restored session from persistence:', auth.currentUser.email);
341
+ } else {
342
+ this.logger.log('[AUTH] No persisted Firebase session found');
343
+
344
+ // If storage has auth state but Firebase doesn't, storage is stale - clear it
345
+ if (authState) {
346
+ this.logger.log('[AUTH] Clearing stale auth state from storage');
347
+ await this.extension.storage.local.remove('bxm:authState');
348
+ }
349
+ }
350
+
351
+ } catch (error) {
352
+ this.logger.error('[AUTH] Error restoring auth state:', error.message);
353
+ }
354
+ }
355
+
269
356
  // Get or initialize Firebase auth (reuse existing instance)
270
357
  getFirebaseAuth() {
271
358
  // Return existing instance if available
@@ -298,19 +385,14 @@ class Manager {
298
385
  }
299
386
 
300
387
  // Handle Firebase auth state changes (source of truth for all contexts)
388
+ // Syncs user info to storage (NOT tokens - those are requested via messaging)
301
389
  async handleAuthStateChange(user) {
302
390
  this.logger.log('[AUTH] Auth state changed:', user?.email || 'signed out');
303
391
 
304
392
  if (user) {
305
- // User is signed in - get current stored state to preserve token
306
- const result = await new Promise(resolve =>
307
- this.extension.storage.local.get('bxm:authState', resolve)
308
- );
309
- const currentState = result['bxm:authState'] || {};
310
-
311
- // Update auth state with current user info
393
+ // User is signed in - store user info only (no tokens, they expire)
394
+ // Other contexts will request fresh ID tokens via bxm:getIdToken message
312
395
  const authState = {
313
- token: currentState.token, // Preserve existing token
314
396
  user: {
315
397
  uid: user.uid,
316
398
  email: user.email,
@@ -322,7 +404,7 @@ class Manager {
322
404
  };
323
405
 
324
406
  await this.extension.storage.local.set({ 'bxm:authState': authState });
325
- this.logger.log('[AUTH] Auth state synced to storage');
407
+ this.logger.log('[AUTH] Auth state synced to storage (user info only)');
326
408
  } else {
327
409
  // User is signed out - clear storage
328
410
  await this.extension.storage.local.remove('bxm:authState');
@@ -330,7 +412,7 @@ class Manager {
330
412
  }
331
413
  }
332
414
 
333
- // Handle auth token from website
415
+ // Handle auth token from website (custom token from /token page)
334
416
  async handleAuthToken(token, tabId, authSourceTabId = null) {
335
417
  try {
336
418
  // Log
@@ -340,6 +422,8 @@ class Manager {
340
422
  const auth = this.getFirebaseAuth();
341
423
 
342
424
  // Sign in with custom token
425
+ // Firebase Auth will persist the session - we don't store the custom token (it expires in 1 hour)
426
+ // Other contexts will request fresh ID tokens via bxm:getIdToken message
343
427
  this.logger.log('[AUTH] Calling signInWithCustomToken...');
344
428
  const userCredential = await signInWithCustomToken(auth, token);
345
429
  const user = userCredential.user;
@@ -347,19 +431,7 @@ class Manager {
347
431
  // Log
348
432
  this.logger.log('[AUTH] Signed in successfully:', user.email);
349
433
 
350
- // Save token to storage (user state will be synced by onAuthStateChanged)
351
- const result = await new Promise(resolve =>
352
- this.extension.storage.local.get('bxm:authState', resolve)
353
- );
354
- const currentState = result['bxm:authState'] || {};
355
-
356
- await this.extension.storage.local.set({
357
- 'bxm:authState': {
358
- ...currentState,
359
- token: token,
360
- timestamp: Date.now(),
361
- }
362
- });
434
+ // Note: onAuthStateChanged will sync user info to storage automatically
363
435
 
364
436
  // Close the auth tab
365
437
  await this.extension.tabs.remove(tabId);
@@ -1,36 +1,59 @@
1
1
  // Auth helpers for cross-context auth sync in browser extensions
2
2
  // Used by popup.js, options.js, sidepanel.js, page.js
3
+ //
4
+ // Architecture:
5
+ // - Background.js is the source of truth for Firebase Auth (persists in IndexedDB)
6
+ // - Storage (bxm:authState): Contains user info only, used for UI updates and signaling auth changes
7
+ // - Messages (bxm:getIdToken): Request fresh ID token from background.js when needed
8
+ //
9
+ // Flow:
10
+ // 1. Storage listener detects auth state change (user signed in/out)
11
+ // 2. If signed in, request fresh ID token from background.js via message
12
+ // 3. Sign in local Firebase with the ID token using signInWithCustomToken workaround
3
13
 
4
14
  /**
5
- * Sign in with custom token from storage
15
+ * Request fresh ID token from background.js
16
+ * The ID token can be used for authenticated API calls
6
17
  * @param {Object} context - The manager instance
7
- * @param {Object} authState - Auth state with token
18
+ * @returns {Promise<string|null>} The ID token or null if not signed in
8
19
  */
9
- async function signInWithStoredToken(context, authState) {
10
- const { webManager, logger } = context;
11
-
12
- // Skip if no token
13
- if (!authState?.token) {
14
- logger.log('[AUTH-SYNC] No token in auth state, skipping sign in');
15
- return;
16
- }
20
+ export async function getIdToken(context) {
21
+ const { extension, logger } = context;
17
22
 
18
23
  try {
19
- logger.log('[AUTH-SYNC] Signing in with stored token...');
24
+ logger.log('[AUTH-SYNC] Requesting fresh ID token from background...');
20
25
 
21
- // Sign in using webManager's auth which initializes Firebase
22
- await webManager.auth().signInWithCustomToken(authState.token);
26
+ // Request fresh ID token from background.js
27
+ const response = await new Promise((resolve) => {
28
+ extension.runtime.sendMessage({ command: 'bxm:getIdToken' }, resolve);
29
+ });
23
30
 
24
- logger.log('[AUTH-SYNC] Signed in successfully:', authState.user?.email);
31
+ // Check response
32
+ if (!response?.success) {
33
+ logger.log('[AUTH-SYNC] Failed to get ID token:', response?.error || 'Unknown error');
34
+ return null;
35
+ }
36
+
37
+ logger.log('[AUTH-SYNC] Got fresh ID token from background');
38
+ return response.idToken;
25
39
  } catch (error) {
26
- // Token may have expired, clear it
27
- logger.error('[AUTH-SYNC] Error signing in with token:', error.message);
40
+ logger.error('[AUTH-SYNC] Error getting ID token:', error.message);
41
+ return null;
42
+ }
43
+ }
28
44
 
29
- // If token is invalid/expired, clear the auth state
30
- if (error.code === 'auth/invalid-custom-token' || error.code === 'auth/custom-token-expired') {
31
- logger.log('[AUTH-SYNC] Token expired, clearing auth state');
32
- context.extension.storage.local.remove('bxm:authState');
33
- }
45
+ /**
46
+ * Sync auth state from background.js
47
+ * Fetches fresh ID token and stores for API calls
48
+ * @param {Object} context - The manager instance
49
+ */
50
+ async function syncAuthFromBackground(context) {
51
+ const idToken = await getIdToken(context);
52
+
53
+ if (idToken) {
54
+ // Store on context for API calls
55
+ context._idToken = idToken;
56
+ context.logger.log('[AUTH-SYNC] ID token synced and stored');
34
57
  }
35
58
  }
36
59
 
@@ -42,13 +65,14 @@ async function signInWithStoredToken(context, authState) {
42
65
  export function setupAuthStorageListener(context) {
43
66
  const { extension, webManager, logger } = context;
44
67
 
45
- // Check existing auth state on load and sign in
68
+ // Check existing auth state on load
46
69
  extension.storage.local.get('bxm:authState', (result) => {
47
70
  const authState = result['bxm:authState'];
48
71
 
49
- if (authState?.token) {
50
- logger.log('[AUTH-SYNC] Found existing auth state, signing in...', authState.user?.email);
51
- signInWithStoredToken(context, authState);
72
+ if (authState?.user) {
73
+ logger.log('[AUTH-SYNC] Found existing auth state on load:', authState.user?.email);
74
+ // Request fresh ID token from background for any API calls
75
+ syncAuthFromBackground(context);
52
76
  }
53
77
  });
54
78
 
@@ -63,7 +87,6 @@ export function setupAuthStorageListener(context) {
63
87
  });
64
88
 
65
89
  // Listen for storage changes from background.js
66
- // Note: BEM normalizes storage to sync or local, so we listen to all areas
67
90
  extension.storage.onChanged.addListener((changes) => {
68
91
  // Check for auth state change
69
92
  const authChange = changes['bxm:authState'];
@@ -80,13 +103,14 @@ export function setupAuthStorageListener(context) {
80
103
  // If auth state was cleared (signed out)
81
104
  if (!newAuthState) {
82
105
  logger.log('[AUTH-SYNC] Auth state cleared, signing out...');
106
+ context._idToken = null;
83
107
  webManager.auth().signOut();
84
108
  return;
85
109
  }
86
110
 
87
- // Sign in with the new token
88
- if (newAuthState?.token) {
89
- signInWithStoredToken(context, newAuthState);
111
+ // User signed in - request fresh ID token from background
112
+ if (newAuthState?.user) {
113
+ syncAuthFromBackground(context);
90
114
  }
91
115
  });
92
116
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "browser-extension-manager",
3
- "version": "1.3.15",
3
+ "version": "1.3.16",
4
4
  "description": "Browser Extension Manager dependency manager",
5
5
  "main": "dist/index.js",
6
6
  "exports": {