-
Notifications
You must be signed in to change notification settings - Fork 77
/
Copy pathoutbound.ts
129 lines (121 loc) · 4.25 KB
/
outbound.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
import RequestMessage from "../sip-message/outbound/request.js";
import type InboundMessage from "../sip-message/inbound.js";
import type WebPhone from "../index.js";
import CallSession from "./index.js";
import {
branch,
extractAddress,
fakeDomain,
fakeEmail,
generateAuthorization,
uuid,
withoutTag,
} from "../utils.js";
class OutboundCallSession extends CallSession {
public constructor(webPhone: WebPhone) {
super(webPhone);
this.direction = "outbound";
}
public async call(
callee: string,
callerId?: string,
options?: { headers?: Record<string, string> },
) {
const offer = await this.rtcPeerConnection.createOffer({
iceRestart: true,
});
await this.rtcPeerConnection.setLocalDescription(offer);
// wait for ICE gathering to complete
await new Promise((resolve) => {
this.rtcPeerConnection.onicecandidate = (event) => {
if (event.candidate === null) {
resolve(true);
}
};
setTimeout(() => resolve(false), 3000);
});
const inviteMessage = new RequestMessage(
`INVITE sip:${callee}@${this.webPhone.sipInfo.domain} SIP/2.0`,
{
"Call-Id": uuid(),
Contact: `<sip:${fakeEmail};transport=wss>;expires=60`,
From:
`<sip:${this.webPhone.sipInfo.username}@${this.webPhone.sipInfo.domain}>;tag=${uuid()}`,
To: `<sip:${callee}@${this.webPhone.sipInfo.domain}>`,
Via: `SIP/2.0/WSS ${fakeDomain};branch=${branch()}`,
"Content-Type": "application/sdp",
},
this.rtcPeerConnection.localDescription!.sdp!,
);
if (callerId) {
inviteMessage.headers["P-Asserted-Identity"] =
`sip:${callerId}@${this.webPhone.sipInfo.domain}`;
}
if (options?.headers) {
for (const [key, value] of Object.entries(options.headers)) {
inviteMessage.headers[key] = value;
}
}
const inboundMessage = await this.webPhone.sipClient.request(inviteMessage);
if (inboundMessage.subject.startsWith("SIP/2.0 403 ")) {
// for exmaple, webPhone.sipRegister(0) has been called
return;
}
const proxyAuthenticate = inboundMessage.headers["Proxy-Authenticate"];
const nonce = proxyAuthenticate.match(/, nonce="(.+?)"/)![1];
const newMessage = inviteMessage.fork();
newMessage.headers["Proxy-Authorization"] = generateAuthorization(
this.webPhone.sipInfo,
nonce,
"INVITE",
);
const progressMessage = await this.webPhone.sipClient.request(newMessage);
this.sipMessage = progressMessage;
this.state = "ringing";
this.emit("ringing");
this.localPeer = progressMessage.headers.From;
this.remotePeer = progressMessage.headers.To;
// wait for the call to be answered
// by SIP server design, this happens immediately, even if the callee has not received the INVITE
return new Promise<void>((resolve) => {
const answerHandler = async (message: InboundMessage) => {
if (message.headers.CSeq === this.sipMessage.headers.CSeq) {
this.webPhone.sipClient.off("inboundMessage", answerHandler);
this.state = "answered";
this.emit("answered");
this.rtcPeerConnection.setRemoteDescription({
type: "answer",
sdp: message.body,
});
const ackMessage = new RequestMessage(
`ACK ${extractAddress(this.remotePeer)} SIP/2.0`,
{
"Call-Id": this.callId,
From: this.localPeer,
To: this.remotePeer,
Via: this.sipMessage.headers.Via,
CSeq: this.sipMessage.headers.CSeq.replace(" INVITE", " ACK"),
},
);
await this.webPhone.sipClient.reply(ackMessage);
resolve();
}
};
this.webPhone.sipClient.on("inboundMessage", answerHandler);
});
}
public async cancel() {
const requestMessage = new RequestMessage(
`CANCEL ${extractAddress(this.remotePeer)} SIP/2.0`,
{
"Call-Id": this.callId,
From: this.localPeer,
To: withoutTag(this.remotePeer),
Via: this.sipMessage.headers.Via,
CSeq: this.sipMessage.headers.CSeq.replace(" INVITE", " CANCEL"),
},
);
await this.webPhone.sipClient.request(requestMessage);
}
}
export default OutboundCallSession;