@feelflow/ffid-sdk 5.24.2 → 5.25.0

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.
@@ -270,6 +270,47 @@ interface FFIDServiceAccessDecision {
270
270
  error?: FFIDServiceAccessError;
271
271
  }
272
272
 
273
+ /**
274
+ * Token Store
275
+ *
276
+ * Manages OAuth 2.0 tokens (access + refresh) with dual-storage support.
277
+ * Falls back to in-memory storage when localStorage is unavailable
278
+ * (e.g., Safari private browsing mode).
279
+ */
280
+ /**
281
+ * Token data stored by the token store
282
+ */
283
+ interface TokenData {
284
+ /** OAuth 2.0 access token */
285
+ accessToken: string;
286
+ /** OAuth 2.0 refresh token */
287
+ refreshToken: string;
288
+ /** Expiration timestamp in milliseconds (Unix epoch) */
289
+ expiresAt: number;
290
+ }
291
+ /**
292
+ * Token store interface for managing OAuth tokens
293
+ */
294
+ interface TokenStore {
295
+ /** Get stored tokens (null if not stored) */
296
+ getTokens(): TokenData | null;
297
+ /** Store new tokens */
298
+ setTokens(tokens: TokenData): void;
299
+ /** Clear all stored tokens */
300
+ clearTokens(): void;
301
+ /** Check if access token is expired (with 30s buffer) */
302
+ isAccessTokenExpired(): boolean;
303
+ }
304
+ /**
305
+ * Create a token store with the specified storage type.
306
+ *
307
+ * When storageType is 'localStorage' (default in browser), falls back
308
+ * to memory if localStorage is not available (e.g., Safari private mode).
309
+ *
310
+ * @param storageType - 'localStorage' (default) or 'memory'
311
+ */
312
+ declare function createTokenStore(storageType?: 'localStorage' | 'memory'): TokenStore;
313
+
273
314
  /**
274
315
  * Billing checkout / portal session types.
275
316
  *
@@ -1304,6 +1345,57 @@ interface FFIDUpdateUserProfileRequest {
1304
1345
  preferences?: Record<string, unknown> | null;
1305
1346
  }
1306
1347
 
1348
+ /**
1349
+ * OAuth flow types: client-side redirect results and OAuth token / RFC 7662
1350
+ * introspection payloads.
1351
+ *
1352
+ * Extracted from `types/index.ts` to keep that barrel under the 800-line hard
1353
+ * limit (`scripts/check-file-size.sh`). Re-exported from `types/index.ts`, so
1354
+ * the public API is unchanged — import from `@feelflow/ffid-sdk` as before.
1355
+ */
1356
+
1357
+ /**
1358
+ * Discriminant for redirect failures that callers need to handle
1359
+ * programmatically (vs. logging the human-readable `error` string).
1360
+ *
1361
+ * - `'redirect_loop_detected'`: `redirectToAuthorize()` detected that the
1362
+ * same authorize URL (keyed by `baseUrl + client_id + organization_id`)
1363
+ * has been fired **3 times within 60 seconds**. Caller should surface a
1364
+ * manual "再度ログインする" UI rather than retry automatically (#2406 / #2411).
1365
+ *
1366
+ * - `'state_persistence_failed'`: `redirectToAuthorize()` could not persist
1367
+ * the OAuth CSRF `state` (storage unavailable / quota). Redirect is aborted
1368
+ * fail-closed (#4136) — callers should surface a manual retry UI.
1369
+ *
1370
+ * Other failure paths (SSR environment, PKCE generation failure, empty
1371
+ * organizationId) currently return `error` without a `code`. Treat this union
1372
+ * as forward-extensible and do NOT exhaustively `switch` over it without a
1373
+ * `default` branch for consumer code that must stay compatible across SDK
1374
+ * upgrades.
1375
+ */
1376
+ type FFIDRedirectErrorCode = 'redirect_loop_detected' | 'state_persistence_failed';
1377
+ /**
1378
+ * Result of a redirect operation (redirectToLogin / redirectToAuthorize / redirectToLogout)
1379
+ *
1380
+ * Structured return type so callers can inspect failure reasons
1381
+ * instead of receiving a bare `false`. When `code` is set, branch on it
1382
+ * for programmatic handling; otherwise log `error` for humans.
1383
+ *
1384
+ * `loopDetectionDisabled` (#4119): set to `true` when sessionStorage is
1385
+ * unavailable (iOS WebKit private mode / eviction) so redirect-loop detection
1386
+ * ran **fail-open** — the redirect fired, but repeated-redirect protection is
1387
+ * disabled for this browser session. Callers that need loop protection on
1388
+ * such browsers should add their own max-retry guard.
1389
+ */
1390
+ type FFIDRedirectResult = {
1391
+ success: true;
1392
+ loopDetectionDisabled?: boolean;
1393
+ } | {
1394
+ success: false;
1395
+ error: string;
1396
+ code?: FFIDRedirectErrorCode;
1397
+ };
1398
+
1307
1399
  /**
1308
1400
  * FFID SDK Type Definitions
1309
1401
  *
@@ -1447,6 +1539,19 @@ interface FFIDOAuthUserInfoSubscription {
1447
1539
  * round-trip.
1448
1540
  */
