brainerce 1.28.0 → 1.30.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.
@@ -0,0 +1,505 @@
1
+ // src/bot/widget.ts
2
+ var DEFAULT_BASE_URL = "https://api.brainerce.com";
3
+ var RTL_LOCALES = /* @__PURE__ */ new Set(["he", "ar"]);
4
+ var CHROME = {
5
+ en: {
6
+ online: "Online",
7
+ placeholder: "Ask anything\u2026",
8
+ error: "Something went wrong \u2014 please try again.",
9
+ leaveMessage: "Leave a message for the team",
10
+ yourEmail: "Your email",
11
+ yourMessage: "Your message",
12
+ send: "Send",
13
+ sent: "Thanks! The team will get back to you by email.",
14
+ close: "Close"
15
+ },
16
+ he: {
17
+ online: "\u05DE\u05D7\u05D5\u05D1\u05E8",
18
+ placeholder: "\u05E9\u05D0\u05DC\u05D5 \u05D0\u05D5\u05EA\u05D9 \u05D4\u05DB\u05DC\u2026",
19
+ error: "\u05DE\u05E9\u05D4\u05D5 \u05D4\u05E9\u05EA\u05D1\u05E9 \u2014 \u05E0\u05E1\u05D5 \u05E9\u05D5\u05D1.",
20
+ leaveMessage: "\u05D4\u05E9\u05D0\u05D9\u05E8\u05D5 \u05D4\u05D5\u05D3\u05E2\u05D4 \u05DC\u05E6\u05D5\u05D5\u05EA",
21
+ yourEmail: "\u05D4\u05D0\u05D9\u05DE\u05D9\u05D9\u05DC \u05E9\u05DC\u05DB\u05DD",
22
+ yourMessage: "\u05D4\u05D4\u05D5\u05D3\u05E2\u05D4 \u05E9\u05DC\u05DB\u05DD",
23
+ send: "\u05E9\u05DC\u05D9\u05D7\u05D4",
24
+ sent: "\u05EA\u05D5\u05D3\u05D4! \u05D4\u05E6\u05D5\u05D5\u05EA \u05D9\u05D7\u05D6\u05D5\u05E8 \u05D0\u05DC\u05D9\u05DB\u05DD \u05D1\u05DE\u05D9\u05D9\u05DC.",
25
+ close: "\u05E1\u05D2\u05D9\u05E8\u05D4"
26
+ }
27
+ };
28
+ function randomId(prefix) {
29
+ const bytes = new Uint8Array(16);
30
+ crypto.getRandomValues(bytes);
31
+ const b64 = btoa(String.fromCharCode(...bytes)).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
32
+ return `${prefix}${b64}`;
33
+ }
34
+ var BrainerceBot = class _BrainerceBot {
35
+ constructor(options) {
36
+ this.settings = { enabled: false };
37
+ this.locale = "en";
38
+ this.sessionId = null;
39
+ this.conversationId = null;
40
+ this.busy = false;
41
+ this.opened = false;
42
+ this.destroyed = false;
43
+ this.connectionId = options.connectionId;
44
+ this.baseUrl = (options.baseUrl || DEFAULT_BASE_URL).replace(/\/$/, "");
45
+ this.storageKey = `brainerce-bot:${this.connectionId}`;
46
+ }
47
+ /** Boot the widget. Resolves to null when the bot is disabled server-side. */
48
+ static async mount(options) {
49
+ if (!options?.connectionId) {
50
+ console.warn("[BrainerceBot] connectionId is required");
51
+ return null;
52
+ }
53
+ const bot = new _BrainerceBot(options);
54
+ const ok = await bot.boot(options.target ?? document.body);
55
+ return ok ? bot : null;
56
+ }
57
+ destroy() {
58
+ this.destroyed = true;
59
+ this.host?.remove();
60
+ }
61
+ // ---------------------------------------------------------------------------
62
+ async boot(target) {
63
+ try {
64
+ const res = await fetch(
65
+ `${this.baseUrl}/api/storefront-bot/${encodeURIComponent(this.connectionId)}/settings`
66
+ );
67
+ if (!res.ok) return false;
68
+ this.settings = await res.json();
69
+ } catch {
70
+ return false;
71
+ }
72
+ if (!this.settings.enabled) return false;
73
+ this.locale = this.settings.languages?.[0] ?? "en";
74
+ this.restoreIds();
75
+ this.render(target);
76
+ if (this.settings.displayMode === "auto_open") {
77
+ setTimeout(() => !this.destroyed && this.open(), 3e3);
78
+ }
79
+ return true;
80
+ }
81
+ t(key) {
82
+ return (CHROME[this.locale] ?? CHROME.en)[key] ?? CHROME.en[key] ?? key;
83
+ }
84
+ restoreIds() {
85
+ try {
86
+ const raw = localStorage.getItem(this.storageKey);
87
+ if (raw) {
88
+ const parsed = JSON.parse(raw);
89
+ this.sessionId = parsed.sessionId ?? null;
90
+ this.conversationId = parsed.conversationId ?? null;
91
+ }
92
+ } catch {
93
+ }
94
+ }
95
+ persistIds() {
96
+ try {
97
+ localStorage.setItem(
98
+ this.storageKey,
99
+ JSON.stringify({ sessionId: this.sessionId, conversationId: this.conversationId })
100
+ );
101
+ } catch {
102
+ }
103
+ }
104
+ // --------------------------------------------------------------------------
105
+ // Rendering
106
+ // --------------------------------------------------------------------------
107
+ render(target) {
108
+ const accent = this.settings.accentColor || "#6366F1";
109
+ const dir = RTL_LOCALES.has(this.locale) ? "rtl" : "ltr";
110
+ const radius = this.settings.bubbleShape === "square" ? "8px" : "16px";
111
+ const side = this.settings.position === "start" ? "left" : "right";
112
+ const sideRtlAware = dir === "rtl" ? side === "left" ? "right" : "left" : side;
113
+ this.host = document.createElement("div");
114
+ this.host.setAttribute("data-brainerce-bot", this.connectionId);
115
+ this.root = this.host.attachShadow({ mode: "open" });
116
+ const style = document.createElement("style");
117
+ style.textContent = `
118
+ :host { all: initial; }
119
+ * { box-sizing: border-box; font-family: ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif; }
120
+ .bb-root { position: fixed; bottom: 20px; ${sideRtlAware}: 20px; z-index: 2147483000; direction: ${dir}; }
121
+ .bb-launcher {
122
+ width: 56px; height: 56px; border: none; cursor: pointer; display: flex;
123
+ align-items: center; justify-content: center; color: #fff; background: ${accent};
124
+ border-radius: ${this.settings.bubbleShape === "square" ? "14px" : "9999px"};
125
+ box-shadow: 0 8px 24px rgba(0,0,0,.22); transition: transform .15s ease;
126
+ overflow: hidden; padding: 0;
127
+ }
128
+ .bb-launcher:hover { transform: scale(1.06); }
129
+ .bb-launcher img { width: 100%; height: 100%; object-fit: cover; }
130
+ .bb-window {
131
+ position: absolute; bottom: 70px; ${sideRtlAware}: 0; width: 360px; max-width: calc(100vw - 32px);
132
+ height: 540px; max-height: calc(100vh - 110px); display: none; flex-direction: column;
133
+ background: #fff; border-radius: 16px; overflow: hidden;
134
+ box-shadow: 0 16px 48px rgba(0,0,0,.24); border: 1px solid rgba(0,0,0,.06);
135
+ }
136
+ .bb-window.open { display: flex; }
137
+ .bb-header { display: flex; align-items: center; gap: 10px; padding: 12px 14px; background: ${accent}; color: #fff; }
138
+ .bb-avatar { width: 34px; height: 34px; border-radius: 9999px; background: rgba(255,255,255,.25);
139
+ display: flex; align-items: center; justify-content: center; font-weight: 600; overflow: hidden; flex-shrink: 0; }
140
+ .bb-avatar img { width: 100%; height: 100%; object-fit: cover; }
141
+ .bb-head-main { flex: 1; min-width: 0; }
142
+ .bb-name { font-size: 14px; font-weight: 600; line-height: 1.2; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
143
+ .bb-status { font-size: 11px; opacity: .9; display: flex; align-items: center; gap: 5px; }
144
+ .bb-dot { width: 6px; height: 6px; border-radius: 9999px; background: #34d399; }
145
+ .bb-iconbtn { background: none; border: none; color: #fff; cursor: pointer; opacity: .85; font-size: 16px; padding: 4px; }
146
+ .bb-iconbtn:hover { opacity: 1; }
147
+ .bb-messages { flex: 1; overflow-y: auto; padding: 14px; background: #f7f7f9; display: flex; flex-direction: column; gap: 8px; }
148
+ .bb-msg { max-width: 80%; padding: 9px 12px; border-radius: ${radius}; font-size: 13.5px; line-height: 1.45; white-space: pre-wrap; word-break: break-word; }
149
+ .bb-msg.bot { align-self: flex-start; background: #fff; border: 1px solid rgba(0,0,0,.07); border-end-start-radius: 4px; }
150
+ .bb-msg.user { align-self: flex-end; background: ${accent}; color: #fff; border-end-end-radius: 4px; }
151
+ .bb-msg.err { align-self: flex-start; background: #fef2f2; color: #b91c1c; border: 1px solid #fecaca; }
152
+ .bb-typing { align-self: flex-start; font-size: 11.5px; color: #6b7280; padding: 2px 4px; }
153
+ .bb-card { align-self: flex-start; width: 230px; background: #fff; border: 1px solid rgba(0,0,0,.08);
154
+ border-radius: 12px; overflow: hidden; text-decoration: none; color: inherit; display: block; }
155
+ .bb-card img { width: 100%; height: 120px; object-fit: cover; display: block; background: #eee; }
156
+ .bb-card-body { padding: 9px 11px; }
157
+ .bb-card-title { font-size: 13px; font-weight: 600; margin: 0 0 3px; }
158
+ .bb-card-price { font-size: 13px; color: ${accent}; font-weight: 600; }
159
+ .bb-chips { display: flex; flex-wrap: wrap; gap: 6px; padding: 0 14px 10px; background: #f7f7f9; }
160
+ .bb-chip { border: 1px solid ${accent}; color: ${accent}; background: #fff; border-radius: 9999px;
161
+ font-size: 12px; padding: 5px 11px; cursor: pointer; }
162
+ .bb-inputrow { display: flex; gap: 8px; padding: 10px 12px; border-top: 1px solid rgba(0,0,0,.07); background: #fff; }
163
+ .bb-input { flex: 1; border: none; outline: none; font-size: 13.5px; background: #f1f1f4; border-radius: 9999px; padding: 9px 14px; }
164
+ .bb-send { border: none; background: ${accent}; color: #fff; width: 36px; height: 36px; border-radius: 9999px; cursor: pointer; font-size: 15px; flex-shrink: 0; }
165
+ .bb-send:disabled { opacity: .5; cursor: default; }
166
+ .bb-esc { padding: 12px 14px; background: #fff; border-top: 1px solid rgba(0,0,0,.07); display: none; flex-direction: column; gap: 8px; }
167
+ .bb-esc.open { display: flex; }
168
+ .bb-esc input, .bb-esc textarea { border: 1px solid rgba(0,0,0,.12); border-radius: 8px; padding: 8px 10px; font-size: 13px; outline: none; resize: none; }
169
+ .bb-esc button { border: none; background: ${accent}; color: #fff; border-radius: 8px; padding: 8px; font-size: 13px; cursor: pointer; }
170
+ .bb-esc-note { font-size: 12px; color: #047857; }
171
+ `;
172
+ this.root.appendChild(style);
173
+ const rootEl = document.createElement("div");
174
+ rootEl.className = "bb-root";
175
+ this.root.appendChild(rootEl);
176
+ this.windowEl = document.createElement("div");
177
+ this.windowEl.className = "bb-window";
178
+ rootEl.appendChild(this.windowEl);
179
+ const name = this.settings.displayName || "Assistant";
180
+ const header = document.createElement("div");
181
+ header.className = "bb-header";
182
+ const avatar = document.createElement("span");
183
+ avatar.className = "bb-avatar";
184
+ if (this.settings.avatarUrl && isSafeUrl(this.settings.avatarUrl)) {
185
+ const img = document.createElement("img");
186
+ img.src = this.settings.avatarUrl;
187
+ img.alt = "";
188
+ avatar.appendChild(img);
189
+ } else {
190
+ avatar.textContent = name.charAt(0).toUpperCase();
191
+ }
192
+ const headMain = document.createElement("span");
193
+ headMain.className = "bb-head-main";
194
+ const nameEl = document.createElement("span");
195
+ nameEl.className = "bb-name";
196
+ nameEl.textContent = name;
197
+ const statusEl = document.createElement("span");
198
+ statusEl.className = "bb-status";
199
+ const dot = document.createElement("span");
200
+ dot.className = "bb-dot";
201
+ statusEl.appendChild(dot);
202
+ statusEl.appendChild(document.createTextNode(this.t("online")));
203
+ headMain.appendChild(nameEl);
204
+ headMain.appendChild(statusEl);
205
+ const escBtn = document.createElement("button");
206
+ escBtn.className = "bb-iconbtn";
207
+ escBtn.dataset.act = "esc";
208
+ escBtn.title = this.t("leaveMessage");
209
+ escBtn.textContent = "\u2709";
210
+ const closeBtn = document.createElement("button");
211
+ closeBtn.className = "bb-iconbtn";
212
+ closeBtn.dataset.act = "close";
213
+ closeBtn.title = this.t("close");
214
+ closeBtn.textContent = "\u2715";
215
+ header.appendChild(avatar);
216
+ header.appendChild(headMain);
217
+ header.appendChild(escBtn);
218
+ header.appendChild(closeBtn);
219
+ this.windowEl.appendChild(header);
220
+ header.querySelector('[data-act="close"]')?.addEventListener("click", () => this.close());
221
+ header.querySelector('[data-act="esc"]')?.addEventListener("click", () => this.toggleEscalation());
222
+ this.messagesEl = document.createElement("div");
223
+ this.messagesEl.className = "bb-messages";
224
+ this.windowEl.appendChild(this.messagesEl);
225
+ this.chipsEl = document.createElement("div");
226
+ this.chipsEl.className = "bb-chips";
227
+ for (const q of this.settings.starterQuestions ?? []) {
228
+ const chip = document.createElement("button");
229
+ chip.className = "bb-chip";
230
+ chip.textContent = q;
231
+ chip.addEventListener("click", () => this.send(q));
232
+ this.chipsEl.appendChild(chip);
233
+ }
234
+ this.windowEl.appendChild(this.chipsEl);
235
+ const esc = document.createElement("div");
236
+ esc.className = "bb-esc";
237
+ const escEmail = document.createElement("input");
238
+ escEmail.type = "email";
239
+ escEmail.name = "email";
240
+ escEmail.placeholder = this.t("yourEmail");
241
+ const escMsg = document.createElement("textarea");
242
+ escMsg.name = "message";
243
+ escMsg.rows = 2;
244
+ escMsg.placeholder = this.t("yourMessage");
245
+ const escSend = document.createElement("button");
246
+ escSend.type = "button";
247
+ escSend.textContent = this.t("send");
248
+ esc.appendChild(escEmail);
249
+ esc.appendChild(escMsg);
250
+ esc.appendChild(escSend);
251
+ esc.querySelector("button")?.addEventListener("click", () => this.submitEscalation(esc));
252
+ this.windowEl.appendChild(esc);
253
+ const inputRow = document.createElement("div");
254
+ inputRow.className = "bb-inputrow";
255
+ this.inputEl = document.createElement("input");
256
+ this.inputEl.className = "bb-input";
257
+ this.inputEl.placeholder = this.t("placeholder");
258
+ this.inputEl.addEventListener("keydown", (e) => {
259
+ if (e.key === "Enter") this.send(this.inputEl?.value ?? "");
260
+ });
261
+ const sendBtn = document.createElement("button");
262
+ sendBtn.className = "bb-send";
263
+ sendBtn.textContent = "\u27A4";
264
+ sendBtn.addEventListener("click", () => this.send(this.inputEl?.value ?? ""));
265
+ inputRow.appendChild(this.inputEl);
266
+ inputRow.appendChild(sendBtn);
267
+ this.windowEl.appendChild(inputRow);
268
+ const launcher = document.createElement("button");
269
+ launcher.className = "bb-launcher";
270
+ launcher.setAttribute("aria-label", name);
271
+ if (this.settings.avatarUrl && isSafeUrl(this.settings.avatarUrl)) {
272
+ const img = document.createElement("img");
273
+ img.src = this.settings.avatarUrl;
274
+ img.alt = "";
275
+ launcher.appendChild(img);
276
+ } else {
277
+ launcher.textContent = "\u{1F4AC}";
278
+ }
279
+ launcher.addEventListener("click", () => this.opened ? this.close() : this.open());
280
+ rootEl.appendChild(launcher);
281
+ target.appendChild(this.host);
282
+ }
283
+ open() {
284
+ if (!this.windowEl || this.opened) return;
285
+ this.opened = true;
286
+ this.windowEl.classList.add("open");
287
+ if (this.messagesEl && this.messagesEl.childElementCount === 0) {
288
+ void this.primeThread();
289
+ }
290
+ this.inputEl?.focus();
291
+ }
292
+ close() {
293
+ this.opened = false;
294
+ this.windowEl?.classList.remove("open");
295
+ }
296
+ /** First open: restore the server thread, or show the greeting. */
297
+ async primeThread() {
298
+ if (this.conversationId && this.sessionId) {
299
+ try {
300
+ const res = await fetch(
301
+ `${this.baseUrl}/api/storefront-bot/${encodeURIComponent(this.connectionId)}/conversations/${encodeURIComponent(this.conversationId)}?limit=50`,
302
+ { headers: { "X-Bot-Session": this.sessionId } }
303
+ );
304
+ if (res.ok) {
305
+ const data = await res.json();
306
+ for (const m of data.data) {
307
+ this.appendMessage(m.role === "assistant" ? "bot" : "user", m.content);
308
+ }
309
+ if (data.data.length > 0) {
310
+ this.chipsEl?.remove();
311
+ return;
312
+ }
313
+ } else {
314
+ this.conversationId = null;
315
+ this.sessionId = null;
316
+ this.persistIds();
317
+ }
318
+ } catch {
319
+ }
320
+ }
321
+ if (this.settings.greeting) this.appendMessage("bot", this.settings.greeting);
322
+ }
323
+ // --------------------------------------------------------------------------
324
+ // The chat turn
325
+ // --------------------------------------------------------------------------
326
+ async send(text) {
327
+ const message = text.trim();
328
+ if (!message || this.busy) return;
329
+ this.busy = true;
330
+ if (this.inputEl) this.inputEl.value = "";
331
+ this.chipsEl?.remove();
332
+ this.appendMessage("user", message);
333
+ const typing = this.appendTyping();
334
+ let botBubble = null;
335
+ try {
336
+ const res = await fetch(
337
+ `${this.baseUrl}/api/storefront-bot/${encodeURIComponent(this.connectionId)}/chat`,
338
+ {
339
+ method: "POST",
340
+ headers: { "Content-Type": "application/json" },
341
+ body: JSON.stringify({
342
+ message,
343
+ turnId: randomId("trn_"),
344
+ ...this.conversationId ? { conversationId: this.conversationId } : {},
345
+ ...this.sessionId ? { anonymousSessionId: this.sessionId } : {},
346
+ locale: this.locale
347
+ })
348
+ }
349
+ );
350
+ if (!res.ok || !res.body) {
351
+ throw new Error(`chat failed (${res.status})`);
352
+ }
353
+ const reader = res.body.getReader();
354
+ const decoder = new TextDecoder();
355
+ let buffer = "";
356
+ for (; ; ) {
357
+ const { value, done } = await reader.read();
358
+ if (done) break;
359
+ buffer += decoder.decode(value, { stream: true });
360
+ let idx;
361
+ while ((idx = buffer.indexOf("\n\n")) >= 0) {
362
+ const raw = buffer.slice(0, idx);
363
+ buffer = buffer.slice(idx + 2);
364
+ if (!raw.startsWith("data: ")) continue;
365
+ let frame;
366
+ try {
367
+ frame = JSON.parse(raw.slice(6));
368
+ } catch {
369
+ continue;
370
+ }
371
+ botBubble = this.handleFrame(frame, typing, botBubble);
372
+ }
373
+ }
374
+ } catch {
375
+ this.appendMessage("err", this.t("error"));
376
+ } finally {
377
+ typing.remove();
378
+ this.busy = false;
379
+ }
380
+ }
381
+ handleFrame(frame, typing, botBubble) {
382
+ switch (frame.type) {
383
+ case "connected":
384
+ this.conversationId = frame.conversationId || this.conversationId;
385
+ this.sessionId = frame.anonymousSessionId || this.sessionId;
386
+ this.persistIds();
387
+ return botBubble;
388
+ case "token": {
389
+ if (!botBubble) {
390
+ typing.remove();
391
+ botBubble = this.appendMessage("bot", "");
392
+ }
393
+ botBubble.textContent = (botBubble.textContent ?? "") + frame.text;
394
+ this.scrollDown();
395
+ return botBubble;
396
+ }
397
+ case "tool":
398
+ typing.textContent = frame.status === "running" ? "\u22EF" : "";
399
+ return botBubble;
400
+ case "card":
401
+ this.appendCard(frame.card);
402
+ return botBubble;
403
+ case "error":
404
+ this.appendMessage("err", frame.message || this.t("error"));
405
+ return botBubble;
406
+ case "done":
407
+ default:
408
+ return botBubble;
409
+ }
410
+ }
411
+ // --------------------------------------------------------------------------
412
+ // Escalation + attribution
413
+ // --------------------------------------------------------------------------
414
+ toggleEscalation() {
415
+ this.root?.querySelector(".bb-esc")?.classList.toggle("open");
416
+ }
417
+ async submitEscalation(form) {
418
+ const email = form.querySelector('input[name="email"]')?.value.trim();
419
+ const message = form.querySelector('textarea[name="message"]')?.value.trim();
420
+ if (!email || !message || !this.conversationId || !this.sessionId) return;
421
+ try {
422
+ const res = await fetch(
423
+ `${this.baseUrl}/api/storefront-bot/${encodeURIComponent(this.connectionId)}/escalate`,
424
+ {
425
+ method: "POST",
426
+ headers: { "Content-Type": "application/json" },
427
+ body: JSON.stringify({
428
+ email,
429
+ message,
430
+ conversationId: this.conversationId,
431
+ anonymousSessionId: this.sessionId,
432
+ locale: this.locale
433
+ })
434
+ }
435
+ );
436
+ if (res.ok) {
437
+ const note = document.createElement("span");
438
+ note.className = "bb-esc-note";
439
+ note.textContent = this.t("sent");
440
+ form.replaceChildren(note);
441
+ }
442
+ } catch {
443
+ }
444
+ }
445
+ appendCard(card) {
446
+ if (!this.messagesEl) return;
447
+ const a = document.createElement("a");
448
+ a.className = "bb-card";
449
+ a.href = isSafeUrl(card.url) ? card.url : "#";
450
+ if (card.imageUrl && isSafeUrl(card.imageUrl)) {
451
+ const img = document.createElement("img");
452
+ img.src = card.imageUrl;
453
+ img.alt = "";
454
+ a.appendChild(img);
455
+ }
456
+ const body = document.createElement("span");
457
+ body.className = "bb-card-body";
458
+ const title = document.createElement("p");
459
+ title.className = "bb-card-title";
460
+ title.textContent = card.title;
461
+ const priceEl = document.createElement("span");
462
+ priceEl.className = "bb-card-price";
463
+ priceEl.textContent = card.price.formatted;
464
+ body.appendChild(title);
465
+ body.appendChild(priceEl);
466
+ a.appendChild(body);
467
+ a.addEventListener("click", () => {
468
+ try {
469
+ navigator.sendBeacon?.(
470
+ `${this.baseUrl}/api/storefront-bot/attribution/click`,
471
+ new Blob([JSON.stringify({ botRef: card.botRef })], { type: "application/json" })
472
+ );
473
+ } catch {
474
+ }
475
+ });
476
+ this.messagesEl.appendChild(a);
477
+ this.scrollDown();
478
+ }
479
+ // --------------------------------------------------------------------------
480
+ appendMessage(kind, text) {
481
+ const el = document.createElement("div");
482
+ el.className = `bb-msg ${kind}`;
483
+ el.textContent = text;
484
+ this.messagesEl?.appendChild(el);
485
+ this.scrollDown();
486
+ return el;
487
+ }
488
+ appendTyping() {
489
+ const el = document.createElement("div");
490
+ el.className = "bb-typing";
491
+ el.textContent = "\u22EF";
492
+ this.messagesEl?.appendChild(el);
493
+ this.scrollDown();
494
+ return el;
495
+ }
496
+ scrollDown() {
497
+ if (this.messagesEl) this.messagesEl.scrollTop = this.messagesEl.scrollHeight;
498
+ }
499
+ };
500
+ function isSafeUrl(url) {
501
+ return /^\/(?!\/)/.test(url) || /^https?:\/\//i.test(url);
502
+ }
503
+ export {
504
+ BrainerceBot
505
+ };
package/dist/index.d.mts CHANGED
@@ -3807,6 +3807,35 @@ interface PublicRegionPaymentProvider {
3807
3807
  interface PublicRegionDetail extends PublicRegion {
3808
3808
  paymentProviders: PublicRegionPaymentProvider[];
3809
3809
  }
3810
+ /**
3811
+ * Server-side region resolution for a buyer country (storefront seam).
3812
+ * `matched=true` → the country was explicitly listed by a region. `false` →
3813
+ * fell back to the default region (or `region=null` when no default exists).
3814
+ */
3815
+ interface AutoRegionResponse {
3816
+ region: PublicRegion | null;
3817
+ matched: boolean;
3818
+ /** Upper-cased ISO-3166-1 alpha-2. Empty string when caller omitted `country`. */
3819
+ country: string;
3820
+ }
3821
+ /**
3822
+ * Non-binding tax preview for PDP / PLP / cart. The authoritative calculation
3823
+ * still runs at checkout against the full shipping address (state / postal /
3824
+ * per-class). Render with an "Estimate" affordance.
3825
+ */
3826
+ interface TaxEstimateResponse {
3827
+ appliesTax: boolean;
3828
+ /** Percent — e.g. 18 for 18%. `null` when no matching rule. */
3829
+ rate: number | null;
3830
+ rateName: string | null;
3831
+ /** Tax portion of `subtotal` at the store's `pricesIncludeTax` mode. */
3832
+ estimatedTax: number;
3833
+ pricesIncludeTax: boolean;
3834
+ /** Store currency — the cart / order currency, unchanged by FX display overlay. */
3835
+ currency: string;
3836
+ /** Disclaimer copy — always present to surface the "preview, not binding" nature. */
3837
+ note: string;
3838
+ }
3810
3839
  interface CreateRegionDto {
3811
3840
  name: string;
3812
3841
  /** ISO 4217 */
@@ -8616,6 +8645,33 @@ declare class BrainerceClient {
8616
8645
  * `countries` includes `country`, else the default region, else null.
8617
8646
  */
8618
8647
  detectRegion(country: string, regions: Array<Region | PublicRegion>): Region | PublicRegion | null;
8648
+ /**
8649
+ * Server-side region resolution in one round trip (storefront seam). Pair
8650
+ * with the country your edge runtime extracts — Cloudflare `CF-IPCountry`,
8651
+ * Vercel `request.geo?.country`, Fastly `client-geo-country`, etc.
8652
+ *
8653
+ * Brainerce does NOT derive the country from the request IP server-side
8654
+ * because the storefront server is what reaches the backend (not the end-
8655
+ * customer). The storefront must extract + forward the country.
8656
+ *
8657
+ * Returns `{ region, matched, country }` — `matched=true` means the country
8658
+ * was explicitly listed by a region; `false` means we fell back to the
8659
+ * default region (or `region=null` if no default exists either).
8660
+ */
8661
+ getAutoRegion(country?: string): Promise<AutoRegionResponse>;
8662
+ /**
8663
+ * Non-binding tax preview for PDP / PLP / cart. Pass the buyer's country
8664
+ * (from your edge runtime — same source as `getAutoRegion`) and the current
8665
+ * items subtotal in the store currency. The authoritative tax still runs
8666
+ * at checkout — render with an "Estimate" affordance.
8667
+ *
8668
+ * Returns `appliesTax=false` when tax is disabled, the country is missing,
8669
+ * or no active rate covers it.
8670
+ */
8671
+ estimateTax(params: {
8672
+ country?: string;
8673
+ subtotal: number;
8674
+ }): Promise<TaxEstimateResponse>;
8619
8675
  /**
8620
8676
  * List the store's tax classes (public, no apiKey — storeId mode). Storefront-
8621
8677
  * safe fields only (id/name/slug/description/isDefault) for transparency UIs
@@ -9245,4 +9301,41 @@ declare function isAllowedPaymentUrl(url: string, options?: PaymentUrlOptions):
9245
9301
  */
9246
9302
  declare function safePaymentRedirect(url: string, options?: PaymentUrlOptions): void;
9247
9303
 
9248
- export { type AddToCartDto, type AnnouncementContent, type AnnouncementSeverity, type AppliedDiscount, type ApplyCouponDto, type AttachModifierGroupInput, type Attribute, type AttributeOption, type AttributeSource, type BrainerceApiError, BrainerceClient, type BrainerceClientOptions, BrainerceError, type Brand, type BulkInventoryResponse, type BulkSaveVariantsDto, type BulkSaveVariantsResponse, type BulkVariantInput, type Cart, type CartAppliedDiscount, type CartBundleOffer, type CartBundlesResponse, type CartIncludeOption, type CartIncludeOptions, type CartItem, type CartItemModifierLine, type CartNudge, type CartRecommendationsResponse, type CartStatus, type CartUpgradeSuggestion, type CartUpgradesResponse, type CartWithIncludes, type Category, type CategoryNode, type CategorySuggestion, type Checkout, type CheckoutAddress, type CheckoutBumpsResponse, type CheckoutCustomFieldDefinition, type CheckoutFieldPricing, type CheckoutFieldVisibility, type CheckoutLineItem, type CheckoutPrefillData, type CheckoutStatus, type CompleteCheckoutResponse, type CompleteDraftDto, type ConfigureOAuthProviderDto as ConfigureOAuthProviderInput, type ConflictStatus, type ConnectorPlatform, type ContactFormFieldType, type ContactFormFieldValidation, type ContactFormPublic, type ContactFormPublicField, type ContactFormSummary, type Content, type ContentDataMap, type ContentStatus, type ContentSummary, type ContentType, type Coupon, type CouponCreateResponse, type CouponQueryParams, type CouponStatus, type CouponType, type CouponValidationWarning, type CreateAddressDto, type CreateAttributeDto as CreateAttributeInput, type CreateAttributeOptionDto as CreateAttributeOptionInput, type CreateBrandDto as CreateBrandInput, type CreateCategoryDto as CreateCategoryInput, type CreateCheckoutDto, type CreateContentInput, type CreateCouponDto, type CreateCustomApiDto, type CreateCustomerDto, type CreateEmailTemplateDto as CreateEmailTemplateInput, type CreateGuestOrderDto, type CreateInquiryInput, type CreateInquiryResponse, type CreateMetafieldDefinitionDto as CreateMetafieldDefinitionInput, type CreateModifierGroupInput, type CreateModifierInput, type CreateOrderDto, type CreateProductDto, type CreateRefundDto, type CreateShippingRateDto as CreateShippingRateInput, type CreateShippingZoneDto as CreateShippingZoneInput, type CreateTagDto as CreateTagInput, type CreateTaxRateDto as CreateTaxRateInput, type CreateVariantDto, type CustomApiAuthType, type CustomApiConnectionStatus, type CustomApiCredentials, type CustomApiIntegration, type CustomApiSyncConfig, type CustomApiSyncDirection, type CustomApiTestResult, type Customer, type CustomerAddress, type CustomerAuthResponse, type CustomerOAuthProvider, type CustomerProfile, type CustomerQueryParams, type DeleteProductResponse, type DiscountBanner, type DiscountRuleType, type DownloadFile, type DraftLineItem, type EditInventoryDto, type EmailDomain, type EmailEventSettings, type EmailEventType, type EmailSettings, type EmailTemplate, type EmailTemplatePreview, type EmailTemplatesResponse, type EmailVerificationResponse, type ExtendReservationResponse, type FaqContent, type FaqItem, type FooterColumn, type FooterContent, type FooterLink, type FooterSocialLink, type FormatPriceOptions, type FreeAllocationPolicy, type FulfillOrderDto, type GuestCheckoutStartResponse, type GuestOrderResponse, type HeaderContent, type HeaderCta, type HeaderLogo, type HeaderNavItem, type InsufficientStockError, type InventoryInfo, type InventoryReservationStrategy, type InventorySyncStatus, type InventoryTrackingMode, type InvitationStatus, type InviteMemberDto as InviteMemberInput, type InviteStoreMemberDto as InviteStoreMemberInput, type ListModifierGroupsParams, type LocalCart, type LocalCartItem, type LockedVariant, type MergeCartsDto, type MetafieldConflict, type MetafieldConflictResolution, type MetafieldDefinition, type MetafieldType, type Modifier, type ModifierGroup, type ModifierSelection, type ModifierSelectionType, type ModifierValidationCode, type ModifierValidationError, type OAuthAuthorizeResponse, type OAuthCallbackResponse, type OAuthConnection, type OAuthConnectionsResponse, type OAuthProviderConfig, type OAuthProviderType, type OAuthProvidersResponse, type Order, type OrderAddress, type OrderBump, type OrderCustomer, type OrderDownloadLink, type OrderItem, type OrderQueryParams, type OrderStatus, type OrderStatusChange, type PageContent, type PageSeo, type PaginatedResponse, type PaymentClientSdk, type PaymentConfig, type PaymentIntent, type PaymentProvider, type PaymentProviderConfig, type PaymentProvidersConfig, type PaymentStatus, type PaymentUrlOptions, type PickupLocation, type PlatformCouponCapabilities, type PlatformMetafieldMetadata, type PreviewEmailTemplateDto as PreviewEmailTemplateInput, type Product, type ProductAttributeInput, type ProductAvailability, type ProductCustomizationField, type ProductDiscount, type ProductDiscountBadge, type ProductImage, type ProductMetafield, type ProductMetafieldValue, type ProductModifierGroupAttachment, type ProductQueryParams, type ProductRecommendation, type ProductRecommendationsResponse, type ProductRelationType, type ProductSuggestion, type ProductVariant, type PublicMetafieldDefinition, type PublishProductResponse, RTL_LOCALES, type RecommendationVariant, type ReconcileInventoryResponse, type Refund, type RefundLineItem, type RefundLineItemResponse, type RefundType, type RegisterCustomerDto, type ReservationInfo, type ResolveMetafieldConflictDto as ResolveMetafieldConflictInput, type ResolveSyncConflictDto as ResolveSyncConflictInput, type RichTextContent, SDK_VERSION, type SearchSuggestions, type SelectPickupLocationDto, type SelectShippingMethodDto, type SendInvoiceDto, type SessionCartRef, type SetBillingAddressDto, type SetCheckoutCustomFieldsDto, type SetCheckoutCustomerDto, type SetDefinitionProductsDto as SetDefinitionProductsInput, type SetMetafieldPlatformsDto as SetMetafieldPlatformsInput, type SetShippingAddressDto, type SetShippingAddressResponse, type ShippingDestinations, type ShippingLine, type ShippingRate, type ShippingRateConfig, type ShippingRateType, type ShippingZone, type ShippingZoneQueryParams, type StockAvailabilityRequest, type StockAvailabilityResponse, type StockAvailabilityResult, type StoreInfo, type StoreInvitation, type StoreInvitationDetails, type StoreMember, type StorePermission, type StoreRole, type StoreTeamResponse, type SupportedLocaleObject, type SyncConflict, type SyncConflictResolution, type SyncJob, type Tag, type TaxBreakdown, type TaxBreakdownItem, type TaxRate, type TaxonomyQueryParams, type TeamInvitation, type TeamInvitationsResponse, type TeamMember, type TeamMembersResponse, type TeamRole, type UpdateAddressDto, type UpdateAttachmentInput, type UpdateAttributeDto as UpdateAttributeInput, type UpdateAttributeOptionDto as UpdateAttributeOptionInput, type UpdateBrandDto as UpdateBrandInput, type UpdateCartItemDto, type UpdateCategoryDto as UpdateCategoryInput, type UpdateContentInput, type UpdateCouponDto, type UpdateCustomApiDto, type UpdateCustomerDto, type UpdateDraftDto, type UpdateEmailSettingsDto as UpdateEmailSettingsInput, type UpdateEmailTemplateDto as UpdateEmailTemplateInput, type UpdateInventoryDto, type UpdateMemberRoleDto as UpdateMemberRoleInput, type UpdateMetafieldDefinitionDto as UpdateMetafieldDefinitionInput, type UpdateModifierGroupInput, type UpdateModifierInput, type UpdateOAuthProviderDto as UpdateOAuthProviderInput, type UpdateOrderDto, type UpdateOrderShippingDto, type UpdateProductDto, type UpdateShippingRateDto as UpdateShippingRateInput, type UpdateShippingZoneDto as UpdateShippingZoneInput, type UpdateStoreMemberDto as UpdateStoreMemberInput, type UpdateTagDto as UpdateTagInput, type UpdateTaxRateDto as UpdateTaxRateInput, type UpdateVariantDto, type UpdateVariantInventoryDto, type UpsertProductMetafieldDto as UpsertProductMetafieldInput, type UserStore, type UserStorePermissions, type VariantInventoryResponse, type VariantPlatformOverlay, type VariantStatus, type WaitForOrderOptions, type WaitForOrderResult, type WebhookEvent, type WebhookEventType, createWebhookHandler, deriveSeoDescription, enableDevGuards, formatPrice, getCartItemImage, getCartItemName, getCartTotals, getDescriptionContent, getDirectionForLocale, formatPrice as getPriceDisplay, getProductCustomizationFields, getProductMetafield, getProductMetafieldValue, getProductMetafieldsByType, getProductPrice, getProductPriceInfo, getProductSwatches, getStockStatus, getVariantOptions, getVariantPrice, isAllowedPaymentUrl, isCouponApplicableToProduct, isHtmlDescription, isWebhookEventType, parseWebhookEvent, safePaymentRedirect, stripHtml, verifyWebhook };
9304
+ interface FormatProductPriceOptions {
9305
+ /**
9306
+ * BCP-47 locale for `Intl.NumberFormat`. Controls number grouping and
9307
+ * currency-symbol placement. Defaults to the browser locale (client) or
9308
+ * `"en"` (SSR / Node).
9309
+ */
9310
+ locale?: string;
9311
+ /**
9312
+ * When the product has both a regular price and a sale price, which one to
9313
+ * format. Defaults to `"sale-if-available"` — i.e. prefer the discounted
9314
+ * price when set, fall back to the regular price. Pass `"base"` to always
9315
+ * format the regular price (useful for crossed-out display alongside sale).
9316
+ */
9317
+ prefer?: 'sale-if-available' | 'base';
9318
+ /**
9319
+ * Fallback ISO 4217 currency to use when a product has no
9320
+ * `displayCurrency` (i.e. the read wasn't region-scoped or the buyer is in
9321
+ * the default region). Usually the store's `currency` from `getStoreInfo()`.
9322
+ * If omitted, the helper returns just the numeric amount (no symbol).
9323
+ */
9324
+ storeCurrency?: string;
9325
+ }
9326
+ /**
9327
+ * Format a product's price for display. Picks the region-converted
9328
+ * `displayPrice` / `displayCurrency` when present (set by the backend when
9329
+ * the read was region-scoped); otherwise falls back to the canonical
9330
+ * `basePrice` / `salePrice` in the store currency.
9331
+ *
9332
+ * Returns a localized string like `"€97.76"` or `"₪399.00"`. Returns `null`
9333
+ * when no price is available (e.g. a draft product with no base price yet).
9334
+ */
9335
+ declare function formatProductPrice(product: Pick<Product, 'basePrice' | 'salePrice' | 'displayPrice' | 'displaySalePrice' | 'displayCurrency'>, options?: FormatProductPriceOptions): string | null;
9336
+ /** Same as `formatProductPrice` but for a `ProductVariant`. */
9337
+ declare function formatVariantPrice(variant: Pick<ProductVariant, 'price' | 'salePrice' | 'displayPrice' | 'displaySalePrice' | 'displayCurrency'>, options?: FormatProductPriceOptions): string | null;
9338
+ /** Format any numeric amount as currency. Useful for cart totals, fees, etc. */
9339
+ declare function formatMoney(amount: number, currency: string, locale?: string): string;
9340
+
9341
+ export { type AddToCartDto, type AnnouncementContent, type AnnouncementSeverity, type AppliedDiscount, type ApplyCouponDto, type AttachModifierGroupInput, type Attribute, type AttributeOption, type AttributeSource, type BrainerceApiError, BrainerceClient, type BrainerceClientOptions, BrainerceError, type Brand, type BulkInventoryResponse, type BulkSaveVariantsDto, type BulkSaveVariantsResponse, type BulkVariantInput, type Cart, type CartAppliedDiscount, type CartBundleOffer, type CartBundlesResponse, type CartIncludeOption, type CartIncludeOptions, type CartItem, type CartItemModifierLine, type CartNudge, type CartRecommendationsResponse, type CartStatus, type CartUpgradeSuggestion, type CartUpgradesResponse, type CartWithIncludes, type Category, type CategoryNode, type CategorySuggestion, type Checkout, type CheckoutAddress, type CheckoutBumpsResponse, type CheckoutCustomFieldDefinition, type CheckoutFieldPricing, type CheckoutFieldVisibility, type CheckoutLineItem, type CheckoutPrefillData, type CheckoutStatus, type CompleteCheckoutResponse, type CompleteDraftDto, type ConfigureOAuthProviderDto as ConfigureOAuthProviderInput, type ConflictStatus, type ConnectorPlatform, type ContactFormFieldType, type ContactFormFieldValidation, type ContactFormPublic, type ContactFormPublicField, type ContactFormSummary, type Content, type ContentDataMap, type ContentStatus, type ContentSummary, type ContentType, type Coupon, type CouponCreateResponse, type CouponQueryParams, type CouponStatus, type CouponType, type CouponValidationWarning, type CreateAddressDto, type CreateAttributeDto as CreateAttributeInput, type CreateAttributeOptionDto as CreateAttributeOptionInput, type CreateBrandDto as CreateBrandInput, type CreateCategoryDto as CreateCategoryInput, type CreateCheckoutDto, type CreateContentInput, type CreateCouponDto, type CreateCustomApiDto, type CreateCustomerDto, type CreateEmailTemplateDto as CreateEmailTemplateInput, type CreateGuestOrderDto, type CreateInquiryInput, type CreateInquiryResponse, type CreateMetafieldDefinitionDto as CreateMetafieldDefinitionInput, type CreateModifierGroupInput, type CreateModifierInput, type CreateOrderDto, type CreateProductDto, type CreateRefundDto, type CreateShippingRateDto as CreateShippingRateInput, type CreateShippingZoneDto as CreateShippingZoneInput, type CreateTagDto as CreateTagInput, type CreateTaxRateDto as CreateTaxRateInput, type CreateVariantDto, type CustomApiAuthType, type CustomApiConnectionStatus, type CustomApiCredentials, type CustomApiIntegration, type CustomApiSyncConfig, type CustomApiSyncDirection, type CustomApiTestResult, type Customer, type CustomerAddress, type CustomerAuthResponse, type CustomerOAuthProvider, type CustomerProfile, type CustomerQueryParams, type DeleteProductResponse, type DiscountBanner, type DiscountRuleType, type DownloadFile, type DraftLineItem, type EditInventoryDto, type EmailDomain, type EmailEventSettings, type EmailEventType, type EmailSettings, type EmailTemplate, type EmailTemplatePreview, type EmailTemplatesResponse, type EmailVerificationResponse, type ExtendReservationResponse, type FaqContent, type FaqItem, type FooterColumn, type FooterContent, type FooterLink, type FooterSocialLink, type FormatPriceOptions, type FormatProductPriceOptions, type FreeAllocationPolicy, type FulfillOrderDto, type GuestCheckoutStartResponse, type GuestOrderResponse, type HeaderContent, type HeaderCta, type HeaderLogo, type HeaderNavItem, type InsufficientStockError, type InventoryInfo, type InventoryReservationStrategy, type InventorySyncStatus, type InventoryTrackingMode, type InvitationStatus, type InviteMemberDto as InviteMemberInput, type InviteStoreMemberDto as InviteStoreMemberInput, type ListModifierGroupsParams, type LocalCart, type LocalCartItem, type LockedVariant, type MergeCartsDto, type MetafieldConflict, type MetafieldConflictResolution, type MetafieldDefinition, type MetafieldType, type Modifier, type ModifierGroup, type ModifierSelection, type ModifierSelectionType, type ModifierValidationCode, type ModifierValidationError, type OAuthAuthorizeResponse, type OAuthCallbackResponse, type OAuthConnection, type OAuthConnectionsResponse, type OAuthProviderConfig, type OAuthProviderType, type OAuthProvidersResponse, type Order, type OrderAddress, type OrderBump, type OrderCustomer, type OrderDownloadLink, type OrderItem, type OrderQueryParams, type OrderStatus, type OrderStatusChange, type PageContent, type PageSeo, type PaginatedResponse, type PaymentClientSdk, type PaymentConfig, type PaymentIntent, type PaymentProvider, type PaymentProviderConfig, type PaymentProvidersConfig, type PaymentStatus, type PaymentUrlOptions, type PickupLocation, type PlatformCouponCapabilities, type PlatformMetafieldMetadata, type PreviewEmailTemplateDto as PreviewEmailTemplateInput, type Product, type ProductAttributeInput, type ProductAvailability, type ProductCustomizationField, type ProductDiscount, type ProductDiscountBadge, type ProductImage, type ProductMetafield, type ProductMetafieldValue, type ProductModifierGroupAttachment, type ProductQueryParams, type ProductRecommendation, type ProductRecommendationsResponse, type ProductRelationType, type ProductSuggestion, type ProductVariant, type PublicMetafieldDefinition, type PublishProductResponse, RTL_LOCALES, type RecommendationVariant, type ReconcileInventoryResponse, type Refund, type RefundLineItem, type RefundLineItemResponse, type RefundType, type RegisterCustomerDto, type ReservationInfo, type ResolveMetafieldConflictDto as ResolveMetafieldConflictInput, type ResolveSyncConflictDto as ResolveSyncConflictInput, type RichTextContent, SDK_VERSION, type SearchSuggestions, type SelectPickupLocationDto, type SelectShippingMethodDto, type SendInvoiceDto, type SessionCartRef, type SetBillingAddressDto, type SetCheckoutCustomFieldsDto, type SetCheckoutCustomerDto, type SetDefinitionProductsDto as SetDefinitionProductsInput, type SetMetafieldPlatformsDto as SetMetafieldPlatformsInput, type SetShippingAddressDto, type SetShippingAddressResponse, type ShippingDestinations, type ShippingLine, type ShippingRate, type ShippingRateConfig, type ShippingRateType, type ShippingZone, type ShippingZoneQueryParams, type StockAvailabilityRequest, type StockAvailabilityResponse, type StockAvailabilityResult, type StoreInfo, type StoreInvitation, type StoreInvitationDetails, type StoreMember, type StorePermission, type StoreRole, type StoreTeamResponse, type SupportedLocaleObject, type SyncConflict, type SyncConflictResolution, type SyncJob, type Tag, type TaxBreakdown, type TaxBreakdownItem, type TaxRate, type TaxonomyQueryParams, type TeamInvitation, type TeamInvitationsResponse, type TeamMember, type TeamMembersResponse, type TeamRole, type UpdateAddressDto, type UpdateAttachmentInput, type UpdateAttributeDto as UpdateAttributeInput, type UpdateAttributeOptionDto as UpdateAttributeOptionInput, type UpdateBrandDto as UpdateBrandInput, type UpdateCartItemDto, type UpdateCategoryDto as UpdateCategoryInput, type UpdateContentInput, type UpdateCouponDto, type UpdateCustomApiDto, type UpdateCustomerDto, type UpdateDraftDto, type UpdateEmailSettingsDto as UpdateEmailSettingsInput, type UpdateEmailTemplateDto as UpdateEmailTemplateInput, type UpdateInventoryDto, type UpdateMemberRoleDto as UpdateMemberRoleInput, type UpdateMetafieldDefinitionDto as UpdateMetafieldDefinitionInput, type UpdateModifierGroupInput, type UpdateModifierInput, type UpdateOAuthProviderDto as UpdateOAuthProviderInput, type UpdateOrderDto, type UpdateOrderShippingDto, type UpdateProductDto, type UpdateShippingRateDto as UpdateShippingRateInput, type UpdateShippingZoneDto as UpdateShippingZoneInput, type UpdateStoreMemberDto as UpdateStoreMemberInput, type UpdateTagDto as UpdateTagInput, type UpdateTaxRateDto as UpdateTaxRateInput, type UpdateVariantDto, type UpdateVariantInventoryDto, type UpsertProductMetafieldDto as UpsertProductMetafieldInput, type UserStore, type UserStorePermissions, type VariantInventoryResponse, type VariantPlatformOverlay, type VariantStatus, type WaitForOrderOptions, type WaitForOrderResult, type WebhookEvent, type WebhookEventType, createWebhookHandler, deriveSeoDescription, enableDevGuards, formatMoney, formatPrice, formatProductPrice, formatVariantPrice, getCartItemImage, getCartItemName, getCartTotals, getDescriptionContent, getDirectionForLocale, formatPrice as getPriceDisplay, getProductCustomizationFields, getProductMetafield, getProductMetafieldValue, getProductMetafieldsByType, getProductPrice, getProductPriceInfo, getProductSwatches, getStockStatus, getVariantOptions, getVariantPrice, isAllowedPaymentUrl, isCouponApplicableToProduct, isHtmlDescription, isWebhookEventType, parseWebhookEvent, safePaymentRedirect, stripHtml, verifyWebhook };