@kavchat/widget 0.0.1 → 0.0.3
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/README.md +39 -4
- package/dist/index.mjs +896 -121
- package/dist/kavchat-live.js +20 -0
- package/dist/kavchat.js +8 -8
- package/dist/types/live-client.d.ts +85 -0
- package/dist/types/live-entry.d.ts +1 -0
- package/dist/types/live-load-cdn.d.ts +2 -0
- package/dist/types/live-load.d.ts +5 -0
- package/dist/types/live-styles.d.ts +5 -0
- package/dist/types/live.d.ts +67 -0
- package/dist/types/styles.d.ts +4 -2
- package/dist/types/widget.d.ts +13 -0
- package/package.json +2 -2
package/dist/index.mjs
CHANGED
|
@@ -1,3 +1,839 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
3
|
+
var __esm = (fn, res, err) => function __init() {
|
|
4
|
+
if (err) throw err[0];
|
|
5
|
+
try {
|
|
6
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
7
|
+
} catch (e) {
|
|
8
|
+
throw err = [e], e;
|
|
9
|
+
}
|
|
10
|
+
};
|
|
11
|
+
var __export = (target, all) => {
|
|
12
|
+
for (var name in all)
|
|
13
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
// src/dom.ts
|
|
17
|
+
function h(tag, attrs, ...children) {
|
|
18
|
+
const el = document.createElement(tag);
|
|
19
|
+
if (attrs) {
|
|
20
|
+
for (const [k, v] of Object.entries(attrs)) {
|
|
21
|
+
if (v === void 0 || v === null || v === false) continue;
|
|
22
|
+
el.setAttribute(k, v === true ? "" : v);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
append(el, children);
|
|
26
|
+
return el;
|
|
27
|
+
}
|
|
28
|
+
function append(el, children) {
|
|
29
|
+
for (const c of children) {
|
|
30
|
+
if (c === null || c === void 0 || c === false) continue;
|
|
31
|
+
el.appendChild(typeof c === "string" ? document.createTextNode(c) : c);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
function icon(d) {
|
|
35
|
+
const svg = document.createElementNS(SVG_NS, "svg");
|
|
36
|
+
svg.setAttribute("viewBox", "0 0 24 24");
|
|
37
|
+
svg.setAttribute("width", "24");
|
|
38
|
+
svg.setAttribute("height", "24");
|
|
39
|
+
svg.setAttribute("aria-hidden", "true");
|
|
40
|
+
svg.setAttribute("focusable", "false");
|
|
41
|
+
const path = document.createElementNS(SVG_NS, "path");
|
|
42
|
+
path.setAttribute("d", d);
|
|
43
|
+
path.setAttribute("fill", "currentColor");
|
|
44
|
+
svg.appendChild(path);
|
|
45
|
+
return svg;
|
|
46
|
+
}
|
|
47
|
+
function safeUrl(raw, schemes) {
|
|
48
|
+
try {
|
|
49
|
+
const url = new URL(String(raw || "").trim());
|
|
50
|
+
return schemes.includes(url.protocol) ? url.href : null;
|
|
51
|
+
} catch (e) {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
function richText(text) {
|
|
56
|
+
const frag = document.createDocumentFragment();
|
|
57
|
+
let last = 0;
|
|
58
|
+
URL_RE.lastIndex = 0;
|
|
59
|
+
let m;
|
|
60
|
+
while (m = URL_RE.exec(text)) {
|
|
61
|
+
const raw = m[0].replace(TRAILING, "");
|
|
62
|
+
const href = safeUrl(raw, ["https:"]);
|
|
63
|
+
if (!href || !raw) continue;
|
|
64
|
+
if (m.index > last) frag.appendChild(document.createTextNode(text.slice(last, m.index)));
|
|
65
|
+
const a = h("a", { href, target: "_blank", rel: "noopener noreferrer" }, raw);
|
|
66
|
+
frag.appendChild(a);
|
|
67
|
+
last = m.index + raw.length;
|
|
68
|
+
URL_RE.lastIndex = last;
|
|
69
|
+
}
|
|
70
|
+
if (last < text.length) frag.appendChild(document.createTextNode(text.slice(last)));
|
|
71
|
+
return frag;
|
|
72
|
+
}
|
|
73
|
+
var SVG_NS, ICONS, URL_RE, TRAILING;
|
|
74
|
+
var init_dom = __esm({
|
|
75
|
+
"src/dom.ts"() {
|
|
76
|
+
"use strict";
|
|
77
|
+
SVG_NS = "http://www.w3.org/2000/svg";
|
|
78
|
+
ICONS = {
|
|
79
|
+
chat: "M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2z",
|
|
80
|
+
close: "M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z",
|
|
81
|
+
send: "M2.01 21 23 12 2.01 3 2 10l15 2-15 2z"
|
|
82
|
+
};
|
|
83
|
+
URL_RE = /https:\/\/[^\s<>"'`]+/g;
|
|
84
|
+
TRAILING = /[.,;:!?)\]}'"»]+$/;
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
// src/storage.ts
|
|
89
|
+
function read(store, key) {
|
|
90
|
+
try {
|
|
91
|
+
const raw = store().getItem(key);
|
|
92
|
+
return raw ? JSON.parse(raw) : null;
|
|
93
|
+
} catch (e) {
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
function write(store, key, value) {
|
|
98
|
+
try {
|
|
99
|
+
store().setItem(key, JSON.stringify(value));
|
|
100
|
+
} catch (e) {
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
function randomId(length = 22) {
|
|
104
|
+
const bytes = new Uint8Array(length);
|
|
105
|
+
if (typeof crypto !== "undefined" && crypto.getRandomValues) crypto.getRandomValues(bytes);
|
|
106
|
+
else for (let i = 0; i < length; i++) bytes[i] = Math.floor(Math.random() * 256);
|
|
107
|
+
let out = "";
|
|
108
|
+
for (let i = 0; i < length; i++) out += ID_CHARS[bytes[i] & 63];
|
|
109
|
+
return out;
|
|
110
|
+
}
|
|
111
|
+
var RELEASE_KEY, SESSION_KEY, SCREENS_KEY, local, session, readCachedRelease, writeCachedRelease, readScreensMark, writeScreensMark, readSession, writeSession, ID_CHARS;
|
|
112
|
+
var init_storage = __esm({
|
|
113
|
+
"src/storage.ts"() {
|
|
114
|
+
"use strict";
|
|
115
|
+
RELEASE_KEY = (pk) => `kavchat:release:${pk}`;
|
|
116
|
+
SESSION_KEY = (pk) => `kavchat:session:${pk}`;
|
|
117
|
+
SCREENS_KEY = (pk) => `kavchat:screens:${pk}`;
|
|
118
|
+
local = () => window.localStorage;
|
|
119
|
+
session = () => window.sessionStorage;
|
|
120
|
+
readCachedRelease = (pk) => read(local, RELEASE_KEY(pk));
|
|
121
|
+
writeCachedRelease = (pk, value) => write(local, RELEASE_KEY(pk), value);
|
|
122
|
+
readScreensMark = (pk) => read(local, SCREENS_KEY(pk));
|
|
123
|
+
writeScreensMark = (pk, value) => write(local, SCREENS_KEY(pk), value);
|
|
124
|
+
readSession = (pk) => read(session, SESSION_KEY(pk));
|
|
125
|
+
writeSession = (pk, value) => write(session, SESSION_KEY(pk), value);
|
|
126
|
+
ID_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-";
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
// src/live-client.ts
|
|
131
|
+
import {
|
|
132
|
+
LIVE_MESSAGE_MAX,
|
|
133
|
+
LIVE_TYPING_HIDE_MS,
|
|
134
|
+
LIVE_TYPING_INTERVAL_MS,
|
|
135
|
+
isLiveFinal,
|
|
136
|
+
liveAddPending,
|
|
137
|
+
liveApplyConversation,
|
|
138
|
+
liveApplyEvent,
|
|
139
|
+
liveApplySync,
|
|
140
|
+
liveBackoff,
|
|
141
|
+
liveSetPending,
|
|
142
|
+
liveStart
|
|
143
|
+
} from "@kavchat/core";
|
|
144
|
+
async function call(c, path, body) {
|
|
145
|
+
try {
|
|
146
|
+
const res = await fetch(liveUrl(c, path), {
|
|
147
|
+
method: body === void 0 ? "GET" : "POST",
|
|
148
|
+
cache: "no-store",
|
|
149
|
+
credentials: "omit",
|
|
150
|
+
...body === void 0 ? {} : { body: JSON.stringify(body) }
|
|
151
|
+
});
|
|
152
|
+
const b = await res.json().catch(() => null);
|
|
153
|
+
return { status: res.status, ok: res.ok && !!b && b.success === true, data: b && b.data, code: String(b && b.code || ""), message: String(b && b.message || "") };
|
|
154
|
+
} catch (e) {
|
|
155
|
+
return { status: 0, ok: false, data: null, code: "network", message: "" };
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
var liveUrl, livePost, liveGet, retryable, sleep, LIVE_IDLE_MS, BUSY_POLL_MS, LiveConnection;
|
|
159
|
+
var init_live_client = __esm({
|
|
160
|
+
"src/live-client.ts"() {
|
|
161
|
+
"use strict";
|
|
162
|
+
init_storage();
|
|
163
|
+
liveUrl = (c, path) => `${c.apiBase.replace(/\/+$/, "")}/v1/client/${encodeURIComponent(c.publicKey)}/live/${path}`;
|
|
164
|
+
livePost = (c, path, body) => call(c, path, body);
|
|
165
|
+
liveGet = (c, path) => call(c, path);
|
|
166
|
+
retryable = (r) => r.status === 0 || r.status === 429 || r.status >= 500;
|
|
167
|
+
sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
168
|
+
LIVE_IDLE_MS = 9e4;
|
|
169
|
+
BUSY_POLL_MS = 12e4;
|
|
170
|
+
LiveConnection = class {
|
|
171
|
+
constructor(api, credential, conversation, cb) {
|
|
172
|
+
this.api = api;
|
|
173
|
+
this.credential = credential;
|
|
174
|
+
this.cb = cb;
|
|
175
|
+
this.stopped = false;
|
|
176
|
+
this.random = Math.random;
|
|
177
|
+
this.es = null;
|
|
178
|
+
this.attempt = 0;
|
|
179
|
+
/** כישלונות זרם לפני ready, כשכרטיס הזרם התקבל: כנראה פרוקסי שחוסם SSE */
|
|
180
|
+
this.blocked = 0;
|
|
181
|
+
this.pollUntil = 0;
|
|
182
|
+
this.polling = false;
|
|
183
|
+
this.typingOn = false;
|
|
184
|
+
this.typingAt = 0;
|
|
185
|
+
this.numberTried = false;
|
|
186
|
+
this.onOnline = () => {
|
|
187
|
+
if (!this.es && !this.polling) this.schedule(0);
|
|
188
|
+
};
|
|
189
|
+
this.chat = liveStart(conversation);
|
|
190
|
+
window.addEventListener("online", this.onOnline);
|
|
191
|
+
}
|
|
192
|
+
get final() {
|
|
193
|
+
return isLiveFinal(this.chat.conversation.state);
|
|
194
|
+
}
|
|
195
|
+
body(extra) {
|
|
196
|
+
return { credential: this.credential, ...extra };
|
|
197
|
+
}
|
|
198
|
+
set(chat) {
|
|
199
|
+
if (chat === this.chat) return;
|
|
200
|
+
const before = this.chat;
|
|
201
|
+
this.chat = chat;
|
|
202
|
+
if (chat.events.length > before.events.length && chat.events.some((e) => e.type === "message" && e.author.kind === "agent" && !before.events.includes(e))) this.cb.typing(false);
|
|
203
|
+
this.cb.change(chat);
|
|
204
|
+
if (this.final) this.finish();
|
|
205
|
+
}
|
|
206
|
+
/** POST /live/sync עד hasMore=false. false: נכשל */
|
|
207
|
+
async sync() {
|
|
208
|
+
for (let i = 0; i < 50; i++) {
|
|
209
|
+
const r = await livePost(this.api, "sync", this.body({ after: this.chat.lastSeq }));
|
|
210
|
+
if (this.stopped) return false;
|
|
211
|
+
if (!r.ok) {
|
|
212
|
+
this.fail(r);
|
|
213
|
+
return false;
|
|
214
|
+
}
|
|
215
|
+
this.set(liveApplySync(this.chat, r.data));
|
|
216
|
+
if (!r.data.hasMore) return true;
|
|
217
|
+
}
|
|
218
|
+
return true;
|
|
219
|
+
}
|
|
220
|
+
/** קודים שמשנים את הזרימה (סעיף 2.5). true: טופל */
|
|
221
|
+
fail(r) {
|
|
222
|
+
if (r.code === "live_credential_invalid") {
|
|
223
|
+
if (!this.final) this.set(liveApplyConversation(this.chat, { ...this.chat.conversation, state: "closed", closeReason: this.chat.conversation.closeReason || "expired" }));
|
|
224
|
+
else this.finish();
|
|
225
|
+
return true;
|
|
226
|
+
}
|
|
227
|
+
if (r.code === "live_conversation_closed") {
|
|
228
|
+
void this.sync();
|
|
229
|
+
return true;
|
|
230
|
+
}
|
|
231
|
+
return false;
|
|
232
|
+
}
|
|
233
|
+
connect() {
|
|
234
|
+
if (this.stopped || this.final) return;
|
|
235
|
+
clearTimeout(this.timer);
|
|
236
|
+
if (navigator.onLine === false) return;
|
|
237
|
+
if (typeof EventSource !== "function" || Date.now() < this.pollUntil) void this.poll();
|
|
238
|
+
else void this.openStream();
|
|
239
|
+
}
|
|
240
|
+
async openStream() {
|
|
241
|
+
const r = await livePost(this.api, "stream-ticket", this.body());
|
|
242
|
+
if (this.stopped || this.final) return;
|
|
243
|
+
if (!r.ok) {
|
|
244
|
+
if (this.fail(r)) return;
|
|
245
|
+
if (r.code === "live_busy") this.pollUntil = Date.now() + BUSY_POLL_MS;
|
|
246
|
+
return this.retry();
|
|
247
|
+
}
|
|
248
|
+
const es = new EventSource(`${liveUrl(this.api, "stream")}?ticket=${encodeURIComponent(String(r.data.ticket))}&after=${this.chat.lastSeq}`);
|
|
249
|
+
this.es = es;
|
|
250
|
+
let ready = false;
|
|
251
|
+
const on = (name, fn) => es.addEventListener(name, (e) => {
|
|
252
|
+
if (this.es !== es) return;
|
|
253
|
+
this.touch();
|
|
254
|
+
let d;
|
|
255
|
+
try {
|
|
256
|
+
d = JSON.parse(e.data);
|
|
257
|
+
} catch (e2) {
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
if (d && typeof d === "object") fn(d);
|
|
261
|
+
});
|
|
262
|
+
on("ready", (d) => {
|
|
263
|
+
ready = true;
|
|
264
|
+
this.blocked = 0;
|
|
265
|
+
this.attempt = 0;
|
|
266
|
+
this.cb.notice(null);
|
|
267
|
+
this.set(liveApplyConversation(this.chat, d.conversation));
|
|
268
|
+
});
|
|
269
|
+
on("event", (d) => this.set(liveApplyEvent(this.chat, d)));
|
|
270
|
+
on("state", (d) => this.set(liveApplyConversation(this.chat, d)));
|
|
271
|
+
on("typing", (d) => this.showTyping(d.on !== false));
|
|
272
|
+
on("bye", (d) => {
|
|
273
|
+
this.closeStream();
|
|
274
|
+
this.bye(d.reason);
|
|
275
|
+
});
|
|
276
|
+
es.onerror = () => {
|
|
277
|
+
if (this.es !== es) return;
|
|
278
|
+
this.closeStream();
|
|
279
|
+
if (!ready && ++this.blocked >= 3) this.pollUntil = Infinity;
|
|
280
|
+
this.cb.notice("reconnecting");
|
|
281
|
+
this.retry();
|
|
282
|
+
};
|
|
283
|
+
this.touch();
|
|
284
|
+
}
|
|
285
|
+
touch() {
|
|
286
|
+
clearTimeout(this.idle);
|
|
287
|
+
this.idle = setTimeout(() => {
|
|
288
|
+
if (!this.es) return;
|
|
289
|
+
this.closeStream();
|
|
290
|
+
this.schedule(0);
|
|
291
|
+
}, LIVE_IDLE_MS);
|
|
292
|
+
}
|
|
293
|
+
closeStream() {
|
|
294
|
+
clearTimeout(this.idle);
|
|
295
|
+
if (this.es) this.es.close();
|
|
296
|
+
this.es = null;
|
|
297
|
+
}
|
|
298
|
+
bye(reason) {
|
|
299
|
+
if (reason === "replaced") {
|
|
300
|
+
this.stop();
|
|
301
|
+
this.cb.notice("replaced");
|
|
302
|
+
} else if (reason === "expired" || reason === "deleted") {
|
|
303
|
+
this.set(liveApplyConversation(this.chat, { ...this.chat.conversation, state: "closed", closeReason: "expired" }));
|
|
304
|
+
} else this.retry();
|
|
305
|
+
}
|
|
306
|
+
retry() {
|
|
307
|
+
this.schedule(liveBackoff(this.attempt++, this.random));
|
|
308
|
+
}
|
|
309
|
+
/** אחרי השהיה: סנכרון לפי lastSeq, ואז זרם חדש */
|
|
310
|
+
schedule(ms) {
|
|
311
|
+
clearTimeout(this.timer);
|
|
312
|
+
if (this.stopped || this.final) return;
|
|
313
|
+
this.timer = setTimeout(async () => {
|
|
314
|
+
if (this.stopped || navigator.onLine === false) return;
|
|
315
|
+
if (await this.sync()) this.connect();
|
|
316
|
+
else if (!this.stopped && !this.final) this.retry();
|
|
317
|
+
}, ms);
|
|
318
|
+
}
|
|
319
|
+
/** long-poll כשאין EventSource, כשהזרם חסום, או לזמן מה אחרי live_busy */
|
|
320
|
+
async poll() {
|
|
321
|
+
if (this.polling) return;
|
|
322
|
+
this.polling = true;
|
|
323
|
+
while (!this.stopped && !this.final) {
|
|
324
|
+
if (typeof EventSource === "function" && Date.now() >= this.pollUntil) break;
|
|
325
|
+
const at = Date.now();
|
|
326
|
+
const before = this.chat;
|
|
327
|
+
const r = await livePost(this.api, "sync", this.body({ after: this.chat.lastSeq, wait: 25 }));
|
|
328
|
+
if (this.stopped) break;
|
|
329
|
+
if (!r.ok) {
|
|
330
|
+
this.polling = false;
|
|
331
|
+
if (!this.fail(r)) this.retry();
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
this.attempt = 0;
|
|
335
|
+
this.set(liveApplySync(this.chat, r.data));
|
|
336
|
+
const same = this.chat.lastSeq === before.lastSeq && this.chat.conversation.state === before.conversation.state;
|
|
337
|
+
if (!(r.data && r.data.hasMore) && same && Date.now() - at < 1e3) await sleep(1e3);
|
|
338
|
+
}
|
|
339
|
+
this.polling = false;
|
|
340
|
+
if (!this.stopped && !this.final) this.connect();
|
|
341
|
+
}
|
|
342
|
+
showTyping(on) {
|
|
343
|
+
clearTimeout(this.typingTimer);
|
|
344
|
+
this.cb.typing(on);
|
|
345
|
+
if (on) this.typingTimer = setTimeout(() => this.cb.typing(false), LIVE_TYPING_HIDE_MS);
|
|
346
|
+
}
|
|
347
|
+
/** שליחת הודעה: מוצגת מיד כ"נשלח", עם clientId שנוצר פעם אחת ומשמש לכל ניסיון חוזר */
|
|
348
|
+
send(raw) {
|
|
349
|
+
const text = raw.trim();
|
|
350
|
+
if (!text) return "empty";
|
|
351
|
+
if (text.length > LIVE_MESSAGE_MAX) return "too_long";
|
|
352
|
+
if (this.final || this.stopped) return "closed";
|
|
353
|
+
const clientId = randomId(22);
|
|
354
|
+
this.set(liveAddPending(this.chat, clientId, text, (/* @__PURE__ */ new Date()).toISOString()));
|
|
355
|
+
this.typing(false);
|
|
356
|
+
void this.deliver(clientId, text, 0);
|
|
357
|
+
return "ok";
|
|
358
|
+
}
|
|
359
|
+
retrySend(clientId) {
|
|
360
|
+
const p = this.chat.pending.find((x) => x.clientId === clientId);
|
|
361
|
+
if (!p || p.status !== "failed" || this.final || this.stopped) return;
|
|
362
|
+
this.set(liveSetPending(this.chat, clientId, "sending"));
|
|
363
|
+
void this.deliver(clientId, p.text, 0);
|
|
364
|
+
}
|
|
365
|
+
async deliver(clientId, text, attempt) {
|
|
366
|
+
const r = await livePost(this.api, "messages", this.body({ clientId, text }));
|
|
367
|
+
if (this.stopped) return;
|
|
368
|
+
if (r.ok) {
|
|
369
|
+
this.set(liveApplyEvent(this.chat, r.data.event));
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
if (retryable(r) && attempt < 5) {
|
|
373
|
+
await sleep(liveBackoff(attempt, this.random));
|
|
374
|
+
return this.deliver(clientId, text, attempt + 1);
|
|
375
|
+
}
|
|
376
|
+
this.set(liveSetPending(this.chat, clientId, "failed"));
|
|
377
|
+
this.fail(r);
|
|
378
|
+
}
|
|
379
|
+
/** "מקליד" לנציג: לכל היותר פעם ב-3 שניות, false כשהשדה התרוקן או נשלח. בלי ניסיונות חוזרים */
|
|
380
|
+
typing(on) {
|
|
381
|
+
if (this.chat.conversation.state !== "active" || this.stopped) return;
|
|
382
|
+
const now = Date.now();
|
|
383
|
+
if (on ? this.typingOn && now - this.typingAt < LIVE_TYPING_INTERVAL_MS : !this.typingOn) return;
|
|
384
|
+
this.typingOn = on;
|
|
385
|
+
this.typingAt = now;
|
|
386
|
+
void livePost(this.api, "typing", this.body({ on }));
|
|
387
|
+
}
|
|
388
|
+
/** "להשאיר הודעה במקום" או "סיום השיחה". בטוח לחזור עליהן */
|
|
389
|
+
async command(path) {
|
|
390
|
+
for (let a = 0; a < 4 && !this.stopped; a++) {
|
|
391
|
+
const r = await livePost(this.api, path, this.body());
|
|
392
|
+
if (r.ok) {
|
|
393
|
+
this.set(liveApplyConversation(this.chat, r.data && r.data.conversation));
|
|
394
|
+
return true;
|
|
395
|
+
}
|
|
396
|
+
if (!retryable(r)) return this.fail(r);
|
|
397
|
+
await sleep(liveBackoff(a, this.random));
|
|
398
|
+
}
|
|
399
|
+
return false;
|
|
400
|
+
}
|
|
401
|
+
/** מצב סופי: סוגרים את הזרם ומוחקים את הקרדנשל. אם מספר הפנייה עוד לא ידוע, סנכרון אחד אחרי 2 שניות */
|
|
402
|
+
finish() {
|
|
403
|
+
if (this.stopped) return;
|
|
404
|
+
this.closeStream();
|
|
405
|
+
clearTimeout(this.timer);
|
|
406
|
+
const ticket = this.chat.conversation.ticket;
|
|
407
|
+
if (this.chat.conversation.state === "ticket" && (!ticket || ticket.number == null) && !this.numberTried) {
|
|
408
|
+
this.numberTried = true;
|
|
409
|
+
this.timer = setTimeout(() => void this.sync().then(() => this.end()), 2e3);
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
412
|
+
this.end();
|
|
413
|
+
}
|
|
414
|
+
end() {
|
|
415
|
+
if (this.stopped) return;
|
|
416
|
+
this.stop();
|
|
417
|
+
this.cb.ended();
|
|
418
|
+
}
|
|
419
|
+
stop() {
|
|
420
|
+
this.stopped = true;
|
|
421
|
+
this.closeStream();
|
|
422
|
+
clearTimeout(this.timer);
|
|
423
|
+
clearTimeout(this.typingTimer);
|
|
424
|
+
window.removeEventListener("online", this.onOnline);
|
|
425
|
+
}
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
// src/live-styles.ts
|
|
431
|
+
var LIVE_CSS;
|
|
432
|
+
var init_live_styles = __esm({
|
|
433
|
+
"src/live-styles.ts"() {
|
|
434
|
+
"use strict";
|
|
435
|
+
LIVE_CSS = `
|
|
436
|
+
.lstatus{flex:none;padding:8px 14px;font-size:13px;line-height:1.6;color:#374151;background:#fff;border-block-end:1px solid #e3e6ea}
|
|
437
|
+
.lsys{align-self:center;max-width:90%;text-align:center;font-size:12px;color:#6b7280;padding:2px 8px}
|
|
438
|
+
.lsep{margin-block:6px;border-block-start:1px solid #e3e6ea;padding-block-start:8px;align-self:stretch;max-width:none}
|
|
439
|
+
.lname{display:block;font-size:11px;font-weight:600;color:#6b7280}
|
|
440
|
+
.lp-sending{opacity:.65}
|
|
441
|
+
.lp-failed{opacity:.85;outline:1px solid #b42318}
|
|
442
|
+
.lp-failed .btn{display:block;margin-block-start:6px;padding:3px 8px;font-size:12px;color:#b42318;background:#fff;border-color:#b42318}
|
|
443
|
+
.ltyping{flex:none;padding:0 14px 4px;font-size:12px;color:#6b7280}
|
|
444
|
+
.lerror{flex:none;padding:0 14px}
|
|
445
|
+
.lerror:empty{display:none}
|
|
446
|
+
.lactions{flex:none;display:flex;flex-wrap:wrap;gap:6px;padding:4px 14px 8px}
|
|
447
|
+
.lactions:empty{display:none}
|
|
448
|
+
.lactions .btn{padding:6px 12px;font-size:14px}
|
|
449
|
+
.note{margin:0;font-size:14px;color:#4b5563}
|
|
450
|
+
.composer input:disabled{opacity:.5}
|
|
451
|
+
.send:disabled{opacity:.5;cursor:default}
|
|
452
|
+
.launcher.kc-unread::after{content:"";position:absolute;inset-block-start:4px;inset-inline-end:4px;width:12px;height:12px;border-radius:50%;background:#e11d48;border:2px solid #fff}
|
|
453
|
+
`;
|
|
454
|
+
}
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
// src/live.ts
|
|
458
|
+
var live_exports = {};
|
|
459
|
+
__export(live_exports, {
|
|
460
|
+
createLive: () => createLive
|
|
461
|
+
});
|
|
462
|
+
import { isLiveFinal as isLiveFinal2, liveBackoff as liveBackoff2, liveLines, liveStatus, liveUnavailableText, liveWaitLeft, uiTexts, withLiveChip, LIVE_AVAILABILITY_TTL_MS, LIVE_MESSAGE_MAX as LIVE_MESSAGE_MAX2 } from "@kavchat/core";
|
|
463
|
+
function readStored(pk) {
|
|
464
|
+
try {
|
|
465
|
+
const id = localStorage.getItem(INDEX(pk));
|
|
466
|
+
const raw = id && localStorage.getItem(RECORD(pk, id));
|
|
467
|
+
const s = raw ? JSON.parse(raw) : null;
|
|
468
|
+
if (s && s.id === id && /^lvc_/.test(s.credential) && Date.parse(s.expiresAt) > Date.now()) return s;
|
|
469
|
+
if (id) clearStored(pk, id);
|
|
470
|
+
} catch (e) {
|
|
471
|
+
}
|
|
472
|
+
return null;
|
|
473
|
+
}
|
|
474
|
+
function writeStored(pk, s) {
|
|
475
|
+
try {
|
|
476
|
+
localStorage.setItem(RECORD(pk, s.id), JSON.stringify(s));
|
|
477
|
+
localStorage.setItem(INDEX(pk), s.id);
|
|
478
|
+
} catch (e) {
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
function clearStored(pk, id, credential) {
|
|
482
|
+
try {
|
|
483
|
+
const raw = localStorage.getItem(RECORD(pk, id));
|
|
484
|
+
if (credential && raw && JSON.parse(raw).credential !== credential) return;
|
|
485
|
+
localStorage.removeItem(RECORD(pk, id));
|
|
486
|
+
if (localStorage.getItem(INDEX(pk)) === id) localStorage.removeItem(INDEX(pk));
|
|
487
|
+
} catch (e) {
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
function createLive(host) {
|
|
491
|
+
const pk = host.api.publicKey;
|
|
492
|
+
const t = { ...host.t, ...uiTexts(host.locale) };
|
|
493
|
+
const ui = host.ui;
|
|
494
|
+
const style = h("style", null, LIVE_CSS);
|
|
495
|
+
ui.root.appendChild(style);
|
|
496
|
+
let avail = null;
|
|
497
|
+
let inflight = null;
|
|
498
|
+
let conn = null;
|
|
499
|
+
let stored = null;
|
|
500
|
+
let view = null;
|
|
501
|
+
let typingOn = false;
|
|
502
|
+
let notice = null;
|
|
503
|
+
let seen = /* @__PURE__ */ new Set();
|
|
504
|
+
let lastState = "";
|
|
505
|
+
let ticker;
|
|
506
|
+
let deadlineSync = 0;
|
|
507
|
+
let destroyed = false;
|
|
508
|
+
let quiet = false;
|
|
509
|
+
const els = {};
|
|
510
|
+
function refresh() {
|
|
511
|
+
var _a;
|
|
512
|
+
if (avail && Date.now() - avail.at < LIVE_AVAILABILITY_TTL_MS) return Promise.resolve();
|
|
513
|
+
if (!inflight) {
|
|
514
|
+
const wasOpen = !!((_a = avail == null ? void 0 : avail.data) == null ? void 0 : _a.open);
|
|
515
|
+
inflight = liveGet(host.api, "availability").then((r) => {
|
|
516
|
+
var _a2;
|
|
517
|
+
inflight = null;
|
|
518
|
+
avail = { at: Date.now(), data: r.ok ? r.data : null };
|
|
519
|
+
if (!destroyed && wasOpen !== !!((_a2 = avail.data) == null ? void 0 : _a2.open)) host.renderChips();
|
|
520
|
+
});
|
|
521
|
+
}
|
|
522
|
+
return inflight;
|
|
523
|
+
}
|
|
524
|
+
function nextOpen(data) {
|
|
525
|
+
var _a;
|
|
526
|
+
const iso = (_a = data == null ? void 0 : data.departments.find((d) => d.nextOpenAt)) == null ? void 0 : _a.nextOpenAt;
|
|
527
|
+
const at = iso ? new Date(iso) : null;
|
|
528
|
+
if (!at || isNaN(at.getTime())) return null;
|
|
529
|
+
const today = at.toDateString() === (/* @__PURE__ */ new Date()).toDateString();
|
|
530
|
+
try {
|
|
531
|
+
return new Intl.DateTimeFormat(host.locale, { ...today ? {} : { weekday: "short" }, hour: "2-digit", minute: "2-digit" }).format(at);
|
|
532
|
+
} catch (e) {
|
|
533
|
+
return null;
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
const note = () => {
|
|
537
|
+
const d = avail == null ? void 0 : avail.data;
|
|
538
|
+
return d && d.enabled && !d.open ? liveUnavailableText(d.reason, t, d.reason === "closed" ? nextOpen(d) : null) : "";
|
|
539
|
+
};
|
|
540
|
+
function requestForm(effect, data) {
|
|
541
|
+
const guest = !host.opts.userToken;
|
|
542
|
+
const requestId = randomId(22);
|
|
543
|
+
const messageClientId = randomId(22);
|
|
544
|
+
const field = (id, label, control) => h("div", { class: "field" }, h("label", { for: id }, label), control);
|
|
545
|
+
const name = h("input", { id: "kl-name", type: "text", autocomplete: "name", maxlength: "100", dir: "auto" });
|
|
546
|
+
name.value = host.opts.userName || "";
|
|
547
|
+
const email = h("input", { id: "kl-email", type: "email", autocomplete: "email", dir: "auto" });
|
|
548
|
+
const phone = h("input", { id: "kl-phone", type: "tel", autocomplete: "tel", dir: "auto" });
|
|
549
|
+
const departments = data.departments.filter((d) => d.open);
|
|
550
|
+
let dept = null;
|
|
551
|
+
if (departments.length > 1) {
|
|
552
|
+
dept = h("select", { id: "kl-dept" });
|
|
553
|
+
for (const d of departments) dept.appendChild(h("option", { value: d.id }, d.name));
|
|
554
|
+
}
|
|
555
|
+
const message = h("textarea", { id: "kl-message", maxlength: String(LIVE_MESSAGE_MAX2), dir: "auto" });
|
|
556
|
+
message.value = effect.prefill || "";
|
|
557
|
+
const error = h("div", { class: "error", role: "alert" });
|
|
558
|
+
const submit = h("button", { type: "submit", class: "btn btn-primary" }, t["live.start"]);
|
|
559
|
+
const cancel = h("button", { type: "button", class: "btn" }, t["form.cancel"]);
|
|
560
|
+
cancel.addEventListener("click", () => host.showChat());
|
|
561
|
+
const form = h(
|
|
562
|
+
"form",
|
|
563
|
+
{ class: "form", novalidate: true },
|
|
564
|
+
h("h2", null, t["live.talk"]),
|
|
565
|
+
guest ? h("p", { class: "note" }, t["live.intro"]) : null,
|
|
566
|
+
guest ? field("kl-name", t["form.name"], name) : null,
|
|
567
|
+
guest ? field("kl-email", t["form.email"], email) : null,
|
|
568
|
+
guest ? field("kl-phone", t["form.phone"], phone) : null,
|
|
569
|
+
dept ? field("kl-dept", t["live.department"], dept) : null,
|
|
570
|
+
field("kl-message", t["live.message"], message),
|
|
571
|
+
error,
|
|
572
|
+
h("div", { class: "actions" }, submit, cancel)
|
|
573
|
+
);
|
|
574
|
+
form.addEventListener("submit", async (e) => {
|
|
575
|
+
var _a, _b;
|
|
576
|
+
e.preventDefault();
|
|
577
|
+
const pre = { name: name.value.trim(), email: email.value.trim(), phone: phone.value.trim(), message: message.value.trim() };
|
|
578
|
+
const problem = guest && !pre.email && !pre.phone ? t["form.contactRequired"] : guest && pre.email && !EMAIL_RE.test(pre.email) ? t["form.emailInvalid"] : "";
|
|
579
|
+
if (problem) {
|
|
580
|
+
error.textContent = problem;
|
|
581
|
+
return;
|
|
582
|
+
}
|
|
583
|
+
error.textContent = "";
|
|
584
|
+
submit.disabled = true;
|
|
585
|
+
submit.textContent = t["live.connecting"];
|
|
586
|
+
const body = {
|
|
587
|
+
requestId,
|
|
588
|
+
sessionId: (_a = host.session) == null ? void 0 : _a.id,
|
|
589
|
+
identityToken: host.opts.userToken,
|
|
590
|
+
...guest ? { name: pre.name || void 0, email: pre.email || void 0, phone: pre.phone || void 0 } : {},
|
|
591
|
+
departmentId: dept ? dept.value : (_b = departments[0]) == null ? void 0 : _b.id,
|
|
592
|
+
topicId: effect.contactTopicId || void 0,
|
|
593
|
+
...pre.message ? { message: pre.message, messageClientId } : {},
|
|
594
|
+
// השרת מוחק query ו-fragment, ובכל זאת לא שולחים אותם
|
|
595
|
+
page: location.href.split(/[?#]/)[0],
|
|
596
|
+
transcript: effect.transcript.slice(-200).map((x) => ({ ...x, text: x.text.slice(0, 1e3) }))
|
|
597
|
+
};
|
|
598
|
+
let r = await livePost(host.api, "requests", body);
|
|
599
|
+
for (let a = 0; a < 2 && retryable(r); a++) {
|
|
600
|
+
await sleep(liveBackoff2(a));
|
|
601
|
+
r = await livePost(host.api, "requests", body);
|
|
602
|
+
}
|
|
603
|
+
if (destroyed) return;
|
|
604
|
+
submit.disabled = false;
|
|
605
|
+
submit.textContent = t["live.start"];
|
|
606
|
+
if (r.ok && r.data.accepted) {
|
|
607
|
+
const d = r.data;
|
|
608
|
+
stored = { id: d.conversation.id, credential: d.credential, expiresAt: d.credentialExpiresAt, transcript: body.transcript };
|
|
609
|
+
writeStored(pk, stored);
|
|
610
|
+
begin(d.conversation, false);
|
|
611
|
+
} else if (r.ok) {
|
|
612
|
+
const known = avail == null ? void 0 : avail.data;
|
|
613
|
+
avail = null;
|
|
614
|
+
host.showForm(effect, { ...pre, note: liveUnavailableText(r.data.reason, t, r.data.reason === "closed" ? nextOpen(known) : null) });
|
|
615
|
+
} else {
|
|
616
|
+
error.textContent = r.code === "ticket_contact_required" ? t["form.contactRequired"] : r.code === "email_invalid" ? t["form.emailInvalid"] : r.code === "live_conversation_open" ? t["live.elsewhere"] : r.code === "network" ? t["form.error"] : r.message || t["form.error"];
|
|
617
|
+
}
|
|
618
|
+
});
|
|
619
|
+
ui.formView.replaceChildren(form);
|
|
620
|
+
ui.chatView.hidden = true;
|
|
621
|
+
ui.formView.hidden = false;
|
|
622
|
+
if (host.isOpen) (form.querySelector("input,select,textarea") || ui.panel).focus();
|
|
623
|
+
}
|
|
624
|
+
function begin(conversation, restoring) {
|
|
625
|
+
const mine = stored;
|
|
626
|
+
conn == null ? void 0 : conn.stop();
|
|
627
|
+
seen = /* @__PURE__ */ new Set();
|
|
628
|
+
notice = null;
|
|
629
|
+
typingOn = false;
|
|
630
|
+
const c = new LiveConnection(host.api, mine.credential, conversation, {
|
|
631
|
+
change: () => render(),
|
|
632
|
+
typing: (on) => {
|
|
633
|
+
typingOn = on;
|
|
634
|
+
renderTyping();
|
|
635
|
+
},
|
|
636
|
+
notice: (kind) => {
|
|
637
|
+
notice = kind;
|
|
638
|
+
render();
|
|
639
|
+
},
|
|
640
|
+
ended: () => {
|
|
641
|
+
clearStored(pk, mine.id, mine.credential);
|
|
642
|
+
render();
|
|
643
|
+
}
|
|
644
|
+
});
|
|
645
|
+
conn = c;
|
|
646
|
+
mountView();
|
|
647
|
+
if (!restoring) {
|
|
648
|
+
render();
|
|
649
|
+
c.connect();
|
|
650
|
+
}
|
|
651
|
+
return c;
|
|
652
|
+
}
|
|
653
|
+
function mountView() {
|
|
654
|
+
if (view) return;
|
|
655
|
+
els.status = h("div", { class: "lstatus" });
|
|
656
|
+
els.msgs = h("div", { class: "messages" });
|
|
657
|
+
els.typing = h("div", { class: "ltyping", "aria-hidden": "true" }, t["live.typing"]);
|
|
658
|
+
els.actions = h("div", { class: "lactions" });
|
|
659
|
+
els.error = h("div", { class: "error lerror", role: "alert" });
|
|
660
|
+
els.input = h("input", { type: "text", dir: "auto", maxlength: String(LIVE_MESSAGE_MAX2), autocomplete: "off", placeholder: t["live.placeholder"], "aria-label": t["live.placeholder"], enterkeyhint: "send" });
|
|
661
|
+
els.send = h("button", { type: "submit", class: "send", "aria-label": t["widget.send"] }, icon(ICONS.send));
|
|
662
|
+
const composer = h("form", { class: "composer" }, els.input, els.send);
|
|
663
|
+
composer.addEventListener("submit", (e) => {
|
|
664
|
+
e.preventDefault();
|
|
665
|
+
if (!conn) return;
|
|
666
|
+
const r = conn.send(els.input.value);
|
|
667
|
+
if (r === "ok") {
|
|
668
|
+
els.input.value = "";
|
|
669
|
+
els.error.textContent = "";
|
|
670
|
+
} else if (r === "too_long") els.error.textContent = t["live.tooLong"];
|
|
671
|
+
});
|
|
672
|
+
els.input.addEventListener("input", () => conn == null ? void 0 : conn.typing(els.input.value.trim() !== ""));
|
|
673
|
+
view = h("div", { class: "view lv" }, els.status, els.msgs, els.typing, els.error, els.actions, composer);
|
|
674
|
+
ui.panel.insertBefore(view, ui.live);
|
|
675
|
+
}
|
|
676
|
+
function button(label, fn, primary = false) {
|
|
677
|
+
const b = h("button", { type: "button", class: primary ? "btn btn-primary" : "btn" }, label);
|
|
678
|
+
b.addEventListener("click", fn);
|
|
679
|
+
return b;
|
|
680
|
+
}
|
|
681
|
+
function backToBot() {
|
|
682
|
+
if (conn) {
|
|
683
|
+
conn.stop();
|
|
684
|
+
if (stored) clearStored(pk, stored.id, stored.credential);
|
|
685
|
+
}
|
|
686
|
+
conn = null;
|
|
687
|
+
stored = null;
|
|
688
|
+
clearInterval(ticker);
|
|
689
|
+
ticker = void 0;
|
|
690
|
+
view == null ? void 0 : view.remove();
|
|
691
|
+
view = null;
|
|
692
|
+
host.showChat();
|
|
693
|
+
}
|
|
694
|
+
function renderTyping() {
|
|
695
|
+
if (!view || !conn) return;
|
|
696
|
+
els.typing.hidden = !(typingOn && conn.chat.conversation.state === "active");
|
|
697
|
+
}
|
|
698
|
+
function render() {
|
|
699
|
+
if (quiet || !view || !conn || !stored) return;
|
|
700
|
+
const c = conn.chat;
|
|
701
|
+
const conv = c.conversation;
|
|
702
|
+
const final = isLiveFinal2(conv.state);
|
|
703
|
+
const off = final || notice === "replaced";
|
|
704
|
+
ui.chatView.hidden = true;
|
|
705
|
+
ui.formView.hidden = true;
|
|
706
|
+
view.hidden = false;
|
|
707
|
+
renderStatus();
|
|
708
|
+
const atBottom = els.msgs.scrollHeight - els.msgs.scrollTop - els.msgs.clientHeight < 40;
|
|
709
|
+
els.msgs.textContent = "";
|
|
710
|
+
for (const m of stored.transcript) els.msgs.appendChild(h("div", { class: `msg msg-${m.from === "user" ? "user" : "bot"}`, dir: "auto" }, m.text));
|
|
711
|
+
if (stored.transcript.length) els.msgs.appendChild(h("div", { class: "lsys lsep" }, t["live.talk"]));
|
|
712
|
+
const fresh = [];
|
|
713
|
+
for (const l of liveLines(c, t)) {
|
|
714
|
+
let el;
|
|
715
|
+
if (l.kind === "system") el = h("div", { class: "lsys" }, l.text);
|
|
716
|
+
else if (l.kind === "agent") el = h("div", { class: "msg msg-bot", dir: "auto" }, h("span", { class: "lname" }, l.name || ""), l.text);
|
|
717
|
+
else {
|
|
718
|
+
el = h("div", { class: `msg msg-user${l.status ? ` lp-${l.status}` : ""}`, dir: "auto" }, l.text);
|
|
719
|
+
if (l.status === "failed") {
|
|
720
|
+
const id = l.clientId;
|
|
721
|
+
el.appendChild(button(t["live.failed"], () => conn == null ? void 0 : conn.retrySend(id)));
|
|
722
|
+
} else if (l.status === "sending") el.setAttribute("aria-busy", "true");
|
|
723
|
+
}
|
|
724
|
+
els.msgs.appendChild(el);
|
|
725
|
+
if (!seen.has(l.key)) {
|
|
726
|
+
seen.add(l.key);
|
|
727
|
+
if (l.kind !== "visitor") fresh.push(l.kind === "agent" ? `${l.name}: ${l.text}` : l.text);
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
if (fresh.length) {
|
|
731
|
+
ui.live.textContent = fresh.join("\n");
|
|
732
|
+
if (!host.isOpen) ui.launcher.classList.add("kc-unread");
|
|
733
|
+
}
|
|
734
|
+
if (atBottom || fresh.length) els.msgs.scrollTop = els.msgs.scrollHeight;
|
|
735
|
+
renderTyping();
|
|
736
|
+
els.actions.textContent = "";
|
|
737
|
+
if (off) els.actions.appendChild(button(t["live.back"], backToBot, true));
|
|
738
|
+
else {
|
|
739
|
+
if (conv.state === "queued") els.actions.appendChild(button(t["live.leave"], () => void (conn == null ? void 0 : conn.command("leave-message"))));
|
|
740
|
+
els.actions.appendChild(button(t["live.end"], () => void (conn == null ? void 0 : conn.command("end"))));
|
|
741
|
+
}
|
|
742
|
+
els.input.disabled = off;
|
|
743
|
+
els.send.disabled = off;
|
|
744
|
+
if (conv.state !== lastState) {
|
|
745
|
+
lastState = conv.state;
|
|
746
|
+
host.emit({ type: "live", state: conv.state, id: conv.id });
|
|
747
|
+
}
|
|
748
|
+
if (conv.state === "queued" && conv.queueDeadline && !ticker) ticker = setInterval(tick, 1e3);
|
|
749
|
+
if (conv.state !== "queued" && ticker) {
|
|
750
|
+
clearInterval(ticker);
|
|
751
|
+
ticker = void 0;
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
function renderStatus() {
|
|
755
|
+
if (!conn) return;
|
|
756
|
+
const conv = conn.chat.conversation;
|
|
757
|
+
els.status.textContent = notice === "replaced" ? t["live.replaced"] : `${liveStatus(conv, t, Date.now())}${notice === "reconnecting" && !isLiveFinal2(conv.state) ? ` \xB7 ${t["live.reconnecting"]}` : ""}`;
|
|
758
|
+
}
|
|
759
|
+
function tick() {
|
|
760
|
+
if (!conn) return;
|
|
761
|
+
renderStatus();
|
|
762
|
+
const left = liveWaitLeft(conn.chat.conversation, Date.now());
|
|
763
|
+
if (left === 0 && Date.now() - deadlineSync > 1e4) {
|
|
764
|
+
deadlineSync = Date.now();
|
|
765
|
+
void conn.sync();
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
return {
|
|
769
|
+
chips(chips) {
|
|
770
|
+
var _a, _b, _c;
|
|
771
|
+
if (!((_b = (_a = host.release) == null ? void 0 : _a.liveChat) == null ? void 0 : _b.enabled) || !chips.some((c) => c.kind === "contact")) return chips;
|
|
772
|
+
void refresh();
|
|
773
|
+
return ((_c = avail == null ? void 0 : avail.data) == null ? void 0 : _c.open) ? withLiveChip(chips, t["live.talk"]) : chips;
|
|
774
|
+
},
|
|
775
|
+
note,
|
|
776
|
+
async start(effect) {
|
|
777
|
+
await refresh();
|
|
778
|
+
if (destroyed) return;
|
|
779
|
+
const d = avail == null ? void 0 : avail.data;
|
|
780
|
+
if (d && d.open) requestForm(effect, d);
|
|
781
|
+
else host.showForm(effect, { note: note() });
|
|
782
|
+
},
|
|
783
|
+
async restore() {
|
|
784
|
+
const s = readStored(pk);
|
|
785
|
+
if (!s) return;
|
|
786
|
+
stored = s;
|
|
787
|
+
quiet = true;
|
|
788
|
+
const c = begin({ id: s.id, state: "queued", lastSeq: 0, credentialExpiresAt: s.expiresAt }, true);
|
|
789
|
+
const ok = await c.sync();
|
|
790
|
+
quiet = false;
|
|
791
|
+
if (destroyed || conn !== c) return;
|
|
792
|
+
if (!ok && c.final && c.chat.events.length === 0) {
|
|
793
|
+
c.stop();
|
|
794
|
+
clearStored(pk, s.id, s.credential);
|
|
795
|
+
conn = null;
|
|
796
|
+
stored = null;
|
|
797
|
+
view == null ? void 0 : view.remove();
|
|
798
|
+
view = null;
|
|
799
|
+
return;
|
|
800
|
+
}
|
|
801
|
+
for (const l of liveLines(c.chat, t)) seen.add(l.key);
|
|
802
|
+
render();
|
|
803
|
+
c.connect();
|
|
804
|
+
},
|
|
805
|
+
focus() {
|
|
806
|
+
if (!view || view.hidden) return false;
|
|
807
|
+
ui.launcher.classList.remove("kc-unread");
|
|
808
|
+
if (coarse()) ui.panel.focus();
|
|
809
|
+
else if (!els.input.disabled) els.input.focus();
|
|
810
|
+
else ui.panel.focus();
|
|
811
|
+
return true;
|
|
812
|
+
},
|
|
813
|
+
destroy() {
|
|
814
|
+
destroyed = true;
|
|
815
|
+
conn == null ? void 0 : conn.stop();
|
|
816
|
+
clearInterval(ticker);
|
|
817
|
+
view == null ? void 0 : view.remove();
|
|
818
|
+
style.remove();
|
|
819
|
+
}
|
|
820
|
+
};
|
|
821
|
+
}
|
|
822
|
+
var INDEX, RECORD, EMAIL_RE, coarse;
|
|
823
|
+
var init_live = __esm({
|
|
824
|
+
"src/live.ts"() {
|
|
825
|
+
"use strict";
|
|
826
|
+
init_dom();
|
|
827
|
+
init_live_client();
|
|
828
|
+
init_live_styles();
|
|
829
|
+
init_storage();
|
|
830
|
+
INDEX = (pk) => `kavchat:live:${pk}`;
|
|
831
|
+
RECORD = (pk, id) => `kavchat:live:${pk}:${id}`;
|
|
832
|
+
EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
833
|
+
coarse = () => typeof window.matchMedia === "function" && window.matchMedia("(pointer: coarse)").matches;
|
|
834
|
+
}
|
|
835
|
+
});
|
|
836
|
+
|
|
1
837
|
// src/widget.ts
|
|
2
838
|
import {
|
|
3
839
|
UI_TEXTS,
|
|
@@ -11,7 +847,7 @@ import {
|
|
|
11
847
|
pressChip,
|
|
12
848
|
sendText,
|
|
13
849
|
startConversation,
|
|
14
|
-
uiTexts
|
|
850
|
+
uiTexts as uiTexts2
|
|
15
851
|
} from "@kavchat/core";
|
|
16
852
|
|
|
17
853
|
// src/api.ts
|
|
@@ -121,112 +957,14 @@ function themeFor(primaryColor) {
|
|
|
121
957
|
return { primary, onPrimary, accent };
|
|
122
958
|
}
|
|
123
959
|
|
|
124
|
-
// src/
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
if (attrs) {
|
|
128
|
-
for (const [k, v] of Object.entries(attrs)) {
|
|
129
|
-
if (v === void 0 || v === null || v === false) continue;
|
|
130
|
-
el.setAttribute(k, v === true ? "" : v);
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
append(el, children);
|
|
134
|
-
return el;
|
|
135
|
-
}
|
|
136
|
-
function append(el, children) {
|
|
137
|
-
for (const c of children) {
|
|
138
|
-
if (c === null || c === void 0 || c === false) continue;
|
|
139
|
-
el.appendChild(typeof c === "string" ? document.createTextNode(c) : c);
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
var SVG_NS = "http://www.w3.org/2000/svg";
|
|
143
|
-
function icon(d) {
|
|
144
|
-
const svg = document.createElementNS(SVG_NS, "svg");
|
|
145
|
-
svg.setAttribute("viewBox", "0 0 24 24");
|
|
146
|
-
svg.setAttribute("width", "24");
|
|
147
|
-
svg.setAttribute("height", "24");
|
|
148
|
-
svg.setAttribute("aria-hidden", "true");
|
|
149
|
-
svg.setAttribute("focusable", "false");
|
|
150
|
-
const path = document.createElementNS(SVG_NS, "path");
|
|
151
|
-
path.setAttribute("d", d);
|
|
152
|
-
path.setAttribute("fill", "currentColor");
|
|
153
|
-
svg.appendChild(path);
|
|
154
|
-
return svg;
|
|
155
|
-
}
|
|
156
|
-
var ICONS = {
|
|
157
|
-
chat: "M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2z",
|
|
158
|
-
close: "M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z",
|
|
159
|
-
send: "M2.01 21 23 12 2.01 3 2 10l15 2-15 2z"
|
|
160
|
-
};
|
|
161
|
-
function safeUrl(raw, schemes) {
|
|
162
|
-
try {
|
|
163
|
-
const url = new URL(String(raw || "").trim());
|
|
164
|
-
return schemes.includes(url.protocol) ? url.href : null;
|
|
165
|
-
} catch (e) {
|
|
166
|
-
return null;
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
var URL_RE = /https:\/\/[^\s<>"'`]+/g;
|
|
170
|
-
var TRAILING = /[.,;:!?)\]}'"»]+$/;
|
|
171
|
-
function richText(text) {
|
|
172
|
-
const frag = document.createDocumentFragment();
|
|
173
|
-
let last = 0;
|
|
174
|
-
URL_RE.lastIndex = 0;
|
|
175
|
-
let m;
|
|
176
|
-
while (m = URL_RE.exec(text)) {
|
|
177
|
-
const raw = m[0].replace(TRAILING, "");
|
|
178
|
-
const href = safeUrl(raw, ["https:"]);
|
|
179
|
-
if (!href || !raw) continue;
|
|
180
|
-
if (m.index > last) frag.appendChild(document.createTextNode(text.slice(last, m.index)));
|
|
181
|
-
const a = h("a", { href, target: "_blank", rel: "noopener noreferrer" }, raw);
|
|
182
|
-
frag.appendChild(a);
|
|
183
|
-
last = m.index + raw.length;
|
|
184
|
-
URL_RE.lastIndex = last;
|
|
185
|
-
}
|
|
186
|
-
if (last < text.length) frag.appendChild(document.createTextNode(text.slice(last)));
|
|
187
|
-
return frag;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
// src/storage.ts
|
|
191
|
-
var RELEASE_KEY = (pk) => `kavchat:release:${pk}`;
|
|
192
|
-
var SESSION_KEY = (pk) => `kavchat:session:${pk}`;
|
|
193
|
-
var SCREENS_KEY = (pk) => `kavchat:screens:${pk}`;
|
|
194
|
-
function read(store, key) {
|
|
195
|
-
try {
|
|
196
|
-
const raw = store().getItem(key);
|
|
197
|
-
return raw ? JSON.parse(raw) : null;
|
|
198
|
-
} catch (e) {
|
|
199
|
-
return null;
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
function write(store, key, value) {
|
|
203
|
-
try {
|
|
204
|
-
store().setItem(key, JSON.stringify(value));
|
|
205
|
-
} catch (e) {
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
var local = () => window.localStorage;
|
|
209
|
-
var session = () => window.sessionStorage;
|
|
210
|
-
var readCachedRelease = (pk) => read(local, RELEASE_KEY(pk));
|
|
211
|
-
var writeCachedRelease = (pk, value) => write(local, RELEASE_KEY(pk), value);
|
|
212
|
-
var readScreensMark = (pk) => read(local, SCREENS_KEY(pk));
|
|
213
|
-
var writeScreensMark = (pk, value) => write(local, SCREENS_KEY(pk), value);
|
|
214
|
-
var readSession = (pk) => read(session, SESSION_KEY(pk));
|
|
215
|
-
var writeSession = (pk, value) => write(session, SESSION_KEY(pk), value);
|
|
216
|
-
var ID_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-";
|
|
217
|
-
function randomId(length = 22) {
|
|
218
|
-
const bytes = new Uint8Array(length);
|
|
219
|
-
if (typeof crypto !== "undefined" && crypto.getRandomValues) crypto.getRandomValues(bytes);
|
|
220
|
-
else for (let i = 0; i < length; i++) bytes[i] = Math.floor(Math.random() * 256);
|
|
221
|
-
let out = "";
|
|
222
|
-
for (let i = 0; i < length; i++) out += ID_CHARS[bytes[i] & 63];
|
|
223
|
-
return out;
|
|
224
|
-
}
|
|
960
|
+
// src/widget.ts
|
|
961
|
+
init_dom();
|
|
962
|
+
init_storage();
|
|
225
963
|
|
|
226
964
|
// src/styles.ts
|
|
227
965
|
var CSS = `
|
|
228
966
|
:host{all:initial}
|
|
229
|
-
.kc{font-family:system-ui,-apple-system,"Segoe UI",Roboto,"Noto Sans Hebrew","Noto Sans Arabic",Arial,sans-serif;font-size:15px;line-height:1.45;color:#1f2328;-webkit-font-smoothing:antialiased}
|
|
967
|
+
.kc{font-family:system-ui,-apple-system,"Segoe UI",Roboto,"Noto Sans Hebrew","Noto Sans Arabic","Noto Sans Thai",Arial,sans-serif;font-size:15px;line-height:1.45;color:#1f2328;-webkit-font-smoothing:antialiased}
|
|
230
968
|
.kc *,.kc *::before,.kc *::after{box-sizing:border-box}
|
|
231
969
|
.kc [hidden]{display:none!important}
|
|
232
970
|
.kc button,.kc input,.kc select,.kc textarea{font:inherit;color:inherit;margin:0}
|
|
@@ -241,8 +979,8 @@ var CSS = `
|
|
|
241
979
|
.avatar{width:38px;height:38px;border-radius:50%;flex:none;display:flex;align-items:center;justify-content:center;overflow:hidden;background:rgba(255,255,255,.22);font-weight:600;font-size:15px}
|
|
242
980
|
.avatar img{width:100%;height:100%;object-fit:cover}
|
|
243
981
|
.titles{min-width:0;flex:1}
|
|
244
|
-
.name{font-weight:600;font-size:16px;margin:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
|
245
|
-
.subtitle{font-size:13px;opacity:.85;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
|
982
|
+
.name{font-weight:600;font-size:16px;line-height:1.6;margin:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
|
983
|
+
.subtitle{font-size:13px;line-height:1.6;opacity:.85;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
|
246
984
|
.close{flex:none;width:36px;height:36px;border:0;border-radius:50%;background:transparent;color:inherit;cursor:pointer;display:flex;align-items:center;justify-content:center;padding:0}
|
|
247
985
|
.close:hover{background:rgba(127,127,127,.2)}
|
|
248
986
|
.close svg{width:22px;height:22px}
|
|
@@ -259,7 +997,7 @@ var CSS = `
|
|
|
259
997
|
@keyframes kc-dot{50%{opacity:.3}}
|
|
260
998
|
.msg img{display:block;max-width:100%;height:auto;border-radius:10px;margin-block-end:6px}
|
|
261
999
|
.chips{flex:none;display:flex;flex-wrap:wrap;gap:6px;padding:4px 14px 10px;max-height:40%;overflow-y:auto}
|
|
262
|
-
.chip{border:1px solid var(--kc-accent);background:#fff;color:var(--kc-accent);border-radius:999px;padding:6px 13px;cursor:pointer;font-size:14px;line-height:1.
|
|
1000
|
+
.chip{border:1px solid var(--kc-accent);background:#fff;color:var(--kc-accent);border-radius:999px;padding:6px 13px;cursor:pointer;font-size:14px;line-height:1.4;text-align:start}
|
|
263
1001
|
.chip:hover{background:var(--kc-primary);color:var(--kc-on-primary);border-color:var(--kc-primary)}
|
|
264
1002
|
.chip-home,.chip-back{border-color:#cfd4da;color:#4b5563}
|
|
265
1003
|
.composer{flex:none;display:flex;align-items:center;gap:8px;padding:8px 10px;border-block-start:1px solid #e3e6ea;background:#fff}
|
|
@@ -288,6 +1026,9 @@ var CSS = `
|
|
|
288
1026
|
@media (prefers-reduced-motion:reduce){.kc *{animation:none!important;transition:none!important}}
|
|
289
1027
|
`;
|
|
290
1028
|
|
|
1029
|
+
// src/live-load.ts
|
|
1030
|
+
var loadLive = () => Promise.resolve().then(() => (init_live(), live_exports));
|
|
1031
|
+
|
|
291
1032
|
// src/widget.ts
|
|
292
1033
|
var DEFAULT_API = "https://api.kavchat.com";
|
|
293
1034
|
var BRANDING_URL = "https://kavchat.com";
|
|
@@ -323,7 +1064,7 @@ function pickLocale(requested, releaseLocale) {
|
|
|
323
1064
|
const fallback = baseLocale(releaseLocale);
|
|
324
1065
|
return UI_TEXTS[fallback] ? fallback : "en";
|
|
325
1066
|
}
|
|
326
|
-
var
|
|
1067
|
+
var EMAIL_RE2 = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
327
1068
|
var KavchatWidget = class {
|
|
328
1069
|
constructor(options) {
|
|
329
1070
|
this.release = null;
|
|
@@ -331,7 +1072,7 @@ var KavchatWidget = class {
|
|
|
331
1072
|
this.ui = null;
|
|
332
1073
|
this.state = null;
|
|
333
1074
|
this.session = null;
|
|
334
|
-
this.t =
|
|
1075
|
+
this.t = uiTexts2("en");
|
|
335
1076
|
this.locale = "en";
|
|
336
1077
|
this.isOpen = false;
|
|
337
1078
|
this.prevFocus = null;
|
|
@@ -340,6 +1081,7 @@ var KavchatWidget = class {
|
|
|
340
1081
|
this.destroyed = false;
|
|
341
1082
|
/** מחכים לתשובת AI: מציגים "מקליד" ומתעלמים מקלט חדש */
|
|
342
1083
|
this.waiting = false;
|
|
1084
|
+
this.live = null;
|
|
343
1085
|
this.onPageHide = () => this.flush(true);
|
|
344
1086
|
this.onVisibility = () => {
|
|
345
1087
|
if (document.visibilityState === "hidden") this.flush(true);
|
|
@@ -410,6 +1152,7 @@ var KavchatWidget = class {
|
|
|
410
1152
|
if (this.reportTimer) clearTimeout(this.reportTimer);
|
|
411
1153
|
window.removeEventListener("pagehide", this.onPageHide);
|
|
412
1154
|
document.removeEventListener("visibilitychange", this.onVisibility);
|
|
1155
|
+
this.setupLive();
|
|
413
1156
|
this.unmount();
|
|
414
1157
|
}
|
|
415
1158
|
// ---------- טעינה ----------
|
|
@@ -456,16 +1199,40 @@ var KavchatWidget = class {
|
|
|
456
1199
|
const changed = !this.release || this.release.version !== release.version;
|
|
457
1200
|
this.release = release;
|
|
458
1201
|
this.locale = pickLocale(this.opts.locale, release.locale);
|
|
459
|
-
this.t =
|
|
1202
|
+
this.t = uiTexts2(this.locale);
|
|
460
1203
|
if (changed || !this.ui) {
|
|
461
1204
|
this.state = null;
|
|
462
1205
|
this.restoreSession();
|
|
463
1206
|
this.mount();
|
|
1207
|
+
this.setupLive();
|
|
464
1208
|
}
|
|
465
1209
|
if (this.ui) this.ui.banner.hidden = !this.offline;
|
|
466
1210
|
this.emit({ type: "ready", version: release.version, offline });
|
|
467
1211
|
if (this.wantOpen) this.open();
|
|
468
1212
|
}
|
|
1213
|
+
/**
|
|
1214
|
+
* הצ'אט עם נציג נטען רק כשהוא מופעל בגרסה, או כשיש שיחה פתוחה שמורה. כל mount מקבל בקר חדש,
|
|
1215
|
+
* שמשחזר את השיחה מהאחסון. כישלון בטעינה משאיר את הבוט ואת טופס הפנייה כמו שהם
|
|
1216
|
+
*/
|
|
1217
|
+
setupLive() {
|
|
1218
|
+
if (this.live) this.live.destroy();
|
|
1219
|
+
this.live = null;
|
|
1220
|
+
const ui = this.ui;
|
|
1221
|
+
const lc = this.release && this.release.liveChat;
|
|
1222
|
+
let saved;
|
|
1223
|
+
try {
|
|
1224
|
+
saved = localStorage.getItem(`kavchat:live:${this.opts.publicKey}`);
|
|
1225
|
+
} catch (e) {
|
|
1226
|
+
}
|
|
1227
|
+
if (this.destroyed || !ui || !(lc && lc.enabled || saved)) return;
|
|
1228
|
+
loadLive().then((m) => {
|
|
1229
|
+
if (this.ui !== ui || this.destroyed) return;
|
|
1230
|
+
this.live = m.createLive(this);
|
|
1231
|
+
void this.live.restore();
|
|
1232
|
+
this.renderChips();
|
|
1233
|
+
}, () => {
|
|
1234
|
+
});
|
|
1235
|
+
}
|
|
469
1236
|
restoreSession() {
|
|
470
1237
|
const release = this.release;
|
|
471
1238
|
if (!release) return;
|
|
@@ -541,7 +1308,9 @@ var KavchatWidget = class {
|
|
|
541
1308
|
handleEffect(effect) {
|
|
542
1309
|
var _a, _b, _c;
|
|
543
1310
|
this.emit({ type: "effect", effect });
|
|
544
|
-
if (effect.type === "
|
|
1311
|
+
if (effect.type === "live" && this.live) {
|
|
1312
|
+
void this.live.start(effect);
|
|
1313
|
+
} else if (effect.type === "contact" || effect.type === "live") {
|
|
545
1314
|
this.showForm(effect);
|
|
546
1315
|
} else if (effect.type === "link") {
|
|
547
1316
|
openLink(effect.url, (_a = this.ui) == null ? void 0 : _a.root);
|
|
@@ -652,12 +1421,12 @@ var KavchatWidget = class {
|
|
|
652
1421
|
}
|
|
653
1422
|
focusInside() {
|
|
654
1423
|
const ui = this.ui;
|
|
655
|
-
if (!ui) return;
|
|
656
|
-
const
|
|
1424
|
+
if (!ui || this.live && this.live.focus()) return;
|
|
1425
|
+
const coarse2 = typeof window.matchMedia === "function" && window.matchMedia("(pointer: coarse)").matches;
|
|
657
1426
|
if (!ui.formView.hidden) {
|
|
658
1427
|
const first = ui.formView.querySelector("input,select,textarea");
|
|
659
1428
|
(first || ui.panel).focus();
|
|
660
|
-
} else if (
|
|
1429
|
+
} else if (coarse2) ui.panel.focus();
|
|
661
1430
|
else ui.input.focus();
|
|
662
1431
|
}
|
|
663
1432
|
render() {
|
|
@@ -690,12 +1459,13 @@ var KavchatWidget = class {
|
|
|
690
1459
|
const state = this.state;
|
|
691
1460
|
if (!ui || !state) return;
|
|
692
1461
|
ui.chips.textContent = "";
|
|
693
|
-
|
|
1462
|
+
const chips = this.live ? this.live.chips(state.chips) : state.chips;
|
|
1463
|
+
for (const chip of chips) {
|
|
694
1464
|
const b = h("button", { type: "button", class: `chip chip-${chip.kind}` }, chip.label);
|
|
695
1465
|
b.addEventListener("click", () => this.onChip(chip));
|
|
696
1466
|
ui.chips.appendChild(b);
|
|
697
1467
|
}
|
|
698
|
-
ui.chips.hidden =
|
|
1468
|
+
ui.chips.hidden = chips.length === 0;
|
|
699
1469
|
}
|
|
700
1470
|
showChat() {
|
|
701
1471
|
const ui = this.ui;
|
|
@@ -706,7 +1476,8 @@ var KavchatWidget = class {
|
|
|
706
1476
|
this.render();
|
|
707
1477
|
if (this.isOpen) this.focusInside();
|
|
708
1478
|
}
|
|
709
|
-
|
|
1479
|
+
/** pre: מה שהמשתמש כבר כתב בבקשה לנציג, ולמה אין נציג (note) */
|
|
1480
|
+
showForm(effect, pre = {}) {
|
|
710
1481
|
const ui = this.ui;
|
|
711
1482
|
const release = this.release;
|
|
712
1483
|
if (!ui || !release) return;
|
|
@@ -714,9 +1485,12 @@ var KavchatWidget = class {
|
|
|
714
1485
|
const topics = release.settings.contactTopics || [];
|
|
715
1486
|
const field = (id, label, control) => h("div", { class: "field" }, h("label", { for: id }, label), control);
|
|
716
1487
|
const name = h("input", { id: "kc-name", type: "text", autocomplete: "name", maxlength: "100", dir: "auto" });
|
|
717
|
-
name.value = this.opts.userName || "";
|
|
1488
|
+
name.value = pre.name || this.opts.userName || "";
|
|
718
1489
|
const email = h("input", { id: "kc-email", type: "email", autocomplete: "email", dir: "auto" });
|
|
1490
|
+
email.value = pre.email || "";
|
|
719
1491
|
const phone = h("input", { id: "kc-phone", type: "tel", autocomplete: "tel", dir: "auto" });
|
|
1492
|
+
phone.value = pre.phone || "";
|
|
1493
|
+
const note = "note" in pre ? pre.note : this.live && this.live.note();
|
|
720
1494
|
let topic = null;
|
|
721
1495
|
if (topics.length) {
|
|
722
1496
|
topic = h("select", { id: "kc-topic" }, h("option", { value: "" }, ""));
|
|
@@ -724,7 +1498,7 @@ var KavchatWidget = class {
|
|
|
724
1498
|
if (effect.contactTopicId && topics.some((tp) => tp.id === effect.contactTopicId)) topic.value = effect.contactTopicId;
|
|
725
1499
|
}
|
|
726
1500
|
const message = h("textarea", { id: "kc-message", required: true, maxlength: "2000", dir: "auto" });
|
|
727
|
-
message.value = effect.prefill || "";
|
|
1501
|
+
message.value = pre.message || effect.prefill || "";
|
|
728
1502
|
const error = h("div", { class: "error", role: "alert" });
|
|
729
1503
|
const submit = h("button", { type: "submit", class: "btn btn-primary" }, t["form.submit"]);
|
|
730
1504
|
const cancel = h("button", { type: "button", class: "btn" }, t["form.cancel"]);
|
|
@@ -733,6 +1507,7 @@ var KavchatWidget = class {
|
|
|
733
1507
|
"form",
|
|
734
1508
|
{ class: "form", novalidate: true },
|
|
735
1509
|
h("h2", null, t["form.title"]),
|
|
1510
|
+
note ? h("p", { class: "note", role: "status" }, note) : null,
|
|
736
1511
|
field("kc-name", t["form.name"], name),
|
|
737
1512
|
field("kc-email", t["form.email"], email),
|
|
738
1513
|
field("kc-phone", t["form.phone"], phone),
|
|
@@ -754,7 +1529,7 @@ var KavchatWidget = class {
|
|
|
754
1529
|
sessionId: (_a = this.session) == null ? void 0 : _a.id,
|
|
755
1530
|
transcript: effect.transcript.slice(-200).map((x) => ({ ...x, text: x.text.slice(0, 1e3) }))
|
|
756
1531
|
};
|
|
757
|
-
const problem = !input.message ? t["form.required"] : !input.email && !input.phone && !this.opts.userToken ? t["form.contactRequired"] : input.email && !
|
|
1532
|
+
const problem = !input.message ? t["form.required"] : !input.email && !input.phone && !this.opts.userToken ? t["form.contactRequired"] : input.email && !EMAIL_RE2.test(input.email) ? t["form.emailInvalid"] : "";
|
|
758
1533
|
if (problem) {
|
|
759
1534
|
error.textContent = problem;
|
|
760
1535
|
return;
|
|
@@ -871,8 +1646,8 @@ function installGlobal(win = window) {
|
|
|
871
1646
|
win.Kavchat = api;
|
|
872
1647
|
return { api, queue };
|
|
873
1648
|
}
|
|
874
|
-
function run(api,
|
|
875
|
-
const [name, ...args] =
|
|
1649
|
+
function run(api, call2) {
|
|
1650
|
+
const [name, ...args] = call2;
|
|
876
1651
|
if (!METHODS.includes(name)) {
|
|
877
1652
|
console.warn(`Kavchat: unknown method ${name}`);
|
|
878
1653
|
return;
|