@feelflow/ffid-sdk 5.24.3 → 5.27.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.
@@ -1345,6 +1345,158 @@ interface FFIDUpdateUserProfileRequest {
1345
1345
  preferences?: Record<string, unknown> | null;
1346
1346
  }
1347
1347
 
1348
+ /**
1349
+ * OAuth flow types: client-side redirect results and OAuth token / RFC 7662
1350
+ * introspection payloads.
1351
+ *
1352
+ * Extracted from `types/index.ts` to keep that barrel under the 800-line hard
1353
+ * limit (`scripts/check-file-size.sh`). Re-exported from `types/index.ts`, so
1354
+ * the public API is unchanged — import from `@feelflow/ffid-sdk` as before.
1355
+ */
1356
+
1357
+ /**
1358
+ * Discriminant for redirect failures that callers need to handle
1359
+ * programmatically (vs. logging the human-readable `error` string).
1360
+ *
1361
+ * - `'redirect_loop_detected'`: `redirectToAuthorize()` detected that the
1362
+ * same authorize URL (keyed by `baseUrl + client_id + organization_id`)
1363
+ * has been fired **3 times within 60 seconds**. Caller should surface a
1364
+ * manual "再度ログインする" UI rather than retry automatically (#2406 / #2411).
1365
+ *
1366
+ * - `'state_persistence_failed'`: `redirectToAuthorize()` could not persist
1367
+ * the OAuth CSRF `state` (storage unavailable / quota). Redirect is aborted
1368
+ * fail-closed (#4136) — callers should surface a manual retry UI.
1369
+ *
1370
+ * Other failure paths (SSR environment, PKCE generation failure, empty
1371
+ * organizationId) currently return `error` without a `code`. Treat this union
1372
+ * as forward-extensible and do NOT exhaustively `switch` over it without a
1373
+ * `default` branch for consumer code that must stay compatible across SDK
1374
+ * upgrades.
1375
+ */
1376
+ type FFIDRedirectErrorCode = 'redirect_loop_detected' | 'state_persistence_failed';
1377
+ /**
1378
+ * Result of a redirect operation (redirectToLogin / redirectToAuthorize / redirectToLogout)
1379
+ *
1380
+ * Structured return type so callers can inspect failure reasons
1381
+ * instead of receiving a bare `false`. When `code` is set, branch on it
1382
+ * for programmatic handling; otherwise log `error` for humans.
1383
+ *
1384
+ * `loopDetectionDisabled` (#4119): set to `true` when sessionStorage is
1385
+ * unavailable (iOS WebKit private mode / eviction) so redirect-loop detection
1386
+ * ran **fail-open** — the redirect fired, but repeated-redirect protection is
1387
+ * disabled for this browser session. Callers that need loop protection on
1388
+ * such browsers should add their own max-retry guard.
1389
+ */
1390
+ type FFIDRedirectResult = {
1391
+ success: true;
1392
+ loopDetectionDisabled?: boolean;
1393
+ } | {
1394
+ success: false;
1395
+ error: string;
1396
+ code?: FFIDRedirectErrorCode;
1397
+ };
1398
+
1399
+ /** Deployment environments a catalog publication can apply to (single source) */
1400
+ declare const FFID_CATALOG_ENVIRONMENTS: readonly ["staging", "production"];
1401
+ /** Deployment environment a catalog publication applies to */
1402
+ type FFIDCatalogEnvironment = (typeof FFID_CATALOG_ENVIRONMENTS)[number];
1403
+ /**
1404
+ * Tax behavior of a published price.
1405
+ * `inclusive`: displayed price contains Stripe-calculated tax (Personal/Pro).
1406
+ * `exclusive`: tax is added on top of the base price (Team).
1407
+ */
1408
+ type FFIDTaxBehavior = 'inclusive' | 'exclusive';
1409
+ /**
1410
+ * Publication lifecycle statuses (single source — the type, the SDK runtime
1411
+ * validator, and consumer sets all derive from this tuple so a new literal
1412
+ * cannot be added to one side only).
1413
+ */
1414
+ declare const FFID_CATALOG_PUBLICATION_STATUSES: readonly ["approved", "superseded", "revoking", "revoked"];
1415
+ /**
1416
+ * Publication lifecycle status.
1417
+ * - `approved`: sellable — the only status that permits new paid checkout
1418
+ * - `superseded`: replaced by a newer revision (old sessions may sell through
1419
+ * until `sellThroughUntil`)
1420
+ * - `revoking`: emergency stop in progress — never sellable
1421
+ * - `revoked`: emergency stop completed — never sellable
1422
+ */
1423
+ type FFIDCatalogPublicationStatus = (typeof FFID_CATALOG_PUBLICATION_STATUSES)[number];
1424
+ /**
1425
+ * Human-approved publication record binding a catalog revision to the
1426
+ * content/config hashes reviewed at approval time.
1427
+ *
1428
+ * All `*Hash` fields are lowercase SHA-256 hex computed with the shared
1429
+ * canonicalization helpers in `shared/catalog-hash` — consumers re-hash the
1430
+ * content they actually render and must refuse new sales on any mismatch.
1431
+ */
1432
+ interface FFIDCatalogPublication {
1433
+ serviceCode: string;
1434
+ environment: FFIDCatalogEnvironment;
1435
+ catalogVersion: string;
1436
+ praxisCopyHash: string;
1437
+ ffidCheckoutCopyHash: string;
1438
+ legalDisclosureHash: string;
1439
+ taxConfigurationHash: string;
1440
+ paymentConfigurationHash: string;
1441
+ scopeHash: string;
1442
+ /** Monotonically increasing per (service, environment) publication counter */
1443
+ salesEpoch: number;
1444
+ /** FFID-generated immutable human-review approval ID */
1445
+ approvalId: string;
1446
+ /** ISO 8601 timestamp of the recorded human review */
1447
+ reviewedAt: string;
1448
+ /** ISO 8601 timestamp the publication became effective */
1449
+ effectiveAt: string;
1450
+ /** ISO 8601 timestamp the publication was superseded (null while current) */
1451
+ supersededAt: string | null;
1452
+ /** ISO 8601 upper bound for old-session sell-through (null unless superseded) */
1453
+ sellThroughUntil: string | null;
1454
+ /** ISO 8601 timestamp an emergency revoke took effect (null unless revoking/revoked) */
1455
+ revocationEffectiveAt: string | null;
1456
+ status: FFIDCatalogPublicationStatus;
1457
+ }
1458
+ /**
1459
+ * A plan inside a published catalog revision (public-safe snapshot).
1460
+ * Never contains Stripe Product/Price identifiers or other secrets.
1461
+ */
1462
+ interface FFIDPublishedPlan {
1463
+ /**
1464
+ * Plan code — always a lowercase slug (`^[a-z0-9][a-z0-9_-]*$`).
1465
+ * Per-service open set; see the FFID repo's
1466
+ * `docs/04-api/PLAN_CODE_CONTRACT.md` for the full guarantees.
1467
+ */
1468
+ code: string;
1469
+ name: string;
1470
+ description: string | null;
1471
+ /** Monthly price in the catalog currency; null when monthly is not offered */
1472
+ priceMonthly: number | null;
1473
+ /** Yearly price in the catalog currency; null when yearly is not offered */
1474
+ priceYearly: number | null;
1475
+ currency: string;
1476
+ /** Billing intervals actually purchasable for this plan */
1477
+ billingIntervals: FFIDBillingInterval[];
1478
+ taxBehavior: FFIDTaxBehavior;
1479
+ minSeats: number;
1480
+ maxSeats: number | null;
1481
+ displayOrder: number;
1482
+ }
1483
+ /**
1484
+ * Response of GET /api/v1/subscriptions/ext/catalog/{serviceCode}.
1485
+ *
1486
+ * `catalogVersion` always equals `publication.catalogVersion`; it is hoisted
1487
+ * to the top level so consumers can pin the version (e.g. as
1488
+ * `expectedCatalogVersion` for checkout) without digging into the publication.
1489
+ */
1490
+ interface FFIDPublishedCatalog {
1491
+ service: {
1492
+ code: string;
1493
+ name: string;
1494
+ };
1495
+ catalogVersion: string;
1496
+ publication: FFIDCatalogPublication;
1497
+ plans: FFIDPublishedPlan[];
1498
+ }
1499
+
1348
1500
  /**
1349
1501
  * FFID SDK Type Definitions
1350
1502
  *
@@ -1452,6 +1604,12 @@ interface FFIDSubscription {
1452
1604
  organizationName?: string | null | undefined;
1453
1605
  }
1454
1606
  /** OAuth userinfo subscription summary */
