@wazo/sdk 0.37.0-rc.5 → 0.37.2

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 (149) hide show
  1. package/dist/wazo-sdk.js +5 -5
  2. package/esm/__tests__/api-client.test.js +93 -16
  3. package/esm/__tests__/web-rtc-client.test.js +153 -54
  4. package/esm/api/agentd.js +24 -3
  5. package/esm/api/amid.js +7 -1
  6. package/esm/api/application.js +24 -3
  7. package/esm/api/auth.js +24 -3
  8. package/esm/api/call-logd.js +78 -20
  9. package/esm/api/calld.js +24 -20
  10. package/esm/api/chatd.js +24 -3
  11. package/esm/api/confd.js +24 -3
  12. package/esm/api/ctid-ng.js +24 -3
  13. package/esm/api/dird.js +27 -6
  14. package/esm/api/webhookd.js +7 -1
  15. package/esm/api-client.js +93 -16
  16. package/esm/checker/Checker.js +156 -54
  17. package/esm/checker/checks/aor.js +95 -16
  18. package/esm/checker/checks/api.js +94 -16
  19. package/esm/checker/checks/index.js +156 -54
  20. package/esm/checker/checks/wazo-websocket.js +7 -1
  21. package/esm/checker/checks/webrtc-transport.js +153 -54
  22. package/esm/checker/checks/webrtc.js +153 -54
  23. package/esm/domain/AdHocAPIConference.js +1717 -1640
  24. package/esm/domain/CallLog.js +71 -19
  25. package/esm/domain/CallSession.js +1 -18
  26. package/esm/domain/Contact.js +3 -3
  27. package/esm/domain/Features.js +93 -16
  28. package/esm/domain/IndirectTransfer.js +0 -17
  29. package/esm/domain/Phone/CTIPhone.js +100 -19
  30. package/esm/domain/Phone/Phone.js +0 -17
  31. package/esm/domain/Phone/WebRTCPhone.js +51 -36
  32. package/esm/domain/Recording.js +51 -0
  33. package/esm/domain/Room.js +0 -17
  34. package/esm/domain/SwitchboardCall.js +0 -17
  35. package/esm/domain/__tests__/CallLog.test.js +89 -19
  36. package/esm/domain/__tests__/CallSession.test.js +1 -18
  37. package/esm/domain/__tests__/Contact.test.js +3 -3
  38. package/esm/domain/__tests__/IndirectTransfer.test.js +1 -18
  39. package/esm/domain/__tests__/Room.test.js +1 -18
  40. package/esm/domain/__tests__/Session.test.js +7 -0
  41. package/esm/index.js +3436 -2911
  42. package/esm/lib/WazoSessionDescriptionHandler.js +28 -10
  43. package/esm/service/CallApi.js +93 -16
  44. package/esm/service/IssueReporter.js +7 -1
  45. package/esm/service/__tests__/IssueReporter.test.js +26 -2
  46. package/esm/service/getApiClient.js +93 -16
  47. package/esm/simple/Auth.js +619 -97
  48. package/esm/simple/Configuration.js +619 -97
  49. package/esm/simple/Directory.js +93 -16
  50. package/esm/simple/Phone.js +3261 -2739
  51. package/esm/simple/Websocket.js +7 -1
  52. package/esm/simple/index.js +619 -97
  53. package/esm/simple/room/LocalParticipant.js +619 -97
  54. package/esm/simple/room/Participant.js +619 -97
  55. package/esm/simple/room/RemoteParticipant.js +619 -97
  56. package/esm/simple/room/Room.js +619 -97
  57. package/esm/simple/room/Stream.js +619 -97
  58. package/esm/simple/room/__tests__/RemoteParticipant.test.js +619 -97
  59. package/esm/simple/utils.js +619 -97
  60. package/esm/utils/__tests__/api-requester.test.js +32 -6
  61. package/esm/utils/api-requester.js +24 -3
  62. package/esm/web-rtc-client.js +153 -54
  63. package/esm/websocket-client.js +7 -1
  64. package/lib/__tests__/api-client.test.js +93 -16
  65. package/lib/__tests__/web-rtc-client.test.js +154 -55
  66. package/lib/api/agentd.js +24 -3
  67. package/lib/api/amid.js +7 -1
  68. package/lib/api/application.js +24 -3
  69. package/lib/api/auth.js +24 -3
  70. package/lib/api/call-logd.js +78 -20
  71. package/lib/api/calld.js +24 -20
  72. package/lib/api/chatd.js +24 -3
  73. package/lib/api/confd.js +24 -3
  74. package/lib/api/ctid-ng.js +24 -3
  75. package/lib/api/dird.js +27 -6
  76. package/lib/api/webhookd.js +7 -1
  77. package/lib/api-client.js +93 -16
  78. package/lib/checker/Checker.js +157 -55
  79. package/lib/checker/checks/aor.js +95 -16
  80. package/lib/checker/checks/api.js +94 -16
  81. package/lib/checker/checks/index.js +157 -55
  82. package/lib/checker/checks/wazo-websocket.js +7 -1
  83. package/lib/checker/checks/webrtc-transport.js +154 -55
  84. package/lib/checker/checks/webrtc.js +154 -55
  85. package/lib/domain/AdHocAPIConference.js +1717 -1640
  86. package/lib/domain/CallLog.js +71 -19
  87. package/lib/domain/CallSession.js +1 -18
  88. package/lib/domain/Contact.js +3 -3
  89. package/lib/domain/Features.js +93 -16
  90. package/lib/domain/IndirectTransfer.js +0 -17
  91. package/lib/domain/Phone/CTIPhone.js +100 -19
  92. package/lib/domain/Phone/Phone.js +0 -16
  93. package/lib/domain/Phone/WebRTCPhone.js +51 -36
  94. package/lib/domain/Recording.js +57 -0
  95. package/lib/domain/Room.js +0 -17
  96. package/lib/domain/SwitchboardCall.js +0 -17
  97. package/lib/domain/__tests__/CallLog.test.js +89 -19
  98. package/lib/domain/__tests__/CallSession.test.js +1 -18
  99. package/lib/domain/__tests__/Contact.test.js +3 -3
  100. package/lib/domain/__tests__/IndirectTransfer.test.js +1 -18
  101. package/lib/domain/__tests__/Room.test.js +1 -18
  102. package/lib/domain/__tests__/Session.test.js +7 -0
  103. package/lib/index.js +3437 -2912
  104. package/lib/lib/WazoSessionDescriptionHandler.js +28 -10
  105. package/lib/service/CallApi.js +93 -16
  106. package/lib/service/IssueReporter.js +7 -1
  107. package/lib/service/__tests__/IssueReporter.test.js +26 -2
  108. package/lib/service/getApiClient.js +93 -16
  109. package/lib/simple/Auth.js +620 -98
  110. package/lib/simple/Configuration.js +620 -98
  111. package/lib/simple/Directory.js +93 -16
  112. package/lib/simple/Phone.js +3262 -2740
  113. package/lib/simple/Websocket.js +7 -1
  114. package/lib/simple/index.js +620 -98
  115. package/lib/simple/room/LocalParticipant.js +620 -98
  116. package/lib/simple/room/Participant.js +620 -98
  117. package/lib/simple/room/RemoteParticipant.js +620 -98
  118. package/lib/simple/room/Room.js +620 -98
  119. package/lib/simple/room/Stream.js +620 -98
  120. package/lib/simple/room/__tests__/RemoteParticipant.test.js +620 -98
  121. package/lib/simple/utils.js +620 -98
  122. package/lib/utils/__tests__/api-requester.test.js +32 -6
  123. package/lib/utils/api-requester.js +24 -3
  124. package/lib/web-rtc-client.js +154 -55
  125. package/lib/websocket-client.js +7 -1
  126. package/package.json +1 -1
  127. package/src/api-client.js +4 -0
  128. package/src/checker/checks/aor.js +2 -0
  129. package/src/checker/checks/api.js +1 -0
  130. package/src/domain/CallLog.js +18 -11
  131. package/src/domain/CallSession.js +1 -2
  132. package/src/domain/Contact.js +3 -3
  133. package/src/domain/Phone/CTIPhone.js +6 -2
  134. package/src/domain/Phone/Phone.js +2 -0
  135. package/src/domain/Phone/WebRTCPhone.js +26 -16
  136. package/src/domain/Recording.js +48 -0
  137. package/src/domain/__tests__/CallLog.test.js +19 -0
  138. package/src/domain/__tests__/Session.test.js +7 -0
  139. package/src/index.js +6 -0
  140. package/src/lib/WazoSessionDescriptionHandler.js +21 -9
  141. package/src/service/IssueReporter.js +7 -1
  142. package/src/service/__tests__/IssueReporter.test.js +20 -1
  143. package/src/simple/Phone.js +5 -1
  144. package/src/simple/index.js +6 -0
  145. package/src/simple/room/Room.js +18 -15
  146. package/src/simple/room/Stream.js +1 -4
  147. package/src/utils/__tests__/api-requester.test.js +9 -4
  148. package/src/utils/api-requester.js +17 -2
  149. package/src/web-rtc-client.js +40 -13
