@feelflow/ffid-sdk 5.3.1 → 5.5.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.
@@ -341,9 +341,32 @@ type GetDocumentWire = z.infer<typeof GetDocumentWireSchema>;
341
341
  /**
342
342
  * Warning event surfaced to `onConsentChange(state, warning)` after a `/sync`
343
343
  * call. Spec §5.3 requires non-null warnings to be visible to the user.
344
+ *
345
+ * @remarks
346
+ * Multi-locale consumers should pattern-match on `code` and resolve their own
347
+ * copy. The `message` field defaults to the SDK's built-in Japanese copy
348
+ * (`DEFAULT_MERGE_WARNING_MESSAGES`); pass `mergeWarningMessages` on
349
+ * `<FFIDAnalyticsProvider>` to inject locale-specific overrides without
350
+ * losing the warning event itself.
344
351
  */
345
352
  interface FFIDConsentMergeWarningEvent {
346
353
  code: FFIDConsentMergeWarning;
354
+ /**
355
+ * Human-readable copy. Defaults to the SDK's built-in Japanese copy unless
356
+ * `<FFIDAnalyticsProvider mergeWarningMessages={...}>` overrides it.
357
+ *
358
+ * @deprecated Scheduled for removal in `@feelflow/ffid-sdk` v6.0 so that
359
+ * `FFIDConsentMergeWarningEvent` becomes symmetric with
360
+ * `FFIDConsentSyncResult.warning` (code-only). Consumers should
361
+ * pattern-match on `code` and resolve copy in their own i18n layer.
362
+ *
363
+ * Migration: replace `event.message` reads with `messages[event.code]`
364
+ * where `messages` is typed as
365
+ * `Record<FFIDConsentMergeWarning, string>` (not `Partial<...>`) so
366
+ * TypeScript exhaustiveness-checks every enum value at compile time
367
+ * and the dictionary can never leak `undefined` the way an unchecked
368
+ * `event.message` could in pre-v5.5 SDKs.
369
+ */
347
370
  message: string;
348
371
  }
349
372
  /**
@@ -708,11 +731,27 @@ interface FFIDAnalyticsProviderProps {
708
731
  * suppressed by a UX dismissal.
709
732
  */
710
733
  dismissalCooldownMinutes?: number;
734
+ /**
735
+ * Per-locale override map for `FFIDConsentMergeWarningEvent.message`.
736
+ *
737
+ * The Provider derives `message` for the `onConsentChange(state, warning)`
738
+ * callback from this map, falling back to `DEFAULT_MERGE_WARNING_MESSAGES`
739
+ * (JP) for any missing key. Pass only the keys you need to translate —
740
+ * partial overrides are merged with the defaults.
741
+ *
742
+ * Note: the underlying `code` (`FFIDConsentMergeWarning`) is the canonical
743
+ * identifier — multi-locale consumers should prefer pattern-matching on
744
+ * `warning.code` over reading `warning.message` directly. The `message`
745
+ * field is `@deprecated` and scheduled for removal in v6.0; this prop is
746
+ * the interim escape hatch so non-JP consumers don't have to wait for the
747
+ * v6.0 major to surface localized copy.
748
+ */
749
+ mergeWarningMessages?: Partial<Record<FFIDConsentMergeWarning, string>>;
711
750
  /** Optional fetch override (mainly for tests). */
712
751
  fetchImpl?: typeof fetch;
713
752
  children: ReactNode;
714
753
  }
715
- declare function FFIDAnalyticsProvider({ baseUrl, serviceApiKey, getAccessToken, gaMeasurementId, sentryReplay, sentry, onConsentChange, onError, autoShowBanner, dismissalCooldownMinutes, fetchImpl, children, }: FFIDAnalyticsProviderProps): React.JSX.Element;
754
+ declare function FFIDAnalyticsProvider({ baseUrl, serviceApiKey, getAccessToken, gaMeasurementId, sentryReplay, sentry, onConsentChange, onError, autoShowBanner, dismissalCooldownMinutes, mergeWarningMessages, fetchImpl, children, }: FFIDAnalyticsProviderProps): React.JSX.Element;
716
755
 