1449
1541
  currentPeriodEnd: string | null;
1542
+ /**
1543
+ * Per-service entitlement codes (e.g. `praxis.pack.<slug>.access`) granting
1544
+ * access to one-time / single-pack purchases not encoded in the plan tier
1545
+ * (#4333, SDK 5.25.0). Read this list to gate per-pack access; an empty
1546
+ * array means "no pack entitlements" (the normal case for Pro / Team tiers
1547
+ * that unlock packs via `planCode`).
1548
+ *
1549
+ * The `normalizeUserinfo` boundary defaults this to `[]` when the server
1550
+ * omits it (older FFID pre-#4333) or when the introspect path uses a service
1551
+ * API key without `subscription:read`, so consumers always see a plain
1552
+ * `readonly string[]` and never branch on an "unknown" sentinel.
1553
+ */
1554
+ entitlements: readonly string[];
1450
1555
  }
1451
1556
  /**
1452
1557
  * Unified user-identity shape returned by `getSession()` (→ `/oauth/userinfo`)
@@ -1608,6 +1713,29 @@ interface FFIDConfig {
1608
1713
  * @default undefined (no timeout, uses fetch default)
1609
1714
  */
1610
1715
  timeout?: number | undefined;
1716
+ /**
1717
+ * Token mode only: inject a shared {@link TokenStore} instance so the SDK and
1718
+ * the host app read/write the *same* token source (#4318).
1719
+ *
1720
+ * By default the client creates its own store internally
1721
+ * (`createTokenStore()` in `token` mode, `createTokenStore('memory')`
1722
+ * otherwise). That is fine when `localStorage` is available, because a
1723
+ * separately-created `localStorage` store still points at the same
1724
+ * `ffid_tokens` key. But when `localStorage` is unavailable (e.g. Safari
1725
+ * Private Mode) the SDK falls back to an *in-memory* store, and a store the
1726
+ * host app creates independently is a **different** object — so the app would
1727
+ * see the user as signed-out even though the SDK holds valid tokens.
1728
+ *
1729
+ * Passing a single instance here (typically `createTokenStore()` created once
1730
+ * by the host) guarantees both sides share one source of truth regardless of
1731
+ * storage backend. Prefer reading the token via `useFFID().getAccessToken()`
1732
+ * so refresh/logout stay reflected without the host polling the store.
1733
+ *
1734
+ * Ignored in `cookie` and `service-key` modes (neither exposes a browser
1735
+ * access-token store). When omitted, behavior is unchanged from previous
1736
+ * versions.
1737
+ */
1738
+ tokenStore?: TokenStore | undefined;
1611
1739
  }
1612
1740
  /**
1613
1741
  * OIDC `prompt` values the SDK forwards to FFID `/oauth/authorize` (#4027).
@@ -1725,47 +1853,6 @@ interface FFIDAnalyticsConfig {
1725
1853
  */
1726
1854
  isActive: boolean;
1727
1855
  }
