@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 +375 -323
- package/dist/index.d.ts +3 -1
- package/dist/marketplace-sdk.es.js +9 -5
- package/dist/marketplace-sdk.es.js.map +1 -1
- package/dist/marketplace-sdk.umd.js +1 -1
- package/dist/marketplace-sdk.umd.js.map +1 -1
- package/package.json +5 -1
package/README.md
CHANGED
|
@@ -4,265 +4,382 @@
|
|
|
4
4
|
|
|
5
5
|
[](https://opensource.org/licenses/MIT)
|
|
6
6
|
[](https://www.typescriptlang.org/)
|
|
7
|
+
[](https://www.npmjs.com/package/@mission_sciences/provider-sdk)
|
|
8
|
+
[](https://github.com/Mission-Sciences/provider-sdk/actions)
|
|
7
9
|
|
|
8
|
-
|
|
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
|
-
|
|
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
|
-
|
|
20
|
+
jwksUri: 'https://api.generalwisdom.com/.well-known/jwks.json',
|
|
21
21
|
applicationId: 'your-app-id',
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
-
##
|
|
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
|
-
|
|
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
|
-
|
|
57
|
+
## Features
|
|
44
58
|
|
|
45
|
-
|
|
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
|
-
###
|
|
48
|
-
-
|
|
49
|
-
-
|
|
50
|
-
-
|
|
51
|
-
-
|
|
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
|
|
56
|
-
-
|
|
57
|
-
-
|
|
58
|
-
-
|
|
59
|
-
-
|
|
60
|
-
-
|
|
61
|
-
-
|
|
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
|
-
##
|
|
80
|
+
## How It Works
|
|
64
81
|
|
|
65
82
|
```
|
|
66
|
-
Marketplace
|
|
83
|
+
Marketplace --> JWT in URL --> SDK validates via JWKS --> Lifecycle hooks fire --> Session active
|
|
67
84
|
```
|
|
68
85
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
###
|
|
106
|
+
### AWS CodeArtifact (Private Registry)
|
|
95
107
|
|
|
96
108
|
```bash
|
|
97
|
-
|
|
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
|
-
##
|
|
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
|
-
|
|
126
|
+
jwksUri: '/.well-known/jwks.json',
|
|
109
127
|
applicationId: 'my-app',
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
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
|
-
//
|
|
133
|
-
|
|
134
|
-
|
|
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
|
-
|
|
182
|
+
jwksUri: '/.well-known/jwks.json',
|
|
147
183
|
applicationId: 'my-react-app',
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
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
|
-
|
|
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 [
|
|
222
|
+
See the [Integration Guide](./INTEGRATION_GUIDE.md) for Vue, Chrome Extensions, and more patterns.
|
|
169
223
|
|
|
170
|
-
|
|
224
|
+
### Auth Integration Demo
|
|
171
225
|
|
|
172
|
-
|
|
226
|
+
A complete working example with Docker, 5 identity providers, and 2 frontend implementations lives in `examples/auth-integration/`:
|
|
173
227
|
|
|
174
|
-
```
|
|
175
|
-
|
|
176
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
194
|
-
font-size: 18px;
|
|
195
|
-
color: #00ff88;
|
|
196
|
-
}
|
|
249
|
+
## Configuration
|
|
197
250
|
|
|
198
|
-
|
|
199
|
-
|
|
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
|
-
##
|
|
285
|
+
## API Reference
|
|
204
286
|
|
|
205
|
-
###
|
|
287
|
+
### MarketplaceSDK
|
|
206
288
|
|
|
207
289
|
```typescript
|
|
208
|
-
|
|
209
|
-
|
|
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
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
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
|
-
|
|
316
|
+
// Data
|
|
317
|
+
getSessionData(): SessionData | null
|
|
244
318
|
|
|
245
|
-
|
|
319
|
+
// Cleanup
|
|
320
|
+
destroy(): void
|
|
321
|
+
}
|
|
322
|
+
```
|
|
246
323
|
|
|
247
|
-
###
|
|
324
|
+
### Context Types
|
|
248
325
|
|
|
249
326
|
```typescript
|
|
250
|
-
interface
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
//
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
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
|
-
|
|
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
|
-
###
|
|
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?
|
|
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
|
-
|
|
304
|
-
|
|
305
|
-
### Build
|
|
423
|
+
### Unit Tests
|
|
306
424
|
|
|
307
425
|
```bash
|
|
308
|
-
npm run
|
|
309
|
-
npm run
|
|
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
|
-
|
|
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
|
-
###
|
|
441
|
+
### Build Output
|
|
321
442
|
|
|
322
|
-
```bash
|
|
323
|
-
cd examples/vanilla-js
|
|
324
|
-
npm install
|
|
325
|
-
npm run dev
|
|
326
443
|
```
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
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
|
-
|
|
452
|
+
## Infrastructure & CI/CD
|
|
359
453
|
|
|
360
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
467
|
+
## Secure Publishing & Provenance
|
|
398
468
|
|
|
399
|
-
|
|
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
|
-
|
|
474
|
+
```bash
|
|
475
|
+
# Verify provenance
|
|
476
|
+
npm view @mission_sciences/provider-sdk --json | jq .dist
|
|
477
|
+
```
|
|
402
478
|
|
|
403
|
-
|
|
404
|
-
- Check JWKS URL is correct
|
|
405
|
-
- Verify applicationId matches
|
|
406
|
-
- Ensure JWT not expired
|
|
479
|
+
## Production Checklist
|
|
407
480
|
|
|
408
|
-
|
|
409
|
-
-
|
|
410
|
-
-
|
|
411
|
-
-
|
|
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
|
-
|
|
414
|
-
- Check Cognito configuration
|
|
415
|
-
- Verify client secret (if required)
|
|
416
|
-
- Review server logs
|
|
491
|
+
## Troubleshooting
|
|
417
492
|
|
|
418
|
-
|
|
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
|
-
|
|
495
|
+
**JWT validation failed** -- Check that `jwksUri` points to the correct JWKS endpoint and that the JWT hasn't expired.
|
|
421
496
|
|
|
422
|
-
|
|
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
|
-
|
|
499
|
+
**Session header not rendering** -- `SessionHeader` is a separate exported class, not a method on `MarketplaceSDK`. Import and instantiate it directly.
|
|
430
500
|
|
|
431
|
-
|
|
501
|
+
**Multi-tab conflicts** -- Enable `enableTabSync: true` to elect a master tab and sync session state across tabs via BroadcastChannel.
|
|
432
502
|
|
|
433
|
-
|
|
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
|
-
|
|
441
|
-
|
|
442
|
-
|
|
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
|
-
|
|
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
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
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
|
-
|
|
524
|
+
## Changelog
|
|
462
525
|
|
|
463
|
-
|
|
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
|
-
|
|
536
|
+
### v0.1.1 (2024) -- Pre-Migration
|
|
537
|
+
- Initial Bitbucket release
|
|
538
|
+
- CodeArtifact-only distribution
|
|
466
539
|
|
|
467
|
-
##
|
|
540
|
+
## License
|
|
468
541
|
|
|
469
|
-
MIT
|
|
542
|
+
MIT -- see [LICENSE](./LICENSE)
|
|
470
543
|
|
|
471
|
-
##
|
|
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**
|