@convex-dev/twilio
Version:
Convex component for sending/receiving SMS messages with Twilio.
233 lines • 9.63 kB
JavaScript
// This file is for thick component clients and helpers that run
import { createFunctionHandle, httpActionGeneric, HttpRouter, } from "convex/server";
import schema from "../component/schema.js";
export const messageValidator = schema.tables.messages.validator;
export class Twilio {
componentApi;
accountSid;
authToken;
httpPrefix;
defaultFrom;
incomingMessageCallback;
defaultOutgoingMessageCallback;
constructor(componentApi, options) {
this.componentApi = componentApi;
this.accountSid =
options?.TWILIO_ACCOUNT_SID ?? process.env.TWILIO_ACCOUNT_SID;
this.authToken =
options?.TWILIO_AUTH_TOKEN ?? process.env.TWILIO_AUTH_TOKEN;
if (!this.accountSid || !this.authToken) {
throw new Error("Missing Twilio credentials\n\n" +
"npx convex env set TWILIO_ACCOUNT_SID=ACxxxxx\n" +
"npx convex env set TWILIO_AUTH_TOKEN=xxxxx");
}
this.defaultFrom = options.defaultFrom;
this.httpPrefix = options?.httpPrefix ?? "/twilio";
this.incomingMessageCallback = options?.incomingMessageCallback;
this.defaultOutgoingMessageCallback =
options?.defaultOutgoingMessageCallback;
}
/**
* Registers the routes for handling Twilio message status and incoming messages.
*
* @param http - The HTTP router to register routes on.
*/
registerRoutes(http) {
http.route({
path: this.httpPrefix + "/message-status",
method: "POST",
handler: httpActionGeneric(async (ctx, request) => {
const requestValues = new URLSearchParams(await request.text());
const sid = requestValues.get("MessageSid");
const status = requestValues.get("MessageStatus");
if (sid && status) {
await ctx.runMutation(this.componentApi.messages.updateStatus, {
account_sid: this.accountSid,
sid: sid ?? "",
status: status ?? "",
});
}
else {
console.log(`Invalid request: ${requestValues}`);
}
return new Response(null, { status: 200 });
}),
});
http.route({
path: this.httpPrefix + "/incoming-message",
method: "POST",
handler: httpActionGeneric(async (ctx, request) => {
const requestValues = new URLSearchParams(await request.text());
console.log(requestValues);
await ctx.runAction(this.componentApi.messages.getFromTwilioBySidAndInsert, {
account_sid: this.accountSid,
auth_token: this.authToken,
sid: requestValues.get("SmsSid") ?? "",
incomingMessageCallback: this.incomingMessageCallback &&
(await createFunctionHandle(this.incomingMessageCallback)),
});
const emptyResponseTwiML = `
<?xml version="1.0" encoding="UTF-8"?>
<Response></Response>`;
return new Response(emptyResponseTwiML, {
status: 200,
headers: {
"Content-Type": "application/xml",
},
});
}),
});
}
/**
* Sends a message using the Twilio API.
*
* @param ctx - A Convex context for running the action.
* @param args - The arguments for sending the message.
* @param args.to - The recipient's phone number e.g. +14151234567.
* @param args.body - The body of the message.
* @param args.callback - An optional callback function to be called after successfully sending.
* @param args.from - The sender's phone number. If not provided, the default from number is used.
* @throws {Error} If the from number is missing and no default from number is set.
* @returns A promise that resolves with the result of the message creation action.
*/
async sendMessage(ctx, args) {
const from = args.from ?? this.defaultFrom;
if (!from) {
throw new Error("Missing from number");
}
return ctx.runAction(this.componentApi.messages.create, {
from,
to: args.to,
body: args.body,
account_sid: this.accountSid,
auth_token: this.authToken,
status_callback: process.env.CONVEX_SITE_URL + this.httpPrefix + "/message-status",
callback: args.callback
? await createFunctionHandle(args.callback)
: this.defaultOutgoingMessageCallback &&
(await createFunctionHandle(this.defaultOutgoingMessageCallback)),
});
}
/**
* Registers an incoming SMS handler for a Twilio phone number.
*
* @param ctx - The Convex function context.
* @param args - The arguments for registering the SMS handler.
* @param args.sid - The SID of the phone number to update.
* @returns A promise that resolves with the result of the action.
*/
async registerIncomingSmsHandler(ctx, args) {
return ctx.runAction(this.componentApi.phone_numbers.updateSmsUrl, {
account_sid: this.accountSid,
auth_token: this.authToken,
sid: args.sid,
sms_url: process.env.CONVEX_SITE_URL + this.httpPrefix + "/incoming-message",
});
}
/**
* Lists messages sent or received using this component.
*
* @param ctx - The Convex function context.
* @param args - Optional arguments for listing messages.
* @param args.limit - The maximum number of messages to retrieve.
* @returns A promise that resolves with the list of messages.
*/
async list(ctx, args) {
return ctx.runQuery(this.componentApi.messages.list, {
...args,
account_sid: this.accountSid,
});
}
/**
* Lists messages received using this component.
*
* @param ctx - The Convex function context.
* @param args - Optional arguments for listing messages.
* @param args.limit - The maximum number of messages to retrieve.
* @returns A promise that resolves with the list of messages.
*/
async listIncoming(ctx, args) {
return ctx.runQuery(this.componentApi.messages.listIncoming, {
...args,
account_sid: this.accountSid,
});
}
/**
* Lists messages sent using this component.
*
* @param ctx - The Convex function context.
* @param args - Optional arguments for listing messages.
* @param args.limit - The maximum number of messages to retrieve.
* @returns A promise that resolves with the list of messages.
*/
async listOutgoing(ctx, args) {
return ctx.runQuery(this.componentApi.messages.listOutgoing, {
...args,
account_sid: this.accountSid,
});
}
/**
* Retrieves a message by its Twilio SID.
*
* @param ctx - The Convex function context.
* @param args - The arguments for retrieving the message.
* @param args.sid - The SID of the message to retrieve.
* @returns A promise that resolves with the message details.
*/
async getMessageBySid(ctx, args) {
return ctx.runQuery(this.componentApi.messages.getBySid, {
account_sid: this.accountSid,
sid: args.sid,
});
}
/**
* Retrieves messages sent to a specific phone number using the component.
*
* @param ctx - The Convex function context.
* @param args - The arguments for retrieving the messages.
* @param args.to - The recipient's phone number.
* @param args.limit - Optional. The maximum number of messages to retrieve.
* @returns A promise that resolves with the list of messages.
*/
async getMessagesTo(ctx, args) {
return ctx.runQuery(this.componentApi.messages.getTo, {
...args,
account_sid: this.accountSid,
});
}
/**
* Retrieves messages received from a specific phone number using the component.
*
* @param ctx - The Convex function context.
* @param args - The arguments for retrieving the messages.
* @param args.from - The sender's phone number.
* @param args.limit - Optional. The maximum number of messages to retrieve.
* @returns A promise that resolves with the list of messages.
*/
async getMessagesFrom(ctx, args) {
return ctx.runQuery(this.componentApi.messages.getFrom, {
...args,
account_sid: this.accountSid,
});
}
/**
* Retrieves messages sent to or received from a specific phone number using the component.
*
* @param ctx - The Convex function context.
* @param args - The arguments for retrieving the messages.
* @param args.counterparty - The recipient's or sender's phone number.
* @param args.limit - Optional. The maximum number of messages to retrieve.
* @returns A promise that resolves with the list of messages.
*/
async getMessagesByCounterparty(ctx, args) {
return ctx.runQuery(this.componentApi.messages.getByCounterparty, {
...args,
account_sid: this.accountSid,
});
}
}
export default Twilio;
if (typeof Convex === "undefined") {
throw new Error("this is Convex backend code, but it's running somewhere else!");
}
//# sourceMappingURL=index.js.map