@namiml/sdk-core 3.4.3-rc.202606180430 → 3.4.3
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.cjs +227 -2
- package/dist/index.d.ts +115 -4
- package/dist/index.mjs +226 -3
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -98,7 +98,7 @@ const {
|
|
|
98
98
|
// version — stamped by scripts/version.sh
|
|
99
99
|
NAMI_SDK_VERSION: exports.NAMI_SDK_VERSION = "3.4.3",
|
|
100
100
|
// full package version including dev suffix — stamped by scripts/version.sh
|
|
101
|
-
NAMI_SDK_PACKAGE_VERSION: exports.NAMI_SDK_PACKAGE_VERSION = "3.4.3
|
|
101
|
+
NAMI_SDK_PACKAGE_VERSION: exports.NAMI_SDK_PACKAGE_VERSION = "3.4.3",
|
|
102
102
|
// environments
|
|
103
103
|
PRODUCTION: exports.PRODUCTION = "production", DEVELOPMENT: exports.DEVELOPMENT = "development",
|
|
104
104
|
// error messages
|
|
@@ -3193,6 +3193,22 @@ class StorageService {
|
|
|
3193
3193
|
.filter(key => key.includes(exports.CUSTOMER_ATTRIBUTES_KEY_PREFIX))
|
|
3194
3194
|
.map(key => key.replace(exports.CUSTOMER_ATTRIBUTES_KEY_PREFIX, ""));
|
|
3195
3195
|
}
|
|
3196
|
+
/**
|
|
3197
|
+
* All persisted customer attributes as a `{ key: value }` map. Used to seed
|
|
3198
|
+
* `PaywallState.customer` when a paywall screen initializes, so attributes set
|
|
3199
|
+
* on an earlier flow screen (e.g. via setTagsFromForm) are available to the
|
|
3200
|
+
* `${customer.x}` smart-text resolver on subsequent screens.
|
|
3201
|
+
*/
|
|
3202
|
+
getAllCustomerAttributesMap() {
|
|
3203
|
+
const out = {};
|
|
3204
|
+
this.getAllCustomerAttributesKeys().forEach((key) => {
|
|
3205
|
+
const value = this.getCustomerAttribute(key);
|
|
3206
|
+
if (value !== null && value !== undefined) {
|
|
3207
|
+
out[key] = String(value);
|
|
3208
|
+
}
|
|
3209
|
+
});
|
|
3210
|
+
return out;
|
|
3211
|
+
}
|
|
3196
3212
|
clearCustomerAttribute(attribute) {
|
|
3197
3213
|
const key = exports.CUSTOMER_ATTRIBUTES_KEY_PREFIX + attribute;
|
|
3198
3214
|
const itemExists = this.getItem(key);
|
|
@@ -11317,6 +11333,8 @@ const initialState = {
|
|
|
11317
11333
|
groups: [],
|
|
11318
11334
|
selectedProducts: {},
|
|
11319
11335
|
formStates: {},
|
|
11336
|
+
formFieldErrors: {},
|
|
11337
|
+
formFieldValidators: {},
|
|
11320
11338
|
timerStates: {},
|
|
11321
11339
|
currentPage: 'page1',
|
|
11322
11340
|
pageHistory: ['page1'],
|
|
@@ -11419,6 +11437,11 @@ class PaywallState extends SimpleEventTarget {
|
|
|
11419
11437
|
this.state = cloneDeep(initialState);
|
|
11420
11438
|
const profile = storageService.getNamiProfile();
|
|
11421
11439
|
this.state.isLoggedIn = !!profile?.externalId;
|
|
11440
|
+
// Seed customer attributes persisted on earlier screens (e.g. via
|
|
11441
|
+
// setTagsFromForm / setTags) so `${customer.x}` smart text resolves on every
|
|
11442
|
+
// flow screen — each screen builds a fresh provider, so without this the
|
|
11443
|
+
// value captured on page 1 would be invisible on page 2.
|
|
11444
|
+
this.state.customer = { ...this.state.customer, ...storageService.getAllCustomerAttributesMap() };
|
|
11422
11445
|
this.setPaywall(paywall, context, campaign);
|
|
11423
11446
|
this.subscribers = new Set();
|
|
11424
11447
|
}
|
|
@@ -11456,6 +11479,41 @@ class PaywallState extends SimpleEventTarget {
|
|
|
11456
11479
|
setFormState(formId, state) {
|
|
11457
11480
|
this.setState({ ...this.state, formStates: { ...this.state.formStates, [formId]: state } });
|
|
11458
11481
|
}
|
|
11482
|
+
/**
|
|
11483
|
+
* Register the validator for a text-input field (keyed by formId). Used by the
|
|
11484
|
+
* implicit form-submit gate in NamiFlow.triggerActions and by the web element
|
|
11485
|
+
* when it mounts.
|
|
11486
|
+
*/
|
|
11487
|
+
registerFormFieldValidator(formId, validator) {
|
|
11488
|
+
this.setState({
|
|
11489
|
+
...this.state,
|
|
11490
|
+
formFieldValidators: { ...this.state.formFieldValidators, [formId]: validator },
|
|
11491
|
+
});
|
|
11492
|
+
}
|
|
11493
|
+
getFormFieldValidators() {
|
|
11494
|
+
return this.state.formFieldValidators;
|
|
11495
|
+
}
|
|
11496
|
+
/**
|
|
11497
|
+
* Replace the full set of field errors (formId → message). The gate writes the
|
|
11498
|
+
* result of validateForm here; an empty map clears all errors.
|
|
11499
|
+
*/
|
|
11500
|
+
setFormFieldErrors(errors) {
|
|
11501
|
+
this.setState({ ...this.state, formFieldErrors: { ...errors } });
|
|
11502
|
+
}
|
|
11503
|
+
setFormFieldError(formId, message) {
|
|
11504
|
+
this.setState({
|
|
11505
|
+
...this.state,
|
|
11506
|
+
formFieldErrors: { ...this.state.formFieldErrors, [formId]: message },
|
|
11507
|
+
});
|
|
11508
|
+
}
|
|
11509
|
+
clearFormFieldError(formId) {
|
|
11510
|
+
const nextErrors = { ...this.state.formFieldErrors };
|
|
11511
|
+
delete nextErrors[formId];
|
|
11512
|
+
this.setState({ ...this.state, formFieldErrors: nextErrors });
|
|
11513
|
+
}
|
|
11514
|
+
getFormFieldErrors() {
|
|
11515
|
+
return this.state.formFieldErrors;
|
|
11516
|
+
}
|
|
11459
11517
|
setTimerState(timerId, remainingSeconds, savedAt, hasEmittedCompletion) {
|
|
11460
11518
|
this.setState({
|
|
11461
11519
|
...this.state,
|
|
@@ -11674,7 +11732,12 @@ class PaywallState extends SimpleEventTarget {
|
|
|
11674
11732
|
this.emitEvent('user-interaction-changed', { enabled });
|
|
11675
11733
|
}
|
|
11676
11734
|
setUserTags(tags) {
|
|
11677
|
-
|
|
11735
|
+
// User tags ARE customer attributes for personalization — they must land in
|
|
11736
|
+
// `state.customer`, the store the `${customer.x}` smart-text resolver reads
|
|
11737
|
+
// (see valueFromSmartText: replacements.customer = state.customer). Writing a
|
|
11738
|
+
// separate `state.userTags` (read nowhere) meant setTags / setTagsFromForm
|
|
11739
|
+
// values never reached `${customer.firstName}` on a subsequent flow page.
|
|
11740
|
+
this.setState({ ...this.state, customer: { ...this.state.customer, ...tags } });
|
|
11678
11741
|
}
|
|
11679
11742
|
setCurrentSlideIndex(index) {
|
|
11680
11743
|
this.setState({ ...this.state, currentSlideIndex: index });
|
|
@@ -12551,9 +12614,11 @@ exports.NamiFlowActionFunction = void 0;
|
|
|
12551
12614
|
NamiFlowActionFunction["FLOW_ENABLED"] = "flowInteractionEnabled";
|
|
12552
12615
|
NamiFlowActionFunction["FLOW_DISABLED"] = "flowInteractionDisabled";
|
|
12553
12616
|
NamiFlowActionFunction["SET_TAGS"] = "setTags";
|
|
12617
|
+
NamiFlowActionFunction["SET_TAGS_FROM_FORM"] = "setTagsFromForm";
|
|
12554
12618
|
NamiFlowActionFunction["PAUSE"] = "flowPause";
|
|
12555
12619
|
NamiFlowActionFunction["RESUME"] = "flowResume";
|
|
12556
12620
|
NamiFlowActionFunction["SET_LAUNCH_CONTEXT"] = "setLaunchContext";
|
|
12621
|
+
NamiFlowActionFunction["SET_FLOW_PATH"] = "setFlowPath";
|
|
12557
12622
|
})(exports.NamiFlowActionFunction || (exports.NamiFlowActionFunction = {}));
|
|
12558
12623
|
const HandoffTag = {
|
|
12559
12624
|
SEQUENCE: '__handoff_sequence__',
|
|
@@ -13077,6 +13142,42 @@ class URLParams {
|
|
|
13077
13142
|
URLParams._instance = null;
|
|
13078
13143
|
const getUrlParams = () => URLParams.instance.params;
|
|
13079
13144
|
|
|
13145
|
+
/**
|
|
13146
|
+
* Coerce a single form-state value to the string form used by tags / customer
|
|
13147
|
+
* attributes / `{{ form.x }}` interpolation. Shared by the `setTagsFromForm`
|
|
13148
|
+
* flow action and FlowLiquidResolver so they stay in lockstep.
|
|
13149
|
+
*
|
|
13150
|
+
* Rules (order matters — Boolean is checked before number):
|
|
13151
|
+
* - `null` / `undefined` → skip (returns `undefined`)
|
|
13152
|
+
* - empty string `""` → skip
|
|
13153
|
+
* - `false` → "false", `true` → "true" (booleans are kept)
|
|
13154
|
+
* - `0` → "0" (zero is kept)
|
|
13155
|
+
* - integral number → no trailing ".0" (e.g. 3 → "3", 3.5 → "3.5")
|
|
13156
|
+
* - everything else → String(value)
|
|
13157
|
+
*/
|
|
13158
|
+
function formTagValue(value) {
|
|
13159
|
+
if (value === null || value === undefined) {
|
|
13160
|
+
return undefined;
|
|
13161
|
+
}
|
|
13162
|
+
if (value === '') {
|
|
13163
|
+
return undefined;
|
|
13164
|
+
}
|
|
13165
|
+
// Boolean BEFORE number: typeof true !== 'number', but be explicit so the
|
|
13166
|
+
// intent (and the false→"false" / true→"true" mapping) is unmistakable.
|
|
13167
|
+
if (typeof value === 'boolean') {
|
|
13168
|
+
return value ? 'true' : 'false';
|
|
13169
|
+
}
|
|
13170
|
+
if (typeof value === 'number') {
|
|
13171
|
+
if (!Number.isFinite(value)) {
|
|
13172
|
+
return undefined;
|
|
13173
|
+
}
|
|
13174
|
+
// JS `String` already renders integers without a trailing ".0"
|
|
13175
|
+
// (3 → "3", 3.5 → "3.5"), which is exactly the contract we want.
|
|
13176
|
+
return String(value);
|
|
13177
|
+
}
|
|
13178
|
+
return String(value);
|
|
13179
|
+
}
|
|
13180
|
+
|
|
13080
13181
|
const LIQUID_VAR_REGEX = /\{\{\s*([^}]+?)\s*\}\}/g;
|
|
13081
13182
|
/**
|
|
13082
13183
|
* Resolver for liquid-style flow variables, without requiring namespace prefixes.
|
|
@@ -13101,6 +13202,13 @@ class FlowLiquidResolver {
|
|
|
13101
13202
|
}
|
|
13102
13203
|
return undefined;
|
|
13103
13204
|
}
|
|
13205
|
+
// Handle form field paths: {{ form.fieldId }} reads the live form state and
|
|
13206
|
+
// coerces it through the same helper used by setTagsFromForm.
|
|
13207
|
+
if (key.startsWith("form.")) {
|
|
13208
|
+
const fieldId = key.substring("form.".length);
|
|
13209
|
+
const value = PaywallState.currentProvider?.state.formStates[fieldId];
|
|
13210
|
+
return formTagValue(value);
|
|
13211
|
+
}
|
|
13104
13212
|
switch (key) {
|
|
13105
13213
|
case "campaignId":
|
|
13106
13214
|
return screenState?.campaign?.rule;
|
|
@@ -13152,6 +13260,8 @@ class FlowLiquidResolver {
|
|
|
13152
13260
|
const s = PaywallState.currentProvider?.state;
|
|
13153
13261
|
return s?.groups?.find(g => g.id === s.currentGroupId)?.ref ?? undefined;
|
|
13154
13262
|
}
|
|
13263
|
+
case "flowPath":
|
|
13264
|
+
return flow?.flowPath;
|
|
13155
13265
|
default:
|
|
13156
13266
|
return undefined;
|
|
13157
13267
|
}
|
|
@@ -14102,6 +14212,67 @@ let NamiCustomerManager$2 = class NamiCustomerManager {
|
|
|
14102
14212
|
};
|
|
14103
14213
|
NamiCustomerManager$2.instance = new NamiCustomerManager$2();
|
|
14104
14214
|
|
|
14215
|
+
/**
|
|
14216
|
+
* Pure, platform-agnostic text-input validation. Shared by the flow submit gate
|
|
14217
|
+
* (sdk/core) and the web Lit element (sdk/web). The rules are identical to the
|
|
14218
|
+
* Apple (NAM-1142) and Android (NAM-1143) ports.
|
|
14219
|
+
*/
|
|
14220
|
+
const DEFAULT_MESSAGES = {
|
|
14221
|
+
required: 'This field is required',
|
|
14222
|
+
email: 'Please enter a valid email address',
|
|
14223
|
+
name: 'Please enter at least 2 characters',
|
|
14224
|
+
};
|
|
14225
|
+
/**
|
|
14226
|
+
* An email must contain an `@` and a domain with a dot, e.g. `a@b.co`.
|
|
14227
|
+
* `a@b` (no dotted domain) and `nope` (no `@`) are invalid.
|
|
14228
|
+
*/
|
|
14229
|
+
const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
14230
|
+
/**
|
|
14231
|
+
* Validate a single field. Returns `undefined` when valid, otherwise a default
|
|
14232
|
+
* error message for the failing rule. Callers that want author-supplied text
|
|
14233
|
+
* should use {@link validateForm}.
|
|
14234
|
+
*
|
|
14235
|
+
* Rules:
|
|
14236
|
+
* - required && blank → invalid
|
|
14237
|
+
* - email && non-blank && not email-format → invalid
|
|
14238
|
+
* - name && non-blank && len < 2 → invalid
|
|
14239
|
+
* - freeText → only the required rule applies
|
|
14240
|
+
*/
|
|
14241
|
+
function validateTextInput(type, required, value) {
|
|
14242
|
+
const text = typeof value === 'string' ? value : value == null ? '' : String(value);
|
|
14243
|
+
const trimmed = text.trim();
|
|
14244
|
+
if (required && trimmed.length === 0) {
|
|
14245
|
+
return DEFAULT_MESSAGES.required;
|
|
14246
|
+
}
|
|
14247
|
+
if (trimmed.length === 0) {
|
|
14248
|
+
// Blank optional value: no format rule applies.
|
|
14249
|
+
return undefined;
|
|
14250
|
+
}
|
|
14251
|
+
if (type === 'email' && !EMAIL_REGEX.test(trimmed)) {
|
|
14252
|
+
return DEFAULT_MESSAGES.email;
|
|
14253
|
+
}
|
|
14254
|
+
if (type === 'name' && trimmed.length < 2) {
|
|
14255
|
+
return DEFAULT_MESSAGES.name;
|
|
14256
|
+
}
|
|
14257
|
+
return undefined;
|
|
14258
|
+
}
|
|
14259
|
+
/**
|
|
14260
|
+
* Validate every registered field against the current form state. Returns a map
|
|
14261
|
+
* of `formId → error message` for invalid fields only (valid fields are omitted).
|
|
14262
|
+
* The validator's own `message` is preferred; otherwise the per-rule default
|
|
14263
|
+
* from {@link validateTextInput} is used.
|
|
14264
|
+
*/
|
|
14265
|
+
function validateForm(validators, formStates) {
|
|
14266
|
+
const errors = {};
|
|
14267
|
+
for (const [formId, validator] of Object.entries(validators)) {
|
|
14268
|
+
const defaultError = validateTextInput(validator.type, validator.required, formStates[formId]);
|
|
14269
|
+
if (defaultError !== undefined) {
|
|
14270
|
+
errors[formId] = validator.message ?? defaultError;
|
|
14271
|
+
}
|
|
14272
|
+
}
|
|
14273
|
+
return errors;
|
|
14274
|
+
}
|
|
14275
|
+
|
|
14105
14276
|
class BasicNamiFlow {
|
|
14106
14277
|
constructor(flowObject = {}) {
|
|
14107
14278
|
this.id = flowObject.id ?? '';
|
|
@@ -14123,6 +14294,7 @@ class NamiFlow extends BasicNamiFlow {
|
|
|
14123
14294
|
this.activeHandoffSequence = null;
|
|
14124
14295
|
this.currentScreenState = null;
|
|
14125
14296
|
this.isPaused = false;
|
|
14297
|
+
this.flowPath = "";
|
|
14126
14298
|
this.timerStates = {};
|
|
14127
14299
|
this.campaign = campaign;
|
|
14128
14300
|
this.component = paywall;
|
|
@@ -14333,12 +14505,40 @@ class NamiFlow extends BasicNamiFlow {
|
|
|
14333
14505
|
}
|
|
14334
14506
|
return lifecycles;
|
|
14335
14507
|
}
|
|
14508
|
+
/**
|
|
14509
|
+
* A lifecycle chain is a "form submit" if any phase (before/action/after) runs
|
|
14510
|
+
* one of the advancing functions: setTagsFromForm, flowNext, or flowNav. The
|
|
14511
|
+
* POC submit buttons use flowNext/flowNav (no dedicated submit action), so the
|
|
14512
|
+
* gate keys off these. Close/dismiss/back/pause are deliberately excluded —
|
|
14513
|
+
* they must never be blocked by validation. Mirrors Apple
|
|
14514
|
+
* NamiFlowControl.isFormSubmitLifecycle / Android NamiFlow.isFormSubmitLifecycle.
|
|
14515
|
+
*/
|
|
14516
|
+
isFormSubmitLifecycle(lifecycles) {
|
|
14517
|
+
const submitFns = [
|
|
14518
|
+
exports.NamiFlowActionFunction.SET_TAGS_FROM_FORM,
|
|
14519
|
+
exports.NamiFlowActionFunction.NEXT,
|
|
14520
|
+
exports.NamiFlowActionFunction.NAVIGATE,
|
|
14521
|
+
];
|
|
14522
|
+
return lifecycles.some(cycle => [cycle.before, cycle.action, cycle.after].some(actions => actions?.some(action => submitFns.includes(action.function))));
|
|
14523
|
+
}
|
|
14336
14524
|
triggerActions(actionId, component, data) {
|
|
14337
14525
|
const lifecycles = this.lifecycles(actionId);
|
|
14338
14526
|
if (!lifecycles)
|
|
14339
14527
|
return;
|
|
14340
14528
|
this.flowLog(`triggerActions(${actionId}) — step=${this.currentFlowStep?.id}, isPaused=${this.isPaused}`);
|
|
14341
14529
|
this.currentButton = component;
|
|
14530
|
+
// Implicit form-submit validation gate (the NAM-1143 on-device lesson).
|
|
14531
|
+
// When this action advances the flow and validators are registered, validate
|
|
14532
|
+
// first; write the errors and halt the chain if any field is invalid.
|
|
14533
|
+
const validators = PaywallState.currentProvider?.state.formFieldValidators ?? {};
|
|
14534
|
+
if (this.isFormSubmitLifecycle(lifecycles) && Object.keys(validators).length) {
|
|
14535
|
+
const errors = validateForm(validators, this.getFormData());
|
|
14536
|
+
PaywallState.currentProvider?.setFormFieldErrors(errors);
|
|
14537
|
+
if (Object.keys(errors).length) {
|
|
14538
|
+
this.flowLog(`triggerActions(${actionId}) — HALTED by form validation: ${JSON.stringify(errors)}`);
|
|
14539
|
+
return;
|
|
14540
|
+
}
|
|
14541
|
+
}
|
|
14342
14542
|
this.executeFullLifecycles(lifecycles, data);
|
|
14343
14543
|
}
|
|
14344
14544
|
executeFullLifecycles(lifecycles, data) {
|
|
@@ -14545,6 +14745,22 @@ class NamiFlow extends BasicNamiFlow {
|
|
|
14545
14745
|
});
|
|
14546
14746
|
}
|
|
14547
14747
|
break;
|
|
14748
|
+
case exports.NamiFlowActionFunction.SET_TAGS_FROM_FORM: {
|
|
14749
|
+
const formData = this.getFormData();
|
|
14750
|
+
const tags = {};
|
|
14751
|
+
Object.entries(formData).forEach(([key, raw]) => {
|
|
14752
|
+
const value = formTagValue(raw);
|
|
14753
|
+
if (value === undefined) {
|
|
14754
|
+
return; // skip null / "" (false and 0 are kept by formTagValue)
|
|
14755
|
+
}
|
|
14756
|
+
tags[key] = value;
|
|
14757
|
+
NamiCustomerManager$2.setCustomerAttribute(key, value);
|
|
14758
|
+
});
|
|
14759
|
+
if (Object.keys(tags).length) {
|
|
14760
|
+
PaywallState.setUserTags(tags);
|
|
14761
|
+
}
|
|
14762
|
+
break;
|
|
14763
|
+
}
|
|
14548
14764
|
case exports.NamiFlowActionFunction.SET_LAUNCH_CONTEXT: {
|
|
14549
14765
|
// Two supported shapes: { customAttributes: {...} } (nav-action wire shape)
|
|
14550
14766
|
// and { key, value } (used by Apple/Android/Roku). We deliberately do NOT
|
|
@@ -14559,6 +14775,13 @@ class NamiFlow extends BasicNamiFlow {
|
|
|
14559
14775
|
}
|
|
14560
14776
|
break;
|
|
14561
14777
|
}
|
|
14778
|
+
case exports.NamiFlowActionFunction.SET_FLOW_PATH: {
|
|
14779
|
+
const flowPath = action.parameters?.flowPath;
|
|
14780
|
+
if (typeof flowPath === "string") {
|
|
14781
|
+
this.flowPath = flowPath;
|
|
14782
|
+
}
|
|
14783
|
+
break;
|
|
14784
|
+
}
|
|
14562
14785
|
default:
|
|
14563
14786
|
logger.warn(`Missing action handler for ${action.function}`, action);
|
|
14564
14787
|
break;
|
|
@@ -64908,4 +65131,6 @@ exports.tryParseB64Gzip = tryParseB64Gzip;
|
|
|
64908
65131
|
exports.tryParseJson = tryParseJson;
|
|
64909
65132
|
exports.updateRelatedSKUsForNamiEntitlement = updateRelatedSKUsForNamiEntitlement;
|
|
64910
65133
|
exports.uuidFromSplitPosition = uuidFromSplitPosition;
|
|
65134
|
+
exports.validateForm = validateForm;
|
|
64911
65135
|
exports.validateMinSDKVersion = validateMinSDKVersion;
|
|
65136
|
+
exports.validateTextInput = validateTextInput;
|
package/dist/index.d.ts
CHANGED
|
@@ -133,9 +133,11 @@ declare enum NamiFlowActionFunction {
|
|
|
133
133
|
FLOW_ENABLED = "flowInteractionEnabled",
|
|
134
134
|
FLOW_DISABLED = "flowInteractionDisabled",
|
|
135
135
|
SET_TAGS = "setTags",
|
|
136
|
+
SET_TAGS_FROM_FORM = "setTagsFromForm",
|
|
136
137
|
PAUSE = "flowPause",
|
|
137
138
|
RESUME = "flowResume",
|
|
138
|
-
SET_LAUNCH_CONTEXT = "setLaunchContext"
|
|
139
|
+
SET_LAUNCH_CONTEXT = "setLaunchContext",
|
|
140
|
+
SET_FLOW_PATH = "setFlowPath"
|
|
139
141
|
}
|
|
140
142
|
type NamiFlowHandoffStepHandler = (handoffTag: string, handoffData?: Record<string, any>) => void;
|
|
141
143
|
type NamiFlowEventHandler = (eventHandler: Record<string, any>) => void;
|
|
@@ -170,6 +172,7 @@ interface NamiFlowAction {
|
|
|
170
172
|
handoffTag?: string;
|
|
171
173
|
handoffData?: Record<string, any>;
|
|
172
174
|
handoffFormId?: string;
|
|
175
|
+
flowPath?: string;
|
|
173
176
|
step?: string;
|
|
174
177
|
eventName?: string;
|
|
175
178
|
delay?: string | number;
|
|
@@ -553,6 +556,36 @@ type TToggleButtonComponent = TBaseComponent & {
|
|
|
553
556
|
mode?: "radio" | "toggle";
|
|
554
557
|
value?: string;
|
|
555
558
|
};
|
|
559
|
+
type TTextInputComponent = TBaseComponent & {
|
|
560
|
+
component: "textInput";
|
|
561
|
+
type?: "name" | "email";
|
|
562
|
+
formId: string;
|
|
563
|
+
/** Required flag — the real (misspelled) wire key. `required` never populates. */
|
|
564
|
+
reqed?: boolean;
|
|
565
|
+
validateOn?: "submit" | "change";
|
|
566
|
+
placeholderText?: string;
|
|
567
|
+
placeholderFontColor?: string;
|
|
568
|
+
labelText?: string;
|
|
569
|
+
labelPosition?: "top" | "none";
|
|
570
|
+
labelFontColor?: string;
|
|
571
|
+
labelFontName?: string;
|
|
572
|
+
labelFontSize?: number | string;
|
|
573
|
+
validationMessage?: string;
|
|
574
|
+
validationTextFontColor?: string;
|
|
575
|
+
validationTextFontName?: string;
|
|
576
|
+
validationTextFontSize?: number | string;
|
|
577
|
+
fontColor?: string;
|
|
578
|
+
fontName?: string;
|
|
579
|
+
fontSize?: number | string;
|
|
580
|
+
errorBorderColor?: string;
|
|
581
|
+
errorBorderWidth?: number | string;
|
|
582
|
+
errorBorderRadius?: number | string;
|
|
583
|
+
innerTopPadding?: number | string;
|
|
584
|
+
innerBottomPadding?: number | string;
|
|
585
|
+
innerLeftPadding?: number | string;
|
|
586
|
+
innerRightPadding?: number | string;
|
|
587
|
+
showPrefixIcon?: boolean;
|
|
588
|
+
};
|
|
556
589
|
type TCountdownTimerTextComponent = TBaseComponent & {
|
|
557
590
|
component: "countdownTimerText";
|
|
558
591
|
mode?: "duration" | "targetDateTime";
|
|
@@ -885,7 +918,7 @@ interface TBaseComponent {
|
|
|
885
918
|
hidden?: boolean;
|
|
886
919
|
animation?: NamiAnimationSpec;
|
|
887
920
|
}
|
|
888
|
-
type TComponent = TButtonContainer | TContainer | TTextListComponent | TTextComponent | TSpacerComponent | TImageComponent | TSvgImageComponent | TSymbolComponent | TCarouselContainer | TProductContainer | TFlexProductContainer | TStack | TConditionalComponent | TSegmentPicker | TSegmentPickerItem | TVideoComponent | TCollapseContainer | TResponsiveGrid | TRepeatingGrid | TVolumeButton | TPlayPauseButton | TQRCodeComponent | TToggleSwitch | TRadioButton | TProgressIndicatorComponent | TToggleButtonComponent | TCountdownTimerTextComponent | TProgressBarComponent;
|
|
921
|
+
type TComponent = TButtonContainer | TContainer | TTextListComponent | TTextComponent | TSpacerComponent | TImageComponent | TSvgImageComponent | TSymbolComponent | TCarouselContainer | TProductContainer | TFlexProductContainer | TStack | TConditionalComponent | TSegmentPicker | TSegmentPickerItem | TVideoComponent | TCollapseContainer | TResponsiveGrid | TRepeatingGrid | TVolumeButton | TPlayPauseButton | TQRCodeComponent | TToggleSwitch | TRadioButton | TProgressIndicatorComponent | TToggleButtonComponent | TCountdownTimerTextComponent | TTextInputComponent | TProgressBarComponent;
|
|
889
922
|
type DirectionType = "vertical" | "horizontal";
|
|
890
923
|
type AlignmentType = "center" | "right" | "left" | "top" | "bottom";
|
|
891
924
|
type BorderLocationType = "upperLeft" | "upperRight" | "lowerLeft" | "lowerRight";
|
|
@@ -1490,6 +1523,38 @@ type NamiPurchaseDetails = {
|
|
|
1490
1523
|
|
|
1491
1524
|
type Optional<T, K extends keyof T> = Pick<Partial<T>, K> & Omit<T, K>;
|
|
1492
1525
|
|
|
1526
|
+
/**
|
|
1527
|
+
* Pure, platform-agnostic text-input validation. Shared by the flow submit gate
|
|
1528
|
+
* (sdk/core) and the web Lit element (sdk/web). The rules are identical to the
|
|
1529
|
+
* Apple (NAM-1142) and Android (NAM-1143) ports.
|
|
1530
|
+
*/
|
|
1531
|
+
type TTextInputType = 'name' | 'email' | 'freeText';
|
|
1532
|
+
interface FormFieldValidator {
|
|
1533
|
+
type: TTextInputType;
|
|
1534
|
+
required: boolean;
|
|
1535
|
+
/** Author-supplied error text; preferred over the per-rule default. */
|
|
1536
|
+
message?: string;
|
|
1537
|
+
}
|
|
1538
|
+
/**
|
|
1539
|
+
* Validate a single field. Returns `undefined` when valid, otherwise a default
|
|
1540
|
+
* error message for the failing rule. Callers that want author-supplied text
|
|
1541
|
+
* should use {@link validateForm}.
|
|
1542
|
+
*
|
|
1543
|
+
* Rules:
|
|
1544
|
+
* - required && blank → invalid
|
|
1545
|
+
* - email && non-blank && not email-format → invalid
|
|
1546
|
+
* - name && non-blank && len < 2 → invalid
|
|
1547
|
+
* - freeText → only the required rule applies
|
|
1548
|
+
*/
|
|
1549
|
+
declare function validateTextInput(type: TTextInputType, required: boolean, value: unknown): string | undefined;
|
|
1550
|
+
/**
|
|
1551
|
+
* Validate every registered field against the current form state. Returns a map
|
|
1552
|
+
* of `formId → error message` for invalid fields only (valid fields are omitted).
|
|
1553
|
+
* The validator's own `message` is preferred; otherwise the per-rule default
|
|
1554
|
+
* from {@link validateTextInput} is used.
|
|
1555
|
+
*/
|
|
1556
|
+
declare function validateForm(validators: Record<string, FormFieldValidator>, formStates: Record<string, unknown>): Record<string, string>;
|
|
1557
|
+
|
|
1493
1558
|
type NamiPresentationStyle = 'fullscreen' | 'sheet' | 'compact_sheet' | 'modal';
|
|
1494
1559
|
declare function getEffectiveWebStyle(style: NamiPresentationStyle | undefined | null): NamiPresentationStyle;
|
|
1495
1560
|
type TPaywallContext = TInitialState & {
|
|
@@ -1528,6 +1593,12 @@ type TPaywallContext = TInitialState & {
|
|
|
1528
1593
|
formStates: {
|
|
1529
1594
|
[formId: string]: boolean | string;
|
|
1530
1595
|
};
|
|
1596
|
+
formFieldErrors: {
|
|
1597
|
+
[formId: string]: string;
|
|
1598
|
+
};
|
|
1599
|
+
formFieldValidators: {
|
|
1600
|
+
[formId: string]: FormFieldValidator;
|
|
1601
|
+
};
|
|
1531
1602
|
timerStates: {
|
|
1532
1603
|
[timerId: string]: TimerState;
|
|
1533
1604
|
};
|
|
@@ -1935,6 +2006,7 @@ declare class NamiFlow extends BasicNamiFlow {
|
|
|
1935
2006
|
currentScreenState: TPaywallContext | null;
|
|
1936
2007
|
isPaused: boolean;
|
|
1937
2008
|
pausedStepID?: string;
|
|
2009
|
+
flowPath: string;
|
|
1938
2010
|
timerStates: {
|
|
1939
2011
|
[timerId: string]: TimerState;
|
|
1940
2012
|
};
|
|
@@ -1959,6 +2031,15 @@ declare class NamiFlow extends BasicNamiFlow {
|
|
|
1959
2031
|
resumeFromPause(): void;
|
|
1960
2032
|
executeLifecycle(step: NamiFlowStep, key: string, data?: Record<string, any>): void;
|
|
1961
2033
|
private lifecycles;
|
|
2034
|
+
/**
|
|
2035
|
+
* A lifecycle chain is a "form submit" if any phase (before/action/after) runs
|
|
2036
|
+
* one of the advancing functions: setTagsFromForm, flowNext, or flowNav. The
|
|
2037
|
+
* POC submit buttons use flowNext/flowNav (no dedicated submit action), so the
|
|
2038
|
+
* gate keys off these. Close/dismiss/back/pause are deliberately excluded —
|
|
2039
|
+
* they must never be blocked by validation. Mirrors Apple
|
|
2040
|
+
* NamiFlowControl.isFormSubmitLifecycle / Android NamiFlow.isFormSubmitLifecycle.
|
|
2041
|
+
*/
|
|
2042
|
+
isFormSubmitLifecycle(lifecycles: NamiFlowOn[]): boolean;
|
|
1962
2043
|
triggerActions(actionId: string, component?: any, data?: Record<string, any>): void;
|
|
1963
2044
|
executeFullLifecycles(lifecycles: NamiFlowOn[], data?: Record<string, any>): void;
|
|
1964
2045
|
triggerBeforeActions(actionId: string, component?: any, data?: Record<string, any>): void;
|
|
@@ -2026,6 +2107,27 @@ declare class PaywallState extends SimpleEventTarget {
|
|
|
2026
2107
|
getProductDetails(): NamiProductDetails[];
|
|
2027
2108
|
setCurrentGroupData(currentGroupId: string, currentGroupName: string): void;
|
|
2028
2109
|
setFormState(formId: string, state: boolean | string): void;
|
|
2110
|
+
/**
|
|
2111
|
+
* Register the validator for a text-input field (keyed by formId). Used by the
|
|
2112
|
+
* implicit form-submit gate in NamiFlow.triggerActions and by the web element
|
|
2113
|
+
* when it mounts.
|
|
2114
|
+
*/
|
|
2115
|
+
registerFormFieldValidator(formId: string, validator: FormFieldValidator): void;
|
|
2116
|
+
getFormFieldValidators(): {
|
|
2117
|
+
[formId: string]: FormFieldValidator;
|
|
2118
|
+
};
|
|
2119
|
+
/**
|
|
2120
|
+
* Replace the full set of field errors (formId → message). The gate writes the
|
|
2121
|
+
* result of validateForm here; an empty map clears all errors.
|
|
2122
|
+
*/
|
|
2123
|
+
setFormFieldErrors(errors: {
|
|
2124
|
+
[formId: string]: string;
|
|
2125
|
+
}): void;
|
|
2126
|
+
setFormFieldError(formId: string, message: string): void;
|
|
2127
|
+
clearFormFieldError(formId: string): void;
|
|
2128
|
+
getFormFieldErrors(): {
|
|
2129
|
+
[formId: string]: string;
|
|
2130
|
+
};
|
|
2029
2131
|
setTimerState(timerId: string, remainingSeconds: number, savedAt: number, hasEmittedCompletion: boolean): void;
|
|
2030
2132
|
getTimerState(timerId: string): TimerState | undefined;
|
|
2031
2133
|
getCurrentGroupId(): string;
|
|
@@ -2422,6 +2524,15 @@ declare class StorageService {
|
|
|
2422
2524
|
getCustomerAttribute<T>(attribute: string): T | null;
|
|
2423
2525
|
getAllCustomerAttributes(): string[];
|
|
2424
2526
|
getAllCustomerAttributesKeys(): string[];
|
|
2527
|
+
/**
|
|
2528
|
+
* All persisted customer attributes as a `{ key: value }` map. Used to seed
|
|
2529
|
+
* `PaywallState.customer` when a paywall screen initializes, so attributes set
|
|
2530
|
+
* on an earlier flow screen (e.g. via setTagsFromForm) are available to the
|
|
2531
|
+
* `${customer.x}` smart-text resolver on subsequent screens.
|
|
2532
|
+
*/
|
|
2533
|
+
getAllCustomerAttributesMap(): {
|
|
2534
|
+
[key: string]: string;
|
|
2535
|
+
};
|
|
2425
2536
|
clearCustomerAttribute(attribute: string): boolean;
|
|
2426
2537
|
clearAllCustomerAttributes(): void;
|
|
2427
2538
|
setNamiProfile(profileData: NamiProfile): void;
|
|
@@ -3489,5 +3600,5 @@ declare namespace internal {
|
|
|
3489
3600
|
};
|
|
3490
3601
|
}
|
|
3491
3602
|
|
|
3492
|
-
export { ALREADY_CONFIGURED, ANONYMOUS_MODE, ANONYMOUS_MODE_ALREADY_OFF, ANONYMOUS_MODE_ALREADY_ON, ANONYMOUS_MODE_LOGIN_NOT_ALLOWED, ANONYMOUS_UUID, APIError, API_ACTIVE_ENTITLEMENTS, API_CAMPAIGN_RULES, API_CAMPAIGN_SESSION_TIMESTAMP, API_CONFIG, API_MAX_CALLS_LIMIT, API_PAYWALLS, API_PRODUCTS, API_RETRY_DELAY_SEC, API_TIMEOUT_LIMIT, API_VERSION, AUTH_DEVICE, AVAILABLE_ACTIVE_ENTITLEMENTS_CHANGED, AVAILABLE_CAMPAIGNS_CHANGED, AccountStateAction, AnonymousCDPError, AnonymousLoginError, AnonymousModeAlreadyOffError, AnonymousModeAlreadyOnError, BASE_STAGING_URL, BASE_URL, BASE_URL_PATH, BadRequestError, BasicNamiFlow, BorderMap, BorderSideMap, CAMPAIGN_NOT_AVAILABLE, CUSTOMER_ATTRIBUTES_KEY_PREFIX, CUSTOMER_JOURNEY_STATE_CHANGED, CUSTOM_HOST_PREFIX, CampaignNotAvailableError, CampaignRuleConversionEventType, CampaignRuleRepository, Capabilities, ClientError, ConfigRepository, ConflictError, CustomerJourneyRepository, DEVELOPMENT, DEVICE_API_TIMEOUT_LIMIT, DEVICE_ID_NOT_SET, DEVICE_ID_REQUIRED, DISABLE_ASYNC_LOGIN_LOGOUT, DeviceIDRequiredError, DeviceRepository, EXTENDED_CLIENT_INFO_DELIMITER, EXTENDED_CLIENT_INFO_PREFIX, EXTENDED_PLATFORM, EXTENDED_PLATFORM_VERSION, EXTERNAL_ID_REQUIRED, EntitlementRepository, EntitlementUtils, ExternalIDRequiredError, FLOW_SCREENS_NOT_AVAILABLE, FlowScreensNotAvailableError, HTML_REGEX, INITIAL_APP_CONFIG, INITIAL_CAMPAIGN_RULES, INITIAL_PAYWALLS, INITIAL_PRODUCTS, INITIAL_SESSION_COUNTER_VALUE, INITIAL_SUCCESS, InternalServerError, KEY_SESSION_COUNTER, LIQUID_VARIABLE_REGEX, LOCAL_NAMI_ENTITLEMENTS, LOG_HTTP_REQUESTS, LOG_HTTP_TRAFFIC, LaunchCampaignError, LaunchContextResolver, LogLevel, NAMI_CONFIGURATION, NAMI_CUSTOMER_JOURNEY_STATE, NAMI_LANGUAGE_CODE, NAMI_LAST_IMPRESSION_ID, NAMI_LAUNCH_ID, NAMI_PROFILE, NAMI_PURCHASE_CHANNEL, NAMI_PURCHASE_IMPRESSION_ID, NAMI_SDK_PACKAGE_VERSION, NAMI_SDK_VERSION, NAMI_SESSION_ID, NAMI_STORAGE_KEYS, Nami, NamiAPI, NamiAnimationType, NamiCampaignManager$2 as NamiCampaignManager, NamiCampaignRuleType, NamiConditionEvaluator, NamiCustomerManager$2 as NamiCustomerManager, NamiEntitlementManager$2 as NamiEntitlementManager, NamiEventEmitter, NamiFlow, NamiFlowActionFunction, NamiFlowManager$1 as NamiFlowManager, NamiFlowStepType, NamiPaywallAction, NamiPaywallManager$2 as NamiPaywallManager, PaywallManagerEvents as NamiPaywallManagerEvents, NamiPurchaseManager$2 as NamiPurchaseManager, NamiRefs, NamiReservedActions, NotFoundError, PAYWALL_ACTION_EVENT, PLATFORM_ID_REQUIRED, PRODUCTION, PaywallManagerEvents, PaywallRepository, PaywallState, PlacementLabelResolver, PlatformIDRequiredError, ProductRepository, RECONFIG_SUCCESS, RetryLimitExceededError, SDKNotInitializedError, SDK_NOT_INITIALIZED, SERVER_NAMI_ENTITLEMENTS, SESSION_REQUIRED, SHOULD_SHOW_LOADING_INDICATOR, SKU_TEXT_REGEX, SMART_TEXT_PATTERN, STARTUP_TELEMETRY, STATUS_BAD_REQUEST, STATUS_CONFLICT, STATUS_INTERNAL_SERVER_ERROR, STATUS_NOT_FOUND, STATUS_SUCCESS, SessionService, SimpleEventTarget, StorageService, UNABLE_TO_UPDATE_CDP_ID, USE_STAGING_API, VALIDATE_PRODUCT_GROUPS, VAR_REGEX, NamiProfileManager$1 as _NamiProfileManager, internal as _internal, activateEntitlementByPurchase, activeEntitlements, aggregateScreenreaderText, allCampaigns, allPaywalls, applyEntitlementActivation, audienceSplitPosition, bestUrlCampaignMatch, bigintToUuid, checkAnySkuHasPromoOffer, checkAnySkuHasTrialOffer, convertISO8601PeriodToText, convertLocale, convertOfferToPricingPhase, createNamiEntitlements, currentSku, empty, extractStandardPricingPhases, formatDate, formattedPrice, generateUUID, getApiCampaigns, getApiPaywalls, getBaseUrl, getBillingPeriodNumber, getCurrencyFormat, getDeviceData, getDeviceFormFactor, getDeviceScaleFactor, getEffectiveWebStyle, getEntitlementRefIdsForSku, getExtendedClientInfo, getFreeTrialPeriod, getInitialCampaigns, getInitialPaywalls, getPaywall, getPaywallDataFromLabel, getPercentagePriceDifference, getPeriodNumberInDays, getPeriodNumberInWeeks, getPlatformAdapters, getPriceDifference, getPricePerMonth, getProductDetail, getPurchaseAdapter, getReferenceSku, getSkuProductDetailKeys, getSkuSmartTextValue, getSlideSmartTextValue, getStandardBillingPeriod, getTranslate, getUrlParams, handleErrors, hasAllPaywalls, hasCapability, hasPurchaseManagement, initialState, invokeHandler, isAnonymousMode, isInitialConfigCompressed, isNamiFlowCampaign, isSubscription, isValidISODate, isValidUrl, logger, mapAnonymousCampaigns, namiBuySKU, normalizeLaunchContext, parseToSemver, postConversion, productDetail, registerPlatformAdapters, registerPurchaseAdapter, selectSegment, setActiveNamiEntitlements, shouldValidateProductGroups, skuItems, skuMapFromEntitlements, storageService, toDouble, toNamiEntitlements, toNamiSKU, tryParseB64Gzip, tryParseJson, updateRelatedSKUsForNamiEntitlement, uuidFromSplitPosition, validateMinSDKVersion };
|
|
3493
|
-
export type { AccountStateHandler$1 as AccountStateHandler, AlignmentType, AmazonProduct, ApiResponse, AppleProduct, AvailableCampaignsResponseHandler, BorderLocationType, BorderSideType, Callback$1 as Callback, CloseHandler, CustomerJourneyState, DeepLinkUrlHandler, Device, DevicePayload, DeviceProfile, DirectionType, ExtendedPlatformInfo, FlexDirectionObject, FlowNavigationOptions, FontCollection, FontDetails, FormFactor, GoogleProduct, IConfig, IDeviceAdapter, IEntitlements$1 as IEntitlements, IPaywall, IPlatformAdapters, IProductsWithComponents, IPurchaseAdapter, ISkuMenu, IStorageAdapter, IUIAdapter, Impression, InitialConfig, InitialConfigCompressed, InitiateStateGroup, LoginResponse, NamiAnimation, NamiAnimationObjectSpec, NamiAnimationSpec, NamiAnonymousCampaign, NamiAppSuppliedVideoDetails, NamiCampaign, NamiCampaignManagerStatic, NamiCampaignSegment, NamiConfiguration, NamiConfigurationState, NamiCustomAttributeValue, NamiCustomerManagerStatic, NamiEntitlement$1 as NamiEntitlement, NamiEntitlementManagerStatic, NamiFlowAction, NamiFlowAnimation, NamiFlowCampaign, NamiFlowDTO, NamiFlowEventHandler, NamiFlowHandoffStepHandler, NamiFlowManagerStatic, NamiFlowObjectDTO, NamiFlowOn, NamiFlowStep, NamiFlowTransition, NamiFlowTransitionDirection, NamiFlowWithObject, NamiInitialConfig, NamiLanguageCodes, NamiLogLevel, NamiPaywallActionHandler, NamiPaywallComponentChange, NamiPaywallEvent, NamiPaywallEventVideoMetadata, NamiPaywallLaunchContext, NamiPaywallManagerStatic, NamiPresentationStyle, NamiProductDetails, NamiProductOffer, NamiProfile, NamiPurchase, NamiPurchaseCompleteResult, NamiPurchaseDetails, NamiPurchaseManagerStatic, NamiPurchasesState, NamiSKU, NamiSKUType, NamiSubscriptionInterval, NamiSubscriptionPeriod, None, NoneSpec, PaywallActionEvent, PaywallHandle, PaywallResultHandler, PaywallSKU, PricingPhase, ProductGroup, Pulse, PulseSpec, PurchaseContext, PurchaseResult, PurchaseValidationRequest, SKU, SKUActionHandler, ScreenInfo, Session, TBaseComponent, TButtonContainer, TCarouselContainer, TCarouselSlide, TCarouselSlidesState, TCollapseContainer, TComponent, TConditionalAttributes, TConditionalComponent, TContainer, TContainerPosition, TCountdownTimerTextComponent, TDevice, TDisabledButton, TField, TFieldSettings, TFlexProductContainer, THeaderFooter, TImageComponent, TInitialState, TMediaTypes, TOffer, TPages, TPaywallContext, TPaywallLaunchContext, TPaywallMedia, TPaywallTemplate, TPlayPauseButton, TProductContainer, TProductGroup, TProgressBarComponent, TProgressIndicatorComponent, TQRCodeComponent, TRadioButton, TRepeatingGrid, TResponsiveGrid, TSegmentPicker, TSegmentPickerItem, TSemverObj, TSpacerComponent, TStack, TSvgImageComponent, TSymbolComponent, TTestObject, TTextComponent, TTextLikeComponent, TTextListComponent, TTimelineRail, TToggleButtonComponent, TToggleSwitch, TVariablePattern, TVideoComponent, TVolumeButton, TimerState, TransactionRequest, UserAction, UserActionParameters, Wave, WaveSpec };
|
|
3603
|
+
export { ALREADY_CONFIGURED, ANONYMOUS_MODE, ANONYMOUS_MODE_ALREADY_OFF, ANONYMOUS_MODE_ALREADY_ON, ANONYMOUS_MODE_LOGIN_NOT_ALLOWED, ANONYMOUS_UUID, APIError, API_ACTIVE_ENTITLEMENTS, API_CAMPAIGN_RULES, API_CAMPAIGN_SESSION_TIMESTAMP, API_CONFIG, API_MAX_CALLS_LIMIT, API_PAYWALLS, API_PRODUCTS, API_RETRY_DELAY_SEC, API_TIMEOUT_LIMIT, API_VERSION, AUTH_DEVICE, AVAILABLE_ACTIVE_ENTITLEMENTS_CHANGED, AVAILABLE_CAMPAIGNS_CHANGED, AccountStateAction, AnonymousCDPError, AnonymousLoginError, AnonymousModeAlreadyOffError, AnonymousModeAlreadyOnError, BASE_STAGING_URL, BASE_URL, BASE_URL_PATH, BadRequestError, BasicNamiFlow, BorderMap, BorderSideMap, CAMPAIGN_NOT_AVAILABLE, CUSTOMER_ATTRIBUTES_KEY_PREFIX, CUSTOMER_JOURNEY_STATE_CHANGED, CUSTOM_HOST_PREFIX, CampaignNotAvailableError, CampaignRuleConversionEventType, CampaignRuleRepository, Capabilities, ClientError, ConfigRepository, ConflictError, CustomerJourneyRepository, DEVELOPMENT, DEVICE_API_TIMEOUT_LIMIT, DEVICE_ID_NOT_SET, DEVICE_ID_REQUIRED, DISABLE_ASYNC_LOGIN_LOGOUT, DeviceIDRequiredError, DeviceRepository, EXTENDED_CLIENT_INFO_DELIMITER, EXTENDED_CLIENT_INFO_PREFIX, EXTENDED_PLATFORM, EXTENDED_PLATFORM_VERSION, EXTERNAL_ID_REQUIRED, EntitlementRepository, EntitlementUtils, ExternalIDRequiredError, FLOW_SCREENS_NOT_AVAILABLE, FlowScreensNotAvailableError, HTML_REGEX, INITIAL_APP_CONFIG, INITIAL_CAMPAIGN_RULES, INITIAL_PAYWALLS, INITIAL_PRODUCTS, INITIAL_SESSION_COUNTER_VALUE, INITIAL_SUCCESS, InternalServerError, KEY_SESSION_COUNTER, LIQUID_VARIABLE_REGEX, LOCAL_NAMI_ENTITLEMENTS, LOG_HTTP_REQUESTS, LOG_HTTP_TRAFFIC, LaunchCampaignError, LaunchContextResolver, LogLevel, NAMI_CONFIGURATION, NAMI_CUSTOMER_JOURNEY_STATE, NAMI_LANGUAGE_CODE, NAMI_LAST_IMPRESSION_ID, NAMI_LAUNCH_ID, NAMI_PROFILE, NAMI_PURCHASE_CHANNEL, NAMI_PURCHASE_IMPRESSION_ID, NAMI_SDK_PACKAGE_VERSION, NAMI_SDK_VERSION, NAMI_SESSION_ID, NAMI_STORAGE_KEYS, Nami, NamiAPI, NamiAnimationType, NamiCampaignManager$2 as NamiCampaignManager, NamiCampaignRuleType, NamiConditionEvaluator, NamiCustomerManager$2 as NamiCustomerManager, NamiEntitlementManager$2 as NamiEntitlementManager, NamiEventEmitter, NamiFlow, NamiFlowActionFunction, NamiFlowManager$1 as NamiFlowManager, NamiFlowStepType, NamiPaywallAction, NamiPaywallManager$2 as NamiPaywallManager, PaywallManagerEvents as NamiPaywallManagerEvents, NamiPurchaseManager$2 as NamiPurchaseManager, NamiRefs, NamiReservedActions, NotFoundError, PAYWALL_ACTION_EVENT, PLATFORM_ID_REQUIRED, PRODUCTION, PaywallManagerEvents, PaywallRepository, PaywallState, PlacementLabelResolver, PlatformIDRequiredError, ProductRepository, RECONFIG_SUCCESS, RetryLimitExceededError, SDKNotInitializedError, SDK_NOT_INITIALIZED, SERVER_NAMI_ENTITLEMENTS, SESSION_REQUIRED, SHOULD_SHOW_LOADING_INDICATOR, SKU_TEXT_REGEX, SMART_TEXT_PATTERN, STARTUP_TELEMETRY, STATUS_BAD_REQUEST, STATUS_CONFLICT, STATUS_INTERNAL_SERVER_ERROR, STATUS_NOT_FOUND, STATUS_SUCCESS, SessionService, SimpleEventTarget, StorageService, UNABLE_TO_UPDATE_CDP_ID, USE_STAGING_API, VALIDATE_PRODUCT_GROUPS, VAR_REGEX, NamiProfileManager$1 as _NamiProfileManager, internal as _internal, activateEntitlementByPurchase, activeEntitlements, aggregateScreenreaderText, allCampaigns, allPaywalls, applyEntitlementActivation, audienceSplitPosition, bestUrlCampaignMatch, bigintToUuid, checkAnySkuHasPromoOffer, checkAnySkuHasTrialOffer, convertISO8601PeriodToText, convertLocale, convertOfferToPricingPhase, createNamiEntitlements, currentSku, empty, extractStandardPricingPhases, formatDate, formattedPrice, generateUUID, getApiCampaigns, getApiPaywalls, getBaseUrl, getBillingPeriodNumber, getCurrencyFormat, getDeviceData, getDeviceFormFactor, getDeviceScaleFactor, getEffectiveWebStyle, getEntitlementRefIdsForSku, getExtendedClientInfo, getFreeTrialPeriod, getInitialCampaigns, getInitialPaywalls, getPaywall, getPaywallDataFromLabel, getPercentagePriceDifference, getPeriodNumberInDays, getPeriodNumberInWeeks, getPlatformAdapters, getPriceDifference, getPricePerMonth, getProductDetail, getPurchaseAdapter, getReferenceSku, getSkuProductDetailKeys, getSkuSmartTextValue, getSlideSmartTextValue, getStandardBillingPeriod, getTranslate, getUrlParams, handleErrors, hasAllPaywalls, hasCapability, hasPurchaseManagement, initialState, invokeHandler, isAnonymousMode, isInitialConfigCompressed, isNamiFlowCampaign, isSubscription, isValidISODate, isValidUrl, logger, mapAnonymousCampaigns, namiBuySKU, normalizeLaunchContext, parseToSemver, postConversion, productDetail, registerPlatformAdapters, registerPurchaseAdapter, selectSegment, setActiveNamiEntitlements, shouldValidateProductGroups, skuItems, skuMapFromEntitlements, storageService, toDouble, toNamiEntitlements, toNamiSKU, tryParseB64Gzip, tryParseJson, updateRelatedSKUsForNamiEntitlement, uuidFromSplitPosition, validateForm, validateMinSDKVersion, validateTextInput };
|
|
3604
|
+
export type { AccountStateHandler$1 as AccountStateHandler, AlignmentType, AmazonProduct, ApiResponse, AppleProduct, AvailableCampaignsResponseHandler, BorderLocationType, BorderSideType, Callback$1 as Callback, CloseHandler, CustomerJourneyState, DeepLinkUrlHandler, Device, DevicePayload, DeviceProfile, DirectionType, ExtendedPlatformInfo, FlexDirectionObject, FlowNavigationOptions, FontCollection, FontDetails, FormFactor, FormFieldValidator, GoogleProduct, IConfig, IDeviceAdapter, IEntitlements$1 as IEntitlements, IPaywall, IPlatformAdapters, IProductsWithComponents, IPurchaseAdapter, ISkuMenu, IStorageAdapter, IUIAdapter, Impression, InitialConfig, InitialConfigCompressed, InitiateStateGroup, LoginResponse, NamiAnimation, NamiAnimationObjectSpec, NamiAnimationSpec, NamiAnonymousCampaign, NamiAppSuppliedVideoDetails, NamiCampaign, NamiCampaignManagerStatic, NamiCampaignSegment, NamiConfiguration, NamiConfigurationState, NamiCustomAttributeValue, NamiCustomerManagerStatic, NamiEntitlement$1 as NamiEntitlement, NamiEntitlementManagerStatic, NamiFlowAction, NamiFlowAnimation, NamiFlowCampaign, NamiFlowDTO, NamiFlowEventHandler, NamiFlowHandoffStepHandler, NamiFlowManagerStatic, NamiFlowObjectDTO, NamiFlowOn, NamiFlowStep, NamiFlowTransition, NamiFlowTransitionDirection, NamiFlowWithObject, NamiInitialConfig, NamiLanguageCodes, NamiLogLevel, NamiPaywallActionHandler, NamiPaywallComponentChange, NamiPaywallEvent, NamiPaywallEventVideoMetadata, NamiPaywallLaunchContext, NamiPaywallManagerStatic, NamiPresentationStyle, NamiProductDetails, NamiProductOffer, NamiProfile, NamiPurchase, NamiPurchaseCompleteResult, NamiPurchaseDetails, NamiPurchaseManagerStatic, NamiPurchasesState, NamiSKU, NamiSKUType, NamiSubscriptionInterval, NamiSubscriptionPeriod, None, NoneSpec, PaywallActionEvent, PaywallHandle, PaywallResultHandler, PaywallSKU, PricingPhase, ProductGroup, Pulse, PulseSpec, PurchaseContext, PurchaseResult, PurchaseValidationRequest, SKU, SKUActionHandler, ScreenInfo, Session, TBaseComponent, TButtonContainer, TCarouselContainer, TCarouselSlide, TCarouselSlidesState, TCollapseContainer, TComponent, TConditionalAttributes, TConditionalComponent, TContainer, TContainerPosition, TCountdownTimerTextComponent, TDevice, TDisabledButton, TField, TFieldSettings, TFlexProductContainer, THeaderFooter, TImageComponent, TInitialState, TMediaTypes, TOffer, TPages, TPaywallContext, TPaywallLaunchContext, TPaywallMedia, TPaywallTemplate, TPlayPauseButton, TProductContainer, TProductGroup, TProgressBarComponent, TProgressIndicatorComponent, TQRCodeComponent, TRadioButton, TRepeatingGrid, TResponsiveGrid, TSegmentPicker, TSegmentPickerItem, TSemverObj, TSpacerComponent, TStack, TSvgImageComponent, TSymbolComponent, TTestObject, TTextComponent, TTextInputComponent, TTextInputType, TTextLikeComponent, TTextListComponent, TTimelineRail, TToggleButtonComponent, TToggleSwitch, TVariablePattern, TVideoComponent, TVolumeButton, TimerState, TransactionRequest, UserAction, UserActionParameters, Wave, WaveSpec };
|
package/dist/index.mjs
CHANGED
|
@@ -96,7 +96,7 @@ const {
|
|
|
96
96
|
// version — stamped by scripts/version.sh
|
|
97
97
|
NAMI_SDK_VERSION = "3.4.3",
|
|
98
98
|
// full package version including dev suffix — stamped by scripts/version.sh
|
|
99
|
-
NAMI_SDK_PACKAGE_VERSION = "3.4.3
|
|
99
|
+
NAMI_SDK_PACKAGE_VERSION = "3.4.3",
|
|
100
100
|
// environments
|
|
101
101
|
PRODUCTION = "production", DEVELOPMENT = "development",
|
|
102
102
|
// error messages
|
|
@@ -3191,6 +3191,22 @@ class StorageService {
|
|
|
3191
3191
|
.filter(key => key.includes(CUSTOMER_ATTRIBUTES_KEY_PREFIX))
|
|
3192
3192
|
.map(key => key.replace(CUSTOMER_ATTRIBUTES_KEY_PREFIX, ""));
|
|
3193
3193
|
}
|
|
3194
|
+
/**
|
|
3195
|
+
* All persisted customer attributes as a `{ key: value }` map. Used to seed
|
|
3196
|
+
* `PaywallState.customer` when a paywall screen initializes, so attributes set
|
|
3197
|
+
* on an earlier flow screen (e.g. via setTagsFromForm) are available to the
|
|
3198
|
+
* `${customer.x}` smart-text resolver on subsequent screens.
|
|
3199
|
+
*/
|
|
3200
|
+
getAllCustomerAttributesMap() {
|
|
3201
|
+
const out = {};
|
|
3202
|
+
this.getAllCustomerAttributesKeys().forEach((key) => {
|
|
3203
|
+
const value = this.getCustomerAttribute(key);
|
|
3204
|
+
if (value !== null && value !== undefined) {
|
|
3205
|
+
out[key] = String(value);
|
|
3206
|
+
}
|
|
3207
|
+
});
|
|
3208
|
+
return out;
|
|
3209
|
+
}
|
|
3194
3210
|
clearCustomerAttribute(attribute) {
|
|
3195
3211
|
const key = CUSTOMER_ATTRIBUTES_KEY_PREFIX + attribute;
|
|
3196
3212
|
const itemExists = this.getItem(key);
|
|
@@ -11315,6 +11331,8 @@ const initialState = {
|
|
|
11315
11331
|
groups: [],
|
|
11316
11332
|
selectedProducts: {},
|
|
11317
11333
|
formStates: {},
|
|
11334
|
+
formFieldErrors: {},
|
|
11335
|
+
formFieldValidators: {},
|
|
11318
11336
|
timerStates: {},
|
|
11319
11337
|
currentPage: 'page1',
|
|
11320
11338
|
pageHistory: ['page1'],
|
|
@@ -11417,6 +11435,11 @@ class PaywallState extends SimpleEventTarget {
|
|
|
11417
11435
|
this.state = cloneDeep(initialState);
|
|
11418
11436
|
const profile = storageService.getNamiProfile();
|
|
11419
11437
|
this.state.isLoggedIn = !!profile?.externalId;
|
|
11438
|
+
// Seed customer attributes persisted on earlier screens (e.g. via
|
|
11439
|
+
// setTagsFromForm / setTags) so `${customer.x}` smart text resolves on every
|
|
11440
|
+
// flow screen — each screen builds a fresh provider, so without this the
|
|
11441
|
+
// value captured on page 1 would be invisible on page 2.
|
|
11442
|
+
this.state.customer = { ...this.state.customer, ...storageService.getAllCustomerAttributesMap() };
|
|
11420
11443
|
this.setPaywall(paywall, context, campaign);
|
|
11421
11444
|
this.subscribers = new Set();
|
|
11422
11445
|
}
|
|
@@ -11454,6 +11477,41 @@ class PaywallState extends SimpleEventTarget {
|
|
|
11454
11477
|
setFormState(formId, state) {
|
|
11455
11478
|
this.setState({ ...this.state, formStates: { ...this.state.formStates, [formId]: state } });
|
|
11456
11479
|
}
|
|
11480
|
+
/**
|
|
11481
|
+
* Register the validator for a text-input field (keyed by formId). Used by the
|
|
11482
|
+
* implicit form-submit gate in NamiFlow.triggerActions and by the web element
|
|
11483
|
+
* when it mounts.
|
|
11484
|
+
*/
|
|
11485
|
+
registerFormFieldValidator(formId, validator) {
|
|
11486
|
+
this.setState({
|
|
11487
|
+
...this.state,
|
|
11488
|
+
formFieldValidators: { ...this.state.formFieldValidators, [formId]: validator },
|
|
11489
|
+
});
|
|
11490
|
+
}
|
|
11491
|
+
getFormFieldValidators() {
|
|
11492
|
+
return this.state.formFieldValidators;
|
|
11493
|
+
}
|
|
11494
|
+
/**
|
|
11495
|
+
* Replace the full set of field errors (formId → message). The gate writes the
|
|
11496
|
+
* result of validateForm here; an empty map clears all errors.
|
|
11497
|
+
*/
|
|
11498
|
+
setFormFieldErrors(errors) {
|
|
11499
|
+
this.setState({ ...this.state, formFieldErrors: { ...errors } });
|
|
11500
|
+
}
|
|
11501
|
+
setFormFieldError(formId, message) {
|
|
11502
|
+
this.setState({
|
|
11503
|
+
...this.state,
|
|
11504
|
+
formFieldErrors: { ...this.state.formFieldErrors, [formId]: message },
|
|
11505
|
+
});
|
|
11506
|
+
}
|
|
11507
|
+
clearFormFieldError(formId) {
|
|
11508
|
+
const nextErrors = { ...this.state.formFieldErrors };
|
|
11509
|
+
delete nextErrors[formId];
|
|
11510
|
+
this.setState({ ...this.state, formFieldErrors: nextErrors });
|
|
11511
|
+
}
|
|
11512
|
+
getFormFieldErrors() {
|
|
11513
|
+
return this.state.formFieldErrors;
|
|
11514
|
+
}
|
|
11457
11515
|
setTimerState(timerId, remainingSeconds, savedAt, hasEmittedCompletion) {
|
|
11458
11516
|
this.setState({
|
|
11459
11517
|
...this.state,
|
|
@@ -11672,7 +11730,12 @@ class PaywallState extends SimpleEventTarget {
|
|
|
11672
11730
|
this.emitEvent('user-interaction-changed', { enabled });
|
|
11673
11731
|
}
|
|
11674
11732
|
setUserTags(tags) {
|
|
11675
|
-
|
|
11733
|
+
// User tags ARE customer attributes for personalization — they must land in
|
|
11734
|
+
// `state.customer`, the store the `${customer.x}` smart-text resolver reads
|
|
11735
|
+
// (see valueFromSmartText: replacements.customer = state.customer). Writing a
|
|
11736
|
+
// separate `state.userTags` (read nowhere) meant setTags / setTagsFromForm
|
|
11737
|
+
// values never reached `${customer.firstName}` on a subsequent flow page.
|
|
11738
|
+
this.setState({ ...this.state, customer: { ...this.state.customer, ...tags } });
|
|
11676
11739
|
}
|
|
11677
11740
|
setCurrentSlideIndex(index) {
|
|
11678
11741
|
this.setState({ ...this.state, currentSlideIndex: index });
|
|
@@ -12549,9 +12612,11 @@ var NamiFlowActionFunction;
|
|
|
12549
12612
|
NamiFlowActionFunction["FLOW_ENABLED"] = "flowInteractionEnabled";
|
|
12550
12613
|
NamiFlowActionFunction["FLOW_DISABLED"] = "flowInteractionDisabled";
|
|
12551
12614
|
NamiFlowActionFunction["SET_TAGS"] = "setTags";
|
|
12615
|
+
NamiFlowActionFunction["SET_TAGS_FROM_FORM"] = "setTagsFromForm";
|
|
12552
12616
|
NamiFlowActionFunction["PAUSE"] = "flowPause";
|
|
12553
12617
|
NamiFlowActionFunction["RESUME"] = "flowResume";
|
|
12554
12618
|
NamiFlowActionFunction["SET_LAUNCH_CONTEXT"] = "setLaunchContext";
|
|
12619
|
+
NamiFlowActionFunction["SET_FLOW_PATH"] = "setFlowPath";
|
|
12555
12620
|
})(NamiFlowActionFunction || (NamiFlowActionFunction = {}));
|
|
12556
12621
|
const HandoffTag = {
|
|
12557
12622
|
SEQUENCE: '__handoff_sequence__',
|
|
@@ -13075,6 +13140,42 @@ class URLParams {
|
|
|
13075
13140
|
URLParams._instance = null;
|
|
13076
13141
|
const getUrlParams = () => URLParams.instance.params;
|
|
13077
13142
|
|
|
13143
|
+
/**
|
|
13144
|
+
* Coerce a single form-state value to the string form used by tags / customer
|
|
13145
|
+
* attributes / `{{ form.x }}` interpolation. Shared by the `setTagsFromForm`
|
|
13146
|
+
* flow action and FlowLiquidResolver so they stay in lockstep.
|
|
13147
|
+
*
|
|
13148
|
+
* Rules (order matters — Boolean is checked before number):
|
|
13149
|
+
* - `null` / `undefined` → skip (returns `undefined`)
|
|
13150
|
+
* - empty string `""` → skip
|
|
13151
|
+
* - `false` → "false", `true` → "true" (booleans are kept)
|
|
13152
|
+
* - `0` → "0" (zero is kept)
|
|
13153
|
+
* - integral number → no trailing ".0" (e.g. 3 → "3", 3.5 → "3.5")
|
|
13154
|
+
* - everything else → String(value)
|
|
13155
|
+
*/
|
|
13156
|
+
function formTagValue(value) {
|
|
13157
|
+
if (value === null || value === undefined) {
|
|
13158
|
+
return undefined;
|
|
13159
|
+
}
|
|
13160
|
+
if (value === '') {
|
|
13161
|
+
return undefined;
|
|
13162
|
+
}
|
|
13163
|
+
// Boolean BEFORE number: typeof true !== 'number', but be explicit so the
|
|
13164
|
+
// intent (and the false→"false" / true→"true" mapping) is unmistakable.
|
|
13165
|
+
if (typeof value === 'boolean') {
|
|
13166
|
+
return value ? 'true' : 'false';
|
|
13167
|
+
}
|
|
13168
|
+
if (typeof value === 'number') {
|
|
13169
|
+
if (!Number.isFinite(value)) {
|
|
13170
|
+
return undefined;
|
|
13171
|
+
}
|
|
13172
|
+
// JS `String` already renders integers without a trailing ".0"
|
|
13173
|
+
// (3 → "3", 3.5 → "3.5"), which is exactly the contract we want.
|
|
13174
|
+
return String(value);
|
|
13175
|
+
}
|
|
13176
|
+
return String(value);
|
|
13177
|
+
}
|
|
13178
|
+
|
|
13078
13179
|
const LIQUID_VAR_REGEX = /\{\{\s*([^}]+?)\s*\}\}/g;
|
|
13079
13180
|
/**
|
|
13080
13181
|
* Resolver for liquid-style flow variables, without requiring namespace prefixes.
|
|
@@ -13099,6 +13200,13 @@ class FlowLiquidResolver {
|
|
|
13099
13200
|
}
|
|
13100
13201
|
return undefined;
|
|
13101
13202
|
}
|
|
13203
|
+
// Handle form field paths: {{ form.fieldId }} reads the live form state and
|
|
13204
|
+
// coerces it through the same helper used by setTagsFromForm.
|
|
13205
|
+
if (key.startsWith("form.")) {
|
|
13206
|
+
const fieldId = key.substring("form.".length);
|
|
13207
|
+
const value = PaywallState.currentProvider?.state.formStates[fieldId];
|
|
13208
|
+
return formTagValue(value);
|
|
13209
|
+
}
|
|
13102
13210
|
switch (key) {
|
|
13103
13211
|
case "campaignId":
|
|
13104
13212
|
return screenState?.campaign?.rule;
|
|
@@ -13150,6 +13258,8 @@ class FlowLiquidResolver {
|
|
|
13150
13258
|
const s = PaywallState.currentProvider?.state;
|
|
13151
13259
|
return s?.groups?.find(g => g.id === s.currentGroupId)?.ref ?? undefined;
|
|
13152
13260
|
}
|
|
13261
|
+
case "flowPath":
|
|
13262
|
+
return flow?.flowPath;
|
|
13153
13263
|
default:
|
|
13154
13264
|
return undefined;
|
|
13155
13265
|
}
|
|
@@ -14100,6 +14210,67 @@ let NamiCustomerManager$2 = class NamiCustomerManager {
|
|
|
14100
14210
|
};
|
|
14101
14211
|
NamiCustomerManager$2.instance = new NamiCustomerManager$2();
|
|
14102
14212
|
|
|
14213
|
+
/**
|
|
14214
|
+
* Pure, platform-agnostic text-input validation. Shared by the flow submit gate
|
|
14215
|
+
* (sdk/core) and the web Lit element (sdk/web). The rules are identical to the
|
|
14216
|
+
* Apple (NAM-1142) and Android (NAM-1143) ports.
|
|
14217
|
+
*/
|
|
14218
|
+
const DEFAULT_MESSAGES = {
|
|
14219
|
+
required: 'This field is required',
|
|
14220
|
+
email: 'Please enter a valid email address',
|
|
14221
|
+
name: 'Please enter at least 2 characters',
|
|
14222
|
+
};
|
|
14223
|
+
/**
|
|
14224
|
+
* An email must contain an `@` and a domain with a dot, e.g. `a@b.co`.
|
|
14225
|
+
* `a@b` (no dotted domain) and `nope` (no `@`) are invalid.
|
|
14226
|
+
*/
|
|
14227
|
+
const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
14228
|
+
/**
|
|
14229
|
+
* Validate a single field. Returns `undefined` when valid, otherwise a default
|
|
14230
|
+
* error message for the failing rule. Callers that want author-supplied text
|
|
14231
|
+
* should use {@link validateForm}.
|
|
14232
|
+
*
|
|
14233
|
+
* Rules:
|
|
14234
|
+
* - required && blank → invalid
|
|
14235
|
+
* - email && non-blank && not email-format → invalid
|
|
14236
|
+
* - name && non-blank && len < 2 → invalid
|
|
14237
|
+
* - freeText → only the required rule applies
|
|
14238
|
+
*/
|
|
14239
|
+
function validateTextInput(type, required, value) {
|
|
14240
|
+
const text = typeof value === 'string' ? value : value == null ? '' : String(value);
|
|
14241
|
+
const trimmed = text.trim();
|
|
14242
|
+
if (required && trimmed.length === 0) {
|
|
14243
|
+
return DEFAULT_MESSAGES.required;
|
|
14244
|
+
}
|
|
14245
|
+
if (trimmed.length === 0) {
|
|
14246
|
+
// Blank optional value: no format rule applies.
|
|
14247
|
+
return undefined;
|
|
14248
|
+
}
|
|
14249
|
+
if (type === 'email' && !EMAIL_REGEX.test(trimmed)) {
|
|
14250
|
+
return DEFAULT_MESSAGES.email;
|
|
14251
|
+
}
|
|
14252
|
+
if (type === 'name' && trimmed.length < 2) {
|
|
14253
|
+
return DEFAULT_MESSAGES.name;
|
|
14254
|
+
}
|
|
14255
|
+
return undefined;
|
|
14256
|
+
}
|
|
14257
|
+
/**
|
|
14258
|
+
* Validate every registered field against the current form state. Returns a map
|
|
14259
|
+
* of `formId → error message` for invalid fields only (valid fields are omitted).
|
|
14260
|
+
* The validator's own `message` is preferred; otherwise the per-rule default
|
|
14261
|
+
* from {@link validateTextInput} is used.
|
|
14262
|
+
*/
|
|
14263
|
+
function validateForm(validators, formStates) {
|
|
14264
|
+
const errors = {};
|
|
14265
|
+
for (const [formId, validator] of Object.entries(validators)) {
|
|
14266
|
+
const defaultError = validateTextInput(validator.type, validator.required, formStates[formId]);
|
|
14267
|
+
if (defaultError !== undefined) {
|
|
14268
|
+
errors[formId] = validator.message ?? defaultError;
|
|
14269
|
+
}
|
|
14270
|
+
}
|
|
14271
|
+
return errors;
|
|
14272
|
+
}
|
|
14273
|
+
|
|
14103
14274
|
class BasicNamiFlow {
|
|
14104
14275
|
constructor(flowObject = {}) {
|
|
14105
14276
|
this.id = flowObject.id ?? '';
|
|
@@ -14121,6 +14292,7 @@ class NamiFlow extends BasicNamiFlow {
|
|
|
14121
14292
|
this.activeHandoffSequence = null;
|
|
14122
14293
|
this.currentScreenState = null;
|
|
14123
14294
|
this.isPaused = false;
|
|
14295
|
+
this.flowPath = "";
|
|
14124
14296
|
this.timerStates = {};
|
|
14125
14297
|
this.campaign = campaign;
|
|
14126
14298
|
this.component = paywall;
|
|
@@ -14331,12 +14503,40 @@ class NamiFlow extends BasicNamiFlow {
|
|
|
14331
14503
|
}
|
|
14332
14504
|
return lifecycles;
|
|
14333
14505
|
}
|
|
14506
|
+
/**
|
|
14507
|
+
* A lifecycle chain is a "form submit" if any phase (before/action/after) runs
|
|
14508
|
+
* one of the advancing functions: setTagsFromForm, flowNext, or flowNav. The
|
|
14509
|
+
* POC submit buttons use flowNext/flowNav (no dedicated submit action), so the
|
|
14510
|
+
* gate keys off these. Close/dismiss/back/pause are deliberately excluded —
|
|
14511
|
+
* they must never be blocked by validation. Mirrors Apple
|
|
14512
|
+
* NamiFlowControl.isFormSubmitLifecycle / Android NamiFlow.isFormSubmitLifecycle.
|
|
14513
|
+
*/
|
|
14514
|
+
isFormSubmitLifecycle(lifecycles) {
|
|
14515
|
+
const submitFns = [
|
|
14516
|
+
NamiFlowActionFunction.SET_TAGS_FROM_FORM,
|
|
14517
|
+
NamiFlowActionFunction.NEXT,
|
|
14518
|
+
NamiFlowActionFunction.NAVIGATE,
|
|
14519
|
+
];
|
|
14520
|
+
return lifecycles.some(cycle => [cycle.before, cycle.action, cycle.after].some(actions => actions?.some(action => submitFns.includes(action.function))));
|
|
14521
|
+
}
|
|
14334
14522
|
triggerActions(actionId, component, data) {
|
|
14335
14523
|
const lifecycles = this.lifecycles(actionId);
|
|
14336
14524
|
if (!lifecycles)
|
|
14337
14525
|
return;
|
|
14338
14526
|
this.flowLog(`triggerActions(${actionId}) — step=${this.currentFlowStep?.id}, isPaused=${this.isPaused}`);
|
|
14339
14527
|
this.currentButton = component;
|
|
14528
|
+
// Implicit form-submit validation gate (the NAM-1143 on-device lesson).
|
|
14529
|
+
// When this action advances the flow and validators are registered, validate
|
|
14530
|
+
// first; write the errors and halt the chain if any field is invalid.
|
|
14531
|
+
const validators = PaywallState.currentProvider?.state.formFieldValidators ?? {};
|
|
14532
|
+
if (this.isFormSubmitLifecycle(lifecycles) && Object.keys(validators).length) {
|
|
14533
|
+
const errors = validateForm(validators, this.getFormData());
|
|
14534
|
+
PaywallState.currentProvider?.setFormFieldErrors(errors);
|
|
14535
|
+
if (Object.keys(errors).length) {
|
|
14536
|
+
this.flowLog(`triggerActions(${actionId}) — HALTED by form validation: ${JSON.stringify(errors)}`);
|
|
14537
|
+
return;
|
|
14538
|
+
}
|
|
14539
|
+
}
|
|
14340
14540
|
this.executeFullLifecycles(lifecycles, data);
|
|
14341
14541
|
}
|
|
14342
14542
|
executeFullLifecycles(lifecycles, data) {
|
|
@@ -14543,6 +14743,22 @@ class NamiFlow extends BasicNamiFlow {
|
|
|
14543
14743
|
});
|
|
14544
14744
|
}
|
|
14545
14745
|
break;
|
|
14746
|
+
case NamiFlowActionFunction.SET_TAGS_FROM_FORM: {
|
|
14747
|
+
const formData = this.getFormData();
|
|
14748
|
+
const tags = {};
|
|
14749
|
+
Object.entries(formData).forEach(([key, raw]) => {
|
|
14750
|
+
const value = formTagValue(raw);
|
|
14751
|
+
if (value === undefined) {
|
|
14752
|
+
return; // skip null / "" (false and 0 are kept by formTagValue)
|
|
14753
|
+
}
|
|
14754
|
+
tags[key] = value;
|
|
14755
|
+
NamiCustomerManager$2.setCustomerAttribute(key, value);
|
|
14756
|
+
});
|
|
14757
|
+
if (Object.keys(tags).length) {
|
|
14758
|
+
PaywallState.setUserTags(tags);
|
|
14759
|
+
}
|
|
14760
|
+
break;
|
|
14761
|
+
}
|
|
14546
14762
|
case NamiFlowActionFunction.SET_LAUNCH_CONTEXT: {
|
|
14547
14763
|
// Two supported shapes: { customAttributes: {...} } (nav-action wire shape)
|
|
14548
14764
|
// and { key, value } (used by Apple/Android/Roku). We deliberately do NOT
|
|
@@ -14557,6 +14773,13 @@ class NamiFlow extends BasicNamiFlow {
|
|
|
14557
14773
|
}
|
|
14558
14774
|
break;
|
|
14559
14775
|
}
|
|
14776
|
+
case NamiFlowActionFunction.SET_FLOW_PATH: {
|
|
14777
|
+
const flowPath = action.parameters?.flowPath;
|
|
14778
|
+
if (typeof flowPath === "string") {
|
|
14779
|
+
this.flowPath = flowPath;
|
|
14780
|
+
}
|
|
14781
|
+
break;
|
|
14782
|
+
}
|
|
14560
14783
|
default:
|
|
14561
14784
|
logger.warn(`Missing action handler for ${action.function}`, action);
|
|
14562
14785
|
break;
|
|
@@ -64765,4 +64988,4 @@ var internal = /*#__PURE__*/Object.freeze({
|
|
|
64765
64988
|
NamiPurchaseManager: NamiPurchaseManager
|
|
64766
64989
|
});
|
|
64767
64990
|
|
|
64768
|
-
export { ALREADY_CONFIGURED, ANONYMOUS_MODE, ANONYMOUS_MODE_ALREADY_OFF, ANONYMOUS_MODE_ALREADY_ON, ANONYMOUS_MODE_LOGIN_NOT_ALLOWED, ANONYMOUS_UUID, APIError, API_ACTIVE_ENTITLEMENTS, API_CAMPAIGN_RULES, API_CAMPAIGN_SESSION_TIMESTAMP, API_CONFIG, API_MAX_CALLS_LIMIT, API_PAYWALLS, API_PRODUCTS, API_RETRY_DELAY_SEC, API_TIMEOUT_LIMIT, API_VERSION, AUTH_DEVICE, AVAILABLE_ACTIVE_ENTITLEMENTS_CHANGED, AVAILABLE_CAMPAIGNS_CHANGED, AccountStateAction, AnonymousCDPError, AnonymousLoginError, AnonymousModeAlreadyOffError, AnonymousModeAlreadyOnError, BASE_STAGING_URL, BASE_URL, BASE_URL_PATH, BadRequestError, BasicNamiFlow, BorderMap, BorderSideMap, CAMPAIGN_NOT_AVAILABLE, CUSTOMER_ATTRIBUTES_KEY_PREFIX, CUSTOMER_JOURNEY_STATE_CHANGED, CUSTOM_HOST_PREFIX, CampaignNotAvailableError, CampaignRuleConversionEventType, CampaignRuleRepository, Capabilities, ClientError, ConfigRepository, ConflictError, CustomerJourneyRepository, DEVELOPMENT, DEVICE_API_TIMEOUT_LIMIT, DEVICE_ID_NOT_SET, DEVICE_ID_REQUIRED, DISABLE_ASYNC_LOGIN_LOGOUT, DeviceIDRequiredError, DeviceRepository, EXTENDED_CLIENT_INFO_DELIMITER, EXTENDED_CLIENT_INFO_PREFIX, EXTENDED_PLATFORM, EXTENDED_PLATFORM_VERSION, EXTERNAL_ID_REQUIRED, EntitlementRepository, EntitlementUtils, ExternalIDRequiredError, FLOW_SCREENS_NOT_AVAILABLE, FlowScreensNotAvailableError, HTML_REGEX, INITIAL_APP_CONFIG, INITIAL_CAMPAIGN_RULES, INITIAL_PAYWALLS, INITIAL_PRODUCTS, INITIAL_SESSION_COUNTER_VALUE, INITIAL_SUCCESS, InternalServerError, KEY_SESSION_COUNTER, LIQUID_VARIABLE_REGEX, LOCAL_NAMI_ENTITLEMENTS, LOG_HTTP_REQUESTS, LOG_HTTP_TRAFFIC, LaunchCampaignError, LaunchContextResolver, LogLevel, NAMI_CONFIGURATION, NAMI_CUSTOMER_JOURNEY_STATE, NAMI_LANGUAGE_CODE, NAMI_LAST_IMPRESSION_ID, NAMI_LAUNCH_ID, NAMI_PROFILE, NAMI_PURCHASE_CHANNEL, NAMI_PURCHASE_IMPRESSION_ID, NAMI_SDK_PACKAGE_VERSION, NAMI_SDK_VERSION, NAMI_SESSION_ID, NAMI_STORAGE_KEYS, Nami, NamiAPI, NamiAnimationType, NamiCampaignManager$1 as NamiCampaignManager, NamiCampaignRuleType, NamiConditionEvaluator, NamiCustomerManager$1 as NamiCustomerManager, NamiEntitlementManager$1 as NamiEntitlementManager, NamiEventEmitter, NamiFlow, NamiFlowActionFunction, NamiFlowManager$1 as NamiFlowManager, NamiFlowStepType, NamiPaywallAction, NamiPaywallManager$1 as NamiPaywallManager, PaywallManagerEvents as NamiPaywallManagerEvents, NamiPurchaseManager$1 as NamiPurchaseManager, NamiRefs, NamiReservedActions, NotFoundError, PAYWALL_ACTION_EVENT, PLATFORM_ID_REQUIRED, PRODUCTION, PaywallManagerEvents, PaywallRepository, PaywallState, PlacementLabelResolver, PlatformIDRequiredError, ProductRepository, RECONFIG_SUCCESS, RetryLimitExceededError, SDKNotInitializedError, SDK_NOT_INITIALIZED, SERVER_NAMI_ENTITLEMENTS, SESSION_REQUIRED, SHOULD_SHOW_LOADING_INDICATOR, SKU_TEXT_REGEX, SMART_TEXT_PATTERN, STARTUP_TELEMETRY, STATUS_BAD_REQUEST, STATUS_CONFLICT, STATUS_INTERNAL_SERVER_ERROR, STATUS_NOT_FOUND, STATUS_SUCCESS, SessionService, SimpleEventTarget, StorageService, UNABLE_TO_UPDATE_CDP_ID, USE_STAGING_API, VALIDATE_PRODUCT_GROUPS, VAR_REGEX, NamiProfileManager$1 as _NamiProfileManager, internal as _internal, activateEntitlementByPurchase, activeEntitlements, aggregateScreenreaderText, allCampaigns, allPaywalls, applyEntitlementActivation, audienceSplitPosition, bestUrlCampaignMatch, bigintToUuid, checkAnySkuHasPromoOffer, checkAnySkuHasTrialOffer, convertISO8601PeriodToText, convertLocale, convertOfferToPricingPhase, createNamiEntitlements, currentSku, empty, extractStandardPricingPhases, formatDate, formattedPrice, generateUUID, getApiCampaigns, getApiPaywalls, getBaseUrl, getBillingPeriodNumber, getCurrencyFormat, getDeviceData, getDeviceFormFactor, getDeviceScaleFactor, getEffectiveWebStyle, getEntitlementRefIdsForSku, getExtendedClientInfo, getFreeTrialPeriod, getInitialCampaigns, getInitialPaywalls, getPaywall, getPaywallDataFromLabel, getPercentagePriceDifference, getPeriodNumberInDays, getPeriodNumberInWeeks, getPlatformAdapters, getPriceDifference, getPricePerMonth, getProductDetail, getPurchaseAdapter, getReferenceSku, getSkuProductDetailKeys, getSkuSmartTextValue, getSlideSmartTextValue, getStandardBillingPeriod, getTranslate, getUrlParams, handleErrors, hasAllPaywalls, hasCapability, hasPurchaseManagement, initialState, invokeHandler, isAnonymousMode, isInitialConfigCompressed, isNamiFlowCampaign, isSubscription, isValidISODate, isValidUrl, logger, mapAnonymousCampaigns, namiBuySKU, normalizeLaunchContext, parseToSemver, postConversion, productDetail, registerPlatformAdapters, registerPurchaseAdapter, selectSegment, setActiveNamiEntitlements, shouldValidateProductGroups, skuItems, skuMapFromEntitlements, storageService, toDouble, toNamiEntitlements, toNamiSKU, tryParseB64Gzip, tryParseJson, updateRelatedSKUsForNamiEntitlement, uuidFromSplitPosition, validateMinSDKVersion };
|
|
64991
|
+
export { ALREADY_CONFIGURED, ANONYMOUS_MODE, ANONYMOUS_MODE_ALREADY_OFF, ANONYMOUS_MODE_ALREADY_ON, ANONYMOUS_MODE_LOGIN_NOT_ALLOWED, ANONYMOUS_UUID, APIError, API_ACTIVE_ENTITLEMENTS, API_CAMPAIGN_RULES, API_CAMPAIGN_SESSION_TIMESTAMP, API_CONFIG, API_MAX_CALLS_LIMIT, API_PAYWALLS, API_PRODUCTS, API_RETRY_DELAY_SEC, API_TIMEOUT_LIMIT, API_VERSION, AUTH_DEVICE, AVAILABLE_ACTIVE_ENTITLEMENTS_CHANGED, AVAILABLE_CAMPAIGNS_CHANGED, AccountStateAction, AnonymousCDPError, AnonymousLoginError, AnonymousModeAlreadyOffError, AnonymousModeAlreadyOnError, BASE_STAGING_URL, BASE_URL, BASE_URL_PATH, BadRequestError, BasicNamiFlow, BorderMap, BorderSideMap, CAMPAIGN_NOT_AVAILABLE, CUSTOMER_ATTRIBUTES_KEY_PREFIX, CUSTOMER_JOURNEY_STATE_CHANGED, CUSTOM_HOST_PREFIX, CampaignNotAvailableError, CampaignRuleConversionEventType, CampaignRuleRepository, Capabilities, ClientError, ConfigRepository, ConflictError, CustomerJourneyRepository, DEVELOPMENT, DEVICE_API_TIMEOUT_LIMIT, DEVICE_ID_NOT_SET, DEVICE_ID_REQUIRED, DISABLE_ASYNC_LOGIN_LOGOUT, DeviceIDRequiredError, DeviceRepository, EXTENDED_CLIENT_INFO_DELIMITER, EXTENDED_CLIENT_INFO_PREFIX, EXTENDED_PLATFORM, EXTENDED_PLATFORM_VERSION, EXTERNAL_ID_REQUIRED, EntitlementRepository, EntitlementUtils, ExternalIDRequiredError, FLOW_SCREENS_NOT_AVAILABLE, FlowScreensNotAvailableError, HTML_REGEX, INITIAL_APP_CONFIG, INITIAL_CAMPAIGN_RULES, INITIAL_PAYWALLS, INITIAL_PRODUCTS, INITIAL_SESSION_COUNTER_VALUE, INITIAL_SUCCESS, InternalServerError, KEY_SESSION_COUNTER, LIQUID_VARIABLE_REGEX, LOCAL_NAMI_ENTITLEMENTS, LOG_HTTP_REQUESTS, LOG_HTTP_TRAFFIC, LaunchCampaignError, LaunchContextResolver, LogLevel, NAMI_CONFIGURATION, NAMI_CUSTOMER_JOURNEY_STATE, NAMI_LANGUAGE_CODE, NAMI_LAST_IMPRESSION_ID, NAMI_LAUNCH_ID, NAMI_PROFILE, NAMI_PURCHASE_CHANNEL, NAMI_PURCHASE_IMPRESSION_ID, NAMI_SDK_PACKAGE_VERSION, NAMI_SDK_VERSION, NAMI_SESSION_ID, NAMI_STORAGE_KEYS, Nami, NamiAPI, NamiAnimationType, NamiCampaignManager$1 as NamiCampaignManager, NamiCampaignRuleType, NamiConditionEvaluator, NamiCustomerManager$1 as NamiCustomerManager, NamiEntitlementManager$1 as NamiEntitlementManager, NamiEventEmitter, NamiFlow, NamiFlowActionFunction, NamiFlowManager$1 as NamiFlowManager, NamiFlowStepType, NamiPaywallAction, NamiPaywallManager$1 as NamiPaywallManager, PaywallManagerEvents as NamiPaywallManagerEvents, NamiPurchaseManager$1 as NamiPurchaseManager, NamiRefs, NamiReservedActions, NotFoundError, PAYWALL_ACTION_EVENT, PLATFORM_ID_REQUIRED, PRODUCTION, PaywallManagerEvents, PaywallRepository, PaywallState, PlacementLabelResolver, PlatformIDRequiredError, ProductRepository, RECONFIG_SUCCESS, RetryLimitExceededError, SDKNotInitializedError, SDK_NOT_INITIALIZED, SERVER_NAMI_ENTITLEMENTS, SESSION_REQUIRED, SHOULD_SHOW_LOADING_INDICATOR, SKU_TEXT_REGEX, SMART_TEXT_PATTERN, STARTUP_TELEMETRY, STATUS_BAD_REQUEST, STATUS_CONFLICT, STATUS_INTERNAL_SERVER_ERROR, STATUS_NOT_FOUND, STATUS_SUCCESS, SessionService, SimpleEventTarget, StorageService, UNABLE_TO_UPDATE_CDP_ID, USE_STAGING_API, VALIDATE_PRODUCT_GROUPS, VAR_REGEX, NamiProfileManager$1 as _NamiProfileManager, internal as _internal, activateEntitlementByPurchase, activeEntitlements, aggregateScreenreaderText, allCampaigns, allPaywalls, applyEntitlementActivation, audienceSplitPosition, bestUrlCampaignMatch, bigintToUuid, checkAnySkuHasPromoOffer, checkAnySkuHasTrialOffer, convertISO8601PeriodToText, convertLocale, convertOfferToPricingPhase, createNamiEntitlements, currentSku, empty, extractStandardPricingPhases, formatDate, formattedPrice, generateUUID, getApiCampaigns, getApiPaywalls, getBaseUrl, getBillingPeriodNumber, getCurrencyFormat, getDeviceData, getDeviceFormFactor, getDeviceScaleFactor, getEffectiveWebStyle, getEntitlementRefIdsForSku, getExtendedClientInfo, getFreeTrialPeriod, getInitialCampaigns, getInitialPaywalls, getPaywall, getPaywallDataFromLabel, getPercentagePriceDifference, getPeriodNumberInDays, getPeriodNumberInWeeks, getPlatformAdapters, getPriceDifference, getPricePerMonth, getProductDetail, getPurchaseAdapter, getReferenceSku, getSkuProductDetailKeys, getSkuSmartTextValue, getSlideSmartTextValue, getStandardBillingPeriod, getTranslate, getUrlParams, handleErrors, hasAllPaywalls, hasCapability, hasPurchaseManagement, initialState, invokeHandler, isAnonymousMode, isInitialConfigCompressed, isNamiFlowCampaign, isSubscription, isValidISODate, isValidUrl, logger, mapAnonymousCampaigns, namiBuySKU, normalizeLaunchContext, parseToSemver, postConversion, productDetail, registerPlatformAdapters, registerPurchaseAdapter, selectSegment, setActiveNamiEntitlements, shouldValidateProductGroups, skuItems, skuMapFromEntitlements, storageService, toDouble, toNamiEntitlements, toNamiSKU, tryParseB64Gzip, tryParseJson, updateRelatedSKUsForNamiEntitlement, uuidFromSplitPosition, validateForm, validateMinSDKVersion, validateTextInput };
|
package/package.json
CHANGED