browser-extension-manager 1.3.15 → 1.3.17
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 +27 -37
- package/README.md +18 -87
- package/dist/background.js +189 -60
- package/dist/lib/auth-helpers.js +105 -67
- package/dist/options.js +9 -3
- package/dist/page.js +9 -3
- package/dist/popup.js +9 -3
- package/dist/sidepanel.js +9 -3
- package/package.json +1 -1
package/CLAUDE.md
CHANGED
|
@@ -354,11 +354,7 @@ Provides cross-context auth synchronization and reusable auth UI event handlers.
|
|
|
354
354
|
|
|
355
355
|
### Cross-Context Auth Architecture
|
|
356
356
|
|
|
357
|
-
**Background.js is the source of truth** for authentication. Browser extensions have multiple isolated JavaScript contexts (background, popup, options, pages, sidepanel) - each runs its own Firebase instance. BEM
|
|
358
|
-
|
|
359
|
-
1. Background.js monitors for auth tokens via `tabs.onUpdated`
|
|
360
|
-
2. When detected, background signs in Firebase and saves to `chrome.storage`
|
|
361
|
-
3. Other contexts listen to storage changes and sync their Firebase instances
|
|
357
|
+
**Background.js is the source of truth** for authentication. Browser extensions have multiple isolated JavaScript contexts (background, popup, options, pages, sidepanel) - each runs its own Firebase instance. BEM syncs them via messaging (no storage).
|
|
362
358
|
|
|
363
359
|
**Sign-in Flow:**
|
|
364
360
|
```
|
|
@@ -366,29 +362,36 @@ User clicks .auth-signin-btn
|
|
|
366
362
|
→ openAuthPage() opens https://{authDomain}/token?authSourceTabId=123
|
|
367
363
|
→ Website authenticates, redirects to /token?authToken=xxx
|
|
368
364
|
→ background.js tabs.onUpdated detects authDomain URL with authToken param
|
|
369
|
-
→ background.js
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
365
|
+
→ background.js signs in with signInWithCustomToken()
|
|
366
|
+
→ background.js broadcasts token to all open contexts via postMessage()
|
|
367
|
+
→ Closes the /token tab, reactivates original tab
|
|
368
|
+
→ Open contexts receive broadcast and sign in with the token
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
**Context Load Flow:**
|
|
372
|
+
```
|
|
373
|
+
Context loads (popup, page, options, sidepanel)
|
|
374
|
+
→ Web Manager initializes, waits for auth to settle via auth.listen({once: true})
|
|
375
|
+
→ Sends bxm:syncAuth message to background with local UID
|
|
376
|
+
→ Background compares UIDs:
|
|
377
|
+
→ Same UID (including both null) → already in sync, no action
|
|
378
|
+
→ Different UID → background fetches fresh custom token from server, sends to context
|
|
379
|
+
→ Background signed out, context signed in → tells context to sign out
|
|
376
380
|
```
|
|
377
381
|
|
|
378
382
|
**Sign-out Flow:**
|
|
379
383
|
```
|
|
380
384
|
User clicks .auth-signout-btn
|
|
381
|
-
→ Web Manager signs out Firebase
|
|
382
|
-
→
|
|
383
|
-
→
|
|
384
|
-
→ background.js
|
|
385
|
-
→
|
|
386
|
-
→ Other contexts detect storage cleared and sign out
|
|
385
|
+
→ Web Manager signs out that context's Firebase
|
|
386
|
+
→ setupSignOutListener() detects sign-out, sends bxm:signOut to background
|
|
387
|
+
→ background.js signs out its Firebase
|
|
388
|
+
→ background.js broadcasts bxm:signOut to all other contexts
|
|
389
|
+
→ All contexts sign out
|
|
387
390
|
```
|
|
388
391
|
|
|
389
392
|
**Key Implementation Details:**
|
|
390
393
|
|
|
391
|
-
1. **
|
|
394
|
+
1. **No storage**: Auth state is NOT stored in `chrome.storage`. Firebase persists sessions in IndexedDB per context. Web Manager handles UI bindings.
|
|
392
395
|
|
|
393
396
|
2. **Firebase in service workers**: Static ES6 imports are required. Dynamic `import()` fails with webpack chunking in service workers.
|
|
394
397
|
|
|
@@ -397,7 +400,9 @@ User clicks .auth-signout-btn
|
|
|
397
400
|
4. **Required permission**: `tabs` permission needed for `tabs.onUpdated` listener.
|
|
398
401
|
|
|
399
402
|
**Functions:**
|
|
400
|
-
- `
|
|
403
|
+
- `syncWithBackground(context)` - Compares context's UID with background's UID on load, syncs if different
|
|
404
|
+
- `setupAuthBroadcastListener(context)` - Listens for sign-in/sign-out broadcasts from background
|
|
405
|
+
- `setupSignOutListener(context)` - Notifies background when context signs out
|
|
401
406
|
- `setupAuthEventListeners(context)` - Sets up delegated click handlers for auth buttons
|
|
402
407
|
- `openAuthPage(context, options)` - Opens auth page on the website with authSourceTabId for tab restoration
|
|
403
408
|
|
|
@@ -432,21 +437,6 @@ Add these classes to your HTML elements to enable automatic auth handling:
|
|
|
432
437
|
- `data-wm-bind="@text auth.user.email"` - Display user's email
|
|
433
438
|
- `data-wm-bind="@attr src auth.user.photoURL"` - Set avatar image src
|
|
434
439
|
|
|
435
|
-
**Storage Schema (`bxm:authState`):**
|
|
436
|
-
```javascript
|
|
437
|
-
{
|
|
438
|
-
token: "firebase-custom-token", // Used by other contexts to sign in
|
|
439
|
-
user: {
|
|
440
|
-
uid: "...",
|
|
441
|
-
email: "...",
|
|
442
|
-
displayName: "...",
|
|
443
|
-
photoURL: "...",
|
|
444
|
-
emailVerified: true
|
|
445
|
-
},
|
|
446
|
-
timestamp: 1234567890
|
|
447
|
-
}
|
|
448
|
-
```
|
|
449
|
-
|
|
450
440
|
**Logger:** [src/lib/logger.js](src/lib/logger.js)
|
|
451
441
|
- Full logging utility
|
|
452
442
|
- [src/lib/logger-lite.js](src/lib/logger-lite.js) for lightweight contexts
|
|
@@ -808,8 +798,8 @@ Manager.initialize().then(() => {
|
|
|
808
798
|
- [src/cli.js](src/cli.js) - CLI implementation
|
|
809
799
|
|
|
810
800
|
**Auth System (Cross-Context):**
|
|
811
|
-
- [src/background.js](src/background.js) - Source of truth
|
|
812
|
-
- [src/lib/auth-helpers.js](src/lib/auth-helpers.js) - `
|
|
801
|
+
- [src/background.js](src/background.js) - Source of truth; `handleSyncAuth()`, `handleSignOut()`, `broadcastAuthToken()`
|
|
802
|
+
- [src/lib/auth-helpers.js](src/lib/auth-helpers.js) - `syncWithBackground()`, `setupAuthBroadcastListener()`, `setupSignOutListener()`
|
|
813
803
|
|
|
814
804
|
**CSS Framework:**
|
|
815
805
|
- [src/assets/css/browser-extension-manager.scss](src/assets/css/browser-extension-manager.scss) - Main entry
|
package/README.md
CHANGED
|
@@ -88,112 +88,43 @@ Only stores with configured credentials will be published to.
|
|
|
88
88
|
|
|
89
89
|
## 🔐 Authentication
|
|
90
90
|
|
|
91
|
-
BEM provides built-in authentication
|
|
91
|
+
BEM provides built-in authentication that syncs across all extension contexts (popup, options, pages, sidepanel, background).
|
|
92
92
|
|
|
93
|
-
###
|
|
93
|
+
### How It Works
|
|
94
94
|
|
|
95
|
-
**Background.js is the source of truth
|
|
95
|
+
**Background.js is the source of truth.** Auth syncs via messaging (no storage).
|
|
96
96
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
├─────────────────────────────────────────────────────────────────────────┤
|
|
101
|
-
│ 1. User clicks .auth-signin-btn in any context │
|
|
102
|
-
│ 2. Extension opens https://{authDomain}/token?authSourceTabId=123 │
|
|
103
|
-
│ 3. Website authenticates user, redirects to /token?authToken=xxx │
|
|
104
|
-
│ 4. Background.js detects URL via tabs.onUpdated listener │
|
|
105
|
-
│ 5. Background signs in Firebase with custom token │
|
|
106
|
-
│ 6. Background saves auth state to chrome.storage (bxm:authState) │
|
|
107
|
-
│ 7. Background closes /token tab and reactivates original tab │
|
|
108
|
-
│ 8. Other contexts detect storage change and sign in their Firebase │
|
|
109
|
-
└─────────────────────────────────────────────────────────────────────────┘
|
|
110
|
-
|
|
111
|
-
┌─────────────────────────────────────────────────────────────────────────┐
|
|
112
|
-
│ SIGN-OUT FLOW │
|
|
113
|
-
├─────────────────────────────────────────────────────────────────────────┤
|
|
114
|
-
│ 1. User clicks .auth-signout-btn in any context │
|
|
115
|
-
│ 2. Web Manager signs out Firebase locally │
|
|
116
|
-
│ 3. Auth helper detects WM auth change, clears bxm:authState storage │
|
|
117
|
-
│ 4. Background.js detects storage cleared, signs out its Firebase │
|
|
118
|
-
│ 5. Other contexts detect storage change and sign out │
|
|
119
|
-
└─────────────────────────────────────────────────────────────────────────┘
|
|
120
|
-
```
|
|
121
|
-
|
|
122
|
-
### Required Configuration
|
|
123
|
-
|
|
124
|
-
Add `authDomain` to your Firebase config in `config/browser-extension-manager.json`:
|
|
125
|
-
|
|
126
|
-
```json
|
|
127
|
-
{
|
|
128
|
-
"firebaseConfig": {
|
|
129
|
-
"apiKey": "...",
|
|
130
|
-
"authDomain": "your-app.firebaseapp.com",
|
|
131
|
-
"projectId": "..."
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
```
|
|
97
|
+
- **Sign-in**: User clicks `.auth-signin-btn` → opens `/token` page on website → website authenticates and redirects with token → background.js signs in and broadcasts to all open contexts
|
|
98
|
+
- **Context load**: Each context compares its UID with background's UID on load; syncs if different
|
|
99
|
+
- **Sign-out**: User clicks `.auth-signout-btn` → context signs out → notifies background → background broadcasts sign-out to all contexts
|
|
135
100
|
|
|
136
|
-
### Required
|
|
101
|
+
### Required Setup
|
|
137
102
|
|
|
138
|
-
Add
|
|
139
|
-
|
|
140
|
-
```json
|
|
141
|
-
{
|
|
142
|
-
"permissions": ["tabs"]
|
|
143
|
-
}
|
|
144
|
-
```
|
|
145
|
-
|
|
146
|
-
This is required for background.js to monitor tab URL changes and detect auth tokens.
|
|
103
|
+
1. Add `authDomain` to your Firebase config in `config/browser-extension-manager.json`
|
|
104
|
+
2. Add `tabs` permission to `src/manifest.json` (for URL monitoring)
|
|
147
105
|
|
|
148
106
|
### Auth Button Classes
|
|
149
107
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
|
153
|
-
|
|
154
|
-
| `.auth-
|
|
155
|
-
| `.auth-signout-btn` | Sign out button | Signs out via Web Manager (which triggers storage sync) |
|
|
156
|
-
| `.auth-account-btn` | Account button | Opens `/account` page on website |
|
|
108
|
+
| Class | Action |
|
|
109
|
+
|-------|--------|
|
|
110
|
+
| `.auth-signin-btn` | Opens `/token` page on website |
|
|
111
|
+
| `.auth-signout-btn` | Signs out via Web Manager |
|
|
112
|
+
| `.auth-account-btn` | Opens `/account` page on website |
|
|
157
113
|
|
|
158
114
|
### Example
|
|
159
115
|
```html
|
|
160
|
-
|
|
161
|
-
<button class="btn auth-signin-btn" data-wm-bind="@show !auth.user">
|
|
162
|
-
Sign In
|
|
163
|
-
</button>
|
|
116
|
+
<button class="btn auth-signin-btn" data-wm-bind="@show !auth.user">Sign In</button>
|
|
164
117
|
|
|
165
|
-
<!-- Account Section (shown when logged in) -->
|
|
166
118
|
<div data-wm-bind="@show auth.user" hidden>
|
|
167
119
|
<span data-wm-bind="@text auth.user.displayName">User</span>
|
|
168
|
-
<a class="auth-account-btn" href="#">Account</a>
|
|
169
120
|
<button class="auth-signout-btn">Sign Out</button>
|
|
170
121
|
</div>
|
|
171
122
|
```
|
|
172
123
|
|
|
173
124
|
### Reactive Bindings
|
|
174
|
-
- `
|
|
175
|
-
- `
|
|
176
|
-
-
|
|
177
|
-
- `data-wm-bind="@text auth.user.email"` - Display user's email
|
|
178
|
-
- `data-wm-bind="@attr src auth.user.photoURL"` - Set avatar image src
|
|
179
|
-
|
|
180
|
-
### Storage Key
|
|
181
|
-
|
|
182
|
-
Auth state is stored in `chrome.storage` under the key `bxm:authState`:
|
|
183
|
-
|
|
184
|
-
```javascript
|
|
185
|
-
{
|
|
186
|
-
token: "firebase-custom-token",
|
|
187
|
-
user: {
|
|
188
|
-
uid: "...",
|
|
189
|
-
email: "...",
|
|
190
|
-
displayName: "...",
|
|
191
|
-
photoURL: "...",
|
|
192
|
-
emailVerified: true
|
|
193
|
-
},
|
|
194
|
-
timestamp: 1234567890
|
|
195
|
-
}
|
|
196
|
-
```
|
|
125
|
+
- `@show auth.user` / `@show !auth.user` - Show/hide based on auth state
|
|
126
|
+
- `@text auth.user.displayName` / `@text auth.user.email` - Display user info
|
|
127
|
+
- `@attr src auth.user.photoURL` - Set avatar image
|
|
197
128
|
|
|
198
129
|
<!-- ## ⛳️ Flags
|
|
199
130
|
* `--test=false` - Coming soon
|
package/dist/background.js
CHANGED
|
@@ -65,8 +65,8 @@ class Manager {
|
|
|
65
65
|
// Setup auth token listener (for cross-runtime auth)
|
|
66
66
|
this.setupAuthTokenListener();
|
|
67
67
|
|
|
68
|
-
//
|
|
69
|
-
this.
|
|
68
|
+
// Initialize Firebase auth on startup (restores persisted session if any)
|
|
69
|
+
this.initializeAuth();
|
|
70
70
|
|
|
71
71
|
// Setup livereload
|
|
72
72
|
this.setupLiveReload();
|
|
@@ -120,10 +120,147 @@ class Manager {
|
|
|
120
120
|
}
|
|
121
121
|
});
|
|
122
122
|
|
|
123
|
+
// Listen for runtime messages (from popup, options, pages, etc.)
|
|
124
|
+
this.extension.runtime.onMessage.addListener((message, _sender, sendResponse) => {
|
|
125
|
+
// Handle auth sync requests - contexts ask background for auth state on load
|
|
126
|
+
if (message.command === 'bxm:syncAuth') {
|
|
127
|
+
this.handleSyncAuth(message, sendResponse);
|
|
128
|
+
return true; // Keep channel open for async response
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Handle sign-out requests from contexts
|
|
132
|
+
if (message.command === 'bxm:signOut') {
|
|
133
|
+
this.handleSignOut(sendResponse);
|
|
134
|
+
return true; // Keep channel open for async response
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
|
|
123
138
|
// Log
|
|
124
139
|
this.logger.log('Set up message handlers');
|
|
125
140
|
}
|
|
126
141
|
|
|
142
|
+
// Handle auth sync request from other contexts (popup, page, options, sidepanel)
|
|
143
|
+
// Compares context's UID with background's UID and provides fresh token only if different
|
|
144
|
+
async handleSyncAuth(message, sendResponse) {
|
|
145
|
+
try {
|
|
146
|
+
const contextUid = message.contextUid || null; // UID from asking context (or null)
|
|
147
|
+
|
|
148
|
+
// Get or initialize Firebase auth
|
|
149
|
+
const auth = this.getFirebaseAuth();
|
|
150
|
+
const bgUser = auth.currentUser;
|
|
151
|
+
const bgUid = bgUser?.uid || null;
|
|
152
|
+
|
|
153
|
+
this.logger.log('[AUTH] syncAuth: Comparing UIDs - context:', contextUid, 'background:', bgUid);
|
|
154
|
+
|
|
155
|
+
// Already in sync (both null, or same UID)
|
|
156
|
+
if (contextUid === bgUid) {
|
|
157
|
+
this.logger.log('[AUTH] syncAuth: Already in sync');
|
|
158
|
+
sendResponse({ needsSync: false });
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Context is signed in but background is not → context should sign out
|
|
163
|
+
if (!bgUser && contextUid) {
|
|
164
|
+
this.logger.log('[AUTH] syncAuth: Background signed out, telling context to sign out');
|
|
165
|
+
sendResponse({ needsSync: true, signOut: true });
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Background is signed in, context is not (or different user) → provide token
|
|
170
|
+
this.logger.log('[AUTH] syncAuth: Fetching fresh custom token for context...', bgUser.email);
|
|
171
|
+
|
|
172
|
+
// Get API URL from config
|
|
173
|
+
const apiUrl = this.config?.web_manager?.api?.url || 'https://api.itwcreativeworks.com';
|
|
174
|
+
|
|
175
|
+
// Get fresh ID token for authorization
|
|
176
|
+
const idToken = await bgUser.getIdToken(true);
|
|
177
|
+
|
|
178
|
+
// Fetch fresh custom token from server
|
|
179
|
+
const response = await fetch(`${apiUrl}/backend-manager`, {
|
|
180
|
+
method: 'POST',
|
|
181
|
+
headers: {
|
|
182
|
+
'Content-Type': 'application/json',
|
|
183
|
+
'Authorization': `Bearer ${idToken}`,
|
|
184
|
+
},
|
|
185
|
+
body: JSON.stringify({
|
|
186
|
+
command: 'user:create-custom-token',
|
|
187
|
+
payload: {},
|
|
188
|
+
}),
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
// Check response
|
|
192
|
+
if (!response.ok) {
|
|
193
|
+
throw new Error(`Server responded with ${response.status}`);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Parse response
|
|
197
|
+
const data = await response.json();
|
|
198
|
+
|
|
199
|
+
// Check for token in response
|
|
200
|
+
if (!data.response?.token) {
|
|
201
|
+
throw new Error('No token in server response');
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
this.logger.log('[AUTH] syncAuth: Got fresh custom token, sending to context');
|
|
205
|
+
|
|
206
|
+
// Send user info and fresh custom token
|
|
207
|
+
sendResponse({
|
|
208
|
+
needsSync: true,
|
|
209
|
+
customToken: data.response.token,
|
|
210
|
+
user: {
|
|
211
|
+
uid: bgUser.uid,
|
|
212
|
+
email: bgUser.email,
|
|
213
|
+
displayName: bgUser.displayName,
|
|
214
|
+
photoURL: bgUser.photoURL,
|
|
215
|
+
emailVerified: bgUser.emailVerified,
|
|
216
|
+
},
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
} catch (error) {
|
|
220
|
+
this.logger.error('[AUTH] syncAuth error:', error.message);
|
|
221
|
+
sendResponse({ needsSync: false, error: error.message });
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Handle sign-out request from a context
|
|
226
|
+
// Signs out background's Firebase and broadcasts to all other contexts
|
|
227
|
+
async handleSignOut(sendResponse) {
|
|
228
|
+
try {
|
|
229
|
+
this.logger.log('[AUTH] handleSignOut: Signing out background Firebase...');
|
|
230
|
+
|
|
231
|
+
// Sign out background's Firebase
|
|
232
|
+
if (this.libraries.firebaseAuth?.currentUser) {
|
|
233
|
+
await this.libraries.firebaseAuth.signOut();
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Broadcast to all contexts
|
|
237
|
+
await this.broadcastSignOut();
|
|
238
|
+
|
|
239
|
+
this.logger.log('[AUTH] handleSignOut: Complete');
|
|
240
|
+
sendResponse({ success: true });
|
|
241
|
+
} catch (error) {
|
|
242
|
+
this.logger.error('[AUTH] handleSignOut error:', error.message);
|
|
243
|
+
sendResponse({ success: false, error: error.message });
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Broadcast sign-out to all open extension contexts
|
|
248
|
+
async broadcastSignOut() {
|
|
249
|
+
try {
|
|
250
|
+
const clients = await self.clients.matchAll({ type: 'all' });
|
|
251
|
+
|
|
252
|
+
this.logger.log(`[AUTH] Broadcasting sign-out to ${clients.length} clients...`);
|
|
253
|
+
|
|
254
|
+
for (const client of clients) {
|
|
255
|
+
client.postMessage({ command: 'bxm:signOut' });
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
this.logger.log('[AUTH] Sign-out broadcast complete');
|
|
259
|
+
} catch (error) {
|
|
260
|
+
this.logger.error('[AUTH] Error broadcasting sign-out:', error.message);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
127
264
|
// Initialize Firebase
|
|
128
265
|
initializeFirebase() {
|
|
129
266
|
// Get Firebase config
|
|
@@ -246,24 +383,26 @@ class Manager {
|
|
|
246
383
|
});
|
|
247
384
|
}
|
|
248
385
|
|
|
249
|
-
//
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
386
|
+
// Initialize Firebase auth on startup
|
|
387
|
+
// Firebase Auth persists sessions in IndexedDB - we just need to initialize it
|
|
388
|
+
initializeAuth() {
|
|
389
|
+
// Get Firebase config
|
|
390
|
+
const firebaseConfig = this.config?.firebase?.app?.config;
|
|
391
|
+
if (!firebaseConfig) {
|
|
392
|
+
this.logger.log('[AUTH] Firebase config not available, skipping auth initialization');
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
258
395
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
this.libraries.firebaseAuth.signOut();
|
|
263
|
-
}
|
|
264
|
-
});
|
|
396
|
+
// Initialize Firebase auth - it will auto-restore from IndexedDB if session exists
|
|
397
|
+
this.logger.log('[AUTH] Initializing Firebase Auth (will restore persisted session if any)...');
|
|
398
|
+
const auth = this.getFirebaseAuth();
|
|
265
399
|
|
|
266
|
-
|
|
400
|
+
// Check if already signed in (Firebase restored from IndexedDB)
|
|
401
|
+
if (auth.currentUser) {
|
|
402
|
+
this.logger.log('[AUTH] Firebase restored session from persistence:', auth.currentUser.email);
|
|
403
|
+
} else {
|
|
404
|
+
this.logger.log('[AUTH] No persisted Firebase session found');
|
|
405
|
+
}
|
|
267
406
|
}
|
|
268
407
|
|
|
269
408
|
// Get or initialize Firebase auth (reuse existing instance)
|
|
@@ -298,39 +437,13 @@ class Manager {
|
|
|
298
437
|
}
|
|
299
438
|
|
|
300
439
|
// Handle Firebase auth state changes (source of truth for all contexts)
|
|
301
|
-
|
|
440
|
+
// No storage operations - Web Manager handles auth state internally
|
|
441
|
+
handleAuthStateChange(user) {
|
|
302
442
|
this.logger.log('[AUTH] Auth state changed:', user?.email || 'signed out');
|
|
303
|
-
|
|
304
|
-
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
|
|
312
|
-
const authState = {
|
|
313
|
-
token: currentState.token, // Preserve existing token
|
|
314
|
-
user: {
|
|
315
|
-
uid: user.uid,
|
|
316
|
-
email: user.email,
|
|
317
|
-
displayName: user.displayName,
|
|
318
|
-
photoURL: user.photoURL,
|
|
319
|
-
emailVerified: user.emailVerified,
|
|
320
|
-
},
|
|
321
|
-
timestamp: Date.now(),
|
|
322
|
-
};
|
|
323
|
-
|
|
324
|
-
await this.extension.storage.local.set({ 'bxm:authState': authState });
|
|
325
|
-
this.logger.log('[AUTH] Auth state synced to storage');
|
|
326
|
-
} else {
|
|
327
|
-
// User is signed out - clear storage
|
|
328
|
-
await this.extension.storage.local.remove('bxm:authState');
|
|
329
|
-
this.logger.log('[AUTH] Auth state cleared from storage');
|
|
330
|
-
}
|
|
443
|
+
// Nothing else to do - contexts sync via messages, WM handles UI
|
|
331
444
|
}
|
|
332
445
|
|
|
333
|
-
// Handle auth token from website
|
|
446
|
+
// Handle auth token from website (custom token from /token page)
|
|
334
447
|
async handleAuthToken(token, tabId, authSourceTabId = null) {
|
|
335
448
|
try {
|
|
336
449
|
// Log
|
|
@@ -347,19 +460,12 @@ class Manager {
|
|
|
347
460
|
// Log
|
|
348
461
|
this.logger.log('[AUTH] Signed in successfully:', user.email);
|
|
349
462
|
|
|
350
|
-
//
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
);
|
|
354
|
-
const currentState = result['bxm:authState'] || {};
|
|
463
|
+
// Broadcast token to all open extension contexts so they can sign in immediately
|
|
464
|
+
// Token is NOT stored - it expires in 1 hour and is only needed for initial sign-in
|
|
465
|
+
this.broadcastAuthToken(token);
|
|
355
466
|
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
...currentState,
|
|
359
|
-
token: token,
|
|
360
|
-
timestamp: Date.now(),
|
|
361
|
-
}
|
|
362
|
-
});
|
|
467
|
+
// Note: onAuthStateChanged will fire and store user info (without token) in storage
|
|
468
|
+
// This allows UI to show auth state, but token is never persisted
|
|
363
469
|
|
|
364
470
|
// Close the auth tab
|
|
365
471
|
await this.extension.tabs.remove(tabId);
|
|
@@ -381,6 +487,29 @@ class Manager {
|
|
|
381
487
|
}
|
|
382
488
|
}
|
|
383
489
|
|
|
490
|
+
// Broadcast auth token to all open extension contexts
|
|
491
|
+
// Used during initial sign-in to immediately sync all open popups, pages, etc.
|
|
492
|
+
async broadcastAuthToken(token) {
|
|
493
|
+
try {
|
|
494
|
+
// Get all clients (extension pages, popups, etc.)
|
|
495
|
+
const clients = await self.clients.matchAll({ type: 'all' });
|
|
496
|
+
|
|
497
|
+
this.logger.log(`[AUTH] Broadcasting token to ${clients.length} clients...`);
|
|
498
|
+
|
|
499
|
+
// Send token to each client
|
|
500
|
+
for (const client of clients) {
|
|
501
|
+
client.postMessage({
|
|
502
|
+
command: 'bxm:signInWithToken',
|
|
503
|
+
token: token,
|
|
504
|
+
});
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
this.logger.log('[AUTH] Token broadcast complete');
|
|
508
|
+
} catch (error) {
|
|
509
|
+
this.logger.error('[AUTH] Error broadcasting token:', error.message);
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
|
|
384
513
|
// Setup livereload
|
|
385
514
|
setupLiveReload() {
|
|
386
515
|
// Quit if not in dev mode
|
package/dist/lib/auth-helpers.js
CHANGED
|
@@ -1,97 +1,136 @@
|
|
|
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 authentication
|
|
6
|
+
// - On context load, contexts wait for WM auth to settle, then ask background if in sync
|
|
7
|
+
// - If out of sync, background provides a fresh custom token (fetched from server)
|
|
8
|
+
// - No BEM-specific storage - Web Manager handles auth state internally
|
|
3
9
|
|
|
4
10
|
/**
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* @param {Object}
|
|
11
|
+
* Sync auth state with background.js on context load
|
|
12
|
+
* Waits for WM auth to settle, then asks background if in sync
|
|
13
|
+
* @param {Object} context - The manager instance (must have extension, webManager, logger)
|
|
8
14
|
*/
|
|
9
|
-
async function
|
|
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
|
-
}
|
|
15
|
+
export async function syncWithBackground(context) {
|
|
16
|
+
const { extension, webManager, logger } = context;
|
|
17
17
|
|
|
18
18
|
try {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
19
|
+
// Wait for WM auth state to settle FIRST (prevents race conditions)
|
|
20
|
+
const localState = await new Promise(resolve => {
|
|
21
|
+
webManager.auth().listen({ once: true }, resolve);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
const localUid = localState.user?.uid || null;
|
|
25
|
+
logger.log('[AUTH-SYNC] Local auth state settled, UID:', localUid);
|
|
26
|
+
|
|
27
|
+
// Ask background for auth state comparison
|
|
28
|
+
const response = await new Promise((resolve) => {
|
|
29
|
+
extension.runtime.sendMessage(
|
|
30
|
+
{ command: 'bxm:syncAuth', contextUid: localUid },
|
|
31
|
+
(res) => {
|
|
32
|
+
if (extension.runtime.lastError) {
|
|
33
|
+
logger.log('[AUTH-SYNC] Background not ready:', extension.runtime.lastError.message);
|
|
34
|
+
resolve({ needsSync: false });
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
resolve(res || { needsSync: false });
|
|
38
|
+
}
|
|
39
|
+
);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
// Already in sync
|
|
43
|
+
if (!response.needsSync) {
|
|
44
|
+
logger.log('[AUTH-SYNC] Already in sync with background');
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
23
47
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
48
|
+
// Need to sign out (background is signed out, context is signed in)
|
|
49
|
+
if (response.signOut) {
|
|
50
|
+
logger.log('[AUTH-SYNC] Background signed out, signing out context...');
|
|
51
|
+
await webManager.auth().signOut();
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
28
54
|
|
|
29
|
-
//
|
|
30
|
-
if (
|
|
31
|
-
logger.log('[AUTH-SYNC]
|
|
32
|
-
|
|
55
|
+
// Need to sign in with token
|
|
56
|
+
if (response.customToken) {
|
|
57
|
+
logger.log('[AUTH-SYNC] Syncing with background...', response.user?.email);
|
|
58
|
+
await webManager.auth().signInWithCustomToken(response.customToken);
|
|
59
|
+
logger.log('[AUTH-SYNC] Synced successfully');
|
|
33
60
|
}
|
|
61
|
+
|
|
62
|
+
} catch (error) {
|
|
63
|
+
logger.error('[AUTH-SYNC] Error syncing with background:', error.message);
|
|
34
64
|
}
|
|
35
65
|
}
|
|
36
66
|
|
|
37
67
|
/**
|
|
38
|
-
* Set up
|
|
39
|
-
*
|
|
68
|
+
* Set up listener for auth token broadcasts from background.js
|
|
69
|
+
* Handles both sign-in broadcasts and sign-out broadcasts
|
|
40
70
|
* @param {Object} context - The manager instance (must have extension, webManager, logger)
|
|
41
71
|
*/
|
|
42
|
-
export function
|
|
43
|
-
const {
|
|
44
|
-
|
|
45
|
-
// Check existing auth state on load and sign in
|
|
46
|
-
extension.storage.local.get('bxm:authState', (result) => {
|
|
47
|
-
const authState = result['bxm:authState'];
|
|
72
|
+
export function setupAuthBroadcastListener(context) {
|
|
73
|
+
const { webManager, logger } = context;
|
|
48
74
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
75
|
+
// Listen for messages from service worker (background.js)
|
|
76
|
+
navigator.serviceWorker?.addEventListener('message', async (event) => {
|
|
77
|
+
const { command, token } = event.data || {};
|
|
78
|
+
|
|
79
|
+
// Handle sign-in broadcast
|
|
80
|
+
if (command === 'bxm:signInWithToken' && token) {
|
|
81
|
+
logger.log('[AUTH-BROADCAST] Received sign-in broadcast');
|
|
82
|
+
try {
|
|
83
|
+
await webManager.auth().signInWithCustomToken(token);
|
|
84
|
+
logger.log('[AUTH-BROADCAST] Signed in via broadcast');
|
|
85
|
+
} catch (error) {
|
|
86
|
+
logger.error('[AUTH-BROADCAST] Error signing in:', error.message);
|
|
87
|
+
}
|
|
88
|
+
return;
|
|
52
89
|
}
|
|
53
|
-
});
|
|
54
90
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
91
|
+
// Handle sign-out broadcast
|
|
92
|
+
if (command === 'bxm:signOut') {
|
|
93
|
+
// Skip if already signed out (prevents loops)
|
|
94
|
+
if (!webManager.auth().getUser()) {
|
|
95
|
+
logger.log('[AUTH-BROADCAST] Already signed out, ignoring broadcast');
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
logger.log('[AUTH-BROADCAST] Received sign-out broadcast');
|
|
99
|
+
try {
|
|
100
|
+
await webManager.auth().signOut();
|
|
101
|
+
logger.log('[AUTH-BROADCAST] Signed out via broadcast');
|
|
102
|
+
} catch (error) {
|
|
103
|
+
logger.error('[AUTH-BROADCAST] Error signing out:', error.message);
|
|
104
|
+
}
|
|
62
105
|
}
|
|
63
106
|
});
|
|
64
107
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
108
|
+
logger.log('[AUTH-BROADCAST] Broadcast listener set up');
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Set up listener to notify background when user signs out from this context
|
|
113
|
+
* @param {Object} context - The manager instance (must have extension, webManager, logger)
|
|
114
|
+
*/
|
|
115
|
+
export function setupSignOutListener(context) {
|
|
116
|
+
const { extension, webManager, logger } = context;
|
|
73
117
|
|
|
74
|
-
|
|
75
|
-
|
|
118
|
+
// Track previous user to detect sign-out
|
|
119
|
+
let previousUid = null;
|
|
76
120
|
|
|
77
|
-
|
|
78
|
-
const
|
|
121
|
+
webManager.auth().listen((state) => {
|
|
122
|
+
const currentUid = state.user?.uid || null;
|
|
79
123
|
|
|
80
|
-
//
|
|
81
|
-
if (!
|
|
82
|
-
logger.log('[AUTH-SYNC]
|
|
83
|
-
|
|
84
|
-
return;
|
|
124
|
+
// Detect sign-out (had user, now don't)
|
|
125
|
+
if (previousUid && !currentUid) {
|
|
126
|
+
logger.log('[AUTH-SYNC] Detected sign-out, notifying background...');
|
|
127
|
+
extension.runtime.sendMessage({ command: 'bxm:signOut' });
|
|
85
128
|
}
|
|
86
129
|
|
|
87
|
-
|
|
88
|
-
if (newAuthState?.token) {
|
|
89
|
-
signInWithStoredToken(context, newAuthState);
|
|
90
|
-
}
|
|
130
|
+
previousUid = currentUid;
|
|
91
131
|
});
|
|
92
132
|
|
|
93
|
-
|
|
94
|
-
logger.log('Auth storage listener set up');
|
|
133
|
+
logger.log('[AUTH-SYNC] Sign-out listener set up');
|
|
95
134
|
}
|
|
96
135
|
|
|
97
136
|
/**
|
|
@@ -176,8 +215,7 @@ export function setupAuthEventListeners(context) {
|
|
|
176
215
|
});
|
|
177
216
|
|
|
178
217
|
// Note: .auth-signout-btn is handled by web-manager's auth module
|
|
179
|
-
//
|
|
180
|
-
// If background hasn't initialized Firebase yet, stale storage is cleared on next auth attempt
|
|
218
|
+
// setupSignOutListener detects sign-out and notifies background
|
|
181
219
|
|
|
182
220
|
// Log
|
|
183
221
|
context.logger.log('Auth event listeners set up');
|
package/dist/options.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { Manager as WebManager } from 'web-manager';
|
|
3
3
|
import extension from './lib/extension.js';
|
|
4
4
|
import LoggerLite from './lib/logger-lite.js';
|
|
5
|
-
import {
|
|
5
|
+
import { syncWithBackground, setupAuthBroadcastListener, setupSignOutListener, setupAuthEventListeners, openAuthPage as openAuthPageHelper } from './lib/auth-helpers.js';
|
|
6
6
|
|
|
7
7
|
// Import theme (exposes Bootstrap to window.bootstrap)
|
|
8
8
|
import '__theme__/_theme.js';
|
|
@@ -35,8 +35,14 @@ class Manager {
|
|
|
35
35
|
this.logger.log('Auth state changed:', state);
|
|
36
36
|
});
|
|
37
37
|
|
|
38
|
-
//
|
|
39
|
-
|
|
38
|
+
// Sync auth with background.js (waits for WM auth to settle first)
|
|
39
|
+
await syncWithBackground(this);
|
|
40
|
+
|
|
41
|
+
// Set up broadcast listener for sign-in/sign-out from background
|
|
42
|
+
setupAuthBroadcastListener(this);
|
|
43
|
+
|
|
44
|
+
// Set up sign-out listener to notify background when user signs out
|
|
45
|
+
setupSignOutListener(this);
|
|
40
46
|
|
|
41
47
|
// Set up auth event listeners (sign in, account buttons)
|
|
42
48
|
setupAuthEventListeners(this);
|
package/dist/page.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { Manager as WebManager } from 'web-manager';
|
|
3
3
|
import extension from './lib/extension.js';
|
|
4
4
|
import LoggerLite from './lib/logger-lite.js';
|
|
5
|
-
import {
|
|
5
|
+
import { syncWithBackground, setupAuthBroadcastListener, setupSignOutListener, setupAuthEventListeners, openAuthPage as openAuthPageHelper } from './lib/auth-helpers.js';
|
|
6
6
|
|
|
7
7
|
// Import theme (exposes Bootstrap to window.bootstrap)
|
|
8
8
|
import '__theme__/_theme.js';
|
|
@@ -35,8 +35,14 @@ class Manager {
|
|
|
35
35
|
this.logger.log('Auth state changed:', state);
|
|
36
36
|
});
|
|
37
37
|
|
|
38
|
-
//
|
|
39
|
-
|
|
38
|
+
// Sync auth with background.js (waits for WM auth to settle first)
|
|
39
|
+
await syncWithBackground(this);
|
|
40
|
+
|
|
41
|
+
// Set up broadcast listener for sign-in/sign-out from background
|
|
42
|
+
setupAuthBroadcastListener(this);
|
|
43
|
+
|
|
44
|
+
// Set up sign-out listener to notify background when user signs out
|
|
45
|
+
setupSignOutListener(this);
|
|
40
46
|
|
|
41
47
|
// Set up auth event listeners (sign in, account buttons)
|
|
42
48
|
setupAuthEventListeners(this);
|
package/dist/popup.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { Manager as WebManager } from 'web-manager';
|
|
3
3
|
import extension from './lib/extension.js';
|
|
4
4
|
import LoggerLite from './lib/logger-lite.js';
|
|
5
|
-
import {
|
|
5
|
+
import { syncWithBackground, setupAuthBroadcastListener, setupSignOutListener, setupAuthEventListeners, openAuthPage as openAuthPageHelper } from './lib/auth-helpers.js';
|
|
6
6
|
|
|
7
7
|
// Import theme (exposes Bootstrap to window.bootstrap)
|
|
8
8
|
import '__theme__/_theme.js';
|
|
@@ -35,8 +35,14 @@ class Manager {
|
|
|
35
35
|
this.logger.log('Auth state changed:', state);
|
|
36
36
|
});
|
|
37
37
|
|
|
38
|
-
//
|
|
39
|
-
|
|
38
|
+
// Sync auth with background.js (waits for WM auth to settle first)
|
|
39
|
+
await syncWithBackground(this);
|
|
40
|
+
|
|
41
|
+
// Set up broadcast listener for sign-in/sign-out from background
|
|
42
|
+
setupAuthBroadcastListener(this);
|
|
43
|
+
|
|
44
|
+
// Set up sign-out listener to notify background when user signs out
|
|
45
|
+
setupSignOutListener(this);
|
|
40
46
|
|
|
41
47
|
// Set up auth event listeners (sign in, account buttons)
|
|
42
48
|
setupAuthEventListeners(this);
|
package/dist/sidepanel.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { Manager as WebManager } from 'web-manager';
|
|
3
3
|
import extension from './lib/extension.js';
|
|
4
4
|
import LoggerLite from './lib/logger-lite.js';
|
|
5
|
-
import {
|
|
5
|
+
import { syncWithBackground, setupAuthBroadcastListener, setupSignOutListener, setupAuthEventListeners, openAuthPage as openAuthPageHelper } from './lib/auth-helpers.js';
|
|
6
6
|
|
|
7
7
|
// Import theme (exposes Bootstrap to window.bootstrap)
|
|
8
8
|
import '__theme__/_theme.js';
|
|
@@ -35,8 +35,14 @@ class Manager {
|
|
|
35
35
|
this.logger.log('Auth state changed:', state);
|
|
36
36
|
});
|
|
37
37
|
|
|
38
|
-
//
|
|
39
|
-
|
|
38
|
+
// Sync auth with background.js (waits for WM auth to settle first)
|
|
39
|
+
await syncWithBackground(this);
|
|
40
|
+
|
|
41
|
+
// Set up broadcast listener for sign-in/sign-out from background
|
|
42
|
+
setupAuthBroadcastListener(this);
|
|
43
|
+
|
|
44
|
+
// Set up sign-out listener to notify background when user signs out
|
|
45
|
+
setupSignOutListener(this);
|
|
40
46
|
|
|
41
47
|
// Set up auth event listeners (sign in, account buttons)
|
|
42
48
|
setupAuthEventListeners(this);
|