@@ -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,2052 +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;
610
+ static getQueryString(obj ) {
611
+ return Object.keys(obj)
612
+ .filter(key => obj[key])
613
+ .map(key => `${key}=${encodeURIComponent(obj[key])}`)
614
+ .join('&');
615
+ }
698
616
 
699
- this.sipLines = sipLines || [];
617
+ static base64Encode(str ) {
618
+ return typeof btoa !== 'undefined' ? btoa(str) : Base64.encode(str);
700
619
  }
701
620
 
702
- static getLinesState(lines ) {
703
- let result = LINE_STATE.UNAVAILABLE;
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
+ }
704
632
 
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
- }
633
+ this.shouldLogErrors = true;
711
634
 
712
- if (line.state === LINE_STATE.TALKING) {
713
- result = LINE_STATE.TALKING;
714
- break;
715
- }
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);
716
640
 
717
- if (line.state === LINE_STATE.AVAILABLE) {
718
- result = LINE_STATE.AVAILABLE;
719
- }
720
- }
641
+ return this.call.call(this, ...args);
642
+ };
643
+ });
644
+ }
721
645
 
722
- return result;
646
+ setTenant(tenant ) {
647
+ this.tenant = tenant;
723
648
  }
724
649
 
725
- hasId(id ) {
726
- return id === this.id;
650
+ setToken(token ) {
651
+ this.token = token;
727
652
  }
728
653
 
729
- setMobileNumber(number ) {
730
- this.mobileNumber = number;
654
+ setFetchOptions(options ) {
655
+ this.fetchOptions = options;
656
+ }
731
657
 
732
- return this;
658
+ disableErrorLogging() {
659
+ this.shouldLogErrors = false;
733
660
  }
734
661
 
735
- setForwardOption(forwardOption ) {
736
- const updatedForwardOptions = this.forwards.slice();
737
- const index = updatedForwardOptions.findIndex(forward => forward.is(forwardOption));
738
- updatedForwardOptions.splice(index, 1, forwardOption);
662
+ enableErrorLogging() {
663
+ this.shouldLogErrors = true;
664
+ }
739
665
 
740
- this.forwards = updatedForwardOptions;
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;
741
683
 
742
- 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
+ });
743
742
  }
744
743
 
745
- setDoNotDisturb(enabled ) {
746
- this.doNotDisturb = enabled;
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);
747
747
 
748
- return this;
748
+ return response.status === 401 || isTokenNotFound;
749
749
  }
750
750
 
