@scalemule/nextjs 0.1.29 → 0.1.31
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/README.md +38 -0
- package/dist/client.d.mts +16 -1
- package/dist/client.d.ts +16 -1
- package/dist/client.js +199 -13
- package/dist/client.mjs +199 -13
- package/dist/index-Tq5WdDfS.d.mts +939 -0
- package/dist/index-Tq5WdDfS.d.ts +939 -0
- package/dist/index.d.mts +28 -4
- package/dist/index.d.ts +28 -4
- package/dist/index.js +1268 -43
- package/dist/index.mjs +1269 -45
- package/dist/ledvery-CxPzZpxP.d.mts +44 -0
- package/dist/ledvery-CxPzZpxP.d.ts +44 -0
- package/dist/server/auth.js +6 -1
- package/dist/server/auth.mjs +6 -1
- package/dist/server/index.d.mts +3 -3
- package/dist/server/index.d.ts +3 -3
- package/dist/server/index.js +645 -675
- package/dist/server/index.mjs +646 -676
- package/dist/server/ledvery.d.mts +1 -0
- package/dist/server/ledvery.d.ts +1 -0
- package/dist/server/ledvery.js +336 -0
- package/dist/server/ledvery.mjs +333 -0
- package/dist/server/webhook-handler.d.mts +2 -2
- package/dist/server/webhook-handler.d.ts +2 -2
- package/dist/testing.d.mts +1 -1
- package/dist/testing.d.ts +1 -1
- package/dist/webhook-handler-Cfa7-yKN.d.ts +808 -0
- package/dist/webhook-handler-DN4WBjbm.d.mts +808 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -95,6 +95,44 @@ For zero-config auth (reads from env vars):
|
|
|
95
95
|
export { GET, POST, DELETE, PATCH } from '@scalemule/nextjs/server/auth'
|
|
96
96
|
```
|
|
97
97
|
|
|
98
|
+
## Proxy Mode (keep API keys server-side)
|
|
99
|
+
|
|
100
|
+
If you don't want a real ScaleMule API key in browser bundles, run the SDK in **proxy mode**: pass the literal sentinel string `'proxy-mode'` as `apiKey` and route all server-bound traffic through Next.js routes that hold the real `SCALEMULE_API_KEY` server-side. **Both** the auth route and the analytics route are required — without the analytics route, every `trackEvent` / `trackPageView` silently 401s against the gateway and the dashboard reads empty.
|
|
101
|
+
|
|
102
|
+
```ts
|
|
103
|
+
// app/api/auth/[...scalemule]/route.ts
|
|
104
|
+
export { GET, POST, DELETE, PATCH } from '@scalemule/nextjs/server/auth'
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
```ts
|
|
108
|
+
// app/api/analytics/event/route.ts
|
|
109
|
+
import { NextRequest } from 'next/server'
|
|
110
|
+
import { createAnalyticsRoutes } from '@scalemule/nextjs/server'
|
|
111
|
+
|
|
112
|
+
const { POST: handler } = createAnalyticsRoutes({ simpleProxy: true })
|
|
113
|
+
|
|
114
|
+
export const dynamic = 'force-dynamic'
|
|
115
|
+
export function POST(req: NextRequest, ctx: { params: Promise<Record<string, string | string[]>> }) {
|
|
116
|
+
return handler(req, ctx)
|
|
117
|
+
}
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
```tsx
|
|
121
|
+
// app/layout.tsx
|
|
122
|
+
<ScaleMuleProvider
|
|
123
|
+
apiKey="proxy-mode"
|
|
124
|
+
applicationId={process.env.SCALEMULE_APP_ID}
|
|
125
|
+
environment="prod"
|
|
126
|
+
gatewayUrl="https://api.scalemule.com"
|
|
127
|
+
authProxyUrl="/api/auth"
|
|
128
|
+
analyticsProxyUrl="/api/analytics/event"
|
|
129
|
+
>
|
|
130
|
+
{children}
|
|
131
|
+
</ScaleMuleProvider>
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
If `apiKey="proxy-mode"` is set without `analyticsProxyUrl` (and no `publishableKey`), `useAnalytics` emits a one-time dev console warning — that exact misconfig caused a silent-empty dashboard in production for one customer (Gistyo, app ID `019dda2a-c7d9-75a1-9258-12e4c48f1499`).
|
|
135
|
+
|
|
98
136
|
## Client-Side Provider
|
|
99
137
|
|
|
100
138
|
```tsx
|
package/dist/client.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a8 as StorageAdapter, A as ApiError } from './index-
|
|
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-
|
|
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
|
|
20
|
-
var
|
|
21
|
-
var
|
|
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(
|
|
509
|
+
this.storage.setItem(WORKSPACE_STORAGE_KEY2, id);
|
|
363
510
|
} else {
|
|
364
|
-
this.storage.removeItem(
|
|
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(
|
|
412
|
-
const userId = await this.storage.getItem(
|
|
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(
|
|
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(
|
|
431
|
-
await this.storage.setItem(
|
|
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(
|
|
462
|
-
await this.storage.removeItem(
|
|
463
|
-
await this.storage.removeItem(
|
|
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
|
|
18
|
-
var
|
|
19
|
-
var
|
|
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(
|
|
507
|
+
this.storage.setItem(WORKSPACE_STORAGE_KEY2, id);
|
|
361
508
|
} else {
|
|
362
|
-
this.storage.removeItem(
|
|
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(
|
|
410
|
-
const userId = await this.storage.getItem(
|
|
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(
|
|
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(
|
|
429
|
-
await this.storage.setItem(
|
|
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(
|
|
460
|
-
await this.storage.removeItem(
|
|
461
|
-
await this.storage.removeItem(
|
|
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;
|