@feelflow/ffid-sdk 5.24.2 → 5.24.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.
@@ -1228,8 +1228,35 @@ function createNonContractMethods(deps) {
1228
1228
  };
1229
1229
  }
1230
1230
 
1231
+ // src/shared/token-response.ts
1232
+ function invalidTokenFields(tokenResponse) {
1233
+ const invalid = [];
1234
+ if (typeof tokenResponse.access_token !== "string" || tokenResponse.access_token.length === 0) {
1235
+ invalid.push("access_token");
1236
+ }
1237
+ if (typeof tokenResponse.refresh_token !== "string" || tokenResponse.refresh_token.length === 0) {
1238
+ invalid.push("refresh_token");
1239
+ }
1240
+ if (typeof tokenResponse.expires_in !== "number" || !Number.isFinite(tokenResponse.expires_in) || tokenResponse.expires_in <= 0) {
1241
+ invalid.push("expires_in");
1242
+ }
1243
+ return invalid;
1244
+ }
1245
+ function hasValidatedTokens(tokenResponse) {
1246
+ return invalidTokenFields(tokenResponse).length === 0;
1247
+ }
1248
+ function validateTokenResponse(tokenResponse) {
1249
+ if (hasValidatedTokens(tokenResponse)) {
1250
+ return { ok: true, tokens: tokenResponse };
1251
+ }
1252
+ return {
1253
+ ok: false,
1254
+ message: `\u30C8\u30FC\u30AF\u30F3\u30EC\u30B9\u30DD\u30F3\u30B9\u306B\u4E0D\u6B63\u306A\u30D5\u30A3\u30FC\u30EB\u30C9\u304C\u3042\u308A\u307E\u3059: ${invalidTokenFields(tokenResponse).join(", ")}`
1255
+ };
1256
+ }
1257
+
1231
1258
  // src/client/version-check.ts
1232
- var SDK_VERSION = "5.24.2";
1259
+ var SDK_VERSION = "5.24.3";
1233
1260
  var SDK_USER_AGENT = `FFID-SDK/${SDK_VERSION} (TypeScript)`;
1234
1261
  var SDK_VERSION_HEADER = "X-FFID-SDK-Version";
