@wazo/sdk 0.39.0-beta.8 → 0.39.0

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 (146) 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/chatd.js +17 -3
  6. package/esm/api/dird.js +2 -2
  7. package/esm/api-client.js +4 -0
  8. package/esm/checker/checks/ice-ipv4.js +2 -1
  9. package/esm/domain/Contact.js +15 -8
  10. package/esm/domain/Phone/WebRTCPhone.js +9 -11
  11. package/esm/domain/Session.js +24 -0
  12. package/esm/domain/__tests__/Contact.test.js +200 -48
  13. package/esm/lib/WazoSessionDescriptionHandler.js +14 -11
  14. package/esm/service/IssueReporter.js +12 -2
  15. package/esm/service/__tests__/IssueReporter.test.js +12 -0
  16. package/esm/service/getApiClient.js +9 -0
  17. package/esm/simple/Auth.js +40 -5
  18. package/esm/simple/Phone.js +3 -1
  19. package/esm/simple/Websocket.js +1 -0
  20. package/esm/simple/room/Stream.js +3 -3
  21. package/esm/types/api/chatd.d.ts +1 -1
  22. package/esm/types/api/chatd.d.ts.map +1 -1
  23. package/esm/types/api/dird.d.ts.map +1 -1
  24. package/esm/types/api-client.d.ts +3 -2
  25. package/esm/types/api-client.d.ts.map +1 -1
  26. package/esm/types/checker/checks/ice-ipv4.d.ts.map +1 -1
  27. package/esm/types/domain/Contact.d.ts +1 -1
  28. package/esm/types/domain/Contact.d.ts.map +1 -1
  29. package/esm/types/domain/Phone/WebRTCPhone.d.ts +1 -1
  30. package/esm/types/domain/Phone/WebRTCPhone.d.ts.map +1 -1
  31. package/esm/types/domain/Session.d.ts +2 -0
  32. package/esm/types/domain/Session.d.ts.map +1 -1
  33. package/esm/types/domain/types.d.ts +40 -14
  34. package/esm/types/domain/types.d.ts.map +1 -1
  35. package/esm/types/index.d.ts +2 -0
  36. package/esm/types/index.d.ts.map +1 -1
  37. package/esm/types/lib/WazoSessionDescriptionHandler.d.ts +1 -1
  38. package/esm/types/lib/WazoSessionDescriptionHandler.d.ts.map +1 -1
  39. package/esm/types/service/IssueReporter.d.ts +1 -0
  40. package/esm/types/service/IssueReporter.d.ts.map +1 -1
  41. package/esm/types/service/getApiClient.d.ts +2 -0
  42. package/esm/types/service/getApiClient.d.ts.map +1 -1
  43. package/esm/types/simple/Auth.d.ts.map +1 -1
  44. package/esm/types/simple/Phone.d.ts +4 -3
  45. package/esm/types/simple/Phone.d.ts.map +1 -1
  46. package/esm/types/simple/Websocket.d.ts.map +1 -1
  47. package/esm/types/simple/index.d.ts +1 -0
  48. package/esm/types/simple/index.d.ts.map +1 -1
  49. package/esm/types/utils/Emitter.d.ts +1 -1
  50. package/esm/types/utils/Heartbeat.d.ts +3 -2
  51. package/esm/types/utils/Heartbeat.d.ts.map +1 -1
  52. package/esm/types/utils/api-requester.d.ts +5 -1
  53. package/esm/types/utils/api-requester.d.ts.map +1 -1
  54. package/esm/types/web-rtc-client.d.ts +5 -29
  55. package/esm/types/web-rtc-client.d.ts.map +1 -1
  56. package/esm/types/websocket-client.d.ts +5 -1
  57. package/esm/types/websocket-client.d.ts.map +1 -1
  58. package/esm/utils/__tests__/api-requester.test.js +7 -2
  59. package/esm/utils/api-requester.js +23 -5
  60. package/esm/web-rtc-client.js +69 -40
  61. package/esm/websocket-client.js +9 -2
  62. package/lib/__tests__/api-client.test.js +7 -1
  63. package/lib/__tests__/web-rtc-client.test.js +16 -0
  64. package/lib/api/chatd.js +23 -7
  65. package/lib/api/dird.js +2 -2
  66. package/lib/api-client.js +4 -0
  67. package/lib/checker/checks/ice-ipv4.js +2 -1
  68. package/lib/domain/Contact.js +15 -8
  69. package/lib/domain/Phone/WebRTCPhone.js +9 -11
  70. package/lib/domain/Session.js +23 -0
  71. package/lib/domain/__tests__/Contact.test.js +175 -50
  72. package/lib/lib/WazoSessionDescriptionHandler.js +15 -12
  73. package/lib/service/IssueReporter.js +12 -2
  74. package/lib/service/__tests__/IssueReporter.test.js +12 -0
  75. package/lib/service/getApiClient.js +12 -1
  76. package/lib/simple/Auth.js +34 -4
  77. package/lib/simple/Phone.js +3 -1
  78. package/lib/simple/Websocket.js +1 -0
  79. package/lib/simple/room/Stream.js +3 -3
  80. package/lib/types/api/chatd.d.ts +1 -1
  81. package/lib/types/api/chatd.d.ts.map +1 -1
  82. package/lib/types/api/dird.d.ts.map +1 -1
  83. package/lib/types/api-client.d.ts +3 -2
  84. package/lib/types/api-client.d.ts.map +1 -1
  85. package/lib/types/checker/checks/ice-ipv4.d.ts.map +1 -1
  86. package/lib/types/domain/Contact.d.ts +1 -1
  87. package/lib/types/domain/Contact.d.ts.map +1 -1
  88. package/lib/types/domain/Phone/WebRTCPhone.d.ts +1 -1
  89. package/lib/types/domain/Phone/WebRTCPhone.d.ts.map +1 -1
  90. package/lib/types/domain/Session.d.ts +2 -0
  91. package/lib/types/domain/Session.d.ts.map +1 -1
  92. package/lib/types/domain/types.d.ts +40 -14
  93. package/lib/types/domain/types.d.ts.map +1 -1
  94. package/lib/types/index.d.ts +2 -0
  95. package/lib/types/index.d.ts.map +1 -1
  96. package/lib/types/lib/WazoSessionDescriptionHandler.d.ts +1 -1
  97. package/lib/types/lib/WazoSessionDescriptionHandler.d.ts.map +1 -1
  98. package/lib/types/service/IssueReporter.d.ts +1 -0
  99. package/lib/types/service/IssueReporter.d.ts.map +1 -1
  100. package/lib/types/service/getApiClient.d.ts +2 -0
  101. package/lib/types/service/getApiClient.d.ts.map +1 -1
  102. package/lib/types/simple/Auth.d.ts.map +1 -1
  103. package/lib/types/simple/Phone.d.ts +4 -3
  104. package/lib/types/simple/Phone.d.ts.map +1 -1
  105. package/lib/types/simple/Websocket.d.ts.map +1 -1
  106. package/lib/types/simple/index.d.ts +1 -0
  107. package/lib/types/simple/index.d.ts.map +1 -1
  108. package/lib/types/utils/Emitter.d.ts +1 -1
  109. package/lib/types/utils/Heartbeat.d.ts +3 -2
  110. package/lib/types/utils/Heartbeat.d.ts.map +1 -1
  111. package/lib/types/utils/api-requester.d.ts +5 -1
  112. package/lib/types/utils/api-requester.d.ts.map +1 -1
  113. package/lib/types/web-rtc-client.d.ts +5 -29
  114. package/lib/types/web-rtc-client.d.ts.map +1 -1
  115. package/lib/types/websocket-client.d.ts +5 -1
  116. package/lib/types/websocket-client.d.ts.map +1 -1
  117. package/lib/utils/__tests__/api-requester.test.js +7 -2
  118. package/lib/utils/api-requester.js +19 -5
  119. package/lib/web-rtc-client.js +67 -39
  120. package/lib/websocket-client.js +8 -2
  121. package/package.json +37 -35
  122. package/src/__tests__/api-client.test.ts +9 -1
  123. package/src/__tests__/web-rtc-client.test.ts +12 -0
  124. package/src/api/chatd.ts +22 -4
  125. package/src/api/dird.ts +3 -2
  126. package/src/api-client.ts +8 -2
  127. package/src/checker/checks/ice-ipv4.ts +2 -1
  128. package/src/domain/Contact.ts +16 -8
  129. package/src/domain/Phone/WebRTCPhone.ts +11 -12
  130. package/src/domain/Session.ts +28 -0
  131. package/src/domain/__tests__/Contact.test.ts +213 -54
  132. package/src/domain/types.ts +42 -14
  133. package/src/lib/WazoSessionDescriptionHandler.ts +15 -11
  134. package/src/service/IssueReporter.ts +13 -2
  135. package/src/service/__tests__/IssueReporter.test.ts +14 -0
  136. package/src/service/getApiClient.ts +12 -0
  137. package/src/simple/Auth.ts +58 -6
  138. package/src/simple/Phone.ts +15 -6
  139. package/src/simple/Websocket.ts +1 -0
  140. package/src/simple/room/Stream.ts +3 -3
  141. package/src/types/global.d.ts +1 -0
  142. package/src/utils/Heartbeat.ts +2 -2
  143. package/src/utils/__tests__/api-requester.test.ts +8 -2
  144. package/src/utils/api-requester.ts +31 -4
  145. package/src/web-rtc-client.ts +84 -70
  146. package/src/websocket-client.ts +14 -1