1607
+ type FFIDSeatAssignmentStatus = 'active' | 'revoking' | 'revoked';
1608
+ interface FFIDSeatAssignmentClaim {
1609
+ organizationId: string;
1610
+ subscriptionId: string;
1611
+ status: FFIDSeatAssignmentStatus;
1612
+ }
1455
1613
  interface FFIDOAuthUserInfoSubscription {
1456
1614
  subscriptionId: string | null;
1457
1615
  status: FFIDSubscriptionStatus | null;
@@ -1469,6 +1627,12 @@ interface FFIDOAuthUserInfoSubscription {
1469
1627
  organizationId: string | null;
1470
1628
  organizationName?: string | null | undefined;
1471
1629
  hasSeatAssignment: boolean | null;
1630
+ /**
1631
+ * Organization/subscription-bound seat claim (#4352). `undefined` means an
1632
+ * older FFID omitted the field; `null` means a present claim was malformed.
1633
+ * Only `status === 'active'` grants Team access.
1634
+ */
1635
+ seatAssignment: FFIDSeatAssignmentClaim | null | undefined;
1472
1636
  /**
1473
1637
  * Stripe `cancel_at_period_end` (#3464, SDK 5.8.0). `true` while the
1474
1638
  * subscription is scheduled to terminate at `currentPeriodEnd`. The
@@ -1488,6 +1652,19 @@ interface FFIDOAuthUserInfoSubscription {
1488
1652
  * round-trip.
1489
1653
  */
1490
1654
  currentPeriodEnd: string | null;
1655
+ /**
1656
+ * Per-service entitlement codes (e.g. `praxis.pack.<slug>.access`) granting
1657
+ * access to one-time / single-pack purchases not encoded in the plan tier
1658
+ * (#4333, SDK 5.25.0). Read this list to gate per-pack access; an empty
1659
+ * array means "no pack entitlements" (the normal case for Pro / Team tiers
1660
+ * that unlock packs via `planCode`).
1661
+ *
1662
+ * The `normalizeUserinfo` boundary defaults this to `[]` when the server
1663
+ * omits it (older FFID pre-#4333) or when the introspect path uses a service
1664
+ * API key without `subscription:read`, so consumers always see a plain
1665
+ * `readonly string[]` and never branch on an "unknown" sentinel.
1666
+ */
1667
+ entitlements: readonly string[];
1491
1668
  }
1492
1669
  /**
1493
1670
  * Unified user-identity shape returned by `getSession()` (→ `/oauth/userinfo`)
@@ -1789,47 +1966,6 @@ interface FFIDAnalyticsConfig {
1789
1966
  */
1790
1967
  isActive: boolean;
1791
1968
  }
