@wazo/sdk 0.37.0 → 0.37.4

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 (144) hide show
  1. package/dist/wazo-sdk.js +5 -5
  2. package/esm/__tests__/api-client.test.js +91 -23
  3. package/esm/__tests__/web-rtc-client.test.js +165 -56
  4. package/esm/api/agentd.js +7 -1
  5. package/esm/api/amid.js +7 -1
  6. package/esm/api/application.js +7 -1
  7. package/esm/api/auth.js +7 -1
  8. package/esm/api/call-logd.js +17 -23
  9. package/esm/api/calld.js +10 -18
  10. package/esm/api/chatd.js +7 -1
  11. package/esm/api/confd.js +78 -1
  12. package/esm/api/ctid-ng.js +7 -1
  13. package/esm/api/dird.js +7 -1
  14. package/esm/api/webhookd.js +7 -1
  15. package/esm/api-client.js +91 -23
  16. package/esm/checker/Checker.js +169 -56
  17. package/esm/checker/checks/aor.js +91 -23
  18. package/esm/checker/checks/api.js +91 -23
  19. package/esm/checker/checks/index.js +169 -56
  20. package/esm/checker/checks/wazo-websocket.js +11 -1
  21. package/esm/checker/checks/webrtc-transport.js +165 -56
  22. package/esm/checker/checks/webrtc.js +165 -56
  23. package/esm/domain/AdHocAPIConference.js +1722 -1654
  24. package/esm/domain/CallLog.js +10 -22
  25. package/esm/domain/CallSession.js +1 -18
  26. package/esm/domain/Features.js +91 -23
  27. package/esm/domain/IndirectTransfer.js +0 -17
  28. package/esm/domain/Meeting.js +71 -0
  29. package/esm/domain/Phone/CTIPhone.js +98 -26
  30. package/esm/domain/Phone/Phone.js +0 -17
  31. package/esm/domain/Phone/WebRTCPhone.js +47 -27
  32. package/esm/domain/Room.js +0 -17
  33. package/esm/domain/SwitchboardCall.js +0 -17
  34. package/esm/domain/__tests__/CallLog.test.js +10 -22
  35. package/esm/domain/__tests__/CallSession.test.js +1 -18
  36. package/esm/domain/__tests__/IndirectTransfer.test.js +1 -18
  37. package/esm/domain/__tests__/Room.test.js +1 -18
  38. package/esm/index.js +3880 -3111
  39. package/esm/lib/WazoSessionDescriptionHandler.js +24 -10
  40. package/esm/service/CallApi.js +91 -23
  41. package/esm/service/IssueReporter.js +7 -1
  42. package/esm/service/__tests__/IssueReporter.test.js +9 -1
  43. package/esm/service/getApiClient.js +91 -23
  44. package/esm/simple/Auth.js +1126 -360
  45. package/esm/simple/Configuration.js +1126 -360
  46. package/esm/simple/Directory.js +91 -23
  47. package/esm/simple/Phone.js +3724 -2958
  48. package/esm/simple/Websocket.js +13 -1
  49. package/esm/simple/index.js +1128 -362
  50. package/esm/simple/room/LocalParticipant.js +1097 -331
  51. package/esm/simple/room/Participant.js +1097 -331
  52. package/esm/simple/room/RemoteParticipant.js +1097 -331
  53. package/esm/simple/room/Room.js +1097 -331
  54. package/esm/simple/room/SipRoom.js +12996 -0
  55. package/esm/simple/room/Stream.js +1128 -362
  56. package/esm/simple/room/__tests__/RemoteParticipant.test.js +5157 -4389
  57. package/esm/simple/utils.js +1128 -362
  58. package/esm/utils/__tests__/api-requester.test.js +7 -1
  59. package/esm/utils/api-requester.js +7 -1
  60. package/esm/web-rtc-client.js +165 -56
  61. package/esm/websocket-client.js +12 -2
  62. package/lib/__tests__/api-client.test.js +91 -23
  63. package/lib/__tests__/web-rtc-client.test.js +165 -56
  64. package/lib/api/agentd.js +7 -1
  65. package/lib/api/amid.js +7 -1
  66. package/lib/api/application.js +7 -1
  67. package/lib/api/auth.js +7 -1
  68. package/lib/api/call-logd.js +17 -23
  69. package/lib/api/calld.js +10 -18
  70. package/lib/api/chatd.js +7 -1
  71. package/lib/api/confd.js +78 -1
  72. package/lib/api/ctid-ng.js +7 -1
  73. package/lib/api/dird.js +7 -1
  74. package/lib/api/webhookd.js +7 -1
  75. package/lib/api-client.js +91 -23
  76. package/lib/checker/Checker.js +169 -56
  77. package/lib/checker/checks/aor.js +91 -23
  78. package/lib/checker/checks/api.js +91 -23
  79. package/lib/checker/checks/index.js +169 -56
  80. package/lib/checker/checks/wazo-websocket.js +11 -1
  81. package/lib/checker/checks/webrtc-transport.js +165 -56
  82. package/lib/checker/checks/webrtc.js +165 -56
  83. package/lib/domain/AdHocAPIConference.js +1722 -1654
  84. package/lib/domain/CallLog.js +10 -22
  85. package/lib/domain/CallSession.js +1 -18
  86. package/lib/domain/Features.js +91 -23
  87. package/lib/domain/IndirectTransfer.js +0 -17
  88. package/lib/domain/Meeting.js +73 -0
  89. package/lib/domain/Phone/CTIPhone.js +98 -26
  90. package/lib/domain/Phone/Phone.js +0 -16
  91. package/lib/domain/Phone/WebRTCPhone.js +47 -27
  92. package/lib/domain/Room.js +0 -17
  93. package/lib/domain/SwitchboardCall.js +0 -17
  94. package/lib/domain/__tests__/CallLog.test.js +10 -22
  95. package/lib/domain/__tests__/CallSession.test.js +1 -18
  96. package/lib/domain/__tests__/IndirectTransfer.test.js +1 -18
  97. package/lib/domain/__tests__/Room.test.js +1 -18
  98. package/lib/index.js +3880 -3111
  99. package/lib/lib/WazoSessionDescriptionHandler.js +24 -10
  100. package/lib/service/CallApi.js +91 -23
  101. package/lib/service/IssueReporter.js +7 -1
  102. package/lib/service/__tests__/IssueReporter.test.js +9 -1
  103. package/lib/service/getApiClient.js +91 -23
  104. package/lib/simple/Auth.js +1126 -360
  105. package/lib/simple/Configuration.js +1126 -360
  106. package/lib/simple/Directory.js +91 -23
  107. package/lib/simple/Phone.js +3724 -2958
  108. package/lib/simple/Websocket.js +13 -1
  109. package/lib/simple/index.js +1128 -362
  110. package/lib/simple/room/LocalParticipant.js +1097 -331
  111. package/lib/simple/room/Participant.js +1097 -331
  112. package/lib/simple/room/RemoteParticipant.js +1097 -331
  113. package/lib/simple/room/Room.js +1097 -331
  114. package/lib/simple/room/SipRoom.js +13006 -0
  115. package/lib/simple/room/Stream.js +1128 -362
  116. package/lib/simple/room/__tests__/RemoteParticipant.test.js +5157 -4389
  117. package/lib/simple/utils.js +1128 -362
  118. package/lib/utils/__tests__/api-requester.test.js +7 -1
  119. package/lib/utils/api-requester.js +7 -1
  120. package/lib/web-rtc-client.js +165 -56
  121. package/lib/websocket-client.js +13 -1
  122. package/package.json +1 -1
  123. package/src/api/calld.js +3 -0
  124. package/src/api/confd.js +13 -0
  125. package/src/domain/CallLog.js +10 -22
  126. package/src/domain/CallSession.js +1 -2
  127. package/src/domain/Meeting.js +59 -0
  128. package/src/domain/Phone/CTIPhone.js +6 -2
  129. package/src/domain/Phone/Phone.js +2 -0
  130. package/src/domain/Phone/WebRTCPhone.js +22 -8
  131. package/src/index.js +6 -0
  132. package/src/lib/WazoSessionDescriptionHandler.js +17 -9
  133. package/src/service/IssueReporter.js +7 -1
  134. package/src/service/__tests__/IssueReporter.test.js +2 -0
  135. package/src/simple/Phone.js +13 -4
  136. package/src/simple/index.js +8 -0
  137. package/src/simple/room/Participant.js +7 -0
  138. package/src/simple/room/Room.js +157 -79
  139. package/src/simple/room/SipRoom.js +147 -0
  140. package/src/simple/room/Stream.js +1 -3
  141. package/src/simple/room/__tests__/RemoteParticipant.test.js +3 -2
  142. package/src/types/WebSocketMessage.js +21 -0
  143. package/src/web-rtc-client.js +57 -24
  144. package/src/websocket-client.js +4 -0
@@ -1,7 +1,7 @@
1
1
  import 'sip.js/lib/api/session-state';
2
2
  import moment from 'moment';
3
- import { jws, b64utoutf8, KEYUTIL } from 'jsrsasign';
4
3
  import { Base64 } from 'js-base64';
4
+ import { jws, b64utoutf8, KEYUTIL } from 'jsrsasign';
5
5
  import 'events';
6
6
  import { jsonToGraphQLQuery } from 'json-to-graphql-query/lib/jsonToGraphQLQuery';
7
7
 
@@ -207,2067 +207,2073 @@ class Call {
207
207
  }
208
208
 
209
209
  //
210
+ /* eslint-disable no-param-reassign */
210
211
 
211
- const swarmPublicKey = `-----BEGIN PUBLIC KEY-----
212
- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAmkXOuNfY8u5xoTiIhkb8
213
- djnbIwG/Wrz3vpo8BZir8L5e1a1nSy740qBjP7ZINBQoALDhFfmdOfJnCyEGiHuz
214
- ZW6jbG6C3PryE3Bu6GKwqSmD6k3q4Zk27fpwYAnNl+rWhYYM563rJZBda/INyHNN
215
- pK7M1mixWi7gNdjjXwoEXSSBx+VpYMkY6LiAB2mvHXTY9M1qI14dvgGoQISZQoKi
216
- NMTRCg5UP2ic0Dd9nSz/XpcOxGfa+0fwIl1F7RC1tJXOqvkGGPTOV4LLfg/Yta3h
217
- nUPX9EZZDIX6vO/0IBV1LzjSl2A1bYFYAjJfowv3i1CpvONBOClHjSY5t9Y8MH6p
218
- BwIDAQAB
219
- -----END PUBLIC KEY-----`;
212
+ var updateFrom = (instance , from ) => {
213
+ Object.keys(from).forEach(key => {
214
+ // $FlowFixMe
215
+ if (
216
+ (!instance[key] && from[key])
217
+ || (typeof instance[key] !== 'undefined' && typeof from[key] !== 'undefined' && instance[key] !== from[key])
218
+ ) {
219
+ // $FlowFixMe
220
+ instance[key] = from[key];
221
+ }
222
+ });
223
+ };
220
224
 
221
- //
225
+ /* */
222
226
 
