@insforge/sdk 1.0.1-refresh.3 → 1.0.1-refresh.5

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/index.mjs CHANGED
@@ -85,11 +85,11 @@ var HttpClient = class {
85
85
  method,
86
86
  headers: requestHeaders,
87
87
  body: processedBody,
88
- credentials: "include",
89
- // Essential for httpOnly cookies (refresh token)
90
- ...fetchOptions
88
+ ...fetchOptions,
89
+ credentials: "include"
91
90
  });
92
- if (response.status === 401 && !isRetry && this.refreshCallback) {
91
+ const isRefreshEndpoint = path.includes("/api/auth/refresh") || path.includes("/api/auth/logout");
92
+ if (response.status === 401 && !isRetry && !isRefreshEndpoint && this.refreshCallback) {
93
93
  const newToken = await this.handleTokenRefresh();
94
94
  if (newToken) {
95
95
  this.setAuthToken(newToken);
@@ -139,7 +139,7 @@ var HttpClient = class {
139
139
  }
140
140
  this.isRefreshing = true;
141
141
  try {
142
- const newToken = await this.refreshCallback();
142
+ const newToken = await this.refreshCallback?.();
143
143
  this.refreshQueue.forEach(({ resolve, reject }) => {
144
144
  if (newToken) {
145
145
  resolve(newToken);
@@ -148,7 +148,7 @@ var HttpClient = class {
148
148
  }
149
149
  });
150
150
  this.refreshQueue = [];
151
- return newToken;
151
+ return newToken || null;
152
152
  } catch (error) {
153
153
  this.refreshQueue.forEach(({ reject }) => {
154
154
  reject(error instanceof Error ? error : new Error("Token refresh failed"));
@@ -200,10 +200,9 @@ var SecureSessionStorage = class {
200
200
  saveSession(session) {
201
201
  this.accessToken = session.accessToken;
202
202
  this.user = session.user;
203
- this.setAuthFlag(true);
204
203
  }
205
204
  getSession() {
206
- if (!this.accessToken) return null;
205
+ if (!this.accessToken || !this.user) return null;
207
206
  return {
208
207
  accessToken: this.accessToken,
209
208
  user: this.user
@@ -224,25 +223,17 @@ var SecureSessionStorage = class {
224
223
  clearSession() {
225
224
  this.accessToken = null;
226
225
  this.user = null;
227
- this.setAuthFlag(false);
228
226
  }
229
227
  shouldAttemptRefresh() {
230
228
  if (this.accessToken) return false;
231
229
  return this.hasAuthFlag();
232
230
  }
233
- // --- Private: Auth Flag Cookie Management ---
234
- setAuthFlag(authenticated) {
235
- if (typeof document === "undefined") return;
236
- if (authenticated) {
237
- const maxAge = 7 * 24 * 60 * 60;
238
- document.cookie = `${AUTH_FLAG_COOKIE}=true; path=/; max-age=${maxAge}; SameSite=Lax`;
239
- } else {
240
- document.cookie = `${AUTH_FLAG_COOKIE}=; path=/; max-age=0`;
241
- }
242
- }
231
+ // --- Private: Auth Flag Cookie Detection (SDK-managed on frontend domain) ---
243
232
  hasAuthFlag() {
244
233
  if (typeof document === "undefined") return false;
245
- return document.cookie.includes(`${AUTH_FLAG_COOKIE}=true`);
234
+ return document.cookie.split(";").some(
235
+ (c) => c.trim().startsWith(`${AUTH_FLAG_COOKIE}=`)
236
+ );
246
237
  }
247
238
  };
248
239
  var LocalSessionStorage = class {
@@ -388,41 +379,6 @@ var TokenManager = class {
388
379
  }
389
380
  };
390
381
 
391
- // src/lib/backend-config.ts
392
- var DEFAULT_CONFIG = {
393
- secureSessionStorage: false,
394
- refreshTokens: false
395
- };
396
- async function discoverBackendConfig(baseUrl, fetchImpl = globalThis.fetch) {
397
- try {
398
- const response = await fetchImpl(`${baseUrl}/api/health`, {
399
- method: "GET",
400
- headers: {
401
- "Accept": "application/json"
402
- }
403
- });
404
- if (!response.ok) {
405
- return DEFAULT_CONFIG;
406
- }
407
- const health = await response.json();
408
- if (health.config) {
409
- return health.config;
410
- }
411
- return DEFAULT_CONFIG;
412
- } catch {
413
- return DEFAULT_CONFIG;
414
- }
415
- }
416
- function createSessionStorage(config, storage) {
417
- if (config.secureSessionStorage && config.refreshTokens) {
418
- return new SecureSessionStorage();
419
- }
420
- return new LocalSessionStorage(storage);
421
- }
422
- function getDefaultBackendConfig() {
423
- return { ...DEFAULT_CONFIG };
424
- }
425
-
426
382
  // src/modules/database-postgrest.ts
427
383
  import { PostgrestClient } from "@supabase/postgrest-js";
428
384
  function createInsForgePostgrestFetch(httpClient, tokenManager) {
@@ -529,99 +485,75 @@ function isHostedAuthEnvironment() {
529
485
  return false;
530
486
  }
531
487
  var Auth = class {
532
- constructor(http, tokenManager, initializePromise) {
488
+ constructor(http, tokenManager) {
533
489
  this.http = http;
534
490
  this.tokenManager = tokenManager;
535
- this.authStateListeners = /* @__PURE__ */ new Set();
536
491
  this.database = new Database(http, tokenManager);
537
- this.initializePromise = initializePromise ?? Promise.resolve();
492
+ this.detectAuthCallback();
538
493
  }
539
494
  /**
540
- * Subscribe to auth state changes
541
- *
542
- * New subscribers will receive an INITIAL_SESSION event after initialization completes.
543
- * This ensures no race condition where subscribers miss the initial state.
544
- *
545
- * @param callback - Function called when auth state changes
546
- * @returns Unsubscribe function
547
- *
548
- * @example
549
- * ```typescript
550
- * const { data: { subscription } } = client.auth.onAuthStateChange((event, session) => {
551
- * if (event === 'SIGNED_IN') {
552
- * console.log('User signed in:', session?.user.email);
553
- * } else if (event === 'SIGNED_OUT') {
554
- * console.log('User signed out');
555
- * }
556
- * });
557
- *
558
- * // Later: unsubscribe
559
- * subscription.unsubscribe();
560
- * ```
495
+ * Set the isAuthenticated cookie flag on the frontend domain
496
+ * This is managed by SDK, not backend, to work in cross-origin scenarios
561
497
  */
562
- onAuthStateChange(callback) {
563
- this.authStateListeners.add(callback);
564
- ;
565
- (async () => {
566
- await this.initializePromise;
567
- if (this.authStateListeners.has(callback)) {
568
- const session = this.tokenManager.getSession();
569
- try {
570
- callback("INITIAL_SESSION", session);
571
- } catch (error) {
572
- console.error("[Auth] Error in auth state change listener:", error);
573
- }
574
- }
575
- })();
576
- return {
577
- data: {
578
- subscription: {
579
- unsubscribe: () => {
580
- this.authStateListeners.delete(callback);
581
- }
582
- }
583
- }
584
- };
498
+ setAuthenticatedCookie() {
499
+ if (typeof document === "undefined") return;
500
+ const maxAge = 7 * 24 * 60 * 60;
501
+ document.cookie = `${AUTH_FLAG_COOKIE}=true; path=/; max-age=${maxAge}; SameSite=Lax`;
585
502
  }
586
503
  /**
587
- * Emit auth state change to all listeners
504
+ * Clear the isAuthenticated cookie flag from the frontend domain
505
+ */
506
+ clearAuthenticatedCookie() {
507
+ if (typeof document === "undefined") return;
508
+ document.cookie = `${AUTH_FLAG_COOKIE}=; path=/; max-age=0; SameSite=Lax`;
509
+ }
510
+ /**
511
+ * Switch to SecureSessionStorage (cookie-based auth)
512
+ * Called when backend returns sessionMode: 'secure'
588
513
  * @internal
589
514
  */
590
- _emitAuthStateChange(event, session) {
591
- this.authStateListeners.forEach((callback) => {
592
- try {
593
- callback(event, session);
594
- } catch (error) {
595
- console.error("[Auth] Error in auth state change listener:", error);
596
- }
597
- });
515
+ _switchToSecureStorage() {
516
+ if (this.tokenManager.getStrategyId() === "secure") return;
517
+ const currentSession = this.tokenManager.getSession();
518
+ this.tokenManager.setStrategy(new SecureSessionStorage());
519
+ if (typeof localStorage !== "undefined") {
520
+ localStorage.removeItem(TOKEN_KEY);
521
+ localStorage.removeItem(USER_KEY);
522
+ }
523
+ this.setAuthenticatedCookie();
524
+ if (currentSession) {
525
+ this.tokenManager.saveSession(currentSession);
526
+ }
598
527
  }
599
528
  /**
600
- * Check if an error represents an authentication failure
601
- * Used to determine appropriate HTTP status code (401 vs 500)
529
+ * Switch to LocalSessionStorage (localStorage-based auth)
530
+ * Called when cookie-based auth fails (fallback)
531
+ * @internal
602
532
  */
603
- isAuthenticationError(error) {
604
- if (error instanceof Error) {
605
- const message = error.message.toLowerCase();
606
- const authKeywords = [
607
- "unauthorized",
608
- "invalid token",
609
- "expired token",
610
- "token expired",
611
- "invalid refresh token",
612
- "refresh token",
613
- "authentication",
614
- "not authenticated",
615
- "session expired"
616
- ];
617
- return authKeywords.some((keyword) => message.includes(keyword));
533
+ _switchToLocalStorage() {
534
+ if (this.tokenManager.getStrategyId() === "local") return;
535
+ const currentSession = this.tokenManager.getSession();
536
+ this.tokenManager.setStrategy(new LocalSessionStorage());
537
+ this.clearAuthenticatedCookie();
538
+ if (currentSession) {
539
+ this.tokenManager.saveSession(currentSession);
618
540
  }
619
- return false;
620
541
  }
621
542
  /**
622
- * Detect and handle OAuth callback parameters in the URL.
623
- * Called by client after initialization.
543
+ * Detect storage strategy based on backend response
544
+ * @param sessionMode - The sessionMode returned by backend ('secure' or undefined)
545
+ * @internal
624
546
  */
547
+ _detectStorageFromResponse(sessionMode) {
548
+ if (sessionMode === "secure") {
549
+ this._switchToSecureStorage();
550
+ }
551
+ }
552
+ /**
553
+ * Automatically detect and handle OAuth callback parameters in the URL
554
+ * This runs on initialization to seamlessly complete the OAuth flow
555
+ * Matches the backend's OAuth callback response (backend/src/api/routes/auth.ts:540-544)
556
+ */
625
557
  detectAuthCallback() {
626
558
  if (typeof window === "undefined") return;
627
559
  try {
@@ -630,7 +562,9 @@ var Auth = class {
630
562
  const userId = params.get("user_id");
631
563
  const email = params.get("email");
632
564
  const name = params.get("name");
565
+ const sessionMode = params.get("session_mode");
633
566
  if (accessToken && userId && email) {
567
+ this._detectStorageFromResponse(sessionMode || void 0);
634
568
  const session = {
635
569
  accessToken,
636
570
  user: {
@@ -651,13 +585,14 @@ var Auth = class {
651
585
  url.searchParams.delete("user_id");
652
586
  url.searchParams.delete("email");
653
587
  url.searchParams.delete("name");
588
+ url.searchParams.delete("session_mode");
654
589
  if (params.has("error")) {
655
590
  url.searchParams.delete("error");
656
591
  }
657
592
  window.history.replaceState({}, document.title, url.toString());
658
- this._emitAuthStateChange("SIGNED_IN", session);
659
593
  }
660
- } catch {
594
+ } catch (error) {
595
+ console.debug("OAuth callback detection skipped:", error);
661
596
  }
662
597
  }
663
598
  /**
@@ -666,6 +601,8 @@ var Auth = class {
666
601
  async signUp(request) {
667
602
  try {
668
603
  const response = await this.http.post("/api/auth/users", request);
604
+ const sessionMode = response.sessionMode;
605
+ this._detectStorageFromResponse(sessionMode);
669
606
  if (response.accessToken && response.user) {
670
607
  const session = {
671
608
  accessToken: response.accessToken,
@@ -675,7 +612,6 @@ var Auth = class {
675
612
  this.tokenManager.saveSession(session);
676
613
  }
677
614
  this.http.setAuthToken(response.accessToken);
678
- this._emitAuthStateChange("SIGNED_IN", session);
679
615
  }
680
616
  return {
681
617
  data: response,
@@ -701,6 +637,8 @@ var Auth = class {
701
637
  async signInWithPassword(request) {
702
638
  try {
703
639
  const response = await this.http.post("/api/auth/sessions", request);
640
+ const sessionMode = response.sessionMode;
641
+ this._detectStorageFromResponse(sessionMode);
704
642
  const session = {
705
643
  accessToken: response.accessToken || "",
706
644
  user: response.user || {
@@ -716,7 +654,6 @@ var Auth = class {
716
654
  this.tokenManager.saveSession(session);
717
655
  }
718
656
  this.http.setAuthToken(response.accessToken || "");
719
- this._emitAuthStateChange("SIGNED_IN", session);
720
657
  return {
721
658
  data: response,
722
659
  error: null
@@ -783,7 +720,7 @@ var Auth = class {
783
720
  }
784
721
  this.tokenManager.clearSession();
785
722
  this.http.setAuthToken(null);
786
- this._emitAuthStateChange("SIGNED_OUT", null);
723
+ this.clearAuthenticatedCookie();
787
724
  return { error: null };
788
725
  } catch (error) {
789
726
  return {
@@ -807,13 +744,12 @@ var Auth = class {
807
744
  "/api/auth/refresh"
808
745
  );
809
746
  if (response.accessToken) {
747
+ this._detectStorageFromResponse(response.sessionMode);
810
748
  this.tokenManager.setAccessToken(response.accessToken);
811
749
  this.http.setAuthToken(response.accessToken);
812
750
  if (response.user) {
813
751
  this.tokenManager.setUser(response.user);
814
752
  }
815
- const session = this.tokenManager.getSession();
816
- this._emitAuthStateChange("TOKEN_REFRESHED", session);
817
753
  return response.accessToken;
818
754
  }
819
755
  throw new InsForgeError(
@@ -826,18 +762,13 @@ var Auth = class {
826
762
  if (error.statusCode === 401 || error.statusCode === 403) {
827
763
  this.tokenManager.clearSession();
828
764
  this.http.setAuthToken(null);
765
+ this.clearAuthenticatedCookie();
829
766
  }
830
767
  throw error;
831
768
  }
832
- const errorMessage = error instanceof Error ? error.message : "Token refresh failed";
833
- const isAuthError = this.isAuthenticationError(error);
834
- if (isAuthError) {
835
- this.tokenManager.clearSession();
836
- this.http.setAuthToken(null);
837
- }
838
769
  throw new InsForgeError(
839
- errorMessage,
840
- isAuthError ? 401 : 500,
770
+ "Token refresh failed",
771
+ 500,
841
772
  "REFRESH_FAILED"
842
773
  );
843
774
  }
@@ -882,14 +813,27 @@ var Auth = class {
882
813
  /**
883
814
  * Get the current user with full profile information
884
815
  * Returns both auth info (id, email, role) and profile data (dynamic fields from users table)
816
+ *
817
+ * In secure session mode (httpOnly cookie), this method will automatically attempt
818
+ * to refresh the session if no access token is available (e.g., after page reload).
885
819
  */
886
820
  async getCurrentUser() {
887
821
  try {
888
- const session = this.tokenManager.getSession();
889
- if (!session?.accessToken) {
822
+ let accessToken = this.tokenManager.getAccessToken();
823
+ if (!accessToken && this.tokenManager.shouldAttemptRefresh()) {
824
+ try {
825
+ accessToken = await this.refreshToken();
826
+ } catch (error) {
827
+ if (error instanceof InsForgeError && (error.statusCode === 401 || error.statusCode === 403)) {
828
+ return { data: null, error };
829
+ }
830
+ return { data: null, error: error instanceof InsForgeError ? error : new InsForgeError("Token refresh failed", 500, "REFRESH_FAILED") };
831
+ }
832
+ }
833
+ if (!accessToken) {
890
834
  return { data: null, error: null };
891
835
  }
892
- this.http.setAuthToken(session.accessToken);
836
+ this.http.setAuthToken(accessToken);
893
837
  const authResponse = await this.http.get("/api/auth/sessions/current");
894
838
  const { data: profile, error: profileError } = await this.database.from("users").select("*").eq("id", authResponse.user.id).single();
895
839
  if (profileError && profileError.code !== "PGRST116") {
@@ -1152,6 +1096,8 @@ var Auth = class {
1152
1096
  "/api/auth/email/verify",
1153
1097
  request
1154
1098
  );
1099
+ const sessionMode = response.sessionMode;
1100
+ this._detectStorageFromResponse(sessionMode);
1155
1101
  if (response.accessToken) {
1156
1102
  const session = {
1157
1103
  accessToken: response.accessToken,
@@ -1159,7 +1105,6 @@ var Auth = class {
1159
1105
  };
1160
1106
  this.tokenManager.saveSession(session);
1161
1107
  this.http.setAuthToken(response.accessToken);
1162
- this._emitAuthStateChange("SIGNED_IN", session);
1163
1108
  }
1164
1109
  return {
1165
1110
  data: response,
@@ -1703,15 +1648,19 @@ var Functions = class {
1703
1648
  };
1704
1649
 
1705
1650
  // src/client.ts
1651
+ function hasAuthenticatedCookie() {
1652
+ if (typeof document === "undefined") return false;
1653
+ return document.cookie.split(";").some(
1654
+ (c) => c.trim().startsWith(`${AUTH_FLAG_COOKIE}=`)
1655
+ );
1656
+ }
1706
1657
  var InsForgeClient = class {
1707
1658
  constructor(config = {}) {
1708
- this.backendConfig = null;
1709
- this.initializePromise = new Promise((resolve) => {
1710
- this.initializeResolve = resolve;
1711
- });
1712
1659
  this.http = new HttpClient(config);
1713
1660
  this.tokenManager = new TokenManager(config.storage);
1714
- this.auth = new Auth(this.http, this.tokenManager, this.initializePromise);
1661
+ if (hasAuthenticatedCookie()) {
1662
+ this.tokenManager.setStrategy(new SecureSessionStorage());
1663
+ }
1715
1664
  if (config.edgeFunctionToken) {
1716
1665
  this.http.setAuthToken(config.edgeFunctionToken);
1717
1666
  this.tokenManager.saveSession({
@@ -1724,82 +1673,55 @@ var InsForgeClient = class {
1724
1673
  try {
1725
1674
  return await this.auth.refreshToken();
1726
1675
  } catch {
1676
+ if (this.tokenManager.getStrategyId() === "secure") {
1677
+ this.auth._switchToLocalStorage();
1678
+ }
1727
1679
  return null;
1728
1680
  }
1729
1681
  });
1730
1682
  const existingSession = this.tokenManager.getSession();
1731
1683
  if (existingSession?.accessToken) {
1732
1684
  this.http.setAuthToken(existingSession.accessToken);
1685
+ } else if (this.tokenManager.getStrategyId() === "secure") {
1733
1686
  }
1687
+ this.auth = new Auth(this.http, this.tokenManager);
1734
1688
  this.database = new Database(this.http, this.tokenManager);
1735
1689
  this.storage = new Storage(this.http);
1736
1690
  this.ai = new AI(this.http);
1737
1691
  this.functions = new Functions(this.http);
1738
- this._initializeAsync();
1739
- }
1740
- /**
1741
- * Internal async initialization - discovers backend config and recovers session.
1742
- * Emits INITIAL_SESSION event when complete.
1743
- * @internal
1744
- */
1745
- async _initializeAsync() {
1746
- try {
1747
- this.backendConfig = await discoverBackendConfig(
1748
- this.http.baseUrl,
1749
- this.http.fetch
1750
- );
1751
- const strategy = createSessionStorage(this.backendConfig);
1752
- this.tokenManager.setStrategy(strategy);
1753
- this.auth.detectAuthCallback();
1754
- let currentSession = this.tokenManager.getSession();
1755
- if (!currentSession?.accessToken && this.backendConfig.refreshTokens) {
1756
- if (this.tokenManager.shouldAttemptRefresh()) {
1757
- try {
1758
- await this.auth.refreshToken();
1759
- currentSession = this.tokenManager.getSession();
1760
- } catch {
1761
- this.tokenManager.clearSession();
1762
- this.http.setAuthToken(null);
1763
- }
1764
- }
1765
- }
1766
- this.initializeResolve();
1767
- } catch {
1768
- this.auth.detectAuthCallback();
1769
- this.initializeResolve();
1770
- }
1771
- }
1772
- /**
1773
- * Wait for client initialization to complete
1774
- * @returns Promise that resolves when initialization is done
1775
- */
1776
- async waitForInitialization() {
1777
- return this.initializePromise;
1778
1692
  }
1779
1693
  /**
1780
1694
  * Get the underlying HTTP client for custom requests
1695
+ *
1696
+ * @example
1697
+ * ```typescript
1698
+ * const httpClient = client.getHttpClient();
1699
+ * const customData = await httpClient.get('/api/custom-endpoint');
1700
+ * ```
1781
1701
  */
1782
1702
  getHttpClient() {
1783
1703
  return this.http;
1784
1704
  }
1785
- /**
1786
- * Get the discovered backend configuration
1787
- */
1788
- getBackendConfig() {
1789
- return this.backendConfig;
1790
- }
1791
1705
  /**
1792
1706
  * Get the current storage strategy identifier
1793
1707
  */
1794
1708
  getStorageStrategy() {
1795
1709
  return this.tokenManager.getStrategyId();
1796
1710
  }
1711
+ /**
1712
+ * Future modules will be added here:
1713
+ * - database: Database operations
1714
+ * - storage: File storage operations
1715
+ * - functions: Serverless functions
1716
+ * - tables: Table management
1717
+ * - metadata: Backend metadata
1718
+ */
1797
1719
  };
1798
- function createClient(config = {}) {
1799
- return new InsForgeClient(config);
1800
- }
1801
1720
 
1802
1721
  // src/index.ts
1722
+ function createClient(config) {
1723
+ return new InsForgeClient(config);
1724
+ }
1803
1725
  var index_default = InsForgeClient;
1804
1726
  export {
1805
1727
  AI,
@@ -1815,9 +1737,6 @@ export {
1815
1737
  StorageBucket,
1816
1738
  TokenManager,
1817
1739
  createClient,
1818
- createSessionStorage,
1819
- index_default as default,
1820
- discoverBackendConfig,
1821
- getDefaultBackendConfig
1740
+ index_default as default
1822
1741
  };
1823
1742
  //# sourceMappingURL=index.mjs.map