@insforge/sdk 1.0.1-refresh.2 → 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.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,30 +223,22 @@ 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 (read-only) ---
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
- var PersistentSessionStorage = class {
239
+ var LocalSessionStorage = class {
249
240
  constructor(storage) {
250
- this.strategyId = "persistent";
241
+ this.strategyId = "local";
251
242
  if (storage) {
252
243
  this.storage = storage;
253
244
  } else if (typeof window !== "undefined" && window.localStorage) {
@@ -313,10 +304,10 @@ var PersistentSessionStorage = class {
313
304
  var TokenManager = class {
314
305
  /**
315
306
  * Create a new TokenManager
316
- * @param storage - Optional custom storage adapter (used for initial PersistentSessionStorage)
307
+ * @param storage - Optional custom storage adapter (used for initial LocalSessionStorage)
317
308
  */
318
309
  constructor(storage) {
319
- this.strategy = new PersistentSessionStorage(storage);
310
+ this.strategy = new LocalSessionStorage(storage);
320
311
  }
321
312
  /**
322
313
  * Set the storage strategy
@@ -388,41 +379,6 @@ var TokenManager = class {
388
379
  }
389
380
  };
390
381
 
391
- // src/lib/capability-discovery.ts
392
- var DEFAULT_CAPABILITIES = {
393
- secureSessionStorage: false,
394
- refreshTokens: false
395
- };
396
- async function discoverCapabilities(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_CAPABILITIES;
406
- }
407
- const health = await response.json();
408
- if (health.capabilities) {
409
- return health.capabilities;
410
- }
411
- return DEFAULT_CAPABILITIES;
412
- } catch {
413
- return DEFAULT_CAPABILITIES;
414
- }
415
- }
416
- function createSessionStorage(capabilities, storage) {
417
- if (capabilities.secureSessionStorage && capabilities.refreshTokens) {
418
- return new SecureSessionStorage();
419
- }
420
- return new PersistentSessionStorage(storage);
421
- }
422
- function getDefaultCapabilities() {
423
- return { ...DEFAULT_CAPABILITIES };
424
- }
425
-
426
382
  // src/modules/database-postgrest.ts
427
383
  import { PostgrestClient } from "@supabase/postgrest-js";
428
384
  function createInsForgePostgrestFetch(httpClient, tokenManager) {
@@ -532,62 +488,73 @@ var Auth = class {
532
488
  constructor(http, tokenManager) {
533
489
  this.http = http;
534
490
  this.tokenManager = tokenManager;
535
- this.initPromise = null;
536
491
  this.database = new Database(http, tokenManager);
492
+ this.detectAuthCallback();
537
493
  }
538
494
  /**
539
- * Check if an error represents an authentication failure
540
- * Used to determine appropriate HTTP status code (401 vs 500)
495
+ * Check if the isAuthenticated cookie flag exists
541
496
  */
542
- isAuthenticationError(error) {
543
- if (error instanceof Error) {
544
- const message = error.message.toLowerCase();
545
- const authKeywords = [
546
- "unauthorized",
547
- "invalid token",
548
- "expired token",
549
- "token expired",
550
- "invalid refresh token",
551
- "refresh token",
552
- "authentication",
553
- "not authenticated",
554
- "session expired"
555
- ];
556
- return authKeywords.some((keyword) => message.includes(keyword));
557
- }
558
- return false;
497
+ hasAuthenticatedCookie() {
498
+ if (typeof document === "undefined") return false;
499
+ return document.cookie.split(";").some(
500
+ (c) => c.trim().startsWith(`${AUTH_FLAG_COOKIE}=`)
501
+ );
559
502
  }
560
503
  /**
561
- * Set the initialization promise that auth operations should wait for
562
- * This ensures TokenManager mode is set before any auth operations
504
+ * Switch to SecureSessionStorage (cookie-based auth)
505
+ * Called when we detect backend supports secure cookie mode
506
+ * @internal
563
507
  */
564
- setInitPromise(promise) {
565
- this.initPromise = promise;
566
- this.detectAuthCallbackAsync();
508
+ _switchToSecureStorage() {
509
+ if (this.tokenManager.getStrategyId() === "secure") return;
510
+ const currentSession = this.tokenManager.getSession();
511
+ this.tokenManager.setStrategy(new SecureSessionStorage());
512
+ if (typeof localStorage !== "undefined") {
513
+ localStorage.removeItem(TOKEN_KEY);
514
+ localStorage.removeItem(USER_KEY);
515
+ }
516
+ if (currentSession) {
517
+ this.tokenManager.saveSession(currentSession);
518
+ }
567
519
  }
568
520
  /**
569
- * Wait for initialization to complete (if set)
521
+ * Switch to LocalSessionStorage (localStorage-based auth)
522
+ * Called when cookie-based auth fails (fallback)
523
+ * @internal
570
524
  */
571
- async waitForInit() {
572
- if (this.initPromise) {
573
- await this.initPromise;
525
+ _switchToLocalStorage() {
526
+ if (this.tokenManager.getStrategyId() === "local") return;
527
+ const currentSession = this.tokenManager.getSession();
528
+ this.tokenManager.setStrategy(new LocalSessionStorage());
529
+ if (currentSession) {
530
+ this.tokenManager.saveSession(currentSession);
574
531
  }
575
532
  }
576
533
  /**
577
- * Automatically detect and handle OAuth callback parameters in the URL
578
- * This runs after initialization to seamlessly complete the OAuth flow
579
- * Matches the backend's OAuth callback response (backend/src/api/routes/auth.ts:540-544)
534
+ * Detect storage strategy after successful auth
535
+ * Checks for isAuthenticated cookie to determine backend mode
536
+ * @internal
580
537
  */
581
- async detectAuthCallbackAsync() {
538
+ _detectStorageAfterAuth() {
539
+ if (this.hasAuthenticatedCookie()) {
540
+ this._switchToSecureStorage();
541
+ }
542
+ }
543
+ /**
544
+ * Automatically detect and handle OAuth callback parameters in the URL
545
+ * This runs on initialization to seamlessly complete the OAuth flow
546
+ * Matches the backend's OAuth callback response (backend/src/api/routes/auth.ts:540-544)
547
+ */
548
+ detectAuthCallback() {
582
549
  if (typeof window === "undefined") return;
583
550
  try {
584
- await this.waitForInit();
585
551
  const params = new URLSearchParams(window.location.search);
586
552
  const accessToken = params.get("access_token");
587
553
  const userId = params.get("user_id");
588
554
  const email = params.get("email");
589
555
  const name = params.get("name");
590
556
  if (accessToken && userId && email) {
557
+ this._detectStorageAfterAuth();
591
558
  const session = {
592
559
  accessToken,
593
560
  user: {
@@ -613,7 +580,8 @@ var Auth = class {
613
580
  }
614
581
  window.history.replaceState({}, document.title, url.toString());
615
582
  }
616
- } catch {
583
+ } catch (error) {
584
+ console.debug("OAuth callback detection skipped:", error);
617
585
  }
618
586
  }
619
587
  /**
@@ -621,7 +589,6 @@ var Auth = class {
621
589
  */
622
590
  async signUp(request) {
623
591
  try {
624
- await this.waitForInit();
625
592
  const response = await this.http.post("/api/auth/users", request);
626
593
  if (response.accessToken && response.user) {
627
594
  const session = {
@@ -632,6 +599,7 @@ var Auth = class {
632
599
  this.tokenManager.saveSession(session);
633
600
  }
634
601
  this.http.setAuthToken(response.accessToken);
602
+ this._detectStorageAfterAuth();
635
603
  }
636
604
  return {
637
605
  data: response,
@@ -656,7 +624,6 @@ var Auth = class {
656
624
  */
657
625
  async signInWithPassword(request) {
658
626
  try {
659
- await this.waitForInit();
660
627
  const response = await this.http.post("/api/auth/sessions", request);
661
628
  const session = {
662
629
  accessToken: response.accessToken || "",
@@ -673,6 +640,7 @@ var Auth = class {
673
640
  this.tokenManager.saveSession(session);
674
641
  }
675
642
  this.http.setAuthToken(response.accessToken || "");
643
+ this._detectStorageAfterAuth();
676
644
  return {
677
645
  data: response,
678
646
  error: null
@@ -782,15 +750,9 @@ var Auth = class {
782
750
  }
783
751
  throw error;
784
752
  }
785
- const errorMessage = error instanceof Error ? error.message : "Token refresh failed";
786
- const isAuthError = this.isAuthenticationError(error);
787
- if (isAuthError) {
788
- this.tokenManager.clearSession();
789
- this.http.setAuthToken(null);
790
- }
791
753
  throw new InsForgeError(
792
- errorMessage,
793
- isAuthError ? 401 : 500,
754
+ "Token refresh failed",
755
+ 500,
794
756
  "REFRESH_FAILED"
795
757
  );
796
758
  }
@@ -835,14 +797,27 @@ var Auth = class {
835
797
  /**
836
798
  * Get the current user with full profile information
837
799
  * Returns both auth info (id, email, role) and profile data (dynamic fields from users table)
800
+ *
801
+ * In secure session mode (httpOnly cookie), this method will automatically attempt
802
+ * to refresh the session if no access token is available (e.g., after page reload).
838
803
  */
839
804
  async getCurrentUser() {
840
805
  try {
841
- const session = this.tokenManager.getSession();
842
- if (!session?.accessToken) {
806
+ let accessToken = this.tokenManager.getAccessToken();
807
+ if (!accessToken && this.tokenManager.shouldAttemptRefresh()) {
808
+ try {
809
+ accessToken = await this.refreshToken();
810
+ } catch (error) {
811
+ if (error instanceof InsForgeError && (error.statusCode === 401 || error.statusCode === 403)) {
812
+ return { data: null, error };
813
+ }
814
+ return { data: null, error: error instanceof InsForgeError ? error : new InsForgeError("Token refresh failed", 500, "REFRESH_FAILED") };
815
+ }
816
+ }
817
+ if (!accessToken) {
843
818
  return { data: null, error: null };
844
819
  }
845
- this.http.setAuthToken(session.accessToken);
820
+ this.http.setAuthToken(accessToken);
846
821
  const authResponse = await this.http.get("/api/auth/sessions/current");
847
822
  const { data: profile, error: profileError } = await this.database.from("users").select("*").eq("id", authResponse.user.id).single();
848
823
  if (profileError && profileError.code !== "PGRST116") {
@@ -1101,7 +1076,6 @@ var Auth = class {
1101
1076
  */
1102
1077
  async verifyEmail(request) {
1103
1078
  try {
1104
- await this.waitForInit();
1105
1079
  const response = await this.http.post(
1106
1080
  "/api/auth/email/verify",
1107
1081
  request
@@ -1656,13 +1630,19 @@ var Functions = class {
1656
1630
  };
1657
1631
 
1658
1632
  // src/client.ts
1633
+ function hasAuthenticatedCookie() {
1634
+ if (typeof document === "undefined") return false;
1635
+ return document.cookie.split(";").some(
1636
+ (c) => c.trim().startsWith(`${AUTH_FLAG_COOKIE}=`)
1637
+ );
1638
+ }
1659
1639
  var InsForgeClient = class {
1660
1640
  constructor(config = {}) {
1661
- this.initialized = false;
1662
- this.initializationPromise = null;
1663
- this.capabilities = null;
1664
1641
  this.http = new HttpClient(config);
1665
1642
  this.tokenManager = new TokenManager(config.storage);
1643
+ if (hasAuthenticatedCookie()) {
1644
+ this.tokenManager.setStrategy(new SecureSessionStorage());
1645
+ }
1666
1646
  if (config.edgeFunctionToken) {
1667
1647
  this.http.setAuthToken(config.edgeFunctionToken);
1668
1648
  this.tokenManager.saveSession({
@@ -1671,65 +1651,26 @@ var InsForgeClient = class {
1671
1651
  // Will be populated by getCurrentUser()
1672
1652
  });
1673
1653
  }
1674
- this.auth = new Auth(this.http, this.tokenManager);
1675
1654
  this.http.setRefreshCallback(async () => {
1676
1655
  try {
1677
1656
  return await this.auth.refreshToken();
1678
1657
  } catch {
1658
+ if (this.tokenManager.getStrategyId() === "secure") {
1659
+ this.auth._switchToLocalStorage();
1660
+ }
1679
1661
  return null;
1680
1662
  }
1681
1663
  });
1682
1664
  const existingSession = this.tokenManager.getSession();
1683
1665
  if (existingSession?.accessToken) {
1684
1666
  this.http.setAuthToken(existingSession.accessToken);
1667
+ } else if (this.tokenManager.getStrategyId() === "secure") {
1685
1668
  }
1669
+ this.auth = new Auth(this.http, this.tokenManager);
1686
1670
  this.database = new Database(this.http, this.tokenManager);
1687
1671
  this.storage = new Storage(this.http);
1688
1672
  this.ai = new AI(this.http);
1689
1673
  this.functions = new Functions(this.http);
1690
- this.initializationPromise = this.initializeAsync();
1691
- this.auth.setInitPromise(this.initializationPromise);
1692
- }
1693
- /**
1694
- * Initialize the client by discovering backend capabilities
1695
- * This is called automatically on construction but can be awaited for guaranteed initialization
1696
- *
1697
- * @example
1698
- * ```typescript
1699
- * const client = new InsForgeClient({ baseUrl: 'https://api.example.com' });
1700
- * await client.initialize(); // Wait for capability discovery
1701
- * ```
1702
- */
1703
- async initialize() {
1704
- if (this.initializationPromise) {
1705
- await this.initializationPromise;
1706
- }
1707
- }
1708
- /**
1709
- * Internal async initialization - discovers capabilities and configures storage strategy
1710
- */
1711
- async initializeAsync() {
1712
- if (this.initialized) return;
1713
- try {
1714
- this.capabilities = await discoverCapabilities(
1715
- this.http.baseUrl,
1716
- this.http.fetch
1717
- );
1718
- const strategy = createSessionStorage(this.capabilities);
1719
- this.tokenManager.setStrategy(strategy);
1720
- if (this.capabilities.refreshTokens && this.tokenManager.shouldAttemptRefresh()) {
1721
- try {
1722
- const newToken = await this.auth.refreshToken();
1723
- this.http.setAuthToken(newToken);
1724
- } catch {
1725
- this.tokenManager.clearSession();
1726
- this.http.setAuthToken(null);
1727
- }
1728
- }
1729
- this.initialized = true;
1730
- } catch {
1731
- this.initialized = true;
1732
- }
1733
1674
  }
1734
1675
  /**
1735
1676
  * Get the underlying HTTP client for custom requests
@@ -1743,12 +1684,6 @@ var InsForgeClient = class {
1743
1684
  getHttpClient() {
1744
1685
  return this.http;
1745
1686
  }
1746
- /**
1747
- * Get the discovered backend capabilities
1748
- */
1749
- getCapabilities() {
1750
- return this.capabilities;
1751
- }
1752
1687
  /**
1753
1688
  * Get the current storage strategy identifier
1754
1689
  */
@@ -1756,11 +1691,13 @@ var InsForgeClient = class {
1756
1691
  return this.tokenManager.getStrategyId();
1757
1692
  }
1758
1693
  /**
1759
- * Check if the client has been fully initialized
1694
+ * Future modules will be added here:
1695
+ * - database: Database operations
1696
+ * - storage: File storage operations
1697
+ * - functions: Serverless functions
1698
+ * - tables: Table management
1699
+ * - metadata: Backend metadata
1760
1700
  */
1761
- isInitialized() {
1762
- return this.initialized;
1763
- }
1764
1701
  };
1765
1702
 
1766
1703
  // src/index.ts
@@ -1776,15 +1713,12 @@ export {
1776
1713
  HttpClient,
1777
1714
  InsForgeClient,
1778
1715
  InsForgeError,
1779
- PersistentSessionStorage,
1716
+ LocalSessionStorage,
1780
1717
  SecureSessionStorage,
1781
1718
  Storage,
1782
1719
  StorageBucket,
1783
1720
  TokenManager,
1784
1721
  createClient,
1785
- createSessionStorage,
1786
- index_default as default,
1787
- discoverCapabilities,
1788
- getDefaultCapabilities
1722
+ index_default as default
1789
1723
  };
1790
1724
  //# sourceMappingURL=index.mjs.map