@funnelsgrove/runtime 0.1.0 → 0.1.2
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/README.md +20 -1
- package/dist/components/FunnelContext.d.ts +5 -2
- package/dist/components/FunnelContext.js +3 -0
- package/dist/components/FunnelEditorPanel.d.ts +3 -5
- package/dist/components/FunnelEditorPanel.js +3 -3
- package/dist/components/ManageSubscriptionScreen.d.ts +51 -0
- package/dist/components/ManageSubscriptionScreen.js +349 -0
- package/dist/components/RuntimeDevInfoBox.d.ts +23 -0
- package/dist/components/RuntimeDevInfoBox.js +363 -0
- package/dist/components/SubscriptionHandoffScreen.d.ts +31 -0
- package/dist/components/SubscriptionHandoffScreen.js +338 -0
- package/dist/config/builder-preview.protocol.d.ts +73 -0
- package/dist/config/builder-preview.protocol.js +3 -0
- package/dist/config/env.config.d.ts +44 -0
- package/dist/config/env.config.js +161 -0
- package/dist/config/font-config.d.ts +14 -0
- package/dist/config/font-config.js +101 -0
- package/dist/config/funnel-theme.d.ts +61 -10
- package/dist/config/funnel-theme.js +355 -35
- package/dist/config/funnel.manifest.types.d.ts +13 -7
- package/dist/content/step-content.d.ts +130 -0
- package/dist/content/step-content.js +381 -0
- package/dist/index.d.ts +33 -21
- package/dist/index.js +33 -21
- package/dist/runtime/browser-helpers.d.ts +1 -0
- package/dist/runtime/browser-helpers.js +14 -0
- package/dist/runtime/experiment-assignment.d.ts +13 -4
- package/dist/runtime/experiment-assignment.js +9 -27
- package/dist/runtime/funnel-attribution.d.ts +18 -0
- package/dist/runtime/funnel-attribution.js +226 -0
- package/dist/runtime/funnel-flow.d.ts +9 -10
- package/dist/runtime/funnel-flow.js +4 -18
- package/dist/runtime/funnel-manifest.validation.d.ts +1 -1
- package/dist/runtime/funnel-manifest.validation.js +2 -6
- package/dist/runtime/funnel-runtime.d.ts +2 -3
- package/dist/runtime/funnel-runtime.js +6 -13
- package/dist/runtime/posthog-flags.d.ts +30 -0
- package/dist/runtime/posthog-flags.js +71 -0
- package/dist/runtime/preview-bridge.d.ts +12 -2
- package/dist/runtime/preview-bridge.js +95 -4
- package/dist/runtime/preview-definition-overrides.d.ts +20 -0
- package/dist/runtime/preview-definition-overrides.js +148 -0
- package/dist/runtime/route-resolver.d.ts +2 -3
- package/dist/runtime/route-resolver.js +15 -26
- package/dist/runtime/subscription-handoff.d.ts +32 -0
- package/dist/runtime/subscription-handoff.js +113 -0
- package/dist/runtime/use-funnel-flow-controller.d.ts +19 -10
- package/dist/runtime/use-funnel-flow-controller.js +190 -159
- package/dist/sdk/userAnswers.d.ts +2 -2
- package/dist/services/api.service.d.ts +21 -4
- package/dist/services/api.service.js +165 -35
- package/dist/services/funnel-state.service.d.ts +8 -0
- package/dist/services/funnel-state.service.js +44 -0
- package/dist/services/preview-frame.service.d.ts +2 -2
- package/dist/services/preview-frame.service.js +2 -2
- package/dist/services/public-env.d.ts +69 -0
- package/dist/services/public-env.js +105 -0
- package/dist/services/runtime-api.config.d.ts +5 -0
- package/dist/services/runtime-api.config.js +12 -7
- package/dist/services/runtime-mode.service.d.ts +3 -0
- package/dist/services/runtime-mode.service.js +142 -4
- package/package.json +8 -2
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
export type StepImage = {
|
|
2
|
+
src: string;
|
|
3
|
+
alt?: string;
|
|
4
|
+
};
|
|
5
|
+
export type ChoiceItem = {
|
|
6
|
+
id: string;
|
|
7
|
+
label: string;
|
|
8
|
+
description?: string;
|
|
9
|
+
icon?: string;
|
|
10
|
+
image?: StepImage;
|
|
11
|
+
badge?: string;
|
|
12
|
+
value?: string;
|
|
13
|
+
};
|
|
14
|
+
export type InfoItem = {
|
|
15
|
+
id: string;
|
|
16
|
+
title: string;
|
|
17
|
+
description?: string;
|
|
18
|
+
icon?: string;
|
|
19
|
+
image?: StepImage;
|
|
20
|
+
badge?: string;
|
|
21
|
+
meta?: string;
|
|
22
|
+
};
|
|
23
|
+
export type QuoteItem = {
|
|
24
|
+
id: string;
|
|
25
|
+
quote: string;
|
|
26
|
+
author: string;
|
|
27
|
+
role?: string;
|
|
28
|
+
avatar?: StepImage;
|
|
29
|
+
rating?: number;
|
|
30
|
+
};
|
|
31
|
+
export type ImageItem = {
|
|
32
|
+
id: string;
|
|
33
|
+
image: StepImage;
|
|
34
|
+
label?: string;
|
|
35
|
+
};
|
|
36
|
+
export type QaItem = {
|
|
37
|
+
id: string;
|
|
38
|
+
question: string;
|
|
39
|
+
answer?: string;
|
|
40
|
+
};
|
|
41
|
+
export type LinkItem = {
|
|
42
|
+
id: string;
|
|
43
|
+
label: string;
|
|
44
|
+
href?: string;
|
|
45
|
+
};
|
|
46
|
+
export type ReasonItem = {
|
|
47
|
+
id: string;
|
|
48
|
+
label: string;
|
|
49
|
+
description?: string;
|
|
50
|
+
icon?: string;
|
|
51
|
+
};
|
|
52
|
+
export type StoreLinkItem = {
|
|
53
|
+
id: string;
|
|
54
|
+
eyebrow: string;
|
|
55
|
+
title: string;
|
|
56
|
+
href: string;
|
|
57
|
+
variant?: string;
|
|
58
|
+
};
|
|
59
|
+
export type PlanPresentationItem = {
|
|
60
|
+
id: string;
|
|
61
|
+
title?: string;
|
|
62
|
+
description?: string;
|
|
63
|
+
badge?: string;
|
|
64
|
+
};
|
|
65
|
+
export type StepChoiceItem = ChoiceItem;
|
|
66
|
+
export type StepInfoItem = InfoItem;
|
|
67
|
+
export type StepQuoteItem = QuoteItem;
|
|
68
|
+
export type StepImageItem = ImageItem;
|
|
69
|
+
export type StepQaItem = QaItem;
|
|
70
|
+
export type StepLinkItem = LinkItem;
|
|
71
|
+
export type StepReasonItem = ReasonItem;
|
|
72
|
+
export type StepStoreLinkItem = StoreLinkItem;
|
|
73
|
+
export type StepPlanPresentationItem = PlanPresentationItem;
|
|
74
|
+
export type LocalizedStepContent<TLocaleContent, TLocaleCode extends string = string> = {
|
|
75
|
+
defaultLocale: TLocaleCode;
|
|
76
|
+
locales: Record<TLocaleCode, TLocaleContent>;
|
|
77
|
+
};
|
|
78
|
+
export type CountryPricingOverride<TOfferKey extends string = string> = {
|
|
79
|
+
defaultOfferKey?: TOfferKey;
|
|
80
|
+
offerOrder?: readonly TOfferKey[];
|
|
81
|
+
};
|
|
82
|
+
export type CountryPricingProfile<TOfferKey extends string = string> = {
|
|
83
|
+
defaultOfferKey: TOfferKey;
|
|
84
|
+
defaultOfferOrder: readonly TOfferKey[];
|
|
85
|
+
countryOverrides?: Record<string, CountryPricingOverride<TOfferKey>>;
|
|
86
|
+
};
|
|
87
|
+
export type ResolvedCountryPricing<TOfferKey extends string = string> = {
|
|
88
|
+
countryCode: string | null;
|
|
89
|
+
defaultOfferKey: TOfferKey;
|
|
90
|
+
offerOrder: readonly TOfferKey[];
|
|
91
|
+
};
|
|
92
|
+
export type StepEditorFieldKind = 'text' | 'textLines' | 'textarea' | 'image' | 'boolean' | 'select' | 'list' | 'pricingProfile';
|
|
93
|
+
export type StepEditorListPreset = 'choiceItems' | 'infoItems' | 'quoteItems' | 'imageItems' | 'qaItems' | 'linkItems' | 'reasonItems' | 'storeLinkItems' | 'planPresentationItems';
|
|
94
|
+
export type StepEditorSelectOption = {
|
|
95
|
+
label: string;
|
|
96
|
+
value: string;
|
|
97
|
+
};
|
|
98
|
+
export type StepEditorFieldVariableSource = 'plan' | 'user';
|
|
99
|
+
export type StepEditorVariableMapping = {
|
|
100
|
+
id: string;
|
|
101
|
+
label: string;
|
|
102
|
+
token: `user.${string}`;
|
|
103
|
+
sourcePath: string;
|
|
104
|
+
};
|
|
105
|
+
export type StepEditorField = {
|
|
106
|
+
id: string;
|
|
107
|
+
label: string;
|
|
108
|
+
kind: StepEditorFieldKind;
|
|
109
|
+
path: string;
|
|
110
|
+
supportsVariables?: boolean;
|
|
111
|
+
variableSources?: readonly StepEditorFieldVariableSource[];
|
|
112
|
+
preset?: StepEditorListPreset;
|
|
113
|
+
options?: readonly StepEditorSelectOption[];
|
|
114
|
+
};
|
|
115
|
+
export type StepEditorSection = {
|
|
116
|
+
id: string;
|
|
117
|
+
label: string;
|
|
118
|
+
variables?: readonly StepEditorVariableMapping[];
|
|
119
|
+
fields: readonly StepEditorField[];
|
|
120
|
+
};
|
|
121
|
+
export declare function validateStepContentDefinition<TLocaleContent, TLocaleCode extends string = string>(definition: LocalizedStepContent<TLocaleContent, TLocaleCode>): LocalizedStepContent<TLocaleContent, TLocaleCode>;
|
|
122
|
+
export declare function validateCountryPricingProfile<TOfferKey extends string>(profile: CountryPricingProfile<TOfferKey>): CountryPricingProfile<TOfferKey>;
|
|
123
|
+
export declare function normalizeCountryPricingProfile<TOfferKey extends string>(profile: CountryPricingProfile<TOfferKey>): CountryPricingProfile<TOfferKey>;
|
|
124
|
+
export declare function validateStepEditorSections(sections: readonly StepEditorSection[]): readonly StepEditorSection[];
|
|
125
|
+
export declare const resolveUniversalContentVariables: <TContent>(content: TContent, variables: Record<string, string>) => TContent;
|
|
126
|
+
export declare const collectStepEditorVariables: (sections: readonly StepEditorSection[]) => readonly StepEditorVariableMapping[];
|
|
127
|
+
export declare const resolveStepEditorUserVariableValues: (sections: readonly StepEditorSection[], answers: Record<string, unknown>) => Record<string, string>;
|
|
128
|
+
export declare const resolveStepContentVariables: <TContent extends Record<string, unknown>>(content: TContent, sections: readonly StepEditorSection[], variables: Record<string, string>) => TContent;
|
|
129
|
+
export declare function resolveLocalizedStepContent<TLocaleContent>(definition: LocalizedStepContent<TLocaleContent>, requestedLocale?: string | null): TLocaleContent;
|
|
130
|
+
export declare function resolveCountryPricing<TOfferKey extends string>(profile: CountryPricingProfile<TOfferKey>, requestedCountryCode?: string | null): ResolvedCountryPricing<TOfferKey>;
|
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
const PREVIEW_DEFINITION_OVERRIDE_STORE_KEY = '__funnelsgrovePreviewDefinitionOverrides';
|
|
2
|
+
const isValidFieldKind = (value) => {
|
|
3
|
+
return (value === 'text' ||
|
|
4
|
+
value === 'textLines' ||
|
|
5
|
+
value === 'textarea' ||
|
|
6
|
+
value === 'image' ||
|
|
7
|
+
value === 'boolean' ||
|
|
8
|
+
value === 'select' ||
|
|
9
|
+
value === 'list' ||
|
|
10
|
+
value === 'pricingProfile');
|
|
11
|
+
};
|
|
12
|
+
const isValidListPreset = (value) => {
|
|
13
|
+
return (value === 'choiceItems' ||
|
|
14
|
+
value === 'infoItems' ||
|
|
15
|
+
value === 'quoteItems' ||
|
|
16
|
+
value === 'imageItems' ||
|
|
17
|
+
value === 'qaItems' ||
|
|
18
|
+
value === 'linkItems' ||
|
|
19
|
+
value === 'reasonItems' ||
|
|
20
|
+
value === 'storeLinkItems' ||
|
|
21
|
+
value === 'planPresentationItems');
|
|
22
|
+
};
|
|
23
|
+
const isValidVariableSource = (value) => {
|
|
24
|
+
return value === 'plan' || value === 'user';
|
|
25
|
+
};
|
|
26
|
+
const isObjectRecord = (value) => {
|
|
27
|
+
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
28
|
+
};
|
|
29
|
+
export function validateStepContentDefinition(definition) {
|
|
30
|
+
const localeEntries = Object.entries(definition.locales);
|
|
31
|
+
if (localeEntries.length === 0) {
|
|
32
|
+
throw new Error('Step content definition requires at least one locale.');
|
|
33
|
+
}
|
|
34
|
+
const normalizedDefaultLocale = normalizeLocaleCode(definition.defaultLocale);
|
|
35
|
+
if (!normalizedDefaultLocale) {
|
|
36
|
+
throw new Error('Step content definition requires a valid default locale.');
|
|
37
|
+
}
|
|
38
|
+
const hasDefaultLocale = localeEntries.some(([localeCode]) => normalizeLocaleCode(localeCode) === normalizedDefaultLocale);
|
|
39
|
+
if (!hasDefaultLocale) {
|
|
40
|
+
throw new Error('Step content definition requires the default locale to exist in locales.');
|
|
41
|
+
}
|
|
42
|
+
return definition;
|
|
43
|
+
}
|
|
44
|
+
export function validateCountryPricingProfile(profile) {
|
|
45
|
+
var _a;
|
|
46
|
+
if (profile.defaultOfferOrder.length === 0) {
|
|
47
|
+
throw new Error('Country pricing profile requires at least one default offer.');
|
|
48
|
+
}
|
|
49
|
+
if (!profile.defaultOfferOrder.includes(profile.defaultOfferKey)) {
|
|
50
|
+
throw new Error('Country pricing profile default offer must exist in the default order.');
|
|
51
|
+
}
|
|
52
|
+
for (const [countryCode, override] of Object.entries((_a = profile.countryOverrides) !== null && _a !== void 0 ? _a : {})) {
|
|
53
|
+
if (!normalizeCountryCode(countryCode)) {
|
|
54
|
+
throw new Error(`Country pricing profile contains an invalid country code: ${countryCode}`);
|
|
55
|
+
}
|
|
56
|
+
if (override.defaultOfferKey &&
|
|
57
|
+
override.offerOrder &&
|
|
58
|
+
!override.offerOrder.includes(override.defaultOfferKey)) {
|
|
59
|
+
throw new Error(`Country pricing override default offer must exist in the offer order for ${countryCode}.`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return profile;
|
|
63
|
+
}
|
|
64
|
+
const normalizeOfferKeyList = (value) => {
|
|
65
|
+
return Array.from(new Set((value !== null && value !== void 0 ? value : [])
|
|
66
|
+
.map((offerKey) => `${offerKey}`.trim())
|
|
67
|
+
.filter(Boolean)));
|
|
68
|
+
};
|
|
69
|
+
const normalizeOfferKey = (value) => {
|
|
70
|
+
return (value ? `${value}`.trim() : '');
|
|
71
|
+
};
|
|
72
|
+
export function normalizeCountryPricingProfile(profile) {
|
|
73
|
+
var _a, _b, _c;
|
|
74
|
+
let defaultOfferOrder = normalizeOfferKeyList(profile.defaultOfferOrder);
|
|
75
|
+
let defaultOfferKey = normalizeOfferKey(profile.defaultOfferKey);
|
|
76
|
+
if (!defaultOfferKey && defaultOfferOrder.length > 0) {
|
|
77
|
+
defaultOfferKey = (_a = defaultOfferOrder[0]) !== null && _a !== void 0 ? _a : '';
|
|
78
|
+
}
|
|
79
|
+
if (defaultOfferKey && !defaultOfferOrder.includes(defaultOfferKey)) {
|
|
80
|
+
defaultOfferOrder = [defaultOfferKey, ...defaultOfferOrder];
|
|
81
|
+
}
|
|
82
|
+
if (defaultOfferOrder.length === 0 && defaultOfferKey) {
|
|
83
|
+
defaultOfferOrder = [defaultOfferKey];
|
|
84
|
+
}
|
|
85
|
+
if (defaultOfferOrder.length === 0) {
|
|
86
|
+
defaultOfferOrder = ['default'];
|
|
87
|
+
}
|
|
88
|
+
if (!defaultOfferKey) {
|
|
89
|
+
defaultOfferKey = (_b = defaultOfferOrder[0]) !== null && _b !== void 0 ? _b : 'default';
|
|
90
|
+
}
|
|
91
|
+
const normalizedOverrides = Object.fromEntries(Object.entries((_c = profile.countryOverrides) !== null && _c !== void 0 ? _c : {}).flatMap(([countryCodeRaw, overrideRaw]) => {
|
|
92
|
+
var _a;
|
|
93
|
+
const countryCode = normalizeCountryCode(countryCodeRaw);
|
|
94
|
+
if (!countryCode) {
|
|
95
|
+
return [];
|
|
96
|
+
}
|
|
97
|
+
const override = overrideRaw !== null && overrideRaw !== void 0 ? overrideRaw : {};
|
|
98
|
+
const hasExplicitOfferOrder = Array.isArray(override.offerOrder) && override.offerOrder.length > 0;
|
|
99
|
+
let offerOrder = normalizeOfferKeyList(override.offerOrder);
|
|
100
|
+
let offerKey = normalizeOfferKey(override.defaultOfferKey);
|
|
101
|
+
if (!offerKey && offerOrder.length > 0) {
|
|
102
|
+
offerKey = (_a = offerOrder[0]) !== null && _a !== void 0 ? _a : '';
|
|
103
|
+
}
|
|
104
|
+
if (offerKey && hasExplicitOfferOrder && !offerOrder.includes(offerKey)) {
|
|
105
|
+
offerOrder = [offerKey, ...offerOrder];
|
|
106
|
+
}
|
|
107
|
+
return [
|
|
108
|
+
[
|
|
109
|
+
countryCode,
|
|
110
|
+
Object.assign(Object.assign({}, (offerKey ? { defaultOfferKey: offerKey } : {})), (hasExplicitOfferOrder && offerOrder.length > 0 ? { offerOrder } : {})),
|
|
111
|
+
],
|
|
112
|
+
];
|
|
113
|
+
}));
|
|
114
|
+
return {
|
|
115
|
+
defaultOfferKey: defaultOfferKey,
|
|
116
|
+
defaultOfferOrder,
|
|
117
|
+
countryOverrides: normalizedOverrides,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
export function validateStepEditorSections(sections) {
|
|
121
|
+
var _a, _b, _c;
|
|
122
|
+
if (sections.length === 0) {
|
|
123
|
+
throw new Error('Step editor definition requires at least one section.');
|
|
124
|
+
}
|
|
125
|
+
for (const section of sections) {
|
|
126
|
+
if (!section.id.trim() || !section.label.trim()) {
|
|
127
|
+
throw new Error('Step editor sections require non-empty ids and labels.');
|
|
128
|
+
}
|
|
129
|
+
if (section.fields.length === 0) {
|
|
130
|
+
throw new Error(`Step editor section "${section.id}" requires at least one field.`);
|
|
131
|
+
}
|
|
132
|
+
for (const field of section.fields) {
|
|
133
|
+
if (!field.id.trim() || !field.label.trim() || !field.path.trim()) {
|
|
134
|
+
throw new Error(`Step editor field in section "${section.id}" is missing required text.`);
|
|
135
|
+
}
|
|
136
|
+
if (!isValidFieldKind(field.kind)) {
|
|
137
|
+
throw new Error(`Unsupported step editor field kind: ${String(field.kind)}`);
|
|
138
|
+
}
|
|
139
|
+
if (field.kind === 'list' && !isValidListPreset(field.preset)) {
|
|
140
|
+
throw new Error(`List field "${field.id}" requires a valid list preset.`);
|
|
141
|
+
}
|
|
142
|
+
if (field.kind === 'select') {
|
|
143
|
+
const options = (_a = field.options) !== null && _a !== void 0 ? _a : [];
|
|
144
|
+
if (options.length === 0) {
|
|
145
|
+
throw new Error(`Select field "${field.id}" requires at least one option.`);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
if (field.supportsVariables) {
|
|
149
|
+
if (field.kind !== 'text' && field.kind !== 'textLines' && field.kind !== 'textarea') {
|
|
150
|
+
throw new Error(`Only text-capable fields can enable variables: ${field.id}`);
|
|
151
|
+
}
|
|
152
|
+
const variableSources = (_b = field.variableSources) !== null && _b !== void 0 ? _b : ['plan', 'user'];
|
|
153
|
+
if (variableSources.length === 0 || variableSources.some((value) => !isValidVariableSource(value))) {
|
|
154
|
+
throw new Error(`Variable-enabled field "${field.id}" requires valid variable sources.`);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
for (const variable of (_c = section.variables) !== null && _c !== void 0 ? _c : []) {
|
|
159
|
+
if (!variable.id.trim() || !variable.label.trim() || !variable.sourcePath.trim()) {
|
|
160
|
+
throw new Error(`Step editor variable in section "${section.id}" is missing required text.`);
|
|
161
|
+
}
|
|
162
|
+
if (!variable.token.startsWith('user.')) {
|
|
163
|
+
throw new Error(`Step editor variable "${variable.id}" must use a user.* token.`);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return sections;
|
|
168
|
+
}
|
|
169
|
+
const readValueAtPath = (value, path) => {
|
|
170
|
+
if (!path.trim()) {
|
|
171
|
+
return value;
|
|
172
|
+
}
|
|
173
|
+
return path.split('.').reduce((currentValue, segment) => {
|
|
174
|
+
if (!isObjectRecord(currentValue) || !segment) {
|
|
175
|
+
return undefined;
|
|
176
|
+
}
|
|
177
|
+
return currentValue[segment];
|
|
178
|
+
}, value);
|
|
179
|
+
};
|
|
180
|
+
const updateValueAtPath = (value, path, nextValue) => {
|
|
181
|
+
const segments = path.split('.').filter(Boolean);
|
|
182
|
+
if (segments.length === 0) {
|
|
183
|
+
return nextValue;
|
|
184
|
+
}
|
|
185
|
+
const [head, ...tail] = segments;
|
|
186
|
+
const currentRecord = isObjectRecord(value) ? value : {};
|
|
187
|
+
if (tail.length === 0) {
|
|
188
|
+
return Object.assign(Object.assign({}, currentRecord), { [head]: nextValue });
|
|
189
|
+
}
|
|
190
|
+
return Object.assign(Object.assign({}, currentRecord), { [head]: updateValueAtPath(currentRecord[head], tail.join('.'), nextValue) });
|
|
191
|
+
};
|
|
192
|
+
const VARIABLE_TOKEN_PATTERN = /\{\{\s*([a-zA-Z0-9_.-]+)\s*\}\}/g;
|
|
193
|
+
const interpolateVariableString = (value, variables) => {
|
|
194
|
+
return value.replace(VARIABLE_TOKEN_PATTERN, (_, rawToken) => {
|
|
195
|
+
var _a;
|
|
196
|
+
const token = rawToken.trim();
|
|
197
|
+
return (_a = variables[token]) !== null && _a !== void 0 ? _a : '';
|
|
198
|
+
});
|
|
199
|
+
};
|
|
200
|
+
const UNIVERSAL_VARIABLE_TOKEN_PATTERN = /\{([a-zA-Z0-9_.-]+)\}/g;
|
|
201
|
+
const interpolateUniversalVariableString = (value, variables) => {
|
|
202
|
+
return value.replace(UNIVERSAL_VARIABLE_TOKEN_PATTERN, (match, rawToken) => {
|
|
203
|
+
var _a;
|
|
204
|
+
const token = rawToken.trim();
|
|
205
|
+
return token in variables ? (_a = variables[token]) !== null && _a !== void 0 ? _a : '' : match;
|
|
206
|
+
});
|
|
207
|
+
};
|
|
208
|
+
export const resolveUniversalContentVariables = (content, variables) => {
|
|
209
|
+
if (typeof content === 'string') {
|
|
210
|
+
return interpolateUniversalVariableString(content, variables);
|
|
211
|
+
}
|
|
212
|
+
if (Array.isArray(content)) {
|
|
213
|
+
return content.map((item) => resolveUniversalContentVariables(item, variables));
|
|
214
|
+
}
|
|
215
|
+
if (isObjectRecord(content)) {
|
|
216
|
+
return Object.fromEntries(Object.entries(content).map(([key, value]) => [
|
|
217
|
+
key,
|
|
218
|
+
resolveUniversalContentVariables(value, variables),
|
|
219
|
+
]));
|
|
220
|
+
}
|
|
221
|
+
return content;
|
|
222
|
+
};
|
|
223
|
+
export const collectStepEditorVariables = (sections) => {
|
|
224
|
+
return sections.flatMap((section) => { var _a; return (_a = section.variables) !== null && _a !== void 0 ? _a : []; });
|
|
225
|
+
};
|
|
226
|
+
export const resolveStepEditorUserVariableValues = (sections, answers) => {
|
|
227
|
+
return Object.fromEntries(collectStepEditorVariables(sections).flatMap((mapping) => {
|
|
228
|
+
const rawValue = readValueAtPath(answers, mapping.sourcePath);
|
|
229
|
+
if (typeof rawValue === 'string') {
|
|
230
|
+
return [[mapping.token, rawValue]];
|
|
231
|
+
}
|
|
232
|
+
if (Array.isArray(rawValue)) {
|
|
233
|
+
const labels = rawValue
|
|
234
|
+
.map((item) => (typeof item === 'string' ? item.trim() : ''))
|
|
235
|
+
.filter(Boolean);
|
|
236
|
+
return labels.length > 0 ? [[mapping.token, labels.join(' + ')]] : [];
|
|
237
|
+
}
|
|
238
|
+
if (typeof rawValue === 'number' || typeof rawValue === 'boolean') {
|
|
239
|
+
return [[mapping.token, String(rawValue)]];
|
|
240
|
+
}
|
|
241
|
+
return [];
|
|
242
|
+
}));
|
|
243
|
+
};
|
|
244
|
+
export const resolveStepContentVariables = (content, sections, variables) => {
|
|
245
|
+
let nextContent = content;
|
|
246
|
+
for (const section of sections) {
|
|
247
|
+
for (const field of section.fields) {
|
|
248
|
+
if (!field.supportsVariables) {
|
|
249
|
+
continue;
|
|
250
|
+
}
|
|
251
|
+
const currentValue = readValueAtPath(nextContent, field.path);
|
|
252
|
+
if (typeof currentValue === 'string') {
|
|
253
|
+
nextContent = updateValueAtPath(nextContent, field.path, interpolateVariableString(currentValue, variables));
|
|
254
|
+
continue;
|
|
255
|
+
}
|
|
256
|
+
if (field.kind === 'textLines' && Array.isArray(currentValue)) {
|
|
257
|
+
nextContent = updateValueAtPath(nextContent, field.path, currentValue.map((line) => typeof line === 'string' ? interpolateVariableString(line, variables) : line));
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
return nextContent;
|
|
262
|
+
};
|
|
263
|
+
const normalizeLocaleCode = (localeCode) => {
|
|
264
|
+
if (typeof localeCode !== 'string') {
|
|
265
|
+
return null;
|
|
266
|
+
}
|
|
267
|
+
const trimmed = localeCode.trim();
|
|
268
|
+
if (!trimmed) {
|
|
269
|
+
return null;
|
|
270
|
+
}
|
|
271
|
+
return trimmed.replace(/_/g, '-').toLowerCase();
|
|
272
|
+
};
|
|
273
|
+
const normalizeCountryCode = (countryCode) => {
|
|
274
|
+
if (typeof countryCode !== 'string') {
|
|
275
|
+
return null;
|
|
276
|
+
}
|
|
277
|
+
const trimmed = countryCode.trim();
|
|
278
|
+
if (!trimmed) {
|
|
279
|
+
return null;
|
|
280
|
+
}
|
|
281
|
+
return trimmed.toUpperCase();
|
|
282
|
+
};
|
|
283
|
+
const getPreviewDefinitionOverrideState = () => {
|
|
284
|
+
var _a;
|
|
285
|
+
const globalStore = globalThis[PREVIEW_DEFINITION_OVERRIDE_STORE_KEY];
|
|
286
|
+
return (_a = globalStore === null || globalStore === void 0 ? void 0 : globalStore.state) !== null && _a !== void 0 ? _a : null;
|
|
287
|
+
};
|
|
288
|
+
const getPreviewStepIdFromWindowLocation = () => {
|
|
289
|
+
if (typeof window === 'undefined') {
|
|
290
|
+
return null;
|
|
291
|
+
}
|
|
292
|
+
const stepFromSearch = (() => {
|
|
293
|
+
try {
|
|
294
|
+
const value = new URLSearchParams(window.location.search).get('step');
|
|
295
|
+
return typeof value === 'string' ? value.trim() : '';
|
|
296
|
+
}
|
|
297
|
+
catch (_a) {
|
|
298
|
+
return '';
|
|
299
|
+
}
|
|
300
|
+
})();
|
|
301
|
+
if (stepFromSearch) {
|
|
302
|
+
return stepFromSearch;
|
|
303
|
+
}
|
|
304
|
+
const pathSegments = window.location.pathname.split('/').filter(Boolean);
|
|
305
|
+
if (pathSegments[0] === 'builder-preview' && pathSegments[2]) {
|
|
306
|
+
return pathSegments[2];
|
|
307
|
+
}
|
|
308
|
+
if (pathSegments[0] === 'published' && pathSegments[1] === 'f' && pathSegments[3]) {
|
|
309
|
+
return pathSegments[3];
|
|
310
|
+
}
|
|
311
|
+
if (pathSegments[0] === 'preview' && pathSegments[1] === 's' && pathSegments[3]) {
|
|
312
|
+
return pathSegments[3];
|
|
313
|
+
}
|
|
314
|
+
return null;
|
|
315
|
+
};
|
|
316
|
+
export function resolveLocalizedStepContent(definition, requestedLocale) {
|
|
317
|
+
var _a;
|
|
318
|
+
const validatedDefinition = validateStepContentDefinition(definition);
|
|
319
|
+
const entries = Object.entries(validatedDefinition.locales);
|
|
320
|
+
const previewStepId = getPreviewStepIdFromWindowLocation();
|
|
321
|
+
const previewStepOverrides = previewStepId && ((_a = getPreviewDefinitionOverrideState()) === null || _a === void 0 ? void 0 : _a.contentByStepId[previewStepId])
|
|
322
|
+
? getPreviewDefinitionOverrideState().contentByStepId[previewStepId]
|
|
323
|
+
: null;
|
|
324
|
+
const normalizedRequestedLocale = normalizeLocaleCode(requestedLocale);
|
|
325
|
+
const localeMap = new Map();
|
|
326
|
+
for (const [localeCode, localeContent] of entries) {
|
|
327
|
+
const normalizedLocale = normalizeLocaleCode(localeCode);
|
|
328
|
+
if (!normalizedLocale) {
|
|
329
|
+
continue;
|
|
330
|
+
}
|
|
331
|
+
localeMap.set(normalizedLocale, localeContent);
|
|
332
|
+
}
|
|
333
|
+
if (previewStepOverrides) {
|
|
334
|
+
for (const [localeCode, localeContent] of Object.entries(previewStepOverrides)) {
|
|
335
|
+
const normalizedLocale = normalizeLocaleCode(localeCode);
|
|
336
|
+
if (!normalizedLocale) {
|
|
337
|
+
continue;
|
|
338
|
+
}
|
|
339
|
+
localeMap.set(normalizedLocale, localeContent);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
if (normalizedRequestedLocale) {
|
|
343
|
+
const exactMatch = localeMap.get(normalizedRequestedLocale);
|
|
344
|
+
if (exactMatch) {
|
|
345
|
+
return exactMatch;
|
|
346
|
+
}
|
|
347
|
+
const baseLocale = normalizedRequestedLocale.split('-')[0];
|
|
348
|
+
if (baseLocale) {
|
|
349
|
+
const baseMatch = localeMap.get(baseLocale);
|
|
350
|
+
if (baseMatch) {
|
|
351
|
+
return baseMatch;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
const normalizedDefaultLocale = normalizeLocaleCode(validatedDefinition.defaultLocale);
|
|
356
|
+
if (normalizedDefaultLocale) {
|
|
357
|
+
const defaultMatch = localeMap.get(normalizedDefaultLocale);
|
|
358
|
+
if (defaultMatch) {
|
|
359
|
+
return defaultMatch;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
return entries[0][1];
|
|
363
|
+
}
|
|
364
|
+
export function resolveCountryPricing(profile, requestedCountryCode) {
|
|
365
|
+
var _a, _b, _c, _d;
|
|
366
|
+
const previewStepId = getPreviewStepIdFromWindowLocation();
|
|
367
|
+
const previewPricingOverride = previewStepId && ((_a = getPreviewDefinitionOverrideState()) === null || _a === void 0 ? void 0 : _a.pricingByStepId[previewStepId])
|
|
368
|
+
? getPreviewDefinitionOverrideState().pricingByStepId[previewStepId]
|
|
369
|
+
: null;
|
|
370
|
+
const validatedProfile = validateCountryPricingProfile(normalizeCountryPricingProfile(previewPricingOverride || profile));
|
|
371
|
+
const countryCode = normalizeCountryCode(requestedCountryCode);
|
|
372
|
+
const overrideEntry = countryCode
|
|
373
|
+
? Object.entries((_b = validatedProfile.countryOverrides) !== null && _b !== void 0 ? _b : {}).find(([overrideCountryCode]) => normalizeCountryCode(overrideCountryCode) === countryCode)
|
|
374
|
+
: null;
|
|
375
|
+
const override = overrideEntry === null || overrideEntry === void 0 ? void 0 : overrideEntry[1];
|
|
376
|
+
return {
|
|
377
|
+
countryCode,
|
|
378
|
+
defaultOfferKey: (_c = override === null || override === void 0 ? void 0 : override.defaultOfferKey) !== null && _c !== void 0 ? _c : validatedProfile.defaultOfferKey,
|
|
379
|
+
offerOrder: (_d = override === null || override === void 0 ? void 0 : override.offerOrder) !== null && _d !== void 0 ? _d : validatedProfile.defaultOfferOrder,
|
|
380
|
+
};
|
|
381
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,21 +1,33 @@
|
|
|
1
|
-
export * from './config/builder-preview.protocol';
|
|
2
|
-
export * from './config/
|
|
3
|
-
export * from './config/funnel
|
|
4
|
-
export * from './
|
|
5
|
-
export * from './
|
|
6
|
-
export * from './
|
|
7
|
-
export * from './runtime/
|
|
8
|
-
export * from './runtime/
|
|
9
|
-
export * from './runtime/
|
|
10
|
-
export * from './runtime/
|
|
11
|
-
export * from './runtime/
|
|
12
|
-
export * from './
|
|
13
|
-
export * from './
|
|
14
|
-
export * from './
|
|
15
|
-
export * from './
|
|
16
|
-
export * from './
|
|
17
|
-
export * from './
|
|
18
|
-
export * from './
|
|
19
|
-
export * from './
|
|
20
|
-
export * from './
|
|
21
|
-
export * from './
|
|
1
|
+
export * from './config/builder-preview.protocol.js';
|
|
2
|
+
export * from './config/env.config.js';
|
|
3
|
+
export * from './config/funnel.manifest.types.js';
|
|
4
|
+
export * from './config/funnel-theme.js';
|
|
5
|
+
export * from './config/font-config.js';
|
|
6
|
+
export * from './content/step-content.js';
|
|
7
|
+
export * from './runtime/experiment-assignment.js';
|
|
8
|
+
export * from './runtime/browser-helpers.js';
|
|
9
|
+
export * from './runtime/funnel-flow.js';
|
|
10
|
+
export * from './runtime/funnel-attribution.js';
|
|
11
|
+
export * from './runtime/funnel-manifest.validation.js';
|
|
12
|
+
export * from './runtime/funnel-runtime.js';
|
|
13
|
+
export * from './runtime/posthog-flags.js';
|
|
14
|
+
export * from './runtime/use-funnel-flow-controller.js';
|
|
15
|
+
export * from './runtime/preview-bridge.js';
|
|
16
|
+
export * from './runtime/preview-definition-overrides.js';
|
|
17
|
+
export * from './runtime/route-resolver.js';
|
|
18
|
+
export * from './runtime/subscription-handoff.js';
|
|
19
|
+
export * from './services/api.service.js';
|
|
20
|
+
export * from './services/funnel-state.service.js';
|
|
21
|
+
export * from './services/logger.js';
|
|
22
|
+
export * from './services/preview-frame.service.js';
|
|
23
|
+
export * from './services/public-env.js';
|
|
24
|
+
export * from './services/runtime-api.config.js';
|
|
25
|
+
export * from './services/runtime-mode.service.js';
|
|
26
|
+
export * from './sdk/userAnswers.js';
|
|
27
|
+
export * from './components/FunnelContext.js';
|
|
28
|
+
export * from './components/FunnelEditorPanel.js';
|
|
29
|
+
export * from './components/ManageSubscriptionScreen.js';
|
|
30
|
+
export * from './components/RuntimeDevInfoBox.js';
|
|
31
|
+
export * from './components/SubscriptionHandoffScreen.js';
|
|
32
|
+
export * from './components/shared/PrimaryButton.js';
|
|
33
|
+
export * from './steps/types.js';
|
package/dist/index.js
CHANGED
|
@@ -1,21 +1,33 @@
|
|
|
1
|
-
export * from './config/builder-preview.protocol';
|
|
2
|
-
export * from './config/
|
|
3
|
-
export * from './config/funnel
|
|
4
|
-
export * from './
|
|
5
|
-
export * from './
|
|
6
|
-
export * from './
|
|
7
|
-
export * from './runtime/
|
|
8
|
-
export * from './runtime/
|
|
9
|
-
export * from './runtime/
|
|
10
|
-
export * from './runtime/
|
|
11
|
-
export * from './runtime/
|
|
12
|
-
export * from './
|
|
13
|
-
export * from './
|
|
14
|
-
export * from './
|
|
15
|
-
export * from './
|
|
16
|
-
export * from './
|
|
17
|
-
export * from './
|
|
18
|
-
export * from './
|
|
19
|
-
export * from './
|
|
20
|
-
export * from './
|
|
21
|
-
export * from './
|
|
1
|
+
export * from './config/builder-preview.protocol.js';
|
|
2
|
+
export * from './config/env.config.js';
|
|
3
|
+
export * from './config/funnel.manifest.types.js';
|
|
4
|
+
export * from './config/funnel-theme.js';
|
|
5
|
+
export * from './config/font-config.js';
|
|
6
|
+
export * from './content/step-content.js';
|
|
7
|
+
export * from './runtime/experiment-assignment.js';
|
|
8
|
+
export * from './runtime/browser-helpers.js';
|
|
9
|
+
export * from './runtime/funnel-flow.js';
|
|
10
|
+
export * from './runtime/funnel-attribution.js';
|
|
11
|
+
export * from './runtime/funnel-manifest.validation.js';
|
|
12
|
+
export * from './runtime/funnel-runtime.js';
|
|
13
|
+
export * from './runtime/posthog-flags.js';
|
|
14
|
+
export * from './runtime/use-funnel-flow-controller.js';
|
|
15
|
+
export * from './runtime/preview-bridge.js';
|
|
16
|
+
export * from './runtime/preview-definition-overrides.js';
|
|
17
|
+
export * from './runtime/route-resolver.js';
|
|
18
|
+
export * from './runtime/subscription-handoff.js';
|
|
19
|
+
export * from './services/api.service.js';
|
|
20
|
+
export * from './services/funnel-state.service.js';
|
|
21
|
+
export * from './services/logger.js';
|
|
22
|
+
export * from './services/preview-frame.service.js';
|
|
23
|
+
export * from './services/public-env.js';
|
|
24
|
+
export * from './services/runtime-api.config.js';
|
|
25
|
+
export * from './services/runtime-mode.service.js';
|
|
26
|
+
export * from './sdk/userAnswers.js';
|
|
27
|
+
export * from './components/FunnelContext.js';
|
|
28
|
+
export * from './components/FunnelEditorPanel.js';
|
|
29
|
+
export * from './components/ManageSubscriptionScreen.js';
|
|
30
|
+
export * from './components/RuntimeDevInfoBox.js';
|
|
31
|
+
export * from './components/SubscriptionHandoffScreen.js';
|
|
32
|
+
export * from './components/shared/PrimaryButton.js';
|
|
33
|
+
export * from './steps/types.js';
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export declare const canUseDom: () => boolean;
|
|
2
2
|
export declare const readWindowStorageValue: (storageKey: string) => string | null;
|
|
3
3
|
export declare const writeWindowStorageValue: (storageKey: string, value: string) => void;
|
|
4
|
+
export declare const removeWindowStorageValue: (storageKey: string) => void;
|
|
4
5
|
export declare const dispatchWindowCustomEvent: <Detail>(eventType: string, detail: Detail) => void;
|
|
5
6
|
export declare const buildHostedStepLocation: (input: {
|
|
6
7
|
currentHref: string;
|
|
@@ -23,6 +23,17 @@ export const writeWindowStorageValue = (storageKey, value) => {
|
|
|
23
23
|
// ignore storage access failures
|
|
24
24
|
}
|
|
25
25
|
};
|
|
26
|
+
export const removeWindowStorageValue = (storageKey) => {
|
|
27
|
+
if (!canUseDom()) {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
try {
|
|
31
|
+
window.localStorage.removeItem(storageKey);
|
|
32
|
+
}
|
|
33
|
+
catch (_a) {
|
|
34
|
+
// ignore storage access failures
|
|
35
|
+
}
|
|
36
|
+
};
|
|
26
37
|
export const dispatchWindowCustomEvent = (eventType, detail) => {
|
|
27
38
|
if (!canUseDom()) {
|
|
28
39
|
return;
|
|
@@ -42,6 +53,9 @@ const getHostedFunnelBaseSegments = (pathname) => {
|
|
|
42
53
|
if (pathSegments[0] === 'catalog' && pathSegments[1]) {
|
|
43
54
|
return pathSegments.slice(0, 2);
|
|
44
55
|
}
|
|
56
|
+
if (pathSegments[0] === 'builder-preview' && pathSegments[1]) {
|
|
57
|
+
return pathSegments.slice(0, 2);
|
|
58
|
+
}
|
|
45
59
|
return [];
|
|
46
60
|
};
|
|
47
61
|
export const buildHostedStepLocation = (input) => {
|