Untitled
unknown
plain_text
15 days ago
13 kB
7
Indexable
Never
"use node" import { TemplateConfig, TwilioService, TwilioSubChannel, vTwilioSubChannel, } from "../../../omni/models/types" import { SubChannel, TLCType } from "../../../tlc/types" import { TwilioAttachment } from "../../../twilio/schema" import { internal } from "../../../_generated/api" import { Doc, Id } from "../../../_generated/dataModel" import { ActionCtx, internalAction, internalQuery, MutationCtx, QueryCtx, } from "../../../_generated/server" import { ConversationDataTwilio } from "../../conversationMap/types" import { ChannelNodeTwilio } from "../channelNodeChannels/twilio" import { ChannelNodeService } from "../../channelNode/public" import { ContactNodeService } from "../../contactNode/public" import { TwilioMessageType } from "../../../twilio/types" import moment from "moment" import { v4 as uuidv4 } from "uuid" import { v } from "convex/values" import { ChannelNode } from "../../channelNode/schema" import { ConversationMapService } from "../../conversationMap/public" import { TwilioClient } from "../../../omni/services/channels/twilioClient" import { decrypt } from "../../../actions/utils/crypto" import { createTwilioService } from "../../../omni/services/twilioServiceFactory" // ┌────────────────┐ // │ TLC │ // │ ┌───────────┤ // │ │ SubChannel│ // │ │ │ // └────┴───────────┘ // This class is used for complex UI interactions with twilio // the twilio service is wrapped by the contact integration here // thus we use contactNodeService and channelNodeService export class TwilioActionsService { // Sending from channelNode static async sendMessageByConversationSid( ctx: ActionCtx, twilioService: TwilioService, channelNodeId: Id<"channelNode">, conversationSid: string, messageString: string, attachments?: TwilioAttachment[] ) { await ChannelNodeTwilio.sendMessage({ ctx, twilioService, channelNodeId, conversationSid, messageString, attachments, }) } static async sendTemplateByConversationSid( ctx: ActionCtx, twilioService: TwilioService, channelNodeId: Id<"channelNode">, conversationSid: string, templateConfig: TemplateConfig ) { await ChannelNodeTwilio.sendTemplate({ ctx, twilioService, channelNodeId, conversationSid, templateConfig, }) } static async sendMessageByChannelNodeId({ ctx, workspaceId, twilioService, channelNodeId, provider, recipient, messageString, attachments, }: { ctx: ActionCtx workspaceId: Id<"workspaces"> twilioService: TwilioService channelNodeId: Id<"channelNode"> provider: string recipient: string messageString: string attachments?: TwilioAttachment[] }) { const finalConversationSid = await this.getOrCreateConversationSid( ctx, workspaceId, twilioService, channelNodeId, provider, recipient ) // We should probably schedule this out await this.sendMessageByConversationSid( ctx, twilioService, channelNodeId, finalConversationSid, messageString, attachments ) } static async sendTemplateByChannelNodeId( ctx: ActionCtx, workspaceId: Id<"workspaces">, twilioService: TwilioService, channelNodeId: Id<"channelNode">, provider: string, recipient: string, templateConfig: TemplateConfig ) { const finalConversationSid = await this.getOrCreateConversationSid( ctx, workspaceId, twilioService, channelNodeId, provider, recipient ) // We should probably schedule this out await this.sendTemplateByConversationSid( ctx, twilioService, channelNodeId, finalConversationSid, templateConfig ) } private static async getOrCreateConversationSid( ctx: ActionCtx, workspaceId: Id<"workspaces">, twilioService: TwilioService, channelNodeId: Id<"channelNode">, provider: string, recipient: string ): Promise<string> { const conversationMap = await ctx.runQuery( internal.contacts.conversationMap.public .getByChannelNodeIdAndProvider, { workspaceId, channelNodeId, provider } ) if (conversationMap) { return ( conversationMap.conversationData as ConversationDataTwilio ).conversationId } else { const conversation = await ChannelNodeTwilio.createConversation({ ctx, twilioService, workspaceId, recipients: [recipient], channelNodeId, }) return conversation.conversationSid } } // Receiving messages ----------------------------------------------------- static async receiveKnownMessage( ctx: MutationCtx, workspaceId: Id<"workspaces">, type: TwilioSubChannel, conversationSid: string, payload: TwilioMessageType ) { const existingConversationMap = await ConversationMapService.getByTypeAndConversationId( ctx, workspaceId, type, conversationSid ) if (!existingConversationMap) { throw new Error( `Conversation map not found;; type: ${type}, conversationSid: ${conversationSid}` ) } const existingChannelNode = await ctx.db.get( existingConversationMap.channelNodeId ) if (!existingChannelNode) { throw new Error( `Channel node not found;; channelNodeId: ${existingConversationMap.channelNodeId}` ) } await ChannelNodeTwilio.addAndPropagateUMData({ ctx, channelNode: existingChannelNode, payload, }) } static async receivedUnknownMessage( ctx: MutationCtx, workspaceId: Id<"workspaces">, type: TwilioSubChannel, from: string, recipients: string[], // This is a string of recipients if it is a group provider: string, body: string, dateCreated: string ) { const combinedRecipientsString = recipients.join(",") let existingChannelNode = await ChannelNodeService.getByTypeAndValue( ctx, workspaceId, type, combinedRecipientsString ) if (existingChannelNode) { const existingConversationMap = await ConversationMapService.getByChannelNodeIdAndProvider( ctx, workspaceId, existingChannelNode._id, provider ) if (existingConversationMap) return } else { const contactId = await ContactNodeService.createUnmappedContact( ctx, workspaceId ) const channelNodeId = await ChannelNodeService.create( ctx, workspaceId, contactId, { type, value: combinedRecipientsString, } ) // This always exists existingChannelNode = (await ctx.db.get(channelNodeId))! } await ctx.scheduler.runAfter( 0, internal.contacts.services.channels.twilio .createConversationAddMessage, { workspaceId, recipients, messageString: body, channelNode: existingChannelNode, from, dateCreated, } ) // await this.ctx.scheduler.runAfter( // 0, // internal.actions.ai.tools.summarizer // .runSummarizationAndSentimentFlow, // { // workspaceId: this.workspaceId, // conversationId: conversationSid, // subChannel: type, // } // ) } } export const createConversationAddMessage = internalAction({ args: { workspaceId: v.id("workspaces"), recipients: v.array(v.string()), messageString: v.string(), channelNode: v.object(ChannelNode.withSystemFields), from: v.string(), dateCreated: v.string(), }, handler: async ( ctx, { workspaceId, recipients, messageString, channelNode, from, dateCreated, } ) => { const twilioService = {} as TwilioService const { conversationSid, participantBindings } = await ChannelNodeTwilio.createConversation({ ctx, twilioService, workspaceId, recipients, channelNodeId: channelNode._id, }) const messageSender = participantBindings.find( (participantBinding) => { return participantBinding.address === from } ) if (!messageSender) { throw new Error( `ParticipantBinding not found;; participantBindings: ${participantBindings}` ) } await ctx.runMutation( internal.contacts.services.channelNodeChannels.twilio .addAndPropagateUMData, { channelNode, payload: { workspaceId, messageSid: uuidv4(), participantSid: messageSender.participantSid, conversationSid, body: messageString, date: moment(dateCreated).utc().toISOString(), isIncoming: true, }, } ) }, }) // Refactor this code and SMS/WA channels code to become static export async function getTwilioService( ctx: ActionCtx, workspaceId: Id<"workspaces">, channelType: TwilioSubChannel ) { const twilioSubaccountAndProviderPhone = await ctx.runQuery( internal.contacts.services.channels.twilioFactory .getTwilioSubaccountAndProviderPhone, { workspaceId, channelType, } ) const { providerPhone, subaccount } = twilioSubaccountAndProviderPhone const twilioClient = new TwilioClient( subaccount.accountSid, decrypt(subaccount.encryptedAuthToken) ) return createTwilioService({ ctx, workspaceId, channelType, twilioClient, providerPhone, }) } // export const sendMessageByConversationSidInternalAction = // internalAction({ // args: { // channelNodeId: v.id("channelNode"), // conversationSid: v.string(), // messageString: v.string(), // attachments: v.optional(v.array(vTwilioAttachment)), // }, // handler: async ( // ctx, // { // channelNodeId, // conversationSid, // messageString, // attachments, // } // ): Promise<void> => { // const twilioService = {} as TwilioService // // await TwilioActionsService.sendMessageByConversationSid( // ctx, // twilioService, // channelNodeId, // conversationSid, // messageString, // attachments // ) // }, // }) // // export const sendTemplateByConversationSidInternalAction = // internalAction({ // args: { // channelNodeId: v.id("channelNode"), // conversationSid: v.string(), // templateConfig: v.any(), // }, // handler: async ( // ctx, // { channelNodeId, conversationSid, templateConfig } // ): Promise<void> => { // const twilioService = {} as TwilioService // // await TwilioActionsService.sendTemplateByConversationSid( // ctx, // twilioService, // channelNodeId, // conversationSid, // templateConfig // ) // }, // })
Leave a Comment