@namiml/sdk-core 3.5.0-dev.202611011626 → 3.5.0-dev.202611020145
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 +725 -62
- package/dist/index.d.ts +49 -3
- package/dist/index.mjs +726 -63
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -104,13 +104,17 @@ 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.
|
|
107
|
+
NAMI_SDK_PACKAGE_VERSION = "3.5.0-dev.202611020145",
|
|
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",
|
|
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",
|
|
116
|
+
// Set at the first app_install journey evaluation on this device, whatever its outcome (NAM-3233).
|
|
117
|
+
NAMI_JOURNEY_INSTALL_EVALUATED = "nami_journey_install_evaluated", 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
118
|
// API settings
|
|
115
119
|
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
120
|
// // extended client info
|
|
@@ -118,6 +122,8 @@ EXTENDED_CLIENT_INFO_PREFIX = "extendedClientInfo", EXTENDED_CLIENT_INFO_DELIMIT
|
|
|
118
122
|
// Test hook: every platform-config fetch attempt fails before the request leaves.
|
|
119
123
|
// Same namiCommand as Apple's and Android's, so the shared first-run flows drive it.
|
|
120
124
|
SIMULATE_CONFIG_FETCH_FAILURE = "simulateConfigFetchFailure",
|
|
125
|
+
// Journeys are evaluated but never raised (NAM-5234); on in every e2e suite but the journey ones.
|
|
126
|
+
DISABLE_JOURNEY_DELIVERY = "disableJourneyDelivery",
|
|
121
127
|
// Test hook: every per-paywall body fetch fails before the request leaves, so the body cache's
|
|
122
128
|
// failure rows can be exercised without a proxy. Same namiCommand as Apple's and Android's (NAM-4357).
|
|
123
129
|
SIMULATE_PAYWALL_FETCH_FAILURE = "simulatePaywallFetchFailure",
|
|
@@ -140,6 +146,9 @@ CONFIG_API_TIMEOUT_LIMIT = 10000,
|
|
|
140
146
|
// A foreground within this long of the last launch/resume run is skipped — Apple's
|
|
141
147
|
// NamiStartupCoordinator.shouldRunResume window.
|
|
142
148
|
RESUME_THROTTLE_MS = 30000,
|
|
149
|
+
// A return to the foreground at least this long after the background starts a new session —
|
|
150
|
+
// the unified session boundary Apple and Android share (NAM-3234/3235). Drives journey sessions.
|
|
151
|
+
SESSION_GAP_MS = 30000,
|
|
143
152
|
// status codes
|
|
144
153
|
STATUS_SUCCESS = 200, STATUS_BAD_REQUEST = 400, STATUS_NOT_FOUND = 404, STATUS_CONFLICT = 409, STATUS_INTERNAL_SERVER_ERROR = 500,
|
|
145
154
|
// configuration states
|
|
@@ -160,7 +169,7 @@ const NAMI_STORAGE_KEYS = [
|
|
|
160
169
|
API_PRODUCTS, API_ACTIVE_ENTITLEMENTS, SERVER_NAMI_ENTITLEMENTS,
|
|
161
170
|
INITIAL_APP_CONFIG, INITIAL_CAMPAIGN_RULES, INITIAL_PAYWALLS, INITIAL_PRODUCTS,
|
|
162
171
|
LOCAL_NAMI_ENTITLEMENTS,
|
|
163
|
-
NAMI_CUSTOMER_JOURNEY_STATE,
|
|
172
|
+
NAMI_CUSTOMER_JOURNEY_STATE, NAMI_JOURNEY_RULE_STATE, NAMI_JOURNEY_INSTALL_EVALUATED,
|
|
164
173
|
ANONYMOUS_MODE, ANONYMOUS_UUID,
|
|
165
174
|
KEY_SESSION_COUNTER, NAMI_LAST_IMPRESSION_ID, NAMI_PURCHASE_IMPRESSION_ID,
|
|
166
175
|
NAMI_LAUNCH_ID, NAMI_SESSION_ID, NAMI_LANGUAGE_CODE, NAMI_PURCHASE_CHANNEL,
|
|
@@ -603,7 +612,7 @@ function baseGetTag(value) {
|
|
|
603
612
|
* _.isObject(null);
|
|
604
613
|
* // => false
|
|
605
614
|
*/
|
|
606
|
-
function isObject(value) {
|
|
615
|
+
function isObject$1(value) {
|
|
607
616
|
var type = typeof value;
|
|
608
617
|
return value != null && (type == 'object' || type == 'function');
|
|
609
618
|
}
|
|
@@ -632,7 +641,7 @@ var asyncTag = '[object AsyncFunction]',
|
|
|
632
641
|
* // => false
|
|
633
642
|
*/
|
|
634
643
|
function isFunction(value) {
|
|
635
|
-
if (!isObject(value)) {
|
|
644
|
+
if (!isObject$1(value)) {
|
|
636
645
|
return false;
|
|
637
646
|
}
|
|
638
647
|
// The use of `Object#toString` avoids issues with the `typeof` operator
|
|
@@ -720,7 +729,7 @@ var reIsNative = RegExp('^' +
|
|
|
720
729
|
* else `false`.
|
|
721
730
|
*/
|
|
722
731
|
function baseIsNative(value) {
|
|
723
|
-
if (!isObject(value) || isMasked(value)) {
|
|
732
|
+
if (!isObject$1(value) || isMasked(value)) {
|
|
724
733
|
return false;
|
|
725
734
|
}
|
|
726
735
|
var pattern = isFunction(value) ? reIsNative : reIsHostCtor;
|
|
@@ -2296,7 +2305,7 @@ function baseIsMatch(object, source, matchData, customizer) {
|
|
|
2296
2305
|
* equality comparisons, else `false`.
|
|
2297
2306
|
*/
|
|
2298
2307
|
function isStrictComparable(value) {
|
|
2299
|
-
return value === value && !isObject(value);
|
|
2308
|
+
return value === value && !isObject$1(value);
|
|
2300
2309
|
}
|
|
2301
2310
|
|
|
2302
2311
|
/**
|
|
@@ -3523,6 +3532,23 @@ class StorageService {
|
|
|
3523
3532
|
getCustomerJourneyState() {
|
|
3524
3533
|
return this.getItem(NAMI_CUSTOMER_JOURNEY_STATE);
|
|
3525
3534
|
}
|
|
3535
|
+
/** The raw journey delivery blob; `journeyStateStore` validates its shape. */
|
|
3536
|
+
getJourneyRuleState() {
|
|
3537
|
+
return this.getItem(NAMI_JOURNEY_RULE_STATE);
|
|
3538
|
+
}
|
|
3539
|
+
setJourneyRuleState(state) {
|
|
3540
|
+
this.setItem(NAMI_JOURNEY_RULE_STATE, state);
|
|
3541
|
+
}
|
|
3542
|
+
removeJourneyRuleState() {
|
|
3543
|
+
this.resetItem(NAMI_JOURNEY_RULE_STATE);
|
|
3544
|
+
}
|
|
3545
|
+
/** Whether the one-time app_install journey evaluation has run on this device. */
|
|
3546
|
+
getJourneyInstallEvaluated() {
|
|
3547
|
+
return this.getItem(NAMI_JOURNEY_INSTALL_EVALUATED) === true;
|
|
3548
|
+
}
|
|
3549
|
+
setJourneyInstallEvaluated() {
|
|
3550
|
+
this.setItem(NAMI_JOURNEY_INSTALL_EVALUATED, true);
|
|
3551
|
+
}
|
|
3526
3552
|
setSessionCounter() {
|
|
3527
3553
|
const counter = this.getItem(KEY_SESSION_COUNTER);
|
|
3528
3554
|
const currentCounter = counter ? parseInt(counter) : INITIAL_SESSION_COUNTER_VALUE
|
|
@@ -7145,6 +7171,11 @@ const shouldValidateProductGroups = () => {
|
|
|
7145
7171
|
const namiCommands = storageService.getNamiConfig()?.namiCommands;
|
|
7146
7172
|
return namiCommands?.includes(VALIDATE_PRODUCT_GROUPS) ?? false;
|
|
7147
7173
|
};
|
|
7174
|
+
/** Whether journey delivery is suppressed by the `disableJourneyDelivery` namiCommand (NAM-5234). */
|
|
7175
|
+
const shouldDisableJourneyDelivery = () => {
|
|
7176
|
+
const namiCommands = storageService.getNamiConfig()?.namiCommands;
|
|
7177
|
+
return namiCommands?.includes(DISABLE_JOURNEY_DELIVERY) ?? false;
|
|
7178
|
+
};
|
|
7148
7179
|
const shouldLogHTTPRequests = () => {
|
|
7149
7180
|
const namiCommands = storageService.getNamiConfig()?.namiCommands;
|
|
7150
7181
|
return namiCommands?.includes(LOG_HTTP_REQUESTS) ?? false;
|
|
@@ -57040,7 +57071,7 @@ const TRIGGER_EVENT_TYPES = ["app_install", "session_start"];
|
|
|
57040
57071
|
const GOAL_TYPES = ["flow_completed", "user_context_set"];
|
|
57041
57072
|
const PRIORITIES = ["high", "medium", "low"];
|
|
57042
57073
|
const oneOf = (allowed, value) => allowed.includes(value) ? value : null;
|
|
57043
|
-
const numberOrNull = (value) => (typeof value === "number" ? value : null);
|
|
57074
|
+
const numberOrNull$1 = (value) => (typeof value === "number" ? value : null);
|
|
57044
57075
|
/**
|
|
57045
57076
|
* Resolves a rule's `journey` object, reading `journey_id` from the top level and the other
|
|
57046
57077
|
* fields from the `object` envelope when present, else from the top level (the flat contract
|
|
@@ -57060,17 +57091,17 @@ function parseJourneyMetadata(dto) {
|
|
|
57060
57091
|
? {
|
|
57061
57092
|
type: oneOf(GOAL_TYPES, goal.type),
|
|
57062
57093
|
context_key: goal.context_key ?? null,
|
|
57063
|
-
timeout_days: numberOrNull(goal.timeout_days),
|
|
57064
|
-
re_eligibility_days: numberOrNull(goal.re_eligibility_days),
|
|
57094
|
+
timeout_days: numberOrNull$1(goal.timeout_days),
|
|
57095
|
+
re_eligibility_days: numberOrNull$1(goal.re_eligibility_days),
|
|
57065
57096
|
re_entry_after_goal: goal.re_entry_after_goal === true,
|
|
57066
57097
|
}
|
|
57067
57098
|
: null,
|
|
57068
57099
|
delivery: {
|
|
57069
57100
|
show_once: delivery?.show_once === true,
|
|
57070
|
-
max_impressions_total: numberOrNull(delivery?.max_impressions_total),
|
|
57071
|
-
max_per_session: numberOrNull(delivery?.max_per_session),
|
|
57072
|
-
max_per_day: numberOrNull(delivery?.max_per_day),
|
|
57073
|
-
delay_seconds: numberOrNull(delivery?.delay_seconds) ?? 0,
|
|
57101
|
+
max_impressions_total: numberOrNull$1(delivery?.max_impressions_total),
|
|
57102
|
+
max_per_session: numberOrNull$1(delivery?.max_per_session),
|
|
57103
|
+
max_per_day: numberOrNull$1(delivery?.max_per_day),
|
|
57104
|
+
delay_seconds: numberOrNull$1(delivery?.delay_seconds) ?? 0,
|
|
57074
57105
|
},
|
|
57075
57106
|
};
|
|
57076
57107
|
}
|
|
@@ -61060,7 +61091,7 @@ var objectCreate = Object.create;
|
|
|
61060
61091
|
var baseCreate = (function() {
|
|
61061
61092
|
function object() {}
|
|
61062
61093
|
return function(proto) {
|
|
61063
|
-
if (!isObject(proto)) {
|
|
61094
|
+
if (!isObject$1(proto)) {
|
|
61064
61095
|
return {};
|
|
61065
61096
|
}
|
|
61066
61097
|
if (objectCreate) {
|
|
@@ -61224,7 +61255,7 @@ function baseClone(value, bitmask, customizer, key, object, stack) {
|
|
|
61224
61255
|
if (result !== undefined) {
|
|
61225
61256
|
return result;
|
|
61226
61257
|
}
|
|
61227
|
-
if (!isObject(value)) {
|
|
61258
|
+
if (!isObject$1(value)) {
|
|
61228
61259
|
return value;
|
|
61229
61260
|
}
|
|
61230
61261
|
var isArr = isArray(value);
|
|
@@ -61788,8 +61819,11 @@ class PaywallState extends SimpleEventTarget {
|
|
|
61788
61819
|
this.providers.push(provider);
|
|
61789
61820
|
return provider;
|
|
61790
61821
|
}
|
|
61822
|
+
// `PaywallState.providers`, never `this.providers`: called through a subclass (web's
|
|
61823
|
+
// PaywallContextProvider), an assignment to `this.providers` would create a second array on the
|
|
61824
|
+
// subclass and leave this one holding every closed paywall.
|
|
61791
61825
|
static remove(provider) {
|
|
61792
|
-
|
|
61826
|
+
PaywallState.providers = PaywallState.providers.filter(p => p !== provider);
|
|
61793
61827
|
}
|
|
61794
61828
|
/**
|
|
61795
61829
|
* Clear the static `providers` array. Called from `Nami.reset()` so
|
|
@@ -61798,7 +61832,7 @@ class PaywallState extends SimpleEventTarget {
|
|
|
61798
61832
|
* for the next configure() cycle.
|
|
61799
61833
|
*/
|
|
61800
61834
|
static reset() {
|
|
61801
|
-
|
|
61835
|
+
PaywallState.providers = [];
|
|
61802
61836
|
}
|
|
61803
61837
|
static get currentProvider() {
|
|
61804
61838
|
return PaywallState.providers[0];
|
|
@@ -62577,6 +62611,612 @@ class StartupTelemetry {
|
|
|
62577
62611
|
}
|
|
62578
62612
|
const startupTelemetry = new StartupTelemetry();
|
|
62579
62613
|
|
|
62614
|
+
/**
|
|
62615
|
+
* One-shot timer that can pause and resume, keeping the remaining time (NAM-3233). Mirrors
|
|
62616
|
+
* Apple's `JourneyDelayTimer` (NAM-3234). Remaining time is measured with the injected `now`,
|
|
62617
|
+
* a monotonic clock in production, so a wall-clock change cannot shorten or stretch a delay.
|
|
62618
|
+
*/
|
|
62619
|
+
class JourneyDelayTimer {
|
|
62620
|
+
constructor(now) {
|
|
62621
|
+
this.now = now;
|
|
62622
|
+
this.handle = null;
|
|
62623
|
+
this.fire = null;
|
|
62624
|
+
this.deadline = 0;
|
|
62625
|
+
this.remaining = null;
|
|
62626
|
+
}
|
|
62627
|
+
/** True while a delay is running or paused. */
|
|
62628
|
+
get isPending() {
|
|
62629
|
+
return this.fire !== null;
|
|
62630
|
+
}
|
|
62631
|
+
/** True while a delay is pending but not counting down (paused, or started paused). */
|
|
62632
|
+
get isPaused() {
|
|
62633
|
+
return this.fire !== null && this.handle === null;
|
|
62634
|
+
}
|
|
62635
|
+
/**
|
|
62636
|
+
* Starts the delay, cancelling any pending one first: the previous `fire` never runs. The
|
|
62637
|
+
* single-slot rule (drop a new winner while one waits) is the caller's, which checks
|
|
62638
|
+
* {@link isPending} first.
|
|
62639
|
+
*/
|
|
62640
|
+
start(ms, fire) {
|
|
62641
|
+
this.cancel();
|
|
62642
|
+
this.fire = fire;
|
|
62643
|
+
this.arm(ms);
|
|
62644
|
+
}
|
|
62645
|
+
/** Like {@link start}, but the delay begins paused with `ms` remaining until {@link resume}. */
|
|
62646
|
+
startPaused(ms, fire) {
|
|
62647
|
+
this.cancel();
|
|
62648
|
+
this.fire = fire;
|
|
62649
|
+
this.remaining = Math.max(0, ms);
|
|
62650
|
+
}
|
|
62651
|
+
/** Stops the countdown, remembering what is left. No-op unless running. */
|
|
62652
|
+
pause() {
|
|
62653
|
+
if (this.handle === null)
|
|
62654
|
+
return;
|
|
62655
|
+
this.remaining = Math.max(0, this.deadline - this.now());
|
|
62656
|
+
this.disarm();
|
|
62657
|
+
}
|
|
62658
|
+
/** Re-arms with the remaining time. No-op unless paused. */
|
|
62659
|
+
resume() {
|
|
62660
|
+
if (this.fire === null || this.handle !== null || this.remaining === null)
|
|
62661
|
+
return;
|
|
62662
|
+
const left = this.remaining;
|
|
62663
|
+
this.remaining = null;
|
|
62664
|
+
this.arm(left);
|
|
62665
|
+
}
|
|
62666
|
+
/** Clears everything; the pending `fire` never runs. */
|
|
62667
|
+
cancel() {
|
|
62668
|
+
this.disarm();
|
|
62669
|
+
this.fire = null;
|
|
62670
|
+
this.remaining = null;
|
|
62671
|
+
}
|
|
62672
|
+
arm(ms) {
|
|
62673
|
+
const delay = Math.max(0, ms);
|
|
62674
|
+
this.deadline = this.now() + delay;
|
|
62675
|
+
this.handle = setTimeout(() => {
|
|
62676
|
+
const action = this.fire;
|
|
62677
|
+
this.handle = null;
|
|
62678
|
+
this.fire = null;
|
|
62679
|
+
this.remaining = null;
|
|
62680
|
+
action?.();
|
|
62681
|
+
}, delay);
|
|
62682
|
+
}
|
|
62683
|
+
disarm() {
|
|
62684
|
+
if (this.handle !== null)
|
|
62685
|
+
clearTimeout(this.handle);
|
|
62686
|
+
this.handle = null;
|
|
62687
|
+
}
|
|
62688
|
+
}
|
|
62689
|
+
|
|
62690
|
+
/**
|
|
62691
|
+
* The journey engine's persisted state (NAM-3229), split from the store so the engine can use
|
|
62692
|
+
* these without importing storage. Mirrors Apple's `JourneyRecord.swift` (NAM-3230).
|
|
62693
|
+
*/
|
|
62694
|
+
const emptyJourneyRecord = () => ({
|
|
62695
|
+
impressionsTotal: 0,
|
|
62696
|
+
sessionKey: null,
|
|
62697
|
+
sessionCount: 0,
|
|
62698
|
+
dayKey: null,
|
|
62699
|
+
dayCount: 0,
|
|
62700
|
+
lastDismissedAt: null,
|
|
62701
|
+
goalAchievedAt: null,
|
|
62702
|
+
enteredAt: null,
|
|
62703
|
+
});
|
|
62704
|
+
const emptyGlobalRecord = () => ({
|
|
62705
|
+
sessionOrdinal: 0,
|
|
62706
|
+
sessionKey: null,
|
|
62707
|
+
sessionCount: 0,
|
|
62708
|
+
dayKey: null,
|
|
62709
|
+
dayCount: 0,
|
|
62710
|
+
lastAnyImpressionAt: null,
|
|
62711
|
+
});
|
|
62712
|
+
const emptyJourneySnapshot = () => ({ journeys: {}, global: emptyGlobalRecord() });
|
|
62713
|
+
|
|
62714
|
+
const DAY_MS = 86_400_000;
|
|
62715
|
+
const PRIORITY_RANK = { high: 0, medium: 1, low: 2 };
|
|
62716
|
+
/** A counter stored under `storedKey` reads as 0 once the current key has moved on. */
|
|
62717
|
+
const countFor = (storedKey, count, currentKey) => storedKey === currentKey ? count : 0;
|
|
62718
|
+
/**
|
|
62719
|
+
* Pure journey eligibility and selection, plus the state transitions that follow (NAM-3229).
|
|
62720
|
+
* Mirrors Apple's `JourneyEngine` (NAM-3230), whose decision order the Android and Roku engines
|
|
62721
|
+
* share. The clock (`now`) and the time zone are injected; it imports no UI, adapter or storage
|
|
62722
|
+
* module, and never mutates the snapshot it is given.
|
|
62723
|
+
*/
|
|
62724
|
+
class JourneyEngine {
|
|
62725
|
+
/**
|
|
62726
|
+
* @param options.timeZone IANA zone that defines the local day for daily counts, read on every
|
|
62727
|
+
* call. When absent or returning undefined, the device's local day is used.
|
|
62728
|
+
*/
|
|
62729
|
+
constructor(options = {}) {
|
|
62730
|
+
this.options = options;
|
|
62731
|
+
}
|
|
62732
|
+
/**
|
|
62733
|
+
* The day of `now` as ISO `yyyy-MM-dd`. Always Gregorian with Latin digits: a Buddhist or
|
|
62734
|
+
* Japanese locale calendar must not change the year.
|
|
62735
|
+
*/
|
|
62736
|
+
dayKey(now) {
|
|
62737
|
+
const zone = this.options.timeZone?.();
|
|
62738
|
+
if (!zone) {
|
|
62739
|
+
return `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}-${String(now.getDate()).padStart(2, "0")}`;
|
|
62740
|
+
}
|
|
62741
|
+
const parts = new Intl.DateTimeFormat("en-US", {
|
|
62742
|
+
timeZone: zone,
|
|
62743
|
+
calendar: "gregory",
|
|
62744
|
+
numberingSystem: "latn",
|
|
62745
|
+
year: "numeric",
|
|
62746
|
+
month: "2-digit",
|
|
62747
|
+
day: "2-digit",
|
|
62748
|
+
}).formatToParts(now);
|
|
62749
|
+
const part = (type) => parts.find((p) => p.type === type)?.value ?? "";
|
|
62750
|
+
return `${part("year").padStart(4, "0")}-${part("month")}-${part("day")}`;
|
|
62751
|
+
}
|
|
62752
|
+
/**
|
|
62753
|
+
* Picks the journey to deliver for `event`, applying decision rows 1-10 in payload order.
|
|
62754
|
+
*
|
|
62755
|
+
* - Rules without journey metadata are skipped without a rejection.
|
|
62756
|
+
* - Only the FIRST rule per `journey_id` in payload order is considered. Later rules sharing an
|
|
62757
|
+
* id neither compete nor get a rejection entry, and the winner's id never appears in
|
|
62758
|
+
* `rejections`.
|
|
62759
|
+
* - The active window (row 2) is the rule's `not_before`/`expires_at`, strict at both ends.
|
|
62760
|
+
* - Priority ties (`high` > `medium` > `low`) are broken by payload order: the wire has no
|
|
62761
|
+
* created timestamp, so the server's order is the only deterministic signal.
|
|
62762
|
+
*/
|
|
62763
|
+
select({ event, journeys, snapshot, settings, isContentActive, now }) {
|
|
62764
|
+
const today = this.dayKey(now);
|
|
62765
|
+
const rejections = {};
|
|
62766
|
+
const survivors = [];
|
|
62767
|
+
const seen = new Set();
|
|
62768
|
+
for (const rule of journeys) {
|
|
62769
|
+
const journey = parseJourneyMetadata(rule.journey);
|
|
62770
|
+
if (!journey || seen.has(journey.journey_id))
|
|
62771
|
+
continue;
|
|
62772
|
+
seen.add(journey.journey_id);
|
|
62773
|
+
const rejection = this.perJourneyRejection(rule, journey, event, snapshot, settings, today, now);
|
|
62774
|
+
if (rejection)
|
|
62775
|
+
rejections[journey.journey_id] = rejection;
|
|
62776
|
+
else
|
|
62777
|
+
survivors.push({ rule, journey });
|
|
62778
|
+
}
|
|
62779
|
+
const global = this.globalRejection(snapshot.global, settings, isContentActive, today, now);
|
|
62780
|
+
if (global) {
|
|
62781
|
+
for (const s of survivors)
|
|
62782
|
+
rejections[s.journey.journey_id] = global;
|
|
62783
|
+
return { winner: null, rejections };
|
|
62784
|
+
}
|
|
62785
|
+
// The first survivor of the best rank wins, which is the payload-order tiebreak.
|
|
62786
|
+
let winner = null;
|
|
62787
|
+
for (const s of survivors) {
|
|
62788
|
+
if (!winner || PRIORITY_RANK[s.journey.priority] < PRIORITY_RANK[winner.journey.priority])
|
|
62789
|
+
winner = s;
|
|
62790
|
+
}
|
|
62791
|
+
for (const s of survivors)
|
|
62792
|
+
if (s !== winner)
|
|
62793
|
+
rejections[s.journey.journey_id] = "outranked";
|
|
62794
|
+
return { winner, rejections };
|
|
62795
|
+
}
|
|
62796
|
+
/** Rows 1-7. Returns null when the journey passes all of them. */
|
|
62797
|
+
perJourneyRejection(rule, journey, event, snapshot, settings, today, now) {
|
|
62798
|
+
if (!isSupportedJourney(journey) || journey.trigger.event_type !== event)
|
|
62799
|
+
return "triggerMismatch";
|
|
62800
|
+
if (!isCampaignRuleActive(rule.not_before, rule.expires_at, now))
|
|
62801
|
+
return "outsideWindow";
|
|
62802
|
+
const record = snapshot.journeys[journey.journey_id] ?? emptyJourneyRecord();
|
|
62803
|
+
const { delivery, goal } = journey;
|
|
62804
|
+
const nowMs = now.getTime();
|
|
62805
|
+
if (delivery.show_once) {
|
|
62806
|
+
if (record.impressionsTotal >= 1)
|
|
62807
|
+
return "shownOnce";
|
|
62808
|
+
}
|
|
62809
|
+
else {
|
|
62810
|
+
if (delivery.max_impressions_total != null && record.impressionsTotal >= delivery.max_impressions_total)
|
|
62811
|
+
return "totalCap";
|
|
62812
|
+
// A per-journey cap only tightens the global one.
|
|
62813
|
+
const sessionCap = Math.min(delivery.max_per_session ?? settings.max_per_session, settings.max_per_session);
|
|
62814
|
+
const dayCap = Math.min(delivery.max_per_day ?? settings.max_per_day, settings.max_per_day);
|
|
62815
|
+
if (countFor(record.sessionKey, record.sessionCount, snapshot.global.sessionOrdinal) >= sessionCap)
|
|
62816
|
+
return "sessionCap";
|
|
62817
|
+
if (countFor(record.dayKey, record.dayCount, today) >= dayCap)
|
|
62818
|
+
return "dayCap";
|
|
62819
|
+
const days = goal?.re_eligibility_days;
|
|
62820
|
+
if (days != null && record.lastDismissedAt != null && nowMs < record.lastDismissedAt + days * DAY_MS) {
|
|
62821
|
+
return "reEligibility";
|
|
62822
|
+
}
|
|
62823
|
+
}
|
|
62824
|
+
if (record.goalAchievedAt != null && (delivery.show_once || goal?.re_entry_after_goal !== true))
|
|
62825
|
+
return "goalAchieved";
|
|
62826
|
+
const timeout = goal?.timeout_days;
|
|
62827
|
+
if (timeout != null && record.enteredAt != null && nowMs >= record.enteredAt + timeout * DAY_MS)
|
|
62828
|
+
return "timedOut";
|
|
62829
|
+
return null;
|
|
62830
|
+
}
|
|
62831
|
+
/** Rows 8 and 9: device-wide checks, evaluated once per decision. */
|
|
62832
|
+
globalRejection(global, settings, isContentActive, today, now) {
|
|
62833
|
+
if (countFor(global.sessionKey, global.sessionCount, global.sessionOrdinal) >= settings.max_per_session)
|
|
62834
|
+
return "globalSessionCap";
|
|
62835
|
+
if (countFor(global.dayKey, global.dayCount, today) >= settings.max_per_day)
|
|
62836
|
+
return "globalDayCap";
|
|
62837
|
+
if (global.lastAnyImpressionAt != null && now.getTime() < global.lastAnyImpressionAt + settings.cooldown_seconds * 1000) {
|
|
62838
|
+
return "cooldown";
|
|
62839
|
+
}
|
|
62840
|
+
if (settings.suppress_when_active && isContentActive)
|
|
62841
|
+
return "suppressed";
|
|
62842
|
+
return null;
|
|
62843
|
+
}
|
|
62844
|
+
// Transitions
|
|
62845
|
+
/** Records a delivered journey impression: journey and global counters, entry and cooldown. */
|
|
62846
|
+
recordImpression(s, journeyId, now) {
|
|
62847
|
+
const today = this.dayKey(now);
|
|
62848
|
+
const ordinal = s.global.sessionOrdinal;
|
|
62849
|
+
const record = s.journeys[journeyId] ?? emptyJourneyRecord();
|
|
62850
|
+
const out = this.recordExternalImpression(s, now);
|
|
62851
|
+
return this.withRecord(out, journeyId, {
|
|
62852
|
+
...record,
|
|
62853
|
+
impressionsTotal: record.impressionsTotal + 1,
|
|
62854
|
+
sessionKey: ordinal,
|
|
62855
|
+
sessionCount: countFor(record.sessionKey, record.sessionCount, ordinal) + 1,
|
|
62856
|
+
dayKey: today,
|
|
62857
|
+
dayCount: countFor(record.dayKey, record.dayCount, today) + 1,
|
|
62858
|
+
enteredAt: record.enteredAt ?? now.getTime(),
|
|
62859
|
+
});
|
|
62860
|
+
}
|
|
62861
|
+
/** Records an impression shown outside Nami journeys: global counters and cooldown only. */
|
|
62862
|
+
recordExternalImpression(s, now) {
|
|
62863
|
+
const today = this.dayKey(now);
|
|
62864
|
+
const g = s.global;
|
|
62865
|
+
return {
|
|
62866
|
+
...s,
|
|
62867
|
+
global: {
|
|
62868
|
+
...g,
|
|
62869
|
+
sessionKey: g.sessionOrdinal,
|
|
62870
|
+
sessionCount: countFor(g.sessionKey, g.sessionCount, g.sessionOrdinal) + 1,
|
|
62871
|
+
dayKey: today,
|
|
62872
|
+
dayCount: countFor(g.dayKey, g.dayCount, today) + 1,
|
|
62873
|
+
lastAnyImpressionAt: now.getTime(),
|
|
62874
|
+
},
|
|
62875
|
+
};
|
|
62876
|
+
}
|
|
62877
|
+
/** Records a dismiss; it starts the re-eligibility window but never counts as an impression. */
|
|
62878
|
+
recordDismiss(s, journeyId, now) {
|
|
62879
|
+
return this.withRecord(s, journeyId, { ...(s.journeys[journeyId] ?? emptyJourneyRecord()), lastDismissedAt: now.getTime() });
|
|
62880
|
+
}
|
|
62881
|
+
/** Records goal completion and clears entry so a re-entry journey starts a fresh timeout. */
|
|
62882
|
+
recordGoal(s, journeyId, now) {
|
|
62883
|
+
return this.withRecord(s, journeyId, {
|
|
62884
|
+
...(s.journeys[journeyId] ?? emptyJourneyRecord()),
|
|
62885
|
+
goalAchievedAt: now.getTime(),
|
|
62886
|
+
enteredAt: null,
|
|
62887
|
+
});
|
|
62888
|
+
}
|
|
62889
|
+
withRecord(s, journeyId, record) {
|
|
62890
|
+
return { ...s, journeys: { ...s.journeys, [journeyId]: record } };
|
|
62891
|
+
}
|
|
62892
|
+
}
|
|
62893
|
+
|
|
62894
|
+
const isObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
62895
|
+
const numberOrNull = (value) => typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
62896
|
+
const stringOrNull = (value) => (typeof value === "string" ? value : null);
|
|
62897
|
+
// Each field falls back to its default when missing or mistyped, so a partial blob still loads.
|
|
62898
|
+
function decodeRecord(raw) {
|
|
62899
|
+
if (!isObject(raw))
|
|
62900
|
+
return emptyJourneyRecord();
|
|
62901
|
+
return {
|
|
62902
|
+
impressionsTotal: numberOrNull(raw.impressionsTotal) ?? 0,
|
|
62903
|
+
sessionKey: numberOrNull(raw.sessionKey),
|
|
62904
|
+
sessionCount: numberOrNull(raw.sessionCount) ?? 0,
|
|
62905
|
+
dayKey: stringOrNull(raw.dayKey),
|
|
62906
|
+
dayCount: numberOrNull(raw.dayCount) ?? 0,
|
|
62907
|
+
lastDismissedAt: numberOrNull(raw.lastDismissedAt),
|
|
62908
|
+
goalAchievedAt: numberOrNull(raw.goalAchievedAt),
|
|
62909
|
+
enteredAt: numberOrNull(raw.enteredAt),
|
|
62910
|
+
};
|
|
62911
|
+
}
|
|
62912
|
+
function decodeGlobal(raw) {
|
|
62913
|
+
if (!isObject(raw))
|
|
62914
|
+
return emptyGlobalRecord();
|
|
62915
|
+
return {
|
|
62916
|
+
sessionOrdinal: numberOrNull(raw.sessionOrdinal) ?? 0,
|
|
62917
|
+
sessionKey: numberOrNull(raw.sessionKey),
|
|
62918
|
+
sessionCount: numberOrNull(raw.sessionCount) ?? 0,
|
|
62919
|
+
dayKey: stringOrNull(raw.dayKey),
|
|
62920
|
+
dayCount: numberOrNull(raw.dayCount) ?? 0,
|
|
62921
|
+
lastAnyImpressionAt: numberOrNull(raw.lastAnyImpressionAt),
|
|
62922
|
+
};
|
|
62923
|
+
}
|
|
62924
|
+
/** A stored blob as a snapshot. A missing, corrupt or wrongly shaped blob reads as empty. */
|
|
62925
|
+
function decodeSnapshot(raw) {
|
|
62926
|
+
if (!isObject(raw) || !isObject(raw.journeys))
|
|
62927
|
+
return emptyJourneySnapshot();
|
|
62928
|
+
const journeys = {};
|
|
62929
|
+
for (const [id, record] of Object.entries(raw.journeys))
|
|
62930
|
+
journeys[id] = decodeRecord(record);
|
|
62931
|
+
return { journeys, global: decodeGlobal(raw.global) };
|
|
62932
|
+
}
|
|
62933
|
+
/**
|
|
62934
|
+
* Persists the journey engine's counters as one JSON blob under `nami_journey_rule_state`. The
|
|
62935
|
+
* key is in `NAMI_STORAGE_KEYS`, so `storageService.clearAll()` wipes it. There is no in-memory
|
|
62936
|
+
* cache: every read goes to storage, so a wipe can never leave stale counters behind.
|
|
62937
|
+
*/
|
|
62938
|
+
const journeyStateStore = {
|
|
62939
|
+
snapshot() {
|
|
62940
|
+
return decodeSnapshot(storageService.getJourneyRuleState());
|
|
62941
|
+
},
|
|
62942
|
+
/** Applies `transform` to the stored snapshot, persists the result and returns it. */
|
|
62943
|
+
update(transform) {
|
|
62944
|
+
const updated = transform(this.snapshot());
|
|
62945
|
+
storageService.setJourneyRuleState(updated);
|
|
62946
|
+
return updated;
|
|
62947
|
+
},
|
|
62948
|
+
/** Starts a new local session: bumps the ordinal, which re-keys per-session counts to 0. */
|
|
62949
|
+
startSession() {
|
|
62950
|
+
this.update((s) => ({ ...s, global: { ...s.global, sessionOrdinal: s.global.sessionOrdinal + 1 } }));
|
|
62951
|
+
},
|
|
62952
|
+
/** Drops all journey state. */
|
|
62953
|
+
clear() {
|
|
62954
|
+
storageService.removeJourneyRuleState();
|
|
62955
|
+
},
|
|
62956
|
+
};
|
|
62957
|
+
|
|
62958
|
+
/**
|
|
62959
|
+
* Two-slot, session-scoped store of the most recent launches.
|
|
62960
|
+
*
|
|
62961
|
+
* On `record(rec)` the existing `current` slot is demoted to `previous` and
|
|
62962
|
+
* `rec` becomes the new `current`. Flow conditions read `previous`, which
|
|
62963
|
+
* structurally excludes the placement that is launching right now (the PPO
|
|
62964
|
+
* requirement): the first launch in a session leaves `previous` undefined.
|
|
62965
|
+
*
|
|
62966
|
+
* State is in-memory only and cleared via `reset()` (wired into `Nami.reset()`).
|
|
62967
|
+
*/
|
|
62968
|
+
class LastLaunchStore {
|
|
62969
|
+
static get shared() {
|
|
62970
|
+
if (!this._shared)
|
|
62971
|
+
this._shared = new LastLaunchStore();
|
|
62972
|
+
return this._shared;
|
|
62973
|
+
}
|
|
62974
|
+
/** The Last Launch — the launch immediately before the current one. */
|
|
62975
|
+
get previous() {
|
|
62976
|
+
return this._previous;
|
|
62977
|
+
}
|
|
62978
|
+
/** Demote `current` → `previous`, then set `current` to `rec`. */
|
|
62979
|
+
record(rec) {
|
|
62980
|
+
this._previous = this._current;
|
|
62981
|
+
this._current = rec;
|
|
62982
|
+
}
|
|
62983
|
+
/** Clear both slots. */
|
|
62984
|
+
reset() {
|
|
62985
|
+
this._current = undefined;
|
|
62986
|
+
this._previous = undefined;
|
|
62987
|
+
}
|
|
62988
|
+
}
|
|
62989
|
+
|
|
62990
|
+
// `NamiFlowManager` imports the `nami` module back, so importing it here would close a load-order
|
|
62991
|
+
// cycle (the one `Nami.reset()` sidesteps with a lazy import). It registers this probe when it
|
|
62992
|
+
// loads instead; until then no flow can be open.
|
|
62993
|
+
let flowOpenProbe = () => false;
|
|
62994
|
+
/** Called by `NamiFlowManager` when its module loads. */
|
|
62995
|
+
const registerFlowOpenProbe = (probe) => {
|
|
62996
|
+
flowOpenProbe = probe;
|
|
62997
|
+
};
|
|
62998
|
+
/** Whether a Nami paywall or flow is on screen: a journey never raises over one (NAM-3233). */
|
|
62999
|
+
const isNamiContentActive = () => PaywallState.providers.length > 0 || flowOpenProbe();
|
|
63000
|
+
/** Whether the app is visible, per the device adapter; visible when the adapter cannot tell. */
|
|
63001
|
+
const isAppVisible = () => getPlatformAdapters().device.isAppVisible?.() ?? true;
|
|
63002
|
+
/**
|
|
63003
|
+
* Raises a journey's campaign through the UI adapter, the way `NamiCampaignManager.launch` raises
|
|
63004
|
+
* a placement, and reports whether it was presented (NAM-3233). Mirrors Apple's
|
|
63005
|
+
* `JourneyCoordinator.deliverOnMain` + `raiseCampaign`.
|
|
63006
|
+
*
|
|
63007
|
+
* Re-checks right before the raise: no `Nami.reset()` since the fire (`stillValid`), no paywall or
|
|
63008
|
+
* flow open (whatever `suppress_when_active` says: two would stack), and the app visible. Then the
|
|
63009
|
+
* campaign's content must be there (every flow screen, or the paywall), and the adapter must be able
|
|
63010
|
+
* to present by itself (`presentCampaign`; Expo and the CLI cannot).
|
|
63011
|
+
*
|
|
63012
|
+
* Like a launch, it records the Last Launch store (with no placement label: a journey has no
|
|
63013
|
+
* `value`) and a fresh launch id, so pages, flows, impressions and the rule's segment behave as for
|
|
63014
|
+
* a placement.
|
|
63015
|
+
*/
|
|
63016
|
+
function presentJourneyCampaign(campaign, stillValid) {
|
|
63017
|
+
const { ui } = getPlatformAdapters();
|
|
63018
|
+
const skip = (reason) => {
|
|
63019
|
+
logger.debug(`Journey raise skipped for rule ${campaign.rule}: ${reason}`);
|
|
63020
|
+
return false;
|
|
63021
|
+
};
|
|
63022
|
+
if (!stillValid())
|
|
63023
|
+
return skip("Nami.reset() ran after it fired");
|
|
63024
|
+
if (isNamiContentActive())
|
|
63025
|
+
return skip("a paywall or flow is already open");
|
|
63026
|
+
if (!isAppVisible())
|
|
63027
|
+
return skip("the app is not visible");
|
|
63028
|
+
if (!ui.presentCampaign)
|
|
63029
|
+
return skip("this platform cannot present a journey");
|
|
63030
|
+
if (campaign.flow) {
|
|
63031
|
+
if (!campaign.flow.object || !hasAllPaywalls(campaign.flow.object.screens ?? [])) {
|
|
63032
|
+
return skip("its flow screens are not available");
|
|
63033
|
+
}
|
|
63034
|
+
}
|
|
63035
|
+
else if (!campaign.paywall || !getPaywall(campaign.paywall)) {
|
|
63036
|
+
return skip("its paywall is not available");
|
|
63037
|
+
}
|
|
63038
|
+
LastLaunchStore.shared.record({ placementLabel: "", tags: campaign.placement_tags ?? [], customAttributes: {} });
|
|
63039
|
+
storageService.setLaunchId(generateUUID());
|
|
63040
|
+
return ui.presentCampaign(campaign, { customAttributes: {} });
|
|
63041
|
+
}
|
|
63042
|
+
|
|
63043
|
+
const monotonicNow = () => typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now();
|
|
63044
|
+
const liveJourneyEnvironment = {
|
|
63045
|
+
now: () => new Date(),
|
|
63046
|
+
monotonicNow,
|
|
63047
|
+
journeys: journeyCampaigns,
|
|
63048
|
+
settings: getJourneySettings,
|
|
63049
|
+
contentActive: isNamiContentActive,
|
|
63050
|
+
isAppVisible,
|
|
63051
|
+
present: presentJourneyCampaign,
|
|
63052
|
+
deliveryDisabled: shouldDisableJourneyDelivery,
|
|
63053
|
+
installMarker: {
|
|
63054
|
+
get: () => storageService.getJourneyInstallEvaluated(),
|
|
63055
|
+
set: () => storageService.setJourneyInstallEvaluated(),
|
|
63056
|
+
},
|
|
63057
|
+
};
|
|
63058
|
+
const journeyIdOf = (rule) => parseJourneyMetadata(rule.journey)?.journey_id;
|
|
63059
|
+
/**
|
|
63060
|
+
* Drives journey delivery for the `app_install` and `session_start` triggers (NAM-3233):
|
|
63061
|
+
* evaluation, the single delay slot, the fire-time re-check, delivery and local impression
|
|
63062
|
+
* accounting. A port of Apple's `JourneyCoordinator` (NAM-3234) minus campaign-rule refresh,
|
|
63063
|
+
* which core gains in NAM-3236. JS is single-threaded, so there is no queue: the reset
|
|
63064
|
+
* generation alone keeps work from before a `Nami.reset()` off the fresh state.
|
|
63065
|
+
*
|
|
63066
|
+
* Guards on the way to a raise:
|
|
63067
|
+
* - A winner chosen while the app is hidden starts its delay paused, a 0-second delay included,
|
|
63068
|
+
* and only counts down once the app is visible again.
|
|
63069
|
+
* - When the delay ends, the winner is looked up again by `journey_id` in the CURRENT journey
|
|
63070
|
+
* rules and re-checked against fresh state. Absent (removed or paused by the server, or no
|
|
63071
|
+
* journeys left at all) means dropped: a failed fetch keeps the previous rules, so an empty
|
|
63072
|
+
* list really is empty.
|
|
63073
|
+
* - `present` re-checks reset, open content and visibility right before the raise.
|
|
63074
|
+
*/
|
|
63075
|
+
class JourneyCoordinator {
|
|
63076
|
+
constructor(env) {
|
|
63077
|
+
this.env = env;
|
|
63078
|
+
this.engine = new JourneyEngine();
|
|
63079
|
+
this.resetGeneration = 0;
|
|
63080
|
+
this.backgroundedAt = null;
|
|
63081
|
+
this.delay = new JourneyDelayTimer(env.monotonicNow);
|
|
63082
|
+
}
|
|
63083
|
+
/**
|
|
63084
|
+
* The current reset generation; {@link reset} bumps it. A startup pass reads it when it begins
|
|
63085
|
+
* and hands it back to {@link sessionStarted}.
|
|
63086
|
+
*/
|
|
63087
|
+
get generation() {
|
|
63088
|
+
return this.resetGeneration;
|
|
63089
|
+
}
|
|
63090
|
+
/** Whether a delay is pending but not counting down. */
|
|
63091
|
+
get isDelayPaused() {
|
|
63092
|
+
return this.delay.isPaused;
|
|
63093
|
+
}
|
|
63094
|
+
// Triggers and lifecycle
|
|
63095
|
+
/**
|
|
63096
|
+
* A new session began (configure, or a return after the session gap). Advances the journey
|
|
63097
|
+
* session ordinal and evaluates the triggers.
|
|
63098
|
+
*
|
|
63099
|
+
* @param generation the {@link generation} read when the startup pass began. A `Nami.reset()`
|
|
63100
|
+
* since then makes the call stale, and it is dropped whole: no ordinal bump, no evaluation,
|
|
63101
|
+
* no install marker. Omitted, nothing is checked.
|
|
63102
|
+
*/
|
|
63103
|
+
sessionStarted(isInstallCandidate, generation) {
|
|
63104
|
+
if (generation !== undefined && generation !== this.resetGeneration) {
|
|
63105
|
+
logger.debug("Journey session start dropped: Nami.reset() ran during the startup pass");
|
|
63106
|
+
return;
|
|
63107
|
+
}
|
|
63108
|
+
journeyStateStore.startSession();
|
|
63109
|
+
this.evaluate(isInstallCandidate);
|
|
63110
|
+
}
|
|
63111
|
+
/** Pauses the delay, keeping the remainder, and starts measuring the session gap. */
|
|
63112
|
+
appDidEnterBackground() {
|
|
63113
|
+
this.delay.pause();
|
|
63114
|
+
this.backgroundedAt ??= this.env.monotonicNow();
|
|
63115
|
+
}
|
|
63116
|
+
/**
|
|
63117
|
+
* Resumes the delay with the remaining time, including one held since a hidden start.
|
|
63118
|
+
*
|
|
63119
|
+
* @returns whether this return begins a new session: it came at least {@link SESSION_GAP_MS}
|
|
63120
|
+
* after the background (the session boundary Apple and Android share). A foreground with no
|
|
63121
|
+
* background recorded never does.
|
|
63122
|
+
*/
|
|
63123
|
+
appWillEnterForeground() {
|
|
63124
|
+
const hiddenAt = this.backgroundedAt;
|
|
63125
|
+
this.backgroundedAt = null;
|
|
63126
|
+
this.delay.resume();
|
|
63127
|
+
return hiddenAt !== null && this.env.monotonicNow() - hiddenAt >= SESSION_GAP_MS;
|
|
63128
|
+
}
|
|
63129
|
+
/** A placement campaign is about to be raised: a journey still waiting must not fire on top of it. */
|
|
63130
|
+
placementWillRaise() {
|
|
63131
|
+
this.delay.cancel();
|
|
63132
|
+
}
|
|
63133
|
+
/** Cancels the delay and invalidates in-flight work (`Nami.reset()`), before the storage wipe. */
|
|
63134
|
+
reset() {
|
|
63135
|
+
this.delay.cancel();
|
|
63136
|
+
this.backgroundedAt = null;
|
|
63137
|
+
this.resetGeneration += 1;
|
|
63138
|
+
}
|
|
63139
|
+
// Evaluation
|
|
63140
|
+
evaluate(isInstallCandidate) {
|
|
63141
|
+
let selected = null;
|
|
63142
|
+
if (isInstallCandidate && !this.env.installMarker.get()) {
|
|
63143
|
+
const winner = this.select("app_install");
|
|
63144
|
+
if (winner)
|
|
63145
|
+
selected = { event: "app_install", winner };
|
|
63146
|
+
}
|
|
63147
|
+
// The marker is set at the first evaluation whether or not a journey won.
|
|
63148
|
+
if (isInstallCandidate)
|
|
63149
|
+
this.env.installMarker.set();
|
|
63150
|
+
if (!selected) {
|
|
63151
|
+
const winner = this.select("session_start");
|
|
63152
|
+
if (winner)
|
|
63153
|
+
selected = { event: "session_start", winner };
|
|
63154
|
+
}
|
|
63155
|
+
if (!selected)
|
|
63156
|
+
return;
|
|
63157
|
+
const { event, winner } = selected;
|
|
63158
|
+
const journeyId = winner.journey.journey_id;
|
|
63159
|
+
if (this.delay.isPending) {
|
|
63160
|
+
logger.debug(`Journey ${journeyId} dropped: another journey is waiting for its delay`);
|
|
63161
|
+
return;
|
|
63162
|
+
}
|
|
63163
|
+
const ms = Math.max(0, winner.journey.delivery.delay_seconds * 1000);
|
|
63164
|
+
const fire = () => this.fire(event, journeyId);
|
|
63165
|
+
if (this.env.isAppVisible()) {
|
|
63166
|
+
this.delay.start(ms, fire);
|
|
63167
|
+
}
|
|
63168
|
+
else {
|
|
63169
|
+
logger.debug(`Journey ${journeyId} held: the app is not visible, its delay starts paused`);
|
|
63170
|
+
this.delay.startPaused(ms, fire);
|
|
63171
|
+
}
|
|
63172
|
+
}
|
|
63173
|
+
decide(event, journeys) {
|
|
63174
|
+
return this.engine.select({
|
|
63175
|
+
event,
|
|
63176
|
+
journeys,
|
|
63177
|
+
snapshot: journeyStateStore.snapshot(),
|
|
63178
|
+
settings: this.env.settings(),
|
|
63179
|
+
isContentActive: this.env.contentActive(),
|
|
63180
|
+
now: this.env.now(),
|
|
63181
|
+
});
|
|
63182
|
+
}
|
|
63183
|
+
select(event) {
|
|
63184
|
+
const { winner, rejections } = this.decide(event, this.env.journeys());
|
|
63185
|
+
if (!winner && Object.keys(rejections).length > 0) {
|
|
63186
|
+
logger.debug(`No ${event} journey selected: ${JSON.stringify(rejections)}`);
|
|
63187
|
+
}
|
|
63188
|
+
return winner;
|
|
63189
|
+
}
|
|
63190
|
+
/** The delay ended: re-resolve the winner, re-check it against fresh state, then deliver. */
|
|
63191
|
+
fire(event, journeyId) {
|
|
63192
|
+
if (!this.env.isAppVisible()) {
|
|
63193
|
+
// The countdown ended out of sight: hold it again until the foreground instead of dropping it.
|
|
63194
|
+
this.delay.startPaused(0, () => this.fire(event, journeyId));
|
|
63195
|
+
return;
|
|
63196
|
+
}
|
|
63197
|
+
const candidate = this.env.journeys().find((rule) => journeyIdOf(rule) === journeyId);
|
|
63198
|
+
if (!candidate) {
|
|
63199
|
+
logger.debug(`Journey ${journeyId} dropped at fire time: no longer in the journey rules`);
|
|
63200
|
+
return;
|
|
63201
|
+
}
|
|
63202
|
+
const { winner, rejections } = this.decide(event, [candidate]);
|
|
63203
|
+
if (!winner) {
|
|
63204
|
+
logger.debug(`Journey ${journeyId} dropped at fire time: ${rejections[journeyId]}`);
|
|
63205
|
+
return;
|
|
63206
|
+
}
|
|
63207
|
+
if (this.env.deliveryDisabled()) {
|
|
63208
|
+
logger.debug(`Journey ${journeyId} not delivered: disableJourneyDelivery is set`);
|
|
63209
|
+
return;
|
|
63210
|
+
}
|
|
63211
|
+
const generation = this.resetGeneration;
|
|
63212
|
+
const stillValid = () => this.resetGeneration === generation;
|
|
63213
|
+
if (!this.env.present(winner.rule, stillValid) || !stillValid())
|
|
63214
|
+
return;
|
|
63215
|
+
journeyStateStore.update((s) => this.engine.recordImpression(s, journeyId, this.env.now()));
|
|
63216
|
+
}
|
|
63217
|
+
}
|
|
63218
|
+
JourneyCoordinator.shared = new JourneyCoordinator(liveJourneyEnvironment);
|
|
63219
|
+
|
|
62580
63220
|
class NamiRefs {
|
|
62581
63221
|
constructor() {
|
|
62582
63222
|
// In-memory flag for device registration failures (not persisted)
|
|
@@ -62592,6 +63232,9 @@ class NamiRefs {
|
|
|
62592
63232
|
this.lastConfig = null;
|
|
62593
63233
|
this.lastStartupRunAt = 0;
|
|
62594
63234
|
this.startupInFlight = false;
|
|
63235
|
+
// The startup rules fetch, settled either way. With an initial config `configure()` does not
|
|
63236
|
+
// wait for it, but the journey session does, so it evaluates the fetched rules (NAM-3233).
|
|
63237
|
+
this.startupFetch = Promise.resolve();
|
|
62595
63238
|
// Register this instance to avoid circular dependency issues
|
|
62596
63239
|
setNamiRefsInstance(this);
|
|
62597
63240
|
}
|
|
@@ -62618,7 +63261,8 @@ class NamiRefs {
|
|
|
62618
63261
|
return this.startupDisabled;
|
|
62619
63262
|
}
|
|
62620
63263
|
async init(config) {
|
|
62621
|
-
this.isFirstSession =
|
|
63264
|
+
this.isFirstSession = this.hasNoDevice();
|
|
63265
|
+
const journeyGeneration = JourneyCoordinator.shared.generation;
|
|
62622
63266
|
// Cleared per configure so a transient failure cannot disable a later run.
|
|
62623
63267
|
this.startupDisabled = false;
|
|
62624
63268
|
// `Nami.configure` has already normalised a missing or undecodable initialConfig to `{}`,
|
|
@@ -62631,12 +63275,42 @@ class NamiRefs {
|
|
|
62631
63275
|
try {
|
|
62632
63276
|
if (!(await this.fetchPlatformConfig()))
|
|
62633
63277
|
return;
|
|
62634
|
-
|
|
63278
|
+
try {
|
|
63279
|
+
await this.runStartupAfterConfig(config);
|
|
63280
|
+
}
|
|
63281
|
+
finally {
|
|
63282
|
+
// Even after a failed step: the rules may be cached (Apple starts it the same way).
|
|
63283
|
+
this.startJourneySession(this.isFirstSession, journeyGeneration);
|
|
63284
|
+
}
|
|
62635
63285
|
}
|
|
62636
63286
|
finally {
|
|
62637
63287
|
this.startupInFlight = false;
|
|
62638
63288
|
}
|
|
62639
63289
|
}
|
|
63290
|
+
/**
|
|
63291
|
+
* The app returned to the foreground. A return at least `SESSION_GAP_MS` after the background
|
|
63292
|
+
* starts a new journey session once the {@link resume} config refresh is done, unless startup is
|
|
63293
|
+
* disabled or a configure is already running (it starts its own). Every return runs `resume()`.
|
|
63294
|
+
*/
|
|
63295
|
+
async foreground() {
|
|
63296
|
+
const journeys = JourneyCoordinator.shared;
|
|
63297
|
+
const newSession = journeys.appWillEnterForeground();
|
|
63298
|
+
const startNewSession = newSession && this.lastConfig !== null && !this.startupInFlight;
|
|
63299
|
+
const generation = journeys.generation;
|
|
63300
|
+
const isInstallCandidate = this.hasNoDevice();
|
|
63301
|
+
await this.resume();
|
|
63302
|
+
if (startNewSession && !this.startupDisabled) {
|
|
63303
|
+
this.startJourneySession(isInstallCandidate, generation);
|
|
63304
|
+
}
|
|
63305
|
+
}
|
|
63306
|
+
/** No registered device id yet, so the device is a candidate for the `app_install` trigger. */
|
|
63307
|
+
hasNoDevice() {
|
|
63308
|
+
return storageService.getDevice() === null && !storageService.getAnonymousMode();
|
|
63309
|
+
}
|
|
63310
|
+
/** Starts the journey session once the startup rules fetch has settled, without blocking on it. */
|
|
63311
|
+
startJourneySession(isInstallCandidate, generation) {
|
|
63312
|
+
void this.startupFetch.then(() => JourneyCoordinator.shared.sessionStarted(isInstallCandidate, generation));
|
|
63313
|
+
}
|
|
62640
63314
|
/**
|
|
62641
63315
|
* The app returned to the foreground. Re-reads the platform config, as Apple's `.appResume`
|
|
62642
63316
|
* and Android's `onAppComesFromBackground` do, so a config change — the `sdk` kill switch
|
|
@@ -62779,6 +63453,7 @@ class NamiRefs {
|
|
|
62779
63453
|
}
|
|
62780
63454
|
throw error;
|
|
62781
63455
|
});
|
|
63456
|
+
this.startupFetch = promises.then(() => undefined, () => undefined);
|
|
62782
63457
|
if (!initialConfig.campaign_rules?.length || !initialConfig.paywalls?.length) {
|
|
62783
63458
|
await promises;
|
|
62784
63459
|
}
|
|
@@ -62950,39 +63625,7 @@ let NamiProfileManager$1 = class NamiProfileManager {
|
|
|
62950
63625
|
};
|
|
62951
63626
|
NamiProfileManager$1.instance = new NamiProfileManager$1();
|
|
62952
63627
|
|
|
62953
|
-
|
|
62954
|
-
* Two-slot, session-scoped store of the most recent launches.
|
|
62955
|
-
*
|
|
62956
|
-
* On `record(rec)` the existing `current` slot is demoted to `previous` and
|
|
62957
|
-
* `rec` becomes the new `current`. Flow conditions read `previous`, which
|
|
62958
|
-
* structurally excludes the placement that is launching right now (the PPO
|
|
62959
|
-
* requirement): the first launch in a session leaves `previous` undefined.
|
|
62960
|
-
*
|
|
62961
|
-
* State is in-memory only and cleared via `reset()` (wired into `Nami.reset()`).
|
|
62962
|
-
*/
|
|
62963
|
-
class LastLaunchStore {
|
|
62964
|
-
static get shared() {
|
|
62965
|
-
if (!this._shared)
|
|
62966
|
-
this._shared = new LastLaunchStore();
|
|
62967
|
-
return this._shared;
|
|
62968
|
-
}
|
|
62969
|
-
/** The Last Launch — the launch immediately before the current one. */
|
|
62970
|
-
get previous() {
|
|
62971
|
-
return this._previous;
|
|
62972
|
-
}
|
|
62973
|
-
/** Demote `current` → `previous`, then set `current` to `rec`. */
|
|
62974
|
-
record(rec) {
|
|
62975
|
-
this._previous = this._current;
|
|
62976
|
-
this._current = rec;
|
|
62977
|
-
}
|
|
62978
|
-
/** Clear both slots. */
|
|
62979
|
-
reset() {
|
|
62980
|
-
this._current = undefined;
|
|
62981
|
-
this._previous = undefined;
|
|
62982
|
-
}
|
|
62983
|
-
}
|
|
62984
|
-
|
|
62985
|
-
var _Nami_isInitialized, _Nami_removeForegroundListener;
|
|
63628
|
+
var _Nami_isInitialized, _Nami_removeForegroundListener, _Nami_removeBackgroundListener;
|
|
62986
63629
|
// NamiFlowManager is intentionally NOT imported at top level — it
|
|
62987
63630
|
// transitively imports back to this module (`Nami` for logging), and
|
|
62988
63631
|
// pulling it in here causes a load-order cycle that breaks tests
|
|
@@ -62991,6 +63634,7 @@ class Nami {
|
|
|
62991
63634
|
constructor() {
|
|
62992
63635
|
_Nami_isInitialized.set(this, false);
|
|
62993
63636
|
_Nami_removeForegroundListener.set(this, null);
|
|
63637
|
+
_Nami_removeBackgroundListener.set(this, null);
|
|
62994
63638
|
this.maxLogging = false;
|
|
62995
63639
|
}
|
|
62996
63640
|
get isInitialized() {
|
|
@@ -63014,7 +63658,9 @@ class Nami {
|
|
|
63014
63658
|
* integrating app, consistent with the Apple and Android SDKs.
|
|
63015
63659
|
*/
|
|
63016
63660
|
static async reset() {
|
|
63017
|
-
var _a;
|
|
63661
|
+
var _a, _b;
|
|
63662
|
+
// Before the wipe, so a pending journey can neither fire nor record into the fresh store.
|
|
63663
|
+
JourneyCoordinator.shared.reset();
|
|
63018
63664
|
storageService.clearAll();
|
|
63019
63665
|
// The per-URL paywall body cache keeps its own prefixed keys and an in-memory index (NAM-4357).
|
|
63020
63666
|
paywallBodyCache.purgeAll();
|
|
@@ -63022,6 +63668,8 @@ class Nami {
|
|
|
63022
63668
|
__classPrivateFieldSet(Nami.instance, _Nami_isInitialized, false, "f");
|
|
63023
63669
|
__classPrivateFieldGet((_a = Nami.instance), _Nami_removeForegroundListener, "f")?.call(_a);
|
|
63024
63670
|
__classPrivateFieldSet(Nami.instance, _Nami_removeForegroundListener, null, "f");
|
|
63671
|
+
__classPrivateFieldGet((_b = Nami.instance), _Nami_removeBackgroundListener, "f")?.call(_b);
|
|
63672
|
+
__classPrivateFieldSet(Nami.instance, _Nami_removeBackgroundListener, null, "f");
|
|
63025
63673
|
NamiRefs.instance.clearResumeState();
|
|
63026
63674
|
// In-memory singleton state that previously survived reset and
|
|
63027
63675
|
// bled into the next configure() cycle.
|
|
@@ -63086,16 +63734,24 @@ class Nami {
|
|
|
63086
63734
|
config.initialConfig = {};
|
|
63087
63735
|
}
|
|
63088
63736
|
/**
|
|
63089
|
-
* Subscribes
|
|
63090
|
-
*
|
|
63737
|
+
* Subscribes to the host's foreground and background signals, once per process — a
|
|
63738
|
+
* reconfigure must not stack a second listener (the Android defect NAM-4541 fixed).
|
|
63091
63739
|
* Registered before startup runs; `resume()` itself skips while startup is in flight.
|
|
63740
|
+
*
|
|
63741
|
+
* A foreground runs the resume config refresh and, after the session gap, a journey session
|
|
63742
|
+
* start (`NamiRefs.foreground`); a background pauses a pending journey and starts the gap
|
|
63743
|
+
* (NAM-3233).
|
|
63092
63744
|
*/
|
|
63093
63745
|
listenForAppForeground() {
|
|
63094
|
-
|
|
63095
|
-
|
|
63096
|
-
|
|
63097
|
-
|
|
63098
|
-
|
|
63746
|
+
const device = getPlatformAdapters().device;
|
|
63747
|
+
if (!__classPrivateFieldGet(this, _Nami_removeForegroundListener, "f")) {
|
|
63748
|
+
__classPrivateFieldSet(this, _Nami_removeForegroundListener, device.onAppForeground?.(() => {
|
|
63749
|
+
void NamiRefs.instance.foreground();
|
|
63750
|
+
}) ?? null, "f");
|
|
63751
|
+
}
|
|
63752
|
+
if (!__classPrivateFieldGet(this, _Nami_removeBackgroundListener, "f")) {
|
|
63753
|
+
__classPrivateFieldSet(this, _Nami_removeBackgroundListener, device.onAppBackground?.(() => JourneyCoordinator.shared.appDidEnterBackground()) ?? null, "f");
|
|
63754
|
+
}
|
|
63099
63755
|
}
|
|
63100
63756
|
async initializeSDK(config) {
|
|
63101
63757
|
let fullReconfig = false;
|
|
@@ -63149,7 +63805,7 @@ class Nami {
|
|
|
63149
63805
|
};
|
|
63150
63806
|
}
|
|
63151
63807
|
}
|
|
63152
|
-
_Nami_isInitialized = new WeakMap(), _Nami_removeForegroundListener = new WeakMap();
|
|
63808
|
+
_Nami_isInitialized = new WeakMap(), _Nami_removeForegroundListener = new WeakMap(), _Nami_removeBackgroundListener = new WeakMap();
|
|
63153
63809
|
Nami.instance = new Nami();
|
|
63154
63810
|
|
|
63155
63811
|
const VALID_STYLES = new Set(['fullscreen', 'sheet', 'compact_sheet', 'modal']);
|
|
@@ -64721,6 +65377,8 @@ let NamiFlowManager$2 = class NamiFlowManager {
|
|
|
64721
65377
|
this.flowOpen = false;
|
|
64722
65378
|
this.currentFlow = undefined;
|
|
64723
65379
|
this.lastAnimatedFlowProgress.clear();
|
|
65380
|
+
// Lets an adapter tear down a presentation it owns, e.g. a web journey (NAM-3233).
|
|
65381
|
+
getPlatformAdapters().ui.onFlowFinished?.();
|
|
64724
65382
|
}
|
|
64725
65383
|
/**
|
|
64726
65384
|
* Gets the last animated progress for a component by ID
|
|
@@ -64973,6 +65631,8 @@ let NamiFlowManager$2 = class NamiFlowManager {
|
|
|
64973
65631
|
return this.instance.getCurrentFlowStepAutoAdvanceDelay();
|
|
64974
65632
|
}
|
|
64975
65633
|
};
|
|
65634
|
+
// Journey delivery reads whether a flow is open without importing this module (NAM-3233).
|
|
65635
|
+
registerFlowOpenProbe(() => NamiFlowManager$2.isFlowOpen());
|
|
64976
65636
|
|
|
64977
65637
|
var NamiFlowManager$3 = /*#__PURE__*/Object.freeze({
|
|
64978
65638
|
__proto__: null,
|
|
@@ -66616,6 +67276,9 @@ let NamiCampaignManager$2 = class NamiCampaignManager {
|
|
|
66616
67276
|
customAttributes: (context?.customAttributes ?? {}),
|
|
66617
67277
|
});
|
|
66618
67278
|
}
|
|
67279
|
+
// A journey still waiting for its delay must not fire on top of this placement (NAM-3233).
|
|
67280
|
+
if (!campaignNotFound)
|
|
67281
|
+
JourneyCoordinator.shared.placementWillRaise();
|
|
66619
67282
|
const component = getPlatformAdapters().ui.createPaywall(type, value, context);
|
|
66620
67283
|
// Generate and store launch ID for successful campaign launch
|
|
66621
67284
|
const launchId = generateUUID();
|
|
@@ -67313,4 +67976,4 @@ var internal = /*#__PURE__*/Object.freeze({
|
|
|
67313
67976
|
NamiPurchaseManager: NamiPurchaseManager
|
|
67314
67977
|
});
|
|
67315
67978
|
|
|
67316
|
-
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, 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 };
|
|
67979
|
+
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, DISABLE_JOURNEY_DELIVERY, 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_INSTALL_EVALUATED, 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_GAP_MS, 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 };
|