@namiml/sdk-core 3.4.3-dev.202606120223 → 3.4.3-dev.202606121440

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.202606120223",
101
+ NAMI_SDK_PACKAGE_VERSION: exports.NAMI_SDK_PACKAGE_VERSION = "3.4.3-dev.202606121440",
102
102
  // environments
103
103
  PRODUCTION: exports.PRODUCTION = "production", DEVELOPMENT: exports.DEVELOPMENT = "development",
104
104
  // error messages
@@ -12548,6 +12548,7 @@ exports.NamiFlowActionFunction = void 0;
12548
12548
  NamiFlowActionFunction["SET_TAGS"] = "setTags";
12549
12549
  NamiFlowActionFunction["PAUSE"] = "flowPause";
12550
12550
  NamiFlowActionFunction["RESUME"] = "flowResume";
12551
+ NamiFlowActionFunction["SET_LAUNCH_CONTEXT"] = "setLaunchContext";
12551
12552
  })(exports.NamiFlowActionFunction || (exports.NamiFlowActionFunction = {}));
12552
12553
  const HandoffTag = {
12553
12554
  SEQUENCE: '__handoff_sequence__',
@@ -12582,8 +12583,26 @@ const FilterOperator = {
12582
12583
  NOT_CONTAINS: 'not_contains',
12583
12584
  SET: 'set',
12584
12585
  NOT_SET: 'notSet',
12586
+ GREATER_THAN: 'greater_than',
12587
+ GREATER_THAN_OR_EQUAL_TO: 'greater_than_or_equal_to',
12588
+ LESS_THAN: 'less_than',
12589
+ LESS_THAN_OR_EQUAL_TO: 'less_than_or_equal_to',
12585
12590
  };
12586
12591
 
12592
+ function toComparableNumber(value) {
12593
+ if (typeof value === 'boolean')
12594
+ return null;
12595
+ if (typeof value === 'number')
12596
+ return Number.isFinite(value) ? value : null;
12597
+ if (typeof value === 'string') {
12598
+ const trimmed = value.trim();
12599
+ if (trimmed === '')
12600
+ return null;
12601
+ const n = Number(trimmed);
12602
+ return Number.isFinite(n) ? n : null;
12603
+ }
12604
+ return null;
12605
+ }
12587
12606
  class NamiConditionEvaluator {
12588
12607
  constructor() {
12589
12608
  this.resolvers = new Map();
@@ -12674,88 +12693,76 @@ class NamiConditionEvaluator {
12674
12693
  return true;
12675
12694
  return false;
12676
12695
  }
12677
- // this.log(
12678
- // `Evaluating filter: ${filter.operator} with values: ${filter.values} against resolved value: ${JSON.stringify(resolvedValue)}`
12679
- // );
12680
12696
  switch (filter.operator) {
12681
12697
  case FilterOperator.I_CONTAINS:
12682
12698
  if (Array.isArray(resolvedValue)) {
12683
- const result = filter.values.some(expected => resolvedValue.some(item => item.localeCompare(expected, undefined, { sensitivity: 'accent' }) === 0));
12684
- // this.log(`i_contains evaluation result: ${result}`);
12699
+ const result = filter.values.some(expected => typeof expected === 'string' && resolvedValue.some(item => item.localeCompare(expected, undefined, { sensitivity: 'accent' }) === 0));
12685
12700
  return result;
12686
12701
  }
12687
12702
  this.log('Resolved value is not an array of strings for i_contains.');
12688
12703
  return false;
12689
12704
  case FilterOperator.EQUALS: {
12690
- const result = filter.values.some(expected => {
12691
- if (typeof resolvedValue === 'string') {
12692
- // this.log(`Comparing string ${resolvedValue} == ${expected}`);
12693
- return resolvedValue === expected;
12694
- }
12695
- if (typeof resolvedValue === 'boolean') {
12696
- const expectedBool = expected.toLowerCase() === 'true';
12697
- // this.log(`Comparing boolean ${resolvedValue} == ${expectedBool}`);
12698
- return resolvedValue === expectedBool;
12699
- }
12700
- this.log(`Unsupported type for equals comparison: ${typeof resolvedValue}`);
12701
- return false;
12702
- });
12703
- // this.log(`equals evaluation result: ${result}`);
12704
- return result;
12705
+ return filter.values.some(expected => this.strictEquals(resolvedValue, expected, false));
12705
12706
  }
12706
12707
  case FilterOperator.I_EQUALS: {
12707
- const result = filter.values.some(expected => {
12708
- if (typeof resolvedValue === 'string') {
12709
- const match = resolvedValue.localeCompare(expected, undefined, { sensitivity: 'accent' }) === 0;
12710
- // this.log(`Comparing string ${resolvedValue} ~= ${expected}: ${match}`);
12711
- return match;
12712
- }
12713
- if (typeof resolvedValue === 'boolean') {
12714
- const expectedBool = expected.toLowerCase() === 'true';
12715
- // this.log(`Comparing boolean ${resolvedValue} ~= ${expectedBool}`);
12716
- return resolvedValue === expectedBool;
12717
- }
12718
- this.log(`Unsupported type for i_equals comparison: ${typeof resolvedValue}`);
12719
- return false;
12720
- });
12721
- // this.log(`i_equals evaluation result: ${result}`);
12722
- return result;
12708
+ return filter.values.some(expected => this.strictEquals(resolvedValue, expected, true));
12723
12709
  }
12724
12710
  case FilterOperator.NOT_EQUALS: {
12725
- const result = filter.values.every(expected => {
12726
- if (typeof resolvedValue === 'string') {
12727
- return resolvedValue !== expected;
12728
- }
12729
- return true;
12730
- });
12731
- // this.log(`not_equals evaluation result: ${result}`);
12732
- return result;
12711
+ return filter.values.every(expected => !this.strictEquals(resolvedValue, expected, false));
12733
12712
  }
12734
12713
  case FilterOperator.NOT_I_EQUALS: {
12735
- const result = filter.values.every(expected => {
12736
- if (typeof resolvedValue === 'string') {
12737
- return (resolvedValue.localeCompare(expected, undefined, { sensitivity: 'accent' }) !== 0);
12738
- }
12739
- return true;
12740
- });
12741
- // this.log(`not_i_equals evaluation result: ${result}`);
12742
- return result;
12714
+ return filter.values.every(expected => !this.strictEquals(resolvedValue, expected, true));
12743
12715
  }
12744
12716
  case FilterOperator.NOT_CONTAINS: {
12745
12717
  const result = filter.values.every(expected => {
12746
- if (typeof resolvedValue === 'string') {
12718
+ if (typeof resolvedValue === 'string' && typeof expected === 'string') {
12747
12719
  return !resolvedValue.includes(expected);
12748
12720
  }
12749
12721
  return true;
12750
12722
  });
12751
- // this.log(`not_contains evaluation result: ${result}`);
12752
12723
  return result;
12753
12724
  }
12725
+ case FilterOperator.GREATER_THAN:
12726
+ case FilterOperator.GREATER_THAN_OR_EQUAL_TO:
12727
+ case FilterOperator.LESS_THAN:
12728
+ case FilterOperator.LESS_THAN_OR_EQUAL_TO: {
12729
+ const lhs = toComparableNumber(resolvedValue);
12730
+ if (lhs === null) {
12731
+ this.log(`Resolved value is not numerically comparable for ${filter.operator}`);
12732
+ return false;
12733
+ }
12734
+ return filter.values.some(expected => {
12735
+ const rhs = toComparableNumber(expected);
12736
+ if (rhs === null)
12737
+ return false;
12738
+ switch (filter.operator) {
12739
+ case FilterOperator.GREATER_THAN: return lhs > rhs;
12740
+ case FilterOperator.GREATER_THAN_OR_EQUAL_TO: return lhs >= rhs;
12741
+ case FilterOperator.LESS_THAN: return lhs < rhs;
12742
+ default: return lhs <= rhs;
12743
+ }
12744
+ });
12745
+ }
12754
12746
  default:
12755
12747
  this.log(`Unsupported operator: ${filter.operator}`);
12756
12748
  return false;
12757
12749
  }
12758
12750
  }
12751
+ /** Type-strict equality: string↔string (optionally case-insensitive), boolean↔boolean, or number↔number. */
12752
+ strictEquals(resolved, expected, caseInsensitive) {
12753
+ if (typeof resolved === 'string' && typeof expected === 'string') {
12754
+ return caseInsensitive
12755
+ ? resolved.localeCompare(expected, undefined, { sensitivity: 'accent' }) === 0
12756
+ : resolved === expected;
12757
+ }
12758
+ if (typeof resolved === 'boolean' && typeof expected === 'boolean') {
12759
+ return resolved === expected;
12760
+ }
12761
+ if (typeof resolved === 'number' && typeof expected === 'number') {
12762
+ return resolved === expected;
12763
+ }
12764
+ return false;
12765
+ }
12759
12766
  resolve(identifier) {
12760
12767
  const exact = this.resolveRaw(identifier);
12761
12768
  if (exact !== undefined) {
@@ -12818,7 +12825,6 @@ class NamiConditionEvaluator {
12818
12825
  }
12819
12826
  break;
12820
12827
  default:
12821
- // this.log(`Unsupported property: .${property}`);
12822
12828
  return undefined;
12823
12829
  }
12824
12830
  this.log(`Could not extract .${property} from type ${typeof baseValue}`);
@@ -14125,6 +14131,17 @@ class NamiFlow extends BasicNamiFlow {
14125
14131
  this.forward(entry.id);
14126
14132
  }
14127
14133
  }
14134
+ applyLaunchContextAttributes(attrs) {
14135
+ if (!this.context) {
14136
+ this.context = { customAttributes: {} };
14137
+ new LaunchContextResolver(this.context);
14138
+ }
14139
+ // Guard against a runtime-supplied context that omits customAttributes
14140
+ // (the field is required by the type but may be absent in untyped JSON).
14141
+ this.context.customAttributes ??= {};
14142
+ // New values win: merge attrs over existing entries.
14143
+ Object.assign(this.context.customAttributes, attrs);
14144
+ }
14128
14145
  registerResolvers(context) {
14129
14146
  if (context) {
14130
14147
  new LaunchContextResolver(context);
@@ -14381,6 +14398,9 @@ class NamiFlow extends BasicNamiFlow {
14381
14398
  this.back();
14382
14399
  break;
14383
14400
  case exports.NamiFlowActionFunction.NEXT:
14401
+ if (action.parameters?.customAttributes) {
14402
+ this.applyLaunchContextAttributes(action.parameters.customAttributes);
14403
+ }
14384
14404
  if (action.parameters?.step) {
14385
14405
  this.forward(action.parameters.step);
14386
14406
  }
@@ -14389,6 +14409,9 @@ class NamiFlow extends BasicNamiFlow {
14389
14409
  }
14390
14410
  break;
14391
14411
  case exports.NamiFlowActionFunction.NAVIGATE:
14412
+ if (action.parameters?.customAttributes) {
14413
+ this.applyLaunchContextAttributes(action.parameters.customAttributes);
14414
+ }
14392
14415
  if (action.parameters?.step) {
14393
14416
  if (this.previousFlowStep?.id === action.parameters.step) {
14394
14417
  this.back();
@@ -14514,6 +14537,20 @@ class NamiFlow extends BasicNamiFlow {
14514
14537
  });
14515
14538
  }
14516
14539
  break;
14540
+ case exports.NamiFlowActionFunction.SET_LAUNCH_CONTEXT: {
14541
+ // Two supported shapes: { customAttributes: {...} } (nav-action wire shape)
14542
+ // and { key, value } (used by Apple/Android/Roku). We deliberately do NOT
14543
+ // treat arbitrary top-level parameters as attributes to avoid writing
14544
+ // reserved keys (step, delay, …) into the launch context.
14545
+ const params = action.parameters;
14546
+ if (params?.customAttributes) {
14547
+ this.applyLaunchContextAttributes(params.customAttributes);
14548
+ }
14549
+ else if (params?.key !== undefined && params?.value !== undefined) {
14550
+ this.applyLaunchContextAttributes({ [params.key]: params.value });
14551
+ }
14552
+ break;
14553
+ }
14517
14554
  default:
14518
14555
  logger.warn(`Missing action handler for ${action.function}`, action);
14519
14556
  break;
package/dist/index.d.ts CHANGED
@@ -93,12 +93,16 @@ declare const FilterOperator: {
93
93
  readonly NOT_CONTAINS: "not_contains";
94
94
  readonly SET: "set";
95
95
  readonly NOT_SET: "notSet";
96
+ readonly GREATER_THAN: "greater_than";
97
+ readonly GREATER_THAN_OR_EQUAL_TO: "greater_than_or_equal_to";
98
+ readonly LESS_THAN: "less_than";
99
+ readonly LESS_THAN_OR_EQUAL_TO: "less_than_or_equal_to";
96
100
  };
97
101
  type FilterOperator = (typeof FilterOperator)[keyof typeof FilterOperator];
98
102
  interface NamiConditionFilter {
99
103
  identifier: string;
100
104
  operator: FilterOperator;
101
- values: string[];
105
+ values: (string | boolean | number)[];
102
106
  }
103
107
  interface NamiConditions {
104
108
  filter?: NamiConditionFilter[];
@@ -130,7 +134,8 @@ declare enum NamiFlowActionFunction {
130
134
  FLOW_DISABLED = "flowInteractionDisabled",
131
135
  SET_TAGS = "setTags",
132
136
  PAUSE = "flowPause",
133
- RESUME = "flowResume"
137
+ RESUME = "flowResume",
138
+ SET_LAUNCH_CONTEXT = "setLaunchContext"
134
139
  }
135
140
  type NamiFlowHandoffStepHandler = (handoffTag: string, handoffData?: Record<string, any>) => void;
136
141
  type NamiFlowEventHandler = (eventHandler: Record<string, any>) => void;
@@ -1299,6 +1304,14 @@ type NamiPaywallEvent = {
1299
1304
  purchaseChannel?: string;
1300
1305
  };
1301
1306
  type NamiPaywallActionHandler = (event: NamiPaywallEvent) => void;
1307
+ /**
1308
+ * Permitted value types for entries in {@link NamiPaywallLaunchContext.customAttributes}.
1309
+ *
1310
+ * Values are matched strictly by type: the string "true" and the boolean true are
1311
+ * distinct values that never compare equal. A key present with any non-null value —
1312
+ * including false or "false" — is considered "set".
1313
+ */
1314
+ type NamiCustomAttributeValue = string | boolean | number;
1302
1315
  /**
1303
1316
  * @type NamiPaywallLaunchContext
1304
1317
  * Will be used to pass custom context while launching paywall
@@ -1306,7 +1319,7 @@ type NamiPaywallActionHandler = (event: NamiPaywallEvent) => void;
1306
1319
  type NamiPaywallLaunchContext = {
1307
1320
  productGroups?: string[];
1308
1321
  customAttributes: {
1309
- [key: string]: string;
1322
+ [key: string]: NamiCustomAttributeValue;
1310
1323
  };
1311
1324
  customObject?: {
1312
1325
  [key: string]: any;
@@ -1926,6 +1939,7 @@ declare class NamiFlow extends BasicNamiFlow {
1926
1939
  [timerId: string]: TimerState;
1927
1940
  };
1928
1941
  constructor(campaign: NamiFlowCampaign, paywall: PaywallHandle, manager: NamiFlowManager$2, context?: NamiPaywallLaunchContext);
1942
+ private applyLaunchContextAttributes;
1929
1943
  private registerResolvers;
1930
1944
  get currentFlowStep(): NamiFlowStep | undefined;
1931
1945
  get nextStepAvailable(): boolean;
@@ -3058,6 +3072,8 @@ declare class NamiConditionEvaluator {
3058
3072
  evaluate(conditions: NamiConditions): boolean;
3059
3073
  evaluateOrdered(conditions?: NamiConditions): boolean;
3060
3074
  private evaluateFilter;
3075
+ /** Type-strict equality: string↔string (optionally case-insensitive), boolean↔boolean, or number↔number. */
3076
+ private strictEquals;
3061
3077
  private resolve;
3062
3078
  private resolveRaw;
3063
3079
  private extractProperty;
@@ -3472,4 +3488,4 @@ declare namespace internal {
3472
3488
  }
3473
3489
 
3474
3490
  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 };
3475
- 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, 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 };
3491
+ 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 };
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.202606120223",
99
+ NAMI_SDK_PACKAGE_VERSION = "3.4.3-dev.202606121440",
100
100
  // environments
101
101
  PRODUCTION = "production", DEVELOPMENT = "development",
102
102
  // error messages
@@ -12546,6 +12546,7 @@ var NamiFlowActionFunction;
12546
12546
  NamiFlowActionFunction["SET_TAGS"] = "setTags";
12547
12547
  NamiFlowActionFunction["PAUSE"] = "flowPause";
12548
12548
  NamiFlowActionFunction["RESUME"] = "flowResume";
12549
+ NamiFlowActionFunction["SET_LAUNCH_CONTEXT"] = "setLaunchContext";
12549
12550
  })(NamiFlowActionFunction || (NamiFlowActionFunction = {}));
12550
12551
  const HandoffTag = {
12551
12552
  SEQUENCE: '__handoff_sequence__',
@@ -12580,8 +12581,26 @@ const FilterOperator = {
12580
12581
  NOT_CONTAINS: 'not_contains',
12581
12582
  SET: 'set',
12582
12583
  NOT_SET: 'notSet',
12584
+ GREATER_THAN: 'greater_than',
12585
+ GREATER_THAN_OR_EQUAL_TO: 'greater_than_or_equal_to',
12586
+ LESS_THAN: 'less_than',
12587
+ LESS_THAN_OR_EQUAL_TO: 'less_than_or_equal_to',
12583
12588
  };
12584
12589
 
12590
+ function toComparableNumber(value) {
12591
+ if (typeof value === 'boolean')
12592
+ return null;
12593
+ if (typeof value === 'number')
12594
+ return Number.isFinite(value) ? value : null;
12595
+ if (typeof value === 'string') {
12596
+ const trimmed = value.trim();
12597
+ if (trimmed === '')
12598
+ return null;
12599
+ const n = Number(trimmed);
12600
+ return Number.isFinite(n) ? n : null;
12601
+ }
12602
+ return null;
12603
+ }
12585
12604
  class NamiConditionEvaluator {
12586
12605
  constructor() {
12587
12606
  this.resolvers = new Map();
@@ -12672,88 +12691,76 @@ class NamiConditionEvaluator {
12672
12691
  return true;
12673
12692
  return false;
12674
12693
  }
12675
- // this.log(
12676
- // `Evaluating filter: ${filter.operator} with values: ${filter.values} against resolved value: ${JSON.stringify(resolvedValue)}`
12677
- // );
12678
12694
  switch (filter.operator) {
12679
12695
  case FilterOperator.I_CONTAINS:
12680
12696
  if (Array.isArray(resolvedValue)) {
12681
- const result = filter.values.some(expected => resolvedValue.some(item => item.localeCompare(expected, undefined, { sensitivity: 'accent' }) === 0));
12682
- // this.log(`i_contains evaluation result: ${result}`);
12697
+ const result = filter.values.some(expected => typeof expected === 'string' && resolvedValue.some(item => item.localeCompare(expected, undefined, { sensitivity: 'accent' }) === 0));
12683
12698
  return result;
12684
12699
  }
12685
12700
  this.log('Resolved value is not an array of strings for i_contains.');
12686
12701
  return false;
12687
12702
  case FilterOperator.EQUALS: {
12688
- const result = filter.values.some(expected => {
12689
- if (typeof resolvedValue === 'string') {
12690
- // this.log(`Comparing string ${resolvedValue} == ${expected}`);
12691
- return resolvedValue === expected;
12692
- }
12693
- if (typeof resolvedValue === 'boolean') {
12694
- const expectedBool = expected.toLowerCase() === 'true';
12695
- // this.log(`Comparing boolean ${resolvedValue} == ${expectedBool}`);
12696
- return resolvedValue === expectedBool;
12697
- }
12698
- this.log(`Unsupported type for equals comparison: ${typeof resolvedValue}`);
12699
- return false;
12700
- });
12701
- // this.log(`equals evaluation result: ${result}`);
12702
- return result;
12703
+ return filter.values.some(expected => this.strictEquals(resolvedValue, expected, false));
12703
12704
  }
12704
12705
  case FilterOperator.I_EQUALS: {
12705
- const result = filter.values.some(expected => {
12706
- if (typeof resolvedValue === 'string') {
12707
- const match = resolvedValue.localeCompare(expected, undefined, { sensitivity: 'accent' }) === 0;
12708
- // this.log(`Comparing string ${resolvedValue} ~= ${expected}: ${match}`);
12709
- return match;
12710
- }
12711
- if (typeof resolvedValue === 'boolean') {
12712
- const expectedBool = expected.toLowerCase() === 'true';
12713
- // this.log(`Comparing boolean ${resolvedValue} ~= ${expectedBool}`);
12714
- return resolvedValue === expectedBool;
12715
- }
12716
- this.log(`Unsupported type for i_equals comparison: ${typeof resolvedValue}`);
12717
- return false;
12718
- });
12719
- // this.log(`i_equals evaluation result: ${result}`);
12720
- return result;
12706
+ return filter.values.some(expected => this.strictEquals(resolvedValue, expected, true));
12721
12707
  }
12722
12708
  case FilterOperator.NOT_EQUALS: {
12723
- const result = filter.values.every(expected => {
12724
- if (typeof resolvedValue === 'string') {
12725
- return resolvedValue !== expected;
12726
- }
12727
- return true;
12728
- });
12729
- // this.log(`not_equals evaluation result: ${result}`);
12730
- return result;
12709
+ return filter.values.every(expected => !this.strictEquals(resolvedValue, expected, false));
12731
12710
  }
12732
12711
  case FilterOperator.NOT_I_EQUALS: {
12733
- const result = filter.values.every(expected => {
12734
- if (typeof resolvedValue === 'string') {
12735
- return (resolvedValue.localeCompare(expected, undefined, { sensitivity: 'accent' }) !== 0);
12736
- }
12737
- return true;
12738
- });
12739
- // this.log(`not_i_equals evaluation result: ${result}`);
12740
- return result;
12712
+ return filter.values.every(expected => !this.strictEquals(resolvedValue, expected, true));
12741
12713
  }
12742
12714
  case FilterOperator.NOT_CONTAINS: {
12743
12715
  const result = filter.values.every(expected => {
12744
- if (typeof resolvedValue === 'string') {
12716
+ if (typeof resolvedValue === 'string' && typeof expected === 'string') {
12745
12717
  return !resolvedValue.includes(expected);
12746
12718
  }
12747
12719
  return true;
12748
12720
  });
12749
- // this.log(`not_contains evaluation result: ${result}`);
12750
12721
  return result;
12751
12722
  }
12723
+ case FilterOperator.GREATER_THAN:
12724
+ case FilterOperator.GREATER_THAN_OR_EQUAL_TO:
12725
+ case FilterOperator.LESS_THAN:
12726
+ case FilterOperator.LESS_THAN_OR_EQUAL_TO: {
12727
+ const lhs = toComparableNumber(resolvedValue);
12728
+ if (lhs === null) {
12729
+ this.log(`Resolved value is not numerically comparable for ${filter.operator}`);
12730
+ return false;
12731
+ }
12732
+ return filter.values.some(expected => {
12733
+ const rhs = toComparableNumber(expected);
12734
+ if (rhs === null)
12735
+ return false;
12736
+ switch (filter.operator) {
12737
+ case FilterOperator.GREATER_THAN: return lhs > rhs;
12738
+ case FilterOperator.GREATER_THAN_OR_EQUAL_TO: return lhs >= rhs;
12739
+ case FilterOperator.LESS_THAN: return lhs < rhs;
12740
+ default: return lhs <= rhs;
12741
+ }
12742
+ });
12743
+ }
12752
12744
  default:
12753
12745
  this.log(`Unsupported operator: ${filter.operator}`);
12754
12746
  return false;
12755
12747
  }
12756
12748
  }
12749
+ /** Type-strict equality: string↔string (optionally case-insensitive), boolean↔boolean, or number↔number. */
12750
+ strictEquals(resolved, expected, caseInsensitive) {
12751
+ if (typeof resolved === 'string' && typeof expected === 'string') {
12752
+ return caseInsensitive
12753
+ ? resolved.localeCompare(expected, undefined, { sensitivity: 'accent' }) === 0
12754
+ : resolved === expected;
12755
+ }
12756
+ if (typeof resolved === 'boolean' && typeof expected === 'boolean') {
12757
+ return resolved === expected;
12758
+ }
12759
+ if (typeof resolved === 'number' && typeof expected === 'number') {
12760
+ return resolved === expected;
12761
+ }
12762
+ return false;
12763
+ }
12757
12764
  resolve(identifier) {
12758
12765
  const exact = this.resolveRaw(identifier);
12759
12766
  if (exact !== undefined) {
@@ -12816,7 +12823,6 @@ class NamiConditionEvaluator {
12816
12823
  }
12817
12824
  break;
12818
12825
  default:
12819
- // this.log(`Unsupported property: .${property}`);
12820
12826
  return undefined;
12821
12827
  }
12822
12828
  this.log(`Could not extract .${property} from type ${typeof baseValue}`);
@@ -14123,6 +14129,17 @@ class NamiFlow extends BasicNamiFlow {
14123
14129
  this.forward(entry.id);
14124
14130
  }
14125
14131
  }
14132
+ applyLaunchContextAttributes(attrs) {
14133
+ if (!this.context) {
14134
+ this.context = { customAttributes: {} };
14135
+ new LaunchContextResolver(this.context);
14136
+ }
14137
+ // Guard against a runtime-supplied context that omits customAttributes
14138
+ // (the field is required by the type but may be absent in untyped JSON).
14139
+ this.context.customAttributes ??= {};
14140
+ // New values win: merge attrs over existing entries.
14141
+ Object.assign(this.context.customAttributes, attrs);
14142
+ }
14126
14143
  registerResolvers(context) {
14127
14144
  if (context) {
14128
14145
  new LaunchContextResolver(context);
@@ -14379,6 +14396,9 @@ class NamiFlow extends BasicNamiFlow {
14379
14396
  this.back();
14380
14397
  break;
14381
14398
  case NamiFlowActionFunction.NEXT:
14399
+ if (action.parameters?.customAttributes) {
14400
+ this.applyLaunchContextAttributes(action.parameters.customAttributes);
14401
+ }
14382
14402
  if (action.parameters?.step) {
14383
14403
  this.forward(action.parameters.step);
14384
14404
  }
@@ -14387,6 +14407,9 @@ class NamiFlow extends BasicNamiFlow {
14387
14407
  }
14388
14408
  break;
14389
14409
  case NamiFlowActionFunction.NAVIGATE:
14410
+ if (action.parameters?.customAttributes) {
14411
+ this.applyLaunchContextAttributes(action.parameters.customAttributes);
14412
+ }
14390
14413
  if (action.parameters?.step) {
14391
14414
  if (this.previousFlowStep?.id === action.parameters.step) {
14392
14415
  this.back();
@@ -14512,6 +14535,20 @@ class NamiFlow extends BasicNamiFlow {
14512
14535
  });
14513
14536
  }
14514
14537
  break;
14538
+ case NamiFlowActionFunction.SET_LAUNCH_CONTEXT: {
14539
+ // Two supported shapes: { customAttributes: {...} } (nav-action wire shape)
14540
+ // and { key, value } (used by Apple/Android/Roku). We deliberately do NOT
14541
+ // treat arbitrary top-level parameters as attributes to avoid writing
14542
+ // reserved keys (step, delay, …) into the launch context.
14543
+ const params = action.parameters;
14544
+ if (params?.customAttributes) {
14545
+ this.applyLaunchContextAttributes(params.customAttributes);
14546
+ }
14547
+ else if (params?.key !== undefined && params?.value !== undefined) {
14548
+ this.applyLaunchContextAttributes({ [params.key]: params.value });
14549
+ }
14550
+ break;
14551
+ }
14515
14552
  default:
14516
14553
  logger.warn(`Missing action handler for ${action.function}`, action);
14517
14554
  break;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@namiml/sdk-core",
3
- "version": "3.4.3-dev.202606120223",
3
+ "version": "3.4.3-dev.202606121440",
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",