@feelflow/ffid-sdk 7.1.0 → 8.0.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.
@@ -155,7 +155,7 @@ function normalizeUserinfo(raw) {
155
155
  emailVerified: raw.email_verified,
156
156
  name: raw.name,
157
157
  picture: raw.picture,
158
- companyName: raw.company_name ?? null,
158
+ companyName: null,
159
159
  department: raw.department ?? null,
160
160
  position: raw.position ?? null,
161
161
  phoneNumber: raw.phone_number ?? null,
@@ -223,13 +223,18 @@ function mapUserinfoSubscriptionToSession(userinfo, serviceCode) {
223
223
  ];
224
224
  }
225
225
  var JWKS_ENDPOINT = "/.well-known/jwks.json";
226
- var JWT_ISSUER = "https://id.feelflow.net";
226
+ var LEGACY_JWT_ISSUER = "https://id.feelflow.net";
227
227
  var JWT_ALGORITHM = "ES256";
228
228
  var JWT_CLOCK_TOLERANCE_SECONDS = 30;
229
+ function resolveAcceptedIssuers(baseUrl) {
230
+ const deployedOrigin = new URL(baseUrl).origin;
231
+ return deployedOrigin === LEGACY_JWT_ISSUER ? [LEGACY_JWT_ISSUER] : [deployedOrigin, LEGACY_JWT_ISSUER];
232
+ }
229
233
  function createJwtVerifier(deps) {
230
234
  const { baseUrl, serviceCode, logger, createError, errorCodes } = deps;
231
235
  const jwksUrl = new URL(JWKS_ENDPOINT, baseUrl);
232
236
  const jwks = jose.createRemoteJWKSet(jwksUrl);
237
+ const acceptedIssuers = resolveAcceptedIssuers(baseUrl);
233
238
  async function verifyJwt(accessToken) {
234
239
  if (!accessToken || !accessToken.trim()) {
235
240
  return {
@@ -242,7 +247,7 @@ function createJwtVerifier(deps) {
242
247
  try {
243
248
  const { payload } = await jose.jwtVerify(accessToken, jwks, {
244
249
  algorithms: [JWT_ALGORITHM],
245
- issuer: JWT_ISSUER,
250
+ issuer: acceptedIssuers,
246
251
  audience: serviceCode,
247
252
  clockTolerance: JWT_CLOCK_TOLERANCE_SECONDS
248
253
  });
@@ -459,7 +464,7 @@ function createVerifyAccessToken(deps) {
459
464
  email_verified: introspectResponse.email_verified,
460
465
  name: introspectResponse.name ?? null,
461
466
  picture: introspectResponse.picture ?? null,
462
- company_name: introspectResponse.company_name ?? null,
467
+ company_name: null,
463
468
  department: introspectResponse.department ?? null,
464
469
  position: introspectResponse.position ?? null,
465
470
  phone_number: introspectResponse.phone_number ?? null,
@@ -710,6 +715,24 @@ function validateSeatChangePreview(preview) {
710
715
  `SDK: server returned malformed SeatChangePreview \u2014 estimateReason must be one of 'no_stripe_data' | 'custom_pricing' | 'stripe_error' (got ${JSON.stringify(p.estimateReason)})`
711
716
  );
712
717
  }
718
+ if (p.willApplyAtPeriodEnd !== void 0 && typeof p.willApplyAtPeriodEnd !== "boolean") {
719
+ throw new FFIDSDKError(
720
+ MALFORMED_SEAT_PREVIEW_CODE,
721
+ `SDK: server returned malformed SeatChangePreview \u2014 willApplyAtPeriodEnd must be boolean when present (got ${typeof p.willApplyAtPeriodEnd})`
722
+ );
723
+ }
724
+ if (p.effectiveDate !== void 0 && p.effectiveDate !== null && typeof p.effectiveDate !== "string") {
725
+ throw new FFIDSDKError(
726
+ MALFORMED_SEAT_PREVIEW_CODE,
727
+ `SDK: server returned malformed SeatChangePreview \u2014 effectiveDate must be string or null when present (got ${typeof p.effectiveDate})`
728
+ );
729
+ }
730
+ if (p.willApplyAtPeriodEnd === true && p.proratedAmount !== 0) {
731
+ throw new FFIDSDKError(
732
+ MALFORMED_SEAT_PREVIEW_CODE,
733
+ `SDK: server returned malformed SeatChangePreview \u2014 willApplyAtPeriodEnd=true requires proratedAmount=0 (got ${JSON.stringify(p.proratedAmount)})`
734
+ );
735
+ }
713
736
  if (!Array.isArray(p.lineItems)) {
714
737
  throw new FFIDSDKError(
715
738
  MALFORMED_SEAT_PREVIEW_CODE,
@@ -1350,7 +1373,7 @@ var FFID_ORGANIZATION_PROFILE_ERROR_CODES = {
1350
1373
  };
1351
1374
 
1352
1375
  // src/client/version-check.ts
1353
- var SDK_VERSION = "7.1.0";
1376
+ var SDK_VERSION = "8.0.0";
1354
1377
  var SDK_USER_AGENT = `FFID-SDK/${SDK_VERSION} (TypeScript)`;
1355
1378
  var SDK_VERSION_HEADER = "X-FFID-SDK-Version";
1356
1379
  function sdkHeaders() {
@@ -2509,6 +2532,86 @@ function cleanupStateStorage(logger) {
2509
2532
  }
2510
2533
  }
2511
2534
 
2535
+ // src/client/agency-invite-token.ts
2536
+ var AGENCY_INVITE_TOKEN_PARAM = "invite";
2537
+ var AGENCY_INVITE_COOKIE_NAME = "ffid_invite";
2538
+ var AGENCY_INVITE_TOKEN_REDACTION = "[REDACTED]";
2539
+ function hasDocument() {
2540
+ return typeof document !== "undefined";
2541
+ }
2542
+ function toQueryString(source) {
2543
+ const queryStart = source.indexOf("?");
2544
+ if (queryStart < 0) return "";
2545
+ const afterQuery = source.slice(queryStart);
2546
+ const fragmentStart = afterQuery.indexOf("#");
2547
+ return fragmentStart < 0 ? afterQuery : afterQuery.slice(0, fragmentStart);
2548
+ }
2549
+ function readTokenFromUrl(options) {
2550
+ const source = options.url ?? (typeof window !== "undefined" ? window.location.search : void 0);
2551
+ if (source === void 0) return void 0;
2552
+ const params = new URLSearchParams(toQueryString(source));
2553
+ return params.get(AGENCY_INVITE_TOKEN_PARAM)?.trim() || void 0;
2554
+ }
2555
+ function readAgencyInviteCookie() {
2556
+ if (!hasDocument()) return void 0;
2557
+ const prefix = `${AGENCY_INVITE_COOKIE_NAME}=`;
2558
+ const raw = document.cookie.split(";").map((entry) => entry.trim()).find((entry) => entry.startsWith(prefix));
2559
+ if (!raw) return void 0;
2560
+ try {
2561
+ return decodeURIComponent(raw.slice(prefix.length)).trim() || void 0;
2562
+ } catch {
2563
+ return void 0;
2564
+ }
2565
+ }
2566
+ function readAgencyInviteToken(options = {}) {
2567
+ return readTokenFromUrl(options) ?? readAgencyInviteCookie();
2568
+ }
2569
+ var REDACT_BOUNDARIES = [
2570
+ "%2526",
2571
+ "%2523",
2572
+ "%253B",
2573
+ "%26",
2574
+ "%23",
2575
+ "%3B",
2576
+ "&",
2577
+ "#",
2578
+ ";"
2579
+ ];
2580
+ var REDACT_LEADS = ["%253F", "%3F", "?", ...REDACT_BOUNDARIES];
2581
+ var REDACT_SEPARATORS = ["%253D", "%3D", "="];
2582
+ function escapeForRegExp(literal) {
2583
+ return literal.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2584
+ }
2585
+ var REDACT_PATTERN = new RegExp(
2586
+ [
2587
+ // 先頭区切り。`\b` が使えないのは、入れ子形 (`%3Finvite%3D`) では `F` と `i` が
2588
+ // どちらも単語文字なので単語境界が存在しないため。
2589
+ `(^|${REDACT_LEADS.map(escapeForRegExp).join("|")})`,
2590
+ // パラメータ名と区切りはキャプチャして再出力する。`i` フラグがあるので、
2591
+ // 入力の `INVITE=` / `%3d` などの元の表記を保つ必要がある。
2592
+ `(${AGENCY_INVITE_TOKEN_PARAM})`,
2593
+ `(${REDACT_SEPARATORS.map(escapeForRegExp).join("|")})`,
2594
+ // 値。境界の直前まで貪欲に食う。`\s` は終端にしない — 生の空白や改行を含む
2595
+ // トークンでマスクが途中で切れ、後半が漏れる。
2596
+ `(?:(?!${REDACT_BOUNDARIES.map(escapeForRegExp).join("|")})[^&#;])*`
2597
+ ].join(""),
2598
+ "gi"
2599
+ );
2600
+ function redactAgencyInviteToken(url) {
2601
+ return url.replace(
2602
+ REDACT_PATTERN,
2603
+ // 文字列 replacement(`'$1$2$3[REDACTED]'`)にしないのは、
2604
+ // `AGENCY_INVITE_TOKEN_REDACTION` に `$` が入った瞬間に置換パターンとして
2605
+ // 解釈される罠を作らないため。
2606
+ (_match, lead, name, separator) => `${lead}${name}${separator}${AGENCY_INVITE_TOKEN_REDACTION}`
2607
+ );
2608
+ }
2609
+ function appendAgencyInviteToken(signupUrl, token) {
2610
+ if (token === void 0) return signupUrl;
2611
+ const separator = signupUrl.includes("?") ? "&" : "?";
2612
+ return `${signupUrl}${separator}${AGENCY_INVITE_TOKEN_PARAM}=${encodeURIComponent(token)}`;
2613
+ }
2614
+
2512
2615
  // src/client/redirect.ts
2513
2616
  var OAUTH_AUTHORIZE_ENDPOINT = "/api/v1/oauth/authorize";
2514
2617
  var AUTH_LOGOUT_ENDPOINT = "/api/v1/auth/logout";
@@ -2521,6 +2624,7 @@ var REDIRECT_LOOP_WINDOW_MS = 6e4;
2521
2624
  var REDIRECT_LOOP_THRESHOLD = 3;
2522
2625
  var storageReadFailureLogged = false;
2523
2626
  var storageWriteFailureLogged = false;
2627
+ var ssrInviteTokenUnavailableLogged = false;
2524
2628
  function isLoopDetectionDegraded() {
2525
2629
  return storageReadFailureLogged || storageWriteFailureLogged;
2526
2630
  }
@@ -2699,12 +2803,16 @@ function createRedirectMethods(deps) {
2699
2803
  }
2700
2804
  if (options?.screenHint === SCREEN_HINT_SIGNUP) {
2701
2805
  params.set(SCREEN_HINT_PARAM, SCREEN_HINT_SIGNUP);
2806
+ const inviteToken = readAgencyInviteToken();
2807
+ if (inviteToken !== void 0) {
2808
+ params.set(AGENCY_INVITE_TOKEN_PARAM, inviteToken);
2809
+ }
2702
2810
  }
2703
2811
  if (options?.prompt) {
2704
2812
  params.set("prompt", options.prompt);
2705
2813
  }
2706
2814
  const authorizeUrl = `${baseUrl}${OAUTH_AUTHORIZE_ENDPOINT}?${params.toString()}`;
2707
- logger.debug("Redirecting to authorize:", authorizeUrl);
2815
+ logger.debug("Redirecting to authorize:", redactAgencyInviteToken(authorizeUrl));
2708
2816
  recordRedirectAttempt(authorizeKey, now, logger);
2709
2817
  try {
2710
2818
  window.location.href = authorizeUrl;
@@ -2723,7 +2831,7 @@ function createRedirectMethods(deps) {
2723
2831
  return redirectToAuthorize(options);
2724
2832
  }
2725
2833
  const targetUrl = options?.screenHint === SCREEN_HINT_SIGNUP ? getSignupUrl() : getLoginUrl(void 0, options?.prompt ? { prompt: options.prompt } : void 0);
2726
- logger.debug("Redirecting to auth page:", targetUrl);
2834
+ logger.debug("Redirecting to auth page:", redactAgencyInviteToken(targetUrl));
2727
2835
  window.location.href = targetUrl;
2728
2836
  return { success: true };
2729
2837
  }
@@ -2740,7 +2848,7 @@ function createRedirectMethods(deps) {
2740
2848
  const promptSuffix = options?.prompt ? `&prompt=${encodeURIComponent(options.prompt)}` : "";
2741
2849
  return `${baseUrl}/login?redirect=${encodeURIComponent(redirect)}&service=${encodeURIComponent(serviceCode)}${promptSuffix}`;
2742
2850
  }
2743
- function getSignupUrl(redirectUrl) {
2851
+ function getSignupUrl(redirectUrl, options) {
2744
2852
  let redirect;
2745
2853
  if (redirectUrl != null) {
2746
2854
  redirect = redirectUrl;
@@ -2750,7 +2858,22 @@ function createRedirectMethods(deps) {
2750
2858
  logger.warn("getSignupUrl: SSR \u74B0\u5883\u3067 redirectUrl \u304C\u672A\u6307\u5B9A\u306E\u305F\u3081\u7A7A\u6587\u5B57\u306B\u30D5\u30A9\u30FC\u30EB\u30D0\u30C3\u30AF\u3057\u307E\u3059");
2751
2859
  redirect = "";
2752
2860
  }
2753
- return `${baseUrl}/signup?redirect=${encodeURIComponent(redirect)}&service=${encodeURIComponent(serviceCode)}`;
2861
+ let inviteToken = options?.inviteToken;
2862
+ if (inviteToken === void 0) {
2863
+ inviteToken = readAgencyInviteToken();
2864
+ if (inviteToken === void 0 && typeof window === "undefined") {
2865
+ if (!ssrInviteTokenUnavailableLogged) {
2866
+ ssrInviteTokenUnavailableLogged = true;
2867
+ logger.warn(
2868
+ "[FFID SDK] getSignupUrl: SSR \u3067\u306F\u62DB\u5F85\u30C8\u30FC\u30AF\u30F3\uFF08invite\uFF09\u3092\u8AAD\u3081\u307E\u305B\u3093\u3002\u4EE3\u7406\u5E97\u306E\u62DB\u5F85\u30EA\u30F3\u30AF\u3092\u4F7F\u3046\u5834\u5408\u306F options.inviteToken \u306B\u6E21\u3057\u3066\u304F\u3060\u3055\u3044\uFF08\u4F7F\u3063\u3066\u3044\u306A\u3044\u5834\u5408\u306F\u3053\u306E\u8B66\u544A\u3092\u7121\u8996\u3057\u3066\u3088\u3044\uFF09"
2869
+ );
2870
+ }
2871
+ }
2872
+ }
2873
+ return appendAgencyInviteToken(
2874
+ `${baseUrl}/signup?redirect=${encodeURIComponent(redirect)}&service=${encodeURIComponent(serviceCode)}`,
2875
+ inviteToken
2876
+ );
2754
2877
  }
2755
2878
  function getLogoutUrl(postLogoutRedirectUri) {
2756
2879
  const url = new URL(`${baseUrl}${AUTH_LOGOUT_ENDPOINT}`);
@@ -2766,7 +2889,7 @@ function createRedirectMethods(deps) {
2766
2889
  return { success: false, error: "SSR \u74B0\u5883\u3067\u306F\u30EA\u30C0\u30A4\u30EC\u30AF\u30C8\u3067\u304D\u307E\u305B\u3093" };
2767
2890
  }
2768
2891
  const logoutUrl = getLogoutUrl(postLogoutRedirectUri);
2769
- logger.debug("Redirecting to logout:", logoutUrl);
2892
+ logger.debug("Redirecting to logout:", redactAgencyInviteToken(logoutUrl));
2770
2893
  window.location.href = logoutUrl;
2771
2894
  return { success: true };
2772
2895
  }
@@ -4320,9 +4443,9 @@ function createServerAuthClient(config) {
4320
4443
  var FFID_SESSION_COOKIE_NAME = "ffid_session";
4321
4444
  var DEFAULT_PATH = "/";
4322
4445
  var DEFAULT_SAME_SITE = "lax";
4323
- var HOURS_PER_DAY2 = 24;
4446
+ var HOURS_PER_DAY3 = 24;
4324
4447
  var DEFAULT_MAX_AGE_DAYS = 30;
4325
- var DEFAULT_MAX_AGE_SECONDS = 60 * 60 * HOURS_PER_DAY2 * DEFAULT_MAX_AGE_DAYS;
4448
+ var DEFAULT_MAX_AGE_SECONDS = 60 * 60 * HOURS_PER_DAY3 * DEFAULT_MAX_AGE_DAYS;
4326
4449
  var BASE64_GROUP = 4;
4327
4450
  function base64UrlEncode2(input) {
4328
4451
  const bytes = new TextEncoder().encode(input);
@@ -1,5 +1,5 @@
1
- import { F as FFIDLogger, a as FFIDError, b as FFIDCacheAdapter, c as FFIDVerifyAccessTokenOptions, d as FFIDApiResponse, e as FFIDOAuthUserInfo, f as FFIDCatalogEnvironment, g as FFIDBillingInterval, h as FFIDTaxBehavior, i as FFIDPublishedCatalog } from '../ffid-client-BFgoVPOZ.cjs';
2
- export { j as FFIDAddMemberParams, k as FFIDAddMemberRequest, l as FFIDAddMemberResponse, m as FFIDAllowedLogoMimeType, n as FFIDAssignableMemberRole, o as FFIDCacheConfig, p as FFIDCatalogPublication, q as FFIDCatalogPublicationStatus, r as FFIDClient, s as FFIDCompanySizeCode, t as FFIDConfig, u as FFIDIndustryCode, v as FFIDListMembersResponse, w as FFIDListingConsentInput, x as FFIDLogoWallEntry, y as FFIDLogoWallResponse, z as FFIDMemberRole, A as FFIDOrganization, B as FFIDOrganizationLogoUploadResponse, C as FFIDOrganizationMember, D as FFIDOrganizationProfile, E as FFIDOrganizationProfileErrorCode, G as FFIDOrganizationProfileResponse, H as FFIDOtpSendResponse, I as FFIDOtpVerifyResponse, J as FFIDPasswordResetConfirmResponse, K as FFIDPasswordResetResponse, L as FFIDPasswordResetVerifyResponse, M as FFIDProfileCallOptions, N as FFIDProvisionMemberPlan, O as FFIDProvisionMemberPlanStatus, P as FFIDProvisionMemberResult, Q as FFIDProvisionMemberStatus, R as FFIDProvisionOrganizationDryRun, S as FFIDProvisionOrganizationMemberInput, T as FFIDProvisionOrganizationOutcome, U as FFIDProvisionOrganizationParams, V as FFIDProvisionOrganizationResponse, W as FFIDProvisionUserDryRun, X as FFIDProvisionUserOutcome, Y as FFIDProvisionUserParams, Z as FFIDProvisionUserProfileInput, _ as FFIDProvisionUserResponse, $ as FFIDProvisionedOrganization, a0 as FFIDProvisionedUser, a1 as FFIDPublishedPlan, a2 as FFIDRemoveMemberResponse, a3 as FFIDResetSessionResponse, a4 as FFIDSubscription, a5 as FFIDUpdateMemberRoleResponse, a6 as FFIDUpdateUserProfileRequest, a7 as FFIDUploadOrganizationLogoParams, a8 as FFIDUpsertOrganizationProfileParams, a9 as FFIDUpsertOrganizationProfileRequest, aa as FFIDUser, ab as FFIDUserProfile, ac as FFID_ALLOWED_ORGANIZATION_LOGO_MIME_TYPES, ad as FFID_CATALOG_ENVIRONMENTS, ae as FFID_CATALOG_PUBLICATION_STATUSES, af as FFID_COMPANY_SIZE_CODES, ag as FFID_INDUSTRY_CODES, ah as FFID_MAX_ORGANIZATION_LOGO_FILE_SIZE_BYTES, ai as FFID_MAX_ORGANIZATION_LOGO_FILE_SIZE_MB, aj as FFID_ORGANIZATION_PROFILE_ERROR_CODES, ak as TokenData, al as TokenStore, am as createFFIDClient, an as createTokenStore } from '../ffid-client-BFgoVPOZ.cjs';
1
+ import { F as FFIDLogger, a as FFIDError, b as FFIDCacheAdapter, c as FFIDVerifyAccessTokenOptions, d as FFIDApiResponse, e as FFIDOAuthUserInfo, f as FFIDCatalogEnvironment, g as FFIDBillingInterval, h as FFIDTaxBehavior, i as FFIDPublishedCatalog } from '../ffid-client-BaEmw7jP.cjs';
2
+ export { j as FFIDAddMemberParams, k as FFIDAddMemberRequest, l as FFIDAddMemberResponse, m as FFIDAllowedLogoMimeType, n as FFIDAssignableMemberRole, o as FFIDCacheConfig, p as FFIDCatalogPublication, q as FFIDCatalogPublicationStatus, r as FFIDClient, s as FFIDCompanySizeCode, t as FFIDConfig, u as FFIDIndustryCode, v as FFIDListMembersResponse, w as FFIDListingConsentInput, x as FFIDLogoWallEntry, y as FFIDLogoWallResponse, z as FFIDMemberRole, A as FFIDOrganization, B as FFIDOrganizationLogoUploadResponse, C as FFIDOrganizationMember, D as FFIDOrganizationProfile, E as FFIDOrganizationProfileErrorCode, G as FFIDOrganizationProfileResponse, H as FFIDOtpSendResponse, I as FFIDOtpVerifyResponse, J as FFIDPasswordResetConfirmResponse, K as FFIDPasswordResetResponse, L as FFIDPasswordResetVerifyResponse, M as FFIDProfileCallOptions, N as FFIDProvisionMemberPlan, O as FFIDProvisionMemberPlanStatus, P as FFIDProvisionMemberResult, Q as FFIDProvisionMemberStatus, R as FFIDProvisionOrganizationDryRun, S as FFIDProvisionOrganizationMemberInput, T as FFIDProvisionOrganizationOutcome, U as FFIDProvisionOrganizationParams, V as FFIDProvisionOrganizationResponse, W as FFIDProvisionUserDryRun, X as FFIDProvisionUserOutcome, Y as FFIDProvisionUserParams, Z as FFIDProvisionUserProfileInput, _ as FFIDProvisionUserResponse, $ as FFIDProvisionedOrganization, a0 as FFIDProvisionedUser, a1 as FFIDPublishedPlan, a2 as FFIDRemoveMemberResponse, a3 as FFIDResetSessionResponse, a4 as FFIDSubscription, a5 as FFIDUpdateMemberRoleResponse, a6 as FFIDUpdateUserProfileRequest, a7 as FFIDUploadOrganizationLogoParams, a8 as FFIDUpsertOrganizationProfileParams, a9 as FFIDUpsertOrganizationProfileRequest, aa as FFIDUser, ab as FFIDUserProfile, ac as FFID_ALLOWED_ORGANIZATION_LOGO_MIME_TYPES, ad as FFID_CATALOG_ENVIRONMENTS, ae as FFID_CATALOG_PUBLICATION_STATUSES, af as FFID_COMPANY_SIZE_CODES, ag as FFID_INDUSTRY_CODES, ah as FFID_MAX_ORGANIZATION_LOGO_FILE_SIZE_BYTES, ai as FFID_MAX_ORGANIZATION_LOGO_FILE_SIZE_MB, aj as FFID_ORGANIZATION_PROFILE_ERROR_CODES, ak as TokenData, al as TokenStore, am as createFFIDClient, an as createTokenStore } from '../ffid-client-BaEmw7jP.cjs';
3
3
  export { D as DEFAULT_API_BASE_URL, a as DEFAULT_OAUTH_SCOPES } from '../constants-Ce2eXQtB.cjs';
4
4
  import { z } from 'zod';
5
5
  import '../types-Cjb9J0rL.cjs';
@@ -1,5 +1,5 @@
1
- import { F as FFIDLogger, a as FFIDError, b as FFIDCacheAdapter, c as FFIDVerifyAccessTokenOptions, d as FFIDApiResponse, e as FFIDOAuthUserInfo, f as FFIDCatalogEnvironment, g as FFIDBillingInterval, h as FFIDTaxBehavior, i as FFIDPublishedCatalog } from '../ffid-client-QNEFA3mv.js';
2
- export { j as FFIDAddMemberParams, k as FFIDAddMemberRequest, l as FFIDAddMemberResponse, m as FFIDAllowedLogoMimeType, n as FFIDAssignableMemberRole, o as FFIDCacheConfig, p as FFIDCatalogPublication, q as FFIDCatalogPublicationStatus, r as FFIDClient, s as FFIDCompanySizeCode, t as FFIDConfig, u as FFIDIndustryCode, v as FFIDListMembersResponse, w as FFIDListingConsentInput, x as FFIDLogoWallEntry, y as FFIDLogoWallResponse, z as FFIDMemberRole, A as FFIDOrganization, B as FFIDOrganizationLogoUploadResponse, C as FFIDOrganizationMember, D as FFIDOrganizationProfile, E as FFIDOrganizationProfileErrorCode, G as FFIDOrganizationProfileResponse, H as FFIDOtpSendResponse, I as FFIDOtpVerifyResponse, J as FFIDPasswordResetConfirmResponse, K as FFIDPasswordResetResponse, L as FFIDPasswordResetVerifyResponse, M as FFIDProfileCallOptions, N as FFIDProvisionMemberPlan, O as FFIDProvisionMemberPlanStatus, P as FFIDProvisionMemberResult, Q as FFIDProvisionMemberStatus, R as FFIDProvisionOrganizationDryRun, S as FFIDProvisionOrganizationMemberInput, T as FFIDProvisionOrganizationOutcome, U as FFIDProvisionOrganizationParams, V as FFIDProvisionOrganizationResponse, W as FFIDProvisionUserDryRun, X as FFIDProvisionUserOutcome, Y as FFIDProvisionUserParams, Z as FFIDProvisionUserProfileInput, _ as FFIDProvisionUserResponse, $ as FFIDProvisionedOrganization, a0 as FFIDProvisionedUser, a1 as FFIDPublishedPlan, a2 as FFIDRemoveMemberResponse, a3 as FFIDResetSessionResponse, a4 as FFIDSubscription, a5 as FFIDUpdateMemberRoleResponse, a6 as FFIDUpdateUserProfileRequest, a7 as FFIDUploadOrganizationLogoParams, a8 as FFIDUpsertOrganizationProfileParams, a9 as FFIDUpsertOrganizationProfileRequest, aa as FFIDUser, ab as FFIDUserProfile, ac as FFID_ALLOWED_ORGANIZATION_LOGO_MIME_TYPES, ad as FFID_CATALOG_ENVIRONMENTS, ae as FFID_CATALOG_PUBLICATION_STATUSES, af as FFID_COMPANY_SIZE_CODES, ag as FFID_INDUSTRY_CODES, ah as FFID_MAX_ORGANIZATION_LOGO_FILE_SIZE_BYTES, ai as FFID_MAX_ORGANIZATION_LOGO_FILE_SIZE_MB, aj as FFID_ORGANIZATION_PROFILE_ERROR_CODES, ak as TokenData, al as TokenStore, am as createFFIDClient, an as createTokenStore } from '../ffid-client-QNEFA3mv.js';
1
+ import { F as FFIDLogger, a as FFIDError, b as FFIDCacheAdapter, c as FFIDVerifyAccessTokenOptions, d as FFIDApiResponse, e as FFIDOAuthUserInfo, f as FFIDCatalogEnvironment, g as FFIDBillingInterval, h as FFIDTaxBehavior, i as FFIDPublishedCatalog } from '../ffid-client-5GAPcRIG.js';
2
+ export { j as FFIDAddMemberParams, k as FFIDAddMemberRequest, l as FFIDAddMemberResponse, m as FFIDAllowedLogoMimeType, n as FFIDAssignableMemberRole, o as FFIDCacheConfig, p as FFIDCatalogPublication, q as FFIDCatalogPublicationStatus, r as FFIDClient, s as FFIDCompanySizeCode, t as FFIDConfig, u as FFIDIndustryCode, v as FFIDListMembersResponse, w as FFIDListingConsentInput, x as FFIDLogoWallEntry, y as FFIDLogoWallResponse, z as FFIDMemberRole, A as FFIDOrganization, B as FFIDOrganizationLogoUploadResponse, C as FFIDOrganizationMember, D as FFIDOrganizationProfile, E as FFIDOrganizationProfileErrorCode, G as FFIDOrganizationProfileResponse, H as FFIDOtpSendResponse, I as FFIDOtpVerifyResponse, J as FFIDPasswordResetConfirmResponse, K as FFIDPasswordResetResponse, L as FFIDPasswordResetVerifyResponse, M as FFIDProfileCallOptions, N as FFIDProvisionMemberPlan, O as FFIDProvisionMemberPlanStatus, P as FFIDProvisionMemberResult, Q as FFIDProvisionMemberStatus, R as FFIDProvisionOrganizationDryRun, S as FFIDProvisionOrganizationMemberInput, T as FFIDProvisionOrganizationOutcome, U as FFIDProvisionOrganizationParams, V as FFIDProvisionOrganizationResponse, W as FFIDProvisionUserDryRun, X as FFIDProvisionUserOutcome, Y as FFIDProvisionUserParams, Z as FFIDProvisionUserProfileInput, _ as FFIDProvisionUserResponse, $ as FFIDProvisionedOrganization, a0 as FFIDProvisionedUser, a1 as FFIDPublishedPlan, a2 as FFIDRemoveMemberResponse, a3 as FFIDResetSessionResponse, a4 as FFIDSubscription, a5 as FFIDUpdateMemberRoleResponse, a6 as FFIDUpdateUserProfileRequest, a7 as FFIDUploadOrganizationLogoParams, a8 as FFIDUpsertOrganizationProfileParams, a9 as FFIDUpsertOrganizationProfileRequest, aa as FFIDUser, ab as FFIDUserProfile, ac as FFID_ALLOWED_ORGANIZATION_LOGO_MIME_TYPES, ad as FFID_CATALOG_ENVIRONMENTS, ae as FFID_CATALOG_PUBLICATION_STATUSES, af as FFID_COMPANY_SIZE_CODES, ag as FFID_INDUSTRY_CODES, ah as FFID_MAX_ORGANIZATION_LOGO_FILE_SIZE_BYTES, ai as FFID_MAX_ORGANIZATION_LOGO_FILE_SIZE_MB, aj as FFID_ORGANIZATION_PROFILE_ERROR_CODES, ak as TokenData, al as TokenStore, am as createFFIDClient, an as createTokenStore } from '../ffid-client-5GAPcRIG.js';
3
3
  export { D as DEFAULT_API_BASE_URL, a as DEFAULT_OAUTH_SCOPES } from '../constants-Ce2eXQtB.js';
4
4
  import { z } from 'zod';
5
5
  import '../types-Cjb9J0rL.js';
@@ -154,7 +154,7 @@ function normalizeUserinfo(raw) {
154
154
  emailVerified: raw.email_verified,
155
155
  name: raw.name,
156
156
  picture: raw.picture,
157
- companyName: raw.company_name ?? null,
157
+ companyName: null,
158
158
  department: raw.department ?? null,
159
159
  position: raw.position ?? null,
160
160
  phoneNumber: raw.phone_number ?? null,
@@ -222,13 +222,18 @@ function mapUserinfoSubscriptionToSession(userinfo, serviceCode) {
222
222
  ];
223
223
  }
224
224
  var JWKS_ENDPOINT = "/.well-known/jwks.json";
225
- var JWT_ISSUER = "https://id.feelflow.net";
225
+ var LEGACY_JWT_ISSUER = "https://id.feelflow.net";
226
226
  var JWT_ALGORITHM = "ES256";
227
227
  var JWT_CLOCK_TOLERANCE_SECONDS = 30;
228
+ function resolveAcceptedIssuers(baseUrl) {
229
+ const deployedOrigin = new URL(baseUrl).origin;
230
+ return deployedOrigin === LEGACY_JWT_ISSUER ? [LEGACY_JWT_ISSUER] : [deployedOrigin, LEGACY_JWT_ISSUER];
231
+ }
228
232
  function createJwtVerifier(deps) {
229
233
  const { baseUrl, serviceCode, logger, createError, errorCodes } = deps;
230
234
  const jwksUrl = new URL(JWKS_ENDPOINT, baseUrl);
231
235
  const jwks = createRemoteJWKSet(jwksUrl);
236
+ const acceptedIssuers = resolveAcceptedIssuers(baseUrl);
232
237
  async function verifyJwt(accessToken) {
233
238
  if (!accessToken || !accessToken.trim()) {
234
239
  return {
@@ -241,7 +246,7 @@ function createJwtVerifier(deps) {
241
246
  try {
242
247
  const { payload } = await jwtVerify(accessToken, jwks, {
243
248
  algorithms: [JWT_ALGORITHM],
244
- issuer: JWT_ISSUER,
249
+ issuer: acceptedIssuers,
245
250
  audience: serviceCode,
246
251
  clockTolerance: JWT_CLOCK_TOLERANCE_SECONDS
247
252
  });
@@ -458,7 +463,7 @@ function createVerifyAccessToken(deps) {
458
463
  email_verified: introspectResponse.email_verified,
459
464
  name: introspectResponse.name ?? null,
460
465
  picture: introspectResponse.picture ?? null,
461
- company_name: introspectResponse.company_name ?? null,
466
+ company_name: null,
462
467
  department: introspectResponse.department ?? null,
463
468
  position: introspectResponse.position ?? null,
464
469
  phone_number: introspectResponse.phone_number ?? null,
@@ -709,6 +714,24 @@ function validateSeatChangePreview(preview) {
709
714
  `SDK: server returned malformed SeatChangePreview \u2014 estimateReason must be one of 'no_stripe_data' | 'custom_pricing' | 'stripe_error' (got ${JSON.stringify(p.estimateReason)})`
710
715
  );
711
716
  }
717
+ if (p.willApplyAtPeriodEnd !== void 0 && typeof p.willApplyAtPeriodEnd !== "boolean") {
718
+ throw new FFIDSDKError(
719
+ MALFORMED_SEAT_PREVIEW_CODE,
720
+ `SDK: server returned malformed SeatChangePreview \u2014 willApplyAtPeriodEnd must be boolean when present (got ${typeof p.willApplyAtPeriodEnd})`
721
+ );
722
+ }
723
+ if (p.effectiveDate !== void 0 && p.effectiveDate !== null && typeof p.effectiveDate !== "string") {
724
+ throw new FFIDSDKError(
725
+ MALFORMED_SEAT_PREVIEW_CODE,
726
+ `SDK: server returned malformed SeatChangePreview \u2014 effectiveDate must be string or null when present (got ${typeof p.effectiveDate})`
727
+ );
728
+ }
729
+ if (p.willApplyAtPeriodEnd === true && p.proratedAmount !== 0) {
730
+ throw new FFIDSDKError(
731
+ MALFORMED_SEAT_PREVIEW_CODE,
732
+ `SDK: server returned malformed SeatChangePreview \u2014 willApplyAtPeriodEnd=true requires proratedAmount=0 (got ${JSON.stringify(p.proratedAmount)})`
733
+ );
734
+ }
712
735
  if (!Array.isArray(p.lineItems)) {
713
736
  throw new FFIDSDKError(
714
737
  MALFORMED_SEAT_PREVIEW_CODE,
@@ -1349,7 +1372,7 @@ var FFID_ORGANIZATION_PROFILE_ERROR_CODES = {
1349
1372
  };
1350
1373
 
1351
1374
  // src/client/version-check.ts
1352
- var SDK_VERSION = "7.1.0";
1375
+ var SDK_VERSION = "8.0.0";
1353
1376
  var SDK_USER_AGENT = `FFID-SDK/${SDK_VERSION} (TypeScript)`;
1354
1377
  var SDK_VERSION_HEADER = "X-FFID-SDK-Version";
1355
1378
  function sdkHeaders() {
@@ -2508,6 +2531,86 @@ function cleanupStateStorage(logger) {
2508
2531
  }
2509
2532
  }
2510
2533
 
2534
+ // src/client/agency-invite-token.ts
2535
+ var AGENCY_INVITE_TOKEN_PARAM = "invite";
2536
+ var AGENCY_INVITE_COOKIE_NAME = "ffid_invite";
2537
+ var AGENCY_INVITE_TOKEN_REDACTION = "[REDACTED]";
2538
+ function hasDocument() {
2539
+ return typeof document !== "undefined";
2540
+ }
2541
+ function toQueryString(source) {
2542
+ const queryStart = source.indexOf("?");
2543
+ if (queryStart < 0) return "";
2544
+ const afterQuery = source.slice(queryStart);
2545
+ const fragmentStart = afterQuery.indexOf("#");
2546
+ return fragmentStart < 0 ? afterQuery : afterQuery.slice(0, fragmentStart);
2547
+ }
2548
+ function readTokenFromUrl(options) {
2549
+ const source = options.url ?? (typeof window !== "undefined" ? window.location.search : void 0);
2550
+ if (source === void 0) return void 0;
2551
+ const params = new URLSearchParams(toQueryString(source));
2552
+ return params.get(AGENCY_INVITE_TOKEN_PARAM)?.trim() || void 0;
2553
+ }
2554
+ function readAgencyInviteCookie() {
2555
+ if (!hasDocument()) return void 0;
2556
+ const prefix = `${AGENCY_INVITE_COOKIE_NAME}=`;
2557
+ const raw = document.cookie.split(";").map((entry) => entry.trim()).find((entry) => entry.startsWith(prefix));
2558
+ if (!raw) return void 0;
2559
+ try {
2560
+ return decodeURIComponent(raw.slice(prefix.length)).trim() || void 0;
2561
+ } catch {
2562
+ return void 0;
2563
+ }
2564
+ }
2565
+ function readAgencyInviteToken(options = {}) {
2566
+ return readTokenFromUrl(options) ?? readAgencyInviteCookie();
2567
+ }
2568
+ var REDACT_BOUNDARIES = [
2569
+ "%2526",
2570
+ "%2523",
2571
+ "%253B",
2572
+ "%26",
2573
+ "%23",
2574
+ "%3B",
2575
+ "&",
2576
+ "#",
2577
+ ";"
2578
+ ];
2579
+ var REDACT_LEADS = ["%253F", "%3F", "?", ...REDACT_BOUNDARIES];
2580
+ var REDACT_SEPARATORS = ["%253D", "%3D", "="];
2581
+ function escapeForRegExp(literal) {
2582
+ return literal.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2583
+ }
2584
+ var REDACT_PATTERN = new RegExp(
2585
+ [
2586
+ // 先頭区切り。`\b` が使えないのは、入れ子形 (`%3Finvite%3D`) では `F` と `i` が
2587
+ // どちらも単語文字なので単語境界が存在しないため。
2588
+ `(^|${REDACT_LEADS.map(escapeForRegExp).join("|")})`,
2589
+ // パラメータ名と区切りはキャプチャして再出力する。`i` フラグがあるので、
2590
+ // 入力の `INVITE=` / `%3d` などの元の表記を保つ必要がある。
2591
+ `(${AGENCY_INVITE_TOKEN_PARAM})`,
2592
+ `(${REDACT_SEPARATORS.map(escapeForRegExp).join("|")})`,
2593
+ // 値。境界の直前まで貪欲に食う。`\s` は終端にしない — 生の空白や改行を含む
2594
+ // トークンでマスクが途中で切れ、後半が漏れる。
2595
+ `(?:(?!${REDACT_BOUNDARIES.map(escapeForRegExp).join("|")})[^&#;])*`
2596
+ ].join(""),
2597
+ "gi"
2598
+ );
2599
+ function redactAgencyInviteToken(url) {
2600
+ return url.replace(
2601
+ REDACT_PATTERN,
2602
+ // 文字列 replacement(`'$1$2$3[REDACTED]'`)にしないのは、
2603
+ // `AGENCY_INVITE_TOKEN_REDACTION` に `$` が入った瞬間に置換パターンとして
2604
+ // 解釈される罠を作らないため。
2605
+ (_match, lead, name, separator) => `${lead}${name}${separator}${AGENCY_INVITE_TOKEN_REDACTION}`
2606
+ );
2607
+ }
2608
+ function appendAgencyInviteToken(signupUrl, token) {
2609
+ if (token === void 0) return signupUrl;
2610
+ const separator = signupUrl.includes("?") ? "&" : "?";
2611
+ return `${signupUrl}${separator}${AGENCY_INVITE_TOKEN_PARAM}=${encodeURIComponent(token)}`;
2612
+ }
2613
+
2511
2614
  // src/client/redirect.ts
2512
2615
  var OAUTH_AUTHORIZE_ENDPOINT = "/api/v1/oauth/authorize";
2513
2616
  var AUTH_LOGOUT_ENDPOINT = "/api/v1/auth/logout";
@@ -2520,6 +2623,7 @@ var REDIRECT_LOOP_WINDOW_MS = 6e4;
2520
2623
  var REDIRECT_LOOP_THRESHOLD = 3;
2521
2624
  var storageReadFailureLogged = false;
2522
2625
  var storageWriteFailureLogged = false;
2626
+ var ssrInviteTokenUnavailableLogged = false;
2523
2627
  function isLoopDetectionDegraded() {
2524
2628
  return storageReadFailureLogged || storageWriteFailureLogged;
2525
2629
  }
@@ -2698,12 +2802,16 @@ function createRedirectMethods(deps) {
2698
2802
  }
2699
2803
  if (options?.screenHint === SCREEN_HINT_SIGNUP) {
2700
2804
  params.set(SCREEN_HINT_PARAM, SCREEN_HINT_SIGNUP);
2805
+ const inviteToken = readAgencyInviteToken();
2806
+ if (inviteToken !== void 0) {
2807
+ params.set(AGENCY_INVITE_TOKEN_PARAM, inviteToken);
2808
+ }
2701
2809
  }
2702
2810
  if (options?.prompt) {
2703
2811
  params.set("prompt", options.prompt);
2704
2812
  }
2705
2813
  const authorizeUrl = `${baseUrl}${OAUTH_AUTHORIZE_ENDPOINT}?${params.toString()}`;
2706
- logger.debug("Redirecting to authorize:", authorizeUrl);
2814
+ logger.debug("Redirecting to authorize:", redactAgencyInviteToken(authorizeUrl));
2707
2815
  recordRedirectAttempt(authorizeKey, now, logger);
2708
2816
  try {
2709
2817
  window.location.href = authorizeUrl;
@@ -2722,7 +2830,7 @@ function createRedirectMethods(deps) {
2722
2830
  return redirectToAuthorize(options);
2723
2831
  }
2724
2832
  const targetUrl = options?.screenHint === SCREEN_HINT_SIGNUP ? getSignupUrl() : getLoginUrl(void 0, options?.prompt ? { prompt: options.prompt } : void 0);
2725
- logger.debug("Redirecting to auth page:", targetUrl);
2833
+ logger.debug("Redirecting to auth page:", redactAgencyInviteToken(targetUrl));
2726
2834
  window.location.href = targetUrl;
2727
2835
  return { success: true };
2728
2836
  }
@@ -2739,7 +2847,7 @@ function createRedirectMethods(deps) {
2739
2847
  const promptSuffix = options?.prompt ? `&prompt=${encodeURIComponent(options.prompt)}` : "";
2740
2848
  return `${baseUrl}/login?redirect=${encodeURIComponent(redirect)}&service=${encodeURIComponent(serviceCode)}${promptSuffix}`;
2741
2849
  }
2742
- function getSignupUrl(redirectUrl) {
2850
+ function getSignupUrl(redirectUrl, options) {
2743
2851
  let redirect;
2744
2852
  if (redirectUrl != null) {
2745
2853
  redirect = redirectUrl;
@@ -2749,7 +2857,22 @@ function createRedirectMethods(deps) {
2749
2857
  logger.warn("getSignupUrl: SSR \u74B0\u5883\u3067 redirectUrl \u304C\u672A\u6307\u5B9A\u306E\u305F\u3081\u7A7A\u6587\u5B57\u306B\u30D5\u30A9\u30FC\u30EB\u30D0\u30C3\u30AF\u3057\u307E\u3059");
2750
2858
  redirect = "";
2751
2859
  }
2752
- return `${baseUrl}/signup?redirect=${encodeURIComponent(redirect)}&service=${encodeURIComponent(serviceCode)}`;
2860
+ let inviteToken = options?.inviteToken;
2861
+ if (inviteToken === void 0) {
2862
+ inviteToken = readAgencyInviteToken();
2863
+ if (inviteToken === void 0 && typeof window === "undefined") {
2864
+ if (!ssrInviteTokenUnavailableLogged) {
2865
+ ssrInviteTokenUnavailableLogged = true;
2866
+ logger.warn(
2867
+ "[FFID SDK] getSignupUrl: SSR \u3067\u306F\u62DB\u5F85\u30C8\u30FC\u30AF\u30F3\uFF08invite\uFF09\u3092\u8AAD\u3081\u307E\u305B\u3093\u3002\u4EE3\u7406\u5E97\u306E\u62DB\u5F85\u30EA\u30F3\u30AF\u3092\u4F7F\u3046\u5834\u5408\u306F options.inviteToken \u306B\u6E21\u3057\u3066\u304F\u3060\u3055\u3044\uFF08\u4F7F\u3063\u3066\u3044\u306A\u3044\u5834\u5408\u306F\u3053\u306E\u8B66\u544A\u3092\u7121\u8996\u3057\u3066\u3088\u3044\uFF09"
2868
+ );
2869
+ }
2870
+ }
2871
+ }
2872
+ return appendAgencyInviteToken(
2873
+ `${baseUrl}/signup?redirect=${encodeURIComponent(redirect)}&service=${encodeURIComponent(serviceCode)}`,
2874
+ inviteToken
2875
+ );
2753
2876
  }
2754
2877
  function getLogoutUrl(postLogoutRedirectUri) {
2755
2878
  const url = new URL(`${baseUrl}${AUTH_LOGOUT_ENDPOINT}`);
@@ -2765,7 +2888,7 @@ function createRedirectMethods(deps) {
2765
2888
  return { success: false, error: "SSR \u74B0\u5883\u3067\u306F\u30EA\u30C0\u30A4\u30EC\u30AF\u30C8\u3067\u304D\u307E\u305B\u3093" };
2766
2889
  }
2767
2890
  const logoutUrl = getLogoutUrl(postLogoutRedirectUri);
2768
- logger.debug("Redirecting to logout:", logoutUrl);
2891
+ logger.debug("Redirecting to logout:", redactAgencyInviteToken(logoutUrl));
2769
2892
  window.location.href = logoutUrl;
2770
2893
  return { success: true };
2771
2894
  }
@@ -4319,9 +4442,9 @@ function createServerAuthClient(config) {
4319
4442
  var FFID_SESSION_COOKIE_NAME = "ffid_session";
4320
4443
  var DEFAULT_PATH = "/";
4321
4444
  var DEFAULT_SAME_SITE = "lax";
4322
- var HOURS_PER_DAY2 = 24;
4445
+ var HOURS_PER_DAY3 = 24;
4323
4446
  var DEFAULT_MAX_AGE_DAYS = 30;
4324
- var DEFAULT_MAX_AGE_SECONDS = 60 * 60 * HOURS_PER_DAY2 * DEFAULT_MAX_AGE_DAYS;
4447
+ var DEFAULT_MAX_AGE_SECONDS = 60 * 60 * HOURS_PER_DAY3 * DEFAULT_MAX_AGE_DAYS;
4325
4448
  var BASE64_GROUP = 4;
4326
4449
  function base64UrlEncode2(input) {
4327
4450
  const bytes = new TextEncoder().encode(input);
@@ -1,4 +1,4 @@
1
- import { r as FFIDClient, e as FFIDOAuthUserInfo } from '../../ffid-client-BFgoVPOZ.cjs';
1
+ import { r as FFIDClient, e as FFIDOAuthUserInfo } from '../../ffid-client-BaEmw7jP.cjs';
2
2
  import '../../types-Cjb9J0rL.cjs';
3
3
 
4
4
  /**
@@ -1,4 +1,4 @@
1
- import { r as FFIDClient, e as FFIDOAuthUserInfo } from '../../ffid-client-QNEFA3mv.js';
1
+ import { r as FFIDClient, e as FFIDOAuthUserInfo } from '../../ffid-client-5GAPcRIG.js';
2
2
  import '../../types-Cjb9J0rL.js';
3
3
 
4
4
  /**
@@ -47,7 +47,7 @@ declare class FFIDWebhookPayloadError extends FFIDWebhookError {
47
47
  */
48
48
 
49
49
  /** All supported webhook event types */
50
- type FFIDWebhookEventType = 'subscription.created' | 'subscription.updated' | 'subscription.canceled' | 'subscription.downgrade_scheduled' | 'subscription.downgrade_applied' | 'subscription.downgrade_canceled' | 'subscription.trial_ending' | 'subscription.payment_failed' | 'user.created' | 'user.updated' | 'user.deleted' | 'user.deletion_requested' | 'organization.created' | 'organization.updated' | 'organization.member.added' | 'organization.member.removed' | 'organization.member.role_changed' | 'legal.document.updated' | 'legal.agreement.required' | 'system.maintenance.scheduled' | 'system.maintenance.started' | 'system.maintenance.completed' | 'announcement.published' | 'test.ping';
50
+ type FFIDWebhookEventType = 'subscription.created' | 'subscription.reactivated' | 'subscription.updated' | 'subscription.canceled' | 'subscription.downgrade_scheduled' | 'subscription.downgrade_applied' | 'subscription.downgrade_canceled' | 'subscription.trial_ending' | 'subscription.payment_failed' | 'user.created' | 'user.updated' | 'user.deleted' | 'user.deletion_requested' | 'organization.created' | 'organization.updated' | 'organization.member.added' | 'organization.member.removed' | 'organization.member.role_changed' | 'legal.document.updated' | 'legal.agreement.required' | 'system.maintenance.scheduled' | 'system.maintenance.started' | 'system.maintenance.completed' | 'announcement.published' | 'test.ping';
51
51
  interface FFIDSubscriptionCreatedPayload {
52
52
  subscriptionId: string;
53
53
  organizationId?: string;
@@ -65,6 +65,46 @@ interface FFIDSubscriptionUpdatedPayload {
65
65
  effectiveStatus?: EffectiveSubscriptionStatus;
66
66
  gracePeriodEndsAt?: string | null;
67
67
  }
68
+ /**
69
+ * `subscription.reactivated` (8.0.0): a cancellation reserved until the period end was
70
+ * withdrawn in the FFID portal, so the subscription continues. It pairs with the
71
+ * reservation-time `subscription.canceled` (`source: 'user_initiated'`,
72
+ * `reactivatable: true`). Sent only for contracts billed through Stripe.
73
+ */
74
+ interface FFIDSubscriptionReactivatedPayload {
75
+ subscriptionId: string;
76
+ organizationId?: string;
77
+ /** Plan code */
78
+ plan?: string;
79
+ status?: string;
80
+ effectiveStatus?: EffectiveSubscriptionStatus;
81
+ gracePeriodEndsAt?: string | null;
82
+ stripeSubscriptionId: string;
83
+ stripeCustomerId: string;
84
+ /** Code of the service the subscription belongs to (resolved by FFID when the event is sent) */
85
+ serviceCode?: string;
86
+ }
87
+ /**
88
+ * `subscription.canceled`.
89
+ *
90
+ * A contract billed through Stripe is canceled in two steps (FFID #6327), so **two events
91
+ * arrive per cancellation**:
92
+ *
93
+ * 1. At the reservation — `source: 'user_initiated'`, `reactivatable: true`, `cancelAt` /
94
+ * `expiredAt` = the period end, no `effectiveStatus`. **Access continues until `cancelAt`**;
95
+ * do not revoke it here. The reservation may still be withdrawn
96
+ * (`subscription.reactivated`).
97
+ * 2. At the period end — `source: 'stripe_confirmed'`, `effectiveStatus: 'canceled'`. Revoke
98
+ * access on this one.
99
+ *
100
+ * **Rule for consumers:** only an event with `source === 'user_initiated'` **and** a
101
+ * `cancelAt` is a reservation (keep access until `cancelAt`). Every other
102
+ * `subscription.canceled` ends access: the final `stripe_confirmed`, an immediate
103
+ * cancellation (`user_initiated` without `cancelAt` — a contract that cannot be reserved,
104
+ * e.g. no Stripe subscription or no period end yet; `expiredAt` is the cancellation time),
105
+ * and `payment_failure_auto_cancel` (automatic cancellation after unpaid invoices).
106
+ * Do not key the decision on `effectiveStatus`: it is set only on Stripe's deletion notice.
107
+ */
68
108
  interface FFIDSubscriptionCanceledPayload {
69
109
  /** FFID subscription UUID. */
70
110
  subscriptionId: string;
@@ -80,20 +120,28 @@ interface FFIDSubscriptionCanceledPayload {
80
120
  */
81
121
  stripeSubscriptionId: string | null;
82
122
  reason?: string;
123
+ /** When a reserved cancellation takes effect (the period end) */
83
124
  cancelAt?: string;
84
125
  /**
85
126
  * Origin of the cancellation. Always emitted by the FFID server — required
86
- * by the wire shape in `SubscriptionCanceledPayload`.
127
+ * by the wire shape in `SubscriptionCanceledPayload`. Distinguishes the reservation
128
+ * (`user_initiated`) from the final event (`stripe_confirmed`); see the interface doc.
87
129
  */
88
130
  source: 'user_initiated' | 'stripe_confirmed' | 'payment_failure_auto_cancel';
89
131
  /** Whether this cancellation can be resumed via resubscribe flow. */
90
132
  reactivatable?: boolean;
91
133
  /**
92
- * ISO timestamp at which access was actually lost. May differ from `cancelAt`
93
- * when Stripe runs out the paid period before emitting the deletion event.
134
+ * ISO timestamp at which access is (or was) lost. On the reservation event this is the
135
+ * future period end. May differ from `cancelAt` when Stripe runs out the paid period
136
+ * before emitting the deletion event.
94
137
  */
95
138
  expiredAt?: string;
96
- /** Cancellation always leaves the canonical access-control status as `canceled`. */
139
+ /**
140
+ * `canceled` only on Stripe's deletion notice (e.g. the final `stripe_confirmed`). Absent on
141
+ * the reservation event, on immediate cancellations and on the cron's
142
+ * `payment_failure_auto_cancel` — so its absence does not mean "still usable"; decide by
143
+ * `source` + `cancelAt` (see the interface doc).
144
+ */
97
145
  effectiveStatus?: EffectiveSubscriptionStatus;
98
146
  /** Always null for cancellation events because no grace window remains. */
99
147
  gracePeriodEndsAt?: string | null;
@@ -279,6 +327,7 @@ interface FFIDTestPingPayload {
279
327
  /** Maps event type strings to their typed payloads */
280
328
  interface FFIDWebhookEventMap {
281
329
  'subscription.created': FFIDSubscriptionCreatedPayload;
330
+ 'subscription.reactivated': FFIDSubscriptionReactivatedPayload;
282
331
  'subscription.updated': FFIDSubscriptionUpdatedPayload;
283
332
  'subscription.canceled': FFIDSubscriptionCanceledPayload;
284
333
  'subscription.downgrade_scheduled': FFIDSubscriptionDowngradeScheduledPayload;
@@ -426,4 +475,4 @@ declare function createFFIDWebhookHandler(config: FFIDWebhookHandlerConfig): {
426
475
  /** Type of the FFID Webhook handler */
427
476
  type FFIDWebhookHandler = ReturnType<typeof createFFIDWebhookHandler>;
428
477
 
429
- export { DEFAULT_TOLERANCE_SECONDS, type FFIDLegalAgreementRequiredPayload, type FFIDLegalDocumentUpdatedPayload, type FFIDOrganizationCreatedPayload, type FFIDOrganizationMemberAddedPayload, type FFIDOrganizationMemberRemovedPayload, type FFIDOrganizationMemberRoleChangedPayload, type FFIDOrganizationUpdatedPayload, type FFIDSubscriptionCanceledPayload, type FFIDSubscriptionCreatedPayload, type FFIDSubscriptionDowngradeAppliedPayload, type FFIDSubscriptionDowngradeCanceledPayload, type FFIDSubscriptionDowngradeScheduledPayload, type FFIDSubscriptionPaymentFailedPayload, type FFIDSubscriptionTrialEndingPayload, type FFIDSubscriptionUpdatedPayload, type FFIDSystemMaintenancePayload, type FFIDTestPingPayload, type FFIDUserCreatedPayload, type FFIDUserDeletedPayload, type FFIDUserDeletionRequestedPayload, type FFIDUserUpdatedPayload, FFIDWebhookError, type FFIDWebhookEvent, type FFIDWebhookEventHandler, type FFIDWebhookEventMap, type FFIDWebhookEventType, type FFIDWebhookHandler, type FFIDWebhookHandlerConfig, type FFIDWebhookLogger, FFIDWebhookPayloadError, FFIDWebhookSignatureError, FFIDWebhookTimestampError, FFID_WEBHOOK_EVENT_ID_HEADER, FFID_WEBHOOK_SIGNATURE_HEADER, FFID_WEBHOOK_SIGNATURE_VERSION, FFID_WEBHOOK_TIMESTAMP_HEADER, computeSignature, createFFIDWebhookHandler, parseSignatureHeader, verifyWebhookSignature };
478
+ export { DEFAULT_TOLERANCE_SECONDS, type FFIDLegalAgreementRequiredPayload, type FFIDLegalDocumentUpdatedPayload, type FFIDOrganizationCreatedPayload, type FFIDOrganizationMemberAddedPayload, type FFIDOrganizationMemberRemovedPayload, type FFIDOrganizationMemberRoleChangedPayload, type FFIDOrganizationUpdatedPayload, type FFIDSubscriptionCanceledPayload, type FFIDSubscriptionCreatedPayload, type FFIDSubscriptionDowngradeAppliedPayload, type FFIDSubscriptionDowngradeCanceledPayload, type FFIDSubscriptionDowngradeScheduledPayload, type FFIDSubscriptionPaymentFailedPayload, type FFIDSubscriptionReactivatedPayload, type FFIDSubscriptionTrialEndingPayload, type FFIDSubscriptionUpdatedPayload, type FFIDSystemMaintenancePayload, type FFIDTestPingPayload, type FFIDUserCreatedPayload, type FFIDUserDeletedPayload, type FFIDUserDeletionRequestedPayload, type FFIDUserUpdatedPayload, FFIDWebhookError, type FFIDWebhookEvent, type FFIDWebhookEventHandler, type FFIDWebhookEventMap, type FFIDWebhookEventType, type FFIDWebhookHandler, type FFIDWebhookHandlerConfig, type FFIDWebhookLogger, FFIDWebhookPayloadError, FFIDWebhookSignatureError, FFIDWebhookTimestampError, FFID_WEBHOOK_EVENT_ID_HEADER, FFID_WEBHOOK_SIGNATURE_HEADER, FFID_WEBHOOK_SIGNATURE_VERSION, FFID_WEBHOOK_TIMESTAMP_HEADER, computeSignature, createFFIDWebhookHandler, parseSignatureHeader, verifyWebhookSignature };