@namiml/sdk-core 3.4.3-dev.202606120301 → 3.4.3-dev.202606121502

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.202606120301",
101
+ NAMI_SDK_PACKAGE_VERSION: exports.NAMI_SDK_PACKAGE_VERSION = "3.4.3-dev.202606121502",
102
102
  // environments
103
103
  PRODUCTION: exports.PRODUCTION = "production", DEVELOPMENT: exports.DEVELOPMENT = "development",
104
104
  // error messages
@@ -6803,10 +6803,6 @@ const shouldLogHTTPTraffic = () => {
6803
6803
  const namiCommands = storageService.getNamiConfig()?.namiCommands;
6804
6804
  return namiCommands?.includes(exports.LOG_HTTP_TRAFFIC) ?? false;
6805
6805
  };
6806
- const shouldLogStartupTelemetry = () => {
6807
- const namiCommands = storageService.getNamiConfig()?.namiCommands;
6808
- return namiCommands?.includes(exports.STARTUP_TELEMETRY) ?? false;
6809
- };
6810
6806
  function tryParseJson(str) {
6811
6807
  const trimmed = str.trim();
6812
6808
  if (!trimmed.startsWith("{") && !trimmed.startsWith("["))
@@ -11944,9 +11940,10 @@ const PREFIX = "[NamiStartup]";
11944
11940
  * Module-level singleton for startup timing instrumentation.
11945
11941
  *
11946
11942
  * All methods are cheap no-ops when the `startupTelemetry` namiCommand flag
11947
- * is absent. The flag is read once at `start()` and cached; subsequent calls
11948
- * to `mark()` and the span helpers use that cached boolean so the hot path
11949
- * never hits storage.
11943
+ * is absent. The flag is read once at `start()` from the namiCommands passed
11944
+ * to the current configure() call NOT from persisted storage, which still
11945
+ * holds the previous run's config at that point (NAM-1876) — and cached;
11946
+ * subsequent calls to `mark()` and the span helpers use that cached boolean.
11950
11947
  *
11951
11948
  * Log format (info level via the shared logger):
11952
11949
  * [NamiStartup] phase=<name> ms=<n>
@@ -11965,11 +11962,13 @@ class StartupTelemetry {
11965
11962
  this.firstLaunchLogged = false;
11966
11963
  }
11967
11964
  /**
11968
- * Call at the first line of configure(). Reads the feature flag once and
11969
- * stores t0 if the flag is on.
11965
+ * Call at the first line of configure(), passing the incoming config's
11966
+ * namiCommands. Reads the feature flag once and stores t0 if the flag is on.
11967
+ *
11968
+ * @param namiCommands The `namiCommands` from the current configure() options.
11970
11969
  */
11971
- start() {
11972
- this.enabled = shouldLogStartupTelemetry();
11970
+ start(namiCommands) {
11971
+ this.enabled = namiCommands?.includes(exports.STARTUP_TELEMETRY) ?? false;
11973
11972
  if (!this.enabled)
11974
11973
  return;
11975
11974
  this.t0 = Date.now();
@@ -12363,7 +12362,7 @@ class Nami {
12363
12362
  * @returns {Promise<NamiConfigurationState>}
12364
12363
  */
12365
12364
  static async configure(options) {
12366
- startupTelemetry.start();
12365
+ startupTelemetry.start(options.namiCommands);
12367
12366
  if (!options.appPlatformID) {
12368
12367
  throw new PlatformIDRequiredError();
12369
12368
  }
@@ -12548,6 +12547,7 @@ exports.NamiFlowActionFunction = void 0;
12548
12547
  NamiFlowActionFunction["SET_TAGS"] = "setTags";
12549
12548
  NamiFlowActionFunction["PAUSE"] = "flowPause";
12550
12549
  NamiFlowActionFunction["RESUME"] = "flowResume";
12550
+ NamiFlowActionFunction["SET_LAUNCH_CONTEXT"] = "setLaunchContext";
12551
12551
  })(exports.NamiFlowActionFunction || (exports.NamiFlowActionFunction = {}));
12552
12552
  const HandoffTag = {
12553
12553
  SEQUENCE: '__handoff_sequence__',
@@ -12692,89 +12692,33 @@ class NamiConditionEvaluator {
12692
12692
  return true;
12693
12693
  return false;
12694
12694
  }
12695
- // this.log(
12696
- // `Evaluating filter: ${filter.operator} with values: ${filter.values} against resolved value: ${JSON.stringify(resolvedValue)}`
12697
- // );
12698
12695
  switch (filter.operator) {
12699
12696
  case FilterOperator.I_CONTAINS:
12700
12697
  if (Array.isArray(resolvedValue)) {
12701
- const result = filter.values.some(expected => {
12702
- const expectedStr = String(expected);
12703
- return resolvedValue.some(item => item.localeCompare(expectedStr, undefined, { sensitivity: 'accent' }) === 0);
12704
- });
12705
- // this.log(`i_contains evaluation result: ${result}`);
12698
+ const result = filter.values.some(expected => typeof expected === 'string' && resolvedValue.some(item => item.localeCompare(expected, undefined, { sensitivity: 'accent' }) === 0));
12706
12699
  return result;
12707
12700
  }
12708
12701
  this.log('Resolved value is not an array of strings for i_contains.');
12709
12702
  return false;
12710
12703
  case FilterOperator.EQUALS: {
12711
- const result = filter.values.some(expected => {
12712
- const expectedStr = String(expected);
12713
- if (typeof resolvedValue === 'string') {
12714
- // this.log(`Comparing string ${resolvedValue} == ${expected}`);
12715
- return resolvedValue === expectedStr;
12716
- }
12717
- if (typeof resolvedValue === 'boolean') {
12718
- const expectedBool = expectedStr.toLowerCase() === 'true';
12719
- // this.log(`Comparing boolean ${resolvedValue} == ${expectedBool}`);
12720
- return resolvedValue === expectedBool;
12721
- }
12722
- this.log(`Unsupported type for equals comparison: ${typeof resolvedValue}`);
12723
- return false;
12724
- });
12725
- // this.log(`equals evaluation result: ${result}`);
12726
- return result;
12704
+ return filter.values.some(expected => this.strictEquals(resolvedValue, expected, false));
12727
12705
  }
12728
12706
  case FilterOperator.I_EQUALS: {
12729
- const result = filter.values.some(expected => {
12730
- const expectedStr = String(expected);
12731
- if (typeof resolvedValue === 'string') {
12732
- const match = resolvedValue.localeCompare(expectedStr, undefined, { sensitivity: 'accent' }) === 0;
12733
- // this.log(`Comparing string ${resolvedValue} ~= ${expected}: ${match}`);
12734
- return match;
12735
- }
12736
- if (typeof resolvedValue === 'boolean') {
12737
- const expectedBool = expectedStr.toLowerCase() === 'true';
12738
- // this.log(`Comparing boolean ${resolvedValue} ~= ${expectedBool}`);
12739
- return resolvedValue === expectedBool;
12740
- }
12741
- this.log(`Unsupported type for i_equals comparison: ${typeof resolvedValue}`);
12742
- return false;
12743
- });
12744
- // this.log(`i_equals evaluation result: ${result}`);
12745
- return result;
12707
+ return filter.values.some(expected => this.strictEquals(resolvedValue, expected, true));
12746
12708
  }
12747
12709
  case FilterOperator.NOT_EQUALS: {
12748
- const result = filter.values.every(expected => {
12749
- const expectedStr = String(expected);
12750
- if (typeof resolvedValue === 'string') {
12751
- return resolvedValue !== expectedStr;
12752
- }
12753
- return true;
12754
- });
12755
- // this.log(`not_equals evaluation result: ${result}`);
12756
- return result;
12710
+ return filter.values.every(expected => !this.strictEquals(resolvedValue, expected, false));
12757
12711
  }
12758
12712
  case FilterOperator.NOT_I_EQUALS: {
12759
- const result = filter.values.every(expected => {
12760
- const expectedStr = String(expected);
12761
- if (typeof resolvedValue === 'string') {
12762
- return (resolvedValue.localeCompare(expectedStr, undefined, { sensitivity: 'accent' }) !== 0);
12763
- }
12764
- return true;
12765
- });
12766
- // this.log(`not_i_equals evaluation result: ${result}`);
12767
- return result;
12713
+ return filter.values.every(expected => !this.strictEquals(resolvedValue, expected, true));
12768
12714
  }
12769
12715
  case FilterOperator.NOT_CONTAINS: {
12770
12716
  const result = filter.values.every(expected => {
12771
- const expectedStr = String(expected);
12772
- if (typeof resolvedValue === 'string') {
12773
- return !resolvedValue.includes(expectedStr);
12717
+ if (typeof resolvedValue === 'string' && typeof expected === 'string') {
12718
+ return !resolvedValue.includes(expected);
12774
12719
  }
12775
12720
  return true;
12776
12721
  });
12777
- // this.log(`not_contains evaluation result: ${result}`);
12778
12722
  return result;
12779
12723
  }
12780
12724
  case FilterOperator.GREATER_THAN:
@@ -12803,6 +12747,21 @@ class NamiConditionEvaluator {
12803
12747
  return false;
12804
12748
  }
12805
12749
  }
12750
+ /** Type-strict equality: string↔string (optionally case-insensitive), boolean↔boolean, or number↔number. */
12751
+ strictEquals(resolved, expected, caseInsensitive) {
12752
+ if (typeof resolved === 'string' && typeof expected === 'string') {
12753
+ return caseInsensitive
12754
+ ? resolved.localeCompare(expected, undefined, { sensitivity: 'accent' }) === 0
12755
+ : resolved === expected;
12756
+ }
12757
+ if (typeof resolved === 'boolean' && typeof expected === 'boolean') {
12758
+ return resolved === expected;
12759
+ }
12760
+ if (typeof resolved === 'number' && typeof expected === 'number') {
12761
+ return resolved === expected;
12762
+ }
12763
+ return false;
12764
+ }
12806
12765
  resolve(identifier) {
12807
12766
  const exact = this.resolveRaw(identifier);
12808
12767
  if (exact !== undefined) {
@@ -12865,7 +12824,6 @@ class NamiConditionEvaluator {
12865
12824
  }
12866
12825
  break;
12867
12826
  default:
12868
- // this.log(`Unsupported property: .${property}`);
12869
12827
  return undefined;
12870
12828
  }
12871
12829
  this.log(`Could not extract .${property} from type ${typeof baseValue}`);
@@ -14172,6 +14130,17 @@ class NamiFlow extends BasicNamiFlow {
14172
14130
  this.forward(entry.id);
14173
14131
  }
14174
14132
  }
14133
+ applyLaunchContextAttributes(attrs) {
14134
+ if (!this.context) {
14135
+ this.context = { customAttributes: {} };
14136
+ new LaunchContextResolver(this.context);
14137
+ }
14138
+ // Guard against a runtime-supplied context that omits customAttributes
14139
+ // (the field is required by the type but may be absent in untyped JSON).
14140
+ this.context.customAttributes ??= {};
14141
+ // New values win: merge attrs over existing entries.
14142
+ Object.assign(this.context.customAttributes, attrs);
14143
+ }
14175
14144
  registerResolvers(context) {
14176
14145
  if (context) {
14177
14146
  new LaunchContextResolver(context);
@@ -14428,6 +14397,9 @@ class NamiFlow extends BasicNamiFlow {
14428
14397
  this.back();
14429
14398
  break;
14430
14399
  case exports.NamiFlowActionFunction.NEXT:
14400
+ if (action.parameters?.customAttributes) {
14401
+ this.applyLaunchContextAttributes(action.parameters.customAttributes);
14402
+ }
14431
14403
  if (action.parameters?.step) {
14432
14404
  this.forward(action.parameters.step);
14433
14405
  }
@@ -14436,6 +14408,9 @@ class NamiFlow extends BasicNamiFlow {
14436
14408
  }
14437
14409
  break;
14438
14410
  case exports.NamiFlowActionFunction.NAVIGATE:
14411
+ if (action.parameters?.customAttributes) {
14412
+ this.applyLaunchContextAttributes(action.parameters.customAttributes);
14413
+ }
14439
14414
  if (action.parameters?.step) {
14440
14415
  if (this.previousFlowStep?.id === action.parameters.step) {
14441
14416
  this.back();
@@ -14561,6 +14536,20 @@ class NamiFlow extends BasicNamiFlow {
14561
14536
  });
14562
14537
  }
14563
14538
  break;
14539
+ case exports.NamiFlowActionFunction.SET_LAUNCH_CONTEXT: {
14540
+ // Two supported shapes: { customAttributes: {...} } (nav-action wire shape)
14541
+ // and { key, value } (used by Apple/Android/Roku). We deliberately do NOT
14542
+ // treat arbitrary top-level parameters as attributes to avoid writing
14543
+ // reserved keys (step, delay, …) into the launch context.
14544
+ const params = action.parameters;
14545
+ if (params?.customAttributes) {
14546
+ this.applyLaunchContextAttributes(params.customAttributes);
14547
+ }
14548
+ else if (params?.key !== undefined && params?.value !== undefined) {
14549
+ this.applyLaunchContextAttributes({ [params.key]: params.value });
14550
+ }
14551
+ break;
14552
+ }
14564
14553
  default:
14565
14554
  logger.warn(`Missing action handler for ${action.function}`, action);
14566
14555
  break;
package/dist/index.d.ts CHANGED
@@ -102,7 +102,7 @@ type FilterOperator = (typeof FilterOperator)[keyof typeof FilterOperator];
102
102
  interface NamiConditionFilter {
103
103
  identifier: string;
104
104
  operator: FilterOperator;
105
- values: (string | number)[];
105
+ values: (string | boolean | number)[];
106
106
  }
107
107
  interface NamiConditions {
108
108
  filter?: NamiConditionFilter[];
@@ -134,7 +134,8 @@ declare enum NamiFlowActionFunction {
134
134
  FLOW_DISABLED = "flowInteractionDisabled",
135
135
  SET_TAGS = "setTags",
136
136
  PAUSE = "flowPause",
137
- RESUME = "flowResume"
137
+ RESUME = "flowResume",
138
+ SET_LAUNCH_CONTEXT = "setLaunchContext"
138
139
  }
139
140
  type NamiFlowHandoffStepHandler = (handoffTag: string, handoffData?: Record<string, any>) => void;
140
141
  type NamiFlowEventHandler = (eventHandler: Record<string, any>) => void;
@@ -1303,6 +1304,14 @@ type NamiPaywallEvent = {
1303
1304
  purchaseChannel?: string;
1304
1305
  };
1305
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;
1306
1315
  /**
1307
1316
  * @type NamiPaywallLaunchContext
1308
1317
  * Will be used to pass custom context while launching paywall
@@ -1310,7 +1319,7 @@ type NamiPaywallActionHandler = (event: NamiPaywallEvent) => void;
1310
1319
  type NamiPaywallLaunchContext = {
1311
1320
  productGroups?: string[];
1312
1321
  customAttributes: {
1313
- [key: string]: string;
1322
+ [key: string]: NamiCustomAttributeValue;
1314
1323
  };
1315
1324
  customObject?: {
1316
1325
  [key: string]: any;
@@ -1930,6 +1939,7 @@ declare class NamiFlow extends BasicNamiFlow {
1930
1939
  [timerId: string]: TimerState;
1931
1940
  };
1932
1941
  constructor(campaign: NamiFlowCampaign, paywall: PaywallHandle, manager: NamiFlowManager$2, context?: NamiPaywallLaunchContext);
1942
+ private applyLaunchContextAttributes;
1933
1943
  private registerResolvers;
1934
1944
  get currentFlowStep(): NamiFlowStep | undefined;
1935
1945
  get nextStepAvailable(): boolean;
@@ -3062,6 +3072,8 @@ declare class NamiConditionEvaluator {
3062
3072
  evaluate(conditions: NamiConditions): boolean;
3063
3073
  evaluateOrdered(conditions?: NamiConditions): boolean;
3064
3074
  private evaluateFilter;
3075
+ /** Type-strict equality: string↔string (optionally case-insensitive), boolean↔boolean, or number↔number. */
3076
+ private strictEquals;
3065
3077
  private resolve;
3066
3078
  private resolveRaw;
3067
3079
  private extractProperty;
@@ -3476,4 +3488,4 @@ declare namespace internal {
3476
3488
  }
3477
3489
 
3478
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 };
3479
- 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.202606120301",
99
+ NAMI_SDK_PACKAGE_VERSION = "3.4.3-dev.202606121502",
100
100
  // environments
101
101
  PRODUCTION = "production", DEVELOPMENT = "development",
102
102
  // error messages
@@ -6801,10 +6801,6 @@ const shouldLogHTTPTraffic = () => {
6801
6801
  const namiCommands = storageService.getNamiConfig()?.namiCommands;
6802
6802
  return namiCommands?.includes(LOG_HTTP_TRAFFIC) ?? false;
6803
6803
  };
6804
- const shouldLogStartupTelemetry = () => {
6805
- const namiCommands = storageService.getNamiConfig()?.namiCommands;
6806
- return namiCommands?.includes(STARTUP_TELEMETRY) ?? false;
6807
- };
6808
6804
  function tryParseJson(str) {
6809
6805
  const trimmed = str.trim();
6810
6806
  if (!trimmed.startsWith("{") && !trimmed.startsWith("["))
@@ -11942,9 +11938,10 @@ const PREFIX = "[NamiStartup]";
11942
11938
  * Module-level singleton for startup timing instrumentation.
11943
11939
  *
11944
11940
  * All methods are cheap no-ops when the `startupTelemetry` namiCommand flag
11945
- * is absent. The flag is read once at `start()` and cached; subsequent calls
11946
- * to `mark()` and the span helpers use that cached boolean so the hot path
11947
- * never hits storage.
11941
+ * is absent. The flag is read once at `start()` from the namiCommands passed
11942
+ * to the current configure() call NOT from persisted storage, which still
11943
+ * holds the previous run's config at that point (NAM-1876) — and cached;
11944
+ * subsequent calls to `mark()` and the span helpers use that cached boolean.
11948
11945
  *
11949
11946
  * Log format (info level via the shared logger):
11950
11947
  * [NamiStartup] phase=<name> ms=<n>
@@ -11963,11 +11960,13 @@ class StartupTelemetry {
11963
11960
  this.firstLaunchLogged = false;
11964
11961
  }
11965
11962
  /**
11966
- * Call at the first line of configure(). Reads the feature flag once and
11967
- * stores t0 if the flag is on.
11963
+ * Call at the first line of configure(), passing the incoming config's
11964
+ * namiCommands. Reads the feature flag once and stores t0 if the flag is on.
11965
+ *
11966
+ * @param namiCommands The `namiCommands` from the current configure() options.
11968
11967
  */
11969
- start() {
11970
- this.enabled = shouldLogStartupTelemetry();
11968
+ start(namiCommands) {
11969
+ this.enabled = namiCommands?.includes(STARTUP_TELEMETRY) ?? false;
11971
11970
  if (!this.enabled)
11972
11971
  return;
11973
11972
  this.t0 = Date.now();
@@ -12361,7 +12360,7 @@ class Nami {
12361
12360
  * @returns {Promise<NamiConfigurationState>}
12362
12361
  */
12363
12362
  static async configure(options) {
12364
- startupTelemetry.start();
12363
+ startupTelemetry.start(options.namiCommands);
12365
12364
  if (!options.appPlatformID) {
12366
12365
  throw new PlatformIDRequiredError();
12367
12366
  }
@@ -12546,6 +12545,7 @@ var NamiFlowActionFunction;
12546
12545
  NamiFlowActionFunction["SET_TAGS"] = "setTags";
12547
12546
  NamiFlowActionFunction["PAUSE"] = "flowPause";
12548
12547
  NamiFlowActionFunction["RESUME"] = "flowResume";
12548
+ NamiFlowActionFunction["SET_LAUNCH_CONTEXT"] = "setLaunchContext";
12549
12549
  })(NamiFlowActionFunction || (NamiFlowActionFunction = {}));
12550
12550
  const HandoffTag = {
12551
12551
  SEQUENCE: '__handoff_sequence__',
@@ -12690,89 +12690,33 @@ class NamiConditionEvaluator {
12690
12690
  return true;
12691
12691
  return false;
12692
12692
  }
12693
- // this.log(
12694
- // `Evaluating filter: ${filter.operator} with values: ${filter.values} against resolved value: ${JSON.stringify(resolvedValue)}`
12695
- // );
12696
12693
  switch (filter.operator) {
12697
12694
  case FilterOperator.I_CONTAINS:
12698
12695
  if (Array.isArray(resolvedValue)) {
12699
- const result = filter.values.some(expected => {
12700
- const expectedStr = String(expected);
12701
- return resolvedValue.some(item => item.localeCompare(expectedStr, undefined, { sensitivity: 'accent' }) === 0);
12702
- });
12703
- // this.log(`i_contains evaluation result: ${result}`);
12696
+ const result = filter.values.some(expected => typeof expected === 'string' && resolvedValue.some(item => item.localeCompare(expected, undefined, { sensitivity: 'accent' }) === 0));
12704
12697
  return result;
12705
12698
  }
12706
12699
  this.log('Resolved value is not an array of strings for i_contains.');
12707
12700
  return false;
12708
12701
  case FilterOperator.EQUALS: {
12709
- const result = filter.values.some(expected => {
12710
- const expectedStr = String(expected);
12711
- if (typeof resolvedValue === 'string') {
12712
- // this.log(`Comparing string ${resolvedValue} == ${expected}`);
12713
- return resolvedValue === expectedStr;
12714
- }
12715
- if (typeof resolvedValue === 'boolean') {
12716
- const expectedBool = expectedStr.toLowerCase() === 'true';
12717
- // this.log(`Comparing boolean ${resolvedValue} == ${expectedBool}`);
12718
- return resolvedValue === expectedBool;
12719
- }
12720
- this.log(`Unsupported type for equals comparison: ${typeof resolvedValue}`);
12721
- return false;
12722
- });
12723
- // this.log(`equals evaluation result: ${result}`);
12724
- return result;
12702
+ return filter.values.some(expected => this.strictEquals(resolvedValue, expected, false));
12725
12703
  }
12726
12704
  case FilterOperator.I_EQUALS: {
12727
- const result = filter.values.some(expected => {
12728
- const expectedStr = String(expected);
12729
- if (typeof resolvedValue === 'string') {
12730
- const match = resolvedValue.localeCompare(expectedStr, undefined, { sensitivity: 'accent' }) === 0;
12731
- // this.log(`Comparing string ${resolvedValue} ~= ${expected}: ${match}`);
12732
- return match;
12733
- }
12734
- if (typeof resolvedValue === 'boolean') {
12735
- const expectedBool = expectedStr.toLowerCase() === 'true';
12736
- // this.log(`Comparing boolean ${resolvedValue} ~= ${expectedBool}`);
12737
- return resolvedValue === expectedBool;
12738
- }
12739
- this.log(`Unsupported type for i_equals comparison: ${typeof resolvedValue}`);
12740
- return false;
12741
- });
12742
- // this.log(`i_equals evaluation result: ${result}`);
12743
- return result;
12705
+ return filter.values.some(expected => this.strictEquals(resolvedValue, expected, true));
12744
12706
  }
12745
12707
  case FilterOperator.NOT_EQUALS: {
12746
- const result = filter.values.every(expected => {
12747
- const expectedStr = String(expected);
12748
- if (typeof resolvedValue === 'string') {
12749
- return resolvedValue !== expectedStr;
12750
- }
12751
- return true;
12752
- });
12753
- // this.log(`not_equals evaluation result: ${result}`);
12754
- return result;
12708
+ return filter.values.every(expected => !this.strictEquals(resolvedValue, expected, false));
12755
12709
  }
12756
12710
  case FilterOperator.NOT_I_EQUALS: {
12757
- const result = filter.values.every(expected => {
12758
- const expectedStr = String(expected);
12759
- if (typeof resolvedValue === 'string') {
12760
- return (resolvedValue.localeCompare(expectedStr, undefined, { sensitivity: 'accent' }) !== 0);
12761
- }
12762
- return true;
12763
- });
12764
- // this.log(`not_i_equals evaluation result: ${result}`);
12765
- return result;
12711
+ return filter.values.every(expected => !this.strictEquals(resolvedValue, expected, true));
12766
12712
  }
12767
12713
  case FilterOperator.NOT_CONTAINS: {
12768
12714
  const result = filter.values.every(expected => {
12769
- const expectedStr = String(expected);
12770
- if (typeof resolvedValue === 'string') {
12771
- return !resolvedValue.includes(expectedStr);
12715
+ if (typeof resolvedValue === 'string' && typeof expected === 'string') {
12716
+ return !resolvedValue.includes(expected);
12772
12717
  }
12773
12718
  return true;
12774
12719
  });
12775
- // this.log(`not_contains evaluation result: ${result}`);
12776
12720
  return result;
12777
12721
  }
12778
12722
  case FilterOperator.GREATER_THAN:
@@ -12801,6 +12745,21 @@ class NamiConditionEvaluator {
12801
12745
  return false;
12802
12746
  }
12803
12747
  }
12748
+ /** Type-strict equality: string↔string (optionally case-insensitive), boolean↔boolean, or number↔number. */
12749
+ strictEquals(resolved, expected, caseInsensitive) {
12750
+ if (typeof resolved === 'string' && typeof expected === 'string') {
12751
+ return caseInsensitive
12752
+ ? resolved.localeCompare(expected, undefined, { sensitivity: 'accent' }) === 0
12753
+ : resolved === expected;
12754
+ }
12755
+ if (typeof resolved === 'boolean' && typeof expected === 'boolean') {
12756
+ return resolved === expected;
12757
+ }
12758
+ if (typeof resolved === 'number' && typeof expected === 'number') {
12759
+ return resolved === expected;
12760
+ }
12761
+ return false;
12762
+ }
12804
12763
  resolve(identifier) {
12805
12764
  const exact = this.resolveRaw(identifier);
12806
12765
  if (exact !== undefined) {
@@ -12863,7 +12822,6 @@ class NamiConditionEvaluator {
12863
12822
  }
12864
12823
  break;
12865
12824
  default:
12866
- // this.log(`Unsupported property: .${property}`);
12867
12825
  return undefined;
12868
12826
  }
12869
12827
  this.log(`Could not extract .${property} from type ${typeof baseValue}`);
@@ -14170,6 +14128,17 @@ class NamiFlow extends BasicNamiFlow {
14170
14128
  this.forward(entry.id);
14171
14129
  }
14172
14130
  }
14131
+ applyLaunchContextAttributes(attrs) {
14132
+ if (!this.context) {
14133
+ this.context = { customAttributes: {} };
14134
+ new LaunchContextResolver(this.context);
14135
+ }
14136
+ // Guard against a runtime-supplied context that omits customAttributes
14137
+ // (the field is required by the type but may be absent in untyped JSON).
14138
+ this.context.customAttributes ??= {};
14139
+ // New values win: merge attrs over existing entries.
14140
+ Object.assign(this.context.customAttributes, attrs);
14141
+ }
14173
14142
  registerResolvers(context) {
14174
14143
  if (context) {
14175
14144
  new LaunchContextResolver(context);
@@ -14426,6 +14395,9 @@ class NamiFlow extends BasicNamiFlow {
14426
14395
  this.back();
14427
14396
  break;
14428
14397
  case NamiFlowActionFunction.NEXT:
14398
+ if (action.parameters?.customAttributes) {
14399
+ this.applyLaunchContextAttributes(action.parameters.customAttributes);
14400
+ }
14429
14401
  if (action.parameters?.step) {
14430
14402
  this.forward(action.parameters.step);
14431
14403
  }
@@ -14434,6 +14406,9 @@ class NamiFlow extends BasicNamiFlow {
14434
14406
  }
14435
14407
  break;
14436
14408
  case NamiFlowActionFunction.NAVIGATE:
14409
+ if (action.parameters?.customAttributes) {
14410
+ this.applyLaunchContextAttributes(action.parameters.customAttributes);
14411
+ }
14437
14412
  if (action.parameters?.step) {
14438
14413
  if (this.previousFlowStep?.id === action.parameters.step) {
14439
14414
  this.back();
@@ -14559,6 +14534,20 @@ class NamiFlow extends BasicNamiFlow {
14559
14534
  });
14560
14535
  }
14561
14536
  break;
14537
+ case NamiFlowActionFunction.SET_LAUNCH_CONTEXT: {
14538
+ // Two supported shapes: { customAttributes: {...} } (nav-action wire shape)
14539
+ // and { key, value } (used by Apple/Android/Roku). We deliberately do NOT
14540
+ // treat arbitrary top-level parameters as attributes to avoid writing
14541
+ // reserved keys (step, delay, …) into the launch context.
14542
+ const params = action.parameters;
14543
+ if (params?.customAttributes) {
14544
+ this.applyLaunchContextAttributes(params.customAttributes);
14545
+ }
14546
+ else if (params?.key !== undefined && params?.value !== undefined) {
14547
+ this.applyLaunchContextAttributes({ [params.key]: params.value });
14548
+ }
14549
+ break;
14550
+ }
14562
14551
  default:
14563
14552
  logger.warn(`Missing action handler for ${action.function}`, action);
14564
14553
  break;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@namiml/sdk-core",
3
- "version": "3.4.3-dev.202606120301",
3
+ "version": "3.4.3-dev.202606121502",
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",