@okta/spa-platform 0.6.0 → 0.9.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.
Files changed (58) hide show
  1. package/README.md +204 -0
  2. package/claude.md +562 -0
  3. package/dist/esm/Credential/Credential.js.map +1 -1
  4. package/dist/esm/Credential/CredentialCoordinator.js +25 -22
  5. package/dist/esm/Credential/CredentialCoordinator.js.map +1 -1
  6. package/dist/esm/Credential/TokenStorage.js +19 -3
  7. package/dist/esm/Credential/TokenStorage.js.map +1 -1
  8. package/dist/esm/FetchClient/index.js.map +1 -1
  9. package/dist/esm/core.js +25 -0
  10. package/dist/esm/core.js.map +1 -0
  11. package/dist/esm/flows/AuthorizationCodeFlow.js.map +1 -1
  12. package/dist/esm/flows/SessionLogoutFlow.js.map +1 -1
  13. package/dist/esm/flows/TransactionStorage.js.map +1 -1
  14. package/dist/esm/flows.js +30 -0
  15. package/dist/esm/flows.js.map +1 -0
  16. package/dist/esm/index.js +4 -13
  17. package/dist/esm/index.js.map +1 -1
  18. package/dist/esm/orchestrators/AuthorizationCodeFlowOrchestrator.js +4 -0
  19. package/dist/esm/orchestrators/AuthorizationCodeFlowOrchestrator.js.map +1 -1
  20. package/dist/esm/orchestrators/HostOrchestrator/Host.js +9 -1
  21. package/dist/esm/orchestrators/HostOrchestrator/Host.js.map +1 -1
  22. package/dist/esm/orchestrators/HostOrchestrator/OrchestrationBridge.js.map +1 -1
  23. package/dist/esm/orchestrators/HostOrchestrator/SubApp.js +8 -2
  24. package/dist/esm/orchestrators/HostOrchestrator/SubApp.js.map +1 -1
  25. package/dist/esm/orchestrators/HostOrchestrator/index.js +11 -6
  26. package/dist/esm/orchestrators/HostOrchestrator/index.js.map +1 -1
  27. package/dist/esm/platform/OAuth2Client.js +4 -7
  28. package/dist/esm/platform/OAuth2Client.js.map +1 -1
  29. package/dist/esm/platform/defaults.js.map +1 -1
  30. package/dist/esm/platform/index.js.map +1 -1
  31. package/dist/esm/utils/IndexedDBStore.js +27 -18
  32. package/dist/esm/utils/IndexedDBStore.js.map +1 -1
  33. package/dist/esm/utils/SynchronizedResult.js +53 -60
  34. package/dist/esm/utils/SynchronizedResult.js.map +1 -1
  35. package/dist/esm/utils/isModernBrowser.js.map +1 -1
  36. package/dist/types/Credential/Credential.d.ts +8 -2
  37. package/dist/types/Credential/TokenStorage.d.ts +25 -1
  38. package/dist/types/FetchClient/index.d.ts +6 -0
  39. package/dist/types/core.d.ts +19 -0
  40. package/dist/types/flows/AuthorizationCodeFlow.d.ts +63 -13
  41. package/dist/types/flows/SessionLogoutFlow.d.ts +24 -0
  42. package/dist/types/flows/TransactionStorage.d.ts +7 -0
  43. package/dist/types/flows/index.d.ts +15 -2
  44. package/dist/types/flows.d.ts +9 -0
  45. package/dist/types/index.d.ts +1 -14
  46. package/dist/types/orchestrators/AuthorizationCodeFlowOrchestrator.d.ts +40 -6
  47. package/dist/types/orchestrators/HostOrchestrator/Host.d.ts +15 -0
  48. package/dist/types/orchestrators/HostOrchestrator/OrchestrationBridge.d.ts +4 -0
  49. package/dist/types/orchestrators/HostOrchestrator/SubApp.d.ts +13 -2
  50. package/dist/types/orchestrators/HostOrchestrator/index.d.ts +50 -16
  51. package/dist/types/orchestrators/index.d.ts +4 -1
  52. package/dist/types/platform/OAuth2Client.d.ts +9 -3
  53. package/dist/types/platform/defaults.d.ts +8 -0
  54. package/dist/types/platform/index.d.ts +6 -0
  55. package/dist/types/utils/IndexedDBStore.d.ts +1 -0
  56. package/dist/types/utils/SynchronizedResult.d.ts +1 -8
  57. package/dist/types/utils/isModernBrowser.d.ts +2 -1
  58. package/package.json +1 -1
