@absolutejs/voice 0.0.22-beta.271 → 0.0.22-beta.273

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/index.d.ts CHANGED
@@ -159,13 +159,15 @@ export type { VoiceS3ReviewStoreClient, VoiceS3ReviewStoreFile, VoiceS3ReviewSto
159
159
  export type { VoiceSQLiteRuntimeStorage, VoiceSQLiteStoreOptions } from './sqliteStore';
160
160
  export type { StoredVoiceIntegrationEvent, StoredVoiceExternalObjectMap, StoredVoiceOpsTask, VoiceExternalObjectMap, VoiceExternalObjectMapStore, VoiceOpsTaskAgeBucket, VoiceOpsTaskAnalyticsOptions, VoiceOpsTaskAnalyticsSummary, VoiceOpsTaskAssignmentRule, VoiceOpsTaskAssignmentRuleCondition, VoiceOpsTaskAssignmentRules, VoiceOpsTaskAssigneeAnalytics, VoiceOpsDispositionTaskPolicies, VoiceOpsSLABreachPolicy, VoiceIntegrationDeliveryStatus, VoiceIntegrationEvent, VoiceIntegrationEventStore, VoiceIntegrationSinkDelivery, VoiceIntegrationEventType, VoiceIntegrationWebhookConfig, VoiceOpsTask, VoiceOpsTaskHistoryEntry, VoiceOpsTaskKind, VoiceOpsTaskPolicy, VoiceOpsTaskPriority, VoiceOpsTaskStatus, VoiceOpsTaskStore, VoiceOpsTaskSummary, VoiceOpsTaskWorkerAnalytics } from './ops';
161
161
  export { createTwilioMediaStreamBridge, createTwilioVoiceRoutes, createTwilioVoiceResponse, decodeTwilioMulawBase64, encodeTwilioMulawBase64, transcodePCMToTwilioOutboundPayload, transcodeTwilioInboundPayloadToPCM16 } from './telephony/twilio';
162
+ export { createVoiceTelephonyWebhookSecurityPreset } from './telephony/security';
162
163
  export { evaluateVoiceTelephonyContract } from './telephony/contract';
163
- export { createTelnyxMediaStreamBridge, createTelnyxVoiceResponse, createTelnyxVoiceRoutes, verifyVoiceTelnyxWebhookSignature } from './telephony/telnyx';
164
+ export { createMemoryVoiceTelnyxWebhookEventStore, createTelnyxMediaStreamBridge, createTelnyxVoiceResponse, createTelnyxVoiceRoutes, createVoicePostgresTelnyxWebhookEventStore, createVoiceRedisTelnyxWebhookEventStore, createVoiceSQLiteTelnyxWebhookEventStore, createVoiceTelnyxWebhookVerifier, verifyVoiceTelnyxWebhookSignature } from './telephony/telnyx';
164
165
  export { createMemoryVoicePlivoWebhookNonceStore, createPlivoMediaStreamBridge, createPlivoVoiceResponse, createPlivoVoiceRoutes, createVoicePostgresPlivoWebhookNonceStore, createVoicePlivoWebhookVerifier, createVoiceRedisPlivoWebhookNonceStore, createVoiceSQLitePlivoWebhookNonceStore, signVoicePlivoWebhook, verifyVoicePlivoWebhookSignature } from './telephony/plivo';
165
166
  export { createVoiceTelephonyCarrierMatrix, createVoiceTelephonyCarrierMatrixRoutes, renderVoiceTelephonyCarrierMatrixHTML } from './telephony/matrix';
166
167
  export type { TwilioInboundMessage, TwilioMediaStreamBridge, TwilioMediaStreamBridgeOptions, TwilioMediaStreamSocket, TwilioOutboundClearMessage, TwilioOutboundMarkMessage, TwilioOutboundMediaMessage, TwilioOutboundMessage, TwilioVoiceRouteParameters, TwilioVoiceResponseOptions, TwilioVoiceSmokeCheck, TwilioVoiceSmokeOptions, TwilioVoiceSmokeReport, TwilioVoiceSetupOptions, TwilioVoiceSetupStatus, TwilioVoiceRoutesOptions } from './telephony/twilio';
168
+ export type { VoiceTelephonyWebhookSecurityOptions, VoiceTelephonyWebhookSecurityPreset, VoiceTelephonyWebhookSecurityStorePreset } from './telephony/security';
167
169
  export type { VoiceTelephonyContractIssue, VoiceTelephonyContractOptions, VoiceTelephonyContractReport, VoiceTelephonyContractRequirement, VoiceTelephonyProvider, VoiceTelephonySetupStatus, VoiceTelephonySmokeCheck, VoiceTelephonySmokeReport } from './telephony/contract';
168
- export type { TelnyxInboundMessage, TelnyxMediaPayload, TelnyxMediaStreamBridge, TelnyxMediaStreamBridgeOptions, TelnyxMediaStreamSocket, TelnyxOutboundClearMessage, TelnyxOutboundMarkMessage, TelnyxOutboundMediaMessage, TelnyxOutboundMessage, TelnyxVoiceResponseOptions, TelnyxVoiceRoutesOptions, TelnyxVoiceSetupOptions, TelnyxVoiceSetupStatus, TelnyxVoiceSmokeCheck, TelnyxVoiceSmokeOptions, TelnyxVoiceSmokeReport } from './telephony/telnyx';
170
+ export type { TelnyxInboundMessage, TelnyxMediaPayload, TelnyxMediaStreamBridge, TelnyxMediaStreamBridgeOptions, TelnyxMediaStreamSocket, TelnyxOutboundClearMessage, TelnyxOutboundMarkMessage, TelnyxOutboundMediaMessage, TelnyxOutboundMessage, TelnyxVoiceResponseOptions, TelnyxVoiceRoutesOptions, TelnyxVoiceSetupOptions, TelnyxVoiceSetupStatus, TelnyxVoiceSmokeCheck, TelnyxVoiceSmokeOptions, TelnyxVoiceSmokeReport, VoicePostgresTelnyxWebhookEventStoreOptions, VoiceRedisTelnyxWebhookEventClient, VoiceRedisTelnyxWebhookEventStoreOptions, VoiceSQLiteTelnyxWebhookEventStoreOptions, VoiceTelnyxWebhookEventStore, VoiceTelnyxWebhookEventStoreOptions, VoiceTelnyxWebhookVerifierOptions } from './telephony/telnyx';
169
171
  export type { PlivoInboundMessage, PlivoMediaStreamBridge, PlivoMediaStreamBridgeOptions, PlivoMediaStreamSocket, PlivoOutboundCheckpointMessage, PlivoOutboundClearAudioMessage, PlivoOutboundMessage, PlivoOutboundPlayAudioMessage, PlivoVoiceResponseOptions, PlivoVoiceRoutesOptions, PlivoVoiceSetupOptions, PlivoVoiceSetupStatus, PlivoVoiceSmokeCheck, PlivoVoiceSmokeOptions, PlivoVoiceSmokeReport, VoicePostgresPlivoWebhookNonceStoreOptions, VoicePlivoWebhookNonceStore, VoicePlivoWebhookNonceStoreOptions, VoicePlivoWebhookVerifierOptions, VoiceRedisPlivoWebhookNonceClient, VoiceRedisPlivoWebhookNonceStoreOptions, VoiceSQLitePlivoWebhookNonceStoreOptions } from './telephony/plivo';
170
172
  export type { VoiceTelephonyCarrierMatrix, VoiceTelephonyCarrierMatrixEntry, VoiceTelephonyCarrierMatrixInput, VoiceTelephonyCarrierMatrixOptions, VoiceTelephonyCarrierMatrixRoutesOptions, VoiceTelephonyCarrierMatrixStatus } from './telephony/matrix';
171
173
  export { shapeTelephonyAssistantText } from './telephony/response';
package/dist/index.js CHANGED
@@ -19549,6 +19549,7 @@ var createPlivoVoiceRoutes = (options = {}) => {
19549
19549
 
19550
19550
  // src/telephony/telnyx.ts
19551
19551
  import { Buffer as Buffer6 } from "buffer";
19552
+ import { Database as Database2 } from "bun:sqlite";
19552
19553
  import { Elysia as Elysia31 } from "elysia";
19553
19554
  var escapeXml4 = (value) => value.replaceAll("&", "&amp;").replaceAll('"', "&quot;").replaceAll("'", "&apos;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
19554
19555
  var escapeHtml30 = (value) => value.replaceAll("&", "&amp;").replaceAll('"', "&quot;").replaceAll("'", "&#39;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
@@ -19721,6 +19722,198 @@ var verifyVoiceTelnyxWebhookSignature = async (input) => {
19721
19722
  return { ok: false, reason: "invalid-signature" };
19722
19723
  }
19723
19724
  };
19725
+ var createMemoryVoiceTelnyxWebhookEventStore = () => {
19726
+ const eventIds = new Set;
19727
+ return {
19728
+ claim: (eventId) => {
19729
+ if (eventIds.has(eventId)) {
19730
+ return false;
19731
+ }
19732
+ eventIds.add(eventId);
19733
+ return true;
19734
+ },
19735
+ has: (eventId) => eventIds.has(eventId),
19736
+ set: (eventId) => {
19737
+ eventIds.add(eventId);
19738
+ }
19739
+ };
19740
+ };
19741
+ var normalizeTelnyxStoreIdentifierSegment = (value) => value.trim().replace(/[^a-zA-Z0-9_]+/g, "_").replace(/^_+|_+$/g, "") || "voice";
19742
+ var quoteTelnyxStoreIdentifier = (value) => `"${value.replace(/"/g, '""')}"`;
19743
+ var resolveTelnyxEventTableName = (input) => {
19744
+ if (input.tableName) {
19745
+ return normalizeTelnyxStoreIdentifierSegment(input.tableName);
19746
+ }
19747
+ return `${normalizeTelnyxStoreIdentifierSegment(input.tablePrefix ?? "voice")}_${normalizeTelnyxStoreIdentifierSegment(input.fallback)}`;
19748
+ };
19749
+ var getTelnyxEventExpiresAt = (ttlSeconds) => typeof ttlSeconds === "number" && ttlSeconds > 0 ? Date.now() + Math.ceil(ttlSeconds * 1000) : null;
19750
+ var createVoiceSQLiteTelnyxWebhookEventStore = (options) => {
19751
+ const database = options.database ?? new Database2(options.path ?? ":memory:", {
19752
+ create: true
19753
+ });
19754
+ const tableName = resolveTelnyxEventTableName({
19755
+ fallback: "telnyx_webhook_events",
19756
+ tableName: options.tableName,
19757
+ tablePrefix: options.tablePrefix
19758
+ });
19759
+ database.exec(`CREATE TABLE IF NOT EXISTS "${tableName}" (
19760
+ event_id TEXT PRIMARY KEY,
19761
+ created_at INTEGER NOT NULL,
19762
+ expires_at INTEGER
19763
+ )`);
19764
+ const pruneExpired = database.query(`DELETE FROM "${tableName}" WHERE expires_at IS NOT NULL AND expires_at <= ?1`);
19765
+ const select = database.query(`SELECT event_id FROM "${tableName}" WHERE event_id = ?1 AND (expires_at IS NULL OR expires_at > ?2) LIMIT 1`);
19766
+ const insert = database.query(`INSERT OR IGNORE INTO "${tableName}" (event_id, created_at, expires_at) VALUES (?1, ?2, ?3)`);
19767
+ const upsert = database.query(`INSERT INTO "${tableName}" (event_id, created_at, expires_at) VALUES (?1, ?2, ?3)
19768
+ ON CONFLICT(event_id) DO UPDATE SET expires_at = excluded.expires_at`);
19769
+ return {
19770
+ claim: (eventId) => {
19771
+ const now = Date.now();
19772
+ pruneExpired.run(now);
19773
+ const result = insert.run(eventId, now, getTelnyxEventExpiresAt(options.ttlSeconds));
19774
+ return result.changes > 0;
19775
+ },
19776
+ has: (eventId) => Boolean(select.get(eventId, Date.now())),
19777
+ set: (eventId) => {
19778
+ upsert.run(eventId, Date.now(), getTelnyxEventExpiresAt(options.ttlSeconds));
19779
+ }
19780
+ };
19781
+ };
19782
+ var createVoiceTelnyxPostgresClient = async (options) => {
19783
+ if (options.sql) {
19784
+ return options.sql;
19785
+ }
19786
+ if (!options.connectionString) {
19787
+ throw new Error("createVoicePostgresTelnyxWebhookEventStore requires either options.sql or options.connectionString.");
19788
+ }
19789
+ const sql = new Bun.SQL(options.connectionString);
19790
+ return {
19791
+ unsafe: sql.unsafe.bind(sql)
19792
+ };
19793
+ };
19794
+ var resolveTelnyxEventQualifiedTableName = (options) => {
19795
+ const schema = normalizeTelnyxStoreIdentifierSegment(options.schemaName ?? "public");
19796
+ const table = resolveTelnyxEventTableName({
19797
+ fallback: "telnyx_webhook_events",
19798
+ tableName: options.tableName,
19799
+ tablePrefix: options.tablePrefix
19800
+ });
19801
+ return `${quoteTelnyxStoreIdentifier(schema)}.${quoteTelnyxStoreIdentifier(table)}`;
19802
+ };
19803
+ var createVoicePostgresTelnyxWebhookEventStore = (options = {}) => {
19804
+ const qualifiedTableName = resolveTelnyxEventQualifiedTableName(options);
19805
+ const schemaMatch = qualifiedTableName.match(/^"([^"]+)"\./);
19806
+ const client = createVoiceTelnyxPostgresClient(options);
19807
+ const initialized = (async () => {
19808
+ const sql = await client;
19809
+ if (schemaMatch?.[1]) {
19810
+ await sql.unsafe(`CREATE SCHEMA IF NOT EXISTS ${quoteTelnyxStoreIdentifier(schemaMatch[1])}`);
19811
+ }
19812
+ await sql.unsafe(`CREATE TABLE IF NOT EXISTS ${qualifiedTableName} (
19813
+ event_id TEXT PRIMARY KEY,
19814
+ created_at BIGINT NOT NULL,
19815
+ expires_at BIGINT
19816
+ )`);
19817
+ })();
19818
+ const pruneExpired = async () => {
19819
+ await initialized;
19820
+ const sql = await client;
19821
+ await sql.unsafe(`DELETE FROM ${qualifiedTableName} WHERE expires_at IS NOT NULL AND expires_at <= $1`, [Date.now()]);
19822
+ };
19823
+ return {
19824
+ claim: async (eventId) => {
19825
+ await pruneExpired();
19826
+ const sql = await client;
19827
+ const rows = await sql.unsafe(`INSERT INTO ${qualifiedTableName} (event_id, created_at, expires_at)
19828
+ VALUES ($1, $2, $3)
19829
+ ON CONFLICT (event_id) DO NOTHING
19830
+ RETURNING event_id`, [eventId, Date.now(), getTelnyxEventExpiresAt(options.ttlSeconds)]);
19831
+ return rows.length > 0;
19832
+ },
19833
+ has: async (eventId) => {
19834
+ await initialized;
19835
+ const sql = await client;
19836
+ const rows = await sql.unsafe(`SELECT event_id FROM ${qualifiedTableName}
19837
+ WHERE event_id = $1 AND (expires_at IS NULL OR expires_at > $2)
19838
+ LIMIT 1`, [eventId, Date.now()]);
19839
+ return rows.length > 0;
19840
+ },
19841
+ set: async (eventId) => {
19842
+ await initialized;
19843
+ const sql = await client;
19844
+ await sql.unsafe(`INSERT INTO ${qualifiedTableName} (event_id, created_at, expires_at)
19845
+ VALUES ($1, $2, $3)
19846
+ ON CONFLICT (event_id) DO UPDATE SET expires_at = EXCLUDED.expires_at`, [eventId, Date.now(), getTelnyxEventExpiresAt(options.ttlSeconds)]);
19847
+ }
19848
+ };
19849
+ };
19850
+ var getTelnyxRedisEventKey = (keyPrefix, eventId) => `${keyPrefix}:${eventId}`;
19851
+ var createVoiceRedisTelnyxWebhookEventStore = (options = {}) => {
19852
+ const client = options.client ?? new Bun.RedisClient(options.url);
19853
+ const keyPrefix = options.keyPrefix?.trim() || "voice:telnyx-webhook-event";
19854
+ const ttlSeconds = options.ttlSeconds;
19855
+ const setEvent = async (eventId, nx) => {
19856
+ const key = getTelnyxRedisEventKey(keyPrefix, eventId);
19857
+ if (typeof ttlSeconds === "number" && ttlSeconds > 0) {
19858
+ return client.set(key, "1", "EX", String(Math.ceil(ttlSeconds)), ...nx ? ["NX"] : []);
19859
+ }
19860
+ return client.set(key, "1", ...nx ? ["NX"] : []);
19861
+ };
19862
+ return {
19863
+ claim: async (eventId) => await setEvent(eventId, true) === "OK",
19864
+ has: async (eventId) => Boolean(await client.exists(getTelnyxRedisEventKey(keyPrefix, eventId))),
19865
+ set: async (eventId) => {
19866
+ await setEvent(eventId, false);
19867
+ }
19868
+ };
19869
+ };
19870
+ var readTelnyxWebhookEventId = (rawBody) => {
19871
+ try {
19872
+ const body = JSON.parse(rawBody);
19873
+ if (!body || typeof body !== "object" || Array.isArray(body)) {
19874
+ return;
19875
+ }
19876
+ const record = body;
19877
+ const data = record.data;
19878
+ if (data && typeof data === "object" && !Array.isArray(data)) {
19879
+ const eventId2 = data.id;
19880
+ if (typeof eventId2 === "string" && eventId2.trim()) {
19881
+ return eventId2;
19882
+ }
19883
+ }
19884
+ const eventId = record.id ?? record.event_id;
19885
+ return typeof eventId === "string" && eventId.trim() ? eventId : undefined;
19886
+ } catch {
19887
+ return;
19888
+ }
19889
+ };
19890
+ var createVoiceTelnyxWebhookVerifier = (options) => async (input) => {
19891
+ const verification = await verifyVoiceTelnyxWebhookSignature({
19892
+ body: input.rawBody,
19893
+ headers: input.headers,
19894
+ publicKey: options.publicKey,
19895
+ toleranceSeconds: options.toleranceSeconds
19896
+ });
19897
+ if (!verification.ok) {
19898
+ return verification;
19899
+ }
19900
+ const eventStore = options.eventStore;
19901
+ if (!eventStore) {
19902
+ return verification;
19903
+ }
19904
+ const eventId = readTelnyxWebhookEventId(input.rawBody);
19905
+ if (!eventId) {
19906
+ return { ok: false, reason: "invalid-signature" };
19907
+ }
19908
+ if (eventStore.claim) {
19909
+ return await eventStore.claim(eventId) ? verification : { ok: false, reason: "invalid-signature" };
19910
+ }
19911
+ if (await eventStore.has(eventId)) {
19912
+ return { ok: false, reason: "invalid-signature" };
19913
+ }
19914
+ await eventStore.set(eventId);
19915
+ return verification;
19916
+ };
19724
19917
  var buildTelnyxVoiceSetupStatus = async (options, input) => {
19725
19918
  const origin = resolveRequestOrigin3(input.request);
19726
19919
  const stream = await resolveTelnyxStreamUrl(options, input);
@@ -19854,11 +20047,10 @@ var createTelnyxVoiceRoutes = (options = {}) => {
19854
20047
  const smokePath = options.smoke?.path === false ? false : options.smoke?.path ?? "/api/voice/telnyx/smoke";
19855
20048
  const bridges = new WeakMap;
19856
20049
  const webhookPolicy = options.webhook?.policy ?? options.outcomePolicy ?? createVoiceTelephonyOutcomePolicy();
19857
- const verify = options.webhook?.verify ?? (options.webhook?.publicKey ? (input) => verifyVoiceTelnyxWebhookSignature({
19858
- body: input.rawBody,
19859
- headers: input.headers,
19860
- publicKey: options.webhook?.publicKey,
19861
- toleranceSeconds: options.webhook?.toleranceSeconds
20050
+ const verify = options.webhook?.verify ?? (options.webhook?.publicKey ? createVoiceTelnyxWebhookVerifier({
20051
+ eventStore: options.webhook.eventStore,
20052
+ publicKey: options.webhook.publicKey,
20053
+ toleranceSeconds: options.webhook.toleranceSeconds
19862
20054
  }) : undefined);
19863
20055
  const app = new Elysia31({
19864
20056
  name: options.name ?? "absolutejs-voice-telnyx"
@@ -23990,7 +24182,7 @@ var createVoiceOpsRecoveryRoutes = (options = {}) => {
23990
24182
 
23991
24183
  // src/observabilityExport.ts
23992
24184
  import { Elysia as Elysia41 } from "elysia";
23993
- import { Database as Database2 } from "bun:sqlite";
24185
+ import { Database as Database3 } from "bun:sqlite";
23994
24186
  import { createHash } from "crypto";
23995
24187
  import { mkdir as mkdir4, readFile as readFile2, stat, unlink } from "fs/promises";
23996
24188
  import { join as join3 } from "path";
@@ -25220,7 +25412,7 @@ var loadVoiceObservabilityExportReplaySource = async (source) => {
25220
25412
  if (!source.database && !source.path) {
25221
25413
  throw new Error("SQLite observability export replay requires source.database or source.path.");
25222
25414
  }
25223
- const database = source.database ?? new Database2(source.path, { create: false });
25415
+ const database = source.database ?? new Database3(source.path, { create: false });
25224
25416
  const table2 = quoteObservabilityIdentifier(normalizeObservabilityIdentifier(source.tableName));
25225
25417
  const row2 = database.query(`SELECT manifest_json, artifact_index_json, payload_json FROM ${table2} WHERE run_id = $runId`).get({ $runId: source.runId });
25226
25418
  if (!row2) {
@@ -25297,7 +25489,7 @@ var deliverObservabilityExportToSQLite = async (input) => {
25297
25489
  if (!input.destination.database && !input.destination.path) {
25298
25490
  throw new Error("SQLite observability export delivery requires destination.database or destination.path.");
25299
25491
  }
25300
- const database = input.destination.database ?? new Database2(input.destination.path, { create: true });
25492
+ const database = input.destination.database ?? new Database3(input.destination.path, { create: true });
25301
25493
  const table = quoteObservabilityIdentifier(normalizeObservabilityIdentifier(input.destination.tableName));
25302
25494
  const record = buildObservabilityExportDatabaseRecord(input);
25303
25495
  database.exec(`CREATE TABLE IF NOT EXISTS ${table} (
@@ -29407,7 +29599,7 @@ var createVoiceTraceDeliveryRoutes = (options) => {
29407
29599
  return routes;
29408
29600
  };
29409
29601
  // src/sqliteStore.ts
29410
- import { Database as Database3 } from "bun:sqlite";
29602
+ import { Database as Database4 } from "bun:sqlite";
29411
29603
  var normalizeTableNameSegment = (value) => value.trim().replace(/[^a-zA-Z0-9_]+/g, "_").replace(/^_+|_+$/g, "") || "voice";
29412
29604
  var resolveTableName = (input) => {
29413
29605
  if (input.options.tableName) {
@@ -29418,7 +29610,7 @@ var resolveTableName = (input) => {
29418
29610
  return `${prefix}_${fallback}`;
29419
29611
  };
29420
29612
  var openVoiceSQLiteDatabase = (path) => {
29421
- const database = new Database3(path, {
29613
+ const database = new Database4(path, {
29422
29614
  create: true
29423
29615
  });
29424
29616
  database.exec("PRAGMA journal_mode = WAL;");
@@ -31472,6 +31664,139 @@ var createVoiceSTTRoutingCorrectionHandler = (mode = "generic") => {
31472
31664
  }
31473
31665
  return createPhraseHintCorrectionHandler();
31474
31666
  };
31667
+ // src/telephony/security.ts
31668
+ var resolveVerificationUrl2 = (option, input) => typeof option === "function" ? option(input) : option ?? input.request.url;
31669
+ var createStores = (options) => {
31670
+ const ttlSeconds = options.ttlSeconds;
31671
+ const store = options.store ?? { kind: "memory" };
31672
+ if (store.kind === "sqlite") {
31673
+ return {
31674
+ idempotency: options.twilio?.idempotencyStore ?? createVoiceSQLiteTelephonyWebhookIdempotencyStore({
31675
+ path: store.path,
31676
+ tableName: "voice_twilio_webhook_idempotency",
31677
+ tablePrefix: store.tablePrefix
31678
+ }),
31679
+ plivo: options.plivo?.nonceStore ?? createVoiceSQLitePlivoWebhookNonceStore({
31680
+ path: store.path,
31681
+ tableName: "voice_plivo_webhook_nonces",
31682
+ tablePrefix: store.tablePrefix,
31683
+ ttlSeconds
31684
+ }),
31685
+ telnyx: options.telnyx?.eventStore ?? createVoiceSQLiteTelnyxWebhookEventStore({
31686
+ path: store.path,
31687
+ tableName: "voice_telnyx_webhook_events",
31688
+ tablePrefix: store.tablePrefix,
31689
+ ttlSeconds
31690
+ })
31691
+ };
31692
+ }
31693
+ if (store.kind === "postgres") {
31694
+ return {
31695
+ idempotency: options.twilio?.idempotencyStore ?? createVoicePostgresTelephonyWebhookIdempotencyStore({
31696
+ connectionString: store.connectionString,
31697
+ schemaName: store.schemaName,
31698
+ sql: store.sql,
31699
+ tableName: "voice_twilio_webhook_idempotency",
31700
+ tablePrefix: store.tablePrefix
31701
+ }),
31702
+ plivo: options.plivo?.nonceStore ?? createVoicePostgresPlivoWebhookNonceStore({
31703
+ connectionString: store.connectionString,
31704
+ schemaName: store.schemaName,
31705
+ sql: store.sql,
31706
+ tableName: "voice_plivo_webhook_nonces",
31707
+ tablePrefix: store.tablePrefix,
31708
+ ttlSeconds
31709
+ }),
31710
+ telnyx: options.telnyx?.eventStore ?? createVoicePostgresTelnyxWebhookEventStore({
31711
+ connectionString: store.connectionString,
31712
+ schemaName: store.schemaName,
31713
+ sql: store.sql,
31714
+ tableName: "voice_telnyx_webhook_events",
31715
+ tablePrefix: store.tablePrefix,
31716
+ ttlSeconds
31717
+ })
31718
+ };
31719
+ }
31720
+ if (store.kind === "redis") {
31721
+ const keyPrefix = store.keyPrefix?.trim() || "voice:webhook-security";
31722
+ return {
31723
+ idempotency: options.twilio?.idempotencyStore ?? createVoiceRedisTelephonyWebhookIdempotencyStore({
31724
+ client: store.idempotencyClient,
31725
+ keyPrefix: `${keyPrefix}:twilio:idempotency`,
31726
+ ttlSeconds,
31727
+ url: store.url
31728
+ }),
31729
+ plivo: options.plivo?.nonceStore ?? createVoiceRedisPlivoWebhookNonceStore({
31730
+ client: store.plivoClient,
31731
+ keyPrefix: `${keyPrefix}:plivo:nonce`,
31732
+ ttlSeconds,
31733
+ url: store.url
31734
+ }),
31735
+ telnyx: options.telnyx?.eventStore ?? createVoiceRedisTelnyxWebhookEventStore({
31736
+ client: store.telnyxClient,
31737
+ keyPrefix: `${keyPrefix}:telnyx:event`,
31738
+ ttlSeconds,
31739
+ url: store.url
31740
+ })
31741
+ };
31742
+ }
31743
+ return {
31744
+ idempotency: options.twilio?.idempotencyStore ?? createMemoryVoiceTelephonyWebhookIdempotencyStore(),
31745
+ plivo: options.plivo?.nonceStore ?? createMemoryVoicePlivoWebhookNonceStore(),
31746
+ telnyx: options.telnyx?.eventStore ?? createMemoryVoiceTelnyxWebhookEventStore()
31747
+ };
31748
+ };
31749
+ var createVoiceTelephonyWebhookSecurityPreset = (options = {}) => {
31750
+ const stores = createStores(options);
31751
+ const twilioVerificationUrl = options.twilio?.verificationUrl;
31752
+ const plivoVerify = createVoicePlivoWebhookVerifier({
31753
+ authToken: options.plivo?.authToken,
31754
+ nonceStore: stores.plivo,
31755
+ verificationUrl: options.plivo?.verificationUrl
31756
+ });
31757
+ const telnyxVerify = createVoiceTelnyxWebhookVerifier({
31758
+ eventStore: stores.telnyx,
31759
+ publicKey: options.telnyx?.publicKey,
31760
+ toleranceSeconds: options.telnyx?.toleranceSeconds
31761
+ });
31762
+ const twilioVerify = async (input) => verifyVoiceTwilioWebhookSignature({
31763
+ authToken: options.twilio?.authToken,
31764
+ body: input.body,
31765
+ headers: input.headers,
31766
+ url: resolveVerificationUrl2(twilioVerificationUrl, {
31767
+ query: input.query,
31768
+ request: input.request
31769
+ })
31770
+ });
31771
+ return {
31772
+ plivo: {
31773
+ authToken: options.plivo?.authToken,
31774
+ nonceStore: stores.plivo,
31775
+ verify: plivoVerify
31776
+ },
31777
+ telnyx: {
31778
+ eventStore: stores.telnyx,
31779
+ publicKey: options.telnyx?.publicKey,
31780
+ toleranceSeconds: options.telnyx?.toleranceSeconds,
31781
+ verify: telnyxVerify
31782
+ },
31783
+ twilio: {
31784
+ idempotency: {
31785
+ enabled: true,
31786
+ store: stores.idempotency
31787
+ },
31788
+ requireVerification: true,
31789
+ signingSecret: options.twilio?.authToken,
31790
+ verificationUrl: twilioVerificationUrl,
31791
+ verify: twilioVerify
31792
+ },
31793
+ verify: {
31794
+ plivo: plivoVerify,
31795
+ telnyx: telnyxVerify,
31796
+ twilio: twilioVerify
31797
+ }
31798
+ };
31799
+ };
31475
31800
  // src/telephony/response.ts
31476
31801
  var normalizeWhitespace = (value) => value.replace(/\s+/g, " ").trim();
31477
31802
  var DEFAULT_MAX_WORDS = 12;
@@ -31780,7 +32105,9 @@ export {
31780
32105
  createVoiceToolContractJSONHandler,
31781
32106
  createVoiceToolContractHTMLHandler,
31782
32107
  createVoiceToolContract,
32108
+ createVoiceTelnyxWebhookVerifier,
31783
32109
  createVoiceTelnyxCampaignDialer,
32110
+ createVoiceTelephonyWebhookSecurityPreset,
31784
32111
  createVoiceTelephonyWebhookRoutes,
31785
32112
  createVoiceTelephonyWebhookHandler,
31786
32113
  createVoiceTelephonyOutcomePolicy,
@@ -31803,6 +32130,7 @@ export {
31803
32130
  createVoiceSTTProviderRouter,
31804
32131
  createVoiceSQLiteTraceSinkDeliveryStore,
31805
32132
  createVoiceSQLiteTraceEventStore,
32133
+ createVoiceSQLiteTelnyxWebhookEventStore,
31806
32134
  createVoiceSQLiteTelephonyWebhookIdempotencyStore,
31807
32135
  createVoiceSQLiteTaskStore,
31808
32136
  createVoiceSQLiteSessionStore,
@@ -31820,6 +32148,7 @@ export {
31820
32148
  createVoiceRoutingDecisionSummary,
31821
32149
  createVoiceReviewSavedEvent,
31822
32150
  createVoiceResilienceRoutes,
32151
+ createVoiceRedisTelnyxWebhookEventStore,
31823
32152
  createVoiceRedisTelephonyWebhookIdempotencyStore,
31824
32153
  createVoiceRedisTaskLeaseCoordinator,
31825
32154
  createVoiceRedisPlivoWebhookNonceStore,
@@ -31843,6 +32172,7 @@ export {
31843
32172
  createVoiceProductionReadinessRoutes,
31844
32173
  createVoicePostgresTraceSinkDeliveryStore,
31845
32174
  createVoicePostgresTraceEventStore,
32175
+ createVoicePostgresTelnyxWebhookEventStore,
31846
32176
  createVoicePostgresTelephonyWebhookIdempotencyStore,
31847
32177
  createVoicePostgresTaskStore,
31848
32178
  createVoicePostgresSessionStore,
@@ -31998,6 +32328,7 @@ export {
31998
32328
  createOpenAIVoiceTTS,
31999
32329
  createOpenAIVoiceAssistantModel,
32000
32330
  createOpenAIRealtimeAdapter,
32331
+ createMemoryVoiceTelnyxWebhookEventStore,
32001
32332
  createMemoryVoiceTelephonyWebhookIdempotencyStore,
32002
32333
  createMemoryVoicePlivoWebhookNonceStore,
32003
32334
  createJSONVoiceAssistantModel,
@@ -0,0 +1,95 @@
1
+ import { type VoiceTelephonyWebhookIdempotencyStore, type VoiceTelephonyWebhookVerificationResult } from '../telephonyOutcome';
2
+ import { type VoicePostgresClient } from '../postgresStore';
3
+ import { type VoiceRedisTelephonyWebhookIdempotencyClient } from '../queue';
4
+ import { type VoicePlivoWebhookNonceStore, type VoiceRedisPlivoWebhookNonceClient } from './plivo';
5
+ import { type VoiceRedisTelnyxWebhookEventClient, type VoiceTelnyxWebhookEventStore } from './telnyx';
6
+ export type VoiceTelephonyWebhookSecurityStorePreset = {
7
+ kind?: 'memory';
8
+ } | {
9
+ kind: 'sqlite';
10
+ path: string;
11
+ tablePrefix?: string;
12
+ } | {
13
+ connectionString?: string;
14
+ kind: 'postgres';
15
+ schemaName?: string;
16
+ sql?: VoicePostgresClient;
17
+ tablePrefix?: string;
18
+ } | {
19
+ idempotencyClient?: VoiceRedisTelephonyWebhookIdempotencyClient;
20
+ keyPrefix?: string;
21
+ kind: 'redis';
22
+ plivoClient?: VoiceRedisPlivoWebhookNonceClient;
23
+ telnyxClient?: VoiceRedisTelnyxWebhookEventClient;
24
+ url?: string;
25
+ };
26
+ export type VoiceTelephonyWebhookSecurityOptions<TResult = unknown> = {
27
+ plivo?: {
28
+ authToken?: string;
29
+ nonceStore?: VoicePlivoWebhookNonceStore;
30
+ verificationUrl?: string | ((input: {
31
+ query: Record<string, unknown>;
32
+ request: Request;
33
+ }) => string);
34
+ };
35
+ store?: VoiceTelephonyWebhookSecurityStorePreset;
36
+ telnyx?: {
37
+ eventStore?: VoiceTelnyxWebhookEventStore;
38
+ publicKey?: string;
39
+ toleranceSeconds?: number;
40
+ };
41
+ ttlSeconds?: number;
42
+ twilio?: {
43
+ authToken?: string;
44
+ idempotencyStore?: VoiceTelephonyWebhookIdempotencyStore<TResult>;
45
+ verificationUrl?: string | ((input: {
46
+ query: Record<string, unknown>;
47
+ request: Request;
48
+ }) => string);
49
+ };
50
+ };
51
+ export type VoiceTelephonyWebhookSecurityPreset<TResult = unknown> = {
52
+ plivo: {
53
+ authToken?: string;
54
+ nonceStore: VoicePlivoWebhookNonceStore;
55
+ verify: (input: {
56
+ body: unknown;
57
+ headers: Headers;
58
+ query: Record<string, unknown>;
59
+ request: Request;
60
+ }) => Promise<VoiceTelephonyWebhookVerificationResult>;
61
+ };
62
+ telnyx: {
63
+ eventStore: VoiceTelnyxWebhookEventStore;
64
+ publicKey?: string;
65
+ toleranceSeconds?: number;
66
+ verify: (input: {
67
+ headers: Headers;
68
+ rawBody: string;
69
+ }) => Promise<VoiceTelephonyWebhookVerificationResult>;
70
+ };
71
+ twilio: {
72
+ idempotency: {
73
+ enabled: true;
74
+ store: VoiceTelephonyWebhookIdempotencyStore<TResult>;
75
+ };
76
+ requireVerification: true;
77
+ signingSecret?: string;
78
+ verificationUrl?: string | ((input: {
79
+ query: Record<string, unknown>;
80
+ request: Request;
81
+ }) => string);
82
+ verify: (input: {
83
+ body: unknown;
84
+ headers: Headers;
85
+ query: Record<string, unknown>;
86
+ request: Request;
87
+ }) => Promise<VoiceTelephonyWebhookVerificationResult>;
88
+ };
89
+ verify: {
90
+ plivo: VoiceTelephonyWebhookSecurityPreset<TResult>['plivo']['verify'];
91
+ telnyx: VoiceTelephonyWebhookSecurityPreset<TResult>['telnyx']['verify'];
92
+ twilio: VoiceTelephonyWebhookSecurityPreset<TResult>['twilio']['verify'];
93
+ };
94
+ };
95
+ export declare const createVoiceTelephonyWebhookSecurityPreset: <TResult = unknown>(options?: VoiceTelephonyWebhookSecurityOptions<TResult>) => VoiceTelephonyWebhookSecurityPreset<TResult>;
@@ -1,8 +1,11 @@
1
+ import { Database } from 'bun:sqlite';
2
+ import type { RedisClient } from 'bun';
1
3
  import { Elysia } from 'elysia';
2
4
  import { type VoiceTelephonyContractReport, type VoiceTelephonySetupStatus, type VoiceTelephonySmokeCheck, type VoiceTelephonySmokeReport } from './contract';
3
5
  import { type VoiceTelephonyOutcomePolicy, type VoiceTelephonyWebhookRoutesOptions, type VoiceTelephonyWebhookVerificationResult } from '../telephonyOutcome';
4
6
  import type { VoiceServerMessage, VoiceSessionRecord } from '../types';
5
7
  import { type TwilioMediaStreamBridgeOptions } from './twilio';
8
+ import type { VoicePostgresClient } from '../postgresStore';
6
9
  export type TelnyxMediaPayload = {
7
10
  chunk?: string;
8
11
  payload: string;
@@ -151,12 +154,45 @@ export type TelnyxVoiceRoutesOptions<TContext = unknown, TSession extends VoiceS
151
154
  }) => Promise<string> | string);
152
155
  };
153
156
  webhook?: Omit<VoiceTelephonyWebhookRoutesOptions<TContext, TSession, TResult>, 'context' | 'path' | 'policy' | 'provider'> & {
157
+ eventStore?: VoiceTelnyxWebhookEventStore;
154
158
  path?: string;
155
159
  policy?: VoiceTelephonyOutcomePolicy;
156
160
  publicKey?: string;
157
161
  toleranceSeconds?: number;
158
162
  };
159
163
  };
164
+ export type VoiceTelnyxWebhookEventStore = {
165
+ claim?: (eventId: string) => Promise<boolean> | boolean;
166
+ has: (eventId: string) => Promise<boolean> | boolean;
167
+ set: (eventId: string) => Promise<void> | void;
168
+ };
169
+ export type VoiceTelnyxWebhookEventStoreOptions = {
170
+ ttlSeconds?: number;
171
+ };
172
+ export type VoiceSQLiteTelnyxWebhookEventStoreOptions = VoiceTelnyxWebhookEventStoreOptions & {
173
+ database?: Database;
174
+ path?: string;
175
+ tableName?: string;
176
+ tablePrefix?: string;
177
+ };
178
+ export type VoicePostgresTelnyxWebhookEventStoreOptions = VoiceTelnyxWebhookEventStoreOptions & {
179
+ connectionString?: string;
180
+ schemaName?: string;
181
+ sql?: VoicePostgresClient;
182
+ tableName?: string;
183
+ tablePrefix?: string;
184
+ };
185
+ export type VoiceRedisTelnyxWebhookEventClient = Pick<RedisClient, 'exists' | 'set'>;
186
+ export type VoiceRedisTelnyxWebhookEventStoreOptions = VoiceTelnyxWebhookEventStoreOptions & {
187
+ client?: VoiceRedisTelnyxWebhookEventClient;
188
+ keyPrefix?: string;
189
+ url?: string;
190
+ };
191
+ export type VoiceTelnyxWebhookVerifierOptions = {
192
+ eventStore?: VoiceTelnyxWebhookEventStore;
193
+ publicKey?: string;
194
+ toleranceSeconds?: number;
195
+ };
160
196
  export declare const createTelnyxVoiceResponse: (options: TelnyxVoiceResponseOptions) => string;
161
197
  export declare const createTelnyxMediaStreamBridge: <TContext = unknown, TSession extends VoiceSessionRecord = VoiceSessionRecord, TResult = unknown>(socket: TelnyxMediaStreamSocket, options: TelnyxMediaStreamBridgeOptions<TContext, TSession, TResult>) => TelnyxMediaStreamBridge;
162
198
  export declare const verifyVoiceTelnyxWebhookSignature: (input: {
@@ -165,6 +201,14 @@ export declare const verifyVoiceTelnyxWebhookSignature: (input: {
165
201
  publicKey?: string;
166
202
  toleranceSeconds?: number;
167
203
  }) => Promise<VoiceTelephonyWebhookVerificationResult>;
204
+ export declare const createMemoryVoiceTelnyxWebhookEventStore: () => VoiceTelnyxWebhookEventStore;
205
+ export declare const createVoiceSQLiteTelnyxWebhookEventStore: (options: VoiceSQLiteTelnyxWebhookEventStoreOptions) => VoiceTelnyxWebhookEventStore;
206
+ export declare const createVoicePostgresTelnyxWebhookEventStore: (options?: VoicePostgresTelnyxWebhookEventStoreOptions) => VoiceTelnyxWebhookEventStore;
207
+ export declare const createVoiceRedisTelnyxWebhookEventStore: (options?: VoiceRedisTelnyxWebhookEventStoreOptions) => VoiceTelnyxWebhookEventStore;
208
+ export declare const createVoiceTelnyxWebhookVerifier: (options: VoiceTelnyxWebhookVerifierOptions) => (input: {
209
+ rawBody: string;
210
+ headers: Headers;
211
+ }) => Promise<VoiceTelephonyWebhookVerificationResult>;
168
212
  export declare const createTelnyxVoiceRoutes: <TContext = unknown, TSession extends VoiceSessionRecord = VoiceSessionRecord, TResult = unknown>(options?: TelnyxVoiceRoutesOptions<TContext, TSession, TResult>) => Elysia<"", {
169
213
  decorator: {};
170
214
  store: {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@absolutejs/voice",
3
- "version": "0.0.22-beta.271",
3
+ "version": "0.0.22-beta.273",
4
4
  "description": "Voice primitives and Elysia plugin for AbsoluteJS",
5
5
  "repository": {
6
6
  "type": "git",