717
756
  interface FFIDConsentContextValue {
718
757
  /** Current consent state. Always non-null; "not yet decided" is encoded by `hasDecided=false`. */
@@ -341,9 +341,32 @@ type GetDocumentWire = z.infer<typeof GetDocumentWireSchema>;
341
341
  /**
342
342
  * Warning event surfaced to `onConsentChange(state, warning)` after a `/sync`
343
343
  * call. Spec §5.3 requires non-null warnings to be visible to the user.
344
+ *
345
+ * @remarks
346
+ * Multi-locale consumers should pattern-match on `code` and resolve their own
347
+ * copy. The `message` field defaults to the SDK's built-in Japanese copy
348
+ * (`DEFAULT_MERGE_WARNING_MESSAGES`); pass `mergeWarningMessages` on
349
+ * `<FFIDAnalyticsProvider>` to inject locale-specific overrides without
350
+ * losing the warning event itself.
344
351
  */
345
352
  interface FFIDConsentMergeWarningEvent {
346
353
  code: FFIDConsentMergeWarning;
354
+ /**
355
+ * Human-readable copy. Defaults to the SDK's built-in Japanese copy unless
356
+ * `<FFIDAnalyticsProvider mergeWarningMessages={...}>` overrides it.
357
+ *
358
+ * @deprecated Scheduled for removal in `@feelflow/ffid-sdk` v6.0 so that
359
+ * `FFIDConsentMergeWarningEvent` becomes symmetric with
360
+ * `FFIDConsentSyncResult.warning` (code-only). Consumers should
361
+ * pattern-match on `code` and resolve copy in their own i18n layer.
362
+ *
363
+ * Migration: replace `event.message` reads with `messages[event.code]`
364
+ * where `messages` is typed as
365
+ * `Record<FFIDConsentMergeWarning, string>` (not `Partial<...>`) so
366
+ * TypeScript exhaustiveness-checks every enum value at compile time
367
+ * and the dictionary can never leak `undefined` the way an unchecked
368
+ * `event.message` could in pre-v5.5 SDKs.
369
+ */
347
370
  message: string;
348
371
  }
349
372
  /**
@@ -708,11 +731,27 @@ interface FFIDAnalyticsProviderProps {
708
731
  * suppressed by a UX dismissal.
709
732
  */
710
733
  dismissalCooldownMinutes?: number;
734
+ /**
735
+ * Per-locale override map for `FFIDConsentMergeWarningEvent.message`.
736
+ *
737
+ * The Provider derives `message` for the `onConsentChange(state, warning)`
738
+ * callback from this map, falling back to `DEFAULT_MERGE_WARNING_MESSAGES`
739
+ * (JP) for any missing key. Pass only the keys you need to translate —
740
+ * partial overrides are merged with the defaults.
741
+ *
742
+ * Note: the underlying `code` (`FFIDConsentMergeWarning`) is the canonical
743
+ * identifier — multi-locale consumers should prefer pattern-matching on
744
+ * `warning.code` over reading `warning.message` directly. The `message`
745
+ * field is `@deprecated` and scheduled for removal in v6.0; this prop is
746
+ * the interim escape hatch so non-JP consumers don't have to wait for the
747
+ * v6.0 major to surface localized copy.
748
+ */
749
+ mergeWarningMessages?: Partial<Record<FFIDConsentMergeWarning, string>>;
711
750
  /** Optional fetch override (mainly for tests). */
712
751
  fetchImpl?: typeof fetch;
713
752
  children: ReactNode;
714
753
  }
715
- declare function FFIDAnalyticsProvider({ baseUrl, serviceApiKey, getAccessToken, gaMeasurementId, sentryReplay, sentry, onConsentChange, onError, autoShowBanner, dismissalCooldownMinutes, fetchImpl, children, }: FFIDAnalyticsProviderProps): React.JSX.Element;
754
+ declare function FFIDAnalyticsProvider({ baseUrl, serviceApiKey, getAccessToken, gaMeasurementId, sentryReplay, sentry, onConsentChange, onError, autoShowBanner, dismissalCooldownMinutes, mergeWarningMessages, fetchImpl, children, }: FFIDAnalyticsProviderProps): React.JSX.Element;
716
755
 
