@namiml/sdk-core 3.4.3-dev.202606180014 → 3.4.3-dev.202606181527

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-dev.202606180014",
101
+ NAMI_SDK_PACKAGE_VERSION: exports.NAMI_SDK_PACKAGE_VERSION = "3.4.3-dev.202606181527",
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,6 +12614,7 @@ 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";
@@ -13077,6 +13141,42 @@ class URLParams {
13077
13141
  URLParams._instance = null;
13078
13142
  const getUrlParams = () => URLParams.instance.params;
13079
13143
 
13144
+ /**
13145
+ * Coerce a single form-state value to the string form used by tags / customer
13146
+ * attributes / `{{ form.x }}` interpolation. Shared by the `setTagsFromForm`
13147
+ * flow action and FlowLiquidResolver so they stay in lockstep.
13148
+ *
13149
+ * Rules (order matters — Boolean is checked before number):
13150
+ * - `null` / `undefined` → skip (returns `undefined`)
13151
+ * - empty string `""` → skip
13152
+ * - `false` → "false", `true` → "true" (booleans are kept)
13153
+ * - `0` → "0" (zero is kept)
13154
+ * - integral number → no trailing ".0" (e.g. 3 → "3", 3.5 → "3.5")
13155
+ * - everything else → String(value)
13156
+ */
13157
+ function formTagValue(value) {
13158
+ if (value === null || value === undefined) {
13159
+ return undefined;
13160
+ }
13161
+ if (value === '') {
13162
+ return undefined;
13163
+ }
13164
+ // Boolean BEFORE number: typeof true !== 'number', but be explicit so the
13165
+ // intent (and the false→"false" / true→"true" mapping) is unmistakable.
13166
+ if (typeof value === 'boolean') {
13167
+ return value ? 'true' : 'false';
13168
+ }
13169
+ if (typeof value === 'number') {
13170
+ if (!Number.isFinite(value)) {
13171
+ return undefined;
13172
+ }
13173
+ // JS `String` already renders integers without a trailing ".0"
13174
+ // (3 → "3", 3.5 → "3.5"), which is exactly the contract we want.
13175
+ return String(value);
13176
+ }
13177
+ return String(value);
13178
+ }
13179
+
13080
13180
  const LIQUID_VAR_REGEX = /\{\{\s*([^}]+?)\s*\}\}/g;
