@insforge/sdk 1.4.3 → 1.4.5-beta.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/dist/ssr.js CHANGED
@@ -94,7 +94,9 @@ function redactHeaders(headers) {
94
94
  return redacted;
95
95
  }
96
96
  function sanitizeBody(body) {
97
- if (body === null || body === void 0) return body;
97
+ if (body === null || body === void 0) {
98
+ return body;
99
+ }
98
100
  if (typeof body === "string") {
99
101
  try {
100
102
  const parsed = JSON.parse(body);
@@ -103,7 +105,9 @@ function sanitizeBody(body) {
103
105
  return body;
104
106
  }
105
107
  }
106
- if (Array.isArray(body)) return body.map(sanitizeBody);
108
+ if (Array.isArray(body)) {
109
+ return body.map(sanitizeBody);
110
+ }
107
111
  if (typeof body === "object") {
108
112
  const sanitized = {};
109
113
  for (const [key, value] of Object.entries(body)) {
@@ -118,7 +122,9 @@ function sanitizeBody(body) {
118
122
  return body;
119
123
  }
120
124
  function formatBody(body) {
121
- if (body === void 0 || body === null) return "";
125
+ if (body === void 0 || body === null) {
126
+ return "";
127
+ }
122
128
  if (typeof body === "string") {
123
129
  try {
124
130
  return JSON.stringify(JSON.parse(body), null, 2);
@@ -155,7 +161,9 @@ var Logger = class {
155
161
  * @param args - Additional arguments to pass to the log function
156
162
  */
157
163
  log(message, ...args) {
158
- if (!this.enabled) return;
164
+ if (!this.enabled) {
165
+ return;
166
+ }
159
167
  const formatted = `[InsForge Debug] ${message}`;
160
168
  if (this.customLog) {
161
169
  this.customLog(formatted, ...args);
@@ -169,7 +177,9 @@ var Logger = class {
169
177
  * @param args - Additional arguments to pass to the log function
170
178
  */
171
179
  warn(message, ...args) {
172
- if (!this.enabled) return;
180
+ if (!this.enabled) {
181
+ return;
182
+ }
173
183
  const formatted = `[InsForge Debug] ${message}`;
174
184
  if (this.customLog) {
175
185
  this.customLog(formatted, ...args);
@@ -183,7 +193,9 @@ var Logger = class {
183
193
  * @param args - Additional arguments to pass to the log function
184
194
  */
185
195
  error(message, ...args) {
186
- if (!this.enabled) return;
196
+ if (!this.enabled) {
197
+ return;
198
+ }
187
199
  const formatted = `[InsForge Debug] ${message}`;
188
200
  if (this.customLog) {
189
201
  this.customLog(formatted, ...args);
@@ -200,10 +212,10 @@ var Logger = class {
200
212
  * @param body - Request body (sensitive fields will be masked)
201
213
  */
202
214
  logRequest(method, url, headers, body) {
203
- if (!this.enabled) return;
204
- const parts = [
205
- `\u2192 ${method} ${url}`
206
- ];
215
+ if (!this.enabled) {
216
+ return;
217
+ }
218
+ const parts = [`\u2192 ${method} ${url}`];
207
219
  if (headers && Object.keys(headers).length > 0) {
208
220
  parts.push(` Headers: ${JSON.stringify(redactHeaders(headers))}`);
209
221
  }
@@ -224,10 +236,10 @@ var Logger = class {
224
236
  * @param body - Response body (sensitive fields will be masked, large bodies truncated)
225
237
  */
226
238
  logResponse(method, url, status, durationMs, body) {
227
- if (!this.enabled) return;
228
- const parts = [
229
- `\u2190 ${method} ${url} ${status} (${durationMs}ms)`
230
- ];
239
+ if (!this.enabled) {
240
+ return;
241
+ }
242
+ const parts = [`\u2190 ${method} ${url} ${status} (${durationMs}ms)`];
231
243
  const formattedBody = formatBody(sanitizeBody(body));
232
244
  if (formattedBody) {
233
245
  const truncated = formattedBody.length > 1e3 ? formattedBody.slice(0, 1e3) + "... [truncated]" : formattedBody;
@@ -242,21 +254,34 @@ var Logger = class {
242
254
  };
243
255
 
244
256
  // src/lib/token-manager.ts
257
+ var AuthChangeEvent = {
258
+ SIGNED_IN: "signedIn",
259
+ SIGNED_OUT: "signedOut",
260
+ TOKEN_REFRESHED: "tokenRefreshed"
261
+ };
245
262
  var CSRF_TOKEN_COOKIE = "insforge_csrf_token";
246
263
  function getCsrfToken() {
247
- if (typeof document === "undefined") return null;
264
+ if (typeof document === "undefined") {
265
+ return null;
266
+ }
248
267
  const match = document.cookie.split(";").find((c) => c.trim().startsWith(`${CSRF_TOKEN_COOKIE}=`));
249
- if (!match) return null;
268
+ if (!match) {
269
+ return null;
270
+ }
250
271
  return match.split("=")[1] || null;
251
272
  }
252
273
  function setCsrfToken(token) {
253
- if (typeof document === "undefined") return;
274
+ if (typeof document === "undefined") {
275
+ return;
276
+ }
254
277
  const maxAge = 7 * 24 * 60 * 60;
255
278
  const secure = typeof window !== "undefined" && window.location.protocol === "https:" ? "; Secure" : "";
256
279
  document.cookie = `${CSRF_TOKEN_COOKIE}=${encodeURIComponent(token)}; path=/; max-age=${maxAge}; SameSite=Lax${secure}`;
257
280
  }
258
281
  function clearCsrfToken() {
259
- if (typeof document === "undefined") return;
282
+ if (typeof document === "undefined") {
283
+ return;
284
+ }
260
285
  const secure = typeof window !== "undefined" && window.location.protocol === "https:" ? "; Secure" : "";
261
286
  document.cookie = `${CSRF_TOKEN_COOKIE}=; path=/; max-age=0; SameSite=Lax${secure}`;
262
287
  }
@@ -265,25 +290,26 @@ var TokenManager = class {
265
290
  // In-memory storage
266
291
  this.accessToken = null;
267
292
  this.user = null;
268
- // Callback for token changes (used by realtime to reconnect with new token)
269
- this.onTokenChange = null;
293
+ this.authStateChangeCallbacks = /* @__PURE__ */ new Map();
270
294
  }
271
295
  /**
272
296
  * Save session in memory
273
297
  */
274
- saveSession(session) {
298
+ saveSession(session, event = AuthChangeEvent.SIGNED_IN) {
275
299
  const tokenChanged = session.accessToken !== this.accessToken;
276
300
  this.accessToken = session.accessToken;
277
301
  this.user = session.user;
278
- if (tokenChanged && this.onTokenChange) {
279
- this.onTokenChange();
302
+ if (tokenChanged) {
303
+ this.notifyAuthStateChange(event);
280
304
  }
281
305
  }
282
306
  /**
283
307
  * Get current session
284
308
  */
285
309
  getSession() {
286
- if (!this.accessToken || !this.user) return null;
310
+ if (!this.accessToken || !this.user) {
311
+ return null;
312
+ }
287
313
  return {
288
314
  accessToken: this.accessToken,
289
315
  user: this.user
@@ -298,11 +324,11 @@ var TokenManager = class {
298
324
  /**
299
325
  * Set access token
300
326
  */
301
- setAccessToken(token) {
327
+ setAccessToken(token, event = AuthChangeEvent.SIGNED_IN) {
302
328
  const tokenChanged = token !== this.accessToken;
303
329
  this.accessToken = token;
304
- if (tokenChanged && this.onTokenChange) {
305
- this.onTokenChange();
330
+ if (tokenChanged) {
331
+ this.notifyAuthStateChange(event);
306
332
  }
307
333
  }
308
334
  /**
@@ -324,22 +350,74 @@ var TokenManager = class {
324
350
  const hadToken = this.accessToken !== null;
325
351
  this.accessToken = null;
326
352
  this.user = null;
327
- if (hadToken && this.onTokenChange) {
328
- this.onTokenChange();
353
+ if (hadToken) {
354
+ this.notifyAuthStateChange(AuthChangeEvent.SIGNED_OUT);
355
+ }
356
+ }
357
+ onAuthStateChange(callback) {
358
+ const id = Symbol("auth-state-change");
359
+ this.authStateChangeCallbacks.set(id, callback);
360
+ return () => this.authStateChangeCallbacks.delete(id);
361
+ }
362
+ notifyAuthStateChange(event) {
363
+ for (const callback of this.authStateChangeCallbacks.values()) {
364
+ try {
365
+ callback(event);
366
+ } catch (error) {
367
+ console.error("Error in auth state change callback:", error);
368
+ }
329
369
  }
330
370
  }
331
371
  };
332
372
 
373
+ // src/lib/jwt.ts
374
+ function decodeBase64Url(input) {
375
+ const normalized = input.replace(/-/g, "+").replace(/_/g, "/");
376
+ const padded = normalized.padEnd(normalized.length + (4 - normalized.length % 4) % 4, "=");
377
+ const binary = atob(padded);
378
+ const bytes = Uint8Array.from(binary, (char) => char.charCodeAt(0));
379
+ return new TextDecoder().decode(bytes);
380
+ }
381
+ function getJwtExpiration(token) {
382
+ if (!token) {
383
+ return null;
384
+ }
385
+ const [, payload] = token.split(".");
386
+ if (!payload) {
387
+ return null;
388
+ }
389
+ try {
390
+ const parsed = JSON.parse(decodeBase64Url(payload));
391
+ if (typeof parsed.exp !== "number" || !Number.isFinite(parsed.exp)) {
392
+ return null;
393
+ }
394
+ return new Date(parsed.exp * 1e3);
395
+ } catch {
396
+ return null;
397
+ }
398
+ }
399
+ function isJwtExpiredOrExpiring(token, leewaySeconds = 60) {
400
+ if (!token) {
401
+ return false;
402
+ }
403
+ const expires = getJwtExpiration(token);
404
+ if (!expires) {
405
+ return true;
406
+ }
407
+ return expires.getTime() <= Date.now() + leewaySeconds * 1e3;
408
+ }
409
+
333
410
  // src/lib/http-client.ts
334
411
  var RETRYABLE_STATUS_CODES = /* @__PURE__ */ new Set([500, 502, 503, 504]);
335
412
  var IDEMPOTENT_METHODS = /* @__PURE__ */ new Set(["GET", "HEAD", "PUT", "DELETE", "OPTIONS"]);
336
- var REFRESHABLE_AUTH_ERROR_CODES = /* @__PURE__ */ new Set([
337
- "AUTH_UNAUTHORIZED",
338
- "PGRST301"
339
- ]);
413
+ var REFRESHABLE_AUTH_ERROR_CODES = /* @__PURE__ */ new Set(["AUTH_UNAUTHORIZED", "PGRST301"]);
340
414
  function serializeBody(method, body, headers) {
341
- if (body === void 0) return void 0;
342
- if (method === "GET" || method === "HEAD") return void 0;
415
+ if (body === void 0) {
416
+ return void 0;
417
+ }
418
+ if (method === "GET" || method === "HEAD") {
419
+ return void 0;
420
+ }
343
421
  if (typeof FormData !== "undefined" && body instanceof FormData) {
344
422
  return body;
345
423
  }
@@ -347,7 +425,9 @@ function serializeBody(method, body, headers) {
347
425
  return JSON.stringify(body);
348
426
  }
349
427
  async function parseResponse(response) {
350
- if (response.status === 204) return void 0;
428
+ if (response.status === 204) {
429
+ return void 0;
430
+ }
351
431
  let data;
352
432
  const contentType = response.headers.get("content-type");
353
433
  try {
@@ -449,31 +529,24 @@ var HttpClient = class {
449
529
  return statusCode === 401 && REFRESHABLE_AUTH_ERROR_CODES.has(errorCode ?? "") && !this.config.isServerMode && !this.config.accessToken && !this.config.edgeFunctionToken && !options.skipAuthRefresh && authToken !== null;
450
530
  }
451
531
  async fetchWithRetry(args) {
452
- const {
453
- method,
454
- url,
455
- headers,
456
- body,
457
- fetchOptions,
458
- callerSignal,
459
- maxAttempts
460
- } = args;
532
+ const { method, url, headers, body, fetchOptions, callerSignal, maxAttempts } = args;
461
533
  let lastError;
462
534
  for (let attempt = 0; attempt <= maxAttempts; attempt++) {
463
535
  if (attempt > 0) {
464
536
  const delay = this.computeRetryDelay(attempt);
465
- this.logger.warn(
466
- `Retry ${attempt}/${maxAttempts} for ${method} ${url} in ${delay}ms`
467
- );
468
- if (callerSignal?.aborted) throw callerSignal.reason;
537
+ this.logger.warn(`Retry ${attempt}/${maxAttempts} for ${method} ${url} in ${delay}ms`);
538
+ if (callerSignal?.aborted) {
539
+ throw callerSignal.reason;
540
+ }
469
541
  await new Promise((resolve, reject) => {
470
542
  const onAbort = () => {
471
543
  clearTimeout(timer2);
472
544
  reject(callerSignal.reason);
473
545
  };
474
546
  const timer2 = setTimeout(() => {
475
- if (callerSignal)
547
+ if (callerSignal) {
476
548
  callerSignal.removeEventListener("abort", onAbort);
549
+ }
477
550
  resolve();
478
551
  }, delay);
479
552
  if (callerSignal) {
@@ -515,7 +588,9 @@ var HttpClient = class {
515
588
  ...controller ? { signal: controller.signal } : {}
516
589
  });
517
590
  if (this.isRetryableStatus(response.status) && attempt < maxAttempts) {
518
- if (timer !== void 0) clearTimeout(timer);
591
+ if (timer !== void 0) {
592
+ clearTimeout(timer);
593
+ }
519
594
  await response.body?.cancel();
520
595
  lastError = new InsForgeError(
521
596
  `Server error: ${response.status} ${response.statusText}`,
@@ -524,10 +599,14 @@ var HttpClient = class {
524
599
  );
525
600
  continue;
526
601
  }
527
- if (timer !== void 0) clearTimeout(timer);
602
+ if (timer !== void 0) {
603
+ clearTimeout(timer);
604
+ }
528
605
  return response;
529
606
  } catch (err) {
530
- if (timer !== void 0) clearTimeout(timer);
607
+ if (timer !== void 0) {
608
+ clearTimeout(timer);
609
+ }
531
610
  if (err?.name === "AbortError") {
532
611
  if (controller && controller.signal.aborted && this.timeout > 0 && !callerSignal?.aborted) {
533
612
  throw new InsForgeError(
@@ -549,11 +628,7 @@ var HttpClient = class {
549
628
  );
550
629
  }
551
630
  }
552
- throw lastError || new InsForgeError(
553
- "Request failed after all retry attempts",
554
- 0,
555
- "NETWORK_ERROR"
556
- );
631
+ throw lastError || new InsForgeError("Request failed after all retry attempts", 0, "NETWORK_ERROR");
557
632
  }
558
633
  /**
559
634
  * Performs an HTTP request with automatic retry and timeout handling.
@@ -633,31 +708,15 @@ var HttpClient = class {
633
708
  }
634
709
  throw err;
635
710
  }
636
- this.logger.logResponse(
637
- method,
638
- url,
639
- response.status,
640
- Date.now() - startTime,
641
- data
642
- );
711
+ this.logger.logResponse(method, url, response.status, Date.now() - startTime, data);
643
712
  return data;
644
713
  }
645
714
  async request(method, path, options = {}) {
646
715
  const tokenUsed = this.userToken;
647
716
  try {
648
- return await this.handleRequest(
649
- method,
650
- path,
651
- { ...options },
652
- tokenUsed
653
- );
717
+ return await this.handleRequest(method, path, { ...options }, tokenUsed);
654
718
  } catch (error) {
655
- if (!(error instanceof InsForgeError) || !this.shouldRefreshAccessToken(
656
- error.statusCode,
657
- error.error,
658
- tokenUsed,
659
- options
660
- )) {
719
+ if (!(error instanceof InsForgeError) || !this.shouldRefreshAccessToken(error.statusCode, error.error, tokenUsed, options)) {
661
720
  throw error;
662
721
  }
663
722
  if (tokenUsed !== this.userToken) {
@@ -742,12 +801,7 @@ var HttpClient = class {
742
801
  callerSignal,
743
802
  maxAttempts
744
803
  });
745
- this.logger.logResponse(
746
- method,
747
- url,
748
- response.status,
749
- Date.now() - startTime
750
- );
804
+ this.logger.logResponse(method, url, response.status, Date.now() - startTime);
751
805
  let errorCode = null;
752
806
  if (response.status === 401) {
753
807
  try {
@@ -761,12 +815,7 @@ var HttpClient = class {
761
815
  } catch {
762
816
  }
763
817
  }
764
- if (!this.shouldRefreshAccessToken(
765
- response.status,
766
- errorCode,
767
- tokenUsed,
768
- options
769
- )) {
818
+ if (!this.shouldRefreshAccessToken(response.status, errorCode, tokenUsed, options)) {
770
819
  return response;
771
820
  }
772
821
  if (tokenUsed !== this.userToken) {
@@ -860,10 +909,30 @@ var HttpClient = class {
860
909
  })();
861
910
  return this.refreshPromise;
862
911
  }
912
+ /** Returns a token safe to use for a new connection handshake. */
913
+ async getValidAccessToken(leewaySeconds = 60) {
914
+ const accessToken = this.tokenManager.getAccessToken() ?? this.userToken;
915
+ if (!accessToken || !isJwtExpiredOrExpiring(accessToken, leewaySeconds)) {
916
+ return accessToken;
917
+ }
918
+ const canRefresh = !this.config.isServerMode && !this.config.accessToken && !this.config.edgeFunctionToken && this.userToken !== null;
919
+ if (!canRefresh) {
920
+ return accessToken;
921
+ }
922
+ try {
923
+ const refreshed = await this.refreshAndSaveSession();
924
+ return refreshed.accessToken;
925
+ } catch (error) {
926
+ if (error instanceof InsForgeError && (error.statusCode === 401 || error.statusCode === 403) && this.userToken === accessToken) {
927
+ this.clearAuthSession();
928
+ }
929
+ throw error;
930
+ }
931
+ }
863
932
  async refreshAndSaveSession() {
864
933
  const newTokenData = await this.refreshAccessToken();
865
934
  this.setAuthToken(newTokenData.accessToken);
866
- this.tokenManager.saveSession(newTokenData);
935
+ this.tokenManager.saveSession(newTokenData, AuthChangeEvent.TOKEN_REFRESHED);
867
936
  if (newTokenData.csrfToken) {
868
937
  setCsrfToken(newTokenData.csrfToken);
869
938
  }
@@ -959,11 +1028,15 @@ var Auth = class {
959
1028
  isServerMode() {
960
1029
  return !!this.options.isServerMode;
961
1030
  }
1031
+ /** Subscribe to SDK authentication state changes. */
1032
+ onAuthStateChange(callback) {
1033
+ return this.tokenManager.onAuthStateChange(callback);
1034
+ }
962
1035
  /**
963
1036
  * Save session from API response
964
1037
  * Handles token storage, CSRF token, and HTTP auth header
965
1038
  */
966
- saveSessionFromResponse(response) {
1039
+ saveSessionFromResponse(response, event = AuthChangeEvent.SIGNED_IN) {
967
1040
  if (!response.accessToken || !response.user) {
968
1041
  return false;
969
1042
  }
@@ -975,7 +1048,7 @@ var Auth = class {
975
1048
  setCsrfToken(response.csrfToken);
976
1049
  }
977
1050
  if (!this.isServerMode()) {
978
- this.tokenManager.saveSession(session);
1051
+ this.tokenManager.saveSession(session, event);
979
1052
  }
980
1053
  this.http.setAuthToken(response.accessToken);
981
1054
  this.http.setRefreshToken(response.refreshToken ?? null);
@@ -989,7 +1062,9 @@ var Auth = class {
989
1062
  * Supports PKCE flow (insforge_code)
990
1063
  */
991
1064
  async detectAuthCallback() {
992
- if (this.isServerMode() || typeof window === "undefined") return;
1065
+ if (this.isServerMode() || typeof window === "undefined") {
1066
+ return;
1067
+ }
993
1068
  try {
994
1069
  const params = new URLSearchParams(window.location.search);
995
1070
  const error = params.get("error");
@@ -1097,11 +1172,7 @@ var Auth = class {
1097
1172
  if (!signInOptions || !signInOptions.redirectTo) {
1098
1173
  return {
1099
1174
  data: {},
1100
- error: new InsForgeError(
1101
- "Redirect URI is required",
1102
- 400,
1103
- import_shared_schemas.ERROR_CODES.INVALID_INPUT
1104
- )
1175
+ error: new InsForgeError("Redirect URI is required", 400, import_shared_schemas.ERROR_CODES.INVALID_INPUT)
1105
1176
  };
1106
1177
  }
1107
1178
  const { provider } = signInOptions;
@@ -1176,10 +1247,7 @@ var Auth = class {
1176
1247
  error: null
1177
1248
  };
1178
1249
  } catch (error) {
1179
- return wrapError(
1180
- error,
1181
- "An unexpected error occurred during OAuth code exchange"
1182
- );
1250
+ return wrapError(error, "An unexpected error occurred during OAuth code exchange");
1183
1251
  }
1184
1252
  }
1185
1253
  /**
@@ -1206,10 +1274,7 @@ var Auth = class {
1206
1274
  error: null
1207
1275
  };
1208
1276
  } catch (error) {
1209
- return wrapError(
1210
- error,
1211
- "An unexpected error occurred during ID token sign in"
1212
- );
1277
+ return wrapError(error, "An unexpected error occurred during ID token sign in");
1213
1278
  }
1214
1279
  }
1215
1280
  // ============================================================================
@@ -1250,14 +1315,11 @@ var Auth = class {
1250
1315
  }
1251
1316
  );
1252
1317
  if (response.accessToken) {
1253
- this.saveSessionFromResponse(response);
1318
+ this.saveSessionFromResponse(response, AuthChangeEvent.TOKEN_REFRESHED);
1254
1319
  }
1255
1320
  return { data: response, error: null };
1256
1321
  } catch (error) {
1257
- return wrapError(
1258
- error,
1259
- "An unexpected error occurred during session refresh"
1260
- );
1322
+ return wrapError(error, "An unexpected error occurred during session refresh");
1261
1323
  }
1262
1324
  }
1263
1325
  /**
@@ -1268,11 +1330,11 @@ var Auth = class {
1268
1330
  try {
1269
1331
  if (this.isServerMode()) {
1270
1332
  const accessToken = this.tokenManager.getAccessToken();
1271
- if (!accessToken) return { data: { user: null }, error: null };
1333
+ if (!accessToken) {
1334
+ return { data: { user: null }, error: null };
1335
+ }
1272
1336
  this.http.setAuthToken(accessToken);
1273
- const response = await this.http.get(
1274
- "/api/auth/sessions/current"
1275
- );
1337
+ const response = await this.http.get("/api/auth/sessions/current");
1276
1338
  const user = response.user ?? null;
1277
1339
  return { data: { user }, error: null };
1278
1340
  }
@@ -1310,25 +1372,17 @@ var Auth = class {
1310
1372
  // ============================================================================
1311
1373
  async getProfile(userId) {
1312
1374
  try {
1313
- const response = await this.http.get(
1314
- `/api/auth/profiles/${userId}`
1315
- );
1375
+ const response = await this.http.get(`/api/auth/profiles/${userId}`);
1316
1376
  return { data: response, error: null };
1317
1377
  } catch (error) {
1318
- return wrapError(
1319
- error,
1320
- "An unexpected error occurred while fetching user profile"
1321
- );
1378
+ return wrapError(error, "An unexpected error occurred while fetching user profile");
1322
1379
  }
1323
1380
  }
1324
1381
  async setProfile(profile) {
1325
1382
  try {
1326
- const response = await this.http.patch(
1327
- "/api/auth/profiles/current",
1328
- {
1329
- profile
1330
- }
1331
- );
1383
+ const response = await this.http.patch("/api/auth/profiles/current", {
1384
+ profile
1385
+ });
1332
1386
  const currentUser = this.tokenManager.getUser();
1333
1387
  if (!this.isServerMode() && currentUser && response.profile !== void 0) {
1334
1388
  this.tokenManager.setUser({
@@ -1338,10 +1392,7 @@ var Auth = class {
1338
1392
  }
1339
1393
  return { data: response, error: null };
1340
1394
  } catch (error) {
1341
- return wrapError(
1342
- error,
1343
- "An unexpected error occurred while updating user profile"
1344
- );
1395
+ return wrapError(error, "An unexpected error occurred while updating user profile");
1345
1396
  }
1346
1397
  }
1347
1398
  // ============================================================================
@@ -1354,10 +1405,7 @@ var Auth = class {
1354
1405
  });
1355
1406
  return { data: response, error: null };
1356
1407
  } catch (error) {
1357
- return wrapError(
1358
- error,
1359
- "An unexpected error occurred while sending verification email"
1360
- );
1408
+ return wrapError(error, "An unexpected error occurred while sending verification email");
1361
1409
  }
1362
1410
  }
1363
1411
  async verifyEmail(request) {
@@ -1373,10 +1421,7 @@ var Auth = class {
1373
1421
  }
1374
1422
  return { data: response, error: null };
1375
1423
  } catch (error) {
1376
- return wrapError(
1377
- error,
1378
- "An unexpected error occurred while verifying email"
1379
- );
1424
+ return wrapError(error, "An unexpected error occurred while verifying email");
1380
1425
  }
1381
1426
  }
1382
1427
  // ============================================================================
@@ -1389,10 +1434,7 @@ var Auth = class {
1389
1434
  });
1390
1435
  return { data: response, error: null };
1391
1436
  } catch (error) {
1392
- return wrapError(
1393
- error,
1394
- "An unexpected error occurred while sending password reset email"
1395
- );
1437
+ return wrapError(error, "An unexpected error occurred while sending password reset email");
1396
1438
  }
1397
1439
  }
1398
1440
  async exchangeResetPasswordToken(request) {
@@ -1404,10 +1446,7 @@ var Auth = class {
1404
1446
  );
1405
1447
  return { data: response, error: null };
1406
1448
  } catch (error) {
1407
- return wrapError(
1408
- error,
1409
- "An unexpected error occurred while verifying reset code"
1410
- );
1449
+ return wrapError(error, "An unexpected error occurred while verifying reset code");
1411
1450
  }
1412
1451
  }
1413
1452
  async resetPassword(request) {
@@ -1419,10 +1458,7 @@ var Auth = class {
1419
1458
  );
1420
1459
  return { data: response, error: null };
1421
1460
  } catch (error) {
1422
- return wrapError(
1423
- error,
1424
- "An unexpected error occurred while resetting password"
1425
- );
1461
+ return wrapError(error, "An unexpected error occurred while resetting password");
1426
1462
  }
1427
1463
  }
1428
1464
  // ============================================================================
@@ -1430,16 +1466,12 @@ var Auth = class {
1430
1466
  // ============================================================================
1431
1467
  async getPublicAuthConfig() {
1432
1468
  try {
1433
- const response = await this.http.get(
1434
- "/api/auth/public-config",
1435
- { skipAuthRefresh: true }
1436
- );
1469
+ const response = await this.http.get("/api/auth/public-config", {
1470
+ skipAuthRefresh: true
1471
+ });
1437
1472
  return { data: response, error: null };
1438
1473
  } catch (error) {
1439
- return wrapError(
1440
- error,
1441
- "An unexpected error occurred while fetching auth configuration"
1442
- );
1474
+ return wrapError(error, "An unexpected error occurred while fetching auth configuration");
1443
1475
  }
1444
1476
  }
1445
1477
  };
@@ -1597,11 +1629,7 @@ var StorageBucket = class {
1597
1629
  } catch (error) {
1598
1630
  return {
1599
1631
  data: null,
1600
- error: error instanceof InsForgeError ? error : new InsForgeError(
1601
- "Upload failed",
1602
- 500,
1603
- "STORAGE_ERROR"
1604
- )
1632
+ error: error instanceof InsForgeError ? error : new InsForgeError("Upload failed", 500, "STORAGE_ERROR")
1605
1633
  };
1606
1634
  }
1607
1635
  }
@@ -1647,11 +1675,7 @@ var StorageBucket = class {
1647
1675
  } catch (error) {
1648
1676
  return {
1649
1677
  data: null,
1650
- error: error instanceof InsForgeError ? error : new InsForgeError(
1651
- "Upload failed",
1652
- 500,
1653
- "STORAGE_ERROR"
1654
- )
1678
+ error: error instanceof InsForgeError ? error : new InsForgeError("Upload failed", 500, "STORAGE_ERROR")
1655
1679
  };
1656
1680
  }
1657
1681
  }
@@ -1679,13 +1703,10 @@ var StorageBucket = class {
1679
1703
  );
1680
1704
  }
1681
1705
  if (strategy.confirmRequired && strategy.confirmUrl) {
1682
- const confirmResponse = await this.http.post(
1683
- strategy.confirmUrl,
1684
- {
1685
- size: file.size,
1686
- contentType: file.type || "application/octet-stream"
1687
- }
1688
- );
1706
+ const confirmResponse = await this.http.post(strategy.confirmUrl, {
1707
+ size: file.size,
1708
+ contentType: file.type || "application/octet-stream"
1709
+ });
1689
1710
  return { data: confirmResponse, error: null };
1690
1711
  }
1691
1712
  return {
@@ -1700,11 +1721,7 @@ var StorageBucket = class {
1700
1721
  error: null
1701
1722
  };
1702
1723
  } catch (error) {
1703
- throw error instanceof InsForgeError ? error : new InsForgeError(
1704
- "Presigned upload failed",
1705
- 500,
1706
- "STORAGE_ERROR"
1707
- );
1724
+ throw error instanceof InsForgeError ? error : new InsForgeError("Presigned upload failed", 500, "STORAGE_ERROR");
1708
1725
  }
1709
1726
  }
1710
1727
  /**
@@ -1758,11 +1775,7 @@ var StorageBucket = class {
1758
1775
  } catch (error) {
1759
1776
  return {
1760
1777
  data: null,
1761
- error: error instanceof InsForgeError ? error : new InsForgeError(
1762
- "Download failed",
1763
- 500,
1764
- "STORAGE_ERROR"
1765
- )
1778
+ error: error instanceof InsForgeError ? error : new InsForgeError("Download failed", 500, "STORAGE_ERROR")
1766
1779
  };
1767
1780
  }
1768
1781
  }
@@ -1797,7 +1810,9 @@ var StorageBucket = class {
1797
1810
  } catch (error) {
1798
1811
  const status = error instanceof InsForgeError ? error.statusCode : void 0;
1799
1812
  const isMissingRoute = (status === 404 || status === 405) && !(error instanceof InsForgeError && error.error === "STORAGE_NOT_FOUND");
1800
- if (!isMissingRoute) throw error;
1813
+ if (!isMissingRoute) {
1814
+ throw error;
1815
+ }
1801
1816
  return await this.http.post(
1802
1817
  `/api/storage/buckets/${this.bucketName}/objects/${encoded}/download-strategy`,
1803
1818
  { expiresIn }
@@ -1873,10 +1888,18 @@ var StorageBucket = class {
1873
1888
  async list(options) {
1874
1889
  try {
1875
1890
  const params = {};
1876
- if (options?.prefix) params.prefix = options.prefix;
1877
- if (options?.search) params.search = options.search;
1878
- if (options?.limit) params.limit = options.limit.toString();
1879
- if (options?.offset) params.offset = options.offset.toString();
1891
+ if (options?.prefix) {
1892
+ params.prefix = options.prefix;
1893
+ }
1894
+ if (options?.search) {
1895
+ params.search = options.search;
1896
+ }
1897
+ if (options?.limit) {
1898
+ params.limit = options.limit.toString();
1899
+ }
1900
+ if (options?.offset) {
1901
+ params.offset = options.offset.toString();
1902
+ }
1880
1903
  const response = await this.http.get(
1881
1904
  `/api/storage/buckets/${this.bucketName}/objects`,
1882
1905
  { params }
@@ -1885,11 +1908,7 @@ var StorageBucket = class {
1885
1908
  } catch (error) {
1886
1909
  return {
1887
1910
  data: null,
1888
- error: error instanceof InsForgeError ? error : new InsForgeError(
1889
- "List failed",
1890
- 500,
1891
- "STORAGE_ERROR"
1892
- )
1911
+ error: error instanceof InsForgeError ? error : new InsForgeError("List failed", 500, "STORAGE_ERROR")
1893
1912
  };
1894
1913
  }
1895
1914
  }
@@ -1906,11 +1925,7 @@ var StorageBucket = class {
1906
1925
  } catch (error) {
1907
1926
  return {
1908
1927
  data: null,
1909
- error: error instanceof InsForgeError ? error : new InsForgeError(
1910
- "Delete failed",
1911
- 500,
1912
- "STORAGE_ERROR"
1913
- )
1928
+ error: error instanceof InsForgeError ? error : new InsForgeError("Delete failed", 500, "STORAGE_ERROR")
1914
1929
  };
1915
1930
  }
1916
1931
  }
@@ -2032,14 +2047,11 @@ var ChatCompletions = class {
2032
2047
  if (params.stream) {
2033
2048
  const headers = this.http.getHeaders();
2034
2049
  headers["Content-Type"] = "application/json";
2035
- const response2 = await this.http.fetch(
2036
- `${this.http.baseUrl}/api/ai/chat/completion`,
2037
- {
2038
- method: "POST",
2039
- headers,
2040
- body: JSON.stringify(backendParams)
2041
- }
2042
- );
2050
+ const response2 = await this.http.fetch(`${this.http.baseUrl}/api/ai/chat/completion`, {
2051
+ method: "POST",
2052
+ headers,
2053
+ body: JSON.stringify(backendParams)
2054
+ });
2043
2055
  if (!response2.ok) {
2044
2056
  const error = await response2.json();
2045
2057
  throw new Error(error.error || "Stream request failed");
@@ -2087,7 +2099,9 @@ var ChatCompletions = class {
2087
2099
  try {
2088
2100
  while (true) {
2089
2101
  const { done, value } = await reader.read();
2090
- if (done) break;
2102
+ if (done) {
2103
+ break;
2104
+ }
2091
2105
  buffer += decoder.decode(value, { stream: true });
2092
2106
  const lines = buffer.split("\n");
2093
2107
  buffer = lines.pop() || "";
@@ -2135,7 +2149,7 @@ var ChatCompletions = class {
2135
2149
  reader.releaseLock();
2136
2150
  return;
2137
2151
  }
2138
- } catch (e) {
2152
+ } catch {
2139
2153
  console.warn("Failed to parse SSE data:", dataStr);
2140
2154
  }
2141
2155
  }
@@ -2188,10 +2202,7 @@ var Embeddings = class {
2188
2202
  * ```
2189
2203
  */
2190
2204
  async create(params) {
2191
- const response = await this.http.post(
2192
- "/api/ai/embeddings",
2193
- params
2194
- );
2205
+ const response = await this.http.post("/api/ai/embeddings", params);
2195
2206
  return {
2196
2207
  object: response.object,
2197
2208
  data: response.data,
@@ -2277,7 +2288,9 @@ var Functions = class _Functions {
2277
2288
  static deriveSubhostingUrl(baseUrl) {
2278
2289
  try {
2279
2290
  const { hostname } = new URL(baseUrl);
2280
- if (!hostname.endsWith(".insforge.app")) return void 0;
2291
+ if (!hostname.endsWith(".insforge.app")) {
2292
+ return void 0;
2293
+ }
2281
2294
  const appKey = hostname.split(".")[0];
2282
2295
  return `https://${appKey}.functions.insforge.app`;
2283
2296
  } catch {
@@ -2323,7 +2336,9 @@ var Functions = class _Functions {
2323
2336
  const data = await parseResponse(res);
2324
2337
  return { data, error: null };
2325
2338
  } catch (error) {
2326
- if (error instanceof Error && error.name === "AbortError") throw error;
2339
+ if (error instanceof Error && error.name === "AbortError") {
2340
+ throw error;
2341
+ }
2327
2342
  return {
2328
2343
  data: null,
2329
2344
  error: error instanceof InsForgeError ? error : new InsForgeError(
@@ -2342,7 +2357,9 @@ var Functions = class _Functions {
2342
2357
  });
2343
2358
  return { data, error: null };
2344
2359
  } catch (error) {
2345
- if (error instanceof Error && error.name === "AbortError") throw error;
2360
+ if (error instanceof Error && error.name === "AbortError") {
2361
+ throw error;
2362
+ }
2346
2363
  if (error instanceof InsForgeError && error.statusCode === 404) {
2347
2364
  } else {
2348
2365
  return {
@@ -2361,7 +2378,9 @@ var Functions = class _Functions {
2361
2378
  const data = await this.http.request(method, path, { body, headers });
2362
2379
  return { data, error: null };
2363
2380
  } catch (error) {
2364
- if (error instanceof Error && error.name === "AbortError") throw error;
2381
+ if (error instanceof Error && error.name === "AbortError") {
2382
+ throw error;
2383
+ }
2365
2384
  return {
2366
2385
  data: null,
2367
2386
  error: error instanceof InsForgeError ? error : new InsForgeError(
@@ -2376,32 +2395,37 @@ var Functions = class _Functions {
2376
2395
 
2377
2396
  // src/modules/realtime.ts
2378
2397
  var CONNECT_TIMEOUT = 1e4;
2398
+ var SUBSCRIBE_TIMEOUT = 1e4;
2379
2399
  var Realtime = class {
2380
- constructor(baseUrl, tokenManager, anonKey) {
2381
- this.socket = null;
2382
- this.connectPromise = null;
2383
- this.subscribedChannels = /* @__PURE__ */ new Set();
2384
- this.eventListeners = /* @__PURE__ */ new Map();
2400
+ constructor(baseUrl, tokenManager, anonKey, getValidAccessToken = async () => tokenManager.getAccessToken()) {
2385
2401
  this.baseUrl = baseUrl;
2386
2402
  this.tokenManager = tokenManager;
2387
2403
  this.anonKey = anonKey;
2388
- this.tokenManager.onTokenChange = () => this.onTokenChange();
2404
+ this.getValidAccessToken = getValidAccessToken;
2405
+ this.socket = null;
2406
+ this.connectPromise = null;
2407
+ this.connectionAttempt = null;
2408
+ this.nextConnectionAttemptId = 0;
2409
+ this.subscriptions = /* @__PURE__ */ new Map();
2410
+ this.eventListeners = /* @__PURE__ */ new Map();
2411
+ this.tokenManager.onAuthStateChange((event) => {
2412
+ if (event !== AuthChangeEvent.TOKEN_REFRESHED) {
2413
+ this.reconnectForAuthChange();
2414
+ }
2415
+ });
2389
2416
  }
2390
2417
  notifyListeners(event, payload) {
2391
- const listeners = this.eventListeners.get(event);
2392
- if (!listeners) return;
2393
- for (const cb of listeners) {
2418
+ for (const callback of this.eventListeners.get(event) ?? []) {
2394
2419
  try {
2395
- cb(payload);
2396
- } catch (err) {
2397
- console.error(`Error in ${event} callback:`, err);
2420
+ callback(payload);
2421
+ } catch (error) {
2422
+ console.error(`Error in ${event} callback:`, error);
2398
2423
  }
2399
2424
  }
2400
2425
  }
2401
- /**
2402
- * Connect to the realtime server
2403
- * @returns Promise that resolves when connected
2404
- */
2426
+ async getHandshakeToken() {
2427
+ return await this.getValidAccessToken() ?? this.anonKey ?? null;
2428
+ }
2405
2429
  connect() {
2406
2430
  if (this.socket?.connected) {
2407
2431
  return Promise.resolve();
@@ -2409,210 +2433,324 @@ var Realtime = class {
2409
2433
  if (this.connectPromise) {
2410
2434
  return this.connectPromise;
2411
2435
  }
2412
- this.connectPromise = (async () => {
2413
- try {
2414
- const { io } = await import("socket.io-client");
2415
- await new Promise((resolve, reject) => {
2416
- const token = this.tokenManager.getAccessToken() ?? this.anonKey;
2417
- this.socket = io(this.baseUrl, {
2418
- transports: ["websocket"],
2419
- auth: token ? { token } : void 0
2420
- });
2421
- let initialConnection = true;
2422
- let timeoutId = null;
2423
- const cleanup = () => {
2424
- if (timeoutId) {
2425
- clearTimeout(timeoutId);
2426
- timeoutId = null;
2427
- }
2428
- };
2429
- timeoutId = setTimeout(() => {
2430
- if (initialConnection) {
2431
- initialConnection = false;
2432
- this.connectPromise = null;
2433
- this.socket?.disconnect();
2434
- this.socket = null;
2435
- reject(new Error(`Connection timeout after ${CONNECT_TIMEOUT}ms`));
2436
- }
2437
- }, CONNECT_TIMEOUT);
2438
- this.socket.on("connect", () => {
2439
- cleanup();
2440
- for (const channel of this.subscribedChannels) {
2441
- this.socket.emit("realtime:subscribe", { channel });
2442
- }
2443
- this.notifyListeners("connect");
2444
- if (initialConnection) {
2445
- initialConnection = false;
2446
- this.connectPromise = null;
2447
- resolve();
2448
- }
2449
- });
2450
- this.socket.on("connect_error", (error) => {
2451
- cleanup();
2452
- this.notifyListeners("connect_error", error);
2453
- if (initialConnection) {
2454
- initialConnection = false;
2455
- this.connectPromise = null;
2456
- reject(error);
2457
- }
2458
- });
2459
- this.socket.on("disconnect", (reason) => {
2460
- this.notifyListeners("disconnect", reason);
2461
- });
2462
- this.socket.on("realtime:error", (error) => {
2463
- this.notifyListeners("error", error);
2464
- });
2465
- this.socket.onAny((event, message) => {
2466
- if (event === "realtime:error") return;
2467
- this.notifyListeners(event, message);
2468
- });
2436
+ const attemptId = ++this.nextConnectionAttemptId;
2437
+ const connection = (async () => {
2438
+ const { io } = await import("socket.io-client");
2439
+ if (attemptId !== this.nextConnectionAttemptId) {
2440
+ throw new Error("Connection cancelled");
2441
+ }
2442
+ await new Promise((resolve, reject) => {
2443
+ const socket = io(this.baseUrl, {
2444
+ transports: ["websocket"],
2445
+ auth: (callback) => {
2446
+ void this.getHandshakeToken().then(
2447
+ (token) => callback(token ? { token } : {}),
2448
+ () => callback({})
2449
+ );
2450
+ }
2469
2451
  });
2470
- } catch (error) {
2452
+ this.socket = socket;
2453
+ let initialConnection = true;
2454
+ let timeoutId = null;
2455
+ const clearConnectTimeout = () => {
2456
+ if (timeoutId) {
2457
+ clearTimeout(timeoutId);
2458
+ timeoutId = null;
2459
+ }
2460
+ };
2461
+ const dispose = () => {
2462
+ clearConnectTimeout();
2463
+ socket.off("connect", onConnect);
2464
+ socket.off("connect_error", onConnectError);
2465
+ socket.off("disconnect", onDisconnect);
2466
+ socket.off("realtime:error", onRealtimeError);
2467
+ socket.offAny(onAny);
2468
+ socket.disconnect();
2469
+ if (this.socket === socket) {
2470
+ this.socket = null;
2471
+ }
2472
+ if (this.connectionAttempt?.id === attemptId) {
2473
+ this.connectionAttempt = null;
2474
+ }
2475
+ };
2476
+ const fail = (error) => {
2477
+ if (!initialConnection) {
2478
+ return;
2479
+ }
2480
+ initialConnection = false;
2481
+ dispose();
2482
+ reject(error);
2483
+ };
2484
+ const onConnect = () => {
2485
+ if (this.socket !== socket) {
2486
+ return;
2487
+ }
2488
+ clearConnectTimeout();
2489
+ this.resubscribeChannels();
2490
+ this.notifyListeners("connect");
2491
+ if (initialConnection) {
2492
+ initialConnection = false;
2493
+ if (this.connectionAttempt?.id === attemptId) {
2494
+ this.connectionAttempt = null;
2495
+ }
2496
+ resolve();
2497
+ }
2498
+ };
2499
+ const onConnectError = (error) => {
2500
+ clearConnectTimeout();
2501
+ this.notifyListeners("connect_error", error);
2502
+ if (initialConnection) {
2503
+ fail(error);
2504
+ }
2505
+ };
2506
+ const onDisconnect = (reason) => {
2507
+ this.handleDisconnect(reason);
2508
+ };
2509
+ const onRealtimeError = (error) => {
2510
+ this.notifyListeners("error", error);
2511
+ };
2512
+ const onAny = (event, message) => {
2513
+ if (event === "realtime:error") {
2514
+ return;
2515
+ }
2516
+ this.applyPresenceEvent(event, message);
2517
+ this.notifyListeners(event, message);
2518
+ };
2519
+ this.connectionAttempt = { id: attemptId, socket, cancel: fail };
2520
+ socket.on("connect", onConnect);
2521
+ socket.on("connect_error", onConnectError);
2522
+ socket.on("disconnect", onDisconnect);
2523
+ socket.on("realtime:error", onRealtimeError);
2524
+ socket.onAny(onAny);
2525
+ timeoutId = setTimeout(
2526
+ () => fail(new Error(`Connection timeout after ${CONNECT_TIMEOUT}ms`)),
2527
+ CONNECT_TIMEOUT
2528
+ );
2529
+ });
2530
+ })();
2531
+ const trackedConnection = connection.finally(() => {
2532
+ if (this.connectPromise === trackedConnection) {
2471
2533
  this.connectPromise = null;
2472
- throw error;
2473
2534
  }
2474
- })();
2475
- return this.connectPromise;
2535
+ });
2536
+ this.connectPromise = trackedConnection;
2537
+ return trackedConnection;
2476
2538
  }
2477
- /**
2478
- * Disconnect from the realtime server
2479
- */
2480
2539
  disconnect() {
2481
- if (this.socket) {
2482
- this.socket.disconnect();
2483
- this.socket = null;
2540
+ this.nextConnectionAttemptId++;
2541
+ this.connectionAttempt?.cancel(new Error("Disconnected"));
2542
+ this.socket?.disconnect();
2543
+ this.socket = null;
2544
+ this.connectPromise = null;
2545
+ for (const subscription of this.subscriptions.values()) {
2546
+ this.settleSubscription(
2547
+ subscription,
2548
+ {
2549
+ ok: false,
2550
+ channel: subscription.channel,
2551
+ error: { code: "DISCONNECTED", message: "Disconnected" }
2552
+ },
2553
+ false
2554
+ );
2484
2555
  }
2485
- this.subscribedChannels.clear();
2556
+ this.subscriptions.clear();
2486
2557
  }
2487
- /**
2488
- * Handle token changes (e.g., after auth refresh)
2489
- * Updates socket auth so reconnects use the new token
2490
- * If connected, triggers reconnect to apply new token immediately
2491
- */
2492
- onTokenChange() {
2493
- const token = this.tokenManager.getAccessToken() ?? this.anonKey;
2494
- if (this.socket) {
2495
- this.socket.auth = token ? { token } : {};
2558
+ reconnectForAuthChange() {
2559
+ for (const subscription of this.subscriptions.values()) {
2560
+ if (subscription.status === "rejected") {
2561
+ subscription.status = "pending";
2562
+ }
2496
2563
  }
2497
- if (this.socket && (this.socket.connected || this.connectPromise)) {
2498
- this.socket.disconnect();
2499
- this.socket.connect();
2564
+ if (!this.socket) {
2565
+ return;
2566
+ }
2567
+ this.socket.disconnect();
2568
+ this.socket.connect();
2569
+ }
2570
+ handleDisconnect(reason) {
2571
+ for (const subscription of this.subscriptions.values()) {
2572
+ if (subscription.status === "rejected") {
2573
+ continue;
2574
+ }
2575
+ subscription.status = "pending";
2576
+ this.settleSubscription(
2577
+ subscription,
2578
+ {
2579
+ ok: false,
2580
+ channel: subscription.channel,
2581
+ error: { code: "DISCONNECTED", message: "Connection lost before subscription completed" }
2582
+ },
2583
+ true
2584
+ );
2585
+ }
2586
+ this.notifyListeners("disconnect", reason);
2587
+ }
2588
+ resubscribeChannels() {
2589
+ for (const [channel, subscription] of this.subscriptions) {
2590
+ if (subscription.status === "pending") {
2591
+ this.requestSubscription(channel, subscription);
2592
+ }
2593
+ }
2594
+ }
2595
+ requestSubscription(channel, subscription) {
2596
+ if (subscription.pending) {
2597
+ return subscription.pending;
2598
+ }
2599
+ const socket = this.socket;
2600
+ if (!socket?.connected) {
2601
+ return Promise.resolve({
2602
+ ok: false,
2603
+ channel,
2604
+ error: { code: "CONNECTION_FAILED", message: "Not connected to realtime server" }
2605
+ });
2606
+ }
2607
+ subscription.status = "pending";
2608
+ const epoch = ++subscription.epoch;
2609
+ let timeoutId;
2610
+ subscription.pending = new Promise((resolve) => {
2611
+ subscription.settlePending = (response) => {
2612
+ if (timeoutId) {
2613
+ clearTimeout(timeoutId);
2614
+ }
2615
+ subscription.pending = void 0;
2616
+ subscription.settlePending = void 0;
2617
+ resolve(response);
2618
+ };
2619
+ timeoutId = setTimeout(() => {
2620
+ if (this.subscriptions.get(channel) === subscription && subscription.epoch === epoch) {
2621
+ this.settleSubscription(
2622
+ subscription,
2623
+ {
2624
+ ok: false,
2625
+ channel,
2626
+ error: {
2627
+ code: "SUBSCRIBE_TIMEOUT",
2628
+ message: "Subscription acknowledgement timed out"
2629
+ }
2630
+ },
2631
+ true
2632
+ );
2633
+ }
2634
+ }, SUBSCRIBE_TIMEOUT);
2635
+ socket.emit("realtime:subscribe", { channel }, (response) => {
2636
+ if (this.subscriptions.get(channel) !== subscription || subscription.epoch !== epoch) {
2637
+ return;
2638
+ }
2639
+ if (response.ok) {
2640
+ subscription.status = "subscribed";
2641
+ subscription.members = new Map(
2642
+ response.presence.members.map((member) => [member.presenceId, member])
2643
+ );
2644
+ } else {
2645
+ subscription.status = "rejected";
2646
+ subscription.members.clear();
2647
+ }
2648
+ this.settleSubscription(subscription, response, false);
2649
+ });
2650
+ });
2651
+ return subscription.pending;
2652
+ }
2653
+ settleSubscription(subscription, response, incrementEpoch) {
2654
+ if (incrementEpoch) {
2655
+ subscription.epoch++;
2656
+ }
2657
+ subscription.settlePending?.(response);
2658
+ }
2659
+ applyPresenceEvent(event, message) {
2660
+ if (event !== "presence:join" && event !== "presence:leave") {
2661
+ return;
2662
+ }
2663
+ const presenceEvent = message;
2664
+ const channel = presenceEvent.meta?.channel;
2665
+ const member = presenceEvent.member;
2666
+ if (!channel || !member) {
2667
+ return;
2668
+ }
2669
+ const subscription = this.subscriptions.get(channel);
2670
+ if (!subscription) {
2671
+ return;
2672
+ }
2673
+ if (event === "presence:join") {
2674
+ subscription.members.set(member.presenceId, member);
2675
+ } else {
2676
+ subscription.members.delete(member.presenceId);
2500
2677
  }
2501
2678
  }
2502
- /**
2503
- * Check if connected to the realtime server
2504
- */
2505
2679
  get isConnected() {
2506
2680
  return this.socket?.connected ?? false;
2507
2681
  }
2508
- /**
2509
- * Get the current connection state
2510
- */
2511
2682
  get connectionState() {
2512
- if (!this.socket) return "disconnected";
2513
- if (this.socket.connected) return "connected";
2514
- return "connecting";
2683
+ if (!this.socket) {
2684
+ return "disconnected";
2685
+ }
2686
+ return this.socket.connected ? "connected" : "connecting";
2515
2687
  }
2516
- /**
2517
- * Get the socket ID (if connected)
2518
- */
2519
2688
  get socketId() {
2520
2689
  return this.socket?.id;
2521
2690
  }
2522
- /**
2523
- * Subscribe to a channel
2524
- *
2525
- * Automatically connects if not already connected.
2526
- *
2527
- * @param channel - Channel name (e.g., 'orders:123', 'broadcast')
2528
- * @returns Promise with the subscription response
2529
- */
2530
2691
  async subscribe(channel) {
2531
- if (this.subscribedChannels.has(channel)) {
2532
- return { ok: true, channel, presence: { members: [] } };
2692
+ let subscription = this.subscriptions.get(channel);
2693
+ if (subscription) {
2694
+ if (subscription.pending) {
2695
+ return subscription.pending;
2696
+ }
2697
+ if (subscription.status === "subscribed") {
2698
+ return { ok: true, channel, presence: { members: [...subscription.members.values()] } };
2699
+ }
2700
+ } else {
2701
+ subscription = { channel, epoch: 0, status: "pending", members: /* @__PURE__ */ new Map() };
2702
+ this.subscriptions.set(channel, subscription);
2533
2703
  }
2534
2704
  if (!this.socket?.connected) {
2535
2705
  try {
2536
2706
  await this.connect();
2537
2707
  } catch (error) {
2708
+ if (this.subscriptions.get(channel) === subscription) {
2709
+ this.subscriptions.delete(channel);
2710
+ }
2538
2711
  const message = error instanceof Error ? error.message : "Connection failed";
2539
2712
  return { ok: false, channel, error: { code: "CONNECTION_FAILED", message } };
2540
2713
  }
2541
2714
  }
2542
- return new Promise((resolve) => {
2543
- this.socket.emit("realtime:subscribe", { channel }, (response) => {
2544
- if (response.ok) {
2545
- this.subscribedChannels.add(channel);
2546
- }
2547
- resolve(response);
2548
- });
2549
- });
2715
+ return subscription.pending ?? this.requestSubscription(channel, subscription);
2550
2716
  }
2551
- /**
2552
- * Unsubscribe from a channel (fire-and-forget)
2553
- *
2554
- * @param channel - Channel name to unsubscribe from
2555
- */
2556
2717
  unsubscribe(channel) {
2557
- this.subscribedChannels.delete(channel);
2718
+ const subscription = this.subscriptions.get(channel);
2719
+ if (!subscription) {
2720
+ return;
2721
+ }
2722
+ this.subscriptions.delete(channel);
2723
+ this.settleSubscription(
2724
+ subscription,
2725
+ {
2726
+ ok: false,
2727
+ channel,
2728
+ error: { code: "SUBSCRIPTION_CANCELLED", message: "Subscription cancelled" }
2729
+ },
2730
+ true
2731
+ );
2558
2732
  if (this.socket?.connected) {
2559
2733
  this.socket.emit("realtime:unsubscribe", { channel });
2560
2734
  }
2561
2735
  }
2562
- /**
2563
- * Publish a message to a channel
2564
- *
2565
- * @param channel - Channel name
2566
- * @param event - Event name
2567
- * @param payload - Message payload
2568
- */
2569
2736
  async publish(channel, event, payload) {
2570
2737
  if (!this.socket?.connected) {
2571
2738
  throw new Error("Not connected to realtime server. Call connect() first.");
2572
2739
  }
2573
2740
  this.socket.emit("realtime:publish", { channel, event, payload });
2574
2741
  }
2575
- /**
2576
- * Listen for events
2577
- *
2578
- * Reserved event names:
2579
- * - 'connect' - Fired when connected to the server
2580
- * - 'connect_error' - Fired when connection fails (payload: Error)
2581
- * - 'disconnect' - Fired when disconnected (payload: reason string)
2582
- * - 'error' - Fired when a realtime error occurs (payload: RealtimeErrorPayload)
2583
- *
2584
- * All other events receive a `SocketMessage` payload with metadata.
2585
- *
2586
- * @param event - Event name to listen for
2587
- * @param callback - Callback function when event is received
2588
- */
2589
2742
  on(event, callback) {
2590
- if (!this.eventListeners.has(event)) {
2591
- this.eventListeners.set(event, /* @__PURE__ */ new Set());
2592
- }
2593
- this.eventListeners.get(event).add(callback);
2743
+ const listeners = this.eventListeners.get(event) ?? /* @__PURE__ */ new Set();
2744
+ listeners.add(callback);
2745
+ this.eventListeners.set(event, listeners);
2594
2746
  }
2595
- /**
2596
- * Remove a listener for a specific event
2597
- *
2598
- * @param event - Event name
2599
- * @param callback - The callback function to remove
2600
- */
2601
2747
  off(event, callback) {
2602
2748
  const listeners = this.eventListeners.get(event);
2603
- if (listeners) {
2604
- listeners.delete(callback);
2605
- if (listeners.size === 0) {
2606
- this.eventListeners.delete(event);
2607
- }
2749
+ listeners?.delete(callback);
2750
+ if (listeners?.size === 0) {
2751
+ this.eventListeners.delete(event);
2608
2752
  }
2609
2753
  }
2610
- /**
2611
- * Listen for an event only once, then automatically remove the listener
2612
- *
2613
- * @param event - Event name to listen for
2614
- * @param callback - Callback function when event is received
2615
- */
2616
2754
  once(event, callback) {
2617
2755
  const wrapper = (payload) => {
2618
2756
  this.off(event, wrapper);
@@ -2620,13 +2758,11 @@ var Realtime = class {
2620
2758
  };
2621
2759
  this.on(event, wrapper);
2622
2760
  }
2623
- /**
2624
- * Get all currently subscribed channels
2625
- *
2626
- * @returns Array of channel names
2627
- */
2628
2761
  getSubscribedChannels() {
2629
- return Array.from(this.subscribedChannels);
2762
+ return [...this.subscriptions.values()].filter((subscription) => subscription.status === "subscribed").map((subscription) => subscription.channel);
2763
+ }
2764
+ getPresenceState(channel) {
2765
+ return [...this.subscriptions.get(channel)?.members.values() ?? []];
2630
2766
  }
2631
2767
  };
2632
2768
 
@@ -2641,13 +2777,12 @@ var Emails = class {
2641
2777
  */
2642
2778
  async send(options) {
2643
2779
  try {
2644
- const data = await this.http.post(
2645
- "/api/email/send-raw",
2646
- options
2647
- );
2780
+ const data = await this.http.post("/api/email/send-raw", options);
2648
2781
  return { data, error: null };
2649
2782
  } catch (error) {
2650
- if (error instanceof Error && error.name === "AbortError") throw error;
2783
+ if (error instanceof Error && error.name === "AbortError") {
2784
+ throw error;
2785
+ }
2651
2786
  return {
2652
2787
  data: null,
2653
2788
  error: error instanceof InsForgeError ? error : new InsForgeError(
@@ -2730,10 +2865,7 @@ var RazorpayPayments = class {
2730
2865
  );
2731
2866
  return { data, error: null };
2732
2867
  } catch (error) {
2733
- return wrapError(
2734
- error,
2735
- "Razorpay order creation failed"
2736
- );
2868
+ return wrapError(error, "Razorpay order creation failed");
2737
2869
  }
2738
2870
  }
2739
2871
  async verifyOrder(environment, request) {
@@ -2744,10 +2876,7 @@ var RazorpayPayments = class {
2744
2876
  );
2745
2877
  return { data, error: null };
2746
2878
  } catch (error) {
2747
- return wrapError(
2748
- error,
2749
- "Razorpay order verification failed"
2750
- );
2879
+ return wrapError(error, "Razorpay order verification failed");
2751
2880
  }
2752
2881
  }
2753
2882
  async createSubscription(environment, request) {
@@ -2856,7 +2985,8 @@ var InsForgeClient = class {
2856
2985
  this.realtime = new Realtime(
2857
2986
  this.http.baseUrl,
2858
2987
  this.tokenManager,
2859
- config.anonKey
2988
+ config.anonKey,
2989
+ () => this.http.getValidAccessToken()
2860
2990
  );
2861
2991
  this.emails = new Emails(this.http);
2862
2992
  this.payments = new Payments(this.http);
@@ -2876,32 +3006,35 @@ var InsForgeClient = class {
2876
3006
  /**
2877
3007
  * Set the access token used by every SDK surface. Updates both the HTTP
2878
3008
  * client (database / storage / functions / AI / emails) and the realtime
2879
- * token manager (which fires `onTokenChange` to reconnect the WebSocket
2880
- * with the new bearer). Pass `null` to clear.
3009
+ * token manager. Pass `null` to sign out. By default a token replacement is
3010
+ * treated as a sign-in boundary and reconnects realtime. Pass
3011
+ * `AuthChangeEvent.TOKEN_REFRESHED` for a same-identity refresh to preserve a live socket; the
3012
+ * refreshed token is then used at the next handshake.
2881
3013
  *
2882
3014
  * Use this when an external auth provider (Better Auth, Clerk, Auth0,
2883
3015
  * WorkOS, Kinde, Stytch, …) issues the JWT and you need to keep the
2884
3016
  * long-lived InsForge client in sync. Without this, you'd have to call
2885
3017
  * `client.getHttpClient().setAuthToken(token)` AND reach into the private
2886
- * `client.realtime.tokenManager.setAccessToken(token)` separately
2887
- * forgetting the second one silently breaks realtime auth.
3018
+ * realtime token manager separately.
2888
3019
  *
2889
3020
  * @example
2890
3021
  * ```typescript
3022
+ * import { AuthChangeEvent } from '@insforge/sdk';
3023
+ *
2891
3024
  * // Refresh a third-party-issued JWT periodically
2892
3025
  * const { token } = await fetch('/api/insforge-token').then((r) => r.json());
2893
- * client.setAccessToken(token);
3026
+ * client.setAccessToken(token, AuthChangeEvent.TOKEN_REFRESHED);
2894
3027
  *
2895
3028
  * // Sign-out
2896
3029
  * client.setAccessToken(null);
2897
3030
  * ```
2898
3031
  */
2899
- setAccessToken(token) {
3032
+ setAccessToken(token, event = AuthChangeEvent.SIGNED_IN) {
2900
3033
  this.http.setAuthToken(token);
2901
3034
  if (token === null) {
2902
3035
  this.tokenManager.clearSession();
2903
3036
  } else {
2904
- this.tokenManager.setAccessToken(token);
3037
+ this.tokenManager.setAccessToken(token, event);
2905
3038
  }
2906
3039
  }
2907
3040
  /**
@@ -2914,38 +3047,6 @@ var InsForgeClient = class {
2914
3047
  */
2915
3048
  };
2916
3049
 
2917
- // src/lib/jwt.ts
2918
- function decodeBase64Url(input) {
2919
- const normalized = input.replace(/-/g, "+").replace(/_/g, "/");
2920
- const padded = normalized.padEnd(
2921
- normalized.length + (4 - normalized.length % 4) % 4,
2922
- "="
2923
- );
2924
- const binary = atob(padded);
2925
- const bytes = Uint8Array.from(binary, (char) => char.charCodeAt(0));
2926
- return new TextDecoder().decode(bytes);
2927
- }
2928
- function getJwtExpiration(token) {
2929
- if (!token) return null;
2930
- const [, payload] = token.split(".");
2931
- if (!payload) return null;
2932
- try {
2933
- const parsed = JSON.parse(decodeBase64Url(payload));
2934
- if (typeof parsed.exp !== "number" || !Number.isFinite(parsed.exp)) {
2935
- return null;
2936
- }
2937
- return new Date(parsed.exp * 1e3);
2938
- } catch {
2939
- return null;
2940
- }
2941
- }
2942
- function isJwtExpiredOrExpiring(token, leewaySeconds = 60) {
2943
- if (!token) return false;
2944
- const expires = getJwtExpiration(token);
2945
- if (!expires) return true;
2946
- return expires.getTime() <= Date.now() + leewaySeconds * 1e3;
2947
- }
2948
-
2949
3050
  // src/ssr/browser-client.ts
2950
3051
  var import_shared_schemas2 = require("@insforge/shared-schemas");
2951
3052
 
@@ -2960,18 +3061,28 @@ function getRefreshTokenCookieName(names) {
2960
3061
  return names?.refreshToken ?? DEFAULT_REFRESH_TOKEN_COOKIE;
2961
3062
  }
2962
3063
  function getCookieValue(cookies, name) {
2963
- if (!cookies) return null;
3064
+ if (!cookies) {
3065
+ return null;
3066
+ }
2964
3067
  const value = cookies.get(name);
2965
- if (typeof value === "string") return value || null;
2966
- if (value && typeof value.value === "string") return value.value || null;
3068
+ if (typeof value === "string") {
3069
+ return value || null;
3070
+ }
3071
+ if (value && typeof value.value === "string") {
3072
+ return value.value || null;
3073
+ }
2967
3074
  return null;
2968
3075
  }
2969
3076
  function getCookieValueFromHeader(cookieHeader, name) {
2970
- if (!cookieHeader) return null;
3077
+ if (!cookieHeader) {
3078
+ return null;
3079
+ }
2971
3080
  const parts = cookieHeader.split(";");
2972
3081
  for (const part of parts) {
2973
3082
  const [rawName, ...rawValue] = part.trim().split("=");
2974
- if (rawName !== name) continue;
3083
+ if (rawName !== name) {
3084
+ continue;
3085
+ }
2975
3086
  try {
2976
3087
  return decodeURIComponent(rawValue.join("="));
2977
3088
  } catch {
@@ -2981,7 +3092,9 @@ function getCookieValueFromHeader(cookieHeader, name) {
2981
3092
  return null;
2982
3093
  }
2983
3094
  function getBrowserCookie(name) {
2984
- if (typeof document === "undefined") return null;
3095
+ if (typeof document === "undefined") {
3096
+ return null;
3097
+ }
2985
3098
  return getCookieValueFromHeader(document.cookie, name);
2986
3099
  }
2987
3100
  function defaultCookieOptions() {
@@ -3018,11 +3131,15 @@ function expiredCookieOptions(overrides) {
3018
3131
  };
3019
3132
  }
3020
3133
  function setCookie(cookies, name, value, options) {
3021
- if (!cookies?.set) return;
3134
+ if (!cookies?.set) {
3135
+ return;
3136
+ }
3022
3137
  cookies.set(name, value, options);
3023
3138
  }
3024
3139
  function deleteCookie(cookies, name, options) {
3025
- if (!cookies) return;
3140
+ if (!cookies) {
3141
+ return;
3142
+ }
3026
3143
  if (cookies.set) {
3027
3144
  cookies.set(name, "", expiredCookieOptions(options));
3028
3145
  return;
@@ -3031,12 +3148,24 @@ function deleteCookie(cookies, name, options) {
3031
3148
  }
3032
3149
  function serializeCookie(name, value, options = {}) {
3033
3150
  const parts = [`${encodeURIComponent(name)}=${encodeURIComponent(value)}`];
3034
- if (options.maxAge !== void 0) parts.push(`Max-Age=${options.maxAge}`);
3035
- if (options.domain) parts.push(`Domain=${options.domain}`);
3036
- if (options.path) parts.push(`Path=${options.path}`);
3037
- if (options.expires) parts.push(`Expires=${options.expires.toUTCString()}`);
3038
- if (options.httpOnly) parts.push("HttpOnly");
3039
- if (options.secure) parts.push("Secure");
3151
+ if (options.maxAge !== void 0) {
3152
+ parts.push(`Max-Age=${options.maxAge}`);
3153
+ }
3154
+ if (options.domain) {
3155
+ parts.push(`Domain=${options.domain}`);
3156
+ }
3157
+ if (options.path) {
3158
+ parts.push(`Path=${options.path}`);
3159
+ }
3160
+ if (options.expires) {
3161
+ parts.push(`Expires=${options.expires.toUTCString()}`);
3162
+ }
3163
+ if (options.httpOnly) {
3164
+ parts.push("HttpOnly");
3165
+ }
3166
+ if (options.secure) {
3167
+ parts.push("Secure");
3168
+ }
3040
3169
  if (options.sameSite) {
3041
3170
  const sameSite = options.sameSite.charAt(0).toUpperCase() + options.sameSite.slice(1);
3042
3171
  parts.push(`SameSite=${sameSite}`);
@@ -3049,20 +3178,14 @@ function appendSetCookie(headers, name, value, options) {
3049
3178
  function setAuthCookies(cookies, tokens, settings = {}) {
3050
3179
  const accessName = getAccessTokenCookieName(settings.names);
3051
3180
  const refreshName = getRefreshTokenCookieName(settings.names);
3052
- const accessOptions = accessTokenCookieOptions(
3053
- tokens.accessToken,
3054
- settings.options?.accessToken
3055
- );
3181
+ const accessOptions = accessTokenCookieOptions(tokens.accessToken, settings.options?.accessToken);
3056
3182
  setCookie(cookies, accessName, tokens.accessToken, accessOptions);
3057
3183
  if (tokens.refreshToken) {
3058
3184
  setCookie(
3059
3185
  cookies,
3060
3186
  refreshName,
3061
3187
  tokens.refreshToken,
3062
- refreshTokenCookieOptions(
3063
- tokens.refreshToken,
3064
- settings.options?.refreshToken
3065
- )
3188
+ refreshTokenCookieOptions(tokens.refreshToken, settings.options?.refreshToken)
3066
3189
  );
3067
3190
  }
3068
3191
  }
@@ -3088,10 +3211,7 @@ function setAuthCookieHeaders(headers, tokens, settings = {}) {
3088
3211
  headers,
3089
3212
  refreshName,
3090
3213
  tokens.refreshToken,
3091
- refreshTokenCookieOptions(
3092
- tokens.refreshToken,
3093
- settings.options?.refreshToken
3094
- )
3214
+ refreshTokenCookieOptions(tokens.refreshToken, settings.options?.refreshToken)
3095
3215
  );
3096
3216
  }
3097
3217
  }
@@ -3134,10 +3254,14 @@ function toRefreshError(response, body) {
3134
3254
  );
3135
3255
  }
3136
3256
  async function readErrorCode(response) {
3137
- if (response.status !== 401) return null;
3257
+ if (response.status !== 401) {
3258
+ return null;
3259
+ }
3138
3260
  try {
3139
3261
  const body = await response.clone().json();
3140
- if (!body || typeof body !== "object") return null;
3262
+ if (!body || typeof body !== "object") {
3263
+ return null;
3264
+ }
3141
3265
  const candidate = body.error ?? body.code;
3142
3266
  return typeof candidate === "string" ? candidate : null;
3143
3267
  } catch {
@@ -3156,7 +3280,9 @@ function withAuthHeader(init, token) {
3156
3280
  };
3157
3281
  }
3158
3282
  function getRequestUrl(input) {
3159
- if (typeof input === "string") return input;
3283
+ if (typeof input === "string") {
3284
+ return input;
3285
+ }
3160
3286
  if (typeof Request !== "undefined" && input instanceof Request) {
3161
3287
  return input.url;
3162
3288
  }
@@ -3174,21 +3300,19 @@ function createBrowserClient(options = {}) {
3174
3300
  "Missing InsForge baseUrl or anonKey. Pass baseUrl and anonKey to createBrowserClient() or set NEXT_PUBLIC_INSFORGE_URL and NEXT_PUBLIC_INSFORGE_ANON_KEY."
3175
3301
  );
3176
3302
  }
3177
- let accessToken = getBrowserCookie(
3178
- getAccessTokenCookieName(options.names)
3179
- );
3303
+ let accessToken = getBrowserCookie(getAccessTokenCookieName(options.names));
3180
3304
  const refreshUrl = options.refreshUrl ?? "/api/auth/refresh";
3181
3305
  const fetchImpl = options.fetch ?? (globalThis.fetch ? globalThis.fetch.bind(globalThis) : void 0);
3182
3306
  let client;
3183
3307
  let sessionChecked = false;
3184
3308
  let refreshPromise = null;
3185
3309
  const refreshFromRoute = () => {
3186
- if (refreshPromise) return refreshPromise;
3310
+ if (refreshPromise) {
3311
+ return refreshPromise;
3312
+ }
3187
3313
  refreshPromise = (async () => {
3188
3314
  if (!fetchImpl) {
3189
- throw new Error(
3190
- "Fetch is not available. Please provide a fetch implementation."
3191
- );
3315
+ throw new Error("Fetch is not available. Please provide a fetch implementation.");
3192
3316
  }
3193
3317
  const response = await fetchImpl(refreshUrl, {
3194
3318
  method: "POST",
@@ -3205,11 +3329,15 @@ function createBrowserClient(options = {}) {
3205
3329
  }
3206
3330
  throw error;
3207
3331
  }
3208
- if (!body || typeof body !== "object") return null;
3332
+ if (!body || typeof body !== "object") {
3333
+ return null;
3334
+ }
3209
3335
  const refreshBody = body;
3210
- if (!refreshBody.accessToken || !refreshBody.user) return null;
3336
+ if (!refreshBody.accessToken || !refreshBody.user) {
3337
+ return null;
3338
+ }
3211
3339
  accessToken = refreshBody.accessToken;
3212
- client?.setAccessToken(refreshBody.accessToken);
3340
+ client?.setAccessToken(refreshBody.accessToken, AuthChangeEvent.TOKEN_REFRESHED);
3213
3341
  return refreshBody;
3214
3342
  })().finally(() => {
3215
3343
  sessionChecked = true;
@@ -3223,9 +3351,7 @@ function createBrowserClient(options = {}) {
3223
3351
  };
3224
3352
  const ssrFetch = async (input, init) => {
3225
3353
  if (!fetchImpl) {
3226
- throw new Error(
3227
- "Fetch is not available. Please provide a fetch implementation."
3228
- );
3354
+ throw new Error("Fetch is not available. Please provide a fetch implementation.");
3229
3355
  }
3230
3356
  if (shouldSkipRefresh(input)) {
3231
3357
  return fetchImpl(input, init);
@@ -3262,9 +3388,9 @@ function createBrowserClient(options = {}) {
3262
3388
  }
3263
3389
  });
3264
3390
  const setAccessToken = client.setAccessToken.bind(client);
3265
- client.setAccessToken = (token) => {
3391
+ client.setAccessToken = (token, event) => {
3266
3392
  accessToken = token;
3267
- setAccessToken(token);
3393
+ setAccessToken(token, event);
3268
3394
  };
3269
3395
  if (accessToken) {
3270
3396
  client.setAccessToken(accessToken);
@@ -3288,10 +3414,7 @@ function createServerClient(options = {}) {
3288
3414
  "Missing InsForge baseUrl or anonKey. Pass baseUrl and anonKey to createServerClient() or set NEXT_PUBLIC_INSFORGE_URL and NEXT_PUBLIC_INSFORGE_ANON_KEY."
3289
3415
  );
3290
3416
  }
3291
- const accessToken = options.accessToken ?? getCookieValue(
3292
- options.cookies,
3293
- getAccessTokenCookieName(options.names)
3294
- );
3417
+ const accessToken = options.accessToken ?? getCookieValue(options.cookies, getAccessTokenCookieName(options.names));
3295
3418
  return new InsForgeClient({
3296
3419
  ...options,
3297
3420
  baseUrl,
@@ -3314,7 +3437,9 @@ function jsonResponse(body, init = {}, headers = new Headers(init.headers)) {
3314
3437
  });
3315
3438
  }
3316
3439
  function normalizeError(error) {
3317
- if (error instanceof InsForgeError) return error;
3440
+ if (error instanceof InsForgeError) {
3441
+ return error;
3442
+ }
3318
3443
  if (error && typeof error === "object") {
3319
3444
  const body = error;
3320
3445
  return new InsForgeError(
@@ -3331,18 +3456,21 @@ function normalizeError(error) {
3331
3456
  }
3332
3457
  async function readJson(response) {
3333
3458
  const contentType = response.headers.get("content-type");
3334
- if (!contentType?.includes("json")) return null;
3459
+ if (!contentType?.includes("json")) {
3460
+ return null;
3461
+ }
3335
3462
  return response.json();
3336
3463
  }
3337
3464
  function readRefreshToken(options) {
3338
- if (options.refreshToken) return options.refreshToken;
3465
+ if (options.refreshToken) {
3466
+ return options.refreshToken;
3467
+ }
3339
3468
  const refreshCookieName = getRefreshTokenCookieName(options.names);
3340
3469
  const cookieValue = getCookieValue(options.cookies, refreshCookieName);
3341
- if (cookieValue) return cookieValue;
3342
- return getCookieValueFromHeader(
3343
- options.request?.headers.get("cookie"),
3344
- refreshCookieName
3345
- );
3470
+ if (cookieValue) {
3471
+ return cookieValue;
3472
+ }
3473
+ return getCookieValueFromHeader(options.request?.headers.get("cookie"), refreshCookieName);
3346
3474
  }
3347
3475
  async function refreshAuth(options = {}) {
3348
3476
  const headers = new Headers();
@@ -3383,9 +3511,7 @@ async function refreshAuth(options = {}) {
3383
3511
  }
3384
3512
  const fetchImpl = options.fetch ?? (globalThis.fetch ? globalThis.fetch.bind(globalThis) : void 0);
3385
3513
  if (!fetchImpl) {
3386
- throw new Error(
3387
- "Fetch is not available. Please provide a fetch implementation."
3388
- );
3514
+ throw new Error("Fetch is not available. Please provide a fetch implementation.");
3389
3515
  }
3390
3516
  const requestHeaders = new Headers(options.headers);
3391
3517
  requestHeaders.set("Authorization", `Bearer ${anonKey}`);
@@ -3466,7 +3592,9 @@ function createRefreshAuthRouter(options = {}) {
3466
3592
 
3467
3593
  // src/ssr/auth-actions.ts
3468
3594
  function persistSessionCookies(cookies, data, settings) {
3469
- if (!data?.accessToken) return;
3595
+ if (!data?.accessToken) {
3596
+ return;
3597
+ }
3470
3598
  setAuthCookies(
3471
3599
  cookies,
3472
3600
  {
@@ -3477,7 +3605,9 @@ function persistSessionCookies(cookies, data, settings) {
3477
3605
  );
3478
3606
  }
3479
3607
  function sanitizeAuthData(data) {
3480
- if (!data) return null;
3608
+ if (!data) {
3609
+ return null;
3610
+ }
3481
3611
  const {
3482
3612
  accessToken: _accessToken,
3483
3613
  refreshToken: _refreshToken,
@@ -3527,32 +3657,20 @@ function createAuthActions(options = {}) {
3527
3657
  signInWithPassword: async (request) => {
3528
3658
  const result = await createClient().auth.signInWithPassword(request);
3529
3659
  persistSessionCookies(writeCookies, result.data, cookieSettings);
3530
- return toSafeAuthResult(
3531
- result
3532
- );
3660
+ return toSafeAuthResult(result);
3533
3661
  },
3534
3662
  signInWithOAuth: async (providerOrOptions, signInOptions) => {
3535
- return createClient().auth.signInWithOAuth(
3536
- providerOrOptions,
3537
- signInOptions
3538
- );
3663
+ return createClient().auth.signInWithOAuth(providerOrOptions, signInOptions);
3539
3664
  },
3540
3665
  signInWithIdToken: async (credentials) => {
3541
3666
  const result = await createClient().auth.signInWithIdToken(credentials);
3542
3667
  persistSessionCookies(writeCookies, result.data, cookieSettings);
3543
- return toSafeAuthResult(
3544
- result
3545
- );
3668
+ return toSafeAuthResult(result);
3546
3669
  },
3547
3670
  exchangeOAuthCode: async (code, codeVerifier) => {
3548
- const result = await createClient().auth.exchangeOAuthCode(
3549
- code,
3550
- codeVerifier
3551
- );
3671
+ const result = await createClient().auth.exchangeOAuthCode(code, codeVerifier);
3552
3672
  persistSessionCookies(writeCookies, result.data, cookieSettings);
3553
- return toSafeAuthResult(
3554
- result
3555
- );
3673
+ return toSafeAuthResult(result);
3556
3674
  },
3557
3675
  verifyEmail: async (request) => {
3558
3676
  const result = await createClient().auth.verifyEmail(request);
@@ -3571,10 +3689,7 @@ function createAuthActions(options = {}) {
3571
3689
  async function updateSession(options) {
3572
3690
  const accessCookieName = getAccessTokenCookieName(options.names);
3573
3691
  const refreshCookieName = getRefreshTokenCookieName(options.names);
3574
- const accessToken = getCookieValue(
3575
- options.requestCookies,
3576
- accessCookieName
3577
- );
3692
+ const accessToken = getCookieValue(options.requestCookies, accessCookieName);
3578
3693
  if (accessToken && !isJwtExpiredOrExpiring(accessToken, options.refreshLeewaySeconds)) {
3579
3694
  return {
3580
3695
  refreshed: false,
@@ -3582,10 +3697,7 @@ async function updateSession(options) {
3582
3697
  error: null
3583
3698
  };
3584
3699
  }
3585
- const refreshToken = getCookieValue(
3586
- options.requestCookies,
3587
- refreshCookieName
3588
- );
3700
+ const refreshToken = getCookieValue(options.requestCookies, refreshCookieName);
3589
3701
  if (!refreshToken) {
3590
3702
  if (accessToken) {
3591
3703
  clearAuthCookies(options.requestCookies, options);