@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.
@@ -116,6 +116,24 @@ function isSessionEligibleSubscriptionStatus(value) {
116
116
  function hasOwnKey(value, key) {
117
117
  return Object.prototype.hasOwnProperty.call(value, key);
118
118
  }
119
+ function normalizeEntitlements(raw) {
120
+ if (!Array.isArray(raw)) return [];
121
+ return raw.flatMap((code) => {
122
+ if (typeof code !== "string") return [];
123
+ const trimmed = code.trim();
124
+ return trimmed.length > 0 ? [trimmed] : [];
125
+ });
126
+ }
127
+ function normalizeSeatAssignmentClaim(raw, organizationId, subscriptionId) {
128
+ if (!raw || typeof raw !== "object") return null;
129
+ const { organization_id, subscription_id, status } = raw;
130
+ if (typeof organization_id !== "string" || typeof subscription_id !== "string" || typeof status !== "string" || !["active", "revoking", "revoked"].includes(status) || organization_id !== organizationId || subscription_id !== subscriptionId) return null;
131
+ return {
132
+ organizationId: organization_id,
133
+ subscriptionId: subscription_id,
134
+ status
135
+ };
136
+ }
119
137
  function normalizeUserinfo(raw) {
120
138
  const hasOrganizationId = hasOwnKey(raw, "organization_id");
121
139
  const hasOrganizationName = hasOwnKey(raw, "organization_name");
@@ -149,6 +167,11 @@ function normalizeUserinfo(raw) {
149
167
  organizationId: raw.subscription.organization_id ?? null,
150
168
  organizationName: hasOwnKey(raw.subscription, "organization_name") ? raw.subscription.organization_name ?? null : null,
151
169
  hasSeatAssignment: raw.subscription.has_seat_assignment ?? false,
170
+ seatAssignment: hasOwnKey(raw.subscription, "seat_assignment") ? normalizeSeatAssignmentClaim(
171
+ raw.subscription.seat_assignment,
172
+ raw.subscription.organization_id ?? null,
173
+ raw.subscription.subscription_id ?? null
174
+ ) : void 0,
152
175
  // #3464: legacy FFID (< this issue) omits the field on the wire.
153
176
  // The "unknown" axis is unused — no consumer differentiates "server
154
177
  // didn't tell us" from "definitely not scheduled" because the
@@ -156,7 +179,10 @@ function normalizeUserinfo(raw) {
156
179
  // `'active_canceling'`) for legacy backends. Defaulting to `false`
157
180
  // here keeps the downstream contract a plain boolean.
158
181
  cancelAtPeriodEnd: raw.subscription.cancel_at_period_end ?? false,
159
- currentPeriodEnd: raw.subscription.current_period_end ?? null
182
+ currentPeriodEnd: raw.subscription.current_period_end ?? null,
183
+ // #4333: legacy FFID (< this issue) omits the field on the wire; the
184
+ // guard defaults to `[]` so consumers always get a plain string[].
185
+ entitlements: normalizeEntitlements(raw.subscription.entitlements)
160
186
  } : void 0
161
187
  };
162
188
  }
@@ -443,7 +469,12 @@ function createVerifyAccessToken(deps) {
443
469
  member_role: introspectResponse.subscription.member_role,
444
470
  organization_id: introspectResponse.subscription.organization_id,
445
471
  organization_name: introspectResponse.subscription.organization_name ?? null,
446
- has_seat_assignment: introspectResponse.subscription.has_seat_assignment ?? false
472
+ has_seat_assignment: introspectResponse.subscription.has_seat_assignment ?? false,
473
+ ...Object.prototype.hasOwnProperty.call(introspectResponse.subscription, "seat_assignment") ? { seat_assignment: introspectResponse.subscription.seat_assignment } : {},
474
+ // #4333: forward pack entitlement codes through the introspect →
475
+ // userinfo normalization boundary. normalizeUserinfo guards the
476
+ // value, so an older FFID that omits it degrades to `[]`.
477
+ entitlements: introspectResponse.subscription.entitlements
447
478
  }
448
479
  } : base;
449
480
  const userinfo = normalizeUserinfo(raw);
@@ -814,6 +845,364 @@ function createSubscriptionMethods(deps) {
814
845
  };
815
846
  }
816
847
 