@@ -1,9 +1,13 @@
1
+ import { AbortController } from 'node-abort-controller';
1
2
  import { Base64 } from 'js-base64';
3
+
2
4
  import WazoApiClient from '../api-client';
3
5
  import ServerError from '../domain/ServerError';
4
6
  import BadResponse from '../domain/BadResponse';
5
7
  import Session from '../domain/Session';
6
8
 
9
+ const controller = new AbortController();
10
+
7
11
  const mockedResponse = {
8
12
  data: {
9
13
  token: 1,
@@ -75,7 +79,7 @@ const server = 'localhost';
75
79
  const authVersion = '0.1';
76
80
  const username = 'wazo';
77
81
  const password = 'zowa';
78
- jest.mock('node-fetch/lib/index', () => {});
82
+ jest.mock('node-fetch/src/index', () => {});
79
83
  const token = '1234';
80
84
 
81
85
  const client = new WazoApiClient({
@@ -112,6 +116,7 @@ describe('With correct API results', () => {
112
116
  expect(global.fetch).toBeCalledWith(`https://${server}/api/auth/${authVersion}/token`, {
113
117
  method: 'post',
114
118
  body: JSON.stringify(data),
119
+ signal: controller.signal,
115
120
  headers,
116
121
  agent: null,
117
122
  });
@@ -126,6 +131,7 @@ describe('With correct API results', () => {
126
131
  expect(global.fetch).toBeCalledWith(`https://${server}/api/auth/${authVersion}/token/${oldToken}`, {
127
132
  method: 'delete',
128
133
  body: null,
134
+ signal: controller.signal,
129
135
  headers: {},
130
136
  agent: null,
131
137
  });
@@ -148,6 +154,7 @@ describe('With unAuthorized API results', () => {
148
154
  expect(global.fetch).toBeCalledWith(`https://${server}/api/auth/${authVersion}/token/${tokenToCheck}`, {
149
155
  method: 'head',
150
156
  body: null,
157
+ signal: controller.signal,
151
158
  headers: {},
152
159
  agent: null,
153
160
  });
@@ -179,6 +186,7 @@ describe('With not found API results', () => {
179
186
  expect(global.fetch).toBeCalledWith(`https://${server}/api/calld/1.0/users/me/voicemails`, {
180
187
  method: 'get',
181
188
  body: null,
189
+ signal: controller.signal,
182
190
  headers: {
183
191
  'X-Auth-Token': token,
184
192
  'Content-Type': 'application/json',
@@ -1,6 +1,18 @@
1
1
  import WebRTCClient from '../web-rtc-client';
2
2
 
3
3
  jest.mock('sip.js/lib/platform/web/transport');
4
+ jest.mock('sip.js/lib/api/user-agent', () => ({
5
+ start: () => {},
6
+ UserAgent: class UserAgent {
7
+ // @ts-ignore
8
+ static makeURI = () => {};
9
+
10
+ // @ts-ignore
11
+ start = () => {};
12
+
13
+ transport = {};
14
+ },
15
+ }));
4
16
 
5
17
  // @ts-expect-error
6
18
  const client = new WebRTCClient({});
package/src/api/chatd.ts CHANGED
@@ -38,7 +38,7 @@ export interface ChatD {
38
38
  getState: (contactUuid: UUID) => Promise<string>;
39
39
  getContactStatusInfo: (contactUuid: UUID) => Promise<PresenceResponse>;
40
40
  getLineState: (contactUuid: UUID) => Promise<string>;
41
- getMultipleLineState: (contactUuids: Array<UUID> | null | undefined) => Promise<string>;
41
+ getMultipleLineState: (contactUuids: Array<UUID> | null | undefined) => Promise<Array<PresenceResponse>>;
42
42
  getUserRooms: () => Promise<Array<ChatRoom>>;
43
43
  createRoom: (name: string, users: Array<ChatUser>) => Promise<ChatRoom>;
44
44
  getRoomMessages: (roomUuid: string, params?: GetMessagesOptions) => Promise<Array<ChatMessage>>;
@@ -46,6 +46,9 @@ export interface ChatD {
46
46
  getMessages: (options: GetMessagesOptions) => Promise<ChatMessage>;
47
47
  }
48
48
 
49
+ // split contact status retrieval to avoid `414 Request-URI Too Large`.
50
+ const MAX_PRESENCE_FETCHED = 30;
51
+
49
52
  export default ((client: ApiRequester, baseUrl: string): ChatD => ({
50
53
  updateState: (contactUuid: UUID, state: string): Promise<boolean> => client.put(`${baseUrl}/users/${contactUuid}/presences`, {
51
54
  state,
@@ -60,11 +63,26 @@ export default ((client: ApiRequester, baseUrl: string): ChatD => ({
60
63
  getState: async (contactUuid: UUID): Promise<string> => client.get(`${baseUrl}/users/${contactUuid}/presences`).then((response: PresenceResponse) => response.state),
61
64
  getContactStatusInfo: async (contactUuid: UUID): Promise<PresenceResponse> => client.get(`${baseUrl}/users/${contactUuid}/presences`).then((response: PresenceResponse) => response),
62
65
  getLineState: async (contactUuid: UUID): Promise<string> => client.get(`${baseUrl}/users/${contactUuid}/presences`).then((response: PresenceResponse) => Profile.getLinesState(response.lines)),
63
- getMultipleLineState: async (contactUuids: Array<UUID> | null | undefined): Promise<string> => {
66
+ async getMultipleLineState(contactUuids: Array<UUID> | null | undefined): Promise<Array<PresenceResponse>> {
64
67
  const body: any = {};
68
+ const uuids: Array<UUID> = contactUuids || [];
69
+
70
+ if (uuids.length > MAX_PRESENCE_FETCHED) {
71
+ const requests = uuids.reduce((acc, _, i) => {
72
+ if (i % MAX_PRESENCE_FETCHED === 0) {
73
+ // @ts-ignore
74
+ acc.push(this.getMultipleLineState(uuids.slice(i, i + MAX_PRESENCE_FETCHED)));
75
+ }
76
+ return acc;
77
+ }, []);
78
+
79
+ const splittedStatuses = await Promise.all(requests);
80
+
81
+ return splittedStatuses.flat();
82
+ }
65
83
 
66
- if (contactUuids && contactUuids.length) {
67
- body.user_uuid = contactUuids.join(',');
84
+ if (uuids.length) {
85
+ body.user_uuid = uuids.join(',');
68
86
  }
69
87
 
70
88
  return client.get(`${baseUrl}/users/presences`, body).then((response: PresenceListResponse) => response.items);
package/src/api/dird.ts CHANGED
@@ -5,6 +5,7 @@ import Contact from '../domain/Contact';
5
5
  import type { NewContact } from '../domain/Contact';
6
6
  import type { DirectorySource, DirectorySources } from '../domain/DirectorySource';
7
7
  import type { Sources } from '../index';
8
+ import { ContactsResponse } from '../index';
8
9
 
9
10
  const getContactPayload = (contact: NewContact | Contact) => ({
10
11
  email: contact.email,
@@ -51,9 +52,9 @@ type ContactSearchQueryParams = {
51
52
  uuid?: string;
52
53
  } | null;
53
54
  export default ((client: ApiRequester, baseUrl: string): DirD => ({
54
- search: (context: string, term: string): Promise<Array<Contact>> => client.get(`${baseUrl}/directories/lookup/${context}`, {
55
+ search: (context: string, term: string, offset = 0, limit: (number | null) = null): Promise<Array<Contact>> => client.get(`${baseUrl}/directories/lookup/${context}`, {
55
56
  term,
56
- }).then(Contact.parseMany),
57
+ }).then((response: ContactsResponse) => Contact.parseMany(response, offset, limit)),
57
58
  listPersonalContacts: (queryParams: ContactSearchQueryParams = null): Promise<Array<Contact>> => client.get(`${baseUrl}/personal`, queryParams).then((response: any) => Contact.parseManyPersonal(response.items)),
58
59
  fetchPersonalContact: (contactUuid: string): Promise<Contact> => client.get(`${baseUrl}/personal/${contactUuid}`).then(Contact.parsePersonal),
59
60
  addContact: (contact: NewContact): Promise<Contact> => client.post(`${baseUrl}/personal`, getContactPayload(contact)).then(Contact.parsePersonal),
package/src/api-client.ts CHANGED
@@ -125,12 +125,12 @@ export default class ApiClient {
125
125
  this.initializeEndpoints();
126
126
  }
127
127
 
128
- async forceRefreshToken() {
128
+ async forceRefreshToken(): Promise<string | null> {
129
129
  logger.info('forcing refresh token, calling callback');
130
130
  return this.refreshTokenCallback();
131
131
  }
132
132
 
133
- async refreshTokenCallback() {
133
+ async refreshTokenCallback(): Promise<string | null> {
134
134
  logger.info('refresh token callback called', {
135
135
  refreshToken: this.refreshToken,
136
136
  refreshBackend: this.refreshBackend,
@@ -167,6 +167,8 @@ export default class ApiClient {
167
167
  this.onRefreshTokenError(error);
168
168
  }
169
169
  }
170
+
171
+ return null;
170
172
  }
171
173
 
172
174
  setToken(token: string) {
@@ -181,6 +183,10 @@ export default class ApiClient {
181
183
  this.refreshToken = refreshToken;
182
184
  }
183
185
 
186
+ setRequestTimeout(requestTimeout: number) {
187
+ this.client.setRequestTimeout(requestTimeout);
188
+ }
189
+
184
190
  setClientId(clientId: string | null | undefined) {
185
191
  this.client.clientId = clientId;
186
192
  }
@@ -16,7 +16,8 @@ export default {
16
16
  name: 'Non IP v4 ice',
17
17
  check: (server: string, session: any, externalAppConfig: Record<string, any>) => new Promise((resolve, reject) => {
18
18
  if (typeof MediaStream === 'undefined') {
19
- return resolve('Skipped on node');
19
+ resolve('Skipped on node');
20
+ return;
20
21
  }
21
22
 
22
23
  const mobile = isMobile();
@@ -326,12 +326,13 @@ export default class Contact {
326
326
  return aLastName.localeCompare(bLastName);
327
327
  }
328
328
 
329
- static parseMany(response: ContactsResponse): Array<Contact> {
330
- if (!response || !response.results) {
329
+ static parseMany(response: ContactsResponse, offset = 0, limit: (number | null) = null): Array<Contact> {
330
+ if (!response || !response.results || limit === 0) {
331
331
  return [];
332
332
  }
333
333
 
334
- return response.results.map(r => Contact.parse(r, response.column_types));
334
+ const results = limit !== null && limit > 0 ? response.results.slice(offset, limit) : offset > 0 ? response.results.slice(offset) : response.results;
335
+ return results.map(r => Contact.parse(r, response.column_types));
335
336
  }
336
337
 
337
338
  static manyGraphQlWithNumbersParser(numbers: string[]): (...args: Array<any>) => any {
@@ -444,13 +445,20 @@ export default class Contact {
444
445
  address = `${postalAddress.street} ${postalAddress.city} ${postalAddress.postCode} ${postalAddress.country}`;
445
446
  }
446
447
 
448
+ const firstName = plain.givenName || '';
449
+ const lastName = plain.familyName || '';
450
+ const companyName = plain.company || '';
451
+ const isCompanyAccount = !firstName && !lastName;
452
+ const name = isCompanyAccount ? companyName : `${firstName} ${lastName}`;
453
+
447
454
  return new Contact({
448
- name: `${plain.givenName || ''} ${plain.familyName || ''}`,
455
+ name,
449
456
  number: plain.phoneNumbers.length ? plain.phoneNumbers[0].number : '',
450
- numbers: plain.phoneNumbers.length ? [{
451
- label: 'primary',
452
- number: plain.phoneNumbers[0].number,
453
- }] : [],
457
+ // Make numbers unique
458
+ numbers: [...new Map(plain.phoneNumbers.map(item => [item.number, item])).values()].map((item, idx) => ({
459
+ label: idx === 0 ? 'primary' : 'secondary',
460
+ number: item.number,
461
+ })),
454
462
  email: plain.emailAddresses.length ? plain.emailAddresses[0].email : '',
455
463
  emails: plain.emailAddresses.length ? [{
456
464
  label: 'primary',
@@ -382,8 +382,6 @@ export default class WebRTCPhone extends Emitter implements Phone {
382
382
  delete this.callSessions[sipSessionId];
383
383
  this.eventEmitter.emit(ON_CALL_CANCELED, callSession, elsewhere);
384
384
  };
385
- } else if (sipSession instanceof Invitation) {
386
- console.warn('sipSession._onCancel not found, please update the wazo SDK accordingly');
387
385
  }
388
386
 
389
387
  sipSession.stateChange.addListener((newState: SessionState) => {
@@ -453,11 +451,6 @@ export default class WebRTCPhone extends Emitter implements Phone {
453
451
  return this.eventEmitter.emit(ON_AUDIO_STREAM, stream);
454
452
  }
455
453
 
456
- // not sure this does anything
457
- if (kind === 'video') {
458
- event.track.enabled = false;
459
- }
460
-
461
454
  return this.eventEmitter.emit(ON_VIDEO_STREAM, stream, event.track.id, event, sipSession);
462
455
  };
463
456
 
@@ -509,7 +502,7 @@ export default class WebRTCPhone extends Emitter implements Phone {
509
502
  return screenShareStream;
510
503
  }
511
504
 
512
- async stopScreenSharing(restoreLocalStream = true, callSession?: CallSession | null | undefined): Promise<OutgoingInviteRequest | void> {
505
+ async stopScreenSharing(restoreLocalStream = true, callSession?: CallSession | null | undefined): Promise<OutgoingInviteRequest | void | null> {
513
506
  if (!this.currentScreenShare) {
514
507
  return;
515
508
  }
@@ -517,11 +510,13 @@ export default class WebRTCPhone extends Emitter implements Phone {
517
510
  logger.info('WebRTC phone - stop screen sharing', {
518
511
  restoreLocalStream,
519
512
  });
520
- let reinvited: OutgoingInviteRequest | void;
513
+ let reinvited: OutgoingInviteRequest | null | void = null;
521
514
 
522
515
  try {
523
516
  if (this.currentScreenShare.stream) {
524
- this.currentScreenShare.stream.getTracks().forEach((track: MediaStreamTrack) => track.stop());
517
+ this.currentScreenShare.stream.getTracks()
518
+ .filter((track: MediaStreamTrack) => track.enabled)
519
+ .forEach((track: MediaStreamTrack) => track.stop());
525
520
  }
526
521
 
527
522
  if (restoreLocalStream) {
@@ -552,6 +547,7 @@ export default class WebRTCPhone extends Emitter implements Phone {
552
547
  screensharing: false,
553
548
  }) : null);
554
549
  this.currentScreenShare = null;
550
+
555
551
  return reinvited;
556
552
  }
557
553
 
@@ -1369,12 +1365,15 @@ export default class WebRTCPhone extends Emitter implements Phone {
1369
1365
  logger.info('WebRTC close');
1370
1366
 
1371
1367
  try {
1372
- await Promise.race([this.unregister(), new Promise((resolve, reject) => setTimeout(() => reject(new Error('Unregister, timed out')), 3000))]);
1368
+ await Promise.race([
1369
+ this.unregister(),
1370
+ new Promise((resolve, reject) => setTimeout(() => reject(new Error('Unregister, timed out')), 3000)),
1371
+ ]);
1373
1372
  } catch (e: any) {
1374
1373
  logger.error('WebRTC close, unregister error', e);
1375
1374
  }
1376
1375
 
1377
- this.client.close(force);
1376
+ await this.client.close(force);
1378
1377
  this.unbind();
1379
1378
  this.incomingSessions = [];
1380
1379
  this.currentSipSession = undefined;
@@ -72,6 +72,8 @@ type SessionArguments = {
72
72
  export default class Session {
73
73
  acl: string[];
74
74
 
75
+ contact?: Contact;
76
+
75
77
  token: string;
76
78
 
77
79
  refreshToken: string | null | undefined;
@@ -198,6 +200,32 @@ export default class Session {
198
200
  return this.profile && (this.profile.sipLines || []).find(sipLine => sipLine && !sipLine.isWebRtc());
199
201
  }
200
202
 
203
+ getContact(): Contact {
204
+ if (this.contact) {
205
+ return this.contact;
206
+ }
207
+
208
+ const { email, firstName, lastName, mobileNumber, state, status } = this.profile || {};
209
+ const number = this.primaryNumber() || '';
210
+ const numbers = [{ label: 'primary', number }];
211
+
212
+ if (mobileNumber) {
213
+ numbers.push({ label: 'mobile', number: mobileNumber });
214
+ }
215
+
216
+ return new Contact({
217
+ uuid: this.uuid || '',
218
+ email,
219
+ name: `${firstName} ${lastName}`,
220
+ number,
221
+ numbers,
222
+ state: state || '',
223
+ status,
224
+ backend: 'wazo',
225
+ source: 'default',
226
+ });
227
+ }
228
+
201
229
  primaryContext(): string {
202
230
  if (this.engineVersion) {
203
231
  if (this.hasEngineVersionGte(MINIMUM_WAZO_ENGINE_VERSION_FOR_DEFAULT_CONTEXT)) {
@@ -1,5 +1,138 @@
1
1
  import Contact from '../Contact';
2
2
 
3
+ const genericContactResponse = {
4
+ column_types: [
5
+ 'name',
6
+ 'number',
7
+ 'callable',
8
+ 'voicemail',
9
+ 'favorite',
10
+ 'email',
11
+ 'personal',
12
+ ],
13
+ term: 'a',
14
+ column_headers: [
15
+ 'Nom',
16
+ 'Num\u00e9ro',
17
+ 'Mobile',
18
+ 'Bo\u00eete vocale',
19
+ 'Favoris',
20
+ 'E-mail',
21
+ 'Personnel',
22
+ ],
23
+ results: [],
24
+ };
25
+
26
+ const contact1 = {
27
+ source: 'internal',
28
+ column_values: [
29
+ 'Jonathan Lessard',
30
+ '8020',
31
+ '06800880',
32
+ null,
33
+ false,
34
+ 'jonathan@example.com',
35
+ false,
36
+ ],
37
+ backend: 'wazo',
38
+ relations: {
39
+ user_id: 8,
40
+ xivo_id: '6cd695d2-cdb9-4444-8b2d-27425ab85fa8',
41
+ agent_id: null,
42
+ endpoint_id: 8,
43
+ user_uuid: 'a14dd6d6-547c-434d-bd5c-e882b5b83b54',
44
+ source_entry_id: '8',
45
+ },
46
+ };
47
+
48
+ const contact2 = {
49
+ source: 'internal',
50
+ column_values: [
51
+ 'John Doe',
52
+ '8021',
53
+ '06800881',
54
+ null,
55
+ false,
56
+ 'john@example.com',
57
+ false,
58
+ ],
59
+ backend: 'wazo',
60
+ relations: {
61
+ user_id: 9,
62
+ xivo_id: '6cd695d2-cdb9-4444-8b2d-27425ab85fa9',
63
+ agent_id: null,
64
+ endpoint_id: 9,
65
+ user_uuid: 'a14dd6d6-547c-434d-bd5c-e882b5b83b55',
66
+ source_entry_id: '9',
67
+ },
68
+ };
69
+
70
+ const parsedContact1 = new Contact({
71
+ name: 'Jonathan Lessard',
72
+ number: '8020',
73
+ numbers: [
74
+ {
75
+ label: 'primary',
76
+ number: '8020',
77
+ },
78
+ {
79
+ label: 'secondary',
80
+ number: '06800880',
81
+ },
82
+ ],
83
+ favorited: false,
84
+ email: 'jonathan@example.com',
85
+ emails: [
86
+ {
87
+ label: 'primary',
88
+ email: 'jonathan@example.com',
89
+ },
90
+ ],
91
+ address: '',
92
+ entreprise: '',
93
+ birthday: '',
94
+ note: '',
95
+ endpointId: 8,
96
+ personal: false,
97
+ source: 'internal',
98
+ sourceId: '8',
99
+ uuid: 'a14dd6d6-547c-434d-bd5c-e882b5b83b54',
100
+ backend: 'wazo',
101
+ });
102
+
103
+ const parsedContact2 = new Contact({
104
+ name: 'John Doe',
105
+ number: '8021',
106
+ numbers: [
107
+ {
108
+ label: 'primary',
109
+ number: '8021',
110
+ },
111
+ {
112
+ label: 'secondary',
113
+ number: '06800881',
114
+ },
115
+ ],
116
+ favorited: false,
117
+ email: 'john@example.com',
118
+ emails: [
119
+ {
120
+ label: 'primary',
121
+ email: 'john@example.com',
122
+ },
123
+ ],
124
+ address: '',
125
+ entreprise: '',
126
+ birthday: '',
127
+ note: '',
128
+ endpointId: 9,
129
+ personal: false,
130
+ source: 'internal',
131
+ sourceId: '9',
132
+ uuid: 'a14dd6d6-547c-434d-bd5c-e882b5b83b55',
133
+ backend: 'wazo',
134
+ });
135
+
3
136
  describe('Contact domain', () => {
4
137
  it('is extern when given no uuid', () => {
5
138
  const boba = new Contact({});
@@ -132,10 +265,7 @@ describe('Contact domain', () => {
132
265
  const contact = new Contact({
133
266
  name: 'John Smith',
134
267
  });
135
- const {
136
- firstName,
137
- lastName,
138
- } = contact.separateName();
268
+ const { firstName, lastName } = contact.separateName();
139
269
  expect(firstName).toBe('John');
140
270
  expect(lastName).toBe('Smith');
141
271
  });
@@ -143,10 +273,7 @@ describe('Contact domain', () => {
143
273
  const contact = new Contact({
144
274
  name: 'John Smith',
145
275
  });
146
- const {
147
- firstName,
148
- lastName,
149
- } = contact.separateName();
276
+ const { firstName, lastName } = contact.separateName();
150
277
  expect(firstName).toBe('John');
151
278
  expect(lastName).toBe('Smith');
152
279
  });
@@ -172,67 +299,99 @@ describe('Contact domain', () => {
172
299
  });
173
300
  it('can parse a plain contact to domain', () => {
174
301
  const response = {
175
- column_types: ['name', 'number', 'callable', 'voicemail', 'favorite', 'email', 'personal'],
176
- term: 'a',
177
- column_headers: ['Nom', 'Num\u00e9ro', 'Mobile', 'Bo\u00eete vocale', 'Favoris', 'E-mail', 'Personnel'],
178
- results: [{
179
- source: 'internal',
180
- column_values: ['Jonathan Lessard', '8020', '06800880', null, false, 'contact@nexapp.ca', false],
181
- backend: 'wazo',
182
- relations: {
183
- user_id: 8,
184
- xivo_id: '6cd695d2-cdb9-4444-8b2d-27425ab85fa8',
185
- agent_id: null,
186
- endpoint_id: 8,
187
- user_uuid: 'a14dd6d6-547c-434d-bd5c-e882b5b83b54',
188
- source_entry_id: '8',
189
- },
190
- }],
302
+ ...genericContactResponse,
303
+ results: [contact1],
191
304
  };
192
305
  const contact = Contact.parse(response.results[0], response.column_types);
193
- expect(contact).toEqual(new Contact({
194
- name: 'Jonathan Lessard',
195
- number: '8020',
196
- numbers: [{
197
- label: 'primary',
198
- number: '8020',
199
- }, {
200
- label: 'secondary',
201
- number: '06800880',
202
- }],
203
- favorited: false,
204
- email: 'contact@nexapp.ca',
205
- emails: [{
206
- label: 'primary',
207
- email: 'contact@nexapp.ca',
208
- }],
209
- address: '',
210
- entreprise: '',
211
- birthday: '',
212
- note: '',
213
- endpointId: 8,
214
- personal: false,
215
- source: 'internal',
216
- sourceId: '8',
217
- uuid: 'a14dd6d6-547c-434d-bd5c-e882b5b83b54',
218
- backend: 'wazo',
219
- }));
306
+ expect(contact).toEqual(parsedContact1);
220
307
  });
221
308
  it('should merge 2 contact', () => {
222
- const contact = new Contact({
309
+ const noBirthdayContact = new Contact({
223
310
  uuid: 'uuid-12345',
224
311
  lastActivity: 'yesterday',
225
312
  // @ts-expect-error
226
313
  birthday: null,
227
314
  });
228
- const contact2 = new Contact({
315
+ const noLastActivityContact = new Contact({
229
316
  uuid: 'uuid-12345',
230
317
  // @ts-expect-error
231
318
  lastActivity: null,
232
319
  birthday: 'tomorrow',
233
320
  });
234
- const result = contact.merge(contact2);
321
+ const result = noBirthdayContact.merge(noLastActivityContact);
235
322
  expect(result.lastActivity).toBe('yesterday');
236
323
  expect(result.birthday).toBe('tomorrow');
237
324
  });
325
+ it('returns all contacts when parsing many contacts with no limit', () => {
326
+ const response = {
327
+ ...genericContactResponse,
328
+ results: [contact1, contact2],
329
+ };
330
+
331
+ const contacts = Contact.parseMany(response);
332
+ expect(contacts).toEqual([parsedContact1, parsedContact2]);
333
+ });
334
+ it('returns all contacts when parsing many contacts with a higher limit than number of results', () => {
335
+ const response = {
336
+ ...genericContactResponse,
337
+ results: [contact1, contact2],
338
+ };
339
+
340
+ const contacts = Contact.parseMany(response, 0, 1000);
341
+ expect(contacts).toEqual([parsedContact1, parsedContact2]);
342
+ });
343
+ it('returns limited contacts when parsing many contacts with a smaller limit than number of results', () => {
344
+ const response = {
345
+ ...genericContactResponse,
346
+ results: [contact1],
347
+ };
348
+
349
+ const contacts = Contact.parseMany(response, 0, 1);
350
+ expect(contacts).toEqual([parsedContact1]);
351
+ });
352
+ it('returns all contacts when parsing many contacts with same limit than number of results', () => {
353
+ const response = {
354
+ ...genericContactResponse,
355
+ results: [contact1, contact2],
356
+ };
357
+
358
+ const contacts = Contact.parseMany(response, 0, 2);
359
+ expect(contacts).toEqual([parsedContact1, parsedContact2]);
360
+ });
361
+ it('returns an empty array when parsing many contact with limit of 0', () => {
362
+ const response = {
363
+ ...genericContactResponse,
364
+ results: [contact1, contact2],
365
+ };
366
+
367
+ const contacts = Contact.parseMany(response, 0, 0);
368
+ expect(contacts).toEqual([]);
369
+ });
370
+ it('returns all contacts when parsing many contacts with a negative limit', () => {
371
+ const response = {
372
+ ...genericContactResponse,
373
+ results: [contact1, contact2],
374
+ };
375
+
376
+ const contacts = Contact.parseMany(response, 0, -1);
377
+ expect(contacts).toEqual([parsedContact1, parsedContact2]);
378
+ });
379
+ it('returns all contacts when parsing many contacts with a null limit', () => {
380
+ const response = {
381
+ ...genericContactResponse,
382
+ results: [contact1, contact2],
383
+ };
384
+
385
+ const contacts = Contact.parseMany(response, 0, null);
386
+ expect(contacts).toEqual([parsedContact1, parsedContact2]);
387
+ });
388
+ it('returns all contacts after the first one when parsing many contacts with an offset of 1', () => {
389
+ const response = {
390
+ ...genericContactResponse,
391
+ results: [contact1, contact2],
392
+ };
393
+
394
+ const contacts = Contact.parseMany(response, 1, null);
395
+ expect(contacts).toEqual([parsedContact2]);
396
+ });
238
397
  });