@telegenta/webclient 2.0.10 → 2.0.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/README.md +0 -0
  2. package/babel.config.js +0 -0
  3. package/browser/telegenta.min.js +1 -1
  4. package/demos/add_phone_number.html +0 -0
  5. package/demos/calldata_available.html +0 -0
  6. package/demos/embedded_config.html +94 -94
  7. package/demos/event_client.html +225 -225
  8. package/demos/phone_client.html +557 -557
  9. package/demos/styles.css +0 -0
  10. package/demos/telegenta.min.js +1 -0
  11. package/docs/EventClient.html +1 -1
  12. package/docs/Phone.html +1 -1
  13. package/docs/Session.html +1 -1
  14. package/docs/fonts/OpenSans-Bold-webfont.eot +0 -0
  15. package/docs/fonts/OpenSans-Bold-webfont.svg +0 -0
  16. package/docs/fonts/OpenSans-Bold-webfont.woff +0 -0
  17. package/docs/fonts/OpenSans-BoldItalic-webfont.eot +0 -0
  18. package/docs/fonts/OpenSans-BoldItalic-webfont.svg +0 -0
  19. package/docs/fonts/OpenSans-BoldItalic-webfont.woff +0 -0
  20. package/docs/fonts/OpenSans-Italic-webfont.eot +0 -0
  21. package/docs/fonts/OpenSans-Italic-webfont.svg +0 -0
  22. package/docs/fonts/OpenSans-Italic-webfont.woff +0 -0
  23. package/docs/fonts/OpenSans-Light-webfont.eot +0 -0
  24. package/docs/fonts/OpenSans-Light-webfont.svg +0 -0
  25. package/docs/fonts/OpenSans-Light-webfont.woff +0 -0
  26. package/docs/fonts/OpenSans-LightItalic-webfont.eot +0 -0
  27. package/docs/fonts/OpenSans-LightItalic-webfont.svg +0 -0
  28. package/docs/fonts/OpenSans-LightItalic-webfont.woff +0 -0
  29. package/docs/fonts/OpenSans-Regular-webfont.eot +0 -0
  30. package/docs/fonts/OpenSans-Regular-webfont.svg +0 -0
  31. package/docs/fonts/OpenSans-Regular-webfont.woff +0 -0
  32. package/docs/global.html +1 -1
  33. package/docs/index.html +1 -1
  34. package/docs/scripts/linenumber.js +0 -0
  35. package/docs/scripts/prettify/Apache-License-2.0.txt +0 -0
  36. package/docs/scripts/prettify/lang-css.js +0 -0
  37. package/docs/scripts/prettify/prettify.js +0 -0
  38. package/docs/styles/jsdoc-default.css +0 -0
  39. package/docs/styles/prettify-jsdoc.css +0 -0
  40. package/docs/styles/prettify-tomorrow.css +0 -0
  41. package/embedded_client/embedded_phone.js +0 -0
  42. package/embedded_client/index.html +0 -0
  43. package/embedded_client/server.php +0 -0
  44. package/embedded_client/styles.css +0 -0
  45. package/embedded_client/tiny_rest_client.php +0 -0
  46. package/embedded_client/webphone_sprites.png +0 -0
  47. package/es5/event-client.js +175 -219
  48. package/es5/index.js +0 -0
  49. package/es5/phone-client.js +771 -1173
  50. package/package.json +1 -1
  51. package/src/event-client.js +1 -1
  52. package/src/index.js +0 -0
  53. package/src/phone-client.js +1 -1
  54. package/webpack.config.js +0 -0
