@feelflow/ffid-sdk 5.10.0 → 5.13.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.
@@ -124,6 +124,9 @@ function normalizeUserinfo(raw) {
124
124
  return {
125
125
  sub: raw.sub,
126
126
  email: raw.email,
127
+ // #3791: undefined → undefined を保つ(false に丸めない)。古い FFID は wire に
128
+ // 含めないため、consumer は undefined を「未確認」ではなく「不明」として扱える。
129
+ emailVerified: raw.email_verified,
127
130
  name: raw.name,
128
131
  picture: raw.picture,
129
132
  companyName: raw.company_name ?? null,
@@ -379,6 +382,9 @@ function createVerifyAccessToken(deps) {
379
382
  const base = {
380
383
  sub: introspectResponse.sub,
381
384
  email: introspectResponse.email ?? null,
385
+ // #3791: preserve undefined (do NOT coerce to false) so introspect-strategy
386
+ // callers can distinguish "verified" / "not verified" / "older server".
387
+ email_verified: introspectResponse.email_verified,
382
388
  name: introspectResponse.name ?? null,
383
389
  picture: introspectResponse.picture ?? null,
384
390
  company_name: introspectResponse.company_name ?? null,
@@ -436,6 +442,8 @@ function createVerifyAccessToken(deps) {
436
442
  // src/client/billing-methods.ts
437
443
  var BILLING_CHECKOUT_ENDPOINT = "/api/v1/billing/checkout";
438
444
  var BILLING_PORTAL_ENDPOINT = "/api/v1/billing/portal";
445
+ var EXT_INVOICES_ENDPOINT = "/api/v1/billing/ext/invoices";
446
+ var EXT_RETRY_PAYMENT_ENDPOINT = "/api/v1/billing/ext/retry-payment";
439
447
  function createBillingMethods(deps) {
440
448
  const { fetchWithAuth, createError } = deps;
441
449
  async function createCheckoutSession(params) {
@@ -477,7 +485,31 @@ function createBillingMethods(deps) {
477
485
  `${BILLING_PORTAL_ENDPOINT}?${query.toString()}`
478
486
  );
479
487
  }
480
- return { createCheckoutSession, createPortalSession };
488
+ async function listInvoices(params) {
489
+ if (!params.organizationId) {
490
+ return { error: createError("VALIDATION_ERROR", "organizationId \u306F\u5FC5\u9808\u3067\u3059") };
491
+ }
492
+ const query = new URLSearchParams({ organizationId: params.organizationId });
493
+ return fetchWithAuth(`${EXT_INVOICES_ENDPOINT}?${query.toString()}`);
494
+ }
495
+ async function getInvoice(params) {
496
+ if (!params.invoiceId) {
497
+ return { error: createError("VALIDATION_ERROR", "invoiceId \u306F\u5FC5\u9808\u3067\u3059") };
498
+ }
499
+ return fetchWithAuth(
500
+ `${EXT_INVOICES_ENDPOINT}/${encodeURIComponent(params.invoiceId)}`
501
+ );
502
+ }
503
+ async function retryPayment(params) {
504
+ if (!params.organizationId) {
505
+ return { error: createError("VALIDATION_ERROR", "organizationId \u306F\u5FC5\u9808\u3067\u3059") };
506
+ }
507
+ return fetchWithAuth(EXT_RETRY_PAYMENT_ENDPOINT, {
508
+ method: "POST",
509
+ body: JSON.stringify({ organizationId: params.organizationId })
510
+ });
511
+ }
512
+ return { createCheckoutSession, createPortalSession, listInvoices, getInvoice, retryPayment };
481
513
  }
482
514
 
483
515
  // src/client/subscription-methods.ts
@@ -819,6 +851,45 @@ function createMembersMethods(deps) {
819
851
  return { listMembers, addMember, updateMemberRole, removeMember };
820
852
  }
821
853
 
854
+ // src/client/seat-methods.ts
855
+ function seatsEndpoint(subscriptionId) {
856
+ return `/api/v1/subscriptions/ext/${encodeURIComponent(subscriptionId)}/seats/assignments`;
857
+ }
858
+ function createSeatMethods(deps) {
859
+ const { fetchWithAuth, createError } = deps;
860
+ async function listSeatAssignments(params) {
861
+ if (!params.subscriptionId) {
862
+ return { error: createError("VALIDATION_ERROR", "subscriptionId \u306F\u5FC5\u9808\u3067\u3059") };
863
+ }
864
+ return fetchWithAuth(seatsEndpoint(params.subscriptionId));
865
+ }
866
+ async function assignSeats(params) {
867
+ if (!params.subscriptionId) {
868
+ return { error: createError("VALIDATION_ERROR", "subscriptionId \u306F\u5FC5\u9808\u3067\u3059") };
869
+ }
870
+ if (!Array.isArray(params.userIds) || params.userIds.length === 0) {
871
+ return { error: createError("VALIDATION_ERROR", "userIds \u306F 1 \u4EF6\u4EE5\u4E0A\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044") };
872
+ }
873
+ return fetchWithAuth(seatsEndpoint(params.subscriptionId), {
874
+ method: "POST",
875
+ body: JSON.stringify({ userIds: params.userIds })
876
+ });
877
+ }
878
+ async function unassignSeat(params) {
879
+ if (!params.subscriptionId) {
880
+ return { error: createError("VALIDATION_ERROR", "subscriptionId \u306F\u5FC5\u9808\u3067\u3059") };
881
+ }
882
+ if (!params.userId) {
883
+ return { error: createError("VALIDATION_ERROR", "userId \u306F\u5FC5\u9808\u3067\u3059") };
884
+ }
885
+ return fetchWithAuth(
886
+ `${seatsEndpoint(params.subscriptionId)}/${encodeURIComponent(params.userId)}`,
887
+ { method: "DELETE" }
888
+ );
889
+ }
890
+ return { listSeatAssignments, assignSeats, unassignSeat };
891
+ }
892
+
822
893
  // src/client/profile-methods.ts
823
894
  var EXT_PROFILE_ENDPOINT = "/api/v1/users/ext/me";
824
895
  function resolveAuthOverride(options, createError) {
@@ -869,7 +940,7 @@ function createProfileMethods(deps) {
869
940
  }
870
941
 
871
942
  // src/client/version-check.ts
872
- var SDK_VERSION = "5.10.0";
943
+ var SDK_VERSION = "5.13.0";
873
944
  var SDK_USER_AGENT = `FFID-SDK/${SDK_VERSION} (TypeScript)`;
874
945
  var SDK_VERSION_HEADER = "X-FFID-SDK-Version";
875
946
  function sdkHeaders() {
@@ -941,9 +1012,20 @@ function createOAuthTokenMethods(deps) {
941
1012
  logger,
942
1013
  errorCodes
943
1014
  } = deps;
944
- async function exchangeCodeForTokens(code, codeVerifier) {
1015
+ async function exchangeCodeForTokens(code, codeVerifier, opts) {
945
1016
  const url = `${baseUrl}${OAUTH_TOKEN_ENDPOINT}`;
946
1017
  logger.debug("Exchanging code for tokens:", url);
1018
+ if (opts?.expectedState !== void 0 || opts?.actualState !== void 0) {
1019
+ if (opts?.expectedState !== opts?.actualState) {
1020
+ logger.error("Token exchange aborted: OAuth state mismatch or omitted (possible CSRF)");
1021
+ return {
1022
+ error: {
1023
+ code: errorCodes.STATE_MISMATCH_ERROR,
1024
+ message: "OAuth state \u304C\u4E00\u81F4\u3057\u307E\u305B\u3093\u3002CSRF \u306E\u53EF\u80FD\u6027\u304C\u3042\u308B\u305F\u3081\u8A8D\u8A3C\u3092\u4E2D\u6B62\u3057\u307E\u3057\u305F"
1025
+ }
1026
+ };
1027
+ }
1028
+ }
947
1029
  const effectiveRedirectUri = resolvedRedirectUri ?? (typeof window !== "undefined" ? window.location.origin + window.location.pathname : null);
948
1030
  if (!effectiveRedirectUri) {
949
1031
  logger.error("redirectUri is required for token exchange in SSR environments. Set config.redirectUri explicitly.");
@@ -1427,6 +1509,102 @@ function base64UrlEncode(buffer) {
1427
1509
  return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
1428
1510
  }
1429
1511
 
1512
+ // src/auth/state.ts
1513
+ var STATE_STORAGE_KEY = "ffid_oauth_state";
1514
+ var STATE_FALLBACK_STORAGE_KEY = "ffid_oauth_state_fb";
1515
+ var STATE_FALLBACK_TIMESTAMP_KEY = "ffid_oauth_state_fb_ts";
1516
+ var OAUTH_STATE_MAX_AGE_MS = 5 * 60 * 1e3;
1517
+ function storeState(state, logger) {
1518
+ if (typeof window === "undefined") {
1519
+ logger?.warn("storeState: storage is not available in SSR context");
1520
+ return false;
1521
+ }
1522
+ let sessionStored = false;
1523
+ try {
1524
+ window.sessionStorage.setItem(STATE_STORAGE_KEY, state);
1525
+ sessionStored = true;
1526
+ } catch (error) {
1527
+ logger?.warn("storeState: sessionStorage \u3078\u306E\u4FDD\u5B58\u306B\u5931\u6557\u3057\u307E\u3057\u305F:", error);
1528
+ }
1529
+ let localStored = false;
1530
+ try {
1531
+ window.localStorage.setItem(STATE_FALLBACK_STORAGE_KEY, state);
1532
+ window.localStorage.setItem(STATE_FALLBACK_TIMESTAMP_KEY, String(Date.now()));
1533
+ localStored = true;
1534
+ } catch (error) {
1535
+ logger?.warn("storeState: localStorage fallback \u3078\u306E\u4FDD\u5B58\u306B\u5931\u6557\u3057\u307E\u3057\u305F:", error);
1536
+ }
1537
+ return sessionStored || localStored;
1538
+ }
1539
+ function retrieveState(logger) {
1540
+ if (typeof window === "undefined") return null;
1541
+ let sessionState = null;
1542
+ try {
1543
+ sessionState = window.sessionStorage.getItem(STATE_STORAGE_KEY);
1544
+ } catch (error) {
1545
+ logger?.warn("retrieveState: sessionStorage \u304B\u3089\u306E\u53D6\u5F97\u306B\u5931\u6557\u3057\u307E\u3057\u305F:", error);
1546
+ }
1547
+ if (sessionState) {
1548
+ cleanupStateStorage(logger);
1549
+ return sessionState;
1550
+ }
1551
+ let fallbackState = null;
1552
+ let fallbackTimestamp = null;
1553
+ try {
1554
+ fallbackState = window.localStorage.getItem(STATE_FALLBACK_STORAGE_KEY);
1555
+ fallbackTimestamp = window.localStorage.getItem(STATE_FALLBACK_TIMESTAMP_KEY);
1556
+ } catch (error) {
1557
+ logger?.warn("retrieveState: localStorage fallback \u304B\u3089\u306E\u53D6\u5F97\u306B\u5931\u6557\u3057\u307E\u3057\u305F:", error);
1558
+ cleanupStateStorage(logger);
1559
+ return null;
1560
+ }
1561
+ if (!fallbackState || !fallbackTimestamp) {
1562
+ cleanupStateStorage(logger);
1563
+ return null;
1564
+ }
1565
+ const ts = Number(fallbackTimestamp);
1566
+ const age = Date.now() - ts;
1567
+ if (!Number.isFinite(ts) || age < 0 || age > OAUTH_STATE_MAX_AGE_MS) {
1568
+ cleanupStateStorage(logger);
1569
+ return null;
1570
+ }
1571
+ logger?.warn(
1572
+ "retrieveState: sessionStorage was empty, recovered OAuth state from localStorage fallback"
1573
+ );
1574
+ cleanupStateStorage(logger);
1575
+ return fallbackState;
1576
+ }
1577
+ function isStateStorageAvailable() {
1578
+ if (typeof window === "undefined") return false;
1579
+ const probeKey = "__ffid_oauth_state_probe__";
1580
+ try {
1581
+ window.sessionStorage.setItem(probeKey, "1");
1582
+ window.sessionStorage.removeItem(probeKey);
1583
+ return true;
1584
+ } catch {
1585
+ }
1586
+ try {
1587
+ window.localStorage.setItem(probeKey, "1");
1588
+ window.localStorage.removeItem(probeKey);
1589
+ return true;
1590
+ } catch {
1591
+ return false;
1592
+ }
1593
+ }
1594
+ function cleanupStateStorage(logger) {
1595
+ try {
1596
+ window.sessionStorage.removeItem(STATE_STORAGE_KEY);
1597
+ } catch (error) {
1598
+ logger?.warn("retrieveState: sessionStorage \u306E\u30AF\u30EA\u30FC\u30F3\u30A2\u30C3\u30D7\u306B\u5931\u6557\u3057\u307E\u3057\u305F:", error);
1599
+ }
1600
+ try {
1601
+ window.localStorage.removeItem(STATE_FALLBACK_STORAGE_KEY);
1602
+ window.localStorage.removeItem(STATE_FALLBACK_TIMESTAMP_KEY);
1603
+ } catch (error) {
1604
+ logger?.warn("retrieveState: localStorage \u306E\u30AF\u30EA\u30FC\u30F3\u30A2\u30C3\u30D7\u306B\u5931\u6557\u3057\u307E\u3057\u305F:", error);
1605
+ }
1606
+ }
1607
+
1430
1608
  // src/client/redirect.ts
1431
1609
  var OAUTH_AUTHORIZE_ENDPOINT = "/api/v1/oauth/authorize";
1432
1610
  var AUTH_LOGOUT_ENDPOINT = "/api/v1/auth/logout";
@@ -1558,6 +1736,7 @@ function createRedirectMethods(deps) {
1558
1736
  const recentCount = getRecentRedirectCount(authorizeKey, now, logger);
1559
1737
  if (recentCount >= REDIRECT_LOOP_THRESHOLD) {
1560
1738
  cleanupVerifierStorage(logger);
1739
+ cleanupStateStorage(logger);
1561
1740
  logger.warn("[FFID SDK] redirect loop detected \u2014 \u81EA\u52D5\u30EA\u30C0\u30A4\u30EC\u30AF\u30C8\u3092\u505C\u6B62\u3057\u307E\u3057\u305F", {
1562
1741
  authorizeKey,
1563
1742
  recentCount,
@@ -1581,6 +1760,11 @@ function createRedirectMethods(deps) {
1581
1760
  return { success: false, error: errorMessage };
1582
1761
  }
1583
1762
  const state = generateRandomState();
1763
+ if (!storeState(state, logger)) {
1764
+ logger.error(
1765
+ "redirectToAuthorize: OAuth state \u3092\u4FDD\u5B58\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F\uFF08storage \u5229\u7528\u4E0D\u53EF\uFF09\u3002\u30B3\u30FC\u30EB\u30D0\u30C3\u30AF\u3067\u306E CSRF \u691C\u8A3C\u304C\u5931\u6557\u3057\u307E\u3059"
1766
+ );
1767
+ }
1584
1768
  const redirectUri = resolvedRedirectUri ?? window.location.origin + window.location.pathname;
1585
1769
  const params = new URLSearchParams({
1586
1770
  response_type: "code",
@@ -2259,6 +2443,15 @@ function createInquiryMethods(deps) {
2259
2443
  }
2260
2444
 
2261
2445
  // src/subscriptions/types.ts
2446
+ var EFFECTIVE_SUBSCRIPTION_STATUSES = [
2447
+ "active",
2448
+ "active_canceling",
2449
+ "past_due_grace",
2450
+ "blocked",
2451
+ "canceled",
2452
+ "trial_expired",
2453
+ "expired"
2454
+ ];
2262
2455
  var PAST_DUE_GRACE_PERIOD_DAYS = 7;
2263
2456
  var HOURS_PER_DAY = 24;
2264
2457
  var MINUTES_PER_HOUR = 60;
@@ -2361,7 +2554,8 @@ var FFID_ERROR_CODES = {
2361
2554
  TOKEN_EXCHANGE_ERROR: "TOKEN_EXCHANGE_ERROR",
2362
2555
  TOKEN_REFRESH_ERROR: "TOKEN_REFRESH_ERROR",
2363
2556
  NO_TOKENS: "NO_TOKENS",
2364
- TOKEN_VERIFICATION_ERROR: "TOKEN_VERIFICATION_ERROR"
2557
+ TOKEN_VERIFICATION_ERROR: "TOKEN_VERIFICATION_ERROR",
2558
+ STATE_MISMATCH_ERROR: "STATE_MISMATCH_ERROR"
2365
2559
  };
2366
2560
  var EXT_CHECK_ENDPOINT = "/api/v1/subscriptions/ext/check";
2367
2561
  var DEFAULT_ALLOW_GRACE = true;
@@ -2719,7 +2913,11 @@ function createFFIDClient(config) {
2719
2913
  }
2720
2914
  return result;
2721
2915
  }
2722
- const { createCheckoutSession, createPortalSession } = createBillingMethods({
2916
+ const { createCheckoutSession, createPortalSession, listInvoices, getInvoice, retryPayment } = createBillingMethods({
2917
+ fetchWithAuth,
2918
+ createError
2919
+ });
2920
+ const { listSeatAssignments, assignSeats, unassignSeat } = createSeatMethods({
2723
2921
  fetchWithAuth,
2724
2922
  createError
2725
2923
  });
@@ -2828,6 +3026,12 @@ function createFFIDClient(config) {
2828
3026
  getAnalyticsConfig,
2829
3027
  createCheckoutSession,
2830
3028
  createPortalSession,
3029
+ listInvoices,
3030
+ getInvoice,
3031
+ retryPayment,
3032
+ listSeatAssignments,
3033
+ assignSeats,
3034
+ unassignSeat,
2831
3035
  listPlans,
2832
3036
  getSubscription,
2833
3037
  subscribe,
@@ -2869,6 +3073,99 @@ function createFFIDClient(config) {
2869
3073
  redirectUri: resolvedRedirectUri
2870
3074
  };
2871
3075
  }
3076
+
3077
+ // src/client/callback.ts
3078
+ var CALLBACK_ERROR_CODES = {
3079
+ /** Stored state missing or did not match the callback `state` (CSRF guard) */
3080
+ STATE_MISMATCH: "STATE_MISMATCH_ERROR",
3081
+ /**
3082
+ * Web storage is unavailable, so `state` could never be persisted across the
3083
+ * OAuth bounce. An environment issue (e.g. iOS private mode), not an attack —
3084
+ * surfaced distinctly so callers don't mistake it for a CSRF mismatch.
3085
+ */
3086
+ STATE_UNAVAILABLE: "STATE_STORAGE_UNAVAILABLE",
3087
+ /** Callback URL had no `code` param */
3088
+ MISSING_CODE: "MISSING_AUTHORIZATION_CODE",
3089
+ /** SSR with no `options.url` — cannot resolve callback params */
3090
+ CALLBACK_URL_UNAVAILABLE: "CALLBACK_URL_UNAVAILABLE"
3091
+ };
3092
+ function extractSearchParams(source) {
3093
+ try {
3094
+ return new URL(source).searchParams;
3095
+ } catch {
3096
+ const queryStart = source.indexOf("?");
3097
+ const query = queryStart >= 0 ? source.slice(queryStart + 1) : source;
3098
+ return new URLSearchParams(query);
3099
+ }
3100
+ }
3101
+ function cleanCallbackUrlParams(logger) {
3102
+ try {
3103
+ const cleanUrl = new URL(window.location.href);
3104
+ cleanUrl.searchParams.delete("code");
3105
+ cleanUrl.searchParams.delete("state");
3106
+ window.history.replaceState({}, "", cleanUrl.toString());
3107
+ } catch (error) {
3108
+ logger.warn("handleRedirectCallback: URL \u306E\u30AF\u30EA\u30FC\u30F3\u30A2\u30C3\u30D7\u306B\u5931\u6557\u3057\u307E\u3057\u305F:", error);
3109
+ }
3110
+ }
3111
+ async function handleRedirectCallback(client, options) {
3112
+ const { logger } = client;
3113
+ const hasWindow = typeof window !== "undefined";
3114
+ const source = options?.url ?? (hasWindow ? window.location.search : null);
3115
+ if (source === null) {
3116
+ logger.warn("handleRedirectCallback: SSR \u74B0\u5883\u3067\u306F options.url \u304C\u5FC5\u8981\u3067\u3059");
3117
+ return {
3118
+ error: client.createError(
3119
+ CALLBACK_ERROR_CODES.CALLBACK_URL_UNAVAILABLE,
3120
+ "\u30B3\u30FC\u30EB\u30D0\u30C3\u30AF URL \u3092\u89E3\u6C7A\u3067\u304D\u307E\u305B\u3093\u3002SSR \u74B0\u5883\u3067\u306F options.url \u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044"
3121
+ )
3122
+ };
3123
+ }
3124
+ const params = extractSearchParams(source);
3125
+ const code = params.get("code");
3126
+ const urlState = params.get("state");
3127
+ if (!code) {
3128
+ logger.warn("handleRedirectCallback: \u8A8D\u53EF\u30B3\u30FC\u30C9\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093");
3129
+ return {
3130
+ error: client.createError(
3131
+ CALLBACK_ERROR_CODES.MISSING_CODE,
3132
+ "\u30B3\u30FC\u30EB\u30D0\u30C3\u30AF URL \u306B\u8A8D\u53EF\u30B3\u30FC\u30C9\u304C\u542B\u307E\u308C\u3066\u3044\u307E\u305B\u3093"
3133
+ )
3134
+ };
3135
+ }
3136
+ const storedState = retrieveState(logger);
3137
+ if (storedState !== null || urlState !== null) {
3138
+ const stateMatches = storedState !== null && storedState === urlState;
3139
+ if (!stateMatches) {
3140
+ if (storedState === null && !isStateStorageAvailable()) {
3141
+ logger.error(
3142
+ "handleRedirectCallback: OAuth state \u3092\u4FDD\u5B58\u3067\u304D\u308B storage \u304C\u5229\u7528\u3067\u304D\u306A\u3044\u305F\u3081 CSRF \u691C\u8A3C\u304C\u3067\u304D\u307E\u305B\u3093\uFF08iOS private mode \u7B49\uFF09"
3143
+ );
3144
+ return {
3145
+ error: client.createError(
3146
+ CALLBACK_ERROR_CODES.STATE_UNAVAILABLE,
3147
+ "OAuth state \u3092\u4FDD\u5B58\u3067\u304D\u308B storage \u304C\u5229\u7528\u3067\u304D\u306A\u3044\u305F\u3081\u8A8D\u8A3C\u3092\u5B8C\u4E86\u3067\u304D\u307E\u305B\u3093\u3002\u30D6\u30E9\u30A6\u30B6\u306E\u30D7\u30E9\u30A4\u30D9\u30FC\u30C8\u30E2\u30FC\u30C9\u3084\u30B9\u30C8\u30EC\u30FC\u30B8\u8A2D\u5B9A\u3092\u3054\u78BA\u8A8D\u304F\u3060\u3055\u3044"
3148
+ )
3149
+ };
3150
+ }
3151
+ logger.error(
3152
+ "handleRedirectCallback: OAuth state mismatch or omitted (possible CSRF) \u2014 \u8A8D\u8A3C\u3092\u4E2D\u6B62\u3057\u307E\u3057\u305F"
3153
+ );
3154
+ return {
3155
+ error: client.createError(
3156
+ CALLBACK_ERROR_CODES.STATE_MISMATCH,
3157
+ "OAuth state \u304C\u4E00\u81F4\u3057\u307E\u305B\u3093\u3002CSRF \u306E\u53EF\u80FD\u6027\u304C\u3042\u308B\u305F\u3081\u8A8D\u8A3C\u3092\u4E2D\u6B62\u3057\u307E\u3057\u305F"
3158
+ )
3159
+ };
3160
+ }
3161
+ }
3162
+ const verifier = retrieveCodeVerifier(logger);
3163
+ const result = await client.exchangeCodeForTokens(code, verifier ?? void 0);
3164
+ if (!result.error && hasWindow) {
3165
+ cleanCallbackUrlParams(logger);
3166
+ }
3167
+ return result;
3168
+ }
2872
3169
  var DEFAULT_REFRESH_INTERVAL_MS = 5 * 60 * 1e3;
2873
3170
  var TOKEN_REFRESH_RATIO = 0.8;
2874
3171
  var FFIDContext = react.createContext(null);
@@ -3043,8 +3340,7 @@ function FFIDProvider({
3043
3340
  const code = urlParams.get("code");
3044
3341
  if (!code) return;
3045
3342
  client.logger.debug("Authorization code detected, exchanging for tokens");
3046
- const codeVerifier = retrieveCodeVerifier(client.logger);
3047
- client.exchangeCodeForTokens(code, codeVerifier ?? void 0).then((result) => {
3343
+ handleRedirectCallback(client).then((result) => {
3048
3344
  if (result.error) {
3049
3345
  client.logger.error("Token exchange failed:", result.error);
3050
3346
  setError(result.error);
@@ -3052,10 +3348,6 @@ function FFIDProvider({
3052
3348
  setIsLoading(false);
3053
3349
  return;
3054
3350
  }
3055
- const cleanUrl = new URL(window.location.href);
3056
- cleanUrl.searchParams.delete("code");
3057
- cleanUrl.searchParams.delete("state");
3058
- window.history.replaceState({}, "", cleanUrl.toString());
3059
3351
  client.logger.debug("Token exchange successful, refreshing session");
3060
3352
  return refresh();
3061
3353
  }).catch((err) => {
@@ -4860,6 +5152,7 @@ exports.ACCESS_GRANTING_EFFECTIVE_STATUSES = ACCESS_GRANTING_EFFECTIVE_STATUSES;
4860
5152
  exports.BLOCKING_EFFECTIVE_STATUSES = BLOCKING_EFFECTIVE_STATUSES;
4861
5153
  exports.DEFAULT_API_BASE_URL = DEFAULT_API_BASE_URL;
4862
5154
  exports.DEFAULT_OAUTH_SCOPES = DEFAULT_OAUTH_SCOPES;
5155
+ exports.EFFECTIVE_SUBSCRIPTION_STATUSES = EFFECTIVE_SUBSCRIPTION_STATUSES;
4863
5156
  exports.FFIDAnnouncementBadge = FFIDAnnouncementBadge;
4864
5157
  exports.FFIDAnnouncementList = FFIDAnnouncementList;
4865
5158
  exports.FFIDInquiryForm = FFIDInquiryForm;
@@ -4872,18 +5165,22 @@ exports.FFIDUserMenu = FFIDUserMenu;
4872
5165
  exports.FFID_ANNOUNCEMENTS_ERROR_CODES = FFID_ANNOUNCEMENTS_ERROR_CODES;
4873
5166
  exports.FFID_INQUIRY_CATEGORIES = FFID_INQUIRY_CATEGORIES;
4874
5167
  exports.FFID_INQUIRY_CATEGORIES_SITE_2026 = FFID_INQUIRY_CATEGORIES_SITE_2026;
5168
+ exports.cleanupStateStorage = cleanupStateStorage;
4875
5169
  exports.computeEffectiveStatusFromSession = computeEffectiveStatusFromSession;
4876
5170
  exports.createFFIDAnnouncementsClient = createFFIDAnnouncementsClient;
4877
5171
  exports.createFFIDClient = createFFIDClient;
4878
5172
  exports.createTokenStore = createTokenStore;
4879
5173
  exports.generateCodeChallenge = generateCodeChallenge;
4880
5174
  exports.generateCodeVerifier = generateCodeVerifier;
5175
+ exports.handleRedirectCallback = handleRedirectCallback;
4881
5176
  exports.hasAccessFromUserinfo = hasAccessFromUserinfo;
4882
5177
  exports.isBlockedFromUserinfo = isBlockedFromUserinfo;
4883
5178
  exports.isFFIDInquiryCategorySite2026 = isFFIDInquiryCategorySite2026;
4884
5179
  exports.normalizeRedirectUri = normalizeRedirectUri;
4885
5180
  exports.retrieveCodeVerifier = retrieveCodeVerifier;
5181
+ exports.retrieveState = retrieveState;
4886
5182
  exports.storeCodeVerifier = storeCodeVerifier;
5183
+ exports.storeState = storeState;
4887
5184
  exports.useFFID = useFFID;
4888
5185
  exports.useFFIDAnnouncements = useFFIDAnnouncements;
4889
5186
  exports.useFFIDContext = useFFIDContext;