1235
1262
  function sdkHeaders() {
@@ -1276,31 +1303,6 @@ npm install @feelflow/ffid-sdk@latest \u3067\u30A2\u30C3\u30D7\u30C7\u30FC\u30C8
1276
1303
  var OAUTH_TOKEN_ENDPOINT = "/api/v1/oauth/token";
1277
1304
  var OAUTH_REVOKE_ENDPOINT = "/api/v1/oauth/revoke";
1278
1305
  var MS_PER_SECOND = 1e3;
1279
- function invalidTokenFields(tokenResponse) {
1280
- const invalid = [];
1281
- if (!tokenResponse.access_token) {
1282
- invalid.push("access_token");
1283
- }
1284
- if (!tokenResponse.refresh_token) {
1285
- invalid.push("refresh_token");
1286
- }
1287
- if (typeof tokenResponse.expires_in !== "number" || !Number.isFinite(tokenResponse.expires_in) || tokenResponse.expires_in <= 0) {
1288
- invalid.push("expires_in");
1289
- }
1290
- return invalid;
1291
- }
1292
- function hasValidatedTokens(tokenResponse) {
1293
- return invalidTokenFields(tokenResponse).length === 0;
1294
- }
1295
- function validateTokenResponse(tokenResponse) {
1296
- if (hasValidatedTokens(tokenResponse)) {
1297
- return { ok: true, tokens: tokenResponse };
1298
- }
1299
- return {
1300
- ok: false,
1301
- message: `\u30C8\u30FC\u30AF\u30F3\u30EC\u30B9\u30DD\u30F3\u30B9\u306B\u4E0D\u6B63\u306A\u30D5\u30A3\u30FC\u30EB\u30C9\u304C\u3042\u308A\u307E\u3059: ${invalidTokenFields(tokenResponse).join(", ")}`
1302
- };
1303
- }
1304
1306
  function createOAuthTokenMethods(deps) {
1305
1307
  const {
1306
1308
  baseUrl,
@@ -3021,10 +3023,16 @@ function createFFIDClient(config) {
3021
3023
  }
3022
3024
  const logger = config.logger ?? (config.debug ? consoleLogger : noopLogger);
3023
3025
  const resolvedRedirectUri = resolveRedirectUri(rawRedirectUri, logger);
3024
- const tokenStore = authMode === "token" ? createTokenStore() : createTokenStore("memory");
3026
+ const tokenStore = authMode === "token" ? config.tokenStore ?? createTokenStore() : createTokenStore("memory");
3025
3027
  function createError(code, message) {
3026
3028
  return { code, message };
3027
3029
  }
3030
+ function getAccessToken() {
3031
+ if (authMode !== "token") return null;
3032
+ if (tokenStore.isAccessTokenExpired()) return null;
3033
+ const accessToken = tokenStore.getTokens()?.accessToken;
3034
+ return accessToken && accessToken.length > 0 ? accessToken : null;
3035
+ }
3028
3036
  function buildFetchOptions(options, authOverride) {
3029
3037
  if (authOverride) {
3030
3038
  return {
@@ -3434,6 +3442,11 @@ function createFFIDClient(config) {
3434
3442
  inquiry,
3435
3443
  /** Token store (token mode only) */
3436
3444
  tokenStore,
3445
+ /**
3446
+ * Return the currently-valid access token, or null (token mode only; #4318).
3447
+ * Live-reads the store and applies the 30s expiry buffer.
3448
+ */
3449
+ getAccessToken,
3437
3450
  /** Resolved auth mode */
3438
3451
  authMode,
3439
3452
  /** Resolved logger instance */
@@ -3558,6 +3571,7 @@ function FFIDProvider({
3558
3571
  authMode,
3559
3572
  clientId,
3560
3573
  timeout,
3574
+ tokenStore,
3561
3575
  cleanCallbackUrl = true
3562
3576
  }) {
3563
3577
  const [user, setUser] = react.useState(null);
@@ -3583,9 +3597,10 @@ function FFIDProvider({
3583
3597
  logger,
3584
3598
  authMode,
3585
3599
  clientId,
3586
- timeout
3600
+ timeout,
3601
+ tokenStore
3587
3602
  }),
3588
- [serviceCode, scope, apiBaseUrl, debug, logger, authMode, clientId, timeout]
3603
+ [serviceCode, scope, apiBaseUrl, debug, logger, authMode, clientId, timeout, tokenStore]
3589
3604
  );
3590
3605
  const refresh = react.useCallback(async () => {
3591
3606
  client.logger.debug("Refreshing session...");
@@ -3799,7 +3814,10 @@ function FFIDProvider({
3799
3814
  login,
3800
3815
  logout,
3801
3816
  switchOrganization,
3802
- refresh
3817
+ refresh,
3818
+ // Stable across renders (client is memoized): live-reads the shared store
3819
+ // so refreshed/cleared tokens are reflected without host polling (#4318).
3820
+ getAccessToken: client.getAccessToken
3803
3821
  }),
3804
3822
  [
3805
3823
  user,
@@ -3811,7 +3829,8 @@ function FFIDProvider({
3811
3829
  login,
3812
3830
  logout,
3813
3831
  switchOrganization,
3814
- refresh
3832
+ refresh,
3833
+ client
3815
3834
  ]
3816
3835
  );
3817
3836
  return /* @__PURE__ */ jsxRuntime.jsx(FFIDClientContext.Provider, { value: client, children: /* @__PURE__ */ jsxRuntime.jsx(FFIDContext.Provider, { value: contextValue, children }) });
@@ -3850,6 +3869,7 @@ function useFFID() {
3850
3869
  switchAccount: client.switchAccount,
3851
3870
  switchOrganization: context.switchOrganization,
3852
3871
  refresh: context.refresh,
3872
+ getAccessToken: context.getAccessToken,
3853
3873
  getLoginUrl: client.getLoginUrl,
3854
3874
  getSignupUrl: client.getSignupUrl,
3855
3875
  getSubscribeUrl: client.getSubscribeUrl,
@@ -1226,8 +1226,35 @@ function createNonContractMethods(deps) {
1226
1226
  };
1227
1227
  }
1228
1228
 
1229
+ // src/shared/token-response.ts
1230
+ function invalidTokenFields(tokenResponse) {
1231
+ const invalid = [];
1232
+ if (typeof tokenResponse.access_token !== "string" || tokenResponse.access_token.length === 0) {
1233
+ invalid.push("access_token");
1234
+ }
1235
+ if (typeof tokenResponse.refresh_token !== "string" || tokenResponse.refresh_token.length === 0) {
1236
+ invalid.push("refresh_token");
1237
+ }
1238
+ if (typeof tokenResponse.expires_in !== "number" || !Number.isFinite(tokenResponse.expires_in) || tokenResponse.expires_in <= 0) {
1239
+ invalid.push("expires_in");
1240
+ }
1241
+ return invalid;
1242
+ }
1243
+ function hasValidatedTokens(tokenResponse) {
1244
+ return invalidTokenFields(tokenResponse).length === 0;
1245
+ }
1246
+ function validateTokenResponse(tokenResponse) {
1247
+ if (hasValidatedTokens(tokenResponse)) {
1248
+ return { ok: true, tokens: tokenResponse };
1249
+ }
1250
+ return {
1251
+ ok: false,
1252
+ message: `\u30C8\u30FC\u30AF\u30F3\u30EC\u30B9\u30DD\u30F3\u30B9\u306B\u4E0D\u6B63\u306A\u30D5\u30A3\u30FC\u30EB\u30C9\u304C\u3042\u308A\u307E\u3059: ${invalidTokenFields(tokenResponse).join(", ")}`
1253
+ };
1254
+ }
1255
+
1229
1256
  // src/client/version-check.ts
1230
- var SDK_VERSION = "5.24.2";
1257
+ var SDK_VERSION = "5.24.3";
1231
1258
  var SDK_USER_AGENT = `FFID-SDK/${SDK_VERSION} (TypeScript)`;
1232
1259
  var SDK_VERSION_HEADER = "X-FFID-SDK-Version";
1233
1260
  function sdkHeaders() {
@@ -1274,31 +1301,6 @@ npm install @feelflow/ffid-sdk@latest \u3067\u30A2\u30C3\u30D7\u30C7\u30FC\u30C8
1274
1301
  var OAUTH_TOKEN_ENDPOINT = "/api/v1/oauth/token";
1275
1302
  var OAUTH_REVOKE_ENDPOINT = "/api/v1/oauth/revoke";
1276
1303
  var MS_PER_SECOND = 1e3;
1277
- function invalidTokenFields(tokenResponse) {
1278
- const invalid = [];
1279
- if (!tokenResponse.access_token) {
1280
- invalid.push("access_token");
1281
- }
1282
- if (!tokenResponse.refresh_token) {
1283
- invalid.push("refresh_token");
1284
- }
1285
- if (typeof tokenResponse.expires_in !== "number" || !Number.isFinite(tokenResponse.expires_in) || tokenResponse.expires_in <= 0) {
1286
- invalid.push("expires_in");
1287
- }
1288
- return invalid;
1289
- }
1290
- function hasValidatedTokens(tokenResponse) {
1291
- return invalidTokenFields(tokenResponse).length === 0;
1292
- }
1293
- function validateTokenResponse(tokenResponse) {
1294
- if (hasValidatedTokens(tokenResponse)) {
1295
- return { ok: true, tokens: tokenResponse };
1296
- }
1297
- return {
1298
- ok: false,
1299
- message: `\u30C8\u30FC\u30AF\u30F3\u30EC\u30B9\u30DD\u30F3\u30B9\u306B\u4E0D\u6B63\u306A\u30D5\u30A3\u30FC\u30EB\u30C9\u304C\u3042\u308A\u307E\u3059: ${invalidTokenFields(tokenResponse).join(", ")}`
1300
- };
1301
- }
1302
1304
  function createOAuthTokenMethods(deps) {
1303
1305
  const {
1304
1306
  baseUrl,
@@ -3019,10 +3021,16 @@ function createFFIDClient(config) {
3019
3021
  }
3020
3022
  const logger = config.logger ?? (config.debug ? consoleLogger : noopLogger);
3021
3023
  const resolvedRedirectUri = resolveRedirectUri(rawRedirectUri, logger);
3022
- const tokenStore = authMode === "token" ? createTokenStore() : createTokenStore("memory");
3024
+ const tokenStore = authMode === "token" ? config.tokenStore ?? createTokenStore() : createTokenStore("memory");
3023
3025
  function createError(code, message) {
3024
3026
  return { code, message };
3025
3027
  }
3028
+ function getAccessToken() {
3029
+ if (authMode !== "token") return null;
3030
+ if (tokenStore.isAccessTokenExpired()) return null;
3031
+ const accessToken = tokenStore.getTokens()?.accessToken;
3032
+ return accessToken && accessToken.length > 0 ? accessToken : null;
3033
+ }
3026
3034
  function buildFetchOptions(options, authOverride) {
3027
3035
  if (authOverride) {
3028
3036
  return {
@@ -3432,6 +3440,11 @@ function createFFIDClient(config) {
3432
3440
  inquiry,
3433
3441
  /** Token store (token mode only) */
3434
3442
  tokenStore,
3443
+ /**
3444
+ * Return the currently-valid access token, or null (token mode only; #4318).
3445
+ * Live-reads the store and applies the 30s expiry buffer.
3446
+ */
3447
+ getAccessToken,
3435
3448
  /** Resolved auth mode */
3436
3449
  authMode,
3437
3450
  /** Resolved logger instance */
@@ -3556,6 +3569,7 @@ function FFIDProvider({
3556
3569
  authMode,
3557
3570
  clientId,
3558
3571
  timeout,
3572
+ tokenStore,
3559
3573
  cleanCallbackUrl = true
3560
3574
  }) {
3561
3575
  const [user, setUser] = useState(null);
@@ -3581,9 +3595,10 @@ function FFIDProvider({
3581
3595
  logger,
3582
3596
  authMode,
3583
3597
  clientId,
3584
- timeout
3598
+ timeout,
3599
+ tokenStore
3585
3600
  }),
3586
- [serviceCode, scope, apiBaseUrl, debug, logger, authMode, clientId, timeout]
3601
+ [serviceCode, scope, apiBaseUrl, debug, logger, authMode, clientId, timeout, tokenStore]
3587
3602
  );
3588
3603
  const refresh = useCallback(async () => {
3589
3604
  client.logger.debug("Refreshing session...");
@@ -3797,7 +3812,10 @@ function FFIDProvider({
3797
3812
  login,
3798
3813
  logout,
3799
3814
  switchOrganization,
3800
- refresh
3815
+ refresh,
3816
+ // Stable across renders (client is memoized): live-reads the shared store
3817
+ // so refreshed/cleared tokens are reflected without host polling (#4318).
3818
+ getAccessToken: client.getAccessToken
3801
3819
  }),
3802
3820
  [
3803
3821
  user,
@@ -3809,7 +3827,8 @@ function FFIDProvider({
3809
3827
  login,
3810
3828
  logout,
3811
3829
  switchOrganization,
3812
- refresh
3830
+ refresh,
3831
+ client
3813
3832
  ]
3814
3833
  );
3815
3834
  return /* @__PURE__ */ jsx(FFIDClientContext.Provider, { value: client, children: /* @__PURE__ */ jsx(FFIDContext.Provider, { value: contextValue, children }) });
@@ -3848,6 +3867,7 @@ function useFFID() {
3848
3867
  switchAccount: client.switchAccount,
3849
3868
  switchOrganization: context.switchOrganization,
3850
3869
  refresh: context.refresh,
3870
+ getAccessToken: context.getAccessToken,
3851
3871
  getLoginUrl: client.getLoginUrl,
3852
3872
  getSignupUrl: client.getSignupUrl,
3853
3873
  getSubscribeUrl: client.getSubscribeUrl,
@@ -1,34 +1,34 @@
1
1
  'use strict';
2
2
 
3
- var chunkQMWUOVZK_cjs = require('../chunk-QMWUOVZK.cjs');
3
+ var chunkAMVL7J2G_cjs = require('../chunk-AMVL7J2G.cjs');
4
4
 
5
5
 
6
6
 
7
7
  Object.defineProperty(exports, "FFIDAnnouncementBadge", {
8
8
  enumerable: true,
9
- get: function () { return chunkQMWUOVZK_cjs.FFIDAnnouncementBadge; }
9
+ get: function () { return chunkAMVL7J2G_cjs.FFIDAnnouncementBadge; }
10
10
  });
11
11
  Object.defineProperty(exports, "FFIDAnnouncementList", {
12
12
  enumerable: true,
13
- get: function () { return chunkQMWUOVZK_cjs.FFIDAnnouncementList; }
13
+ get: function () { return chunkAMVL7J2G_cjs.FFIDAnnouncementList; }
14
14
  });
15
15
  Object.defineProperty(exports, "FFIDInquiryForm", {
16
16
  enumerable: true,
17
- get: function () { return chunkQMWUOVZK_cjs.FFIDInquiryForm; }
17
+ get: function () { return chunkAMVL7J2G_cjs.FFIDInquiryForm; }
18
18
  });
19
19
  Object.defineProperty(exports, "FFIDLoginButton", {
20
20
  enumerable: true,
21
- get: function () { return chunkQMWUOVZK_cjs.FFIDLoginButton; }
21
+ get: function () { return chunkAMVL7J2G_cjs.FFIDLoginButton; }
22
22
  });
23
23
  Object.defineProperty(exports, "FFIDOrganizationSwitcher", {
24
24
  enumerable: true,
25
- get: function () { return chunkQMWUOVZK_cjs.FFIDOrganizationSwitcher; }
25
+ get: function () { return chunkAMVL7J2G_cjs.FFIDOrganizationSwitcher; }
26
26
  });
27
27
  Object.defineProperty(exports, "FFIDSubscriptionBadge", {
28
28
  enumerable: true,
29
- get: function () { return chunkQMWUOVZK_cjs.FFIDSubscriptionBadge; }
29
+ get: function () { return chunkAMVL7J2G_cjs.FFIDSubscriptionBadge; }
30
30
  });
31
31
  Object.defineProperty(exports, "FFIDUserMenu", {
32
32
  enumerable: true,
33
- get: function () { return chunkQMWUOVZK_cjs.FFIDUserMenu; }
33
+ get: function () { return chunkAMVL7J2G_cjs.FFIDUserMenu; }
34
34
  });
@@ -1,3 +1,3 @@
1
- export { H as FFIDAnnouncementBadge, ai as FFIDAnnouncementBadgeClassNames, aj as FFIDAnnouncementBadgeProps, I as FFIDAnnouncementList, ak as FFIDAnnouncementListClassNames, al as FFIDAnnouncementListProps, S as FFIDInquiryForm, T as FFIDInquiryFormCategoryItem, U as FFIDInquiryFormClassNames, V as FFIDInquiryFormLegalLayout, W as FFIDInquiryFormOrganization, X as FFIDInquiryFormPlaceholderContext, Y as FFIDInquiryFormPrefill, Z as FFIDInquiryFormProps, _ as FFIDInquiryFormSubmitData, $ as FFIDInquiryFormSubmitResult, a1 as FFIDLoginButton, am as FFIDLoginButtonProps, a4 as FFIDOrganizationSwitcher, an as FFIDOrganizationSwitcherClassNames, ao as FFIDOrganizationSwitcherProps, a9 as FFIDSubscriptionBadge, ap as FFIDSubscriptionBadgeClassNames, aq as FFIDSubscriptionBadgeProps, ab as FFIDUserMenu, ar as FFIDUserMenuClassNames, as as FFIDUserMenuProps } from '../index-GqflEaKi.cjs';
1
+ export { H as FFIDAnnouncementBadge, al as FFIDAnnouncementBadgeClassNames, am as FFIDAnnouncementBadgeProps, I as FFIDAnnouncementList, an as FFIDAnnouncementListClassNames, ao as FFIDAnnouncementListProps, S as FFIDInquiryForm, U as FFIDInquiryFormCategoryItem, V as FFIDInquiryFormClassNames, W as FFIDInquiryFormLegalLayout, X as FFIDInquiryFormOrganization, Y as FFIDInquiryFormPlaceholderContext, Z as FFIDInquiryFormPrefill, _ as FFIDInquiryFormProps, $ as FFIDInquiryFormSubmitData, a0 as FFIDInquiryFormSubmitResult, a2 as FFIDLoginButton, ap as FFIDLoginButtonProps, a5 as FFIDOrganizationSwitcher, aq as FFIDOrganizationSwitcherClassNames, ar as FFIDOrganizationSwitcherProps, aa as FFIDSubscriptionBadge, as as FFIDSubscriptionBadgeClassNames, at as FFIDSubscriptionBadgeProps, ac as FFIDUserMenu, au as FFIDUserMenuClassNames, av as FFIDUserMenuProps } from '../index-BRMn6xT0.cjs';
2
2
  import 'react/jsx-runtime';
3
3
  import 'react';
@@ -1,3 +1,3 @@
1
- export { H as FFIDAnnouncementBadge, ai as FFIDAnnouncementBadgeClassNames, aj as FFIDAnnouncementBadgeProps, I as FFIDAnnouncementList, ak as FFIDAnnouncementListClassNames, al as FFIDAnnouncementListProps, S as FFIDInquiryForm, T as FFIDInquiryFormCategoryItem, U as FFIDInquiryFormClassNames, V as FFIDInquiryFormLegalLayout, W as FFIDInquiryFormOrganization, X as FFIDInquiryFormPlaceholderContext, Y as FFIDInquiryFormPrefill, Z as FFIDInquiryFormProps, _ as FFIDInquiryFormSubmitData, $ as FFIDInquiryFormSubmitResult, a1 as FFIDLoginButton, am as FFIDLoginButtonProps, a4 as FFIDOrganizationSwitcher, an as FFIDOrganizationSwitcherClassNames, ao as FFIDOrganizationSwitcherProps, a9 as FFIDSubscriptionBadge, ap as FFIDSubscriptionBadgeClassNames, aq as FFIDSubscriptionBadgeProps, ab as FFIDUserMenu, ar as FFIDUserMenuClassNames, as as FFIDUserMenuProps } from '../index-GqflEaKi.js';
1
+ export { H as FFIDAnnouncementBadge, al as FFIDAnnouncementBadgeClassNames, am as FFIDAnnouncementBadgeProps, I as FFIDAnnouncementList, an as FFIDAnnouncementListClassNames, ao as FFIDAnnouncementListProps, S as FFIDInquiryForm, U as FFIDInquiryFormCategoryItem, V as FFIDInquiryFormClassNames, W as FFIDInquiryFormLegalLayout, X as FFIDInquiryFormOrganization, Y as FFIDInquiryFormPlaceholderContext, Z as FFIDInquiryFormPrefill, _ as FFIDInquiryFormProps, $ as FFIDInquiryFormSubmitData, a0 as FFIDInquiryFormSubmitResult, a2 as FFIDLoginButton, ap as FFIDLoginButtonProps, a5 as FFIDOrganizationSwitcher, aq as FFIDOrganizationSwitcherClassNames, ar as FFIDOrganizationSwitcherProps, aa as FFIDSubscriptionBadge, as as FFIDSubscriptionBadgeClassNames, at as FFIDSubscriptionBadgeProps, ac as FFIDUserMenu, au as FFIDUserMenuClassNames, av as FFIDUserMenuProps } from '../index-BRMn6xT0.js';
2
2
  import 'react/jsx-runtime';
3
3
  import 'react';
@@ -1 +1 @@
1
- export { FFIDAnnouncementBadge, FFIDAnnouncementList, FFIDInquiryForm, FFIDLoginButton, FFIDOrganizationSwitcher, FFIDSubscriptionBadge, FFIDUserMenu } from '../chunk-Q7ZEF3EN.js';
1
+ export { FFIDAnnouncementBadge, FFIDAnnouncementList, FFIDInquiryForm, FFIDLoginButton, FFIDOrganizationSwitcher, FFIDSubscriptionBadge, FFIDUserMenu } from '../chunk-O3HGRALD.js';
@@ -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
  *
@@ -1608,6 +1649,29 @@ interface FFIDConfig {
1608
1649
  * @default undefined (no timeout, uses fetch default)
1609
1650
  */
1610
1651
  timeout?: number | undefined;
1652
+ /**
1653
+ * Token mode only: inject a shared {@link TokenStore} instance so the SDK and
1654
+ * the host app read/write the *same* token source (#4318).
1655
+ *
1656
+ * By default the client creates its own store internally
1657
+ * (`createTokenStore()` in `token` mode, `createTokenStore('memory')`
1658
+ * otherwise). That is fine when `localStorage` is available, because a
1659
+ * separately-created `localStorage` store still points at the same
1660
+ * `ffid_tokens` key. But when `localStorage` is unavailable (e.g. Safari
1661
+ * Private Mode) the SDK falls back to an *in-memory* store, and a store the
1662
+ * host app creates independently is a **different** object — so the app would
1663
+ * see the user as signed-out even though the SDK holds valid tokens.
1664
+ *
1665
+ * Passing a single instance here (typically `createTokenStore()` created once
1666
+ * by the host) guarantees both sides share one source of truth regardless of
1667
+ * storage backend. Prefer reading the token via `useFFID().getAccessToken()`
1668
+ * so refresh/logout stay reflected without the host polling the store.
1669
+ *
1670
+ * Ignored in `cookie` and `service-key` modes (neither exposes a browser
1671
+ * access-token store). When omitted, behavior is unchanged from previous
1672
+ * versions.
1673
+ */
1674
+ tokenStore?: TokenStore | undefined;
1611
1675
  }
1612
1676
  /**
1613
1677
  * OIDC `prompt` values the SDK forwards to FFID `/oauth/authorize` (#4027).
@@ -1850,47 +1914,6 @@ interface FFIDGetLoginHistoryParams {
1850
1914
  limit?: number;
1851
1915
  }
1852
1916
 
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
1917
  /** OAuth token operations - exchangeCodeForTokens / refreshAccessToken / signOutToken */
1895
1918
 
1896
1919
  /**
@@ -2065,6 +2088,11 @@ declare function createFFIDClient(config: FFIDConfig): {
2065
2088
  };
2066
2089
  /** Token store (token mode only) */
2067
2090
  tokenStore: TokenStore;
2091
+ /**
2092
+ * Return the currently-valid access token, or null (token mode only; #4318).
2093
+ * Live-reads the store and applies the 30s expiry buffer.
2094
+ */
2095
+ getAccessToken: () => string | null;
2068
2096
  /** Resolved auth mode */
2069
2097
  authMode: FFIDAuthMode;
2070
2098
  /** 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
  *
@@ -1608,6 +1649,29 @@ interface FFIDConfig {
1608
1649
  * @default undefined (no timeout, uses fetch default)
1609
1650
  */
1610
1651
  timeout?: number | undefined;
1652
+ /**
1653
+ * Token mode only: inject a shared {@link TokenStore} instance so the SDK and
1654
+ * the host app read/write the *same* token source (#4318).
1655
+ *
1656
+ * By default the client creates its own store internally
1657
+ * (`createTokenStore()` in `token` mode, `createTokenStore('memory')`
1658
+ * otherwise). That is fine when `localStorage` is available, because a
1659
+ * separately-created `localStorage` store still points at the same
1660
+ * `ffid_tokens` key. But when `localStorage` is unavailable (e.g. Safari
1661
+ * Private Mode) the SDK falls back to an *in-memory* store, and a store the
1662
+ * host app creates independently is a **different** object — so the app would
1663
+ * see the user as signed-out even though the SDK holds valid tokens.
1664
+ *
1665
+ * Passing a single instance here (typically `createTokenStore()` created once
1666
+ * by the host) guarantees both sides share one source of truth regardless of
1667
+ * storage backend. Prefer reading the token via `useFFID().getAccessToken()`
1668
+ * so refresh/logout stay reflected without the host polling the store.
1669
+ *
1670
+ * Ignored in `cookie` and `service-key` modes (neither exposes a browser
1671
+ * access-token store). When omitted, behavior is unchanged from previous
1672
+ * versions.
1673
+ */
1674
+ tokenStore?: TokenStore | undefined;
1611
1675
  }
1612
1676
  /**
1613
1677
  * OIDC `prompt` values the SDK forwards to FFID `/oauth/authorize` (#4027).
@@ -1850,47 +1914,6 @@ interface FFIDGetLoginHistoryParams {
1850
1914
  limit?: number;
1851
1915
  }
1852
1916
 
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
1917
  /** OAuth token operations - exchangeCodeForTokens / refreshAccessToken / signOutToken */
1895
1918
 
1896
1919
  /**
@@ -2065,6 +2088,11 @@ declare function createFFIDClient(config: FFIDConfig): {
2065
2088
  };
2066
2089
  /** Token store (token mode only) */
2067
2090
  tokenStore: TokenStore;
2091
+ /**
2092
+ * Return the currently-valid access token, or null (token mode only; #4318).
2093
+ * Live-reads the store and applies the 30s expiry buffer.
2094
+ */
2095
+ getAccessToken: () => string | null;
2068
2096
  /** Resolved auth mode */
2069
2097
  authMode: FFIDAuthMode;
2070
2098
  /** Resolved logger instance */