@scalemule/nextjs 0.1.29 → 0.1.30

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/client.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { a8 as StorageAdapter, A as ApiError } from './index-zQloSkpW.mjs';
1
+ import { a8 as StorageAdapter, A as ApiError } from './index-Tq5WdDfS.mjs';
2
2
 
3
3
  /**
4
4
  * ScaleMule API Client
@@ -77,6 +77,8 @@ declare class ScaleMuleClient {
77
77
  private sessionGate;
78
78
  private resolveSessionGate;
79
79
  private workspaceId;
80
+ private anonymousId;
81
+ private anonymousIdPromise;
80
82
  private refreshPromise;
81
83
  private onRefreshStart?;
82
84
  private onRefreshEnd?;
@@ -133,6 +135,19 @@ declare class ScaleMuleClient {
133
135
  * Initialize client by loading persisted session
134
136
  */
135
137
  initialize(): Promise<void>;
138
+ /**
139
+ * Sync accessor for the cached anonymous ID. Returns `null` until either
140
+ * `initialize()` has run or `ensureAnonymousId()` has been awaited at
141
+ * least once. Use `ensureAnonymousId()` if you need a guaranteed value.
142
+ */
143
+ getAnonymousId(): string | null;
144
+ /**
145
+ * Lazy-mint or cache-then-return the anonymous ID via the shared
146
+ * `@scalemule/sdk` helper. Concurrent callers receive the same in-flight
147
+ * promise, so a burst of simultaneous first-use requests all see the
148
+ * same minted value.
149
+ */
150
+ ensureAnonymousId(): Promise<string>;
136
151
  /**
137
152
  * Set session after login
138
153
  */
package/dist/client.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { a8 as StorageAdapter, A as ApiError } from './index-zQloSkpW.js';
1
+ import { a8 as StorageAdapter, A as ApiError } from './index-Tq5WdDfS.js';
2
2
 
3
3
  /**
4
4
  * ScaleMule API Client
@@ -77,6 +77,8 @@ declare class ScaleMuleClient {
77
77
  private sessionGate;
78
78
  private resolveSessionGate;
79
79
  private workspaceId;
80
+ private anonymousId;
81
+ private anonymousIdPromise;
80
82
  private refreshPromise;
81
83
  private onRefreshStart?;
82
84
  private onRefreshEnd?;
@@ -133,6 +135,19 @@ declare class ScaleMuleClient {
133
135
  * Initialize client by loading persisted session
134
136
  */
135
137
  initialize(): Promise<void>;
138
+ /**
139
+ * Sync accessor for the cached anonymous ID. Returns `null` until either
140
+ * `initialize()` has run or `ensureAnonymousId()` has been awaited at
141
+ * least once. Use `ensureAnonymousId()` if you need a guaranteed value.
142
+ */
143
+ getAnonymousId(): string | null;
144
+ /**
145
+ * Lazy-mint or cache-then-return the anonymous ID via the shared
146
+ * `@scalemule/sdk` helper. Concurrent callers receive the same in-flight
147
+ * promise, so a burst of simultaneous first-use requests all see the
148
+ * same minted value.
149
+ */
150
+ ensureAnonymousId(): Promise<string>;
136
151
  /**
137
152
  * Set session after login
138
153
  */
package/dist/client.js CHANGED
@@ -1,5 +1,143 @@
1
1
  'use strict';