223
-
224
-
225
-
226
-
227
-
228
-
227
+ class BadResponse extends Error {
228
+ static fromResponse(error , status ) {
229
+ return new BadResponse(
230
+ error.message || JSON.stringify(error),
231
+ status,
232
+ error.timestamp,
233
+ error.error_id,
234
+ error.details,
235
+ error,
236
+ );
237
+ }
229
238
 
230
-
231
-
232
-
233
-
234
-
239
+ static fromText(response , status ) {
240
+ return new BadResponse(response, status);
241
+ }
235
242
 
243
+
244
+
236
245
 
237
-
238
-
239
-
240
-
241
-
242
-
246
+
247
+
248
+
243
249
 
244
-
245
-
246
-
247
-
248
-
249
-
250
-
250
+ constructor(
251
+ message ,
252
+ status ,
253
+ timestamp = null,
254
+ errorId = null,
255
+ details = null,
256
+ error = null,
257
+ ) {
258
+ super(message);
251
259
 
252
- class Line {
253
-
260
+ this.timestamp = timestamp;
261
+ this.status = status;
262
+ this.errorId = errorId;
263
+ this.details = details;
264
+ this.error = error;
265
+ }
266
+ }
254
267
 
255
-
256
-
257
-
258
-
259
-
268
+ /* */
260
269
 
261
- static parse(plain ) {
262
- return new Line({
263
- id: plain.id,
264
- extensions: plain.extensions,
265
- endpointCustom: plain.endpoint_custom || null,
266
- endpointSccp: plain.endpoint_sccp || null,
267
- endpointSip: plain.endpoint_sip || null,
268
- });
270
+ class ServerError extends BadResponse {
271
+ static fromResponse(error , status ) {
272
+ return new ServerError(
273
+ error.message || JSON.stringify(error),
274
+ status,
275
+ error.timestamp,
276
+ error.error_id,
277
+ error.details,
278
+ );
269
279
  }
270
280
 
271
- static newFrom(profile ) {
272
- return newFrom(profile, Line);
281
+ static fromText(response , status ) {
282
+ return new ServerError(response, status);
273
283
  }
284
+ }
274
285
 
275
- is(line ) {
276
- return this.id === line.id;
277
- }
286
+ /* */
287
+ /* global navigator */
288
+ const isMobile = () => typeof navigator !== 'undefined' && navigator.product === 'ReactNative';
278
289
 
279
- hasExtension(extension ) {
280
- if (!this.extensions) {
281
- return false;
282
- }
290
+ /* global window */
283
291
 
284
- return this.extensions.some(ext => ext.exten === extension);
285
- }
292
+ const TRACE = 'trace';
293
+ const DEBUG = 'debug';
294
+ const INFO = 'info';
295
+ const LOG = 'log';
296
+ const WARN = 'warn';
297
+ const ERROR = 'error';
298
+ const CONSOLE_METHODS = [INFO, LOG, WARN, ERROR];
299
+ const LOG_LEVELS = [TRACE, DEBUG, INFO, LOG, WARN, ERROR];
300
+ const CATEGORY_PREFIX = 'logger-category=';
286
301
 
287
- constructor({ id, extensions, endpointCustom, endpointSccp, endpointSip } = {}) {
288
- this.id = id;
289
- this.extensions = extensions;
290
- this.endpointCustom = endpointCustom || null;
291
- this.endpointSccp = endpointSccp || null;
292
- this.endpointSip = endpointSip || null;
302
+ const MAX_REMOTE_RETRY = 100;
293
303
 
294
- // Useful to compare instead of instanceof with minified code
295
- this.type = 'Line';
304
+ const addLevelsTo = (instance , withMethods = false) => {
305
+ instance.TRACE = TRACE;
306
+ instance.DEBUG = DEBUG;
307
+ instance.INFO = INFO;
308
+ instance.LOG = LOG;
309
+ instance.WARN = WARN;
310
+ instance.ERROR = ERROR;
311
+
312
+ if (withMethods) {
313
+ instance.trace = (...args) => instance.apply(null, [TRACE, ...args]);
314
+ instance.debug = (...args) => instance.apply(null, [DEBUG, ...args]);
315
+ instance.info = (...args) => instance.apply(null, [INFO, ...args]);
316
+ instance.log = (...args) => instance.apply(null, [LOG, ...args]);
317
+ instance.warn = (...args) => instance.apply(null, [WARN, ...args]);
318
+ instance.error = (...args) => instance.apply(null, [ERROR, ...args]);
296
319
  }
297
- }
298
320
 
299
- //
321
+ return instance;
322
+ };
300
323
 
301
-
302
-
303
-
304
-
324
+ const safeStringify = (object ) => {
325
+ const result = '{"message": "Not parsable JSON"}';
326
+ try {
327
+ return JSON.stringify(object);
328
+ } catch (e) {
329
+ // Nothing to do
330
+ }
305
331
 
306
- const FORWARD_KEYS = {
307
- BUSY: 'busy',
308
- NO_ANSWER: 'noanswer',
309
- UNCONDITIONAL: 'unconditional',
332
+ return result;
310
333
  };
311
334
 
312
-
313
-
314
-
335
+ class IssueReporter {
336
+
337
+
315
338
 
316
-
339
+
340
+
317
341
 
318
- class ForwardOption {
319
-
342
+
320
343
 
321
-
344
+
322
345
 
323
- static parse(plain , key ) {
324
- return new ForwardOption({
325
- destination: plain.destination || '',
326
- enabled: plain.enabled,
327
- key,
328
- });
346
+ constructor() {
347
+ addLevelsTo(this);
348
+
349
+ this.oldConsoleMethods = null;
350
+ this.enabled = false;
351
+ this.remoteClientConfiguration = null;
329
352
  }
330
353
 
331
- static newFrom(profile ) {
332
- return newFrom(profile, ForwardOption);
354
+ init() {
355
+ this._catchConsole();
333
356
  }
334
357
 
335
- constructor({ destination, enabled, key } = {}) {
336
- this.destination = destination;
337
- this.enabled = enabled;
338
- this.key = key;
358
+ configureRemoteClient(configuration = { tag: 'wazo-sdk', host: null, port: null, level: null, extra: {} }) {
359
+ this.remoteClientConfiguration = configuration;
339
360
  }
340
361
 
341
- setDestination(number ) {
342
- this.destination = number;
362
+ enable() {
363
+ if (!this.oldConsoleMethods) {
364
+ this.init();
365
+ }
343
366
 
344
- return this;
367
+ this.enabled = true;
345
368
  }
346
369
 
347
- setEnabled(enabled ) {
348
- this.enabled = enabled;
349
-
350
- return this;
370
+ disable() {
371
+ this.enabled = false;
351
372
  }
352
373
 
353
- is(other ) {
354
- return this.key === other.key;
374
+ loggerFor(category ) {
375
+ const logger = (level , ...args ) => {
376
+ this.log.apply(this, [level, this._makeCategory(category), ...args]);
377
+ };
378
+
379
+ return addLevelsTo(logger, true);
355
380
  }
356
- }
357
381
 
358
- //
359
-
382
+ removeSlashes(str ) {
383
+ return str.replace(/"/g, "'").replace(/\\/g, '');
384
+ }
360
385
 
361
- const availableCodecs = ['vp8', 'vp9', 'h264'];
386
+ log(level , ...args ) {
387
+ if (!this.enabled) {
388
+ return;
389
+ }
362
390
 
363
-
364
-
365
-
366
-
367
-
368
-
369
-
370
-
371
-
372
-
373
-
374
-
375
-
376
-
377
-
391
+ // Handle category label
392
+ let category = null;
393
+ let extra = {};
394
+ if (args[0].indexOf(CATEGORY_PREFIX) === 0) {
395
+ category = args[0].split('=')[1];
396
+ // eslint-disable-next-line no-param-reassign
397
+ args.splice(0, 1);
398
+ }
378
399
 
379
-
380
-
381
-
382
-
383
-
384
-
385
-
386
-
387
-
388
-
389
-
390
-
391
-
392
-
400
+ // Handle extra data as object for the last argument
401
+ const lastArg = args[args.length - 1];
402
+ if (lastArg && ((typeof lastArg === 'object' && Object.keys(lastArg).length) || lastArg instanceof Error)) {
403
+ if (lastArg instanceof Error) {
404
+ extra = {
405
+ errorMessage: lastArg.message,
406
+ errorStack: lastArg.stack,
407
+ errorType: lastArg.constructor.name,
408
+ };
409
+ } else {
410
+ extra = lastArg;
411
+ }
412
+ // eslint-disable-next-line no-param-reassign
413
+ args.splice(1, 1);
414
+ }
393
415
 
394
- class SipLine {
395
-
396
-
397
-
398
-
399
-
400
-
401
-
402
-
403
-
404
-
405
-
406
-
416
+ const date = moment().format('YYYY-MM-DD HH:mm:ss.SSS');
417
+ const message = args.join(', ');
418
+ let consoleMessage = message;
407
419
 
408
- static parse(plain ) {
409
- let { username, secret, host } = plain;
420
+ if (Object.keys(extra).length) {
421
+ const parsedExtra = safeStringify(extra);
422
+ consoleMessage = `${consoleMessage} (${parsedExtra})`;
423
+ }
410
424
 
411
- // Since 20.13 engine so options are now in section
412
- if (plain.auth_section_options) {
413
- // $FlowFixMe
414
- const findOption = (options , name ) => options.find(option => option[0] === name);
425
+ if (category) {
426
+ consoleMessage = `[${category}] ${consoleMessage}`;
427
+ }
415
428
 
416
- const usernameOption = findOption(plain.auth_section_options, 'username');
417
- const secretOption = findOption(plain.auth_section_options, 'password');
418
- const hostOption = findOption(plain.endpoint_section_options, 'media_address');
429
+ // Log the message in the console anyway (but don't console.error on mobile)
430
+ const consoleLevel = isMobile() && level === 'error' ? WARN : level;
419
431
 
420
- username = usernameOption ? usernameOption[1] : '';
421
- secret = secretOption ? secretOption[1] : '';
422
- host = hostOption ? hostOption[1] : '';
432
+ // eslint-disable-next-line
433
+ const oldMethod = this.oldConsoleMethods[consoleLevel] || this.oldConsoleMethods.log;
434
+ oldMethod.apply(oldMethod, [date, consoleMessage]);
435
+
436
+ this._sendToRemoteLogger(level, { date, message, category, ...extra });
437
+ }
438
+
439
+ logRequest(url , options , response , start ) {
440
+ if (!this.enabled) {
441
+ return;
423
442
  }
443
+ const { status } = response;
444
+ const duration = new Date() - start;
424
445
 
425
- return new SipLine({
426
- id: plain.id,
427
- uuid: plain.uuid,
428
- tenantUuid: plain.tenant_uuid,
429
- username,
430
- secret,
431
- type: plain.type,
432
- host,
433
- options: plain.options,
434
- endpointSectionOptions: plain.endpoint_section_options,
435
- links: plain.links,
436
- trunk: plain.trunk,
437
- line: plain.line,
446
+ let level = TRACE;
447
+ if (status >= 400 && status < 500) {
448
+ level = WARN;
449
+ } else if (status >= 500) {
450
+ level = ERROR;
451
+ }
452
+
453
+ this.log(level, this._makeCategory('http'), url, {
454
+ status,
455
+ body: this.removeSlashes(JSON.stringify(options.body)),
456
+ method: options.method,
457
+ headers: options.headers,
458
+ duration,
438
459
  });
439
460
  }
440
461
 
441
- static newFrom(sipLine ) {
442
- return newFrom(sipLine, SipLine);
462
+ // Logs aren't stored anymore
463
+ getLogs() {
464
+ console.warn('IssueReporter\'s logs aren\'t stored anymore. Please use fluentd to store them');
465
+ return [];
443
466
  }
444
467
 
445
- is(line ) {
446
- if (line.uuid) {
447
- return this.uuid === line.uuid;
448
- }
449
- return this.id === line.id;
468
+ getParsedLogs() {
469
+ console.warn('IssueReporter\'s logs aren\'t stored anymore. Please use fluentd to store them');
470
+ return [];
450
471
  }
451
472
 
452
- getOptions() {
453
- // `options` params have beend remove since 20.13... we should now use endpointSectionOptions
454
- return this.endpointSectionOptions || this.options || [];
473
+ getReport() {
474
+ return this.getParsedLogs().join('\r\n');
455
475
  }
456
476
 
457
- isWebRtc() {
458
- return this.getOptions().some(option => option[0] === 'webrtc' && option[1] === 'yes');
477
+ _catchConsole() {
478
+ this.oldConsoleMethods = {};
479
+ CONSOLE_METHODS.forEach((methodName ) => {
480
+ // eslint-disable-next-line
481
+ this.oldConsoleMethods[methodName] = console[methodName];
482
+ window.console[methodName] = (...args) => {
483
+ // Store message
484
+ try {
485
+ this.log(methodName, args.join(' '));
486
+ // Use old console method to log it normally
487
+ this.oldConsoleMethods[methodName].apply(null, args);
488
+ } catch (e) {
489
+ // Avoid circular structure issues
490
+ }
491
+ };
492
+ });
459
493
  }
460
494
 
461
- hasVideo() {
462
- const allow = this.getOptions().find(option => option[0] === 'allow');
463
- return Array.isArray(allow) && allow[1].split(',').some(codec => availableCodecs.some(c => c === codec));
464
- }
495
+ _sendToRemoteLogger(level , payload , retry = 0) {
496
+ if (!this.remoteClientConfiguration || retry >= MAX_REMOTE_RETRY) {
497
+ return;
498
+ }
465
499
 
466
- hasVideoConference() {
467
- return (
468
- this.getOptions().some(option => option[0] === 'max_audio_streams' && parseInt(option[1], 10) > 0)
469
- && this.getOptions().some(option => option[0] === 'max_video_streams' && parseInt(option[1], 10) > 1)
470
- );
500
+ const { tag, host, port, extra, level: minLevel } = this.remoteClientConfiguration;
501
+ if (!minLevel || this._isLevelAbove(minLevel, level)) {
502
+ return;
503
+ }
504
+
505
+ const isSecure = +port === 443;
506
+ const url = `http${isSecure ? 's' : ''}://${host}${isSecure ? '' : `:${port}`}/${tag}`;
507
+
508
+ realFetch()(url, {
509
+ method: 'POST',
510
+ headers: {
511
+ Accept: 'application/json',
512
+ 'Content-Type': 'application/json',
513
+ },
514
+ body: safeStringify({
515
+ level,
516
+ ...payload,
517
+ ...extra,
518
+ }),
519
+ }).catch(() => {
520
+ setTimeout(() => {
521
+ // eslint-disable-next-line no-underscore-dangle
522
+ payload._retry = retry + 1;
523
+ this._sendToRemoteLogger(level, payload, retry + 1);
524
+ }, 5000 + retry * 1000);
525
+ });
471
526
  }
472
527
 
473
- constructor({
474
- id,
475
- uuid,
476
- tenantUuid,
477
- username,
478
- secret,
479
- type,
480
- host,
481
- options,
482
- endpointSectionOptions,
483
- links,
484
- trunk,
485
- line,
486
- } = {}) {
487
- this.id = id;
488
- this.uuid = uuid;
489
- this.tenantUuid = tenantUuid;
490
- this.username = username;
491
- this.secret = secret;
492
- this.type = type;
493
- this.host = host;
494
- this.options = options;
495
- this.endpointSectionOptions = endpointSectionOptions;
496
- this.links = links;
497
- this.trunk = trunk;
498
- this.line = line;
528
+ _isLevelAbove(level1 , level2 ) {
529
+ const index1 = LOG_LEVELS.indexOf(level1);
530
+ const index2 = LOG_LEVELS.indexOf(level2);
531
+ if (index1 === -1 || index2 === -1) {
532
+ return false;
533
+ }
499
534
 
500
- // Useful to compare instead of instanceof with minified code
501
- this.type = 'SipLine';
535
+ return index1 > index2;
502
536
  }
503
- }
504
537
 
505
- //
538
+ _makeCategory(category ) {
539
+ return `${CATEGORY_PREFIX}${category}`;
540
+ }
541
+ }
506
542
 
507
- const STATE = {
508
- AVAILABLE: 'available',
509
- UNAVAILABLE: 'unavailable',
510
- INVISIBLE: 'invisible',
511
- DISCONNECTED: 'disconnected',
512
- AWAY: 'away',
513
- };
543
+ var IssueReporter$1 = new IssueReporter();
514
544
 
515
- const LINE_STATE = {
516
- AVAILABLE: 'available',
517
- HOLDING: 'holding',
518
- RINGING: 'ringing',
519
- TALKING: 'talking',
520
- UNAVAILABLE: 'unavailable',
521
- PROGRESSING: 'progressing',
522
- };
545
+ /* */
523
546
 
524
-
525
-
547
+
548
+
549
+
526
550
 
527
-
528
-
529
-
530
-
531
-
532
-
533
-
534
-
535
-
536
-
537
-
538
-
539
-
540
-
541
-
542
-
543
-
544
-
545
-
546
-
547
-
548
-
549
-
550
-
551
-
552
-
553
-
554
-
555
-
556
-
557
-
558
-
559
-
560
-
561
-
562
-
563
-
564
-
565
-
566
-
567
-
568
-
569
-
570
-
571
-
572
-
573
-
574
-
575
-
576
-
577
-
551
+
578
552
 
579
-
580
-
553
+
581
554
 
582
555
 
583
-
584
-
585
-
586
-
587
-
588
-
589
-
590
-
591
-
592
-
593
-
594
-
556
+ const methods = ['head', 'get', 'post', 'put', 'delete'];
557
+
558
+ const logger$1 = IssueReporter$1 ? IssueReporter$1.loggerFor('api') : console;
559
+
560
+ // Use a function here to be able to mock it in tests
561
+ const realFetch = () => {
562
+ if (typeof document !== 'undefined') {
563
+ // Browser
564
+ return window.fetch;
565
+ }
566
+
567
+ if (isMobile()) {
568
+ // React native
569
+ return fetch;
570
+ }
571
+
572
+ // nodejs
573
+ // this package is disable for react-native in package.json because it requires nodejs modules
574
+ // Used to trick the optimizer to avoid requiring `node-fetch/lib/index` directly
575
+ // It causes to require it on browsers when delivered by a nodejs engine (cf: vitejs).
576
+ try {
577
+ // $FlowFixMe
578
+ return require(Math.random() >= 0 ? 'node-fetch/lib/index' : '');
579
+ } catch (e) {
580
+ return fetch;
581
+ }
582
+ };
583
+
584
+ class ApiRequester {
595
585
 
596
-
597
-
598
-
599
586
 
600
-
601
-
602
-
603
-
604
-
605
-
606
-
607
-
608
-
609
-
610
-
611
-
612
- class Profile {
613
-
614
587
 
615
-
616
588
 
617
-
618
-
619
-
589
+
620
590
 
621
591
 
622
-
623
-
592
+
593
+
594
+
624
595
 
625
-
596
+
626
597
 
627
-
628
-
629
-
630
-
598
+
599
+
631
600
 
632
- static parse(plain ) {
633
- return new Profile({
634
- id: plain.uuid,
635
- firstName: plain.firstName || plain.firstname || '',
636
- lastName: plain.lastName || plain.lastname || '',
637
- email: plain.email,
638
- lines: plain.lines.map(line => Line.parse(line)),
639
- username: plain.username,
640
- mobileNumber: plain.mobile_phone_number || '',
641
- forwards: [
642
- ForwardOption.parse(plain.forwards.unconditional, FORWARD_KEYS.UNCONDITIONAL),
643
- ForwardOption.parse(plain.forwards.noanswer, FORWARD_KEYS.NO_ANSWER),
644
- ForwardOption.parse(plain.forwards.busy, FORWARD_KEYS.BUSY),
645
- ],
646
- doNotDisturb: plain.services.dnd.enabled,
647
- subscriptionType: plain.subscription_type,
648
- voicemail: plain.voicemail,
649
- switchboards: plain.switchboards || [],
650
- agent: plain.agent,
651
- status: '',
652
- callPickupTargetUsers: plain.call_pickup_target_users || [],
653
- onlineCallRecordEnabled: plain.online_call_record_enabled,
654
- });
601
+ // eslint-disable-next-line
602
+ static successResponseParser(response , isJson ) {
603
+ return response.status === 204 || response.status === 201 || response.status === 200;
655
604
  }
656
605
 
657
- static newFrom(profile ) {
658
- return newFrom(profile, Profile);
606
+ static defaultParser(response ) {
607
+ return response.json().then((data ) => data);
659
608
  }
660
609
 
661
- constructor({
662
- id,
663
- firstName,
664
- lastName,
665
- email,
666
- lines,
667
- username,
668
- mobileNumber,
669
- forwards,
670
- doNotDisturb,
671
- state,
672
- subscriptionType,
673
- voicemail,
674
- switchboards,
675
- agent,
676
- status,
677
- sipLines,
678
- callPickupTargetUsers,
679
- onlineCallRecordEnabled,
680
- } = {}) {
681
- this.id = id;
682
- this.firstName = firstName;
683
- this.lastName = lastName;
684
- this.email = email;
685
- this.lines = lines;
686
- this.username = username;
687
- this.mobileNumber = mobileNumber;
688
- this.forwards = forwards;
689
- this.doNotDisturb = doNotDisturb;
690
- this.state = state;
691
- this.voicemail = voicemail;
692
- this.subscriptionType = subscriptionType;
693
- this.switchboards = switchboards;
694
- this.agent = agent;
695
- this.status = status;
696
- this.callPickupTargetUsers = callPickupTargetUsers;
697
- this.onlineCallRecordEnabled = onlineCallRecordEnabled;
698
-
699
- this.sipLines = sipLines || [];
610
+ static getQueryString(obj ) {
611
+ return Object.keys(obj)
612
+ .filter(key => obj[key])
613
+ .map(key => `${key}=${encodeURIComponent(obj[key])}`)
614
+ .join('&');
700
615
  }
701
616
 
702
- static getLinesState(lines ) {
703
- let result = LINE_STATE.UNAVAILABLE;
617
+ static base64Encode(str ) {
618
+ return typeof btoa !== 'undefined' ? btoa(str) : Base64.encode(str);
619
+ }
704
620
 
705
- // eslint-disable-next-line
706
- for (const line of lines) {
707
- if (line.state === LINE_STATE.RINGING) {
708
- result = LINE_STATE.RINGING;
709
- break;
710
- }
621
+ // @see https://github.com/facebook/flow/issues/183#issuecomment-358607052
622
+ constructor({ server, refreshTokenCallback, clientId, agent = null, token = null, fetchOptions } ) {
623
+ this.server = server;
624
+ this.agent = agent;
625
+ this.clientId = clientId;
626
+ this.refreshTokenCallback = refreshTokenCallback;
627
+ this.refreshTokenPromise = null;
628
+ this.fetchOptions = fetchOptions || {};
629
+ if (token) {
630
+ this.token = token;
631
+ }
711
632
 
712
- if (line.state === LINE_STATE.TALKING) {
713
- result = LINE_STATE.TALKING;
714
- break;
715
- }
633
+ this.shouldLogErrors = true;
716
634
 
717
- if (line.state === LINE_STATE.AVAILABLE) {
718
- result = LINE_STATE.AVAILABLE;
719
- }
720
- }
635
+ methods.forEach(method => {
636
+ // $FlowFixMe
637
+ ApiRequester.prototype[method] = function sugar(...args) {
638
+ // Add method in arguments passed to `call`
639
+ args.splice(1, 0, method);
721
640
 
722
- return result;
641
+ return this.call.call(this, ...args);
642
+ };
643
+ });
723
644
  }
724
645
 
725
- hasId(id ) {
726
- return id === this.id;
646
+ setTenant(tenant ) {
647
+ this.tenant = tenant;
727
648
  }
728
649
 
729
- setMobileNumber(number ) {
730
- this.mobileNumber = number;
731
-
732
- return this;
650
+ setToken(token ) {
651
+ this.token = token;
733
652
  }
734
653
 
735
- setForwardOption(forwardOption ) {
736
- const updatedForwardOptions = this.forwards.slice();
737
- const index = updatedForwardOptions.findIndex(forward => forward.is(forwardOption));
738
- updatedForwardOptions.splice(index, 1, forwardOption);
654
+ setFetchOptions(options ) {
655
+ this.fetchOptions = options;
656
+ }
739
657
 
740
- this.forwards = updatedForwardOptions;
658
+ disableErrorLogging() {
659
+ this.shouldLogErrors = false;
660
+ }
741
661
 
742
- return this;
662
+ enableErrorLogging() {
663
+ this.shouldLogErrors = true;
743
664
  }
744
665
 
745
- setDoNotDisturb(enabled ) {
746
- this.doNotDisturb = enabled;
666
+ async call(
667
+ path ,
668
+ method = 'get',
669
+ body = null,
670
+ headers = null,
671
+ parse = ApiRequester.defaultParser,
672
+ firstCall = true,
673
+ ) {
674
+ const url = this.computeUrl(method, path, body);
675
+ const newHeaders = this.getHeaders(headers);
676
+ let newBody = method === 'get' ? null : body;
677
+ if (newBody && newHeaders['Content-Type'] === 'application/json') {
678
+ newBody = JSON.stringify(newBody);
679
+ }
680
+ const isHead = method === 'head';
681
+ const hasEmptyResponse = method === 'delete' || isHead;
682
+ const newParse = hasEmptyResponse ? ApiRequester.successResponseParser : parse;
747
683
 
748
- return this;
684
+ const fetchOptions = { ...this.fetchOptions || {} };
685
+ const extraHeaders = fetchOptions.headers || {};
686
+ delete fetchOptions.headers;
687
+
688
+ const options = {
689
+ method,
690
+ body: newBody,
691
+ headers: { ...this.getHeaders(headers), ...extraHeaders },
692
+ agent: this.agent,
693
+ ...fetchOptions,
694
+ };
695
+
696
+ if (this.refreshTokenPromise) {
697
+ logger$1.info('A token is already refreshing, waiting ...', { url });
698
+ await this.refreshTokenPromise;
699
+ }
700
+
701
+ const start = new Date();
702
+
703
+ return realFetch()(url, options).then(response => {
704
+ const contentType = response.headers.get('content-type') || '';
705
+ const isJson = contentType.indexOf('application/json') !== -1;
706
+
707
+ IssueReporter$1.logRequest(url, options, response, start);
708
+
709
+ // Throw an error only if status >= 400
710
+ if ((isHead && response.status >= 500) || (!isHead && response.status >= 400)) {
711
+ const promise = isJson ? response.json() : response.text();
712
+ const exceptionClass = response.status >= 500 ? ServerError : BadResponse;
713
+
714
+ return promise.then(async (err ) => {
715
+ // Check if the token is still valid
716
+ if (firstCall && this._checkTokenExpired(response, err)) {
717
+ logger$1.warn('token expired', { error: err.reason });
718
+ // Replay the call after refreshing the token
719
+ return this._replayWithNewToken(err, path, method, body, headers, parse);
720
+ }
721
+
722
+ const error = typeof err === 'string'
723
+ ? exceptionClass.fromText(err, response.status)
724
+ : exceptionClass.fromResponse(err, response.status);
725
+
726
+ if (this.shouldLogErrors) {
727
+ logger$1.error('API error', error);
728
+ }
729
+
730
+ throw error;
731
+ });
732
+ }
733
+
734
+ return newParse(response, isJson);
735
+ }).catch(error => {
736
+ if (this.shouldLogErrors) {
737
+ logger$1.error('Fetch failed', { url, options, message: error.message, stack: error.stack });
738
+ }
739
+
740
+ throw error;
741
+ });
749
742
  }
750
743
 
751
- setState(state ) {
752
- this.state = state;
744
+ _checkTokenExpired(response , err ) {
745
+ // Special case when authenticating form a token: we got a 404
746
+ const isTokenNotFound = response.status === 404 && this._isTokenNotFound(err);
753
747
 
754
- return this;
748
+ return response.status === 401 || isTokenNotFound;
749
+ }
750
+
751
+ _isTokenNotFound(err ) {
752
+ return err && err.reason && err.reason[0] === 'No such token';
753
+ }
754
+
755
+ _replayWithNewToken(
756
+ err ,
757
+ path ,
758
+ method ,
759
+ body = null,
760
+ headers = null,
761
+ parse ,
762
+ ) {
763
+ const isTokenNotFound = this._isTokenNotFound(err);
764
+ let newPath = path;
765
+ logger$1.info('refreshing token', { inProgress: !!this.refreshTokenPromise });
766
+
767
+ this.refreshTokenPromise = this.refreshTokenPromise || this.refreshTokenCallback();
768
+
769
+ return this.refreshTokenPromise.then(() => {
770
+ this.refreshTokenPromise = null;
771
+ logger$1.info('token refreshed', { isTokenNotFound });
772
+ if (isTokenNotFound) {
773
+ const pathParts = path.split('/');
774
+ pathParts.pop();
775
+ pathParts.push(this.token);
776
+ newPath = pathParts.join('/');
777
+ }
778
+
779
+ return this.call(newPath, method, body, headers, parse, false);
780
+ }).catch((e) => {
781
+ this.refreshTokenPromise = null;
782
+ throw e;
783
+ });
784
+ }
785
+
786
+ getHeaders(header ) {
787
+ if (header instanceof Object) {
788
+ return header;
789
+ }
790
+
791
+ return {
792
+ 'X-Auth-Token': this.token,
793
+ ...(this.tenant ? { 'Wazo-Tenant': this.tenant } : null),
794
+ Accept: 'application/json',
795
+ 'Content-Type': 'application/json',
796
+ };
797
+ }
798
+
799
+ computeUrl(method , path , body ) {
800
+ const url = `${this.baseUrl}/${path}`;
801
+
802
+ return method === 'get' && body && Object.keys(body).length ? `${url}?${ApiRequester.getQueryString(body)}` : url;
803
+ }
804
+
805
+ get baseUrl() {
806
+ return `https://${this.server}/api`;
755
807
  }
756
808
  }
757
809
 
758
810
  //
759
-
760
811
 
761
- const BACKEND = {
762
- OFFICE365: 'office365',
763
- PERSONAL: 'personal',
764
- GOOGLE: 'google',
765
- WAZO: 'wazo',
766
- };
812
+ const swarmPublicKey = `-----BEGIN PUBLIC KEY-----
813
+ MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAmkXOuNfY8u5xoTiIhkb8
814
+ djnbIwG/Wrz3vpo8BZir8L5e1a1nSy740qBjP7ZINBQoALDhFfmdOfJnCyEGiHuz
815
+ ZW6jbG6C3PryE3Bu6GKwqSmD6k3q4Zk27fpwYAnNl+rWhYYM563rJZBda/INyHNN
816
+ pK7M1mixWi7gNdjjXwoEXSSBx+VpYMkY6LiAB2mvHXTY9M1qI14dvgGoQISZQoKi
817
+ NMTRCg5UP2ic0Dd9nSz/XpcOxGfa+0fwIl1F7RC1tJXOqvkGGPTOV4LLfg/Yta3h
818
+ nUPX9EZZDIX6vO/0IBV1LzjSl2A1bYFYAjJfowv3i1CpvONBOClHjSY5t9Y8MH6p
819
+ BwIDAQAB
820
+ -----END PUBLIC KEY-----`;
767
821
 
768
-
769
-
770
-
771
-
772
-
773
-
774
-
775
-
822
+ //
823
+
824
+
825
+
776
826
 
777
-
778
-
827
+
828
+
779
829
 
780
830
 
781
-
782
-
783
-
784
-
785
-
786
-
787
-
788
-
789
831
 
790
-
791
-
792
-
832
+
833
+
834
+
793
835
 
794
836
 
795
-
837
+
838
+
839
+
840
+
796
841
 
797
-
798
-
799
-
800
-
801
-
802
-
803
842
 
804
-
805
-
806
-
807
-
808
-
809
-
810
-
811
-
812
-
813
-
814
-
815
-
816
-
817
-
818
843
 
819
844
 
820
-
821
-
822
-
823
-
824
845
 
825
-
846
+
826
847
 
827
-
828
-
829
-
848
+
830
849
 
831
-
832
-
833
-
834
-
835
-
836
-
837
-
838
-
850
+
839
851
 
840
852
 
841
- // @see: https://github.com/rt2zz/react-native-contacts#example-contact-record
842
-
843
-
844
-
845
-
846
-
847
-
848
-
849
-
850
-
851
-
852
-
853
+ class Line {
853
854
 
854
-
855
-
856
-
857
-
858
-
859
-
860
-
861
-
862
-
863
-
864
-
865
-
866
-
867
-
868
-
869
-
870
-
871
-
872
-
855
+
873
856
 
874
-
875
-
876
-
877
-
878
-
857
+
858
+
859
+
860
+
861
+
862
+ static parse(plain ) {
863
+ return new Line({
864
+ id: plain.id,
865
+ extensions: plain.extensions,
866
+ endpointCustom: plain.endpoint_custom || null,
867
+ endpointSccp: plain.endpoint_sccp || null,
868
+ endpointSip: plain.endpoint_sip || null,
869
+ });
870
+ }
871
+
872
+ static newFrom(profile ) {
873
+ return newFrom(profile, Line);
874
+ }
875
+
876
+ is(line ) {
877
+ return this.id === line.id;
878
+ }
879
+
880
+ hasExtension(extension ) {
881
+ if (!this.extensions) {
882
+ return false;
883
+ }
884
+
885
+ return this.extensions.some(ext => ext.exten === extension);
886
+ }
887
+
888
+ constructor({ id, extensions, endpointCustom, endpointSccp, endpointSip } = {}) {
889
+ this.id = id;
890
+ this.extensions = extensions;
891
+ this.endpointCustom = endpointCustom || null;
892
+ this.endpointSccp = endpointSccp || null;
893
+ this.endpointSip = endpointSip || null;
894
+
895
+ // Useful to compare instead of instanceof with minified code
896
+ this.type = 'Line';
897
+ }
898
+ }
899
+
900
+ //
879
901
 
880
-
881
-
882
-
883
-
884
-
885
-
886
-
887
902
 
888
-
889
903
 
890
-
891
-
892
-
893
-
894
-
895
-
896
-
897
-
898
-
899
-
900
-
901
-
902
-
903
904
 
904
-
905
-
905
+
906
+
907
+ const FORWARD_KEYS = {
908
+ BUSY: 'busy',
909
+ NO_ANSWER: 'noanswer',
910
+ UNCONDITIONAL: 'unconditional',
911
+ };
912
+
913
+
906
914
 
907
-
908
-
909
915
 
916
+
910
917
 
911
918
 
912
-
913
-
914
-
915
-
916
-
917
-
918
-
919
-
920
-
921
-
922
-
923
-
919
+ class ForwardOption {
924
920
 
925
-
926
-
927
-
928
-
929
921
 
930
-
922
+
923
+
924
+ static parse(plain , key ) {
925
+ return new ForwardOption({
926
+ destination: plain.destination || '',
927
+ enabled: plain.enabled,
928
+ key,
929
+ });
930
+ }
931
+
932
+ static newFrom(profile ) {
933
+ return newFrom(profile, ForwardOption);
934
+ }
935
+
936
+ constructor({ destination, enabled, key } = {}) {
937
+ this.destination = destination;
938
+ this.enabled = enabled;
939
+ this.key = key;
940
+ }
941
+
942
+ setDestination(number ) {
943
+ this.destination = number;
944
+
945
+ return this;
946
+ }
947
+
948
+ setEnabled(enabled ) {
949
+ this.enabled = enabled;
950
+
951
+ return this;
952
+ }
953
+
954
+ is(other ) {
955
+ return this.key === other.key;
956
+ }
957
+ }
958
+
959
+ //
960
+
961
+
962
+ const availableCodecs = ['vp8', 'vp9', 'h264'];
963
+
964
+
931
965
 
932
-
933
-
934
-
935
-
936
966
 
937
-
938
-
939
-
940
967
 
941
-
942
-
943
-
944
-
945
-
946
-
947
-
948
-
949
-
950
968
 
969
+
970
+
971
+
972
+
973
+
974
+
975
+
976
+
977
+
951
978
 
952
979
 
980
+
981
+
982
+
953
983
 
954
-
955
-
956
-
957
984
 
958
-
985
+
959
986
 
960
-
961
-
962
-
963
-
964
-
965
987
 
966
-
967
-
968
-
988
+
989
+
990
+
991
+
992
+
969
993
 
970
994
 
971
-
995
+ class SipLine {
996
+
997
+
998
+
972
999
 
973
-
1000
+
974
1001
 
975
-
976
-
977
-
978
-
979
- const SOURCE_MOBILE = 'mobile';
980
-
981
- class Contact {
982
1002
 
983
-
984
-
985
-
986
-
987
-
988
-
989
-
1003
+
1004
+
1005
+
990
1006
 
991
-
992
-
993
-
994
-
995
-
996
-
997
-
998
1007
 
999
-
1000
-
1001
-
1002
-
1003
-
1004
-
1005
-
1006
-
1007
-
1008
-
1009
-
1010
-
1011
-
1012
-
1013
-
1014
- static merge(oldContacts , newContacts ) {
1015
- return newContacts.map(current => {
1016
- const old = oldContacts.find(contact => contact && contact.is(current));
1017
-
1018
- return old ? current.merge(old) : current;
1019
- });
1020
- }
1021
-
1022
- static sortContacts(a , b ) {
1023
- const aNames = a.separateName();
1024
- const bNames = b.separateName();
1025
- const aLastName = aNames.lastName;
1026
- const bLastName = bNames.lastName;
1027
-
1028
- // last Name can be empty
1029
- if (aLastName === bLastName) {
1030
- return aNames.firstName.localeCompare(bNames.firstName);
1031
- }
1032
-
1033
- return aLastName.localeCompare(bLastName);
1034
- }
1035
-
1036
- static parseMany(response ) {
1037
- return response.results.map(r => Contact.parse(r, response.column_types));
1038
- }
1039
-
1040
- static manyGraphQlWithNumbersParser(numbers ) {
1041
- return (response ) => {
1042
- if (!response.data || !response.data.me || !response.data.me.contacts) {
1043
- return [];
1044
- }
1045
-
1046
- return response.data.me.contacts.edges.map((edge, i) => {
1047
- if (!edge.node) {
1048
- return null;
1049
- }
1050
-
1051
- const { email } = edge.node;
1052
- const name = edge.node.firstname && edge.node.lastname
1053
- ? `${edge.node.firstname || ''} ${edge.node.lastname || ''}` : edge.node.wazoReverse;
1054
-
1055
- return new Contact({
1056
- name,
1057
- number: numbers[i],
1058
- numbers: [{ label: 'primary', number: numbers[i] }],
1059
- backend: edge.node.wazoBackend,
1060
- source: edge.node.wazoSourceName,
1061
- sourceId: edge.node.wazoSourceEntryId || '',
1062
- email: email || '',
1063
- emails: email ? [{ label: 'primary', email }] : [],
1064
- uuid: edge.node.userUuid,
1065
- });
1066
- }).filter(contact => !!contact);
1067
- };
1068
- }
1069
-
1070
- static fetchNumbers(plain , columns ) {
1071
- const numberColumns = columns
1072
- .map((e, index) => ({ index, columnName: e }))
1073
- .filter(e => e.columnName === 'number' || e.columnName === 'callable')
1074
- .map(e => e.index);
1075
-
1076
- return plain.column_values.filter((e, index) => numberColumns.some(i => i === index) && e !== null);
1077
- }
1078
-
1079
- static parse(plain , columns ) {
1080
- const numbers = Contact.fetchNumbers(plain, columns);
1081
- const email = plain.column_values[columns.indexOf('email')];
1082
-
1083
- return new Contact({
1084
- name: plain.column_values[columns.indexOf('name')],
1085
- number: numbers.length ? numbers[0] : '',
1086
- numbers: numbers.map((number, i) => ({ label: i === 0 ? 'primary' : 'secondary', number })),
1087
- favorited: plain.column_values[columns.indexOf('favorite')],
1088
- email: email || '',
1089
- emails: email ? [{ label: 'primary', email }] : [],
1090
- entreprise: plain.column_values[columns.indexOf('entreprise')] || '',
1091
- birthday: plain.column_values[columns.indexOf('birthday')] || '',
1092
- address: plain.column_values[columns.indexOf('address')] || '',
1093
- note: plain.column_values[columns.indexOf('note')] || '',
1094
- endpointId: plain.relations.endpoint_id,
1095
- personal: plain.column_values[columns.indexOf('personal')],
1096
- source: plain.source,
1097
- sourceId: plain.relations.source_entry_id || '',
1098
- uuid: plain.relations.user_uuid,
1099
- backend: plain.backend || '',
1100
- });
1101
- }
1102
-
1103
- static parseManyPersonal(results ) {
1104
- return results.map(r => Contact.parsePersonal(r));
1105
- }
1106
-
1107
- static parsePersonal(plain ) {
1108
- return new Contact({
1109
- name: `${plain.firstName || plain.firstname || ''} ${plain.lastName || plain.lastname || ''}`,
1110
- number: plain.number || '',
1111
- numbers: plain.number ? [{ label: 'primary', number: plain.number }] : [],
1112
- email: plain.email || '',
1113
- emails: plain.email ? [{ label: 'primary', email: plain.email }] : [],
1114
- source: 'personal',
1115
- sourceId: plain.id || '',
1116
- entreprise: plain.entreprise || '',
1117
- birthday: plain.birthday || '',
1118
- address: plain.address || '',
1119
- note: plain.note || '',
1120
- favorited: plain.favorited,
1121
- personal: true,
1122
- backend: plain.backend || BACKEND.PERSONAL,
1123
- });
1124
- }
1125
-
1126
- static parseMobile(plain ) {
1127
- let address = '';
1128
- if (plain.postalAddresses.length) {
1129
- const postalAddress = plain.postalAddresses[0];
1130
-
1131
- address = `${postalAddress.street} ${postalAddress.city} ${postalAddress.postCode} ${postalAddress.country}`;
1132
- }
1133
-
1134
- return new Contact({
1135
- name: `${plain.givenName || ''} ${plain.familyName || ''}`,
1136
- number: plain.phoneNumbers.length ? plain.phoneNumbers[0].number : '',
1137
- numbers: plain.phoneNumbers.length ? [{ label: 'primary', number: plain.phoneNumbers[0].number }] : [],
1138
- email: plain.emailAddresses.length ? plain.emailAddresses[0].email : '',
1139
- emails: plain.emailAddresses.length ? [{ label: 'primary', email: plain.emailAddresses[0].email }] : [],
1140
- source: SOURCE_MOBILE,
1141
- sourceId: plain.recordID || '',
1142
- birthday: plain.birthday ? `${plain.birthday.year}-${plain.birthday.month}-${plain.birthday.day}` : '',
1143
- address,
1144
- note: plain.note || '',
1145
- favorited: false,
1146
- personal: true,
1147
- });
1148
- }
1149
-
1150
- static parseManyOffice365(response , source ) {
1151
- return response.map(r => Contact.parseOffice365(r, source));
1152
- }
1153
-
1154
- static parseOffice365(single , source ) {
1155
- const emails = [];
1156
- const numbers = [];
1157
1008
 
1158
- if (single.emailAddresses) {
1159
- single.emailAddresses.map(email => emails.push({ email: email.address }));
1160
- }
1009
+ static parse(plain ) {
1010
+ let { username, secret, host } = plain;
1161
1011
 
1162
- if (single.businessPhones) {
1163
- single.businessPhones.map(phone => numbers.push({ label: 'business', number: phone }));
1164
- }
1012
+ // Since 20.13 engine so options are now in section
1013
+ if (plain.auth_section_options) {
1014
+ // $FlowFixMe
1015
+ const findOption = (options , name ) => options.find(option => option[0] === name);
1165
1016
 
1166
- if (single.mobilePhone) {
1167
- numbers.push({ label: 'mobile', number: single.mobilePhone });
1168
- }
1017
+ const usernameOption = findOption(plain.auth_section_options, 'username');
1018
+ const secretOption = findOption(plain.auth_section_options, 'password');
1019
+ const hostOption = findOption(plain.endpoint_section_options, 'media_address');
1169
1020
 
1170
- if (single.homePhones) {
1171
- single.homePhones.map(phone => numbers.push({ label: 'home', number: phone }));
1021
+ username = usernameOption ? usernameOption[1] : '';
1022
+ secret = secretOption ? secretOption[1] : '';
1023
+ host = hostOption ? hostOption[1] : '';
1172
1024
  }
1173
1025
 
1174
- return new Contact({
1175
- sourceId: single.id || '',
1176
- name: single.displayName || '',
1177
- number: numbers.length ? numbers[0].number : '',
1178
- numbers,
1179
- emails,
1180
- source: source.name,
1181
- backend: BACKEND.OFFICE365,
1026
+ return new SipLine({
1027
+ id: plain.id,
1028
+ uuid: plain.uuid,
1029
+ tenantUuid: plain.tenant_uuid,
1030
+ username,
1031
+ secret,
1032
+ type: plain.type,
1033
+ host,
1034
+ options: plain.options,
1035
+ endpointSectionOptions: plain.endpoint_section_options,
1036
+ links: plain.links,
1037
+ trunk: plain.trunk,
1038
+ line: plain.line,
1182
1039
  });
1183
1040
  }
1184
1041
 
1185
- static parseManyGoogle(response , source ) {
1186
- return response.map(r => Contact.parseGoogle(r, source));
1042
+ static newFrom(sipLine ) {
1043
+ return newFrom(sipLine, SipLine);
1187
1044
  }
1188
1045
 
1189
- static parseGoogle(single , source ) {
1190
- const emails = [];
1191
- const numbers = [];
1192
-
1193
- if (single.emails) {
1194
- single.emails.forEach(email =>
1195
- (typeof email === 'object' ? { email: email.address, label: email.label } : { email }));
1196
- }
1197
-
1198
- if (single.numbers_by_label) {
1199
- Object.keys(single.numbers_by_label).forEach(label =>
1200
- numbers.push({ label, number: single.numbers_by_label[label] }));
1201
- } else if (single.numbers) {
1202
- single.numbers.forEach(phone => numbers.push({ number: phone }));
1046
+ is(line ) {
1047
+ if (line.uuid) {
1048
+ return this.uuid === line.uuid;
1203
1049
  }
1204
-
1205
- return new Contact({
1206
- sourceId: single.id || '',
1207
- name: single.name || '',
1208
- number: numbers.length ? numbers[0].number : '',
1209
- numbers,
1210
- emails,
1211
- source: source.name,
1212
- backend: BACKEND.GOOGLE,
1213
- });
1214
- }
1215
-
1216
- static parseManyWazo(response , source ) {
1217
- return response.map(r => Contact.parseWazo(r, source));
1050
+ return this.id === line.id;
1218
1051
  }
1219
1052
 
1220
- static parseWazo(single , source ) {
1221
- const emails = [];
1222
- const numbers = [];
1223
-
1224
- if (single.email) {
1225
- emails.push({ label: 'email', email: single.email });
1226
- }
1227
-
1228
- if (single.exten) {
1229
- numbers.push({ label: 'exten', number: single.exten });
1230
- }
1231
-
1232
- if (single.mobile_phone_number) {
1233
- numbers.push({ label: 'mobile', number: single.mobile_phone_number });
1234
- }
1235
-
1236
- return new Contact({
1237
- uuid: single.uuid,
1238
- sourceId: String(single.id) || '',
1239
- name: `${single.firstname}${single.lastname ? ` ${single.lastname}` : ''}`,
1240
- number: numbers.length ? numbers[0].number : '',
1241
- numbers,
1242
- emails,
1243
- source: source.name,
1244
- backend: BACKEND.WAZO,
1245
- });
1053
+ getOptions() {
1054
+ // `options` params have beend remove since 20.13... we should now use endpointSectionOptions
1055
+ return this.endpointSectionOptions || this.options || [];
1246
1056
  }
1247
1057
 
1248
- static parseManyConference(response , source ) {
1249
- return response.map(r => Contact.parseConference(r, source));
1058
+ isWebRtc() {
1059
+ return this.getOptions().some(option => option[0] === 'webrtc' && option[1] === 'yes');
1250
1060
  }
1251
1061
 
1252
- static parseConference(single , source ) {
1253
- const numbers = [];
1254
- let firstNumber = '';
1255
-
1256
- if (single && single.extensions && single.extensions.length > 0 && single.extensions[0].exten) {
1257
- firstNumber = single.extensions[0].exten;
1258
- numbers.push({ label: 'exten', number: firstNumber });
1259
- }
1260
-
1261
- return new Contact({
1262
- sourceId: String(single.id),
1263
- name: single.name,
1264
- number: firstNumber,
1265
- numbers,
1266
- source: source.name,
1267
- backend: 'conference',
1268
- });
1062
+ hasVideo() {
1063
+ const allow = this.getOptions().find(option => option[0] === 'allow');
1064
+ return Array.isArray(allow) && allow[1].split(',').some(codec => availableCodecs.some(c => c === codec));
1269
1065
  }
1270
1066
 
1271
- static newFrom(contact ) {
1272
- return newFrom(contact, Contact);
1067
+ hasVideoConference() {
1068
+ return (
1069
+ this.getOptions().some(option => option[0] === 'max_audio_streams' && parseInt(option[1], 10) > 0)
1070
+ && this.getOptions().some(option => option[0] === 'max_video_streams' && parseInt(option[1], 10) > 1)
1071
+ );
1273
1072
  }
1274
1073
 
1275
1074
  constructor({
1276
1075
  id,
1277
1076
  uuid,
1278
- name,
1279
- number,
1280
- numbers,
1281
- email,
1282
- emails,
1283
- source,
1284
- sourceId,
1285
- entreprise,
1286
- birthday,
1287
- address,
1288
- note,
1289
- state,
1290
- lineState,
1291
- lastActivity,
1292
- mobile,
1293
- status,
1294
- endpointId,
1295
- personal,
1296
- favorited,
1297
- backend,
1298
- personalStatus,
1299
- sessions,
1300
- connected,
1301
- doNotDisturb,
1302
- ringing,
1303
- previousPresential,
1304
- lines,
1077
+ tenantUuid,
1078
+ username,
1079
+ secret,
1080
+ type,
1081
+ host,
1082
+ options,
1083
+ endpointSectionOptions,
1084
+ links,
1085
+ trunk,
1086
+ line,
1305
1087
  } = {}) {
1306
1088
  this.id = id;
1307
1089
  this.uuid = uuid;
1308
- this.name = name || '';
1309
- this.number = number;
1310
- this.numbers = numbers;
1311
- this.email = email;
1312
- this.emails = emails;
1313
- this.source = source;
1314
- this.sourceId = sourceId;
1315
- this.entreprise = entreprise;
1316
- this.birthday = birthday;
1317
- this.address = address;
1318
- this.note = note;
1319
- this.state = state;
1320
- this.lineState = lineState;
1321
- this.lastActivity = lastActivity;
1322
- this.mobile = mobile;
1323
- this.status = status;
1324
- this.endpointId = endpointId;
1325
- this.personal = personal;
1326
- this.favorited = favorited;
1327
- this.backend = backend;
1328
- this.personalStatus = personalStatus || '';
1329
- this.sessions = sessions || [];
1330
- this.connected = connected;
1331
- this.doNotDisturb = doNotDisturb;
1332
- this.ringing = ringing;
1333
- this.previousPresential = previousPresential;
1334
- this.lines = lines || [];
1090
+ this.tenantUuid = tenantUuid;
1091
+ this.username = username;
1092
+ this.secret = secret;
1093
+ this.type = type;
1094
+ this.host = host;
1095
+ this.options = options;
1096
+ this.endpointSectionOptions = endpointSectionOptions;
1097
+ this.links = links;
1098
+ this.trunk = trunk;
1099
+ this.line = line;
1335
1100
 
1336
1101
  // Useful to compare instead of instanceof with minified code
1337
- this.type = 'Contact';
1102
+ this.type = 'SipLine';
1338
1103
  }
1104
+ }
1339
1105
 
1340
- setFavorite(value ) {
1341
- this.favorited = value;
1106
+ //
1342
1107
 
1343
- return this;
1344
- }
1108
+ const STATE = {
1109
+ AVAILABLE: 'available',
1110
+ UNAVAILABLE: 'unavailable',
1111
+ INVISIBLE: 'invisible',
1112
+ DISCONNECTED: 'disconnected',
1113
+ AWAY: 'away',
1114
+ };
1345
1115
 
1346
- is(other ) {
1347
- const sameSourceId = this.sourceId !== null && other.sourceId !== null && this.sourceId === other.sourceId;
1348
- const sameUuid = !!this.uuid && !!other.uuid && this.uuid === other.uuid;
1116
+ const LINE_STATE = {
1117
+ AVAILABLE: 'available',
1118
+ HOLDING: 'holding',
1119
+ RINGING: 'ringing',
1120
+ TALKING: 'talking',
1121
+ UNAVAILABLE: 'unavailable',
1122
+ PROGRESSING: 'progressing',
1123
+ };
1349
1124
 
1350
- const hasSameId = sameSourceId || sameUuid;
1351
- const hasSameBackend = !!this.backend && !!other.backend && this.backend === other.backend;
1352
- const hasSameSource = !!this.source && !!other.source && this.source === other.source;
1125
+
1126
+
1127
+
1128
+
1129
+
1130
+
1131
+
1132
+
1133
+
1134
+
1135
+
1136
+
1137
+
1138
+
1139
+
1140
+
1141
+
1142
+
1143
+
1144
+
1145
+
1146
+
1147
+
1148
+
1149
+
1150
+
1151
+
1152
+
1153
+
1154
+
1155
+
1156
+
1157
+
1158
+
1159
+
1160
+
1161
+
1162
+
1163
+
1164
+
1165
+
1166
+
1167
+
1168
+
1169
+
1170
+
1171
+
1172
+
1173
+
1174
+
1175
+
1176
+
1177
+
1178
+
1179
+
1180
+
1181
+
1182
+
1353
1183
 
1354
- return !!other && hasSameId && hasSameBackend && hasSameSource;
1184
+
1185
+
1186
+
1187
+
1188
+
1189
+
1190
+
1191
+
1192
+
1193
+
1194
+
1195
+
1196
+
1197
+
1198
+
1199
+
1200
+
1201
+
1202
+
1203
+
1204
+
1205
+
1206
+
1207
+
1208
+
1209
+
1210
+
1211
+
1212
+
1213
+ class Profile {
1214
+
1215
+
1216
+
1217
+
1218
+
1219
+
1220
+
1221
+
1222
+
1223
+
1224
+
1225
+
1226
+
1227
+
1228
+
1229
+
1230
+
1231
+
1232
+
1233
+ static parse(plain ) {
1234
+ return new Profile({
1235
+ id: plain.uuid,
1236
+ firstName: plain.firstName || plain.firstname || '',
1237
+ lastName: plain.lastName || plain.lastname || '',
1238
+ email: plain.email,
1239
+ lines: plain.lines.map(line => Line.parse(line)),
1240
+ username: plain.username,
1241
+ mobileNumber: plain.mobile_phone_number || '',
1242
+ forwards: [
1243
+ ForwardOption.parse(plain.forwards.unconditional, FORWARD_KEYS.UNCONDITIONAL),
1244
+ ForwardOption.parse(plain.forwards.noanswer, FORWARD_KEYS.NO_ANSWER),
1245
+ ForwardOption.parse(plain.forwards.busy, FORWARD_KEYS.BUSY),
1246
+ ],
1247
+ doNotDisturb: plain.services.dnd.enabled,
1248
+ subscriptionType: plain.subscription_type,
1249
+ voicemail: plain.voicemail,
1250
+ switchboards: plain.switchboards || [],
1251
+ agent: plain.agent,
1252
+ status: '',
1253
+ callPickupTargetUsers: plain.call_pickup_target_users || [],
1254
+ onlineCallRecordEnabled: plain.online_call_record_enabled,
1255
+ });
1355
1256
  }
1356
1257
 
1357
- hasId(id ) {
1358
- return this.uuid === id;
1258
+ static newFrom(profile ) {
1259
+ return newFrom(profile, Profile);
1359
1260
  }
1360
1261
 
1361
- hasNumber(number ) {
1362
- return this.number === number;
1363
- }
1262
+ constructor({
1263
+ id,
1264
+ firstName,
1265
+ lastName,
1266
+ email,
1267
+ lines,
1268
+ username,
1269
+ mobileNumber,
1270
+ forwards,
1271
+ doNotDisturb,
1272
+ state,
1273
+ subscriptionType,
1274
+ voicemail,
1275
+ switchboards,
1276
+ agent,
1277
+ status,
1278
+ sipLines,
1279
+ callPickupTargetUsers,
1280
+ onlineCallRecordEnabled,
1281
+ } = {}) {
1282
+ this.id = id;
1283
+ this.firstName = firstName;
1284
+ this.lastName = lastName;
1285
+ this.email = email;
1286
+ this.lines = lines;
1287
+ this.username = username;
1288
+ this.mobileNumber = mobileNumber;
1289
+ this.forwards = forwards;
1290
+ this.doNotDisturb = doNotDisturb;
1291
+ this.state = state;
1292
+ this.voicemail = voicemail;
1293
+ this.subscriptionType = subscriptionType;
1294
+ this.switchboards = switchboards;
1295
+ this.agent = agent;
1296
+ this.status = status;
1297
+ this.callPickupTargetUsers = callPickupTargetUsers;
1298
+ this.onlineCallRecordEnabled = onlineCallRecordEnabled;
1364
1299
 
1365
- hasEndpointId(endpointId ) {
1366
- return this.endpointId === endpointId;
1300
+ this.sipLines = sipLines || [];
1367
1301
  }
1368
1302
 
1369
- isAvailable() {
1370
- return this.state === STATE.AVAILABLE;
1371
- }
1303
+ static getLinesState(lines ) {
1304
+ let result = LINE_STATE.UNAVAILABLE;
1372
1305
 
1373
- isAway() {
1374
- return this.state === STATE.AWAY;
1375
- }
1306
+ // eslint-disable-next-line
1307
+ for (const line of lines) {
1308
+ if (line.state === LINE_STATE.RINGING) {
1309
+ result = LINE_STATE.RINGING;
1310
+ break;
1311
+ }
1376
1312
 
1377
- isUnavailable() {
1378
- return this.state === STATE.UNAVAILABLE;
1379
- }
1313
+ if (line.state === LINE_STATE.TALKING) {
1314
+ result = LINE_STATE.TALKING;
1315
+ break;
1316
+ }
1380
1317
 
1381
- isInvisible() {
1382
- return this.state === STATE.INVISIBLE;
1383
- }
1318
+ if (line.state === LINE_STATE.AVAILABLE) {
1319
+ result = LINE_STATE.AVAILABLE;
1320
+ }
1321
+ }
1384
1322
 
1385
- isInCall() {
1386
- return this.lineState === LINE_STATE.TALKING || this.lineState === LINE_STATE.HOLDING;
1323
+ return result;
1387
1324
  }
1388
1325
 
1389
- isRinging() {
1390
- return this.ringing;
1326
+ hasId(id ) {
1327
+ return id === this.id;
1391
1328
  }
1392
1329
 
1393
- isInUseOrRinging() {
1394
- return this.lineState === LINE_STATE.TALKING || this.lineState === LINE_STATE.RINGING;
1395
- }
1330
+ setMobileNumber(number ) {
1331
+ this.mobileNumber = number;
1396
1332
 
1397
- isProgressing() {
1398
- return this.lineState === LINE_STATE.PROGRESSING;
1333
+ return this;
1399
1334
  }
1400
1335
 
1401
- merge(old ) {
1402
- Object.keys(old).filter(key => key !== 'lineState').forEach(key => {
1403
- // $FlowFixMe
1404
- this[key] = old[key] || this[key];
1405
- });
1336
+ setForwardOption(forwardOption ) {
1337
+ const updatedForwardOptions = this.forwards.slice();
1338
+ const index = updatedForwardOptions.findIndex(forward => forward.is(forwardOption));
1339
+ updatedForwardOptions.splice(index, 1, forwardOption);
1406
1340
 
1407
- if (old.lineState && !this.lineState) {
1408
- this.lineState = old.lineState;
1409
- }
1341
+ this.forwards = updatedForwardOptions;
1410
1342
 
1411
1343
  return this;
1412
1344
  }
1413
1345
 
1414
- isIntern() {
1415
- return !!this.uuid;
1416
- }
1417
-
1418
- isCallable(session ) {
1419
- return !!this.number && !!session && !session.is(this);
1420
- }
1421
-
1422
- isFromMobile() {
1423
- return this.source === SOURCE_MOBILE;
1424
- }
1346
+ setDoNotDisturb(enabled ) {
1347
+ this.doNotDisturb = enabled;
1425
1348
 
1426
- isFavorite() {
1427
- return this.favorited;
1349
+ return this;
1428
1350
  }
1429
1351
 
1430
- separateName() {
1431
- if (!this.name) {
1432
- return {
1433
- firstName: '',
1434
- lastName: '',
1435
- };
1436
- }
1437
- const names = this.name.split(/\s+/);
1438
- const firstName = names[0];
1439
- const lastName = names.slice(1).join(' ');
1352
+ setState(state ) {
1353
+ this.state = state;
1440
1354
 
1441
- return {
1442
- firstName,
1443
- lastName,
1444
- };
1355
+ return this;
1445
1356
  }
1446
1357
  }
1447
1358
 
1448
- // Can't use `semver` package as Wazo version aren't in semver format
1449
-
1450
- const compareVersions = (a, b) => {
1451
- let i;
1452
- let diff;
1453
- const regExStrip0 = /(\.0+)+$/;
1454
- const segmentsA = a.replace(regExStrip0, '').split('.');
1455
- const segmentsB = b.replace(regExStrip0, '').split('.');
1456
- const l = Math.min(segmentsA.length, segmentsB.length);
1359
+ //
1360
+
1457
1361
 
1458
- for (i = 0; i < l; i++) {
1459
- diff = parseInt(segmentsA[i], 10) - parseInt(segmentsB[i], 10);
1460
- if (diff) {
1461
- return diff;
1462
- }
1463
- }
1464
- return segmentsA.length - segmentsB.length;
1362
+ const BACKEND = {
1363
+ OFFICE365: 'office365',
1364
+ PERSONAL: 'personal',
1365
+ GOOGLE: 'google',
1366
+ WAZO: 'wazo',
1465
1367
  };
1466
1368
 
1467
- //
1369
+
1370
+
1371
+
1372
+
1373
+
1374
+
1375
+
1376
+
1377
+
1378
+
1379
+
1380
+
1468
1381
 
1469
- const swarmKey = KEYUTIL.getKey(swarmPublicKey);
1470
- const MINIMUM_WAZO_ENGINE_VERSION_FOR_DEFAULT_CONTEXT = '19.08';
1382
+
1383
+
1384
+
1385
+
1386
+
1387
+
1388
+
1389
+
1390
+
1391
+
1392
+
1393
+
1394
+
1395
+
1396
+
1397
+
1398
+
1399
+
1400
+
1401
+
1471
1402
 
1403
+
1404
+
1405
+
1406
+
1407
+
1408
+
1472
1409
 
1410
+
1411
+
1412
+
1413
+
1414
+
1415
+
1416
+
1417
+
1418
+
1419
+
1420
+
1421
+
1422
+
1473
1423
 
1474
1424
 
1475
-
1476
-
1477
-
1478
-
1479
-
1480
-
1481
1425
 
1426
+
1427
+
1428
+
1429
+
1430
+
1431
+
1432
+
1433
+
1434
+
1435
+
1436
+
1437
+
1438
+
1439
+
1440
+
1441
+
1442
+ // @see: https://github.com/rt2zz/react-native-contacts#example-contact-record
1443
+
1444
+
1445
+
1446
+
1447
+
1448
+
1449
+
1482
1450
 
1451
+
1452
+
1453
+
1454
+
1483
1455
 
1484
-
1456
+
1485
1457
 
1458
+
1459
+
1460
+
1486
1461
 
1462
+
1463
+
1487
1464
 
1465
+
1466
+
1488
1467
 
1489
-
1490
-
1491
-
1492
-
1493
-
1468
+
1469
+
1494
1470
 
1495
-
1471
+
1472
+
1473
+
1474
+
1475
+
1476
+
1477
+
1496
1478
 
1497
1479
 
1498
1480
 
1481
+
1482
+
1483
+
1484
+
1485
+
1486
+
1499
1487
 
1500
-
1501
-
1488
+
1489
+
1490
+
1491
+
1492
+
1493
+
1494
+
1495
+
1496
+
1497
+
1498
+
1499
+
1500
+
1501
+
1502
+
1503
+
1504
+
1505
+
1506
+
1507
+
1508
+
1509
+
1502
1510
 
1503
1511
 
1504
1512
 
1513
+
1514
+
1515
+
1516
+
1517
+
1518
+
1505
1519
 
1506
-
1520
+
1521
+
1522
+
1523
+
1524
+
1525
+
1526
+
1527
+
1528
+
1507
1529
 
1530
+
1531
+
1532
+
1533
+
1534
+
1535
+
1536
+
1537
+
1538
+
1539
+
1508
1540
 
1541
+
1542
+
1509
1543
 
1510
1544
 
1511
-
1545
+
1546
+
1547
+
1548
+
1512
1549
 
1513
1550
 
1551
+
1552
+
1553
+
1554
+
1555
+
1556
+
1557
+
1558
+
1559
+
1560
+
1561
+
1562
+
1563
+
1564
+
1514
1565
 
1515
-
1516
-
1566
+
1567
+
1568
+
1569
+
1570
+
1571
+
1517
1572
 
1573
+
1574
+
1575
+
1576
+
1577
+
1518
1578
 
1519
1579
 
1520
- class Session {
1580
+ const SOURCE_MOBILE = 'mobile';
1581
+
1582
+ class Contact {
1583
+
1584
+
1585
+
1521
1586
 
1522
1587
 
1523
-
1588
+
1589
+
1590
+
1591
+
1592
+
1593
+
1594
+
1595
+
1524
1596
 
1525
1597
 
1598
+
1599
+
1600
+
1601
+
1602
+
1603
+
1604
+
1605
+
1606
+
1607
+
1608
+
1609
+
1526
1610
 
1527
-
1528
1611
 
1529
1612
 
1530
1613
 
1531
-
1532
-
1533
- static parse(plain ) {
1534
- const token = plain.data.metadata ? plain.data.metadata.jwt : null;
1535
- let authorizations = [];
1536
1614
 
1537
- // Add authorizations from JWT
1538
- if (token) {
1539
- const isValid = jws.JWS.verifyJWT(token, swarmKey, { alg: ['RS256'], verifyAt: new Date() });
1540
- if (isValid) {
1541
- const decodedToken = jws.JWS.readSafeJSONString(b64utoutf8(token.split('.')[1]));
1542
- authorizations = decodedToken ? decodedToken.authorizations : [];
1543
- }
1544
- }
1615
+ static merge(oldContacts , newContacts ) {
1616
+ return newContacts.map(current => {
1617
+ const old = oldContacts.find(contact => contact && contact.is(current));
1545
1618
 
1546
- return new Session({
1547
- token: plain.data.token,
1548
- refreshToken: plain.data.refresh_token || null,
1549
- uuid: plain.data.metadata ? plain.data.metadata.uuid : null,
1550
- sessionUuid: plain.data.session_uuid,
1551
- authorizations,
1552
- acl: plain.data.acls ? plain.data.acls : plain.data.acl ? plain.data.acl : [],
1553
- tenantUuid: plain.data.metadata ? plain.data.metadata.tenant_uuid : undefined,
1554
- expiresAt: new Date(`${plain.data.utc_expires_at}z`),
1555
- engineUuid: plain.data.xivo_uuid,
1619
+ return old ? current.merge(old) : current;
1556
1620
  });
1557
1621
  }
1558
1622
 
1559
- static newFrom(profile ) {
1560
- return newFrom(profile, Session);
1561
- }
1623
+ static sortContacts(a , b ) {
1624
+ const aNames = a.separateName();
1625
+ const bNames = b.separateName();
1626
+ const aLastName = aNames.lastName;
1627
+ const bLastName = bNames.lastName;
1562
1628
 
1563
- constructor({
1564
- token,
1565
- uuid,
1566
- tenantUuid,
1567
- profile,
1568
- expiresAt,
1569
- authorizations,
1570
- acl,
1571
- engineVersion,
1572
- refreshToken,
1573
- sessionUuid,
1574
- engineUuid,
1575
- } = {}) {
1576
- this.token = token;
1577
- this.uuid = uuid;
1578
- this.tenantUuid = tenantUuid || null;
1579
- this.profile = profile;
1580
- this.expiresAt = expiresAt;
1581
- this.authorizations = authorizations || [];
1582
- this.acl = acl || [];
1583
- this.engineVersion = engineVersion;
1584
- this.refreshToken = refreshToken;
1585
- this.sessionUuid = sessionUuid;
1586
- this.engineUuid = engineUuid;
1587
- }
1629
+ // last Name can be empty
1630
+ if (aLastName === bLastName) {
1631
+ return aNames.firstName.localeCompare(bNames.firstName);
1632
+ }
1588
1633
 
1589
- hasExpired(date = new Date()) {
1590
- return date >= this.expiresAt;
1634
+ return aLastName.localeCompare(bLastName);
1591
1635
  }
1592
1636
 
1593
- is(contact ) {
1594
- return Boolean(contact) && this.uuid === contact.uuid;
1637
+ static parseMany(response ) {
1638
+ return response.results.map(r => Contact.parse(r, response.column_types));
1595
1639
  }
1596
1640
 
1597
- using(profile ) {
1598
- this.profile = profile;
1599
-
1600
- return this;
1601
- }
1641
+ static manyGraphQlWithNumbersParser(numbers ) {
1642
+ return (response ) => {
1643
+ if (!response.data || !response.data.me || !response.data.me.contacts) {
1644
+ return [];
1645
+ }
1602
1646
 
1603
- hasAuthorizations() {
1604
- return this.authorizations && !!this.authorizations.length;
1605
- }
1647
+ return response.data.me.contacts.edges.map((edge, i) => {
1648
+ if (!edge.node) {
1649
+ return null;
1650
+ }
1606
1651
 
1607
- displayName() {
1608
- return this.profile ? `${this.profile.firstName} ${this.profile.lastName}` : '';
1609
- }
1652
+ const { email } = edge.node;
1653
+ const name = edge.node.firstname && edge.node.lastname
1654
+ ? `${edge.node.firstname || ''} ${edge.node.lastname || ''}` : edge.node.wazoReverse;
1610
1655
 
1611
- hasAccessToVoicemail() {
1612
- if (!this.profile) {
1613
- return false;
1614
- }
1615
- return !!this.profile.voicemail;
1656
+ return new Contact({
1657
+ name,
1658
+ number: numbers[i],
1659
+ numbers: [{ label: 'primary', number: numbers[i] }],
1660
+ backend: edge.node.wazoBackend,
1661
+ source: edge.node.wazoSourceName,
1662
+ sourceId: edge.node.wazoSourceEntryId || '',
1663
+ email: email || '',
1664
+ emails: email ? [{ label: 'primary', email }] : [],
1665
+ uuid: edge.node.userUuid,
1666
+ });
1667
+ }).filter(contact => !!contact);
1668
+ };
1616
1669
  }
1617
1670
 
1618
- primaryLine() {
1619
- return this.profile && this.profile.lines.length > 0 ? this.profile.lines[0] : null;
1671
+ static fetchNumbers(plain , columns ) {
1672
+ const numberColumns = columns
1673
+ .map((e, index) => ({ index, columnName: e }))
1674
+ .filter(e => e.columnName === 'number' || e.columnName === 'callable')
1675
+ .map(e => e.index);
1676
+
1677
+ return plain.column_values.filter((e, index) => numberColumns.some(i => i === index) && e !== null);
1620
1678
  }
1621
1679
 
1622
- primarySipLine() {
1623
- return this.profile && this.profile.sipLines.length > 0 ? this.profile.sipLines[0] : null;
1680
+ static parse(plain , columns ) {
1681
+ const numbers = Contact.fetchNumbers(plain, columns);
1682
+ const email = plain.column_values[columns.indexOf('email')];
1683
+
1684
+ return new Contact({
1685
+ name: plain.column_values[columns.indexOf('name')],
1686
+ number: numbers.length ? numbers[0] : '',
1687
+ numbers: numbers.map((number, i) => ({ label: i === 0 ? 'primary' : 'secondary', number })),
1688
+ favorited: plain.column_values[columns.indexOf('favorite')],
1689
+ email: email || '',
1690
+ emails: email ? [{ label: 'primary', email }] : [],
1691
+ entreprise: plain.column_values[columns.indexOf('entreprise')] || '',
1692
+ birthday: plain.column_values[columns.indexOf('birthday')] || '',
1693
+ address: plain.column_values[columns.indexOf('address')] || '',
1694
+ note: plain.column_values[columns.indexOf('note')] || '',
1695
+ endpointId: plain.relations.endpoint_id,
1696
+ personal: plain.column_values[columns.indexOf('personal')],
1697
+ source: plain.source,
1698
+ sourceId: plain.relations.source_entry_id || '',
1699
+ uuid: plain.relations.user_uuid,
1700
+ backend: plain.backend || '',
1701
+ });
1624
1702
  }
1625
1703
 
1626
- primaryWebRtcLine() {
1627
- return this.profile && (this.profile.sipLines || []).find(sipLine => sipLine && sipLine.isWebRtc());
1704
+ static parseManyPersonal(results ) {
1705
+ return results.map(r => Contact.parsePersonal(r));
1628
1706
  }
1629
1707
 
1630
- primaryCtiLine() {
1631
- return this.profile && (this.profile.sipLines || []).find(sipLine => sipLine && !sipLine.isWebRtc());
1708
+ static parsePersonal(plain ) {
1709
+ return new Contact({
1710
+ name: `${plain.firstName || plain.firstname || ''} ${plain.lastName || plain.lastname || ''}`,
1711
+ number: plain.number || '',
1712
+ numbers: plain.number ? [{ label: 'primary', number: plain.number }] : [],
1713
+ email: plain.email || '',
1714
+ emails: plain.email ? [{ label: 'primary', email: plain.email }] : [],
1715
+ source: 'personal',
1716
+ sourceId: plain.id || '',
1717
+ entreprise: plain.entreprise || '',
1718
+ birthday: plain.birthday || '',
1719
+ address: plain.address || '',
1720
+ note: plain.note || '',
1721
+ favorited: plain.favorited,
1722
+ personal: true,
1723
+ backend: plain.backend || BACKEND.PERSONAL,
1724
+ });
1632
1725
  }
1633
1726
 
1634
- primaryContext() {
1635
- if (this.engineVersion) {
1636
- if (this.hasEngineVersionGte(MINIMUM_WAZO_ENGINE_VERSION_FOR_DEFAULT_CONTEXT)) {
1637
- return 'default';
1638
- }
1639
- }
1727
+ static parseMobile(plain ) {
1728
+ let address = '';
1729
+ if (plain.postalAddresses.length) {
1730
+ const postalAddress = plain.postalAddresses[0];
1640
1731
 
1641
- const line = this.primaryLine();
1732
+ address = `${postalAddress.street} ${postalAddress.city} ${postalAddress.postCode} ${postalAddress.country}`;
1733
+ }
1642
1734
 
1643
- return line && line.extensions.length > 0 ? line.extensions[0].context : 'default';
1735
+ return new Contact({
1736
+ name: `${plain.givenName || ''} ${plain.familyName || ''}`,
1737
+ number: plain.phoneNumbers.length ? plain.phoneNumbers[0].number : '',
1738
+ numbers: plain.phoneNumbers.length ? [{ label: 'primary', number: plain.phoneNumbers[0].number }] : [],
1739
+ email: plain.emailAddresses.length ? plain.emailAddresses[0].email : '',
1740
+ emails: plain.emailAddresses.length ? [{ label: 'primary', email: plain.emailAddresses[0].email }] : [],
1741
+ source: SOURCE_MOBILE,
1742
+ sourceId: plain.recordID || '',
1743
+ birthday: plain.birthday ? `${plain.birthday.year}-${plain.birthday.month}-${plain.birthday.day}` : '',
1744
+ address,
1745
+ note: plain.note || '',
1746
+ favorited: false,
1747
+ personal: true,
1748
+ });
1644
1749
  }
1645
1750
 
1646
- hasEngineVersionGte(version ) {
1647
- return this.engineVersion && compareVersions(String(this.engineVersion), String(version)) >= 0;
1751
+ static parseManyOffice365(response , source ) {
1752
+ return response.map(r => Contact.parseOffice365(r, source));
1648
1753
  }
1649
1754
 
1650
- primaryNumber() {
1651
- const line = this.primaryLine();
1755
+ static parseOffice365(single , source ) {
1756
+ const emails = [];
1757
+ const numbers = [];
1652
1758
 
1653
- return line && line.extensions.length ? line.extensions[0].exten : null;
1654
- }
1759
+ if (single.emailAddresses) {
1760
+ single.emailAddresses.map(email => emails.push({ email: email.address }));
1761
+ }
1655
1762
 
1656
- allLines() {
1657
- return this.profile ? this.profile.lines || [] : [];
1658
- }
1763
+ if (single.businessPhones) {
1764
+ single.businessPhones.map(phone => numbers.push({ label: 'business', number: phone }));
1765
+ }
1659
1766
 
1660
- allNumbers() {
1661
- const extensions = this.allLines().map(line => line.extensions.map(extension => extension.exten));
1662
- if (!extensions.length) {
1663
- return [];
1767
+ if (single.mobilePhone) {
1768
+ numbers.push({ label: 'mobile', number: single.mobilePhone });
1664
1769
  }
1665
1770
 
1666
- return extensions.reduce((a, b) => a.concat(b));
1667
- }
1771
+ if (single.homePhones) {
1772
+ single.homePhones.map(phone => numbers.push({ label: 'home', number: phone }));
1773
+ }
1668
1774
 
1669
- hasExtension(extension ) {
1670
- return this.allNumbers().some(number => number === extension);
1775
+ return new Contact({
1776
+ sourceId: single.id || '',
1777
+ name: single.displayName || '',
1778
+ number: numbers.length ? numbers[0].number : '',
1779
+ numbers,
1780
+ emails,
1781
+ source: source.name,
1782
+ backend: BACKEND.OFFICE365,
1783
+ });
1671
1784
  }
1672
1785
 
1673
- get acls() {
1674
- console.warn('`acls` property of Session has been removed in Wazo\'s SDK, please use `acl` instead.');
1675
- return this.acl;
1786
+ static parseManyGoogle(response , source ) {
1787
+ return response.map(r => Contact.parseGoogle(r, source));
1676
1788
  }
1677
- }
1678
1789
 
1679
- //
1680
- /* eslint-disable no-param-reassign */
1790
+ static parseGoogle(single , source ) {
1791
+ const emails = [];
1792
+ const numbers = [];
1681
1793
 
1682
- var updateFrom = (instance , from ) => {
1683
- Object.keys(from).forEach(key => {
1684
- // $FlowFixMe
1685
- if (
1686
- (!instance[key] && from[key])
1687
- || (typeof instance[key] !== 'undefined' && typeof from[key] !== 'undefined' && instance[key] !== from[key])
1688
- ) {
1689
- // $FlowFixMe
1690
- instance[key] = from[key];
1794
+ if (single.emails) {
1795
+ single.emails.forEach(email =>
1796
+ (typeof email === 'object' ? { email: email.address, label: email.label } : { email }));
1691
1797
  }
1692
- });
1693
- };
1694
1798
 
1695
- /* */
1799
+ if (single.numbers_by_label) {
1800
+ Object.keys(single.numbers_by_label).forEach(label =>
1801
+ numbers.push({ label, number: single.numbers_by_label[label] }));
1802
+ } else if (single.numbers) {
1803
+ single.numbers.forEach(phone => numbers.push({ number: phone }));
1804
+ }
1696
1805
 
1697
- class BadResponse extends Error {
1698
- static fromResponse(error , status ) {
1699
- return new BadResponse(
1700
- error.message || JSON.stringify(error),
1701
- status,
1702
- error.timestamp,
1703
- error.error_id,
1704
- error.details,
1705
- error,
1706
- );
1806
+ return new Contact({
1807
+ sourceId: single.id || '',
1808
+ name: single.name || '',
1809
+ number: numbers.length ? numbers[0].number : '',
1810
+ numbers,
1811
+ emails,
1812
+ source: source.name,
1813
+ backend: BACKEND.GOOGLE,
1814
+ });
1707
1815
  }
1708
1816
 
1709
- static fromText(response , status ) {
1710
- return new BadResponse(response, status);
1817
+ static parseManyWazo(response , source ) {
1818
+ return response.map(r => Contact.parseWazo(r, source));
1711
1819
  }
1712
1820
 
1713
-
1714
-
1715
-
1716
-
1717
-
1718
-
1821
+ static parseWazo(single , source ) {
1822
+ const emails = [];
1823
+ const numbers = [];
1719
1824
 
1720
- constructor(
1721
- message ,
1722
- status ,
1723
- timestamp = null,
1724
- errorId = null,
1725
- details = null,
1726
- error = null,
1727
- ) {
1728
- super(message);
1825
+ if (single.email) {
1826
+ emails.push({ label: 'email', email: single.email });
1827
+ }
1729
1828
 
1730
- this.timestamp = timestamp;
1731
- this.status = status;
1732
- this.errorId = errorId;
1733
- this.details = details;
1734
- this.error = error;
1735
- }
1736
- }
1829
+ if (single.exten) {
1830
+ numbers.push({ label: 'exten', number: single.exten });
1831
+ }
1737
1832
 
1738
- /* */
1833
+ if (single.mobile_phone_number) {
1834
+ numbers.push({ label: 'mobile', number: single.mobile_phone_number });
1835
+ }
1739
1836
 
1740
- class ServerError extends BadResponse {
1741
- static fromResponse(error , status ) {
1742
- return new ServerError(
1743
- error.message || JSON.stringify(error),
1744
- status,
1745
- error.timestamp,
1746
- error.error_id,
1747
- error.details,
1748
- );
1837
+ return new Contact({
1838
+ uuid: single.uuid,
1839
+ sourceId: String(single.id) || '',
1840
+ name: `${single.firstname}${single.lastname ? ` ${single.lastname}` : ''}`,
1841
+ number: numbers.length ? numbers[0].number : '',
1842
+ numbers,
1843
+ emails,
1844
+ source: source.name,
1845
+ backend: BACKEND.WAZO,
1846
+ });
1749
1847
  }
1750
1848
 
1751
- static fromText(response , status ) {
1752
- return new ServerError(response, status);
1849
+ static parseManyConference(response , source ) {
1850
+ return response.map(r => Contact.parseConference(r, source));
1753
1851
  }
1754
- }
1755
-
1756
- /* */
1757
- /* global navigator */
1758
- const isMobile = () => typeof navigator !== 'undefined' && navigator.product === 'ReactNative';
1759
-
1760
- /* global window */
1761
-
1762
- const TRACE = 'trace';
1763
- const DEBUG = 'debug';
1764
- const INFO = 'info';
1765
- const LOG = 'log';
1766
- const WARN = 'warn';
1767
- const ERROR = 'error';
1768
- const CONSOLE_METHODS = [INFO, LOG, WARN, ERROR];
1769
- const LOG_LEVELS = [TRACE, DEBUG, INFO, LOG, WARN, ERROR];
1770
- const CATEGORY_PREFIX = 'logger-category=';
1771
1852
 
1772
- const MAX_REMOTE_RETRY = 100;
1853
+ static parseConference(single , source ) {
1854
+ const numbers = [];
1855
+ let firstNumber = '';
1773
1856
 
1774
- const addLevelsTo = (instance , withMethods = false) => {
1775
- instance.TRACE = TRACE;
1776
- instance.DEBUG = DEBUG;
1777
- instance.INFO = INFO;
1778
- instance.LOG = LOG;
1779
- instance.WARN = WARN;
1780
- instance.ERROR = ERROR;
1857
+ if (single && single.extensions && single.extensions.length > 0 && single.extensions[0].exten) {
1858
+ firstNumber = single.extensions[0].exten;
1859
+ numbers.push({ label: 'exten', number: firstNumber });
1860
+ }
1781
1861
 
1782
- if (withMethods) {
1783
- instance.trace = (...args) => instance.apply(null, [TRACE, ...args]);
1784
- instance.debug = (...args) => instance.apply(null, [DEBUG, ...args]);
1785
- instance.info = (...args) => instance.apply(null, [INFO, ...args]);
1786
- instance.log = (...args) => instance.apply(null, [LOG, ...args]);
1787
- instance.warn = (...args) => instance.apply(null, [WARN, ...args]);
1788
- instance.error = (...args) => instance.apply(null, [ERROR, ...args]);
1862
+ return new Contact({
1863
+ sourceId: String(single.id),
1864
+ name: single.name,
1865
+ number: firstNumber,
1866
+ numbers,
1867
+ source: source.name,
1868
+ backend: 'conference',
1869
+ });
1789
1870
  }
1790
1871
 
1791
- return instance;
1792
- };
1872
+ static newFrom(contact ) {
1873
+ return newFrom(contact, Contact);
1874
+ }
1793
1875
 
1794
- const safeStringify = (object ) => {
1795
- const result = '{"message": "Not parsable JSON"}';
1796
- try {
1797
- return JSON.stringify(object);
1798
- } catch (e) {
1799
- // Nothing to do
1876
+ constructor({
1877
+ id,
1878
+ uuid,
1879
+ name,
1880
+ number,
1881
+ numbers,
1882
+ email,
1883
+ emails,
1884
+ source,
1885
+ sourceId,
1886
+ entreprise,
1887
+ birthday,
1888
+ address,
1889
+ note,
1890
+ state,
1891
+ lineState,
1892
+ lastActivity,
1893
+ mobile,
1894
+ status,
1895
+ endpointId,
1896
+ personal,
1897
+ favorited,
1898
+ backend,
1899
+ personalStatus,
1900
+ sessions,
1901
+ connected,
1902
+ doNotDisturb,
1903
+ ringing,
1904
+ previousPresential,
1905
+ lines,
1906
+ } = {}) {
1907
+ this.id = id;
1908
+ this.uuid = uuid;
1909
+ this.name = name || '';
1910
+ this.number = number;
1911
+ this.numbers = numbers;
1912
+ this.email = email;
1913
+ this.emails = emails;
1914
+ this.source = source;
1915
+ this.sourceId = sourceId;
1916
+ this.entreprise = entreprise;
1917
+ this.birthday = birthday;
1918
+ this.address = address;
1919
+ this.note = note;
1920
+ this.state = state;
1921
+ this.lineState = lineState;
1922
+ this.lastActivity = lastActivity;
1923
+ this.mobile = mobile;
1924
+ this.status = status;
1925
+ this.endpointId = endpointId;
1926
+ this.personal = personal;
1927
+ this.favorited = favorited;
1928
+ this.backend = backend;
1929
+ this.personalStatus = personalStatus || '';
1930
+ this.sessions = sessions || [];
1931
+ this.connected = connected;
1932
+ this.doNotDisturb = doNotDisturb;
1933
+ this.ringing = ringing;
1934
+ this.previousPresential = previousPresential;
1935
+ this.lines = lines || [];
1936
+
1937
+ // Useful to compare instead of instanceof with minified code
1938
+ this.type = 'Contact';
1800
1939
  }
1801
1940
 
1802
- return result;
1803
- };
1941
+ setFavorite(value ) {
1942
+ this.favorited = value;
1804
1943
 
1805
- class IssueReporter {
1806
-
1807
-
1808
-
1809
-
1810
-
1944
+ return this;
1945
+ }
1811
1946
 
1812
-
1813
-
1814
-
1947
+ is(other ) {
1948
+ const sameSourceId = this.sourceId !== null && other.sourceId !== null && this.sourceId === other.sourceId;
1949
+ const sameUuid = !!this.uuid && !!other.uuid && this.uuid === other.uuid;
1815
1950
 
1816
- constructor() {
1817
- addLevelsTo(this);
1951
+ const hasSameId = sameSourceId || sameUuid;
1952
+ const hasSameBackend = !!this.backend && !!other.backend && this.backend === other.backend;
1953
+ const hasSameSource = !!this.source && !!other.source && this.source === other.source;
1818
1954
 
1819
- this.oldConsoleMethods = null;
1820
- this.enabled = false;
1821
- this.remoteClientConfiguration = null;
1955
+ return !!other && hasSameId && hasSameBackend && hasSameSource;
1822
1956
  }
1823
1957
 
1824
- init() {
1825
- this._catchConsole();
1958
+ hasId(id ) {
1959
+ return this.uuid === id;
1826
1960
  }
1827
1961
 
1828
- configureRemoteClient(configuration = { tag: 'wazo-sdk', host: null, port: null, level: null, extra: {} }) {
1829
- this.remoteClientConfiguration = configuration;
1962
+ hasNumber(number ) {
1963
+ return this.number === number;
1830
1964
  }
1831
1965
 
1832
- enable() {
1833
- if (!this.oldConsoleMethods) {
1834
- this.init();
1835
- }
1836
-
1837
- this.enabled = true;
1966
+ hasEndpointId(endpointId ) {
1967
+ return this.endpointId === endpointId;
1838
1968
  }
1839
1969
 
1840
- disable() {
1841
- this.enabled = false;
1970
+ isAvailable() {
1971
+ return this.state === STATE.AVAILABLE;
1842
1972
  }
1843
1973
 
1844
- loggerFor(category ) {
1845
- const logger = (level , ...args ) => {
1846
- this.log.apply(this, [level, this._makeCategory(category), ...args]);
1847
- };
1848
-
1849
- return addLevelsTo(logger, true);
1974
+ isAway() {
1975
+ return this.state === STATE.AWAY;
1850
1976
  }
1851
1977
 
1852
- removeSlashes(str ) {
1853
- return str.replace(/"/g, "'").replace(/\\/g, '');
1978
+ isUnavailable() {
1979
+ return this.state === STATE.UNAVAILABLE;
1854
1980
  }
1855
1981
 
1856
- log(level , ...args ) {
1857
- if (!this.enabled) {
1858
- return;
1859
- }
1860
-
1861
- // Handle category label
1862
- let category = null;
1863
- let extra = {};
1864
- if (args[0].indexOf(CATEGORY_PREFIX) === 0) {
1865
- category = args[0].split('=')[1];
1866
- // eslint-disable-next-line no-param-reassign
1867
- args.splice(0, 1);
1868
- }
1869
-
1870
- // Handle extra data as object for the last argument
1871
- const lastArg = args[args.length - 1];
1872
- if (lastArg && ((typeof lastArg === 'object' && Object.keys(lastArg).length) || lastArg instanceof Error)) {
1873
- if (lastArg instanceof Error) {
1874
- extra = {
1875
- errorMessage: lastArg.message,
1876
- errorStack: lastArg.stack,
1877
- errorType: lastArg.constructor.name,
1878
- };
1879
- } else {
1880
- extra = lastArg;
1881
- }
1882
- // eslint-disable-next-line no-param-reassign
1883
- args.splice(1, 1);
1884
- }
1885
-
1886
- const date = moment().format('YYYY-MM-DD HH:mm:ss.SSS');
1887
- const message = args.join(', ');
1888
- let consoleMessage = message;
1889
-
1890
- if (Object.keys(extra).length) {
1891
- const parsedExtra = safeStringify(extra);
1892
- consoleMessage = `${consoleMessage} (${parsedExtra})`;
1893
- }
1982
+ isInvisible() {
1983
+ return this.state === STATE.INVISIBLE;
1984
+ }
1894
1985
 
1895
- if (category) {
1896
- consoleMessage = `[${category}] ${consoleMessage}`;
1897
- }
1986
+ isInCall() {
1987
+ return this.lineState === LINE_STATE.TALKING || this.lineState === LINE_STATE.HOLDING;
1988
+ }
1898
1989
 
1899
- // Log the message in the console anyway (but don't console.error on mobile)
1900
- const consoleLevel = isMobile() && level === 'error' ? WARN : level;
1990
+ isRinging() {
1991
+ return this.ringing;
1992
+ }
1901
1993
 
1902
- // eslint-disable-next-line
1903
- const oldMethod = this.oldConsoleMethods[consoleLevel] || this.oldConsoleMethods.log;
1904
- oldMethod.apply(oldMethod, [date, consoleMessage]);
1994
+ isInUseOrRinging() {
1995
+ return this.lineState === LINE_STATE.TALKING || this.lineState === LINE_STATE.RINGING;
1996
+ }
1905
1997
 
1906
- this._sendToRemoteLogger(level, { date, message, category, ...extra });
1998
+ isProgressing() {
1999
+ return this.lineState === LINE_STATE.PROGRESSING;
1907
2000
  }
1908
2001
 
1909
- logRequest(url , options , response , start ) {
1910
- if (!this.enabled) {
1911
- return;
1912
- }
1913
- const { status } = response;
1914
- const duration = new Date() - start;
2002
+ merge(old ) {
2003
+ Object.keys(old).filter(key => key !== 'lineState').forEach(key => {
2004
+ // $FlowFixMe
2005
+ this[key] = old[key] || this[key];
2006
+ });
1915
2007
 
1916
- let level = TRACE;
1917
- if (status >= 400 && status < 500) {
1918
- level = WARN;
1919
- } else if (status >= 500) {
1920
- level = ERROR;
2008
+ if (old.lineState && !this.lineState) {
2009
+ this.lineState = old.lineState;
1921
2010
  }
1922
2011
 
1923
- this.log(level, this._makeCategory('http'), url, {
1924
- status,
1925
- body: this.removeSlashes(JSON.stringify(options.body)),
1926
- method: options.method,
1927
- headers: options.headers,
1928
- duration,
1929
- });
1930
- }
1931
-
1932
- // Logs aren't stored anymore
1933
- getLogs() {
1934
- console.warn('IssueReporter\'s logs aren\'t stored anymore. Please use fluentd to store them');
1935
- return [];
2012
+ return this;
1936
2013
  }
1937
2014
 
1938
- getParsedLogs() {
1939
- console.warn('IssueReporter\'s logs aren\'t stored anymore. Please use fluentd to store them');
1940
- return [];
2015
+ isIntern() {
2016
+ return !!this.uuid;
1941
2017
  }
1942
2018
 
1943
- getReport() {
1944
- return this.getParsedLogs().join('\r\n');
2019
+ isCallable(session ) {
2020
+ return !!this.number && !!session && !session.is(this);
1945
2021
  }
1946
2022
 
1947
- _catchConsole() {
1948
- this.oldConsoleMethods = {};
1949
- CONSOLE_METHODS.forEach((methodName ) => {
1950
- // eslint-disable-next-line
1951
- this.oldConsoleMethods[methodName] = console[methodName];
1952
- window.console[methodName] = (...args) => {
1953
- // Store message
1954
- try {
1955
- this.log(methodName, args.join(' '));
1956
- // Use old console method to log it normally
1957
- this.oldConsoleMethods[methodName].apply(null, args);
1958
- } catch (e) {
1959
- // Avoid circular structure issues
1960
- }
1961
- };
1962
- });
2023
+ isFromMobile() {
2024
+ return this.source === SOURCE_MOBILE;
1963
2025
  }
1964
2026
 
1965
- _sendToRemoteLogger(level , payload , retry = 0) {
1966
- if (!this.remoteClientConfiguration || retry >= MAX_REMOTE_RETRY) {
1967
- return;
1968
- }
2027
+ isFavorite() {
2028
+ return this.favorited;
2029
+ }
1969
2030
 
1970
- const { tag, host, port, extra, level: minLevel } = this.remoteClientConfiguration;
1971
- if (!minLevel || this._isLevelAbove(minLevel, level)) {
1972
- return;
2031
+ separateName() {
2032
+ if (!this.name) {
2033
+ return {
2034
+ firstName: '',
2035
+ lastName: '',
2036
+ };
1973
2037
  }
2038
+ const names = this.name.split(/\s+/);
2039
+ const firstName = names[0];
2040
+ const lastName = names.slice(1).join(' ');
1974
2041
 
1975
- const isSecure = +port === 443;
1976
- const url = `http${isSecure ? 's' : ''}://${host}${isSecure ? '' : `:${port}`}/${tag}`;
1977
-
1978
- realFetch()(url, {
1979
- method: 'POST',
1980
- headers: {
1981
- Accept: 'application/json',
1982
- 'Content-Type': 'application/json',
1983
- },
1984
- body: safeStringify({
1985
- level,
1986
- ...payload,
1987
- ...extra,
1988
- }),
1989
- }).catch(() => {
1990
- setTimeout(() => {
1991
- // eslint-disable-next-line no-underscore-dangle
1992
- payload._retry = retry + 1;
1993
- this._sendToRemoteLogger(level, payload, retry + 1);
1994
- }, 5000 + retry * 1000);
1995
- });
2042
+ return {
2043
+ firstName,
2044
+ lastName,
2045
+ };
1996
2046
  }
2047
+ }
1997
2048
 
1998
- _isLevelAbove(level1 , level2 ) {
1999
- return LOG_LEVELS.indexOf(level1) > LOG_LEVELS.indexOf(level2);
2000
- }
2049
+ // Can't use `semver` package as Wazo version aren't in semver format
2001
2050
 
2002
- _makeCategory(category ) {
2003
- return `${CATEGORY_PREFIX}${category}`;
2051
+ const compareVersions = (a, b) => {
2052
+ let i;
2053
+ let diff;
2054
+ const regExStrip0 = /(\.0+)+$/;
2055
+ const segmentsA = a.replace(regExStrip0, '').split('.');
2056
+ const segmentsB = b.replace(regExStrip0, '').split('.');
2057
+ const l = Math.min(segmentsA.length, segmentsB.length);
2058
+
2059
+ for (i = 0; i < l; i++) {
2060
+ diff = parseInt(segmentsA[i], 10) - parseInt(segmentsB[i], 10);
2061
+ if (diff) {
2062
+ return diff;
2063
+ }
2004
2064
  }
2005
- }
2065
+ return segmentsA.length - segmentsB.length;
2066
+ };
2006
2067
 
2007
- var IssueReporter$1 = new IssueReporter();
2068
+ //
2008
2069
 
2009
- /* */
2070
+ const swarmKey = KEYUTIL.getKey(swarmPublicKey);
2071
+ const MINIMUM_WAZO_ENGINE_VERSION_FOR_DEFAULT_CONTEXT = '19.08';
2010
2072
 
2011
-
2012
2073
 
2074
+
2013
2075
 
2076
+
2077
+
2078
+
2079
+
2080
+
2081
+
2082
+
2014
2083
 
2015
-
2016
-
2084
+
2085
+
2086
+
2087
+
2017
2088
 
2089
+
2090
+
2091
+
2092
+
2093
+
2094
+
2095
+
2096
+
2097
+
2018
2098
 
2019
2099
 
2020
- const methods = ['head', 'get', 'post', 'put', 'delete'];
2021
-
2022
- const logger$1 = IssueReporter$1 ? IssueReporter$1.loggerFor('api') : console;
2023
-
2024
- // Use a function here to be able to mock it in tests
2025
- const realFetch = () => {
2026
- if (typeof document !== 'undefined') {
2027
- // Browser
2028
- return window.fetch;
2029
- }
2030
-
2031
- if (isMobile()) {
2032
- // React native
2033
- return fetch;
2034
- }
2035
-
2036
- // nodejs
2037
- // this package is disable for react-native in package.json because it requires nodejs modules
2038
- // Used to trick the optimizer to avoid requiring `node-fetch/lib/index` directly
2039
- // It causes to require it on browsers when delivered by a nodejs engine (cf: vitejs).
2040
- try {
2041
- // $FlowFixMe
2042
- return require(Math.random() >= 0 ? 'node-fetch/lib/index' : '');
2043
- } catch (e) {
2044
- return fetch;
2045
- }
2046
- };
2100
+
2101
+
2102
+
2103
+
2104
+
2047
2105
 
2048
- class ApiRequester {
2049
-
2106
+
2107
+
2050
2108
 
2051
-
2052
2109
 
2110
+
2111
+
2112
+
2113
+
2114
+
2053
2115
 
2116
+
2117
+
2054
2118
 
2055
-
2056
-
2057
-
2119
+
2058
2120
 
2059
-
2121
+ class Session {
2060
2122
 
2061
-
2062
2123
 
2063
-
2064
-
2065
- // eslint-disable-next-line
2066
- static successResponseParser(response , isJson ) {
2067
- return response.status === 204 || response.status === 201 || response.status === 200;
2068
- }
2069
-
2070
- static defaultParser(response ) {
2071
- return response.json().then((data ) => data);
2072
- }
2073
-
2074
- static getQueryString(obj ) {
2075
- return Object.keys(obj)
2076
- .filter(key => obj[key])
2077
- .map(key => `${key}=${encodeURIComponent(obj[key])}`)
2078
- .join('&');
2079
- }
2124
+
2125
+
2126
+
2127
+
2128
+
2129
+
2130
+
2131
+
2132
+
2080
2133
 
2081
- static base64Encode(str ) {
2082
- return typeof btoa !== 'undefined' ? btoa(str) : Base64.encode(str);
2083
- }
2134
+ static parse(plain ) {
2135
+ const token = plain.data.metadata ? plain.data.metadata.jwt : null;
2136
+ let authorizations = [];
2084
2137
 
2085
- // @see https://github.com/facebook/flow/issues/183#issuecomment-358607052
2086
- constructor({ server, refreshTokenCallback, clientId, agent = null, token = null, fetchOptions } ) {
2087
- this.server = server;
2088
- this.agent = agent;
2089
- this.clientId = clientId;
2090
- this.refreshTokenCallback = refreshTokenCallback;
2091
- this.refreshTokenPromise = null;
2092
- this.fetchOptions = fetchOptions || {};
2138
+ // Add authorizations from JWT
2093
2139
  if (token) {
2094
- this.token = token;
2140
+ const isValid = jws.JWS.verifyJWT(token, swarmKey, { alg: ['RS256'], verifyAt: new Date() });
2141
+ if (isValid) {
2142
+ const decodedToken = jws.JWS.readSafeJSONString(b64utoutf8(token.split('.')[1]));
2143
+ authorizations = decodedToken ? decodedToken.authorizations : [];
2144
+ }
2095
2145
  }
2096
2146
 
2097
- this.shouldLogErrors = true;
2147
+ return new Session({
2148
+ token: plain.data.token,
2149
+ refreshToken: plain.data.refresh_token || null,
2150
+ uuid: plain.data.metadata ? plain.data.metadata.uuid : null,
2151
+ sessionUuid: plain.data.session_uuid,
2152
+ authorizations,
2153
+ acl: plain.data.acls ? plain.data.acls : plain.data.acl ? plain.data.acl : [],
2154
+ tenantUuid: plain.data.metadata ? plain.data.metadata.tenant_uuid : undefined,
2155
+ expiresAt: new Date(`${plain.data.utc_expires_at}z`),
2156
+ engineUuid: plain.data.xivo_uuid,
2157
+ });
2158
+ }
2098
2159
 
2099
- methods.forEach(method => {
2100
- // $FlowFixMe
2101
- ApiRequester.prototype[method] = function sugar(...args) {
2102
- // Add method in arguments passed to `call`
2103
- args.splice(1, 0, method);
2160
+ static newFrom(profile ) {
2161
+ return newFrom(profile, Session);
2162
+ }
2104
2163
 
2105
- return this.call.call(this, ...args);
2106
- };
2107
- });
2164
+ constructor({
2165
+ token,
2166
+ uuid,
2167
+ tenantUuid,
2168
+ profile,
2169
+ expiresAt,
2170
+ authorizations,
2171
+ acl,
2172
+ engineVersion,
2173
+ refreshToken,
2174
+ sessionUuid,
2175
+ engineUuid,
2176
+ } = {}) {
2177
+ this.token = token;
2178
+ this.uuid = uuid;
2179
+ this.tenantUuid = tenantUuid || null;
2180
+ this.profile = profile;
2181
+ this.expiresAt = expiresAt;
2182
+ this.authorizations = authorizations || [];
2183
+ this.acl = acl || [];
2184
+ this.engineVersion = engineVersion;
2185
+ this.refreshToken = refreshToken;
2186
+ this.sessionUuid = sessionUuid;
2187
+ this.engineUuid = engineUuid;
2108
2188
  }
2109
2189
 
2110
- setTenant(tenant ) {
2111
- this.tenant = tenant;
2190
+ hasExpired(date = new Date()) {
2191
+ return date >= this.expiresAt;
2112
2192
  }
2113
2193
 
2114
- setToken(token ) {
2115
- this.token = token;
2194
+ is(contact ) {
2195
+ return Boolean(contact) && this.uuid === contact.uuid;
2116
2196
  }
2117
2197
 
2118
- setFetchOptions(options ) {
2119
- this.fetchOptions = options;
2120
- }
2198
+ using(profile ) {
2199
+ this.profile = profile;
2121
2200
 
2122
- disableErrorLogging() {
2123
- this.shouldLogErrors = false;
2201
+ return this;
2124
2202
  }
2125
2203
 
2126
- enableErrorLogging() {
2127
- this.shouldLogErrors = true;
2204
+ hasAuthorizations() {
2205
+ return this.authorizations && !!this.authorizations.length;
2128
2206
  }
2129
2207
 
2130
- async call(
2131
- path ,
2132
- method = 'get',
2133
- body = null,
2134
- headers = null,
2135
- parse = ApiRequester.defaultParser,
2136
- firstCall = true,
2137
- ) {
2138
- const url = this.computeUrl(method, path, body);
2139
- const newHeaders = this.getHeaders(headers);
2140
- let newBody = method === 'get' ? null : body;
2141
- if (newBody && newHeaders['Content-Type'] === 'application/json') {
2142
- newBody = JSON.stringify(newBody);
2143
- }
2144
- const isHead = method === 'head';
2145
- const hasEmptyResponse = method === 'delete' || isHead;
2146
- const newParse = hasEmptyResponse ? ApiRequester.successResponseParser : parse;
2147
-
2148
- const fetchOptions = { ...this.fetchOptions || {} };
2149
- const extraHeaders = fetchOptions.headers || {};
2150
- delete fetchOptions.headers;
2151
-
2152
- const options = {
2153
- method,
2154
- body: newBody,
2155
- headers: { ...this.getHeaders(headers), ...extraHeaders },
2156
- agent: this.agent,
2157
- ...fetchOptions,
2158
- };
2208
+ displayName() {
2209
+ return this.profile ? `${this.profile.firstName} ${this.profile.lastName}` : '';
2210
+ }
2159
2211
 
2160
- if (this.refreshTokenPromise) {
2161
- logger$1.info('A token is already refreshing, waiting ...', { url });
2162
- await this.refreshTokenPromise;
2212
+ hasAccessToVoicemail() {
2213
+ if (!this.profile) {
2214
+ return false;
2163
2215
  }
2216
+ return !!this.profile.voicemail;
2217
+ }
2164
2218
 
2165
- const start = new Date();
2166
-
2167
- return realFetch()(url, options).then(response => {
2168
- const contentType = response.headers.get('content-type') || '';
2169
- const isJson = contentType.indexOf('application/json') !== -1;
2170
-
2171
- IssueReporter$1.logRequest(url, options, response, start);
2172
-
2173
- // Throw an error only if status >= 400
2174
- if ((isHead && response.status >= 500) || (!isHead && response.status >= 400)) {
2175
- const promise = isJson ? response.json() : response.text();
2176
- const exceptionClass = response.status >= 500 ? ServerError : BadResponse;
2177
-
2178
- return promise.then(async (err ) => {
2179
- // Check if the token is still valid
2180
- if (firstCall && this._checkTokenExpired(response, err)) {
2181
- logger$1.warn('token expired', { error: err.reason });
2182
- // Replay the call after refreshing the token
2183
- return this._replayWithNewToken(err, path, method, body, headers, parse);
2184
- }
2219
+ primaryLine() {
2220
+ return this.profile && this.profile.lines.length > 0 ? this.profile.lines[0] : null;
2221
+ }
2185
2222
 
2186
- const error = typeof err === 'string'
2187
- ? exceptionClass.fromText(err, response.status)
2188
- : exceptionClass.fromResponse(err, response.status);
2223
+ primarySipLine() {
2224
+ return this.profile && this.profile.sipLines.length > 0 ? this.profile.sipLines[0] : null;
2225
+ }
2189
2226
 
2190
- if (this.shouldLogErrors) {
2191
- logger$1.error('API error', error);
2192
- }
2227
+ primaryWebRtcLine() {
2228
+ return this.profile && (this.profile.sipLines || []).find(sipLine => sipLine && sipLine.isWebRtc());
2229
+ }
2193
2230
 
2194
- throw error;
2195
- });
2196
- }
2231
+ primaryCtiLine() {
2232
+ return this.profile && (this.profile.sipLines || []).find(sipLine => sipLine && !sipLine.isWebRtc());
2233
+ }
2197
2234
 
2198
- return newParse(response, isJson);
2199
- }).catch(error => {
2200
- if (this.shouldLogErrors) {
2201
- logger$1.error('Fetch failed', { url, options, message: error.message, stack: error.stack });
2235
+ primaryContext() {
2236
+ if (this.engineVersion) {
2237
+ if (this.hasEngineVersionGte(MINIMUM_WAZO_ENGINE_VERSION_FOR_DEFAULT_CONTEXT)) {
2238
+ return 'default';
2202
2239
  }
2240
+ }
2203
2241
 
2204
- throw error;
2205
- });
2206
- }
2207
-
2208
- _checkTokenExpired(response , err ) {
2209
- // Special case when authenticating form a token: we got a 404
2210
- const isTokenNotFound = response.status === 404 && this._isTokenNotFound(err);
2242
+ const line = this.primaryLine();
2211
2243
 
2212
- return response.status === 401 || isTokenNotFound;
2244
+ return line && line.extensions.length > 0 ? line.extensions[0].context : 'default';
2213
2245
  }
2214
2246
 
2215
- _isTokenNotFound(err ) {
2216
- return err && err.reason && err.reason[0] === 'No such token';
2247
+ hasEngineVersionGte(version ) {
2248
+ return this.engineVersion && compareVersions(String(this.engineVersion), String(version)) >= 0;
2217
2249
  }
2218
2250
 
2219
- _replayWithNewToken(
2220
- err ,
2221
- path ,
2222
- method ,
2223
- body = null,
2224
- headers = null,
2225
- parse ,
2226
- ) {
2227
- const isTokenNotFound = this._isTokenNotFound(err);
2228
- let newPath = path;
2229
- logger$1.info('refreshing token', { inProgress: !!this.refreshTokenPromise });
2230
-
2231
- this.refreshTokenPromise = this.refreshTokenPromise || this.refreshTokenCallback();
2251
+ primaryNumber() {
2252
+ const line = this.primaryLine();
2232
2253
 
2233
- return this.refreshTokenPromise.then(() => {
2234
- this.refreshTokenPromise = null;
2235
- logger$1.info('token refreshed', { isTokenNotFound });
2236
- if (isTokenNotFound) {
2237
- const pathParts = path.split('/');
2238
- pathParts.pop();
2239
- pathParts.push(this.token);
2240
- newPath = pathParts.join('/');
2241
- }
2254
+ return line && line.extensions.length ? line.extensions[0].exten : null;
2255
+ }
2242
2256
 
2243
- return this.call(newPath, method, body, headers, parse, false);
2244
- }).catch((e) => {
2245
- this.refreshTokenPromise = null;
2246
- throw e;
2247
- });
2257
+ allLines() {
2258
+ return this.profile ? this.profile.lines || [] : [];
2248
2259
  }
2249
2260
 
2250
- getHeaders(header ) {
2251
- if (header instanceof Object) {
2252
- return header;
2261
+ allNumbers() {
2262
+ const extensions = this.allLines().map(line => line.extensions.map(extension => extension.exten));
2263
+ if (!extensions.length) {
2264
+ return [];
2253
2265
  }
2254
2266
 
2255
- return {
2256
- 'X-Auth-Token': this.token,
2257
- ...(this.tenant ? { 'Wazo-Tenant': this.tenant } : null),
2258
- Accept: 'application/json',
2259
- 'Content-Type': 'application/json',
2260
- };
2267
+ return extensions.reduce((a, b) => a.concat(b));
2261
2268
  }
2262
2269
 
2263
- computeUrl(method , path , body ) {
2264
- const url = `${this.baseUrl}/${path}`;
2265
-
2266
- return method === 'get' && body && Object.keys(body).length ? `${url}?${ApiRequester.getQueryString(body)}` : url;
2270
+ hasExtension(extension ) {
2271
+ return this.allNumbers().some(number => number === extension);
2267
2272
  }
2268
2273
 
2269
- get baseUrl() {
2270
- return `https://${this.server}/api`;
2274
+ get acls() {
2275
+ console.warn('`acls` property of Session has been removed in Wazo\'s SDK, please use `acl` instead.');
2276
+ return this.acl;
2271
2277
  }
2272
2278
  }
2273
2279
 
@@ -2693,6 +2699,65 @@ class ExternalApp {
2693
2699
  }
2694
2700
  }
2695
2701
 
2702
+ //
2703
+
2704
+
2705
+
2706
+
2707
+
2708
+
2709
+
2710
+
2711
+
2712
+
2713
+
2714
+ class Meeting {
2715
+
2716
+
2717
+
2718
+
2719
+
2720
+
2721
+
2722
+
2723
+
2724
+
2725
+ static parse(plain ) {
2726
+ return new Meeting({
2727
+ guestSipAuthorization: plain.guest_sip_authorization,
2728
+ hostname: plain.hostname,
2729
+ name: plain.name,
2730
+ ownerUuids: plain.owner_uuids,
2731
+ port: plain.port,
2732
+ uuid: plain.uuid,
2733
+ number: plain.number,
2734
+ });
2735
+ }
2736
+
2737
+ static newFrom(meeting ) {
2738
+ return newFrom(meeting, Meeting);
2739
+ }
2740
+
2741
+ constructor({ uuid, name, guestSipAuthorization, ownerUuids, port, hostname, number } = {}) {
2742
+ this.guestSipAuthorization = guestSipAuthorization;
2743
+ this.hostname = hostname;
2744
+ this.name = name;
2745
+ this.ownerUuids = ownerUuids;
2746
+ this.port = port;
2747
+ this.uuid = uuid;
2748
+ this.number = number;
2749
+
2750
+ // Useful to compare instead of instanceof with minified code
2751
+ this.type = 'Meeting';
2752
+ }
2753
+
2754
+ getGuestSipCredentials() {
2755
+ // eslint-disable-next-line no-undef
2756
+ const [username, secret] = atob(this.guestSipAuthorization).split(':');
2757
+ return { username, secret };
2758
+ }
2759
+ }
2760
+
2696
2761
  /* */
2697
2762
 
2698
2763
  var confdMethods = (client , baseUrl ) => ({
@@ -2757,6 +2822,18 @@ var confdMethods = (client , baseUrl ) => ({
2757
2822
  return null;
2758
2823
  }
2759
2824
  },
2825
+
2826
+ createMyMeeting: (name ) =>
2827
+ client.post(`${baseUrl}/users/me/meetings`, { name }).then(Meeting.parse),
2828
+
2829
+ deleteMyMeeting: (meetingUuid ) =>
2830
+ client.delete(`${baseUrl}/users/me/meetings/${meetingUuid}`, null),
2831
+
2832
+ getMeeting: (meetingUuid ) =>
2833
+ client.get(`${baseUrl}/meetings/${meetingUuid}`, null).then(Meeting.parse),
2834
+
2835
+ guestGetMeeting: (meetingUuid ) =>
2836
+ client.get(`${baseUrl}/guests/me/meetings/${meetingUuid}`, null).then(Meeting.parse),
2760
2837
  });
2761
2838
 
2762
2839
  //
@@ -3135,7 +3212,11 @@ class CallLog {
3135
3212
  answered: plain.answered,
3136
3213
  callDirection: plain.call_direction,
3137
3214
  destination: {
3138
- extension: plain.requested_extension || plain.destination_extension,
3215
+ // @TEMP: Temporarily assuming empty numbers are meetings
3216
+ // which is admittedly a very dangerous assumption. Did i mention it was temporary?
3217
+ extension: plain.requested_extension
3218
+ || plain.destination_extension
3219
+ || `meeting-${plain.requested_name || plain.destination_name || ''}`,
3139
3220
  name: plain.requested_name || plain.destination_name || '',
3140
3221
  },
3141
3222
  source: {
@@ -3151,27 +3232,11 @@ class CallLog {
3151
3232
  }
3152
3233
 
3153
3234
  static parseNew(plain , session ) {
3154
- return new CallLog({
3155
- answer: plain.answer ? moment(plain.answer).toDate() : null,
3156
- answered: plain.answered,
3157
- callDirection: plain.call_direction,
3158
- destination: {
3159
- extension: plain.requested_extension || plain.destination_extension,
3160
- name: plain.requested_name || plain.destination_name || '',
3161
- },
3162
- source: {
3163
- extension: plain.source_extension,
3164
- name: plain.source_name,
3165
- },
3166
- id: plain.id,
3167
- duration: (plain.duration || 0) * 1000, // duration is in seconds
3168
- start: moment(plain.start).toDate(),
3169
- end: plain.end ? moment(plain.end).toDate() : null,
3170
- recordings: Recording.parseMany(plain.recordings),
3171
- // @TODO: FIXME add verification declined vs missed call
3172
- newMissedCall: session
3173
- && session.hasExtension(plain.requested_extension || plain.destination_extension) && !plain.answered,
3174
- });
3235
+ const callLog = CallLog.parse(plain);
3236
+ // @TODO: FIXME add verification declined vs missed call
3237
+ callLog.newMissedCall = session
3238
+ && session.hasExtension(plain.requested_extension || plain.destination_extension) && !plain.answered;
3239
+ return callLog;
3175
3240
  }
3176
3241
 
3177
3242
  static newFrom(profile ) {
@@ -3913,6 +3978,9 @@ var calldMethods = (client , baseUrl ) => ({
3913
3978
  getConferenceParticipantsAsUser: async (conferenceId ) =>
3914
3979
  client.get(`${baseUrl}/users/me/conferences/${conferenceId}/participants`),
3915
3980
 
3981
+ getMeetingParticipantsAsUser: async (meetingUuid ) =>
3982
+ client.get(`${baseUrl}/users/me/meetings/${meetingUuid}/participants`),
3983
+
3916
3984
  listTrunks: () => client.get(`${baseUrl}/trunks`),
3917
3985
 
3918
3986
  mute: (callId ) =>