Untitled
unknown
plain_text
a year ago
13 kB
12
Indexable
"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
// )
// },
// })
Editor is loading...
Leave a Comment