1792
- /**
1793
- * Discriminant for redirect failures that callers need to handle
1794
- * programmatically (vs. logging the human-readable `error` string).
1795
- *
1796
- * - `'redirect_loop_detected'`: `redirectToAuthorize()` detected that the
1797
- * same authorize URL (keyed by `baseUrl + client_id + organization_id`)
1798
- * has been fired **3 times within 60 seconds**. Caller should surface a
1799
- * manual "再度ログインする" UI rather than retry automatically (#2406 / #2411).
1800
- *
1801
- * - `'state_persistence_failed'`: `redirectToAuthorize()` could not persist
1802
- * the OAuth CSRF `state` (storage unavailable / quota). Redirect is aborted
1803
- * fail-closed (#4136) — callers should surface a manual retry UI.
1804
- *
1805
- * Other failure paths (SSR environment, PKCE generation failure, empty
1806
- * organizationId) currently return `error` without a `code`. Treat this union
1807
- * as forward-extensible and do NOT exhaustively `switch` over it without a
1808
- * `default` branch for consumer code that must stay compatible across SDK
1809
- * upgrades.
1810
- */
1811
- type FFIDRedirectErrorCode = 'redirect_loop_detected' | 'state_persistence_failed';
1812
- /**
1813
- * Result of a redirect operation (redirectToLogin / redirectToAuthorize / redirectToLogout)
1814
- *
1815
- * Structured return type so callers can inspect failure reasons
1816
- * instead of receiving a bare `false`. When `code` is set, branch on it
1817
- * for programmatic handling; otherwise log `error` for humans.
1818
- *
1819
- * `loopDetectionDisabled` (#4119): set to `true` when sessionStorage is
1820
- * unavailable (iOS WebKit private mode / eviction) so redirect-loop detection
1821
- * ran **fail-open** — the redirect fired, but repeated-redirect protection is
1822
- * disabled for this browser session. Callers that need loop protection on
1823
- * such browsers should add their own max-retry guard.
1824
- */
1825
- type FFIDRedirectResult = {
1826
- success: true;
1827
- loopDetectionDisabled?: boolean;
1828
- } | {
1829
- success: false;
1830
- error: string;
1831
- code?: FFIDRedirectErrorCode;
1832
- };
1833
1969
 
1834
1970
  /** OTP / magic link methods - sendOtp / verifyOtp */
1835
1971
 
