@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 +338 -428
- package/dist/index.d.ts +7 -3
- package/dist/marketplace-sdk.es.js +14 -9
- 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 +1 -1
package/README.md
CHANGED
|
@@ -7,293 +7,378 @@
|
|
|
7
7
|
[](https://www.npmjs.com/package/@mission_sciences/provider-sdk)
|
|
8
8
|
[](https://github.com/Mission-Sciences/provider-sdk/actions)
|
|
9
9
|
|
|
10
|
-
|
|
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
|
-
|
|
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
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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
|
-
##
|
|
48
|
+
## Documentation
|
|
37
49
|
|
|
38
|
-
- **[Integration Guide](./INTEGRATION_GUIDE.md)**
|
|
39
|
-
- **[Quick Start Guide](./QUICKSTART.md)**
|
|
40
|
-
- **[
|
|
41
|
-
- **[
|
|
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
|
-
|
|
56
|
+
## Features
|
|
44
57
|
|
|
45
|
-
|
|
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
|
-
|
|
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
|
-
###
|
|
50
|
-
-
|
|
51
|
-
-
|
|
52
|
-
-
|
|
53
|
-
-
|
|
54
|
-
-
|
|
55
|
-
-
|
|
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
|
-
|
|
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
|
|
82
|
+
Marketplace --> JWT in URL --> SDK validates via JWKS --> Lifecycle hooks fire --> Session active
|
|
69
83
|
```
|
|
70
84
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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
|
-
|
|
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
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
## 📦 Installation
|
|
95
|
+
# npm (public registry)
|
|
96
|
+
npm install @mission_sciences/provider-sdk
|
|
98
97
|
|
|
99
|
-
|
|
98
|
+
# yarn
|
|
99
|
+
yarn add @mission_sciences/provider-sdk
|
|
100
100
|
|
|
101
|
-
|
|
102
|
-
|
|
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
|
-
|
|
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
|
-
|
|
125
|
+
jwksUri: '/.well-known/jwks.json',
|
|
140
126
|
applicationId: 'my-app',
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
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
|
-
//
|
|
164
|
-
|
|
165
|
-
|
|
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
|
-
|
|
181
|
+
jwksUri: '/.well-known/jwks.json',
|
|
178
182
|
applicationId: 'my-react-app',
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
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
|
-
|
|
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 [
|
|
221
|
+
See the [Integration Guide](./INTEGRATION_GUIDE.md) for Vue, Chrome Extensions, and more patterns.
|
|
200
222
|
|
|
201
|
-
|
|
223
|
+
### Auth Integration Demo
|
|
202
224
|
|
|
203
|
-
|
|
225
|
+
A complete working example with Docker, 5 identity providers, and 2 frontend implementations lives in `examples/auth-integration/`:
|
|
204
226
|
|
|
205
|
-
```
|
|
206
|
-
|
|
207
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
225
|
-
font-size: 18px;
|
|
226
|
-
color: #00ff88;
|
|
227
|
-
}
|
|
248
|
+
## Configuration
|
|
228
249
|
|
|
229
|
-
|
|
230
|
-
|
|
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
|
-
##
|
|
284
|
+
## API Reference
|
|
235
285
|
|
|
236
|
-
###
|
|
286
|
+
### MarketplaceSDK
|
|
237
287
|
|
|
238
288
|
```typescript
|
|
239
|
-
|
|
240
|
-
|
|
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
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
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
|
-
|
|
315
|
+
// Data
|
|
316
|
+
getSessionData(): SessionData | null
|
|
275
317
|
|
|
276
|
-
|
|
318
|
+
// Cleanup
|
|
319
|
+
destroy(): void
|
|
320
|
+
}
|
|
321
|
+
```
|
|
277
322
|
|
|
278
|
-
###
|
|
323
|
+
### Context Types
|
|
279
324
|
|
|
280
325
|
```typescript
|
|
281
|
-
interface
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
//
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
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
|
-
|
|
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
|
-
###
|
|
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?
|
|
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
|
-
|
|
335
|
-
|
|
336
|
-
### Build
|
|
422
|
+
### Unit Tests
|
|
337
423
|
|
|
338
424
|
```bash
|
|
339
|
-
npm run
|
|
340
|
-
npm run
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
**
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
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
|
-
|
|
464
|
+
**Authentication**: AWS via OIDC (no access keys), npm via Trusted Publishing with cryptographic provenance (no tokens).
|
|
482
465
|
|
|
483
|
-
##
|
|
466
|
+
## Secure Publishing & Provenance
|
|
484
467
|
|
|
485
|
-
- **[
|
|
486
|
-
- **
|
|
487
|
-
- **[
|
|
488
|
-
- **
|
|
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
|
-
|
|
517
|
-
npm
|
|
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
|
-
|
|
478
|
+
## Production Checklist
|
|
531
479
|
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
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
|
-
|
|
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
|
-
**
|
|
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
|
-
|
|
493
|
+
**JWT validation failed** -- Check that `jwksUri` points to the correct JWKS endpoint and that the JWT hasn't expired.
|
|
546
494
|
|
|
547
|
-
|
|
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
|
-
|
|
497
|
+
**Session header not rendering** -- `SessionHeader` is a separate exported class, not a method on `MarketplaceSDK`. Import and instantiate it directly.
|
|
554
498
|
|
|
555
|
-
|
|
499
|
+
**Multi-tab conflicts** -- Enable `enableTabSync: true` to elect a master tab and sync session state across tabs via BroadcastChannel.
|
|
556
500
|
|
|
557
|
-
##
|
|
501
|
+
## License
|
|
558
502
|
|
|
559
|
-
MIT
|
|
503
|
+
MIT -- see [LICENSE](./LICENSE)
|
|
560
504
|
|
|
561
|
-
##
|
|
505
|
+
## Support
|
|
562
506
|
|
|
563
507
|
- **Issues**: [GitHub Issues](https://github.com/Mission-Sciences/provider-sdk/issues)
|
|
564
|
-
- **
|
|
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)
|