@cognitiondesk/widget 1.1.0 → 1.2.1
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 +212 -44
- package/dist/react.es.js +171 -133
- package/dist/react.umd.cjs +4 -4
- package/dist/widget.es.js +106 -80
- package/dist/widget.umd.cjs +4 -4
- package/package.json +1 -1
- package/src/react.jsx +12 -0
- package/src/widget.js +57 -5
package/dist/widget.es.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const u = "https://mounaji-backendv3.onrender.com",
|
|
1
|
+
const u = "https://mounaji-backendv3.onrender.com", _ = `
|
|
2
2
|
:host {
|
|
3
3
|
all: initial;
|
|
4
4
|
font-family: system-ui, sans-serif;
|
|
@@ -244,16 +244,17 @@ const u = "https://mounaji-backendv3.onrender.com", b = `
|
|
|
244
244
|
font-size: 16px;
|
|
245
245
|
}
|
|
246
246
|
}
|
|
247
|
-
`,
|
|
247
|
+
`, p = {
|
|
248
248
|
chat: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>',
|
|
249
249
|
close: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>',
|
|
250
250
|
send: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="22" y1="2" x2="11" y2="13"/><polygon points="22 2 15 22 11 13 2 9 22 2"/></svg>'
|
|
251
251
|
};
|
|
252
|
-
function
|
|
252
|
+
function b() {
|
|
253
253
|
return "cd_" + Math.random().toString(36).slice(2) + Date.now().toString(36);
|
|
254
254
|
}
|
|
255
|
-
class
|
|
255
|
+
class f {
|
|
256
256
|
constructor(e = {}) {
|
|
257
|
+
var t, a, s, i, r;
|
|
257
258
|
if (!e.apiKey) throw new Error("[CognitionDesk] apiKey is required");
|
|
258
259
|
this._cfg = {
|
|
259
260
|
apiKey: e.apiKey,
|
|
@@ -268,9 +269,17 @@ class m {
|
|
|
268
269
|
welcomeMessage: e.welcomeMessage || "Hello! How can I help you today?",
|
|
269
270
|
placeholder: e.placeholder || "Type a message…",
|
|
270
271
|
position: e.position || "bottom-right",
|
|
271
|
-
streaming: e.streaming !== !1
|
|
272
|
+
streaming: e.streaming !== !1,
|
|
272
273
|
// default: true
|
|
273
|
-
|
|
274
|
+
// Rate limiting — can be overridden by inline config or merged from server config
|
|
275
|
+
rateLimiting: {
|
|
276
|
+
enabled: ((t = e.rateLimiting) == null ? void 0 : t.enabled) !== !1,
|
|
277
|
+
maxMessagesPerSession: ((a = e.rateLimiting) == null ? void 0 : a.maxMessagesPerSession) ?? 0,
|
|
278
|
+
maxMessagesPerMinute: ((s = e.rateLimiting) == null ? void 0 : s.maxMessagesPerMinute) ?? 0,
|
|
279
|
+
limitReachedMessage: ((i = e.rateLimiting) == null ? void 0 : i.limitReachedMessage) || "You've reached the message limit for this session.",
|
|
280
|
+
rateLimitMessage: ((r = e.rateLimiting) == null ? void 0 : r.rateLimitMessage) || "You're sending messages too quickly. Please wait a moment."
|
|
281
|
+
}
|
|
282
|
+
}, this._sessionId = b(), this._messageCount = 0, this._minuteCount = 0, this._minuteStart = Date.now(), this._messages = [], this._open = !1, this._loading = !1, this._container = null, this._shadow = null, this._panel = null, this._messagesEl = null, this._textarea = null, this._sendBtn = null, this._viewportHandler = null;
|
|
274
283
|
}
|
|
275
284
|
// ── Public API ──────────────────────────────────────────────────────────────
|
|
276
285
|
/**
|
|
@@ -280,10 +289,10 @@ class m {
|
|
|
280
289
|
*/
|
|
281
290
|
mount(e) {
|
|
282
291
|
const t = () => {
|
|
283
|
-
const
|
|
284
|
-
this._container = document.createElement("div"), this._container.setAttribute("data-cognitiondesk", ""),
|
|
285
|
-
const
|
|
286
|
-
|
|
292
|
+
const a = e || document.body;
|
|
293
|
+
this._container = document.createElement("div"), this._container.setAttribute("data-cognitiondesk", ""), a.appendChild(this._container), this._shadow = this._container.attachShadow({ mode: "open" });
|
|
294
|
+
const s = document.createElement("style");
|
|
295
|
+
s.textContent = _, this._shadow.appendChild(s), this._buildDOM(), this._applyTheme(), this._syncViewportMetrics(), this._bindViewportMetrics(), this._bindEvents(), this._cfg.welcomeMessage && this._appendMessage("assistant", this._cfg.welcomeMessage, !1);
|
|
287
296
|
};
|
|
288
297
|
return this._cfg.widgetId ? this._fetchWidgetConfig().then(() => t()).catch(() => t()) : (t(), Promise.resolve(this));
|
|
289
298
|
}
|
|
@@ -295,11 +304,11 @@ class m {
|
|
|
295
304
|
var t;
|
|
296
305
|
const e = `${this._cfg.backendUrl}/platforms/web-widget/public/${this._cfg.widgetId}`;
|
|
297
306
|
try {
|
|
298
|
-
const
|
|
299
|
-
if (!
|
|
300
|
-
const { config:
|
|
301
|
-
if (!
|
|
302
|
-
|
|
307
|
+
const a = await fetch(e);
|
|
308
|
+
if (!a.ok) return;
|
|
309
|
+
const { config: s } = await a.json();
|
|
310
|
+
if (!s) return;
|
|
311
|
+
s.botName && !this._userSet("botName") && (this._cfg.botName = s.botName), s.welcomeMessage && !this._userSet("welcomeMessage") && (this._cfg.welcomeMessage = s.welcomeMessage), s.primaryColor && !this._userSet("primaryColor") && (this._cfg.primaryColor = s.primaryColor), s.placeholder && !this._userSet("placeholder") && (this._cfg.placeholder = s.placeholder), s.assistantId && !this._cfg.assistantId && (this._cfg.assistantId = s.assistantId), (t = s.avatar) != null && t.value && !this._userSet("botEmoji") && (this._cfg.botEmoji = s.avatar.value), s.rateLimiting && (this._cfg.rateLimiting = { ...this._cfg.rateLimiting, ...s.rateLimiting });
|
|
303
312
|
} catch {
|
|
304
313
|
}
|
|
305
314
|
}
|
|
@@ -329,11 +338,11 @@ class m {
|
|
|
329
338
|
const e = document.createElement("div");
|
|
330
339
|
e.className = "cd-root";
|
|
331
340
|
const t = document.createElement("button");
|
|
332
|
-
t.className = "cd-widget-btn", t.innerHTML =
|
|
333
|
-
const s = document.createElement("div");
|
|
334
|
-
s.className = "cd-panel", this._panel = s;
|
|
341
|
+
t.className = "cd-widget-btn", t.innerHTML = p.chat, t.setAttribute("aria-label", "Open chat"), t.style.setProperty("--cd-primary", this._cfg.primaryColor), this._toggleBtn = t;
|
|
335
342
|
const a = document.createElement("div");
|
|
336
|
-
a.className = "cd-
|
|
343
|
+
a.className = "cd-panel", this._panel = a;
|
|
344
|
+
const s = document.createElement("div");
|
|
345
|
+
s.className = "cd-header", s.innerHTML = `
|
|
337
346
|
<div class="cd-header-avatar">${this._cfg.botEmoji}</div>
|
|
338
347
|
<div class="cd-header-info">
|
|
339
348
|
<div class="cd-header-name">${this._escHtml(this._cfg.botName)}</div>
|
|
@@ -342,22 +351,22 @@ class m {
|
|
|
342
351
|
</div>
|
|
343
352
|
</div>
|
|
344
353
|
`;
|
|
345
|
-
const
|
|
346
|
-
|
|
347
|
-
const
|
|
348
|
-
|
|
349
|
-
const
|
|
350
|
-
|
|
351
|
-
const
|
|
352
|
-
|
|
354
|
+
const i = document.createElement("button");
|
|
355
|
+
i.className = "cd-close-btn", i.innerHTML = p.close, i.setAttribute("aria-label", "Close chat"), s.appendChild(i), this._closeBtn = i;
|
|
356
|
+
const r = document.createElement("div");
|
|
357
|
+
r.className = "cd-messages", r.setAttribute("role", "log"), r.setAttribute("aria-live", "polite"), this._messagesEl = r;
|
|
358
|
+
const o = document.createElement("div");
|
|
359
|
+
o.className = "cd-input-area";
|
|
360
|
+
const n = document.createElement("textarea");
|
|
361
|
+
n.className = "cd-textarea", n.rows = 1, n.placeholder = this._cfg.placeholder, this._textarea = n;
|
|
353
362
|
const d = document.createElement("button");
|
|
354
|
-
d.className = "cd-send-btn", d.innerHTML =
|
|
363
|
+
d.className = "cd-send-btn", d.innerHTML = p.send, d.setAttribute("aria-label", "Send"), this._sendBtn = d, o.appendChild(n), o.appendChild(d);
|
|
355
364
|
const l = document.createElement("div");
|
|
356
|
-
l.className = "cd-powered", l.innerHTML = 'Powered by <a href="https://cognitiondesk.com" target="_blank" rel="noopener">CognitionDesk</a>',
|
|
365
|
+
l.className = "cd-powered", l.innerHTML = 'Powered by <a href="https://cognitiondesk.com" target="_blank" rel="noopener">CognitionDesk</a>', a.appendChild(s), a.appendChild(r), a.appendChild(o), a.appendChild(l), e.appendChild(t), e.appendChild(a), this._shadow.appendChild(e), this._root = e;
|
|
357
366
|
}
|
|
358
367
|
_applyTheme() {
|
|
359
|
-
var t,
|
|
360
|
-
(this._cfg.theme === "auto" ? window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light" : this._cfg.theme) === "dark" && ((t = this._root) == null || t.classList.add("cd-dark")), (
|
|
368
|
+
var t, a, s;
|
|
369
|
+
(this._cfg.theme === "auto" ? window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light" : this._cfg.theme) === "dark" && ((t = this._root) == null || t.classList.add("cd-dark")), (a = this._root) == null || a.style.setProperty("--cd-primary", this._cfg.primaryColor), (s = this._panel) == null || s.style.setProperty("--cd-primary", this._cfg.primaryColor);
|
|
361
370
|
}
|
|
362
371
|
// ── Events ──────────────────────────────────────────────────────────────────
|
|
363
372
|
_bindEvents() {
|
|
@@ -378,27 +387,44 @@ class m {
|
|
|
378
387
|
_syncViewportMetrics() {
|
|
379
388
|
const e = this._root || this._container;
|
|
380
389
|
if (!e) return;
|
|
381
|
-
const t = window.visualViewport,
|
|
382
|
-
e.style.setProperty("--cd-viewport-height", `${Math.round(
|
|
390
|
+
const t = window.visualViewport, a = t ? t.height : window.innerHeight;
|
|
391
|
+
e.style.setProperty("--cd-viewport-height", `${Math.round(a)}px`);
|
|
383
392
|
}
|
|
384
393
|
// ── Chat ────────────────────────────────────────────────────────────────────
|
|
385
394
|
async _sendMessage() {
|
|
395
|
+
var a;
|
|
386
396
|
const e = this._textarea.value.trim();
|
|
387
|
-
if (!
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
} finally {
|
|
399
|
-
this._loading = !1, this._sendBtn.disabled = !1, this._textarea.focus();
|
|
397
|
+
if (!e || this._loading) return;
|
|
398
|
+
const t = this._cfg.rateLimiting;
|
|
399
|
+
if (t != null && t.enabled) {
|
|
400
|
+
const s = Date.now();
|
|
401
|
+
if (s - this._minuteStart >= 6e4 && (this._minuteCount = 0, this._minuteStart = s), t.maxMessagesPerSession > 0 && this._messageCount >= t.maxMessagesPerSession) {
|
|
402
|
+
this._appendMessage("error", t.limitReachedMessage, !1), this._textarea.disabled = !0, this._sendBtn.disabled = !0;
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
if (t.maxMessagesPerMinute > 0 && this._minuteCount >= t.maxMessagesPerMinute) {
|
|
406
|
+
this._appendMessage("error", t.rateLimitMessage, !1);
|
|
407
|
+
return;
|
|
400
408
|
}
|
|
401
409
|
}
|
|
410
|
+
this._textarea.value = "", this._textarea.style.height = "auto", this._loading = !0, this._sendBtn.disabled = !0, this._messages.push({ role: "user", content: e }), this._appendMessage("user", e, !1), this._messageCount += 1, this._minuteCount += 1;
|
|
411
|
+
try {
|
|
412
|
+
if (this._cfg.streaming)
|
|
413
|
+
await this._callApiStream();
|
|
414
|
+
else {
|
|
415
|
+
const s = this._showTyping(), i = await this._callApi();
|
|
416
|
+
this._removeTyping(s), this._messages.push({ role: "assistant", content: i }), this._appendMessage("assistant", i, !0);
|
|
417
|
+
}
|
|
418
|
+
} catch (s) {
|
|
419
|
+
if (s.status === 429 || (a = s.message) != null && a.includes("429")) {
|
|
420
|
+
const i = s.rateLimitMessage || (t == null ? void 0 : t.rateLimitMessage) || "You're sending messages too quickly. Please wait a moment.";
|
|
421
|
+
this._appendMessage("error", i, !1);
|
|
422
|
+
} else
|
|
423
|
+
this._appendMessage("error", "Sorry, something went wrong. Please try again.", !1);
|
|
424
|
+
console.error("[CognitionDesk]", s);
|
|
425
|
+
} finally {
|
|
426
|
+
this._loading = !1, (t == null ? void 0 : t.maxMessagesPerSession) > 0 && this._messageCount >= t.maxMessagesPerSession || (this._sendBtn.disabled = !1), this._textarea.focus();
|
|
427
|
+
}
|
|
402
428
|
}
|
|
403
429
|
_buildRequestBody() {
|
|
404
430
|
return {
|
|
@@ -420,56 +446,56 @@ class m {
|
|
|
420
446
|
body: JSON.stringify({ ...this._buildRequestBody(), stream: !0 })
|
|
421
447
|
});
|
|
422
448
|
if (!t.ok) {
|
|
423
|
-
const
|
|
424
|
-
throw
|
|
449
|
+
const n = await t.json().catch(() => ({})), d = new Error(n.message || `HTTP ${t.status}`);
|
|
450
|
+
throw d.status = t.status, d.rateLimitMessage = n.message, d;
|
|
425
451
|
}
|
|
426
|
-
const
|
|
427
|
-
|
|
428
|
-
const
|
|
429
|
-
let
|
|
452
|
+
const a = document.createElement("div");
|
|
453
|
+
a.className = "cd-msg assistant", a.innerHTML = '<div class="cd-typing"><span></span><span></span><span></span></div>', this._messagesEl.appendChild(a), this._scrollToBottom();
|
|
454
|
+
const s = t.body.getReader(), i = new TextDecoder();
|
|
455
|
+
let r = "", o = !1;
|
|
430
456
|
try {
|
|
431
457
|
for (; ; ) {
|
|
432
|
-
const { done:
|
|
433
|
-
if (
|
|
434
|
-
const l =
|
|
458
|
+
const { done: n, value: d } = await s.read();
|
|
459
|
+
if (n) break;
|
|
460
|
+
const l = i.decode(d, { stream: !0 });
|
|
435
461
|
for (const g of l.split(`
|
|
436
462
|
`)) {
|
|
437
463
|
if (!g.startsWith("data: ")) continue;
|
|
438
|
-
const
|
|
439
|
-
if (
|
|
440
|
-
let
|
|
464
|
+
const m = g.slice(6).trim();
|
|
465
|
+
if (m === "[DONE]") break;
|
|
466
|
+
let h;
|
|
441
467
|
try {
|
|
442
|
-
|
|
468
|
+
h = JSON.parse(m);
|
|
443
469
|
} catch {
|
|
444
470
|
continue;
|
|
445
471
|
}
|
|
446
|
-
|
|
472
|
+
h.type === "content" && h.data && (o || (a.innerHTML = "", o = !0), r += h.data, a.innerHTML = this._renderMarkdown(r), this._scrollToBottom());
|
|
447
473
|
}
|
|
448
474
|
}
|
|
449
475
|
} finally {
|
|
450
|
-
|
|
476
|
+
s.releaseLock();
|
|
451
477
|
}
|
|
452
|
-
|
|
478
|
+
o || (a.innerHTML = this._renderMarkdown("No response")), r && this._messages.push({ role: "assistant", content: r });
|
|
453
479
|
}
|
|
454
480
|
/** Non-streaming fallback path */
|
|
455
481
|
async _callApi() {
|
|
456
|
-
var
|
|
482
|
+
var s, i, r;
|
|
457
483
|
const e = `${this._cfg.backendUrl}/chat-apiKeyAuth/chat`, t = await fetch(e, {
|
|
458
484
|
method: "POST",
|
|
459
485
|
headers: { "Content-Type": "application/json", "x-api-key": this._cfg.apiKey },
|
|
460
486
|
body: JSON.stringify(this._buildRequestBody())
|
|
461
487
|
});
|
|
462
488
|
if (!t.ok) {
|
|
463
|
-
const
|
|
464
|
-
throw
|
|
489
|
+
const o = await t.json().catch(() => ({})), n = new Error(o.message || `HTTP ${t.status}`);
|
|
490
|
+
throw n.status = t.status, n.rateLimitMessage = o.message, n;
|
|
465
491
|
}
|
|
466
|
-
const
|
|
467
|
-
return
|
|
492
|
+
const a = await t.json();
|
|
493
|
+
return a.response || a.message || a.content || ((r = (i = (s = a.choices) == null ? void 0 : s[0]) == null ? void 0 : i.message) == null ? void 0 : r.content) || "No response";
|
|
468
494
|
}
|
|
469
495
|
// ── DOM helpers ─────────────────────────────────────────────────────────────
|
|
470
|
-
_appendMessage(e, t,
|
|
471
|
-
const
|
|
472
|
-
|
|
496
|
+
_appendMessage(e, t, a = !0) {
|
|
497
|
+
const s = document.createElement("div");
|
|
498
|
+
s.className = `cd-msg ${e}`, a && e !== "user" ? s.innerHTML = this._renderMarkdown(t) : s.textContent = t, this._messagesEl.appendChild(s), this._scrollToBottom();
|
|
473
499
|
}
|
|
474
500
|
/** Minimal, safe markdown → HTML renderer (no dependencies). */
|
|
475
501
|
_renderMarkdown(e) {
|
|
@@ -477,7 +503,7 @@ class m {
|
|
|
477
503
|
let t = this._escHtml(e);
|
|
478
504
|
return t = t.replace(
|
|
479
505
|
/```[\w]*\n?([\s\S]*?)```/g,
|
|
480
|
-
(
|
|
506
|
+
(a, s) => `<pre style="background:#0f172a;color:#e2e8f0;padding:10px;border-radius:6px;font-size:12px;overflow-x:auto;margin:6px 0;white-space:pre-wrap">${s.trim()}</pre>`
|
|
481
507
|
), t = t.replace(/`([^`]+)`/g, '<code style="background:rgba(0,0,0,.08);padding:1px 5px;border-radius:4px;font-size:.9em">$1</code>'), t = t.replace(/\*\*([^*]+)\*\*/g, "<strong>$1</strong>"), t = t.replace(/__([^_]+)__/g, "<strong>$1</strong>"), t = t.replace(/\*([^*]+)\*/g, "<em>$1</em>"), t = t.replace(/_([^_]+)_/g, "<em>$1</em>"), t = t.replace(
|
|
482
508
|
/\[([^\]]+)\]\((https?:\/\/[^)]+)\)/g,
|
|
483
509
|
'<a href="$2" target="_blank" rel="noopener" style="color:var(--cd-primary,#2563eb)">$1</a>'
|
|
@@ -497,8 +523,8 @@ class m {
|
|
|
497
523
|
return String(e).replace(/[&<>"']/g, (t) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[t]);
|
|
498
524
|
}
|
|
499
525
|
}
|
|
500
|
-
function
|
|
501
|
-
const e = new
|
|
526
|
+
function x(c) {
|
|
527
|
+
const e = new f(c);
|
|
502
528
|
return e.mount(), e;
|
|
503
529
|
}
|
|
504
530
|
if (typeof document < "u") {
|
|
@@ -506,9 +532,9 @@ if (typeof document < "u") {
|
|
|
506
532
|
document.querySelectorAll("script[data-api-key]").forEach((e) => {
|
|
507
533
|
const t = e.getAttribute("data-api-key");
|
|
508
534
|
if (!t) return;
|
|
509
|
-
const
|
|
535
|
+
const a = e.getAttribute("data-widget-id") || void 0, s = e.getAttribute("data-streaming"), i = new f({
|
|
510
536
|
apiKey: t,
|
|
511
|
-
widgetId:
|
|
537
|
+
widgetId: a,
|
|
512
538
|
assistantId: e.getAttribute("data-assistant-id") || void 0,
|
|
513
539
|
theme: e.getAttribute("data-theme") || "light",
|
|
514
540
|
primaryColor: e.getAttribute("data-primary-color") || "#2563eb",
|
|
@@ -517,15 +543,15 @@ if (typeof document < "u") {
|
|
|
517
543
|
welcomeMessage: e.getAttribute("data-welcome-message") || void 0,
|
|
518
544
|
placeholder: e.getAttribute("data-placeholder") || void 0,
|
|
519
545
|
backendUrl: e.getAttribute("data-backend-url") || void 0,
|
|
520
|
-
streaming:
|
|
546
|
+
streaming: s === null ? !0 : s !== "false"
|
|
521
547
|
});
|
|
522
|
-
Promise.resolve(
|
|
548
|
+
Promise.resolve(i.mount());
|
|
523
549
|
});
|
|
524
550
|
};
|
|
525
551
|
document.readyState === "loading" ? document.addEventListener("DOMContentLoaded", c) : c();
|
|
526
552
|
}
|
|
527
553
|
export {
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
554
|
+
f as CognitionDeskWidget,
|
|
555
|
+
f as default,
|
|
556
|
+
x as init
|
|
531
557
|
};
|
package/dist/widget.umd.cjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
(function(
|
|
1
|
+
(function(c,l){typeof exports=="object"&&typeof module<"u"?l(exports):typeof define=="function"&&define.amd?define(["exports"],l):(c=typeof globalThis<"u"?globalThis:c||self,l(c.CognitionDeskWidget={}))})(this,function(c){"use strict";const l="https://mounaji-backendv3.onrender.com",b=`
|
|
2
2
|
:host {
|
|
3
3
|
all: initial;
|
|
4
4
|
font-family: system-ui, sans-serif;
|
|
@@ -244,7 +244,7 @@
|
|
|
244
244
|
font-size: 16px;
|
|
245
245
|
}
|
|
246
246
|
}
|
|
247
|
-
`,
|
|
247
|
+
`,f={chat:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>',close:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>',send:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="22" y1="2" x2="11" y2="13"/><polygon points="22 2 15 22 11 13 2 9 22 2"/></svg>'};function w(){return"cd_"+Math.random().toString(36).slice(2)+Date.now().toString(36)}class g{constructor(e={}){var t,a,s,i,n;if(!e.apiKey)throw new Error("[CognitionDesk] apiKey is required");this._cfg={apiKey:e.apiKey,widgetId:e.widgetId||null,assistantId:e.assistantId||null,backendUrl:e.backendUrl||l,primaryColor:e.primaryColor||"#2563eb",theme:e.theme||"light",botName:e.botName||"AI Assistant",botEmoji:e.botEmoji||"🤖",welcomeMessage:e.welcomeMessage||"Hello! How can I help you today?",placeholder:e.placeholder||"Type a message…",position:e.position||"bottom-right",streaming:e.streaming!==!1,rateLimiting:{enabled:((t=e.rateLimiting)==null?void 0:t.enabled)!==!1,maxMessagesPerSession:((a=e.rateLimiting)==null?void 0:a.maxMessagesPerSession)??0,maxMessagesPerMinute:((s=e.rateLimiting)==null?void 0:s.maxMessagesPerMinute)??0,limitReachedMessage:((i=e.rateLimiting)==null?void 0:i.limitReachedMessage)||"You've reached the message limit for this session.",rateLimitMessage:((n=e.rateLimiting)==null?void 0:n.rateLimitMessage)||"You're sending messages too quickly. Please wait a moment."}},this._sessionId=w(),this._messageCount=0,this._minuteCount=0,this._minuteStart=Date.now(),this._messages=[],this._open=!1,this._loading=!1,this._container=null,this._shadow=null,this._panel=null,this._messagesEl=null,this._textarea=null,this._sendBtn=null,this._viewportHandler=null}mount(e){const t=()=>{const a=e||document.body;this._container=document.createElement("div"),this._container.setAttribute("data-cognitiondesk",""),a.appendChild(this._container),this._shadow=this._container.attachShadow({mode:"open"});const s=document.createElement("style");s.textContent=b,this._shadow.appendChild(s),this._buildDOM(),this._applyTheme(),this._syncViewportMetrics(),this._bindViewportMetrics(),this._bindEvents(),this._cfg.welcomeMessage&&this._appendMessage("assistant",this._cfg.welcomeMessage,!1)};return this._cfg.widgetId?this._fetchWidgetConfig().then(()=>t()).catch(()=>t()):(t(),Promise.resolve(this))}async _fetchWidgetConfig(){var t;const e=`${this._cfg.backendUrl}/platforms/web-widget/public/${this._cfg.widgetId}`;try{const a=await fetch(e);if(!a.ok)return;const{config:s}=await a.json();if(!s)return;s.botName&&!this._userSet("botName")&&(this._cfg.botName=s.botName),s.welcomeMessage&&!this._userSet("welcomeMessage")&&(this._cfg.welcomeMessage=s.welcomeMessage),s.primaryColor&&!this._userSet("primaryColor")&&(this._cfg.primaryColor=s.primaryColor),s.placeholder&&!this._userSet("placeholder")&&(this._cfg.placeholder=s.placeholder),s.assistantId&&!this._cfg.assistantId&&(this._cfg.assistantId=s.assistantId),(t=s.avatar)!=null&&t.value&&!this._userSet("botEmoji")&&(this._cfg.botEmoji=s.avatar.value),s.rateLimiting&&(this._cfg.rateLimiting={...this._cfg.rateLimiting,...s.rateLimiting})}catch{}}_userSet(e){return!1}unmount(){this._unbindViewportMetrics(),this._container&&(this._container.remove(),this._container=null)}open(){var e,t;this._open=!0,this._syncViewportMetrics(),(e=this._panel)==null||e.classList.add("open"),(t=this._textarea)==null||t.focus()}close(){var e;this._open=!1,(e=this._panel)==null||e.classList.remove("open")}toggle(){this._open?this.close():this.open()}clearHistory(){this._messages=[],this._messagesEl&&(this._messagesEl.innerHTML=""),this._cfg.welcomeMessage&&this._appendMessage("assistant",this._cfg.welcomeMessage)}_buildDOM(){const e=document.createElement("div");e.className="cd-root";const t=document.createElement("button");t.className="cd-widget-btn",t.innerHTML=f.chat,t.setAttribute("aria-label","Open chat"),t.style.setProperty("--cd-primary",this._cfg.primaryColor),this._toggleBtn=t;const a=document.createElement("div");a.className="cd-panel",this._panel=a;const s=document.createElement("div");s.className="cd-header",s.innerHTML=`
|
|
248
248
|
<div class="cd-header-avatar">${this._cfg.botEmoji}</div>
|
|
249
249
|
<div class="cd-header-info">
|
|
250
250
|
<div class="cd-header-name">${this._escHtml(this._cfg.botName)}</div>
|
|
@@ -252,5 +252,5 @@
|
|
|
252
252
|
<span class="cd-status-dot"></span> Online
|
|
253
253
|
</div>
|
|
254
254
|
</div>
|
|
255
|
-
`;const
|
|
256
|
-
`)){if(!u.startsWith("data: "))continue;const
|
|
255
|
+
`;const i=document.createElement("button");i.className="cd-close-btn",i.innerHTML=f.close,i.setAttribute("aria-label","Close chat"),s.appendChild(i),this._closeBtn=i;const n=document.createElement("div");n.className="cd-messages",n.setAttribute("role","log"),n.setAttribute("aria-live","polite"),this._messagesEl=n;const o=document.createElement("div");o.className="cd-input-area";const r=document.createElement("textarea");r.className="cd-textarea",r.rows=1,r.placeholder=this._cfg.placeholder,this._textarea=r;const d=document.createElement("button");d.className="cd-send-btn",d.innerHTML=f.send,d.setAttribute("aria-label","Send"),this._sendBtn=d,o.appendChild(r),o.appendChild(d);const p=document.createElement("div");p.className="cd-powered",p.innerHTML='Powered by <a href="https://cognitiondesk.com" target="_blank" rel="noopener">CognitionDesk</a>',a.appendChild(s),a.appendChild(n),a.appendChild(o),a.appendChild(p),e.appendChild(t),e.appendChild(a),this._shadow.appendChild(e),this._root=e}_applyTheme(){var t,a,s;(this._cfg.theme==="auto"?window.matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light":this._cfg.theme)==="dark"&&((t=this._root)==null||t.classList.add("cd-dark")),(a=this._root)==null||a.style.setProperty("--cd-primary",this._cfg.primaryColor),(s=this._panel)==null||s.style.setProperty("--cd-primary",this._cfg.primaryColor)}_bindEvents(){this._toggleBtn.addEventListener("click",()=>this.toggle()),this._closeBtn.addEventListener("click",()=>this.close()),this._sendBtn.addEventListener("click",()=>this._sendMessage()),this._textarea.addEventListener("keydown",e=>{e.key==="Enter"&&!e.shiftKey&&(e.preventDefault(),this._sendMessage())}),this._textarea.addEventListener("input",()=>{this._textarea.style.height="auto",this._textarea.style.height=Math.min(this._textarea.scrollHeight,120)+"px"}),this._textarea.addEventListener("focus",()=>{this._syncViewportMetrics()})}_bindViewportMetrics(){this._viewportHandler||(this._viewportHandler=()=>this._syncViewportMetrics(),window.addEventListener("resize",this._viewportHandler,{passive:!0}),window.addEventListener("orientationchange",this._viewportHandler),window.visualViewport&&(window.visualViewport.addEventListener("resize",this._viewportHandler),window.visualViewport.addEventListener("scroll",this._viewportHandler)))}_unbindViewportMetrics(){this._viewportHandler&&(window.removeEventListener("resize",this._viewportHandler),window.removeEventListener("orientationchange",this._viewportHandler),window.visualViewport&&(window.visualViewport.removeEventListener("resize",this._viewportHandler),window.visualViewport.removeEventListener("scroll",this._viewportHandler)),this._viewportHandler=null)}_syncViewportMetrics(){const e=this._root||this._container;if(!e)return;const t=window.visualViewport,a=t?t.height:window.innerHeight;e.style.setProperty("--cd-viewport-height",`${Math.round(a)}px`)}async _sendMessage(){var a;const e=this._textarea.value.trim();if(!e||this._loading)return;const t=this._cfg.rateLimiting;if(t!=null&&t.enabled){const s=Date.now();if(s-this._minuteStart>=6e4&&(this._minuteCount=0,this._minuteStart=s),t.maxMessagesPerSession>0&&this._messageCount>=t.maxMessagesPerSession){this._appendMessage("error",t.limitReachedMessage,!1),this._textarea.disabled=!0,this._sendBtn.disabled=!0;return}if(t.maxMessagesPerMinute>0&&this._minuteCount>=t.maxMessagesPerMinute){this._appendMessage("error",t.rateLimitMessage,!1);return}}this._textarea.value="",this._textarea.style.height="auto",this._loading=!0,this._sendBtn.disabled=!0,this._messages.push({role:"user",content:e}),this._appendMessage("user",e,!1),this._messageCount+=1,this._minuteCount+=1;try{if(this._cfg.streaming)await this._callApiStream();else{const s=this._showTyping(),i=await this._callApi();this._removeTyping(s),this._messages.push({role:"assistant",content:i}),this._appendMessage("assistant",i,!0)}}catch(s){if(s.status===429||(a=s.message)!=null&&a.includes("429")){const i=s.rateLimitMessage||(t==null?void 0:t.rateLimitMessage)||"You're sending messages too quickly. Please wait a moment.";this._appendMessage("error",i,!1)}else this._appendMessage("error","Sorry, something went wrong. Please try again.",!1);console.error("[CognitionDesk]",s)}finally{this._loading=!1,(t==null?void 0:t.maxMessagesPerSession)>0&&this._messageCount>=t.maxMessagesPerSession||(this._sendBtn.disabled=!1),this._textarea.focus()}}_buildRequestBody(){return{messages:this._messages,assistantId:this._cfg.assistantId||void 0,userContext:{sessionId:this._sessionId,assistantId:this._cfg.assistantId||void 0,widgetId:this._cfg.widgetId||void 0,platform:"cognitiondesk-widget"}}}async _callApiStream(){const e=`${this._cfg.backendUrl}/chat-apiKeyAuth/stream`,t=await fetch(e,{method:"POST",headers:{"Content-Type":"application/json","x-api-key":this._cfg.apiKey},body:JSON.stringify({...this._buildRequestBody(),stream:!0})});if(!t.ok){const r=await t.json().catch(()=>({})),d=new Error(r.message||`HTTP ${t.status}`);throw d.status=t.status,d.rateLimitMessage=r.message,d}const a=document.createElement("div");a.className="cd-msg assistant",a.innerHTML='<div class="cd-typing"><span></span><span></span><span></span></div>',this._messagesEl.appendChild(a),this._scrollToBottom();const s=t.body.getReader(),i=new TextDecoder;let n="",o=!1;try{for(;;){const{done:r,value:d}=await s.read();if(r)break;const p=i.decode(d,{stream:!0});for(const u of p.split(`
|
|
256
|
+
`)){if(!u.startsWith("data: "))continue;const _=u.slice(6).trim();if(_==="[DONE]")break;let m;try{m=JSON.parse(_)}catch{continue}m.type==="content"&&m.data&&(o||(a.innerHTML="",o=!0),n+=m.data,a.innerHTML=this._renderMarkdown(n),this._scrollToBottom())}}}finally{s.releaseLock()}o||(a.innerHTML=this._renderMarkdown("No response")),n&&this._messages.push({role:"assistant",content:n})}async _callApi(){var s,i,n;const e=`${this._cfg.backendUrl}/chat-apiKeyAuth/chat`,t=await fetch(e,{method:"POST",headers:{"Content-Type":"application/json","x-api-key":this._cfg.apiKey},body:JSON.stringify(this._buildRequestBody())});if(!t.ok){const o=await t.json().catch(()=>({})),r=new Error(o.message||`HTTP ${t.status}`);throw r.status=t.status,r.rateLimitMessage=o.message,r}const a=await t.json();return a.response||a.message||a.content||((n=(i=(s=a.choices)==null?void 0:s[0])==null?void 0:i.message)==null?void 0:n.content)||"No response"}_appendMessage(e,t,a=!0){const s=document.createElement("div");s.className=`cd-msg ${e}`,a&&e!=="user"?s.innerHTML=this._renderMarkdown(t):s.textContent=t,this._messagesEl.appendChild(s),this._scrollToBottom()}_renderMarkdown(e){if(!e)return"";let t=this._escHtml(e);return t=t.replace(/```[\w]*\n?([\s\S]*?)```/g,(a,s)=>`<pre style="background:#0f172a;color:#e2e8f0;padding:10px;border-radius:6px;font-size:12px;overflow-x:auto;margin:6px 0;white-space:pre-wrap">${s.trim()}</pre>`),t=t.replace(/`([^`]+)`/g,'<code style="background:rgba(0,0,0,.08);padding:1px 5px;border-radius:4px;font-size:.9em">$1</code>'),t=t.replace(/\*\*([^*]+)\*\*/g,"<strong>$1</strong>"),t=t.replace(/__([^_]+)__/g,"<strong>$1</strong>"),t=t.replace(/\*([^*]+)\*/g,"<em>$1</em>"),t=t.replace(/_([^_]+)_/g,"<em>$1</em>"),t=t.replace(/\[([^\]]+)\]\((https?:\/\/[^)]+)\)/g,'<a href="$2" target="_blank" rel="noopener" style="color:var(--cd-primary,#2563eb)">$1</a>'),t=t.replace(/\n/g,"<br>"),t}_showTyping(){const e=document.createElement("div");return e.className="cd-msg assistant",e.innerHTML='<div class="cd-typing"><span></span><span></span><span></span></div>',this._messagesEl.appendChild(e),this._scrollToBottom(),e}_removeTyping(e){e==null||e.remove()}_scrollToBottom(){this._messagesEl&&(this._messagesEl.scrollTop=this._messagesEl.scrollHeight)}_escHtml(e){return String(e).replace(/[&<>"']/g,t=>({"&":"&","<":"<",">":">",'"':""","'":"'"})[t])}}function x(h){const e=new g(h);return e.mount(),e}if(typeof document<"u"){const h=()=>{document.querySelectorAll("script[data-api-key]").forEach(e=>{const t=e.getAttribute("data-api-key");if(!t)return;const a=e.getAttribute("data-widget-id")||void 0,s=e.getAttribute("data-streaming"),i=new g({apiKey:t,widgetId:a,assistantId:e.getAttribute("data-assistant-id")||void 0,theme:e.getAttribute("data-theme")||"light",primaryColor:e.getAttribute("data-primary-color")||"#2563eb",botName:e.getAttribute("data-bot-name")||"AI Assistant",botEmoji:e.getAttribute("data-bot-emoji")||void 0,welcomeMessage:e.getAttribute("data-welcome-message")||void 0,placeholder:e.getAttribute("data-placeholder")||void 0,backendUrl:e.getAttribute("data-backend-url")||void 0,streaming:s===null?!0:s!=="false"});Promise.resolve(i.mount())})};document.readyState==="loading"?document.addEventListener("DOMContentLoaded",h):h()}c.CognitionDeskWidget=g,c.default=g,c.init=x,Object.defineProperties(c,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}})});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cognitiondesk/widget",
|
|
3
|
-
"version": "1.1
|
|
3
|
+
"version": "1.2.1",
|
|
4
4
|
"description": "Embed a CognitionDesk AI chat widget into any website. Authenticate with an API key from your CognitionDesk dashboard.",
|
|
5
5
|
"keywords": ["chat", "widget", "ai", "cognitiondesk", "chatbot", "react", "streaming", "mounaji"],
|
|
6
6
|
"license": "MIT",
|
package/src/react.jsx
CHANGED
|
@@ -42,6 +42,11 @@ export const CognitionDeskWidget = forwardRef(function CognitionDeskWidget(props
|
|
|
42
42
|
defaultOpen = false,
|
|
43
43
|
streaming = true,
|
|
44
44
|
backendUrl,
|
|
45
|
+
// Rate limiting — 0 means unlimited
|
|
46
|
+
maxMessagesPerSession = 0,
|
|
47
|
+
maxMessagesPerMinute = 0,
|
|
48
|
+
limitReachedMessage,
|
|
49
|
+
rateLimitMessage,
|
|
45
50
|
onOpen,
|
|
46
51
|
onClose,
|
|
47
52
|
} = props;
|
|
@@ -75,6 +80,13 @@ export const CognitionDeskWidget = forwardRef(function CognitionDeskWidget(props
|
|
|
75
80
|
placeholder,
|
|
76
81
|
streaming,
|
|
77
82
|
...(backendUrl ? { backendUrl } : {}),
|
|
83
|
+
rateLimiting: {
|
|
84
|
+
enabled: true,
|
|
85
|
+
maxMessagesPerSession,
|
|
86
|
+
maxMessagesPerMinute,
|
|
87
|
+
...(limitReachedMessage ? { limitReachedMessage } : {}),
|
|
88
|
+
...(rateLimitMessage ? { rateLimitMessage } : {}),
|
|
89
|
+
},
|
|
78
90
|
});
|
|
79
91
|
|
|
80
92
|
// Patch open/close to fire callbacks
|
package/src/widget.js
CHANGED
|
@@ -287,9 +287,20 @@ export default class CognitionDeskWidget {
|
|
|
287
287
|
placeholder: config.placeholder || 'Type a message…',
|
|
288
288
|
position: config.position || 'bottom-right',
|
|
289
289
|
streaming: config.streaming !== false, // default: true
|
|
290
|
+
// Rate limiting — can be overridden by inline config or merged from server config
|
|
291
|
+
rateLimiting: {
|
|
292
|
+
enabled: config.rateLimiting?.enabled !== false,
|
|
293
|
+
maxMessagesPerSession: config.rateLimiting?.maxMessagesPerSession ?? 0,
|
|
294
|
+
maxMessagesPerMinute: config.rateLimiting?.maxMessagesPerMinute ?? 0,
|
|
295
|
+
limitReachedMessage: config.rateLimiting?.limitReachedMessage || "You've reached the message limit for this session.",
|
|
296
|
+
rateLimitMessage: config.rateLimiting?.rateLimitMessage || "You're sending messages too quickly. Please wait a moment.",
|
|
297
|
+
},
|
|
290
298
|
};
|
|
291
299
|
|
|
292
|
-
this._sessionId
|
|
300
|
+
this._sessionId = generateSessionId();
|
|
301
|
+
this._messageCount = 0; // total sent this session
|
|
302
|
+
this._minuteCount = 0; // sent in current minute window
|
|
303
|
+
this._minuteStart = Date.now(); // start of current minute window
|
|
293
304
|
this._messages = []; // { role: 'user'|'assistant', content: string }[]
|
|
294
305
|
this._open = false;
|
|
295
306
|
this._loading = false;
|
|
@@ -360,6 +371,10 @@ export default class CognitionDeskWidget {
|
|
|
360
371
|
if (config.placeholder && !this._userSet('placeholder')) this._cfg.placeholder = config.placeholder;
|
|
361
372
|
if (config.assistantId && !this._cfg.assistantId) this._cfg.assistantId = config.assistantId;
|
|
362
373
|
if (config.avatar?.value && !this._userSet('botEmoji')) this._cfg.botEmoji = config.avatar.value;
|
|
374
|
+
// Merge rate limiting from server config (server is authoritative)
|
|
375
|
+
if (config.rateLimiting) {
|
|
376
|
+
this._cfg.rateLimiting = { ...this._cfg.rateLimiting, ...config.rateLimiting };
|
|
377
|
+
}
|
|
363
378
|
} catch {
|
|
364
379
|
// Silently ignore — widget falls back to inline config
|
|
365
380
|
}
|
|
@@ -560,6 +575,27 @@ export default class CognitionDeskWidget {
|
|
|
560
575
|
const text = this._textarea.value.trim();
|
|
561
576
|
if (!text || this._loading) return;
|
|
562
577
|
|
|
578
|
+
// ── Client-side rate limit check (mirrors server enforcement) ──
|
|
579
|
+
const rl = this._cfg.rateLimiting;
|
|
580
|
+
if (rl?.enabled) {
|
|
581
|
+
const now = Date.now();
|
|
582
|
+
// Reset minute window
|
|
583
|
+
if (now - this._minuteStart >= 60_000) {
|
|
584
|
+
this._minuteCount = 0;
|
|
585
|
+
this._minuteStart = now;
|
|
586
|
+
}
|
|
587
|
+
if (rl.maxMessagesPerSession > 0 && this._messageCount >= rl.maxMessagesPerSession) {
|
|
588
|
+
this._appendMessage('error', rl.limitReachedMessage, false);
|
|
589
|
+
this._textarea.disabled = true;
|
|
590
|
+
this._sendBtn.disabled = true;
|
|
591
|
+
return;
|
|
592
|
+
}
|
|
593
|
+
if (rl.maxMessagesPerMinute > 0 && this._minuteCount >= rl.maxMessagesPerMinute) {
|
|
594
|
+
this._appendMessage('error', rl.rateLimitMessage, false);
|
|
595
|
+
return;
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
|
|
563
599
|
this._textarea.value = '';
|
|
564
600
|
this._textarea.style.height = 'auto';
|
|
565
601
|
this._loading = true;
|
|
@@ -567,6 +603,8 @@ export default class CognitionDeskWidget {
|
|
|
567
603
|
|
|
568
604
|
this._messages.push({ role: 'user', content: text });
|
|
569
605
|
this._appendMessage('user', text, false);
|
|
606
|
+
this._messageCount += 1;
|
|
607
|
+
this._minuteCount += 1;
|
|
570
608
|
|
|
571
609
|
try {
|
|
572
610
|
if (this._cfg.streaming) {
|
|
@@ -579,11 +617,19 @@ export default class CognitionDeskWidget {
|
|
|
579
617
|
this._appendMessage('assistant', response, true);
|
|
580
618
|
}
|
|
581
619
|
} catch (err) {
|
|
582
|
-
|
|
620
|
+
// Handle server-side 429 rate limit response
|
|
621
|
+
if (err.status === 429 || err.message?.includes('429')) {
|
|
622
|
+
const msg = err.rateLimitMessage || rl?.rateLimitMessage || "You're sending messages too quickly. Please wait a moment.";
|
|
623
|
+
this._appendMessage('error', msg, false);
|
|
624
|
+
} else {
|
|
625
|
+
this._appendMessage('error', 'Sorry, something went wrong. Please try again.', false);
|
|
626
|
+
}
|
|
583
627
|
console.error('[CognitionDesk]', err);
|
|
584
628
|
} finally {
|
|
585
629
|
this._loading = false;
|
|
586
|
-
this.
|
|
630
|
+
if (!(rl?.maxMessagesPerSession > 0 && this._messageCount >= rl.maxMessagesPerSession)) {
|
|
631
|
+
this._sendBtn.disabled = false;
|
|
632
|
+
}
|
|
587
633
|
this._textarea.focus();
|
|
588
634
|
}
|
|
589
635
|
}
|
|
@@ -613,7 +659,10 @@ export default class CognitionDeskWidget {
|
|
|
613
659
|
|
|
614
660
|
if (!res.ok) {
|
|
615
661
|
const errData = await res.json().catch(() => ({}));
|
|
616
|
-
|
|
662
|
+
const err = new Error(errData.message || `HTTP ${res.status}`);
|
|
663
|
+
err.status = res.status;
|
|
664
|
+
err.rateLimitMessage = errData.message;
|
|
665
|
+
throw err;
|
|
617
666
|
}
|
|
618
667
|
|
|
619
668
|
// Create an empty assistant bubble to stream into
|
|
@@ -673,7 +722,10 @@ export default class CognitionDeskWidget {
|
|
|
673
722
|
|
|
674
723
|
if (!res.ok) {
|
|
675
724
|
const errData = await res.json().catch(() => ({}));
|
|
676
|
-
|
|
725
|
+
const err = new Error(errData.message || `HTTP ${res.status}`);
|
|
726
|
+
err.status = res.status;
|
|
727
|
+
err.rateLimitMessage = errData.message;
|
|
728
|
+
throw err;
|
|
677
729
|
}
|
|
678
730
|
|
|
679
731
|
const data = await res.json();
|