@mrrocket/sdk 0.21.0 → 0.23.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 +14 -0
- package/README.md +12 -0
- package/dist/api-client.d.ts +14 -1
- package/dist/api-client.js +13 -4
- package/dist/api-client.js.map +1 -1
- package/dist/browser/mrrocket-flows.js +2 -2
- package/dist/browser/mrrocket-messenger.js +14 -5
- package/dist/browser/mrrocket.js +1 -1
- package/dist/browser.d.ts +1 -1
- package/dist/browser.js +2 -1
- package/dist/browser.js.map +1 -1
- package/dist/client.d.ts +11 -0
- package/dist/client.js +10 -3
- package/dist/client.js.map +1 -1
- package/dist/core.d.ts +3 -1
- package/dist/core.js +3 -1
- package/dist/core.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/messenger-ui.d.ts +2 -0
- package/dist/messenger-ui.js +138 -25
- package/dist/messenger-ui.js.map +1 -1
- package/dist/messenger.js +1 -0
- package/dist/messenger.js.map +1 -1
- package/dist/onboarding-report.d.ts +19 -0
- package/dist/onboarding-report.js +23 -0
- package/dist/onboarding-report.js.map +1 -0
- package/dist/onboarding-state.d.ts +3 -0
- package/dist/onboarding-state.js +7 -1
- package/dist/onboarding-state.js.map +1 -1
- package/dist/react-native/index.d.cts +33 -3
- package/dist/react-native/index.d.ts +33 -3
- package/dist/react-native/index.js +248 -211
- package/dist/react-native/index.js.map +1 -1
- package/dist/styles-css.d.ts +1 -1
- package/dist/styles-css.js +11 -2
- package/dist/styles-css.js.map +1 -1
- package/package.json +1 -1
|
@@ -43,6 +43,7 @@ __export(native_exports, {
|
|
|
43
43
|
useMessenger: () => useMessenger,
|
|
44
44
|
useNavigationTracking: () => useNavigationTracking,
|
|
45
45
|
useOnboarding: () => useOnboarding,
|
|
46
|
+
useOnboardingReport: () => useOnboardingReport,
|
|
46
47
|
usePaywall: () => usePaywall,
|
|
47
48
|
usePush: () => usePush,
|
|
48
49
|
useReview: () => useReview,
|
|
@@ -113,7 +114,7 @@ var onLocale = (fn) => {
|
|
|
113
114
|
};
|
|
114
115
|
|
|
115
116
|
// src/core.ts
|
|
116
|
-
var SDK_VERSION = "0.
|
|
117
|
+
var SDK_VERSION = "0.23.0";
|
|
117
118
|
var propOf = (person, name) => {
|
|
118
119
|
const want = name.trim().toLowerCase();
|
|
119
120
|
for (const [k, v] of Object.entries(person)) if (k.toLowerCase() === want) return v;
|
|
@@ -155,6 +156,7 @@ var flagFor = (key, config, distinctId, person = {}) => {
|
|
|
155
156
|
const flag = config?.flags?.find((f) => f.key === key);
|
|
156
157
|
return flag ? evaluateFlag(flag, distinctId, person) : false;
|
|
157
158
|
};
|
|
159
|
+
var CONSENT_OFF_EVENT = "$consent_off";
|
|
158
160
|
var UTM_KEYS = ["utm_source", "utm_medium", "utm_campaign", "utm_term", "utm_content"];
|
|
159
161
|
var SESSION_TIMEOUT_MS = 30 * 60 * 1e3;
|
|
160
162
|
var utmFrom = (get) => {
|
|
@@ -237,212 +239,6 @@ var newDistinctId = () => typeof crypto !== "undefined" && "randomUUID" in crypt
|
|
|
237
239
|
|
|
238
240
|
// src/onboarding-state.ts
|
|
239
241
|
var import_react = require("react");
|
|
240
|
-
var none = [];
|
|
241
|
-
var useOnboardingItem = (flows, id, start) => {
|
|
242
|
-
const [state, setState] = (0, import_react.useState)(() => flows && id ? flows.onboarding.item(id) : null);
|
|
243
|
-
(0, import_react.useEffect)(() => {
|
|
244
|
-
if (!flows || !id) {
|
|
245
|
-
setState(null);
|
|
246
|
-
return;
|
|
247
|
-
}
|
|
248
|
-
const up = () => {
|
|
249
|
-
const s2 = flows.onboarding.item(id);
|
|
250
|
-
setState(s2);
|
|
251
|
-
if (s2 && !s2.done && !s2.dismissed) flows.onboarding.seen(id);
|
|
252
|
-
};
|
|
253
|
-
up();
|
|
254
|
-
return flows.subscribe(up);
|
|
255
|
-
}, [flows, id]);
|
|
256
|
-
return (0, import_react.useMemo)(
|
|
257
|
-
() => ({
|
|
258
|
-
item: state?.item ?? null,
|
|
259
|
-
steps: state?.steps ?? none,
|
|
260
|
-
done: !!state?.done,
|
|
261
|
-
dismissed: !!state?.dismissed,
|
|
262
|
-
eligible: !!state?.eligible,
|
|
263
|
-
complete: (stepId) => {
|
|
264
|
-
if (flows && id) flows.onboarding.complete(id, stepId);
|
|
265
|
-
},
|
|
266
|
-
dismiss: () => {
|
|
267
|
-
if (flows && id) flows.onboarding.dismissItem(id);
|
|
268
|
-
},
|
|
269
|
-
answer: (question, value) => {
|
|
270
|
-
if (flows && id) flows.onboarding.answerItem(id, question, value);
|
|
271
|
-
},
|
|
272
|
-
start
|
|
273
|
-
}),
|
|
274
|
-
[state, flows, id, start]
|
|
275
|
-
);
|
|
276
|
-
};
|
|
277
|
-
|
|
278
|
-
// src/identity.ts
|
|
279
|
-
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 }).";
|
|
280
|
-
var warnUnsigned = (status, log = (m) => console.warn(m)) => {
|
|
281
|
-
const g2 = globalThis;
|
|
282
|
-
if (status !== 401 || g2.__mrrocketUnsigned) return;
|
|
283
|
-
g2.__mrrocketUnsigned = true;
|
|
284
|
-
log(`[MRRocket] ${UNSIGNED_HELP}`);
|
|
285
|
-
};
|
|
286
|
-
var nextSigned = (signed, id, hash, log = (m) => console.warn(m)) => {
|
|
287
|
-
const h3 = typeof hash === "string" ? hash.trim().toLowerCase() : "";
|
|
288
|
-
if (/^[0-9a-f]{64}$/.test(h3)) return { id, hash: h3 };
|
|
289
|
-
if (h3) log("[MRRocket] identify: that hash is no userHash(id); not sent.");
|
|
290
|
-
return signed && signed.id === id ? signed : null;
|
|
291
|
-
};
|
|
292
|
-
var hashFor = (signed, id) => signed && id && signed.id === id ? signed.hash : void 0;
|
|
293
|
-
var hashForAll = (signed, ids) => signed && ids.some((id) => id === signed.id) && ids.every((id) => !id || id === signed.id) ? signed.hash : void 0;
|
|
294
|
-
|
|
295
|
-
// src/api-client.ts
|
|
296
|
-
var CHAT_FILE_MAX_BYTES = 10 * 1024 * 1024;
|
|
297
|
-
var CHAT_FILES_PER_MESSAGE = 4;
|
|
298
|
-
var CORNERS = {
|
|
299
|
-
round: { panel: 22, bubble: 18, tail: 6, card: 16, field: 20 },
|
|
300
|
-
soft: { panel: 14, bubble: 12, tail: 4, card: 10, field: 10 },
|
|
301
|
-
square: { panel: 6, bubble: 6, tail: 2, card: 4, field: 4 }
|
|
302
|
-
};
|
|
303
|
-
var BRAND_URL = "https://mrrocket.dev/?utm_source=messenger&utm_medium=powered-by";
|
|
304
|
-
var inkOn = (hex) => {
|
|
305
|
-
const m = /^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i.exec(hex);
|
|
306
|
-
if (!m) return "#ffffff";
|
|
307
|
-
const [r, g2, b] = [m[1], m[2], m[3]].map((x) => {
|
|
308
|
-
const c = parseInt(x, 16) / 255;
|
|
309
|
-
return c <= 0.03928 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4;
|
|
310
|
-
});
|
|
311
|
-
return 0.2126 * r + 0.7152 * g2 + 0.0722 * b > 0.45 ? "#111214" : "#ffffff";
|
|
312
|
-
};
|
|
313
|
-
var ApiError = class extends Error {
|
|
314
|
-
constructor(message, status) {
|
|
315
|
-
super(message);
|
|
316
|
-
__publicField(this, "status");
|
|
317
|
-
this.status = status;
|
|
318
|
-
}
|
|
319
|
-
};
|
|
320
|
-
var qs = (params, lang = currentLocale()) => Object.entries({ ...params, lang }).filter((e) => !!e[1]).map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`).join("&");
|
|
321
|
-
var withLang = (url) => {
|
|
322
|
-
const q = qs({});
|
|
323
|
-
return q ? `${url}?${q}` : url;
|
|
324
|
-
};
|
|
325
|
-
var call = async (url, init, signal) => {
|
|
326
|
-
let res;
|
|
327
|
-
try {
|
|
328
|
-
res = await fetch(url, {
|
|
329
|
-
...init,
|
|
330
|
-
signal,
|
|
331
|
-
// A form (a file upload) sets its own multipart type with the boundary.
|
|
332
|
-
headers: typeof init?.body === "string" ? { "content-type": "application/json" } : void 0
|
|
333
|
-
});
|
|
334
|
-
} catch (e) {
|
|
335
|
-
if (e.name === "AbortError") throw e;
|
|
336
|
-
throw new ApiError(t("Could not reach the server. Check your connection and try again."), 0);
|
|
337
|
-
}
|
|
338
|
-
if (!res.ok) {
|
|
339
|
-
warnUnsigned(res.status);
|
|
340
|
-
let detail = "";
|
|
341
|
-
try {
|
|
342
|
-
detail = (await res.json()).error ?? "";
|
|
343
|
-
} catch {
|
|
344
|
-
}
|
|
345
|
-
throw new ApiError(detail || (res.status === 429 ? t("Too many requests. Try again in a minute.") : t("Something went wrong. Please try again.")), res.status);
|
|
346
|
-
}
|
|
347
|
-
return await res.json();
|
|
348
|
-
};
|
|
349
|
-
var createApi = (c) => {
|
|
350
|
-
const base = `${c.host}/api/sdk`;
|
|
351
|
-
const who2 = { key: c.apiKey, distinctId: c.distinctId, user: c.user, userHash: c.userHash };
|
|
352
|
-
const me = { key: c.apiKey, distinctId: c.distinctId, userHash: c.userHash };
|
|
353
|
-
const post = (path, body, signal) => call(withLang(`${base}${path}`), { method: "POST", body: JSON.stringify({ ...who2, ...body }) }, signal);
|
|
354
|
-
return {
|
|
355
|
-
list: (o, signal) => call(`${base}/feedback?${qs({ ...me, board: o.board, sort: o.sort, q: o.q })}`, void 0, signal),
|
|
356
|
-
post: (id, signal) => call(`${base}/feedback/${encodeURIComponent(id)}?${qs(me)}`, void 0, signal),
|
|
357
|
-
roadmap: (signal) => call(`${base}/feedback/roadmap?${qs(me)}`, void 0, signal),
|
|
358
|
-
changelog: (signal) => call(`${base}/feedback/changelog?${qs({ key: c.apiKey })}`, void 0, signal),
|
|
359
|
-
submit: (p) => post("/feedback", p),
|
|
360
|
-
vote: (id) => post(`/feedback/${encodeURIComponent(id)}/vote`, {}),
|
|
361
|
-
comments: (id, signal) => call(`${base}/feedback/${encodeURIComponent(id)}/comments?${qs({ key: c.apiKey })}`, void 0, signal),
|
|
362
|
-
comment: (id, body) => post(`/feedback/${encodeURIComponent(id)}/comments`, { body }),
|
|
363
|
-
survey: (url, signal) => call(`${base}/surveys?${qs({ ...me, url })}`, void 0, signal),
|
|
364
|
-
seen: (id) => post(`/surveys/${encodeURIComponent(id)}/seen`, { user: void 0 }),
|
|
365
|
-
respond: (id, r) => post(`/surveys/${encodeURIComponent(id)}/respond`, r),
|
|
366
|
-
/** One chat by id, else the visitor's newest; `conversations` lists them all. */
|
|
367
|
-
chat: (signal, conversation) => call(`${base}/messages?${qs({ ...me, conversation })}`, void 0, signal),
|
|
368
|
-
/**
|
|
369
|
-
* A message; `attachments` are upload ids from `uploadChatFile` (then the body may be empty). `conversation` is
|
|
370
|
-
* the chat it answers, or "new" to start one; without it, the running chat or else a new one.
|
|
371
|
-
*/
|
|
372
|
-
sendChat: (m) => post("/messages", m),
|
|
373
|
-
/** Uploads one picked file for the next message (pictures and PDFs, at most 10 MB); answers its upload id. */
|
|
374
|
-
uploadChatFile: (file, signal) => {
|
|
375
|
-
const form = new FormData();
|
|
376
|
-
form.append("key", c.apiKey);
|
|
377
|
-
form.append("distinctId", c.distinctId);
|
|
378
|
-
if (c.userHash) form.append("userHash", c.userHash);
|
|
379
|
-
if ("uri" in file) form.append("file", file);
|
|
380
|
-
else form.append("file", file, file.name || "file");
|
|
381
|
-
return call(withLang(`${base}/messages/attachments`), { method: "POST", body: form }, signal);
|
|
382
|
-
},
|
|
383
|
-
/** Where a sent file of this visitor's chat is read (an image's src, a PDF's link). */
|
|
384
|
-
attachmentUrl: (id) => `${base}/messages/attachments/${encodeURIComponent(id)}?${qs(me, "")}`,
|
|
385
|
-
articles: (q, signal) => call(`${base}/articles?${qs({ key: c.apiKey, q })}`, void 0, signal),
|
|
386
|
-
article: (id, signal) => call(`${base}/articles/${encodeURIComponent(id)}?${qs(me)}`, void 0, signal),
|
|
387
|
-
messengerLook: (signal) => call(`${base}/messenger?${qs({ key: c.apiKey })}`, void 0, signal),
|
|
388
|
-
rateArticle: (id, helpful) => post(`/articles/${encodeURIComponent(id)}/rate`, { helpful })
|
|
389
|
-
};
|
|
390
|
-
};
|
|
391
|
-
|
|
392
|
-
// src/batch.ts
|
|
393
|
-
var MAX_BATCH = 100;
|
|
394
|
-
var MAX_BYTES = 6e4;
|
|
395
|
-
var byteLength = (s2) => {
|
|
396
|
-
let n = 0;
|
|
397
|
-
for (let i = 0; i < s2.length; i++) {
|
|
398
|
-
const c = s2.charCodeAt(i);
|
|
399
|
-
if (c < 128) n += 1;
|
|
400
|
-
else if (c < 2048) n += 2;
|
|
401
|
-
else if (c >= 55296 && c <= 56319 && i + 1 < s2.length && (s2.charCodeAt(i + 1) & 64512) === 56320) {
|
|
402
|
-
n += 4;
|
|
403
|
-
i++;
|
|
404
|
-
} else n += 3;
|
|
405
|
-
}
|
|
406
|
-
return n;
|
|
407
|
-
};
|
|
408
|
-
var batchRequests = (key, events, sdk, userHash) => {
|
|
409
|
-
const head = `{"key":${JSON.stringify(key)},${userHash ? `"userHash":${JSON.stringify(userHash)},` : ""}"sdk":${JSON.stringify(sdk)},"events":[`;
|
|
410
|
-
const empty = byteLength(head) + 2;
|
|
411
|
-
const out = [];
|
|
412
|
-
let chunk = [];
|
|
413
|
-
let items = [];
|
|
414
|
-
let size = empty;
|
|
415
|
-
const close = () => {
|
|
416
|
-
if (chunk.length) out.push({ body: `${head}${chunk.join(",")}]}`, events: items });
|
|
417
|
-
chunk = [];
|
|
418
|
-
items = [];
|
|
419
|
-
size = empty;
|
|
420
|
-
};
|
|
421
|
-
for (const event of events) {
|
|
422
|
-
const json = JSON.stringify(event);
|
|
423
|
-
const n = byteLength(json) + 1;
|
|
424
|
-
if (empty + n > MAX_BYTES) continue;
|
|
425
|
-
if (chunk.length === MAX_BATCH || size + n > MAX_BYTES) close();
|
|
426
|
-
chunk.push(json);
|
|
427
|
-
items.push(event);
|
|
428
|
-
size += n;
|
|
429
|
-
}
|
|
430
|
-
close();
|
|
431
|
-
return out;
|
|
432
|
-
};
|
|
433
|
-
|
|
434
|
-
// src/errors.ts
|
|
435
|
-
var MAX_STACK = 8 * 1024;
|
|
436
|
-
var clip = (s2, n) => s2.length > n ? `${s2.slice(0, n - 1)}\u2026` : s2;
|
|
437
|
-
var describeError = (error) => {
|
|
438
|
-
if (error instanceof Error) return { type: error.name || "Error", message: clip(error.message || String(error), 1e3), stack: error.stack ? clip(error.stack, MAX_STACK) : void 0 };
|
|
439
|
-
if (typeof error === "string") return { type: "Error", message: clip(error, 1e3) };
|
|
440
|
-
try {
|
|
441
|
-
return { type: "Error", message: clip(JSON.stringify(error) ?? String(error), 1e3) };
|
|
442
|
-
} catch {
|
|
443
|
-
return { type: "Error", message: String(error) };
|
|
444
|
-
}
|
|
445
|
-
};
|
|
446
242
|
|
|
447
243
|
// src/flows.ts
|
|
448
244
|
var CANCEL_EVENT = "$cancel_flow";
|
|
@@ -1303,6 +1099,245 @@ var createFlows = (env) => {
|
|
|
1303
1099
|
return controller;
|
|
1304
1100
|
};
|
|
1305
1101
|
|
|
1102
|
+
// src/onboarding-report.ts
|
|
1103
|
+
var KIND = { questions: "question", checklist: "checklist", tour: "tour" };
|
|
1104
|
+
var createOnboardingReport = (track, name, options = {}) => {
|
|
1105
|
+
const type = options.type ?? "questions";
|
|
1106
|
+
const kind = KIND[type] ?? "question";
|
|
1107
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1108
|
+
const send = (props) => track(ONBOARDING_EVENT, { code: eventSlug(name), name, type, ...options.order?.length ? { order: options.order } : {}, ...props });
|
|
1109
|
+
return {
|
|
1110
|
+
saw: (step, title) => {
|
|
1111
|
+
if (seen.has(step)) return;
|
|
1112
|
+
seen.add(step);
|
|
1113
|
+
send({ step, title, kind, action: "shown" });
|
|
1114
|
+
},
|
|
1115
|
+
answered: (question, answer, label) => send({ step: question, kind: "question", action: "completed", question, answer, label: label ?? answer }),
|
|
1116
|
+
done: (step) => send({ step, kind, action: "completed" }),
|
|
1117
|
+
closed: (step) => send({ ...step ? { step } : {}, kind, action: "dismissed" })
|
|
1118
|
+
};
|
|
1119
|
+
};
|
|
1120
|
+
|
|
1121
|
+
// src/onboarding-state.ts
|
|
1122
|
+
var none = [];
|
|
1123
|
+
var useOnboardingItem = (flows, id, start) => {
|
|
1124
|
+
const [state, setState] = (0, import_react.useState)(() => flows && id ? flows.onboarding.item(id) : null);
|
|
1125
|
+
(0, import_react.useEffect)(() => {
|
|
1126
|
+
if (!flows || !id) {
|
|
1127
|
+
setState(null);
|
|
1128
|
+
return;
|
|
1129
|
+
}
|
|
1130
|
+
const up = () => {
|
|
1131
|
+
const s2 = flows.onboarding.item(id);
|
|
1132
|
+
setState(s2);
|
|
1133
|
+
if (s2 && !s2.done && !s2.dismissed) flows.onboarding.seen(id);
|
|
1134
|
+
};
|
|
1135
|
+
up();
|
|
1136
|
+
return flows.subscribe(up);
|
|
1137
|
+
}, [flows, id]);
|
|
1138
|
+
return (0, import_react.useMemo)(
|
|
1139
|
+
() => ({
|
|
1140
|
+
item: state?.item ?? null,
|
|
1141
|
+
steps: state?.steps ?? none,
|
|
1142
|
+
done: !!state?.done,
|
|
1143
|
+
dismissed: !!state?.dismissed,
|
|
1144
|
+
eligible: !!state?.eligible,
|
|
1145
|
+
complete: (stepId) => {
|
|
1146
|
+
if (flows && id) flows.onboarding.complete(id, stepId);
|
|
1147
|
+
},
|
|
1148
|
+
dismiss: () => {
|
|
1149
|
+
if (flows && id) flows.onboarding.dismissItem(id);
|
|
1150
|
+
},
|
|
1151
|
+
answer: (question, value) => {
|
|
1152
|
+
if (flows && id) flows.onboarding.answerItem(id, question, value);
|
|
1153
|
+
},
|
|
1154
|
+
start
|
|
1155
|
+
}),
|
|
1156
|
+
[state, flows, id, start]
|
|
1157
|
+
);
|
|
1158
|
+
};
|
|
1159
|
+
var useReportWith = (track, name, options) => {
|
|
1160
|
+
const key = JSON.stringify(options ?? null);
|
|
1161
|
+
return (0, import_react.useMemo)(() => createOnboardingReport(track, name, options), [track, name, key]);
|
|
1162
|
+
};
|
|
1163
|
+
|
|
1164
|
+
// src/identity.ts
|
|
1165
|
+
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 }).";
|
|
1166
|
+
var warnUnsigned = (status, log = (m) => console.warn(m)) => {
|
|
1167
|
+
const g2 = globalThis;
|
|
1168
|
+
if (status !== 401 || g2.__mrrocketUnsigned) return;
|
|
1169
|
+
g2.__mrrocketUnsigned = true;
|
|
1170
|
+
log(`[MRRocket] ${UNSIGNED_HELP}`);
|
|
1171
|
+
};
|
|
1172
|
+
var nextSigned = (signed, id, hash, log = (m) => console.warn(m)) => {
|
|
1173
|
+
const h3 = typeof hash === "string" ? hash.trim().toLowerCase() : "";
|
|
1174
|
+
if (/^[0-9a-f]{64}$/.test(h3)) return { id, hash: h3 };
|
|
1175
|
+
if (h3) log("[MRRocket] identify: that hash is no userHash(id); not sent.");
|
|
1176
|
+
return signed && signed.id === id ? signed : null;
|
|
1177
|
+
};
|
|
1178
|
+
var hashFor = (signed, id) => signed && id && signed.id === id ? signed.hash : void 0;
|
|
1179
|
+
var hashForAll = (signed, ids) => signed && ids.some((id) => id === signed.id) && ids.every((id) => !id || id === signed.id) ? signed.hash : void 0;
|
|
1180
|
+
|
|
1181
|
+
// src/api-client.ts
|
|
1182
|
+
var CHAT_FILE_MAX_BYTES = 10 * 1024 * 1024;
|
|
1183
|
+
var CHAT_FILES_PER_MESSAGE = 4;
|
|
1184
|
+
var CORNERS = {
|
|
1185
|
+
round: { panel: 22, bubble: 18, tail: 6, card: 16, field: 20 },
|
|
1186
|
+
soft: { panel: 14, bubble: 12, tail: 4, card: 10, field: 10 },
|
|
1187
|
+
square: { panel: 6, bubble: 6, tail: 2, card: 4, field: 4 }
|
|
1188
|
+
};
|
|
1189
|
+
var BRAND_URL = "https://mrrocket.dev/?utm_source=messenger&utm_medium=powered-by";
|
|
1190
|
+
var inkOn = (hex) => {
|
|
1191
|
+
const m = /^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i.exec(hex);
|
|
1192
|
+
if (!m) return "#ffffff";
|
|
1193
|
+
const [r, g2, b] = [m[1], m[2], m[3]].map((x) => {
|
|
1194
|
+
const c = parseInt(x, 16) / 255;
|
|
1195
|
+
return c <= 0.03928 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4;
|
|
1196
|
+
});
|
|
1197
|
+
return 0.2126 * r + 0.7152 * g2 + 0.0722 * b > 0.45 ? "#111214" : "#ffffff";
|
|
1198
|
+
};
|
|
1199
|
+
var stillThere = (t2, chats) => (m) => {
|
|
1200
|
+
if (m.chat === t2.conversation?.id) return t2.messages.some((x) => x.id === m.id);
|
|
1201
|
+
const last = chats.find((c) => c.id === m.chat)?.last;
|
|
1202
|
+
return !!last && m.at <= last.at;
|
|
1203
|
+
};
|
|
1204
|
+
var ApiError = class extends Error {
|
|
1205
|
+
constructor(message, status) {
|
|
1206
|
+
super(message);
|
|
1207
|
+
__publicField(this, "status");
|
|
1208
|
+
this.status = status;
|
|
1209
|
+
}
|
|
1210
|
+
};
|
|
1211
|
+
var qs = (params, lang = currentLocale()) => Object.entries({ ...params, lang }).filter((e) => !!e[1]).map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`).join("&");
|
|
1212
|
+
var withLang = (url) => {
|
|
1213
|
+
const q = qs({});
|
|
1214
|
+
return q ? `${url}?${q}` : url;
|
|
1215
|
+
};
|
|
1216
|
+
var call = async (url, init, signal) => {
|
|
1217
|
+
let res;
|
|
1218
|
+
try {
|
|
1219
|
+
res = await fetch(url, {
|
|
1220
|
+
...init,
|
|
1221
|
+
signal,
|
|
1222
|
+
// A form (a file upload) sets its own multipart type with the boundary.
|
|
1223
|
+
headers: typeof init?.body === "string" ? { "content-type": "application/json" } : void 0
|
|
1224
|
+
});
|
|
1225
|
+
} catch (e) {
|
|
1226
|
+
if (e.name === "AbortError") throw e;
|
|
1227
|
+
throw new ApiError(t("Could not reach the server. Check your connection and try again."), 0);
|
|
1228
|
+
}
|
|
1229
|
+
if (!res.ok) {
|
|
1230
|
+
warnUnsigned(res.status);
|
|
1231
|
+
let detail = "";
|
|
1232
|
+
try {
|
|
1233
|
+
detail = (await res.json()).error ?? "";
|
|
1234
|
+
} catch {
|
|
1235
|
+
}
|
|
1236
|
+
throw new ApiError(detail || (res.status === 429 ? t("Too many requests. Try again in a minute.") : t("Something went wrong. Please try again.")), res.status);
|
|
1237
|
+
}
|
|
1238
|
+
return await res.json();
|
|
1239
|
+
};
|
|
1240
|
+
var createApi = (c) => {
|
|
1241
|
+
const base = `${c.host}/api/sdk`;
|
|
1242
|
+
const who2 = { key: c.apiKey, distinctId: c.distinctId, user: c.user, userHash: c.userHash };
|
|
1243
|
+
const me = { key: c.apiKey, distinctId: c.distinctId, userHash: c.userHash };
|
|
1244
|
+
const post = (path, body, signal) => call(withLang(`${base}${path}`), { method: "POST", body: JSON.stringify({ ...who2, ...body }) }, signal);
|
|
1245
|
+
return {
|
|
1246
|
+
list: (o, signal) => call(`${base}/feedback?${qs({ ...me, board: o.board, sort: o.sort, q: o.q })}`, void 0, signal),
|
|
1247
|
+
post: (id, signal) => call(`${base}/feedback/${encodeURIComponent(id)}?${qs(me)}`, void 0, signal),
|
|
1248
|
+
roadmap: (signal) => call(`${base}/feedback/roadmap?${qs(me)}`, void 0, signal),
|
|
1249
|
+
changelog: (signal) => call(`${base}/feedback/changelog?${qs({ key: c.apiKey })}`, void 0, signal),
|
|
1250
|
+
submit: (p) => post("/feedback", p),
|
|
1251
|
+
vote: (id) => post(`/feedback/${encodeURIComponent(id)}/vote`, {}),
|
|
1252
|
+
comments: (id, signal) => call(`${base}/feedback/${encodeURIComponent(id)}/comments?${qs({ key: c.apiKey })}`, void 0, signal),
|
|
1253
|
+
comment: (id, body) => post(`/feedback/${encodeURIComponent(id)}/comments`, { body }),
|
|
1254
|
+
survey: (url, signal) => call(`${base}/surveys?${qs({ ...me, url })}`, void 0, signal),
|
|
1255
|
+
seen: (id) => post(`/surveys/${encodeURIComponent(id)}/seen`, { user: void 0 }),
|
|
1256
|
+
respond: (id, r) => post(`/surveys/${encodeURIComponent(id)}/respond`, r),
|
|
1257
|
+
/**
|
|
1258
|
+
* One chat by id, else the visitor's newest; `conversations` lists them all. `aiLabel`: the messenger says "AI agent"
|
|
1259
|
+
* beside an answer's name, so the answer comes without its AI notice line (from 0.22.0).
|
|
1260
|
+
*/
|
|
1261
|
+
chat: (signal, conversation) => call(`${base}/messages?${qs({ ...me, conversation, aiLabel: "1" })}`, void 0, signal),
|
|
1262
|
+
/**
|
|
1263
|
+
* A message; `attachments` are upload ids from `uploadChatFile` (then the body may be empty). `conversation` is
|
|
1264
|
+
* the chat it answers, or "new" to start one; without it, the running chat or else a new one.
|
|
1265
|
+
*/
|
|
1266
|
+
sendChat: (m) => post("/messages", { ...m, aiLabel: true }),
|
|
1267
|
+
/** Uploads one picked file for the next message (pictures and PDFs, at most 10 MB); answers its upload id. */
|
|
1268
|
+
uploadChatFile: (file, signal) => {
|
|
1269
|
+
const form = new FormData();
|
|
1270
|
+
form.append("key", c.apiKey);
|
|
1271
|
+
form.append("distinctId", c.distinctId);
|
|
1272
|
+
if (c.userHash) form.append("userHash", c.userHash);
|
|
1273
|
+
if ("uri" in file) form.append("file", file);
|
|
1274
|
+
else form.append("file", file, file.name || "file");
|
|
1275
|
+
return call(withLang(`${base}/messages/attachments`), { method: "POST", body: form }, signal);
|
|
1276
|
+
},
|
|
1277
|
+
/** Where a sent file of this visitor's chat is read (an image's src, a PDF's link). */
|
|
1278
|
+
attachmentUrl: (id) => `${base}/messages/attachments/${encodeURIComponent(id)}?${qs(me, "")}`,
|
|
1279
|
+
articles: (q, signal) => call(`${base}/articles?${qs({ key: c.apiKey, q })}`, void 0, signal),
|
|
1280
|
+
article: (id, signal) => call(`${base}/articles/${encodeURIComponent(id)}?${qs(me)}`, void 0, signal),
|
|
1281
|
+
messengerLook: (signal) => call(`${base}/messenger?${qs({ key: c.apiKey })}`, void 0, signal),
|
|
1282
|
+
rateArticle: (id, helpful) => post(`/articles/${encodeURIComponent(id)}/rate`, { helpful })
|
|
1283
|
+
};
|
|
1284
|
+
};
|
|
1285
|
+
|
|
1286
|
+
// src/batch.ts
|
|
1287
|
+
var MAX_BATCH = 100;
|
|
1288
|
+
var MAX_BYTES = 6e4;
|
|
1289
|
+
var byteLength = (s2) => {
|
|
1290
|
+
let n = 0;
|
|
1291
|
+
for (let i = 0; i < s2.length; i++) {
|
|
1292
|
+
const c = s2.charCodeAt(i);
|
|
1293
|
+
if (c < 128) n += 1;
|
|
1294
|
+
else if (c < 2048) n += 2;
|
|
1295
|
+
else if (c >= 55296 && c <= 56319 && i + 1 < s2.length && (s2.charCodeAt(i + 1) & 64512) === 56320) {
|
|
1296
|
+
n += 4;
|
|
1297
|
+
i++;
|
|
1298
|
+
} else n += 3;
|
|
1299
|
+
}
|
|
1300
|
+
return n;
|
|
1301
|
+
};
|
|
1302
|
+
var batchRequests = (key, events, sdk, userHash) => {
|
|
1303
|
+
const head = `{"key":${JSON.stringify(key)},${userHash ? `"userHash":${JSON.stringify(userHash)},` : ""}"sdk":${JSON.stringify(sdk)},"events":[`;
|
|
1304
|
+
const empty = byteLength(head) + 2;
|
|
1305
|
+
const out = [];
|
|
1306
|
+
let chunk = [];
|
|
1307
|
+
let items = [];
|
|
1308
|
+
let size = empty;
|
|
1309
|
+
const close = () => {
|
|
1310
|
+
if (chunk.length) out.push({ body: `${head}${chunk.join(",")}]}`, events: items });
|
|
1311
|
+
chunk = [];
|
|
1312
|
+
items = [];
|
|
1313
|
+
size = empty;
|
|
1314
|
+
};
|
|
1315
|
+
for (const event of events) {
|
|
1316
|
+
const json = JSON.stringify(event);
|
|
1317
|
+
const n = byteLength(json) + 1;
|
|
1318
|
+
if (empty + n > MAX_BYTES) continue;
|
|
1319
|
+
if (chunk.length === MAX_BATCH || size + n > MAX_BYTES) close();
|
|
1320
|
+
chunk.push(json);
|
|
1321
|
+
items.push(event);
|
|
1322
|
+
size += n;
|
|
1323
|
+
}
|
|
1324
|
+
close();
|
|
1325
|
+
return out;
|
|
1326
|
+
};
|
|
1327
|
+
|
|
1328
|
+
// src/errors.ts
|
|
1329
|
+
var MAX_STACK = 8 * 1024;
|
|
1330
|
+
var clip = (s2, n) => s2.length > n ? `${s2.slice(0, n - 1)}\u2026` : s2;
|
|
1331
|
+
var describeError = (error) => {
|
|
1332
|
+
if (error instanceof Error) return { type: error.name || "Error", message: clip(error.message || String(error), 1e3), stack: error.stack ? clip(error.stack, MAX_STACK) : void 0 };
|
|
1333
|
+
if (typeof error === "string") return { type: "Error", message: clip(error, 1e3) };
|
|
1334
|
+
try {
|
|
1335
|
+
return { type: "Error", message: clip(JSON.stringify(error) ?? String(error), 1e3) };
|
|
1336
|
+
} catch {
|
|
1337
|
+
return { type: "Error", message: String(error) };
|
|
1338
|
+
}
|
|
1339
|
+
};
|
|
1340
|
+
|
|
1306
1341
|
// src/paywall.ts
|
|
1307
1342
|
var PAYWALL_EVENT = "$paywall";
|
|
1308
1343
|
var paywallPages = (config, w, distinctId) => {
|
|
@@ -1833,7 +1868,7 @@ var createClient = (options, deps) => {
|
|
|
1833
1868
|
}
|
|
1834
1869
|
};
|
|
1835
1870
|
const sendQueue = async () => {
|
|
1836
|
-
if (!queue.length || consent === "off"
|
|
1871
|
+
if (!queue.length || !apiKey || consent === "off" && queue.some((e) => e.event !== CONSENT_OFF_EVENT)) return;
|
|
1837
1872
|
if (now() < backoffUntil) return;
|
|
1838
1873
|
const snapshot = queue.slice();
|
|
1839
1874
|
const requests = batchRequests(apiKey, snapshot, sdk, hashOf(sendId()));
|
|
@@ -2257,7 +2292,8 @@ var createClient = (options, deps) => {
|
|
|
2257
2292
|
consent = next;
|
|
2258
2293
|
if (next === "off") {
|
|
2259
2294
|
recorder?.drop();
|
|
2260
|
-
queue = [];
|
|
2295
|
+
queue = identified && before !== "off" ? [{ event: CONSENT_OFF_EVENT, distinctId: identified, ts: Date.now() }] : [];
|
|
2296
|
+
if (queue.length) void flush();
|
|
2261
2297
|
session = null;
|
|
2262
2298
|
errorQueue.length = 0;
|
|
2263
2299
|
void Promise.all(Object.values(KEYS).map(remove));
|
|
@@ -3550,7 +3586,7 @@ var POLL_QUIET_MS = 6e4;
|
|
|
3550
3586
|
var WAITING_FOR_MS = 10 * 6e4;
|
|
3551
3587
|
var EMAIL = /^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/;
|
|
3552
3588
|
var whoLabel = (from) => from === "visitor" ? t("You") : from === "ai" ? t("AI agent") : t("Team");
|
|
3553
|
-
var who = (from, agentName) => from === "ai" && agentName?.trim()
|
|
3589
|
+
var who = (from, agentName) => from === "ai" && agentName?.trim() ? `${agentName.trim()} \xB7 ${t("AI agent")}` : whoLabel(from);
|
|
3554
3590
|
var msgView = "home";
|
|
3555
3591
|
var msgChat = null;
|
|
3556
3592
|
var messengerOpen = opener();
|
|
@@ -3668,7 +3704,7 @@ var useChat = (client, o, open, view) => {
|
|
|
3668
3704
|
if (fresh.length && o.haptics !== false) buzz();
|
|
3669
3705
|
const shown2 = shownChat(at.current, all);
|
|
3670
3706
|
const counted = fresh.filter((m) => !watch.current || m.chat !== shown2);
|
|
3671
|
-
setUnseen((u) => [...u.filter((
|
|
3707
|
+
setUnseen((u) => [...u.filter(stillThere(t2, all)), ...counted]);
|
|
3672
3708
|
list.current = all;
|
|
3673
3709
|
setChats(all);
|
|
3674
3710
|
setHasEmail(t2.hasEmail);
|
|
@@ -4544,6 +4580,7 @@ var useOnboarding = (id) => {
|
|
|
4544
4580
|
const client = useClient();
|
|
4545
4581
|
return useOnboardingItem(client.flows, id, client.startOnboarding);
|
|
4546
4582
|
};
|
|
4583
|
+
var useOnboardingReport = (name, options) => useReportWith(useClient().track, name, options);
|
|
4547
4584
|
var useFlag = (key) => {
|
|
4548
4585
|
const client = useClient();
|
|
4549
4586
|
useVersion(client);
|