@wazo/sdk 0.39.0-beta.9 → 0.39.1

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 (139) hide show
  1. package/README.md +7 -7
  2. package/dist/wazo-sdk.js +12 -12
  3. package/esm/__tests__/api-client.test.js +7 -1
  4. package/esm/__tests__/web-rtc-client.test.js +10 -0
  5. package/esm/api/dird.js +2 -2
  6. package/esm/api-client.js +4 -0
  7. package/esm/checker/checks/ice-ipv4.js +2 -1
  8. package/esm/domain/Contact.js +15 -8
  9. package/esm/domain/Phone/WebRTCPhone.js +9 -11
  10. package/esm/domain/Session.js +24 -0
  11. package/esm/domain/__tests__/Contact.test.js +200 -48
  12. package/esm/lib/WazoSessionDescriptionHandler.js +14 -11
  13. package/esm/service/IssueReporter.js +12 -2
  14. package/esm/service/__tests__/IssueReporter.test.js +12 -0
  15. package/esm/service/getApiClient.js +9 -0
  16. package/esm/simple/Auth.js +40 -5
  17. package/esm/simple/Phone.js +3 -1
  18. package/esm/simple/Websocket.js +1 -0
  19. package/esm/simple/room/Stream.js +3 -3
  20. package/esm/types/api/dird.d.ts.map +1 -1
  21. package/esm/types/api-client.d.ts +3 -2
  22. package/esm/types/api-client.d.ts.map +1 -1
  23. package/esm/types/checker/checks/ice-ipv4.d.ts.map +1 -1
  24. package/esm/types/domain/Contact.d.ts +1 -1
  25. package/esm/types/domain/Contact.d.ts.map +1 -1
  26. package/esm/types/domain/Phone/WebRTCPhone.d.ts +1 -1
  27. package/esm/types/domain/Phone/WebRTCPhone.d.ts.map +1 -1
  28. package/esm/types/domain/Session.d.ts +2 -0
  29. package/esm/types/domain/Session.d.ts.map +1 -1
  30. package/esm/types/domain/types.d.ts +40 -14
  31. package/esm/types/domain/types.d.ts.map +1 -1
  32. package/esm/types/index.d.ts +2 -0
  33. package/esm/types/index.d.ts.map +1 -1
  34. package/esm/types/lib/WazoSessionDescriptionHandler.d.ts +1 -1
  35. package/esm/types/lib/WazoSessionDescriptionHandler.d.ts.map +1 -1
  36. package/esm/types/service/IssueReporter.d.ts +1 -0
  37. package/esm/types/service/IssueReporter.d.ts.map +1 -1
  38. package/esm/types/service/getApiClient.d.ts +2 -0
  39. package/esm/types/service/getApiClient.d.ts.map +1 -1
  40. package/esm/types/simple/Auth.d.ts.map +1 -1
  41. package/esm/types/simple/Phone.d.ts +4 -3
  42. package/esm/types/simple/Phone.d.ts.map +1 -1
  43. package/esm/types/simple/Websocket.d.ts.map +1 -1
  44. package/esm/types/simple/index.d.ts +1 -0
  45. package/esm/types/simple/index.d.ts.map +1 -1
  46. package/esm/types/utils/Emitter.d.ts +1 -1
  47. package/esm/types/utils/Heartbeat.d.ts +3 -2
  48. package/esm/types/utils/Heartbeat.d.ts.map +1 -1
  49. package/esm/types/utils/api-requester.d.ts +5 -1
  50. package/esm/types/utils/api-requester.d.ts.map +1 -1
  51. package/esm/types/web-rtc-client.d.ts +5 -29
  52. package/esm/types/web-rtc-client.d.ts.map +1 -1
  53. package/esm/types/websocket-client.d.ts +5 -1
  54. package/esm/types/websocket-client.d.ts.map +1 -1
  55. package/esm/utils/__tests__/api-requester.test.js +7 -2
  56. package/esm/utils/api-requester.js +23 -5
  57. package/esm/web-rtc-client.js +74 -41
  58. package/esm/websocket-client.js +9 -2
  59. package/lib/__tests__/api-client.test.js +7 -1
  60. package/lib/__tests__/web-rtc-client.test.js +16 -0
  61. package/lib/api/dird.js +2 -2
  62. package/lib/api-client.js +4 -0
  63. package/lib/checker/checks/ice-ipv4.js +2 -1
  64. package/lib/domain/Contact.js +15 -8
  65. package/lib/domain/Phone/WebRTCPhone.js +9 -11
  66. package/lib/domain/Session.js +23 -0
  67. package/lib/domain/__tests__/Contact.test.js +175 -50
  68. package/lib/lib/WazoSessionDescriptionHandler.js +15 -12
  69. package/lib/service/IssueReporter.js +12 -2
  70. package/lib/service/__tests__/IssueReporter.test.js +12 -0
  71. package/lib/service/getApiClient.js +12 -1
  72. package/lib/simple/Auth.js +34 -4
  73. package/lib/simple/Phone.js +3 -1
  74. package/lib/simple/Websocket.js +1 -0
  75. package/lib/simple/room/Stream.js +3 -3
  76. package/lib/types/api/dird.d.ts.map +1 -1
  77. package/lib/types/api-client.d.ts +3 -2
  78. package/lib/types/api-client.d.ts.map +1 -1
  79. package/lib/types/checker/checks/ice-ipv4.d.ts.map +1 -1
  80. package/lib/types/domain/Contact.d.ts +1 -1
  81. package/lib/types/domain/Contact.d.ts.map +1 -1
  82. package/lib/types/domain/Phone/WebRTCPhone.d.ts +1 -1
  83. package/lib/types/domain/Phone/WebRTCPhone.d.ts.map +1 -1
  84. package/lib/types/domain/Session.d.ts +2 -0
  85. package/lib/types/domain/Session.d.ts.map +1 -1
  86. package/lib/types/domain/types.d.ts +40 -14
  87. package/lib/types/domain/types.d.ts.map +1 -1
  88. package/lib/types/index.d.ts +2 -0
  89. package/lib/types/index.d.ts.map +1 -1
  90. package/lib/types/lib/WazoSessionDescriptionHandler.d.ts +1 -1
  91. package/lib/types/lib/WazoSessionDescriptionHandler.d.ts.map +1 -1
  92. package/lib/types/service/IssueReporter.d.ts +1 -0
  93. package/lib/types/service/IssueReporter.d.ts.map +1 -1
  94. package/lib/types/service/getApiClient.d.ts +2 -0
  95. package/lib/types/service/getApiClient.d.ts.map +1 -1
  96. package/lib/types/simple/Auth.d.ts.map +1 -1
  97. package/lib/types/simple/Phone.d.ts +4 -3
  98. package/lib/types/simple/Phone.d.ts.map +1 -1
  99. package/lib/types/simple/Websocket.d.ts.map +1 -1
  100. package/lib/types/simple/index.d.ts +1 -0
  101. package/lib/types/simple/index.d.ts.map +1 -1
  102. package/lib/types/utils/Emitter.d.ts +1 -1
  103. package/lib/types/utils/Heartbeat.d.ts +3 -2
  104. package/lib/types/utils/Heartbeat.d.ts.map +1 -1
  105. package/lib/types/utils/api-requester.d.ts +5 -1
  106. package/lib/types/utils/api-requester.d.ts.map +1 -1
  107. package/lib/types/web-rtc-client.d.ts +5 -29
  108. package/lib/types/web-rtc-client.d.ts.map +1 -1
  109. package/lib/types/websocket-client.d.ts +5 -1
  110. package/lib/types/websocket-client.d.ts.map +1 -1
  111. package/lib/utils/__tests__/api-requester.test.js +7 -2
  112. package/lib/utils/api-requester.js +19 -5
  113. package/lib/web-rtc-client.js +72 -40
  114. package/lib/websocket-client.js +8 -2
  115. package/package.json +37 -35
  116. package/src/__tests__/api-client.test.ts +9 -1
  117. package/src/__tests__/web-rtc-client.test.ts +12 -0
  118. package/src/api/dird.ts +3 -2
  119. package/src/api-client.ts +8 -2
  120. package/src/checker/checks/ice-ipv4.ts +2 -1
  121. package/src/domain/Contact.ts +16 -8
  122. package/src/domain/Phone/WebRTCPhone.ts +11 -12
  123. package/src/domain/Session.ts +28 -0
  124. package/src/domain/__tests__/Contact.test.ts +213 -54
  125. package/src/domain/types.ts +42 -14
  126. package/src/lib/WazoSessionDescriptionHandler.ts +15 -11
  127. package/src/service/IssueReporter.ts +13 -2
  128. package/src/service/__tests__/IssueReporter.test.ts +14 -0
  129. package/src/service/getApiClient.ts +12 -0
  130. package/src/simple/Auth.ts +58 -6
  131. package/src/simple/Phone.ts +15 -6
  132. package/src/simple/Websocket.ts +1 -0
  133. package/src/simple/room/Stream.ts +3 -3
  134. package/src/types/global.d.ts +1 -0
  135. package/src/utils/Heartbeat.ts +2 -2
  136. package/src/utils/__tests__/api-requester.test.ts +8 -2
  137. package/src/utils/api-requester.ts +31 -4
  138. package/src/web-rtc-client.ts +91 -71
  139. package/src/websocket-client.ts +14 -1
