@mrrocket/sdk 0.15.0 → 0.16.0

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.
Files changed (64) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/README.md +36 -1
  3. package/dist/api-client.d.ts +5 -4
  4. package/dist/api-client.js +12 -8
  5. package/dist/api-client.js.map +1 -1
  6. package/dist/api.d.ts +4 -1
  7. package/dist/api.js +2 -1
  8. package/dist/api.js.map +1 -1
  9. package/dist/batch.d.ts +6 -4
  10. package/dist/batch.js +3 -3
  11. package/dist/batch.js.map +1 -1
  12. package/dist/browser/mrrocket-flows.js +2 -2
  13. package/dist/browser/mrrocket-forms.js +1 -1
  14. package/dist/browser/mrrocket-messenger.js +2 -2
  15. package/dist/browser/mrrocket-push.js +1 -1
  16. package/dist/browser/mrrocket-replay.js +3 -3
  17. package/dist/browser/mrrocket.js +1 -1
  18. package/dist/client.d.ts +10 -2
  19. package/dist/client.js +36 -15
  20. package/dist/client.js.map +1 -1
  21. package/dist/context.d.ts +5 -1
  22. package/dist/context.js.map +1 -1
  23. package/dist/core.d.ts +1 -1
  24. package/dist/core.js +1 -1
  25. package/dist/core.js.map +1 -1
  26. package/dist/errors.d.ts +5 -1
  27. package/dist/errors.js +8 -2
  28. package/dist/errors.js.map +1 -1
  29. package/dist/flows-web.d.ts +2 -0
  30. package/dist/flows-web.js +4 -1
  31. package/dist/flows-web.js.map +1 -1
  32. package/dist/flows.d.ts +10 -0
  33. package/dist/flows.js +2 -0
  34. package/dist/flows.js.map +1 -1
  35. package/dist/forms.d.ts +4 -0
  36. package/dist/forms.js +4 -1
  37. package/dist/forms.js.map +1 -1
  38. package/dist/identity.d.ts +42 -0
  39. package/dist/identity.js +24 -0
  40. package/dist/identity.js.map +1 -0
  41. package/dist/index.d.ts +2 -0
  42. package/dist/index.js.map +1 -1
  43. package/dist/messenger-ui.d.ts +2 -0
  44. package/dist/messenger-ui.js +2 -2
  45. package/dist/messenger-ui.js.map +1 -1
  46. package/dist/messenger.js +3 -2
  47. package/dist/messenger.js.map +1 -1
  48. package/dist/push-web.d.ts +2 -13
  49. package/dist/push-web.js +4 -2
  50. package/dist/push-web.js.map +1 -1
  51. package/dist/react-native/index.d.cts +61 -9
  52. package/dist/react-native/index.d.ts +61 -9
  53. package/dist/react-native/index.js +107 -28
  54. package/dist/react-native/index.js.map +1 -1
  55. package/dist/replay.d.ts +4 -1
  56. package/dist/replay.js +8 -2
  57. package/dist/replay.js.map +1 -1
  58. package/dist/server.d.ts +2 -0
  59. package/dist/server.js +2 -0
  60. package/dist/server.js.map +1 -1
  61. package/dist/user-hash.d.ts +27 -0
  62. package/dist/user-hash.js +24 -0
  63. package/dist/user-hash.js.map +1 -0
  64. package/package.json +5 -1
@@ -43,6 +43,7 @@ __export(native_exports, {
43
43
  useOnboarding: () => useOnboarding,
44
44
  usePaywall: () => usePaywall,
45
45
  usePush: () => usePush,
46
+ useReview: () => useReview,
46
47
  useText: () => useText,
47
48
  useVariant: () => useVariant
48
49
  });
@@ -50,7 +51,7 @@ module.exports = __toCommonJS(native_exports);
50
51
  var import_react3 = require("react");
51
52
 
52
53
  // src/core.ts
