@feelflow/ffid-sdk 2.17.1 → 2.18.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.
@@ -105,10 +105,7 @@ function createTokenStore(storageType) {
105
105
  // src/client/oauth-userinfo.ts
106
106
  var SESSION_ELIGIBLE_SUBSCRIPTION_STATUSES = [
107
107
  "trialing",
108
- "active",
109
- "past_due",
110
- "canceled",
111
- "paused"
108
+ "active"
112
109
  ];
113
110
  function isSessionEligibleSubscriptionStatus(value) {
114
111
  return typeof value === "string" && SESSION_ELIGIBLE_SUBSCRIPTION_STATUSES.includes(value);
@@ -810,7 +807,7 @@ function createProfileMethods(deps) {
810
807
  }
811
808
 
812
809
  // src/client/version-check.ts
813
- var SDK_VERSION = "2.17.1";
810
+ var SDK_VERSION = "2.18.0";
814
811
  var SDK_USER_AGENT = `FFID-SDK/${SDK_VERSION} (TypeScript)`;
815
812
  var SDK_VERSION_HEADER = "X-FFID-SDK-Version";
816
813
  function sdkHeaders() {
@@ -1373,6 +1370,87 @@ var OAUTH_AUTHORIZE_ENDPOINT = "/api/v1/oauth/authorize";
1373
1370
  var AUTH_LOGOUT_ENDPOINT = "/api/v1/auth/logout";
1374
1371
  var STATE_RANDOM_BYTES = 16;
1375
1372
  var HEX_BASE2 = 16;
1373
+ var REDIRECT_LOOP_KEY = "ffid_sdk_redirect_loop_history";
1374
+ var REDIRECT_LOOP_WINDOW_MS = 6e4;
1375
+ var REDIRECT_LOOP_THRESHOLD = 3;
1376
+ var storageReadFailureLogged = false;
1377
+ var storageWriteFailureLogged = false;
1378
+ function logStorageReadFailure(logger, err) {
1379
+ if (storageReadFailureLogged) return;
1380
+ storageReadFailureLogged = true;
1381
+ logger.warn(
1382
+ "[FFID SDK] sessionStorage read failed \u2014 redirect loop detection disabled on this browser (fail-open). iOS WebKit private mode / eviction \u304C\u5178\u578B\u8981\u56E0",
1383
+ err
1384
+ );
1385
+ }
1386
+ function logStorageWriteFailure(logger, err) {
1387
+ if (storageWriteFailureLogged) return;
1388
+ storageWriteFailureLogged = true;
1389
+ logger.warn(
1390
+ "[FFID SDK] sessionStorage write failed \u2014 redirect loop detection disabled on this browser (fail-open).",
1391
+ err
1392
+ );
1393
+ }
1394
+ function isRedirectLoopHistory(value) {
1395
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
1396
+ return Object.values(value).every(
1397
+ (arr) => Array.isArray(arr) && arr.every((t) => typeof t === "number" && Number.isFinite(t))
1398
+ );
1399
+ }
1400
+ function readRedirectLoopHistory(logger) {
1401
+ if (typeof window === "undefined") return {};
1402
+ try {
1403
+ const raw = window.sessionStorage.getItem(REDIRECT_LOOP_KEY);
1404
+ if (raw === null) return {};
1405
+ const parsed = JSON.parse(raw);
1406
+ return isRedirectLoopHistory(parsed) ? parsed : {};
1407
+ } catch (err) {
1408
+ logStorageReadFailure(logger, err);
1409
+ return {};
1410
+ }
1411
+ }
1412
+ function writeRedirectLoopHistory(history, logger) {
1413
+ if (typeof window === "undefined") return;
1414
+ try {
1415
+ window.sessionStorage.setItem(REDIRECT_LOOP_KEY, JSON.stringify(history));
1416
+ } catch (err) {
1417
+ logStorageWriteFailure(logger, err);
1418
+ }
1419
+ }
1420
+ function pruneRedirectLoopHistory(history, now) {
1421
+ const cutoff = now - REDIRECT_LOOP_WINDOW_MS;
1422
+ const pruned = {};
1423
+ for (const [key, timestamps] of Object.entries(history)) {
1424
+ const fresh = timestamps.filter((t) => t > cutoff);
1425
+ if (fresh.length > 0) pruned[key] = fresh;
1426
+ }
1427
+ return pruned;
1428
+ }
1429
+ function getRecentRedirectCount(authorizeKey, now, logger) {
1430
+ const pruned = pruneRedirectLoopHistory(readRedirectLoopHistory(logger), now);
1431
+ writeRedirectLoopHistory(pruned, logger);
1432
+ return pruned[authorizeKey]?.length ?? 0;
1433
+ }
1434
+ function recordRedirectAttempt(authorizeKey, now, logger) {
1435
+ const history = readRedirectLoopHistory(logger);
1436
+ const existing = history[authorizeKey] ?? [];
1437
+ history[authorizeKey] = [...existing, now];
1438
+ writeRedirectLoopHistory(history, logger);
1439
+ }
1440
+ function rollbackLastRedirectAttempt(authorizeKey, logger) {
1441
+ const history = readRedirectLoopHistory(logger);
1442
+ const existing = history[authorizeKey];
1443
+ if (!Array.isArray(existing) || existing.length === 0) return;
1444
+ history[authorizeKey] = existing.slice(0, -1);
1445
+ if (history[authorizeKey].length === 0) delete history[authorizeKey];
1446
+ writeRedirectLoopHistory(history, logger);
1447
+ }
1448
+ function buildAuthorizeKey(baseUrl, clientId, organizationId) {
1449
+ const params = new URLSearchParams({ client_id: clientId });
1450
+ const trimmedOrg = organizationId?.trim();
1451
+ if (trimmedOrg) params.set("organization_id", trimmedOrg);
1452
+ return `${baseUrl}${OAUTH_AUTHORIZE_ENDPOINT}?${params.toString()}`;
1453
+ }
1376
1454
  function generateRandomState() {
1377
1455
  const array = new Uint8Array(STATE_RANDOM_BYTES);
1378
1456
  crypto.getRandomValues(array);
@@ -1392,6 +1470,23 @@ function createRedirectMethods(deps) {
1392
1470
  logger.warn("SSR \u74B0\u5883\u3067\u306F\u30EA\u30C0\u30A4\u30EC\u30AF\u30C8\u3067\u304D\u307E\u305B\u3093");
1393
1471
  return { success: false, error: "SSR \u74B0\u5883\u3067\u306F\u30EA\u30C0\u30A4\u30EC\u30AF\u30C8\u3067\u304D\u307E\u305B\u3093" };
1394
1472
  }
1473
+ const authorizeKey = buildAuthorizeKey(baseUrl, clientId, options?.organizationId);
1474
+ const now = Date.now();
1475
+ const recentCount = getRecentRedirectCount(authorizeKey, now, logger);
1476
+ if (recentCount >= REDIRECT_LOOP_THRESHOLD) {
1477
+ cleanupVerifierStorage(logger);
1478
+ logger.warn("[FFID SDK] redirect loop detected \u2014 \u81EA\u52D5\u30EA\u30C0\u30A4\u30EC\u30AF\u30C8\u3092\u505C\u6B62\u3057\u307E\u3057\u305F", {
1479
+ authorizeKey,
1480
+ recentCount,
1481
+ windowMs: REDIRECT_LOOP_WINDOW_MS,
1482
+ threshold: REDIRECT_LOOP_THRESHOLD
1483
+ });
1484
+ return {
1485
+ success: false,
1486
+ code: "redirect_loop_detected",
1487
+ error: "\u77ED\u6642\u9593\u306B\u540C\u3058\u8A8D\u53EF\u30A8\u30F3\u30C9\u30DD\u30A4\u30F3\u30C8\u3078\u306E\u30EA\u30C0\u30A4\u30EC\u30AF\u30C8\u304C\u7E70\u308A\u8FD4\u3055\u308C\u305F\u305F\u3081\u3001\u30EB\u30FC\u30D7\u691C\u51FA\u306B\u3088\u308A\u81EA\u52D5\u30EA\u30C0\u30A4\u30EC\u30AF\u30C8\u3092\u505C\u6B62\u3057\u307E\u3057\u305F"
1488
+ };
1489
+ }
1395
1490
  const verifier = generateCodeVerifier();
1396
1491
  storeCodeVerifier(verifier, logger);
1397
1492
  let challenge;
@@ -1422,7 +1517,13 @@ function createRedirectMethods(deps) {
1422
1517
  }
1423
1518
  const authorizeUrl = `${baseUrl}${OAUTH_AUTHORIZE_ENDPOINT}?${params.toString()}`;
1424
1519
  logger.debug("Redirecting to authorize:", authorizeUrl);
1425
- window.location.href = authorizeUrl;
1520
+ recordRedirectAttempt(authorizeKey, now, logger);
1521
+ try {
1522
+ window.location.href = authorizeUrl;
1523
+ } catch (err) {
1524
+ rollbackLastRedirectAttempt(authorizeKey, logger);
1525
+ throw err;
1526
+ }
1426
1527
  return { success: true };
1427
1528
  }
1428
1529
  async function redirectToLogin() {
@@ -103,10 +103,7 @@ function createTokenStore(storageType) {
103
103
  // src/client/oauth-userinfo.ts
104
104
  var SESSION_ELIGIBLE_SUBSCRIPTION_STATUSES = [
105
105
  "trialing",
106
- "active",
107
- "past_due",
108
- "canceled",
109
- "paused"
106
+ "active"
110
107
  ];
111
108
  function isSessionEligibleSubscriptionStatus(value) {
112
109
  return typeof value === "string" && SESSION_ELIGIBLE_SUBSCRIPTION_STATUSES.includes(value);
@@ -808,7 +805,7 @@ function createProfileMethods(deps) {
808
805
  }
809
806
 
810
807
  // src/client/version-check.ts
811
- var SDK_VERSION = "2.17.1";
808
+ var SDK_VERSION = "2.18.0";
812
809
  var SDK_USER_AGENT = `FFID-SDK/${SDK_VERSION} (TypeScript)`;
813
810
  var SDK_VERSION_HEADER = "X-FFID-SDK-Version";
814
811
  function sdkHeaders() {
@@ -1371,6 +1368,87 @@ var OAUTH_AUTHORIZE_ENDPOINT = "/api/v1/oauth/authorize";
1371
1368
  var AUTH_LOGOUT_ENDPOINT = "/api/v1/auth/logout";
1372
1369
  var STATE_RANDOM_BYTES = 16;
1373
1370
  var HEX_BASE2 = 16;
1371
+ var REDIRECT_LOOP_KEY = "ffid_sdk_redirect_loop_history";
1372
+ var REDIRECT_LOOP_WINDOW_MS = 6e4;
1373
+ var REDIRECT_LOOP_THRESHOLD = 3;
1374
+ var storageReadFailureLogged = false;
1375
+ var storageWriteFailureLogged = false;
1376
+ function logStorageReadFailure(logger, err) {
1377
+ if (storageReadFailureLogged) return;
1378
+ storageReadFailureLogged = true;
1379
+ logger.warn(
1380
+ "[FFID SDK] sessionStorage read failed \u2014 redirect loop detection disabled on this browser (fail-open). iOS WebKit private mode / eviction \u304C\u5178\u578B\u8981\u56E0",
1381
+ err
1382
+ );
1383
+ }
1384
+ function logStorageWriteFailure(logger, err) {
1385
+ if (storageWriteFailureLogged) return;
1386
+ storageWriteFailureLogged = true;
1387
+ logger.warn(
1388
+ "[FFID SDK] sessionStorage write failed \u2014 redirect loop detection disabled on this browser (fail-open).",
1389
+ err
1390
+ );
1391
+ }
1392
+ function isRedirectLoopHistory(value) {
1393
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
1394
+ return Object.values(value).every(
1395
+ (arr) => Array.isArray(arr) && arr.every((t) => typeof t === "number" && Number.isFinite(t))
1396
+ );
1397
+ }
1398
+ function readRedirectLoopHistory(logger) {
1399
+ if (typeof window === "undefined") return {};
1400
+ try {
1401
+ const raw = window.sessionStorage.getItem(REDIRECT_LOOP_KEY);
1402
+ if (raw === null) return {};
1403
+ const parsed = JSON.parse(raw);
1404
+ return isRedirectLoopHistory(parsed) ? parsed : {};
1405
+ } catch (err) {
1406
+ logStorageReadFailure(logger, err);
1407
+ return {};
1408
+ }
1409
+ }
1410
+ function writeRedirectLoopHistory(history, logger) {
1411
+ if (typeof window === "undefined") return;
1412
+ try {
1413
+ window.sessionStorage.setItem(REDIRECT_LOOP_KEY, JSON.stringify(history));
1414
+ } catch (err) {
1415
+ logStorageWriteFailure(logger, err);
1416
+ }
1417
+ }
1418
+ function pruneRedirectLoopHistory(history, now) {
1419
+ const cutoff = now - REDIRECT_LOOP_WINDOW_MS;
1420
+ const pruned = {};
1421
+ for (const [key, timestamps] of Object.entries(history)) {
1422
+ const fresh = timestamps.filter((t) => t > cutoff);
1423
+ if (fresh.length > 0) pruned[key] = fresh;
1424
+ }
1425
+ return pruned;
1426
+ }
1427
+ function getRecentRedirectCount(authorizeKey, now, logger) {
1428
+ const pruned = pruneRedirectLoopHistory(readRedirectLoopHistory(logger), now);
1429
+ writeRedirectLoopHistory(pruned, logger);
1430
+ return pruned[authorizeKey]?.length ?? 0;
1431
+ }
1432
+ function recordRedirectAttempt(authorizeKey, now, logger) {
1433
+ const history = readRedirectLoopHistory(logger);
1434
+ const existing = history[authorizeKey] ?? [];
1435
+ history[authorizeKey] = [...existing, now];
1436
+ writeRedirectLoopHistory(history, logger);
1437
+ }
1438
+ function rollbackLastRedirectAttempt(authorizeKey, logger) {
1439
+ const history = readRedirectLoopHistory(logger);
1440
+ const existing = history[authorizeKey];
1441
+ if (!Array.isArray(existing) || existing.length === 0) return;
1442
+ history[authorizeKey] = existing.slice(0, -1);
1443
+ if (history[authorizeKey].length === 0) delete history[authorizeKey];
1444
+ writeRedirectLoopHistory(history, logger);
1445
+ }
1446
+ function buildAuthorizeKey(baseUrl, clientId, organizationId) {
1447
+ const params = new URLSearchParams({ client_id: clientId });
1448
+ const trimmedOrg = organizationId?.trim();
1449
+ if (trimmedOrg) params.set("organization_id", trimmedOrg);
1450
+ return `${baseUrl}${OAUTH_AUTHORIZE_ENDPOINT}?${params.toString()}`;
1451
+ }
1374
1452
  function generateRandomState() {
1375
1453
  const array = new Uint8Array(STATE_RANDOM_BYTES);
1376
1454
  crypto.getRandomValues(array);
@@ -1390,6 +1468,23 @@ function createRedirectMethods(deps) {
1390
1468
  logger.warn("SSR \u74B0\u5883\u3067\u306F\u30EA\u30C0\u30A4\u30EC\u30AF\u30C8\u3067\u304D\u307E\u305B\u3093");
1391
1469
  return { success: false, error: "SSR \u74B0\u5883\u3067\u306F\u30EA\u30C0\u30A4\u30EC\u30AF\u30C8\u3067\u304D\u307E\u305B\u3093" };
1392
1470
  }
1471
+ const authorizeKey = buildAuthorizeKey(baseUrl, clientId, options?.organizationId);
1472
+ const now = Date.now();
1473
+ const recentCount = getRecentRedirectCount(authorizeKey, now, logger);
1474
+ if (recentCount >= REDIRECT_LOOP_THRESHOLD) {
1475
+ cleanupVerifierStorage(logger);
1476
+ logger.warn("[FFID SDK] redirect loop detected \u2014 \u81EA\u52D5\u30EA\u30C0\u30A4\u30EC\u30AF\u30C8\u3092\u505C\u6B62\u3057\u307E\u3057\u305F", {
1477
+ authorizeKey,
1478
+ recentCount,
1479
+ windowMs: REDIRECT_LOOP_WINDOW_MS,
1480
+ threshold: REDIRECT_LOOP_THRESHOLD
1481
+ });
1482
+ return {
1483
+ success: false,
1484
+ code: "redirect_loop_detected",
1485
+ error: "\u77ED\u6642\u9593\u306B\u540C\u3058\u8A8D\u53EF\u30A8\u30F3\u30C9\u30DD\u30A4\u30F3\u30C8\u3078\u306E\u30EA\u30C0\u30A4\u30EC\u30AF\u30C8\u304C\u7E70\u308A\u8FD4\u3055\u308C\u305F\u305F\u3081\u3001\u30EB\u30FC\u30D7\u691C\u51FA\u306B\u3088\u308A\u81EA\u52D5\u30EA\u30C0\u30A4\u30EC\u30AF\u30C8\u3092\u505C\u6B62\u3057\u307E\u3057\u305F"
1486
+ };
1487
+ }
1393
1488
  const verifier = generateCodeVerifier();
1394
1489
  storeCodeVerifier(verifier, logger);
1395
1490
  let challenge;
@@ -1420,7 +1515,13 @@ function createRedirectMethods(deps) {
1420
1515
  }
1421
1516
  const authorizeUrl = `${baseUrl}${OAUTH_AUTHORIZE_ENDPOINT}?${params.toString()}`;
1422
1517
  logger.debug("Redirecting to authorize:", authorizeUrl);
1423
- window.location.href = authorizeUrl;
1518
+ recordRedirectAttempt(authorizeKey, now, logger);
1519
+ try {
1520
+ window.location.href = authorizeUrl;
1521
+ } catch (err) {
1522
+ rollbackLastRedirectAttempt(authorizeKey, logger);
1523
+ throw err;
1524
+ }
1424
1525
  return { success: true };
1425
1526
  }
1426
1527
  async function redirectToLogin() {
@@ -1,34 +1,34 @@
1
1
  'use strict';
2
2
 
3
- var chunkDERFBYBZ_cjs = require('../chunk-DERFBYBZ.cjs');
3
+ var chunkEK2W67BW_cjs = require('../chunk-EK2W67BW.cjs');
4
4
 
5
5
 
6
6
 
7
7
  Object.defineProperty(exports, "FFIDAnnouncementBadge", {
8
8
  enumerable: true,
9
- get: function () { return chunkDERFBYBZ_cjs.FFIDAnnouncementBadge; }
9
+ get: function () { return chunkEK2W67BW_cjs.FFIDAnnouncementBadge; }
10
10
  });
11
11
  Object.defineProperty(exports, "FFIDAnnouncementList", {
12
12
  enumerable: true,
13
- get: function () { return chunkDERFBYBZ_cjs.FFIDAnnouncementList; }
13
+ get: function () { return chunkEK2W67BW_cjs.FFIDAnnouncementList; }
14
14
  });
15
15
  Object.defineProperty(exports, "FFIDInquiryForm", {
16
16
  enumerable: true,
17
- get: function () { return chunkDERFBYBZ_cjs.FFIDInquiryForm; }
17
+ get: function () { return chunkEK2W67BW_cjs.FFIDInquiryForm; }
18
18
  });
19
19
  Object.defineProperty(exports, "FFIDLoginButton", {
20
20
  enumerable: true,
21
- get: function () { return chunkDERFBYBZ_cjs.FFIDLoginButton; }
21
+ get: function () { return chunkEK2W67BW_cjs.FFIDLoginButton; }
22
22
  });
23
23
  Object.defineProperty(exports, "FFIDOrganizationSwitcher", {
24
24
  enumerable: true,
25
- get: function () { return chunkDERFBYBZ_cjs.FFIDOrganizationSwitcher; }
25
+ get: function () { return chunkEK2W67BW_cjs.FFIDOrganizationSwitcher; }
26
26
  });
27
27
  Object.defineProperty(exports, "FFIDSubscriptionBadge", {
28
28
  enumerable: true,
29
- get: function () { return chunkDERFBYBZ_cjs.FFIDSubscriptionBadge; }
29
+ get: function () { return chunkEK2W67BW_cjs.FFIDSubscriptionBadge; }
30
30
  });
31
31
  Object.defineProperty(exports, "FFIDUserMenu", {
32
32
  enumerable: true,
33
- get: function () { return chunkDERFBYBZ_cjs.FFIDUserMenu; }
33
+ get: function () { return chunkEK2W67BW_cjs.FFIDUserMenu; }
34
34
  });
@@ -1,3 +1,3 @@
1
- export { K as FFIDAnnouncementBadge, aj as FFIDAnnouncementBadgeClassNames, ak as FFIDAnnouncementBadgeProps, M as FFIDAnnouncementList, al as FFIDAnnouncementListClassNames, am as FFIDAnnouncementListProps, U as FFIDInquiryForm, V as FFIDInquiryFormCategoryItem, W as FFIDInquiryFormClassNames, X as FFIDInquiryFormOrganization, Y as FFIDInquiryFormPlaceholderContext, Z as FFIDInquiryFormPrefill, _ as FFIDInquiryFormProps, $ as FFIDInquiryFormSubmitData, a0 as FFIDInquiryFormSubmitResult, a2 as FFIDLoginButton, an as FFIDLoginButtonProps, a8 as FFIDOrganizationSwitcher, ao as FFIDOrganizationSwitcherClassNames, ap as FFIDOrganizationSwitcherProps, aa as FFIDSubscriptionBadge, aq as FFIDSubscriptionBadgeClassNames, ar as FFIDSubscriptionBadgeProps, ac as FFIDUserMenu, as as FFIDUserMenuClassNames, at as FFIDUserMenuProps } from '../index-Cv1qXIl1.cjs';
1
+ export { K as FFIDAnnouncementBadge, ak as FFIDAnnouncementBadgeClassNames, al as FFIDAnnouncementBadgeProps, M as FFIDAnnouncementList, am as FFIDAnnouncementListClassNames, an as FFIDAnnouncementListProps, U as FFIDInquiryForm, V as FFIDInquiryFormCategoryItem, W as FFIDInquiryFormClassNames, X as FFIDInquiryFormOrganization, Y as FFIDInquiryFormPlaceholderContext, Z as FFIDInquiryFormPrefill, _ as FFIDInquiryFormProps, $ as FFIDInquiryFormSubmitData, a0 as FFIDInquiryFormSubmitResult, a2 as FFIDLoginButton, ao as FFIDLoginButtonProps, a8 as FFIDOrganizationSwitcher, ap as FFIDOrganizationSwitcherClassNames, aq as FFIDOrganizationSwitcherProps, ab as FFIDSubscriptionBadge, ar as FFIDSubscriptionBadgeClassNames, as as FFIDSubscriptionBadgeProps, ad as FFIDUserMenu, at as FFIDUserMenuClassNames, au as FFIDUserMenuProps } from '../index--S6rLHjr.cjs';
2
2
  import 'react/jsx-runtime';
3
3
  import 'react';
@@ -1,3 +1,3 @@
1
- export { K as FFIDAnnouncementBadge, aj as FFIDAnnouncementBadgeClassNames, ak as FFIDAnnouncementBadgeProps, M as FFIDAnnouncementList, al as FFIDAnnouncementListClassNames, am as FFIDAnnouncementListProps, U as FFIDInquiryForm, V as FFIDInquiryFormCategoryItem, W as FFIDInquiryFormClassNames, X as FFIDInquiryFormOrganization, Y as FFIDInquiryFormPlaceholderContext, Z as FFIDInquiryFormPrefill, _ as FFIDInquiryFormProps, $ as FFIDInquiryFormSubmitData, a0 as FFIDInquiryFormSubmitResult, a2 as FFIDLoginButton, an as FFIDLoginButtonProps, a8 as FFIDOrganizationSwitcher, ao as FFIDOrganizationSwitcherClassNames, ap as FFIDOrganizationSwitcherProps, aa as FFIDSubscriptionBadge, aq as FFIDSubscriptionBadgeClassNames, ar as FFIDSubscriptionBadgeProps, ac as FFIDUserMenu, as as FFIDUserMenuClassNames, at as FFIDUserMenuProps } from '../index-Cv1qXIl1.js';
1
+ export { K as FFIDAnnouncementBadge, ak as FFIDAnnouncementBadgeClassNames, al as FFIDAnnouncementBadgeProps, M as FFIDAnnouncementList, am as FFIDAnnouncementListClassNames, an as FFIDAnnouncementListProps, U as FFIDInquiryForm, V as FFIDInquiryFormCategoryItem, W as FFIDInquiryFormClassNames, X as FFIDInquiryFormOrganization, Y as FFIDInquiryFormPlaceholderContext, Z as FFIDInquiryFormPrefill, _ as FFIDInquiryFormProps, $ as FFIDInquiryFormSubmitData, a0 as FFIDInquiryFormSubmitResult, a2 as FFIDLoginButton, ao as FFIDLoginButtonProps, a8 as FFIDOrganizationSwitcher, ap as FFIDOrganizationSwitcherClassNames, aq as FFIDOrganizationSwitcherProps, ab as FFIDSubscriptionBadge, ar as FFIDSubscriptionBadgeClassNames, as as FFIDSubscriptionBadgeProps, ad as FFIDUserMenu, at as FFIDUserMenuClassNames, au as FFIDUserMenuProps } from '../index--S6rLHjr.js';
2
2
  import 'react/jsx-runtime';
3
3
  import 'react';
@@ -1 +1 @@
1
- export { FFIDAnnouncementBadge, FFIDAnnouncementList, FFIDInquiryForm, FFIDLoginButton, FFIDOrganizationSwitcher, FFIDSubscriptionBadge, FFIDUserMenu } from '../chunk-FGTRPNSW.js';
1
+ export { FFIDAnnouncementBadge, FFIDAnnouncementList, FFIDInquiryForm, FFIDLoginButton, FFIDOrganizationSwitcher, FFIDSubscriptionBadge, FFIDUserMenu } from '../chunk-IEYXT3LA.js';
@@ -537,17 +537,36 @@ interface FFIDUpdateUserProfileRequest {
537
537
  /** Arbitrary user preferences bag (null clears the column; reads return {}) */
538
538
  preferences?: Record<string, unknown> | null;
539
539
  }
540
+ /**
541
+ * Discriminant for redirect failures that callers need to handle
542
+ * programmatically (vs. logging the human-readable `error` string).
543
+ *
544
+ * - `'redirect_loop_detected'`: `redirectToAuthorize()` detected that the
545
+ * same authorize URL (keyed by `baseUrl + client_id + organization_id`)
546
+ * has been fired **3 times within 60 seconds**. Caller should surface a
547
+ * manual "再度ログインする" UI rather than retry automatically (#2406 / #2411).
548
+ *
549
+ * SDK 2.18.0 only ships `'redirect_loop_detected'`. Other failure paths
550
+ * (SSR environment, PKCE generation failure, empty organizationId) currently
551
+ * return `error` without a `code`. New codes will be added in future minor
552
+ * versions — treat this union as forward-extensible and do NOT exhaustively
553
+ * `switch` over it without a `default` branch for consumer code that must
554
+ * stay compatible across SDK upgrades.
555
+ */
556
+ type FFIDRedirectErrorCode = 'redirect_loop_detected';
540
557
  /**
541
558
  * Result of a redirect operation (redirectToLogin / redirectToAuthorize / redirectToLogout)
542
559
  *
543
560
  * Structured return type so callers can inspect failure reasons
544
- * instead of receiving a bare `false`.
561
+ * instead of receiving a bare `false`. When `code` is set, branch on it
562
+ * for programmatic handling; otherwise log `error` for humans.
545
563
  */
546
564
  type FFIDRedirectResult = {
547
565
  success: true;
548
566
  } | {
549
567
  success: false;
550
568
  error: string;
569
+ code?: FFIDRedirectErrorCode;
551
570
  };
552
571
  /**
553
572
  * OAuth 2.0 token response from FFID token endpoint
@@ -1282,4 +1301,4 @@ interface FFIDInquiryFormPlaceholderContext {
1282
1301
  }
1283
1302
  declare function FFIDInquiryForm({ mode, prefill, organizations, preselectedOrganizationId, categories, termsVersion, privacyVersion, termsHref, privacyHref, turnstileToken, turnstileSlot, onSubmit, onChange, separateLegalCheckboxes, messagePlaceholder, requireCategorySelection, unstyled, classNames, locale, className, }: FFIDInquiryFormProps): react_jsx_runtime.JSX.Element;
1284
1303
 
1285
- export { type FFIDInquiryFormSubmitData as $, type FFIDSubscription as A, type FFIDSubscriptionContextValue as B, type FFIDAnnouncementsClientConfig as C, type FFIDAnnouncementsApiResponse as D, type AnnouncementListResponse as E, type FFIDSubscriptionStatus as F, type FFIDAnnouncementsLogger as G, type Announcement as H, type AnnouncementStatus as I, type AnnouncementType as J, FFIDAnnouncementBadge as K, type ListAnnouncementsOptions as L, FFIDAnnouncementList as M, type FFIDAnnouncementsError as N, type FFIDAnnouncementsErrorCode as O, type FFIDAnnouncementsServerResponse as P, type FFIDCacheConfig as Q, type FFIDContextValue as R, type FFIDInquiryCategory as S, type FFIDInquiryCategorySite2026 as T, FFIDInquiryForm as U, type FFIDInquiryFormCategoryItem as V, type FFIDInquiryFormClassNames as W, type FFIDInquiryFormOrganization as X, type FFIDInquiryFormPlaceholderContext as Y, type FFIDInquiryFormPrefill as Z, type FFIDInquiryFormProps as _, type FFIDConfig as a, type FFIDInquiryFormSubmitResult as a0, type FFIDJwtClaims as a1, FFIDLoginButton as a2, type FFIDMemberStatus as a3, type FFIDOAuthTokenResponse as a4, type FFIDOAuthUserInfoMemberRole as a5, type FFIDOAuthUserInfoSubscription as a6, type FFIDOrganizationMember as a7, FFIDOrganizationSwitcher as a8, type FFIDSeatModel as a9, FFIDSubscriptionBadge as aa, type FFIDTokenIntrospectionResponse as ab, FFIDUserMenu as ac, FFID_INQUIRY_CATEGORIES as ad, FFID_INQUIRY_CATEGORIES_SITE_2026 as ae, type UseFFIDAnnouncementsOptions as af, type UseFFIDAnnouncementsReturn as ag, isFFIDInquiryCategorySite2026 as ah, useFFIDAnnouncements as ai, type FFIDAnnouncementBadgeClassNames as aj, type FFIDAnnouncementBadgeProps as ak, type FFIDAnnouncementListClassNames as al, type FFIDAnnouncementListProps as am, type FFIDLoginButtonProps as an, type FFIDOrganizationSwitcherClassNames as ao, type FFIDOrganizationSwitcherProps as ap, type FFIDSubscriptionBadgeClassNames as aq, type FFIDSubscriptionBadgeProps as ar, type FFIDUserMenuClassNames as as, type FFIDUserMenuProps as at, type FFIDApiResponse as b, type FFIDSessionResponse as c, type FFIDRedirectResult as d, type FFIDError as e, type FFIDSubscriptionCheckResponse as f, type FFIDListMembersResponse as g, type FFIDMemberRole as h, type FFIDUpdateMemberRoleResponse as i, type FFIDRemoveMemberResponse as j, type FFIDProfileCallOptions as k, type FFIDUserProfile as l, type FFIDUpdateUserProfileRequest as m, type FFIDCreateCheckoutParams as n, type FFIDCheckoutSessionResponse as o, type FFIDCreatePortalParams as p, type FFIDPortalSessionResponse as q, type FFIDVerifyAccessTokenOptions as r, type FFIDOAuthUserInfo as s, type FFIDInquiryCreateParams as t, type FFIDInquiryCreateResponse as u, type FFIDAuthMode as v, type FFIDLogger as w, type FFIDCacheAdapter as x, type FFIDUser as y, type FFIDOrganization as z };
1304
+ export { type FFIDInquiryFormSubmitData as $, type FFIDSubscription as A, type FFIDSubscriptionContextValue as B, type FFIDAnnouncementsClientConfig as C, type FFIDAnnouncementsApiResponse as D, type AnnouncementListResponse as E, type FFIDSubscriptionStatus as F, type FFIDAnnouncementsLogger as G, type Announcement as H, type AnnouncementStatus as I, type AnnouncementType as J, FFIDAnnouncementBadge as K, type ListAnnouncementsOptions as L, FFIDAnnouncementList as M, type FFIDAnnouncementsError as N, type FFIDAnnouncementsErrorCode as O, type FFIDAnnouncementsServerResponse as P, type FFIDCacheConfig as Q, type FFIDContextValue as R, type FFIDInquiryCategory as S, type FFIDInquiryCategorySite2026 as T, FFIDInquiryForm as U, type FFIDInquiryFormCategoryItem as V, type FFIDInquiryFormClassNames as W, type FFIDInquiryFormOrganization as X, type FFIDInquiryFormPlaceholderContext as Y, type FFIDInquiryFormPrefill as Z, type FFIDInquiryFormProps as _, type FFIDConfig as a, type FFIDInquiryFormSubmitResult as a0, type FFIDJwtClaims as a1, FFIDLoginButton as a2, type FFIDMemberStatus as a3, type FFIDOAuthTokenResponse as a4, type FFIDOAuthUserInfoMemberRole as a5, type FFIDOAuthUserInfoSubscription as a6, type FFIDOrganizationMember as a7, FFIDOrganizationSwitcher as a8, type FFIDRedirectErrorCode as a9, type FFIDSeatModel as aa, FFIDSubscriptionBadge as ab, type FFIDTokenIntrospectionResponse as ac, FFIDUserMenu as ad, FFID_INQUIRY_CATEGORIES as ae, FFID_INQUIRY_CATEGORIES_SITE_2026 as af, type UseFFIDAnnouncementsOptions as ag, type UseFFIDAnnouncementsReturn as ah, isFFIDInquiryCategorySite2026 as ai, useFFIDAnnouncements as aj, type FFIDAnnouncementBadgeClassNames as ak, type FFIDAnnouncementBadgeProps as al, type FFIDAnnouncementListClassNames as am, type FFIDAnnouncementListProps as an, type FFIDLoginButtonProps as ao, type FFIDOrganizationSwitcherClassNames as ap, type FFIDOrganizationSwitcherProps as aq, type FFIDSubscriptionBadgeClassNames as ar, type FFIDSubscriptionBadgeProps as as, type FFIDUserMenuClassNames as at, type FFIDUserMenuProps as au, type FFIDApiResponse as b, type FFIDSessionResponse as c, type FFIDRedirectResult as d, type FFIDError as e, type FFIDSubscriptionCheckResponse as f, type FFIDListMembersResponse as g, type FFIDMemberRole as h, type FFIDUpdateMemberRoleResponse as i, type FFIDRemoveMemberResponse as j, type FFIDProfileCallOptions as k, type FFIDUserProfile as l, type FFIDUpdateUserProfileRequest as m, type FFIDCreateCheckoutParams as n, type FFIDCheckoutSessionResponse as o, type FFIDCreatePortalParams as p, type FFIDPortalSessionResponse as q, type FFIDVerifyAccessTokenOptions as r, type FFIDOAuthUserInfo as s, type FFIDInquiryCreateParams as t, type FFIDInquiryCreateResponse as u, type FFIDAuthMode as v, type FFIDLogger as w, type FFIDCacheAdapter as x, type FFIDUser as y, type FFIDOrganization as z };
@@ -537,17 +537,36 @@ interface FFIDUpdateUserProfileRequest {
537
537
  /** Arbitrary user preferences bag (null clears the column; reads return {}) */
538
538
  preferences?: Record<string, unknown> | null;
539
539
  }
540
+ /**
541
+ * Discriminant for redirect failures that callers need to handle
542
+ * programmatically (vs. logging the human-readable `error` string).
543
+ *
544
+ * - `'redirect_loop_detected'`: `redirectToAuthorize()` detected that the
545
+ * same authorize URL (keyed by `baseUrl + client_id + organization_id`)
546
+ * has been fired **3 times within 60 seconds**. Caller should surface a
547
+ * manual "再度ログインする" UI rather than retry automatically (#2406 / #2411).
548
+ *
549
+ * SDK 2.18.0 only ships `'redirect_loop_detected'`. Other failure paths
550
+ * (SSR environment, PKCE generation failure, empty organizationId) currently
551
+ * return `error` without a `code`. New codes will be added in future minor
552
+ * versions — treat this union as forward-extensible and do NOT exhaustively
553
+ * `switch` over it without a `default` branch for consumer code that must
554
+ * stay compatible across SDK upgrades.
555
+ */
556
+ type FFIDRedirectErrorCode = 'redirect_loop_detected';
540
557
  /**
541
558
  * Result of a redirect operation (redirectToLogin / redirectToAuthorize / redirectToLogout)
542
559
  *
543
560
  * Structured return type so callers can inspect failure reasons
544
- * instead of receiving a bare `false`.
561
+ * instead of receiving a bare `false`. When `code` is set, branch on it
562
+ * for programmatic handling; otherwise log `error` for humans.
545
563
  */
546
564
  type FFIDRedirectResult = {
547
565
  success: true;
548
566
  } | {
549
567
  success: false;
550
568
  error: string;
569
+ code?: FFIDRedirectErrorCode;
551
570
  };
552
571
  /**
553
572
  * OAuth 2.0 token response from FFID token endpoint
@@ -1282,4 +1301,4 @@ interface FFIDInquiryFormPlaceholderContext {
1282
1301
  }
1283
1302
  declare function FFIDInquiryForm({ mode, prefill, organizations, preselectedOrganizationId, categories, termsVersion, privacyVersion, termsHref, privacyHref, turnstileToken, turnstileSlot, onSubmit, onChange, separateLegalCheckboxes, messagePlaceholder, requireCategorySelection, unstyled, classNames, locale, className, }: FFIDInquiryFormProps): react_jsx_runtime.JSX.Element;
1284
1303
 
1285
- export { type FFIDInquiryFormSubmitData as $, type FFIDSubscription as A, type FFIDSubscriptionContextValue as B, type FFIDAnnouncementsClientConfig as C, type FFIDAnnouncementsApiResponse as D, type AnnouncementListResponse as E, type FFIDSubscriptionStatus as F, type FFIDAnnouncementsLogger as G, type Announcement as H, type AnnouncementStatus as I, type AnnouncementType as J, FFIDAnnouncementBadge as K, type ListAnnouncementsOptions as L, FFIDAnnouncementList as M, type FFIDAnnouncementsError as N, type FFIDAnnouncementsErrorCode as O, type FFIDAnnouncementsServerResponse as P, type FFIDCacheConfig as Q, type FFIDContextValue as R, type FFIDInquiryCategory as S, type FFIDInquiryCategorySite2026 as T, FFIDInquiryForm as U, type FFIDInquiryFormCategoryItem as V, type FFIDInquiryFormClassNames as W, type FFIDInquiryFormOrganization as X, type FFIDInquiryFormPlaceholderContext as Y, type FFIDInquiryFormPrefill as Z, type FFIDInquiryFormProps as _, type FFIDConfig as a, type FFIDInquiryFormSubmitResult as a0, type FFIDJwtClaims as a1, FFIDLoginButton as a2, type FFIDMemberStatus as a3, type FFIDOAuthTokenResponse as a4, type FFIDOAuthUserInfoMemberRole as a5, type FFIDOAuthUserInfoSubscription as a6, type FFIDOrganizationMember as a7, FFIDOrganizationSwitcher as a8, type FFIDSeatModel as a9, FFIDSubscriptionBadge as aa, type FFIDTokenIntrospectionResponse as ab, FFIDUserMenu as ac, FFID_INQUIRY_CATEGORIES as ad, FFID_INQUIRY_CATEGORIES_SITE_2026 as ae, type UseFFIDAnnouncementsOptions as af, type UseFFIDAnnouncementsReturn as ag, isFFIDInquiryCategorySite2026 as ah, useFFIDAnnouncements as ai, type FFIDAnnouncementBadgeClassNames as aj, type FFIDAnnouncementBadgeProps as ak, type FFIDAnnouncementListClassNames as al, type FFIDAnnouncementListProps as am, type FFIDLoginButtonProps as an, type FFIDOrganizationSwitcherClassNames as ao, type FFIDOrganizationSwitcherProps as ap, type FFIDSubscriptionBadgeClassNames as aq, type FFIDSubscriptionBadgeProps as ar, type FFIDUserMenuClassNames as as, type FFIDUserMenuProps as at, type FFIDApiResponse as b, type FFIDSessionResponse as c, type FFIDRedirectResult as d, type FFIDError as e, type FFIDSubscriptionCheckResponse as f, type FFIDListMembersResponse as g, type FFIDMemberRole as h, type FFIDUpdateMemberRoleResponse as i, type FFIDRemoveMemberResponse as j, type FFIDProfileCallOptions as k, type FFIDUserProfile as l, type FFIDUpdateUserProfileRequest as m, type FFIDCreateCheckoutParams as n, type FFIDCheckoutSessionResponse as o, type FFIDCreatePortalParams as p, type FFIDPortalSessionResponse as q, type FFIDVerifyAccessTokenOptions as r, type FFIDOAuthUserInfo as s, type FFIDInquiryCreateParams as t, type FFIDInquiryCreateResponse as u, type FFIDAuthMode as v, type FFIDLogger as w, type FFIDCacheAdapter as x, type FFIDUser as y, type FFIDOrganization as z };
1304
+ export { type FFIDInquiryFormSubmitData as $, type FFIDSubscription as A, type FFIDSubscriptionContextValue as B, type FFIDAnnouncementsClientConfig as C, type FFIDAnnouncementsApiResponse as D, type AnnouncementListResponse as E, type FFIDSubscriptionStatus as F, type FFIDAnnouncementsLogger as G, type Announcement as H, type AnnouncementStatus as I, type AnnouncementType as J, FFIDAnnouncementBadge as K, type ListAnnouncementsOptions as L, FFIDAnnouncementList as M, type FFIDAnnouncementsError as N, type FFIDAnnouncementsErrorCode as O, type FFIDAnnouncementsServerResponse as P, type FFIDCacheConfig as Q, type FFIDContextValue as R, type FFIDInquiryCategory as S, type FFIDInquiryCategorySite2026 as T, FFIDInquiryForm as U, type FFIDInquiryFormCategoryItem as V, type FFIDInquiryFormClassNames as W, type FFIDInquiryFormOrganization as X, type FFIDInquiryFormPlaceholderContext as Y, type FFIDInquiryFormPrefill as Z, type FFIDInquiryFormProps as _, type FFIDConfig as a, type FFIDInquiryFormSubmitResult as a0, type FFIDJwtClaims as a1, FFIDLoginButton as a2, type FFIDMemberStatus as a3, type FFIDOAuthTokenResponse as a4, type FFIDOAuthUserInfoMemberRole as a5, type FFIDOAuthUserInfoSubscription as a6, type FFIDOrganizationMember as a7, FFIDOrganizationSwitcher as a8, type FFIDRedirectErrorCode as a9, type FFIDSeatModel as aa, FFIDSubscriptionBadge as ab, type FFIDTokenIntrospectionResponse as ac, FFIDUserMenu as ad, FFID_INQUIRY_CATEGORIES as ae, FFID_INQUIRY_CATEGORIES_SITE_2026 as af, type UseFFIDAnnouncementsOptions as ag, type UseFFIDAnnouncementsReturn as ah, isFFIDInquiryCategorySite2026 as ai, useFFIDAnnouncements as aj, type FFIDAnnouncementBadgeClassNames as ak, type FFIDAnnouncementBadgeProps as al, type FFIDAnnouncementListClassNames as am, type FFIDAnnouncementListProps as an, type FFIDLoginButtonProps as ao, type FFIDOrganizationSwitcherClassNames as ap, type FFIDOrganizationSwitcherProps as aq, type FFIDSubscriptionBadgeClassNames as ar, type FFIDSubscriptionBadgeProps as as, type FFIDUserMenuClassNames as at, type FFIDUserMenuProps as au, type FFIDApiResponse as b, type FFIDSessionResponse as c, type FFIDRedirectResult as d, type FFIDError as e, type FFIDSubscriptionCheckResponse as f, type FFIDListMembersResponse as g, type FFIDMemberRole as h, type FFIDUpdateMemberRoleResponse as i, type FFIDRemoveMemberResponse as j, type FFIDProfileCallOptions as k, type FFIDUserProfile as l, type FFIDUpdateUserProfileRequest as m, type FFIDCreateCheckoutParams as n, type FFIDCheckoutSessionResponse as o, type FFIDCreatePortalParams as p, type FFIDPortalSessionResponse as q, type FFIDVerifyAccessTokenOptions as r, type FFIDOAuthUserInfo as s, type FFIDInquiryCreateParams as t, type FFIDInquiryCreateResponse as u, type FFIDAuthMode as v, type FFIDLogger as w, type FFIDCacheAdapter as x, type FFIDUser as y, type FFIDOrganization as z };
package/dist/index.cjs CHANGED
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var chunkDERFBYBZ_cjs = require('./chunk-DERFBYBZ.cjs');
3
+ var chunkEK2W67BW_cjs = require('./chunk-EK2W67BW.cjs');
4
4
  var react = require('react');
5
5
  var jsxRuntime = require('react/jsx-runtime');
6
6
 
@@ -46,7 +46,7 @@ function createKVCacheAdapter(kv) {
46
46
  }
47
47
  function withFFIDAuth(Component, options = {}) {
48
48
  const WrappedComponent = (props) => {
49
- const { isLoading, isAuthenticated, login } = chunkDERFBYBZ_cjs.useFFIDContext();
49
+ const { isLoading, isAuthenticated, login } = chunkEK2W67BW_cjs.useFFIDContext();
50
50
  const hasRedirected = react.useRef(false);
51
51
  react.useEffect(() => {
52
52
  if (!isLoading && !isAuthenticated && options.redirectToLogin && !hasRedirected.current) {
@@ -74,107 +74,107 @@ var FFID_NEWSLETTER_TYPES = ["inquiry_followup", "general"];
74
74
 
75
75
  Object.defineProperty(exports, "DEFAULT_API_BASE_URL", {
76
76
  enumerable: true,
77
- get: function () { return chunkDERFBYBZ_cjs.DEFAULT_API_BASE_URL; }
77
+ get: function () { return chunkEK2W67BW_cjs.DEFAULT_API_BASE_URL; }
78
78
  });
79
79
  Object.defineProperty(exports, "FFIDAnnouncementBadge", {
80
80
  enumerable: true,
81
- get: function () { return chunkDERFBYBZ_cjs.FFIDAnnouncementBadge; }
81
+ get: function () { return chunkEK2W67BW_cjs.FFIDAnnouncementBadge; }
82
82
  });
83
83
  Object.defineProperty(exports, "FFIDAnnouncementList", {
84
84
  enumerable: true,
85
- get: function () { return chunkDERFBYBZ_cjs.FFIDAnnouncementList; }
85
+ get: function () { return chunkEK2W67BW_cjs.FFIDAnnouncementList; }
86
86
  });
87
87
  Object.defineProperty(exports, "FFIDInquiryForm", {
88
88
  enumerable: true,
89
- get: function () { return chunkDERFBYBZ_cjs.FFIDInquiryForm; }
89
+ get: function () { return chunkEK2W67BW_cjs.FFIDInquiryForm; }
90
90
  });
91
91
  Object.defineProperty(exports, "FFIDLoginButton", {
92
92
  enumerable: true,
93
- get: function () { return chunkDERFBYBZ_cjs.FFIDLoginButton; }
93
+ get: function () { return chunkEK2W67BW_cjs.FFIDLoginButton; }
94
94
  });
95
95
  Object.defineProperty(exports, "FFIDOrganizationSwitcher", {
96
96
  enumerable: true,
97
- get: function () { return chunkDERFBYBZ_cjs.FFIDOrganizationSwitcher; }
97
+ get: function () { return chunkEK2W67BW_cjs.FFIDOrganizationSwitcher; }
98
98
  });
99
99
  Object.defineProperty(exports, "FFIDProvider", {
100
100
  enumerable: true,
101
- get: function () { return chunkDERFBYBZ_cjs.FFIDProvider; }
101
+ get: function () { return chunkEK2W67BW_cjs.FFIDProvider; }
102
102
  });
103
103
  Object.defineProperty(exports, "FFIDSDKError", {
104
104
  enumerable: true,
105
- get: function () { return chunkDERFBYBZ_cjs.FFIDSDKError; }
105
+ get: function () { return chunkEK2W67BW_cjs.FFIDSDKError; }
106
106
  });
107
107
  Object.defineProperty(exports, "FFIDSubscriptionBadge", {
108
108
  enumerable: true,
109
- get: function () { return chunkDERFBYBZ_cjs.FFIDSubscriptionBadge; }
109
+ get: function () { return chunkEK2W67BW_cjs.FFIDSubscriptionBadge; }
110
110
  });
111
111
  Object.defineProperty(exports, "FFIDUserMenu", {
112
112
  enumerable: true,
113
- get: function () { return chunkDERFBYBZ_cjs.FFIDUserMenu; }
113
+ get: function () { return chunkEK2W67BW_cjs.FFIDUserMenu; }
114
114
  });
115
115
  Object.defineProperty(exports, "FFID_ANNOUNCEMENTS_ERROR_CODES", {
116
116
  enumerable: true,
117
- get: function () { return chunkDERFBYBZ_cjs.FFID_ANNOUNCEMENTS_ERROR_CODES; }
117
+ get: function () { return chunkEK2W67BW_cjs.FFID_ANNOUNCEMENTS_ERROR_CODES; }
118
118
  });
119
119
  Object.defineProperty(exports, "FFID_INQUIRY_CATEGORIES", {
120
120
  enumerable: true,
121
- get: function () { return chunkDERFBYBZ_cjs.FFID_INQUIRY_CATEGORIES; }
121
+ get: function () { return chunkEK2W67BW_cjs.FFID_INQUIRY_CATEGORIES; }
122
122
  });
123
123
  Object.defineProperty(exports, "FFID_INQUIRY_CATEGORIES_SITE_2026", {
124
124
  enumerable: true,
125
- get: function () { return chunkDERFBYBZ_cjs.FFID_INQUIRY_CATEGORIES_SITE_2026; }
125
+ get: function () { return chunkEK2W67BW_cjs.FFID_INQUIRY_CATEGORIES_SITE_2026; }
126
126
  });
127
127
  Object.defineProperty(exports, "createFFIDAnnouncementsClient", {
128
128
  enumerable: true,
129
- get: function () { return chunkDERFBYBZ_cjs.createFFIDAnnouncementsClient; }
129
+ get: function () { return chunkEK2W67BW_cjs.createFFIDAnnouncementsClient; }
130
130
  });
131
131
  Object.defineProperty(exports, "createFFIDClient", {
132
132
  enumerable: true,
133
- get: function () { return chunkDERFBYBZ_cjs.createFFIDClient; }
133
+ get: function () { return chunkEK2W67BW_cjs.createFFIDClient; }
134
134
  });
135
135
  Object.defineProperty(exports, "createTokenStore", {
136
136
  enumerable: true,
137
- get: function () { return chunkDERFBYBZ_cjs.createTokenStore; }
137
+ get: function () { return chunkEK2W67BW_cjs.createTokenStore; }
138
138
  });
139
139
  Object.defineProperty(exports, "generateCodeChallenge", {
140
140
  enumerable: true,
141
- get: function () { return chunkDERFBYBZ_cjs.generateCodeChallenge; }
141
+ get: function () { return chunkEK2W67BW_cjs.generateCodeChallenge; }
142
142
  });
143
143
  Object.defineProperty(exports, "generateCodeVerifier", {
144
144
  enumerable: true,
145
- get: function () { return chunkDERFBYBZ_cjs.generateCodeVerifier; }
145
+ get: function () { return chunkEK2W67BW_cjs.generateCodeVerifier; }
146
146
  });
147
147
  Object.defineProperty(exports, "isFFIDInquiryCategorySite2026", {
148
148
  enumerable: true,
149
- get: function () { return chunkDERFBYBZ_cjs.isFFIDInquiryCategorySite2026; }
149
+ get: function () { return chunkEK2W67BW_cjs.isFFIDInquiryCategorySite2026; }
150
150
  });
151
151
  Object.defineProperty(exports, "normalizeRedirectUri", {
152
152
  enumerable: true,
153
- get: function () { return chunkDERFBYBZ_cjs.normalizeRedirectUri; }
153
+ get: function () { return chunkEK2W67BW_cjs.normalizeRedirectUri; }
154
154
  });
155
155
  Object.defineProperty(exports, "retrieveCodeVerifier", {
156
156
  enumerable: true,
157
- get: function () { return chunkDERFBYBZ_cjs.retrieveCodeVerifier; }
157
+ get: function () { return chunkEK2W67BW_cjs.retrieveCodeVerifier; }
158
158
  });
159
159
  Object.defineProperty(exports, "storeCodeVerifier", {
160
160
  enumerable: true,
161
- get: function () { return chunkDERFBYBZ_cjs.storeCodeVerifier; }
161
+ get: function () { return chunkEK2W67BW_cjs.storeCodeVerifier; }
162
162
  });
163
163
  Object.defineProperty(exports, "useFFID", {
164
164
  enumerable: true,
165
- get: function () { return chunkDERFBYBZ_cjs.useFFID; }
165
+ get: function () { return chunkEK2W67BW_cjs.useFFID; }
166
166
  });
167
167
  Object.defineProperty(exports, "useFFIDAnnouncements", {
168
168
  enumerable: true,
169
- get: function () { return chunkDERFBYBZ_cjs.useFFIDAnnouncements; }
169
+ get: function () { return chunkEK2W67BW_cjs.useFFIDAnnouncements; }
170
170
  });
171
171
  Object.defineProperty(exports, "useSubscription", {
172
172
  enumerable: true,
173
- get: function () { return chunkDERFBYBZ_cjs.useSubscription; }
173
+ get: function () { return chunkEK2W67BW_cjs.useSubscription; }
174
174
  });
175
175
  Object.defineProperty(exports, "withSubscription", {
176
176
  enumerable: true,
177
- get: function () { return chunkDERFBYBZ_cjs.withSubscription; }
177
+ get: function () { return chunkEK2W67BW_cjs.withSubscription; }
178
178
  });
179
179
  exports.FFID_NEWSLETTER_TYPES = FFID_NEWSLETTER_TYPES;
180
180
  exports.createKVCacheAdapter = createKVCacheAdapter;
package/dist/index.d.cts CHANGED
@@ -1,5 +1,5 @@
1
- import { F as FFIDSubscriptionStatus, a as FFIDConfig, b as FFIDApiResponse, c as FFIDSessionResponse, d as FFIDRedirectResult, e as FFIDError, f as FFIDSubscriptionCheckResponse, g as FFIDListMembersResponse, h as FFIDMemberRole, i as FFIDUpdateMemberRoleResponse, j as FFIDRemoveMemberResponse, k as FFIDProfileCallOptions, l as FFIDUserProfile, m as FFIDUpdateUserProfileRequest, n as FFIDCreateCheckoutParams, o as FFIDCheckoutSessionResponse, p as FFIDCreatePortalParams, q as FFIDPortalSessionResponse, r as FFIDVerifyAccessTokenOptions, s as FFIDOAuthUserInfo, t as FFIDInquiryCreateParams, u as FFIDInquiryCreateResponse, v as FFIDAuthMode, w as FFIDLogger, x as FFIDCacheAdapter, y as FFIDUser, z as FFIDOrganization, A as FFIDSubscription, B as FFIDSubscriptionContextValue, C as FFIDAnnouncementsClientConfig, L as ListAnnouncementsOptions, D as FFIDAnnouncementsApiResponse, E as AnnouncementListResponse, G as FFIDAnnouncementsLogger } from './index-Cv1qXIl1.cjs';
2
- export { H as Announcement, I as AnnouncementStatus, J as AnnouncementType, K as FFIDAnnouncementBadge, M as FFIDAnnouncementList, N as FFIDAnnouncementsError, O as FFIDAnnouncementsErrorCode, P as FFIDAnnouncementsServerResponse, Q as FFIDCacheConfig, R as FFIDContextValue, S as FFIDInquiryCategory, T as FFIDInquiryCategorySite2026, U as FFIDInquiryForm, V as FFIDInquiryFormCategoryItem, W as FFIDInquiryFormClassNames, X as FFIDInquiryFormOrganization, Y as FFIDInquiryFormPlaceholderContext, Z as FFIDInquiryFormPrefill, _ as FFIDInquiryFormProps, $ as FFIDInquiryFormSubmitData, a0 as FFIDInquiryFormSubmitResult, a1 as FFIDJwtClaims, a2 as FFIDLoginButton, a3 as FFIDMemberStatus, a4 as FFIDOAuthTokenResponse, a5 as FFIDOAuthUserInfoMemberRole, a6 as FFIDOAuthUserInfoSubscription, a7 as FFIDOrganizationMember, a8 as FFIDOrganizationSwitcher, a9 as FFIDSeatModel, aa as FFIDSubscriptionBadge, ab as FFIDTokenIntrospectionResponse, ac as FFIDUserMenu, ad as FFID_INQUIRY_CATEGORIES, ae as FFID_INQUIRY_CATEGORIES_SITE_2026, af as UseFFIDAnnouncementsOptions, ag as UseFFIDAnnouncementsReturn, ah as isFFIDInquiryCategorySite2026, ai as useFFIDAnnouncements } from './index-Cv1qXIl1.cjs';
1
+ import { F as FFIDSubscriptionStatus, a as FFIDConfig, b as FFIDApiResponse, c as FFIDSessionResponse, d as FFIDRedirectResult, e as FFIDError, f as FFIDSubscriptionCheckResponse, g as FFIDListMembersResponse, h as FFIDMemberRole, i as FFIDUpdateMemberRoleResponse, j as FFIDRemoveMemberResponse, k as FFIDProfileCallOptions, l as FFIDUserProfile, m as FFIDUpdateUserProfileRequest, n as FFIDCreateCheckoutParams, o as FFIDCheckoutSessionResponse, p as FFIDCreatePortalParams, q as FFIDPortalSessionResponse, r as FFIDVerifyAccessTokenOptions, s as FFIDOAuthUserInfo, t as FFIDInquiryCreateParams, u as FFIDInquiryCreateResponse, v as FFIDAuthMode, w as FFIDLogger, x as FFIDCacheAdapter, y as FFIDUser, z as FFIDOrganization, A as FFIDSubscription, B as FFIDSubscriptionContextValue, C as FFIDAnnouncementsClientConfig, L as ListAnnouncementsOptions, D as FFIDAnnouncementsApiResponse, E as AnnouncementListResponse, G as FFIDAnnouncementsLogger } from './index--S6rLHjr.cjs';
2
+ export { H as Announcement, I as AnnouncementStatus, J as AnnouncementType, K as FFIDAnnouncementBadge, M as FFIDAnnouncementList, N as FFIDAnnouncementsError, O as FFIDAnnouncementsErrorCode, P as FFIDAnnouncementsServerResponse, Q as FFIDCacheConfig, R as FFIDContextValue, S as FFIDInquiryCategory, T as FFIDInquiryCategorySite2026, U as FFIDInquiryForm, V as FFIDInquiryFormCategoryItem, W as FFIDInquiryFormClassNames, X as FFIDInquiryFormOrganization, Y as FFIDInquiryFormPlaceholderContext, Z as FFIDInquiryFormPrefill, _ as FFIDInquiryFormProps, $ as FFIDInquiryFormSubmitData, a0 as FFIDInquiryFormSubmitResult, a1 as FFIDJwtClaims, a2 as FFIDLoginButton, a3 as FFIDMemberStatus, a4 as FFIDOAuthTokenResponse, a5 as FFIDOAuthUserInfoMemberRole, a6 as FFIDOAuthUserInfoSubscription, a7 as FFIDOrganizationMember, a8 as FFIDOrganizationSwitcher, a9 as FFIDRedirectErrorCode, aa as FFIDSeatModel, ab as FFIDSubscriptionBadge, ac as FFIDTokenIntrospectionResponse, ad as FFIDUserMenu, ae as FFID_INQUIRY_CATEGORIES, af as FFID_INQUIRY_CATEGORIES_SITE_2026, ag as UseFFIDAnnouncementsOptions, ah as UseFFIDAnnouncementsReturn, ai as isFFIDInquiryCategorySite2026, aj as useFFIDAnnouncements } from './index--S6rLHjr.cjs';
3
3
  import * as react_jsx_runtime from 'react/jsx-runtime';
4
4
  import { ReactNode, ComponentType, FC } from 'react';
5
5
 
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { F as FFIDSubscriptionStatus, a as FFIDConfig, b as FFIDApiResponse, c as FFIDSessionResponse, d as FFIDRedirectResult, e as FFIDError, f as FFIDSubscriptionCheckResponse, g as FFIDListMembersResponse, h as FFIDMemberRole, i as FFIDUpdateMemberRoleResponse, j as FFIDRemoveMemberResponse, k as FFIDProfileCallOptions, l as FFIDUserProfile, m as FFIDUpdateUserProfileRequest, n as FFIDCreateCheckoutParams, o as FFIDCheckoutSessionResponse, p as FFIDCreatePortalParams, q as FFIDPortalSessionResponse, r as FFIDVerifyAccessTokenOptions, s as FFIDOAuthUserInfo, t as FFIDInquiryCreateParams, u as FFIDInquiryCreateResponse, v as FFIDAuthMode, w as FFIDLogger, x as FFIDCacheAdapter, y as FFIDUser, z as FFIDOrganization, A as FFIDSubscription, B as FFIDSubscriptionContextValue, C as FFIDAnnouncementsClientConfig, L as ListAnnouncementsOptions, D as FFIDAnnouncementsApiResponse, E as AnnouncementListResponse, G as FFIDAnnouncementsLogger } from './index-Cv1qXIl1.js';
2
- export { H as Announcement, I as AnnouncementStatus, J as AnnouncementType, K as FFIDAnnouncementBadge, M as FFIDAnnouncementList, N as FFIDAnnouncementsError, O as FFIDAnnouncementsErrorCode, P as FFIDAnnouncementsServerResponse, Q as FFIDCacheConfig, R as FFIDContextValue, S as FFIDInquiryCategory, T as FFIDInquiryCategorySite2026, U as FFIDInquiryForm, V as FFIDInquiryFormCategoryItem, W as FFIDInquiryFormClassNames, X as FFIDInquiryFormOrganization, Y as FFIDInquiryFormPlaceholderContext, Z as FFIDInquiryFormPrefill, _ as FFIDInquiryFormProps, $ as FFIDInquiryFormSubmitData, a0 as FFIDInquiryFormSubmitResult, a1 as FFIDJwtClaims, a2 as FFIDLoginButton, a3 as FFIDMemberStatus, a4 as FFIDOAuthTokenResponse, a5 as FFIDOAuthUserInfoMemberRole, a6 as FFIDOAuthUserInfoSubscription, a7 as FFIDOrganizationMember, a8 as FFIDOrganizationSwitcher, a9 as FFIDSeatModel, aa as FFIDSubscriptionBadge, ab as FFIDTokenIntrospectionResponse, ac as FFIDUserMenu, ad as FFID_INQUIRY_CATEGORIES, ae as FFID_INQUIRY_CATEGORIES_SITE_2026, af as UseFFIDAnnouncementsOptions, ag as UseFFIDAnnouncementsReturn, ah as isFFIDInquiryCategorySite2026, ai as useFFIDAnnouncements } from './index-Cv1qXIl1.js';
1
+ import { F as FFIDSubscriptionStatus, a as FFIDConfig, b as FFIDApiResponse, c as FFIDSessionResponse, d as FFIDRedirectResult, e as FFIDError, f as FFIDSubscriptionCheckResponse, g as FFIDListMembersResponse, h as FFIDMemberRole, i as FFIDUpdateMemberRoleResponse, j as FFIDRemoveMemberResponse, k as FFIDProfileCallOptions, l as FFIDUserProfile, m as FFIDUpdateUserProfileRequest, n as FFIDCreateCheckoutParams, o as FFIDCheckoutSessionResponse, p as FFIDCreatePortalParams, q as FFIDPortalSessionResponse, r as FFIDVerifyAccessTokenOptions, s as FFIDOAuthUserInfo, t as FFIDInquiryCreateParams, u as FFIDInquiryCreateResponse, v as FFIDAuthMode, w as FFIDLogger, x as FFIDCacheAdapter, y as FFIDUser, z as FFIDOrganization, A as FFIDSubscription, B as FFIDSubscriptionContextValue, C as FFIDAnnouncementsClientConfig, L as ListAnnouncementsOptions, D as FFIDAnnouncementsApiResponse, E as AnnouncementListResponse, G as FFIDAnnouncementsLogger } from './index--S6rLHjr.js';
2
+ export { H as Announcement, I as AnnouncementStatus, J as AnnouncementType, K as FFIDAnnouncementBadge, M as FFIDAnnouncementList, N as FFIDAnnouncementsError, O as FFIDAnnouncementsErrorCode, P as FFIDAnnouncementsServerResponse, Q as FFIDCacheConfig, R as FFIDContextValue, S as FFIDInquiryCategory, T as FFIDInquiryCategorySite2026, U as FFIDInquiryForm, V as FFIDInquiryFormCategoryItem, W as FFIDInquiryFormClassNames, X as FFIDInquiryFormOrganization, Y as FFIDInquiryFormPlaceholderContext, Z as FFIDInquiryFormPrefill, _ as FFIDInquiryFormProps, $ as FFIDInquiryFormSubmitData, a0 as FFIDInquiryFormSubmitResult, a1 as FFIDJwtClaims, a2 as FFIDLoginButton, a3 as FFIDMemberStatus, a4 as FFIDOAuthTokenResponse, a5 as FFIDOAuthUserInfoMemberRole, a6 as FFIDOAuthUserInfoSubscription, a7 as FFIDOrganizationMember, a8 as FFIDOrganizationSwitcher, a9 as FFIDRedirectErrorCode, aa as FFIDSeatModel, ab as FFIDSubscriptionBadge, ac as FFIDTokenIntrospectionResponse, ad as FFIDUserMenu, ae as FFID_INQUIRY_CATEGORIES, af as FFID_INQUIRY_CATEGORIES_SITE_2026, ag as UseFFIDAnnouncementsOptions, ah as UseFFIDAnnouncementsReturn, ai as isFFIDInquiryCategorySite2026, aj as useFFIDAnnouncements } from './index--S6rLHjr.js';
3
3
  import * as react_jsx_runtime from 'react/jsx-runtime';
4
4
  import { ReactNode, ComponentType, FC } from 'react';
5
5
 
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
- import { useFFIDContext } from './chunk-FGTRPNSW.js';
2
- export { DEFAULT_API_BASE_URL, FFIDAnnouncementBadge, FFIDAnnouncementList, FFIDInquiryForm, FFIDLoginButton, FFIDOrganizationSwitcher, FFIDProvider, FFIDSDKError, FFIDSubscriptionBadge, FFIDUserMenu, FFID_ANNOUNCEMENTS_ERROR_CODES, FFID_INQUIRY_CATEGORIES, FFID_INQUIRY_CATEGORIES_SITE_2026, createFFIDAnnouncementsClient, createFFIDClient, createTokenStore, generateCodeChallenge, generateCodeVerifier, isFFIDInquiryCategorySite2026, normalizeRedirectUri, retrieveCodeVerifier, storeCodeVerifier, useFFID, useFFIDAnnouncements, useSubscription, withSubscription } from './chunk-FGTRPNSW.js';
1
+ import { useFFIDContext } from './chunk-IEYXT3LA.js';
2
+ export { DEFAULT_API_BASE_URL, FFIDAnnouncementBadge, FFIDAnnouncementList, FFIDInquiryForm, FFIDLoginButton, FFIDOrganizationSwitcher, FFIDProvider, FFIDSDKError, FFIDSubscriptionBadge, FFIDUserMenu, FFID_ANNOUNCEMENTS_ERROR_CODES, FFID_INQUIRY_CATEGORIES, FFID_INQUIRY_CATEGORIES_SITE_2026, createFFIDAnnouncementsClient, createFFIDClient, createTokenStore, generateCodeChallenge, generateCodeVerifier, isFFIDInquiryCategorySite2026, normalizeRedirectUri, retrieveCodeVerifier, storeCodeVerifier, useFFID, useFFIDAnnouncements, useSubscription, withSubscription } from './chunk-IEYXT3LA.js';
3
3
  import { useRef, useEffect } from 'react';
4
4
  import { jsx, Fragment } from 'react/jsx-runtime';
5
5
 
@@ -101,10 +101,7 @@ function createTokenStore(storageType) {
101
101
  // src/client/oauth-userinfo.ts
102
102
  var SESSION_ELIGIBLE_SUBSCRIPTION_STATUSES = [
103
103
  "trialing",
104
- "active",
105
- "past_due",
106
- "canceled",
107
- "paused"
104
+ "active"
108
105
  ];
109
106
  function isSessionEligibleSubscriptionStatus(value) {
110
107
  return typeof value === "string" && SESSION_ELIGIBLE_SUBSCRIPTION_STATUSES.includes(value);
@@ -806,7 +803,7 @@ function createProfileMethods(deps) {
806
803
  }
807
804
 
808
805
  // src/client/version-check.ts
809
- var SDK_VERSION = "2.17.1";
806
+ var SDK_VERSION = "2.18.0";
810
807
  var SDK_USER_AGENT = `FFID-SDK/${SDK_VERSION} (TypeScript)`;
811
808
  var SDK_VERSION_HEADER = "X-FFID-SDK-Version";
812
809
  function sdkHeaders() {
@@ -1303,6 +1300,19 @@ function storeCodeVerifier(verifier, logger) {
1303
1300
  }
1304
1301
  return sessionStored || localStored;
1305
1302
  }
1303
+ function cleanupVerifierStorage(logger) {
1304
+ try {
1305
+ window.sessionStorage.removeItem(VERIFIER_STORAGE_KEY);
1306
+ } catch (error) {
1307
+ logger?.warn("retrieveCodeVerifier: sessionStorage \u306E\u30AF\u30EA\u30FC\u30F3\u30A2\u30C3\u30D7\u306B\u5931\u6557\u3057\u307E\u3057\u305F:", error);
1308
+ }
1309
+ try {
1310
+ window.localStorage.removeItem(VERIFIER_FALLBACK_STORAGE_KEY);
1311
+ window.localStorage.removeItem(VERIFIER_FALLBACK_TIMESTAMP_KEY);
1312
+ } catch (error) {
1313
+ logger?.warn("retrieveCodeVerifier: localStorage \u306E\u30AF\u30EA\u30FC\u30F3\u30A2\u30C3\u30D7\u306B\u5931\u6557\u3057\u307E\u3057\u305F:", error);
1314
+ }
1315
+ }
1306
1316
  function base64UrlEncode(buffer) {
1307
1317
  const bytes = new Uint8Array(buffer);
1308
1318
  let binary = "";
@@ -1317,6 +1327,87 @@ var OAUTH_AUTHORIZE_ENDPOINT = "/api/v1/oauth/authorize";
1317
1327
  var AUTH_LOGOUT_ENDPOINT = "/api/v1/auth/logout";
1318
1328
  var STATE_RANDOM_BYTES = 16;
1319
1329
  var HEX_BASE2 = 16;
1330
+ var REDIRECT_LOOP_KEY = "ffid_sdk_redirect_loop_history";
1331
+ var REDIRECT_LOOP_WINDOW_MS = 6e4;
1332
+ var REDIRECT_LOOP_THRESHOLD = 3;
1333
+ var storageReadFailureLogged = false;
1334
+ var storageWriteFailureLogged = false;
1335
+ function logStorageReadFailure(logger, err) {
1336
+ if (storageReadFailureLogged) return;
1337
+ storageReadFailureLogged = true;
1338
+ logger.warn(
1339
+ "[FFID SDK] sessionStorage read failed \u2014 redirect loop detection disabled on this browser (fail-open). iOS WebKit private mode / eviction \u304C\u5178\u578B\u8981\u56E0",
1340
+ err
1341
+ );
1342
+ }
1343
+ function logStorageWriteFailure(logger, err) {
1344
+ if (storageWriteFailureLogged) return;
1345
+ storageWriteFailureLogged = true;
1346
+ logger.warn(
1347
+ "[FFID SDK] sessionStorage write failed \u2014 redirect loop detection disabled on this browser (fail-open).",
1348
+ err
1349
+ );
1350
+ }
1351
+ function isRedirectLoopHistory(value) {
1352
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
1353
+ return Object.values(value).every(
1354
+ (arr) => Array.isArray(arr) && arr.every((t) => typeof t === "number" && Number.isFinite(t))
1355
+ );
1356
+ }
1357
+ function readRedirectLoopHistory(logger) {
1358
+ if (typeof window === "undefined") return {};
1359
+ try {
1360
+ const raw = window.sessionStorage.getItem(REDIRECT_LOOP_KEY);
1361
+ if (raw === null) return {};
1362
+ const parsed = JSON.parse(raw);
1363
+ return isRedirectLoopHistory(parsed) ? parsed : {};
1364
+ } catch (err) {
1365
+ logStorageReadFailure(logger, err);
1366
+ return {};
1367
+ }
1368
+ }
1369
+ function writeRedirectLoopHistory(history, logger) {
1370
+ if (typeof window === "undefined") return;
1371
+ try {
1372
+ window.sessionStorage.setItem(REDIRECT_LOOP_KEY, JSON.stringify(history));
1373
+ } catch (err) {
1374
+ logStorageWriteFailure(logger, err);
1375
+ }
1376
+ }
1377
+ function pruneRedirectLoopHistory(history, now) {
1378
+ const cutoff = now - REDIRECT_LOOP_WINDOW_MS;
1379
+ const pruned = {};
1380
+ for (const [key, timestamps] of Object.entries(history)) {
1381
+ const fresh = timestamps.filter((t) => t > cutoff);
1382
+ if (fresh.length > 0) pruned[key] = fresh;
1383
+ }
1384
+ return pruned;
1385
+ }
1386
+ function getRecentRedirectCount(authorizeKey, now, logger) {
1387
+ const pruned = pruneRedirectLoopHistory(readRedirectLoopHistory(logger), now);
1388
+ writeRedirectLoopHistory(pruned, logger);
1389
+ return pruned[authorizeKey]?.length ?? 0;
1390
+ }
1391
+ function recordRedirectAttempt(authorizeKey, now, logger) {
1392
+ const history = readRedirectLoopHistory(logger);
1393
+ const existing = history[authorizeKey] ?? [];
1394
+ history[authorizeKey] = [...existing, now];
1395
+ writeRedirectLoopHistory(history, logger);
1396
+ }
1397
+ function rollbackLastRedirectAttempt(authorizeKey, logger) {
1398
+ const history = readRedirectLoopHistory(logger);
1399
+ const existing = history[authorizeKey];
1400
+ if (!Array.isArray(existing) || existing.length === 0) return;
1401
+ history[authorizeKey] = existing.slice(0, -1);
1402
+ if (history[authorizeKey].length === 0) delete history[authorizeKey];
1403
+ writeRedirectLoopHistory(history, logger);
1404
+ }
1405
+ function buildAuthorizeKey(baseUrl, clientId, organizationId) {
1406
+ const params = new URLSearchParams({ client_id: clientId });
1407
+ const trimmedOrg = organizationId?.trim();
1408
+ if (trimmedOrg) params.set("organization_id", trimmedOrg);
1409
+ return `${baseUrl}${OAUTH_AUTHORIZE_ENDPOINT}?${params.toString()}`;
1410
+ }
1320
1411
  function generateRandomState() {
1321
1412
  const array = new Uint8Array(STATE_RANDOM_BYTES);
1322
1413
  crypto.getRandomValues(array);
@@ -1336,6 +1427,23 @@ function createRedirectMethods(deps) {
1336
1427
  logger.warn("SSR \u74B0\u5883\u3067\u306F\u30EA\u30C0\u30A4\u30EC\u30AF\u30C8\u3067\u304D\u307E\u305B\u3093");
1337
1428
  return { success: false, error: "SSR \u74B0\u5883\u3067\u306F\u30EA\u30C0\u30A4\u30EC\u30AF\u30C8\u3067\u304D\u307E\u305B\u3093" };
1338
1429
  }
1430
+ const authorizeKey = buildAuthorizeKey(baseUrl, clientId, options?.organizationId);
1431
+ const now = Date.now();
1432
+ const recentCount = getRecentRedirectCount(authorizeKey, now, logger);
1433
+ if (recentCount >= REDIRECT_LOOP_THRESHOLD) {
1434
+ cleanupVerifierStorage(logger);
1435
+ logger.warn("[FFID SDK] redirect loop detected \u2014 \u81EA\u52D5\u30EA\u30C0\u30A4\u30EC\u30AF\u30C8\u3092\u505C\u6B62\u3057\u307E\u3057\u305F", {
1436
+ authorizeKey,
1437
+ recentCount,
1438
+ windowMs: REDIRECT_LOOP_WINDOW_MS,
1439
+ threshold: REDIRECT_LOOP_THRESHOLD
1440
+ });
1441
+ return {
1442
+ success: false,
1443
+ code: "redirect_loop_detected",
1444
+ error: "\u77ED\u6642\u9593\u306B\u540C\u3058\u8A8D\u53EF\u30A8\u30F3\u30C9\u30DD\u30A4\u30F3\u30C8\u3078\u306E\u30EA\u30C0\u30A4\u30EC\u30AF\u30C8\u304C\u7E70\u308A\u8FD4\u3055\u308C\u305F\u305F\u3081\u3001\u30EB\u30FC\u30D7\u691C\u51FA\u306B\u3088\u308A\u81EA\u52D5\u30EA\u30C0\u30A4\u30EC\u30AF\u30C8\u3092\u505C\u6B62\u3057\u307E\u3057\u305F"
1445
+ };
1446
+ }
1339
1447
  const verifier = generateCodeVerifier();
1340
1448
  storeCodeVerifier(verifier, logger);
1341
1449
  let challenge;
@@ -1366,7 +1474,13 @@ function createRedirectMethods(deps) {
1366
1474
  }
1367
1475
  const authorizeUrl = `${baseUrl}${OAUTH_AUTHORIZE_ENDPOINT}?${params.toString()}`;
1368
1476
  logger.debug("Redirecting to authorize:", authorizeUrl);
1369
- window.location.href = authorizeUrl;
1477
+ recordRedirectAttempt(authorizeKey, now, logger);
1478
+ try {
1479
+ window.location.href = authorizeUrl;
1480
+ } catch (err) {
1481
+ rollbackLastRedirectAttempt(authorizeKey, logger);
1482
+ throw err;
1483
+ }
1370
1484
  return { success: true };
1371
1485
  }
1372
1486
  async function redirectToLogin() {
@@ -891,17 +891,36 @@ interface FFIDUpdateUserProfileRequest {
891
891
  /** Arbitrary user preferences bag (null clears the column; reads return {}) */
892
892
  preferences?: Record<string, unknown> | null;
893
893
  }
894
+ /**
895
+ * Discriminant for redirect failures that callers need to handle
896
+ * programmatically (vs. logging the human-readable `error` string).
897
+ *
898
+ * - `'redirect_loop_detected'`: `redirectToAuthorize()` detected that the
899
+ * same authorize URL (keyed by `baseUrl + client_id + organization_id`)
900
+ * has been fired **3 times within 60 seconds**. Caller should surface a
901
+ * manual "再度ログインする" UI rather than retry automatically (#2406 / #2411).
902
+ *
903
+ * SDK 2.18.0 only ships `'redirect_loop_detected'`. Other failure paths
904
+ * (SSR environment, PKCE generation failure, empty organizationId) currently
905
+ * return `error` without a `code`. New codes will be added in future minor
906
+ * versions — treat this union as forward-extensible and do NOT exhaustively
907
+ * `switch` over it without a `default` branch for consumer code that must
908
+ * stay compatible across SDK upgrades.
909
+ */
910
+ type FFIDRedirectErrorCode = 'redirect_loop_detected';
894
911
  /**
895
912
  * Result of a redirect operation (redirectToLogin / redirectToAuthorize / redirectToLogout)
896
913
  *
897
914
  * Structured return type so callers can inspect failure reasons
898
- * instead of receiving a bare `false`.
915
+ * instead of receiving a bare `false`. When `code` is set, branch on it
916
+ * for programmatic handling; otherwise log `error` for humans.
899
917
  */
900
918
  type FFIDRedirectResult = {
901
919
  success: true;
902
920
  } | {
903
921
  success: false;
904
922
  error: string;
923
+ code?: FFIDRedirectErrorCode;
905
924
  };
906
925
 
907
926
  /** OTP / magic link methods - sendOtp / verifyOtp */
@@ -891,17 +891,36 @@ interface FFIDUpdateUserProfileRequest {
891
891
  /** Arbitrary user preferences bag (null clears the column; reads return {}) */
892
892
  preferences?: Record<string, unknown> | null;
893
893
  }
894
+ /**
895
+ * Discriminant for redirect failures that callers need to handle
896
+ * programmatically (vs. logging the human-readable `error` string).
897
+ *
898
+ * - `'redirect_loop_detected'`: `redirectToAuthorize()` detected that the
899
+ * same authorize URL (keyed by `baseUrl + client_id + organization_id`)
900
+ * has been fired **3 times within 60 seconds**. Caller should surface a
901
+ * manual "再度ログインする" UI rather than retry automatically (#2406 / #2411).
902
+ *
903
+ * SDK 2.18.0 only ships `'redirect_loop_detected'`. Other failure paths
904
+ * (SSR environment, PKCE generation failure, empty organizationId) currently
905
+ * return `error` without a `code`. New codes will be added in future minor
906
+ * versions — treat this union as forward-extensible and do NOT exhaustively
907
+ * `switch` over it without a `default` branch for consumer code that must
908
+ * stay compatible across SDK upgrades.
909
+ */
910
+ type FFIDRedirectErrorCode = 'redirect_loop_detected';
894
911
  /**
895
912
  * Result of a redirect operation (redirectToLogin / redirectToAuthorize / redirectToLogout)
896
913
  *
897
914
  * Structured return type so callers can inspect failure reasons
898
- * instead of receiving a bare `false`.
915
+ * instead of receiving a bare `false`. When `code` is set, branch on it
916
+ * for programmatic handling; otherwise log `error` for humans.
899
917
  */
900
918
  type FFIDRedirectResult = {
901
919
  success: true;
902
920
  } | {
903
921
  success: false;
904
922
  error: string;
923
+ code?: FFIDRedirectErrorCode;
905
924
  };
906
925
 
907
926
  /** OTP / magic link methods - sendOtp / verifyOtp */
@@ -100,10 +100,7 @@ function createTokenStore(storageType) {
100
100
  // src/client/oauth-userinfo.ts
101
101
  var SESSION_ELIGIBLE_SUBSCRIPTION_STATUSES = [
102
102
  "trialing",
103
- "active",
104
- "past_due",
105
- "canceled",
106
- "paused"
103
+ "active"
107
104
  ];
108
105
  function isSessionEligibleSubscriptionStatus(value) {
109
106
  return typeof value === "string" && SESSION_ELIGIBLE_SUBSCRIPTION_STATUSES.includes(value);
@@ -805,7 +802,7 @@ function createProfileMethods(deps) {
805
802
  }
806
803
 
807
804
  // src/client/version-check.ts
808
- var SDK_VERSION = "2.17.1";
805
+ var SDK_VERSION = "2.18.0";
809
806
  var SDK_USER_AGENT = `FFID-SDK/${SDK_VERSION} (TypeScript)`;
810
807
  var SDK_VERSION_HEADER = "X-FFID-SDK-Version";
811
808
  function sdkHeaders() {
@@ -1302,6 +1299,19 @@ function storeCodeVerifier(verifier, logger) {
1302
1299
  }
1303
1300
  return sessionStored || localStored;
1304
1301
  }
1302
+ function cleanupVerifierStorage(logger) {
1303
+ try {
1304
+ window.sessionStorage.removeItem(VERIFIER_STORAGE_KEY);
1305
+ } catch (error) {
1306
+ logger?.warn("retrieveCodeVerifier: sessionStorage \u306E\u30AF\u30EA\u30FC\u30F3\u30A2\u30C3\u30D7\u306B\u5931\u6557\u3057\u307E\u3057\u305F:", error);
1307
+ }
1308
+ try {
1309
+ window.localStorage.removeItem(VERIFIER_FALLBACK_STORAGE_KEY);
1310
+ window.localStorage.removeItem(VERIFIER_FALLBACK_TIMESTAMP_KEY);
1311
+ } catch (error) {
1312
+ logger?.warn("retrieveCodeVerifier: localStorage \u306E\u30AF\u30EA\u30FC\u30F3\u30A2\u30C3\u30D7\u306B\u5931\u6557\u3057\u307E\u3057\u305F:", error);
1313
+ }
1314
+ }
1305
1315
  function base64UrlEncode(buffer) {
1306
1316
  const bytes = new Uint8Array(buffer);
1307
1317
  let binary = "";
@@ -1316,6 +1326,87 @@ var OAUTH_AUTHORIZE_ENDPOINT = "/api/v1/oauth/authorize";
1316
1326
  var AUTH_LOGOUT_ENDPOINT = "/api/v1/auth/logout";
1317
1327
  var STATE_RANDOM_BYTES = 16;
1318
1328
  var HEX_BASE2 = 16;
1329
+ var REDIRECT_LOOP_KEY = "ffid_sdk_redirect_loop_history";
1330
+ var REDIRECT_LOOP_WINDOW_MS = 6e4;
1331
+ var REDIRECT_LOOP_THRESHOLD = 3;
1332
+ var storageReadFailureLogged = false;
1333
+ var storageWriteFailureLogged = false;
1334
+ function logStorageReadFailure(logger, err) {
1335
+ if (storageReadFailureLogged) return;
1336
+ storageReadFailureLogged = true;
1337
+ logger.warn(
1338
+ "[FFID SDK] sessionStorage read failed \u2014 redirect loop detection disabled on this browser (fail-open). iOS WebKit private mode / eviction \u304C\u5178\u578B\u8981\u56E0",
1339
+ err
1340
+ );
1341
+ }
1342
+ function logStorageWriteFailure(logger, err) {
1343
+ if (storageWriteFailureLogged) return;
1344
+ storageWriteFailureLogged = true;
1345
+ logger.warn(
1346
+ "[FFID SDK] sessionStorage write failed \u2014 redirect loop detection disabled on this browser (fail-open).",
1347
+ err
1348
+ );
1349
+ }
1350
+ function isRedirectLoopHistory(value) {
1351
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
1352
+ return Object.values(value).every(
1353
+ (arr) => Array.isArray(arr) && arr.every((t) => typeof t === "number" && Number.isFinite(t))
1354
+ );
1355
+ }
1356
+ function readRedirectLoopHistory(logger) {
1357
+ if (typeof window === "undefined") return {};
1358
+ try {
1359
+ const raw = window.sessionStorage.getItem(REDIRECT_LOOP_KEY);
1360
+ if (raw === null) return {};
1361
+ const parsed = JSON.parse(raw);
1362
+ return isRedirectLoopHistory(parsed) ? parsed : {};
1363
+ } catch (err) {
1364
+ logStorageReadFailure(logger, err);
1365
+ return {};
1366
+ }
1367
+ }
1368
+ function writeRedirectLoopHistory(history, logger) {
1369
+ if (typeof window === "undefined") return;
1370
+ try {
1371
+ window.sessionStorage.setItem(REDIRECT_LOOP_KEY, JSON.stringify(history));
1372
+ } catch (err) {
1373
+ logStorageWriteFailure(logger, err);
1374
+ }
1375
+ }
1376
+ function pruneRedirectLoopHistory(history, now) {
1377
+ const cutoff = now - REDIRECT_LOOP_WINDOW_MS;
1378
+ const pruned = {};
1379
+ for (const [key, timestamps] of Object.entries(history)) {
1380
+ const fresh = timestamps.filter((t) => t > cutoff);
1381
+ if (fresh.length > 0) pruned[key] = fresh;
1382
+ }
1383
+ return pruned;
1384
+ }
1385
+ function getRecentRedirectCount(authorizeKey, now, logger) {
1386
+ const pruned = pruneRedirectLoopHistory(readRedirectLoopHistory(logger), now);
1387
+ writeRedirectLoopHistory(pruned, logger);
1388
+ return pruned[authorizeKey]?.length ?? 0;
1389
+ }
1390
+ function recordRedirectAttempt(authorizeKey, now, logger) {
1391
+ const history = readRedirectLoopHistory(logger);
1392
+ const existing = history[authorizeKey] ?? [];
1393
+ history[authorizeKey] = [...existing, now];
1394
+ writeRedirectLoopHistory(history, logger);
1395
+ }
1396
+ function rollbackLastRedirectAttempt(authorizeKey, logger) {
1397
+ const history = readRedirectLoopHistory(logger);
1398
+ const existing = history[authorizeKey];
1399
+ if (!Array.isArray(existing) || existing.length === 0) return;
1400
+ history[authorizeKey] = existing.slice(0, -1);
1401
+ if (history[authorizeKey].length === 0) delete history[authorizeKey];
1402
+ writeRedirectLoopHistory(history, logger);
1403
+ }
1404
+ function buildAuthorizeKey(baseUrl, clientId, organizationId) {
1405
+ const params = new URLSearchParams({ client_id: clientId });
1406
+ const trimmedOrg = organizationId?.trim();
1407
+ if (trimmedOrg) params.set("organization_id", trimmedOrg);
1408
+ return `${baseUrl}${OAUTH_AUTHORIZE_ENDPOINT}?${params.toString()}`;
1409
+ }
1319
1410
  function generateRandomState() {
1320
1411
  const array = new Uint8Array(STATE_RANDOM_BYTES);
1321
1412
  crypto.getRandomValues(array);
@@ -1335,6 +1426,23 @@ function createRedirectMethods(deps) {
1335
1426
  logger.warn("SSR \u74B0\u5883\u3067\u306F\u30EA\u30C0\u30A4\u30EC\u30AF\u30C8\u3067\u304D\u307E\u305B\u3093");
1336
1427
  return { success: false, error: "SSR \u74B0\u5883\u3067\u306F\u30EA\u30C0\u30A4\u30EC\u30AF\u30C8\u3067\u304D\u307E\u305B\u3093" };
1337
1428
  }
1429
+ const authorizeKey = buildAuthorizeKey(baseUrl, clientId, options?.organizationId);
1430
+ const now = Date.now();
1431
+ const recentCount = getRecentRedirectCount(authorizeKey, now, logger);
1432
+ if (recentCount >= REDIRECT_LOOP_THRESHOLD) {
1433
+ cleanupVerifierStorage(logger);
1434
+ logger.warn("[FFID SDK] redirect loop detected \u2014 \u81EA\u52D5\u30EA\u30C0\u30A4\u30EC\u30AF\u30C8\u3092\u505C\u6B62\u3057\u307E\u3057\u305F", {
1435
+ authorizeKey,
1436
+ recentCount,
1437
+ windowMs: REDIRECT_LOOP_WINDOW_MS,
1438
+ threshold: REDIRECT_LOOP_THRESHOLD
1439
+ });
1440
+ return {
1441
+ success: false,
1442
+ code: "redirect_loop_detected",
1443
+ error: "\u77ED\u6642\u9593\u306B\u540C\u3058\u8A8D\u53EF\u30A8\u30F3\u30C9\u30DD\u30A4\u30F3\u30C8\u3078\u306E\u30EA\u30C0\u30A4\u30EC\u30AF\u30C8\u304C\u7E70\u308A\u8FD4\u3055\u308C\u305F\u305F\u3081\u3001\u30EB\u30FC\u30D7\u691C\u51FA\u306B\u3088\u308A\u81EA\u52D5\u30EA\u30C0\u30A4\u30EC\u30AF\u30C8\u3092\u505C\u6B62\u3057\u307E\u3057\u305F"
1444
+ };
1445
+ }
1338
1446
  const verifier = generateCodeVerifier();
1339
1447
  storeCodeVerifier(verifier, logger);
1340
1448
  let challenge;
@@ -1365,7 +1473,13 @@ function createRedirectMethods(deps) {
1365
1473
  }
1366
1474
  const authorizeUrl = `${baseUrl}${OAUTH_AUTHORIZE_ENDPOINT}?${params.toString()}`;
1367
1475
  logger.debug("Redirecting to authorize:", authorizeUrl);
1368
- window.location.href = authorizeUrl;
1476
+ recordRedirectAttempt(authorizeKey, now, logger);
1477
+ try {
1478
+ window.location.href = authorizeUrl;
1479
+ } catch (err) {
1480
+ rollbackLastRedirectAttempt(authorizeKey, logger);
1481
+ throw err;
1482
+ }
1369
1483
  return { success: true };
1370
1484
  }
1371
1485
  async function redirectToLogin() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@feelflow/ffid-sdk",
3
- "version": "2.17.1",
3
+ "version": "2.18.0",
4
4
  "description": "FeelFlow ID Platform SDK for React/Next.js applications",
5
5
  "keywords": [
6
6
  "feelflow",