@absolutejs/voice 0.0.22-beta.74 → 0.0.22-beta.75

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.
@@ -29,7 +29,7 @@ export type VoiceOpsStatusWidgetOptions = VoiceAppKitStatusClientOptions & {
29
29
  includeLinks?: boolean;
30
30
  title?: string;
31
31
  };
32
- export declare const getVoiceOpsStatusLabel: (report?: VoiceAppKitStatusReport | null, error?: string | null) => "Passing" | "Unavailable" | "Checking" | "Needs attention";
32
+ export declare const getVoiceOpsStatusLabel: (report?: VoiceAppKitStatusReport | null, error?: string | null) => "Passing" | "Needs attention" | "Unavailable" | "Checking";
33
33
  export declare const createVoiceOpsStatusViewModel: (snapshot: VoiceAppKitStatusSnapshot, options?: VoiceOpsStatusWidgetOptions) => VoiceOpsStatusViewModel;
34
34
  export declare const renderVoiceOpsStatusHTML: (snapshot: VoiceAppKitStatusSnapshot, options?: VoiceOpsStatusWidgetOptions) => string;
35
35
  export declare const getVoiceOpsStatusCSS: () => string;
package/dist/index.d.ts CHANGED
@@ -81,7 +81,7 @@ export type { VoiceS3ReviewStoreClient, VoiceS3ReviewStoreFile, VoiceS3ReviewSto
81
81
  export type { VoiceSQLiteRuntimeStorage, VoiceSQLiteStoreOptions } from './sqliteStore';
82
82
  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';
83
83
  export { createTwilioMediaStreamBridge, createTwilioVoiceRoutes, createTwilioVoiceResponse, decodeTwilioMulawBase64, encodeTwilioMulawBase64, transcodePCMToTwilioOutboundPayload, transcodeTwilioInboundPayloadToPCM16 } from './telephony/twilio';
84
- export type { TwilioInboundMessage, TwilioMediaStreamBridge, TwilioMediaStreamBridgeOptions, TwilioMediaStreamSocket, TwilioOutboundClearMessage, TwilioOutboundMarkMessage, TwilioOutboundMediaMessage, TwilioOutboundMessage, TwilioVoiceRouteParameters, TwilioVoiceResponseOptions, TwilioVoiceRoutesOptions } from './telephony/twilio';
84
+ export type { TwilioInboundMessage, TwilioMediaStreamBridge, TwilioMediaStreamBridgeOptions, TwilioMediaStreamSocket, TwilioOutboundClearMessage, TwilioOutboundMarkMessage, TwilioOutboundMediaMessage, TwilioOutboundMessage, TwilioVoiceRouteParameters, TwilioVoiceResponseOptions, TwilioVoiceSetupOptions, TwilioVoiceSetupStatus, TwilioVoiceRoutesOptions } from './telephony/twilio';
85
85
  export { shapeTelephonyAssistantText } from './telephony/response';
86
86
  export type { TelephonyResponseShapeMode, TelephonyResponseShapeOptions } from './telephony/response';
87
87
  export * from './types';
package/dist/index.js CHANGED
@@ -15226,6 +15226,68 @@ var resolveTwilioStreamParameters = async (parameters, input) => {
15226
15226
  }
15227
15227
  return parameters;
15228
15228
  };
