@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.js CHANGED
@@ -33,10 +33,7 @@ __export(index_exports, {
33
33
  StorageBucket: () => StorageBucket,
34
34
  TokenManager: () => TokenManager,
35
35
  createClient: () => createClient,
36
- createSessionStorage: () => createSessionStorage,
37
- default: () => index_default,
38
- discoverBackendConfig: () => discoverBackendConfig,
39
- getDefaultBackendConfig: () => getDefaultBackendConfig
36
+ default: () => index_default
40
37
  });
41
38
  module.exports = __toCommonJS(index_exports);
42
39
 
@@ -127,11 +124,11 @@ var HttpClient = class {
127
124
  method,
128
125
  headers: requestHeaders,
129
126
  body: processedBody,
130
- credentials: "include",
131
- // Essential for httpOnly cookies (refresh token)
132
- ...fetchOptions
127
+ ...fetchOptions,
128
+ credentials: "include"
133
129
  });
134
- if (response.status === 401 && !isRetry && this.refreshCallback) {
130
+ const isRefreshEndpoint = path.includes("/api/auth/refresh") || path.includes("/api/auth/logout");
131
+ if (response.status === 401 && !isRetry && !isRefreshEndpoint && this.refreshCallback) {
135
132
  const newToken = await this.handleTokenRefresh();
136
133
  if (newToken) {
137
134
  this.setAuthToken(newToken);
@@ -181,7 +178,7 @@ var HttpClient = class {
181
178
  }
182
179
  this.isRefreshing = true;
183
180
  try {
184
- const newToken = await this.refreshCallback();
181
+ const newToken = await this.refreshCallback?.();
185
182
  this.refreshQueue.forEach(({ resolve, reject }) => {
186
183
  if (newToken) {
187
184
  resolve(newToken);
@@ -190,7 +187,7 @@ var HttpClient = class {
190
187
  }
191
188
  });
192
189
  this.refreshQueue = [];
193
- return newToken;
190
+ return newToken || null;
194
191
  } catch (error) {
195
192
  this.refreshQueue.forEach(({ reject }) => {
196
193
  reject(error instanceof Error ? error : new Error("Token refresh failed"));
@@ -242,10 +239,9 @@ var SecureSessionStorage = class {
242
239
  saveSession(session) {
243
240
  this.accessToken = session.accessToken;
244
241
  this.user = session.user;
245
- this.setAuthFlag(true);
246
242
  }
247
243
  getSession() {
248
- if (!this.accessToken) return null;
244
+ if (!this.accessToken || !this.user) return null;
249
245
  return {
250
246
  accessToken: this.accessToken,
251
247
  user: this.user
@@ -266,25 +262,17 @@ var SecureSessionStorage = class {
266
262
  clearSession() {
267
263
  this.accessToken = null;
268
264
  this.user = null;
269
- this.setAuthFlag(false);
270
265
  }
271
266
  shouldAttemptRefresh() {
272
267
  if (this.accessToken) return false;
273
268
  return this.hasAuthFlag();
274
269
  }
275
- // --- Private: Auth Flag Cookie Management ---
276
- setAuthFlag(authenticated) {
277
- if (typeof document === "undefined") return;
278
- if (authenticated) {
279
- const maxAge = 7 * 24 * 60 * 60;
280
- document.cookie = `${AUTH_FLAG_COOKIE}=true; path=/; max-age=${maxAge}; SameSite=Lax`;
281
- } else {
282
- document.cookie = `${AUTH_FLAG_COOKIE}=; path=/; max-age=0`;
283
- }
284
- }
270
+ // --- Private: Auth Flag Cookie Detection (SDK-managed on frontend domain) ---
285
271
  hasAuthFlag() {
286
272
  if (typeof document === "undefined") return false;
287
- return document.cookie.includes(`${AUTH_FLAG_COOKIE}=true`);
273
+ return document.cookie.split(";").some(
274
+ (c) => c.trim().startsWith(`${AUTH_FLAG_COOKIE}=`)
275
+ );
288
276
  }
289
277
  };
290
278
  var LocalSessionStorage = class {
@@ -430,41 +418,6 @@ var TokenManager = class {
430
418
  }
431
419
  };
432
420
 
433
- // src/lib/backend-config.ts
434
- var DEFAULT_CONFIG = {
435
- secureSessionStorage: false,
436
- refreshTokens: false
437
- };
438
- async function discoverBackendConfig(baseUrl, fetchImpl = globalThis.fetch) {
439
- try {
440
- const response = await fetchImpl(`${baseUrl}/api/health`, {
441
- method: "GET",
442
- headers: {
443
- "Accept": "application/json"
444
- }
445
- });
446
- if (!response.ok) {
447
- return DEFAULT_CONFIG;
448
- }
449
- const health = await response.json();
450
- if (health.config) {
451
- return health.config;
452
- }
453
- return DEFAULT_CONFIG;
454
- } catch {
455
- return DEFAULT_CONFIG;
456
- }
457
- }
458
- function createSessionStorage(config, storage) {
459
- if (config.secureSessionStorage && config.refreshTokens) {
460
- return new SecureSessionStorage();
461
- }
462
- return new LocalSessionStorage(storage);
463
- }
464
- function getDefaultBackendConfig() {
465
- return { ...DEFAULT_CONFIG };
466
- }
467
-
468
421
  // src/modules/database-postgrest.ts
469
422
  var import_postgrest_js = require("@supabase/postgrest-js");
470
423
  function createInsForgePostgrestFetch(httpClient, tokenManager) {
@@ -571,99 +524,75 @@ function isHostedAuthEnvironment() {
571
524
  return false;
572
525
  }
573
526
  var Auth = class {
574
- constructor(http, tokenManager, initializePromise) {
527
+ constructor(http, tokenManager) {
575
528
  this.http = http;
576
529
  this.tokenManager = tokenManager;
577
- this.authStateListeners = /* @__PURE__ */ new Set();
578
530
  this.database = new Database(http, tokenManager);
579
- this.initializePromise = initializePromise ?? Promise.resolve();
531
+ this.detectAuthCallback();
580
532
  }
581
533
  /**
582
- * Subscribe to auth state changes
583
- *
584
- * New subscribers will receive an INITIAL_SESSION event after initialization completes.
585
- * This ensures no race condition where subscribers miss the initial state.
586
- *
587
- * @param callback - Function called when auth state changes
588
- * @returns Unsubscribe function
589
- *
590
- * @example
591
- * ```typescript
592
- * const { data: { subscription } } = client.auth.onAuthStateChange((event, session) => {
593
- * if (event === 'SIGNED_IN') {
594
- * console.log('User signed in:', session?.user.email);
595
- * } else if (event === 'SIGNED_OUT') {
596
- * console.log('User signed out');
597
- * }
598
- * });
599
- *
600
- * // Later: unsubscribe
601
- * subscription.unsubscribe();
602
- * ```
534
+ * Set the isAuthenticated cookie flag on the frontend domain
535
+ * This is managed by SDK, not backend, to work in cross-origin scenarios
603
536
  */
604
- onAuthStateChange(callback) {
605
- this.authStateListeners.add(callback);
606
- ;
607
- (async () => {
608
- await this.initializePromise;
609
- if (this.authStateListeners.has(callback)) {
610
- const session = this.tokenManager.getSession();
611
- try {
612
- callback("INITIAL_SESSION", session);
613
- } catch (error) {
614
- console.error("[Auth] Error in auth state change listener:", error);
615
- }
616
- }
617
- })();
618
- return {
619
- data: {
620
- subscription: {
621
- unsubscribe: () => {
622
- this.authStateListeners.delete(callback);
623
- }
624
- }
625
- }
626
- };
537
+ setAuthenticatedCookie() {
538
+ if (typeof document === "undefined") return;
539
+ const maxAge = 7 * 24 * 60 * 60;
540
+ document.cookie = `${AUTH_FLAG_COOKIE}=true; path=/; max-age=${maxAge}; SameSite=Lax`;
627
541
  }
628
542
  /**
629
- * Emit auth state change to all listeners
543
+ * Clear the isAuthenticated cookie flag from the frontend domain
544
+ */
545
+ clearAuthenticatedCookie() {
546
+ if (typeof document === "undefined") return;
547
+ document.cookie = `${AUTH_FLAG_COOKIE}=; path=/; max-age=0; SameSite=Lax`;
548
+ }
549
+ /**
550
+ * Switch to SecureSessionStorage (cookie-based auth)
551
+ * Called when backend returns sessionMode: 'secure'
630
552
  * @internal
631
553
  */
632
- _emitAuthStateChange(event, session) {
633
- this.authStateListeners.forEach((callback) => {
634
- try {
635
- callback(event, session);
636
- } catch (error) {
637
- console.error("[Auth] Error in auth state change listener:", error);
638
- }
639
- });
554
+ _switchToSecureStorage() {
555
+ if (this.tokenManager.getStrategyId() === "secure") return;
556
+ const currentSession = this.tokenManager.getSession();
557
+ this.tokenManager.setStrategy(new SecureSessionStorage());
558
+ if (typeof localStorage !== "undefined") {
559
+ localStorage.removeItem(TOKEN_KEY);
560
+ localStorage.removeItem(USER_KEY);
561
+ }
562
+ this.setAuthenticatedCookie();
563
+ if (currentSession) {
564
+ this.tokenManager.saveSession(currentSession);
565
+ }
640
566
  }
641
567
  /**
642
- * Check if an error represents an authentication failure
643
- * Used to determine appropriate HTTP status code (401 vs 500)
568
+ * Switch to LocalSessionStorage (localStorage-based auth)
569
+ * Called when cookie-based auth fails (fallback)
570
+ * @internal
644
571
  */
645
- isAuthenticationError(error) {
646
- if (error instanceof Error) {
647
- const message = error.message.toLowerCase();
648
- const authKeywords = [
649
- "unauthorized",
650
- "invalid token",
651
- "expired token",
652
- "token expired",
653
- "invalid refresh token",
654
- "refresh token",
655
- "authentication",
656
- "not authenticated",
657
- "session expired"
658
- ];
659
- return authKeywords.some((keyword) => message.includes(keyword));
572
+ _switchToLocalStorage() {
573
+ if (this.tokenManager.getStrategyId() === "local") return;
574
+ const currentSession = this.tokenManager.getSession();
575
+ this.tokenManager.setStrategy(new LocalSessionStorage());
576
+ this.clearAuthenticatedCookie();
577
+ if (currentSession) {
578
+ this.tokenManager.saveSession(currentSession);
660
579
  }
661
- return false;
662
580
  }
663
581
  /**
664
- * Detect and handle OAuth callback parameters in the URL.
665
- * Called by client after initialization.
582
+ * Detect storage strategy based on backend response
583
+ * @param sessionMode - The sessionMode returned by backend ('secure' or undefined)
584
+ * @internal
666
585
  */
586
+ _detectStorageFromResponse(sessionMode) {
587
+ if (sessionMode === "secure") {
588
+ this._switchToSecureStorage();
589
+ }
590
+ }
591
+ /**
592
+ * Automatically detect and handle OAuth callback parameters in the URL
593
+ * This runs on initialization to seamlessly complete the OAuth flow
594
+ * Matches the backend's OAuth callback response (backend/src/api/routes/auth.ts:540-544)
595
+ */
667
596
  detectAuthCallback() {
668
597
  if (typeof window === "undefined") return;
669
598
  try {
@@ -672,7 +601,9 @@ var Auth = class {
672
601
  const userId = params.get("user_id");
673
602
  const email = params.get("email");
674
603
  const name = params.get("name");
604
+ const sessionMode = params.get("session_mode");
675
605
  if (accessToken && userId && email) {
606
+ this._detectStorageFromResponse(sessionMode || void 0);
676
607
  const session = {
677
608
  accessToken,
678
609
  user: {
@@ -693,13 +624,14 @@ var Auth = class {
693
624
  url.searchParams.delete("user_id");
694
625
  url.searchParams.delete("email");
695
626
  url.searchParams.delete("name");
627
+ url.searchParams.delete("session_mode");
696
628
  if (params.has("error")) {
697
629
  url.searchParams.delete("error");
698
630
  }
699
631
  window.history.replaceState({}, document.title, url.toString());
700
- this._emitAuthStateChange("SIGNED_IN", session);
701
632
  }
702
- } catch {
633
+ } catch (error) {
634
+ console.debug("OAuth callback detection skipped:", error);
703
635
  }
704
636
  }
705
637
  /**
@@ -708,6 +640,8 @@ var Auth = class {
708
640
  async signUp(request) {
709
641
  try {
710
642
  const response = await this.http.post("/api/auth/users", request);
643
+ const sessionMode = response.sessionMode;
644
+ this._detectStorageFromResponse(sessionMode);
711
645
  if (response.accessToken && response.user) {
712
646
  const session = {
713
647
  accessToken: response.accessToken,
@@ -717,7 +651,6 @@ var Auth = class {
717
651
  this.tokenManager.saveSession(session);
718
652
  }
719
653
  this.http.setAuthToken(response.accessToken);
720
- this._emitAuthStateChange("SIGNED_IN", session);
721
654
  }
722
655
  return {
723
656
  data: response,
@@ -743,6 +676,8 @@ var Auth = class {
743
676
  async signInWithPassword(request) {
744
677
  try {
745
678
  const response = await this.http.post("/api/auth/sessions", request);
679
+ const sessionMode = response.sessionMode;
680
+ this._detectStorageFromResponse(sessionMode);
746
681
  const session = {
747
682
  accessToken: response.accessToken || "",
748
683
  user: response.user || {
@@ -758,7 +693,6 @@ var Auth = class {
758
693
  this.tokenManager.saveSession(session);
759
694
  }
760
695
  this.http.setAuthToken(response.accessToken || "");
761
- this._emitAuthStateChange("SIGNED_IN", session);
762
696
  return {
763
697
  data: response,
764
698
  error: null
@@ -825,7 +759,7 @@ var Auth = class {
825
759
  }
826
760
  this.tokenManager.clearSession();
827
761
  this.http.setAuthToken(null);
828
- this._emitAuthStateChange("SIGNED_OUT", null);
762
+ this.clearAuthenticatedCookie();
829
763
  return { error: null };
830
764
  } catch (error) {
831
765
  return {
@@ -849,13 +783,12 @@ var Auth = class {
849
783
  "/api/auth/refresh"
850
784
  );
851
785
  if (response.accessToken) {
786
+ this._detectStorageFromResponse(response.sessionMode);
852
787
  this.tokenManager.setAccessToken(response.accessToken);
853
788
  this.http.setAuthToken(response.accessToken);
854
789
  if (response.user) {
855
790
  this.tokenManager.setUser(response.user);
856
791
  }
857
- const session = this.tokenManager.getSession();
858
- this._emitAuthStateChange("TOKEN_REFRESHED", session);
859
792
  return response.accessToken;
860
793
  }
861
794
  throw new InsForgeError(
@@ -868,18 +801,13 @@ var Auth = class {
868
801
  if (error.statusCode === 401 || error.statusCode === 403) {
869
802
  this.tokenManager.clearSession();
870
803
  this.http.setAuthToken(null);
804
+ this.clearAuthenticatedCookie();
871
805
  }
872
806
  throw error;
873
807
  }
874
- const errorMessage = error instanceof Error ? error.message : "Token refresh failed";
875
- const isAuthError = this.isAuthenticationError(error);
876
- if (isAuthError) {
877
- this.tokenManager.clearSession();
878
- this.http.setAuthToken(null);
879
- }
880
808
  throw new InsForgeError(
881
- errorMessage,
882
- isAuthError ? 401 : 500,
809
+ "Token refresh failed",
810
+ 500,
883
811
  "REFRESH_FAILED"
884
812
  );
885
813
  }
@@ -924,14 +852,27 @@ var Auth = class {
924
852
  /**
925
853
  * Get the current user with full profile information
926
854
  * Returns both auth info (id, email, role) and profile data (dynamic fields from users table)
855
+ *
856
+ * In secure session mode (httpOnly cookie), this method will automatically attempt
857
+ * to refresh the session if no access token is available (e.g., after page reload).
927
858
  */
928
859
  async getCurrentUser() {
929
860
  try {
930
- const session = this.tokenManager.getSession();
931
- if (!session?.accessToken) {
861
+ let accessToken = this.tokenManager.getAccessToken();
862
+ if (!accessToken && this.tokenManager.shouldAttemptRefresh()) {
863
+ try {
864
+ accessToken = await this.refreshToken();
865
+ } catch (error) {
866
+ if (error instanceof InsForgeError && (error.statusCode === 401 || error.statusCode === 403)) {
867
+ return { data: null, error };
868
+ }
869
+ return { data: null, error: error instanceof InsForgeError ? error : new InsForgeError("Token refresh failed", 500, "REFRESH_FAILED") };
870
+ }
871
+ }
872
+ if (!accessToken) {
932
873
  return { data: null, error: null };
933
874
  }
934
- this.http.setAuthToken(session.accessToken);
875
+ this.http.setAuthToken(accessToken);
935
876
  const authResponse = await this.http.get("/api/auth/sessions/current");
936
877
  const { data: profile, error: profileError } = await this.database.from("users").select("*").eq("id", authResponse.user.id).single();
937
878
  if (profileError && profileError.code !== "PGRST116") {
@@ -1194,6 +1135,8 @@ var Auth = class {
1194
1135
  "/api/auth/email/verify",
1195
1136
  request
1196
1137
  );
1138
+ const sessionMode = response.sessionMode;
1139
+ this._detectStorageFromResponse(sessionMode);
1197
1140
  if (response.accessToken) {
1198
1141
  const session = {
1199
1142
  accessToken: response.accessToken,
@@ -1201,7 +1144,6 @@ var Auth = class {
1201
1144
  };
1202
1145
  this.tokenManager.saveSession(session);
1203
1146
  this.http.setAuthToken(response.accessToken);
1204
- this._emitAuthStateChange("SIGNED_IN", session);
1205
1147
  }
1206
1148
  return {
1207
1149
  data: response,
@@ -1745,15 +1687,19 @@ var Functions = class {
1745
1687
  };
1746
1688
 
1747
1689
  // src/client.ts
1690
+ function hasAuthenticatedCookie() {
1691
+ if (typeof document === "undefined") return false;
1692
+ return document.cookie.split(";").some(
1693
+ (c) => c.trim().startsWith(`${AUTH_FLAG_COOKIE}=`)
1694
+ );
1695
+ }
1748
1696
  var InsForgeClient = class {
1749
1697
  constructor(config = {}) {
1750
- this.backendConfig = null;
1751
- this.initializePromise = new Promise((resolve) => {
1752
- this.initializeResolve = resolve;
1753
- });
1754
1698
  this.http = new HttpClient(config);
1755
1699
  this.tokenManager = new TokenManager(config.storage);
1756
- this.auth = new Auth(this.http, this.tokenManager, this.initializePromise);
1700
+ if (hasAuthenticatedCookie()) {
1701
+ this.tokenManager.setStrategy(new SecureSessionStorage());
1702
+ }
1757
1703
  if (config.edgeFunctionToken) {
1758
1704
  this.http.setAuthToken(config.edgeFunctionToken);
1759
1705
  this.tokenManager.saveSession({
@@ -1766,82 +1712,55 @@ var InsForgeClient = class {
1766
1712
  try {
1767
1713
  return await this.auth.refreshToken();
1768
1714
  } catch {
1715
+ if (this.tokenManager.getStrategyId() === "secure") {
1716
+ this.auth._switchToLocalStorage();
1717
+ }
1769
1718
  return null;
1770
1719
  }
1771
1720
  });
1772
1721
  const existingSession = this.tokenManager.getSession();
1773
1722
  if (existingSession?.accessToken) {
1774
1723
  this.http.setAuthToken(existingSession.accessToken);
1724
+ } else if (this.tokenManager.getStrategyId() === "secure") {
1775
1725
  }
1726
+ this.auth = new Auth(this.http, this.tokenManager);
1776
1727
  this.database = new Database(this.http, this.tokenManager);
1777
1728
  this.storage = new Storage(this.http);
1778
1729
  this.ai = new AI(this.http);
1779
1730
  this.functions = new Functions(this.http);
1780
- this._initializeAsync();
1781
- }
1782
- /**
1783
- * Internal async initialization - discovers backend config and recovers session.
1784
- * Emits INITIAL_SESSION event when complete.
1785
- * @internal
1786
- */
1787
- async _initializeAsync() {
1788
- try {
1789
- this.backendConfig = await discoverBackendConfig(
1790
- this.http.baseUrl,
1791
- this.http.fetch
1792
- );
1793
- const strategy = createSessionStorage(this.backendConfig);
1794
- this.tokenManager.setStrategy(strategy);
1795
- this.auth.detectAuthCallback();
1796
- let currentSession = this.tokenManager.getSession();
1797
- if (!currentSession?.accessToken && this.backendConfig.refreshTokens) {
1798
- if (this.tokenManager.shouldAttemptRefresh()) {
1799
- try {
1800
- await this.auth.refreshToken();
1801
- currentSession = this.tokenManager.getSession();
1802
- } catch {
1803
- this.tokenManager.clearSession();
1804
- this.http.setAuthToken(null);
1805
- }
1806
- }
1807
- }
1808
- this.initializeResolve();
1809
- } catch {
1810
- this.auth.detectAuthCallback();
1811
- this.initializeResolve();
1812
- }
1813
- }
1814
- /**
1815
- * Wait for client initialization to complete
1816
- * @returns Promise that resolves when initialization is done
1817
- */
1818
- async waitForInitialization() {
1819
- return this.initializePromise;
1820
1731
  }
1821
1732
  /**
1822
1733
  * Get the underlying HTTP client for custom requests
1734
+ *
1735
+ * @example
1736
+ * ```typescript
1737
+ * const httpClient = client.getHttpClient();
1738
+ * const customData = await httpClient.get('/api/custom-endpoint');
1739
+ * ```
1823
1740
  */
1824
1741
  getHttpClient() {
1825
1742
  return this.http;
1826
1743
  }
1827
- /**
1828
- * Get the discovered backend configuration
1829
- */
1830
- getBackendConfig() {
1831
- return this.backendConfig;
1832
- }
1833
1744
  /**
1834
1745
  * Get the current storage strategy identifier
1835
1746
  */
1836
1747
  getStorageStrategy() {
1837
1748
  return this.tokenManager.getStrategyId();
1838
1749
  }
1750
+ /**
1751
+ * Future modules will be added here:
1752
+ * - database: Database operations
1753
+ * - storage: File storage operations
1754
+ * - functions: Serverless functions
1755
+ * - tables: Table management
1756
+ * - metadata: Backend metadata
1757
+ */
1839
1758
  };
1840
- function createClient(config = {}) {
1841
- return new InsForgeClient(config);
1842
- }
1843
1759
 
1844
1760
  // src/index.ts
1761
+ function createClient(config) {
1762
+ return new InsForgeClient(config);
1763
+ }
1845
1764
  var index_default = InsForgeClient;
1846
1765
  // Annotate the CommonJS export names for ESM import in node:
1847
1766
  0 && (module.exports = {
@@ -1857,9 +1776,6 @@ var index_default = InsForgeClient;
1857
1776
  Storage,
1858
1777
  StorageBucket,
1859
1778
  TokenManager,
1860
- createClient,
1861
- createSessionStorage,
1862
- discoverBackendConfig,
1863
- getDefaultBackendConfig
1779
+ createClient
1864
1780
  });
1865
1781
  //# sourceMappingURL=index.js.map