@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.mjs CHANGED
@@ -45,7 +45,9 @@ function redactHeaders(headers) {
45
45
  return redacted;
46
46
  }
47
47
  function sanitizeBody(body) {
48
- if (body === null || body === void 0) return body;
48
+ if (body === null || body === void 0) {
49
+ return body;
50
+ }
49
51
  if (typeof body === "string") {
50
52
  try {
51
53
  const parsed = JSON.parse(body);
@@ -54,7 +56,9 @@ function sanitizeBody(body) {
54
56
  return body;
55
57
  }
56
58
  }
57
- if (Array.isArray(body)) return body.map(sanitizeBody);
59
+ if (Array.isArray(body)) {
60
+ return body.map(sanitizeBody);
61
+ }
58
62
  if (typeof body === "object") {
59
63
  const sanitized = {};
60
64
  for (const [key, value] of Object.entries(body)) {
@@ -69,7 +73,9 @@ function sanitizeBody(body) {
69
73
  return body;
70
74
  }
71
75
  function formatBody(body) {
72
- if (body === void 0 || body === null) return "";
76
+ if (body === void 0 || body === null) {
77
+ return "";
78
+ }
73
79
  if (typeof body === "string") {
74
80
  try {
75
81
  return JSON.stringify(JSON.parse(body), null, 2);
@@ -106,7 +112,9 @@ var Logger = class {
106
112
  * @param args - Additional arguments to pass to the log function
107
113
  */
108
114
  log(message, ...args) {
109
- if (!this.enabled) return;
115
+ if (!this.enabled) {
116
+ return;
117
+ }
110
118
  const formatted = `[InsForge Debug] ${message}`;
111
119
  if (this.customLog) {
112
120
  this.customLog(formatted, ...args);
@@ -120,7 +128,9 @@ var Logger = class {
120
128
  * @param args - Additional arguments to pass to the log function
121
129
  */
122
130
  warn(message, ...args) {
123
- if (!this.enabled) return;
131
+ if (!this.enabled) {
132
+ return;
133
+ }
124
134
  const formatted = `[InsForge Debug] ${message}`;
125
135
  if (this.customLog) {
126
136
  this.customLog(formatted, ...args);
@@ -134,7 +144,9 @@ var Logger = class {
134
144
  * @param args - Additional arguments to pass to the log function
135
145
  */
136
146
  error(message, ...args) {
137
- if (!this.enabled) return;
147
+ if (!this.enabled) {
148
+ return;
149
+ }
138
150
  const formatted = `[InsForge Debug] ${message}`;
139
151
  if (this.customLog) {
140
152
  this.customLog(formatted, ...args);
@@ -151,10 +163,10 @@ var Logger = class {
151
163
  * @param body - Request body (sensitive fields will be masked)
152
164
  */
153
165
  logRequest(method, url, headers, body) {
154
- if (!this.enabled) return;
155
- const parts = [
156
- `\u2192 ${method} ${url}`
157
- ];
166
+ if (!this.enabled) {
167
+ return;
168
+ }
169
+ const parts = [`\u2192 ${method} ${url}`];
158
170
  if (headers && Object.keys(headers).length > 0) {
159
171
  parts.push(` Headers: ${JSON.stringify(redactHeaders(headers))}`);
160
172
  }
@@ -175,10 +187,10 @@ var Logger = class {
175
187
  * @param body - Response body (sensitive fields will be masked, large bodies truncated)
176
188
  */
177
189
  logResponse(method, url, status, durationMs, body) {
178
- if (!this.enabled) return;
179
- const parts = [
180
- `\u2190 ${method} ${url} ${status} (${durationMs}ms)`
181
- ];
190
+ if (!this.enabled) {
191
+ return;
192
+ }
193
+ const parts = [`\u2190 ${method} ${url} ${status} (${durationMs}ms)`];
182
194
  const formattedBody = formatBody(sanitizeBody(body));
183
195
  if (formattedBody) {
184
196
  const truncated = formattedBody.length > 1e3 ? formattedBody.slice(0, 1e3) + "... [truncated]" : formattedBody;
@@ -193,21 +205,34 @@ var Logger = class {
193
205
  };
194
206
 
195
207
  // src/lib/token-manager.ts
208
+ var AuthChangeEvent = {
209
+ SIGNED_IN: "signedIn",
210
+ SIGNED_OUT: "signedOut",
211
+ TOKEN_REFRESHED: "tokenRefreshed"
212
+ };
196
213
  var CSRF_TOKEN_COOKIE = "insforge_csrf_token";
197
214
  function getCsrfToken() {
198
- if (typeof document === "undefined") return null;
215
+ if (typeof document === "undefined") {
216
+ return null;
217
+ }
199
218
  const match = document.cookie.split(";").find((c) => c.trim().startsWith(`${CSRF_TOKEN_COOKIE}=`));
200
- if (!match) return null;
219
+ if (!match) {
220
+ return null;
221
+ }
201
222
  return match.split("=")[1] || null;
202
223
  }
203
224
  function setCsrfToken(token) {
204
- if (typeof document === "undefined") return;
225
+ if (typeof document === "undefined") {
226
+ return;
227
+ }
205
228
  const maxAge = 7 * 24 * 60 * 60;
206
229
  const secure = typeof window !== "undefined" && window.location.protocol === "https:" ? "; Secure" : "";
207
230
  document.cookie = `${CSRF_TOKEN_COOKIE}=${encodeURIComponent(token)}; path=/; max-age=${maxAge}; SameSite=Lax${secure}`;
208
231
  }
209
232
  function clearCsrfToken() {
210
- if (typeof document === "undefined") return;
233
+ if (typeof document === "undefined") {
234
+ return;
235
+ }
211
236
  const secure = typeof window !== "undefined" && window.location.protocol === "https:" ? "; Secure" : "";
212
237
  document.cookie = `${CSRF_TOKEN_COOKIE}=; path=/; max-age=0; SameSite=Lax${secure}`;
213
238
  }
@@ -216,25 +241,26 @@ var TokenManager = class {
216
241
  // In-memory storage
217
242
  this.accessToken = null;
218
243
  this.user = null;
219
- // Callback for token changes (used by realtime to reconnect with new token)
220
- this.onTokenChange = null;
244
+ this.authStateChangeCallbacks = /* @__PURE__ */ new Map();
221
245
  }
222
246
  /**
223
247
  * Save session in memory
224
248
  */
225
- saveSession(session) {
249
+ saveSession(session, event = AuthChangeEvent.SIGNED_IN) {
226
250
  const tokenChanged = session.accessToken !== this.accessToken;
227
251
  this.accessToken = session.accessToken;
228
252
  this.user = session.user;
229
- if (tokenChanged && this.onTokenChange) {
230
- this.onTokenChange();
253
+ if (tokenChanged) {
254
+ this.notifyAuthStateChange(event);
231
255
  }
232
256
  }
233
257
  /**
234
258
  * Get current session
235
259
  */
236
260
  getSession() {
237
- if (!this.accessToken || !this.user) return null;
261
+ if (!this.accessToken || !this.user) {
262
+ return null;
263
+ }
238
264
  return {
239
265
  accessToken: this.accessToken,
240
266
  user: this.user
@@ -249,11 +275,11 @@ var TokenManager = class {
249
275
  /**
250
276
  * Set access token
251
277
  */
252
- setAccessToken(token) {
278
+ setAccessToken(token, event = AuthChangeEvent.SIGNED_IN) {
253
279
  const tokenChanged = token !== this.accessToken;
254
280
  this.accessToken = token;
255
- if (tokenChanged && this.onTokenChange) {
256
- this.onTokenChange();
281
+ if (tokenChanged) {
282
+ this.notifyAuthStateChange(event);
257
283
  }
258
284
  }
259
285
  /**
@@ -275,22 +301,74 @@ var TokenManager = class {
275
301
  const hadToken = this.accessToken !== null;
276
302
  this.accessToken = null;
277
303
  this.user = null;
278
- if (hadToken && this.onTokenChange) {
279
- this.onTokenChange();
304
+ if (hadToken) {
305
+ this.notifyAuthStateChange(AuthChangeEvent.SIGNED_OUT);
306
+ }
307
+ }
308
+ onAuthStateChange(callback) {
309
+ const id = Symbol("auth-state-change");
310
+ this.authStateChangeCallbacks.set(id, callback);
311
+ return () => this.authStateChangeCallbacks.delete(id);
312
+ }
313
+ notifyAuthStateChange(event) {
314
+ for (const callback of this.authStateChangeCallbacks.values()) {
315
+ try {
316
+ callback(event);
317
+ } catch (error) {
318
+ console.error("Error in auth state change callback:", error);
319
+ }
280
320
  }
281
321
  }
282
322
  };
283
323
 
324
+ // src/lib/jwt.ts
325
+ function decodeBase64Url(input) {
326
+ const normalized = input.replace(/-/g, "+").replace(/_/g, "/");
327
+ const padded = normalized.padEnd(normalized.length + (4 - normalized.length % 4) % 4, "=");
328
+ const binary = atob(padded);
329
+ const bytes = Uint8Array.from(binary, (char) => char.charCodeAt(0));
330
+ return new TextDecoder().decode(bytes);
331
+ }
332
+ function getJwtExpiration(token) {
333
+ if (!token) {
334
+ return null;
335
+ }
336
+ const [, payload] = token.split(".");
337
+ if (!payload) {
338
+ return null;
339
+ }
340
+ try {
341
+ const parsed = JSON.parse(decodeBase64Url(payload));
342
+ if (typeof parsed.exp !== "number" || !Number.isFinite(parsed.exp)) {
343
+ return null;
344
+ }
345
+ return new Date(parsed.exp * 1e3);
346
+ } catch {
347
+ return null;
348
+ }
349
+ }
350
+ function isJwtExpiredOrExpiring(token, leewaySeconds = 60) {
351
+ if (!token) {
352
+ return false;
353
+ }
354
+ const expires = getJwtExpiration(token);
355
+ if (!expires) {
356
+ return true;
357
+ }
358
+ return expires.getTime() <= Date.now() + leewaySeconds * 1e3;
359
+ }
360
+
284
361
  // src/lib/http-client.ts
285
362
  var RETRYABLE_STATUS_CODES = /* @__PURE__ */ new Set([500, 502, 503, 504]);
286
363
  var IDEMPOTENT_METHODS = /* @__PURE__ */ new Set(["GET", "HEAD", "PUT", "DELETE", "OPTIONS"]);
287
- var REFRESHABLE_AUTH_ERROR_CODES = /* @__PURE__ */ new Set([
288
- "AUTH_UNAUTHORIZED",
289
- "PGRST301"
290
- ]);
364
+ var REFRESHABLE_AUTH_ERROR_CODES = /* @__PURE__ */ new Set(["AUTH_UNAUTHORIZED", "PGRST301"]);
291
365
  function serializeBody(method, body, headers) {
292
- if (body === void 0) return void 0;
293
- if (method === "GET" || method === "HEAD") return void 0;
366
+ if (body === void 0) {
367
+ return void 0;
368
+ }
369
+ if (method === "GET" || method === "HEAD") {
370
+ return void 0;
371
+ }
294
372
  if (typeof FormData !== "undefined" && body instanceof FormData) {
295
373
  return body;
296
374
  }
@@ -298,7 +376,9 @@ function serializeBody(method, body, headers) {
298
376
  return JSON.stringify(body);
299
377
  }
300
378
  async function parseResponse(response) {
301
- if (response.status === 204) return void 0;
379
+ if (response.status === 204) {
380
+ return void 0;
381
+ }
302
382
  let data;
303
383
  const contentType = response.headers.get("content-type");
304
384
  try {
@@ -400,31 +480,24 @@ var HttpClient = class {
400
480
  return statusCode === 401 && REFRESHABLE_AUTH_ERROR_CODES.has(errorCode ?? "") && !this.config.isServerMode && !this.config.accessToken && !this.config.edgeFunctionToken && !options.skipAuthRefresh && authToken !== null;
401
481
  }
402
482
  async fetchWithRetry(args) {
403
- const {
404
- method,
405
- url,
406
- headers,
407
- body,
408
- fetchOptions,
409
- callerSignal,
410
- maxAttempts
411
- } = args;
483
+ const { method, url, headers, body, fetchOptions, callerSignal, maxAttempts } = args;
412
484
  let lastError;
413
485
  for (let attempt = 0; attempt <= maxAttempts; attempt++) {
414
486
  if (attempt > 0) {
415
487
  const delay = this.computeRetryDelay(attempt);
416
- this.logger.warn(
417
- `Retry ${attempt}/${maxAttempts} for ${method} ${url} in ${delay}ms`
418
- );
419
- if (callerSignal?.aborted) throw callerSignal.reason;
488
+ this.logger.warn(`Retry ${attempt}/${maxAttempts} for ${method} ${url} in ${delay}ms`);
489
+ if (callerSignal?.aborted) {
490
+ throw callerSignal.reason;
491
+ }
420
492
  await new Promise((resolve, reject) => {
421
493
  const onAbort = () => {
422
494
  clearTimeout(timer2);
423
495
  reject(callerSignal.reason);
424
496
  };
425
497
  const timer2 = setTimeout(() => {
426
- if (callerSignal)
498
+ if (callerSignal) {
427
499
  callerSignal.removeEventListener("abort", onAbort);
500
+ }
428
501
  resolve();
429
502
  }, delay);
430
503
  if (callerSignal) {
@@ -466,7 +539,9 @@ var HttpClient = class {
466
539
  ...controller ? { signal: controller.signal } : {}
467
540
  });
468
541
  if (this.isRetryableStatus(response.status) && attempt < maxAttempts) {
469
- if (timer !== void 0) clearTimeout(timer);
542
+ if (timer !== void 0) {
543
+ clearTimeout(timer);
544
+ }
470
545
  await response.body?.cancel();
471
546
  lastError = new InsForgeError(
472
547
  `Server error: ${response.status} ${response.statusText}`,
@@ -475,10 +550,14 @@ var HttpClient = class {
475
550
  );
476
551
  continue;
477
552
  }
478
- if (timer !== void 0) clearTimeout(timer);
553
+ if (timer !== void 0) {
554
+ clearTimeout(timer);
555
+ }
479
556
  return response;
480
557
  } catch (err) {
481
- if (timer !== void 0) clearTimeout(timer);
558
+ if (timer !== void 0) {
559
+ clearTimeout(timer);
560
+ }
482
561
  if (err?.name === "AbortError") {
483
562
  if (controller && controller.signal.aborted && this.timeout > 0 && !callerSignal?.aborted) {
484
563
  throw new InsForgeError(
@@ -500,11 +579,7 @@ var HttpClient = class {
500
579
  );
501
580
  }
502
581
  }
503
- throw lastError || new InsForgeError(
504
- "Request failed after all retry attempts",
505
- 0,
506
- "NETWORK_ERROR"
507
- );
582
+ throw lastError || new InsForgeError("Request failed after all retry attempts", 0, "NETWORK_ERROR");
508
583
  }
509
584
  /**
510
585
  * Performs an HTTP request with automatic retry and timeout handling.
@@ -584,31 +659,15 @@ var HttpClient = class {
584
659
  }
585
660
  throw err;
586
661
  }
587
- this.logger.logResponse(
588
- method,
589
- url,
590
- response.status,
591
- Date.now() - startTime,
592
- data
593
- );
662
+ this.logger.logResponse(method, url, response.status, Date.now() - startTime, data);
594
663
  return data;
595
664
  }
596
665
  async request(method, path, options = {}) {
597
666
  const tokenUsed = this.userToken;
598
667
  try {
599
- return await this.handleRequest(
600
- method,
601
- path,
602
- { ...options },
603
- tokenUsed
604
- );
668
+ return await this.handleRequest(method, path, { ...options }, tokenUsed);
605
669
  } catch (error) {
606
- if (!(error instanceof InsForgeError) || !this.shouldRefreshAccessToken(
607
- error.statusCode,
608
- error.error,
609
- tokenUsed,
610
- options
611
- )) {
670
+ if (!(error instanceof InsForgeError) || !this.shouldRefreshAccessToken(error.statusCode, error.error, tokenUsed, options)) {
612
671
  throw error;
613
672
  }
614
673
  if (tokenUsed !== this.userToken) {
@@ -693,12 +752,7 @@ var HttpClient = class {
693
752
  callerSignal,
694
753
  maxAttempts
695
754
  });
696
- this.logger.logResponse(
697
- method,
698
- url,
699
- response.status,
700
- Date.now() - startTime
701
- );
755
+ this.logger.logResponse(method, url, response.status, Date.now() - startTime);
702
756
  let errorCode = null;
703
757
  if (response.status === 401) {
704
758
  try {
@@ -712,12 +766,7 @@ var HttpClient = class {
712
766
  } catch {
713
767
  }
714
768
  }
715
- if (!this.shouldRefreshAccessToken(
716
- response.status,
717
- errorCode,
718
- tokenUsed,
719
- options
720
- )) {
769
+ if (!this.shouldRefreshAccessToken(response.status, errorCode, tokenUsed, options)) {
721
770
  return response;
722
771
  }
723
772
  if (tokenUsed !== this.userToken) {
@@ -811,10 +860,30 @@ var HttpClient = class {
811
860
  })();
812
861
  return this.refreshPromise;
813
862
  }
863
+ /** Returns a token safe to use for a new connection handshake. */
864
+ async getValidAccessToken(leewaySeconds = 60) {
865
+ const accessToken = this.tokenManager.getAccessToken() ?? this.userToken;
866
+ if (!accessToken || !isJwtExpiredOrExpiring(accessToken, leewaySeconds)) {
867
+ return accessToken;
868
+ }
869
+ const canRefresh = !this.config.isServerMode && !this.config.accessToken && !this.config.edgeFunctionToken && this.userToken !== null;
870
+ if (!canRefresh) {
871
+ return accessToken;
872
+ }
873
+ try {
874
+ const refreshed = await this.refreshAndSaveSession();
875
+ return refreshed.accessToken;
876
+ } catch (error) {
877
+ if (error instanceof InsForgeError && (error.statusCode === 401 || error.statusCode === 403) && this.userToken === accessToken) {
878
+ this.clearAuthSession();
879
+ }
880
+ throw error;
881
+ }
882
+ }
814
883
  async refreshAndSaveSession() {
815
884
  const newTokenData = await this.refreshAccessToken();
816
885
  this.setAuthToken(newTokenData.accessToken);
817
- this.tokenManager.saveSession(newTokenData);
886
+ this.tokenManager.saveSession(newTokenData, AuthChangeEvent.TOKEN_REFRESHED);
818
887
  if (newTokenData.csrfToken) {
819
888
  setCsrfToken(newTokenData.csrfToken);
820
889
  }
@@ -899,7 +968,10 @@ function cleanUrlParams(...params) {
899
968
  }
900
969
 
901
970
  // src/modules/auth/auth.ts
902
- import { ERROR_CODES, oAuthProvidersSchema } from "@insforge/shared-schemas";
971
+ import {
972
+ ERROR_CODES,
973
+ oAuthProvidersSchema
974
+ } from "@insforge/shared-schemas";
903
975
  var Auth = class {
904
976
  constructor(http, tokenManager, options = {}) {
905
977
  this.http = http;
@@ -910,11 +982,15 @@ var Auth = class {
910
982
  isServerMode() {
911
983
  return !!this.options.isServerMode;
912
984
  }
985
+ /** Subscribe to SDK authentication state changes. */
986
+ onAuthStateChange(callback) {
987
+ return this.tokenManager.onAuthStateChange(callback);
988
+ }
913
989
  /**
914
990
  * Save session from API response
915
991
  * Handles token storage, CSRF token, and HTTP auth header
916
992
  */
917
- saveSessionFromResponse(response) {
993
+ saveSessionFromResponse(response, event = AuthChangeEvent.SIGNED_IN) {
918
994
  if (!response.accessToken || !response.user) {
919
995
  return false;
920
996
  }
@@ -926,7 +1002,7 @@ var Auth = class {
926
1002
  setCsrfToken(response.csrfToken);
927
1003
  }
928
1004
  if (!this.isServerMode()) {
929
- this.tokenManager.saveSession(session);
1005
+ this.tokenManager.saveSession(session, event);
930
1006
  }
931
1007
  this.http.setAuthToken(response.accessToken);
932
1008
  this.http.setRefreshToken(response.refreshToken ?? null);
@@ -940,7 +1016,9 @@ var Auth = class {
940
1016
  * Supports PKCE flow (insforge_code)
941
1017
  */
942
1018
  async detectAuthCallback() {
943
- if (this.isServerMode() || typeof window === "undefined") return;
1019
+ if (this.isServerMode() || typeof window === "undefined") {
1020
+ return;
1021
+ }
944
1022
  try {
945
1023
  const params = new URLSearchParams(window.location.search);
946
1024
  const error = params.get("error");
@@ -1048,11 +1126,7 @@ var Auth = class {
1048
1126
  if (!signInOptions || !signInOptions.redirectTo) {
1049
1127
  return {
1050
1128
  data: {},
1051
- error: new InsForgeError(
1052
- "Redirect URI is required",
1053
- 400,
1054
- ERROR_CODES.INVALID_INPUT
1055
- )
1129
+ error: new InsForgeError("Redirect URI is required", 400, ERROR_CODES.INVALID_INPUT)
1056
1130
  };
1057
1131
  }
1058
1132
  const { provider } = signInOptions;
@@ -1127,10 +1201,7 @@ var Auth = class {
1127
1201
  error: null
1128
1202
  };
1129
1203
  } catch (error) {
1130
- return wrapError(
1131
- error,
1132
- "An unexpected error occurred during OAuth code exchange"
1133
- );
1204
+ return wrapError(error, "An unexpected error occurred during OAuth code exchange");
1134
1205
  }
1135
1206
  }
1136
1207
  /**
@@ -1157,10 +1228,7 @@ var Auth = class {
1157
1228
  error: null
1158
1229
  };
1159
1230
  } catch (error) {
1160
- return wrapError(
1161
- error,
1162
- "An unexpected error occurred during ID token sign in"
1163
- );
1231
+ return wrapError(error, "An unexpected error occurred during ID token sign in");
1164
1232
  }
1165
1233
  }
1166
1234
  // ============================================================================
@@ -1201,14 +1269,11 @@ var Auth = class {
1201
1269
  }
1202
1270
  );
1203
1271
  if (response.accessToken) {
1204
- this.saveSessionFromResponse(response);
1272
+ this.saveSessionFromResponse(response, AuthChangeEvent.TOKEN_REFRESHED);
1205
1273
  }
1206
1274
  return { data: response, error: null };
1207
1275
  } catch (error) {
1208
- return wrapError(
1209
- error,
1210
- "An unexpected error occurred during session refresh"
1211
- );
1276
+ return wrapError(error, "An unexpected error occurred during session refresh");
1212
1277
  }
1213
1278
  }
1214
1279
  /**
@@ -1219,11 +1284,11 @@ var Auth = class {
1219
1284
  try {
1220
1285
  if (this.isServerMode()) {
1221
1286
  const accessToken = this.tokenManager.getAccessToken();
1222
- if (!accessToken) return { data: { user: null }, error: null };
1287
+ if (!accessToken) {
1288
+ return { data: { user: null }, error: null };
1289
+ }
1223
1290
  this.http.setAuthToken(accessToken);
1224
- const response = await this.http.get(
1225
- "/api/auth/sessions/current"
1226
- );
1291
+ const response = await this.http.get("/api/auth/sessions/current");
1227
1292
  const user = response.user ?? null;
1228
1293
  return { data: { user }, error: null };
1229
1294
  }
@@ -1261,25 +1326,17 @@ var Auth = class {
1261
1326
  // ============================================================================
1262
1327
  async getProfile(userId) {
1263
1328
  try {
1264
- const response = await this.http.get(
1265
- `/api/auth/profiles/${userId}`
1266
- );
1329
+ const response = await this.http.get(`/api/auth/profiles/${userId}`);
1267
1330
  return { data: response, error: null };
1268
1331
  } catch (error) {
1269
- return wrapError(
1270
- error,
1271
- "An unexpected error occurred while fetching user profile"
1272
- );
1332
+ return wrapError(error, "An unexpected error occurred while fetching user profile");
1273
1333
  }
1274
1334
  }
1275
1335
  async setProfile(profile) {
1276
1336
  try {
1277
- const response = await this.http.patch(
1278
- "/api/auth/profiles/current",
1279
- {
1280
- profile
1281
- }
1282
- );
1337
+ const response = await this.http.patch("/api/auth/profiles/current", {
1338
+ profile
1339
+ });
1283
1340
  const currentUser = this.tokenManager.getUser();
1284
1341
  if (!this.isServerMode() && currentUser && response.profile !== void 0) {
1285
1342
  this.tokenManager.setUser({
@@ -1289,10 +1346,7 @@ var Auth = class {
1289
1346
  }
1290
1347
  return { data: response, error: null };
1291
1348
  } catch (error) {
1292
- return wrapError(
1293
- error,
1294
- "An unexpected error occurred while updating user profile"
1295
- );
1349
+ return wrapError(error, "An unexpected error occurred while updating user profile");
1296
1350
  }
1297
1351
  }
1298
1352
  // ============================================================================
@@ -1305,10 +1359,7 @@ var Auth = class {
1305
1359
  });
1306
1360
  return { data: response, error: null };
1307
1361
  } catch (error) {
1308
- return wrapError(
1309
- error,
1310
- "An unexpected error occurred while sending verification email"
1311
- );
1362
+ return wrapError(error, "An unexpected error occurred while sending verification email");
1312
1363
  }
1313
1364
  }
1314
1365
  async verifyEmail(request) {
@@ -1324,10 +1375,7 @@ var Auth = class {
1324
1375
  }
1325
1376
  return { data: response, error: null };
1326
1377
  } catch (error) {
1327
- return wrapError(
1328
- error,
1329
- "An unexpected error occurred while verifying email"
1330
- );
1378
+ return wrapError(error, "An unexpected error occurred while verifying email");
1331
1379
  }
1332
1380
  }
1333
1381
  // ============================================================================
@@ -1340,10 +1388,7 @@ var Auth = class {
1340
1388
  });
1341
1389
  return { data: response, error: null };
1342
1390
  } catch (error) {
1343
- return wrapError(
1344
- error,
1345
- "An unexpected error occurred while sending password reset email"
1346
- );
1391
+ return wrapError(error, "An unexpected error occurred while sending password reset email");
1347
1392
  }
1348
1393
  }
1349
1394
  async exchangeResetPasswordToken(request) {
@@ -1355,10 +1400,7 @@ var Auth = class {
1355
1400
  );
1356
1401
  return { data: response, error: null };
1357
1402
  } catch (error) {
1358
- return wrapError(
1359
- error,
1360
- "An unexpected error occurred while verifying reset code"
1361
- );
1403
+ return wrapError(error, "An unexpected error occurred while verifying reset code");
1362
1404
  }
1363
1405
  }
1364
1406
  async resetPassword(request) {
@@ -1370,10 +1412,7 @@ var Auth = class {
1370
1412
  );
1371
1413
  return { data: response, error: null };
1372
1414
  } catch (error) {
1373
- return wrapError(
1374
- error,
1375
- "An unexpected error occurred while resetting password"
1376
- );
1415
+ return wrapError(error, "An unexpected error occurred while resetting password");
1377
1416
  }
1378
1417
  }
1379
1418
  // ============================================================================
@@ -1381,16 +1420,12 @@ var Auth = class {
1381
1420
  // ============================================================================
1382
1421
  async getPublicAuthConfig() {
1383
1422
  try {
1384
- const response = await this.http.get(
1385
- "/api/auth/public-config",
1386
- { skipAuthRefresh: true }
1387
- );
1423
+ const response = await this.http.get("/api/auth/public-config", {
1424
+ skipAuthRefresh: true
1425
+ });
1388
1426
  return { data: response, error: null };
1389
1427
  } catch (error) {
1390
- return wrapError(
1391
- error,
1392
- "An unexpected error occurred while fetching auth configuration"
1393
- );
1428
+ return wrapError(error, "An unexpected error occurred while fetching auth configuration");
1394
1429
  }
1395
1430
  }
1396
1431
  };
@@ -1548,11 +1583,7 @@ var StorageBucket = class {
1548
1583
  } catch (error) {
1549
1584
  return {
1550
1585
  data: null,
1551
- error: error instanceof InsForgeError ? error : new InsForgeError(
1552
- "Upload failed",
1553
- 500,
1554
- "STORAGE_ERROR"
1555
- )
1586
+ error: error instanceof InsForgeError ? error : new InsForgeError("Upload failed", 500, "STORAGE_ERROR")
1556
1587
  };
1557
1588
  }
1558
1589
  }
@@ -1598,11 +1629,7 @@ var StorageBucket = class {
1598
1629
  } catch (error) {
1599
1630
  return {
1600
1631
  data: null,
1601
- error: error instanceof InsForgeError ? error : new InsForgeError(
1602
- "Upload failed",
1603
- 500,
1604
- "STORAGE_ERROR"
1605
- )
1632
+ error: error instanceof InsForgeError ? error : new InsForgeError("Upload failed", 500, "STORAGE_ERROR")
1606
1633
  };
1607
1634
  }
1608
1635
  }
@@ -1630,13 +1657,10 @@ var StorageBucket = class {
1630
1657
  );
1631
1658
  }
1632
1659
  if (strategy.confirmRequired && strategy.confirmUrl) {
1633
- const confirmResponse = await this.http.post(
1634
- strategy.confirmUrl,
1635
- {
1636
- size: file.size,
1637
- contentType: file.type || "application/octet-stream"
1638
- }
1639
- );
1660
+ const confirmResponse = await this.http.post(strategy.confirmUrl, {
1661
+ size: file.size,
1662
+ contentType: file.type || "application/octet-stream"
1663
+ });
1640
1664
  return { data: confirmResponse, error: null };
1641
1665
  }
1642
1666
  return {
@@ -1651,11 +1675,7 @@ var StorageBucket = class {
1651
1675
  error: null
1652
1676
  };
1653
1677
  } catch (error) {
1654
- throw error instanceof InsForgeError ? error : new InsForgeError(
1655
- "Presigned upload failed",
1656
- 500,
1657
- "STORAGE_ERROR"
1658
- );
1678
+ throw error instanceof InsForgeError ? error : new InsForgeError("Presigned upload failed", 500, "STORAGE_ERROR");
1659
1679
  }
1660
1680
  }
1661
1681
  /**
@@ -1709,11 +1729,7 @@ var StorageBucket = class {
1709
1729
  } catch (error) {
1710
1730
  return {
1711
1731
  data: null,
1712
- error: error instanceof InsForgeError ? error : new InsForgeError(
1713
- "Download failed",
1714
- 500,
1715
- "STORAGE_ERROR"
1716
- )
1732
+ error: error instanceof InsForgeError ? error : new InsForgeError("Download failed", 500, "STORAGE_ERROR")
1717
1733
  };
1718
1734
  }
1719
1735
  }
@@ -1748,7 +1764,9 @@ var StorageBucket = class {
1748
1764
  } catch (error) {
1749
1765
  const status = error instanceof InsForgeError ? error.statusCode : void 0;
1750
1766
  const isMissingRoute = (status === 404 || status === 405) && !(error instanceof InsForgeError && error.error === "STORAGE_NOT_FOUND");
1751
- if (!isMissingRoute) throw error;
1767
+ if (!isMissingRoute) {
1768
+ throw error;
1769
+ }
1752
1770
  return await this.http.post(
1753
1771
  `/api/storage/buckets/${this.bucketName}/objects/${encoded}/download-strategy`,
1754
1772
  { expiresIn }
@@ -1824,10 +1842,18 @@ var StorageBucket = class {
1824
1842
  async list(options) {
1825
1843
  try {
1826
1844
  const params = {};
1827
- if (options?.prefix) params.prefix = options.prefix;
1828
- if (options?.search) params.search = options.search;
1829
- if (options?.limit) params.limit = options.limit.toString();
1830
- if (options?.offset) params.offset = options.offset.toString();
1845
+ if (options?.prefix) {
1846
+ params.prefix = options.prefix;
1847
+ }
1848
+ if (options?.search) {
1849
+ params.search = options.search;
1850
+ }
1851
+ if (options?.limit) {
1852
+ params.limit = options.limit.toString();
1853
+ }
1854
+ if (options?.offset) {
1855
+ params.offset = options.offset.toString();
1856
+ }
1831
1857
  const response = await this.http.get(
1832
1858
  `/api/storage/buckets/${this.bucketName}/objects`,
1833
1859
  { params }
@@ -1836,11 +1862,7 @@ var StorageBucket = class {
1836
1862
  } catch (error) {
1837
1863
  return {
1838
1864
  data: null,
1839
- error: error instanceof InsForgeError ? error : new InsForgeError(
1840
- "List failed",
1841
- 500,
1842
- "STORAGE_ERROR"
1843
- )
1865
+ error: error instanceof InsForgeError ? error : new InsForgeError("List failed", 500, "STORAGE_ERROR")
1844
1866
  };
1845
1867
  }
1846
1868
  }
@@ -1857,11 +1879,7 @@ var StorageBucket = class {
1857
1879
  } catch (error) {
1858
1880
  return {
1859
1881
  data: null,
1860
- error: error instanceof InsForgeError ? error : new InsForgeError(
1861
- "Delete failed",
1862
- 500,
1863
- "STORAGE_ERROR"
1864
- )
1882
+ error: error instanceof InsForgeError ? error : new InsForgeError("Delete failed", 500, "STORAGE_ERROR")
1865
1883
  };
1866
1884
  }
1867
1885
  }
@@ -1983,14 +2001,11 @@ var ChatCompletions = class {
1983
2001
  if (params.stream) {
1984
2002
  const headers = this.http.getHeaders();
1985
2003
  headers["Content-Type"] = "application/json";
1986
- const response2 = await this.http.fetch(
1987
- `${this.http.baseUrl}/api/ai/chat/completion`,
1988
- {
1989
- method: "POST",
1990
- headers,
1991
- body: JSON.stringify(backendParams)
1992
- }
1993
- );
2004
+ const response2 = await this.http.fetch(`${this.http.baseUrl}/api/ai/chat/completion`, {
2005
+ method: "POST",
2006
+ headers,
2007
+ body: JSON.stringify(backendParams)
2008
+ });
1994
2009
  if (!response2.ok) {
1995
2010
  const error = await response2.json();
1996
2011
  throw new Error(error.error || "Stream request failed");
@@ -2038,7 +2053,9 @@ var ChatCompletions = class {
2038
2053
  try {
2039
2054
  while (true) {
2040
2055
  const { done, value } = await reader.read();
2041
- if (done) break;
2056
+ if (done) {
2057
+ break;
2058
+ }
2042
2059
  buffer += decoder.decode(value, { stream: true });
2043
2060
  const lines = buffer.split("\n");
2044
2061
  buffer = lines.pop() || "";
@@ -2086,7 +2103,7 @@ var ChatCompletions = class {
2086
2103
  reader.releaseLock();
2087
2104
  return;
2088
2105
  }
2089
- } catch (e) {
2106
+ } catch {
2090
2107
  console.warn("Failed to parse SSE data:", dataStr);
2091
2108
  }
2092
2109
  }
@@ -2139,10 +2156,7 @@ var Embeddings = class {
2139
2156
  * ```
2140
2157
  */
2141
2158
  async create(params) {
2142
- const response = await this.http.post(
2143
- "/api/ai/embeddings",
2144
- params
2145
- );
2159
+ const response = await this.http.post("/api/ai/embeddings", params);
2146
2160
  return {
2147
2161
  object: response.object,
2148
2162
  data: response.data,
@@ -2228,7 +2242,9 @@ var Functions = class _Functions {
2228
2242
  static deriveSubhostingUrl(baseUrl) {
2229
2243
  try {
2230
2244
  const { hostname } = new URL(baseUrl);
2231
- if (!hostname.endsWith(".insforge.app")) return void 0;
2245
+ if (!hostname.endsWith(".insforge.app")) {
2246
+ return void 0;
2247
+ }
2232
2248
  const appKey = hostname.split(".")[0];
2233
2249
  return `https://${appKey}.functions.insforge.app`;
2234
2250
  } catch {
@@ -2274,7 +2290,9 @@ var Functions = class _Functions {
2274
2290
  const data = await parseResponse(res);
2275
2291
  return { data, error: null };
2276
2292
  } catch (error) {
2277
- if (error instanceof Error && error.name === "AbortError") throw error;
2293
+ if (error instanceof Error && error.name === "AbortError") {
2294
+ throw error;
2295
+ }
2278
2296
  return {
2279
2297
  data: null,
2280
2298
  error: error instanceof InsForgeError ? error : new InsForgeError(
@@ -2293,7 +2311,9 @@ var Functions = class _Functions {
2293
2311
  });
2294
2312
  return { data, error: null };
2295
2313
  } catch (error) {
2296
- if (error instanceof Error && error.name === "AbortError") throw error;
2314
+ if (error instanceof Error && error.name === "AbortError") {
2315
+ throw error;
2316
+ }
2297
2317
  if (error instanceof InsForgeError && error.statusCode === 404) {
2298
2318
  } else {
2299
2319
  return {
@@ -2312,7 +2332,9 @@ var Functions = class _Functions {
2312
2332
  const data = await this.http.request(method, path, { body, headers });
2313
2333
  return { data, error: null };
2314
2334
  } catch (error) {
2315
- if (error instanceof Error && error.name === "AbortError") throw error;
2335
+ if (error instanceof Error && error.name === "AbortError") {
2336
+ throw error;
2337
+ }
2316
2338
  return {
2317
2339
  data: null,
2318
2340
  error: error instanceof InsForgeError ? error : new InsForgeError(
@@ -2327,32 +2349,37 @@ var Functions = class _Functions {
2327
2349
 
2328
2350
  // src/modules/realtime.ts
2329
2351
  var CONNECT_TIMEOUT = 1e4;
2352
+ var SUBSCRIBE_TIMEOUT = 1e4;
2330
2353
  var Realtime = class {
2331
- constructor(baseUrl, tokenManager, anonKey) {
2332
- this.socket = null;
2333
- this.connectPromise = null;
2334
- this.subscribedChannels = /* @__PURE__ */ new Set();
2335
- this.eventListeners = /* @__PURE__ */ new Map();
2354
+ constructor(baseUrl, tokenManager, anonKey, getValidAccessToken = async () => tokenManager.getAccessToken()) {
2336
2355
  this.baseUrl = baseUrl;
2337
2356
  this.tokenManager = tokenManager;
2338
2357
  this.anonKey = anonKey;
2339
- this.tokenManager.onTokenChange = () => this.onTokenChange();
2358
+ this.getValidAccessToken = getValidAccessToken;
2359
+ this.socket = null;
2360
+ this.connectPromise = null;
2361
+ this.connectionAttempt = null;
2362
+ this.nextConnectionAttemptId = 0;
2363
+ this.subscriptions = /* @__PURE__ */ new Map();
2364
+ this.eventListeners = /* @__PURE__ */ new Map();
2365
+ this.tokenManager.onAuthStateChange((event) => {
2366
+ if (event !== AuthChangeEvent.TOKEN_REFRESHED) {
2367
+ this.reconnectForAuthChange();
2368
+ }
2369
+ });
2340
2370
  }
2341
2371
  notifyListeners(event, payload) {
2342
- const listeners = this.eventListeners.get(event);
2343
- if (!listeners) return;
2344
- for (const cb of listeners) {
2372
+ for (const callback of this.eventListeners.get(event) ?? []) {
2345
2373
  try {
2346
- cb(payload);
2347
- } catch (err) {
2348
- console.error(`Error in ${event} callback:`, err);
2374
+ callback(payload);
2375
+ } catch (error) {
2376
+ console.error(`Error in ${event} callback:`, error);
2349
2377
  }
2350
2378
  }
2351
2379
  }
2352
- /**
2353
- * Connect to the realtime server
2354
- * @returns Promise that resolves when connected
2355
- */
2380
+ async getHandshakeToken() {
2381
+ return await this.getValidAccessToken() ?? this.anonKey ?? null;
2382
+ }
2356
2383
  connect() {
2357
2384
  if (this.socket?.connected) {
2358
2385
  return Promise.resolve();
@@ -2360,210 +2387,324 @@ var Realtime = class {
2360
2387
  if (this.connectPromise) {
2361
2388
  return this.connectPromise;
2362
2389
  }
2363
- this.connectPromise = (async () => {
2364
- try {
2365
- const { io } = await import("socket.io-client");
2366
- await new Promise((resolve, reject) => {
2367
- const token = this.tokenManager.getAccessToken() ?? this.anonKey;
2368
- this.socket = io(this.baseUrl, {
2369
- transports: ["websocket"],
2370
- auth: token ? { token } : void 0
2371
- });
2372
- let initialConnection = true;
2373
- let timeoutId = null;
2374
- const cleanup = () => {
2375
- if (timeoutId) {
2376
- clearTimeout(timeoutId);
2377
- timeoutId = null;
2378
- }
2379
- };
2380
- timeoutId = setTimeout(() => {
2381
- if (initialConnection) {
2382
- initialConnection = false;
2383
- this.connectPromise = null;
2384
- this.socket?.disconnect();
2385
- this.socket = null;
2386
- reject(new Error(`Connection timeout after ${CONNECT_TIMEOUT}ms`));
2387
- }
2388
- }, CONNECT_TIMEOUT);
2389
- this.socket.on("connect", () => {
2390
- cleanup();
2391
- for (const channel of this.subscribedChannels) {
2392
- this.socket.emit("realtime:subscribe", { channel });
2393
- }
2394
- this.notifyListeners("connect");
2395
- if (initialConnection) {
2396
- initialConnection = false;
2397
- this.connectPromise = null;
2398
- resolve();
2399
- }
2400
- });
2401
- this.socket.on("connect_error", (error) => {
2402
- cleanup();
2403
- this.notifyListeners("connect_error", error);
2404
- if (initialConnection) {
2405
- initialConnection = false;
2406
- this.connectPromise = null;
2407
- reject(error);
2408
- }
2409
- });
2410
- this.socket.on("disconnect", (reason) => {
2411
- this.notifyListeners("disconnect", reason);
2412
- });
2413
- this.socket.on("realtime:error", (error) => {
2414
- this.notifyListeners("error", error);
2415
- });
2416
- this.socket.onAny((event, message) => {
2417
- if (event === "realtime:error") return;
2418
- this.notifyListeners(event, message);
2419
- });
2390
+ const attemptId = ++this.nextConnectionAttemptId;
2391
+ const connection = (async () => {
2392
+ const { io } = await import("socket.io-client");
2393
+ if (attemptId !== this.nextConnectionAttemptId) {
2394
+ throw new Error("Connection cancelled");
2395
+ }
2396
+ await new Promise((resolve, reject) => {
2397
+ const socket = io(this.baseUrl, {
2398
+ transports: ["websocket"],
2399
+ auth: (callback) => {
2400
+ void this.getHandshakeToken().then(
2401
+ (token) => callback(token ? { token } : {}),
2402
+ () => callback({})
2403
+ );
2404
+ }
2420
2405
  });
2421
- } catch (error) {
2406
+ this.socket = socket;
2407
+ let initialConnection = true;
2408
+ let timeoutId = null;
2409
+ const clearConnectTimeout = () => {
2410
+ if (timeoutId) {
2411
+ clearTimeout(timeoutId);
2412
+ timeoutId = null;
2413
+ }
2414
+ };
2415
+ const dispose = () => {
2416
+ clearConnectTimeout();
2417
+ socket.off("connect", onConnect);
2418
+ socket.off("connect_error", onConnectError);
2419
+ socket.off("disconnect", onDisconnect);
2420
+ socket.off("realtime:error", onRealtimeError);
2421
+ socket.offAny(onAny);
2422
+ socket.disconnect();
2423
+ if (this.socket === socket) {
2424
+ this.socket = null;
2425
+ }
2426
+ if (this.connectionAttempt?.id === attemptId) {
2427
+ this.connectionAttempt = null;
2428
+ }
2429
+ };
2430
+ const fail = (error) => {
2431
+ if (!initialConnection) {
2432
+ return;
2433
+ }
2434
+ initialConnection = false;
2435
+ dispose();
2436
+ reject(error);
2437
+ };
2438
+ const onConnect = () => {
2439
+ if (this.socket !== socket) {
2440
+ return;
2441
+ }
2442
+ clearConnectTimeout();
2443
+ this.resubscribeChannels();
2444
+ this.notifyListeners("connect");
2445
+ if (initialConnection) {
2446
+ initialConnection = false;
2447
+ if (this.connectionAttempt?.id === attemptId) {
2448
+ this.connectionAttempt = null;
2449
+ }
2450
+ resolve();
2451
+ }
2452
+ };
2453
+ const onConnectError = (error) => {
2454
+ clearConnectTimeout();
2455
+ this.notifyListeners("connect_error", error);
2456
+ if (initialConnection) {
2457
+ fail(error);
2458
+ }
2459
+ };
2460
+ const onDisconnect = (reason) => {
2461
+ this.handleDisconnect(reason);
2462
+ };
2463
+ const onRealtimeError = (error) => {
2464
+ this.notifyListeners("error", error);
2465
+ };
2466
+ const onAny = (event, message) => {
2467
+ if (event === "realtime:error") {
2468
+ return;
2469
+ }
2470
+ this.applyPresenceEvent(event, message);
2471
+ this.notifyListeners(event, message);
2472
+ };
2473
+ this.connectionAttempt = { id: attemptId, socket, cancel: fail };
2474
+ socket.on("connect", onConnect);
2475
+ socket.on("connect_error", onConnectError);
2476
+ socket.on("disconnect", onDisconnect);
2477
+ socket.on("realtime:error", onRealtimeError);
2478
+ socket.onAny(onAny);
2479
+ timeoutId = setTimeout(
2480
+ () => fail(new Error(`Connection timeout after ${CONNECT_TIMEOUT}ms`)),
2481
+ CONNECT_TIMEOUT
2482
+ );
2483
+ });
2484
+ })();
2485
+ const trackedConnection = connection.finally(() => {
2486
+ if (this.connectPromise === trackedConnection) {
2422
2487
  this.connectPromise = null;
2423
- throw error;
2424
2488
  }
2425
- })();
2426
- return this.connectPromise;
2489
+ });
2490
+ this.connectPromise = trackedConnection;
2491
+ return trackedConnection;
2427
2492
  }
2428
- /**
2429
- * Disconnect from the realtime server
2430
- */
2431
2493
  disconnect() {
2432
- if (this.socket) {
2433
- this.socket.disconnect();
2434
- this.socket = null;
2494
+ this.nextConnectionAttemptId++;
2495
+ this.connectionAttempt?.cancel(new Error("Disconnected"));
2496
+ this.socket?.disconnect();
2497
+ this.socket = null;
2498
+ this.connectPromise = null;
2499
+ for (const subscription of this.subscriptions.values()) {
2500
+ this.settleSubscription(
2501
+ subscription,
2502
+ {
2503
+ ok: false,
2504
+ channel: subscription.channel,
2505
+ error: { code: "DISCONNECTED", message: "Disconnected" }
2506
+ },
2507
+ false
2508
+ );
2435
2509
  }
2436
- this.subscribedChannels.clear();
2510
+ this.subscriptions.clear();
2437
2511
  }
2438
- /**
2439
- * Handle token changes (e.g., after auth refresh)
2440
- * Updates socket auth so reconnects use the new token
2441
- * If connected, triggers reconnect to apply new token immediately
2442
- */
2443
- onTokenChange() {
2444
- const token = this.tokenManager.getAccessToken() ?? this.anonKey;
2445
- if (this.socket) {
2446
- this.socket.auth = token ? { token } : {};
2512
+ reconnectForAuthChange() {
2513
+ for (const subscription of this.subscriptions.values()) {
2514
+ if (subscription.status === "rejected") {
2515
+ subscription.status = "pending";
2516
+ }
2447
2517
  }
2448
- if (this.socket && (this.socket.connected || this.connectPromise)) {
2449
- this.socket.disconnect();
2450
- this.socket.connect();
2518
+ if (!this.socket) {
2519
+ return;
2520
+ }
2521
+ this.socket.disconnect();
2522
+ this.socket.connect();
2523
+ }
2524
+ handleDisconnect(reason) {
2525
+ for (const subscription of this.subscriptions.values()) {
2526
+ if (subscription.status === "rejected") {
2527
+ continue;
2528
+ }
2529
+ subscription.status = "pending";
2530
+ this.settleSubscription(
2531
+ subscription,
2532
+ {
2533
+ ok: false,
2534
+ channel: subscription.channel,
2535
+ error: { code: "DISCONNECTED", message: "Connection lost before subscription completed" }
2536
+ },
2537
+ true
2538
+ );
2539
+ }
2540
+ this.notifyListeners("disconnect", reason);
2541
+ }
2542
+ resubscribeChannels() {
2543
+ for (const [channel, subscription] of this.subscriptions) {
2544
+ if (subscription.status === "pending") {
2545
+ this.requestSubscription(channel, subscription);
2546
+ }
2547
+ }
2548
+ }
2549
+ requestSubscription(channel, subscription) {
2550
+ if (subscription.pending) {
2551
+ return subscription.pending;
2552
+ }
2553
+ const socket = this.socket;
2554
+ if (!socket?.connected) {
2555
+ return Promise.resolve({
2556
+ ok: false,
2557
+ channel,
2558
+ error: { code: "CONNECTION_FAILED", message: "Not connected to realtime server" }
2559
+ });
2560
+ }
2561
+ subscription.status = "pending";
2562
+ const epoch = ++subscription.epoch;
2563
+ let timeoutId;
2564
+ subscription.pending = new Promise((resolve) => {
2565
+ subscription.settlePending = (response) => {
2566
+ if (timeoutId) {
2567
+ clearTimeout(timeoutId);
2568
+ }
2569
+ subscription.pending = void 0;
2570
+ subscription.settlePending = void 0;
2571
+ resolve(response);
2572
+ };
2573
+ timeoutId = setTimeout(() => {
2574
+ if (this.subscriptions.get(channel) === subscription && subscription.epoch === epoch) {
2575
+ this.settleSubscription(
2576
+ subscription,
2577
+ {
2578
+ ok: false,
2579
+ channel,
2580
+ error: {
2581
+ code: "SUBSCRIBE_TIMEOUT",
2582
+ message: "Subscription acknowledgement timed out"
2583
+ }
2584
+ },
2585
+ true
2586
+ );
2587
+ }
2588
+ }, SUBSCRIBE_TIMEOUT);
2589
+ socket.emit("realtime:subscribe", { channel }, (response) => {
2590
+ if (this.subscriptions.get(channel) !== subscription || subscription.epoch !== epoch) {
2591
+ return;
2592
+ }
2593
+ if (response.ok) {
2594
+ subscription.status = "subscribed";
2595
+ subscription.members = new Map(
2596
+ response.presence.members.map((member) => [member.presenceId, member])
2597
+ );
2598
+ } else {
2599
+ subscription.status = "rejected";
2600
+ subscription.members.clear();
2601
+ }
2602
+ this.settleSubscription(subscription, response, false);
2603
+ });
2604
+ });
2605
+ return subscription.pending;
2606
+ }
2607
+ settleSubscription(subscription, response, incrementEpoch) {
2608
+ if (incrementEpoch) {
2609
+ subscription.epoch++;
2610
+ }
2611
+ subscription.settlePending?.(response);
2612
+ }
2613
+ applyPresenceEvent(event, message) {
2614
+ if (event !== "presence:join" && event !== "presence:leave") {
2615
+ return;
2616
+ }
2617
+ const presenceEvent = message;
2618
+ const channel = presenceEvent.meta?.channel;
2619
+ const member = presenceEvent.member;
2620
+ if (!channel || !member) {
2621
+ return;
2622
+ }
2623
+ const subscription = this.subscriptions.get(channel);
2624
+ if (!subscription) {
2625
+ return;
2626
+ }
2627
+ if (event === "presence:join") {
2628
+ subscription.members.set(member.presenceId, member);
2629
+ } else {
2630
+ subscription.members.delete(member.presenceId);
2451
2631
  }
2452
2632
  }
2453
- /**
2454
- * Check if connected to the realtime server
2455
- */
2456
2633
  get isConnected() {
2457
2634
  return this.socket?.connected ?? false;
2458
2635
  }
2459
- /**
2460
- * Get the current connection state
2461
- */
2462
2636
  get connectionState() {
2463
- if (!this.socket) return "disconnected";
2464
- if (this.socket.connected) return "connected";
2465
- return "connecting";
2637
+ if (!this.socket) {
2638
+ return "disconnected";
2639
+ }
2640
+ return this.socket.connected ? "connected" : "connecting";
2466
2641
  }
2467
- /**
2468
- * Get the socket ID (if connected)
2469
- */
2470
2642
  get socketId() {
2471
2643
  return this.socket?.id;
2472
2644
  }
2473
- /**
2474
- * Subscribe to a channel
2475
- *
2476
- * Automatically connects if not already connected.
2477
- *
2478
- * @param channel - Channel name (e.g., 'orders:123', 'broadcast')
2479
- * @returns Promise with the subscription response
2480
- */
2481
2645
  async subscribe(channel) {
2482
- if (this.subscribedChannels.has(channel)) {
2483
- return { ok: true, channel, presence: { members: [] } };
2646
+ let subscription = this.subscriptions.get(channel);
2647
+ if (subscription) {
2648
+ if (subscription.pending) {
2649
+ return subscription.pending;
2650
+ }
2651
+ if (subscription.status === "subscribed") {
2652
+ return { ok: true, channel, presence: { members: [...subscription.members.values()] } };
2653
+ }
2654
+ } else {
2655
+ subscription = { channel, epoch: 0, status: "pending", members: /* @__PURE__ */ new Map() };
2656
+ this.subscriptions.set(channel, subscription);
2484
2657
  }
2485
2658
  if (!this.socket?.connected) {
2486
2659
  try {
2487
2660
  await this.connect();
2488
2661
  } catch (error) {
2662
+ if (this.subscriptions.get(channel) === subscription) {
2663
+ this.subscriptions.delete(channel);
2664
+ }
2489
2665
  const message = error instanceof Error ? error.message : "Connection failed";
2490
2666
  return { ok: false, channel, error: { code: "CONNECTION_FAILED", message } };
2491
2667
  }
2492
2668
  }
2493
- return new Promise((resolve) => {
2494
- this.socket.emit("realtime:subscribe", { channel }, (response) => {
2495
- if (response.ok) {
2496
- this.subscribedChannels.add(channel);
2497
- }
2498
- resolve(response);
2499
- });
2500
- });
2669
+ return subscription.pending ?? this.requestSubscription(channel, subscription);
2501
2670
  }
2502
- /**
2503
- * Unsubscribe from a channel (fire-and-forget)
2504
- *
2505
- * @param channel - Channel name to unsubscribe from
2506
- */
2507
2671
  unsubscribe(channel) {
2508
- this.subscribedChannels.delete(channel);
2672
+ const subscription = this.subscriptions.get(channel);
2673
+ if (!subscription) {
2674
+ return;
2675
+ }
2676
+ this.subscriptions.delete(channel);
2677
+ this.settleSubscription(
2678
+ subscription,
2679
+ {
2680
+ ok: false,
2681
+ channel,
2682
+ error: { code: "SUBSCRIPTION_CANCELLED", message: "Subscription cancelled" }
2683
+ },
2684
+ true
2685
+ );
2509
2686
  if (this.socket?.connected) {
2510
2687
  this.socket.emit("realtime:unsubscribe", { channel });
2511
2688
  }
2512
2689
  }
2513
- /**
2514
- * Publish a message to a channel
2515
- *
2516
- * @param channel - Channel name
2517
- * @param event - Event name
2518
- * @param payload - Message payload
2519
- */
2520
2690
  async publish(channel, event, payload) {
2521
2691
  if (!this.socket?.connected) {
2522
2692
  throw new Error("Not connected to realtime server. Call connect() first.");
2523
2693
  }
2524
2694
  this.socket.emit("realtime:publish", { channel, event, payload });
2525
2695
  }
2526
- /**
2527
- * Listen for events
2528
- *
2529
- * Reserved event names:
2530
- * - 'connect' - Fired when connected to the server
2531
- * - 'connect_error' - Fired when connection fails (payload: Error)
2532
- * - 'disconnect' - Fired when disconnected (payload: reason string)
2533
- * - 'error' - Fired when a realtime error occurs (payload: RealtimeErrorPayload)
2534
- *
2535
- * All other events receive a `SocketMessage` payload with metadata.
2536
- *
2537
- * @param event - Event name to listen for
2538
- * @param callback - Callback function when event is received
2539
- */
2540
2696
  on(event, callback) {
2541
- if (!this.eventListeners.has(event)) {
2542
- this.eventListeners.set(event, /* @__PURE__ */ new Set());
2543
- }
2544
- this.eventListeners.get(event).add(callback);
2697
+ const listeners = this.eventListeners.get(event) ?? /* @__PURE__ */ new Set();
2698
+ listeners.add(callback);
2699
+ this.eventListeners.set(event, listeners);
2545
2700
  }
2546
- /**
2547
- * Remove a listener for a specific event
2548
- *
2549
- * @param event - Event name
2550
- * @param callback - The callback function to remove
2551
- */
2552
2701
  off(event, callback) {
2553
2702
  const listeners = this.eventListeners.get(event);
2554
- if (listeners) {
2555
- listeners.delete(callback);
2556
- if (listeners.size === 0) {
2557
- this.eventListeners.delete(event);
2558
- }
2703
+ listeners?.delete(callback);
2704
+ if (listeners?.size === 0) {
2705
+ this.eventListeners.delete(event);
2559
2706
  }
2560
2707
  }
2561
- /**
2562
- * Listen for an event only once, then automatically remove the listener
2563
- *
2564
- * @param event - Event name to listen for
2565
- * @param callback - Callback function when event is received
2566
- */
2567
2708
  once(event, callback) {
2568
2709
  const wrapper = (payload) => {
2569
2710
  this.off(event, wrapper);
@@ -2571,13 +2712,11 @@ var Realtime = class {
2571
2712
  };
2572
2713
  this.on(event, wrapper);
2573
2714
  }
2574
- /**
2575
- * Get all currently subscribed channels
2576
- *
2577
- * @returns Array of channel names
2578
- */
2579
2715
  getSubscribedChannels() {
2580
- return Array.from(this.subscribedChannels);
2716
+ return [...this.subscriptions.values()].filter((subscription) => subscription.status === "subscribed").map((subscription) => subscription.channel);
2717
+ }
2718
+ getPresenceState(channel) {
2719
+ return [...this.subscriptions.get(channel)?.members.values() ?? []];
2581
2720
  }
2582
2721
  };
2583
2722
 
@@ -2592,13 +2731,12 @@ var Emails = class {
2592
2731
  */
2593
2732
  async send(options) {
2594
2733
  try {
2595
- const data = await this.http.post(
2596
- "/api/email/send-raw",
2597
- options
2598
- );
2734
+ const data = await this.http.post("/api/email/send-raw", options);
2599
2735
  return { data, error: null };
2600
2736
  } catch (error) {
2601
- if (error instanceof Error && error.name === "AbortError") throw error;
2737
+ if (error instanceof Error && error.name === "AbortError") {
2738
+ throw error;
2739
+ }
2602
2740
  return {
2603
2741
  data: null,
2604
2742
  error: error instanceof InsForgeError ? error : new InsForgeError(
@@ -2681,10 +2819,7 @@ var RazorpayPayments = class {
2681
2819
  );
2682
2820
  return { data, error: null };
2683
2821
  } catch (error) {
2684
- return wrapError(
2685
- error,
2686
- "Razorpay order creation failed"
2687
- );
2822
+ return wrapError(error, "Razorpay order creation failed");
2688
2823
  }
2689
2824
  }
2690
2825
  async verifyOrder(environment, request) {
@@ -2695,10 +2830,7 @@ var RazorpayPayments = class {
2695
2830
  );
2696
2831
  return { data, error: null };
2697
2832
  } catch (error) {
2698
- return wrapError(
2699
- error,
2700
- "Razorpay order verification failed"
2701
- );
2833
+ return wrapError(error, "Razorpay order verification failed");
2702
2834
  }
2703
2835
  }
2704
2836
  async createSubscription(environment, request) {
@@ -2807,7 +2939,8 @@ var InsForgeClient = class {
2807
2939
  this.realtime = new Realtime(
2808
2940
  this.http.baseUrl,
2809
2941
  this.tokenManager,
2810
- config.anonKey
2942
+ config.anonKey,
2943
+ () => this.http.getValidAccessToken()
2811
2944
  );
2812
2945
  this.emails = new Emails(this.http);
2813
2946
  this.payments = new Payments(this.http);
@@ -2827,32 +2960,35 @@ var InsForgeClient = class {
2827
2960
  /**
2828
2961
  * Set the access token used by every SDK surface. Updates both the HTTP
2829
2962
  * client (database / storage / functions / AI / emails) and the realtime
2830
- * token manager (which fires `onTokenChange` to reconnect the WebSocket
2831
- * with the new bearer). Pass `null` to clear.
2963
+ * token manager. Pass `null` to sign out. By default a token replacement is
2964
+ * treated as a sign-in boundary and reconnects realtime. Pass
2965
+ * `AuthChangeEvent.TOKEN_REFRESHED` for a same-identity refresh to preserve a live socket; the
2966
+ * refreshed token is then used at the next handshake.
2832
2967
  *
2833
2968
  * Use this when an external auth provider (Better Auth, Clerk, Auth0,
2834
2969
  * WorkOS, Kinde, Stytch, …) issues the JWT and you need to keep the
2835
2970
  * long-lived InsForge client in sync. Without this, you'd have to call
2836
2971
  * `client.getHttpClient().setAuthToken(token)` AND reach into the private
2837
- * `client.realtime.tokenManager.setAccessToken(token)` separately
2838
- * forgetting the second one silently breaks realtime auth.
2972
+ * realtime token manager separately.
2839
2973
  *
2840
2974
  * @example
2841
2975
  * ```typescript
2976
+ * import { AuthChangeEvent } from '@insforge/sdk';
2977
+ *
2842
2978
  * // Refresh a third-party-issued JWT periodically
2843
2979
  * const { token } = await fetch('/api/insforge-token').then((r) => r.json());
2844
- * client.setAccessToken(token);
2980
+ * client.setAccessToken(token, AuthChangeEvent.TOKEN_REFRESHED);
2845
2981
  *
2846
2982
  * // Sign-out
2847
2983
  * client.setAccessToken(null);
2848
2984
  * ```
2849
2985
  */
2850
- setAccessToken(token) {
2986
+ setAccessToken(token, event = AuthChangeEvent.SIGNED_IN) {
2851
2987
  this.http.setAuthToken(token);
2852
2988
  if (token === null) {
2853
2989
  this.tokenManager.clearSession();
2854
2990
  } else {
2855
- this.tokenManager.setAccessToken(token);
2991
+ this.tokenManager.setAccessToken(token, event);
2856
2992
  }
2857
2993
  }
2858
2994
  /**
@@ -2865,38 +3001,6 @@ var InsForgeClient = class {
2865
3001
  */
2866
3002
  };
2867
3003
 
2868
- // src/lib/jwt.ts
2869
- function decodeBase64Url(input) {
2870
- const normalized = input.replace(/-/g, "+").replace(/_/g, "/");
2871
- const padded = normalized.padEnd(
2872
- normalized.length + (4 - normalized.length % 4) % 4,
2873
- "="
2874
- );
2875
- const binary = atob(padded);
2876
- const bytes = Uint8Array.from(binary, (char) => char.charCodeAt(0));
2877
- return new TextDecoder().decode(bytes);
2878
- }
2879
- function getJwtExpiration(token) {
2880
- if (!token) return null;
2881
- const [, payload] = token.split(".");
2882
- if (!payload) return null;
2883
- try {
2884
- const parsed = JSON.parse(decodeBase64Url(payload));
2885
- if (typeof parsed.exp !== "number" || !Number.isFinite(parsed.exp)) {
2886
- return null;
2887
- }
2888
- return new Date(parsed.exp * 1e3);
2889
- } catch {
2890
- return null;
2891
- }
2892
- }
2893
- function isJwtExpiredOrExpiring(token, leewaySeconds = 60) {
2894
- if (!token) return false;
2895
- const expires = getJwtExpiration(token);
2896
- if (!expires) return true;
2897
- return expires.getTime() <= Date.now() + leewaySeconds * 1e3;
2898
- }
2899
-
2900
3004
  // src/ssr/browser-client.ts
2901
3005
  import { ERROR_CODES as ERROR_CODES2 } from "@insforge/shared-schemas";
2902
3006
 
@@ -2911,18 +3015,28 @@ function getRefreshTokenCookieName(names) {
2911
3015
  return names?.refreshToken ?? DEFAULT_REFRESH_TOKEN_COOKIE;
2912
3016
  }
2913
3017
  function getCookieValue(cookies, name) {
2914
- if (!cookies) return null;
3018
+ if (!cookies) {
3019
+ return null;
3020
+ }
2915
3021
  const value = cookies.get(name);
2916
- if (typeof value === "string") return value || null;
2917
- if (value && typeof value.value === "string") return value.value || null;
3022
+ if (typeof value === "string") {
3023
+ return value || null;
3024
+ }
3025
+ if (value && typeof value.value === "string") {
3026
+ return value.value || null;
3027
+ }
2918
3028
  return null;
2919
3029
  }
2920
3030
  function getCookieValueFromHeader(cookieHeader, name) {
2921
- if (!cookieHeader) return null;
3031
+ if (!cookieHeader) {
3032
+ return null;
3033
+ }
2922
3034
  const parts = cookieHeader.split(";");
2923
3035
  for (const part of parts) {
2924
3036
  const [rawName, ...rawValue] = part.trim().split("=");
2925
- if (rawName !== name) continue;
3037
+ if (rawName !== name) {
3038
+ continue;
3039
+ }
2926
3040
  try {
2927
3041
  return decodeURIComponent(rawValue.join("="));
2928
3042
  } catch {
@@ -2932,7 +3046,9 @@ function getCookieValueFromHeader(cookieHeader, name) {
2932
3046
  return null;
2933
3047
  }
2934
3048
  function getBrowserCookie(name) {
2935
- if (typeof document === "undefined") return null;
3049
+ if (typeof document === "undefined") {
3050
+ return null;
3051
+ }
2936
3052
  return getCookieValueFromHeader(document.cookie, name);
2937
3053
  }
2938
3054
  function defaultCookieOptions() {
@@ -2969,11 +3085,15 @@ function expiredCookieOptions(overrides) {
2969
3085
  };
2970
3086
  }
2971
3087
  function setCookie(cookies, name, value, options) {
2972
- if (!cookies?.set) return;
3088
+ if (!cookies?.set) {
3089
+ return;
3090
+ }
2973
3091
  cookies.set(name, value, options);
2974
3092
  }
2975
3093
  function deleteCookie(cookies, name, options) {
2976
- if (!cookies) return;
3094
+ if (!cookies) {
3095
+ return;
3096
+ }
2977
3097
  if (cookies.set) {
2978
3098
  cookies.set(name, "", expiredCookieOptions(options));
2979
3099
  return;
@@ -2982,12 +3102,24 @@ function deleteCookie(cookies, name, options) {
2982
3102
  }
2983
3103
  function serializeCookie(name, value, options = {}) {
2984
3104
  const parts = [`${encodeURIComponent(name)}=${encodeURIComponent(value)}`];
2985
- if (options.maxAge !== void 0) parts.push(`Max-Age=${options.maxAge}`);
2986
- if (options.domain) parts.push(`Domain=${options.domain}`);
2987
- if (options.path) parts.push(`Path=${options.path}`);
2988
- if (options.expires) parts.push(`Expires=${options.expires.toUTCString()}`);
2989
- if (options.httpOnly) parts.push("HttpOnly");
2990
- if (options.secure) parts.push("Secure");
3105
+ if (options.maxAge !== void 0) {
3106
+ parts.push(`Max-Age=${options.maxAge}`);
3107
+ }
3108
+ if (options.domain) {
3109
+ parts.push(`Domain=${options.domain}`);
3110
+ }
3111
+ if (options.path) {
3112
+ parts.push(`Path=${options.path}`);
3113
+ }
3114
+ if (options.expires) {
3115
+ parts.push(`Expires=${options.expires.toUTCString()}`);
3116
+ }
3117
+ if (options.httpOnly) {
3118
+ parts.push("HttpOnly");
3119
+ }
3120
+ if (options.secure) {
3121
+ parts.push("Secure");
3122
+ }
2991
3123
  if (options.sameSite) {
2992
3124
  const sameSite = options.sameSite.charAt(0).toUpperCase() + options.sameSite.slice(1);
2993
3125
  parts.push(`SameSite=${sameSite}`);
@@ -3000,20 +3132,14 @@ function appendSetCookie(headers, name, value, options) {
3000
3132
  function setAuthCookies(cookies, tokens, settings = {}) {
3001
3133
  const accessName = getAccessTokenCookieName(settings.names);
3002
3134
  const refreshName = getRefreshTokenCookieName(settings.names);
3003
- const accessOptions = accessTokenCookieOptions(
3004
- tokens.accessToken,
3005
- settings.options?.accessToken
3006
- );
3135
+ const accessOptions = accessTokenCookieOptions(tokens.accessToken, settings.options?.accessToken);
3007
3136
  setCookie(cookies, accessName, tokens.accessToken, accessOptions);
3008
3137
  if (tokens.refreshToken) {
3009
3138
  setCookie(
3010
3139
  cookies,
3011
3140
  refreshName,
3012
3141
  tokens.refreshToken,
3013
- refreshTokenCookieOptions(
3014
- tokens.refreshToken,
3015
- settings.options?.refreshToken
3016
- )
3142
+ refreshTokenCookieOptions(tokens.refreshToken, settings.options?.refreshToken)
3017
3143
  );
3018
3144
  }
3019
3145
  }
@@ -3039,10 +3165,7 @@ function setAuthCookieHeaders(headers, tokens, settings = {}) {
3039
3165
  headers,
3040
3166
  refreshName,
3041
3167
  tokens.refreshToken,
3042
- refreshTokenCookieOptions(
3043
- tokens.refreshToken,
3044
- settings.options?.refreshToken
3045
- )
3168
+ refreshTokenCookieOptions(tokens.refreshToken, settings.options?.refreshToken)
3046
3169
  );
3047
3170
  }
3048
3171
  }
@@ -3085,10 +3208,14 @@ function toRefreshError(response, body) {
3085
3208
  );
3086
3209
  }
3087
3210
  async function readErrorCode(response) {
3088
- if (response.status !== 401) return null;
3211
+ if (response.status !== 401) {
3212
+ return null;
3213
+ }
3089
3214
  try {
3090
3215
  const body = await response.clone().json();
3091
- if (!body || typeof body !== "object") return null;
3216
+ if (!body || typeof body !== "object") {
3217
+ return null;
3218
+ }
3092
3219
  const candidate = body.error ?? body.code;
3093
3220
  return typeof candidate === "string" ? candidate : null;
3094
3221
  } catch {
@@ -3107,7 +3234,9 @@ function withAuthHeader(init, token) {
3107
3234
  };
3108
3235
  }
3109
3236
  function getRequestUrl(input) {
3110
- if (typeof input === "string") return input;
3237
+ if (typeof input === "string") {
3238
+ return input;
3239
+ }
3111
3240
  if (typeof Request !== "undefined" && input instanceof Request) {
3112
3241
  return input.url;
3113
3242
  }
@@ -3125,21 +3254,19 @@ function createBrowserClient(options = {}) {
3125
3254
  "Missing InsForge baseUrl or anonKey. Pass baseUrl and anonKey to createBrowserClient() or set NEXT_PUBLIC_INSFORGE_URL and NEXT_PUBLIC_INSFORGE_ANON_KEY."
3126
3255
  );
3127
3256
  }
3128
- let accessToken = getBrowserCookie(
3129
- getAccessTokenCookieName(options.names)
3130
- );
3257
+ let accessToken = getBrowserCookie(getAccessTokenCookieName(options.names));
3131
3258
  const refreshUrl = options.refreshUrl ?? "/api/auth/refresh";
3132
3259
  const fetchImpl = options.fetch ?? (globalThis.fetch ? globalThis.fetch.bind(globalThis) : void 0);
3133
3260
  let client;
3134
3261
  let sessionChecked = false;
3135
3262
  let refreshPromise = null;
3136
3263
  const refreshFromRoute = () => {
3137
- if (refreshPromise) return refreshPromise;
3264
+ if (refreshPromise) {
3265
+ return refreshPromise;
3266
+ }
3138
3267
  refreshPromise = (async () => {
3139
3268
  if (!fetchImpl) {
3140
- throw new Error(
3141
- "Fetch is not available. Please provide a fetch implementation."
3142
- );
3269
+ throw new Error("Fetch is not available. Please provide a fetch implementation.");
3143
3270
  }
3144
3271
  const response = await fetchImpl(refreshUrl, {
3145
3272
  method: "POST",
@@ -3156,11 +3283,15 @@ function createBrowserClient(options = {}) {
3156
3283
  }
3157
3284
  throw error;
3158
3285
  }
3159
- if (!body || typeof body !== "object") return null;
3286
+ if (!body || typeof body !== "object") {
3287
+ return null;
3288
+ }
3160
3289
  const refreshBody = body;
3161
- if (!refreshBody.accessToken || !refreshBody.user) return null;
3290
+ if (!refreshBody.accessToken || !refreshBody.user) {
3291
+ return null;
3292
+ }
3162
3293
  accessToken = refreshBody.accessToken;
3163
- client?.setAccessToken(refreshBody.accessToken);
3294
+ client?.setAccessToken(refreshBody.accessToken, AuthChangeEvent.TOKEN_REFRESHED);
3164
3295
  return refreshBody;
3165
3296
  })().finally(() => {
3166
3297
  sessionChecked = true;
@@ -3174,9 +3305,7 @@ function createBrowserClient(options = {}) {
3174
3305
  };
3175
3306
  const ssrFetch = async (input, init) => {
3176
3307
  if (!fetchImpl) {
3177
- throw new Error(
3178
- "Fetch is not available. Please provide a fetch implementation."
3179
- );
3308
+ throw new Error("Fetch is not available. Please provide a fetch implementation.");
3180
3309
  }
3181
3310
  if (shouldSkipRefresh(input)) {
3182
3311
  return fetchImpl(input, init);
@@ -3213,9 +3342,9 @@ function createBrowserClient(options = {}) {
3213
3342
  }
3214
3343
  });
3215
3344
  const setAccessToken = client.setAccessToken.bind(client);
3216
- client.setAccessToken = (token) => {
3345
+ client.setAccessToken = (token, event) => {
3217
3346
  accessToken = token;
3218
- setAccessToken(token);
3347
+ setAccessToken(token, event);
3219
3348
  };
3220
3349
  if (accessToken) {
3221
3350
  client.setAccessToken(accessToken);
@@ -3239,10 +3368,7 @@ function createServerClient(options = {}) {
3239
3368
  "Missing InsForge baseUrl or anonKey. Pass baseUrl and anonKey to createServerClient() or set NEXT_PUBLIC_INSFORGE_URL and NEXT_PUBLIC_INSFORGE_ANON_KEY."
3240
3369
  );
3241
3370
  }
3242
- const accessToken = options.accessToken ?? getCookieValue(
3243
- options.cookies,
3244
- getAccessTokenCookieName(options.names)
3245
- );
3371
+ const accessToken = options.accessToken ?? getCookieValue(options.cookies, getAccessTokenCookieName(options.names));
3246
3372
  return new InsForgeClient({
3247
3373
  ...options,
3248
3374
  baseUrl,
@@ -3265,7 +3391,9 @@ function jsonResponse(body, init = {}, headers = new Headers(init.headers)) {
3265
3391
  });
3266
3392
  }
3267
3393
  function normalizeError(error) {
3268
- if (error instanceof InsForgeError) return error;
3394
+ if (error instanceof InsForgeError) {
3395
+ return error;
3396
+ }
3269
3397
  if (error && typeof error === "object") {
3270
3398
  const body = error;
3271
3399
  return new InsForgeError(
@@ -3282,18 +3410,21 @@ function normalizeError(error) {
3282
3410
  }
3283
3411
  async function readJson(response) {
3284
3412
  const contentType = response.headers.get("content-type");
3285
- if (!contentType?.includes("json")) return null;
3413
+ if (!contentType?.includes("json")) {
3414
+ return null;
3415
+ }
3286
3416
  return response.json();
3287
3417
  }
3288
3418
  function readRefreshToken(options) {
3289
- if (options.refreshToken) return options.refreshToken;
3419
+ if (options.refreshToken) {
3420
+ return options.refreshToken;
3421
+ }
3290
3422
  const refreshCookieName = getRefreshTokenCookieName(options.names);
3291
3423
  const cookieValue = getCookieValue(options.cookies, refreshCookieName);
3292
- if (cookieValue) return cookieValue;
3293
- return getCookieValueFromHeader(
3294
- options.request?.headers.get("cookie"),
3295
- refreshCookieName
3296
- );
3424
+ if (cookieValue) {
3425
+ return cookieValue;
3426
+ }
3427
+ return getCookieValueFromHeader(options.request?.headers.get("cookie"), refreshCookieName);
3297
3428
  }
3298
3429
  async function refreshAuth(options = {}) {
3299
3430
  const headers = new Headers();
@@ -3334,9 +3465,7 @@ async function refreshAuth(options = {}) {
3334
3465
  }
3335
3466
  const fetchImpl = options.fetch ?? (globalThis.fetch ? globalThis.fetch.bind(globalThis) : void 0);
3336
3467
  if (!fetchImpl) {
3337
- throw new Error(
3338
- "Fetch is not available. Please provide a fetch implementation."
3339
- );
3468
+ throw new Error("Fetch is not available. Please provide a fetch implementation.");
3340
3469
  }
3341
3470
  const requestHeaders = new Headers(options.headers);
3342
3471
  requestHeaders.set("Authorization", `Bearer ${anonKey}`);
@@ -3417,7 +3546,9 @@ function createRefreshAuthRouter(options = {}) {
3417
3546
 
3418
3547
  // src/ssr/auth-actions.ts
3419
3548
  function persistSessionCookies(cookies, data, settings) {
3420
- if (!data?.accessToken) return;
3549
+ if (!data?.accessToken) {
3550
+ return;
3551
+ }
3421
3552
  setAuthCookies(
3422
3553
  cookies,
3423
3554
  {
@@ -3428,7 +3559,9 @@ function persistSessionCookies(cookies, data, settings) {
3428
3559
  );
3429
3560
  }
3430
3561
  function sanitizeAuthData(data) {
3431
- if (!data) return null;
3562
+ if (!data) {
3563
+ return null;
3564
+ }
3432
3565
  const {
3433
3566
  accessToken: _accessToken,
3434
3567
  refreshToken: _refreshToken,
@@ -3478,32 +3611,20 @@ function createAuthActions(options = {}) {
3478
3611
  signInWithPassword: async (request) => {
3479
3612
  const result = await createClient().auth.signInWithPassword(request);
3480
3613
  persistSessionCookies(writeCookies, result.data, cookieSettings);
3481
- return toSafeAuthResult(
3482
- result
3483
- );
3614
+ return toSafeAuthResult(result);
3484
3615
  },
3485
3616
  signInWithOAuth: async (providerOrOptions, signInOptions) => {
3486
- return createClient().auth.signInWithOAuth(
3487
- providerOrOptions,
3488
- signInOptions
3489
- );
3617
+ return createClient().auth.signInWithOAuth(providerOrOptions, signInOptions);
3490
3618
  },
3491
3619
  signInWithIdToken: async (credentials) => {
3492
3620
  const result = await createClient().auth.signInWithIdToken(credentials);
3493
3621
  persistSessionCookies(writeCookies, result.data, cookieSettings);
3494
- return toSafeAuthResult(
3495
- result
3496
- );
3622
+ return toSafeAuthResult(result);
3497
3623
  },
3498
3624
  exchangeOAuthCode: async (code, codeVerifier) => {
3499
- const result = await createClient().auth.exchangeOAuthCode(
3500
- code,
3501
- codeVerifier
3502
- );
3625
+ const result = await createClient().auth.exchangeOAuthCode(code, codeVerifier);
3503
3626
  persistSessionCookies(writeCookies, result.data, cookieSettings);
3504
- return toSafeAuthResult(
3505
- result
3506
- );
3627
+ return toSafeAuthResult(result);
3507
3628
  },
3508
3629
  verifyEmail: async (request) => {
3509
3630
  const result = await createClient().auth.verifyEmail(request);
@@ -3522,10 +3643,7 @@ function createAuthActions(options = {}) {
3522
3643
  async function updateSession(options) {
3523
3644
  const accessCookieName = getAccessTokenCookieName(options.names);
3524
3645
  const refreshCookieName = getRefreshTokenCookieName(options.names);
3525
- const accessToken = getCookieValue(
3526
- options.requestCookies,
3527
- accessCookieName
3528
- );
3646
+ const accessToken = getCookieValue(options.requestCookies, accessCookieName);
3529
3647
  if (accessToken && !isJwtExpiredOrExpiring(accessToken, options.refreshLeewaySeconds)) {
3530
3648
  return {
3531
3649
  refreshed: false,
@@ -3533,10 +3651,7 @@ async function updateSession(options) {
3533
3651
  error: null
3534
3652
  };
3535
3653
  }
3536
- const refreshToken = getCookieValue(
3537
- options.requestCookies,
3538
- refreshCookieName
3539
- );
3654
+ const refreshToken = getCookieValue(options.requestCookies, refreshCookieName);
3540
3655
  if (!refreshToken) {
3541
3656
  if (accessToken) {
3542
3657
  clearAuthCookies(options.requestCookies, options);