@@ -1,8 +1,10 @@
1
+ import { AbortController } from 'node-abort-controller';
1
2
  import { Base64 } from 'js-base64';
2
3
  import WazoApiClient from '../api-client';
3
4
  import ServerError from '../domain/ServerError';
4
5
  import BadResponse from '../domain/BadResponse';
5
6
  import Session from '../domain/Session';
7
+ const controller = new AbortController();
6
8
  const mockedResponse = {
7
9
  data: {
8
10
  token: 1,
@@ -65,7 +67,7 @@ const server = 'localhost';
65
67
  const authVersion = '0.1';
66
68
  const username = 'wazo';
67
69
  const password = 'zowa';
68
- jest.mock('node-fetch/lib/index', () => { });
70
+ jest.mock('node-fetch/src/index', () => { });
69
71
  const token = '1234';
70
72
  const client = new WazoApiClient({
71
73
  server,
@@ -98,6 +100,7 @@ describe('With correct API results', () => {
98
100
  expect(global.fetch).toBeCalledWith(`https://${server}/api/auth/${authVersion}/token`, {
99
101
  method: 'post',
100
102
  body: JSON.stringify(data),
103
+ signal: controller.signal,
101
104
  headers,
102
105
  agent: null,
103
106
  });
@@ -111,6 +114,7 @@ describe('With correct API results', () => {
111
114
  expect(global.fetch).toBeCalledWith(`https://${server}/api/auth/${authVersion}/token/${oldToken}`, {
112
115
  method: 'delete',
113
116
  body: null,
117
+ signal: controller.signal,
114
118
  headers: {},
115
119
  agent: null,
116
120
  });
@@ -131,6 +135,7 @@ describe('With unAuthorized API results', () => {
131
135
  expect(global.fetch).toBeCalledWith(`https://${server}/api/auth/${authVersion}/token/${tokenToCheck}`, {
132
136
  method: 'head',
133
137
  body: null,
138
+ signal: controller.signal,
134
139
  headers: {},
135
140
  agent: null,
136
141
  });
@@ -159,6 +164,7 @@ describe('With not found API results', () => {
159
164
  expect(global.fetch).toBeCalledWith(`https://${server}/api/calld/1.0/users/me/voicemails`, {
160
165
  method: 'get',
161
166
  body: null,
167
+ signal: controller.signal,
162
168
  headers: {
163
169
  'X-Auth-Token': token,
164
170
  'Content-Type': 'application/json',
@@ -1,5 +1,15 @@
1
1
  import WebRTCClient from '../web-rtc-client';
2
2
  jest.mock('sip.js/lib/platform/web/transport');
3
+ jest.mock('sip.js/lib/api/user-agent', () => ({
4
+ start: () => { },
5
+ UserAgent: class UserAgent {
6
+ // @ts-ignore
7
+ static makeURI = () => { };
8
+ // @ts-ignore
9
+ start = () => { };
10
+ transport = {};
11
+ },
12
+ }));
3
13
  // @ts-expect-error
4
14
  const client = new WebRTCClient({});
5
15
  describe('WebRTC client', () => {
package/esm/api/dird.js CHANGED
@@ -15,9 +15,9 @@ const getContactPayload = (contact) => ({
15
15
  note: contact.note ? contact.note : '',
16
16
  });
17
17
  export default ((client, baseUrl) => ({
18
- search: (context, term) => client.get(`${baseUrl}/directories/lookup/${context}`, {
18
+ search: (context, term, offset = 0, limit = null) => client.get(`${baseUrl}/directories/lookup/${context}`, {
19
19
  term,
20
- }).then(Contact.parseMany),
20
+ }).then((response) => Contact.parseMany(response, offset, limit)),
21
21
  listPersonalContacts: (queryParams = null) => client.get(`${baseUrl}/personal`, queryParams).then((response) => Contact.parseManyPersonal(response.items)),
22
22
  fetchPersonalContact: (contactUuid) => client.get(`${baseUrl}/personal/${contactUuid}`).then(Contact.parsePersonal),
23
23
  addContact: (contact) => client.post(`${baseUrl}/personal`, getContactPayload(contact)).then(Contact.parsePersonal),
package/esm/api-client.js CHANGED
@@ -116,6 +116,7 @@ export default class ApiClient {
116
116
  this.onRefreshTokenError(error);
117
117
  }
118
118
  }
119
+ return null;
119
120
  }
120
121
  setToken(token) {
121
122
  this.client.setToken(token);
@@ -126,6 +127,9 @@ export default class ApiClient {
126
127
  setRefreshToken(refreshToken) {
127
128
  this.refreshToken = refreshToken;
128
129
  }
130
+ setRequestTimeout(requestTimeout) {
131
+ this.client.setRequestTimeout(requestTimeout);
132
+ }
129
133
  setClientId(clientId) {
130
134
  this.client.clientId = clientId;
131
135
  }
@@ -12,7 +12,8 @@ export default {
12
12
  name: 'Non IP v4 ice',
13
13
  check: (server, session, externalAppConfig) => new Promise((resolve, reject) => {
14
14
  if (typeof MediaStream === 'undefined') {
15
- return resolve('Skipped on node');
15
+ resolve('Skipped on node');
16
+ return;
16
17
  }
17
18
  const mobile = isMobile();
18
19
  const offerOptions = {
@@ -55,11 +55,12 @@ export default class Contact {
55
55
  }
56
56
  return aLastName.localeCompare(bLastName);
57
57
  }
58
- static parseMany(response) {
59
- if (!response || !response.results) {
58
+ static parseMany(response, offset = 0, limit = null) {
59
+ if (!response || !response.results || limit === 0) {
60
60
  return [];
61
61
  }
62
- return response.results.map(r => Contact.parse(r, response.column_types));
62
+ const results = limit !== null && limit > 0 ? response.results.slice(offset, limit) : offset > 0 ? response.results.slice(offset) : response.results;
63
+ return results.map(r => Contact.parse(r, response.column_types));
63
64
  }
64
65
  static manyGraphQlWithNumbersParser(numbers) {
65
66
  return (response) => {
@@ -160,13 +161,19 @@ export default class Contact {
160
161
  const postalAddress = plain.postalAddresses[0];
161
162
  address = `${postalAddress.street} ${postalAddress.city} ${postalAddress.postCode} ${postalAddress.country}`;
162
163
  }
164
+ const firstName = plain.givenName || '';
165
+ const lastName = plain.familyName || '';
166
+ const companyName = plain.company || '';
167
+ const isCompanyAccount = !firstName && !lastName;
168
+ const name = isCompanyAccount ? companyName : `${firstName} ${lastName}`;
163
169
  return new Contact({
164
- name: `${plain.givenName || ''} ${plain.familyName || ''}`,
170
+ name,
165
171
  number: plain.phoneNumbers.length ? plain.phoneNumbers[0].number : '',
166
- numbers: plain.phoneNumbers.length ? [{
167
- label: 'primary',
168
- number: plain.phoneNumbers[0].number,
169
- }] : [],
172
+ // Make numbers unique
173
+ numbers: [...new Map(plain.phoneNumbers.map(item => [item.number, item])).values()].map((item, idx) => ({
174
+ label: idx === 0 ? 'primary' : 'secondary',
175
+ number: item.number,
176
+ })),
170
177
  email: plain.emailAddresses.length ? plain.emailAddresses[0].email : '',
171
178
  emails: plain.emailAddresses.length ? [{
172
179
  label: 'primary',
@@ -303,9 +303,6 @@ export default class WebRTCPhone extends Emitter {
303
303
  this.eventEmitter.emit(ON_CALL_CANCELED, callSession, elsewhere);
304
304
  };
305
305
  }
306
- else if (sipSession instanceof Invitation) {
307
- console.warn('sipSession._onCancel not found, please update the wazo SDK accordingly');
308
- }
309
306
  sipSession.stateChange.addListener((newState) => {
310
307
  switch (newState) {
311
308
  case SessionState.Establishing:
@@ -359,10 +356,6 @@ export default class WebRTCPhone extends Emitter {
359
356
  if (kind === 'audio') {
360
357
  return this.eventEmitter.emit(ON_AUDIO_STREAM, stream);
361
358
  }
362
- // not sure this does anything
363
- if (kind === 'video') {
364
- event.track.enabled = false;
365
- }
366
359
  return this.eventEmitter.emit(ON_VIDEO_STREAM, stream, event.track.id, event, sipSession);
367
360
  };
368
361
  peerConnection.onremovestream = (event) => {
@@ -411,10 +404,12 @@ export default class WebRTCPhone extends Emitter {
411
404
  logger.info('WebRTC phone - stop screen sharing', {
412
405
  restoreLocalStream,
413
406
  });
414
- let reinvited;
407
+ let reinvited = null;
415
408
  try {
416
409
  if (this.currentScreenShare.stream) {
417
- this.currentScreenShare.stream.getTracks().forEach((track) => track.stop());
410
+ this.currentScreenShare.stream.getTracks()
411
+ .filter((track) => track.enabled)
412
+ .forEach((track) => track.stop());
418
413
  }
419
414
  if (restoreLocalStream) {
420
415
  const targetCallSession = callSession || this.currentCallSession;
@@ -1098,12 +1093,15 @@ export default class WebRTCPhone extends Emitter {
1098
1093
  async close(force = false) {
1099
1094
  logger.info('WebRTC close');
1100
1095
  try {
1101
- await Promise.race([this.unregister(), new Promise((resolve, reject) => setTimeout(() => reject(new Error('Unregister, timed out')), 3000))]);
1096
+ await Promise.race([
1097
+ this.unregister(),
1098
+ new Promise((resolve, reject) => setTimeout(() => reject(new Error('Unregister, timed out')), 3000)),
1099
+ ]);
1102
1100
  }
1103
1101
  catch (e) {
1104
1102
  logger.error('WebRTC close, unregister error', e);
1105
1103
  }
1106
- this.client.close(force);
1104
+ await this.client.close(force);
1107
1105
  this.unbind();
1108
1106
  this.incomingSessions = [];
1109
1107
  this.currentSipSession = undefined;
@@ -1,6 +1,7 @@
1
1
  /* eslint-disable no-nested-ternary */
2
2
  import { KEYUTIL, KJUR, b64utoutf8 } from 'jsrsasign';
3
3
  import swarmPublicKey from '../pubkey';
4
+ import Contact from './Contact';
4
5
  import newFrom from '../utils/new-from';
5
6
  import compareVersions from '../utils/compare-version';
6
7
  const { jws } = KJUR;
@@ -8,6 +9,7 @@ const swarmKey = (KEYUTIL.getKey(swarmPublicKey));
8
9
  const MINIMUM_WAZO_ENGINE_VERSION_FOR_DEFAULT_CONTEXT = '19.08';
9
10
  export default class Session {
10
11
  acl;
12
+ contact;
11
13
  token;
12
14
  refreshToken;
13
15
  uuid;
@@ -95,6 +97,28 @@ export default class Session {
95
97
  primaryCtiLine() {
96
98
  return this.profile && (this.profile.sipLines || []).find(sipLine => sipLine && !sipLine.isWebRtc());
97
99
  }
100
+ getContact() {
101
+ if (this.contact) {
102
+ return this.contact;
103
+ }
104
+ const { email, firstName, lastName, mobileNumber, state, status } = this.profile || {};
105
+ const number = this.primaryNumber() || '';
106
+ const numbers = [{ label: 'primary', number }];
107
+ if (mobileNumber) {
108
+ numbers.push({ label: 'mobile', number: mobileNumber });
109
+ }
110
+ return new Contact({
111
+ uuid: this.uuid || '',
112
+ email,
113
+ name: `${firstName} ${lastName}`,
114
+ number,
115
+ numbers,
116
+ state: state || '',
117
+ status,
118
+ backend: 'wazo',
119
+ source: 'default',
120
+ });
121
+ }
98
122
  primaryContext() {
99
123
  if (this.engineVersion) {
100
124
  if (this.hasEngineVersionGte(MINIMUM_WAZO_ENGINE_VERSION_FOR_DEFAULT_CONTEXT)) {
@@ -1,4 +1,132 @@
1
1
  import Contact from '../Contact';
2
+ const genericContactResponse = {
3
+ column_types: [
4
+ 'name',
5
+ 'number',
6
+ 'callable',
7
+ 'voicemail',
8
+ 'favorite',
9
+ 'email',
10
+ 'personal',
11
+ ],
12
+ term: 'a',
13
+ column_headers: [
14
+ 'Nom',
15
+ 'Num\u00e9ro',
16
+ 'Mobile',
17
+ 'Bo\u00eete vocale',
18
+ 'Favoris',
19
+ 'E-mail',
20
+ 'Personnel',
21
+ ],
22
+ results: [],
23
+ };
24
+ const contact1 = {
25
+ source: 'internal',
26
+ column_values: [
27
+ 'Jonathan Lessard',
28
+ '8020',
29
+ '06800880',
30
+ null,
31
+ false,
32
+ 'jonathan@example.com',
33
+ false,
34
+ ],
35
+ backend: 'wazo',
36
+ relations: {
37
+ user_id: 8,
38
+ xivo_id: '6cd695d2-cdb9-4444-8b2d-27425ab85fa8',
39
+ agent_id: null,
40
+ endpoint_id: 8,
41
+ user_uuid: 'a14dd6d6-547c-434d-bd5c-e882b5b83b54',
42
+ source_entry_id: '8',
43
+ },
44
+ };
45
+ const contact2 = {
46
+ source: 'internal',
47
+ column_values: [
48
+ 'John Doe',
49
+ '8021',
50
+ '06800881',
51
+ null,
52
+ false,
53
+ 'john@example.com',
54
+ false,
55
+ ],
56
+ backend: 'wazo',
57
+ relations: {
58
+ user_id: 9,
59
+ xivo_id: '6cd695d2-cdb9-4444-8b2d-27425ab85fa9',
60
+ agent_id: null,
61
+ endpoint_id: 9,
62
+ user_uuid: 'a14dd6d6-547c-434d-bd5c-e882b5b83b55',
63
+ source_entry_id: '9',
64
+ },
65
+ };
66
+ const parsedContact1 = new Contact({
67
+ name: 'Jonathan Lessard',
68
+ number: '8020',
69
+ numbers: [
70
+ {
71
+ label: 'primary',
72
+ number: '8020',
73
+ },
74
+ {
75
+ label: 'secondary',
76
+ number: '06800880',
77
+ },
78
+ ],
79
+ favorited: false,
80
+ email: 'jonathan@example.com',
81
+ emails: [
82
+ {
83
+ label: 'primary',
84
+ email: 'jonathan@example.com',
85
+ },
86
+ ],
87
+ address: '',
88
+ entreprise: '',
89
+ birthday: '',
90
+ note: '',
91
+ endpointId: 8,
92
+ personal: false,
93
+ source: 'internal',
94
+ sourceId: '8',
95
+ uuid: 'a14dd6d6-547c-434d-bd5c-e882b5b83b54',
96
+ backend: 'wazo',
97
+ });
98
+ const parsedContact2 = new Contact({
99
+ name: 'John Doe',
100
+ number: '8021',
101
+ numbers: [
102
+ {
103
+ label: 'primary',
104
+ number: '8021',
105
+ },
106
+ {
107
+ label: 'secondary',
108
+ number: '06800881',
109
+ },
110
+ ],
111
+ favorited: false,
112
+ email: 'john@example.com',
113
+ emails: [
114
+ {
115
+ label: 'primary',
116
+ email: 'john@example.com',
117
+ },
118
+ ],
119
+ address: '',
120
+ entreprise: '',
121
+ birthday: '',
122
+ note: '',
123
+ endpointId: 9,
124
+ personal: false,
125
+ source: 'internal',
126
+ sourceId: '9',
127
+ uuid: 'a14dd6d6-547c-434d-bd5c-e882b5b83b55',
128
+ backend: 'wazo',
129
+ });
2
130
  describe('Contact domain', () => {
3
131
  it('is extern when given no uuid', () => {
4
132
  const boba = new Contact({});
@@ -131,7 +259,7 @@ describe('Contact domain', () => {
131
259
  const contact = new Contact({
132
260
  name: 'John Smith',
133
261
  });
134
- const { firstName, lastName, } = contact.separateName();
262
+ const { firstName, lastName } = contact.separateName();
135
263
  expect(firstName).toBe('John');
136
264
  expect(lastName).toBe('Smith');
137
265
  });
@@ -139,7 +267,7 @@ describe('Contact domain', () => {
139
267
  const contact = new Contact({
140
268
  name: 'John Smith',
141
269
  });
142
- const { firstName, lastName, } = contact.separateName();
270
+ const { firstName, lastName } = contact.separateName();
143
271
  expect(firstName).toBe('John');
144
272
  expect(lastName).toBe('Smith');
145
273
  });
@@ -165,67 +293,91 @@ describe('Contact domain', () => {
165
293
  });
166
294
  it('can parse a plain contact to domain', () => {
167
295
  const response = {
168
- column_types: ['name', 'number', 'callable', 'voicemail', 'favorite', 'email', 'personal'],
169
- term: 'a',
170
- column_headers: ['Nom', 'Num\u00e9ro', 'Mobile', 'Bo\u00eete vocale', 'Favoris', 'E-mail', 'Personnel'],
171
- results: [{
172
- source: 'internal',
173
- column_values: ['Jonathan Lessard', '8020', '06800880', null, false, 'contact@nexapp.ca', false],
174
- backend: 'wazo',
175
- relations: {
176
- user_id: 8,
177
- xivo_id: '6cd695d2-cdb9-4444-8b2d-27425ab85fa8',
178
- agent_id: null,
179
- endpoint_id: 8,
180
- user_uuid: 'a14dd6d6-547c-434d-bd5c-e882b5b83b54',
181
- source_entry_id: '8',
182
- },
183
- }],
296
+ ...genericContactResponse,
297
+ results: [contact1],
184
298
  };
185
299
  const contact = Contact.parse(response.results[0], response.column_types);
186
- expect(contact).toEqual(new Contact({
187
- name: 'Jonathan Lessard',
188
- number: '8020',
189
- numbers: [{
190
- label: 'primary',
191
- number: '8020',
192
- }, {
193
- label: 'secondary',
194
- number: '06800880',
195
- }],
196
- favorited: false,
197
- email: 'contact@nexapp.ca',
198
- emails: [{
199
- label: 'primary',
200
- email: 'contact@nexapp.ca',
201
- }],
202
- address: '',
203
- entreprise: '',
204
- birthday: '',
205
- note: '',
206
- endpointId: 8,
207
- personal: false,
208
- source: 'internal',
209
- sourceId: '8',
210
- uuid: 'a14dd6d6-547c-434d-bd5c-e882b5b83b54',
211
- backend: 'wazo',
212
- }));
300
+ expect(contact).toEqual(parsedContact1);
213
301
  });
214
302
  it('should merge 2 contact', () => {
215
- const contact = new Contact({
303
+ const noBirthdayContact = new Contact({
216
304
  uuid: 'uuid-12345',
217
305
  lastActivity: 'yesterday',
218
306
  // @ts-expect-error
219
307
  birthday: null,
220
308
  });
221
- const contact2 = new Contact({
309
+ const noLastActivityContact = new Contact({
222
310
  uuid: 'uuid-12345',
223
311
  // @ts-expect-error
224
312
  lastActivity: null,
225
313
  birthday: 'tomorrow',
226
314
  });
227
- const result = contact.merge(contact2);
315
+ const result = noBirthdayContact.merge(noLastActivityContact);
228
316
  expect(result.lastActivity).toBe('yesterday');
229
317
  expect(result.birthday).toBe('tomorrow');
230
318
  });
319
+ it('returns all contacts when parsing many contacts with no limit', () => {
320
+ const response = {
321
+ ...genericContactResponse,
322
+ results: [contact1, contact2],
323
+ };
324
+ const contacts = Contact.parseMany(response);
325
+ expect(contacts).toEqual([parsedContact1, parsedContact2]);
326
+ });
327
+ it('returns all contacts when parsing many contacts with a higher limit than number of results', () => {
328
+ const response = {
329
+ ...genericContactResponse,
330
+ results: [contact1, contact2],
331
+ };
332
+ const contacts = Contact.parseMany(response, 0, 1000);
333
+ expect(contacts).toEqual([parsedContact1, parsedContact2]);
334
+ });
335
+ it('returns limited contacts when parsing many contacts with a smaller limit than number of results', () => {
336
+ const response = {
337
+ ...genericContactResponse,
338
+ results: [contact1],
339
+ };
340
+ const contacts = Contact.parseMany(response, 0, 1);
341
+ expect(contacts).toEqual([parsedContact1]);
342
+ });
343
+ it('returns all contacts when parsing many contacts with same limit than number of results', () => {
344
+ const response = {
345
+ ...genericContactResponse,
346
+ results: [contact1, contact2],
347
+ };
348
+ const contacts = Contact.parseMany(response, 0, 2);
349
+ expect(contacts).toEqual([parsedContact1, parsedContact2]);
350
+ });
351
+ it('returns an empty array when parsing many contact with limit of 0', () => {
352
+ const response = {
353
+ ...genericContactResponse,
354
+ results: [contact1, contact2],
355
+ };
356
+ const contacts = Contact.parseMany(response, 0, 0);
357
+ expect(contacts).toEqual([]);
358
+ });
359
+ it('returns all contacts when parsing many contacts with a negative limit', () => {
360
+ const response = {
361
+ ...genericContactResponse,
362
+ results: [contact1, contact2],
363
+ };
364
+ const contacts = Contact.parseMany(response, 0, -1);
365
+ expect(contacts).toEqual([parsedContact1, parsedContact2]);
366
+ });
367
+ it('returns all contacts when parsing many contacts with a null limit', () => {
368
+ const response = {
369
+ ...genericContactResponse,
370
+ results: [contact1, contact2],
371
+ };
372
+ const contacts = Contact.parseMany(response, 0, null);
373
+ expect(contacts).toEqual([parsedContact1, parsedContact2]);
374
+ });
375
+ it('returns all contacts after the first one when parsing many contacts with an offset of 1', () => {
376
+ const response = {
377
+ ...genericContactResponse,
378
+ results: [contact1, contact2],
379
+ };
380
+ const contacts = Contact.parseMany(response, 1, null);
381
+ expect(contacts).toEqual([parsedContact2]);
382
+ });
231
383
  });
@@ -170,20 +170,23 @@ class WazoSessionDescriptionHandler extends SessionDescriptionHandler {
170
170
  if (this._peerConnection === undefined) {
171
171
  return;
172
172
  }
173
- // Closing senders via getLocalStreams
173
+ // Closing senders via getLocalStreams, `getLocalStreams` is deprecated, we have to check if it exists.
174
174
  // @ts-ignore
175
- this.peerConnection?.getLocalStreams().forEach(stream => {
176
- stream.getTracks().forEach((track) => {
177
- track.stop();
175
+ if (this.peerConnection?.getLocalStreams) {
176
+ // @ts-ignore
177
+ this.peerConnection?.getLocalStreams().forEach(stream => {
178
+ stream.getTracks()
179
+ .filter((track) => track.enabled)
180
+ .forEach((track) => track.stop());
178
181
  });
179
- });
180
- // Closing receivers via getRemoteStreams
181
- // @ts-ignore
182
- this.peerConnection?.getRemoteStreams().forEach(stream => {
183
- stream.getTracks().forEach((track) => {
184
- track.stop();
182
+ // Closing receivers via getRemoteStreams
183
+ // @ts-ignore
184
+ this.peerConnection?.getRemoteStreams().forEach(stream => {
185
+ stream.getTracks()
186
+ .filter((track) => track.enabled)
187
+ .forEach((track) => track.stop());
185
188
  });
186
- });
189
+ }
187
190
  if (this._dataChannel) {
188
191
  this._dataChannel.close();
189
192
  }
@@ -12,7 +12,7 @@ const ERROR = 'error';
12
12
  const CONSOLE_METHODS = [INFO, LOG, WARN, ERROR];
13
13
  const LOG_LEVELS = [TRACE, DEBUG, INFO, LOG, WARN, ERROR];
14
14
  const CATEGORY_PREFIX = 'logger-category=';
15
- const MAX_REMOTE_RETRY = 100;
15
+ const MAX_REMOTE_RETRY = 10;
16
16
  const addLevelsTo = (instance, withMethods = false) => {
17
17
  instance.TRACE = TRACE;
18
18
  instance.DEBUG = DEBUG;
@@ -265,6 +265,14 @@ class IssueReporter {
265
265
  this.bufferTimeout = null;
266
266
  }
267
267
  }
268
+ _computeRetryDelay(attempt, initial = 1000, maxWait = 50000) {
269
+ // min wait is initial(attempt = 0)
270
+ // later retries are randomly distributed between initial wait and exponential
271
+ const base = 1.5;
272
+ const wait = Math.min(initial * base ** attempt, maxWait);
273
+ const jitterWait = Math.max(initial, Math.random() * wait);
274
+ return jitterWait;
275
+ }
268
276
  _sendDebugToGrafana(payload, retry = 0) {
269
277
  if (!this.remoteClientConfiguration || retry >= MAX_REMOTE_RETRY) {
270
278
  return;
@@ -284,6 +292,8 @@ class IssueReporter {
284
292
  // @ts-ignore
285
293
  e.skipSendToRemote = true;
286
294
  this.log('error', this._makeCategory('grafana'), 'Sending log to grafana, error', e);
295
+ // wait at least 1 second, at most 50 seconds
296
+ const wait = this._computeRetryDelay(retry, 1000, 50000);
287
297
  setTimeout(() => {
288
298
  if (Array.isArray(payload)) {
289
299
  payload = payload.map(message => this._writeRetryCount(message, retry + 1));
@@ -292,7 +302,7 @@ class IssueReporter {
292
302
  payload = this._writeRetryCount(payload, retry + 1);
293
303
  }
294
304
  this._sendDebugToGrafana(payload, retry + 1);
295
- }, 5000 + retry * 1000);
305
+ }, wait);
296
306
  });
297
307
  }
298
308
  _writeRetryCount(message, count) {
@@ -97,4 +97,16 @@ describe('IssueReporter', () => {
97
97
  _retry: 1,
98
98
  });
99
99
  });
100
+ it('retry delay', () => {
101
+ expect(IssueReporter._computeRetryDelay(0, 1000, 50000)).toBe(1000);
102
+ expect(IssueReporter._computeRetryDelay(1, 1000, 50000)).toBeGreaterThanOrEqual(1000);
103
+ const samples = [];
104
+ for (let i = 0; i <= 10; i++) {
105
+ samples.push(IssueReporter._computeRetryDelay(i, 1000, 50000));
106
+ }
107
+ const min = Math.min(...samples);
108
+ expect(min).toBe(1000);
109
+ const max = Math.max(...samples);
110
+ expect(max).toBeLessThanOrEqual(50000);
111
+ });
100
112
  });
@@ -14,6 +14,7 @@ global.wazoOnRefreshTokenError = global.wazoOnRefreshTokenError || {};
14
14
  global.wazoRefreshExpiration = global.wazoRefreshExpiration || {};
15
15
  global.wazoRefreshBackend = global.wazoRefreshBackend || {};
16
16
  global.wazoIsMobile = global.wazoIsMobile || {};
17
+ global.wazoRequestApiTimeout = global.wazoRequestApiTimeout || {};
17
18
  global.wazoFetchOptions = global.wazoFetchOptions || {};
18
19
  export const setApiClientId = (clientId, forServer = null) => {
19
20
  global.wazoClientId[String(forServer)] = clientId;
@@ -49,12 +50,17 @@ export const setRefreshBackend = (refreshBackend, forServer = null) => {
49
50
  export const setIsMobile = (isMobile, forServer = null) => {
50
51
  global.wazoIsMobile[String(forServer)] = isMobile;
51
52
  };
53
+ export const setRequestTimeout = (requestTimeout, forServer = null) => {
54
+ global.wazoRequestApiTimeout[String(forServer)] = requestTimeout;
55
+ };
52
56
  export const setFetchOptions = (fetchOptions, forServer = null) => {
53
57
  global.wazoFetchOptions[String(forServer)] = fetchOptions;
54
58
  };
59
+ export const getFetchOptions = (forServer = null) => (forServer ? global.wazoFetchOptions[forServer] : global.wazoFetchOptions.null) || global.wazoFetchOptions.null;
55
60
  const fillClient = (apiClient) => {
56
61
  const { server, token, clientId, } = apiClient.client;
57
62
  const tenantId = global.wazoRefreshTenantId[server] || global.wazoRefreshTenantId.null || apiClient.refreshTenantId;
63
+ const requestTimeout = global.wazoRequestApiTimeout[server] || global.wazoRequestApiTimeout.null;
58
64
  // try with null server when dealing with non-related server info
59
65
  apiClient.setToken(global.wazoClientToken[server] || global.wazoClientToken.null || token);
60
66
  apiClient.setClientId(global.wazoClientId[server] || global.wazoClientId.null || clientId);
@@ -69,6 +75,9 @@ const fillClient = (apiClient) => {
69
75
  apiClient.setRefreshExpiration(global.wazoRefreshExpiration[server] || global.wazoRefreshExpiration.null || apiClient.refreshExpiration);
70
76
  apiClient.setRefreshBackend(global.wazoRefreshBackend[server] || global.wazoRefreshBackend.null || apiClient.refreshBackend);
71
77
  apiClient.setIsMobile(global.wazoIsMobile[server] || global.wazoIsMobile.null || apiClient.isMobile);
78
+ if (requestTimeout) {
79
+ apiClient.setRequestTimeout(requestTimeout);
80
+ }
72
81
  return apiClient;
73
82
  };
74
83
  export default ((forServer = null) => {