@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.mjs CHANGED
@@ -140,9 +140,11 @@ var HttpClient = class {
140
140
  this.isRefreshing = true;
141
141
  try {
142
142
  const newToken = await this.refreshCallback();
143
- this.refreshQueue.forEach(({ resolve }) => {
143
+ this.refreshQueue.forEach(({ resolve, reject }) => {
144
144
  if (newToken) {
145
145
  resolve(newToken);
146
+ } else {
147
+ reject(new Error("Token refresh failed"));
146
148
  }
147
149
  });
148
150
  this.refreshQueue = [];
@@ -243,9 +245,9 @@ var SecureSessionStorage = class {
243
245
  return document.cookie.includes(`${AUTH_FLAG_COOKIE}=true`);
244
246
  }
245
247
  };
246
- var PersistentSessionStorage = class {
248
+ var LocalSessionStorage = class {
247
249
  constructor(storage) {
248
- this.strategyId = "persistent";
250
+ this.strategyId = "local";
249
251
  if (storage) {
250
252
  this.storage = storage;
251
253
  } else if (typeof window !== "undefined" && window.localStorage) {
@@ -311,10 +313,10 @@ var PersistentSessionStorage = class {
311
313
  var TokenManager = class {
312
314
  /**
313
315
  * Create a new TokenManager
314
- * @param storage - Optional custom storage adapter (used for initial PersistentSessionStorage)
316
+ * @param storage - Optional custom storage adapter (used for initial LocalSessionStorage)
315
317
  */
316
318
  constructor(storage) {
317
- this.strategy = new PersistentSessionStorage(storage);
319
+ this.strategy = new LocalSessionStorage(storage);
318
320
  }
319
321
  /**
320
322
  * Set the storage strategy
@@ -386,12 +388,12 @@ var TokenManager = class {
386
388
  }
387
389
  };
388
390
 
389
- // src/lib/capability-discovery.ts
390
- var DEFAULT_CAPABILITIES = {
391
+ // src/lib/backend-config.ts
392
+ var DEFAULT_CONFIG = {
391
393
  secureSessionStorage: false,
392
394
  refreshTokens: false
393
395
  };
394
- async function discoverCapabilities(baseUrl, fetchImpl = globalThis.fetch) {
396
+ async function discoverBackendConfig(baseUrl, fetchImpl = globalThis.fetch) {
395
397
  try {
396
398
  const response = await fetchImpl(`${baseUrl}/api/health`, {
397
399
  method: "GET",
@@ -400,25 +402,25 @@ async function discoverCapabilities(baseUrl, fetchImpl = globalThis.fetch) {
400
402
  }
401
403
  });
402
404
  if (!response.ok) {
403
- return DEFAULT_CAPABILITIES;
405
+ return DEFAULT_CONFIG;
404
406
  }
405
407
  const health = await response.json();
406
- if (health.capabilities) {
407
- return health.capabilities;
408
+ if (health.config) {
409
+ return health.config;
408
410
  }
409
- return DEFAULT_CAPABILITIES;
411
+ return DEFAULT_CONFIG;
410
412
  } catch {
411
- return DEFAULT_CAPABILITIES;
413
+ return DEFAULT_CONFIG;
412
414
  }
413
415
  }
414
- function createSessionStorage(capabilities, storage) {
415
- if (capabilities.secureSessionStorage && capabilities.refreshTokens) {
416
+ function createSessionStorage(config, storage) {
417
+ if (config.secureSessionStorage && config.refreshTokens) {
416
418
  return new SecureSessionStorage();
417
419
  }
418
- return new PersistentSessionStorage(storage);
420
+ return new LocalSessionStorage(storage);
419
421
  }
420
- function getDefaultCapabilities() {
421
- return { ...DEFAULT_CAPABILITIES };
422
+ function getDefaultBackendConfig() {
423
+ return { ...DEFAULT_CONFIG };
422
424
  }
423
425
 
424
426
  // src/modules/database-postgrest.ts
@@ -527,37 +529,102 @@ function isHostedAuthEnvironment() {
527
529
  return false;
528
530
  }
529
531
  var Auth = class {
530
- constructor(http, tokenManager) {
532
+ constructor(http, tokenManager, initializePromise) {
531
533
  this.http = http;
532
534
  this.tokenManager = tokenManager;
533
- this.initPromise = null;
535
+ this.authStateListeners = /* @__PURE__ */ new Set();
534
536
  this.database = new Database(http, tokenManager);
537
+ this.initializePromise = initializePromise ?? Promise.resolve();
538
+ }
539
+ /**
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
+ * ```
561
+ */
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
+ };
535
585
  }
536
586
  /**
537
- * Set the initialization promise that auth operations should wait for
538
- * This ensures TokenManager mode is set before any auth operations
587
+ * Emit auth state change to all listeners
588
+ * @internal
539
589
  */
540
- setInitPromise(promise) {
541
- this.initPromise = promise;
542
- this.detectAuthCallbackAsync();
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
+ });
543
598
  }
544
599
  /**
545
- * Wait for initialization to complete (if set)
600
+ * Check if an error represents an authentication failure
601
+ * Used to determine appropriate HTTP status code (401 vs 500)
546
602
  */
547
- async waitForInit() {
548
- if (this.initPromise) {
549
- await this.initPromise;
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));
550
618
  }
619
+ return false;
551
620
  }
552
621
  /**
553
- * Automatically detect and handle OAuth callback parameters in the URL
554
- * This runs after initialization to seamlessly complete the OAuth flow
555
- * Matches the backend's OAuth callback response (backend/src/api/routes/auth.ts:540-544)
622
+ * Detect and handle OAuth callback parameters in the URL.
623
+ * Called by client after initialization.
556
624
  */
557
- async detectAuthCallbackAsync() {
625
+ detectAuthCallback() {
558
626
  if (typeof window === "undefined") return;
559
627
  try {
560
- await this.waitForInit();
561
628
  const params = new URLSearchParams(window.location.search);
562
629
  const accessToken = params.get("access_token");
563
630
  const userId = params.get("user_id");
@@ -588,6 +655,7 @@ var Auth = class {
588
655
  url.searchParams.delete("error");
589
656
  }
590
657
  window.history.replaceState({}, document.title, url.toString());
658
+ this._emitAuthStateChange("SIGNED_IN", session);
591
659
  }
592
660
  } catch {
593
661
  }
@@ -597,7 +665,6 @@ var Auth = class {
597
665
  */
598
666
  async signUp(request) {
599
667
  try {
600
- await this.waitForInit();
601
668
  const response = await this.http.post("/api/auth/users", request);
602
669
  if (response.accessToken && response.user) {
603
670
  const session = {
@@ -608,6 +675,7 @@ var Auth = class {
608
675
  this.tokenManager.saveSession(session);
609
676
  }
610
677
  this.http.setAuthToken(response.accessToken);
678
+ this._emitAuthStateChange("SIGNED_IN", session);
611
679
  }
612
680
  return {
613
681
  data: response,
@@ -632,7 +700,6 @@ var Auth = class {
632
700
  */
633
701
  async signInWithPassword(request) {
634
702
  try {
635
- await this.waitForInit();
636
703
  const response = await this.http.post("/api/auth/sessions", request);
637
704
  const session = {
638
705
  accessToken: response.accessToken || "",
@@ -649,6 +716,7 @@ var Auth = class {
649
716
  this.tokenManager.saveSession(session);
650
717
  }
651
718
  this.http.setAuthToken(response.accessToken || "");
719
+ this._emitAuthStateChange("SIGNED_IN", session);
652
720
  return {
653
721
  data: response,
654
722
  error: null
@@ -715,6 +783,7 @@ var Auth = class {
715
783
  }
716
784
  this.tokenManager.clearSession();
717
785
  this.http.setAuthToken(null);
786
+ this._emitAuthStateChange("SIGNED_OUT", null);
718
787
  return { error: null };
719
788
  } catch (error) {
720
789
  return {
@@ -743,6 +812,8 @@ var Auth = class {
743
812
  if (response.user) {
744
813
  this.tokenManager.setUser(response.user);
745
814
  }
815
+ const session = this.tokenManager.getSession();
816
+ this._emitAuthStateChange("TOKEN_REFRESHED", session);
746
817
  return response.accessToken;
747
818
  }
748
819
  throw new InsForgeError(
@@ -751,14 +822,22 @@ var Auth = class {
751
822
  "REFRESH_FAILED"
752
823
  );
753
824
  } catch (error) {
754
- this.tokenManager.clearSession();
755
- this.http.setAuthToken(null);
756
825
  if (error instanceof InsForgeError) {
826
+ if (error.statusCode === 401 || error.statusCode === 403) {
827
+ this.tokenManager.clearSession();
828
+ this.http.setAuthToken(null);
829
+ }
757
830
  throw error;
758
831
  }
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
+ }
759
838
  throw new InsForgeError(
760
- error instanceof Error ? error.message : "Token refresh failed",
761
- 401,
839
+ errorMessage,
840
+ isAuthError ? 401 : 500,
762
841
  "REFRESH_FAILED"
763
842
  );
764
843
  }
@@ -1069,7 +1148,6 @@ var Auth = class {
1069
1148
  */
1070
1149
  async verifyEmail(request) {
1071
1150
  try {
1072
- await this.waitForInit();
1073
1151
  const response = await this.http.post(
1074
1152
  "/api/auth/email/verify",
1075
1153
  request
@@ -1081,6 +1159,7 @@ var Auth = class {
1081
1159
  };
1082
1160
  this.tokenManager.saveSession(session);
1083
1161
  this.http.setAuthToken(response.accessToken);
1162
+ this._emitAuthStateChange("SIGNED_IN", session);
1084
1163
  }
1085
1164
  return {
1086
1165
  data: response,
@@ -1626,11 +1705,13 @@ var Functions = class {
1626
1705
  // src/client.ts
1627
1706
  var InsForgeClient = class {
1628
1707
  constructor(config = {}) {
1629
- this.initialized = false;
1630
- this.initializationPromise = null;
1631
- this.capabilities = null;
1708
+ this.backendConfig = null;
1709
+ this.initializePromise = new Promise((resolve) => {
1710
+ this.initializeResolve = resolve;
1711
+ });
1632
1712
  this.http = new HttpClient(config);
1633
1713
  this.tokenManager = new TokenManager(config.storage);
1714
+ this.auth = new Auth(this.http, this.tokenManager, this.initializePromise);
1634
1715
  if (config.edgeFunctionToken) {
1635
1716
  this.http.setAuthToken(config.edgeFunctionToken);
1636
1717
  this.tokenManager.saveSession({
@@ -1639,7 +1720,6 @@ var InsForgeClient = class {
1639
1720
  // Will be populated by getCurrentUser()
1640
1721
  });
1641
1722
  }
1642
- this.auth = new Auth(this.http, this.tokenManager);
1643
1723
  this.http.setRefreshCallback(async () => {
1644
1724
  try {
1645
1725
  return await this.auth.refreshToken();
@@ -1655,67 +1735,58 @@ var InsForgeClient = class {
1655
1735
  this.storage = new Storage(this.http);
1656
1736
  this.ai = new AI(this.http);
1657
1737
  this.functions = new Functions(this.http);
1658
- this.initializationPromise = this.initializeAsync();
1659
- this.auth.setInitPromise(this.initializationPromise);
1738
+ this._initializeAsync();
1660
1739
  }
1661
1740
  /**
1662
- * Initialize the client by discovering backend capabilities
1663
- * This is called automatically on construction but can be awaited for guaranteed initialization
1664
- *
1665
- * @example
1666
- * ```typescript
1667
- * const client = new InsForgeClient({ baseUrl: 'https://api.example.com' });
1668
- * await client.initialize(); // Wait for capability discovery
1669
- * ```
1741
+ * Internal async initialization - discovers backend config and recovers session.
1742
+ * Emits INITIAL_SESSION event when complete.
1743
+ * @internal
1670
1744
  */
1671
- async initialize() {
1672
- if (this.initializationPromise) {
1673
- await this.initializationPromise;
1674
- }
1675
- }
1676
- /**
1677
- * Internal async initialization - discovers capabilities and configures storage strategy
1678
- */
1679
- async initializeAsync() {
1680
- if (this.initialized) return;
1745
+ async _initializeAsync() {
1681
1746
  try {
1682
- this.capabilities = await discoverCapabilities(
1747
+ this.backendConfig = await discoverBackendConfig(
1683
1748
  this.http.baseUrl,
1684
1749
  this.http.fetch
1685
1750
  );
1686
- const strategy = createSessionStorage(this.capabilities);
1751
+ const strategy = createSessionStorage(this.backendConfig);
1687
1752
  this.tokenManager.setStrategy(strategy);
1688
- if (this.capabilities.refreshTokens && this.tokenManager.shouldAttemptRefresh()) {
1689
- try {
1690
- const newToken = await this.auth.refreshToken();
1691
- this.http.setAuthToken(newToken);
1692
- } catch {
1693
- this.tokenManager.clearSession();
1694
- this.http.setAuthToken(null);
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
+ }
1695
1764
  }
1696
1765
  }
1697
- this.initialized = true;
1766
+ this.initializeResolve();
1698
1767
  } catch {
1699
- this.initialized = true;
1768
+ this.auth.detectAuthCallback();
1769
+ this.initializeResolve();
1700
1770
  }
1701
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
+ }
1702
1779
  /**
1703
1780
  * Get the underlying HTTP client for custom requests
1704
- *
1705
- * @example
1706
- * ```typescript
1707
- * const httpClient = client.getHttpClient();
1708
- * const customData = await httpClient.get('/api/custom-endpoint');
1709
- * ```
1710
1781
  */
1711
1782
  getHttpClient() {
1712
1783
  return this.http;
1713
1784
  }
1714
1785
  /**
1715
- * Get the discovered backend capabilities
1786
+ * Get the discovered backend configuration
1716
1787
  */
1717
- getCapabilities() {
1718
- return this.capabilities;
1788
+ getBackendConfig() {
1789
+ return this.backendConfig;
1719
1790
  }
1720
1791
  /**
1721
1792
  * Get the current storage strategy identifier
@@ -1723,18 +1794,12 @@ var InsForgeClient = class {
1723
1794
  getStorageStrategy() {
1724
1795
  return this.tokenManager.getStrategyId();
1725
1796
  }
1726
- /**
1727
- * Check if the client has been fully initialized
1728
- */
1729
- isInitialized() {
1730
- return this.initialized;
1731
- }
1732
1797
  };
1733
-
1734
- // src/index.ts
1735
- function createClient(config) {
1798
+ function createClient(config = {}) {
1736
1799
  return new InsForgeClient(config);
1737
1800
  }
1801
+
1802
+ // src/index.ts
1738
1803
  var index_default = InsForgeClient;
1739
1804
  export {
1740
1805
  AI,
@@ -1744,7 +1809,7 @@ export {
1744
1809
  HttpClient,
1745
1810
  InsForgeClient,
1746
1811
  InsForgeError,
1747
- PersistentSessionStorage,
1812
+ LocalSessionStorage,
1748
1813
  SecureSessionStorage,
1749
1814
  Storage,
1750
1815
  StorageBucket,
@@ -1752,7 +1817,7 @@ export {
1752
1817
  createClient,
1753
1818
  createSessionStorage,
1754
1819
  index_default as default,
1755
- discoverCapabilities,
1756
- getDefaultCapabilities
1820
+ discoverBackendConfig,
1821
+ getDefaultBackendConfig
1757
1822
  };
1758
1823
  //# sourceMappingURL=index.mjs.map