Skip to content

Commit 723a724

Browse files
authored
Add custom cause code to the verto.bye (#589)
* verto.bue with reason * update cause * only change the
1 parent c65d7c0 commit 723a724

File tree

3 files changed

+310
-8
lines changed

3 files changed

+310
-8
lines changed

README.md

Lines changed: 84 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,93 @@ This package provides a client for Signalwire services and supports different Ja
66

77
### Packages
88

9-
| Project | Description | README | CHANGELOG | Version |
10-
| ------- | ------- | ------- | ------- |:-----:|
11-
| **Node.js** | SignalWire in Node.js | [`README.md`](packages/node/README.md) | [`CHANGELOG.md`](packages/node/CHANGELOG.md) | ![NPM](https://img.shields.io/npm/v/@signalwire/node.svg?color=brightgreen)
12-
| **JavaScript** | SignalWire in the browser! | [`README.md`](packages/js/README.md) | [`CHANGELOG.md`](packages/js/CHANGELOG.md) | ![NPM](https://img.shields.io/npm/v/@signalwire/js/legacy.svg?color=brightgreen)
13-
| **React Native** | SignalWire in a React Native App | [`README.md`](packages/react-native/README.md) | [`CHANGELOG.md`](packages/react-native/CHANGELOG.md) | ![NPM](https://img.shields.io/npm/v/@signalwire/react-native.svg?color=brightgreen)
9+
| Project | Description | README | CHANGELOG | Version |
10+
| ---------------- | -------------------------------- | ---------------------------------------------- | ---------------------------------------------------- | :---------------------------------------------------------------------------------: |
11+
| **Node.js** | SignalWire in Node.js | [`README.md`](packages/node/README.md) | [`CHANGELOG.md`](packages/node/CHANGELOG.md) | ![NPM](https://img.shields.io/npm/v/@signalwire/node.svg?color=brightgreen) |
12+
| **JavaScript** | SignalWire in the browser! | [`README.md`](packages/js/README.md) | [`CHANGELOG.md`](packages/js/CHANGELOG.md) | ![NPM](https://img.shields.io/npm/v/@signalwire/js/legacy.svg?color=brightgreen) |
13+
| **React Native** | SignalWire in a React Native App | [`README.md`](packages/react-native/README.md) | [`CHANGELOG.md`](packages/react-native/CHANGELOG.md) | ![NPM](https://img.shields.io/npm/v/@signalwire/react-native.svg?color=brightgreen) |
1414

1515
Refer to the README of each package for further details.
1616

17+
## Hangup Error Handling
18+
19+
The SDK now properly reports error causes when calls are terminated due to exceptions. When an error occurs during call setup or negotiation, the `verto.bye` message will include specific error information instead of the generic `NORMAL_CLEARING` cause.
20+
21+
### Error Payload Examples
22+
23+
#### Normal Call Clearing
24+
25+
```json
26+
{
27+
"method": "verto.bye",
28+
"params": {
29+
"sessid": "session-id",
30+
"dialogParams": {
31+
/* ... */
32+
},
33+
"cause": "NORMAL_CLEARING",
34+
"causeCode": 16
35+
}
36+
}
37+
```
38+
39+
#### Error Setting Remote SDP
40+
41+
```json
42+
{
43+
"method": "verto.bye",
44+
"params": {
45+
"sessid": "session-id",
46+
"dialogParams": {
47+
/* ... */
48+
},
49+
"cause": "NORMAL_CLEARING",
50+
"causeCode": 50004
51+
}
52+
}
53+
```
54+
55+
#### Error Sending Answer
56+
57+
```json
58+
{
59+
"method": "verto.bye",
60+
"params": {
61+
"sessid": "session-id",
62+
"dialogParams": {
63+
/* ... */
64+
},
65+
"cause": "NORMAL_CLEARING",
66+
"causeCode": 50001
67+
}
68+
}
69+
```
70+
71+
#### Error Sending Attach
72+
73+
```json
74+
{
75+
"method": "verto.bye",
76+
"params": {
77+
"sessid": "session-id",
78+
"dialogParams": {
79+
/* ... */
80+
},
81+
"cause": "NORMAL_CLEARING",
82+
"causeCode": 50002
83+
}
84+
}
85+
```
86+
87+
### Error Cause Codes
88+
89+
- **REMOTE_SDP_ERROR_CAUSE_CODE 50004**: Failed to set the remote session description during WebRTC negotiation
90+
- **EXECUTE_ANSWER_ERROR_CAUSE_CODE 50001**: Failed to send an answer message to the server
91+
- **EXECUTE_ATTACH_ERROR_CAUSE_CODE 50002**: Failed to send an attach message to the server
92+
- **EXECUTE_INVITE_ERROR_CAUSE_CODE 50003**: Failed to send an invite message to the server
93+
94+
All error causes use the special error code `666` to distinguish them from standard telephony cause codes.
95+
1796
## License
1897

1998
Copyright © 2018-2019 SignalWire. It is free software, and may be redistributed under the terms specified in the MIT-LICENSE file.

packages/common/src/webrtc/BaseCall.ts

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,11 @@ import {
4444
} from './WebRTC'
4545
import { MCULayoutEventHandler } from './LayoutHandler'
4646

47+
// Error code for hangup when caused by an error
48+
export const EXECUTE_ANSWER_ERROR_CAUSE_CODE = 50001
49+
export const EXECUTE_ATTACH_ERROR_CAUSE_CODE = 50002
50+
export const EXECUTE_INVITE_ERROR_CAUSE_CODE = 50003
51+
export const REMOTE_SDP_ERROR_CAUSE_CODE = 50004
4752
export default abstract class BaseCall implements IWebRTCCall {
4853
public id: string = ''
4954
public state: string = State[State.New]
@@ -161,6 +166,8 @@ export default abstract class BaseCall implements IWebRTCCall {
161166
const bye = new Bye({
162167
sessid: this.session.sessionid,
163168
dialogParams: this.options,
169+
cause: this.cause,
170+
causeCode: this.causeCode,
164171
})
165172
logger.trace('Verto Bye stacktrace:', stackTrace())
166173
this._execute(bye)
@@ -832,7 +839,10 @@ export default abstract class BaseCall implements IWebRTCCall {
832839
})
833840
.catch((error) => {
834841
logger.error('Call setRemoteDescription Error: ', error)
835-
this.hangup()
842+
this.hangup({
843+
cause: 'NORMAL_CLEARING',
844+
causeCode: REMOTE_SDP_ERROR_CAUSE_CODE,
845+
})
836846
})
837847
}
838848

@@ -870,7 +880,7 @@ export default abstract class BaseCall implements IWebRTCCall {
870880
this._requestAnotherLocalDescription()
871881
return
872882
}
873-
let msg = null
883+
let msg
874884
const tmpParams = {
875885
sessid: this.session.sessionid,
876886
sdp,
@@ -902,7 +912,16 @@ export default abstract class BaseCall implements IWebRTCCall {
902912
})
903913
.catch((error) => {
904914
logger.error(`${this.id} - Sending ${type} error:`, error)
905-
this.hangup()
915+
let causeCode
916+
switch (msg.toSring()) {
917+
case VertoMethod.Answer:
918+
causeCode = EXECUTE_ANSWER_ERROR_CAUSE_CODE
919+
case VertoMethod.Attach:
920+
causeCode = EXECUTE_ATTACH_ERROR_CAUSE_CODE
921+
case VertoMethod.Invite:
922+
causeCode = EXECUTE_INVITE_ERROR_CAUSE_CODE
923+
}
924+
this.hangup({ cause: 'NORMAL_CLEARING', causeCode })
906925
})
907926
}
908927

Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
import BaseCall, {
2+
EXECUTE_ANSWER_ERROR_CAUSE_CODE,
3+
EXECUTE_ATTACH_ERROR_CAUSE_CODE,
4+
REMOTE_SDP_ERROR_CAUSE_CODE,
5+
} from '../../src/webrtc/BaseCall'
6+
import { Bye } from '../../src/messages/Verto'
7+
import { State } from '../../src/webrtc/constants'
8+
9+
// Mock dependencies
10+
jest.mock('../../src/messages/Verto')
11+
jest.mock('../../src/util/logger', () => {
12+
return {
13+
__esModule: true,
14+
default: {
15+
trace: jest.fn(),
16+
debug: jest.fn(),
17+
error: jest.fn(),
18+
info: jest.fn(),
19+
warn: jest.fn(),
20+
},
21+
}
22+
})
23+
24+
// Get mocked version
25+
const MockedBye = Bye as jest.MockedClass<typeof Bye>
26+
27+
// Create a concrete implementation of the abstract BaseCall class for testing
28+
class TestCall extends BaseCall {
29+
constructor(session, opts) {
30+
super(session, opts)
31+
}
32+
33+
invite() {
34+
throw new Error('Method not implemented.')
35+
}
36+
37+
answer(params) {
38+
throw new Error('Method not implemented.')
39+
}
40+
41+
// Add protected method accessor for testing
42+
testExecute(msg) {
43+
return this['_execute'](msg)
44+
}
45+
}
46+
47+
describe('BaseCall', () => {
48+
let mockSession
49+
let call
50+
let mockExecute
51+
52+
beforeEach(() => {
53+
jest.clearAllMocks()
54+
55+
mockExecute = jest.fn().mockResolvedValue({})
56+
57+
mockSession = {
58+
sessionid: 'test-session-id',
59+
execute: mockExecute,
60+
uuid: 'test-uuid',
61+
mediaConstraints: {
62+
audio: true,
63+
video: false,
64+
},
65+
options: {
66+
userVariables: {},
67+
},
68+
calls: {},
69+
}
70+
71+
call = new TestCall(mockSession, {
72+
id: 'test-call-id',
73+
destinationNumber: '1234567890',
74+
})
75+
76+
// Mock the internal execute method by overriding it directly
77+
call['_execute'] = mockExecute
78+
79+
// Mock peer if needed
80+
call.peer = {
81+
instance: {
82+
close: jest.fn(),
83+
},
84+
}
85+
})
86+
87+
describe('hangup method', () => {
88+
describe('cause and causeCode handling', () => {
89+
it('should use default cause and causeCode when not provided', () => {
90+
call.hangup()
91+
92+
expect(call.cause).toBe('NORMAL_CLEARING')
93+
expect(call.causeCode).toBe(16)
94+
})
95+
96+
it('should use custom cause and causeCode when provided', () => {
97+
call.hangup({ cause: 'USER_BUSY', causeCode: 17 })
98+
99+
expect(call.cause).toBe('USER_BUSY')
100+
expect(call.causeCode).toBe(17)
101+
})
102+
103+
it('should use error cause and code for REMOTE_SDP_ERROR_CAUSE_CODE', () => {
104+
call.hangup({
105+
cause: 'NORMAL_CLEARING',
106+
causeCode: REMOTE_SDP_ERROR_CAUSE_CODE,
107+
})
108+
109+
expect(call.cause).toBe('NORMAL_CLEARING')
110+
expect(call.causeCode).toBe(REMOTE_SDP_ERROR_CAUSE_CODE)
111+
})
112+
113+
it('should use error cause and code for EXECUTE_ANSWER_ERROR_CAUSE_CODE', () => {
114+
call.hangup({
115+
cause: 'NORMAL_CLEARING',
116+
causeCode: EXECUTE_ANSWER_ERROR_CAUSE_CODE,
117+
})
118+
119+
expect(call.cause).toBe('NORMAL_CLEARING')
120+
expect(call.causeCode).toBe(EXECUTE_ANSWER_ERROR_CAUSE_CODE)
121+
})
122+
123+
it('should use error cause and code for EXECUTE_ATTACH_ERROR_CAUSE_CODE', () => {
124+
call.hangup({
125+
cause: 'NORMAL_CLEARING',
126+
causeCode: EXECUTE_ATTACH_ERROR_CAUSE_CODE,
127+
})
128+
129+
expect(call.cause).toBe('NORMAL_CLEARING')
130+
expect(call.causeCode).toBe(EXECUTE_ATTACH_ERROR_CAUSE_CODE)
131+
})
132+
})
133+
134+
describe('Bye message creation', () => {
135+
it('should create Bye message with cause and causeCode when execute is true', () => {
136+
const mockByeInstance = { method: 'verto.bye' } as any
137+
MockedBye.mockImplementation(() => mockByeInstance)
138+
139+
call.hangup({ cause: 'TEST_CAUSE', causeCode: 999 })
140+
141+
expect(MockedBye).toHaveBeenCalledWith({
142+
sessid: 'test-session-id',
143+
dialogParams: call.options,
144+
cause: 'TEST_CAUSE',
145+
causeCode: 999,
146+
})
147+
})
148+
149+
it('should not create Bye message when execute is false', () => {
150+
call.hangup({}, false)
151+
152+
expect(MockedBye).not.toHaveBeenCalled()
153+
})
154+
155+
it('should execute the Bye message when execute is true', () => {
156+
const mockByeInstance = { method: 'verto.bye' } as any
157+
MockedBye.mockImplementation(() => mockByeInstance)
158+
159+
call.hangup()
160+
161+
expect(mockExecute).toHaveBeenCalledWith(mockByeInstance)
162+
})
163+
})
164+
})
165+
166+
describe('verto.bye message payload', () => {
167+
it('should include cause and causeCode in the Bye message parameters', () => {
168+
const mockByeInstance = { method: 'verto.bye' } as any
169+
MockedBye.mockImplementation(() => mockByeInstance)
170+
171+
call.hangup({ cause: 'CUSTOM_CAUSE', causeCode: 123 })
172+
173+
const byeCallArgs = MockedBye.mock.calls[0][0]
174+
expect(byeCallArgs).toEqual({
175+
sessid: 'test-session-id',
176+
dialogParams: call.options,
177+
cause: 'CUSTOM_CAUSE',
178+
causeCode: 123,
179+
})
180+
})
181+
182+
it('should include session ID and dialog parameters', () => {
183+
const mockByeInstance = { method: 'verto.bye' } as any
184+
MockedBye.mockImplementation(() => mockByeInstance)
185+
186+
call.hangup()
187+
188+
const byeCallArgs = MockedBye.mock.calls[0][0]
189+
expect(byeCallArgs.sessid).toBe('test-session-id')
190+
expect(byeCallArgs.dialogParams).toBe(call.options)
191+
})
192+
193+
it('should use default values when no parameters provided', () => {
194+
const mockByeInstance = { method: 'verto.bye' } as any
195+
MockedBye.mockImplementation(() => mockByeInstance)
196+
197+
call.hangup()
198+
199+
const byeCallArgs = MockedBye.mock.calls[0][0]
200+
expect(byeCallArgs.cause).toBe('NORMAL_CLEARING')
201+
expect(byeCallArgs.causeCode).toBe(16)
202+
})
203+
})
204+
})

0 commit comments

Comments
 (0)