package/claude.md ADDED
@@ -0,0 +1,562 @@
1
+ # @okta/spa-platform
2
+
3
+ **Package**: `@okta/spa-platform`
4
+ **Version**: 0.6.0
5
+ **Type**: Platform/Management Library
6
+ **Purpose**: High-level SPA platform SDK for token lifecycle management, storage, and authenticated requests.
7
+
8
+ > 📌 **Context**: This is the highest-level package in the monorepo, designed for direct use by SPA applications. See [root claude.md](../../claude.md) for monorepo architecture.
9
+
10
+ ## 🎯 Package Overview
11
+
12
+ This package provides a complete, production-ready SDK for Single Page Applications to handle OAuth2 authentication, token management, and authenticated API requests.
13
+
14
+ ### What This Package Provides
15
+
16
+ - **Credential Management**: High-level `Credential` abstraction for tokens with automatic refresh
17
+ - **Token Orchestrators**: Specialized orchestrators for different authentication patterns
18
+ - `AuthorizationCodeFlowOrchestrator` - Standard SPA authentication flow
19
+ - `HostOrchestrator` - Multi-app/iframe token delegation architecture
20
+ - **Browser Token Storage**: `BrowserTokenStorage` for secure token persistence
21
+ - **OAuth2 Client**: Browser-specific OAuth2 client with tab synchronization
22
+ - **Authenticated FetchClient**: HTTP client with automatic token injection
23
+ - **Flow Wrappers**: Browser-optimized versions of `@okta/oauth2-flows`
24
+ - **DPoP Support**: Browser-specific DPoP key management and signing
25
+
26
+ ### What This Package Does NOT Provide
27
+
28
+ - Server-side authentication (this is SPA-only)
29
+ - Native mobile app support
30
+ - OAuth2 flow implementations (delegates to `@okta/oauth2-flows`)
31
+
32
+ ## 📁 Source Code Structure
33
+
34
+ ```
35
+ src/
36
+ ├── Credential/ # Credential management and abstractions
37
+ ├── FetchClient/ # HTTP client with token injection
38
+ ├── flows/ # Flow integrations (wrapping @okta/oauth2-flows)
39
+ ├── orchestrators/ # Token lifecycle orchestrators
40
+ ├── platform/ # Browser-specific platform utilities
41
+ ├── utils/ # Utility functions
42
+ └── index.ts # Public API exports
43
+ ```
44
+
45
+ ## 📦 Package Exports
46
+
47
+ ```typescript
48
+ import { /* platform APIs */ } from '@okta/spa-platform';
49
+ ```
50
+
51
+ **Main exports**:
52
+ - Credential managers
53
+ - Authenticated fetch client
54
+ - Token orchestrators
55
+ - Browser-specific utilities
56
+ - Integrated flow wrappers
57
+
58
+ ## 🔑 Key Components
59
+
60
+ ### 1. Credential (`Credential/Credential.ts`)
61
+
62
+ High-level abstraction representing an OAuth2 credential (token + metadata).
63
+
64
+ **Key Features**:
65
+ - Wraps a `Token` with lifecycle management
66
+ - Automatic refresh via `refreshIfNeeded()`
67
+ - Static methods for finding and storing credentials
68
+ - Integration with `BrowserTokenStorage`
69
+
70
+ **Usage**:
71
+ ```typescript
72
+ import { Credential } from '@okta/spa-platform';
73
+
74
+ // Store a credential
75
+ const credential = await Credential.store(token, ['app:main']);
76
+
77
+ // Find credentials by filter
78
+ const credentials = await Credential.find((meta) =>
79
+ meta.scopes?.includes('profile')
80
+ );
81
+
82
+ // Refresh if needed (checks expiration automatically)
83
+ await credential.refreshIfNeeded();
84
+
85
+ // Access the token
86
+ const accessToken = credential.token.accessToken;
87
+
88
+ // Remove credential
89
+ await credential.remove();
90
+ ```
91
+
92
+ ### 2. AuthorizationCodeFlowOrchestrator (`orchestrators/AuthorizationCodeFlowOrchestrator.ts`)
93
+
94
+ Implements `TokenOrchestrator` for standard SPA authentication flows.
95
+
96
+ **Responsibilities**:
97
+ - Manage authentication flow lifecycle
98
+ - Store and retrieve credentials
99
+ - Handle redirects and callbacks
100
+ - Automatic token refresh
101
+ - Event emission for UI integration
102
+
103
+ **Usage**:
104
+ ```typescript
105
+ import {
106
+ AuthorizationCodeFlowOrchestrator,
107
+ AuthorizationCodeFlow
108
+ } from '@okta/spa-platform';
109
+
110
+ // Create flow
111
+ const flow = new AuthorizationCodeFlow({
112
+ issuer: 'https://dev-123456.okta.com/oauth2/default',
113
+ clientId: 'your-client-id',
114
+ redirectUri: 'http://localhost:8080/callback',
115
+ scopes: ['openid', 'profile', 'offline_access'],
116
+ });
117
+
118
+ // Create orchestrator
119
+ const orchestrator = new AuthorizationCodeFlowOrchestrator(flow, {
120
+ avoidPrompting: false,
121
+ emitBeforeRedirect: true,
122
+ getOriginalUri: () => window.location.pathname,
123
+ tags: ['main-app'],
124
+ });
125
+
126
+ // Listen for events
127
+ orchestrator.on('login_prompt_required', ({ done, params }) => {
128
+ console.log('Login required', params);
129
+ done(); // Call when ready to proceed
130
+ });
131
+
132
+ orchestrator.on('error', ({ error }) => {
133
+ console.error('Auth error', error);
134
+ });
135
+
136
+ // Get token (will redirect if not authenticated)
137
+ const token = await orchestrator.getToken({
138
+ scopes: ['openid', 'profile'],
139
+ });
140
+
141
+ // Resume flow after redirect callback
142
+ await orchestrator.resumeFlow(window.location.href);
143
+ ```
144
+
145
+ ### 3. HostOrchestrator (`orchestrators/HostOrchestrator/`)
146
+
147
+ Advanced multi-app architecture for token delegation between host app and sub-apps (e.g., iframes).
148
+
149
+ **Components**:
150
+ - `HostOrchestrator.Host` - Receives and fulfills token requests from sub-apps
151
+ - `HostOrchestrator.SubApp` - Delegates token requests to host
152
+ - `HostOrchestrator.ProxyHost` - Adapts any orchestrator into a host
153
+
154
+ **Use Cases**:
155
+ - Micro-frontend architectures
156
+ - Main app + embedded iframes
157
+ - Shared authentication across multiple SPAs
158
+
159
+ **Usage - Host App**:
160
+ ```typescript
161
+ import {
162
+ HostOrchestrator,
163
+ AuthorizationCodeFlowOrchestrator,
164
+ AuthorizationCodeFlow
165
+ } from '@okta/spa-platform';
166
+
167
+ // Create standard orchestrator
168
+ const flow = new AuthorizationCodeFlow({ /* config */ });
169
+ const orchestrator = new AuthorizationCodeFlowOrchestrator(flow);
170
+
171
+ // Wrap in ProxyHost to enable delegation
172
+ const host = new HostOrchestrator.ProxyHost('main-app', orchestrator);
173
+
174
+ // Host automatically handles token requests from sub-apps
175
+ ```
176
+
177
+ **Usage - Sub-App (iframe)**:
178
+ ```typescript
179
+ import { HostOrchestrator } from '@okta/spa-platform';
180
+
181
+ // Create SubApp orchestrator that delegates to host
182
+ const subApp = new HostOrchestrator.SubApp({
183
+ scopes: ['openid', 'profile'],
184
+ targetOrigin: 'https://main-app.example.com',
185
+ });
186
+
187
+ // Use like any orchestrator
188
+ const token = await subApp.getToken();
189
+ ```
190
+
191
+ ### 4. BrowserTokenStorage (`Credential/TokenStorage.ts`)
192
+
193
+ Browser-specific token storage implementation.
194
+
195
+ **Features**:
196
+ - Supports `localStorage` and `sessionStorage`
197
+ - Automatic serialization/deserialization
198
+ - Tab-synchronized updates
199
+ - Secure storage practices
200
+ - Encrypts tokens at-rest
201
+
202
+ ### 5. OAuth2Client (`platform/OAuth2Client.ts`)
203
+
204
+ Browser-specific OAuth2 client extending `@okta/auth-foundation` client.
205
+
206
+ **Key Feature**:
207
+ - **Tab-Synchronized Refresh**: Uses `SynchronizedResult` to coordinate token refresh across browser tabs
208
+ - Only one tab performs refresh, others wait for result
209
+ - Prevents duplicate refresh requests
210
+
211
+ **How It Works**:
212
+ ```typescript
213
+ // Multiple tabs call refresh simultaneously
214
+ // Only one actually makes the request
215
+ // Others receive the result via storage events
216
+ const refreshedToken = await client.refresh(token);
217
+ ```
218
+
219
+ ### 6. FetchClient (`FetchClient/`)
220
+
221
+ Authenticated HTTP client for making API requests.
222
+
223
+ **Features**:
224
+ - Automatic access token injection in `Authorization` header
225
+ - DPoP proof generation (if token is DPoP-bound)
226
+ - Token refresh on 401 responses
227
+ - Platform-specific fetch implementation
228
+
229
+ **Usage**:
230
+ ```typescript
231
+ import {
232
+ FetchClient,
233
+ Credential,
234
+ AuthorizationCodeFlowOrchestrator,
235
+ AuthorizationCodeFlow
236
+ } from '@okta/spa-platform';
237
+
238
+ // Create standard orchestrator
239
+ const flow = new AuthorizationCodeFlow({ /* config */ });
240
+ const orchestrator = new AuthorizationCodeFlowOrchestrator(flow);
241
+
242
+ const client = new FetchClient(orchestrator);
243
+
244
+ // Automatically adds Authorization header
245
+ const response = await client.fetch('https://api.example.com/data');
246
+ const data = await response.json();
247
+ ```
248
+
249
+ ### 7. Flow Wrappers (`flows/`)
250
+
251
+ Browser-optimized versions of `@okta/oauth2-flows`:
252
+ - `AuthorizationCodeFlow` - Extends base flow with browser storage integration
253
+ - `SessionLogoutFlow` - Extends base logout with browser cleanup
254
+
255
+ These wrap the core flows and add browser-specific functionality like:
256
+ - Automatic state/verifier storage in sessionStorage
257
+ - Callback parsing from `window.location`
258
+ - Browser redirect handling
259
+
260
+
261
+ ## 🔧 Development
262
+
263
+ ### Building
264
+ ```bash
265
+ yarn build # Build ESM + types
266
+ yarn build:watch # Watch mode
267
+ yarn build:esm # Rollup bundle only
268
+ yarn build:types # TypeScript definitions only
269
+ ```
270
+
271
+ ### Testing
272
+ ```bash
273
+ yarn test # Run all tests
274
+ yarn test:unit # Unit tests
275
+ yarn test:watch # Watch mode
276
+ ```
277
+
278
+ ### Linting
279
+ ```bash
280
+ yarn lint
281
+ ```
282
+
283
+ ## 🏗️ Architecture Patterns
284
+
285
+ ### Layered Architecture
286
+
287
+ ```
288
+ ┌─────────────────────────────────────┐
289
+ │ SPA Application Code │
290
+ └─────────────────┬───────────────────┘
291
+
292
+ ┌─────────────────▼───────────────────┐
293
+ │ @okta/spa-platform │
294
+ │ • Token Orchestration │
295
+ │ • Storage Management │
296
+ │ • Tab Synchronization │
297
+ └─────────────────┬───────────────────┘
298
+
299
+ ┌─────────────────▼───────────────────┐
300
+ │ @okta/oauth2-flows │
301
+ │ • Authorization Code Flow │
302
+ │ • Logout Flows │
303
+ └─────────────────┬───────────────────┘
304
+
305
+ ┌─────────────────▼───────────────────┐
306
+ │ @okta/auth-foundation │
307
+ │ • Token Primitives │
308
+ │ • HTTP Client │
309
+ │ • Crypto Utils │
310
+ └─────────────────────────────────────┘
311
+ ```
312
+
313
+ ### Credential-Based Architecture
314
+
315
+ This package uses a **Credential** abstraction rather than raw tokens:
316
+
317
+ ```
318
+ Application Code
319
+
320
+ Credential (high-level)
321
+
322
+ Token (from @okta/auth-foundation)
323
+
324
+ OAuth2 protocol primitives
325
+ ```
326
+
327
+ **Benefits**:
328
+ - Automatic refresh management
329
+ - Storage abstraction
330
+ - Metadata (scopes, tags) attached to tokens
331
+ - Query/filter capabilities
332
+
333
+ ### Orchestrator Pattern
334
+
335
+ `TokenOrchestrator` (from `@okta/auth-foundation`) defines the contract:
336
+ - `getToken(params)` - Get or request a token
337
+ - Event emission for lifecycle hooks
338
+
339
+ This package provides implementations:
340
+ - `AuthorizationCodeFlowOrchestrator` - Standard SPA flow
341
+ - `HostOrchestrator.Host` - Token delegation receiver
342
+ - `HostOrchestrator.SubApp` - Token delegation requestor
343
+
344
+ ### Tab Synchronization
345
+
346
+ **Challenge**: Multiple browser tabs need consistent auth state
347
+
348
+ **Solution**:
349
+ - `BrowserTokenStorage` uses storage events
350
+ - `SynchronizedResult` prevents duplicate operations
351
+ - `OAuth2Client.prepareRefreshRequest` coordinates refresh across tabs
352
+
353
+ **Flow**:
354
+ 1. Tab A starts token refresh
355
+ 2. Tab A creates `SynchronizedResult` with unique key
356
+ 3. Tab A stores "pending" state in localStorage
357
+ 4. Tab B attempts refresh with same key
358
+ 5. Tab B sees "pending", waits for result
359
+ 6. Tab A completes, stores result
360
+ 7. Tab B receives result via storage event
361
+
362
+ ## 🧪 Testing Strategy
363
+
364
+ ### Test Organization
365
+ Tests are co-located in `test/` and focus on:
366
+ - Token renewal logic
367
+ - Tab synchronization
368
+ - Storage operations
369
+ - HTTP client behavior
370
+ - Error handling
371
+
372
+ ### Browser Environment
373
+ All tests simulate browser environment:
374
+ - Mock `localStorage` and `sessionStorage`
375
+ - Mock `BroadcastChannel`
376
+ - Mock `fetch` API
377
+
378
+ ### Mocking Strategy
379
+ Uses `@repo/jest-helpers` to mock:
380
+ - Browser storage APIs
381
+ - Token endpoints
382
+ - Time (for expiration testing)
383
+
384
+ ## 🔗 Integration with Other Packages
385
+
386
+ ### Dependencies
387
+ - `@okta/auth-foundation` (peer dependency): Core primitives
388
+ - `@okta/oauth2-flows` (optional peer dependency): OAuth2 flows
389
+
390
+ ### Typical Usage
391
+ ```typescript
392
+ import {
393
+ FetchClient,
394
+ Credential,
395
+ AuthorizationCodeFlowOrchestrator,
396
+ AuthorizationCodeFlow
397
+ } from '@okta/spa-platform';
398
+
399
+ // Create standard orchestrator
400
+ const flow = new AuthorizationCodeFlow({ /* config */ });
401
+ const orchestrator = new AuthorizationCodeFlowOrchestrator(flow);
402
+
403
+ const client = new FetchClient(orchestrator);
404
+
405
+ // Automatically adds Authorization header
406
+ const response = await client.fetch('https://api.example.com/data');
407
+ const data = await response.json();
408
+ ```
409
+
410
+ ## 📘 Key Concepts
411
+
412
+ ### Token Renewal
413
+
414
+ **Why?**
415
+ - Access tokens are short-lived (typically 1 hour)
416
+ - Must be renewed before expiry to avoid interruptions
417
+
418
+ **How?**
419
+ - Orchestrator checks expiration periodically
420
+ - Uses refresh token to get new access token
421
+ - Updates storage atomically
422
+ - Notifies all tabs of new tokens
423
+
424
+ ### Cross-Tab Authentication
425
+
426
+ **Challenges**:
427
+ - User might authenticate in Tab A
428
+ - Tab B needs to know about authentication
429
+ - User might logout in Tab A
430
+ - Tab B should also logout
431
+
432
+ **Solution**:
433
+ - Tab sync via storage events or BroadcastChannel
434
+ - Shared token storage
435
+ - Coordinated logout
436
+
437
+ ### Secure Token Storage
438
+
439
+ **Best Practices**:
440
+ 1. **Never store tokens in cookies** (CSRF risk)
441
+ 2. **Encrypt at rest** (XSS protection)
442
+ 3. **Use localStorage** only if persistence is required
443
+ 4. **Never log tokens** (security risk)
444
+ 5. **Clear storage on logout**
445
+
446
+ ### DPoP in SPA Context
447
+
448
+ **DPoP with SPAs**:
449
+ - Private key must be stored securely
450
+ - Use Web Crypto API for key generation
451
+ - Store (non-extractable) key in IndexedDB (not localStorage)
452
+ - Same key must be used for token and API requests
453
+
454
+ ## 🚨 Common Gotchas
455
+
456
+ 1. **Storage Permissions**: Some browsers block storage in iframes or incognito mode
457
+ 2. **Token Expiry**: Orchestrator must start BEFORE tokens expire
458
+ 3. **Tab Sync Delay**: Storage events have slight delay - don't rely on instant sync
459
+ 4. **Memory Leaks**: Always call `orchestrator.stop()` when component unmounts
460
+ 5. **DPoP Keys**: Must persist DPoP keys across page reloads (use IndexedDB)
461
+
462
+ ## 🎯 Common Use Cases
463
+
464
+ ### Basic SPA Authentication
465
+ ```typescript
466
+ import {
467
+ AuthorizationCodeFlowOrchestrator,
468
+ AuthorizationCodeFlow,
469
+ Credential
470
+ } from '@okta/spa-platform';
471
+
472
+ // 1. Setup
473
+ const flow = new AuthorizationCodeFlow({
474
+ issuer: 'https://dev-123456.okta.com/oauth2/default',
475
+ clientId: 'client-id',
476
+ redirectUri: 'http://localhost:8080/callback',
477
+ scopes: ['openid', 'profile', 'offline_access'],
478
+ });
479
+
480
+ const orchestrator = new AuthorizationCodeFlowOrchestrator(flow);
481
+
482
+ // 2. Check if already authenticated
483
+ const token = await orchestrator.getToken();
484
+ // If not authenticated, this redirects to Okta
485
+
486
+ // 3. Handle callback (in /callback route)
487
+ if (window.location.pathname === '/callback') {
488
+ await orchestrator.resumeFlow();
489
+ // Redirect to originalUri or home
490
+ }
491
+
492
+ // 4. Make API requests
493
+ const credential = (await Credential.find(() => true))[0];
494
+ await credential.refreshIfNeeded();
495
+ const accessToken = credential.token.accessToken;
496
+ ```
497
+
498
+ ### Logout
499
+ ```typescript
500
+ import { SessionLogoutFlow } from '@okta/oauth2-flows';
501
+
502
+ // Logout from Okta
503
+ const logoutFlow = new SessionLogoutFlow(config);
504
+ const credential = await Credential.getDefault();
505
+ const url = await logoutFlow.start(credential.token?.idToken);
506
+ window.location.href = url;
507
+ ```
508
+
509
+ ### Multi-App Architecture
510
+ ```typescript
511
+ // Host App (main-app.example.com)
512
+ import {
513
+ HostOrchestrator,
514
+ AuthorizationCodeFlowOrchestrator,
515
+ AuthorizationCodeFlow
516
+ } from '@okta/spa-platform';
517
+
518
+ const flow = new AuthorizationCodeFlow({ /* config */ });
519
+ const orchestrator = new AuthorizationCodeFlowOrchestrator(flow);
520
+ const host = new HostOrchestrator.ProxyHost('main', orchestrator);
521
+
522
+ // Sub-App (iframe in main app)
523
+ import { HostOrchestrator } from '@okta/spa-platform';
524
+
525
+ const subApp = new HostOrchestrator.SubApp({
526
+ scopes: ['openid', 'profile'],
527
+ targetOrigin: 'https://main-app.example.com',
528
+ });
529
+
530
+ const token = await subApp.getToken(); // Requests from host
531
+ ```
532
+
533
+ ## 💡 Working with Claude
534
+
535
+ When modifying this package:
536
+
537
+ 1. **Browser-Only**: All code must work in browser environment
538
+ 2. **Storage Access**: Always handle storage errors gracefully (incognito, permissions)
539
+ 3. **Tab Sync**: Test with multiple tabs/windows open
540
+ 4. **Token Security**: Never log or expose tokens
541
+ 5. **Orchestrator Lifecycle**: Always clean up (call `stop()`) to prevent memory leaks
542
+
543
+ ### Important Files
544
+ - `src/Credential/` - Credential abstraction and storage
545
+ - `src/orchestrators/` - Token orchestrators
546
+ - `src/flows/` - Browser-specific flow wrappers
547
+ - `src/platform/OAuth2Client.ts` - Tab-synchronized refresh
548
+ - `src/index.ts` - Public API surface
549
+
550
+ ### Related Documentation
551
+ - OAuth2 Token Refresh: https://oauth.net/2/refresh-tokens/
552
+ - Web Storage API: https://developer.mozilla.org/en-US/docs/Web/API/Web_Storage_API
553
+ - BroadcastChannel API: https://developer.mozilla.org/en-US/docs/Web/API/BroadcastChannel
554
+
555
+ ---
556
+
557
+ **Peer Dependencies**:
558
+ - `@okta/auth-foundation@*` (required)
559
+ - `@okta/oauth2-flows@*` (optional)
560
+
561
+ **Target Environment**: Browser (SPA only)
562
+ **Private**: Yes (not published separately)
@@ -1 +1 @@
1
- {"version":3,"file":"Credential.js","sources":["../../../../src/Credential/Credential.ts"],"sourcesContent":[null],"names":["CredentialBase"],"mappings":";;;;;;;;;;;;;;;AAmBM,CAAA,CAAA,CAAA,CAAA,CAAA,CAAO,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAW,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAQA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAc,CAAA,CAAA;AAC5C,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA;QACE,CAAA,CAAA,CAAA,CAAI,CAAC,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAW,CAAA,CAAA,CAAG,IAAI,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAyB,CAAC,IAAI,CAAC,CAAA;;AASjD,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAO,KAAK,CAAA,CAAA,CAAA,CAAA;AAEhB,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAI,CAAC,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAyC,CAAA,CAAE,KAAK,CAAA,CAAA,CAAA,CAAI,CAAA;IAC5D,CAAA;AACD,CAAA;;"}
1
+ {"version":3,"file":"Credential.js","sources":["../../../../src/Credential/Credential.ts"],"sourcesContent":[null],"names":["CredentialBase"],"mappings":";;;;;;;;;;;;;;;AAyBM,CAAA,CAAA,CAAA,CAAA,CAAA,CAAO,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAW,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAQA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAc,CAAA,CAAA;AAC5C,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA;QACE,CAAA,CAAA,CAAA,CAAI,CAAC,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAW,CAAA,CAAA,CAAG,IAAI,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAyB,CAAC,IAAI,CAAC,CAAA;;AASjD,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAO,KAAK,CAAA,CAAA,CAAA,CAAA;AAEhB,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAI,CAAC,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAyC,CAAA,CAAE,KAAK,CAAA,CAAA,CAAA,CAAI,CAAA;IAC5D,CAAA;AACD,CAAA;;"}
@@ -25,8 +25,7 @@ class CredentialCoordinatorImpl extends CredentialCoordinatorImpl$1 {
25
25
  this.credentialDataSource = new DefaultCredentialDataSource(CredentialConstructor);
26
26
  this.registerTabListeners();
27
27
  this.emitter.on('credential_refreshed', ({ credential }) => {
28
- const { token } = credential;
29
- this.broadcast('credential_refreshed', { id: token.id, value: token.toJSON() });
28
+ this.broadcast('credential_refreshed', { id: credential.id });
30
29
  });
31
30
  }
