From a815856a7d7863d8734fb603269febb653ff4887 Mon Sep 17 00:00:00 2001 From: smoghe-bw Date: Thu, 20 Aug 2026 14:58:02 -0400 Subject: [PATCH 1/2] VAPI-3804 Add visual indicator for sent DTMF tones Wires the SDK's onDtmfSent callback (javascript-brtc-sdk#16) to flash the corresponding digit on the dial pad when a tone is actually played on the outbound stream, rather than just on click. Co-Authored-By: Claude Sonnet 5 --- src/components/CallController.tsx | 15 ++++++++++++-- src/components/DigitGrid.tsx | 33 +++++++++++++++++-------------- src/css/DigitGrid.scss | 8 ++++++++ 3 files changed, 39 insertions(+), 17 deletions(-) diff --git a/src/components/CallController.tsx b/src/components/CallController.tsx index 9d0bf85..4205c2f 100644 --- a/src/components/CallController.tsx +++ b/src/components/CallController.tsx @@ -1,4 +1,4 @@ -import React, {ChangeEventHandler, useState} from "react"; +import React, {ChangeEventHandler, useEffect, useState} from "react"; import '../css/DigitGrid.scss'; import '../css/CallControlButton.scss'; @@ -90,6 +90,17 @@ function CallController({bandwidthRtcClient, readyMetadata, inCall, setInCall, c const [endpointType, setEndpointType] = useState(EndpointType.PHONE_NUMBER); const [dtmfDuration, setDtmfDuration] = useState(300); const [dtmfSequence, setDtmfSequence] = useState(''); + // Last tone actually played on the outbound stream, per the SDK's onDtmfSent + // event - distinct from handleDigitClick, which only fires on user intent. + const [lastDtmfSent, setLastDtmfSent] = useState(null); + + useEffect(() => { + bandwidthRtcClient.onDtmfSent((event) => { + console.log("DTMF sent:", event); + setLastDtmfSent(event.tone); + setTimeout(() => setLastDtmfSent(null), 400); + }); + }, [bandwidthRtcClient]); const handleSendDtmfSequence = () => { console.log(`Sending DTMF sequence: ${dtmfSequence}, duration: ${dtmfDuration}ms`); @@ -288,7 +299,7 @@ function CallController({bandwidthRtcClient, readyMetadata, inCall, setInCall, c )} - + )}
diff --git a/src/components/DigitGrid.tsx b/src/components/DigitGrid.tsx index 9f5f6c4..258eee3 100644 --- a/src/components/DigitGrid.tsx +++ b/src/components/DigitGrid.tsx @@ -2,30 +2,33 @@ import React, {JSX} from "react"; import '../css/DigitGrid.scss'; -function Digit({number, letters, onClick}: {number: number | string; letters: string; onClick: () => void}): JSX.Element { +function Digit({number, letters, onClick, active}: {number: number | string; letters: string; onClick: () => void; active: boolean}): JSX.Element { return ( -
+
{number}
{letters}
); } -function DigitGrid({ onClick } : {onClick: (arg0: string) => void}) { +// activeDigit reflects the SDK's onDtmfSent event (the tone actually played on +// the outbound stream), not the click itself, so it also lights up when a +// digit is sent via the sequence input rather than the grid. +function DigitGrid({ onClick, activeDigit } : {onClick: (arg0: string) => void; activeDigit?: string}) { return (
- onClick('1')}/> - onClick('2')}/> - onClick('3')}/> - onClick('4')}/> - onClick('5')}/> - onClick('6')}/> - onClick('7')}/> - onClick('8')}/> - onClick('9')}/> - onClick('*')}/> - onClick('0')}/> - onClick('#')}/> + onClick('1')} active={activeDigit === '1'}/> + onClick('2')} active={activeDigit === '2'}/> + onClick('3')} active={activeDigit === '3'}/> + onClick('4')} active={activeDigit === '4'}/> + onClick('5')} active={activeDigit === '5'}/> + onClick('6')} active={activeDigit === '6'}/> + onClick('7')} active={activeDigit === '7'}/> + onClick('8')} active={activeDigit === '8'}/> + onClick('9')} active={activeDigit === '9'}/> + onClick('*')} active={activeDigit === '*'}/> + onClick('0')} active={activeDigit === '0'}/> + onClick('#')} active={activeDigit === '#'}/>
); } diff --git a/src/css/DigitGrid.scss b/src/css/DigitGrid.scss index 5c87210..669b799 100644 --- a/src/css/DigitGrid.scss +++ b/src/css/DigitGrid.scss @@ -38,6 +38,14 @@ } } +// Flashes when the SDK confirms a tone was actually sent (onDtmfSent), not on click. +.digit-sent { + color: white; + border: 2px solid $blue70; + background-color: $blue70; + transition: background-color 0.1s ease-in, color 0.1s ease-in; +} + .digit-number { font-size: 2rem; font-weight: 600; From a7d3938ba6e0510fbd42c8efbe9fce8975498d37 Mon Sep 17 00:00:00 2001 From: smoghe-bw Date: Thu, 20 Aug 2026 15:02:08 -0400 Subject: [PATCH 2/2] VAPI-3804 Bump bandwidth-rtc to 0.8.0 for onDtmfSent support Now published on npm with the onDtmfSent callback this PR depends on. Co-Authored-By: Claude Sonnet 5 --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8748e7c..e167eb3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,7 +16,7 @@ "@types/node": "^16.18.126", "@types/react": "^19.2.2", "@types/react-dom": "^19.2.2", - "bandwidth-rtc": "^0.6.0", + "bandwidth-rtc": "^0.8.0", "bandwidth-sdk": "^7.3.0", "cors": "^2.8.5", "dotenv": "^16.4.7", @@ -5683,9 +5683,9 @@ "license": "MIT" }, "node_modules/bandwidth-rtc": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/bandwidth-rtc/-/bandwidth-rtc-0.6.0.tgz", - "integrity": "sha512-6fDqfr9y5HlQMOOiHrxt6fqVcj5IAG53WzwS18wfXhCwk7Fh/UK4uPDDwwZrQ5uhdP59eRxQ38R+AkecqyAuog==", + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/bandwidth-rtc/-/bandwidth-rtc-0.8.0.tgz", + "integrity": "sha512-U8FZfAnQzyRVnqmjF8gjgvDaJWkyt1amCqFdb7/x6tI54LyQJPIp/vmD7ZkcdYzIrjG8iETML8QzLV7p++iOKQ==", "license": "MIT", "dependencies": { "@types/uuid": "^10.0.0", diff --git a/package.json b/package.json index 89a70a4..c8198c8 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "@types/node": "^16.18.126", "@types/react": "^19.2.2", "@types/react-dom": "^19.2.2", - "bandwidth-rtc": "^0.6.0", + "bandwidth-rtc": "^0.8.0", "bandwidth-sdk": "^7.3.0", "cors": "^2.8.5", "dotenv": "^16.4.7",