@insforge/sdk 1.0.1-refresh.3 → 1.0.1-refresh.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/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 (read-only) ---
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,66 @@ 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
+ * Check if the isAuthenticated cookie flag exists
603
535
  */
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
- };
536
+ hasAuthenticatedCookie() {
537
+ if (typeof document === "undefined") return false;
538
+ return document.cookie.split(";").some(
539
+ (c) => c.trim().startsWith(`${AUTH_FLAG_COOKIE}=`)
540
+ );
627
541
  }
628
542
  /**
629
- * Emit auth state change to all listeners
543
+ * Switch to SecureSessionStorage (cookie-based auth)
544
+ * Called when we detect backend supports secure cookie mode
630
545
  * @internal
631
546
  */
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
- });
547
+ _switchToSecureStorage() {
548
+ if (this.tokenManager.getStrategyId() === "secure") return;
549
+ const currentSession = this.tokenManager.getSession();
550
+ this.tokenManager.setStrategy(new SecureSessionStorage());
551
+ if (typeof localStorage !== "undefined") {
552
+ localStorage.removeItem(TOKEN_KEY);
553
+ localStorage.removeItem(USER_KEY);
554
+ }
555
+ if (currentSession) {
556
+ this.tokenManager.saveSession(currentSession);
557
+ }
640
558
  }
641
559
  /**
642
- * Check if an error represents an authentication failure
643
- * Used to determine appropriate HTTP status code (401 vs 500)
560
+ * Switch to LocalSessionStorage (localStorage-based auth)
561
+ * Called when cookie-based auth fails (fallback)
562
+ * @internal
644
563
  */
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));
564
+ _switchToLocalStorage() {
565
+ if (this.tokenManager.getStrategyId() === "local") return;
566
+ const currentSession = this.tokenManager.getSession();
567
+ this.tokenManager.setStrategy(new LocalSessionStorage());
568
+ if (currentSession) {
569
+ this.tokenManager.saveSession(currentSession);
660
570
  }
661
- return false;
662
571
  }
663
572
  /**
664
- * Detect and handle OAuth callback parameters in the URL.
665
- * Called by client after initialization.
573
+ * Detect storage strategy after successful auth
574
+ * Checks for isAuthenticated cookie to determine backend mode
575
+ * @internal
666
576
  */
