@newrelic/browser-agent 1.234.0 → 1.235.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/README.md +1 -1
- package/dist/cjs/common/constants/env.cdn.js +1 -1
- package/dist/cjs/common/constants/env.npm.js +1 -1
- package/dist/cjs/common/constants/shared-channel.js +19 -0
- package/dist/cjs/common/harvest/harvest-scheduler.js +21 -5
- package/dist/cjs/common/session/{session-entity.test.js → session-entity.component-test.js} +79 -42
- package/dist/cjs/common/session/session-entity.js +19 -11
- package/dist/cjs/common/timer/interaction-timer.js +1 -1
- package/dist/cjs/common/url/canonicalize-url.test.js +26 -30
- package/dist/cjs/common/util/data-size.test.js +37 -20
- package/dist/cjs/common/util/feature-flags.js +23 -12
- package/dist/cjs/common/util/feature-flags.test.js +84 -0
- package/dist/cjs/common/util/global-scope.js +1 -32
- package/dist/cjs/common/util/global-scope.test.js +72 -0
- package/dist/cjs/common/util/obfuscate.component-test.js +129 -0
- package/dist/cjs/common/util/obfuscate.js +2 -2
- package/dist/cjs/common/util/submit-data.js +3 -3
- package/dist/cjs/common/util/submit-data.test.js +145 -121
- package/dist/cjs/common/wrap/wrap-raf.js +1 -1
- package/dist/cjs/common/wrap/wrap-timer.js +1 -1
- package/dist/cjs/features/jserrors/aggregate/index.js +4 -0
- package/dist/cjs/features/jserrors/instrument/index.js +2 -15
- package/dist/cjs/features/session_replay/aggregate/index.component-test.js +457 -0
- package/dist/cjs/features/session_replay/aggregate/index.js +99 -82
- package/dist/cjs/features/session_replay/replay-mode.js +28 -0
- package/dist/cjs/features/session_trace/aggregate/index.js +222 -99
- package/dist/cjs/features/session_trace/constants.js +1 -3
- package/dist/cjs/features/session_trace/instrument/index.js +0 -16
- package/dist/cjs/features/spa/constants.js +0 -1
- package/dist/cjs/features/utils/agent-session.js +20 -36
- package/dist/cjs/features/utils/agent-session.test.js +211 -0
- package/dist/cjs/features/utils/aggregate-base.js +7 -12
- package/dist/cjs/features/utils/aggregate-base.test.js +110 -0
- package/dist/cjs/features/utils/feature-base.test.js +42 -0
- package/dist/cjs/features/utils/handler-cache.js +28 -23
- package/dist/cjs/features/utils/handler-cache.test.js +53 -0
- package/dist/cjs/features/utils/instrument-base.js +58 -39
- package/dist/cjs/features/utils/instrument-base.test.js +179 -0
- package/dist/cjs/features/utils/lazy-feature-loader.test.js +30 -0
- package/dist/cjs/loaders/agent.js +0 -1
- package/dist/cjs/loaders/api/api.js +1 -1
- package/dist/cjs/loaders/features/featureDependencies.js +2 -0
- package/dist/esm/common/constants/env.cdn.js +1 -1
- package/dist/esm/common/constants/env.npm.js +1 -1
- package/dist/esm/common/constants/shared-channel.js +12 -0
- package/dist/esm/common/harvest/harvest-scheduler.js +21 -5
- package/dist/esm/common/session/{session-entity.test.js → session-entity.component-test.js} +77 -40
- package/dist/esm/common/session/session-entity.js +17 -11
- package/dist/esm/common/timer/interaction-timer.js +1 -1
- package/dist/esm/common/url/canonicalize-url.test.js +25 -29
- package/dist/esm/common/util/data-size.test.js +35 -20
- package/dist/esm/common/util/feature-flags.js +23 -12
- package/dist/esm/common/util/feature-flags.test.js +80 -0
- package/dist/esm/common/util/global-scope.js +1 -29
- package/dist/esm/common/util/global-scope.test.js +70 -0
- package/dist/esm/common/util/obfuscate.component-test.js +125 -0
- package/dist/esm/common/util/obfuscate.js +2 -2
- package/dist/esm/common/util/submit-data.js +3 -3
- package/dist/esm/common/util/submit-data.test.js +143 -121
- package/dist/esm/common/wrap/wrap-raf.js +1 -1
- package/dist/esm/common/wrap/wrap-timer.js +1 -1
- package/dist/esm/features/jserrors/aggregate/index.js +4 -0
- package/dist/esm/features/jserrors/instrument/index.js +2 -15
- package/dist/esm/features/session_replay/aggregate/index.component-test.js +453 -0
- package/dist/esm/features/session_replay/aggregate/index.js +92 -78
- package/dist/esm/features/session_replay/replay-mode.js +23 -0
- package/dist/esm/features/session_trace/aggregate/index.js +223 -100
- package/dist/esm/features/session_trace/constants.js +0 -1
- package/dist/esm/features/session_trace/instrument/index.js +1 -17
- package/dist/esm/features/spa/constants.js +0 -1
- package/dist/esm/features/utils/agent-session.js +21 -37
- package/dist/esm/features/utils/agent-session.test.js +207 -0
- package/dist/esm/features/utils/aggregate-base.js +7 -12
- package/dist/esm/features/utils/aggregate-base.test.js +108 -0
- package/dist/esm/features/utils/feature-base.test.js +40 -0
- package/dist/esm/features/utils/handler-cache.js +28 -23
- package/dist/esm/features/utils/handler-cache.test.js +51 -0
- package/dist/esm/features/utils/instrument-base.js +58 -39
- package/dist/esm/features/utils/instrument-base.test.js +175 -0
- package/dist/esm/features/utils/lazy-feature-loader.test.js +29 -0
- package/dist/esm/loaders/agent.js +0 -1
- package/dist/esm/loaders/api/api.js +2 -2
- package/dist/esm/loaders/features/featureDependencies.js +2 -0
- package/dist/types/common/constants/shared-channel.d.ts +5 -0
- package/dist/types/common/constants/shared-channel.d.ts.map +1 -0
- package/dist/types/common/harvest/harvest-scheduler.component-test.d.ts +2 -0
- package/dist/types/common/harvest/harvest-scheduler.component-test.d.ts.map +1 -0
- package/dist/types/common/harvest/harvest-scheduler.d.ts +4 -0
- package/dist/types/common/harvest/harvest-scheduler.d.ts.map +1 -1
- package/dist/types/common/harvest/harvest.component-test.d.ts +2 -0
- package/dist/types/common/harvest/harvest.component-test.d.ts.map +1 -0
- package/dist/types/common/session/session-entity.component-test.d.ts +2 -0
- package/dist/types/common/session/session-entity.component-test.d.ts.map +1 -0
- package/dist/types/common/session/session-entity.d.ts +9 -5
- package/dist/types/common/session/session-entity.d.ts.map +1 -1
- package/dist/types/common/timer/interaction-timer.component-test.d.ts +2 -0
- package/dist/types/common/timer/interaction-timer.component-test.d.ts.map +1 -0
- package/dist/types/common/url/encode.component-test.d.ts +2 -0
- package/dist/types/common/url/encode.component-test.d.ts.map +1 -0
- package/dist/types/common/url/protocol.component-test.d.ts +2 -0
- package/dist/types/common/url/protocol.component-test.d.ts.map +1 -0
- package/dist/types/common/util/feature-flags.d.ts +1 -0
- package/dist/types/common/util/feature-flags.d.ts.map +1 -1
- package/dist/types/common/util/global-scope.d.ts +0 -9
- package/dist/types/common/util/global-scope.d.ts.map +1 -1
- package/dist/types/common/util/obfuscate.component-test.d.ts +2 -0
- package/dist/types/common/util/obfuscate.component-test.d.ts.map +1 -0
- package/dist/types/features/jserrors/aggregate/index.d.ts +1 -0
- package/dist/types/features/jserrors/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/session_replay/aggregate/index.component-test.d.ts +2 -0
- package/dist/types/features/session_replay/aggregate/index.component-test.d.ts.map +1 -0
- package/dist/types/features/session_replay/aggregate/index.d.ts +14 -5
- package/dist/types/features/session_replay/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/session_replay/instrument/index.d.ts.map +1 -1
- package/dist/types/features/session_replay/replay-mode.d.ts +9 -0
- package/dist/types/features/session_replay/replay-mode.d.ts.map +1 -0
- package/dist/types/features/session_trace/aggregate/index.d.ts +21 -3
- package/dist/types/features/session_trace/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/session_trace/constants.d.ts +0 -1
- package/dist/types/features/session_trace/constants.d.ts.map +1 -1
- package/dist/types/features/session_trace/instrument/index.d.ts +0 -2
- package/dist/types/features/session_trace/instrument/index.d.ts.map +1 -1
- package/dist/types/features/spa/constants.d.ts.map +1 -1
- package/dist/types/features/utils/agent-session.d.ts.map +1 -1
- package/dist/types/features/utils/aggregate-base.d.ts +6 -1
- package/dist/types/features/utils/aggregate-base.d.ts.map +1 -1
- package/dist/types/features/utils/handler-cache.d.ts +12 -11
- package/dist/types/features/utils/handler-cache.d.ts.map +1 -1
- package/dist/types/features/utils/instrument-base.d.ts +17 -1
- package/dist/types/features/utils/instrument-base.d.ts.map +1 -1
- package/dist/types/loaders/agent.d.ts.map +1 -1
- package/dist/types/loaders/features/featureDependencies.d.ts.map +1 -1
- package/package.json +9 -7
- package/src/common/constants/shared-channel.js +13 -0
- package/src/common/harvest/harvest-scheduler.js +17 -6
- package/src/common/session/{session-entity.test.js → session-entity.component-test.js} +70 -47
- package/src/common/session/session-entity.js +15 -12
- package/src/common/timer/interaction-timer.js +1 -1
- package/src/common/url/canonicalize-url.test.js +32 -21
- package/src/common/util/data-size.test.js +27 -20
- package/src/common/util/feature-flags.js +24 -12
- package/src/common/util/feature-flags.test.js +98 -0
- package/src/common/util/global-scope.js +0 -26
- package/src/common/util/global-scope.test.js +87 -0
- package/src/common/util/obfuscate.component-test.js +173 -0
- package/src/common/util/obfuscate.js +2 -2
- package/src/common/util/submit-data.js +3 -3
- package/src/common/util/submit-data.test.js +123 -115
- package/src/common/wrap/wrap-raf.js +1 -1
- package/src/common/wrap/wrap-timer.js +1 -1
- package/src/features/jserrors/aggregate/index.js +5 -0
- package/src/features/jserrors/instrument/index.js +2 -15
- package/src/features/session_replay/aggregate/index.component-test.js +368 -0
- package/src/features/session_replay/aggregate/index.js +96 -71
- package/src/features/session_replay/instrument/index.js +0 -1
- package/src/features/session_replay/replay-mode.js +23 -0
- package/src/features/session_trace/aggregate/index.js +198 -79
- package/src/features/session_trace/constants.js +0 -1
- package/src/features/session_trace/instrument/index.js +2 -19
- package/src/features/spa/constants.js +0 -1
- package/src/features/utils/agent-session.js +22 -34
- package/src/features/utils/agent-session.test.js +194 -0
- package/src/features/utils/aggregate-base.js +12 -9
- package/src/features/utils/aggregate-base.test.js +122 -0
- package/src/features/utils/feature-base.test.js +45 -0
- package/src/features/utils/handler-cache.js +29 -23
- package/src/features/utils/handler-cache.test.js +72 -0
- package/src/features/utils/instrument-base.js +45 -29
- package/src/features/utils/instrument-base.test.js +190 -0
- package/src/features/utils/lazy-feature-loader.test.js +37 -0
- package/src/loaders/agent.js +0 -1
- package/src/loaders/api/api.js +2 -2
- package/src/loaders/features/featureDependencies.js +2 -0
- package/dist/cjs/common/storage/local-memory.js +0 -35
- package/dist/cjs/common/storage/local-memory.test.js +0 -20
- package/dist/esm/common/storage/local-memory.js +0 -28
- package/dist/esm/common/storage/local-memory.test.js +0 -18
- package/dist/types/common/storage/local-memory.d.ts +0 -8
- package/dist/types/common/storage/local-memory.d.ts.map +0 -1
- package/src/common/storage/local-memory.js +0 -30
- package/src/common/storage/local-memory.test.js +0 -19
- /package/dist/cjs/common/harvest/{harvest-scheduler.test.js → harvest-scheduler.component-test.js} +0 -0
- /package/dist/cjs/common/harvest/{harvest.test.js → harvest.component-test.js} +0 -0
- /package/dist/cjs/common/timer/{interaction-timer.test.js → interaction-timer.component-test.js} +0 -0
- /package/dist/cjs/common/url/{encode.test.js → encode.component-test.js} +0 -0
- /package/dist/cjs/common/url/{protocol.test.js → protocol.component-test.js} +0 -0
- /package/dist/esm/common/harvest/{harvest-scheduler.test.js → harvest-scheduler.component-test.js} +0 -0
- /package/dist/esm/common/harvest/{harvest.test.js → harvest.component-test.js} +0 -0
- /package/dist/esm/common/timer/{interaction-timer.test.js → interaction-timer.component-test.js} +0 -0
- /package/dist/esm/common/url/{encode.test.js → encode.component-test.js} +0 -0
- /package/dist/esm/common/url/{protocol.test.js → protocol.component-test.js} +0 -0
- /package/src/common/harvest/{harvest-scheduler.test.js → harvest-scheduler.component-test.js} +0 -0
- /package/src/common/harvest/{harvest.test.js → harvest.component-test.js} +0 -0
- /package/src/common/timer/{interaction-timer.test.js → interaction-timer.component-test.js} +0 -0
- /package/src/common/url/{encode.test.js → encode.component-test.js} +0 -0
- /package/src/common/url/{protocol.test.js → protocol.component-test.js} +0 -0
|
@@ -1,34 +1,45 @@
|
|
|
1
|
+
import { faker } from '@faker-js/faker'
|
|
2
|
+
import * as globalScopeModule from '../util/global-scope'
|
|
3
|
+
import * as cleanUrlModule from './clean-url'
|
|
4
|
+
import { canonicalizeUrl } from './canonicalize-url'
|
|
5
|
+
|
|
6
|
+
jest.mock('../util/global-scope')
|
|
7
|
+
jest.mock('./clean-url')
|
|
8
|
+
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
jest.spyOn(cleanUrlModule, 'cleanURL').mockImplementation(input => input)
|
|
11
|
+
jest.replaceProperty(globalScopeModule, 'initialLocation', faker.internet.url())
|
|
12
|
+
})
|
|
13
|
+
|
|
1
14
|
afterEach(() => {
|
|
2
|
-
jest.
|
|
15
|
+
jest.resetAllMocks()
|
|
3
16
|
})
|
|
4
17
|
|
|
5
18
|
test.each([null, undefined, 34])('returns empty string when url argument is %s', async (url) => {
|
|
6
|
-
const { canonicalizeUrl } = await import('./canonicalize-url')
|
|
7
19
|
expect(canonicalizeUrl(url)).toEqual('')
|
|
8
20
|
})
|
|
9
21
|
|
|
10
|
-
test('
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
expect(
|
|
16
|
-
expect(
|
|
17
|
-
expect(canonicalizeUrl('https://www.example.com/?param=value#fragment')).toBe('https://www.example.com/')
|
|
22
|
+
test('uses cleanURL to clean the input and initial location URLs', () => {
|
|
23
|
+
const url = faker.internet.url()
|
|
24
|
+
canonicalizeUrl(url)
|
|
25
|
+
|
|
26
|
+
expect(cleanUrlModule.cleanURL).toHaveBeenCalledWith(globalScopeModule.initialLocation)
|
|
27
|
+
expect(cleanUrlModule.cleanURL).toHaveBeenCalledWith(url)
|
|
28
|
+
expect(cleanUrlModule.cleanURL).toHaveBeenCalledTimes(2)
|
|
18
29
|
})
|
|
19
30
|
|
|
20
|
-
test('returns <inline> when
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
31
|
+
test('returns <inline> when input and initial page urls are the same', async () => {
|
|
32
|
+
expect(canonicalizeUrl(globalScopeModule.initialLocation)).toEqual('<inline>')
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
test('returns input url when it does not match initial page url', async () => {
|
|
36
|
+
const url = faker.internet.url()
|
|
37
|
+
|
|
38
|
+
expect(canonicalizeUrl(url)).toEqual(url)
|
|
26
39
|
})
|
|
27
40
|
|
|
28
41
|
test('does not identify sub-paths of the loader origin as <inline>', async () => {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
const { canonicalizeUrl } = await import('./canonicalize-url')
|
|
33
|
-
expect(canonicalizeUrl('http://example.com/path/to/script.js')).not.toEqual('<inline>')
|
|
42
|
+
const url = globalScopeModule.initialLocation + '/path/to/script.js'
|
|
43
|
+
|
|
44
|
+
expect(canonicalizeUrl(url)).not.toEqual('<inline>')
|
|
34
45
|
})
|
|
@@ -1,24 +1,29 @@
|
|
|
1
|
+
import { faker } from '@faker-js/faker'
|
|
2
|
+
import * as stringifyModule from './stringify'
|
|
1
3
|
import { dataSize } from './data-size'
|
|
2
4
|
|
|
5
|
+
jest.mock('./stringify')
|
|
6
|
+
|
|
3
7
|
describe('dataSize', () => {
|
|
4
8
|
test('returns length of string', () => {
|
|
5
|
-
const str =
|
|
9
|
+
const str = faker.lorem.sentence()
|
|
6
10
|
expect(dataSize(str)).toBe(str.length)
|
|
7
11
|
})
|
|
8
12
|
|
|
9
13
|
test('returns undefined for non-object, number, or empty string', () => {
|
|
10
14
|
expect(dataSize(Infinity)).toBeUndefined()
|
|
11
|
-
expect(dataSize(
|
|
15
|
+
expect(dataSize(NaN)).toBeUndefined()
|
|
16
|
+
expect(dataSize(12345)).toBeUndefined()
|
|
12
17
|
expect(dataSize('')).toBeUndefined()
|
|
13
18
|
})
|
|
14
19
|
|
|
15
20
|
test('returns byte length of ArrayBuffer object', () => {
|
|
16
|
-
const buffer = new ArrayBuffer(
|
|
17
|
-
expect(dataSize(buffer)).toBe(
|
|
21
|
+
const buffer = new ArrayBuffer(faker.datatype.number({ min: 10, max: 100 }))
|
|
22
|
+
expect(dataSize(buffer)).toBe(buffer.byteLength)
|
|
18
23
|
})
|
|
19
24
|
|
|
20
25
|
test('returns size of Blob object', () => {
|
|
21
|
-
const blob = new Blob([
|
|
26
|
+
const blob = new Blob([faker.lorem.sentence()], { type: 'text/plain' })
|
|
22
27
|
expect(dataSize(blob)).toBe(blob.size)
|
|
23
28
|
})
|
|
24
29
|
|
|
@@ -27,24 +32,26 @@ describe('dataSize', () => {
|
|
|
27
32
|
expect(dataSize(formData)).toBeUndefined()
|
|
28
33
|
})
|
|
29
34
|
|
|
30
|
-
test('
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
num: 12345,
|
|
34
|
-
nestedObj: {
|
|
35
|
-
arr: [1, 2, 3]
|
|
36
|
-
}
|
|
35
|
+
test('uses stringify to get the length of an object', () => {
|
|
36
|
+
const input = {
|
|
37
|
+
[faker.datatype.uuid()]: faker.lorem.sentence()
|
|
37
38
|
}
|
|
38
|
-
const expectedSize =
|
|
39
|
-
|
|
39
|
+
const expectedSize = faker.datatype.number({ min: 1000, max: 10000 })
|
|
40
|
+
|
|
41
|
+
jest.spyOn(stringifyModule, 'stringify').mockReturnValue({ length: expectedSize })
|
|
42
|
+
|
|
43
|
+
expect(dataSize(input)).toBe(expectedSize)
|
|
40
44
|
})
|
|
41
45
|
|
|
42
|
-
test('
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
throw new Error('Error in toJSON')
|
|
46
|
-
}
|
|
46
|
+
test('should not throw an exception if stringify throws an exception', () => {
|
|
47
|
+
const input = {
|
|
48
|
+
[faker.datatype.uuid()]: faker.lorem.sentence()
|
|
47
49
|
}
|
|
48
|
-
|
|
50
|
+
const expectedSize = faker.datatype.number({ min: 1000, max: 10000 })
|
|
51
|
+
|
|
52
|
+
jest.spyOn(stringifyModule, 'stringify').mockImplementation(() => { throw new Error(faker.lorem.sentence()) })
|
|
53
|
+
|
|
54
|
+
expect(() => dataSize(input)).not.toThrow()
|
|
55
|
+
expect(dataSize(input)).toBeUndefined()
|
|
49
56
|
})
|
|
50
57
|
})
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
* Copyright 2020 New Relic Corporation. All rights reserved.
|
|
3
3
|
* SPDX-License-Identifier: Apache-2.0
|
|
4
4
|
*/
|
|
5
|
-
import { mapOwn } from './map-own'
|
|
6
5
|
import { ee } from '../event-emitter/contextual-ee'
|
|
7
6
|
import { handle } from '../event-emitter/handle'
|
|
8
7
|
import { drain } from '../drain/drain'
|
|
@@ -13,24 +12,37 @@ const bucketMap = {
|
|
|
13
12
|
err: [FEATURE_NAMES.jserrors, FEATURE_NAMES.metrics],
|
|
14
13
|
ins: [FEATURE_NAMES.pageAction],
|
|
15
14
|
spa: [FEATURE_NAMES.spa],
|
|
16
|
-
sr: [FEATURE_NAMES.sessionReplay]
|
|
15
|
+
sr: [FEATURE_NAMES.sessionReplay, FEATURE_NAMES.sessionTrace]
|
|
17
16
|
}
|
|
18
17
|
|
|
18
|
+
/** Note that this function only processes each unique flag ONCE, with the first occurrence of each flag and numeric value determining its switch on/off setting. */
|
|
19
19
|
export function activateFeatures (flags, agentIdentifier) {
|
|
20
|
-
|
|
20
|
+
const sharedEE = ee.get(agentIdentifier)
|
|
21
21
|
if (!(flags && typeof flags === 'object')) return
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
22
|
+
|
|
23
|
+
Object.entries(flags).forEach(([flag, num]) => {
|
|
24
|
+
if (activatedFeatures[flag] !== undefined) return
|
|
25
|
+
|
|
26
|
+
if (bucketMap[flag]) {
|
|
27
|
+
bucketMap[flag].forEach(feat => {
|
|
28
|
+
if (!num) handle('block-' + flag, [], undefined, feat, sharedEE)
|
|
29
|
+
else handle('feat-' + flag, [], undefined, feat, sharedEE)
|
|
30
|
+
|
|
31
|
+
handle('rumresp-' + flag, [Boolean(num)], undefined, feat, sharedEE) // this is a duplicate of feat-/block- but makes awaiting for 1 event easier than 2
|
|
26
32
|
})
|
|
27
|
-
}
|
|
33
|
+
} else if (num) handle('feat-' + flag, [], undefined, undefined, sharedEE) // not sure what other flags are overlooked, but there's a test for ones not in the map --
|
|
34
|
+
// ^^^ THIS DOESN'T ACTUALLY DO ANYTHHING AS UNDEFINED/FEATURE GROUP ISN'T DRAINED
|
|
35
|
+
|
|
36
|
+
activatedFeatures[flag] = Boolean(num)
|
|
37
|
+
})
|
|
28
38
|
|
|
29
|
-
|
|
30
|
-
|
|
39
|
+
// Let the features waiting on their respective flags know that RUM response was received and that any missing flags are interpreted as bad entitlement / "off".
|
|
40
|
+
// Hence, those features will not be hanging forever if their flags aren't included in the response.
|
|
41
|
+
Object.keys(bucketMap).forEach(flag => {
|
|
42
|
+
if (activatedFeatures[flag] === undefined) {
|
|
43
|
+
bucketMap[flag]?.forEach(feat => handle('rumresp-' + flag, [false], undefined, feat, sharedEE))
|
|
44
|
+
activatedFeatures[flag] = false
|
|
31
45
|
}
|
|
32
|
-
handle('feat-' + flag, [], undefined, bucketMap[flag], sharedEE)
|
|
33
|
-
activatedFeatures[flag] = true
|
|
34
46
|
})
|
|
35
47
|
drain(agentIdentifier, FEATURE_NAMES.pageViewEvent)
|
|
36
48
|
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { faker } from '@faker-js/faker'
|
|
2
|
+
import * as eventEmitterModule from '../event-emitter/contextual-ee'
|
|
3
|
+
import * as handleModule from '../event-emitter/handle'
|
|
4
|
+
import * as drainModule from '../drain/drain'
|
|
5
|
+
import { activateFeatures, activatedFeatures } from './feature-flags'
|
|
6
|
+
import { FEATURE_NAMES } from '../../loaders/features/features'
|
|
7
|
+
|
|
8
|
+
jest.mock('../event-emitter/handle')
|
|
9
|
+
jest.mock('../drain/drain')
|
|
10
|
+
jest.mock('../event-emitter/contextual-ee', () => ({
|
|
11
|
+
__esModule: true,
|
|
12
|
+
ee: {
|
|
13
|
+
get: jest.fn(() => ({
|
|
14
|
+
foo: `bar_${Math.random()}`
|
|
15
|
+
}))
|
|
16
|
+
}
|
|
17
|
+
}))
|
|
18
|
+
|
|
19
|
+
let agentIdentifier
|
|
20
|
+
|
|
21
|
+
beforeEach(() => {
|
|
22
|
+
agentIdentifier = faker.datatype.uuid()
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
afterEach(() => {
|
|
26
|
+
Object.keys(activatedFeatures)
|
|
27
|
+
.forEach(key => delete activatedFeatures[key])
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
test.each([
|
|
31
|
+
null,
|
|
32
|
+
undefined
|
|
33
|
+
])('should not do anything when flags is %s', (input) => {
|
|
34
|
+
activateFeatures(input, agentIdentifier)
|
|
35
|
+
|
|
36
|
+
expect(handleModule.handle).not.toHaveBeenCalled()
|
|
37
|
+
expect(drainModule.drain).not.toHaveBeenCalled()
|
|
38
|
+
expect(activatedFeatures).toEqual({})
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
const bucketMap = {
|
|
42
|
+
stn: [FEATURE_NAMES.sessionTrace],
|
|
43
|
+
err: [FEATURE_NAMES.jserrors, FEATURE_NAMES.metrics],
|
|
44
|
+
ins: [FEATURE_NAMES.pageAction],
|
|
45
|
+
spa: [FEATURE_NAMES.spa],
|
|
46
|
+
sr: [FEATURE_NAMES.sessionReplay, FEATURE_NAMES.sessionTrace]
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
test('emits the right events when feature flag = 1', () => {
|
|
50
|
+
const flags = {}
|
|
51
|
+
Object.keys(bucketMap).forEach(flag => flags[flag] = 1)
|
|
52
|
+
activateFeatures(flags, agentIdentifier)
|
|
53
|
+
|
|
54
|
+
const sharedEE = jest.mocked(eventEmitterModule.ee.get).mock.results[0].value
|
|
55
|
+
|
|
56
|
+
// each flag gets emitted to each of its mapped features, and a feat- AND a rumresp- for every emit, so (1+2+1+1+2)*2 = 14
|
|
57
|
+
expect(handleModule.handle).toHaveBeenCalledTimes(14)
|
|
58
|
+
expect(handleModule.handle).toHaveBeenNthCalledWith(1, 'feat-stn', [], undefined, FEATURE_NAMES.sessionTrace, sharedEE)
|
|
59
|
+
expect(handleModule.handle).toHaveBeenLastCalledWith('rumresp-sr', [true], undefined, FEATURE_NAMES.sessionTrace, sharedEE)
|
|
60
|
+
expect(drainModule.drain).toHaveBeenCalledWith(agentIdentifier, 'page_view_event')
|
|
61
|
+
|
|
62
|
+
Object.keys(flags).forEach(flag => flags[flag] = true)
|
|
63
|
+
expect(activatedFeatures).toEqual(flags)
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
test('emits the right events when feature flag = 0', () => {
|
|
67
|
+
const flags = {}
|
|
68
|
+
Object.keys(bucketMap).forEach(flag => flags[flag] = 0)
|
|
69
|
+
activateFeatures(flags, agentIdentifier)
|
|
70
|
+
|
|
71
|
+
const sharedEE = jest.mocked(eventEmitterModule.ee.get).mock.results[0].value
|
|
72
|
+
|
|
73
|
+
// each flag gets emitted to each of its mapped features, and a block- AND a rumresp- for every emit, so (1+2+1+1+2)*2 = 14
|
|
74
|
+
expect(handleModule.handle).toHaveBeenCalledTimes(14)
|
|
75
|
+
expect(handleModule.handle).toHaveBeenNthCalledWith(1, 'block-stn', [], undefined, FEATURE_NAMES.sessionTrace, sharedEE)
|
|
76
|
+
expect(handleModule.handle).toHaveBeenLastCalledWith('rumresp-sr', [false], undefined, FEATURE_NAMES.sessionTrace, sharedEE)
|
|
77
|
+
expect(drainModule.drain).toHaveBeenCalledWith(agentIdentifier, 'page_view_event')
|
|
78
|
+
|
|
79
|
+
Object.keys(flags).forEach(flag => flags[flag] = false)
|
|
80
|
+
expect(activatedFeatures).toEqual(flags)
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
test('only the first activate of the same feature is respected', () => {
|
|
84
|
+
const flags = { stn: 1 }
|
|
85
|
+
activateFeatures(flags, agentIdentifier)
|
|
86
|
+
flags.stn = 0
|
|
87
|
+
activateFeatures(flags, agentIdentifier)
|
|
88
|
+
|
|
89
|
+
const sharedEE1 = jest.mocked(eventEmitterModule.ee.get).mock.results[0].value
|
|
90
|
+
const sharedEE2 = jest.mocked(eventEmitterModule.ee.get).mock.results[1].value
|
|
91
|
+
|
|
92
|
+
expect(handleModule.handle).toHaveBeenNthCalledWith(1, 'feat-stn', [], undefined, 'session_trace', sharedEE1)
|
|
93
|
+
expect(handleModule.handle).toHaveBeenNthCalledWith(2, 'rumresp-stn', [true], undefined, 'session_trace', sharedEE1)
|
|
94
|
+
expect(handleModule.handle).not.toHaveBeenNthCalledWith(1, 'feat-stn', [], undefined, 'session_trace', sharedEE2)
|
|
95
|
+
expect(drainModule.drain).toHaveBeenCalledWith(agentIdentifier, 'page_view_event')
|
|
96
|
+
expect(drainModule.drain).toHaveBeenCalledTimes(2)
|
|
97
|
+
expect(activatedFeatures.stn).toBeTruthy()
|
|
98
|
+
})
|
|
@@ -21,29 +21,3 @@ export let globalScope = (() => {
|
|
|
21
21
|
})()
|
|
22
22
|
|
|
23
23
|
export const initialLocation = '' + globalScope.location
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* The below methods are only used for testing and should be removed once the
|
|
27
|
-
* reliant tests are moved to Jest.
|
|
28
|
-
* tests/browser/protocol.browser.js
|
|
29
|
-
* tests/browser/obfuscate.browser.js
|
|
30
|
-
*/
|
|
31
|
-
export function setScope (obj) {
|
|
32
|
-
globalScope = { ...obj }
|
|
33
|
-
}
|
|
34
|
-
export function resetScope () {
|
|
35
|
-
if (isBrowserScope) {
|
|
36
|
-
globalScope = window
|
|
37
|
-
} else if (isWorkerScope) {
|
|
38
|
-
if (typeof globalThis !== 'undefined' && globalThis instanceof WorkerGlobalScope) {
|
|
39
|
-
globalScope = globalThis
|
|
40
|
-
} else if (self instanceof WorkerGlobalScope) {
|
|
41
|
-
globalScope = self
|
|
42
|
-
}
|
|
43
|
-
} else {
|
|
44
|
-
throw new Error('New Relic browser agent shutting down due to error: Unable to locate global scope. This is possibly due to code redefining browser global variables like "self" and "window".')
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
export function getGlobalScope () {
|
|
48
|
-
return globalScope
|
|
49
|
-
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/*
|
|
2
|
+
The global-scope module contains exports that are defined once at the time
|
|
3
|
+
of importing the module. For this reason, the module must be dynamically
|
|
4
|
+
imported in each test case.
|
|
5
|
+
|
|
6
|
+
A scope must always exist or importing the module will throw an error. Use
|
|
7
|
+
`enableWorkerScope` to enable the worker scope. Be sure to call `disableWorkerScope`
|
|
8
|
+
before any calls to `expect` or the test will fail with an error from Jest.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { faker } from '@faker-js/faker'
|
|
12
|
+
|
|
13
|
+
afterEach(() => {
|
|
14
|
+
jest.restoreAllMocks()
|
|
15
|
+
jest.resetModules()
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
test('should indicate a browser scope', async () => {
|
|
19
|
+
jest.spyOn(global, 'window', 'get').mockReturnValue({ document: {} })
|
|
20
|
+
|
|
21
|
+
const globalScopeModule = await import('./global-scope')
|
|
22
|
+
|
|
23
|
+
expect(globalScopeModule.isBrowserScope).toBe(true)
|
|
24
|
+
expect(globalScopeModule.isWorkerScope).toBe(false)
|
|
25
|
+
expect(globalScopeModule.globalScope).toBe(global.window)
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
test('should indicate a worker scope', async () => {
|
|
29
|
+
enableWorkerScope()
|
|
30
|
+
const globalScopeModule = await import('./global-scope')
|
|
31
|
+
const mockedGlobalThis = global.globalThis
|
|
32
|
+
disableWorkerScope()
|
|
33
|
+
|
|
34
|
+
expect(globalScopeModule.isBrowserScope).toBe(false)
|
|
35
|
+
expect(globalScopeModule.isWorkerScope).toBe(true)
|
|
36
|
+
expect(globalScopeModule.globalScope).toBe(mockedGlobalThis)
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
test('should return the self global', async () => {
|
|
40
|
+
enableWorkerScope()
|
|
41
|
+
jest.replaceProperty(global, 'globalThis', null)
|
|
42
|
+
jest.spyOn(global, 'self', 'get').mockReturnValue(new global.WorkerGlobalScope())
|
|
43
|
+
|
|
44
|
+
const globalScopeModule = await import('./global-scope')
|
|
45
|
+
const mockedGlobalSelf = global.self
|
|
46
|
+
disableWorkerScope()
|
|
47
|
+
|
|
48
|
+
expect(globalScopeModule.isBrowserScope).toBe(false)
|
|
49
|
+
expect(globalScopeModule.isWorkerScope).toBe(true)
|
|
50
|
+
expect(globalScopeModule.globalScope).toBe(mockedGlobalSelf)
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
test('should throw an error when a scope cannot be defined', async () => {
|
|
54
|
+
jest.spyOn(global, 'window', 'get').mockReturnValue(undefined)
|
|
55
|
+
|
|
56
|
+
await expect(() => import('./global-scope')).rejects.toThrow()
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
test('should immediately store the current location', async () => {
|
|
60
|
+
const url = faker.internet.url()
|
|
61
|
+
jest.spyOn(window, 'location', 'get').mockReturnValue(url)
|
|
62
|
+
|
|
63
|
+
const globalScopeModule = await import('./global-scope')
|
|
64
|
+
|
|
65
|
+
expect(globalScopeModule.initialLocation).toBe(url)
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
function enableWorkerScope () {
|
|
69
|
+
jest.spyOn(global, 'window', 'get').mockReturnValue(undefined)
|
|
70
|
+
|
|
71
|
+
class WorkerNavigator {}
|
|
72
|
+
class WorkerGlobalScope {
|
|
73
|
+
navigator = new WorkerNavigator()
|
|
74
|
+
}
|
|
75
|
+
global.WorkerGlobalScope = WorkerGlobalScope
|
|
76
|
+
global.WorkerNavigator = WorkerNavigator
|
|
77
|
+
|
|
78
|
+
jest.spyOn(global, 'navigator', 'get').mockReturnValue(new global.WorkerNavigator())
|
|
79
|
+
jest.replaceProperty(global, 'globalThis', new WorkerGlobalScope())
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function disableWorkerScope () {
|
|
83
|
+
delete global.WorkerGlobalScope
|
|
84
|
+
delete global.WorkerNavigator
|
|
85
|
+
|
|
86
|
+
jest.restoreAllMocks()
|
|
87
|
+
}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { faker } from '@faker-js/faker'
|
|
2
|
+
import * as configModule from '../config/config'
|
|
3
|
+
import * as urlProtocolModule from '../url/protocol'
|
|
4
|
+
import * as consolModule from './console'
|
|
5
|
+
import * as obfuscateModule from './obfuscate'
|
|
6
|
+
|
|
7
|
+
jest.mock('../config/config')
|
|
8
|
+
jest.mock('../context/shared-context')
|
|
9
|
+
jest.mock('../url/protocol')
|
|
10
|
+
jest.mock('./console')
|
|
11
|
+
|
|
12
|
+
let agentIdentifier
|
|
13
|
+
const rules = [{
|
|
14
|
+
regex: /pii/g,
|
|
15
|
+
replacement: 'OBFUSCATED'
|
|
16
|
+
}]
|
|
17
|
+
|
|
18
|
+
beforeEach(() => {
|
|
19
|
+
agentIdentifier = faker.datatype.uuid()
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
afterEach(() => {
|
|
23
|
+
jest.resetAllMocks()
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
describe('Obfuscator', () => {
|
|
27
|
+
test('shouldObfuscate returns true when there are rules', () => {
|
|
28
|
+
jest.spyOn(configModule, 'getConfigurationValue').mockReturnValue(rules)
|
|
29
|
+
|
|
30
|
+
const obfuscator = new obfuscateModule.Obfuscator()
|
|
31
|
+
obfuscator.sharedContext = { agentIdentifier }
|
|
32
|
+
|
|
33
|
+
expect(obfuscator.shouldObfuscate()).toEqual(true)
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
test('shouldObfuscate returns false when there are no rules', () => {
|
|
37
|
+
jest.spyOn(configModule, 'getConfigurationValue').mockReturnValue([])
|
|
38
|
+
|
|
39
|
+
const obfuscator = new obfuscateModule.Obfuscator()
|
|
40
|
+
obfuscator.sharedContext = { agentIdentifier }
|
|
41
|
+
|
|
42
|
+
expect(obfuscator.shouldObfuscate()).toEqual(false)
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
test('obfuscateString returns the input when there are no rules', () => {
|
|
46
|
+
jest.spyOn(configModule, 'getConfigurationValue').mockReturnValue([])
|
|
47
|
+
|
|
48
|
+
const input = faker.lorem.sentence()
|
|
49
|
+
const obfuscator = new obfuscateModule.Obfuscator()
|
|
50
|
+
obfuscator.sharedContext = { agentIdentifier }
|
|
51
|
+
|
|
52
|
+
expect(obfuscator.obfuscateString(input)).toEqual(input)
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
test('obfuscateString applies obfuscation rules to input', () => {
|
|
56
|
+
jest.spyOn(configModule, 'getConfigurationValue').mockReturnValue(rules)
|
|
57
|
+
|
|
58
|
+
const input = 'pii'
|
|
59
|
+
const obfuscator = new obfuscateModule.Obfuscator()
|
|
60
|
+
obfuscator.sharedContext = { agentIdentifier }
|
|
61
|
+
|
|
62
|
+
expect(obfuscator.obfuscateString(input)).toEqual(rules[0].replacement)
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
test('obfuscateString replaces input with * when replacement is not set', () => {
|
|
66
|
+
const newRules = [{
|
|
67
|
+
regex: rules[0].regex
|
|
68
|
+
}]
|
|
69
|
+
|
|
70
|
+
jest.spyOn(configModule, 'getConfigurationValue').mockReturnValue(newRules)
|
|
71
|
+
|
|
72
|
+
const input = 'pii'
|
|
73
|
+
const obfuscator = new obfuscateModule.Obfuscator()
|
|
74
|
+
obfuscator.sharedContext = { agentIdentifier }
|
|
75
|
+
|
|
76
|
+
expect(obfuscator.obfuscateString(input)).toEqual('*')
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
test.each([
|
|
80
|
+
null,
|
|
81
|
+
undefined,
|
|
82
|
+
'',
|
|
83
|
+
123
|
|
84
|
+
])('obfuscateString returns the input it is %s', (input) => {
|
|
85
|
+
jest.spyOn(configModule, 'getConfigurationValue').mockReturnValue(rules)
|
|
86
|
+
|
|
87
|
+
const obfuscator = new obfuscateModule.Obfuscator()
|
|
88
|
+
obfuscator.sharedContext = { agentIdentifier }
|
|
89
|
+
|
|
90
|
+
expect(obfuscator.obfuscateString(input)).toEqual(input)
|
|
91
|
+
})
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
describe('getRules', () => {
|
|
95
|
+
test('should return configured rules', () => {
|
|
96
|
+
jest.spyOn(configModule, 'getConfigurationValue').mockReturnValue(rules)
|
|
97
|
+
|
|
98
|
+
expect(obfuscateModule.getRules()).toEqual(rules)
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
test('should include the file protocol obfuscation', () => {
|
|
102
|
+
jest.spyOn(configModule, 'getConfigurationValue').mockReturnValue(rules)
|
|
103
|
+
jest.spyOn(urlProtocolModule, 'isFileProtocol').mockReturnValue(rules)
|
|
104
|
+
|
|
105
|
+
expect(obfuscateModule.getRules()).toEqual(expect.arrayContaining([{
|
|
106
|
+
regex: /^file:\/\/(.*)/,
|
|
107
|
+
replacement: 'file://OBFUSCATED'
|
|
108
|
+
}]))
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
test.each([
|
|
112
|
+
null,
|
|
113
|
+
undefined
|
|
114
|
+
])('should return an empty array when obfuscation rules are %s', (input) => {
|
|
115
|
+
jest.spyOn(configModule, 'getConfigurationValue').mockReturnValue(input)
|
|
116
|
+
|
|
117
|
+
expect(obfuscateModule.getRules()).toEqual([])
|
|
118
|
+
})
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
describe('validateRules', () => {
|
|
122
|
+
test('should return true for empty array', () => {
|
|
123
|
+
expect(obfuscateModule.validateRules([])).toEqual(true)
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
test('should return true for valid rules', () => {
|
|
127
|
+
expect(obfuscateModule.validateRules(rules)).toEqual(true)
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
test.each([
|
|
131
|
+
null,
|
|
132
|
+
123,
|
|
133
|
+
{},
|
|
134
|
+
[]
|
|
135
|
+
])('should warn about an invalid regex type %s', (input) => {
|
|
136
|
+
const newRules = [{
|
|
137
|
+
regex: input,
|
|
138
|
+
replacement: rules[0].replacement
|
|
139
|
+
}]
|
|
140
|
+
|
|
141
|
+
expect(obfuscateModule.validateRules(newRules)).toEqual(false)
|
|
142
|
+
expect(consolModule.warn).toHaveBeenCalledWith(expect.stringContaining(
|
|
143
|
+
'contains a "regex" value with an invalid type'
|
|
144
|
+
))
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
test('should warn about a missing regex with value', () => {
|
|
148
|
+
const newRules = [{
|
|
149
|
+
replacement: rules[0].replacement
|
|
150
|
+
}]
|
|
151
|
+
|
|
152
|
+
expect(obfuscateModule.validateRules(newRules)).toEqual(false)
|
|
153
|
+
expect(consolModule.warn).toHaveBeenCalledWith(expect.stringContaining(
|
|
154
|
+
'missing a "regex" value'
|
|
155
|
+
))
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
test.each([
|
|
159
|
+
123,
|
|
160
|
+
{},
|
|
161
|
+
[]
|
|
162
|
+
])('should warn about an invalid replacement type %s', (input) => {
|
|
163
|
+
const newRules = [{
|
|
164
|
+
regex: rules[0].regex,
|
|
165
|
+
replacement: input
|
|
166
|
+
}]
|
|
167
|
+
|
|
168
|
+
expect(obfuscateModule.validateRules(newRules)).toEqual(false)
|
|
169
|
+
expect(consolModule.warn).toHaveBeenCalledWith(expect.stringContaining(
|
|
170
|
+
'contains a "replacement" value with an invalid type'
|
|
171
|
+
))
|
|
172
|
+
})
|
|
173
|
+
})
|
|
@@ -5,7 +5,7 @@ import { warn } from './console'
|
|
|
5
5
|
|
|
6
6
|
var fileProtocolRule = {
|
|
7
7
|
regex: /^file:\/\/(.*)/,
|
|
8
|
-
replacement: '
|
|
8
|
+
replacement: atob('ZmlsZTovL09CRlVTQ0FURUQ=')
|
|
9
9
|
}
|
|
10
10
|
export class Obfuscator extends SharedContext {
|
|
11
11
|
shouldObfuscate () {
|
|
@@ -51,7 +51,7 @@ export function validateRules (rules) {
|
|
|
51
51
|
if (!('regex' in rules[i])) {
|
|
52
52
|
warn('An obfuscation replacement rule was detected missing a "regex" value.')
|
|
53
53
|
invalidRegexDetected = true
|
|
54
|
-
} else if (typeof rules[i].regex !== 'string' && !(rules[i].regex
|
|
54
|
+
} else if (typeof rules[i].regex !== 'string' && !(rules[i].regex instanceof RegExp)) {
|
|
55
55
|
warn('An obfuscation replacement rule contains a "regex" value with an invalid type (must be a string or RegExp)')
|
|
56
56
|
invalidRegexDetected = true
|
|
57
57
|
}
|
|
@@ -97,10 +97,10 @@ submitData.img = function img ({ url }) {
|
|
|
97
97
|
* @returns {boolean}
|
|
98
98
|
*/
|
|
99
99
|
submitData.beacon = function ({ url, body }) {
|
|
100
|
-
// Navigator has to be bound to ensure it does not error in some browsers
|
|
101
|
-
// https://xgwang.me/posts/you-may-not-know-beacon/#it-may-throw-error%2C-be-sure-to-catch
|
|
102
|
-
const send = window.navigator.sendBeacon.bind(window.navigator)
|
|
103
100
|
try {
|
|
101
|
+
// Navigator has to be bound to ensure it does not error in some browsers
|
|
102
|
+
// https://xgwang.me/posts/you-may-not-know-beacon/#it-may-throw-error%2C-be-sure-to-catch
|
|
103
|
+
const send = window.navigator.sendBeacon.bind(window.navigator)
|
|
104
104
|
return send(url, body)
|
|
105
105
|
} catch (err) {
|
|
106
106
|
// if sendBeacon still trys to throw an illegal invocation error,
|