@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 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-rc.202606180430",
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
- this.setState({ ...this.state, userTags: { ...this.state.userTags, ...tags } });
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-rc.202606180430",
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
- this.setState({ ...this.state, userTags: { ...this.state.userTags, ...tags } });
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@namiml/sdk-core",
3
- "version": "3.4.3-rc.202606180430",
3
+ "version": "3.4.3",
4
4
  "description": "Platform-agnostic core for the Nami SDK — business logic, API, types, and state management",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",