@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.
- package/dist/client/opsStatusWidget.d.ts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +84 -1
- package/dist/telephony/twilio.d.ts +23 -0
- package/dist/testing/index.js +84 -1
- package/package.json +1 -1
|
@@ -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" | "
|
|
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("&", "&").replaceAll('"', """).replaceAll("'", "'").replaceAll("<", "<").replaceAll(">", ">");
|
|
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
|
-
|
|
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;
|
package/dist/testing/index.js
CHANGED
|
@@ -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("&", "&").replaceAll('"', """).replaceAll("'", "'").replaceAll("<", "<").replaceAll(">", ">");
|
|
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
|
-
|
|
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
|