@wazo/sdk 0.40.8 → 0.41.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.
@@ -76,7 +76,6 @@ const server = 'localhost';
76
76
  const authVersion = '0.1';
77
77
  const username = 'wazo';
78
78
  const password = 'zowa';
79
- jest.mock('node-fetch/src/index', () => {});
80
79
  const token = '1234';
81
80
 
82
81
  const client = new WazoApiClient({
@@ -4,13 +4,8 @@ import Line from '../Line';
4
4
 
5
5
  describe('Session domain', () => {
6
6
  it('can parse a plain session to domain', () => {
7
- // Can't use `new Headers` because it's not supported on node 16.
8
- const headers = {
9
- get: () => 'new-stack',
10
- };
11
-
12
7
  const plain = {
13
- _headers: headers,
8
+ _headers: new Headers({ 'wazo-stack-host': 'new-stack' }),
14
9
  data: {
15
10
  token: 'b93ae6bd-08d7-4001-9e61-057e72bbc4b3',
16
11
  acl: ['calld.lines.*.presences.read', 'calld.switchboards.#', 'calld.transfers.*.complete.update', 'calld.transfers.*.delete', 'calld.transfers.*.read', 'calld.users.*.presences.read', 'calld.users.me.#', 'confd.infos.read', 'confd.users.me.#.read', 'confd.users.me.forwards.*.*', 'confd.users.me.funckeys.*', 'confd.users.me.funckeys.*.*', 'confd.users.me.read', 'confd.users.me.services.*.*', 'confd.users.me.update', 'dird.#.me.read', 'dird.directories.favorites.#', 'dird.directories.lookup.*.headers.read', 'dird.directories.lookup.*.read', 'dird.directories.personal.*.read', 'dird.personal.#', 'events.calls.me', 'events.chat.message.*.me', 'events.config.users.me.#', 'events.statuses.*', 'events.switchboards.#', 'events.transfers.me', 'events.users.me.#', 'events.directory.me.#', 'websocketd', 'call-logd.users.me.cdr.read'],
@@ -2,7 +2,6 @@
2
2
 
3
3
  /* eslint-disable prefer-destructuring, no-param-reassign, no-underscore-dangle */
4
4
  import moment from 'moment';
5
- import { realFetch } from '../utils/api-requester';
6
5
  import isMobile from '../utils/isMobile';
7
6
  import { obfuscateToken } from '../utils/string';
8
7
 
@@ -383,7 +382,7 @@ class IssueReporter {
383
382
  const url = `http${isSecure ? 's' : ''}://${host}${isSecure ? '' : `:${port}`}/${tag}`;
384
383
  const body = Array.isArray(payload) ? `[${payload.map(this._boundParseLoggerBody).join(',')}]` : this._parseLoggerBody(payload as Record<string, any>);
385
384
 
386
- realFetch()(url, {
385
+ fetch(url, {
387
386
  method: 'POST',
388
387
  headers: {
389
388
  Accept: 'application/json',
@@ -4,7 +4,6 @@
4
4
 
5
5
  /* eslint-disable no-console */
6
6
  import IssueReporter from '../IssueReporter';
7
- import { realFetch } from '../../utils/api-requester';
8
7
 
9
8
  jest.mock('../../utils/api-requester');
10
9
  let oldLog: any;
@@ -25,9 +24,7 @@ describe('IssueReporter', () => {
25
24
  });
26
25
  beforeEach(() => {
27
26
  jest.resetAllMocks();
28
- (realFetch as any).mockImplementation(() => () => ({
29
- catch: () => {},
30
- }));
27
+ jest.spyOn(global, 'fetch');
31
28
  });
32
29
  afterAll(() => {
33
30
  console.log = oldLog;
@@ -50,7 +47,7 @@ describe('IssueReporter', () => {
50
47
 
51
48
  IssueReporter._sendToRemoteLogger('info');
52
49
 
53
- expect(realFetch).toHaveBeenCalled();
50
+ expect(fetch).toHaveBeenCalled();
54
51
  });
55
52
  it('should send if verbosity is equal than required', () => {
56
53
  // Same level
@@ -60,7 +57,7 @@ describe('IssueReporter', () => {
60
57
 
61
58
  IssueReporter._sendToRemoteLogger('trace');
62
59
 
63
- expect(realFetch).toHaveBeenCalled();
60
+ expect(fetch).toHaveBeenCalled();
64
61
  });
65
62
  it('should not send if verbosity is lower than required', () => {
66
63
  // Lower level
@@ -70,7 +67,7 @@ describe('IssueReporter', () => {
70
67
 
71
68
  IssueReporter._sendToRemoteLogger('trace');
72
69
 
73
- expect(realFetch).not.toHaveBeenCalled();
70
+ expect(fetch).not.toHaveBeenCalled();
74
71
  });
75
72
  it('should log extra data', () => {
76
73
  jest.spyOn(IssueReporter, '_sendToRemoteLogger').mockImplementation(() => {});
@@ -53,8 +53,7 @@ class Auth {
53
53
 
54
54
  BACKEND_LDAP: string;
55
55
 
56
- // Useful to avoid the check of the header by doing an extra request to the stack
57
- shouldCheckUserUuidHeader: boolean;
56
+ usingEdgeServer: boolean | undefined;
58
57
 
59
58
  constructor() {
60
59
  this.expiration = DETAULT_EXPIRATION;
@@ -62,7 +61,6 @@ class Auth {
62
61
  this.minSubscriptionType = null;
63
62
  this.BACKEND_WAZO = DEFAULT_BACKEND_USER;
64
63
  this.BACKEND_LDAP = BACKEND_LDAP_USER;
65
- this.shouldCheckUserUuidHeader = true;
66
64
  }
67
65
 
68
66
  init(clientId: string, expiration: number, minSubscriptionType: number | null | undefined, authorizationName: string | null, mobile: boolean): void {
@@ -210,6 +208,7 @@ class Auth {
210
208
  setRefreshToken(null);
211
209
  this.session = null;
212
210
  this.authenticated = false;
211
+ this.usingEdgeServer = undefined;
213
212
  setFetchOptions({});
214
213
  }
215
214
 
@@ -318,27 +317,54 @@ class Auth {
318
317
  return `${this.getFirstName()} ${this.getLastName()}`;
319
318
  }
320
319
 
321
- async setHttpUserUuidHeader(uuid: string | null | undefined) {
322
- if (!uuid) {
323
- return;
324
- }
325
- const headers = {
320
+ _getHttpUserUuidHeaders(uuid:string) {
321
+ return {
326
322
  ...(getFetchOptions()?.headers || {}),
327
323
  'X-User-UUID': uuid,
328
324
  };
325
+ }
326
+
327
+ setHttpUserUuidHeader(uuid: string) {
328
+ logger.info('Setting http header user uuid', { uuid });
329
+
330
+ if (!uuid) {
331
+ logger.warn('attempting to set a null value to user uuid header');
332
+ return;
333
+ }
334
+
335
+ const headers = this._getHttpUserUuidHeaders(uuid);
336
+
337
+ setFetchOptions({
338
+ ...getFetchOptions(),
339
+ headers,
340
+ });
341
+ }
342
+
343
+ async checkHttpUserUuidHeader(uuid: string | null | undefined) {
344
+ logger.info('Checking user uuid http header', { uuid });
345
+
346
+ if (!uuid) {
347
+ return;
348
+ }
349
+
350
+ const headers = this._getHttpUserUuidHeaders(uuid);
329
351
 
330
352
  try {
331
- if (this.shouldCheckUserUuidHeader) {
332
- await getApiClient().client.head('auth/0.1/status', null, headers);
353
+ const response: Response = await getApiClient().client.head('auth/0.1/status', null, headers, (r: any) => r);
354
+
355
+ const allowsUserUuidHeader = response.headers.get('access-control-allow-headers')?.includes('X-User-UUID');
356
+
357
+ if (this.mobile && !allowsUserUuidHeader) {
358
+ throw new Error('Server does not allow user UUID header on mobile');
333
359
  }
334
360
 
335
361
  // If the previous request went well, it means that the header is accepted
336
- setFetchOptions({
337
- ...getFetchOptions(),
338
- headers,
339
- });
340
- } catch (_) {
341
- // Nothing to do
362
+ this.setHttpUserUuidHeader(uuid);
363
+ this.usingEdgeServer = true;
364
+ logger.info('Setting usingEdgeServer value to TRUE', { requestHeaders: headers, responseHeaders: response.headers, allowsUserUuidHeader });
365
+ } catch (e) {
366
+ this.usingEdgeServer = false;
367
+ logger.info('Setting usingEdgeServer to FALSE', { justification: e });
342
368
  }
343
369
  }
344
370
 
@@ -348,12 +374,17 @@ class Auth {
348
374
  }
349
375
 
350
376
  const session = rawSession;
351
- await this.setHttpUserUuidHeader(session.uuid);
352
377
 
353
378
  if (!session) {
354
379
  return null;
355
380
  }
356
381
 
382
+ if (this.usingEdgeServer) {
383
+ this.setHttpUserUuidHeader(session.uuid as string);
384
+ } else if (typeof this.usingEdgeServer === 'undefined') {
385
+ await this.checkHttpUserUuidHeader(session.uuid);
386
+ }
387
+
357
388
  setApiToken(session.token);
358
389
 
359
390
  if (session.refreshToken) {
@@ -79,8 +79,6 @@ describe('Retrieving headers', () => {
79
79
  });
80
80
  describe('Calling fetch', () => {
81
81
  it('should call fetch without body but query string in get method', async () => {
82
- jest.mock('node-fetch/src/index', () => {});
83
-
84
82
  global.fetch = jest.fn(() => Promise.resolve({
85
83
  json: () => Promise.resolve({}),
86
84
  headers: {
@@ -108,7 +106,6 @@ describe('Calling fetch', () => {
108
106
 
109
107
  describe('With a refresh token', () => {
110
108
  it('should retry the call with a new token', async () => {
111
- jest.mock('node-fetch/src/index', () => {});
112
109
  let calls = 0;
113
110
 
114
111
  global.fetch = jest.fn(() => {
@@ -1,12 +1,7 @@
1
- /* global window */
2
- /* eslint-disable global-require */
3
- /* eslint-disable import/no-dynamic-require */
4
1
  import { Base64 } from 'js-base64';
5
- import { AbortController as NodeAbortController } from 'node-abort-controller';
6
2
 
7
3
  import BadResponse from '../domain/BadResponse';
8
4
  import ServerError from '../domain/ServerError';
9
- import isMobile from './isMobile';
10
5
  import type { Token } from '../domain/types';
11
6
  import IssueReporter from '../service/IssueReporter';
12
7
 
@@ -23,32 +18,6 @@ const methods = ['head', 'get', 'post', 'put', 'delete', 'options'];
23
18
  const logger = IssueReporter ? IssueReporter.loggerFor('api') : console;
24
19
  const REQUEST_TIMEOUT_MS = 300 * 1000; // 300s like the Chrome engine default value.
25
20
 
26
- // Use a function here to be able to mock it in tests
27
- export const realFetch = () => {
28
- if (typeof fetch !== 'undefined') {
29
- return fetch;
30
- }
31
-
32
- if (typeof document !== 'undefined' && window.fetch) {
33
- // Browser
34
- return window.fetch;
35
- }
36
-
37
- if (isMobile()) {
38
- // React native
39
- return fetch;
40
- }
41
-
42
- // nodejs
43
- // this package is disabled for react-native in package.json because it requires nodejs modules
44
- // Used to trick the optimizer to avoid requiring `node-fetch/lib/index` directly
45
- // It causes to require it on browsers when delivered by a nodejs engine (cf: vitejs).
46
- try {
47
- return require(Math.random() >= 0 ? 'node-fetch/lib/index' : '');
48
- } catch (e: any) {
49
- return fetch;
50
- }
51
- };
52
21
  export default class ApiRequester {
53
22
  server: string;
54
23
 
@@ -167,9 +136,9 @@ export default class ApiRequester {
167
136
 
168
137
  const isHead = method === 'head';
169
138
  const hasEmptyResponse = method === 'delete' || isHead;
170
- const newParse = hasEmptyResponse ? ApiRequester.successResponseParser : parse;
139
+ const newParse = hasEmptyResponse && parse === ApiRequester.defaultParser ? ApiRequester.successResponseParser : parse;
171
140
  const fetchOptions = { ...(this.fetchOptions || {}) };
172
- const controller = typeof AbortController !== 'undefined' ? new AbortController() : new NodeAbortController();
141
+ const controller = new AbortController();
173
142
  const extraHeaders = fetchOptions.headers || {};
174
143
  delete fetchOptions.headers;
175
144
  const options = {
@@ -192,7 +161,7 @@ export default class ApiRequester {
192
161
  }
193
162
 
194
163
  const start = new Date();
195
- const requestPromise = realFetch()(url, options).then((response: any) => {
164
+ const requestPromise = fetch(url, options).then((response: any) => {
196
165
  const contentType = response.headers.get('content-type') || '';
197
166
  const isJson = contentType.indexOf('application/json') !== -1;
198
167
  IssueReporter.logRequest(url, options, response, start);