1728
- /**
1729
- * Discriminant for redirect failures that callers need to handle
1730
- * programmatically (vs. logging the human-readable `error` string).
1731
- *
1732
- * - `'redirect_loop_detected'`: `redirectToAuthorize()` detected that the
1733
- * same authorize URL (keyed by `baseUrl + client_id + organization_id`)
1734
- * has been fired **3 times within 60 seconds**. Caller should surface a
1735
- * manual "再度ログインする" UI rather than retry automatically (#2406 / #2411).
1736
- *
1737
- * - `'state_persistence_failed'`: `redirectToAuthorize()` could not persist
1738
- * the OAuth CSRF `state` (storage unavailable / quota). Redirect is aborted
1739
- * fail-closed (#4136) — callers should surface a manual retry UI.
1740
- *
1741
- * Other failure paths (SSR environment, PKCE generation failure, empty
1742
- * organizationId) currently return `error` without a `code`. Treat this union
1743
- * as forward-extensible and do NOT exhaustively `switch` over it without a
1744
- * `default` branch for consumer code that must stay compatible across SDK
1745
- * upgrades.
1746
- */
1747
- type FFIDRedirectErrorCode = 'redirect_loop_detected' | 'state_persistence_failed';
1748
- /**
1749
- * Result of a redirect operation (redirectToLogin / redirectToAuthorize / redirectToLogout)
1750
- *
1751
- * Structured return type so callers can inspect failure reasons
1752
- * instead of receiving a bare `false`. When `code` is set, branch on it
1753
- * for programmatic handling; otherwise log `error` for humans.
1754
- *
1755
- * `loopDetectionDisabled` (#4119): set to `true` when sessionStorage is
1756
- * unavailable (iOS WebKit private mode / eviction) so redirect-loop detection
1757
- * ran **fail-open** — the redirect fired, but repeated-redirect protection is
1758
- * disabled for this browser session. Callers that need loop protection on
1759
- * such browsers should add their own max-retry guard.
1760
- */
1761
- type FFIDRedirectResult = {
1762
- success: true;
1763
- loopDetectionDisabled?: boolean;
1764
- } | {
1765
- success: false;
1766
- error: string;
1767
- code?: FFIDRedirectErrorCode;
1768
- };
1769
1856
 
1770
1857
  /** OTP / magic link methods - sendOtp / verifyOtp */
1771
1858
 
@@ -1850,47 +1937,6 @@ interface FFIDGetLoginHistoryParams {
1850
1937
  limit?: number;
1851
1938
  }
1852
1939
 
1853
- /**
1854
- * Token Store
1855
- *
1856
- * Manages OAuth 2.0 tokens (access + refresh) with dual-storage support.
1857
- * Falls back to in-memory storage when localStorage is unavailable
1858
- * (e.g., Safari private browsing mode).
1859
- */
1860
- /**
1861
- * Token data stored by the token store
1862
- */
1863
- interface TokenData {
1864
- /** OAuth 2.0 access token */
1865
- accessToken: string;
1866
- /** OAuth 2.0 refresh token */
1867
- refreshToken: string;
1868
- /** Expiration timestamp in milliseconds (Unix epoch) */
1869
- expiresAt: number;
1870
- }
1871
- /**
1872
- * Token store interface for managing OAuth tokens
1873
- */
1874
- interface TokenStore {
1875
- /** Get stored tokens (null if not stored) */
1876
- getTokens(): TokenData | null;
1877
- /** Store new tokens */
1878
- setTokens(tokens: TokenData): void;
1879
- /** Clear all stored tokens */
1880
- clearTokens(): void;
1881
- /** Check if access token is expired (with 30s buffer) */
1882
- isAccessTokenExpired(): boolean;
1883
- }
1884
- /**
1885
- * Create a token store with the specified storage type.
1886
- *
1887
- * When storageType is 'localStorage' (default in browser), falls back
1888
- * to memory if localStorage is not available (e.g., Safari private mode).
1889
- *
1890
- * @param storageType - 'localStorage' (default) or 'memory'
1891
- */
1892
- declare function createTokenStore(storageType?: 'localStorage' | 'memory'): TokenStore;
1893
-
1894
1940
  /** OAuth token operations - exchangeCodeForTokens / refreshAccessToken / signOutToken */
1895
1941
 
1896
1942
  /**
@@ -2065,6 +2111,11 @@ declare function createFFIDClient(config: FFIDConfig): {
2065
2111
  };
2066
2112
  /** Token store (token mode only) */
2067
2113
  tokenStore: TokenStore;
2114
+ /**
2115
+ * Return the currently-valid access token, or null (token mode only; #4318).
2116
+ * Live-reads the store and applies the 30s expiry buffer.
2117
+ */
2118
+ getAccessToken: () => string | null;
2068
2119
  /** Resolved auth mode */
2069
2120
  authMode: FFIDAuthMode;
