@namiml/sdk-core 3.4.1-dev.202605270014 → 3.4.1-dev.202605280043

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.1",
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.1-dev.202605270014",
101
+ NAMI_SDK_PACKAGE_VERSION: exports.NAMI_SDK_PACKAGE_VERSION = "3.4.1-dev.202605280043",
102
102
  // environments
103
103
  PRODUCTION: exports.PRODUCTION = "production", DEVELOPMENT: exports.DEVELOPMENT = "development",
104
104
  // error messages
@@ -108,7 +108,7 @@ AUTH_DEVICE: exports.AUTH_DEVICE = "nami_auth_device", NAMI_CONFIGURATION: expor
108
108
  // API settings
109
109
  API_VERSION: exports.API_VERSION = "v3", BASE_URL_PATH: exports.BASE_URL_PATH = `sdk/${exports.API_VERSION}/platform`, BASE_URL: exports.BASE_URL = "https://app.namiml.com", BASE_STAGING_URL: exports.BASE_STAGING_URL = "https://app-staging.namiml.com", CUSTOM_HOST_PREFIX: exports.CUSTOM_HOST_PREFIX = "namiAPIHost=", USE_STAGING_API: exports.USE_STAGING_API = "useStagingAPI",
110
110
  // // extended client info
111
- EXTENDED_CLIENT_INFO_PREFIX: exports.EXTENDED_CLIENT_INFO_PREFIX = "extendedClientInfo", EXTENDED_CLIENT_INFO_DELIMITER: exports.EXTENDED_CLIENT_INFO_DELIMITER = ":", VALIDATE_PRODUCT_GROUPS: exports.VALIDATE_PRODUCT_GROUPS = "validateProductGroups", LOG_HTTP_REQUESTS: exports.LOG_HTTP_REQUESTS = "logHTTPRequests", LOG_HTTP_TRAFFIC: exports.LOG_HTTP_TRAFFIC = "logHTTPTraffic", EXTENDED_PLATFORM: exports.EXTENDED_PLATFORM = "extended-platform", EXTENDED_PLATFORM_VERSION: exports.EXTENDED_PLATFORM_VERSION = "extended-platform-version", API_MAX_CALLS_LIMIT: exports.API_MAX_CALLS_LIMIT = 2, API_RETRY_DELAY_SEC: exports.API_RETRY_DELAY_SEC = 2, API_TIMEOUT_LIMIT: exports.API_TIMEOUT_LIMIT = 20000, DEVICE_API_TIMEOUT_LIMIT: exports.DEVICE_API_TIMEOUT_LIMIT = 2000,
111
+ EXTENDED_CLIENT_INFO_PREFIX: exports.EXTENDED_CLIENT_INFO_PREFIX = "extendedClientInfo", EXTENDED_CLIENT_INFO_DELIMITER: exports.EXTENDED_CLIENT_INFO_DELIMITER = ":", VALIDATE_PRODUCT_GROUPS: exports.VALIDATE_PRODUCT_GROUPS = "validateProductGroups", LOG_HTTP_REQUESTS: exports.LOG_HTTP_REQUESTS = "logHTTPRequests", LOG_HTTP_TRAFFIC: exports.LOG_HTTP_TRAFFIC = "logHTTPTraffic", STARTUP_TELEMETRY: exports.STARTUP_TELEMETRY = "startupTelemetry", EXTENDED_PLATFORM: exports.EXTENDED_PLATFORM = "extended-platform", EXTENDED_PLATFORM_VERSION: exports.EXTENDED_PLATFORM_VERSION = "extended-platform-version", API_MAX_CALLS_LIMIT: exports.API_MAX_CALLS_LIMIT = 2, API_RETRY_DELAY_SEC: exports.API_RETRY_DELAY_SEC = 2, API_TIMEOUT_LIMIT: exports.API_TIMEOUT_LIMIT = 20000, DEVICE_API_TIMEOUT_LIMIT: exports.DEVICE_API_TIMEOUT_LIMIT = 2000,
112
112
  // status codes
113
113
  STATUS_SUCCESS: exports.STATUS_SUCCESS = 200, STATUS_BAD_REQUEST: exports.STATUS_BAD_REQUEST = 400, STATUS_NOT_FOUND: exports.STATUS_NOT_FOUND = 404, STATUS_CONFLICT: exports.STATUS_CONFLICT = 409, STATUS_INTERNAL_SERVER_ERROR: exports.STATUS_INTERNAL_SERVER_ERROR = 500,
114
114
  // configuration states
