@funnelsgrove/runtime 0.7.18 → 0.7.20
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.
|
@@ -19,7 +19,6 @@ export type GeneratedOfferSetPlanModeConfig = {
|
|
|
19
19
|
export type GeneratedOfferSetPlanItem = {
|
|
20
20
|
readonly funnelPlanKey: string;
|
|
21
21
|
readonly runtimePlanKey?: string | null;
|
|
22
|
-
readonly projectBillingPlanId?: string | null;
|
|
23
22
|
readonly testProvider?: 'stripe' | 'solidgate' | null;
|
|
24
23
|
readonly liveProvider?: 'stripe' | 'solidgate' | null;
|
|
25
24
|
readonly testProviderPlanId?: string | null;
|
|
@@ -82,7 +81,6 @@ export type OfferSetRuntimePlanOverrides = {
|
|
|
82
81
|
offerSetId?: string;
|
|
83
82
|
offerSetKey?: string;
|
|
84
83
|
funnelPlanKey?: string;
|
|
85
|
-
projectPlanId?: string;
|
|
86
84
|
providerPlanId?: string;
|
|
87
85
|
paymentMode?: 'one_time';
|
|
88
86
|
analyticsPurchaseValue?: number;
|
|
@@ -155,12 +155,11 @@ const readGeneratedPlanNumberValue = (item, mode, itemKeys, metadataKeys = itemK
|
|
|
155
155
|
return ((_c = (_b = (_a = readFiniteNumberField(modeConfig, metadataKeys)) !== null && _a !== void 0 ? _a : readFiniteNumberField(fallbackModeConfig, metadataKeys)) !== null && _b !== void 0 ? _b : itemValue) !== null && _c !== void 0 ? _c : readFiniteNumberField(metadata, metadataKeys));
|
|
156
156
|
};
|
|
157
157
|
const buildGeneratedPlanOverrides = (item, mode, isDefault) => {
|
|
158
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p
|
|
158
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p;
|
|
159
159
|
const metadata = readOfferItemMetadata(item);
|
|
160
160
|
const providerPlanId = readProviderPlanId(item, mode);
|
|
161
161
|
const followUpProviderPlanId = readFollowUpProviderPlanId(item, mode);
|
|
162
|
-
const
|
|
163
|
-
const title = (_c = (_b = readString(item.title)) !== null && _b !== void 0 ? _b : readString(item.displayNameOverride)) !== null && _c !== void 0 ? _c : readStringField(metadata, ['title', 'displayName', 'publicPlanName', 'public_plan_name']);
|
|
162
|
+
const title = (_b = (_a = readString(item.title)) !== null && _a !== void 0 ? _a : readString(item.displayNameOverride)) !== null && _b !== void 0 ? _b : readStringField(metadata, ['title', 'displayName', 'publicPlanName', 'public_plan_name']);
|
|
164
163
|
const description = readGeneratedPlanStringValue(item, mode, ['description']);
|
|
165
164
|
const priceDisplayFallbackMode = mode === 'live' ? 'test' : undefined;
|
|
166
165
|
const readPriceString = (key) => (readGeneratedPlanStringValue(item, mode, [key], [String(key)], priceDisplayFallbackMode));
|
|
@@ -171,17 +170,17 @@ const buildGeneratedPlanOverrides = (item, mode, isDefault) => {
|
|
|
171
170
|
const checkoutSummaryLabel = readPriceString('checkoutSummaryLabel');
|
|
172
171
|
const billingInterval = readPriceString('billingInterval');
|
|
173
172
|
const billingIntervalCount = readPriceNumber('billingIntervalCount');
|
|
174
|
-
const followUpPlanTitle = (
|
|
173
|
+
const followUpPlanTitle = (_d = (_c = readString(item.followUpPlanTitle)) !== null && _c !== void 0 ? _c : readString(item.followUpTitle)) !== null && _d !== void 0 ? _d : readStringField(metadata, ['followUpPlanTitle', 'followUpTitle', 'follow_up_plan_title']);
|
|
175
174
|
const followUpBillingInterval = readGeneratedPlanStringValue(item, mode, ['followUpBillingInterval'], ['followUpBillingInterval', 'follow_up_billing_interval']);
|
|
176
175
|
const followUpBillingIntervalCount = readGeneratedPlanNumberValue(item, mode, ['followUpBillingIntervalCount'], ['followUpBillingIntervalCount', 'follow_up_billing_interval_count']);
|
|
177
176
|
const introIterations = readGeneratedPlanNumberValue(item, mode, ['introIterations'], ['introIterations', 'intro_iterations']);
|
|
178
177
|
const amountCents = readPriceNumber('amountCents');
|
|
179
178
|
const modeConfig = readGeneratedModeConfig(item, mode);
|
|
180
|
-
const paymentMode = (
|
|
181
|
-
const analyticsPurchaseValue = (
|
|
182
|
-
const featuredTag = (
|
|
183
|
-
const oldPriceLabel = (
|
|
184
|
-
return Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(
|
|
179
|
+
const paymentMode = (_l = (_k = (_j = (_h = (_g = (_f = (_e = readOneTimePaymentMode(modeConfig.paymentMode)) !== null && _e !== void 0 ? _e : readOneTimePaymentMode(modeConfig.payment_mode)) !== null && _f !== void 0 ? _f : readOneTimePaymentMode(modeConfig.checkoutMode)) !== null && _g !== void 0 ? _g : readOneTimePaymentMode(modeConfig.checkout_mode)) !== null && _h !== void 0 ? _h : readOneTimePaymentMode(metadata.paymentMode)) !== null && _j !== void 0 ? _j : readOneTimePaymentMode(metadata.payment_mode)) !== null && _k !== void 0 ? _k : readOneTimePaymentMode(metadata.checkoutMode)) !== null && _l !== void 0 ? _l : readOneTimePaymentMode(metadata.checkout_mode);
|
|
180
|
+
const analyticsPurchaseValue = (_m = readFiniteNumber(item.analyticsPurchaseValue)) !== null && _m !== void 0 ? _m : readFiniteNumberField(metadata, ['analyticsPurchaseValue']);
|
|
181
|
+
const featuredTag = (_o = readString(item.featuredTag)) !== null && _o !== void 0 ? _o : readStringField(metadata, ['featuredTag']);
|
|
182
|
+
const oldPriceLabel = (_p = readString(item.oldPriceLabel)) !== null && _p !== void 0 ? _p : readStringField(metadata, ['oldPriceLabel']);
|
|
183
|
+
return Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({ funnelPlanKey: item.funnelPlanKey }, (providerPlanId ? { providerPlanId } : {})), (paymentMode ? { paymentMode } : {})), (analyticsPurchaseValue !== null ? { analyticsPurchaseValue } : {})), (title ? { title } : {})), (description ? { description } : {})), (priceLabel ? { priceLabel } : {})), (perDayAmount ? { perDayAmount } : {})), (perDayLabel ? { perDayLabel } : {})), (amountCents !== null ? { amountCents: Math.max(0, Math.round(amountCents)) } : {})), (checkoutSummaryLabel ? { checkoutSummaryLabel } : {})), (billingInterval ? { billingInterval } : {})), (billingIntervalCount !== null ? { billingIntervalCount: Math.max(1, Math.round(billingIntervalCount)) } : {})), (followUpProviderPlanId ? { followUpProviderPlanId } : {})), (followUpPlanTitle ? { followUpPlanTitle } : {})), (followUpBillingInterval ? { followUpBillingInterval } : {})), (followUpBillingIntervalCount !== null ? { followUpBillingIntervalCount: Math.max(1, Math.round(followUpBillingIntervalCount)) } : {})), (introIterations !== null ? { introIterations: Math.max(1, Math.round(introIterations)) } : {})), (featuredTag ? { featuredTag } : {})), (oldPriceLabel ? { oldPriceLabel } : {})), (isDefault ? { isDefault: true } : {}));
|
|
185
184
|
};
|
|
186
185
|
export const applyGeneratedOfferSetToPlanCatalog = (baseCatalog, mode, offerSet) => {
|
|
187
186
|
var _a, _b, _c, _d;
|
|
@@ -204,7 +203,9 @@ export const applyGeneratedOfferSetToPlanCatalog = (baseCatalog, mode, offerSet)
|
|
|
204
203
|
if (!key) {
|
|
205
204
|
continue;
|
|
206
205
|
}
|
|
207
|
-
|
|
206
|
+
const basePlan = Object.assign({}, ((_c = nextCatalog[key]) !== null && _c !== void 0 ? _c : {}));
|
|
207
|
+
delete basePlan.projectPlanId;
|
|
208
|
+
nextCatalog[key] = Object.assign(Object.assign(Object.assign(Object.assign({}, basePlan), (((_d = offerSet.id) === null || _d === void 0 ? void 0 : _d.trim()) ? { offerSetId: offerSet.id.trim() } : {})), { offerSetKey: offerSet.key }), buildGeneratedPlanOverrides(item, mode, getOfferSetItemKind(item) === 'plan' && key === defaultPlanKey));
|
|
208
209
|
}
|
|
209
210
|
return nextCatalog;
|
|
210
211
|
};
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
const inflightByFunnelId = new Map();
|
|
2
|
+
const TRANSIENT_RUNTIME_CONFIG_STATUSES = new Set([408, 429, 500, 502, 503, 504]);
|
|
3
|
+
const RUNTIME_CONFIG_RETRY_DELAYS_MS = [250, 750];
|
|
4
|
+
const wait = (delayMs) => (new Promise((resolve) => setTimeout(resolve, delayMs)));
|
|
2
5
|
const isRecord = (value) => (value !== null && typeof value === 'object' && !Array.isArray(value));
|
|
3
6
|
const readString = (value) => (typeof value === 'string' && value.trim() ? value.trim() : null);
|
|
4
7
|
const readNumber = (value) => (typeof value === 'number' && Number.isFinite(value) ? value : null);
|
|
@@ -112,7 +115,6 @@ const normalizePublishedOfferSets = (config) => {
|
|
|
112
115
|
if (!funnelPlanKey) {
|
|
113
116
|
throw new Error(`Published runtime offer set "${key}" has an item without a plan key`);
|
|
114
117
|
}
|
|
115
|
-
const projectBillingPlanId = pickString(item, 'projectBillingPlanId', 'project_billing_plan_id');
|
|
116
118
|
const testProvider = parsePaymentProvider(pick(item, 'testProvider', 'test_provider'), `Published runtime offer set "${key}"`);
|
|
117
119
|
const liveProvider = parsePaymentProvider(pick(item, 'liveProvider', 'live_provider'), `Published runtime offer set "${key}"`);
|
|
118
120
|
const testMapping = buildPlanModeConfig(item, 'test');
|
|
@@ -121,9 +123,9 @@ const normalizePublishedOfferSets = (config) => {
|
|
|
121
123
|
|| pickString(metadata, 'title', 'publicPlanName', 'public_plan_name');
|
|
122
124
|
const description = pickString(item, 'description')
|
|
123
125
|
|| pickString(metadata, 'description');
|
|
124
|
-
return Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(
|
|
126
|
+
return Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({ funnelPlanKey }, (pickString(metadata, 'runtimePlanKey', 'planKey', 'billingPlanKey')
|
|
125
127
|
? { runtimePlanKey: pickString(metadata, 'runtimePlanKey', 'planKey', 'billingPlanKey') }
|
|
126
|
-
: {})), (
|
|
128
|
+
: {})), (testProvider ? { testProvider } : {})), (liveProvider ? { liveProvider } : {})), (pickString(item, 'testProviderPlanId', 'test_provider_plan_id')
|
|
127
129
|
? { testProviderPlanId: pickString(item, 'testProviderPlanId', 'test_provider_plan_id') }
|
|
128
130
|
: {})), (pickString(item, 'liveProviderPlanId', 'live_provider_plan_id')
|
|
129
131
|
? { liveProviderPlanId: pickString(item, 'liveProviderPlanId', 'live_provider_plan_id') }
|
|
@@ -298,19 +300,38 @@ export const loadFunnelRuntimeConfig = (funnelId, options = {}) => {
|
|
|
298
300
|
}
|
|
299
301
|
const fetcher = options.fetcher || globalThis.fetch;
|
|
300
302
|
const basePath = (options.basePath || '/api/funnel-config').replace(/\/$/, '');
|
|
301
|
-
const
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
303
|
+
const requestUrl = `${basePath}/${encodeURIComponent(normalizedFunnelId)}?versionId=${encodeURIComponent(versionId)}`;
|
|
304
|
+
const request = (async () => {
|
|
305
|
+
for (let attempt = 0;; attempt += 1) {
|
|
306
|
+
let response;
|
|
307
|
+
try {
|
|
308
|
+
response = await fetcher(requestUrl, {
|
|
309
|
+
cache: 'no-store',
|
|
310
|
+
headers: { accept: 'application/json', 'cache-control': 'no-cache' },
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
catch (error) {
|
|
314
|
+
const retryDelay = RUNTIME_CONFIG_RETRY_DELAYS_MS[attempt];
|
|
315
|
+
if (retryDelay === undefined)
|
|
316
|
+
throw error;
|
|
317
|
+
await wait(retryDelay);
|
|
318
|
+
continue;
|
|
319
|
+
}
|
|
320
|
+
if (!response.ok) {
|
|
321
|
+
const retryDelay = RUNTIME_CONFIG_RETRY_DELAYS_MS[attempt];
|
|
322
|
+
if (retryDelay !== undefined && TRANSIENT_RUNTIME_CONFIG_STATUSES.has(response.status)) {
|
|
323
|
+
await wait(retryDelay);
|
|
324
|
+
continue;
|
|
325
|
+
}
|
|
326
|
+
throw new Error(`Unable to load funnel runtime config (${response.status})`);
|
|
327
|
+
}
|
|
328
|
+
const published = parsePublishedRuntimeConfig(await response.json());
|
|
329
|
+
if (published.config.funnelId !== normalizedFunnelId) {
|
|
330
|
+
throw new Error('Funnel runtime config belongs to a different funnel');
|
|
331
|
+
}
|
|
332
|
+
return published;
|
|
311
333
|
}
|
|
312
|
-
|
|
313
|
-
}).finally(() => {
|
|
334
|
+
})().finally(() => {
|
|
314
335
|
inflightByFunnelId.delete(cacheKey);
|
|
315
336
|
});
|
|
316
337
|
inflightByFunnelId.set(cacheKey, request);
|