751
- setState(state ) {
752
- this.state = state;
751
+ _isTokenNotFound(err ) {
752
+ return err && err.reason && err.reason[0] === 'No such token';
753
+ }
753
754
 
754
- return this;
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
-
890
-
891
-
892
-
893
903
 
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
967
 
939
-
940
-
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
1008
 
1018
- return old ? current.merge(old) : current;
1019
- });
1020
- }
1009
+ static parse(plain ) {
1010
+ let { username, secret, host } = plain;
1021
1011
 
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
-
1158
- if (single.emailAddresses) {
1159
- single.emailAddresses.map(email => emails.push({ email: email.address }));
1160
- }
1161
-
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
-
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
- }
1545
-
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,
1556
- });
1557
- }
1558
-
1559
- static newFrom(profile ) {
1560
- return newFrom(profile, Session);
1561
- }
1562
-
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
- }
1588
-
1589
- hasExpired(date = new Date()) {
1590
- return date >= this.expiresAt;
1591
- }
1592
-
1593
- is(contact ) {
1594
- return Boolean(contact) && this.uuid === contact.uuid;
1595
- }
1596
-
1597
- using(profile ) {
1598
- this.profile = profile;
1599
-
1600
- return this;
1601
- }
1602
-
1603
- hasAuthorizations() {
1604
- return this.authorizations && !!this.authorizations.length;
1605
- }
1606
-
1607
- displayName() {
1608
- return this.profile ? `${this.profile.firstName} ${this.profile.lastName}` : '';
1609
- }
1610
-
1611
- hasAccessToVoicemail() {
1612
- if (!this.profile) {
1613
- return false;
1614
- }
1615
- return !!this.profile.voicemail;
1616
- }
1617
-
1618
- primaryLine() {
1619
- return this.profile && this.profile.lines.length > 0 ? this.profile.lines[0] : null;
1620
- }
1621
-
1622
- primarySipLine() {
1623
- return this.profile && this.profile.sipLines.length > 0 ? this.profile.sipLines[0] : null;
1624
- }
1625
-
1626
- primaryWebRtcLine() {
1627
- return this.profile && (this.profile.sipLines || []).find(sipLine => sipLine && sipLine.isWebRtc());
1628
- }
1629
-
1630
- primaryCtiLine() {
1631
- return this.profile && (this.profile.sipLines || []).find(sipLine => sipLine && !sipLine.isWebRtc());
1632
- }
1633
-
1634
- primaryContext() {
1635
- if (this.engineVersion) {
1636
- if (this.hasEngineVersionGte(MINIMUM_WAZO_ENGINE_VERSION_FOR_DEFAULT_CONTEXT)) {
1637
- return 'default';
1638
- }
1639
- }
1640
1614
 
1641
- const line = this.primaryLine();
1615
+ static merge(oldContacts , newContacts ) {
1616
+ return newContacts.map(current => {
1617
+ const old = oldContacts.find(contact => contact && contact.is(current));
1642
1618
 
1643
- return line && line.extensions.length > 0 ? line.extensions[0].context : 'default';
1619
+ return old ? current.merge(old) : current;
1620
+ });
1644
1621
  }
1645
1622
 
1646
- hasEngineVersionGte(version ) {
1647
- return this.engineVersion && compareVersions(String(this.engineVersion), String(version)) >= 0;
1648
- }
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;
1649
1628
 
1650
- primaryNumber() {
1651
- const line = this.primaryLine();
1629
+ // last Name can be empty
1630
+ if (aLastName === bLastName) {
1631
+ return aNames.firstName.localeCompare(bNames.firstName);
1632
+ }
1652
1633
 
1653
- return line && line.extensions.length ? line.extensions[0].exten : null;
1634
+ return aLastName.localeCompare(bLastName);
1654
1635
  }
1655
1636
 
1656
- allLines() {
1657
- return this.profile ? this.profile.lines || [] : [];
1637
+ static parseMany(response ) {
1638
+ return response.results.map(r => Contact.parse(r, response.column_types));
1658
1639
  }
1659
1640
 
1660
- allNumbers() {
1661
- const extensions = this.allLines().map(line => line.extensions.map(extension => extension.exten));
1662
- if (!extensions.length) {
1663
- return [];
1664
- }
1641
+ static manyGraphQlWithNumbersParser(numbers ) {
1642
+ return (response ) => {
1643
+ if (!response.data || !response.data.me || !response.data.me.contacts) {
1644
+ return [];
1645
+ }
1665
1646
 
1666
- return extensions.reduce((a, b) => a.concat(b));
1667
- }
1647
+ return response.data.me.contacts.edges.map((edge, i) => {
1648
+ if (!edge.node) {
1649
+ return null;
1650
+ }
1668
1651
 
1669
- hasExtension(extension ) {
1670
- return this.allNumbers().some(number => number === extension);
1671
- }
1652
+ const { email } = edge.node;
1653
+ const name = edge.node.firstname && edge.node.lastname
1654
+ ? `${edge.node.firstname || ''} ${edge.node.lastname || ''}` : edge.node.wazoReverse;
1672
1655
 
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;
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
+ };
1676
1669
  }
1677
- }
1678
1670
 
1679
- //
1680
- /* eslint-disable no-param-reassign */
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);
1681
1676
 
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];
1691
- }
1692
- });
1693
- };
1677
+ return plain.column_values.filter((e, index) => numberColumns.some(i => i === index) && e !== null);
1678
+ }
1694
1679
 
1695
- /* */
1680
+ static parse(plain , columns ) {
1681
+ const numbers = Contact.fetchNumbers(plain, columns);
1682
+ const email = plain.column_values[columns.indexOf('email')];
1696
1683
 
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
- );
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
+ });
1707
1702
  }
1708
1703
 
