@wazo/sdk 0.37.20 → 0.37.24

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 (147) hide show
  1. package/dist/wazo-sdk.js +6 -6
  2. package/esm/__tests__/api-client.test.js +91 -21
  3. package/esm/__tests__/web-rtc-client.test.js +214 -165
  4. package/esm/api/agentd.js +25 -2
  5. package/esm/api/amid.js +26 -3
  6. package/esm/api/application.js +25 -2
  7. package/esm/api/auth.js +25 -2
  8. package/esm/api/call-logd.js +34 -4
  9. package/esm/api/calld.js +35 -2
  10. package/esm/api/chatd.js +32 -2
  11. package/esm/api/confd.js +56 -9
  12. package/esm/api/ctid-ng.js +38 -5
  13. package/esm/api/dird.js +29 -3
  14. package/esm/api/webhookd.js +26 -3
  15. package/esm/api-client.js +91 -21
  16. package/esm/checker/Checker.js +241 -190
  17. package/esm/checker/checks/aor.js +91 -21
  18. package/esm/checker/checks/api.js +91 -21
  19. package/esm/checker/checks/index.js +231 -180
  20. package/esm/checker/checks/wazo-websocket.js +42 -17
  21. package/esm/checker/checks/webrtc-transport.js +214 -165
  22. package/esm/checker/checks/webrtc.js +214 -165
  23. package/esm/domain/AdHocAPIConference.js +91 -21
  24. package/esm/domain/Call.js +3 -0
  25. package/esm/domain/CallLog.js +8 -1
  26. package/esm/domain/ChatMessage.js +4 -0
  27. package/esm/domain/ChatRoom.js +3 -0
  28. package/esm/domain/Contact.js +4 -1
  29. package/esm/domain/Features.js +91 -21
  30. package/esm/domain/Meeting.js +28 -5
  31. package/esm/domain/Phone/CTIPhone.js +109 -35
  32. package/esm/domain/Phone/WebRTCPhone.js +159 -102
  33. package/esm/domain/Recording.js +3 -0
  34. package/esm/domain/Voicemail.js +3 -0
  35. package/esm/domain/__tests__/Call.test.js +3 -0
  36. package/esm/domain/__tests__/CallLog.test.js +8 -1
  37. package/esm/domain/__tests__/Contact.test.js +4 -1
  38. package/esm/domain/__tests__/Meeting.test.js +144 -0
  39. package/esm/domain/__tests__/Voicemail.test.js +3 -0
  40. package/esm/index.js +509 -414
  41. package/esm/lib/WazoSessionDescriptionHandler.js +26 -41
  42. package/esm/service/CallApi.js +91 -21
  43. package/esm/service/IssueReporter.js +26 -3
  44. package/esm/service/__tests__/IssueReporter.test.js +26 -3
  45. package/esm/service/getApiClient.js +91 -21
  46. package/esm/simple/Auth.js +499 -404
  47. package/esm/simple/Configuration.js +499 -404
  48. package/esm/simple/Directory.js +91 -21
  49. package/esm/simple/Phone.js +499 -404
  50. package/esm/simple/Websocket.js +45 -19
  51. package/esm/simple/index.js +503 -408
  52. package/esm/simple/room/LocalParticipant.js +503 -408
  53. package/esm/simple/room/Participant.js +503 -408
  54. package/esm/simple/room/RemoteParticipant.js +503 -408
  55. package/esm/simple/room/Room.js +504 -409
  56. package/esm/simple/room/SipRoom.js +503 -408
  57. package/esm/simple/room/Stream.js +503 -408
  58. package/esm/simple/room/__tests__/RemoteParticipant.test.js +503 -408
  59. package/esm/simple/utils.js +503 -408
  60. package/esm/types/WebSocketMessage.js +1 -1
  61. package/esm/utils/__tests__/api-requester.test.js +25 -2
  62. package/esm/utils/api-requester.js +25 -2
  63. package/esm/web-rtc-client.js +214 -165
  64. package/esm/websocket-client.js +43 -18
  65. package/lib/__tests__/api-client.test.js +91 -21
  66. package/lib/__tests__/web-rtc-client.test.js +214 -165
  67. package/lib/api/agentd.js +25 -2
  68. package/lib/api/amid.js +26 -3
  69. package/lib/api/application.js +25 -2
  70. package/lib/api/auth.js +25 -2
  71. package/lib/api/call-logd.js +34 -4
  72. package/lib/api/calld.js +35 -2
  73. package/lib/api/chatd.js +32 -2
  74. package/lib/api/confd.js +56 -9
  75. package/lib/api/ctid-ng.js +38 -5
  76. package/lib/api/dird.js +29 -3
  77. package/lib/api/webhookd.js +26 -3
  78. package/lib/api-client.js +91 -21
  79. package/lib/checker/Checker.js +241 -190
  80. package/lib/checker/checks/aor.js +91 -21
  81. package/lib/checker/checks/api.js +91 -21
  82. package/lib/checker/checks/index.js +231 -180
  83. package/lib/checker/checks/wazo-websocket.js +42 -17
  84. package/lib/checker/checks/webrtc-transport.js +214 -165
  85. package/lib/checker/checks/webrtc.js +214 -165
  86. package/lib/domain/AdHocAPIConference.js +91 -21
  87. package/lib/domain/Call.js +3 -0
  88. package/lib/domain/CallLog.js +8 -1
  89. package/lib/domain/ChatMessage.js +4 -0
  90. package/lib/domain/ChatRoom.js +3 -0
  91. package/lib/domain/Contact.js +4 -1
  92. package/lib/domain/Features.js +91 -21
  93. package/lib/domain/Meeting.js +32 -5
  94. package/lib/domain/Phone/CTIPhone.js +109 -35
  95. package/lib/domain/Phone/WebRTCPhone.js +159 -102
  96. package/lib/domain/Recording.js +3 -0
  97. package/lib/domain/Voicemail.js +3 -0
  98. package/lib/domain/__tests__/Call.test.js +3 -0
  99. package/lib/domain/__tests__/CallLog.test.js +8 -1
  100. package/lib/domain/__tests__/Contact.test.js +4 -1
  101. package/lib/domain/__tests__/Meeting.test.js +150 -0
  102. package/lib/domain/__tests__/Voicemail.test.js +3 -0
  103. package/lib/index.js +509 -414
  104. package/lib/lib/WazoSessionDescriptionHandler.js +26 -41
  105. package/lib/service/CallApi.js +91 -21
  106. package/lib/service/IssueReporter.js +26 -3
  107. package/lib/service/__tests__/IssueReporter.test.js +26 -3
  108. package/lib/service/getApiClient.js +91 -21
  109. package/lib/simple/Auth.js +499 -404
  110. package/lib/simple/Configuration.js +499 -404
  111. package/lib/simple/Directory.js +91 -21
  112. package/lib/simple/Phone.js +499 -404
  113. package/lib/simple/Websocket.js +45 -19
  114. package/lib/simple/index.js +503 -408
  115. package/lib/simple/room/LocalParticipant.js +503 -408
  116. package/lib/simple/room/Participant.js +503 -408
  117. package/lib/simple/room/RemoteParticipant.js +503 -408
  118. package/lib/simple/room/Room.js +504 -409
  119. package/lib/simple/room/SipRoom.js +503 -408
  120. package/lib/simple/room/Stream.js +503 -408
  121. package/lib/simple/room/__tests__/RemoteParticipant.test.js +503 -408
  122. package/lib/simple/utils.js +503 -408
  123. package/lib/types/WebSocketMessage.js +2 -0
  124. package/lib/utils/__tests__/api-requester.test.js +25 -2
  125. package/lib/utils/api-requester.js +25 -2
  126. package/lib/web-rtc-client.js +214 -165
  127. package/lib/websocket-client.js +43 -17
  128. package/package.json +1 -1
  129. package/src/api/confd.js +5 -2
  130. package/src/domain/Call.js +3 -0
  131. package/src/domain/CallLog.js +4 -0
  132. package/src/domain/ChatMessage.js +4 -0
  133. package/src/domain/ChatRoom.js +3 -0
  134. package/src/domain/Contact.js +3 -0
  135. package/src/domain/Meeting.js +28 -5
  136. package/src/domain/Phone/CTIPhone.js +4 -0
  137. package/src/domain/Phone/Phone.js +2 -0
  138. package/src/domain/Phone/WebRTCPhone.js +49 -15
  139. package/src/domain/Recording.js +3 -0
  140. package/src/domain/Voicemail.js +3 -0
  141. package/src/domain/__tests__/Meeting.test.js +50 -0
  142. package/src/lib/WazoSessionDescriptionHandler.js +1 -35
  143. package/src/service/IssueReporter.js +25 -2
  144. package/src/simple/Phone.js +5 -0
  145. package/src/types/WebSocketMessage.js +5 -1
  146. package/src/web-rtc-client.js +41 -24
  147. package/src/websocket-client.js +2 -0
