Skip to content

Commit

Permalink
Merge branch 'main' into feat/exportAsJSON
Browse files Browse the repository at this point in the history
Co-authored-by: Phil Pick <[email protected]>
  • Loading branch information
mick-potzkai and phil-pick committed May 11, 2022
2 parents abf6335 + 4498250 commit b74b002
Show file tree
Hide file tree
Showing 5 changed files with 143 additions and 1 deletion.
15 changes: 14 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -486,6 +486,12 @@ interface WebhookResponseInterface {
sendToVoicemail: () => VoicemailObject;
rejectCall: (rejectOptions: RejectOptions) => RejectObject;
playAudio: (playOptions: PlayOptions) => PlayObject;
playAudioAndHangUp: (
playOptions: PlayOptions,
client: SipgateIOClient,
callId: string,
timeout?: number
) => Promise<PlayObject>;
gatherDTMF: (gatherOptions: GatherOptions) => GatherObject;
hangUpCall: () => HangUpObject;
}
Expand Down Expand Up @@ -530,7 +536,14 @@ Linux users might want to use mpg123 to convert the file:
mpg123 --rate 8000 --mono -w output.wav input.mp3
```

**Note:** If you want to hang up your call immediately after playing the audio file, you have to use the `gatherDTMF` function with `timeout:0` and `maxDigits:1`.
##### Play audio and hang up

The `playAudioAndHangUp` method accepts an options object of type `PlayOptions` with a single field, the URL to a sound file to be played.
In addition, this also requires a `sipgateIOClient`, a unique `callId` from an current active call and a `timeout` which is optional.

After the audio file has been played and the additional timeout has expired, the call is terminated based on the `callId`.

**Note:** For any information about the audio file please look at [play audio](#play-audio).

##### Gather DTMF tones

Expand Down
1 change: 1 addition & 0 deletions lib/webhook/audioUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export interface ValidateOptions {
bitsPerSample?: number;
sampleRate?: number;
numberOfChannels?: number;
duration?:number;
}

interface ValidateResult {
Expand Down
88 changes: 88 additions & 0 deletions lib/webhook/webhook.test.node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import qs from 'qs';

import * as audioUtils from './audioUtils';
import { WebhookErrorMessage } from './webhook.errors';
import { SipgateIOClient } from '../core/sipgateIOClient';

const mockedGetAudioMetadata = jest.spyOn(audioUtils, 'getAudioMetadata');

Expand Down Expand Up @@ -113,6 +114,15 @@ describe('create webhook module', () => {
});

describe('create webhook-"Response" module', () => {

let mockClient: SipgateIOClient;
jest.spyOn(global, 'setTimeout');

beforeEach(() => {
mockClient = {} as SipgateIOClient;
jest.useFakeTimers();
});

it('should return a gather object without play tag', async () => {
const gatherOptions = { maxDigits: 1, timeout: 2000 };
const gatherObject = {
Expand Down Expand Up @@ -227,6 +237,84 @@ describe('create webhook-"Response" module', () => {
expect(result).toEqual(playObject);
});

it('should return a play audio object for a valid audio file with hangUp and timeOut', async () => {

const duration = 7140;
const timeout = 1000;

mockedGetAudioMetadata.mockReturnValue(
new Promise((resolve) =>
resolve({
container: 'WAVE',
codec: 'PCM',
bitsPerSample: 16,
sampleRate: 8000,
numberOfChannels: 1,
duration: duration/1000
})
)
);

mockClient.delete = jest
.fn()
.mockImplementation(() => Promise.resolve());

const testUrl = 'www.testurl.com';
const callId = '1234567890';

const playOptions = {
announcement: testUrl,
};
const result = await WebhookResponse.playAudioAndHangUp(playOptions, mockClient, callId, timeout);
const playObject = { Play: { Url: testUrl } };

expect(result).toEqual(playObject);
expect(setTimeout).toHaveBeenCalledTimes(1);
expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), duration+timeout);
jest.runAllTimers();
expect(mockClient.delete).toHaveBeenCalledTimes(1);
expect(mockClient.delete).toHaveBeenCalledWith(`/calls/${callId}`);
});

it('should return a play audio object for a valid audio file with hangUp and without timeOut', async () => {

const duration = 7140;
const timeout = 0;

mockedGetAudioMetadata.mockReturnValue(
new Promise((resolve) =>
resolve({
container: 'WAVE',
codec: 'PCM',
bitsPerSample: 16,
sampleRate: 8000,
numberOfChannels: 1,
duration: duration/1000
})
)
);

mockClient.delete = jest
.fn()
.mockImplementation(() => Promise.resolve());

const testUrl = 'www.testurl.com';
const callId = '1234567890';

const playOptions = {
announcement: testUrl,
};
const result = await WebhookResponse.playAudioAndHangUp(playOptions, mockClient, callId);
const playObject = { Play: { Url: testUrl } };

expect(result).toEqual(playObject);
expect(setTimeout).toHaveBeenCalledTimes(1);
expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), duration+timeout);
jest.runAllTimers();
expect(mockClient.delete).toHaveBeenCalledTimes(1);
expect(mockClient.delete).toHaveBeenCalledWith(`/calls/${callId}`);
});

it('should throw an exception for an invalid audio file in play audio', async () => {
mockedGetAudioMetadata.mockReturnValue(
new Promise((resolve) =>
Expand Down
38 changes: 38 additions & 0 deletions lib/webhook/webhook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { isSipgateSignature } from './signatureVerifier';
import { js2xml } from 'xml-js';
import { parse } from 'qs';
import { validateAnnouncementAudio } from './audioUtils';
import { createRTCMModule, SipgateIOClient } from '..';

interface WebhookApiResponse {
_declaration: {
Expand Down Expand Up @@ -330,6 +331,43 @@ export const WebhookResponse: WebhookResponseInterface = {
return { Play: { Url: playOptions.announcement } };
},

playAudioAndHangUp: async (
playOptions: PlayOptions,
client: SipgateIOClient,
callId: string,
timeout?: number
): Promise<PlayObject> => {
const validationResult = await validateAnnouncementAudio(
playOptions.announcement
);

if (!validationResult.isValid) {
throw new Error(
`\n\n${
WebhookErrorMessage.AUDIO_FORMAT_ERROR
}\nYour format was: ${JSON.stringify(validationResult.metadata)}\n`
);
}

let duration = validationResult.metadata.duration
? validationResult.metadata.duration * 1000
: 0;

duration += timeout ? timeout : 0;

setTimeout(() => {
const rtcm = createRTCMModule(client);
try {
rtcm.hangUp({ callId });
} catch (error) {
console.log(error)
return;
}
}, duration);

return { Play: { Url: playOptions.announcement } };
},

redirectCall: (redirectOptions: RedirectOptions): RedirectObject => {
return {
Dial: {
Expand Down
2 changes: 2 additions & 0 deletions lib/webhook/webhook.types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Server } from 'http';
import { SipgateIOClient } from '..';

export enum EventType {
NEW_CALL = 'newCall',
Expand Down Expand Up @@ -50,6 +51,7 @@ export interface WebhookResponseInterface {
redirectCall: (redirectOptions: RedirectOptions) => RedirectObject;
gatherDTMF: (gatherOptions: GatherOptions) => Promise<GatherObject>;
playAudio: (playOptions: PlayOptions) => Promise<PlayObject>;
playAudioAndHangUp: (playOptions: PlayOptions, client: SipgateIOClient, callId: string, timeout?: number) => Promise<PlayObject>;
rejectCall: (rejectOptions: RejectOptions) => RejectObject;
hangUpCall: () => HangUpObject;
sendToVoicemail: () => VoicemailObject;
Expand Down

0 comments on commit b74b002

Please sign in to comment.