@reauth-dev/sdk 0.1.0 → 0.3.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.
@@ -1,11 +1,15 @@
1
1
  import {
2
2
  createReauthClient
3
- } from "../chunk-JX2J36FS.mjs";
3
+ } from "../chunk-5LFJ5PXQ.mjs";
4
+ import "../chunk-EY5LQCDG.mjs";
4
5
 
5
6
  // src/react/useAuth.ts
6
- import { useState, useEffect, useCallback, useMemo } from "react";
7
+ import { useState, useEffect, useCallback, useMemo, useRef } from "react";
8
+ var DEFAULT_REFRESH_INTERVAL_MS = 5 * 60 * 1e3;
7
9
  function useAuth(config) {
8
- const client = useMemo(() => createReauthClient(config), [config.domain]);
10
+ const { refreshInterval = DEFAULT_REFRESH_INTERVAL_MS, ...clientConfig } = config;
11
+ const client = useMemo(() => createReauthClient(clientConfig), [clientConfig.domain]);
12
+ const isRefreshing = useRef(false);
9
13
  const [state, setState] = useState({
10
14
  user: null,
11
15
  loading: true,
@@ -14,12 +18,15 @@ function useAuth(config) {
14
18
  waitlistPosition: null
15
19
  });
16
20
  const checkSession = useCallback(async () => {
21
+ if (isRefreshing.current) return;
22
+ isRefreshing.current = true;
17
23
  try {
18
24
  let session = await client.getSession();
19
25
  if (!session.valid && !session.error_code && !session.end_user_id) {
20
- const refreshed = await client.refresh();
21
- if (refreshed) {
26
+ try {
27
+ await client.refresh();
22
28
  session = await client.getSession();
29
+ } catch {
23
30
  }
24
31
  }
25
32
  if (session.error_code === "ACCOUNT_SUSPENDED") {
@@ -37,7 +44,9 @@ function useAuth(config) {
37
44
  user: {
38
45
  id: session.end_user_id,
39
46
  email: session.email,
40
- roles: session.roles || []
47
+ roles: session.roles || [],
48
+ activeOrgId: session.active_org_id || "",
49
+ orgRole: session.org_role || ""
41
50
  },
42
51
  loading: false,
43
52
  error: null,
@@ -51,7 +60,9 @@ function useAuth(config) {
51
60
  user: {
52
61
  id: session.end_user_id,
53
62
  email: session.email,
54
- roles: session.roles || []
63
+ roles: session.roles || [],
64
+ activeOrgId: session.active_org_id || "",
65
+ orgRole: session.org_role || ""
55
66
  },
56
67
  loading: false,
57
68
  error: null,
@@ -75,11 +86,20 @@ function useAuth(config) {
75
86
  isOnWaitlist: false,
76
87
  waitlistPosition: null
77
88
  });
89
+ } finally {
90
+ isRefreshing.current = false;
78
91
  }
79
92
  }, [client]);
80
93
  useEffect(() => {
81
94
  checkSession();
82
95
  }, [checkSession]);
96
+ useEffect(() => {
97
+ if (refreshInterval <= 0) return;
98
+ const intervalId = setInterval(() => {
99
+ checkSession();
100
+ }, refreshInterval);
101
+ return () => clearInterval(intervalId);
102
+ }, [checkSession, refreshInterval]);
83
103
  const logout = useCallback(async () => {
84
104
  await client.logout();
85
105
  setState({
@@ -98,6 +118,131 @@ function useAuth(config) {
98
118
  };
99
119
  }
100
120
 
121
+ // src/react/useHeadlessAuth.ts
122
+ import { useState as useState2, useCallback as useCallback2, useMemo as useMemo2 } from "react";
123
+ function useHeadlessAuth(options) {
124
+ const client = useMemo2(
125
+ () => createReauthClient({ domain: options.domain, timeout: options.timeout }),
126
+ [options.domain, options.timeout]
127
+ );
128
+ const [state, setState] = useState2({
129
+ loading: false,
130
+ error: null,
131
+ step: "idle",
132
+ config: null,
133
+ verifyResult: null
134
+ });
135
+ const getConfig = useCallback2(async () => {
136
+ setState((s) => ({ ...s, loading: true, error: null }));
137
+ try {
138
+ const config = await client.getConfig();
139
+ setState((s) => ({ ...s, loading: false, config }));
140
+ return config;
141
+ } catch (err) {
142
+ const message = err instanceof Error ? err.message : "Failed to get config";
143
+ setState((s) => ({ ...s, loading: false, error: message }));
144
+ throw err;
145
+ }
146
+ }, [client]);
147
+ const requestMagicLink = useCallback2(
148
+ async (email) => {
149
+ setState((s) => ({ ...s, loading: true, error: null }));
150
+ try {
151
+ await client.requestMagicLink({
152
+ email,
153
+ callbackUrl: options.callbackUrl
154
+ });
155
+ setState((s) => ({
156
+ ...s,
157
+ loading: false,
158
+ step: "magic_link_sent"
159
+ }));
160
+ } catch (err) {
161
+ const message = err instanceof Error ? err.message : "Failed to send magic link";
162
+ setState((s) => ({ ...s, loading: false, error: message }));
163
+ throw err;
164
+ }
165
+ },
166
+ [client, options.callbackUrl]
167
+ );
168
+ const verifyMagicLink = useCallback2(
169
+ async (token) => {
170
+ setState((s) => ({ ...s, loading: true, error: null }));
171
+ try {
172
+ const result = await client.verifyMagicLink({ token });
173
+ setState((s) => ({
174
+ ...s,
175
+ loading: false,
176
+ step: "completed",
177
+ verifyResult: result
178
+ }));
179
+ return result;
180
+ } catch (err) {
181
+ const message = err instanceof Error ? err.message : "Failed to verify magic link";
182
+ setState((s) => ({ ...s, loading: false, error: message }));
183
+ throw err;
184
+ }
185
+ },
186
+ [client]
187
+ );
188
+ const startGoogleOAuth = useCallback2(async () => {
189
+ setState((s) => ({ ...s, loading: true, error: null }));
190
+ try {
191
+ const result = await client.startGoogleOAuth();
192
+ setState((s) => ({
193
+ ...s,
194
+ loading: false,
195
+ step: "google_started"
196
+ }));
197
+ if (typeof window !== "undefined") {
198
+ window.location.href = result.authUrl;
199
+ }
200
+ return result;
201
+ } catch (err) {
202
+ const message = err instanceof Error ? err.message : "Failed to start Google OAuth";
203
+ setState((s) => ({ ...s, loading: false, error: message }));
204
+ throw err;
205
+ }
206
+ }, [client]);
207
+ const startTwitterOAuth = useCallback2(async () => {
208
+ setState((s) => ({ ...s, loading: true, error: null }));
209
+ try {
210
+ const result = await client.startTwitterOAuth();
211
+ setState((s) => ({
212
+ ...s,
213
+ loading: false,
214
+ step: "twitter_started"
215
+ }));
216
+ if (typeof window !== "undefined") {
217
+ window.location.href = result.authUrl;
218
+ }
219
+ return result;
220
+ } catch (err) {
221
+ const message = err instanceof Error ? err.message : "Failed to start Twitter OAuth";
222
+ setState((s) => ({ ...s, loading: false, error: message }));
223
+ throw err;
224
+ }
225
+ }, [client]);
226
+ const reset = useCallback2(() => {
227
+ setState({
228
+ loading: false,
229
+ error: null,
230
+ step: "idle",
231
+ config: null,
232
+ verifyResult: null
233
+ });
234
+ }, []);
235
+ return {
236
+ ...state,
237
+ getConfig,
238
+ requestMagicLink,
239
+ verifyMagicLink,
240
+ startGoogleOAuth,
241
+ startTwitterOAuth,
242
+ reset
243
+ };
244
+ }
245
+
101
246
  // src/react/AuthProvider.tsx
102
247
  import { createContext, useContext } from "react";
103
248
  import { jsx } from "react/jsx-runtime";
@@ -115,7 +260,7 @@ function useAuthContext() {
115
260
  }
116
261
 
117
262
  // src/react/ProtectedRoute.tsx
118
- import { useEffect as useEffect2 } from "react";
263
+ import { useEffect as useEffect2, useRef as useRef2 } from "react";
119
264
  import { Fragment, jsx as jsx2 } from "react/jsx-runtime";
120
265
  function ProtectedRoute({
121
266
  children,
@@ -124,8 +269,10 @@ function ProtectedRoute({
124
269
  onWaitlist
125
270
  }) {
126
271
  const { user, loading, isOnWaitlist, login } = useAuthContext();
272
+ const hasRedirected = useRef2(false);
127
273
  useEffect2(() => {
128
- if (!loading && !user) {
274
+ if (!loading && !user && !hasRedirected.current) {
275
+ hasRedirected.current = true;
129
276
  if (onUnauthenticated) {
130
277
  onUnauthenticated();
131
278
  } else {
@@ -134,7 +281,8 @@ function ProtectedRoute({
134
281
  }
135
282
  }, [loading, user, login, onUnauthenticated]);
136
283
  useEffect2(() => {
137
- if (!loading && isOnWaitlist && onWaitlist) {
284
+ if (!loading && isOnWaitlist && onWaitlist && !hasRedirected.current) {
285
+ hasRedirected.current = true;
138
286
  onWaitlist();
139
287
  }
140
288
  }, [loading, isOnWaitlist, onWaitlist]);
@@ -150,5 +298,6 @@ export {
150
298
  AuthProvider,
151
299
  ProtectedRoute,
152
300
  useAuth,
153
- useAuthContext
301
+ useAuthContext,
302
+ useHeadlessAuth
154
303
  };
package/dist/server.d.mts CHANGED
@@ -1,5 +1,23 @@
1
- import { e as ReauthServerConfig, A as AuthResult, f as RequestLike, d as UserDetails, h as ChargeOptions, i as DepositOptions, b as TransactionsPaginationOptions, B as BalanceTransaction } from './types-D8oOYbeC.mjs';
1
+ import { q as ReauthServerConfig, r as AuthResult, s as RequestLike, p as UserDetails, x as ChargeOptions, y as DepositOptions, e as TransactionsPaginationOptions, B as BalanceTransaction } from './types-DKUKhCNE.mjs';
2
2
 
3
+ /**
4
+ * Derives a JWT signing secret from an API key using HKDF-SHA256.
5
+ * This must match the Rust backend implementation exactly.
6
+ *
7
+ * IMPORTANT: Returns hex-encoded string (64 chars), NOT raw bytes.
8
+ * The Rust API uses the ASCII bytes of the hex string as the JWT secret,
9
+ * so we must do the same for compatibility.
10
+ *
11
+ * @param apiKey - The raw API key (e.g., "sk_live_...")
12
+ * @param domainId - UUID of the domain (used as salt for domain isolation)
13
+ * @returns Promise<string> - 64-char hex string (to be used as ASCII bytes for JWT signing)
14
+ */
15
+ declare function deriveJwtSecret(apiKey: string, domainId: string): Promise<string>;
16
+ /**
17
+ * Parse cookies from a cookie header string.
18
+ * Handles URL encoding and multiple cookies properly.
19
+ */
20
+ declare function parseCookies(cookieHeader: string): Record<string, string>;
3
21
  /**
4
22
  * Create a reauth client for server-side authentication.
5
23
  * Uses local JWT verification for fast, reliable auth checks.
@@ -185,4 +203,4 @@ declare function createServerClient(config: ReauthServerConfig): {
185
203
  };
186
204
  type ServerClient = ReturnType<typeof createServerClient>;
187
205
 
188
- export { type ServerClient, createServerClient };
206
+ export { type ServerClient, createServerClient, deriveJwtSecret, parseCookies };
package/dist/server.d.ts CHANGED
@@ -1,5 +1,23 @@
1
- import { e as ReauthServerConfig, A as AuthResult, f as RequestLike, d as UserDetails, h as ChargeOptions, i as DepositOptions, b as TransactionsPaginationOptions, B as BalanceTransaction } from './types-D8oOYbeC.js';
1
+ import { q as ReauthServerConfig, r as AuthResult, s as RequestLike, p as UserDetails, x as ChargeOptions, y as DepositOptions, e as TransactionsPaginationOptions, B as BalanceTransaction } from './types-DKUKhCNE.js';
2
2
 
3
+ /**
4
+ * Derives a JWT signing secret from an API key using HKDF-SHA256.
5
+ * This must match the Rust backend implementation exactly.
6
+ *
7
+ * IMPORTANT: Returns hex-encoded string (64 chars), NOT raw bytes.
8
+ * The Rust API uses the ASCII bytes of the hex string as the JWT secret,
9
+ * so we must do the same for compatibility.
10
+ *
11
+ * @param apiKey - The raw API key (e.g., "sk_live_...")
12
+ * @param domainId - UUID of the domain (used as salt for domain isolation)
13
+ * @returns Promise<string> - 64-char hex string (to be used as ASCII bytes for JWT signing)
14
+ */
15
+ declare function deriveJwtSecret(apiKey: string, domainId: string): Promise<string>;
16
+ /**
17
+ * Parse cookies from a cookie header string.
18
+ * Handles URL encoding and multiple cookies properly.
19
+ */
20
+ declare function parseCookies(cookieHeader: string): Record<string, string>;
3
21
  /**
4
22
  * Create a reauth client for server-side authentication.
5
23
  * Uses local JWT verification for fast, reliable auth checks.
@@ -185,4 +203,4 @@ declare function createServerClient(config: ReauthServerConfig): {
185
203
  };
186
204
  type ServerClient = ReturnType<typeof createServerClient>;
187
205
 
188
- export { type ServerClient, createServerClient };
206
+ export { type ServerClient, createServerClient, deriveJwtSecret, parseCookies };
package/dist/server.js CHANGED
@@ -30,11 +30,18 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
30
30
  // src/server.ts
31
31
  var server_exports = {};
32
32
  __export(server_exports, {
33
- createServerClient: () => createServerClient
33
+ createServerClient: () => createServerClient,
34
+ deriveJwtSecret: () => deriveJwtSecret,
35
+ parseCookies: () => parseCookies
34
36
  });
35
37
  module.exports = __toCommonJS(server_exports);
36
38
  var import_crypto = require("crypto");
37
39
  var jose = __toESM(require("jose"));
40
+
41
+ // src/types.ts
42
+ var DEFAULT_TIMEOUT_MS = 1e4;
43
+
44
+ // src/server.ts
38
45
  async function deriveJwtSecret(apiKey, domainId) {
39
46
  const salt = Buffer.from(domainId.replace(/-/g, ""), "hex");
40
47
  const info = Buffer.from("reauth-jwt-v1");
@@ -45,6 +52,30 @@ async function deriveJwtSecret(apiKey, domainId) {
45
52
  });
46
53
  });
47
54
  }
55
+ var VALID_SUBSCRIPTION_STATUSES = /* @__PURE__ */ new Set([
56
+ "active",
57
+ "past_due",
58
+ "canceled",
59
+ "trialing",
60
+ "incomplete",
61
+ "incomplete_expired",
62
+ "unpaid",
63
+ "paused",
64
+ "none"
65
+ ]);
66
+ function isValidClaims(payload) {
67
+ if (typeof payload.sub !== "string") return false;
68
+ if (typeof payload.domain_id !== "string") return false;
69
+ if (typeof payload.domain !== "string") return false;
70
+ if (typeof payload.active_org_id !== "string") return false;
71
+ if (typeof payload.org_role !== "string") return false;
72
+ if (!Array.isArray(payload.roles) || !payload.roles.every((r) => typeof r === "string")) return false;
73
+ const subscription = payload.subscription;
74
+ if (typeof subscription !== "object" || subscription === null || Array.isArray(subscription)) return false;
75
+ const status = subscription.status;
76
+ if (typeof status !== "string" || !VALID_SUBSCRIPTION_STATUSES.has(status)) return false;
77
+ return true;
78
+ }
48
79
  function transformSubscription(sub) {
49
80
  return {
50
81
  status: sub.status,
@@ -71,6 +102,10 @@ function parseCookies(cookieHeader) {
71
102
  }
72
103
  function createServerClient(config) {
73
104
  const { domain, apiKey } = config;
105
+ if (config.timeout !== void 0 && (!Number.isFinite(config.timeout) || config.timeout <= 0)) {
106
+ throw new Error("timeout must be a positive finite number in milliseconds");
107
+ }
108
+ const timeoutMs = config.timeout ?? DEFAULT_TIMEOUT_MS;
74
109
  if (!apiKey) {
75
110
  throw new Error(
76
111
  "apiKey is required for createServerClient. Get one from the Reauth dashboard."
@@ -118,7 +153,11 @@ function createServerClient(config) {
118
153
  // 60 seconds clock skew tolerance
119
154
  }
120
155
  );
121
- const claims = payload;
156
+ const payloadRecord = payload;
157
+ if (!isValidClaims(payloadRecord)) {
158
+ return { valid: false, user: null, claims: null, error: "Invalid token claims: missing required fields" };
159
+ }
160
+ const claims = payloadRecord;
122
161
  if (claims.domain !== domain) {
123
162
  return { valid: false, user: null, claims: null, error: "Domain mismatch" };
124
163
  }
@@ -127,6 +166,8 @@ function createServerClient(config) {
127
166
  user: {
128
167
  id: claims.sub,
129
168
  roles: claims.roles,
169
+ activeOrgId: claims.active_org_id,
170
+ orgRole: claims.org_role,
130
171
  subscription: transformSubscription(claims.subscription)
131
172
  },
132
173
  claims
@@ -241,10 +282,11 @@ function createServerClient(config) {
241
282
  * ```
242
283
  */
243
284
  async getUserById(userId) {
244
- const res = await fetch(`${developerBaseUrl}/users/${userId}`, {
285
+ const res = await fetch(`${developerBaseUrl}/users/${encodeURIComponent(userId)}`, {
245
286
  headers: {
246
287
  Authorization: `Bearer ${apiKey}`
247
- }
288
+ },
289
+ signal: AbortSignal.timeout(timeoutMs)
248
290
  });
249
291
  if (!res.ok) {
250
292
  if (res.status === 401) {
@@ -277,16 +319,18 @@ function createServerClient(config) {
277
319
  * @returns Object with the current balance
278
320
  */
279
321
  async getBalance(userId) {
280
- const res = await fetch(`${developerBaseUrl}/users/${userId}/balance`, {
322
+ const res = await fetch(`${developerBaseUrl}/users/${encodeURIComponent(userId)}/balance`, {
281
323
  headers: {
282
324
  Authorization: `Bearer ${apiKey}`
283
- }
325
+ },
326
+ signal: AbortSignal.timeout(timeoutMs)
284
327
  });
285
328
  if (!res.ok) {
286
329
  if (res.status === 401) throw new Error("Invalid API key");
287
330
  throw new Error(`Failed to get balance: ${res.status}`);
288
331
  }
289
- return res.json();
332
+ const data = await res.json();
333
+ return { balance: data.balance };
290
334
  },
291
335
  /**
292
336
  * Charge (deduct) credits from a user's balance.
@@ -297,12 +341,13 @@ function createServerClient(config) {
297
341
  * @throws Error with status 402 if insufficient balance, 400 if invalid amount
298
342
  */
299
343
  async charge(userId, opts) {
300
- const res = await fetch(`${developerBaseUrl}/users/${userId}/charge`, {
344
+ const res = await fetch(`${developerBaseUrl}/users/${encodeURIComponent(userId)}/charge`, {
301
345
  method: "POST",
302
346
  headers: {
303
347
  "Content-Type": "application/json",
304
348
  Authorization: `Bearer ${apiKey}`
305
349
  },
350
+ signal: AbortSignal.timeout(timeoutMs),
306
351
  body: JSON.stringify({
307
352
  amount: opts.amount,
308
353
  request_uuid: opts.requestUuid,
@@ -326,12 +371,13 @@ function createServerClient(config) {
326
371
  * @returns Object with the new balance after deposit
327
372
  */
328
373
  async deposit(userId, opts) {
329
- const res = await fetch(`${developerBaseUrl}/users/${userId}/deposit`, {
374
+ const res = await fetch(`${developerBaseUrl}/users/${encodeURIComponent(userId)}/deposit`, {
330
375
  method: "POST",
331
376
  headers: {
332
377
  "Content-Type": "application/json",
333
378
  Authorization: `Bearer ${apiKey}`
334
379
  },
380
+ signal: AbortSignal.timeout(timeoutMs),
335
381
  body: JSON.stringify({
336
382
  amount: opts.amount,
337
383
  request_uuid: opts.requestUuid,
@@ -359,11 +405,12 @@ function createServerClient(config) {
359
405
  if (opts?.offset !== void 0) params.set("offset", String(opts.offset));
360
406
  const qs = params.toString();
361
407
  const res = await fetch(
362
- `${developerBaseUrl}/users/${userId}/balance/transactions${qs ? `?${qs}` : ""}`,
408
+ `${developerBaseUrl}/users/${encodeURIComponent(userId)}/balance/transactions${qs ? `?${qs}` : ""}`,
363
409
  {
364
410
  headers: {
365
411
  Authorization: `Bearer ${apiKey}`
366
- }
412
+ },
413
+ signal: AbortSignal.timeout(timeoutMs)
367
414
  }
368
415
  );
369
416
  if (!res.ok) {
@@ -387,5 +434,7 @@ function createServerClient(config) {
387
434
  }
388
435
  // Annotate the CommonJS export names for ESM import in node:
389
436
  0 && (module.exports = {
390
- createServerClient
437
+ createServerClient,
438
+ deriveJwtSecret,
439
+ parseCookies
391
440
  });
package/dist/server.mjs CHANGED
@@ -1,3 +1,7 @@
1
+ import {
2
+ DEFAULT_TIMEOUT_MS
3
+ } from "./chunk-EY5LQCDG.mjs";
4
+
1
5
  // src/server.ts
2
6
  import { hkdf } from "crypto";
3
7
  import * as jose from "jose";
@@ -11,6 +15,30 @@ async function deriveJwtSecret(apiKey, domainId) {
11
15
  });
12
16
  });
13
17
  }
18
+ var VALID_SUBSCRIPTION_STATUSES = /* @__PURE__ */ new Set([
19
+ "active",
20
+ "past_due",
21
+ "canceled",
22
+ "trialing",
23
+ "incomplete",
24
+ "incomplete_expired",
25
+ "unpaid",
26
+ "paused",
27
+ "none"
28
+ ]);
29
+ function isValidClaims(payload) {
30
+ if (typeof payload.sub !== "string") return false;
31
+ if (typeof payload.domain_id !== "string") return false;
32
+ if (typeof payload.domain !== "string") return false;
33
+ if (typeof payload.active_org_id !== "string") return false;
34
+ if (typeof payload.org_role !== "string") return false;
35
+ if (!Array.isArray(payload.roles) || !payload.roles.every((r) => typeof r === "string")) return false;
36
+ const subscription = payload.subscription;
37
+ if (typeof subscription !== "object" || subscription === null || Array.isArray(subscription)) return false;
38
+ const status = subscription.status;
39
+ if (typeof status !== "string" || !VALID_SUBSCRIPTION_STATUSES.has(status)) return false;
40
+ return true;
41
+ }
14
42
  function transformSubscription(sub) {
15
43
  return {
16
44
  status: sub.status,
@@ -37,6 +65,10 @@ function parseCookies(cookieHeader) {
37
65
  }
38
66
  function createServerClient(config) {
39
67
  const { domain, apiKey } = config;
68
+ if (config.timeout !== void 0 && (!Number.isFinite(config.timeout) || config.timeout <= 0)) {
69
+ throw new Error("timeout must be a positive finite number in milliseconds");
70
+ }
71
+ const timeoutMs = config.timeout ?? DEFAULT_TIMEOUT_MS;
40
72
  if (!apiKey) {
41
73
  throw new Error(
42
74
  "apiKey is required for createServerClient. Get one from the Reauth dashboard."
@@ -84,7 +116,11 @@ function createServerClient(config) {
84
116
  // 60 seconds clock skew tolerance
85
117
  }
86
118
  );
87
- const claims = payload;
119
+ const payloadRecord = payload;
120
+ if (!isValidClaims(payloadRecord)) {
121
+ return { valid: false, user: null, claims: null, error: "Invalid token claims: missing required fields" };
122
+ }
123
+ const claims = payloadRecord;
88
124
  if (claims.domain !== domain) {
89
125
  return { valid: false, user: null, claims: null, error: "Domain mismatch" };
90
126
  }
@@ -93,6 +129,8 @@ function createServerClient(config) {
93
129
  user: {
94
130
  id: claims.sub,
95
131
  roles: claims.roles,
132
+ activeOrgId: claims.active_org_id,
133
+ orgRole: claims.org_role,
96
134
  subscription: transformSubscription(claims.subscription)
97
135
  },
98
136
  claims
@@ -207,10 +245,11 @@ function createServerClient(config) {
207
245
  * ```
208
246
  */
209
247
  async getUserById(userId) {
210
- const res = await fetch(`${developerBaseUrl}/users/${userId}`, {
248
+ const res = await fetch(`${developerBaseUrl}/users/${encodeURIComponent(userId)}`, {
211
249
  headers: {
212
250
  Authorization: `Bearer ${apiKey}`
213
- }
251
+ },
252
+ signal: AbortSignal.timeout(timeoutMs)
214
253
  });
215
254
  if (!res.ok) {
216
255
  if (res.status === 401) {
@@ -243,16 +282,18 @@ function createServerClient(config) {
243
282
  * @returns Object with the current balance
244
283
  */
245
284
  async getBalance(userId) {
246
- const res = await fetch(`${developerBaseUrl}/users/${userId}/balance`, {
285
+ const res = await fetch(`${developerBaseUrl}/users/${encodeURIComponent(userId)}/balance`, {
247
286
  headers: {
248
287
  Authorization: `Bearer ${apiKey}`
249
- }
288
+ },
289
+ signal: AbortSignal.timeout(timeoutMs)
250
290
  });
251
291
  if (!res.ok) {
252
292
  if (res.status === 401) throw new Error("Invalid API key");
253
293
  throw new Error(`Failed to get balance: ${res.status}`);
254
294
  }
255
- return res.json();
295
+ const data = await res.json();
296
+ return { balance: data.balance };
256
297
  },
257
298
  /**
258
299
  * Charge (deduct) credits from a user's balance.
@@ -263,12 +304,13 @@ function createServerClient(config) {
263
304
  * @throws Error with status 402 if insufficient balance, 400 if invalid amount
264
305
  */
265
306
  async charge(userId, opts) {
266
- const res = await fetch(`${developerBaseUrl}/users/${userId}/charge`, {
307
+ const res = await fetch(`${developerBaseUrl}/users/${encodeURIComponent(userId)}/charge`, {
267
308
  method: "POST",
268
309
  headers: {
269
310
  "Content-Type": "application/json",
270
311
  Authorization: `Bearer ${apiKey}`
271
312
  },
313
+ signal: AbortSignal.timeout(timeoutMs),
272
314
  body: JSON.stringify({
273
315
  amount: opts.amount,
274
316
  request_uuid: opts.requestUuid,
@@ -292,12 +334,13 @@ function createServerClient(config) {
292
334
  * @returns Object with the new balance after deposit
293
335
  */
294
336
  async deposit(userId, opts) {
295
- const res = await fetch(`${developerBaseUrl}/users/${userId}/deposit`, {
337
+ const res = await fetch(`${developerBaseUrl}/users/${encodeURIComponent(userId)}/deposit`, {
296
338
  method: "POST",
297
339
  headers: {
298
340
  "Content-Type": "application/json",
299
341
  Authorization: `Bearer ${apiKey}`
300
342
  },
343
+ signal: AbortSignal.timeout(timeoutMs),
301
344
  body: JSON.stringify({
302
345
  amount: opts.amount,
303
346
  request_uuid: opts.requestUuid,
@@ -325,11 +368,12 @@ function createServerClient(config) {
325
368
  if (opts?.offset !== void 0) params.set("offset", String(opts.offset));
326
369
  const qs = params.toString();
327
370
  const res = await fetch(
328
- `${developerBaseUrl}/users/${userId}/balance/transactions${qs ? `?${qs}` : ""}`,
371
+ `${developerBaseUrl}/users/${encodeURIComponent(userId)}/balance/transactions${qs ? `?${qs}` : ""}`,
329
372
  {
330
373
  headers: {
331
374
  Authorization: `Bearer ${apiKey}`
332
- }
375
+ },
376
+ signal: AbortSignal.timeout(timeoutMs)
333
377
  }
334
378
  );
335
379
  if (!res.ok) {
@@ -352,5 +396,7 @@ function createServerClient(config) {
352
396
  };
353
397
  }
354
398
  export {
355
- createServerClient
399
+ createServerClient,
400
+ deriveJwtSecret,
401
+ parseCookies
356
402
  };