@@ -76,6 +76,9 @@ class Call {
76
76
 
77
77
 
78
78
  static parseMany(plain ) {
79
+ if (!plain) {
80
+ return [];
81
+ }
79
82
  return plain.map((plainCall ) => Call.parse(plainCall));
80
83
  }
81
84
 
@@ -347,6 +350,7 @@ class IssueReporter {
347
350
 
348
351
 
349
352
 
353
+
350
354
 
351
355
  constructor() {
352
356
  addLevelsTo(this);
@@ -358,12 +362,17 @@ class IssueReporter {
358
362
  this.bufferTimeout = null;
359
363
  this._boundProcessBuffer = this._processBuffer.bind(this);
360
364
  this._boundParseLoggerBody = this._parseLoggerBody.bind(this);
365
+ this._callback = null;
361
366
  }
362
367
 
363
368
  init() {
364
369
  this._catchConsole();
365
370
  }
366
371
 
372
+ setCallback(cb ) {
373
+ this._callback = cb;
374
+ }
375
+
367
376
  configureRemoteClient(configuration = { tag: 'wazo-sdk', host: null, port: null, level: null, extra: {} }) {
368
377
  this.remoteClientConfiguration = configuration;
369
378
  }
@@ -399,6 +408,7 @@ class IssueReporter {
399
408
 
400
409
  // Handle category label
401
410
  let category = null;
411
+ let skipSendToRemote = false;
402
412
  let extra = {};
403
413
  if (args[0].indexOf(CATEGORY_PREFIX) === 0) {
404
414
  category = args[0].split('=')[1];
@@ -414,6 +424,7 @@ class IssueReporter {
414
424
  errorMessage: lastArg.message,
415
425
  errorStack: lastArg.stack,
416
426
  errorType: lastArg.constructor.name,
427
+ skipSendToRemote: lastArg.skipSendToRemote,
417
428
  };
418
429
  } else {
419
430
  extra = lastArg;
@@ -422,6 +433,11 @@ class IssueReporter {
422
433
  args.splice(1, 1);
423
434
  }
424
435
 
436
+ if (extra.skipSendToRemote) {
437
+ skipSendToRemote = true;
438
+ delete extra.skipSendToRemote;
439
+ }
440
+
425
441
  const date = moment().format('YYYY-MM-DD HH:mm:ss.SSS');
426
442
  const message = args.join(', ');
427
443
  let consoleMessage = message;
@@ -442,7 +458,13 @@ class IssueReporter {
442
458
  const oldMethod = this.oldConsoleMethods[consoleLevel] || this.oldConsoleMethods.log;
443
459
  oldMethod.apply(oldMethod, [date, consoleMessage]);
444
460
 
445
- this._sendToRemoteLogger(level, { date, message, category, ...extra });
461
+ if (this._callback) {
462
+ this._callback(level, consoleMessage);
463
+ }
464
+
465
+ if (!skipSendToRemote) {
466
+ this._sendToRemoteLogger(level, { date, message, category, ...extra });
467
+ }
446
468
  }
447
469
 
448
470
  logRequest(url , options , response , start ) {
@@ -587,7 +609,11 @@ class IssueReporter {
587
609
  },
588
610
  // $FlowFixMe
589
611
  body,
590
- }).catch(() => {
612
+ }).catch((e ) => {
613
+ // $FlowFixMe
614
+ e.skipSendToRemote = true;
615
+ this.log('error', this._makeCategory('grafana'), 'Sending log to grafana, error', e);
616
+
591
617
  setTimeout(() => {
592
618
  if (isArray) {
593
619
  payload = payload.map(message => {
@@ -634,7 +660,7 @@ var IssueReporter$1 = new IssueReporter();
634
660
 
635
661
  const methods = ['head', 'get', 'post', 'put', 'delete'];
636
662
 
637
- const logger$1 = IssueReporter$1 ? IssueReporter$1.loggerFor('api') : console;
663
+ const logger = IssueReporter$1 ? IssueReporter$1.loggerFor('api') : console;
638
664
 
639
665
  // Use a function here to be able to mock it in tests
640
666
  const realFetch = () => {
@@ -773,7 +799,7 @@ class ApiRequester {
773
799
  };
774
800
 
775
801
  if (this.refreshTokenPromise) {
776
- logger$1.info('A token is already refreshing, waiting ...', { url });
802
+ logger.info('A token is already refreshing, waiting ...', { url });
777
803
  await this.refreshTokenPromise;
778
804
  }
779
805
 
@@ -793,7 +819,7 @@ class ApiRequester {
793
819
  return promise.then(async (err ) => {
794
820
  // Check if the token is still valid
795
821
  if (firstCall && this._checkTokenExpired(response, err)) {
796
- logger$1.warn('token expired', { error: err.reason });
822
+ logger.warn('token expired', { error: err.reason });
797
823
  // Replay the call after refreshing the token
798
824
  return this._replayWithNewToken(err, path, method, body, headers, parse);
799
825
  }
@@ -803,7 +829,7 @@ class ApiRequester {
803
829
  : exceptionClass.fromResponse(err, response.status);
804
830
 
805
831
  if (this.shouldLogErrors) {
806
- logger$1.error('API error', error);
832
+ logger.error('API error', error);
807
833
  }
808
834
 
809
835
  throw error;
@@ -813,7 +839,7 @@ class ApiRequester {
813
839
  return newParse(response, isJson);
814
840
  }).catch(error => {
815
841
  if (this.shouldLogErrors) {
816
- logger$1.error('Fetch failed', { url, options, message: error.message, stack: error.stack });
842
+ logger.error('Fetch failed', { url, options, message: error.message, stack: error.stack });
817
843
  }
818
844
 
819
845
  throw error;
@@ -841,13 +867,13 @@ class ApiRequester {
841
867
  ) {
842
868
  const isTokenNotFound = this._isTokenNotFound(err);
843
869
  let newPath = path;
844
- logger$1.info('refreshing token', { inProgress: !!this.refreshTokenPromise });
870
+ logger.info('refreshing token', { inProgress: !!this.refreshTokenPromise });
845
871
 
846
872
  this.refreshTokenPromise = this.refreshTokenPromise || this.refreshTokenCallback();
847
873
 
848
874
  return this.refreshTokenPromise.then(() => {
849
875
  this.refreshTokenPromise = null;
850
- logger$1.info('token refreshed', { isTokenNotFound });
876
+ logger.info('token refreshed', { isTokenNotFound });
851
877
  if (isTokenNotFound) {
852
878
  const pathParts = path.split('/');
853
879
  pathParts.pop();
@@ -1714,6 +1740,9 @@ class Contact {
1714
1740
  }
1715
1741
 
1716
1742
  static parseMany(response ) {
1743
+ if (!response || !response.results) {
1744
+ return [];
1745
+ }
1717
1746
  return response.results.map(r => Contact.parse(r, response.column_types));
1718
1747
  }
1719
1748
 
@@ -2791,8 +2820,9 @@ class ExternalApp {
2791
2820
 
2792
2821
 
2793
2822
 
2794
-
2823
+
2795
2824
 
2825
+
2796
2826
 
2797
2827
 
2798
2828
  class Meeting {
@@ -2803,9 +2833,10 @@ class Meeting {
2803
2833
 
2804
2834
 
2805
2835
 
2806
-
2836
+
2807
2837
 
2808
2838
 
2839
+
2809
2840
 
2810
2841
  static parse(plain ) {
2811
2842
  return new Meeting({
@@ -2815,24 +2846,43 @@ class Meeting {
2815
2846
  ownerUuids: plain.owner_uuids,
2816
2847
  port: plain.port,
2817
2848
  uuid: plain.uuid,
2818
- number: plain.number,
2849
+ extension: plain.exten,
2819
2850
  persistent: plain.persistent,
2851
+ creationTime: moment(plain.creation_time).toDate(),
2820
2852
  });
2821
2853
  }
2822
2854
 
2855
+ static parseMany(items ) {
2856
+ if (!items) {
2857
+ return [];
2858
+ }
2859
+ return items.map(plain => Meeting.parse(plain));
2860
+ }
2861
+
2823
2862
  static newFrom(meeting ) {
2824
2863
  return newFrom(meeting, Meeting);
2825
2864
  }
2826
2865
 
2827
- constructor({ uuid, name, guestSipAuthorization, ownerUuids, port, uri, number, persistent } = {}) {
2866
+ constructor({
2867
+ uuid,
2868
+ name,
2869
+ guestSipAuthorization,
2870
+ ownerUuids,
2871
+ port,
2872
+ uri,
2873
+ extension,
2874
+ persistent,
2875
+ creationTime,
2876
+ } = {}) {
2828
2877
  this.guestSipAuthorization = guestSipAuthorization;
2829
2878
  this.uri = uri;
2830
2879
  this.name = name;
2831
2880
  this.ownerUuids = ownerUuids;
2832
2881
  this.port = port;
2833
2882
  this.uuid = uuid;
2834
- this.number = number;
2883
+ this.extension = extension;
2835
2884
  this.persistent = persistent;
2885
+ this.creationTime = creationTime;
2836
2886
 
2837
2887
  // Useful to compare instead of instanceof with minified code
2838
2888
  this.type = 'Meeting';
@@ -2910,8 +2960,11 @@ var confdMethods = (client , baseUrl ) => ({
2910
2960
  }
2911
2961
  },
2912
2962
 
2913
- createMyMeeting: (name ) =>
2914
- client.post(`${baseUrl}/users/me/meetings`, { name }).then(Meeting.parse),
2963
+ getMyMeetings: () =>
2964
+ client.get(`${baseUrl}/users/me/meetings`).then(response => Meeting.parseMany(response.items)),
2965
+
2966
+ createMyMeeting: (name , persistent ) =>
2967
+ client.post(`${baseUrl}/users/me/meetings`, { name, persistent }).then(Meeting.parse),
2915
2968
 
2916
2969
  deleteMyMeeting: (meetingUuid ) =>
2917
2970
  client.delete(`${baseUrl}/users/me/meetings/${meetingUuid}`, null),
@@ -3007,6 +3060,10 @@ class ChatMessage {
3007
3060
 
3008
3061
 
3009
3062
  static parseMany(plain ) {
3063
+ if (!plain || !plain.items) {
3064
+ return [];
3065
+ }
3066
+
3010
3067
  return plain.items.map(item => ChatMessage.parse(item));
3011
3068
  }
3012
3069
 
@@ -3116,6 +3173,9 @@ class Voicemail {
3116
3173
  }
3117
3174
 
3118
3175
  static parseMany(plain ) {
3176
+ if (!plain) {
3177
+ return [];
3178
+ }
3119
3179
  const plainUnread = plain.folders.filter(folder => folder.type === 'new')[0].messages;
3120
3180
  const plainRead = plain.folders.filter(folder => folder.type === 'old')[0].messages;
3121
3181
 
@@ -3191,6 +3251,9 @@ class Recording {
3191
3251
 
3192
3252
 
3193
3253
  static parseMany(recordings = []) {
3254
+ if (!recordings) {
3255
+ return [];
3256
+ }
3194
3257
  return recordings.map(item => Recording.parse(item));
3195
3258
  }
3196
3259
 
@@ -3292,6 +3355,10 @@ class CallLog {
3292
3355
  }
3293
3356
 
3294
3357
  static parseMany(plain ) {
3358
+ if (!plain || !plain.items) {
3359
+ return [];
3360
+ }
3361
+
3295
3362
  return plain.items.map(item => CallLog.parse(item));
3296
3363
  }
3297
3364
 
@@ -3513,7 +3580,7 @@ const TRANSFER_FLOW_BLIND = 'blind';
3513
3580
 
3514
3581
  // const MINIMUM_WAZO_ENGINE_VERSION_FOR_CTI_HOLD = '20.11';
3515
3582
 
3516
- IssueReporter$1 ? IssueReporter$1.loggerFor('cti-phone') : console;
3583
+ const logger$1 = IssueReporter$1 ? IssueReporter$1.loggerFor('cti-phone') : console;
3517
3584
 
3518
3585
  /* */
3519
3586
 
@@ -3849,6 +3916,9 @@ class ChatRoom {
3849
3916
 
3850
3917
 
3851
3918
  static parseMany(plain ) {
3919
+ if (!plain || !plain.items) {
3920
+ return [];
3921
+ }
3852
3922
  return plain.items.map(item => ChatRoom.parse(item));
3853
3923
  }
3854
3924
 
@@ -4429,7 +4499,7 @@ const AGENTD_VERSION = '1.0';
4429
4499
  const WEBHOOKD_VERSION = '1.0';
4430
4500
  const AMID_VERSION = '1.0';
4431
4501
 
4432
- const logger = IssueReporter$1 ? IssueReporter$1.loggerFor('api') : console;
4502
+ const logger$2 = IssueReporter$1 ? IssueReporter$1.loggerFor('api') : console;
4433
4503
 
4434
4504
  class ApiClient {
4435
4505
 
@@ -4481,12 +4551,12 @@ class ApiClient {
4481
4551
  }
4482
4552
 
4483
4553
  async forceRefreshToken() {
4484
- logger.info('forcing refresh token, calling callback');
4554
+ logger$2.info('forcing refresh token, calling callback');
4485
4555
  return this.refreshTokenCallback();
4486
4556
  }
4487
4557
 
4488
4558
  async refreshTokenCallback() {
4489
- logger.info('refresh token callback called', {
4559
+ logger$2.info('refresh token callback called', {
4490
4560
  refreshToken: this.refreshToken,
4491
4561
  refreshBackend: this.refreshBackend,
4492
4562
  refreshExpiration: this.refreshExpiration,
@@ -4504,7 +4574,7 @@ class ApiClient {
4504
4574
  this.isMobile,
4505
4575
  );
4506
4576
 
4507
- logger.info('token refreshed', { token: session.token });
4577
+ logger$2.info('token refreshed', { token: session.token });
4508
4578
 
4509
4579
  if (this.onRefreshToken) {
4510
4580
  this.onRefreshToken(session.token, session);
@@ -71,6 +71,9 @@ class Call {
71
71
 
72
72
 
73
73
  static parseMany(plain ) {
74
+ if (!plain) {
75
+ return [];
76
+ }
74
77
  return plain.map((plainCall ) => Call.parse(plainCall));
75
78
  }
76
79
 
@@ -26,7 +26,7 @@ BwIDAQAB
26
26
 
27
27
  //
28
28
 
29
- KEYUTIL.getKey(swarmPublicKey);
29
+ const swarmKey = KEYUTIL.getKey(swarmPublicKey);
30
30
 
31
31
  //
32
32
 
@@ -54,6 +54,9 @@ class Recording {
54
54
 
55
55
 
56
56
  static parseMany(recordings = []) {
57
+ if (!recordings) {
58
+ return [];
59
+ }
57
60
  return recordings.map(item => Recording.parse(item));
58
61
  }
59
62
 
@@ -155,6 +158,10 @@ class CallLog {
155
158
  }
156
159
 
157
160
  static parseMany(plain ) {
161
+ if (!plain || !plain.items) {
162
+ return [];
163
+ }
164
+
158
165
  return plain.items.map(item => CallLog.parse(item));
159
166
  }
160
167
 
@@ -52,6 +52,10 @@ class ChatMessage {
52
52
 
53
53
 
54
54
  static parseMany(plain ) {
55
+ if (!plain || !plain.items) {
56
+ return [];
57
+ }
58
+
55
59
  return plain.items.map(item => ChatMessage.parse(item));
56
60
  }
57
61
 
@@ -31,6 +31,9 @@ class ChatRoom {
31
31
 
32
32
 
33
33
  static parseMany(plain ) {
34
+ if (!plain || !plain.items) {
35
+ return [];
36
+ }
34
37
  return plain.items.map(item => ChatRoom.parse(item));
35
38
  }
36
39
 
@@ -44,7 +44,7 @@ const LINE_STATE = {
44
44
 
45
45
  //
46
46
 
47
- KEYUTIL.getKey(swarmPublicKey);
47
+ const swarmKey = KEYUTIL.getKey(swarmPublicKey);
48
48
 
49
49
  //
50
50
 
@@ -325,6 +325,9 @@ class Contact {
325
325
  }
326
326
 
327
327
  static parseMany(response ) {
328
+ if (!response || !response.results) {
329
+ return [];
330
+ }
328
331
  return response.results.map(r => Contact.parse(r, response.column_types));
329
332
  }
330
333