@diegoaltoworks/talker 0.14.0 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapters/twilio.d.ts +9 -2
- package/dist/adapters/twilio.d.ts.map +1 -1
- package/dist/db/migrate.d.ts.map +1 -1
- package/dist/db/sessions.d.ts +16 -0
- package/dist/db/sessions.d.ts.map +1 -1
- package/dist/index.d.ts +6 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +262 -85
- package/dist/index.mjs +259 -85
- package/dist/routes/shared/handle-fallback.d.ts +20 -0
- package/dist/routes/shared/handle-fallback.d.ts.map +1 -0
- package/dist/routes/shared/handle-fallback.test.d.ts +7 -0
- package/dist/routes/shared/handle-fallback.test.d.ts.map +1 -0
- package/dist/routes/shared/handle-status-callback.d.ts +23 -0
- package/dist/routes/shared/handle-status-callback.d.ts.map +1 -0
- package/dist/routes/shared/handle-status-callback.test.d.ts +7 -0
- package/dist/routes/shared/handle-status-callback.test.d.ts.map +1 -0
- package/dist/routes/shared/index.d.ts +8 -0
- package/dist/routes/shared/index.d.ts.map +1 -0
- package/dist/routes/sms/index.d.ts +6 -0
- package/dist/routes/sms/index.d.ts.map +1 -1
- package/dist/routes/whatsapp/index.d.ts +6 -0
- package/dist/routes/whatsapp/index.d.ts.map +1 -1
- package/dist/types.d.ts +32 -1
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -10,15 +10,22 @@ import type { TwilioConfig } from "../types";
|
|
|
10
10
|
* Returns the bare phone number (e.g., "+1234567890").
|
|
11
11
|
*/
|
|
12
12
|
export declare function stripWhatsAppPrefix(phoneNumber: string): string;
|
|
13
|
+
/**
|
|
14
|
+
* Options for outbound message sending
|
|
15
|
+
*/
|
|
16
|
+
export interface SendMessageOptions {
|
|
17
|
+
/** Status callback URL for delivery status updates */
|
|
18
|
+
statusCallback?: string;
|
|
19
|
+
}
|
|
13
20
|
/**
|
|
14
21
|
* Send an SMS via Twilio REST API
|
|
15
22
|
*/
|
|
16
|
-
export declare function sendSMS(config: TwilioConfig, to: string, message: string): Promise<boolean>;
|
|
23
|
+
export declare function sendSMS(config: TwilioConfig, to: string, message: string, options?: SendMessageOptions): Promise<boolean>;
|
|
17
24
|
/**
|
|
18
25
|
* Send a WhatsApp message via Twilio REST API.
|
|
19
26
|
*
|
|
20
27
|
* Twilio's WhatsApp API uses the same Messages endpoint as SMS but
|
|
21
28
|
* requires `whatsapp:` prefixed From/To numbers.
|
|
22
29
|
*/
|
|
23
|
-
export declare function sendWhatsApp(config: TwilioConfig, to: string, message: string): Promise<boolean>;
|
|
30
|
+
export declare function sendWhatsApp(config: TwilioConfig, to: string, message: string, options?: SendMessageOptions): Promise<boolean>;
|
|
24
31
|
//# sourceMappingURL=twilio.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"twilio.d.ts","sourceRoot":"","sources":["../../src/adapters/twilio.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAE7C;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAE/D;AAED;;GAEG;AACH,wBAAsB,OAAO,
|
|
1
|
+
{"version":3,"file":"twilio.d.ts","sourceRoot":"","sources":["../../src/adapters/twilio.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAE7C;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAE/D;AAED;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,sDAAsD;IACtD,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AA+BD;;GAEG;AACH,wBAAsB,OAAO,CAC3B,MAAM,EAAE,YAAY,EACpB,EAAE,EAAE,MAAM,EACV,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,kBAAkB,GAC3B,OAAO,CAAC,OAAO,CAAC,CA0ClB;AAED;;;;;GAKG;AACH,wBAAsB,YAAY,CAChC,MAAM,EAAE,YAAY,EACpB,EAAE,EAAE,MAAM,EACV,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,kBAAkB,GAC3B,OAAO,CAAC,OAAO,CAAC,CA8DlB"}
|
package/dist/db/migrate.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"migrate.d.ts","sourceRoot":"","sources":["../../src/db/migrate.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;
|
|
1
|
+
{"version":3,"file":"migrate.d.ts","sourceRoot":"","sources":["../../src/db/migrate.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAgDH,wBAAsB,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC,CAsBnD"}
|
package/dist/db/sessions.d.ts
CHANGED
|
@@ -53,5 +53,21 @@ export declare function updateSessionIncremental(phoneNumber: string, channel: C
|
|
|
53
53
|
content: string;
|
|
54
54
|
timestamp: number;
|
|
55
55
|
}>, conversationId?: string): Promise<boolean>;
|
|
56
|
+
/**
|
|
57
|
+
* Message delivery status record (from Twilio status callbacks)
|
|
58
|
+
*/
|
|
59
|
+
export interface MessageStatusRecord {
|
|
60
|
+
messageSid: string;
|
|
61
|
+
channel: "sms" | "whatsapp";
|
|
62
|
+
from: string;
|
|
63
|
+
to: string;
|
|
64
|
+
status: string;
|
|
65
|
+
errorCode?: string;
|
|
66
|
+
errorMessage?: string;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Upsert a message delivery status (from Twilio status callback)
|
|
70
|
+
*/
|
|
71
|
+
export declare function upsertMessageStatus(record: MessageStatusRecord): Promise<boolean>;
|
|
56
72
|
export { generateId };
|
|
57
73
|
//# sourceMappingURL=sessions.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sessions.d.ts","sourceRoot":"","sources":["../../src/db/sessions.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AAGxC,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,OAAO,GAAG,YAAY,CAAC;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,GAAG,WAAW,CAAC;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,iBAAS,UAAU,IAAI,MAAM,CAE5B;AAED,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAGhF;AAED;;GAEG;AACH,wBAAsB,aAAa,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,OAAO,CAAC,CAiD5E;AAED;;GAEG;AACH,wBAAsB,aAAa,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,OAAO,CAAC,CAkB5E;AAED;;GAEG;AACH,wBAAsB,uBAAuB,CAC3C,OAAO,EAAE,aAAa,EACtB,QAAQ,EAAE,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,GAAG,WAAW,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAAC,GAClF,OAAO,CAAC,OAAO,CAAC,CAgBlB;AAED;;;GAGG;AACH,wBAAsB,wBAAwB,CAC5C,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,OAAO,EAChB,OAAO,EAAE;IAAE,SAAS,EAAE,MAAM,CAAA;CAAE,EAC9B,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,GAAG,WAAW,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAAC,EACnF,cAAc,CAAC,EAAE,MAAM,GACtB,OAAO,CAAC,OAAO,CAAC,CAwClB;AAED,OAAO,EAAE,UAAU,EAAE,CAAC"}
|
|
1
|
+
{"version":3,"file":"sessions.d.ts","sourceRoot":"","sources":["../../src/db/sessions.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AAGxC,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,OAAO,GAAG,YAAY,CAAC;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,GAAG,WAAW,CAAC;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,iBAAS,UAAU,IAAI,MAAM,CAE5B;AAED,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAGhF;AAED;;GAEG;AACH,wBAAsB,aAAa,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,OAAO,CAAC,CAiD5E;AAED;;GAEG;AACH,wBAAsB,aAAa,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,OAAO,CAAC,CAkB5E;AAED;;GAEG;AACH,wBAAsB,uBAAuB,CAC3C,OAAO,EAAE,aAAa,EACtB,QAAQ,EAAE,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,GAAG,WAAW,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAAC,GAClF,OAAO,CAAC,OAAO,CAAC,CAgBlB;AAED;;;GAGG;AACH,wBAAsB,wBAAwB,CAC5C,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,OAAO,EAChB,OAAO,EAAE;IAAE,SAAS,EAAE,MAAM,CAAA;CAAE,EAC9B,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,GAAG,WAAW,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAAC,EACnF,cAAc,CAAC,EAAE,MAAM,GACtB,OAAO,CAAC,OAAO,CAAC,CAwClB;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,KAAK,GAAG,UAAU,CAAC;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;GAEG;AACH,wBAAsB,mBAAmB,CAAC,MAAM,EAAE,mBAAmB,GAAG,OAAO,CAAC,OAAO,CAAC,CA0CvF;AAED,OAAO,EAAE,UAAU,EAAE,CAAC"}
|
package/dist/index.d.ts
CHANGED
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
export { createTelephonyRoutes } from "./plugin";
|
|
40
40
|
export { createStandaloneServer } from "./standalone";
|
|
41
41
|
export type { StandaloneConfig } from "./standalone";
|
|
42
|
-
export type { TalkerConfig, TalkerDependencies, TelephonyFeatureFlags, TwilioConfig, ChatbotConfig, ProcessingConfig, Channel, VoiceConfig, IncomingResult, TelephonyContext, Phrases, } from "./types";
|
|
42
|
+
export type { TalkerConfig, TalkerDependencies, TelephonyFeatureFlags, TwilioConfig, ChatbotConfig, ProcessingConfig, Channel, VoiceConfig, IncomingResult, TelephonyContext, Phrases, MessageStatusEvent, } from "./types";
|
|
43
43
|
export type { FlowDefinition, FlowSchema, FlowSchemaProperty, FlowState, FlowHandler, FlowHandlerResult, FlowHandlerContext, FlowPrefill, FlowExtractionResult, FlowResult, IntentDetection, LoadedFlow, } from "./types";
|
|
44
44
|
export { processIncoming, processOutgoing } from "./core/processing";
|
|
45
45
|
export { getVoiceConfig, getDefaultVoices } from "./core/voice";
|
|
@@ -52,14 +52,18 @@ export { processFlow, shouldExitFlow } from "./flows/manager";
|
|
|
52
52
|
export { loadFlowsFromDirectory } from "./flows/loader";
|
|
53
53
|
export { getExitMessage } from "./flows/utils";
|
|
54
54
|
export { sendSMS, sendWhatsApp, stripWhatsAppPrefix } from "./adapters/twilio";
|
|
55
|
+
export type { SendMessageOptions } from "./adapters/twilio";
|
|
55
56
|
export { initDbClient, getDbClient, closeDbClient } from "./db/client";
|
|
56
57
|
export { runMigrations } from "./db/migrate";
|
|
57
58
|
export { persistFinalSession, persistSession } from "./db/persist";
|
|
58
|
-
export type { SessionRecord, MessageRecord } from "./db/sessions";
|
|
59
|
+
export type { SessionRecord, MessageRecord, MessageStatusRecord } from "./db/sessions";
|
|
60
|
+
export { upsertMessageStatus } from "./db/sessions";
|
|
59
61
|
export { callRoutes } from "./routes/call";
|
|
60
62
|
export { smsRoutes } from "./routes/sms";
|
|
61
63
|
export { whatsappRoutes } from "./routes/whatsapp";
|
|
62
64
|
export { logger, redactPhone } from "./core/logger";
|
|
65
|
+
export { handleStatusCallback } from "./routes/shared/handle-status-callback";
|
|
66
|
+
export { handleFallback } from "./routes/shared/handle-fallback";
|
|
63
67
|
export { twilioSignatureMiddleware, validateTwilioSignature } from "./middleware/twilio-signature";
|
|
64
68
|
export { rateLimitMiddleware } from "./middleware/rate-limit";
|
|
65
69
|
export { inputSanitizeMiddleware } from "./middleware/input-sanitize";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AAGH,OAAO,EAAE,qBAAqB,EAAE,MAAM,UAAU,CAAC;AAGjD,OAAO,EAAE,sBAAsB,EAAE,MAAM,cAAc,CAAC;AACtD,YAAY,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAGrD,YAAY,EACV,YAAY,EACZ,kBAAkB,EAClB,qBAAqB,EACrB,YAAY,EACZ,aAAa,EACb,gBAAgB,EAChB,OAAO,EACP,WAAW,EACX,cAAc,EACd,gBAAgB,EAChB,OAAO,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -85,6 +85,8 @@ __export(index_exports, {
|
|
|
85
85
|
getSmsPhrase: () => getSmsPhrase,
|
|
86
86
|
getVoiceConfig: () => getVoiceConfig,
|
|
87
87
|
getWhatsAppPhrase: () => getWhatsAppPhrase,
|
|
88
|
+
handleFallback: () => handleFallback,
|
|
89
|
+
handleStatusCallback: () => handleStatusCallback,
|
|
88
90
|
incrementNoSpeechRetries: () => incrementNoSpeechRetries,
|
|
89
91
|
initDbClient: () => initDbClient,
|
|
90
92
|
inputSanitizeMiddleware: () => inputSanitizeMiddleware,
|
|
@@ -115,6 +117,7 @@ __export(index_exports, {
|
|
|
115
117
|
transferTwiml: () => transferTwiml,
|
|
116
118
|
twilioSignatureMiddleware: () => twilioSignatureMiddleware,
|
|
117
119
|
updateFlowParams: () => updateFlowParams,
|
|
120
|
+
upsertMessageStatus: () => upsertMessageStatus,
|
|
118
121
|
validateTwilioSignature: () => validateTwilioSignature,
|
|
119
122
|
whatsappRoutes: () => whatsappRoutes
|
|
120
123
|
});
|
|
@@ -314,7 +317,7 @@ var SCHEMA_STATEMENTS = [
|
|
|
314
317
|
`CREATE TABLE IF NOT EXISTS talker_sessions (
|
|
315
318
|
id TEXT PRIMARY KEY,
|
|
316
319
|
phone_number TEXT NOT NULL,
|
|
317
|
-
channel TEXT NOT NULL CHECK(channel IN ('call', 'sms')),
|
|
320
|
+
channel TEXT NOT NULL CHECK(channel IN ('call', 'sms', 'whatsapp')),
|
|
318
321
|
reason TEXT NOT NULL CHECK(reason IN ('ended', 'redirected')),
|
|
319
322
|
language TEXT NOT NULL,
|
|
320
323
|
started_at INTEGER NOT NULL,
|
|
@@ -334,10 +337,23 @@ var SCHEMA_STATEMENTS = [
|
|
|
334
337
|
created_at INTEGER NOT NULL DEFAULT (unixepoch()),
|
|
335
338
|
FOREIGN KEY (session_id) REFERENCES talker_sessions(id) ON DELETE CASCADE
|
|
336
339
|
)`,
|
|
340
|
+
`CREATE TABLE IF NOT EXISTS talker_message_status (
|
|
341
|
+
message_sid TEXT PRIMARY KEY,
|
|
342
|
+
channel TEXT NOT NULL CHECK(channel IN ('sms', 'whatsapp')),
|
|
343
|
+
phone_from TEXT NOT NULL,
|
|
344
|
+
phone_to TEXT NOT NULL,
|
|
345
|
+
status TEXT NOT NULL,
|
|
346
|
+
error_code TEXT,
|
|
347
|
+
error_message TEXT,
|
|
348
|
+
created_at INTEGER NOT NULL DEFAULT (unixepoch()),
|
|
349
|
+
updated_at INTEGER NOT NULL DEFAULT (unixepoch())
|
|
350
|
+
)`,
|
|
337
351
|
"CREATE INDEX IF NOT EXISTS idx_talker_sessions_phone ON talker_sessions(phone_number)",
|
|
338
352
|
"CREATE INDEX IF NOT EXISTS idx_talker_sessions_created ON talker_sessions(created_at DESC)",
|
|
339
353
|
"CREATE INDEX IF NOT EXISTS idx_talker_messages_session ON talker_messages(session_id)",
|
|
340
|
-
"CREATE INDEX IF NOT EXISTS idx_talker_messages_ts ON talker_messages(timestamp)"
|
|
354
|
+
"CREATE INDEX IF NOT EXISTS idx_talker_messages_ts ON talker_messages(timestamp)",
|
|
355
|
+
"CREATE INDEX IF NOT EXISTS idx_talker_message_status_phone ON talker_message_status(phone_to)",
|
|
356
|
+
"CREATE INDEX IF NOT EXISTS idx_talker_message_status_created ON talker_message_status(created_at DESC)"
|
|
341
357
|
];
|
|
342
358
|
async function runMigrations() {
|
|
343
359
|
const client2 = getDbClient();
|
|
@@ -1088,6 +1104,47 @@ async function insertMessage(message) {
|
|
|
1088
1104
|
return false;
|
|
1089
1105
|
}
|
|
1090
1106
|
}
|
|
1107
|
+
async function upsertMessageStatus(record) {
|
|
1108
|
+
const client2 = getDbClient();
|
|
1109
|
+
if (!client2) return false;
|
|
1110
|
+
try {
|
|
1111
|
+
await client2.execute({
|
|
1112
|
+
sql: `
|
|
1113
|
+
INSERT INTO talker_message_status (
|
|
1114
|
+
message_sid, channel, phone_from, phone_to, status,
|
|
1115
|
+
error_code, error_message, updated_at
|
|
1116
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
1117
|
+
ON CONFLICT(message_sid) DO UPDATE SET
|
|
1118
|
+
status = excluded.status,
|
|
1119
|
+
error_code = excluded.error_code,
|
|
1120
|
+
error_message = excluded.error_message,
|
|
1121
|
+
updated_at = excluded.updated_at
|
|
1122
|
+
`,
|
|
1123
|
+
args: [
|
|
1124
|
+
record.messageSid,
|
|
1125
|
+
record.channel,
|
|
1126
|
+
record.from,
|
|
1127
|
+
record.to,
|
|
1128
|
+
record.status,
|
|
1129
|
+
record.errorCode || null,
|
|
1130
|
+
record.errorMessage || null,
|
|
1131
|
+
Date.now()
|
|
1132
|
+
]
|
|
1133
|
+
});
|
|
1134
|
+
logger.info("message status upserted", {
|
|
1135
|
+
messageSid: record.messageSid,
|
|
1136
|
+
channel: record.channel,
|
|
1137
|
+
status: record.status
|
|
1138
|
+
});
|
|
1139
|
+
return true;
|
|
1140
|
+
} catch (error) {
|
|
1141
|
+
logger.error("failed to upsert message status", {
|
|
1142
|
+
messageSid: record.messageSid,
|
|
1143
|
+
error: error instanceof Error ? error.message : "Unknown"
|
|
1144
|
+
});
|
|
1145
|
+
return false;
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
1091
1148
|
|
|
1092
1149
|
// src/db/persist.ts
|
|
1093
1150
|
function persistSession(phoneNumber, channel, conversationId) {
|
|
@@ -1921,6 +1978,194 @@ function callRoutes(deps, registry) {
|
|
|
1921
1978
|
// src/routes/sms/index.ts
|
|
1922
1979
|
var import_hono2 = require("hono");
|
|
1923
1980
|
|
|
1981
|
+
// src/adapters/twilio.ts
|
|
1982
|
+
function stripWhatsAppPrefix(phoneNumber) {
|
|
1983
|
+
return phoneNumber.replace(/^whatsapp:/, "");
|
|
1984
|
+
}
|
|
1985
|
+
function buildMessageParams(config, to, message, options) {
|
|
1986
|
+
const params = {
|
|
1987
|
+
To: to,
|
|
1988
|
+
Body: message
|
|
1989
|
+
};
|
|
1990
|
+
if (config.messagingServiceSid) {
|
|
1991
|
+
params.MessagingServiceSid = config.messagingServiceSid;
|
|
1992
|
+
} else if (config.phoneNumber) {
|
|
1993
|
+
params.From = config.phoneNumber;
|
|
1994
|
+
}
|
|
1995
|
+
if (options?.statusCallback) {
|
|
1996
|
+
params.StatusCallback = options.statusCallback;
|
|
1997
|
+
}
|
|
1998
|
+
return new URLSearchParams(params);
|
|
1999
|
+
}
|
|
2000
|
+
async function sendSMS(config, to, message, options) {
|
|
2001
|
+
if (!config.accountSid || !config.authToken) {
|
|
2002
|
+
logger.warn("Twilio credentials not configured, skipping SMS send", { to });
|
|
2003
|
+
return false;
|
|
2004
|
+
}
|
|
2005
|
+
if (!config.phoneNumber && !config.messagingServiceSid) {
|
|
2006
|
+
logger.warn("No phone number or messaging service configured, skipping SMS send", { to });
|
|
2007
|
+
return false;
|
|
2008
|
+
}
|
|
2009
|
+
try {
|
|
2010
|
+
const auth = Buffer.from(`${config.accountSid}:${config.authToken}`).toString("base64");
|
|
2011
|
+
const response = await fetch(
|
|
2012
|
+
`https://api.twilio.com/2010-04-01/Accounts/${config.accountSid}/Messages.json`,
|
|
2013
|
+
{
|
|
2014
|
+
method: "POST",
|
|
2015
|
+
headers: {
|
|
2016
|
+
Authorization: `Basic ${auth}`,
|
|
2017
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
2018
|
+
},
|
|
2019
|
+
body: buildMessageParams(config, to, message, options)
|
|
2020
|
+
}
|
|
2021
|
+
);
|
|
2022
|
+
if (!response.ok) {
|
|
2023
|
+
const error = await response.text();
|
|
2024
|
+
logger.error("SMS send failed", { to, status: response.status, error });
|
|
2025
|
+
return false;
|
|
2026
|
+
}
|
|
2027
|
+
const data = await response.json();
|
|
2028
|
+
logger.info("SMS sent successfully", { to, messageSid: data.sid });
|
|
2029
|
+
return true;
|
|
2030
|
+
} catch (error) {
|
|
2031
|
+
logger.error("SMS send error", {
|
|
2032
|
+
to,
|
|
2033
|
+
error: error instanceof Error ? error.message : "Unknown"
|
|
2034
|
+
});
|
|
2035
|
+
return false;
|
|
2036
|
+
}
|
|
2037
|
+
}
|
|
2038
|
+
async function sendWhatsApp(config, to, message, options) {
|
|
2039
|
+
if (!config.accountSid || !config.authToken) {
|
|
2040
|
+
logger.warn("Twilio credentials not configured, skipping WhatsApp send", { to });
|
|
2041
|
+
return false;
|
|
2042
|
+
}
|
|
2043
|
+
if (!config.phoneNumber && !config.messagingServiceSid) {
|
|
2044
|
+
logger.warn("No phone number or messaging service configured, skipping WhatsApp send", { to });
|
|
2045
|
+
return false;
|
|
2046
|
+
}
|
|
2047
|
+
try {
|
|
2048
|
+
const auth = Buffer.from(`${config.accountSid}:${config.authToken}`).toString("base64");
|
|
2049
|
+
const whatsappTo = to.startsWith("whatsapp:") ? to : `whatsapp:${to}`;
|
|
2050
|
+
const params = {
|
|
2051
|
+
To: whatsappTo,
|
|
2052
|
+
Body: message
|
|
2053
|
+
};
|
|
2054
|
+
if (config.messagingServiceSid) {
|
|
2055
|
+
params.MessagingServiceSid = config.messagingServiceSid;
|
|
2056
|
+
} else if (config.phoneNumber) {
|
|
2057
|
+
const whatsappFrom = config.phoneNumber.startsWith("whatsapp:") ? config.phoneNumber : `whatsapp:${config.phoneNumber}`;
|
|
2058
|
+
params.From = whatsappFrom;
|
|
2059
|
+
}
|
|
2060
|
+
if (options?.statusCallback) {
|
|
2061
|
+
params.StatusCallback = options.statusCallback;
|
|
2062
|
+
}
|
|
2063
|
+
const response = await fetch(
|
|
2064
|
+
`https://api.twilio.com/2010-04-01/Accounts/${config.accountSid}/Messages.json`,
|
|
2065
|
+
{
|
|
2066
|
+
method: "POST",
|
|
2067
|
+
headers: {
|
|
2068
|
+
Authorization: `Basic ${auth}`,
|
|
2069
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
2070
|
+
},
|
|
2071
|
+
body: new URLSearchParams(params)
|
|
2072
|
+
}
|
|
2073
|
+
);
|
|
2074
|
+
if (!response.ok) {
|
|
2075
|
+
const error = await response.text();
|
|
2076
|
+
logger.error("WhatsApp send failed", { to, status: response.status, error });
|
|
2077
|
+
return false;
|
|
2078
|
+
}
|
|
2079
|
+
const data = await response.json();
|
|
2080
|
+
logger.info("WhatsApp message sent successfully", { to, messageSid: data.sid });
|
|
2081
|
+
return true;
|
|
2082
|
+
} catch (error) {
|
|
2083
|
+
logger.error("WhatsApp send error", {
|
|
2084
|
+
to,
|
|
2085
|
+
error: error instanceof Error ? error.message : "Unknown"
|
|
2086
|
+
});
|
|
2087
|
+
return false;
|
|
2088
|
+
}
|
|
2089
|
+
}
|
|
2090
|
+
|
|
2091
|
+
// src/routes/shared/handle-fallback.ts
|
|
2092
|
+
async function handleFallback(c, deps, channel) {
|
|
2093
|
+
const body = await c.req.parseBody();
|
|
2094
|
+
const rawFrom = body.From || "unknown";
|
|
2095
|
+
const phoneNumber = channel === "whatsapp" ? stripWhatsAppPrefix(rawFrom) : rawFrom.trim();
|
|
2096
|
+
const messageBody = body.Body || "";
|
|
2097
|
+
const errorCode = body.ErrorCode;
|
|
2098
|
+
const errorUrl = body.ErrorUrl;
|
|
2099
|
+
logger.error("fallback webhook triggered", {
|
|
2100
|
+
channel,
|
|
2101
|
+
phoneNumber,
|
|
2102
|
+
messageBody: messageBody.substring(0, 100),
|
|
2103
|
+
errorCode,
|
|
2104
|
+
errorUrl
|
|
2105
|
+
});
|
|
2106
|
+
const errorMessage = channel === "whatsapp" ? getWhatsAppPhrase("en", "genericError", deps.config.languageDir) : getSmsPhrase("en", "genericError", deps.config.languageDir);
|
|
2107
|
+
return c.text(messageTwiml(errorMessage), 200, { "Content-Type": "text/xml" });
|
|
2108
|
+
}
|
|
2109
|
+
|
|
2110
|
+
// src/routes/shared/handle-status-callback.ts
|
|
2111
|
+
async function handleStatusCallback(c, deps, channel) {
|
|
2112
|
+
const body = await c.req.parseBody();
|
|
2113
|
+
const messageSid = body.MessageSid || "";
|
|
2114
|
+
const messageStatus = body.MessageStatus || "";
|
|
2115
|
+
const rawFrom = body.From || "";
|
|
2116
|
+
const rawTo = body.To || "";
|
|
2117
|
+
const errorCode = body.ErrorCode;
|
|
2118
|
+
const errorMessage = body.ErrorMessage;
|
|
2119
|
+
const from = channel === "whatsapp" ? stripWhatsAppPrefix(rawFrom) : rawFrom;
|
|
2120
|
+
const to = channel === "whatsapp" ? stripWhatsAppPrefix(rawTo) : rawTo;
|
|
2121
|
+
logger.info("message status callback", {
|
|
2122
|
+
channel,
|
|
2123
|
+
messageSid,
|
|
2124
|
+
messageStatus,
|
|
2125
|
+
from,
|
|
2126
|
+
to,
|
|
2127
|
+
errorCode
|
|
2128
|
+
});
|
|
2129
|
+
if (!messageSid || !messageStatus) {
|
|
2130
|
+
logger.warn("status callback missing required fields", { channel, messageSid, messageStatus });
|
|
2131
|
+
return c.text("", 200);
|
|
2132
|
+
}
|
|
2133
|
+
upsertMessageStatus({
|
|
2134
|
+
messageSid,
|
|
2135
|
+
channel,
|
|
2136
|
+
from,
|
|
2137
|
+
to,
|
|
2138
|
+
status: messageStatus,
|
|
2139
|
+
errorCode,
|
|
2140
|
+
errorMessage
|
|
2141
|
+
}).catch((err) => {
|
|
2142
|
+
logger.error("failed to persist message status", {
|
|
2143
|
+
messageSid,
|
|
2144
|
+
error: getErrorMessage(err)
|
|
2145
|
+
});
|
|
2146
|
+
});
|
|
2147
|
+
if (deps.config.onMessageStatus) {
|
|
2148
|
+
const event = {
|
|
2149
|
+
messageSid,
|
|
2150
|
+
messageStatus,
|
|
2151
|
+
channel,
|
|
2152
|
+
from,
|
|
2153
|
+
to,
|
|
2154
|
+
errorCode,
|
|
2155
|
+
errorMessage
|
|
2156
|
+
};
|
|
2157
|
+
try {
|
|
2158
|
+
await Promise.resolve(deps.config.onMessageStatus(event));
|
|
2159
|
+
} catch (err) {
|
|
2160
|
+
logger.error("onMessageStatus callback error", {
|
|
2161
|
+
messageSid,
|
|
2162
|
+
error: getErrorMessage(err)
|
|
2163
|
+
});
|
|
2164
|
+
}
|
|
2165
|
+
}
|
|
2166
|
+
return c.text("", 200);
|
|
2167
|
+
}
|
|
2168
|
+
|
|
1924
2169
|
// src/routes/sms/processor.ts
|
|
1925
2170
|
async function processSms(deps, registry, phoneNumber, messageBody) {
|
|
1926
2171
|
const incoming = await processIncoming(deps, phoneNumber, messageBody, "sms");
|
|
@@ -1985,7 +2230,13 @@ function smsRoutes(deps, registry) {
|
|
|
1985
2230
|
app.post("/sms", twilioSignatureMiddleware(authToken, baseUrl));
|
|
1986
2231
|
app.post("/sms", rateLimitMiddleware(deps.config.rateLimit));
|
|
1987
2232
|
app.post("/sms", inputSanitizeMiddleware(deps.config.maxInputLength));
|
|
2233
|
+
app.post("/sms/fallback", twilioSignatureMiddleware(authToken, baseUrl));
|
|
2234
|
+
app.post("/sms/fallback", rateLimitMiddleware(deps.config.rateLimit));
|
|
2235
|
+
app.post("/sms/fallback", inputSanitizeMiddleware(deps.config.maxInputLength));
|
|
2236
|
+
app.post("/sms/status", twilioSignatureMiddleware(authToken, baseUrl));
|
|
1988
2237
|
app.post("/sms", (c) => handleIncomingSMS(c, deps, registry));
|
|
2238
|
+
app.post("/sms/fallback", (c) => handleFallback(c, deps, "sms"));
|
|
2239
|
+
app.post("/sms/status", (c) => handleStatusCallback(c, deps, "sms"));
|
|
1989
2240
|
app.get("/sms", (c) => c.text("SMS endpoint active"));
|
|
1990
2241
|
return app;
|
|
1991
2242
|
}
|
|
@@ -1993,89 +2244,6 @@ function smsRoutes(deps, registry) {
|
|
|
1993
2244
|
// src/routes/whatsapp/index.ts
|
|
1994
2245
|
var import_hono3 = require("hono");
|
|
1995
2246
|
|
|
1996
|
-
// src/adapters/twilio.ts
|
|
1997
|
-
function stripWhatsAppPrefix(phoneNumber) {
|
|
1998
|
-
return phoneNumber.replace(/^whatsapp:/, "");
|
|
1999
|
-
}
|
|
2000
|
-
async function sendSMS(config, to, message) {
|
|
2001
|
-
if (!config.accountSid || !config.authToken || !config.phoneNumber) {
|
|
2002
|
-
logger.warn("Twilio credentials not configured, skipping SMS send", { to });
|
|
2003
|
-
return false;
|
|
2004
|
-
}
|
|
2005
|
-
try {
|
|
2006
|
-
const auth = Buffer.from(`${config.accountSid}:${config.authToken}`).toString("base64");
|
|
2007
|
-
const response = await fetch(
|
|
2008
|
-
`https://api.twilio.com/2010-04-01/Accounts/${config.accountSid}/Messages.json`,
|
|
2009
|
-
{
|
|
2010
|
-
method: "POST",
|
|
2011
|
-
headers: {
|
|
2012
|
-
Authorization: `Basic ${auth}`,
|
|
2013
|
-
"Content-Type": "application/x-www-form-urlencoded"
|
|
2014
|
-
},
|
|
2015
|
-
body: new URLSearchParams({
|
|
2016
|
-
From: config.phoneNumber,
|
|
2017
|
-
To: to,
|
|
2018
|
-
Body: message
|
|
2019
|
-
})
|
|
2020
|
-
}
|
|
2021
|
-
);
|
|
2022
|
-
if (!response.ok) {
|
|
2023
|
-
const error = await response.text();
|
|
2024
|
-
logger.error("SMS send failed", { to, status: response.status, error });
|
|
2025
|
-
return false;
|
|
2026
|
-
}
|
|
2027
|
-
const data = await response.json();
|
|
2028
|
-
logger.info("SMS sent successfully", { to, messageSid: data.sid });
|
|
2029
|
-
return true;
|
|
2030
|
-
} catch (error) {
|
|
2031
|
-
logger.error("SMS send error", {
|
|
2032
|
-
to,
|
|
2033
|
-
error: error instanceof Error ? error.message : "Unknown"
|
|
2034
|
-
});
|
|
2035
|
-
return false;
|
|
2036
|
-
}
|
|
2037
|
-
}
|
|
2038
|
-
async function sendWhatsApp(config, to, message) {
|
|
2039
|
-
if (!config.accountSid || !config.authToken || !config.phoneNumber) {
|
|
2040
|
-
logger.warn("Twilio credentials not configured, skipping WhatsApp send", { to });
|
|
2041
|
-
return false;
|
|
2042
|
-
}
|
|
2043
|
-
try {
|
|
2044
|
-
const auth = Buffer.from(`${config.accountSid}:${config.authToken}`).toString("base64");
|
|
2045
|
-
const whatsappTo = to.startsWith("whatsapp:") ? to : `whatsapp:${to}`;
|
|
2046
|
-
const whatsappFrom = config.phoneNumber.startsWith("whatsapp:") ? config.phoneNumber : `whatsapp:${config.phoneNumber}`;
|
|
2047
|
-
const response = await fetch(
|
|
2048
|
-
`https://api.twilio.com/2010-04-01/Accounts/${config.accountSid}/Messages.json`,
|
|
2049
|
-
{
|
|
2050
|
-
method: "POST",
|
|
2051
|
-
headers: {
|
|
2052
|
-
Authorization: `Basic ${auth}`,
|
|
2053
|
-
"Content-Type": "application/x-www-form-urlencoded"
|
|
2054
|
-
},
|
|
2055
|
-
body: new URLSearchParams({
|
|
2056
|
-
From: whatsappFrom,
|
|
2057
|
-
To: whatsappTo,
|
|
2058
|
-
Body: message
|
|
2059
|
-
})
|
|
2060
|
-
}
|
|
2061
|
-
);
|
|
2062
|
-
if (!response.ok) {
|
|
2063
|
-
const error = await response.text();
|
|
2064
|
-
logger.error("WhatsApp send failed", { to, status: response.status, error });
|
|
2065
|
-
return false;
|
|
2066
|
-
}
|
|
2067
|
-
const data = await response.json();
|
|
2068
|
-
logger.info("WhatsApp message sent successfully", { to, messageSid: data.sid });
|
|
2069
|
-
return true;
|
|
2070
|
-
} catch (error) {
|
|
2071
|
-
logger.error("WhatsApp send error", {
|
|
2072
|
-
to,
|
|
2073
|
-
error: error instanceof Error ? error.message : "Unknown"
|
|
2074
|
-
});
|
|
2075
|
-
return false;
|
|
2076
|
-
}
|
|
2077
|
-
}
|
|
2078
|
-
|
|
2079
2247
|
// src/routes/whatsapp/processor.ts
|
|
2080
2248
|
async function processWhatsApp(deps, registry, phoneNumber, messageBody) {
|
|
2081
2249
|
const incoming = await processIncoming(deps, phoneNumber, messageBody, "whatsapp");
|
|
@@ -2147,7 +2315,13 @@ function whatsappRoutes(deps, registry) {
|
|
|
2147
2315
|
app.post("/whatsapp", twilioSignatureMiddleware(authToken, baseUrl));
|
|
2148
2316
|
app.post("/whatsapp", rateLimitMiddleware(deps.config.rateLimit));
|
|
2149
2317
|
app.post("/whatsapp", inputSanitizeMiddleware(deps.config.maxInputLength));
|
|
2318
|
+
app.post("/whatsapp/fallback", twilioSignatureMiddleware(authToken, baseUrl));
|
|
2319
|
+
app.post("/whatsapp/fallback", rateLimitMiddleware(deps.config.rateLimit));
|
|
2320
|
+
app.post("/whatsapp/fallback", inputSanitizeMiddleware(deps.config.maxInputLength));
|
|
2321
|
+
app.post("/whatsapp/status", twilioSignatureMiddleware(authToken, baseUrl));
|
|
2150
2322
|
app.post("/whatsapp", (c) => handleIncomingWhatsApp(c, deps, registry));
|
|
2323
|
+
app.post("/whatsapp/fallback", (c) => handleFallback(c, deps, "whatsapp"));
|
|
2324
|
+
app.post("/whatsapp/status", (c) => handleStatusCallback(c, deps, "whatsapp"));
|
|
2151
2325
|
app.get("/whatsapp", (c) => c.text("WhatsApp endpoint active"));
|
|
2152
2326
|
return app;
|
|
2153
2327
|
}
|
|
@@ -2274,6 +2448,8 @@ async function createStandaloneServer(config) {
|
|
|
2274
2448
|
getSmsPhrase,
|
|
2275
2449
|
getVoiceConfig,
|
|
2276
2450
|
getWhatsAppPhrase,
|
|
2451
|
+
handleFallback,
|
|
2452
|
+
handleStatusCallback,
|
|
2277
2453
|
incrementNoSpeechRetries,
|
|
2278
2454
|
initDbClient,
|
|
2279
2455
|
inputSanitizeMiddleware,
|
|
@@ -2304,6 +2480,7 @@ async function createStandaloneServer(config) {
|
|
|
2304
2480
|
transferTwiml,
|
|
2305
2481
|
twilioSignatureMiddleware,
|
|
2306
2482
|
updateFlowParams,
|
|
2483
|
+
upsertMessageStatus,
|
|
2307
2484
|
validateTwilioSignature,
|
|
2308
2485
|
whatsappRoutes
|
|
2309
2486
|
});
|
package/dist/index.mjs
CHANGED
|
@@ -237,7 +237,7 @@ var SCHEMA_STATEMENTS = [
|
|
|
237
237
|
`CREATE TABLE IF NOT EXISTS talker_sessions (
|
|
238
238
|
id TEXT PRIMARY KEY,
|
|
239
239
|
phone_number TEXT NOT NULL,
|
|
240
|
-
channel TEXT NOT NULL CHECK(channel IN ('call', 'sms')),
|
|
240
|
+
channel TEXT NOT NULL CHECK(channel IN ('call', 'sms', 'whatsapp')),
|
|
241
241
|
reason TEXT NOT NULL CHECK(reason IN ('ended', 'redirected')),
|
|
242
242
|
language TEXT NOT NULL,
|
|
243
243
|
started_at INTEGER NOT NULL,
|
|
@@ -257,10 +257,23 @@ var SCHEMA_STATEMENTS = [
|
|
|
257
257
|
created_at INTEGER NOT NULL DEFAULT (unixepoch()),
|
|
258
258
|
FOREIGN KEY (session_id) REFERENCES talker_sessions(id) ON DELETE CASCADE
|
|
259
259
|
)`,
|
|
260
|
+
`CREATE TABLE IF NOT EXISTS talker_message_status (
|
|
261
|
+
message_sid TEXT PRIMARY KEY,
|
|
262
|
+
channel TEXT NOT NULL CHECK(channel IN ('sms', 'whatsapp')),
|
|
263
|
+
phone_from TEXT NOT NULL,
|
|
264
|
+
phone_to TEXT NOT NULL,
|
|
265
|
+
status TEXT NOT NULL,
|
|
266
|
+
error_code TEXT,
|
|
267
|
+
error_message TEXT,
|
|
268
|
+
created_at INTEGER NOT NULL DEFAULT (unixepoch()),
|
|
269
|
+
updated_at INTEGER NOT NULL DEFAULT (unixepoch())
|
|
270
|
+
)`,
|
|
260
271
|
"CREATE INDEX IF NOT EXISTS idx_talker_sessions_phone ON talker_sessions(phone_number)",
|
|
261
272
|
"CREATE INDEX IF NOT EXISTS idx_talker_sessions_created ON talker_sessions(created_at DESC)",
|
|
262
273
|
"CREATE INDEX IF NOT EXISTS idx_talker_messages_session ON talker_messages(session_id)",
|
|
263
|
-
"CREATE INDEX IF NOT EXISTS idx_talker_messages_ts ON talker_messages(timestamp)"
|
|
274
|
+
"CREATE INDEX IF NOT EXISTS idx_talker_messages_ts ON talker_messages(timestamp)",
|
|
275
|
+
"CREATE INDEX IF NOT EXISTS idx_talker_message_status_phone ON talker_message_status(phone_to)",
|
|
276
|
+
"CREATE INDEX IF NOT EXISTS idx_talker_message_status_created ON talker_message_status(created_at DESC)"
|
|
264
277
|
];
|
|
265
278
|
async function runMigrations() {
|
|
266
279
|
const client2 = getDbClient();
|
|
@@ -1011,6 +1024,47 @@ async function insertMessage(message) {
|
|
|
1011
1024
|
return false;
|
|
1012
1025
|
}
|
|
1013
1026
|
}
|
|
1027
|
+
async function upsertMessageStatus(record) {
|
|
1028
|
+
const client2 = getDbClient();
|
|
1029
|
+
if (!client2) return false;
|
|
1030
|
+
try {
|
|
1031
|
+
await client2.execute({
|
|
1032
|
+
sql: `
|
|
1033
|
+
INSERT INTO talker_message_status (
|
|
1034
|
+
message_sid, channel, phone_from, phone_to, status,
|
|
1035
|
+
error_code, error_message, updated_at
|
|
1036
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
1037
|
+
ON CONFLICT(message_sid) DO UPDATE SET
|
|
1038
|
+
status = excluded.status,
|
|
1039
|
+
error_code = excluded.error_code,
|
|
1040
|
+
error_message = excluded.error_message,
|
|
1041
|
+
updated_at = excluded.updated_at
|
|
1042
|
+
`,
|
|
1043
|
+
args: [
|
|
1044
|
+
record.messageSid,
|
|
1045
|
+
record.channel,
|
|
1046
|
+
record.from,
|
|
1047
|
+
record.to,
|
|
1048
|
+
record.status,
|
|
1049
|
+
record.errorCode || null,
|
|
1050
|
+
record.errorMessage || null,
|
|
1051
|
+
Date.now()
|
|
1052
|
+
]
|
|
1053
|
+
});
|
|
1054
|
+
logger.info("message status upserted", {
|
|
1055
|
+
messageSid: record.messageSid,
|
|
1056
|
+
channel: record.channel,
|
|
1057
|
+
status: record.status
|
|
1058
|
+
});
|
|
1059
|
+
return true;
|
|
1060
|
+
} catch (error) {
|
|
1061
|
+
logger.error("failed to upsert message status", {
|
|
1062
|
+
messageSid: record.messageSid,
|
|
1063
|
+
error: error instanceof Error ? error.message : "Unknown"
|
|
1064
|
+
});
|
|
1065
|
+
return false;
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1014
1068
|
|
|
1015
1069
|
// src/db/persist.ts
|
|
1016
1070
|
function persistSession(phoneNumber, channel, conversationId) {
|
|
@@ -1844,6 +1898,194 @@ function callRoutes(deps, registry) {
|
|
|
1844
1898
|
// src/routes/sms/index.ts
|
|
1845
1899
|
import { Hono as Hono2 } from "hono";
|
|
1846
1900
|
|
|
1901
|
+
// src/adapters/twilio.ts
|
|
1902
|
+
function stripWhatsAppPrefix(phoneNumber) {
|
|
1903
|
+
return phoneNumber.replace(/^whatsapp:/, "");
|
|
1904
|
+
}
|
|
1905
|
+
function buildMessageParams(config, to, message, options) {
|
|
1906
|
+
const params = {
|
|
1907
|
+
To: to,
|
|
1908
|
+
Body: message
|
|
1909
|
+
};
|
|
1910
|
+
if (config.messagingServiceSid) {
|
|
1911
|
+
params.MessagingServiceSid = config.messagingServiceSid;
|
|
1912
|
+
} else if (config.phoneNumber) {
|
|
1913
|
+
params.From = config.phoneNumber;
|
|
1914
|
+
}
|
|
1915
|
+
if (options?.statusCallback) {
|
|
1916
|
+
params.StatusCallback = options.statusCallback;
|
|
1917
|
+
}
|
|
1918
|
+
return new URLSearchParams(params);
|
|
1919
|
+
}
|
|
1920
|
+
async function sendSMS(config, to, message, options) {
|
|
1921
|
+
if (!config.accountSid || !config.authToken) {
|
|
1922
|
+
logger.warn("Twilio credentials not configured, skipping SMS send", { to });
|
|
1923
|
+
return false;
|
|
1924
|
+
}
|
|
1925
|
+
if (!config.phoneNumber && !config.messagingServiceSid) {
|
|
1926
|
+
logger.warn("No phone number or messaging service configured, skipping SMS send", { to });
|
|
1927
|
+
return false;
|
|
1928
|
+
}
|
|
1929
|
+
try {
|
|
1930
|
+
const auth = Buffer.from(`${config.accountSid}:${config.authToken}`).toString("base64");
|
|
1931
|
+
const response = await fetch(
|
|
1932
|
+
`https://api.twilio.com/2010-04-01/Accounts/${config.accountSid}/Messages.json`,
|
|
1933
|
+
{
|
|
1934
|
+
method: "POST",
|
|
1935
|
+
headers: {
|
|
1936
|
+
Authorization: `Basic ${auth}`,
|
|
1937
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
1938
|
+
},
|
|
1939
|
+
body: buildMessageParams(config, to, message, options)
|
|
1940
|
+
}
|
|
1941
|
+
);
|
|
1942
|
+
if (!response.ok) {
|
|
1943
|
+
const error = await response.text();
|
|
1944
|
+
logger.error("SMS send failed", { to, status: response.status, error });
|
|
1945
|
+
return false;
|
|
1946
|
+
}
|
|
1947
|
+
const data = await response.json();
|
|
1948
|
+
logger.info("SMS sent successfully", { to, messageSid: data.sid });
|
|
1949
|
+
return true;
|
|
1950
|
+
} catch (error) {
|
|
1951
|
+
logger.error("SMS send error", {
|
|
1952
|
+
to,
|
|
1953
|
+
error: error instanceof Error ? error.message : "Unknown"
|
|
1954
|
+
});
|
|
1955
|
+
return false;
|
|
1956
|
+
}
|
|
1957
|
+
}
|
|
1958
|
+
async function sendWhatsApp(config, to, message, options) {
|
|
1959
|
+
if (!config.accountSid || !config.authToken) {
|
|
1960
|
+
logger.warn("Twilio credentials not configured, skipping WhatsApp send", { to });
|
|
1961
|
+
return false;
|
|
1962
|
+
}
|
|
1963
|
+
if (!config.phoneNumber && !config.messagingServiceSid) {
|
|
1964
|
+
logger.warn("No phone number or messaging service configured, skipping WhatsApp send", { to });
|
|
1965
|
+
return false;
|
|
1966
|
+
}
|
|
1967
|
+
try {
|
|
1968
|
+
const auth = Buffer.from(`${config.accountSid}:${config.authToken}`).toString("base64");
|
|
1969
|
+
const whatsappTo = to.startsWith("whatsapp:") ? to : `whatsapp:${to}`;
|
|
1970
|
+
const params = {
|
|
1971
|
+
To: whatsappTo,
|
|
1972
|
+
Body: message
|
|
1973
|
+
};
|
|
1974
|
+
if (config.messagingServiceSid) {
|
|
1975
|
+
params.MessagingServiceSid = config.messagingServiceSid;
|
|
1976
|
+
} else if (config.phoneNumber) {
|
|
1977
|
+
const whatsappFrom = config.phoneNumber.startsWith("whatsapp:") ? config.phoneNumber : `whatsapp:${config.phoneNumber}`;
|
|
1978
|
+
params.From = whatsappFrom;
|
|
1979
|
+
}
|
|
1980
|
+
if (options?.statusCallback) {
|
|
1981
|
+
params.StatusCallback = options.statusCallback;
|
|
1982
|
+
}
|
|
1983
|
+
const response = await fetch(
|
|
1984
|
+
`https://api.twilio.com/2010-04-01/Accounts/${config.accountSid}/Messages.json`,
|
|
1985
|
+
{
|
|
1986
|
+
method: "POST",
|
|
1987
|
+
headers: {
|
|
1988
|
+
Authorization: `Basic ${auth}`,
|
|
1989
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
1990
|
+
},
|
|
1991
|
+
body: new URLSearchParams(params)
|
|
1992
|
+
}
|
|
1993
|
+
);
|
|
1994
|
+
if (!response.ok) {
|
|
1995
|
+
const error = await response.text();
|
|
1996
|
+
logger.error("WhatsApp send failed", { to, status: response.status, error });
|
|
1997
|
+
return false;
|
|
1998
|
+
}
|
|
1999
|
+
const data = await response.json();
|
|
2000
|
+
logger.info("WhatsApp message sent successfully", { to, messageSid: data.sid });
|
|
2001
|
+
return true;
|
|
2002
|
+
} catch (error) {
|
|
2003
|
+
logger.error("WhatsApp send error", {
|
|
2004
|
+
to,
|
|
2005
|
+
error: error instanceof Error ? error.message : "Unknown"
|
|
2006
|
+
});
|
|
2007
|
+
return false;
|
|
2008
|
+
}
|
|
2009
|
+
}
|
|
2010
|
+
|
|
2011
|
+
// src/routes/shared/handle-fallback.ts
|
|
2012
|
+
async function handleFallback(c, deps, channel) {
|
|
2013
|
+
const body = await c.req.parseBody();
|
|
2014
|
+
const rawFrom = body.From || "unknown";
|
|
2015
|
+
const phoneNumber = channel === "whatsapp" ? stripWhatsAppPrefix(rawFrom) : rawFrom.trim();
|
|
2016
|
+
const messageBody = body.Body || "";
|
|
2017
|
+
const errorCode = body.ErrorCode;
|
|
2018
|
+
const errorUrl = body.ErrorUrl;
|
|
2019
|
+
logger.error("fallback webhook triggered", {
|
|
2020
|
+
channel,
|
|
2021
|
+
phoneNumber,
|
|
2022
|
+
messageBody: messageBody.substring(0, 100),
|
|
2023
|
+
errorCode,
|
|
2024
|
+
errorUrl
|
|
2025
|
+
});
|
|
2026
|
+
const errorMessage = channel === "whatsapp" ? getWhatsAppPhrase("en", "genericError", deps.config.languageDir) : getSmsPhrase("en", "genericError", deps.config.languageDir);
|
|
2027
|
+
return c.text(messageTwiml(errorMessage), 200, { "Content-Type": "text/xml" });
|
|
2028
|
+
}
|
|
2029
|
+
|
|
2030
|
+
// src/routes/shared/handle-status-callback.ts
|
|
2031
|
+
async function handleStatusCallback(c, deps, channel) {
|
|
2032
|
+
const body = await c.req.parseBody();
|
|
2033
|
+
const messageSid = body.MessageSid || "";
|
|
2034
|
+
const messageStatus = body.MessageStatus || "";
|
|
2035
|
+
const rawFrom = body.From || "";
|
|
2036
|
+
const rawTo = body.To || "";
|
|
2037
|
+
const errorCode = body.ErrorCode;
|
|
2038
|
+
const errorMessage = body.ErrorMessage;
|
|
2039
|
+
const from = channel === "whatsapp" ? stripWhatsAppPrefix(rawFrom) : rawFrom;
|
|
2040
|
+
const to = channel === "whatsapp" ? stripWhatsAppPrefix(rawTo) : rawTo;
|
|
2041
|
+
logger.info("message status callback", {
|
|
2042
|
+
channel,
|
|
2043
|
+
messageSid,
|
|
2044
|
+
messageStatus,
|
|
2045
|
+
from,
|
|
2046
|
+
to,
|
|
2047
|
+
errorCode
|
|
2048
|
+
});
|
|
2049
|
+
if (!messageSid || !messageStatus) {
|
|
2050
|
+
logger.warn("status callback missing required fields", { channel, messageSid, messageStatus });
|
|
2051
|
+
return c.text("", 200);
|
|
2052
|
+
}
|
|
2053
|
+
upsertMessageStatus({
|
|
2054
|
+
messageSid,
|
|
2055
|
+
channel,
|
|
2056
|
+
from,
|
|
2057
|
+
to,
|
|
2058
|
+
status: messageStatus,
|
|
2059
|
+
errorCode,
|
|
2060
|
+
errorMessage
|
|
2061
|
+
}).catch((err) => {
|
|
2062
|
+
logger.error("failed to persist message status", {
|
|
2063
|
+
messageSid,
|
|
2064
|
+
error: getErrorMessage(err)
|
|
2065
|
+
});
|
|
2066
|
+
});
|
|
2067
|
+
if (deps.config.onMessageStatus) {
|
|
2068
|
+
const event = {
|
|
2069
|
+
messageSid,
|
|
2070
|
+
messageStatus,
|
|
2071
|
+
channel,
|
|
2072
|
+
from,
|
|
2073
|
+
to,
|
|
2074
|
+
errorCode,
|
|
2075
|
+
errorMessage
|
|
2076
|
+
};
|
|
2077
|
+
try {
|
|
2078
|
+
await Promise.resolve(deps.config.onMessageStatus(event));
|
|
2079
|
+
} catch (err) {
|
|
2080
|
+
logger.error("onMessageStatus callback error", {
|
|
2081
|
+
messageSid,
|
|
2082
|
+
error: getErrorMessage(err)
|
|
2083
|
+
});
|
|
2084
|
+
}
|
|
2085
|
+
}
|
|
2086
|
+
return c.text("", 200);
|
|
2087
|
+
}
|
|
2088
|
+
|
|
1847
2089
|
// src/routes/sms/processor.ts
|
|
1848
2090
|
async function processSms(deps, registry, phoneNumber, messageBody) {
|
|
1849
2091
|
const incoming = await processIncoming(deps, phoneNumber, messageBody, "sms");
|
|
@@ -1908,7 +2150,13 @@ function smsRoutes(deps, registry) {
|
|
|
1908
2150
|
app.post("/sms", twilioSignatureMiddleware(authToken, baseUrl));
|
|
1909
2151
|
app.post("/sms", rateLimitMiddleware(deps.config.rateLimit));
|
|
1910
2152
|
app.post("/sms", inputSanitizeMiddleware(deps.config.maxInputLength));
|
|
2153
|
+
app.post("/sms/fallback", twilioSignatureMiddleware(authToken, baseUrl));
|
|
2154
|
+
app.post("/sms/fallback", rateLimitMiddleware(deps.config.rateLimit));
|
|
2155
|
+
app.post("/sms/fallback", inputSanitizeMiddleware(deps.config.maxInputLength));
|
|
2156
|
+
app.post("/sms/status", twilioSignatureMiddleware(authToken, baseUrl));
|
|
1911
2157
|
app.post("/sms", (c) => handleIncomingSMS(c, deps, registry));
|
|
2158
|
+
app.post("/sms/fallback", (c) => handleFallback(c, deps, "sms"));
|
|
2159
|
+
app.post("/sms/status", (c) => handleStatusCallback(c, deps, "sms"));
|
|
1912
2160
|
app.get("/sms", (c) => c.text("SMS endpoint active"));
|
|
1913
2161
|
return app;
|
|
1914
2162
|
}
|
|
@@ -1916,89 +2164,6 @@ function smsRoutes(deps, registry) {
|
|
|
1916
2164
|
// src/routes/whatsapp/index.ts
|
|
1917
2165
|
import { Hono as Hono3 } from "hono";
|
|
1918
2166
|
|
|
1919
|
-
// src/adapters/twilio.ts
|
|
1920
|
-
function stripWhatsAppPrefix(phoneNumber) {
|
|
1921
|
-
return phoneNumber.replace(/^whatsapp:/, "");
|
|
1922
|
-
}
|
|
1923
|
-
async function sendSMS(config, to, message) {
|
|
1924
|
-
if (!config.accountSid || !config.authToken || !config.phoneNumber) {
|
|
1925
|
-
logger.warn("Twilio credentials not configured, skipping SMS send", { to });
|
|
1926
|
-
return false;
|
|
1927
|
-
}
|
|
1928
|
-
try {
|
|
1929
|
-
const auth = Buffer.from(`${config.accountSid}:${config.authToken}`).toString("base64");
|
|
1930
|
-
const response = await fetch(
|
|
1931
|
-
`https://api.twilio.com/2010-04-01/Accounts/${config.accountSid}/Messages.json`,
|
|
1932
|
-
{
|
|
1933
|
-
method: "POST",
|
|
1934
|
-
headers: {
|
|
1935
|
-
Authorization: `Basic ${auth}`,
|
|
1936
|
-
"Content-Type": "application/x-www-form-urlencoded"
|
|
1937
|
-
},
|
|
1938
|
-
body: new URLSearchParams({
|
|
1939
|
-
From: config.phoneNumber,
|
|
1940
|
-
To: to,
|
|
1941
|
-
Body: message
|
|
1942
|
-
})
|
|
1943
|
-
}
|
|
1944
|
-
);
|
|
1945
|
-
if (!response.ok) {
|
|
1946
|
-
const error = await response.text();
|
|
1947
|
-
logger.error("SMS send failed", { to, status: response.status, error });
|
|
1948
|
-
return false;
|
|
1949
|
-
}
|
|
1950
|
-
const data = await response.json();
|
|
1951
|
-
logger.info("SMS sent successfully", { to, messageSid: data.sid });
|
|
1952
|
-
return true;
|
|
1953
|
-
} catch (error) {
|
|
1954
|
-
logger.error("SMS send error", {
|
|
1955
|
-
to,
|
|
1956
|
-
error: error instanceof Error ? error.message : "Unknown"
|
|
1957
|
-
});
|
|
1958
|
-
return false;
|
|
1959
|
-
}
|
|
1960
|
-
}
|
|
1961
|
-
async function sendWhatsApp(config, to, message) {
|
|
1962
|
-
if (!config.accountSid || !config.authToken || !config.phoneNumber) {
|
|
1963
|
-
logger.warn("Twilio credentials not configured, skipping WhatsApp send", { to });
|
|
1964
|
-
return false;
|
|
1965
|
-
}
|
|
1966
|
-
try {
|
|
1967
|
-
const auth = Buffer.from(`${config.accountSid}:${config.authToken}`).toString("base64");
|
|
1968
|
-
const whatsappTo = to.startsWith("whatsapp:") ? to : `whatsapp:${to}`;
|
|
1969
|
-
const whatsappFrom = config.phoneNumber.startsWith("whatsapp:") ? config.phoneNumber : `whatsapp:${config.phoneNumber}`;
|
|
1970
|
-
const response = await fetch(
|
|
1971
|
-
`https://api.twilio.com/2010-04-01/Accounts/${config.accountSid}/Messages.json`,
|
|
1972
|
-
{
|
|
1973
|
-
method: "POST",
|
|
1974
|
-
headers: {
|
|
1975
|
-
Authorization: `Basic ${auth}`,
|
|
1976
|
-
"Content-Type": "application/x-www-form-urlencoded"
|
|
1977
|
-
},
|
|
1978
|
-
body: new URLSearchParams({
|
|
1979
|
-
From: whatsappFrom,
|
|
1980
|
-
To: whatsappTo,
|
|
1981
|
-
Body: message
|
|
1982
|
-
})
|
|
1983
|
-
}
|
|
1984
|
-
);
|
|
1985
|
-
if (!response.ok) {
|
|
1986
|
-
const error = await response.text();
|
|
1987
|
-
logger.error("WhatsApp send failed", { to, status: response.status, error });
|
|
1988
|
-
return false;
|
|
1989
|
-
}
|
|
1990
|
-
const data = await response.json();
|
|
1991
|
-
logger.info("WhatsApp message sent successfully", { to, messageSid: data.sid });
|
|
1992
|
-
return true;
|
|
1993
|
-
} catch (error) {
|
|
1994
|
-
logger.error("WhatsApp send error", {
|
|
1995
|
-
to,
|
|
1996
|
-
error: error instanceof Error ? error.message : "Unknown"
|
|
1997
|
-
});
|
|
1998
|
-
return false;
|
|
1999
|
-
}
|
|
2000
|
-
}
|
|
2001
|
-
|
|
2002
2167
|
// src/routes/whatsapp/processor.ts
|
|
2003
2168
|
async function processWhatsApp(deps, registry, phoneNumber, messageBody) {
|
|
2004
2169
|
const incoming = await processIncoming(deps, phoneNumber, messageBody, "whatsapp");
|
|
@@ -2070,7 +2235,13 @@ function whatsappRoutes(deps, registry) {
|
|
|
2070
2235
|
app.post("/whatsapp", twilioSignatureMiddleware(authToken, baseUrl));
|
|
2071
2236
|
app.post("/whatsapp", rateLimitMiddleware(deps.config.rateLimit));
|
|
2072
2237
|
app.post("/whatsapp", inputSanitizeMiddleware(deps.config.maxInputLength));
|
|
2238
|
+
app.post("/whatsapp/fallback", twilioSignatureMiddleware(authToken, baseUrl));
|
|
2239
|
+
app.post("/whatsapp/fallback", rateLimitMiddleware(deps.config.rateLimit));
|
|
2240
|
+
app.post("/whatsapp/fallback", inputSanitizeMiddleware(deps.config.maxInputLength));
|
|
2241
|
+
app.post("/whatsapp/status", twilioSignatureMiddleware(authToken, baseUrl));
|
|
2073
2242
|
app.post("/whatsapp", (c) => handleIncomingWhatsApp(c, deps, registry));
|
|
2243
|
+
app.post("/whatsapp/fallback", (c) => handleFallback(c, deps, "whatsapp"));
|
|
2244
|
+
app.post("/whatsapp/status", (c) => handleStatusCallback(c, deps, "whatsapp"));
|
|
2074
2245
|
app.get("/whatsapp", (c) => c.text("WhatsApp endpoint active"));
|
|
2075
2246
|
return app;
|
|
2076
2247
|
}
|
|
@@ -2196,6 +2367,8 @@ export {
|
|
|
2196
2367
|
getSmsPhrase,
|
|
2197
2368
|
getVoiceConfig,
|
|
2198
2369
|
getWhatsAppPhrase,
|
|
2370
|
+
handleFallback,
|
|
2371
|
+
handleStatusCallback,
|
|
2199
2372
|
incrementNoSpeechRetries,
|
|
2200
2373
|
initDbClient,
|
|
2201
2374
|
inputSanitizeMiddleware,
|
|
@@ -2226,6 +2399,7 @@ export {
|
|
|
2226
2399
|
transferTwiml,
|
|
2227
2400
|
twilioSignatureMiddleware,
|
|
2228
2401
|
updateFlowParams,
|
|
2402
|
+
upsertMessageStatus,
|
|
2229
2403
|
validateTwilioSignature,
|
|
2230
2404
|
whatsappRoutes
|
|
2231
2405
|
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared Fallback Handler
|
|
3
|
+
*
|
|
4
|
+
* Handles POST /sms/fallback and /whatsapp/fallback — called by Twilio
|
|
5
|
+
* when the primary incoming message webhook URL cannot be reached or
|
|
6
|
+
* throws a runtime exception.
|
|
7
|
+
*
|
|
8
|
+
* Twilio sends the same payload as the incoming message webhook.
|
|
9
|
+
* The fallback handler responds with a safe error message so the
|
|
10
|
+
* end user is not left without a response.
|
|
11
|
+
*/
|
|
12
|
+
import type { Context } from "hono";
|
|
13
|
+
import type { TalkerDependencies } from "../../types";
|
|
14
|
+
/**
|
|
15
|
+
* Handle a fallback webhook from Twilio.
|
|
16
|
+
* Called when the primary incoming URL fails.
|
|
17
|
+
* Returns a safe error message TwiML to the user.
|
|
18
|
+
*/
|
|
19
|
+
export declare function handleFallback(c: Context, deps: TalkerDependencies, channel: "sms" | "whatsapp"): Promise<Response>;
|
|
20
|
+
//# sourceMappingURL=handle-fallback.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"handle-fallback.d.ts","sourceRoot":"","sources":["../../../src/routes/shared/handle-fallback.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAKpC,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAEtD;;;;GAIG;AACH,wBAAsB,cAAc,CAClC,CAAC,EAAE,OAAO,EACV,IAAI,EAAE,kBAAkB,EACxB,OAAO,EAAE,KAAK,GAAG,UAAU,GAC1B,OAAO,CAAC,QAAQ,CAAC,CAwBnB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"handle-fallback.test.d.ts","sourceRoot":"","sources":["../../../src/routes/shared/handle-fallback.test.ts"],"names":[],"mappings":"AAAA;;;;GAIG"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared Message Status Callback Handler
|
|
3
|
+
*
|
|
4
|
+
* Handles POST /sms/status and /whatsapp/status — called by Twilio
|
|
5
|
+
* when a message delivery status changes (queued, sent, delivered,
|
|
6
|
+
* undelivered, failed, read).
|
|
7
|
+
*
|
|
8
|
+
* Twilio sends these fields (among others):
|
|
9
|
+
* - MessageSid: unique message identifier
|
|
10
|
+
* - MessageStatus: queued | sending | sent | delivered | undelivered | failed | read
|
|
11
|
+
* - From: sender phone number
|
|
12
|
+
* - To: recipient phone number
|
|
13
|
+
* - ErrorCode: Twilio error code (only on failure)
|
|
14
|
+
* - ErrorMessage: human-readable error (only on failure)
|
|
15
|
+
*/
|
|
16
|
+
import type { Context } from "hono";
|
|
17
|
+
import type { TalkerDependencies } from "../../types";
|
|
18
|
+
/**
|
|
19
|
+
* Handle a message status callback from Twilio.
|
|
20
|
+
* Shared by both SMS and WhatsApp status endpoints.
|
|
21
|
+
*/
|
|
22
|
+
export declare function handleStatusCallback(c: Context, deps: TalkerDependencies, channel: "sms" | "whatsapp"): Promise<Response>;
|
|
23
|
+
//# sourceMappingURL=handle-status-callback.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"handle-status-callback.d.ts","sourceRoot":"","sources":["../../../src/routes/shared/handle-status-callback.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAKpC,OAAO,KAAK,EAA+B,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAEnF;;;GAGG;AACH,wBAAsB,oBAAoB,CACxC,CAAC,EAAE,OAAO,EACV,IAAI,EAAE,kBAAkB,EACxB,OAAO,EAAE,KAAK,GAAG,UAAU,GAC1B,OAAO,CAAC,QAAQ,CAAC,CAoEnB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"handle-status-callback.test.d.ts","sourceRoot":"","sources":["../../../src/routes/shared/handle-status-callback.test.ts"],"names":[],"mappings":"AAAA;;;;GAIG"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/routes/shared/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AAChE,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC"}
|
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
* SMS Routes
|
|
3
3
|
*
|
|
4
4
|
* Hono route factory for SMS webhooks.
|
|
5
|
+
*
|
|
6
|
+
* Endpoints:
|
|
7
|
+
* - POST /sms — Incoming message webhook
|
|
8
|
+
* - POST /sms/fallback — Fallback when incoming URL fails
|
|
9
|
+
* - POST /sms/status — Message delivery status callback
|
|
10
|
+
* - GET /sms — Health check
|
|
5
11
|
*/
|
|
6
12
|
import { Hono } from "hono";
|
|
7
13
|
import type { FlowRegistry } from "../../flows/registry";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/routes/sms/index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/routes/sms/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAIzD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAKtD;;GAEG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,kBAAkB,EAAE,QAAQ,EAAE,YAAY,8EA6BzE"}
|
|
@@ -3,6 +3,12 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Hono route factory for WhatsApp webhooks.
|
|
5
5
|
* Twilio uses the same TwiML response format for WhatsApp as it does for SMS.
|
|
6
|
+
*
|
|
7
|
+
* Endpoints:
|
|
8
|
+
* - POST /whatsapp — Incoming message webhook
|
|
9
|
+
* - POST /whatsapp/fallback — Fallback when incoming URL fails
|
|
10
|
+
* - POST /whatsapp/status — Message delivery status callback
|
|
11
|
+
* - GET /whatsapp — Health check
|
|
6
12
|
*/
|
|
7
13
|
import { Hono } from "hono";
|
|
8
14
|
import type { FlowRegistry } from "../../flows/registry";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/routes/whatsapp/index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/routes/whatsapp/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAIzD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAKtD;;GAEG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,kBAAkB,EAAE,QAAQ,EAAE,YAAY,8EA6B9E"}
|
package/dist/types.d.ts
CHANGED
|
@@ -26,8 +26,34 @@ export interface TwilioConfig {
|
|
|
26
26
|
accountSid?: string;
|
|
27
27
|
/** Twilio auth token */
|
|
28
28
|
authToken?: string;
|
|
29
|
-
/** Twilio phone number for outbound SMS */
|
|
29
|
+
/** Twilio phone number for outbound SMS/WhatsApp */
|
|
30
30
|
phoneNumber?: string;
|
|
31
|
+
/**
|
|
32
|
+
* Twilio Messaging Service SID.
|
|
33
|
+
* When set, outbound messages use MessagingServiceSid instead of From.
|
|
34
|
+
* This enables features like sender pool, sticky sender, and compliance.
|
|
35
|
+
*/
|
|
36
|
+
messagingServiceSid?: string;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Status callback event from Twilio.
|
|
40
|
+
* Received at /sms/status and /whatsapp/status endpoints.
|
|
41
|
+
*/
|
|
42
|
+
export interface MessageStatusEvent {
|
|
43
|
+
/** Twilio message SID */
|
|
44
|
+
messageSid: string;
|
|
45
|
+
/** Message status: queued, sent, delivered, undelivered, failed, read */
|
|
46
|
+
messageStatus: string;
|
|
47
|
+
/** Channel the message was sent on */
|
|
48
|
+
channel: "sms" | "whatsapp";
|
|
49
|
+
/** Sender phone number */
|
|
50
|
+
from: string;
|
|
51
|
+
/** Recipient phone number */
|
|
52
|
+
to: string;
|
|
53
|
+
/** Twilio error code (if failed/undelivered) */
|
|
54
|
+
errorCode?: string;
|
|
55
|
+
/** Twilio error message (if failed/undelivered) */
|
|
56
|
+
errorMessage?: string;
|
|
31
57
|
}
|
|
32
58
|
/**
|
|
33
59
|
* Remote chatbot API configuration (standalone mode)
|
|
@@ -116,6 +142,11 @@ export interface TalkerConfig {
|
|
|
116
142
|
maxInputLength?: number;
|
|
117
143
|
/** Chat function override. By default, talker queries chatter's RAG pipeline directly */
|
|
118
144
|
chatFn?: (phoneNumber: string, message: string) => Promise<string>;
|
|
145
|
+
/**
|
|
146
|
+
* Callback invoked when a message delivery status update is received.
|
|
147
|
+
* Called for both SMS and WhatsApp status callbacks.
|
|
148
|
+
*/
|
|
149
|
+
onMessageStatus?: (event: MessageStatusEvent) => void | Promise<void>;
|
|
119
150
|
}
|
|
120
151
|
/**
|
|
121
152
|
* Dependencies available to talker routes and handlers
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAIlE;;GAEG;AACH,MAAM,MAAM,OAAO,GAAG,MAAM,GAAG,KAAK,GAAG,UAAU,CAAC;AAElD;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,iDAAiD;IACjD,KAAK,EAAE,MAAM,CAAC;IACd,0CAA0C;IAC1C,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,yBAAyB;IACzB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,wBAAwB;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|