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

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
@@ -27,7 +27,7 @@ __export(index_exports, {
27
27
  HttpClient: () => HttpClient,
28
28
  InsForgeClient: () => InsForgeClient,
29
29
  InsForgeError: () => InsForgeError,
30
- PersistentSessionStorage: () => PersistentSessionStorage,
30
+ LocalSessionStorage: () => LocalSessionStorage,
31
31
  SecureSessionStorage: () => SecureSessionStorage,
32
32
  Storage: () => Storage,
33
33
  StorageBucket: () => StorageBucket,
@@ -35,8 +35,8 @@ __export(index_exports, {
35
35
  createClient: () => createClient,
36
36
  createSessionStorage: () => createSessionStorage,
37
37
  default: () => index_default,
38
- discoverCapabilities: () => discoverCapabilities,
39
- getDefaultCapabilities: () => getDefaultCapabilities
38
+ discoverBackendConfig: () => discoverBackendConfig,
39
+ getDefaultBackendConfig: () => getDefaultBackendConfig
40
40
  });
41
41
  module.exports = __toCommonJS(index_exports);
42
42
 
@@ -182,9 +182,11 @@ var HttpClient = class {
182
182
  this.isRefreshing = true;
183
183
  try {
184
184
  const newToken = await this.refreshCallback();
185
- this.refreshQueue.forEach(({ resolve }) => {
185
+ this.refreshQueue.forEach(({ resolve, reject }) => {
186
186
  if (newToken) {
187
187
  resolve(newToken);
188
+ } else {
189
+ reject(new Error("Token refresh failed"));
188
190
  }
189
191
  });
190
192
  this.refreshQueue = [];
@@ -285,9 +287,9 @@ var SecureSessionStorage = class {
285
287
  return document.cookie.includes(`${AUTH_FLAG_COOKIE}=true`);
286
288
  }
287
289
  };
288
- var PersistentSessionStorage = class {
290
+ var LocalSessionStorage = class {
289
291
  constructor(storage) {
290
- this.strategyId = "persistent";
292
+ this.strategyId = "local";
291
293
  if (storage) {
292
294
  this.storage = storage;
293
295
  } else if (typeof window !== "undefined" && window.localStorage) {
@@ -353,10 +355,10 @@ var PersistentSessionStorage = class {
353
355
  var TokenManager = class {
354
356
  /**
355
357
  * Create a new TokenManager
356
- * @param storage - Optional custom storage adapter (used for initial PersistentSessionStorage)
358
+ * @param storage - Optional custom storage adapter (used for initial LocalSessionStorage)
357
359
  */
358
360
  constructor(storage) {
359
- this.strategy = new PersistentSessionStorage(storage);
361
+ this.strategy = new LocalSessionStorage(storage);
360
362
  }
361
363
  /**
362
364
  * Set the storage strategy
@@ -428,12 +430,12 @@ var TokenManager = class {
428
430
  }
429
431
  };
430
432
 
431
- // src/lib/capability-discovery.ts
432
- var DEFAULT_CAPABILITIES = {
433
+ // src/lib/backend-config.ts
434
+ var DEFAULT_CONFIG = {
433
435
  secureSessionStorage: false,
434
436
  refreshTokens: false
435
437
  };
436
- async function discoverCapabilities(baseUrl, fetchImpl = globalThis.fetch) {
438
+ async function discoverBackendConfig(baseUrl, fetchImpl = globalThis.fetch) {
437
439
  try {
438
440
  const response = await fetchImpl(`${baseUrl}/api/health`, {
439
441
  method: "GET",
@@ -442,25 +444,25 @@ async function discoverCapabilities(baseUrl, fetchImpl = globalThis.fetch) {
442
444
  }
443
445
  });
444
446
  if (!response.ok) {
445
- return DEFAULT_CAPABILITIES;
447
+ return DEFAULT_CONFIG;
446
448
  }
447
449
  const health = await response.json();
448
- if (health.capabilities) {
449
- return health.capabilities;
450
+ if (health.config) {
451
+ return health.config;
450
452
  }
451
- return DEFAULT_CAPABILITIES;
453
+ return DEFAULT_CONFIG;
452
454
  } catch {
453
- return DEFAULT_CAPABILITIES;
455
+ return DEFAULT_CONFIG;
454
456
  }
455
457
  }
456
- function createSessionStorage(capabilities, storage) {
457
- if (capabilities.secureSessionStorage && capabilities.refreshTokens) {
458
+ function createSessionStorage(config, storage) {
459
+ if (config.secureSessionStorage && config.refreshTokens) {
458
460
  return new SecureSessionStorage();
459
461
  }
460
- return new PersistentSessionStorage(storage);
462
+ return new LocalSessionStorage(storage);
461
463
  }
462
- function getDefaultCapabilities() {
463
- return { ...DEFAULT_CAPABILITIES };
464
+ function getDefaultBackendConfig() {
465
+ return { ...DEFAULT_CONFIG };
464
466
  }
465
467
 
466
468
  // src/modules/database-postgrest.ts
@@ -569,37 +571,102 @@ function isHostedAuthEnvironment() {
569
571
  return false;
570
572
  }
571
573
  var Auth = class {
572
- constructor(http, tokenManager) {
574
+ constructor(http, tokenManager, initializePromise) {
573
575
  this.http = http;
574
576
  this.tokenManager = tokenManager;
575
- this.initPromise = null;
577
+ this.authStateListeners = /* @__PURE__ */ new Set();
576
578
  this.database = new Database(http, tokenManager);
579
+ this.initializePromise = initializePromise ?? Promise.resolve();
580
+ }
581
+ /**
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
+ * ```
603
+ */
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
+ };
577
627
  }
578
628
  /**
579
- * Set the initialization promise that auth operations should wait for
580
- * This ensures TokenManager mode is set before any auth operations
629
+ * Emit auth state change to all listeners
630
+ * @internal
581
631
  */
582
- setInitPromise(promise) {
583
- this.initPromise = promise;
584
- this.detectAuthCallbackAsync();
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
+ });
585
640
  }
586
641
  /**
587
- * Wait for initialization to complete (if set)
642
+ * Check if an error represents an authentication failure
643
+ * Used to determine appropriate HTTP status code (401 vs 500)
588
644
  */
589
- async waitForInit() {
590
- if (this.initPromise) {
591
- await this.initPromise;
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));
592
660
  }
661
+ return false;
593
662
  }
594
663
  /**
595
- * Automatically detect and handle OAuth callback parameters in the URL
596
- * This runs after initialization to seamlessly complete the OAuth flow
597
- * Matches the backend's OAuth callback response (backend/src/api/routes/auth.ts:540-544)
664
+ * Detect and handle OAuth callback parameters in the URL.
665
+ * Called by client after initialization.
598
666
  */
599
- async detectAuthCallbackAsync() {
667
+ detectAuthCallback() {
600
668
  if (typeof window === "undefined") return;
601
669
  try {
602
- await this.waitForInit();
603
670
  const params = new URLSearchParams(window.location.search);
604
671
  const accessToken = params.get("access_token");
605
672
  const userId = params.get("user_id");
@@ -630,6 +697,7 @@ var Auth = class {
630
697
  url.searchParams.delete("error");
631
698
  }
632
699
  window.history.replaceState({}, document.title, url.toString());
700
+ this._emitAuthStateChange("SIGNED_IN", session);
633
701
  }
634
702
  } catch {
635
703
  }
@@ -639,7 +707,6 @@ var Auth = class {
639
707
  */
640
708
  async signUp(request) {
641
709
  try {
642
- await this.waitForInit();
643
710
  const response = await this.http.post("/api/auth/users", request);
644
711
  if (response.accessToken && response.user) {
645
712
  const session = {
@@ -650,6 +717,7 @@ var Auth = class {
650
717
  this.tokenManager.saveSession(session);
651
718
  }
652
719
  this.http.setAuthToken(response.accessToken);
720
+ this._emitAuthStateChange("SIGNED_IN", session);
653
721
  }
654
722
  return {
655
723
  data: response,
@@ -674,7 +742,6 @@ var Auth = class {
674
742
  */
675
743
  async signInWithPassword(request) {
676
744
  try {
677
- await this.waitForInit();
678
745
  const response = await this.http.post("/api/auth/sessions", request);
679
746
  const session = {
680
747
  accessToken: response.accessToken || "",
@@ -691,6 +758,7 @@ var Auth = class {
691
758
  this.tokenManager.saveSession(session);
692
759
  }
693
760
  this.http.setAuthToken(response.accessToken || "");
761
+ this._emitAuthStateChange("SIGNED_IN", session);
694
762
  return {
695
763
  data: response,
696
764
  error: null
@@ -757,6 +825,7 @@ var Auth = class {
757
825
  }
758
826
  this.tokenManager.clearSession();
759
827
  this.http.setAuthToken(null);
828
+ this._emitAuthStateChange("SIGNED_OUT", null);
760
829
  return { error: null };
761
830
  } catch (error) {
762
831
  return {
@@ -785,6 +854,8 @@ var Auth = class {
785
854
  if (response.user) {
786
855
  this.tokenManager.setUser(response.user);
787
856
  }
857
+ const session = this.tokenManager.getSession();
858
+ this._emitAuthStateChange("TOKEN_REFRESHED", session);
788
859
  return response.accessToken;
789
860
  }
790
861
  throw new InsForgeError(
@@ -793,14 +864,22 @@ var Auth = class {
793
864
  "REFRESH_FAILED"
794
865
  );
795
866
  } catch (error) {
796
- this.tokenManager.clearSession();
797
- this.http.setAuthToken(null);
798
867
  if (error instanceof InsForgeError) {
868
+ if (error.statusCode === 401 || error.statusCode === 403) {
869
+ this.tokenManager.clearSession();
870
+ this.http.setAuthToken(null);
871
+ }
799
872
  throw error;
800
873
  }
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
+ }
801
880
  throw new InsForgeError(
802
- error instanceof Error ? error.message : "Token refresh failed",
803
- 401,
881
+ errorMessage,
882
+ isAuthError ? 401 : 500,
804
883
  "REFRESH_FAILED"
805
884
  );
806
885
  }
@@ -1111,7 +1190,6 @@ var Auth = class {
1111
1190
  */
1112
1191
  async verifyEmail(request) {
1113
1192
  try {
1114
- await this.waitForInit();
1115
1193
  const response = await this.http.post(
1116
1194
  "/api/auth/email/verify",
1117
1195
  request
@@ -1123,6 +1201,7 @@ var Auth = class {
1123
1201
  };
1124
1202
  this.tokenManager.saveSession(session);
1125
1203
  this.http.setAuthToken(response.accessToken);
1204
+ this._emitAuthStateChange("SIGNED_IN", session);
1126
1205
  }
1127
1206
  return {
1128
1207
  data: response,
@@ -1668,11 +1747,13 @@ var Functions = class {
1668
1747
  // src/client.ts
1669
1748
  var InsForgeClient = class {
1670
1749
  constructor(config = {}) {
1671
- this.initialized = false;
1672
- this.initializationPromise = null;
1673
- this.capabilities = null;
1750
+ this.backendConfig = null;
1751
+ this.initializePromise = new Promise((resolve) => {
1752
+ this.initializeResolve = resolve;
1753
+ });
1674
1754
  this.http = new HttpClient(config);
1675
1755
  this.tokenManager = new TokenManager(config.storage);
1756
+ this.auth = new Auth(this.http, this.tokenManager, this.initializePromise);
1676
1757
  if (config.edgeFunctionToken) {
1677
1758
  this.http.setAuthToken(config.edgeFunctionToken);
1678
1759
  this.tokenManager.saveSession({
@@ -1681,7 +1762,6 @@ var InsForgeClient = class {
1681
1762
  // Will be populated by getCurrentUser()
1682
1763
  });
1683
1764
  }
1684
- this.auth = new Auth(this.http, this.tokenManager);
1685
1765
  this.http.setRefreshCallback(async () => {
1686
1766
  try {
1687
1767
  return await this.auth.refreshToken();
@@ -1697,67 +1777,58 @@ var InsForgeClient = class {
1697
1777
  this.storage = new Storage(this.http);
1698
1778
  this.ai = new AI(this.http);
1699
1779
  this.functions = new Functions(this.http);
1700
- this.initializationPromise = this.initializeAsync();
1701
- this.auth.setInitPromise(this.initializationPromise);
1780
+ this._initializeAsync();
1702
1781
  }
1703
1782
  /**
1704
- * Initialize the client by discovering backend capabilities
1705
- * This is called automatically on construction but can be awaited for guaranteed initialization
1706
- *
1707
- * @example
1708
- * ```typescript
1709
- * const client = new InsForgeClient({ baseUrl: 'https://api.example.com' });
1710
- * await client.initialize(); // Wait for capability discovery
1711
- * ```
1783
+ * Internal async initialization - discovers backend config and recovers session.
1784
+ * Emits INITIAL_SESSION event when complete.
1785
+ * @internal
1712
1786
  */
1713
- async initialize() {
1714
- if (this.initializationPromise) {
1715
- await this.initializationPromise;
1716
- }
1717
- }
1718
- /**
1719
- * Internal async initialization - discovers capabilities and configures storage strategy
1720
- */
1721
- async initializeAsync() {
1722
- if (this.initialized) return;
1787
+ async _initializeAsync() {
1723
1788
  try {
1724
- this.capabilities = await discoverCapabilities(
1789
+ this.backendConfig = await discoverBackendConfig(
1725
1790
  this.http.baseUrl,
1726
1791
  this.http.fetch
1727
1792
  );
1728
- const strategy = createSessionStorage(this.capabilities);
1793
+ const strategy = createSessionStorage(this.backendConfig);
1729
1794
  this.tokenManager.setStrategy(strategy);
1730
- if (this.capabilities.refreshTokens && this.tokenManager.shouldAttemptRefresh()) {
1731
- try {
1732
- const newToken = await this.auth.refreshToken();
1733
- this.http.setAuthToken(newToken);
1734
- } catch {
1735
- this.tokenManager.clearSession();
1736
- this.http.setAuthToken(null);
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
+ }
1737
1806
  }
1738
1807
  }
1739
- this.initialized = true;
1808
+ this.initializeResolve();
1740
1809
  } catch {
1741
- this.initialized = true;
1810
+ this.auth.detectAuthCallback();
1811
+ this.initializeResolve();
1742
1812
  }
1743
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
+ }
1744
1821
  /**
1745
1822
  * Get the underlying HTTP client for custom requests
1746
- *
1747
- * @example
1748
- * ```typescript
1749
- * const httpClient = client.getHttpClient();
1750
- * const customData = await httpClient.get('/api/custom-endpoint');
1751
- * ```
1752
1823
  */
1753
1824
  getHttpClient() {
1754
1825
  return this.http;
1755
1826
  }
1756
1827
  /**
1757
- * Get the discovered backend capabilities
1828
+ * Get the discovered backend configuration
1758
1829
  */
1759
- getCapabilities() {
1760
- return this.capabilities;
1830
+ getBackendConfig() {
1831
+ return this.backendConfig;
1761
1832
  }
1762
1833
  /**
1763
1834
  * Get the current storage strategy identifier
@@ -1765,18 +1836,12 @@ var InsForgeClient = class {
1765
1836
  getStorageStrategy() {
1766
1837
  return this.tokenManager.getStrategyId();
1767
1838
  }
1768
- /**
1769
- * Check if the client has been fully initialized
1770
- */
1771
- isInitialized() {
1772
- return this.initialized;
1773
- }
1774
1839
  };
1775
-
1776
- // src/index.ts
1777
- function createClient(config) {
1840
+ function createClient(config = {}) {
1778
1841
  return new InsForgeClient(config);
1779
1842
  }
1843
+
1844
+ // src/index.ts
1780
1845
  var index_default = InsForgeClient;
1781
1846
  // Annotate the CommonJS export names for ESM import in node:
1782
1847
  0 && (module.exports = {
@@ -1787,14 +1852,14 @@ var index_default = InsForgeClient;
1787
1852
  HttpClient,
1788
1853
  InsForgeClient,
1789
1854
  InsForgeError,
1790
- PersistentSessionStorage,
1855
+ LocalSessionStorage,
1791
1856
  SecureSessionStorage,
1792
1857
  Storage,
1793
1858
  StorageBucket,
1794
1859
  TokenManager,
1795
1860
  createClient,
1796
1861
  createSessionStorage,
1797
- discoverCapabilities,
1798
- getDefaultCapabilities
1862
+ discoverBackendConfig,
1863
+ getDefaultBackendConfig
1799
1864
  });
1800
1865
  //# sourceMappingURL=index.js.map