@feelflow/ffid-sdk 2.11.0 → 2.12.1

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.
@@ -457,9 +457,118 @@ function createBillingMethods(deps) {
457
457
  var EXT_PLANS_ENDPOINT = "/api/v1/subscriptions/ext/plans";
458
458
  var EXT_SUBSCRIPTION_ENDPOINT = "/api/v1/subscriptions/ext";
459
459
  var EXT_SUBSCRIBE_ENDPOINT = "/api/v1/subscriptions/ext/subscribe";
460
+ var FFIDSDKError = class extends Error {
461
+ code;
462
+ constructor(code, message) {
463
+ super(message);
464
+ this.name = "FFIDSDKError";
465
+ this.code = code;
466
+ }
467
+ };
460
468
  function isBlankString(value) {
461
469
  return typeof value !== "string" || value.trim() === "";
462
470
  }
471
+ var MALFORMED_PREVIEW_CODE = "MALFORMED_PLAN_CHANGE_PREVIEW";
472
+ var MALFORMED_SEAT_PREVIEW_CODE = "MALFORMED_SEAT_CHANGE_PREVIEW";
473
+ var SEAT_ESTIMATE_REASONS = /* @__PURE__ */ new Set(["no_stripe_data", "custom_pricing", "stripe_error"]);
474
+ function validatePlanChangePreview(preview) {
475
+ if (preview === null || typeof preview !== "object") {
476
+ throw new FFIDSDKError(
477
+ MALFORMED_PREVIEW_CODE,
478
+ "SDK: server returned malformed PlanChangePreview \u2014 expected object, got " + (preview === null ? "null" : typeof preview)
479
+ );
480
+ }
481
+ const p = preview;
482
+ const flag = p.willApplyAtPeriodEnd;
483
+ if (typeof flag !== "boolean") {
484
+ throw new FFIDSDKError(
485
+ MALFORMED_PREVIEW_CODE,
486
+ `SDK: server returned malformed PlanChangePreview \u2014 willApplyAtPeriodEnd must be boolean (got ${typeof flag})`
487
+ );
488
+ }
489
+ const { proratedAmount, effectiveDate } = p;
490
+ if (flag === true) {
491
+ if (proratedAmount !== 0) {
492
+ throw new FFIDSDKError(
493
+ MALFORMED_PREVIEW_CODE,
494
+ `SDK: server returned malformed PlanChangePreview \u2014 willApplyAtPeriodEnd=true requires proratedAmount=0 (got ${JSON.stringify(proratedAmount)})`
495
+ );
496
+ }
497
+ if (effectiveDate !== null && typeof effectiveDate !== "string") {
498
+ throw new FFIDSDKError(
499
+ MALFORMED_PREVIEW_CODE,
500
+ `SDK: server returned malformed PlanChangePreview \u2014 willApplyAtPeriodEnd=true requires effectiveDate to be string or null (got ${typeof effectiveDate})`
501
+ );
502
+ }
503
+ return;
504
+ }
505
+ if (effectiveDate !== null) {
506
+ throw new FFIDSDKError(
507
+ MALFORMED_PREVIEW_CODE,
508
+ `SDK: server returned malformed PlanChangePreview \u2014 willApplyAtPeriodEnd=false requires effectiveDate=null (got ${JSON.stringify(effectiveDate)})`
509
+ );
510
+ }
511
+ if (typeof proratedAmount !== "number") {
512
+ throw new FFIDSDKError(
513
+ MALFORMED_PREVIEW_CODE,
514
+ `SDK: server returned malformed PlanChangePreview \u2014 willApplyAtPeriodEnd=false requires proratedAmount to be a number (got ${typeof proratedAmount})`
515
+ );
516
+ }
517
+ }
518
+ function validateSeatChangePreview(preview) {
519
+ if (preview === null || typeof preview !== "object") {
520
+ throw new FFIDSDKError(
521
+ MALFORMED_SEAT_PREVIEW_CODE,
522
+ "SDK: server returned malformed SeatChangePreview \u2014 expected object, got " + (preview === null ? "null" : typeof preview)
523
+ );
524
+ }
525
+ const p = preview;
526
+ if (p.type !== "seat-change") {
527
+ throw new FFIDSDKError(
528
+ MALFORMED_SEAT_PREVIEW_CODE,
529
+ `SDK: server returned malformed SeatChangePreview \u2014 type must be 'seat-change' (got ${JSON.stringify(p.type)})`
530
+ );
531
+ }
532
+ const numericFields = [
533
+ "currentQuantity",
534
+ "newQuantity",
535
+ "unitPrice",
536
+ "proratedAmount",
537
+ "nextInvoiceAmount"
538
+ ];
539
+ for (const key of numericFields) {
540
+ if (typeof p[key] !== "number") {
541
+ throw new FFIDSDKError(
542
+ MALFORMED_SEAT_PREVIEW_CODE,
543
+ `SDK: server returned malformed SeatChangePreview \u2014 ${key} must be a number (got ${typeof p[key]})`
544
+ );
545
+ }
546
+ }
547
+ if (p.nextInvoiceDate !== null && typeof p.nextInvoiceDate !== "string") {
548
+ throw new FFIDSDKError(
549
+ MALFORMED_SEAT_PREVIEW_CODE,
550
+ `SDK: server returned malformed SeatChangePreview \u2014 nextInvoiceDate must be string or null (got ${typeof p.nextInvoiceDate})`
551
+ );
552
+ }
553
+ if (typeof p.isEstimate !== "boolean") {
554
+ throw new FFIDSDKError(
555
+ MALFORMED_SEAT_PREVIEW_CODE,
556
+ `SDK: server returned malformed SeatChangePreview \u2014 isEstimate must be boolean (got ${typeof p.isEstimate})`
557
+ );
558
+ }
559
+ if (p.estimateReason !== void 0 && !SEAT_ESTIMATE_REASONS.has(p.estimateReason)) {
560
+ throw new FFIDSDKError(
561
+ MALFORMED_SEAT_PREVIEW_CODE,
562
+ `SDK: server returned malformed SeatChangePreview \u2014 estimateReason must be one of 'no_stripe_data' | 'custom_pricing' | 'stripe_error' (got ${JSON.stringify(p.estimateReason)})`
563
+ );
564
+ }
565
+ if (!Array.isArray(p.lineItems)) {
566
+ throw new FFIDSDKError(
567
+ MALFORMED_SEAT_PREVIEW_CODE,
568
+ `SDK: server returned malformed SeatChangePreview \u2014 lineItems must be an array (got ${typeof p.lineItems})`
569
+ );
570
+ }
571
+ }
463
572
  function createSubscriptionMethods(deps) {
464
573
  const { fetchWithAuth, createError } = deps;
465
574
  async function listPlans() {
@@ -546,7 +655,7 @@ function createSubscriptionMethods(deps) {
546
655
  error: createError("VALIDATION_ERROR", "subscriptionId \u3068 planCode \u306F\u5FC5\u9808\u3067\u3059")
547
656
  };
548
657
  }
549
- return fetchWithAuth(
658
+ const response = await fetchWithAuth(
550
659
  `${EXT_SUBSCRIPTION_ENDPOINT}/${encodeURIComponent(params.subscriptionId)}/plan/preview`,
551
660
  {
552
661
  method: "POST",
@@ -556,6 +665,36 @@ function createSubscriptionMethods(deps) {
556
665
  })
557
666
  }
558
667
  );
668
+ if (response.data !== void 0) {
669
+ validatePlanChangePreview(response.data.preview);
670
+ }
671
+ return response;
672
+ }
673
+ async function previewSeatChange(params) {
674
+ if (isBlankString(params.subscriptionId)) {
675
+ return {
676
+ error: createError("VALIDATION_ERROR", "subscriptionId \u306F\u5FC5\u9808\u3067\u3059")
677
+ };
678
+ }
679
+ if (typeof params.quantity !== "number" || !Number.isInteger(params.quantity) || params.quantity < 1) {
680
+ return {
681
+ error: createError(
682
+ "VALIDATION_ERROR",
683
+ `quantity \u306F 1 \u4EE5\u4E0A\u306E\u6574\u6570\u3067\u3042\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059 (received: ${String(params.quantity)})`
684
+ )
685
+ };
686
+ }
687
+ const response = await fetchWithAuth(
688
+ `${EXT_SUBSCRIPTION_ENDPOINT}/${encodeURIComponent(params.subscriptionId)}/seats/preview`,
689
+ {
690
+ method: "POST",
691
+ body: JSON.stringify({ quantity: params.quantity })
692
+ }
693
+ );
694
+ if (response.data !== void 0) {
695
+ validateSeatChangePreview(response.data.preview);
696
+ }
697
+ return response;
559
698
  }
560
699
  return {
561
700
  listPlans,
@@ -564,7 +703,8 @@ function createSubscriptionMethods(deps) {
564
703
  changePlan,
565
704
  cancelSubscription,
566
705
  cancelPendingDowngrade,
567
- previewPlanChange
706
+ previewPlanChange,
707
+ previewSeatChange
568
708
  };
569
709
  }
570
710
 
@@ -621,7 +761,7 @@ function createMembersMethods(deps) {
621
761
  }
622
762
 
623
763
  // src/client/version-check.ts
624
- var SDK_VERSION = "2.11.0";
764
+ var SDK_VERSION = "2.12.1";
625
765
  var SDK_USER_AGENT = `FFID-SDK/${SDK_VERSION} (TypeScript)`;
626
766
  var SDK_VERSION_HEADER = "X-FFID-SDK-Version";
627
767
  function sdkHeaders() {
@@ -1244,6 +1384,23 @@ function createRedirectMethods(deps) {
1244
1384
  return { redirectToLogin, redirectToAuthorize, getLoginUrl, getSignupUrl, getLogoutUrl, redirectToLogout };
1245
1385
  }
1246
1386
 
1387
+ // src/client/redirect-uri.ts
1388
+ var AUTHORITY_BOUNDARY = /^([a-zA-Z][a-zA-Z0-9+.-]*:\/\/[^/?#]+)([/?#]?)/;
1389
+ function normalizeRedirectUri(input) {
1390
+ const url = new URL(input);
1391
+ const isRootPath = url.pathname === "" || url.pathname === "/";
1392
+ if (!isRootPath) {
1393
+ return { normalized: input, changed: false };
1394
+ }
1395
+ const match = input.match(AUTHORITY_BOUNDARY);
1396
+ if (match === null || match[2] === "/") {
1397
+ return { normalized: input, changed: false };
1398
+ }
1399
+ const authority = match[1];
1400
+ const rest = input.slice(authority.length);
1401
+ return { normalized: `${authority}/${rest}`, changed: true };
1402
+ }
1403
+
1247
1404
  // src/client/password-reset.ts
1248
1405
  var RESET_PASSWORD_BASE = "/api/v1/auth/reset-password";
1249
1406
  function isBlank(value) {
@@ -1754,6 +1911,24 @@ var FFID_ERROR_CODES = {
1754
1911
  TOKEN_VERIFICATION_ERROR: "TOKEN_VERIFICATION_ERROR"
1755
1912
  };
1756
1913
  var EXT_CHECK_ENDPOINT = "/api/v1/subscriptions/ext/check";
1914
+ function resolveRedirectUri(raw, logger) {
1915
+ if (raw === null) return null;
1916
+ try {
1917
+ const { normalized, changed } = normalizeRedirectUri(raw);
1918
+ if (changed) {
1919
+ logger.warn(
1920
+ `FFID Client: redirect_uri \u3092\u6B63\u898F\u5316\u3057\u307E\u3057\u305F (${raw} \u2192 ${normalized})\u3002FFID \u7BA1\u7406\u753B\u9762\u3067\u306E\u767B\u9332\u5024\u3068\u4E00\u81F4\u3055\u305B\u3066\u304F\u3060\u3055\u3044\u3002`
1921
+ );
1922
+ }
1923
+ return normalized;
1924
+ } catch (error) {
1925
+ logger.warn(
1926
+ `FFID Client: redirectUri \u306E\u30D1\u30FC\u30B9\u306B\u5931\u6557\u3057\u305F\u305F\u3081\u6B63\u898F\u5316\u3092\u30B9\u30AD\u30C3\u30D7\u3057\u307E\u3057\u305F (${raw})`,
1927
+ error
1928
+ );
1929
+ return raw;
1930
+ }
1931
+ }
1757
1932
  function createFFIDClient(config) {
1758
1933
  if (!config.serviceCode || !config.serviceCode.trim()) {
1759
1934
  throw new Error("FFID Client: serviceCode \u304C\u672A\u8A2D\u5B9A\u3067\u3059");
@@ -1761,7 +1936,7 @@ function createFFIDClient(config) {
1761
1936
  const baseUrl = config.apiBaseUrl ?? DEFAULT_API_BASE_URL;
1762
1937
  const authMode = config.authMode ?? "cookie";
1763
1938
  const clientId = config.clientId ?? config.serviceCode;
1764
- const resolvedRedirectUri = config.redirectUri ?? null;
1939
+ const rawRedirectUri = config.redirectUri ?? null;
1765
1940
  const serviceApiKey = config.serviceApiKey?.trim();
1766
1941
  const verifyStrategy = config.verifyStrategy ?? "jwt";
1767
1942
  const cache = config.cache;
@@ -1776,6 +1951,7 @@ function createFFIDClient(config) {
1776
1951
  throw new Error("FFID Client: timeout \u306F\u6B63\u306E\u6570\u5024\u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044");
1777
1952
  }
1778
1953
  const logger = config.logger ?? (config.debug ? consoleLogger : noopLogger);
1954
+ const resolvedRedirectUri = resolveRedirectUri(rawRedirectUri, logger);
1779
1955
  const tokenStore = authMode === "token" ? createTokenStore() : createTokenStore("memory");
1780
1956
  function createError(code, message) {
1781
1957
  return { code, message };
@@ -1958,7 +2134,8 @@ function createFFIDClient(config) {
1958
2134
  changePlan,
1959
2135
  cancelSubscription,
1960
2136
  cancelPendingDowngrade,
1961
- previewPlanChange
2137
+ previewPlanChange,
2138
+ previewSeatChange
1962
2139
  } = createSubscriptionMethods({
1963
2140
  fetchWithAuth,
1964
2141
  createError
@@ -2048,6 +2225,7 @@ function createFFIDClient(config) {
2048
2225
  cancelSubscription,
2049
2226
  cancelPendingDowngrade,
2050
2227
  previewPlanChange,
2228
+ previewSeatChange,
2051
2229
  verifyAccessToken,
2052
2230
  getSubscribeUrl,
2053
2231
  redirectToSubscribe,
@@ -3982,6 +4160,7 @@ exports.FFIDInquiryForm = FFIDInquiryForm;
3982
4160
  exports.FFIDLoginButton = FFIDLoginButton;
3983
4161
  exports.FFIDOrganizationSwitcher = FFIDOrganizationSwitcher;
3984
4162
  exports.FFIDProvider = FFIDProvider;
4163
+ exports.FFIDSDKError = FFIDSDKError;
3985
4164
  exports.FFIDSubscriptionBadge = FFIDSubscriptionBadge;
3986
4165
  exports.FFIDUserMenu = FFIDUserMenu;
3987
4166
  exports.FFID_ANNOUNCEMENTS_ERROR_CODES = FFID_ANNOUNCEMENTS_ERROR_CODES;
@@ -3991,6 +4170,7 @@ exports.createFFIDClient = createFFIDClient;
3991
4170
  exports.createTokenStore = createTokenStore;
3992
4171
  exports.generateCodeChallenge = generateCodeChallenge;
3993
4172
  exports.generateCodeVerifier = generateCodeVerifier;
4173
+ exports.normalizeRedirectUri = normalizeRedirectUri;
3994
4174
  exports.retrieveCodeVerifier = retrieveCodeVerifier;
3995
4175
  exports.storeCodeVerifier = storeCodeVerifier;
3996
4176
  exports.useFFID = useFFID;
@@ -455,9 +455,118 @@ function createBillingMethods(deps) {
455
455
  var EXT_PLANS_ENDPOINT = "/api/v1/subscriptions/ext/plans";
456
456
  var EXT_SUBSCRIPTION_ENDPOINT = "/api/v1/subscriptions/ext";
457
457
  var EXT_SUBSCRIBE_ENDPOINT = "/api/v1/subscriptions/ext/subscribe";
458
+ var FFIDSDKError = class extends Error {
459
+ code;
460
+ constructor(code, message) {
461
+ super(message);
462
+ this.name = "FFIDSDKError";
463
+ this.code = code;
464
+ }
465
+ };
458
466
  function isBlankString(value) {
459
467
  return typeof value !== "string" || value.trim() === "";
460
468
  }
469
+ var MALFORMED_PREVIEW_CODE = "MALFORMED_PLAN_CHANGE_PREVIEW";
470
+ var MALFORMED_SEAT_PREVIEW_CODE = "MALFORMED_SEAT_CHANGE_PREVIEW";
471
+ var SEAT_ESTIMATE_REASONS = /* @__PURE__ */ new Set(["no_stripe_data", "custom_pricing", "stripe_error"]);
472
+ function validatePlanChangePreview(preview) {
473
+ if (preview === null || typeof preview !== "object") {
474
+ throw new FFIDSDKError(
475
+ MALFORMED_PREVIEW_CODE,
476
+ "SDK: server returned malformed PlanChangePreview \u2014 expected object, got " + (preview === null ? "null" : typeof preview)
477
+ );
478
+ }
479
+ const p = preview;
480
+ const flag = p.willApplyAtPeriodEnd;
481
+ if (typeof flag !== "boolean") {
482
+ throw new FFIDSDKError(
483
+ MALFORMED_PREVIEW_CODE,
484
+ `SDK: server returned malformed PlanChangePreview \u2014 willApplyAtPeriodEnd must be boolean (got ${typeof flag})`
485
+ );
486
+ }
487
+ const { proratedAmount, effectiveDate } = p;
488
+ if (flag === true) {
489
+ if (proratedAmount !== 0) {
490
+ throw new FFIDSDKError(
491
+ MALFORMED_PREVIEW_CODE,
492
+ `SDK: server returned malformed PlanChangePreview \u2014 willApplyAtPeriodEnd=true requires proratedAmount=0 (got ${JSON.stringify(proratedAmount)})`
493
+ );
494
+ }
495
+ if (effectiveDate !== null && typeof effectiveDate !== "string") {
496
+ throw new FFIDSDKError(
497
+ MALFORMED_PREVIEW_CODE,
498
+ `SDK: server returned malformed PlanChangePreview \u2014 willApplyAtPeriodEnd=true requires effectiveDate to be string or null (got ${typeof effectiveDate})`
499
+ );
500
+ }
501
+ return;
502
+ }
503
+ if (effectiveDate !== null) {
504
+ throw new FFIDSDKError(
505
+ MALFORMED_PREVIEW_CODE,
506
+ `SDK: server returned malformed PlanChangePreview \u2014 willApplyAtPeriodEnd=false requires effectiveDate=null (got ${JSON.stringify(effectiveDate)})`
507
+ );
508
+ }
509
+ if (typeof proratedAmount !== "number") {
510
+ throw new FFIDSDKError(
511
+ MALFORMED_PREVIEW_CODE,
512
+ `SDK: server returned malformed PlanChangePreview \u2014 willApplyAtPeriodEnd=false requires proratedAmount to be a number (got ${typeof proratedAmount})`
513
+ );
514
+ }
515
+ }
516
+ function validateSeatChangePreview(preview) {
517
+ if (preview === null || typeof preview !== "object") {
518
+ throw new FFIDSDKError(
519
+ MALFORMED_SEAT_PREVIEW_CODE,
520
+ "SDK: server returned malformed SeatChangePreview \u2014 expected object, got " + (preview === null ? "null" : typeof preview)
521
+ );
522
+ }
523
+ const p = preview;
524
+ if (p.type !== "seat-change") {
525
+ throw new FFIDSDKError(
526
+ MALFORMED_SEAT_PREVIEW_CODE,
527
+ `SDK: server returned malformed SeatChangePreview \u2014 type must be 'seat-change' (got ${JSON.stringify(p.type)})`
528
+ );
529
+ }
530
+ const numericFields = [
531
+ "currentQuantity",
532
+ "newQuantity",
533
+ "unitPrice",
534
+ "proratedAmount",
535
+ "nextInvoiceAmount"
536
+ ];
537
+ for (const key of numericFields) {
538
+ if (typeof p[key] !== "number") {
539
+ throw new FFIDSDKError(
540
+ MALFORMED_SEAT_PREVIEW_CODE,
541
+ `SDK: server returned malformed SeatChangePreview \u2014 ${key} must be a number (got ${typeof p[key]})`
542
+ );
543
+ }
544
+ }
545
+ if (p.nextInvoiceDate !== null && typeof p.nextInvoiceDate !== "string") {
546
+ throw new FFIDSDKError(
547
+ MALFORMED_SEAT_PREVIEW_CODE,
548
+ `SDK: server returned malformed SeatChangePreview \u2014 nextInvoiceDate must be string or null (got ${typeof p.nextInvoiceDate})`
549
+ );
550
+ }
551
+ if (typeof p.isEstimate !== "boolean") {
552
+ throw new FFIDSDKError(
553
+ MALFORMED_SEAT_PREVIEW_CODE,
554
+ `SDK: server returned malformed SeatChangePreview \u2014 isEstimate must be boolean (got ${typeof p.isEstimate})`
555
+ );
556
+ }
557
+ if (p.estimateReason !== void 0 && !SEAT_ESTIMATE_REASONS.has(p.estimateReason)) {
558
+ throw new FFIDSDKError(
559
+ MALFORMED_SEAT_PREVIEW_CODE,
560
+ `SDK: server returned malformed SeatChangePreview \u2014 estimateReason must be one of 'no_stripe_data' | 'custom_pricing' | 'stripe_error' (got ${JSON.stringify(p.estimateReason)})`
561
+ );
562
+ }
563
+ if (!Array.isArray(p.lineItems)) {
564
+ throw new FFIDSDKError(
565
+ MALFORMED_SEAT_PREVIEW_CODE,
566
+ `SDK: server returned malformed SeatChangePreview \u2014 lineItems must be an array (got ${typeof p.lineItems})`
567
+ );
568
+ }
569
+ }
461
570
  function createSubscriptionMethods(deps) {
462
571
  const { fetchWithAuth, createError } = deps;
463
572
  async function listPlans() {
@@ -544,7 +653,7 @@ function createSubscriptionMethods(deps) {
544
653
  error: createError("VALIDATION_ERROR", "subscriptionId \u3068 planCode \u306F\u5FC5\u9808\u3067\u3059")
545
654
  };
546
655
  }
547
- return fetchWithAuth(
656
+ const response = await fetchWithAuth(
548
657
  `${EXT_SUBSCRIPTION_ENDPOINT}/${encodeURIComponent(params.subscriptionId)}/plan/preview`,
549
658
  {
550
659
  method: "POST",
@@ -554,6 +663,36 @@ function createSubscriptionMethods(deps) {
554
663
  })
555
664
  }
556
665
  );
666
+ if (response.data !== void 0) {
667
+ validatePlanChangePreview(response.data.preview);
668
+ }
669
+ return response;
670
+ }
671
+ async function previewSeatChange(params) {
672
+ if (isBlankString(params.subscriptionId)) {
673
+ return {
674
+ error: createError("VALIDATION_ERROR", "subscriptionId \u306F\u5FC5\u9808\u3067\u3059")
675
+ };
676
+ }
677
+ if (typeof params.quantity !== "number" || !Number.isInteger(params.quantity) || params.quantity < 1) {
678
+ return {
679
+ error: createError(
680
+ "VALIDATION_ERROR",
681
+ `quantity \u306F 1 \u4EE5\u4E0A\u306E\u6574\u6570\u3067\u3042\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059 (received: ${String(params.quantity)})`
682
+ )
683
+ };
684
+ }
685
+ const response = await fetchWithAuth(
686
+ `${EXT_SUBSCRIPTION_ENDPOINT}/${encodeURIComponent(params.subscriptionId)}/seats/preview`,
687
+ {
688
+ method: "POST",
689
+ body: JSON.stringify({ quantity: params.quantity })
690
+ }
691
+ );
692
+ if (response.data !== void 0) {
693
+ validateSeatChangePreview(response.data.preview);
694
+ }
695
+ return response;
557
696
  }
558
697
  return {
559
698
  listPlans,
@@ -562,7 +701,8 @@ function createSubscriptionMethods(deps) {
562
701
  changePlan,
563
702
  cancelSubscription,
564
703
  cancelPendingDowngrade,
565
- previewPlanChange
704
+ previewPlanChange,
705
+ previewSeatChange
566
706
  };
567
707
  }
568
708
 
@@ -619,7 +759,7 @@ function createMembersMethods(deps) {
619
759
  }
620
760
 
621
761
  // src/client/version-check.ts
622
- var SDK_VERSION = "2.11.0";
762
+ var SDK_VERSION = "2.12.1";
623
763
  var SDK_USER_AGENT = `FFID-SDK/${SDK_VERSION} (TypeScript)`;
624
764
  var SDK_VERSION_HEADER = "X-FFID-SDK-Version";
625
765
  function sdkHeaders() {
@@ -1242,6 +1382,23 @@ function createRedirectMethods(deps) {
1242
1382
  return { redirectToLogin, redirectToAuthorize, getLoginUrl, getSignupUrl, getLogoutUrl, redirectToLogout };
1243
1383
  }
1244
1384
 
1385
+ // src/client/redirect-uri.ts
1386
+ var AUTHORITY_BOUNDARY = /^([a-zA-Z][a-zA-Z0-9+.-]*:\/\/[^/?#]+)([/?#]?)/;
1387
+ function normalizeRedirectUri(input) {
1388
+ const url = new URL(input);
1389
+ const isRootPath = url.pathname === "" || url.pathname === "/";
1390
+ if (!isRootPath) {
1391
+ return { normalized: input, changed: false };
1392
+ }
1393
+ const match = input.match(AUTHORITY_BOUNDARY);
1394
+ if (match === null || match[2] === "/") {
1395
+ return { normalized: input, changed: false };
1396
+ }
1397
+ const authority = match[1];
1398
+ const rest = input.slice(authority.length);
1399
+ return { normalized: `${authority}/${rest}`, changed: true };
1400
+ }
1401
+
1245
1402
  // src/client/password-reset.ts
1246
1403
  var RESET_PASSWORD_BASE = "/api/v1/auth/reset-password";
1247
1404
  function isBlank(value) {
@@ -1752,6 +1909,24 @@ var FFID_ERROR_CODES = {
1752
1909
  TOKEN_VERIFICATION_ERROR: "TOKEN_VERIFICATION_ERROR"
1753
1910
  };
1754
1911
  var EXT_CHECK_ENDPOINT = "/api/v1/subscriptions/ext/check";
1912
+ function resolveRedirectUri(raw, logger) {
1913
+ if (raw === null) return null;
1914
+ try {
1915
+ const { normalized, changed } = normalizeRedirectUri(raw);
1916
+ if (changed) {
1917
+ logger.warn(
1918
+ `FFID Client: redirect_uri \u3092\u6B63\u898F\u5316\u3057\u307E\u3057\u305F (${raw} \u2192 ${normalized})\u3002FFID \u7BA1\u7406\u753B\u9762\u3067\u306E\u767B\u9332\u5024\u3068\u4E00\u81F4\u3055\u305B\u3066\u304F\u3060\u3055\u3044\u3002`
1919
+ );
1920
+ }
1921
+ return normalized;
1922
+ } catch (error) {
1923
+ logger.warn(
1924
+ `FFID Client: redirectUri \u306E\u30D1\u30FC\u30B9\u306B\u5931\u6557\u3057\u305F\u305F\u3081\u6B63\u898F\u5316\u3092\u30B9\u30AD\u30C3\u30D7\u3057\u307E\u3057\u305F (${raw})`,
1925
+ error
1926
+ );
1927
+ return raw;
1928
+ }
1929
+ }
1755
1930
  function createFFIDClient(config) {
1756
1931
  if (!config.serviceCode || !config.serviceCode.trim()) {
1757
1932
  throw new Error("FFID Client: serviceCode \u304C\u672A\u8A2D\u5B9A\u3067\u3059");
@@ -1759,7 +1934,7 @@ function createFFIDClient(config) {
1759
1934
  const baseUrl = config.apiBaseUrl ?? DEFAULT_API_BASE_URL;
1760
1935
  const authMode = config.authMode ?? "cookie";
1761
1936
  const clientId = config.clientId ?? config.serviceCode;
1762
- const resolvedRedirectUri = config.redirectUri ?? null;
1937
+ const rawRedirectUri = config.redirectUri ?? null;
1763
1938
  const serviceApiKey = config.serviceApiKey?.trim();
1764
1939
  const verifyStrategy = config.verifyStrategy ?? "jwt";
1765
1940
  const cache = config.cache;
@@ -1774,6 +1949,7 @@ function createFFIDClient(config) {
1774
1949
  throw new Error("FFID Client: timeout \u306F\u6B63\u306E\u6570\u5024\u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044");
1775
1950
  }
1776
1951
  const logger = config.logger ?? (config.debug ? consoleLogger : noopLogger);
1952
+ const resolvedRedirectUri = resolveRedirectUri(rawRedirectUri, logger);
1777
1953
  const tokenStore = authMode === "token" ? createTokenStore() : createTokenStore("memory");
1778
1954
  function createError(code, message) {
1779
1955
  return { code, message };
@@ -1956,7 +2132,8 @@ function createFFIDClient(config) {
1956
2132
  changePlan,
1957
2133
  cancelSubscription,
1958
2134
  cancelPendingDowngrade,
1959
- previewPlanChange
2135
+ previewPlanChange,
2136
+ previewSeatChange
1960
2137
  } = createSubscriptionMethods({
1961
2138
  fetchWithAuth,
1962
2139
  createError
@@ -2046,6 +2223,7 @@ function createFFIDClient(config) {
2046
2223
  cancelSubscription,
2047
2224
  cancelPendingDowngrade,
2048
2225
  previewPlanChange,
2226
+ previewSeatChange,
2049
2227
  verifyAccessToken,
2050
2228
  getSubscribeUrl,
2051
2229
  redirectToSubscribe,
@@ -3973,4 +4151,4 @@ function FFIDInquiryForm({
3973
4151
  );
3974
4152
  }
3975
4153
 
3976
- export { DEFAULT_API_BASE_URL, FFIDAnnouncementBadge, FFIDAnnouncementList, FFIDInquiryForm, FFIDLoginButton, FFIDOrganizationSwitcher, FFIDProvider, FFIDSubscriptionBadge, FFIDUserMenu, FFID_ANNOUNCEMENTS_ERROR_CODES, FFID_INQUIRY_CATEGORIES, createFFIDAnnouncementsClient, createFFIDClient, createTokenStore, generateCodeChallenge, generateCodeVerifier, retrieveCodeVerifier, storeCodeVerifier, useFFID, useFFIDAnnouncements, useFFIDContext, useSubscription, withSubscription };
4154
+ export { DEFAULT_API_BASE_URL, FFIDAnnouncementBadge, FFIDAnnouncementList, FFIDInquiryForm, FFIDLoginButton, FFIDOrganizationSwitcher, FFIDProvider, FFIDSDKError, FFIDSubscriptionBadge, FFIDUserMenu, FFID_ANNOUNCEMENTS_ERROR_CODES, FFID_INQUIRY_CATEGORIES, createFFIDAnnouncementsClient, createFFIDClient, createTokenStore, generateCodeChallenge, generateCodeVerifier, normalizeRedirectUri, retrieveCodeVerifier, storeCodeVerifier, useFFID, useFFIDAnnouncements, useFFIDContext, useSubscription, withSubscription };
@@ -1,34 +1,34 @@
1
1
  'use strict';
2
2
 
3
- var chunkBEHDXUB2_cjs = require('../chunk-BEHDXUB2.cjs');
3
+ var chunkDJPOGNAO_cjs = require('../chunk-DJPOGNAO.cjs');
4
4
 
5
5
 
6
6
 
7
7
  Object.defineProperty(exports, "FFIDAnnouncementBadge", {
8
8
  enumerable: true,
9
- get: function () { return chunkBEHDXUB2_cjs.FFIDAnnouncementBadge; }
9
+ get: function () { return chunkDJPOGNAO_cjs.FFIDAnnouncementBadge; }
10
10
  });
11
11
  Object.defineProperty(exports, "FFIDAnnouncementList", {
12
12
  enumerable: true,
13
- get: function () { return chunkBEHDXUB2_cjs.FFIDAnnouncementList; }
13
+ get: function () { return chunkDJPOGNAO_cjs.FFIDAnnouncementList; }
14
14
  });
15
15
  Object.defineProperty(exports, "FFIDInquiryForm", {
16
16
  enumerable: true,
17
- get: function () { return chunkBEHDXUB2_cjs.FFIDInquiryForm; }
17
+ get: function () { return chunkDJPOGNAO_cjs.FFIDInquiryForm; }
18
18
  });
19
19
  Object.defineProperty(exports, "FFIDLoginButton", {
20
20
  enumerable: true,
21
- get: function () { return chunkBEHDXUB2_cjs.FFIDLoginButton; }
21
+ get: function () { return chunkDJPOGNAO_cjs.FFIDLoginButton; }
22
22
  });
23
23
  Object.defineProperty(exports, "FFIDOrganizationSwitcher", {
24
24
  enumerable: true,
25
- get: function () { return chunkBEHDXUB2_cjs.FFIDOrganizationSwitcher; }
25
+ get: function () { return chunkDJPOGNAO_cjs.FFIDOrganizationSwitcher; }
26
26
  });
27
27
  Object.defineProperty(exports, "FFIDSubscriptionBadge", {
28
28
  enumerable: true,
29
- get: function () { return chunkBEHDXUB2_cjs.FFIDSubscriptionBadge; }
29
+ get: function () { return chunkDJPOGNAO_cjs.FFIDSubscriptionBadge; }
30
30
  });
31
31
  Object.defineProperty(exports, "FFIDUserMenu", {
32
32
  enumerable: true,
33
- get: function () { return chunkBEHDXUB2_cjs.FFIDUserMenu; }
33
+ get: function () { return chunkDJPOGNAO_cjs.FFIDUserMenu; }
34
34
  });
@@ -1 +1 @@
1
- export { FFIDAnnouncementBadge, FFIDAnnouncementList, FFIDInquiryForm, FFIDLoginButton, FFIDOrganizationSwitcher, FFIDSubscriptionBadge, FFIDUserMenu } from '../chunk-2OAFWUEL.js';
1
+ export { FFIDAnnouncementBadge, FFIDAnnouncementList, FFIDInquiryForm, FFIDLoginButton, FFIDOrganizationSwitcher, FFIDSubscriptionBadge, FFIDUserMenu } from '../chunk-MDBV4WY3.js';