53
- var SDK_VERSION = "0.15.0";
54
+ var SDK_VERSION = "0.16.0";
54
55
  var propOf = (person, name) => {
55
56
  const want = name.trim().toLowerCase();
56
57
  for (const [k, v] of Object.entries(person)) if (k.toLowerCase() === want) return v;
@@ -172,6 +173,23 @@ var textChanges = (config, distinctId, preview) => {
172
173
  };
173
174
  var newDistinctId = () => typeof crypto !== "undefined" && "randomUUID" in crypto ? crypto.randomUUID() : `ck_${Math.random().toString(36).slice(2)}${Date.now().toString(36)}`;
174
175
 
176
+ // src/identity.ts
177
+ var UNSIGNED_HELP = "401: sign the user id on your server with userHash(id) from @mrrocket/sdk/next/server and pass identify(id, props, { hash }).";
178
+ var warnUnsigned = (status, log = (m) => console.warn(m)) => {
179
+ const g = globalThis;
180
+ if (status !== 401 || g.__mrrocketUnsigned) return;
181
+ g.__mrrocketUnsigned = true;
182
+ log(`[MRRocket] ${UNSIGNED_HELP}`);
183
+ };
184
+ var nextSigned = (signed, id, hash, log = (m) => console.warn(m)) => {
185
+ const h3 = typeof hash === "string" ? hash.trim().toLowerCase() : "";
186
+ if (/^[0-9a-f]{64}$/.test(h3)) return { id, hash: h3 };
187
+ if (h3) log("[MRRocket] identify: that hash is no userHash(id); not sent.");
188
+ return signed && signed.id === id ? signed : null;
189
+ };
190
+ var hashFor = (signed, id) => signed && id && signed.id === id ? signed.hash : void 0;
191
+ var hashForAll = (signed, ids) => signed && ids.some((id) => id === signed.id) && ids.every((id) => !id || id === signed.id) ? signed.hash : void 0;
192
+
175
193
  // src/api-client.ts
176
194
  var CHAT_FILE_MAX_BYTES = 10 * 1024 * 1024;
177
195
  var CHAT_FILES_PER_MESSAGE = 4;
@@ -212,6 +230,7 @@ var call = async (url, init, signal) => {
212
230
  throw new ApiError("Could not reach the server. Check your connection and try again.", 0);
213
231
  }
214
232
  if (!res.ok) {
233
+ warnUnsigned(res.status);
215
234
  let detail = "";
216
235
  try {
217
236
  detail = (await res.json()).error ?? "";
@@ -223,21 +242,22 @@ var call = async (url, init, signal) => {
223
242
  };
224
243
  var createApi = (c) => {
225
244
  const base = `${c.host}/api/sdk`;
226
- const who = { key: c.apiKey, distinctId: c.distinctId, user: c.user };
245
+ const who = { key: c.apiKey, distinctId: c.distinctId, user: c.user, userHash: c.userHash };
246
+ const me = { key: c.apiKey, distinctId: c.distinctId, userHash: c.userHash };
227
247
  const post = (path, body, signal) => call(`${base}${path}`, { method: "POST", body: JSON.stringify({ ...who, ...body }) }, signal);
228
248
  return {
229
- list: (o, signal) => call(`${base}/feedback?${qs({ key: c.apiKey, distinctId: c.distinctId, board: o.board, sort: o.sort, q: o.q })}`, void 0, signal),
230
- post: (id, signal) => call(`${base}/feedback/${encodeURIComponent(id)}?${qs({ key: c.apiKey, distinctId: c.distinctId })}`, void 0, signal),
231
- roadmap: (signal) => call(`${base}/feedback/roadmap?${qs({ key: c.apiKey, distinctId: c.distinctId })}`, void 0, signal),
249
+ list: (o, signal) => call(`${base}/feedback?${qs({ ...me, board: o.board, sort: o.sort, q: o.q })}`, void 0, signal),
250
+ post: (id, signal) => call(`${base}/feedback/${encodeURIComponent(id)}?${qs(me)}`, void 0, signal),
251
+ roadmap: (signal) => call(`${base}/feedback/roadmap?${qs(me)}`, void 0, signal),
232
252
  changelog: (signal) => call(`${base}/feedback/changelog?${qs({ key: c.apiKey })}`, void 0, signal),
233
253
  submit: (p) => post("/feedback", p),
234
254
  vote: (id) => post(`/feedback/${encodeURIComponent(id)}/vote`, {}),
235
255
  comments: (id, signal) => call(`${base}/feedback/${encodeURIComponent(id)}/comments?${qs({ key: c.apiKey })}`, void 0, signal),
236
256
  comment: (id, body) => post(`/feedback/${encodeURIComponent(id)}/comments`, { body }),
237
- survey: (url, signal) => call(`${base}/surveys?${qs({ key: c.apiKey, distinctId: c.distinctId, url })}`, void 0, signal),
257
+ survey: (url, signal) => call(`${base}/surveys?${qs({ ...me, url })}`, void 0, signal),
238
258
  seen: (id) => post(`/surveys/${encodeURIComponent(id)}/seen`, { user: void 0 }),
239
259
  respond: (id, r) => post(`/surveys/${encodeURIComponent(id)}/respond`, r),
240
- chat: (signal) => call(`${base}/messages?${qs({ key: c.apiKey, distinctId: c.distinctId })}`, void 0, signal),
260
+ chat: (signal) => call(`${base}/messages?${qs(me)}`, void 0, signal),
241
261
  /** A message; `attachments` are upload ids from `uploadChatFile` (then the body may be empty). */
242
262
  sendChat: (m) => post("/messages", m),
243
263
  /** Uploads one picked file for the next message (pictures and PDFs, at most 10 MB); answers its upload id. */
@@ -245,14 +265,15 @@ var createApi = (c) => {
245
265
  const form = new FormData();
246
266
  form.append("key", c.apiKey);
247
267
  form.append("distinctId", c.distinctId);
268
+ if (c.userHash) form.append("userHash", c.userHash);
248
269
  if ("uri" in file) form.append("file", file);
249
270
  else form.append("file", file, file.name || "file");
250
271
  return call(`${base}/messages/attachments`, { method: "POST", body: form }, signal);
251
272
  },
252
273
  /** Where a sent file of this visitor's chat is read (an image's src, a PDF's link). */
253
- attachmentUrl: (id) => `${base}/messages/attachments/${encodeURIComponent(id)}?${qs({ key: c.apiKey, distinctId: c.distinctId })}`,
274
+ attachmentUrl: (id) => `${base}/messages/attachments/${encodeURIComponent(id)}?${qs(me)}`,
254
275
  articles: (q, signal) => call(`${base}/articles?${qs({ key: c.apiKey, q })}`, void 0, signal),
255
- article: (id, signal) => call(`${base}/articles/${encodeURIComponent(id)}?${qs({ key: c.apiKey, distinctId: c.distinctId })}`, void 0, signal),
276
+ article: (id, signal) => call(`${base}/articles/${encodeURIComponent(id)}?${qs(me)}`, void 0, signal),
256
277
  messengerLook: (signal) => call(`${base}/messenger?${qs({ key: c.apiKey })}`, void 0, signal),
257
278
  rateArticle: (id, helpful) => post(`/articles/${encodeURIComponent(id)}/rate`, { helpful })
258
279
  };
@@ -274,8 +295,8 @@ var byteLength = (s) => {
274
295
  }
275
296
  return n;
276
297
  };
277
- var batchRequests = (key, events, sdk) => {
278
- const head = `{"key":${JSON.stringify(key)},"sdk":${JSON.stringify(sdk)},"events":[`;
298
+ var batchRequests = (key, events, sdk, userHash) => {
299
+ const head = `{"key":${JSON.stringify(key)},${userHash ? `"userHash":${JSON.stringify(userHash)},` : ""}"sdk":${JSON.stringify(sdk)},"events":[`;
279
300
  const empty = byteLength(head) + 2;
280
301
  const out = [];
281
302
  let chunk = [];
@@ -317,6 +338,7 @@ var describeError = (error) => {
317
338
  var CANCEL_EVENT = "$cancel_flow";
318
339
  var ONBOARDING_EVENT = "$onboarding";
319
340
  var INAPP_EVENT = "$inapp";
341
+ var REVIEW_EVENT = "$review_prompt";
320
342
  var PLAIN_CANCEL = {
321
343
  version: 0,
322
344
  question: "",
@@ -1047,7 +1069,7 @@ var createRecorder = (deps, st) => {
1047
1069
  const batch = frames.splice(0, frames.length);
1048
1070
  const ms = marks.splice(0, marks.length);
1049
1071
  const size = deps.screen();
1050
- const { distinctId, identified } = st.who();
1072
+ const { distinctId, identified, userHash } = st.who();
1051
1073
  const body = JSON.stringify({
1052
1074
  key: st.apiKey,
1053
1075
  consent: { replay: true },
@@ -1059,11 +1081,12 @@ var createRecorder = (deps, st) => {
1059
1081
  meta: { first: batch[0].t, last: batch[batch.length - 1].t, vw: size ? Math.round(size.w) : null, vh: size ? Math.round(size.h) : null, marks: ms },
1060
1082
  distinctId,
1061
1083
  identified,
1084
+ ...userHash ? { userHash } : {},
1062
1085
  trigger: r.trigger,
1063
1086
  app: st.app
1064
1087
  });
1065
1088
  const status = await deps.send(body);
1066
- if (status === 403 || status === 409 || status === 413) r.stopped = true;
1089
+ if (status === 401 || status === 403 || status === 409 || status === 413) r.stopped = true;
1067
1090
  };
1068
1091
  const tick = async () => {
1069
1092
  if (busy || !st.allowed() || !st.config()) return;
@@ -1223,6 +1246,7 @@ var createClient = (options, deps) => {
1223
1246
  let installId = null;
1224
1247
  let memoryId = newDistinctId();
1225
1248
  let identified;
1249
+ let signed = null;
1226
1250
  let person;
1227
1251
  let config = null;
1228
1252
  let queue = [];
@@ -1257,6 +1281,13 @@ var createClient = (options, deps) => {
1257
1281
  const errorQueue = [];
1258
1282
  const errorsSeen = /* @__PURE__ */ new Set();
1259
1283
  let errorsSent = 0;
1284
+ const fetchInApp = async (id) => {
1285
+ if (!apiKey) return null;
1286
+ const hash = hashOf(id);
1287
+ const r = await deps.fetch(`${host}/api/sdk/inapp?key=${encodeURIComponent(apiKey)}&distinctId=${encodeURIComponent(id)}${hash ? `&userHash=${hash}` : ""}`, { method: "GET" });
1288
+ warnUnsigned(r.status, deps.log);
1289
+ return r.ok ? await r.json() : null;
1290
+ };
1260
1291
  const paywall = createPaywallHost({ config: () => config, distinctId: () => distinctId(), track: (event, props) => record(event, props) });
1261
1292
  const flows = createFlows({
1262
1293
  track: (event, props) => record(event, props),
@@ -1267,11 +1298,7 @@ var createClient = (options, deps) => {
1267
1298
  set: (k, v) => void set(k, v)
1268
1299
  } : null,
1269
1300
  openUrl: deps.openUrl,
1270
- fetchInApp: async (id) => {
1271
- if (!apiKey) return null;
1272
- const r = await deps.fetch(`${host}/api/sdk/inapp?key=${encodeURIComponent(apiKey)}&distinctId=${encodeURIComponent(id)}`, { method: "GET" });
1273
- return r.ok ? await r.json() : null;
1274
- }
1301
+ fetchInApp: (id) => fetchInApp(id)
1275
1302
  });
1276
1303
  const refreshFlows = () => {
1277
1304
  if (!isReady || closed) return;
@@ -1334,6 +1361,7 @@ var createClient = (options, deps) => {
1334
1361
  };
1335
1362
  const distinctId = () => identified ?? (consent === "persistent" && installId ? installId : memoryId);
1336
1363
  const sendId = () => consent === "persistent" ? distinctId() : identified;
1364
+ const hashOf = (id) => hashFor(signed, id);
1337
1365
  const touchSession = () => {
1338
1366
  const t = now();
1339
1367
  if (!session || t - session.last >= SESSION_TIMEOUT_MS) session = { id: newDistinctId(), last: t };
@@ -1371,6 +1399,7 @@ var createClient = (options, deps) => {
1371
1399
  const timer = ctl ? setTimeoutFn(() => ctl.abort(), REQUEST_TIMEOUT_MS) : null;
1372
1400
  try {
1373
1401
  const r = await deps.fetch(`${host}${path}`, { method: "POST", headers: { "content-type": "application/json" }, body, signal: ctl?.signal });
1402
+ warnUnsigned(r.status, deps.log);
1374
1403
  return r.status;
1375
1404
  } catch {
1376
1405
  return 0;
@@ -1382,7 +1411,7 @@ var createClient = (options, deps) => {
1382
1411
  if (!queue.length || consent === "off" || !apiKey) return;
1383
1412
  if (now() < backoffUntil) return;
1384
1413
  const snapshot = queue.slice();
1385
- const requests = batchRequests(apiKey, snapshot, sdk);
1414
+ const requests = batchRequests(apiKey, snapshot, sdk, hashOf(sendId()));
1386
1415
  const included = new Set(requests.flatMap((r) => r.events));
1387
1416
  const done = /* @__PURE__ */ new Set();
1388
1417
  for (const r of requests) {
@@ -1393,7 +1422,7 @@ var createClient = (options, deps) => {
1393
1422
  backoffUntil = 0;
1394
1423
  } else if (status >= 400 && status < 500 && status !== 408 && status !== 429) {
1395
1424
  r.events.forEach((e) => done.add(e));
1396
- warn(`Events were rejected with ${status}. Check the public key (MRRocket \u203A Settings \u203A Products) and the host.`);
1425
+ if (status !== 401) warn(`Events were rejected with ${status}. Check the public key (MRRocket \u203A Settings \u203A Products) and the host.`);
1397
1426
  } else {
1398
1427
  failures++;
1399
1428
  backoffUntil = now() + Math.min(MAX_BACKOFF_MS, 1e3 * 2 ** failures);
@@ -1432,8 +1461,9 @@ var createClient = (options, deps) => {
1432
1461
  }
1433
1462
  while (errorQueue.length) {
1434
1463
  const batch = errorQueue.splice(0, 4);
1435
- const status = await post("/api/sdk/errors", JSON.stringify({ key: apiKey, sdk, errors: batch }));
1436
- if (status >= 400 && status < 500 && status !== 429) warn(`Error reports were rejected with ${status}. Check the public key and the host.`);
1464
+ const userHash = hashForAll(signed, batch.map((r) => r.distinctId));
1465
+ const status = await post("/api/sdk/errors", JSON.stringify({ key: apiKey, ...userHash ? { userHash } : {}, sdk, errors: batch }));
1466
+ if (status >= 400 && status < 500 && status !== 429 && status !== 401) warn(`Error reports were rejected with ${status}. Check the public key and the host.`);
1437
1467
  }
1438
1468
  };
1439
1469
  const capture = (error, mechanism, fatal = false) => {
@@ -1491,7 +1521,7 @@ var createClient = (options, deps) => {
1491
1521
  allowed: () => replayOn && loaded && !closed && consent !== "off" && !!apiKey,
1492
1522
  config: () => config?.replay,
1493
1523
  session: () => session?.id ?? touchSession(),
1494
- who: () => ({ distinctId: sendId(), identified: !!identified }),
1524
+ who: () => ({ distinctId: sendId(), identified: !!identified, userHash: hashOf(sendId()) }),
1495
1525
  apiKey,
1496
1526
  app: context
1497
1527
  }
@@ -1541,6 +1571,7 @@ var createClient = (options, deps) => {
1541
1571
  };
1542
1572
  let pushToken = null;
1543
1573
  let pushFor;
1574
+ let pushHash;
1544
1575
  let pushOff = null;
1545
1576
  const opened = /* @__PURE__ */ new Set();
1546
1577
  const onPushOpen = (data) => {
@@ -1568,18 +1599,20 @@ var createClient = (options, deps) => {
1568
1599
  if (!apiKey || consent === "off") return { status: "error", reason: !apiKey ? "No public key." : "The consent is off." };
1569
1600
  if (!identified) return { status: "not-signed-in" };
1570
1601
  const who = identified;
1602
+ const userHash = hashOf(who);
1571
1603
  try {
1572
1604
  const permission = await deps.push.permission(o.ask !== false);
1573
1605
  if (permission !== "granted") return { status: permission === "denied" ? "denied" : "not-asked" };
1574
1606
  const token = await deps.push.token(o.projectId);
1575
1607
  if (!token) return { status: "unavailable", reason: "This device has no push token (a simulator, or no EAS project id)." };
1576
- const status = await post("/api/sdk/push", JSON.stringify({ key: apiKey, distinctId: who, platform: "expo", token, app: context }));
1608
+ const status = await post("/api/sdk/push", JSON.stringify({ key: apiKey, distinctId: who, userHash, platform: "expo", token, app: context }));
1577
1609
  if (status < 200 || status >= 300) {
1578
1610
  warn(`Push registration was rejected with ${status || "no answer"}. Check the public key and the host.`);
1579
1611
  return { status: "error", reason: `MRRocket answered ${status || "nothing"}.` };
1580
1612
  }
1581
1613
  pushToken = token;
1582
1614
  pushFor = who;
1615
+ pushHash = userHash;
1583
1616
  listenForOpens();
1584
1617
  debug("Registered for push.");
1585
1618
  return { status: "registered", token };
@@ -1591,9 +1624,28 @@ var createClient = (options, deps) => {
1591
1624
  const token = pushToken;
1592
1625
  if (!token || !apiKey) return false;
1593
1626
  pushToken = null;
1594
- const status = await post("/api/sdk/push", JSON.stringify({ key: apiKey, distinctId: pushFor, platform: "expo", token, remove: true }));
1627
+ const status = await post("/api/sdk/push", JSON.stringify({ key: apiKey, distinctId: pushFor, userHash: pushHash, platform: "expo", token, remove: true }));
1595
1628
  return status >= 200 && status < 300;
1596
1629
  };
1630
+ let reviewing = false;
1631
+ const requestReview = async () => {
1632
+ if (!deps.storeReview) return "unavailable";
1633
+ await loadedNow();
1634
+ if (!apiKey || consent === "off") return "not-now";
1635
+ if (!identified) return "not-signed-in";
1636
+ if (reviewing) return "not-now";
1637
+ reviewing = true;
1638
+ try {
1639
+ const ask = (await fetchInApp(identified).catch(() => null))?.review;
1640
+ if (!ask) return "not-now";
1641
+ const asked = await deps.storeReview.request().catch(() => false);
1642
+ record(REVIEW_EVENT, { campaign: ask.campaign, store: ask.store, action: asked ? "requested" : "unavailable" });
1643
+ void flush();
1644
+ return asked ? "requested" : "unavailable";
1645
+ } finally {
1646
+ reviewing = false;
1647
+ }
1648
+ };
1597
1649
  let resolveReady = () => {
1598
1650
  };
1599
1651
  const ready = new Promise((r) => {
@@ -1723,13 +1775,14 @@ var createClient = (options, deps) => {
1723
1775
  record("$screen", { ...props, path, screen: name, ...previous ? { previous } : {} });
1724
1776
  recorder?.mark({ kind: "pageview", path });
1725
1777
  },
1726
- identify: (id, props) => {
1778
+ identify: (id, props, options2) => {
1727
1779
  if (typeof id !== "string" || !id) return;
1728
1780
  if (!loaded) {
1729
- early.push(() => client.identify(id, props));
1781
+ early.push(() => client.identify(id, props, options2));
1730
1782
  return;
1731
1783
  }
1732
1784
  const previous = sendId();
1785
+ signed = nextSigned(signed, id, options2?.hash, deps.log);
1733
1786
  identified = id;
1734
1787
  person = { ...props, id };
1735
1788
  record("$identify", { ...props, id, ...previous && previous !== id ? { previousId: previous } : {} });
@@ -1743,7 +1796,9 @@ var createClient = (options, deps) => {
1743
1796
  return;
1744
1797
  }
1745
1798
  if (pushToken) void unregisterForPush();
1799
+ void flush();
1746
1800
  identified = void 0;
1801
+ signed = null;
1747
1802
  person = void 0;
1748
1803
  session = null;
1749
1804
  utm = null;
@@ -1822,6 +1877,7 @@ var createClient = (options, deps) => {
1822
1877
  paywall,
1823
1878
  registerForPush,
1824
1879
  unregisterForPush,
1880
+ requestReview,
1825
1881
  flows,
1826
1882
  flush,
1827
1883
  ready,
@@ -1837,7 +1893,7 @@ var createClient = (options, deps) => {
1837
1893
  if (!apiKey || consent === "off") return null;
1838
1894
  const known = client.getUser();
1839
1895
  const user = as?.name || as?.email ? { ...known, ...as.name ? { name: as.name } : {}, ...as.email ? { email: as.email } : {} } : known;
1840
- return createApi({ host, apiKey, distinctId: distinctId(), user });
1896
+ return createApi({ host, apiKey, distinctId: distinctId(), user, userHash: hashOf(distinctId()) });
1841
1897
  },
1842
1898
  isSurveyDone: (id) => surveysDone.has(id),
1843
1899
  markSurveyDone: (id) => {
@@ -2227,6 +2283,12 @@ try {
2227
2283
  } catch {
2228
2284
  expoLocalization = null;
2229
2285
  }
2286
+ var expoStoreReview = null;
2287
+ try {
2288
+ expoStoreReview = require("expo-store-review");
2289
+ } catch {
2290
+ expoStoreReview = null;
2291
+ }
2230
2292
  var viewShot = null;
2231
2293
  try {
2232
2294
  viewShot = require("react-native-view-shot");
@@ -2330,6 +2392,17 @@ var recorderDeps = () => {
2330
2392
  masks: measureMasks
2331
2393
  };
2332
2394
  };
2395
+ var storeReview = () => {
2396
+ const m = expoStoreReview;
2397
+ if (!m?.requestReview) return null;
2398
+ return {
2399
+ request: async () => {
2400
+ if (m.isAvailableAsync && !await m.isAvailableAsync()) return false;
2401
+ await m.requestReview();
2402
+ return true;
2403
+ }
2404
+ };
2405
+ };
2333
2406
  var nativeDeps = (overrides = {}) => ({
2334
2407
  storage: overrides.storage !== void 0 ? overrides.storage : asyncStorage(),
2335
2408
  fetch: ((url, init) => fetch(url, init)),
@@ -2344,6 +2417,7 @@ var nativeDeps = (overrides = {}) => ({
2344
2417
  initialUrl: () => RN2.Linking.getInitialURL(),
2345
2418
  openUrl: (url) => void RN2.Linking.openURL(url).catch(() => void 0),
2346
2419
  push: expoPush(),
2420
+ storeReview: storeReview(),
2347
2421
  recorder: recorderDeps()
2348
2422
  });
2349
2423
  var optionalModules = () => ({
@@ -2352,6 +2426,7 @@ var optionalModules = () => ({
2352
2426
  expoApplication: !!expoApplication,
2353
2427
  expoLocalization: !!expoLocalization,
2354
2428
  expoNotifications: !!expoNotifications,
2429
+ expoStoreReview: !!expoStoreReview?.requestReview,
2355
2430
  viewShot: !!viewShot?.captureScreen
2356
2431
  });
2357
2432
 
@@ -3570,6 +3645,10 @@ var usePaywall = () => {
3570
3645
  const client = useClient();
3571
3646
  return { open: client.openPaywall };
3572
3647
  };
3648
+ var useReview = () => {
3649
+ const client = useClient();
3650
+ return { request: client.requestReview };
3651
+ };
3573
3652
  var useOnboarding = () => {
3574
3653
  const client = useClient();
3575
3654
  return { start: client.startOnboarding };