@scalemule/chat 0.0.4 → 0.0.7
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/{ChatClient-COmdEJ11.d.ts → ChatClient-DQPHdUHX.d.cts} +21 -2
- package/dist/{ChatClient-BoZaTtyM.d.cts → ChatClient-DtUKF-4c.d.ts} +21 -2
- package/dist/chat.embed.global.js +1 -1
- package/dist/chat.umd.global.js +288 -12
- package/dist/{chunk-ZLMMNFZL.js → chunk-5O5YLRJL.js} +386 -16
- package/dist/chunk-GTMAK3IA.js +285 -0
- package/dist/chunk-TRCELAZQ.cjs +287 -0
- package/dist/{chunk-YDLRISR7.cjs → chunk-W2PWFS3E.cjs} +386 -15
- package/dist/constants.d.ts +9 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/core/ChatClient.d.ts +96 -0
- package/dist/core/ChatClient.d.ts.map +1 -0
- package/dist/core/EventEmitter.d.ts +11 -0
- package/dist/core/EventEmitter.d.ts.map +1 -0
- package/dist/core/MessageCache.d.ts +18 -0
- package/dist/core/MessageCache.d.ts.map +1 -0
- package/dist/core/OfflineQueue.d.ts +19 -0
- package/dist/core/OfflineQueue.d.ts.map +1 -0
- package/dist/element.cjs +542 -51
- package/dist/element.d.ts +2 -2
- package/dist/element.d.ts.map +1 -0
- package/dist/element.js +541 -50
- package/dist/embed/index.d.ts +2 -0
- package/dist/embed/index.d.ts.map +1 -0
- package/dist/factory.d.ts +8 -0
- package/dist/factory.d.ts.map +1 -0
- package/dist/iframe.d.cts +1 -1
- package/dist/iframe.d.ts +3 -5
- package/dist/iframe.d.ts.map +1 -0
- package/dist/index.cjs +34 -5
- package/dist/index.d.cts +93 -4
- package/dist/index.d.ts +8 -77
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +29 -4
- package/dist/react-components/ChatInput.d.ts +16 -0
- package/dist/react-components/ChatInput.d.ts.map +1 -0
- package/dist/react-components/ChatMessageItem.d.ts +13 -0
- package/dist/react-components/ChatMessageItem.d.ts.map +1 -0
- package/dist/react-components/ChatMessageList.d.ts +15 -0
- package/dist/react-components/ChatMessageList.d.ts.map +1 -0
- package/dist/react-components/ChatThread.d.ts +12 -0
- package/dist/react-components/ChatThread.d.ts.map +1 -0
- package/dist/react-components/ConversationList.d.ts +13 -0
- package/dist/react-components/ConversationList.d.ts.map +1 -0
- package/dist/react-components/EmojiPicker.d.ts +8 -0
- package/dist/react-components/EmojiPicker.d.ts.map +1 -0
- package/dist/react-components/index.d.ts +8 -0
- package/dist/react-components/index.d.ts.map +1 -0
- package/dist/react-components/theme.d.ts +19 -0
- package/dist/react-components/theme.d.ts.map +1 -0
- package/dist/react-components/utils.d.ts +4 -0
- package/dist/react-components/utils.d.ts.map +1 -0
- package/dist/react.cjs +1213 -53
- package/dist/react.d.cts +100 -4
- package/dist/react.d.ts +38 -15
- package/dist/react.d.ts.map +1 -0
- package/dist/react.js +1167 -18
- package/dist/shared/ChatController.d.ts +65 -0
- package/dist/shared/ChatController.d.ts.map +1 -0
- package/dist/shared/upload.d.ts +3 -0
- package/dist/shared/upload.d.ts.map +1 -0
- package/dist/support-widget.global.js +485 -157
- package/dist/support.d.ts +99 -0
- package/dist/support.d.ts.map +1 -0
- package/dist/transport/HttpTransport.d.ts +20 -0
- package/dist/transport/HttpTransport.d.ts.map +1 -0
- package/dist/transport/WebSocketTransport.d.ts +78 -0
- package/dist/transport/WebSocketTransport.d.ts.map +1 -0
- package/dist/{types-BmD7f1gV.d.cts → types-COPVrm3K.d.cts} +25 -1
- package/dist/{types-BmD7f1gV.d.ts → types-COPVrm3K.d.ts} +25 -1
- package/dist/types.d.ts +271 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/umd.d.ts +6 -0
- package/dist/umd.d.ts.map +1 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/widget/icons.d.ts +8 -0
- package/dist/widget/icons.d.ts.map +1 -0
- package/dist/widget/index.d.ts +2 -0
- package/dist/widget/index.d.ts.map +1 -0
- package/dist/widget/storage.d.ts +5 -0
- package/dist/widget/storage.d.ts.map +1 -0
- package/dist/widget/styles.d.ts +3 -0
- package/dist/widget/styles.d.ts.map +1 -0
- package/package.json +5 -2
|
@@ -61,6 +61,9 @@ var MessageCache = class {
|
|
|
61
61
|
getMessages(conversationId) {
|
|
62
62
|
return this.cache.get(conversationId) ?? [];
|
|
63
63
|
}
|
|
64
|
+
getMessage(conversationId, messageId) {
|
|
65
|
+
return this.getMessages(conversationId).find((message) => message.id === messageId);
|
|
66
|
+
}
|
|
64
67
|
setMessages(conversationId, messages) {
|
|
65
68
|
this.cache.set(conversationId, messages.slice(0, this.maxMessages));
|
|
66
69
|
this.evictOldConversations();
|
|
@@ -75,6 +78,13 @@ var MessageCache = class {
|
|
|
75
78
|
}
|
|
76
79
|
this.cache.set(conversationId, messages);
|
|
77
80
|
}
|
|
81
|
+
upsertMessage(conversationId, message) {
|
|
82
|
+
if (this.getMessage(conversationId, message.id)) {
|
|
83
|
+
this.updateMessage(conversationId, message);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
this.addMessage(conversationId, message);
|
|
87
|
+
}
|
|
78
88
|
updateMessage(conversationId, message) {
|
|
79
89
|
const messages = this.cache.get(conversationId);
|
|
80
90
|
if (!messages) return;
|
|
@@ -83,6 +93,23 @@ var MessageCache = class {
|
|
|
83
93
|
messages[idx] = message;
|
|
84
94
|
}
|
|
85
95
|
}
|
|
96
|
+
reconcileOptimisticMessage(conversationId, message) {
|
|
97
|
+
const messages = this.cache.get(conversationId);
|
|
98
|
+
if (!messages) return message;
|
|
99
|
+
const incomingAttachmentIds = (message.attachments ?? []).map((attachment) => attachment.file_id).sort();
|
|
100
|
+
const pendingIndex = messages.findIndex((cached) => {
|
|
101
|
+
if (!cached.id.startsWith("pending-")) return false;
|
|
102
|
+
if (cached.sender_id !== message.sender_id) return false;
|
|
103
|
+
if (cached.content !== message.content) return false;
|
|
104
|
+
const cachedAttachmentIds = (cached.attachments ?? []).map((attachment) => attachment.file_id).sort();
|
|
105
|
+
if (cachedAttachmentIds.length !== incomingAttachmentIds.length) return false;
|
|
106
|
+
return cachedAttachmentIds.every((fileId, index) => fileId === incomingAttachmentIds[index]);
|
|
107
|
+
});
|
|
108
|
+
if (pendingIndex >= 0) {
|
|
109
|
+
messages[pendingIndex] = message;
|
|
110
|
+
}
|
|
111
|
+
return message;
|
|
112
|
+
}
|
|
86
113
|
removeMessage(conversationId, messageId) {
|
|
87
114
|
const messages = this.cache.get(conversationId);
|
|
88
115
|
if (!messages) return;
|
|
@@ -198,8 +225,7 @@ var HttpTransport = class {
|
|
|
198
225
|
method,
|
|
199
226
|
headers,
|
|
200
227
|
body: body ? JSON.stringify(body) : void 0,
|
|
201
|
-
signal: controller.signal
|
|
202
|
-
credentials: "include"
|
|
228
|
+
signal: controller.signal
|
|
203
229
|
});
|
|
204
230
|
clearTimeout(timeoutId);
|
|
205
231
|
if (response.status === 204) {
|
|
@@ -346,8 +372,7 @@ var WebSocketTransport = class extends EventEmitter {
|
|
|
346
372
|
try {
|
|
347
373
|
const response = await fetch(this.ticketUrl, {
|
|
348
374
|
method: "POST",
|
|
349
|
-
headers
|
|
350
|
-
credentials: "include"
|
|
375
|
+
headers
|
|
351
376
|
});
|
|
352
377
|
if (!response.ok) return null;
|
|
353
378
|
const json = await response.json();
|
|
@@ -429,14 +454,14 @@ var WebSocketTransport = class extends EventEmitter {
|
|
|
429
454
|
}
|
|
430
455
|
this.setStatus("reconnecting");
|
|
431
456
|
this.emit("reconnecting", { attempt: this.reconnectAttempt + 1 });
|
|
432
|
-
const
|
|
457
|
+
const delay2 = Math.min(
|
|
433
458
|
this.baseDelay * Math.pow(2, this.reconnectAttempt) + Math.random() * this.baseDelay * 0.3,
|
|
434
459
|
this.maxDelay
|
|
435
460
|
);
|
|
436
461
|
this.reconnectTimer = setTimeout(() => {
|
|
437
462
|
this.reconnectAttempt++;
|
|
438
463
|
this.connect();
|
|
439
|
-
},
|
|
464
|
+
}, delay2);
|
|
440
465
|
}
|
|
441
466
|
setStatus(status) {
|
|
442
467
|
if (this.status !== status) {
|
|
@@ -446,6 +471,173 @@ var WebSocketTransport = class extends EventEmitter {
|
|
|
446
471
|
}
|
|
447
472
|
};
|
|
448
473
|
|
|
474
|
+
// src/shared/upload.ts
|
|
475
|
+
var RETRY_DELAYS_MS = [0, 1e3, 3e3];
|
|
476
|
+
var STALL_TIMEOUT_MS = 45e3;
|
|
477
|
+
var RETRYABLE_STATUS_CODES = /* @__PURE__ */ new Set([0, 408, 429, 500, 502, 503, 504]);
|
|
478
|
+
function delay(ms) {
|
|
479
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
480
|
+
}
|
|
481
|
+
function shouldRetry(status, code) {
|
|
482
|
+
if (code === "aborted") {
|
|
483
|
+
return false;
|
|
484
|
+
}
|
|
485
|
+
return RETRYABLE_STATUS_CODES.has(status) || code === "upload_stalled";
|
|
486
|
+
}
|
|
487
|
+
function uploadOnce(url, file, onProgress, signal) {
|
|
488
|
+
return new Promise((resolve) => {
|
|
489
|
+
if (typeof XMLHttpRequest === "undefined") {
|
|
490
|
+
resolve({
|
|
491
|
+
data: null,
|
|
492
|
+
error: {
|
|
493
|
+
code: "unsupported_environment",
|
|
494
|
+
message: "XMLHttpRequest is not available in this environment",
|
|
495
|
+
status: 0
|
|
496
|
+
}
|
|
497
|
+
});
|
|
498
|
+
return;
|
|
499
|
+
}
|
|
500
|
+
const xhr = new XMLHttpRequest();
|
|
501
|
+
let settled = false;
|
|
502
|
+
let stallTimer = null;
|
|
503
|
+
let lastLoaded = 0;
|
|
504
|
+
let totalBytes = file.size;
|
|
505
|
+
const finish = (result) => {
|
|
506
|
+
if (settled) return;
|
|
507
|
+
settled = true;
|
|
508
|
+
if (stallTimer) {
|
|
509
|
+
clearTimeout(stallTimer);
|
|
510
|
+
stallTimer = null;
|
|
511
|
+
}
|
|
512
|
+
resolve(result);
|
|
513
|
+
};
|
|
514
|
+
const resetStallTimer = () => {
|
|
515
|
+
if (stallTimer) {
|
|
516
|
+
clearTimeout(stallTimer);
|
|
517
|
+
}
|
|
518
|
+
stallTimer = setTimeout(() => {
|
|
519
|
+
xhr.abort();
|
|
520
|
+
finish({
|
|
521
|
+
data: null,
|
|
522
|
+
error: {
|
|
523
|
+
code: "upload_stalled",
|
|
524
|
+
message: `Upload stalled (no progress for ${STALL_TIMEOUT_MS / 1e3}s)`,
|
|
525
|
+
status: 0,
|
|
526
|
+
details: {
|
|
527
|
+
bytes_sent: lastLoaded,
|
|
528
|
+
total_bytes: totalBytes
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
});
|
|
532
|
+
}, STALL_TIMEOUT_MS);
|
|
533
|
+
};
|
|
534
|
+
if (signal) {
|
|
535
|
+
if (signal.aborted) {
|
|
536
|
+
finish({
|
|
537
|
+
data: null,
|
|
538
|
+
error: { code: "aborted", message: "Upload aborted", status: 0 }
|
|
539
|
+
});
|
|
540
|
+
return;
|
|
541
|
+
}
|
|
542
|
+
signal.addEventListener(
|
|
543
|
+
"abort",
|
|
544
|
+
() => {
|
|
545
|
+
xhr.abort();
|
|
546
|
+
finish({
|
|
547
|
+
data: null,
|
|
548
|
+
error: { code: "aborted", message: "Upload aborted", status: 0 }
|
|
549
|
+
});
|
|
550
|
+
},
|
|
551
|
+
{ once: true }
|
|
552
|
+
);
|
|
553
|
+
}
|
|
554
|
+
xhr.upload.addEventListener("progress", (event) => {
|
|
555
|
+
resetStallTimer();
|
|
556
|
+
lastLoaded = event.loaded;
|
|
557
|
+
totalBytes = event.total || totalBytes;
|
|
558
|
+
if (event.lengthComputable) {
|
|
559
|
+
onProgress?.(Math.round(event.loaded / event.total * 100));
|
|
560
|
+
}
|
|
561
|
+
});
|
|
562
|
+
xhr.addEventListener("load", () => {
|
|
563
|
+
if (xhr.status >= 200 && xhr.status < 300) {
|
|
564
|
+
onProgress?.(100);
|
|
565
|
+
finish({ data: null, error: null });
|
|
566
|
+
return;
|
|
567
|
+
}
|
|
568
|
+
finish({
|
|
569
|
+
data: null,
|
|
570
|
+
error: {
|
|
571
|
+
code: "upload_error",
|
|
572
|
+
message: `S3 upload failed: ${xhr.status}`,
|
|
573
|
+
status: xhr.status,
|
|
574
|
+
details: {
|
|
575
|
+
bytes_sent: lastLoaded,
|
|
576
|
+
total_bytes: totalBytes
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
});
|
|
580
|
+
});
|
|
581
|
+
xhr.addEventListener("error", () => {
|
|
582
|
+
finish({
|
|
583
|
+
data: null,
|
|
584
|
+
error: {
|
|
585
|
+
code: "upload_error",
|
|
586
|
+
message: "S3 upload failed",
|
|
587
|
+
status: xhr.status || 0,
|
|
588
|
+
details: {
|
|
589
|
+
bytes_sent: lastLoaded,
|
|
590
|
+
total_bytes: totalBytes
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
});
|
|
594
|
+
});
|
|
595
|
+
xhr.addEventListener("abort", () => {
|
|
596
|
+
if (settled) return;
|
|
597
|
+
finish({
|
|
598
|
+
data: null,
|
|
599
|
+
error: { code: "aborted", message: "Upload aborted", status: 0 }
|
|
600
|
+
});
|
|
601
|
+
});
|
|
602
|
+
xhr.open("PUT", url, true);
|
|
603
|
+
if (file.type) {
|
|
604
|
+
xhr.setRequestHeader("Content-Type", file.type);
|
|
605
|
+
}
|
|
606
|
+
resetStallTimer();
|
|
607
|
+
xhr.send(file);
|
|
608
|
+
});
|
|
609
|
+
}
|
|
610
|
+
async function uploadToPresignedUrl(url, file, onProgress, signal) {
|
|
611
|
+
let lastError = null;
|
|
612
|
+
for (const [attempt, delayMs] of RETRY_DELAYS_MS.entries()) {
|
|
613
|
+
if (delayMs > 0) {
|
|
614
|
+
await delay(delayMs);
|
|
615
|
+
}
|
|
616
|
+
const result = await uploadOnce(url, file, onProgress, signal);
|
|
617
|
+
if (!result.error) {
|
|
618
|
+
return result;
|
|
619
|
+
}
|
|
620
|
+
lastError = {
|
|
621
|
+
...result.error,
|
|
622
|
+
details: {
|
|
623
|
+
...result.error.details,
|
|
624
|
+
attempt: attempt + 1
|
|
625
|
+
}
|
|
626
|
+
};
|
|
627
|
+
if (!shouldRetry(result.error.status, result.error.code)) {
|
|
628
|
+
break;
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
return {
|
|
632
|
+
data: null,
|
|
633
|
+
error: lastError ?? {
|
|
634
|
+
code: "upload_error",
|
|
635
|
+
message: "Upload failed",
|
|
636
|
+
status: 0
|
|
637
|
+
}
|
|
638
|
+
};
|
|
639
|
+
}
|
|
640
|
+
|
|
449
641
|
// src/core/ChatClient.ts
|
|
450
642
|
var ChatClient = class extends EventEmitter {
|
|
451
643
|
constructor(config) {
|
|
@@ -453,6 +645,7 @@ var ChatClient = class extends EventEmitter {
|
|
|
453
645
|
this.conversationSubs = /* @__PURE__ */ new Map();
|
|
454
646
|
this.conversationTypes = /* @__PURE__ */ new Map();
|
|
455
647
|
const baseUrl = config.apiBaseUrl ?? DEFAULT_API_BASE_URL;
|
|
648
|
+
this.currentUserId = config.userId;
|
|
456
649
|
this.http = new HttpTransport({
|
|
457
650
|
baseUrl,
|
|
458
651
|
apiKey: config.apiKey,
|
|
@@ -511,6 +704,9 @@ var ChatClient = class extends EventEmitter {
|
|
|
511
704
|
get status() {
|
|
512
705
|
return this.ws.getStatus();
|
|
513
706
|
}
|
|
707
|
+
get userId() {
|
|
708
|
+
return this.currentUserId;
|
|
709
|
+
}
|
|
514
710
|
connect() {
|
|
515
711
|
this.ws.connect();
|
|
516
712
|
}
|
|
@@ -562,9 +758,15 @@ var ChatClient = class extends EventEmitter {
|
|
|
562
758
|
}
|
|
563
759
|
);
|
|
564
760
|
if (result.data) {
|
|
565
|
-
this.cache.
|
|
761
|
+
const reconciled = this.cache.reconcileOptimisticMessage(conversationId, result.data);
|
|
762
|
+
this.cache.upsertMessage(conversationId, reconciled);
|
|
566
763
|
} else if (result.error?.status === 0) {
|
|
567
|
-
this.offlineQueue.enqueue(
|
|
764
|
+
this.offlineQueue.enqueue(
|
|
765
|
+
conversationId,
|
|
766
|
+
options.content,
|
|
767
|
+
options.message_type ?? "text",
|
|
768
|
+
options.attachments
|
|
769
|
+
);
|
|
568
770
|
}
|
|
569
771
|
return result;
|
|
570
772
|
}
|
|
@@ -593,9 +795,60 @@ var ChatClient = class extends EventEmitter {
|
|
|
593
795
|
async deleteMessage(messageId) {
|
|
594
796
|
return this.http.del(`/v1/chat/messages/${messageId}`);
|
|
595
797
|
}
|
|
798
|
+
async uploadAttachment(file, onProgress, signal) {
|
|
799
|
+
const filename = typeof File !== "undefined" && file instanceof File ? file.name : "attachment";
|
|
800
|
+
const contentType = file.type || "application/octet-stream";
|
|
801
|
+
const initResult = await this.http.post("/v1/storage/signed-url/upload", {
|
|
802
|
+
filename,
|
|
803
|
+
content_type: contentType,
|
|
804
|
+
size_bytes: file.size,
|
|
805
|
+
is_public: false,
|
|
806
|
+
metadata: {
|
|
807
|
+
source: "chat_sdk"
|
|
808
|
+
}
|
|
809
|
+
});
|
|
810
|
+
if (initResult.error || !initResult.data) {
|
|
811
|
+
return { data: null, error: initResult.error };
|
|
812
|
+
}
|
|
813
|
+
const uploadResult = await uploadToPresignedUrl(
|
|
814
|
+
initResult.data.upload_url,
|
|
815
|
+
file,
|
|
816
|
+
onProgress,
|
|
817
|
+
signal
|
|
818
|
+
);
|
|
819
|
+
if (uploadResult.error) {
|
|
820
|
+
return { data: null, error: uploadResult.error };
|
|
821
|
+
}
|
|
822
|
+
const completeResult = await this.http.post("/v1/storage/signed-url/complete", {
|
|
823
|
+
file_id: initResult.data.file_id,
|
|
824
|
+
completion_token: initResult.data.completion_token
|
|
825
|
+
});
|
|
826
|
+
if (completeResult.error || !completeResult.data) {
|
|
827
|
+
return { data: null, error: completeResult.error };
|
|
828
|
+
}
|
|
829
|
+
return {
|
|
830
|
+
data: {
|
|
831
|
+
file_id: completeResult.data.file_id,
|
|
832
|
+
file_name: completeResult.data.filename,
|
|
833
|
+
file_size: completeResult.data.size_bytes,
|
|
834
|
+
mime_type: completeResult.data.content_type,
|
|
835
|
+
presigned_url: completeResult.data.url
|
|
836
|
+
},
|
|
837
|
+
error: null
|
|
838
|
+
};
|
|
839
|
+
}
|
|
840
|
+
async refreshAttachmentUrl(messageId, fileId) {
|
|
841
|
+
return this.http.get(
|
|
842
|
+
`/v1/chat/messages/${messageId}/attachment/${fileId}/url`
|
|
843
|
+
);
|
|
844
|
+
}
|
|
596
845
|
getCachedMessages(conversationId) {
|
|
597
846
|
return this.cache.getMessages(conversationId);
|
|
598
847
|
}
|
|
848
|
+
stageOptimisticMessage(conversationId, message) {
|
|
849
|
+
this.cache.upsertMessage(conversationId, message);
|
|
850
|
+
return message;
|
|
851
|
+
}
|
|
599
852
|
// ============ Reactions ============
|
|
600
853
|
async addReaction(messageId, emoji) {
|
|
601
854
|
return this.http.post(`/v1/chat/messages/${messageId}/reactions`, { emoji });
|
|
@@ -603,6 +856,20 @@ var ChatClient = class extends EventEmitter {
|
|
|
603
856
|
async removeReaction(messageId, emoji) {
|
|
604
857
|
return this.http.del(`/v1/chat/messages/${messageId}/reactions/${encodeURIComponent(emoji)}`);
|
|
605
858
|
}
|
|
859
|
+
async reportMessage(messageId, reason, description) {
|
|
860
|
+
return this.http.post(`/v1/chat/messages/${messageId}/report`, {
|
|
861
|
+
reason,
|
|
862
|
+
description
|
|
863
|
+
});
|
|
864
|
+
}
|
|
865
|
+
async muteConversation(conversationId, mutedUntil) {
|
|
866
|
+
return this.http.post(`/v1/chat/conversations/${conversationId}/mute`, {
|
|
867
|
+
muted_until: mutedUntil
|
|
868
|
+
});
|
|
869
|
+
}
|
|
870
|
+
async unmuteConversation(conversationId) {
|
|
871
|
+
return this.http.del(`/v1/chat/conversations/${conversationId}/mute`);
|
|
872
|
+
}
|
|
606
873
|
// ============ Unread Count ============
|
|
607
874
|
async getUnreadTotal() {
|
|
608
875
|
return this.http.get("/v1/chat/conversations/unread-total");
|
|
@@ -778,6 +1045,91 @@ var ChatClient = class extends EventEmitter {
|
|
|
778
1045
|
}
|
|
779
1046
|
}
|
|
780
1047
|
}
|
|
1048
|
+
normalizeMessage(payload) {
|
|
1049
|
+
return {
|
|
1050
|
+
id: payload.id ?? payload.message_id ?? "",
|
|
1051
|
+
content: payload.content ?? "",
|
|
1052
|
+
message_type: payload.message_type ?? "text",
|
|
1053
|
+
sender_id: payload.sender_id ?? payload.sender_user_id ?? "",
|
|
1054
|
+
sender_type: payload.sender_type,
|
|
1055
|
+
sender_agent_model: payload.sender_agent_model,
|
|
1056
|
+
attachments: payload.attachments,
|
|
1057
|
+
reactions: payload.reactions,
|
|
1058
|
+
is_edited: Boolean(payload.is_edited ?? false),
|
|
1059
|
+
created_at: payload.created_at ?? payload.updated_at ?? payload.timestamp ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
1060
|
+
};
|
|
1061
|
+
}
|
|
1062
|
+
buildEditedMessage(conversationId, update) {
|
|
1063
|
+
const messageId = update.message_id ?? update.id;
|
|
1064
|
+
if (!messageId) {
|
|
1065
|
+
return null;
|
|
1066
|
+
}
|
|
1067
|
+
const existing = this.cache.getMessage(conversationId, messageId);
|
|
1068
|
+
return {
|
|
1069
|
+
id: existing?.id ?? messageId,
|
|
1070
|
+
content: update.content ?? update.new_content ?? existing?.content ?? "",
|
|
1071
|
+
message_type: existing?.message_type ?? "text",
|
|
1072
|
+
sender_id: existing?.sender_id ?? "",
|
|
1073
|
+
sender_type: existing?.sender_type,
|
|
1074
|
+
sender_agent_model: existing?.sender_agent_model,
|
|
1075
|
+
attachments: existing?.attachments,
|
|
1076
|
+
reactions: existing?.reactions,
|
|
1077
|
+
is_edited: true,
|
|
1078
|
+
created_at: existing?.created_at ?? update.updated_at ?? update.timestamp ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
1079
|
+
};
|
|
1080
|
+
}
|
|
1081
|
+
applyReactionEvent(conversationId, reactionEvent) {
|
|
1082
|
+
const reaction = {
|
|
1083
|
+
id: `${reactionEvent.message_id}:${reactionEvent.user_id}:${reactionEvent.emoji}`,
|
|
1084
|
+
message_id: reactionEvent.message_id,
|
|
1085
|
+
user_id: reactionEvent.user_id,
|
|
1086
|
+
emoji: reactionEvent.emoji,
|
|
1087
|
+
action: reactionEvent.action,
|
|
1088
|
+
timestamp: reactionEvent.timestamp
|
|
1089
|
+
};
|
|
1090
|
+
const existingMessage = this.cache.getMessage(conversationId, reactionEvent.message_id);
|
|
1091
|
+
if (!existingMessage) {
|
|
1092
|
+
return reaction;
|
|
1093
|
+
}
|
|
1094
|
+
const nextReactions = [...existingMessage.reactions ?? []];
|
|
1095
|
+
const reactionIndex = nextReactions.findIndex((entry) => entry.emoji === reactionEvent.emoji);
|
|
1096
|
+
if (reactionEvent.action === "added") {
|
|
1097
|
+
if (reactionIndex >= 0) {
|
|
1098
|
+
const current = nextReactions[reactionIndex];
|
|
1099
|
+
if (!current.user_ids.includes(reactionEvent.user_id)) {
|
|
1100
|
+
const user_ids = [...current.user_ids, reactionEvent.user_id];
|
|
1101
|
+
nextReactions[reactionIndex] = {
|
|
1102
|
+
...current,
|
|
1103
|
+
user_ids,
|
|
1104
|
+
count: user_ids.length
|
|
1105
|
+
};
|
|
1106
|
+
}
|
|
1107
|
+
} else {
|
|
1108
|
+
nextReactions.push({
|
|
1109
|
+
emoji: reactionEvent.emoji,
|
|
1110
|
+
count: 1,
|
|
1111
|
+
user_ids: [reactionEvent.user_id]
|
|
1112
|
+
});
|
|
1113
|
+
}
|
|
1114
|
+
} else if (reactionIndex >= 0) {
|
|
1115
|
+
const current = nextReactions[reactionIndex];
|
|
1116
|
+
const user_ids = current.user_ids.filter((userId) => userId !== reactionEvent.user_id);
|
|
1117
|
+
if (user_ids.length === 0) {
|
|
1118
|
+
nextReactions.splice(reactionIndex, 1);
|
|
1119
|
+
} else {
|
|
1120
|
+
nextReactions[reactionIndex] = {
|
|
1121
|
+
...current,
|
|
1122
|
+
user_ids,
|
|
1123
|
+
count: user_ids.length
|
|
1124
|
+
};
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1127
|
+
this.cache.updateMessage(conversationId, {
|
|
1128
|
+
...existingMessage,
|
|
1129
|
+
reactions: nextReactions
|
|
1130
|
+
});
|
|
1131
|
+
return reaction;
|
|
1132
|
+
}
|
|
781
1133
|
handleConversationMessage(channel, data) {
|
|
782
1134
|
const conversationId = channel.replace(/^conversation:(?:lr:|bc:|support:)?/, "");
|
|
783
1135
|
const raw = data;
|
|
@@ -786,15 +1138,32 @@ var ChatClient = class extends EventEmitter {
|
|
|
786
1138
|
const payload = raw.data ?? raw;
|
|
787
1139
|
switch (event) {
|
|
788
1140
|
case "new_message": {
|
|
789
|
-
const message = payload;
|
|
790
|
-
this.cache.
|
|
791
|
-
this.
|
|
1141
|
+
const message = this.normalizeMessage(payload);
|
|
1142
|
+
const reconciled = this.cache.reconcileOptimisticMessage(conversationId, message);
|
|
1143
|
+
this.cache.upsertMessage(conversationId, reconciled);
|
|
1144
|
+
this.emit("message", { message: reconciled, conversationId });
|
|
792
1145
|
break;
|
|
793
1146
|
}
|
|
794
1147
|
case "message_edited": {
|
|
795
|
-
const
|
|
796
|
-
this.
|
|
797
|
-
|
|
1148
|
+
const update = payload;
|
|
1149
|
+
const message = this.buildEditedMessage(conversationId, update);
|
|
1150
|
+
if (message) {
|
|
1151
|
+
this.cache.upsertMessage(conversationId, message);
|
|
1152
|
+
this.emit("message:updated", { message, conversationId, update });
|
|
1153
|
+
}
|
|
1154
|
+
break;
|
|
1155
|
+
}
|
|
1156
|
+
case "reaction": {
|
|
1157
|
+
const reactionEvent = payload;
|
|
1158
|
+
if (!reactionEvent.message_id || !reactionEvent.user_id || !reactionEvent.emoji) {
|
|
1159
|
+
break;
|
|
1160
|
+
}
|
|
1161
|
+
const reaction = this.applyReactionEvent(conversationId, reactionEvent);
|
|
1162
|
+
this.emit("reaction", {
|
|
1163
|
+
reaction,
|
|
1164
|
+
conversationId,
|
|
1165
|
+
action: reactionEvent.action
|
|
1166
|
+
});
|
|
798
1167
|
break;
|
|
799
1168
|
}
|
|
800
1169
|
case "message_deleted": {
|
|
@@ -855,10 +1224,11 @@ var ChatClient = class extends EventEmitter {
|
|
|
855
1224
|
for (const item of queued) {
|
|
856
1225
|
await this.sendMessage(item.conversationId, {
|
|
857
1226
|
content: item.content,
|
|
858
|
-
message_type: item.message_type
|
|
1227
|
+
message_type: item.message_type,
|
|
1228
|
+
attachments: item.attachments
|
|
859
1229
|
});
|
|
860
1230
|
}
|
|
861
1231
|
}
|
|
862
1232
|
};
|
|
863
1233
|
|
|
864
|
-
export { ChatClient };
|
|
1234
|
+
export { ChatClient, EventEmitter };
|