@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.js CHANGED
@@ -94,7 +94,9 @@ function redactHeaders(headers) {
94
94
  return redacted;
95
95
  }
96
96
  function sanitizeBody(body) {
97
- if (body === null || body === void 0) return body;
97
+ if (body === null || body === void 0) {
98
+ return body;
99
+ }
98
100
  if (typeof body === "string") {
99
101
  try {
100
102
  const parsed = JSON.parse(body);
@@ -103,7 +105,9 @@ function sanitizeBody(body) {
103
105
  return body;
104
106
  }
105
107
  }
106
- if (Array.isArray(body)) return body.map(sanitizeBody);
108
+ if (Array.isArray(body)) {
109
+ return body.map(sanitizeBody);
110
+ }
107
111
  if (typeof body === "object") {
108
112
  const sanitized = {};
109
113
  for (const [key, value] of Object.entries(body)) {
@@ -118,7 +122,9 @@ function sanitizeBody(body) {
118
122
  return body;
119
123
  }
120
124
  function formatBody(body) {
121
- if (body === void 0 || body === null) return "";
125
+ if (body === void 0 || body === null) {
126
+ return "";
127
+ }
122
128
  if (typeof body === "string") {
123
129
  try {
124
130
  return JSON.stringify(JSON.parse(body), null, 2);
@@ -155,7 +161,9 @@ var Logger = class {
155
161
  * @param args - Additional arguments to pass to the log function
156
162
  */
157
163
  log(message, ...args) {
158
- if (!this.enabled) return;
164
+ if (!this.enabled) {
165
+ return;
166
+ }
159
167
  const formatted = `[InsForge Debug] ${message}`;
160
168
  if (this.customLog) {
161
169
  this.customLog(formatted, ...args);
@@ -169,7 +177,9 @@ var Logger = class {
169
177
  * @param args - Additional arguments to pass to the log function
170
178
  */
171
179
  warn(message, ...args) {
172
- if (!this.enabled) return;
180
+ if (!this.enabled) {
181
+ return;
182
+ }
173
183
  const formatted = `[InsForge Debug] ${message}`;
174
184
  if (this.customLog) {
175
185
  this.customLog(formatted, ...args);
@@ -183,7 +193,9 @@ var Logger = class {
183
193
  * @param args - Additional arguments to pass to the log function
184
194
  */
185
195
  error(message, ...args) {
186
- if (!this.enabled) return;
196
+ if (!this.enabled) {
197
+ return;
198
+ }
187
199
  const formatted = `[InsForge Debug] ${message}`;
188
200
  if (this.customLog) {
189
201
  this.customLog(formatted, ...args);
@@ -200,10 +212,10 @@ var Logger = class {
200
212
  * @param body - Request body (sensitive fields will be masked)
201
213
  */
202
214
  logRequest(method, url, headers, body) {
203
- if (!this.enabled) return;
204
- const parts = [
205
- `\u2192 ${method} ${url}`
206
- ];
215
+ if (!this.enabled) {
216
+ return;
217
+ }
218
+ const parts = [`\u2192 ${method} ${url}`];
207
219
  if (headers && Object.keys(headers).length > 0) {
208
220
  parts.push(` Headers: ${JSON.stringify(redactHeaders(headers))}`);
209
221
  }
@@ -224,10 +236,10 @@ var Logger = class {
224
236
  * @param body - Response body (sensitive fields will be masked, large bodies truncated)
225
237
  */
226
238
  logResponse(method, url, status, durationMs, body) {
227
- if (!this.enabled) return;
228
- const parts = [
229
- `\u2190 ${method} ${url} ${status} (${durationMs}ms)`
230
- ];
239
+ if (!this.enabled) {
240
+ return;
241
+ }
242
+ const parts = [`\u2190 ${method} ${url} ${status} (${durationMs}ms)`];
231
243
  const formattedBody = formatBody(sanitizeBody(body));
232
244
  if (formattedBody) {
233
245
  const truncated = formattedBody.length > 1e3 ? formattedBody.slice(0, 1e3) + "... [truncated]" : formattedBody;
@@ -242,21 +254,34 @@ var Logger = class {
242
254
  };
243
255
 
244
256
  // src/lib/token-manager.ts
257
+ var AuthChangeEvent = {
258
+ SIGNED_IN: "signedIn",
259
+ SIGNED_OUT: "signedOut",
260
+ TOKEN_REFRESHED: "tokenRefreshed"
261
+ };
245
262
  var CSRF_TOKEN_COOKIE = "insforge_csrf_token";
246
263
  function getCsrfToken() {
247
- if (typeof document === "undefined") return null;
264
+ if (typeof document === "undefined") {
265
+ return null;
266
+ }
248
267
  const match = document.cookie.split(";").find((c) => c.trim().startsWith(`${CSRF_TOKEN_COOKIE}=`));
249
- if (!match) return null;
268
+ if (!match) {
269
+ return null;
270
+ }
250
271
  return match.split("=")[1] || null;
251
272
  }
252
273
  function setCsrfToken(token) {
253
- if (typeof document === "undefined") return;
274
+ if (typeof document === "undefined") {
275
+ return;
276
+ }
254
277
  const maxAge = 7 * 24 * 60 * 60;
255
278
  const secure = typeof window !== "undefined" && window.location.protocol === "https:" ? "; Secure" : "";
256
279
  document.cookie = `${CSRF_TOKEN_COOKIE}=${encodeURIComponent(token)}; path=/; max-age=${maxAge}; SameSite=Lax${secure}`;
257
280
  }
258
281
  function clearCsrfToken() {
259
- if (typeof document === "undefined") return;
282
+ if (typeof document === "undefined") {
283
+ return;
284
+ }
260
285
  const secure = typeof window !== "undefined" && window.location.protocol === "https:" ? "; Secure" : "";
261
286
  document.cookie = `${CSRF_TOKEN_COOKIE}=; path=/; max-age=0; SameSite=Lax${secure}`;
262
287
  }
@@ -265,25 +290,26 @@ var TokenManager = class {
265
290
  // In-memory storage
266
291
  this.accessToken = null;
267
292
  this.user = null;
268
- // Callback for token changes (used by realtime to reconnect with new token)
269
- this.onTokenChange = null;
293
+ this.authStateChangeCallbacks = /* @__PURE__ */ new Map();
270
294
  }
271
295
  /**
272
296
  * Save session in memory
273
297
  */
274
- saveSession(session) {
298
+ saveSession(session, event = AuthChangeEvent.SIGNED_IN) {
275
299
  const tokenChanged = session.accessToken !== this.accessToken;
276
300
  this.accessToken = session.accessToken;
277
301
  this.user = session.user;
278
- if (tokenChanged && this.onTokenChange) {
279
- this.onTokenChange();
302
+ if (tokenChanged) {
303
+ this.notifyAuthStateChange(event);
280
304
  }
281
305
  }
282
306
  /**
283
307
  * Get current session
284
308
  */
285
309
  getSession() {
286
- if (!this.accessToken || !this.user) return null;
310
+ if (!this.accessToken || !this.user) {
311
+ return null;
312
+ }
287
313
  return {
288
314
  accessToken: this.accessToken,
289
315
  user: this.user
@@ -298,11 +324,11 @@ var TokenManager = class {
298
324
  /**
299
325
  * Set access token
300
326
  */
301
- setAccessToken(token) {
327
+ setAccessToken(token, event = AuthChangeEvent.SIGNED_IN) {
302
328
  const tokenChanged = token !== this.accessToken;
303
329
  this.accessToken = token;
304
- if (tokenChanged && this.onTokenChange) {
305
- this.onTokenChange();
330
+ if (tokenChanged) {
331
+ this.notifyAuthStateChange(event);
306
332
  }
307
333
  }
308
334
  /**
@@ -324,22 +350,74 @@ var TokenManager = class {
324
350
  const hadToken = this.accessToken !== null;
325
351
  this.accessToken = null;
326
352
  this.user = null;
327
- if (hadToken && this.onTokenChange) {
328
- this.onTokenChange();
353
+ if (hadToken) {
354
+ this.notifyAuthStateChange(AuthChangeEvent.SIGNED_OUT);
355
+ }
356
+ }
357
+ onAuthStateChange(callback) {
358
+ const id = Symbol("auth-state-change");
359
+ this.authStateChangeCallbacks.set(id, callback);
360
+ return () => this.authStateChangeCallbacks.delete(id);
361
+ }
362
+ notifyAuthStateChange(event) {
363
+ for (const callback of this.authStateChangeCallbacks.values()) {
364
+ try {
365
+ callback(event);
366
+ } catch (error) {
367
+ console.error("Error in auth state change callback:", error);
368
+ }
329
369
  }
330
370
  }
331
371
  };
332
372
 
373
+ // src/lib/jwt.ts
374
+ function decodeBase64Url(input) {
375
+ const normalized = input.replace(/-/g, "+").replace(/_/g, "/");
376
+ const padded = normalized.padEnd(normalized.length + (4 - normalized.length % 4) % 4, "=");
377
+ const binary = atob(padded);
378
+ const bytes = Uint8Array.from(binary, (char) => char.charCodeAt(0));
379
+ return new TextDecoder().decode(bytes);
380
+ }
381
+ function getJwtExpiration(token) {
382
+ if (!token) {
383
+ return null;
384
+ }
385
+ const [, payload] = token.split(".");
386
+ if (!payload) {
387
+ return null;
388
+ }
389
+ try {
390
+ const parsed = JSON.parse(decodeBase64Url(payload));
391
+ if (typeof parsed.exp !== "number" || !Number.isFinite(parsed.exp)) {
392
+ return null;
393
+ }
394
+ return new Date(parsed.exp * 1e3);
395
+ } catch {
396
+ return null;
397
+ }
398
+ }
399
+ function isJwtExpiredOrExpiring(token, leewaySeconds = 60) {
400
+ if (!token) {
401
+ return false;
402
+ }
403
+ const expires = getJwtExpiration(token);
404
+ if (!expires) {
405
+ return true;
406
+ }
407
+ return expires.getTime() <= Date.now() + leewaySeconds * 1e3;
408
+ }
409
+
333
410
  // src/lib/http-client.ts
334
411
  var RETRYABLE_STATUS_CODES = /* @__PURE__ */ new Set([500, 502, 503, 504]);
335
412
  var IDEMPOTENT_METHODS = /* @__PURE__ */ new Set(["GET", "HEAD", "PUT", "DELETE", "OPTIONS"]);
336
- var REFRESHABLE_AUTH_ERROR_CODES = /* @__PURE__ */ new Set([
337
- "AUTH_UNAUTHORIZED",
338
- "PGRST301"
339
- ]);
413
+ var REFRESHABLE_AUTH_ERROR_CODES = /* @__PURE__ */ new Set(["AUTH_UNAUTHORIZED", "PGRST301"]);
340
414
  function serializeBody(method, body, headers) {
341
- if (body === void 0) return void 0;
342
- if (method === "GET" || method === "HEAD") return void 0;
415
+ if (body === void 0) {
416
+ return void 0;
417
+ }
418
+ if (method === "GET" || method === "HEAD") {
419
+ return void 0;
420
+ }
343
421
  if (typeof FormData !== "undefined" && body instanceof FormData) {
344
422
  return body;
345
423
  }
@@ -347,7 +425,9 @@ function serializeBody(method, body, headers) {
347
425
  return JSON.stringify(body);
348
426
  }
349
427
  async function parseResponse(response) {
350
- if (response.status === 204) return void 0;
428
+ if (response.status === 204) {
429
+ return void 0;
430
+ }
351
431
  let data;
352
432
  const contentType = response.headers.get("content-type");
353
433
  try {
@@ -449,31 +529,24 @@ var HttpClient = class {
449
529
  return statusCode === 401 && REFRESHABLE_AUTH_ERROR_CODES.has(errorCode ?? "") && !this.config.isServerMode && !this.config.accessToken && !this.config.edgeFunctionToken && !options.skipAuthRefresh && authToken !== null;
450
530
  }
451
531
  async fetchWithRetry(args) {
452
- const {
453
- method,
454
- url,
455
- headers,
456
- body,
457
- fetchOptions,
458
- callerSignal,
459
- maxAttempts
460
- } = args;
532
+ const { method, url, headers, body, fetchOptions, callerSignal, maxAttempts } = args;
461
533
  let lastError;
462
534
  for (let attempt = 0; attempt <= maxAttempts; attempt++) {
463
535
  if (attempt > 0) {
464
536
  const delay = this.computeRetryDelay(attempt);
465
- this.logger.warn(
466
- `Retry ${attempt}/${maxAttempts} for ${method} ${url} in ${delay}ms`
467
- );
468
- if (callerSignal?.aborted) throw callerSignal.reason;
537
+ this.logger.warn(`Retry ${attempt}/${maxAttempts} for ${method} ${url} in ${delay}ms`);
538
+ if (callerSignal?.aborted) {
539
+ throw callerSignal.reason;
540
+ }
469
541
  await new Promise((resolve, reject) => {
470
542
  const onAbort = () => {
471
543
  clearTimeout(timer2);
472
544
  reject(callerSignal.reason);
473
545
  };
474
546
  const timer2 = setTimeout(() => {
475
- if (callerSignal)
547
+ if (callerSignal) {
476
548
  callerSignal.removeEventListener("abort", onAbort);
549
+ }
477
550
  resolve();
478
551
  }, delay);
479
552
  if (callerSignal) {
@@ -515,7 +588,9 @@ var HttpClient = class {
515
588
  ...controller ? { signal: controller.signal } : {}
516
589
  });
517
590
  if (this.isRetryableStatus(response.status) && attempt < maxAttempts) {
518
- if (timer !== void 0) clearTimeout(timer);
591
+ if (timer !== void 0) {
592
+ clearTimeout(timer);
593
+ }
519
594
  await response.body?.cancel();
520
595
  lastError = new InsForgeError(
521
596
  `Server error: ${response.status} ${response.statusText}`,
@@ -524,10 +599,14 @@ var HttpClient = class {
524
599
  );
525
600
  continue;
526
601
  }
527
- if (timer !== void 0) clearTimeout(timer);
602
+ if (timer !== void 0) {
603
+ clearTimeout(timer);
604
+ }
528
605
  return response;
529
606
  } catch (err) {
530
- if (timer !== void 0) clearTimeout(timer);
607
+ if (timer !== void 0) {
608
+ clearTimeout(timer);
609
+ }
531
610
  if (err?.name === "AbortError") {
532
611
  if (controller && controller.signal.aborted && this.timeout > 0 && !callerSignal?.aborted) {
533
612
  throw new InsForgeError(
@@ -549,11 +628,7 @@ var HttpClient = class {
549
628
  );
550
629
  }
551
630
  }
552
- throw lastError || new InsForgeError(
553
- "Request failed after all retry attempts",
554
- 0,
555
- "NETWORK_ERROR"
556
- );
631
+ throw lastError || new InsForgeError("Request failed after all retry attempts", 0, "NETWORK_ERROR");
557
632
  }
558
633
  /**
559
634
  * Performs an HTTP request with automatic retry and timeout handling.
@@ -633,31 +708,15 @@ var HttpClient = class {
633
708
  }
634
709
  throw err;
635
710
  }
636
- this.logger.logResponse(
637
- method,
638
- url,
639
- response.status,
640
- Date.now() - startTime,
641
- data
642
- );
711
+ this.logger.logResponse(method, url, response.status, Date.now() - startTime, data);
643
712
  return data;
644
713
  }
645
714
  async request(method, path, options = {}) {
646
715
  const tokenUsed = this.userToken;
647
716
  try {
648
- return await this.handleRequest(
649
- method,
650
- path,
651
- { ...options },
652
- tokenUsed
653
- );
717
+ return await this.handleRequest(method, path, { ...options }, tokenUsed);
654
718
  } catch (error) {
655
- if (!(error instanceof InsForgeError) || !this.shouldRefreshAccessToken(
656
- error.statusCode,
657
- error.error,
658
- tokenUsed,
659
- options
660
- )) {
719
+ if (!(error instanceof InsForgeError) || !this.shouldRefreshAccessToken(error.statusCode, error.error, tokenUsed, options)) {
661
720
  throw error;
662
721
  }
663
722
  if (tokenUsed !== this.userToken) {
@@ -742,12 +801,7 @@ var HttpClient = class {
742
801
  callerSignal,
743
802
  maxAttempts
744
803
  });
745
- this.logger.logResponse(
746
- method,
747
- url,
748
- response.status,
749
- Date.now() - startTime
750
- );
804
+ this.logger.logResponse(method, url, response.status, Date.now() - startTime);
751
805
  let errorCode = null;
752
806
  if (response.status === 401) {
753
807
  try {
@@ -761,12 +815,7 @@ var HttpClient = class {
761
815
  } catch {
762
816
  }
763
817
  }
764
- if (!this.shouldRefreshAccessToken(
765
- response.status,
766
- errorCode,
767
- tokenUsed,
768
- options
769
- )) {
818
+ if (!this.shouldRefreshAccessToken(response.status, errorCode, tokenUsed, options)) {
770
819
  return response;
771
820
  }
772
821
  if (tokenUsed !== this.userToken) {
@@ -860,10 +909,30 @@ var HttpClient = class {
860
909
  })();
861
910
  return this.refreshPromise;
862
911
  }
912
+ /** Returns a token safe to use for a new connection handshake. */
913
+ async getValidAccessToken(leewaySeconds = 60) {
914
+ const accessToken = this.tokenManager.getAccessToken() ?? this.userToken;
915
+ if (!accessToken || !isJwtExpiredOrExpiring(accessToken, leewaySeconds)) {
916
+ return accessToken;
917
+ }
918
+ const canRefresh = !this.config.isServerMode && !this.config.accessToken && !this.config.edgeFunctionToken && this.userToken !== null;
919
+ if (!canRefresh) {
920
+ return accessToken;
921
+ }
922
+ try {
923
+ const refreshed = await this.refreshAndSaveSession();
924
+ return refreshed.accessToken;
925
+ } catch (error) {
926
+ if (error instanceof InsForgeError && (error.statusCode === 401 || error.statusCode === 403) && this.userToken === accessToken) {
927
+ this.clearAuthSession();
928
+ }
929
+ throw error;
930
+ }
931
+ }
863
932
  async refreshAndSaveSession() {
864
933
  const newTokenData = await this.refreshAccessToken();
865
934
  this.setAuthToken(newTokenData.accessToken);
866
- this.tokenManager.saveSession(newTokenData);
935
+ this.tokenManager.saveSession(newTokenData, AuthChangeEvent.TOKEN_REFRESHED);
867
936
  if (newTokenData.csrfToken) {
868
937
  setCsrfToken(newTokenData.csrfToken);
869
938
  }
@@ -959,11 +1028,15 @@ var Auth = class {
959
1028
  isServerMode() {
960
1029
  return !!this.options.isServerMode;
961
1030
  }
1031
+ /** Subscribe to SDK authentication state changes. */
1032
+ onAuthStateChange(callback) {
1033
+ return this.tokenManager.onAuthStateChange(callback);
1034
+ }
962
1035
  /**
963
1036
  * Save session from API response
964
1037
  * Handles token storage, CSRF token, and HTTP auth header
965
1038
  */
966
- saveSessionFromResponse(response) {
1039
+ saveSessionFromResponse(response, event = AuthChangeEvent.SIGNED_IN) {
967
1040
  if (!response.accessToken || !response.user) {
968
1041
  return false;
969
1042
  }
@@ -975,7 +1048,7 @@ var Auth = class {
975
1048
  setCsrfToken(response.csrfToken);
976
1049
  }
977
1050
  if (!this.isServerMode()) {
978
- this.tokenManager.saveSession(session);
1051
+ this.tokenManager.saveSession(session, event);
979
1052
  }
980
1053
  this.http.setAuthToken(response.accessToken);
981
1054
  this.http.setRefreshToken(response.refreshToken ?? null);
@@ -989,7 +1062,9 @@ var Auth = class {
989
1062
  * Supports PKCE flow (insforge_code)
990
1063
  */
991
1064
  async detectAuthCallback() {
992
- if (this.isServerMode() || typeof window === "undefined") return;
1065
+ if (this.isServerMode() || typeof window === "undefined") {
1066
+ return;
1067
+ }
993
1068
  try {
994
1069
  const params = new URLSearchParams(window.location.search);
995
1070
  const error = params.get("error");
@@ -1051,10 +1126,16 @@ var Auth = class {
1051
1126
  async signOut() {
1052
1127
  try {
1053
1128
  try {
1129
+ const serverMode = this.isServerMode();
1130
+ const csrfToken = !serverMode ? getCsrfToken() : null;
1054
1131
  await this.http.post(
1055
- this.isServerMode() ? "/api/auth/logout?client_type=mobile" : "/api/auth/logout",
1132
+ serverMode ? "/api/auth/logout?client_type=mobile" : "/api/auth/logout",
1056
1133
  void 0,
1057
- { credentials: "include", skipAuthRefresh: true }
1134
+ {
1135
+ credentials: "include",
1136
+ skipAuthRefresh: true,
1137
+ ...csrfToken ? { headers: { "X-CSRF-Token": csrfToken } } : {}
1138
+ }
1058
1139
  );
1059
1140
  } catch {
1060
1141
  }
@@ -1091,11 +1172,7 @@ var Auth = class {
1091
1172
  if (!signInOptions || !signInOptions.redirectTo) {
1092
1173
  return {
1093
1174
  data: {},
1094
- error: new InsForgeError(
1095
- "Redirect URI is required",
1096
- 400,
1097
- import_shared_schemas.ERROR_CODES.INVALID_INPUT
1098
- )
1175
+ error: new InsForgeError("Redirect URI is required", 400, import_shared_schemas.ERROR_CODES.INVALID_INPUT)
1099
1176
  };
1100
1177
  }
1101
1178
  const { provider } = signInOptions;
@@ -1170,10 +1247,7 @@ var Auth = class {
1170
1247
  error: null
1171
1248
  };
1172
1249
  } catch (error) {
1173
- return wrapError(
1174
- error,
1175
- "An unexpected error occurred during OAuth code exchange"
1176
- );
1250
+ return wrapError(error, "An unexpected error occurred during OAuth code exchange");
1177
1251
  }
1178
1252
  }
1179
1253
  /**
@@ -1200,10 +1274,7 @@ var Auth = class {
1200
1274
  error: null
1201
1275
  };
1202
1276
  } catch (error) {
1203
- return wrapError(
1204
- error,
1205
- "An unexpected error occurred during ID token sign in"
1206
- );
1277
+ return wrapError(error, "An unexpected error occurred during ID token sign in");
1207
1278
  }
1208
1279
  }
1209
1280
  // ============================================================================
@@ -1244,14 +1315,11 @@ var Auth = class {
1244
1315
  }
1245
1316
  );
1246
1317
  if (response.accessToken) {
1247
- this.saveSessionFromResponse(response);
1318
+ this.saveSessionFromResponse(response, AuthChangeEvent.TOKEN_REFRESHED);
1248
1319
  }
1249
1320
  return { data: response, error: null };
1250
1321
  } catch (error) {
1251
- return wrapError(
1252
- error,
1253
- "An unexpected error occurred during session refresh"
1254
- );
1322
+ return wrapError(error, "An unexpected error occurred during session refresh");
1255
1323
  }
1256
1324
  }
1257
1325
  /**
@@ -1262,11 +1330,11 @@ var Auth = class {
1262
1330
  try {
1263
1331
  if (this.isServerMode()) {
1264
1332
  const accessToken = this.tokenManager.getAccessToken();
1265
- if (!accessToken) return { data: { user: null }, error: null };
1333
+ if (!accessToken) {
1334
+ return { data: { user: null }, error: null };
1335
+ }
1266
1336
  this.http.setAuthToken(accessToken);
1267
- const response = await this.http.get(
1268
- "/api/auth/sessions/current"
1269
- );
1337
+ const response = await this.http.get("/api/auth/sessions/current");
1270
1338
  const user = response.user ?? null;
1271
1339
  return { data: { user }, error: null };
1272
1340
  }
@@ -1304,25 +1372,17 @@ var Auth = class {
1304
1372
  // ============================================================================
1305
1373
  async getProfile(userId) {
1306
1374
  try {
1307
- const response = await this.http.get(
1308
- `/api/auth/profiles/${userId}`
1309
- );
1375
+ const response = await this.http.get(`/api/auth/profiles/${userId}`);
1310
1376
  return { data: response, error: null };
1311
1377
  } catch (error) {
1312
- return wrapError(
1313
- error,
1314
- "An unexpected error occurred while fetching user profile"
1315
- );
1378
+ return wrapError(error, "An unexpected error occurred while fetching user profile");
1316
1379
  }
1317
1380
  }
1318
1381
  async setProfile(profile) {
1319
1382
  try {
1320
- const response = await this.http.patch(
1321
- "/api/auth/profiles/current",
1322
- {
1323
- profile
1324
- }
1325
- );
1383
+ const response = await this.http.patch("/api/auth/profiles/current", {
1384
+ profile
1385
+ });
1326
1386
  const currentUser = this.tokenManager.getUser();
1327
1387
  if (!this.isServerMode() && currentUser && response.profile !== void 0) {
1328
1388
  this.tokenManager.setUser({
@@ -1332,10 +1392,7 @@ var Auth = class {
1332
1392
  }
1333
1393
  return { data: response, error: null };
1334
1394
  } catch (error) {
1335
- return wrapError(
1336
- error,
1337
- "An unexpected error occurred while updating user profile"
1338
- );
1395
+ return wrapError(error, "An unexpected error occurred while updating user profile");
1339
1396
  }
1340
1397
  }
1341
1398
  // ============================================================================
@@ -1348,10 +1405,7 @@ var Auth = class {
1348
1405
  });
1349
1406
  return { data: response, error: null };
1350
1407
  } catch (error) {
1351
- return wrapError(
1352
- error,
1353
- "An unexpected error occurred while sending verification email"
1354
- );
1408
+ return wrapError(error, "An unexpected error occurred while sending verification email");
1355
1409
  }
1356
1410
  }
1357
1411
  async verifyEmail(request) {
@@ -1367,10 +1421,7 @@ var Auth = class {
1367
1421
  }
1368
1422
  return { data: response, error: null };
1369
1423
  } catch (error) {
1370
- return wrapError(
1371
- error,
1372
- "An unexpected error occurred while verifying email"
1373
- );
1424
+ return wrapError(error, "An unexpected error occurred while verifying email");
1374
1425
  }
1375
1426
  }
1376
1427
  // ============================================================================
@@ -1383,10 +1434,7 @@ var Auth = class {
1383
1434
  });
1384
1435
  return { data: response, error: null };
1385
1436
  } catch (error) {
1386
- return wrapError(
1387
- error,
1388
- "An unexpected error occurred while sending password reset email"
1389
- );
1437
+ return wrapError(error, "An unexpected error occurred while sending password reset email");
1390
1438
  }
1391
1439
  }
1392
1440
  async exchangeResetPasswordToken(request) {
@@ -1398,10 +1446,7 @@ var Auth = class {
1398
1446
  );
1399
1447
  return { data: response, error: null };
1400
1448
  } catch (error) {
1401
- return wrapError(
1402
- error,
1403
- "An unexpected error occurred while verifying reset code"
1404
- );
1449
+ return wrapError(error, "An unexpected error occurred while verifying reset code");
1405
1450
  }
1406
1451
  }
1407
1452
  async resetPassword(request) {
@@ -1413,10 +1458,7 @@ var Auth = class {
1413
1458
  );
1414
1459
  return { data: response, error: null };
1415
1460
  } catch (error) {
1416
- return wrapError(
1417
- error,
1418
- "An unexpected error occurred while resetting password"
1419
- );
1461
+ return wrapError(error, "An unexpected error occurred while resetting password");
1420
1462
  }
1421
1463
  }
1422
1464
  // ============================================================================
@@ -1424,16 +1466,12 @@ var Auth = class {
1424
1466
  // ============================================================================
1425
1467
  async getPublicAuthConfig() {
1426
1468
  try {
1427
- const response = await this.http.get(
1428
- "/api/auth/public-config",
1429
- { skipAuthRefresh: true }
1430
- );
1469
+ const response = await this.http.get("/api/auth/public-config", {
1470
+ skipAuthRefresh: true
1471
+ });
1431
1472
  return { data: response, error: null };
1432
1473
  } catch (error) {
1433
- return wrapError(
1434
- error,
1435
- "An unexpected error occurred while fetching auth configuration"
1436
- );
1474
+ return wrapError(error, "An unexpected error occurred while fetching auth configuration");
1437
1475
  }
1438
1476
  }
1439
1477
  };
@@ -1460,12 +1498,30 @@ function createInsForgePostgrestFetch(httpClient) {
1460
1498
  };
1461
1499
  }
1462
1500
  var Database = class {
1463
- constructor(httpClient) {
1501
+ constructor(httpClient, defaultSchema) {
1464
1502
  this.postgrest = new import_postgrest_js.PostgrestClient("http://dummy", {
1465
1503
  fetch: createInsForgePostgrestFetch(httpClient),
1466
- headers: {}
1504
+ headers: {},
1505
+ ...defaultSchema ? { schema: defaultSchema } : {}
1467
1506
  });
1468
1507
  }
1508
+ /**
1509
+ * Select a non-default Postgres schema for the chained query. Maps to
1510
+ * PostgREST's `Accept-Profile` (reads) / `Content-Profile` (writes) header.
1511
+ * The schema must be exposed by the backend.
1512
+ *
1513
+ * @example
1514
+ * const { data } = await client.database
1515
+ * .schema('analytics')
1516
+ * .from('events')
1517
+ * .select('*');
1518
+ *
1519
+ * @example
1520
+ * await client.database.schema('analytics').rpc('rollup', { day: '2026-01-01' });
1521
+ */
1522
+ schema(schemaName) {
1523
+ return this.postgrest.schema(schemaName);
1524
+ }
1469
1525
  /**
1470
1526
  * Create a query builder for a table
1471
1527
  *
@@ -1573,11 +1629,7 @@ var StorageBucket = class {
1573
1629
  } catch (error) {
1574
1630
  return {
1575
1631
  data: null,
1576
- error: error instanceof InsForgeError ? error : new InsForgeError(
1577
- "Upload failed",
1578
- 500,
1579
- "STORAGE_ERROR"
1580
- )
1632
+ error: error instanceof InsForgeError ? error : new InsForgeError("Upload failed", 500, "STORAGE_ERROR")
1581
1633
  };
1582
1634
  }
1583
1635
  }
@@ -1623,11 +1675,7 @@ var StorageBucket = class {
1623
1675
  } catch (error) {
1624
1676
  return {
1625
1677
  data: null,
1626
- error: error instanceof InsForgeError ? error : new InsForgeError(
1627
- "Upload failed",
1628
- 500,
1629
- "STORAGE_ERROR"
1630
- )
1678
+ error: error instanceof InsForgeError ? error : new InsForgeError("Upload failed", 500, "STORAGE_ERROR")
1631
1679
  };
1632
1680
  }
1633
1681
  }
@@ -1655,13 +1703,10 @@ var StorageBucket = class {
1655
1703
  );
1656
1704
  }
1657
1705
  if (strategy.confirmRequired && strategy.confirmUrl) {
1658
- const confirmResponse = await this.http.post(
1659
- strategy.confirmUrl,
1660
- {
1661
- size: file.size,
1662
- contentType: file.type || "application/octet-stream"
1663
- }
1664
- );
1706
+ const confirmResponse = await this.http.post(strategy.confirmUrl, {
1707
+ size: file.size,
1708
+ contentType: file.type || "application/octet-stream"
1709
+ });
1665
1710
  return { data: confirmResponse, error: null };
1666
1711
  }
1667
1712
  return {
@@ -1676,11 +1721,7 @@ var StorageBucket = class {
1676
1721
  error: null
1677
1722
  };
1678
1723
  } catch (error) {
1679
- throw error instanceof InsForgeError ? error : new InsForgeError(
1680
- "Presigned upload failed",
1681
- 500,
1682
- "STORAGE_ERROR"
1683
- );
1724
+ throw error instanceof InsForgeError ? error : new InsForgeError("Presigned upload failed", 500, "STORAGE_ERROR");
1684
1725
  }
1685
1726
  }
1686
1727
  /**
@@ -1734,11 +1775,7 @@ var StorageBucket = class {
1734
1775
  } catch (error) {
1735
1776
  return {
1736
1777
  data: null,
1737
- error: error instanceof InsForgeError ? error : new InsForgeError(
1738
- "Download failed",
1739
- 500,
1740
- "STORAGE_ERROR"
1741
- )
1778
+ error: error instanceof InsForgeError ? error : new InsForgeError("Download failed", 500, "STORAGE_ERROR")
1742
1779
  };
1743
1780
  }
1744
1781
  }
@@ -1773,7 +1810,9 @@ var StorageBucket = class {
1773
1810
  } catch (error) {
1774
1811
  const status = error instanceof InsForgeError ? error.statusCode : void 0;
1775
1812
  const isMissingRoute = (status === 404 || status === 405) && !(error instanceof InsForgeError && error.error === "STORAGE_NOT_FOUND");
1776
- if (!isMissingRoute) throw error;
1813
+ if (!isMissingRoute) {
1814
+ throw error;
1815
+ }
1777
1816
  return await this.http.post(
1778
1817
  `/api/storage/buckets/${this.bucketName}/objects/${encoded}/download-strategy`,
1779
1818
  { expiresIn }
@@ -1849,10 +1888,18 @@ var StorageBucket = class {
1849
1888
  async list(options) {
1850
1889
  try {
1851
1890
  const params = {};
1852
- if (options?.prefix) params.prefix = options.prefix;
1853
- if (options?.search) params.search = options.search;
1854
- if (options?.limit) params.limit = options.limit.toString();
1855
- if (options?.offset) params.offset = options.offset.toString();
1891
+ if (options?.prefix) {
1892
+ params.prefix = options.prefix;
1893
+ }
1894
+ if (options?.search) {
1895
+ params.search = options.search;
1896
+ }
1897
+ if (options?.limit) {
1898
+ params.limit = options.limit.toString();
1899
+ }
1900
+ if (options?.offset) {
1901
+ params.offset = options.offset.toString();
1902
+ }
1856
1903
  const response = await this.http.get(
1857
1904
  `/api/storage/buckets/${this.bucketName}/objects`,
1858
1905
  { params }
@@ -1861,11 +1908,7 @@ var StorageBucket = class {
1861
1908
  } catch (error) {
1862
1909
  return {
1863
1910
  data: null,
1864
- error: error instanceof InsForgeError ? error : new InsForgeError(
1865
- "List failed",
1866
- 500,
1867
- "STORAGE_ERROR"
1868
- )
1911
+ error: error instanceof InsForgeError ? error : new InsForgeError("List failed", 500, "STORAGE_ERROR")
1869
1912
  };
1870
1913
  }
1871
1914
  }
@@ -1882,11 +1925,7 @@ var StorageBucket = class {
1882
1925
  } catch (error) {
1883
1926
  return {
1884
1927
  data: null,
1885
- error: error instanceof InsForgeError ? error : new InsForgeError(
1886
- "Delete failed",
1887
- 500,
1888
- "STORAGE_ERROR"
1889
- )
1928
+ error: error instanceof InsForgeError ? error : new InsForgeError("Delete failed", 500, "STORAGE_ERROR")
1890
1929
  };
1891
1930
  }
1892
1931
  }
@@ -2008,14 +2047,11 @@ var ChatCompletions = class {
2008
2047
  if (params.stream) {
2009
2048
  const headers = this.http.getHeaders();
2010
2049
  headers["Content-Type"] = "application/json";
2011
- const response2 = await this.http.fetch(
2012
- `${this.http.baseUrl}/api/ai/chat/completion`,
2013
- {
2014
- method: "POST",
2015
- headers,
2016
- body: JSON.stringify(backendParams)
2017
- }
2018
- );
2050
+ const response2 = await this.http.fetch(`${this.http.baseUrl}/api/ai/chat/completion`, {
2051
+ method: "POST",
2052
+ headers,
2053
+ body: JSON.stringify(backendParams)
2054
+ });
2019
2055
  if (!response2.ok) {
2020
2056
  const error = await response2.json();
2021
2057
  throw new Error(error.error || "Stream request failed");
@@ -2063,7 +2099,9 @@ var ChatCompletions = class {
2063
2099
  try {
2064
2100
  while (true) {
2065
2101
  const { done, value } = await reader.read();
2066
- if (done) break;
2102
+ if (done) {
2103
+ break;
2104
+ }
2067
2105
  buffer += decoder.decode(value, { stream: true });
2068
2106
  const lines = buffer.split("\n");
2069
2107
  buffer = lines.pop() || "";
@@ -2111,7 +2149,7 @@ var ChatCompletions = class {
2111
2149
  reader.releaseLock();
2112
2150
  return;
2113
2151
  }
2114
- } catch (e) {
2152
+ } catch {
2115
2153
  console.warn("Failed to parse SSE data:", dataStr);
2116
2154
  }
2117
2155
  }
@@ -2164,10 +2202,7 @@ var Embeddings = class {
2164
2202
  * ```
2165
2203
  */
2166
2204
  async create(params) {
2167
- const response = await this.http.post(
2168
- "/api/ai/embeddings",
2169
- params
2170
- );
2205
+ const response = await this.http.post("/api/ai/embeddings", params);
2171
2206
  return {
2172
2207
  object: response.object,
2173
2208
  data: response.data,
@@ -2253,7 +2288,9 @@ var Functions = class _Functions {
2253
2288
  static deriveSubhostingUrl(baseUrl) {
2254
2289
  try {
2255
2290
  const { hostname } = new URL(baseUrl);
2256
- if (!hostname.endsWith(".insforge.app")) return void 0;
2291
+ if (!hostname.endsWith(".insforge.app")) {
2292
+ return void 0;
2293
+ }
2257
2294
  const appKey = hostname.split(".")[0];
2258
2295
  return `https://${appKey}.functions.insforge.app`;
2259
2296
  } catch {
@@ -2299,7 +2336,9 @@ var Functions = class _Functions {
2299
2336
  const data = await parseResponse(res);
2300
2337
  return { data, error: null };
2301
2338
  } catch (error) {
2302
- if (error instanceof Error && error.name === "AbortError") throw error;
2339
+ if (error instanceof Error && error.name === "AbortError") {
2340
+ throw error;
2341
+ }
2303
2342
  return {
2304
2343
  data: null,
2305
2344
  error: error instanceof InsForgeError ? error : new InsForgeError(
@@ -2318,7 +2357,9 @@ var Functions = class _Functions {
2318
2357
  });
2319
2358
  return { data, error: null };
2320
2359
  } catch (error) {
2321
- if (error instanceof Error && error.name === "AbortError") throw error;
2360
+ if (error instanceof Error && error.name === "AbortError") {
2361
+ throw error;
2362
+ }
2322
2363
  if (error instanceof InsForgeError && error.statusCode === 404) {
2323
2364
  } else {
2324
2365
  return {
@@ -2337,7 +2378,9 @@ var Functions = class _Functions {
2337
2378
  const data = await this.http.request(method, path, { body, headers });
2338
2379
  return { data, error: null };
2339
2380
  } catch (error) {
2340
- if (error instanceof Error && error.name === "AbortError") throw error;
2381
+ if (error instanceof Error && error.name === "AbortError") {
2382
+ throw error;
2383
+ }
2341
2384
  return {
2342
2385
  data: null,
2343
2386
  error: error instanceof InsForgeError ? error : new InsForgeError(
@@ -2352,32 +2395,37 @@ var Functions = class _Functions {
2352
2395
 
2353
2396
  // src/modules/realtime.ts
2354
2397
  var CONNECT_TIMEOUT = 1e4;
2398
+ var SUBSCRIBE_TIMEOUT = 1e4;
2355
2399
  var Realtime = class {
2356
- constructor(baseUrl, tokenManager, anonKey) {
2357
- this.socket = null;
2358
- this.connectPromise = null;
2359
- this.subscribedChannels = /* @__PURE__ */ new Set();
2360
- this.eventListeners = /* @__PURE__ */ new Map();
2400
+ constructor(baseUrl, tokenManager, anonKey, getValidAccessToken = async () => tokenManager.getAccessToken()) {
2361
2401
  this.baseUrl = baseUrl;
2362
2402
  this.tokenManager = tokenManager;
2363
2403
  this.anonKey = anonKey;
2364
- this.tokenManager.onTokenChange = () => this.onTokenChange();
2404
+ this.getValidAccessToken = getValidAccessToken;
2405
+ this.socket = null;
2406
+ this.connectPromise = null;
2407
+ this.connectionAttempt = null;
2408
+ this.nextConnectionAttemptId = 0;
2409
+ this.subscriptions = /* @__PURE__ */ new Map();
2410
+ this.eventListeners = /* @__PURE__ */ new Map();
2411
+ this.tokenManager.onAuthStateChange((event) => {
2412
+ if (event !== AuthChangeEvent.TOKEN_REFRESHED) {
2413
+ this.reconnectForAuthChange();
2414
+ }
2415
+ });
2365
2416
  }
2366
2417
  notifyListeners(event, payload) {
2367
- const listeners = this.eventListeners.get(event);
2368
- if (!listeners) return;
2369
- for (const cb of listeners) {
2418
+ for (const callback of this.eventListeners.get(event) ?? []) {
2370
2419
  try {
2371
- cb(payload);
2372
- } catch (err) {
2373
- console.error(`Error in ${event} callback:`, err);
2420
+ callback(payload);
2421
+ } catch (error) {
2422
+ console.error(`Error in ${event} callback:`, error);
2374
2423
  }
2375
2424
  }
2376
2425
  }
2377
- /**
2378
- * Connect to the realtime server
2379
- * @returns Promise that resolves when connected
2380
- */
2426
+ async getHandshakeToken() {
2427
+ return await this.getValidAccessToken() ?? this.anonKey ?? null;
2428
+ }
2381
2429
  connect() {
2382
2430
  if (this.socket?.connected) {
2383
2431
  return Promise.resolve();
@@ -2385,210 +2433,324 @@ var Realtime = class {
2385
2433
  if (this.connectPromise) {
2386
2434
  return this.connectPromise;
2387
2435
  }
2388
- this.connectPromise = (async () => {
2389
- try {
2390
- const { io } = await import("socket.io-client");
2391
- await new Promise((resolve, reject) => {
2392
- const token = this.tokenManager.getAccessToken() ?? this.anonKey;
2393
- this.socket = io(this.baseUrl, {
2394
- transports: ["websocket"],
2395
- auth: token ? { token } : void 0
2396
- });
2397
- let initialConnection = true;
2398
- let timeoutId = null;
2399
- const cleanup = () => {
2400
- if (timeoutId) {
2401
- clearTimeout(timeoutId);
2402
- timeoutId = null;
2403
- }
2404
- };
2405
- timeoutId = setTimeout(() => {
2406
- if (initialConnection) {
2407
- initialConnection = false;
2408
- this.connectPromise = null;
2409
- this.socket?.disconnect();
2410
- this.socket = null;
2411
- reject(new Error(`Connection timeout after ${CONNECT_TIMEOUT}ms`));
2412
- }
2413
- }, CONNECT_TIMEOUT);
2414
- this.socket.on("connect", () => {
2415
- cleanup();
2416
- for (const channel of this.subscribedChannels) {
2417
- this.socket.emit("realtime:subscribe", { channel });
2418
- }
2419
- this.notifyListeners("connect");
2420
- if (initialConnection) {
2421
- initialConnection = false;
2422
- this.connectPromise = null;
2423
- resolve();
2424
- }
2425
- });
2426
- this.socket.on("connect_error", (error) => {
2427
- cleanup();
2428
- this.notifyListeners("connect_error", error);
2429
- if (initialConnection) {
2430
- initialConnection = false;
2431
- this.connectPromise = null;
2432
- reject(error);
2433
- }
2434
- });
2435
- this.socket.on("disconnect", (reason) => {
2436
- this.notifyListeners("disconnect", reason);
2437
- });
2438
- this.socket.on("realtime:error", (error) => {
2439
- this.notifyListeners("error", error);
2440
- });
2441
- this.socket.onAny((event, message) => {
2442
- if (event === "realtime:error") return;
2443
- this.notifyListeners(event, message);
2444
- });
2436
+ const attemptId = ++this.nextConnectionAttemptId;
2437
+ const connection = (async () => {
2438
+ const { io } = await import("socket.io-client");
2439
+ if (attemptId !== this.nextConnectionAttemptId) {
2440
+ throw new Error("Connection cancelled");
2441
+ }
2442
+ await new Promise((resolve, reject) => {
2443
+ const socket = io(this.baseUrl, {
2444
+ transports: ["websocket"],
2445
+ auth: (callback) => {
2446
+ void this.getHandshakeToken().then(
2447
+ (token) => callback(token ? { token } : {}),
2448
+ () => callback({})
2449
+ );
2450
+ }
2445
2451
  });
2446
- } catch (error) {
2452
+ this.socket = socket;
2453
+ let initialConnection = true;
2454
+ let timeoutId = null;
2455
+ const clearConnectTimeout = () => {
2456
+ if (timeoutId) {
2457
+ clearTimeout(timeoutId);
2458
+ timeoutId = null;
2459
+ }
2460
+ };
2461
+ const dispose = () => {
2462
+ clearConnectTimeout();
2463
+ socket.off("connect", onConnect);
2464
+ socket.off("connect_error", onConnectError);
2465
+ socket.off("disconnect", onDisconnect);
2466
+ socket.off("realtime:error", onRealtimeError);
2467
+ socket.offAny(onAny);
2468
+ socket.disconnect();
2469
+ if (this.socket === socket) {
2470
+ this.socket = null;
2471
+ }
2472
+ if (this.connectionAttempt?.id === attemptId) {
2473
+ this.connectionAttempt = null;
2474
+ }
2475
+ };
2476
+ const fail = (error) => {
2477
+ if (!initialConnection) {
2478
+ return;
2479
+ }
2480
+ initialConnection = false;
2481
+ dispose();
2482
+ reject(error);
2483
+ };
2484
+ const onConnect = () => {
2485
+ if (this.socket !== socket) {
2486
+ return;
2487
+ }
2488
+ clearConnectTimeout();
2489
+ this.resubscribeChannels();
2490
+ this.notifyListeners("connect");
2491
+ if (initialConnection) {
2492
+ initialConnection = false;
2493
+ if (this.connectionAttempt?.id === attemptId) {
2494
+ this.connectionAttempt = null;
2495
+ }
2496
+ resolve();
2497
+ }
2498
+ };
2499
+ const onConnectError = (error) => {
2500
+ clearConnectTimeout();
2501
+ this.notifyListeners("connect_error", error);
2502
+ if (initialConnection) {
2503
+ fail(error);
2504
+ }
2505
+ };
2506
+ const onDisconnect = (reason) => {
2507
+ this.handleDisconnect(reason);
2508
+ };
2509
+ const onRealtimeError = (error) => {
2510
+ this.notifyListeners("error", error);
2511
+ };
2512
+ const onAny = (event, message) => {
2513
+ if (event === "realtime:error") {
2514
+ return;
2515
+ }
2516
+ this.applyPresenceEvent(event, message);
2517
+ this.notifyListeners(event, message);
2518
+ };
2519
+ this.connectionAttempt = { id: attemptId, socket, cancel: fail };
2520
+ socket.on("connect", onConnect);
2521
+ socket.on("connect_error", onConnectError);
2522
+ socket.on("disconnect", onDisconnect);
2523
+ socket.on("realtime:error", onRealtimeError);
2524
+ socket.onAny(onAny);
2525
+ timeoutId = setTimeout(
2526
+ () => fail(new Error(`Connection timeout after ${CONNECT_TIMEOUT}ms`)),
2527
+ CONNECT_TIMEOUT
2528
+ );
2529
+ });
2530
+ })();
2531
+ const trackedConnection = connection.finally(() => {
2532
+ if (this.connectPromise === trackedConnection) {
2447
2533
  this.connectPromise = null;
2448
- throw error;
2449
2534
  }
2450
- })();
2451
- return this.connectPromise;
2535
+ });
2536
+ this.connectPromise = trackedConnection;
2537
+ return trackedConnection;
2452
2538
  }
2453
- /**
2454
- * Disconnect from the realtime server
2455
- */
2456
2539
  disconnect() {
2457
- if (this.socket) {
2458
- this.socket.disconnect();
2459
- this.socket = null;
2540
+ this.nextConnectionAttemptId++;
2541
+ this.connectionAttempt?.cancel(new Error("Disconnected"));
2542
+ this.socket?.disconnect();
2543
+ this.socket = null;
2544
+ this.connectPromise = null;
2545
+ for (const subscription of this.subscriptions.values()) {
2546
+ this.settleSubscription(
2547
+ subscription,
2548
+ {
2549
+ ok: false,
2550
+ channel: subscription.channel,
2551
+ error: { code: "DISCONNECTED", message: "Disconnected" }
2552
+ },
2553
+ false
2554
+ );
2460
2555
  }
2461
- this.subscribedChannels.clear();
2556
+ this.subscriptions.clear();
2462
2557
  }
2463
- /**
2464
- * Handle token changes (e.g., after auth refresh)
2465
- * Updates socket auth so reconnects use the new token
2466
- * If connected, triggers reconnect to apply new token immediately
2467
- */
2468
- onTokenChange() {
2469
- const token = this.tokenManager.getAccessToken() ?? this.anonKey;
2470
- if (this.socket) {
2471
- this.socket.auth = token ? { token } : {};
2558
+ reconnectForAuthChange() {
2559
+ for (const subscription of this.subscriptions.values()) {
2560
+ if (subscription.status === "rejected") {
2561
+ subscription.status = "pending";
2562
+ }
2472
2563
  }
2473
- if (this.socket && (this.socket.connected || this.connectPromise)) {
2474
- this.socket.disconnect();
2475
- this.socket.connect();
2564
+ if (!this.socket) {
2565
+ return;
2566
+ }
2567
+ this.socket.disconnect();
2568
+ this.socket.connect();
2569
+ }
2570
+ handleDisconnect(reason) {
2571
+ for (const subscription of this.subscriptions.values()) {
2572
+ if (subscription.status === "rejected") {
2573
+ continue;
2574
+ }
2575
+ subscription.status = "pending";
2576
+ this.settleSubscription(
2577
+ subscription,
2578
+ {
2579
+ ok: false,
2580
+ channel: subscription.channel,
2581
+ error: { code: "DISCONNECTED", message: "Connection lost before subscription completed" }
2582
+ },
2583
+ true
2584
+ );
2585
+ }
2586
+ this.notifyListeners("disconnect", reason);
2587
+ }
2588
+ resubscribeChannels() {
2589
+ for (const [channel, subscription] of this.subscriptions) {
2590
+ if (subscription.status === "pending") {
2591
+ this.requestSubscription(channel, subscription);
2592
+ }
2593
+ }
2594
+ }
2595
+ requestSubscription(channel, subscription) {
2596
+ if (subscription.pending) {
2597
+ return subscription.pending;
2598
+ }
2599
+ const socket = this.socket;
2600
+ if (!socket?.connected) {
2601
+ return Promise.resolve({
2602
+ ok: false,
2603
+ channel,
2604
+ error: { code: "CONNECTION_FAILED", message: "Not connected to realtime server" }
2605
+ });
2606
+ }
2607
+ subscription.status = "pending";
2608
+ const epoch = ++subscription.epoch;
2609
+ let timeoutId;
2610
+ subscription.pending = new Promise((resolve) => {
2611
+ subscription.settlePending = (response) => {
2612
+ if (timeoutId) {
2613
+ clearTimeout(timeoutId);
2614
+ }
2615
+ subscription.pending = void 0;
2616
+ subscription.settlePending = void 0;
2617
+ resolve(response);
2618
+ };
2619
+ timeoutId = setTimeout(() => {
2620
+ if (this.subscriptions.get(channel) === subscription && subscription.epoch === epoch) {
2621
+ this.settleSubscription(
2622
+ subscription,
2623
+ {
2624
+ ok: false,
2625
+ channel,
2626
+ error: {
2627
+ code: "SUBSCRIBE_TIMEOUT",
2628
+ message: "Subscription acknowledgement timed out"
2629
+ }
2630
+ },
2631
+ true
2632
+ );
2633
+ }
2634
+ }, SUBSCRIBE_TIMEOUT);
2635
+ socket.emit("realtime:subscribe", { channel }, (response) => {
2636
+ if (this.subscriptions.get(channel) !== subscription || subscription.epoch !== epoch) {
2637
+ return;
2638
+ }
2639
+ if (response.ok) {
2640
+ subscription.status = "subscribed";
2641
+ subscription.members = new Map(
2642
+ response.presence.members.map((member) => [member.presenceId, member])
2643
+ );
2644
+ } else {
2645
+ subscription.status = "rejected";
2646
+ subscription.members.clear();
2647
+ }
2648
+ this.settleSubscription(subscription, response, false);
2649
+ });
2650
+ });
2651
+ return subscription.pending;
2652
+ }
2653
+ settleSubscription(subscription, response, incrementEpoch) {
2654
+ if (incrementEpoch) {
2655
+ subscription.epoch++;
2656
+ }
2657
+ subscription.settlePending?.(response);
2658
+ }
2659
+ applyPresenceEvent(event, message) {
2660
+ if (event !== "presence:join" && event !== "presence:leave") {
2661
+ return;
2662
+ }
2663
+ const presenceEvent = message;
2664
+ const channel = presenceEvent.meta?.channel;
2665
+ const member = presenceEvent.member;
2666
+ if (!channel || !member) {
2667
+ return;
2668
+ }
2669
+ const subscription = this.subscriptions.get(channel);
2670
+ if (!subscription) {
2671
+ return;
2672
+ }
2673
+ if (event === "presence:join") {
2674
+ subscription.members.set(member.presenceId, member);
2675
+ } else {
2676
+ subscription.members.delete(member.presenceId);
2476
2677
  }
2477
2678
  }
2478
- /**
2479
- * Check if connected to the realtime server
2480
- */
2481
2679
  get isConnected() {
2482
2680
  return this.socket?.connected ?? false;
2483
2681
  }
2484
- /**
2485
- * Get the current connection state
2486
- */
2487
2682
  get connectionState() {
2488
- if (!this.socket) return "disconnected";
2489
- if (this.socket.connected) return "connected";
2490
- return "connecting";
2683
+ if (!this.socket) {
2684
+ return "disconnected";
2685
+ }
2686
+ return this.socket.connected ? "connected" : "connecting";
2491
2687
  }
2492
- /**
2493
- * Get the socket ID (if connected)
2494
- */
2495
2688
  get socketId() {
2496
2689
  return this.socket?.id;
2497
2690
  }
2498
- /**
2499
- * Subscribe to a channel
2500
- *
2501
- * Automatically connects if not already connected.
2502
- *
2503
- * @param channel - Channel name (e.g., 'orders:123', 'broadcast')
2504
- * @returns Promise with the subscription response
2505
- */
2506
2691
  async subscribe(channel) {
2507
- if (this.subscribedChannels.has(channel)) {
2508
- return { ok: true, channel, presence: { members: [] } };
2692
+ let subscription = this.subscriptions.get(channel);
2693
+ if (subscription) {
2694
+ if (subscription.pending) {
2695
+ return subscription.pending;
2696
+ }
2697
+ if (subscription.status === "subscribed") {
2698
+ return { ok: true, channel, presence: { members: [...subscription.members.values()] } };
2699
+ }
2700
+ } else {
2701
+ subscription = { channel, epoch: 0, status: "pending", members: /* @__PURE__ */ new Map() };
2702
+ this.subscriptions.set(channel, subscription);
2509
2703
  }
2510
2704
  if (!this.socket?.connected) {
2511
2705
  try {
2512
2706
  await this.connect();
2513
2707
  } catch (error) {
2708
+ if (this.subscriptions.get(channel) === subscription) {
2709
+ this.subscriptions.delete(channel);
2710
+ }
2514
2711
  const message = error instanceof Error ? error.message : "Connection failed";
2515
2712
  return { ok: false, channel, error: { code: "CONNECTION_FAILED", message } };
2516
2713
  }
2517
2714
  }
2518
- return new Promise((resolve) => {
2519
- this.socket.emit("realtime:subscribe", { channel }, (response) => {
2520
- if (response.ok) {
2521
- this.subscribedChannels.add(channel);
2522
- }
2523
- resolve(response);
2524
- });
2525
- });
2715
+ return subscription.pending ?? this.requestSubscription(channel, subscription);
2526
2716
  }
2527
- /**
2528
- * Unsubscribe from a channel (fire-and-forget)
2529
- *
2530
- * @param channel - Channel name to unsubscribe from
2531
- */
2532
2717
  unsubscribe(channel) {
2533
- this.subscribedChannels.delete(channel);
2718
+ const subscription = this.subscriptions.get(channel);
2719
+ if (!subscription) {
2720
+ return;
2721
+ }
2722
+ this.subscriptions.delete(channel);
2723
+ this.settleSubscription(
2724
+ subscription,
2725
+ {
2726
+ ok: false,
2727
+ channel,
2728
+ error: { code: "SUBSCRIPTION_CANCELLED", message: "Subscription cancelled" }
2729
+ },
2730
+ true
2731
+ );
2534
2732
  if (this.socket?.connected) {
2535
2733
  this.socket.emit("realtime:unsubscribe", { channel });
2536
2734
  }
2537
2735
  }
2538
- /**
2539
- * Publish a message to a channel
2540
- *
2541
- * @param channel - Channel name
2542
- * @param event - Event name
2543
- * @param payload - Message payload
2544
- */
2545
2736
  async publish(channel, event, payload) {
2546
2737
  if (!this.socket?.connected) {
2547
2738
  throw new Error("Not connected to realtime server. Call connect() first.");
2548
2739
  }
2549
2740
  this.socket.emit("realtime:publish", { channel, event, payload });
2550
2741
  }
2551
- /**
2552
- * Listen for events
2553
- *
2554
- * Reserved event names:
2555
- * - 'connect' - Fired when connected to the server
2556
- * - 'connect_error' - Fired when connection fails (payload: Error)
2557
- * - 'disconnect' - Fired when disconnected (payload: reason string)
2558
- * - 'error' - Fired when a realtime error occurs (payload: RealtimeErrorPayload)
2559
- *
2560
- * All other events receive a `SocketMessage` payload with metadata.
2561
- *
2562
- * @param event - Event name to listen for
2563
- * @param callback - Callback function when event is received
2564
- */
2565
2742
  on(event, callback) {
2566
- if (!this.eventListeners.has(event)) {
2567
- this.eventListeners.set(event, /* @__PURE__ */ new Set());
2568
- }
2569
- this.eventListeners.get(event).add(callback);
2743
+ const listeners = this.eventListeners.get(event) ?? /* @__PURE__ */ new Set();
2744
+ listeners.add(callback);
2745
+ this.eventListeners.set(event, listeners);
2570
2746
  }
2571
- /**
2572
- * Remove a listener for a specific event
2573
- *
2574
- * @param event - Event name
2575
- * @param callback - The callback function to remove
2576
- */
2577
2747
  off(event, callback) {
2578
2748
  const listeners = this.eventListeners.get(event);
2579
- if (listeners) {
2580
- listeners.delete(callback);
2581
- if (listeners.size === 0) {
2582
- this.eventListeners.delete(event);
2583
- }
2749
+ listeners?.delete(callback);
2750
+ if (listeners?.size === 0) {
2751
+ this.eventListeners.delete(event);
2584
2752
  }
2585
2753
  }
2586
- /**
2587
- * Listen for an event only once, then automatically remove the listener
2588
- *
2589
- * @param event - Event name to listen for
2590
- * @param callback - Callback function when event is received
2591
- */
2592
2754
  once(event, callback) {
2593
2755
  const wrapper = (payload) => {
2594
2756
  this.off(event, wrapper);
@@ -2596,13 +2758,11 @@ var Realtime = class {
2596
2758
  };
2597
2759
  this.on(event, wrapper);
2598
2760
  }
2599
- /**
2600
- * Get all currently subscribed channels
2601
- *
2602
- * @returns Array of channel names
2603
- */
2604
2761
  getSubscribedChannels() {
2605
- return Array.from(this.subscribedChannels);
2762
+ return [...this.subscriptions.values()].filter((subscription) => subscription.status === "subscribed").map((subscription) => subscription.channel);
2763
+ }
2764
+ getPresenceState(channel) {
2765
+ return [...this.subscriptions.get(channel)?.members.values() ?? []];
2606
2766
  }
2607
2767
  };
2608
2768
 
@@ -2617,13 +2777,12 @@ var Emails = class {
2617
2777
  */
2618
2778
  async send(options) {
2619
2779
  try {
2620
- const data = await this.http.post(
2621
- "/api/email/send-raw",
2622
- options
2623
- );
2780
+ const data = await this.http.post("/api/email/send-raw", options);
2624
2781
  return { data, error: null };
2625
2782
  } catch (error) {
2626
- if (error instanceof Error && error.name === "AbortError") throw error;
2783
+ if (error instanceof Error && error.name === "AbortError") {
2784
+ throw error;
2785
+ }
2627
2786
  return {
2628
2787
  data: null,
2629
2788
  error: error instanceof InsForgeError ? error : new InsForgeError(
@@ -2706,10 +2865,7 @@ var RazorpayPayments = class {
2706
2865
  );
2707
2866
  return { data, error: null };
2708
2867
  } catch (error) {
2709
- return wrapError(
2710
- error,
2711
- "Razorpay order creation failed"
2712
- );
2868
+ return wrapError(error, "Razorpay order creation failed");
2713
2869
  }
2714
2870
  }
2715
2871
  async verifyOrder(environment, request) {
@@ -2720,10 +2876,7 @@ var RazorpayPayments = class {
2720
2876
  );
2721
2877
  return { data, error: null };
2722
2878
  } catch (error) {
2723
- return wrapError(
2724
- error,
2725
- "Razorpay order verification failed"
2726
- );
2879
+ return wrapError(error, "Razorpay order verification failed");
2727
2880
  }
2728
2881
  }
2729
2882
  async createSubscription(environment, request) {
@@ -2825,14 +2978,15 @@ var InsForgeClient = class {
2825
2978
  isServerMode: config.isServerMode ?? !!accessToken,
2826
2979
  detectOAuthCallback: config.auth?.detectOAuthCallback
2827
2980
  });
2828
- this.database = new Database(this.http);
2981
+ this.database = new Database(this.http, config.db?.schema);
2829
2982
  this.storage = new Storage(this.http);
2830
2983
  this.ai = new AI(this.http);
2831
2984
  this.functions = new Functions(this.http, config.functionsUrl);
2832
2985
  this.realtime = new Realtime(
2833
2986
  this.http.baseUrl,
2834
2987
  this.tokenManager,
2835
- config.anonKey
2988
+ config.anonKey,
2989
+ () => this.http.getValidAccessToken()
2836
2990
  );
2837
2991
  this.emails = new Emails(this.http);
2838
2992
  this.payments = new Payments(this.http);
@@ -2852,32 +3006,35 @@ var InsForgeClient = class {
2852
3006
  /**
2853
3007
  * Set the access token used by every SDK surface. Updates both the HTTP
2854
3008
  * client (database / storage / functions / AI / emails) and the realtime
2855
- * token manager (which fires `onTokenChange` to reconnect the WebSocket
2856
- * with the new bearer). Pass `null` to clear.
3009
+ * token manager. Pass `null` to sign out. By default a token replacement is
3010
+ * treated as a sign-in boundary and reconnects realtime. Pass
3011
+ * `AuthChangeEvent.TOKEN_REFRESHED` for a same-identity refresh to preserve a live socket; the
3012
+ * refreshed token is then used at the next handshake.
2857
3013
  *
2858
3014
  * Use this when an external auth provider (Better Auth, Clerk, Auth0,
2859
3015
  * WorkOS, Kinde, Stytch, …) issues the JWT and you need to keep the
2860
3016
  * long-lived InsForge client in sync. Without this, you'd have to call
2861
3017
  * `client.getHttpClient().setAuthToken(token)` AND reach into the private
2862
- * `client.realtime.tokenManager.setAccessToken(token)` separately
2863
- * forgetting the second one silently breaks realtime auth.
3018
+ * realtime token manager separately.
2864
3019
  *
2865
3020
  * @example
2866
3021
  * ```typescript
3022
+ * import { AuthChangeEvent } from '@insforge/sdk';
3023
+ *
2867
3024
  * // Refresh a third-party-issued JWT periodically
2868
3025
  * const { token } = await fetch('/api/insforge-token').then((r) => r.json());
2869
- * client.setAccessToken(token);
3026
+ * client.setAccessToken(token, AuthChangeEvent.TOKEN_REFRESHED);
2870
3027
  *
2871
3028
  * // Sign-out
2872
3029
  * client.setAccessToken(null);
2873
3030
  * ```
2874
3031
  */
2875
- setAccessToken(token) {
3032
+ setAccessToken(token, event = AuthChangeEvent.SIGNED_IN) {
2876
3033
  this.http.setAuthToken(token);
2877
3034
  if (token === null) {
2878
3035
  this.tokenManager.clearSession();
2879
3036
  } else {
2880
- this.tokenManager.setAccessToken(token);
3037
+ this.tokenManager.setAccessToken(token, event);
2881
3038
  }
2882
3039
  }
2883
3040
  /**
@@ -2890,38 +3047,6 @@ var InsForgeClient = class {
2890
3047
  */
2891
3048
  };
2892
3049
 
2893
- // src/lib/jwt.ts
2894
- function decodeBase64Url(input) {
2895
- const normalized = input.replace(/-/g, "+").replace(/_/g, "/");
2896
- const padded = normalized.padEnd(
2897
- normalized.length + (4 - normalized.length % 4) % 4,
2898
- "="
2899
- );
2900
- const binary = atob(padded);
2901
- const bytes = Uint8Array.from(binary, (char) => char.charCodeAt(0));
2902
- return new TextDecoder().decode(bytes);
2903
- }
2904
- function getJwtExpiration(token) {
2905
- if (!token) return null;
2906
- const [, payload] = token.split(".");
2907
- if (!payload) return null;
2908
- try {
2909
- const parsed = JSON.parse(decodeBase64Url(payload));
2910
- if (typeof parsed.exp !== "number" || !Number.isFinite(parsed.exp)) {
2911
- return null;
2912
- }
2913
- return new Date(parsed.exp * 1e3);
2914
- } catch {
2915
- return null;
2916
- }
2917
- }
2918
- function isJwtExpiredOrExpiring(token, leewaySeconds = 60) {
2919
- if (!token) return false;
2920
- const expires = getJwtExpiration(token);
2921
- if (!expires) return true;
2922
- return expires.getTime() <= Date.now() + leewaySeconds * 1e3;
2923
- }
2924
-
2925
3050
  // src/ssr/browser-client.ts
2926
3051
  var import_shared_schemas2 = require("@insforge/shared-schemas");
2927
3052
 
@@ -2936,18 +3061,28 @@ function getRefreshTokenCookieName(names) {
2936
3061
  return names?.refreshToken ?? DEFAULT_REFRESH_TOKEN_COOKIE;
2937
3062
  }
2938
3063
  function getCookieValue(cookies, name) {
2939
- if (!cookies) return null;
3064
+ if (!cookies) {
3065
+ return null;
3066
+ }
2940
3067
  const value = cookies.get(name);
2941
- if (typeof value === "string") return value || null;
2942
- if (value && typeof value.value === "string") return value.value || null;
3068
+ if (typeof value === "string") {
3069
+ return value || null;
3070
+ }
3071
+ if (value && typeof value.value === "string") {
3072
+ return value.value || null;
3073
+ }
2943
3074
  return null;
2944
3075
  }
2945
3076
  function getCookieValueFromHeader(cookieHeader, name) {
2946
- if (!cookieHeader) return null;
3077
+ if (!cookieHeader) {
3078
+ return null;
3079
+ }
2947
3080
  const parts = cookieHeader.split(";");
2948
3081
  for (const part of parts) {
2949
3082
  const [rawName, ...rawValue] = part.trim().split("=");
2950
- if (rawName !== name) continue;
3083
+ if (rawName !== name) {
3084
+ continue;
3085
+ }
2951
3086
  try {
2952
3087
  return decodeURIComponent(rawValue.join("="));
2953
3088
  } catch {
@@ -2957,7 +3092,9 @@ function getCookieValueFromHeader(cookieHeader, name) {
2957
3092
  return null;
2958
3093
  }
2959
3094
  function getBrowserCookie(name) {
2960
- if (typeof document === "undefined") return null;
3095
+ if (typeof document === "undefined") {
3096
+ return null;
3097
+ }
2961
3098
  return getCookieValueFromHeader(document.cookie, name);
2962
3099
  }
2963
3100
  function defaultCookieOptions() {
@@ -2994,11 +3131,15 @@ function expiredCookieOptions(overrides) {
2994
3131
  };
2995
3132
  }
2996
3133
  function setCookie(cookies, name, value, options) {
2997
- if (!cookies?.set) return;
3134
+ if (!cookies?.set) {
3135
+ return;
3136
+ }
2998
3137
  cookies.set(name, value, options);
2999
3138
  }
3000
3139
  function deleteCookie(cookies, name, options) {
3001
- if (!cookies) return;
3140
+ if (!cookies) {
3141
+ return;
3142
+ }
3002
3143
  if (cookies.set) {
3003
3144
  cookies.set(name, "", expiredCookieOptions(options));
3004
3145
  return;
@@ -3007,12 +3148,24 @@ function deleteCookie(cookies, name, options) {
3007
3148
  }
3008
3149
  function serializeCookie(name, value, options = {}) {
3009
3150
  const parts = [`${encodeURIComponent(name)}=${encodeURIComponent(value)}`];
3010
- if (options.maxAge !== void 0) parts.push(`Max-Age=${options.maxAge}`);
3011
- if (options.domain) parts.push(`Domain=${options.domain}`);
3012
- if (options.path) parts.push(`Path=${options.path}`);
3013
- if (options.expires) parts.push(`Expires=${options.expires.toUTCString()}`);
3014
- if (options.httpOnly) parts.push("HttpOnly");
3015
- if (options.secure) parts.push("Secure");
3151
+ if (options.maxAge !== void 0) {
3152
+ parts.push(`Max-Age=${options.maxAge}`);
3153
+ }
3154
+ if (options.domain) {
3155
+ parts.push(`Domain=${options.domain}`);
3156
+ }
3157
+ if (options.path) {
3158
+ parts.push(`Path=${options.path}`);
3159
+ }
3160
+ if (options.expires) {
3161
+ parts.push(`Expires=${options.expires.toUTCString()}`);
3162
+ }
3163
+ if (options.httpOnly) {
3164
+ parts.push("HttpOnly");
3165
+ }
3166
+ if (options.secure) {
3167
+ parts.push("Secure");
3168
+ }
3016
3169
  if (options.sameSite) {
3017
3170
  const sameSite = options.sameSite.charAt(0).toUpperCase() + options.sameSite.slice(1);
3018
3171
  parts.push(`SameSite=${sameSite}`);
@@ -3025,20 +3178,14 @@ function appendSetCookie(headers, name, value, options) {
3025
3178
  function setAuthCookies(cookies, tokens, settings = {}) {
3026
3179
  const accessName = getAccessTokenCookieName(settings.names);
3027
3180
  const refreshName = getRefreshTokenCookieName(settings.names);
3028
- const accessOptions = accessTokenCookieOptions(
3029
- tokens.accessToken,
3030
- settings.options?.accessToken
3031
- );
3181
+ const accessOptions = accessTokenCookieOptions(tokens.accessToken, settings.options?.accessToken);
3032
3182
  setCookie(cookies, accessName, tokens.accessToken, accessOptions);
3033
3183
  if (tokens.refreshToken) {
3034
3184
  setCookie(
3035
3185
  cookies,
3036
3186
  refreshName,
3037
3187
  tokens.refreshToken,
3038
- refreshTokenCookieOptions(
3039
- tokens.refreshToken,
3040
- settings.options?.refreshToken
3041
- )
3188
+ refreshTokenCookieOptions(tokens.refreshToken, settings.options?.refreshToken)
3042
3189
  );
3043
3190
  }
3044
3191
  }
@@ -3064,10 +3211,7 @@ function setAuthCookieHeaders(headers, tokens, settings = {}) {
3064
3211
  headers,
3065
3212
  refreshName,
3066
3213
  tokens.refreshToken,
3067
- refreshTokenCookieOptions(
3068
- tokens.refreshToken,
3069
- settings.options?.refreshToken
3070
- )
3214
+ refreshTokenCookieOptions(tokens.refreshToken, settings.options?.refreshToken)
3071
3215
  );
3072
3216
  }
3073
3217
  }
@@ -3110,10 +3254,14 @@ function toRefreshError(response, body) {
3110
3254
  );
3111
3255
  }
3112
3256
  async function readErrorCode(response) {
3113
- if (response.status !== 401) return null;
3257
+ if (response.status !== 401) {
3258
+ return null;
3259
+ }
3114
3260
  try {
3115
3261
  const body = await response.clone().json();
3116
- if (!body || typeof body !== "object") return null;
3262
+ if (!body || typeof body !== "object") {
3263
+ return null;
3264
+ }
3117
3265
  const candidate = body.error ?? body.code;
3118
3266
  return typeof candidate === "string" ? candidate : null;
3119
3267
  } catch {
@@ -3132,7 +3280,9 @@ function withAuthHeader(init, token) {
3132
3280
  };
3133
3281
  }
3134
3282
  function getRequestUrl(input) {
3135
- if (typeof input === "string") return input;
3283
+ if (typeof input === "string") {
3284
+ return input;
3285
+ }
3136
3286
  if (typeof Request !== "undefined" && input instanceof Request) {
3137
3287
  return input.url;
3138
3288
  }
@@ -3150,21 +3300,19 @@ function createBrowserClient(options = {}) {
3150
3300
  "Missing InsForge baseUrl or anonKey. Pass baseUrl and anonKey to createBrowserClient() or set NEXT_PUBLIC_INSFORGE_URL and NEXT_PUBLIC_INSFORGE_ANON_KEY."
3151
3301
  );
3152
3302
  }
3153
- let accessToken = getBrowserCookie(
3154
- getAccessTokenCookieName(options.names)
3155
- );
3303
+ let accessToken = getBrowserCookie(getAccessTokenCookieName(options.names));
3156
3304
  const refreshUrl = options.refreshUrl ?? "/api/auth/refresh";
3157
3305
  const fetchImpl = options.fetch ?? (globalThis.fetch ? globalThis.fetch.bind(globalThis) : void 0);
3158
3306
  let client;
3159
3307
  let sessionChecked = false;
3160
3308
  let refreshPromise = null;
3161
3309
  const refreshFromRoute = () => {
3162
- if (refreshPromise) return refreshPromise;
3310
+ if (refreshPromise) {
3311
+ return refreshPromise;
3312
+ }
3163
3313
  refreshPromise = (async () => {
3164
3314
  if (!fetchImpl) {
3165
- throw new Error(
3166
- "Fetch is not available. Please provide a fetch implementation."
3167
- );
3315
+ throw new Error("Fetch is not available. Please provide a fetch implementation.");
3168
3316
  }
3169
3317
  const response = await fetchImpl(refreshUrl, {
3170
3318
  method: "POST",
@@ -3181,11 +3329,15 @@ function createBrowserClient(options = {}) {
3181
3329
  }
3182
3330
  throw error;
3183
3331
  }
3184
- if (!body || typeof body !== "object") return null;
3332
+ if (!body || typeof body !== "object") {
3333
+ return null;
3334
+ }
3185
3335
  const refreshBody = body;
3186
- if (!refreshBody.accessToken || !refreshBody.user) return null;
3336
+ if (!refreshBody.accessToken || !refreshBody.user) {
3337
+ return null;
3338
+ }
3187
3339
  accessToken = refreshBody.accessToken;
3188
- client?.setAccessToken(refreshBody.accessToken);
3340
+ client?.setAccessToken(refreshBody.accessToken, AuthChangeEvent.TOKEN_REFRESHED);
3189
3341
  return refreshBody;
3190
3342
  })().finally(() => {
3191
3343
  sessionChecked = true;
@@ -3199,9 +3351,7 @@ function createBrowserClient(options = {}) {
3199
3351
  };
3200
3352
  const ssrFetch = async (input, init) => {
3201
3353
  if (!fetchImpl) {
3202
- throw new Error(
3203
- "Fetch is not available. Please provide a fetch implementation."
3204
- );
3354
+ throw new Error("Fetch is not available. Please provide a fetch implementation.");
3205
3355
  }
3206
3356
  if (shouldSkipRefresh(input)) {
3207
3357
  return fetchImpl(input, init);
@@ -3238,9 +3388,9 @@ function createBrowserClient(options = {}) {
3238
3388
  }
3239
3389
  });
3240
3390
  const setAccessToken = client.setAccessToken.bind(client);
3241
- client.setAccessToken = (token) => {
3391
+ client.setAccessToken = (token, event) => {
3242
3392
  accessToken = token;
3243
- setAccessToken(token);
3393
+ setAccessToken(token, event);
3244
3394
  };
3245
3395
  if (accessToken) {
3246
3396
  client.setAccessToken(accessToken);
@@ -3264,10 +3414,7 @@ function createServerClient(options = {}) {
3264
3414
  "Missing InsForge baseUrl or anonKey. Pass baseUrl and anonKey to createServerClient() or set NEXT_PUBLIC_INSFORGE_URL and NEXT_PUBLIC_INSFORGE_ANON_KEY."
3265
3415
  );
3266
3416
  }
3267
- const accessToken = options.accessToken ?? getCookieValue(
3268
- options.cookies,
3269
- getAccessTokenCookieName(options.names)
3270
- );
3417
+ const accessToken = options.accessToken ?? getCookieValue(options.cookies, getAccessTokenCookieName(options.names));
3271
3418
  return new InsForgeClient({
3272
3419
  ...options,
3273
3420
  baseUrl,
@@ -3290,7 +3437,9 @@ function jsonResponse(body, init = {}, headers = new Headers(init.headers)) {
3290
3437
  });
3291
3438
  }
3292
3439
  function normalizeError(error) {
3293
- if (error instanceof InsForgeError) return error;
3440
+ if (error instanceof InsForgeError) {
3441
+ return error;
3442
+ }
3294
3443
  if (error && typeof error === "object") {
3295
3444
  const body = error;
3296
3445
  return new InsForgeError(
@@ -3307,18 +3456,21 @@ function normalizeError(error) {
3307
3456
  }
3308
3457
  async function readJson(response) {
3309
3458
  const contentType = response.headers.get("content-type");
3310
- if (!contentType?.includes("json")) return null;
3459
+ if (!contentType?.includes("json")) {
3460
+ return null;
3461
+ }
3311
3462
  return response.json();
3312
3463
  }
3313
3464
  function readRefreshToken(options) {
3314
- if (options.refreshToken) return options.refreshToken;
3465
+ if (options.refreshToken) {
3466
+ return options.refreshToken;
3467
+ }
3315
3468
  const refreshCookieName = getRefreshTokenCookieName(options.names);
3316
3469
  const cookieValue = getCookieValue(options.cookies, refreshCookieName);
3317
- if (cookieValue) return cookieValue;
3318
- return getCookieValueFromHeader(
3319
- options.request?.headers.get("cookie"),
3320
- refreshCookieName
3321
- );
3470
+ if (cookieValue) {
3471
+ return cookieValue;
3472
+ }
3473
+ return getCookieValueFromHeader(options.request?.headers.get("cookie"), refreshCookieName);
3322
3474
  }
3323
3475
  async function refreshAuth(options = {}) {
3324
3476
  const headers = new Headers();
@@ -3359,9 +3511,7 @@ async function refreshAuth(options = {}) {
3359
3511
  }
3360
3512
  const fetchImpl = options.fetch ?? (globalThis.fetch ? globalThis.fetch.bind(globalThis) : void 0);
3361
3513
  if (!fetchImpl) {
3362
- throw new Error(
3363
- "Fetch is not available. Please provide a fetch implementation."
3364
- );
3514
+ throw new Error("Fetch is not available. Please provide a fetch implementation.");
3365
3515
  }
3366
3516
  const requestHeaders = new Headers(options.headers);
3367
3517
  requestHeaders.set("Authorization", `Bearer ${anonKey}`);
@@ -3442,7 +3592,9 @@ function createRefreshAuthRouter(options = {}) {
3442
3592
 
3443
3593
  // src/ssr/auth-actions.ts
3444
3594
  function persistSessionCookies(cookies, data, settings) {
3445
- if (!data?.accessToken) return;
3595
+ if (!data?.accessToken) {
3596
+ return;
3597
+ }
3446
3598
  setAuthCookies(
3447
3599
  cookies,
3448
3600
  {
@@ -3453,7 +3605,9 @@ function persistSessionCookies(cookies, data, settings) {
3453
3605
  );
3454
3606
  }
3455
3607
  function sanitizeAuthData(data) {
3456
- if (!data) return null;
3608
+ if (!data) {
3609
+ return null;
3610
+ }
3457
3611
  const {
3458
3612
  accessToken: _accessToken,
3459
3613
  refreshToken: _refreshToken,
@@ -3503,32 +3657,20 @@ function createAuthActions(options = {}) {
3503
3657
  signInWithPassword: async (request) => {
3504
3658
  const result = await createClient().auth.signInWithPassword(request);
3505
3659
  persistSessionCookies(writeCookies, result.data, cookieSettings);
3506
- return toSafeAuthResult(
3507
- result
3508
- );
3660
+ return toSafeAuthResult(result);
3509
3661
  },
3510
3662
  signInWithOAuth: async (providerOrOptions, signInOptions) => {
3511
- return createClient().auth.signInWithOAuth(
3512
- providerOrOptions,
3513
- signInOptions
3514
- );
3663
+ return createClient().auth.signInWithOAuth(providerOrOptions, signInOptions);
3515
3664
  },
3516
3665
  signInWithIdToken: async (credentials) => {
3517
3666
  const result = await createClient().auth.signInWithIdToken(credentials);
3518
3667
  persistSessionCookies(writeCookies, result.data, cookieSettings);
3519
- return toSafeAuthResult(
3520
- result
3521
- );
3668
+ return toSafeAuthResult(result);
3522
3669
  },
3523
3670
  exchangeOAuthCode: async (code, codeVerifier) => {
3524
- const result = await createClient().auth.exchangeOAuthCode(
3525
- code,
3526
- codeVerifier
3527
- );
3671
+ const result = await createClient().auth.exchangeOAuthCode(code, codeVerifier);
3528
3672
  persistSessionCookies(writeCookies, result.data, cookieSettings);
3529
- return toSafeAuthResult(
3530
- result
3531
- );
3673
+ return toSafeAuthResult(result);
3532
3674
  },
3533
3675
  verifyEmail: async (request) => {
3534
3676
  const result = await createClient().auth.verifyEmail(request);
@@ -3547,10 +3689,7 @@ function createAuthActions(options = {}) {
3547
3689
  async function updateSession(options) {
3548
3690
  const accessCookieName = getAccessTokenCookieName(options.names);
3549
3691
  const refreshCookieName = getRefreshTokenCookieName(options.names);
3550
- const accessToken = getCookieValue(
3551
- options.requestCookies,
3552
- accessCookieName
3553
- );
3692
+ const accessToken = getCookieValue(options.requestCookies, accessCookieName);
3554
3693
  if (accessToken && !isJwtExpiredOrExpiring(accessToken, options.refreshLeewaySeconds)) {
3555
3694
  return {
3556
3695
  refreshed: false,
@@ -3558,10 +3697,7 @@ async function updateSession(options) {
3558
3697
  error: null
3559
3698
  };
3560
3699
  }
3561
- const refreshToken = getCookieValue(
3562
- options.requestCookies,
3563
- refreshCookieName
3564
- );
3700
+ const refreshToken = getCookieValue(options.requestCookies, refreshCookieName);
3565
3701
  if (!refreshToken) {
3566
3702
  if (accessToken) {
3567
3703
  clearAuthCookies(options.requestCookies, options);