@mrrocket/sdk 0.14.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.
- package/CHANGELOG.md +10 -0
- package/README.md +45 -4
- package/dist/api-client.d.ts +67 -5
- package/dist/api-client.js +39 -8
- package/dist/api-client.js.map +1 -1
- package/dist/api.d.ts +4 -1
- package/dist/api.js +2 -1
- package/dist/api.js.map +1 -1
- package/dist/batch.d.ts +6 -4
- package/dist/batch.js +3 -3
- package/dist/batch.js.map +1 -1
- package/dist/browser/mrrocket-flows.js +2 -2
- package/dist/browser/mrrocket-forms.js +1 -1
- package/dist/browser/mrrocket-messenger.js +202 -46
- package/dist/browser/mrrocket-push.js +1 -1
- package/dist/browser/mrrocket-replay.js +3 -3
- package/dist/browser/mrrocket.js +1 -1
- package/dist/client.d.ts +10 -2
- package/dist/client.js +36 -15
- package/dist/client.js.map +1 -1
- package/dist/context.d.ts +5 -1
- package/dist/context.js.map +1 -1
- package/dist/core.d.ts +1 -1
- package/dist/core.js +1 -1
- package/dist/core.js.map +1 -1
- package/dist/errors.d.ts +5 -1
- package/dist/errors.js +8 -2
- package/dist/errors.js.map +1 -1
- package/dist/flows-ui.js +48 -1
- package/dist/flows-ui.js.map +1 -1
- package/dist/flows-web.d.ts +2 -0
- package/dist/flows-web.js +4 -1
- package/dist/flows-web.js.map +1 -1
- package/dist/flows.d.ts +61 -1
- package/dist/flows.js +64 -21
- package/dist/flows.js.map +1 -1
- package/dist/forms.d.ts +4 -0
- package/dist/forms.js +4 -1
- package/dist/forms.js.map +1 -1
- package/dist/identity.d.ts +42 -0
- package/dist/identity.js +24 -0
- package/dist/identity.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js.map +1 -1
- package/dist/messenger-ui.d.ts +21 -6
- package/dist/messenger-ui.js +334 -80
- package/dist/messenger-ui.js.map +1 -1
- package/dist/messenger.d.ts +11 -3
- package/dist/messenger.js +9 -5
- package/dist/messenger.js.map +1 -1
- package/dist/push-web.d.ts +2 -13
- package/dist/push-web.js +4 -2
- package/dist/push-web.js.map +1 -1
- package/dist/react-native/index.d.cts +162 -14
- package/dist/react-native/index.d.ts +162 -14
- package/dist/react-native/index.js +552 -207
- package/dist/react-native/index.js.map +1 -1
- package/dist/replay.d.ts +4 -1
- package/dist/replay.js +8 -2
- package/dist/replay.js.map +1 -1
- package/dist/server.d.ts +2 -0
- package/dist/server.js +2 -0
- package/dist/server.js.map +1 -1
- package/dist/styles-css.d.ts +1 -1
- package/dist/styles-css.js +200 -44
- package/dist/styles-css.js.map +1 -1
- package/dist/user-hash.d.ts +27 -0
- package/dist/user-hash.js +24 -0
- package/dist/user-hash.js.map +1 -0
- package/package.json +9 -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.
|
|
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,8 +173,32 @@ 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
|
-
var
|
|
194
|
+
var CHAT_FILE_MAX_BYTES = 10 * 1024 * 1024;
|
|
195
|
+
var CHAT_FILES_PER_MESSAGE = 4;
|
|
196
|
+
var CORNERS = {
|
|
197
|
+
round: { panel: 22, bubble: 18, tail: 6, card: 16, field: 20 },
|
|
198
|
+
soft: { panel: 14, bubble: 12, tail: 4, card: 10, field: 10 },
|
|
199
|
+
square: { panel: 6, bubble: 6, tail: 2, card: 4, field: 4 }
|
|
200
|
+
};
|
|
201
|
+
var BRAND_URL = "https://mrrocket.dev/?utm_source=messenger&utm_medium=powered-by";
|
|
177
202
|
var inkOn = (hex) => {
|
|
178
203
|
const m = /^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i.exec(hex);
|
|
179
204
|
if (!m) return "#ffffff";
|
|
@@ -197,13 +222,15 @@ var call = async (url, init, signal) => {
|
|
|
197
222
|
res = await fetch(url, {
|
|
198
223
|
...init,
|
|
199
224
|
signal,
|
|
200
|
-
|
|
225
|
+
// A form (a file upload) sets its own multipart type with the boundary.
|
|
226
|
+
headers: typeof init?.body === "string" ? { "content-type": "application/json" } : void 0
|
|
201
227
|
});
|
|
202
228
|
} catch (e) {
|
|
203
229
|
if (e.name === "AbortError") throw e;
|
|
204
230
|
throw new ApiError("Could not reach the server. Check your connection and try again.", 0);
|
|
205
231
|
}
|
|
206
232
|
if (!res.ok) {
|
|
233
|
+
warnUnsigned(res.status);
|
|
207
234
|
let detail = "";
|
|
208
235
|
try {
|
|
209
236
|
detail = (await res.json()).error ?? "";
|
|
@@ -215,24 +242,38 @@ var call = async (url, init, signal) => {
|
|
|
215
242
|
};
|
|
216
243
|
var createApi = (c) => {
|
|
217
244
|
const base = `${c.host}/api/sdk`;
|
|
218
|
-
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 };
|
|
219
247
|
const post = (path, body, signal) => call(`${base}${path}`, { method: "POST", body: JSON.stringify({ ...who, ...body }) }, signal);
|
|
220
248
|
return {
|
|
221
|
-
list: (o, signal) => call(`${base}/feedback?${qs({
|
|
222
|
-
post: (id, signal) => call(`${base}/feedback/${encodeURIComponent(id)}?${qs(
|
|
223
|
-
roadmap: (signal) => call(`${base}/feedback/roadmap?${qs(
|
|
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),
|
|
224
252
|
changelog: (signal) => call(`${base}/feedback/changelog?${qs({ key: c.apiKey })}`, void 0, signal),
|
|
225
253
|
submit: (p) => post("/feedback", p),
|
|
226
254
|
vote: (id) => post(`/feedback/${encodeURIComponent(id)}/vote`, {}),
|
|
227
255
|
comments: (id, signal) => call(`${base}/feedback/${encodeURIComponent(id)}/comments?${qs({ key: c.apiKey })}`, void 0, signal),
|
|
228
256
|
comment: (id, body) => post(`/feedback/${encodeURIComponent(id)}/comments`, { body }),
|
|
229
|
-
survey: (url, signal) => call(`${base}/surveys?${qs({
|
|
257
|
+
survey: (url, signal) => call(`${base}/surveys?${qs({ ...me, url })}`, void 0, signal),
|
|
230
258
|
seen: (id) => post(`/surveys/${encodeURIComponent(id)}/seen`, { user: void 0 }),
|
|
231
259
|
respond: (id, r) => post(`/surveys/${encodeURIComponent(id)}/respond`, r),
|
|
232
|
-
chat: (signal) => call(`${base}/messages?${qs(
|
|
260
|
+
chat: (signal) => call(`${base}/messages?${qs(me)}`, void 0, signal),
|
|
261
|
+
/** A message; `attachments` are upload ids from `uploadChatFile` (then the body may be empty). */
|
|
233
262
|
sendChat: (m) => post("/messages", m),
|
|
263
|
+
/** Uploads one picked file for the next message (pictures and PDFs, at most 10 MB); answers its upload id. */
|
|
264
|
+
uploadChatFile: (file, signal) => {
|
|
265
|
+
const form = new FormData();
|
|
266
|
+
form.append("key", c.apiKey);
|
|
267
|
+
form.append("distinctId", c.distinctId);
|
|
268
|
+
if (c.userHash) form.append("userHash", c.userHash);
|
|
269
|
+
if ("uri" in file) form.append("file", file);
|
|
270
|
+
else form.append("file", file, file.name || "file");
|
|
271
|
+
return call(`${base}/messages/attachments`, { method: "POST", body: form }, signal);
|
|
272
|
+
},
|
|
273
|
+
/** Where a sent file of this visitor's chat is read (an image's src, a PDF's link). */
|
|
274
|
+
attachmentUrl: (id) => `${base}/messages/attachments/${encodeURIComponent(id)}?${qs(me)}`,
|
|
234
275
|
articles: (q, signal) => call(`${base}/articles?${qs({ key: c.apiKey, q })}`, void 0, signal),
|
|
235
|
-
article: (id, signal) => call(`${base}/articles/${encodeURIComponent(id)}?${qs(
|
|
276
|
+
article: (id, signal) => call(`${base}/articles/${encodeURIComponent(id)}?${qs(me)}`, void 0, signal),
|
|
236
277
|
messengerLook: (signal) => call(`${base}/messenger?${qs({ key: c.apiKey })}`, void 0, signal),
|
|
237
278
|
rateArticle: (id, helpful) => post(`/articles/${encodeURIComponent(id)}/rate`, { helpful })
|
|
238
279
|
};
|
|
@@ -254,8 +295,8 @@ var byteLength = (s) => {
|
|
|
254
295
|
}
|
|
255
296
|
return n;
|
|
256
297
|
};
|
|
257
|
-
var batchRequests = (key, events, sdk) => {
|
|
258
|
-
const head = `{"key":${JSON.stringify(key)}
|
|
298
|
+
var batchRequests = (key, events, sdk, userHash) => {
|
|
299
|
+
const head = `{"key":${JSON.stringify(key)},${userHash ? `"userHash":${JSON.stringify(userHash)},` : ""}"sdk":${JSON.stringify(sdk)},"events":[`;
|
|
259
300
|
const empty = byteLength(head) + 2;
|
|
260
301
|
const out = [];
|
|
261
302
|
let chunk = [];
|
|
@@ -297,6 +338,7 @@ var describeError = (error) => {
|
|
|
297
338
|
var CANCEL_EVENT = "$cancel_flow";
|
|
298
339
|
var ONBOARDING_EVENT = "$onboarding";
|
|
299
340
|
var INAPP_EVENT = "$inapp";
|
|
341
|
+
var REVIEW_EVENT = "$review_prompt";
|
|
300
342
|
var PLAIN_CANCEL = {
|
|
301
343
|
version: 0,
|
|
302
344
|
question: "",
|
|
@@ -343,6 +385,9 @@ var pageMatches = (pattern, path) => {
|
|
|
343
385
|
return at === (p.replace(/\/+$/, "") || "/");
|
|
344
386
|
};
|
|
345
387
|
var stepsIn = (nodes) => nodes.flatMap((n) => n.kind === "condition" ? [...stepsIn(n.yes), ...stepsIn(n.no)] : n.kind === "wait" ? [] : [n]);
|
|
388
|
+
var monthsSince = (since, now = Date.now()) => since == null || since === "" ? NaN : Math.floor((now - new Date(since).getTime()) / 2630016e3);
|
|
389
|
+
var low = (x) => x?.trim().toLowerCase();
|
|
390
|
+
var ruleMatches = ({ when: w = {} }, c) => (w.answer == null || low(w.answer) === low(c.answer)) && (w.plan == null || [w.plan].flat().some((x) => low(x) === low(c.plan))) && !(c.months >= w.tenureUnder) && !(c.months < w.tenureOver) && (w.tenureUnder == null || c.months >= 0) && (w.tenureOver == null || c.months >= 0);
|
|
346
391
|
var createFlows = (env) => {
|
|
347
392
|
let config = null;
|
|
348
393
|
let cancel = null;
|
|
@@ -510,18 +555,23 @@ var createFlows = (env) => {
|
|
|
510
555
|
};
|
|
511
556
|
const view = () => {
|
|
512
557
|
if (cancel) {
|
|
513
|
-
const
|
|
558
|
+
const c = cancel;
|
|
559
|
+
const followUp = c.flow.followUps?.[c.reason];
|
|
560
|
+
const f = followUp ? 1 : 0;
|
|
561
|
+
const steps = c.plain ? 1 : 3 + f + (c.flow.second?.[c.reason] ? 1 : 0);
|
|
514
562
|
return {
|
|
515
563
|
type: "cancel",
|
|
516
|
-
screen:
|
|
517
|
-
flow:
|
|
518
|
-
reason:
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
564
|
+
screen: c.screen,
|
|
565
|
+
flow: c.flow,
|
|
566
|
+
reason: c.reason,
|
|
567
|
+
...c.screen === "followup" && followUp ? { followUp, answer: c.answer } : {},
|
|
568
|
+
offer: c.screen === "offer" ? c.offer : void 0,
|
|
569
|
+
...c.screen === "offer" ? { round: c.round } : {},
|
|
570
|
+
price: c.options.price,
|
|
571
|
+
busy: c.busy,
|
|
572
|
+
error: c.error,
|
|
573
|
+
step: c.screen === "survey" ? 1 : c.screen === "followup" ? 2 : c.screen === "offer" ? 1 + f + c.round : steps,
|
|
574
|
+
steps
|
|
525
575
|
};
|
|
526
576
|
}
|
|
527
577
|
const ob = onboardingView();
|
|
@@ -600,12 +650,22 @@ var createFlows = (env) => {
|
|
|
600
650
|
});
|
|
601
651
|
return loading;
|
|
602
652
|
};
|
|
653
|
+
const offerAt = (c) => c.round > 1 ? { round: 2 } : c.rule >= 0 ? { rule: c.rule } : {};
|
|
654
|
+
const toOffer = (c) => {
|
|
655
|
+
const who = { answer: c.answer, plan: c.options.plan, months: monthsSince(c.options.customerSince, env.now?.()) };
|
|
656
|
+
const rules = c.flow.rules?.[c.reason] ?? [];
|
|
657
|
+
c.rule = rules.findIndex((r) => ruleMatches(r, who));
|
|
658
|
+
c.offer = c.rule < 0 ? c.flow.offers[c.reason] : rules[c.rule].offer ?? void 0;
|
|
659
|
+
c.round = 1;
|
|
660
|
+
c.screen = c.offer ? "offer" : "confirm";
|
|
661
|
+
};
|
|
603
662
|
const result = (c, outcome, comment) => {
|
|
604
|
-
const offer = c.
|
|
663
|
+
const offer = c.screen === "offer" ? c.offer : void 0;
|
|
605
664
|
return {
|
|
606
665
|
outcome,
|
|
607
666
|
run: c.run,
|
|
608
667
|
...c.reason ? { reason: c.reason, reasonLabel: c.flow.reasons.find((r) => r.id === c.reason)?.label } : {},
|
|
668
|
+
...c.answer ? { answer: c.answer } : {},
|
|
609
669
|
...outcome === "saved" && offer ? { offer } : {},
|
|
610
670
|
...comment?.trim() ? { comment: comment.trim().slice(0, 500) } : {}
|
|
611
671
|
};
|
|
@@ -618,7 +678,8 @@ var createFlows = (env) => {
|
|
|
618
678
|
outcome: r.outcome,
|
|
619
679
|
customer: c.options.customer ?? "consumer",
|
|
620
680
|
...r.reason ? { reason: r.reason } : {},
|
|
621
|
-
...r.
|
|
681
|
+
...r.answer ? { answer: r.answer } : {},
|
|
682
|
+
...r.offer ? { offer: r.offer.kind, ...offerAt(c) } : {},
|
|
622
683
|
...r.comment ? { comment: r.comment } : {}
|
|
623
684
|
});
|
|
624
685
|
if (cancel === c) cancel = null;
|
|
@@ -648,7 +709,7 @@ var createFlows = (env) => {
|
|
|
648
709
|
let resolve = () => {
|
|
649
710
|
};
|
|
650
711
|
const promise = new Promise((r) => resolve = r);
|
|
651
|
-
const c = { flow, options, plain, screen: plain ? "confirm" : "survey", run: runId(), busy: false, resolve, promise };
|
|
712
|
+
const c = { flow, options, plain, screen: plain ? "confirm" : "survey", round: 1, run: runId(), busy: false, resolve, promise };
|
|
652
713
|
cancel = c;
|
|
653
714
|
env.track(CANCEL_EVENT, { action: "shown", run: c.run, version: flow.version, customer: options.customer ?? "consumer", plain });
|
|
654
715
|
emit();
|
|
@@ -700,29 +761,43 @@ var createFlows = (env) => {
|
|
|
700
761
|
},
|
|
701
762
|
cancel: {
|
|
702
763
|
pick: (reason) => {
|
|
703
|
-
if (!cancel || cancel.busy || !cancel.flow.reasons.some((r) => r.id === reason)) return;
|
|
764
|
+
if (!cancel || cancel.busy || cancel.screen !== "survey" || !cancel.flow.reasons.some((r) => r.id === reason)) return;
|
|
765
|
+
if (cancel.reason !== reason) cancel.answer = void 0;
|
|
704
766
|
cancel.reason = reason;
|
|
705
767
|
cancel.error = void 0;
|
|
706
768
|
emit();
|
|
707
769
|
},
|
|
770
|
+
answer: (text2) => {
|
|
771
|
+
const c = cancel;
|
|
772
|
+
if (!c || c.busy || c.screen !== "followup") return;
|
|
773
|
+
const t = text2.slice(0, 500);
|
|
774
|
+
c.answer = t.trim() ? t : void 0;
|
|
775
|
+
emit();
|
|
776
|
+
},
|
|
708
777
|
next: () => {
|
|
709
778
|
const c = cancel;
|
|
710
|
-
if (!c ||
|
|
711
|
-
|
|
712
|
-
|
|
779
|
+
if (!c || !c.reason) return;
|
|
780
|
+
if (c.screen === "survey") {
|
|
781
|
+
env.track(CANCEL_EVENT, { action: "answered", run: c.run, version: c.flow.version, reason: c.reason });
|
|
782
|
+
if (c.flow.followUps?.[c.reason]) c.screen = "followup";
|
|
783
|
+
else toOffer(c);
|
|
784
|
+
} else if (c.screen === "followup") {
|
|
785
|
+
c.answer = c.answer?.trim();
|
|
786
|
+
toOffer(c);
|
|
787
|
+
} else return;
|
|
713
788
|
emit();
|
|
714
789
|
},
|
|
715
790
|
accept: (comment) => {
|
|
716
791
|
const c = cancel;
|
|
717
792
|
if (!c || c.screen !== "offer" || !c.reason) return;
|
|
718
|
-
const offer = c.flow.offers[c.reason];
|
|
719
793
|
if (!c.busy)
|
|
720
794
|
env.track(CANCEL_EVENT, {
|
|
721
795
|
action: "offer",
|
|
722
796
|
run: c.run,
|
|
723
797
|
version: c.flow.version,
|
|
724
798
|
reason: c.reason,
|
|
725
|
-
offer: offer?.kind,
|
|
799
|
+
offer: c.offer?.kind,
|
|
800
|
+
...offerAt(c),
|
|
726
801
|
outcome: "accepted"
|
|
727
802
|
});
|
|
728
803
|
settle(c, "saved", comment);
|
|
@@ -735,10 +810,16 @@ var createFlows = (env) => {
|
|
|
735
810
|
run: c.run,
|
|
736
811
|
version: c.flow.version,
|
|
737
812
|
reason: c.reason,
|
|
738
|
-
offer: c.
|
|
813
|
+
offer: c.offer?.kind,
|
|
814
|
+
...offerAt(c),
|
|
739
815
|
outcome: "declined"
|
|
740
816
|
});
|
|
741
|
-
c.
|
|
817
|
+
const second = c.round === 1 && c.reason ? c.flow.second?.[c.reason] : void 0;
|
|
818
|
+
if (second) {
|
|
819
|
+
c.offer = second;
|
|
820
|
+
c.rule = void 0;
|
|
821
|
+
c.round = 2;
|
|
822
|
+
} else c.screen = "confirm";
|
|
742
823
|
c.error = void 0;
|
|
743
824
|
emit();
|
|
744
825
|
},
|
|
@@ -988,7 +1069,7 @@ var createRecorder = (deps, st) => {
|
|
|
988
1069
|
const batch = frames.splice(0, frames.length);
|
|
989
1070
|
const ms = marks.splice(0, marks.length);
|
|
990
1071
|
const size = deps.screen();
|
|
991
|
-
const { distinctId, identified } = st.who();
|
|
1072
|
+
const { distinctId, identified, userHash } = st.who();
|
|
992
1073
|
const body = JSON.stringify({
|
|
993
1074
|
key: st.apiKey,
|
|
994
1075
|
consent: { replay: true },
|
|
@@ -1000,11 +1081,12 @@ var createRecorder = (deps, st) => {
|
|
|
1000
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 },
|
|
1001
1082
|
distinctId,
|
|
1002
1083
|
identified,
|
|
1084
|
+
...userHash ? { userHash } : {},
|
|
1003
1085
|
trigger: r.trigger,
|
|
1004
1086
|
app: st.app
|
|
1005
1087
|
});
|
|
1006
1088
|
const status = await deps.send(body);
|
|
1007
|
-
if (status === 403 || status === 409 || status === 413) r.stopped = true;
|
|
1089
|
+
if (status === 401 || status === 403 || status === 409 || status === 413) r.stopped = true;
|
|
1008
1090
|
};
|
|
1009
1091
|
const tick = async () => {
|
|
1010
1092
|
if (busy || !st.allowed() || !st.config()) return;
|
|
@@ -1164,6 +1246,7 @@ var createClient = (options, deps) => {
|
|
|
1164
1246
|
let installId = null;
|
|
1165
1247
|
let memoryId = newDistinctId();
|
|
1166
1248
|
let identified;
|
|
1249
|
+
let signed = null;
|
|
1167
1250
|
let person;
|
|
1168
1251
|
let config = null;
|
|
1169
1252
|
let queue = [];
|
|
@@ -1198,6 +1281,13 @@ var createClient = (options, deps) => {
|
|
|
1198
1281
|
const errorQueue = [];
|
|
1199
1282
|
const errorsSeen = /* @__PURE__ */ new Set();
|
|
1200
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
|
+
};
|
|
1201
1291
|
const paywall = createPaywallHost({ config: () => config, distinctId: () => distinctId(), track: (event, props) => record(event, props) });
|
|
1202
1292
|
const flows = createFlows({
|
|
1203
1293
|
track: (event, props) => record(event, props),
|
|
@@ -1208,11 +1298,7 @@ var createClient = (options, deps) => {
|
|
|
1208
1298
|
set: (k, v) => void set(k, v)
|
|
1209
1299
|
} : null,
|
|
1210
1300
|
openUrl: deps.openUrl,
|
|
1211
|
-
fetchInApp:
|
|
1212
|
-
if (!apiKey) return null;
|
|
1213
|
-
const r = await deps.fetch(`${host}/api/sdk/inapp?key=${encodeURIComponent(apiKey)}&distinctId=${encodeURIComponent(id)}`, { method: "GET" });
|
|
1214
|
-
return r.ok ? await r.json() : null;
|
|
1215
|
-
}
|
|
1301
|
+
fetchInApp: (id) => fetchInApp(id)
|
|
1216
1302
|
});
|
|
1217
1303
|
const refreshFlows = () => {
|
|
1218
1304
|
if (!isReady || closed) return;
|
|
@@ -1275,6 +1361,7 @@ var createClient = (options, deps) => {
|
|
|
1275
1361
|
};
|
|
1276
1362
|
const distinctId = () => identified ?? (consent === "persistent" && installId ? installId : memoryId);
|
|
1277
1363
|
const sendId = () => consent === "persistent" ? distinctId() : identified;
|
|
1364
|
+
const hashOf = (id) => hashFor(signed, id);
|
|
1278
1365
|
const touchSession = () => {
|
|
1279
1366
|
const t = now();
|
|
1280
1367
|
if (!session || t - session.last >= SESSION_TIMEOUT_MS) session = { id: newDistinctId(), last: t };
|
|
@@ -1312,6 +1399,7 @@ var createClient = (options, deps) => {
|
|
|
1312
1399
|
const timer = ctl ? setTimeoutFn(() => ctl.abort(), REQUEST_TIMEOUT_MS) : null;
|
|
1313
1400
|
try {
|
|
1314
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);
|
|
1315
1403
|
return r.status;
|
|
1316
1404
|
} catch {
|
|
1317
1405
|
return 0;
|
|
@@ -1323,7 +1411,7 @@ var createClient = (options, deps) => {
|
|
|
1323
1411
|
if (!queue.length || consent === "off" || !apiKey) return;
|
|
1324
1412
|
if (now() < backoffUntil) return;
|
|
1325
1413
|
const snapshot = queue.slice();
|
|
1326
|
-
const requests = batchRequests(apiKey, snapshot, sdk);
|
|
1414
|
+
const requests = batchRequests(apiKey, snapshot, sdk, hashOf(sendId()));
|
|
1327
1415
|
const included = new Set(requests.flatMap((r) => r.events));
|
|
1328
1416
|
const done = /* @__PURE__ */ new Set();
|
|
1329
1417
|
for (const r of requests) {
|
|
@@ -1334,7 +1422,7 @@ var createClient = (options, deps) => {
|
|
|
1334
1422
|
backoffUntil = 0;
|
|
1335
1423
|
} else if (status >= 400 && status < 500 && status !== 408 && status !== 429) {
|
|
1336
1424
|
r.events.forEach((e) => done.add(e));
|
|
1337
|
-
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.`);
|
|
1338
1426
|
} else {
|
|
1339
1427
|
failures++;
|
|
1340
1428
|
backoffUntil = now() + Math.min(MAX_BACKOFF_MS, 1e3 * 2 ** failures);
|
|
@@ -1373,8 +1461,9 @@ var createClient = (options, deps) => {
|
|
|
1373
1461
|
}
|
|
1374
1462
|
while (errorQueue.length) {
|
|
1375
1463
|
const batch = errorQueue.splice(0, 4);
|
|
1376
|
-
const
|
|
1377
|
-
|
|
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.`);
|
|
1378
1467
|
}
|
|
1379
1468
|
};
|
|
1380
1469
|
const capture = (error, mechanism, fatal = false) => {
|
|
@@ -1432,7 +1521,7 @@ var createClient = (options, deps) => {
|
|
|
1432
1521
|
allowed: () => replayOn && loaded && !closed && consent !== "off" && !!apiKey,
|
|
1433
1522
|
config: () => config?.replay,
|
|
1434
1523
|
session: () => session?.id ?? touchSession(),
|
|
1435
|
-
who: () => ({ distinctId: sendId(), identified: !!identified }),
|
|
1524
|
+
who: () => ({ distinctId: sendId(), identified: !!identified, userHash: hashOf(sendId()) }),
|
|
1436
1525
|
apiKey,
|
|
1437
1526
|
app: context
|
|
1438
1527
|
}
|
|
@@ -1482,6 +1571,7 @@ var createClient = (options, deps) => {
|
|
|
1482
1571
|
};
|
|
1483
1572
|
let pushToken = null;
|
|
1484
1573
|
let pushFor;
|
|
1574
|
+
let pushHash;
|
|
1485
1575
|
let pushOff = null;
|
|
1486
1576
|
const opened = /* @__PURE__ */ new Set();
|
|
1487
1577
|
const onPushOpen = (data) => {
|
|
@@ -1509,18 +1599,20 @@ var createClient = (options, deps) => {
|
|
|
1509
1599
|
if (!apiKey || consent === "off") return { status: "error", reason: !apiKey ? "No public key." : "The consent is off." };
|
|
1510
1600
|
if (!identified) return { status: "not-signed-in" };
|
|
1511
1601
|
const who = identified;
|
|
1602
|
+
const userHash = hashOf(who);
|
|
1512
1603
|
try {
|
|
1513
1604
|
const permission = await deps.push.permission(o.ask !== false);
|
|
1514
1605
|
if (permission !== "granted") return { status: permission === "denied" ? "denied" : "not-asked" };
|
|
1515
1606
|
const token = await deps.push.token(o.projectId);
|
|
1516
1607
|
if (!token) return { status: "unavailable", reason: "This device has no push token (a simulator, or no EAS project id)." };
|
|
1517
|
-
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 }));
|
|
1518
1609
|
if (status < 200 || status >= 300) {
|
|
1519
1610
|
warn(`Push registration was rejected with ${status || "no answer"}. Check the public key and the host.`);
|
|
1520
1611
|
return { status: "error", reason: `MRRocket answered ${status || "nothing"}.` };
|
|
1521
1612
|
}
|
|
1522
1613
|
pushToken = token;
|
|
1523
1614
|
pushFor = who;
|
|
1615
|
+
pushHash = userHash;
|
|
1524
1616
|
listenForOpens();
|
|
1525
1617
|
debug("Registered for push.");
|
|
1526
1618
|
return { status: "registered", token };
|
|
@@ -1532,9 +1624,28 @@ var createClient = (options, deps) => {
|
|
|
1532
1624
|
const token = pushToken;
|
|
1533
1625
|
if (!token || !apiKey) return false;
|
|
1534
1626
|
pushToken = null;
|
|
1535
|
-
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 }));
|
|
1536
1628
|
return status >= 200 && status < 300;
|
|
1537
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
|
+
};
|
|
1538
1649
|
let resolveReady = () => {
|
|
1539
1650
|
};
|
|
1540
1651
|
const ready = new Promise((r) => {
|
|
@@ -1664,13 +1775,14 @@ var createClient = (options, deps) => {
|
|
|
1664
1775
|
record("$screen", { ...props, path, screen: name, ...previous ? { previous } : {} });
|
|
1665
1776
|
recorder?.mark({ kind: "pageview", path });
|
|
1666
1777
|
},
|
|
1667
|
-
identify: (id, props) => {
|
|
1778
|
+
identify: (id, props, options2) => {
|
|
1668
1779
|
if (typeof id !== "string" || !id) return;
|
|
1669
1780
|
if (!loaded) {
|
|
1670
|
-
early.push(() => client.identify(id, props));
|
|
1781
|
+
early.push(() => client.identify(id, props, options2));
|
|
1671
1782
|
return;
|
|
1672
1783
|
}
|
|
1673
1784
|
const previous = sendId();
|
|
1785
|
+
signed = nextSigned(signed, id, options2?.hash, deps.log);
|
|
1674
1786
|
identified = id;
|
|
1675
1787
|
person = { ...props, id };
|
|
1676
1788
|
record("$identify", { ...props, id, ...previous && previous !== id ? { previousId: previous } : {} });
|
|
@@ -1684,7 +1796,9 @@ var createClient = (options, deps) => {
|
|
|
1684
1796
|
return;
|
|
1685
1797
|
}
|
|
1686
1798
|
if (pushToken) void unregisterForPush();
|
|
1799
|
+
void flush();
|
|
1687
1800
|
identified = void 0;
|
|
1801
|
+
signed = null;
|
|
1688
1802
|
person = void 0;
|
|
1689
1803
|
session = null;
|
|
1690
1804
|
utm = null;
|
|
@@ -1763,6 +1877,7 @@ var createClient = (options, deps) => {
|
|
|
1763
1877
|
paywall,
|
|
1764
1878
|
registerForPush,
|
|
1765
1879
|
unregisterForPush,
|
|
1880
|
+
requestReview,
|
|
1766
1881
|
flows,
|
|
1767
1882
|
flush,
|
|
1768
1883
|
ready,
|
|
@@ -1778,7 +1893,7 @@ var createClient = (options, deps) => {
|
|
|
1778
1893
|
if (!apiKey || consent === "off") return null;
|
|
1779
1894
|
const known = client.getUser();
|
|
1780
1895
|
const user = as?.name || as?.email ? { ...known, ...as.name ? { name: as.name } : {}, ...as.email ? { email: as.email } : {} } : known;
|
|
1781
|
-
return createApi({ host, apiKey, distinctId: distinctId(), user });
|
|
1896
|
+
return createApi({ host, apiKey, distinctId: distinctId(), user, userHash: hashOf(distinctId()) });
|
|
1782
1897
|
},
|
|
1783
1898
|
isSurveyDone: (id) => surveysDone.has(id),
|
|
1784
1899
|
markSurveyDone: (id) => {
|
|
@@ -1899,9 +2014,9 @@ var MRRocketFlows = ({ flows, paywall, appearance = {} }) => {
|
|
|
1899
2014
|
card(children, { borderBottomLeftRadius: 0, borderBottomRightRadius: 0, paddingBottom: 34 })
|
|
1900
2015
|
)
|
|
1901
2016
|
);
|
|
1902
|
-
const input = (placeholder) => (0, import_react.createElement)(RN.TextInput, {
|
|
1903
|
-
value
|
|
1904
|
-
onChangeText:
|
|
2017
|
+
const input = (placeholder, value = comment, onChange = setComment) => (0, import_react.createElement)(RN.TextInput, {
|
|
2018
|
+
value,
|
|
2019
|
+
onChangeText: onChange,
|
|
1905
2020
|
placeholder,
|
|
1906
2021
|
placeholderTextColor: p.muted,
|
|
1907
2022
|
multiline: true,
|
|
@@ -1986,38 +2101,49 @@ var MRRocketFlows = ({ flows, paywall, appearance = {} }) => {
|
|
|
1986
2101
|
);
|
|
1987
2102
|
const error = v.error ? text2(v.error, { color: p.danger, fontSize: 13 }) : null;
|
|
1988
2103
|
let body = [];
|
|
2104
|
+
const choices = (items) => (0, import_react.createElement)(
|
|
2105
|
+
RN.ScrollView,
|
|
2106
|
+
{ style: { maxHeight: 320 }, contentContainerStyle: { gap: 6 }, accessibilityRole: "radiogroup" },
|
|
2107
|
+
...items.map(
|
|
2108
|
+
(c) => (0, import_react.createElement)(
|
|
2109
|
+
RN.Pressable,
|
|
2110
|
+
{
|
|
2111
|
+
key: c.key,
|
|
2112
|
+
onPress: c.press,
|
|
2113
|
+
accessibilityRole: "radio",
|
|
2114
|
+
accessibilityState: { checked: c.on },
|
|
2115
|
+
style: {
|
|
2116
|
+
minHeight: 44,
|
|
2117
|
+
paddingHorizontal: 12,
|
|
2118
|
+
borderRadius: 10,
|
|
2119
|
+
borderWidth: 1,
|
|
2120
|
+
borderColor: c.on ? p.accent : p.line,
|
|
2121
|
+
backgroundColor: c.on ? p.soft : p.bg,
|
|
2122
|
+
justifyContent: "center"
|
|
2123
|
+
}
|
|
2124
|
+
},
|
|
2125
|
+
text2(c.label, { fontWeight: "500" })
|
|
2126
|
+
)
|
|
2127
|
+
)
|
|
2128
|
+
);
|
|
1989
2129
|
if (v.screen === "survey")
|
|
1990
2130
|
body = [
|
|
1991
2131
|
title(v.flow.question || "Why are you leaving?"),
|
|
1992
|
-
(
|
|
1993
|
-
RN.ScrollView,
|
|
1994
|
-
{ style: { maxHeight: 320 }, contentContainerStyle: { gap: 6 }, accessibilityRole: "radiogroup" },
|
|
1995
|
-
...v.flow.reasons.map(
|
|
1996
|
-
(r) => (0, import_react.createElement)(
|
|
1997
|
-
RN.Pressable,
|
|
1998
|
-
{
|
|
1999
|
-
key: r.id,
|
|
2000
|
-
onPress: () => flows.cancel.pick(r.id),
|
|
2001
|
-
accessibilityRole: "radio",
|
|
2002
|
-
accessibilityState: { checked: v.reason === r.id },
|
|
2003
|
-
style: {
|
|
2004
|
-
minHeight: 44,
|
|
2005
|
-
paddingHorizontal: 12,
|
|
2006
|
-
borderRadius: 10,
|
|
2007
|
-
borderWidth: 1,
|
|
2008
|
-
borderColor: v.reason === r.id ? p.accent : p.line,
|
|
2009
|
-
backgroundColor: v.reason === r.id ? p.soft : p.bg,
|
|
2010
|
-
justifyContent: "center"
|
|
2011
|
-
}
|
|
2012
|
-
},
|
|
2013
|
-
text2(r.label, { fontWeight: "500" })
|
|
2014
|
-
)
|
|
2015
|
-
)
|
|
2016
|
-
),
|
|
2132
|
+
choices(v.flow.reasons.map((r) => ({ key: r.id, label: r.label, on: v.reason === r.id, press: () => flows.cancel.pick(r.id) }))),
|
|
2017
2133
|
btn("Continue", "primary", flows.cancel.next, !v.reason),
|
|
2018
2134
|
btn("Never mind, keep my plan", "quiet", flows.cancel.close)
|
|
2019
2135
|
];
|
|
2020
|
-
else if (v.screen === "
|
|
2136
|
+
else if (v.screen === "followup" && v.followUp) {
|
|
2137
|
+
const f = v.followUp;
|
|
2138
|
+
const label = v.flow.reasons.find((r) => r.id === v.reason)?.label;
|
|
2139
|
+
body = [
|
|
2140
|
+
label ? muted(`You said: ${label}`) : null,
|
|
2141
|
+
title(f.question),
|
|
2142
|
+
f.answers.length ? choices(f.answers.map((a, i) => ({ key: String(i), label: a, on: v.answer === a, press: () => flows.cancel.answer(a) }))) : input("Your answer (optional)", v.answer ?? "", flows.cancel.answer),
|
|
2143
|
+
btn("Continue", "primary", flows.cancel.next),
|
|
2144
|
+
btn("Never mind, keep my plan", "quiet", flows.cancel.close)
|
|
2145
|
+
];
|
|
2146
|
+
} else if (v.screen === "offer" && v.offer) {
|
|
2021
2147
|
const o = v.offer;
|
|
2022
2148
|
const label = v.flow.reasons.find((r) => r.id === v.reason)?.label;
|
|
2023
2149
|
const detail = o.kind === "discount" && o.percent ? v.price ? `${money(discounted(v.price.amount, o.percent), v.price.currency)} instead of ${money(v.price.amount, v.price.currency)}${o.months ? ` for ${months(o.months)}` : ""}` : `${o.percent}% off${o.months ? ` for ${months(o.months)}` : ""}` : o.kind === "pause" && o.months ? `Pause for up to ${months(o.months)}. Your data stays.` : o.kind === "downgrade" && o.planName ? o.planName : null;
|
|
@@ -2157,6 +2283,12 @@ try {
|
|
|
2157
2283
|
} catch {
|
|
2158
2284
|
expoLocalization = null;
|
|
2159
2285
|
}
|
|
2286
|
+
var expoStoreReview = null;
|
|
2287
|
+
try {
|
|
2288
|
+
expoStoreReview = require("expo-store-review");
|
|
2289
|
+
} catch {
|
|
2290
|
+
expoStoreReview = null;
|
|
2291
|
+
}
|
|
2160
2292
|
var viewShot = null;
|
|
2161
2293
|
try {
|
|
2162
2294
|
viewShot = require("react-native-view-shot");
|
|
@@ -2260,6 +2392,17 @@ var recorderDeps = () => {
|
|
|
2260
2392
|
masks: measureMasks
|
|
2261
2393
|
};
|
|
2262
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
|
+
};
|
|
2263
2406
|
var nativeDeps = (overrides = {}) => ({
|
|
2264
2407
|
storage: overrides.storage !== void 0 ? overrides.storage : asyncStorage(),
|
|
2265
2408
|
fetch: ((url, init) => fetch(url, init)),
|
|
@@ -2274,6 +2417,7 @@ var nativeDeps = (overrides = {}) => ({
|
|
|
2274
2417
|
initialUrl: () => RN2.Linking.getInitialURL(),
|
|
2275
2418
|
openUrl: (url) => void RN2.Linking.openURL(url).catch(() => void 0),
|
|
2276
2419
|
push: expoPush(),
|
|
2420
|
+
storeReview: storeReview(),
|
|
2277
2421
|
recorder: recorderDeps()
|
|
2278
2422
|
});
|
|
2279
2423
|
var optionalModules = () => ({
|
|
@@ -2282,6 +2426,7 @@ var optionalModules = () => ({
|
|
|
2282
2426
|
expoApplication: !!expoApplication,
|
|
2283
2427
|
expoLocalization: !!expoLocalization,
|
|
2284
2428
|
expoNotifications: !!expoNotifications,
|
|
2429
|
+
expoStoreReview: !!expoStoreReview?.requestReview,
|
|
2285
2430
|
viewShot: !!viewShot?.captureScreen
|
|
2286
2431
|
});
|
|
2287
2432
|
|
|
@@ -2693,6 +2838,13 @@ var buzz = () => {
|
|
|
2693
2838
|
} catch {
|
|
2694
2839
|
}
|
|
2695
2840
|
};
|
|
2841
|
+
var imagePicker = null;
|
|
2842
|
+
try {
|
|
2843
|
+
imagePicker = require("expo-image-picker");
|
|
2844
|
+
} catch {
|
|
2845
|
+
imagePicker = null;
|
|
2846
|
+
}
|
|
2847
|
+
var PHOTO_TYPES = { jpg: "image/jpeg", jpeg: "image/jpeg", png: "image/png", webp: "image/webp", gif: "image/gif" };
|
|
2696
2848
|
var teamOf = (t) => (t?.messages ?? []).filter((m) => m.from !== "visitor");
|
|
2697
2849
|
var svg = null;
|
|
2698
2850
|
try {
|
|
@@ -2708,9 +2860,10 @@ var GLYPH = {
|
|
|
2708
2860
|
up: "M8 13V3M3.5 7.5 8 3l4.5 4.5",
|
|
2709
2861
|
next: "M6 3.5 10.5 8 6 12.5",
|
|
2710
2862
|
search: "M7 11.5a4.5 4.5 0 1 0 0-9 4.5 4.5 0 0 0 0 9zM10.5 10.5l3 3",
|
|
2711
|
-
cross: "m4 4 8 8M12 4l-8 8"
|
|
2863
|
+
cross: "m4 4 8 8M12 4l-8 8",
|
|
2864
|
+
clip: "m14.3 7.4-6.1 6.1a4 4 0 0 1-5.7-5.7l5.7-5.7a2.7 2.7 0 0 1 3.8 3.8L6.3 11.6a1.3 1.3 0 0 1-1.9-1.9l5.7-5.6"
|
|
2712
2865
|
};
|
|
2713
|
-
var FALLBACK = { chat: "\u{1F4AC}", home: "\u2302", help: "?", send: "\u2192", up: "\u2191", next: "\u203A", search: "\u2315", cross: "\xD7" };
|
|
2866
|
+
var FALLBACK = { chat: "\u{1F4AC}", home: "\u2302", help: "?", send: "\u2192", up: "\u2191", next: "\u203A", search: "\u2315", cross: "\xD7", clip: "\u{1F4CE}" };
|
|
2714
2867
|
var glyph = (name, color, size = 20) => {
|
|
2715
2868
|
const Svg = svg?.Svg ?? svg?.default;
|
|
2716
2869
|
if (!svg || !Svg) return (0, import_react2.createElement)(RN3.Text, { style: { color, fontSize: Math.round(size * 0.85), lineHeight: size + 2, textAlign: "center", minWidth: size } }, FALLBACK[name]);
|
|
@@ -2722,7 +2875,11 @@ var teamMark = (title, bg, fg, size = 36) => (0, import_react2.createElement)(
|
|
|
2722
2875
|
{ style: { width: size, height: size, borderRadius: size / 2, backgroundColor: bg, alignItems: "center", justifyContent: "center" } },
|
|
2723
2876
|
(0, import_react2.createElement)(RN3.Text, { style: { color: fg, fontSize: Math.round(size * 0.42), fontWeight: "700" } }, (title.trim()[0] ?? "?").toUpperCase())
|
|
2724
2877
|
);
|
|
2725
|
-
var
|
|
2878
|
+
var HU = {
|
|
2879
|
+
light: { bg: "#ffffff", surface: "#f4f4f5", def: "#ececee", segment: "#ffffff", fg: "#18181b", muted: "#71717a" },
|
|
2880
|
+
dark: { bg: "#111113", surface: "#1c1c1f", def: "#26262a", segment: "#3a3a40", fg: "#fafafa", muted: "#a1a1aa" }
|
|
2881
|
+
};
|
|
2882
|
+
var hairline = { shadowColor: "#000", shadowOpacity: 0.07, shadowRadius: 3, shadowOffset: { width: 0, height: 1 }, elevation: 1 };
|
|
2726
2883
|
var useChat = (client, o, open, view) => {
|
|
2727
2884
|
const [thread, setThread] = (0, import_react2.useState)(null);
|
|
2728
2885
|
const [loaded, setLoaded] = (0, import_react2.useState)(false);
|
|
@@ -2889,12 +3046,14 @@ var MessengerSheet = ({
|
|
|
2889
3046
|
o,
|
|
2890
3047
|
chat,
|
|
2891
3048
|
view,
|
|
2892
|
-
setView
|
|
3049
|
+
setView,
|
|
3050
|
+
brand
|
|
2893
3051
|
}) => {
|
|
2894
3052
|
const k = kit(p);
|
|
2895
3053
|
const [body, setBody] = (0, import_react2.useState)("");
|
|
2896
3054
|
const [email, setEmail] = (0, import_react2.useState)("");
|
|
2897
3055
|
const [pending, setPending] = (0, import_react2.useState)(null);
|
|
3056
|
+
const [photos, setPhotos] = (0, import_react2.useState)([]);
|
|
2898
3057
|
const [query, setQuery] = (0, import_react2.useState)("");
|
|
2899
3058
|
const helpOn = o.help !== false;
|
|
2900
3059
|
const arts = useArticles(client, helpOn, query);
|
|
@@ -2908,52 +3067,135 @@ var MessengerSheet = ({
|
|
|
2908
3067
|
}, [count, view]);
|
|
2909
3068
|
const needEmail = (o.askEmail ?? true) && !thread?.hasEmail && !client.getUser()?.email;
|
|
2910
3069
|
const emailOk = !email.trim() || EMAIL.test(email.trim());
|
|
3070
|
+
const pickPhotos = async () => {
|
|
3071
|
+
const api = client.api();
|
|
3072
|
+
if (!api || !imagePicker?.launchImageLibraryAsync || pending) return;
|
|
3073
|
+
const room = CHAT_FILES_PER_MESSAGE - photos.length;
|
|
3074
|
+
if (room <= 0) return chat.setError(`Up to ${CHAT_FILES_PER_MESSAGE} files per message.`);
|
|
3075
|
+
let res;
|
|
3076
|
+
try {
|
|
3077
|
+
res = await imagePicker.launchImageLibraryAsync({ allowsMultipleSelection: room > 1, selectionLimit: room, quality: 0.8, exif: false });
|
|
3078
|
+
} catch (e) {
|
|
3079
|
+
return chat.setError(e.message || "The photos could not be opened.");
|
|
3080
|
+
}
|
|
3081
|
+
if (res.canceled || res.cancelled || !res.assets?.length) return;
|
|
3082
|
+
chat.setError("");
|
|
3083
|
+
const added = [];
|
|
3084
|
+
for (const a of res.assets.slice(0, room)) {
|
|
3085
|
+
const name = a.fileName || a.uri.split("/").pop() || "photo.jpg";
|
|
3086
|
+
const type = a.mimeType && Object.values(PHOTO_TYPES).includes(a.mimeType) ? a.mimeType : PHOTO_TYPES[name.split(".").pop()?.toLowerCase() ?? ""];
|
|
3087
|
+
if (!type) {
|
|
3088
|
+
chat.setError("Only pictures (PNG, JPEG, WebP, GIF) can be sent.");
|
|
3089
|
+
continue;
|
|
3090
|
+
}
|
|
3091
|
+
if (a.fileSize && a.fileSize > CHAT_FILE_MAX_BYTES) {
|
|
3092
|
+
chat.setError(`${name} is larger than 10 MB.`);
|
|
3093
|
+
continue;
|
|
3094
|
+
}
|
|
3095
|
+
const ph = { key: `local-${Date.now().toString(36)}${Math.random().toString(36).slice(2, 7)}`, uri: a.uri, name, type, size: a.fileSize ?? 0, up: Promise.resolve() };
|
|
3096
|
+
ph.up = api.uploadChatFile({ uri: a.uri, name, type }).then(
|
|
3097
|
+
(r2) => {
|
|
3098
|
+
ph.id = r2.id;
|
|
3099
|
+
setPhotos((list) => list.map((x) => x.key === ph.key ? { ...ph } : x));
|
|
3100
|
+
},
|
|
3101
|
+
(e) => {
|
|
3102
|
+
setPhotos((list) => list.filter((x) => x.key !== ph.key));
|
|
3103
|
+
chat.setError(`${name}: ${e.message}`);
|
|
3104
|
+
}
|
|
3105
|
+
);
|
|
3106
|
+
added.push(ph);
|
|
3107
|
+
}
|
|
3108
|
+
setPhotos((list) => [...list, ...added]);
|
|
3109
|
+
};
|
|
3110
|
+
const canSend = (!!body.trim() || photos.length > 0) && !pending && emailOk;
|
|
2911
3111
|
const send = async () => {
|
|
2912
3112
|
const text2 = body.trim();
|
|
2913
3113
|
const api = client.api();
|
|
2914
|
-
|
|
2915
|
-
|
|
3114
|
+
const files = photos;
|
|
3115
|
+
if (!text2 && !files.length || pending || !emailOk || !api) return;
|
|
3116
|
+
setPending({
|
|
3117
|
+
id: "pending",
|
|
3118
|
+
from: "visitor",
|
|
3119
|
+
body: text2,
|
|
3120
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3121
|
+
...files.length ? { attachments: files.map((f) => ({ id: f.key, name: f.name, type: f.type, size: f.size })) } : {}
|
|
3122
|
+
});
|
|
2916
3123
|
setBody("");
|
|
3124
|
+
setPhotos([]);
|
|
2917
3125
|
chat.setError("");
|
|
2918
3126
|
try {
|
|
2919
|
-
|
|
3127
|
+
await Promise.all(files.map((f) => f.up));
|
|
3128
|
+
const ids = files.filter((f) => f.id).map((f) => f.id);
|
|
3129
|
+
if (!text2 && !ids.length) throw new Error("The photo could not be sent.");
|
|
3130
|
+
const win = RN3.Dimensions.get("screen");
|
|
3131
|
+
chat.take(
|
|
3132
|
+
await api.sendChat({
|
|
3133
|
+
body: text2,
|
|
3134
|
+
email: needEmail && email.trim() ? email.trim() : void 0,
|
|
3135
|
+
...ids.length ? { attachments: ids } : {},
|
|
3136
|
+
// A little context for a bug report: the system and the screen size.
|
|
3137
|
+
context: { os: `${RN3.Platform.OS === "ios" ? "iOS" : RN3.Platform.OS === "android" ? "Android" : RN3.Platform.OS} ${RN3.Platform.Version ?? ""}`.trim(), browser: "App", viewport: `${Math.round(win.width)}\xD7${Math.round(win.height)}` }
|
|
3138
|
+
})
|
|
3139
|
+
);
|
|
2920
3140
|
} catch (e) {
|
|
2921
3141
|
setBody(text2);
|
|
3142
|
+
setPhotos(files.filter((f) => f.id));
|
|
2922
3143
|
chat.setError(e.message);
|
|
2923
3144
|
} finally {
|
|
2924
3145
|
setPending(null);
|
|
2925
3146
|
}
|
|
2926
3147
|
};
|
|
3148
|
+
const localUris = (0, import_react2.useRef)(/* @__PURE__ */ new Map());
|
|
3149
|
+
for (const f of photos) localUris.current.set(f.key, f.uri);
|
|
3150
|
+
const fileUrl = (id) => localUris.current.get(id) ?? client.api()?.attachmentUrl(id) ?? "";
|
|
2927
3151
|
const ink = inkOn(p.accent);
|
|
2928
3152
|
const dark = p.bg === DARK.bg;
|
|
2929
|
-
const
|
|
2930
|
-
const
|
|
2931
|
-
const
|
|
2932
|
-
const
|
|
3153
|
+
const r = CORNERS[o.corners ?? "round"];
|
|
3154
|
+
const plain = o.header !== "color";
|
|
3155
|
+
const hu = dark ? HU.dark : HU.light;
|
|
3156
|
+
const surf = {};
|
|
3157
|
+
const segLift = dark ? {} : hairline;
|
|
3158
|
+
const canvas = hu.bg;
|
|
3159
|
+
const raised = hu.surface;
|
|
3160
|
+
const title = o.title?.trim() ?? "";
|
|
2933
3161
|
const bubble = (m, isPending = false) => {
|
|
2934
3162
|
const mine = m.from === "visitor";
|
|
2935
3163
|
return (0, import_react2.createElement)(
|
|
2936
3164
|
RN3.View,
|
|
2937
3165
|
{ key: m.id, style: { alignSelf: mine ? "flex-end" : "flex-start", maxWidth: "82%", gap: 4 } },
|
|
2938
|
-
(
|
|
3166
|
+
...(m.attachments ?? []).map(
|
|
3167
|
+
(f) => (0, import_react2.createElement)(
|
|
3168
|
+
RN3.Pressable,
|
|
3169
|
+
{
|
|
3170
|
+
key: f.id,
|
|
3171
|
+
onPress: () => void RN3.Linking.openURL(fileUrl(f.id)).catch(() => void 0),
|
|
3172
|
+
accessibilityRole: "imagebutton",
|
|
3173
|
+
accessibilityLabel: f.name,
|
|
3174
|
+
style: { alignSelf: mine ? "flex-end" : "flex-start", opacity: isPending ? 0.7 : 1 }
|
|
3175
|
+
},
|
|
3176
|
+
f.type.startsWith("image/") ? (0, import_react2.createElement)(RN3.Image, { source: { uri: fileUrl(f.id) }, resizeMode: "cover", style: { width: 200, height: 150, borderRadius: r.bubble, backgroundColor: p.soft } }) : (0, import_react2.createElement)(RN3.View, { style: { backgroundColor: p.soft, borderRadius: r.card, paddingHorizontal: 10, paddingVertical: 7 } }, k.text(`\u{1F4C4} ${f.name}`, { fontSize: 13 }))
|
|
3177
|
+
)
|
|
3178
|
+
),
|
|
3179
|
+
!m.body && m.attachments?.length ? null : (0, import_react2.createElement)(
|
|
2939
3180
|
RN3.View,
|
|
2940
3181
|
{
|
|
2941
3182
|
style: {
|
|
2942
|
-
backgroundColor: mine ? p.accent :
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
|
|
3183
|
+
backgroundColor: mine ? p.accent : hu.surface,
|
|
3184
|
+
...mine ? {} : surf,
|
|
3185
|
+
borderRadius: r.bubble,
|
|
3186
|
+
borderBottomRightRadius: mine ? r.tail : r.bubble,
|
|
3187
|
+
borderBottomLeftRadius: mine ? r.bubble : r.tail,
|
|
2946
3188
|
paddingHorizontal: 14,
|
|
2947
3189
|
paddingVertical: 9,
|
|
2948
3190
|
opacity: isPending ? 0.7 : 1
|
|
2949
3191
|
}
|
|
2950
3192
|
},
|
|
2951
|
-
k.text(m.body, { color: mine ? ink :
|
|
3193
|
+
k.text(m.body, { color: mine ? ink : hu.fg })
|
|
2952
3194
|
),
|
|
2953
3195
|
...(m.articles ?? []).map(
|
|
2954
3196
|
(a) => (0, import_react2.createElement)(
|
|
2955
3197
|
RN3.Pressable,
|
|
2956
|
-
{ key: a.id, onPress: () => setView({ article: a.id }), accessibilityRole: "link", style: {
|
|
3198
|
+
{ key: a.id, onPress: () => setView({ article: a.id }), accessibilityRole: "link", style: { backgroundColor: p.soft, borderRadius: r.card, paddingHorizontal: 10, paddingVertical: 7 } },
|
|
2957
3199
|
k.text(`\u{1F4C4} ${a.title}`, { fontSize: 13 })
|
|
2958
3200
|
)
|
|
2959
3201
|
),
|
|
@@ -2966,60 +3208,80 @@ var MessengerSheet = ({
|
|
|
2966
3208
|
k.text(a.title, { flex: 1 }),
|
|
2967
3209
|
glyph("next", p.accent, 14)
|
|
2968
3210
|
);
|
|
2969
|
-
const card = (children, style = {}) => (0, import_react2.createElement)(RN3.View, { style: { backgroundColor: raised, borderRadius:
|
|
3211
|
+
const card = (children, style = {}) => (0, import_react2.createElement)(RN3.View, { style: { backgroundColor: raised, borderRadius: r.card, padding: 16, ...surf, ...style } }, ...children);
|
|
2970
3212
|
const searchRow = (onPress) => (0, import_react2.createElement)(
|
|
2971
3213
|
RN3.Pressable,
|
|
2972
|
-
{ key: "s", onPress, accessibilityRole: "search", style: { flexDirection: "row", alignItems: "center", height: 44, borderRadius:
|
|
3214
|
+
{ key: "s", onPress, accessibilityRole: "search", style: { flexDirection: "row", alignItems: "center", height: 44, borderRadius: r.field, paddingHorizontal: 14, backgroundColor: hu.def } },
|
|
2973
3215
|
k.muted("Search for help", { flex: 1, fontSize: 15 }),
|
|
2974
3216
|
glyph("search", p.accent, 16)
|
|
2975
3217
|
);
|
|
2976
|
-
const search = k.field({ value: query, onChange: setQuery, placeholder: "Search for help", max: 200, style: { borderWidth: 0, borderRadius:
|
|
3218
|
+
const search = k.field({ value: query, onChange: setQuery, placeholder: "Search for help", max: 200, style: { borderWidth: 0, borderRadius: r.field, backgroundColor: raised, ...surf } });
|
|
3219
|
+
const first = client.getUser()?.name?.trim().split(/\s+/)[0];
|
|
3220
|
+
const rest = greeting.replace(/^(hi|hello|hey|hallo)( there)?\s*[!,.]*\s*/i, "").trim() || "How can we help you today?";
|
|
3221
|
+
const hero = (fg, sub) => (0, import_react2.createElement)(
|
|
3222
|
+
RN3.Text,
|
|
3223
|
+
{ key: "hero", accessibilityRole: "header", style: { color: fg, fontSize: 25, fontWeight: "600", letterSpacing: -0.6, lineHeight: 30 } },
|
|
3224
|
+
first ? `Hi, ${first} \u{1F44B}
|
|
3225
|
+
` : "Hi there \u{1F44B}\n",
|
|
3226
|
+
(0, import_react2.createElement)(RN3.Text, { style: { color: sub, fontWeight: "500" } }, rest)
|
|
3227
|
+
);
|
|
2977
3228
|
const last = thread?.messages.at(-1);
|
|
2978
3229
|
const showHelp = helpOn && arts.any !== false;
|
|
2979
3230
|
let content;
|
|
2980
|
-
if (view === "home")
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
{ key: "home", style: { flex: 1, backgroundColor: canvas }, contentContainerStyle: { paddingBottom: 24 }, keyboardShouldPersistTaps: "handled" },
|
|
2984
|
-
(0, import_react2.createElement)(RN3.View, { style: { backgroundColor: p.accent, paddingHorizontal: 22, paddingTop: 4, paddingBottom: 64 } }, (0, import_react2.createElement)(RN3.Text, { style: { color: ink, fontSize: 28, fontWeight: "700", letterSpacing: -0.4, lineHeight: 34 } }, greeting)),
|
|
3231
|
+
if (view === "home") {
|
|
3232
|
+
const cards = [
|
|
3233
|
+
// "Ask a question": the words go into the chat's field and are sent as the chat opens.
|
|
2985
3234
|
(0, import_react2.createElement)(
|
|
2986
3235
|
RN3.View,
|
|
2987
|
-
{ style: {
|
|
2988
|
-
|
|
2989
|
-
RN3.Pressable,
|
|
2990
|
-
{ key: "recent", onPress: () => setView("messages"), accessibilityRole: "button" },
|
|
2991
|
-
card([
|
|
2992
|
-
k.muted("Recent message", { fontSize: 12, fontWeight: "600", marginBottom: 10 }),
|
|
2993
|
-
(0, import_react2.createElement)(
|
|
2994
|
-
RN3.View,
|
|
2995
|
-
{ key: "r", style: { flexDirection: "row", alignItems: "center", gap: 12 } },
|
|
2996
|
-
teamMark(title, p.accent, ink),
|
|
2997
|
-
(0, import_react2.createElement)(RN3.View, { style: { flex: 1, gap: 2 } }, (0, import_react2.createElement)(RN3.Text, { numberOfLines: 1, style: { color: p.fg, fontSize: 15 } }, last.body), k.muted(`${last.from === "visitor" ? "You" : whoLabel[last.from]} \xB7 ${timeAgo(last.at)}`, { fontSize: 12 })),
|
|
2998
|
-
chat.unseen.length ? (0, import_react2.createElement)(RN3.View, { style: { minWidth: 20, height: 20, borderRadius: 10, paddingHorizontal: 6, backgroundColor: p.danger, alignItems: "center", justifyContent: "center" } }, k.text(String(chat.unseen.length), { color: "#fff", fontSize: 12, fontWeight: "700" })) : glyph("next", p.muted, 14)
|
|
2999
|
-
)
|
|
3000
|
-
])
|
|
3001
|
-
) : null,
|
|
3236
|
+
{ key: "ask", style: { flexDirection: "row", alignItems: "center", gap: 8, height: 48, borderRadius: 24, paddingLeft: 18, paddingRight: 6, backgroundColor: hu.surface, ...surf } },
|
|
3237
|
+
k.field({ value: body, onChange: setBody, placeholder: "Ask a question", max: 4e3, style: { flex: 1, borderWidth: 0, paddingHorizontal: 0, backgroundColor: "transparent" } }),
|
|
3002
3238
|
(0, import_react2.createElement)(
|
|
3003
3239
|
RN3.Pressable,
|
|
3004
|
-
{
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
3240
|
+
{
|
|
3241
|
+
onPress: () => {
|
|
3242
|
+
setView("messages");
|
|
3243
|
+
if (body.trim()) void send();
|
|
3244
|
+
},
|
|
3245
|
+
accessibilityRole: "button",
|
|
3246
|
+
accessibilityLabel: "Send",
|
|
3247
|
+
style: { width: 36, height: 36, borderRadius: 18, alignItems: "center", justifyContent: "center", backgroundColor: body.trim() ? p.accent : hu.def }
|
|
3248
|
+
},
|
|
3249
|
+
glyph("up", body.trim() ? ink : hu.muted, 18)
|
|
3250
|
+
)
|
|
3251
|
+
),
|
|
3252
|
+
last ? (0, import_react2.createElement)(
|
|
3253
|
+
RN3.Pressable,
|
|
3254
|
+
{ key: "recent", onPress: () => setView("messages"), accessibilityRole: "button" },
|
|
3255
|
+
card([
|
|
3256
|
+
(0, import_react2.createElement)(
|
|
3257
|
+
RN3.View,
|
|
3258
|
+
{ key: "r", style: { flexDirection: "row", alignItems: "center", gap: 10 } },
|
|
3259
|
+
title ? teamMark(title, alpha(p.accent, 0.16), p.accent, 26) : null,
|
|
3260
|
+
(0, import_react2.createElement)(RN3.View, { style: { flex: 1, gap: 1 } }, (0, import_react2.createElement)(RN3.Text, { numberOfLines: 1, style: { color: hu.fg, fontSize: 14, fontWeight: "500" } }, last.body || (last.attachments?.length ? `Sent ${last.attachments.length === 1 ? last.attachments[0].name : `${last.attachments.length} files`}` : "")), k.muted(`${last.from === "visitor" ? "You" : whoLabel[last.from]} \xB7 ${timeAgo(last.at)}`, { fontSize: 11.5 })),
|
|
3261
|
+
chat.unseen.length ? (0, import_react2.createElement)(RN3.View, { style: { width: 8, height: 8, borderRadius: 4, backgroundColor: p.danger } }) : glyph("next", p.muted, 14)
|
|
3011
3262
|
)
|
|
3012
|
-
)
|
|
3013
|
-
|
|
3014
|
-
)
|
|
3263
|
+
], { paddingVertical: 10, paddingHorizontal: 12 })
|
|
3264
|
+
) : null,
|
|
3265
|
+
showHelp ? card([searchRow(() => setView("help")), ...(arts.list ?? []).slice(0, 4).map(row)], { padding: 8, gap: 2 }) : null
|
|
3266
|
+
];
|
|
3267
|
+
content = plain ? (0, import_react2.createElement)(
|
|
3268
|
+
RN3.ScrollView,
|
|
3269
|
+
{ key: "home", style: { flex: 1, backgroundColor: canvas }, contentContainerStyle: { paddingHorizontal: 16, paddingTop: 4, paddingBottom: 24, gap: 12 }, keyboardShouldPersistTaps: "handled" },
|
|
3270
|
+
(0, import_react2.createElement)(RN3.View, { key: "top", style: { paddingHorizontal: 6, paddingBottom: 10 } }, hero(p.fg, p.muted)),
|
|
3271
|
+
...cards
|
|
3272
|
+
) : (0, import_react2.createElement)(
|
|
3273
|
+
RN3.ScrollView,
|
|
3274
|
+
{ key: "home", style: { flex: 1, backgroundColor: canvas }, contentContainerStyle: { paddingBottom: 24 }, keyboardShouldPersistTaps: "handled" },
|
|
3275
|
+
(0, import_react2.createElement)(RN3.View, { style: { backgroundColor: p.accent, paddingHorizontal: 22, paddingTop: 4, paddingBottom: 64 } }, hero(ink, alpha(ink, 0.8))),
|
|
3276
|
+
(0, import_react2.createElement)(RN3.View, { style: { marginTop: -48, paddingHorizontal: 16, gap: 12 } }, ...cards)
|
|
3015
3277
|
);
|
|
3016
|
-
else if (view === "messages")
|
|
3278
|
+
} else if (view === "messages")
|
|
3017
3279
|
content = (0, import_react2.createElement)(
|
|
3018
3280
|
RN3.View,
|
|
3019
|
-
{ key: "messages", style: { flex: 1, backgroundColor:
|
|
3281
|
+
{ key: "messages", style: { flex: 1, backgroundColor: hu.bg } },
|
|
3020
3282
|
(0, import_react2.createElement)(
|
|
3021
3283
|
RN3.ScrollView,
|
|
3022
|
-
{ ref: scroll, style: { flex: 1 }, contentContainerStyle: {
|
|
3284
|
+
{ ref: scroll, style: { flex: 1 }, contentContainerStyle: { paddingHorizontal: 16, paddingTop: 4, paddingBottom: 16, gap: 12 } },
|
|
3023
3285
|
bubble({ id: "greeting", from: "team", body: greeting, at: "" }),
|
|
3024
3286
|
!chat.loaded ? k.muted("Loading\u2026") : null,
|
|
3025
3287
|
...(thread?.messages ?? []).map((m) => bubble(m)),
|
|
@@ -3028,23 +3290,57 @@ var MessengerSheet = ({
|
|
|
3028
3290
|
),
|
|
3029
3291
|
(0, import_react2.createElement)(
|
|
3030
3292
|
RN3.View,
|
|
3031
|
-
{ style: { paddingHorizontal: 12, paddingTop: 8, paddingBottom:
|
|
3293
|
+
{ style: { paddingHorizontal: 12, paddingTop: 8, paddingBottom: 6, gap: 8 } },
|
|
3032
3294
|
chat.error ? k.text(chat.error, { color: p.danger, fontSize: 12 }) : null,
|
|
3033
|
-
needEmail ? k.field({ value: email, onChange: setEmail, placeholder: "Your email (optional), so we can follow up", email: true, max: 200, style: { borderWidth: emailOk ? 0 : 1, borderColor: p.danger, borderRadius:
|
|
3295
|
+
needEmail ? k.field({ value: email, onChange: setEmail, placeholder: "Your email (optional), so we can follow up", email: true, max: 200, style: { borderWidth: emailOk ? 0 : 1, borderColor: p.danger, borderRadius: r.field, paddingHorizontal: 16, backgroundColor: hu.surface, ...surf } }) : null,
|
|
3296
|
+
// Photos picked for the next message, each with a way to take it out again.
|
|
3297
|
+
photos.length ? (0, import_react2.createElement)(
|
|
3298
|
+
RN3.View,
|
|
3299
|
+
{ style: { flexDirection: "row", flexWrap: "wrap", gap: 10, paddingTop: 6, paddingHorizontal: 2 } },
|
|
3300
|
+
...photos.map(
|
|
3301
|
+
(f) => (0, import_react2.createElement)(
|
|
3302
|
+
RN3.View,
|
|
3303
|
+
{ key: f.key, style: { width: 56, height: 56 } },
|
|
3304
|
+
(0, import_react2.createElement)(RN3.Image, { source: { uri: f.uri }, resizeMode: "cover", accessibilityLabel: f.id ? f.name : `${f.name}, uploading`, style: { width: 56, height: 56, borderRadius: Math.max(4, r.card - 4), backgroundColor: p.soft, opacity: f.id ? 1 : 0.5 } }),
|
|
3305
|
+
(0, import_react2.createElement)(
|
|
3306
|
+
RN3.Pressable,
|
|
3307
|
+
{
|
|
3308
|
+
onPress: () => setPhotos((list) => list.filter((x) => x.key !== f.key)),
|
|
3309
|
+
accessibilityRole: "button",
|
|
3310
|
+
accessibilityLabel: `Remove ${f.name}`,
|
|
3311
|
+
hitSlop: 8,
|
|
3312
|
+
style: { position: "absolute", top: -7, right: -7, width: 22, height: 22, borderRadius: 11, borderWidth: 2, borderColor: p.bg, backgroundColor: p.fg, alignItems: "center", justifyContent: "center" }
|
|
3313
|
+
},
|
|
3314
|
+
glyph("cross", p.bg, 10)
|
|
3315
|
+
)
|
|
3316
|
+
)
|
|
3317
|
+
)
|
|
3318
|
+
) : null,
|
|
3319
|
+
// The message field on top, the paperclip (when the app has expo-image-picker) and the send button under it.
|
|
3034
3320
|
(0, import_react2.createElement)(
|
|
3035
3321
|
RN3.View,
|
|
3036
|
-
{ style: {
|
|
3037
|
-
k.field({ value: body, onChange: setBody, placeholder: "Write a message\u2026", multiline: true, max: 4e3, style: {
|
|
3322
|
+
{ style: { gap: 2, borderRadius: 20, paddingTop: 10, paddingBottom: 6, paddingRight: 6, paddingLeft: 16, backgroundColor: hu.surface, ...surf } },
|
|
3323
|
+
k.field({ value: body, onChange: setBody, placeholder: "Write a message\u2026", multiline: true, max: 4e3, style: { minHeight: 28, maxHeight: 120, borderWidth: 0, paddingHorizontal: 0, paddingVertical: 4, backgroundColor: "transparent" } }),
|
|
3038
3324
|
(0, import_react2.createElement)(
|
|
3039
|
-
RN3.
|
|
3040
|
-
{
|
|
3041
|
-
|
|
3042
|
-
|
|
3043
|
-
accessibilityRole: "button",
|
|
3044
|
-
|
|
3045
|
-
|
|
3046
|
-
},
|
|
3047
|
-
|
|
3325
|
+
RN3.View,
|
|
3326
|
+
{ style: { flexDirection: "row", alignItems: "center", marginLeft: -8 } },
|
|
3327
|
+
imagePicker?.launchImageLibraryAsync ? (0, import_react2.createElement)(
|
|
3328
|
+
RN3.Pressable,
|
|
3329
|
+
{ onPress: () => void pickPhotos(), disabled: !!pending, accessibilityRole: "button", accessibilityLabel: "Attach a photo or screenshot", hitSlop: 6, style: { width: 34, height: 34, borderRadius: 17, alignItems: "center", justifyContent: "center" } },
|
|
3330
|
+
glyph("clip", hu.muted, 19)
|
|
3331
|
+
) : null,
|
|
3332
|
+
(0, import_react2.createElement)(RN3.View, { style: { flex: 1 } }),
|
|
3333
|
+
(0, import_react2.createElement)(
|
|
3334
|
+
RN3.Pressable,
|
|
3335
|
+
{
|
|
3336
|
+
onPress: () => void send(),
|
|
3337
|
+
disabled: !canSend,
|
|
3338
|
+
accessibilityRole: "button",
|
|
3339
|
+
accessibilityLabel: "Send",
|
|
3340
|
+
style: { width: 34, height: 34, borderRadius: 17, alignItems: "center", justifyContent: "center", backgroundColor: canSend ? p.accent : hu.def }
|
|
3341
|
+
},
|
|
3342
|
+
glyph("up", canSend ? ink : hu.muted, 18)
|
|
3343
|
+
)
|
|
3048
3344
|
)
|
|
3049
3345
|
)
|
|
3050
3346
|
)
|
|
@@ -3054,7 +3350,7 @@ var MessengerSheet = ({
|
|
|
3054
3350
|
for (const a of arts.list ?? []) groups.set(a.collection, [...groups.get(a.collection) ?? [], a]);
|
|
3055
3351
|
content = (0, import_react2.createElement)(
|
|
3056
3352
|
RN3.ScrollView,
|
|
3057
|
-
{ key: "help", style: { flex: 1, backgroundColor: canvas }, contentContainerStyle: {
|
|
3353
|
+
{ key: "help", style: { flex: 1, backgroundColor: canvas }, contentContainerStyle: { paddingHorizontal: 16, paddingTop: 4, paddingBottom: 16, gap: 12 }, keyboardShouldPersistTaps: "handled" },
|
|
3058
3354
|
search,
|
|
3059
3355
|
arts.list === null ? k.muted("Loading\u2026") : null,
|
|
3060
3356
|
arts.list?.length === 0 ? (0, import_react2.createElement)(RN3.View, { style: { gap: 10, alignItems: "center", paddingVertical: 24 } }, k.muted(query.trim() ? `Nothing found for \u201C${query.trim()}\u201D.` : "No help articles yet."), k.btn("Send us a message", "primary", () => setView("messages"))) : null,
|
|
@@ -3063,46 +3359,73 @@ var MessengerSheet = ({
|
|
|
3063
3359
|
} else
|
|
3064
3360
|
content = (0, import_react2.createElement)(
|
|
3065
3361
|
RN3.ScrollView,
|
|
3066
|
-
{ key: `a-${view.article}`, style: { flex: 1 }, contentContainerStyle: {
|
|
3362
|
+
{ key: `a-${view.article}`, style: { flex: 1 }, contentContainerStyle: { paddingHorizontal: 16, paddingTop: 4, paddingBottom: 32 } },
|
|
3067
3363
|
(0, import_react2.createElement)(ArticleView, { client, p, id: view.article, onAsk: () => setView("messages") })
|
|
3068
3364
|
);
|
|
3069
3365
|
const current = typeof view === "string" ? view : "help";
|
|
3070
|
-
const tab = (v, name, icon, badge = 0) =>
|
|
3366
|
+
const tab = (v, name, icon, badge = 0) => {
|
|
3367
|
+
const on = current === v;
|
|
3368
|
+
return (0, import_react2.createElement)(
|
|
3369
|
+
RN3.Pressable,
|
|
3370
|
+
{
|
|
3371
|
+
key: v,
|
|
3372
|
+
onPress: () => setView(v),
|
|
3373
|
+
accessibilityRole: "tab",
|
|
3374
|
+
accessibilityState: { selected: on },
|
|
3375
|
+
style: { flex: 1, flexDirection: "row", alignItems: "center", justifyContent: "center", gap: 6, height: 36, borderRadius: 18, ...on ? { backgroundColor: hu.segment, ...segLift } : {} }
|
|
3376
|
+
},
|
|
3377
|
+
glyph(icon, on ? hu.fg : hu.muted, 17),
|
|
3378
|
+
k.text(name, { fontSize: 13.5, fontWeight: "500", color: on ? hu.fg : hu.muted }),
|
|
3379
|
+
badge ? (0, import_react2.createElement)(RN3.View, { style: { position: "absolute", top: 7, right: 12, width: 8, height: 8, borderRadius: 4, backgroundColor: p.danger } }) : null
|
|
3380
|
+
);
|
|
3381
|
+
};
|
|
3382
|
+
const onAccent = view === "home" && !plain;
|
|
3383
|
+
const ground = onAccent ? p.accent : hu.bg;
|
|
3384
|
+
const back = (to, label) => (0, import_react2.createElement)(
|
|
3071
3385
|
RN3.Pressable,
|
|
3072
|
-
{ key:
|
|
3073
|
-
glyph(
|
|
3074
|
-
k.text(name, { fontSize: 12, fontWeight: "600", color: current === v ? p.accent : p.muted }),
|
|
3075
|
-
badge ? (0, import_react2.createElement)(RN3.View, { style: { position: "absolute", top: 4, right: "28%", minWidth: 18, height: 18, borderRadius: 9, backgroundColor: p.danger, alignItems: "center", justifyContent: "center" } }, k.text(String(Math.min(badge, 9)), { color: "#fff", fontSize: 11, fontWeight: "700" })) : null
|
|
3386
|
+
{ key: "back", onPress: () => setView(to), accessibilityRole: "button", accessibilityLabel: label, hitSlop: 10, style: { width: 32, height: 32, alignItems: "center", justifyContent: "center", transform: [{ rotate: "180deg" }] } },
|
|
3387
|
+
glyph("next", hu.fg, 18)
|
|
3076
3388
|
);
|
|
3077
|
-
const onHome = view === "home";
|
|
3078
|
-
const headFg = onHome ? ink : p.fg;
|
|
3079
3389
|
const head = (0, import_react2.createElement)(
|
|
3080
3390
|
RN3.View,
|
|
3081
|
-
{ key: "head", style: { flexDirection: "row", alignItems: "center", gap:
|
|
3082
|
-
typeof view !== "string" ? (
|
|
3083
|
-
|
|
3391
|
+
{ key: "head", style: { flexDirection: "row", alignItems: "center", gap: 8, paddingLeft: 12, paddingRight: 12, paddingTop: 12, paddingBottom: 6, backgroundColor: ground } },
|
|
3392
|
+
typeof view !== "string" ? back("help", "All articles") : view === "messages" ? back("home", "Back to home") : view === "home" && (o.logo || title) ? o.logo ? (0, import_react2.createElement)(RN3.Image, { key: "logo", source: { uri: o.logo }, accessibilityIgnoresInvertColors: true, style: { width: 28, height: 28, borderRadius: 8, marginLeft: 4 } }) : teamMark(title, alpha(p.accent, 0.16), p.accent, 28) : null,
|
|
3393
|
+
// In the chat: who answers, in the middle.
|
|
3394
|
+
view === "messages" && title ? (0, import_react2.createElement)(
|
|
3084
3395
|
RN3.View,
|
|
3085
|
-
{ style: { flex: 1,
|
|
3086
|
-
|
|
3087
|
-
|
|
3088
|
-
),
|
|
3089
|
-
(0, import_react2.createElement)(RN3.Pressable, { onPress: close, accessibilityRole: "button", accessibilityLabel: "Close", hitSlop: 12, style: { width: 32, height: 32, borderRadius: 10, alignItems: "center", justifyContent: "center" } }, glyph("cross",
|
|
3396
|
+
{ style: { flex: 1, flexDirection: "row", alignItems: "center", justifyContent: "center", gap: 8 } },
|
|
3397
|
+
teamMark(title, alpha(p.accent, 0.16), p.accent, 28),
|
|
3398
|
+
(0, import_react2.createElement)(RN3.Text, { numberOfLines: 1, style: { flexShrink: 1, color: hu.fg, fontSize: 15, fontWeight: "600" } }, title)
|
|
3399
|
+
) : (0, import_react2.createElement)(RN3.View, { style: { flex: 1 } }),
|
|
3400
|
+
(0, import_react2.createElement)(RN3.Pressable, { onPress: close, accessibilityRole: "button", accessibilityLabel: "Close", hitSlop: 12, style: { width: 32, height: 32, borderRadius: 10, alignItems: "center", justifyContent: "center" } }, glyph("cross", onAccent ? ink : p.muted, 18))
|
|
3090
3401
|
);
|
|
3402
|
+
const powered = brand ? (0, import_react2.createElement)(
|
|
3403
|
+
RN3.View,
|
|
3404
|
+
{ key: "brand", style: { paddingTop: 2, paddingBottom: 6, alignItems: "center", backgroundColor: hu.bg } },
|
|
3405
|
+
(0, import_react2.createElement)(
|
|
3406
|
+
RN3.Text,
|
|
3407
|
+
{ onPress: () => void RN3.Linking.openURL(BRAND_URL).catch(() => void 0), accessibilityRole: "link", hitSlop: 8, style: { color: p.muted, fontSize: 11.5 } },
|
|
3408
|
+
"Powered by ",
|
|
3409
|
+
(0, import_react2.createElement)(RN3.Text, { style: { fontWeight: "600" } }, "MRRocket")
|
|
3410
|
+
)
|
|
3411
|
+
) : null;
|
|
3091
3412
|
return (0, import_react2.createElement)(
|
|
3092
3413
|
RN3.Modal,
|
|
3093
3414
|
{ visible: true, animationType: "slide", presentationStyle: "pageSheet", onRequestClose: close },
|
|
3094
3415
|
(0, import_react2.createElement)(
|
|
3095
3416
|
RN3.KeyboardAvoidingView,
|
|
3096
|
-
{ behavior: RN3.Platform.OS === "ios" ? "padding" : void 0, style: { flex: 1, backgroundColor:
|
|
3417
|
+
{ behavior: RN3.Platform.OS === "ios" ? "padding" : void 0, style: { flex: 1, backgroundColor: hu.bg } },
|
|
3097
3418
|
head,
|
|
3098
3419
|
content,
|
|
3099
|
-
(0, import_react2.createElement)(
|
|
3420
|
+
view === "messages" ? null : (0, import_react2.createElement)(
|
|
3100
3421
|
RN3.View,
|
|
3101
|
-
{ key: "tabs", accessibilityRole: "tablist", style: { flexDirection: "row",
|
|
3422
|
+
{ key: "tabs", accessibilityRole: "tablist", style: { flexDirection: "row", marginHorizontal: 12, marginTop: 6, marginBottom: 6, padding: 4, borderRadius: 22, backgroundColor: hu.surface } },
|
|
3102
3423
|
tab("home", "Home", "home"),
|
|
3103
|
-
tab("messages", "Messages", "chat",
|
|
3424
|
+
tab("messages", "Messages", "chat", chat.unseen.length),
|
|
3104
3425
|
showHelp ? tab("help", "Help", "help") : null
|
|
3105
|
-
)
|
|
3426
|
+
),
|
|
3427
|
+
powered,
|
|
3428
|
+
(0, import_react2.createElement)(RN3.View, { style: { height: 16 } })
|
|
3106
3429
|
)
|
|
3107
3430
|
);
|
|
3108
3431
|
};
|
|
@@ -3127,6 +3450,9 @@ var MRRocketMessenger = ({ client, appearance, options: given = {} }) => {
|
|
|
3127
3450
|
preview: look.preview,
|
|
3128
3451
|
askEmail: look.askEmail,
|
|
3129
3452
|
haptics: look.sound,
|
|
3453
|
+
corners: look.corners,
|
|
3454
|
+
header: look.header,
|
|
3455
|
+
logo: look.logo,
|
|
3130
3456
|
...defined
|
|
3131
3457
|
} : given;
|
|
3132
3458
|
const p = usePalette({ theme: appearance?.theme ?? look?.theme, accent: appearance?.accent ?? look?.accent });
|
|
@@ -3155,37 +3481,42 @@ var MRRocketMessenger = ({ client, appearance, options: given = {} }) => {
|
|
|
3155
3481
|
const unseen = chat.unseen.length;
|
|
3156
3482
|
const ink = inkOn(p.accent);
|
|
3157
3483
|
const label = options.label?.trim() ?? "";
|
|
3158
|
-
const
|
|
3484
|
+
const r = CORNERS[options.corners ?? "round"];
|
|
3485
|
+
const [edge, side] = (options.position ?? "bottom-right").split("-");
|
|
3486
|
+
const place = {
|
|
3487
|
+
...edge === "top" ? { top: 60 } : edge === "bottom" ? { bottom: 34 } : { top: 0, bottom: 0, justifyContent: "center" },
|
|
3488
|
+
...side === "left" ? { left: 20 } : side === "right" ? { right: 20 } : { left: 0, right: 0, alignItems: "center" }
|
|
3489
|
+
};
|
|
3159
3490
|
const launcher = options.launcher ? (0, import_react2.createElement)(
|
|
3160
|
-
RN3.
|
|
3161
|
-
{
|
|
3162
|
-
|
|
3163
|
-
|
|
3164
|
-
|
|
3165
|
-
|
|
3166
|
-
|
|
3167
|
-
|
|
3168
|
-
|
|
3169
|
-
|
|
3170
|
-
|
|
3171
|
-
|
|
3172
|
-
|
|
3173
|
-
|
|
3174
|
-
|
|
3175
|
-
|
|
3176
|
-
|
|
3177
|
-
|
|
3178
|
-
|
|
3179
|
-
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
|
|
3183
|
-
|
|
3184
|
-
}
|
|
3185
|
-
|
|
3186
|
-
|
|
3187
|
-
|
|
3188
|
-
|
|
3491
|
+
RN3.View,
|
|
3492
|
+
{ key: "launcher", pointerEvents: "box-none", style: { position: "absolute", ...place } },
|
|
3493
|
+
(0, import_react2.createElement)(
|
|
3494
|
+
RN3.Pressable,
|
|
3495
|
+
{
|
|
3496
|
+
onPress: () => openAt(unseen ? "messages" : "home"),
|
|
3497
|
+
accessibilityRole: "button",
|
|
3498
|
+
accessibilityLabel: unseen ? `Open chat, ${unseen} new` : "Open chat",
|
|
3499
|
+
style: {
|
|
3500
|
+
height: label ? 50 : 56,
|
|
3501
|
+
minWidth: label ? 0 : 56,
|
|
3502
|
+
paddingHorizontal: label ? 18 : 0,
|
|
3503
|
+
borderRadius: 28,
|
|
3504
|
+
flexDirection: "row",
|
|
3505
|
+
gap: 8,
|
|
3506
|
+
backgroundColor: p.accent,
|
|
3507
|
+
alignItems: "center",
|
|
3508
|
+
justifyContent: "center",
|
|
3509
|
+
shadowColor: p.accent,
|
|
3510
|
+
shadowOpacity: 0.4,
|
|
3511
|
+
shadowRadius: 12,
|
|
3512
|
+
shadowOffset: { width: 0, height: 6 },
|
|
3513
|
+
elevation: 6
|
|
3514
|
+
}
|
|
3515
|
+
},
|
|
3516
|
+
glyph("chat", ink, label ? 20 : 26),
|
|
3517
|
+
label ? k.text(label, { color: ink, fontWeight: "700" }) : null,
|
|
3518
|
+
unseen ? (0, import_react2.createElement)(RN3.View, { style: { position: "absolute", top: -2, right: -2, minWidth: 20, height: 20, borderRadius: 10, backgroundColor: p.danger, alignItems: "center", justifyContent: "center", borderWidth: 2, borderColor: p.bg } }, k.text(String(Math.min(unseen, 9)), { color: "#fff", fontSize: 11, fontWeight: "700" })) : null
|
|
3519
|
+
)
|
|
3189
3520
|
) : null;
|
|
3190
3521
|
const toast = banner ? (0, import_react2.createElement)(
|
|
3191
3522
|
RN3.Pressable,
|
|
@@ -3194,15 +3525,25 @@ var MRRocketMessenger = ({ client, appearance, options: given = {} }) => {
|
|
|
3194
3525
|
onPress: () => openAt("messages"),
|
|
3195
3526
|
accessibilityRole: "button",
|
|
3196
3527
|
accessibilityLiveRegion: "polite",
|
|
3197
|
-
style: {
|
|
3528
|
+
style: {
|
|
3529
|
+
position: "absolute",
|
|
3530
|
+
top: 54,
|
|
3531
|
+
left: 16,
|
|
3532
|
+
right: 16,
|
|
3533
|
+
gap: 3,
|
|
3534
|
+
backgroundColor: p.bg === DARK.bg ? p.soft : p.bg,
|
|
3535
|
+
borderRadius: r.card + 2,
|
|
3536
|
+
paddingHorizontal: 16,
|
|
3537
|
+
paddingVertical: 13,
|
|
3538
|
+
shadowColor: "#101218",
|
|
3539
|
+
shadowOpacity: 0.14,
|
|
3540
|
+
shadowRadius: 18,
|
|
3541
|
+
shadowOffset: { width: 0, height: 8 },
|
|
3542
|
+
elevation: 6
|
|
3543
|
+
}
|
|
3198
3544
|
},
|
|
3199
|
-
|
|
3200
|
-
(0, import_react2.createElement)(
|
|
3201
|
-
RN3.View,
|
|
3202
|
-
{ style: { flex: 1, gap: 2 } },
|
|
3203
|
-
k.muted(`${whoLabel[banner.from]}${unseen > 1 ? ` \xB7 ${unseen} new` : ""}`, { fontWeight: "600", fontSize: 12 }),
|
|
3204
|
-
(0, import_react2.createElement)(RN3.Text, { numberOfLines: 3, style: { color: p.fg, fontSize: 15 } }, banner.body)
|
|
3205
|
-
)
|
|
3545
|
+
k.muted(`${whoLabel[banner.from]}${unseen > 1 ? ` \xB7 ${unseen} new` : ""}`, { fontWeight: "600", fontSize: 12 }),
|
|
3546
|
+
(0, import_react2.createElement)(RN3.Text, { numberOfLines: 3, style: { color: p.fg, fontSize: 15, lineHeight: 20 } }, banner.body)
|
|
3206
3547
|
) : null;
|
|
3207
3548
|
if (!launcher && !toast && !open) return null;
|
|
3208
3549
|
return (0, import_react2.createElement)(
|
|
@@ -3210,7 +3551,7 @@ var MRRocketMessenger = ({ client, appearance, options: given = {} }) => {
|
|
|
3210
3551
|
{ pointerEvents: "box-none", style: { position: "absolute", top: 0, left: 0, right: 0, bottom: 0 } },
|
|
3211
3552
|
launcher,
|
|
3212
3553
|
toast,
|
|
3213
|
-
open ? (0, import_react2.createElement)(MessengerSheet, { client, p, o: options, chat, view, setView }) : null
|
|
3554
|
+
open ? (0, import_react2.createElement)(MessengerSheet, { client, p, o: options, chat, view, setView, brand: look?.branding !== false }) : null
|
|
3214
3555
|
);
|
|
3215
3556
|
};
|
|
3216
3557
|
|
|
@@ -3304,6 +3645,10 @@ var usePaywall = () => {
|
|
|
3304
3645
|
const client = useClient();
|
|
3305
3646
|
return { open: client.openPaywall };
|
|
3306
3647
|
};
|
|
3648
|
+
var useReview = () => {
|
|
3649
|
+
const client = useClient();
|
|
3650
|
+
return { request: client.requestReview };
|
|
3651
|
+
};
|
|
3307
3652
|
var useOnboarding = () => {
|
|
3308
3653
|
const client = useClient();
|
|
3309
3654
|
return { start: client.startOnboarding };
|