@@ -1,558 +1,558 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="UTF-8">
5
- <title>Telegenta Phone client Demo</title>
6
- <script src="telegenta.min.js"></script>
7
- <link rel="stylesheet" type="text/css" href="styles.css">
8
- </head>
9
-
10
- <body>
11
- <!-- Logo -->
12
- <div class="groupelementsnoline" id="Telegenta"></div>
13
- <!-- Logo end -->
14
- <div>
15
- <div class="groupelementsnoline">
16
- <h2>Telegenta Phone Client Demo</h2>
17
- </div>
18
- <div class="groupelements">
19
- <H3>STEP 1: Get account list</H3>
20
- <div class="inputelements">
21
- <label for="api_key">API Key</label>
22
- <input type="text" size="40" id="api_key" placeholder="Enter API Key"
23
- value="">
24
- <button onclick="populateSelectElements()">Get sip accounts</button>
25
- </div>
26
- </div>
27
-
28
- <div class="groupelements">
29
- <H3>STEP 2: Start phone</H3>
30
- <div class="inputelements">
31
- <label for="account_selector">SIP Accounts</label><span class="smallright"><small>Connection: <span
32
- id="conn_status">DISCONNECTED</span></small></span>
33
- <select id="account_selector">
34
- <option value="" disabled selected>Waiting for SIP accounts</option>
35
- </select>
36
- <button onclick="startPhoneClient()">Start phone and use selected account</button>
37
- <button onclick="stopPhoneClient()">Stop the phone client</button>
38
- </div>
39
- </div>
40
-
41
- <div class="groupelements">
42
- <H3>STEP 3: Add a new call</H3>
43
- <div class="inputelements">
44
- <label for="destination_number">Number to call</label><span class="smallright"><small>Status: <span
45
- id="call_status">IDLE</span>
46
- &nbsp;| Duration: <span id="call_duration">-</span></small></span>
47
- <input type="text" size="40" id="destination_number" placeholder="Enter phone number" value="">
48
- <button onclick="makeCall()">Call number now</button>
49
- </div>
50
- </div>
51
-
52
- <div class="groupelements">
53
- <H3>Transfer call</H3>
54
-
55
- <div class="inputelements">
56
- <h3 id="transfer_from"></h3>
57
- <label for="transfer_selector">Select call: </label>
58
- <select id="transfer_selector">
59
- <option value="" disabled selected>No call to transfer</option>
60
- </select>
61
-
62
- <button onclick="doCallTransfer()">Transfer call</button>
63
- </div>
64
- </div>
65
-
66
- <div class="groupelements">
67
- <H3>Listen call</H3>
68
-
69
- <div class="inputelements">
70
- <h3>Listen to another call</h3>
71
- <label for="listen_key">Call listen key</label>
72
- <input type="text" size="40" id="listen_key" placeholder="Enter call listen key" value="">
73
- <button onclick="doListen()">Listen now</button>
74
- </div>
75
- </div>
76
-
77
- <div class="groupelements">
78
- <H3>Playback</H3>
79
- <div class="inputelements">
80
- <label for="media_file_selector">Media files</label>
81
- <select id="media_file_selector">
82
- <option value="" disabled selected>Select media file</option>
83
- </select>
84
- </div>
85
- </div>
86
-
87
- <div class="groupelementsnoline">
88
- <H3>Active calls</H3>
89
- <table id="session_table">
90
- <tr>
91
- <th>Direction</th>
92
- <th>Destination number</th>
93
- <th>State</th>
94
- <th>Duration</th>
95
- <th>Quality metrics</th>
96
- <th>Mic muted</th>
97
- <th>Recording</th>
98
- <th>On hold</th>
99
- <th>Actions</th>
100
- </tr>
101
- </table>
102
- </div>
103
- <div class="groupelementsnoline">
104
- <small>Tip: Press F12 (developer console) to get additional information</small>
105
- </div>
106
- </div>
107
- </body>
108
-
109
- <!-- Javascript stuff -->
110
- <script>
111
- const API_URL = 'https://api.telegenta.com/api';
112
- let phoneClient = null;
113
- let logger;
114
- let transferSession;
115
-
116
- // Start when DOM is ready
117
- window.onload = function () {
118
- // Create phone instance first
119
- logger = new Telegenta.Logger.LogHandler(Telegenta.Logger.LOGLVL.DEBUG);
120
- phoneClient = new Telegenta.Phone(logger);
121
-
122
- // Check browser is supported first
123
- if (!phoneClient.browserSupported) {
124
- console.error('Your browser is not supported');
125
- }
126
- };
127
-
128
-
129
- /*
130
- * STEP 1: Get a list of sip accounts from API server and populate select box with these accounts
131
- */
132
- function populateSelectElements() {
133
- // SIP Accounts
134
- jsonApiGetRequest('/sip-trunks/').then(function (data) {
135
- console.info(`Fetched ${data.results.length} sip accounts:`);
136
- console.debug(data);
137
-
138
- // Now reset and populate select box with accounts
139
- document.getElementById('account_selector').innerHTML = '';
140
- for (const account of data.results) {
141
- document.getElementById('account_selector').appendChild(new Option(account.name, account.id));
142
- }
143
- }, function (reason) {
144
- console.error(`Request failed ${reason}`);
145
- });
146
-
147
- // Media files
148
- jsonApiGetRequest('/media-files/').then(function (data) {
149
- console.info(`Fetched ${data.results.length} media files:`);
150
- console.debug(data);
151
-
152
- // Now reset and populate select box with accounts
153
- document.getElementById('media_file_selector').innerHTML = '';
154
- for (const mfile of data.results) {
155
- document.getElementById('media_file_selector').appendChild(new Option(mfile.name, mfile.id));
156
- }
157
- }, function (reason) {
158
- console.error(`Request failed ${reason}`);
159
- });
160
- }
161
-
162
- /*
163
- * STEP 2: Get a new call_token from API and start phone client with account chosen in select box
164
- */
165
- function startPhoneClient() {
166
- const accountId = document.getElementById('account_selector').value;
167
- console.info(`Starting phone client with account id ${accountId}`);
168
-
169
- // Create call session and get provision token
170
- jsonApiGetRequest(`/get-call-session/?id=${accountId}`).then(function (data) {
171
- console.info('Fetched a new call session');
172
- console.debug(data);
173
-
174
- // Now prepare media and get access to microphone
175
- phoneClient.prepareMedia().then(function () {
176
- // Media was prepared, now hook up events to display whats going on
177
-
178
- // Connection state shows the client connection to the phone services
179
- phoneClient.on('connectionStateChange', function (event) {
180
- console.info(`Phone connection is now: ${event.state}`);
181
- document.getElementById('conn_status').innerHTML = event.state;
182
- });
183
-
184
- // Get local information about call after it has ended
185
- phoneClient.on('callEnded', function (data) {
186
- console.info('Call ended event:');
187
- console.info(data);
188
- });
189
-
190
- // Get remote information about call after it has ended
191
- phoneClient.on('callDataInfo', function (data) {
192
- console.info('Call data info event:');
193
- console.info(data);
194
- });
195
-
196
- // New session created
197
- phoneClient.on('sessionCreated', function (data) {
198
- newSession(data.session);
199
- });
200
-
201
- // Session removed
202
- phoneClient.on('sessionRemoved', function (data) {
203
- console.log(data);
204
- removeSession(data.session);
205
- });
206
-
207
- // Show incoming call
208
- phoneClient.on('incomingCall', function (data) {
209
- console.log(`Incoming call from ${data.originatingNumber}`);
210
- });
211
-
212
- // Now start phone with the following options
213
- const opts = {
214
- // Enable event client
215
- connectEventService: true,
216
-
217
- // Subscribe to detailed information about call after processing
218
- useCallDataInfoEvent: true,
219
-
220
- // Enable incoming calls (you must set a phone number to call the account also)
221
- register: true,
222
-
223
- // Configure client with provision token
224
- provisionToken: data.provision_token,
225
-
226
- // Enable signal tones (busy, invalid, etc)
227
- playSignalTones: true,
228
-
229
- // Enable call initialized tone
230
- playCallInitializedTone: true
231
- };
232
-
233
- try {
234
- phoneClient.start(opts);
235
- console.info('Media prepared and phone started');
236
- } catch (e) {
237
- console.error('Unable to start phone');
238
- console.error(e);
239
- }
240
- }, function (reason) {
241
- console.error('Unable to get media permission: ' + reason);
242
- });
243
- }, function (reason) {
244
- console.error(`Request failed ${reason}`);
245
- });
246
- }
247
-
248
- // Stop phone client
249
- function stopPhoneClient() {
250
- phoneClient.stop();
251
- }
252
-
253
- /*
254
- * STEP 3: Make a test call
255
- */
256
- function makeCall() {
257
- const destination_number = document.getElementById('destination_number').value;
258
- phoneClient.call(destination_number, {
259
- // Change this to number of days to keep recording (enables recording)
260
- record: 0,
261
-
262
- // Change this maximum amount in customer currency the call must cost before hanging up (0 = no checking)
263
- maximumCallCost: 0,
264
-
265
- // Include this to force a specific caller id (use id of phone number from api service)
266
- // explicitCallerId: 35,
267
-
268
- // Include this to force a specific smart cid (use id of shot-cid from api service)
269
- // explicitShortCallerId: 1,
270
-
271
- // Configure answer call timeout
272
- answerCallTimeout: 0,
273
-
274
- // Add meta info to call
275
- metaInfo: 'You can add up to 256 bytes of meta info for your call (including json like {"type": "json"})'
276
- });
277
- }
278
-
279
- // Set the source of call transfer and populate transfer destination selector
280
- function setTransferSession(session) {
281
- transferSession = session;
282
-
283
- var otherSessions = session.getOtherSessions();
284
- if (otherSessions.length < 1) {
285
- document.getElementById('transfer_from').innerHTML = 'Requires two active sessions to perform transfer!';
286
- return;
287
- }
288
-
289
- // Show source call destination number
290
- document.getElementById('transfer_from').innerHTML = session.destinationNumber + ' => ';
291
-
292
- // Add all other sessions to transfer selector
293
- document.getElementById('transfer_selector').innerHTML = '';
294
-
295
- for (const tSession of otherSessions) {
296
- document.getElementById('transfer_selector').appendChild(new Option(tSession.destinationNumber, tSession.uuid));
297
- }
298
- }
299
-
300
- // Transfer call from "transferSession" to item in transfer_selector
301
- function doCallTransfer() {
302
- if (!transferSession) {
303
- console.error('No session selected');
304
- return;
305
- }
306
-
307
- // Use session uuid from selectbox and get session object
308
- const dstSession = phoneClient.getSessionByUUID(document.getElementById('transfer_selector').value);
309
-
310
- // Now do transfer
311
- transferSession.transfer(dstSession);
312
- }
313
-
314
- // Create listen session
315
- function doListen() {
316
- phoneClient.listen(document.getElementById('listen_key').value);
317
- }
318
-
319
- // Playback message
320
- function playback(session) {
321
- const hangup_after = true;
322
- session.playback(document.getElementById('media_file_selector').value, hangup_after).then(() => {
323
- console.info('Playback started');
324
- })
325
- }
326
-
327
- /*
328
- * Helper functions
329
- */
330
-
331
- // Create new session
332
- // Add table row for session using session_<uuid> as element id,
333
- // (We do this so we can find and remove row when session ends)
334
- function newSession(session) {
335
- const rowId = `session_${session.uuid}`;
336
-
337
- // Create row in table for session
338
- const table = document.getElementById("session_table");
339
- const newRow = table.insertRow();
340
- newRow.id = rowId;
341
- for (let i = 0; i < table.rows[0].cells.length; i++)
342
- newRow.insertCell();
343
-
344
- // Set cells
345
- newRow.cells[0].innerText = session.direction;
346
-
347
- // Show name of local number called for inbound
348
- if (session.localNumberName) {
349
- newRow.cells[1].innerText = session.destinationNumber + ' (' + session.localNumberName + ')';
350
- } else {
351
- newRow.cells[1].innerText = session.destinationNumber;
352
- }
353
-
354
- // Hookup events
355
- // Session state show the current calling state
356
- session.on('sessionStateChange', function (event) {
357
- console.info(`Phone session is now: ${event.state}`);
358
- newRow.cells[2].innerHTML = event.state;
359
- // Add failure cause
360
- if (event.state === 'FAILED') {
361
- newRow.cells[2].innerHTML = newRow.cells[2].innerHTML + ` (${event.cause})`;
362
- }
363
- updateButtons(newRow.cells[8], session);
364
- });
365
-
366
- // Get record state events
367
- newRow.cells[6].innerHTML = session.isRecording;
368
- session.on('recordingStateChange', function (data) {
369
- console.info(`Recording state changed to ${data.state}`);
370
- newRow.cells[6].innerHTML = data.state;
371
- });
372
-
373
- // Get mute state events
374
- newRow.cells[5].innerHTML = session.isMuted;
375
- session.on('muteStateChange', function (data) {
376
- console.info(`Microphone mute state changed to ${data.state}`);
377
- newRow.cells[5].innerHTML = data.state;
378
- });
379
-
380
- // Get hold state events
381
- newRow.cells[7].innerHTML = session.isOnHold;
382
- session.on('holdStateChange', function (data) {
383
- console.info(`Call hold state changed to ${data.state}`);
384
- newRow.cells[7].innerHTML = data.state;
385
- });
386
-
387
- // Get call duration update events
388
- session.on('callDurationUpdate', function (data) {
389
- newRow.cells[3].innerHTML = `${data.durationStr} (${data.activeDurationStr})`;
390
- });
391
-
392
- // Get call duration update events
393
- session.on('callQualityReportUpdate', function (data) {
394
- console.info('Call quality report:');
395
- console.info(data);
396
- newRow.cells[4].innerHTML = `${data.delta.effectiveLatency.toFixed(1)} ms (${data.delta.rFactor.toFixed(1)} R)`;
397
- });
398
-
399
- // Get local information about call after it has ended
400
- session.on('callEnded', function (data) {
401
- console.info('Call ended event:');
402
- console.debug(data);
403
- });
404
-
405
- session.on('whisperModeChange', function (data) {
406
- console.info('Whisper mode: ' + data.mode);
407
- });
408
-
409
- }
410
-
411
- // Remove table row for session
412
- function removeSession(session) {
413
- const row = document.getElementById(`session_${session.uuid}`);
414
- if (row) {
415
- row.parentNode.removeChild(row);
416
- }
417
- }
418
-
419
- // Add buttons based on session state on element (the table row)
420
- function updateButtons(element, session) {
421
- // Remove all buttons first
422
- let e = element.firstElementChild;
423
- while (e) {
424
- e.remove();
425
- e = element.firstElementChild;
426
- }
427
-
428
- // Put session on TD element, so buttons can use it
429
- element._session = session;
430
-
431
- // NON TERMINATED/INCOMING/FAILED BUTTONS
432
- if (session.state !== 'TERMINATED' && session.state !== 'INCOMING' && session.state !== 'FAILED') {
433
- const btn = document.createElement("BUTTON");
434
- btn.innerHTML = 'Hangup';
435
- btn.onclick = function () {
436
- this.parentNode._session.hangup();
437
- };
438
- element.appendChild(btn)
439
- }
440
-
441
- if (session.direction === 'OUTBOUND' || session.direction === 'INBOUND') {
442
- // ACTIVE CALL BUTTONS
443
- if (session.state === 'ACTIVE' || session.state === 'ON_HOLD') {
444
- const btn = document.createElement("BUTTON");
445
- btn.innerHTML = 'Play tone';
446
- btn.onclick = function () {
447
- phoneClient.playTone(1000, 440, 880, function () {
448
- console.info('Play tone complete!')
449
- });
450
- };
451
- element.appendChild(btn);
452
-
453
- const btn2 = document.createElement("BUTTON");
454
- btn2.innerHTML = 'Send DTMF 1';
455
- btn2.onclick = function () {
456
- this.parentNode._session.sendDTMF(1);
457
- };
458
- element.appendChild(btn2);
459
-
460
- const btn3 = document.createElement("BUTTON");
461
- btn3.innerHTML = 'Toggle mute mic';
462
- btn3.onclick = function () {
463
- this.parentNode._session.toggleMute();
464
- };
465
- element.appendChild(btn3);
466
-
467
- const btn4 = document.createElement("BUTTON");
468
- btn4.innerHTML = 'Toggle recording';
469
- btn4.onclick = function () {
470
- this.parentNode._session.toggleRecording();
471
- };
472
- element.appendChild(btn4);
473
-
474
- const btn5 = document.createElement("BUTTON");
475
- btn5.innerHTML = 'Toggle hold';
476
- btn5.onclick = function () {
477
- this.parentNode._session.toggleHold();
478
- };
479
- element.appendChild(btn5);
480
-
481
- const btn6 = document.createElement("BUTTON");
482
- btn6.innerHTML = 'Transfer';
483
- btn6.onclick = function () {
484
- setTransferSession(this.parentNode._session);
485
- };
486
- element.appendChild(btn6);
487
-
488
- const btn7 = document.createElement("BUTTON");
489
- btn7.innerHTML = 'Playback';
490
- btn7.onclick = function () {
491
- playback(this.parentNode._session);
492
- };
493
- element.appendChild(btn7);
494
- }
495
-
496
- // INCOMING BUTTONS
497
- if (session.state === 'INCOMING') {
498
- const btn = document.createElement("BUTTON");
499
- btn.innerHTML = 'Answer';
500
- btn.onclick = function () {
501
- this.parentNode._session.answer(metaInfo = 'When answering calls you can add meta info as well');
502
- };
503
- element.appendChild(btn);
504
-
505
- const btn2 = document.createElement("BUTTON");
506
- btn2.innerHTML = 'Reject';
507
- btn2.onclick = function () {
508
- this.parentNode._session.reject();
509
- };
510
- element.appendChild(btn2);
511
- }
512
- }
513
-
514
- // LISTEN BUTTONS
515
- if (session.direction === 'LISTEN' && session.state === 'ACTIVE') {
516
- const btn = document.createElement("BUTTON");
517
- btn.innerHTML = 'Start whisper';
518
- btn.onclick = function () {
519
- this.parentNode._session.setWhisperMode(1);
520
- };
521
- element.appendChild(btn);
522
-
523
- const btn2 = document.createElement("BUTTON");
524
- btn2.innerHTML = 'End whisper';
525
- btn2.onclick = function () {
526
- this.parentNode._session.setWhisperMode(0);
527
- };
528
- element.appendChild(btn2);
529
- }
530
- }
531
-
532
- // Perform authenticated ajax request to api server and return json payload on success
533
- function jsonApiGetRequest(path) {
534
- const xhr = new XMLHttpRequest();
535
- const url = API_URL + path;
536
- xhr.open('GET', url, true);
537
- xhr.responseType = 'json';
538
- const apiToken = document.getElementById('api_key').value;
539
- xhr.setRequestHeader('Authorization', 'Bearer ' + apiToken);
540
- xhr.setRequestHeader('Accept', 'application/json');
541
-
542
- return new Promise(function (resolve, reject) {
543
- xhr.onreadystatechange = function () {
544
- if (xhr.readyState === 4) {
545
- if (xhr.status !== 200) {
546
- reject(xhr.status + ' ' + xhr.statusText);
547
- return;
548
- }
549
- resolve(xhr.response);
550
- }
551
- };
552
- console.debug(`API Request: ${url}`);
553
- xhr.send();
554
- })
555
- }
556
- </script>
557
-
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <title>Telegenta Phone client Demo</title>
6
+ <script src="telegenta.min.js"></script>
7
+ <link rel="stylesheet" type="text/css" href="styles.css">
8
+ </head>
9
+
10
+ <body>
11
+ <!-- Logo -->
12
+ <div class="groupelementsnoline" id="Telegenta"></div>
13
+ <!-- Logo end -->
14
+ <div>
15
+ <div class="groupelementsnoline">
16
+ <h2>Telegenta Phone Client Demo</h2>
17
+ </div>
18
+ <div class="groupelements">
19
+ <H3>STEP 1: Get account list</H3>
20
+ <div class="inputelements">
21
+ <label for="api_key">API Key</label>
22
+ <input type="text" size="40" id="api_key" placeholder="Enter API Key"
23
+ value="">
24
+ <button onclick="populateSelectElements()">Get sip accounts</button>
25
+ </div>
26
+ </div>
27
+
28
+ <div class="groupelements">
29
+ <H3>STEP 2: Start phone</H3>
30
+ <div class="inputelements">
31
+ <label for="account_selector">SIP Accounts</label><span class="smallright"><small>Connection: <span
32
+ id="conn_status">DISCONNECTED</span></small></span>
33
+ <select id="account_selector">
34
+ <option value="" disabled selected>Waiting for SIP accounts</option>
35
+ </select>
36
+ <button onclick="startPhoneClient()">Start phone and use selected account</button>
37
+ <button onclick="stopPhoneClient()">Stop the phone client</button>
38
+ </div>
39
+ </div>
40
+
41
+ <div class="groupelements">
42
+ <H3>STEP 3: Add a new call</H3>
43
+ <div class="inputelements">
44
+ <label for="destination_number">Number to call</label><span class="smallright"><small>Status: <span
45
+ id="call_status">IDLE</span>
46
+ &nbsp;| Duration: <span id="call_duration">-</span></small></span>
47
+ <input type="text" size="40" id="destination_number" placeholder="Enter phone number" value="">
48
+ <button onclick="makeCall()">Call number now</button>
49
+ </div>
50
+ </div>
51
+
52
+ <div class="groupelements">
53
+ <H3>Transfer call</H3>
54
+
55
+ <div class="inputelements">
56
+ <h3 id="transfer_from"></h3>
57
+ <label for="transfer_selector">Select call: </label>
58
+ <select id="transfer_selector">
59
+ <option value="" disabled selected>No call to transfer</option>
60
+ </select>
61
+
62
+ <button onclick="doCallTransfer()">Transfer call</button>
63
+ </div>
64
+ </div>
65
+
66
+ <div class="groupelements">
67
+ <H3>Listen call</H3>
68
+
69
+ <div class="inputelements">
70
+ <h3>Listen to another call</h3>
71
+ <label for="listen_key">Call listen key</label>
72
+ <input type="text" size="40" id="listen_key" placeholder="Enter call listen key" value="">
73
+ <button onclick="doListen()">Listen now</button>
74
+ </div>
75
+ </div>
76
+
77
+ <div class="groupelements">
78
+ <H3>Playback</H3>
79
+ <div class="inputelements">
80
+ <label for="media_file_selector">Media files</label>
81
+ <select id="media_file_selector">
82
+ <option value="" disabled selected>Select media file</option>
83
+ </select>
84
+ </div>
85
+ </div>
86
+
87
+ <div class="groupelementsnoline">
88
+ <H3>Active calls</H3>
89
+ <table id="session_table">
90
+ <tr>
91
+ <th>Direction</th>
92
+ <th>Destination number</th>
93
+ <th>State</th>
94
+ <th>Duration</th>
95
+ <th>Quality metrics</th>
96
+ <th>Mic muted</th>
97
+ <th>Recording</th>
98
+ <th>On hold</th>
99
+ <th>Actions</th>
100
+ </tr>
101
+ </table>
102
+ </div>
103
+ <div class="groupelementsnoline">
104
+ <small>Tip: Press F12 (developer console) to get additional information</small>
105
+ </div>
106
+ </div>
107
+ </body>
108
+
109
+ <!-- Javascript stuff -->
110
+ <script>
111
+ const API_URL = 'https://api.telegenta.com/api';
112
+ let phoneClient = null;
113
+ let logger;
114
+ let transferSession;
115
+
116
+ // Start when DOM is ready
117
+ window.onload = function () {
118
+ // Create phone instance first
119
+ logger = new Telegenta.Logger.LogHandler(Telegenta.Logger.LOGLVL.DEBUG);
120
+ phoneClient = new Telegenta.Phone(logger);
121
+
122
+ // Check browser is supported first
123
+ if (!phoneClient.browserSupported) {
124
+ console.error('Your browser is not supported');
125
+ }
126
+ };
127
+
128
+
129
+ /*
130
+ * STEP 1: Get a list of sip accounts from API server and populate select box with these accounts
131
+ */
132
+ function populateSelectElements() {
133
+ // SIP Accounts
134
+ jsonApiGetRequest('/sip-trunks/').then(function (data) {
135
+ console.info(`Fetched ${data.results.length} sip accounts:`);
136
+ console.debug(data);
137
+
138
+ // Now reset and populate select box with accounts
139
+ document.getElementById('account_selector').innerHTML = '';
140
+ for (const account of data.results) {
141
+ document.getElementById('account_selector').appendChild(new Option(account.name, account.id));
142
+ }
143
+ }, function (reason) {
144
+ console.error(`Request failed ${reason}`);
145
+ });
146
+
147
+ // Media files
148
+ jsonApiGetRequest('/media-files/').then(function (data) {
149
+ console.info(`Fetched ${data.results.length} media files:`);
150
+ console.debug(data);
151
+
152
+ // Now reset and populate select box with accounts
153
+ document.getElementById('media_file_selector').innerHTML = '';
154
+ for (const mfile of data.results) {
155
+ document.getElementById('media_file_selector').appendChild(new Option(mfile.name, mfile.id));
156
+ }
157
+ }, function (reason) {
158
+ console.error(`Request failed ${reason}`);
159
+ });
160
+ }
161
+
162
+ /*
163
+ * STEP 2: Get a new call_token from API and start phone client with account chosen in select box
164
+ */
165
+ function startPhoneClient() {
166
+ const accountId = document.getElementById('account_selector').value;
167
+ console.info(`Starting phone client with account id ${accountId}`);
168
+
169
+ // Create call session and get provision token
170
+ jsonApiGetRequest(`/get-call-session/?id=${accountId}`).then(function (data) {
171
+ console.info('Fetched a new call session');
172
+ console.debug(data);
173
+
174
+ // Now prepare media and get access to microphone
175
+ phoneClient.prepareMedia().then(function () {
176
+ // Media was prepared, now hook up events to display whats going on
177
+
178
+ // Connection state shows the client connection to the phone services
179
+ phoneClient.on('connectionStateChange', function (event) {
180
+ console.info(`Phone connection is now: ${event.state}`);
181
+ document.getElementById('conn_status').innerHTML = event.state;
182
+ });
183
+
184
+ // Get local information about call after it has ended
185
+ phoneClient.on('callEnded', function (data) {
186
+ console.info('Call ended event:');
187
+ console.info(data);
188
+ });
189
+
190
+ // Get remote information about call after it has ended
191
+ phoneClient.on('callDataInfo', function (data) {
192
+ console.info('Call data info event:');
193
+ console.info(data);
194
+ });
195
+
196
+ // New session created
197
+ phoneClient.on('sessionCreated', function (data) {
198
+ newSession(data.session);
199
+ });
200
+
201
+ // Session removed
202
+ phoneClient.on('sessionRemoved', function (data) {
203
+ console.log(data);
204
+ removeSession(data.session);
205
+ });
206
+
207
+ // Show incoming call
208
+ phoneClient.on('incomingCall', function (data) {
209
+ console.log(`Incoming call from ${data.originatingNumber}`);
210
+ });
211
+
212
+ // Now start phone with the following options
213
+ const opts = {
214
+ // Enable event client
215
+ connectEventService: true,
216
+
217
+ // Subscribe to detailed information about call after processing
218
+ useCallDataInfoEvent: true,
219
+
220
+ // Enable incoming calls (you must set a phone number to call the account also)
221
+ register: true,
222
+
223
+ // Configure client with provision token
224
+ provisionToken: data.provision_token,
225
+
226
+ // Enable signal tones (busy, invalid, etc)
227
+ playSignalTones: true,
228
+
229
+ // Enable call initialized tone
230
+ playCallInitializedTone: true
231
+ };
232
+
233
+ try {
234
+ phoneClient.start(opts);
235
+ console.info('Media prepared and phone started');
236
+ } catch (e) {
237
+ console.error('Unable to start phone');
238
+ console.error(e);
239
+ }
240
+ }, function (reason) {
241
+ console.error('Unable to get media permission: ' + reason);
242
+ });
243
+ }, function (reason) {
244
+ console.error(`Request failed ${reason}`);
245
+ });
246
+ }
247
+
248
+ // Stop phone client
249
+ function stopPhoneClient() {
250
+ phoneClient.stop();
251
+ }
252
+
253
+ /*
254
+ * STEP 3: Make a test call
255
+ */
256
+ function makeCall() {
257
+ const destination_number = document.getElementById('destination_number').value;
258
+ phoneClient.call(destination_number, {
259
+ // Change this to number of days to keep recording (enables recording)
260
+ record: 0,
261
+
262
+ // Change this maximum amount in customer currency the call must cost before hanging up (0 = no checking)
263
+ maximumCallCost: 0,
264
+
265
+ // Include this to force a specific caller id (use id of phone number from api service)
266
+ // explicitCallerId: 35,
267
+
268
+ // Include this to force a specific smart cid (use id of shot-cid from api service)
269
+ // explicitShortCallerId: 1,
270
+
271
+ // Configure answer call timeout
272
+ answerCallTimeout: 0,
273
+
274
+ // Add meta info to call
275
+ metaInfo: 'You can add up to 256 bytes of meta info for your call (including json like {"type": "json"})'
276
+ });
277
+ }
278
+
279
+ // Set the source of call transfer and populate transfer destination selector
280
+ function setTransferSession(session) {
281
+ transferSession = session;
282
+
283
+ var otherSessions = session.getOtherSessions();
284
+ if (otherSessions.length < 1) {
285
+ document.getElementById('transfer_from').innerHTML = 'Requires two active sessions to perform transfer!';
286
+ return;
287
+ }
288
+
289
+ // Show source call destination number
290
+ document.getElementById('transfer_from').innerHTML = session.destinationNumber + ' => ';
291
+
292
+ // Add all other sessions to transfer selector
293
+ document.getElementById('transfer_selector').innerHTML = '';
294
+
295
+ for (const tSession of otherSessions) {
296
+ document.getElementById('transfer_selector').appendChild(new Option(tSession.destinationNumber, tSession.uuid));
297
+ }
298
+ }
299
+
300
+ // Transfer call from "transferSession" to item in transfer_selector
301
+ function doCallTransfer() {
302
+ if (!transferSession) {
303
+ console.error('No session selected');
304
+ return;
305
+ }
306
+
307
+ // Use session uuid from selectbox and get session object
308
+ const dstSession = phoneClient.getSessionByUUID(document.getElementById('transfer_selector').value);
309
+
310
+ // Now do transfer
311
+ transferSession.transfer(dstSession);
312
+ }
313
+
314
+ // Create listen session
315
+ function doListen() {
316
+ phoneClient.listen(document.getElementById('listen_key').value);
317
+ }
318
+
319
+ // Playback message
320
+ function playback(session) {
321
+ const hangup_after = false;
322
+ session.playback(document.getElementById('media_file_selector').value, hangup_after).then(() => {
323
+ console.info('Playback started');
324
+ })
325
+ }
326
+
327
+ /*
328
+ * Helper functions
329
+ */
330
+
331
+ // Create new session
332
+ // Add table row for session using session_<uuid> as element id,
333
+ // (We do this so we can find and remove row when session ends)
334
+ function newSession(session) {
335
+ const rowId = `session_${session.uuid}`;
336
+
337
+ // Create row in table for session
338
+ const table = document.getElementById("session_table");
339
+ const newRow = table.insertRow();
340
+ newRow.id = rowId;
341
+ for (let i = 0; i < table.rows[0].cells.length; i++)
342
+ newRow.insertCell();
343
+
344
+ // Set cells
345
+ newRow.cells[0].innerText = session.direction;
346
+
347
+ // Show name of local number called for inbound
348
+ if (session.localNumberName) {
349
+ newRow.cells[1].innerText = session.destinationNumber + ' (' + session.localNumberName + ')';
350
+ } else {
351
+ newRow.cells[1].innerText = session.destinationNumber;
352
+ }
353
+
354
+ // Hookup events
355
+ // Session state show the current calling state
356
+ session.on('sessionStateChange', function (event) {
357
+ console.info(`Phone session is now: ${event.state}`);
358
+ newRow.cells[2].innerHTML = event.state;
359
+ // Add failure cause
360
+ if (event.state === 'FAILED') {
361
+ newRow.cells[2].innerHTML = newRow.cells[2].innerHTML + ` (${event.cause})`;
362
+ }
363
+ updateButtons(newRow.cells[8], session);
364
+ });
365
+
366
+ // Get record state events
367
+ newRow.cells[6].innerHTML = session.isRecording;
368
+ session.on('recordingStateChange', function (data) {
369
+ console.info(`Recording state changed to ${data.state}`);
370
+ newRow.cells[6].innerHTML = data.state;
371
+ });
372
+
373
+ // Get mute state events
374
+ newRow.cells[5].innerHTML = session.isMuted;
375
+ session.on('muteStateChange', function (data) {
376
+ console.info(`Microphone mute state changed to ${data.state}`);
377
+ newRow.cells[5].innerHTML = data.state;
378
+ });
379
+
380
+ // Get hold state events
381
+ newRow.cells[7].innerHTML = session.isOnHold;
382
+ session.on('holdStateChange', function (data) {
383
+ console.info(`Call hold state changed to ${data.state}`);
384
+ newRow.cells[7].innerHTML = data.state;
385
+ });
386
+
387
+ // Get call duration update events
388
+ session.on('callDurationUpdate', function (data) {
389
+ newRow.cells[3].innerHTML = `${data.durationStr} (${data.activeDurationStr})`;
390
+ });
391
+
392
+ // Get call duration update events
393
+ session.on('callQualityReportUpdate', function (data) {
394
+ console.info('Call quality report:');
395
+ console.info(data);
396
+ newRow.cells[4].innerHTML = `${data.delta.effectiveLatency.toFixed(1)} ms (${data.delta.rFactor.toFixed(1)} R)`;
397
+ });
398
+
399
+ // Get local information about call after it has ended
400
+ session.on('callEnded', function (data) {
401
+ console.info('Call ended event:');
402
+ console.debug(data);
403
+ });
404
+
405
+ session.on('whisperModeChange', function (data) {
406
+ console.info('Whisper mode: ' + data.mode);
407
+ });
408
+
409
+ }
410
+
411
+ // Remove table row for session
412
+ function removeSession(session) {
413
+ const row = document.getElementById(`session_${session.uuid}`);
414
+ if (row) {
415
+ row.parentNode.removeChild(row);
416
+ }
417
+ }
418
+
419
+ // Add buttons based on session state on element (the table row)
420
+ function updateButtons(element, session) {
421
+ // Remove all buttons first
422
+ let e = element.firstElementChild;
423
+ while (e) {
424
+ e.remove();
425
+ e = element.firstElementChild;
426
+ }
427
+
428
+ // Put session on TD element, so buttons can use it
429
+ element._session = session;
430
+
431
+ // NON TERMINATED/INCOMING/FAILED BUTTONS
432
+ if (session.state !== 'TERMINATED' && session.state !== 'INCOMING' && session.state !== 'FAILED') {
433
+ const btn = document.createElement("BUTTON");
434
+ btn.innerHTML = 'Hangup';
435
+ btn.onclick = function () {
436
+ this.parentNode._session.hangup();
437
+ };
438
+ element.appendChild(btn)
439
+ }
440
+
441
+ if (session.direction === 'OUTBOUND' || session.direction === 'INBOUND') {
442
+ // ACTIVE CALL BUTTONS
443
+ if (session.state === 'ACTIVE' || session.state === 'ON_HOLD') {
444
+ const btn = document.createElement("BUTTON");
445
+ btn.innerHTML = 'Play tone';
446
+ btn.onclick = function () {
447
+ phoneClient.playTone(1000, 440, 880, function () {
448
+ console.info('Play tone complete!')
449
+ });
450
+ };
451
+ element.appendChild(btn);
452
+
453
+ const btn2 = document.createElement("BUTTON");
454
+ btn2.innerHTML = 'Send DTMF 1';
455
+ btn2.onclick = function () {
456
+ this.parentNode._session.sendDTMF(1);
457
+ };
458
+ element.appendChild(btn2);
459
+
460
+ const btn3 = document.createElement("BUTTON");
461
+ btn3.innerHTML = 'Toggle mute mic';
462
+ btn3.onclick = function () {
463
+ this.parentNode._session.toggleMute();
464
+ };
465
+ element.appendChild(btn3);
466
+
467
+ const btn4 = document.createElement("BUTTON");
468
+ btn4.innerHTML = 'Toggle recording';
469
+ btn4.onclick = function () {
470
+ this.parentNode._session.toggleRecording();
471
+ };
472
+ element.appendChild(btn4);
473
+
474
+ const btn5 = document.createElement("BUTTON");
475
+ btn5.innerHTML = 'Toggle hold';
476
+ btn5.onclick = function () {
477
+ this.parentNode._session.toggleHold();
478
+ };
479
+ element.appendChild(btn5);
480
+
481
+ const btn6 = document.createElement("BUTTON");
482
+ btn6.innerHTML = 'Transfer';
483
+ btn6.onclick = function () {
484
+ setTransferSession(this.parentNode._session);
485
+ };
486
+ element.appendChild(btn6);
487
+
488
+ const btn7 = document.createElement("BUTTON");
489
+ btn7.innerHTML = 'Playback';
490
+ btn7.onclick = function () {
491
+ playback(this.parentNode._session);
492
+ };
493
+ element.appendChild(btn7);
494
+ }
495
+
496
+ // INCOMING BUTTONS
497
+ if (session.state === 'INCOMING') {
498
+ const btn = document.createElement("BUTTON");
499
+ btn.innerHTML = 'Answer';
500
+ btn.onclick = function () {
501
+ this.parentNode._session.answer(metaInfo = 'When answering calls you can add meta info as well');
502
+ };
503
+ element.appendChild(btn);
504
+
505
+ const btn2 = document.createElement("BUTTON");
506
+ btn2.innerHTML = 'Reject';
507
+ btn2.onclick = function () {
508
+ this.parentNode._session.reject();
509
+ };
510
+ element.appendChild(btn2);
511
+ }
512
+ }
513
+
514
+ // LISTEN BUTTONS
515
+ if (session.direction === 'LISTEN' && session.state === 'ACTIVE') {
516
+ const btn = document.createElement("BUTTON");
517
+ btn.innerHTML = 'Start whisper';
518
+ btn.onclick = function () {
519
+ this.parentNode._session.setWhisperMode(1);
520
+ };
521
+ element.appendChild(btn);
522
+
523
+ const btn2 = document.createElement("BUTTON");
524
+ btn2.innerHTML = 'End whisper';
525
+ btn2.onclick = function () {
526
+ this.parentNode._session.setWhisperMode(0);
527
+ };
528
+ element.appendChild(btn2);
529
+ }
530
+ }
531
+
532
+ // Perform authenticated ajax request to api server and return json payload on success
533
+ function jsonApiGetRequest(path) {
534
+ const xhr = new XMLHttpRequest();
535
+ const url = API_URL + path;
536
+ xhr.open('GET', url, true);
537
+ xhr.responseType = 'json';
538
+ const apiToken = document.getElementById('api_key').value;
539
+ xhr.setRequestHeader('Authorization', 'Bearer ' + apiToken);
540
+ xhr.setRequestHeader('Accept', 'application/json');
541
+
542
+ return new Promise(function (resolve, reject) {
543
+ xhr.onreadystatechange = function () {
544
+ if (xhr.readyState === 4) {
545
+ if (xhr.status !== 200) {
546
+ reject(xhr.status + ' ' + xhr.statusText);
547
+ return;
548
+ }
549
+ resolve(xhr.response);
550
+ }
551
+ };
552
+ console.debug(`API Request: ${url}`);
553
+ xhr.send();
554
+ })
555
+ }
556
+ </script>
557
+
558
558
  </html>