wexa-chat 0.2.9 → 0.3.0

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/index.js CHANGED
@@ -72,12 +72,17 @@ function createConversationModel(options, conn) {
72
72
  lastMessageSenderModel: String,
73
73
  lastLinkedInId: String,
74
74
  lastWhatsAppId: String,
75
+ lastEmailId: String,
75
76
  linkedinChatId: String,
76
77
  whatsappChatId: String,
78
+ emailChatId: String,
77
79
  metadata: Schema.Types.Mixed
78
80
  },
79
81
  { timestamps: true }
80
82
  );
83
+ ConversationSchema.index({ linkedinChatId: 1 }, { unique: false, sparse: true });
84
+ ConversationSchema.index({ whatsappChatId: 1 }, { unique: false, sparse: true });
85
+ ConversationSchema.index({ emailChatId: 1 }, { unique: false, sparse: true });
81
86
  const model = conn.model(name, ConversationSchema);
82
87
  modelRegistry.set(registryKey, model);
83
88
  return model;
@@ -140,6 +145,10 @@ function createMessageModel(options, conn) {
140
145
  type: String,
141
146
  trim: true
142
147
  },
148
+ externalMessageId: {
149
+ type: String,
150
+ index: true
151
+ },
143
152
  parentMessageId: {
144
153
  type: String,
145
154
  ref: "Message"
@@ -153,7 +162,8 @@ function createMessageModel(options, conn) {
153
162
  },
154
163
  deletedAt: {
155
164
  type: Date
156
- }
165
+ },
166
+ metadata: Schema.Types.Mixed
157
167
  },
158
168
  { timestamps: true }
159
169
  );
@@ -162,6 +172,21 @@ function createMessageModel(options, conn) {
162
172
  conversationId: 1,
163
173
  createdAt: -1
164
174
  });
