@insforge/sdk 1.4.2 → 1.4.4

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");
@@ -1002,10 +1080,16 @@ var Auth = class {
1002
1080
  async signOut() {
1003
1081
  try {
1004
1082
  try {
1083
+ const serverMode = this.isServerMode();
1084
+ const csrfToken = !serverMode ? getCsrfToken() : null;
1005
1085
  await this.http.post(
1006
- this.isServerMode() ? "/api/auth/logout?client_type=mobile" : "/api/auth/logout",
1086
+ serverMode ? "/api/auth/logout?client_type=mobile" : "/api/auth/logout",
1007
1087
  void 0,
1008
- { credentials: "include", skipAuthRefresh: true }
1088
+ {
1089
+ credentials: "include",
1090
+ skipAuthRefresh: true,
1091
+ ...csrfToken ? { headers: { "X-CSRF-Token": csrfToken } } : {}
1092
+ }
1009
1093
  );
1010
1094
  } catch {
1011
1095
  }
@@ -1042,11 +1126,7 @@ var Auth = class {
1042
1126
  if (!signInOptions || !signInOptions.redirectTo) {
1043
1127
  return {
1044
1128
  data: {},
1045
- error: new InsForgeError(
1046
- "Redirect URI is required",
1047
- 400,
1048
- ERROR_CODES.INVALID_INPUT
1049
- )
1129
+ error: new InsForgeError("Redirect URI is required", 400, ERROR_CODES.INVALID_INPUT)
1050
1130
  };
1051
1131
  }
1052
1132
  const { provider } = signInOptions;
@@ -1121,10 +1201,7 @@ var Auth = class {
1121
1201
  error: null
1122
1202
  };
1123
1203
  } catch (error) {
1124
- return wrapError(
1125
- error,
1126
- "An unexpected error occurred during OAuth code exchange"
1127
- );
1204
+ return wrapError(error, "An unexpected error occurred during OAuth code exchange");
1128
1205
  }
1129
1206
  }
1130
1207
  /**
@@ -1151,10 +1228,7 @@ var Auth = class {
1151
1228
  error: null
1152
1229
  };
1153
1230
  } catch (error) {
1154
- return wrapError(
1155
- error,
1156
- "An unexpected error occurred during ID token sign in"
1157
- );
1231
+ return wrapError(error, "An unexpected error occurred during ID token sign in");
1158
1232
  }
1159
1233
  }
1160
1234
  // ============================================================================
@@ -1195,14 +1269,11 @@ var Auth = class {
1195
1269
  }
1196
1270
  );
1197
1271
  if (response.accessToken) {
1198
- this.saveSessionFromResponse(response);
1272
+ this.saveSessionFromResponse(response, AuthChangeEvent.TOKEN_REFRESHED);
1199
1273
  }
1200
1274
  return { data: response, error: null };
1201
1275
  } catch (error) {
1202
- return wrapError(
1203
- error,
1204
- "An unexpected error occurred during session refresh"
1205
- );
1276
+ return wrapError(error, "An unexpected error occurred during session refresh");
1206
1277
  }
1207
1278
  }
1208
1279
  /**
@@ -1213,11 +1284,11 @@ var Auth = class {
1213
1284
  try {
1214
1285
  if (this.isServerMode()) {
1215
1286
  const accessToken = this.tokenManager.getAccessToken();
1216
- if (!accessToken) return { data: { user: null }, error: null };
1287
+ if (!accessToken) {
1288
+ return { data: { user: null }, error: null };
1289
+ }
1217
1290
  this.http.setAuthToken(accessToken);
1218
- const response = await this.http.get(
1219
- "/api/auth/sessions/current"
1220
- );
1291
+ const response = await this.http.get("/api/auth/sessions/current");
1221
1292
  const user = response.user ?? null;
1222
1293
  return { data: { user }, error: null };
1223
1294
  }
@@ -1255,25 +1326,17 @@ var Auth = class {
1255
1326
  // ============================================================================
1256
1327
  async getProfile(userId) {
1257
1328
  try {
1258
- const response = await this.http.get(
1259
- `/api/auth/profiles/${userId}`
1260
- );
1329
+ const response = await this.http.get(`/api/auth/profiles/${userId}`);
1261
1330
  return { data: response, error: null };
1262
1331
  } catch (error) {
1263
- return wrapError(
1264
- error,
1265
- "An unexpected error occurred while fetching user profile"
1266
- );
1332
+ return wrapError(error, "An unexpected error occurred while fetching user profile");
1267
1333
  }
1268
1334
  }
1269
1335
  async setProfile(profile) {
1270
1336
  try {
1271
- const response = await this.http.patch(
1272
- "/api/auth/profiles/current",
1273
- {
1274
- profile
1275
- }
1276
- );
1337
+ const response = await this.http.patch("/api/auth/profiles/current", {
1338
+ profile
1339
+ });
1277
1340
  const currentUser = this.tokenManager.getUser();
1278
1341
  if (!this.isServerMode() && currentUser && response.profile !== void 0) {
1279
1342
  this.tokenManager.setUser({
@@ -1283,10 +1346,7 @@ var Auth = class {
1283
1346
  }
1284
1347
  return { data: response, error: null };
1285
1348
  } catch (error) {
1286
- return wrapError(
1287
- error,
1288
- "An unexpected error occurred while updating user profile"
1289
- );
1349
+ return wrapError(error, "An unexpected error occurred while updating user profile");
1290
1350
  }
1291
1351
  }
1292
1352
  // ============================================================================
@@ -1299,10 +1359,7 @@ var Auth = class {
1299
1359
  });
1300
1360
  return { data: response, error: null };
1301
1361
  } catch (error) {
1302
- return wrapError(
1303
- error,
1304
- "An unexpected error occurred while sending verification email"
1305
- );
1362
+ return wrapError(error, "An unexpected error occurred while sending verification email");
1306
1363
  }
1307
1364
  }
1308
1365
  async verifyEmail(request) {
@@ -1318,10 +1375,7 @@ var Auth = class {
1318
1375
  }
1319
1376
  return { data: response, error: null };
1320
1377
  } catch (error) {
1321
- return wrapError(
1322
- error,
1323
- "An unexpected error occurred while verifying email"
1324
- );
1378
+ return wrapError(error, "An unexpected error occurred while verifying email");
1325
1379
  }
1326
1380
  }
1327
1381
  // ============================================================================
@@ -1334,10 +1388,7 @@ var Auth = class {
1334
1388
  });
1335
1389
  return { data: response, error: null };
1336
1390
  } catch (error) {
1337
- return wrapError(
1338
- error,
1339
- "An unexpected error occurred while sending password reset email"
1340
- );
1391
+ return wrapError(error, "An unexpected error occurred while sending password reset email");
1341
1392
  }
1342
1393
  }
1343
1394
  async exchangeResetPasswordToken(request) {
@@ -1349,10 +1400,7 @@ var Auth = class {
1349
1400
  );
1350
1401
  return { data: response, error: null };
1351
1402
  } catch (error) {
1352
- return wrapError(
1353
- error,
1354
- "An unexpected error occurred while verifying reset code"
1355
- );
1403
+ return wrapError(error, "An unexpected error occurred while verifying reset code");
1356
1404
  }
1357
1405
  }
1358
1406
  async resetPassword(request) {
@@ -1364,10 +1412,7 @@ var Auth = class {
1364
1412
  );
1365
1413
  return { data: response, error: null };
1366
1414
  } catch (error) {
1367
- return wrapError(
1368
- error,
1369
- "An unexpected error occurred while resetting password"
1370
- );
1415
+ return wrapError(error, "An unexpected error occurred while resetting password");
1371
1416
  }
1372
1417
  }
1373
1418
  // ============================================================================
@@ -1375,16 +1420,12 @@ var Auth = class {
1375
1420
  // ============================================================================
1376
1421
  async getPublicAuthConfig() {
1377
1422
  try {
1378
- const response = await this.http.get(
1379
- "/api/auth/public-config",
1380
- { skipAuthRefresh: true }
1381
- );
1423
+ const response = await this.http.get("/api/auth/public-config", {
1424
+ skipAuthRefresh: true
1425
+ });
1382
1426
  return { data: response, error: null };
1383
1427
  } catch (error) {
1384
- return wrapError(
1385
- error,
1386
- "An unexpected error occurred while fetching auth configuration"
1387
- );
1428
+ return wrapError(error, "An unexpected error occurred while fetching auth configuration");
1388
1429
  }
1389
1430
  }
1390
1431
  };
@@ -1411,12 +1452,30 @@ function createInsForgePostgrestFetch(httpClient) {
1411
1452
  };
1412
1453
  }
1413
1454
  var Database = class {
1414
- constructor(httpClient) {
1455
+ constructor(httpClient, defaultSchema) {
1415
1456
  this.postgrest = new PostgrestClient("http://dummy", {
1416
1457
  fetch: createInsForgePostgrestFetch(httpClient),
1417
- headers: {}
1458
+ headers: {},
1459
+ ...defaultSchema ? { schema: defaultSchema } : {}
1418
1460
  });
1419
1461
  }
1462
+ /**
1463
+ * Select a non-default Postgres schema for the chained query. Maps to
1464
+ * PostgREST's `Accept-Profile` (reads) / `Content-Profile` (writes) header.
1465
+ * The schema must be exposed by the backend.
1466
+ *
1467
+ * @example
1468
+ * const { data } = await client.database
1469
+ * .schema('analytics')
1470
+ * .from('events')
1471
+ * .select('*');
1472
+ *
1473
+ * @example
1474
+ * await client.database.schema('analytics').rpc('rollup', { day: '2026-01-01' });
1475
+ */
1476
+ schema(schemaName) {
1477
+ return this.postgrest.schema(schemaName);
1478
+ }
1420
1479
  /**
1421
1480
  * Create a query builder for a table
1422
1481
  *
@@ -1524,11 +1583,7 @@ var StorageBucket = class {
1524
1583
  } catch (error) {
1525
1584
  return {
1526
1585
  data: null,
1527
- error: error instanceof InsForgeError ? error : new InsForgeError(
1528
- "Upload failed",
1529
- 500,
1530
- "STORAGE_ERROR"
1531
- )
1586
+ error: error instanceof InsForgeError ? error : new InsForgeError("Upload failed", 500, "STORAGE_ERROR")
1532
1587
  };
1533
1588
  }
1534
1589
  }
@@ -1574,11 +1629,7 @@ var StorageBucket = class {
1574
1629
  } catch (error) {
1575
1630
  return {
1576
1631
  data: null,
1577
- error: error instanceof InsForgeError ? error : new InsForgeError(
1578
- "Upload failed",
1579
- 500,
1580
- "STORAGE_ERROR"
1581
- )
1632
+ error: error instanceof InsForgeError ? error : new InsForgeError("Upload failed", 500, "STORAGE_ERROR")
1582
1633
  };
1583
1634
  }
1584
1635
  }
@@ -1606,13 +1657,10 @@ var StorageBucket = class {
1606
1657
  );
1607
1658
  }
1608
1659
  if (strategy.confirmRequired && strategy.confirmUrl) {
1609
- const confirmResponse = await this.http.post(
1610
- strategy.confirmUrl,
1611
- {
1612
- size: file.size,
1613
- contentType: file.type || "application/octet-stream"
1614
- }
1615
- );
1660
+ const confirmResponse = await this.http.post(strategy.confirmUrl, {
1661
+ size: file.size,
1662
+ contentType: file.type || "application/octet-stream"
1663
+ });
1616
1664
  return { data: confirmResponse, error: null };
1617
1665
  }
1618
1666
  return {
@@ -1627,11 +1675,7 @@ var StorageBucket = class {
1627
1675
  error: null
1628
1676
  };
1629
1677
  } catch (error) {
1630
- throw error instanceof InsForgeError ? error : new InsForgeError(
1631
- "Presigned upload failed",
1632
- 500,
1633
- "STORAGE_ERROR"
1634
- );
1678
+ throw error instanceof InsForgeError ? error : new InsForgeError("Presigned upload failed", 500, "STORAGE_ERROR");
1635
1679
  }
1636
1680
  }
1637
1681
  /**
@@ -1685,11 +1729,7 @@ var StorageBucket = class {
1685
1729
  } catch (error) {
1686
1730
  return {
1687
1731
  data: null,
1688
- error: error instanceof InsForgeError ? error : new InsForgeError(
1689
- "Download failed",
1690
- 500,
1691
- "STORAGE_ERROR"
1692
- )
1732
+ error: error instanceof InsForgeError ? error : new InsForgeError("Download failed", 500, "STORAGE_ERROR")
1693
1733
  };
1694
1734
  }
1695
1735
  }
@@ -1724,7 +1764,9 @@ var StorageBucket = class {
1724
1764
  } catch (error) {
1725
1765
  const status = error instanceof InsForgeError ? error.statusCode : void 0;
1726
1766
  const isMissingRoute = (status === 404 || status === 405) && !(error instanceof InsForgeError && error.error === "STORAGE_NOT_FOUND");
1727
- if (!isMissingRoute) throw error;
1767
+ if (!isMissingRoute) {
1768
+ throw error;
1769
+ }
1728
1770
  return await this.http.post(
1729
1771
  `/api/storage/buckets/${this.bucketName}/objects/${encoded}/download-strategy`,
1730
1772
  { expiresIn }
@@ -1800,10 +1842,18 @@ var StorageBucket = class {
1800
1842
  async list(options) {
1801
1843
  try {
1802
1844
  const params = {};
1803
- if (options?.prefix) params.prefix = options.prefix;
1804
- if (options?.search) params.search = options.search;
1805
- if (options?.limit) params.limit = options.limit.toString();
1806
- 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
+ }
1807
1857
  const response = await this.http.get(
1808
1858
  `/api/storage/buckets/${this.bucketName}/objects`,
1809
1859
  { params }
@@ -1812,11 +1862,7 @@ var StorageBucket = class {
1812
1862
  } catch (error) {
1813
1863
  return {
1814
1864
  data: null,
1815
- error: error instanceof InsForgeError ? error : new InsForgeError(
1816
- "List failed",
1817
- 500,
1818
- "STORAGE_ERROR"
1819
- )
1865
+ error: error instanceof InsForgeError ? error : new InsForgeError("List failed", 500, "STORAGE_ERROR")
1820
1866
  };
1821
1867
  }
1822
1868
  }
@@ -1833,11 +1879,7 @@ var StorageBucket = class {
1833
1879
  } catch (error) {
1834
1880
  return {
1835
1881
  data: null,
1836
- error: error instanceof InsForgeError ? error : new InsForgeError(
1837
- "Delete failed",
1838
- 500,
1839
- "STORAGE_ERROR"
1840
- )
1882
+ error: error instanceof InsForgeError ? error : new InsForgeError("Delete failed", 500, "STORAGE_ERROR")
1841
1883
  };
1842
1884
  }
1843
1885
  }
@@ -1959,14 +2001,11 @@ var ChatCompletions = class {
1959
2001
  if (params.stream) {
1960
2002
  const headers = this.http.getHeaders();
1961
2003
  headers["Content-Type"] = "application/json";
1962
- const response2 = await this.http.fetch(
1963
- `${this.http.baseUrl}/api/ai/chat/completion`,
1964
- {
1965
- method: "POST",
1966
- headers,
1967
- body: JSON.stringify(backendParams)
1968
- }
1969
- );
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
+ });
1970
2009
  if (!response2.ok) {
1971
2010
  const error = await response2.json();
1972
2011
  throw new Error(error.error || "Stream request failed");
@@ -2014,7 +2053,9 @@ var ChatCompletions = class {
2014
2053
  try {
2015
2054
  while (true) {
2016
2055
  const { done, value } = await reader.read();
2017
- if (done) break;
2056
+ if (done) {
2057
+ break;
2058
+ }
2018
2059
  buffer += decoder.decode(value, { stream: true });
2019
2060
  const lines = buffer.split("\n");
2020
2061
  buffer = lines.pop() || "";
@@ -2062,7 +2103,7 @@ var ChatCompletions = class {
2062
2103
  reader.releaseLock();
2063
2104
  return;
2064
2105
  }
2065
- } catch (e) {
2106
+ } catch {
2066
2107
  console.warn("Failed to parse SSE data:", dataStr);
2067
2108
  }
2068
2109
  }
@@ -2115,10 +2156,7 @@ var Embeddings = class {
2115
2156
  * ```
2116
2157
  */
2117
2158
  async create(params) {
2118
- const response = await this.http.post(
2119
- "/api/ai/embeddings",
2120
- params
2121
- );
2159
+ const response = await this.http.post("/api/ai/embeddings", params);
2122
2160
  return {
2123
2161
  object: response.object,
2124
2162
  data: response.data,
@@ -2204,7 +2242,9 @@ var Functions = class _Functions {
2204
2242
  static deriveSubhostingUrl(baseUrl) {
2205
2243
  try {
2206
2244
  const { hostname } = new URL(baseUrl);
2207
- if (!hostname.endsWith(".insforge.app")) return void 0;
2245
+ if (!hostname.endsWith(".insforge.app")) {
2246
+ return void 0;
2247
+ }
2208
2248
  const appKey = hostname.split(".")[0];
2209
2249
  return `https://${appKey}.functions.insforge.app`;
2210
2250
  } catch {
@@ -2250,7 +2290,9 @@ var Functions = class _Functions {
2250
2290
  const data = await parseResponse(res);
2251
2291
  return { data, error: null };
2252
2292
  } catch (error) {
2253
- if (error instanceof Error && error.name === "AbortError") throw error;
2293
+ if (error instanceof Error && error.name === "AbortError") {
2294
+ throw error;
2295
+ }
2254
2296
  return {
2255
2297
  data: null,
2256
2298
  error: error instanceof InsForgeError ? error : new InsForgeError(
@@ -2269,7 +2311,9 @@ var Functions = class _Functions {
2269
2311
  });
2270
2312
  return { data, error: null };
2271
2313
  } catch (error) {
2272
- if (error instanceof Error && error.name === "AbortError") throw error;
2314
+ if (error instanceof Error && error.name === "AbortError") {
2315
+ throw error;
2316
+ }
2273
2317
  if (error instanceof InsForgeError && error.statusCode === 404) {
2274
2318
  } else {
2275
2319
  return {
@@ -2288,7 +2332,9 @@ var Functions = class _Functions {
2288
2332
  const data = await this.http.request(method, path, { body, headers });
2289
2333
  return { data, error: null };
2290
2334
  } catch (error) {
2291
- if (error instanceof Error && error.name === "AbortError") throw error;
2335
+ if (error instanceof Error && error.name === "AbortError") {
2336
+ throw error;
2337
+ }
2292
2338
  return {
2293
2339
  data: null,
2294
2340
  error: error instanceof InsForgeError ? error : new InsForgeError(
@@ -2303,32 +2349,37 @@ var Functions = class _Functions {
2303
2349
 
2304
2350
  // src/modules/realtime.ts
2305
2351
  var CONNECT_TIMEOUT = 1e4;
2352
+ var SUBSCRIBE_TIMEOUT = 1e4;
2306
2353
  var Realtime = class {
2307
- constructor(baseUrl, tokenManager, anonKey) {
2308
- this.socket = null;
2309
- this.connectPromise = null;
2310
- this.subscribedChannels = /* @__PURE__ */ new Set();
2311
- this.eventListeners = /* @__PURE__ */ new Map();
2354
+ constructor(baseUrl, tokenManager, anonKey, getValidAccessToken = async () => tokenManager.getAccessToken()) {
2312
2355
  this.baseUrl = baseUrl;
2313
2356
  this.tokenManager = tokenManager;
2314
2357
  this.anonKey = anonKey;
2315
- 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
+ });
2316
2370
  }
2317
2371
  notifyListeners(event, payload) {
2318
- const listeners = this.eventListeners.get(event);
2319
- if (!listeners) return;
2320
- for (const cb of listeners) {
2372
+ for (const callback of this.eventListeners.get(event) ?? []) {
2321
2373
  try {
2322
- cb(payload);
2323
- } catch (err) {
2324
- console.error(`Error in ${event} callback:`, err);
2374
+ callback(payload);
2375
+ } catch (error) {
2376
+ console.error(`Error in ${event} callback:`, error);
2325
2377
  }
2326
2378
  }
2327
2379
  }
2328
- /**
2329
- * Connect to the realtime server
2330
- * @returns Promise that resolves when connected
2331
- */
2380
+ async getHandshakeToken() {
2381
+ return await this.getValidAccessToken() ?? this.anonKey ?? null;
2382
+ }
2332
2383
  connect() {
2333
2384
  if (this.socket?.connected) {
2334
2385
  return Promise.resolve();
@@ -2336,210 +2387,324 @@ var Realtime = class {
2336
2387
  if (this.connectPromise) {
2337
2388
  return this.connectPromise;
2338
2389
  }
2339
- this.connectPromise = (async () => {
2340
- try {
2341
- const { io } = await import("socket.io-client");
2342
- await new Promise((resolve, reject) => {
2343
- const token = this.tokenManager.getAccessToken() ?? this.anonKey;
2344
- this.socket = io(this.baseUrl, {
2345
- transports: ["websocket"],
2346
- auth: token ? { token } : void 0
2347
- });
2348
- let initialConnection = true;
2349
- let timeoutId = null;
2350
- const cleanup = () => {
2351
- if (timeoutId) {
2352
- clearTimeout(timeoutId);
2353
- timeoutId = null;
2354
- }
2355
- };
2356
- timeoutId = setTimeout(() => {
2357
- if (initialConnection) {
2358
- initialConnection = false;
2359
- this.connectPromise = null;
2360
- this.socket?.disconnect();
2361
- this.socket = null;
2362
- reject(new Error(`Connection timeout after ${CONNECT_TIMEOUT}ms`));
2363
- }
2364
- }, CONNECT_TIMEOUT);
2365
- this.socket.on("connect", () => {
2366
- cleanup();
2367
- for (const channel of this.subscribedChannels) {
2368
- this.socket.emit("realtime:subscribe", { channel });
2369
- }
2370
- this.notifyListeners("connect");
2371
- if (initialConnection) {
2372
- initialConnection = false;
2373
- this.connectPromise = null;
2374
- resolve();
2375
- }
2376
- });
2377
- this.socket.on("connect_error", (error) => {
2378
- cleanup();
2379
- this.notifyListeners("connect_error", error);
2380
- if (initialConnection) {
2381
- initialConnection = false;
2382
- this.connectPromise = null;
2383
- reject(error);
2384
- }
2385
- });
2386
- this.socket.on("disconnect", (reason) => {
2387
- this.notifyListeners("disconnect", reason);
2388
- });
2389
- this.socket.on("realtime:error", (error) => {
2390
- this.notifyListeners("error", error);
2391
- });
2392
- this.socket.onAny((event, message) => {
2393
- if (event === "realtime:error") return;
2394
- this.notifyListeners(event, message);
2395
- });
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
+ }
2396
2405
  });
2397
- } 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) {
2398
2487
  this.connectPromise = null;
2399
- throw error;
2400
2488
  }
2401
- })();
2402
- return this.connectPromise;
2489
+ });
2490
+ this.connectPromise = trackedConnection;
2491
+ return trackedConnection;
2403
2492
  }
2404
- /**
2405
- * Disconnect from the realtime server
2406
- */
2407
2493
  disconnect() {
2408
- if (this.socket) {
2409
- this.socket.disconnect();
2410
- 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
+ );
2411
2509
  }
2412
- this.subscribedChannels.clear();
2510
+ this.subscriptions.clear();
2413
2511
  }
2414
- /**
2415
- * Handle token changes (e.g., after auth refresh)
2416
- * Updates socket auth so reconnects use the new token
2417
- * If connected, triggers reconnect to apply new token immediately
2418
- */
2419
- onTokenChange() {
2420
- const token = this.tokenManager.getAccessToken() ?? this.anonKey;
2421
- if (this.socket) {
2422
- 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
+ }
2423
2517
  }
2424
- if (this.socket && (this.socket.connected || this.connectPromise)) {
2425
- this.socket.disconnect();
2426
- 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);
2427
2631
  }
2428
2632
  }
2429
- /**
2430
- * Check if connected to the realtime server
2431
- */
2432
2633
  get isConnected() {
2433
2634
  return this.socket?.connected ?? false;
2434
2635
  }
2435
- /**
2436
- * Get the current connection state
2437
- */
2438
2636
  get connectionState() {
2439
- if (!this.socket) return "disconnected";
2440
- if (this.socket.connected) return "connected";
2441
- return "connecting";
2637
+ if (!this.socket) {
2638
+ return "disconnected";
2639
+ }
2640
+ return this.socket.connected ? "connected" : "connecting";
2442
2641
  }
2443
- /**
2444
- * Get the socket ID (if connected)
2445
- */
2446
2642
  get socketId() {
2447
2643
  return this.socket?.id;
2448
2644
  }
2449
- /**
2450
- * Subscribe to a channel
2451
- *
2452
- * Automatically connects if not already connected.
2453
- *
2454
- * @param channel - Channel name (e.g., 'orders:123', 'broadcast')
2455
- * @returns Promise with the subscription response
2456
- */
2457
2645
  async subscribe(channel) {
2458
- if (this.subscribedChannels.has(channel)) {
2459
- 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);
2460
2657
  }
2461
2658
  if (!this.socket?.connected) {
2462
2659
  try {
2463
2660
  await this.connect();
2464
2661
  } catch (error) {
2662
+ if (this.subscriptions.get(channel) === subscription) {
2663
+ this.subscriptions.delete(channel);
2664
+ }
2465
2665
  const message = error instanceof Error ? error.message : "Connection failed";
2466
2666
  return { ok: false, channel, error: { code: "CONNECTION_FAILED", message } };
2467
2667
  }
2468
2668
  }
2469
- return new Promise((resolve) => {
2470
- this.socket.emit("realtime:subscribe", { channel }, (response) => {
2471
- if (response.ok) {
2472
- this.subscribedChannels.add(channel);
2473
- }
2474
- resolve(response);
2475
- });
2476
- });
2669
+ return subscription.pending ?? this.requestSubscription(channel, subscription);
2477
2670
  }
2478
- /**
2479
- * Unsubscribe from a channel (fire-and-forget)
2480
- *
2481
- * @param channel - Channel name to unsubscribe from
2482
- */
2483
2671
  unsubscribe(channel) {
2484
- 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
+ );
2485
2686
  if (this.socket?.connected) {
2486
2687
  this.socket.emit("realtime:unsubscribe", { channel });
2487
2688
  }
2488
2689
  }
2489
- /**
2490
- * Publish a message to a channel
2491
- *
2492
- * @param channel - Channel name
2493
- * @param event - Event name
2494
- * @param payload - Message payload
2495
- */
2496
2690
  async publish(channel, event, payload) {
2497
2691
  if (!this.socket?.connected) {
2498
2692
  throw new Error("Not connected to realtime server. Call connect() first.");
2499
2693
  }
2500
2694
  this.socket.emit("realtime:publish", { channel, event, payload });
2501
2695
  }
2502
- /**
2503
- * Listen for events
2504
- *
2505
- * Reserved event names:
2506
- * - 'connect' - Fired when connected to the server
2507
- * - 'connect_error' - Fired when connection fails (payload: Error)
2508
- * - 'disconnect' - Fired when disconnected (payload: reason string)
2509
- * - 'error' - Fired when a realtime error occurs (payload: RealtimeErrorPayload)
2510
- *
2511
- * All other events receive a `SocketMessage` payload with metadata.
2512
- *
2513
- * @param event - Event name to listen for
2514
- * @param callback - Callback function when event is received
2515
- */
2516
2696
  on(event, callback) {
2517
- if (!this.eventListeners.has(event)) {
2518
- this.eventListeners.set(event, /* @__PURE__ */ new Set());
2519
- }
2520
- 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);
2521
2700
  }
2522
- /**
2523
- * Remove a listener for a specific event
2524
- *
2525
- * @param event - Event name
2526
- * @param callback - The callback function to remove
2527
- */
2528
2701
  off(event, callback) {
2529
2702
  const listeners = this.eventListeners.get(event);
2530
- if (listeners) {
2531
- listeners.delete(callback);
2532
- if (listeners.size === 0) {
2533
- this.eventListeners.delete(event);
2534
- }
2703
+ listeners?.delete(callback);
2704
+ if (listeners?.size === 0) {
2705
+ this.eventListeners.delete(event);
2535
2706
  }
2536
2707
  }
2537
- /**
2538
- * Listen for an event only once, then automatically remove the listener
2539
- *
2540
- * @param event - Event name to listen for
2541
- * @param callback - Callback function when event is received
2542
- */
2543
2708
  once(event, callback) {
2544
2709
  const wrapper = (payload) => {
2545
2710
  this.off(event, wrapper);
@@ -2547,13 +2712,11 @@ var Realtime = class {
2547
2712
  };
2548
2713
  this.on(event, wrapper);
2549
2714
  }
2550
- /**
2551
- * Get all currently subscribed channels
2552
- *
2553
- * @returns Array of channel names
2554
- */
2555
2715
  getSubscribedChannels() {
2556
- 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() ?? []];
2557
2720
  }
2558
2721
  };
2559
2722
 
@@ -2568,13 +2731,12 @@ var Emails = class {
2568
2731
  */
2569
2732
  async send(options) {
2570
2733
  try {
2571
- const data = await this.http.post(
2572
- "/api/email/send-raw",
2573
- options
2574
- );
2734
+ const data = await this.http.post("/api/email/send-raw", options);
2575
2735
  return { data, error: null };
2576
2736
  } catch (error) {
2577
- if (error instanceof Error && error.name === "AbortError") throw error;
2737
+ if (error instanceof Error && error.name === "AbortError") {
2738
+ throw error;
2739
+ }
2578
2740
  return {
2579
2741
  data: null,
2580
2742
  error: error instanceof InsForgeError ? error : new InsForgeError(
@@ -2657,10 +2819,7 @@ var RazorpayPayments = class {
2657
2819
  );
2658
2820
  return { data, error: null };
2659
2821
  } catch (error) {
2660
- return wrapError(
2661
- error,
2662
- "Razorpay order creation failed"
2663
- );
2822
+ return wrapError(error, "Razorpay order creation failed");
2664
2823
  }
2665
2824
  }
2666
2825
  async verifyOrder(environment, request) {
@@ -2671,10 +2830,7 @@ var RazorpayPayments = class {
2671
2830
  );
2672
2831
  return { data, error: null };
2673
2832
  } catch (error) {
2674
- return wrapError(
2675
- error,
2676
- "Razorpay order verification failed"
2677
- );
2833
+ return wrapError(error, "Razorpay order verification failed");
2678
2834
  }
2679
2835
  }
2680
2836
  async createSubscription(environment, request) {
@@ -2776,14 +2932,15 @@ var InsForgeClient = class {
2776
2932
  isServerMode: config.isServerMode ?? !!accessToken,
2777
2933
  detectOAuthCallback: config.auth?.detectOAuthCallback
2778
2934
  });
2779
- this.database = new Database(this.http);
2935
+ this.database = new Database(this.http, config.db?.schema);
2780
2936
  this.storage = new Storage(this.http);
2781
2937
  this.ai = new AI(this.http);
2782
2938
  this.functions = new Functions(this.http, config.functionsUrl);
2783
2939
  this.realtime = new Realtime(
2784
2940
  this.http.baseUrl,
2785
2941
  this.tokenManager,
2786
- config.anonKey
2942
+ config.anonKey,
2943
+ () => this.http.getValidAccessToken()
2787
2944
  );
2788
2945
  this.emails = new Emails(this.http);
2789
2946
  this.payments = new Payments(this.http);
@@ -2803,32 +2960,35 @@ var InsForgeClient = class {
2803
2960
  /**
2804
2961
  * Set the access token used by every SDK surface. Updates both the HTTP
2805
2962
  * client (database / storage / functions / AI / emails) and the realtime
2806
- * token manager (which fires `onTokenChange` to reconnect the WebSocket
2807
- * 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.
2808
2967
  *
2809
2968
  * Use this when an external auth provider (Better Auth, Clerk, Auth0,
2810
2969
  * WorkOS, Kinde, Stytch, …) issues the JWT and you need to keep the
2811
2970
  * long-lived InsForge client in sync. Without this, you'd have to call
2812
2971
  * `client.getHttpClient().setAuthToken(token)` AND reach into the private
2813
- * `client.realtime.tokenManager.setAccessToken(token)` separately
2814
- * forgetting the second one silently breaks realtime auth.
2972
+ * realtime token manager separately.
2815
2973
  *
2816
2974
  * @example
2817
2975
  * ```typescript
2976
+ * import { AuthChangeEvent } from '@insforge/sdk';
2977
+ *
2818
2978
  * // Refresh a third-party-issued JWT periodically
2819
2979
  * const { token } = await fetch('/api/insforge-token').then((r) => r.json());
2820
- * client.setAccessToken(token);
2980
+ * client.setAccessToken(token, AuthChangeEvent.TOKEN_REFRESHED);
2821
2981
  *
2822
2982
  * // Sign-out
2823
2983
  * client.setAccessToken(null);
2824
2984
  * ```
2825
2985
  */
2826
- setAccessToken(token) {
2986
+ setAccessToken(token, event = AuthChangeEvent.SIGNED_IN) {
2827
2987
  this.http.setAuthToken(token);
2828
2988
  if (token === null) {
2829
2989
  this.tokenManager.clearSession();
2830
2990
  } else {
2831
- this.tokenManager.setAccessToken(token);
2991
+ this.tokenManager.setAccessToken(token, event);
2832
2992
  }
2833
2993
  }
2834
2994
  /**
@@ -2841,38 +3001,6 @@ var InsForgeClient = class {
2841
3001
  */
2842
3002
  };
2843
3003
 
2844
- // src/lib/jwt.ts
2845
- function decodeBase64Url(input) {
2846
- const normalized = input.replace(/-/g, "+").replace(/_/g, "/");
2847
- const padded = normalized.padEnd(
2848
- normalized.length + (4 - normalized.length % 4) % 4,
2849
- "="
2850
- );
2851
- const binary = atob(padded);
2852
- const bytes = Uint8Array.from(binary, (char) => char.charCodeAt(0));
2853
- return new TextDecoder().decode(bytes);
2854
- }
2855
- function getJwtExpiration(token) {
2856
- if (!token) return null;
2857
- const [, payload] = token.split(".");
2858
- if (!payload) return null;
2859
- try {
2860
- const parsed = JSON.parse(decodeBase64Url(payload));
2861
- if (typeof parsed.exp !== "number" || !Number.isFinite(parsed.exp)) {
2862
- return null;
2863
- }
2864
- return new Date(parsed.exp * 1e3);
2865
- } catch {
2866
- return null;
2867
- }
2868
- }
2869
- function isJwtExpiredOrExpiring(token, leewaySeconds = 60) {
2870
- if (!token) return false;
2871
- const expires = getJwtExpiration(token);
2872
- if (!expires) return true;
2873
- return expires.getTime() <= Date.now() + leewaySeconds * 1e3;
2874
- }
2875
-
2876
3004
  // src/ssr/browser-client.ts
2877
3005
  import { ERROR_CODES as ERROR_CODES2 } from "@insforge/shared-schemas";
2878
3006
 
@@ -2887,18 +3015,28 @@ function getRefreshTokenCookieName(names) {
2887
3015
  return names?.refreshToken ?? DEFAULT_REFRESH_TOKEN_COOKIE;
2888
3016
  }
2889
3017
  function getCookieValue(cookies, name) {
2890
- if (!cookies) return null;
3018
+ if (!cookies) {
3019
+ return null;
3020
+ }
2891
3021
  const value = cookies.get(name);
2892
- if (typeof value === "string") return value || null;
2893
- 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
+ }
2894
3028
  return null;
2895
3029
  }
2896
3030
  function getCookieValueFromHeader(cookieHeader, name) {
2897
- if (!cookieHeader) return null;
3031
+ if (!cookieHeader) {
3032
+ return null;
3033
+ }
2898
3034
  const parts = cookieHeader.split(";");
2899
3035
  for (const part of parts) {
2900
3036
  const [rawName, ...rawValue] = part.trim().split("=");
2901
- if (rawName !== name) continue;
3037
+ if (rawName !== name) {
3038
+ continue;
3039
+ }
2902
3040
  try {
2903
3041
  return decodeURIComponent(rawValue.join("="));
2904
3042
  } catch {
@@ -2908,7 +3046,9 @@ function getCookieValueFromHeader(cookieHeader, name) {
2908
3046
  return null;
2909
3047
  }
2910
3048
  function getBrowserCookie(name) {
2911
- if (typeof document === "undefined") return null;
3049
+ if (typeof document === "undefined") {
3050
+ return null;
3051
+ }
2912
3052
  return getCookieValueFromHeader(document.cookie, name);
2913
3053
  }
2914
3054
  function defaultCookieOptions() {
@@ -2945,11 +3085,15 @@ function expiredCookieOptions(overrides) {
2945
3085
  };
2946
3086
  }
2947
3087
  function setCookie(cookies, name, value, options) {
2948
- if (!cookies?.set) return;
3088
+ if (!cookies?.set) {
3089
+ return;
3090
+ }
2949
3091
  cookies.set(name, value, options);
2950
3092
  }
2951
3093
  function deleteCookie(cookies, name, options) {
2952
- if (!cookies) return;
3094
+ if (!cookies) {
3095
+ return;
3096
+ }
2953
3097
  if (cookies.set) {
2954
3098
  cookies.set(name, "", expiredCookieOptions(options));
2955
3099
  return;
@@ -2958,12 +3102,24 @@ function deleteCookie(cookies, name, options) {
2958
3102
  }
2959
3103
  function serializeCookie(name, value, options = {}) {
2960
3104
  const parts = [`${encodeURIComponent(name)}=${encodeURIComponent(value)}`];
2961
- if (options.maxAge !== void 0) parts.push(`Max-Age=${options.maxAge}`);
2962
- if (options.domain) parts.push(`Domain=${options.domain}`);
2963
- if (options.path) parts.push(`Path=${options.path}`);
2964
- if (options.expires) parts.push(`Expires=${options.expires.toUTCString()}`);
2965
- if (options.httpOnly) parts.push("HttpOnly");
2966
- 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
+ }
2967
3123
  if (options.sameSite) {
2968
3124
  const sameSite = options.sameSite.charAt(0).toUpperCase() + options.sameSite.slice(1);
2969
3125
  parts.push(`SameSite=${sameSite}`);
@@ -2976,20 +3132,14 @@ function appendSetCookie(headers, name, value, options) {
2976
3132
  function setAuthCookies(cookies, tokens, settings = {}) {
2977
3133
  const accessName = getAccessTokenCookieName(settings.names);
2978
3134
  const refreshName = getRefreshTokenCookieName(settings.names);
2979
- const accessOptions = accessTokenCookieOptions(
2980
- tokens.accessToken,
2981
- settings.options?.accessToken
2982
- );
3135
+ const accessOptions = accessTokenCookieOptions(tokens.accessToken, settings.options?.accessToken);
2983
3136
  setCookie(cookies, accessName, tokens.accessToken, accessOptions);
2984
3137
  if (tokens.refreshToken) {
2985
3138
  setCookie(
2986
3139
  cookies,
2987
3140
  refreshName,
2988
3141
  tokens.refreshToken,
2989
- refreshTokenCookieOptions(
2990
- tokens.refreshToken,
2991
- settings.options?.refreshToken
2992
- )
3142
+ refreshTokenCookieOptions(tokens.refreshToken, settings.options?.refreshToken)
2993
3143
  );
2994
3144
  }
2995
3145
  }
@@ -3015,10 +3165,7 @@ function setAuthCookieHeaders(headers, tokens, settings = {}) {
3015
3165
  headers,
3016
3166
  refreshName,
3017
3167
  tokens.refreshToken,
3018
- refreshTokenCookieOptions(
3019
- tokens.refreshToken,
3020
- settings.options?.refreshToken
3021
- )
3168
+ refreshTokenCookieOptions(tokens.refreshToken, settings.options?.refreshToken)
3022
3169
  );
3023
3170
  }
3024
3171
  }
@@ -3061,10 +3208,14 @@ function toRefreshError(response, body) {
3061
3208
  );
3062
3209
  }
3063
3210
  async function readErrorCode(response) {
3064
- if (response.status !== 401) return null;
3211
+ if (response.status !== 401) {
3212
+ return null;
3213
+ }
3065
3214
  try {
3066
3215
  const body = await response.clone().json();
3067
- if (!body || typeof body !== "object") return null;
3216
+ if (!body || typeof body !== "object") {
3217
+ return null;
3218
+ }
3068
3219
  const candidate = body.error ?? body.code;
3069
3220
  return typeof candidate === "string" ? candidate : null;
3070
3221
  } catch {
@@ -3083,7 +3234,9 @@ function withAuthHeader(init, token) {
3083
3234
  };
3084
3235
  }
3085
3236
  function getRequestUrl(input) {
3086
- if (typeof input === "string") return input;
3237
+ if (typeof input === "string") {
3238
+ return input;
3239
+ }
3087
3240
  if (typeof Request !== "undefined" && input instanceof Request) {
3088
3241
  return input.url;
3089
3242
  }
@@ -3101,21 +3254,19 @@ function createBrowserClient(options = {}) {
3101
3254
  "Missing InsForge baseUrl or anonKey. Pass baseUrl and anonKey to createBrowserClient() or set NEXT_PUBLIC_INSFORGE_URL and NEXT_PUBLIC_INSFORGE_ANON_KEY."
3102
3255
  );
3103
3256
  }
3104
- let accessToken = getBrowserCookie(
3105
- getAccessTokenCookieName(options.names)
3106
- );
3257
+ let accessToken = getBrowserCookie(getAccessTokenCookieName(options.names));
3107
3258
  const refreshUrl = options.refreshUrl ?? "/api/auth/refresh";
3108
3259
  const fetchImpl = options.fetch ?? (globalThis.fetch ? globalThis.fetch.bind(globalThis) : void 0);
3109
3260
  let client;
3110
3261
  let sessionChecked = false;
3111
3262
  let refreshPromise = null;
3112
3263
  const refreshFromRoute = () => {
3113
- if (refreshPromise) return refreshPromise;
3264
+ if (refreshPromise) {
3265
+ return refreshPromise;
3266
+ }
3114
3267
  refreshPromise = (async () => {
3115
3268
  if (!fetchImpl) {
3116
- throw new Error(
3117
- "Fetch is not available. Please provide a fetch implementation."
3118
- );
3269
+ throw new Error("Fetch is not available. Please provide a fetch implementation.");
3119
3270
  }
3120
3271
  const response = await fetchImpl(refreshUrl, {
3121
3272
  method: "POST",
@@ -3132,11 +3283,15 @@ function createBrowserClient(options = {}) {
3132
3283
  }
3133
3284
  throw error;
3134
3285
  }
3135
- if (!body || typeof body !== "object") return null;
3286
+ if (!body || typeof body !== "object") {
3287
+ return null;
3288
+ }
3136
3289
  const refreshBody = body;
3137
- if (!refreshBody.accessToken || !refreshBody.user) return null;
3290
+ if (!refreshBody.accessToken || !refreshBody.user) {
3291
+ return null;
3292
+ }
3138
3293
  accessToken = refreshBody.accessToken;
3139
- client?.setAccessToken(refreshBody.accessToken);
3294
+ client?.setAccessToken(refreshBody.accessToken, AuthChangeEvent.TOKEN_REFRESHED);
3140
3295
  return refreshBody;
3141
3296
  })().finally(() => {
3142
3297
  sessionChecked = true;
@@ -3150,9 +3305,7 @@ function createBrowserClient(options = {}) {
3150
3305
  };
3151
3306
  const ssrFetch = async (input, init) => {
3152
3307
  if (!fetchImpl) {
3153
- throw new Error(
3154
- "Fetch is not available. Please provide a fetch implementation."
3155
- );
3308
+ throw new Error("Fetch is not available. Please provide a fetch implementation.");
3156
3309
  }
3157
3310
  if (shouldSkipRefresh(input)) {
3158
3311
  return fetchImpl(input, init);
@@ -3189,9 +3342,9 @@ function createBrowserClient(options = {}) {
3189
3342
  }
3190
3343
  });
3191
3344
  const setAccessToken = client.setAccessToken.bind(client);
3192
- client.setAccessToken = (token) => {
3345
+ client.setAccessToken = (token, event) => {
3193
3346
  accessToken = token;
3194
- setAccessToken(token);
3347
+ setAccessToken(token, event);
3195
3348
  };
3196
3349
  if (accessToken) {
3197
3350
  client.setAccessToken(accessToken);
@@ -3215,10 +3368,7 @@ function createServerClient(options = {}) {
3215
3368
  "Missing InsForge baseUrl or anonKey. Pass baseUrl and anonKey to createServerClient() or set NEXT_PUBLIC_INSFORGE_URL and NEXT_PUBLIC_INSFORGE_ANON_KEY."
3216
3369
  );
3217
3370
  }
3218
- const accessToken = options.accessToken ?? getCookieValue(
3219
- options.cookies,
3220
- getAccessTokenCookieName(options.names)
3221
- );
3371
+ const accessToken = options.accessToken ?? getCookieValue(options.cookies, getAccessTokenCookieName(options.names));
3222
3372
  return new InsForgeClient({
3223
3373
  ...options,
3224
3374
  baseUrl,
@@ -3241,7 +3391,9 @@ function jsonResponse(body, init = {}, headers = new Headers(init.headers)) {
3241
3391
  });
3242
3392
  }
3243
3393
  function normalizeError(error) {
3244
- if (error instanceof InsForgeError) return error;
3394
+ if (error instanceof InsForgeError) {
3395
+ return error;
3396
+ }
3245
3397
  if (error && typeof error === "object") {
3246
3398
  const body = error;
3247
3399
  return new InsForgeError(
@@ -3258,18 +3410,21 @@ function normalizeError(error) {
3258
3410
  }
3259
3411
  async function readJson(response) {
3260
3412
  const contentType = response.headers.get("content-type");
3261
- if (!contentType?.includes("json")) return null;
3413
+ if (!contentType?.includes("json")) {
3414
+ return null;
3415
+ }
3262
3416
  return response.json();
3263
3417
  }
3264
3418
  function readRefreshToken(options) {
3265
- if (options.refreshToken) return options.refreshToken;
3419
+ if (options.refreshToken) {
3420
+ return options.refreshToken;
3421
+ }
3266
3422
  const refreshCookieName = getRefreshTokenCookieName(options.names);
3267
3423
  const cookieValue = getCookieValue(options.cookies, refreshCookieName);
3268
- if (cookieValue) return cookieValue;
3269
- return getCookieValueFromHeader(
3270
- options.request?.headers.get("cookie"),
3271
- refreshCookieName
3272
- );
3424
+ if (cookieValue) {
3425
+ return cookieValue;
3426
+ }
3427
+ return getCookieValueFromHeader(options.request?.headers.get("cookie"), refreshCookieName);
3273
3428
  }
3274
3429
  async function refreshAuth(options = {}) {
3275
3430
  const headers = new Headers();
@@ -3310,9 +3465,7 @@ async function refreshAuth(options = {}) {
3310
3465
  }
3311
3466
  const fetchImpl = options.fetch ?? (globalThis.fetch ? globalThis.fetch.bind(globalThis) : void 0);
3312
3467
  if (!fetchImpl) {
3313
- throw new Error(
3314
- "Fetch is not available. Please provide a fetch implementation."
3315
- );
3468
+ throw new Error("Fetch is not available. Please provide a fetch implementation.");
3316
3469
  }
3317
3470
  const requestHeaders = new Headers(options.headers);
3318
3471
  requestHeaders.set("Authorization", `Bearer ${anonKey}`);
@@ -3393,7 +3546,9 @@ function createRefreshAuthRouter(options = {}) {
3393
3546
 
3394
3547
  // src/ssr/auth-actions.ts
3395
3548
  function persistSessionCookies(cookies, data, settings) {
3396
- if (!data?.accessToken) return;
3549
+ if (!data?.accessToken) {
3550
+ return;
3551
+ }
3397
3552
  setAuthCookies(
3398
3553
  cookies,
3399
3554
  {
@@ -3404,7 +3559,9 @@ function persistSessionCookies(cookies, data, settings) {
3404
3559
  );
3405
3560
  }
3406
3561
  function sanitizeAuthData(data) {
3407
- if (!data) return null;
3562
+ if (!data) {
3563
+ return null;
3564
+ }
3408
3565
  const {
3409
3566
  accessToken: _accessToken,
3410
3567
  refreshToken: _refreshToken,
@@ -3454,32 +3611,20 @@ function createAuthActions(options = {}) {
3454
3611
  signInWithPassword: async (request) => {
3455
3612
  const result = await createClient().auth.signInWithPassword(request);
3456
3613
  persistSessionCookies(writeCookies, result.data, cookieSettings);
3457
- return toSafeAuthResult(
3458
- result
3459
- );
3614
+ return toSafeAuthResult(result);
3460
3615
  },
3461
3616
  signInWithOAuth: async (providerOrOptions, signInOptions) => {
3462
- return createClient().auth.signInWithOAuth(
3463
- providerOrOptions,
3464
- signInOptions
3465
- );
3617
+ return createClient().auth.signInWithOAuth(providerOrOptions, signInOptions);
3466
3618
  },
3467
3619
  signInWithIdToken: async (credentials) => {
3468
3620
  const result = await createClient().auth.signInWithIdToken(credentials);
3469
3621
  persistSessionCookies(writeCookies, result.data, cookieSettings);
3470
- return toSafeAuthResult(
3471
- result
3472
- );
3622
+ return toSafeAuthResult(result);
3473
3623
  },
3474
3624
  exchangeOAuthCode: async (code, codeVerifier) => {
3475
- const result = await createClient().auth.exchangeOAuthCode(
3476
- code,
3477
- codeVerifier
3478
- );
3625
+ const result = await createClient().auth.exchangeOAuthCode(code, codeVerifier);
3479
3626
  persistSessionCookies(writeCookies, result.data, cookieSettings);
3480
- return toSafeAuthResult(
3481
- result
3482
- );
3627
+ return toSafeAuthResult(result);
3483
3628
  },
3484
3629
  verifyEmail: async (request) => {
3485
3630
  const result = await createClient().auth.verifyEmail(request);
@@ -3498,10 +3643,7 @@ function createAuthActions(options = {}) {
3498
3643
  async function updateSession(options) {
3499
3644
  const accessCookieName = getAccessTokenCookieName(options.names);
3500
3645
  const refreshCookieName = getRefreshTokenCookieName(options.names);
3501
- const accessToken = getCookieValue(
3502
- options.requestCookies,
3503
- accessCookieName
3504
- );
3646
+ const accessToken = getCookieValue(options.requestCookies, accessCookieName);
3505
3647
  if (accessToken && !isJwtExpiredOrExpiring(accessToken, options.refreshLeewaySeconds)) {
3506
3648
  return {
3507
3649
  refreshed: false,
@@ -3509,10 +3651,7 @@ async function updateSession(options) {
3509
3651
  error: null
3510
3652
  };
3511
3653
  }
3512
- const refreshToken = getCookieValue(
3513
- options.requestCookies,
3514
- refreshCookieName
3515
- );
3654
+ const refreshToken = getCookieValue(options.requestCookies, refreshCookieName);
3516
3655
  if (!refreshToken) {
3517
3656
  if (accessToken) {
3518
3657
  clearAuthCookies(options.requestCookies, options);