13081
13181
  /**
13082
13182
  * Resolver for liquid-style flow variables, without requiring namespace prefixes.
@@ -13101,6 +13201,13 @@ class FlowLiquidResolver {
13101
13201
  }
13102
13202
  return undefined;
13103
13203
  }
13204
+ // Handle form field paths: {{ form.fieldId }} reads the live form state and
13205
+ // coerces it through the same helper used by setTagsFromForm.
13206
+ if (key.startsWith("form.")) {
13207
+ const fieldId = key.substring("form.".length);
13208
+ const value = PaywallState.currentProvider?.state.formStates[fieldId];
13209
+ return formTagValue(value);
13210
+ }
13104
13211
  switch (key) {
13105
13212
  case "campaignId":
13106
13213
  return screenState?.campaign?.rule;
@@ -14102,6 +14209,67 @@ let NamiCustomerManager$2 = class NamiCustomerManager {
14102
14209
  };
14103
14210
  NamiCustomerManager$2.instance = new NamiCustomerManager$2();
14104
14211
 
14212
+ /**
14213
+ * Pure, platform-agnostic text-input validation. Shared by the flow submit gate
14214
+ * (sdk/core) and the web Lit element (sdk/web). The rules are identical to the
14215
+ * Apple (NAM-1142) and Android (NAM-1143) ports.
14216
+ */
14217
+ const DEFAULT_MESSAGES = {
14218
+ required: 'This field is required',
14219
+ email: 'Please enter a valid email address',
14220
+ name: 'Please enter at least 2 characters',
14221
+ };
14222
+ /**
14223
+ * An email must contain an `@` and a domain with a dot, e.g. `a@b.co`.
14224
+ * `a@b` (no dotted domain) and `nope` (no `@`) are invalid.
14225
+ */
14226
+ const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
14227
+ /**
14228
+ * Validate a single field. Returns `undefined` when valid, otherwise a default
14229
+ * error message for the failing rule. Callers that want author-supplied text
14230
+ * should use {@link validateForm}.
14231
+ *
14232
+ * Rules:
14233
+ * - required && blank → invalid
14234
+ * - email && non-blank && not email-format → invalid
14235
+ * - name && non-blank && len < 2 → invalid
14236
+ * - freeText → only the required rule applies
14237
+ */
14238
+ function validateTextInput(type, required, value) {
14239
+ const text = typeof value === 'string' ? value : value == null ? '' : String(value);
14240
+ const trimmed = text.trim();
14241
+ if (required && trimmed.length === 0) {
14242
+ return DEFAULT_MESSAGES.required;
14243
+ }
14244
+ if (trimmed.length === 0) {
14245
+ // Blank optional value: no format rule applies.
14246
+ return undefined;
14247
+ }
14248
+ if (type === 'email' && !EMAIL_REGEX.test(trimmed)) {
14249
+ return DEFAULT_MESSAGES.email;
14250
+ }
14251
+ if (type === 'name' && trimmed.length < 2) {
14252
+ return DEFAULT_MESSAGES.name;
14253
+ }
14254
+ return undefined;
14255
+ }
14256
+ /**
14257
+ * Validate every registered field against the current form state. Returns a map
14258
+ * of `formId → error message` for invalid fields only (valid fields are omitted).
14259
+ * The validator's own `message` is preferred; otherwise the per-rule default
14260
+ * from {@link validateTextInput} is used.
14261
+ */
14262
+ function validateForm(validators, formStates) {
14263
+ const errors = {};
14264
+ for (const [formId, validator] of Object.entries(validators)) {
14265
+ const defaultError = validateTextInput(validator.type, validator.required, formStates[formId]);
14266
+ if (defaultError !== undefined) {
14267
+ errors[formId] = validator.message ?? defaultError;
14268
+ }
14269
+ }
14270
+ return errors;
14271
+ }
14272
+
14105
14273
  class BasicNamiFlow {
14106
14274
  constructor(flowObject = {}) {
14107
14275
  this.id = flowObject.id ?? '';
@@ -14333,12 +14501,40 @@ class NamiFlow extends BasicNamiFlow {
14333
14501
  }
14334
14502
  return lifecycles;
14335
14503
  }
14504
+ /**
14505
+ * A lifecycle chain is a "form submit" if any phase (before/action/after) runs
14506
+ * one of the advancing functions: setTagsFromForm, flowNext, or flowNav. The
14507
+ * POC submit buttons use flowNext/flowNav (no dedicated submit action), so the
14508
+ * gate keys off these. Close/dismiss/back/pause are deliberately excluded —
14509
+ * they must never be blocked by validation. Mirrors Apple
14510
+ * NamiFlowControl.isFormSubmitLifecycle / Android NamiFlow.isFormSubmitLifecycle.
14511
+ */
14512
+ isFormSubmitLifecycle(lifecycles) {
14513
+ const submitFns = [
14514
+ exports.NamiFlowActionFunction.SET_TAGS_FROM_FORM,
14515
+ exports.NamiFlowActionFunction.NEXT,
14516
+ exports.NamiFlowActionFunction.NAVIGATE,
14517
+ ];
14518
+ return lifecycles.some(cycle => [cycle.before, cycle.action, cycle.after].some(actions => actions?.some(action => submitFns.includes(action.function))));
14519
+ }
14336
14520
  triggerActions(actionId, component, data) {
14337
14521
  const lifecycles = this.lifecycles(actionId);
14338
14522
  if (!lifecycles)
14339
14523
  return;
14340
14524
  this.flowLog(`triggerActions(${actionId}) — step=${this.currentFlowStep?.id}, isPaused=${this.isPaused}`);
14341
14525
  this.currentButton = component;
14526
+ // Implicit form-submit validation gate (the NAM-1143 on-device lesson).
14527
+ // When this action advances the flow and validators are registered, validate
14528
+ // first; write the errors and halt the chain if any field is invalid.
14529
+ const validators = PaywallState.currentProvider?.state.formFieldValidators ?? {};
14530
+ if (this.isFormSubmitLifecycle(lifecycles) && Object.keys(validators).length) {
14531
+ const errors = validateForm(validators, this.getFormData());
14532
+ PaywallState.currentProvider?.setFormFieldErrors(errors);
14533
+ if (Object.keys(errors).length) {
14534
+ this.flowLog(`triggerActions(${actionId}) — HALTED by form validation: ${JSON.stringify(errors)}`);
14535
+ return;
14536
+ }
14537
+ }
14342
14538
  this.executeFullLifecycles(lifecycles, data);
14343
14539
  }
14344
14540
  executeFullLifecycles(lifecycles, data) {
@@ -14545,6 +14741,22 @@ class NamiFlow extends BasicNamiFlow {
14545
14741
  });
14546
14742
  }