848
+ // src/types/published-catalog.ts
849
+ var FFID_CATALOG_ENVIRONMENTS = ["staging", "production"];
850
+ var FFID_CATALOG_PUBLICATION_STATUSES = [
851
+ "approved",
852
+ "superseded",
853
+ "revoking",
854
+ "revoked"
855
+ ];
856
+
857
+ // src/shared/catalog-hash.ts
858
+ var CATALOG_HASH_PATTERN = /^[0-9a-f]{64}$/;
859
+ var HEX_RADIX = 16;
860
+ var HEX_DIGITS_PER_BYTE = 2;
861
+ var FFIDCatalogHashError = class extends Error {
862
+ constructor(message) {
863
+ super(message);
864
+ this.name = "FFIDCatalogHashError";
865
+ }
866
+ };
867
+ function isPlainObject(value) {
868
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
869
+ return false;
870
+ }
871
+ const proto = Object.getPrototypeOf(value);
872
+ return proto === Object.prototype || proto === null;
873
+ }
874
+ function canonicalize(value, path) {
875
+ if (value === null) return "null";
876
+ switch (typeof value) {
877
+ case "string":
878
+ return JSON.stringify(value);
879
+ case "boolean":
880
+ return value ? "true" : "false";
881
+ case "number":
882
+ if (!Number.isFinite(value)) {
883
+ throw new FFIDCatalogHashError(
884
+ `canonicalize: non-finite number at ${path} \u2014 hashes require finite numbers`
885
+ );
886
+ }
887
+ return JSON.stringify(value);
888
+ case "undefined":
889
+ throw new FFIDCatalogHashError(
890
+ `canonicalize: undefined at ${path} \u2014 omit the key entirely or use null`
891
+ );
892
+ }
893
+ if (Array.isArray(value)) {
894
+ const items = value.map((item, index) => canonicalize(item, `${path}[${index}]`));
895
+ return `[${items.join(",")}]`;
896
+ }
897
+ if (isPlainObject(value)) {
898
+ const keys = Object.keys(value).sort();
899
+ const entries = [];
900
+ for (const key of keys) {
901
+ const child = value[key];
902
+ if (child === void 0) {
903
+ throw new FFIDCatalogHashError(
904
+ `canonicalize: undefined at ${path}.${key} \u2014 omit the key entirely or use null`
905
+ );
906
+ }
907
+ entries.push(`${JSON.stringify(key)}:${canonicalize(child, `${path}.${key}`)}`);
908
+ }
909
+ return `{${entries.join(",")}}`;
910
+ }
911
+ throw new FFIDCatalogHashError(
912
+ `canonicalize: unsupported value type (${typeof value}${typeof value === "object" ? `: ${Object.prototype.toString.call(value)}` : ""}) at ${path} \u2014 only JSON-plain values are hashable`
913
+ );
914
+ }
915
+ function canonicalizeCatalogValue(value) {
916
+ return canonicalize(value, "$");
917
+ }
918
+ async function sha256Hex(text) {
919
+ const subtle = globalThis.crypto?.subtle;
920
+ if (!subtle) {
921
+ throw new FFIDCatalogHashError(
922
+ "sha256Hex: Web Crypto (crypto.subtle) is not available in this runtime"
923
+ );
924
+ }
925
+ const digest = await subtle.digest("SHA-256", new TextEncoder().encode(text));
926
+ return Array.from(new Uint8Array(digest)).map((byte) => byte.toString(HEX_RADIX).padStart(HEX_DIGITS_PER_BYTE, "0")).join("");
927
+ }
928
+ async function hashCanonicalJson(value) {
929
+ return sha256Hex(canonicalizeCatalogValue(value));
930
+ }
931
+ function assertHashFormat(hash, field) {
932
+ if (!CATALOG_HASH_PATTERN.test(hash)) {
933
+ throw new FFIDCatalogHashError(
934
+ `${field} must be lowercase SHA-256 hex (64 chars), got: ${JSON.stringify(hash)}`
935
+ );
936
+ }
937
+ }
938
+ function normalizeStringSet(values) {
939
+ return [...new Set(values)].sort();
940
+ }
941
+ function compareByCodeUnits(a, b) {
942
+ if (a.code < b.code) return -1;
943
+ if (a.code > b.code) return 1;
944
+ return 0;
945
+ }
946
+ async function computePraxisCopyHash(presentation) {
947
+ return hashCanonicalJson(presentation);
948
+ }
949
+ async function computeFFIDCheckoutCopyHash(checkoutCopy) {
950
+ return hashCanonicalJson(checkoutCopy);
951
+ }
952
+ async function computeLegalDisclosureHash(canonicalText) {
953
+ return sha256Hex(canonicalText.replace(/\r\n?/g, "\n"));
954
+ }
955
+ async function computeTaxConfigurationHash(config) {
956
+ return hashCanonicalJson({
957
+ allowedCountries: normalizeStringSet(config.allowedCountries),
958
+ stripeTaxRegistrations: normalizeStringSet(config.stripeTaxRegistrations),
959
+ billingAddressRequired: config.billingAddressRequired,
960
+ planTaxCodes: config.planTaxCodes,
961
+ planTaxBehaviors: config.planTaxBehaviors
962
+ });
963
+ }
964
+ async function computePaymentConfigurationHash(config) {
965
+ return hashCanonicalJson({
966
+ environment: config.environment,
967
+ currency: config.currency,
968
+ paymentMethodTypes: normalizeStringSet(config.paymentMethodTypes),
969
+ dynamicPaymentMethodsEnabled: config.dynamicPaymentMethodsEnabled,
970
+ manualInvoiceAllowed: config.manualInvoiceAllowed,
971
+ customerBalanceAllowed: config.customerBalanceAllowed,
972
+ delayedPaymentMethodsAllowed: config.delayedPaymentMethodsAllowed,
973
+ customerInvoiceBalanceRequired: config.customerInvoiceBalanceRequired
974
+ });
975
+ }
976
+ async function computeScopeHash(input) {
977
+ assertHashFormat(input.praxisCopyHash, "praxisCopyHash");
978
+ assertHashFormat(input.ffidCheckoutCopyHash, "ffidCheckoutCopyHash");
979
+ assertHashFormat(input.legalDisclosureHash, "legalDisclosureHash");
980
+ assertHashFormat(input.taxConfigurationHash, "taxConfigurationHash");
981
+ assertHashFormat(input.paymentConfigurationHash, "paymentConfigurationHash");
982
+ const seenCodes = /* @__PURE__ */ new Set();
983
+ for (const plan of input.plans) {
984
+ if (seenCodes.has(plan.code)) {
985
+ throw new FFIDCatalogHashError(
986
+ `computeScopeHash: duplicate plan code ${JSON.stringify(plan.code)}`
987
+ );
988
+ }
989
+ seenCodes.add(plan.code);
990
+ }
991
+ const plans = [...input.plans].sort(compareByCodeUnits).map((plan) => ({
992
+ code: plan.code,
993
+ priceMonthly: plan.priceMonthly,
994
+ priceYearly: plan.priceYearly,
995
+ currency: plan.currency,
996
+ // Set semantics — interval order must not flip the digest.
997
+ billingIntervals: normalizeStringSet(plan.billingIntervals),
998
+ taxBehavior: plan.taxBehavior,
999
+ minSeats: plan.minSeats
1000
+ }));
1001
+ return hashCanonicalJson({
1002
+ plans,
1003
+ praxisCopyHash: input.praxisCopyHash,
1004
+ ffidCheckoutCopyHash: input.ffidCheckoutCopyHash,
1005
+ legalDisclosureHash: input.legalDisclosureHash,
1006
+ taxConfigurationHash: input.taxConfigurationHash,
1007
+ paymentConfigurationHash: input.paymentConfigurationHash
1008
+ });
1009
+ }
1010
+
1011
+ // src/client/catalog-methods.ts
1012
+ var EXT_CATALOG_ENDPOINT = "/api/v1/subscriptions/ext/catalog";
1013
+ var MALFORMED_CATALOG_CODE = "MALFORMED_PUBLISHED_CATALOG";
1014
+ var PUBLICATION_STATUSES = new Set(
1015
+ FFID_CATALOG_PUBLICATION_STATUSES
1016
+ );
1017
+ var CATALOG_ENVIRONMENTS = new Set(FFID_CATALOG_ENVIRONMENTS);
1018
+ var BILLING_INTERVALS = /* @__PURE__ */ new Set(["monthly", "yearly"]);
1019
+ var TAX_BEHAVIORS = /* @__PURE__ */ new Set(["inclusive", "exclusive"]);
1020
+ var PUBLICATION_HASH_FIELDS = [
1021
+ "praxisCopyHash",
1022
+ "ffidCheckoutCopyHash",
1023
+ "legalDisclosureHash",
1024
+ "taxConfigurationHash",
1025
+ "paymentConfigurationHash",
1026
+ "scopeHash"
1027
+ ];
1028
+ var PUBLICATION_REQUIRED_TIMESTAMP_FIELDS = ["reviewedAt", "effectiveAt"];
1029
+ var PUBLICATION_NULLABLE_TIMESTAMP_FIELDS = [
1030
+ "supersededAt",
1031
+ "sellThroughUntil",
1032
+ "revocationEffectiveAt"
1033
+ ];
1034
+ function malformed(detail) {
1035
+ return new FFIDSDKError(
1036
+ MALFORMED_CATALOG_CODE,
1037
+ `SDK: server returned malformed PublishedCatalog \u2014 ${detail}`
1038
+ );
1039
+ }
1040
+ function isNullableIsoString(value) {
1041
+ return value === null || typeof value === "string" && value.trim() !== "";
1042
+ }
1043
+ function isNonNegativeIntOrNull(value) {
1044
+ return value === null || typeof value === "number" && Number.isInteger(value) && value >= 0;
1045
+ }
1046
+ function validatePublishedPlan(plan, index) {
1047
+ if (plan === null || typeof plan !== "object") {
1048
+ throw malformed(`plans[${index}] must be an object`);
1049
+ }
1050
+ const record = plan;
1051
+ for (const key of Object.keys(record)) {
1052
+ if (/stripe/i.test(key)) {
1053
+ throw malformed(
1054
+ `plans[${index}] contains a Stripe identifier field (${JSON.stringify(key)}) \u2014 published catalogs must never expose Stripe IDs`
1055
+ );
1056
+ }
1057
+ }
1058
+ if (isBlankString(record.code)) {
1059
+ throw malformed(`plans[${index}].code must be a non-empty string`);
1060
+ }
1061
+ if (isBlankString(record.name)) {
1062
+ throw malformed(`plans[${index}].name must be a non-empty string`);
1063
+ }
1064
+ if (record.description !== null && typeof record.description !== "string") {
1065
+ throw malformed(`plans[${index}].description must be string or null`);
1066
+ }
1067
+ if (isBlankString(record.currency)) {
1068
+ throw malformed(`plans[${index}].currency must be a non-empty string`);
1069
+ }
1070
+ if (!isNonNegativeIntOrNull(record.priceMonthly)) {
1071
+ throw malformed(`plans[${index}].priceMonthly must be a non-negative integer or null`);
1072
+ }
1073
+ if (!isNonNegativeIntOrNull(record.priceYearly)) {
1074
+ throw malformed(`plans[${index}].priceYearly must be a non-negative integer or null`);
1075
+ }
1076
+ if (typeof record.taxBehavior !== "string" || !TAX_BEHAVIORS.has(record.taxBehavior)) {
1077
+ throw malformed(
1078
+ `plans[${index}].taxBehavior must be inclusive | exclusive (got ${JSON.stringify(record.taxBehavior)})`
1079
+ );
1080
+ }
1081
+ const intervals = record.billingIntervals;
1082
+ if (!Array.isArray(intervals) || intervals.length === 0 || intervals.some((interval) => typeof interval !== "string" || !BILLING_INTERVALS.has(interval))) {
1083
+ throw malformed(
1084
+ `plans[${index}].billingIntervals must be a non-empty array of monthly | yearly`
1085
+ );
1086
+ }
1087
+ if (intervals.includes("monthly") && record.priceMonthly === null) {
1088
+ throw malformed(`plans[${index}] offers monthly billing but priceMonthly is null`);
1089
+ }
1090
+ if (intervals.includes("yearly") && record.priceYearly === null) {
1091
+ throw malformed(`plans[${index}] offers yearly billing but priceYearly is null`);
1092
+ }
1093
+ if (typeof record.minSeats !== "number" || !Number.isInteger(record.minSeats) || record.minSeats < 1) {
1094
+ throw malformed(`plans[${index}].minSeats must be a positive integer`);
1095
+ }
1096
+ if (record.maxSeats !== null && (typeof record.maxSeats !== "number" || !Number.isInteger(record.maxSeats) || record.maxSeats < 1)) {
1097
+ throw malformed(`plans[${index}].maxSeats must be a positive integer or null`);
1098
+ }
1099
+ if (record.maxSeats !== null && record.maxSeats < record.minSeats) {
1100
+ throw malformed(`plans[${index}].maxSeats must be >= minSeats`);
1101
+ }
1102
+ if (typeof record.displayOrder !== "number" || !Number.isInteger(record.displayOrder)) {
1103
+ throw malformed(`plans[${index}].displayOrder must be an integer`);
1104
+ }
1105
+ }
1106
+ function validatePublishedCatalog(catalog) {
1107
+ if (catalog === null || typeof catalog !== "object") {
1108
+ throw malformed(
1109
+ `expected object, got ${catalog === null ? "null" : typeof catalog}`
1110
+ );
1111
+ }
1112
+ const c = catalog;
1113
+ const service = c.service;
1114
+ if (service === null || typeof service !== "object") {
1115
+ throw malformed("service must be an object");
1116
+ }
1117
+ const s = service;
1118
+ if (isBlankString(s.code)) {
1119
+ throw malformed("service.code must be a non-empty string");
1120
+ }
1121
+ if (isBlankString(s.name)) {
1122
+ throw malformed("service.name must be a non-empty string");
1123
+ }
1124
+ if (isBlankString(c.catalogVersion)) {
1125
+ throw malformed("catalogVersion must be a non-empty string");
1126
+ }
1127
+ const publication = c.publication;
1128
+ if (publication === null || typeof publication !== "object") {
1129
+ throw malformed("publication must be an object");
1130
+ }
1131
+ const p = publication;
1132
+ if (isBlankString(p.serviceCode)) {
1133
+ throw malformed("publication.serviceCode must be a non-empty string");
1134
+ }
1135
+ if (typeof p.environment !== "string" || !CATALOG_ENVIRONMENTS.has(p.environment)) {
1136
+ throw malformed(
1137
+ `publication.environment must be staging | production (got ${JSON.stringify(p.environment)})`
1138
+ );
1139
+ }
1140
+ if (p.catalogVersion !== c.catalogVersion) {
1141
+ throw malformed(
1142
+ `publication.catalogVersion (${JSON.stringify(p.catalogVersion)}) does not match top-level catalogVersion (${JSON.stringify(c.catalogVersion)})`
1143
+ );
1144
+ }
1145
+ if (typeof p.status !== "string" || !PUBLICATION_STATUSES.has(p.status)) {
1146
+ throw malformed(
1147
+ `publication.status must be one of approved | superseded | revoking | revoked (got ${JSON.stringify(p.status)})`
1148
+ );
1149
+ }
1150
+ for (const field of PUBLICATION_HASH_FIELDS) {
1151
+ const value = p[field];
1152
+ if (typeof value !== "string" || !CATALOG_HASH_PATTERN.test(value)) {
1153
+ throw malformed(`publication.${field} must be lowercase SHA-256 hex (64 chars)`);
1154
+ }
1155
+ }
1156
+ if (typeof p.salesEpoch !== "number" || !Number.isInteger(p.salesEpoch) || p.salesEpoch < 1) {
1157
+ throw malformed(
1158
+ `publication.salesEpoch must be a positive integer (got ${JSON.stringify(p.salesEpoch)})`
1159
+ );
1160
+ }
1161
+ if (isBlankString(p.approvalId)) {
1162
+ throw malformed("publication.approvalId must be a non-empty string");
1163
+ }
1164
+ for (const field of PUBLICATION_REQUIRED_TIMESTAMP_FIELDS) {
1165
+ if (isBlankString(p[field])) {
1166
+ throw malformed(`publication.${field} must be a non-empty ISO 8601 string`);
1167
+ }
1168
+ }
1169
+ for (const field of PUBLICATION_NULLABLE_TIMESTAMP_FIELDS) {
1170
+ if (!isNullableIsoString(p[field])) {
1171
+ throw malformed(`publication.${field} must be a non-empty ISO 8601 string or null`);
1172
+ }
1173
+ }
1174
+ if (!Array.isArray(c.plans)) {
1175
+ throw malformed(`plans must be an array (got ${typeof c.plans})`);
1176
+ }
1177
+ for (const [index, plan] of c.plans.entries()) {
1178
+ validatePublishedPlan(plan, index);
1179
+ }
1180
+ }
1181
+ function createCatalogMethods(deps) {
1182
+ const { fetchWithAuth, createError } = deps;
1183
+ async function getPublishedCatalog(serviceCode) {
1184
+ if (isBlankString(serviceCode)) {
1185
+ return {
1186
+ error: createError("VALIDATION_ERROR", "serviceCode \u306F\u5FC5\u9808\u3067\u3059")
1187
+ };
1188
+ }
1189
+ const requestedCode = serviceCode.trim();
1190
+ const response = await fetchWithAuth(
1191
+ `${EXT_CATALOG_ENDPOINT}/${encodeURIComponent(requestedCode)}`
1192
+ );
1193
+ if (response.data !== void 0) {
1194
+ validatePublishedCatalog(response.data);
1195
+ if (response.data.service.code !== requestedCode || response.data.publication.serviceCode !== requestedCode) {
1196
+ throw malformed(
1197
+ `response is for service ${JSON.stringify(response.data.service.code)} (publication: ${JSON.stringify(response.data.publication.serviceCode)}) but ${JSON.stringify(requestedCode)} was requested`
1198
+ );
1199
+ }
1200
+ }
1201
+ return response;
1202
+ }
1203
+ return { getPublishedCatalog };
1204
+ }
1205
+
817
1206
  // src/client/members-methods.ts