717
756
  interface FFIDConsentContextValue {
718
757
  /** Current consent state. Always non-null; "not yet decided" is encoded by `hasDecided=false`. */
@@ -842,7 +842,7 @@ function createProfileMethods(deps) {
842
842
  }
843
843
 
844
844
  // src/client/version-check.ts
845
- var SDK_VERSION = "5.3.1";
845
+ var SDK_VERSION = "5.5.0";
846
846
  var SDK_USER_AGENT = `FFID-SDK/${SDK_VERSION} (TypeScript)`;
847
847
  var SDK_VERSION_HEADER = "X-FFID-SDK-Version";
848
848
  function sdkHeaders() {
@@ -2231,6 +2231,83 @@ function createInquiryMethods(deps) {
2231
2231
  return { create };
2232
2232
  }
2233
2233
 
2234
+ // src/subscriptions/types.ts
2235
+ var PAST_DUE_GRACE_PERIOD_DAYS = 7;
2236
+ var HOURS_PER_DAY = 24;
2237
+ var MINUTES_PER_HOUR = 60;
2238
+ var SECONDS_PER_MINUTE = 60;
2239
+ var MS_PER_SECOND2 = 1e3;
2240
+ var MS_PER_DAY = HOURS_PER_DAY * MINUTES_PER_HOUR * SECONDS_PER_MINUTE * MS_PER_SECOND2;
2241
+
2242
+ // src/subscriptions/compute-effective-status.ts
2243
+ function isExpired(isoTimestamp, nowMs) {
2244
+ if (!isoTimestamp) return null;
2245
+ const parsed = Date.parse(isoTimestamp);
2246
+ if (Number.isNaN(parsed)) return null;
2247
+ return parsed < nowMs;
2248
+ }
2249
+ function computeEffectiveStatusFromSession(input, nowMs = Date.now()) {
2250
+ const { status, currentPeriodEnd, trialEnd, pastDueSince } = input;
2251
+ switch (status) {
2252
+ case "trialing": {
2253
+ const relevantEnd = trialEnd ?? currentPeriodEnd;
2254
+ const expired = isExpired(relevantEnd, nowMs);
2255
+ return expired === true ? "trial_expired" : "active";
2256
+ }
2257
+ case "active": {
2258
+ const expired = isExpired(currentPeriodEnd, nowMs);
2259
+ return expired === true ? "expired" : "active";
2260
+ }
2261
+ case "past_due": {
2262
+ const baselineIso = pastDueSince ?? currentPeriodEnd;
2263
+ if (!baselineIso) return "blocked";
2264
+ const baselineMs = Date.parse(baselineIso);
2265
+ if (Number.isNaN(baselineMs)) return "blocked";
2266
+ const graceEndMs = baselineMs + PAST_DUE_GRACE_PERIOD_DAYS * MS_PER_DAY;
2267
+ return graceEndMs > nowMs ? "past_due_grace" : "blocked";
2268
+ }
2269
+ case "pending_invoice":
2270
+ return "active";
2271
+ case "paused":
2272
+ case "incomplete":
2273
+ return "active";
2274
+ case "canceled":
2275
+ return "canceled";
2276
+ case "unpaid":
2277
+ case "incomplete_expired":
2278
+ return "blocked";
2279
+ default: {
2280
+ return "blocked";
2281
+ }
2282
+ }
2283
+ }
2284
+
2285
+ // src/subscriptions/userinfo-helpers.ts
2286
+ var ACCESS_GRANTING_EFFECTIVE_STATUSES = [
2287
+ "active",
2288
+ "past_due_grace"
2289
+ ];
2290
+ var BLOCKING_EFFECTIVE_STATUSES = [
2291
+ "blocked",
2292
+ "canceled",
2293
+ "expired",
2294
+ "trial_expired"
2295
+ ];
2296
+ function hasAccessFromUserinfo(sub) {
2297
+ const effectiveStatus = sub?.effectiveStatus;
2298
+ if (effectiveStatus === void 0 || effectiveStatus === null) {
2299
+ return false;
2300
+ }
2301
+ return ACCESS_GRANTING_EFFECTIVE_STATUSES.includes(effectiveStatus);
2302
+ }
2303
+ function isBlockedFromUserinfo(sub) {
2304
+ const effectiveStatus = sub?.effectiveStatus;
2305
+ if (effectiveStatus === void 0 || effectiveStatus === null) {
2306
+ return false;
2307
+ }
2308
+ return BLOCKING_EFFECTIVE_STATUSES.includes(effectiveStatus);
2309
+ }
2310
+
2234
2311
  // src/client/ffid-client.ts
2235
2312
  var UNAUTHORIZED_STATUS2 = 401;
2236
2313
  var SDK_LOG_PREFIX = "[FFID SDK]";
@@ -2261,13 +2338,6 @@ var EXT_CHECK_ENDPOINT = "/api/v1/subscriptions/ext/check";
2261
2338
  var DEFAULT_ALLOW_GRACE = true;
2262
2339
  var DEFAULT_SERVICE_ACCESS_FAIL_POLICY = "failClosed";
2263
2340
  var SERVICE_ACCESS_DENIED_CODE = "SERVICE_ACCESS_DENIED";
2264
- var ACCESS_GRANTING_EFFECTIVE_STATUSES = ["active", "past_due_grace"];
2265
- var BLOCKING_EFFECTIVE_STATUSES = [
2266
- "blocked",
2267
- "canceled",
2268
- "expired",
2269
- "trial_expired"
2270
- ];
2271
2341
  function resolveServiceAccessDenialReason(params, allowGrace) {
2272
2342
  if (params.hasAccess) {
2273
2343
  return null;
@@ -3450,57 +3520,6 @@ function FFIDOrganizationSwitcher({
3450
3520
  ] })
3451
3521
  ] });
3452
3522
  }