15229
+ var joinUrlPath = (origin, path) => `${origin.replace(/\/$/, "")}${path.startsWith("/") ? path : `/${path}`}`;
15230
+ var escapeHtml17 = (value) => value.replaceAll("&", "&amp;").replaceAll('"', "&quot;").replaceAll("'", "&#39;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
15231
+ var getWebhookVerificationUrl = (webhook, input) => {
15232
+ if (!webhook?.verificationUrl) {
15233
+ return;
15234
+ }
15235
+ if (typeof webhook.verificationUrl === "function") {
15236
+ return webhook.verificationUrl(input);
15237
+ }
15238
+ return webhook.verificationUrl;
15239
+ };
15240
+ var buildTwilioVoiceSetupStatus = async (options, input) => {
15241
+ const origin = resolveRequestOrigin(input.request);
15242
+ const stream = await resolveTwilioStreamUrl(options, input);
15243
+ const twiml = joinUrlPath(origin, input.twimlPath);
15244
+ const webhook = joinUrlPath(origin, input.webhookPath);
15245
+ const verificationUrl = getWebhookVerificationUrl(options.webhook, input);
15246
+ const missing = Object.entries(options.setup?.requiredEnv ?? {}).filter((entry) => !entry[1]).map(([name]) => name);
15247
+ const signingConfigured = Boolean(options.webhook?.signingSecret || options.webhook?.verify);
15248
+ const warnings = [
15249
+ ...stream.startsWith("wss://") ? [] : ["Twilio media streams should use wss:// in production."],
15250
+ ...signingConfigured ? [] : ["Webhook signature verification is not configured."],
15251
+ ...verificationUrl || !signingConfigured ? [] : ["Webhook signing is configured without an explicit verification URL."]
15252
+ ];
15253
+ return {
15254
+ generatedAt: Date.now(),
15255
+ missing,
15256
+ provider: "twilio",
15257
+ ready: missing.length === 0 && signingConfigured && warnings.length === 0,
15258
+ signing: {
15259
+ configured: signingConfigured,
15260
+ mode: options.webhook?.verify ? "custom" : options.webhook?.signingSecret ? "twilio-signature" : "none",
15261
+ verificationUrl
15262
+ },
15263
+ urls: {
15264
+ stream,
15265
+ twiml,
15266
+ webhook
15267
+ },
15268
+ warnings
15269
+ };
15270
+ };
15271
+ var renderTwilioVoiceSetupHTML = (status, title) => `<main style="font-family: ui-sans-serif, system-ui; max-width: 860px; margin: 40px auto; padding: 0 20px;">
15272
+ <p style="letter-spacing: .12em; text-transform: uppercase; color: #52606d;">Twilio setup</p>
15273
+ <h1>${escapeHtml17(title)}</h1>
15274
+ <p><strong>Status:</strong> ${status.ready ? "Ready" : "Needs attention"}</p>
15275
+ <section>
15276
+ <h2>URLs</h2>
15277
+ <ul>
15278
+ <li><strong>TwiML:</strong> <code>${escapeHtml17(status.urls.twiml)}</code></li>
15279
+ <li><strong>Media stream:</strong> <code>${escapeHtml17(status.urls.stream)}</code></li>
15280
+ <li><strong>Status webhook:</strong> <code>${escapeHtml17(status.urls.webhook)}</code></li>
15281
+ </ul>
15282
+ </section>
15283
+ <section>
15284
+ <h2>Signing</h2>
15285
+ <p>Mode: <code>${status.signing.mode}</code></p>
15286
+ ${status.signing.verificationUrl ? `<p>Verification URL: <code>${escapeHtml17(status.signing.verificationUrl)}</code></p>` : ""}
15287
+ </section>
15288
+ ${status.missing.length ? `<section><h2>Missing env</h2><ul>${status.missing.map((name) => `<li><code>${escapeHtml17(name)}</code></li>`).join("")}</ul></section>` : ""}
15289
+ ${status.warnings.length ? `<section><h2>Warnings</h2><ul>${status.warnings.map((warning) => `<li>${escapeHtml17(warning)}</li>`).join("")}</ul></section>` : ""}
15290
+ </main>`;
15229
15291
  var normalizeOnTurn2 = (handler) => {
15230
15292
  if (handler.length > 1) {
15231
15293
  const directHandler = handler;
@@ -15606,9 +15668,10 @@ var createTwilioVoiceRoutes = (options) => {
15606
15668
  const streamPath = options.streamPath ?? "/api/voice/twilio/stream";
15607
15669
  const twimlPath = options.twiml?.path ?? "/api/voice/twilio";
15608
15670
  const webhookPath = options.webhook?.path ?? "/api/voice/twilio/webhook";
15671
+ const setupPath = options.setup?.path === false ? false : options.setup?.path ?? "/api/voice/twilio/setup";
15609
15672
  const bridges = new WeakMap;
15610
15673
  const webhookPolicy = options.webhook?.policy ?? options.outcomePolicy ?? createVoiceTelephonyOutcomePolicy();
15611
- return new Elysia18({
15674
+ const app = new Elysia18({
15612
15675
  name: options.name ?? "absolutejs-voice-twilio"
15613
15676
  }).get(twimlPath, async ({ query, request }) => {
15614
15677
  const streamUrl = await resolveTwilioStreamUrl(options, {
@@ -15678,6 +15741,26 @@ var createTwilioVoiceRoutes = (options) => {
15678
15741
  policy: webhookPolicy,
15679
15742
  provider: "twilio"
15680
15743
  }));
15744
+ if (!setupPath) {
15745
+ return app;
15746
+ }
15747
+ return app.get(setupPath, async ({ query, request }) => {
15748
+ const status = await buildTwilioVoiceSetupStatus(options, {
15749
+ query,
15750
+ request,
15751
+ streamPath,
15752
+ twimlPath,
15753
+ webhookPath
15754
+ });
15755
+ if (query.format === "html") {
15756
+ return new Response(renderTwilioVoiceSetupHTML(status, options.setup?.title ?? "AbsoluteJS Twilio Voice Setup"), {
15757
+ headers: {
15758
+ "content-type": "text/html; charset=utf-8"
15759
+ }
15760
+ });
15761
+ }
15762
+ return status;
15763
+ });
15681
15764
  };
15682
15765
  // src/telephony/response.ts
15683
15766
  var normalizeWhitespace = (value) => value.replace(/\s+/g, " ").trim();
@@ -113,9 +113,32 @@ export type TwilioVoiceRouteParameters = Record<string, string | number | boolea
113
113
  query: Record<string, unknown>;
114
114
  request: Request;
115
115
  }) => Promise<Record<string, string | number | boolean | undefined>> | Record<string, string | number | boolean | undefined>);
116
+ export type TwilioVoiceSetupStatus = {
117
+ generatedAt: number;
118
+ missing: string[];
119
+ provider: 'twilio';
120
+ ready: boolean;
121
+ signing: {
122
+ configured: boolean;
123
+ mode: 'custom' | 'none' | 'twilio-signature';
124
+ verificationUrl?: string;
125
+ };
126
+ urls: {
127
+ stream: string;
128
+ twiml: string;
129
+ webhook: string;
130
+ };
131
+ warnings: string[];
132
+ };
133
+ export type TwilioVoiceSetupOptions = {
134
+ path?: false | string;
135
+ requiredEnv?: Record<string, string | undefined>;
136
+ title?: string;
137
+ };
116
138
  export type TwilioVoiceRoutesOptions<TContext = unknown, TSession extends VoiceSessionRecord = VoiceSessionRecord, TResult = unknown> = TwilioMediaStreamBridgeOptions<TContext, TSession, TResult> & {
117
139
  name?: string;
118
140
  outcomePolicy?: VoiceTelephonyOutcomePolicy;
141
+ setup?: TwilioVoiceSetupOptions;
119
142
  streamPath?: string;
120
143
  twiml?: {
121
144
  parameters?: TwilioVoiceRouteParameters;
@@ -8565,6 +8565,68 @@ var resolveTwilioStreamParameters = async (parameters, input) => {
8565
8565
  }
8566
8566
  return parameters;
8567
8567
  };
8568
+ var joinUrlPath = (origin, path) => `${origin.replace(/\/$/, "")}${path.startsWith("/") ? path : `/${path}`}`;
8569
+ var escapeHtml2 = (value) => value.replaceAll("&", "&amp;").replaceAll('"', "&quot;").replaceAll("'", "&#39;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
8570
+ var getWebhookVerificationUrl = (webhook, input) => {
8571
+ if (!webhook?.verificationUrl) {
8572
+ return;
8573
+ }
8574
+ if (typeof webhook.verificationUrl === "function") {
8575
+ return webhook.verificationUrl(input);
8576
+ }
8577
+ return webhook.verificationUrl;
8578
+ };
8579
+ var buildTwilioVoiceSetupStatus = async (options, input) => {
8580
+ const origin = resolveRequestOrigin(input.request);
8581
+ const stream = await resolveTwilioStreamUrl(options, input);
8582
+ const twiml = joinUrlPath(origin, input.twimlPath);
8583
+ const webhook = joinUrlPath(origin, input.webhookPath);
8584
+ const verificationUrl = getWebhookVerificationUrl(options.webhook, input);
8585
+ const missing = Object.entries(options.setup?.requiredEnv ?? {}).filter((entry) => !entry[1]).map(([name]) => name);
8586
+ const signingConfigured = Boolean(options.webhook?.signingSecret || options.webhook?.verify);
8587
+ const warnings = [
8588
+ ...stream.startsWith("wss://") ? [] : ["Twilio media streams should use wss:// in production."],
8589
+ ...signingConfigured ? [] : ["Webhook signature verification is not configured."],
8590
+ ...verificationUrl || !signingConfigured ? [] : ["Webhook signing is configured without an explicit verification URL."]
8591
+ ];
8592
+ return {
8593
+ generatedAt: Date.now(),
8594
+ missing,
8595
+ provider: "twilio",
8596
+ ready: missing.length === 0 && signingConfigured && warnings.length === 0,
8597
+ signing: {
8598
+ configured: signingConfigured,
8599
+ mode: options.webhook?.verify ? "custom" : options.webhook?.signingSecret ? "twilio-signature" : "none",
8600
+ verificationUrl
8601
+ },
8602
+ urls: {
8603
+ stream,
8604
+ twiml,
8605
+ webhook
8606
+ },
8607
+ warnings
8608
+ };
8609
+ };
8610
+ var renderTwilioVoiceSetupHTML = (status, title) => `<main style="font-family: ui-sans-serif, system-ui; max-width: 860px; margin: 40px auto; padding: 0 20px;">
8611
+ <p style="letter-spacing: .12em; text-transform: uppercase; color: #52606d;">Twilio setup</p>
8612
+ <h1>${escapeHtml2(title)}</h1>
8613
+ <p><strong>Status:</strong> ${status.ready ? "Ready" : "Needs attention"}</p>
8614
+ <section>
8615
+ <h2>URLs</h2>
8616
+ <ul>
8617
+ <li><strong>TwiML:</strong> <code>${escapeHtml2(status.urls.twiml)}</code></li>
8618
+ <li><strong>Media stream:</strong> <code>${escapeHtml2(status.urls.stream)}</code></li>
8619
+ <li><strong>Status webhook:</strong> <code>${escapeHtml2(status.urls.webhook)}</code></li>
8620
+ </ul>
8621
+ </section>
8622
+ <section>
8623
+ <h2>Signing</h2>
8624
+ <p>Mode: <code>${status.signing.mode}</code></p>
8625
+ ${status.signing.verificationUrl ? `<p>Verification URL: <code>${escapeHtml2(status.signing.verificationUrl)}</code></p>` : ""}
8626
+ </section>
8627
+ ${status.missing.length ? `<section><h2>Missing env</h2><ul>${status.missing.map((name) => `<li><code>${escapeHtml2(name)}</code></li>`).join("")}</ul></section>` : ""}
8628
+ ${status.warnings.length ? `<section><h2>Warnings</h2><ul>${status.warnings.map((warning) => `<li>${escapeHtml2(warning)}</li>`).join("")}</ul></section>` : ""}
8629
+ </main>`;
8568
8630
  var normalizeOnTurn = (handler) => {
8569
8631
  if (handler.length > 1) {
8570
8632
  const directHandler = handler;
@@ -8945,9 +9007,10 @@ var createTwilioVoiceRoutes = (options) => {
8945
9007
  const streamPath = options.streamPath ?? "/api/voice/twilio/stream";
8946
9008
  const twimlPath = options.twiml?.path ?? "/api/voice/twilio";
8947
9009
  const webhookPath = options.webhook?.path ?? "/api/voice/twilio/webhook";
9010
+ const setupPath = options.setup?.path === false ? false : options.setup?.path ?? "/api/voice/twilio/setup";
8948
9011
  const bridges = new WeakMap;
8949
9012
  const webhookPolicy = options.webhook?.policy ?? options.outcomePolicy ?? createVoiceTelephonyOutcomePolicy();
8950
- return new Elysia2({
9013
+ const app = new Elysia2({
8951
9014
  name: options.name ?? "absolutejs-voice-twilio"
8952
9015
  }).get(twimlPath, async ({ query, request }) => {
8953
9016
  const streamUrl = await resolveTwilioStreamUrl(options, {
@@ -9017,6 +9080,26 @@ var createTwilioVoiceRoutes = (options) => {
9017
9080
  policy: webhookPolicy,
9018
9081
  provider: "twilio"
9019
9082
  }));
9083
+ if (!setupPath) {
9084
+ return app;
9085
+ }
9086
+ return app.get(setupPath, async ({ query, request }) => {
9087
+ const status = await buildTwilioVoiceSetupStatus(options, {
9088
+ query,
9089
+ request,
9090
+ streamPath,
9091
+ twimlPath,
9092
+ webhookPath
9093
+ });
9094
+ if (query.format === "html") {
9095
+ return new Response(renderTwilioVoiceSetupHTML(status, options.setup?.title ?? "AbsoluteJS Twilio Voice Setup"), {
9096
+ headers: {
9097
+ "content-type": "text/html; charset=utf-8"
9098
+ }
9099
+ });
9100
+ }
9101
+ return status;
9102
+ });
9020
9103
  };
9021
9104
 
9022
9105
  // src/testing/telephony.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@absolutejs/voice",
3
- "version": "0.0.22-beta.74",
3
+ "version": "0.0.22-beta.75",
4
4
  "description": "Voice primitives and Elysia plugin for AbsoluteJS",
5
5
  "repository": {
6
6
  "type": "git",