@namiml/sdk-core 3.4.12-dev.202609251647 → 3.4.12-dev.202609252349

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,7 +106,7 @@ const {
106
106
  // version — stamped by scripts/version.sh
107
107
  NAMI_SDK_VERSION: exports.NAMI_SDK_VERSION = "3.4.12",
108
108
  // full package version including dev suffix — stamped by scripts/version.sh
109
- NAMI_SDK_PACKAGE_VERSION: exports.NAMI_SDK_PACKAGE_VERSION = "3.4.12-dev.202609251647",
109
+ NAMI_SDK_PACKAGE_VERSION: exports.NAMI_SDK_PACKAGE_VERSION = "3.4.12-dev.202609252349",
110
110
  // environments
111
111
  PRODUCTION: exports.PRODUCTION = "production", DEVELOPMENT: exports.DEVELOPMENT = "development",
112
112
  // error messages
@@ -116,7 +116,19 @@ AUTH_DEVICE: exports.AUTH_DEVICE = "nami_auth_device", NAMI_CONFIGURATION: expor
116
116
  // API settings
117
117
  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
118
  // // extended client info
119
- EXTENDED_CLIENT_INFO_PREFIX: exports.EXTENDED_CLIENT_INFO_PREFIX = "extendedClientInfo", EXTENDED_CLIENT_INFO_DELIMITER: exports.EXTENDED_CLIENT_INFO_DELIMITER = ":", VALIDATE_PRODUCT_GROUPS: exports.VALIDATE_PRODUCT_GROUPS = "validateProductGroups", LOG_HTTP_REQUESTS: exports.LOG_HTTP_REQUESTS = "logHTTPRequests", LOG_HTTP_TRAFFIC: exports.LOG_HTTP_TRAFFIC = "logHTTPTraffic", API_TRAFFIC_OBSERVER: exports.API_TRAFFIC_OBSERVER = "apiTrafficObserver", STARTUP_TELEMETRY: exports.STARTUP_TELEMETRY = "startupTelemetry", EXTENDED_PLATFORM: exports.EXTENDED_PLATFORM = "extended-platform", EXTENDED_PLATFORM_VERSION: exports.EXTENDED_PLATFORM_VERSION = "extended-platform-version", API_MAX_CALLS_LIMIT: exports.API_MAX_CALLS_LIMIT = 2, API_RETRY_DELAY_SEC: exports.API_RETRY_DELAY_SEC = 2, API_TIMEOUT_LIMIT: exports.API_TIMEOUT_LIMIT = 20000, DEVICE_API_TIMEOUT_LIMIT: exports.DEVICE_API_TIMEOUT_LIMIT = 2000,
119
+ EXTENDED_CLIENT_INFO_PREFIX: exports.EXTENDED_CLIENT_INFO_PREFIX = "extendedClientInfo", EXTENDED_CLIENT_INFO_DELIMITER: exports.EXTENDED_CLIENT_INFO_DELIMITER = ":", VALIDATE_PRODUCT_GROUPS: exports.VALIDATE_PRODUCT_GROUPS = "validateProductGroups", LOG_HTTP_REQUESTS: exports.LOG_HTTP_REQUESTS = "logHTTPRequests", LOG_HTTP_TRAFFIC: exports.LOG_HTTP_TRAFFIC = "logHTTPTraffic", API_TRAFFIC_OBSERVER: exports.API_TRAFFIC_OBSERVER = "apiTrafficObserver", STARTUP_TELEMETRY: exports.STARTUP_TELEMETRY = "startupTelemetry",
120
+ // Test hook: every platform-config fetch attempt fails before the request leaves.
121
+ // Same namiCommand as Apple's and Android's, so the shared first-run flows drive it.
122
+ SIMULATE_CONFIG_FETCH_FAILURE: exports.SIMULATE_CONFIG_FETCH_FAILURE = "simulateConfigFetchFailure", EXTENDED_PLATFORM: exports.EXTENDED_PLATFORM = "extended-platform", EXTENDED_PLATFORM_VERSION: exports.EXTENDED_PLATFORM_VERSION = "extended-platform-version", API_MAX_CALLS_LIMIT: exports.API_MAX_CALLS_LIMIT = 2, API_RETRY_DELAY_SEC: exports.API_RETRY_DELAY_SEC = 2, API_TIMEOUT_LIMIT: exports.API_TIMEOUT_LIMIT = 20000, DEVICE_API_TIMEOUT_LIMIT: exports.DEVICE_API_TIMEOUT_LIMIT = 2000,
123
+ // Platform-config attempts when no initial config was supplied (Android's
124
+ // CONFIG_MAX_ATTEMPTS; Apple's retry(maxAttempts: 3)).
125
+ CONFIG_MAX_ATTEMPTS: exports.CONFIG_MAX_ATTEMPTS = 3,
126
+ // Per-attempt timeout for GET config/. Android's config client allows 5 s to connect plus 5 s
127
+ // to read (Apple allows 30 s); three attempts keep a total outage near 30 s.
128
+ CONFIG_API_TIMEOUT_LIMIT: exports.CONFIG_API_TIMEOUT_LIMIT = 10000,
129
+ // A foreground within this long of the last launch/resume run is skipped — Apple's
130
+ // NamiStartupCoordinator.shouldRunResume window.
131
+ RESUME_THROTTLE_MS: exports.RESUME_THROTTLE_MS = 30000,
120
132
  // status codes
121
133
  STATUS_SUCCESS: exports.STATUS_SUCCESS = 200, STATUS_BAD_REQUEST: exports.STATUS_BAD_REQUEST = 400, STATUS_NOT_FOUND: exports.STATUS_NOT_FOUND = 404, STATUS_CONFLICT: exports.STATUS_CONFLICT = 409, STATUS_INTERNAL_SERVER_ERROR: exports.STATUS_INTERNAL_SERVER_ERROR = 500,
122
134
  // configuration states
@@ -58033,27 +58045,125 @@ class PaywallRepository {
58033
58045
  PaywallRepository.instance = new PaywallRepository();
58034
58046
 
58035
58047
  class ConfigRepository {
58048
+ /**
58049
+ * One `GET config/` attempt. Persists the config on success and throws on any failure —
58050
+ * a non-2xx or a transport error alike. `withRetry`'s own retries are turned off, so the
58051
+ * attempt count belongs to {@link fetchConfigForStartup} alone.
58052
+ */
58036
58053
  async fetchConfig() {
58037
- let data;
58038
- try {
58039
- data = await NamiAPI.instance.fetchAPI("config/");
58040
- ;
58041
- }
58042
- catch (error) {
58043
- data = storageService.getAppConfig(exports.API_CONFIG)
58044
- || storageService.getAppConfig(exports.INITIAL_APP_CONFIG);
58045
- if (!data) {
58046
- logger.error(handleErrors(error.status, "config/"));
58047
- throw error;
58048
- }
58054
+ // Thrown from *inside* the attempt on purpose, like Apple's and Android's: the ladder then
58055
+ // treats it exactly as a real 500, which is the whole point of the mock.
58056
+ if (storageService.getNamiConfig()?.namiCommands?.includes(exports.SIMULATE_CONFIG_FETCH_FAILURE)) {
58057
+ throw new RetryLimitExceededError(exports.STATUS_INTERNAL_SERVER_ERROR, exports.SIMULATE_CONFIG_FETCH_FAILURE);
58049
58058
  }
58050
- if (data) {
58059
+ const platformId = storageService.getNamiConfig()?.appPlatformID;
58060
+ const data = await NamiAPI.instance.fetchAPI("config/", exports.CONFIG_API_TIMEOUT_LIMIT, 0);
58061
+ // A late answer (the startup await gave up on it) must not overwrite another platform's
58062
+ // config after a reset or a platform-switching reconfigure.
58063
+ if (storageService.getNamiConfig()?.appPlatformID === platformId) {
58051
58064
  storageService.setAppConfig(exports.API_CONFIG, data);
58052
58065
  }
58066
+ else {
58067
+ logger.debug("Discarding an app config response for a platform that is no longer configured");
58068
+ }
58053
58069
  return data;
58054
58070
  }
58071
+ /**
58072
+ * The startup platform-config step, converged on Apple's `NamiPlatformConfigService.fetch` and
58073
+ * Android's `fetchAppConfigOnce` (NAM-4470, NAM-4589):
58074
+ *
58075
+ * | case | outcome |
58076
+ * |---|---|
58077
+ * | an initial config was supplied | NOT awaited: one attempt runs in the background and stores the config when it lands; usable |
58078
+ * | no initial config, success, capabilities include `sdk` | usable |
58079
+ * | no initial config, success, capabilities lack `sdk` | not usable |
58080
+ * | no initial config, failure | {@link CONFIG_MAX_ATTEMPTS} attempts of up to {@link CONFIG_API_TIMEOUT_LIMIT} each, then usable only if a config is cached (and grants `sdk`) |
58081
+ *
58082
+ * The two fallbacks are consulted at two different points, deliberately. An initial config
58083
+ * decides the case *before* any request: startup does not wait on the network at all, since the
58084
+ * initial config is what makes the SDK ready without it (Apple: "Initial config is available.
58085
+ * Skipping retry."). The config cached by a previous launch is consulted only *after* the
58086
+ * retries run out. A stored `INITIAL_APP_CONFIG` is not that cache.
58087
+ *
58088
+ * One check does apply with an initial config, because it costs no wait: a cached config that
58089
+ * withholds `sdk` still stops startup, as Apple's cache load re-applies `updateSdkFlags`.
58090
+ * Without it a kill switch could never reach an app that ships an initial config — the
58091
+ * background fetch lands after startup has already run, so it takes effect from the next launch.
58092
+ *
58093
+ * A `404` is treated as any other 4xx, matching Apple's effective behaviour (its dedicated 404
58094
+ * branch is unreachable, NAM-4471) and Android's.
58095
+ *
58096
+ * @param hasInitialConfig whether the host passed an `initialConfig` that decoded to content.
58097
+ */
58098
+ async fetchConfigForStartup(hasInitialConfig) {
58099
+ if (hasInitialConfig)
58100
+ return this.startupOnInitialConfig();
58101
+ const maxAttempts = exports.CONFIG_MAX_ATTEMPTS;
58102
+ let lastError;
58103
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
58104
+ if (attempt > 1) {
58105
+ logger.debug(`Retrying app config fetch (attempt ${attempt} of ${maxAttempts})`);
58106
+ }
58107
+ try {
58108
+ return outcomeOf(await this.fetchConfig());
58109
+ }
58110
+ catch (error) {
58111
+ lastError = error;
58112
+ }
58113
+ }
58114
+ const cached = storageService.getAppConfig(exports.API_CONFIG);
58115
+ if (cached) {
58116
+ // A cached kill switch still holds: Apple re-applies `updateSdkFlags` when it loads the
58117
+ // cached config, so one that withheld `sdk` keeps the SDK disabled through an outage.
58118
+ if (!grantsSdk(cached)) {
58119
+ return { usable: false, reason: "the cached app config does not grant the 'sdk' capability" };
58120
+ }
58121
+ logger.info(`App config fetch failed after ${maxAttempts} attempts - continuing on the cached config`);
58122
+ return { usable: true };
58123
+ }
58124
+ return {
58125
+ usable: false,
58126
+ reason: `app config fetch failed after ${maxAttempts} attempts (${describeFailure(lastError)}) ` +
58127
+ "and no initial or cached config is available",
58128
+ };
58129
+ }
58130
+ /**
58131
+ * The initial-config case: nothing is awaited. The cached kill switch is a storage read; the
58132
+ * config request is left running and persists its answer (`fetchConfig`) when it lands.
58133
+ */
58134
+ startupOnInitialConfig() {
58135
+ // Read before the request can land, so this launch is judged on what the last one stored.
58136
+ const cached = storageService.getAppConfig(exports.API_CONFIG);
58137
+ // Issued even when the cache holds a kill switch: that is how a LIFTED kill switch reaches
58138
+ // the next launch.
58139
+ this.fetchConfig().then((config) => {
58140
+ if (!grantsSdk(config)) {
58141
+ logger.warn("App config does not grant the 'sdk' capability - it takes effect from the next launch");
58142
+ }
58143
+ }, (error) => {
58144
+ logger.info(`App config fetch failed (${describeFailure(error)}). Initial config is available. Skipping retry.`);
58145
+ });
58146
+ if (cached && !grantsSdk(cached)) {
58147
+ return { usable: false, reason: "the cached app config does not grant the 'sdk' capability" };
58148
+ }
58149
+ return { usable: true };
58150
+ }
58055
58151
  }
58056
58152
  ConfigRepository.instance = new ConfigRepository();
58153
+ function outcomeOf(config) {
58154
+ return grantsSdk(config)
58155
+ ? { usable: true }
58156
+ : { usable: false, reason: "app config does not grant the 'sdk' capability" };
58157
+ }
58158
+ function grantsSdk(config) {
58159
+ return config.capabilities?.includes(exports.Capabilities.SDK) ?? false;
58160
+ }
58161
+ function describeFailure(error) {
58162
+ const status = error?.status;
58163
+ if (typeof status === "number")
58164
+ return `HTTP ${status}`;
58165
+ return error instanceof Error ? error.message : String(error);
58166
+ }
58057
58167
 
58058
58168
  // Global reference to avoid circular imports
58059
58169
  let namiRefsInstance = null;
@@ -61597,6 +61707,15 @@ class NamiRefs {
61597
61707
  this.inMemoryAnonymousMode = false;
61598
61708
  // Captured before device registration; true only on genuine fresh installs
61599
61709
  this.isFirstSession = false;
61710
+ // Set when the platform-config step leaves no usable config (see isStartupDisabled)
61711
+ this.startupDisabled = false;
61712
+ // Whether this configure() supplied an initial config (the config ladder's early exit)
61713
+ this.hasInitialConfig = false;
61714
+ // Resume bookkeeping: the configuration startup last ran with, when it (or a resume) last
61715
+ // ran, and whether one is running now.
61716
+ this.lastConfig = null;
61717
+ this.lastStartupRunAt = 0;
61718
+ this.startupInFlight = false;
61600
61719
  // Register this instance to avoid circular dependency issues
61601
61720
  setNamiRefsInstance(this);
61602
61721
  }
@@ -61614,13 +61733,101 @@ class NamiRefs {
61614
61733
  getIsFirstSession() {
61615
61734
  return this.isFirstSession;
61616
61735
  }
61736
+ /**
61737
+ * True when the platform-config step found no usable config and startup stopped after it —
61738
+ * the equivalent of Apple's `sdkEnabled = false` and Android's `NamiRefs.startupDisabled`.
61739
+ * Like both, it gates only the startup sequence, nothing else.
61740
+ */
61741
+ isStartupDisabled() {
61742
+ return this.startupDisabled;
61743
+ }
61617
61744
  async init(config) {
61618
61745
  this.isFirstSession = storageService.getDevice() === null && !storageService.getAnonymousMode();
61746
+ // Cleared per configure so a transient failure cannot disable a later run.
61747
+ this.startupDisabled = false;
61748
+ // `Nami.configure` has already normalised a missing or undecodable initialConfig to `{}`,
61749
+ // so "has content" is Apple's `initialConfig != nil`.
61750
+ this.hasInitialConfig = Object.keys(config.initialConfig ?? {}).length > 0;
61619
61751
  this.setInitialValues(config);
61620
- ConfigRepository.instance.fetchConfig();
61752
+ this.lastConfig = config;
61753
+ this.lastStartupRunAt = Date.now();
61754
+ this.startupInFlight = true;
61755
+ try {
61756
+ if (!(await this.fetchPlatformConfig()))
61757
+ return;
61758
+ await this.runStartupAfterConfig(config);
61759
+ }
61760
+ finally {
61761
+ this.startupInFlight = false;
61762
+ }
61763
+ }
61764
+ /**
61765
+ * The app returned to the foreground. Re-reads the platform config, as Apple's `.appResume`
61766
+ * and Android's `onAppComesFromBackground` do, so a config change — the `sdk` kill switch
61767
+ * included — reaches a long-lived tab or app without a cold start.
61768
+ *
61769
+ * - Skipped within {@link RESUME_THROTTLE_MS} of the last launch/resume, and while one is
61770
+ * still running.
61771
+ * - When the launch had **stopped** for want of a config and one is now usable, the rest of
61772
+ * startup runs — the steps an Apple resume re-runs because the launch never executed them.
61773
+ * - Otherwise this is a config refresh only. Apple and Android also re-run device, campaigns,
61774
+ * profile, products and the session start on resume; core does not yet, and has no
61775
+ * session end on background for a second session start to pair with (NAM-4623).
61776
+ */
61777
+ async resume() {
61778
+ if (!this.lastConfig || this.startupInFlight)
61779
+ return;
61780
+ if (Date.now() - this.lastStartupRunAt < exports.RESUME_THROTTLE_MS) {
61781
+ logger.debug("Resuming too quickly to call the service.");
61782
+ return;
61783
+ }
61784
+ const config = this.lastConfig;
61785
+ const wasStopped = this.startupDisabled;
61786
+ this.lastStartupRunAt = Date.now();
61787
+ this.startupInFlight = true;
61788
+ this.startupDisabled = false;
61789
+ try {
61790
+ if (!(await this.fetchPlatformConfig()) || !wasStopped)
61791
+ return;
61792
+ logger.info("Usable app config on resume - completing the startup that stopped at launch");
61793
+ await this.runStartupAfterConfig(config);
61794
+ }
61795
+ catch (error) {
61796
+ logger.warn(`Resume refresh failed: ${error}`);
61797
+ }
61798
+ finally {
61799
+ this.startupInFlight = false;
61800
+ }
61801
+ }
61802
+ /** Everything startup runs once the platform-config step has found a usable config. */
61803
+ async runStartupAfterConfig(config) {
61621
61804
  await this.initAndFetchRequiredData(config);
61622
61805
  SessionService.instance.startSession();
61623
61806
  }
61807
+ /** Forgets the configuration a resume would run against; called by `Nami.reset()`. */
61808
+ clearResumeState() {
61809
+ this.lastConfig = null;
61810
+ this.lastStartupRunAt = 0;
61811
+ this.startupDisabled = false;
61812
+ }
61813
+ /**
61814
+ * The platform-config startup step, ahead of device registration — the position it holds on
61815
+ * Apple (`.fetchPlatformConfig`) and Android (`fetchAppConfigOnce`) (NAM-4464, NAM-4589).
61816
+ * Without an initial config it is awaited, so everything downstream — `campaign_cache_ttl`
61817
+ * included — is evaluated against the config it leaves in storage. With one it resolves at
61818
+ * once and the request finishes in the background (see `fetchConfigForStartup`).
61819
+ *
61820
+ * @returns false when there is no usable config, in which case startup must stop.
61821
+ */
61822
+ async fetchPlatformConfig() {
61823
+ const outcome = await ConfigRepository.instance.fetchConfigForStartup(this.hasInitialConfig);
61824
+ if (!outcome.usable) {
61825
+ this.startupDisabled = true;
61826
+ logger.error(`Nami startup stopped: ${outcome.reason}`);
61827
+ return false;
61828
+ }
61829
+ return true;
61830
+ }
61624
61831
  setInitialValues(config) {
61625
61832
  const baseConfig = { ...config };
61626
61833
  delete baseConfig.initialConfig;
@@ -61881,7 +62088,7 @@ class LastLaunchStore {
61881
62088
  }
61882
62089
  }
61883
62090
 
61884
- var _Nami_isInitialized;
62091
+ var _Nami_isInitialized, _Nami_removeForegroundListener;
61885
62092
  // NamiFlowManager is intentionally NOT imported at top level — it
61886
62093
  // transitively imports back to this module (`Nami` for logging), and
61887
62094
  // pulling it in here causes a load-order cycle that breaks tests
@@ -61889,6 +62096,7 @@ var _Nami_isInitialized;
61889
62096
  class Nami {
61890
62097
  constructor() {
61891
62098
  _Nami_isInitialized.set(this, false);
62099
+ _Nami_removeForegroundListener.set(this, null);
61892
62100
  this.maxLogging = false;
61893
62101
  }
61894
62102
  get isInitialized() {
@@ -61912,9 +62120,13 @@ class Nami {
61912
62120
  * integrating app, consistent with the Apple and Android SDKs.
61913
62121
  */
61914
62122
  static async reset() {
62123
+ var _a;
61915
62124
  storageService.clearAll();
61916
62125
  clearInMemoryAnonymousMode();
61917
62126
  __classPrivateFieldSet(Nami.instance, _Nami_isInitialized, false, "f");
62127
+ __classPrivateFieldGet((_a = Nami.instance), _Nami_removeForegroundListener, "f")?.call(_a);
62128
+ __classPrivateFieldSet(Nami.instance, _Nami_removeForegroundListener, null, "f");
62129
+ NamiRefs.instance.clearResumeState();
61918
62130
  // In-memory singleton state that previously survived reset and
61919
62131
  // bled into the next configure() cycle.
61920
62132
  NamiProfileManager$1.instance.setExternalId(undefined);
@@ -61977,6 +62189,18 @@ class Nami {
61977
62189
  }
61978
62190
  config.initialConfig = {};
61979
62191
  }
62192
+ /**
62193
+ * Subscribes the resume config refresh to the host's foreground signal, once per process —
62194
+ * a reconfigure must not stack a second listener (the Android defect NAM-4541 fixed).
62195
+ * Registered before startup runs; `resume()` itself skips while startup is in flight.
62196
+ */
62197
+ listenForAppForeground() {
62198
+ if (__classPrivateFieldGet(this, _Nami_removeForegroundListener, "f"))
62199
+ return;
62200
+ __classPrivateFieldSet(this, _Nami_removeForegroundListener, getPlatformAdapters().device.onAppForeground?.(() => {
62201
+ void NamiRefs.instance.resume();
62202
+ }) ?? null, "f");
62203
+ }
61980
62204
  async initializeSDK(config) {
61981
62205
  let fullReconfig = false;
61982
62206
  let partialReconfig = false;
@@ -62017,6 +62241,7 @@ class Nami {
62017
62241
  // configure SDK with new config
62018
62242
  __classPrivateFieldSet(this, _Nami_isInitialized, true, "f");
62019
62243
  NamiAPI.configure(config);
62244
+ this.listenForAppForeground();
62020
62245
  await NamiRefs.instance.init(config);
62021
62246
  logger.info("SDK successfully initialized!");
62022
62247
  const state = partialReconfig || fullReconfig ? exports.RECONFIG_SUCCESS : exports.INITIAL_SUCCESS;
@@ -62028,7 +62253,7 @@ class Nami {
62028
62253
  };
62029
62254
  }
62030
62255
  }
62031
- _Nami_isInitialized = new WeakMap();
62256
+ _Nami_isInitialized = new WeakMap(), _Nami_removeForegroundListener = new WeakMap();
62032
62257
  Nami.instance = new Nami();
62033
62258
 
62034
62259
  const VALID_STYLES = new Set(['fullscreen', 'sheet', 'compact_sheet', 'modal']);
package/dist/index.d.ts CHANGED
@@ -65,6 +65,13 @@ interface IDeviceAdapter {
65
65
  generateUUID(): string;
66
66
  getScreenInfo(): ScreenInfo;
67
67
  getLanguage(): string;
68
+ /**
69
+ * Calls `listener` each time the app returns to the foreground — a browser tab becoming
70
+ * visible, a React Native app going `active` — and returns a function that stops listening.
71
+ * Drives core's resume config refresh (NAM-4589). Optional: a host with no foreground
72
+ * concept (the CLI) omits it and simply never resumes.
73
+ */
74
+ onAppForeground?(listener: () => void): () => void;
68
75
  }
69
76
 
70
77
  interface FormFactor {
@@ -2122,6 +2129,12 @@ declare class Nami {
2122
2129
  */
2123
2130
  static configure(options: NamiConfiguration): Promise<NamiConfigurationState>;
2124
2131
  private static setInitialConfig;
2132
+ /**
2133
+ * Subscribes the resume config refresh to the host's foreground signal, once per process —
2134
+ * a reconfigure must not stack a second listener (the Android defect NAM-4541 fixed).
2135
+ * Registered before startup runs; `resume()` itself skips while startup is in flight.
2136
+ */
2137
+ private listenForAppForeground;
2125
2138
  private initializeSDK;
2126
2139
  }
2127
2140
 
@@ -3183,12 +3196,16 @@ declare const LOG_HTTP_REQUESTS: string;
3183
3196
  declare const LOG_HTTP_TRAFFIC: string;
3184
3197
  declare const API_TRAFFIC_OBSERVER: string;
3185
3198
  declare const STARTUP_TELEMETRY: string;
3199
+ declare const SIMULATE_CONFIG_FETCH_FAILURE: string;
3186
3200
  declare const EXTENDED_PLATFORM: string;
3187
3201
  declare const EXTENDED_PLATFORM_VERSION: string;
3188
3202
  declare const API_MAX_CALLS_LIMIT: number;
3189
3203
  declare const API_RETRY_DELAY_SEC: number;
3190
3204
  declare const API_TIMEOUT_LIMIT: number;
3191
3205
  declare const DEVICE_API_TIMEOUT_LIMIT: number;
3206
+ declare const CONFIG_MAX_ATTEMPTS: number;
3207
+ declare const CONFIG_API_TIMEOUT_LIMIT: number;
3208
+ declare const RESUME_THROTTLE_MS: number;
3192
3209
  declare const STATUS_SUCCESS: number;
3193
3210
  declare const STATUS_BAD_REQUEST: number;
3194
3211
  declare const STATUS_NOT_FOUND: number;
@@ -3392,12 +3409,51 @@ declare class NamiRefs {
3392
3409
  static instance: NamiRefs;
3393
3410
  private inMemoryAnonymousMode;
3394
3411
  private isFirstSession;
3412
+ private startupDisabled;
3413
+ private hasInitialConfig;
3414
+ private lastConfig;
3415
+ private lastStartupRunAt;
3416
+ private startupInFlight;
3395
3417
  constructor();
3396
3418
  setInMemoryAnonymousMode(enabled: boolean): void;
3397
3419
  isInMemoryAnonymousMode(): boolean;
3398
3420
  isAnonymousMode(): boolean;
3399
3421
  getIsFirstSession(): boolean;
3422
+ /**
3423
+ * True when the platform-config step found no usable config and startup stopped after it —
3424
+ * the equivalent of Apple's `sdkEnabled = false` and Android's `NamiRefs.startupDisabled`.
3425
+ * Like both, it gates only the startup sequence, nothing else.
3426
+ */
3427
+ isStartupDisabled(): boolean;
3400
3428
  init(config: NamiConfiguration): Promise<void>;
3429
+ /**
3430
+ * The app returned to the foreground. Re-reads the platform config, as Apple's `.appResume`
3431
+ * and Android's `onAppComesFromBackground` do, so a config change — the `sdk` kill switch
3432
+ * included — reaches a long-lived tab or app without a cold start.
3433
+ *
3434
+ * - Skipped within {@link RESUME_THROTTLE_MS} of the last launch/resume, and while one is
3435
+ * still running.
3436
+ * - When the launch had **stopped** for want of a config and one is now usable, the rest of
3437
+ * startup runs — the steps an Apple resume re-runs because the launch never executed them.
3438
+ * - Otherwise this is a config refresh only. Apple and Android also re-run device, campaigns,
3439
+ * profile, products and the session start on resume; core does not yet, and has no
3440
+ * session end on background for a second session start to pair with (NAM-4623).
3441
+ */
3442
+ resume(): Promise<void>;
3443
+ /** Everything startup runs once the platform-config step has found a usable config. */
3444
+ private runStartupAfterConfig;
3445
+ /** Forgets the configuration a resume would run against; called by `Nami.reset()`. */
3446
+ clearResumeState(): void;
3447
+ /**
3448
+ * The platform-config startup step, ahead of device registration — the position it holds on
3449
+ * Apple (`.fetchPlatformConfig`) and Android (`fetchAppConfigOnce`) (NAM-4464, NAM-4589).
3450
+ * Without an initial config it is awaited, so everything downstream — `campaign_cache_ttl`
3451
+ * included — is evaluated against the config it leaves in storage. With one it resolves at
3452
+ * once and the request finishes in the background (see `fetchConfigForStartup`).
3453
+ *
3454
+ * @returns false when there is no usable config, in which case startup must stop.
3455
+ */
3456
+ private fetchPlatformConfig;
3401
3457
  private setInitialValues;
3402
3458
  private initAnonymousDevice;
3403
3459
  private initIdentifiedDevice;
@@ -3602,9 +3658,56 @@ declare class CampaignRuleRepository {
3602
3658
  private fallbackData;
3603
3659
  }
3604
3660
 
3661
+ /**
3662
+ * Whether startup may continue past the platform-config step; `reason` explains a `false`.
3663
+ * A plain shape rather than a discriminated union: core does not compile with
3664
+ * `strictNullChecks`, so a union on `usable` would not narrow.
3665
+ */
3666
+ type ConfigStartupOutcome = {
3667
+ usable: boolean;
3668
+ reason?: string;
3669
+ };
3605
3670
  declare class ConfigRepository {
3606
3671
  static instance: ConfigRepository;
3607
- fetchConfig(): Promise<IConfig | null>;
3672
+ /**
3673
+ * One `GET config/` attempt. Persists the config on success and throws on any failure —
3674
+ * a non-2xx or a transport error alike. `withRetry`'s own retries are turned off, so the
3675
+ * attempt count belongs to {@link fetchConfigForStartup} alone.
3676
+ */
3677
+ fetchConfig(): Promise<IConfig>;
3678
+ /**
3679
+ * The startup platform-config step, converged on Apple's `NamiPlatformConfigService.fetch` and
3680
+ * Android's `fetchAppConfigOnce` (NAM-4470, NAM-4589):
3681
+ *
3682
+ * | case | outcome |
3683
+ * |---|---|
3684
+ * | an initial config was supplied | NOT awaited: one attempt runs in the background and stores the config when it lands; usable |
3685
+ * | no initial config, success, capabilities include `sdk` | usable |
3686
+ * | no initial config, success, capabilities lack `sdk` | not usable |
3687
+ * | no initial config, failure | {@link CONFIG_MAX_ATTEMPTS} attempts of up to {@link CONFIG_API_TIMEOUT_LIMIT} each, then usable only if a config is cached (and grants `sdk`) |
3688
+ *
3689
+ * The two fallbacks are consulted at two different points, deliberately. An initial config
3690
+ * decides the case *before* any request: startup does not wait on the network at all, since the
3691
+ * initial config is what makes the SDK ready without it (Apple: "Initial config is available.
3692
+ * Skipping retry."). The config cached by a previous launch is consulted only *after* the
3693
+ * retries run out. A stored `INITIAL_APP_CONFIG` is not that cache.
3694
+ *
3695
+ * One check does apply with an initial config, because it costs no wait: a cached config that
3696
+ * withholds `sdk` still stops startup, as Apple's cache load re-applies `updateSdkFlags`.
3697
+ * Without it a kill switch could never reach an app that ships an initial config — the
3698
+ * background fetch lands after startup has already run, so it takes effect from the next launch.
3699
+ *
3700
+ * A `404` is treated as any other 4xx, matching Apple's effective behaviour (its dedicated 404
3701
+ * branch is unreachable, NAM-4471) and Android's.
3702
+ *
3703
+ * @param hasInitialConfig whether the host passed an `initialConfig` that decoded to content.
3704
+ */
3705
+ fetchConfigForStartup(hasInitialConfig: boolean): Promise<ConfigStartupOutcome>;
3706
+ /**
3707
+ * The initial-config case: nothing is awaited. The cached kill switch is a storage read; the
3708
+ * config request is left running and persists its answer (`fetchConfig`) when it lands.
3709
+ */
3710
+ private startupOnInitialConfig;
3608
3711
  }
3609
3712
 
3610
3713
  declare class CustomerJourneyRepository {
@@ -4117,5 +4220,5 @@ declare namespace internal {
4117
4220
  };
4118
4221
  }
4119
4222
 
4120
- 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_NOT_AVAILABLE, 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, 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, RetryLimitExceededError, SDKNotInitializedError, SDK_NOT_INITIALIZED, SERVER_NAMI_ENTITLEMENTS, SESSION_REQUIRED, SHOULD_SHOW_LOADING_INDICATOR, SKU_TEXT_REGEX, SMART_TEXT_PATTERN, STARTUP_TELEMETRY, STATUS_BAD_REQUEST, STATUS_CONFLICT, STATUS_INTERNAL_SERVER_ERROR, STATUS_NOT_FOUND, STATUS_SUCCESS, SessionService, SimpleEventTarget, StorageService, 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, extractStandardPricingPhases, 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, getTranslate, getUrlParams, handleErrors, hasAllPaywalls, hasCapability, hasPurchaseManagement, initialState, invokeHandler, isAnonymousMode, isInitialConfigCompressed, isNamiFlowCampaign, isSubscription, isValidISODate, isValidUrl, logger, mapAnonymousCampaigns, namiBuySKU, normalizeLaunchContext, parseToSemver, postConversion, productDetail, registerAPITrafficObserver, registerPlatformAdapters, registerPurchaseAdapter, resolveLoopSource, selectSegment, setActiveNamiEntitlements, shouldValidateProductGroups, skuItems, skuMapFromEntitlements, storageService, toDouble, toNamiEntitlements, toNamiSKU, tryParseB64Gzip, tryParseJson, updateRelatedSKUsForNamiEntitlement, uuidFromSplitPosition, validateForm, validateMinSDKVersion, validateTextInput };
4223
+ 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_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, 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, SKU_TEXT_REGEX, SMART_TEXT_PATTERN, STARTUP_TELEMETRY, STATUS_BAD_REQUEST, STATUS_CONFLICT, STATUS_INTERNAL_SERVER_ERROR, STATUS_NOT_FOUND, STATUS_SUCCESS, SessionService, SimpleEventTarget, StorageService, 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, extractStandardPricingPhases, 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, getTranslate, getUrlParams, handleErrors, hasAllPaywalls, hasCapability, hasPurchaseManagement, initialState, invokeHandler, isAnonymousMode, isInitialConfigCompressed, isNamiFlowCampaign, isSubscription, isValidISODate, isValidUrl, logger, mapAnonymousCampaigns, namiBuySKU, normalizeLaunchContext, parseToSemver, postConversion, productDetail, registerAPITrafficObserver, registerPlatformAdapters, registerPurchaseAdapter, resolveLoopSource, selectSegment, setActiveNamiEntitlements, shouldValidateProductGroups, skuItems, skuMapFromEntitlements, storageService, toDouble, toNamiEntitlements, toNamiSKU, tryParseB64Gzip, tryParseJson, updateRelatedSKUsForNamiEntitlement, uuidFromSplitPosition, validateForm, validateMinSDKVersion, validateTextInput };
4121
4224
  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, 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,7 +104,7 @@ const {
104
104
  // version — stamped by scripts/version.sh
105
105
  NAMI_SDK_VERSION = "3.4.12",
106
106
  // full package version including dev suffix — stamped by scripts/version.sh
107
- NAMI_SDK_PACKAGE_VERSION = "3.4.12-dev.202609251647",
107
+ NAMI_SDK_PACKAGE_VERSION = "3.4.12-dev.202609252349",
108
108
  // environments
109
109
  PRODUCTION = "production", DEVELOPMENT = "development",
110
110
  // error messages
@@ -114,7 +114,19 @@ AUTH_DEVICE = "nami_auth_device", NAMI_CONFIGURATION = "nami_configuration", NAM
114
114
  // API settings
115
115
  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
116
  // // extended client info
117
- EXTENDED_CLIENT_INFO_PREFIX = "extendedClientInfo", EXTENDED_CLIENT_INFO_DELIMITER = ":", VALIDATE_PRODUCT_GROUPS = "validateProductGroups", LOG_HTTP_REQUESTS = "logHTTPRequests", LOG_HTTP_TRAFFIC = "logHTTPTraffic", API_TRAFFIC_OBSERVER = "apiTrafficObserver", STARTUP_TELEMETRY = "startupTelemetry", EXTENDED_PLATFORM = "extended-platform", EXTENDED_PLATFORM_VERSION = "extended-platform-version", API_MAX_CALLS_LIMIT = 2, API_RETRY_DELAY_SEC = 2, API_TIMEOUT_LIMIT = 20000, DEVICE_API_TIMEOUT_LIMIT = 2000,
117
+ EXTENDED_CLIENT_INFO_PREFIX = "extendedClientInfo", EXTENDED_CLIENT_INFO_DELIMITER = ":", VALIDATE_PRODUCT_GROUPS = "validateProductGroups", LOG_HTTP_REQUESTS = "logHTTPRequests", LOG_HTTP_TRAFFIC = "logHTTPTraffic", API_TRAFFIC_OBSERVER = "apiTrafficObserver", STARTUP_TELEMETRY = "startupTelemetry",
118
+ // Test hook: every platform-config fetch attempt fails before the request leaves.
119
+ // Same namiCommand as Apple's and Android's, so the shared first-run flows drive it.
120
+ SIMULATE_CONFIG_FETCH_FAILURE = "simulateConfigFetchFailure", EXTENDED_PLATFORM = "extended-platform", EXTENDED_PLATFORM_VERSION = "extended-platform-version", API_MAX_CALLS_LIMIT = 2, API_RETRY_DELAY_SEC = 2, API_TIMEOUT_LIMIT = 20000, DEVICE_API_TIMEOUT_LIMIT = 2000,
121
+ // Platform-config attempts when no initial config was supplied (Android's
122
+ // CONFIG_MAX_ATTEMPTS; Apple's retry(maxAttempts: 3)).
123
+ CONFIG_MAX_ATTEMPTS = 3,
124
+ // Per-attempt timeout for GET config/. Android's config client allows 5 s to connect plus 5 s
125
+ // to read (Apple allows 30 s); three attempts keep a total outage near 30 s.
126
+ CONFIG_API_TIMEOUT_LIMIT = 10000,
127
+ // A foreground within this long of the last launch/resume run is skipped — Apple's
128
+ // NamiStartupCoordinator.shouldRunResume window.
129
+ RESUME_THROTTLE_MS = 30000,
118
130
  // status codes
119
131
  STATUS_SUCCESS = 200, STATUS_BAD_REQUEST = 400, STATUS_NOT_FOUND = 404, STATUS_CONFLICT = 409, STATUS_INTERNAL_SERVER_ERROR = 500,
120
132
  // configuration states
@@ -58031,27 +58043,125 @@ class PaywallRepository {
58031
58043
  PaywallRepository.instance = new PaywallRepository();
58032
58044
 
58033
58045
  class ConfigRepository {
58046
+ /**
58047
+ * One `GET config/` attempt. Persists the config on success and throws on any failure —
58048
+ * a non-2xx or a transport error alike. `withRetry`'s own retries are turned off, so the
58049
+ * attempt count belongs to {@link fetchConfigForStartup} alone.
58050
+ */
58034
58051
  async fetchConfig() {
58035
- let data;
58036
- try {
58037
- data = await NamiAPI.instance.fetchAPI("config/");
58038
- ;
58039
- }
58040
- catch (error) {
58041
- data = storageService.getAppConfig(API_CONFIG)
58042
- || storageService.getAppConfig(INITIAL_APP_CONFIG);
58043
- if (!data) {
58044
- logger.error(handleErrors(error.status, "config/"));
58045
- throw error;
58046
- }
58052
+ // Thrown from *inside* the attempt on purpose, like Apple's and Android's: the ladder then
58053
+ // treats it exactly as a real 500, which is the whole point of the mock.
58054
+ if (storageService.getNamiConfig()?.namiCommands?.includes(SIMULATE_CONFIG_FETCH_FAILURE)) {
58055
+ throw new RetryLimitExceededError(STATUS_INTERNAL_SERVER_ERROR, SIMULATE_CONFIG_FETCH_FAILURE);
58047
58056
  }
58048
- if (data) {
58057
+ const platformId = storageService.getNamiConfig()?.appPlatformID;
58058
+ const data = await NamiAPI.instance.fetchAPI("config/", CONFIG_API_TIMEOUT_LIMIT, 0);
58059
+ // A late answer (the startup await gave up on it) must not overwrite another platform's
58060
+ // config after a reset or a platform-switching reconfigure.
58061
+ if (storageService.getNamiConfig()?.appPlatformID === platformId) {
58049
58062
  storageService.setAppConfig(API_CONFIG, data);
58050
58063
  }
58064
+ else {
58065
+ logger.debug("Discarding an app config response for a platform that is no longer configured");
58066
+ }
58051
58067
  return data;
58052
58068
  }
58069
+ /**
58070
+ * The startup platform-config step, converged on Apple's `NamiPlatformConfigService.fetch` and
58071
+ * Android's `fetchAppConfigOnce` (NAM-4470, NAM-4589):
58072
+ *
58073
+ * | case | outcome |
58074
+ * |---|---|
58075
+ * | an initial config was supplied | NOT awaited: one attempt runs in the background and stores the config when it lands; usable |
58076
+ * | no initial config, success, capabilities include `sdk` | usable |
58077
+ * | no initial config, success, capabilities lack `sdk` | not usable |
58078
+ * | no initial config, failure | {@link CONFIG_MAX_ATTEMPTS} attempts of up to {@link CONFIG_API_TIMEOUT_LIMIT} each, then usable only if a config is cached (and grants `sdk`) |
58079
+ *
58080
+ * The two fallbacks are consulted at two different points, deliberately. An initial config
58081
+ * decides the case *before* any request: startup does not wait on the network at all, since the
58082
+ * initial config is what makes the SDK ready without it (Apple: "Initial config is available.
58083
+ * Skipping retry."). The config cached by a previous launch is consulted only *after* the
58084
+ * retries run out. A stored `INITIAL_APP_CONFIG` is not that cache.
58085
+ *
58086
+ * One check does apply with an initial config, because it costs no wait: a cached config that
58087
+ * withholds `sdk` still stops startup, as Apple's cache load re-applies `updateSdkFlags`.
58088
+ * Without it a kill switch could never reach an app that ships an initial config — the
58089
+ * background fetch lands after startup has already run, so it takes effect from the next launch.
58090
+ *
58091
+ * A `404` is treated as any other 4xx, matching Apple's effective behaviour (its dedicated 404
58092
+ * branch is unreachable, NAM-4471) and Android's.
58093
+ *
58094
+ * @param hasInitialConfig whether the host passed an `initialConfig` that decoded to content.
58095
+ */
58096
+ async fetchConfigForStartup(hasInitialConfig) {
58097
+ if (hasInitialConfig)
58098
+ return this.startupOnInitialConfig();
58099
+ const maxAttempts = CONFIG_MAX_ATTEMPTS;
58100
+ let lastError;
58101
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
58102
+ if (attempt > 1) {
58103
+ logger.debug(`Retrying app config fetch (attempt ${attempt} of ${maxAttempts})`);
58104
+ }
58105
+ try {
58106
+ return outcomeOf(await this.fetchConfig());
58107
+ }
58108
+ catch (error) {
58109
+ lastError = error;
58110
+ }
58111
+ }
58112
+ const cached = storageService.getAppConfig(API_CONFIG);
58113
+ if (cached) {
58114
+ // A cached kill switch still holds: Apple re-applies `updateSdkFlags` when it loads the
58115
+ // cached config, so one that withheld `sdk` keeps the SDK disabled through an outage.
58116
+ if (!grantsSdk(cached)) {
58117
+ return { usable: false, reason: "the cached app config does not grant the 'sdk' capability" };
58118
+ }
58119
+ logger.info(`App config fetch failed after ${maxAttempts} attempts - continuing on the cached config`);
58120
+ return { usable: true };
58121
+ }
58122
+ return {
58123
+ usable: false,
58124
+ reason: `app config fetch failed after ${maxAttempts} attempts (${describeFailure(lastError)}) ` +
58125
+ "and no initial or cached config is available",
58126
+ };
58127
+ }
58128
+ /**
58129
+ * The initial-config case: nothing is awaited. The cached kill switch is a storage read; the
58130
+ * config request is left running and persists its answer (`fetchConfig`) when it lands.
58131
+ */
58132
+ startupOnInitialConfig() {
58133
+ // Read before the request can land, so this launch is judged on what the last one stored.
58134
+ const cached = storageService.getAppConfig(API_CONFIG);
58135
+ // Issued even when the cache holds a kill switch: that is how a LIFTED kill switch reaches
58136
+ // the next launch.
58137
+ this.fetchConfig().then((config) => {
58138
+ if (!grantsSdk(config)) {
58139
+ logger.warn("App config does not grant the 'sdk' capability - it takes effect from the next launch");
58140
+ }
58141
+ }, (error) => {
58142
+ logger.info(`App config fetch failed (${describeFailure(error)}). Initial config is available. Skipping retry.`);
58143
+ });
58144
+ if (cached && !grantsSdk(cached)) {
58145
+ return { usable: false, reason: "the cached app config does not grant the 'sdk' capability" };
58146
+ }
58147
+ return { usable: true };
58148
+ }
58053
58149
  }
58054
58150
  ConfigRepository.instance = new ConfigRepository();
58151
+ function outcomeOf(config) {
58152
+ return grantsSdk(config)
58153
+ ? { usable: true }
58154
+ : { usable: false, reason: "app config does not grant the 'sdk' capability" };
58155
+ }
58156
+ function grantsSdk(config) {
58157
+ return config.capabilities?.includes(Capabilities.SDK) ?? false;
58158
+ }
58159
+ function describeFailure(error) {
58160
+ const status = error?.status;
58161
+ if (typeof status === "number")
58162
+ return `HTTP ${status}`;
58163
+ return error instanceof Error ? error.message : String(error);
58164
+ }
58055
58165
 
58056
58166
  // Global reference to avoid circular imports
58057
58167
  let namiRefsInstance = null;
@@ -61595,6 +61705,15 @@ class NamiRefs {
61595
61705
  this.inMemoryAnonymousMode = false;
61596
61706
  // Captured before device registration; true only on genuine fresh installs
61597
61707
  this.isFirstSession = false;
61708
+ // Set when the platform-config step leaves no usable config (see isStartupDisabled)
61709
+ this.startupDisabled = false;
61710
+ // Whether this configure() supplied an initial config (the config ladder's early exit)
61711
+ this.hasInitialConfig = false;
61712
+ // Resume bookkeeping: the configuration startup last ran with, when it (or a resume) last
61713
+ // ran, and whether one is running now.
61714
+ this.lastConfig = null;
61715
+ this.lastStartupRunAt = 0;
61716
+ this.startupInFlight = false;
61598
61717
  // Register this instance to avoid circular dependency issues
61599
61718
  setNamiRefsInstance(this);
61600
61719
  }
@@ -61612,13 +61731,101 @@ class NamiRefs {
61612
61731
  getIsFirstSession() {
61613
61732
  return this.isFirstSession;
61614
61733
  }
61734
+ /**
61735
+ * True when the platform-config step found no usable config and startup stopped after it —
61736
+ * the equivalent of Apple's `sdkEnabled = false` and Android's `NamiRefs.startupDisabled`.
61737
+ * Like both, it gates only the startup sequence, nothing else.
61738
+ */
61739
+ isStartupDisabled() {
61740
+ return this.startupDisabled;
61741
+ }
61615
61742
  async init(config) {
61616
61743
  this.isFirstSession = storageService.getDevice() === null && !storageService.getAnonymousMode();
61744
+ // Cleared per configure so a transient failure cannot disable a later run.
61745
+ this.startupDisabled = false;
61746
+ // `Nami.configure` has already normalised a missing or undecodable initialConfig to `{}`,
61747
+ // so "has content" is Apple's `initialConfig != nil`.
61748
+ this.hasInitialConfig = Object.keys(config.initialConfig ?? {}).length > 0;
61617
61749
  this.setInitialValues(config);
61618
- ConfigRepository.instance.fetchConfig();
61750
+ this.lastConfig = config;
61751
+ this.lastStartupRunAt = Date.now();
61752
+ this.startupInFlight = true;
61753
+ try {
61754
+ if (!(await this.fetchPlatformConfig()))
61755
+ return;
61756
+ await this.runStartupAfterConfig(config);
61757
+ }
61758
+ finally {
61759
+ this.startupInFlight = false;
61760
+ }
61761
+ }
61762
+ /**
61763
+ * The app returned to the foreground. Re-reads the platform config, as Apple's `.appResume`
61764
+ * and Android's `onAppComesFromBackground` do, so a config change — the `sdk` kill switch
61765
+ * included — reaches a long-lived tab or app without a cold start.
61766
+ *
61767
+ * - Skipped within {@link RESUME_THROTTLE_MS} of the last launch/resume, and while one is
61768
+ * still running.
61769
+ * - When the launch had **stopped** for want of a config and one is now usable, the rest of
61770
+ * startup runs — the steps an Apple resume re-runs because the launch never executed them.
61771
+ * - Otherwise this is a config refresh only. Apple and Android also re-run device, campaigns,
61772
+ * profile, products and the session start on resume; core does not yet, and has no
61773
+ * session end on background for a second session start to pair with (NAM-4623).
61774
+ */
61775
+ async resume() {
61776
+ if (!this.lastConfig || this.startupInFlight)
61777
+ return;
61778
+ if (Date.now() - this.lastStartupRunAt < RESUME_THROTTLE_MS) {
61779
+ logger.debug("Resuming too quickly to call the service.");
61780
+ return;
61781
+ }
61782
+ const config = this.lastConfig;
61783
+ const wasStopped = this.startupDisabled;
61784
+ this.lastStartupRunAt = Date.now();
61785
+ this.startupInFlight = true;
61786
+ this.startupDisabled = false;
61787
+ try {
61788
+ if (!(await this.fetchPlatformConfig()) || !wasStopped)
61789
+ return;
61790
+ logger.info("Usable app config on resume - completing the startup that stopped at launch");
61791
+ await this.runStartupAfterConfig(config);
61792
+ }
61793
+ catch (error) {
61794
+ logger.warn(`Resume refresh failed: ${error}`);
61795
+ }
61796
+ finally {
61797
+ this.startupInFlight = false;
61798
+ }
61799
+ }
61800
+ /** Everything startup runs once the platform-config step has found a usable config. */
61801
+ async runStartupAfterConfig(config) {
61619
61802
  await this.initAndFetchRequiredData(config);
61620
61803
  SessionService.instance.startSession();
61621
61804
  }
61805
+ /** Forgets the configuration a resume would run against; called by `Nami.reset()`. */
61806
+ clearResumeState() {
61807
+ this.lastConfig = null;
61808
+ this.lastStartupRunAt = 0;
61809
+ this.startupDisabled = false;
61810
+ }
61811
+ /**
61812
+ * The platform-config startup step, ahead of device registration — the position it holds on
61813
+ * Apple (`.fetchPlatformConfig`) and Android (`fetchAppConfigOnce`) (NAM-4464, NAM-4589).
61814
+ * Without an initial config it is awaited, so everything downstream — `campaign_cache_ttl`
61815
+ * included — is evaluated against the config it leaves in storage. With one it resolves at
61816
+ * once and the request finishes in the background (see `fetchConfigForStartup`).
61817
+ *
61818
+ * @returns false when there is no usable config, in which case startup must stop.
61819
+ */
61820
+ async fetchPlatformConfig() {
61821
+ const outcome = await ConfigRepository.instance.fetchConfigForStartup(this.hasInitialConfig);
61822
+ if (!outcome.usable) {
61823
+ this.startupDisabled = true;
61824
+ logger.error(`Nami startup stopped: ${outcome.reason}`);
61825
+ return false;
61826
+ }
61827
+ return true;
61828
+ }
61622
61829
  setInitialValues(config) {
61623
61830
  const baseConfig = { ...config };
61624
61831
  delete baseConfig.initialConfig;
@@ -61879,7 +62086,7 @@ class LastLaunchStore {
61879
62086
  }
61880
62087
  }
61881
62088
 
61882
- var _Nami_isInitialized;
62089
+ var _Nami_isInitialized, _Nami_removeForegroundListener;
61883
62090
  // NamiFlowManager is intentionally NOT imported at top level — it
61884
62091
  // transitively imports back to this module (`Nami` for logging), and
61885
62092
  // pulling it in here causes a load-order cycle that breaks tests
@@ -61887,6 +62094,7 @@ var _Nami_isInitialized;
61887
62094
  class Nami {
61888
62095
  constructor() {
61889
62096
  _Nami_isInitialized.set(this, false);
62097
+ _Nami_removeForegroundListener.set(this, null);
61890
62098
  this.maxLogging = false;
61891
62099
  }
61892
62100
  get isInitialized() {
@@ -61910,9 +62118,13 @@ class Nami {
61910
62118
  * integrating app, consistent with the Apple and Android SDKs.
61911
62119
  */
61912
62120
  static async reset() {
62121
+ var _a;
61913
62122
  storageService.clearAll();
61914
62123
  clearInMemoryAnonymousMode();
61915
62124
  __classPrivateFieldSet(Nami.instance, _Nami_isInitialized, false, "f");
62125
+ __classPrivateFieldGet((_a = Nami.instance), _Nami_removeForegroundListener, "f")?.call(_a);
62126
+ __classPrivateFieldSet(Nami.instance, _Nami_removeForegroundListener, null, "f");
62127
+ NamiRefs.instance.clearResumeState();
61916
62128
  // In-memory singleton state that previously survived reset and
61917
62129
  // bled into the next configure() cycle.
61918
62130
  NamiProfileManager$1.instance.setExternalId(undefined);
@@ -61975,6 +62187,18 @@ class Nami {
61975
62187
  }
61976
62188
  config.initialConfig = {};
61977
62189
  }
62190
+ /**
62191
+ * Subscribes the resume config refresh to the host's foreground signal, once per process —
62192
+ * a reconfigure must not stack a second listener (the Android defect NAM-4541 fixed).
62193
+ * Registered before startup runs; `resume()` itself skips while startup is in flight.
62194
+ */
62195
+ listenForAppForeground() {
62196
+ if (__classPrivateFieldGet(this, _Nami_removeForegroundListener, "f"))
62197
+ return;
62198
+ __classPrivateFieldSet(this, _Nami_removeForegroundListener, getPlatformAdapters().device.onAppForeground?.(() => {
62199
+ void NamiRefs.instance.resume();
62200
+ }) ?? null, "f");
62201
+ }
61978
62202
  async initializeSDK(config) {
61979
62203
  let fullReconfig = false;
61980
62204
  let partialReconfig = false;
@@ -62015,6 +62239,7 @@ class Nami {
62015
62239
  // configure SDK with new config
62016
62240
  __classPrivateFieldSet(this, _Nami_isInitialized, true, "f");
62017
62241
  NamiAPI.configure(config);
62242
+ this.listenForAppForeground();
62018
62243
  await NamiRefs.instance.init(config);
62019
62244
  logger.info("SDK successfully initialized!");
62020
62245
  const state = partialReconfig || fullReconfig ? RECONFIG_SUCCESS : INITIAL_SUCCESS;
@@ -62026,7 +62251,7 @@ class Nami {
62026
62251
  };
62027
62252
  }
62028
62253
  }
62029
- _Nami_isInitialized = new WeakMap();
62254
+ _Nami_isInitialized = new WeakMap(), _Nami_removeForegroundListener = new WeakMap();
62030
62255
  Nami.instance = new Nami();
62031
62256
 
62032
62257
  const VALID_STYLES = new Set(['fullscreen', 'sheet', 'compact_sheet', 'modal']);
@@ -65996,4 +66221,4 @@ var internal = /*#__PURE__*/Object.freeze({
65996
66221
  NamiPurchaseManager: NamiPurchaseManager
65997
66222
  });
65998
66223
 
65999
- 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_NOT_AVAILABLE, 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, 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, RetryLimitExceededError, SDKNotInitializedError, SDK_NOT_INITIALIZED, SERVER_NAMI_ENTITLEMENTS, SESSION_REQUIRED, SHOULD_SHOW_LOADING_INDICATOR, SKU_TEXT_REGEX, SMART_TEXT_PATTERN, STARTUP_TELEMETRY, STATUS_BAD_REQUEST, STATUS_CONFLICT, STATUS_INTERNAL_SERVER_ERROR, STATUS_NOT_FOUND, STATUS_SUCCESS, SessionService, SimpleEventTarget, StorageService, 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, extractStandardPricingPhases, 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, getTranslate, getUrlParams, handleErrors, hasAllPaywalls, hasCapability, hasPurchaseManagement, initialState, invokeHandler, isAnonymousMode, isInitialConfigCompressed, isNamiFlowCampaign, isSubscription, isValidISODate, isValidUrl, logger, mapAnonymousCampaigns, namiBuySKU, normalizeLaunchContext, parseToSemver, postConversion, productDetail, registerAPITrafficObserver, registerPlatformAdapters, registerPurchaseAdapter, resolveLoopSource, selectSegment, setActiveNamiEntitlements, shouldValidateProductGroups, skuItems, skuMapFromEntitlements, storageService, toDouble, toNamiEntitlements, toNamiSKU, tryParseB64Gzip, tryParseJson, updateRelatedSKUsForNamiEntitlement, uuidFromSplitPosition, validateForm, validateMinSDKVersion, validateTextInput };
66224
+ 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_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, 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, SKU_TEXT_REGEX, SMART_TEXT_PATTERN, STARTUP_TELEMETRY, STATUS_BAD_REQUEST, STATUS_CONFLICT, STATUS_INTERNAL_SERVER_ERROR, STATUS_NOT_FOUND, STATUS_SUCCESS, SessionService, SimpleEventTarget, StorageService, 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, extractStandardPricingPhases, 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, getTranslate, getUrlParams, handleErrors, hasAllPaywalls, hasCapability, hasPurchaseManagement, initialState, invokeHandler, isAnonymousMode, isInitialConfigCompressed, isNamiFlowCampaign, isSubscription, isValidISODate, isValidUrl, logger, mapAnonymousCampaigns, namiBuySKU, normalizeLaunchContext, parseToSemver, postConversion, productDetail, registerAPITrafficObserver, registerPlatformAdapters, registerPurchaseAdapter, resolveLoopSource, selectSegment, setActiveNamiEntitlements, shouldValidateProductGroups, skuItems, skuMapFromEntitlements, 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.4.12-dev.202609251647",
3
+ "version": "3.4.12-dev.202609252349",
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",