-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathserver.js
288 lines (251 loc) · 8.12 KB
/
server.js
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
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
import express from "express";
import bodyParser from "body-parser";
import BandwidthWebRTC from "@bandwidth/webrtc";
import BandwidthVoice from "@bandwidth/voice";
import { v1 as uuidv1 } from "uuid";
import dotenv from "dotenv";
dotenv.config();
const app = express();
app.use(bodyParser.json());
app.use(express.static("public"));
// config
const port = 3000;
const accountId = process.env.BW_ACCOUNT_ID;
const username = process.env.BW_USERNAME;
const password = process.env.BW_PASSWORD;
// Check to make sure required environment variables are set
if (!accountId || !username || !password) {
console.error(
"ERROR! Please set the BW_ACCOUNT_ID, BW_USERNAME, and BW_PASSWORD environment variables before running this app"
);
process.exit(1);
}
// Global variables
const {Client: WebRTCClient, ApiController: WebRTCController} = BandwidthWebRTC;
const webrtcClient = new WebRTCClient({
basicAuthUserName: username,
basicAuthPassword: password
});
const webRTCController = new WebRTCController(webrtcClient);
const {Client: VoiceClient, ApiController: VoiceController} = BandwidthVoice;
const voiceClient = new VoiceClient({
basicAuthUserName: username,
basicAuthPassword: password
});
const voiceController = new VoiceController(voiceClient);
// create a map of PSTN calls that will persist
const calls = new Map();
// track our session ID and phone call Id
// - if not a demo, these would be stored in persistent storage
let currentSessionId = false;
let currentCallId = false;
/**
* Setup the call and pass info to the browser so they can join
*/
app.get("/startBrowserCall", async (req, res) => {
console.log("setup browser client");
try {
// create the session
let sessionId = await getSessionId("session-test");
let [participant, token] = await createParticipant(uuidv1());
await addParticipantToSession(participant.id, sessionId);
// now that we have added them to the session, we can send back the token they need to join
res.send({
message: "created participant and setup session",
token: token,
});
} catch (error) {
console.log("Failed to start the browser call:", error);
res.status(500).send({ message: "failed to set up participant" });
}
});
/**
* Start the Phone Call
*/
app.get("/startPSTNCall", async (req, res) => {
try {
let sessionId = await getSessionId();
let [participant, token] = await createParticipant(uuidv1());
await addParticipantToSession(participant.id, sessionId);
console.log("start the PSTN call to", process.env.USER_NUMBER);
let callResponse = await initiateCallToPSTN(
process.env.BW_NUMBER,
process.env.USER_NUMBER
);
// store the token with the participant for later use
participant.token = token;
currentCallId = callResponse.result.callId;
calls.set(currentCallId, participant);
res.send({ status: "ringing" });
} catch (error) {
console.log("Failed to start PSTN call:", error);
res.status(500).send({ message: "failed to set up PSTN call" });
}
});
/**
* Bandwidth's Voice API will hit this endpoint when an outgoing call is answered
*/
app.post("/callAnswered", async (req, res) => {
let callId = req.body.callId;
console.log(
`received answered callback for call ${callId} to ${req.body.to}`
);
const participant = calls.get(callId);
if (!participant) {
console.log(`no participant found for ${callId}!`);
res.status(200).send(); // have to return 200 to the BAND server
return;
}
// This is the response payload that we will send back to the Voice API to transfer the call into the WebRTC session
// Use the SDK to generate this BXML
console.log(`transferring call ${callId} to session ${currentSessionId}`);
const bxml = WebRTCController.generateTransferBxml(participant.token, callId);
// Send the payload back to the Voice API
res.contentType("application/xml").send(bxml);
console.log("transferred");
});
/**
* End the Phone Call
*/
app.get("/endPSTNCall", async (req, res) => {
console.log("Hanging up PSTN call");
try {
await getSessionId();
await endCallToPSTN(currentCallId);
res.send({ status: "hungup" });
} catch (error) {
console.log(
`error hanging up ${process.env.USER_NUMBER}:`,
error
);
res.status(500).send({ status: "call hangup failed" });
}
});
/**
* start our server
*/
app.listen(port, () => {
console.log(`Example app listening on port http://localhost:${port}`);
});
// ------------------------------------------------------------------------------------------
// All the functions for interacting with Bandwidth WebRTC services below here
//
/**
* @param sessionId New current session ID
*/
function saveSessionId(sessionId) {
// saved globally for simplicity of demo
currentSessionId = sessionId;
console.log('Saved session %s', sessionId);
}
/**
* Return the session id
* This will either create one via the API, or return the one already created for this session
* @param tag
* @return a Session id
*/
async function getSessionId(tag) {
// check if we've already created a session for this call
// - this is a simplification we're doing for this demo
if (currentSessionId) {
return currentSessionId;
}
console.log("No session found, creating one");
// otherwise, create the session
// tags are useful to audit or manage billing records
const sessionBody = { tag: tag };
try {
let sessionResponse = await webRTCController.createSession(
accountId,
sessionBody
);
// saves it for future use, this would normally be stored with meeting/call/appt details
saveSessionId(sessionResponse.result.id);
return sessionResponse.result.id;
} catch (error) {
console.log("Failed to create session:", error);
throw new Error(
"Error in createSession, error from BAND:" + error.errorMessage
);
}
}
/**
* Create a new participant
* @param tag to tag the participant with, no PII should be placed here
* @return list: (a Participant json object, the participant token)
*/
async function createParticipant(tag) {
// create a participant for this browser user
const participantBody = {
tag: tag,
publishPermissions: ["AUDIO"],
deviceApiVersion: "V3"
};
try {
let createResponse = await webRTCController.createParticipant(
accountId,
participantBody
);
return [createResponse.result.participant, createResponse.result.token];
} catch (error) {
console.log("failed to create Participant", error);
throw new Error(
"Failed to createParticipant, error from BAND:" + error.errorMessage
);
}
}
/**
* @param participantId a Participant id
* @param sessionId The session to add this participant to
*/
async function addParticipantToSession(participantId, sessionId) {
const body = { sessionId: sessionId };
try {
await webRTCController.addParticipantToSession(
accountId,
sessionId,
participantId,
body
);
} catch (error) {
console.log("Error on addParticipant to Session:", error);
throw new Error(
"Failed to addParticipantToSession, error from BAND:" + error.errorMessage
);
}
}
/**
* Start a call out to the PSTN
* @param account_id The id for this account
* @param from_number the FROM on the call
* @param to_number the number to call
*/
async function initiateCallToPSTN(fromNumber, toNumber) {
// call body, see here for more details: https://dev.bandwidth.com/voice/methods/calls/postCalls.html
const body = {
from: fromNumber,
to: toNumber,
applicationId: process.env.BW_VOICE_APPLICATION_ID,
answerUrl: process.env.BASE_CALLBACK_URL + "/callAnswered",
answerMethod: "POST",
callTimeout: "30",
};
return await voiceController.createCall(accountId, body);
}
/**
* End the PSTN call
* @param callId The id of the call
*/
async function endCallToPSTN(callId) {
// call body, see here for more details: https://dev.bandwidth.com/voice/methods/calls/postCallsCallId.html
const body = {
state: "completed",
redirectUrl: "foo"
};
try {
await voiceController.modifyCall(accountId, callId, body);
} catch (error) {
console.log("Failed to hangup the call", error);
throw error;
}
}