@feelflow/ffid-sdk 5.25.0 → 5.28.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.
- package/dist/{chunk-HIKX4AVY.js → chunk-75UNNOLV.js} +438 -35
- package/dist/{chunk-ZFKXOTFF.cjs → chunk-S5FV6H2U.cjs} +453 -34
- package/dist/components/index.cjs +8 -8
- package/dist/components/index.d.cts +1 -1
- package/dist/components/index.d.ts +1 -1
- package/dist/components/index.js +1 -1
- package/dist/{ffid-client-BZERrMAF.d.cts → ffid-client-B_VBvIt4.d.cts} +141 -1
- package/dist/{ffid-client-CFZPX5ej.d.ts → ffid-client-DEM1nYqe.d.ts} +141 -1
- package/dist/{index-Dd6CMIBd.d.cts → index-_-MqJCN3.d.cts} +68 -2
- package/dist/{index-Dd6CMIBd.d.ts → index-_-MqJCN3.d.ts} +68 -2
- package/dist/index.cjs +106 -42
- package/dist/index.d.cts +287 -3
- package/dist/index.d.ts +287 -3
- package/dist/index.js +2 -2
- package/dist/server/index.cjs +436 -36
- package/dist/server/index.d.cts +180 -3
- package/dist/server/index.d.ts +180 -3
- package/dist/server/index.js +423 -37
- package/dist/server/test/index.d.cts +1 -1
- package/dist/server/test/index.d.ts +1 -1
- package/package.json +1 -1
package/dist/server/index.cjs
CHANGED
|
@@ -119,6 +119,16 @@ function normalizeEntitlements(raw) {
|
|
|
119
119
|
return trimmed.length > 0 ? [trimmed] : [];
|
|
120
120
|
});
|
|
121
121
|
}
|
|
122
|
+
function normalizeSeatAssignmentClaim(raw, organizationId, subscriptionId) {
|
|
123
|
+
if (!raw || typeof raw !== "object") return null;
|
|
124
|
+
const { organization_id, subscription_id, status } = raw;
|
|
125
|
+
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;
|
|
126
|
+
return {
|
|
127
|
+
organizationId: organization_id,
|
|
128
|
+
subscriptionId: subscription_id,
|
|
129
|
+
status
|
|
130
|
+
};
|
|
131
|
+
}
|
|
122
132
|
function normalizeUserinfo(raw) {
|
|
123
133
|
const hasOrganizationId = hasOwnKey(raw, "organization_id");
|
|
124
134
|
const hasOrganizationName = hasOwnKey(raw, "organization_name");
|
|
@@ -152,6 +162,11 @@ function normalizeUserinfo(raw) {
|
|
|
152
162
|
organizationId: raw.subscription.organization_id ?? null,
|
|
153
163
|
organizationName: hasOwnKey(raw.subscription, "organization_name") ? raw.subscription.organization_name ?? null : null,
|
|
154
164
|
hasSeatAssignment: raw.subscription.has_seat_assignment ?? false,
|
|
165
|
+
seatAssignment: hasOwnKey(raw.subscription, "seat_assignment") ? normalizeSeatAssignmentClaim(
|
|
166
|
+
raw.subscription.seat_assignment,
|
|
167
|
+
raw.subscription.organization_id ?? null,
|
|
168
|
+
raw.subscription.subscription_id ?? null
|
|
169
|
+
) : void 0,
|
|
155
170
|
// #3464: legacy FFID (< this issue) omits the field on the wire.
|
|
156
171
|
// The "unknown" axis is unused — no consumer differentiates "server
|
|
157
172
|
// didn't tell us" from "definitely not scheduled" because the
|
|
@@ -450,6 +465,7 @@ function createVerifyAccessToken(deps) {
|
|
|
450
465
|
organization_id: introspectResponse.subscription.organization_id,
|
|
451
466
|
organization_name: introspectResponse.subscription.organization_name ?? null,
|
|
452
467
|
has_seat_assignment: introspectResponse.subscription.has_seat_assignment ?? false,
|
|
468
|
+
...Object.prototype.hasOwnProperty.call(introspectResponse.subscription, "seat_assignment") ? { seat_assignment: introspectResponse.subscription.seat_assignment } : {},
|
|
453
469
|
// #4333: forward pack entitlement codes through the introspect →
|
|
454
470
|
// userinfo normalization boundary. normalizeUserinfo guards the
|
|
455
471
|
// value, so an older FFID that omits it degrades to `[]`.
|
|
@@ -824,6 +840,364 @@ function createSubscriptionMethods(deps) {
|
|
|
824
840
|
};
|
|
825
841
|
}
|
|
826
842
|
|
|
843
|
+
// src/types/published-catalog.ts
|
|
844
|
+
var FFID_CATALOG_ENVIRONMENTS = ["staging", "production"];
|
|
845
|
+
var FFID_CATALOG_PUBLICATION_STATUSES = [
|
|
846
|
+
"approved",
|
|
847
|
+
"superseded",
|
|
848
|
+
"revoking",
|
|
849
|
+
"revoked"
|
|
850
|
+
];
|
|
851
|
+
|
|
852
|
+
// src/shared/catalog-hash.ts
|
|
853
|
+
var CATALOG_HASH_PATTERN = /^[0-9a-f]{64}$/;
|
|
854
|
+
var HEX_RADIX = 16;
|
|
855
|
+
var HEX_DIGITS_PER_BYTE = 2;
|
|
856
|
+
var FFIDCatalogHashError = class extends Error {
|
|
857
|
+
constructor(message) {
|
|
858
|
+
super(message);
|
|
859
|
+
this.name = "FFIDCatalogHashError";
|
|
860
|
+
}
|
|
861
|
+
};
|
|
862
|
+
function isPlainObject(value) {
|
|
863
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
864
|
+
return false;
|
|
865
|
+
}
|
|
866
|
+
const proto = Object.getPrototypeOf(value);
|
|
867
|
+
return proto === Object.prototype || proto === null;
|
|
868
|
+
}
|
|
869
|
+
function canonicalize(value, path) {
|
|
870
|
+
if (value === null) return "null";
|
|
871
|
+
switch (typeof value) {
|
|
872
|
+
case "string":
|
|
873
|
+
return JSON.stringify(value);
|
|
874
|
+
case "boolean":
|
|
875
|
+
return value ? "true" : "false";
|
|
876
|
+
case "number":
|
|
877
|
+
if (!Number.isFinite(value)) {
|
|
878
|
+
throw new FFIDCatalogHashError(
|
|
879
|
+
`canonicalize: non-finite number at ${path} \u2014 hashes require finite numbers`
|
|
880
|
+
);
|
|
881
|
+
}
|
|
882
|
+
return JSON.stringify(value);
|
|
883
|
+
case "undefined":
|
|
884
|
+
throw new FFIDCatalogHashError(
|
|
885
|
+
`canonicalize: undefined at ${path} \u2014 omit the key entirely or use null`
|
|
886
|
+
);
|
|
887
|
+
}
|
|
888
|
+
if (Array.isArray(value)) {
|
|
889
|
+
const items = value.map((item, index) => canonicalize(item, `${path}[${index}]`));
|
|
890
|
+
return `[${items.join(",")}]`;
|
|
891
|
+
}
|
|
892
|
+
if (isPlainObject(value)) {
|
|
893
|
+
const keys = Object.keys(value).sort();
|
|
894
|
+
const entries = [];
|
|
895
|
+
for (const key of keys) {
|
|
896
|
+
const child = value[key];
|
|
897
|
+
if (child === void 0) {
|
|
898
|
+
throw new FFIDCatalogHashError(
|
|
899
|
+
`canonicalize: undefined at ${path}.${key} \u2014 omit the key entirely or use null`
|
|
900
|
+
);
|
|
901
|
+
}
|
|
902
|
+
entries.push(`${JSON.stringify(key)}:${canonicalize(child, `${path}.${key}`)}`);
|
|
903
|
+
}
|
|
904
|
+
return `{${entries.join(",")}}`;
|
|
905
|
+
}
|
|
906
|
+
throw new FFIDCatalogHashError(
|
|
907
|
+
`canonicalize: unsupported value type (${typeof value}${typeof value === "object" ? `: ${Object.prototype.toString.call(value)}` : ""}) at ${path} \u2014 only JSON-plain values are hashable`
|
|
908
|
+
);
|
|
909
|
+
}
|
|
910
|
+
function canonicalizeCatalogValue(value) {
|
|
911
|
+
return canonicalize(value, "$");
|
|
912
|
+
}
|
|
913
|
+
async function sha256Hex(text) {
|
|
914
|
+
const subtle = globalThis.crypto?.subtle;
|
|
915
|
+
if (!subtle) {
|
|
916
|
+
throw new FFIDCatalogHashError(
|
|
917
|
+
"sha256Hex: Web Crypto (crypto.subtle) is not available in this runtime"
|
|
918
|
+
);
|
|
919
|
+
}
|
|
920
|
+
const digest = await subtle.digest("SHA-256", new TextEncoder().encode(text));
|
|
921
|
+
return Array.from(new Uint8Array(digest)).map((byte) => byte.toString(HEX_RADIX).padStart(HEX_DIGITS_PER_BYTE, "0")).join("");
|
|
922
|
+
}
|
|
923
|
+
async function hashCanonicalJson(value) {
|
|
924
|
+
return sha256Hex(canonicalizeCatalogValue(value));
|
|
925
|
+
}
|
|
926
|
+
function assertHashFormat(hash, field) {
|
|
927
|
+
if (!CATALOG_HASH_PATTERN.test(hash)) {
|
|
928
|
+
throw new FFIDCatalogHashError(
|
|
929
|
+
`${field} must be lowercase SHA-256 hex (64 chars), got: ${JSON.stringify(hash)}`
|
|
930
|
+
);
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
function normalizeStringSet(values) {
|
|
934
|
+
return [...new Set(values)].sort();
|
|
935
|
+
}
|
|
936
|
+
function compareByCodeUnits(a, b) {
|
|
937
|
+
if (a.code < b.code) return -1;
|
|
938
|
+
if (a.code > b.code) return 1;
|
|
939
|
+
return 0;
|
|
940
|
+
}
|
|
941
|
+
async function computePraxisCopyHash(presentation) {
|
|
942
|
+
return hashCanonicalJson(presentation);
|
|
943
|
+
}
|
|
944
|
+
async function computeFFIDCheckoutCopyHash(checkoutCopy) {
|
|
945
|
+
return hashCanonicalJson(checkoutCopy);
|
|
946
|
+
}
|
|
947
|
+
async function computeLegalDisclosureHash(canonicalText) {
|
|
948
|
+
return sha256Hex(canonicalText.replace(/\r\n?/g, "\n"));
|
|
949
|
+
}
|
|
950
|
+
async function computeTaxConfigurationHash(config) {
|
|
951
|
+
return hashCanonicalJson({
|
|
952
|
+
allowedCountries: normalizeStringSet(config.allowedCountries),
|
|
953
|
+
stripeTaxRegistrations: normalizeStringSet(config.stripeTaxRegistrations),
|
|
954
|
+
billingAddressRequired: config.billingAddressRequired,
|
|
955
|
+
planTaxCodes: config.planTaxCodes,
|
|
956
|
+
planTaxBehaviors: config.planTaxBehaviors
|
|
957
|
+
});
|
|
958
|
+
}
|
|
959
|
+
async function computePaymentConfigurationHash(config) {
|
|
960
|
+
return hashCanonicalJson({
|
|
961
|
+
environment: config.environment,
|
|
962
|
+
currency: config.currency,
|
|
963
|
+
paymentMethodTypes: normalizeStringSet(config.paymentMethodTypes),
|
|
964
|
+
dynamicPaymentMethodsEnabled: config.dynamicPaymentMethodsEnabled,
|
|
965
|
+
manualInvoiceAllowed: config.manualInvoiceAllowed,
|
|
966
|
+
customerBalanceAllowed: config.customerBalanceAllowed,
|
|
967
|
+
delayedPaymentMethodsAllowed: config.delayedPaymentMethodsAllowed,
|
|
968
|
+
customerInvoiceBalanceRequired: config.customerInvoiceBalanceRequired
|
|
969
|
+
});
|
|
970
|
+
}
|
|
971
|
+
async function computeScopeHash(input) {
|
|
972
|
+
assertHashFormat(input.praxisCopyHash, "praxisCopyHash");
|
|
973
|
+
assertHashFormat(input.ffidCheckoutCopyHash, "ffidCheckoutCopyHash");
|
|
974
|
+
assertHashFormat(input.legalDisclosureHash, "legalDisclosureHash");
|
|
975
|
+
assertHashFormat(input.taxConfigurationHash, "taxConfigurationHash");
|
|
976
|
+
assertHashFormat(input.paymentConfigurationHash, "paymentConfigurationHash");
|
|
977
|
+
const seenCodes = /* @__PURE__ */ new Set();
|
|
978
|
+
for (const plan of input.plans) {
|
|
979
|
+
if (seenCodes.has(plan.code)) {
|
|
980
|
+
throw new FFIDCatalogHashError(
|
|
981
|
+
`computeScopeHash: duplicate plan code ${JSON.stringify(plan.code)}`
|
|
982
|
+
);
|
|
983
|
+
}
|
|
984
|
+
seenCodes.add(plan.code);
|
|
985
|
+
}
|
|
986
|
+
const plans = [...input.plans].sort(compareByCodeUnits).map((plan) => ({
|
|
987
|
+
code: plan.code,
|
|
988
|
+
priceMonthly: plan.priceMonthly,
|
|
989
|
+
priceYearly: plan.priceYearly,
|
|
990
|
+
currency: plan.currency,
|
|
991
|
+
// Set semantics — interval order must not flip the digest.
|
|
992
|
+
billingIntervals: normalizeStringSet(plan.billingIntervals),
|
|
993
|
+
taxBehavior: plan.taxBehavior,
|
|
994
|
+
minSeats: plan.minSeats
|
|
995
|
+
}));
|
|
996
|
+
return hashCanonicalJson({
|
|
997
|
+
plans,
|
|
998
|
+
praxisCopyHash: input.praxisCopyHash,
|
|
999
|
+
ffidCheckoutCopyHash: input.ffidCheckoutCopyHash,
|
|
1000
|
+
legalDisclosureHash: input.legalDisclosureHash,
|
|
1001
|
+
taxConfigurationHash: input.taxConfigurationHash,
|
|
1002
|
+
paymentConfigurationHash: input.paymentConfigurationHash
|
|
1003
|
+
});
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
// src/client/catalog-methods.ts
|
|
1007
|
+
var EXT_CATALOG_ENDPOINT = "/api/v1/subscriptions/ext/catalog";
|
|
1008
|
+
var MALFORMED_CATALOG_CODE = "MALFORMED_PUBLISHED_CATALOG";
|
|
1009
|
+
var PUBLICATION_STATUSES = new Set(
|
|
1010
|
+
FFID_CATALOG_PUBLICATION_STATUSES
|
|
1011
|
+
);
|
|
1012
|
+
var CATALOG_ENVIRONMENTS = new Set(FFID_CATALOG_ENVIRONMENTS);
|
|
1013
|
+
var BILLING_INTERVALS = /* @__PURE__ */ new Set(["monthly", "yearly"]);
|
|
1014
|
+
var TAX_BEHAVIORS = /* @__PURE__ */ new Set(["inclusive", "exclusive"]);
|
|
1015
|
+
var PUBLICATION_HASH_FIELDS = [
|
|
1016
|
+
"praxisCopyHash",
|
|
1017
|
+
"ffidCheckoutCopyHash",
|
|
1018
|
+
"legalDisclosureHash",
|
|
1019
|
+
"taxConfigurationHash",
|
|
1020
|
+
"paymentConfigurationHash",
|
|
1021
|
+
"scopeHash"
|
|
1022
|
+
];
|
|
1023
|
+
var PUBLICATION_REQUIRED_TIMESTAMP_FIELDS = ["reviewedAt", "effectiveAt"];
|
|
1024
|
+
var PUBLICATION_NULLABLE_TIMESTAMP_FIELDS = [
|
|
1025
|
+
"supersededAt",
|
|
1026
|
+
"sellThroughUntil",
|
|
1027
|
+
"revocationEffectiveAt"
|
|
1028
|
+
];
|
|
1029
|
+
function malformed(detail) {
|
|
1030
|
+
return new FFIDSDKError(
|
|
1031
|
+
MALFORMED_CATALOG_CODE,
|
|
1032
|
+
`SDK: server returned malformed PublishedCatalog \u2014 ${detail}`
|
|
1033
|
+
);
|
|
1034
|
+
}
|
|
1035
|
+
function isNullableIsoString(value) {
|
|
1036
|
+
return value === null || typeof value === "string" && value.trim() !== "";
|
|
1037
|
+
}
|
|
1038
|
+
function isNonNegativeIntOrNull(value) {
|
|
1039
|
+
return value === null || typeof value === "number" && Number.isInteger(value) && value >= 0;
|
|
1040
|
+
}
|
|
1041
|
+
function validatePublishedPlan(plan, index) {
|
|
1042
|
+
if (plan === null || typeof plan !== "object") {
|
|
1043
|
+
throw malformed(`plans[${index}] must be an object`);
|
|
1044
|
+
}
|
|
1045
|
+
const record = plan;
|
|
1046
|
+
for (const key of Object.keys(record)) {
|
|
1047
|
+
if (/stripe/i.test(key)) {
|
|
1048
|
+
throw malformed(
|
|
1049
|
+
`plans[${index}] contains a Stripe identifier field (${JSON.stringify(key)}) \u2014 published catalogs must never expose Stripe IDs`
|
|
1050
|
+
);
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
if (isBlankString(record.code)) {
|
|
1054
|
+
throw malformed(`plans[${index}].code must be a non-empty string`);
|
|
1055
|
+
}
|
|
1056
|
+
if (isBlankString(record.name)) {
|
|
1057
|
+
throw malformed(`plans[${index}].name must be a non-empty string`);
|
|
1058
|
+
}
|
|
1059
|
+
if (record.description !== null && typeof record.description !== "string") {
|
|
1060
|
+
throw malformed(`plans[${index}].description must be string or null`);
|
|
1061
|
+
}
|
|
1062
|
+
if (isBlankString(record.currency)) {
|
|
1063
|
+
throw malformed(`plans[${index}].currency must be a non-empty string`);
|
|
1064
|
+
}
|
|
1065
|
+
if (!isNonNegativeIntOrNull(record.priceMonthly)) {
|
|
1066
|
+
throw malformed(`plans[${index}].priceMonthly must be a non-negative integer or null`);
|
|
1067
|
+
}
|
|
1068
|
+
if (!isNonNegativeIntOrNull(record.priceYearly)) {
|
|
1069
|
+
throw malformed(`plans[${index}].priceYearly must be a non-negative integer or null`);
|
|
1070
|
+
}
|
|
1071
|
+
if (typeof record.taxBehavior !== "string" || !TAX_BEHAVIORS.has(record.taxBehavior)) {
|
|
1072
|
+
throw malformed(
|
|
1073
|
+
`plans[${index}].taxBehavior must be inclusive | exclusive (got ${JSON.stringify(record.taxBehavior)})`
|
|
1074
|
+
);
|
|
1075
|
+
}
|
|
1076
|
+
const intervals = record.billingIntervals;
|
|
1077
|
+
if (!Array.isArray(intervals) || intervals.length === 0 || intervals.some((interval) => typeof interval !== "string" || !BILLING_INTERVALS.has(interval))) {
|
|
1078
|
+
throw malformed(
|
|
1079
|
+
`plans[${index}].billingIntervals must be a non-empty array of monthly | yearly`
|
|
1080
|
+
);
|
|
1081
|
+
}
|
|
1082
|
+
if (intervals.includes("monthly") && record.priceMonthly === null) {
|
|
1083
|
+
throw malformed(`plans[${index}] offers monthly billing but priceMonthly is null`);
|
|
1084
|
+
}
|
|
1085
|
+
if (intervals.includes("yearly") && record.priceYearly === null) {
|
|
1086
|
+
throw malformed(`plans[${index}] offers yearly billing but priceYearly is null`);
|
|
1087
|
+
}
|
|
1088
|
+
if (typeof record.minSeats !== "number" || !Number.isInteger(record.minSeats) || record.minSeats < 1) {
|
|
1089
|
+
throw malformed(`plans[${index}].minSeats must be a positive integer`);
|
|
1090
|
+
}
|
|
1091
|
+
if (record.maxSeats !== null && (typeof record.maxSeats !== "number" || !Number.isInteger(record.maxSeats) || record.maxSeats < 1)) {
|
|
1092
|
+
throw malformed(`plans[${index}].maxSeats must be a positive integer or null`);
|
|
1093
|
+
}
|
|
1094
|
+
if (record.maxSeats !== null && record.maxSeats < record.minSeats) {
|
|
1095
|
+
throw malformed(`plans[${index}].maxSeats must be >= minSeats`);
|
|
1096
|
+
}
|
|
1097
|
+
if (typeof record.displayOrder !== "number" || !Number.isInteger(record.displayOrder)) {
|
|
1098
|
+
throw malformed(`plans[${index}].displayOrder must be an integer`);
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
function validatePublishedCatalog(catalog) {
|
|
1102
|
+
if (catalog === null || typeof catalog !== "object") {
|
|
1103
|
+
throw malformed(
|
|
1104
|
+
`expected object, got ${catalog === null ? "null" : typeof catalog}`
|
|
1105
|
+
);
|
|
1106
|
+
}
|
|
1107
|
+
const c = catalog;
|
|
1108
|
+
const service = c.service;
|
|
1109
|
+
if (service === null || typeof service !== "object") {
|
|
1110
|
+
throw malformed("service must be an object");
|
|
1111
|
+
}
|
|
1112
|
+
const s = service;
|
|
1113
|
+
if (isBlankString(s.code)) {
|
|
1114
|
+
throw malformed("service.code must be a non-empty string");
|
|
1115
|
+
}
|
|
1116
|
+
if (isBlankString(s.name)) {
|
|
1117
|
+
throw malformed("service.name must be a non-empty string");
|
|
1118
|
+
}
|
|
1119
|
+
if (isBlankString(c.catalogVersion)) {
|
|
1120
|
+
throw malformed("catalogVersion must be a non-empty string");
|
|
1121
|
+
}
|
|
1122
|
+
const publication = c.publication;
|
|
1123
|
+
if (publication === null || typeof publication !== "object") {
|
|
1124
|
+
throw malformed("publication must be an object");
|
|
1125
|
+
}
|
|
1126
|
+
const p = publication;
|
|
1127
|
+
if (isBlankString(p.serviceCode)) {
|
|
1128
|
+
throw malformed("publication.serviceCode must be a non-empty string");
|
|
1129
|
+
}
|
|
1130
|
+
if (typeof p.environment !== "string" || !CATALOG_ENVIRONMENTS.has(p.environment)) {
|
|
1131
|
+
throw malformed(
|
|
1132
|
+
`publication.environment must be staging | production (got ${JSON.stringify(p.environment)})`
|
|
1133
|
+
);
|
|
1134
|
+
}
|
|
1135
|
+
if (p.catalogVersion !== c.catalogVersion) {
|
|
1136
|
+
throw malformed(
|
|
1137
|
+
`publication.catalogVersion (${JSON.stringify(p.catalogVersion)}) does not match top-level catalogVersion (${JSON.stringify(c.catalogVersion)})`
|
|
1138
|
+
);
|
|
1139
|
+
}
|
|
1140
|
+
if (typeof p.status !== "string" || !PUBLICATION_STATUSES.has(p.status)) {
|
|
1141
|
+
throw malformed(
|
|
1142
|
+
`publication.status must be one of approved | superseded | revoking | revoked (got ${JSON.stringify(p.status)})`
|
|
1143
|
+
);
|
|
1144
|
+
}
|
|
1145
|
+
for (const field of PUBLICATION_HASH_FIELDS) {
|
|
1146
|
+
const value = p[field];
|
|
1147
|
+
if (typeof value !== "string" || !CATALOG_HASH_PATTERN.test(value)) {
|
|
1148
|
+
throw malformed(`publication.${field} must be lowercase SHA-256 hex (64 chars)`);
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
if (typeof p.salesEpoch !== "number" || !Number.isInteger(p.salesEpoch) || p.salesEpoch < 1) {
|
|
1152
|
+
throw malformed(
|
|
1153
|
+
`publication.salesEpoch must be a positive integer (got ${JSON.stringify(p.salesEpoch)})`
|
|
1154
|
+
);
|
|
1155
|
+
}
|
|
1156
|
+
if (isBlankString(p.approvalId)) {
|
|
1157
|
+
throw malformed("publication.approvalId must be a non-empty string");
|
|
1158
|
+
}
|
|
1159
|
+
for (const field of PUBLICATION_REQUIRED_TIMESTAMP_FIELDS) {
|
|
1160
|
+
if (isBlankString(p[field])) {
|
|
1161
|
+
throw malformed(`publication.${field} must be a non-empty ISO 8601 string`);
|
|
1162
|
+
}
|
|
1163
|
+
}
|
|
1164
|
+
for (const field of PUBLICATION_NULLABLE_TIMESTAMP_FIELDS) {
|
|
1165
|
+
if (!isNullableIsoString(p[field])) {
|
|
1166
|
+
throw malformed(`publication.${field} must be a non-empty ISO 8601 string or null`);
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1169
|
+
if (!Array.isArray(c.plans)) {
|
|
1170
|
+
throw malformed(`plans must be an array (got ${typeof c.plans})`);
|
|
1171
|
+
}
|
|
1172
|
+
for (const [index, plan] of c.plans.entries()) {
|
|
1173
|
+
validatePublishedPlan(plan, index);
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1176
|
+
function createCatalogMethods(deps) {
|
|
1177
|
+
const { fetchWithAuth, createError } = deps;
|
|
1178
|
+
async function getPublishedCatalog(serviceCode) {
|
|
1179
|
+
if (isBlankString(serviceCode)) {
|
|
1180
|
+
return {
|
|
1181
|
+
error: createError("VALIDATION_ERROR", "serviceCode \u306F\u5FC5\u9808\u3067\u3059")
|
|
1182
|
+
};
|
|
1183
|
+
}
|
|
1184
|
+
const requestedCode = serviceCode.trim();
|
|
1185
|
+
const response = await fetchWithAuth(
|
|
1186
|
+
`${EXT_CATALOG_ENDPOINT}/${encodeURIComponent(requestedCode)}`
|
|
1187
|
+
);
|
|
1188
|
+
if (response.data !== void 0) {
|
|
1189
|
+
validatePublishedCatalog(response.data);
|
|
1190
|
+
if (response.data.service.code !== requestedCode || response.data.publication.serviceCode !== requestedCode) {
|
|
1191
|
+
throw malformed(
|
|
1192
|
+
`response is for service ${JSON.stringify(response.data.service.code)} (publication: ${JSON.stringify(response.data.publication.serviceCode)}) but ${JSON.stringify(requestedCode)} was requested`
|
|
1193
|
+
);
|
|
1194
|
+
}
|
|
1195
|
+
}
|
|
1196
|
+
return response;
|
|
1197
|
+
}
|
|
1198
|
+
return { getPublishedCatalog };
|
|
1199
|
+
}
|
|
1200
|
+
|
|
827
1201
|
// src/client/members-methods.ts
|
|
828
1202
|
var EXT_MEMBERS_ENDPOINT = "/api/v1/organizations/ext/members";
|
|
829
1203
|
function createMembersMethods(deps) {
|
|
@@ -1266,7 +1640,7 @@ function validateTokenResponse(tokenResponse) {
|
|
|
1266
1640
|
}
|
|
1267
1641
|
|
|
1268
1642
|
// src/client/version-check.ts
|
|
1269
|
-
var SDK_VERSION = "5.
|
|
1643
|
+
var SDK_VERSION = "5.28.0";
|
|
1270
1644
|
var SDK_USER_AGENT = `FFID-SDK/${SDK_VERSION} (TypeScript)`;
|
|
1271
1645
|
var SDK_VERSION_HEADER = "X-FFID-SDK-Version";
|
|
1272
1646
|
function sdkHeaders() {
|
|
@@ -2678,6 +3052,11 @@ function createInquiryMethods(deps) {
|
|
|
2678
3052
|
name,
|
|
2679
3053
|
message,
|
|
2680
3054
|
category: params.category,
|
|
3055
|
+
// Normalize null ("survey not collected", e.g. forwarded verbatim
|
|
3056
|
+
// from FFIDInquiryFormSubmitData) to undefined so JSON.stringify
|
|
3057
|
+
// drops the key entirely — older FFID deployments never see it and
|
|
3058
|
+
// current ones treat the omission as NULL.
|
|
3059
|
+
acquisitionSource: params.acquisitionSource ?? void 0,
|
|
2681
3060
|
company: params.company,
|
|
2682
3061
|
phone: params.phone,
|
|
2683
3062
|
locale: params.locale,
|
|
@@ -2710,39 +3089,7 @@ var BLOCKING_EFFECTIVE_STATUSES = [
|
|
|
2710
3089
|
"trial_expired"
|
|
2711
3090
|
];
|
|
2712
3091
|
|
|
2713
|
-
// src/client/
|
|
2714
|
-
var FFID_ERROR_CODES = {
|
|
2715
|
-
NETWORK_ERROR: "NETWORK_ERROR",
|
|
2716
|
-
PARSE_ERROR: "PARSE_ERROR",
|
|
2717
|
-
UNKNOWN_ERROR: "UNKNOWN_ERROR",
|
|
2718
|
-
TOKEN_EXCHANGE_ERROR: "TOKEN_EXCHANGE_ERROR",
|
|
2719
|
-
TOKEN_REFRESH_ERROR: "TOKEN_REFRESH_ERROR",
|
|
2720
|
-
NO_TOKENS: "NO_TOKENS",
|
|
2721
|
-
TOKEN_VERIFICATION_ERROR: "TOKEN_VERIFICATION_ERROR",
|
|
2722
|
-
TOKEN_EXPIRED: "TOKEN_EXPIRED",
|
|
2723
|
-
STATE_MISMATCH_ERROR: "STATE_MISMATCH_ERROR"
|
|
2724
|
-
};
|
|
2725
|
-
|
|
2726
|
-
// src/client/ffid-client.ts
|
|
2727
|
-
var UNAUTHORIZED_STATUS2 = 401;
|
|
2728
|
-
var SDK_LOG_PREFIX = "[FFID SDK]";
|
|
2729
|
-
var noopLogger = {
|
|
2730
|
-
debug: () => {
|
|
2731
|
-
},
|
|
2732
|
-
info: () => {
|
|
2733
|
-
},
|
|
2734
|
-
warn: () => {
|
|
2735
|
-
},
|
|
2736
|
-
error: () => {
|
|
2737
|
-
}
|
|
2738
|
-
};
|
|
2739
|
-
var consoleLogger = {
|
|
2740
|
-
debug: (...args) => console.debug(SDK_LOG_PREFIX, ...args),
|
|
2741
|
-
info: (...args) => console.info(SDK_LOG_PREFIX, ...args),
|
|
2742
|
-
warn: (...args) => console.warn(SDK_LOG_PREFIX, ...args),
|
|
2743
|
-
error: (...args) => console.error(SDK_LOG_PREFIX, ...args)
|
|
2744
|
-
};
|
|
2745
|
-
var EXT_CHECK_ENDPOINT = "/api/v1/subscriptions/ext/check";
|
|
3092
|
+
// src/client/service-access-decision.ts
|
|
2746
3093
|
var DEFAULT_ALLOW_GRACE = true;
|
|
2747
3094
|
var DEFAULT_SERVICE_ACCESS_FAIL_POLICY = "failClosed";
|
|
2748
3095
|
var SERVICE_ACCESS_DENIED_CODE = "SERVICE_ACCESS_DENIED";
|
|
@@ -2809,6 +3156,40 @@ function failClosedServiceAccessDecision(params, error) {
|
|
|
2809
3156
|
error
|
|
2810
3157
|
};
|
|
2811
3158
|
}
|
|
3159
|
+
|
|
3160
|
+
// src/client/error-codes.ts
|
|
3161
|
+
var FFID_ERROR_CODES = {
|
|
3162
|
+
NETWORK_ERROR: "NETWORK_ERROR",
|
|
3163
|
+
PARSE_ERROR: "PARSE_ERROR",
|
|
3164
|
+
UNKNOWN_ERROR: "UNKNOWN_ERROR",
|
|
3165
|
+
TOKEN_EXCHANGE_ERROR: "TOKEN_EXCHANGE_ERROR",
|
|
3166
|
+
TOKEN_REFRESH_ERROR: "TOKEN_REFRESH_ERROR",
|
|
3167
|
+
NO_TOKENS: "NO_TOKENS",
|
|
3168
|
+
TOKEN_VERIFICATION_ERROR: "TOKEN_VERIFICATION_ERROR",
|
|
3169
|
+
TOKEN_EXPIRED: "TOKEN_EXPIRED",
|
|
3170
|
+
STATE_MISMATCH_ERROR: "STATE_MISMATCH_ERROR"
|
|
3171
|
+
};
|
|
3172
|
+
|
|
3173
|
+
// src/client/ffid-client.ts
|
|
3174
|
+
var UNAUTHORIZED_STATUS2 = 401;
|
|
3175
|
+
var SDK_LOG_PREFIX = "[FFID SDK]";
|
|
3176
|
+
var noopLogger = {
|
|
3177
|
+
debug: () => {
|
|
3178
|
+
},
|
|
3179
|
+
info: () => {
|
|
3180
|
+
},
|
|
3181
|
+
warn: () => {
|
|
3182
|
+
},
|
|
3183
|
+
error: () => {
|
|
3184
|
+
}
|
|
3185
|
+
};
|
|
3186
|
+
var consoleLogger = {
|
|
3187
|
+
debug: (...args) => console.debug(SDK_LOG_PREFIX, ...args),
|
|
3188
|
+
info: (...args) => console.info(SDK_LOG_PREFIX, ...args),
|
|
3189
|
+
warn: (...args) => console.warn(SDK_LOG_PREFIX, ...args),
|
|
3190
|
+
error: (...args) => console.error(SDK_LOG_PREFIX, ...args)
|
|
3191
|
+
};
|
|
3192
|
+
var EXT_CHECK_ENDPOINT = "/api/v1/subscriptions/ext/check";
|
|
2812
3193
|
function resolveRedirectUri(raw, logger) {
|
|
2813
3194
|
if (raw === null) return null;
|
|
2814
3195
|
try {
|
|
@@ -3126,6 +3507,10 @@ function createFFIDClient(config) {
|
|
|
3126
3507
|
fetchWithAuth,
|
|
3127
3508
|
createError
|
|
3128
3509
|
});
|
|
3510
|
+
const { getPublishedCatalog } = createCatalogMethods({
|
|
3511
|
+
fetchWithAuth,
|
|
3512
|
+
createError
|
|
3513
|
+
});
|
|
3129
3514
|
const { listMembers, addMember, updateMemberRole, removeMember } = createMembersMethods({
|
|
3130
3515
|
fetchWithAuth,
|
|
3131
3516
|
createError,
|
|
@@ -3252,6 +3637,7 @@ function createFFIDClient(config) {
|
|
|
3252
3637
|
assignSeats,
|
|
3253
3638
|
unassignSeat,
|
|
3254
3639
|
listPlans,
|
|
3640
|
+
getPublishedCatalog,
|
|
3255
3641
|
getSubscription,
|
|
3256
3642
|
subscribe,
|
|
3257
3643
|
changePlan,
|
|
@@ -3432,11 +3818,11 @@ function getFFIDConsentFromCookieHeader(cookieHeader) {
|
|
|
3432
3818
|
|
|
3433
3819
|
// src/server/auth/server-state.ts
|
|
3434
3820
|
var STATE_RANDOM_BYTES2 = 16;
|
|
3435
|
-
var
|
|
3821
|
+
var HEX_RADIX2 = 16;
|
|
3436
3822
|
function generateServerState() {
|
|
3437
3823
|
const bytes = new Uint8Array(STATE_RANDOM_BYTES2);
|
|
3438
3824
|
globalThis.crypto.getRandomValues(bytes);
|
|
3439
|
-
return Array.from(bytes, (b) => b.toString(
|
|
3825
|
+
return Array.from(bytes, (b) => b.toString(HEX_RADIX2).padStart(2, "0")).join("");
|
|
3440
3826
|
}
|
|
3441
3827
|
|
|
3442
3828
|
// src/server/auth/server-oauth-token.ts
|
|
@@ -3695,10 +4081,21 @@ Object.defineProperty(exports, "DEFAULT_OAUTH_SCOPES", {
|
|
|
3695
4081
|
get: function () { return chunkMDHKSVLP_cjs.DEFAULT_OAUTH_SCOPES; }
|
|
3696
4082
|
});
|
|
3697
4083
|
exports.ALL_DENIED_EXCEPT_NECESSARY = ALL_DENIED_EXCEPT_NECESSARY;
|
|
4084
|
+
exports.CATALOG_HASH_PATTERN = CATALOG_HASH_PATTERN;
|
|
3698
4085
|
exports.CONSENT_COOKIE_NAME = CONSENT_COOKIE_NAME;
|
|
3699
4086
|
exports.COOKIE_VERSION = COOKIE_VERSION;
|
|
4087
|
+
exports.FFIDCatalogHashError = FFIDCatalogHashError;
|
|
4088
|
+
exports.FFID_CATALOG_ENVIRONMENTS = FFID_CATALOG_ENVIRONMENTS;
|
|
4089
|
+
exports.FFID_CATALOG_PUBLICATION_STATUSES = FFID_CATALOG_PUBLICATION_STATUSES;
|
|
3700
4090
|
exports.FFID_ERROR_CODES = FFID_ERROR_CODES;
|
|
3701
4091
|
exports.FFID_SESSION_COOKIE_NAME = FFID_SESSION_COOKIE_NAME;
|
|
4092
|
+
exports.canonicalizeCatalogValue = canonicalizeCatalogValue;
|
|
4093
|
+
exports.computeFFIDCheckoutCopyHash = computeFFIDCheckoutCopyHash;
|
|
4094
|
+
exports.computeLegalDisclosureHash = computeLegalDisclosureHash;
|
|
4095
|
+
exports.computePaymentConfigurationHash = computePaymentConfigurationHash;
|
|
4096
|
+
exports.computePraxisCopyHash = computePraxisCopyHash;
|
|
4097
|
+
exports.computeScopeHash = computeScopeHash;
|
|
4098
|
+
exports.computeTaxConfigurationHash = computeTaxConfigurationHash;
|
|
3702
4099
|
exports.createFFIDClient = createFFIDClient;
|
|
3703
4100
|
exports.createKVCacheAdapter = createKVCacheAdapter;
|
|
3704
4101
|
exports.createMemoryCacheAdapter = createMemoryCacheAdapter;
|
|
@@ -3710,4 +4107,7 @@ exports.encodeConsentCookie = encodeConsentCookie;
|
|
|
3710
4107
|
exports.generateServerState = generateServerState;
|
|
3711
4108
|
exports.getFFIDConsentFromCookieHeader = getFFIDConsentFromCookieHeader;
|
|
3712
4109
|
exports.getFFIDConsentFromCookies = getFFIDConsentFromCookies;
|
|
4110
|
+
exports.hashCanonicalJson = hashCanonicalJson;
|
|
3713
4111
|
exports.sessionCookies = sessionCookies;
|
|
4112
|
+
exports.sha256Hex = sha256Hex;
|
|
4113
|
+
exports.validatePublishedCatalog = validatePublishedCatalog;
|