diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index beb9074..12776e1 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,2 +1,2 @@ # Global rule: -* @Bandwidth-Samples/dx +* @Bandwidth-Samples/band-swi @Bandwidth-Samples/band-devx diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..8350171 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,19 @@ +version: 2 + +updates: + - package-ecosystem: bundler + directory: / + schedule: + interval: monthly + open-pull-requests-limit: 5 + ignore: + - dependency-name: bandwidth-sdk + update-types: + - version-update:semver-minor + - version-update:semver-patch + groups: + dependencies: + patterns: + - "*" + exclude-patterns: + - bandwidth-sdk diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..76198e5 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,15 @@ +name: Test + +on: + pull_request: + branches: + - main + schedule: + - cron: "0 9 * * 1" + +jobs: + contract-test: + name: Contract Test + uses: Bandwidth-Samples/sample-app-tooling/.github/workflows/ruby.yml@main + with: + sample: voice-record diff --git a/Gemfile b/Gemfile index 9147bd1..d4f81ee 100644 --- a/Gemfile +++ b/Gemfile @@ -1,4 +1,6 @@ source 'https://rubygems.org' -gem 'bandwidth-sdk', '11.0.0' -gem 'sinatra', '3.1.0' +gem 'bandwidth-sdk', '18.3.1' +gem 'puma', '8.0.2' +gem 'rackup', '2.3.1' +gem 'sinatra', '4.2.1' diff --git a/README.md b/README.md index 5e53219..c53d144 100644 --- a/README.md +++ b/README.md @@ -16,9 +16,9 @@ This sample app demonstrates how to record a message from an inbound call, similar to how a voicemail machine would work. -When the user calls the Bandwidth phone number associated with this application, the user will hear an audio prompt to leave a message, then a beep. Once this happens, the call will begin recording. +When the user calls the Bandwidth phone number associated with this application, the user will hear an audio prompt to leave a message, then a beep. Once this happens, the call will begin recording, for up to 30 seconds. -When the call ends, Bandwidth will start to process the recording. Once this processing is complete, Bandwidth will send a webhook to the /recordingAvailableCallback endpoint on this application. Once received, the application will download the recording via Bandwidth's voice recording API. +When the call ends, Bandwidth will start to process the recording. Once this processing is complete, Bandwidth will send a webhook to the `/callbacks/inbound/voice/recordingAvailable` endpoint on this application. Once received, the application will download the recording via Bandwidth's voice recording API. # Pre-Requisites @@ -48,8 +48,8 @@ The sample app uses the below environmental variables. ```sh BW_ACCOUNT_ID # Your Bandwidth Account Id -BW_USERNAME # Your Bandwidth API Username -BW_PASSWORD # Your Bandwidth API Password +BW_CLIENT_ID # Your Bandwidth API OAuth Client Id +BW_CLIENT_SECRET # Your Bandwidth API OAuth Client Secret LOCAL_PORT # The port number you wish to run the sample on ``` @@ -58,8 +58,10 @@ LOCAL_PORT # The port number you wish to run the sampl For a detailed introduction, check out our [Bandwidth Voice Callbacks](https://dev.bandwidth.com/docs/voice/webhooks) page. Below are the callback paths: -* `/callbacks/callInitiatedCallback` -* `/callbacks/recordingAvailableCallback` +* `/callbacks/inbound/voice` +* `/callbacks/inbound/voice/recordingAvailable` + +The app also serves the prompt tone at `/tone.mp3`, which is not a callback. ## Ngrok @@ -70,4 +72,4 @@ After you have downloaded and installed `ngrok` run the following command to ope ngrok http $LOCAL_PORT ``` -You can view your public URL at `http://127.0.0.1:4040` after ngrok is running. You can also view the status of the tunnel and requests/responses here. Once your public ngrok url has been created, you can use it as the `BASE_CALLBACK_URL` environmental variable and set it in the voice application created in the [Pre-Requisites](#pre-requisites) section. +You can view your public URL at `http://127.0.0.1:4040` after ngrok is running. You can also view the status of the tunnel and requests/responses here. Once your public ngrok url has been created, you can set it, followed by `/callbacks/inbound/voice`, as the callback URL of the voice application created in the [Pre-Requisites](#pre-requisites) section. diff --git a/app.rb b/app.rb index 898c4e6..c27febe 100644 --- a/app.rb +++ b/app.rb @@ -2,56 +2,62 @@ require 'bandwidth-sdk' begin - BW_USERNAME = ENV.fetch('BW_USERNAME') - BW_PASSWORD = ENV.fetch('BW_PASSWORD') BW_ACCOUNT_ID = ENV.fetch('BW_ACCOUNT_ID') - LOCAL_PORT = ENV.fetch('LOCAL_PORT') + BW_CLIENT_ID = ENV.fetch('BW_CLIENT_ID') + BW_CLIENT_SECRET = ENV.fetch('BW_CLIENT_SECRET') + LOCAL_PORT = Integer(ENV.fetch('LOCAL_PORT')) rescue StandardError puts 'Please set the environmental variables defined in the README' exit(-1) end set :port, LOCAL_PORT +set :host_authorization, permitted_hosts: [] -Bandwidth.configure do |config| # Configure Basic Auth - config.username = BW_USERNAME - config.password = BW_PASSWORD +Bandwidth.configure do |config| + config.client_id = BW_CLIENT_ID + config.client_secret = BW_CLIENT_SECRET config.return_binary_data = true end -post '/callbacks/callInitiatedCallback' do +def parse_callback(expected_event_type) + data = JSON.parse(request.body.read) + halt 400 unless data.is_a?(Hash) && data['eventType'] == expected_event_type + data +rescue JSON::ParserError + halt 400 +end + +post '/callbacks/inbound/voice' do + parse_callback('initiate') + unavailable_speak_sentence = Bandwidth::Bxml::SpeakSentence.new('You have reached Vandelay Industries, Kal Varnsen is unavailable at this time.') message_speak_sentence = Bandwidth::Bxml::SpeakSentence.new('At the tone, please record your message, when you have finished recording, you may hang up.') - play_audio = Bandwidth::Bxml::PlayAudio.new('Tone.mp3') - record = Bandwidth::Bxml::Record.new({ recording_available_url: '/callbacks/recordingAvailableCallback' }) - response = Bandwidth::Bxml::Response.new([unavailable_speak_sentence, message_speak_sentence, play_audio, record]) + play_audio = Bandwidth::Bxml::PlayAudio.new('/tone.mp3') + record = Bandwidth::Bxml::Record.new({ recording_available_url: '/callbacks/inbound/voice/recordingAvailable', max_duration: 30 }) + bxml = Bandwidth::Bxml::Response.new([unavailable_speak_sentence, message_speak_sentence, play_audio, record]) - return response.to_bxml + content_type 'application/xml' + bxml.to_bxml end -get '/callbacks/Tone.mp3' do - response.headers['Content-Type'] = 'audio/mp3' - - tone_file = File.open('Tone.mp3') - file_data = tone_file.read - tone_file.close - file_data +get '/tone.mp3' do + send_file 'tone.mp3', type: 'audio/mpeg' end -post '/callbacks/recordingAvailableCallback' do - data = JSON.parse(request.body.read) - - if data['eventType'] == 'recordingAvailable' - recordings_api = Bandwidth::RecordingsApi.new +post '/callbacks/inbound/voice/recordingAvailable' do + data = parse_callback('recordingAvailable') - file_format = data['fileFormat'] - call_id = data['callId'] - recording_id = data['recordingId'] + begin + recording = Bandwidth::RecordingsApi.new.download_call_recording(BW_ACCOUNT_ID, data['callId'], data['recordingId']) - recording = recordings_api.download_call_recording(BW_ACCOUNT_ID, call_id, recording_id) - - File.open("#{recording_id}.#{file_format}", 'wb') do |file| + File.open("#{data['recordingId']}.#{data['fileFormat']}", 'wb') do |file| file.write(recording) end + rescue StandardError => e + warn "Failed to download recording: #{e.message}" + halt 500 end + + status 204 end diff --git a/Tone.mp3 b/tone.mp3 similarity index 100% rename from Tone.mp3 rename to tone.mp3