32
31
  get tokenStorage() {
@@ -43,7 +42,7 @@ class CredentialCoordinatorImpl extends CredentialCoordinatorImpl$1 {
43
42
  }
44
43
  super.tokenStorage = tokenStorage;
45
44
  this.tokenStorage.emitter.on('token_added', ({ token }) => {
46
- this.broadcast('credential_added', { id: token.id, value: token.toJSON() });
45
+ this.broadcast('credential_added', { id: token.id });
47
46
  });
48
47
  this.tokenStorage.emitter.on('token_removed', ({ id }) => {
49
48
  this.broadcast('credential_removed', { id });
@@ -67,7 +66,7 @@ class CredentialCoordinatorImpl extends CredentialCoordinatorImpl$1 {
67
66
  if (isFirefox()) {
68
67
  await pause(50);
69
68
  }
70
- const { eventName, id, value, source } = event.data;
69
+ const { eventName, id, source } = event.data;
71
70
  if (source == this.id) {
72
71
  return;
73
72
  }
@@ -88,27 +87,31 @@ class CredentialCoordinatorImpl extends CredentialCoordinatorImpl$1 {
88
87
  this.emitter.emit('metadata_updated', { storage: this.tokenStorage, id, metadata });
89
88
  }
90
89
  }
91
- else {
92
- const token = new Token({ ...value, id });
93
- if (eventName === 'credential_removed') {
94
- if (this.credentialDataSource.hasCredential(token)) {
95
- this.credentialDataSource.remove(id);
96
- }
97
- else {
98
- this.emitter.emit('credential_removed', { dataSource: this.credentialDataSource, id });
99
- }
90
+ else if (eventName === 'credential_added') {
91
+ this.emitter.emit('credential_added', { id });
92
+ }
93
+ else if (eventName === 'credential_removed') {
94
+ if (this.credentialDataSource.hasCredential(id)) {
95
+ this.credentialDataSource.remove(id);
100
96
  }
101
97
  else {
102
- const credential = this.credentialDataSource.credentialFor(token);
103
- if (eventName === 'credential_added') ;
104
- else if (eventName === 'credential_refreshed') {
105
- if (Token.isEqual(token, credential.token)) {
106
- return;
107
- }
108
- credential.token = token;
109
- this.emitter.emit('credential_refreshed', { credential });
110
- }
98
+ this.emitter.emit('credential_removed', { id });
99
+ }
100
+ }
101
+ else if (eventName === 'credential_refreshed') {
102
+ if (!this.credentialDataSource.hasCredential(id)) {
103
+ return;
104
+ }
105
+ const token = await this.tokenStorage.get(id);
106
+ if (!token) {
107
+ return;
108
+ }
109
+ const credential = this.credentialDataSource.credentialFor(token);
110
+ if (Token.isEqual(token, credential.token)) {
111
+ return;
111
112
  }
113
+ credential.token = token;
114
+ this.emitter.emit('credential_refreshed', { credential });
112
115
  }
113
116
  log('allIDs: ', this.allIDs(), 'size: ', this.credentialDataSource.size);
114
117
  };