2070
2121
  /** Resolved logger instance */
@@ -270,6 +270,47 @@ interface FFIDServiceAccessDecision {
270
270
  error?: FFIDServiceAccessError;
271
271
  }
272
272
 
273
+ /**
274
+ * Token Store
275
+ *
276
+ * Manages OAuth 2.0 tokens (access + refresh) with dual-storage support.
277
+ * Falls back to in-memory storage when localStorage is unavailable
278
+ * (e.g., Safari private browsing mode).
279
+ */
280
+ /**
281
+ * Token data stored by the token store
282
+ */
283
+ interface TokenData {
284
+ /** OAuth 2.0 access token */
285
+ accessToken: string;
286
+ /** OAuth 2.0 refresh token */
287
+ refreshToken: string;
288
+ /** Expiration timestamp in milliseconds (Unix epoch) */
289
+ expiresAt: number;
290
+ }
291
+ /**
292
+ * Token store interface for managing OAuth tokens
293
+ */
294
+ interface TokenStore {
295
+ /** Get stored tokens (null if not stored) */
296
+ getTokens(): TokenData | null;
297
+ /** Store new tokens */
298
+ setTokens(tokens: TokenData): void;
299
+ /** Clear all stored tokens */
300
+ clearTokens(): void;
301
+ /** Check if access token is expired (with 30s buffer) */
302
+ isAccessTokenExpired(): boolean;
303
+ }
304
+ /**
305
+ * Create a token store with the specified storage type.
306
+ *
307
+ * When storageType is 'localStorage' (default in browser), falls back
308
+ * to memory if localStorage is not available (e.g., Safari private mode).
309
+ *
310
+ * @param storageType - 'localStorage' (default) or 'memory'
311
+ */
312
+ declare function createTokenStore(storageType?: 'localStorage' | 'memory'): TokenStore;
313
+
273
314
  /**
274
315
  * Billing checkout / portal session types.
275
316
  *
@@ -1304,6 +1345,57 @@ interface FFIDUpdateUserProfileRequest {
1304
1345
  preferences?: Record<string, unknown> | null;
1305
1346
  }
1306
1347
 
1348
+ /**
1349
+ * OAuth flow types: client-side redirect results and OAuth token / RFC 7662
1350
+ * introspection payloads.
1351
+ *
1352
+ * Extracted from `types/index.ts` to keep that barrel under the 800-line hard
1353
+ * limit (`scripts/check-file-size.sh`). Re-exported from `types/index.ts`, so
1354
+ * the public API is unchanged — import from `@feelflow/ffid-sdk` as before.
1355
+ */
1356
+
1357
+ /**
1358
+ * Discriminant for redirect failures that callers need to handle
1359
+ * programmatically (vs. logging the human-readable `error` string).
1360
+ *
1361
+ * - `'redirect_loop_detected'`: `redirectToAuthorize()` detected that the
1362
+ * same authorize URL (keyed by `baseUrl + client_id + organization_id`)
1363
+ * has been fired **3 times within 60 seconds**. Caller should surface a
1364
+ * manual "再度ログインする" UI rather than retry automatically (#2406 / #2411).
1365
+ *
1366
+ * - `'state_persistence_failed'`: `redirectToAuthorize()` could not persist
1367
+ * the OAuth CSRF `state` (storage unavailable / quota). Redirect is aborted
1368
+ * fail-closed (#4136) — callers should surface a manual retry UI.
1369
+ *
1370
+ * Other failure paths (SSR environment, PKCE generation failure, empty
1371
+ * organizationId) currently return `error` without a `code`. Treat this union
1372
+ * as forward-extensible and do NOT exhaustively `switch` over it without a
1373
+ * `default` branch for consumer code that must stay compatible across SDK
1374
+ * upgrades.
1375
+ */
1376
+ type FFIDRedirectErrorCode = 'redirect_loop_detected' | 'state_persistence_failed';
1377
+ /**
1378
+ * Result of a redirect operation (redirectToLogin / redirectToAuthorize / redirectToLogout)
1379
+ *
1380
+ * Structured return type so callers can inspect failure reasons
1381
+ * instead of receiving a bare `false`. When `code` is set, branch on it
1382
+ * for programmatic handling; otherwise log `error` for humans.
1383
+ *
1384
+ * `loopDetectionDisabled` (#4119): set to `true` when sessionStorage is
1385
+ * unavailable (iOS WebKit private mode / eviction) so redirect-loop detection
1386
+ * ran **fail-open** — the redirect fired, but repeated-redirect protection is
1387
+ * disabled for this browser session. Callers that need loop protection on
1388
+ * such browsers should add their own max-retry guard.
1389
+ */
1390
+ type FFIDRedirectResult = {
1391
+ success: true;
1392
+ loopDetectionDisabled?: boolean;
1393
+ } | {
1394
+ success: false;
1395
+ error: string;
1396
+ code?: FFIDRedirectErrorCode;
1397
+ };
1398
+
1307
1399
  /**
1308
1400
  * FFID SDK Type Definitions
1309
1401
  *
@@ -1447,6 +1539,19 @@ interface FFIDOAuthUserInfoSubscription {
1447
1539
  * round-trip.
1448
1540
  */