1709
- static fromText(response , status ) {
1710
- return new BadResponse(response, status);
1704
+ static parseManyPersonal(results ) {
1705
+ return results.map(r => Contact.parsePersonal(r));
1711
1706
  }
1712
1707
 
1713
-
1714
-
1715
-
1716
-
1717
-
1718
-
1719
-
1720
- constructor(
1721
- message ,
1722
- status ,
1723
- timestamp = null,
1724
- errorId = null,
1725
- details = null,
1726
- error = null,
1727
- ) {
1728
- super(message);
1729
-
1730
- this.timestamp = timestamp;
1731
- this.status = status;
1732
- this.errorId = errorId;
1733
- this.details = details;
1734
- this.error = error;
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
+ });
1735
1725
  }
1736
- }
1737
1726
 
1738
- /* */
1727
+ static parseMobile(plain ) {
1728
+ let address = '';
1729
+ if (plain.postalAddresses.length) {
1730
+ const postalAddress = plain.postalAddresses[0];
1739
1731
 
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
- );
1749
- }
1732
+ address = `${postalAddress.street} ${postalAddress.city} ${postalAddress.postCode} ${postalAddress.country}`;
1733
+ }
1750
1734
 
1751
- static fromText(response , status ) {
1752
- return new ServerError(response, status);
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
+ });
1753
1749
  }
1754
- }
1755
-
1756
- /* */
1757
- /* global navigator */
1758
- const isMobile = () => typeof navigator !== 'undefined' && navigator.product === 'ReactNative';
1759
1750
 
1760
- /* global window */
1751
+ static parseManyOffice365(response , source ) {
1752
+ return response.map(r => Contact.parseOffice365(r, source));
1753
+ }
1761
1754
 
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=';
1755
+ static parseOffice365(single , source ) {
1756
+ const emails = [];
1757
+ const numbers = [];
1771
1758
 
1772
- const MAX_REMOTE_RETRY = 100;
1759
+ if (single.emailAddresses) {
1760
+ single.emailAddresses.map(email => emails.push({ email: email.address }));
1761
+ }
1773
1762
 
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;
1763
+ if (single.businessPhones) {
1764
+ single.businessPhones.map(phone => numbers.push({ label: 'business', number: phone }));
1765
+ }
1781
1766
 
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]);
1789
- }
1767
+ if (single.mobilePhone) {
1768
+ numbers.push({ label: 'mobile', number: single.mobilePhone });
1769
+ }
1790
1770
 
1791
- return instance;
1792
- };
1771
+ if (single.homePhones) {
1772
+ single.homePhones.map(phone => numbers.push({ label: 'home', number: phone }));
1773
+ }
1793
1774
 
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
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
+ });
1800
1784
  }
1801
1785
 
1802
- return result;
1803
- };
1786
+ static parseManyGoogle(response , source ) {
1787
+ return response.map(r => Contact.parseGoogle(r, source));
1788
+ }
1804
1789
 
1805
- class IssueReporter {
1806
-
1807
-
1808
-
1809
-
1810
-
1790
+ static parseGoogle(single , source ) {
1791
+ const emails = [];
1792
+ const numbers = [];
1811
1793
 
1812
-
1813
-
1814
-
1794
+ if (single.emails) {
1795
+ single.emails.forEach(email =>
1796
+ (typeof email === 'object' ? { email: email.address, label: email.label } : { email }));
1797
+ }
1815
1798
 
1816
- constructor() {
1817
- addLevelsTo(this);
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
+ }
1818
1805
 
1819
- this.oldConsoleMethods = null;
1820
- this.enabled = false;
1821
- this.remoteClientConfiguration = null;
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
+ });
1822
1815
  }
1823
1816
 
1824
- init() {
1825
- this._catchConsole();
1817
+ static parseManyWazo(response , source ) {
1818
+ return response.map(r => Contact.parseWazo(r, source));
1826
1819
  }
1827
1820
 
1828
- configureRemoteClient(configuration = { tag: 'wazo-sdk', host: null, port: null, level: null, extra: {} }) {
1829
- this.remoteClientConfiguration = configuration;
1830
- }
1821
+ static parseWazo(single , source ) {
1822
+ const emails = [];
1823
+ const numbers = [];
1831
1824
 
1832
- enable() {
1833
- if (!this.oldConsoleMethods) {
1834
- this.init();
1825
+ if (single.email) {
1826
+ emails.push({ label: 'email', email: single.email });
1835
1827
  }
1836
1828
 
1837
- this.enabled = true;
1829
+ if (single.exten) {
1830
+ numbers.push({ label: 'exten', number: single.exten });
1831
+ }
1832
+
1833
+ if (single.mobile_phone_number) {
1834
+ numbers.push({ label: 'mobile', number: single.mobile_phone_number });
1835
+ }
1836
+
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
+ });
1838
1847
  }
1839
1848
 
1840
- disable() {
1841
- this.enabled = false;
1849
+ static parseManyConference(response , source ) {
1850
+ return response.map(r => Contact.parseConference(r, source));
1842
1851
  }
1843
1852
 
1844
- loggerFor(category ) {
1845
- const logger = (level , ...args ) => {
1846
- this.log.apply(this, [level, this._makeCategory(category), ...args]);
1847
- };
1853
+ static parseConference(single , source ) {
1854
+ const numbers = [];
1855
+ let firstNumber = '';
1848
1856
 
1849
- return addLevelsTo(logger, true);
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
+ }
1861
+
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
+ });
1850
1870
  }
1851
1871
 
