@webex/webex-core 2.59.3-next.1 → 2.59.4

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 (189) hide show
  1. package/.eslintrc.js +6 -6
  2. package/README.md +79 -79
  3. package/babel.config.js +3 -3
  4. package/dist/config.js +24 -24
  5. package/dist/config.js.map +1 -1
  6. package/dist/credentials-config.js +56 -56
  7. package/dist/credentials-config.js.map +1 -1
  8. package/dist/index.js.map +1 -1
  9. package/dist/interceptors/auth.js +28 -28
  10. package/dist/interceptors/auth.js.map +1 -1
  11. package/dist/interceptors/default-options.js +24 -24
  12. package/dist/interceptors/default-options.js.map +1 -1
  13. package/dist/interceptors/embargo.js +9 -9
  14. package/dist/interceptors/embargo.js.map +1 -1
  15. package/dist/interceptors/network-timing.js +19 -19
  16. package/dist/interceptors/network-timing.js.map +1 -1
  17. package/dist/interceptors/payload-transformer.js +19 -19
  18. package/dist/interceptors/payload-transformer.js.map +1 -1
  19. package/dist/interceptors/rate-limit.js +40 -40
  20. package/dist/interceptors/rate-limit.js.map +1 -1
  21. package/dist/interceptors/redirect.js +13 -13
  22. package/dist/interceptors/redirect.js.map +1 -1
  23. package/dist/interceptors/request-event.js +23 -23
  24. package/dist/interceptors/request-event.js.map +1 -1
  25. package/dist/interceptors/request-logger.js +13 -13
  26. package/dist/interceptors/request-logger.js.map +1 -1
  27. package/dist/interceptors/request-timing.js +23 -23
  28. package/dist/interceptors/request-timing.js.map +1 -1
  29. package/dist/interceptors/response-logger.js +19 -19
  30. package/dist/interceptors/response-logger.js.map +1 -1
  31. package/dist/interceptors/user-agent.js +29 -29
  32. package/dist/interceptors/user-agent.js.map +1 -1
  33. package/dist/interceptors/webex-tracking-id.js +15 -15
  34. package/dist/interceptors/webex-tracking-id.js.map +1 -1
  35. package/dist/interceptors/webex-user-agent.js +13 -13
  36. package/dist/interceptors/webex-user-agent.js.map +1 -1
  37. package/dist/lib/batcher.js +83 -83
  38. package/dist/lib/batcher.js.map +1 -1
  39. package/dist/lib/credentials/credentials.js +103 -103
  40. package/dist/lib/credentials/credentials.js.map +1 -1
  41. package/dist/lib/credentials/grant-errors.js +17 -17
  42. package/dist/lib/credentials/grant-errors.js.map +1 -1
  43. package/dist/lib/credentials/index.js +2 -2
  44. package/dist/lib/credentials/index.js.map +1 -1
  45. package/dist/lib/credentials/scope.js +11 -11
  46. package/dist/lib/credentials/scope.js.map +1 -1
  47. package/dist/lib/credentials/token-collection.js +2 -2
  48. package/dist/lib/credentials/token-collection.js.map +1 -1
  49. package/dist/lib/credentials/token.js +145 -145
  50. package/dist/lib/credentials/token.js.map +1 -1
  51. package/dist/lib/page.js +49 -49
  52. package/dist/lib/page.js.map +1 -1
  53. package/dist/lib/services/constants.js.map +1 -1
  54. package/dist/lib/services/index.js +2 -2
  55. package/dist/lib/services/index.js.map +1 -1
  56. package/dist/lib/services/interceptors/server-error.js +9 -9
  57. package/dist/lib/services/interceptors/server-error.js.map +1 -1
  58. package/dist/lib/services/interceptors/service.js +24 -24
  59. package/dist/lib/services/interceptors/service.js.map +1 -1
  60. package/dist/lib/services/metrics.js.map +1 -1
  61. package/dist/lib/services/service-catalog.js +104 -104
  62. package/dist/lib/services/service-catalog.js.map +1 -1
  63. package/dist/lib/services/service-fed-ramp.js.map +1 -1
  64. package/dist/lib/services/service-host.js +134 -134
  65. package/dist/lib/services/service-host.js.map +1 -1
  66. package/dist/lib/services/service-registry.js +175 -175
  67. package/dist/lib/services/service-registry.js.map +1 -1
  68. package/dist/lib/services/service-state.js +38 -38
  69. package/dist/lib/services/service-state.js.map +1 -1
  70. package/dist/lib/services/service-url.js +31 -31
  71. package/dist/lib/services/service-url.js.map +1 -1
  72. package/dist/lib/services/services.js +245 -245
  73. package/dist/lib/services/services.js.map +1 -1
  74. package/dist/lib/stateless-webex-plugin.js +28 -28
  75. package/dist/lib/stateless-webex-plugin.js.map +1 -1
  76. package/dist/lib/storage/decorators.js +27 -27
  77. package/dist/lib/storage/decorators.js.map +1 -1
  78. package/dist/lib/storage/errors.js +4 -4
  79. package/dist/lib/storage/errors.js.map +1 -1
  80. package/dist/lib/storage/index.js.map +1 -1
  81. package/dist/lib/storage/make-webex-plugin-store.js +44 -44
  82. package/dist/lib/storage/make-webex-plugin-store.js.map +1 -1
  83. package/dist/lib/storage/make-webex-store.js +40 -40
  84. package/dist/lib/storage/make-webex-store.js.map +1 -1
  85. package/dist/lib/storage/memory-store-adapter.js +9 -9
  86. package/dist/lib/storage/memory-store-adapter.js.map +1 -1
  87. package/dist/lib/webex-core-plugin-mixin.js +13 -13
  88. package/dist/lib/webex-core-plugin-mixin.js.map +1 -1
  89. package/dist/lib/webex-http-error.js +9 -9
  90. package/dist/lib/webex-http-error.js.map +1 -1
  91. package/dist/lib/webex-internal-core-plugin-mixin.js +13 -13
  92. package/dist/lib/webex-internal-core-plugin-mixin.js.map +1 -1
  93. package/dist/lib/webex-plugin.js +36 -36
  94. package/dist/lib/webex-plugin.js.map +1 -1
  95. package/dist/plugins/logger.js +9 -9
  96. package/dist/plugins/logger.js.map +1 -1
  97. package/dist/webex-core.js +104 -104
  98. package/dist/webex-core.js.map +1 -1
  99. package/dist/webex-internal-core.js +12 -12
  100. package/dist/webex-internal-core.js.map +1 -1
  101. package/jest.config.js +3 -3
  102. package/package.json +19 -20
  103. package/process +1 -1
  104. package/src/config.js +90 -90
  105. package/src/credentials-config.js +212 -212
  106. package/src/index.js +62 -62
  107. package/src/interceptors/auth.js +186 -186
  108. package/src/interceptors/default-options.js +55 -55
  109. package/src/interceptors/embargo.js +43 -43
  110. package/src/interceptors/network-timing.js +54 -54
  111. package/src/interceptors/payload-transformer.js +55 -55
  112. package/src/interceptors/rate-limit.js +169 -169
  113. package/src/interceptors/redirect.js +106 -106
  114. package/src/interceptors/request-event.js +93 -93
  115. package/src/interceptors/request-logger.js +78 -78
  116. package/src/interceptors/request-timing.js +65 -65
  117. package/src/interceptors/response-logger.js +98 -98
  118. package/src/interceptors/user-agent.js +77 -77
  119. package/src/interceptors/webex-tracking-id.js +73 -73
  120. package/src/interceptors/webex-user-agent.js +79 -79
  121. package/src/lib/batcher.js +307 -307
  122. package/src/lib/credentials/credentials.js +552 -552
  123. package/src/lib/credentials/grant-errors.js +92 -92
  124. package/src/lib/credentials/index.js +16 -16
  125. package/src/lib/credentials/scope.js +34 -34
  126. package/src/lib/credentials/token-collection.js +17 -17
  127. package/src/lib/credentials/token.js +559 -559
  128. package/src/lib/page.js +159 -159
  129. package/src/lib/services/constants.js +9 -9
  130. package/src/lib/services/index.js +26 -26
  131. package/src/lib/services/interceptors/server-error.js +48 -48
  132. package/src/lib/services/interceptors/service.js +101 -101
  133. package/src/lib/services/metrics.js +4 -4
  134. package/src/lib/services/service-catalog.js +435 -435
  135. package/src/lib/services/service-fed-ramp.js +4 -4
  136. package/src/lib/services/service-host.js +267 -267
  137. package/src/lib/services/service-registry.js +465 -465
  138. package/src/lib/services/service-state.js +78 -78
  139. package/src/lib/services/service-url.js +124 -124
  140. package/src/lib/services/services.js +1018 -1018
  141. package/src/lib/stateless-webex-plugin.js +98 -98
  142. package/src/lib/storage/decorators.js +220 -220
  143. package/src/lib/storage/errors.js +15 -15
  144. package/src/lib/storage/index.js +10 -10
  145. package/src/lib/storage/make-webex-plugin-store.js +211 -211
  146. package/src/lib/storage/make-webex-store.js +140 -140
  147. package/src/lib/storage/memory-store-adapter.js +79 -79
  148. package/src/lib/webex-core-plugin-mixin.js +114 -114
  149. package/src/lib/webex-http-error.js +61 -61
  150. package/src/lib/webex-internal-core-plugin-mixin.js +107 -107
  151. package/src/lib/webex-plugin.js +222 -222
  152. package/src/plugins/logger.js +60 -60
  153. package/src/webex-core.js +745 -745
  154. package/src/webex-internal-core.js +46 -46
  155. package/test/integration/spec/credentials/credentials.js +139 -139
  156. package/test/integration/spec/credentials/token.js +102 -102
  157. package/test/integration/spec/services/service-catalog.js +838 -838
  158. package/test/integration/spec/services/services.js +1221 -1221
  159. package/test/integration/spec/webex-core.js +178 -178
  160. package/test/unit/spec/_setup.js +44 -44
  161. package/test/unit/spec/credentials/credentials.js +1017 -1017
  162. package/test/unit/spec/credentials/token.js +441 -441
  163. package/test/unit/spec/interceptors/auth.js +521 -521
  164. package/test/unit/spec/interceptors/default-options.js +84 -84
  165. package/test/unit/spec/interceptors/embargo.js +144 -144
  166. package/test/unit/spec/interceptors/network-timing.js +49 -49
  167. package/test/unit/spec/interceptors/payload-transformer.js +155 -155
  168. package/test/unit/spec/interceptors/rate-limit.js +302 -302
  169. package/test/unit/spec/interceptors/redirect.js +102 -102
  170. package/test/unit/spec/interceptors/request-timing.js +92 -92
  171. package/test/unit/spec/interceptors/user-agent.js +76 -76
  172. package/test/unit/spec/interceptors/webex-tracking-id.js +76 -76
  173. package/test/unit/spec/interceptors/webex-user-agent.js +159 -159
  174. package/test/unit/spec/lib/batcher.js +330 -330
  175. package/test/unit/spec/lib/page.js +148 -148
  176. package/test/unit/spec/lib/webex-plugin.js +48 -48
  177. package/test/unit/spec/services/interceptors/server-error.js +204 -204
  178. package/test/unit/spec/services/interceptors/service.js +188 -188
  179. package/test/unit/spec/services/service-catalog.js +194 -194
  180. package/test/unit/spec/services/service-host.js +260 -260
  181. package/test/unit/spec/services/service-registry.js +747 -747
  182. package/test/unit/spec/services/service-state.js +60 -60
  183. package/test/unit/spec/services/service-url.js +258 -258
  184. package/test/unit/spec/services/services.js +348 -348
  185. package/test/unit/spec/storage/persist.js +50 -50
  186. package/test/unit/spec/storage/storage-adapter.js +12 -12
  187. package/test/unit/spec/storage/wait-for-value.js +81 -81
  188. package/test/unit/spec/webex-core.js +253 -253
  189. package/test/unit/spec/webex-internal-core.js +91 -91