14547
14743
  break;
14744
+ case exports.NamiFlowActionFunction.SET_TAGS_FROM_FORM: {
14745
+ const formData = this.getFormData();
14746
+ const tags = {};
14747
+ Object.entries(formData).forEach(([key, raw]) => {
14748
+ const value = formTagValue(raw);
14749
+ if (value === undefined) {
14750
+ return; // skip null / "" (false and 0 are kept by formTagValue)
14751
+ }
14752
+ tags[key] = value;
14753
+ NamiCustomerManager$2.setCustomerAttribute(key, value);
14754
+ });
14755
+ if (Object.keys(tags).length) {
14756
+ PaywallState.setUserTags(tags);
14757
+ }
14758
+ break;
14759
+ }
14548
14760
  case exports.NamiFlowActionFunction.SET_LAUNCH_CONTEXT: {
14549
14761
  // Two supported shapes: { customAttributes: {...} } (nav-action wire shape)
14550
14762
  // and { key, value } (used by Apple/Android/Roku). We deliberately do NOT
@@ -64908,4 +65120,6 @@ exports.tryParseB64Gzip = tryParseB64Gzip;
64908
65120
  exports.tryParseJson = tryParseJson;
64909
65121
  exports.updateRelatedSKUsForNamiEntitlement = updateRelatedSKUsForNamiEntitlement;
64910
65122
  exports.uuidFromSplitPosition = uuidFromSplitPosition;
65123
+ exports.validateForm = validateForm;
64911
65124
  exports.validateMinSDKVersion = validateMinSDKVersion;
65125
+ exports.validateTextInput = validateTextInput;
package/dist/index.d.ts CHANGED
@@ -133,6 +133,7 @@ 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
139
  SET_LAUNCH_CONTEXT = "setLaunchContext"
@@ -553,6 +554,36 @@ type TToggleButtonComponent = TBaseComponent & {
553
554
  mode?: "radio" | "toggle";
554
555
  value?: string;
555
556
  };
557
+ type TTextInputComponent = TBaseComponent & {
558
+ component: "textInput";
559
+ type?: "name" | "email";
560
+ formId: string;
561
+ /** Required flag — the real (misspelled) wire key. `required` never populates. */
562
+ reqed?: boolean;
563
+ validateOn?: "submit" | "change";
564
+ placeholderText?: string;
565
+ placeholderFontColor?: string;
566
+ labelText?: string;
567
+ labelPosition?: "top" | "none";
568
+ labelFontColor?: string;
569
+ labelFontName?: string;
570
+ labelFontSize?: number | string;
571
+ validationMessage?: string;
572
+ validationTextFontColor?: string;
573
+ validationTextFontName?: string;
574
+ validationTextFontSize?: number | string;
575
+ fontColor?: string;
576
+ fontName?: string;
577
+ fontSize?: number | string;
578
+ errorBorderColor?: string;
579
+ errorBorderWidth?: number | string;
580
+ errorBorderRadius?: number | string;
581
+ innerTopPadding?: number | string;
582
+ innerBottomPadding?: number | string;
583
+ innerLeftPadding?: number | string;
584
+ innerRightPadding?: number | string;
585
+ showPrefixIcon?: boolean;
586
+ };
556
587
  type TCountdownTimerTextComponent = TBaseComponent & {
557
588
  component: "countdownTimerText";
558
589
  mode?: "duration" | "targetDateTime";
@@ -885,7 +916,7 @@ interface TBaseComponent {
885
916
  hidden?: boolean;
886
917
  animation?: NamiAnimationSpec;
887
918
  }
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;
919
+ 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
920
  type DirectionType = "vertical" | "horizontal";
890
921
  type AlignmentType = "center" | "right" | "left" | "top" | "bottom";
891
922
  type BorderLocationType = "upperLeft" | "upperRight" | "lowerLeft" | "lowerRight";
@@ -1490,6 +1521,38 @@ type NamiPurchaseDetails = {
1490
1521
 
1491
1522
  type Optional<T, K extends keyof T> = Pick<Partial<T>, K> & Omit<T, K>;
1492
1523
 
1524
+ /**
1525
+ * Pure, platform-agnostic text-input validation. Shared by the flow submit gate
1526
+ * (sdk/core) and the web Lit element (sdk/web). The rules are identical to the
1527
+ * Apple (NAM-1142) and Android (NAM-1143) ports.
1528
+ */
1529
+ type TTextInputType = 'name' | 'email' | 'freeText';
1530
+ interface FormFieldValidator {
1531
+ type: TTextInputType;
1532
+ required: boolean;
1533
+ /** Author-supplied error text; preferred over the per-rule default. */
1534
+ message?: string;
1535
+ }
1536
+ /**
1537
+ * Validate a single field. Returns `undefined` when valid, otherwise a default
1538
+ * error message for the failing rule. Callers that want author-supplied text
1539
+ * should use {@link validateForm}.
1540
+ *
1541
+ * Rules:
1542
+ * - required && blank → invalid
1543
+ * - email && non-blank && not email-format → invalid
1544
+ * - name && non-blank && len < 2 → invalid
1545
+ * - freeText → only the required rule applies
1546
+ */
1547
+ declare function validateTextInput(type: TTextInputType, required: boolean, value: unknown): string | undefined;
1548
+ /**
1549
+ * Validate every registered field against the current form state. Returns a map
1550
+ * of `formId → error message` for invalid fields only (valid fields are omitted).
1551
+ * The validator's own `message` is preferred; otherwise the per-rule default
1552
+ * from {@link validateTextInput} is used.
1553
+ */
1554
+ declare function validateForm(validators: Record<string, FormFieldValidator>, formStates: Record<string, unknown>): Record<string, string>;
1555
+
1493
1556
  type NamiPresentationStyle = 'fullscreen' | 'sheet' | 'compact_sheet' | 'modal';
1494
1557
  declare function getEffectiveWebStyle(style: NamiPresentationStyle | undefined | null): NamiPresentationStyle;
1495
1558
  type TPaywallContext = TInitialState & {
@@ -1528,6 +1591,12 @@ type TPaywallContext = TInitialState & {
1528
1591
  formStates: {
1529
1592
  [formId: string]: boolean | string;
1530
1593
  };
1594
+ formFieldErrors: {
1595
+ [formId: string]: string;
1596
+ };
1597
+ formFieldValidators: {
1598
+ [formId: string]: FormFieldValidator;
1599
+ };
1531
1600
  timerStates: {
1532
1601
  [timerId: string]: TimerState;
1533
1602
  };
@@ -1959,6 +2028,15 @@ declare class NamiFlow extends BasicNamiFlow {
1959
2028
  resumeFromPause(): void;
1960
2029
  executeLifecycle(step: NamiFlowStep, key: string, data?: Record<string, any>): void;
1961
2030
  private lifecycles;
2031
+ /**
2032
+ * A lifecycle chain is a "form submit" if any phase (before/action/after) runs
2033
+ * one of the advancing functions: setTagsFromForm, flowNext, or flowNav. The
2034
+ * POC submit buttons use flowNext/flowNav (no dedicated submit action), so the
2035
+ * gate keys off these. Close/dismiss/back/pause are deliberately excluded —
2036
+ * they must never be blocked by validation. Mirrors Apple
2037
+ * NamiFlowControl.isFormSubmitLifecycle / Android NamiFlow.isFormSubmitLifecycle.
2038
+ */
2039
+ isFormSubmitLifecycle(lifecycles: NamiFlowOn[]): boolean;
1962
2040
  triggerActions(actionId: string, component?: any, data?: Record<string, any>): void;
1963
2041
  executeFullLifecycles(lifecycles: NamiFlowOn[], data?: Record<string, any>): void;
1964
2042
  triggerBeforeActions(actionId: string, component?: any, data?: Record<string, any>): void;
@@ -2026,6 +2104,27 @@ declare class PaywallState extends SimpleEventTarget {
2026
2104
  getProductDetails(): NamiProductDetails[];
2027
2105
  setCurrentGroupData(currentGroupId: string, currentGroupName: string): void;
2028
2106
  setFormState(formId: string, state: boolean | string): void;
2107
+ /**
2108
+ * Register the validator for a text-input field (keyed by formId). Used by the
2109
+ * implicit form-submit gate in NamiFlow.triggerActions and by the web element
2110
+ * when it mounts.
2111
+ */
2112
+ registerFormFieldValidator(formId: string, validator: FormFieldValidator): void;
2113
+ getFormFieldValidators(): {
2114
+ [formId: string]: FormFieldValidator;
2115
+ };
2116
+ /**
2117
+ * Replace the full set of field errors (formId → message). The gate writes the
2118
+ * result of validateForm here; an empty map clears all errors.
2119
+ */
2120
+ setFormFieldErrors(errors: {
2121
+ [formId: string]: string;
2122
+ }): void;
2123
+ setFormFieldError(formId: string, message: string): void;
2124
+ clearFormFieldError(formId: string): void;
2125
+ getFormFieldErrors(): {
2126
+ [formId: string]: string;
2127
+ };
2029
2128
  setTimerState(timerId: string, remainingSeconds: number, savedAt: number, hasEmittedCompletion: boolean): void;
2030
2129
  getTimerState(timerId: string): TimerState | undefined;
2031
2130
  getCurrentGroupId(): string;
@@ -2422,6 +2521,15 @@ declare class StorageService {
2422
2521
  getCustomerAttribute<T>(attribute: string): T | null;
2423
2522
  getAllCustomerAttributes(): string[];
2424
2523
  getAllCustomerAttributesKeys(): string[];
2524
+ /**
2525
+ * All persisted customer attributes as a `{ key: value }` map. Used to seed
2526
+ * `PaywallState.customer` when a paywall screen initializes, so attributes set
2527
+ * on an earlier flow screen (e.g. via setTagsFromForm) are available to the
2528
+ * `${customer.x}` smart-text resolver on subsequent screens.
2529
+ */
2530
+ getAllCustomerAttributesMap(): {
2531
+ [key: string]: string;
2532
+ };
2425
2533
  clearCustomerAttribute(attribute: string): boolean;
2426
2534
  clearAllCustomerAttributes(): void;
2427
2535
  setNamiProfile(profileData: NamiProfile): void;
@@ -3489,5 +3597,5 @@ declare namespace internal {
3489
3597
  };
3490
3598
  }
3491
3599
 
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 };
3600
+ 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 };
3601
+ 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-dev.202606180014",
99
+ NAMI_SDK_PACKAGE_VERSION = "3.4.3-dev.202606181527",
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,6 +12612,7 @@ 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";
@@ -13075,6 +13139,42 @@ class URLParams {
13075
13139
  URLParams._instance = null;
13076
13140
  const getUrlParams = () => URLParams.instance.params;
13077
13141
 
13142
+ /**
13143
+ * Coerce a single form-state value to the string form used by tags / customer
13144
+ * attributes / `{{ form.x }}` interpolation. Shared by the `setTagsFromForm`
13145
+ * flow action and FlowLiquidResolver so they stay in lockstep.
13146
+ *
13147
+ * Rules (order matters — Boolean is checked before number):
13148
+ * - `null` / `undefined` → skip (returns `undefined`)
13149
+ * - empty string `""` → skip
13150
+ * - `false` → "false", `true` → "true" (booleans are kept)
13151
+ * - `0` → "0" (zero is kept)
13152
+ * - integral number → no trailing ".0" (e.g. 3 → "3", 3.5 → "3.5")
13153
+ * - everything else → String(value)
13154
+ */
13155
+ function formTagValue(value) {
13156
+ if (value === null || value === undefined) {
13157
+ return undefined;
13158
+ }
13159
+ if (value === '') {
13160
+ return undefined;
13161
+ }
13162
+ // Boolean BEFORE number: typeof true !== 'number', but be explicit so the
13163
+ // intent (and the false→"false" / true→"true" mapping) is unmistakable.
13164
+ if (typeof value === 'boolean') {
13165
+ return value ? 'true' : 'false';
13166
+ }
13167
+ if (typeof value === 'number') {
13168
+ if (!Number.isFinite(value)) {
13169
+ return undefined;
13170
+ }
13171
+ // JS `String` already renders integers without a trailing ".0"
13172
+ // (3 → "3", 3.5 → "3.5"), which is exactly the contract we want.
13173
+ return String(value);
13174
+ }
13175
+ return String(value);
13176
+ }
13177
+
13078
13178
  const LIQUID_VAR_REGEX = /\{\{\s*([^}]+?)\s*\}\}/g;
13079
13179
  /**
13080
13180
  * Resolver for liquid-style flow variables, without requiring namespace prefixes.
@@ -13099,6 +13199,13 @@ class FlowLiquidResolver {
13099
13199
  }
13100
13200
  return undefined;
13101
13201
  }
13202
+ // Handle form field paths: {{ form.fieldId }} reads the live form state and
13203
+ // coerces it through the same helper used by setTagsFromForm.
13204
+ if (key.startsWith("form.")) {
13205
+ const fieldId = key.substring("form.".length);
13206
+ const value = PaywallState.currentProvider?.state.formStates[fieldId];
13207
+ return formTagValue(value);
13208
+ }
13102
13209
  switch (key) {
13103
13210
  case "campaignId":
13104
13211
  return screenState?.campaign?.rule;
@@ -14100,6 +14207,67 @@ let NamiCustomerManager$2 = class NamiCustomerManager {
14100
14207
  };
14101
14208
  NamiCustomerManager$2.instance = new NamiCustomerManager$2();
14102
14209
 
14210
+ /**
14211
+ * Pure, platform-agnostic text-input validation. Shared by the flow submit gate
14212
+ * (sdk/core) and the web Lit element (sdk/web). The rules are identical to the
14213
+ * Apple (NAM-1142) and Android (NAM-1143) ports.
14214
+ */
14215
+ const DEFAULT_MESSAGES = {
14216
+ required: 'This field is required',
14217
+ email: 'Please enter a valid email address',
14218
+ name: 'Please enter at least 2 characters',
14219
+ };
14220
+ /**
14221
+ * An email must contain an `@` and a domain with a dot, e.g. `a@b.co`.
14222
+ * `a@b` (no dotted domain) and `nope` (no `@`) are invalid.
14223
+ */
14224
+ const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
14225
+ /**
14226
+ * Validate a single field. Returns `undefined` when valid, otherwise a default
14227
+ * error message for the failing rule. Callers that want author-supplied text
14228
+ * should use {@link validateForm}.
14229
+ *
14230
+ * Rules:
14231
+ * - required && blank → invalid
14232
+ * - email && non-blank && not email-format → invalid
14233
+ * - name && non-blank && len < 2 → invalid
14234
+ * - freeText → only the required rule applies
14235
+ */
14236
+ function validateTextInput(type, required, value) {
14237
+ const text = typeof value === 'string' ? value : value == null ? '' : String(value);
14238
+ const trimmed = text.trim();
14239
+ if (required && trimmed.length === 0) {
14240
+ return DEFAULT_MESSAGES.required;
14241
+ }
14242
+ if (trimmed.length === 0) {
14243
+ // Blank optional value: no format rule applies.
14244
+ return undefined;
14245
+ }
14246
+ if (type === 'email' && !EMAIL_REGEX.test(trimmed)) {
14247
+ return DEFAULT_MESSAGES.email;
14248
+ }
14249
+ if (type === 'name' && trimmed.length < 2) {
14250
+ return DEFAULT_MESSAGES.name;
14251
+ }
14252
+ return undefined;
14253
+ }
14254
+ /**
14255
+ * Validate every registered field against the current form state. Returns a map
14256
+ * of `formId → error message` for invalid fields only (valid fields are omitted).
14257
+ * The validator's own `message` is preferred; otherwise the per-rule default
14258
+ * from {@link validateTextInput} is used.
14259
+ */
14260
+ function validateForm(validators, formStates) {
14261
+ const errors = {};
14262
+ for (const [formId, validator] of Object.entries(validators)) {
14263
+ const defaultError = validateTextInput(validator.type, validator.required, formStates[formId]);
14264
+ if (defaultError !== undefined) {
14265
+ errors[formId] = validator.message ?? defaultError;
14266
+ }
14267
+ }
14268
+ return errors;
14269
+ }
14270
+
14103
14271
  class BasicNamiFlow {
14104
14272
  constructor(flowObject = {}) {
14105
14273
  this.id = flowObject.id ?? '';
@@ -14331,12 +14499,40 @@ class NamiFlow extends BasicNamiFlow {
14331
14499
  }
14332
14500
  return lifecycles;
14333
14501
  }
14502
+ /**
14503
+ * A lifecycle chain is a "form submit" if any phase (before/action/after) runs
14504
+ * one of the advancing functions: setTagsFromForm, flowNext, or flowNav. The
14505
+ * POC submit buttons use flowNext/flowNav (no dedicated submit action), so the
14506
+ * gate keys off these. Close/dismiss/back/pause are deliberately excluded —
14507
+ * they must never be blocked by validation. Mirrors Apple
14508
+ * NamiFlowControl.isFormSubmitLifecycle / Android NamiFlow.isFormSubmitLifecycle.
14509
+ */
14510
+ isFormSubmitLifecycle(lifecycles) {
14511
+ const submitFns = [
14512
+ NamiFlowActionFunction.SET_TAGS_FROM_FORM,
14513
+ NamiFlowActionFunction.NEXT,
14514
+ NamiFlowActionFunction.NAVIGATE,
14515
+ ];
14516
+ return lifecycles.some(cycle => [cycle.before, cycle.action, cycle.after].some(actions => actions?.some(action => submitFns.includes(action.function))));
14517
+ }
14334
14518
  triggerActions(actionId, component, data) {
14335
14519
  const lifecycles = this.lifecycles(actionId);
14336
14520
  if (!lifecycles)
14337
14521
  return;
14338
14522
  this.flowLog(`triggerActions(${actionId}) — step=${this.currentFlowStep?.id}, isPaused=${this.isPaused}`);
14339
14523
  this.currentButton = component;
14524
+ // Implicit form-submit validation gate (the NAM-1143 on-device lesson).
14525
+ // When this action advances the flow and validators are registered, validate
14526
+ // first; write the errors and halt the chain if any field is invalid.
14527
+ const validators = PaywallState.currentProvider?.state.formFieldValidators ?? {};
14528
+ if (this.isFormSubmitLifecycle(lifecycles) && Object.keys(validators).length) {
14529
+ const errors = validateForm(validators, this.getFormData());
14530
+ PaywallState.currentProvider?.setFormFieldErrors(errors);
14531
+ if (Object.keys(errors).length) {
14532
+ this.flowLog(`triggerActions(${actionId}) — HALTED by form validation: ${JSON.stringify(errors)}`);
14533
+ return;
14534
+ }
14535
+ }
14340
14536
  this.executeFullLifecycles(lifecycles, data);
14341
14537
  }
14342
14538
  executeFullLifecycles(lifecycles, data) {
@@ -14543,6 +14739,22 @@ class NamiFlow extends BasicNamiFlow {
14543
14739
  });
14544
14740
  }
14545
14741
  break;
14742
+ case NamiFlowActionFunction.SET_TAGS_FROM_FORM: {
14743
+ const formData = this.getFormData();
14744
+ const tags = {};
14745
+ Object.entries(formData).forEach(([key, raw]) => {
14746
+ const value = formTagValue(raw);
14747
+ if (value === undefined) {
14748
+ return; // skip null / "" (false and 0 are kept by formTagValue)
14749
+ }
14750
+ tags[key] = value;
14751
+ NamiCustomerManager$2.setCustomerAttribute(key, value);
14752
+ });
14753
+ if (Object.keys(tags).length) {
14754
+ PaywallState.setUserTags(tags);
14755
+ }
14756
+ break;
14757
+ }
14546
14758
  case NamiFlowActionFunction.SET_LAUNCH_CONTEXT: {
14547
14759
  // Two supported shapes: { customAttributes: {...} } (nav-action wire shape)
14548
14760
  // and { key, value } (used by Apple/Android/Roku). We deliberately do NOT
@@ -64765,4 +64977,4 @@ var internal = /*#__PURE__*/Object.freeze({
64765
64977
  NamiPurchaseManager: NamiPurchaseManager
64766
64978
  });
64767
64979
 
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 };
64980
+ 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-dev.202606180014",
3
+ "version": "3.4.3-dev.202606181527",
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",