@namiml/sdk-core 3.4.0-dev.202605190929 → 3.4.0-dev.202605191752

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -98,7 +98,7 @@ const {
98
98
  // version — stamped by scripts/version.sh
99
99
  NAMI_SDK_VERSION: exports.NAMI_SDK_VERSION = "3.4.0",
100
100
  // full package version including dev suffix — stamped by scripts/version.sh
101
- NAMI_SDK_PACKAGE_VERSION: exports.NAMI_SDK_PACKAGE_VERSION = "3.4.0-dev.202605190929",
101
+ NAMI_SDK_PACKAGE_VERSION: exports.NAMI_SDK_PACKAGE_VERSION = "3.4.0-dev.202605191752",
102
102
  // environments
103
103
  PRODUCTION: exports.PRODUCTION = "production", DEVELOPMENT: exports.DEVELOPMENT = "development",
104
104
  // error messages
@@ -6927,6 +6927,21 @@ class NamiAPI {
6927
6927
  this.baseURL = getBaseUrl(config.namiCommands);
6928
6928
  this.platformID = config.appPlatformID;
6929
6929
  }
6930
+ /**
6931
+ * Clear cached baseURL and platformID so the next `configure()` call
6932
+ * re-derives them from fresh storage. Called from `Nami.reset()`.
6933
+ *
6934
+ * Without this, the NamiAPI singleton holds the previous lifecycle's
6935
+ * platform/base URL even after storage has been wiped — any in-flight
6936
+ * or queued request after reset would hit the old endpoint.
6937
+ */
6938
+ static reset() {
6939
+ this.instance.reset();
6940
+ }
6941
+ reset() {
6942
+ this.baseURL = undefined;
6943
+ this.platformID = undefined;
6944
+ }
6930
6945
  async login(externalId) {
6931
6946
  if (!externalId) {
6932
6947
  throw new ExternalIDRequiredError();
@@ -8000,6 +8015,18 @@ class CampaignRuleRepository {
8000
8015
  this.disableCampaignUpdates = namiCommands.includes("disableCampaignUpdates");
8001
8016
  this.useLegacyPaywallFetch = namiCommands.includes("useLegacyPaywallFetch");
8002
8017
  }
8018
+ /**
8019
+ * Reset the singleton's configured state. Called from `Nami.reset()`
8020
+ * so the previous lifecycle's split position and namiCommands flags
8021
+ * don't leak into the next configure() cycle. The form factor reverts
8022
+ * to the platform's natural value via `getDeviceFormFactor()`.
8023
+ */
8024
+ reset() {
8025
+ this.currentFormFactor = getDeviceFormFactor();
8026
+ this.splitPosition = undefined;
8027
+ this.disableCampaignUpdates = false;
8028
+ this.useLegacyPaywallFetch = false;
8029
+ }
8003
8030
  async fetchCampaignRules(paywalls) {
8004
8031
  // get deviceId
8005
8032
  const authDevice = storageService.getDevice();
@@ -11310,6 +11337,15 @@ class PaywallState extends SimpleEventTarget {
11310
11337
  static remove(provider) {
11311
11338
  this.providers = this.providers.filter(p => p !== provider);
11312
11339
  }
11340
+ /**
11341
+ * Clear the static `providers` array. Called from `Nami.reset()` so
11342
+ * orphaned PaywallState instances from a previous SDK lifecycle don't
11343
+ * receive customer-attribute / login / product-detail updates intended
11344
+ * for the next configure() cycle.
11345
+ */
11346
+ static reset() {
11347
+ this.providers = [];
11348
+ }
11313
11349
  static get currentProvider() {
11314
11350
  return PaywallState.providers[0];
11315
11351
  }
@@ -12063,7 +12099,87 @@ function isEqual(objA, objB) {
12063
12099
  return isEqual$1(normA, normB);
12064
12100
  }
12065
12101
 
12102
+ class NamiProfileManager {
12103
+ constructor() {
12104
+ this.load();
12105
+ }
12106
+ setExternalId(externalId) {
12107
+ this.externalId = externalId;
12108
+ return this;
12109
+ }
12110
+ getExternalId() {
12111
+ return this.externalId;
12112
+ }
12113
+ isLoggedIn() {
12114
+ return !!this.externalId;
12115
+ }
12116
+ save() {
12117
+ if (this.externalId) {
12118
+ storageService.setNamiProfile({
12119
+ externalId: this.externalId,
12120
+ });
12121
+ }
12122
+ else {
12123
+ storageService.removeNamiProfile();
12124
+ }
12125
+ return this;
12126
+ }
12127
+ load() {
12128
+ const profile = storageService.getNamiProfile();
12129
+ if (profile) {
12130
+ this.externalId = profile.externalId;
12131
+ }
12132
+ PaywallState.setIsLoggedIn(!!this.externalId);
12133
+ }
12134
+ /**
12135
+ * Update local profile state for login: persist the external id and flip the
12136
+ * logged-in flag in PaywallState. No network I/O — pair with `loginRemote`
12137
+ * to perform the actual API call. Used by the async login/logout fast path.
12138
+ */
12139
+ loginLocal(externalId) {
12140
+ this.setExternalId(externalId).save();
12141
+ PaywallState.setIsLoggedIn(true);
12142
+ }
12143
+ /**
12144
+ * Perform the login network call only. If the server returns a different
12145
+ * `external_id` than the locally stored value, reconcile it into local state.
12146
+ */
12147
+ async loginRemote(externalId) {
12148
+ const loginData = await NamiAPI.instance.login(externalId);
12149
+ if (loginData.external_id && loginData.external_id !== this.externalId) {
12150
+ this.setExternalId(loginData.external_id).save();
12151
+ }
12152
+ }
12153
+ /**
12154
+ * Update local profile state for logout: clear the external id, persist,
12155
+ * and flip the logged-in flag in PaywallState. Used by the async fast path.
12156
+ */
12157
+ logoutLocal() {
12158
+ this.setExternalId(undefined).save();
12159
+ PaywallState.setIsLoggedIn(false);
12160
+ }
12161
+ /**
12162
+ * Perform the logout network call only.
12163
+ */
12164
+ async logoutRemote() {
12165
+ await NamiAPI.instance.logout();
12166
+ }
12167
+ async login(externalId) {
12168
+ this.loginLocal(externalId);
12169
+ await this.loginRemote(externalId);
12170
+ }
12171
+ async logout() {
12172
+ this.logoutLocal();
12173
+ await this.logoutRemote();
12174
+ }
12175
+ }
12176
+ NamiProfileManager.instance = new NamiProfileManager();
12177
+
12066
12178
  var _Nami_isInitialized;
12179
+ // NamiFlowManager is intentionally NOT imported at top level — it
12180
+ // transitively imports back to this module (`Nami` for logging), and
12181
+ // pulling it in here causes a load-order cycle that breaks tests
12182
+ // importing `ConditionalEvaluator`. It's loaded lazily inside `reset()`.
12067
12183
  class Nami {
12068
12184
  constructor() {
12069
12185
  _Nami_isInitialized.set(this, false);
@@ -12080,13 +12196,32 @@ class Nami {
12080
12196
  }
12081
12197
  /**
12082
12198
  * Clear all locally persisted SDK state (device ID, customer attributes,
12083
- * campaigns/paywalls/products caches, session, and anonymous-mode flags).
12084
- * After reset, the next configure() call regenerates the device ID.
12199
+ * campaigns/paywalls/products caches, session, and anonymous-mode flags)
12200
+ * AND the in-memory state held by module-level singletons so the next
12201
+ * configure() call starts from a truly clean slate.
12202
+ *
12203
+ * Host-registered handler refs (flow event/handoff handlers, customer
12204
+ * journey/account state handlers, purchase complete handler) are
12205
+ * intentionally preserved across reset — they're owned by the
12206
+ * integrating app, consistent with the Apple and Android SDKs.
12085
12207
  */
12086
12208
  static async reset() {
12087
12209
  storageService.clearAll();
12088
12210
  clearInMemoryAnonymousMode();
12089
12211
  __classPrivateFieldSet(Nami.instance, _Nami_isInitialized, false, "f");
12212
+ // In-memory singleton state that previously survived reset and
12213
+ // bled into the next configure() cycle.
12214
+ NamiProfileManager.instance.setExternalId(undefined);
12215
+ PaywallState.reset();
12216
+ NamiAPI.reset();
12217
+ CampaignRuleRepository.instance.reset();
12218
+ // Lazy import to avoid a load-order cycle (NamiFlowManager imports
12219
+ // back to this module for `Nami.instance.maxLogging`).
12220
+ const { NamiFlowManager } = await Promise.resolve().then(function () { return NamiFlowManager$1; });
12221
+ NamiFlowManager.reset();
12222
+ // Give platform adapters a chance to drop their own state
12223
+ // (e.g. Expo's UI adapter holds listener refs from PaywallView).
12224
+ getPlatformAdapters().ui.onReset?.();
12090
12225
  }
12091
12226
  /**
12092
12227
  * Configures and initializes the SDK.
@@ -12963,57 +13098,6 @@ class PlacementLabelResolver extends BaseNamespaceResolver {
12963
13098
  }
12964
13099
  }
12965
13100
 
12966
- class NamiProfileManager {
12967
- constructor() {
12968
- this.load();
12969
- }
12970
- setExternalId(externalId) {
12971
- this.externalId = externalId;
12972
- return this;
12973
- }
12974
- getExternalId() {
12975
- return this.externalId;
12976
- }
12977
- isLoggedIn() {
12978
- return !!this.externalId;
12979
- }
12980
- save() {
12981
- if (this.externalId) {
12982
- storageService.setNamiProfile({
12983
- externalId: this.externalId,
12984
- });
12985
- }
12986
- else {
12987
- storageService.removeNamiProfile();
12988
- }
12989
- return this;
12990
- }
12991
- load() {
12992
- const profile = storageService.getNamiProfile();
12993
- if (profile) {
12994
- this.externalId = profile.externalId;
12995
- }
12996
- PaywallState.setIsLoggedIn(!!this.externalId);
12997
- }
12998
- async login(externalId) {
12999
- const loginData = await NamiAPI.instance.login(externalId);
13000
- if (loginData.external_id) {
13001
- PaywallState.setIsLoggedIn(true);
13002
- }
13003
- this
13004
- .setExternalId(loginData.external_id)
13005
- .save();
13006
- }
13007
- async logout() {
13008
- await NamiAPI.instance.logout();
13009
- PaywallState.setIsLoggedIn(false);
13010
- this
13011
- .setExternalId(undefined)
13012
- .save();
13013
- }
13014
- }
13015
- NamiProfileManager.instance = new NamiProfileManager();
13016
-
13017
13101
  /**
13018
13102
  * Represents the possible account actions states that can be returned by callback registered from
13019
13103
  * [NamiCustomerManager.registerAccountStateHandler]
@@ -13046,6 +13130,23 @@ class NamiFlowManager {
13046
13130
  this.lastAnimatedFlowProgress = new Map();
13047
13131
  this.navGraphCache = new WeakMap();
13048
13132
  }
13133
+ /**
13134
+ * Clear flow data state. Called from `Nami.reset()`.
13135
+ *
13136
+ * Resets the active flow, openness flag, animation progress, and the
13137
+ * nav-graph cache. Host-registered handler refs (`handoffStepHandler`,
13138
+ * `eventHandler`) are intentionally preserved across reset — they're
13139
+ * owned by the integrating app via `registerStepHandoff()` /
13140
+ * `registerEventHandler()`, consistent with how Apple/Android handle
13141
+ * host callbacks across reset.
13142
+ */
13143
+ static reset() {
13144
+ const i = NamiFlowManager.instance;
13145
+ i.currentFlow = undefined;
13146
+ i.flowOpen = false;
13147
+ i.lastAnimatedFlowProgress = new Map();
13148
+ i.navGraphCache = new WeakMap();
13149
+ }
13049
13150
  static registerStepHandoff(handoffStepHandler) {
13050
13151
  this.instance.handoffStepHandler = handoffStepHandler;
13051
13152
  }
@@ -13329,6 +13430,26 @@ class NamiFlowManager {
13329
13430
  }
13330
13431
  }
13331
13432
 
13433
+ var NamiFlowManager$1 = /*#__PURE__*/Object.freeze({
13434
+ __proto__: null,
13435
+ NamiFlowManager: NamiFlowManager
13436
+ });
13437
+
13438
+ const SHOULD_SHOW_LOADING_INDICATOR = false;
13439
+ const DISABLE_ASYNC_LOGIN_LOGOUT = "disableAsyncLoginLogout";
13440
+ /**
13441
+ * Returns true when `disableAsyncLoginLogout` appears in the active `namiCommands` list,
13442
+ * meaning login/logout should await the underlying API call before notifying handlers.
13443
+ *
13444
+ * Default behavior (flag absent) is the async/fast path: update local state and notify
13445
+ * handlers immediately while dispatching the API call as fire-and-forget.
13446
+ *
13447
+ * Internal feature flag — opt-out via `Nami.configure({ namiCommands: [...] })`.
13448
+ */
13449
+ const isAsyncLoginLogoutDisabled = () => {
13450
+ return storageService.getNamiConfig()?.namiCommands?.includes(DISABLE_ASYNC_LOGIN_LOGOUT) ?? false;
13451
+ };
13452
+
13332
13453
  /**
13333
13454
  * @class NamiCustomerManager
13334
13455
  * Provides methods for managing customer-related functionality.
@@ -13390,6 +13511,30 @@ class NamiCustomerManager {
13390
13511
  this.invokeStateHandler(exports.AccountStateAction.LOGIN, false, error);
13391
13512
  throw error;
13392
13513
  }
13514
+ if (!isAsyncLoginLogoutDisabled()) {
13515
+ // Default sync/fast path: update local state, notify handlers, and fire
13516
+ // lifecycle immediately. Dispatch the underlying API call and entitlement
13517
+ // refresh fire-and-forget — failures are logged but do not surface.
13518
+ NamiProfileManager.instance.loginLocal(externalId);
13519
+ this.invokeStateHandler(exports.AccountStateAction.LOGIN, true);
13520
+ if (NamiFlowManager.instance.flowOpen) {
13521
+ const currentFlow = NamiFlowManager.instance.currentFlow;
13522
+ const currentStep = currentFlow?.currentFlowStep;
13523
+ if (currentStep) {
13524
+ if (Nami.instance.maxLogging) {
13525
+ logger.debug(`[NamiCustomerManager] async login success — triggering __login_success__ on step ${currentStep.id}`);
13526
+ }
13527
+ currentFlow.executeLifecycle(currentStep, NamiReservedActions.LOGIN_SUCCESS);
13528
+ }
13529
+ }
13530
+ void NamiProfileManager.instance.loginRemote(externalId).catch((err) => {
13531
+ logger.error('[NamiCustomerManager] async login API failed', err);
13532
+ });
13533
+ void EntitlementRepository.instance.fetchActiveEntitlements().catch((err) => {
13534
+ logger.error('[NamiCustomerManager] async login entitlement refresh failed', err);
13535
+ });
13536
+ return;
13537
+ }
13393
13538
  try {
13394
13539
  await NamiProfileManager.instance.login(externalId);
13395
13540
  await EntitlementRepository.instance.fetchActiveEntitlements();
@@ -13435,6 +13580,30 @@ class NamiCustomerManager {
13435
13580
  this.invokeStateHandler(exports.AccountStateAction.LOGOUT, false, error);
13436
13581
  throw error;
13437
13582
  }
13583
+ if (!isAsyncLoginLogoutDisabled()) {
13584
+ // Default sync/fast path: clear local state, notify handlers, and fire
13585
+ // lifecycle immediately. Dispatch the underlying API call and entitlement
13586
+ // refresh fire-and-forget — failures are logged but do not surface.
13587
+ NamiProfileManager.instance.logoutLocal();
13588
+ this.invokeStateHandler(exports.AccountStateAction.LOGOUT, true);
13589
+ if (NamiFlowManager.instance.flowOpen) {
13590
+ const currentFlow = NamiFlowManager.instance.currentFlow;
13591
+ const currentStep = currentFlow?.currentFlowStep;
13592
+ if (currentStep) {
13593
+ if (Nami.instance.maxLogging) {
13594
+ logger.debug(`[NamiCustomerManager] async logout success — triggering __logout_success__ on step ${currentStep.id}`);
13595
+ }
13596
+ currentFlow.executeLifecycle(currentStep, NamiReservedActions.LOGOUT_SUCCESS);
13597
+ }
13598
+ }
13599
+ void NamiProfileManager.instance.logoutRemote().catch((err) => {
13600
+ logger.error('[NamiCustomerManager] async logout API failed', err);
13601
+ });
13602
+ void EntitlementRepository.instance.fetchActiveEntitlements().catch((err) => {
13603
+ logger.error('[NamiCustomerManager] async logout entitlement refresh failed', err);
13604
+ });
13605
+ return;
13606
+ }
13438
13607
  try {
13439
13608
  await NamiProfileManager.instance.logout();
13440
13609
  await EntitlementRepository.instance.fetchActiveEntitlements();
@@ -14869,8 +15038,6 @@ exports.CampaignRuleConversionEventType = void 0;
14869
15038
  CampaignRuleConversionEventType["IN_APP"] = "in_app";
14870
15039
  })(exports.CampaignRuleConversionEventType || (exports.CampaignRuleConversionEventType = {}));
14871
15040
 
14872
- const SHOULD_SHOW_LOADING_INDICATOR = false;
14873
-
14874
15041
  function parseToSemver(versionString) {
14875
15042
  const [semVer, major, minor, patch, prerelease, buildmetadata] = versionString.match(/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/) ?? [];
14876
15043
  return {
@@ -64345,6 +64512,7 @@ exports.ClientError = ClientError;
64345
64512
  exports.ConfigRepository = ConfigRepository;
64346
64513
  exports.ConflictError = ConflictError;
64347
64514
  exports.CustomerJourneyRepository = CustomerJourneyRepository;
64515
+ exports.DISABLE_ASYNC_LOGIN_LOGOUT = DISABLE_ASYNC_LOGIN_LOGOUT;
64348
64516
  exports.DeviceIDRequiredError = DeviceIDRequiredError;
64349
64517
  exports.DeviceRepository = DeviceRepository;
64350
64518
  exports.EntitlementRepository = EntitlementRepository;
package/dist/index.d.ts CHANGED
@@ -1458,6 +1458,17 @@ declare class NamiFlowManager {
1458
1458
  private lastAnimatedFlowProgress;
1459
1459
  private navGraphCache;
1460
1460
  private constructor();
1461
+ /**
1462
+ * Clear flow data state. Called from `Nami.reset()`.
1463
+ *
1464
+ * Resets the active flow, openness flag, animation progress, and the
1465
+ * nav-graph cache. Host-registered handler refs (`handoffStepHandler`,
1466
+ * `eventHandler`) are intentionally preserved across reset — they're
1467
+ * owned by the integrating app via `registerStepHandoff()` /
1468
+ * `registerEventHandler()`, consistent with how Apple/Android handle
1469
+ * host callbacks across reset.
1470
+ */
1471
+ static reset(): void;
1461
1472
  handoffStepHandler?: NamiFlowHandoffStepHandler;
1462
1473
  static registerStepHandoff(handoffStepHandler?: NamiFlowHandoffStepHandler): void;
1463
1474
  eventHandler?: NamiFlowEventHandler;
@@ -1827,6 +1838,13 @@ interface IUIAdapter {
1827
1838
  flowNavigateToScreen(paywall: IPaywall, options: FlowNavigationOptions): void;
1828
1839
  /** Called after SDK configuration completes — used for platform-specific post-init setup */
1829
1840
  postConfigure?(): void;
1841
+ /**
1842
+ * Called from `Nami.reset()` so the adapter can drop any platform-specific
1843
+ * state that survives the storage wipe (e.g. listener references on the
1844
+ * Expo UI bridge, mounted paywall handles, etc.). Optional — adapters
1845
+ * with no platform-specific state should leave this unset.
1846
+ */
1847
+ onReset?(): void;
1830
1848
  }
1831
1849
 
1832
1850
  interface PurchaseContext {
@@ -1871,8 +1889,14 @@ declare class Nami {
1871
1889
  static sdkPackageVersion(): string;
1872
1890
  /**
1873
1891
  * Clear all locally persisted SDK state (device ID, customer attributes,
1874
- * campaigns/paywalls/products caches, session, and anonymous-mode flags).
1875
- * After reset, the next configure() call regenerates the device ID.
1892
+ * campaigns/paywalls/products caches, session, and anonymous-mode flags)
1893
+ * AND the in-memory state held by module-level singletons so the next
1894
+ * configure() call starts from a truly clean slate.
1895
+ *
1896
+ * Host-registered handler refs (flow event/handoff handlers, customer
1897
+ * journey/account state handlers, purchase complete handler) are
1898
+ * intentionally preserved across reset — they're owned by the
1899
+ * integrating app, consistent with the Apple and Android SDKs.
1876
1900
  */
1877
1901
  static reset(): Promise<void>;
1878
1902
  /**
@@ -2133,6 +2157,26 @@ declare class NamiProfileManager {
2133
2157
  isLoggedIn(): boolean;
2134
2158
  private save;
2135
2159
  private load;
2160
+ /**
2161
+ * Update local profile state for login: persist the external id and flip the
2162
+ * logged-in flag in PaywallState. No network I/O — pair with `loginRemote`
2163
+ * to perform the actual API call. Used by the async login/logout fast path.
2164
+ */
2165
+ loginLocal(externalId: string): void;
2166
+ /**
2167
+ * Perform the login network call only. If the server returns a different
2168
+ * `external_id` than the locally stored value, reconcile it into local state.
2169
+ */
2170
+ loginRemote(externalId: string): Promise<void>;
2171
+ /**
2172
+ * Update local profile state for logout: clear the external id, persist,
2173
+ * and flip the logged-in flag in PaywallState. Used by the async fast path.
2174
+ */
2175
+ logoutLocal(): void;
2176
+ /**
2177
+ * Perform the logout network call only.
2178
+ */
2179
+ logoutRemote(): Promise<void>;
2136
2180
  login(externalId: string): Promise<void>;
2137
2181
  logout(): Promise<void>;
2138
2182
  }
@@ -2307,6 +2351,13 @@ declare class PaywallState extends SimpleEventTarget {
2307
2351
  filteredSkuMenus: ISkuMenu[];
2308
2352
  static create(paywall: IPaywall, context: NamiPaywallLaunchContext, campaign: NamiCampaign): PaywallState;
2309
2353
  static remove(provider: PaywallState): void;
2354
+ /**
2355
+ * Clear the static `providers` array. Called from `Nami.reset()` so
2356
+ * orphaned PaywallState instances from a previous SDK lifecycle don't
2357
+ * receive customer-attribute / login / product-detail updates intended
2358
+ * for the next configure() cycle.
2359
+ */
2360
+ static reset(): void;
2310
2361
  static get currentProvider(): PaywallState | undefined;
2311
2362
  static setCustomerAttribute(attributes: {
2312
2363
  [key: string]: string;
@@ -2607,6 +2658,7 @@ declare const LIQUID_VARIABLE_REGEX: RegExp;
2607
2658
  declare const NAMI_STORAGE_KEYS: readonly string[];
2608
2659
 
2609
2660
  declare const SHOULD_SHOW_LOADING_INDICATOR = false;
2661
+ declare const DISABLE_ASYNC_LOGIN_LOGOUT = "disableAsyncLoginLogout";
2610
2662
 
2611
2663
  declare const getBaseUrl: (namiCommands?: string[]) => string;
2612
2664
  declare const getExtendedClientInfo: (namiCommands: string[]) => ExtendedPlatformInfo;
@@ -2714,6 +2766,16 @@ declare class NamiAPI {
2714
2766
  constructor();
2715
2767
  static configure(config: NamiConfiguration): void;
2716
2768
  protected configure(config: NamiConfiguration): void;
2769
+ /**
2770
+ * Clear cached baseURL and platformID so the next `configure()` call
2771
+ * re-derives them from fresh storage. Called from `Nami.reset()`.
2772
+ *
2773
+ * Without this, the NamiAPI singleton holds the previous lifecycle's
2774
+ * platform/base URL even after storage has been wiped — any in-flight
2775
+ * or queued request after reset would hit the old endpoint.
2776
+ */
2777
+ static reset(): void;
2778
+ protected reset(): void;
2717
2779
  login(externalId: string): Promise<LoginResponse>;
2718
2780
  logout(): Promise<Record<string, never>>;
2719
2781
  startSession(sessionStartTime: Date): Promise<void>;
@@ -2927,6 +2989,13 @@ declare class CampaignRuleRepository {
2927
2989
  static instance: CampaignRuleRepository;
2928
2990
  constructor();
2929
2991
  configure(formFactor: TDevice, splitPosition: number, namiCommands?: string[]): void;
2992
+ /**
2993
+ * Reset the singleton's configured state. Called from `Nami.reset()`
2994
+ * so the previous lifecycle's split position and namiCommands flags
2995
+ * don't leak into the next configure() cycle. The form factor reverts
2996
+ * to the platform's natural value via `getDeviceFormFactor()`.
2997
+ */
2998
+ reset(): void;
2930
2999
  fetchCampaignRules(paywalls: IPaywall[]): Promise<NamiCampaign[]>;
2931
3000
  static extractPaywallUrls(campaigns: NamiCampaign[]): string[];
2932
3001
  static hasPaywallUrls(campaigns: NamiCampaign[]): boolean;
@@ -3082,5 +3151,5 @@ declare const getBillingPeriodNumber: (billingPeriod: string) => number;
3082
3151
  declare const formattedPrice: (price: number) => number;
3083
3152
  declare function toDouble(num: number): number;
3084
3153
 
3085
- export { ALREADY_CONFIGURED, ANONYMOUS_MODE, ANONYMOUS_MODE_ALREADY_OFF, ANONYMOUS_MODE_ALREADY_ON, ANONYMOUS_MODE_LOGIN_NOT_ALLOWED, ANONYMOUS_UUID, APIError, API_ACTIVE_ENTITLEMENTS, API_CAMPAIGN_RULES, API_CAMPAIGN_SESSION_TIMESTAMP, API_CONFIG, API_MAX_CALLS_LIMIT, API_PAYWALLS, API_PRODUCTS, API_RETRY_DELAY_SEC, API_TIMEOUT_LIMIT, API_VERSION, AUTH_DEVICE, AVAILABLE_ACTIVE_ENTITLEMENTS_CHANGED, AVAILABLE_CAMPAIGNS_CHANGED, AccountStateAction, AnonymousCDPError, AnonymousLoginError, AnonymousModeAlreadyOffError, AnonymousModeAlreadyOnError, BASE_STAGING_URL, BASE_URL, BASE_URL_PATH, BadRequestError, BasicNamiFlow, BorderMap, BorderSideMap, CAMPAIGN_NOT_AVAILABLE, CUSTOMER_ATTRIBUTES_KEY_PREFIX, CUSTOMER_JOURNEY_STATE_CHANGED, CUSTOM_HOST_PREFIX, CampaignNotAvailableError, CampaignRuleConversionEventType, CampaignRuleRepository, Capabilities, ClientError, ConfigRepository, ConflictError, CustomerJourneyRepository, DEVELOPMENT, DEVICE_API_TIMEOUT_LIMIT, DEVICE_ID_NOT_SET, DEVICE_ID_REQUIRED, DeviceIDRequiredError, DeviceRepository, EXTENDED_CLIENT_INFO_DELIMITER, EXTENDED_CLIENT_INFO_PREFIX, EXTENDED_PLATFORM, EXTENDED_PLATFORM_VERSION, EXTERNAL_ID_REQUIRED, EntitlementRepository, EntitlementUtils, ExternalIDRequiredError, FLOW_SCREENS_NOT_AVAILABLE, FlowScreensNotAvailableError, HTML_REGEX, INITIAL_APP_CONFIG, INITIAL_CAMPAIGN_RULES, INITIAL_PAYWALLS, INITIAL_PRODUCTS, INITIAL_SESSION_COUNTER_VALUE, INITIAL_SUCCESS, InternalServerError, KEY_SESSION_COUNTER, LIQUID_VARIABLE_REGEX, LOCAL_NAMI_ENTITLEMENTS, LOG_HTTP_REQUESTS, LOG_HTTP_TRAFFIC, LaunchCampaignError, LaunchContextResolver, LogLevel, NAMI_CONFIGURATION, NAMI_CUSTOMER_JOURNEY_STATE, NAMI_LANGUAGE_CODE, NAMI_LAST_IMPRESSION_ID, NAMI_LAUNCH_ID, NAMI_PROFILE, NAMI_PURCHASE_CHANNEL, NAMI_PURCHASE_IMPRESSION_ID, NAMI_SDK_PACKAGE_VERSION, NAMI_SDK_VERSION, NAMI_SESSION_ID, NAMI_STORAGE_KEYS, Nami, NamiAPI, NamiAnimationType, NamiCampaignManager, NamiCampaignRuleType, NamiConditionEvaluator, NamiCustomerManager, NamiEntitlementManager, NamiEventEmitter, NamiFlow, NamiFlowActionFunction, NamiFlowManager, NamiFlowStepType, NamiPaywallAction, NamiPaywallManager, PaywallManagerEvents as NamiPaywallManagerEvents, NamiProfileManager, NamiPurchaseManager, NamiRefs, NamiReservedActions, NotFoundError, PAYWALL_ACTION_EVENT, PLATFORM_ID_REQUIRED, PRODUCTION, PaywallManagerEvents, PaywallRepository, PaywallState, PlacementLabelResolver, PlatformIDRequiredError, ProductRepository, RECONFIG_SUCCESS, RetryLimitExceededError, SDKNotInitializedError, SDK_NOT_INITIALIZED, SERVER_NAMI_ENTITLEMENTS, SESSION_REQUIRED, SHOULD_SHOW_LOADING_INDICATOR, SKU_TEXT_REGEX, SMART_TEXT_PATTERN, STATUS_BAD_REQUEST, STATUS_CONFLICT, STATUS_INTERNAL_SERVER_ERROR, STATUS_NOT_FOUND, STATUS_SUCCESS, SessionService, SimpleEventTarget, StorageService, UNABLE_TO_UPDATE_CDP_ID, USE_STAGING_API, VALIDATE_PRODUCT_GROUPS, VAR_REGEX, activateEntitlementByPurchase, activeEntitlements, aggregateScreenreaderText, allCampaigns, allPaywalls, applyEntitlementActivation, audienceSplitPosition, bestUrlCampaignMatch, bigintToUuid, checkAnySkuHasPromoOffer, checkAnySkuHasTrialOffer, convertISO8601PeriodToText, convertLocale, convertOfferToPricingPhase, createNamiEntitlements, currentSku, empty, extractStandardPricingPhases, formatDate, formattedPrice, generateUUID, getApiCampaigns, getApiPaywalls, getBaseUrl, getBillingPeriodNumber, getCurrencyFormat, getDeviceData, getDeviceFormFactor, getDeviceScaleFactor, getEffectiveWebStyle, getEntitlementRefIdsForSku, getExtendedClientInfo, getFreeTrialPeriod, getInitialCampaigns, getInitialPaywalls, getPaywall, getPaywallDataFromLabel, getPercentagePriceDifference, getPeriodNumberInDays, getPeriodNumberInWeeks, getPlatformAdapters, getPriceDifference, getPricePerMonth, getProductDetail, getPurchaseAdapter, getReferenceSku, getSkuProductDetailKeys, getSkuSmartTextValue, getSlideSmartTextValue, getStandardBillingPeriod, getTranslate, getUrlParams, handleErrors, hasAllPaywalls, hasCapability, hasPurchaseManagement, initialState, invokeHandler, isAnonymousMode, isInitialConfigCompressed, isNamiFlowCampaign, isSubscription, isValidISODate, isValidUrl, logger, mapAnonymousCampaigns, namiBuySKU, normalizeLaunchContext, parseToSemver, postConversion, productDetail, registerPlatformAdapters, registerPurchaseAdapter, selectSegment, setActiveNamiEntitlements, shouldValidateProductGroups, skuItems, skuMapFromEntitlements, storageService, toDouble, toNamiEntitlements, toNamiSKU, tryParseB64Gzip, tryParseJson, updateRelatedSKUsForNamiEntitlement, uuidFromSplitPosition, validateMinSDKVersion };
3154
+ export { ALREADY_CONFIGURED, ANONYMOUS_MODE, ANONYMOUS_MODE_ALREADY_OFF, ANONYMOUS_MODE_ALREADY_ON, ANONYMOUS_MODE_LOGIN_NOT_ALLOWED, ANONYMOUS_UUID, APIError, API_ACTIVE_ENTITLEMENTS, API_CAMPAIGN_RULES, API_CAMPAIGN_SESSION_TIMESTAMP, API_CONFIG, API_MAX_CALLS_LIMIT, API_PAYWALLS, API_PRODUCTS, API_RETRY_DELAY_SEC, API_TIMEOUT_LIMIT, API_VERSION, AUTH_DEVICE, AVAILABLE_ACTIVE_ENTITLEMENTS_CHANGED, AVAILABLE_CAMPAIGNS_CHANGED, AccountStateAction, AnonymousCDPError, AnonymousLoginError, AnonymousModeAlreadyOffError, AnonymousModeAlreadyOnError, BASE_STAGING_URL, BASE_URL, BASE_URL_PATH, BadRequestError, BasicNamiFlow, BorderMap, BorderSideMap, CAMPAIGN_NOT_AVAILABLE, CUSTOMER_ATTRIBUTES_KEY_PREFIX, CUSTOMER_JOURNEY_STATE_CHANGED, CUSTOM_HOST_PREFIX, CampaignNotAvailableError, CampaignRuleConversionEventType, CampaignRuleRepository, Capabilities, ClientError, ConfigRepository, ConflictError, CustomerJourneyRepository, DEVELOPMENT, DEVICE_API_TIMEOUT_LIMIT, DEVICE_ID_NOT_SET, DEVICE_ID_REQUIRED, DISABLE_ASYNC_LOGIN_LOGOUT, DeviceIDRequiredError, DeviceRepository, EXTENDED_CLIENT_INFO_DELIMITER, EXTENDED_CLIENT_INFO_PREFIX, EXTENDED_PLATFORM, EXTENDED_PLATFORM_VERSION, EXTERNAL_ID_REQUIRED, EntitlementRepository, EntitlementUtils, ExternalIDRequiredError, FLOW_SCREENS_NOT_AVAILABLE, FlowScreensNotAvailableError, HTML_REGEX, INITIAL_APP_CONFIG, INITIAL_CAMPAIGN_RULES, INITIAL_PAYWALLS, INITIAL_PRODUCTS, INITIAL_SESSION_COUNTER_VALUE, INITIAL_SUCCESS, InternalServerError, KEY_SESSION_COUNTER, LIQUID_VARIABLE_REGEX, LOCAL_NAMI_ENTITLEMENTS, LOG_HTTP_REQUESTS, LOG_HTTP_TRAFFIC, LaunchCampaignError, LaunchContextResolver, LogLevel, NAMI_CONFIGURATION, NAMI_CUSTOMER_JOURNEY_STATE, NAMI_LANGUAGE_CODE, NAMI_LAST_IMPRESSION_ID, NAMI_LAUNCH_ID, NAMI_PROFILE, NAMI_PURCHASE_CHANNEL, NAMI_PURCHASE_IMPRESSION_ID, NAMI_SDK_PACKAGE_VERSION, NAMI_SDK_VERSION, NAMI_SESSION_ID, NAMI_STORAGE_KEYS, Nami, NamiAPI, NamiAnimationType, NamiCampaignManager, NamiCampaignRuleType, NamiConditionEvaluator, NamiCustomerManager, NamiEntitlementManager, NamiEventEmitter, NamiFlow, NamiFlowActionFunction, NamiFlowManager, NamiFlowStepType, NamiPaywallAction, NamiPaywallManager, PaywallManagerEvents as NamiPaywallManagerEvents, NamiProfileManager, NamiPurchaseManager, NamiRefs, NamiReservedActions, NotFoundError, PAYWALL_ACTION_EVENT, PLATFORM_ID_REQUIRED, PRODUCTION, PaywallManagerEvents, PaywallRepository, PaywallState, PlacementLabelResolver, PlatformIDRequiredError, ProductRepository, RECONFIG_SUCCESS, RetryLimitExceededError, SDKNotInitializedError, SDK_NOT_INITIALIZED, SERVER_NAMI_ENTITLEMENTS, SESSION_REQUIRED, SHOULD_SHOW_LOADING_INDICATOR, SKU_TEXT_REGEX, SMART_TEXT_PATTERN, STATUS_BAD_REQUEST, STATUS_CONFLICT, STATUS_INTERNAL_SERVER_ERROR, STATUS_NOT_FOUND, STATUS_SUCCESS, SessionService, SimpleEventTarget, StorageService, UNABLE_TO_UPDATE_CDP_ID, USE_STAGING_API, VALIDATE_PRODUCT_GROUPS, VAR_REGEX, activateEntitlementByPurchase, activeEntitlements, aggregateScreenreaderText, allCampaigns, allPaywalls, applyEntitlementActivation, audienceSplitPosition, bestUrlCampaignMatch, bigintToUuid, checkAnySkuHasPromoOffer, checkAnySkuHasTrialOffer, convertISO8601PeriodToText, convertLocale, convertOfferToPricingPhase, createNamiEntitlements, currentSku, empty, extractStandardPricingPhases, formatDate, formattedPrice, generateUUID, getApiCampaigns, getApiPaywalls, getBaseUrl, getBillingPeriodNumber, getCurrencyFormat, getDeviceData, getDeviceFormFactor, getDeviceScaleFactor, getEffectiveWebStyle, getEntitlementRefIdsForSku, getExtendedClientInfo, getFreeTrialPeriod, getInitialCampaigns, getInitialPaywalls, getPaywall, getPaywallDataFromLabel, getPercentagePriceDifference, getPeriodNumberInDays, getPeriodNumberInWeeks, getPlatformAdapters, getPriceDifference, getPricePerMonth, getProductDetail, getPurchaseAdapter, getReferenceSku, getSkuProductDetailKeys, getSkuSmartTextValue, getSlideSmartTextValue, getStandardBillingPeriod, getTranslate, getUrlParams, handleErrors, hasAllPaywalls, hasCapability, hasPurchaseManagement, initialState, invokeHandler, isAnonymousMode, isInitialConfigCompressed, isNamiFlowCampaign, isSubscription, isValidISODate, isValidUrl, logger, mapAnonymousCampaigns, namiBuySKU, normalizeLaunchContext, parseToSemver, postConversion, productDetail, registerPlatformAdapters, registerPurchaseAdapter, selectSegment, setActiveNamiEntitlements, shouldValidateProductGroups, skuItems, skuMapFromEntitlements, storageService, toDouble, toNamiEntitlements, toNamiSKU, tryParseB64Gzip, tryParseJson, updateRelatedSKUsForNamiEntitlement, uuidFromSplitPosition, validateMinSDKVersion };
3086
3155
  export type { AlignmentType, AmazonProduct, ApiResponse, AppleProduct, AvailableCampaignsResponseHandler, BorderLocationType, BorderSideType, Callback$1 as Callback, CloseHandler, CustomerJourneyState, DeepLinkUrlHandler, Device, DevicePayload, DeviceProfile, DirectionType, ExtendedPlatformInfo, FlexDirectionObject, FlowNavigationOptions, FontCollection, FontDetails, FormFactor, GoogleProduct, IConfig, IDeviceAdapter, IEntitlements$1 as IEntitlements, IPaywall, IPlatformAdapters, IProductsWithComponents, IPurchaseAdapter, ISkuMenu, IStorageAdapter, IUIAdapter, Impression, InitialConfig, InitialConfigCompressed, InitiateStateGroup, LoginResponse, NamiAnimation, NamiAnimationObjectSpec, NamiAnimationSpec, NamiAnonymousCampaign, NamiAppSuppliedVideoDetails, NamiCampaign, NamiCampaignSegment, NamiConfiguration, NamiConfigurationState, NamiEntitlement$1 as NamiEntitlement, NamiFlowAction, NamiFlowAnimation, NamiFlowCampaign, NamiFlowDTO, NamiFlowEventHandler, NamiFlowHandoffStepHandler, NamiFlowObjectDTO, NamiFlowOn, NamiFlowStep, NamiFlowTransition, NamiFlowTransitionDirection, NamiFlowWithObject, NamiInitialConfig, NamiLanguageCodes, NamiLogLevel, NamiPaywallActionHandler, NamiPaywallComponentChange, NamiPaywallEvent, NamiPaywallEventVideoMetadata, NamiPaywallLaunchContext, NamiPresentationStyle, NamiProductDetails, NamiProductOffer, NamiProfile, NamiPurchase, NamiPurchaseCompleteResult, NamiPurchaseDetails, NamiPurchasesState, NamiSKU, NamiSKUType, NamiSubscriptionInterval, NamiSubscriptionPeriod, None, NoneSpec, PaywallActionEvent, PaywallHandle, PaywallResultHandler, PaywallSKU, PricingPhase, ProductGroup, Pulse, PulseSpec, PurchaseContext, PurchaseResult, PurchaseValidationRequest, SKU, SKUActionHandler, ScreenInfo, Session, TBaseComponent, TButtonContainer, TCarouselContainer, TCarouselSlide, TCarouselSlidesState, TCollapseContainer, TComponent, TConditionalAttributes, TConditionalComponent, TContainer, TContainerPosition, TCountdownTimerTextComponent, TDevice, TDisabledButton, TField, TFieldSettings, TFlexProductContainer, THeaderFooter, TImageComponent, TInitialState, TMediaTypes, TOffer, TPages, TPaywallContext, TPaywallLaunchContext, TPaywallMedia, TPaywallTemplate, TPlayPauseButton, TProductContainer, TProductGroup, TProgressBarComponent, TProgressIndicatorComponent, TQRCodeComponent, TRadioButton, TRepeatingGrid, TResponsiveGrid, TSegmentPicker, TSegmentPickerItem, TSemverObj, TSpacerComponent, TStack, TSvgImageComponent, TSymbolComponent, TTestObject, TTextComponent, TTextLikeComponent, TTextListComponent, TToggleButtonComponent, TToggleSwitch, TVariablePattern, TVideoComponent, TVolumeButton, TimerState, TransactionRequest, UserAction, UserActionParameters, Wave, WaveSpec };
package/dist/index.mjs CHANGED
@@ -96,7 +96,7 @@ const {
96
96
  // version — stamped by scripts/version.sh
97
97
  NAMI_SDK_VERSION = "3.4.0",
98
98
  // full package version including dev suffix — stamped by scripts/version.sh
99
- NAMI_SDK_PACKAGE_VERSION = "3.4.0-dev.202605190929",
99
+ NAMI_SDK_PACKAGE_VERSION = "3.4.0-dev.202605191752",
100
100
  // environments
101
101
  PRODUCTION = "production", DEVELOPMENT = "development",
102
102
  // error messages
@@ -6925,6 +6925,21 @@ class NamiAPI {
6925
6925
  this.baseURL = getBaseUrl(config.namiCommands);
6926
6926
  this.platformID = config.appPlatformID;
6927
6927
  }
6928
+ /**
6929
+ * Clear cached baseURL and platformID so the next `configure()` call
6930
+ * re-derives them from fresh storage. Called from `Nami.reset()`.
6931
+ *
6932
+ * Without this, the NamiAPI singleton holds the previous lifecycle's
6933
+ * platform/base URL even after storage has been wiped — any in-flight
6934
+ * or queued request after reset would hit the old endpoint.
6935
+ */
6936
+ static reset() {
6937
+ this.instance.reset();
6938
+ }
6939
+ reset() {
6940
+ this.baseURL = undefined;
6941
+ this.platformID = undefined;
6942
+ }
6928
6943
  async login(externalId) {
6929
6944
  if (!externalId) {
6930
6945
  throw new ExternalIDRequiredError();
@@ -7998,6 +8013,18 @@ class CampaignRuleRepository {
7998
8013
  this.disableCampaignUpdates = namiCommands.includes("disableCampaignUpdates");
7999
8014
  this.useLegacyPaywallFetch = namiCommands.includes("useLegacyPaywallFetch");
8000
8015
  }
8016
+ /**
8017
+ * Reset the singleton's configured state. Called from `Nami.reset()`
8018
+ * so the previous lifecycle's split position and namiCommands flags
8019
+ * don't leak into the next configure() cycle. The form factor reverts
8020
+ * to the platform's natural value via `getDeviceFormFactor()`.
8021
+ */
8022
+ reset() {
8023
+ this.currentFormFactor = getDeviceFormFactor();
8024
+ this.splitPosition = undefined;
8025
+ this.disableCampaignUpdates = false;
8026
+ this.useLegacyPaywallFetch = false;
8027
+ }
8001
8028
  async fetchCampaignRules(paywalls) {
8002
8029
  // get deviceId
8003
8030
  const authDevice = storageService.getDevice();
@@ -11308,6 +11335,15 @@ class PaywallState extends SimpleEventTarget {
11308
11335
  static remove(provider) {
11309
11336
  this.providers = this.providers.filter(p => p !== provider);
11310
11337
  }
11338
+ /**
11339
+ * Clear the static `providers` array. Called from `Nami.reset()` so
11340
+ * orphaned PaywallState instances from a previous SDK lifecycle don't
11341
+ * receive customer-attribute / login / product-detail updates intended
11342
+ * for the next configure() cycle.
11343
+ */
11344
+ static reset() {
11345
+ this.providers = [];
11346
+ }
11311
11347
  static get currentProvider() {
11312
11348
  return PaywallState.providers[0];
11313
11349
  }
@@ -12061,7 +12097,87 @@ function isEqual(objA, objB) {
12061
12097
  return isEqual$1(normA, normB);
12062
12098
  }
12063
12099
 
12100
+ class NamiProfileManager {
12101
+ constructor() {
12102
+ this.load();
12103
+ }
12104
+ setExternalId(externalId) {
12105
+ this.externalId = externalId;
12106
+ return this;
12107
+ }
12108
+ getExternalId() {
12109
+ return this.externalId;
12110
+ }
12111
+ isLoggedIn() {
12112
+ return !!this.externalId;
12113
+ }
12114
+ save() {
12115
+ if (this.externalId) {
12116
+ storageService.setNamiProfile({
12117
+ externalId: this.externalId,
12118
+ });
12119
+ }
12120
+ else {
12121
+ storageService.removeNamiProfile();
12122
+ }
12123
+ return this;
12124
+ }
12125
+ load() {
12126
+ const profile = storageService.getNamiProfile();
12127
+ if (profile) {
12128
+ this.externalId = profile.externalId;
12129
+ }
12130
+ PaywallState.setIsLoggedIn(!!this.externalId);
12131
+ }
12132
+ /**
12133
+ * Update local profile state for login: persist the external id and flip the
12134
+ * logged-in flag in PaywallState. No network I/O — pair with `loginRemote`
12135
+ * to perform the actual API call. Used by the async login/logout fast path.
12136
+ */
12137
+ loginLocal(externalId) {
12138
+ this.setExternalId(externalId).save();
12139
+ PaywallState.setIsLoggedIn(true);
12140
+ }
12141
+ /**
12142
+ * Perform the login network call only. If the server returns a different
12143
+ * `external_id` than the locally stored value, reconcile it into local state.
12144
+ */
12145
+ async loginRemote(externalId) {
12146
+ const loginData = await NamiAPI.instance.login(externalId);
12147
+ if (loginData.external_id && loginData.external_id !== this.externalId) {
12148
+ this.setExternalId(loginData.external_id).save();
12149
+ }
12150
+ }
12151
+ /**
12152
+ * Update local profile state for logout: clear the external id, persist,
12153
+ * and flip the logged-in flag in PaywallState. Used by the async fast path.
12154
+ */
12155
+ logoutLocal() {
12156
+ this.setExternalId(undefined).save();
12157
+ PaywallState.setIsLoggedIn(false);
12158
+ }
12159
+ /**
12160
+ * Perform the logout network call only.
12161
+ */
12162
+ async logoutRemote() {
12163
+ await NamiAPI.instance.logout();
12164
+ }
12165
+ async login(externalId) {
12166
+ this.loginLocal(externalId);
12167
+ await this.loginRemote(externalId);
12168
+ }
12169
+ async logout() {
12170
+ this.logoutLocal();
12171
+ await this.logoutRemote();
12172
+ }
12173
+ }
12174
+ NamiProfileManager.instance = new NamiProfileManager();
12175
+
12064
12176
  var _Nami_isInitialized;
12177
+ // NamiFlowManager is intentionally NOT imported at top level — it
12178
+ // transitively imports back to this module (`Nami` for logging), and
12179
+ // pulling it in here causes a load-order cycle that breaks tests
12180
+ // importing `ConditionalEvaluator`. It's loaded lazily inside `reset()`.
12065
12181
  class Nami {
12066
12182
  constructor() {
12067
12183
  _Nami_isInitialized.set(this, false);
@@ -12078,13 +12194,32 @@ class Nami {
12078
12194
  }
12079
12195
  /**
12080
12196
  * Clear all locally persisted SDK state (device ID, customer attributes,
12081
- * campaigns/paywalls/products caches, session, and anonymous-mode flags).
12082
- * After reset, the next configure() call regenerates the device ID.
12197
+ * campaigns/paywalls/products caches, session, and anonymous-mode flags)
12198
+ * AND the in-memory state held by module-level singletons so the next
12199
+ * configure() call starts from a truly clean slate.
12200
+ *
12201
+ * Host-registered handler refs (flow event/handoff handlers, customer
12202
+ * journey/account state handlers, purchase complete handler) are
12203
+ * intentionally preserved across reset — they're owned by the
12204
+ * integrating app, consistent with the Apple and Android SDKs.
12083
12205
  */
12084
12206
  static async reset() {
12085
12207
  storageService.clearAll();
12086
12208
  clearInMemoryAnonymousMode();
12087
12209
  __classPrivateFieldSet(Nami.instance, _Nami_isInitialized, false, "f");
12210
+ // In-memory singleton state that previously survived reset and
12211
+ // bled into the next configure() cycle.
12212
+ NamiProfileManager.instance.setExternalId(undefined);
12213
+ PaywallState.reset();
12214
+ NamiAPI.reset();
12215
+ CampaignRuleRepository.instance.reset();
12216
+ // Lazy import to avoid a load-order cycle (NamiFlowManager imports
12217
+ // back to this module for `Nami.instance.maxLogging`).
12218
+ const { NamiFlowManager } = await Promise.resolve().then(function () { return NamiFlowManager$1; });
12219
+ NamiFlowManager.reset();
12220
+ // Give platform adapters a chance to drop their own state
12221
+ // (e.g. Expo's UI adapter holds listener refs from PaywallView).
12222
+ getPlatformAdapters().ui.onReset?.();
12088
12223
  }
12089
12224
  /**
12090
12225
  * Configures and initializes the SDK.
@@ -12961,57 +13096,6 @@ class PlacementLabelResolver extends BaseNamespaceResolver {
12961
13096
  }
12962
13097
  }
12963
13098
 
12964
- class NamiProfileManager {
12965
- constructor() {
12966
- this.load();
12967
- }
12968
- setExternalId(externalId) {
12969
- this.externalId = externalId;
12970
- return this;
12971
- }
12972
- getExternalId() {
12973
- return this.externalId;
12974
- }
12975
- isLoggedIn() {
12976
- return !!this.externalId;
12977
- }
12978
- save() {
12979
- if (this.externalId) {
12980
- storageService.setNamiProfile({
12981
- externalId: this.externalId,
12982
- });
12983
- }
12984
- else {
12985
- storageService.removeNamiProfile();
12986
- }
12987
- return this;
12988
- }
12989
- load() {
12990
- const profile = storageService.getNamiProfile();
12991
- if (profile) {
12992
- this.externalId = profile.externalId;
12993
- }
12994
- PaywallState.setIsLoggedIn(!!this.externalId);
12995
- }
12996
- async login(externalId) {
12997
- const loginData = await NamiAPI.instance.login(externalId);
12998
- if (loginData.external_id) {
12999
- PaywallState.setIsLoggedIn(true);
13000
- }
13001
- this
13002
- .setExternalId(loginData.external_id)
13003
- .save();
13004
- }
13005
- async logout() {
13006
- await NamiAPI.instance.logout();
13007
- PaywallState.setIsLoggedIn(false);
13008
- this
13009
- .setExternalId(undefined)
13010
- .save();
13011
- }
13012
- }
13013
- NamiProfileManager.instance = new NamiProfileManager();
13014
-
13015
13099
  /**
13016
13100
  * Represents the possible account actions states that can be returned by callback registered from
13017
13101
  * [NamiCustomerManager.registerAccountStateHandler]
@@ -13044,6 +13128,23 @@ class NamiFlowManager {
13044
13128
  this.lastAnimatedFlowProgress = new Map();
13045
13129
  this.navGraphCache = new WeakMap();
13046
13130
  }
13131
+ /**
13132
+ * Clear flow data state. Called from `Nami.reset()`.
13133
+ *
13134
+ * Resets the active flow, openness flag, animation progress, and the
13135
+ * nav-graph cache. Host-registered handler refs (`handoffStepHandler`,
13136
+ * `eventHandler`) are intentionally preserved across reset — they're
13137
+ * owned by the integrating app via `registerStepHandoff()` /
13138
+ * `registerEventHandler()`, consistent with how Apple/Android handle
13139
+ * host callbacks across reset.
13140
+ */
13141
+ static reset() {
13142
+ const i = NamiFlowManager.instance;
13143
+ i.currentFlow = undefined;
13144
+ i.flowOpen = false;
13145
+ i.lastAnimatedFlowProgress = new Map();
13146
+ i.navGraphCache = new WeakMap();
13147
+ }
13047
13148
  static registerStepHandoff(handoffStepHandler) {
13048
13149
  this.instance.handoffStepHandler = handoffStepHandler;
13049
13150
  }
@@ -13327,6 +13428,26 @@ class NamiFlowManager {
13327
13428
  }
13328
13429
  }
13329
13430
 
13431
+ var NamiFlowManager$1 = /*#__PURE__*/Object.freeze({
13432
+ __proto__: null,
13433
+ NamiFlowManager: NamiFlowManager
13434
+ });
13435
+
13436
+ const SHOULD_SHOW_LOADING_INDICATOR = false;
13437
+ const DISABLE_ASYNC_LOGIN_LOGOUT = "disableAsyncLoginLogout";
13438
+ /**
13439
+ * Returns true when `disableAsyncLoginLogout` appears in the active `namiCommands` list,
13440
+ * meaning login/logout should await the underlying API call before notifying handlers.
13441
+ *
13442
+ * Default behavior (flag absent) is the async/fast path: update local state and notify
13443
+ * handlers immediately while dispatching the API call as fire-and-forget.
13444
+ *
13445
+ * Internal feature flag — opt-out via `Nami.configure({ namiCommands: [...] })`.
13446
+ */
13447
+ const isAsyncLoginLogoutDisabled = () => {
13448
+ return storageService.getNamiConfig()?.namiCommands?.includes(DISABLE_ASYNC_LOGIN_LOGOUT) ?? false;
13449
+ };
13450
+
13330
13451
  /**
13331
13452
  * @class NamiCustomerManager
13332
13453
  * Provides methods for managing customer-related functionality.
@@ -13388,6 +13509,30 @@ class NamiCustomerManager {
13388
13509
  this.invokeStateHandler(AccountStateAction.LOGIN, false, error);
13389
13510
  throw error;
13390
13511
  }
13512
+ if (!isAsyncLoginLogoutDisabled()) {
13513
+ // Default sync/fast path: update local state, notify handlers, and fire
13514
+ // lifecycle immediately. Dispatch the underlying API call and entitlement
13515
+ // refresh fire-and-forget — failures are logged but do not surface.
13516
+ NamiProfileManager.instance.loginLocal(externalId);
13517
+ this.invokeStateHandler(AccountStateAction.LOGIN, true);
13518
+ if (NamiFlowManager.instance.flowOpen) {
13519
+ const currentFlow = NamiFlowManager.instance.currentFlow;
13520
+ const currentStep = currentFlow?.currentFlowStep;
13521
+ if (currentStep) {
13522
+ if (Nami.instance.maxLogging) {
13523
+ logger.debug(`[NamiCustomerManager] async login success — triggering __login_success__ on step ${currentStep.id}`);
13524
+ }
13525
+ currentFlow.executeLifecycle(currentStep, NamiReservedActions.LOGIN_SUCCESS);
13526
+ }
13527
+ }
13528
+ void NamiProfileManager.instance.loginRemote(externalId).catch((err) => {
13529
+ logger.error('[NamiCustomerManager] async login API failed', err);
13530
+ });
13531
+ void EntitlementRepository.instance.fetchActiveEntitlements().catch((err) => {
13532
+ logger.error('[NamiCustomerManager] async login entitlement refresh failed', err);
13533
+ });
13534
+ return;
13535
+ }
13391
13536
  try {
13392
13537
  await NamiProfileManager.instance.login(externalId);
13393
13538
  await EntitlementRepository.instance.fetchActiveEntitlements();
@@ -13433,6 +13578,30 @@ class NamiCustomerManager {
13433
13578
  this.invokeStateHandler(AccountStateAction.LOGOUT, false, error);
13434
13579
  throw error;
13435
13580
  }
13581
+ if (!isAsyncLoginLogoutDisabled()) {
13582
+ // Default sync/fast path: clear local state, notify handlers, and fire
13583
+ // lifecycle immediately. Dispatch the underlying API call and entitlement
13584
+ // refresh fire-and-forget — failures are logged but do not surface.
13585
+ NamiProfileManager.instance.logoutLocal();
13586
+ this.invokeStateHandler(AccountStateAction.LOGOUT, true);
13587
+ if (NamiFlowManager.instance.flowOpen) {
13588
+ const currentFlow = NamiFlowManager.instance.currentFlow;
13589
+ const currentStep = currentFlow?.currentFlowStep;
13590
+ if (currentStep) {
13591
+ if (Nami.instance.maxLogging) {
13592
+ logger.debug(`[NamiCustomerManager] async logout success — triggering __logout_success__ on step ${currentStep.id}`);
13593
+ }
13594
+ currentFlow.executeLifecycle(currentStep, NamiReservedActions.LOGOUT_SUCCESS);
13595
+ }
13596
+ }
13597
+ void NamiProfileManager.instance.logoutRemote().catch((err) => {
13598
+ logger.error('[NamiCustomerManager] async logout API failed', err);
13599
+ });
13600
+ void EntitlementRepository.instance.fetchActiveEntitlements().catch((err) => {
13601
+ logger.error('[NamiCustomerManager] async logout entitlement refresh failed', err);
13602
+ });
13603
+ return;
13604
+ }
13436
13605
  try {
13437
13606
  await NamiProfileManager.instance.logout();
13438
13607
  await EntitlementRepository.instance.fetchActiveEntitlements();
@@ -14867,8 +15036,6 @@ var CampaignRuleConversionEventType;
14867
15036
  CampaignRuleConversionEventType["IN_APP"] = "in_app";
14868
15037
  })(CampaignRuleConversionEventType || (CampaignRuleConversionEventType = {}));
14869
15038
 
14870
- const SHOULD_SHOW_LOADING_INDICATOR = false;
14871
-
14872
15039
  function parseToSemver(versionString) {
14873
15040
  const [semVer, major, minor, patch, prerelease, buildmetadata] = versionString.match(/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/) ?? [];
14874
15041
  return {
@@ -64328,4 +64495,4 @@ function namiBuySKU(skuRefId) {
64328
64495
  return result;
64329
64496
  }
64330
64497
 
64331
- export { ALREADY_CONFIGURED, ANONYMOUS_MODE, ANONYMOUS_MODE_ALREADY_OFF, ANONYMOUS_MODE_ALREADY_ON, ANONYMOUS_MODE_LOGIN_NOT_ALLOWED, ANONYMOUS_UUID, APIError, API_ACTIVE_ENTITLEMENTS, API_CAMPAIGN_RULES, API_CAMPAIGN_SESSION_TIMESTAMP, API_CONFIG, API_MAX_CALLS_LIMIT, API_PAYWALLS, API_PRODUCTS, API_RETRY_DELAY_SEC, API_TIMEOUT_LIMIT, API_VERSION, AUTH_DEVICE, AVAILABLE_ACTIVE_ENTITLEMENTS_CHANGED, AVAILABLE_CAMPAIGNS_CHANGED, AccountStateAction, AnonymousCDPError, AnonymousLoginError, AnonymousModeAlreadyOffError, AnonymousModeAlreadyOnError, BASE_STAGING_URL, BASE_URL, BASE_URL_PATH, BadRequestError, BasicNamiFlow, BorderMap, BorderSideMap, CAMPAIGN_NOT_AVAILABLE, CUSTOMER_ATTRIBUTES_KEY_PREFIX, CUSTOMER_JOURNEY_STATE_CHANGED, CUSTOM_HOST_PREFIX, CampaignNotAvailableError, CampaignRuleConversionEventType, CampaignRuleRepository, Capabilities, ClientError, ConfigRepository, ConflictError, CustomerJourneyRepository, DEVELOPMENT, DEVICE_API_TIMEOUT_LIMIT, DEVICE_ID_NOT_SET, DEVICE_ID_REQUIRED, DeviceIDRequiredError, DeviceRepository, EXTENDED_CLIENT_INFO_DELIMITER, EXTENDED_CLIENT_INFO_PREFIX, EXTENDED_PLATFORM, EXTENDED_PLATFORM_VERSION, EXTERNAL_ID_REQUIRED, EntitlementRepository, EntitlementUtils, ExternalIDRequiredError, FLOW_SCREENS_NOT_AVAILABLE, FlowScreensNotAvailableError, HTML_REGEX, INITIAL_APP_CONFIG, INITIAL_CAMPAIGN_RULES, INITIAL_PAYWALLS, INITIAL_PRODUCTS, INITIAL_SESSION_COUNTER_VALUE, INITIAL_SUCCESS, InternalServerError, KEY_SESSION_COUNTER, LIQUID_VARIABLE_REGEX, LOCAL_NAMI_ENTITLEMENTS, LOG_HTTP_REQUESTS, LOG_HTTP_TRAFFIC, LaunchCampaignError, LaunchContextResolver, LogLevel, NAMI_CONFIGURATION, NAMI_CUSTOMER_JOURNEY_STATE, NAMI_LANGUAGE_CODE, NAMI_LAST_IMPRESSION_ID, NAMI_LAUNCH_ID, NAMI_PROFILE, NAMI_PURCHASE_CHANNEL, NAMI_PURCHASE_IMPRESSION_ID, NAMI_SDK_PACKAGE_VERSION, NAMI_SDK_VERSION, NAMI_SESSION_ID, NAMI_STORAGE_KEYS, Nami, NamiAPI, NamiAnimationType, NamiCampaignManager, NamiCampaignRuleType, NamiConditionEvaluator, NamiCustomerManager, NamiEntitlementManager, NamiEventEmitter, NamiFlow, NamiFlowActionFunction, NamiFlowManager, NamiFlowStepType, NamiPaywallAction, NamiPaywallManager, PaywallManagerEvents as NamiPaywallManagerEvents, NamiProfileManager, NamiPurchaseManager, NamiRefs, NamiReservedActions, NotFoundError, PAYWALL_ACTION_EVENT, PLATFORM_ID_REQUIRED, PRODUCTION, PaywallManagerEvents, PaywallRepository, PaywallState, PlacementLabelResolver, PlatformIDRequiredError, ProductRepository, RECONFIG_SUCCESS, RetryLimitExceededError, SDKNotInitializedError, SDK_NOT_INITIALIZED, SERVER_NAMI_ENTITLEMENTS, SESSION_REQUIRED, SHOULD_SHOW_LOADING_INDICATOR, SKU_TEXT_REGEX, SMART_TEXT_PATTERN, STATUS_BAD_REQUEST, STATUS_CONFLICT, STATUS_INTERNAL_SERVER_ERROR, STATUS_NOT_FOUND, STATUS_SUCCESS, SessionService, SimpleEventTarget, StorageService, UNABLE_TO_UPDATE_CDP_ID, USE_STAGING_API, VALIDATE_PRODUCT_GROUPS, VAR_REGEX, activateEntitlementByPurchase, activeEntitlements, aggregateScreenreaderText, allCampaigns, allPaywalls, applyEntitlementActivation, audienceSplitPosition, bestUrlCampaignMatch, bigintToUuid, checkAnySkuHasPromoOffer, checkAnySkuHasTrialOffer, convertISO8601PeriodToText, convertLocale, convertOfferToPricingPhase, createNamiEntitlements, currentSku, empty, extractStandardPricingPhases, formatDate, formattedPrice, generateUUID, getApiCampaigns, getApiPaywalls, getBaseUrl, getBillingPeriodNumber, getCurrencyFormat, getDeviceData, getDeviceFormFactor, getDeviceScaleFactor, getEffectiveWebStyle, getEntitlementRefIdsForSku, getExtendedClientInfo, getFreeTrialPeriod, getInitialCampaigns, getInitialPaywalls, getPaywall, getPaywallDataFromLabel, getPercentagePriceDifference, getPeriodNumberInDays, getPeriodNumberInWeeks, getPlatformAdapters, getPriceDifference, getPricePerMonth, getProductDetail, getPurchaseAdapter, getReferenceSku, getSkuProductDetailKeys, getSkuSmartTextValue, getSlideSmartTextValue, getStandardBillingPeriod, getTranslate, getUrlParams, handleErrors, hasAllPaywalls, hasCapability, hasPurchaseManagement, initialState, invokeHandler, isAnonymousMode, isInitialConfigCompressed, isNamiFlowCampaign, isSubscription, isValidISODate, isValidUrl, logger, mapAnonymousCampaigns, namiBuySKU, normalizeLaunchContext, parseToSemver, postConversion, productDetail, registerPlatformAdapters, registerPurchaseAdapter, selectSegment, setActiveNamiEntitlements, shouldValidateProductGroups, skuItems, skuMapFromEntitlements, storageService, toDouble, toNamiEntitlements, toNamiSKU, tryParseB64Gzip, tryParseJson, updateRelatedSKUsForNamiEntitlement, uuidFromSplitPosition, validateMinSDKVersion };
64498
+ export { ALREADY_CONFIGURED, ANONYMOUS_MODE, ANONYMOUS_MODE_ALREADY_OFF, ANONYMOUS_MODE_ALREADY_ON, ANONYMOUS_MODE_LOGIN_NOT_ALLOWED, ANONYMOUS_UUID, APIError, API_ACTIVE_ENTITLEMENTS, API_CAMPAIGN_RULES, API_CAMPAIGN_SESSION_TIMESTAMP, API_CONFIG, API_MAX_CALLS_LIMIT, API_PAYWALLS, API_PRODUCTS, API_RETRY_DELAY_SEC, API_TIMEOUT_LIMIT, API_VERSION, AUTH_DEVICE, AVAILABLE_ACTIVE_ENTITLEMENTS_CHANGED, AVAILABLE_CAMPAIGNS_CHANGED, AccountStateAction, AnonymousCDPError, AnonymousLoginError, AnonymousModeAlreadyOffError, AnonymousModeAlreadyOnError, BASE_STAGING_URL, BASE_URL, BASE_URL_PATH, BadRequestError, BasicNamiFlow, BorderMap, BorderSideMap, CAMPAIGN_NOT_AVAILABLE, CUSTOMER_ATTRIBUTES_KEY_PREFIX, CUSTOMER_JOURNEY_STATE_CHANGED, CUSTOM_HOST_PREFIX, CampaignNotAvailableError, CampaignRuleConversionEventType, CampaignRuleRepository, Capabilities, ClientError, ConfigRepository, ConflictError, CustomerJourneyRepository, DEVELOPMENT, DEVICE_API_TIMEOUT_LIMIT, DEVICE_ID_NOT_SET, DEVICE_ID_REQUIRED, DISABLE_ASYNC_LOGIN_LOGOUT, DeviceIDRequiredError, DeviceRepository, EXTENDED_CLIENT_INFO_DELIMITER, EXTENDED_CLIENT_INFO_PREFIX, EXTENDED_PLATFORM, EXTENDED_PLATFORM_VERSION, EXTERNAL_ID_REQUIRED, EntitlementRepository, EntitlementUtils, ExternalIDRequiredError, FLOW_SCREENS_NOT_AVAILABLE, FlowScreensNotAvailableError, HTML_REGEX, INITIAL_APP_CONFIG, INITIAL_CAMPAIGN_RULES, INITIAL_PAYWALLS, INITIAL_PRODUCTS, INITIAL_SESSION_COUNTER_VALUE, INITIAL_SUCCESS, InternalServerError, KEY_SESSION_COUNTER, LIQUID_VARIABLE_REGEX, LOCAL_NAMI_ENTITLEMENTS, LOG_HTTP_REQUESTS, LOG_HTTP_TRAFFIC, LaunchCampaignError, LaunchContextResolver, LogLevel, NAMI_CONFIGURATION, NAMI_CUSTOMER_JOURNEY_STATE, NAMI_LANGUAGE_CODE, NAMI_LAST_IMPRESSION_ID, NAMI_LAUNCH_ID, NAMI_PROFILE, NAMI_PURCHASE_CHANNEL, NAMI_PURCHASE_IMPRESSION_ID, NAMI_SDK_PACKAGE_VERSION, NAMI_SDK_VERSION, NAMI_SESSION_ID, NAMI_STORAGE_KEYS, Nami, NamiAPI, NamiAnimationType, NamiCampaignManager, NamiCampaignRuleType, NamiConditionEvaluator, NamiCustomerManager, NamiEntitlementManager, NamiEventEmitter, NamiFlow, NamiFlowActionFunction, NamiFlowManager, NamiFlowStepType, NamiPaywallAction, NamiPaywallManager, PaywallManagerEvents as NamiPaywallManagerEvents, NamiProfileManager, NamiPurchaseManager, NamiRefs, NamiReservedActions, NotFoundError, PAYWALL_ACTION_EVENT, PLATFORM_ID_REQUIRED, PRODUCTION, PaywallManagerEvents, PaywallRepository, PaywallState, PlacementLabelResolver, PlatformIDRequiredError, ProductRepository, RECONFIG_SUCCESS, RetryLimitExceededError, SDKNotInitializedError, SDK_NOT_INITIALIZED, SERVER_NAMI_ENTITLEMENTS, SESSION_REQUIRED, SHOULD_SHOW_LOADING_INDICATOR, SKU_TEXT_REGEX, SMART_TEXT_PATTERN, STATUS_BAD_REQUEST, STATUS_CONFLICT, STATUS_INTERNAL_SERVER_ERROR, STATUS_NOT_FOUND, STATUS_SUCCESS, SessionService, SimpleEventTarget, StorageService, UNABLE_TO_UPDATE_CDP_ID, USE_STAGING_API, VALIDATE_PRODUCT_GROUPS, VAR_REGEX, activateEntitlementByPurchase, activeEntitlements, aggregateScreenreaderText, allCampaigns, allPaywalls, applyEntitlementActivation, audienceSplitPosition, bestUrlCampaignMatch, bigintToUuid, checkAnySkuHasPromoOffer, checkAnySkuHasTrialOffer, convertISO8601PeriodToText, convertLocale, convertOfferToPricingPhase, createNamiEntitlements, currentSku, empty, extractStandardPricingPhases, formatDate, formattedPrice, generateUUID, getApiCampaigns, getApiPaywalls, getBaseUrl, getBillingPeriodNumber, getCurrencyFormat, getDeviceData, getDeviceFormFactor, getDeviceScaleFactor, getEffectiveWebStyle, getEntitlementRefIdsForSku, getExtendedClientInfo, getFreeTrialPeriod, getInitialCampaigns, getInitialPaywalls, getPaywall, getPaywallDataFromLabel, getPercentagePriceDifference, getPeriodNumberInDays, getPeriodNumberInWeeks, getPlatformAdapters, getPriceDifference, getPricePerMonth, getProductDetail, getPurchaseAdapter, getReferenceSku, getSkuProductDetailKeys, getSkuSmartTextValue, getSlideSmartTextValue, getStandardBillingPeriod, getTranslate, getUrlParams, handleErrors, hasAllPaywalls, hasCapability, hasPurchaseManagement, initialState, invokeHandler, isAnonymousMode, isInitialConfigCompressed, isNamiFlowCampaign, isSubscription, isValidISODate, isValidUrl, logger, mapAnonymousCampaigns, namiBuySKU, normalizeLaunchContext, parseToSemver, postConversion, productDetail, registerPlatformAdapters, registerPurchaseAdapter, selectSegment, setActiveNamiEntitlements, shouldValidateProductGroups, skuItems, skuMapFromEntitlements, storageService, toDouble, toNamiEntitlements, toNamiSKU, tryParseB64Gzip, tryParseJson, updateRelatedSKUsForNamiEntitlement, uuidFromSplitPosition, validateMinSDKVersion };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@namiml/sdk-core",
3
- "version": "3.4.0-dev.202605190929",
3
+ "version": "3.4.0-dev.202605191752",
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",