2
2
 
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __esm = (fn, res) => function __init() {
5
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
6
+ };
7
+ var init_chunk_3FTGBRLU = __esm({
8
+ "node_modules/@scalemule/sdk/dist/chunk-3FTGBRLU.mjs"() {
9
+ }
10
+ });
11
+
12
+ // node_modules/@scalemule/sdk/dist/index.mjs
13
+ init_chunk_3FTGBRLU();
14
+ var STORAGE_KEYS = {
15
+ SESSION: "scalemule_session",
16
+ USER_ID: "scalemule_user_id",
17
+ WORKSPACE_ID: "scalemule_workspace_id",
18
+ ANONYMOUS_ID: "scalemule_anonymous_id",
19
+ SESSION_POOL: "scalemule_session_pool",
20
+ ACTIVE_ACCOUNT: "scalemule_active_account",
21
+ KNOWN_ACCOUNTS: "scalemule_known_accounts",
22
+ OFFLINE_QUEUE: "scalemule_offline_queue"
23
+ };
24
+ var LEGACY_ANONYMOUS_ID_KEYS = ["sm_anonymous_id"];
25
+ function generateAnonymousId() {
26
+ if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
27
+ return crypto.randomUUID();
28
+ }
29
+ return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
30
+ const r = Math.random() * 16 | 0;
31
+ const v = c === "x" ? r : r & 3 | 8;
32
+ return v.toString(16);
33
+ });
34
+ }
35
+ var inFlight = /* @__PURE__ */ new WeakMap();
36
+ function ensureAnonymousId(storage) {
37
+ const existing = inFlight.get(storage);
38
+ if (existing) return existing;
39
+ const promise = (async () => {
40
+ try {
41
+ return await resolveAnonymousId(storage);
42
+ } finally {
43
+ inFlight.delete(storage);
44
+ }
45
+ })();
46
+ inFlight.set(storage, promise);
47
+ return promise;
48
+ }
49
+ async function resolveAnonymousId(storage) {
50
+ const canonical = await storage.getItem(STORAGE_KEYS.ANONYMOUS_ID);
51
+ if (canonical) {
52
+ for (const legacyKey of LEGACY_ANONYMOUS_ID_KEYS) {
53
+ const legacy = await storage.getItem(legacyKey);
54
+ if (legacy !== canonical) {
55
+ try {
56
+ await storage.setItem(legacyKey, canonical);
57
+ } catch {
58
+ }
59
+ }
60
+ }
61
+ return canonical;
62
+ }
63
+ for (const legacyKey of LEGACY_ANONYMOUS_ID_KEYS) {
64
+ const legacy = await storage.getItem(legacyKey);
65
+ if (legacy) {
66
+ try {
67
+ await storage.setItem(STORAGE_KEYS.ANONYMOUS_ID, legacy);
68
+ } catch {
69
+ }
70
+ return legacy;
71
+ }
72
+ }
73
+ const fresh = generateAnonymousId();
74
+ try {
75
+ await storage.setItem(STORAGE_KEYS.ANONYMOUS_ID, fresh);
76
+ } catch {
77
+ }
78
+ for (const legacyKey of LEGACY_ANONYMOUS_ID_KEYS) {
79
+ try {
80
+ await storage.setItem(legacyKey, fresh);
81
+ } catch {
82
+ }
83
+ }
84
+ return fresh;
85
+ }
86
+ var PHONE_COUNTRIES = [
87
+ { code: "US", name: "United States", dialCode: "+1" },
88
+ { code: "CA", name: "Canada", dialCode: "+1" },
89
+ { code: "GB", name: "United Kingdom", dialCode: "+44" },
90
+ { code: "AU", name: "Australia", dialCode: "+61" },
91
+ { code: "DE", name: "Germany", dialCode: "+49" },
92
+ { code: "FR", name: "France", dialCode: "+33" },
93
+ { code: "IT", name: "Italy", dialCode: "+39" },
94
+ { code: "ES", name: "Spain", dialCode: "+34" },
95
+ { code: "NL", name: "Netherlands", dialCode: "+31" },
96
+ { code: "BE", name: "Belgium", dialCode: "+32" },
97
+ { code: "CH", name: "Switzerland", dialCode: "+41" },
98
+ { code: "AT", name: "Austria", dialCode: "+43" },
99
+ { code: "SE", name: "Sweden", dialCode: "+46" },
100
+ { code: "NO", name: "Norway", dialCode: "+47" },
101
+ { code: "DK", name: "Denmark", dialCode: "+45" },
102
+ { code: "FI", name: "Finland", dialCode: "+358" },
103
+ { code: "IE", name: "Ireland", dialCode: "+353" },
104
+ { code: "PT", name: "Portugal", dialCode: "+351" },
105
+ { code: "PL", name: "Poland", dialCode: "+48" },
106
+ { code: "CZ", name: "Czech Republic", dialCode: "+420" },
107
+ { code: "GR", name: "Greece", dialCode: "+30" },
108
+ { code: "RU", name: "Russia", dialCode: "+7" },
109
+ { code: "JP", name: "Japan", dialCode: "+81" },
110
+ { code: "KR", name: "South Korea", dialCode: "+82" },
111
+ { code: "CN", name: "China", dialCode: "+86" },
112
+ { code: "HK", name: "Hong Kong", dialCode: "+852" },
113
+ { code: "TW", name: "Taiwan", dialCode: "+886" },
114
+ { code: "SG", name: "Singapore", dialCode: "+65" },
115
+ { code: "MY", name: "Malaysia", dialCode: "+60" },
116
+ { code: "TH", name: "Thailand", dialCode: "+66" },
117
+ { code: "VN", name: "Vietnam", dialCode: "+84" },
118
+ { code: "PH", name: "Philippines", dialCode: "+63" },
119
+ { code: "ID", name: "Indonesia", dialCode: "+62" },
120
+ { code: "IN", name: "India", dialCode: "+91" },
121
+ { code: "PK", name: "Pakistan", dialCode: "+92" },
122
+ { code: "BD", name: "Bangladesh", dialCode: "+880" },
123
+ { code: "AE", name: "UAE", dialCode: "+971" },
124
+ { code: "SA", name: "Saudi Arabia", dialCode: "+966" },
125
+ { code: "IL", name: "Israel", dialCode: "+972" },
126
+ { code: "TR", name: "Turkey", dialCode: "+90" },
127
+ { code: "EG", name: "Egypt", dialCode: "+20" },
128
+ { code: "ZA", name: "South Africa", dialCode: "+27" },
129
+ { code: "NG", name: "Nigeria", dialCode: "+234" },
130
+ { code: "KE", name: "Kenya", dialCode: "+254" },
131
+ { code: "BR", name: "Brazil", dialCode: "+55" },
132
+ { code: "MX", name: "Mexico", dialCode: "+52" },
133
+ { code: "AR", name: "Argentina", dialCode: "+54" },
134
+ { code: "CL", name: "Chile", dialCode: "+56" },
135
+ { code: "CO", name: "Colombia", dialCode: "+57" },
136
+ { code: "PE", name: "Peru", dialCode: "+51" },
137
+ { code: "NZ", name: "New Zealand", dialCode: "+64" }
138
+ ];
139
+ [...PHONE_COUNTRIES].sort((a, b) => b.dialCode.length - a.dialCode.length);
140
+
3
141
  // src/types/index.ts
