@spilki/widget 0.1.4 → 1.0.27
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/dist/bootstrap.es.js +359 -217
- 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 +360 -218
- 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/bootstrap.es.js
CHANGED
|
@@ -1,9 +1,16 @@
|
|
|
1
|
-
const
|
|
1
|
+
const L = `
|
|
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 A = `
|
|
|
44
51
|
</span>
|
|
45
52
|
</button>
|
|
46
53
|
`;
|
|
47
|
-
function
|
|
54
|
+
function B(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 = L;
|
|
52
59
|
const s = t.querySelector("button");
|
|
53
60
|
return s.addEventListener("click", () => r.onClick()), {
|
|
54
61
|
element: e,
|
|
@@ -58,41 +65,103 @@ function M(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 K = "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: K,
|
|
82
|
+
position: "bottom-right",
|
|
83
|
+
theme: "auto",
|
|
84
|
+
color: "#6366f1",
|
|
85
|
+
welcome: S.welcome,
|
|
86
|
+
persist: !0,
|
|
87
|
+
i18n: S
|
|
88
|
+
};
|
|
89
|
+
function P(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 D(r = "msg") {
|
|
105
|
+
return typeof crypto != "undefined" && crypto.randomUUID ? crypto.randomUUID() : `${r}-${Math.random().toString(16).slice(2)}`;
|
|
106
|
+
}
|
|
107
|
+
function N() {
|
|
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 : N() ? "dark" : "light";
|
|
113
|
+
}
|
|
114
|
+
function y(r, e = 30) {
|
|
115
|
+
return r.slice(-e);
|
|
116
|
+
}
|
|
117
|
+
function F(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 H = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
|
|
125
|
+
function U(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}` : `${H[e.getMonth()]} ${e.getDate()}, ${i}`;
|
|
128
|
+
}
|
|
129
|
+
const z = ':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 J {
|
|
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>${z}</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 W {
|
|
|
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 W {
|
|
|
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 = U(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 W {
|
|
|
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: $,
|
|
161
|
-
position: "bottom-right",
|
|
162
|
-
theme: "auto",
|
|
163
|
-
color: "#6366f1",
|
|
164
|
-
welcome: g.welcome,
|
|
165
|
-
persist: !0,
|
|
166
|
-
i18n: g
|
|
167
|
-
};
|
|
168
|
-
function B(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 L(r = "msg") {
|
|
184
|
-
return typeof crypto != "undefined" && crypto.randomUUID ? crypto.randomUUID() : `${r}-${Math.random().toString(16).slice(2)}`;
|
|
185
|
-
}
|
|
186
|
-
function K() {
|
|
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 : K() ? "dark" : "light";
|
|
192
|
-
}
|
|
193
|
-
function u(r, e = 30) {
|
|
194
|
-
return r.slice(-e);
|
|
195
|
-
}
|
|
196
|
-
function P(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 U {
|
|
266
|
+
const f = 1500, I = 8e3;
|
|
267
|
+
class q {
|
|
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 U {
|
|
|
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 : "",
|
|
@@ -234,34 +307,36 @@ class U {
|
|
|
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 U {
|
|
|
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 U {
|
|
|
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 U {
|
|
|
369
471
|
this.handlers.onMessage(e);
|
|
370
472
|
}
|
|
371
473
|
}
|
|
372
|
-
const
|
|
373
|
-
class
|
|
474
|
+
const A = 30 * 60 * 1e3, x = 30;
|
|
475
|
+
class _ {
|
|
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 N {
|
|
|
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 : D("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 N {
|
|
|
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 N {
|
|
|
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 j(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 D(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 Y(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 = j(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 R(r) {
|
|
647
|
+
const e = Y(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 X = {
|
|
516
653
|
onOpen() {
|
|
517
654
|
},
|
|
518
655
|
onClose() {
|
|
@@ -524,7 +661,7 @@ const H = {
|
|
|
524
661
|
onTransportChange() {
|
|
525
662
|
}
|
|
526
663
|
};
|
|
527
|
-
async function
|
|
664
|
+
async function G(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,7 +670,7 @@ async function q(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 V(r, e) {
|
|
537
674
|
const t = `${r.replace(/\/$/, "")}/widget/refresh`, s = await fetch(t, {
|
|
538
675
|
method: "POST",
|
|
539
676
|
headers: { "X-Authorization": `Bearer ${e}`, Origin: window.location.origin }
|
|
@@ -541,43 +678,52 @@ async function J(r, e) {
|
|
|
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 = P(r);
|
|
686
|
+
F(e.allowedOriginsHint);
|
|
687
|
+
const t = { ...X, ...(m = e.hooks) != null ? m : {} }, s = new _(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 && R(i))
|
|
692
|
+
try {
|
|
693
|
+
i = await V(e.apiBase, i), s.persistAccessToken(i), h.setAccessToken(i);
|
|
694
|
+
return;
|
|
695
|
+
} catch {
|
|
696
|
+
i = void 0, s.clearAccessToken();
|
|
697
|
+
}
|
|
698
|
+
if (!i) {
|
|
699
|
+
if (!r.installationToken) throw new Error("SpilkiWidget: missing installationToken");
|
|
700
|
+
if (!r.org) throw new Error("SpilkiWidget: missing org");
|
|
701
|
+
i = await G(e.apiBase, r.installationToken, r.org), s.persistAccessToken(i), h.setAccessToken(i);
|
|
702
|
+
}
|
|
703
|
+
} finally {
|
|
704
|
+
o = null;
|
|
558
705
|
}
|
|
559
|
-
|
|
560
|
-
}, l = M({
|
|
706
|
+
})(), o), a = B({
|
|
561
707
|
color: e.color,
|
|
562
|
-
position: (
|
|
708
|
+
position: (T = e.position) != null ? T : "bottom-right",
|
|
563
709
|
onClick: () => {
|
|
564
710
|
s.snapshot.isOpen ? (s.close(), t.onClose()) : (s.open(), t.onOpen());
|
|
565
711
|
}
|
|
566
|
-
}),
|
|
712
|
+
}), n = new J({
|
|
567
713
|
color: e.color,
|
|
568
|
-
theme:
|
|
569
|
-
position: (
|
|
714
|
+
theme: C(e.theme),
|
|
715
|
+
position: (O = e.position) != null ? O : "bottom-right",
|
|
570
716
|
i18n: e.i18n,
|
|
571
717
|
onClose: () => {
|
|
572
718
|
s.close(), t.onClose();
|
|
573
719
|
},
|
|
574
|
-
onSend: (
|
|
575
|
-
const
|
|
576
|
-
|
|
577
|
-
t.onError(
|
|
720
|
+
onSend: (l) => {
|
|
721
|
+
const p = s.addMessage({ author: "user", text: l });
|
|
722
|
+
n.appendMessage(p), c().then(() => h.send(l, p.id)).catch((W) => {
|
|
723
|
+
t.onError(W), s.setConnected(!1), n.setOffline(!0);
|
|
578
724
|
});
|
|
579
725
|
}
|
|
580
|
-
}), h = new
|
|
726
|
+
}), h = new q(
|
|
581
727
|
{
|
|
582
728
|
apiBase: e.apiBase,
|
|
583
729
|
accessToken: i,
|
|
@@ -585,46 +731,45 @@ function E(r) {
|
|
|
585
731
|
sessionId: s.sessionId
|
|
586
732
|
},
|
|
587
733
|
{
|
|
588
|
-
onOpen(
|
|
589
|
-
|
|
734
|
+
onOpen(l) {
|
|
735
|
+
k.transport = l, t.onTransportChange(l), s.setConnected(!0), n.setOffline(!1);
|
|
590
736
|
},
|
|
591
|
-
onMessage(
|
|
592
|
-
const
|
|
593
|
-
|
|
737
|
+
onMessage(l) {
|
|
738
|
+
const p = s.addMessage(l);
|
|
739
|
+
n.appendMessage(p), t.onMessage(p);
|
|
594
740
|
},
|
|
595
|
-
onTyping(
|
|
596
|
-
s.setTyping(
|
|
741
|
+
onTyping(l) {
|
|
742
|
+
s.setTyping(l);
|
|
597
743
|
},
|
|
598
|
-
onError(
|
|
599
|
-
t.onError(
|
|
744
|
+
onError(l) {
|
|
745
|
+
t.onError(l), s.setConnected(!1), s.snapshot.isOpen && n.setOffline(!0);
|
|
600
746
|
}
|
|
601
747
|
}
|
|
602
748
|
);
|
|
603
|
-
|
|
604
|
-
const
|
|
605
|
-
if (
|
|
606
|
-
const
|
|
749
|
+
a.mount(), n.mount();
|
|
750
|
+
const u = s.snapshot.messages, v = s.isSessionExpired(), d = s.getConversationGroups();
|
|
751
|
+
if (u.length === 0 && e.welcome) {
|
|
752
|
+
const l = {
|
|
607
753
|
id: "welcome",
|
|
608
754
|
author: "bot",
|
|
609
755
|
text: e.welcome,
|
|
610
756
|
ts: Date.now()
|
|
611
757
|
};
|
|
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();
|
|
758
|
+
s.addMessage(l), n.appendMessage(l);
|
|
759
|
+
} 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);
|
|
760
|
+
const w = s.subscribe(() => {
|
|
761
|
+
const l = s.snapshot;
|
|
762
|
+
a.setOpen(l.isOpen), n.setTyping(l.isTyping), n.setOffline(!l.isConnected), n.updateTheme(C(e.theme)), l.isOpen ? n.show() : n.hide();
|
|
618
763
|
});
|
|
619
|
-
|
|
620
|
-
() => h.connect().then((
|
|
621
|
-
var
|
|
622
|
-
e.persist && s.persistSession(
|
|
764
|
+
c().then(
|
|
765
|
+
() => h.connect().then((l) => {
|
|
766
|
+
var p;
|
|
767
|
+
e.persist && s.persistSession(l.sessionId), s.setConnected(!0), t.onTransportChange((p = h.kind) != null ? p : "ws");
|
|
623
768
|
})
|
|
624
|
-
).catch((
|
|
625
|
-
t.onError(
|
|
769
|
+
).catch((l) => {
|
|
770
|
+
t.onError(l), s.setConnected(!1), n.setOffline(!0);
|
|
626
771
|
});
|
|
627
|
-
const
|
|
772
|
+
const k = {
|
|
628
773
|
transport: null,
|
|
629
774
|
open() {
|
|
630
775
|
s.open(), t.onOpen();
|
|
@@ -633,12 +778,12 @@ function E(r) {
|
|
|
633
778
|
s.close(), t.onClose();
|
|
634
779
|
},
|
|
635
780
|
destroy() {
|
|
636
|
-
|
|
781
|
+
w(), h.stop(), a.destroy(), n.destroy();
|
|
637
782
|
}
|
|
638
783
|
};
|
|
639
|
-
return
|
|
784
|
+
return k;
|
|
640
785
|
}
|
|
641
|
-
function
|
|
786
|
+
function $() {
|
|
642
787
|
var s, i;
|
|
643
788
|
if (typeof document == "undefined") return;
|
|
644
789
|
const r = document.currentScript;
|
|
@@ -650,7 +795,7 @@ function T() {
|
|
|
650
795
|
console.error("SpilkiWidget: data-org and is required for auto init");
|
|
651
796
|
return;
|
|
652
797
|
}
|
|
653
|
-
|
|
798
|
+
M({
|
|
654
799
|
org: t,
|
|
655
800
|
installationToken: e.installationToken,
|
|
656
801
|
apiBase: e.apiBase,
|
|
@@ -658,10 +803,7 @@ function T() {
|
|
|
658
803
|
theme: (i = e.theme) != null ? i : void 0
|
|
659
804
|
});
|
|
660
805
|
}
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
}
|
|
665
|
-
T();
|
|
666
|
-
T();
|
|
806
|
+
typeof window != "undefined" && (window.SpilkiWidget = window.SpilkiWidget || {}, window.SpilkiWidget.init = (r) => M(r));
|
|
807
|
+
$();
|
|
808
|
+
$();
|
|
667
809
|
//# sourceMappingURL=bootstrap.es.js.map
|