Sendi is a Webex to Twilio SMS gateway written in TypeScript that runs directly on Node 22.18+ via native type-stripping (no bundler/transpiler). Each Webex space represents one external SMS contact; inbound MMS is proxied both ways, with delivery-status cards for outbound sends. Highlights: - Express 5 HTTP surface for Twilio /sms and /callback (signature-validated) - webex-node-bot-framework in websocket transport mode for bot control plane - Prisma 6 + SQLite via better-sqlite3 driver adapter - Zod-validated env with fail-fast startup and pino secret redaction - Pino logging: pretty in dev, structured JSON in prod - 13 vitest unit tests (phone normalization, Twilio signature validation, card action dispatch) - Native --require preload patches Node 22's read-only globalThis.navigator so @webex/internal-media-core (transitive) can load Co-authored-by: Cursor <cursoragent@cursor.com>
51 lines
1.7 KiB
TypeScript
51 lines
1.7 KiB
TypeScript
import { describe, expect, it, vi } from 'vitest';
|
|
import express from 'express';
|
|
import bodyParser from 'body-parser';
|
|
import twilio from 'twilio';
|
|
import request from 'supertest';
|
|
|
|
import { twilioSignature } from '../../src/middleware/twilioSignature.ts';
|
|
|
|
const FAKE_AUTH_TOKEN = process.env.TWILIO_AUTH_TOKEN!;
|
|
const FAKE_PUBLIC = process.env.WEBEX_PUBLIC_URL!.replace(/\/$/, '');
|
|
|
|
function makeApp() {
|
|
const app = express();
|
|
app.use(bodyParser.urlencoded({ extended: true }));
|
|
app.post('/sms', twilioSignature, (_req, res) => {
|
|
res.status(200).send('ok');
|
|
});
|
|
return app;
|
|
}
|
|
|
|
describe('twilioSignature middleware', () => {
|
|
it('rejects requests with no X-Twilio-Signature', async () => {
|
|
const app = makeApp();
|
|
const res = await request(app).post('/sms').send({ From: '+15551234567', Body: 'hi' });
|
|
expect(res.status).toBe(403);
|
|
});
|
|
|
|
it('rejects requests with a bad signature', async () => {
|
|
const app = makeApp();
|
|
const res = await request(app)
|
|
.post('/sms')
|
|
.set('X-Twilio-Signature', 'definitely-not-real')
|
|
.send({ From: '+15551234567', Body: 'hi' });
|
|
expect(res.status).toBe(403);
|
|
});
|
|
|
|
it('accepts a properly signed request', async () => {
|
|
const app = makeApp();
|
|
const params = { From: '+15551234567', To: '+15557654321', Body: 'hi' };
|
|
const url = `${FAKE_PUBLIC}/sms`;
|
|
const signature = twilio.getExpectedTwilioSignature(FAKE_AUTH_TOKEN, url, params);
|
|
|
|
const res = await request(app)
|
|
.post('/sms')
|
|
.set('X-Twilio-Signature', signature)
|
|
.type('form')
|
|
.send(params);
|
|
expect(res.status).toBe(200);
|
|
expect(res.text).toBe('ok');
|
|
});
|
|
});
|