@pulseid/client 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +85 -0
- package/dist/chunk-BRQ2T53Z.js +406 -0
- package/dist/chunk-BRQ2T53Z.js.map +1 -0
- package/dist/index.d.ts +404 -0
- package/dist/index.js +29 -0
- package/dist/index.js.map +1 -0
- package/dist/server.d.ts +44 -0
- package/dist/server.js +108 -0
- package/dist/server.js.map +1 -0
- package/package.json +75 -0
package/README.md
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# @pulseid/client
|
|
2
|
+
|
|
3
|
+
TypeScript SDK for [PULSE ID](https://id.pulserunning.at).
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pnpm add @pulseid/client
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Server (Recommended)
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
import { PulseIdServer } from '@pulseid/client/server';
|
|
15
|
+
|
|
16
|
+
const server = new PulseIdServer({
|
|
17
|
+
issuer: 'https://id.pulserunning.at',
|
|
18
|
+
clientId: process.env.PULSEID_CLIENT_ID!,
|
|
19
|
+
clientSecret: process.env.PULSEID_CLIENT_SECRET!,
|
|
20
|
+
storage: tokenStorage,
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
// Get a user-scoped client with auto-refresh
|
|
24
|
+
const pulse = server.forUser(userId);
|
|
25
|
+
const profile = await pulse.profile.get();
|
|
26
|
+
await pulse.profile.update({ displayName: 'New Name' });
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Browser
|
|
30
|
+
|
|
31
|
+
```typescript
|
|
32
|
+
import { PulseIdClient } from '@pulseid/client';
|
|
33
|
+
|
|
34
|
+
const client = new PulseIdClient({
|
|
35
|
+
issuer: 'https://id.pulserunning.at',
|
|
36
|
+
clientId: 'your-client-id',
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
// Build auth URL
|
|
40
|
+
const authUrl = client.buildAuthorizationUrl({
|
|
41
|
+
redirectUri: 'https://yourapp.com/callback',
|
|
42
|
+
scope: 'openid profile email offline_access',
|
|
43
|
+
state: crypto.randomUUID(),
|
|
44
|
+
nonce: crypto.randomUUID(),
|
|
45
|
+
codeChallenge,
|
|
46
|
+
codeChallengeMethod: 'S256',
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
// After callback
|
|
50
|
+
const profile = await client.getProfile(accessToken);
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Token Storage
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
import type { TokenStorage, StoredTokens } from '@pulseid/client/server';
|
|
57
|
+
|
|
58
|
+
const tokenStorage: TokenStorage = {
|
|
59
|
+
async getTokens(userId) { /* fetch from db */ },
|
|
60
|
+
async setTokens(userId, tokens) { /* save to db */ },
|
|
61
|
+
async deleteTokens(userId) { /* delete from db */ },
|
|
62
|
+
};
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Error Handling
|
|
66
|
+
|
|
67
|
+
```typescript
|
|
68
|
+
import { InvalidTokenError, InsufficientScopeError } from '@pulseid/client';
|
|
69
|
+
|
|
70
|
+
try {
|
|
71
|
+
await pulse.profile.get();
|
|
72
|
+
} catch (error) {
|
|
73
|
+
if (error instanceof InvalidTokenError) redirect('/login');
|
|
74
|
+
if (error instanceof InsufficientScopeError) console.log(error.requiredScope);
|
|
75
|
+
throw error;
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Docs
|
|
80
|
+
|
|
81
|
+
See [docs](./docs) or run `pnpm docs:dev`.
|
|
82
|
+
|
|
83
|
+
## License
|
|
84
|
+
|
|
85
|
+
MIT
|
|
@@ -0,0 +1,406 @@
|
|
|
1
|
+
// src/errors.ts
|
|
2
|
+
var PulseIdError = class _PulseIdError extends Error {
|
|
3
|
+
code;
|
|
4
|
+
statusCode;
|
|
5
|
+
constructor(code, message, statusCode = 400) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.name = "PulseIdError";
|
|
8
|
+
this.code = code;
|
|
9
|
+
this.statusCode = statusCode;
|
|
10
|
+
if (Error.captureStackTrace) {
|
|
11
|
+
Error.captureStackTrace(this, _PulseIdError);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Create an error from an API response.
|
|
16
|
+
*/
|
|
17
|
+
static fromResponse(response, statusCode) {
|
|
18
|
+
const code = mapErrorCode(response.error);
|
|
19
|
+
switch (code) {
|
|
20
|
+
case "invalid_token":
|
|
21
|
+
case "expired_token":
|
|
22
|
+
return new InvalidTokenError(response.error_description);
|
|
23
|
+
case "insufficient_scope":
|
|
24
|
+
return new InsufficientScopeError(
|
|
25
|
+
response.error_description,
|
|
26
|
+
response.required_scope
|
|
27
|
+
);
|
|
28
|
+
case "not_found":
|
|
29
|
+
return new NotFoundError(response.error_description);
|
|
30
|
+
default:
|
|
31
|
+
return new _PulseIdError(code, response.error_description, statusCode);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
var InvalidTokenError = class extends PulseIdError {
|
|
36
|
+
constructor(message = "Invalid or expired access token") {
|
|
37
|
+
super("invalid_token", message, 401);
|
|
38
|
+
this.name = "InvalidTokenError";
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
var InsufficientScopeError = class extends PulseIdError {
|
|
42
|
+
requiredScope;
|
|
43
|
+
constructor(message, requiredScope) {
|
|
44
|
+
super("insufficient_scope", message, 403);
|
|
45
|
+
this.name = "InsufficientScopeError";
|
|
46
|
+
this.requiredScope = requiredScope;
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
var NotFoundError = class extends PulseIdError {
|
|
50
|
+
constructor(message = "Resource not found") {
|
|
51
|
+
super("not_found", message, 404);
|
|
52
|
+
this.name = "NotFoundError";
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
var NetworkError = class extends PulseIdError {
|
|
56
|
+
cause;
|
|
57
|
+
constructor(message, cause) {
|
|
58
|
+
super("server_error", message, 0);
|
|
59
|
+
this.name = "NetworkError";
|
|
60
|
+
this.cause = cause;
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
var TimeoutError = class extends PulseIdError {
|
|
64
|
+
constructor(message = "Request timed out") {
|
|
65
|
+
super("server_error", message, 0);
|
|
66
|
+
this.name = "TimeoutError";
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
function mapErrorCode(apiError) {
|
|
70
|
+
switch (apiError) {
|
|
71
|
+
case "invalid_token":
|
|
72
|
+
return "invalid_token";
|
|
73
|
+
case "expired_token":
|
|
74
|
+
return "expired_token";
|
|
75
|
+
case "insufficient_scope":
|
|
76
|
+
return "insufficient_scope";
|
|
77
|
+
case "not_found":
|
|
78
|
+
return "not_found";
|
|
79
|
+
case "invalid_request":
|
|
80
|
+
return "invalid_request";
|
|
81
|
+
default:
|
|
82
|
+
return "server_error";
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// src/http.ts
|
|
87
|
+
var DEFAULT_TIMEOUT = 3e4;
|
|
88
|
+
function validateIssuerUrl(issuer) {
|
|
89
|
+
let url;
|
|
90
|
+
try {
|
|
91
|
+
url = new URL(issuer);
|
|
92
|
+
} catch {
|
|
93
|
+
throw new Error(`Invalid issuer URL: ${issuer}`);
|
|
94
|
+
}
|
|
95
|
+
if (url.username || url.password) {
|
|
96
|
+
throw new Error(`Issuer URL must not include credentials: ${issuer}`);
|
|
97
|
+
}
|
|
98
|
+
const isLocalhost = url.hostname === "localhost" || url.hostname === "127.0.0.1" || url.hostname === "::1";
|
|
99
|
+
if (url.protocol !== "https:" && !isLocalhost) {
|
|
100
|
+
throw new Error(`Issuer URL must use HTTPS: ${issuer}`);
|
|
101
|
+
}
|
|
102
|
+
return url.origin + url.pathname.replace(/\/$/, "");
|
|
103
|
+
}
|
|
104
|
+
function createHttpClient(config) {
|
|
105
|
+
const baseUrl = validateIssuerUrl(config.issuer);
|
|
106
|
+
const fetchFn = config.fetch ?? globalThis.fetch;
|
|
107
|
+
const timeout = config.timeout ?? DEFAULT_TIMEOUT;
|
|
108
|
+
async function request(path, options) {
|
|
109
|
+
const url = `${baseUrl}${path}`;
|
|
110
|
+
const controller = new AbortController();
|
|
111
|
+
const timeoutId = setTimeout(() => {
|
|
112
|
+
controller.abort();
|
|
113
|
+
}, options.timeout ?? timeout);
|
|
114
|
+
try {
|
|
115
|
+
const response = await fetchFn(url, {
|
|
116
|
+
method: options.method,
|
|
117
|
+
headers: {
|
|
118
|
+
"Content-Type": "application/json",
|
|
119
|
+
Accept: "application/json",
|
|
120
|
+
...options.headers
|
|
121
|
+
},
|
|
122
|
+
...options.body !== void 0 && { body: JSON.stringify(options.body) },
|
|
123
|
+
signal: controller.signal
|
|
124
|
+
});
|
|
125
|
+
clearTimeout(timeoutId);
|
|
126
|
+
const contentType = response.headers.get("content-type");
|
|
127
|
+
const isJson = contentType?.includes("application/json");
|
|
128
|
+
if (!response.ok) {
|
|
129
|
+
if (isJson) {
|
|
130
|
+
const errorBody = await response.json();
|
|
131
|
+
throw PulseIdError.fromResponse(errorBody, response.status);
|
|
132
|
+
}
|
|
133
|
+
const errorText = await response.text();
|
|
134
|
+
throw new PulseIdError(
|
|
135
|
+
"server_error",
|
|
136
|
+
errorText || `HTTP ${response.status}`,
|
|
137
|
+
response.status
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
if (response.status === 204 || !isJson) {
|
|
141
|
+
return {};
|
|
142
|
+
}
|
|
143
|
+
return await response.json();
|
|
144
|
+
} catch (error) {
|
|
145
|
+
clearTimeout(timeoutId);
|
|
146
|
+
if (error instanceof PulseIdError) {
|
|
147
|
+
throw error;
|
|
148
|
+
}
|
|
149
|
+
if (error instanceof DOMException && error.name === "AbortError") {
|
|
150
|
+
throw new TimeoutError(`Request to ${path} timed out after ${timeout}ms`);
|
|
151
|
+
}
|
|
152
|
+
if (error instanceof TypeError) {
|
|
153
|
+
throw new NetworkError(`Network request to ${path} failed`, error);
|
|
154
|
+
}
|
|
155
|
+
throw new NetworkError(
|
|
156
|
+
`Unknown error during request to ${path}`,
|
|
157
|
+
error instanceof Error ? error : void 0
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return {
|
|
162
|
+
/**
|
|
163
|
+
* Make a GET request.
|
|
164
|
+
*/
|
|
165
|
+
get(path, headers) {
|
|
166
|
+
return request(path, { method: "GET", ...headers && { headers } });
|
|
167
|
+
},
|
|
168
|
+
/**
|
|
169
|
+
* Make a POST request.
|
|
170
|
+
*/
|
|
171
|
+
post(path, body, headers) {
|
|
172
|
+
return request(path, { method: "POST", body, ...headers && { headers } });
|
|
173
|
+
},
|
|
174
|
+
/**
|
|
175
|
+
* Make a PATCH request.
|
|
176
|
+
*/
|
|
177
|
+
patch(path, body, headers) {
|
|
178
|
+
return request(path, { method: "PATCH", body, ...headers && { headers } });
|
|
179
|
+
},
|
|
180
|
+
/**
|
|
181
|
+
* Make a DELETE request.
|
|
182
|
+
*/
|
|
183
|
+
delete(path, headers) {
|
|
184
|
+
return request(path, { method: "DELETE", ...headers && { headers } });
|
|
185
|
+
},
|
|
186
|
+
/**
|
|
187
|
+
* Make a form-encoded POST request (for OAuth token endpoints).
|
|
188
|
+
*/
|
|
189
|
+
async postForm(path, data, headers) {
|
|
190
|
+
const url = `${baseUrl}${path}`;
|
|
191
|
+
const controller = new AbortController();
|
|
192
|
+
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
193
|
+
try {
|
|
194
|
+
const response = await fetchFn(url, {
|
|
195
|
+
method: "POST",
|
|
196
|
+
headers: {
|
|
197
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
198
|
+
Accept: "application/json",
|
|
199
|
+
...headers
|
|
200
|
+
},
|
|
201
|
+
body: new URLSearchParams(data).toString(),
|
|
202
|
+
signal: controller.signal
|
|
203
|
+
});
|
|
204
|
+
clearTimeout(timeoutId);
|
|
205
|
+
const contentType = response.headers.get("content-type");
|
|
206
|
+
const isJson = contentType?.includes("application/json");
|
|
207
|
+
if (!response.ok) {
|
|
208
|
+
if (isJson) {
|
|
209
|
+
const errorBody = await response.json();
|
|
210
|
+
throw PulseIdError.fromResponse(errorBody, response.status);
|
|
211
|
+
}
|
|
212
|
+
const errorText = await response.text();
|
|
213
|
+
throw new PulseIdError(
|
|
214
|
+
"server_error",
|
|
215
|
+
errorText || `HTTP ${response.status}`,
|
|
216
|
+
response.status
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
return await response.json();
|
|
220
|
+
} catch (error) {
|
|
221
|
+
clearTimeout(timeoutId);
|
|
222
|
+
if (error instanceof PulseIdError) throw error;
|
|
223
|
+
if (error instanceof DOMException && error.name === "AbortError") {
|
|
224
|
+
throw new TimeoutError();
|
|
225
|
+
}
|
|
226
|
+
throw new NetworkError(
|
|
227
|
+
`Request to ${path} failed`,
|
|
228
|
+
error instanceof Error ? error : void 0
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// src/client.ts
|
|
236
|
+
var PulseIdClient = class {
|
|
237
|
+
http;
|
|
238
|
+
config;
|
|
239
|
+
constructor(config) {
|
|
240
|
+
this.config = config;
|
|
241
|
+
this.http = createHttpClient(config);
|
|
242
|
+
}
|
|
243
|
+
// ===========================================================================
|
|
244
|
+
// PROFILE
|
|
245
|
+
// ===========================================================================
|
|
246
|
+
/**
|
|
247
|
+
* Get the authenticated user's profile.
|
|
248
|
+
*
|
|
249
|
+
* The fields returned depend on the scopes granted to the access token:
|
|
250
|
+
* - `profile`: name, avatar, birthday, etc.
|
|
251
|
+
* - `email`: email address and verification status
|
|
252
|
+
* - `address`: address information
|
|
253
|
+
* - `phone`: phone number
|
|
254
|
+
*
|
|
255
|
+
* @param accessToken - A valid access token
|
|
256
|
+
* @returns The user's profile
|
|
257
|
+
*
|
|
258
|
+
* @example
|
|
259
|
+
* ```typescript
|
|
260
|
+
* const profile = await client.getProfile(accessToken);
|
|
261
|
+
* console.log(`Hello, ${profile.displayName}!`);
|
|
262
|
+
* ```
|
|
263
|
+
*/
|
|
264
|
+
async getProfile(accessToken) {
|
|
265
|
+
return this.http.get("/api/v1/me", {
|
|
266
|
+
Authorization: `Bearer ${accessToken}`
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Update the authenticated user's profile.
|
|
271
|
+
*
|
|
272
|
+
* Requires the `profile:write` scope.
|
|
273
|
+
*
|
|
274
|
+
* @param accessToken - A valid access token with `profile:write` scope
|
|
275
|
+
* @param data - The profile fields to update
|
|
276
|
+
* @returns The updated profile
|
|
277
|
+
*
|
|
278
|
+
* @example
|
|
279
|
+
* ```typescript
|
|
280
|
+
* const updated = await client.updateProfile(accessToken, {
|
|
281
|
+
* displayName: 'Max Runner',
|
|
282
|
+
* height: 180,
|
|
283
|
+
* });
|
|
284
|
+
* ```
|
|
285
|
+
*/
|
|
286
|
+
async updateProfile(accessToken, data) {
|
|
287
|
+
return this.http.patch("/api/v1/me", data, {
|
|
288
|
+
Authorization: `Bearer ${accessToken}`
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* Get the authenticated user's OIDC userinfo.
|
|
293
|
+
*
|
|
294
|
+
* Requires the `openid` scope. Additional fields depend on granted scopes.
|
|
295
|
+
*
|
|
296
|
+
* @param accessToken - A valid access token
|
|
297
|
+
* @returns The user's OIDC userinfo
|
|
298
|
+
*
|
|
299
|
+
* @example
|
|
300
|
+
* ```typescript
|
|
301
|
+
* const info = await client.getUserInfo(accessToken);
|
|
302
|
+
* console.log(info.sub);
|
|
303
|
+
* ```
|
|
304
|
+
*/
|
|
305
|
+
async getUserInfo(accessToken) {
|
|
306
|
+
return this.http.get("/userinfo", {
|
|
307
|
+
Authorization: `Bearer ${accessToken}`
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
// ===========================================================================
|
|
311
|
+
// UTILITY METHODS
|
|
312
|
+
// ===========================================================================
|
|
313
|
+
/**
|
|
314
|
+
* Get the configured issuer URL.
|
|
315
|
+
*/
|
|
316
|
+
get issuer() {
|
|
317
|
+
return this.config.issuer;
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* Get the configured client ID.
|
|
321
|
+
*/
|
|
322
|
+
get clientId() {
|
|
323
|
+
return this.config.clientId;
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* Build an authorization URL for the OAuth flow.
|
|
327
|
+
*
|
|
328
|
+
* @param options - Authorization options
|
|
329
|
+
* @returns The authorization URL to redirect the user to
|
|
330
|
+
*
|
|
331
|
+
* @example
|
|
332
|
+
* ```typescript
|
|
333
|
+
* const authUrl = client.buildAuthorizationUrl({
|
|
334
|
+
* redirectUri: 'https://myapp.com/callback',
|
|
335
|
+
* scope: 'openid profile email',
|
|
336
|
+
* state: 'random-state-string',
|
|
337
|
+
* });
|
|
338
|
+
* window.location.href = authUrl;
|
|
339
|
+
* ```
|
|
340
|
+
*/
|
|
341
|
+
buildAuthorizationUrl(options) {
|
|
342
|
+
if (options.codeChallengeMethod && options.codeChallengeMethod !== "S256") {
|
|
343
|
+
throw new Error("code_challenge_method must be S256");
|
|
344
|
+
}
|
|
345
|
+
if (!options.state) {
|
|
346
|
+
throw new Error("state is required for authorization requests");
|
|
347
|
+
}
|
|
348
|
+
if (!options.codeChallenge) {
|
|
349
|
+
throw new Error("code_challenge is required for authorization requests");
|
|
350
|
+
}
|
|
351
|
+
const scopes = options.scope.split(" ").filter(Boolean);
|
|
352
|
+
if (scopes.includes("openid") && !options.nonce) {
|
|
353
|
+
throw new Error("nonce is required when requesting openid scope");
|
|
354
|
+
}
|
|
355
|
+
const url = new URL(`${this.config.issuer}/authorize`);
|
|
356
|
+
url.searchParams.set("response_type", "code");
|
|
357
|
+
url.searchParams.set("client_id", this.config.clientId);
|
|
358
|
+
url.searchParams.set("redirect_uri", options.redirectUri);
|
|
359
|
+
url.searchParams.set("scope", options.scope);
|
|
360
|
+
url.searchParams.set("state", options.state);
|
|
361
|
+
if (options.nonce) {
|
|
362
|
+
url.searchParams.set("nonce", options.nonce);
|
|
363
|
+
}
|
|
364
|
+
url.searchParams.set("code_challenge", options.codeChallenge);
|
|
365
|
+
url.searchParams.set("code_challenge_method", options.codeChallengeMethod ?? "S256");
|
|
366
|
+
if (options.prompt) {
|
|
367
|
+
url.searchParams.set("prompt", options.prompt);
|
|
368
|
+
}
|
|
369
|
+
if (options.loginHint) {
|
|
370
|
+
url.searchParams.set("login_hint", options.loginHint);
|
|
371
|
+
}
|
|
372
|
+
return url.toString();
|
|
373
|
+
}
|
|
374
|
+
/**
|
|
375
|
+
* Build a logout URL for ending the session.
|
|
376
|
+
*
|
|
377
|
+
* @param options - Logout options
|
|
378
|
+
* @returns The logout URL to redirect the user to
|
|
379
|
+
*
|
|
380
|
+
* @example
|
|
381
|
+
* ```typescript
|
|
382
|
+
* const logoutUrl = client.buildLogoutUrl({
|
|
383
|
+
* idTokenHint: idToken,
|
|
384
|
+
* postLogoutRedirectUri: 'https://myapp.com',
|
|
385
|
+
* });
|
|
386
|
+
* window.location.href = logoutUrl;
|
|
387
|
+
* ```
|
|
388
|
+
*/
|
|
389
|
+
buildLogoutUrl(options) {
|
|
390
|
+
const url = new URL(`${this.config.issuer}/logout`);
|
|
391
|
+
if (options?.idTokenHint) {
|
|
392
|
+
url.searchParams.set("id_token_hint", options.idTokenHint);
|
|
393
|
+
}
|
|
394
|
+
if (options?.postLogoutRedirectUri) {
|
|
395
|
+
url.searchParams.set("post_logout_redirect_uri", options.postLogoutRedirectUri);
|
|
396
|
+
}
|
|
397
|
+
if (options?.state) {
|
|
398
|
+
url.searchParams.set("state", options.state);
|
|
399
|
+
}
|
|
400
|
+
return url.toString();
|
|
401
|
+
}
|
|
402
|
+
};
|
|
403
|
+
|
|
404
|
+
export { InsufficientScopeError, InvalidTokenError, NetworkError, NotFoundError, PulseIdClient, PulseIdError, TimeoutError };
|
|
405
|
+
//# sourceMappingURL=chunk-BRQ2T53Z.js.map
|
|
406
|
+
//# sourceMappingURL=chunk-BRQ2T53Z.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/errors.ts","../src/http.ts","../src/client.ts"],"names":[],"mappings":"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","file":"chunk-BRQ2T53Z.js","sourcesContent":["/**\n * PULSE ID SDK Errors\n *\n * Custom error classes for better error handling.\n */\n\nimport type { ApiErrorResponse, ErrorCode } from './types.js';\n\n/**\n * Base error class for PULSE ID SDK errors.\n */\nexport class PulseIdError extends Error {\n readonly code: ErrorCode;\n readonly statusCode: number;\n\n constructor(code: ErrorCode, message: string, statusCode = 400) {\n super(message);\n this.name = 'PulseIdError';\n this.code = code;\n this.statusCode = statusCode;\n\n // Maintains proper stack trace in V8 environments\n if (Error.captureStackTrace) {\n Error.captureStackTrace(this, PulseIdError);\n }\n }\n\n /**\n * Create an error from an API response.\n */\n static fromResponse(response: ApiErrorResponse, statusCode: number): PulseIdError {\n const code = mapErrorCode(response.error);\n\n switch (code) {\n case 'invalid_token':\n case 'expired_token':\n return new InvalidTokenError(response.error_description);\n case 'insufficient_scope':\n return new InsufficientScopeError(\n response.error_description,\n response.required_scope\n );\n case 'not_found':\n return new NotFoundError(response.error_description);\n default:\n return new PulseIdError(code, response.error_description, statusCode);\n }\n }\n}\n\n/**\n * Error thrown when the access token is invalid or expired.\n */\nexport class InvalidTokenError extends PulseIdError {\n constructor(message = 'Invalid or expired access token') {\n super('invalid_token', message, 401);\n this.name = 'InvalidTokenError';\n }\n}\n\n/**\n * Error thrown when the access token lacks required scopes.\n */\nexport class InsufficientScopeError extends PulseIdError {\n readonly requiredScope: string | undefined;\n\n constructor(message: string, requiredScope?: string) {\n super('insufficient_scope', message, 403);\n this.name = 'InsufficientScopeError';\n this.requiredScope = requiredScope;\n }\n}\n\n/**\n * Error thrown when a resource is not found.\n */\nexport class NotFoundError extends PulseIdError {\n constructor(message = 'Resource not found') {\n super('not_found', message, 404);\n this.name = 'NotFoundError';\n }\n}\n\n/**\n * Error thrown when a network request fails.\n */\nexport class NetworkError extends PulseIdError {\n readonly cause: Error | undefined;\n\n constructor(message: string, cause?: Error) {\n super('server_error', message, 0);\n this.name = 'NetworkError';\n this.cause = cause;\n }\n}\n\n/**\n * Error thrown when a request times out.\n */\nexport class TimeoutError extends PulseIdError {\n constructor(message = 'Request timed out') {\n super('server_error', message, 0);\n this.name = 'TimeoutError';\n }\n}\n\n/**\n * Map API error codes to SDK error codes.\n */\nfunction mapErrorCode(apiError: string): ErrorCode {\n switch (apiError) {\n case 'invalid_token':\n return 'invalid_token';\n case 'expired_token':\n return 'expired_token';\n case 'insufficient_scope':\n return 'insufficient_scope';\n case 'not_found':\n return 'not_found';\n case 'invalid_request':\n return 'invalid_request';\n default:\n return 'server_error';\n }\n}\n","/**\n * HTTP Client Utilities\n *\n * Internal utilities for making HTTP requests with proper error handling.\n */\n\nimport { NetworkError, PulseIdError, TimeoutError } from './errors.js';\nimport type { ApiErrorResponse, PulseIdConfig } from './types.js';\n\nconst DEFAULT_TIMEOUT = 30_000; // 30 seconds\n\ntype HttpMethod = 'GET' | 'POST' | 'PATCH' | 'DELETE';\n\ntype RequestOptions = {\n method: HttpMethod;\n headers?: Record<string, string>;\n body?: unknown;\n timeout?: number;\n};\n\n/**\n * Validate that a URL is a valid HTTPS URL.\n * Prevents SSRF and ensures secure communication.\n */\nfunction validateIssuerUrl(issuer: string): string {\n let url: URL;\n try {\n url = new URL(issuer);\n } catch {\n throw new Error(`Invalid issuer URL: ${issuer}`);\n }\n\n if (url.username || url.password) {\n throw new Error(`Issuer URL must not include credentials: ${issuer}`);\n }\n\n // Only allow HTTPS in production (allow HTTP for localhost in development)\n const isLocalhost =\n url.hostname === 'localhost' || url.hostname === '127.0.0.1' || url.hostname === '::1';\n if (url.protocol !== 'https:' && !isLocalhost) {\n throw new Error(`Issuer URL must use HTTPS: ${issuer}`);\n }\n\n // Remove trailing slash for consistent URL building\n return url.origin + url.pathname.replace(/\\/$/, '');\n}\n\n/**\n * Create a configured HTTP client for the PULSE ID API.\n */\nexport function createHttpClient(config: PulseIdConfig) {\n const baseUrl = validateIssuerUrl(config.issuer);\n const fetchFn = config.fetch ?? globalThis.fetch;\n const timeout = config.timeout ?? DEFAULT_TIMEOUT;\n\n /**\n * Make an HTTP request to the PULSE ID API.\n */\n async function request<T>(path: string, options: RequestOptions): Promise<T> {\n const url = `${baseUrl}${path}`;\n const controller = new AbortController();\n\n // Set up timeout\n const timeoutId = setTimeout(() => {\n controller.abort();\n }, options.timeout ?? timeout);\n\n try {\n const response = await fetchFn(url, {\n method: options.method,\n headers: {\n 'Content-Type': 'application/json',\n Accept: 'application/json',\n ...options.headers,\n },\n ...(options.body !== undefined && { body: JSON.stringify(options.body) }),\n signal: controller.signal,\n });\n\n // Clear the timeout\n clearTimeout(timeoutId);\n\n // Parse response body\n const contentType = response.headers.get('content-type');\n const isJson = contentType?.includes('application/json');\n\n if (!response.ok) {\n if (isJson) {\n const errorBody = (await response.json()) as ApiErrorResponse;\n throw PulseIdError.fromResponse(errorBody, response.status);\n }\n\n const errorText = await response.text();\n throw new PulseIdError(\n 'server_error',\n errorText || `HTTP ${response.status}`,\n response.status\n );\n }\n\n // Return parsed JSON or empty object for 204\n if (response.status === 204 || !isJson) {\n return {} as T;\n }\n\n return (await response.json()) as T;\n } catch (error) {\n clearTimeout(timeoutId);\n\n // Re-throw SDK errors as-is\n if (error instanceof PulseIdError) {\n throw error;\n }\n\n // Handle abort (timeout)\n if (error instanceof DOMException && error.name === 'AbortError') {\n throw new TimeoutError(`Request to ${path} timed out after ${timeout}ms`);\n }\n\n // Handle network errors\n if (error instanceof TypeError) {\n throw new NetworkError(`Network request to ${path} failed`, error);\n }\n\n // Unknown error\n throw new NetworkError(\n `Unknown error during request to ${path}`,\n error instanceof Error ? error : undefined\n );\n }\n }\n\n return {\n /**\n * Make a GET request.\n */\n get<T>(path: string, headers?: Record<string, string>): Promise<T> {\n return request<T>(path, { method: 'GET', ...(headers && { headers }) });\n },\n\n /**\n * Make a POST request.\n */\n post<T>(path: string, body?: unknown, headers?: Record<string, string>): Promise<T> {\n return request<T>(path, { method: 'POST', body, ...(headers && { headers }) });\n },\n\n /**\n * Make a PATCH request.\n */\n patch<T>(path: string, body?: unknown, headers?: Record<string, string>): Promise<T> {\n return request<T>(path, { method: 'PATCH', body, ...(headers && { headers }) });\n },\n\n /**\n * Make a DELETE request.\n */\n delete<T>(path: string, headers?: Record<string, string>): Promise<T> {\n return request<T>(path, { method: 'DELETE', ...(headers && { headers }) });\n },\n\n /**\n * Make a form-encoded POST request (for OAuth token endpoints).\n */\n async postForm<T>(\n path: string,\n data: Record<string, string>,\n headers?: Record<string, string>\n ): Promise<T> {\n const url = `${baseUrl}${path}`;\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), timeout);\n\n try {\n const response = await fetchFn(url, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/x-www-form-urlencoded',\n Accept: 'application/json',\n ...headers,\n },\n body: new URLSearchParams(data).toString(),\n signal: controller.signal,\n });\n\n clearTimeout(timeoutId);\n\n const contentType = response.headers.get('content-type');\n const isJson = contentType?.includes('application/json');\n\n if (!response.ok) {\n if (isJson) {\n const errorBody = (await response.json()) as ApiErrorResponse;\n throw PulseIdError.fromResponse(errorBody, response.status);\n }\n // Handle non-JSON error responses (e.g., HTML from proxy)\n const errorText = await response.text();\n throw new PulseIdError(\n 'server_error',\n errorText || `HTTP ${response.status}`,\n response.status\n );\n }\n\n return (await response.json()) as T;\n } catch (error) {\n clearTimeout(timeoutId);\n\n if (error instanceof PulseIdError) throw error;\n if (error instanceof DOMException && error.name === 'AbortError') {\n throw new TimeoutError();\n }\n throw new NetworkError(\n `Request to ${path} failed`,\n error instanceof Error ? error : undefined\n );\n }\n },\n };\n}\n\nexport type HttpClient = ReturnType<typeof createHttpClient>;\n","/**\n * PULSE ID Client\n *\n * Client-side SDK for interacting with the PULSE ID API.\n * Use this in browser environments where you have an access token.\n *\n * For server-side usage with refresh token management, use `PulseIdServer` from './server'.\n */\n\nimport { createHttpClient, type HttpClient } from './http.js';\nimport type { Profile, ProfileUpdate, PulseIdConfig, UserInfo } from './types.js';\n\n/**\n * PULSE ID API Client.\n *\n * @example\n * ```typescript\n * const client = new PulseIdClient({\n * issuer: 'https://id.pulserunning.at',\n * clientId: 'your-client-id',\n * });\n *\n * const profile = await client.getProfile(accessToken);\n * console.log(profile.displayName);\n * ```\n */\nexport class PulseIdClient {\n protected readonly http: HttpClient;\n protected readonly config: PulseIdConfig;\n\n constructor(config: PulseIdConfig) {\n this.config = config;\n this.http = createHttpClient(config);\n }\n\n // ===========================================================================\n // PROFILE\n // ===========================================================================\n\n /**\n * Get the authenticated user's profile.\n *\n * The fields returned depend on the scopes granted to the access token:\n * - `profile`: name, avatar, birthday, etc.\n * - `email`: email address and verification status\n * - `address`: address information\n * - `phone`: phone number\n *\n * @param accessToken - A valid access token\n * @returns The user's profile\n *\n * @example\n * ```typescript\n * const profile = await client.getProfile(accessToken);\n * console.log(`Hello, ${profile.displayName}!`);\n * ```\n */\n async getProfile(accessToken: string): Promise<Profile> {\n return this.http.get<Profile>('/api/v1/me', {\n Authorization: `Bearer ${accessToken}`,\n });\n }\n\n /**\n * Update the authenticated user's profile.\n *\n * Requires the `profile:write` scope.\n *\n * @param accessToken - A valid access token with `profile:write` scope\n * @param data - The profile fields to update\n * @returns The updated profile\n *\n * @example\n * ```typescript\n * const updated = await client.updateProfile(accessToken, {\n * displayName: 'Max Runner',\n * height: 180,\n * });\n * ```\n */\n async updateProfile(accessToken: string, data: ProfileUpdate): Promise<Profile> {\n return this.http.patch<Profile>('/api/v1/me', data, {\n Authorization: `Bearer ${accessToken}`,\n });\n }\n\n /**\n * Get the authenticated user's OIDC userinfo.\n *\n * Requires the `openid` scope. Additional fields depend on granted scopes.\n *\n * @param accessToken - A valid access token\n * @returns The user's OIDC userinfo\n *\n * @example\n * ```typescript\n * const info = await client.getUserInfo(accessToken);\n * console.log(info.sub);\n * ```\n */\n async getUserInfo(accessToken: string): Promise<UserInfo> {\n return this.http.get<UserInfo>('/userinfo', {\n Authorization: `Bearer ${accessToken}`,\n });\n }\n\n // ===========================================================================\n // UTILITY METHODS\n // ===========================================================================\n\n /**\n * Get the configured issuer URL.\n */\n get issuer(): string {\n return this.config.issuer;\n }\n\n /**\n * Get the configured client ID.\n */\n get clientId(): string {\n return this.config.clientId;\n }\n\n /**\n * Build an authorization URL for the OAuth flow.\n *\n * @param options - Authorization options\n * @returns The authorization URL to redirect the user to\n *\n * @example\n * ```typescript\n * const authUrl = client.buildAuthorizationUrl({\n * redirectUri: 'https://myapp.com/callback',\n * scope: 'openid profile email',\n * state: 'random-state-string',\n * });\n * window.location.href = authUrl;\n * ```\n */\n buildAuthorizationUrl(options: {\n redirectUri: string;\n scope: string;\n state: string;\n nonce?: string;\n codeChallenge: string;\n codeChallengeMethod?: 'S256';\n prompt?: 'none' | 'login' | 'consent' | 'create';\n loginHint?: string;\n }): string {\n if (options.codeChallengeMethod && options.codeChallengeMethod !== 'S256') {\n throw new Error('code_challenge_method must be S256');\n }\n\n if (!options.state) {\n throw new Error('state is required for authorization requests');\n }\n\n if (!options.codeChallenge) {\n throw new Error('code_challenge is required for authorization requests');\n }\n\n const scopes = options.scope.split(' ').filter(Boolean);\n if (scopes.includes('openid') && !options.nonce) {\n throw new Error('nonce is required when requesting openid scope');\n }\n\n const url = new URL(`${this.config.issuer}/authorize`);\n\n url.searchParams.set('response_type', 'code');\n url.searchParams.set('client_id', this.config.clientId);\n url.searchParams.set('redirect_uri', options.redirectUri);\n url.searchParams.set('scope', options.scope);\n\n url.searchParams.set('state', options.state);\n if (options.nonce) {\n url.searchParams.set('nonce', options.nonce);\n }\n url.searchParams.set('code_challenge', options.codeChallenge);\n url.searchParams.set('code_challenge_method', options.codeChallengeMethod ?? 'S256');\n if (options.prompt) {\n url.searchParams.set('prompt', options.prompt);\n }\n if (options.loginHint) {\n url.searchParams.set('login_hint', options.loginHint);\n }\n\n return url.toString();\n }\n\n /**\n * Build a logout URL for ending the session.\n *\n * @param options - Logout options\n * @returns The logout URL to redirect the user to\n *\n * @example\n * ```typescript\n * const logoutUrl = client.buildLogoutUrl({\n * idTokenHint: idToken,\n * postLogoutRedirectUri: 'https://myapp.com',\n * });\n * window.location.href = logoutUrl;\n * ```\n */\n buildLogoutUrl(options?: {\n idTokenHint?: string;\n postLogoutRedirectUri?: string;\n state?: string;\n }): string {\n const url = new URL(`${this.config.issuer}/logout`);\n\n if (options?.idTokenHint) {\n url.searchParams.set('id_token_hint', options.idTokenHint);\n }\n if (options?.postLogoutRedirectUri) {\n url.searchParams.set('post_logout_redirect_uri', options.postLogoutRedirectUri);\n }\n if (options?.state) {\n url.searchParams.set('state', options.state);\n }\n\n return url.toString();\n }\n}\n"]}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,404 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PULSE ID SDK Types
|
|
3
|
+
*
|
|
4
|
+
* Type definitions for the PULSE ID API.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Configuration for the PULSE ID client.
|
|
8
|
+
*/
|
|
9
|
+
type PulseIdConfig = {
|
|
10
|
+
/**
|
|
11
|
+
* The PULSE ID issuer URL (e.g., 'https://id.pulserunning.at').
|
|
12
|
+
* Do not include a trailing slash.
|
|
13
|
+
*/
|
|
14
|
+
issuer: string;
|
|
15
|
+
/**
|
|
16
|
+
* OAuth client ID registered with PULSE ID.
|
|
17
|
+
*/
|
|
18
|
+
clientId: string;
|
|
19
|
+
/**
|
|
20
|
+
* OAuth client secret. Only required for server-side operations.
|
|
21
|
+
* Never expose this in client-side code.
|
|
22
|
+
*/
|
|
23
|
+
clientSecret?: string;
|
|
24
|
+
/**
|
|
25
|
+
* Custom fetch implementation. Defaults to global fetch.
|
|
26
|
+
* Useful for testing or custom HTTP handling.
|
|
27
|
+
*/
|
|
28
|
+
fetch?: typeof fetch;
|
|
29
|
+
/**
|
|
30
|
+
* Request timeout in milliseconds. Defaults to 30000 (30 seconds).
|
|
31
|
+
*/
|
|
32
|
+
timeout?: number;
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* User profile returned by PULSE ID.
|
|
36
|
+
*/
|
|
37
|
+
type Profile = {
|
|
38
|
+
/** Unique user identifier (UUID) */
|
|
39
|
+
id: string;
|
|
40
|
+
/** User's email address (requires 'email' scope) */
|
|
41
|
+
email?: string;
|
|
42
|
+
/** Whether the email has been verified */
|
|
43
|
+
emailVerified?: boolean;
|
|
44
|
+
/** User's first name */
|
|
45
|
+
firstName?: string | null;
|
|
46
|
+
/** User's last name */
|
|
47
|
+
lastName?: string | null;
|
|
48
|
+
/** Display name (how the user appears to others) */
|
|
49
|
+
displayName?: string | null;
|
|
50
|
+
/** URL to the user's avatar image */
|
|
51
|
+
avatar?: string | null;
|
|
52
|
+
/** User's birthday in ISO date format (YYYY-MM-DD) */
|
|
53
|
+
birthday?: string | null;
|
|
54
|
+
/** User's gender */
|
|
55
|
+
gender?: 'male' | 'female' | 'other' | null;
|
|
56
|
+
/** Height in centimeters */
|
|
57
|
+
height?: number | null;
|
|
58
|
+
/** Weight in kilograms */
|
|
59
|
+
weight?: number | null;
|
|
60
|
+
/** User's address (requires 'address' scope) */
|
|
61
|
+
address?: {
|
|
62
|
+
street?: string | null;
|
|
63
|
+
city?: string | null;
|
|
64
|
+
postalCode?: string | null;
|
|
65
|
+
country?: string | null;
|
|
66
|
+
};
|
|
67
|
+
/** User's phone number (requires 'phone' scope) */
|
|
68
|
+
phone?: string | null;
|
|
69
|
+
/** When the account was created */
|
|
70
|
+
createdAt: string;
|
|
71
|
+
/** When the profile was last updated */
|
|
72
|
+
updatedAt: string;
|
|
73
|
+
};
|
|
74
|
+
/**
|
|
75
|
+
* Fields that can be updated on a user profile.
|
|
76
|
+
*/
|
|
77
|
+
type ProfileUpdate = {
|
|
78
|
+
firstName?: string | null;
|
|
79
|
+
lastName?: string | null;
|
|
80
|
+
displayName?: string | null;
|
|
81
|
+
avatar?: string | null;
|
|
82
|
+
birthday?: string | null;
|
|
83
|
+
gender?: 'male' | 'female' | 'other' | null;
|
|
84
|
+
height?: number | null;
|
|
85
|
+
weight?: number | null;
|
|
86
|
+
phone?: string | null;
|
|
87
|
+
street?: string | null;
|
|
88
|
+
city?: string | null;
|
|
89
|
+
postalCode?: string | null;
|
|
90
|
+
country?: string | null;
|
|
91
|
+
};
|
|
92
|
+
/**
|
|
93
|
+
* Standard OIDC userinfo response from PULSE ID.
|
|
94
|
+
*/
|
|
95
|
+
type UserInfo = {
|
|
96
|
+
/** Subject identifier (user ID) */
|
|
97
|
+
sub: string;
|
|
98
|
+
/** User's email address (requires 'email' scope) */
|
|
99
|
+
email?: string;
|
|
100
|
+
/** Whether the email has been verified */
|
|
101
|
+
email_verified?: boolean;
|
|
102
|
+
/** Full name */
|
|
103
|
+
name?: string;
|
|
104
|
+
/** Given name */
|
|
105
|
+
given_name?: string;
|
|
106
|
+
/** Family name */
|
|
107
|
+
family_name?: string;
|
|
108
|
+
/** Preferred username */
|
|
109
|
+
preferred_username?: string;
|
|
110
|
+
/** Profile picture URL */
|
|
111
|
+
picture?: string;
|
|
112
|
+
/** Locale */
|
|
113
|
+
locale?: string;
|
|
114
|
+
/** When the userinfo was last updated (seconds since epoch) */
|
|
115
|
+
updated_at?: number;
|
|
116
|
+
};
|
|
117
|
+
/**
|
|
118
|
+
* OAuth token response from PULSE ID.
|
|
119
|
+
*/
|
|
120
|
+
type TokenResponse = {
|
|
121
|
+
/** The access token for API requests */
|
|
122
|
+
access_token: string;
|
|
123
|
+
/** Token type (always 'Bearer') */
|
|
124
|
+
token_type: 'Bearer';
|
|
125
|
+
/** Time until the access token expires (in seconds) */
|
|
126
|
+
expires_in: number;
|
|
127
|
+
/** Refresh token for obtaining new access tokens */
|
|
128
|
+
refresh_token?: string;
|
|
129
|
+
/** ID token containing user claims (JWT) */
|
|
130
|
+
id_token?: string;
|
|
131
|
+
/** Granted scopes (space-separated) */
|
|
132
|
+
scope?: string;
|
|
133
|
+
};
|
|
134
|
+
/**
|
|
135
|
+
* Token refresh options.
|
|
136
|
+
*/
|
|
137
|
+
type RefreshOptions = {
|
|
138
|
+
/** The refresh token to exchange */
|
|
139
|
+
refreshToken: string;
|
|
140
|
+
/** Optionally request a subset of the original scopes */
|
|
141
|
+
scope?: string;
|
|
142
|
+
};
|
|
143
|
+
/**
|
|
144
|
+
* Standard OAuth error response.
|
|
145
|
+
*/
|
|
146
|
+
type ApiErrorResponse = {
|
|
147
|
+
/** Error code (e.g., 'invalid_token', 'insufficient_scope') */
|
|
148
|
+
error: string;
|
|
149
|
+
/** Human-readable error description */
|
|
150
|
+
error_description: string;
|
|
151
|
+
/** Required scope (for 'insufficient_scope' errors) */
|
|
152
|
+
required_scope?: string;
|
|
153
|
+
};
|
|
154
|
+
/**
|
|
155
|
+
* Error codes returned by PULSE ID.
|
|
156
|
+
*/
|
|
157
|
+
type ErrorCode = 'invalid_request' | 'invalid_token' | 'expired_token' | 'insufficient_scope' | 'not_found' | 'server_error';
|
|
158
|
+
/**
|
|
159
|
+
* Available OAuth scopes for PULSE ID.
|
|
160
|
+
*/
|
|
161
|
+
declare const SCOPES: {
|
|
162
|
+
/** Required for OIDC. Returns the user's ID. */
|
|
163
|
+
readonly OPENID: "openid";
|
|
164
|
+
/** Read profile information (name, avatar, etc.) */
|
|
165
|
+
readonly PROFILE: "profile";
|
|
166
|
+
/** Update profile information */
|
|
167
|
+
readonly PROFILE_WRITE: "profile:write";
|
|
168
|
+
/** Read email address */
|
|
169
|
+
readonly EMAIL: "email";
|
|
170
|
+
/** Read address information */
|
|
171
|
+
readonly ADDRESS: "address";
|
|
172
|
+
/** Read phone number */
|
|
173
|
+
readonly PHONE: "phone";
|
|
174
|
+
/** Request a refresh token */
|
|
175
|
+
readonly OFFLINE_ACCESS: "offline_access";
|
|
176
|
+
/** Read activities (future) */
|
|
177
|
+
readonly ACTIVITIES: "activities";
|
|
178
|
+
/** Create/update activities (future) */
|
|
179
|
+
readonly ACTIVITIES_WRITE: "activities:write";
|
|
180
|
+
/** Manage external sync connections (future) */
|
|
181
|
+
readonly CONNECTIONS: "connections";
|
|
182
|
+
};
|
|
183
|
+
type Scope = (typeof SCOPES)[keyof typeof SCOPES];
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* HTTP Client Utilities
|
|
187
|
+
*
|
|
188
|
+
* Internal utilities for making HTTP requests with proper error handling.
|
|
189
|
+
*/
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Create a configured HTTP client for the PULSE ID API.
|
|
193
|
+
*/
|
|
194
|
+
declare function createHttpClient(config: PulseIdConfig): {
|
|
195
|
+
/**
|
|
196
|
+
* Make a GET request.
|
|
197
|
+
*/
|
|
198
|
+
get<T>(path: string, headers?: Record<string, string>): Promise<T>;
|
|
199
|
+
/**
|
|
200
|
+
* Make a POST request.
|
|
201
|
+
*/
|
|
202
|
+
post<T>(path: string, body?: unknown, headers?: Record<string, string>): Promise<T>;
|
|
203
|
+
/**
|
|
204
|
+
* Make a PATCH request.
|
|
205
|
+
*/
|
|
206
|
+
patch<T>(path: string, body?: unknown, headers?: Record<string, string>): Promise<T>;
|
|
207
|
+
/**
|
|
208
|
+
* Make a DELETE request.
|
|
209
|
+
*/
|
|
210
|
+
delete<T>(path: string, headers?: Record<string, string>): Promise<T>;
|
|
211
|
+
/**
|
|
212
|
+
* Make a form-encoded POST request (for OAuth token endpoints).
|
|
213
|
+
*/
|
|
214
|
+
postForm<T>(path: string, data: Record<string, string>, headers?: Record<string, string>): Promise<T>;
|
|
215
|
+
};
|
|
216
|
+
type HttpClient = ReturnType<typeof createHttpClient>;
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* PULSE ID Client
|
|
220
|
+
*
|
|
221
|
+
* Client-side SDK for interacting with the PULSE ID API.
|
|
222
|
+
* Use this in browser environments where you have an access token.
|
|
223
|
+
*
|
|
224
|
+
* For server-side usage with refresh token management, use `PulseIdServer` from './server'.
|
|
225
|
+
*/
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* PULSE ID API Client.
|
|
229
|
+
*
|
|
230
|
+
* @example
|
|
231
|
+
* ```typescript
|
|
232
|
+
* const client = new PulseIdClient({
|
|
233
|
+
* issuer: 'https://id.pulserunning.at',
|
|
234
|
+
* clientId: 'your-client-id',
|
|
235
|
+
* });
|
|
236
|
+
*
|
|
237
|
+
* const profile = await client.getProfile(accessToken);
|
|
238
|
+
* console.log(profile.displayName);
|
|
239
|
+
* ```
|
|
240
|
+
*/
|
|
241
|
+
declare class PulseIdClient {
|
|
242
|
+
protected readonly http: HttpClient;
|
|
243
|
+
protected readonly config: PulseIdConfig;
|
|
244
|
+
constructor(config: PulseIdConfig);
|
|
245
|
+
/**
|
|
246
|
+
* Get the authenticated user's profile.
|
|
247
|
+
*
|
|
248
|
+
* The fields returned depend on the scopes granted to the access token:
|
|
249
|
+
* - `profile`: name, avatar, birthday, etc.
|
|
250
|
+
* - `email`: email address and verification status
|
|
251
|
+
* - `address`: address information
|
|
252
|
+
* - `phone`: phone number
|
|
253
|
+
*
|
|
254
|
+
* @param accessToken - A valid access token
|
|
255
|
+
* @returns The user's profile
|
|
256
|
+
*
|
|
257
|
+
* @example
|
|
258
|
+
* ```typescript
|
|
259
|
+
* const profile = await client.getProfile(accessToken);
|
|
260
|
+
* console.log(`Hello, ${profile.displayName}!`);
|
|
261
|
+
* ```
|
|
262
|
+
*/
|
|
263
|
+
getProfile(accessToken: string): Promise<Profile>;
|
|
264
|
+
/**
|
|
265
|
+
* Update the authenticated user's profile.
|
|
266
|
+
*
|
|
267
|
+
* Requires the `profile:write` scope.
|
|
268
|
+
*
|
|
269
|
+
* @param accessToken - A valid access token with `profile:write` scope
|
|
270
|
+
* @param data - The profile fields to update
|
|
271
|
+
* @returns The updated profile
|
|
272
|
+
*
|
|
273
|
+
* @example
|
|
274
|
+
* ```typescript
|
|
275
|
+
* const updated = await client.updateProfile(accessToken, {
|
|
276
|
+
* displayName: 'Max Runner',
|
|
277
|
+
* height: 180,
|
|
278
|
+
* });
|
|
279
|
+
* ```
|
|
280
|
+
*/
|
|
281
|
+
updateProfile(accessToken: string, data: ProfileUpdate): Promise<Profile>;
|
|
282
|
+
/**
|
|
283
|
+
* Get the authenticated user's OIDC userinfo.
|
|
284
|
+
*
|
|
285
|
+
* Requires the `openid` scope. Additional fields depend on granted scopes.
|
|
286
|
+
*
|
|
287
|
+
* @param accessToken - A valid access token
|
|
288
|
+
* @returns The user's OIDC userinfo
|
|
289
|
+
*
|
|
290
|
+
* @example
|
|
291
|
+
* ```typescript
|
|
292
|
+
* const info = await client.getUserInfo(accessToken);
|
|
293
|
+
* console.log(info.sub);
|
|
294
|
+
* ```
|
|
295
|
+
*/
|
|
296
|
+
getUserInfo(accessToken: string): Promise<UserInfo>;
|
|
297
|
+
/**
|
|
298
|
+
* Get the configured issuer URL.
|
|
299
|
+
*/
|
|
300
|
+
get issuer(): string;
|
|
301
|
+
/**
|
|
302
|
+
* Get the configured client ID.
|
|
303
|
+
*/
|
|
304
|
+
get clientId(): string;
|
|
305
|
+
/**
|
|
306
|
+
* Build an authorization URL for the OAuth flow.
|
|
307
|
+
*
|
|
308
|
+
* @param options - Authorization options
|
|
309
|
+
* @returns The authorization URL to redirect the user to
|
|
310
|
+
*
|
|
311
|
+
* @example
|
|
312
|
+
* ```typescript
|
|
313
|
+
* const authUrl = client.buildAuthorizationUrl({
|
|
314
|
+
* redirectUri: 'https://myapp.com/callback',
|
|
315
|
+
* scope: 'openid profile email',
|
|
316
|
+
* state: 'random-state-string',
|
|
317
|
+
* });
|
|
318
|
+
* window.location.href = authUrl;
|
|
319
|
+
* ```
|
|
320
|
+
*/
|
|
321
|
+
buildAuthorizationUrl(options: {
|
|
322
|
+
redirectUri: string;
|
|
323
|
+
scope: string;
|
|
324
|
+
state: string;
|
|
325
|
+
nonce?: string;
|
|
326
|
+
codeChallenge: string;
|
|
327
|
+
codeChallengeMethod?: 'S256';
|
|
328
|
+
prompt?: 'none' | 'login' | 'consent' | 'create';
|
|
329
|
+
loginHint?: string;
|
|
330
|
+
}): string;
|
|
331
|
+
/**
|
|
332
|
+
* Build a logout URL for ending the session.
|
|
333
|
+
*
|
|
334
|
+
* @param options - Logout options
|
|
335
|
+
* @returns The logout URL to redirect the user to
|
|
336
|
+
*
|
|
337
|
+
* @example
|
|
338
|
+
* ```typescript
|
|
339
|
+
* const logoutUrl = client.buildLogoutUrl({
|
|
340
|
+
* idTokenHint: idToken,
|
|
341
|
+
* postLogoutRedirectUri: 'https://myapp.com',
|
|
342
|
+
* });
|
|
343
|
+
* window.location.href = logoutUrl;
|
|
344
|
+
* ```
|
|
345
|
+
*/
|
|
346
|
+
buildLogoutUrl(options?: {
|
|
347
|
+
idTokenHint?: string;
|
|
348
|
+
postLogoutRedirectUri?: string;
|
|
349
|
+
state?: string;
|
|
350
|
+
}): string;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* PULSE ID SDK Errors
|
|
355
|
+
*
|
|
356
|
+
* Custom error classes for better error handling.
|
|
357
|
+
*/
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* Base error class for PULSE ID SDK errors.
|
|
361
|
+
*/
|
|
362
|
+
declare class PulseIdError extends Error {
|
|
363
|
+
readonly code: ErrorCode;
|
|
364
|
+
readonly statusCode: number;
|
|
365
|
+
constructor(code: ErrorCode, message: string, statusCode?: number);
|
|
366
|
+
/**
|
|
367
|
+
* Create an error from an API response.
|
|
368
|
+
*/
|
|
369
|
+
static fromResponse(response: ApiErrorResponse, statusCode: number): PulseIdError;
|
|
370
|
+
}
|
|
371
|
+
/**
|
|
372
|
+
* Error thrown when the access token is invalid or expired.
|
|
373
|
+
*/
|
|
374
|
+
declare class InvalidTokenError extends PulseIdError {
|
|
375
|
+
constructor(message?: string);
|
|
376
|
+
}
|
|
377
|
+
/**
|
|
378
|
+
* Error thrown when the access token lacks required scopes.
|
|
379
|
+
*/
|
|
380
|
+
declare class InsufficientScopeError extends PulseIdError {
|
|
381
|
+
readonly requiredScope: string | undefined;
|
|
382
|
+
constructor(message: string, requiredScope?: string);
|
|
383
|
+
}
|
|
384
|
+
/**
|
|
385
|
+
* Error thrown when a resource is not found.
|
|
386
|
+
*/
|
|
387
|
+
declare class NotFoundError extends PulseIdError {
|
|
388
|
+
constructor(message?: string);
|
|
389
|
+
}
|
|
390
|
+
/**
|
|
391
|
+
* Error thrown when a network request fails.
|
|
392
|
+
*/
|
|
393
|
+
declare class NetworkError extends PulseIdError {
|
|
394
|
+
readonly cause: Error | undefined;
|
|
395
|
+
constructor(message: string, cause?: Error);
|
|
396
|
+
}
|
|
397
|
+
/**
|
|
398
|
+
* Error thrown when a request times out.
|
|
399
|
+
*/
|
|
400
|
+
declare class TimeoutError extends PulseIdError {
|
|
401
|
+
constructor(message?: string);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
export { type ApiErrorResponse, type ErrorCode, InsufficientScopeError, InvalidTokenError, NetworkError, NotFoundError, type Profile, type ProfileUpdate, PulseIdClient, type PulseIdConfig, PulseIdError, type RefreshOptions, SCOPES, type Scope, TimeoutError, type TokenResponse, type UserInfo };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export { InsufficientScopeError, InvalidTokenError, NetworkError, NotFoundError, PulseIdClient, PulseIdError, TimeoutError } from './chunk-BRQ2T53Z.js';
|
|
2
|
+
|
|
3
|
+
// src/types.ts
|
|
4
|
+
var SCOPES = {
|
|
5
|
+
/** Required for OIDC. Returns the user's ID. */
|
|
6
|
+
OPENID: "openid",
|
|
7
|
+
/** Read profile information (name, avatar, etc.) */
|
|
8
|
+
PROFILE: "profile",
|
|
9
|
+
/** Update profile information */
|
|
10
|
+
PROFILE_WRITE: "profile:write",
|
|
11
|
+
/** Read email address */
|
|
12
|
+
EMAIL: "email",
|
|
13
|
+
/** Read address information */
|
|
14
|
+
ADDRESS: "address",
|
|
15
|
+
/** Read phone number */
|
|
16
|
+
PHONE: "phone",
|
|
17
|
+
/** Request a refresh token */
|
|
18
|
+
OFFLINE_ACCESS: "offline_access",
|
|
19
|
+
/** Read activities (future) */
|
|
20
|
+
ACTIVITIES: "activities",
|
|
21
|
+
/** Create/update activities (future) */
|
|
22
|
+
ACTIVITIES_WRITE: "activities:write",
|
|
23
|
+
/** Manage external sync connections (future) */
|
|
24
|
+
CONNECTIONS: "connections"
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export { SCOPES };
|
|
28
|
+
//# sourceMappingURL=index.js.map
|
|
29
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/types.ts"],"names":[],"mappings":";;;AAiOO,IAAM,MAAA,GAAS;AAAA;AAAA,EAEpB,MAAA,EAAQ,QAAA;AAAA;AAAA,EAGR,OAAA,EAAS,SAAA;AAAA;AAAA,EAGT,aAAA,EAAe,eAAA;AAAA;AAAA,EAGf,KAAA,EAAO,OAAA;AAAA;AAAA,EAGP,OAAA,EAAS,SAAA;AAAA;AAAA,EAGT,KAAA,EAAO,OAAA;AAAA;AAAA,EAGP,cAAA,EAAgB,gBAAA;AAAA;AAAA,EAGhB,UAAA,EAAY,YAAA;AAAA;AAAA,EAGZ,gBAAA,EAAkB,kBAAA;AAAA;AAAA,EAGlB,WAAA,EAAa;AACf","file":"index.js","sourcesContent":["/**\n * PULSE ID SDK Types\n *\n * Type definitions for the PULSE ID API.\n */\n\n// =============================================================================\n// CLIENT CONFIGURATION\n// =============================================================================\n\n/**\n * Configuration for the PULSE ID client.\n */\nexport type PulseIdConfig = {\n /**\n * The PULSE ID issuer URL (e.g., 'https://id.pulserunning.at').\n * Do not include a trailing slash.\n */\n issuer: string;\n\n /**\n * OAuth client ID registered with PULSE ID.\n */\n clientId: string;\n\n /**\n * OAuth client secret. Only required for server-side operations.\n * Never expose this in client-side code.\n */\n clientSecret?: string;\n\n /**\n * Custom fetch implementation. Defaults to global fetch.\n * Useful for testing or custom HTTP handling.\n */\n fetch?: typeof fetch;\n\n /**\n * Request timeout in milliseconds. Defaults to 30000 (30 seconds).\n */\n timeout?: number;\n};\n\n// =============================================================================\n// USER PROFILE\n// =============================================================================\n\n/**\n * User profile returned by PULSE ID.\n */\nexport type Profile = {\n /** Unique user identifier (UUID) */\n id: string;\n\n /** User's email address (requires 'email' scope) */\n email?: string;\n\n /** Whether the email has been verified */\n emailVerified?: boolean;\n\n /** User's first name */\n firstName?: string | null;\n\n /** User's last name */\n lastName?: string | null;\n\n /** Display name (how the user appears to others) */\n displayName?: string | null;\n\n /** URL to the user's avatar image */\n avatar?: string | null;\n\n /** User's birthday in ISO date format (YYYY-MM-DD) */\n birthday?: string | null;\n\n /** User's gender */\n gender?: 'male' | 'female' | 'other' | null;\n\n /** Height in centimeters */\n height?: number | null;\n\n /** Weight in kilograms */\n weight?: number | null;\n\n /** User's address (requires 'address' scope) */\n address?: {\n street?: string | null;\n city?: string | null;\n postalCode?: string | null;\n country?: string | null;\n };\n\n /** User's phone number (requires 'phone' scope) */\n phone?: string | null;\n\n /** When the account was created */\n createdAt: string;\n\n /** When the profile was last updated */\n updatedAt: string;\n};\n\n/**\n * Fields that can be updated on a user profile.\n */\nexport type ProfileUpdate = {\n firstName?: string | null;\n lastName?: string | null;\n displayName?: string | null;\n avatar?: string | null;\n birthday?: string | null;\n gender?: 'male' | 'female' | 'other' | null;\n height?: number | null;\n weight?: number | null;\n phone?: string | null;\n street?: string | null;\n city?: string | null;\n postalCode?: string | null;\n country?: string | null;\n};\n\n// =============================================================================\n// OIDC USERINFO\n// =============================================================================\n\n/**\n * Standard OIDC userinfo response from PULSE ID.\n */\nexport type UserInfo = {\n /** Subject identifier (user ID) */\n sub: string;\n /** User's email address (requires 'email' scope) */\n email?: string;\n /** Whether the email has been verified */\n email_verified?: boolean;\n /** Full name */\n name?: string;\n /** Given name */\n given_name?: string;\n /** Family name */\n family_name?: string;\n /** Preferred username */\n preferred_username?: string;\n /** Profile picture URL */\n picture?: string;\n /** Locale */\n locale?: string;\n /** When the userinfo was last updated (seconds since epoch) */\n updated_at?: number;\n};\n\n// =============================================================================\n// OAUTH / TOKENS\n// =============================================================================\n\n/**\n * OAuth token response from PULSE ID.\n */\nexport type TokenResponse = {\n /** The access token for API requests */\n access_token: string;\n\n /** Token type (always 'Bearer') */\n token_type: 'Bearer';\n\n /** Time until the access token expires (in seconds) */\n expires_in: number;\n\n /** Refresh token for obtaining new access tokens */\n refresh_token?: string;\n\n /** ID token containing user claims (JWT) */\n id_token?: string;\n\n /** Granted scopes (space-separated) */\n scope?: string;\n};\n\n/**\n * Token refresh options.\n */\nexport type RefreshOptions = {\n /** The refresh token to exchange */\n refreshToken: string;\n\n /** Optionally request a subset of the original scopes */\n scope?: string;\n};\n\n// =============================================================================\n// API ERRORS\n// =============================================================================\n\n/**\n * Standard OAuth error response.\n */\nexport type ApiErrorResponse = {\n /** Error code (e.g., 'invalid_token', 'insufficient_scope') */\n error: string;\n\n /** Human-readable error description */\n error_description: string;\n\n /** Required scope (for 'insufficient_scope' errors) */\n required_scope?: string;\n};\n\n/**\n * Error codes returned by PULSE ID.\n */\nexport type ErrorCode =\n | 'invalid_request'\n | 'invalid_token'\n | 'expired_token'\n | 'insufficient_scope'\n | 'not_found'\n | 'server_error';\n\n// =============================================================================\n// SCOPES\n// =============================================================================\n\n/**\n * Available OAuth scopes for PULSE ID.\n */\nexport const SCOPES = {\n /** Required for OIDC. Returns the user's ID. */\n OPENID: 'openid',\n\n /** Read profile information (name, avatar, etc.) */\n PROFILE: 'profile',\n\n /** Update profile information */\n PROFILE_WRITE: 'profile:write',\n\n /** Read email address */\n EMAIL: 'email',\n\n /** Read address information */\n ADDRESS: 'address',\n\n /** Read phone number */\n PHONE: 'phone',\n\n /** Request a refresh token */\n OFFLINE_ACCESS: 'offline_access',\n\n /** Read activities (future) */\n ACTIVITIES: 'activities',\n\n /** Create/update activities (future) */\n ACTIVITIES_WRITE: 'activities:write',\n\n /** Manage external sync connections (future) */\n CONNECTIONS: 'connections',\n} as const;\n\nexport type Scope = (typeof SCOPES)[keyof typeof SCOPES];\n"]}
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { Profile, ProfileUpdate, PulseIdClient, PulseIdConfig, UserInfo, TokenResponse, RefreshOptions } from './index.js';
|
|
2
|
+
export { InsufficientScopeError, InvalidTokenError, PulseIdError } from './index.js';
|
|
3
|
+
|
|
4
|
+
type ServerConfig = PulseIdConfig & {
|
|
5
|
+
clientSecret: string;
|
|
6
|
+
storage?: TokenStorage;
|
|
7
|
+
};
|
|
8
|
+
interface TokenStorage {
|
|
9
|
+
getTokens(userId: string): Promise<StoredTokens | null>;
|
|
10
|
+
setTokens(userId: string, tokens: StoredTokens): Promise<void>;
|
|
11
|
+
deleteTokens(userId: string): Promise<void>;
|
|
12
|
+
}
|
|
13
|
+
type StoredTokens = {
|
|
14
|
+
accessToken: string;
|
|
15
|
+
refreshToken: string;
|
|
16
|
+
expiresAt: Date;
|
|
17
|
+
scope?: string;
|
|
18
|
+
};
|
|
19
|
+
interface ProfileResource {
|
|
20
|
+
get(): Promise<Profile>;
|
|
21
|
+
update(data: ProfileUpdate): Promise<Profile>;
|
|
22
|
+
}
|
|
23
|
+
interface UserInfoResource {
|
|
24
|
+
get(): Promise<UserInfo>;
|
|
25
|
+
}
|
|
26
|
+
interface UserClient {
|
|
27
|
+
profile: ProfileResource;
|
|
28
|
+
userInfo: UserInfoResource;
|
|
29
|
+
}
|
|
30
|
+
declare class PulseIdServer extends PulseIdClient {
|
|
31
|
+
private readonly clientSecret;
|
|
32
|
+
private readonly storage;
|
|
33
|
+
constructor(config: ServerConfig);
|
|
34
|
+
forUser(userId: string): UserClient;
|
|
35
|
+
exchangeCode(code: string, redirectUri: string, codeVerifier?: string): Promise<TokenResponse>;
|
|
36
|
+
refreshTokens(options: RefreshOptions): Promise<TokenResponse>;
|
|
37
|
+
revokeToken(token: string, tokenTypeHint?: 'access_token' | 'refresh_token'): Promise<void>;
|
|
38
|
+
getProfileWithRefresh(storage: TokenStorage, userId: string): Promise<Profile>;
|
|
39
|
+
updateProfileWithRefresh(storage: TokenStorage, userId: string, data: ProfileUpdate): Promise<Profile>;
|
|
40
|
+
getUserInfoWithRefresh(storage: TokenStorage, userId: string): Promise<UserInfo>;
|
|
41
|
+
private ensureValidTokens;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export { Profile, type ProfileResource, ProfileUpdate, PulseIdConfig, PulseIdServer, type ServerConfig, type StoredTokens, TokenResponse, type TokenStorage, type UserClient, UserInfo, type UserInfoResource };
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { PulseIdClient, PulseIdError } from './chunk-BRQ2T53Z.js';
|
|
2
|
+
export { InsufficientScopeError, InvalidTokenError, PulseIdError } from './chunk-BRQ2T53Z.js';
|
|
3
|
+
|
|
4
|
+
// src/server.ts
|
|
5
|
+
var PulseIdServer = class extends PulseIdClient {
|
|
6
|
+
clientSecret;
|
|
7
|
+
storage;
|
|
8
|
+
constructor(config) {
|
|
9
|
+
if (!config.clientSecret) {
|
|
10
|
+
throw new Error("clientSecret is required for server-side operations");
|
|
11
|
+
}
|
|
12
|
+
super(config);
|
|
13
|
+
this.clientSecret = config.clientSecret;
|
|
14
|
+
this.storage = config.storage;
|
|
15
|
+
}
|
|
16
|
+
forUser(userId) {
|
|
17
|
+
if (!this.storage) {
|
|
18
|
+
throw new Error("storage must be configured to use forUser()");
|
|
19
|
+
}
|
|
20
|
+
const storage = this.storage;
|
|
21
|
+
return {
|
|
22
|
+
profile: {
|
|
23
|
+
get: () => this.getProfileWithRefresh(storage, userId),
|
|
24
|
+
update: (data) => this.updateProfileWithRefresh(storage, userId, data)
|
|
25
|
+
},
|
|
26
|
+
userInfo: {
|
|
27
|
+
get: () => this.getUserInfoWithRefresh(storage, userId)
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
async exchangeCode(code, redirectUri, codeVerifier) {
|
|
32
|
+
const data = {
|
|
33
|
+
grant_type: "authorization_code",
|
|
34
|
+
code,
|
|
35
|
+
redirect_uri: redirectUri,
|
|
36
|
+
client_id: this.config.clientId,
|
|
37
|
+
client_secret: this.clientSecret
|
|
38
|
+
};
|
|
39
|
+
if (codeVerifier) {
|
|
40
|
+
data["code_verifier"] = codeVerifier;
|
|
41
|
+
}
|
|
42
|
+
return this.http.postForm("/token", data);
|
|
43
|
+
}
|
|
44
|
+
async refreshTokens(options) {
|
|
45
|
+
const data = {
|
|
46
|
+
grant_type: "refresh_token",
|
|
47
|
+
refresh_token: options.refreshToken,
|
|
48
|
+
client_id: this.config.clientId,
|
|
49
|
+
client_secret: this.clientSecret
|
|
50
|
+
};
|
|
51
|
+
if (options.scope) {
|
|
52
|
+
data["scope"] = options.scope;
|
|
53
|
+
}
|
|
54
|
+
return this.http.postForm("/token", data);
|
|
55
|
+
}
|
|
56
|
+
async revokeToken(token, tokenTypeHint) {
|
|
57
|
+
const data = {
|
|
58
|
+
token,
|
|
59
|
+
client_id: this.config.clientId,
|
|
60
|
+
client_secret: this.clientSecret
|
|
61
|
+
};
|
|
62
|
+
if (tokenTypeHint) {
|
|
63
|
+
data["token_type_hint"] = tokenTypeHint;
|
|
64
|
+
}
|
|
65
|
+
await this.http.postForm("/revoke", data);
|
|
66
|
+
}
|
|
67
|
+
async getProfileWithRefresh(storage, userId) {
|
|
68
|
+
const tokens = await this.ensureValidTokens(storage, userId);
|
|
69
|
+
return this.getProfile(tokens.accessToken);
|
|
70
|
+
}
|
|
71
|
+
async updateProfileWithRefresh(storage, userId, data) {
|
|
72
|
+
const tokens = await this.ensureValidTokens(storage, userId);
|
|
73
|
+
return this.updateProfile(tokens.accessToken, data);
|
|
74
|
+
}
|
|
75
|
+
async getUserInfoWithRefresh(storage, userId) {
|
|
76
|
+
const tokens = await this.ensureValidTokens(storage, userId);
|
|
77
|
+
return this.getUserInfo(tokens.accessToken);
|
|
78
|
+
}
|
|
79
|
+
async ensureValidTokens(storage, userId) {
|
|
80
|
+
const tokens = await storage.getTokens(userId);
|
|
81
|
+
if (!tokens) {
|
|
82
|
+
throw new PulseIdError("invalid_token", "No tokens found. User needs to re-authenticate.", 401);
|
|
83
|
+
}
|
|
84
|
+
const bufferMs = 5 * 60 * 1e3;
|
|
85
|
+
const isExpired = tokens.expiresAt.getTime() - bufferMs < Date.now();
|
|
86
|
+
if (!isExpired) {
|
|
87
|
+
return tokens;
|
|
88
|
+
}
|
|
89
|
+
try {
|
|
90
|
+
const newTokens = await this.refreshTokens({ refreshToken: tokens.refreshToken });
|
|
91
|
+
const updatedTokens = {
|
|
92
|
+
accessToken: newTokens.access_token,
|
|
93
|
+
refreshToken: newTokens.refresh_token ?? tokens.refreshToken,
|
|
94
|
+
expiresAt: new Date(Date.now() + newTokens.expires_in * 1e3),
|
|
95
|
+
...newTokens.scope ?? tokens.scope ? { scope: newTokens.scope ?? tokens.scope } : {}
|
|
96
|
+
};
|
|
97
|
+
await storage.setTokens(userId, updatedTokens);
|
|
98
|
+
return updatedTokens;
|
|
99
|
+
} catch {
|
|
100
|
+
await storage.deleteTokens(userId);
|
|
101
|
+
throw new PulseIdError("invalid_token", "Token refresh failed. User needs to re-authenticate.", 401);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
export { PulseIdServer };
|
|
107
|
+
//# sourceMappingURL=server.js.map
|
|
108
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/server.ts"],"names":[],"mappings":"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","file":"server.js","sourcesContent":["import { PulseIdClient } from './client.js';\nimport { PulseIdError } from './errors.js';\nimport type {\n Profile,\n ProfileUpdate,\n PulseIdConfig,\n RefreshOptions,\n TokenResponse,\n UserInfo,\n} from './types.js';\n\nexport type ServerConfig = PulseIdConfig & {\n clientSecret: string;\n storage?: TokenStorage;\n};\n\nexport interface TokenStorage {\n getTokens(userId: string): Promise<StoredTokens | null>;\n setTokens(userId: string, tokens: StoredTokens): Promise<void>;\n deleteTokens(userId: string): Promise<void>;\n}\n\nexport type StoredTokens = {\n accessToken: string;\n refreshToken: string;\n expiresAt: Date;\n scope?: string;\n};\n\nexport interface ProfileResource {\n get(): Promise<Profile>;\n update(data: ProfileUpdate): Promise<Profile>;\n}\n\nexport interface UserInfoResource {\n get(): Promise<UserInfo>;\n}\n\nexport interface UserClient {\n profile: ProfileResource;\n userInfo: UserInfoResource;\n}\n\nexport class PulseIdServer extends PulseIdClient {\n private readonly clientSecret: string;\n private readonly storage: TokenStorage | undefined;\n\n constructor(config: ServerConfig) {\n if (!config.clientSecret) {\n throw new Error('clientSecret is required for server-side operations');\n }\n super(config);\n this.clientSecret = config.clientSecret;\n this.storage = config.storage;\n }\n\n forUser(userId: string): UserClient {\n if (!this.storage) {\n throw new Error('storage must be configured to use forUser()');\n }\n const storage = this.storage;\n\n return {\n profile: {\n get: () => this.getProfileWithRefresh(storage, userId),\n update: (data: ProfileUpdate) => this.updateProfileWithRefresh(storage, userId, data),\n },\n userInfo: {\n get: () => this.getUserInfoWithRefresh(storage, userId),\n },\n };\n }\n\n async exchangeCode(code: string, redirectUri: string, codeVerifier?: string): Promise<TokenResponse> {\n const data: Record<string, string> = {\n grant_type: 'authorization_code',\n code,\n redirect_uri: redirectUri,\n client_id: this.config.clientId,\n client_secret: this.clientSecret,\n };\n if (codeVerifier) {\n data['code_verifier'] = codeVerifier;\n }\n return this.http.postForm<TokenResponse>('/token', data);\n }\n\n async refreshTokens(options: RefreshOptions): Promise<TokenResponse> {\n const data: Record<string, string> = {\n grant_type: 'refresh_token',\n refresh_token: options.refreshToken,\n client_id: this.config.clientId,\n client_secret: this.clientSecret,\n };\n if (options.scope) {\n data['scope'] = options.scope;\n }\n return this.http.postForm<TokenResponse>('/token', data);\n }\n\n async revokeToken(token: string, tokenTypeHint?: 'access_token' | 'refresh_token'): Promise<void> {\n const data: Record<string, string> = {\n token,\n client_id: this.config.clientId,\n client_secret: this.clientSecret,\n };\n if (tokenTypeHint) {\n data['token_type_hint'] = tokenTypeHint;\n }\n await this.http.postForm<void>('/revoke', data);\n }\n\n async getProfileWithRefresh(storage: TokenStorage, userId: string): Promise<Profile> {\n const tokens = await this.ensureValidTokens(storage, userId);\n return this.getProfile(tokens.accessToken);\n }\n\n async updateProfileWithRefresh(storage: TokenStorage, userId: string, data: ProfileUpdate): Promise<Profile> {\n const tokens = await this.ensureValidTokens(storage, userId);\n return this.updateProfile(tokens.accessToken, data);\n }\n\n async getUserInfoWithRefresh(storage: TokenStorage, userId: string): Promise<UserInfo> {\n const tokens = await this.ensureValidTokens(storage, userId);\n return this.getUserInfo(tokens.accessToken);\n }\n\n private async ensureValidTokens(storage: TokenStorage, userId: string): Promise<StoredTokens> {\n const tokens = await storage.getTokens(userId);\n if (!tokens) {\n throw new PulseIdError('invalid_token', 'No tokens found. User needs to re-authenticate.', 401);\n }\n\n const bufferMs = 5 * 60 * 1000;\n const isExpired = tokens.expiresAt.getTime() - bufferMs < Date.now();\n if (!isExpired) {\n return tokens;\n }\n\n try {\n const newTokens = await this.refreshTokens({ refreshToken: tokens.refreshToken });\n const updatedTokens: StoredTokens = {\n accessToken: newTokens.access_token,\n refreshToken: newTokens.refresh_token ?? tokens.refreshToken,\n expiresAt: new Date(Date.now() + newTokens.expires_in * 1000),\n ...(newTokens.scope ?? tokens.scope ? { scope: newTokens.scope ?? tokens.scope } : {}),\n };\n await storage.setTokens(userId, updatedTokens);\n return updatedTokens;\n } catch {\n await storage.deleteTokens(userId);\n throw new PulseIdError('invalid_token', 'Token refresh failed. User needs to re-authenticate.', 401);\n }\n }\n}\n\nexport type { TokenResponse, Profile, ProfileUpdate, PulseIdConfig, UserInfo } from './types.js';\nexport { PulseIdError, InvalidTokenError, InsufficientScopeError } from './errors.js';\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@pulseid/client",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "TypeScript SDK for PULSE ID - Your athlete identity",
|
|
5
|
+
"author": "Patrick Hübl-Neschkudla <patrick@pulserunning.at>",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"main": "./dist/index.js",
|
|
9
|
+
"module": "./dist/index.js",
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"import": "./dist/index.js",
|
|
14
|
+
"types": "./dist/index.d.ts"
|
|
15
|
+
},
|
|
16
|
+
"./server": {
|
|
17
|
+
"import": "./dist/server.js",
|
|
18
|
+
"types": "./dist/server.d.ts"
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"dist",
|
|
23
|
+
"README.md"
|
|
24
|
+
],
|
|
25
|
+
"engines": {
|
|
26
|
+
"node": ">=22.0.0"
|
|
27
|
+
},
|
|
28
|
+
"scripts": {
|
|
29
|
+
"build": "tsup",
|
|
30
|
+
"dev": "tsup --watch",
|
|
31
|
+
"test": "vitest run",
|
|
32
|
+
"test:watch": "vitest",
|
|
33
|
+
"test:coverage": "vitest run --coverage",
|
|
34
|
+
"lint": "eslint src --ext .ts",
|
|
35
|
+
"typecheck": "tsc --noEmit",
|
|
36
|
+
"clean": "rm -rf dist",
|
|
37
|
+
"changeset": "changeset",
|
|
38
|
+
"version": "changeset version",
|
|
39
|
+
"release": "pnpm build && changeset publish",
|
|
40
|
+
"prepublishOnly": "npm run build",
|
|
41
|
+
"docs:dev": "cd docs && pnpm dev",
|
|
42
|
+
"docs:build": "cd docs && pnpm build"
|
|
43
|
+
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"@changesets/changelog-github": "0.5.2",
|
|
46
|
+
"@changesets/cli": "2.29.8",
|
|
47
|
+
"@types/node": "22.15.3",
|
|
48
|
+
"@vitest/coverage-v8": "4.0.18",
|
|
49
|
+
"eslint": "9.39.2",
|
|
50
|
+
"tsup": "8.5.1",
|
|
51
|
+
"typescript": "5.8.3",
|
|
52
|
+
"vitest": "4.0.18"
|
|
53
|
+
},
|
|
54
|
+
"keywords": [
|
|
55
|
+
"pulse",
|
|
56
|
+
"pulseid",
|
|
57
|
+
"oauth",
|
|
58
|
+
"oidc",
|
|
59
|
+
"authentication",
|
|
60
|
+
"identity",
|
|
61
|
+
"sports",
|
|
62
|
+
"athlete"
|
|
63
|
+
],
|
|
64
|
+
"repository": {
|
|
65
|
+
"type": "git",
|
|
66
|
+
"url": "git+https://github.com/flipace/pulse-id-sdk-ts.git"
|
|
67
|
+
},
|
|
68
|
+
"bugs": {
|
|
69
|
+
"url": "https://github.com/flipace/pulse-id-sdk-ts/issues"
|
|
70
|
+
},
|
|
71
|
+
"homepage": "https://github.com/flipace/pulse-id-sdk-ts#readme",
|
|
72
|
+
"directories": {
|
|
73
|
+
"doc": "docs"
|
|
74
|
+
}
|
|
75
|
+
}
|