1449
1541
  currentPeriodEnd: string | null;
1542
+ /**
1543
+ * Per-service entitlement codes (e.g. `praxis.pack.<slug>.access`) granting
1544
+ * access to one-time / single-pack purchases not encoded in the plan tier
1545
+ * (#4333, SDK 5.25.0). Read this list to gate per-pack access; an empty
1546
+ * array means "no pack entitlements" (the normal case for Pro / Team tiers
1547
+ * that unlock packs via `planCode`).
1548
+ *
1549
+ * The `normalizeUserinfo` boundary defaults this to `[]` when the server
1550
+ * omits it (older FFID pre-#4333) or when the introspect path uses a service
1551
+ * API key without `subscription:read`, so consumers always see a plain
1552
+ * `readonly string[]` and never branch on an "unknown" sentinel.
1553
+ */
1554
+ entitlements: readonly string[];
1450
1555
  }
1451
1556
  /**
1452
1557
  * Unified user-identity shape returned by `getSession()` (→ `/oauth/userinfo`)
@@ -1608,6 +1713,29 @@ interface FFIDConfig {
1608
1713
  * @default undefined (no timeout, uses fetch default)
1609
1714
  */
1610
1715
  timeout?: number | undefined;
1716
+ /**
1717
+ * Token mode only: inject a shared {@link TokenStore} instance so the SDK and
1718
+ * the host app read/write the *same* token source (#4318).
1719
+ *
1720
+ * By default the client creates its own store internally
1721
+ * (`createTokenStore()` in `token` mode, `createTokenStore('memory')`
1722
+ * otherwise). That is fine when `localStorage` is available, because a
1723
+ * separately-created `localStorage` store still points at the same
1724
+ * `ffid_tokens` key. But when `localStorage` is unavailable (e.g. Safari
1725
+ * Private Mode) the SDK falls back to an *in-memory* store, and a store the
1726
+ * host app creates independently is a **different** object — so the app would
1727
+ * see the user as signed-out even though the SDK holds valid tokens.
1728
+ *
1729
+ * Passing a single instance here (typically `createTokenStore()` created once
1730
+ * by the host) guarantees both sides share one source of truth regardless of
1731
+ * storage backend. Prefer reading the token via `useFFID().getAccessToken()`
1732
+ * so refresh/logout stay reflected without the host polling the store.
1733
+ *
1734
+ * Ignored in `cookie` and `service-key` modes (neither exposes a browser
1735
+ * access-token store). When omitted, behavior is unchanged from previous
1736
+ * versions.
1737
+ */
1738
+ tokenStore?: TokenStore | undefined;
1611
1739
  }
1612
1740
  /**
1613
1741
  * OIDC `prompt` values the SDK forwards to FFID `/oauth/authorize` (#4027).
@@ -1725,47 +1853,6 @@ interface FFIDAnalyticsConfig {
1725
1853
  */
1726
1854
  isActive: boolean;
1727
1855
  }
