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.
- package/dist/background.js +95 -23
- package/dist/lib/auth-helpers.js +53 -29
- package/package.json +1 -1
package/dist/background.js
CHANGED
|
@@ -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 -
|
|
306
|
-
|
|
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
|
-
//
|
|
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);
|
package/dist/lib/auth-helpers.js
CHANGED
|
@@ -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
|
-
*
|
|
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
|
-
* @
|
|
18
|
+
* @returns {Promise<string|null>} The ID token or null if not signed in
|
|
8
19
|
*/
|
|
9
|
-
async function
|
|
10
|
-
const {
|
|
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]
|
|
24
|
+
logger.log('[AUTH-SYNC] Requesting fresh ID token from background...');
|
|
20
25
|
|
|
21
|
-
//
|
|
22
|
-
await
|
|
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
|
-
|
|
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
|
-
|
|
27
|
-
|
|
40
|
+
logger.error('[AUTH-SYNC] Error getting ID token:', error.message);
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
28
44
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
|
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?.
|
|
50
|
-
logger.log('[AUTH-SYNC] Found existing auth state
|
|
51
|
-
|
|
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
|
-
//
|
|
88
|
-
if (newAuthState?.
|
|
89
|
-
|
|
111
|
+
// User signed in - request fresh ID token from background
|
|
112
|
+
if (newAuthState?.user) {
|
|
113
|
+
syncAuthFromBackground(context);
|
|
90
114
|
}
|
|
91
115
|
});
|
|
92
116
|
|