@@ -2046,6 +2182,7 @@ declare function createFFIDClient(config: FFIDConfig): {
2046
2182
  assignSeats: (params: FFIDAssignSeatsParams) => Promise<FFIDApiResponse<FFIDAssignSeatsResponse>>;
2047
2183
  unassignSeat: (params: FFIDUnassignSeatParams) => Promise<FFIDApiResponse<FFIDUnassignSeatResponse>>;
2048
2184
  listPlans: () => Promise<FFIDApiResponse<FFIDListPlansResponse>>;
2185
+ getPublishedCatalog: (serviceCode: string) => Promise<FFIDApiResponse<FFIDPublishedCatalog>>;
2049
2186
  getSubscription: (subscriptionId: string) => Promise<FFIDApiResponse<FFIDSubscriptionDetail>>;
2050
2187
  subscribe: (params: FFIDSubscribeParams) => Promise<FFIDApiResponse<FFIDSubscribeResponse>>;
2051
2188
  changePlan: (params: FFIDChangePlanParams) => Promise<FFIDApiResponse<FFIDChangePlanResponse>>;
@@ -1345,6 +1345,158 @@ interface FFIDUpdateUserProfileRequest {
1345
1345
  preferences?: Record<string, unknown> | null;
1346
1346
  }
1347
1347
 
1348
+ /**
1349
+ * OAuth flow types: client-side redirect results and OAuth token / RFC 7662
1350
+ * introspection payloads.
1351
+ *
1352
+ * Extracted from `types/index.ts` to keep that barrel under the 800-line hard
1353
+ * limit (`scripts/check-file-size.sh`). Re-exported from `types/index.ts`, so
1354
+ * the public API is unchanged — import from `@feelflow/ffid-sdk` as before.
1355
+ */
1356
+
1357
+ /**
1358
+ * Discriminant for redirect failures that callers need to handle
1359
+ * programmatically (vs. logging the human-readable `error` string).
1360
+ *
1361
+ * - `'redirect_loop_detected'`: `redirectToAuthorize()` detected that the
1362
+ * same authorize URL (keyed by `baseUrl + client_id + organization_id`)
1363
+ * has been fired **3 times within 60 seconds**. Caller should surface a
1364
+ * manual "再度ログインする" UI rather than retry automatically (#2406 / #2411).
1365
+ *
1366
+ * - `'state_persistence_failed'`: `redirectToAuthorize()` could not persist
1367
+ * the OAuth CSRF `state` (storage unavailable / quota). Redirect is aborted
1368
+ * fail-closed (#4136) — callers should surface a manual retry UI.
1369
+ *
1370
+ * Other failure paths (SSR environment, PKCE generation failure, empty
1371
+ * organizationId) currently return `error` without a `code`. Treat this union
1372
+ * as forward-extensible and do NOT exhaustively `switch` over it without a
1373
+ * `default` branch for consumer code that must stay compatible across SDK
1374
+ * upgrades.
1375
+ */
1376
+ type FFIDRedirectErrorCode = 'redirect_loop_detected' | 'state_persistence_failed';
1377
+ /**
1378
+ * Result of a redirect operation (redirectToLogin / redirectToAuthorize / redirectToLogout)
1379
+ *
1380
+ * Structured return type so callers can inspect failure reasons
1381
+ * instead of receiving a bare `false`. When `code` is set, branch on it
1382
+ * for programmatic handling; otherwise log `error` for humans.
1383
+ *
1384
+ * `loopDetectionDisabled` (#4119): set to `true` when sessionStorage is
1385
+ * unavailable (iOS WebKit private mode / eviction) so redirect-loop detection
1386
+ * ran **fail-open** — the redirect fired, but repeated-redirect protection is
1387
+ * disabled for this browser session. Callers that need loop protection on
1388
+ * such browsers should add their own max-retry guard.
1389
+ */
1390
+ type FFIDRedirectResult = {
1391
+ success: true;
1392
+ loopDetectionDisabled?: boolean;
1393
+ } | {
1394
+ success: false;
1395
+ error: string;
1396
+ code?: FFIDRedirectErrorCode;
1397
+ };
1398
+
1399
+ /** Deployment environments a catalog publication can apply to (single source) */
1400
+ declare const FFID_CATALOG_ENVIRONMENTS: readonly ["staging", "production"];
1401
+ /** Deployment environment a catalog publication applies to */
1402
+ type FFIDCatalogEnvironment = (typeof FFID_CATALOG_ENVIRONMENTS)[number];
1403
+ /**
1404
+ * Tax behavior of a published price.
1405
+ * `inclusive`: displayed price contains Stripe-calculated tax (Personal/Pro).
1406
+ * `exclusive`: tax is added on top of the base price (Team).
1407
+ */
1408
+ type FFIDTaxBehavior = 'inclusive' | 'exclusive';
1409
+ /**
1410
+ * Publication lifecycle statuses (single source — the type, the SDK runtime
1411
+ * validator, and consumer sets all derive from this tuple so a new literal
1412
+ * cannot be added to one side only).
1413
+ */
1414
+ declare const FFID_CATALOG_PUBLICATION_STATUSES: readonly ["approved", "superseded", "revoking", "revoked"];
1415
+ /**
1416
+ * Publication lifecycle status.
1417
+ * - `approved`: sellable — the only status that permits new paid checkout
1418
+ * - `superseded`: replaced by a newer revision (old sessions may sell through
1419
+ * until `sellThroughUntil`)
1420
+ * - `revoking`: emergency stop in progress — never sellable
1421
+ * - `revoked`: emergency stop completed — never sellable
1422
+ */
1423
+ type FFIDCatalogPublicationStatus = (typeof FFID_CATALOG_PUBLICATION_STATUSES)[number];
1424
+ /**
1425
+ * Human-approved publication record binding a catalog revision to the
1426
+ * content/config hashes reviewed at approval time.
1427
+ *
1428
+ * All `*Hash` fields are lowercase SHA-256 hex computed with the shared
1429
+ * canonicalization helpers in `shared/catalog-hash` — consumers re-hash the
1430
+ * content they actually render and must refuse new sales on any mismatch.
1431
+ */
1432
+ interface FFIDCatalogPublication {
1433
+ serviceCode: string;
1434
+ environment: FFIDCatalogEnvironment;
1435
+ catalogVersion: string;
1436
+ praxisCopyHash: string;
1437
+ ffidCheckoutCopyHash: string;
1438
+ legalDisclosureHash: string;
1439
+ taxConfigurationHash: string;
1440
+ paymentConfigurationHash: string;
1441
+ scopeHash: string;
1442
+ /** Monotonically increasing per (service, environment) publication counter */
1443
+ salesEpoch: number;
1444
+ /** FFID-generated immutable human-review approval ID */
1445
+ approvalId: string;
1446
+ /** ISO 8601 timestamp of the recorded human review */
1447
+ reviewedAt: string;
1448
+ /** ISO 8601 timestamp the publication became effective */
1449
+ effectiveAt: string;
1450
+ /** ISO 8601 timestamp the publication was superseded (null while current) */
1451
+ supersededAt: string | null;
1452
+ /** ISO 8601 upper bound for old-session sell-through (null unless superseded) */
1453
+ sellThroughUntil: string | null;
1454
+ /** ISO 8601 timestamp an emergency revoke took effect (null unless revoking/revoked) */
1455
+ revocationEffectiveAt: string | null;
1456
+ status: FFIDCatalogPublicationStatus;
1457
+ }
1458
+ /**
1459
+ * A plan inside a published catalog revision (public-safe snapshot).
1460
+ * Never contains Stripe Product/Price identifiers or other secrets.
1461
+ */
1462
+ interface FFIDPublishedPlan {
1463
+ /**
1464
+ * Plan code — always a lowercase slug (`^[a-z0-9][a-z0-9_-]*$`).
1465
+ * Per-service open set; see the FFID repo's
1466
+ * `docs/04-api/PLAN_CODE_CONTRACT.md` for the full guarantees.
1467
+ */
1468
+ code: string;
1469
+ name: string;
1470
+ description: string | null;
1471
+ /** Monthly price in the catalog currency; null when monthly is not offered */
1472
+ priceMonthly: number | null;
1473
+ /** Yearly price in the catalog currency; null when yearly is not offered */
1474
+ priceYearly: number | null;
1475
+ currency: string;
1476
+ /** Billing intervals actually purchasable for this plan */
1477
+ billingIntervals: FFIDBillingInterval[];
1478
+ taxBehavior: FFIDTaxBehavior;
1479
+ minSeats: number;
1480
+ maxSeats: number | null;
1481
+ displayOrder: number;
1482
+ }
1483
+ /**
1484
+ * Response of GET /api/v1/subscriptions/ext/catalog/{serviceCode}.
1485
+ *
1486
+ * `catalogVersion` always equals `publication.catalogVersion`; it is hoisted
1487
+ * to the top level so consumers can pin the version (e.g. as
1488
+ * `expectedCatalogVersion` for checkout) without digging into the publication.
1489
+ */
1490
+ interface FFIDPublishedCatalog {
1491
+ service: {
1492
+ code: string;
1493
+ name: string;
1494
+ };
1495
+ catalogVersion: string;
1496
+ publication: FFIDCatalogPublication;
1497
+ plans: FFIDPublishedPlan[];
1498
+ }
1499
+
1348
1500
  /**
1349
1501
  * FFID SDK Type Definitions
1350
1502
  *
@@ -1452,6 +1604,12 @@ interface FFIDSubscription {
1452
1604
  organizationName?: string | null | undefined;
1453
1605
  }
1454
1606
  /** OAuth userinfo subscription summary */
1607
+ type FFIDSeatAssignmentStatus = 'active' | 'revoking' | 'revoked';
1608
+ interface FFIDSeatAssignmentClaim {
1609
+ organizationId: string;
1610
+ subscriptionId: string;
1611
+ status: FFIDSeatAssignmentStatus;
1612
+ }
1455
1613
  interface FFIDOAuthUserInfoSubscription {
1456
1614
  subscriptionId: string | null;
1457
1615
  status: FFIDSubscriptionStatus | null;
@@ -1469,6 +1627,12 @@ interface FFIDOAuthUserInfoSubscription {
1469
1627
  organizationId: string | null;
1470
1628
  organizationName?: string | null | undefined;
1471
1629
  hasSeatAssignment: boolean | null;
1630
+ /**
1631
+ * Organization/subscription-bound seat claim (#4352). `undefined` means an
1632
+ * older FFID omitted the field; `null` means a present claim was malformed.
1633
+ * Only `status === 'active'` grants Team access.
1634
+ */
1635
+ seatAssignment: FFIDSeatAssignmentClaim | null | undefined;
1472
1636
  /**
1473
1637
  * Stripe `cancel_at_period_end` (#3464, SDK 5.8.0). `true` while the
1474
1638
  * subscription is scheduled to terminate at `currentPeriodEnd`. The
@@ -1488,6 +1652,19 @@ interface FFIDOAuthUserInfoSubscription {
1488
1652
  * round-trip.
1489
1653
  */
1490
1654
  currentPeriodEnd: string | null;
1655
+ /**
1656
+ * Per-service entitlement codes (e.g. `praxis.pack.<slug>.access`) granting
1657
+ * access to one-time / single-pack purchases not encoded in the plan tier
1658
+ * (#4333, SDK 5.25.0). Read this list to gate per-pack access; an empty
1659
+ * array means "no pack entitlements" (the normal case for Pro / Team tiers
1660
+ * that unlock packs via `planCode`).
1661
+ *
1662
+ * The `normalizeUserinfo` boundary defaults this to `[]` when the server
1663
+ * omits it (older FFID pre-#4333) or when the introspect path uses a service
1664
+ * API key without `subscription:read`, so consumers always see a plain
1665
+ * `readonly string[]` and never branch on an "unknown" sentinel.
1666
+ */
1667
+ entitlements: readonly string[];
1491
1668
  }
1492
1669
  /**
1493
1670
  * Unified user-identity shape returned by `getSession()` (→ `/oauth/userinfo`)
@@ -1789,47 +1966,6 @@ interface FFIDAnalyticsConfig {
1789
1966
  */
1790
1967
  isActive: boolean;
1791
1968
  }
1792
- /**
1793
- * Discriminant for redirect failures that callers need to handle
1794
- * programmatically (vs. logging the human-readable `error` string).
1795
- *
1796
- * - `'redirect_loop_detected'`: `redirectToAuthorize()` detected that the
1797
- * same authorize URL (keyed by `baseUrl + client_id + organization_id`)
1798
- * has been fired **3 times within 60 seconds**. Caller should surface a
1799
- * manual "再度ログインする" UI rather than retry automatically (#2406 / #2411).
1800
- *
1801
- * - `'state_persistence_failed'`: `redirectToAuthorize()` could not persist
1802
- * the OAuth CSRF `state` (storage unavailable / quota). Redirect is aborted
1803
- * fail-closed (#4136) — callers should surface a manual retry UI.
1804
- *
1805
- * Other failure paths (SSR environment, PKCE generation failure, empty
1806
- * organizationId) currently return `error` without a `code`. Treat this union
1807
- * as forward-extensible and do NOT exhaustively `switch` over it without a
1808
- * `default` branch for consumer code that must stay compatible across SDK
1809
- * upgrades.
1810
- */
1811
- type FFIDRedirectErrorCode = 'redirect_loop_detected' | 'state_persistence_failed';
1812
- /**
1813
- * Result of a redirect operation (redirectToLogin / redirectToAuthorize / redirectToLogout)
1814
- *
1815
- * Structured return type so callers can inspect failure reasons
1816
- * instead of receiving a bare `false`. When `code` is set, branch on it
1817
- * for programmatic handling; otherwise log `error` for humans.
1818
- *
1819
- * `loopDetectionDisabled` (#4119): set to `true` when sessionStorage is
1820
- * unavailable (iOS WebKit private mode / eviction) so redirect-loop detection
1821
- * ran **fail-open** — the redirect fired, but repeated-redirect protection is
1822
- * disabled for this browser session. Callers that need loop protection on
1823
- * such browsers should add their own max-retry guard.
1824
- */
1825
- type FFIDRedirectResult = {
1826
- success: true;
1827
- loopDetectionDisabled?: boolean;
1828
- } | {
1829
- success: false;
1830
- error: string;
1831
- code?: FFIDRedirectErrorCode;
1832
- };
1833
1969
 
1834
1970
  /** OTP / magic link methods - sendOtp / verifyOtp */
1835
1971
 
@@ -2046,6 +2182,7 @@ declare function createFFIDClient(config: FFIDConfig): {
2046
2182
  assignSeats: (params: FFIDAssignSeatsParams) => Promise<FFIDApiResponse<FFIDAssignSeatsResponse>>;
2047
2183
  unassignSeat: (params: FFIDUnassignSeatParams) => Promise<FFIDApiResponse<FFIDUnassignSeatResponse>>;
2048
2184
  listPlans: () => Promise<FFIDApiResponse<FFIDListPlansResponse>>;
2185
+ getPublishedCatalog: (serviceCode: string) => Promise<FFIDApiResponse<FFIDPublishedCatalog>>;
2049
2186
  getSubscription: (subscriptionId: string) => Promise<FFIDApiResponse<FFIDSubscriptionDetail>>;
2050
2187
  subscribe: (params: FFIDSubscribeParams) => Promise<FFIDApiResponse<FFIDSubscribeResponse>>;
2051
2188
  changePlan: (params: FFIDChangePlanParams) => Promise<FFIDApiResponse<FFIDChangePlanResponse>>;
@@ -318,6 +318,12 @@ interface FFIDSubscription {
318
318
  organizationName?: string | null | undefined;
319
319
  }
320
320
  /** OAuth userinfo subscription summary */
321
+ type FFIDSeatAssignmentStatus = 'active' | 'revoking' | 'revoked';
322
+ interface FFIDSeatAssignmentClaim {
323
+ organizationId: string;
324
+ subscriptionId: string;
325
+ status: FFIDSeatAssignmentStatus;
326
+ }
321
327
  interface FFIDOAuthUserInfoSubscription {
322
328
  subscriptionId: string | null;
323
329
  status: FFIDSubscriptionStatus | null;
@@ -335,6 +341,12 @@ interface FFIDOAuthUserInfoSubscription {
335
341
  organizationId: string | null;
336
342
  organizationName?: string | null | undefined;
337
343
  hasSeatAssignment: boolean | null;
344
+ /**
345
+ * Organization/subscription-bound seat claim (#4352). `undefined` means an
346
+ * older FFID omitted the field; `null` means a present claim was malformed.
347
+ * Only `status === 'active'` grants Team access.
348
+ */
349
+ seatAssignment: FFIDSeatAssignmentClaim | null | undefined;
338
350
  /**
339
351
  * Stripe `cancel_at_period_end` (#3464, SDK 5.8.0). `true` while the
340
352
  * subscription is scheduled to terminate at `currentPeriodEnd`. The
@@ -354,6 +366,19 @@ interface FFIDOAuthUserInfoSubscription {
354
366
  * round-trip.
355
367
  */
356
368
  currentPeriodEnd: string | null;
369
+ /**
370
+ * Per-service entitlement codes (e.g. `praxis.pack.<slug>.access`) granting
371
+ * access to one-time / single-pack purchases not encoded in the plan tier
372
+ * (#4333, SDK 5.25.0). Read this list to gate per-pack access; an empty
373
+ * array means "no pack entitlements" (the normal case for Pro / Team tiers
374
+ * that unlock packs via `planCode`).
375
+ *
376
+ * The `normalizeUserinfo` boundary defaults this to `[]` when the server
377
+ * omits it (older FFID pre-#4333) or when the introspect path uses a service
378
+ * API key without `subscription:read`, so consumers always see a plain
379
+ * `readonly string[]` and never branch on an "unknown" sentinel.
380
+ */
381
+ entitlements: readonly string[];
357
382
  }
358
383
  /**
359
384
  * Unified user-identity shape returned by `getSession()` (→ `/oauth/userinfo`)
@@ -811,97 +836,6 @@ interface FFIDAnalyticsConfig {
811
836
  */
812
837
  isActive: boolean;
813
838
  }
814
- /**
815
- * Discriminant for redirect failures that callers need to handle
816
- * programmatically (vs. logging the human-readable `error` string).
817
- *
818
- * - `'redirect_loop_detected'`: `redirectToAuthorize()` detected that the
819
- * same authorize URL (keyed by `baseUrl + client_id + organization_id`)
820
- * has been fired **3 times within 60 seconds**. Caller should surface a
821
- * manual "再度ログインする" UI rather than retry automatically (#2406 / #2411).
822
- *
823
- * - `'state_persistence_failed'`: `redirectToAuthorize()` could not persist
824
- * the OAuth CSRF `state` (storage unavailable / quota). Redirect is aborted
825
- * fail-closed (#4136) — callers should surface a manual retry UI.
826
- *
827
- * Other failure paths (SSR environment, PKCE generation failure, empty
828
- * organizationId) currently return `error` without a `code`. Treat this union
829
- * as forward-extensible and do NOT exhaustively `switch` over it without a
830
- * `default` branch for consumer code that must stay compatible across SDK
831
- * upgrades.
832
- */
833
- type FFIDRedirectErrorCode = 'redirect_loop_detected' | 'state_persistence_failed';
834
- /**
835
- * Result of a redirect operation (redirectToLogin / redirectToAuthorize / redirectToLogout)
836
- *
837
- * Structured return type so callers can inspect failure reasons
838
- * instead of receiving a bare `false`. When `code` is set, branch on it
839
- * for programmatic handling; otherwise log `error` for humans.
840
- *
841
- * `loopDetectionDisabled` (#4119): set to `true` when sessionStorage is
842
- * unavailable (iOS WebKit private mode / eviction) so redirect-loop detection
843
- * ran **fail-open** — the redirect fired, but repeated-redirect protection is
844
- * disabled for this browser session. Callers that need loop protection on
845
- * such browsers should add their own max-retry guard.
846
- */
847
- type FFIDRedirectResult = {
848
- success: true;
849
- loopDetectionDisabled?: boolean;
850
- } | {
851
- success: false;
852
- error: string;
853
- code?: FFIDRedirectErrorCode;
854
- };
855
- /**
856
- * OAuth 2.0 token response from FFID token endpoint
857
- */
858
- interface FFIDOAuthTokenResponse {
859
- access_token: string;
860
- token_type: 'Bearer';
861
- expires_in: number;
862
- refresh_token: string;
863
- }
864
- /**
865
- * RFC 7662 Token Introspection response (raw format from server)
866
- *
867
- * Used internally by verifyAccessToken(). Consumers receive the normalized
868
- * FFIDOAuthUserInfo type instead.
869
- *
870
- * @see https://tools.ietf.org/html/rfc7662
871
- */
872
- interface FFIDTokenIntrospectionResponse {
873
- active: boolean;
874
- sub?: string;
875
- email?: string;
876
- /** Whether the user's email is verified (#3791). Optional for backward compat with older FFID. */
877
- email_verified?: boolean;
878
- name?: string;
879
- picture?: string | null;
880
- company_name?: string | null;
881
- department?: string | null;
882
- position?: string | null;
883
- phone_number?: string | null;
884
- scope?: string | null;
885
- exp?: number;
886
- iat?: number;
887
- iss?: string;
888
- token_type?: 'Bearer';
889
- client_id?: string;
890
- organization_id?: string | null;
891
- organization_name?: string | null;
892
- subscription?: {
893
- subscription_id: string | null;
894
- status: FFIDOAuthUserInfoSubscription['status'];
895
- /** Optional for backward compat with older FFID (< 5.3.0 / #3353). */
896
- effective_status?: FFIDOAuthUserInfoSubscription['effectiveStatus'];
897
- plan_code: string | null;
898
- seat_model: FFIDOAuthUserInfoSubscription['seatModel'];
899
- member_role: FFIDOAuthUserInfoSubscription['memberRole'];
900
- organization_id: string | null;
901
- organization_name?: string | null;
902
- has_seat_assignment?: boolean;
903
- };
904
- }
905
839
 
906
840
  /**
907
841
  * FFID Announcements SDK Type Definitions
@@ -1602,4 +1536,4 @@ interface FFIDInquiryFormPlaceholderContext {
1602
1536
  }
1603
1537
  declare function FFIDInquiryForm({ mode, prefill, organizations, preselectedOrganizationId, categories, termsVersion, privacyVersion, termsHref, privacyHref, turnstileToken, turnstileSlot, onSubmit, onChange, legalLayout, messagePlaceholder, requireCategorySelection, unstyled, classNames, locale, className, }: FFIDInquiryFormProps): react_jsx_runtime.JSX.Element;
1604
1538
 
1605
- export { type FFIDInquiryFormSubmitData as $, type AnnouncementListResponse as A, type Announcement as B, type AnnouncementStatus as C, type AnnouncementType as D, type EffectiveSubscriptionStatus as E, type FFIDSubscriptionStatus as F, EFFECTIVE_SUBSCRIPTION_STATUSES as G, FFIDAnnouncementBadge as H, FFIDAnnouncementList as I, type FFIDAnnouncementsError as J, type FFIDAnnouncementsErrorCode as K, type ListAnnouncementsOptions as L, type FFIDAnnouncementsServerResponse as M, type FFIDApiResponseMeta as N, type FFIDCacheConfig as O, type FFIDContextValue as P, type FFIDInquiryCategory as Q, type FFIDInquiryCategorySite2026 as R, FFIDInquiryForm as S, type TokenStore as T, type FFIDInquiryFormCategoryItem as U, type FFIDInquiryFormClassNames as V, type FFIDInquiryFormLegalLayout as W, type FFIDInquiryFormOrganization as X, type FFIDInquiryFormPlaceholderContext as Y, type FFIDInquiryFormPrefill as Z, type FFIDInquiryFormProps as _, type FFIDPrompt as a, type FFIDInquiryFormSubmitResult as a0, type FFIDJwtClaims as a1, FFIDLoginButton as a2, type FFIDOAuthTokenResponse as a3, type FFIDOAuthUserInfoMemberRole as a4, FFIDOrganizationSwitcher as a5, type FFIDRedirectErrorCode as a6, type FFIDSeatModel as a7, type FFIDServiceAccessDenialReason as a8, type FFIDServiceAccessFailPolicy as a9, FFIDSubscriptionBadge as aa, type FFIDTokenIntrospectionResponse as ab, FFIDUserMenu as ac, FFID_INQUIRY_CATEGORIES as ad, FFID_INQUIRY_CATEGORIES_SITE_2026 as ae, type TokenData as af, type UseFFIDAnnouncementsOptions as ag, type UseFFIDAnnouncementsReturn as ah, createTokenStore as ai, isFFIDInquiryCategorySite2026 as aj, useFFIDAnnouncements as ak, type FFIDAnnouncementBadgeClassNames as al, type FFIDAnnouncementBadgeProps as am, type FFIDAnnouncementListClassNames as an, type FFIDAnnouncementListProps as ao, type FFIDLoginButtonProps as ap, type FFIDOrganizationSwitcherClassNames as aq, type FFIDOrganizationSwitcherProps as ar, type FFIDSubscriptionBadgeClassNames as as, type FFIDSubscriptionBadgeProps as at, type FFIDUserMenuClassNames as au, type FFIDUserMenuProps as av, type FFIDConfig as b, type FFIDApiResponse as c, type FFIDSessionResponse as d, type FFIDSignOutResult as e, type FFIDRedirectResult as f, type FFIDError as g, type FFIDSubscriptionCheckResponse as h, type FFIDCheckServiceAccessParams as i, type FFIDServiceAccessDecision as j, type FFIDAnalyticsConfig as k, type FFIDVerifyAccessTokenOptions as l, type FFIDOAuthUserInfo as m, type FFIDInquiryCreateParams as n, type FFIDInquiryCreateResponse as o, type FFIDAuthMode as p, type FFIDLogger as q, type FFIDCacheAdapter as r, type FFIDUser as s, type FFIDOrganization as t, type FFIDSubscription as u, type FFIDSubscriptionContextValue as v, type FFIDOAuthUserInfoSubscription as w, type FFIDAnnouncementsClientConfig as x, type FFIDAnnouncementsApiResponse as y, type FFIDAnnouncementsLogger as z };
1539
+ export { type FFIDInquiryFormSubmitResult as $, type AnnouncementListResponse as A, type AnnouncementStatus as B, type AnnouncementType as C, EFFECTIVE_SUBSCRIPTION_STATUSES as D, type EffectiveSubscriptionStatus as E, type FFIDSubscriptionStatus as F, FFIDAnnouncementBadge as G, FFIDAnnouncementList as H, type FFIDAnnouncementsError as I, type FFIDAnnouncementsErrorCode as J, type FFIDAnnouncementsServerResponse as K, type ListAnnouncementsOptions as L, type FFIDApiResponseMeta as M, type FFIDCacheConfig as N, type FFIDContextValue as O, type FFIDInquiryCategory as P, type FFIDInquiryCategorySite2026 as Q, FFIDInquiryForm as R, type FFIDInquiryFormCategoryItem as S, type TokenStore as T, type FFIDInquiryFormClassNames as U, type FFIDInquiryFormLegalLayout as V, type FFIDInquiryFormOrganization as W, type FFIDInquiryFormPlaceholderContext as X, type FFIDInquiryFormPrefill as Y, type FFIDInquiryFormProps as Z, type FFIDInquiryFormSubmitData as _, type FFIDOAuthUserInfoSubscription as a, type FFIDJwtClaims as a0, FFIDLoginButton as a1, type FFIDOAuthUserInfoMemberRole as a2, FFIDOrganizationSwitcher as a3, type FFIDSeatAssignmentClaim as a4, type FFIDSeatAssignmentStatus as a5, type FFIDSeatModel as a6, type FFIDServiceAccessDenialReason as a7, type FFIDServiceAccessFailPolicy as a8, FFIDSubscriptionBadge as a9, FFIDUserMenu as aa, FFID_INQUIRY_CATEGORIES as ab, FFID_INQUIRY_CATEGORIES_SITE_2026 as ac, type TokenData as ad, type UseFFIDAnnouncementsOptions as ae, type UseFFIDAnnouncementsReturn as af, createTokenStore as ag, isFFIDInquiryCategorySite2026 as ah, useFFIDAnnouncements as ai, type FFIDAnnouncementBadgeClassNames as aj, type FFIDAnnouncementBadgeProps as ak, type FFIDAnnouncementListClassNames as al, type FFIDAnnouncementListProps as am, type FFIDLoginButtonProps as an, type FFIDOrganizationSwitcherClassNames as ao, type FFIDOrganizationSwitcherProps as ap, type FFIDSubscriptionBadgeClassNames as aq, type FFIDSubscriptionBadgeProps as ar, type FFIDUserMenuClassNames as as, type FFIDUserMenuProps as at, type FFIDPrompt as b, type FFIDConfig as c, type FFIDApiResponse as d, type FFIDSessionResponse as e, type FFIDSignOutResult as f, type FFIDError as g, type FFIDSubscriptionCheckResponse as h, type FFIDCheckServiceAccessParams as i, type FFIDServiceAccessDecision as j, type FFIDAnalyticsConfig as k, type FFIDVerifyAccessTokenOptions as l, type FFIDOAuthUserInfo as m, type FFIDInquiryCreateParams as n, type FFIDInquiryCreateResponse as o, type FFIDAuthMode as p, type FFIDLogger as q, type FFIDCacheAdapter as r, type FFIDUser as s, type FFIDOrganization as t, type FFIDSubscription as u, type FFIDSubscriptionContextValue as v, type FFIDAnnouncementsClientConfig as w, type FFIDAnnouncementsApiResponse as x, type FFIDAnnouncementsLogger as y, type Announcement as z };