@namiml/sdk-core 3.5.0-dev.202611011549 → 3.5.0-dev.202611011853

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
@@ -106,13 +106,15 @@ const {
106
106
  // version — stamped by scripts/version.sh
107
107
  NAMI_SDK_VERSION: exports.NAMI_SDK_VERSION = "3.5.0",
108
108
  // full package version including dev suffix — stamped by scripts/version.sh
109
- NAMI_SDK_PACKAGE_VERSION: exports.NAMI_SDK_PACKAGE_VERSION = "3.5.0-dev.202611011549",
109
+ NAMI_SDK_PACKAGE_VERSION: exports.NAMI_SDK_PACKAGE_VERSION = "3.5.0-dev.202611011853",
110
110
  // environments
111
111
  PRODUCTION: exports.PRODUCTION = "production", DEVELOPMENT: exports.DEVELOPMENT = "development",
112
112
  // error messages
113
113
  PLATFORM_ID_REQUIRED: exports.PLATFORM_ID_REQUIRED = "Platform ID is required", DEVICE_ID_REQUIRED: exports.DEVICE_ID_REQUIRED = "Device ID is required", EXTERNAL_ID_REQUIRED: exports.EXTERNAL_ID_REQUIRED = "External ID is required", SDK_NOT_INITIALIZED: exports.SDK_NOT_INITIALIZED = "SDK has not been initialized. Make sure to call Nami.configure() first!", CAMPAIGN_NOT_AVAILABLE: exports.CAMPAIGN_NOT_AVAILABLE = "Campaign is not available", FLOW_SCREENS_NOT_AVAILABLE: exports.FLOW_SCREENS_NOT_AVAILABLE = "Flow screens not available", UNABLE_TO_UPDATE_CDP_ID: exports.UNABLE_TO_UPDATE_CDP_ID = "Unable to update CDP id in anonymous mode", ANONYMOUS_MODE_ALREADY_ON: exports.ANONYMOUS_MODE_ALREADY_ON = "Anonymous mode is already turned on.", ANONYMOUS_MODE_ALREADY_OFF: exports.ANONYMOUS_MODE_ALREADY_OFF = "Anonymous mode is already turned off.", ANONYMOUS_MODE_LOGIN_NOT_ALLOWED: exports.ANONYMOUS_MODE_LOGIN_NOT_ALLOWED = "Login/logout are not available in anonymous mode", SESSION_REQUIRED: exports.SESSION_REQUIRED = "Session is required", DEVICE_ID_NOT_SET: exports.DEVICE_ID_NOT_SET = "Nami Device ID is not set",
114
114
  // localstorage keys
115
- AUTH_DEVICE: exports.AUTH_DEVICE = "nami_auth_device", NAMI_CONFIGURATION: exports.NAMI_CONFIGURATION = "nami_configuration", NAMI_PROFILE: exports.NAMI_PROFILE = "nami_profile", API_CONFIG: exports.API_CONFIG = "api_config", API_CAMPAIGN_RULES: exports.API_CAMPAIGN_RULES = "api_campaign_rules", API_PAYWALLS: exports.API_PAYWALLS = "api_paywalls", API_CAMPAIGN_SESSION_TIMESTAMP: exports.API_CAMPAIGN_SESSION_TIMESTAMP = "api_campaign_session_timestamp", API_PRODUCTS: exports.API_PRODUCTS = "api_products", API_ACTIVE_ENTITLEMENTS: exports.API_ACTIVE_ENTITLEMENTS = "api_active_entitlements", SERVER_NAMI_ENTITLEMENTS: exports.SERVER_NAMI_ENTITLEMENTS = "server_active_entitlements", INITIAL_APP_CONFIG: exports.INITIAL_APP_CONFIG = "initial_app_config", INITIAL_CAMPAIGN_RULES: exports.INITIAL_CAMPAIGN_RULES = "initial_campaign_rules", INITIAL_PAYWALLS: exports.INITIAL_PAYWALLS = "initial_paywalls", INITIAL_PRODUCTS: exports.INITIAL_PRODUCTS = "initial_products", LOCAL_NAMI_ENTITLEMENTS: exports.LOCAL_NAMI_ENTITLEMENTS = "local_active_entitlements", CUSTOMER_ATTRIBUTES_KEY_PREFIX: exports.CUSTOMER_ATTRIBUTES_KEY_PREFIX = "nami_cmr_attributes_", NAMI_CUSTOMER_JOURNEY_STATE: exports.NAMI_CUSTOMER_JOURNEY_STATE = "nami_customer_journey_state", ANONYMOUS_MODE: exports.ANONYMOUS_MODE = "nami_anonymous_mode_v2", ANONYMOUS_UUID: exports.ANONYMOUS_UUID = "nami_anonymous_uuid", KEY_SESSION_COUNTER: exports.KEY_SESSION_COUNTER = "nami_session_counter", INITIAL_SESSION_COUNTER_VALUE: exports.INITIAL_SESSION_COUNTER_VALUE = 1, NAMI_LAST_IMPRESSION_ID: exports.NAMI_LAST_IMPRESSION_ID = "nami_last_impression_id", NAMI_PURCHASE_IMPRESSION_ID: exports.NAMI_PURCHASE_IMPRESSION_ID = "nami_purchase_impression_id", NAMI_LAUNCH_ID: exports.NAMI_LAUNCH_ID = "nami_launch_id", NAMI_SESSION_ID: exports.NAMI_SESSION_ID = "nami_session_id", NAMI_LANGUAGE_CODE: exports.NAMI_LANGUAGE_CODE = "nami_language_code", NAMI_PURCHASE_CHANNEL: exports.NAMI_PURCHASE_CHANNEL = "nami_purchase_channel",
115
+ AUTH_DEVICE: exports.AUTH_DEVICE = "nami_auth_device", NAMI_CONFIGURATION: exports.NAMI_CONFIGURATION = "nami_configuration", NAMI_PROFILE: exports.NAMI_PROFILE = "nami_profile", API_CONFIG: exports.API_CONFIG = "api_config", API_CAMPAIGN_RULES: exports.API_CAMPAIGN_RULES = "api_campaign_rules", API_PAYWALLS: exports.API_PAYWALLS = "api_paywalls", API_CAMPAIGN_SESSION_TIMESTAMP: exports.API_CAMPAIGN_SESSION_TIMESTAMP = "api_campaign_session_timestamp", API_PRODUCTS: exports.API_PRODUCTS = "api_products", API_ACTIVE_ENTITLEMENTS: exports.API_ACTIVE_ENTITLEMENTS = "api_active_entitlements", SERVER_NAMI_ENTITLEMENTS: exports.SERVER_NAMI_ENTITLEMENTS = "server_active_entitlements", INITIAL_APP_CONFIG: exports.INITIAL_APP_CONFIG = "initial_app_config", INITIAL_CAMPAIGN_RULES: exports.INITIAL_CAMPAIGN_RULES = "initial_campaign_rules", INITIAL_PAYWALLS: exports.INITIAL_PAYWALLS = "initial_paywalls", INITIAL_PRODUCTS: exports.INITIAL_PRODUCTS = "initial_products", LOCAL_NAMI_ENTITLEMENTS: exports.LOCAL_NAMI_ENTITLEMENTS = "local_active_entitlements", CUSTOMER_ATTRIBUTES_KEY_PREFIX: exports.CUSTOMER_ATTRIBUTES_KEY_PREFIX = "nami_cmr_attributes_", NAMI_CUSTOMER_JOURNEY_STATE: exports.NAMI_CUSTOMER_JOURNEY_STATE = "nami_customer_journey_state",
116
+ // Journey delivery counters (NAM-3229), unrelated to the customer journey state above.
117
+ NAMI_JOURNEY_RULE_STATE: exports.NAMI_JOURNEY_RULE_STATE = "nami_journey_rule_state", ANONYMOUS_MODE: exports.ANONYMOUS_MODE = "nami_anonymous_mode_v2", ANONYMOUS_UUID: exports.ANONYMOUS_UUID = "nami_anonymous_uuid", KEY_SESSION_COUNTER: exports.KEY_SESSION_COUNTER = "nami_session_counter", INITIAL_SESSION_COUNTER_VALUE: exports.INITIAL_SESSION_COUNTER_VALUE = 1, NAMI_LAST_IMPRESSION_ID: exports.NAMI_LAST_IMPRESSION_ID = "nami_last_impression_id", NAMI_PURCHASE_IMPRESSION_ID: exports.NAMI_PURCHASE_IMPRESSION_ID = "nami_purchase_impression_id", NAMI_LAUNCH_ID: exports.NAMI_LAUNCH_ID = "nami_launch_id", NAMI_SESSION_ID: exports.NAMI_SESSION_ID = "nami_session_id", NAMI_LANGUAGE_CODE: exports.NAMI_LANGUAGE_CODE = "nami_language_code", NAMI_PURCHASE_CHANNEL: exports.NAMI_PURCHASE_CHANNEL = "nami_purchase_channel",
116
118
  // API settings
117
119
  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",
118
120
  // // extended client info
@@ -162,7 +164,7 @@ const NAMI_STORAGE_KEYS = [
162
164
  exports.API_PRODUCTS, exports.API_ACTIVE_ENTITLEMENTS, exports.SERVER_NAMI_ENTITLEMENTS,
163
165
  exports.INITIAL_APP_CONFIG, exports.INITIAL_CAMPAIGN_RULES, exports.INITIAL_PAYWALLS, exports.INITIAL_PRODUCTS,
164
166
  exports.LOCAL_NAMI_ENTITLEMENTS,
165
- exports.NAMI_CUSTOMER_JOURNEY_STATE,
167
+ exports.NAMI_CUSTOMER_JOURNEY_STATE, exports.NAMI_JOURNEY_RULE_STATE,
166
168
  exports.ANONYMOUS_MODE, exports.ANONYMOUS_UUID,
167
169
  exports.KEY_SESSION_COUNTER, exports.NAMI_LAST_IMPRESSION_ID, exports.NAMI_PURCHASE_IMPRESSION_ID,
168
170
  exports.NAMI_LAUNCH_ID, exports.NAMI_SESSION_ID, exports.NAMI_LANGUAGE_CODE, exports.NAMI_PURCHASE_CHANNEL,
@@ -3525,6 +3527,16 @@ class StorageService {
3525
3527
  getCustomerJourneyState() {
3526
3528
  return this.getItem(exports.NAMI_CUSTOMER_JOURNEY_STATE);
3527
3529
  }
3530
+ /** The raw journey delivery blob; `journeyStateStore` validates its shape. */
3531
+ getJourneyRuleState() {
3532
+ return this.getItem(exports.NAMI_JOURNEY_RULE_STATE);
3533
+ }
3534
+ setJourneyRuleState(state) {
3535
+ this.setItem(exports.NAMI_JOURNEY_RULE_STATE, state);
3536
+ }
3537
+ removeJourneyRuleState() {
3538
+ this.resetItem(exports.NAMI_JOURNEY_RULE_STATE);
3539
+ }
3528
3540
  setSessionCounter() {
3529
3541
  const counter = this.getItem(exports.KEY_SESSION_COUNTER);
3530
3542
  const currentCounter = counter ? parseInt(counter) : exports.INITIAL_SESSION_COUNTER_VALUE
@@ -7814,6 +7826,7 @@ function isNamiFlowCampaign(campaign) {
7814
7826
  exports.NamiCampaignRuleType = void 0;
7815
7827
  (function (NamiCampaignRuleType) {
7816
7828
  NamiCampaignRuleType["DEFAULT"] = "default";
7829
+ NamiCampaignRuleType["JOURNEY"] = "journey";
7817
7830
  NamiCampaignRuleType["LABEL"] = "label";
7818
7831
  NamiCampaignRuleType["UNKNOWN"] = "unknown";
7819
7832
  NamiCampaignRuleType["URL"] = "url";
@@ -57037,6 +57050,69 @@ const isCampaignRuleActive = (notBefore, expiresAt, now = new Date()) => {
57037
57050
  return startOk && endOk;
57038
57051
  };
57039
57052
 
57053
+ const TRIGGER_EVENT_TYPES = ["app_install", "session_start"];
57054
+ const GOAL_TYPES = ["flow_completed", "user_context_set"];
57055
+ const PRIORITIES = ["high", "medium", "low"];
57056
+ const oneOf = (allowed, value) => allowed.includes(value) ? value : null;
57057
+ const numberOrNull = (value) => (typeof value === "number" ? value : null);
57058
+ /**
57059
+ * Resolves a rule's `journey` object, reading `journey_id` from the top level and the other
57060
+ * fields from the `object` envelope when present, else from the top level (the flat contract
57061
+ * shape). Never throws: unknown values become `null` or their spec default.
57062
+ */
57063
+ function parseJourneyMetadata(dto) {
57064
+ if (!dto)
57065
+ return null;
57066
+ const fields = "object" in dto ? dto.object ?? {} : dto;
57067
+ const { goal, delivery } = fields;
57068
+ return {
57069
+ journey_id: typeof dto.journey_id === "string" ? dto.journey_id : "",
57070
+ category: fields.category ?? null,
57071
+ priority: oneOf(PRIORITIES, fields.priority) ?? "medium",
57072
+ trigger: { event_type: oneOf(TRIGGER_EVENT_TYPES, fields.trigger?.event_type) },
57073
+ goal: goal
57074
+ ? {
57075
+ type: oneOf(GOAL_TYPES, goal.type),
57076
+ context_key: goal.context_key ?? null,
57077
+ timeout_days: numberOrNull(goal.timeout_days),
57078
+ re_eligibility_days: numberOrNull(goal.re_eligibility_days),
57079
+ re_entry_after_goal: goal.re_entry_after_goal === true,
57080
+ }
57081
+ : null,
57082
+ delivery: {
57083
+ show_once: delivery?.show_once === true,
57084
+ max_impressions_total: numberOrNull(delivery?.max_impressions_total),
57085
+ max_per_session: numberOrNull(delivery?.max_per_session),
57086
+ max_per_day: numberOrNull(delivery?.max_per_day),
57087
+ delay_seconds: numberOrNull(delivery?.delay_seconds) ?? 0,
57088
+ },
57089
+ };
57090
+ }
57091
+ /**
57092
+ * Whether this SDK can act on the journey: the `journey_id` is non-blank, the trigger and goal
57093
+ * types are known, and a `user_context_set` goal names the key it watches.
57094
+ */
57095
+ function isSupportedJourney(meta) {
57096
+ if (!meta || !meta.journey_id.trim())
57097
+ return false;
57098
+ if (!meta.trigger.event_type || !meta.goal?.type)
57099
+ return false;
57100
+ if (meta.goal.type === "user_context_set")
57101
+ return !!meta.goal.context_key;
57102
+ return true;
57103
+ }
57104
+ const isJourneyCampaign = (rule) => rule.type === exports.NamiCampaignRuleType.JOURNEY;
57105
+ /**
57106
+ * Whether this SDK can use the rule. Every placement rule qualifies. A journey also needs
57107
+ * supported metadata and a flow, because journeys are flow-only. Callers skip an unsupported
57108
+ * journey rather than failing the payload.
57109
+ */
57110
+ function isSupportedCampaignRule(rule) {
57111
+ if (!isJourneyCampaign(rule))
57112
+ return true;
57113
+ return rule.flow != null && isSupportedJourney(parseJourneyMetadata(rule.journey));
57114
+ }
57115
+
57040
57116
  const isValidUrl = (label) => {
57041
57117
  try {
57042
57118
  const url = new URL(label);
@@ -57150,7 +57226,7 @@ const mapAnonymousCampaigns = (campaigns, splitPosition, formFactor) => {
57150
57226
  if (segments.length === 0)
57151
57227
  return [];
57152
57228
  const segment = selectSegment(segments, splitPosition);
57153
- return {
57229
+ const mapped = {
57154
57230
  form_factors: segment.form_factors,
57155
57231
  paywall: segment.paywall,
57156
57232
  paywall_url: segment.paywall_url,
@@ -57165,12 +57241,47 @@ const mapAnonymousCampaigns = (campaigns, splitPosition, formFactor) => {
57165
57241
  type: campaign.type,
57166
57242
  value: campaign.value,
57167
57243
  external_segment: campaign.external_segment,
57244
+ // Rule-level, like Apple's decoder: the window applies whichever segment wins.
57245
+ not_before: campaign.not_before,
57246
+ expires_at: campaign.expires_at,
57247
+ journey: campaign.journey,
57168
57248
  };
57249
+ // The initial-config path stores this list directly, so it needs the same skip-not-fail
57250
+ // check the repository applies to fetched rules (NAM-3225).
57251
+ if (!isSupportedCampaignRule(mapped)) {
57252
+ logger.debug(`Skipping unsupported journey rule ${campaign.rule}`);
57253
+ return [];
57254
+ }
57255
+ return mapped;
57256
+ });
57257
+ };
57258
+ /** Stored rules that target this device's form factor and are inside their active window. */
57259
+ const liveForDevice = (campaigns) => {
57260
+ const formFactor = getDeviceFormFactor();
57261
+ return compact(campaigns)
57262
+ .filter((cRule) => cRule.form_factors?.some((f) => f.form_factor === formFactor))
57263
+ .filter((cRule) => isCampaignRuleActive(cRule.not_before, cRule.expires_at));
57264
+ };
57265
+ /** Keeps the first rule per key, so API entries (listed first) win and payload order is kept. */
57266
+ const dedupeBy = (campaigns, key) => {
57267
+ const seen = new Set();
57268
+ return campaigns.filter((c) => {
57269
+ const k = key(c);
57270
+ if (seen.has(k))
57271
+ return false;
57272
+ seen.add(k);
57273
+ return true;
57169
57274
  });
57170
57275
  };
57276
+ const storedApiAndInitialRules = () => [
57277
+ ...(storageService.getCampaignRules(exports.API_CAMPAIGN_RULES) ?? []),
57278
+ ...(storageService.getCampaignRules(exports.INITIAL_CAMPAIGN_RULES) ?? []),
57279
+ ];
57171
57280
  /**
57172
- * Returns the combined list of API + initial-config campaigns for the current device's
57173
- * form factor, deduplicated by (type, value) with API entries winning on collision.
57281
+ * Returns the combined list of API + initial-config placement campaigns for the current
57282
+ * device's form factor, deduplicated by (type, value) with API entries winning on collision.
57283
+ * Journey rules are never included: they are launched by trigger events, not by a placement
57284
+ * (see {@link journeyCampaigns}).
57174
57285
  *
57175
57286
  * Mirrors NamiCampaignManager.allCampaigns() on Apple (Set keyed on hash(value, type))
57176
57287
  * and Android (.distinct() with server-first ordering). Keying on (type, value) — rather
@@ -57184,35 +57295,22 @@ const mapAnonymousCampaigns = (campaigns, splitPosition, formFactor) => {
57184
57295
  * when the API pair is incomplete — matching the merged-paywall-lookup behavior on
57185
57296
  * Apple and Android.
57186
57297
  */
57187
- const allCampaigns = () => {
57188
- const apiCampaigns = storageService.getCampaignRules(exports.API_CAMPAIGN_RULES) ?? [];
57189
- const initialCampaigns = storageService.getCampaignRules(exports.INITIAL_CAMPAIGN_RULES) ?? [];
57190
- const formFactor = getDeviceFormFactor();
57191
- const campaigns = compact([...apiCampaigns, ...initialCampaigns])
57192
- .filter((cRule) => cRule.form_factors?.some((f) => f.form_factor === formFactor))
57193
- .filter((cRule) => isCampaignRuleActive(cRule.not_before, cRule.expires_at));
57194
- const seen = new Set();
57195
- return campaigns.filter((c) => {
57196
- const key = `${c.type ?? ''}::${c.value ?? ''}`;
57197
- if (seen.has(key))
57198
- return false;
57199
- seen.add(key);
57200
- return true;
57201
- });
57202
- };
57298
+ const allCampaigns = () => dedupeBy(liveForDevice(storedApiAndInitialRules()).filter((c) => !isJourneyCampaign(c)), (c) => `${c.type ?? ''}::${c.value ?? ''}`);
57203
57299
  /**
57204
- * Get campaigns by rule key, filtered by form factor.
57300
+ * API + initial-config journey rules for the current device's form factor and inside their
57301
+ * window, deduplicated on `rule` (every journey's `value` is null, so the placement key would
57302
+ * collapse them) with API entries first and payload order kept: the engine's tiebreaks depend
57303
+ * on it. This is the read surface for journey delivery; placement APIs never see these rules.
57304
+ */
57305
+ const journeyCampaigns = () => dedupeBy(liveForDevice(storedApiAndInitialRules()).filter(isJourneyCampaign), (c) => c.rule);
57306
+ /**
57307
+ * Get placement campaigns by rule key, filtered by form factor and active window. Journey
57308
+ * rules are excluded so label, URL and default matching never resolve to one.
57205
57309
  *
57206
57310
  * @param ruleKey The key of the campaign rule to filter by
57207
57311
  * @returns An array of campaigns that match the rule key and form factor
57208
57312
  */
57209
- const getCampaignsByRule = (ruleKey) => {
57210
- const campaigns = storageService.getCampaignRules(ruleKey) ?? [];
57211
- const formFactor = getDeviceFormFactor();
57212
- return compact(campaigns)
57213
- .filter((cRule) => cRule.form_factors?.some((f) => f.form_factor === formFactor))
57214
- .filter((cRule) => isCampaignRuleActive(cRule.not_before, cRule.expires_at));
57215
- };
57313
+ const getCampaignsByRule = (ruleKey) => liveForDevice(storageService.getCampaignRules(ruleKey) ?? []).filter((c) => !isJourneyCampaign(c));
57216
57314
  const getInitialCampaigns = () => getCampaignsByRule(exports.INITIAL_CAMPAIGN_RULES);
57217
57315
  const getApiCampaigns = () => getCampaignsByRule(exports.API_CAMPAIGN_RULES);
57218
57316
  /**
@@ -57761,6 +57859,22 @@ class CampaignRuleRepository {
57761
57859
  constructor() {
57762
57860
  this.disableCampaignUpdates = false;
57763
57861
  this.useLegacyPaywallFetch = false;
57862
+ /**
57863
+ * A rule this device can use: it has content (a paywall or a flow), targets the current form
57864
+ * factor, and, for a journey, carries supported metadata. An unsupported journey is skipped,
57865
+ * never the payload (NAM-3225).
57866
+ */
57867
+ this.isUsableOnDevice = (cRule) => {
57868
+ if (!cRule.paywall && !cRule.flow)
57869
+ return false;
57870
+ if (!cRule.form_factors?.some((f) => f.form_factor === this.currentFormFactor))
57871
+ return false;
57872
+ if (!isSupportedCampaignRule(cRule)) {
57873
+ logger.debug(`Skipping unsupported journey rule ${cRule.rule}`);
57874
+ return false;
57875
+ }
57876
+ return true;
57877
+ };
57764
57878
  this.currentFormFactor = getDeviceFormFactor();
57765
57879
  }
57766
57880
  configure(formFactor, splitPosition, namiCommands = []) {
@@ -57795,9 +57909,7 @@ class CampaignRuleRepository {
57795
57909
  else {
57796
57910
  campaignRules = await this.getCampaigns(authDevice.id);
57797
57911
  }
57798
- // get campaign rules according to device's form_factor
57799
- campaignRules = campaignRules?.filter((cRule) => (cRule.paywall && cRule.form_factors.some((f) => f.form_factor === this.currentFormFactor)) ||
57800
- (cRule.flow && cRule.form_factors?.some((f) => f.form_factor === this.currentFormFactor)));
57912
+ campaignRules = campaignRules?.filter(this.isUsableOnDevice);
57801
57913
  // Keep only those campaign rules whose corresponding paywall is available
57802
57914
  campaignRules = campaignRules?.filter((cRule) => {
57803
57915
  if (cRule.paywall) {
@@ -57847,8 +57959,7 @@ class CampaignRuleRepository {
57847
57959
  const fetched = authDevice?.id
57848
57960
  ? await this.fetchCampaigns(authDevice.id)
57849
57961
  : await this.fetchAnonymousCampaigns();
57850
- const campaigns = fetched.campaigns?.filter((cRule) => (cRule.paywall && cRule.form_factors.some((f) => f.form_factor === this.currentFormFactor)) ||
57851
- (cRule.flow && cRule.form_factors?.some((f) => f.form_factor === this.currentFormFactor)));
57962
+ const campaigns = fetched.campaigns?.filter(this.isUsableOnDevice);
57852
57963
  return { campaigns, fromNetwork: fetched.fromNetwork };
57853
57964
  }
57854
57965
  finalizeCampaignRules(campaignRules, paywalls) {
@@ -66739,6 +66850,7 @@ const NamiAnimationType = {
66739
66850
  var CampaignRuleType;
66740
66851
  (function (CampaignRuleType) {
66741
66852
  CampaignRuleType["DEFAULT"] = "default";
66853
+ CampaignRuleType["JOURNEY"] = "journey";
66742
66854
  CampaignRuleType["LABEL"] = "label";
66743
66855
  CampaignRuleType["URL"] = "url";
66744
66856
  })(CampaignRuleType || (CampaignRuleType = {}));
@@ -67349,6 +67461,7 @@ exports.isSubscription = isSubscription;
67349
67461
  exports.isSystemTimeAssertion = isSystemTimeAssertion;
67350
67462
  exports.isValidISODate = isValidISODate;
67351
67463
  exports.isValidUrl = isValidUrl;
67464
+ exports.journeyCampaigns = journeyCampaigns;
67352
67465
  exports.logger = logger;
67353
67466
  exports.mapAnonymousCampaigns = mapAnonymousCampaigns;
67354
67467
  exports.namiBuySKU = namiBuySKU;
package/dist/index.d.ts CHANGED
@@ -1065,6 +1065,47 @@ interface NamiCampaignSegment {
1065
1065
  conversion_event_type?: CampaignRuleConversionEventType | null;
1066
1066
  conversion_event_url?: string | null;
1067
1067
  }
1068
+ /**
1069
+ * @type NamiJourneyFields
1070
+ * The per-journey fields of a `journey` object. Values stay loose (`string`) on the wire so an
1071
+ * unknown trigger, goal or priority from a newer backend parses; `parseJourneyMetadata` narrows
1072
+ * them and drops what this SDK doesn't support.
1073
+ */
1074
+ interface NamiJourneyFields {
1075
+ status?: string | null;
1076
+ category?: string | null;
1077
+ priority?: string | null;
1078
+ trigger?: {
1079
+ event_type?: string | null;
1080
+ parameters?: Record<string, unknown> | null;
1081
+ } | null;
1082
+ goal?: {
1083
+ type?: string | null;
1084
+ context_key?: string | null;
1085
+ timeout_days?: number | null;
1086
+ re_eligibility_days?: number | null;
1087
+ re_entry_after_goal?: boolean | null;
1088
+ } | null;
1089
+ delivery?: {
1090
+ show_once?: boolean | null;
1091
+ max_impressions_total?: number | null;
1092
+ max_per_session?: number | null;
1093
+ max_per_day?: number | null;
1094
+ delay_seconds?: number | null;
1095
+ } | null;
1096
+ }
1097
+ /**
1098
+ * @type NamiJourneyDTO
1099
+ * The `journey` object on a `type: "journey"` campaign rule. It is rule-level, so it applies to
1100
+ * whichever A/B segment wins. The backend nests every field but `journey_id` and `object_id` in
1101
+ * an `object` envelope, the way `flow` is delivered; the flat contract shape (fields at the top
1102
+ * level) is accepted too.
1103
+ */
1104
+ interface NamiJourneyDTO extends NamiJourneyFields {
1105
+ journey_id: string;
1106
+ object_id?: string | null;
1107
+ object?: NamiJourneyFields | null;
1108
+ }
1068
1109
  /**
1069
1110
  * @type NamiAnonymousCampaign
1070
1111
  * Represents the anonymous campaign in Nami.
@@ -1078,6 +1119,7 @@ interface NamiAnonymousCampaign {
1078
1119
  segments: NamiCampaignSegment[];
1079
1120
  not_before?: string | null;
1080
1121
  expires_at?: string | null;
1122
+ journey?: NamiJourneyDTO | null;
1081
1123
  }
1082
1124
  /**
1083
1125
  * @type NamiCampaign
@@ -1100,6 +1142,8 @@ interface NamiCampaign {
1100
1142
  flow?: NamiFlowDTO | null;
1101
1143
  not_before?: string | null;
1102
1144
  expires_at?: string | null;
1145
+ /** Present on `type: "journey"` rules. Journeys are launched by trigger events, never by a placement. */
1146
+ journey?: NamiJourneyDTO | null;
1103
1147
  }
1104
1148
  interface NamiFlowCampaign extends NamiCampaign {
1105
1149
  flow: NamiFlowWithObject;
@@ -1111,6 +1155,7 @@ declare function isNamiFlowCampaign(campaign?: NamiCampaign): campaign is NamiFl
1111
1155
  */
1112
1156
  declare enum NamiCampaignRuleType {
1113
1157
  DEFAULT = "default",
1158
+ JOURNEY = "journey",
1114
1159
  LABEL = "label",
1115
1160
  UNKNOWN = "unknown",
1116
1161
  URL = "url"
@@ -3047,6 +3092,10 @@ declare class StorageService {
3047
3092
  removeNamiProfile(): void;
3048
3093
  setCustomerJourneyState(state: CustomerJourneyState): void;
3049
3094
  getCustomerJourneyState(): CustomerJourneyState | null;
3095
+ /** The raw journey delivery blob; `journeyStateStore` validates its shape. */
3096
+ getJourneyRuleState(): unknown;
3097
+ setJourneyRuleState(state: unknown): void;
3098
+ removeJourneyRuleState(): void;
3050
3099
  setSessionCounter(): void;
3051
3100
  getSessionCounter(): number;
3052
3101
  setLastImpressionId(id: string): void;
@@ -3268,6 +3317,7 @@ declare const INITIAL_PRODUCTS: string;
3268
3317
  declare const LOCAL_NAMI_ENTITLEMENTS: string;
3269
3318
  declare const CUSTOMER_ATTRIBUTES_KEY_PREFIX: string;
3270
3319
  declare const NAMI_CUSTOMER_JOURNEY_STATE: string;
3320
+ declare const NAMI_JOURNEY_RULE_STATE: string;
3271
3321
  declare const ANONYMOUS_MODE: string;
3272
3322
  declare const ANONYMOUS_UUID: string;
3273
3323
  declare const KEY_SESSION_COUNTER: string;
@@ -3658,8 +3708,10 @@ declare const bestUrlCampaignMatch: (incomingUrl: string, campaigns: NamiCampaig
3658
3708
  declare const selectSegment: (segments: NamiCampaignSegment[], splitPosition: number) => NamiCampaignSegment;
3659
3709
  declare const mapAnonymousCampaigns: (campaigns: NamiAnonymousCampaign[], splitPosition: number, formFactor?: TDevice) => NamiCampaign[];
3660
3710
  /**
3661
- * Returns the combined list of API + initial-config campaigns for the current device's
3662
- * form factor, deduplicated by (type, value) with API entries winning on collision.
3711
+ * Returns the combined list of API + initial-config placement campaigns for the current
3712
+ * device's form factor, deduplicated by (type, value) with API entries winning on collision.
3713
+ * Journey rules are never included: they are launched by trigger events, not by a placement
3714
+ * (see {@link journeyCampaigns}).
3663
3715
  *
3664
3716
  * Mirrors NamiCampaignManager.allCampaigns() on Apple (Set keyed on hash(value, type))
3665
3717
  * and Android (.distinct() with server-first ordering). Keying on (type, value) — rather
@@ -3674,6 +3726,13 @@ declare const mapAnonymousCampaigns: (campaigns: NamiAnonymousCampaign[], splitP
3674
3726
  * Apple and Android.
3675
3727
  */
3676
3728
  declare const allCampaigns: () => NamiCampaign[];
3729
+ /**
3730
+ * API + initial-config journey rules for the current device's form factor and inside their
3731
+ * window, deduplicated on `rule` (every journey's `value` is null, so the placement key would
3732
+ * collapse them) with API entries first and payload order kept: the engine's tiebreaks depend
3733
+ * on it. This is the read surface for journey delivery; placement APIs never see these rules.
3734
+ */
3735
+ declare const journeyCampaigns: () => NamiCampaign[];
3677
3736
  declare const getInitialCampaigns: () => NamiCampaign[];
3678
3737
  declare const getApiCampaigns: () => NamiCampaign[];
3679
3738
  /**
@@ -3838,6 +3897,12 @@ declare class CampaignRuleRepository {
3838
3897
  */
3839
3898
  reset(): void;
3840
3899
  fetchCampaignRules(paywalls: IPaywall[]): Promise<NamiCampaign[]>;
3900
+ /**
3901
+ * A rule this device can use: it has content (a paywall or a flow), targets the current form
3902
+ * factor, and, for a journey, carries supported metadata. An unsupported journey is skipped,
3903
+ * never the payload (NAM-3225).
3904
+ */
3905
+ private isUsableOnDevice;
3841
3906
  static extractPaywallUrls(campaigns: NamiCampaign[]): string[];
3842
3907
  static hasPaywallUrls(campaigns: NamiCampaign[]): boolean;
3843
3908
  /**
@@ -4542,5 +4607,5 @@ declare namespace internal {
4542
4607
  };
4543
4608
  }
4544
4609
 
4545
- 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_TRAFFIC_OBSERVER, 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_CACHE_TTL_PREFIX, CAMPAIGN_NOT_AVAILABLE, CONFIG_API_TIMEOUT_LIMIT, CONFIG_MAX_ATTEMPTS, CUSTOMER_ATTRIBUTES_KEY_PREFIX, CUSTOMER_JOURNEY_STATE_CHANGED, CUSTOM_HOST_PREFIX, CampaignNotAvailableError, CampaignRuleConversionEventType, CampaignRuleRepository, Capabilities, ClientError, ConfigRepository, ConflictError, CustomerJourneyRepository, DEFAULT_JOURNEY_SETTINGS, 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, MAX_FIXED_STEP_COUNT, MIN_FIXED_STEP_COUNT, 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, NamiHandoffTag, NamiPaywallAction, NamiPaywallManager$2 as NamiPaywallManager, PaywallManagerEvents as NamiPaywallManagerEvents, NamiPermissionOutcome, NamiPurchaseManager$2 as NamiPurchaseManager, NamiRefs, NamiReservedActions, NotFoundError, PAYWALL_ACTION_EVENT, PLATFORM_ID_REQUIRED, PRODUCTION, PaywallManagerEvents, PaywallRepository, PaywallState, PlacementLabelResolver, PlatformIDRequiredError, ProductRepository, RECONFIG_SUCCESS, RESUME_THROTTLE_MS, RetryLimitExceededError, SDKNotInitializedError, SDK_NOT_INITIALIZED, SERVER_NAMI_ENTITLEMENTS, SESSION_REQUIRED, SHOULD_SHOW_LOADING_INDICATOR, SIMULATE_CONFIG_FETCH_FAILURE, SIMULATE_PAYWALL_FETCH_FAILURE, SKU_TEXT_REGEX, SMART_TEXT_PATTERN, STARTUP_TELEMETRY, STATUS_BAD_REQUEST, STATUS_CONFLICT, STATUS_INTERNAL_SERVER_ERROR, STATUS_NOT_FOUND, STATUS_SUCCESS, SYSTEM_TIME_FILTER_PREFIX, SYSTEM_TIME_TOKEN_PREFIX, SessionService, SimpleEventTarget, StorageService, TIME_OVERRIDE_PREFIX, TRANSPORT_FAILURE_STATUS, 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, didReceiveResponse, empty, evaluateSystemTimeAssertion, evaluateSystemTimeCondition, evenSegmentWidth, extractStandardPricingPhases, firstSet, formatDate, formattedPrice, generateUUID, getApiCampaigns, getApiPaywalls, getBaseUrl, getBillingPeriodNumber, getCurrencyFormat, getDeviceData, getDeviceFormFactor, getDeviceScaleFactor, getEffectiveWebStyle, getEntitlementRefIdsForSku, getExtendedClientInfo, getFreeTrialPeriod, getInitialCampaigns, getInitialPaywalls, getJourneySettings, getPaywall, getPaywallDataFromLabel, getPercentagePriceDifference, getPeriodNumberInDays, getPeriodNumberInWeeks, getPlatformAdapters, getPriceDifference, getPricePerMonth, getProductDetail, getPurchaseAdapter, getReferenceSku, getSkuProductDetailKeys, getSkuSmartTextValue, getSlideSmartTextValue, getStandardBillingPeriod, getSystemTimeNow, getTranslate, getUrlParams, handleErrors, hasAllPaywalls, hasCapability, hasPurchaseManagement, initialState, invokeHandler, isAnonymousMode, isInitialConfigCompressed, isNamiFlowCampaign, isStepsStyle, isSubscription, isSystemTimeAssertion, isValidISODate, isValidUrl, logger, mapAnonymousCampaigns, namiBuySKU, normalizeLaunchContext, parseTimeOverride, parseToSemver, postConversion, productDetail, registerAPITrafficObserver, registerPlatformAdapters, registerPurchaseAdapter, resolveCurrentStep, resolveLoopSource, resolveStepAlignment, resolveStepCount, resolveStepSizing, selectSegment, setActiveNamiEntitlements, shouldValidateProductGroups, skuItems, skuMapFromEntitlements, stepSegmentState, storageService, toDouble, toNamiEntitlements, toNamiSKU, tryParseB64Gzip, tryParseJson, updateRelatedSKUsForNamiEntitlement, uuidFromSplitPosition, validateForm, validateMinSDKVersion, validateTextInput };
4546
- export type { AccountStateHandler$1 as AccountStateHandler, AlignmentType, AmazonProduct, ApiResponse, AppleProduct, AvailableCampaignsResponseHandler, BorderLocationType, BorderSideType, Callback$1 as Callback, CloseHandler, CustomerJourneyState, DeepLinkUrlHandler, Device, DevicePayload, DeviceProfile, DirectionType, ExtendedPlatformInfo, FlexDirectionObject, FlowNavigationOptions, FontCollection, FontDetails, FormFactor, FormFieldValidator, GoogleProduct, IConfig, IDeviceAdapter, IEntitlements$1 as IEntitlements, IPaywall, IPlatformAdapters, IProductsWithComponents, IPurchaseAdapter, ISkuMenu, IStorageAdapter, IUIAdapter, Impression, InitialConfig, InitialConfigCompressed, InitiateStateGroup, JourneySettings, LoginResponse, NamiAPITrafficKind, NamiAPITrafficObserver, NamiAPITrafficRecord, NamiAnimation, NamiAnimationObjectSpec, NamiAnimationSpec, NamiAnonymousCampaign, NamiAppSuppliedVideoDetails, NamiCampaign, NamiCampaignManagerStatic, NamiCampaignSegment, NamiCompletionResult, NamiConfiguration, NamiConfigurationState, NamiCustomAttributeValue, NamiCustomerManagerStatic, NamiEntitlement$1 as NamiEntitlement, NamiEntitlementManagerStatic, NamiFlowAction, NamiFlowAnimation, NamiFlowCampaign, NamiFlowCompletionCondition, NamiFlowDTO, NamiFlowEventHandler, NamiFlowHandoffStepHandler, NamiFlowManagerStatic, NamiFlowObjectDTO, NamiFlowOn, NamiFlowStep, NamiFlowTransition, NamiFlowTransitionDirection, NamiFlowWithObject, NamiHandoffAttributeValue, NamiHandoffComplete, NamiHandoffOutcome, NamiHandoffTagValue, NamiInitialConfig, NamiKnownHandoffTag, NamiLanguageCodes, NamiLogLevel, NamiLoginSuccess, NamiPaywallActionHandler, NamiPaywallComponentChange, NamiPaywallEvent, NamiPaywallEventVideoMetadata, NamiPaywallLaunchContext, NamiPaywallManagerStatic, NamiPresentationStyle, NamiProductDetails, NamiProductOffer, NamiProfile, NamiPurchase, NamiPurchaseCompleteResult, NamiPurchaseDetails, NamiPurchaseManagerStatic, NamiPurchaseSuccess, NamiPurchasesState, NamiSKU, NamiSKUType, NamiSubscriptionInterval, NamiSubscriptionPeriod, NamiUserDataEnvelope, 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, TSlot, TSlotGroupSelector, TSpacerComponent, TStack, TStepState, TSvgImageComponent, TSymbolComponent, TTestObject, TTextComponent, TTextInputComponent, TTextInputType, TTextLikeComponent, TTextListComponent, TTimelineRail, TToggleButtonComponent, TToggleSwitch, TVariablePattern, TVideoComponent, TVolumeButton, TimerState, TransactionRequest, UserAction, UserActionParameters, Wave, WaveSpec };
4610
+ 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_TRAFFIC_OBSERVER, 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_CACHE_TTL_PREFIX, CAMPAIGN_NOT_AVAILABLE, CONFIG_API_TIMEOUT_LIMIT, CONFIG_MAX_ATTEMPTS, CUSTOMER_ATTRIBUTES_KEY_PREFIX, CUSTOMER_JOURNEY_STATE_CHANGED, CUSTOM_HOST_PREFIX, CampaignNotAvailableError, CampaignRuleConversionEventType, CampaignRuleRepository, Capabilities, ClientError, ConfigRepository, ConflictError, CustomerJourneyRepository, DEFAULT_JOURNEY_SETTINGS, 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, MAX_FIXED_STEP_COUNT, MIN_FIXED_STEP_COUNT, NAMI_CONFIGURATION, NAMI_CUSTOMER_JOURNEY_STATE, NAMI_JOURNEY_RULE_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, NamiHandoffTag, NamiPaywallAction, NamiPaywallManager$2 as NamiPaywallManager, PaywallManagerEvents as NamiPaywallManagerEvents, NamiPermissionOutcome, NamiPurchaseManager$2 as NamiPurchaseManager, NamiRefs, NamiReservedActions, NotFoundError, PAYWALL_ACTION_EVENT, PLATFORM_ID_REQUIRED, PRODUCTION, PaywallManagerEvents, PaywallRepository, PaywallState, PlacementLabelResolver, PlatformIDRequiredError, ProductRepository, RECONFIG_SUCCESS, RESUME_THROTTLE_MS, RetryLimitExceededError, SDKNotInitializedError, SDK_NOT_INITIALIZED, SERVER_NAMI_ENTITLEMENTS, SESSION_REQUIRED, SHOULD_SHOW_LOADING_INDICATOR, SIMULATE_CONFIG_FETCH_FAILURE, SIMULATE_PAYWALL_FETCH_FAILURE, SKU_TEXT_REGEX, SMART_TEXT_PATTERN, STARTUP_TELEMETRY, STATUS_BAD_REQUEST, STATUS_CONFLICT, STATUS_INTERNAL_SERVER_ERROR, STATUS_NOT_FOUND, STATUS_SUCCESS, SYSTEM_TIME_FILTER_PREFIX, SYSTEM_TIME_TOKEN_PREFIX, SessionService, SimpleEventTarget, StorageService, TIME_OVERRIDE_PREFIX, TRANSPORT_FAILURE_STATUS, 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, didReceiveResponse, empty, evaluateSystemTimeAssertion, evaluateSystemTimeCondition, evenSegmentWidth, extractStandardPricingPhases, firstSet, formatDate, formattedPrice, generateUUID, getApiCampaigns, getApiPaywalls, getBaseUrl, getBillingPeriodNumber, getCurrencyFormat, getDeviceData, getDeviceFormFactor, getDeviceScaleFactor, getEffectiveWebStyle, getEntitlementRefIdsForSku, getExtendedClientInfo, getFreeTrialPeriod, getInitialCampaigns, getInitialPaywalls, getJourneySettings, getPaywall, getPaywallDataFromLabel, getPercentagePriceDifference, getPeriodNumberInDays, getPeriodNumberInWeeks, getPlatformAdapters, getPriceDifference, getPricePerMonth, getProductDetail, getPurchaseAdapter, getReferenceSku, getSkuProductDetailKeys, getSkuSmartTextValue, getSlideSmartTextValue, getStandardBillingPeriod, getSystemTimeNow, getTranslate, getUrlParams, handleErrors, hasAllPaywalls, hasCapability, hasPurchaseManagement, initialState, invokeHandler, isAnonymousMode, isInitialConfigCompressed, isNamiFlowCampaign, isStepsStyle, isSubscription, isSystemTimeAssertion, isValidISODate, isValidUrl, journeyCampaigns, logger, mapAnonymousCampaigns, namiBuySKU, normalizeLaunchContext, parseTimeOverride, parseToSemver, postConversion, productDetail, registerAPITrafficObserver, registerPlatformAdapters, registerPurchaseAdapter, resolveCurrentStep, resolveLoopSource, resolveStepAlignment, resolveStepCount, resolveStepSizing, selectSegment, setActiveNamiEntitlements, shouldValidateProductGroups, skuItems, skuMapFromEntitlements, stepSegmentState, storageService, toDouble, toNamiEntitlements, toNamiSKU, tryParseB64Gzip, tryParseJson, updateRelatedSKUsForNamiEntitlement, uuidFromSplitPosition, validateForm, validateMinSDKVersion, validateTextInput };
4611
+ export type { AccountStateHandler$1 as AccountStateHandler, AlignmentType, AmazonProduct, ApiResponse, AppleProduct, AvailableCampaignsResponseHandler, BorderLocationType, BorderSideType, Callback$1 as Callback, CloseHandler, CustomerJourneyState, DeepLinkUrlHandler, Device, DevicePayload, DeviceProfile, DirectionType, ExtendedPlatformInfo, FlexDirectionObject, FlowNavigationOptions, FontCollection, FontDetails, FormFactor, FormFieldValidator, GoogleProduct, IConfig, IDeviceAdapter, IEntitlements$1 as IEntitlements, IPaywall, IPlatformAdapters, IProductsWithComponents, IPurchaseAdapter, ISkuMenu, IStorageAdapter, IUIAdapter, Impression, InitialConfig, InitialConfigCompressed, InitiateStateGroup, JourneySettings, LoginResponse, NamiAPITrafficKind, NamiAPITrafficObserver, NamiAPITrafficRecord, NamiAnimation, NamiAnimationObjectSpec, NamiAnimationSpec, NamiAnonymousCampaign, NamiAppSuppliedVideoDetails, NamiCampaign, NamiCampaignManagerStatic, NamiCampaignSegment, NamiCompletionResult, NamiConfiguration, NamiConfigurationState, NamiCustomAttributeValue, NamiCustomerManagerStatic, NamiEntitlement$1 as NamiEntitlement, NamiEntitlementManagerStatic, NamiFlowAction, NamiFlowAnimation, NamiFlowCampaign, NamiFlowCompletionCondition, NamiFlowDTO, NamiFlowEventHandler, NamiFlowHandoffStepHandler, NamiFlowManagerStatic, NamiFlowObjectDTO, NamiFlowOn, NamiFlowStep, NamiFlowTransition, NamiFlowTransitionDirection, NamiFlowWithObject, NamiHandoffAttributeValue, NamiHandoffComplete, NamiHandoffOutcome, NamiHandoffTagValue, NamiInitialConfig, NamiJourneyDTO, NamiJourneyFields, NamiKnownHandoffTag, NamiLanguageCodes, NamiLogLevel, NamiLoginSuccess, NamiPaywallActionHandler, NamiPaywallComponentChange, NamiPaywallEvent, NamiPaywallEventVideoMetadata, NamiPaywallLaunchContext, NamiPaywallManagerStatic, NamiPresentationStyle, NamiProductDetails, NamiProductOffer, NamiProfile, NamiPurchase, NamiPurchaseCompleteResult, NamiPurchaseDetails, NamiPurchaseManagerStatic, NamiPurchaseSuccess, NamiPurchasesState, NamiSKU, NamiSKUType, NamiSubscriptionInterval, NamiSubscriptionPeriod, NamiUserDataEnvelope, 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, TSlot, TSlotGroupSelector, TSpacerComponent, TStack, TStepState, TSvgImageComponent, TSymbolComponent, TTestObject, TTextComponent, TTextInputComponent, TTextInputType, TTextLikeComponent, TTextListComponent, TTimelineRail, TToggleButtonComponent, TToggleSwitch, TVariablePattern, TVideoComponent, TVolumeButton, TimerState, TransactionRequest, UserAction, UserActionParameters, Wave, WaveSpec };
package/dist/index.mjs CHANGED
@@ -104,13 +104,15 @@ const {
104
104
  // version — stamped by scripts/version.sh
105
105
  NAMI_SDK_VERSION = "3.5.0",
106
106
  // full package version including dev suffix — stamped by scripts/version.sh
107
- NAMI_SDK_PACKAGE_VERSION = "3.5.0-dev.202611011549",
107
+ NAMI_SDK_PACKAGE_VERSION = "3.5.0-dev.202611011853",
108
108
  // environments
109
109
  PRODUCTION = "production", DEVELOPMENT = "development",
110
110
  // error messages
111
111
  PLATFORM_ID_REQUIRED = "Platform ID is required", DEVICE_ID_REQUIRED = "Device ID is required", EXTERNAL_ID_REQUIRED = "External ID is required", SDK_NOT_INITIALIZED = "SDK has not been initialized. Make sure to call Nami.configure() first!", CAMPAIGN_NOT_AVAILABLE = "Campaign is not available", FLOW_SCREENS_NOT_AVAILABLE = "Flow screens not available", UNABLE_TO_UPDATE_CDP_ID = "Unable to update CDP id in anonymous mode", ANONYMOUS_MODE_ALREADY_ON = "Anonymous mode is already turned on.", ANONYMOUS_MODE_ALREADY_OFF = "Anonymous mode is already turned off.", ANONYMOUS_MODE_LOGIN_NOT_ALLOWED = "Login/logout are not available in anonymous mode", SESSION_REQUIRED = "Session is required", DEVICE_ID_NOT_SET = "Nami Device ID is not set",
112
112
  // localstorage keys
113
- AUTH_DEVICE = "nami_auth_device", NAMI_CONFIGURATION = "nami_configuration", NAMI_PROFILE = "nami_profile", API_CONFIG = "api_config", API_CAMPAIGN_RULES = "api_campaign_rules", API_PAYWALLS = "api_paywalls", API_CAMPAIGN_SESSION_TIMESTAMP = "api_campaign_session_timestamp", API_PRODUCTS = "api_products", API_ACTIVE_ENTITLEMENTS = "api_active_entitlements", SERVER_NAMI_ENTITLEMENTS = "server_active_entitlements", INITIAL_APP_CONFIG = "initial_app_config", INITIAL_CAMPAIGN_RULES = "initial_campaign_rules", INITIAL_PAYWALLS = "initial_paywalls", INITIAL_PRODUCTS = "initial_products", LOCAL_NAMI_ENTITLEMENTS = "local_active_entitlements", CUSTOMER_ATTRIBUTES_KEY_PREFIX = "nami_cmr_attributes_", NAMI_CUSTOMER_JOURNEY_STATE = "nami_customer_journey_state", ANONYMOUS_MODE = "nami_anonymous_mode_v2", ANONYMOUS_UUID = "nami_anonymous_uuid", KEY_SESSION_COUNTER = "nami_session_counter", INITIAL_SESSION_COUNTER_VALUE = 1, NAMI_LAST_IMPRESSION_ID = "nami_last_impression_id", NAMI_PURCHASE_IMPRESSION_ID = "nami_purchase_impression_id", NAMI_LAUNCH_ID = "nami_launch_id", NAMI_SESSION_ID = "nami_session_id", NAMI_LANGUAGE_CODE = "nami_language_code", NAMI_PURCHASE_CHANNEL = "nami_purchase_channel",
113
+ AUTH_DEVICE = "nami_auth_device", NAMI_CONFIGURATION = "nami_configuration", NAMI_PROFILE = "nami_profile", API_CONFIG = "api_config", API_CAMPAIGN_RULES = "api_campaign_rules", API_PAYWALLS = "api_paywalls", API_CAMPAIGN_SESSION_TIMESTAMP = "api_campaign_session_timestamp", API_PRODUCTS = "api_products", API_ACTIVE_ENTITLEMENTS = "api_active_entitlements", SERVER_NAMI_ENTITLEMENTS = "server_active_entitlements", INITIAL_APP_CONFIG = "initial_app_config", INITIAL_CAMPAIGN_RULES = "initial_campaign_rules", INITIAL_PAYWALLS = "initial_paywalls", INITIAL_PRODUCTS = "initial_products", LOCAL_NAMI_ENTITLEMENTS = "local_active_entitlements", CUSTOMER_ATTRIBUTES_KEY_PREFIX = "nami_cmr_attributes_", NAMI_CUSTOMER_JOURNEY_STATE = "nami_customer_journey_state",
114
+ // Journey delivery counters (NAM-3229), unrelated to the customer journey state above.
115
+ NAMI_JOURNEY_RULE_STATE = "nami_journey_rule_state", ANONYMOUS_MODE = "nami_anonymous_mode_v2", ANONYMOUS_UUID = "nami_anonymous_uuid", KEY_SESSION_COUNTER = "nami_session_counter", INITIAL_SESSION_COUNTER_VALUE = 1, NAMI_LAST_IMPRESSION_ID = "nami_last_impression_id", NAMI_PURCHASE_IMPRESSION_ID = "nami_purchase_impression_id", NAMI_LAUNCH_ID = "nami_launch_id", NAMI_SESSION_ID = "nami_session_id", NAMI_LANGUAGE_CODE = "nami_language_code", NAMI_PURCHASE_CHANNEL = "nami_purchase_channel",
114
116
  // API settings
115
117
  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",
116
118
  // // extended client info
@@ -160,7 +162,7 @@ const NAMI_STORAGE_KEYS = [
160
162
  API_PRODUCTS, API_ACTIVE_ENTITLEMENTS, SERVER_NAMI_ENTITLEMENTS,
161
163
  INITIAL_APP_CONFIG, INITIAL_CAMPAIGN_RULES, INITIAL_PAYWALLS, INITIAL_PRODUCTS,
162
164
  LOCAL_NAMI_ENTITLEMENTS,
163
- NAMI_CUSTOMER_JOURNEY_STATE,
165
+ NAMI_CUSTOMER_JOURNEY_STATE, NAMI_JOURNEY_RULE_STATE,
164
166
  ANONYMOUS_MODE, ANONYMOUS_UUID,
165
167
  KEY_SESSION_COUNTER, NAMI_LAST_IMPRESSION_ID, NAMI_PURCHASE_IMPRESSION_ID,
166
168
  NAMI_LAUNCH_ID, NAMI_SESSION_ID, NAMI_LANGUAGE_CODE, NAMI_PURCHASE_CHANNEL,
@@ -3523,6 +3525,16 @@ class StorageService {
3523
3525
  getCustomerJourneyState() {
3524
3526
  return this.getItem(NAMI_CUSTOMER_JOURNEY_STATE);
3525
3527
  }
3528
+ /** The raw journey delivery blob; `journeyStateStore` validates its shape. */
3529
+ getJourneyRuleState() {
3530
+ return this.getItem(NAMI_JOURNEY_RULE_STATE);
3531
+ }
3532
+ setJourneyRuleState(state) {
3533
+ this.setItem(NAMI_JOURNEY_RULE_STATE, state);
3534
+ }
3535
+ removeJourneyRuleState() {
3536
+ this.resetItem(NAMI_JOURNEY_RULE_STATE);
3537
+ }
3526
3538
  setSessionCounter() {
3527
3539
  const counter = this.getItem(KEY_SESSION_COUNTER);
3528
3540
  const currentCounter = counter ? parseInt(counter) : INITIAL_SESSION_COUNTER_VALUE
@@ -7812,6 +7824,7 @@ function isNamiFlowCampaign(campaign) {
7812
7824
  var NamiCampaignRuleType;
7813
7825
  (function (NamiCampaignRuleType) {
7814
7826
  NamiCampaignRuleType["DEFAULT"] = "default";
7827
+ NamiCampaignRuleType["JOURNEY"] = "journey";
7815
7828
  NamiCampaignRuleType["LABEL"] = "label";
7816
7829
  NamiCampaignRuleType["UNKNOWN"] = "unknown";
7817
7830
  NamiCampaignRuleType["URL"] = "url";
@@ -57035,6 +57048,69 @@ const isCampaignRuleActive = (notBefore, expiresAt, now = new Date()) => {
57035
57048
  return startOk && endOk;
57036
57049
  };
57037
57050
 
57051
+ const TRIGGER_EVENT_TYPES = ["app_install", "session_start"];
57052
+ const GOAL_TYPES = ["flow_completed", "user_context_set"];
57053
+ const PRIORITIES = ["high", "medium", "low"];
57054
+ const oneOf = (allowed, value) => allowed.includes(value) ? value : null;
57055
+ const numberOrNull = (value) => (typeof value === "number" ? value : null);
57056
+ /**
57057
+ * Resolves a rule's `journey` object, reading `journey_id` from the top level and the other
57058
+ * fields from the `object` envelope when present, else from the top level (the flat contract
57059
+ * shape). Never throws: unknown values become `null` or their spec default.
57060
+ */
57061
+ function parseJourneyMetadata(dto) {
57062
+ if (!dto)
57063
+ return null;
57064
+ const fields = "object" in dto ? dto.object ?? {} : dto;
57065
+ const { goal, delivery } = fields;
57066
+ return {
57067
+ journey_id: typeof dto.journey_id === "string" ? dto.journey_id : "",
57068
+ category: fields.category ?? null,
57069
+ priority: oneOf(PRIORITIES, fields.priority) ?? "medium",
57070
+ trigger: { event_type: oneOf(TRIGGER_EVENT_TYPES, fields.trigger?.event_type) },
57071
+ goal: goal
57072
+ ? {
57073
+ type: oneOf(GOAL_TYPES, goal.type),
57074
+ context_key: goal.context_key ?? null,
57075
+ timeout_days: numberOrNull(goal.timeout_days),
57076
+ re_eligibility_days: numberOrNull(goal.re_eligibility_days),
57077
+ re_entry_after_goal: goal.re_entry_after_goal === true,
57078
+ }
57079
+ : null,
57080
+ delivery: {
57081
+ show_once: delivery?.show_once === true,
57082
+ max_impressions_total: numberOrNull(delivery?.max_impressions_total),
57083
+ max_per_session: numberOrNull(delivery?.max_per_session),
57084
+ max_per_day: numberOrNull(delivery?.max_per_day),
57085
+ delay_seconds: numberOrNull(delivery?.delay_seconds) ?? 0,
57086
+ },
57087
+ };
57088
+ }
57089
+ /**
57090
+ * Whether this SDK can act on the journey: the `journey_id` is non-blank, the trigger and goal
57091
+ * types are known, and a `user_context_set` goal names the key it watches.
57092
+ */
57093
+ function isSupportedJourney(meta) {
57094
+ if (!meta || !meta.journey_id.trim())
57095
+ return false;
57096
+ if (!meta.trigger.event_type || !meta.goal?.type)
57097
+ return false;
57098
+ if (meta.goal.type === "user_context_set")
57099
+ return !!meta.goal.context_key;
57100
+ return true;
57101
+ }
57102
+ const isJourneyCampaign = (rule) => rule.type === NamiCampaignRuleType.JOURNEY;
57103
+ /**
57104
+ * Whether this SDK can use the rule. Every placement rule qualifies. A journey also needs
57105
+ * supported metadata and a flow, because journeys are flow-only. Callers skip an unsupported
57106
+ * journey rather than failing the payload.
57107
+ */
57108
+ function isSupportedCampaignRule(rule) {
57109
+ if (!isJourneyCampaign(rule))
57110
+ return true;
57111
+ return rule.flow != null && isSupportedJourney(parseJourneyMetadata(rule.journey));
57112
+ }
57113
+
57038
57114
  const isValidUrl = (label) => {
57039
57115
  try {
57040
57116
  const url = new URL(label);
@@ -57148,7 +57224,7 @@ const mapAnonymousCampaigns = (campaigns, splitPosition, formFactor) => {
57148
57224
  if (segments.length === 0)
57149
57225
  return [];
57150
57226
  const segment = selectSegment(segments, splitPosition);
57151
- return {
57227
+ const mapped = {
57152
57228
  form_factors: segment.form_factors,
57153
57229
  paywall: segment.paywall,
57154
57230
  paywall_url: segment.paywall_url,
@@ -57163,12 +57239,47 @@ const mapAnonymousCampaigns = (campaigns, splitPosition, formFactor) => {
57163
57239
  type: campaign.type,
57164
57240
  value: campaign.value,
57165
57241
  external_segment: campaign.external_segment,
57242
+ // Rule-level, like Apple's decoder: the window applies whichever segment wins.
57243
+ not_before: campaign.not_before,
57244
+ expires_at: campaign.expires_at,
57245
+ journey: campaign.journey,
57166
57246
  };
57247
+ // The initial-config path stores this list directly, so it needs the same skip-not-fail
57248
+ // check the repository applies to fetched rules (NAM-3225).
57249
+ if (!isSupportedCampaignRule(mapped)) {
57250
+ logger.debug(`Skipping unsupported journey rule ${campaign.rule}`);
57251
+ return [];
57252
+ }
57253
+ return mapped;
57254
+ });
57255
+ };
57256
+ /** Stored rules that target this device's form factor and are inside their active window. */
57257
+ const liveForDevice = (campaigns) => {
57258
+ const formFactor = getDeviceFormFactor();
57259
+ return compact(campaigns)
57260
+ .filter((cRule) => cRule.form_factors?.some((f) => f.form_factor === formFactor))
57261
+ .filter((cRule) => isCampaignRuleActive(cRule.not_before, cRule.expires_at));
57262
+ };
57263
+ /** Keeps the first rule per key, so API entries (listed first) win and payload order is kept. */
57264
+ const dedupeBy = (campaigns, key) => {
57265
+ const seen = new Set();
57266
+ return campaigns.filter((c) => {
57267
+ const k = key(c);
57268
+ if (seen.has(k))
57269
+ return false;
57270
+ seen.add(k);
57271
+ return true;
57167
57272
  });
57168
57273
  };
57274
+ const storedApiAndInitialRules = () => [
57275
+ ...(storageService.getCampaignRules(API_CAMPAIGN_RULES) ?? []),
57276
+ ...(storageService.getCampaignRules(INITIAL_CAMPAIGN_RULES) ?? []),
57277
+ ];
57169
57278
  /**
57170
- * Returns the combined list of API + initial-config campaigns for the current device's
57171
- * form factor, deduplicated by (type, value) with API entries winning on collision.
57279
+ * Returns the combined list of API + initial-config placement campaigns for the current
57280
+ * device's form factor, deduplicated by (type, value) with API entries winning on collision.
57281
+ * Journey rules are never included: they are launched by trigger events, not by a placement
57282
+ * (see {@link journeyCampaigns}).
57172
57283
  *
57173
57284
  * Mirrors NamiCampaignManager.allCampaigns() on Apple (Set keyed on hash(value, type))
57174
57285
  * and Android (.distinct() with server-first ordering). Keying on (type, value) — rather
@@ -57182,35 +57293,22 @@ const mapAnonymousCampaigns = (campaigns, splitPosition, formFactor) => {
57182
57293
  * when the API pair is incomplete — matching the merged-paywall-lookup behavior on
57183
57294
  * Apple and Android.
57184
57295
  */
57185
- const allCampaigns = () => {
57186
- const apiCampaigns = storageService.getCampaignRules(API_CAMPAIGN_RULES) ?? [];
57187
- const initialCampaigns = storageService.getCampaignRules(INITIAL_CAMPAIGN_RULES) ?? [];
57188
- const formFactor = getDeviceFormFactor();
57189
- const campaigns = compact([...apiCampaigns, ...initialCampaigns])
57190
- .filter((cRule) => cRule.form_factors?.some((f) => f.form_factor === formFactor))
57191
- .filter((cRule) => isCampaignRuleActive(cRule.not_before, cRule.expires_at));
57192
- const seen = new Set();
57193
- return campaigns.filter((c) => {
57194
- const key = `${c.type ?? ''}::${c.value ?? ''}`;
57195
- if (seen.has(key))
57196
- return false;
57197
- seen.add(key);
57198
- return true;
57199
- });
57200
- };
57296
+ const allCampaigns = () => dedupeBy(liveForDevice(storedApiAndInitialRules()).filter((c) => !isJourneyCampaign(c)), (c) => `${c.type ?? ''}::${c.value ?? ''}`);
57201
57297
  /**
57202
- * Get campaigns by rule key, filtered by form factor.
57298
+ * API + initial-config journey rules for the current device's form factor and inside their
57299
+ * window, deduplicated on `rule` (every journey's `value` is null, so the placement key would
57300
+ * collapse them) with API entries first and payload order kept: the engine's tiebreaks depend
57301
+ * on it. This is the read surface for journey delivery; placement APIs never see these rules.
57302
+ */
57303
+ const journeyCampaigns = () => dedupeBy(liveForDevice(storedApiAndInitialRules()).filter(isJourneyCampaign), (c) => c.rule);
57304
+ /**
57305
+ * Get placement campaigns by rule key, filtered by form factor and active window. Journey
57306
+ * rules are excluded so label, URL and default matching never resolve to one.
57203
57307
  *
57204
57308
  * @param ruleKey The key of the campaign rule to filter by
57205
57309
  * @returns An array of campaigns that match the rule key and form factor
57206
57310
  */
57207
- const getCampaignsByRule = (ruleKey) => {
57208
- const campaigns = storageService.getCampaignRules(ruleKey) ?? [];
57209
- const formFactor = getDeviceFormFactor();
57210
- return compact(campaigns)
57211
- .filter((cRule) => cRule.form_factors?.some((f) => f.form_factor === formFactor))
57212
- .filter((cRule) => isCampaignRuleActive(cRule.not_before, cRule.expires_at));
57213
- };
57311
+ const getCampaignsByRule = (ruleKey) => liveForDevice(storageService.getCampaignRules(ruleKey) ?? []).filter((c) => !isJourneyCampaign(c));
57214
57312
  const getInitialCampaigns = () => getCampaignsByRule(INITIAL_CAMPAIGN_RULES);
57215
57313
  const getApiCampaigns = () => getCampaignsByRule(API_CAMPAIGN_RULES);
57216
57314
  /**
@@ -57759,6 +57857,22 @@ class CampaignRuleRepository {
57759
57857
  constructor() {
57760
57858
  this.disableCampaignUpdates = false;
57761
57859
  this.useLegacyPaywallFetch = false;
57860
+ /**
57861
+ * A rule this device can use: it has content (a paywall or a flow), targets the current form
57862
+ * factor, and, for a journey, carries supported metadata. An unsupported journey is skipped,
57863
+ * never the payload (NAM-3225).
57864
+ */
57865
+ this.isUsableOnDevice = (cRule) => {
57866
+ if (!cRule.paywall && !cRule.flow)
57867
+ return false;
57868
+ if (!cRule.form_factors?.some((f) => f.form_factor === this.currentFormFactor))
57869
+ return false;
57870
+ if (!isSupportedCampaignRule(cRule)) {
57871
+ logger.debug(`Skipping unsupported journey rule ${cRule.rule}`);
57872
+ return false;
57873
+ }
57874
+ return true;
57875
+ };
57762
57876
  this.currentFormFactor = getDeviceFormFactor();
57763
57877
  }
57764
57878
  configure(formFactor, splitPosition, namiCommands = []) {
@@ -57793,9 +57907,7 @@ class CampaignRuleRepository {
57793
57907
  else {
57794
57908
  campaignRules = await this.getCampaigns(authDevice.id);
57795
57909
  }
57796
- // get campaign rules according to device's form_factor
57797
- campaignRules = campaignRules?.filter((cRule) => (cRule.paywall && cRule.form_factors.some((f) => f.form_factor === this.currentFormFactor)) ||
57798
- (cRule.flow && cRule.form_factors?.some((f) => f.form_factor === this.currentFormFactor)));
57910
+ campaignRules = campaignRules?.filter(this.isUsableOnDevice);
57799
57911
  // Keep only those campaign rules whose corresponding paywall is available
57800
57912
  campaignRules = campaignRules?.filter((cRule) => {
57801
57913
  if (cRule.paywall) {
@@ -57845,8 +57957,7 @@ class CampaignRuleRepository {
57845
57957
  const fetched = authDevice?.id
57846
57958
  ? await this.fetchCampaigns(authDevice.id)
57847
57959
  : await this.fetchAnonymousCampaigns();
57848
- const campaigns = fetched.campaigns?.filter((cRule) => (cRule.paywall && cRule.form_factors.some((f) => f.form_factor === this.currentFormFactor)) ||
57849
- (cRule.flow && cRule.form_factors?.some((f) => f.form_factor === this.currentFormFactor)));
57960
+ const campaigns = fetched.campaigns?.filter(this.isUsableOnDevice);
57850
57961
  return { campaigns, fromNetwork: fetched.fromNetwork };
57851
57962
  }
57852
57963
  finalizeCampaignRules(campaignRules, paywalls) {
@@ -66737,6 +66848,7 @@ const NamiAnimationType = {
66737
66848
  var CampaignRuleType;
66738
66849
  (function (CampaignRuleType) {
66739
66850
  CampaignRuleType["DEFAULT"] = "default";
66851
+ CampaignRuleType["JOURNEY"] = "journey";
66740
66852
  CampaignRuleType["LABEL"] = "label";
66741
66853
  CampaignRuleType["URL"] = "url";
66742
66854
  })(CampaignRuleType || (CampaignRuleType = {}));
@@ -67213,4 +67325,4 @@ var internal = /*#__PURE__*/Object.freeze({
67213
67325
  NamiPurchaseManager: NamiPurchaseManager
67214
67326
  });
67215
67327
 
67216
- 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_TRAFFIC_OBSERVER, 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_CACHE_TTL_PREFIX, CAMPAIGN_NOT_AVAILABLE, CONFIG_API_TIMEOUT_LIMIT, CONFIG_MAX_ATTEMPTS, CUSTOMER_ATTRIBUTES_KEY_PREFIX, CUSTOMER_JOURNEY_STATE_CHANGED, CUSTOM_HOST_PREFIX, CampaignNotAvailableError, CampaignRuleConversionEventType, CampaignRuleRepository, Capabilities, ClientError, ConfigRepository, ConflictError, CustomerJourneyRepository, DEFAULT_JOURNEY_SETTINGS, 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, MAX_FIXED_STEP_COUNT, MIN_FIXED_STEP_COUNT, NAMI_CONFIGURATION, NAMI_CUSTOMER_JOURNEY_STATE, NAMI_LANGUAGE_CODE, NAMI_LAST_IMPRESSION_ID, NAMI_LAUNCH_ID, NAMI_PROFILE, NAMI_PURCHASE_CHANNEL, NAMI_PURCHASE_IMPRESSION_ID, NAMI_SDK_PACKAGE_VERSION, NAMI_SDK_VERSION, NAMI_SESSION_ID, NAMI_STORAGE_KEYS, Nami, NamiAPI, NamiAnimationType, NamiCampaignManager$1 as NamiCampaignManager, NamiCampaignRuleType, NamiConditionEvaluator, NamiCustomerManager$1 as NamiCustomerManager, NamiEntitlementManager$1 as NamiEntitlementManager, NamiEventEmitter, NamiFlow, NamiFlowActionFunction, NamiFlowManager$1 as NamiFlowManager, NamiFlowStepType, NamiHandoffTag, NamiPaywallAction, NamiPaywallManager$1 as NamiPaywallManager, PaywallManagerEvents as NamiPaywallManagerEvents, NamiPermissionOutcome, NamiPurchaseManager$1 as NamiPurchaseManager, NamiRefs, NamiReservedActions, NotFoundError, PAYWALL_ACTION_EVENT, PLATFORM_ID_REQUIRED, PRODUCTION, PaywallManagerEvents, PaywallRepository, PaywallState, PlacementLabelResolver, PlatformIDRequiredError, ProductRepository, RECONFIG_SUCCESS, RESUME_THROTTLE_MS, RetryLimitExceededError, SDKNotInitializedError, SDK_NOT_INITIALIZED, SERVER_NAMI_ENTITLEMENTS, SESSION_REQUIRED, SHOULD_SHOW_LOADING_INDICATOR, SIMULATE_CONFIG_FETCH_FAILURE, SIMULATE_PAYWALL_FETCH_FAILURE, SKU_TEXT_REGEX, SMART_TEXT_PATTERN, STARTUP_TELEMETRY, STATUS_BAD_REQUEST, STATUS_CONFLICT, STATUS_INTERNAL_SERVER_ERROR, STATUS_NOT_FOUND, STATUS_SUCCESS, SYSTEM_TIME_FILTER_PREFIX, SYSTEM_TIME_TOKEN_PREFIX, SessionService, SimpleEventTarget, StorageService, TIME_OVERRIDE_PREFIX, TRANSPORT_FAILURE_STATUS, 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, didReceiveResponse, empty, evaluateSystemTimeAssertion, evaluateSystemTimeCondition, evenSegmentWidth, extractStandardPricingPhases, firstSet, formatDate, formattedPrice, generateUUID, getApiCampaigns, getApiPaywalls, getBaseUrl, getBillingPeriodNumber, getCurrencyFormat, getDeviceData, getDeviceFormFactor, getDeviceScaleFactor, getEffectiveWebStyle, getEntitlementRefIdsForSku, getExtendedClientInfo, getFreeTrialPeriod, getInitialCampaigns, getInitialPaywalls, getJourneySettings, getPaywall, getPaywallDataFromLabel, getPercentagePriceDifference, getPeriodNumberInDays, getPeriodNumberInWeeks, getPlatformAdapters, getPriceDifference, getPricePerMonth, getProductDetail, getPurchaseAdapter, getReferenceSku, getSkuProductDetailKeys, getSkuSmartTextValue, getSlideSmartTextValue, getStandardBillingPeriod, getSystemTimeNow, getTranslate, getUrlParams, handleErrors, hasAllPaywalls, hasCapability, hasPurchaseManagement, initialState, invokeHandler, isAnonymousMode, isInitialConfigCompressed, isNamiFlowCampaign, isStepsStyle, isSubscription, isSystemTimeAssertion, isValidISODate, isValidUrl, logger, mapAnonymousCampaigns, namiBuySKU, normalizeLaunchContext, parseTimeOverride, parseToSemver, postConversion, productDetail, registerAPITrafficObserver, registerPlatformAdapters, registerPurchaseAdapter, resolveCurrentStep, resolveLoopSource, resolveStepAlignment, resolveStepCount, resolveStepSizing, selectSegment, setActiveNamiEntitlements, shouldValidateProductGroups, skuItems, skuMapFromEntitlements, stepSegmentState, storageService, toDouble, toNamiEntitlements, toNamiSKU, tryParseB64Gzip, tryParseJson, updateRelatedSKUsForNamiEntitlement, uuidFromSplitPosition, validateForm, validateMinSDKVersion, validateTextInput };
67328
+ 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_TRAFFIC_OBSERVER, 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_CACHE_TTL_PREFIX, CAMPAIGN_NOT_AVAILABLE, CONFIG_API_TIMEOUT_LIMIT, CONFIG_MAX_ATTEMPTS, CUSTOMER_ATTRIBUTES_KEY_PREFIX, CUSTOMER_JOURNEY_STATE_CHANGED, CUSTOM_HOST_PREFIX, CampaignNotAvailableError, CampaignRuleConversionEventType, CampaignRuleRepository, Capabilities, ClientError, ConfigRepository, ConflictError, CustomerJourneyRepository, DEFAULT_JOURNEY_SETTINGS, 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, MAX_FIXED_STEP_COUNT, MIN_FIXED_STEP_COUNT, NAMI_CONFIGURATION, NAMI_CUSTOMER_JOURNEY_STATE, NAMI_JOURNEY_RULE_STATE, NAMI_LANGUAGE_CODE, NAMI_LAST_IMPRESSION_ID, NAMI_LAUNCH_ID, NAMI_PROFILE, NAMI_PURCHASE_CHANNEL, NAMI_PURCHASE_IMPRESSION_ID, NAMI_SDK_PACKAGE_VERSION, NAMI_SDK_VERSION, NAMI_SESSION_ID, NAMI_STORAGE_KEYS, Nami, NamiAPI, NamiAnimationType, NamiCampaignManager$1 as NamiCampaignManager, NamiCampaignRuleType, NamiConditionEvaluator, NamiCustomerManager$1 as NamiCustomerManager, NamiEntitlementManager$1 as NamiEntitlementManager, NamiEventEmitter, NamiFlow, NamiFlowActionFunction, NamiFlowManager$1 as NamiFlowManager, NamiFlowStepType, NamiHandoffTag, NamiPaywallAction, NamiPaywallManager$1 as NamiPaywallManager, PaywallManagerEvents as NamiPaywallManagerEvents, NamiPermissionOutcome, NamiPurchaseManager$1 as NamiPurchaseManager, NamiRefs, NamiReservedActions, NotFoundError, PAYWALL_ACTION_EVENT, PLATFORM_ID_REQUIRED, PRODUCTION, PaywallManagerEvents, PaywallRepository, PaywallState, PlacementLabelResolver, PlatformIDRequiredError, ProductRepository, RECONFIG_SUCCESS, RESUME_THROTTLE_MS, RetryLimitExceededError, SDKNotInitializedError, SDK_NOT_INITIALIZED, SERVER_NAMI_ENTITLEMENTS, SESSION_REQUIRED, SHOULD_SHOW_LOADING_INDICATOR, SIMULATE_CONFIG_FETCH_FAILURE, SIMULATE_PAYWALL_FETCH_FAILURE, SKU_TEXT_REGEX, SMART_TEXT_PATTERN, STARTUP_TELEMETRY, STATUS_BAD_REQUEST, STATUS_CONFLICT, STATUS_INTERNAL_SERVER_ERROR, STATUS_NOT_FOUND, STATUS_SUCCESS, SYSTEM_TIME_FILTER_PREFIX, SYSTEM_TIME_TOKEN_PREFIX, SessionService, SimpleEventTarget, StorageService, TIME_OVERRIDE_PREFIX, TRANSPORT_FAILURE_STATUS, 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, didReceiveResponse, empty, evaluateSystemTimeAssertion, evaluateSystemTimeCondition, evenSegmentWidth, extractStandardPricingPhases, firstSet, formatDate, formattedPrice, generateUUID, getApiCampaigns, getApiPaywalls, getBaseUrl, getBillingPeriodNumber, getCurrencyFormat, getDeviceData, getDeviceFormFactor, getDeviceScaleFactor, getEffectiveWebStyle, getEntitlementRefIdsForSku, getExtendedClientInfo, getFreeTrialPeriod, getInitialCampaigns, getInitialPaywalls, getJourneySettings, getPaywall, getPaywallDataFromLabel, getPercentagePriceDifference, getPeriodNumberInDays, getPeriodNumberInWeeks, getPlatformAdapters, getPriceDifference, getPricePerMonth, getProductDetail, getPurchaseAdapter, getReferenceSku, getSkuProductDetailKeys, getSkuSmartTextValue, getSlideSmartTextValue, getStandardBillingPeriod, getSystemTimeNow, getTranslate, getUrlParams, handleErrors, hasAllPaywalls, hasCapability, hasPurchaseManagement, initialState, invokeHandler, isAnonymousMode, isInitialConfigCompressed, isNamiFlowCampaign, isStepsStyle, isSubscription, isSystemTimeAssertion, isValidISODate, isValidUrl, journeyCampaigns, logger, mapAnonymousCampaigns, namiBuySKU, normalizeLaunchContext, parseTimeOverride, parseToSemver, postConversion, productDetail, registerAPITrafficObserver, registerPlatformAdapters, registerPurchaseAdapter, resolveCurrentStep, resolveLoopSource, resolveStepAlignment, resolveStepCount, resolveStepSizing, selectSegment, setActiveNamiEntitlements, shouldValidateProductGroups, skuItems, skuMapFromEntitlements, stepSegmentState, storageService, toDouble, toNamiEntitlements, toNamiSKU, tryParseB64Gzip, tryParseJson, updateRelatedSKUsForNamiEntitlement, uuidFromSplitPosition, validateForm, validateMinSDKVersion, validateTextInput };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@namiml/sdk-core",
3
- "version": "3.5.0-dev.202611011549",
3
+ "version": "3.5.0-dev.202611011853",
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",