@funnelsgrove/runtime 0.1.35 → 0.1.37
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/index.d.ts
CHANGED
|
@@ -15,6 +15,7 @@ export * from './runtime/posthog-flags.js';
|
|
|
15
15
|
export * from './runtime/use-funnel-flow-controller.js';
|
|
16
16
|
export * from './runtime/preview-bridge.js';
|
|
17
17
|
export * from './runtime/preview-definition-overrides.js';
|
|
18
|
+
export * from './runtime/offer-set-runtime.js';
|
|
18
19
|
export * from './runtime/route-resolver.js';
|
|
19
20
|
export * from './runtime/subscription-handoff.js';
|
|
20
21
|
export * from './runtime/url-user-attributes.js';
|
package/dist/index.js
CHANGED
|
@@ -15,6 +15,7 @@ export * from './runtime/posthog-flags.js';
|
|
|
15
15
|
export * from './runtime/use-funnel-flow-controller.js';
|
|
16
16
|
export * from './runtime/preview-bridge.js';
|
|
17
17
|
export * from './runtime/preview-definition-overrides.js';
|
|
18
|
+
export * from './runtime/offer-set-runtime.js';
|
|
18
19
|
export * from './runtime/route-resolver.js';
|
|
19
20
|
export * from './runtime/subscription-handoff.js';
|
|
20
21
|
export * from './runtime/url-user-attributes.js';
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import type { RuntimeMode } from '../services/runtime-mode.service.js';
|
|
2
|
+
export type GeneratedOfferSetItemKind = 'plan' | 'upsell' | 'downsell';
|
|
3
|
+
export type GeneratedOfferSetPlanItem = {
|
|
4
|
+
readonly funnelPlanKey: string;
|
|
5
|
+
readonly projectBillingPlanId?: string | null;
|
|
6
|
+
readonly testProviderPlanId?: string | null;
|
|
7
|
+
readonly liveProviderPlanId?: string | null;
|
|
8
|
+
readonly analyticsPurchaseValue?: number | null;
|
|
9
|
+
readonly position?: number | null;
|
|
10
|
+
readonly isDefault?: boolean | null;
|
|
11
|
+
readonly displayNameOverride?: string | null;
|
|
12
|
+
readonly featuredTag?: string | null;
|
|
13
|
+
readonly oldPriceLabel?: string | null;
|
|
14
|
+
readonly metadata?: Record<string, unknown> | null;
|
|
15
|
+
};
|
|
16
|
+
export type GeneratedOfferSetDiscount = {
|
|
17
|
+
readonly key?: string | null;
|
|
18
|
+
readonly testProviderCouponId?: string | null;
|
|
19
|
+
readonly liveProviderCouponId?: string | null;
|
|
20
|
+
readonly position?: number | null;
|
|
21
|
+
readonly metadata?: Record<string, unknown> | null;
|
|
22
|
+
};
|
|
23
|
+
export type GeneratedOfferSet = {
|
|
24
|
+
readonly key: string;
|
|
25
|
+
readonly isActive?: boolean | null;
|
|
26
|
+
readonly metadata?: Record<string, unknown> | null;
|
|
27
|
+
readonly items: readonly GeneratedOfferSetPlanItem[];
|
|
28
|
+
};
|
|
29
|
+
export type OfferSetRuntimeDiscount = {
|
|
30
|
+
readonly stage: 'first';
|
|
31
|
+
readonly couponId: string;
|
|
32
|
+
readonly discountPercent: number;
|
|
33
|
+
readonly durationSeconds: number;
|
|
34
|
+
readonly previousDiscountPercent?: null;
|
|
35
|
+
} | {
|
|
36
|
+
readonly stage: 'second';
|
|
37
|
+
readonly couponId: string;
|
|
38
|
+
readonly discountPercent: number;
|
|
39
|
+
readonly durationSeconds: number;
|
|
40
|
+
readonly previousDiscountPercent: number;
|
|
41
|
+
};
|
|
42
|
+
export type OfferSetRuntimePlanOverrides = {
|
|
43
|
+
projectPlanId?: string;
|
|
44
|
+
providerPlanId?: string;
|
|
45
|
+
analyticsPurchaseValue?: number;
|
|
46
|
+
title?: string;
|
|
47
|
+
featuredTag?: string;
|
|
48
|
+
oldPriceLabel?: string;
|
|
49
|
+
isDefault?: boolean;
|
|
50
|
+
};
|
|
51
|
+
export type OfferSetRuntimeInput<TPlan extends Record<string, unknown>> = {
|
|
52
|
+
offerSets: readonly GeneratedOfferSet[];
|
|
53
|
+
offerSetKey?: string;
|
|
54
|
+
baseCatalogsByMode: Record<RuntimeMode, Record<string, TPlan>>;
|
|
55
|
+
fallbackDiscounts?: readonly OfferSetRuntimeDiscount[];
|
|
56
|
+
};
|
|
57
|
+
export type OfferSetRuntimeResult<TPlan extends Record<string, unknown>> = {
|
|
58
|
+
offerSet: GeneratedOfferSet | null;
|
|
59
|
+
plansByMode: Record<RuntimeMode, Record<string, TPlan & OfferSetRuntimePlanOverrides>>;
|
|
60
|
+
planKeys: string[];
|
|
61
|
+
upsellPlanKeys: string[];
|
|
62
|
+
downsellPlanKeys: string[];
|
|
63
|
+
defaultPlanKey: string;
|
|
64
|
+
discountListsByMode: Record<RuntimeMode, readonly OfferSetRuntimeDiscount[]>;
|
|
65
|
+
getPlanCatalog: (mode: RuntimeMode) => Record<string, TPlan & OfferSetRuntimePlanOverrides>;
|
|
66
|
+
getDiscountList: (mode: RuntimeMode) => readonly OfferSetRuntimeDiscount[];
|
|
67
|
+
};
|
|
68
|
+
export declare const findGeneratedOfferSet: (offerSets: readonly GeneratedOfferSet[], offerSetKey?: string) => GeneratedOfferSet | null;
|
|
69
|
+
export declare const applyGeneratedOfferSetToPlanCatalog: <TPlan extends Record<string, unknown>>(baseCatalog: Record<string, TPlan>, mode: RuntimeMode, offerSet: GeneratedOfferSet | null) => Record<string, TPlan & OfferSetRuntimePlanOverrides>;
|
|
70
|
+
export declare const buildGeneratedOfferSetDiscountList: (offerSet: GeneratedOfferSet | null, mode: RuntimeMode, fallbackDiscounts?: readonly OfferSetRuntimeDiscount[]) => readonly OfferSetRuntimeDiscount[];
|
|
71
|
+
export declare const resolveGeneratedOfferSetRuntime: <TPlan extends Record<string, unknown>>(input: OfferSetRuntimeInput<TPlan>) => OfferSetRuntimeResult<TPlan>;
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
const isRecord = (value) => (typeof value === 'object' && value !== null && !Array.isArray(value));
|
|
2
|
+
const readString = (value) => (typeof value === 'string' && value.trim() ? value.trim() : null);
|
|
3
|
+
const readFiniteNumber = (value) => {
|
|
4
|
+
const numeric = typeof value === 'number'
|
|
5
|
+
? value
|
|
6
|
+
: typeof value === 'string' && value.trim()
|
|
7
|
+
? Number(value.trim())
|
|
8
|
+
: null;
|
|
9
|
+
return numeric !== null && Number.isFinite(numeric) ? numeric : null;
|
|
10
|
+
};
|
|
11
|
+
const readPositiveInteger = (value) => {
|
|
12
|
+
const numeric = readFiniteNumber(value);
|
|
13
|
+
return numeric !== null && numeric > 0 ? Math.round(numeric) : null;
|
|
14
|
+
};
|
|
15
|
+
const getOfferSetItemKind = (item) => {
|
|
16
|
+
var _a;
|
|
17
|
+
const metadata = isRecord(item.metadata) ? item.metadata : {};
|
|
18
|
+
const value = (_a = metadata.offerItemKind) !== null && _a !== void 0 ? _a : metadata.kind;
|
|
19
|
+
return value === 'upsell' || value === 'downsell' ? value : 'plan';
|
|
20
|
+
};
|
|
21
|
+
const getSortedItemsByKind = (offerSet, kind) => {
|
|
22
|
+
var _a;
|
|
23
|
+
return ((_a = offerSet === null || offerSet === void 0 ? void 0 : offerSet.items.filter((item) => getOfferSetItemKind(item) === kind).slice().sort((left, right) => {
|
|
24
|
+
var _a, _b;
|
|
25
|
+
const leftPosition = (_a = readFiniteNumber(left.position)) !== null && _a !== void 0 ? _a : 0;
|
|
26
|
+
const rightPosition = (_b = readFiniteNumber(right.position)) !== null && _b !== void 0 ? _b : 0;
|
|
27
|
+
return leftPosition - rightPosition;
|
|
28
|
+
})) !== null && _a !== void 0 ? _a : []);
|
|
29
|
+
};
|
|
30
|
+
export const findGeneratedOfferSet = (offerSets, offerSetKey) => {
|
|
31
|
+
var _a, _b;
|
|
32
|
+
const activeOfferSets = offerSets.filter((offerSet) => offerSet.isActive !== false);
|
|
33
|
+
return ((_b = (_a = activeOfferSets.find((offerSet) => offerSet.key === offerSetKey)) !== null && _a !== void 0 ? _a : activeOfferSets[0]) !== null && _b !== void 0 ? _b : null);
|
|
34
|
+
};
|
|
35
|
+
const readProviderPlanId = (item, mode) => (mode === 'test'
|
|
36
|
+
? readString(item.testProviderPlanId)
|
|
37
|
+
: readString(item.liveProviderPlanId));
|
|
38
|
+
export const applyGeneratedOfferSetToPlanCatalog = (baseCatalog, mode, offerSet) => {
|
|
39
|
+
var _a, _b, _c;
|
|
40
|
+
if (!offerSet) {
|
|
41
|
+
return Object.assign({}, baseCatalog);
|
|
42
|
+
}
|
|
43
|
+
const planItems = getSortedItemsByKind(offerSet, 'plan');
|
|
44
|
+
const catalogItems = [
|
|
45
|
+
...planItems,
|
|
46
|
+
...getSortedItemsByKind(offerSet, 'upsell'),
|
|
47
|
+
...getSortedItemsByKind(offerSet, 'downsell'),
|
|
48
|
+
];
|
|
49
|
+
const defaultPlanKey = ((_a = planItems.find((item) => item.isDefault === true)) === null || _a === void 0 ? void 0 : _a.funnelPlanKey) ||
|
|
50
|
+
((_b = planItems[0]) === null || _b === void 0 ? void 0 : _b.funnelPlanKey) ||
|
|
51
|
+
'';
|
|
52
|
+
const nextCatalog = Object.assign({}, baseCatalog);
|
|
53
|
+
for (const item of catalogItems) {
|
|
54
|
+
const key = item.funnelPlanKey.trim();
|
|
55
|
+
const basePlan = nextCatalog[key];
|
|
56
|
+
if (!key || !basePlan) {
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
const providerPlanId = readProviderPlanId(item, mode);
|
|
60
|
+
const displayNameOverride = readString(item.displayNameOverride);
|
|
61
|
+
const featuredTag = readString(item.featuredTag);
|
|
62
|
+
const oldPriceLabel = readString(item.oldPriceLabel);
|
|
63
|
+
nextCatalog[key] = Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, basePlan), (readString(item.projectBillingPlanId) ? { projectPlanId: (_c = readString(item.projectBillingPlanId)) !== null && _c !== void 0 ? _c : undefined } : {})), (providerPlanId ? { providerPlanId } : {})), (typeof item.analyticsPurchaseValue === 'number'
|
|
64
|
+
? { analyticsPurchaseValue: item.analyticsPurchaseValue }
|
|
65
|
+
: {})), (displayNameOverride ? { title: displayNameOverride } : {})), (featuredTag ? { featuredTag } : {})), (oldPriceLabel ? { oldPriceLabel } : {})), { isDefault: getOfferSetItemKind(item) === 'plan' && key === defaultPlanKey || undefined });
|
|
66
|
+
}
|
|
67
|
+
return nextCatalog;
|
|
68
|
+
};
|
|
69
|
+
const getOfferSetDiscounts = (offerSet) => {
|
|
70
|
+
const metadata = isRecord(offerSet === null || offerSet === void 0 ? void 0 : offerSet.metadata) ? offerSet.metadata : {};
|
|
71
|
+
const discounts = metadata.discounts;
|
|
72
|
+
return Array.isArray(discounts)
|
|
73
|
+
? discounts.filter((discount) => isRecord(discount))
|
|
74
|
+
: [];
|
|
75
|
+
};
|
|
76
|
+
const readDiscountCouponId = (discount, mode) => (mode === 'test'
|
|
77
|
+
? readString(discount.testProviderCouponId)
|
|
78
|
+
: readString(discount.liveProviderCouponId));
|
|
79
|
+
const readDiscountPercent = (discount, fallback) => {
|
|
80
|
+
var _a, _b, _c;
|
|
81
|
+
const metadata = isRecord(discount.metadata) ? discount.metadata : {};
|
|
82
|
+
return (_c = (_b = (_a = readPositiveInteger(metadata.discountPercent)) !== null && _a !== void 0 ? _a : readPositiveInteger(metadata.percentOff)) !== null && _b !== void 0 ? _b : fallback === null || fallback === void 0 ? void 0 : fallback.discountPercent) !== null && _c !== void 0 ? _c : 0;
|
|
83
|
+
};
|
|
84
|
+
const readDiscountDurationSeconds = (discount, fallback) => {
|
|
85
|
+
var _a, _b, _c;
|
|
86
|
+
const metadata = isRecord(discount.metadata) ? discount.metadata : {};
|
|
87
|
+
return (_c = (_b = (_a = readPositiveInteger(metadata.durationSeconds)) !== null && _a !== void 0 ? _a : readPositiveInteger(metadata.duration_seconds)) !== null && _b !== void 0 ? _b : fallback === null || fallback === void 0 ? void 0 : fallback.durationSeconds) !== null && _c !== void 0 ? _c : 0;
|
|
88
|
+
};
|
|
89
|
+
const isSecondDiscount = (discount, index) => {
|
|
90
|
+
var _a, _b;
|
|
91
|
+
const metadata = isRecord(discount.metadata) ? discount.metadata : {};
|
|
92
|
+
const stage = (_b = (_a = readString(metadata.stage)) !== null && _a !== void 0 ? _a : readString(metadata.offerStage)) !== null && _b !== void 0 ? _b : readString(discount.key);
|
|
93
|
+
return stage === 'second' || stage === 'downsell' || stage === 'upgraded' || index === 1;
|
|
94
|
+
};
|
|
95
|
+
export const buildGeneratedOfferSetDiscountList = (offerSet, mode, fallbackDiscounts = []) => {
|
|
96
|
+
var _a, _b, _c, _d, _e, _f;
|
|
97
|
+
const offerSetDiscounts = getOfferSetDiscounts(offerSet);
|
|
98
|
+
if (offerSetDiscounts.length === 0) {
|
|
99
|
+
return fallbackDiscounts;
|
|
100
|
+
}
|
|
101
|
+
const sortedDiscounts = offerSetDiscounts
|
|
102
|
+
.slice()
|
|
103
|
+
.sort((left, right) => { var _a, _b; return ((_a = readFiniteNumber(left.position)) !== null && _a !== void 0 ? _a : 0) - ((_b = readFiniteNumber(right.position)) !== null && _b !== void 0 ? _b : 0); });
|
|
104
|
+
const firstFallback = (_a = fallbackDiscounts.find((discount) => discount.stage === 'first')) !== null && _a !== void 0 ? _a : null;
|
|
105
|
+
const secondFallback = (_b = fallbackDiscounts.find((discount) => discount.stage === 'second')) !== null && _b !== void 0 ? _b : null;
|
|
106
|
+
const firstDiscount = (_c = sortedDiscounts.find((discount, index) => !isSecondDiscount(discount, index))) !== null && _c !== void 0 ? _c : sortedDiscounts[0];
|
|
107
|
+
const secondDiscount = (_e = (_d = sortedDiscounts.find((discount, index) => isSecondDiscount(discount, index))) !== null && _d !== void 0 ? _d : sortedDiscounts[1]) !== null && _e !== void 0 ? _e : sortedDiscounts[0];
|
|
108
|
+
const firstCouponId = firstDiscount ? readDiscountCouponId(firstDiscount, mode) : null;
|
|
109
|
+
const secondCouponId = secondDiscount ? readDiscountCouponId(secondDiscount, mode) : null;
|
|
110
|
+
if (!firstDiscount || !secondDiscount || !firstCouponId || !secondCouponId) {
|
|
111
|
+
return fallbackDiscounts;
|
|
112
|
+
}
|
|
113
|
+
const firstDiscountPercent = readDiscountPercent(firstDiscount, firstFallback);
|
|
114
|
+
const secondMetadata = isRecord(secondDiscount.metadata) ? secondDiscount.metadata : {};
|
|
115
|
+
return [
|
|
116
|
+
{
|
|
117
|
+
stage: 'first',
|
|
118
|
+
couponId: firstCouponId,
|
|
119
|
+
discountPercent: firstDiscountPercent,
|
|
120
|
+
durationSeconds: readDiscountDurationSeconds(firstDiscount, firstFallback),
|
|
121
|
+
previousDiscountPercent: null,
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
stage: 'second',
|
|
125
|
+
couponId: secondCouponId,
|
|
126
|
+
discountPercent: readDiscountPercent(secondDiscount, secondFallback),
|
|
127
|
+
durationSeconds: readDiscountDurationSeconds(secondDiscount, secondFallback),
|
|
128
|
+
previousDiscountPercent: (_f = readPositiveInteger(secondMetadata.previousDiscountPercent)) !== null && _f !== void 0 ? _f : firstDiscountPercent,
|
|
129
|
+
},
|
|
130
|
+
];
|
|
131
|
+
};
|
|
132
|
+
export const resolveGeneratedOfferSetRuntime = (input) => {
|
|
133
|
+
var _a, _b, _c, _d;
|
|
134
|
+
const offerSet = findGeneratedOfferSet(input.offerSets, input.offerSetKey);
|
|
135
|
+
const planItems = getSortedItemsByKind(offerSet, 'plan');
|
|
136
|
+
const upsellItems = getSortedItemsByKind(offerSet, 'upsell');
|
|
137
|
+
const downsellItems = getSortedItemsByKind(offerSet, 'downsell');
|
|
138
|
+
const planKeys = planItems.map((item) => item.funnelPlanKey);
|
|
139
|
+
const upsellPlanKeys = upsellItems.map((item) => item.funnelPlanKey);
|
|
140
|
+
const downsellPlanKeys = (downsellItems.length > 0 ? downsellItems : planItems)
|
|
141
|
+
.map((item) => item.funnelPlanKey);
|
|
142
|
+
const defaultPlanKey = (_d = (_b = (_a = planItems.find((item) => item.isDefault === true)) === null || _a === void 0 ? void 0 : _a.funnelPlanKey) !== null && _b !== void 0 ? _b : (_c = planItems[0]) === null || _c === void 0 ? void 0 : _c.funnelPlanKey) !== null && _d !== void 0 ? _d : '';
|
|
143
|
+
const plansByMode = {
|
|
144
|
+
test: applyGeneratedOfferSetToPlanCatalog(input.baseCatalogsByMode.test, 'test', offerSet),
|
|
145
|
+
live: applyGeneratedOfferSetToPlanCatalog(input.baseCatalogsByMode.live, 'live', offerSet),
|
|
146
|
+
};
|
|
147
|
+
const discountListsByMode = {
|
|
148
|
+
test: buildGeneratedOfferSetDiscountList(offerSet, 'test', input.fallbackDiscounts),
|
|
149
|
+
live: buildGeneratedOfferSetDiscountList(offerSet, 'live', input.fallbackDiscounts),
|
|
150
|
+
};
|
|
151
|
+
return {
|
|
152
|
+
offerSet,
|
|
153
|
+
plansByMode,
|
|
154
|
+
planKeys,
|
|
155
|
+
upsellPlanKeys,
|
|
156
|
+
downsellPlanKeys,
|
|
157
|
+
defaultPlanKey,
|
|
158
|
+
discountListsByMode,
|
|
159
|
+
getPlanCatalog: (mode) => plansByMode[mode],
|
|
160
|
+
getDiscountList: (mode) => discountListsByMode[mode],
|
|
161
|
+
};
|
|
162
|
+
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import posthog from 'posthog-js';
|
|
2
|
+
import { runtimePublicConfig } from '../services/public-env.js';
|
|
2
3
|
let initialized = false;
|
|
3
4
|
let readyPromise = null;
|
|
4
5
|
export const POSTHOG_FEATURE_FLAG_READY_TIMEOUT_MS = 2500;
|
|
@@ -6,8 +7,8 @@ const STORED_PERSON_PROPERTIES_KEY = '$stored_person_properties';
|
|
|
6
7
|
const buildFlagScopeProperties = (config) => {
|
|
7
8
|
var _a, _b;
|
|
8
9
|
const properties = {};
|
|
9
|
-
const projectId = (_a = config.projectId) === null || _a === void 0 ? void 0 : _a.trim();
|
|
10
|
-
const funnelId = (_b = config.funnelId) === null || _b === void 0 ? void 0 : _b.trim();
|
|
10
|
+
const projectId = ((_a = config.projectId) === null || _a === void 0 ? void 0 : _a.trim()) || runtimePublicConfig.projectId;
|
|
11
|
+
const funnelId = ((_b = config.funnelId) === null || _b === void 0 ? void 0 : _b.trim()) || runtimePublicConfig.funnelId;
|
|
11
12
|
if (projectId) {
|
|
12
13
|
properties.project_id = projectId;
|
|
13
14
|
}
|