@webex/internal-plugin-dss 3.0.0-bnr.5 → 3.0.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.
@@ -0,0 +1,129 @@
1
+ /*!
2
+ * Copyright (c) 2015-2022 Cisco Systems, Inc. See LICENSE file.
3
+ */
4
+ /* eslint-disable no-underscore-dangle */
5
+
6
+ import {Batcher} from '@webex/webex-core';
7
+
8
+ /**
9
+ * @class
10
+ */
11
+ const DssBatcher = Batcher.extend({
12
+ namespace: 'DSS',
13
+
14
+ props: {
15
+ resource: {
16
+ type: 'string',
17
+ required: true,
18
+ setOnce: true,
19
+ allowNull: false,
20
+ },
21
+ dataPath: {
22
+ type: 'string',
23
+ required: true,
24
+ setOnce: true,
25
+ allowNull: false,
26
+ },
27
+ entitiesFoundPath: {
28
+ type: 'string',
29
+ required: true,
30
+ setOnce: true,
31
+ allowNull: false,
32
+ },
33
+ entitiesNotFoundPath: {
34
+ type: 'string',
35
+ required: true,
36
+ setOnce: true,
37
+ allowNull: false,
38
+ },
39
+ requestKey: {
40
+ type: 'string',
41
+ required: true,
42
+ setOnce: true,
43
+ allowNull: false,
44
+ },
45
+ },
46
+
47
+ /**
48
+ * Submits the DSS request
49
+ * @param {Object} payload
50
+ * @returns {Promise<Array>}
51
+ */
52
+ submitHttpRequest(payload: unknown) {
53
+ return this.parent._request({
54
+ dataPath: this.dataPath,
55
+ foundPath: this.entitiesFoundPath,
56
+ notFoundPath: this.entitiesNotFoundPath,
57
+ resource: this.resource,
58
+ params: {
59
+ lookupValues: payload,
60
+ },
61
+ });
62
+ },
63
+
64
+ /**
65
+ * Actions taken when the http request returns a success
66
+ * @param {Promise<Array>} res
67
+ * @returns {Promise<undefined>}
68
+ */
69
+ handleHttpSuccess(res) {
70
+ const successItems = res.foundArray.map((requestValue, index) => ({
71
+ requestValue,
72
+ entity: res.resultArray[index],
73
+ }));
74
+ const failureItems = res.notFoundArray.map((requestValue) => ({requestValue, entity: null}));
75
+
76
+ return Promise.all(successItems.concat(failureItems).map((item) => this.acceptItem(item)));
77
+ },
78
+
79
+ /**
80
+ * Checks if the item was found
81
+ * @param {Object} item
82
+ * @returns {Promise<Boolean>}
83
+ */
84
+ didItemFail(item) {
85
+ return Promise.resolve(item.entity === null);
86
+ },
87
+
88
+ /**
89
+ * Finds the Defer for the specified item and resolves its promise with null
90
+ * @param {Object} item
91
+ * @returns {Promise<undefined>}
92
+ */
93
+ handleItemFailure(item) {
94
+ return this.getDeferredForResponse(item).then((defer) => {
95
+ defer.resolve(null);
96
+ });
97
+ },
98
+
99
+ /**
100
+ * Finds the Defer for the specified item and resolves its promise
101
+ * @param {Object} item
102
+ * @returns {Promise<undefined>}
103
+ */
104
+ handleItemSuccess(item) {
105
+ return this.getDeferredForResponse(item).then((defer) => {
106
+ defer.resolve(item.entity);
107
+ });
108
+ },
109
+
110
+ /**
111
+ * Returns a promise with the unique key for the item
112
+ * @param {Object} item
113
+ * @returns {Promise}
114
+ */
115
+ fingerprintRequest(item) {
116
+ return Promise.resolve(item);
117
+ },
118
+
119
+ /**
120
+ * Returns a promise with the unique key for the item
121
+ * @param {Object} item
122
+ * @returns {Promise}
123
+ */
124
+ fingerprintResponse(item) {
125
+ return Promise.resolve(item.requestValue);
126
+ },
127
+ });
128
+
129
+ export default DssBatcher;
@@ -0,0 +1,36 @@
1
+ import {Exception} from '@webex/common';
2
+ import {RequestOptions} from './types';
3
+
4
+ interface DssTimeoutErrorParams extends Required<Pick<RequestOptions, 'resource' | 'params'>> {
5
+ requestId: string;
6
+ timeout: number;
7
+ }
8
+
9
+ /**
10
+ * Thrown when an expected DSS respond is not received in a timely manner.
11
+ */
12
+ export class DssTimeoutError extends Exception {
13
+ /**
14
+ * Construct DssTimeoutError
15
+ * @param {DssTimeoutErrorParams} details
16
+ */
17
+ // eslint-disable-next-line no-useless-constructor
18
+ constructor(details: DssTimeoutErrorParams) {
19
+ super(details);
20
+ }
21
+
22
+ /**
23
+ * Parse Error details
24
+ *
25
+ * @param {DssTimeoutErrorParams} details
26
+ * @returns {string}
27
+ */
28
+ parse(details: DssTimeoutErrorParams) {
29
+ return (
30
+ `The DSS did not respond within ${details.timeout} ms.` +
31
+ `\n Request Id: ${details.requestId}` +
32
+ `\n Resource: ${details.resource}` +
33
+ `\n Params: ${JSON.stringify(details.params)}`
34
+ );
35
+ }
36
+ }
package/src/dss.ts CHANGED
@@ -2,17 +2,20 @@
2
2
  /*!
3
3
  * Copyright (c) 2015-2022 Cisco Systems, Inc. See LICENSE file.
4
4
  */
