@newrelic/browser-agent 1.241.0 → 1.243.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/CHANGELOG.md +1465 -0
- package/dist/cjs/cdn/polyfills/lite.js +13 -1
- package/dist/cjs/cdn/polyfills/pro.js +17 -1
- package/dist/cjs/cdn/polyfills/spa.js +18 -1
- package/dist/cjs/common/config/state/init.js +32 -5
- package/dist/cjs/common/constants/env.cdn.js +1 -1
- package/dist/cjs/common/constants/env.npm.js +1 -1
- package/dist/cjs/common/dom/query-selector.js +16 -0
- package/dist/cjs/common/session/session-entity.js +20 -2
- package/dist/cjs/common/wrap/wrap-function.js +1 -1
- package/dist/cjs/features/ajax/aggregate/index.js +1 -1
- package/dist/cjs/features/session_replay/aggregate/index.js +84 -50
- package/dist/cjs/features/utils/feature-base.js +1 -2
- package/dist/cjs/features/utils/instrument-base.js +1 -0
- package/dist/cjs/loaders/api/api.js +2 -2
- package/dist/cjs/loaders/api/apiAsync.js +0 -37
- package/dist/cjs/loaders/configure/configure.js +1 -1
- package/dist/cjs/loaders/configure/public-path.js +6 -3
- package/dist/esm/cdn/polyfills/lite.js +8 -1
- package/dist/esm/cdn/polyfills/pro.js +13 -2
- package/dist/esm/cdn/polyfills/spa.js +13 -1
- package/dist/esm/common/config/state/init.js +32 -5
- package/dist/esm/common/constants/env.cdn.js +1 -1
- package/dist/esm/common/constants/env.npm.js +1 -1
- package/dist/esm/common/dom/query-selector.js +9 -0
- package/dist/esm/common/session/session-entity.js +18 -1
- package/dist/esm/common/wrap/wrap-function.js +1 -1
- package/dist/esm/features/ajax/aggregate/index.js +1 -1
- package/dist/esm/features/session_replay/aggregate/index.js +83 -50
- package/dist/esm/features/utils/feature-base.js +1 -2
- package/dist/esm/features/utils/instrument-base.js +1 -0
- package/dist/esm/loaders/api/api.js +2 -2
- package/dist/esm/loaders/api/apiAsync.js +1 -36
- package/dist/esm/loaders/configure/configure.js +1 -1
- package/dist/esm/loaders/configure/public-path.js +6 -3
- package/dist/types/common/config/state/init.d.ts.map +1 -1
- package/dist/types/common/dom/query-selector.d.ts +2 -0
- package/dist/types/common/dom/query-selector.d.ts.map +1 -0
- package/dist/types/common/session/session-entity.d.ts +5 -0
- package/dist/types/common/session/session-entity.d.ts.map +1 -1
- package/dist/types/features/session_replay/aggregate/index.d.ts +11 -14
- package/dist/types/features/session_replay/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/utils/feature-base.d.ts.map +1 -1
- package/dist/types/features/utils/instrument-base.d.ts.map +1 -1
- package/dist/types/loaders/api/api.d.ts.map +1 -1
- package/dist/types/loaders/api/apiAsync.d.ts.map +1 -1
- package/dist/types/loaders/configure/public-path.d.ts +1 -1
- package/dist/types/loaders/configure/public-path.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/cdn/polyfills/lite.js +14 -1
- package/src/cdn/polyfills/pro.js +23 -2
- package/src/cdn/polyfills/spa.js +24 -1
- package/src/common/config/state/init.js +33 -4
- package/src/common/dom/query-selector.js +9 -0
- package/src/common/session/session-entity.js +20 -1
- package/src/common/wrap/wrap-function.js +1 -1
- package/src/features/ajax/aggregate/index.js +2 -2
- package/src/features/session_replay/aggregate/index.js +82 -34
- package/src/features/utils/feature-base.js +1 -2
- package/src/features/utils/instrument-base.js +1 -0
- package/src/loaders/api/api.js +1 -2
- package/src/loaders/api/apiAsync.js +1 -39
- package/src/loaders/configure/configure.js +1 -1
- package/src/loaders/configure/public-path.js +6 -3
- package/src/common/aggregate/aggregator.test.js +0 -107
- package/src/common/config/state/configurable.test.js +0 -73
- package/src/common/config/state/info.test.js +0 -31
- package/src/common/config/state/init.test.js +0 -28
- package/src/common/config/state/loader-config.test.js +0 -21
- package/src/common/config/state/runtime.test.js +0 -21
- package/src/common/constants/env.cdn.test.js +0 -7
- package/src/common/constants/env.npm.test.js +0 -7
- package/src/common/constants/env.test.js +0 -7
- package/src/common/constants/runtime.test.js +0 -176
- package/src/common/deny-list/deny-list.test.js +0 -104
- package/src/common/drain/drain.test.js +0 -74
- package/src/common/event-emitter/contextual-ee.component-test.js +0 -293
- package/src/common/event-emitter/handle.test.js +0 -56
- package/src/common/event-emitter/register-handler.test.js +0 -61
- package/src/common/harvest/harvest-scheduler.test.js +0 -492
- package/src/common/harvest/harvest.test.js +0 -813
- package/src/common/ids/id.test.js +0 -92
- package/src/common/ids/unique-id.test.js +0 -58
- package/src/common/session/session-entity.component-test.js +0 -346
- package/src/common/storage/local-storage.test.js +0 -17
- package/src/common/timer/interaction-timer.component-test.js +0 -212
- package/src/common/timer/timer.test.js +0 -99
- package/src/common/timing/nav-timing.test.js +0 -161
- package/src/common/url/canonicalize-url.test.js +0 -45
- package/src/common/url/clean-url.test.js +0 -25
- package/src/common/url/encode.test.js +0 -81
- package/src/common/url/location.test.js +0 -15
- package/src/common/url/parse-url.test.js +0 -110
- package/src/common/url/protocol.test.js +0 -17
- package/src/common/util/console.test.js +0 -34
- package/src/common/util/data-size.test.js +0 -56
- package/src/common/util/feature-flags.test.js +0 -94
- package/src/common/util/get-or-set.test.js +0 -58
- package/src/common/util/invoke.test.js +0 -65
- package/src/common/util/map-own.test.js +0 -52
- package/src/common/util/obfuscate.component-test.js +0 -173
- package/src/common/util/stringify.test.js +0 -49
- package/src/common/util/submit-data.test.js +0 -183
- package/src/common/util/traverse.test.js +0 -50
- package/src/common/vitals/cumulative-layout-shift.test.js +0 -71
- package/src/common/vitals/first-contentful-paint.test.js +0 -124
- package/src/common/vitals/first-input-delay.test.js +0 -88
- package/src/common/vitals/first-paint.test.js +0 -127
- package/src/common/vitals/interaction-to-next-paint.test.js +0 -74
- package/src/common/vitals/largest-contentful-paint.test.js +0 -94
- package/src/common/vitals/long-task.test.js +0 -122
- package/src/common/vitals/time-to-first-byte.test.js +0 -147
- package/src/common/vitals/vital-metric.test.js +0 -171
- package/src/common/wrap/wrap-promise.component-test.js +0 -110
- package/src/features/ajax/instrument/distributed-tracing.test.js +0 -375
- package/src/features/jserrors/aggregate/canonical-function-name.test.js +0 -13
- package/src/features/jserrors/aggregate/compute-stack-trace.test.js +0 -414
- package/src/features/jserrors/aggregate/format-stack-trace.test.js +0 -39
- package/src/features/jserrors/aggregate/string-hash-code.test.js +0 -12
- package/src/features/metrics/aggregate/framework-detection.test.js +0 -332
- package/src/features/page_view_timing/aggregate/index.component-test.js +0 -86
- package/src/features/session_replay/aggregate/index.component-test.js +0 -317
- package/src/features/spa/aggregate/interaction-node.test.js +0 -17
- package/src/features/utils/agent-session.test.js +0 -194
- package/src/features/utils/aggregate-base.test.js +0 -123
- package/src/features/utils/feature-base.test.js +0 -45
- package/src/features/utils/handler-cache.test.js +0 -72
- package/src/features/utils/instrument-base.test.js +0 -216
- package/src/features/utils/lazy-feature-loader.test.js +0 -37
- package/src/loaders/api/api.component-test.js +0 -45
- package/src/loaders/api/api.test.js +0 -85
- package/src/loaders/api/apiAsync.test.js +0 -17
|
@@ -15,18 +15,47 @@ import { HarvestScheduler } from '../../../common/harvest/harvest-scheduler'
|
|
|
15
15
|
import { FEATURE_NAME } from '../constants'
|
|
16
16
|
import { stringify } from '../../../common/util/stringify'
|
|
17
17
|
import { getConfigurationValue, getInfo, getRuntime } from '../../../common/config/config'
|
|
18
|
-
import { SESSION_EVENTS, MODE } from '../../../common/session/session-entity'
|
|
18
|
+
import { SESSION_EVENTS, MODE, SESSION_EVENT_TYPES } from '../../../common/session/session-entity'
|
|
19
19
|
import { AggregateBase } from '../../utils/aggregate-base'
|
|
20
20
|
import { sharedChannel } from '../../../common/constants/shared-channel'
|
|
21
21
|
import { obj as encodeObj } from '../../../common/url/encode'
|
|
22
22
|
import { warn } from '../../../common/util/console'
|
|
23
23
|
import { globalScope } from '../../../common/constants/runtime'
|
|
24
|
+
import { SUPPORTABILITY_METRIC_CHANNEL } from '../../metrics/constants'
|
|
24
25
|
|
|
25
26
|
// would be better to get this dynamically in some way
|
|
26
27
|
export const RRWEB_VERSION = '2.0.0-alpha.8'
|
|
27
28
|
|
|
28
29
|
export const AVG_COMPRESSION = 0.12
|
|
29
30
|
|
|
31
|
+
export const RRWEB_EVENT_TYPES = {
|
|
32
|
+
DomContentLoaded: 0,
|
|
33
|
+
Load: 1,
|
|
34
|
+
FullSnapshot: 2,
|
|
35
|
+
IncrementalSnapshot: 3,
|
|
36
|
+
Meta: 4,
|
|
37
|
+
Custom: 5
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const ABORT_REASONS = {
|
|
41
|
+
RESET: {
|
|
42
|
+
message: 'Session was reset',
|
|
43
|
+
sm: 'Reset'
|
|
44
|
+
},
|
|
45
|
+
IMPORT: {
|
|
46
|
+
message: 'Recorder failed to import',
|
|
47
|
+
sm: 'Import'
|
|
48
|
+
},
|
|
49
|
+
TOO_MANY: {
|
|
50
|
+
message: '429: Too Many Requests',
|
|
51
|
+
sm: 'Too-Many'
|
|
52
|
+
},
|
|
53
|
+
TOO_BIG: {
|
|
54
|
+
message: 'Payload was too large',
|
|
55
|
+
sm: 'Too-Big'
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
30
59
|
let recorder, gzipper, u8
|
|
31
60
|
|
|
32
61
|
/** Vortex caps payload sizes at 1MB */
|
|
@@ -57,8 +86,6 @@ export class Aggregate extends AggregateBase {
|
|
|
57
86
|
/** can shut off efforts to compress the data */
|
|
58
87
|
this.shouldCompress = true
|
|
59
88
|
|
|
60
|
-
/** Payload metadata -- Should indicate that the payload being sent is the first of a session */
|
|
61
|
-
this.isFirstChunk = false
|
|
62
89
|
/** Payload metadata -- Should indicate that the payload being sent has a full DOM snapshot. This can happen
|
|
63
90
|
* -- When the recording library begins recording, it starts by taking a DOM snapshot
|
|
64
91
|
* -- When visibility changes from "hidden" -> "visible", it must capture a full snapshot for the replay to work correctly across tabs
|
|
@@ -72,7 +99,7 @@ export class Aggregate extends AggregateBase {
|
|
|
72
99
|
/** Payload metadata -- Should indicate when a replay blob started recording. Resets each time a harvest occurs.
|
|
73
100
|
* cycle timestamps are used as fallbacks if event timestamps cannot be used
|
|
74
101
|
*/
|
|
75
|
-
this.
|
|
102
|
+
this.cycleTimestamp = undefined
|
|
76
103
|
|
|
77
104
|
/** A value which increments with every new mutation node reported. Resets after a harvest is sent */
|
|
78
105
|
this.payloadBytesEstimation = 0
|
|
@@ -91,17 +118,26 @@ export class Aggregate extends AggregateBase {
|
|
|
91
118
|
if (shouldSetup) {
|
|
92
119
|
// The SessionEntity class can emit a message indicating the session was cleared and reset (expiry, inactivity). This feature must abort and never resume if that occurs.
|
|
93
120
|
this.ee.on(SESSION_EVENTS.RESET, () => {
|
|
94
|
-
this.abort()
|
|
121
|
+
this.abort(ABORT_REASONS.RESET)
|
|
95
122
|
})
|
|
96
123
|
|
|
97
124
|
// The SessionEntity class can emit a message indicating the session was paused (visibility change). This feature must stop recording if that occurs.
|
|
98
125
|
this.ee.on(SESSION_EVENTS.PAUSE, () => { this.stopRecording() })
|
|
99
126
|
// The SessionEntity class can emit a message indicating the session was resumed (visibility change). This feature must start running again (if already running) if that occurs.
|
|
100
127
|
this.ee.on(SESSION_EVENTS.RESUME, () => {
|
|
128
|
+
// if the mode changed on a different tab, it needs to update this instance to match
|
|
129
|
+
const { session } = getRuntime(this.agentIdentifier)
|
|
130
|
+
this.mode = session.state.sessionReplay
|
|
101
131
|
if (!this.initialized || this.mode === MODE.OFF) return
|
|
102
132
|
this.startRecording()
|
|
103
133
|
})
|
|
104
134
|
|
|
135
|
+
this.ee.on(SESSION_EVENTS.UPDATE, (type, data) => {
|
|
136
|
+
if (!this.initialized || this.blocked || type !== SESSION_EVENT_TYPES.CROSS_TAB) return
|
|
137
|
+
if (this.mode !== MODE.OFF && data.sessionReplay === MODE.OFF) this.abort('Session Entity was set to OFF on another tab')
|
|
138
|
+
this.mode = data.sessionReplay
|
|
139
|
+
})
|
|
140
|
+
|
|
105
141
|
// Bespoke logic for new endpoint. This will change as downstream dependencies become solidified.
|
|
106
142
|
this.scheduler = new HarvestScheduler('browser/blobs', {
|
|
107
143
|
onFinished: this.onHarvestFinished.bind(this),
|
|
@@ -116,12 +152,13 @@ export class Aggregate extends AggregateBase {
|
|
|
116
152
|
this.hasError = true
|
|
117
153
|
this.errorNoticed = true
|
|
118
154
|
// run once
|
|
119
|
-
if (this.mode === MODE.ERROR) {
|
|
155
|
+
if (this.mode === MODE.ERROR && globalScope?.document.visibilityState === 'visible') {
|
|
120
156
|
this.mode = MODE.FULL
|
|
121
157
|
// if the error was noticed AFTER the recorder was already imported....
|
|
122
158
|
if (recorder && this.initialized) {
|
|
123
159
|
this.stopRecording()
|
|
124
160
|
this.startRecording()
|
|
161
|
+
|
|
125
162
|
this.scheduler.startTimer(this.harvestTimeSeconds)
|
|
126
163
|
|
|
127
164
|
this.syncWithSessionManager({ sessionReplay: this.mode })
|
|
@@ -176,7 +213,7 @@ export class Aggregate extends AggregateBase {
|
|
|
176
213
|
// Do not change the webpackChunkName or it will break the webpack nrba-chunking plugin
|
|
177
214
|
recorder = (await import(/* webpackChunkName: "recorder" */'rrweb')).record
|
|
178
215
|
} catch (err) {
|
|
179
|
-
return this.abort()
|
|
216
|
+
return this.abort(ABORT_REASONS.IMPORT)
|
|
180
217
|
}
|
|
181
218
|
|
|
182
219
|
// FULL mode records AND reports from the beginning, while ERROR mode only records (but does not report).
|
|
@@ -198,8 +235,6 @@ export class Aggregate extends AggregateBase {
|
|
|
198
235
|
}
|
|
199
236
|
this.startRecording()
|
|
200
237
|
|
|
201
|
-
this.isFirstChunk = !!session.isNew
|
|
202
|
-
|
|
203
238
|
this.syncWithSessionManager({ sessionReplay: this.mode })
|
|
204
239
|
}
|
|
205
240
|
|
|
@@ -214,6 +249,8 @@ export class Aggregate extends AggregateBase {
|
|
|
214
249
|
this.scheduler.opts.gzip = false
|
|
215
250
|
}
|
|
216
251
|
// TODO -- Gracefully handle the buffer for retries.
|
|
252
|
+
const { session } = getRuntime(this.agentIdentifier)
|
|
253
|
+
if (!session.state.sessionReplaySentFirstChunk) this.syncWithSessionManager({ sessionReplaySentFirstChunk: true })
|
|
217
254
|
this.clearBuffer()
|
|
218
255
|
return [payload]
|
|
219
256
|
}
|
|
@@ -221,8 +258,28 @@ export class Aggregate extends AggregateBase {
|
|
|
221
258
|
getHarvestContents () {
|
|
222
259
|
const agentRuntime = getRuntime(this.agentIdentifier)
|
|
223
260
|
const info = getInfo(this.agentIdentifier)
|
|
224
|
-
|
|
225
|
-
|
|
261
|
+
|
|
262
|
+
// do not let the last node be a meta node, since this NEEDS to precede a snapshot
|
|
263
|
+
// we will manually inject it later if we find a payload that is missing a meta node
|
|
264
|
+
const payloadEndsWithMeta = this.events[this.events.length - 1]?.type === RRWEB_EVENT_TYPES.Meta
|
|
265
|
+
if (payloadEndsWithMeta) {
|
|
266
|
+
this.lastMeta = this.events[this.events.length - 1]
|
|
267
|
+
this.events = this.events.slice(0, this.events.length - 1)
|
|
268
|
+
this.hasMeta = !!this.events.find(x => x.type === RRWEB_EVENT_TYPES.Meta)
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// do not let the first node be a full snapshot node, since this NEEDS to be preceded by a meta node
|
|
272
|
+
// we will manually inject it if this happens
|
|
273
|
+
const payloadStartsWithFullSnapshot = this.events[0]?.type === RRWEB_EVENT_TYPES.FullSnapshot
|
|
274
|
+
if (payloadStartsWithFullSnapshot) {
|
|
275
|
+
this.hasMeta = true
|
|
276
|
+
this.events.unshift(this.lastMeta)
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const firstEventTimestamp = this.events[0]?.timestamp
|
|
280
|
+
const lastEventTimestamp = this.events[this.events.length - 1]?.timestamp
|
|
281
|
+
const firstTimestamp = firstEventTimestamp || this.cycleTimestamp
|
|
282
|
+
const lastTimestamp = lastEventTimestamp || getRuntime(this.agentIdentifier).offset + globalScope.performance.now()
|
|
226
283
|
return {
|
|
227
284
|
qs: {
|
|
228
285
|
browser_monitoring_key: info.licenseKey,
|
|
@@ -234,12 +291,13 @@ export class Aggregate extends AggregateBase {
|
|
|
234
291
|
'replay.firstTimestamp': firstTimestamp,
|
|
235
292
|
'replay.lastTimestamp': lastTimestamp,
|
|
236
293
|
'replay.durationMs': lastTimestamp - firstTimestamp,
|
|
294
|
+
'replay.nodes': this.events.length,
|
|
237
295
|
agentVersion: agentRuntime.version,
|
|
238
296
|
session: agentRuntime.session.state.value,
|
|
239
297
|
hasMeta: this.hasMeta,
|
|
240
298
|
hasSnapshot: this.hasSnapshot,
|
|
241
299
|
hasError: this.hasError,
|
|
242
|
-
isFirstChunk:
|
|
300
|
+
isFirstChunk: agentRuntime.session.state.sessionReplaySentFirstChunk === false,
|
|
243
301
|
decompressedBytes: this.payloadBytesEstimation,
|
|
244
302
|
'nr.rrweb.version': RRWEB_VERSION
|
|
245
303
|
}, MAX_PAYLOAD_SIZE - this.payloadBytesEstimation).substring(1) // remove the leading '&'
|
|
@@ -251,7 +309,7 @@ export class Aggregate extends AggregateBase {
|
|
|
251
309
|
onHarvestFinished (result) {
|
|
252
310
|
// The mutual decision for now is to stop recording and clear buffers if ingest is experiencing 429 rate limiting
|
|
253
311
|
if (result.status === 429) {
|
|
254
|
-
this.abort()
|
|
312
|
+
this.abort(ABORT_REASONS.TOO_MANY)
|
|
255
313
|
}
|
|
256
314
|
|
|
257
315
|
if (this.blocked) this.scheduler.stopTimer(true)
|
|
@@ -260,7 +318,6 @@ export class Aggregate extends AggregateBase {
|
|
|
260
318
|
/** Clears the buffer (this.events), and resets all payload metadata properties */
|
|
261
319
|
clearBuffer () {
|
|
262
320
|
this.events = []
|
|
263
|
-
this.isFirstChunk = false
|
|
264
321
|
this.hasSnapshot = false
|
|
265
322
|
this.hasMeta = false
|
|
266
323
|
this.hasError = false
|
|
@@ -272,7 +329,7 @@ export class Aggregate extends AggregateBase {
|
|
|
272
329
|
startRecording () {
|
|
273
330
|
if (!recorder) {
|
|
274
331
|
warn('Recording library was never imported')
|
|
275
|
-
return this.abort()
|
|
332
|
+
return this.abort(ABORT_REASONS.IMPORT)
|
|
276
333
|
}
|
|
277
334
|
this.clearTimestamps()
|
|
278
335
|
// set the fallbacks as early as possible
|
|
@@ -301,7 +358,7 @@ export class Aggregate extends AggregateBase {
|
|
|
301
358
|
|
|
302
359
|
/** Store a payload in the buffer (this.events). This should be the callback to the recording lib noticing a mutation */
|
|
303
360
|
store (event, isCheckout) {
|
|
304
|
-
this.setTimestamps(
|
|
361
|
+
this.setTimestamps()
|
|
305
362
|
if (this.blocked) return
|
|
306
363
|
const eventBytes = stringify(event).length
|
|
307
364
|
/** The estimated size of the payload after compression */
|
|
@@ -309,7 +366,7 @@ export class Aggregate extends AggregateBase {
|
|
|
309
366
|
// Vortex will block payloads at a certain size, we might as well not send.
|
|
310
367
|
if (payloadSize > MAX_PAYLOAD_SIZE) {
|
|
311
368
|
this.clearBuffer()
|
|
312
|
-
return this.abort()
|
|
369
|
+
return this.abort(ABORT_REASONS.TOO_BIG)
|
|
313
370
|
}
|
|
314
371
|
// Checkout events are flags by the recording lib that indicate a fullsnapshot was taken every n ms. These are important
|
|
315
372
|
// to help reconstruct the replay later and must be included. While waiting and buffering for errors to come through,
|
|
@@ -320,19 +377,13 @@ export class Aggregate extends AggregateBase {
|
|
|
320
377
|
}
|
|
321
378
|
|
|
322
379
|
// meta event
|
|
323
|
-
if (event.type ===
|
|
380
|
+
if (event.type === RRWEB_EVENT_TYPES.Meta) {
|
|
324
381
|
this.hasMeta = true
|
|
325
382
|
this.lastMeta = event
|
|
326
383
|
}
|
|
327
384
|
// snapshot event
|
|
328
|
-
if (event.type ===
|
|
385
|
+
if (event.type === RRWEB_EVENT_TYPES.FullSnapshot) {
|
|
329
386
|
this.hasSnapshot = true
|
|
330
|
-
// small chance that the meta event got separated from its matching snapshot across payload harvests
|
|
331
|
-
// it needs to precede the snapshot, so shove it in first.
|
|
332
|
-
if (!this.hasMeta) {
|
|
333
|
-
this.events.push(this.lastMeta)
|
|
334
|
-
this.hasMeta = true
|
|
335
|
-
}
|
|
336
387
|
}
|
|
337
388
|
|
|
338
389
|
this.events.push(event)
|
|
@@ -352,18 +403,13 @@ export class Aggregate extends AggregateBase {
|
|
|
352
403
|
recorder.takeFullSnapshot()
|
|
353
404
|
}
|
|
354
405
|
|
|
355
|
-
setTimestamps (
|
|
406
|
+
setTimestamps () {
|
|
356
407
|
// fallbacks if timestamps cannot be derived from rrweb events
|
|
357
|
-
this.
|
|
358
|
-
if (!this.timestamp.cycle.first) this.timestamp.cycle.first = this.timestamp.cycle.last
|
|
359
|
-
// timestamps based on rrweb events
|
|
360
|
-
if (!event || !event.timestamp) return
|
|
361
|
-
if (!this.timestamp.event.first) this.timestamp.event.first = event.timestamp
|
|
362
|
-
this.timestamp.event.last = event.timestamp
|
|
408
|
+
if (!this.cycleTimestamp) this.cycleTimestamp = getRuntime(this.agentIdentifier).offset + globalScope.performance.now()
|
|
363
409
|
}
|
|
364
410
|
|
|
365
411
|
clearTimestamps () {
|
|
366
|
-
this.
|
|
412
|
+
this.cycleTimestamp = undefined
|
|
367
413
|
}
|
|
368
414
|
|
|
369
415
|
/** Estimate the payload size */
|
|
@@ -373,7 +419,9 @@ export class Aggregate extends AggregateBase {
|
|
|
373
419
|
}
|
|
374
420
|
|
|
375
421
|
/** Abort the feature, once aborted it will not resume */
|
|
376
|
-
abort () {
|
|
422
|
+
abort (reason = {}) {
|
|
423
|
+
warn(`SR aborted -- ${reason.message}`)
|
|
424
|
+
this.ee.emit(SUPPORTABILITY_METRIC_CHANNEL, [`SessionReplay/Abort/${ABORT_REASONS[reason.sm]}`])
|
|
377
425
|
this.blocked = true
|
|
378
426
|
this.mode = MODE.OFF
|
|
379
427
|
this.stopRecording()
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { getRuntime } from '../../common/config/config'
|
|
2
1
|
import { ee } from '../../common/event-emitter/contextual-ee'
|
|
3
2
|
|
|
4
3
|
export class FeatureBase {
|
|
@@ -8,7 +7,7 @@ export class FeatureBase {
|
|
|
8
7
|
/** @type {Aggregator} */
|
|
9
8
|
this.aggregator = aggregator
|
|
10
9
|
/** @type {ContextualEE} */
|
|
11
|
-
this.ee = ee.get(agentIdentifier
|
|
10
|
+
this.ee = ee.get(agentIdentifier)
|
|
12
11
|
/** @type {string} */
|
|
13
12
|
this.featureName = featureName
|
|
14
13
|
/**
|
|
@@ -108,6 +108,7 @@ export class InstrumentBase extends FeatureBase {
|
|
|
108
108
|
warn(`Downloading and initializing ${this.featureName} failed...`, e)
|
|
109
109
|
this.abortHandler?.() // undo any important alterations made to the page
|
|
110
110
|
// not supported yet but nice to do: "abort" this agent's EE for this feature specifically
|
|
111
|
+
drain(this.agentIdentifier, this.featureName)
|
|
111
112
|
loadedSuccessfully(false)
|
|
112
113
|
}
|
|
113
114
|
}
|
package/src/loaders/api/api.js
CHANGED
|
@@ -19,7 +19,7 @@ export const CUSTOM_ATTR_GROUP = 'CUSTOM/' // the subgroup items should be store
|
|
|
19
19
|
export function setTopLevelCallers () {
|
|
20
20
|
const nr = gosCDN()
|
|
21
21
|
const funcs = [
|
|
22
|
-
'setErrorHandler', 'finished', 'addToTrace', '
|
|
22
|
+
'setErrorHandler', 'finished', 'addToTrace', 'addRelease',
|
|
23
23
|
'addPageAction', 'setCurrentRouteName', 'setPageViewName', 'setCustomAttribute',
|
|
24
24
|
'interaction', 'noticeError', 'setUserId', 'setApplicationVersion', 'start'
|
|
25
25
|
]
|
|
@@ -48,7 +48,6 @@ export function setAPI (agentIdentifier, forceDrain) {
|
|
|
48
48
|
'setErrorHandler',
|
|
49
49
|
'finished',
|
|
50
50
|
'addToTrace',
|
|
51
|
-
'inlineHit',
|
|
52
51
|
'addRelease'
|
|
53
52
|
]
|
|
54
53
|
|
|
@@ -1,22 +1,18 @@
|
|
|
1
1
|
import { FEATURE_NAMES } from '../features/features'
|
|
2
|
-
import {
|
|
2
|
+
import { getRuntime } from '../../common/config/config'
|
|
3
3
|
import { ee } from '../../common/event-emitter/contextual-ee'
|
|
4
4
|
import { handle } from '../../common/event-emitter/handle'
|
|
5
5
|
import { registerHandler } from '../../common/event-emitter/register-handler'
|
|
6
6
|
import { single } from '../../common/util/invoke'
|
|
7
|
-
import * as submitData from '../../common/util/submit-data'
|
|
8
|
-
import { isBrowserScope } from '../../common/constants/runtime'
|
|
9
7
|
import { CUSTOM_METRIC_CHANNEL } from '../../features/metrics/constants'
|
|
10
8
|
|
|
11
9
|
export function setAPI (agentIdentifier) {
|
|
12
10
|
var instanceEE = ee.get(agentIdentifier)
|
|
13
|
-
var cycle = 0
|
|
14
11
|
|
|
15
12
|
var api = {
|
|
16
13
|
finished: single(finished),
|
|
17
14
|
setErrorHandler,
|
|
18
15
|
addToTrace,
|
|
19
|
-
inlineHit,
|
|
20
16
|
addRelease
|
|
21
17
|
}
|
|
22
18
|
|
|
@@ -47,40 +43,6 @@ export function setAPI (agentIdentifier) {
|
|
|
47
43
|
handle('bstApi', [report], undefined, FEATURE_NAMES.sessionTrace, instanceEE)
|
|
48
44
|
}
|
|
49
45
|
|
|
50
|
-
// NREUM.inlineHit(requestName, queueTime, appTime, totalBackEndTime, domTime, frontEndTime)
|
|
51
|
-
//
|
|
52
|
-
// requestName - the 'web page' name or service name
|
|
53
|
-
// queueTime - the amount of time spent in the app tier queue
|
|
54
|
-
// appTime - the amount of time spent in the application code
|
|
55
|
-
// totalBackEndTime - the total roundtrip time of the remote service call
|
|
56
|
-
// domTime - the time spent processing the result of the service call (or user defined)
|
|
57
|
-
// frontEndTime - the time spent rendering the result of the service call (or user defined)
|
|
58
|
-
function inlineHit (t, requestName, queueTime, appTime, totalBackEndTime, domTime, frontEndTime) {
|
|
59
|
-
if (!isBrowserScope) return
|
|
60
|
-
|
|
61
|
-
requestName = window.encodeURIComponent(requestName)
|
|
62
|
-
cycle += 1
|
|
63
|
-
|
|
64
|
-
const agentInfo = getInfo(agentIdentifier)
|
|
65
|
-
if (!agentInfo.beacon) return
|
|
66
|
-
|
|
67
|
-
const agentInit = getConfiguration(agentIdentifier)
|
|
68
|
-
const scheme = agentInit.ssl === false ? 'http' : 'https'
|
|
69
|
-
const beacon = agentInit.proxy.beacon || agentInfo.beacon
|
|
70
|
-
let url = `${scheme}://${beacon}/1/${agentInfo.licenseKey}`
|
|
71
|
-
|
|
72
|
-
url += '?a=' + agentInfo.applicationID + '&'
|
|
73
|
-
url += 't=' + requestName + '&'
|
|
74
|
-
url += 'qt=' + ~~queueTime + '&'
|
|
75
|
-
url += 'ap=' + ~~appTime + '&'
|
|
76
|
-
url += 'be=' + ~~totalBackEndTime + '&'
|
|
77
|
-
url += 'dc=' + ~~domTime + '&'
|
|
78
|
-
url += 'fe=' + ~~frontEndTime + '&'
|
|
79
|
-
url += 'c=' + cycle
|
|
80
|
-
|
|
81
|
-
submitData.xhr({ url })
|
|
82
|
-
}
|
|
83
|
-
|
|
84
46
|
function setErrorHandler (t, handler) {
|
|
85
47
|
getRuntime(agentIdentifier).onerror = handler
|
|
86
48
|
}
|
|
@@ -33,7 +33,7 @@ export function configure (agentIdentifier, opts = {}, loaderType, forceDrain) {
|
|
|
33
33
|
if (!alreadySetOnce) {
|
|
34
34
|
alreadySetOnce = true
|
|
35
35
|
if (updatedInit.proxy.assets) {
|
|
36
|
-
redefinePublicPath(updatedInit.proxy.assets
|
|
36
|
+
redefinePublicPath(updatedInit.proxy.assets)
|
|
37
37
|
internalTrafficList.push(updatedInit.proxy.assets)
|
|
38
38
|
}
|
|
39
39
|
if (updatedInit.proxy.beacon) internalTrafficList.push(updatedInit.proxy.beacon)
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
// Set the default CDN or remote for fetching the assets; NPM shouldn't change this var.
|
|
2
2
|
|
|
3
|
-
export const redefinePublicPath = (
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
export const redefinePublicPath = (urlString) => {
|
|
4
|
+
const isOrigin = urlString.startsWith('http')
|
|
5
|
+
// Input is not expected to end in a slash, but webpack concats as-is, so one is inserted.
|
|
6
|
+
urlString += '/'
|
|
7
|
+
// If there's no existing HTTP scheme, the secure protocol is prepended by default.
|
|
8
|
+
__webpack_public_path__ = isOrigin ? urlString : 'https://' + urlString // eslint-disable-line
|
|
6
9
|
}
|
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
import { Aggregator } from './aggregator.js'
|
|
2
|
-
|
|
3
|
-
let aggregator
|
|
4
|
-
beforeEach(() => {
|
|
5
|
-
aggregator = new Aggregator({ agentIdentifier: 'blah' })
|
|
6
|
-
})
|
|
7
|
-
|
|
8
|
-
test('storing and getting buckets', () => {
|
|
9
|
-
let bucket = aggregator.store('abc', '123')
|
|
10
|
-
const expectedBucketPattern = { params: {}, metrics: { count: 1 } }
|
|
11
|
-
expect(bucket).toEqual(expectedBucketPattern)
|
|
12
|
-
expect(aggregator.get('abc', '123')).toEqual(expectedBucketPattern)
|
|
13
|
-
|
|
14
|
-
aggregator.store('abc', '456')
|
|
15
|
-
// check we can get all buckets under the same type
|
|
16
|
-
expect(aggregator.get('abc')).toEqual({ 123: expectedBucketPattern, 456: expectedBucketPattern })
|
|
17
|
-
})
|
|
18
|
-
|
|
19
|
-
describe('storing the same bucket again', () => {
|
|
20
|
-
test('increments the count', () => {
|
|
21
|
-
aggregator.store('abc', '123')
|
|
22
|
-
aggregator.store('abc', '123')
|
|
23
|
-
expect(aggregator.get('abc', '123')).toEqual({ params: {}, metrics: { count: 2 } })
|
|
24
|
-
})
|
|
25
|
-
|
|
26
|
-
test('does not overwrite params', () => {
|
|
27
|
-
aggregator.store('def', '123', { someParam: true })
|
|
28
|
-
aggregator.store('def', '123', { anotherParam: true })
|
|
29
|
-
expect(aggregator.get('def', '123')).toEqual({ params: { someParam: true }, metrics: { count: 2 } })
|
|
30
|
-
})
|
|
31
|
-
|
|
32
|
-
test('does not overwrite custom params either', () => {
|
|
33
|
-
aggregator.store('ghi', '123', undefined, undefined, { someCustomParam: true })
|
|
34
|
-
aggregator.store('ghi', '123', undefined, undefined, { someCustomParam: false })
|
|
35
|
-
expect(aggregator.get('ghi', '123')).toEqual({ params: {}, custom: { someCustomParam: true }, metrics: { count: 2 } })
|
|
36
|
-
})
|
|
37
|
-
})
|
|
38
|
-
|
|
39
|
-
describe('metrics are properly updated', () => {
|
|
40
|
-
test('when using store fn', () => {
|
|
41
|
-
const expectedBucketPattern = {
|
|
42
|
-
params: {},
|
|
43
|
-
metrics: {
|
|
44
|
-
count: 2,
|
|
45
|
-
met1: { t: 3, min: 1, max: 2, sos: 5, c: 2 },
|
|
46
|
-
met2: { t: 7, min: 3, max: 4, sos: 25, c: 2 }
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
aggregator.store('abc', '123', undefined, { met1: 1, met2: 3 })
|
|
50
|
-
aggregator.store('abc', '123', undefined, { met1: 2, met2: 4 })
|
|
51
|
-
expect(aggregator.get('abc', '123')).toEqual(expectedBucketPattern)
|
|
52
|
-
})
|
|
53
|
-
|
|
54
|
-
test('when using storeMetric fn', () => {
|
|
55
|
-
const expectedBucketPattern = {
|
|
56
|
-
params: {},
|
|
57
|
-
stats: { t: 6, min: 1, max: 3, sos: 14, c: 3 }
|
|
58
|
-
}
|
|
59
|
-
aggregator.storeMetric('abc', 'metric', undefined, 2)
|
|
60
|
-
aggregator.storeMetric('abc', 'metric', undefined, 1)
|
|
61
|
-
aggregator.storeMetric('abc', 'metric', undefined, 3)
|
|
62
|
-
expect(aggregator.get('abc', 'metric')).toEqual(expectedBucketPattern)
|
|
63
|
-
})
|
|
64
|
-
})
|
|
65
|
-
|
|
66
|
-
test('take fn gets and deletes correctly', () => {
|
|
67
|
-
aggregator.store('type1', 'a')
|
|
68
|
-
aggregator.store('type1', 'b')
|
|
69
|
-
aggregator.store('type2', 'a')
|
|
70
|
-
aggregator.store('type3', 'a')
|
|
71
|
-
|
|
72
|
-
expect(aggregator.take(['type0'])).toBeNull() // when type dne in aggregator
|
|
73
|
-
let obj = aggregator.take(['type1', 'type2'])
|
|
74
|
-
expect(obj.type1.length).toEqual(2)
|
|
75
|
-
expect(obj.type2.length).toEqual(1)
|
|
76
|
-
expect(aggregator.get('type1', 'a')).toBeUndefined() // should be gone now
|
|
77
|
-
expect(aggregator.get('type3', 'a')).toEqual(expect.any(Object))
|
|
78
|
-
})
|
|
79
|
-
|
|
80
|
-
test('merge fn combines metrics correctly', () => {
|
|
81
|
-
aggregator.store('abc', '123', undefined, { met1: 1, met2: 3 })
|
|
82
|
-
let bucket = aggregator.store('abc', '123', undefined, { met1: 2 })
|
|
83
|
-
const expectedMetrics = {
|
|
84
|
-
count: 2,
|
|
85
|
-
met1: { t: 3, min: 1, max: 2, sos: 5, c: 2 },
|
|
86
|
-
met2: { t: 3 }
|
|
87
|
-
}
|
|
88
|
-
expect(bucket.metrics).toEqual(expectedMetrics)
|
|
89
|
-
|
|
90
|
-
aggregator.merge('abc', '456', bucket.metrics)
|
|
91
|
-
expect(aggregator.get('abc', '456').metrics).toEqual(expectedMetrics)
|
|
92
|
-
|
|
93
|
-
aggregator.merge('abc', '123', {
|
|
94
|
-
count: 4,
|
|
95
|
-
met1: { t: 4, min: 0, max: 4, sos: 16, c: 2 },
|
|
96
|
-
met2: { t: 5 },
|
|
97
|
-
met3: { t: 6 },
|
|
98
|
-
met4: { t: 7, min: 3, max: 4, sos: 25, c: 2 }
|
|
99
|
-
})
|
|
100
|
-
expect(aggregator.get('abc', '123').metrics).toEqual({
|
|
101
|
-
count: 6,
|
|
102
|
-
met1: { t: 7, min: 0, max: 4, sos: 21, c: 4 },
|
|
103
|
-
met2: { t: 8, min: 3, max: 5, sos: 34, c: 2 },
|
|
104
|
-
met3: { t: 6 },
|
|
105
|
-
met4: { t: 7, min: 3, max: 4, sos: 25, c: 2 }
|
|
106
|
-
})
|
|
107
|
-
})
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
import { getModeledObject } from './configurable'
|
|
2
|
-
|
|
3
|
-
test('if either params are not objects, function fails', () => {
|
|
4
|
-
let result = getModeledObject(123, {})
|
|
5
|
-
expect(result).toEqual(undefined)
|
|
6
|
-
|
|
7
|
-
result = getModeledObject({}, () => 'whatever')
|
|
8
|
-
expect(result).toEqual(undefined)
|
|
9
|
-
})
|
|
10
|
-
|
|
11
|
-
test('models are not mutated by function', () => {
|
|
12
|
-
const template = { dontchangethis: true }
|
|
13
|
-
getModeledObject({ dontchangethis: false, addthis: true }, template)
|
|
14
|
-
expect(template).toEqual({ dontchangethis: true })
|
|
15
|
-
})
|
|
16
|
-
|
|
17
|
-
const model = {
|
|
18
|
-
eat: true,
|
|
19
|
-
sleep: true,
|
|
20
|
-
rave: true,
|
|
21
|
-
repeat: {
|
|
22
|
-
eat: true,
|
|
23
|
-
sleep: true,
|
|
24
|
-
rave: true,
|
|
25
|
-
repeat: false
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
test('empty object does not change model output', () => {
|
|
30
|
-
expect(getModeledObject({}, model)).toEqual(model)
|
|
31
|
-
})
|
|
32
|
-
|
|
33
|
-
test('existing keys get their values changed', () => {
|
|
34
|
-
let obj = {
|
|
35
|
-
sleep: false,
|
|
36
|
-
repeat: {
|
|
37
|
-
eat: false
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
expect(getModeledObject(obj, model)).toEqual({
|
|
41
|
-
eat: true,
|
|
42
|
-
sleep: false,
|
|
43
|
-
rave: true,
|
|
44
|
-
repeat: {
|
|
45
|
-
eat: false, // should recurse
|
|
46
|
-
sleep: true,
|
|
47
|
-
rave: true,
|
|
48
|
-
repeat: false
|
|
49
|
-
}
|
|
50
|
-
})
|
|
51
|
-
})
|
|
52
|
-
|
|
53
|
-
test('existing keys can change types too, to/from object', () => {
|
|
54
|
-
// in other words, if replacement is not an object, it has no effect
|
|
55
|
-
let obj = {
|
|
56
|
-
rave: {},
|
|
57
|
-
repeat: 'just a sad lonely string now'
|
|
58
|
-
}
|
|
59
|
-
expect(getModeledObject(obj, model)).toEqual({
|
|
60
|
-
eat: true,
|
|
61
|
-
sleep: true,
|
|
62
|
-
rave: {},
|
|
63
|
-
repeat: 'just a sad lonely string now'
|
|
64
|
-
})
|
|
65
|
-
})
|
|
66
|
-
|
|
67
|
-
test('keys not in model are ignored', () => {
|
|
68
|
-
expect(getModeledObject({ game: 'allnight' }, model)).toEqual(model)
|
|
69
|
-
})
|
|
70
|
-
|
|
71
|
-
test('but if model is empty, then keys are not ignored', () => {
|
|
72
|
-
expect(getModeledObject({ game: 'allnight' }, {})).toEqual({ game: 'allnight' })
|
|
73
|
-
})
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
let isValid, getInfo, setInfo
|
|
2
|
-
beforeEach(async () => {
|
|
3
|
-
jest.resetModules()
|
|
4
|
-
;({ isValid, getInfo, setInfo } = await import('./info.js'))
|
|
5
|
-
})
|
|
6
|
-
|
|
7
|
-
test('set/getInfo should throw on an invalid agent id', () => {
|
|
8
|
-
// currently only checks if it's a truthy value
|
|
9
|
-
expect(() => setInfo(undefined, {})).toThrow('require an agent id')
|
|
10
|
-
expect(() => getInfo(undefined)).toThrow('require an agent id')
|
|
11
|
-
expect(() => setInfo('', {})).toThrow('require an agent id')
|
|
12
|
-
expect(() => getInfo('')).toThrow('require an agent id')
|
|
13
|
-
})
|
|
14
|
-
|
|
15
|
-
test('set/getInfo works correctly', () => {
|
|
16
|
-
expect(() => setInfo(123, { beacon: 'im here' })).not.toThrow() // notice setInfo accepts numbers
|
|
17
|
-
let cachedObj = getInfo('123')
|
|
18
|
-
expect(Object.keys(cachedObj).length).toBeGreaterThan(1)
|
|
19
|
-
expect(cachedObj.beacon).toEqual('im here')
|
|
20
|
-
expect(cachedObj.licenseKey).toEqual(undefined) // this should mirror default in info.js
|
|
21
|
-
})
|
|
22
|
-
|
|
23
|
-
test('isValid is correct', () => {
|
|
24
|
-
setInfo('ab', { errorBeacon: 'some value' })
|
|
25
|
-
setInfo('cd', { errorBeacon: 'some value', licenseKey: 'cereal box' })
|
|
26
|
-
expect(isValid('ab')).toBeFalsy()
|
|
27
|
-
expect(isValid('cd')).toBeFalsy()
|
|
28
|
-
|
|
29
|
-
setInfo('ef', { errorBeacon: 'some value', licenseKey: 'cereal box', applicationID: '25' })
|
|
30
|
-
expect(isValid('ef')).toBeTruthy()
|
|
31
|
-
})
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
let getConfiguration, setConfiguration, getConfigurationValue
|
|
2
|
-
beforeEach(async () => {
|
|
3
|
-
jest.resetModules()
|
|
4
|
-
;({ getConfiguration, setConfiguration, getConfigurationValue } = await import('./init.js'))
|
|
5
|
-
})
|
|
6
|
-
|
|
7
|
-
test('set/getConfiguration should throw on an invalid agent id', () => {
|
|
8
|
-
// currently only checks if it's a truthy value
|
|
9
|
-
expect(() => setConfiguration(undefined, {})).toThrow('require an agent id')
|
|
10
|
-
expect(() => getConfiguration(undefined)).toThrow('require an agent id')
|
|
11
|
-
expect(() => setConfiguration('', {})).toThrow('require an agent id')
|
|
12
|
-
expect(() => getConfiguration('')).toThrow('require an agent id')
|
|
13
|
-
})
|
|
14
|
-
|
|
15
|
-
test('set/getConfiguration works correctly', () => {
|
|
16
|
-
expect(() => setConfiguration(123, { jserrors: { enabled: false } })).not.toThrow() // notice setConfiguration accepts numbers
|
|
17
|
-
let cachedObj = getConfiguration('123')
|
|
18
|
-
expect(Object.keys(cachedObj).length).toBeGreaterThan(1)
|
|
19
|
-
expect(cachedObj.jserrors.enabled).toEqual(false)
|
|
20
|
-
expect(cachedObj.page_action.enabled).toEqual(true) // this should mirror default in init.js
|
|
21
|
-
})
|
|
22
|
-
|
|
23
|
-
test('getConfigurationValue parses path correctly', () => {
|
|
24
|
-
setConfiguration('ab', { page_action: { harvestTimeSeconds: 1000 } })
|
|
25
|
-
expect(getConfigurationValue('ab', '')).toBeUndefined()
|
|
26
|
-
expect(getConfigurationValue('ab', 'page_action')).toEqual({ enabled: true, harvestTimeSeconds: 1000, autoStart: true })
|
|
27
|
-
expect(getConfigurationValue('ab', 'page_action.harvestTimeSeconds')).toEqual(1000)
|
|
28
|
-
})
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
let getLoaderConfig, setLoaderConfig
|
|
2
|
-
beforeEach(async () => {
|
|
3
|
-
jest.resetModules()
|
|
4
|
-
;({ getLoaderConfig, setLoaderConfig } = await import('./loader-config.js'))
|
|
5
|
-
})
|
|
6
|
-
|
|
7
|
-
test('set/getLoaderConfig should throw on an invalid agent id', () => {
|
|
8
|
-
// currently only checks if it's a truthy value
|
|
9
|
-
expect(() => setLoaderConfig(undefined, {})).toThrow('require an agent id')
|
|
10
|
-
expect(() => getLoaderConfig(undefined)).toThrow('require an agent id')
|
|
11
|
-
expect(() => setLoaderConfig('', {})).toThrow('require an agent id')
|
|
12
|
-
expect(() => getLoaderConfig('')).toThrow('require an agent id')
|
|
13
|
-
})
|
|
14
|
-
|
|
15
|
-
test('set/getLoaderConfig works correctly', () => {
|
|
16
|
-
expect(() => setLoaderConfig(123, { trustKey: 1 })).not.toThrow() // notice setLoaderConfig accepts numbers
|
|
17
|
-
let cachedObj = getLoaderConfig('123')
|
|
18
|
-
expect(Object.keys(cachedObj).length).toBeGreaterThan(1)
|
|
19
|
-
expect(cachedObj.trustKey).toEqual(1)
|
|
20
|
-
expect(cachedObj.xpid).toBeUndefined() // this should mirror default in loader-config.js
|
|
21
|
-
})
|