UNPKG

@convex-dev/twilio

Version:

Convex component for sending/receiving SMS messages with Twilio.

203 lines (193 loc) 5.25 kB
import { v } from "convex/values"; import { action, internalAction, internalMutation, internalQuery, } from "./_generated/server.js"; import { internal } from "./_generated/api.js"; import { twilioRequest, attemptToParse } from "./utils.js"; import schema from "./schema.js"; export const create = action({ args: { account_sid: v.string(), auth_token: v.string(), number: v.string(), }, handler: async (ctx, args) => { const path = "IncomingPhoneNumbers.json"; const body = { PhoneNumber: args.number, }; const data = await twilioRequest( path, args.account_sid, args.auth_token, body, ); const id = await ctx.runMutation(internal.phone_numbers.insert, { phone_number: data, }); data._id = id; return data; }, }); export const insert = internalMutation({ args: { phone_number: v.any(), }, returns: v.id("phone_numbers"), handler: async (ctx, args) => { // It might have extra fields so we need to sanitize it before inserting into the table const result = attemptToParse( schema.tables.phone_numbers.validator, args.phone_number, ); if (result.kind === "error") { console.error("Failed to parse phone number data:", result.error); throw new Error("Invalid phone number data"); } return await ctx.db.insert("phone_numbers", result.data); }, }); export const patch = internalMutation({ args: { convexId: v.id("phone_numbers"), sms_url: v.string(), }, handler: async (ctx, args) => { return await ctx.db.patch(args.convexId, { sms_url: args.sms_url }); }, }); export const get = internalQuery({ args: { account_sid: v.string(), sid: v.string(), }, returns: v.union( v.object({ ...schema.tables.phone_numbers.validator.fields, _id: v.id("phone_numbers"), _creationTime: v.number(), }), v.null(), ), handler: async (ctx, args) => { return await ctx.db .query("phone_numbers") .withIndex("by_sid", (q) => q.eq("account_sid", args.account_sid).eq("sid", args.sid), ) .first(); }, }); export const queryByPhoneNumber = internalQuery({ args: { phone_number: v.string(), account_sid: v.string(), }, returns: v.union( v.object({ ...schema.tables.phone_numbers.validator.fields, _id: v.id("phone_numbers"), _creationTime: v.number(), }), v.null(), ), handler: async (ctx, args) => { return await ctx.db .query("phone_numbers") .withIndex("by_phone_number", (q) => q .eq("account_sid", args.account_sid) .eq("phone_number", args.phone_number), ) .first(); }, }); export const updateSmsUrl = action({ args: { account_sid: v.string(), auth_token: v.string(), sid: v.string(), sms_url: v.string(), }, handler: async (ctx, args) => { const path = `IncomingPhoneNumbers/${args.sid}.json`; const phone_number = await ctx.runQuery(internal.phone_numbers.get, { sid: args.sid, account_sid: args.account_sid, }); let convexId; if (!phone_number) { console.log("Phone number not found in table - fetching from Twilio"); const data = await twilioRequest( path, args.account_sid, args.auth_token, {}, "GET", ); console.log("Inserting into table"); convexId = await ctx.runMutation(internal.phone_numbers.insert, { phone_number: data, }); } else { convexId = phone_number._id; } const body = { SmsUrl: args.sms_url, }; await twilioRequest(path, args.account_sid, args.auth_token, body, "POST"); await ctx.runMutation(internal.phone_numbers.patch, { convexId, sms_url: args.sms_url, }); }, }); export const getByPhoneNumber = internalAction({ args: { account_sid: v.string(), auth_token: v.string(), phone_number: v.string(), }, returns: v.object({ ...schema.tables.phone_numbers.validator.fields, _id: v.id("phone_numbers"), _creationTime: v.number(), }), handler: async (ctx, args) => { const phone_number: any = await ctx.runQuery( internal.phone_numbers.queryByPhoneNumber, { phone_number: args.phone_number, account_sid: args.account_sid, }, ); if (!phone_number) { console.log( `Phone number ${args.phone_number} not found in table - fetching from Twilio`, ); const phone_number = encodeURIComponent(args.phone_number); const path = `IncomingPhoneNumbers.json?PhoneNumber=${phone_number}`; const data = await twilioRequest( path, args.account_sid, args.auth_token, {}, "GET", ); if (data.incoming_phone_numbers.length === 0) { throw new Error("Phone number not found"); } console.log("Inserting phone number into table"); const id = await ctx.runMutation(internal.phone_numbers.insert, { phone_number: data.incoming_phone_numbers[0], }); data.incoming_phone_numbers[0]._id = id; return data.incoming_phone_numbers[0]; } else { return phone_number; } }, });