1852
- removeSlashes(str ) {
1853
- return str.replace(/"/g, "'").replace(/\\/g, '');
1872
+ static newFrom(contact ) {
1873
+ return newFrom(contact, Contact);
1854
1874
  }
1855
1875
 
1856
- log(level , ...args ) {
1857
- if (!this.enabled) {
1858
- return;
1859
- }
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 || [];
1860
1936
 
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
- }
1937
+ // Useful to compare instead of instanceof with minified code
1938
+ this.type = 'Contact';
1939
+ }
1869
1940
 
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
- }
1941
+ setFavorite(value ) {
1942
+ this.favorited = value;
1885
1943
 
1886
- const date = moment().format('YYYY-MM-DD HH:mm:ss.SSS');
1887
- const message = args.join(', ');
1888
- let consoleMessage = message;
1944
+ return this;
1945
+ }
1889
1946
 
1890
- if (Object.keys(extra).length) {
1891
- const parsedExtra = safeStringify(extra);
1892
- consoleMessage = `${consoleMessage} (${parsedExtra})`;
1893
- }
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;
1894
1950
 
1895
- if (category) {
1896
- consoleMessage = `[${category}] ${consoleMessage}`;
1897
- }
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;
1898
1954
 
1899
- // Log the message in the console anyway (but don't console.error on mobile)
1900
- const consoleLevel = isMobile() && level === 'error' ? WARN : level;
1955
+ return !!other && hasSameId && hasSameBackend && hasSameSource;
1956
+ }
1901
1957
 
1902
- // eslint-disable-next-line
1903
- const oldMethod = this.oldConsoleMethods[consoleLevel] || this.oldConsoleMethods.log;
1904
- oldMethod.apply(oldMethod, [date, consoleMessage]);
1958
+ hasId(id ) {
1959
+ return this.uuid === id;
1960
+ }
1905
1961
 
1906
- this._sendToRemoteLogger(level, { date, message, category, ...extra });
1962
+ hasNumber(number ) {
1963
+ return this.number === number;
1907
1964
  }
1908
1965
 
1909
- logRequest(url , options , response , start ) {
1910
- if (!this.enabled) {
1911
- return;
1912
- }
1913
- const { status } = response;
1914
- const duration = new Date() - start;
1966
+ hasEndpointId(endpointId ) {
1967
+ return this.endpointId === endpointId;
1968
+ }
1915
1969
 
1916
- let level = TRACE;
1917
- if (status >= 400 && status < 500) {
1918
- level = WARN;
1919
- } else if (status >= 500) {
1920
- level = ERROR;
1921
- }
1970
+ isAvailable() {
1971
+ return this.state === STATE.AVAILABLE;
1972
+ }
1922
1973
 
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
- });
1974
+ isAway() {
1975
+ return this.state === STATE.AWAY;
1976
+ }
1977
+
1978
+ isUnavailable() {
1979
+ return this.state === STATE.UNAVAILABLE;
1930
1980
  }
1931
1981
 
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 [];
1982
+ isInvisible() {
1983
+ return this.state === STATE.INVISIBLE;
1936
1984
  }
1937
1985
 
1938
- getParsedLogs() {
1939
- console.warn('IssueReporter\'s logs aren\'t stored anymore. Please use fluentd to store them');
1940
- return [];
1986
+ isInCall() {
1987
+ return this.lineState === LINE_STATE.TALKING || this.lineState === LINE_STATE.HOLDING;
1941
1988
  }
1942
1989
 
1943
- getReport() {
1944
- return this.getParsedLogs().join('\r\n');
1990
+ isRinging() {
1991
+ return this.ringing;
1945
1992
  }
1946
1993
 
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
- });
1994
+ isInUseOrRinging() {
1995
+ return this.lineState === LINE_STATE.TALKING || this.lineState === LINE_STATE.RINGING;
1963
1996
  }
1964
1997
 
1965
- _sendToRemoteLogger(level , payload , retry = 0) {
1966
- if (!this.remoteClientConfiguration || retry >= MAX_REMOTE_RETRY) {
1967
- return;
1968
- }
1998
+ isProgressing() {
1999
+ return this.lineState === LINE_STATE.PROGRESSING;
2000
+ }
1969
2001
 
1970
- const { tag, host, port, extra, level: minLevel } = this.remoteClientConfiguration;
1971
- if (!minLevel || this._isLevelAbove(minLevel, level)) {
1972
- return;
2002
+ merge(old ) {
2003
+ Object.keys(old).filter(key => key !== 'lineState').forEach(key => {
2004
+ // $FlowFixMe
2005
+ this[key] = old[key] || this[key];
2006
+ });
2007
+
2008
+ if (old.lineState && !this.lineState) {
2009
+ this.lineState = old.lineState;
1973
2010
  }
1974
2011
 
1975
- const isSecure = +port === 443;
1976
- const url = `http${isSecure ? 's' : ''}://${host}${isSecure ? '' : `:${port}`}/${tag}`;
2012
+ return this;
2013
+ }
1977
2014
 
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
- });
2015
+ isIntern() {
2016
+ return !!this.uuid;
1996
2017
  }
1997
2018
 
1998
- _isLevelAbove(level1 , level2 ) {
1999
- return LOG_LEVELS.indexOf(level1) > LOG_LEVELS.indexOf(level2);
2019
+ isCallable(session ) {
2020
+ return !!this.number && !!session && !session.is(this);
2000
2021
  }
2001
2022
 
2002
- _makeCategory(category ) {
2003
- return `${CATEGORY_PREFIX}${category}`;
2023
+ isFromMobile() {
2024
+ return this.source === SOURCE_MOBILE;
2004
2025
  }
2005
- }
2006
2026
 
2007
- var IssueReporter$1 = new IssueReporter();
2027
+ isFavorite() {
2028
+ return this.favorited;
2029
+ }
2008
2030
 
2009
- /* */
2031
+ separateName() {
2032
+ if (!this.name) {
2033
+ return {
2034
+ firstName: '',
2035
+ lastName: '',
2036
+ };
2037
+ }
2038
+ const names = this.name.split(/\s+/);
2039
+ const firstName = names[0];
2040
+ const lastName = names.slice(1).join(' ');
2010
2041
 
2011
-
2012
-
2013
-
2014
-
2015
-
2016
-
2017
-
2018
-
2042
+ return {
2043
+ firstName,
2044
+ lastName,
2045
+ };
2046
+ }
2047
+ }
2019
2048
 
