Packaging
- Drop stale `main: dist/index.js` from package.json (no build workflow)
- Move `prisma` to dependencies and add `postinstall: prisma generate`
so a fresh `npm ci` on the server produces a runnable client
- Add `prisma:deploy` script for `prisma migrate deploy`
- Rename src/services/Twilioservice.ts -> TwilioService.ts (case fix;
invisible on macOS APFS, would crash on Linux)
Node version guards
- .nvmrc (22.23.1) so `nvm use` picks the right runtime
- .npmrc `engine-strict=true` so npm respects `engines` on install
- Runtime guard in scripts/navigator-patch.cjs that fails fast with an
actionable message on Node < 22.18 (native .ts stripping requirement)
Twilio error taxonomy (src/lib/twilioErrors.ts)
- `classifyTwilioError(err)` for send-time exceptions, mapping known
Twilio REST codes (20003/20429/20500/21211..21614) plus HTTP-status
and Node errno fallback to `{ kind: retryable|terminal, category }`
- `classifyDeliveryFailure(code)` for the 30xxx delivery-status family
- `sendViaTwilio` persists a `send_error[_retryable]` Message row on
failure and surfaces the classified message to the user
- `/callback` extracts ErrorCode and passes classified failure info to
`deliveryStatusCard`, which renders the code + retry-hint inline
Persistent PendingMessageStore
- New PrismaPendingMessageStore (upsert-based) becomes the default
singleton; InMemoryPendingMessageStore retained for tests
- `startPendingMessageSweeper()` runs hourly, drops entries >24h old,
unref()s its timer, and is disabled under NODE_ENV=test
- Wired into the shutdown handler in src/index.ts
Tests (39 total, up from 13)
- Integration: tests/integration/twilioRoutes.test.ts hits /sms and
/callback through the real createApp() with a stub bot, real Twilio
signatures, plus /healthz
- Unit: twilioErrors.test.ts (14), pendingMessageStore.test.ts (5)
- typecheck clean
Co-authored-by: Cursor <cursoragent@cursor.com>
57 lines
2.5 KiB
JavaScript
57 lines
2.5 KiB
JavaScript
// scripts/navigator-patch.cjs
|
|
//
|
|
// Workaround for Node 22 + @webex/internal-media-core.
|
|
//
|
|
// The bot framework transitively depends on `@webex/internal-media-core`, whose
|
|
// pre-compiled CJS bundle does `commonjsGlobal.navigator = { ... }` at import time.
|
|
// Node 22+ exposes `globalThis.navigator` as a non-writable property, so that
|
|
// assignment throws:
|
|
//
|
|
// TypeError: Cannot set property navigator of #<Object> which has only a getter
|
|
//
|
|
// `--require` runs synchronously before any ESM resolution, which is the only
|
|
// hook early enough to fix the descriptor before the framework loads.
|
|
//
|
|
// We make the property writable + configurable, preserving any existing value
|
|
// so anything that reads from it later still works.
|
|
'use strict';
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// Node version guard.
|
|
//
|
|
// Sendi runs .ts files directly via Node's native type-stripping loader,
|
|
// which is unflagged from Node 22.18. On older Nodes you get:
|
|
// TypeError [ERR_UNKNOWN_FILE_EXTENSION]: Unknown file extension ".ts"
|
|
// Fail loudly with an actionable message instead.
|
|
// -----------------------------------------------------------------------------
|
|
(function checkNodeVersion() {
|
|
const [maj, min] = process.versions.node.split('.').map(Number);
|
|
const ok = maj > 22 || (maj === 22 && min >= 18);
|
|
if (!ok) {
|
|
// eslint-disable-next-line no-console
|
|
console.error(
|
|
'\n[sendi] Unsupported Node.js version: ' + process.versions.node + '\n' +
|
|
' Sendi requires Node >= 22.18 (native .ts stripping).\n' +
|
|
' Fix with `nvm use` (a .nvmrc is checked in) or install Node 22.18+.\n' +
|
|
' Current node binary: ' + process.execPath + '\n',
|
|
);
|
|
process.exit(1);
|
|
}
|
|
})();
|
|
|
|
try {
|
|
const desc = Object.getOwnPropertyDescriptor(globalThis, 'navigator');
|
|
if (desc && desc.writable !== true) {
|
|
const current = 'value' in desc ? desc.value : (desc.get ? desc.get() : undefined);
|
|
Object.defineProperty(globalThis, 'navigator', {
|
|
value: current,
|
|
writable: true,
|
|
configurable: true,
|
|
});
|
|
}
|
|
} catch (err) {
|
|
// If the patch can't apply, log loudly so the bot startup failure is at
|
|
// least traceable; the framework import will then fail with the original error.
|
|
// eslint-disable-next-line no-console
|
|
console.warn('[navigator-patch] failed to make globalThis.navigator writable:', err && err.message);
|
|
}
|