@sendoracloud/sdk-react-native 1.6.0 → 1.6.2
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 +135 -45
- package/dist/index.d.cts +18 -3
- package/dist/index.d.ts +18 -3
- package/dist/index.js +135 -45
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -219,6 +219,82 @@ async function request(opts, method, path, body, extraHeaders, reqOpts) {
|
|
|
219
219
|
return res;
|
|
220
220
|
}
|
|
221
221
|
|
|
222
|
+
// package.json
|
|
223
|
+
var package_default = {
|
|
224
|
+
name: "@sendoracloud/sdk-react-native",
|
|
225
|
+
version: "1.6.2",
|
|
226
|
+
description: "Sendora Cloud React Native + Expo SDK \u2014 analytics, identity, push-token registration, auth, deep links (Branch / Firebase Dynamic Links parity). Auth + analytics + deep links work in Expo Go; push token registration requires a Dev Client (EAS Build or `npx expo prebuild`).",
|
|
227
|
+
type: "module",
|
|
228
|
+
main: "./dist/index.cjs",
|
|
229
|
+
module: "./dist/index.js",
|
|
230
|
+
types: "./dist/index.d.ts",
|
|
231
|
+
exports: {
|
|
232
|
+
".": {
|
|
233
|
+
types: "./dist/index.d.ts",
|
|
234
|
+
import: "./dist/index.js",
|
|
235
|
+
require: "./dist/index.cjs"
|
|
236
|
+
},
|
|
237
|
+
"./contact-widget": {
|
|
238
|
+
types: "./dist/contact-widget.d.ts",
|
|
239
|
+
import: "./dist/contact-widget.js",
|
|
240
|
+
require: "./dist/contact-widget.cjs"
|
|
241
|
+
}
|
|
242
|
+
},
|
|
243
|
+
files: [
|
|
244
|
+
"dist",
|
|
245
|
+
"examples",
|
|
246
|
+
"README.md",
|
|
247
|
+
"LICENSE"
|
|
248
|
+
],
|
|
249
|
+
keywords: [
|
|
250
|
+
"sendora",
|
|
251
|
+
"react-native",
|
|
252
|
+
"expo",
|
|
253
|
+
"analytics",
|
|
254
|
+
"tracking",
|
|
255
|
+
"customer-engagement"
|
|
256
|
+
],
|
|
257
|
+
dependencies: {
|
|
258
|
+
"react-native-get-random-values": "^1.11.0"
|
|
259
|
+
},
|
|
260
|
+
peerDependencies: {
|
|
261
|
+
"@react-native-async-storage/async-storage": ">=1.17.0",
|
|
262
|
+
"react-native": ">=0.70.0",
|
|
263
|
+
"react-native-webview": ">=13.0.0"
|
|
264
|
+
},
|
|
265
|
+
peerDependenciesMeta: {
|
|
266
|
+
"@react-native-async-storage/async-storage": {
|
|
267
|
+
optional: false
|
|
268
|
+
},
|
|
269
|
+
"react-native": {
|
|
270
|
+
optional: false
|
|
271
|
+
},
|
|
272
|
+
"react-native-webview": {
|
|
273
|
+
optional: true
|
|
274
|
+
}
|
|
275
|
+
},
|
|
276
|
+
scripts: {
|
|
277
|
+
build: "tsup src/index.ts src/contact-widget.tsx --format esm,cjs --dts --clean --external react --external react-native --external react-native-webview",
|
|
278
|
+
typecheck: "tsc --noEmit"
|
|
279
|
+
},
|
|
280
|
+
devDependencies: {
|
|
281
|
+
"@types/react": "^19.2.16",
|
|
282
|
+
"react-native": "^0.85.3",
|
|
283
|
+
"react-native-webview": "^13.16.1",
|
|
284
|
+
tsup: "^8.0.0",
|
|
285
|
+
typescript: "^5.0.0"
|
|
286
|
+
},
|
|
287
|
+
license: "MIT",
|
|
288
|
+
repository: {
|
|
289
|
+
type: "git",
|
|
290
|
+
url: "https://github.com/sendoracloud/sdk-react-native"
|
|
291
|
+
},
|
|
292
|
+
homepage: "https://sendoracloud.com/sdks"
|
|
293
|
+
};
|
|
294
|
+
|
|
295
|
+
// src/version.ts
|
|
296
|
+
var SDK_VERSION = package_default.version;
|
|
297
|
+
|
|
222
298
|
// src/auth.ts
|
|
223
299
|
var import_react_native = require("react-native");
|
|
224
300
|
var TOKEN_KEY = "auth_access_token";
|
|
@@ -277,6 +353,14 @@ var Auth = class {
|
|
|
277
353
|
this.refreshCooldownUntil = 0;
|
|
278
354
|
this.takeoverListeners = /* @__PURE__ */ new Set();
|
|
279
355
|
this.lastTakeover = null;
|
|
356
|
+
/**
|
|
357
|
+
* Anon refresh token captured at `signIn()` time, stashed keyed to the
|
|
358
|
+
* issued `mfaChallengeToken` so the later `challengeMfa()` can forward it
|
|
359
|
+
* for device-takeover (s58.111). Before this, the MFA branch wiped the anon
|
|
360
|
+
* identity before the challenge resolved → the takeover hint was lost → one
|
|
361
|
+
* device ended with two user_ids + duplicate pushes (audit s58.203 fix).
|
|
362
|
+
*/
|
|
363
|
+
this.pendingAnonTakeover = null;
|
|
280
364
|
/**
|
|
281
365
|
* Proactive refresh (s58.72). Fires when access-token age crosses
|
|
282
366
|
* ~80% of TTL. Pairs with the AppState foreground listener so a
|
|
@@ -496,12 +580,7 @@ var Auth = class {
|
|
|
496
580
|
*/
|
|
497
581
|
signIn(email, password) {
|
|
498
582
|
return this.serialize(async () => {
|
|
499
|
-
|
|
500
|
-
if (this.user?.isAnonymous) {
|
|
501
|
-
const stashed = this.hooks.storage.get(REFRESH_KEY);
|
|
502
|
-
if (stashed) prevAnonRefreshToken = stashed;
|
|
503
|
-
}
|
|
504
|
-
if (this.user !== null) await this.wipeLocalIdentity();
|
|
583
|
+
const prevAnonRefreshToken = this.readPrevAnonRefreshToken();
|
|
505
584
|
const res = await post(
|
|
506
585
|
{ apiUrl: this.hooks.apiUrl, publicKey: this.hooks.publicKey, debug: this.hooks.debug },
|
|
507
586
|
"/api/v1/auth-service/login",
|
|
@@ -519,12 +598,16 @@ var Auth = class {
|
|
|
519
598
|
}
|
|
520
599
|
const r = parsed.data;
|
|
521
600
|
if (r.mfaRequired === true && typeof r.mfaChallengeToken === "string") {
|
|
601
|
+
if (prevAnonRefreshToken) {
|
|
602
|
+
this.pendingAnonTakeover = { challengeToken: r.mfaChallengeToken, prevAnonRefreshToken };
|
|
603
|
+
}
|
|
522
604
|
const u = r.user;
|
|
523
605
|
return { mfaRequired: true, mfaChallengeToken: r.mfaChallengeToken, userId: u?.id ?? "" };
|
|
524
606
|
}
|
|
525
607
|
if (!isAuthApiResponse(parsed.data)) {
|
|
526
608
|
throw new AuthError(parsed.error?.code ?? "AUTH_ERROR", parsed.error?.message ?? "Login failed");
|
|
527
609
|
}
|
|
610
|
+
if (this.user !== null) await this.wipeLocalIdentity();
|
|
528
611
|
this.persist(parsed.data);
|
|
529
612
|
return parsed.data.user;
|
|
530
613
|
});
|
|
@@ -606,13 +689,15 @@ var Auth = class {
|
|
|
606
689
|
*/
|
|
607
690
|
challengeMfa(mfaChallengeToken, code) {
|
|
608
691
|
return this.serialize(async () => {
|
|
609
|
-
const
|
|
692
|
+
const stashed = this.pendingAnonTakeover?.challengeToken === mfaChallengeToken ? this.pendingAnonTakeover.prevAnonRefreshToken : void 0;
|
|
693
|
+
const prevAnonRefreshToken = stashed ?? this.readPrevAnonRefreshToken();
|
|
610
694
|
const body = {
|
|
611
695
|
challengeToken: mfaChallengeToken,
|
|
612
696
|
code
|
|
613
697
|
};
|
|
614
698
|
if (prevAnonRefreshToken) body.prevAnonRefreshToken = prevAnonRefreshToken;
|
|
615
699
|
const data = await this.callAuth("/api/v1/auth-service/mfa/challenge", body);
|
|
700
|
+
if (this.user !== null) await this.wipeLocalIdentity();
|
|
616
701
|
this.persist(data);
|
|
617
702
|
return data.user;
|
|
618
703
|
});
|
|
@@ -1152,6 +1237,7 @@ var Auth = class {
|
|
|
1152
1237
|
this.accessToken = null;
|
|
1153
1238
|
this.accessExpiresAt = 0;
|
|
1154
1239
|
this.lastTakeover = null;
|
|
1240
|
+
this.pendingAnonTakeover = null;
|
|
1155
1241
|
this.hooks.storage.remove(USER_KEY);
|
|
1156
1242
|
this.hooks.storage.remove(TOKEN_KEY);
|
|
1157
1243
|
this.hooks.storage.remove(TOKEN_EXPIRES_KEY);
|
|
@@ -1839,7 +1925,6 @@ function installAutoTrack(args) {
|
|
|
1839
1925
|
}
|
|
1840
1926
|
|
|
1841
1927
|
// src/index.ts
|
|
1842
|
-
var SDK_VERSION = "1.0.0";
|
|
1843
1928
|
var DEFAULT_API_URL = "https://api.sendoracloud.com";
|
|
1844
1929
|
var ANON_KEY = "anon_id";
|
|
1845
1930
|
var USER_ID_KEY = "user_id";
|
|
@@ -2149,7 +2234,13 @@ var SendoraSDK = class {
|
|
|
2149
2234
|
{ apiUrl: this.config.apiUrl, publicKey: this.config.publicKey, debug: this.debug },
|
|
2150
2235
|
"/api/v1/push/tokens",
|
|
2151
2236
|
{
|
|
2152
|
-
|
|
2237
|
+
// Conditional spread — the backend's registerPushTokenSchema makes
|
|
2238
|
+
// userId `.optional()`, which accepts a missing key but REJECTS
|
|
2239
|
+
// `null` (422 "userId: Expected string, received null"). Anonymous
|
|
2240
|
+
// installs (pre-identify()) carry a null userId, so OMIT the field
|
|
2241
|
+
// rather than send null — anon devices must still be able to
|
|
2242
|
+
// register. Mirrors the same conditional-spread sendEvent() uses.
|
|
2243
|
+
...this.userId ? { userId: this.userId } : {},
|
|
2153
2244
|
anonymousId: this.anonId,
|
|
2154
2245
|
token: reg.token,
|
|
2155
2246
|
platform: reg.platform,
|
|
@@ -2191,7 +2282,7 @@ var SendoraSDK = class {
|
|
|
2191
2282
|
if (clickAction !== void 0) body.clickAction = clickAction;
|
|
2192
2283
|
await post(
|
|
2193
2284
|
{ apiUrl: self.config.apiUrl, publicKey: self.config.publicKey, debug: self.debug },
|
|
2194
|
-
"/push/track-open",
|
|
2285
|
+
"/api/v1/push/track-open",
|
|
2195
2286
|
body
|
|
2196
2287
|
);
|
|
2197
2288
|
},
|
|
@@ -2210,7 +2301,7 @@ var SendoraSDK = class {
|
|
|
2210
2301
|
if (!self.config) throw new Error("[sendoracloud] init() must complete before push.liveActivities.startToken().");
|
|
2211
2302
|
const res = await post(
|
|
2212
2303
|
{ apiUrl: self.config.apiUrl, publicKey: self.config.publicKey, debug: self.debug },
|
|
2213
|
-
"/push/live-activities/start-token",
|
|
2304
|
+
"/api/v1/push/live-activities/start-token",
|
|
2214
2305
|
{
|
|
2215
2306
|
pushToken: input.pushToken,
|
|
2216
2307
|
activityType: input.activityType,
|
|
@@ -2244,7 +2335,7 @@ var SendoraSDK = class {
|
|
|
2244
2335
|
if (!self.config) throw new Error("[sendoracloud] init() must complete before push.geofences.list().");
|
|
2245
2336
|
const res = await getJson(
|
|
2246
2337
|
{ apiUrl: self.config.apiUrl, publicKey: self.config.publicKey, debug: self.debug },
|
|
2247
|
-
"/push/geofences/list-for-device"
|
|
2338
|
+
"/api/v1/push/geofences/list-for-device"
|
|
2248
2339
|
);
|
|
2249
2340
|
if (!res || !res.ok) {
|
|
2250
2341
|
throw new Error(`[sendoracloud] geofences list failed (HTTP ${res?.status ?? "network"}).`);
|
|
@@ -2262,7 +2353,7 @@ var SendoraSDK = class {
|
|
|
2262
2353
|
if (!self.config) throw new Error("[sendoracloud] init() must complete before push.geofences.recordEvent().");
|
|
2263
2354
|
const res = await post(
|
|
2264
2355
|
{ apiUrl: self.config.apiUrl, publicKey: self.config.publicKey, debug: self.debug },
|
|
2265
|
-
"/push/geofences/event",
|
|
2356
|
+
"/api/v1/push/geofences/event",
|
|
2266
2357
|
{
|
|
2267
2358
|
geofenceId: input.geofenceId,
|
|
2268
2359
|
kind: input.kind,
|
|
@@ -2306,7 +2397,7 @@ var SendoraSDK = class {
|
|
|
2306
2397
|
if (!self.config) throw new Error("[sendoracloud] init() must complete before attribution.reportInstall().");
|
|
2307
2398
|
const res = await post(
|
|
2308
2399
|
{ apiUrl: self.config.apiUrl, publicKey: self.config.publicKey, debug: self.debug },
|
|
2309
|
-
"/attribution/install",
|
|
2400
|
+
"/api/v1/attribution/install",
|
|
2310
2401
|
input ?? {}
|
|
2311
2402
|
);
|
|
2312
2403
|
if (!res || !res.ok) throw new Error(`[sendoracloud] attribution.reportInstall failed (${res?.status ?? "network"})`);
|
|
@@ -2318,7 +2409,7 @@ var SendoraSDK = class {
|
|
|
2318
2409
|
if (!self.config) throw new Error("[sendoracloud] init() must complete before attribution.checkDeferred().");
|
|
2319
2410
|
const res = await post(
|
|
2320
2411
|
{ apiUrl: self.config.apiUrl, publicKey: self.config.publicKey, debug: self.debug },
|
|
2321
|
-
"/attribution/deferred",
|
|
2412
|
+
"/api/v1/attribution/deferred",
|
|
2322
2413
|
input ?? {}
|
|
2323
2414
|
);
|
|
2324
2415
|
if (!res || !res.ok) throw new Error(`[sendoracloud] attribution.checkDeferred failed (${res?.status ?? "network"})`);
|
|
@@ -2352,7 +2443,7 @@ var SendoraSDK = class {
|
|
|
2352
2443
|
if (!self.config) throw new Error("[sendoracloud] init() must complete before support.createTicket().");
|
|
2353
2444
|
const res = await post(
|
|
2354
2445
|
{ apiUrl: self.config.apiUrl, publicKey: self.config.publicKey, debug: self.debug },
|
|
2355
|
-
"/support/tickets",
|
|
2446
|
+
"/api/v1/support/tickets",
|
|
2356
2447
|
input
|
|
2357
2448
|
);
|
|
2358
2449
|
if (!res || !res.ok) throw new Error(`[sendoracloud] support.createTicket failed (${res?.status ?? "network"})`);
|
|
@@ -2364,7 +2455,7 @@ var SendoraSDK = class {
|
|
|
2364
2455
|
if (!self.config) throw new Error("[sendoracloud] init() must complete before support.submitCsat().");
|
|
2365
2456
|
const res = await post(
|
|
2366
2457
|
{ apiUrl: self.config.apiUrl, publicKey: self.config.publicKey, debug: self.debug },
|
|
2367
|
-
"/support/csat",
|
|
2458
|
+
"/api/v1/support/csat",
|
|
2368
2459
|
input
|
|
2369
2460
|
);
|
|
2370
2461
|
if (!res || !res.ok) throw new Error(`[sendoracloud] support.submitCsat failed (${res?.status ?? "network"})`);
|
|
@@ -2377,11 +2468,12 @@ var SendoraSDK = class {
|
|
|
2377
2468
|
const token = await self.auth.getAccessToken();
|
|
2378
2469
|
if (!token) return 0;
|
|
2379
2470
|
try {
|
|
2380
|
-
const res = await
|
|
2381
|
-
|
|
2382
|
-
{
|
|
2471
|
+
const res = await getJson(
|
|
2472
|
+
{ apiUrl: self.config.apiUrl, publicKey: self.config.publicKey, debug: self.debug },
|
|
2473
|
+
`/api/v1/widgets/${encodeURIComponent(widgetId)}/my-tickets/unread-count`,
|
|
2474
|
+
{ Authorization: `Bearer ${token}` }
|
|
2383
2475
|
);
|
|
2384
|
-
if (!res.ok) return 0;
|
|
2476
|
+
if (!res || !res.ok) return 0;
|
|
2385
2477
|
const parsed = await res.json();
|
|
2386
2478
|
return parsed?.data?.count ?? 0;
|
|
2387
2479
|
} catch {
|
|
@@ -2410,12 +2502,11 @@ var SendoraSDK = class {
|
|
|
2410
2502
|
if (!input.query || input.query.trim().length === 0) return [];
|
|
2411
2503
|
const params = new URLSearchParams({ query: input.query });
|
|
2412
2504
|
if (input.limit) params.set("limit", String(input.limit));
|
|
2413
|
-
const
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
});
|
|
2418
|
-
if (!res.ok) throw new Error(`[sendoracloud] kb.search failed (${res.status})`);
|
|
2505
|
+
const res = await getJson(
|
|
2506
|
+
{ apiUrl: self.config.apiUrl, publicKey: self.config.publicKey, debug: self.debug },
|
|
2507
|
+
`/api/v1/kb/search?${params.toString()}`
|
|
2508
|
+
);
|
|
2509
|
+
if (!res || !res.ok) throw new Error(`[sendoracloud] kb.search failed (${res?.status ?? "network"})`);
|
|
2419
2510
|
const parsed = await res.json();
|
|
2420
2511
|
if (!parsed.success || !Array.isArray(parsed.data)) {
|
|
2421
2512
|
throw new Error("[sendoracloud] kb.search: bad response");
|
|
@@ -2453,7 +2544,7 @@ var SendoraSDK = class {
|
|
|
2453
2544
|
if (!self.config) throw new Error("[sendoracloud] init() must complete before consent.record().");
|
|
2454
2545
|
const res = await post(
|
|
2455
2546
|
{ apiUrl: self.config.apiUrl, publicKey: self.config.publicKey, debug: self.debug },
|
|
2456
|
-
"/consent/record",
|
|
2547
|
+
"/api/v1/consent/record",
|
|
2457
2548
|
input
|
|
2458
2549
|
);
|
|
2459
2550
|
if (!res || !res.ok) throw new Error(`[sendoracloud] consent.record failed (${res?.status ?? "network"})`);
|
|
@@ -2465,7 +2556,7 @@ var SendoraSDK = class {
|
|
|
2465
2556
|
if (!self.config) throw new Error("[sendoracloud] init() must complete before consent.requestDeletion().");
|
|
2466
2557
|
const res = await post(
|
|
2467
2558
|
{ apiUrl: self.config.apiUrl, publicKey: self.config.publicKey, debug: self.debug },
|
|
2468
|
-
"/consent/deletion-request",
|
|
2559
|
+
"/api/v1/consent/deletion-request",
|
|
2469
2560
|
input
|
|
2470
2561
|
);
|
|
2471
2562
|
if (!res || !res.ok) throw new Error(`[sendoracloud] consent.requestDeletion failed (${res?.status ?? "network"})`);
|
|
@@ -2501,11 +2592,11 @@ var SendoraSDK = class {
|
|
|
2501
2592
|
fetchConfig: async (surveyId) => {
|
|
2502
2593
|
if (!self.config) throw new Error("[sendoracloud] init() must complete before surveys.fetchConfig().");
|
|
2503
2594
|
if (typeof surveyId !== "string" || surveyId.length === 0) return null;
|
|
2504
|
-
const
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2595
|
+
const res = await getJson(
|
|
2596
|
+
{ apiUrl: self.config.apiUrl, publicKey: self.config.publicKey, debug: self.debug },
|
|
2597
|
+
`/api/v1/surveys/${encodeURIComponent(surveyId)}/config`
|
|
2598
|
+
);
|
|
2599
|
+
if (!res) return null;
|
|
2509
2600
|
if (res.status === 404) return null;
|
|
2510
2601
|
if (!res.ok) throw new Error(`[sendoracloud] surveys.fetchConfig failed (${res.status})`);
|
|
2511
2602
|
const parsed = await res.json();
|
|
@@ -2516,7 +2607,7 @@ var SendoraSDK = class {
|
|
|
2516
2607
|
if (!self.config) throw new Error("[sendoracloud] init() must complete before surveys.submitResponse().");
|
|
2517
2608
|
const res = await post(
|
|
2518
2609
|
{ apiUrl: self.config.apiUrl, publicKey: self.config.publicKey, debug: self.debug },
|
|
2519
|
-
"/surveys/responses",
|
|
2610
|
+
"/api/v1/surveys/responses",
|
|
2520
2611
|
{ completed: true, ...input }
|
|
2521
2612
|
);
|
|
2522
2613
|
if (!res || !res.ok) throw new Error(`[sendoracloud] surveys.submitResponse failed (${res?.status ?? "network"})`);
|
|
@@ -2550,12 +2641,11 @@ var SendoraSDK = class {
|
|
|
2550
2641
|
return {
|
|
2551
2642
|
fetchActive: async () => {
|
|
2552
2643
|
if (!self.config) throw new Error("[sendoracloud] init() must complete before messages.fetchActive().");
|
|
2553
|
-
const
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
});
|
|
2558
|
-
if (!res.ok) throw new Error(`[sendoracloud] messages.fetchActive failed (${res.status})`);
|
|
2644
|
+
const res = await getJson(
|
|
2645
|
+
{ apiUrl: self.config.apiUrl, publicKey: self.config.publicKey, debug: self.debug },
|
|
2646
|
+
"/api/v1/in-app-messages/active"
|
|
2647
|
+
);
|
|
2648
|
+
if (!res || !res.ok) throw new Error(`[sendoracloud] messages.fetchActive failed (${res?.status ?? "network"})`);
|
|
2559
2649
|
const parsed = await res.json();
|
|
2560
2650
|
if (!parsed.success || !Array.isArray(parsed.data)) {
|
|
2561
2651
|
throw new Error("[sendoracloud] messages.fetchActive: bad response");
|
|
@@ -2566,7 +2656,7 @@ var SendoraSDK = class {
|
|
|
2566
2656
|
if (!self.config) throw new Error("[sendoracloud] init() must complete before messages.recordImpression().");
|
|
2567
2657
|
const res = await post(
|
|
2568
2658
|
{ apiUrl: self.config.apiUrl, publicKey: self.config.publicKey, debug: self.debug },
|
|
2569
|
-
"/in-app-messages/impressions",
|
|
2659
|
+
"/api/v1/in-app-messages/impressions",
|
|
2570
2660
|
input
|
|
2571
2661
|
);
|
|
2572
2662
|
if (!res || !res.ok) throw new Error(`[sendoracloud] messages.recordImpression failed (${res?.status ?? "network"})`);
|
|
@@ -2604,7 +2694,7 @@ var SendoraSDK = class {
|
|
|
2604
2694
|
};
|
|
2605
2695
|
const res = await post(
|
|
2606
2696
|
{ apiUrl: self.config.apiUrl, publicKey: self.config.publicKey, debug: self.debug },
|
|
2607
|
-
"/feature-flags/evaluate",
|
|
2697
|
+
"/api/v1/feature-flags/evaluate",
|
|
2608
2698
|
body
|
|
2609
2699
|
);
|
|
2610
2700
|
if (!res || !res.ok) throw new Error(`[sendoracloud] flags.evaluate failed (${res?.status ?? "network"})`);
|
|
@@ -2621,7 +2711,7 @@ var SendoraSDK = class {
|
|
|
2621
2711
|
};
|
|
2622
2712
|
const res = await post(
|
|
2623
2713
|
{ apiUrl: self.config.apiUrl, publicKey: self.config.publicKey, debug: self.debug },
|
|
2624
|
-
"/feature-flags/evaluate-all",
|
|
2714
|
+
"/api/v1/feature-flags/evaluate-all",
|
|
2625
2715
|
body
|
|
2626
2716
|
);
|
|
2627
2717
|
if (!res || !res.ok) throw new Error(`[sendoracloud] flags.evaluateAll failed (${res?.status ?? "network"})`);
|
|
@@ -2656,7 +2746,7 @@ var SendoraSDK = class {
|
|
|
2656
2746
|
};
|
|
2657
2747
|
const res = await post(
|
|
2658
2748
|
{ apiUrl: self.config.apiUrl, publicKey: self.config.publicKey, debug: self.debug },
|
|
2659
|
-
"/chatbot/message",
|
|
2749
|
+
"/api/v1/chatbot/message",
|
|
2660
2750
|
body
|
|
2661
2751
|
);
|
|
2662
2752
|
if (!res || !res.ok) throw new Error(`[sendoracloud] chatbot.sendMessage failed (${res?.status ?? "network"})`);
|
package/dist/index.d.cts
CHANGED
|
@@ -50,9 +50,16 @@ declare class Storage {
|
|
|
50
50
|
* so a UI double-submit can't mint two anonymous users or interleave
|
|
51
51
|
* a signIn + signOut.
|
|
52
52
|
*
|
|
53
|
-
* Tokens
|
|
54
|
-
*
|
|
55
|
-
*
|
|
53
|
+
* Tokens (including the refresh token) persist in AsyncStorage, not
|
|
54
|
+
* the iOS Keychain / Android Keystore. AsyncStorage is unencrypted
|
|
55
|
+
* app-sandbox storage: readable by anyone with filesystem/backup
|
|
56
|
+
* access to a jailbroken/rooted or unlocked device, but isolated from
|
|
57
|
+
* other apps by the OS sandbox. There is NO `secureStorage` adapter
|
|
58
|
+
* option today — earlier docs claimed a Keychain/SecureStore-backed
|
|
59
|
+
* adapter that was never implemented. If you ship a follow-up that
|
|
60
|
+
* moves the refresh token to expo-secure-store / react-native-keychain
|
|
61
|
+
* (optional peer dep, Metro-resolvable static import — never an inline
|
|
62
|
+
* require), keep the `auth_refresh_token` storage key for back-compat.
|
|
56
63
|
*
|
|
57
64
|
* Response payloads are validated non-empty before persisting so a
|
|
58
65
|
* malformed/MITM'd response can't install an `id = ""` user.
|
|
@@ -134,6 +141,14 @@ declare class Auth {
|
|
|
134
141
|
private refreshCooldownUntil;
|
|
135
142
|
private takeoverListeners;
|
|
136
143
|
private lastTakeover;
|
|
144
|
+
/**
|
|
145
|
+
* Anon refresh token captured at `signIn()` time, stashed keyed to the
|
|
146
|
+
* issued `mfaChallengeToken` so the later `challengeMfa()` can forward it
|
|
147
|
+
* for device-takeover (s58.111). Before this, the MFA branch wiped the anon
|
|
148
|
+
* identity before the challenge resolved → the takeover hint was lost → one
|
|
149
|
+
* device ended with two user_ids + duplicate pushes (audit s58.203 fix).
|
|
150
|
+
*/
|
|
151
|
+
private pendingAnonTakeover;
|
|
137
152
|
/**
|
|
138
153
|
* Resolves when the parent SDK has finished its async init() —
|
|
139
154
|
* AsyncStorage hydrated + auth.hydrate() restored the cached
|
package/dist/index.d.ts
CHANGED
|
@@ -50,9 +50,16 @@ declare class Storage {
|
|
|
50
50
|
* so a UI double-submit can't mint two anonymous users or interleave
|
|
51
51
|
* a signIn + signOut.
|
|
52
52
|
*
|
|
53
|
-
* Tokens
|
|
54
|
-
*
|
|
55
|
-
*
|
|
53
|
+
* Tokens (including the refresh token) persist in AsyncStorage, not
|
|
54
|
+
* the iOS Keychain / Android Keystore. AsyncStorage is unencrypted
|
|
55
|
+
* app-sandbox storage: readable by anyone with filesystem/backup
|
|
56
|
+
* access to a jailbroken/rooted or unlocked device, but isolated from
|
|
57
|
+
* other apps by the OS sandbox. There is NO `secureStorage` adapter
|
|
58
|
+
* option today — earlier docs claimed a Keychain/SecureStore-backed
|
|
59
|
+
* adapter that was never implemented. If you ship a follow-up that
|
|
60
|
+
* moves the refresh token to expo-secure-store / react-native-keychain
|
|
61
|
+
* (optional peer dep, Metro-resolvable static import — never an inline
|
|
62
|
+
* require), keep the `auth_refresh_token` storage key for back-compat.
|
|
56
63
|
*
|
|
57
64
|
* Response payloads are validated non-empty before persisting so a
|
|
58
65
|
* malformed/MITM'd response can't install an `id = ""` user.
|
|
@@ -134,6 +141,14 @@ declare class Auth {
|
|
|
134
141
|
private refreshCooldownUntil;
|
|
135
142
|
private takeoverListeners;
|
|
136
143
|
private lastTakeover;
|
|
144
|
+
/**
|
|
145
|
+
* Anon refresh token captured at `signIn()` time, stashed keyed to the
|
|
146
|
+
* issued `mfaChallengeToken` so the later `challengeMfa()` can forward it
|
|
147
|
+
* for device-takeover (s58.111). Before this, the MFA branch wiped the anon
|
|
148
|
+
* identity before the challenge resolved → the takeover hint was lost → one
|
|
149
|
+
* device ended with two user_ids + duplicate pushes (audit s58.203 fix).
|
|
150
|
+
*/
|
|
151
|
+
private pendingAnonTakeover;
|
|
137
152
|
/**
|
|
138
153
|
* Resolves when the parent SDK has finished its async init() —
|
|
139
154
|
* AsyncStorage hydrated + auth.hydrate() restored the cached
|
package/dist/index.js
CHANGED
|
@@ -176,6 +176,82 @@ async function request(opts, method, path, body, extraHeaders, reqOpts) {
|
|
|
176
176
|
return res;
|
|
177
177
|
}
|
|
178
178
|
|
|
179
|
+
// package.json
|
|
180
|
+
var package_default = {
|
|
181
|
+
name: "@sendoracloud/sdk-react-native",
|
|
182
|
+
version: "1.6.2",
|
|
183
|
+
description: "Sendora Cloud React Native + Expo SDK \u2014 analytics, identity, push-token registration, auth, deep links (Branch / Firebase Dynamic Links parity). Auth + analytics + deep links work in Expo Go; push token registration requires a Dev Client (EAS Build or `npx expo prebuild`).",
|
|
184
|
+
type: "module",
|
|
185
|
+
main: "./dist/index.cjs",
|
|
186
|
+
module: "./dist/index.js",
|
|
187
|
+
types: "./dist/index.d.ts",
|
|
188
|
+
exports: {
|
|
189
|
+
".": {
|
|
190
|
+
types: "./dist/index.d.ts",
|
|
191
|
+
import: "./dist/index.js",
|
|
192
|
+
require: "./dist/index.cjs"
|
|
193
|
+
},
|
|
194
|
+
"./contact-widget": {
|
|
195
|
+
types: "./dist/contact-widget.d.ts",
|
|
196
|
+
import: "./dist/contact-widget.js",
|
|
197
|
+
require: "./dist/contact-widget.cjs"
|
|
198
|
+
}
|
|
199
|
+
},
|
|
200
|
+
files: [
|
|
201
|
+
"dist",
|
|
202
|
+
"examples",
|
|
203
|
+
"README.md",
|
|
204
|
+
"LICENSE"
|
|
205
|
+
],
|
|
206
|
+
keywords: [
|
|
207
|
+
"sendora",
|
|
208
|
+
"react-native",
|
|
209
|
+
"expo",
|
|
210
|
+
"analytics",
|
|
211
|
+
"tracking",
|
|
212
|
+
"customer-engagement"
|
|
213
|
+
],
|
|
214
|
+
dependencies: {
|
|
215
|
+
"react-native-get-random-values": "^1.11.0"
|
|
216
|
+
},
|
|
217
|
+
peerDependencies: {
|
|
218
|
+
"@react-native-async-storage/async-storage": ">=1.17.0",
|
|
219
|
+
"react-native": ">=0.70.0",
|
|
220
|
+
"react-native-webview": ">=13.0.0"
|
|
221
|
+
},
|
|
222
|
+
peerDependenciesMeta: {
|
|
223
|
+
"@react-native-async-storage/async-storage": {
|
|
224
|
+
optional: false
|
|
225
|
+
},
|
|
226
|
+
"react-native": {
|
|
227
|
+
optional: false
|
|
228
|
+
},
|
|
229
|
+
"react-native-webview": {
|
|
230
|
+
optional: true
|
|
231
|
+
}
|
|
232
|
+
},
|
|
233
|
+
scripts: {
|
|
234
|
+
build: "tsup src/index.ts src/contact-widget.tsx --format esm,cjs --dts --clean --external react --external react-native --external react-native-webview",
|
|
235
|
+
typecheck: "tsc --noEmit"
|
|
236
|
+
},
|
|
237
|
+
devDependencies: {
|
|
238
|
+
"@types/react": "^19.2.16",
|
|
239
|
+
"react-native": "^0.85.3",
|
|
240
|
+
"react-native-webview": "^13.16.1",
|
|
241
|
+
tsup: "^8.0.0",
|
|
242
|
+
typescript: "^5.0.0"
|
|
243
|
+
},
|
|
244
|
+
license: "MIT",
|
|
245
|
+
repository: {
|
|
246
|
+
type: "git",
|
|
247
|
+
url: "https://github.com/sendoracloud/sdk-react-native"
|
|
248
|
+
},
|
|
249
|
+
homepage: "https://sendoracloud.com/sdks"
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
// src/version.ts
|
|
253
|
+
var SDK_VERSION = package_default.version;
|
|
254
|
+
|
|
179
255
|
// src/auth.ts
|
|
180
256
|
import { AppState as RnAppState } from "react-native";
|
|
181
257
|
var TOKEN_KEY = "auth_access_token";
|
|
@@ -234,6 +310,14 @@ var Auth = class {
|
|
|
234
310
|
this.refreshCooldownUntil = 0;
|
|
235
311
|
this.takeoverListeners = /* @__PURE__ */ new Set();
|
|
236
312
|
this.lastTakeover = null;
|
|
313
|
+
/**
|
|
314
|
+
* Anon refresh token captured at `signIn()` time, stashed keyed to the
|
|
315
|
+
* issued `mfaChallengeToken` so the later `challengeMfa()` can forward it
|
|
316
|
+
* for device-takeover (s58.111). Before this, the MFA branch wiped the anon
|
|
317
|
+
* identity before the challenge resolved → the takeover hint was lost → one
|
|
318
|
+
* device ended with two user_ids + duplicate pushes (audit s58.203 fix).
|
|
319
|
+
*/
|
|
320
|
+
this.pendingAnonTakeover = null;
|
|
237
321
|
/**
|
|
238
322
|
* Proactive refresh (s58.72). Fires when access-token age crosses
|
|
239
323
|
* ~80% of TTL. Pairs with the AppState foreground listener so a
|
|
@@ -453,12 +537,7 @@ var Auth = class {
|
|
|
453
537
|
*/
|
|
454
538
|
signIn(email, password) {
|
|
455
539
|
return this.serialize(async () => {
|
|
456
|
-
|
|
457
|
-
if (this.user?.isAnonymous) {
|
|
458
|
-
const stashed = this.hooks.storage.get(REFRESH_KEY);
|
|
459
|
-
if (stashed) prevAnonRefreshToken = stashed;
|
|
460
|
-
}
|
|
461
|
-
if (this.user !== null) await this.wipeLocalIdentity();
|
|
540
|
+
const prevAnonRefreshToken = this.readPrevAnonRefreshToken();
|
|
462
541
|
const res = await post(
|
|
463
542
|
{ apiUrl: this.hooks.apiUrl, publicKey: this.hooks.publicKey, debug: this.hooks.debug },
|
|
464
543
|
"/api/v1/auth-service/login",
|
|
@@ -476,12 +555,16 @@ var Auth = class {
|
|
|
476
555
|
}
|
|
477
556
|
const r = parsed.data;
|
|
478
557
|
if (r.mfaRequired === true && typeof r.mfaChallengeToken === "string") {
|
|
558
|
+
if (prevAnonRefreshToken) {
|
|
559
|
+
this.pendingAnonTakeover = { challengeToken: r.mfaChallengeToken, prevAnonRefreshToken };
|
|
560
|
+
}
|
|
479
561
|
const u = r.user;
|
|
480
562
|
return { mfaRequired: true, mfaChallengeToken: r.mfaChallengeToken, userId: u?.id ?? "" };
|
|
481
563
|
}
|
|
482
564
|
if (!isAuthApiResponse(parsed.data)) {
|
|
483
565
|
throw new AuthError(parsed.error?.code ?? "AUTH_ERROR", parsed.error?.message ?? "Login failed");
|
|
484
566
|
}
|
|
567
|
+
if (this.user !== null) await this.wipeLocalIdentity();
|
|
485
568
|
this.persist(parsed.data);
|
|
486
569
|
return parsed.data.user;
|
|
487
570
|
});
|
|
@@ -563,13 +646,15 @@ var Auth = class {
|
|
|
563
646
|
*/
|
|
564
647
|
challengeMfa(mfaChallengeToken, code) {
|
|
565
648
|
return this.serialize(async () => {
|
|
566
|
-
const
|
|
649
|
+
const stashed = this.pendingAnonTakeover?.challengeToken === mfaChallengeToken ? this.pendingAnonTakeover.prevAnonRefreshToken : void 0;
|
|
650
|
+
const prevAnonRefreshToken = stashed ?? this.readPrevAnonRefreshToken();
|
|
567
651
|
const body = {
|
|
568
652
|
challengeToken: mfaChallengeToken,
|
|
569
653
|
code
|
|
570
654
|
};
|
|
571
655
|
if (prevAnonRefreshToken) body.prevAnonRefreshToken = prevAnonRefreshToken;
|
|
572
656
|
const data = await this.callAuth("/api/v1/auth-service/mfa/challenge", body);
|
|
657
|
+
if (this.user !== null) await this.wipeLocalIdentity();
|
|
573
658
|
this.persist(data);
|
|
574
659
|
return data.user;
|
|
575
660
|
});
|
|
@@ -1109,6 +1194,7 @@ var Auth = class {
|
|
|
1109
1194
|
this.accessToken = null;
|
|
1110
1195
|
this.accessExpiresAt = 0;
|
|
1111
1196
|
this.lastTakeover = null;
|
|
1197
|
+
this.pendingAnonTakeover = null;
|
|
1112
1198
|
this.hooks.storage.remove(USER_KEY);
|
|
1113
1199
|
this.hooks.storage.remove(TOKEN_KEY);
|
|
1114
1200
|
this.hooks.storage.remove(TOKEN_EXPIRES_KEY);
|
|
@@ -1801,7 +1887,6 @@ function installAutoTrack(args) {
|
|
|
1801
1887
|
}
|
|
1802
1888
|
|
|
1803
1889
|
// src/index.ts
|
|
1804
|
-
var SDK_VERSION = "1.0.0";
|
|
1805
1890
|
var DEFAULT_API_URL = "https://api.sendoracloud.com";
|
|
1806
1891
|
var ANON_KEY = "anon_id";
|
|
1807
1892
|
var USER_ID_KEY = "user_id";
|
|
@@ -2111,7 +2196,13 @@ var SendoraSDK = class {
|
|
|
2111
2196
|
{ apiUrl: this.config.apiUrl, publicKey: this.config.publicKey, debug: this.debug },
|
|
2112
2197
|
"/api/v1/push/tokens",
|
|
2113
2198
|
{
|
|
2114
|
-
|
|
2199
|
+
// Conditional spread — the backend's registerPushTokenSchema makes
|
|
2200
|
+
// userId `.optional()`, which accepts a missing key but REJECTS
|
|
2201
|
+
// `null` (422 "userId: Expected string, received null"). Anonymous
|
|
2202
|
+
// installs (pre-identify()) carry a null userId, so OMIT the field
|
|
2203
|
+
// rather than send null — anon devices must still be able to
|
|
2204
|
+
// register. Mirrors the same conditional-spread sendEvent() uses.
|
|
2205
|
+
...this.userId ? { userId: this.userId } : {},
|
|
2115
2206
|
anonymousId: this.anonId,
|
|
2116
2207
|
token: reg.token,
|
|
2117
2208
|
platform: reg.platform,
|
|
@@ -2153,7 +2244,7 @@ var SendoraSDK = class {
|
|
|
2153
2244
|
if (clickAction !== void 0) body.clickAction = clickAction;
|
|
2154
2245
|
await post(
|
|
2155
2246
|
{ apiUrl: self.config.apiUrl, publicKey: self.config.publicKey, debug: self.debug },
|
|
2156
|
-
"/push/track-open",
|
|
2247
|
+
"/api/v1/push/track-open",
|
|
2157
2248
|
body
|
|
2158
2249
|
);
|
|
2159
2250
|
},
|
|
@@ -2172,7 +2263,7 @@ var SendoraSDK = class {
|
|
|
2172
2263
|
if (!self.config) throw new Error("[sendoracloud] init() must complete before push.liveActivities.startToken().");
|
|
2173
2264
|
const res = await post(
|
|
2174
2265
|
{ apiUrl: self.config.apiUrl, publicKey: self.config.publicKey, debug: self.debug },
|
|
2175
|
-
"/push/live-activities/start-token",
|
|
2266
|
+
"/api/v1/push/live-activities/start-token",
|
|
2176
2267
|
{
|
|
2177
2268
|
pushToken: input.pushToken,
|
|
2178
2269
|
activityType: input.activityType,
|
|
@@ -2206,7 +2297,7 @@ var SendoraSDK = class {
|
|
|
2206
2297
|
if (!self.config) throw new Error("[sendoracloud] init() must complete before push.geofences.list().");
|
|
2207
2298
|
const res = await getJson(
|
|
2208
2299
|
{ apiUrl: self.config.apiUrl, publicKey: self.config.publicKey, debug: self.debug },
|
|
2209
|
-
"/push/geofences/list-for-device"
|
|
2300
|
+
"/api/v1/push/geofences/list-for-device"
|
|
2210
2301
|
);
|
|
2211
2302
|
if (!res || !res.ok) {
|
|
2212
2303
|
throw new Error(`[sendoracloud] geofences list failed (HTTP ${res?.status ?? "network"}).`);
|
|
@@ -2224,7 +2315,7 @@ var SendoraSDK = class {
|
|
|
2224
2315
|
if (!self.config) throw new Error("[sendoracloud] init() must complete before push.geofences.recordEvent().");
|
|
2225
2316
|
const res = await post(
|
|
2226
2317
|
{ apiUrl: self.config.apiUrl, publicKey: self.config.publicKey, debug: self.debug },
|
|
2227
|
-
"/push/geofences/event",
|
|
2318
|
+
"/api/v1/push/geofences/event",
|
|
2228
2319
|
{
|
|
2229
2320
|
geofenceId: input.geofenceId,
|
|
2230
2321
|
kind: input.kind,
|
|
@@ -2268,7 +2359,7 @@ var SendoraSDK = class {
|
|
|
2268
2359
|
if (!self.config) throw new Error("[sendoracloud] init() must complete before attribution.reportInstall().");
|
|
2269
2360
|
const res = await post(
|
|
2270
2361
|
{ apiUrl: self.config.apiUrl, publicKey: self.config.publicKey, debug: self.debug },
|
|
2271
|
-
"/attribution/install",
|
|
2362
|
+
"/api/v1/attribution/install",
|
|
2272
2363
|
input ?? {}
|
|
2273
2364
|
);
|
|
2274
2365
|
if (!res || !res.ok) throw new Error(`[sendoracloud] attribution.reportInstall failed (${res?.status ?? "network"})`);
|
|
@@ -2280,7 +2371,7 @@ var SendoraSDK = class {
|
|
|
2280
2371
|
if (!self.config) throw new Error("[sendoracloud] init() must complete before attribution.checkDeferred().");
|
|
2281
2372
|
const res = await post(
|
|
2282
2373
|
{ apiUrl: self.config.apiUrl, publicKey: self.config.publicKey, debug: self.debug },
|
|
2283
|
-
"/attribution/deferred",
|
|
2374
|
+
"/api/v1/attribution/deferred",
|
|
2284
2375
|
input ?? {}
|
|
2285
2376
|
);
|
|
2286
2377
|
if (!res || !res.ok) throw new Error(`[sendoracloud] attribution.checkDeferred failed (${res?.status ?? "network"})`);
|
|
@@ -2314,7 +2405,7 @@ var SendoraSDK = class {
|
|
|
2314
2405
|
if (!self.config) throw new Error("[sendoracloud] init() must complete before support.createTicket().");
|
|
2315
2406
|
const res = await post(
|
|
2316
2407
|
{ apiUrl: self.config.apiUrl, publicKey: self.config.publicKey, debug: self.debug },
|
|
2317
|
-
"/support/tickets",
|
|
2408
|
+
"/api/v1/support/tickets",
|
|
2318
2409
|
input
|
|
2319
2410
|
);
|
|
2320
2411
|
if (!res || !res.ok) throw new Error(`[sendoracloud] support.createTicket failed (${res?.status ?? "network"})`);
|
|
@@ -2326,7 +2417,7 @@ var SendoraSDK = class {
|
|
|
2326
2417
|
if (!self.config) throw new Error("[sendoracloud] init() must complete before support.submitCsat().");
|
|
2327
2418
|
const res = await post(
|
|
2328
2419
|
{ apiUrl: self.config.apiUrl, publicKey: self.config.publicKey, debug: self.debug },
|
|
2329
|
-
"/support/csat",
|
|
2420
|
+
"/api/v1/support/csat",
|
|
2330
2421
|
input
|
|
2331
2422
|
);
|
|
2332
2423
|
if (!res || !res.ok) throw new Error(`[sendoracloud] support.submitCsat failed (${res?.status ?? "network"})`);
|
|
@@ -2339,11 +2430,12 @@ var SendoraSDK = class {
|
|
|
2339
2430
|
const token = await self.auth.getAccessToken();
|
|
2340
2431
|
if (!token) return 0;
|
|
2341
2432
|
try {
|
|
2342
|
-
const res = await
|
|
2343
|
-
|
|
2344
|
-
{
|
|
2433
|
+
const res = await getJson(
|
|
2434
|
+
{ apiUrl: self.config.apiUrl, publicKey: self.config.publicKey, debug: self.debug },
|
|
2435
|
+
`/api/v1/widgets/${encodeURIComponent(widgetId)}/my-tickets/unread-count`,
|
|
2436
|
+
{ Authorization: `Bearer ${token}` }
|
|
2345
2437
|
);
|
|
2346
|
-
if (!res.ok) return 0;
|
|
2438
|
+
if (!res || !res.ok) return 0;
|
|
2347
2439
|
const parsed = await res.json();
|
|
2348
2440
|
return parsed?.data?.count ?? 0;
|
|
2349
2441
|
} catch {
|
|
@@ -2372,12 +2464,11 @@ var SendoraSDK = class {
|
|
|
2372
2464
|
if (!input.query || input.query.trim().length === 0) return [];
|
|
2373
2465
|
const params = new URLSearchParams({ query: input.query });
|
|
2374
2466
|
if (input.limit) params.set("limit", String(input.limit));
|
|
2375
|
-
const
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
});
|
|
2380
|
-
if (!res.ok) throw new Error(`[sendoracloud] kb.search failed (${res.status})`);
|
|
2467
|
+
const res = await getJson(
|
|
2468
|
+
{ apiUrl: self.config.apiUrl, publicKey: self.config.publicKey, debug: self.debug },
|
|
2469
|
+
`/api/v1/kb/search?${params.toString()}`
|
|
2470
|
+
);
|
|
2471
|
+
if (!res || !res.ok) throw new Error(`[sendoracloud] kb.search failed (${res?.status ?? "network"})`);
|
|
2381
2472
|
const parsed = await res.json();
|
|
2382
2473
|
if (!parsed.success || !Array.isArray(parsed.data)) {
|
|
2383
2474
|
throw new Error("[sendoracloud] kb.search: bad response");
|
|
@@ -2415,7 +2506,7 @@ var SendoraSDK = class {
|
|
|
2415
2506
|
if (!self.config) throw new Error("[sendoracloud] init() must complete before consent.record().");
|
|
2416
2507
|
const res = await post(
|
|
2417
2508
|
{ apiUrl: self.config.apiUrl, publicKey: self.config.publicKey, debug: self.debug },
|
|
2418
|
-
"/consent/record",
|
|
2509
|
+
"/api/v1/consent/record",
|
|
2419
2510
|
input
|
|
2420
2511
|
);
|
|
2421
2512
|
if (!res || !res.ok) throw new Error(`[sendoracloud] consent.record failed (${res?.status ?? "network"})`);
|
|
@@ -2427,7 +2518,7 @@ var SendoraSDK = class {
|
|
|
2427
2518
|
if (!self.config) throw new Error("[sendoracloud] init() must complete before consent.requestDeletion().");
|
|
2428
2519
|
const res = await post(
|
|
2429
2520
|
{ apiUrl: self.config.apiUrl, publicKey: self.config.publicKey, debug: self.debug },
|
|
2430
|
-
"/consent/deletion-request",
|
|
2521
|
+
"/api/v1/consent/deletion-request",
|
|
2431
2522
|
input
|
|
2432
2523
|
);
|
|
2433
2524
|
if (!res || !res.ok) throw new Error(`[sendoracloud] consent.requestDeletion failed (${res?.status ?? "network"})`);
|
|
@@ -2463,11 +2554,11 @@ var SendoraSDK = class {
|
|
|
2463
2554
|
fetchConfig: async (surveyId) => {
|
|
2464
2555
|
if (!self.config) throw new Error("[sendoracloud] init() must complete before surveys.fetchConfig().");
|
|
2465
2556
|
if (typeof surveyId !== "string" || surveyId.length === 0) return null;
|
|
2466
|
-
const
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2557
|
+
const res = await getJson(
|
|
2558
|
+
{ apiUrl: self.config.apiUrl, publicKey: self.config.publicKey, debug: self.debug },
|
|
2559
|
+
`/api/v1/surveys/${encodeURIComponent(surveyId)}/config`
|
|
2560
|
+
);
|
|
2561
|
+
if (!res) return null;
|
|
2471
2562
|
if (res.status === 404) return null;
|
|
2472
2563
|
if (!res.ok) throw new Error(`[sendoracloud] surveys.fetchConfig failed (${res.status})`);
|
|
2473
2564
|
const parsed = await res.json();
|
|
@@ -2478,7 +2569,7 @@ var SendoraSDK = class {
|
|
|
2478
2569
|
if (!self.config) throw new Error("[sendoracloud] init() must complete before surveys.submitResponse().");
|
|
2479
2570
|
const res = await post(
|
|
2480
2571
|
{ apiUrl: self.config.apiUrl, publicKey: self.config.publicKey, debug: self.debug },
|
|
2481
|
-
"/surveys/responses",
|
|
2572
|
+
"/api/v1/surveys/responses",
|
|
2482
2573
|
{ completed: true, ...input }
|
|
2483
2574
|
);
|
|
2484
2575
|
if (!res || !res.ok) throw new Error(`[sendoracloud] surveys.submitResponse failed (${res?.status ?? "network"})`);
|
|
@@ -2512,12 +2603,11 @@ var SendoraSDK = class {
|
|
|
2512
2603
|
return {
|
|
2513
2604
|
fetchActive: async () => {
|
|
2514
2605
|
if (!self.config) throw new Error("[sendoracloud] init() must complete before messages.fetchActive().");
|
|
2515
|
-
const
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
});
|
|
2520
|
-
if (!res.ok) throw new Error(`[sendoracloud] messages.fetchActive failed (${res.status})`);
|
|
2606
|
+
const res = await getJson(
|
|
2607
|
+
{ apiUrl: self.config.apiUrl, publicKey: self.config.publicKey, debug: self.debug },
|
|
2608
|
+
"/api/v1/in-app-messages/active"
|
|
2609
|
+
);
|
|
2610
|
+
if (!res || !res.ok) throw new Error(`[sendoracloud] messages.fetchActive failed (${res?.status ?? "network"})`);
|
|
2521
2611
|
const parsed = await res.json();
|
|
2522
2612
|
if (!parsed.success || !Array.isArray(parsed.data)) {
|
|
2523
2613
|
throw new Error("[sendoracloud] messages.fetchActive: bad response");
|
|
@@ -2528,7 +2618,7 @@ var SendoraSDK = class {
|
|
|
2528
2618
|
if (!self.config) throw new Error("[sendoracloud] init() must complete before messages.recordImpression().");
|
|
2529
2619
|
const res = await post(
|
|
2530
2620
|
{ apiUrl: self.config.apiUrl, publicKey: self.config.publicKey, debug: self.debug },
|
|
2531
|
-
"/in-app-messages/impressions",
|
|
2621
|
+
"/api/v1/in-app-messages/impressions",
|
|
2532
2622
|
input
|
|
2533
2623
|
);
|
|
2534
2624
|
if (!res || !res.ok) throw new Error(`[sendoracloud] messages.recordImpression failed (${res?.status ?? "network"})`);
|
|
@@ -2566,7 +2656,7 @@ var SendoraSDK = class {
|
|
|
2566
2656
|
};
|
|
2567
2657
|
const res = await post(
|
|
2568
2658
|
{ apiUrl: self.config.apiUrl, publicKey: self.config.publicKey, debug: self.debug },
|
|
2569
|
-
"/feature-flags/evaluate",
|
|
2659
|
+
"/api/v1/feature-flags/evaluate",
|
|
2570
2660
|
body
|
|
2571
2661
|
);
|
|
2572
2662
|
if (!res || !res.ok) throw new Error(`[sendoracloud] flags.evaluate failed (${res?.status ?? "network"})`);
|
|
@@ -2583,7 +2673,7 @@ var SendoraSDK = class {
|
|
|
2583
2673
|
};
|
|
2584
2674
|
const res = await post(
|
|
2585
2675
|
{ apiUrl: self.config.apiUrl, publicKey: self.config.publicKey, debug: self.debug },
|
|
2586
|
-
"/feature-flags/evaluate-all",
|
|
2676
|
+
"/api/v1/feature-flags/evaluate-all",
|
|
2587
2677
|
body
|
|
2588
2678
|
);
|
|
2589
2679
|
if (!res || !res.ok) throw new Error(`[sendoracloud] flags.evaluateAll failed (${res?.status ?? "network"})`);
|
|
@@ -2618,7 +2708,7 @@ var SendoraSDK = class {
|
|
|
2618
2708
|
};
|
|
2619
2709
|
const res = await post(
|
|
2620
2710
|
{ apiUrl: self.config.apiUrl, publicKey: self.config.publicKey, debug: self.debug },
|
|
2621
|
-
"/chatbot/message",
|
|
2711
|
+
"/api/v1/chatbot/message",
|
|
2622
2712
|
body
|
|
2623
2713
|
);
|
|
2624
2714
|
if (!res || !res.ok) throw new Error(`[sendoracloud] chatbot.sendMessage failed (${res?.status ?? "network"})`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sendoracloud/sdk-react-native",
|
|
3
|
-
"version": "1.6.
|
|
3
|
+
"version": "1.6.2",
|
|
4
4
|
"description": "Sendora Cloud React Native + Expo SDK — analytics, identity, push-token registration, auth, deep links (Branch / Firebase Dynamic Links parity). Auth + analytics + deep links work in Expo Go; push token registration requires a Dev Client (EAS Build or `npx expo prebuild`).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|