@namiml/sdk-core 3.4.0-dev.202605191719 → 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.202605191719",
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,82 +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
- /**
12999
- * Update local profile state for login: persist the external id and flip the
13000
- * logged-in flag in PaywallState. No network I/O — pair with `loginRemote`
13001
- * to perform the actual API call. Used by the async login/logout fast path.
13002
- */
13003
- loginLocal(externalId) {
13004
- this.setExternalId(externalId).save();
13005
- PaywallState.setIsLoggedIn(true);
13006
- }
13007
- /**
13008
- * Perform the login network call only. If the server returns a different
13009
- * `external_id` than the locally stored value, reconcile it into local state.
13010
- */
13011
- async loginRemote(externalId) {
13012
- const loginData = await NamiAPI.instance.login(externalId);
13013
- if (loginData.external_id && loginData.external_id !== this.externalId) {
13014
- this.setExternalId(loginData.external_id).save();
13015
- }
13016
- }
13017
- /**
13018
- * Update local profile state for logout: clear the external id, persist,
13019
- * and flip the logged-in flag in PaywallState. Used by the async fast path.
13020
- */
13021
- logoutLocal() {
13022
- this.setExternalId(undefined).save();
13023
- PaywallState.setIsLoggedIn(false);
13024
- }
13025
- /**
13026
- * Perform the logout network call only.
13027
- */
13028
- async logoutRemote() {
13029
- await NamiAPI.instance.logout();
13030
- }
13031
- async login(externalId) {
13032
- this.loginLocal(externalId);
13033
- await this.loginRemote(externalId);
13034
- }
13035
- async logout() {
13036
- this.logoutLocal();
13037
- await this.logoutRemote();
13038
- }
13039
- }
13040
- NamiProfileManager.instance = new NamiProfileManager();
13041
-
13042
13101
  /**
13043
13102
  * Represents the possible account actions states that can be returned by callback registered from
13044
13103
  * [NamiCustomerManager.registerAccountStateHandler]
@@ -13071,6 +13130,23 @@ class NamiFlowManager {
13071
13130
  this.lastAnimatedFlowProgress = new Map();
13072
13131
  this.navGraphCache = new WeakMap();
13073
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
+ }
13074
13150
  static registerStepHandoff(handoffStepHandler) {
13075
13151
  this.instance.handoffStepHandler = handoffStepHandler;
13076
13152
  }
@@ -13354,6 +13430,11 @@ class NamiFlowManager {
13354
13430
  }
13355
13431
  }
13356
13432
 
13433
+ var NamiFlowManager$1 = /*#__PURE__*/Object.freeze({
13434
+ __proto__: null,
13435
+ NamiFlowManager: NamiFlowManager
13436
+ });
13437
+
13357
13438
  const SHOULD_SHOW_LOADING_INDICATOR = false;
13358
13439
  const DISABLE_ASYNC_LOGIN_LOGOUT = "disableAsyncLoginLogout";
13359
13440
  /**
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
  /**
@@ -2327,6 +2351,13 @@ declare class PaywallState extends SimpleEventTarget {
2327
2351
  filteredSkuMenus: ISkuMenu[];
2328
2352
  static create(paywall: IPaywall, context: NamiPaywallLaunchContext, campaign: NamiCampaign): PaywallState;
2329
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;
2330
2361
  static get currentProvider(): PaywallState | undefined;
2331
2362
  static setCustomerAttribute(attributes: {
2332
2363
  [key: string]: string;
@@ -2735,6 +2766,16 @@ declare class NamiAPI {
2735
2766
  constructor();
2736
2767
  static configure(config: NamiConfiguration): void;
2737
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;
2738
2779
  login(externalId: string): Promise<LoginResponse>;
2739
2780
  logout(): Promise<Record<string, never>>;
2740
2781
  startSession(sessionStartTime: Date): Promise<void>;
@@ -2948,6 +2989,13 @@ declare class CampaignRuleRepository {
2948
2989
  static instance: CampaignRuleRepository;
2949
2990
  constructor();
2950
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;
2951
2999
  fetchCampaignRules(paywalls: IPaywall[]): Promise<NamiCampaign[]>;
2952
3000
  static extractPaywallUrls(campaigns: NamiCampaign[]): string[];
2953
3001
  static hasPaywallUrls(campaigns: NamiCampaign[]): boolean;
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.202605191719",
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,82 +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
- /**
12997
- * Update local profile state for login: persist the external id and flip the
12998
- * logged-in flag in PaywallState. No network I/O — pair with `loginRemote`
12999
- * to perform the actual API call. Used by the async login/logout fast path.
13000
- */
13001
- loginLocal(externalId) {
13002
- this.setExternalId(externalId).save();
13003
- PaywallState.setIsLoggedIn(true);
13004
- }
13005
- /**
13006
- * Perform the login network call only. If the server returns a different
13007
- * `external_id` than the locally stored value, reconcile it into local state.
13008
- */
13009
- async loginRemote(externalId) {
13010
- const loginData = await NamiAPI.instance.login(externalId);
13011
- if (loginData.external_id && loginData.external_id !== this.externalId) {
13012
- this.setExternalId(loginData.external_id).save();
13013
- }
13014
- }
13015
- /**
13016
- * Update local profile state for logout: clear the external id, persist,
13017
- * and flip the logged-in flag in PaywallState. Used by the async fast path.
13018
- */
13019
- logoutLocal() {
13020
- this.setExternalId(undefined).save();
13021
- PaywallState.setIsLoggedIn(false);
13022
- }
13023
- /**
13024
- * Perform the logout network call only.
13025
- */
13026
- async logoutRemote() {
13027
- await NamiAPI.instance.logout();
13028
- }
13029
- async login(externalId) {
13030
- this.loginLocal(externalId);
13031
- await this.loginRemote(externalId);
13032
- }
13033
- async logout() {
13034
- this.logoutLocal();
13035
- await this.logoutRemote();
13036
- }
13037
- }
13038
- NamiProfileManager.instance = new NamiProfileManager();
13039
-
13040
13099
  /**
13041
13100
  * Represents the possible account actions states that can be returned by callback registered from
13042
13101
  * [NamiCustomerManager.registerAccountStateHandler]
@@ -13069,6 +13128,23 @@ class NamiFlowManager {
13069
13128
  this.lastAnimatedFlowProgress = new Map();
13070
13129
  this.navGraphCache = new WeakMap();
13071
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
+ }
13072
13148
  static registerStepHandoff(handoffStepHandler) {
13073
13149
  this.instance.handoffStepHandler = handoffStepHandler;
13074
13150
  }
@@ -13352,6 +13428,11 @@ class NamiFlowManager {
13352
13428
  }
13353
13429
  }
13354
13430
 
13431
+ var NamiFlowManager$1 = /*#__PURE__*/Object.freeze({
13432
+ __proto__: null,
13433
+ NamiFlowManager: NamiFlowManager
13434
+ });
13435
+
13355
13436
  const SHOULD_SHOW_LOADING_INDICATOR = false;
13356
13437
  const DISABLE_ASYNC_LOGIN_LOGOUT = "disableAsyncLoginLogout";
13357
13438
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@namiml/sdk-core",
3
- "version": "3.4.0-dev.202605191719",
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",