@mission_sciences/provider-sdk 0.1.1 → 0.1.2-dev.d7154d7

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