4
142
  var ScaleMuleApiError = class extends Error {
5
143
  constructor(error, status) {
@@ -16,9 +154,9 @@ var GATEWAY_URLS = {
16
154
  dev: "https://api-dev.scalemule.com",
17
155
  prod: "https://api.scalemule.com"
18
156
  };
19
- var SESSION_STORAGE_KEY = "scalemule_session";
20
- var USER_ID_STORAGE_KEY = "scalemule_user_id";
21
- var WORKSPACE_STORAGE_KEY = "scalemule_workspace_id";
157
+ var SESSION_STORAGE_KEY2 = STORAGE_KEYS.SESSION;
158
+ var USER_ID_STORAGE_KEY2 = STORAGE_KEYS.USER_ID;
159
+ var WORKSPACE_STORAGE_KEY2 = STORAGE_KEYS.WORKSPACE_ID;
22
160
  var RETRYABLE_STATUS_CODES = /* @__PURE__ */ new Set([408, 429, 500, 502, 503, 504]);
23
161
  function sleep(ms) {
24
162
  return new Promise((resolve) => setTimeout(resolve, ms));
@@ -278,6 +416,15 @@ var ScaleMuleClient = class {
278
416
  this.sessionGate = null;
279
417
  this.resolveSessionGate = null;
280
418
  this.workspaceId = null;
419
+ // Anonymous visitor ID for identity linking. See `@scalemule/sdk/anonymous-id`
420
+ // for the contract. The base SDK and this client share the same canonical
421
+ // localStorage key so a visitor has exactly one ID across all packages.
422
+ this.anonymousId = null;
423
+ // Per-client single-flight cache for the lazy mint. Concurrent first-use
424
+ // callers receive the same promise — without this, two simultaneous
425
+ // unauthenticated requests on a fresh visitor could mint different IDs.
426
+ // The shared helper also single-flights at the storage-adapter level.
427
+ this.anonymousIdPromise = null;
281
428
  this.refreshPromise = null;
282
429
  this.apiKey = config.apiKey;
283
430
  this.applicationId = config.applicationId || null;
@@ -359,9 +506,9 @@ var ScaleMuleClient = class {
359
506
  setWorkspaceContext(id) {
360
507
  this.workspaceId = id;
361
508
  if (id) {
362
- this.storage.setItem(WORKSPACE_STORAGE_KEY, id);
509
+ this.storage.setItem(WORKSPACE_STORAGE_KEY2, id);
363
510
  } else {
364
- this.storage.removeItem(WORKSPACE_STORAGE_KEY);
511
+ this.storage.removeItem(WORKSPACE_STORAGE_KEY2);
365
512
  }
366
513
  }
367
514
  /**
@@ -408,12 +555,16 @@ var ScaleMuleClient = class {
408
555
  * Initialize client by loading persisted session
409
556
  */
410
557
  async initialize() {
411
- const token = await this.storage.getItem(SESSION_STORAGE_KEY);
412
- const userId = await this.storage.getItem(USER_ID_STORAGE_KEY);
558
+ const token = await this.storage.getItem(SESSION_STORAGE_KEY2);
559
+ const userId = await this.storage.getItem(USER_ID_STORAGE_KEY2);
413
560
  if (token) this.sessionToken = token;
414
561
  if (userId) this.userId = userId;
415
- const wsId = await this.storage.getItem(WORKSPACE_STORAGE_KEY);
562
+ const wsId = await this.storage.getItem(WORKSPACE_STORAGE_KEY2);
416
563
  if (wsId) this.workspaceId = wsId;
564
+ try {
565
+ await this.ensureAnonymousId();
566
+ } catch {
567
+ }
417
568
  if (token) {
418
569
  this.resolveSessionPending();
419
570
  }
@@ -421,14 +572,40 @@ var ScaleMuleClient = class {
421
572
  console.log("[ScaleMule] Initialized with session:", !!token);
422
573
  }
423
574
  }
575
+ /**
576
+ * Sync accessor for the cached anonymous ID. Returns `null` until either
577
+ * `initialize()` has run or `ensureAnonymousId()` has been awaited at
578
+ * least once. Use `ensureAnonymousId()` if you need a guaranteed value.
579
+ */
580
+ getAnonymousId() {
581
+ return this.anonymousId;
582
+ }
583
+ /**
584
+ * Lazy-mint or cache-then-return the anonymous ID via the shared
585
+ * `@scalemule/sdk` helper. Concurrent callers receive the same in-flight
586
+ * promise, so a burst of simultaneous first-use requests all see the
587
+ * same minted value.
588
+ */
589
+ async ensureAnonymousId() {
590
+ if (this.anonymousId) return this.anonymousId;
591
+ if (!this.anonymousIdPromise) {
592
+ this.anonymousIdPromise = ensureAnonymousId(this.storage).then((id) => {
593
+ this.anonymousId = id;
594
+ return id;
595
+ }).finally(() => {
596
+ this.anonymousIdPromise = null;
597
+ });
598
+ }
599
+ return this.anonymousIdPromise;
600
+ }
424
601
  /**
425
602
  * Set session after login
426
603
  */
427
604
  async setSession(token, userId) {
428
605
  this.sessionToken = token;
429
606
  this.userId = userId;
430
- await this.storage.setItem(SESSION_STORAGE_KEY, token);
431
- await this.storage.setItem(USER_ID_STORAGE_KEY, userId);
607
+ await this.storage.setItem(SESSION_STORAGE_KEY2, token);
608
+ await this.storage.setItem(USER_ID_STORAGE_KEY2, userId);
432
609
  if (this.debug) {
433
610
  console.log("[ScaleMule] Session set for user:", userId);
434
611
  }
@@ -458,9 +635,9 @@ var ScaleMuleClient = class {
458
635
  this.sessionToken = null;
459
636
  this.userId = null;
460
637
  this.workspaceId = null;
461
- await this.storage.removeItem(SESSION_STORAGE_KEY);
462
- await this.storage.removeItem(USER_ID_STORAGE_KEY);
463
- await this.storage.removeItem(WORKSPACE_STORAGE_KEY);
638
+ await this.storage.removeItem(SESSION_STORAGE_KEY2);
639
+ await this.storage.removeItem(USER_ID_STORAGE_KEY2);
640
+ await this.storage.removeItem(WORKSPACE_STORAGE_KEY2);
464
641
  if (this.debug) {
465
642
  console.log("[ScaleMule] Session cleared");
466
643
  }
@@ -495,6 +672,9 @@ var ScaleMuleClient = class {
495
672
  if (this.workspaceId) {
496
673
  headers.set("x-sm-workspace-id", this.workspaceId);
497
674
  }
675
+ if (!options?.skipAuth && !this.sessionToken && this.anonymousId) {
676
+ headers.set("x-anonymous-id", this.anonymousId);
677
+ }
498
678
  if (!headers.has("Content-Type") && options?.body && typeof options.body === "string") {
499
679
  headers.set("Content-Type", "application/json");
500
680
  }
@@ -507,6 +687,12 @@ var ScaleMuleClient = class {
507
687
  if (this.sessionGate) {
508
688
  await this.sessionGate;
509
689
  }
690
+ if (!options.skipAuth && !this.sessionToken && !this.anonymousId) {
691
+ try {
692
+ await this.ensureAnonymousId();
693
+ } catch {
694
+ }
695
+ }
510
696
  const url = `${this.gatewayUrl}${path}`;
511
697
  const headers = this.buildHeaders(options);
512
698
  const maxRetries = options.skipRetry ? 0 : options.retries ?? 2;
package/dist/client.mjs CHANGED
@@ -1,3 +1,141 @@
1
+ var __getOwnPropNames = Object.getOwnPropertyNames;
2
+ var __esm = (fn, res) => function __init() {
3
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
4
+ };
5
+ var init_chunk_3FTGBRLU = __esm({
6
+ "node_modules/@scalemule/sdk/dist/chunk-3FTGBRLU.mjs"() {
7
+ }
8
+ });
9
+
10
+ // node_modules/@scalemule/sdk/dist/index.mjs
11
+ init_chunk_3FTGBRLU();
12
+ var STORAGE_KEYS = {
13
+ SESSION: "scalemule_session",
14
+ USER_ID: "scalemule_user_id",
15
+ WORKSPACE_ID: "scalemule_workspace_id",
16
+ ANONYMOUS_ID: "scalemule_anonymous_id",
17
+ SESSION_POOL: "scalemule_session_pool",
18
+ ACTIVE_ACCOUNT: "scalemule_active_account",
19
+ KNOWN_ACCOUNTS: "scalemule_known_accounts",
20
+ OFFLINE_QUEUE: "scalemule_offline_queue"
21
+ };
22
+ var LEGACY_ANONYMOUS_ID_KEYS = ["sm_anonymous_id"];
23
+ function generateAnonymousId() {
24
+ if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
25
+ return crypto.randomUUID();
26
+ }
27
+ return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
28
+ const r = Math.random() * 16 | 0;
29
+ const v = c === "x" ? r : r & 3 | 8;
30
+ return v.toString(16);
31
+ });
32
+ }
33
+ var inFlight = /* @__PURE__ */ new WeakMap();
34
+ function ensureAnonymousId(storage) {
35
+ const existing = inFlight.get(storage);
36
+ if (existing) return existing;
37
+ const promise = (async () => {
38
+ try {
39
+ return await resolveAnonymousId(storage);
40
+ } finally {
41
+ inFlight.delete(storage);
42
+ }
43
+ })();
44
+ inFlight.set(storage, promise);
45
+ return promise;
46
+ }
47
+ async function resolveAnonymousId(storage) {
48
+ const canonical = await storage.getItem(STORAGE_KEYS.ANONYMOUS_ID);
49
+ if (canonical) {
50
+ for (const legacyKey of LEGACY_ANONYMOUS_ID_KEYS) {
51
+ const legacy = await storage.getItem(legacyKey);
52
+ if (legacy !== canonical) {
53
+ try {
54
+ await storage.setItem(legacyKey, canonical);
55
+ } catch {
56
+ }
57
+ }
58
+ }
59
+ return canonical;
60
+ }
61
+ for (const legacyKey of LEGACY_ANONYMOUS_ID_KEYS) {
62
+ const legacy = await storage.getItem(legacyKey);
63
+ if (legacy) {
64
+ try {
65
+ await storage.setItem(STORAGE_KEYS.ANONYMOUS_ID, legacy);
66
+ } catch {
67
+ }
68
+ return legacy;
69
+ }
70
+ }
71
+ const fresh = generateAnonymousId();
72
+ try {
73
+ await storage.setItem(STORAGE_KEYS.ANONYMOUS_ID, fresh);
74
+ } catch {
75
+ }
76
+ for (const legacyKey of LEGACY_ANONYMOUS_ID_KEYS) {
77
+ try {
78
+ await storage.setItem(legacyKey, fresh);
79
+ } catch {
80
+ }
81
+ }
82
+ return fresh;
83
+ }
84
+ var PHONE_COUNTRIES = [
85
+ { code: "US", name: "United States", dialCode: "+1" },
86
+ { code: "CA", name: "Canada", dialCode: "+1" },
87
+ { code: "GB", name: "United Kingdom", dialCode: "+44" },
88
+ { code: "AU", name: "Australia", dialCode: "+61" },
89
+ { code: "DE", name: "Germany", dialCode: "+49" },
90
+ { code: "FR", name: "France", dialCode: "+33" },
91
+ { code: "IT", name: "Italy", dialCode: "+39" },
92
+ { code: "ES", name: "Spain", dialCode: "+34" },
93
+ { code: "NL", name: "Netherlands", dialCode: "+31" },
94
+ { code: "BE", name: "Belgium", dialCode: "+32" },
95
+ { code: "CH", name: "Switzerland", dialCode: "+41" },
96
+ { code: "AT", name: "Austria", dialCode: "+43" },
97
+ { code: "SE", name: "Sweden", dialCode: "+46" },
98
+ { code: "NO", name: "Norway", dialCode: "+47" },
99
+ { code: "DK", name: "Denmark", dialCode: "+45" },
100
+ { code: "FI", name: "Finland", dialCode: "+358" },
101
+ { code: "IE", name: "Ireland", dialCode: "+353" },
102
+ { code: "PT", name: "Portugal", dialCode: "+351" },
103
+ { code: "PL", name: "Poland", dialCode: "+48" },
104
+ { code: "CZ", name: "Czech Republic", dialCode: "+420" },
105
+ { code: "GR", name: "Greece", dialCode: "+30" },
106
+ { code: "RU", name: "Russia", dialCode: "+7" },
107
+ { code: "JP", name: "Japan", dialCode: "+81" },
108
+ { code: "KR", name: "South Korea", dialCode: "+82" },
109
+ { code: "CN", name: "China", dialCode: "+86" },
110
+ { code: "HK", name: "Hong Kong", dialCode: "+852" },
111
+ { code: "TW", name: "Taiwan", dialCode: "+886" },
112
+ { code: "SG", name: "Singapore", dialCode: "+65" },
113
+ { code: "MY", name: "Malaysia", dialCode: "+60" },
114
+ { code: "TH", name: "Thailand", dialCode: "+66" },
115
+ { code: "VN", name: "Vietnam", dialCode: "+84" },
116
+ { code: "PH", name: "Philippines", dialCode: "+63" },
117
+ { code: "ID", name: "Indonesia", dialCode: "+62" },
118
+ { code: "IN", name: "India", dialCode: "+91" },
119
+ { code: "PK", name: "Pakistan", dialCode: "+92" },
120
+ { code: "BD", name: "Bangladesh", dialCode: "+880" },
121
+ { code: "AE", name: "UAE", dialCode: "+971" },
122
+ { code: "SA", name: "Saudi Arabia", dialCode: "+966" },
123
+ { code: "IL", name: "Israel", dialCode: "+972" },
124
+ { code: "TR", name: "Turkey", dialCode: "+90" },
125
+ { code: "EG", name: "Egypt", dialCode: "+20" },
126
+ { code: "ZA", name: "South Africa", dialCode: "+27" },
127
+ { code: "NG", name: "Nigeria", dialCode: "+234" },
128
+ { code: "KE", name: "Kenya", dialCode: "+254" },
129
+ { code: "BR", name: "Brazil", dialCode: "+55" },
130
+ { code: "MX", name: "Mexico", dialCode: "+52" },
131
+ { code: "AR", name: "Argentina", dialCode: "+54" },
132
+ { code: "CL", name: "Chile", dialCode: "+56" },
133
+ { code: "CO", name: "Colombia", dialCode: "+57" },
134
+ { code: "PE", name: "Peru", dialCode: "+51" },
135
+ { code: "NZ", name: "New Zealand", dialCode: "+64" }
136
+ ];
137
+ [...PHONE_COUNTRIES].sort((a, b) => b.dialCode.length - a.dialCode.length);
138
+
1
139
  // src/types/index.ts
2
140
  var ScaleMuleApiError = class extends Error {
3
141
  constructor(error, status) {
@@ -14,9 +152,9 @@ var GATEWAY_URLS = {
14
152
  dev: "https://api-dev.scalemule.com",
15
153
  prod: "https://api.scalemule.com"
16
154
  };
17
- var SESSION_STORAGE_KEY = "scalemule_session";
18
- var USER_ID_STORAGE_KEY = "scalemule_user_id";
19
- var WORKSPACE_STORAGE_KEY = "scalemule_workspace_id";
155
+ var SESSION_STORAGE_KEY2 = STORAGE_KEYS.SESSION;
156
+ var USER_ID_STORAGE_KEY2 = STORAGE_KEYS.USER_ID;
157
+ var WORKSPACE_STORAGE_KEY2 = STORAGE_KEYS.WORKSPACE_ID;
20
158
  var RETRYABLE_STATUS_CODES = /* @__PURE__ */ new Set([408, 429, 500, 502, 503, 504]);
21
159
  function sleep(ms) {
22
160
  return new Promise((resolve) => setTimeout(resolve, ms));
@@ -276,6 +414,15 @@ var ScaleMuleClient = class {
276
414
  this.sessionGate = null;
277
415
  this.resolveSessionGate = null;
278
416
  this.workspaceId = null;
417
+ // Anonymous visitor ID for identity linking. See `@scalemule/sdk/anonymous-id`
418
+ // for the contract. The base SDK and this client share the same canonical
419
+ // localStorage key so a visitor has exactly one ID across all packages.
420
+ this.anonymousId = null;
421
+ // Per-client single-flight cache for the lazy mint. Concurrent first-use
422
+ // callers receive the same promise — without this, two simultaneous
423
+ // unauthenticated requests on a fresh visitor could mint different IDs.
424
+ // The shared helper also single-flights at the storage-adapter level.
425
+ this.anonymousIdPromise = null;
279
426
  this.refreshPromise = null;
280
427
  this.apiKey = config.apiKey;
281
428
  this.applicationId = config.applicationId || null;
@@ -357,9 +504,9 @@ var ScaleMuleClient = class {
357
504
  setWorkspaceContext(id) {
358
505
  this.workspaceId = id;
359
506
  if (id) {
360
- this.storage.setItem(WORKSPACE_STORAGE_KEY, id);
507
+ this.storage.setItem(WORKSPACE_STORAGE_KEY2, id);
361
508
  } else {
362
- this.storage.removeItem(WORKSPACE_STORAGE_KEY);
509
+ this.storage.removeItem(WORKSPACE_STORAGE_KEY2);
363
510
  }
364
511
  }
365
512
  /**
@@ -406,12 +553,16 @@ var ScaleMuleClient = class {
406
553
  * Initialize client by loading persisted session
407
554
  */
408
555
  async initialize() {
409
- const token = await this.storage.getItem(SESSION_STORAGE_KEY);
410
- const userId = await this.storage.getItem(USER_ID_STORAGE_KEY);
556
+ const token = await this.storage.getItem(SESSION_STORAGE_KEY2);
557
+ const userId = await this.storage.getItem(USER_ID_STORAGE_KEY2);
411
558
  if (token) this.sessionToken = token;
412
559
  if (userId) this.userId = userId;
413
- const wsId = await this.storage.getItem(WORKSPACE_STORAGE_KEY);
560
+ const wsId = await this.storage.getItem(WORKSPACE_STORAGE_KEY2);
414
561
  if (wsId) this.workspaceId = wsId;
562
+ try {
563
+ await this.ensureAnonymousId();
564
+ } catch {
565
+ }
415
566
  if (token) {
416
567
  this.resolveSessionPending();
417
568
  }
@@ -419,14 +570,40 @@ var ScaleMuleClient = class {
419
570
  console.log("[ScaleMule] Initialized with session:", !!token);
420
571
  }
421
572
  }
573
+ /**
574
+ * Sync accessor for the cached anonymous ID. Returns `null` until either
575
+ * `initialize()` has run or `ensureAnonymousId()` has been awaited at
576
+ * least once. Use `ensureAnonymousId()` if you need a guaranteed value.
577
+ */
578
+ getAnonymousId() {
579
+ return this.anonymousId;
580
+ }
581
+ /**
582
+ * Lazy-mint or cache-then-return the anonymous ID via the shared
583
+ * `@scalemule/sdk` helper. Concurrent callers receive the same in-flight
584
+ * promise, so a burst of simultaneous first-use requests all see the
585
+ * same minted value.
586
+ */
587
+ async ensureAnonymousId() {
588
+ if (this.anonymousId) return this.anonymousId;
589
+ if (!this.anonymousIdPromise) {
590
+ this.anonymousIdPromise = ensureAnonymousId(this.storage).then((id) => {
591
+ this.anonymousId = id;
592
+ return id;
593
+ }).finally(() => {
594
+ this.anonymousIdPromise = null;
595
+ });
596
+ }
597
+ return this.anonymousIdPromise;
598
+ }
422
599
  /**
423
600
  * Set session after login
424
601
  */
425
602
  async setSession(token, userId) {
426
603
  this.sessionToken = token;
427
604
  this.userId = userId;
428
- await this.storage.setItem(SESSION_STORAGE_KEY, token);
429
- await this.storage.setItem(USER_ID_STORAGE_KEY, userId);
605
+ await this.storage.setItem(SESSION_STORAGE_KEY2, token);
606
+ await this.storage.setItem(USER_ID_STORAGE_KEY2, userId);
430
607
  if (this.debug) {
431
608
  console.log("[ScaleMule] Session set for user:", userId);
432
609
  }
@@ -456,9 +633,9 @@ var ScaleMuleClient = class {
456
633
  this.sessionToken = null;
457
634
  this.userId = null;
458
635
  this.workspaceId = null;
459
- await this.storage.removeItem(SESSION_STORAGE_KEY);
460
- await this.storage.removeItem(USER_ID_STORAGE_KEY);
461
- await this.storage.removeItem(WORKSPACE_STORAGE_KEY);
636
+ await this.storage.removeItem(SESSION_STORAGE_KEY2);
637
+ await this.storage.removeItem(USER_ID_STORAGE_KEY2);
638
+ await this.storage.removeItem(WORKSPACE_STORAGE_KEY2);
462
639
  if (this.debug) {
463
640
  console.log("[ScaleMule] Session cleared");
464
641
  }
@@ -493,6 +670,9 @@ var ScaleMuleClient = class {
493
670
  if (this.workspaceId) {
494
671
  headers.set("x-sm-workspace-id", this.workspaceId);
495
672
  }
673
+ if (!options?.skipAuth && !this.sessionToken && this.anonymousId) {
674
+ headers.set("x-anonymous-id", this.anonymousId);
675
+ }
496
676
  if (!headers.has("Content-Type") && options?.body && typeof options.body === "string") {
497
677
  headers.set("Content-Type", "application/json");
498
678
  }
@@ -505,6 +685,12 @@ var ScaleMuleClient = class {
505
685
  if (this.sessionGate) {
506
686
  await this.sessionGate;
507
687
  }
688
+ if (!options.skipAuth && !this.sessionToken && !this.anonymousId) {
689
+ try {
690
+ await this.ensureAnonymousId();
691
+ } catch {
692
+ }
693
+ }
508
694
  const url = `${this.gatewayUrl}${path}`;
509
695
  const headers = this.buildHeaders(options);
510
696
  const maxRetries = options.skipRetry ? 0 : options.retries ?? 2;