@mission_sciences/provider-sdk 0.1.2 → 0.2.0-dev.8e1d7d7

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/README.md CHANGED
@@ -7,293 +7,378 @@
7
7
  [![npm version](https://img.shields.io/npm/v/@mission_sciences/provider-sdk)](https://www.npmjs.com/package/@mission_sciences/provider-sdk)
8
8
  [![GitHub Actions](https://github.com/Mission-Sciences/provider-sdk/workflows/Publish%20Package/badge.svg)](https://github.com/Mission-Sciences/provider-sdk/actions)
9
9
 
10
- > **📦 Migration Complete**: This package has been migrated from Bitbucket to GitHub and renamed from `@marketplace/provider-sdk` to `@mission_sciences/provider-sdk`. Now available on public npm with cryptographic provenance! See [Migration Guide](#-migration-from-marketplaceprovider-sdk) below.
11
-
12
- ## 🚀 Quick Start
10
+ ## Quick Start
13
11
 
14
12
  ```bash
15
- # Install
16
13
  npm install @mission_sciences/provider-sdk
14
+ ```
17
15
 
18
- # Initialize
19
- import MarketplaceSDK from '@mission_sciences/provider-sdk';
16
+ ```javascript
17
+ import { MarketplaceSDK } from '@mission_sciences/provider-sdk';
20
18
 
21
19
  const sdk = new MarketplaceSDK({
22
- jwtParamName: 'jwt',
23
20
  applicationId: 'your-app-id',
24
- jwksUrl: 'https://api.generalwisdom.com/.well-known/jwks.json',
25
- onSessionStart: async (context) => {
26
- // Your auth logic
27
- },
28
- onSessionEnd: async (context) => {
29
- // Cleanup logic
21
+ autoStart: true,
22
+ hooks: {
23
+ async onSessionStart(context) {
24
+ // context.jwt - raw JWT string
25
+ // context.userId - GW user ID
26
+ // context.email - user email
27
+ // context.sessionId, context.orgId, context.applicationId, etc.
28
+
29
+ // Exchange the GW JWT for your app's auth tokens
30
+ const res = await fetch('/auth/marketplace', {
31
+ method: 'POST',
32
+ headers: { 'Content-Type': 'application/json' },
33
+ body: JSON.stringify({ jwt: context.jwt }),
34
+ });
35
+ const { token } = await res.json();
36
+ localStorage.setItem('auth_token', token);
37
+ },
38
+ async onSessionEnd(context) {
39
+ // context.reason = 'expired' | 'manual' | 'error'
40
+ localStorage.clear();
41
+ },
30
42
  },
31
43
  });
32
44
 
33
45
  await sdk.initialize();
34
46
  ```
35
47
 
36
- ## 📚 Documentation
48
+ ## Documentation
37
49
 
38
- - **[Integration Guide](./INTEGRATION_GUIDE.md)** - Comprehensive guide for all frameworks
39
- - **[Quick Start Guide](./QUICKSTART.md)** - Get started in 3 minutes
40
- - **[Testing Guide](./TESTING_GUIDE.md)** - Testing strategies
41
- - **[JWT Specification](./jwt-specification.md)** - Token format details
50
+ - **[Integration Guide](./INTEGRATION_GUIDE.md)** -- Comprehensive guide for all frameworks (vanilla JS, React, Vue, Chrome Extensions)
51
+ - **[Quick Start Guide](./QUICKSTART.md)** -- Get started in 3 minutes
52
+ - **[JWT Specification](./jwt-specification.md)** -- Token format and claim details
53
+ - **[Validation Guide](./VALIDATION.md)** -- Testing and validation strategies
54
+ - **[Auth Integration Demo](./examples/auth-integration/)** -- Full working demo with 5 identity providers and 2 frontend implementations
42
55
 
43
- ### Example Integrations
56
+ ## Features
44
57
 
45
- - **[GhostDog Integration](../extension-ghostdog/MARKETPLACE_INTEGRATION.md)** - Real-world Chrome extension example
58
+ ### Core
59
+ - **Zero Config**: Extracts JWT from URL, validates via JWKS, starts session timer
60
+ - **Framework Agnostic**: Works with vanilla JS, React, Vue, or any framework
61
+ - **TypeScript First**: Full type definitions with all interfaces exported
62
+ - **Lightweight**: Single dependency (`jose` for JWT/JWKS)
63
+ - **Secure**: RS256 JWT verification with JWKS rotation support
46
64
 
47
- ## Features
65
+ ### Lifecycle Hooks
66
+ - **`onSessionStart`** -- Fires after JWT validation, before timer starts. Use to exchange tokens with your auth system. Hook failure prevents session start (strict mode).
67
+ - **`onSessionEnd`** -- Fires on expiration or manual end. Use to revoke sessions. Errors are logged but don't block teardown (lenient mode).
68
+ - **`onSessionWarning`** -- Fires when session nears expiration (configurable threshold).
69
+ - **`onSessionExtend`** -- Fires after session extension. Use to refresh auth tokens.
48
70
 
49
- ### Core Features
50
- - **Zero Dependencies**: Self-contained with minimal external deps
51
- - **Framework Agnostic**: Works with vanilla JS, React, Vue, and more
52
- - **TypeScript First**: Full type definitions included
53
- - **Lightweight**: < 10KB gzipped
54
- - **Secure**: RS256 JWT verification with JWKS
55
- - **Customizable**: Flexible styling and event handling
71
+ ### Advanced (Phase 2)
72
+ - **Heartbeat**: Automatic server sync at configurable intervals
73
+ - **Multi-Tab Sync**: Master tab election via BroadcastChannel API
74
+ - **Session Extension**: Self-service renewal with `extendSession(minutes)`
75
+ - **Early Completion**: End sessions early with `completeSession(actualMinutes)`
76
+ - **Visibility API**: Auto-pause timer when tab is hidden
77
+ - **Backend Validation**: Alternative to JWKS for sensitive apps
56
78
 
57
- ### Advanced Features (Phase 2)
58
- - ❤️ **Heartbeat System**: Automatic server sync
59
- - 🔄 **Multi-Tab Sync**: Master tab election with BroadcastChannel API
60
- - ⏰ **Session Extension**: Self-service renewal
61
- - ✅ **Early Completion**: End sessions early with refund calculation
62
- - 👁️ **Visibility API**: Auto-pause when tab hidden
63
- - 🔐 **Backend Validation**: Alternative to JWKS for sensitive apps
64
-
65
- ## 🎯 How It Works
79
+ ## How It Works
66
80
 
67
81
  ```
68
- Marketplace JWT in URL SDK validates Your app authenticates Session active
82
+ Marketplace --> JWT in URL --> SDK validates via JWKS --> Lifecycle hooks fire --> Session active
69
83
  ```
70
84
 
71
- When users launch your app from the marketplace:
72
-
73
- 1. **URL Detection**: SDK checks for JWT parameter (`?jwt=...`)
74
- 2. **JWT Validation**: Verifies signature using RS256 and JWKS
75
- 3. **Session Start**: Calls your `onSessionStart` hook
76
- 4. **Timer Start**: Countdown begins
77
- 5. **Session Active**: User interacts with your app
78
- 6. **Warning**: Alert at 5 minutes remaining (configurable)
79
- 7. **Session End**: Calls your `onSessionEnd` hook
80
- 8. **Redirect**: Returns to marketplace (optional)
81
-
82
- ## 🔒 Secure Publishing & Provenance
85
+ 1. User launches your app from the marketplace with `?gwSession=<token>` in the URL (parameter name configurable via `jwtParamName`)
86
+ 2. SDK extracts the JWT and verifies it against the JWKS endpoint (RS256)
87
+ 3. `hooks.onSessionStart` fires with the validated session context
88
+ 4. Session timer starts counting down
89
+ 5. `hooks.onSessionWarning` fires at the configured threshold
90
+ 6. When the timer expires or `endSession()` is called, `hooks.onSessionEnd` fires
83
91
 
84
- This package is published with cryptographic provenance attestation:
85
-
86
- - **Dual Publishing**: Available on both [npm](https://www.npmjs.com/package/@mission_sciences/provider-sdk) (public) and AWS CodeArtifact (private)
87
- - **Cryptographic Signatures**: All releases signed with GitHub Actions OIDC
88
- - **Provenance Transparency**: Build provenance recorded in [Sigstore transparency log](https://search.sigstore.dev)
89
- - **No Hardcoded Secrets**: CI/CD uses OIDC for AWS and npm authentication
90
- - **Automated CI/CD**: GitHub Actions workflow with comprehensive testing and security checks
92
+ ## Installation
91
93
 
92
- Verify package provenance:
93
94
  ```bash
94
- npm view @mission_sciences/provider-sdk --json | jq .dist
95
- ```
96
-
97
- ## 📦 Installation
95
+ # npm (public registry)
96
+ npm install @mission_sciences/provider-sdk
98
97
 
99
- ### NPM (Public Registry)
98
+ # yarn
99
+ yarn add @mission_sciences/provider-sdk
100
100
 
101
- ```bash
102
- npm install @mission_sciences/provider-sdk
101
+ # pnpm
102
+ pnpm add @mission_sciences/provider-sdk
103
103
  ```
104
104
 
105
105
  ### AWS CodeArtifact (Private Registry)
106
106
 
107
107
  ```bash
108
- # Configure CodeArtifact
109
108
  aws codeartifact login \
110
109
  --tool npm \
111
110
  --domain general-wisdom-dev \
112
111
  --repository sdk-packages \
113
112
  --region us-east-1
114
113
 
115
- # Install
116
114
  npm install @mission_sciences/provider-sdk
117
115
  ```
118
116
 
119
- ### Yarn
120
-
121
- ```bash
122
- yarn add @mission_sciences/provider-sdk
123
- ```
124
-
125
- ### PNPM
126
-
127
- ```bash
128
- pnpm add @mission_sciences/provider-sdk
129
- ```
130
-
131
- ## 🏗️ Basic Usage
117
+ ## Usage
132
118
 
133
119
  ### Vanilla JavaScript
134
120
 
135
121
  ```javascript
136
- import MarketplaceSDK from '@mission_sciences/provider-sdk';
122
+ import { MarketplaceSDK } from '@mission_sciences/provider-sdk';
137
123
 
138
124
  const sdk = new MarketplaceSDK({
139
- jwtParamName: 'jwt',
125
+ jwksUri: '/.well-known/jwks.json',
140
126
  applicationId: 'my-app',
141
- jwksUrl: 'https://api.generalwisdom.com/.well-known/jwks.json',
142
-
143
- onSessionStart: async (context) => {
144
- // Store user info
145
- localStorage.setItem('user_id', context.userId);
146
- localStorage.setItem('session_id', context.sessionId);
147
-
148
- // Show app UI
149
- document.getElementById('app').style.display = 'block';
150
- },
151
-
152
- onSessionEnd: async (context) => {
153
- // Clear storage
154
- localStorage.clear();
155
-
156
- // Hide app UI
157
- document.getElementById('app').style.display = 'none';
127
+ autoStart: true,
128
+ warningThresholdSeconds: 120,
129
+ hooks: {
130
+ async onSessionStart(context) {
131
+ // Exchange GW JWT for your app's tokens
132
+ const res = await fetch('/auth/exchange', {
133
+ method: 'POST',
134
+ headers: { 'Content-Type': 'application/json' },
135
+ body: JSON.stringify({ jwt: context.jwt }),
136
+ });
137
+ const data = await res.json();
138
+ sessionStorage.setItem('access_token', data.access_token);
139
+ },
140
+ async onSessionEnd(context) {
141
+ sessionStorage.clear();
142
+ },
158
143
  },
159
144
  });
160
145
 
146
+ // Event handlers (separate from hooks -- these fire after hooks complete)
147
+ sdk.on('onSessionStart', (sessionData) => {
148
+ document.getElementById('app').style.display = 'block';
149
+ });
150
+
151
+ sdk.on('onSessionEnd', () => {
152
+ document.getElementById('app').style.display = 'none';
153
+ });
154
+
155
+ sdk.on('onError', (error) => {
156
+ console.error('SDK error:', error.message);
157
+ });
158
+
161
159
  await sdk.initialize();
162
160
 
163
- // Mount session header
164
- const header = sdk.createSessionHeader();
165
- header.mount('#session-header');
161
+ // Timer display
162
+ setInterval(() => {
163
+ document.getElementById('timer').textContent = sdk.getFormattedTime();
164
+ }, 1000);
166
165
  ```
167
166
 
168
167
  ### React
169
168
 
170
169
  ```typescript
171
- import { useEffect } from 'react';
172
- import MarketplaceSDK from '@mission_sciences/provider-sdk';
170
+ import { useEffect, useState, useRef, useCallback } from 'react';
171
+ import { MarketplaceSDK } from '@mission_sciences/provider-sdk';
173
172
 
174
173
  function useMarketplaceSession() {
174
+ const [session, setSession] = useState(null);
175
+ const [loading, setLoading] = useState(true);
176
+ const [time, setTime] = useState('--:--');
177
+ const sdkRef = useRef(null);
178
+
175
179
  useEffect(() => {
176
180
  const sdk = new MarketplaceSDK({
177
- jwtParamName: 'jwt',
181
+ jwksUri: '/.well-known/jwks.json',
178
182
  applicationId: 'my-react-app',
179
- jwksUrl: 'https://api.generalwisdom.com/.well-known/jwks.json',
180
-
181
- onSessionStart: async (context) => {
182
- // Call your auth API
183
- await authenticateUser(context);
184
- },
185
-
186
- onSessionEnd: async (context) => {
187
- // Clear auth state
188
- await logout();
183
+ autoStart: true,
184
+ hooks: {
185
+ async onSessionStart(context) {
186
+ await authenticateUser(context.jwt);
187
+ },
188
+ async onSessionEnd(context) {
189
+ await logout();
190
+ },
189
191
  },
190
192
  });
191
193
 
194
+ sdk.on('onSessionStart', (data) => {
195
+ setSession(data);
196
+ setLoading(false);
197
+ });
198
+
199
+ sdk.on('onError', (err) => {
200
+ console.error(err);
201
+ setLoading(false);
202
+ });
203
+
204
+ sdkRef.current = sdk;
192
205
  sdk.initialize();
193
206
 
194
- return () => sdk.destroy();
207
+ const interval = setInterval(() => {
208
+ if (sdkRef.current) setTime(sdkRef.current.getFormattedTime());
209
+ }, 1000);
210
+
211
+ return () => {
212
+ clearInterval(interval);
213
+ sdk.destroy();
214
+ };
195
215
  }, []);
216
+
217
+ return { session, loading, time, sdk: sdkRef };
196
218
  }
197
219
  ```
198
220
 
199
- See [INTEGRATION_GUIDE.md](./INTEGRATION_GUIDE.md) for Vue, Chrome Extensions, and more.
221
+ See the [Integration Guide](./INTEGRATION_GUIDE.md) for Vue, Chrome Extensions, and more patterns.
200
222
 
201
- ## 🎨 Session Header Component
223
+ ### Auth Integration Demo
202
224
 
203
- Pre-built UI component for displaying session timer:
225
+ A complete working example with Docker, 5 identity providers, and 2 frontend implementations lives in `examples/auth-integration/`:
204
226
 
205
- ```typescript
206
- const header = sdk.createSessionHeader({
207
- containerId: 'session-header',
208
- theme: 'dark', // 'light' | 'dark' | 'auto'
209
- showControls: true,
210
- showEndButton: true,
211
- });
227
+ ```bash
228
+ # Build the SDK first
229
+ npm run build
212
230
 
213
- header.mount('#session-header');
231
+ # Start the demo (defaults to mock IdP + React storefront + Auth0 protocol)
232
+ cd examples/auth-integration
233
+ cp .env.mock .env
234
+ docker compose up --build
235
+
236
+ # Open http://localhost:8080/generate-test-url
214
237
  ```
215
238
 
216
- **Custom Styling**:
239
+ The demo includes:
240
+ - **Mock IdP** that speaks Auth0, Okta, Azure AD, and Cognito protocols
241
+ - **React ecommerce storefront** with product shop, cart, role-gated admin panel
242
+ - **Vanilla JS reference** with auth hooks and event log
243
+ - **Backend** with provider-agnostic auth exchange using the `AuthProvider` interface
244
+ - **Role-based access control** showing `gw-user` vs `org-admin` UI gating
217
245
 
218
- ```css
219
- .gw-session-header {
220
- background: #1a1a1a;
221
- padding: 12px 24px;
222
- }
246
+ See [examples/auth-integration/README.md](./examples/auth-integration/README.md) for full documentation, and [examples/auth-integration/docs/DEMO_WALKTHROUGH.md](./examples/auth-integration/docs/DEMO_WALKTHROUGH.md) for a step-by-step walkthrough.
223
247
 
224
- .gw-session-timer {
225
- font-size: 18px;
226
- color: #00ff88;
227
- }
248
+ ## Configuration
228
249
 
229
- .gw-session-timer--warning {
230
- color: #ff6b00;
250
+ ```typescript
251
+ interface SDKConfig {
252
+ // JWT & Validation
253
+ jwksUri?: string; // JWKS endpoint (default: GW production endpoint)
254
+ jwtParamName?: string; // URL query parameter name (default: 'gwSession')
255
+ applicationId?: string; // Your application ID
256
+ useBackendValidation?: boolean; // Use backend instead of JWKS (default: false)
257
+
258
+ // Session Behavior
259
+ autoStart?: boolean; // Auto-start from URL JWT (default: true)
260
+ warningThresholdSeconds?: number; // Warning before expiry (default: 300)
261
+ marketplaceUrl?: string; // Redirect URL after session end
262
+
263
+ // Lifecycle Hooks
264
+ hooks?: {
265
+ onSessionStart?: (ctx: SessionStartContext) => Promise<void> | void;
266
+ onSessionEnd?: (ctx: SessionEndContext) => Promise<void> | void;
267
+ onSessionExtend?: (ctx: SessionExtendContext) => Promise<void> | void;
268
+ onSessionWarning?: (ctx: SessionWarningContext) => Promise<void> | void;
269
+ };
270
+ hookTimeoutMs?: number; // Hook timeout (default: 5000)
271
+
272
+ // Phase 2 Features
273
+ enableHeartbeat?: boolean; // Server heartbeat (default: false)
274
+ heartbeatIntervalSeconds?: number; // Heartbeat interval (default: 30)
275
+ enableTabSync?: boolean; // Multi-tab sync (default: false)
276
+ pauseOnHidden?: boolean; // Pause when tab hidden (default: false)
277
+
278
+ // UI
279
+ themeMode?: 'light' | 'dark' | 'auto';
280
+ debug?: boolean; // Console logging (default: false)
231
281
  }
232
282
  ```
233
283
 
234
- ## 🔐 Authentication Integration
284
+ ## API Reference
235
285
 
236
- ### Exchange JWT for Your App's Tokens
286
+ ### MarketplaceSDK
237
287
 
238
288
  ```typescript
239
- onSessionStart: async (context) => {
240
- // Send marketplace JWT to your backend
241
- const response = await fetch('https://api.your-app.com/auth/marketplace', {
242
- method: 'POST',
243
- headers: {
244
- 'Authorization': `Bearer ${context.jwt}`,
245
- },
246
- });
247
-
248
- const { token } = await response.json();
249
-
250
- // Store your app's auth token
251
- localStorage.setItem('auth_token', token);
252
- },
253
- ```
289
+ class MarketplaceSDK {
290
+ constructor(config: SDKConfig)
254
291
 
255
- **Backend Example** (Python/Flask):
256
-
257
- ```python
258
- @app.route('/auth/marketplace', methods=['POST'])
259
- def marketplace_auth():
260
- jwt_token = request.headers.get('Authorization').replace('Bearer ', '')
261
-
262
- # Validate JWT
263
- claims = validate_marketplace_jwt(jwt_token)
264
-
265
- # Create/update user
266
- user = get_or_create_user(claims['userId'], claims.get('email'))
267
-
268
- # Generate your app's token
269
- app_token = generate_app_token(user)
270
-
271
- return jsonify({'token': app_token})
272
- ```
292
+ // Initialization
293
+ async initialize(): Promise<SessionData>
294
+
295
+ // Event handlers (fire after hooks complete)
296
+ on(event: 'onSessionStart', handler: (data: SessionData) => void): void
297
+ on(event: 'onSessionEnd', handler: () => void): void
298
+ on(event: 'onSessionWarning', handler: (data: { remainingSeconds: number }) => void): void
299
+ on(event: 'onError', handler: (error: Error) => void): void
300
+
301
+ // Timer
302
+ startTimer(): void
303
+ pauseTimer(): void
304
+ resumeTimer(): void
305
+ isTimerRunning(): boolean
306
+ getRemainingTime(): number // Seconds remaining
307
+ getFormattedTime(): string // "M:SS" format
308
+ getFormattedTimeWithHours(): string // "H:MM:SS" format
309
+
310
+ // Session control
311
+ async endSession(): Promise<void>
312
+ async extendSession(additionalMinutes: number): Promise<void>
313
+ async completeSession(actualUsageMinutes?: number): Promise<void>
273
314
 
274
- See [INTEGRATION_GUIDE.md#authentication-integration](./INTEGRATION_GUIDE.md#authentication-integration) for Cognito, Firebase, Auth0 examples.
315
+ // Data
316
+ getSessionData(): SessionData | null
275
317
 
276
- ## ⚙️ Configuration
318
+ // Cleanup
319
+ destroy(): void
320
+ }
321
+ ```
277
322
 
278
- ### SDK Options
323
+ ### Context Types
279
324
 
280
325
  ```typescript
281
- interface SDKOptions {
282
- // Required
283
- jwtParamName: string; // URL parameter name
284
- applicationId: string; // Your app ID
285
- jwksUrl: string; // JWKS endpoint
286
- onSessionStart: (context) => Promise<void>;
287
- onSessionEnd: (context) => Promise<void>;
288
-
289
- // Optional
290
- onSessionWarning?: (context) => Promise<void>;
291
- onSessionExtend?: (context) => Promise<void>;
292
- marketplaceUrl?: string; // Redirect after session end
293
- warningThresholdMinutes?: number; // Default: 5
294
- debug?: boolean; // Enable logging
295
- pauseWhenHidden?: boolean; // Auto-pause when tab hidden
326
+ interface SessionStartContext {
327
+ sessionId: string;
328
+ userId: string;
329
+ email?: string;
330
+ orgId: string;
331
+ applicationId: string;
332
+ durationMinutes: number;
333
+ expiresAt: number; // Unix seconds
334
+ jwt: string; // Raw JWT for backend exchange
335
+ }
336
+
337
+ interface SessionEndContext {
338
+ sessionId: string;
339
+ userId: string;
340
+ reason: 'expired' | 'manual' | 'error';
341
+ actualDurationMinutes?: number;
296
342
  }
343
+
344
+ interface SessionExtendContext {
345
+ sessionId: string;
346
+ userId: string;
347
+ additionalMinutes: number;
348
+ newExpiresAt: number;
349
+ }
350
+
351
+ interface SessionWarningContext {
352
+ sessionId: string;
353
+ userId: string;
354
+ remainingSeconds: number;
355
+ }
356
+ ```
357
+
358
+ ### Exports
359
+
360
+ ```typescript
361
+ // Main class
362
+ export { MarketplaceSDK } from '@mission_sciences/provider-sdk';
363
+ export { MarketplaceSDK as default } from '@mission_sciences/provider-sdk';
364
+
365
+ // UI components
366
+ export { SessionHeader } from '@mission_sciences/provider-sdk';
367
+ export { WarningModal } from '@mission_sciences/provider-sdk';
368
+
369
+ // Core utilities
370
+ export { JWTParser, JWKSValidator, TimerManager } from '@mission_sciences/provider-sdk';
371
+ export { HeartbeatManager, TabSyncManager } from '@mission_sciences/provider-sdk';
372
+
373
+ // Theming
374
+ export { lightTheme, darkTheme, getTheme, generateCSSVariables } from '@mission_sciences/provider-sdk';
375
+
376
+ // Types
377
+ export type {
378
+ SDKConfig, SessionData, SDKEvents,
379
+ SessionStartContext, SessionEndContext, SessionExtendContext, SessionWarningContext,
380
+ SessionLifecycleHooks, ThemeMode,
381
+ } from '@mission_sciences/provider-sdk';
297
382
  ```
298
383
 
299
384
  ### JWT Structure
@@ -308,11 +393,15 @@ interface SDKOptions {
308
393
  "startTime": 1763599337,
309
394
  "durationMinutes": 60,
310
395
  "exp": 1763602937,
311
- "iat": 1763599337
396
+ "iat": 1763599337,
397
+ "iss": "generalwisdom.com",
398
+ "sub": "a47884c8-50d1-7040-2de8-b7801699643c"
312
399
  }
313
400
  ```
314
401
 
315
- ## 🧪 Testing
402
+ > **Note:** `email` is optional. It is included when available from the user's identity provider but may not be present in all JWTs. Always check for its presence before using it.
403
+
404
+ ## Testing
316
405
 
317
406
  ### Generate Test JWT
318
407
 
@@ -321,278 +410,99 @@ npm run generate-keys # Create RSA key pair (dev only)
321
410
  npm run generate-jwt 60 # Generate 60-minute JWT
322
411
  ```
323
412
 
324
- ### Test Server
413
+ ### Dev Server
325
414
 
326
415
  ```bash
327
416
  npm run test-server # Start dev server at localhost:3000
417
+ npm run test-server-p2 # Phase 2 dev server with heartbeat/tab-sync
328
418
  ```
329
419
 
330
- Open: `http://localhost:3000?jwt=<YOUR_JWT>`
331
-
332
- See [TESTING_GUIDE.md](./TESTING_GUIDE.md) for unit, integration, and E2E testing.
420
+ Open: `http://localhost:3000?gwSession=<YOUR_JWT>`
333
421
 
334
- ## 🛠️ Development
335
-
336
- ### Build
422
+ ### Unit Tests
337
423
 
338
424
  ```bash
339
- npm run build # Build for production
340
- npm run dev # Watch mode
425
+ npm run test # Run all tests
426
+ npm run test:watch # Watch mode
427
+ npm run test:coverage # Coverage report
428
+ npm run test:integration # Integration tests
341
429
  ```
342
430
 
343
- ### Code Quality
431
+ ## Development
344
432
 
345
433
  ```bash
434
+ npm run build # Build for production (tsc + vite)
435
+ npm run dev # Vite dev server with HMR
346
436
  npm run lint # ESLint
347
437
  npm run format # Prettier
348
- npm run type-check # TypeScript
349
- ```
350
-
351
- ### Examples
352
-
353
- ```bash
354
- cd examples/vanilla-js
355
- npm install
356
- npm run dev
357
438
  ```
358
439
 
359
- ## 🏗️ Infrastructure & CI/CD
360
-
361
- ### GitHub Actions Workflow
362
-
363
- The package is built and published using a comprehensive 8-job GitHub Actions pipeline:
440
+ ### Build Output
364
441
 
365
- 1. **Test & Build** - Unit tests, type checking, linting, and production build
366
- 2. **Terraform Plan** - Review infrastructure changes (CodeArtifact setup)
367
- 3. **Terraform Apply** - Create/update AWS infrastructure
368
- 4. **Publish CodeArtifact** - Publish to private AWS registry
369
- 5. **Verify CodeArtifact** - Confirm successful publication
370
- 6. **Publish npm** - Publish to public npm with provenance
371
- 7. **Verify npm** - Confirm successful publication
372
- 8. **Create Release** - Generate GitHub release with artifacts
373
-
374
- **Authentication:**
375
- - AWS: OIDC via IAM role `GitHubActions-ProviderSDK` (no access keys)
376
- - npm: Trusted Publishing with cryptographic provenance (no tokens)
377
-
378
- ### Planning Documentation
379
-
380
- Comprehensive migration and setup documentation available in `planning/`:
381
-
382
- - **[PROJECT_CONTEXT.md](./planning/PROJECT_CONTEXT.md)** - Project overview and context
383
- - **[EXISTING_ANALYSIS.md](./planning/EXISTING_ANALYSIS.md)** - Codebase analysis
384
- - **[REQUIREMENTS.md](./planning/REQUIREMENTS.md)** - Migration requirements
385
- - **[CI_CD_ARCHITECTURE.md](./planning/CI_CD_ARCHITECTURE.md)** - Workflow design
386
- - **[AWS_OIDC_SETUP.md](./planning/AWS_OIDC_SETUP.md)** - AWS OIDC configuration
387
- - **[NPM_TRUSTED_PUBLISHING_SETUP.md](./planning/NPM_TRUSTED_PUBLISHING_SETUP.md)** - npm provenance setup
388
- - **[GITHUB_SETUP_GUIDE.md](./planning/GITHUB_SETUP_GUIDE.md)** - Complete setup guide
389
- - **[MIGRATION_CHECKLIST.md](./planning/MIGRATION_CHECKLIST.md)** - Migration checklist
390
-
391
- ## 📖 API Reference
392
-
393
- ### MarketplaceSDK
394
-
395
- ```typescript
396
- class MarketplaceSDK {
397
- constructor(options: SDKOptions)
398
-
399
- // Initialize SDK
400
- async initialize(): Promise<void>
401
-
402
- // Session management
403
- hasActiveSession(): boolean
404
- getSession(): Session | null
405
- async endSession(reason: string): Promise<void>
406
- async extendSession(minutes: number): Promise<void>
407
-
408
- // UI components
409
- createSessionHeader(options?: HeaderOptions): SessionHeader
410
-
411
- // Timer control
412
- pauseTimer(): void
413
- resumeTimer(): void
414
- isTimerPaused(): boolean
415
-
416
- // Cleanup
417
- destroy(): void
418
- }
419
442
  ```
420
-
421
- ### Context Types
422
-
423
- ```typescript
424
- interface SessionStartContext {
425
- sessionId: string;
426
- applicationId: string;
427
- userId: string;
428
- orgId?: string;
429
- email?: string;
430
- startTime: number;
431
- expiresAt: number;
432
- durationMinutes: number;
433
- jwt: string;
434
- }
435
-
436
- interface SessionEndContext {
437
- sessionId: string;
438
- userId: string;
439
- reason: 'expired' | 'manual' | 'error';
440
- actualDurationMinutes: number;
441
- }
443
+ dist/
444
+ ├── marketplace-sdk.es.js # ESM bundle
445
+ ├── marketplace-sdk.es.js.map
446
+ ├── marketplace-sdk.umd.js # UMD bundle
447
+ ├── marketplace-sdk.umd.js.map
448
+ └── index.d.ts # TypeScript declarations
442
449
  ```
443
450
 
444
- See [INTEGRATION_GUIDE.md#api-reference](./INTEGRATION_GUIDE.md#api-reference) for complete API documentation.
445
-
446
- ## 🚀 Production Deployment
447
-
448
- ### Checklist
449
-
450
- - [ ] Update `jwksUrl` to production endpoint
451
- - [ ] Set correct `applicationId`
452
- - [ ] Enable HTTPS for all endpoints
453
- - [ ] Configure proper CORS headers
454
- - [ ] Set up secrets management
455
- - [ ] Enable rate limiting
456
- - [ ] Configure monitoring and logging
457
- - [ ] Test with production JWT
458
- - [ ] Load test auth endpoints
459
-
460
- See [INTEGRATION_GUIDE.md#production-deployment](./INTEGRATION_GUIDE.md#production-deployment) for complete checklist.
461
-
462
- ## 🐛 Troubleshooting
463
-
464
- ### Common Issues
465
-
466
- **JWT validation failed**
467
- - Check JWKS URL is correct
468
- - Verify applicationId matches
469
- - Ensure JWT not expired
451
+ ## Infrastructure & CI/CD
470
452
 
471
- **Session header not showing**
472
- - Verify mount element exists
473
- - Check SDK initialized
474
- - Confirm active session
453
+ The package is built and published using an 8-job GitHub Actions pipeline:
475
454
 
476
- **Auth server 500 error**
477
- - Check Cognito configuration
478
- - Verify client secret (if required)
479
- - Review server logs
455
+ 1. **Test & Build** -- Unit tests, type checking, linting, production build
456
+ 2. **Terraform Plan** -- Review infrastructure changes (CodeArtifact)
457
+ 3. **Terraform Apply** -- Create/update AWS infrastructure
458
+ 4. **Publish CodeArtifact** -- Publish to private AWS registry
459
+ 5. **Verify CodeArtifact** -- Confirm publication
460
+ 6. **Publish npm** -- Publish to public npm with provenance
461
+ 7. **Verify npm** -- Confirm publication
462
+ 8. **Create Release** -- GitHub release with artifacts
480
463
 
481
- See [INTEGRATION_GUIDE.md#troubleshooting](./INTEGRATION_GUIDE.md#troubleshooting) for detailed solutions.
464
+ **Authentication**: AWS via OIDC (no access keys), npm via Trusted Publishing with cryptographic provenance (no tokens).
482
465
 
483
- ## 📚 Resources
466
+ ## Secure Publishing & Provenance
484
467
 
485
- - **[Integration Guide](./INTEGRATION_GUIDE.md)** - Complete integration reference
486
- - **[Quick Start](./QUICKSTART.md)** - Get started in 3 minutes
487
- - **[Testing Guide](./TESTING_GUIDE.md)** - Testing strategies
488
- - **[JWT Spec](./jwt-specification.md)** - Token format details
489
- - **[Examples](./examples/)** - Sample implementations
490
- - **[GhostDog Integration](../extension-ghostdog/MARKETPLACE_INTEGRATION.md)** - Real-world example
491
-
492
- ## 📦 Migration from @marketplace/provider-sdk
493
-
494
- ### Repository Migration
495
-
496
- This package has been migrated from Bitbucket to GitHub with enhanced security and public availability:
497
-
498
- **Old:**
499
- - Repository: Bitbucket (private)
500
- - Package: `@marketplace/provider-sdk`
501
- - Registry: AWS CodeArtifact only (private)
502
- - CI/CD: Bitbucket Pipelines with hardcoded credentials
503
-
504
- **New:**
505
- - Repository: [GitHub/Mission-Sciences/provider-sdk](https://github.com/Mission-Sciences/provider-sdk) (public)
506
- - Package: `@mission_sciences/provider-sdk`
507
- - Registry: npm (public) + AWS CodeArtifact (private)
508
- - CI/CD: GitHub Actions with OIDC (zero secrets)
509
- - Security: Cryptographic provenance attestation
510
-
511
- ### Migration Steps
512
-
513
- #### Step 1: Update package.json
468
+ - **Dual Publishing**: [npm](https://www.npmjs.com/package/@mission_sciences/provider-sdk) (public) + AWS CodeArtifact (private)
469
+ - **Cryptographic Signatures**: All releases signed with GitHub Actions OIDC
470
+ - **Provenance Transparency**: Build provenance in [Sigstore transparency log](https://search.sigstore.dev)
471
+ - **No Hardcoded Secrets**: CI/CD uses OIDC for all authentication
514
472
 
515
473
  ```bash
516
- npm uninstall @marketplace/provider-sdk
517
- npm install @mission_sciences/provider-sdk
518
- ```
519
-
520
- #### Step 2: Update imports
521
-
522
- ```typescript
523
- // Old
524
- import MarketplaceSDK from '@marketplace/provider-sdk';
525
-
526
- // New
527
- import MarketplaceSDK from '@mission_sciences/provider-sdk';
474
+ # Verify provenance
475
+ npm view @mission_sciences/provider-sdk --json | jq .dist
528
476
  ```
529
477
 
530
- #### Step 3: Simplify registry config
478
+ ## Production Checklist
531
479
 
532
- **If using npm (public registry):**
533
- ```bash
534
- # Remove .npmrc - use default npm registry (no configuration needed!)
535
- ```
480
+ - [ ] Set correct `applicationId`
481
+ - [ ] Set `hookTimeoutMs` appropriately for your auth provider latency
482
+ - [ ] Enable HTTPS on all endpoints
483
+ - [ ] Configure CORS headers
484
+ - [ ] Set up secrets management for backend token exchange
485
+ - [ ] Enable rate limiting on auth endpoints
486
+ - [ ] Verify tokens server-side (not just client-side JWKS)
487
+ - [ ] Test with production JWTs
536
488
 
537
- **If using CodeArtifact (private registry):**
538
- ```bash
539
- # Update your .npmrc
540
- @mission_sciences:registry=https://general-wisdom-dev-540845145946.d.codeartifact.us-east-1.amazonaws.com/npm/sdk-packages/
541
- ```
489
+ ## Troubleshooting
542
490
 
543
- **Note**: The API is 100% compatible. No code changes required beyond the package name!
491
+ **"No token found in URL parameter 'gwSession' or storage"** -- The SDK looks for the JWT in the URL query parameter configured via `jwtParamName` (default: `gwSession`). Make sure the marketplace redirect includes the JWT as `?gwSession=<token>` (or your custom parameter name).
544
492
 
545
- ### Benefits of Migration
493
+ **JWT validation failed** -- Check that `jwksUri` points to the correct JWKS endpoint and that the JWT hasn't expired.
546
494
 
547
- **Public Availability**: Install from npm without AWS credentials
548
- ✅ **Provenance Attestation**: Cryptographic proof of build integrity
549
- ✅ **Enhanced Security**: OIDC authentication, no hardcoded secrets
550
- ✅ **Open Source Workflow**: Public CI/CD pipeline on GitHub Actions
551
- ✅ **Dual Publishing**: Available on both public npm and private CodeArtifact
495
+ **Hook timeout** -- The default `hookTimeoutMs` is 5000ms. If your auth exchange involves multiple API calls (user lookup + creation + token grant), increase it to 10000ms or more.
552
496
 
553
- ## 🤝 Contributing
497
+ **Session header not rendering** -- `SessionHeader` is a separate exported class, not a method on `MarketplaceSDK`. Import and instantiate it directly.
554
498
 
555
- Contributions welcome! Please read [CONTRIBUTING.md](./CONTRIBUTING.md) first.
499
+ **Multi-tab conflicts** -- Enable `enableTabSync: true` to elect a master tab and sync session state across tabs via BroadcastChannel.
556
500
 
557
- ## 📄 License
501
+ ## License
558
502
 
559
- MIT License - see [LICENSE](./LICENSE) file for details
503
+ MIT -- see [LICENSE](./LICENSE)
560
504
 
561
- ## 🆘 Support
505
+ ## Support
562
506
 
563
507
  - **Issues**: [GitHub Issues](https://github.com/Mission-Sciences/provider-sdk/issues)
564
- - **Email**: support@generalwisdom.com
565
- - **Docs**: [docs.generalwisdom.com](https://docs.generalwisdom.com)
566
-
567
- ## 📊 Changelog
568
-
569
- ### v0.1.2 (2025-01-11) - Migration Release
570
- - 🏗️ Migrated from Bitbucket to GitHub
571
- - 📦 Package renamed: `@marketplace/provider-sdk` → `@mission_sciences/provider-sdk`
572
- - 🔒 Added cryptographic provenance attestation
573
- - ☁️ Dual publishing: npm (public) + AWS CodeArtifact (private)
574
- - 🔐 Zero-secret CI/CD with OIDC authentication
575
- - 📝 Comprehensive migration documentation
576
- - 🚀 GitHub Actions workflow with 8-job pipeline
577
-
578
- ### v0.1.1 (2024) - Pre-Migration
579
- - Initial Bitbucket release
580
- - CodeArtifact-only distribution
581
- - Bitbucket Pipelines CI/CD
582
-
583
- ### v2.0.0 (Planned - Phase 2)
584
- - Heartbeat system
585
- - Multi-tab coordination
586
- - Session extension
587
- - Early completion
588
- - Visibility API integration
589
-
590
- ### v1.0.0 (Phase 1)
591
- - JWT validation with JWKS
592
- - Session timer management
593
- - Lifecycle hooks
594
- - Session header component
595
-
596
- ---
597
-
598
- **Built with ❤️ by the General Wisdom team**
508
+ - **Discord**: [Discord](https://discord.com/invite/RN5gFEzXhB)