2020
- const methods = ['head', 'get', 'post', 'put', 'delete'];
2049
+ // Can't use `semver` package as Wazo version aren't in semver format
2021
2050
 
2022
- const logger$1 = IssueReporter$1 ? IssueReporter$1.loggerFor('api') : console;
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);
2023
2058
 
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;
2059
+ for (i = 0; i < l; i++) {
2060
+ diff = parseInt(segmentsA[i], 10) - parseInt(segmentsB[i], 10);
2061
+ if (diff) {
2062
+ return diff;
2063
+ }
2029
2064
  }
2065
+ return segmentsA.length - segmentsB.length;
2066
+ };
2030
2067
 
2031
- if (isMobile()) {
2032
- // React native
2033
- return fetch;
2034
- }
2068
+ //
2035
2069
 
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
- };
2070
+ const swarmKey = KEYUTIL.getKey(swarmPublicKey);
2071
+ const MINIMUM_WAZO_ENGINE_VERSION_FOR_DEFAULT_CONTEXT = '19.08';
2047
2072
 
2048
- class ApiRequester {
2049
2073
 
2074
+
2050
2075
 
2076
+
2077
+
2078
+
2079
+
2080
+
2081
+
2082
+
2051
2083
 
2084
+
2085
+
2052
2086
 
2053
-
2087
+
2088
+
2089
+
2090
+
2091
+
2092
+
2054
2093
 
2055
-
2056
-
2094
+
2095
+
2096
+
2097
+
2098
+
2099
+
2100
+
2101
+
2102
+
2103
+
2104
+
2057
2105
 
2106
+
2107
+
2058
2108
 
2059
2109
 
2110
+
2111
+
2060
2112
 
2061
-
2062
-
2063
-
2064
- // eslint-disable-next-line
2065
- static successResponseParser(response , isJson ) {
2066
- return response.status === 204 || response.status === 201 || response.status === 200;
2067
- }
2068
-
2069
- static defaultParser(response ) {
2070
- return response.json().then((data ) => data);
2071
- }
2113
+
2114
+
2115
+
2116
+
2117
+
2118
+
2119
+
2072
2120
 
2073
- static getQueryString(obj ) {
2074
- return Object.keys(obj)
2075
- .filter(key => obj[key])
2076
- .map(key => `${key}=${encodeURIComponent(obj[key])}`)
2077
- .join('&');
2078
- }
2121
+ class Session {
2122
+
2123
+
2124
+
2125
+
2126
+
2127
+
2128
+
2129
+
2130
+
2131
+
2132
+
2079
2133
 
2080
- static base64Encode(str ) {
2081
- return typeof btoa !== 'undefined' ? btoa(str) : Base64.encode(str);
2082
- }
2134
+ static parse(plain ) {
2135
+ const token = plain.data.metadata ? plain.data.metadata.jwt : null;
2136
+ let authorizations = [];
2083
2137
 
2084
- // @see https://github.com/facebook/flow/issues/183#issuecomment-358607052
2085
- constructor({ server, refreshTokenCallback, clientId, agent = null, token = null, fetchOptions } ) {
2086
- this.server = server;
2087
- this.agent = agent;
2088
- this.clientId = clientId;
2089
- this.refreshTokenCallback = refreshTokenCallback;
2090
- this.refreshTokenPromise = null;
2091
- this.fetchOptions = fetchOptions || {};
2138
+ // Add authorizations from JWT
2092
2139
  if (token) {
2093
- 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
+ }
2094
2145
  }
2095
2146
 
2096
- methods.forEach(method => {
2097
- // $FlowFixMe
2098
- ApiRequester.prototype[method] = function sugar(...args) {
2099
- // Add method in arguments passed to `call`
2100
- args.splice(1, 0, method);
2101
-
2102
- return this.call.call(this, ...args);
2103
- };
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,
2104
2157
  });
2105
2158
  }
2106
2159
 
2107
- setTenant(tenant ) {
2108
- this.tenant = tenant;
2160
+ static newFrom(profile ) {
2161
+ return newFrom(profile, Session);
2109
2162
  }
2110
2163
 
2111
- setToken(token ) {
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
+ } = {}) {
2112
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;
2113
2188
  }
2114
2189
 
2115
- setFetchOptions(options ) {
2116
- this.fetchOptions = options;
2190
+ hasExpired(date = new Date()) {
2191
+ return date >= this.expiresAt;
2117
2192
  }
2118
2193
 
2119
- async call(
2120
- path ,
2121
- method = 'get',
2122
- body = null,
2123
- headers = null,
2124
- parse = ApiRequester.defaultParser,
2125
- firstCall = true,
2126
- ) {
2127
- const url = this.computeUrl(method, path, body);
2128
- const newHeaders = this.getHeaders(headers);
2129
- let newBody = method === 'get' ? null : body;
2130
- if (newBody && newHeaders['Content-Type'] === 'application/json') {
2131
- newBody = JSON.stringify(newBody);
2132
- }
2133
- const isHead = method === 'head';
2134
- const hasEmptyResponse = method === 'delete' || isHead;
2135
- const newParse = hasEmptyResponse ? ApiRequester.successResponseParser : parse;
2136
-
2137
- const fetchOptions = { ...this.fetchOptions || {} };
2138
- const extraHeaders = fetchOptions.headers || {};
2139
- delete fetchOptions.headers;
2194
+ is(contact ) {
2195
+ return Boolean(contact) && this.uuid === contact.uuid;
2196
+ }
2140
2197
 
2141
- const options = {
2142
- method,
2143
- body: newBody,
2144
- headers: { ...this.getHeaders(headers), ...extraHeaders },
2145
- agent: this.agent,
2146
- ...fetchOptions,
2147
- };
2198
+ using(profile ) {
2199
+ this.profile = profile;
2148
2200
 
2149
- if (this.refreshTokenPromise) {
2150
- logger$1.info('A token is already refreshing, waiting ...', { url });
2151
- await this.refreshTokenPromise;
2152
- }
2201
+ return this;
2202
+ }
2153
2203
 
2154
- const start = new Date();
2204
+ hasAuthorizations() {
2205
+ return this.authorizations && !!this.authorizations.length;
2206
+ }
2155
2207
 
2156
- return realFetch()(url, options).then(response => {
2157
- const contentType = response.headers.get('content-type') || '';
2158
- const isJson = contentType.indexOf('application/json') !== -1;
2208
+ displayName() {
2209
+ return this.profile ? `${this.profile.firstName} ${this.profile.lastName}` : '';
2210
+ }
2159
2211
 
2160
- IssueReporter$1.logRequest(url, options, response, start);
2212
+ hasAccessToVoicemail() {
2213
+ if (!this.profile) {
2214
+ return false;
2215
+ }
2216
+ return !!this.profile.voicemail;
2217
+ }
2161
2218
 
2162
- // Throw an error only if status >= 400
2163
- if ((isHead && response.status >= 500) || (!isHead && response.status >= 400)) {
2164
- const promise = isJson ? response.json() : response.text();
2165
- const exceptionClass = response.status >= 500 ? ServerError : BadResponse;
2219
+ primaryLine() {
2220
+ return this.profile && this.profile.lines.length > 0 ? this.profile.lines[0] : null;
2221
+ }
2166
2222
 
2167
- return promise.then(async (err ) => {
2168
- // Check if the token is still valid
2169
- if (firstCall && this._checkTokenExpired(response, err)) {
2170
- logger$1.warn('token expired', { error: err.reason });
2171
- // Replay the call after refreshing the token
2172
- return this._replayWithNewToken(err, path, method, body, headers, parse);
2173
- }
2223
+ primarySipLine() {
2224
+ return this.profile && this.profile.sipLines.length > 0 ? this.profile.sipLines[0] : null;
2225
+ }
2174
2226
 
2175
- const error = typeof err === 'string'
2176
- ? exceptionClass.fromText(err, response.status)
2177
- : exceptionClass.fromResponse(err, response.status);
2227
+ primaryWebRtcLine() {
2228
+ return this.profile && (this.profile.sipLines || []).find(sipLine => sipLine && sipLine.isWebRtc());
2229
+ }
2178
2230
 
2179
- logger$1.error('API error', error);
2231
+ primaryCtiLine() {
2232
+ return this.profile && (this.profile.sipLines || []).find(sipLine => sipLine && !sipLine.isWebRtc());
2233
+ }
2180
2234
 
2181
- throw error;
2182
- });
2235
+ primaryContext() {
2236
+ if (this.engineVersion) {
2237
+ if (this.hasEngineVersionGte(MINIMUM_WAZO_ENGINE_VERSION_FOR_DEFAULT_CONTEXT)) {
2238
+ return 'default';
2183
2239
  }
2240
+ }
2184
2241
 
2185
- return newParse(response, isJson);
2186
- }).catch(error => {
2187
- logger$1.error('Fetch failed', { url, options, message: error.message, stack: error.stack });
2188
-
2189
- throw error;
2190
- });
2191
- }
2192
-
2193
- _checkTokenExpired(response , err ) {
2194
- // Special case when authenticating form a token: we got a 404
2195
- const isTokenNotFound = response.status === 404 && this._isTokenNotFound(err);
2242
+ const line = this.primaryLine();
2196
2243
 
2197
- return response.status === 401 || isTokenNotFound;
2244
+ return line && line.extensions.length > 0 ? line.extensions[0].context : 'default';
2198
2245
  }
2199
2246
 
2200
- _isTokenNotFound(err ) {
2201
- return err && err.reason && err.reason[0] === 'No such token';
2247
+ hasEngineVersionGte(version ) {
2248
+ return this.engineVersion && compareVersions(String(this.engineVersion), String(version)) >= 0;
2202
2249
  }
2203
2250
 
2204
- _replayWithNewToken(
2205
- err ,
2206
- path ,
2207
- method ,
2208
- body = null,
2209
- headers = null,
2210
- parse ,
2211
- ) {
2212
- const isTokenNotFound = this._isTokenNotFound(err);
2213
- let newPath = path;
2214
- logger$1.info('refreshing token', { inProgress: !!this.refreshTokenPromise });
2215
-
2216
- this.refreshTokenPromise = this.refreshTokenPromise || this.refreshTokenCallback();
2251
+ primaryNumber() {
2252
+ const line = this.primaryLine();
2217
2253
 
2218
- return this.refreshTokenPromise.then(() => {
2219
- this.refreshTokenPromise = null;
2220
- logger$1.info('token refreshed', { isTokenNotFound });
2221
- if (isTokenNotFound) {
2222
- const pathParts = path.split('/');
2223
- pathParts.pop();
2224
- pathParts.push(this.token);
2225
- newPath = pathParts.join('/');
2226
- }
2254
+ return line && line.extensions.length ? line.extensions[0].exten : null;
2255
+ }
2227
2256
 
2228
- return this.call(newPath, method, body, headers, parse, false);
2229
- }).catch((e) => {
2230
- this.refreshTokenPromise = null;
2231
- throw e;
2232
- });
2257
+ allLines() {
2258
+ return this.profile ? this.profile.lines || [] : [];
2233
2259
  }
2234
2260
 
2235
- getHeaders(header ) {
2236
- if (header instanceof Object) {
2237
- return header;
2261
+ allNumbers() {
2262
+ const extensions = this.allLines().map(line => line.extensions.map(extension => extension.exten));
2263
+ if (!extensions.length) {
2264
+ return [];
2238
2265
  }
2239
2266
 
2240
- return {
2241
- 'X-Auth-Token': this.token,
2242
- ...(this.tenant ? { 'Wazo-Tenant': this.tenant } : null),
2243
- Accept: 'application/json',
2244
- 'Content-Type': 'application/json',
2245
- };
2267
+ return extensions.reduce((a, b) => a.concat(b));
2246
2268
  }
2247
2269
 
2248
- computeUrl(method , path , body ) {
2249
- const url = `${this.baseUrl}/${path}`;
2250
-
2251
- return method === 'get' && body && Object.keys(body).length ? `${url}?${ApiRequester.getQueryString(body)}` : url;
2270
+ hasExtension(extension ) {
2271
+ return this.allNumbers().some(number => number === extension);
2252
2272
  }
2253
2273
 
2254
- get baseUrl() {
2255
- 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;
2256
2277
  }
2257
2278
  }
2258
2279
 
@@ -2988,17 +3009,66 @@ class Voicemail {
2988
3009
 
2989
3010
  //
2990
3011
 
3012
+
3013
+
3014
+
3015
+
3016
+
3017
+
3018
+
3019
+
3020
+
3021
+
3022
+
3023
+
3024
+
3025
+
3026
+
3027
+
3028
+ class Recording {
3029
+
3030
+
3031
+
3032
+
3033
+
3034
+
3035
+ static parseMany(recordings ) {
3036
+ return recordings.map(item => Recording.parse(item));
3037
+ }
3038
+
3039
+ static parse(plain ) {
3040
+ return new Recording({
3041
+ deleted: plain.deleted,
3042
+ fileName: plain.filename,
3043
+ end: plain.end_time ? moment(plain.end_time).toDate() : null,
3044
+ start: moment(plain.start_time).toDate(),
3045
+ uuid: plain.uuid,
3046
+ });
3047
+ }
3048
+
3049
+ constructor({ deleted, end, fileName, start, uuid } ) {
3050
+ this.deleted = deleted;
3051
+ this.end = end;
3052
+ this.fileName = fileName;
3053
+ this.start = start;
3054
+ this.uuid = uuid;
3055
+ }
3056
+ }
3057
+
3058
+ //
3059
+
2991
3060
 
2992
-
3061
+
2993
3062
 
2994
3063
 
2995
3064
 
2996
3065
 
2997
3066
 
2998
-
3067
+
2999
3068
 
3000
3069
 
3001
3070
 
3071
+
3002
3072
 
3003
3073
 
3004
3074
 
@@ -3011,7 +3081,7 @@ class Voicemail {
3011
3081
 
3012
3082
 
3013
3083
 
3014
-
3084
+
3015
3085
 
3016
3086
 
3017
3087
 
@@ -3026,13 +3096,14 @@ class Voicemail {
3026
3096
 
3027
3097
 
3028
3098
 
3029
-
3099
+
3100
+
3030
3101
 
3031
3102
 
3032
3103
  class CallLog {
3033
3104
 
3034
3105
 
3035
-
3106
+
3036
3107
 
3037
3108
 
3038
3109
 
@@ -3049,7 +3120,7 @@ class CallLog {
3049
3120
 
3050
3121
 
3051
3122
 
3052
-
3123
+
3053
3124
 
3054
3125
  static merge(current , toMerge ) {
3055
3126
  const onlyUnique = (value, index, self) => self.indexOf(value) === index;
@@ -3066,7 +3137,7 @@ class CallLog {
3066
3137
 
3067
3138
  static parse(plain ) {
3068
3139
  return new CallLog({
3069
- answer: moment(plain.answer).toDate(),
3140
+ answer: plain.answer ? moment(plain.answer).toDate() : null,
3070
3141
  answered: plain.answered,
3071
3142
  callDirection: plain.call_direction,
3072
3143
  destination: {
@@ -3080,13 +3151,14 @@ class CallLog {
3080
3151
  id: plain.id,
3081
3152
  duration: (plain.duration || 0) * 1000, // duration is in seconds
3082
3153
  start: moment(plain.start).toDate(),
3083
- end: moment(plain.end).toDate(),
3154
+ end: plain.end ? moment(plain.end).toDate() : null,
3155
+ recordings: Recording.parseMany(plain.recordings),
3084
3156
  });
3085
3157
  }
3086
3158
 
3087
3159
  static parseNew(plain , session ) {
3088
3160
  return new CallLog({
3089
- answer: moment(plain.answer).toDate(),
3161
+ answer: plain.answer ? moment(plain.answer).toDate() : null,
3090
3162
  answered: plain.answered,
3091
3163
  callDirection: plain.call_direction,
3092
3164
  destination: {
@@ -3100,7 +3172,8 @@ class CallLog {
3100
3172
  id: plain.id,
3101
3173
  duration: (plain.duration || 0) * 1000, // duration is in seconds
3102
3174
  start: moment(plain.start).toDate(),
3103
- end: moment(plain.end).toDate(),
3175
+ end: plain.end ? moment(plain.end).toDate() : null,
3176
+ recordings: Recording.parseMany(plain.recordings),
3104
3177
  // @TODO: FIXME add verification declined vs missed call
3105
3178
  newMissedCall: session
3106
3179
  && session.hasExtension(plain.requested_extension || plain.destination_extension) && !plain.answered,
@@ -4321,6 +4394,10 @@ class ApiClient {
4321
4394
  this.fetchOptions = fetchOptions;
4322
4395
  this.client.setFetchOptions(fetchOptions);
4323
4396
  }
4397
+
4398
+ disableErrorLogging() {
4399
+ this.client.disableErrorLogging();
4400
+ }
4324
4401
  }
4325
4402
 
4326
4403
  /* eslint-disable dot-notation */