577
+ _detectStorageAfterAuth() {
578
+ if (this.hasAuthenticatedCookie()) {
579
+ this._switchToSecureStorage();
580
+ }
581
+ }
582
+ /**
583
+ * Automatically detect and handle OAuth callback parameters in the URL
584
+ * This runs on initialization to seamlessly complete the OAuth flow
585
+ * Matches the backend's OAuth callback response (backend/src/api/routes/auth.ts:540-544)
586
+ */
667
587
  detectAuthCallback() {
668
588
  if (typeof window === "undefined") return;
669
589
  try {
@@ -673,6 +593,7 @@ var Auth = class {
673
593
  const email = params.get("email");
674
594
  const name = params.get("name");
675
595
  if (accessToken && userId && email) {
596
+ this._detectStorageAfterAuth();
676
597
  const session = {
677
598
  accessToken,
678
599
  user: {
@@ -697,9 +618,9 @@ var Auth = class {
697
618
  url.searchParams.delete("error");
698
619
  }
699
620
  window.history.replaceState({}, document.title, url.toString());
700
- this._emitAuthStateChange("SIGNED_IN", session);
701
621
  }
702
- } catch {
622
+ } catch (error) {
623
+ console.debug("OAuth callback detection skipped:", error);
703
624
  }
704
625
  }
705
626
  /**
@@ -717,7 +638,7 @@ var Auth = class {
717
638
  this.tokenManager.saveSession(session);
718
639
  }
719
640
  this.http.setAuthToken(response.accessToken);
720
- this._emitAuthStateChange("SIGNED_IN", session);
641
+ this._detectStorageAfterAuth();
721
642
  }
722
643
  return {
723
644
  data: response,
@@ -758,7 +679,7 @@ var Auth = class {
758
679
  this.tokenManager.saveSession(session);
759
680
  }
760
681
  this.http.setAuthToken(response.accessToken || "");
761
- this._emitAuthStateChange("SIGNED_IN", session);
682
+ this._detectStorageAfterAuth();
762
683
  return {
763
684
  data: response,
764
685
  error: null
@@ -825,7 +746,6 @@ var Auth = class {
825
746
  }
826
747
  this.tokenManager.clearSession();
827
748
  this.http.setAuthToken(null);
828
- this._emitAuthStateChange("SIGNED_OUT", null);
829
749
  return { error: null };
830
750
  } catch (error) {
831
751
  return {
@@ -854,8 +774,6 @@ var Auth = class {
854
774
  if (response.user) {
855
775
  this.tokenManager.setUser(response.user);
856
776
  }
857
- const session = this.tokenManager.getSession();
858
- this._emitAuthStateChange("TOKEN_REFRESHED", session);
859
777
  return response.accessToken;
860
778
  }
861
779
  throw new InsForgeError(
@@ -871,15 +789,9 @@ var Auth = class {
871
789
  }
872
790
  throw error;
873
791
  }
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
792
  throw new InsForgeError(
881
- errorMessage,
882
- isAuthError ? 401 : 500,
793
+ "Token refresh failed",
794
+ 500,
883
795
  "REFRESH_FAILED"
884
796
  );
885
797
  }
@@ -924,14 +836,27 @@ var Auth = class {
924
836
  /**
925
837
  * Get the current user with full profile information
926
838
  * Returns both auth info (id, email, role) and profile data (dynamic fields from users table)
839
+ *
840
+ * In secure session mode (httpOnly cookie), this method will automatically attempt
841
+ * to refresh the session if no access token is available (e.g., after page reload).
927
842
  */
928
843
  async getCurrentUser() {
929
844
  try {
930
- const session = this.tokenManager.getSession();
931
- if (!session?.accessToken) {
845
+ let accessToken = this.tokenManager.getAccessToken();
846
+ if (!accessToken && this.tokenManager.shouldAttemptRefresh()) {
847
+ try {
848
+ accessToken = await this.refreshToken();
849
+ } catch (error) {
850
+ if (error instanceof InsForgeError && (error.statusCode === 401 || error.statusCode === 403)) {
851
+ return { data: null, error };
852
+ }
853
+ return { data: null, error: error instanceof InsForgeError ? error : new InsForgeError("Token refresh failed", 500, "REFRESH_FAILED") };
854
+ }
855
+ }
856
+ if (!accessToken) {
932
857
  return { data: null, error: null };
933
858
  }
934
- this.http.setAuthToken(session.accessToken);
859
+ this.http.setAuthToken(accessToken);
935
860
  const authResponse = await this.http.get("/api/auth/sessions/current");
936
861
  const { data: profile, error: profileError } = await this.database.from("users").select("*").eq("id", authResponse.user.id).single();
937
862
  if (profileError && profileError.code !== "PGRST116") {
@@ -1201,7 +1126,6 @@ var Auth = class {
1201
1126
  };
1202
1127
  this.tokenManager.saveSession(session);
1203
1128
  this.http.setAuthToken(response.accessToken);
1204
- this._emitAuthStateChange("SIGNED_IN", session);
1205
1129
  }
1206
1130
  return {
1207
1131
  data: response,
@@ -1745,15 +1669,19 @@ var Functions = class {
1745
1669
  };
1746
1670
 
1747
1671
  // src/client.ts
1672
+ function hasAuthenticatedCookie() {
1673
+ if (typeof document === "undefined") return false;
1674
+ return document.cookie.split(";").some(
1675
+ (c) => c.trim().startsWith(`${AUTH_FLAG_COOKIE}=`)
1676
+ );
1677
+ }
1748
1678
  var InsForgeClient = class {
1749
1679
  constructor(config = {}) {
1750
- this.backendConfig = null;
1751
- this.initializePromise = new Promise((resolve) => {
1752
- this.initializeResolve = resolve;
1753
- });
1754
1680
  this.http = new HttpClient(config);
1755
1681
  this.tokenManager = new TokenManager(config.storage);
1756
- this.auth = new Auth(this.http, this.tokenManager, this.initializePromise);
1682
+ if (hasAuthenticatedCookie()) {
1683
+ this.tokenManager.setStrategy(new SecureSessionStorage());
1684
+ }
1757
1685
  if (config.edgeFunctionToken) {
1758
1686
  this.http.setAuthToken(config.edgeFunctionToken);
1759
1687
  this.tokenManager.saveSession({
@@ -1766,82 +1694,55 @@ var InsForgeClient = class {
1766
1694
  try {
1767
1695
  return await this.auth.refreshToken();
1768
1696
  } catch {
1697
+ if (this.tokenManager.getStrategyId() === "secure") {
1698
+ this.auth._switchToLocalStorage();
1699
+ }
1769
1700
  return null;
1770
1701
  }
1771
1702
  });
1772
1703
  const existingSession = this.tokenManager.getSession();
1773
1704
  if (existingSession?.accessToken) {
1774
1705
  this.http.setAuthToken(existingSession.accessToken);
1706
+ } else if (this.tokenManager.getStrategyId() === "secure") {
1775
1707
  }
1708
+ this.auth = new Auth(this.http, this.tokenManager);
1776
1709
  this.database = new Database(this.http, this.tokenManager);
1777
1710
  this.storage = new Storage(this.http);
1778
1711
  this.ai = new AI(this.http);
1779
1712
  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
1713
  }
1821
1714
  /**
1822
1715
  * Get the underlying HTTP client for custom requests
1716
+ *
1717
+ * @example
1718
+ * ```typescript
1719
+ * const httpClient = client.getHttpClient();
1720
+ * const customData = await httpClient.get('/api/custom-endpoint');
1721
+ * ```
1823
1722
  */
1824
1723
  getHttpClient() {
1825
1724
  return this.http;
1826
1725
  }
1827
- /**
1828
- * Get the discovered backend configuration
1829
- */
1830
- getBackendConfig() {
1831
- return this.backendConfig;
1832
- }
1833
1726
  /**
1834
1727
  * Get the current storage strategy identifier
1835
1728
  */
1836
1729
  getStorageStrategy() {
1837
1730
  return this.tokenManager.getStrategyId();
1838
1731
  }
1732
+ /**
1733
+ * Future modules will be added here:
1734
+ * - database: Database operations
1735
+ * - storage: File storage operations
1736
+ * - functions: Serverless functions
1737
+ * - tables: Table management
1738
+ * - metadata: Backend metadata
1739
+ */
1839
1740
  };
1840
- function createClient(config = {}) {
1841
- return new InsForgeClient(config);
1842
- }
1843
1741
 
1844
1742
  // src/index.ts
1743
+ function createClient(config) {
1744
+ return new InsForgeClient(config);
1745
+ }
1845
1746
  var index_default = InsForgeClient;
1846
1747
  // Annotate the CommonJS export names for ESM import in node:
1847
1748
  0 && (module.exports = {
@@ -1857,9 +1758,6 @@ var index_default = InsForgeClient;
1857
1758
  Storage,
1858
1759
  StorageBucket,
1859
1760
  TokenManager,
1860
- createClient,
1861
- createSessionStorage,
1862
- discoverBackendConfig,
1863
- getDefaultBackendConfig
1761
+ createClient
1864
1762
  });
1865
1763
  //# sourceMappingURL=index.js.map