fix(voice-call): pass Twilio stream auth token via <Parameter> instead of query string (#14029)
Twilio strips query parameters from WebSocket URLs in <Stream> TwiML, so the auth token set via ?token=xxx never arrives on the WebSocket connection. This causes stream rejection when token validation is enabled. Fix: pass the token as a <Parameter> element inside <Stream>, which Twilio delivers in the start message's customParameters field. The media stream handler now extracts the token from customParameters, falling back to query string for backwards compatibility. Co-authored-by: McWiggles <mcwigglesmcgee@users.noreply.github.com>
This commit is contained in:
parent
f8c91b3c5f
commit
f8cad44cd6
@ -146,6 +146,11 @@ export class MediaStreamHandler {
|
||||
const streamSid = message.streamSid || "";
|
||||
const callSid = message.start?.callSid || "";
|
||||
|
||||
// Prefer token from start message customParameters (set via TwiML <Parameter>),
|
||||
// falling back to query string token. Twilio strips query params from WebSocket
|
||||
// URLs but reliably delivers <Parameter> values in customParameters.
|
||||
const effectiveToken = message.start?.customParameters?.token ?? streamToken;
|
||||
|
||||
console.log(`[MediaStream] Stream started: ${streamSid} (call: ${callSid})`);
|
||||
if (!callSid) {
|
||||
console.warn("[MediaStream] Missing callSid; closing stream");
|
||||
@ -154,7 +159,7 @@ export class MediaStreamHandler {
|
||||
}
|
||||
if (
|
||||
this.config.shouldAcceptStream &&
|
||||
!this.config.shouldAcceptStream({ callId: callSid, streamSid, token: streamToken })
|
||||
!this.config.shouldAcceptStream({ callId: callSid, streamSid, token: effectiveToken })
|
||||
) {
|
||||
console.warn(`[MediaStream] Rejecting stream for unknown call: ${callSid}`);
|
||||
ws.close(1008, "Unknown call");
|
||||
@ -393,6 +398,7 @@ interface TwilioMediaMessage {
|
||||
accountSid: string;
|
||||
callSid: string;
|
||||
tracks: string[];
|
||||
customParameters?: Record<string, string>;
|
||||
mediaFormat: {
|
||||
encoding: string;
|
||||
sampleRate: number;
|
||||
|
||||
@ -429,10 +429,21 @@ export class TwilioProvider implements VoiceCallProvider {
|
||||
* @param streamUrl - WebSocket URL (wss://...) for the media stream
|
||||
*/
|
||||
getStreamConnectXml(streamUrl: string): string {
|
||||
// Extract token from URL and pass via <Parameter> instead of query string.
|
||||
// Twilio strips query params from WebSocket URLs, but delivers <Parameter>
|
||||
// values in the "start" message's customParameters field.
|
||||
const parsed = new URL(streamUrl);
|
||||
const token = parsed.searchParams.get("token");
|
||||
parsed.searchParams.delete("token");
|
||||
const cleanUrl = parsed.toString();
|
||||
|
||||
const paramXml = token ? `\n <Parameter name="token" value="${escapeXml(token)}" />` : "";
|
||||
|
||||
return `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Response>
|
||||
<Connect>
|
||||
<Stream url="${escapeXml(streamUrl)}" />
|
||||
<Stream url="${escapeXml(cleanUrl)}">${paramXml}
|
||||
</Stream>
|
||||
</Connect>
|
||||
</Response>`;
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user