175
+ MessageSchema.index(
176
+ {
177
+ organizationId: 1,
178
+ conversationId: 1,
179
+ externalMessageId: 1
180
+ },
181
+ {
182
+ unique: true,
183
+ name: "deduplication_index",
184
+ partialFilterExpression: {
185
+ externalMessageId: { $type: "string" }
186
+ // Only index when field is a string (not null/undefined)
187
+ }
188
+ }
189
+ );
165
190
  if (options.enableTextSearch) {
166
191
  MessageSchema.index(
167
192
  { text: "text" },
@@ -489,6 +514,9 @@ var paths = {
489
514
  },
490
515
  whatsapp: {
491
516
  startChat: (connectorId) => `/actions/whatsapp/start_chat/${connectorId}`
517
+ },
518
+ email: {
519
+ sendEmail: (connectorId) => `/actions/mail/send_email/${connectorId}`
492
520
  }
493
521
  // Add more services here as needed
494
522
  };
@@ -548,28 +576,40 @@ function createMessagesService(models, hooks = {}) {
548
576
  source,
549
577
  parentMessageId,
550
578
  rootThreadId,
551
- connectorIds
579
+ connectorIds,
580
+ metadata
552
581
  } = args;
553
582
  const success_source = ["core"];
554
583
  const failed_source = [];
555
584
  const tasks = [];
556
585
  let lastLinkedInId;
557
586
  let lastWhatsAppId;
587
+ let lastEmailId;
558
588
  let linkedinChatId;
559
589
  let whatsappChatId;
590
+ let emailChatId;
560
591
  if (Array.isArray(source) && source.includes("linkedin")) {
561
592
  const LinkedinConnector = connectorIds && connectorIds.linkedin;
593
+ const linkedinMeta = metadata == null ? void 0 : metadata.linkedin;
562
594
  lastLinkedInId = LinkedinConnector == null ? void 0 : LinkedinConnector.connectorId;
563
595
  if (LinkedinConnector) {
564
596
  tasks.push({
565
597
  name: "linkedin",
566
598
  run: async () => {
567
599
  const path = paths.linkedin.startChat(String(LinkedinConnector.connectorId));
600
+ const payload = {
601
+ linkedin_url: LinkedinConnector.contactId,
602
+ text
603
+ };
604
+ if (linkedinMeta == null ? void 0 : linkedinMeta.subject) {
605
+ payload.subject = linkedinMeta.subject;
606
+ }
607
+ if (linkedinMeta == null ? void 0 : linkedinMeta.isInMail) {
608
+ payload.linkedin_inmail = linkedinMeta.isInMail;
609
+ }
610
+ console.log("LinkedIn payload:", payload);
568
611
  try {
569
- const res = await getAxios().post(path, {
570
- linkedin_url: LinkedinConnector.contactId,
571
- text
572
- });
612
+ const res = await getAxios().post(path, payload);
573
613
  const data = res.data;
574
614
  console.log("linkedinChatId", data);
575
615
  linkedinChatId = data.chat_data.chat_id;
@@ -611,6 +651,83 @@ function createMessagesService(models, hooks = {}) {
611
651
  failed_source.push({ source: "whatsapp", message: "Missing connector configuration" });
612
652
  }
613
653
  }
654
+ if (Array.isArray(source) && source.includes("email")) {
655
+ const emailConnector = connectorIds && connectorIds.email;
656
+ const emailMeta = metadata == null ? void 0 : metadata.email;
657
+ lastEmailId = emailConnector == null ? void 0 : emailConnector.connectorId;
658
+ if (emailConnector) {
659
+ tasks.push({
660
+ name: "email",
661
+ run: async () => {
662
+ var _a;
663
+ const path = paths.email.sendEmail(String(emailConnector.connectorId));
664
+ const primaryRecipient = {
665
+ display_name: (emailMeta == null ? void 0 : emailMeta.recipientName) || "Recipient",
666
+ identifier: emailConnector.contactId
667
+ };
668
+ const toRecipients = [primaryRecipient];
669
+ if (emailMeta == null ? void 0 : emailMeta.additionalRecipients) {
670
+ toRecipients.push(...emailMeta.additionalRecipients);
671
+ }
672
+ const payload = {
673
+ // REQUIRED FIELDS
674
+ to: toRecipients,
675
+ body: text
676
+ };
677
+ if (emailMeta == null ? void 0 : emailMeta.subject) {
678
+ payload.subject = emailMeta.subject;
679
+ }
680
+ if ((emailMeta == null ? void 0 : emailMeta.cc) && emailMeta.cc.length > 0) {
681
+ payload.cc = emailMeta.cc;
682
+ }
683
+ if ((emailMeta == null ? void 0 : emailMeta.bcc) && emailMeta.bcc.length > 0) {
684
+ payload.bcc = emailMeta.bcc;
685
+ }
686
+ if ((emailMeta == null ? void 0 : emailMeta.attachments) && emailMeta.attachments.length > 0) {
687
+ payload.attachments = emailMeta.attachments;
688
+ }
689
+ if (emailMeta == null ? void 0 : emailMeta.replyTo) {
690
+ payload.reply_to = emailMeta.replyTo;
691
+ }
692
+ if (emailMeta == null ? void 0 : emailMeta.tracking) {
693
+ const trackingOpts = {};
694
+ if (emailMeta.tracking.opens !== void 0) {
695
+ trackingOpts.opens = emailMeta.tracking.opens;
696
+ }
697
+ if (emailMeta.tracking.links !== void 0) {
698
+ trackingOpts.links = emailMeta.tracking.links;
699
+ }
700
+ if (emailMeta.tracking.label) {
701
+ trackingOpts.label = emailMeta.tracking.label;
702
+ }
703
+ if (emailMeta.tracking.custom_domain) {
704
+ trackingOpts.custom_domain = emailMeta.tracking.custom_domain;
705
+ }
706
+ if (Object.keys(trackingOpts).length > 0) {
707
+ payload.tracking_options = trackingOpts;
708
+ }
709
+ }
710
+ console.log("Email payload:", payload);
711
+ try {
712
+ const res = await getAxios().post(path, payload);
713
+ console.log("Email response:", res.data);
714
+ const data = res.data;
715
+ if ((_a = data.mail_sent_data) == null ? void 0 : _a.provider_id) {
716
+ emailChatId = data.mail_sent_data.provider_id;
717
+ console.log("Email provider_id extracted:", emailChatId);
718
+ } else {
719
+ console.warn("No provider_id in email response:", data);
720
+ }
721
+ return { status: "fulfilled", value: data };
722
+ } catch (error) {
723
+ return { status: "rejected", reason: error };
724
+ }
725
+ }
726
+ });
727
+ } else {
728
+ failed_source.push({ source: "email", message: "Missing connector configuration" });
729
+ }
730
+ }
614
731
  if (tasks.length > 0) {
615
732
  const results = await Promise.allSettled(tasks.map((t) => t.run()));
616
733
  results.forEach((result, idx) => {
@@ -628,6 +745,16 @@ function createMessagesService(models, hooks = {}) {
628
745
  }
629
746
  });
630
747
  }
748
+ let enhancedMetadata = args.metadata ? { ...args.metadata } : void 0;
749
+ if (emailChatId && success_source.includes("email")) {
750
+ if (!enhancedMetadata) {
751
+ enhancedMetadata = { email: { providerId: emailChatId } };
752
+ } else if (!enhancedMetadata.email) {
753
+ enhancedMetadata.email = { providerId: emailChatId };
754
+ } else {
755
+ enhancedMetadata.email.providerId = emailChatId;
756
+ }
757
+ }
631
758
  const message = new Message({
632
759
  organizationId,
633
760
  conversationId,
@@ -637,7 +764,8 @@ function createMessagesService(models, hooks = {}) {
637
764
  kind,
638
765
  source: success_source,
639
766
  parentMessageId,
640
- rootThreadId: rootThreadId || parentMessageId
767
+ rootThreadId: rootThreadId || parentMessageId,
768
+ metadata: enhancedMetadata
641
769
  });
642
770
  await message.save();
643
771
  const conversationUpdate = {
@@ -648,8 +776,10 @@ function createMessagesService(models, hooks = {}) {
648
776
  };
649
777
  if (lastLinkedInId) conversationUpdate.lastLinkedInId = lastLinkedInId;
650
778
  if (lastWhatsAppId) conversationUpdate.lastWhatsAppId = lastWhatsAppId;
779
+ if (lastEmailId) conversationUpdate.lastEmailId = lastEmailId;
651
780
  if (linkedinChatId) conversationUpdate.linkedinChatId = linkedinChatId;
652
781
  if (whatsappChatId) conversationUpdate.whatsappChatId = whatsappChatId;
782
+ if (emailChatId) conversationUpdate.emailChatId = emailChatId;
653
783
  await Conversation.findByIdAndUpdate(conversationId, {
654
784
  $set: conversationUpdate
655
785
  });
@@ -659,6 +789,197 @@ function createMessagesService(models, hooks = {}) {
659
789
  message.failed_source = failed_source;
660
790
  return message;
661
791
  },
792
+ /**
793
+ * Receive a new message by whatsappChatId
794
+ */
795
+ async receiveWhatsappMessage(args) {
796
+ const { whatsappChatId, message, messageId, senderName } = args;
797
+ const conversation = await Conversation.findOne({ whatsappChatId }).lean();
798
+ if (!conversation) {
799
+ throw new Error("Conversation not found for given whatsappChatId");
800
+ }
801
+ if (messageId) {
802
+ const existingMessage = await Message.findOne({
803
+ organizationId: conversation.organizationId,
804
+ conversationId: String(conversation._id),
805
+ externalMessageId: messageId
806
+ }).lean();
807
+ if (existingMessage) {
808
+ console.log(`Duplicate WhatsApp message detected: ${messageId}`);
809
+ return existingMessage;
810
+ }
811
+ }
812
+ const applicationParticipant = conversation.participants.find(
813
+ (p) => p.entityModel === "Application"
814
+ );
815
+ if (!applicationParticipant) {
816
+ throw new Error("Application participant not found in conversation");
817
+ }
818
+ const orgId = conversation.organizationId;
819
+ const senderModel = "Application";
820
+ const senderId = applicationParticipant.entityId;
821
+ const msgDoc = new Message({
822
+ organizationId: orgId,
823
+ conversationId: String(conversation._id),
824
+ senderModel,
825
+ senderId,
826
+ text: message,
827
+ kind: "text",
828
+ source: ["whatsapp"],
829
+ externalMessageId: messageId,
830
+ parentMessageId: void 0,
831
+ rootThreadId: void 0,
832
+ metadata: senderName ? {
833
+ whatsapp: {
834
+ senderName
835
+ }
836
+ } : void 0
837
+ });
838
+ await msgDoc.save();
839
+ await Conversation.findByIdAndUpdate(conversation._id, {
840
+ $set: {
841
+ lastMessageAt: msgDoc.createdAt,
842
+ lastMessagePreview: message.substring(0, 100),
843
+ lastMessageSenderId: senderId,
844
+ lastMessageSenderModel: senderModel
845
+ }
846
+ });
847
+ if (onMessageCreated) onMessageCreated(msgDoc);
848
+ return msgDoc;
849
+ },
850
+ /**
851
+ * Receive a new message by linkedinChatId
852
+ */
853
+ async receiveLinkedinMessage(args) {
854
+ const { linkedinChatId, message, messageId, senderName, senderProfileUrl } = args;
855
+ const conversation = await Conversation.findOne({ linkedinChatId }).lean();
856
+ if (!conversation) {
857
+ throw new Error("Conversation not found for given linkedinChatId");
858
+ }
859
+ if (messageId) {
860
+ const existingMessage = await Message.findOne({
861
+ organizationId: conversation.organizationId,
862
+ conversationId: String(conversation._id),
863
+ externalMessageId: messageId
864
+ }).lean();
865
+ if (existingMessage) {
866
+ console.log(`Duplicate LinkedIn message detected: ${messageId}`);
867
+ return existingMessage;
868
+ }
869
+ }
870
+ const applicationParticipant = conversation.participants.find(
871
+ (p) => p.entityModel === "Application"
872
+ );
873
+ if (!applicationParticipant) {
874
+ throw new Error("Application participant not found in conversation");
875
+ }
876
+ const orgId = conversation.organizationId;
877
+ const senderModel = "Application";
878
+ const senderId = applicationParticipant.entityId;
879
+ const msgDoc = new Message({
880
+ organizationId: orgId,
881
+ conversationId: String(conversation._id),
882
+ senderModel,
883
+ senderId,
884
+ text: message,
885
+ kind: "text",
886
+ source: ["linkedin"],
887
+ externalMessageId: messageId,
888
+ parentMessageId: void 0,
889
+ rootThreadId: void 0,
890
+ metadata: senderName || senderProfileUrl ? {
891
+ linkedin: {
892
+ senderName,
893
+ senderProfileUrl
894
+ }
895
+ } : void 0
896
+ });
897
+ await msgDoc.save();
898
+ await Conversation.findByIdAndUpdate(conversation._id, {
899
+ $set: {
900
+ lastMessageAt: msgDoc.createdAt,
901
+ lastMessagePreview: message.substring(0, 100),
902
+ lastMessageSenderId: senderId,
903
+ lastMessageSenderModel: senderModel
904
+ }
905
+ });
906
+ if (onMessageCreated) onMessageCreated(msgDoc);
907
+ return msgDoc;
908
+ },
909
+ /**
910
+ * Receive a new message by emailChatId with full email metadata
911
+ */
912
+ async receiveEmailMessage(args) {
913
+ const {
914
+ emailChatId,
915
+ message,
916
+ subject,
917
+ senderEmail,
918
+ senderName,
919
+ providerId,
920
+ messageId,
921
+ attachments
922
+ } = args;
923
+ const conversation = await Conversation.findOne({ emailChatId }).lean();
924
+ if (!conversation) {
925
+ throw new Error("Conversation not found for given emailChatId");
926
+ }
927
+ if (messageId) {
928
+ const existingMessage = await Message.findOne({
929
+ organizationId: conversation.organizationId,
930
+ conversationId: String(conversation._id),
931
+ externalMessageId: messageId
932
+ }).lean();
933
+ if (existingMessage) {
934
+ console.log(`Duplicate email message detected: ${messageId}`);
935
+ return existingMessage;
936
+ }
937
+ }
938
+ const applicationParticipant = conversation.participants.find(
939
+ (p) => p.entityModel === "Application"
940
+ );
941
+ if (!applicationParticipant) {
942
+ throw new Error("Application participant not found in conversation");
943
+ }
944
+ const orgId = conversation.organizationId;
945
+ const senderModel = "Application";
946
+ const senderId = applicationParticipant.entityId;
947
+ const msgDoc = new Message({
948
+ organizationId: orgId,
949
+ conversationId: String(conversation._id),
950
+ senderModel,
951
+ senderId,
952
+ text: message,
953
+ kind: "text",
954
+ source: ["email"],
955
+ externalMessageId: messageId,
956
+ parentMessageId: void 0,
957
+ rootThreadId: void 0,
958
+ metadata: {
959
+ email: {
960
+ subject,
961
+ from: senderEmail ? {
962
+ display_name: senderName || senderEmail,
963
+ identifier: senderEmail
964
+ } : void 0,
965
+ providerId,
966
+ messageId,
967
+ attachments
968
+ }
969
+ }
970
+ });
971
+ await msgDoc.save();
972
+ await Conversation.findByIdAndUpdate(conversation._id, {
973
+ $set: {
974
+ lastMessageAt: msgDoc.createdAt,
975
+ lastMessagePreview: message.substring(0, 100),
976
+ lastMessageSenderId: senderId,
977
+ lastMessageSenderModel: senderModel
978
+ }
979
+ });
980
+ if (onMessageCreated) onMessageCreated(msgDoc);
981
+ return msgDoc;
982
+ },
662
983
  /**
663
984
  * List messages for a conversation with pagination
664
985
  * @param args Message listing arguments