package/src/webex-core.js CHANGED
@@ -1,745 +1,745 @@
1
- /*!
2
- * Copyright (c) 2015-2020 Cisco Systems, Inc. See LICENSE file.
3
- */
4
-
5
- import {EventEmitter} from 'events';
6
- import util from 'util';
7
-
8
- import {proxyEvents, retry, transferEvents} from '@webex/common';
9
- import {HttpStatusInterceptor, defaults as requestDefaults} from '@webex/http-core';
10
- import {defaultsDeep, get, isFunction, isString, last, merge, omit, set, unset} from 'lodash';
11
- import AmpState from 'ampersand-state';
12
- import uuid from 'uuid';
13
-
14
- import AuthInterceptor from './interceptors/auth';
15
- import NetworkTimingInterceptor from './interceptors/network-timing';
16
- import PayloadTransformerInterceptor from './interceptors/payload-transformer';
17
- import RedirectInterceptor from './interceptors/redirect';
18
- import RequestEventInterceptor from './interceptors/request-event';
19
- import RequestLoggerInterceptor from './interceptors/request-logger';
20
- import RequestTimingInterceptor from './interceptors/request-timing';
21
- import ResponseLoggerInterceptor from './interceptors/response-logger';
22
- import WebexHttpError from './lib/webex-http-error';
23
- import UserAgentInterceptor from './interceptors/user-agent';
24
- import WebexTrackingIdInterceptor from './interceptors/webex-tracking-id';
25
- import WebexUserAgentInterceptor from './interceptors/webex-user-agent';
26
- import RateLimitInterceptor from './interceptors/rate-limit';
27
- import EmbargoInterceptor from './interceptors/embargo';
28
- import DefaultOptionsInterceptor from './interceptors/default-options';
29
- import config from './config';
30
- import {makeWebexStore} from './lib/storage';
31
- import mixinWebexCorePlugins from './lib/webex-core-plugin-mixin';
32
- import mixinWebexInternalCorePlugins from './lib/webex-internal-core-plugin-mixin';
33
- import WebexInternalCore from './webex-internal-core';
34
-
35
- // TODO replace the Interceptor.create with Reflect.construct (
36
- // Interceptor.create exists because new was really hard to call on an array of
37
- // constructors)
38
- const interceptors = {
39
- WebexTrackingIdInterceptor: WebexTrackingIdInterceptor.create,
40
- RequestEventInterceptor: RequestEventInterceptor.create,
41
- RateLimitInterceptor: RateLimitInterceptor.create,
42
- /* eslint-disable no-extra-parens */
43
- RequestLoggerInterceptor:
44
- process.env.ENABLE_NETWORK_LOGGING || process.env.ENABLE_VERBOSE_NETWORK_LOGGING
45
- ? RequestLoggerInterceptor.create
46
- : undefined,
47
- ResponseLoggerInterceptor:
48
- process.env.ENABLE_NETWORK_LOGGING || process.env.ENABLE_VERBOSE_NETWORK_LOGGING
49
- ? ResponseLoggerInterceptor.create
50
- : undefined,
51
- /* eslint-enable no-extra-parens */
52
- RequestTimingInterceptor: RequestTimingInterceptor.create,
53
- ServiceInterceptor: undefined,
54
- UserAgentInterceptor: UserAgentInterceptor.create,
55
- WebexUserAgentInterceptor: WebexUserAgentInterceptor.create,
56
- AuthInterceptor: AuthInterceptor.create,
57
- KmsDryErrorInterceptor: undefined,
58
- PayloadTransformerInterceptor: PayloadTransformerInterceptor.create,
59
- ConversationInterceptor: undefined,
60
- RedirectInterceptor: RedirectInterceptor.create,
61
- HttpStatusInterceptor() {
62
- return HttpStatusInterceptor.create({
63
- error: WebexHttpError,
64
- });
65
- },
66
- NetworkTimingInterceptor: NetworkTimingInterceptor.create,
67
- EmbargoInterceptor: EmbargoInterceptor.create,
68
- DefaultOptionsInterceptor: DefaultOptionsInterceptor.create,
69
- };
70
-
71
- const preInterceptors = [
72
- 'ResponseLoggerInterceptor',
73
- 'RequestTimingInterceptor',
74
- 'RequestEventInterceptor',
75
- 'WebexTrackingIdInterceptor',
76
- 'RateLimitInterceptor',
77
- ];
78
-
79
- const postInterceptors = [
80
- 'HttpStatusInterceptor',
81
- 'NetworkTimingInterceptor',
82
- 'EmbargoInterceptor',
83
- 'RequestLoggerInterceptor',
84
- 'RateLimitInterceptor',
85
- ];
86
-
87
- const MAX_FILE_SIZE_IN_MB = 2048;
88
-
89
- /**
90
- * @class
91
- */
92
- const WebexCore = AmpState.extend({
93
- version: PACKAGE_VERSION,
94
-
95
- children: {
96
- internal: WebexInternalCore,
97
- },
98
-
99
- constructor(attrs = {}, options) {
100
- if (typeof attrs === 'string') {
101
- attrs = {
102
- credentials: {
103
- supertoken: {
104
- // eslint-disable-next-line camelcase
105
- access_token: attrs,
106
- },
107
- },
108
- };
109
- } else {
110
- // Reminder: order is important here
111
- [
112
- 'credentials.authorization',
113
- 'authorization',
114
- 'credentials.supertoken.supertoken',
115
- 'supertoken',
116
- 'access_token',
117
- 'credentials.authorization.supertoken',
118
- ].forEach((path) => {
119
- const val = get(attrs, path);
120
-
121
- if (val) {
122
- unset(attrs, path);
123
- set(attrs, 'credentials.supertoken', val);
124
- }
125
- });
126
-
127
- ['credentials', 'credentials.authorization'].forEach((path) => {
128
- const val = get(attrs, path);
129
-
130
- if (typeof val === 'string') {
131
- unset(attrs, path);
132
- set(attrs, 'credentials.supertoken', val);
133
- }
134
- });
135
-
136
- if (typeof get(attrs, 'credentials.access_token') === 'string') {
137
- // Send access_token to get validated and corrected and then set it
138
- set(
139
- attrs,
140
- 'credentials.access_token',
141
- this.bearerValidator(get(attrs, 'credentials.access_token').trim())
142
- );
143
-
144
- set(attrs, 'credentials.supertoken', attrs.credentials);
145
- }
146
- }
147
-
148
- return Reflect.apply(AmpState, this, [attrs, options]);
149
- },
150
-
151
- derived: {
152
- boundedStorage: {
153
- deps: [],
154
- fn() {
155
- return makeWebexStore('bounded', this);
156
- },
157
- },
158
- unboundedStorage: {
159
- deps: [],
160
- fn() {
161
- return makeWebexStore('unbounded', this);
162
- },
163
- },
164
- ready: {
165
- deps: ['loaded', 'internal.ready'],
166
- fn() {
167
- return (
168
- this.loaded &&
169
- Object.keys(this._children).reduce(
170
- (ready, name) => ready && this[name] && this[name].ready !== false,
171
- true
172
- )
173
- );
174
- },
175
- },
176
- },
177
-
178
- session: {
179
- config: {
180
- type: 'object',
181
- },
182
- /**
183
- * When true, indicates that the initial load from the storage layer is
184
- * complete
185
- * @instance
186
- * @memberof WebexCore
187
- * @type {boolean}
188
- */
189
- loaded: {
190
- default: false,
191
- type: 'boolean',
192
- },
193
- request: {
194
- setOnce: true,
195
- // It's supposed to be a function, but that's not a type defined in
196
- // Ampersand
197
- type: 'any',
198
- },
199
- sessionId: {
200
- setOnce: true,
201
- type: 'string',
202
- },
203
- },
204
-
205
- /**
206
- * @instance
207
- * @memberof WebexCore
208
- * @param {[type]} args
209
- * @returns {[type]}
210
- */
211
- refresh(...args) {
212
- return this.credentials.refresh(...args);
213
- },
214
-
215
- /**
216
- * Applies the directionally appropriate transforms to the specified object
217
- * @param {string} direction
218
- * @param {Object} object
219
- * @returns {Promise}
220
- */
221
- transform(direction, object) {
222
- const predicates = this.config.payloadTransformer.predicates.filter(
223
- (p) => !p.direction || p.direction === direction
224
- );
225
- const ctx = {
226
- webex: this,
227
- };
228
-
229
- return Promise.all(
230
- predicates.map((p) =>
231
- p.test(ctx, object).then((shouldTransform) => {
232
- if (!shouldTransform) {
233
- return undefined;
234
- }
235
-
236
- return (
237
- p
238
- .extract(object)
239
- // eslint-disable-next-line max-nested-callbacks
240
- .then((target) => ({
241
- name: p.name,
242
- target,
243
- }))
244
- );
245
- })
246
- )
247
- )
248
- .then((data) =>
249
- data
250
- .filter((d) => Boolean(d))
251
- // eslint-disable-next-line max-nested-callbacks
252
- .reduce(
253
- (promise, {name, target, alias}) =>
254
- promise.then(() => {
255
- if (alias) {
256
- return this.applyNamedTransform(direction, alias, target);
257
- }
258
-
259
- return this.applyNamedTransform(direction, name, target);
260
- }),
261
- Promise.resolve()
262
- )
263
- )
264
- .then(() => object);
265
- },
266
-
267
- /**
268
- * Applies the directionally appropriate transform to the specified parameters
269
- * @param {string} direction
270
- * @param {Object} ctx
271
- * @param {string} name
272
- * @returns {Promise}
273
- */
274
- applyNamedTransform(direction, ctx, name, ...rest) {
275
- if (isString(ctx)) {
276
- rest.unshift(name);
277
- name = ctx;
278
- ctx = {
279
- webex: this,
280
- transform: (...args) => this.applyNamedTransform(direction, ctx, ...args),
281
- };
282
- }
283
-
284
- const transforms = ctx.webex.config.payloadTransformer.transforms.filter(
285
- (tx) => tx.name === name && (!tx.direction || tx.direction === direction)
286
- );
287
-
288
- // too many implicit returns on the same line is difficult to interpret
289
- // eslint-disable-next-line arrow-body-style
290
- return transforms
291
- .reduce(
292
- (promise, tx) =>
293
- promise.then(() => {
294
- if (tx.alias) {
295
- return ctx.transform(tx.alias, ...rest);
296
- }
297
-
298
- return Promise.resolve(tx.fn(ctx, ...rest));
299
- }),
300
- Promise.resolve()
301
- )
302
- .then(() => last(rest));
303
- },
304
-
305
- /**
306
- * @private
307
- * @returns {Window}
308
- */
309
- getWindow() {
310
- // eslint-disable-next-line
311
- return window;
312
- },
313
-
314
- /**
315
- * Initializer
316
- *
317
- * @emits WebexCore#loaded
318
- * @emits WebexCore#ready
319
- * @instance
320
- * @memberof WebexCore
321
- * @param {Object} attrs
322
- * @returns {WebexCore}
323
- */
324
- initialize(attrs = {}) {
325
- this.config = merge({}, config, attrs.config);
326
-
327
- // There's some unfortunateness with the way {@link AmpersandState#children}
328
- // get initialized. We'll fire the change:config event so that
329
- // {@link WebexPlugin#initialize()} can use
330
- // `this.listenToOnce(parent, 'change:config', () => {});` to act on config
331
- // during initialization
332
- this.trigger('change:config');
333
-
334
- const onLoaded = () => {
335
- if (this.loaded) {
336
- /**
337
- * Fires when all data has been loaded from the storage layer
338
- * @event loaded
339
- * @instance
340
- * @memberof WebexCore
341
- */
342
- this.trigger('loaded');
343
-
344
- this.stopListening(this, 'change:loaded', onLoaded);
345
- }
346
- };
347
-
348
- // This needs to run on nextTick or we'll never be able to wire up listeners
349
- process.nextTick(() => {
350
- this.listenToAndRun(this, 'change:loaded', onLoaded);
351
- });
352
-
353
- const onReady = () => {
354
- if (this.ready) {
355
- /**
356
- * Fires when all plugins have fully initialized
357
- * @event ready
358
- * @instance
359
- * @memberof WebexCore
360
- */
361
- this.trigger('ready');
362
-
363
- this.stopListening(this, 'change:ready', onReady);
364
- }
365
- };
366
-
367
- // This needs to run on nextTick or we'll never be able to wire up listeners
368
- process.nextTick(() => {
369
- this.listenToAndRun(this, 'change:ready', onReady);
370
- });
371
-
372
- // Make nested events propagate in a consistent manner
373
- Object.keys(this.constructor.prototype._children).forEach((key) => {
374
- this.listenTo(this[key], 'change', (...args) => {
375
- args.unshift(`change:${key}`);
376
- this.trigger(...args);
377
- });
378
- });
379
-
380
- const addInterceptor = (ints, key) => {
381
- const interceptor = interceptors[key];
382
-
383
- if (!isFunction(interceptor)) {
384
- return ints;
385
- }
386
-
387
- ints.push(Reflect.apply(interceptor, this, []));
388
-
389
- return ints;
390
- };
391
-
392
- let ints = [];
393
-
394
- ints = preInterceptors.reduce(addInterceptor, ints);
395
- ints = Object.keys(interceptors)
396
- .filter((key) => !(preInterceptors.includes(key) || postInterceptors.includes(key)))
397
- .reduce(addInterceptor, ints);
398
- ints = postInterceptors.reduce(addInterceptor, ints);
399
-
400
- this.request = requestDefaults({
401
- json: true,
402
- interceptors: ints,
403
- });
404
-
405
- let sessionId = `${get(this, 'config.trackingIdPrefix', 'webex-js-sdk')}_${get(
406
- this,
407
- 'config.trackingIdBase',
408
- uuid.v4()
409
- )}`;
410
-
411
- if (get(this, 'config.trackingIdSuffix')) {
412
- sessionId += `_${get(this, 'config.trackingIdSuffix')}`;
413
- }
414
-
415
- this.sessionId = sessionId;
416
- },
417
-
418
- /**
419
- * setConfig
420
- *
421
- * Allows updating config
422
- *
423
- * @instance
424
- * @memberof WebexCore
425
- * @param {Object} newConfig
426
- * @returns {null}
427
- */
428
- setConfig(newConfig = {}) {
429
- this.config = merge({}, this.config, newConfig);
430
- },
431
-
432
- /**
433
- *
434
- * Check if access token is correctly formated and correct if it's not
435
- * Warn user if token string has errors in it
436
- * @param {string} token
437
- * @returns {string}
438
- */
439
- bearerValidator(token) {
440
- if (token.includes('Bearer') && token.split(' ').length - 1 === 0) {
441
- console.warn(
442
- `Your access token does not have a space between 'Bearer' and the token, please add a space to it or replace it with this already fixed version:\n\n${token
443
- .replace('Bearer', 'Bearer ')
444
- .replace(/\s+/g, ' ')}`
445
- );
446
- console.info(
447
- "Tip: You don't need to add 'Bearer' to the access_token field. The token by itself is fine"
448
- );
449
-
450
- return token.replace('Bearer', 'Bearer ').replace(/\s+/g, ' ');
451
- }
452
- // Allow elseIf return
453
- // eslint-disable-next-line no-else-return
454
- else if (token.split(' ').length - 1 > 1) {
455
- console.warn(
456
- `Your access token has ${
457
- token.split(' ').length - 2
458
- } too many spaces, please use this format:\n\n${token.replace(/\s+/g, ' ')}`
459
- );
460
- console.info(
461
- "Tip: You don't need to add 'Bearer' to the access_token field, the token by itself is fine"
462
- );
463
-
464
- return token.replace(/\s+/g, ' ');
465
- }
466
-
467
- return token.replace(/\s+/g, ' '); // Clean it anyway (just in case)
468
- },
469
-
470
- /**
471
- * @instance
472
- * @memberof WebexPlugin
473
- * @param {number} depth
474
- * @private
475
- * @returns {Object}
476
- */
477
- inspect(depth) {
478
- return util.inspect(
479
- omit(
480
- this.serialize({
481
- props: true,
482
- session: true,
483
- derived: true,
484
- }),
485
- 'boundedStorage',
486
- 'unboundedStorage',
487
- 'request',
488
- 'config'
489
- ),
490
- {depth}
491
- );
492
- },
493
-
494
- /**
495
- * Invokes all `onBeforeLogout` handlers in the scope of their plugin, clears
496
- * all stores, and revokes the access token
497
- * Note: If you're using the sdk in a server environment, you may be more
498
- * interested in {@link `webex.internal.mercury.disconnect()`| Mercury#disconnect()}
499
- * and {@link `webex.internal.device.unregister()`|Device#unregister()}
500
- * or {@link `webex.phone.unregister()`|Phone#unregister}
501
- * @instance
502
- * @memberof WebexCore
503
- * @param {Object} options Passed as the first argument to all
504
- * `onBeforeLogout` handlers
505
- * @returns {Promise}
506
- */
507
- logout(options, ...rest) {
508
- // prefer the refresh token, but for clients that don't have one, fallback
509
- // to the access token
510
- const token =
511
- this.credentials.supertoken &&
512
- (this.credentials.supertoken.refresh_token || this.credentials.supertoken.access_token);
513
-
514
- options = Object.assign({token}, options);
515
-
516
- // onBeforeLogout should be executed in the opposite order in which handlers
517
- // were registered. In that way, wdm unregister() will be above mercury
518
- // disconnect(), but disconnect() will execute first.
519
- // eslint-disable-next-line arrow-body-style
520
- return this.config.onBeforeLogout
521
- .reverse()
522
- .reduce(
523
- (promise, {plugin, fn}) =>
524
- promise.then(() => {
525
- return (
526
- Promise.resolve(
527
- Reflect.apply(fn, this[plugin] || this.internal[plugin], [options, ...rest])
528
- )
529
- // eslint-disable-next-line max-nested-callbacks
530
- .catch((err) => {
531
- this.logger.warn(`onBeforeLogout from plugin ${plugin}: failed`, err);
532
- })
533
- );
534
- }),
535
- Promise.resolve()
536
- )
537
- .then(() => Promise.all([this.boundedStorage.clear(), this.unboundedStorage.clear()]))
538
- .then(() => this.credentials.invalidate(...rest))
539
- .then(
540
- () =>
541
- this.authorization &&
542
- this.authorization.logout &&
543
- this.authorization.logout(options, ...rest)
544
- )
545
- .then(() => this.trigger('client:logout'));
546
- },
547
-
548
- /**
549
- * General purpose wrapper to submit metrics via the metrics plugin (if the
550
- * metrics plugin is installed)
551
- * @instance
552
- * @memberof WebexCore
553
- * @returns {Promise}
554
- */
555
- measure(...args) {
556
- if (this.metrics) {
557
- return this.metrics.sendUnstructured(...args);
558
- }
559
-
560
- return Promise.resolve();
561
- },
562
-
563
- async upload(options) {
564
- if (!options.file) {
565
- return Promise.reject(new Error('`options.file` is required'));
566
- }
567
-
568
- options.phases = options.phases || {};
569
- options.phases.initialize = options.phases.initialize || {};
570
- options.phases.upload = options.phases.upload || {};
571
- options.phases.finalize = options.phases.finalize || {};
572
-
573
- defaultsDeep(
574
- options.phases.initialize,
575
- {
576
- method: 'POST',
577
- body: {
578
- uploadProtocol: 'content-length',
579
- },
580
- },
581
- omit(options, 'file', 'phases')
582
- );
583
-
584
- defaultsDeep(options.phases.upload, {
585
- method: 'PUT',
586
- json: false,
587
- withCredentials: false,
588
- body: options.file,
589
- headers: {
590
- 'x-trans-id': uuid.v4(),
591
- authorization: undefined,
592
- },
593
- });
594
-
595
- defaultsDeep(
596
- options.phases.finalize,
597
- {
598
- method: 'POST',
599
- },
600
- omit(options, 'file', 'phases')
601
- );
602
-
603
- const shunt = new EventEmitter();
604
-
605
- const promise = this._uploadPhaseInitialize(options)
606
- .then(() => {
607
- const p = this._uploadPhaseUpload(options);
608
-
609
- transferEvents('progress', p, shunt);
610
-
611
- return p;
612
- })
613
- .then((...args) => this._uploadPhaseFinalize(options, ...args))
614
- .then((res) => ({...res.body, ...res.headers}));
615
-
616
- proxyEvents(shunt, promise);
617
-
618
- return promise;
619
- },
620
-
621
- _uploadPhaseInitialize: function _uploadPhaseInitialize(options) {
622
- this.logger.debug('client: initiating upload session');
623
-
624
- return this.request(options.phases.initialize)
625
- .then((...args) => {
626
- const fileUploadSizeLimitInBytes =
627
- (args[0].body.fileUploadSizeLimit || MAX_FILE_SIZE_IN_MB) * 1024 * 1024;
628
- const currentFileSizeInBytes = options.file.byteLength;
629
-
630
- if (fileUploadSizeLimitInBytes && fileUploadSizeLimitInBytes < currentFileSizeInBytes) {
631
- return this._uploadAbortSession(currentFileSizeInBytes, ...args);
632
- }
633
-
634
- return this._uploadApplySession(options, ...args);
635
- })
636
- .then((res) => {
637
- this.logger.debug('client: initiated upload session');
638
-
639
- return res;
640
- });
641
- },
642
-
643
- _uploadAbortSession(currentFileSizeInBytes, response) {
644
- this.logger.debug('client: deleting uploaded file');
645
-
646
- return this.request({
647
- method: 'DELETE',
648
- url: response.body.url,
649
- headers: response.options.headers,
650
- }).then(() => {
651
- this.logger.debug('client: deleting uploaded file complete');
652
-
653
- const abortErrorDetails = {
654
- currentFileSizeInBytes,
655
- fileUploadSizeLimitInMB: response.body.fileUploadSizeLimit || MAX_FILE_SIZE_IN_MB,
656
- message: 'file-upload-size-limit-enabled',
657
- };
658
-
659
- return Promise.reject(new Error(`${JSON.stringify(abortErrorDetails)}`));
660
- });
661
- },
662
-
663
- _uploadApplySession(options, res) {
664
- const session = res.body;
665
-
666
- ['upload', 'finalize'].reduce((opts, key) => {
667
- opts[key] = Object.keys(opts[key]).reduce((phaseOptions, phaseKey) => {
668
- if (phaseKey.startsWith('$')) {
669
- phaseOptions[phaseKey.substr(1)] = phaseOptions[phaseKey](session);
670
- Reflect.deleteProperty(phaseOptions, phaseKey);
671
- }
672
-
673
- return phaseOptions;
674
- }, opts[key]);
675
-
676
- return opts;
677
- }, options.phases);
678
- },
679
-
680
- @retry
681
- _uploadPhaseUpload(options) {
682
- this.logger.debug('client: uploading file');
683
-
684
- const promise = this.request(options.phases.upload).then((res) => {
685
- this.logger.debug('client: uploaded file');
686
-
687
- return res;
688
- });
689
-
690
- proxyEvents(options.phases.upload.upload, promise);
691
-
692
- /* istanbul ignore else */
693
- if (process.env.NODE_ENV === 'test') {
694
- promise.on('progress', (event) => {
695
- this.logger.info('upload progress', event.loaded, event.total);
696
- });
697
- }
698
-
699
- return promise;
700
- },
701
-
702
- _uploadPhaseFinalize: function _uploadPhaseFinalize(options) {
703
- this.logger.debug('client: finalizing upload session');
704
-
705
- return this.request(options.phases.finalize).then((res) => {
706
- this.logger.debug('client: finalized upload session');
707
-
708
- return res;
709
- });
710
- },
711
- });
712
-
713
- WebexCore.version = PACKAGE_VERSION;
714
-
715
- mixinWebexInternalCorePlugins(WebexInternalCore, config, interceptors);
716
- mixinWebexCorePlugins(WebexCore, config, interceptors);
717
-
718
- export default WebexCore;
719
-
720
- /**
721
- * @method registerPlugin
722
- * @param {string} name
723
- * @param {function} constructor
724
- * @param {Object} options
725
- * @param {Array<string>} options.proxies
726
- * @param {Object} options.interceptors
727
- * @returns {null}
728
- */
729
- export function registerPlugin(name, constructor, options = {}) {
730
- WebexCore.registerPlugin(name, constructor, options);
731
- }
732
-
733
- /**
734
- * Registers plugins used by internal products that do not talk to public APIs.
735
- * @method registerInternalPlugin
736
- * @param {string} name
737
- * @param {function} constructor
738
- * @param {Object} options
739
- * @param {Object} options.interceptors
740
- * @private
741
- * @returns {null}
742
- */
743
- export function registerInternalPlugin(name, constructor, options) {
744
- WebexInternalCore.registerPlugin(name, constructor, options);
745
- }
1
+ /*!
2
+ * Copyright (c) 2015-2020 Cisco Systems, Inc. See LICENSE file.
3
+ */
4
+
5
+ import {EventEmitter} from 'events';
6
+ import util from 'util';
7
+
8
+ import {proxyEvents, retry, transferEvents} from '@webex/common';
9
+ import {HttpStatusInterceptor, defaults as requestDefaults} from '@webex/http-core';
10
+ import {defaultsDeep, get, isFunction, isString, last, merge, omit, set, unset} from 'lodash';
11
+ import AmpState from 'ampersand-state';
12
+ import uuid from 'uuid';
13
+
14
+ import AuthInterceptor from './interceptors/auth';
15
+ import NetworkTimingInterceptor from './interceptors/network-timing';
16
+ import PayloadTransformerInterceptor from './interceptors/payload-transformer';
17
+ import RedirectInterceptor from './interceptors/redirect';
18
+ import RequestEventInterceptor from './interceptors/request-event';
19
+ import RequestLoggerInterceptor from './interceptors/request-logger';
20
+ import RequestTimingInterceptor from './interceptors/request-timing';
21
+ import ResponseLoggerInterceptor from './interceptors/response-logger';
22
+ import WebexHttpError from './lib/webex-http-error';
23
+ import UserAgentInterceptor from './interceptors/user-agent';
24
+ import WebexTrackingIdInterceptor from './interceptors/webex-tracking-id';
25
+ import WebexUserAgentInterceptor from './interceptors/webex-user-agent';
26
+ import RateLimitInterceptor from './interceptors/rate-limit';
27
+ import EmbargoInterceptor from './interceptors/embargo';
28
+ import DefaultOptionsInterceptor from './interceptors/default-options';
29
+ import config from './config';
30
+ import {makeWebexStore} from './lib/storage';
31
+ import mixinWebexCorePlugins from './lib/webex-core-plugin-mixin';
32
+ import mixinWebexInternalCorePlugins from './lib/webex-internal-core-plugin-mixin';
33
+ import WebexInternalCore from './webex-internal-core';
34
+
35
+ // TODO replace the Interceptor.create with Reflect.construct (
36
+ // Interceptor.create exists because new was really hard to call on an array of
37
+ // constructors)
38
+ const interceptors = {
39
+ WebexTrackingIdInterceptor: WebexTrackingIdInterceptor.create,
40
+ RequestEventInterceptor: RequestEventInterceptor.create,
41
+ RateLimitInterceptor: RateLimitInterceptor.create,
42
+ /* eslint-disable no-extra-parens */
43
+ RequestLoggerInterceptor:
44
+ process.env.ENABLE_NETWORK_LOGGING || process.env.ENABLE_VERBOSE_NETWORK_LOGGING
45
+ ? RequestLoggerInterceptor.create
46
+ : undefined,
47
+ ResponseLoggerInterceptor:
48
+ process.env.ENABLE_NETWORK_LOGGING || process.env.ENABLE_VERBOSE_NETWORK_LOGGING
49
+ ? ResponseLoggerInterceptor.create
50
+ : undefined,
51
+ /* eslint-enable no-extra-parens */
52
+ RequestTimingInterceptor: RequestTimingInterceptor.create,
53
+ ServiceInterceptor: undefined,
54
+ UserAgentInterceptor: UserAgentInterceptor.create,
55
+ WebexUserAgentInterceptor: WebexUserAgentInterceptor.create,
56
+ AuthInterceptor: AuthInterceptor.create,
57
+ KmsDryErrorInterceptor: undefined,
58
+ PayloadTransformerInterceptor: PayloadTransformerInterceptor.create,
59
+ ConversationInterceptor: undefined,
60
+ RedirectInterceptor: RedirectInterceptor.create,
61
+ HttpStatusInterceptor() {
62
+ return HttpStatusInterceptor.create({
63
+ error: WebexHttpError,
64
+ });
65
+ },
66
+ NetworkTimingInterceptor: NetworkTimingInterceptor.create,
67
+ EmbargoInterceptor: EmbargoInterceptor.create,
68
+ DefaultOptionsInterceptor: DefaultOptionsInterceptor.create,
69
+ };
70
+
71
+ const preInterceptors = [
72
+ 'ResponseLoggerInterceptor',
73
+ 'RequestTimingInterceptor',
74
+ 'RequestEventInterceptor',
75
+ 'WebexTrackingIdInterceptor',
76
+ 'RateLimitInterceptor',
77
+ ];
78
+
79
+ const postInterceptors = [
80
+ 'HttpStatusInterceptor',
81
+ 'NetworkTimingInterceptor',
82
+ 'EmbargoInterceptor',
83
+ 'RequestLoggerInterceptor',
84
+ 'RateLimitInterceptor',
85
+ ];
86
+
87
+ const MAX_FILE_SIZE_IN_MB = 2048;
88
+
89
+ /**
90
+ * @class
91
+ */
92
+ const WebexCore = AmpState.extend({
93
+ version: PACKAGE_VERSION,
94
+
95
+ children: {
96
+ internal: WebexInternalCore,
97
+ },
98
+
99
+ constructor(attrs = {}, options) {
100
+ if (typeof attrs === 'string') {
101
+ attrs = {
102
+ credentials: {
103
+ supertoken: {
104
+ // eslint-disable-next-line camelcase
105
+ access_token: attrs,
106
+ },
107
+ },
108
+ };
109
+ } else {
110
+ // Reminder: order is important here
111
+ [
112
+ 'credentials.authorization',
113
+ 'authorization',
114
+ 'credentials.supertoken.supertoken',
115
+ 'supertoken',
116
+ 'access_token',
117
+ 'credentials.authorization.supertoken',
118
+ ].forEach((path) => {
119
+ const val = get(attrs, path);
120
+
121
+ if (val) {
122
+ unset(attrs, path);
123
+ set(attrs, 'credentials.supertoken', val);
124
+ }
125
+ });
126
+
127
+ ['credentials', 'credentials.authorization'].forEach((path) => {
128
+ const val = get(attrs, path);
129
+
130
+ if (typeof val === 'string') {
131
+ unset(attrs, path);
132
+ set(attrs, 'credentials.supertoken', val);
133
+ }
134
+ });
135
+
136
+ if (typeof get(attrs, 'credentials.access_token') === 'string') {
137
+ // Send access_token to get validated and corrected and then set it
138
+ set(
139
+ attrs,
140
+ 'credentials.access_token',
141
+ this.bearerValidator(get(attrs, 'credentials.access_token').trim())
142
+ );
143
+
144
+ set(attrs, 'credentials.supertoken', attrs.credentials);
145
+ }
146
+ }
147
+
148
+ return Reflect.apply(AmpState, this, [attrs, options]);
149
+ },
150
+
151
+ derived: {
152
+ boundedStorage: {
153
+ deps: [],
154
+ fn() {
155
+ return makeWebexStore('bounded', this);
156
+ },
157
+ },
158
+ unboundedStorage: {
159
+ deps: [],
160
+ fn() {
161
+ return makeWebexStore('unbounded', this);
162
+ },
163
+ },
164
+ ready: {
165
+ deps: ['loaded', 'internal.ready'],
166
+ fn() {
167
+ return (
168
+ this.loaded &&
169
+ Object.keys(this._children).reduce(
170
+ (ready, name) => ready && this[name] && this[name].ready !== false,
171
+ true
172
+ )
173
+ );
174
+ },
175
+ },
176
+ },
177
+
178
+ session: {
179
+ config: {
180
+ type: 'object',
181
+ },
182
+ /**
183
+ * When true, indicates that the initial load from the storage layer is
184
+ * complete
185
+ * @instance
186
+ * @memberof WebexCore
187
+ * @type {boolean}
188
+ */
189
+ loaded: {
190
+ default: false,
191
+ type: 'boolean',
192
+ },
193
+ request: {
194
+ setOnce: true,
195
+ // It's supposed to be a function, but that's not a type defined in
196
+ // Ampersand
197
+ type: 'any',
198
+ },
199
+ sessionId: {
200
+ setOnce: true,
201
+ type: 'string',
202
+ },
203
+ },
204
+
205
+ /**
206
+ * @instance
207
+ * @memberof WebexCore
208
+ * @param {[type]} args
209
+ * @returns {[type]}
210
+ */
211
+ refresh(...args) {
212
+ return this.credentials.refresh(...args);
213
+ },
214
+
215
+ /**
216
+ * Applies the directionally appropriate transforms to the specified object
217
+ * @param {string} direction
218
+ * @param {Object} object
219
+ * @returns {Promise}
220
+ */
221
+ transform(direction, object) {
222
+ const predicates = this.config.payloadTransformer.predicates.filter(
223
+ (p) => !p.direction || p.direction === direction
224
+ );
225
+ const ctx = {
226
+ webex: this,
227
+ };
228
+
229
+ return Promise.all(
230
+ predicates.map((p) =>
231
+ p.test(ctx, object).then((shouldTransform) => {
232
+ if (!shouldTransform) {
233
+ return undefined;
234
+ }
235
+
236
+ return (
237
+ p
238
+ .extract(object)
239
+ // eslint-disable-next-line max-nested-callbacks
240
+ .then((target) => ({
241
+ name: p.name,
242
+ target,
243
+ }))
244
+ );
245
+ })
246
+ )
247
+ )
248
+ .then((data) =>
249
+ data
250
+ .filter((d) => Boolean(d))
251
+ // eslint-disable-next-line max-nested-callbacks
252
+ .reduce(
253
+ (promise, {name, target, alias}) =>
254
+ promise.then(() => {
255
+ if (alias) {
256
+ return this.applyNamedTransform(direction, alias, target);
257
+ }
258
+
259
+ return this.applyNamedTransform(direction, name, target);
260
+ }),
261
+ Promise.resolve()
262
+ )
263
+ )
264
+ .then(() => object);
265
+ },
266
+
267
+ /**
268
+ * Applies the directionally appropriate transform to the specified parameters
269
+ * @param {string} direction
270
+ * @param {Object} ctx
271
+ * @param {string} name
272
+ * @returns {Promise}
273
+ */
274
+ applyNamedTransform(direction, ctx, name, ...rest) {
275
+ if (isString(ctx)) {
276
+ rest.unshift(name);
277
+ name = ctx;
278
+ ctx = {
279
+ webex: this,
280
+ transform: (...args) => this.applyNamedTransform(direction, ctx, ...args),
281
+ };
282
+ }
283
+
284
+ const transforms = ctx.webex.config.payloadTransformer.transforms.filter(
285
+ (tx) => tx.name === name && (!tx.direction || tx.direction === direction)
286
+ );
287
+
288
+ // too many implicit returns on the same line is difficult to interpret
289
+ // eslint-disable-next-line arrow-body-style
290
+ return transforms
291
+ .reduce(
292
+ (promise, tx) =>
293
+ promise.then(() => {
294
+ if (tx.alias) {
295
+ return ctx.transform(tx.alias, ...rest);
296
+ }
297
+
298
+ return Promise.resolve(tx.fn(ctx, ...rest));
299
+ }),
300
+ Promise.resolve()
301
+ )
302
+ .then(() => last(rest));
303
+ },
304
+
305
+ /**
306
+ * @private
307
+ * @returns {Window}
308
+ */
309
+ getWindow() {
310
+ // eslint-disable-next-line
311
+ return window;
312
+ },
313
+
314
+ /**
315
+ * Initializer
316
+ *
317
+ * @emits WebexCore#loaded
318
+ * @emits WebexCore#ready
319
+ * @instance
320
+ * @memberof WebexCore
321
+ * @param {Object} attrs
322
+ * @returns {WebexCore}
323
+ */
324
+ initialize(attrs = {}) {
325
+ this.config = merge({}, config, attrs.config);
326
+
327
+ // There's some unfortunateness with the way {@link AmpersandState#children}
328
+ // get initialized. We'll fire the change:config event so that
329
+ // {@link WebexPlugin#initialize()} can use
330
+ // `this.listenToOnce(parent, 'change:config', () => {});` to act on config
331
+ // during initialization
332
+ this.trigger('change:config');
333
+
334
+ const onLoaded = () => {
335
+ if (this.loaded) {
336
+ /**
337
+ * Fires when all data has been loaded from the storage layer
338
+ * @event loaded
339
+ * @instance
340
+ * @memberof WebexCore
341
+ */
342
+ this.trigger('loaded');
343
+
344
+ this.stopListening(this, 'change:loaded', onLoaded);
345
+ }
346
+ };
347
+
348
+ // This needs to run on nextTick or we'll never be able to wire up listeners
349
+ process.nextTick(() => {
350
+ this.listenToAndRun(this, 'change:loaded', onLoaded);
351
+ });
352
+
353
+ const onReady = () => {
354
+ if (this.ready) {
355
+ /**
356
+ * Fires when all plugins have fully initialized
357
+ * @event ready
358
+ * @instance
359
+ * @memberof WebexCore
360
+ */
361
+ this.trigger('ready');
362
+
363
+ this.stopListening(this, 'change:ready', onReady);
364
+ }
365
+ };
366
+
367
+ // This needs to run on nextTick or we'll never be able to wire up listeners
368
+ process.nextTick(() => {
369
+ this.listenToAndRun(this, 'change:ready', onReady);
370
+ });
371
+
372
+ // Make nested events propagate in a consistent manner
373
+ Object.keys(this.constructor.prototype._children).forEach((key) => {
374
+ this.listenTo(this[key], 'change', (...args) => {
375
+ args.unshift(`change:${key}`);
376
+ this.trigger(...args);
377
+ });
378
+ });
379
+
380
+ const addInterceptor = (ints, key) => {
381
+ const interceptor = interceptors[key];
382
+
383
+ if (!isFunction(interceptor)) {
384
+ return ints;
385
+ }
386
+
387
+ ints.push(Reflect.apply(interceptor, this, []));
388
+
389
+ return ints;
390
+ };
391
+
392
+ let ints = [];
393
+
394
+ ints = preInterceptors.reduce(addInterceptor, ints);
395
+ ints = Object.keys(interceptors)
396
+ .filter((key) => !(preInterceptors.includes(key) || postInterceptors.includes(key)))
397
+ .reduce(addInterceptor, ints);
398
+ ints = postInterceptors.reduce(addInterceptor, ints);
399
+
400
+ this.request = requestDefaults({
401
+ json: true,
402
+ interceptors: ints,
403
+ });
404
+
405
+ let sessionId = `${get(this, 'config.trackingIdPrefix', 'webex-js-sdk')}_${get(
406
+ this,
407
+ 'config.trackingIdBase',
408
+ uuid.v4()
409
+ )}`;
410
+
411
+ if (get(this, 'config.trackingIdSuffix')) {
412
+ sessionId += `_${get(this, 'config.trackingIdSuffix')}`;
413
+ }
414
+
415
+ this.sessionId = sessionId;
416
+ },
417
+
418
+ /**
419
+ * setConfig
420
+ *
421
+ * Allows updating config
422
+ *
423
+ * @instance
424
+ * @memberof WebexCore
425
+ * @param {Object} newConfig
426
+ * @returns {null}
427
+ */
428
+ setConfig(newConfig = {}) {
429
+ this.config = merge({}, this.config, newConfig);
430
+ },
431
+
432
+ /**
433
+ *
434
+ * Check if access token is correctly formated and correct if it's not
435
+ * Warn user if token string has errors in it
436
+ * @param {string} token
437
+ * @returns {string}
438
+ */
439
+ bearerValidator(token) {
440
+ if (token.includes('Bearer') && token.split(' ').length - 1 === 0) {
441
+ console.warn(
442
+ `Your access token does not have a space between 'Bearer' and the token, please add a space to it or replace it with this already fixed version:\n\n${token
443
+ .replace('Bearer', 'Bearer ')
444
+ .replace(/\s+/g, ' ')}`
445
+ );
446
+ console.info(
447
+ "Tip: You don't need to add 'Bearer' to the access_token field. The token by itself is fine"
448
+ );
449
+
450
+ return token.replace('Bearer', 'Bearer ').replace(/\s+/g, ' ');
451
+ }
452
+ // Allow elseIf return
453
+ // eslint-disable-next-line no-else-return
454
+ else if (token.split(' ').length - 1 > 1) {
455
+ console.warn(
456
+ `Your access token has ${
457
+ token.split(' ').length - 2
458
+ } too many spaces, please use this format:\n\n${token.replace(/\s+/g, ' ')}`
459
+ );
460
+ console.info(
461
+ "Tip: You don't need to add 'Bearer' to the access_token field, the token by itself is fine"
462
+ );
463
+
464
+ return token.replace(/\s+/g, ' ');
465
+ }
466
+
467
+ return token.replace(/\s+/g, ' '); // Clean it anyway (just in case)
468
+ },
469
+
470
+ /**
471
+ * @instance
472
+ * @memberof WebexPlugin
473
+ * @param {number} depth
474
+ * @private
475
+ * @returns {Object}
476
+ */
477
+ inspect(depth) {
478
+ return util.inspect(
479
+ omit(
480
+ this.serialize({
481
+ props: true,
482
+ session: true,
483
+ derived: true,
484
+ }),
485
+ 'boundedStorage',
486
+ 'unboundedStorage',
487
+ 'request',
488
+ 'config'
489
+ ),
490
+ {depth}
491
+ );
492
+ },
493
+
494
+ /**
495
+ * Invokes all `onBeforeLogout` handlers in the scope of their plugin, clears
496
+ * all stores, and revokes the access token
497
+ * Note: If you're using the sdk in a server environment, you may be more
498
+ * interested in {@link `webex.internal.mercury.disconnect()`| Mercury#disconnect()}
499
+ * and {@link `webex.internal.device.unregister()`|Device#unregister()}
500
+ * or {@link `webex.phone.unregister()`|Phone#unregister}
501
+ * @instance
502
+ * @memberof WebexCore
503
+ * @param {Object} options Passed as the first argument to all
504
+ * `onBeforeLogout` handlers
505
+ * @returns {Promise}
506
+ */
507
+ logout(options, ...rest) {
508
+ // prefer the refresh token, but for clients that don't have one, fallback
509
+ // to the access token
510
+ const token =
511
+ this.credentials.supertoken &&
512
+ (this.credentials.supertoken.refresh_token || this.credentials.supertoken.access_token);
513
+
514
+ options = Object.assign({token}, options);
515
+
516
+ // onBeforeLogout should be executed in the opposite order in which handlers
517
+ // were registered. In that way, wdm unregister() will be above mercury
518
+ // disconnect(), but disconnect() will execute first.
519
+ // eslint-disable-next-line arrow-body-style
520
+ return this.config.onBeforeLogout
521
+ .reverse()
522
+ .reduce(
523
+ (promise, {plugin, fn}) =>
524
+ promise.then(() => {
525
+ return (
526
+ Promise.resolve(
527
+ Reflect.apply(fn, this[plugin] || this.internal[plugin], [options, ...rest])
528
+ )
529
+ // eslint-disable-next-line max-nested-callbacks
530
+ .catch((err) => {
531
+ this.logger.warn(`onBeforeLogout from plugin ${plugin}: failed`, err);
532
+ })
533
+ );
534
+ }),
535
+ Promise.resolve()
536
+ )
537
+ .then(() => Promise.all([this.boundedStorage.clear(), this.unboundedStorage.clear()]))
538
+ .then(() => this.credentials.invalidate(...rest))
539
+ .then(
540
+ () =>
541
+ this.authorization &&
542
+ this.authorization.logout &&
543
+ this.authorization.logout(options, ...rest)
544
+ )
545
+ .then(() => this.trigger('client:logout'));
546
+ },
547
+
548
+ /**
549
+ * General purpose wrapper to submit metrics via the metrics plugin (if the
550
+ * metrics plugin is installed)
551
+ * @instance
552
+ * @memberof WebexCore
553
+ * @returns {Promise}
554
+ */
555
+ measure(...args) {
556
+ if (this.metrics) {
557
+ return this.metrics.sendUnstructured(...args);
558
+ }
559
+
560
+ return Promise.resolve();
561
+ },
562
+
563
+ async upload(options) {
564
+ if (!options.file) {
565
+ return Promise.reject(new Error('`options.file` is required'));
566
+ }
567
+
568
+ options.phases = options.phases || {};
569
+ options.phases.initialize = options.phases.initialize || {};
570
+ options.phases.upload = options.phases.upload || {};
571
+ options.phases.finalize = options.phases.finalize || {};
572
+
573
+ defaultsDeep(
574
+ options.phases.initialize,
575
+ {
576
+ method: 'POST',
577
+ body: {
578
+ uploadProtocol: 'content-length',
579
+ },
580
+ },
581
+ omit(options, 'file', 'phases')
582
+ );
583
+
584
+ defaultsDeep(options.phases.upload, {
585
+ method: 'PUT',
586
+ json: false,
587
+ withCredentials: false,
588
+ body: options.file,
589
+ headers: {
590
+ 'x-trans-id': uuid.v4(),
591
+ authorization: undefined,
592
+ },
593
+ });
594
+
595
+ defaultsDeep(
596
+ options.phases.finalize,
597
+ {
598
+ method: 'POST',
599
+ },
600
+ omit(options, 'file', 'phases')
601
+ );
602
+
603
+ const shunt = new EventEmitter();
604
+
605
+ const promise = this._uploadPhaseInitialize(options)
606
+ .then(() => {
607
+ const p = this._uploadPhaseUpload(options);
608
+
609
+ transferEvents('progress', p, shunt);
610
+
611
+ return p;
612
+ })
613
+ .then((...args) => this._uploadPhaseFinalize(options, ...args))
614
+ .then((res) => ({...res.body, ...res.headers}));
615
+
616
+ proxyEvents(shunt, promise);
617
+
618
+ return promise;
619
+ },
620
+
621
+ _uploadPhaseInitialize: function _uploadPhaseInitialize(options) {
622
+ this.logger.debug('client: initiating upload session');
623
+
624
+ return this.request(options.phases.initialize)
625
+ .then((...args) => {
626
+ const fileUploadSizeLimitInBytes =
627
+ (args[0].body.fileUploadSizeLimit || MAX_FILE_SIZE_IN_MB) * 1024 * 1024;
628
+ const currentFileSizeInBytes = options.file.byteLength;
629
+
630
+ if (fileUploadSizeLimitInBytes && fileUploadSizeLimitInBytes < currentFileSizeInBytes) {
631
+ return this._uploadAbortSession(currentFileSizeInBytes, ...args);
632
+ }
633
+
634
+ return this._uploadApplySession(options, ...args);
635
+ })
636
+ .then((res) => {
637
+ this.logger.debug('client: initiated upload session');
638
+
639
+ return res;
640
+ });
641
+ },
642
+
643
+ _uploadAbortSession(currentFileSizeInBytes, response) {
644
+ this.logger.debug('client: deleting uploaded file');
645
+
646
+ return this.request({
647
+ method: 'DELETE',
648
+ url: response.body.url,
649
+ headers: response.options.headers,
650
+ }).then(() => {
651
+ this.logger.debug('client: deleting uploaded file complete');
652
+
653
+ const abortErrorDetails = {
654
+ currentFileSizeInBytes,
655
+ fileUploadSizeLimitInMB: response.body.fileUploadSizeLimit || MAX_FILE_SIZE_IN_MB,
656
+ message: 'file-upload-size-limit-enabled',
657
+ };
658
+
659
+ return Promise.reject(new Error(`${JSON.stringify(abortErrorDetails)}`));
660
+ });
661
+ },
662
+
663
+ _uploadApplySession(options, res) {
664
+ const session = res.body;
665
+
666
+ ['upload', 'finalize'].reduce((opts, key) => {
667
+ opts[key] = Object.keys(opts[key]).reduce((phaseOptions, phaseKey) => {
668
+ if (phaseKey.startsWith('$')) {
669
+ phaseOptions[phaseKey.substr(1)] = phaseOptions[phaseKey](session);
670
+ Reflect.deleteProperty(phaseOptions, phaseKey);
671
+ }
672
+
673
+ return phaseOptions;
674
+ }, opts[key]);
675
+
676
+ return opts;
677
+ }, options.phases);
678
+ },
679
+
680
+ @retry
681
+ _uploadPhaseUpload(options) {
682
+ this.logger.debug('client: uploading file');
683
+
684
+ const promise = this.request(options.phases.upload).then((res) => {
685
+ this.logger.debug('client: uploaded file');
686
+
687
+ return res;
688
+ });
689
+
690
+ proxyEvents(options.phases.upload.upload, promise);
691
+
692
+ /* istanbul ignore else */
693
+ if (process.env.NODE_ENV === 'test') {
694
+ promise.on('progress', (event) => {
695
+ this.logger.info('upload progress', event.loaded, event.total);
696
+ });
697
+ }
698
+
699
+ return promise;
700
+ },
701
+
702
+ _uploadPhaseFinalize: function _uploadPhaseFinalize(options) {
703
+ this.logger.debug('client: finalizing upload session');
704
+
705
+ return this.request(options.phases.finalize).then((res) => {
706
+ this.logger.debug('client: finalized upload session');
707
+
708
+ return res;
709
+ });
710
+ },
711
+ });
712
+
713
+ WebexCore.version = PACKAGE_VERSION;
714
+
715
+ mixinWebexInternalCorePlugins(WebexInternalCore, config, interceptors);
716
+ mixinWebexCorePlugins(WebexCore, config, interceptors);
717
+
718
+ export default WebexCore;
719
+
720
+ /**
721
+ * @method registerPlugin
722
+ * @param {string} name
723
+ * @param {function} constructor
724
+ * @param {Object} options
725
+ * @param {Array<string>} options.proxies
726
+ * @param {Object} options.interceptors
727
+ * @returns {null}
728
+ */
729
+ export function registerPlugin(name, constructor, options = {}) {
730
+ WebexCore.registerPlugin(name, constructor, options);
731
+ }
732
+
733
+ /**
734
+ * Registers plugins used by internal products that do not talk to public APIs.
735
+ * @method registerInternalPlugin
736
+ * @param {string} name
737
+ * @param {function} constructor
738
+ * @param {Object} options
739
+ * @param {Object} options.interceptors
740
+ * @private
741
+ * @returns {null}
742
+ */
743
+ export function registerInternalPlugin(name, constructor, options) {
744
+ WebexInternalCore.registerPlugin(name, constructor, options);
745
+ }