@salesforce/lds-runtime-webruntime 1.360.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.
- package/LICENSE.txt +82 -0
- package/dist/types/__mocks__/aura.d.ts +3 -0
- package/dist/types/__mocks__/o11y/activity.d.ts +12 -0
- package/dist/types/__mocks__/o11y/client.d.ts +11 -0
- package/dist/types/__mocks__/o11y/idleDetector.d.ts +18 -0
- package/dist/types/__mocks__/o11y/instrumentation.d.ts +15 -0
- package/dist/types/__mocks__/o11y_schema/sf_lds.d.ts +1 -0
- package/dist/types/jwt-authorized-fetch-service.d.ts +10 -0
- package/dist/types/main.d.ts +2 -0
- package/dist/types/network-sfap.d.ts +9 -0
- package/dist/webruntimeOneRuntimeInit.js +2909 -0
- package/package.json +79 -0
|
@@ -0,0 +1,2909 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2022, Salesforce, Inc.,
|
|
3
|
+
* All rights reserved.
|
|
4
|
+
* For full license text, see the LICENSE.txt file
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/*
|
|
8
|
+
* ATTENTION!
|
|
9
|
+
* THIS IS A GENERATED FILE FROM https://github.com/salesforce-experience-platform-emu/lds-lightning-platform
|
|
10
|
+
* If you would like to contribute to LDS, please follow the steps outlined in the git repo.
|
|
11
|
+
* Any changes made to this file in p4 will be automatically overwritten.
|
|
12
|
+
* *******************************************************************************************
|
|
13
|
+
*/
|
|
14
|
+
/* proxy-compat-disable */
|
|
15
|
+
import { getInstrumentation } from 'o11y/client';
|
|
16
|
+
import { setServices } from 'force/luvioServiceProvisioner1';
|
|
17
|
+
import { executeGlobalControllerRawResponse } from 'aura';
|
|
18
|
+
import { HttpStatusCode as HttpStatusCode$2 } from 'force/luvioEngine';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Copyright (c) 2022, Salesforce, Inc.,
|
|
22
|
+
* All rights reserved.
|
|
23
|
+
* For full license text, see the LICENSE.txt file
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
const { create, freeze, keys, entries } = Object;
|
|
28
|
+
const { isArray } = Array;
|
|
29
|
+
const { stringify, parse } = JSON;
|
|
30
|
+
|
|
31
|
+
const LogLevelMap$1 = {
|
|
32
|
+
TRACE: 4,
|
|
33
|
+
DEBUG: 3,
|
|
34
|
+
INFO: 2,
|
|
35
|
+
WARN: 1,
|
|
36
|
+
ERROR: 0,
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* A simple Logger implementation.
|
|
40
|
+
*/
|
|
41
|
+
let ConsoleLogger$1 = class ConsoleLogger {
|
|
42
|
+
constructor(level = 'WARN',
|
|
43
|
+
// eslint-disable-next-line no-console
|
|
44
|
+
printer = console.log, formatter = (level, message) => `${level}: ${message}`) {
|
|
45
|
+
this.level = level;
|
|
46
|
+
this.printer = printer;
|
|
47
|
+
this.formatter = formatter;
|
|
48
|
+
this.messages = [];
|
|
49
|
+
}
|
|
50
|
+
trace(message) {
|
|
51
|
+
this.log('TRACE', message);
|
|
52
|
+
}
|
|
53
|
+
debug(message) {
|
|
54
|
+
this.log('DEBUG', message);
|
|
55
|
+
}
|
|
56
|
+
info(message) {
|
|
57
|
+
this.log('INFO', message);
|
|
58
|
+
}
|
|
59
|
+
warn(message) {
|
|
60
|
+
this.log('WARN', message);
|
|
61
|
+
}
|
|
62
|
+
error(message) {
|
|
63
|
+
this.log('ERROR', message);
|
|
64
|
+
}
|
|
65
|
+
log(level, message) {
|
|
66
|
+
if (LogLevelMap$1[level] > LogLevelMap$1[this.level]) {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
this.printer(this.formatter(level, message));
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
class Ok {
|
|
74
|
+
constructor(value) {
|
|
75
|
+
this.value = value;
|
|
76
|
+
}
|
|
77
|
+
isOk() {
|
|
78
|
+
return true;
|
|
79
|
+
}
|
|
80
|
+
isErr() {
|
|
81
|
+
return !this.isOk();
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
class Err {
|
|
85
|
+
constructor(error) {
|
|
86
|
+
this.error = error;
|
|
87
|
+
}
|
|
88
|
+
isOk() {
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
isErr() {
|
|
92
|
+
return !this.isOk();
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
const ok = (value) => new Ok(value);
|
|
96
|
+
const err = (err) => new Err(err);
|
|
97
|
+
function isSubscribableResult(x) {
|
|
98
|
+
if (typeof x !== 'object' || x === null) {
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
if ('isOk' in x && typeof x.isOk === 'function') {
|
|
102
|
+
if (x.isOk()) {
|
|
103
|
+
return ('value' in x &&
|
|
104
|
+
typeof x.value === 'object' &&
|
|
105
|
+
x.value !== null &&
|
|
106
|
+
'subscribe' in x.value &&
|
|
107
|
+
typeof x.value.subscribe === 'function' &&
|
|
108
|
+
'refresh' in x.value &&
|
|
109
|
+
typeof x.value.refresh === 'function');
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
return 'error' in x;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Returns a PromiseLike object that resolves with the specified result.
|
|
120
|
+
*
|
|
121
|
+
* @param result resolved result
|
|
122
|
+
* @returns
|
|
123
|
+
*/
|
|
124
|
+
function resolvedPromiseLike(result) {
|
|
125
|
+
// Don't nest anything promise like
|
|
126
|
+
if (isPromiseLike(result)) {
|
|
127
|
+
return result.then((nextResult) => nextResult);
|
|
128
|
+
}
|
|
129
|
+
return {
|
|
130
|
+
then: (onFulfilled, _onRejected) => {
|
|
131
|
+
if (typeof onFulfilled === 'function') {
|
|
132
|
+
try {
|
|
133
|
+
return resolvedPromiseLike(onFulfilled(result));
|
|
134
|
+
}
|
|
135
|
+
catch (e) {
|
|
136
|
+
return rejectedPromiseLike(e);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
// assume TResult1 == Result and just pass result down the chain
|
|
140
|
+
return resolvedPromiseLike(result);
|
|
141
|
+
},
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Returns a PromiseLike object that rejects with the specified reason.
|
|
146
|
+
*
|
|
147
|
+
* @param reason rejection value
|
|
148
|
+
* @returns PromiseLike that rejects with reason
|
|
149
|
+
*/
|
|
150
|
+
function rejectedPromiseLike(reason) {
|
|
151
|
+
if (isPromiseLike(reason)) {
|
|
152
|
+
return reason.then((nextResult) => nextResult);
|
|
153
|
+
}
|
|
154
|
+
return {
|
|
155
|
+
then: (_onFulfilled, onRejected) => {
|
|
156
|
+
if (typeof onRejected === 'function') {
|
|
157
|
+
try {
|
|
158
|
+
return resolvedPromiseLike(onRejected(reason));
|
|
159
|
+
}
|
|
160
|
+
catch (e) {
|
|
161
|
+
return rejectedPromiseLike(e);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
// assume TResult2 == Result and just pass rejection down the chain
|
|
165
|
+
return rejectedPromiseLike(reason);
|
|
166
|
+
},
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Indicates if an object is PromiseLike.
|
|
171
|
+
*
|
|
172
|
+
* @param x anything
|
|
173
|
+
* @returns true if x is PromiseLike; false if not
|
|
174
|
+
*/
|
|
175
|
+
function isPromiseLike(x) {
|
|
176
|
+
return typeof x === 'object' && typeof (x === null || x === void 0 ? void 0 : x.then) === 'function';
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Recursively compares two values and indicates if they are equal.
|
|
181
|
+
*
|
|
182
|
+
* @param x first value
|
|
183
|
+
* @param y second value
|
|
184
|
+
* @returns true if x and y are recursively equal to each other; false if not
|
|
185
|
+
*/
|
|
186
|
+
function deepEquals(x, y) {
|
|
187
|
+
if (x === undefined) {
|
|
188
|
+
return y === undefined;
|
|
189
|
+
}
|
|
190
|
+
else if (x === null) {
|
|
191
|
+
return y === null;
|
|
192
|
+
}
|
|
193
|
+
else if (y === null) {
|
|
194
|
+
return x === null;
|
|
195
|
+
}
|
|
196
|
+
else if (isArray(x)) {
|
|
197
|
+
if (!isArray(y) || x.length !== y.length) {
|
|
198
|
+
return false;
|
|
199
|
+
}
|
|
200
|
+
for (let i = 0; i < x.length; ++i) {
|
|
201
|
+
if (!deepEquals(x[i], y[i])) {
|
|
202
|
+
return false;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
return true;
|
|
206
|
+
}
|
|
207
|
+
else if (typeof x === 'object') {
|
|
208
|
+
if (typeof y !== 'object') {
|
|
209
|
+
return false;
|
|
210
|
+
}
|
|
211
|
+
const xkeys = Object.keys(x);
|
|
212
|
+
const ykeys = Object.keys(y);
|
|
213
|
+
if (xkeys.length !== ykeys.length) {
|
|
214
|
+
return false;
|
|
215
|
+
}
|
|
216
|
+
for (let i = 0; i < xkeys.length; ++i) {
|
|
217
|
+
const key = xkeys[i];
|
|
218
|
+
if (!deepEquals(x[key], y[key])) {
|
|
219
|
+
return false;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
return true;
|
|
223
|
+
}
|
|
224
|
+
return x === y;
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* A deterministic JSON stringify implementation. Heavily adapted from https://github.com/epoberezkin/fast-json-stable-stringify.
|
|
228
|
+
* This is needed because insertion order for JSON.stringify(object) affects output:
|
|
229
|
+
* JSON.stringify({a: 1, b: 2})
|
|
230
|
+
* "{"a":1,"b":2}"
|
|
231
|
+
* JSON.stringify({b: 2, a: 1})
|
|
232
|
+
* "{"b":2,"a":1}"
|
|
233
|
+
* @param data Data to be JSON-stringified.
|
|
234
|
+
* @returns JSON.stringified value with consistent ordering of keys.
|
|
235
|
+
*/
|
|
236
|
+
function stableJSONStringify(node) {
|
|
237
|
+
// This is for Date values.
|
|
238
|
+
if (node && node.toJSON && typeof node.toJSON === 'function') {
|
|
239
|
+
// eslint-disable-next-line no-param-reassign
|
|
240
|
+
node = node.toJSON();
|
|
241
|
+
}
|
|
242
|
+
if (node === undefined) {
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
if (typeof node === 'number') {
|
|
246
|
+
return isFinite(node) ? '' + node : 'null';
|
|
247
|
+
}
|
|
248
|
+
if (typeof node !== 'object') {
|
|
249
|
+
return stringify(node);
|
|
250
|
+
}
|
|
251
|
+
let i;
|
|
252
|
+
let out;
|
|
253
|
+
if (isArray(node)) {
|
|
254
|
+
out = '[';
|
|
255
|
+
for (i = 0; i < node.length; i++) {
|
|
256
|
+
if (i) {
|
|
257
|
+
out += ',';
|
|
258
|
+
}
|
|
259
|
+
out += stableJSONStringify(node[i]) || 'null';
|
|
260
|
+
}
|
|
261
|
+
return out + ']';
|
|
262
|
+
}
|
|
263
|
+
if (node === null) {
|
|
264
|
+
return 'null';
|
|
265
|
+
}
|
|
266
|
+
const objKeys = keys(node).sort();
|
|
267
|
+
out = '';
|
|
268
|
+
for (i = 0; i < objKeys.length; i++) {
|
|
269
|
+
const key = objKeys[i];
|
|
270
|
+
const value = stableJSONStringify(node[key]);
|
|
271
|
+
if (!value) {
|
|
272
|
+
continue;
|
|
273
|
+
}
|
|
274
|
+
if (out) {
|
|
275
|
+
out += ',';
|
|
276
|
+
}
|
|
277
|
+
out += stringify(key) + ':' + value;
|
|
278
|
+
}
|
|
279
|
+
return '{' + out + '}';
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* Converts an arbitrary value to an Error.
|
|
283
|
+
*
|
|
284
|
+
* @param x anything
|
|
285
|
+
* @returns Error corresponding to x
|
|
286
|
+
*/
|
|
287
|
+
function toError(x) {
|
|
288
|
+
if (x instanceof Error) {
|
|
289
|
+
return x;
|
|
290
|
+
}
|
|
291
|
+
return new Error(typeof x === 'string' ? x : JSON.stringify(x));
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* Deep copies a JSON object. Only works for non-complex values.
|
|
295
|
+
* No Date or Functions etc.
|
|
296
|
+
*/
|
|
297
|
+
function deepCopy(x) {
|
|
298
|
+
const stringified = stringify(x);
|
|
299
|
+
return stringified ? parse(stringified) : undefined;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
var HttpStatusCode$1;
|
|
303
|
+
(function (HttpStatusCode) {
|
|
304
|
+
HttpStatusCode[HttpStatusCode["Ok"] = 200] = "Ok";
|
|
305
|
+
HttpStatusCode[HttpStatusCode["Created"] = 201] = "Created";
|
|
306
|
+
HttpStatusCode[HttpStatusCode["NoContent"] = 204] = "NoContent";
|
|
307
|
+
HttpStatusCode[HttpStatusCode["NotModified"] = 304] = "NotModified";
|
|
308
|
+
HttpStatusCode[HttpStatusCode["BadRequest"] = 400] = "BadRequest";
|
|
309
|
+
HttpStatusCode[HttpStatusCode["Unauthorized"] = 401] = "Unauthorized";
|
|
310
|
+
HttpStatusCode[HttpStatusCode["Forbidden"] = 403] = "Forbidden";
|
|
311
|
+
HttpStatusCode[HttpStatusCode["NotFound"] = 404] = "NotFound";
|
|
312
|
+
HttpStatusCode[HttpStatusCode["ServerError"] = 500] = "ServerError";
|
|
313
|
+
HttpStatusCode[HttpStatusCode["GatewayTimeout"] = 504] = "GatewayTimeout";
|
|
314
|
+
})(HttpStatusCode$1 || (HttpStatusCode$1 = {}));
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Copyright (c) 2022, Salesforce, Inc.,
|
|
318
|
+
* All rights reserved.
|
|
319
|
+
* For full license text, see the LICENSE.txt file
|
|
320
|
+
*/
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* BaseCommand is an abstract implementation of SubscribableCommand. It adds the
|
|
324
|
+
* notions of typed configuration, request context, and a set of runtime services
|
|
325
|
+
* to the contract defined by Command/SubscribableCommand.
|
|
326
|
+
*/
|
|
327
|
+
class BaseCommand {
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Copyright (c) 2022, Salesforce, Inc.,
|
|
332
|
+
* All rights reserved.
|
|
333
|
+
* For full license text, see the LICENSE.txt file
|
|
334
|
+
*/
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* An implementation of BaseCommand that makes network requests but does not try to
|
|
339
|
+
* use the store.
|
|
340
|
+
*/
|
|
341
|
+
class NetworkCommand extends BaseCommand {
|
|
342
|
+
constructor(services) {
|
|
343
|
+
super();
|
|
344
|
+
this.services = services;
|
|
345
|
+
this.subscriptions = [];
|
|
346
|
+
this.exposeSubscribeAndRefresh = false;
|
|
347
|
+
}
|
|
348
|
+
execute() {
|
|
349
|
+
const result = this.fetch();
|
|
350
|
+
if (this.exposeSubscribeAndRefresh) {
|
|
351
|
+
return this.fetchSubscribableResult(result);
|
|
352
|
+
}
|
|
353
|
+
return result;
|
|
354
|
+
}
|
|
355
|
+
fetchSubscribableResult(res) {
|
|
356
|
+
return res.then((networkResult) => {
|
|
357
|
+
if (networkResult.isErr()) {
|
|
358
|
+
return err(networkResult.error);
|
|
359
|
+
}
|
|
360
|
+
else {
|
|
361
|
+
const data = networkResult.value;
|
|
362
|
+
return ok({
|
|
363
|
+
data,
|
|
364
|
+
subscribe: (cb) => {
|
|
365
|
+
this.subscriptions.push(cb);
|
|
366
|
+
return () => {
|
|
367
|
+
this.subscriptions = this.subscriptions.filter((cb2) => cb2 !== cb);
|
|
368
|
+
};
|
|
369
|
+
},
|
|
370
|
+
refresh: () => this.refresh(),
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
refresh() {
|
|
376
|
+
return this.execute().then((newResult) => {
|
|
377
|
+
if (newResult.isOk()) {
|
|
378
|
+
if (isSubscribableResult(newResult)) {
|
|
379
|
+
const value = newResult.value;
|
|
380
|
+
this.subscriptions.forEach((cb) => {
|
|
381
|
+
cb(ok(value.data));
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
return ok(undefined);
|
|
385
|
+
}
|
|
386
|
+
return err(newResult.error);
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
async afterRequestHooks(_options) { }
|
|
390
|
+
}
|
|
391
|
+
function buildServiceDescriptor$e() {
|
|
392
|
+
return {
|
|
393
|
+
type: 'networkCommandBaseClass',
|
|
394
|
+
version: '1.0',
|
|
395
|
+
service: NetworkCommand,
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
/**
|
|
400
|
+
* Copyright (c) 2022, Salesforce, Inc.,
|
|
401
|
+
* All rights reserved.
|
|
402
|
+
* For full license text, see the LICENSE.txt file
|
|
403
|
+
*/
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
/**
|
|
407
|
+
* An implementation of NetworkCommand that uses Aura as the transport mechanism
|
|
408
|
+
*/
|
|
409
|
+
class AuraNetworkCommand extends NetworkCommand {
|
|
410
|
+
constructor(services) {
|
|
411
|
+
super(services);
|
|
412
|
+
this.services = services;
|
|
413
|
+
this.actionConfig = {
|
|
414
|
+
background: false,
|
|
415
|
+
hotspot: true,
|
|
416
|
+
longRunning: false,
|
|
417
|
+
storable: false,
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
coerceAuraErrors(auraErrors) {
|
|
421
|
+
return toError(auraErrors[0]); // Default Implmentation stringifies the response
|
|
422
|
+
}
|
|
423
|
+
convertAuraResponseToData(responsePromise, coerceError) {
|
|
424
|
+
return responsePromise
|
|
425
|
+
.then((response) => {
|
|
426
|
+
const auraReturnValue = response.getReturnValue();
|
|
427
|
+
try {
|
|
428
|
+
this.afterRequestHooks({ statusCode: 200 }); // Treat all aura success as 200
|
|
429
|
+
}
|
|
430
|
+
catch (e) { }
|
|
431
|
+
return ok(auraReturnValue);
|
|
432
|
+
})
|
|
433
|
+
.catch((error) => {
|
|
434
|
+
if (!error || !error.getError) {
|
|
435
|
+
return err(toError('Failed to get error from response'));
|
|
436
|
+
}
|
|
437
|
+
else {
|
|
438
|
+
const actionErrors = error.getError();
|
|
439
|
+
if (actionErrors.length > 0) {
|
|
440
|
+
return err(coerceError(actionErrors));
|
|
441
|
+
}
|
|
442
|
+
else {
|
|
443
|
+
return err(toError('Error fetching component'));
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
});
|
|
447
|
+
}
|
|
448
|
+
fetch() {
|
|
449
|
+
return this.convertAuraResponseToData(this.services.auraNetwork(this.endpoint, this.auraParams, this.actionConfig), this.coerceAuraErrors);
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
function buildServiceDescriptor$d() {
|
|
454
|
+
return {
|
|
455
|
+
type: 'auraNetworkCommandBaseClass',
|
|
456
|
+
version: '1.0',
|
|
457
|
+
service: AuraNetworkCommand,
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
/**
|
|
462
|
+
* Copyright (c) 2022, Salesforce, Inc.,
|
|
463
|
+
* All rights reserved.
|
|
464
|
+
* For full license text, see the LICENSE.txt file
|
|
465
|
+
*/
|
|
466
|
+
|
|
467
|
+
|
|
468
|
+
// Needs TS 2023+ (esnext) AND node 20+ to remove
|
|
469
|
+
function setOverlaps(setA, setB) {
|
|
470
|
+
if (setA.size > setB.size) {
|
|
471
|
+
for (const key of setB.keys()) {
|
|
472
|
+
if (setA.has(key)) {
|
|
473
|
+
return true;
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
else {
|
|
478
|
+
for (const key of setA) {
|
|
479
|
+
if (setB.has(key)) {
|
|
480
|
+
return true;
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
return false;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
/**
|
|
488
|
+
* An implementation of BaseCommand that allows for extending abstract cache methods
|
|
489
|
+
*
|
|
490
|
+
* @typeParam Data cache result for read operations
|
|
491
|
+
* @typeParam NetworkResult cache result including network metadata
|
|
492
|
+
* @typeParam ExtraServices additional named services needed by a subclass
|
|
493
|
+
*/
|
|
494
|
+
class CacheControlCommand extends BaseCommand {
|
|
495
|
+
constructor(services) {
|
|
496
|
+
super();
|
|
497
|
+
this.services = services;
|
|
498
|
+
this.keysUsed = new Set();
|
|
499
|
+
this.keysUpdated = undefined;
|
|
500
|
+
this.operationType = 'query';
|
|
501
|
+
this.lastEmittedData = undefined;
|
|
502
|
+
this.unsubscribeFromKeysImpl = () => undefined;
|
|
503
|
+
this.subscriptions = [];
|
|
504
|
+
this.instantiationTime = Date.now() / 1000; // in seconds
|
|
505
|
+
}
|
|
506
|
+
execute(overrides) {
|
|
507
|
+
this.keysUpdated = undefined;
|
|
508
|
+
this.unsubscribeFromKeys();
|
|
509
|
+
const mergedCacheControlConfig = mergeCacheControlConfigs(this.cacheControlStrategyConfig, overrides);
|
|
510
|
+
let returnData;
|
|
511
|
+
let returnError;
|
|
512
|
+
const requestRunner = {
|
|
513
|
+
readFromCache: (cache) => {
|
|
514
|
+
const resultPromise = this.buildResultWithSubscribe(cache);
|
|
515
|
+
return resultPromise.then((result) => {
|
|
516
|
+
if (result.isErr()) {
|
|
517
|
+
return err(result.error);
|
|
518
|
+
}
|
|
519
|
+
returnData = result;
|
|
520
|
+
return ok(undefined);
|
|
521
|
+
});
|
|
522
|
+
},
|
|
523
|
+
requestFromNetwork: () => {
|
|
524
|
+
const that = this;
|
|
525
|
+
return (async function* () {
|
|
526
|
+
const result = await that.requestFromNetwork();
|
|
527
|
+
if (result.isErr()) {
|
|
528
|
+
returnError = result;
|
|
529
|
+
}
|
|
530
|
+
yield result;
|
|
531
|
+
})();
|
|
532
|
+
},
|
|
533
|
+
writeToCache: (cache, networkResult) => {
|
|
534
|
+
return this.writeToCacheAndRecordKeys(cache, networkResult);
|
|
535
|
+
},
|
|
536
|
+
};
|
|
537
|
+
const resultPromise = this.services.cacheController.execute(mergedCacheControlConfig, requestRunner, {
|
|
538
|
+
instrumentationAttributes: this.instrumentationAttributes,
|
|
539
|
+
});
|
|
540
|
+
return resultPromise.then((result) => {
|
|
541
|
+
return this.publishUpdatedKeys().then(() => {
|
|
542
|
+
// First check if there was an error from the network
|
|
543
|
+
if (returnError) {
|
|
544
|
+
return returnError;
|
|
545
|
+
}
|
|
546
|
+
// Then return other errors from read/write
|
|
547
|
+
if (result.isErr()) {
|
|
548
|
+
return err(result.error);
|
|
549
|
+
}
|
|
550
|
+
if (this.subscriptions.length > 0) {
|
|
551
|
+
this.subscribeToKeysUsed();
|
|
552
|
+
}
|
|
553
|
+
if (returnData === undefined) {
|
|
554
|
+
return err(new Error('Cache miss after fetching from network'));
|
|
555
|
+
}
|
|
556
|
+
if (returnData.isOk() && this.lastEmittedData === undefined) {
|
|
557
|
+
this.lastEmittedData = returnData.value.data;
|
|
558
|
+
}
|
|
559
|
+
return returnData;
|
|
560
|
+
});
|
|
561
|
+
});
|
|
562
|
+
}
|
|
563
|
+
publishUpdatedKeys() {
|
|
564
|
+
if (this.services.pubSub) {
|
|
565
|
+
if (this.keysUpdated !== undefined && this.keysUpdated.size > 0) {
|
|
566
|
+
return this.services.pubSub.publish({
|
|
567
|
+
type: 'cacheUpdate',
|
|
568
|
+
data: this.keysUpdated,
|
|
569
|
+
});
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
return resolvedPromiseLike(undefined);
|
|
573
|
+
}
|
|
574
|
+
subscribeToKeysUsed() {
|
|
575
|
+
this.unsubscribeFromKeys();
|
|
576
|
+
const { pubSub } = this.services;
|
|
577
|
+
if (!pubSub) {
|
|
578
|
+
this.unsubscribeFromKeysImpl = () => undefined;
|
|
579
|
+
return;
|
|
580
|
+
}
|
|
581
|
+
const rebuildUnsubscribe = pubSub.subscribe({
|
|
582
|
+
type: 'cacheUpdate',
|
|
583
|
+
predicate: (event) => setOverlaps(event.data, this.keysUsed),
|
|
584
|
+
callback: () => this.rerun({ now: this.instantiationTime }).then(() => undefined),
|
|
585
|
+
keys: this.keysUsed,
|
|
586
|
+
});
|
|
587
|
+
const refreshUnsubscribe = pubSub.subscribe({
|
|
588
|
+
type: 'cacheInvalidation',
|
|
589
|
+
predicate: (event) => setOverlaps(event.data, this.keysUsed),
|
|
590
|
+
callback: () => this.rerun().then(() => undefined),
|
|
591
|
+
keys: this.keysUsed,
|
|
592
|
+
});
|
|
593
|
+
this.unsubscribeFromKeysImpl = () => {
|
|
594
|
+
rebuildUnsubscribe();
|
|
595
|
+
refreshUnsubscribe();
|
|
596
|
+
};
|
|
597
|
+
return;
|
|
598
|
+
}
|
|
599
|
+
unsubscribeFromKeys() {
|
|
600
|
+
this.unsubscribeFromKeysImpl();
|
|
601
|
+
}
|
|
602
|
+
// TODO: This should likely be abstract in v2. For v1, provide default comparison logic.
|
|
603
|
+
equals(result1, result2) {
|
|
604
|
+
return deepEquals(result1, result2);
|
|
605
|
+
}
|
|
606
|
+
async afterRequestHooks(_options) { }
|
|
607
|
+
refresh() {
|
|
608
|
+
return this.rerun({ cacheControlConfig: { type: 'no-cache' } }).then((result) => {
|
|
609
|
+
if (result.isErr()) {
|
|
610
|
+
return err(result.error);
|
|
611
|
+
}
|
|
612
|
+
return ok(undefined);
|
|
613
|
+
});
|
|
614
|
+
}
|
|
615
|
+
writeToCacheAndRecordKeys(cache, networkResult) {
|
|
616
|
+
const recordableCache = cache.record();
|
|
617
|
+
return this.writeToCache(recordableCache, networkResult).then((result) => {
|
|
618
|
+
this.instantiationTime = Date.now() / 1000;
|
|
619
|
+
this.keysUpdated = recordableCache.keysUpdated;
|
|
620
|
+
return ok(result);
|
|
621
|
+
});
|
|
622
|
+
}
|
|
623
|
+
buildResultWithSubscribe(cache) {
|
|
624
|
+
const recordableCache = cache.record();
|
|
625
|
+
const result = this.readFromCache(recordableCache);
|
|
626
|
+
return result.then((readResult) => {
|
|
627
|
+
if (readResult.isErr()) {
|
|
628
|
+
return err(readResult.error);
|
|
629
|
+
}
|
|
630
|
+
else {
|
|
631
|
+
const data = readResult.value;
|
|
632
|
+
this.keysUsed = recordableCache.keysRead;
|
|
633
|
+
return ok({
|
|
634
|
+
data,
|
|
635
|
+
subscribe: this.buildSubscribe(),
|
|
636
|
+
refresh: () => this.refresh(),
|
|
637
|
+
});
|
|
638
|
+
}
|
|
639
|
+
});
|
|
640
|
+
}
|
|
641
|
+
/**
|
|
642
|
+
* Builds a function that subscribes to cache changes via the pubsub service. Whenever
|
|
643
|
+
* relevant cache updates occur, it re-reads the data and compares it against
|
|
644
|
+
* the last known value. If a change is detected, the provided
|
|
645
|
+
* callback is invoked.
|
|
646
|
+
*
|
|
647
|
+
* @param keysRead - keys of interest that were read during readFromCache
|
|
648
|
+
* @returns an unsubscribe function to stop watching for updates
|
|
649
|
+
*/
|
|
650
|
+
buildSubscribe() {
|
|
651
|
+
return (consumerCallback) => {
|
|
652
|
+
if (this.subscriptions.length === 0 && this.operationType === 'query') {
|
|
653
|
+
this.subscribeToKeysUsed();
|
|
654
|
+
}
|
|
655
|
+
this.subscriptions.push(consumerCallback);
|
|
656
|
+
return () => {
|
|
657
|
+
this.subscriptions = this.subscriptions.filter((cb) => cb !== consumerCallback);
|
|
658
|
+
if (this.subscriptions.length === 0) {
|
|
659
|
+
this.unsubscribeFromKeys();
|
|
660
|
+
}
|
|
661
|
+
};
|
|
662
|
+
};
|
|
663
|
+
}
|
|
664
|
+
rerun(overrides) {
|
|
665
|
+
return this.execute(overrides).then((result) => {
|
|
666
|
+
if (result.isErr()) {
|
|
667
|
+
this.invokeConsumerCallbacks(result);
|
|
668
|
+
return result;
|
|
669
|
+
}
|
|
670
|
+
if (!this.equals(this.lastEmittedData, result.value.data)) {
|
|
671
|
+
this.lastEmittedData = result.value.data;
|
|
672
|
+
this.invokeConsumerCallbacks(ok(result.value.data));
|
|
673
|
+
}
|
|
674
|
+
return result;
|
|
675
|
+
});
|
|
676
|
+
}
|
|
677
|
+
invokeConsumerCallbacks(data) {
|
|
678
|
+
this.subscriptions.forEach((cb) => {
|
|
679
|
+
try {
|
|
680
|
+
cb(data);
|
|
681
|
+
}
|
|
682
|
+
catch (error) {
|
|
683
|
+
// TODO: Add logging here
|
|
684
|
+
}
|
|
685
|
+
});
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
/**
|
|
689
|
+
* Merges two CacheControlStrategyConfig objects
|
|
690
|
+
*
|
|
691
|
+
*/
|
|
692
|
+
function mergeCacheControlConfigs(baseConfig, overrides) {
|
|
693
|
+
var _a;
|
|
694
|
+
if (!overrides) {
|
|
695
|
+
return baseConfig;
|
|
696
|
+
}
|
|
697
|
+
const now = (_a = overrides.now) !== null && _a !== void 0 ? _a : baseConfig.now;
|
|
698
|
+
if (!overrides.cacheControlConfig) {
|
|
699
|
+
return {
|
|
700
|
+
...baseConfig,
|
|
701
|
+
now,
|
|
702
|
+
};
|
|
703
|
+
}
|
|
704
|
+
return {
|
|
705
|
+
...overrides.cacheControlConfig,
|
|
706
|
+
now,
|
|
707
|
+
};
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
/**
|
|
711
|
+
* Copyright (c) 2022, Salesforce, Inc.,
|
|
712
|
+
* All rights reserved.
|
|
713
|
+
* For full license text, see the LICENSE.txt file
|
|
714
|
+
*/
|
|
715
|
+
|
|
716
|
+
|
|
717
|
+
/**
|
|
718
|
+
* An implementation of BaseCommand that allows for extending abstract cache methods
|
|
719
|
+
*
|
|
720
|
+
* @typeParam Data cache result for read operations
|
|
721
|
+
* @typeParam NetworkResult cache result including network metadata
|
|
722
|
+
* @typeParam ExtraServices additional named services needed by a subclass
|
|
723
|
+
*/
|
|
724
|
+
class AuraCacheControlCommand extends CacheControlCommand {
|
|
725
|
+
constructor(services) {
|
|
726
|
+
super(services);
|
|
727
|
+
this.services = services;
|
|
728
|
+
this.actionConfig = {
|
|
729
|
+
background: false,
|
|
730
|
+
hotspot: true,
|
|
731
|
+
longRunning: false,
|
|
732
|
+
storable: false,
|
|
733
|
+
};
|
|
734
|
+
}
|
|
735
|
+
requestFromNetwork() {
|
|
736
|
+
return this.convertAuraResponseToData(this.services.auraNetwork(this.endpoint, this.auraParams, this.actionConfig), (errs) => this.coerceError(errs));
|
|
737
|
+
}
|
|
738
|
+
coerceError(auraErrors) {
|
|
739
|
+
return toError(auraErrors[0]); // Default Implementation stringifies the response
|
|
740
|
+
}
|
|
741
|
+
convertAuraResponseToData(responsePromise, coerceError) {
|
|
742
|
+
return responsePromise
|
|
743
|
+
.then((response) => {
|
|
744
|
+
const auraReturnValue = response.getReturnValue();
|
|
745
|
+
try {
|
|
746
|
+
this.afterRequestHooks({ statusCode: 200 }); // Treat all aura success as 200
|
|
747
|
+
}
|
|
748
|
+
catch (e) { }
|
|
749
|
+
return ok(auraReturnValue);
|
|
750
|
+
})
|
|
751
|
+
.catch((error) => {
|
|
752
|
+
if (!error || !error.getError) {
|
|
753
|
+
return err(toError('Failed to get error from response'));
|
|
754
|
+
}
|
|
755
|
+
else {
|
|
756
|
+
const actionErrors = error.getError();
|
|
757
|
+
if (actionErrors.length > 0) {
|
|
758
|
+
return err(coerceError(actionErrors));
|
|
759
|
+
}
|
|
760
|
+
else {
|
|
761
|
+
return err(toError('Error fetching component'));
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
});
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
/**
|
|
769
|
+
* Copyright (c) 2022, Salesforce, Inc.,
|
|
770
|
+
* All rights reserved.
|
|
771
|
+
* For full license text, see the LICENSE.txt file
|
|
772
|
+
*/
|
|
773
|
+
|
|
774
|
+
|
|
775
|
+
/**
|
|
776
|
+
* An implementation of BaseCommand that allows for extending abstract cache methods
|
|
777
|
+
*
|
|
778
|
+
* @typeParam Data cache result for read operations
|
|
779
|
+
* @typeParam NetworkResult cache result including network metadata
|
|
780
|
+
* @typeParam ExtraServices additional named services needed by a subclass
|
|
781
|
+
*/
|
|
782
|
+
class AuraResourceCacheControlCommand extends AuraCacheControlCommand {
|
|
783
|
+
constructor(services) {
|
|
784
|
+
super(services);
|
|
785
|
+
this.services = services;
|
|
786
|
+
}
|
|
787
|
+
execute() {
|
|
788
|
+
return super.execute();
|
|
789
|
+
}
|
|
790
|
+
readFromCache(cache) {
|
|
791
|
+
var _a;
|
|
792
|
+
const data = (_a = cache.get(this.buildKey())) === null || _a === void 0 ? void 0 : _a.value;
|
|
793
|
+
if (data === undefined) {
|
|
794
|
+
return resolvedPromiseLike(err(new Error('Failed to find data in cache')));
|
|
795
|
+
}
|
|
796
|
+
return resolvedPromiseLike(ok(data));
|
|
797
|
+
}
|
|
798
|
+
writeToCache(cache, networkResult) {
|
|
799
|
+
if (networkResult.isOk()) {
|
|
800
|
+
cache.set(this.buildKey(), {
|
|
801
|
+
value: networkResult.value,
|
|
802
|
+
metadata: {
|
|
803
|
+
cacheControl: this.buildCacheControlMetadata(networkResult.value),
|
|
804
|
+
},
|
|
805
|
+
});
|
|
806
|
+
}
|
|
807
|
+
return resolvedPromiseLike(undefined);
|
|
808
|
+
}
|
|
809
|
+
buildKey() {
|
|
810
|
+
return `{"endpoint":${this.endpoint},"params":${stableJSONStringify(this.auraParams)}}`;
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
function buildServiceDescriptor$c() {
|
|
815
|
+
return {
|
|
816
|
+
type: 'auraResourceCacheControlCommand',
|
|
817
|
+
version: '1.0',
|
|
818
|
+
service: AuraResourceCacheControlCommand,
|
|
819
|
+
};
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
/**
|
|
823
|
+
* Copyright (c) 2022, Salesforce, Inc.,
|
|
824
|
+
* All rights reserved.
|
|
825
|
+
* For full license text, see the LICENSE.txt file
|
|
826
|
+
*/
|
|
827
|
+
|
|
828
|
+
|
|
829
|
+
/**
|
|
830
|
+
* An implementation of BaseCommand that allows for extending abstract cache methods
|
|
831
|
+
*
|
|
832
|
+
* @typeParam Data cache result for read operations
|
|
833
|
+
* @typeParam NetworkResult cache result including network metadata
|
|
834
|
+
* @typeParam ExtraServices additional named services needed by a subclass
|
|
835
|
+
*/
|
|
836
|
+
class AuraNormalizedCacheControlCommand extends AuraCacheControlCommand {
|
|
837
|
+
constructor(services) {
|
|
838
|
+
super(services);
|
|
839
|
+
this.services = services;
|
|
840
|
+
}
|
|
841
|
+
readFromCache(cache) {
|
|
842
|
+
const data = this.buildResultType().query(cache, this.buildQuery());
|
|
843
|
+
if (data.isErr()) {
|
|
844
|
+
return resolvedPromiseLike(err(new Error(`Failed to build data from type: ${stringify(data.error)}`)));
|
|
845
|
+
}
|
|
846
|
+
return resolvedPromiseLike(ok(data.value));
|
|
847
|
+
}
|
|
848
|
+
writeToCache(cache, networkResult) {
|
|
849
|
+
if (networkResult.isOk()) {
|
|
850
|
+
this.buildResultType().write(cache.buildFixedTimeWritableCache(Date.now() / 1000), this.buildWriteInput(networkResult.value));
|
|
851
|
+
}
|
|
852
|
+
return resolvedPromiseLike(undefined);
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
function buildServiceDescriptor$b() {
|
|
857
|
+
return {
|
|
858
|
+
type: 'auraNormalizedCacheControlCommand',
|
|
859
|
+
version: '1.0',
|
|
860
|
+
service: AuraNormalizedCacheControlCommand,
|
|
861
|
+
};
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
/**
|
|
865
|
+
* Copyright (c) 2022, Salesforce, Inc.,
|
|
866
|
+
* All rights reserved.
|
|
867
|
+
* For full license text, see the LICENSE.txt file
|
|
868
|
+
*/
|
|
869
|
+
|
|
870
|
+
|
|
871
|
+
/**
|
|
872
|
+
* An implementation of BaseCommand that allows for extending abstract cache methods
|
|
873
|
+
*
|
|
874
|
+
* @typeParam Data cache result for read operations
|
|
875
|
+
* @typeParam NetworkResult cache result including network metadata
|
|
876
|
+
* @typeParam ExtraServices additional named services needed by a subclass
|
|
877
|
+
*/
|
|
878
|
+
class HttpCacheControlCommand extends CacheControlCommand {
|
|
879
|
+
constructor(services) {
|
|
880
|
+
super(services);
|
|
881
|
+
this.services = services;
|
|
882
|
+
}
|
|
883
|
+
requestFromNetwork() {
|
|
884
|
+
return this.fetch();
|
|
885
|
+
}
|
|
886
|
+
fetch() {
|
|
887
|
+
return this.convertFetchResponseToData(this.services.fetch(...this.fetchParams));
|
|
888
|
+
}
|
|
889
|
+
async coerceError(errorResponse) {
|
|
890
|
+
return toError(errorResponse.statusText); // Default Behavior
|
|
891
|
+
}
|
|
892
|
+
convertFetchResponseToData(response) {
|
|
893
|
+
return response.then((response) => {
|
|
894
|
+
if (response.ok) {
|
|
895
|
+
return response
|
|
896
|
+
.json()
|
|
897
|
+
.then((json) => ok(json), (reason) => err(toError(reason)))
|
|
898
|
+
.finally(() => {
|
|
899
|
+
try {
|
|
900
|
+
this.afterRequestHooks({ statusCode: response.status });
|
|
901
|
+
}
|
|
902
|
+
catch (e) { }
|
|
903
|
+
});
|
|
904
|
+
}
|
|
905
|
+
else {
|
|
906
|
+
return this.coerceError(response)
|
|
907
|
+
.then((coercedError) => {
|
|
908
|
+
return err(coercedError);
|
|
909
|
+
})
|
|
910
|
+
.finally(() => {
|
|
911
|
+
try {
|
|
912
|
+
this.afterRequestHooks({ statusCode: response.status });
|
|
913
|
+
}
|
|
914
|
+
catch (e) { }
|
|
915
|
+
});
|
|
916
|
+
}
|
|
917
|
+
}, (reason) => err(toError(reason)));
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
/**
|
|
922
|
+
* Copyright (c) 2022, Salesforce, Inc.,
|
|
923
|
+
* All rights reserved.
|
|
924
|
+
* For full license text, see the LICENSE.txt file
|
|
925
|
+
*/
|
|
926
|
+
|
|
927
|
+
|
|
928
|
+
/**
|
|
929
|
+
* An implementation of BaseCommand that allows for extending abstract cache methods
|
|
930
|
+
*
|
|
931
|
+
* @typeParam Data cache result for read operations
|
|
932
|
+
* @typeParam NetworkResult cache result including network metadata
|
|
933
|
+
* @typeParam ExtraServices additional named services needed by a subclass
|
|
934
|
+
*/
|
|
935
|
+
class HttpNormalizedCacheControlCommand extends HttpCacheControlCommand {
|
|
936
|
+
constructor(services) {
|
|
937
|
+
super(services);
|
|
938
|
+
this.services = services;
|
|
939
|
+
}
|
|
940
|
+
readFromCache(cache) {
|
|
941
|
+
const data = this.buildResultType().query(cache, this.buildQuery());
|
|
942
|
+
if (data.isErr()) {
|
|
943
|
+
return resolvedPromiseLike(err(new Error(`Failed to build data from type: ${stringify(data.error)}`)));
|
|
944
|
+
}
|
|
945
|
+
return resolvedPromiseLike(ok(data.value));
|
|
946
|
+
}
|
|
947
|
+
writeToCache(cache, networkResult) {
|
|
948
|
+
if (networkResult.isOk()) {
|
|
949
|
+
this.buildResultType().write(cache.buildFixedTimeWritableCache(Date.now() / 1000), this.buildWriteInput(networkResult.value));
|
|
950
|
+
}
|
|
951
|
+
return resolvedPromiseLike(undefined);
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
function buildServiceDescriptor$a() {
|
|
956
|
+
return {
|
|
957
|
+
type: 'httpNormalizedCacheControlCommand',
|
|
958
|
+
version: '1.0',
|
|
959
|
+
service: HttpNormalizedCacheControlCommand,
|
|
960
|
+
};
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
/**
|
|
964
|
+
* Copyright (c) 2022, Salesforce, Inc.,
|
|
965
|
+
* All rights reserved.
|
|
966
|
+
* For full license text, see the LICENSE.txt file
|
|
967
|
+
*/
|
|
968
|
+
|
|
969
|
+
|
|
970
|
+
/**
|
|
971
|
+
* An implementation of NetworkCommand that uses HTTP/fetch as the transport mechanism.
|
|
972
|
+
*/
|
|
973
|
+
class FetchNetworkCommand extends NetworkCommand {
|
|
974
|
+
constructor(services) {
|
|
975
|
+
super(services);
|
|
976
|
+
this.services = services;
|
|
977
|
+
}
|
|
978
|
+
fetch() {
|
|
979
|
+
return this.convertFetchResponseToData(this.services.fetch(...this.fetchParams));
|
|
980
|
+
}
|
|
981
|
+
async coerceError(errorResponse) {
|
|
982
|
+
return toError(errorResponse.statusText); // Default Behavior
|
|
983
|
+
}
|
|
984
|
+
convertFetchResponseToData(response) {
|
|
985
|
+
return response.then((response) => {
|
|
986
|
+
if (response.ok) {
|
|
987
|
+
return response
|
|
988
|
+
.json()
|
|
989
|
+
.then((json) => ok(json), (reason) => err(toError(reason)))
|
|
990
|
+
.finally(() => {
|
|
991
|
+
try {
|
|
992
|
+
this.afterRequestHooks({ statusCode: response.status });
|
|
993
|
+
}
|
|
994
|
+
catch (e) { }
|
|
995
|
+
});
|
|
996
|
+
}
|
|
997
|
+
else {
|
|
998
|
+
return this.coerceError(response)
|
|
999
|
+
.then((coercedError) => {
|
|
1000
|
+
return err(coercedError);
|
|
1001
|
+
})
|
|
1002
|
+
.finally(() => {
|
|
1003
|
+
try {
|
|
1004
|
+
this.afterRequestHooks({ statusCode: response.status });
|
|
1005
|
+
}
|
|
1006
|
+
catch (e) { }
|
|
1007
|
+
});
|
|
1008
|
+
}
|
|
1009
|
+
}, (reason) => err(toError(reason)));
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
function buildServiceDescriptor$9() {
|
|
1014
|
+
return {
|
|
1015
|
+
type: 'fetchNetworkCommandBaseClass',
|
|
1016
|
+
version: '1.0',
|
|
1017
|
+
service: FetchNetworkCommand,
|
|
1018
|
+
};
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
/**
|
|
1022
|
+
* Copyright (c) 2022, Salesforce, Inc.,
|
|
1023
|
+
* All rights reserved.
|
|
1024
|
+
* For full license text, see the LICENSE.txt file
|
|
1025
|
+
*/
|
|
1026
|
+
|
|
1027
|
+
|
|
1028
|
+
/**
|
|
1029
|
+
* An implementation of BaseCommand that supports streaming responses and does not use the store.
|
|
1030
|
+
*/
|
|
1031
|
+
class StreamingCommand extends BaseCommand {
|
|
1032
|
+
constructor(services) {
|
|
1033
|
+
super();
|
|
1034
|
+
this.services = services;
|
|
1035
|
+
}
|
|
1036
|
+
execute() {
|
|
1037
|
+
return this.convertFetchStreamResponseToData(this.services.fetch(...this.fetchParams));
|
|
1038
|
+
}
|
|
1039
|
+
convertFetchStreamResponseToData(response) {
|
|
1040
|
+
return response.then((response) => {
|
|
1041
|
+
if (response.ok && response.body) {
|
|
1042
|
+
return ok(this.decodeByteStream(response.body));
|
|
1043
|
+
}
|
|
1044
|
+
else {
|
|
1045
|
+
return err(toError(response.ok ? 'Response body not present' : response.statusText));
|
|
1046
|
+
}
|
|
1047
|
+
}, (reason) => err(toError(reason)));
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
function buildServiceDescriptor$8() {
|
|
1052
|
+
return {
|
|
1053
|
+
type: 'streamingCommandBaseClass',
|
|
1054
|
+
version: '1.0',
|
|
1055
|
+
service: StreamingCommand,
|
|
1056
|
+
};
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
/**
|
|
1060
|
+
* Copyright (c) 2022, Salesforce, Inc.,
|
|
1061
|
+
* All rights reserved.
|
|
1062
|
+
* For full license text, see the LICENSE.txt file
|
|
1063
|
+
*/
|
|
1064
|
+
|
|
1065
|
+
|
|
1066
|
+
/**
|
|
1067
|
+
* An implementation of StreamingCommand that handles SSE (Server-Sent Event) streams.
|
|
1068
|
+
*/
|
|
1069
|
+
class SSECommand extends StreamingCommand {
|
|
1070
|
+
constructor(services) {
|
|
1071
|
+
super(services);
|
|
1072
|
+
this.services = services;
|
|
1073
|
+
}
|
|
1074
|
+
/**
|
|
1075
|
+
* Decodes the bytes returned by fetch into the correct shape.
|
|
1076
|
+
*
|
|
1077
|
+
* @param byteStream ReadableStream<Uint8Array> returned by fetch
|
|
1078
|
+
* @returns ReadableStream<StreamedData> that will be the result of the Command
|
|
1079
|
+
*/
|
|
1080
|
+
decodeByteStream(byteStream) {
|
|
1081
|
+
return this.decodeSSEStream(byteStream
|
|
1082
|
+
// eslint-disable-next-line no-undef
|
|
1083
|
+
.pipeThrough(new TextDecoderStream())
|
|
1084
|
+
.pipeThrough(new SSEParsingStream()));
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
const sseRegex = /^(?<field>[^:]*?)(: ?(?<value>.*?))?(?:\r\n?|\n)/;
|
|
1088
|
+
// +-------v------++--------v--------++-----v----+
|
|
1089
|
+
// | | |
|
|
1090
|
+
// | | CRLF | CR | LF
|
|
1091
|
+
// | |
|
|
1092
|
+
// | ": value", or ": comment" if no field name (optional)
|
|
1093
|
+
// |
|
|
1094
|
+
// field name (optional); can be entire line if no ":"
|
|
1095
|
+
/**
|
|
1096
|
+
* A TransformStream that parses server-sent event (SSE) data according to the SSE spec:
|
|
1097
|
+
* https://html.spec.whatwg.org/multipage/server-sent-events.html
|
|
1098
|
+
*/
|
|
1099
|
+
class SSEParsingStream extends TransformStream {
|
|
1100
|
+
constructor() {
|
|
1101
|
+
let ignoreLeadingLF = false, partialLine = '', data = '', event = '', id = '', retry;
|
|
1102
|
+
super({
|
|
1103
|
+
transform(chunk, controller) {
|
|
1104
|
+
// account for case where chunk boundary splits \r\n
|
|
1105
|
+
if (ignoreLeadingLF && chunk.startsWith('\n')) {
|
|
1106
|
+
// eslint-disable-next-line no-param-reassign
|
|
1107
|
+
chunk = chunk.slice(1);
|
|
1108
|
+
}
|
|
1109
|
+
ignoreLeadingLF = chunk.endsWith('\r');
|
|
1110
|
+
// prepend partial last line from previous chunk
|
|
1111
|
+
let text = partialLine + chunk;
|
|
1112
|
+
let match;
|
|
1113
|
+
while ((match = text.match(sseRegex))) {
|
|
1114
|
+
text = text.slice(match[0].length);
|
|
1115
|
+
// blank linke, end of text/event-stream message
|
|
1116
|
+
if (match.groups.field === '' && match.groups.value === undefined) {
|
|
1117
|
+
// drop messages with no data
|
|
1118
|
+
if (data === '') {
|
|
1119
|
+
event = '';
|
|
1120
|
+
continue;
|
|
1121
|
+
}
|
|
1122
|
+
// strip trailing \n from data
|
|
1123
|
+
if (data.endsWith('\n')) {
|
|
1124
|
+
data = data.slice(0, -1);
|
|
1125
|
+
}
|
|
1126
|
+
const sseMessage = {
|
|
1127
|
+
data,
|
|
1128
|
+
id,
|
|
1129
|
+
// default event value is "message"
|
|
1130
|
+
event: event || 'message',
|
|
1131
|
+
};
|
|
1132
|
+
if (retry !== undefined) {
|
|
1133
|
+
sseMessage.retry = retry;
|
|
1134
|
+
}
|
|
1135
|
+
controller.enqueue(sseMessage);
|
|
1136
|
+
// clear data & event for next message; id and retry persist until changed
|
|
1137
|
+
data = event = '';
|
|
1138
|
+
}
|
|
1139
|
+
// data: line
|
|
1140
|
+
else if (match.groups.field === 'data') {
|
|
1141
|
+
data += (match.groups.value || '') + '\n';
|
|
1142
|
+
}
|
|
1143
|
+
// event: line
|
|
1144
|
+
else if (match.groups.field === 'event') {
|
|
1145
|
+
event = match.groups.value || '';
|
|
1146
|
+
}
|
|
1147
|
+
// id: line
|
|
1148
|
+
else if (match.groups.field === 'id') {
|
|
1149
|
+
const idValue = match.groups.value || '';
|
|
1150
|
+
if (!idValue.includes('\u0000')) {
|
|
1151
|
+
id = idValue;
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
1154
|
+
// retry: line
|
|
1155
|
+
else if (match.groups.field === 'retry') {
|
|
1156
|
+
// ignore bogus values
|
|
1157
|
+
if (/^\d+$/.exec(match.groups.value)) {
|
|
1158
|
+
retry = parseInt(match.groups.value);
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
1161
|
+
// comment or unrecognized field, ignore
|
|
1162
|
+
else ;
|
|
1163
|
+
}
|
|
1164
|
+
// save partial line for next chunk
|
|
1165
|
+
partialLine = text;
|
|
1166
|
+
},
|
|
1167
|
+
});
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
function buildServiceDescriptor$7() {
|
|
1172
|
+
return {
|
|
1173
|
+
type: 'SSECommandBaseClass',
|
|
1174
|
+
version: '1.0',
|
|
1175
|
+
service: SSECommand,
|
|
1176
|
+
};
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
/**
|
|
1180
|
+
* Copyright (c) 2022, Salesforce, Inc.,
|
|
1181
|
+
* All rights reserved.
|
|
1182
|
+
* For full license text, see the LICENSE.txt file
|
|
1183
|
+
*/
|
|
1184
|
+
|
|
1185
|
+
function buildInstrumentCommand(services) {
|
|
1186
|
+
const meter = services.instrumentation.metrics.getMeter('onestore');
|
|
1187
|
+
return function instrumentCommand(commandClass, commandName) {
|
|
1188
|
+
const invocationCounter = meter.createCounter(`${commandName}.command.invocation.count`);
|
|
1189
|
+
const errorCounter = meter.createCounter(`${commandName}.command.error.count`);
|
|
1190
|
+
const durationHistogram = meter.createHistogram(`${commandName}.command.duration`);
|
|
1191
|
+
return class extends commandClass {
|
|
1192
|
+
execute(...args) {
|
|
1193
|
+
invocationCounter.add(1);
|
|
1194
|
+
let result;
|
|
1195
|
+
const start = services.instrumentation.currentTimeMs();
|
|
1196
|
+
function recordDuration() {
|
|
1197
|
+
const end = services.instrumentation.currentTimeMs();
|
|
1198
|
+
durationHistogram.record(end - start);
|
|
1199
|
+
}
|
|
1200
|
+
try {
|
|
1201
|
+
result = super.execute(...args);
|
|
1202
|
+
}
|
|
1203
|
+
catch (e) {
|
|
1204
|
+
errorCounter.add(1);
|
|
1205
|
+
throw e;
|
|
1206
|
+
}
|
|
1207
|
+
if (typeof result === 'object' && result !== null && 'then' in result) {
|
|
1208
|
+
result.then(recordDuration, () => {
|
|
1209
|
+
errorCounter.add(1);
|
|
1210
|
+
});
|
|
1211
|
+
}
|
|
1212
|
+
else {
|
|
1213
|
+
recordDuration();
|
|
1214
|
+
}
|
|
1215
|
+
return result;
|
|
1216
|
+
}
|
|
1217
|
+
};
|
|
1218
|
+
};
|
|
1219
|
+
}
|
|
1220
|
+
|
|
1221
|
+
function buildServiceDescriptor$6(instrumentation) {
|
|
1222
|
+
return {
|
|
1223
|
+
type: 'instrumentCommand',
|
|
1224
|
+
version: '1.0',
|
|
1225
|
+
service: buildInstrumentCommand({ instrumentation }),
|
|
1226
|
+
};
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1229
|
+
/**
|
|
1230
|
+
* Copyright (c) 2022, Salesforce, Inc.,
|
|
1231
|
+
* All rights reserved.
|
|
1232
|
+
* For full license text, see the LICENSE.txt file
|
|
1233
|
+
*/
|
|
1234
|
+
|
|
1235
|
+
|
|
1236
|
+
/* eslint-disable no-dupe-class-members */
|
|
1237
|
+
class O11yOTelTraceAPI {
|
|
1238
|
+
constructor(services) {
|
|
1239
|
+
this.services = services;
|
|
1240
|
+
}
|
|
1241
|
+
getTracer(name, _version, _options) {
|
|
1242
|
+
const o11yInstrumentation = getInstrumentation(name);
|
|
1243
|
+
return new O11yTracer(o11yInstrumentation, this.services.logger);
|
|
1244
|
+
}
|
|
1245
|
+
}
|
|
1246
|
+
class O11yTracer {
|
|
1247
|
+
constructor(o11yInstrumentation, logger) {
|
|
1248
|
+
this.o11yInstrumentation = o11yInstrumentation;
|
|
1249
|
+
this.logger = logger;
|
|
1250
|
+
}
|
|
1251
|
+
startSpan(name, _options, context) {
|
|
1252
|
+
const traceId = context === null || context === void 0 ? void 0 : context.getValue(Symbol.for('traceId'));
|
|
1253
|
+
const spanId = context === null || context === void 0 ? void 0 : context.getValue(Symbol.for('spanId'));
|
|
1254
|
+
const traceFlags = context === null || context === void 0 ? void 0 : context.getValue(Symbol.for('traceFlags'));
|
|
1255
|
+
let spanContext = undefined;
|
|
1256
|
+
if (traceId !== undefined && spanId !== undefined && traceFlags !== undefined) {
|
|
1257
|
+
spanContext = {
|
|
1258
|
+
traceId,
|
|
1259
|
+
spanId,
|
|
1260
|
+
traceFlags,
|
|
1261
|
+
};
|
|
1262
|
+
}
|
|
1263
|
+
const activityOptions = spanContext === undefined
|
|
1264
|
+
? undefined
|
|
1265
|
+
: {
|
|
1266
|
+
instrumentationContext: {
|
|
1267
|
+
rootId: spanContext.traceId,
|
|
1268
|
+
isRootActivitySampled: spanContext.traceFlags === 1,
|
|
1269
|
+
parentId: spanId,
|
|
1270
|
+
},
|
|
1271
|
+
};
|
|
1272
|
+
const activity = this.o11yInstrumentation.startActivity(name, activityOptions);
|
|
1273
|
+
return new O11ySpan(activity, this.logger);
|
|
1274
|
+
}
|
|
1275
|
+
startActiveSpan(name, options, context, fn) {
|
|
1276
|
+
let opts;
|
|
1277
|
+
let ctx;
|
|
1278
|
+
let fun;
|
|
1279
|
+
if (typeof options === 'function') {
|
|
1280
|
+
fun = options;
|
|
1281
|
+
}
|
|
1282
|
+
else {
|
|
1283
|
+
opts = options;
|
|
1284
|
+
if (typeof context === 'function') {
|
|
1285
|
+
fun = context;
|
|
1286
|
+
}
|
|
1287
|
+
else {
|
|
1288
|
+
ctx = context;
|
|
1289
|
+
fun = fn;
|
|
1290
|
+
}
|
|
1291
|
+
}
|
|
1292
|
+
const span = this.startSpan(name, opts, ctx);
|
|
1293
|
+
return fun(span);
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
class O11ySpan {
|
|
1297
|
+
constructor(activity, logger) {
|
|
1298
|
+
this.activity = activity;
|
|
1299
|
+
this.logger = logger;
|
|
1300
|
+
this._isRecording = true;
|
|
1301
|
+
this.attributes = {};
|
|
1302
|
+
}
|
|
1303
|
+
spanContext() {
|
|
1304
|
+
return {
|
|
1305
|
+
traceId: this.activity.getRootId(),
|
|
1306
|
+
spanId: this.activity.getId(),
|
|
1307
|
+
traceFlags: this.activity.getIsSampled() ? 1 : 0,
|
|
1308
|
+
};
|
|
1309
|
+
}
|
|
1310
|
+
setAttribute(key, value) {
|
|
1311
|
+
this.attributes[key] = value;
|
|
1312
|
+
return this;
|
|
1313
|
+
}
|
|
1314
|
+
setAttributes(attributes) {
|
|
1315
|
+
this.attributes = { ...this.attributes, ...attributes };
|
|
1316
|
+
return this;
|
|
1317
|
+
}
|
|
1318
|
+
addEvent(_name, _attributesOrStartTime, _startTime) {
|
|
1319
|
+
this.logger.warn('O11ySpan does not support addEvents.');
|
|
1320
|
+
return this;
|
|
1321
|
+
}
|
|
1322
|
+
setStatus(_status) {
|
|
1323
|
+
this.logger.warn('O11ySpan does not support setStatus.');
|
|
1324
|
+
return this;
|
|
1325
|
+
}
|
|
1326
|
+
updateName(_name) {
|
|
1327
|
+
this.logger.warn('O11ySpan does not support updateName.');
|
|
1328
|
+
return this;
|
|
1329
|
+
}
|
|
1330
|
+
end(endTime) {
|
|
1331
|
+
let endTimeInternal = endTime;
|
|
1332
|
+
try {
|
|
1333
|
+
if (typeof endTimeInternal !== 'number') {
|
|
1334
|
+
this.logger.warn('O11ySpan does not support non number endTime override.');
|
|
1335
|
+
endTimeInternal = undefined;
|
|
1336
|
+
}
|
|
1337
|
+
// TODO: Add support for stop schemas and data
|
|
1338
|
+
this.activity.stop(undefined, undefined, endTimeInternal ? { perfStopOverride: endTimeInternal } : undefined);
|
|
1339
|
+
}
|
|
1340
|
+
finally {
|
|
1341
|
+
this._isRecording = false;
|
|
1342
|
+
}
|
|
1343
|
+
return;
|
|
1344
|
+
}
|
|
1345
|
+
isRecording() {
|
|
1346
|
+
return this._isRecording;
|
|
1347
|
+
}
|
|
1348
|
+
recordException(exception, _time) {
|
|
1349
|
+
// TODO: Add support for exception schemas and data
|
|
1350
|
+
this.activity.error(exception);
|
|
1351
|
+
}
|
|
1352
|
+
}
|
|
1353
|
+
|
|
1354
|
+
class O11yOTelMetricsAPI {
|
|
1355
|
+
constructor(services) {
|
|
1356
|
+
this.services = services;
|
|
1357
|
+
}
|
|
1358
|
+
getMeter(name,
|
|
1359
|
+
// TODO: Not sure what to do with these?
|
|
1360
|
+
_version, __options) {
|
|
1361
|
+
const o11yInstrumentation = getInstrumentation(name);
|
|
1362
|
+
return new O11yMeter(o11yInstrumentation, this.services.logger);
|
|
1363
|
+
}
|
|
1364
|
+
}
|
|
1365
|
+
class O11yMeter {
|
|
1366
|
+
constructor(o11yInstrumentation, logger) {
|
|
1367
|
+
this.o11yInstrumentation = o11yInstrumentation;
|
|
1368
|
+
this.logger = logger;
|
|
1369
|
+
}
|
|
1370
|
+
createHistogram(name, _options) {
|
|
1371
|
+
return new O11yHistogram(name, this.o11yInstrumentation, this.logger);
|
|
1372
|
+
}
|
|
1373
|
+
createCounter(name, options) {
|
|
1374
|
+
if (options) {
|
|
1375
|
+
this.logger.warn('counter options not supported in O11y instrumentation');
|
|
1376
|
+
}
|
|
1377
|
+
return new O11yCounter(name, this.o11yInstrumentation, this.logger);
|
|
1378
|
+
}
|
|
1379
|
+
createUpDownCounter(_name, _options) {
|
|
1380
|
+
return new O11yUpDownCounter(this.logger);
|
|
1381
|
+
}
|
|
1382
|
+
createObservableGauge(_name, _options) {
|
|
1383
|
+
return new O11yObservableGuage(this.logger);
|
|
1384
|
+
}
|
|
1385
|
+
createObservableCounter(_name, _options) {
|
|
1386
|
+
return new O11yObservableCounter(this.logger);
|
|
1387
|
+
}
|
|
1388
|
+
createObservableUpDownCounter(_name, _options) {
|
|
1389
|
+
return new O11yObservableUpDownCounter(this.logger);
|
|
1390
|
+
}
|
|
1391
|
+
addBatchObservableCallback(_callback, _observables) {
|
|
1392
|
+
this.logger.warn('addBatchObservableCallback not supported yet');
|
|
1393
|
+
}
|
|
1394
|
+
removeBatchObservableCallback(_callback, _observables) {
|
|
1395
|
+
this.logger.warn('removeBatchObservableCallback not supported yet');
|
|
1396
|
+
}
|
|
1397
|
+
}
|
|
1398
|
+
class O11yCounter {
|
|
1399
|
+
constructor(name, o11yInstrumentation, logger) {
|
|
1400
|
+
this.name = name;
|
|
1401
|
+
this.o11yInstrumentation = o11yInstrumentation;
|
|
1402
|
+
this.logger = logger;
|
|
1403
|
+
}
|
|
1404
|
+
add(value, attributes, _context) {
|
|
1405
|
+
if (value < 0) {
|
|
1406
|
+
this.logger.warn(`Counter values must be non-negative. Got ${value}.`);
|
|
1407
|
+
return;
|
|
1408
|
+
}
|
|
1409
|
+
this.o11yInstrumentation.incrementCounter(this.name, value, undefined, sanitizeAttributes(attributes));
|
|
1410
|
+
}
|
|
1411
|
+
}
|
|
1412
|
+
class O11yHistogram {
|
|
1413
|
+
constructor(name, o11yInstrumentation, logger) {
|
|
1414
|
+
this.name = name;
|
|
1415
|
+
this.o11yInstrumentation = o11yInstrumentation;
|
|
1416
|
+
this.logger = logger;
|
|
1417
|
+
}
|
|
1418
|
+
record(value, attributes, _context) {
|
|
1419
|
+
if (value < 0) {
|
|
1420
|
+
this.logger.warn(`Histogram values must be non-negative. Got ${value}.`);
|
|
1421
|
+
return;
|
|
1422
|
+
}
|
|
1423
|
+
this.o11yInstrumentation.trackValue(this.name, value, undefined, sanitizeAttributes(attributes));
|
|
1424
|
+
}
|
|
1425
|
+
}
|
|
1426
|
+
class O11yUpDownCounter {
|
|
1427
|
+
constructor(logger) {
|
|
1428
|
+
this.logger = logger;
|
|
1429
|
+
}
|
|
1430
|
+
add(_value, _attributes, _context) {
|
|
1431
|
+
this.logger.warn('O11yUpDownCounter not supported yet.');
|
|
1432
|
+
}
|
|
1433
|
+
}
|
|
1434
|
+
class O11yObservableCounter {
|
|
1435
|
+
constructor(logger) {
|
|
1436
|
+
this.logger = logger;
|
|
1437
|
+
}
|
|
1438
|
+
addCallback(_callback) {
|
|
1439
|
+
this.logger.warn('O11yObservableCounter not supported yet. Defaulting to noop.');
|
|
1440
|
+
}
|
|
1441
|
+
removeCallback(_callback) {
|
|
1442
|
+
this.logger.warn('O11yObservableCounter not supported yet. Defaulting to noop.');
|
|
1443
|
+
}
|
|
1444
|
+
}
|
|
1445
|
+
class O11yObservableGuage {
|
|
1446
|
+
constructor(logger) {
|
|
1447
|
+
this.logger = logger;
|
|
1448
|
+
}
|
|
1449
|
+
addCallback(_callback) {
|
|
1450
|
+
this.logger.warn('O11yObservableGuage not supported yet. Defaulting to noop.');
|
|
1451
|
+
}
|
|
1452
|
+
removeCallback(_callback) {
|
|
1453
|
+
this.logger.warn('O11yObservableGuage not supported yet. Defaulting to noop.');
|
|
1454
|
+
}
|
|
1455
|
+
}
|
|
1456
|
+
class O11yObservableUpDownCounter {
|
|
1457
|
+
constructor(logger) {
|
|
1458
|
+
this.logger = logger;
|
|
1459
|
+
}
|
|
1460
|
+
addCallback(_callback) {
|
|
1461
|
+
this.logger.warn('O11yObservableUpDownCounter not supported yet. Defaulting to noop.');
|
|
1462
|
+
}
|
|
1463
|
+
removeCallback(_callback) {
|
|
1464
|
+
this.logger.warn('O11yObservableUpDownCounter not supported yet. Defaulting to noop.');
|
|
1465
|
+
}
|
|
1466
|
+
}
|
|
1467
|
+
function sanitizeAttributes(attributes) {
|
|
1468
|
+
if (!attributes)
|
|
1469
|
+
return;
|
|
1470
|
+
const metricTags = {};
|
|
1471
|
+
Object.entries(attributes).forEach(([key, value]) => {
|
|
1472
|
+
if (value !== undefined && !Array.isArray(value)) {
|
|
1473
|
+
metricTags[key] = value;
|
|
1474
|
+
}
|
|
1475
|
+
});
|
|
1476
|
+
return metricTags;
|
|
1477
|
+
}
|
|
1478
|
+
|
|
1479
|
+
class O11yInstrumentation {
|
|
1480
|
+
constructor(services) {
|
|
1481
|
+
this.services = services;
|
|
1482
|
+
this.currentTimeMs = () => performance.now();
|
|
1483
|
+
this.trace = new O11yOTelTraceAPI(this.services);
|
|
1484
|
+
this.metrics = new O11yOTelMetricsAPI(this.services);
|
|
1485
|
+
}
|
|
1486
|
+
}
|
|
1487
|
+
function buildServiceDescriptor$5(logger) {
|
|
1488
|
+
return {
|
|
1489
|
+
type: 'instrumentation',
|
|
1490
|
+
version: '1.0',
|
|
1491
|
+
service: new O11yInstrumentation({ logger }),
|
|
1492
|
+
};
|
|
1493
|
+
}
|
|
1494
|
+
|
|
1495
|
+
/**
|
|
1496
|
+
* Copyright (c) 2022, Salesforce, Inc.,
|
|
1497
|
+
* All rights reserved.
|
|
1498
|
+
* For full license text, see the LICENSE.txt file
|
|
1499
|
+
*/
|
|
1500
|
+
|
|
1501
|
+
|
|
1502
|
+
/* eslint-disable no-dupe-class-members */
|
|
1503
|
+
/**
|
|
1504
|
+
A utility class for recording actions made on a cache.
|
|
1505
|
+
*/
|
|
1506
|
+
class DefaultRecordableCache {
|
|
1507
|
+
constructor(baseCache) {
|
|
1508
|
+
this.baseCache = baseCache;
|
|
1509
|
+
this.keysRead = new Set();
|
|
1510
|
+
this.missingKeysRead = new Set();
|
|
1511
|
+
this.keysUpdated = new Set();
|
|
1512
|
+
this.metadataKeysUpdated = new Set();
|
|
1513
|
+
}
|
|
1514
|
+
delete(key) {
|
|
1515
|
+
this.keysUpdated.add(key);
|
|
1516
|
+
this.baseCache.delete(key);
|
|
1517
|
+
}
|
|
1518
|
+
get(key, options) {
|
|
1519
|
+
this.keysRead.add(key);
|
|
1520
|
+
const value = this.baseCache.get(key);
|
|
1521
|
+
if (value === undefined) {
|
|
1522
|
+
this.missingKeysRead.add(key);
|
|
1523
|
+
}
|
|
1524
|
+
if (options === null || options === void 0 ? void 0 : options.copy) {
|
|
1525
|
+
return deepCopy(value);
|
|
1526
|
+
}
|
|
1527
|
+
return value;
|
|
1528
|
+
}
|
|
1529
|
+
set(key, value) {
|
|
1530
|
+
this.keysUpdated.add(key);
|
|
1531
|
+
this.metadataKeysUpdated.add(key);
|
|
1532
|
+
this.baseCache.set(key, value);
|
|
1533
|
+
}
|
|
1534
|
+
setMetadata(key, cacheControlMetadata) {
|
|
1535
|
+
this.metadataKeysUpdated.add(key);
|
|
1536
|
+
this.baseCache.setMetadata(key, cacheControlMetadata);
|
|
1537
|
+
}
|
|
1538
|
+
length() {
|
|
1539
|
+
return this.baseCache.length();
|
|
1540
|
+
}
|
|
1541
|
+
keys() {
|
|
1542
|
+
return this.baseCache.keys();
|
|
1543
|
+
}
|
|
1544
|
+
entries() {
|
|
1545
|
+
return this.baseCache.entries();
|
|
1546
|
+
}
|
|
1547
|
+
record() {
|
|
1548
|
+
return new DefaultRecordableCache(this);
|
|
1549
|
+
}
|
|
1550
|
+
filter(predicate) {
|
|
1551
|
+
return new DefaultFilteredCache(this, predicate);
|
|
1552
|
+
}
|
|
1553
|
+
buildFixedTimeWritableCache(generatedTime) {
|
|
1554
|
+
return new FixedTimeWritableCache(this, generatedTime);
|
|
1555
|
+
}
|
|
1556
|
+
}
|
|
1557
|
+
/**
|
|
1558
|
+
A utility class for filtering cache entries based on a given predicate.
|
|
1559
|
+
*/
|
|
1560
|
+
class DefaultFilteredCache {
|
|
1561
|
+
constructor(baseCache, predicate) {
|
|
1562
|
+
this.baseCache = baseCache;
|
|
1563
|
+
this.predicate = predicate;
|
|
1564
|
+
}
|
|
1565
|
+
delete(key) {
|
|
1566
|
+
this.baseCache.delete(key);
|
|
1567
|
+
}
|
|
1568
|
+
get(key, options) {
|
|
1569
|
+
const result = this.baseCache.get(key);
|
|
1570
|
+
// if we're chaining filtered caches together, then it's possible the result will be undefined here
|
|
1571
|
+
if (result && this.predicate(key, result)) {
|
|
1572
|
+
if (options === null || options === void 0 ? void 0 : options.copy) {
|
|
1573
|
+
return deepCopy(result);
|
|
1574
|
+
}
|
|
1575
|
+
return result;
|
|
1576
|
+
}
|
|
1577
|
+
return undefined;
|
|
1578
|
+
}
|
|
1579
|
+
set(key, value) {
|
|
1580
|
+
this.baseCache.set(key, value);
|
|
1581
|
+
}
|
|
1582
|
+
setMetadata(key, cacheControlMetadata) {
|
|
1583
|
+
this.baseCache.setMetadata(key, cacheControlMetadata);
|
|
1584
|
+
}
|
|
1585
|
+
length() {
|
|
1586
|
+
return this.getFilteredKeys().size;
|
|
1587
|
+
}
|
|
1588
|
+
keys() {
|
|
1589
|
+
return this.getFilteredKeys();
|
|
1590
|
+
}
|
|
1591
|
+
entries() {
|
|
1592
|
+
return this.getFilteredEntries();
|
|
1593
|
+
}
|
|
1594
|
+
record() {
|
|
1595
|
+
return new DefaultRecordableCache(this);
|
|
1596
|
+
}
|
|
1597
|
+
filter(predicate) {
|
|
1598
|
+
return new DefaultFilteredCache(this, predicate);
|
|
1599
|
+
}
|
|
1600
|
+
getFilteredEntries() {
|
|
1601
|
+
return this.baseCache.entries().filter(([key, _value]) => {
|
|
1602
|
+
return this.get(key);
|
|
1603
|
+
});
|
|
1604
|
+
}
|
|
1605
|
+
getFilteredKeys() {
|
|
1606
|
+
const filteredKeySet = new Set();
|
|
1607
|
+
this.baseCache.keys().forEach((key) => {
|
|
1608
|
+
if (this.get(key)) {
|
|
1609
|
+
filteredKeySet.add(key);
|
|
1610
|
+
}
|
|
1611
|
+
});
|
|
1612
|
+
return filteredKeySet;
|
|
1613
|
+
}
|
|
1614
|
+
buildFixedTimeWritableCache(generatedTime) {
|
|
1615
|
+
return new FixedTimeWritableCache(this, generatedTime);
|
|
1616
|
+
}
|
|
1617
|
+
}
|
|
1618
|
+
/**
|
|
1619
|
+
A utility class for automatically setting the generated time on all write operations
|
|
1620
|
+
*/
|
|
1621
|
+
class FixedTimeWritableCache {
|
|
1622
|
+
constructor(baseCache, generatedTime) {
|
|
1623
|
+
this.baseCache = baseCache;
|
|
1624
|
+
this.generatedTime = generatedTime;
|
|
1625
|
+
}
|
|
1626
|
+
delete(key) {
|
|
1627
|
+
this.baseCache.delete(key);
|
|
1628
|
+
}
|
|
1629
|
+
get(key, options) {
|
|
1630
|
+
return this.baseCache.get(key, options);
|
|
1631
|
+
}
|
|
1632
|
+
set(key, value) {
|
|
1633
|
+
this.baseCache.set(key, {
|
|
1634
|
+
...value,
|
|
1635
|
+
metadata: {
|
|
1636
|
+
...value.metadata,
|
|
1637
|
+
cacheControl: { ...value.metadata.cacheControl, generatedTime: this.generatedTime },
|
|
1638
|
+
},
|
|
1639
|
+
});
|
|
1640
|
+
}
|
|
1641
|
+
setMetadata(key, cacheControlMetadata) {
|
|
1642
|
+
this.baseCache.setMetadata(key, {
|
|
1643
|
+
...cacheControlMetadata,
|
|
1644
|
+
generatedTime: this.generatedTime,
|
|
1645
|
+
});
|
|
1646
|
+
}
|
|
1647
|
+
length() {
|
|
1648
|
+
return this.baseCache.length();
|
|
1649
|
+
}
|
|
1650
|
+
keys() {
|
|
1651
|
+
return this.baseCache.keys();
|
|
1652
|
+
}
|
|
1653
|
+
entries() {
|
|
1654
|
+
return this.baseCache.entries();
|
|
1655
|
+
}
|
|
1656
|
+
record() {
|
|
1657
|
+
return new DefaultRecordableCache(this);
|
|
1658
|
+
}
|
|
1659
|
+
filter(predicate) {
|
|
1660
|
+
return new DefaultFilteredCache(this, predicate);
|
|
1661
|
+
}
|
|
1662
|
+
buildFixedTimeWritableCache(generatedTime) {
|
|
1663
|
+
return new FixedTimeWritableCache(this, generatedTime);
|
|
1664
|
+
}
|
|
1665
|
+
}
|
|
1666
|
+
|
|
1667
|
+
/* eslint-disable no-dupe-class-members */
|
|
1668
|
+
class DefaultCache {
|
|
1669
|
+
constructor() {
|
|
1670
|
+
this.data = {};
|
|
1671
|
+
}
|
|
1672
|
+
get(key, options) {
|
|
1673
|
+
if (options === null || options === void 0 ? void 0 : options.copy) {
|
|
1674
|
+
return deepCopy(this.data[key]);
|
|
1675
|
+
}
|
|
1676
|
+
return this.data[key];
|
|
1677
|
+
}
|
|
1678
|
+
/**
|
|
1679
|
+
* Adds the specified key/value to the cache.
|
|
1680
|
+
*
|
|
1681
|
+
* @param key key at which to store value
|
|
1682
|
+
* @param entry value to be stored
|
|
1683
|
+
*/
|
|
1684
|
+
set(key, entry) {
|
|
1685
|
+
if (entry.metadata.cacheControl.type === 'no-store') {
|
|
1686
|
+
return;
|
|
1687
|
+
}
|
|
1688
|
+
this.data[key] = {
|
|
1689
|
+
...entry,
|
|
1690
|
+
metadata: {
|
|
1691
|
+
...entry.metadata,
|
|
1692
|
+
type: entry.metadata.type || {
|
|
1693
|
+
namespace: 'OneStore:Internal',
|
|
1694
|
+
name: 'UnknownType',
|
|
1695
|
+
},
|
|
1696
|
+
cacheControl: {
|
|
1697
|
+
generatedTime: Date.now() / 1000,
|
|
1698
|
+
...entry.metadata.cacheControl,
|
|
1699
|
+
},
|
|
1700
|
+
},
|
|
1701
|
+
};
|
|
1702
|
+
}
|
|
1703
|
+
/**
|
|
1704
|
+
* Removes the cache entry associated with the specified key.
|
|
1705
|
+
*
|
|
1706
|
+
* @param key key to be removed from the store
|
|
1707
|
+
*/
|
|
1708
|
+
delete(key) {
|
|
1709
|
+
delete this.data[key];
|
|
1710
|
+
}
|
|
1711
|
+
/**
|
|
1712
|
+
* Sets the metadata for the specified key if the key is in cache.
|
|
1713
|
+
* If the key doesn't exist, it does nothing.
|
|
1714
|
+
*
|
|
1715
|
+
* @param key key at which to store metadata
|
|
1716
|
+
* @param cacheControlMetadata metadata to be stored
|
|
1717
|
+
*/
|
|
1718
|
+
setMetadata(key, cacheControlMetadata) {
|
|
1719
|
+
if (key in this.data) {
|
|
1720
|
+
this.data[key].metadata.cacheControl = {
|
|
1721
|
+
generatedTime: Date.now() / 1000,
|
|
1722
|
+
...cacheControlMetadata,
|
|
1723
|
+
};
|
|
1724
|
+
}
|
|
1725
|
+
}
|
|
1726
|
+
length() {
|
|
1727
|
+
return this.keys().size;
|
|
1728
|
+
}
|
|
1729
|
+
keys() {
|
|
1730
|
+
return new Set(Object.keys(this.data));
|
|
1731
|
+
}
|
|
1732
|
+
entries() {
|
|
1733
|
+
return Object.entries(this.data);
|
|
1734
|
+
}
|
|
1735
|
+
record() {
|
|
1736
|
+
return new DefaultRecordableCache(this);
|
|
1737
|
+
}
|
|
1738
|
+
filter(predicate) {
|
|
1739
|
+
return new DefaultFilteredCache(this, predicate);
|
|
1740
|
+
}
|
|
1741
|
+
buildFixedTimeWritableCache(generatedTime) {
|
|
1742
|
+
return new FixedTimeWritableCache(this, generatedTime);
|
|
1743
|
+
}
|
|
1744
|
+
}
|
|
1745
|
+
|
|
1746
|
+
function buildServiceDescriptor$4() {
|
|
1747
|
+
return {
|
|
1748
|
+
type: 'cache',
|
|
1749
|
+
version: '1.0',
|
|
1750
|
+
service: new DefaultCache(),
|
|
1751
|
+
};
|
|
1752
|
+
}
|
|
1753
|
+
|
|
1754
|
+
/**
|
|
1755
|
+
* Copyright (c) 2022, Salesforce, Inc.,
|
|
1756
|
+
* All rights reserved.
|
|
1757
|
+
* For full license text, see the LICENSE.txt file
|
|
1758
|
+
*/
|
|
1759
|
+
|
|
1760
|
+
|
|
1761
|
+
class CacheControlStrategy {
|
|
1762
|
+
constructor(services, config, requestRunner) {
|
|
1763
|
+
this.services = services;
|
|
1764
|
+
this.config = config;
|
|
1765
|
+
this.requestRunner = requestRunner;
|
|
1766
|
+
this.filteredCache = this.services.cache.filter((_, entry) => {
|
|
1767
|
+
const { cacheControl } = entry.metadata;
|
|
1768
|
+
return !this.expiredChecks.some((check) => check(cacheControl));
|
|
1769
|
+
});
|
|
1770
|
+
}
|
|
1771
|
+
get expiredChecks() {
|
|
1772
|
+
return [
|
|
1773
|
+
(cacheControlMetadata) => cacheControlMetadata.type === 'max-age' &&
|
|
1774
|
+
this.config.now > cacheControlMetadata.generatedTime + cacheControlMetadata.maxAge,
|
|
1775
|
+
(cacheControlMetadata) => cacheControlMetadata.type === 'max-age' && cacheControlMetadata.maxAge <= 0,
|
|
1776
|
+
(cacheControlMetadata) => cacheControlMetadata.type === 'no-store',
|
|
1777
|
+
(cacheControlMetadata) => cacheControlMetadata.type === 'no-cache' &&
|
|
1778
|
+
cacheControlMetadata.generatedTime < this.config.now,
|
|
1779
|
+
];
|
|
1780
|
+
}
|
|
1781
|
+
}
|
|
1782
|
+
|
|
1783
|
+
class NoCacheCacheControlStrategy extends CacheControlStrategy {
|
|
1784
|
+
execute() {
|
|
1785
|
+
// TODO - should be using wrapper here that suppresses no-store writes
|
|
1786
|
+
const tempCache = this.filteredCache;
|
|
1787
|
+
return new Promise(async (resolve, reject) => {
|
|
1788
|
+
try {
|
|
1789
|
+
let readResult = ok(undefined);
|
|
1790
|
+
for await (const rfnResult of this.requestRunner.requestFromNetwork()) {
|
|
1791
|
+
if (rfnResult) {
|
|
1792
|
+
const result = await this.services.cacheInclusionPolicy.write({
|
|
1793
|
+
l1: tempCache,
|
|
1794
|
+
writeToL1: (l1) => this.requestRunner.writeToCache(l1, rfnResult),
|
|
1795
|
+
});
|
|
1796
|
+
if (result.isErr()) {
|
|
1797
|
+
return resolve(result);
|
|
1798
|
+
}
|
|
1799
|
+
}
|
|
1800
|
+
// give readFromCache another shot at the cache
|
|
1801
|
+
readResult = await this.services.cacheInclusionPolicy.read({
|
|
1802
|
+
l1: tempCache,
|
|
1803
|
+
readFromL1: (l1) => this.requestRunner.readFromCache(l1),
|
|
1804
|
+
});
|
|
1805
|
+
if (readResult.isOk()) {
|
|
1806
|
+
resolve(readResult);
|
|
1807
|
+
}
|
|
1808
|
+
}
|
|
1809
|
+
return resolve(readResult);
|
|
1810
|
+
}
|
|
1811
|
+
catch (error) {
|
|
1812
|
+
return reject(error);
|
|
1813
|
+
}
|
|
1814
|
+
});
|
|
1815
|
+
}
|
|
1816
|
+
}
|
|
1817
|
+
|
|
1818
|
+
class MaxAgeCacheControlStrategy extends CacheControlStrategy {
|
|
1819
|
+
execute(options) {
|
|
1820
|
+
const startTime = this.services.instrumentation
|
|
1821
|
+
? this.services.instrumentation.currentTimeMs()
|
|
1822
|
+
: 0;
|
|
1823
|
+
return this.services.cacheInclusionPolicy
|
|
1824
|
+
.read({
|
|
1825
|
+
l1: this.filteredCache,
|
|
1826
|
+
readFromL1: (l1) => this.requestRunner.readFromCache(l1),
|
|
1827
|
+
})
|
|
1828
|
+
.then((value) => {
|
|
1829
|
+
// ignore error from initial cache read
|
|
1830
|
+
if (value.isOk()) {
|
|
1831
|
+
this.collectCacheHitInstrumentation(startTime, options === null || options === void 0 ? void 0 : options.instrumentationAttributes);
|
|
1832
|
+
return ok(undefined);
|
|
1833
|
+
}
|
|
1834
|
+
this.collectCacheMissInstrumentation(startTime, options === null || options === void 0 ? void 0 : options.instrumentationAttributes);
|
|
1835
|
+
// initial cache read failed, awaits are ok beyond this point since the data
|
|
1836
|
+
// must come from network requests
|
|
1837
|
+
const tempCache = this.filteredCache;
|
|
1838
|
+
return new Promise(async (resolve, reject) => {
|
|
1839
|
+
try {
|
|
1840
|
+
let readResult = ok(undefined);
|
|
1841
|
+
for await (const rfnResult of this.requestRunner.requestFromNetwork()) {
|
|
1842
|
+
// async generator has a value to ingest
|
|
1843
|
+
if (rfnResult) {
|
|
1844
|
+
const result = await this.services.cacheInclusionPolicy.write({
|
|
1845
|
+
l1: tempCache,
|
|
1846
|
+
writeToL1: (l1) => this.requestRunner.writeToCache(l1, rfnResult),
|
|
1847
|
+
});
|
|
1848
|
+
if (result.isErr()) {
|
|
1849
|
+
return resolve(err(result.error));
|
|
1850
|
+
}
|
|
1851
|
+
}
|
|
1852
|
+
// give readFromCache another shot at the cache
|
|
1853
|
+
readResult = await this.services.cacheInclusionPolicy.read({
|
|
1854
|
+
l1: tempCache,
|
|
1855
|
+
readFromL1: (l1) => this.requestRunner.readFromCache(l1),
|
|
1856
|
+
});
|
|
1857
|
+
if (readResult.isOk()) {
|
|
1858
|
+
resolve(ok(undefined));
|
|
1859
|
+
}
|
|
1860
|
+
}
|
|
1861
|
+
return resolve(readResult);
|
|
1862
|
+
}
|
|
1863
|
+
catch (e) {
|
|
1864
|
+
return reject(e);
|
|
1865
|
+
}
|
|
1866
|
+
});
|
|
1867
|
+
});
|
|
1868
|
+
}
|
|
1869
|
+
collectCacheHitInstrumentation(startTime, instrumentationAttributes) {
|
|
1870
|
+
if (this.services.instrumentation) {
|
|
1871
|
+
const meter = this.services.instrumentation.metrics.getMeter('onestore');
|
|
1872
|
+
meter
|
|
1873
|
+
.createCounter(`command.max-age.cache-hit.count`)
|
|
1874
|
+
.add(1, instrumentationAttributes);
|
|
1875
|
+
meter
|
|
1876
|
+
.createHistogram(`command.max-age.cache-hit.duration`)
|
|
1877
|
+
.record(this.services.instrumentation.currentTimeMs() - startTime, instrumentationAttributes);
|
|
1878
|
+
}
|
|
1879
|
+
}
|
|
1880
|
+
collectCacheMissInstrumentation(startTime, instrumentationAttributes) {
|
|
1881
|
+
if (this.services.instrumentation) {
|
|
1882
|
+
const meter = this.services.instrumentation.metrics.getMeter('onestore');
|
|
1883
|
+
meter
|
|
1884
|
+
.createCounter(`command.max-age.cache-miss.count`)
|
|
1885
|
+
.add(1, instrumentationAttributes);
|
|
1886
|
+
meter
|
|
1887
|
+
.createHistogram(`command.max-age.cache-miss.duration`)
|
|
1888
|
+
.record(this.services.instrumentation.currentTimeMs() - startTime, instrumentationAttributes);
|
|
1889
|
+
}
|
|
1890
|
+
}
|
|
1891
|
+
get expiredChecks() {
|
|
1892
|
+
const config = this.config;
|
|
1893
|
+
return [
|
|
1894
|
+
...super.expiredChecks,
|
|
1895
|
+
(cacheControlMetadata) => {
|
|
1896
|
+
return cacheControlMetadata.generatedTime + config.requestMaxAge < config.now;
|
|
1897
|
+
},
|
|
1898
|
+
];
|
|
1899
|
+
}
|
|
1900
|
+
}
|
|
1901
|
+
|
|
1902
|
+
/**
|
|
1903
|
+
* A class that allows the execution of requested cache control strategies,
|
|
1904
|
+
* while also enforcing the canonical cache control metadata.
|
|
1905
|
+
*/
|
|
1906
|
+
class CacheController {
|
|
1907
|
+
constructor(services) {
|
|
1908
|
+
this.services = services;
|
|
1909
|
+
}
|
|
1910
|
+
execute(config, requestRunner, options) {
|
|
1911
|
+
const strategy = this.getCacheControlStrategy(config, requestRunner);
|
|
1912
|
+
return strategy.execute(options);
|
|
1913
|
+
}
|
|
1914
|
+
getCacheControlStrategy(config, requestRunner) {
|
|
1915
|
+
if (config.type === 'max-age') {
|
|
1916
|
+
return new MaxAgeCacheControlStrategy(this.services, config, requestRunner);
|
|
1917
|
+
}
|
|
1918
|
+
else if (config.type === 'no-cache') {
|
|
1919
|
+
return new NoCacheCacheControlStrategy(this.services, config, requestRunner);
|
|
1920
|
+
}
|
|
1921
|
+
throw new Error(`Unknown cache control strategy ${config.type}`);
|
|
1922
|
+
}
|
|
1923
|
+
/**
|
|
1924
|
+
* Finds cache entries that match the given query.
|
|
1925
|
+
* Returns an async generator that yields `[key, entry]`.
|
|
1926
|
+
*/
|
|
1927
|
+
async *find(query) {
|
|
1928
|
+
yield* this.services.cacheInclusionPolicy.find(query);
|
|
1929
|
+
}
|
|
1930
|
+
/**
|
|
1931
|
+
* Finds and modifies cache entries that match the given query.
|
|
1932
|
+
* Extends `find(query)` and returns an async generator of modified keys.
|
|
1933
|
+
*/
|
|
1934
|
+
async *findAndModify(query, cacheUpdate) {
|
|
1935
|
+
yield* this.services.cacheInclusionPolicy.findAndModify(query, cacheUpdate);
|
|
1936
|
+
}
|
|
1937
|
+
}
|
|
1938
|
+
|
|
1939
|
+
function buildServiceDescriptor$3(cache, cacheInclusionPolicy) {
|
|
1940
|
+
return {
|
|
1941
|
+
type: 'cacheController',
|
|
1942
|
+
version: '1.0',
|
|
1943
|
+
service: new CacheController({ cache, cacheInclusionPolicy }),
|
|
1944
|
+
};
|
|
1945
|
+
}
|
|
1946
|
+
|
|
1947
|
+
/**
|
|
1948
|
+
* Copyright (c) 2022, Salesforce, Inc.,
|
|
1949
|
+
* All rights reserved.
|
|
1950
|
+
* For full license text, see the LICENSE.txt file
|
|
1951
|
+
*/
|
|
1952
|
+
|
|
1953
|
+
|
|
1954
|
+
const EventTypeWildcard = Symbol('EventTypeWildcard');
|
|
1955
|
+
|
|
1956
|
+
/**
|
|
1957
|
+
* A simple implementation of PubSubService.
|
|
1958
|
+
*/
|
|
1959
|
+
class DefaultPubSubService {
|
|
1960
|
+
constructor() {
|
|
1961
|
+
this.subscriptions = new Map();
|
|
1962
|
+
}
|
|
1963
|
+
subscribe(subscription) {
|
|
1964
|
+
let eventTypeSubscriptions = this.subscriptions.get(subscription.type);
|
|
1965
|
+
if (eventTypeSubscriptions === undefined) {
|
|
1966
|
+
eventTypeSubscriptions = [];
|
|
1967
|
+
this.subscriptions.set(subscription.type, eventTypeSubscriptions);
|
|
1968
|
+
}
|
|
1969
|
+
eventTypeSubscriptions.push(subscription);
|
|
1970
|
+
return () => {
|
|
1971
|
+
this.subscriptions.set(subscription.type, this.subscriptions.get(subscription.type).filter((value) => value !== subscription));
|
|
1972
|
+
};
|
|
1973
|
+
}
|
|
1974
|
+
publish(event) {
|
|
1975
|
+
const promises = [];
|
|
1976
|
+
const subscriptions = this.getSubscriptions(event);
|
|
1977
|
+
subscriptions.forEach((subscription) => {
|
|
1978
|
+
// need to check that the subscription hasn't been unsubscribed during one of the callbacks
|
|
1979
|
+
if (!this.getSubscriptions(event).includes(subscription)) {
|
|
1980
|
+
return;
|
|
1981
|
+
}
|
|
1982
|
+
// Ensure callback maintains its context
|
|
1983
|
+
const returnVal = subscription.callback.call(subscription, event);
|
|
1984
|
+
if (isPromiseLike(returnVal)) {
|
|
1985
|
+
promises.push(returnVal);
|
|
1986
|
+
}
|
|
1987
|
+
});
|
|
1988
|
+
if (promises.length > 0) {
|
|
1989
|
+
return Promise.all(promises).then(() => undefined);
|
|
1990
|
+
}
|
|
1991
|
+
return resolvedPromiseLike(undefined);
|
|
1992
|
+
}
|
|
1993
|
+
getSubscriptions(event) {
|
|
1994
|
+
const eventTypeSubscriptions = this.subscriptions.get(event.type);
|
|
1995
|
+
const wildcardSubscriptions = this.subscriptions.get(EventTypeWildcard);
|
|
1996
|
+
if (eventTypeSubscriptions === undefined && wildcardSubscriptions === undefined) {
|
|
1997
|
+
return [];
|
|
1998
|
+
}
|
|
1999
|
+
let matchingSubscriptions = [];
|
|
2000
|
+
if (eventTypeSubscriptions !== undefined) {
|
|
2001
|
+
matchingSubscriptions = eventTypeSubscriptions.filter((subscription) => {
|
|
2002
|
+
if (subscription.predicate) {
|
|
2003
|
+
return subscription.predicate(event);
|
|
2004
|
+
}
|
|
2005
|
+
return true;
|
|
2006
|
+
});
|
|
2007
|
+
}
|
|
2008
|
+
matchingSubscriptions = [...matchingSubscriptions, ...(wildcardSubscriptions || [])];
|
|
2009
|
+
return matchingSubscriptions;
|
|
2010
|
+
}
|
|
2011
|
+
}
|
|
2012
|
+
/**
|
|
2013
|
+
* Constructs a default PubSubService
|
|
2014
|
+
*
|
|
2015
|
+
* @returns default PubSubServiceDescriptor
|
|
2016
|
+
*/
|
|
2017
|
+
function buildServiceDescriptor$2() {
|
|
2018
|
+
return {
|
|
2019
|
+
type: 'pubSub',
|
|
2020
|
+
version: '1.0',
|
|
2021
|
+
service: new DefaultPubSubService(),
|
|
2022
|
+
};
|
|
2023
|
+
}
|
|
2024
|
+
|
|
2025
|
+
/**
|
|
2026
|
+
* Copyright (c) 2022, Salesforce, Inc.,
|
|
2027
|
+
* All rights reserved.
|
|
2028
|
+
* For full license text, see the LICENSE.txt file
|
|
2029
|
+
*/
|
|
2030
|
+
|
|
2031
|
+
|
|
2032
|
+
/**
|
|
2033
|
+
* CacheInclusionPolicy is an interface for accessing the cache
|
|
2034
|
+
* and synchronizing the cache data with another external cache.
|
|
2035
|
+
*
|
|
2036
|
+
* https://en.wikipedia.org/wiki/Cache_inclusion_policy
|
|
2037
|
+
*/
|
|
2038
|
+
class CacheInclusionPolicyService {
|
|
2039
|
+
}
|
|
2040
|
+
|
|
2041
|
+
const isAndQuery = (query) => '$and' in query;
|
|
2042
|
+
const isOrQuery = (query) => '$or' in query;
|
|
2043
|
+
const isNotQuery = (query) => '$not' in query;
|
|
2044
|
+
const matchesMetadata = (metadataQuery, cacheControl) => {
|
|
2045
|
+
var _a;
|
|
2046
|
+
if ('cacheControlType' in metadataQuery &&
|
|
2047
|
+
cacheControl.type !== metadataQuery.cacheControlType.$eq) {
|
|
2048
|
+
return false;
|
|
2049
|
+
}
|
|
2050
|
+
if ('maxAge' in metadataQuery && cacheControl.type === 'max-age') {
|
|
2051
|
+
const maxAge = (_a = cacheControl.maxAge) !== null && _a !== void 0 ? _a : 0;
|
|
2052
|
+
if ((metadataQuery.maxAge.$gte !== undefined && maxAge < metadataQuery.maxAge.$gte) ||
|
|
2053
|
+
(metadataQuery.maxAge.$lte !== undefined && maxAge > metadataQuery.maxAge.$lte)) {
|
|
2054
|
+
return false;
|
|
2055
|
+
}
|
|
2056
|
+
}
|
|
2057
|
+
return true;
|
|
2058
|
+
};
|
|
2059
|
+
function queryToPredicate(query) {
|
|
2060
|
+
return (key, entry) => {
|
|
2061
|
+
if (!query)
|
|
2062
|
+
return false;
|
|
2063
|
+
if (isAndQuery(query))
|
|
2064
|
+
return query.$and.every((subQuery) => queryToPredicate(subQuery)(key, entry));
|
|
2065
|
+
if (isOrQuery(query))
|
|
2066
|
+
return query.$or.some((subQuery) => queryToPredicate(subQuery)(key, entry));
|
|
2067
|
+
if (isNotQuery(query))
|
|
2068
|
+
return !queryToPredicate(query.$not)(key, entry);
|
|
2069
|
+
if ('key' in query)
|
|
2070
|
+
return matchesKey(query.key, key);
|
|
2071
|
+
if ('metadata' in query)
|
|
2072
|
+
return matchesMetadata(query.metadata, entry.metadata.cacheControl);
|
|
2073
|
+
if ('value' in query)
|
|
2074
|
+
return false; // TODO: Not implemented
|
|
2075
|
+
throw new Error('Unknown Query Operation');
|
|
2076
|
+
};
|
|
2077
|
+
}
|
|
2078
|
+
function matchesKey(keyQuery, key) {
|
|
2079
|
+
if ('$regex' in keyQuery) {
|
|
2080
|
+
return keyQuery.$regex.test(key);
|
|
2081
|
+
}
|
|
2082
|
+
return false;
|
|
2083
|
+
}
|
|
2084
|
+
|
|
2085
|
+
/**
|
|
2086
|
+
* Processes a cache update operation and determines the appropriate modification.
|
|
2087
|
+
*
|
|
2088
|
+
* This function analyzes the provided `update` and the `existing` cache entry
|
|
2089
|
+
* to determine the necessary update type. It returns one of three possible outcomes:
|
|
2090
|
+
*
|
|
2091
|
+
* - `{ type: 'entry', entry }`: A full cache entry update, including both value and metadata.
|
|
2092
|
+
* - `{ type: 'metadata', metadata }`: A metadata-only update, leaving the value unchanged.
|
|
2093
|
+
* - `{ type: 'no-op' }`: No changes are needed, and the cache should remain as is.
|
|
2094
|
+
*
|
|
2095
|
+
* @param update - The cache update operation to apply.
|
|
2096
|
+
* @param existing - The existing cache entry being modified.
|
|
2097
|
+
* @returns An object indicating the type of update:
|
|
2098
|
+
* - A full cache entry update (`type: 'entry'`)
|
|
2099
|
+
* - A metadata-only update (`type: 'metadata'`)
|
|
2100
|
+
* - A no-op (`type: 'no-op'`) if no changes are required.
|
|
2101
|
+
*/
|
|
2102
|
+
function buildUpdate(update, existing) {
|
|
2103
|
+
switch (update.type) {
|
|
2104
|
+
case 'invalidate':
|
|
2105
|
+
const updatedCacheControl = buildInvalidatedCacheControl(existing.metadata.cacheControl);
|
|
2106
|
+
return updatedCacheControl !== undefined
|
|
2107
|
+
? { type: 'metadata', metadata: updatedCacheControl }
|
|
2108
|
+
: { type: 'no-op' };
|
|
2109
|
+
default:
|
|
2110
|
+
throw new Error(`Invalid update operation: ${update.type}`);
|
|
2111
|
+
}
|
|
2112
|
+
}
|
|
2113
|
+
/**
|
|
2114
|
+
* Builds an updated CacheControlMetadata object that invalidates the cache entry.
|
|
2115
|
+
*
|
|
2116
|
+
* @param existingCacheControl - The current CacheControlMetadata.
|
|
2117
|
+
* @returns A new CacheControlMetadata object with `maxAge` set to `0`, or undefined if no changes are needed.
|
|
2118
|
+
*/
|
|
2119
|
+
function buildInvalidatedCacheControl(existingCacheControl) {
|
|
2120
|
+
switch (existingCacheControl.type) {
|
|
2121
|
+
case 'max-age':
|
|
2122
|
+
case 'stale-while-revalidate':
|
|
2123
|
+
if (existingCacheControl.maxAge !== 0) {
|
|
2124
|
+
return {
|
|
2125
|
+
...existingCacheControl,
|
|
2126
|
+
maxAge: 0,
|
|
2127
|
+
};
|
|
2128
|
+
}
|
|
2129
|
+
}
|
|
2130
|
+
return undefined; // No-op: no changes
|
|
2131
|
+
}
|
|
2132
|
+
|
|
2133
|
+
/**
|
|
2134
|
+
* Implementation of CacheInclusionPolicy that uses a single level, in memory,
|
|
2135
|
+
* synchronous L1 cache.
|
|
2136
|
+
*/
|
|
2137
|
+
class InMemoryCacheInclusionPolicy extends CacheInclusionPolicyService {
|
|
2138
|
+
constructor(services) {
|
|
2139
|
+
super();
|
|
2140
|
+
this.services = services;
|
|
2141
|
+
}
|
|
2142
|
+
/**
|
|
2143
|
+
* Reads data out of a single level in memory store.
|
|
2144
|
+
*/
|
|
2145
|
+
read(options) {
|
|
2146
|
+
const { l1, readFromL1 } = options;
|
|
2147
|
+
// l1 is all we've got
|
|
2148
|
+
return readFromL1(l1);
|
|
2149
|
+
}
|
|
2150
|
+
/**
|
|
2151
|
+
* Writes data to a single level in memory store.
|
|
2152
|
+
*/
|
|
2153
|
+
write(options) {
|
|
2154
|
+
const { l1, writeToL1 } = options;
|
|
2155
|
+
return writeToL1(l1);
|
|
2156
|
+
}
|
|
2157
|
+
/**
|
|
2158
|
+
* Finds cache entries that match the given query.
|
|
2159
|
+
* Returns an async generator that yields `[key, entry]`.
|
|
2160
|
+
*/
|
|
2161
|
+
async *find(query) {
|
|
2162
|
+
const cache = this.services.cache;
|
|
2163
|
+
const predicate = queryToPredicate(query);
|
|
2164
|
+
const filteredEntries = cache.filter(predicate).entries();
|
|
2165
|
+
for (const entry of filteredEntries) {
|
|
2166
|
+
yield entry;
|
|
2167
|
+
}
|
|
2168
|
+
}
|
|
2169
|
+
/**
|
|
2170
|
+
* Finds and modifies cache entries that match the given query.
|
|
2171
|
+
* Extends `find(query)` and returns an async generator of modified keys.
|
|
2172
|
+
*/
|
|
2173
|
+
async *findAndModify(query, cacheUpdate) {
|
|
2174
|
+
const cache = this.services.cache;
|
|
2175
|
+
for await (const [key, value] of this.find(query)) {
|
|
2176
|
+
const update = buildUpdate(cacheUpdate, value);
|
|
2177
|
+
switch (update.type) {
|
|
2178
|
+
case 'entry':
|
|
2179
|
+
this.write({
|
|
2180
|
+
l1: cache,
|
|
2181
|
+
writeToL1: (l1) => resolvedPromiseLike(ok(l1.set(key, update.entry))),
|
|
2182
|
+
});
|
|
2183
|
+
yield key;
|
|
2184
|
+
break;
|
|
2185
|
+
case 'metadata':
|
|
2186
|
+
this.write({
|
|
2187
|
+
l1: cache,
|
|
2188
|
+
writeToL1: (l1) => resolvedPromiseLike(ok(l1.setMetadata(key, update.metadata))),
|
|
2189
|
+
});
|
|
2190
|
+
yield key;
|
|
2191
|
+
break;
|
|
2192
|
+
}
|
|
2193
|
+
}
|
|
2194
|
+
}
|
|
2195
|
+
}
|
|
2196
|
+
/**
|
|
2197
|
+
* Constructs an in-memory-only CacheInclusionPolicy.
|
|
2198
|
+
*
|
|
2199
|
+
* @returns in-memory-only CacheInclusionPolicy
|
|
2200
|
+
*/
|
|
2201
|
+
function buildInMemoryCacheInclusionPolicyService(cache) {
|
|
2202
|
+
return {
|
|
2203
|
+
service: new InMemoryCacheInclusionPolicy({ cache }),
|
|
2204
|
+
type: 'cacheInclusionPolicy',
|
|
2205
|
+
version: '1.0',
|
|
2206
|
+
};
|
|
2207
|
+
}
|
|
2208
|
+
|
|
2209
|
+
/**
|
|
2210
|
+
* Copyright (c) 2022, Salesforce, Inc.,
|
|
2211
|
+
* All rights reserved.
|
|
2212
|
+
* For full license text, see the LICENSE.txt file
|
|
2213
|
+
*/
|
|
2214
|
+
|
|
2215
|
+
|
|
2216
|
+
/**
|
|
2217
|
+
* An implementation of StreamingCommand that handles NDJSON streams.
|
|
2218
|
+
*/
|
|
2219
|
+
class NDJSONCommand extends StreamingCommand {
|
|
2220
|
+
constructor(services) {
|
|
2221
|
+
super(services);
|
|
2222
|
+
this.services = services;
|
|
2223
|
+
}
|
|
2224
|
+
/**
|
|
2225
|
+
* Decodes the bytes returned by fetch into the correct shape.
|
|
2226
|
+
*
|
|
2227
|
+
* @param byteStream ReadableStream<Uint8Array> returned by fetch
|
|
2228
|
+
* @returns ReadableStream<T> that will be the result of the Command
|
|
2229
|
+
*/
|
|
2230
|
+
decodeByteStream(byteStream) {
|
|
2231
|
+
return this.decodeNDJSONStream(byteStream
|
|
2232
|
+
// eslint-disable-next-line no-undef
|
|
2233
|
+
.pipeThrough(new TextDecoderStream())
|
|
2234
|
+
.pipeThrough(new NDJSONParsingStream()));
|
|
2235
|
+
}
|
|
2236
|
+
}
|
|
2237
|
+
/**
|
|
2238
|
+
* A TransformStream that parses NDJSON data according to the NDJSON spec:
|
|
2239
|
+
* https://github.com/ndjson/ndjson-spec
|
|
2240
|
+
*/
|
|
2241
|
+
class NDJSONParsingStream extends TransformStream {
|
|
2242
|
+
constructor() {
|
|
2243
|
+
let partialLine = '';
|
|
2244
|
+
super({
|
|
2245
|
+
transform(chunk, controller) {
|
|
2246
|
+
const text = partialLine + chunk;
|
|
2247
|
+
const lines = text.split(/\r?\n/);
|
|
2248
|
+
partialLine = lines.pop() || '';
|
|
2249
|
+
for (const line of lines) {
|
|
2250
|
+
const trimmed = line.trim();
|
|
2251
|
+
if (trimmed === '')
|
|
2252
|
+
continue;
|
|
2253
|
+
try {
|
|
2254
|
+
const parsed = JSON.parse(trimmed);
|
|
2255
|
+
controller.enqueue(parsed);
|
|
2256
|
+
}
|
|
2257
|
+
catch (e) {
|
|
2258
|
+
throw new Error(`Invalid NDJSON line: ${line}`);
|
|
2259
|
+
}
|
|
2260
|
+
}
|
|
2261
|
+
},
|
|
2262
|
+
flush(controller) {
|
|
2263
|
+
const trimmed = partialLine.trim();
|
|
2264
|
+
if (trimmed !== '') {
|
|
2265
|
+
try {
|
|
2266
|
+
const parsed = JSON.parse(trimmed);
|
|
2267
|
+
controller.enqueue(parsed);
|
|
2268
|
+
}
|
|
2269
|
+
catch (e) {
|
|
2270
|
+
throw new Error(`Invalid NDJSON final line: ${partialLine}`);
|
|
2271
|
+
}
|
|
2272
|
+
}
|
|
2273
|
+
},
|
|
2274
|
+
});
|
|
2275
|
+
}
|
|
2276
|
+
}
|
|
2277
|
+
|
|
2278
|
+
function buildServiceDescriptor$1() {
|
|
2279
|
+
return {
|
|
2280
|
+
type: 'NDJSONCommandBaseClass',
|
|
2281
|
+
version: '1.0',
|
|
2282
|
+
service: NDJSONCommand,
|
|
2283
|
+
};
|
|
2284
|
+
}
|
|
2285
|
+
|
|
2286
|
+
/**
|
|
2287
|
+
* Copyright (c) 2022, Salesforce, Inc.,
|
|
2288
|
+
* All rights reserved.
|
|
2289
|
+
* For full license text, see the LICENSE.txt file
|
|
2290
|
+
*/
|
|
2291
|
+
|
|
2292
|
+
function e(e){this.message=e;}e.prototype=new Error,e.prototype.name="InvalidCharacterError";var r="undefined"!=typeof window&&window.atob&&window.atob.bind(window)||function(r){var t=String(r).replace(/=+$/,"");if(t.length%4==1)throw new e("'atob' failed: The string to be decoded is not correctly encoded.");for(var n,o,a=0,i=0,c="";o=t.charAt(i++);~o&&(n=a%4?64*n+o:o,a++%4)?c+=String.fromCharCode(255&n>>(-2*a&6)):0)o="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=".indexOf(o);return c};function t(e){var t=e.replace(/-/g,"+").replace(/_/g,"/");switch(t.length%4){case 0:break;case 2:t+="==";break;case 3:t+="=";break;default:throw "Illegal base64url string!"}try{return function(e){return decodeURIComponent(r(e).replace(/(.)/g,(function(e,r){var t=r.charCodeAt(0).toString(16).toUpperCase();return t.length<2&&(t="0"+t),"%"+t})))}(t)}catch(e){return r(t)}}function n(e){this.message=e;}function o(e,r){if("string"!=typeof e)throw new n("Invalid token specified");var o=!0===(r=r||{}).header?0:1;try{return JSON.parse(t(e.split(".")[o]))}catch(e){throw new n("Invalid token specified: "+e.message)}}n.prototype=new Error,n.prototype.name="InvalidTokenError";
|
|
2293
|
+
|
|
2294
|
+
/**
|
|
2295
|
+
* Copyright (c) 2022, Salesforce, Inc.,
|
|
2296
|
+
* All rights reserved.
|
|
2297
|
+
* For full license text, see the LICENSE.txt file
|
|
2298
|
+
*/
|
|
2299
|
+
|
|
2300
|
+
const LogLevelMap = {
|
|
2301
|
+
TRACE: 4,
|
|
2302
|
+
DEBUG: 3,
|
|
2303
|
+
INFO: 2,
|
|
2304
|
+
WARN: 1,
|
|
2305
|
+
ERROR: 0,
|
|
2306
|
+
};
|
|
2307
|
+
/**
|
|
2308
|
+
* A simple Logger implementation.
|
|
2309
|
+
*/
|
|
2310
|
+
class ConsoleLogger {
|
|
2311
|
+
constructor(level = 'WARN',
|
|
2312
|
+
// eslint-disable-next-line no-console
|
|
2313
|
+
printer = console.log, formatter = (level, message) => `${level}: ${message}`) {
|
|
2314
|
+
this.level = level;
|
|
2315
|
+
this.printer = printer;
|
|
2316
|
+
this.formatter = formatter;
|
|
2317
|
+
this.messages = [];
|
|
2318
|
+
}
|
|
2319
|
+
trace(message) {
|
|
2320
|
+
this.log('TRACE', message);
|
|
2321
|
+
}
|
|
2322
|
+
debug(message) {
|
|
2323
|
+
this.log('DEBUG', message);
|
|
2324
|
+
}
|
|
2325
|
+
info(message) {
|
|
2326
|
+
this.log('INFO', message);
|
|
2327
|
+
}
|
|
2328
|
+
warn(message) {
|
|
2329
|
+
this.log('WARN', message);
|
|
2330
|
+
}
|
|
2331
|
+
error(message) {
|
|
2332
|
+
this.log('ERROR', message);
|
|
2333
|
+
}
|
|
2334
|
+
log(level, message) {
|
|
2335
|
+
if (LogLevelMap[level] > LogLevelMap[this.level]) {
|
|
2336
|
+
return;
|
|
2337
|
+
}
|
|
2338
|
+
this.printer(this.formatter(level, message));
|
|
2339
|
+
}
|
|
2340
|
+
}
|
|
2341
|
+
/**
|
|
2342
|
+
* Constructs a new LoggerService.
|
|
2343
|
+
*/
|
|
2344
|
+
function loggerService(level, printer, formatter) {
|
|
2345
|
+
return new ConsoleLogger(level, printer, formatter);
|
|
2346
|
+
}
|
|
2347
|
+
|
|
2348
|
+
var HttpStatusCode;
|
|
2349
|
+
(function (HttpStatusCode) {
|
|
2350
|
+
HttpStatusCode[HttpStatusCode["Ok"] = 200] = "Ok";
|
|
2351
|
+
HttpStatusCode[HttpStatusCode["Created"] = 201] = "Created";
|
|
2352
|
+
HttpStatusCode[HttpStatusCode["NoContent"] = 204] = "NoContent";
|
|
2353
|
+
HttpStatusCode[HttpStatusCode["NotModified"] = 304] = "NotModified";
|
|
2354
|
+
HttpStatusCode[HttpStatusCode["BadRequest"] = 400] = "BadRequest";
|
|
2355
|
+
HttpStatusCode[HttpStatusCode["Unauthorized"] = 401] = "Unauthorized";
|
|
2356
|
+
HttpStatusCode[HttpStatusCode["Forbidden"] = 403] = "Forbidden";
|
|
2357
|
+
HttpStatusCode[HttpStatusCode["NotFound"] = 404] = "NotFound";
|
|
2358
|
+
HttpStatusCode[HttpStatusCode["ServerError"] = 500] = "ServerError";
|
|
2359
|
+
HttpStatusCode[HttpStatusCode["GatewayTimeout"] = 504] = "GatewayTimeout";
|
|
2360
|
+
})(HttpStatusCode || (HttpStatusCode = {}));
|
|
2361
|
+
|
|
2362
|
+
/**
|
|
2363
|
+
* Represents a JWT token with its decoded info and optional extra info.
|
|
2364
|
+
*
|
|
2365
|
+
* @typeparam T - Type of the additional payload in the JWT.
|
|
2366
|
+
* @typeparam ExtraInfo - Type of the additional information.
|
|
2367
|
+
*/
|
|
2368
|
+
class JwtToken {
|
|
2369
|
+
/**
|
|
2370
|
+
* Create a new JwtToken.
|
|
2371
|
+
*
|
|
2372
|
+
* @param _token - The JWT string.
|
|
2373
|
+
* @param _decodedInfo - The decoded information from the JWT.
|
|
2374
|
+
* @param _extraInfo - Any additional information associated with the JWT.
|
|
2375
|
+
*/
|
|
2376
|
+
constructor(_token, _decodedInfo, _extraInfo) {
|
|
2377
|
+
this._token = _token;
|
|
2378
|
+
this._decodedInfo = _decodedInfo;
|
|
2379
|
+
this._extraInfo = _extraInfo;
|
|
2380
|
+
}
|
|
2381
|
+
/**
|
|
2382
|
+
* Get the JWT string.
|
|
2383
|
+
*
|
|
2384
|
+
* @returns The JWT string.
|
|
2385
|
+
*/
|
|
2386
|
+
get token() {
|
|
2387
|
+
return this._token;
|
|
2388
|
+
}
|
|
2389
|
+
/**
|
|
2390
|
+
* Get the additional information associated with the JWT.
|
|
2391
|
+
*
|
|
2392
|
+
* @returns The additional information.
|
|
2393
|
+
*/
|
|
2394
|
+
get extraInfo() {
|
|
2395
|
+
return this._extraInfo;
|
|
2396
|
+
}
|
|
2397
|
+
/**
|
|
2398
|
+
* Get the decoded information from the JWT.
|
|
2399
|
+
*
|
|
2400
|
+
* @returns The decoded information.
|
|
2401
|
+
*/
|
|
2402
|
+
get decodedInfo() {
|
|
2403
|
+
return this._decodedInfo;
|
|
2404
|
+
}
|
|
2405
|
+
/**
|
|
2406
|
+
* Get the remaining time in seconds until the JWT expires.
|
|
2407
|
+
*
|
|
2408
|
+
* @returns The remaining time in seconds.
|
|
2409
|
+
*/
|
|
2410
|
+
get tokenRemainingSeconds() {
|
|
2411
|
+
return this.decodedInfo.exp - Date.now() / 1000;
|
|
2412
|
+
}
|
|
2413
|
+
/**
|
|
2414
|
+
* Check if the JWT is expired.
|
|
2415
|
+
*
|
|
2416
|
+
* @returns True if the JWT is expired, false otherwise.
|
|
2417
|
+
*/
|
|
2418
|
+
get isExpired() {
|
|
2419
|
+
return this.tokenRemainingSeconds <= 0;
|
|
2420
|
+
}
|
|
2421
|
+
}
|
|
2422
|
+
|
|
2423
|
+
/**
|
|
2424
|
+
* Default logger if none is provided. Is overwritten in non-production environments.
|
|
2425
|
+
*/
|
|
2426
|
+
let defaultLogger = {
|
|
2427
|
+
trace: () => { },
|
|
2428
|
+
debug: () => { },
|
|
2429
|
+
info: () => { },
|
|
2430
|
+
warn: () => { },
|
|
2431
|
+
error: () => { },
|
|
2432
|
+
};
|
|
2433
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
2434
|
+
defaultLogger = loggerService();
|
|
2435
|
+
}
|
|
2436
|
+
/**
|
|
2437
|
+
* Decodes JWT token information and provides a default expiry if none is present.
|
|
2438
|
+
*
|
|
2439
|
+
* @param token - JWT token as a string.
|
|
2440
|
+
* @param defaultTokenTTLInSeconds - Default expiry time in seconds if "exp" claim is not present in token.
|
|
2441
|
+
* @param logger - Logger for logging warnings and errors.
|
|
2442
|
+
*
|
|
2443
|
+
* @returns An object of decoded JWT token information.
|
|
2444
|
+
*/
|
|
2445
|
+
function computeDecodedInfo(token, defaultTokenTTLInSeconds, logger) {
|
|
2446
|
+
const decodedInfo = o(token);
|
|
2447
|
+
if (decodedInfo.exp === undefined) {
|
|
2448
|
+
logger.warn(`"exp" claim is not present in the provided token.`);
|
|
2449
|
+
decodedInfo.exp = Date.now() / 1000 + defaultTokenTTLInSeconds;
|
|
2450
|
+
}
|
|
2451
|
+
return decodedInfo;
|
|
2452
|
+
}
|
|
2453
|
+
/**
|
|
2454
|
+
* A repository for JWT tokens.
|
|
2455
|
+
*/
|
|
2456
|
+
class JwtRepository {
|
|
2457
|
+
/**
|
|
2458
|
+
* @param limitInSeconds - Time in seconds before the token's expiry to notify observers.
|
|
2459
|
+
* @param defaultTokenTTLInSeconds - Default token expiry time in seconds if "exp" claim is not present in token.
|
|
2460
|
+
* @param logger - Logger for logging warnings and errors.
|
|
2461
|
+
*/
|
|
2462
|
+
constructor(limitInSeconds = 5, defaultTokenTTLInSeconds = 120, logger = defaultLogger) {
|
|
2463
|
+
this.limitInSeconds = limitInSeconds;
|
|
2464
|
+
this.defaultTokenTTLInSeconds = defaultTokenTTLInSeconds;
|
|
2465
|
+
this.logger = logger;
|
|
2466
|
+
this.observers = [];
|
|
2467
|
+
}
|
|
2468
|
+
/**
|
|
2469
|
+
* Get the current token.
|
|
2470
|
+
*/
|
|
2471
|
+
get token() {
|
|
2472
|
+
return this._token;
|
|
2473
|
+
}
|
|
2474
|
+
/**
|
|
2475
|
+
* Set the current token.
|
|
2476
|
+
*
|
|
2477
|
+
* @param token - JWT token as a string.
|
|
2478
|
+
* @param extraInfo - Optional extra information.
|
|
2479
|
+
*/
|
|
2480
|
+
setToken(token, extraInfo) {
|
|
2481
|
+
const decodedInfo = computeDecodedInfo(token, this.defaultTokenTTLInSeconds, this.logger);
|
|
2482
|
+
this._token = new JwtToken(token, decodedInfo, extraInfo);
|
|
2483
|
+
this.observeTokenExpiration();
|
|
2484
|
+
return this._token;
|
|
2485
|
+
}
|
|
2486
|
+
/**
|
|
2487
|
+
* Remove the current token.
|
|
2488
|
+
*/
|
|
2489
|
+
removeToken() {
|
|
2490
|
+
this._token = undefined;
|
|
2491
|
+
this.clearTimeoutHandler();
|
|
2492
|
+
}
|
|
2493
|
+
/**
|
|
2494
|
+
* Subscribe to the token nearing its expiration.
|
|
2495
|
+
*
|
|
2496
|
+
* @param cb - Callback function to execute when token is nearing expiration.
|
|
2497
|
+
*/
|
|
2498
|
+
subscribeToTokenNearExpiration(cb) {
|
|
2499
|
+
this.observers.push(cb);
|
|
2500
|
+
this.observeTokenExpiration();
|
|
2501
|
+
return () => {
|
|
2502
|
+
this.observers = this.observers.filter((observer) => observer !== cb);
|
|
2503
|
+
};
|
|
2504
|
+
}
|
|
2505
|
+
/**
|
|
2506
|
+
* Clear the timeout handler.
|
|
2507
|
+
*/
|
|
2508
|
+
clearTimeoutHandler() {
|
|
2509
|
+
if (this.timeoutHandler !== undefined) {
|
|
2510
|
+
clearTimeout(this.timeoutHandler);
|
|
2511
|
+
}
|
|
2512
|
+
}
|
|
2513
|
+
/**
|
|
2514
|
+
* Observe and handle token expiration.
|
|
2515
|
+
*/
|
|
2516
|
+
observeTokenExpiration() {
|
|
2517
|
+
this.clearTimeoutHandler();
|
|
2518
|
+
if (this.observers.length === 0 || this.token === undefined) {
|
|
2519
|
+
return;
|
|
2520
|
+
}
|
|
2521
|
+
this.timeoutHandler = setTimeout(() => this.notifyTokenIsExpiring(), this.computeTimeoutTimeInMs());
|
|
2522
|
+
}
|
|
2523
|
+
/**
|
|
2524
|
+
* Compute the timeout time in milliseconds.
|
|
2525
|
+
*/
|
|
2526
|
+
computeTimeoutTimeInMs() {
|
|
2527
|
+
const remainingSeconds = this.token.tokenRemainingSeconds;
|
|
2528
|
+
let timeoutTimeInSeconds = remainingSeconds - this.limitInSeconds;
|
|
2529
|
+
return timeoutTimeInSeconds < 0 ? 0 : timeoutTimeInSeconds * 1000;
|
|
2530
|
+
}
|
|
2531
|
+
/**
|
|
2532
|
+
* Notify all observers that the token is expiring.
|
|
2533
|
+
*/
|
|
2534
|
+
notifyTokenIsExpiring() {
|
|
2535
|
+
this.observers.forEach((cb) => {
|
|
2536
|
+
try {
|
|
2537
|
+
cb.call(undefined, this.token);
|
|
2538
|
+
}
|
|
2539
|
+
catch (e) {
|
|
2540
|
+
this.logger.error(e.message);
|
|
2541
|
+
}
|
|
2542
|
+
});
|
|
2543
|
+
}
|
|
2544
|
+
}
|
|
2545
|
+
|
|
2546
|
+
/**
|
|
2547
|
+
* JwtManager class.
|
|
2548
|
+
* It provides methods for managing JWT (Json Web Token), such as retrieving and refreshing.
|
|
2549
|
+
*
|
|
2550
|
+
* @template T The data type the JwtToken carries. Default to unknown.
|
|
2551
|
+
* @template ExtraInfo The type of the optional extra information returned by the getJwt method. Defaults to undefined.
|
|
2552
|
+
* @property {JwtRepository<T, ExtraInfo>} jwtRepository JwtRepository instance used for token management.
|
|
2553
|
+
* @property {JwtResolver<ExtraInfo>} resolver JwtResolver instance used for token retrieval.
|
|
2554
|
+
*/
|
|
2555
|
+
class JwtManager {
|
|
2556
|
+
/**
|
|
2557
|
+
* Constructor for JwtManager class.
|
|
2558
|
+
*
|
|
2559
|
+
* @param {JwtRepository<T, ExtraInfo>} jwtRepository JwtRepository instance used for token management.
|
|
2560
|
+
* @param {JwtResolver<ExtraInfo>} resolver JwtResolver instance used for token retrieval.
|
|
2561
|
+
* @param {JwtManagerOptions} options JwtManagerOptions bag to customize behavior.
|
|
2562
|
+
*/
|
|
2563
|
+
constructor(jwtRepository, resolver, options) {
|
|
2564
|
+
this.jwtRepository = jwtRepository;
|
|
2565
|
+
this.resolver = resolver;
|
|
2566
|
+
if (options === null || options === void 0 ? void 0 : options.keepTokenUpdated) {
|
|
2567
|
+
jwtRepository.subscribeToTokenNearExpiration(() => this.refreshToken());
|
|
2568
|
+
}
|
|
2569
|
+
}
|
|
2570
|
+
/**
|
|
2571
|
+
* Method to get a JWT token.
|
|
2572
|
+
* If there's a token request in progress, it will return the Promise of this request.
|
|
2573
|
+
* If the current token is undefined or expired, it will initiate a token refresh.
|
|
2574
|
+
* Otherwise, it will return the current token.
|
|
2575
|
+
*
|
|
2576
|
+
* @returns {JwtToken<T, ExtraInfo> | Promise<JwtToken<T, ExtraInfo>>} The current token or the Promise of a token request.
|
|
2577
|
+
*/
|
|
2578
|
+
getJwt() {
|
|
2579
|
+
if (this.inflightPromise) {
|
|
2580
|
+
return this.inflightPromise;
|
|
2581
|
+
}
|
|
2582
|
+
const token = this.jwtRepository.token;
|
|
2583
|
+
if (token === undefined || token.isExpired) {
|
|
2584
|
+
return this.refreshToken();
|
|
2585
|
+
}
|
|
2586
|
+
return token;
|
|
2587
|
+
}
|
|
2588
|
+
/**
|
|
2589
|
+
* Method to refresh a JWT token.
|
|
2590
|
+
* If a refresh request is already in progress, it will return the Promise of this request.
|
|
2591
|
+
* Otherwise, it will start a new refresh request and return its Promise.
|
|
2592
|
+
*
|
|
2593
|
+
* @returns {Promise<JwtToken<T, ExtraInfo>>} Promise of the refreshed token.
|
|
2594
|
+
*/
|
|
2595
|
+
refreshToken() {
|
|
2596
|
+
if (this.inflightPromise === undefined) {
|
|
2597
|
+
this.inflightPromise = new Promise((resolve, reject) => {
|
|
2598
|
+
this.resolver
|
|
2599
|
+
.getJwt()
|
|
2600
|
+
.then(({ jwt, extraInfo }) => {
|
|
2601
|
+
this.inflightPromise = undefined;
|
|
2602
|
+
const token = this.jwtRepository.setToken(jwt, extraInfo);
|
|
2603
|
+
resolve(token);
|
|
2604
|
+
})
|
|
2605
|
+
.catch((reason) => {
|
|
2606
|
+
this.inflightPromise = undefined;
|
|
2607
|
+
reject(reason);
|
|
2608
|
+
});
|
|
2609
|
+
});
|
|
2610
|
+
}
|
|
2611
|
+
return this.inflightPromise;
|
|
2612
|
+
}
|
|
2613
|
+
/**
|
|
2614
|
+
* Method to check if a token refresh is in progress.
|
|
2615
|
+
*
|
|
2616
|
+
* @returns {boolean} true if a token refresh is in progress, false otherwise.
|
|
2617
|
+
*/
|
|
2618
|
+
get isRefreshingToken() {
|
|
2619
|
+
return this.inflightPromise !== undefined;
|
|
2620
|
+
}
|
|
2621
|
+
}
|
|
2622
|
+
|
|
2623
|
+
/**
|
|
2624
|
+
* Copyright (c) 2022, Salesforce, Inc.,
|
|
2625
|
+
* All rights reserved.
|
|
2626
|
+
* For full license text, see the LICENSE.txt file
|
|
2627
|
+
*/
|
|
2628
|
+
|
|
2629
|
+
|
|
2630
|
+
function buildServiceDescriptor(interceptors = { request: [] }) {
|
|
2631
|
+
return {
|
|
2632
|
+
type: 'fetch',
|
|
2633
|
+
version: '1.0',
|
|
2634
|
+
service: function (...args) {
|
|
2635
|
+
const { request: requestInterceptors = [] } = interceptors;
|
|
2636
|
+
const pending = requestInterceptors.reduce((previousPromise, interceptor) => previousPromise.then(interceptor), resolvedPromiseLike(args));
|
|
2637
|
+
return pending.then((args) => fetch(...args));
|
|
2638
|
+
},
|
|
2639
|
+
};
|
|
2640
|
+
}
|
|
2641
|
+
|
|
2642
|
+
const UNEXPECTED_AUTHORIZATION_HEADER_MESSAGE = 'Unexpected Authorization header encountered. To specify a custom Authorization header, use a Fetch service that is not configured with JwtRequestHeaderInterceptor';
|
|
2643
|
+
function setHeaderAuthorization({ token }, [resource, options = {}]) {
|
|
2644
|
+
let hasAuthorizationHeaderBeenSet = false;
|
|
2645
|
+
const Authorization = `Bearer ${token}`;
|
|
2646
|
+
// The following is necessary because fetch(RequestInfo, options|RequestInit)
|
|
2647
|
+
// does not merge properties from options|RequestInit.headers into RequestInfo.headers,
|
|
2648
|
+
// instead it will pave over RequestInfo.headers if options|RequestInit.headers
|
|
2649
|
+
// is defined. Therefore, this function must preserve any pre-existing headers
|
|
2650
|
+
// properties across two different arguments that themselves may be four
|
|
2651
|
+
// different data types.
|
|
2652
|
+
//
|
|
2653
|
+
// - a string
|
|
2654
|
+
// - an instance of URL
|
|
2655
|
+
// - an instance of Request
|
|
2656
|
+
//
|
|
2657
|
+
// And for this argument we only need to guard against overwriting an
|
|
2658
|
+
// existing `headers` property whose value is an instance of Headers.
|
|
2659
|
+
// This will only be valid when the resource argument is an instance of
|
|
2660
|
+
// Request, which implicitly holds a property called `headers` whose
|
|
2661
|
+
// value is an instance of Headers AND options has not been explicitly
|
|
2662
|
+
// provided, or has but does not have a headers property. The existance
|
|
2663
|
+
// of an explicit headers property would likely be intentional, so
|
|
2664
|
+
// we defer to the calling code.
|
|
2665
|
+
if (resource instanceof Request && !(options === null || options === void 0 ? void 0 : options.headers)) {
|
|
2666
|
+
// If the resource object's headers instance already contains an
|
|
2667
|
+
// "Authorization" header, then we have to assume this is a mistake
|
|
2668
|
+
// so throw an exception that will result in the fetch call returning
|
|
2669
|
+
// a rejected promise.
|
|
2670
|
+
if (resource.headers.has('Authorization')) {
|
|
2671
|
+
throw new Error(UNEXPECTED_AUTHORIZATION_HEADER_MESSAGE);
|
|
2672
|
+
}
|
|
2673
|
+
// Otherwise, set the "Authorization" header with the provided
|
|
2674
|
+
// JWT value (because options?.headers also doesn't exist)
|
|
2675
|
+
resource.headers.set('Authorization', Authorization);
|
|
2676
|
+
hasAuthorizationHeaderBeenSet = true;
|
|
2677
|
+
}
|
|
2678
|
+
// When the options object is provided, it may already have a
|
|
2679
|
+
// `headers` property whose value is an instance of Headers, or
|
|
2680
|
+
// a `headers` property that is a plain object comprised of key=>value
|
|
2681
|
+
// pairs that will be converted into a Headers object by fetch().
|
|
2682
|
+
//
|
|
2683
|
+
// If the options object contains a property called `headers` whose value
|
|
2684
|
+
// is an instance of Headers...
|
|
2685
|
+
if ((options === null || options === void 0 ? void 0 : options.headers) instanceof Headers) {
|
|
2686
|
+
// ...Check to see if that object contains an "Authorization" header.
|
|
2687
|
+
// If it does, then we have to assume this is a mistake so throw an
|
|
2688
|
+
// exception.
|
|
2689
|
+
if (options === null || options === void 0 ? void 0 : options.headers.has('Authorization')) {
|
|
2690
|
+
throw new Error(UNEXPECTED_AUTHORIZATION_HEADER_MESSAGE);
|
|
2691
|
+
}
|
|
2692
|
+
// Otherwise, set the "Authorization" header with the provided
|
|
2693
|
+
// JWT token value:
|
|
2694
|
+
options === null || options === void 0 ? void 0 : options.headers.set('Authorization', Authorization);
|
|
2695
|
+
}
|
|
2696
|
+
else {
|
|
2697
|
+
// Else, check if options contains a `headers` property whose value
|
|
2698
|
+
// is a plain object comprised of key=>value pairs. If this
|
|
2699
|
+
// object contains a property called "Authorization", then we have
|
|
2700
|
+
// to assume this is a mistake so throw an exception.
|
|
2701
|
+
if ((options === null || options === void 0 ? void 0 : options.headers) && Reflect.has(options === null || options === void 0 ? void 0 : options.headers, 'Authorization')) {
|
|
2702
|
+
throw new Error(UNEXPECTED_AUTHORIZATION_HEADER_MESSAGE);
|
|
2703
|
+
}
|
|
2704
|
+
// Otherwise, add a property called "Authorization", whose value is
|
|
2705
|
+
// the provided JWT value, as long as it has not been set on Request
|
|
2706
|
+
if (!hasAuthorizationHeaderBeenSet) {
|
|
2707
|
+
options.headers = {
|
|
2708
|
+
...options === null || options === void 0 ? void 0 : options.headers,
|
|
2709
|
+
Authorization,
|
|
2710
|
+
};
|
|
2711
|
+
}
|
|
2712
|
+
}
|
|
2713
|
+
return [resource, options];
|
|
2714
|
+
}
|
|
2715
|
+
function buildJwtRequestHeaderInterceptor(jwtManager, jwtRequestModifier = (_e, fetchArgs) => fetchArgs) {
|
|
2716
|
+
return (args) => {
|
|
2717
|
+
return resolvedPromiseLike(jwtManager.getJwt()).then((token) => {
|
|
2718
|
+
const fetchArgsWithRequestHeaderAuthorization = setHeaderAuthorization(token, args);
|
|
2719
|
+
return token.extraInfo
|
|
2720
|
+
? jwtRequestModifier(token.extraInfo, fetchArgsWithRequestHeaderAuthorization)
|
|
2721
|
+
: fetchArgsWithRequestHeaderAuthorization;
|
|
2722
|
+
});
|
|
2723
|
+
};
|
|
2724
|
+
}
|
|
2725
|
+
|
|
2726
|
+
const SFAP_EXCHANGE_PATH = '/webruntime/sfap-info';
|
|
2727
|
+
/**
|
|
2728
|
+
* We expect jwt info and baseUri to be present in the response.
|
|
2729
|
+
*
|
|
2730
|
+
* @param response
|
|
2731
|
+
*/
|
|
2732
|
+
function validateResponse(response) {
|
|
2733
|
+
if (!response || !response.jwt || !response.baseUri) {
|
|
2734
|
+
// wrapped the invocation in env conditional
|
|
2735
|
+
// eslint-disable-next-line @salesforce/lds/no-error-in-production
|
|
2736
|
+
throw new Error(`Expected jwt info and baseUri to be present but instead got: ${JSON.stringify(response)}`);
|
|
2737
|
+
}
|
|
2738
|
+
}
|
|
2739
|
+
/**
|
|
2740
|
+
* Resolves Jwt token for SFAP by calling Aura action
|
|
2741
|
+
* {@link JwtResolver} for platform SFAP
|
|
2742
|
+
*/
|
|
2743
|
+
const platformSfapJwtResolver = {
|
|
2744
|
+
getJwt() {
|
|
2745
|
+
return new Promise((resolve, reject) => {
|
|
2746
|
+
fetch(SFAP_EXCHANGE_PATH)
|
|
2747
|
+
.then((res) => res.json())
|
|
2748
|
+
.then((response) => {
|
|
2749
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
2750
|
+
validateResponse(response);
|
|
2751
|
+
}
|
|
2752
|
+
resolve({
|
|
2753
|
+
jwt: response.jwt,
|
|
2754
|
+
extraInfo: {
|
|
2755
|
+
baseUri: response.baseUri,
|
|
2756
|
+
},
|
|
2757
|
+
});
|
|
2758
|
+
})
|
|
2759
|
+
.catch((error) => {
|
|
2760
|
+
if (error instanceof Error) {
|
|
2761
|
+
reject(error.message);
|
|
2762
|
+
return;
|
|
2763
|
+
}
|
|
2764
|
+
// AuraFetchResponse errors
|
|
2765
|
+
const { status } = error;
|
|
2766
|
+
if (status !== HttpStatusCode$2.ServerError) {
|
|
2767
|
+
// ConnectInJavaError
|
|
2768
|
+
reject(error.body.message);
|
|
2769
|
+
return;
|
|
2770
|
+
}
|
|
2771
|
+
reject(error.body.error);
|
|
2772
|
+
});
|
|
2773
|
+
});
|
|
2774
|
+
},
|
|
2775
|
+
};
|
|
2776
|
+
|
|
2777
|
+
const SFAP_BASE_URL = 'api.salesforce.com';
|
|
2778
|
+
const sfapJwtRepository = new JwtRepository();
|
|
2779
|
+
const sfapJwtManager = new JwtManager(sfapJwtRepository, platformSfapJwtResolver);
|
|
2780
|
+
function buildJwtAuthorizedSfapFetchServiceDescriptor(logger) {
|
|
2781
|
+
const jwtAuthorizedFetchService = buildServiceDescriptor({
|
|
2782
|
+
request: [buildJwtRequestInterceptor(logger)],
|
|
2783
|
+
});
|
|
2784
|
+
return {
|
|
2785
|
+
...jwtAuthorizedFetchService,
|
|
2786
|
+
tags: { authenticationScopes: 'sfap_api' },
|
|
2787
|
+
};
|
|
2788
|
+
}
|
|
2789
|
+
/**
|
|
2790
|
+
* Returns a service descriptor for a fetch service that includes one-off copilot
|
|
2791
|
+
* hacks. This fetch service is not intended for use by anything other than
|
|
2792
|
+
* copilot commands.
|
|
2793
|
+
*/
|
|
2794
|
+
function buildCopilotFetchServiceDescriptor(logger) {
|
|
2795
|
+
return {
|
|
2796
|
+
// Note that this layers the Interceptor below directly on top of fetch(). WHen
|
|
2797
|
+
// we switch to JWT authentication this will need to change to incorporate the
|
|
2798
|
+
// Interceptor here with the logic in buildJwtAuthorizedSfapFetchServiceDescriptor()
|
|
2799
|
+
// above.
|
|
2800
|
+
...buildServiceDescriptor({
|
|
2801
|
+
request: [
|
|
2802
|
+
// Note that this function is VERY closely tied to the fetchParams generated
|
|
2803
|
+
// by copilotStartSessionCommand. Any changes to those parameters will require
|
|
2804
|
+
// corresponding updates to the logic below.
|
|
2805
|
+
(args) => {
|
|
2806
|
+
const [url, requestInit] = args;
|
|
2807
|
+
// ignore anything other than a start session request
|
|
2808
|
+
if (typeof url !== 'string' ||
|
|
2809
|
+
!url.endsWith('/sessions') ||
|
|
2810
|
+
!requestInit ||
|
|
2811
|
+
requestInit.method !== 'POST' ||
|
|
2812
|
+
!requestInit.body ||
|
|
2813
|
+
typeof requestInit.body !== 'string') {
|
|
2814
|
+
return resolvedPromiseLike(args);
|
|
2815
|
+
}
|
|
2816
|
+
return resolvedPromiseLike(sfapJwtManager.getJwt()).then((token) => {
|
|
2817
|
+
// replace the body's instanceConfig.endpoint with the JWT's iss value
|
|
2818
|
+
const body = JSON.parse(requestInit.body);
|
|
2819
|
+
if (!body || !token.decodedInfo || !token.decodedInfo.iss) {
|
|
2820
|
+
logger.warn('skipping injection of endpoint into start session request');
|
|
2821
|
+
}
|
|
2822
|
+
else {
|
|
2823
|
+
if (!body.instanceConfig) {
|
|
2824
|
+
body.instanceConfig = {};
|
|
2825
|
+
}
|
|
2826
|
+
body.instanceConfig.endpoint = token.decodedInfo.iss;
|
|
2827
|
+
}
|
|
2828
|
+
return [
|
|
2829
|
+
args[0],
|
|
2830
|
+
{
|
|
2831
|
+
...args[1],
|
|
2832
|
+
body: JSON.stringify(body),
|
|
2833
|
+
},
|
|
2834
|
+
];
|
|
2835
|
+
});
|
|
2836
|
+
},
|
|
2837
|
+
buildJwtRequestInterceptor(logger),
|
|
2838
|
+
],
|
|
2839
|
+
}),
|
|
2840
|
+
tags: { specialHacksFor: 'copilot' },
|
|
2841
|
+
};
|
|
2842
|
+
}
|
|
2843
|
+
function buildUnauthorizedFetchServiceDescriptor() {
|
|
2844
|
+
const fetchService = buildServiceDescriptor();
|
|
2845
|
+
return {
|
|
2846
|
+
...fetchService,
|
|
2847
|
+
tags: { authenticationScopes: '' },
|
|
2848
|
+
};
|
|
2849
|
+
}
|
|
2850
|
+
function buildJwtRequestInterceptor(logger) {
|
|
2851
|
+
const jwtRequestModifier = ({ baseUri }, [resource, request]) => {
|
|
2852
|
+
if (typeof resource !== 'string' && !(resource instanceof URL)) {
|
|
2853
|
+
// istanbul ignore else: this will not be tested in NODE_ENV = production for test coverage
|
|
2854
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
2855
|
+
throw new Error('SFAP fetch service expects a string or URL resource');
|
|
2856
|
+
}
|
|
2857
|
+
return [resource, request];
|
|
2858
|
+
}
|
|
2859
|
+
const overrideUrl = new URL(baseUri);
|
|
2860
|
+
const url = typeof resource === 'string' ? new URL(resource) : new URL(resource.toString());
|
|
2861
|
+
if (!(url.host === SFAP_BASE_URL)) {
|
|
2862
|
+
logger.warn(`SFAP fetch service requires that the host of the resource is ${SFAP_BASE_URL}`);
|
|
2863
|
+
return [resource, request];
|
|
2864
|
+
}
|
|
2865
|
+
url.host = overrideUrl.host;
|
|
2866
|
+
url.protocol = overrideUrl.protocol;
|
|
2867
|
+
return [url, request];
|
|
2868
|
+
};
|
|
2869
|
+
const jwtRequestHeaderInterceptor = buildJwtRequestHeaderInterceptor(sfapJwtManager, jwtRequestModifier);
|
|
2870
|
+
return jwtRequestHeaderInterceptor;
|
|
2871
|
+
}
|
|
2872
|
+
|
|
2873
|
+
// Initializes OneStore in Webruntime
|
|
2874
|
+
function initOneStore() {
|
|
2875
|
+
const loggerService = new ConsoleLogger$1('ERROR');
|
|
2876
|
+
const cacheServiceDescriptor = buildServiceDescriptor$4();
|
|
2877
|
+
const instrumentationServiceDescriptor = buildServiceDescriptor$5(loggerService);
|
|
2878
|
+
const inMemoryCacheInclusionPolicyServiceDescriptor = buildInMemoryCacheInclusionPolicyService(cacheServiceDescriptor.service);
|
|
2879
|
+
const services = [
|
|
2880
|
+
instrumentationServiceDescriptor,
|
|
2881
|
+
buildUnauthorizedFetchServiceDescriptor(),
|
|
2882
|
+
buildJwtAuthorizedSfapFetchServiceDescriptor(loggerService),
|
|
2883
|
+
buildCopilotFetchServiceDescriptor(loggerService),
|
|
2884
|
+
buildAuraNetworkService(),
|
|
2885
|
+
buildServiceDescriptor$6(instrumentationServiceDescriptor.service),
|
|
2886
|
+
buildServiceDescriptor$3(cacheServiceDescriptor.service, inMemoryCacheInclusionPolicyServiceDescriptor.service),
|
|
2887
|
+
buildServiceDescriptor$d(),
|
|
2888
|
+
buildServiceDescriptor$1(),
|
|
2889
|
+
buildServiceDescriptor$9(),
|
|
2890
|
+
buildServiceDescriptor$e(),
|
|
2891
|
+
buildServiceDescriptor$8(),
|
|
2892
|
+
buildServiceDescriptor$7(),
|
|
2893
|
+
buildServiceDescriptor$c(),
|
|
2894
|
+
buildServiceDescriptor$b(),
|
|
2895
|
+
buildServiceDescriptor$a(),
|
|
2896
|
+
buildServiceDescriptor$2(),
|
|
2897
|
+
];
|
|
2898
|
+
setServices(services);
|
|
2899
|
+
}
|
|
2900
|
+
function buildAuraNetworkService() {
|
|
2901
|
+
return {
|
|
2902
|
+
type: 'auraNetwork',
|
|
2903
|
+
version: '1.0',
|
|
2904
|
+
service: executeGlobalControllerRawResponse,
|
|
2905
|
+
};
|
|
2906
|
+
}
|
|
2907
|
+
|
|
2908
|
+
export { initOneStore };
|
|
2909
|
+
// version: 1.360.0-580249acee
|