@spilki/widget 0.1.3 → 1.0.26
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 +1 -1
- package/dist/bootstrap.es.js +356 -219
- package/dist/bootstrap.es.js.map +1 -1
- package/dist/bootstrap.umd.js +21 -11
- package/dist/bootstrap.umd.js.map +1 -1
- package/dist/core/jwt.d.ts +10 -0
- package/dist/core/jwt.d.ts.map +1 -1
- package/dist/core/state.d.ts +10 -0
- package/dist/core/state.d.ts.map +1 -1
- package/dist/core/transport.d.ts +10 -3
- package/dist/core/transport.d.ts.map +1 -1
- package/dist/core/utils.d.ts +1 -0
- package/dist/core/utils.d.ts.map +1 -1
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/types.d.ts +5 -4
- package/dist/types.d.ts.map +1 -1
- package/dist/ui/bubble.d.ts +9 -0
- package/dist/ui/bubble.d.ts.map +1 -1
- package/dist/ui/panel.d.ts +17 -0
- package/dist/ui/panel.d.ts.map +1 -1
- package/dist/widget.es.js +357 -220
- package/dist/widget.es.js.map +1 -1
- package/dist/widget.umd.js +21 -11
- package/dist/widget.umd.js.map +1 -1
- package/package.json +5 -2
package/dist/widget.es.js
CHANGED
|
@@ -1,9 +1,16 @@
|
|
|
1
|
-
const
|
|
1
|
+
const W = `
|
|
2
2
|
<style>
|
|
3
3
|
:host {
|
|
4
4
|
all: initial;
|
|
5
5
|
position: fixed;
|
|
6
6
|
z-index: 2147483000;
|
|
7
|
+
bottom: 24px;
|
|
8
|
+
}
|
|
9
|
+
:host([data-position="bottom-right"]) {
|
|
10
|
+
right: 24px;
|
|
11
|
+
}
|
|
12
|
+
:host([data-position="bottom-left"]) {
|
|
13
|
+
left: 24px;
|
|
7
14
|
}
|
|
8
15
|
button {
|
|
9
16
|
all: unset;
|
|
@@ -44,11 +51,11 @@ const O = `
|
|
|
44
51
|
</span>
|
|
45
52
|
</button>
|
|
46
53
|
`;
|
|
47
|
-
function
|
|
54
|
+
function L(r) {
|
|
48
55
|
const e = document.createElement("div");
|
|
49
|
-
e.setAttribute("part", "bubble-root"), e.
|
|
56
|
+
e.setAttribute("part", "bubble-root"), e.setAttribute("data-position", r.position), e.style.setProperty("--spilki-accent", r.color);
|
|
50
57
|
const t = e.attachShadow({ mode: "open" });
|
|
51
|
-
t.innerHTML =
|
|
58
|
+
t.innerHTML = W;
|
|
52
59
|
const s = t.querySelector("button");
|
|
53
60
|
return s.addEventListener("click", () => r.onClick()), {
|
|
54
61
|
element: e,
|
|
@@ -58,41 +65,103 @@ function A(r) {
|
|
|
58
65
|
destroy() {
|
|
59
66
|
e.remove();
|
|
60
67
|
},
|
|
61
|
-
setOpen(
|
|
62
|
-
s.setAttribute("aria-expanded", String(
|
|
68
|
+
setOpen(o) {
|
|
69
|
+
s.setAttribute("aria-expanded", String(o)), s.setAttribute("aria-label", o ? "Close chat" : "Open chat");
|
|
63
70
|
}
|
|
64
71
|
};
|
|
65
72
|
}
|
|
66
|
-
const
|
|
67
|
-
|
|
73
|
+
const B = "https://api.spilki.ai", S = {
|
|
74
|
+
welcome: "Hi! I'm your assistant.",
|
|
75
|
+
placeholder: "Type a message…",
|
|
76
|
+
sendLabel: "Send",
|
|
77
|
+
typing: "Assistant is typing…",
|
|
78
|
+
offline: "Unable to connect. Please try again later.",
|
|
79
|
+
title: "Spilki Assistant"
|
|
80
|
+
}, g = {
|
|
81
|
+
apiBase: B,
|
|
82
|
+
position: "bottom-right",
|
|
83
|
+
theme: "auto",
|
|
84
|
+
color: "#6366f1",
|
|
85
|
+
welcome: S.welcome,
|
|
86
|
+
persist: !0,
|
|
87
|
+
i18n: S
|
|
88
|
+
};
|
|
89
|
+
function K(r) {
|
|
90
|
+
var t, s, i, o, c, a, n;
|
|
91
|
+
const e = { ...S, ...(t = r.i18n) != null ? t : {} };
|
|
92
|
+
return {
|
|
93
|
+
...g,
|
|
94
|
+
...r,
|
|
95
|
+
apiBase: (s = r.apiBase) != null ? s : g.apiBase,
|
|
96
|
+
i18n: e,
|
|
97
|
+
welcome: (i = r.welcome) != null ? i : e.welcome,
|
|
98
|
+
position: (o = r.position) != null ? o : g.position,
|
|
99
|
+
theme: (c = r.theme) != null ? c : g.theme,
|
|
100
|
+
color: (a = r.color) != null ? a : g.color,
|
|
101
|
+
persist: (n = r.persist) != null ? n : g.persist
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
function P(r = "msg") {
|
|
105
|
+
return typeof crypto != "undefined" && crypto.randomUUID ? crypto.randomUUID() : `${r}-${Math.random().toString(16).slice(2)}`;
|
|
106
|
+
}
|
|
107
|
+
function D() {
|
|
108
|
+
var r, e;
|
|
109
|
+
return (e = (r = window.matchMedia) == null ? void 0 : r.call(window, "(prefers-color-scheme: dark)").matches) != null ? e : !1;
|
|
110
|
+
}
|
|
111
|
+
function C(r) {
|
|
112
|
+
return r === "light" || r === "dark" ? r : D() ? "dark" : "light";
|
|
113
|
+
}
|
|
114
|
+
function y(r, e = 30) {
|
|
115
|
+
return r.slice(-e);
|
|
116
|
+
}
|
|
117
|
+
function N(r) {
|
|
118
|
+
if (!r || r.length === 0) return;
|
|
119
|
+
const e = window.location.origin;
|
|
120
|
+
r.includes(e) || console.warn(
|
|
121
|
+
`SpilkiWidget: current origin ${e} not in allowedOriginsHint: ${r.join(", ")}`
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
const F = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
|
|
125
|
+
function H(r) {
|
|
126
|
+
const e = new Date(r), t = /* @__PURE__ */ new Date(), s = (n) => String(n).padStart(2, "0"), i = `${s(e.getHours())}:${s(e.getMinutes())}`, o = new Date(t.getFullYear(), t.getMonth(), t.getDate()).getTime(), c = o - 864e5, a = new Date(e.getFullYear(), e.getMonth(), e.getDate()).getTime();
|
|
127
|
+
return a === o ? `Today at ${i}` : a === c ? `Yesterday at ${i}` : `${F[e.getMonth()]} ${e.getDate()}, ${i}`;
|
|
128
|
+
}
|
|
129
|
+
const U = ':host{--spilki-bg-light: #ffffff;--spilki-bg-dark: #0f172a;--spilki-text-light: #0f172a;--spilki-text-dark: #f8fafc;--spilki-border-light: rgba(15, 23, 42, .1);--spilki-border-dark: rgba(148, 163, 184, .25);--spilki-shadow: 0 10px 40px rgba(15, 23, 42, .2);--spilki-radius: 16px;--spilki-font: "Inter", system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;color:inherit;font-family:var(--spilki-font)}.wrapper{width:100%;height:100%;display:flex;flex-direction:column;background:var(--spilki-surface);color:var(--spilki-text);border-radius:var(--spilki-radius);box-shadow:var(--spilki-shadow);border:1px solid var(--spilki-border);overflow:hidden}header{display:flex;align-items:center;justify-content:space-between;padding:.75rem 1rem;background:var(--spilki-surface);border-bottom:1px solid var(--spilki-border)}header h1{font-size:1rem;margin:0;display:flex;align-items:center;gap:.5rem}header button.close{border:none;background:transparent;color:inherit;font-size:1.25rem;cursor:pointer}header button.close:focus-visible{outline:2px solid var(--spilki-accent);outline-offset:2px;border-radius:4px}header .status-dot{width:10px;height:10px;border-radius:999px;background:var(--spilki-accent)}.messages{flex:1;overflow-y:auto;padding:1rem;display:flex;flex-direction:column;gap:.5rem;scrollbar-width:thin;scrollbar-color:rgba(148,163,184,.3) transparent}.message{display:flex;flex-direction:column;gap:.25rem;max-width:85%;line-height:1.4;word-wrap:break-word;overflow-wrap:anywhere;white-space:pre-wrap}.message.user{align-self:flex-end;text-align:right}.message .bubble{padding:.6rem .8rem;border-radius:1rem;background:#6366f126}.message.user .bubble{background:var(--spilki-accent);color:#fff}.message.bot .bubble{background:#94a3b826}.input-area{display:flex;align-items:flex-end;gap:.5rem;padding:.75rem 1rem;border-top:1px solid var(--spilki-border)}.input-area textarea{flex:1;resize:none;min-height:2.5rem;max-height:6rem;border-radius:.75rem;border:1px solid var(--spilki-border);padding:.6rem .75rem;font-family:inherit;font-size:.95rem;background:var(--spilki-surface);color:var(--spilki-text);scrollbar-width:thin;.input-area textarea:focus-visible{outline:2px solid var(--spilki-accent);outline-offset:-1px}scrollbar-color:rgba(148,163,184,.3) transparent}.input-area button{border:none;border-radius:999px;padding:.6rem 1.1rem;background:var(--spilki-accent);color:#fff;font-weight:600;cursor:pointer}.input-area button:focus-visible{outline:2px solid #fff;outline-offset:2px}.typing{font-size:.75rem;color:#94a3b8e6;padding:0 1rem .75rem}.offline{font-size:.8rem;padding:0 1rem;color:#f97316}:host([data-theme="dark"]){--spilki-surface: var(--spilki-bg-dark);--spilki-text: var(--spilki-text-dark);--spilki-border: var(--spilki-border-dark)}:host([data-theme="dark"]) .messages,:host([data-theme="dark"]) .input-area textarea{scrollbar-color:rgba(255,255,255,.2) transparent}:host([data-theme="light"]){--spilki-surface: var(--spilki-bg-light);--spilki-text: var(--spilki-text-light);--spilki-border: var(--spilki-border-light)}:host([data-theme="dark"]) .message .bubble{background:#94a3b81f}:host([data-theme="dark"]) .message.bot .bubble{background:#6366f126}:host([data-theme="dark"]) .input-area textarea{background:#0f172ad9}.messages::-webkit-scrollbar,.input-area textarea::-webkit-scrollbar{width:6px}.messages::-webkit-scrollbar-track,.input-area textarea::-webkit-scrollbar-track{background:transparent}.messages::-webkit-scrollbar-thumb,.input-area textarea::-webkit-scrollbar-thumb{background:#94a3b84d;border-radius:999px}.messages::-webkit-scrollbar-thumb:hover,.input-area textarea::-webkit-scrollbar-thumb:hover{background:#94a3b880}:host([data-theme="dark"]) .messages::-webkit-scrollbar-thumb,:host([data-theme="dark"]) .input-area textarea::-webkit-scrollbar-thumb{background:#fff3}:host([data-theme="dark"]) .messages::-webkit-scrollbar-thumb:hover,:host([data-theme="dark"]) .input-area textarea::-webkit-scrollbar-thumb:hover{background:#ffffff59}.messages{scroll-behavior:smooth}.conversation-separator{display:flex;align-items:center;gap:.5rem;padding:.5rem 0;cursor:pointer;user-select:none;font-size:.7rem;color:#94a3b8b3;white-space:nowrap}.conversation-separator:before,.conversation-separator:after{content:"";flex:1;height:1px;background:var(--spilki-border)}.conversation-separator:hover{color:#94a3b8e6}.conversation-separator:focus-visible{outline:2px solid var(--spilki-accent);outline-offset:2px;border-radius:4px}.conversation-history{display:none;flex-direction:column;gap:.5rem}.conversation-history.expanded{display:flex}';
|
|
130
|
+
function b(r) {
|
|
131
|
+
return r.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
132
|
+
}
|
|
133
|
+
class z {
|
|
68
134
|
constructor(e) {
|
|
69
|
-
this.options = e, this.focusable = [], this.open = !1, this.host = document.createElement("div"), this.host.setAttribute("part", "panel-root"), this.host.style.position = "fixed", this.host.style.bottom = "96px", this.host.style[e.position === "bottom-right" ? "right" : "left"] = "24px", this.host.style.width = "360px", this.host.style.maxWidth = "calc(100vw - 32px)", this.host.style.height = "520px", this.host.style.display = "none", this.host.style.zIndex = "2147483001", this.shadow = this.host.attachShadow({ mode: "open" })
|
|
70
|
-
|
|
71
|
-
|
|
135
|
+
this.options = e, this.focusable = [], this.seenIds = /* @__PURE__ */ new Set(), this.open = !1, this.host = document.createElement("div"), this.host.setAttribute("part", "panel-root"), this.host.style.position = "fixed", this.host.style.bottom = "96px", this.host.style[e.position === "bottom-right" ? "right" : "left"] = "24px", this.host.style.width = "360px", this.host.style.maxWidth = "calc(100vw - 32px)", this.host.style.height = "520px", this.host.style.display = "none", this.host.style.zIndex = "2147483001", this.shadow = this.host.attachShadow({ mode: "open" });
|
|
136
|
+
const t = b(e.i18n.title), s = b(e.i18n.typing), i = b(e.i18n.offline), o = b(e.i18n.placeholder), c = b(e.i18n.sendLabel);
|
|
137
|
+
this.shadow.innerHTML = `
|
|
138
|
+
<style>${U}</style>
|
|
139
|
+
<div class="wrapper" role="dialog" aria-modal="true" aria-label="${t}">
|
|
72
140
|
<header>
|
|
73
|
-
<h1><span class="status-dot" aria-hidden="true"></span>${
|
|
141
|
+
<h1><span class="status-dot" aria-hidden="true"></span>${t}</h1>
|
|
74
142
|
<button class="close" type="button" aria-label="Close">×</button>
|
|
75
143
|
</header>
|
|
76
|
-
<div class="messages" part="messages"></div>
|
|
77
|
-
<div class="typing" hidden>${
|
|
78
|
-
<div class="offline" hidden>${
|
|
144
|
+
<div class="messages" part="messages" role="log" aria-live="polite" aria-label="Chat messages"></div>
|
|
145
|
+
<div class="typing" hidden aria-live="polite" aria-atomic="true">${s}</div>
|
|
146
|
+
<div class="offline" hidden aria-live="assertive" aria-atomic="true">${i}</div>
|
|
79
147
|
<div class="input-area">
|
|
80
|
-
<textarea rows="2" placeholder="${
|
|
81
|
-
<button type="button">${
|
|
148
|
+
<textarea rows="2" placeholder="${o}" aria-label="${o}"></textarea>
|
|
149
|
+
<button type="button">${c}</button>
|
|
82
150
|
</div>
|
|
83
151
|
</div>
|
|
84
|
-
`, this.host.dataset.theme = e.theme, this.host.style.setProperty("--spilki-accent", e.color), this.messagesEl = this.shadow.querySelector(".messages"), this.typingEl = this.shadow.querySelector(".typing"), this.input = this.shadow.querySelector("textarea"), this.offlineEl = this.shadow.querySelector(".offline"), this.sendButton = this.shadow.querySelector(".input-area button"), this.shadow.querySelector("header .close")
|
|
85
|
-
|
|
86
|
-
}
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
}), this.
|
|
152
|
+
`, this.host.dataset.theme = e.theme, this.host.style.setProperty("--spilki-accent", e.color), this.messagesEl = this.shadow.querySelector(".messages"), this.typingEl = this.shadow.querySelector(".typing"), this.input = this.shadow.querySelector("textarea"), this.offlineEl = this.shadow.querySelector(".offline"), this.sendButton = this.shadow.querySelector(".input-area button"), this.closeButton = this.shadow.querySelector("header .close"), this.handleCloseClick = () => this.options.onClose(), this.handleSendClick = () => this.send(), this.handleInputKeydown = (a) => {
|
|
153
|
+
a.key === "Enter" && !a.shiftKey ? (a.preventDefault(), this.send()) : a.key === "Escape" && this.options.onClose();
|
|
154
|
+
}, this.handleShadowKeydown = (a) => {
|
|
155
|
+
const n = a;
|
|
156
|
+
n.key === "Escape" && this.options.onClose(), n.key === "Tab" && this.trapFocus(n);
|
|
157
|
+
}, this.handleFocusin = () => this.collectFocusable(), this.closeButton.addEventListener("click", this.handleCloseClick), this.sendButton.addEventListener("click", this.handleSendClick), this.input.addEventListener("keydown", this.handleInputKeydown), this.shadow.addEventListener("keydown", this.handleShadowKeydown), this.shadow.addEventListener("focusin", this.handleFocusin), this.collectFocusable();
|
|
90
158
|
}
|
|
91
159
|
mount() {
|
|
92
160
|
document.body.appendChild(this.host);
|
|
93
161
|
}
|
|
162
|
+
// #4: remove all event listeners before removing DOM
|
|
94
163
|
destroy() {
|
|
95
|
-
this.host.remove();
|
|
164
|
+
this.closeButton.removeEventListener("click", this.handleCloseClick), this.sendButton.removeEventListener("click", this.handleSendClick), this.input.removeEventListener("keydown", this.handleInputKeydown), this.shadow.removeEventListener("keydown", this.handleShadowKeydown), this.shadow.removeEventListener("focusin", this.handleFocusin), this.host.remove();
|
|
96
165
|
}
|
|
97
166
|
show() {
|
|
98
167
|
this.open || (this.open = !0, this.host.style.display = "block", this.focusInput());
|
|
@@ -108,19 +177,27 @@ class C {
|
|
|
108
177
|
updateTheme(e) {
|
|
109
178
|
this.host.dataset.theme = e;
|
|
110
179
|
}
|
|
180
|
+
// #16: rebuild seen IDs on full re-render
|
|
111
181
|
updateMessages(e) {
|
|
112
|
-
this.messagesEl.innerHTML = "", e.forEach((t) => {
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
182
|
+
this.messagesEl.innerHTML = "", this.seenIds.clear(), e.forEach((t) => {
|
|
183
|
+
this.seenIds.add(t.id), this.messagesEl.appendChild(this.createMessageElement(t));
|
|
184
|
+
}), this.messagesEl.scrollTop = this.messagesEl.scrollHeight, this.collectFocusable();
|
|
185
|
+
}
|
|
186
|
+
// Render conversation groups with collapsed history separators
|
|
187
|
+
renderWithConversations(e, t) {
|
|
188
|
+
this.messagesEl.innerHTML = "", this.seenIds.clear();
|
|
189
|
+
for (const s of e) {
|
|
190
|
+
s.messages.forEach((a) => this.seenIds.add(a.id));
|
|
191
|
+
const i = this.createHistoryContainer(s.messages), o = s.messages[s.messages.length - 1].ts, c = this.createSeparatorElement(o, i);
|
|
192
|
+
this.messagesEl.appendChild(i), this.messagesEl.appendChild(c);
|
|
193
|
+
}
|
|
194
|
+
t.forEach((s) => {
|
|
195
|
+
this.seenIds.add(s.id), this.messagesEl.appendChild(this.createMessageElement(s));
|
|
196
|
+
}), this.messagesEl.scrollTop = this.messagesEl.scrollHeight, this.collectFocusable();
|
|
118
197
|
}
|
|
198
|
+
// #16: deduplicate; #19: smart scroll; #21: update focus trap
|
|
119
199
|
appendMessage(e) {
|
|
120
|
-
|
|
121
|
-
t.className = `message ${e.author}`, t.setAttribute("data-author", e.author);
|
|
122
|
-
const s = document.createElement("div");
|
|
123
|
-
s.className = "bubble", s.textContent = e.text, t.appendChild(s), this.messagesEl.appendChild(t), this.messagesEl.scrollTop = this.messagesEl.scrollHeight;
|
|
200
|
+
this.seenIds.has(e.id) || (this.seenIds.add(e.id), this.messagesEl.appendChild(this.createMessageElement(e)), this.scrollToBottomIfNeeded(), this.collectFocusable());
|
|
124
201
|
}
|
|
125
202
|
setTyping(e) {
|
|
126
203
|
this.typingEl.toggleAttribute("hidden", !e);
|
|
@@ -135,6 +212,43 @@ class C {
|
|
|
135
212
|
const e = this.input.value.trim();
|
|
136
213
|
e && (this.options.onSend(e), this.clearInput());
|
|
137
214
|
}
|
|
215
|
+
// #29 #34: extract shared message element creation; #25: semantic labels
|
|
216
|
+
createMessageElement(e) {
|
|
217
|
+
const t = document.createElement("div");
|
|
218
|
+
t.className = `message ${e.author}`, t.setAttribute("data-author", e.author), t.setAttribute("role", "article"), t.setAttribute(
|
|
219
|
+
"aria-label",
|
|
220
|
+
// #25
|
|
221
|
+
e.author === "user" ? "You" : "Assistant"
|
|
222
|
+
);
|
|
223
|
+
const s = document.createElement("div");
|
|
224
|
+
return s.className = "bubble", s.textContent = e.text, t.appendChild(s), t;
|
|
225
|
+
}
|
|
226
|
+
createSeparatorElement(e, t) {
|
|
227
|
+
const s = document.createElement("div");
|
|
228
|
+
s.className = "conversation-separator", s.setAttribute("role", "button"), s.setAttribute("tabindex", "0"), s.setAttribute("aria-expanded", "false"), s.setAttribute("aria-label", "Show previous conversation"), s.textContent = H(e);
|
|
229
|
+
const i = () => {
|
|
230
|
+
const o = t.classList.toggle("expanded");
|
|
231
|
+
s.setAttribute("aria-expanded", String(o)), s.setAttribute(
|
|
232
|
+
"aria-label",
|
|
233
|
+
o ? "Hide previous conversation" : "Show previous conversation"
|
|
234
|
+
);
|
|
235
|
+
};
|
|
236
|
+
return s.addEventListener("click", i), s.addEventListener("keydown", (o) => {
|
|
237
|
+
const c = o;
|
|
238
|
+
(c.key === "Enter" || c.key === " ") && (c.preventDefault(), i());
|
|
239
|
+
}), s;
|
|
240
|
+
}
|
|
241
|
+
createHistoryContainer(e) {
|
|
242
|
+
const t = document.createElement("div");
|
|
243
|
+
return t.className = "conversation-history", e.forEach((s) => {
|
|
244
|
+
t.appendChild(this.createMessageElement(s));
|
|
245
|
+
}), t;
|
|
246
|
+
}
|
|
247
|
+
// #19: only auto-scroll if user is near the bottom
|
|
248
|
+
scrollToBottomIfNeeded() {
|
|
249
|
+
const e = this.messagesEl;
|
|
250
|
+
e.scrollHeight - e.scrollTop - e.clientHeight < 100 && (e.scrollTop = e.scrollHeight);
|
|
251
|
+
}
|
|
138
252
|
collectFocusable() {
|
|
139
253
|
const e = this.shadow.querySelectorAll(
|
|
140
254
|
'button, textarea, [href], [tabindex]:not([tabindex="-1"])'
|
|
@@ -149,61 +263,10 @@ class C {
|
|
|
149
263
|
e.shiftKey && i === t ? (e.preventDefault(), s.focus()) : !e.shiftKey && i === s && (e.preventDefault(), t.focus());
|
|
150
264
|
}
|
|
151
265
|
}
|
|
152
|
-
const
|
|
153
|
-
|
|
154
|
-
placeholder: "Type a message…",
|
|
155
|
-
sendLabel: "Send",
|
|
156
|
-
typing: "Assistant is typing…",
|
|
157
|
-
offline: "Unable to connect. Please try again later.",
|
|
158
|
-
title: "Spilki Assistant"
|
|
159
|
-
}, p = {
|
|
160
|
-
apiBase: W,
|
|
161
|
-
position: "bottom-right",
|
|
162
|
-
theme: "auto",
|
|
163
|
-
color: "#6366f1",
|
|
164
|
-
welcome: g.welcome,
|
|
165
|
-
persist: !0,
|
|
166
|
-
i18n: g
|
|
167
|
-
};
|
|
168
|
-
function $(r) {
|
|
169
|
-
var t, s, i, n, l, a, h;
|
|
170
|
-
const e = { ...g, ...(t = r.i18n) != null ? t : {} };
|
|
171
|
-
return {
|
|
172
|
-
...p,
|
|
173
|
-
...r,
|
|
174
|
-
apiBase: (s = r.apiBase) != null ? s : p.apiBase,
|
|
175
|
-
i18n: e,
|
|
176
|
-
welcome: (i = r.welcome) != null ? i : e.welcome,
|
|
177
|
-
position: (n = r.position) != null ? n : p.position,
|
|
178
|
-
theme: (l = r.theme) != null ? l : p.theme,
|
|
179
|
-
color: (a = r.color) != null ? a : p.color,
|
|
180
|
-
persist: (h = r.persist) != null ? h : p.persist
|
|
181
|
-
};
|
|
182
|
-
}
|
|
183
|
-
function B(r = "msg") {
|
|
184
|
-
return typeof crypto != "undefined" && crypto.randomUUID ? crypto.randomUUID() : `${r}-${Math.random().toString(16).slice(2)}`;
|
|
185
|
-
}
|
|
186
|
-
function L() {
|
|
187
|
-
var r, e;
|
|
188
|
-
return (e = (r = window.matchMedia) == null ? void 0 : r.call(window, "(prefers-color-scheme: dark)").matches) != null ? e : !1;
|
|
189
|
-
}
|
|
190
|
-
function x(r) {
|
|
191
|
-
return r === "light" || r === "dark" ? r : L() ? "dark" : "light";
|
|
192
|
-
}
|
|
193
|
-
function u(r, e = 30) {
|
|
194
|
-
return r.slice(-e);
|
|
195
|
-
}
|
|
196
|
-
function K(r) {
|
|
197
|
-
if (!r || r.length === 0) return;
|
|
198
|
-
const e = window.location.origin;
|
|
199
|
-
r.includes(e) || console.warn(
|
|
200
|
-
`SpilkiWidget: current origin ${e} not in allowedOriginsHint: ${r.join(", ")}`
|
|
201
|
-
);
|
|
202
|
-
}
|
|
203
|
-
const d = 1500, v = 8e3;
|
|
204
|
-
class P {
|
|
266
|
+
const f = 1500, I = 8e3;
|
|
267
|
+
class J {
|
|
205
268
|
constructor(e, t) {
|
|
206
|
-
this.sessionId = null, this.currentKind = null, this.stopped = !1, this.backoff =
|
|
269
|
+
this.sessionId = null, this.currentKind = null, this.stopped = !1, this.backoff = f, this.connectPromise = null, this.options = e, this.handlers = t;
|
|
207
270
|
}
|
|
208
271
|
setAccessToken(e) {
|
|
209
272
|
this.options.accessToken = e;
|
|
@@ -215,10 +278,20 @@ class P {
|
|
|
215
278
|
var e, t;
|
|
216
279
|
return (t = (e = this.sessionId) != null ? e : this.options.sessionId) != null ? t : null;
|
|
217
280
|
}
|
|
281
|
+
// #3: connect lock — return in-flight promise if already connecting
|
|
218
282
|
async connect() {
|
|
219
|
-
|
|
283
|
+
if (this.connectPromise) return this.connectPromise;
|
|
284
|
+
this.connectPromise = this.doConnect();
|
|
285
|
+
try {
|
|
286
|
+
return await this.connectPromise;
|
|
287
|
+
} finally {
|
|
288
|
+
this.connectPromise = null;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
async doConnect() {
|
|
292
|
+
var c, a;
|
|
220
293
|
this.stopped = !1;
|
|
221
|
-
const e = `${this.options.apiBase.replace(/\/$/, "")}/widget/session`, t = (a = (
|
|
294
|
+
const e = `${this.options.apiBase.replace(/\/$/, "")}/widget/session`, t = (a = (c = this.sessionId) != null ? c : this.options.sessionId) != null ? a : void 0, s = {
|
|
222
295
|
organisationId: this.options.org,
|
|
223
296
|
sessionId: t,
|
|
224
297
|
userAgent: typeof navigator != "undefined" ? navigator.userAgent : "",
|
|
@@ -228,40 +301,42 @@ class P {
|
|
|
228
301
|
method: "POST",
|
|
229
302
|
headers: {
|
|
230
303
|
"Content-Type": "application/json",
|
|
231
|
-
...this.options.accessToken ? { Authorization: `Bearer ${this.options.accessToken}` } : {}
|
|
304
|
+
...this.options.accessToken ? { "X-Authorization": `Bearer ${this.options.accessToken}` } : {}
|
|
232
305
|
},
|
|
233
306
|
body: JSON.stringify(s)
|
|
234
307
|
});
|
|
235
308
|
if (!i.ok)
|
|
236
309
|
throw new Error(`SpilkiWidget: connect failed (${i.status})`);
|
|
237
|
-
const
|
|
238
|
-
return this.sessionId =
|
|
239
|
-
}
|
|
240
|
-
async send(e) {
|
|
241
|
-
var
|
|
242
|
-
const
|
|
243
|
-
sessionId: (
|
|
244
|
-
text: e
|
|
310
|
+
const o = await i.json();
|
|
311
|
+
return this.sessionId = o.sessionId, this.options.sessionId = o.sessionId, this.backoff = f, await this.startTransport(o), o;
|
|
312
|
+
}
|
|
313
|
+
async send(e, t) {
|
|
314
|
+
var c, a;
|
|
315
|
+
const s = {
|
|
316
|
+
sessionId: (a = (c = this.sessionId) != null ? c : this.options.sessionId) != null ? a : "",
|
|
317
|
+
text: e,
|
|
318
|
+
...t ? { messageId: t } : {}
|
|
245
319
|
};
|
|
246
|
-
if (!
|
|
320
|
+
if (!s.sessionId)
|
|
247
321
|
throw new Error("SpilkiWidget: missing session id");
|
|
248
322
|
if (this.currentKind === "ws" && this.ws && this.ws.readyState === WebSocket.OPEN) {
|
|
249
|
-
this.ws.send(JSON.stringify({ type: "message", payload:
|
|
323
|
+
this.ws.send(JSON.stringify({ type: "message", payload: s }));
|
|
250
324
|
return;
|
|
251
325
|
}
|
|
252
|
-
const
|
|
326
|
+
const i = `${this.options.apiBase.replace(/\/$/, "")}/widget/message`, o = await fetch(i, {
|
|
253
327
|
method: "POST",
|
|
254
328
|
headers: {
|
|
255
329
|
"Content-Type": "application/json",
|
|
256
|
-
|
|
330
|
+
"X-Authorization": `Bearer ${this.options.accessToken}`
|
|
257
331
|
},
|
|
258
|
-
body: JSON.stringify(
|
|
332
|
+
body: JSON.stringify(s)
|
|
259
333
|
});
|
|
260
|
-
if (!
|
|
261
|
-
throw new Error(`SpilkiWidget: send failed (${
|
|
334
|
+
if (!o.ok)
|
|
335
|
+
throw new Error(`SpilkiWidget: send failed (${o.status})`);
|
|
262
336
|
}
|
|
263
|
-
|
|
264
|
-
|
|
337
|
+
// #1: accept resetBackoff param; #8 #9: clear retryTimer
|
|
338
|
+
stop(e = !0) {
|
|
339
|
+
this.stopped = !0, e && (this.backoff = f), this.retryTimer && (clearTimeout(this.retryTimer), this.retryTimer = void 0), this.ws && (this.wsOnOpen && this.ws.removeEventListener("open", this.wsOnOpen), this.wsOnMessage && this.ws.removeEventListener("message", this.wsOnMessage), this.wsOnClose && this.ws.removeEventListener("close", this.wsOnClose), this.wsOnError && this.ws.removeEventListener("error", this.wsOnError), this.ws.close(), this.ws = void 0), this.wsOnOpen = this.wsOnMessage = this.wsOnClose = this.wsOnError = void 0, this.sseAbort && (this.sseAbort.abort(), this.sseAbort = void 0), this.pollTimer && (clearTimeout(this.pollTimer), this.pollTimer = void 0), this.currentKind = null;
|
|
265
340
|
}
|
|
266
341
|
async startTransport(e) {
|
|
267
342
|
if (this.stopped) return;
|
|
@@ -276,72 +351,105 @@ class P {
|
|
|
276
351
|
}
|
|
277
352
|
throw new Error("SpilkiWidget: unable to establish transport");
|
|
278
353
|
}
|
|
354
|
+
// #5: store named handler refs for WS cleanup
|
|
279
355
|
startWs(e) {
|
|
280
356
|
return new Promise((t, s) => {
|
|
281
357
|
try {
|
|
282
358
|
const i = new WebSocket(e);
|
|
283
|
-
this.ws = i,
|
|
359
|
+
this.ws = i, this.wsOnOpen = () => {
|
|
284
360
|
if (this.stopped) {
|
|
285
361
|
i.close();
|
|
286
362
|
return;
|
|
287
363
|
}
|
|
288
|
-
this.currentKind = "ws", this.handlers.onOpen("ws"), this.backoff =
|
|
289
|
-
}
|
|
364
|
+
this.currentKind = "ws", this.handlers.onOpen("ws"), this.backoff = f, t();
|
|
365
|
+
}, this.wsOnMessage = (o) => this.handleIncoming(o.data), this.wsOnClose = () => {
|
|
290
366
|
this.stopped || this.retryFallback("ws");
|
|
291
|
-
}
|
|
367
|
+
}, this.wsOnError = () => {
|
|
292
368
|
this.handlers.onError(new Error("SpilkiWidget: websocket error")), i.readyState !== WebSocket.OPEN && s(new Error("WebSocket failed"));
|
|
293
|
-
});
|
|
369
|
+
}, i.addEventListener("open", this.wsOnOpen), i.addEventListener("message", this.wsOnMessage), i.addEventListener("close", this.wsOnClose), i.addEventListener("error", this.wsOnError);
|
|
294
370
|
} catch (i) {
|
|
295
371
|
s(i);
|
|
296
372
|
}
|
|
297
373
|
});
|
|
298
374
|
}
|
|
375
|
+
// SPLK-271: fetch-based SSE with X-Authorization header (EventSource can't send headers)
|
|
299
376
|
startSse(e) {
|
|
300
377
|
return new Promise((t, s) => {
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
if (
|
|
310
|
-
|
|
378
|
+
const i = new AbortController();
|
|
379
|
+
this.sseAbort = i, fetch(e, {
|
|
380
|
+
headers: {
|
|
381
|
+
Accept: "text/event-stream",
|
|
382
|
+
"X-Authorization": `Bearer ${this.options.accessToken}`
|
|
383
|
+
},
|
|
384
|
+
signal: i.signal
|
|
385
|
+
}).then((o) => {
|
|
386
|
+
if (!o.ok || !o.body) {
|
|
387
|
+
s(new Error("SSE failed"));
|
|
311
388
|
return;
|
|
312
389
|
}
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
if (!n) {
|
|
316
|
-
s(new Error("SSE failed"));
|
|
390
|
+
if (this.stopped) {
|
|
391
|
+
i.abort();
|
|
317
392
|
return;
|
|
318
393
|
}
|
|
319
|
-
this.handlers.
|
|
394
|
+
this.currentKind = "sse", this.handlers.onOpen("sse"), this.backoff = f, t();
|
|
395
|
+
const c = o.body.getReader(), a = new TextDecoder();
|
|
396
|
+
let n = "";
|
|
397
|
+
const h = () => {
|
|
398
|
+
c.read().then(({ done: u, value: v }) => {
|
|
399
|
+
var w;
|
|
400
|
+
if (u || this.stopped) {
|
|
401
|
+
this.stopped || (this.handlers.onError(new Error("SpilkiWidget: SSE stream ended")), this.retryFallback("sse"));
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
404
|
+
n += a.decode(v, { stream: !0 });
|
|
405
|
+
const d = n.split(`
|
|
406
|
+
|
|
407
|
+
`);
|
|
408
|
+
n = (w = d.pop()) != null ? w : "";
|
|
409
|
+
for (const k of d)
|
|
410
|
+
for (const m of k.split(`
|
|
411
|
+
`))
|
|
412
|
+
m.startsWith("data:") && this.handleIncoming(m.slice(5).trim());
|
|
413
|
+
h();
|
|
414
|
+
}).catch((u) => {
|
|
415
|
+
i.signal.aborted || (this.handlers.onError(u), this.stopped || this.retryFallback("sse"));
|
|
416
|
+
});
|
|
417
|
+
};
|
|
418
|
+
h();
|
|
419
|
+
}).catch((o) => {
|
|
420
|
+
i.signal.aborted || s(o);
|
|
320
421
|
});
|
|
321
422
|
});
|
|
322
423
|
}
|
|
424
|
+
// #15: add auth header to poll fetch
|
|
323
425
|
async startPoll(e) {
|
|
324
|
-
this.currentKind = "poll", this.handlers.onOpen("poll"), this.backoff =
|
|
426
|
+
this.currentKind = "poll", this.handlers.onOpen("poll"), this.backoff = f;
|
|
325
427
|
const t = async () => {
|
|
326
428
|
if (!this.stopped)
|
|
327
429
|
try {
|
|
328
|
-
const s = await fetch(e
|
|
430
|
+
const s = await fetch(e, {
|
|
431
|
+
headers: {
|
|
432
|
+
"X-Authorization": `Bearer ${this.options.accessToken}`
|
|
433
|
+
}
|
|
434
|
+
});
|
|
329
435
|
if (!s.ok) throw new Error(`Poll failed ${s.status}`);
|
|
330
436
|
const i = await s.json();
|
|
331
|
-
|
|
437
|
+
y(i).forEach((o) => this.handlers.onMessage(o)), this.backoff = f;
|
|
332
438
|
} catch (s) {
|
|
333
|
-
this.handlers.onError(s), this.backoff = Math.min(this.backoff * 1.5,
|
|
439
|
+
this.handlers.onError(s), this.backoff = Math.min(this.backoff * 1.5, I);
|
|
334
440
|
} finally {
|
|
335
441
|
this.stopped || (this.pollTimer = window.setTimeout(t, this.backoff));
|
|
336
442
|
}
|
|
337
443
|
};
|
|
338
444
|
await t();
|
|
339
445
|
}
|
|
446
|
+
// #1: preserve backoff by calling stop(false); #8: check stopped before reconnect
|
|
340
447
|
retryFallback(e) {
|
|
341
|
-
this.stopped || (this.stop(), this.backoff = Math.min(this.backoff * 1.5,
|
|
342
|
-
this.handlers.onError(new Error(`SpilkiWidget: retrying after ${e}`)), this.connect().catch((t) => this.handlers.onError(t));
|
|
448
|
+
this.stopped || (this.stop(!1), this.backoff = Math.min(this.backoff * 1.5, I), this.retryTimer = window.setTimeout(() => {
|
|
449
|
+
this.stopped || (this.handlers.onError(new Error(`SpilkiWidget: retrying after ${e}`)), this.connect().catch((t) => this.handlers.onError(t)));
|
|
343
450
|
}, this.backoff));
|
|
344
451
|
}
|
|
452
|
+
// #17: don't display raw garbage on parse failure
|
|
345
453
|
handleIncoming(e) {
|
|
346
454
|
try {
|
|
347
455
|
const t = JSON.parse(e);
|
|
@@ -351,13 +459,7 @@ class P {
|
|
|
351
459
|
}
|
|
352
460
|
this.dispatchIncoming(t);
|
|
353
461
|
} catch {
|
|
354
|
-
|
|
355
|
-
id: `${Date.now()}`,
|
|
356
|
-
author: "bot",
|
|
357
|
-
text: e,
|
|
358
|
-
ts: Date.now()
|
|
359
|
-
};
|
|
360
|
-
this.handlers.onMessage(t);
|
|
462
|
+
console.error("SpilkiWidget: failed to parse incoming message", e);
|
|
361
463
|
}
|
|
362
464
|
}
|
|
363
465
|
dispatchIncoming(e) {
|
|
@@ -369,18 +471,23 @@ class P {
|
|
|
369
471
|
this.handlers.onMessage(e);
|
|
370
472
|
}
|
|
371
473
|
}
|
|
372
|
-
const
|
|
373
|
-
class
|
|
474
|
+
const A = 30 * 60 * 1e3, x = 30;
|
|
475
|
+
class q {
|
|
374
476
|
constructor(e, t) {
|
|
375
477
|
this.org = e, this.listeners = /* @__PURE__ */ new Set(), this.state = {
|
|
376
478
|
isOpen: !1,
|
|
377
479
|
isTyping: !1,
|
|
378
480
|
isConnected: !1,
|
|
379
481
|
messages: []
|
|
380
|
-
}, this.historyKey = `spilki-history:${e}`, this.sessionKey = `spilki-session:${e}`, this.tokenKey = `spilki-token:${e}`, this.persist = t.persist, this.state.messages = this.loadMessages();
|
|
482
|
+
}, this.historyKey = `spilki-history:${e}`, this.sessionKey = `spilki-session:${e}`, this.tokenKey = `spilki-token:${e}`, this.activityKey = `spilki-activity:${e}`, this.persist = t.persist, this.state.messages = this.loadMessages();
|
|
381
483
|
}
|
|
382
484
|
get snapshot() {
|
|
383
|
-
return {
|
|
485
|
+
return {
|
|
486
|
+
isOpen: this.state.isOpen,
|
|
487
|
+
isTyping: this.state.isTyping,
|
|
488
|
+
isConnected: this.state.isConnected,
|
|
489
|
+
messages: this.state.messages
|
|
490
|
+
};
|
|
384
491
|
}
|
|
385
492
|
subscribe(e) {
|
|
386
493
|
return this.listeners.add(e), () => this.listeners.delete(e);
|
|
@@ -400,15 +507,15 @@ class U {
|
|
|
400
507
|
addMessage(e) {
|
|
401
508
|
var s, i;
|
|
402
509
|
const t = {
|
|
403
|
-
id: (s = e.id) != null ? s :
|
|
510
|
+
id: (s = e.id) != null ? s : P("msg"),
|
|
404
511
|
ts: (i = e.ts) != null ? i : Date.now(),
|
|
405
512
|
author: e.author,
|
|
406
513
|
text: e.text
|
|
407
514
|
};
|
|
408
|
-
return this.state.messages =
|
|
515
|
+
return this.state.messages = y([...this.state.messages, t], x), this.persistMessages(), this.touchActivity(), this.emit(), t;
|
|
409
516
|
}
|
|
410
517
|
setMessages(e) {
|
|
411
|
-
this.state.messages =
|
|
518
|
+
this.state.messages = y(e, x), this.persistMessages(), this.emit();
|
|
412
519
|
}
|
|
413
520
|
clearMessages() {
|
|
414
521
|
this.state.messages = [], this.persistMessages(), this.emit();
|
|
@@ -461,6 +568,36 @@ class U {
|
|
|
461
568
|
console.error("SpilkiWidget: unable to remove item", e);
|
|
462
569
|
}
|
|
463
570
|
}
|
|
571
|
+
get lastActivityTs() {
|
|
572
|
+
if (!this.persist) return 0;
|
|
573
|
+
try {
|
|
574
|
+
const e = localStorage.getItem(this.activityKey);
|
|
575
|
+
return e ? Number(e) : 0;
|
|
576
|
+
} catch {
|
|
577
|
+
return 0;
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
touchActivity() {
|
|
581
|
+
if (this.persist)
|
|
582
|
+
try {
|
|
583
|
+
localStorage.setItem(this.activityKey, String(Date.now()));
|
|
584
|
+
} catch (e) {
|
|
585
|
+
console.error("SpilkiWidget: unable to set item", e);
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
isSessionExpired() {
|
|
589
|
+
const e = this.lastActivityTs;
|
|
590
|
+
return e === 0 ? !1 : Date.now() - e >= A;
|
|
591
|
+
}
|
|
592
|
+
getConversationGroups() {
|
|
593
|
+
const e = this.state.messages;
|
|
594
|
+
if (e.length === 0) return [];
|
|
595
|
+
const t = [];
|
|
596
|
+
let s = { startTs: e[0].ts, messages: [e[0]] };
|
|
597
|
+
for (let i = 1; i < e.length; i++)
|
|
598
|
+
e[i].ts - e[i - 1].ts >= A ? (t.push(s), s = { startTs: e[i].ts, messages: [e[i]] }) : s.messages.push(e[i]);
|
|
599
|
+
return t.push(s), t;
|
|
600
|
+
}
|
|
464
601
|
emit() {
|
|
465
602
|
this.listeners.forEach((e) => e());
|
|
466
603
|
}
|
|
@@ -478,13 +615,13 @@ class U {
|
|
|
478
615
|
const e = localStorage.getItem(this.historyKey);
|
|
479
616
|
if (!e) return [];
|
|
480
617
|
const t = JSON.parse(e);
|
|
481
|
-
return Array.isArray(t) ?
|
|
618
|
+
return Array.isArray(t) ? y(t, x) : [];
|
|
482
619
|
} catch (e) {
|
|
483
620
|
return console.error("SpilkiWidget: unable to load messages", e), [];
|
|
484
621
|
}
|
|
485
622
|
}
|
|
486
623
|
}
|
|
487
|
-
function
|
|
624
|
+
function _(r) {
|
|
488
625
|
const e = r.replace(/-/g, "+").replace(/_/g, "/"), t = e.padEnd(e.length + (4 - e.length % 4) % 4, "=");
|
|
489
626
|
if (typeof atob == "function")
|
|
490
627
|
return decodeURIComponent(
|
|
@@ -495,24 +632,24 @@ function N(r) {
|
|
|
495
632
|
return s.from(t, "base64").toString("utf8");
|
|
496
633
|
throw new Error("SpilkiWidget: no base64 decoder available");
|
|
497
634
|
}
|
|
498
|
-
function
|
|
635
|
+
function j(r) {
|
|
499
636
|
if (!r) return null;
|
|
500
637
|
const e = r.split(".");
|
|
501
638
|
if (e.length < 2) return null;
|
|
502
639
|
try {
|
|
503
|
-
const t =
|
|
640
|
+
const t = _(e[1]);
|
|
504
641
|
return JSON.parse(t);
|
|
505
642
|
} catch (t) {
|
|
506
643
|
return console.error("SpilkiWidget: unable to parse JWT", t), null;
|
|
507
644
|
}
|
|
508
645
|
}
|
|
509
|
-
function
|
|
510
|
-
const e =
|
|
511
|
-
if (!(e != null && e.exp)) return !
|
|
646
|
+
function Y(r) {
|
|
647
|
+
const e = j(r);
|
|
648
|
+
if (!(e != null && e.exp) || typeof e.exp != "number") return !0;
|
|
512
649
|
const t = Math.floor(Date.now() / 1e3);
|
|
513
|
-
return e.exp < t;
|
|
650
|
+
return e.exp < t + 60;
|
|
514
651
|
}
|
|
515
|
-
const
|
|
652
|
+
const R = {
|
|
516
653
|
onOpen() {
|
|
517
654
|
},
|
|
518
655
|
onClose() {
|
|
@@ -524,7 +661,7 @@ const z = {
|
|
|
524
661
|
onTransportChange() {
|
|
525
662
|
}
|
|
526
663
|
};
|
|
527
|
-
async function
|
|
664
|
+
async function X(r, e, t) {
|
|
528
665
|
const s = `${r.replace(/\/$/, "")}/widget/install`, i = await fetch(s, {
|
|
529
666
|
method: "POST",
|
|
530
667
|
headers: { "Content-Type": "application/json", Origin: window.location.origin },
|
|
@@ -533,51 +670,55 @@ async function H(r, e, t) {
|
|
|
533
670
|
if (!i.ok) throw new Error(`SpilkiWidget: install failed (${i.status})`);
|
|
534
671
|
return (await i.json()).accessToken;
|
|
535
672
|
}
|
|
536
|
-
async function
|
|
673
|
+
async function G(r, e) {
|
|
537
674
|
const t = `${r.replace(/\/$/, "")}/widget/refresh`, s = await fetch(t, {
|
|
538
675
|
method: "POST",
|
|
539
|
-
headers: { Authorization: `Bearer ${e}`, Origin: window.location.origin }
|
|
676
|
+
headers: { "X-Authorization": `Bearer ${e}`, Origin: window.location.origin }
|
|
540
677
|
});
|
|
541
678
|
if (!s.ok) throw new Error(`SpilkiWidget: refresh failed (${s.status})`);
|
|
542
679
|
return (await s.json()).accessToken;
|
|
543
680
|
}
|
|
544
|
-
function
|
|
545
|
-
var
|
|
681
|
+
function M(r) {
|
|
682
|
+
var m, E, T, O;
|
|
546
683
|
if (!r.org)
|
|
547
684
|
throw new Error("SpilkiWidget: org is required");
|
|
548
|
-
const e =
|
|
549
|
-
|
|
550
|
-
const t = { ...
|
|
551
|
-
let i = (
|
|
552
|
-
const
|
|
553
|
-
|
|
554
|
-
if (!
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
685
|
+
const e = K(r);
|
|
686
|
+
N(e.allowedOriginsHint);
|
|
687
|
+
const t = { ...R, ...(m = e.hooks) != null ? m : {} }, s = new q(e.org, { persist: e.persist });
|
|
688
|
+
let i = (E = s.accessToken) != null ? E : void 0, o = null;
|
|
689
|
+
const c = async () => o || (o = (async () => {
|
|
690
|
+
try {
|
|
691
|
+
if (!i) {
|
|
692
|
+
if (!r.installationToken) throw new Error("SpilkiWidget: missing installationToken");
|
|
693
|
+
if (!r.org) throw new Error("SpilkiWidget: missing org");
|
|
694
|
+
i = await X(e.apiBase, r.installationToken, r.org), s.persistAccessToken(i), h.setAccessToken(i);
|
|
695
|
+
return;
|
|
696
|
+
}
|
|
697
|
+
Y(i) && (i = await G(e.apiBase, i), s.persistAccessToken(i), h.setAccessToken(i));
|
|
698
|
+
} finally {
|
|
699
|
+
o = null;
|
|
558
700
|
}
|
|
559
|
-
|
|
560
|
-
}, l = A({
|
|
701
|
+
})(), o), a = L({
|
|
561
702
|
color: e.color,
|
|
562
|
-
position: (
|
|
703
|
+
position: (T = e.position) != null ? T : "bottom-right",
|
|
563
704
|
onClick: () => {
|
|
564
705
|
s.snapshot.isOpen ? (s.close(), t.onClose()) : (s.open(), t.onOpen());
|
|
565
706
|
}
|
|
566
|
-
}),
|
|
707
|
+
}), n = new z({
|
|
567
708
|
color: e.color,
|
|
568
|
-
theme:
|
|
569
|
-
position: (
|
|
709
|
+
theme: C(e.theme),
|
|
710
|
+
position: (O = e.position) != null ? O : "bottom-right",
|
|
570
711
|
i18n: e.i18n,
|
|
571
712
|
onClose: () => {
|
|
572
713
|
s.close(), t.onClose();
|
|
573
714
|
},
|
|
574
|
-
onSend: (
|
|
575
|
-
const
|
|
576
|
-
|
|
577
|
-
t.onError(
|
|
715
|
+
onSend: (l) => {
|
|
716
|
+
const p = s.addMessage({ author: "user", text: l });
|
|
717
|
+
n.appendMessage(p), c().then(() => h.send(l, p.id)).catch(($) => {
|
|
718
|
+
t.onError($), s.setConnected(!1), n.setOffline(!0);
|
|
578
719
|
});
|
|
579
720
|
}
|
|
580
|
-
}), h = new
|
|
721
|
+
}), h = new J(
|
|
581
722
|
{
|
|
582
723
|
apiBase: e.apiBase,
|
|
583
724
|
accessToken: i,
|
|
@@ -585,46 +726,45 @@ function E(r) {
|
|
|
585
726
|
sessionId: s.sessionId
|
|
586
727
|
},
|
|
587
728
|
{
|
|
588
|
-
onOpen(
|
|
589
|
-
|
|
729
|
+
onOpen(l) {
|
|
730
|
+
k.transport = l, t.onTransportChange(l), s.setConnected(!0), n.setOffline(!1);
|
|
590
731
|
},
|
|
591
|
-
onMessage(
|
|
592
|
-
const
|
|
593
|
-
|
|
732
|
+
onMessage(l) {
|
|
733
|
+
const p = s.addMessage(l);
|
|
734
|
+
n.appendMessage(p), t.onMessage(p);
|
|
594
735
|
},
|
|
595
|
-
onTyping(
|
|
596
|
-
s.setTyping(
|
|
736
|
+
onTyping(l) {
|
|
737
|
+
s.setTyping(l);
|
|
597
738
|
},
|
|
598
|
-
onError(
|
|
599
|
-
t.onError(
|
|
739
|
+
onError(l) {
|
|
740
|
+
t.onError(l), s.setConnected(!1), s.snapshot.isOpen && n.setOffline(!0);
|
|
600
741
|
}
|
|
601
742
|
}
|
|
602
743
|
);
|
|
603
|
-
|
|
604
|
-
const
|
|
605
|
-
if (
|
|
606
|
-
const
|
|
744
|
+
a.mount(), n.mount();
|
|
745
|
+
const u = s.snapshot.messages, v = s.isSessionExpired(), d = s.getConversationGroups();
|
|
746
|
+
if (u.length === 0 && e.welcome) {
|
|
747
|
+
const l = {
|
|
607
748
|
id: "welcome",
|
|
608
749
|
author: "bot",
|
|
609
750
|
text: e.welcome,
|
|
610
751
|
ts: Date.now()
|
|
611
752
|
};
|
|
612
|
-
s.addMessage(
|
|
613
|
-
} else
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
l.setOpen(o.isOpen), a.setTyping(o.isTyping), a.setOffline(!o.isConnected), a.updateTheme(x(e.theme)), o.isOpen ? a.show() : a.hide();
|
|
753
|
+
s.addMessage(l), n.appendMessage(l);
|
|
754
|
+
} else v && u.length > 0 ? n.renderWithConversations(d, []) : d.length > 1 ? n.renderWithConversations(d.slice(0, -1), d[d.length - 1].messages) : n.updateMessages(u);
|
|
755
|
+
const w = s.subscribe(() => {
|
|
756
|
+
const l = s.snapshot;
|
|
757
|
+
a.setOpen(l.isOpen), n.setTyping(l.isTyping), n.setOffline(!l.isConnected), n.updateTheme(C(e.theme)), l.isOpen ? n.show() : n.hide();
|
|
618
758
|
});
|
|
619
|
-
|
|
620
|
-
() => h.connect().then((
|
|
621
|
-
var
|
|
622
|
-
e.persist && s.persistSession(
|
|
759
|
+
c().then(
|
|
760
|
+
() => h.connect().then((l) => {
|
|
761
|
+
var p;
|
|
762
|
+
e.persist && s.persistSession(l.sessionId), s.setConnected(!0), t.onTransportChange((p = h.kind) != null ? p : "ws");
|
|
623
763
|
})
|
|
624
|
-
).catch((
|
|
625
|
-
t.onError(
|
|
764
|
+
).catch((l) => {
|
|
765
|
+
t.onError(l), s.setConnected(!1), n.setOffline(!0);
|
|
626
766
|
});
|
|
627
|
-
const
|
|
767
|
+
const k = {
|
|
628
768
|
transport: null,
|
|
629
769
|
open() {
|
|
630
770
|
s.open(), t.onOpen();
|
|
@@ -633,12 +773,12 @@ function E(r) {
|
|
|
633
773
|
s.close(), t.onClose();
|
|
634
774
|
},
|
|
635
775
|
destroy() {
|
|
636
|
-
|
|
776
|
+
w(), h.stop(), a.destroy(), n.destroy();
|
|
637
777
|
}
|
|
638
778
|
};
|
|
639
|
-
return
|
|
779
|
+
return k;
|
|
640
780
|
}
|
|
641
|
-
function
|
|
781
|
+
function V() {
|
|
642
782
|
var s, i;
|
|
643
783
|
if (typeof document == "undefined") return;
|
|
644
784
|
const r = document.currentScript;
|
|
@@ -650,7 +790,7 @@ function J() {
|
|
|
650
790
|
console.error("SpilkiWidget: data-org and is required for auto init");
|
|
651
791
|
return;
|
|
652
792
|
}
|
|
653
|
-
|
|
793
|
+
M({
|
|
654
794
|
org: t,
|
|
655
795
|
installationToken: e.installationToken,
|
|
656
796
|
apiBase: e.apiBase,
|
|
@@ -658,13 +798,10 @@ function J() {
|
|
|
658
798
|
theme: (i = e.theme) != null ? i : void 0
|
|
659
799
|
});
|
|
660
800
|
}
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
r.SpilkiWidget = r.SpilkiWidget || {}, r.SpilkiWidget.init = (e) => E(e);
|
|
664
|
-
}
|
|
665
|
-
J();
|
|
801
|
+
typeof window != "undefined" && (window.SpilkiWidget = window.SpilkiWidget || {}, window.SpilkiWidget.init = (r) => M(r));
|
|
802
|
+
V();
|
|
666
803
|
export {
|
|
667
|
-
|
|
668
|
-
|
|
804
|
+
V as autoInit,
|
|
805
|
+
M as initSpilkiWidget
|
|
669
806
|
};
|
|
670
807
|
//# sourceMappingURL=widget.es.js.map
|