@@ -6803,6 +6803,10 @@ 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
+ };
6806
6810
  function tryParseJson(str) {
6807
6811
  const trimmed = str.trim();
6808
6812
  if (!trimmed.startsWith("{") && !trimmed.startsWith("["))
@@ -11925,6 +11929,123 @@ class CustomerJourneyRepository {
11925
11929
  }
11926
11930
  CustomerJourneyRepository.instance = new CustomerJourneyRepository();
11927
11931
 
11932
+ const PREFIX = "[NamiStartup]";
11933
+ /**
11934
+ * Module-level singleton for startup timing instrumentation.
11935
+ *
11936
+ * All methods are cheap no-ops when the `startupTelemetry` namiCommand flag
11937
+ * is absent. The flag is read once at `start()` and cached; subsequent calls
11938
+ * to `mark()` and the span helpers use that cached boolean so the hot path
11939
+ * never hits storage.
11940
+ *
11941
+ * Log format (info level via the shared logger):
11942
+ * [NamiStartup] phase=<name> ms=<n>
11943
+ * [NamiStartup] SUMMARY total_to_ready=<ms> decode=<ms> process=<ms> placements=<n>
11944
+ */
11945
+ class StartupTelemetry {
11946
+ constructor() {
11947
+ this.enabled = false;
11948
+ this.t0 = 0;
11949
+ this.lastMark = 0;
11950
+ // Accumulated segment durations for the SUMMARY line
11951
+ this.decodeDuration = 0;
11952
+ this.processDuration = 0;
11953
+ this.placements = 0;
11954
+ // One-shot guard: only log the very first launch lookup
11955
+ this.firstLaunchLogged = false;
11956
+ }
11957
+ /**
11958
+ * Call at the first line of configure(). Reads the feature flag once and
11959
+ * stores t0 if the flag is on.
11960
+ */
11961
+ start() {
11962
+ this.enabled = shouldLogStartupTelemetry();
11963
+ if (!this.enabled)
11964
+ return;
11965
+ this.t0 = Date.now();
11966
+ this.lastMark = this.t0;
11967
+ this.decodeDuration = 0;
11968
+ this.processDuration = 0;
11969
+ this.placements = 0;
11970
+ this.firstLaunchLogged = false;
11971
+ }
11972
+ /**
11973
+ * Log a named phase checkpoint.
11974
+ *
11975
+ * - Emits `[NamiStartup] phase=<name> ms=<segment> [extras]`
11976
+ * - On phase `ready`: also emits `total_to_ready=<ms>` and a SUMMARY line.
11977
+ * - On phase `initial_config_decoded`: records the decode segment.
11978
+ * - On phase `initial_processed`: records the process segment + placement count.
11979
+ *
11980
+ * @param phase Phase key string (e.g. "initial_config_decoded").
11981
+ * @param extras Optional key/value pairs appended to the log line.
11982
+ */
11983
+ mark(phase, extras) {
11984
+ if (!this.enabled)
11985
+ return;
11986
+ const now = Date.now();
11987
+ const segmentMs = now - this.lastMark;
11988
+ this.lastMark = now;
11989
+ // Accumulate segment durations for SUMMARY
11990
+ if (phase === "initial_config_decoded") {
11991
+ this.decodeDuration = segmentMs;
11992
+ }
11993
+ else if (phase === "initial_processed") {
11994
+ this.processDuration = segmentMs;
11995
+ if (extras?.count !== undefined) {
11996
+ this.placements = extras.count;
11997
+ }
11998
+ }
11999
+ const extrasStr = extras
12000
+ ? " " + Object.entries(extras).map(([k, v]) => `${k}=${v}`).join(" ")
12001
+ : "";
12002
+ if (phase === "ready") {
12003
+ const totalToReady = now - this.t0;
12004
+ logger.info(`${PREFIX} phase=${phase} ms=${segmentMs} total_to_ready=${totalToReady}${extrasStr}`);
12005
+ logger.info(`${PREFIX} SUMMARY total_to_ready=${totalToReady} decode=${this.decodeDuration} process=${this.processDuration} placements=${this.placements}`);
12006
+ }
12007
+ else {
12008
+ logger.info(`${PREFIX} phase=${phase} ms=${segmentMs}${extrasStr}`);
12009
+ }
12010
+ }
12011
+ /**
12012
+ * Wrap the very first launch lookup call. Only the first invocation is
12013
+ * instrumented; subsequent calls return immediately without logging.
12014
+ *
12015
+ * @param label The campaign label being looked up.
12016
+ * @param fn The synchronous lookup function to time.
12017
+ * @returns The return value of `fn`.
12018
+ */
12019
+ firstLaunchLookup(label, fn) {
12020
+ if (!this.enabled || this.firstLaunchLogged) {
12021
+ return fn();
12022
+ }
12023
+ this.firstLaunchLogged = true;
12024
+ const t = Date.now();
12025
+ const result = fn();
12026
+ const ms = Date.now() - t;
12027
+ logger.info(`${PREFIX} phase=first_launch_lookup ms=${ms} label=${label}`);
12028
+ return result;
12029
+ }
12030
+ /**
12031
+ * Wrap the background server fetch. Logs duration and campaign count when
12032
+ * the promise settles.
12033
+ *
12034
+ * @param fn An async function that performs the server fetch.
12035
+ * @returns A promise that resolves/rejects with the same value as `fn`.
12036
+ */
12037
+ async serverFetch(fn) {
12038
+ if (!this.enabled)
12039
+ return fn();
12040
+ const t = Date.now();
12041
+ const result = await fn();
12042
+ const ms = Date.now() - t;
12043
+ logger.info(`${PREFIX} phase=server_fetch ms=${ms} campaigns=${result.length}`);
12044
+ return result;
12045
+ }
12046
+ }
12047
+ const startupTelemetry = new StartupTelemetry();
12048
+
11928
12049
  class NamiRefs {
11929
12050
  constructor() {
11930
12051
  // In-memory flag for device registration failures (not persisted)
@@ -11992,7 +12113,9 @@ class NamiRefs {
11992
12113
  : this.initIdentifiedDevice(config);
11993
12114
  const splitPosition = audienceSplitPosition(deviceId);
11994
12115
  if (initialConfig.campaign_rules) {
11995
- storageService.setCampaignRules(exports.INITIAL_CAMPAIGN_RULES, mapAnonymousCampaigns(initialConfig.campaign_rules, splitPosition, currentFormFactor));
12116
+ const mappedCampaigns = mapAnonymousCampaigns(initialConfig.campaign_rules, splitPosition, currentFormFactor);
12117
+ storageService.setCampaignRules(exports.INITIAL_CAMPAIGN_RULES, mappedCampaigns);
12118
+ startupTelemetry.mark("initial_processed", { count: mappedCampaigns.length });
11996
12119
  }
11997
12120
  if (deviceTask)
11998
12121
  await deviceTask;
@@ -12002,7 +12125,7 @@ class NamiRefs {
12002
12125
  ProductRepository.instance.fetchProducts(),
12003
12126
  CustomerJourneyRepository.instance.fetchCustomerJourneyState(),
12004
12127
  EntitlementRepository.instance.fetchActiveEntitlements(),
12005
- this.fetchCampaignsAndPaywalls(),
12128
+ startupTelemetry.serverFetch(() => this.fetchCampaignsAndPaywalls()),
12006
12129
  ]).catch(async (error) => {
12007
12130
  if (error instanceof ConflictError) {
12008
12131
  storageService.resetDevice();
@@ -12230,10 +12353,12 @@ class Nami {
12230
12353
  * @returns {Promise<NamiConfigurationState>}
12231
12354
  */
12232
12355
  static async configure(options) {
12356
+ startupTelemetry.start();
12233
12357
  if (!options.appPlatformID) {
12234
12358
  throw new PlatformIDRequiredError();
12235
12359
  }
12236
12360
  this.setInitialConfig(options);
12361
+ startupTelemetry.mark("initial_config_decoded");
12237
12362
  return await Nami.instance.initializeSDK(options);
12238
12363
  }
12239
12364
  static setInitialConfig(config) {
@@ -12312,6 +12437,7 @@ class Nami {
12312
12437
  logger.info("SDK successfully initialized!");
12313
12438
  const state = partialReconfig || fullReconfig ? exports.RECONFIG_SUCCESS : exports.INITIAL_SUCCESS;
12314
12439
  getPlatformAdapters().ui.postConfigure?.();
12440
+ startupTelemetry.mark("ready");
12315
12441
  return {
12316
12442
  sdkInitialized: true,
12317
12443
  configureState: state,
@@ -12412,6 +12538,7 @@ exports.NamiFlowActionFunction = void 0;
12412
12538
  NamiFlowActionFunction["SET_TAGS"] = "setTags";
12413
12539
  NamiFlowActionFunction["PAUSE"] = "flowPause";
12414
12540
  NamiFlowActionFunction["RESUME"] = "flowResume";
12541
+ NamiFlowActionFunction["SET_LAUNCH_CONTEXT"] = "setLaunchContext";
12415
12542
  })(exports.NamiFlowActionFunction || (exports.NamiFlowActionFunction = {}));
12416
12543
  const HandoffTag = {
12417
12544
  SEQUENCE: '__handoff_sequence__',
@@ -13950,6 +14077,16 @@ class NamiFlow extends BasicNamiFlow {
13950
14077
  this.forward(entry.id);
13951
14078
  }
13952
14079
  }
14080
+ applyLaunchContextAttributes(attrs) {
14081
+ if (!this.context) {
14082
+ this.context = { customAttributes: {} };
14083
+ new LaunchContextResolver(this.context);
14084
+ }
14085
+ // Guard against a runtime-supplied context that omits customAttributes
14086
+ // (the field is required by the type but may be absent in untyped JSON).
14087
+ this.context.customAttributes ??= {};
14088
+ Object.assign(this.context.customAttributes, attrs);
14089
+ }
13953
14090
  registerResolvers(context) {
13954
14091
  if (context) {
13955
14092
  new LaunchContextResolver(context);
@@ -14206,6 +14343,9 @@ class NamiFlow extends BasicNamiFlow {
14206
14343
  this.back();
14207
14344
  break;
14208
14345
  case exports.NamiFlowActionFunction.NEXT:
14346
+ if (action.parameters?.customAttributes) {
14347
+ this.applyLaunchContextAttributes(action.parameters.customAttributes);
14348
+ }
14209
14349
  if (action.parameters?.step) {
14210
14350
  this.forward(action.parameters.step);
14211
14351
  }
@@ -14214,6 +14354,9 @@ class NamiFlow extends BasicNamiFlow {
14214
14354
  }
14215
14355
  break;
14216
14356
  case exports.NamiFlowActionFunction.NAVIGATE:
14357
+ if (action.parameters?.customAttributes) {
14358
+ this.applyLaunchContextAttributes(action.parameters.customAttributes);
14359
+ }
14217
14360
  if (action.parameters?.step) {
14218
14361
  if (this.previousFlowStep?.id === action.parameters.step) {
14219
14362
  this.back();
@@ -14339,6 +14482,20 @@ class NamiFlow extends BasicNamiFlow {
14339
14482
  });
14340
14483
  }
14341
14484
  break;
14485
+ case exports.NamiFlowActionFunction.SET_LAUNCH_CONTEXT: {
14486
+ // Two supported shapes (matches the nav-action customAttributes wire shape
14487
+ // and the { key, value } shape used by Apple/Android/Roku). We deliberately
14488
+ // do NOT treat arbitrary top-level parameters as attributes, to avoid writing
14489
+ // reserved keys (step, delay, …) into the launch context.
14490
+ const params = action.parameters;
14491
+ if (params?.customAttributes) {
14492
+ this.applyLaunchContextAttributes(params.customAttributes);
14493
+ }
14494
+ else if (params?.key !== undefined && params?.value !== undefined) {
14495
+ this.applyLaunchContextAttributes({ [params.key]: params.value });
14496
+ }
14497
+ break;
14498
+ }
14342
14499
  default:
14343
14500
  logger.warn(`Missing action handler for ${action.function}`, action);
14344
14501
  break;
@@ -14566,7 +14723,7 @@ class NamiCampaignManager {
14566
14723
  resultCallback(false, exports.LaunchCampaignError.SDK_NOT_INITIALIZED);
14567
14724
  throw new SDKNotInitializedError();
14568
14725
  }
14569
- const data = getPaywallDataFromLabel(value, type);
14726
+ const data = startupTelemetry.firstLaunchLookup(value, () => getPaywallDataFromLabel(value, type));
14570
14727
  let paywall = data.paywall;
14571
14728
  const campaign = data.campaign;
14572
14729
  if (!campaign || (!paywall && !campaign.flow)) {
package/dist/index.d.ts CHANGED
@@ -130,7 +130,8 @@ declare enum NamiFlowActionFunction {
130
130
  FLOW_DISABLED = "flowInteractionDisabled",
131
131
  SET_TAGS = "setTags",
132
132
  PAUSE = "flowPause",
133
- RESUME = "flowResume"
133
+ RESUME = "flowResume",
134
+ SET_LAUNCH_CONTEXT = "setLaunchContext"
134
135
  }
135
136
  type NamiFlowHandoffStepHandler = (handoffTag: string, handoffData?: Record<string, any>) => void;
136
137
  type NamiFlowEventHandler = (eventHandler: Record<string, any>) => void;
@@ -1429,6 +1430,7 @@ declare class NamiFlow extends BasicNamiFlow {
1429
1430
  [timerId: string]: TimerState;
1430
1431
  };
1431
1432
  constructor(campaign: NamiFlowCampaign, paywall: PaywallHandle, manager: NamiFlowManager, context?: NamiPaywallLaunchContext);
1433
+ private applyLaunchContextAttributes;
1432
1434
  private registerResolvers;
1433
1435
  get currentFlowStep(): NamiFlowStep | undefined;
1434
1436
  get nextStepAvailable(): boolean;
@@ -2641,6 +2643,7 @@ declare const EXTENDED_CLIENT_INFO_DELIMITER: string;
2641
2643
  declare const VALIDATE_PRODUCT_GROUPS: string;
2642
2644
  declare const LOG_HTTP_REQUESTS: string;
2643
2645
  declare const LOG_HTTP_TRAFFIC: string;
2646
+ declare const STARTUP_TELEMETRY: string;
2644
2647
  declare const EXTENDED_PLATFORM: string;
2645
2648
  declare const EXTENDED_PLATFORM_VERSION: string;
2646
2649
  declare const API_MAX_CALLS_LIMIT: number;
@@ -3176,5 +3179,5 @@ declare const getBillingPeriodNumber: (billingPeriod: string) => number;
3176
3179
  declare const formattedPrice: (price: number) => number;
3177
3180
  declare function toDouble(num: number): number;
3178
3181
 
3179
- 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, NamiCampaignRuleType, NamiConditionEvaluator, NamiCustomerManager, NamiEntitlementManager, NamiEventEmitter, NamiFlow, NamiFlowActionFunction, NamiFlowManager, NamiFlowStepType, NamiPaywallAction, NamiPaywallManager, PaywallManagerEvents as NamiPaywallManagerEvents, NamiProfileManager, 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, 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, activateEntitlementByPurchase, activeEntitlements, aggregateScreenreaderText, allCampaigns, allPaywalls, applyEntitlementActivation, audienceSplitPosition, bestUrlCampaignMatch, bigintToUuid, checkAnySkuHasPromoOffer, checkAnySkuHasTrialOffer, coerceBooleanish, 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 };
3182
+ 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, NamiCampaignRuleType, NamiConditionEvaluator, NamiCustomerManager, NamiEntitlementManager, NamiEventEmitter, NamiFlow, NamiFlowActionFunction, NamiFlowManager, NamiFlowStepType, NamiPaywallAction, NamiPaywallManager, PaywallManagerEvents as NamiPaywallManagerEvents, NamiProfileManager, 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, activateEntitlementByPurchase, activeEntitlements, aggregateScreenreaderText, allCampaigns, allPaywalls, applyEntitlementActivation, audienceSplitPosition, bestUrlCampaignMatch, bigintToUuid, checkAnySkuHasPromoOffer, checkAnySkuHasTrialOffer, coerceBooleanish, 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 };
3180
3183
  export type { 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, NamiCampaignSegment, NamiConfiguration, NamiConfigurationState, NamiCustomAttributeValue, NamiEntitlement$1 as NamiEntitlement, NamiFlowAction, NamiFlowAnimation, NamiFlowCampaign, NamiFlowDTO, NamiFlowEventHandler, NamiFlowHandoffStepHandler, NamiFlowObjectDTO, NamiFlowOn, NamiFlowStep, NamiFlowTransition, NamiFlowTransitionDirection, NamiFlowWithObject, NamiInitialConfig, NamiLanguageCodes, NamiLogLevel, NamiPaywallActionHandler, NamiPaywallComponentChange, NamiPaywallEvent, NamiPaywallEventVideoMetadata, NamiPaywallLaunchContext, NamiPresentationStyle, NamiProductDetails, NamiProductOffer, NamiProfile, NamiPurchase, NamiPurchaseCompleteResult, NamiPurchaseDetails, 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, 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.1",
98
98
  // full package version including dev suffix — stamped by scripts/version.sh
99
- NAMI_SDK_PACKAGE_VERSION = "3.4.1-dev.202605270014",
99
+ NAMI_SDK_PACKAGE_VERSION = "3.4.1-dev.202605280043",
100
100
  // environments
101
101
  PRODUCTION = "production", DEVELOPMENT = "development",
102
102
  // error messages
@@ -106,7 +106,7 @@ AUTH_DEVICE = "nami_auth_device", NAMI_CONFIGURATION = "nami_configuration", NAM
106
106
  // API settings
107
107
  API_VERSION = "v3", BASE_URL_PATH = `sdk/${API_VERSION}/platform`, BASE_URL = "https://app.namiml.com", BASE_STAGING_URL = "https://app-staging.namiml.com", CUSTOM_HOST_PREFIX = "namiAPIHost=", USE_STAGING_API = "useStagingAPI",
108
108
  // // extended client info
109
- EXTENDED_CLIENT_INFO_PREFIX = "extendedClientInfo", EXTENDED_CLIENT_INFO_DELIMITER = ":", VALIDATE_PRODUCT_GROUPS = "validateProductGroups", LOG_HTTP_REQUESTS = "logHTTPRequests", LOG_HTTP_TRAFFIC = "logHTTPTraffic", EXTENDED_PLATFORM = "extended-platform", EXTENDED_PLATFORM_VERSION = "extended-platform-version", API_MAX_CALLS_LIMIT = 2, API_RETRY_DELAY_SEC = 2, API_TIMEOUT_LIMIT = 20000, DEVICE_API_TIMEOUT_LIMIT = 2000,
109
+ EXTENDED_CLIENT_INFO_PREFIX = "extendedClientInfo", EXTENDED_CLIENT_INFO_DELIMITER = ":", VALIDATE_PRODUCT_GROUPS = "validateProductGroups", LOG_HTTP_REQUESTS = "logHTTPRequests", LOG_HTTP_TRAFFIC = "logHTTPTraffic", STARTUP_TELEMETRY = "startupTelemetry", EXTENDED_PLATFORM = "extended-platform", EXTENDED_PLATFORM_VERSION = "extended-platform-version", API_MAX_CALLS_LIMIT = 2, API_RETRY_DELAY_SEC = 2, API_TIMEOUT_LIMIT = 20000, DEVICE_API_TIMEOUT_LIMIT = 2000,
110
110
  // status codes
111
111
  STATUS_SUCCESS = 200, STATUS_BAD_REQUEST = 400, STATUS_NOT_FOUND = 404, STATUS_CONFLICT = 409, STATUS_INTERNAL_SERVER_ERROR = 500,
112
112
  // configuration states
@@ -6801,6 +6801,10 @@ 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
+ };
6804
6808
  function tryParseJson(str) {
6805
6809
  const trimmed = str.trim();
6806
6810
  if (!trimmed.startsWith("{") && !trimmed.startsWith("["))
@@ -11923,6 +11927,123 @@ class CustomerJourneyRepository {
11923
11927
  }
11924
11928
  CustomerJourneyRepository.instance = new CustomerJourneyRepository();
11925
11929
 
11930
+ const PREFIX = "[NamiStartup]";
11931
+ /**
11932
+ * Module-level singleton for startup timing instrumentation.
11933
+ *
11934
+ * All methods are cheap no-ops when the `startupTelemetry` namiCommand flag
11935
+ * is absent. The flag is read once at `start()` and cached; subsequent calls
11936
+ * to `mark()` and the span helpers use that cached boolean so the hot path
11937
+ * never hits storage.
11938
+ *
11939
+ * Log format (info level via the shared logger):
11940
+ * [NamiStartup] phase=<name> ms=<n>
11941
+ * [NamiStartup] SUMMARY total_to_ready=<ms> decode=<ms> process=<ms> placements=<n>
11942
+ */
11943
+ class StartupTelemetry {
11944
+ constructor() {
11945
+ this.enabled = false;
11946
+ this.t0 = 0;
11947
+ this.lastMark = 0;
11948
+ // Accumulated segment durations for the SUMMARY line
11949
+ this.decodeDuration = 0;
11950
+ this.processDuration = 0;
11951
+ this.placements = 0;
11952
+ // One-shot guard: only log the very first launch lookup
11953
+ this.firstLaunchLogged = false;
11954
+ }
11955
+ /**
11956
+ * Call at the first line of configure(). Reads the feature flag once and
11957
+ * stores t0 if the flag is on.
11958
+ */
11959
+ start() {
11960
+ this.enabled = shouldLogStartupTelemetry();
11961
+ if (!this.enabled)
11962
+ return;
11963
+ this.t0 = Date.now();
11964
+ this.lastMark = this.t0;
11965
+ this.decodeDuration = 0;
11966
+ this.processDuration = 0;
11967
+ this.placements = 0;
11968
+ this.firstLaunchLogged = false;
11969
+ }
11970
+ /**
11971
+ * Log a named phase checkpoint.
11972
+ *
11973
+ * - Emits `[NamiStartup] phase=<name> ms=<segment> [extras]`
11974
+ * - On phase `ready`: also emits `total_to_ready=<ms>` and a SUMMARY line.
11975
+ * - On phase `initial_config_decoded`: records the decode segment.
11976
+ * - On phase `initial_processed`: records the process segment + placement count.
11977
+ *
11978
+ * @param phase Phase key string (e.g. "initial_config_decoded").
11979
+ * @param extras Optional key/value pairs appended to the log line.
11980
+ */
11981
+ mark(phase, extras) {
11982
+ if (!this.enabled)
11983
+ return;
11984
+ const now = Date.now();
11985
+ const segmentMs = now - this.lastMark;
11986
+ this.lastMark = now;
11987
+ // Accumulate segment durations for SUMMARY
11988
+ if (phase === "initial_config_decoded") {
11989
+ this.decodeDuration = segmentMs;
11990
+ }
11991
+ else if (phase === "initial_processed") {
11992
+ this.processDuration = segmentMs;
11993
+ if (extras?.count !== undefined) {
11994
+ this.placements = extras.count;
11995
+ }
11996
+ }
11997
+ const extrasStr = extras
11998
+ ? " " + Object.entries(extras).map(([k, v]) => `${k}=${v}`).join(" ")
11999
+ : "";
12000
+ if (phase === "ready") {
12001
+ const totalToReady = now - this.t0;
12002
+ logger.info(`${PREFIX} phase=${phase} ms=${segmentMs} total_to_ready=${totalToReady}${extrasStr}`);
12003
+ logger.info(`${PREFIX} SUMMARY total_to_ready=${totalToReady} decode=${this.decodeDuration} process=${this.processDuration} placements=${this.placements}`);
12004
+ }
12005
+ else {
12006
+ logger.info(`${PREFIX} phase=${phase} ms=${segmentMs}${extrasStr}`);
12007
+ }
12008
+ }
12009
+ /**
12010
+ * Wrap the very first launch lookup call. Only the first invocation is
12011
+ * instrumented; subsequent calls return immediately without logging.
12012
+ *
12013
+ * @param label The campaign label being looked up.
12014
+ * @param fn The synchronous lookup function to time.
12015
+ * @returns The return value of `fn`.
12016
+ */
12017
+ firstLaunchLookup(label, fn) {
12018
+ if (!this.enabled || this.firstLaunchLogged) {
12019
+ return fn();
12020
+ }
12021
+ this.firstLaunchLogged = true;
12022
+ const t = Date.now();
12023
+ const result = fn();
12024
+ const ms = Date.now() - t;
12025
+ logger.info(`${PREFIX} phase=first_launch_lookup ms=${ms} label=${label}`);
12026
+ return result;
12027
+ }
12028
+ /**
12029
+ * Wrap the background server fetch. Logs duration and campaign count when
12030
+ * the promise settles.
12031
+ *
12032
+ * @param fn An async function that performs the server fetch.
12033
+ * @returns A promise that resolves/rejects with the same value as `fn`.
12034
+ */
12035
+ async serverFetch(fn) {
12036
+ if (!this.enabled)
12037
+ return fn();
12038
+ const t = Date.now();
12039
+ const result = await fn();
12040
+ const ms = Date.now() - t;
12041
+ logger.info(`${PREFIX} phase=server_fetch ms=${ms} campaigns=${result.length}`);
12042
+ return result;
12043
+ }
12044
+ }
12045
+ const startupTelemetry = new StartupTelemetry();
12046
+
11926
12047
  class NamiRefs {
11927
12048
  constructor() {
11928
12049
  // In-memory flag for device registration failures (not persisted)
@@ -11990,7 +12111,9 @@ class NamiRefs {
11990
12111
  : this.initIdentifiedDevice(config);
11991
12112
  const splitPosition = audienceSplitPosition(deviceId);
11992
12113
  if (initialConfig.campaign_rules) {
11993
- storageService.setCampaignRules(INITIAL_CAMPAIGN_RULES, mapAnonymousCampaigns(initialConfig.campaign_rules, splitPosition, currentFormFactor));
12114
+ const mappedCampaigns = mapAnonymousCampaigns(initialConfig.campaign_rules, splitPosition, currentFormFactor);
12115
+ storageService.setCampaignRules(INITIAL_CAMPAIGN_RULES, mappedCampaigns);
12116
+ startupTelemetry.mark("initial_processed", { count: mappedCampaigns.length });
11994
12117
  }
11995
12118
  if (deviceTask)
11996
12119
  await deviceTask;
@@ -12000,7 +12123,7 @@ class NamiRefs {
12000
12123
  ProductRepository.instance.fetchProducts(),
12001
12124
  CustomerJourneyRepository.instance.fetchCustomerJourneyState(),
12002
12125
  EntitlementRepository.instance.fetchActiveEntitlements(),
12003
- this.fetchCampaignsAndPaywalls(),
12126
+ startupTelemetry.serverFetch(() => this.fetchCampaignsAndPaywalls()),
12004
12127
  ]).catch(async (error) => {
12005
12128
  if (error instanceof ConflictError) {
12006
12129
  storageService.resetDevice();
@@ -12228,10 +12351,12 @@ class Nami {
12228
12351
  * @returns {Promise<NamiConfigurationState>}
12229
12352
  */
12230
12353
  static async configure(options) {
12354
+ startupTelemetry.start();
12231
12355
  if (!options.appPlatformID) {
12232
12356
  throw new PlatformIDRequiredError();
12233
12357
  }
12234
12358
  this.setInitialConfig(options);
12359
+ startupTelemetry.mark("initial_config_decoded");
12235
12360
  return await Nami.instance.initializeSDK(options);
12236
12361
  }
12237
12362
  static setInitialConfig(config) {
@@ -12310,6 +12435,7 @@ class Nami {
12310
12435
  logger.info("SDK successfully initialized!");
12311
12436
  const state = partialReconfig || fullReconfig ? RECONFIG_SUCCESS : INITIAL_SUCCESS;
12312
12437
  getPlatformAdapters().ui.postConfigure?.();
12438
+ startupTelemetry.mark("ready");
12313
12439
  return {
12314
12440
  sdkInitialized: true,
12315
12441
  configureState: state,
@@ -12410,6 +12536,7 @@ var NamiFlowActionFunction;
12410
12536
  NamiFlowActionFunction["SET_TAGS"] = "setTags";
12411
12537
  NamiFlowActionFunction["PAUSE"] = "flowPause";
12412
12538
  NamiFlowActionFunction["RESUME"] = "flowResume";
12539
+ NamiFlowActionFunction["SET_LAUNCH_CONTEXT"] = "setLaunchContext";
12413
12540
  })(NamiFlowActionFunction || (NamiFlowActionFunction = {}));
12414
12541
  const HandoffTag = {
12415
12542
  SEQUENCE: '__handoff_sequence__',
@@ -13948,6 +14075,16 @@ class NamiFlow extends BasicNamiFlow {
13948
14075
  this.forward(entry.id);
13949
14076
  }
13950
14077
  }
14078
+ applyLaunchContextAttributes(attrs) {
14079
+ if (!this.context) {
14080
+ this.context = { customAttributes: {} };
14081
+ new LaunchContextResolver(this.context);
14082
+ }
14083
+ // Guard against a runtime-supplied context that omits customAttributes
14084
+ // (the field is required by the type but may be absent in untyped JSON).
14085
+ this.context.customAttributes ??= {};
14086
+ Object.assign(this.context.customAttributes, attrs);
14087
+ }
13951
14088
  registerResolvers(context) {
13952
14089
  if (context) {
13953
14090
  new LaunchContextResolver(context);
@@ -14204,6 +14341,9 @@ class NamiFlow extends BasicNamiFlow {
14204
14341
  this.back();
14205
14342
  break;
14206
14343
  case NamiFlowActionFunction.NEXT:
14344
+ if (action.parameters?.customAttributes) {
14345
+ this.applyLaunchContextAttributes(action.parameters.customAttributes);
14346
+ }
14207
14347
  if (action.parameters?.step) {
14208
14348
  this.forward(action.parameters.step);
14209
14349
  }
@@ -14212,6 +14352,9 @@ class NamiFlow extends BasicNamiFlow {
14212
14352
  }
14213
14353
  break;
14214
14354
  case NamiFlowActionFunction.NAVIGATE:
14355
+ if (action.parameters?.customAttributes) {
14356
+ this.applyLaunchContextAttributes(action.parameters.customAttributes);
14357
+ }
14215
14358
  if (action.parameters?.step) {
14216
14359
  if (this.previousFlowStep?.id === action.parameters.step) {
14217
14360
  this.back();
@@ -14337,6 +14480,20 @@ class NamiFlow extends BasicNamiFlow {
14337
14480
  });
14338
14481
  }
14339
14482
  break;
14483
+ case NamiFlowActionFunction.SET_LAUNCH_CONTEXT: {
14484
+ // Two supported shapes (matches the nav-action customAttributes wire shape
14485
+ // and the { key, value } shape used by Apple/Android/Roku). We deliberately
14486
+ // do NOT treat arbitrary top-level parameters as attributes, to avoid writing
14487
+ // reserved keys (step, delay, …) into the launch context.
14488
+ const params = action.parameters;
14489
+ if (params?.customAttributes) {
14490
+ this.applyLaunchContextAttributes(params.customAttributes);
14491
+ }
14492
+ else if (params?.key !== undefined && params?.value !== undefined) {
14493
+ this.applyLaunchContextAttributes({ [params.key]: params.value });
14494
+ }
14495
+ break;
14496
+ }
14340
14497
  default:
14341
14498
  logger.warn(`Missing action handler for ${action.function}`, action);
14342
14499
  break;
@@ -14564,7 +14721,7 @@ class NamiCampaignManager {
14564
14721
  resultCallback(false, LaunchCampaignError.SDK_NOT_INITIALIZED);
14565
14722
  throw new SDKNotInitializedError();
14566
14723
  }
14567
- const data = getPaywallDataFromLabel(value, type);
14724
+ const data = startupTelemetry.firstLaunchLookup(value, () => getPaywallDataFromLabel(value, type));
14568
14725
  let paywall = data.paywall;
14569
14726
  const campaign = data.campaign;
14570
14727
  if (!campaign || (!paywall && !campaign.flow)) {
@@ -64537,4 +64694,4 @@ function namiBuySKU(skuRefId) {
64537
64694
  return result;
64538
64695
  }
64539
64696
 
64540
- 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, NamiCampaignRuleType, NamiConditionEvaluator, NamiCustomerManager, NamiEntitlementManager, NamiEventEmitter, NamiFlow, NamiFlowActionFunction, NamiFlowManager, NamiFlowStepType, NamiPaywallAction, NamiPaywallManager, PaywallManagerEvents as NamiPaywallManagerEvents, NamiProfileManager, 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, 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, activateEntitlementByPurchase, activeEntitlements, aggregateScreenreaderText, allCampaigns, allPaywalls, applyEntitlementActivation, audienceSplitPosition, bestUrlCampaignMatch, bigintToUuid, checkAnySkuHasPromoOffer, checkAnySkuHasTrialOffer, coerceBooleanish, 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 };
64697
+ 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, NamiCampaignRuleType, NamiConditionEvaluator, NamiCustomerManager, NamiEntitlementManager, NamiEventEmitter, NamiFlow, NamiFlowActionFunction, NamiFlowManager, NamiFlowStepType, NamiPaywallAction, NamiPaywallManager, PaywallManagerEvents as NamiPaywallManagerEvents, NamiProfileManager, 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, activateEntitlementByPurchase, activeEntitlements, aggregateScreenreaderText, allCampaigns, allPaywalls, applyEntitlementActivation, audienceSplitPosition, bestUrlCampaignMatch, bigintToUuid, checkAnySkuHasPromoOffer, checkAnySkuHasTrialOffer, coerceBooleanish, 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 };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@namiml/sdk-core",
3
- "version": "3.4.1-dev.202605270014",
3
+ "version": "3.4.1-dev.202605280043",
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",