@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.
Files changed (85) hide show
  1. package/dist/{ChatClient-COmdEJ11.d.ts → ChatClient-DQPHdUHX.d.cts} +21 -2
  2. package/dist/{ChatClient-BoZaTtyM.d.cts → ChatClient-DtUKF-4c.d.ts} +21 -2
  3. package/dist/chat.embed.global.js +1 -1
  4. package/dist/chat.umd.global.js +288 -12
  5. package/dist/{chunk-ZLMMNFZL.js → chunk-5O5YLRJL.js} +386 -16
  6. package/dist/chunk-GTMAK3IA.js +285 -0
  7. package/dist/chunk-TRCELAZQ.cjs +287 -0
  8. package/dist/{chunk-YDLRISR7.cjs → chunk-W2PWFS3E.cjs} +386 -15
  9. package/dist/constants.d.ts +9 -0
  10. package/dist/constants.d.ts.map +1 -0
  11. package/dist/core/ChatClient.d.ts +96 -0
  12. package/dist/core/ChatClient.d.ts.map +1 -0
  13. package/dist/core/EventEmitter.d.ts +11 -0
  14. package/dist/core/EventEmitter.d.ts.map +1 -0
  15. package/dist/core/MessageCache.d.ts +18 -0
  16. package/dist/core/MessageCache.d.ts.map +1 -0
  17. package/dist/core/OfflineQueue.d.ts +19 -0
  18. package/dist/core/OfflineQueue.d.ts.map +1 -0
  19. package/dist/element.cjs +542 -51
  20. package/dist/element.d.ts +2 -2
  21. package/dist/element.d.ts.map +1 -0
  22. package/dist/element.js +541 -50
  23. package/dist/embed/index.d.ts +2 -0
  24. package/dist/embed/index.d.ts.map +1 -0
  25. package/dist/factory.d.ts +8 -0
  26. package/dist/factory.d.ts.map +1 -0
  27. package/dist/iframe.d.cts +1 -1
  28. package/dist/iframe.d.ts +3 -5
  29. package/dist/iframe.d.ts.map +1 -0
  30. package/dist/index.cjs +34 -5
  31. package/dist/index.d.cts +93 -4
  32. package/dist/index.d.ts +8 -77
  33. package/dist/index.d.ts.map +1 -0
  34. package/dist/index.js +29 -4
  35. package/dist/react-components/ChatInput.d.ts +16 -0
  36. package/dist/react-components/ChatInput.d.ts.map +1 -0
  37. package/dist/react-components/ChatMessageItem.d.ts +13 -0
  38. package/dist/react-components/ChatMessageItem.d.ts.map +1 -0
  39. package/dist/react-components/ChatMessageList.d.ts +15 -0
  40. package/dist/react-components/ChatMessageList.d.ts.map +1 -0
  41. package/dist/react-components/ChatThread.d.ts +12 -0
  42. package/dist/react-components/ChatThread.d.ts.map +1 -0
  43. package/dist/react-components/ConversationList.d.ts +13 -0
  44. package/dist/react-components/ConversationList.d.ts.map +1 -0
  45. package/dist/react-components/EmojiPicker.d.ts +8 -0
  46. package/dist/react-components/EmojiPicker.d.ts.map +1 -0
  47. package/dist/react-components/index.d.ts +8 -0
  48. package/dist/react-components/index.d.ts.map +1 -0
  49. package/dist/react-components/theme.d.ts +19 -0
  50. package/dist/react-components/theme.d.ts.map +1 -0
  51. package/dist/react-components/utils.d.ts +4 -0
  52. package/dist/react-components/utils.d.ts.map +1 -0
  53. package/dist/react.cjs +1213 -53
  54. package/dist/react.d.cts +100 -4
  55. package/dist/react.d.ts +38 -15
  56. package/dist/react.d.ts.map +1 -0
  57. package/dist/react.js +1167 -18
  58. package/dist/shared/ChatController.d.ts +65 -0
  59. package/dist/shared/ChatController.d.ts.map +1 -0
  60. package/dist/shared/upload.d.ts +3 -0
  61. package/dist/shared/upload.d.ts.map +1 -0
  62. package/dist/support-widget.global.js +485 -157
  63. package/dist/support.d.ts +99 -0
  64. package/dist/support.d.ts.map +1 -0
  65. package/dist/transport/HttpTransport.d.ts +20 -0
  66. package/dist/transport/HttpTransport.d.ts.map +1 -0
  67. package/dist/transport/WebSocketTransport.d.ts +78 -0
  68. package/dist/transport/WebSocketTransport.d.ts.map +1 -0
  69. package/dist/{types-BmD7f1gV.d.cts → types-COPVrm3K.d.cts} +25 -1
  70. package/dist/{types-BmD7f1gV.d.ts → types-COPVrm3K.d.ts} +25 -1
  71. package/dist/types.d.ts +271 -0
  72. package/dist/types.d.ts.map +1 -0
  73. package/dist/umd.d.ts +6 -0
  74. package/dist/umd.d.ts.map +1 -0
  75. package/dist/version.d.ts +2 -0
  76. package/dist/version.d.ts.map +1 -0
  77. package/dist/widget/icons.d.ts +8 -0
  78. package/dist/widget/icons.d.ts.map +1 -0
  79. package/dist/widget/index.d.ts +2 -0
  80. package/dist/widget/index.d.ts.map +1 -0
  81. package/dist/widget/storage.d.ts +5 -0
  82. package/dist/widget/storage.d.ts.map +1 -0
  83. package/dist/widget/styles.d.ts +3 -0
  84. package/dist/widget/styles.d.ts.map +1 -0
  85. 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 delay = Math.min(
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
- }, delay);
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.addMessage(conversationId, result.data);
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(conversationId, options.content, options.message_type ?? "text");
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.addMessage(conversationId, message);
791
- this.emit("message", { message, conversationId });
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 message = payload;
796
- this.cache.updateMessage(conversationId, message);
797
- this.emit("message:updated", { message, conversationId });
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 };