818
1207
  var EXT_MEMBERS_ENDPOINT = "/api/v1/organizations/ext/members";
819
1208
  function createMembersMethods(deps) {
@@ -1256,7 +1645,7 @@ function validateTokenResponse(tokenResponse) {
1256
1645
  }
1257
1646
 
1258
1647
  // src/client/version-check.ts
1259
- var SDK_VERSION = "5.24.3";
1648
+ var SDK_VERSION = "5.27.0";
1260
1649
  var SDK_USER_AGENT = `FFID-SDK/${SDK_VERSION} (TypeScript)`;
1261
1650
  var SDK_VERSION_HEADER = "X-FFID-SDK-Version";
1262
1651
  function sdkHeaders() {
@@ -2870,39 +3259,7 @@ function isBlockedFromUserinfo(sub) {
2870
3259
  return BLOCKING_EFFECTIVE_STATUSES.includes(effectiveStatus);
2871
3260
  }
2872
3261
 
2873
- // src/client/error-codes.ts
2874
- var FFID_ERROR_CODES = {
2875
- NETWORK_ERROR: "NETWORK_ERROR",
2876
- PARSE_ERROR: "PARSE_ERROR",
2877
- UNKNOWN_ERROR: "UNKNOWN_ERROR",
2878
- TOKEN_EXCHANGE_ERROR: "TOKEN_EXCHANGE_ERROR",
2879
- TOKEN_REFRESH_ERROR: "TOKEN_REFRESH_ERROR",
2880
- NO_TOKENS: "NO_TOKENS",
2881
- TOKEN_VERIFICATION_ERROR: "TOKEN_VERIFICATION_ERROR",
2882
- TOKEN_EXPIRED: "TOKEN_EXPIRED",
2883
- STATE_MISMATCH_ERROR: "STATE_MISMATCH_ERROR"
2884
- };
2885
-
2886
- // src/client/ffid-client.ts
2887
- var UNAUTHORIZED_STATUS2 = 401;
2888
- var SDK_LOG_PREFIX = "[FFID SDK]";
2889
- var noopLogger = {
2890
- debug: () => {
2891
- },
2892
- info: () => {
2893
- },
2894
- warn: () => {
2895
- },
2896
- error: () => {
2897
- }
2898
- };
2899
- var consoleLogger = {
2900
- debug: (...args) => console.debug(SDK_LOG_PREFIX, ...args),
2901
- info: (...args) => console.info(SDK_LOG_PREFIX, ...args),
2902
- warn: (...args) => console.warn(SDK_LOG_PREFIX, ...args),
2903
- error: (...args) => console.error(SDK_LOG_PREFIX, ...args)
2904
- };
2905
- var EXT_CHECK_ENDPOINT = "/api/v1/subscriptions/ext/check";
3262
+ // src/client/service-access-decision.ts
2906
3263
  var DEFAULT_ALLOW_GRACE = true;
2907
3264
  var DEFAULT_SERVICE_ACCESS_FAIL_POLICY = "failClosed";
2908
3265
  var SERVICE_ACCESS_DENIED_CODE = "SERVICE_ACCESS_DENIED";
@@ -2969,6 +3326,40 @@ function failClosedServiceAccessDecision(params, error) {
2969
3326
  error
2970
3327
  };
2971
3328
  }
3329
+
3330
+ // src/client/error-codes.ts
3331
+ var FFID_ERROR_CODES = {
3332
+ NETWORK_ERROR: "NETWORK_ERROR",
3333
+ PARSE_ERROR: "PARSE_ERROR",
3334
+ UNKNOWN_ERROR: "UNKNOWN_ERROR",
3335
+ TOKEN_EXCHANGE_ERROR: "TOKEN_EXCHANGE_ERROR",
3336
+ TOKEN_REFRESH_ERROR: "TOKEN_REFRESH_ERROR",
3337
+ NO_TOKENS: "NO_TOKENS",
3338
+ TOKEN_VERIFICATION_ERROR: "TOKEN_VERIFICATION_ERROR",
3339
+ TOKEN_EXPIRED: "TOKEN_EXPIRED",
3340
+ STATE_MISMATCH_ERROR: "STATE_MISMATCH_ERROR"
3341
+ };
3342
+
3343
+ // src/client/ffid-client.ts
3344
+ var UNAUTHORIZED_STATUS2 = 401;
3345
+ var SDK_LOG_PREFIX = "[FFID SDK]";
3346
+ var noopLogger = {
3347
+ debug: () => {
3348
+ },
3349
+ info: () => {
3350
+ },
3351
+ warn: () => {
3352
+ },
3353
+ error: () => {
3354
+ }
3355
+ };
3356
+ var consoleLogger = {
3357
+ debug: (...args) => console.debug(SDK_LOG_PREFIX, ...args),
3358
+ info: (...args) => console.info(SDK_LOG_PREFIX, ...args),
3359
+ warn: (...args) => console.warn(SDK_LOG_PREFIX, ...args),
3360
+ error: (...args) => console.error(SDK_LOG_PREFIX, ...args)
3361
+ };
3362
+ var EXT_CHECK_ENDPOINT = "/api/v1/subscriptions/ext/check";
2972
3363
  function resolveRedirectUri(raw, logger) {
2973
3364
  if (raw === null) return null;
2974
3365
  try {
@@ -3286,6 +3677,10 @@ function createFFIDClient(config) {
3286
3677
  fetchWithAuth,
3287
3678
  createError
3288
3679
  });
3680
+ const { getPublishedCatalog } = createCatalogMethods({
3681
+ fetchWithAuth,
3682
+ createError
3683
+ });
3289
3684
  const { listMembers, addMember, updateMemberRole, removeMember } = createMembersMethods({
3290
3685
  fetchWithAuth,
3291
3686
  createError,
@@ -3412,6 +3807,7 @@ function createFFIDClient(config) {
3412
3807
  assignSeats,
3413
3808
  unassignSeat,
3414
3809
  listPlans,
3810
+ getPublishedCatalog,
3415
3811
  getSubscription,
3416
3812
  subscribe,
3417
3813
  changePlan,
@@ -5562,11 +5958,13 @@ function FFIDInquiryForm({
5562
5958
 
5563
5959
  exports.ACCESS_GRANTING_EFFECTIVE_STATUSES = ACCESS_GRANTING_EFFECTIVE_STATUSES;
5564
5960
  exports.BLOCKING_EFFECTIVE_STATUSES = BLOCKING_EFFECTIVE_STATUSES;
5961
+ exports.CATALOG_HASH_PATTERN = CATALOG_HASH_PATTERN;
5565
5962
  exports.DEFAULT_API_BASE_URL = DEFAULT_API_BASE_URL;
5566
5963
  exports.DEFAULT_OAUTH_SCOPES = DEFAULT_OAUTH_SCOPES;
5567
5964
  exports.EFFECTIVE_SUBSCRIPTION_STATUSES = EFFECTIVE_SUBSCRIPTION_STATUSES;
5568
5965
  exports.FFIDAnnouncementBadge = FFIDAnnouncementBadge;
5569
5966
  exports.FFIDAnnouncementList = FFIDAnnouncementList;
5967
+ exports.FFIDCatalogHashError = FFIDCatalogHashError;
5570
5968
  exports.FFIDInquiryForm = FFIDInquiryForm;
5571
5969
  exports.FFIDLoginButton = FFIDLoginButton;
5572
5970
  exports.FFIDOrganizationSwitcher = FFIDOrganizationSwitcher;
@@ -5575,11 +5973,20 @@ exports.FFIDSDKError = FFIDSDKError;
5575
5973
  exports.FFIDSubscriptionBadge = FFIDSubscriptionBadge;
5576
5974
  exports.FFIDUserMenu = FFIDUserMenu;
5577
5975
  exports.FFID_ANNOUNCEMENTS_ERROR_CODES = FFID_ANNOUNCEMENTS_ERROR_CODES;
5976
+ exports.FFID_CATALOG_ENVIRONMENTS = FFID_CATALOG_ENVIRONMENTS;
5977
+ exports.FFID_CATALOG_PUBLICATION_STATUSES = FFID_CATALOG_PUBLICATION_STATUSES;
5578
5978
  exports.FFID_ERROR_CODES = FFID_ERROR_CODES;
5579
5979
  exports.FFID_INQUIRY_CATEGORIES = FFID_INQUIRY_CATEGORIES;
5580
5980
  exports.FFID_INQUIRY_CATEGORIES_SITE_2026 = FFID_INQUIRY_CATEGORIES_SITE_2026;
5981
+ exports.canonicalizeCatalogValue = canonicalizeCatalogValue;
5581
5982
  exports.cleanupStateStorage = cleanupStateStorage;
5582
5983
  exports.computeEffectiveStatusFromSession = computeEffectiveStatusFromSession;
5984
+ exports.computeFFIDCheckoutCopyHash = computeFFIDCheckoutCopyHash;
5985
+ exports.computeLegalDisclosureHash = computeLegalDisclosureHash;
5986
+ exports.computePaymentConfigurationHash = computePaymentConfigurationHash;
5987
+ exports.computePraxisCopyHash = computePraxisCopyHash;
5988
+ exports.computeScopeHash = computeScopeHash;
5989
+ exports.computeTaxConfigurationHash = computeTaxConfigurationHash;
5583
5990
  exports.createFFIDAnnouncementsClient = createFFIDAnnouncementsClient;
5584
5991
  exports.createFFIDClient = createFFIDClient;
5585
5992
  exports.createTokenStore = createTokenStore;
@@ -5587,15 +5994,18 @@ exports.generateCodeChallenge = generateCodeChallenge;
5587
5994
  exports.generateCodeVerifier = generateCodeVerifier;
5588
5995
  exports.handleRedirectCallback = handleRedirectCallback;
5589
5996
  exports.hasAccessFromUserinfo = hasAccessFromUserinfo;
5997
+ exports.hashCanonicalJson = hashCanonicalJson;
5590
5998
  exports.isBlockedFromUserinfo = isBlockedFromUserinfo;
5591
5999
  exports.isFFIDInquiryCategorySite2026 = isFFIDInquiryCategorySite2026;
5592
6000
  exports.normalizeRedirectUri = normalizeRedirectUri;
5593
6001
  exports.retrieveCodeVerifier = retrieveCodeVerifier;
5594
6002
  exports.retrieveState = retrieveState;
6003
+ exports.sha256Hex = sha256Hex;
5595
6004
  exports.storeCodeVerifier = storeCodeVerifier;
5596
6005
  exports.storeState = storeState;
5597
6006
  exports.useFFID = useFFID;
5598
6007
  exports.useFFIDAnnouncements = useFFIDAnnouncements;
5599
6008
  exports.useFFIDContext = useFFIDContext;
5600
6009
  exports.useSubscription = useSubscription;
6010
+ exports.validatePublishedCatalog = validatePublishedCatalog;
5601
6011
  exports.withSubscription = withSubscription;
@@ -1,34 +1,34 @@
1
1
  'use strict';
2
2
 
3
- var chunkAMVL7J2G_cjs = require('../chunk-AMVL7J2G.cjs');
3
+ var chunkR4KUHR4S_cjs = require('../chunk-R4KUHR4S.cjs');
4
4
 
5
5
 
6
6
 
7
7
  Object.defineProperty(exports, "FFIDAnnouncementBadge", {
8
8
  enumerable: true,
9
- get: function () { return chunkAMVL7J2G_cjs.FFIDAnnouncementBadge; }
9
+ get: function () { return chunkR4KUHR4S_cjs.FFIDAnnouncementBadge; }
10
10
  });
11
11
  Object.defineProperty(exports, "FFIDAnnouncementList", {
12
12
  enumerable: true,
13
- get: function () { return chunkAMVL7J2G_cjs.FFIDAnnouncementList; }
13
+ get: function () { return chunkR4KUHR4S_cjs.FFIDAnnouncementList; }
14
14
  });
15
15
  Object.defineProperty(exports, "FFIDInquiryForm", {
16
16
  enumerable: true,
17
- get: function () { return chunkAMVL7J2G_cjs.FFIDInquiryForm; }
17
+ get: function () { return chunkR4KUHR4S_cjs.FFIDInquiryForm; }
18
18
  });
19
19
  Object.defineProperty(exports, "FFIDLoginButton", {
20
20
  enumerable: true,
21
- get: function () { return chunkAMVL7J2G_cjs.FFIDLoginButton; }
21
+ get: function () { return chunkR4KUHR4S_cjs.FFIDLoginButton; }
22
22
  });
23
23
  Object.defineProperty(exports, "FFIDOrganizationSwitcher", {
24
24
  enumerable: true,
25
- get: function () { return chunkAMVL7J2G_cjs.FFIDOrganizationSwitcher; }
25
+ get: function () { return chunkR4KUHR4S_cjs.FFIDOrganizationSwitcher; }
26
26
  });
27
27
  Object.defineProperty(exports, "FFIDSubscriptionBadge", {
28
28
  enumerable: true,
29
- get: function () { return chunkAMVL7J2G_cjs.FFIDSubscriptionBadge; }
29
+ get: function () { return chunkR4KUHR4S_cjs.FFIDSubscriptionBadge; }
30
30
  });
31
31
  Object.defineProperty(exports, "FFIDUserMenu", {
32
32
  enumerable: true,
33
- get: function () { return chunkAMVL7J2G_cjs.FFIDUserMenu; }
33
+ get: function () { return chunkR4KUHR4S_cjs.FFIDUserMenu; }
34
34
  });
@@ -1,3 +1,3 @@
1
- export { H as FFIDAnnouncementBadge, al as FFIDAnnouncementBadgeClassNames, am as FFIDAnnouncementBadgeProps, I as FFIDAnnouncementList, an as FFIDAnnouncementListClassNames, ao as FFIDAnnouncementListProps, S as FFIDInquiryForm, U as FFIDInquiryFormCategoryItem, V as FFIDInquiryFormClassNames, W as FFIDInquiryFormLegalLayout, X as FFIDInquiryFormOrganization, Y as FFIDInquiryFormPlaceholderContext, Z as FFIDInquiryFormPrefill, _ as FFIDInquiryFormProps, $ as FFIDInquiryFormSubmitData, a0 as FFIDInquiryFormSubmitResult, a2 as FFIDLoginButton, ap as FFIDLoginButtonProps, a5 as FFIDOrganizationSwitcher, aq as FFIDOrganizationSwitcherClassNames, ar as FFIDOrganizationSwitcherProps, aa as FFIDSubscriptionBadge, as as FFIDSubscriptionBadgeClassNames, at as FFIDSubscriptionBadgeProps, ac as FFIDUserMenu, au as FFIDUserMenuClassNames, av as FFIDUserMenuProps } from '../index-BRMn6xT0.cjs';
1
+ export { G as FFIDAnnouncementBadge, aj as FFIDAnnouncementBadgeClassNames, ak as FFIDAnnouncementBadgeProps, H as FFIDAnnouncementList, al as FFIDAnnouncementListClassNames, am as FFIDAnnouncementListProps, R as FFIDInquiryForm, S as FFIDInquiryFormCategoryItem, U as FFIDInquiryFormClassNames, V as FFIDInquiryFormLegalLayout, W as FFIDInquiryFormOrganization, X as FFIDInquiryFormPlaceholderContext, Y as FFIDInquiryFormPrefill, Z as FFIDInquiryFormProps, _ as FFIDInquiryFormSubmitData, $ as FFIDInquiryFormSubmitResult, a1 as FFIDLoginButton, an as FFIDLoginButtonProps, a3 as FFIDOrganizationSwitcher, ao as FFIDOrganizationSwitcherClassNames, ap as FFIDOrganizationSwitcherProps, a9 as FFIDSubscriptionBadge, aq as FFIDSubscriptionBadgeClassNames, ar as FFIDSubscriptionBadgeProps, aa as FFIDUserMenu, as as FFIDUserMenuClassNames, at as FFIDUserMenuProps } from '../index-D3S_Pkkv.cjs';
2
2
  import 'react/jsx-runtime';
3
3
  import 'react';
@@ -1,3 +1,3 @@
1
- export { H as FFIDAnnouncementBadge, al as FFIDAnnouncementBadgeClassNames, am as FFIDAnnouncementBadgeProps, I as FFIDAnnouncementList, an as FFIDAnnouncementListClassNames, ao as FFIDAnnouncementListProps, S as FFIDInquiryForm, U as FFIDInquiryFormCategoryItem, V as FFIDInquiryFormClassNames, W as FFIDInquiryFormLegalLayout, X as FFIDInquiryFormOrganization, Y as FFIDInquiryFormPlaceholderContext, Z as FFIDInquiryFormPrefill, _ as FFIDInquiryFormProps, $ as FFIDInquiryFormSubmitData, a0 as FFIDInquiryFormSubmitResult, a2 as FFIDLoginButton, ap as FFIDLoginButtonProps, a5 as FFIDOrganizationSwitcher, aq as FFIDOrganizationSwitcherClassNames, ar as FFIDOrganizationSwitcherProps, aa as FFIDSubscriptionBadge, as as FFIDSubscriptionBadgeClassNames, at as FFIDSubscriptionBadgeProps, ac as FFIDUserMenu, au as FFIDUserMenuClassNames, av as FFIDUserMenuProps } from '../index-BRMn6xT0.js';
1
+ export { G as FFIDAnnouncementBadge, aj as FFIDAnnouncementBadgeClassNames, ak as FFIDAnnouncementBadgeProps, H as FFIDAnnouncementList, al as FFIDAnnouncementListClassNames, am as FFIDAnnouncementListProps, R as FFIDInquiryForm, S as FFIDInquiryFormCategoryItem, U as FFIDInquiryFormClassNames, V as FFIDInquiryFormLegalLayout, W as FFIDInquiryFormOrganization, X as FFIDInquiryFormPlaceholderContext, Y as FFIDInquiryFormPrefill, Z as FFIDInquiryFormProps, _ as FFIDInquiryFormSubmitData, $ as FFIDInquiryFormSubmitResult, a1 as FFIDLoginButton, an as FFIDLoginButtonProps, a3 as FFIDOrganizationSwitcher, ao as FFIDOrganizationSwitcherClassNames, ap as FFIDOrganizationSwitcherProps, a9 as FFIDSubscriptionBadge, aq as FFIDSubscriptionBadgeClassNames, ar as FFIDSubscriptionBadgeProps, aa as FFIDUserMenu, as as FFIDUserMenuClassNames, at as FFIDUserMenuProps } from '../index-D3S_Pkkv.js';
2
2
  import 'react/jsx-runtime';
3
3
  import 'react';
@@ -1 +1 @@
1
- export { FFIDAnnouncementBadge, FFIDAnnouncementList, FFIDInquiryForm, FFIDLoginButton, FFIDOrganizationSwitcher, FFIDSubscriptionBadge, FFIDUserMenu } from '../chunk-O3HGRALD.js';
1
+ export { FFIDAnnouncementBadge, FFIDAnnouncementList, FFIDInquiryForm, FFIDLoginButton, FFIDOrganizationSwitcher, FFIDSubscriptionBadge, FFIDUserMenu } from '../chunk-JZB2IM7Y.js';