3453
-
3454
- // src/subscriptions/types.ts
3455
- var PAST_DUE_GRACE_PERIOD_DAYS = 7;
3456
- var HOURS_PER_DAY = 24;
3457
- var MINUTES_PER_HOUR = 60;
3458
- var SECONDS_PER_MINUTE = 60;
3459
- var MS_PER_SECOND2 = 1e3;
3460
- var MS_PER_DAY = HOURS_PER_DAY * MINUTES_PER_HOUR * SECONDS_PER_MINUTE * MS_PER_SECOND2;
3461
-
3462
- // src/subscriptions/compute-effective-status.ts
3463
- function isExpired(isoTimestamp, nowMs) {
3464
- if (!isoTimestamp) return null;
3465
- const parsed = Date.parse(isoTimestamp);
3466
- if (Number.isNaN(parsed)) return null;
3467
- return parsed < nowMs;
3468
- }
3469
- function computeEffectiveStatusFromSession(input, nowMs = Date.now()) {
3470
- const { status, currentPeriodEnd, trialEnd, pastDueSince } = input;
3471
- switch (status) {
3472
- case "trialing": {
3473
- const relevantEnd = trialEnd ?? currentPeriodEnd;
3474
- const expired = isExpired(relevantEnd, nowMs);
3475
- return expired === true ? "trial_expired" : "active";
3476
- }
3477
- case "active": {
3478
- const expired = isExpired(currentPeriodEnd, nowMs);
3479
- return expired === true ? "expired" : "active";
3480
- }
3481
- case "past_due": {
3482
- const baselineIso = pastDueSince ?? currentPeriodEnd;
3483
- if (!baselineIso) return "blocked";
3484
- const baselineMs = Date.parse(baselineIso);
3485
- if (Number.isNaN(baselineMs)) return "blocked";
3486
- const graceEndMs = baselineMs + PAST_DUE_GRACE_PERIOD_DAYS * MS_PER_DAY;
3487
- return graceEndMs > nowMs ? "past_due_grace" : "blocked";
3488
- }
3489
- case "pending_invoice":
3490
- return "active";
3491
- case "paused":
3492
- case "incomplete":
3493
- return "active";
3494
- case "canceled":
3495
- return "canceled";
3496
- case "unpaid":
3497
- case "incomplete_expired":
3498
- return "blocked";
3499
- default: {
3500
- return "blocked";
3501
- }
3502
- }
3503
- }
3504
3523
  var ACCESS_GRANTING_STATUSES = [
3505
3524
  "active",
3506
3525
  "past_due_grace"
@@ -4800,6 +4819,8 @@ function FFIDInquiryForm({
4800
4819
  );
4801
4820
  }
4802
4821
 
4822
+ exports.ACCESS_GRANTING_EFFECTIVE_STATUSES = ACCESS_GRANTING_EFFECTIVE_STATUSES;
4823
+ exports.BLOCKING_EFFECTIVE_STATUSES = BLOCKING_EFFECTIVE_STATUSES;
4803
4824
  exports.DEFAULT_API_BASE_URL = DEFAULT_API_BASE_URL;
4804
4825
  exports.DEFAULT_OAUTH_SCOPES = DEFAULT_OAUTH_SCOPES;
4805
4826
  exports.FFIDAnnouncementBadge = FFIDAnnouncementBadge;
@@ -4820,6 +4841,8 @@ exports.createFFIDClient = createFFIDClient;
4820
4841
  exports.createTokenStore = createTokenStore;
4821
4842
  exports.generateCodeChallenge = generateCodeChallenge;
4822
4843
  exports.generateCodeVerifier = generateCodeVerifier;
4844
+ exports.hasAccessFromUserinfo = hasAccessFromUserinfo;
4845
+ exports.isBlockedFromUserinfo = isBlockedFromUserinfo;
4823
4846
  exports.isFFIDInquiryCategorySite2026 = isFFIDInquiryCategorySite2026;
4824
4847
  exports.normalizeRedirectUri = normalizeRedirectUri;
4825
4848
  exports.retrieveCodeVerifier = retrieveCodeVerifier;
@@ -1,5 +1,5 @@
1
1
  import { z } from 'zod';
2
- import { createContext, useCallback, useState, useRef, useEffect, useMemo, useContext } from 'react';
2
+ import { createContext, useMemo, useCallback, useState, useRef, useEffect, useContext } from 'react';
3
3
  import { jsx, jsxs } from 'react/jsx-runtime';
4
4
 
5
5
  var FFIDConsentCategoryCodeSchema = z.enum([
@@ -1071,10 +1071,18 @@ function FFIDAnalyticsProvider({
1071
1071
  onError,
1072
1072
  autoShowBanner = true,
1073
1073
  dismissalCooldownMinutes = DEFAULT_DISMISSAL_COOLDOWN_MINUTES,
1074
+ mergeWarningMessages,
1074
1075
  fetchImpl,
1075
1076
  children
1076
1077
  }) {
1077
1078
  const cooldownMs = Number.isFinite(dismissalCooldownMinutes) && dismissalCooldownMinutes > 0 ? dismissalCooldownMinutes * MS_PER_MINUTE : 0;
1079
+ const resolvedMergeWarningMessages = useMemo(
1080
+ () => ({
1081
+ ...DEFAULT_MERGE_WARNING_MESSAGES,
1082
+ ...mergeWarningMessages
1083
+ }),
1084
+ [mergeWarningMessages]
1085
+ );
1078
1086
  const isWithinDismissalCooldown = useCallback(() => {
1079
1087
  if (cooldownMs <= 0) return false;
1080
1088
  const dismissedAt = readConsentDismissalTimestamp();
@@ -1311,7 +1319,16 @@ function FFIDAnalyticsProvider({
1311
1319
  persistCategories(result.value.state.categories);
1312
1320
  const warning = result.value.warning ? {
1313
1321
  code: result.value.warning,
1314
- message: DEFAULT_MERGE_WARNING_MESSAGES[result.value.warning]
1322
+ // Belt-and-suspenders fallback per spec §5.3: an empty-string
1323
+ // override (`mergeWarningMessages={{ code: '' }}`) or a future
1324
+ // wire-schema widening that adds a new `FFIDConsentMergeWarning`
1325
+ // literal without a default copy would otherwise leak `''` /
1326
+ // `undefined` into `warning.message`, silently breaking consumers
1327
+ // that render it (`<Toast>{warning.message}</Toast>`,
1328
+ // `Sentry.setContext`). Truthy-fallback preserves visibility of
1329
+ // the JP default; consumers wanting to suppress display should
1330
+ // pattern-match on `code` instead of mutating `message`.
1331
+ message: resolvedMergeWarningMessages[result.value.warning] || DEFAULT_MERGE_WARNING_MESSAGES[result.value.warning]
1315
1332
  } : null;
1316
1333
  onConsentChange?.(result.value.state, warning);
1317
1334
  return result;
@@ -1320,7 +1337,8 @@ function FFIDAnalyticsProvider({
1320
1337
  ensurePolicyVersion,
1321
1338
  onConsentChange,
1322
1339
  onError,
1323
- persistCategories
1340
+ persistCategories,
1341
+ resolvedMergeWarningMessages
1324
1342
  ]);
1325
1343
  const openBanner = useCallback(() => setIsBannerOpen(true), []);
1326
1344
  const closeBanner = useCallback(() => {
@@ -1073,10 +1073,18 @@ function FFIDAnalyticsProvider({
1073
1073
  onError,
1074
1074
  autoShowBanner = true,
1075
1075
  dismissalCooldownMinutes = DEFAULT_DISMISSAL_COOLDOWN_MINUTES,
1076
+ mergeWarningMessages,
1076
1077
  fetchImpl,
1077
1078
  children
1078
1079
  }) {
1079
1080
  const cooldownMs = Number.isFinite(dismissalCooldownMinutes) && dismissalCooldownMinutes > 0 ? dismissalCooldownMinutes * MS_PER_MINUTE : 0;
1081
+ const resolvedMergeWarningMessages = react.useMemo(
1082
+ () => ({
1083
+ ...DEFAULT_MERGE_WARNING_MESSAGES,
1084
+ ...mergeWarningMessages
1085
+ }),
1086
+ [mergeWarningMessages]
1087
+ );
1080
1088
  const isWithinDismissalCooldown = react.useCallback(() => {
1081
1089
  if (cooldownMs <= 0) return false;
1082
1090
  const dismissedAt = readConsentDismissalTimestamp();
@@ -1313,7 +1321,16 @@ function FFIDAnalyticsProvider({
1313
1321
  persistCategories(result.value.state.categories);
1314
1322
  const warning = result.value.warning ? {
1315
1323
  code: result.value.warning,
1316
- message: DEFAULT_MERGE_WARNING_MESSAGES[result.value.warning]
1324
+ // Belt-and-suspenders fallback per spec §5.3: an empty-string
1325
+ // override (`mergeWarningMessages={{ code: '' }}`) or a future
1326
+ // wire-schema widening that adds a new `FFIDConsentMergeWarning`
1327
+ // literal without a default copy would otherwise leak `''` /
1328
+ // `undefined` into `warning.message`, silently breaking consumers
1329
+ // that render it (`<Toast>{warning.message}</Toast>`,
1330
+ // `Sentry.setContext`). Truthy-fallback preserves visibility of
1331
+ // the JP default; consumers wanting to suppress display should
1332
+ // pattern-match on `code` instead of mutating `message`.
1333
+ message: resolvedMergeWarningMessages[result.value.warning] || DEFAULT_MERGE_WARNING_MESSAGES[result.value.warning]
1317
1334
  } : null;
1318
1335
  onConsentChange?.(result.value.state, warning);
1319
1336
  return result;
@@ -1322,7 +1339,8 @@ function FFIDAnalyticsProvider({
1322
1339
  ensurePolicyVersion,
1323
1340
  onConsentChange,
1324
1341
  onError,
1325
- persistCategories
1342
+ persistCategories,
1343
+ resolvedMergeWarningMessages
1326
1344
  ]);
1327
1345
  const openBanner = react.useCallback(() => setIsBannerOpen(true), []);
1328
1346
  const closeBanner = react.useCallback(() => {
@@ -840,7 +840,7 @@ function createProfileMethods(deps) {
840
840
  }
841
841
 
842
842
  // src/client/version-check.ts
843
- var SDK_VERSION = "5.3.1";
843
+ var SDK_VERSION = "5.5.0";
844
844
  var SDK_USER_AGENT = `FFID-SDK/${SDK_VERSION} (TypeScript)`;
845
845
  var SDK_VERSION_HEADER = "X-FFID-SDK-Version";
846
846
  function sdkHeaders() {
@@ -2229,6 +2229,83 @@ function createInquiryMethods(deps) {
2229
2229
  return { create };
2230
2230
  }
2231
2231
 
2232
+ // src/subscriptions/types.ts
2233
+ var PAST_DUE_GRACE_PERIOD_DAYS = 7;
2234
+ var HOURS_PER_DAY = 24;
2235
+ var MINUTES_PER_HOUR = 60;
2236
+ var SECONDS_PER_MINUTE = 60;
2237
+ var MS_PER_SECOND2 = 1e3;
2238
+ var MS_PER_DAY = HOURS_PER_DAY * MINUTES_PER_HOUR * SECONDS_PER_MINUTE * MS_PER_SECOND2;
2239
+
2240
+ // src/subscriptions/compute-effective-status.ts
2241
+ function isExpired(isoTimestamp, nowMs) {
2242
+ if (!isoTimestamp) return null;
2243
+ const parsed = Date.parse(isoTimestamp);
2244
+ if (Number.isNaN(parsed)) return null;
2245
+ return parsed < nowMs;
2246
+ }
2247
+ function computeEffectiveStatusFromSession(input, nowMs = Date.now()) {
2248
+ const { status, currentPeriodEnd, trialEnd, pastDueSince } = input;
2249
+ switch (status) {
2250
+ case "trialing": {
2251
+ const relevantEnd = trialEnd ?? currentPeriodEnd;
2252
+ const expired = isExpired(relevantEnd, nowMs);
2253
+ return expired === true ? "trial_expired" : "active";
2254
+ }
2255
+ case "active": {
2256
+ const expired = isExpired(currentPeriodEnd, nowMs);
2257
+ return expired === true ? "expired" : "active";
2258
+ }
2259
+ case "past_due": {
2260
+ const baselineIso = pastDueSince ?? currentPeriodEnd;
2261
+ if (!baselineIso) return "blocked";
2262
+ const baselineMs = Date.parse(baselineIso);
2263
+ if (Number.isNaN(baselineMs)) return "blocked";
2264
+ const graceEndMs = baselineMs + PAST_DUE_GRACE_PERIOD_DAYS * MS_PER_DAY;
2265
+ return graceEndMs > nowMs ? "past_due_grace" : "blocked";
2266
+ }
2267
+ case "pending_invoice":
2268
+ return "active";
2269
+ case "paused":
2270
+ case "incomplete":
2271
+ return "active";
2272
+ case "canceled":
2273
+ return "canceled";
2274
+ case "unpaid":
2275
+ case "incomplete_expired":
2276
+ return "blocked";
2277
+ default: {
2278
+ return "blocked";
2279
+ }
2280
+ }
2281
+ }
2282
+
2283
+ // src/subscriptions/userinfo-helpers.ts
2284
+ var ACCESS_GRANTING_EFFECTIVE_STATUSES = [
2285
+ "active",
2286
+ "past_due_grace"
2287
+ ];
2288
+ var BLOCKING_EFFECTIVE_STATUSES = [
2289
+ "blocked",
2290
+ "canceled",
2291
+ "expired",
2292
+ "trial_expired"
2293
+ ];
2294
+ function hasAccessFromUserinfo(sub) {
2295
+ const effectiveStatus = sub?.effectiveStatus;
2296
+ if (effectiveStatus === void 0 || effectiveStatus === null) {
2297
+ return false;
2298
+ }
2299
+ return ACCESS_GRANTING_EFFECTIVE_STATUSES.includes(effectiveStatus);
2300
+ }
2301
+ function isBlockedFromUserinfo(sub) {
2302
+ const effectiveStatus = sub?.effectiveStatus;
2303
+ if (effectiveStatus === void 0 || effectiveStatus === null) {
2304
+ return false;
2305
+ }
2306
+ return BLOCKING_EFFECTIVE_STATUSES.includes(effectiveStatus);
2307
+ }
2308
+
2232
2309
  // src/client/ffid-client.ts
2233
2310
  var UNAUTHORIZED_STATUS2 = 401;
2234
2311
  var SDK_LOG_PREFIX = "[FFID SDK]";
@@ -2259,13 +2336,6 @@ var EXT_CHECK_ENDPOINT = "/api/v1/subscriptions/ext/check";
2259
2336
  var DEFAULT_ALLOW_GRACE = true;
2260
2337
  var DEFAULT_SERVICE_ACCESS_FAIL_POLICY = "failClosed";
2261
2338
  var SERVICE_ACCESS_DENIED_CODE = "SERVICE_ACCESS_DENIED";
2262
- var ACCESS_GRANTING_EFFECTIVE_STATUSES = ["active", "past_due_grace"];
2263
- var BLOCKING_EFFECTIVE_STATUSES = [
2264
- "blocked",
2265
- "canceled",
2266
- "expired",
2267
- "trial_expired"
2268
- ];
2269
2339
  function resolveServiceAccessDenialReason(params, allowGrace) {
2270
2340
  if (params.hasAccess) {
2271
2341
  return null;
@@ -3448,57 +3518,6 @@ function FFIDOrganizationSwitcher({
3448
3518
  ] })
3449
3519
  ] });
3450
3520
  }
3451
-
3452
- // src/subscriptions/types.ts
3453
- var PAST_DUE_GRACE_PERIOD_DAYS = 7;
3454
- var HOURS_PER_DAY = 24;
3455
- var MINUTES_PER_HOUR = 60;
3456
- var SECONDS_PER_MINUTE = 60;
3457
- var MS_PER_SECOND2 = 1e3;
3458
- var MS_PER_DAY = HOURS_PER_DAY * MINUTES_PER_HOUR * SECONDS_PER_MINUTE * MS_PER_SECOND2;
3459
-
3460
- // src/subscriptions/compute-effective-status.ts
3461
- function isExpired(isoTimestamp, nowMs) {
3462
- if (!isoTimestamp) return null;
3463
- const parsed = Date.parse(isoTimestamp);
3464
- if (Number.isNaN(parsed)) return null;
3465
- return parsed < nowMs;
3466
- }
3467
- function computeEffectiveStatusFromSession(input, nowMs = Date.now()) {
3468
- const { status, currentPeriodEnd, trialEnd, pastDueSince } = input;
3469
- switch (status) {
3470
- case "trialing": {
3471
- const relevantEnd = trialEnd ?? currentPeriodEnd;
3472
- const expired = isExpired(relevantEnd, nowMs);
3473
- return expired === true ? "trial_expired" : "active";
3474
- }
3475
- case "active": {
3476
- const expired = isExpired(currentPeriodEnd, nowMs);
3477
- return expired === true ? "expired" : "active";
3478
- }
3479
- case "past_due": {
3480
- const baselineIso = pastDueSince ?? currentPeriodEnd;
3481
- if (!baselineIso) return "blocked";
3482
- const baselineMs = Date.parse(baselineIso);
3483
- if (Number.isNaN(baselineMs)) return "blocked";
3484
- const graceEndMs = baselineMs + PAST_DUE_GRACE_PERIOD_DAYS * MS_PER_DAY;
3485
- return graceEndMs > nowMs ? "past_due_grace" : "blocked";
3486
- }
3487
- case "pending_invoice":
3488
- return "active";
3489
- case "paused":
3490
- case "incomplete":
3491
- return "active";
3492
- case "canceled":
3493
- return "canceled";
3494
- case "unpaid":
3495
- case "incomplete_expired":
3496
- return "blocked";
3497
- default: {
3498
- return "blocked";
3499
- }
3500
- }
3501
- }
3502
3521
  var ACCESS_GRANTING_STATUSES = [
3503
3522
  "active",
3504
3523
  "past_due_grace"
@@ -4798,4 +4817,4 @@ function FFIDInquiryForm({
4798
4817
  );
4799
4818
  }
4800
4819
 
4801
- export { DEFAULT_API_BASE_URL, DEFAULT_OAUTH_SCOPES, FFIDAnnouncementBadge, FFIDAnnouncementList, FFIDInquiryForm, FFIDLoginButton, FFIDOrganizationSwitcher, FFIDProvider, FFIDSDKError, FFIDSubscriptionBadge, FFIDUserMenu, FFID_ANNOUNCEMENTS_ERROR_CODES, FFID_INQUIRY_CATEGORIES, FFID_INQUIRY_CATEGORIES_SITE_2026, computeEffectiveStatusFromSession, createFFIDAnnouncementsClient, createFFIDClient, createTokenStore, generateCodeChallenge, generateCodeVerifier, isFFIDInquiryCategorySite2026, normalizeRedirectUri, retrieveCodeVerifier, storeCodeVerifier, useFFID, useFFIDAnnouncements, useFFIDContext, useSubscription, withSubscription };
4820
+ export { ACCESS_GRANTING_EFFECTIVE_STATUSES, BLOCKING_EFFECTIVE_STATUSES, DEFAULT_API_BASE_URL, DEFAULT_OAUTH_SCOPES, FFIDAnnouncementBadge, FFIDAnnouncementList, FFIDInquiryForm, FFIDLoginButton, FFIDOrganizationSwitcher, FFIDProvider, FFIDSDKError, FFIDSubscriptionBadge, FFIDUserMenu, FFID_ANNOUNCEMENTS_ERROR_CODES, FFID_INQUIRY_CATEGORIES, FFID_INQUIRY_CATEGORIES_SITE_2026, computeEffectiveStatusFromSession, createFFIDAnnouncementsClient, createFFIDClient, createTokenStore, generateCodeChallenge, generateCodeVerifier, hasAccessFromUserinfo, isBlockedFromUserinfo, isFFIDInquiryCategorySite2026, normalizeRedirectUri, retrieveCodeVerifier, storeCodeVerifier, useFFID, useFFIDAnnouncements, useFFIDContext, useSubscription, withSubscription };
@@ -1,34 +1,34 @@
1
1
  'use strict';
2
2
 
3
- var chunkAAMNUM62_cjs = require('../chunk-AAMNUM62.cjs');
3
+ var chunkJKP3MBTT_cjs = require('../chunk-JKP3MBTT.cjs');
4
4
 
5
5
 
6
6
 
7
7
  Object.defineProperty(exports, "FFIDAnnouncementBadge", {
8
8
  enumerable: true,
9
- get: function () { return chunkAAMNUM62_cjs.FFIDAnnouncementBadge; }
9
+ get: function () { return chunkJKP3MBTT_cjs.FFIDAnnouncementBadge; }
10
10
  });
11
11
  Object.defineProperty(exports, "FFIDAnnouncementList", {
12
12
  enumerable: true,
13
- get: function () { return chunkAAMNUM62_cjs.FFIDAnnouncementList; }
13
+ get: function () { return chunkJKP3MBTT_cjs.FFIDAnnouncementList; }
14
14
  });
15
15
  Object.defineProperty(exports, "FFIDInquiryForm", {
16
16
  enumerable: true,
17
- get: function () { return chunkAAMNUM62_cjs.FFIDInquiryForm; }
17
+ get: function () { return chunkJKP3MBTT_cjs.FFIDInquiryForm; }
18
18
  });
19
19
  Object.defineProperty(exports, "FFIDLoginButton", {
20
20
  enumerable: true,
21
- get: function () { return chunkAAMNUM62_cjs.FFIDLoginButton; }
21
+ get: function () { return chunkJKP3MBTT_cjs.FFIDLoginButton; }
22
22
  });
23
23
  Object.defineProperty(exports, "FFIDOrganizationSwitcher", {
24
24
  enumerable: true,
25
- get: function () { return chunkAAMNUM62_cjs.FFIDOrganizationSwitcher; }
25
+ get: function () { return chunkJKP3MBTT_cjs.FFIDOrganizationSwitcher; }
26
26
  });
27
27
  Object.defineProperty(exports, "FFIDSubscriptionBadge", {
28
28
  enumerable: true,
29
- get: function () { return chunkAAMNUM62_cjs.FFIDSubscriptionBadge; }
29
+ get: function () { return chunkJKP3MBTT_cjs.FFIDSubscriptionBadge; }
30
30
  });
31
31
  Object.defineProperty(exports, "FFIDUserMenu", {
32
32
  enumerable: true,
33
- get: function () { return chunkAAMNUM62_cjs.FFIDUserMenu; }
33
+ get: function () { return chunkJKP3MBTT_cjs.FFIDUserMenu; }
34
34
  });
@@ -1,3 +1,3 @@
1
- export { S as FFIDAnnouncementBadge, av as FFIDAnnouncementBadgeClassNames, aw as FFIDAnnouncementBadgeProps, T as FFIDAnnouncementList, ax as FFIDAnnouncementListClassNames, ay as FFIDAnnouncementListProps, a0 as FFIDInquiryForm, a1 as FFIDInquiryFormCategoryItem, a2 as FFIDInquiryFormClassNames, a3 as FFIDInquiryFormLegalLayout, a4 as FFIDInquiryFormOrganization, a5 as FFIDInquiryFormPlaceholderContext, a6 as FFIDInquiryFormPrefill, a7 as FFIDInquiryFormProps, a8 as FFIDInquiryFormSubmitData, a9 as FFIDInquiryFormSubmitResult, ab as FFIDLoginButton, az as FFIDLoginButtonProps, ah as FFIDOrganizationSwitcher, aA as FFIDOrganizationSwitcherClassNames, aB as FFIDOrganizationSwitcherProps, am as FFIDSubscriptionBadge, aC as FFIDSubscriptionBadgeClassNames, aD as FFIDSubscriptionBadgeProps, ao as FFIDUserMenu, aE as FFIDUserMenuClassNames, aF as FFIDUserMenuProps } from '../index-DfnHXRMX.cjs';
1
+ export { T as FFIDAnnouncementBadge, av as FFIDAnnouncementBadgeClassNames, aw as FFIDAnnouncementBadgeProps, U as FFIDAnnouncementList, ax as FFIDAnnouncementListClassNames, ay as FFIDAnnouncementListProps, a1 as FFIDInquiryForm, a2 as FFIDInquiryFormCategoryItem, a3 as FFIDInquiryFormClassNames, a4 as FFIDInquiryFormLegalLayout, a5 as FFIDInquiryFormOrganization, a6 as FFIDInquiryFormPlaceholderContext, a7 as FFIDInquiryFormPrefill, a8 as FFIDInquiryFormProps, a9 as FFIDInquiryFormSubmitData, aa as FFIDInquiryFormSubmitResult, ac as FFIDLoginButton, az as FFIDLoginButtonProps, ah as FFIDOrganizationSwitcher, aA as FFIDOrganizationSwitcherClassNames, aB as FFIDOrganizationSwitcherProps, am as FFIDSubscriptionBadge, aC as FFIDSubscriptionBadgeClassNames, aD as FFIDSubscriptionBadgeProps, ao as FFIDUserMenu, aE as FFIDUserMenuClassNames, aF as FFIDUserMenuProps } from '../index-lAvUS_oz.cjs';
2
2
  import 'react/jsx-runtime';
3
3
  import 'react';
@@ -1,3 +1,3 @@
1
- export { S as FFIDAnnouncementBadge, av as FFIDAnnouncementBadgeClassNames, aw as FFIDAnnouncementBadgeProps, T as FFIDAnnouncementList, ax as FFIDAnnouncementListClassNames, ay as FFIDAnnouncementListProps, a0 as FFIDInquiryForm, a1 as FFIDInquiryFormCategoryItem, a2 as FFIDInquiryFormClassNames, a3 as FFIDInquiryFormLegalLayout, a4 as FFIDInquiryFormOrganization, a5 as FFIDInquiryFormPlaceholderContext, a6 as FFIDInquiryFormPrefill, a7 as FFIDInquiryFormProps, a8 as FFIDInquiryFormSubmitData, a9 as FFIDInquiryFormSubmitResult, ab as FFIDLoginButton, az as FFIDLoginButtonProps, ah as FFIDOrganizationSwitcher, aA as FFIDOrganizationSwitcherClassNames, aB as FFIDOrganizationSwitcherProps, am as FFIDSubscriptionBadge, aC as FFIDSubscriptionBadgeClassNames, aD as FFIDSubscriptionBadgeProps, ao as FFIDUserMenu, aE as FFIDUserMenuClassNames, aF as FFIDUserMenuProps } from '../index-DfnHXRMX.js';
1
+ export { T as FFIDAnnouncementBadge, av as FFIDAnnouncementBadgeClassNames, aw as FFIDAnnouncementBadgeProps, U as FFIDAnnouncementList, ax as FFIDAnnouncementListClassNames, ay as FFIDAnnouncementListProps, a1 as FFIDInquiryForm, a2 as FFIDInquiryFormCategoryItem, a3 as FFIDInquiryFormClassNames, a4 as FFIDInquiryFormLegalLayout, a5 as FFIDInquiryFormOrganization, a6 as FFIDInquiryFormPlaceholderContext, a7 as FFIDInquiryFormPrefill, a8 as FFIDInquiryFormProps, a9 as FFIDInquiryFormSubmitData, aa as FFIDInquiryFormSubmitResult, ac as FFIDLoginButton, az as FFIDLoginButtonProps, ah as FFIDOrganizationSwitcher, aA as FFIDOrganizationSwitcherClassNames, aB as FFIDOrganizationSwitcherProps, am as FFIDSubscriptionBadge, aC as FFIDSubscriptionBadgeClassNames, aD as FFIDSubscriptionBadgeProps, ao as FFIDUserMenu, aE as FFIDUserMenuClassNames, aF as FFIDUserMenuProps } from '../index-lAvUS_oz.js';
2
2
  import 'react/jsx-runtime';
3
3
  import 'react';
@@ -1 +1 @@
1
- export { FFIDAnnouncementBadge, FFIDAnnouncementList, FFIDInquiryForm, FFIDLoginButton, FFIDOrganizationSwitcher, FFIDSubscriptionBadge, FFIDUserMenu } from '../chunk-ARKFHB72.js';
1
+ export { FFIDAnnouncementBadge, FFIDAnnouncementList, FFIDInquiryForm, FFIDLoginButton, FFIDOrganizationSwitcher, FFIDSubscriptionBadge, FFIDUserMenu } from '../chunk-WNYMSUHN.js';