5
+ /* eslint-disable no-underscore-dangle */
5
6
  import uuid from 'uuid';
6
7
  import {WebexPlugin} from '@webex/webex-core';
7
8
  import '@webex/internal-plugin-mercury';
8
9
  import {range, isEqual, get} from 'lodash';
10
+
11
+ import {Timer} from '@webex/common-timers';
9
12
  import type {
10
13
  SearchOptions,
11
14
  LookupDetailOptions,
12
15
  LookupOptions,
13
16
  LookupByEmailOptions,
17
+ SearchPlaceOptions,
14
18
  } from './types';
15
-
16
19
  import {
17
20
  DSS_REGISTERED,
18
21
  DSS_UNREGISTERED,
@@ -21,7 +24,15 @@ import {
21
24
  DSS_SERVICE_NAME,
22
25
  DSS_SEARCH_MERCURY_EVENT,
23
26
  DSS_RESULT,
27
+ LOOKUP_DATA_PATH,
28
+ LOOKUP_FOUND_PATH,
29
+ LOOKUP_NOT_FOUND_PATH,
30
+ LOOKUP_REQUEST_KEY,
31
+ SEARCH_DATA_PATH,
24
32
  } from './constants';
33
+ import DssBatcher from './dss-batcher';
34
+ import {DssTimeoutError} from './dss-errors';
35
+ import {BatcherOptions, RequestOptions, RequestResult} from './types';
25
36
 
26
37
  const DSS = WebexPlugin.extend({
27
38
  namespace: 'DSS',
@@ -34,6 +45,18 @@ const DSS = WebexPlugin.extend({
34
45
  */
35
46
  registered: false,
36
47
 
48
+ /**
49
+ * Initializer
50
+ * @private
51
+ * @param {Object} attrs
52
+ * @param {Object} options
53
+ * @returns {undefined}
54
+ */
55
+ initialize(...args) {
56
+ Reflect.apply(WebexPlugin.prototype.initialize, this, args);
57
+ this.batchers = {};
58
+ },
59
+
37
60
  /**
38
61
  * Explicitly sets up the DSS plugin by connecting to mercury, and listening for DSS events.
39
62
  * @returns {Promise}
@@ -113,6 +136,7 @@ const DSS = WebexPlugin.extend({
113
136
  },
114
137
 
115
138
  /**
139
+ * constructs the event name based on request id
116
140
  * @param {UUID} requestId the id of the request
117
141
  * @returns {string}
118
142
  */
@@ -121,6 +145,7 @@ const DSS = WebexPlugin.extend({
121
145
  },
122
146
 
123
147
  /**
148
+ * Takes incoming data and triggers correct events
124
149
  * @param {Object} data the event data
125
150
  * @returns {undefined}
126
151
  */
@@ -133,40 +158,78 @@ const DSS = WebexPlugin.extend({
133
158
  * Makes the request to the directory service
134
159
  * @param {Object} options
135
160
  * @param {string} options.resource the URL to query
136
- * @param {string} options.params additional params for the body of the request
137
- * @param {string} options.dataPath to path to get the data in the result object
138
- * @returns {Promise} Resolves with an array of entities found
161
+ * @param {Mixed} options.params additional params for the body of the request
162
+ * @param {string} options.dataPath the path to get the data in the result object
163
+ * @param {string} [options.foundPath] the path to get the lookups of the found data
164
+ * @param {string} [options.notFoundPath] the path to get the lookups of the not found data
165
+ * @returns {Promise<Object>} result Resolves with an object
166
+ * @returns {Array} result.resultArray an array of entities found
167
+ * @returns {Array} result.foundArray an array of the lookups of the found entities (if foundPath provided)
168
+ * @returns {Array} result.notFoundArray an array of the lookups of the not found entities (if notFoundPath provided)
169
+ * @throws {DssTimeoutError} when server does not respond in the specified timeframe
139
170
  */
140
- _request(options) {
141
- const {resource, params, dataPath} = options;
171
+ _request(options: RequestOptions): Promise<RequestResult> {
172
+ const {resource, params, dataPath, foundPath, notFoundPath} = options;
142
173
 
174
+ const timeout = this.config.requestTimeout;
143
175
  const requestId = uuid.v4();
144
176
  const eventName = this._getResultEventName(requestId);
145
177
  const result = {};
146
- let expectedSeqNums;
178
+ let expectedSeqNums: string[];
179
+ let notFoundArray: unknown[];
180
+
181
+ return new Promise((resolve, reject) => {
182
+ const timer = new Timer(() => {
183
+ this.stopListening(this, eventName);
184
+ reject(new DssTimeoutError({requestId, timeout, resource, params}));
185
+ }, timeout);
147
186
 
148
- return new Promise((resolve) => {
149
187
  this.listenTo(this, eventName, (data) => {
150
- const resultData = get(data, dataPath);
188
+ timer.reset();
189
+ const resultData = get(data, dataPath, []);
190
+ let found;
151
191
 
152
- result[data.sequence] = resultData;
192
+ if (foundPath) {
193
+ found = get(data, foundPath, []);
194
+ }
195
+ result[data.sequence] = foundPath ? {resultData, found} : {resultData};
153
196
 
154
197
  if (data.finished) {
155
198
  expectedSeqNums = range(data.sequence + 1).map(String);
199
+ if (notFoundPath) {
200
+ notFoundArray = get(data, notFoundPath, []);
201
+ }
156
202
  }
157
203
 
158
204
  const done = isEqual(expectedSeqNums, Object.keys(result));
159
205
 
160
206
  if (done) {
161
- const resultArray = [];
207
+ timer.cancel();
208
+
209
+ const resultArray: any[] = [];
210
+ const foundArray: any[] = [];
211
+
162
212
  expectedSeqNums.forEach((index) => {
163
213
  const seqResult = result[index];
214
+
164
215
  if (seqResult) {
165
- resultArray.push(...seqResult);
216
+ resultArray.push(...seqResult.resultData);
217
+ if (foundPath) {
218
+ foundArray.push(...seqResult.found);
219
+ }
166
220
  }
167
221
  });
168
-
169
- resolve(resultArray);
222
+ const resolveValue: RequestResult = {
223
+ resultArray,
224
+ };
225
+
226
+ if (foundPath) {
227
+ resolveValue.foundArray = foundArray;
228
+ }
229
+ if (notFoundPath) {
230
+ resolveValue.notFoundArray = notFoundArray;
231
+ }
232
+ resolve(resolveValue);
170
233
  this.stopListening(this, eventName);
171
234
  }
172
235
  });
@@ -177,62 +240,128 @@ const DSS = WebexPlugin.extend({
177
240
  contentType: 'application/json',
178
241
  body: {requestId, ...params},
179
242
  });
243
+ timer.start();
180
244
  });
181
245
  },
182
246
 
247
+ /**
248
+ * Uses a batcher to make the request to the directory service
249
+ * @param {Object} options
250
+ * @param {string} options.resource the URL to query
251
+ * @param {string} options.value the id or email to lookup
252
+ * @returns {Promise} Resolves with an array of entities found
253
+ * @throws {DssTimeoutError} when server does not respond in the specified timeframe
254
+ */
255
+ _batchedLookup(options: BatcherOptions) {
256
+ const {resource, lookupValue} = options;
257
+ const dataPath = LOOKUP_DATA_PATH;
258
+ const entitiesFoundPath = LOOKUP_FOUND_PATH;
259
+ const entitiesNotFoundPath = LOOKUP_NOT_FOUND_PATH;
260
+ const requestKey = LOOKUP_REQUEST_KEY;
261
+
262
+ this.batchers[resource] =
263
+ this.batchers[resource] ||
264
+ new DssBatcher({
265
+ resource,
266
+ dataPath,
267
+ entitiesFoundPath,
268
+ entitiesNotFoundPath,
269
+ requestKey,
270
+ parent: this,
271
+ });
272
+
273
+ return this.batchers[resource].request(lookupValue);
274
+ },
275
+
183
276
  /**
184
277
  * Retrieves detailed information about an entity
185
278
  * @param {Object} options
186
279
  * @param {UUID} options.id the id of the entity to lookup
187
- * @returns {Promise} Resolves with an array of entities found
280
+ * @returns {Promise} Resolves with the entity found or null if not found
281
+ * @throws {DssTimeoutError} when server does not respond in the specified timeframe
188
282
  */
189
283
  lookupDetail(options: LookupDetailOptions) {
190
284
  const {id} = options;
191
285
 
286
+ const resource = `/lookup/orgid/${this.webex.internal.device.orgId}/identity/${id}/detail`;
287
+
192
288
  return this._request({
193
- dataPath: 'lookupResult.entities',
194
- resource: `/lookup/orgid/${this.webex.internal.device.orgId}/identity/${id}/detail`,
289
+ dataPath: LOOKUP_DATA_PATH,
290
+ foundPath: LOOKUP_FOUND_PATH,
291
+ resource,
292
+ }).then(({resultArray, foundArray}) => {
293
+ // TODO: find out what is actually returned!
294
+ if (foundArray[0] === id) {
295
+ return resultArray[0];
296
+ }
297
+
298
+ return null;
195
299
  });
196
300
  },
197
301
 
198
302
  /**
199
- * Retrieves basic information about a list entities within an organization
303
+ * Retrieves basic information about an entity within an organization
200
304
  * @param {Object} options
201
- * @param {UUID} options.ids the id of the entity to lookup
202
- * @param {UUID} options.entityProviderType the provider to query (optional)
203
- * @returns {Promise} Resolves with an array of entities found
305
+ * @param {UUID} options.id the id of the entity to lookup
306
+ * @param {UUID} [options.entityProviderType] the provider to query
307
+ * @param {Boolean} options.shouldBatch whether to batch the query, set to false for single immediate result (defaults to true)
308
+ * @returns {Promise} Resolves with the entity found or null if not found
309
+ * @throws {DssTimeoutError} when server does not respond in the specified timeframe
204
310
  */
205
311
  lookup(options: LookupOptions) {
206
- const {ids, entityProviderType} = options;
312
+ const {id, entityProviderType, shouldBatch = true} = options;
207
313
 
208
314
  const resource = entityProviderType
209
315
  ? `/lookup/orgid/${this.webex.internal.device.orgId}/entityprovidertype/${entityProviderType}`
210
316
  : `/lookup/orgid/${this.webex.internal.device.orgId}/identities`;
211
317
 
318
+ if (shouldBatch) {
319
+ return this._batchedLookup({
320
+ resource,
321
+ lookupValue: id,
322
+ });
323
+ }
324
+
212
325
  return this._request({
213
- dataPath: 'lookupResult.entities',
326
+ dataPath: LOOKUP_DATA_PATH,
327
+ foundPath: LOOKUP_FOUND_PATH,
214
328
  resource,
215
329
  params: {
216
- lookupValues: ids,
330
+ [LOOKUP_REQUEST_KEY]: [id],
217
331
  },
332
+ }).then(({resultArray, foundArray}) => {
333
+ if (foundArray[0] === id) {
334
+ return resultArray[0];
335
+ }
336
+
337
+ return null;
218
338
  });
219
339
  },
220
340
 
221
341
  /**
222
- * Retrieves basic information about a list entities within an organization
342
+ * Retrieves basic information about an enitity within an organization
223
343
  * @param {Object} options
224
- * @param {UUID} options.emails the emails of the entities to lookup
225
- * @returns {Promise} Resolves with an array of entities found
344
+ * @param {UUID} options.email the email of the entity to lookup
345
+ * @returns {Promise} Resolves with the entity found or rejects if not found
346
+ * @throws {DssTimeoutError} when server does not respond in the specified timeframe
226
347
  */
227
348
  lookupByEmail(options: LookupByEmailOptions) {
228
- const {emails} = options;
349
+ const {email} = options;
350
+ const resource = `/lookup/orgid/${this.webex.internal.device.orgId}/emails`;
229
351
 
230
352
  return this._request({
231
- dataPath: 'lookupResult.entities',
232
- resource: `/lookup/orgid/${this.webex.internal.device.orgId}/emails`,
353
+ dataPath: LOOKUP_DATA_PATH,
354
+ foundPath: LOOKUP_FOUND_PATH,
355
+ resource,
233
356
  params: {
234
- lookupValues: emails,
357
+ [LOOKUP_REQUEST_KEY]: [email],
235
358
  },
359
+ }).then(({resultArray, foundArray}) => {
360
+ if (foundArray[0] === email) {
361
+ return resultArray[0];
362
+ }
363
+
364
+ return null;
236
365
  });
237
366
  },
238
367
 
@@ -243,18 +372,44 @@ const DSS = WebexPlugin.extend({
243
372
  * @param {string[]} options.queryString A query string that will be transformed into a Directory search filter query. It is used to search the following fields: username, givenName, familyName, displayName and email
244
373
  * @param {number} options.resultSize The maximum number of results returned from each provider
245
374
  * @returns {Promise} Resolves with an array of entities found
375
+ * @throws {DssTimeoutError} when server does not respond in the specified timeframe
246
376
  */
247
377
  search(options: SearchOptions) {
248
378
  const {requestedTypes, resultSize, queryString} = options;
249
379
 
250
380
  return this._request({
251
- dataPath: 'directoryEntities',
381
+ dataPath: SEARCH_DATA_PATH,
252
382
  resource: `/search/orgid/${this.webex.internal.device.orgId}/entities`,
253
383
  params: {
254
384
  queryString,
255
385
  resultSize,
256
386
  requestedTypes,
257
387
  },
388
+ }).then(({resultArray}) => resultArray);
389
+ },
390
+
391
+ /**
392
+ * Search for information about places
393
+ * @param {Object} options
394
+ * @param {string} options.queryString A query string that will be transformed into a Directory search filter query. It is used to search the following fields: placeName, displayName.
395
+ * @param {number} options.resultSize The maximum number of results returned from each provider
396
+ * @returns {Promise} Resolves with an array of entities found
397
+ */
398
+ searchPlaces(options: SearchPlaceOptions) {
399
+ const {resultSize, queryString, isOnlySchedulableRooms} = options;
400
+
401
+ return this._request({
402
+ dataPath: 'directoryEntities',
403
+ resource: `/search/orgid/${this.webex.internal.device.orgId}/places`,
404
+ params: {
405
+ queryString,
406
+ resultSize,
407
+ isOnlySchedulableRooms,
408
+ },
409
+ }).catch((error) => {
410
+ this.logger.error(`DSS->search place#ERROR, search place failure, ${error.message}`);
411
+
412
+ return Promise.reject(error);
258
413
  });
259
414
  },
260
415
  });
package/src/index.ts CHANGED
@@ -1,7 +1,8 @@
1
1
  import {registerInternalPlugin} from '@webex/webex-core';
2
2
 
3
3
  import DSS from './dss';
4
+ import config from './config';
4
5
 
5
- registerInternalPlugin('dss', DSS);
6
+ registerInternalPlugin('dss', DSS, {config});
6
7
 
7
8
  export {default} from './dss';
package/src/types.ts CHANGED
@@ -1,8 +1,21 @@
1
+ export interface RequestOptions {
2
+ resource: string;
3
+ dataPath: string;
4
+ foundPath?: string;
5
+ notFoundPath?: string;
6
+ params?: Record<string, unknown>;
7
+ }
8
+
9
+ export interface RequestResult {
10
+ foundArray?: any[];
11
+ notFoundArray?: any[];
12
+ resultArray: any[];
13
+ }
14
+
1
15
  export interface LookupDetailOptions {
2
16
  id: string;
3
17
  }
4
18
 
5
- // eslint-disable-next-line no-shadow
6
19
  export enum EntityProviderType {
7
20
  CI_USER = 'CI_USER',
8
21
  CI_MACHINE = 'CI_MACHINE',
@@ -11,12 +24,13 @@ export enum EntityProviderType {
11
24
  }
12
25
 
13
26
  export interface LookupOptions {
14
- ids: string[];
27
+ id: string;
15
28
  entityProviderType?: EntityProviderType;
29
+ shouldBatch?: boolean;
16
30
  }
17
31
 
18
32
  export interface LookupByEmailOptions {
19
- emails: string[];
33
+ email: string;
20
34
  }
21
35
 
22
36
  // eslint-disable-next-line no-shadow
@@ -33,3 +47,14 @@ export interface SearchOptions {
33
47
  resultSize: number;
34
48
  queryString: string;
35
49
  }
50
+
51
+ export interface BatcherOptions {
52
+ resource: string;
53
+ lookupValue: string;
54
+ }
55
+
56
+ export interface SearchPlaceOptions {
57
+ resultSize: number;
58
+ queryString: string;
59
+ isOnlySchedulableRooms: boolean;
60
+ }