1728
- /**
1729
- * Discriminant for redirect failures that callers need to handle
1730
- * programmatically (vs. logging the human-readable `error` string).
1731
- *
1732
- * - `'redirect_loop_detected'`: `redirectToAuthorize()` detected that the
1733
- * same authorize URL (keyed by `baseUrl + client_id + organization_id`)
1734
- * has been fired **3 times within 60 seconds**. Caller should surface a
1735
- * manual "再度ログインする" UI rather than retry automatically (#2406 / #2411).
1736
- *
1737
- * - `'state_persistence_failed'`: `redirectToAuthorize()` could not persist
1738
- * the OAuth CSRF `state` (storage unavailable / quota). Redirect is aborted
1739
- * fail-closed (#4136) — callers should surface a manual retry UI.
1740
- *
1741
- * Other failure paths (SSR environment, PKCE generation failure, empty
1742
- * organizationId) currently return `error` without a `code`. Treat this union
1743
- * as forward-extensible and do NOT exhaustively `switch` over it without a
1744
- * `default` branch for consumer code that must stay compatible across SDK
1745
- * upgrades.
1746
- */
1747
- type FFIDRedirectErrorCode = 'redirect_loop_detected' | 'state_persistence_failed';
1748
- /**
1749
- * Result of a redirect operation (redirectToLogin / redirectToAuthorize / redirectToLogout)
1750
- *
1751
- * Structured return type so callers can inspect failure reasons
1752
- * instead of receiving a bare `false`. When `code` is set, branch on it
1753
- * for programmatic handling; otherwise log `error` for humans.
1754
- *
1755
- * `loopDetectionDisabled` (#4119): set to `true` when sessionStorage is
1756
- * unavailable (iOS WebKit private mode / eviction) so redirect-loop detection
1757
- * ran **fail-open** — the redirect fired, but repeated-redirect protection is
1758
- * disabled for this browser session. Callers that need loop protection on
1759
- * such browsers should add their own max-retry guard.
1760
- */
1761
- type FFIDRedirectResult = {
1762
- success: true;
1763
- loopDetectionDisabled?: boolean;
1764
- } | {
1765
- success: false;
1766
- error: string;
1767
- code?: FFIDRedirectErrorCode;
1768
- };
1769
1856
 
1770
1857
  /** OTP / magic link methods - sendOtp / verifyOtp */
1771
1858
 
@@ -1850,47 +1937,6 @@ interface FFIDGetLoginHistoryParams {
1850
1937
  limit?: number;
1851
1938
  }
1852
1939
 
1853
- /**
1854
- * Token Store
1855
- *
1856
- * Manages OAuth 2.0 tokens (access + refresh) with dual-storage support.
1857
- * Falls back to in-memory storage when localStorage is unavailable
1858
- * (e.g., Safari private browsing mode).
1859
- */
1860
- /**
1861
- * Token data stored by the token store
1862
- */
1863
- interface TokenData {
1864
- /** OAuth 2.0 access token */
1865
- accessToken: string;
1866
- /** OAuth 2.0 refresh token */
1867
- refreshToken: string;
1868
- /** Expiration timestamp in milliseconds (Unix epoch) */
1869
- expiresAt: number;
1870
- }
1871
- /**
1872
- * Token store interface for managing OAuth tokens
1873
- */
1874
- interface TokenStore {
1875
- /** Get stored tokens (null if not stored) */
1876
- getTokens(): TokenData | null;
1877
- /** Store new tokens */
1878
- setTokens(tokens: TokenData): void;
1879
- /** Clear all stored tokens */
1880
- clearTokens(): void;
1881
- /** Check if access token is expired (with 30s buffer) */
1882
- isAccessTokenExpired(): boolean;
1883
- }
1884
- /**
1885
- * Create a token store with the specified storage type.
1886
- *
1887
- * When storageType is 'localStorage' (default in browser), falls back
1888
- * to memory if localStorage is not available (e.g., Safari private mode).
1889
- *
1890
- * @param storageType - 'localStorage' (default) or 'memory'
1891
- */
1892
- declare function createTokenStore(storageType?: 'localStorage' | 'memory'): TokenStore;
1893
-
1894
1940
  /** OAuth token operations - exchangeCodeForTokens / refreshAccessToken / signOutToken */
1895
1941
 
1896
1942
  /**
@@ -2065,6 +2111,11 @@ declare function createFFIDClient(config: FFIDConfig): {
2065
2111
  };
2066
2112
  /** Token store (token mode only) */
2067
2113
  tokenStore: TokenStore;
2114
+ /**
2115
+ * Return the currently-valid access token, or null (token mode only; #4318).
2116
+ * Live-reads the store and applies the 30s expiry buffer.
2117
+ */
2118
+ getAccessToken: () => string | null;
2068
2119
  /** Resolved auth mode */
2069
2120
  authMode: FFIDAuthMode;
2070
2121
  /** Resolved logger instance */