@newrelic/browser-agent 1.233.1 → 1.234.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/dist/cjs/common/constants/env.cdn.js +1 -1
- package/dist/cjs/common/constants/env.npm.js +1 -1
- package/dist/cjs/common/event-emitter/contextual-ee.test.js +10 -10
- package/dist/cjs/common/harvest/harvest-scheduler.test.js +2 -2
- package/dist/cjs/common/harvest/harvest.js +4 -11
- package/dist/cjs/common/harvest/harvest.test.js +224 -0
- package/dist/cjs/common/url/encode.js +2 -2
- package/dist/cjs/common/util/console.test.js +30 -0
- package/dist/cjs/common/util/get-or-set.js +8 -1
- package/dist/cjs/common/util/get-or-set.test.js +47 -0
- package/dist/cjs/common/util/stringify.test.js +48 -0
- package/dist/cjs/common/util/submit-data.js +15 -15
- package/dist/cjs/common/util/submit-data.test.js +221 -0
- package/dist/cjs/common/util/traverse.js +19 -27
- package/dist/cjs/common/util/traverse.test.js +44 -0
- package/dist/cjs/features/metrics/aggregate/endpoint-map.js +14 -0
- package/dist/cjs/features/metrics/aggregate/index.js +3 -2
- package/dist/cjs/features/metrics/instrument/index.js +0 -2
- package/dist/cjs/features/page_view_event/aggregate/index.js +58 -44
- package/dist/cjs/loaders/configure/configure.js +0 -1
- package/dist/esm/common/constants/env.cdn.js +1 -1
- package/dist/esm/common/constants/env.npm.js +1 -1
- package/dist/esm/common/event-emitter/contextual-ee.test.js +10 -10
- package/dist/esm/common/harvest/harvest-scheduler.test.js +2 -2
- package/dist/esm/common/harvest/harvest.js +4 -11
- package/dist/esm/common/harvest/harvest.test.js +222 -0
- package/dist/esm/common/url/encode.js +2 -2
- package/dist/esm/common/util/console.test.js +28 -0
- package/dist/esm/common/util/get-or-set.js +8 -1
- package/dist/esm/common/util/get-or-set.test.js +45 -0
- package/dist/esm/common/util/stringify.test.js +46 -0
- package/dist/esm/common/util/submit-data.js +15 -15
- package/dist/esm/common/util/submit-data.test.js +219 -0
- package/dist/esm/common/util/traverse.js +19 -27
- package/dist/esm/common/util/traverse.test.js +42 -0
- package/dist/esm/features/metrics/aggregate/endpoint-map.js +7 -0
- package/dist/esm/features/metrics/aggregate/index.js +3 -2
- package/dist/esm/features/metrics/instrument/index.js +0 -2
- package/dist/esm/features/page_view_event/aggregate/index.js +58 -44
- package/dist/esm/loaders/configure/configure.js +0 -1
- package/dist/types/common/config/state/init.d.ts.map +1 -1
- package/dist/types/common/context/shared-context.d.ts.map +1 -1
- package/dist/types/common/harvest/harvest.d.ts.map +1 -1
- package/dist/types/common/timer/timer.d.ts.map +1 -1
- package/dist/types/common/util/get-or-set.d.ts +9 -1
- package/dist/types/common/util/get-or-set.d.ts.map +1 -1
- package/dist/types/common/util/submit-data.d.ts +14 -10
- package/dist/types/common/util/submit-data.d.ts.map +1 -1
- package/dist/types/common/util/traverse.d.ts +10 -1
- package/dist/types/common/util/traverse.d.ts.map +1 -1
- package/dist/types/common/window/nreum.d.ts.map +1 -1
- package/dist/types/features/metrics/aggregate/endpoint-map.d.ts +8 -0
- package/dist/types/features/metrics/aggregate/endpoint-map.d.ts.map +1 -0
- package/dist/types/features/metrics/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/metrics/aggregate/polyfill-detection.es5.d.ts.map +1 -1
- package/dist/types/features/metrics/instrument/index.d.ts.map +1 -1
- package/dist/types/features/page_view_event/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/page_view_event/instrument/index.d.ts.map +1 -1
- package/dist/types/features/utils/handler-cache.d.ts.map +1 -1
- package/dist/types/loaders/configure/configure.d.ts.map +1 -1
- package/package.json +6 -2
- package/src/common/config/state/init.js +0 -1
- package/src/common/context/shared-context.js +0 -1
- package/src/common/event-emitter/contextual-ee.test.js +10 -10
- package/src/common/harvest/harvest-scheduler.test.js +2 -2
- package/src/common/harvest/harvest.js +5 -9
- package/src/common/harvest/harvest.test.js +169 -0
- package/src/common/session/session-entity.test.js +0 -1
- package/src/common/timer/timer.js +0 -1
- package/src/common/url/encode.js +2 -2
- package/src/common/util/console.test.js +34 -0
- package/src/common/util/get-or-set.js +8 -1
- package/src/common/util/get-or-set.test.js +58 -0
- package/src/common/util/stringify.test.js +49 -0
- package/src/common/util/submit-data.js +15 -16
- package/src/common/util/submit-data.test.js +218 -0
- package/src/common/util/traverse.js +18 -27
- package/src/common/util/traverse.test.js +50 -0
- package/src/common/window/nreum.js +0 -1
- package/src/features/metrics/aggregate/endpoint-map.js +7 -0
- package/src/features/metrics/aggregate/index.js +3 -2
- package/src/features/metrics/aggregate/polyfill-detection.es5.js +0 -1
- package/src/features/metrics/instrument/index.js +0 -2
- package/src/features/page_view_event/aggregate/index.js +48 -51
- package/src/features/page_view_event/instrument/index.js +0 -1
- package/src/features/utils/handler-cache.js +0 -1
- package/src/loaders/configure/configure.js +0 -1
- package/dist/cjs/common/util/s-hash.js +0 -19
- package/dist/cjs/features/metrics/instrument/workers-helper.js +0 -124
- package/dist/esm/common/util/s-hash.js +0 -13
- package/dist/esm/features/metrics/instrument/workers-helper.js +0 -119
- package/dist/types/common/util/s-hash.d.ts +0 -2
- package/dist/types/common/util/s-hash.d.ts.map +0 -1
- package/dist/types/features/metrics/instrument/workers-helper.d.ts +0 -7
- package/dist/types/features/metrics/instrument/workers-helper.d.ts.map +0 -1
- package/src/common/util/s-hash.js +0 -14
- package/src/features/metrics/instrument/workers-helper.js +0 -113
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @jest-environment jsdom
|
|
3
|
+
* @jest-environment-options {"html": "<html><head><script></script></head><body></body></html>", "url": "https://example.com/"}
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { submitData } from './submit-data'
|
|
7
|
+
|
|
8
|
+
const mockWorkerScope = jest.fn().mockImplementation(() => false)
|
|
9
|
+
jest.mock('./global-scope', () => ({
|
|
10
|
+
__esModule: true,
|
|
11
|
+
get isWorkerScope () {
|
|
12
|
+
return mockWorkerScope()
|
|
13
|
+
}
|
|
14
|
+
}))
|
|
15
|
+
|
|
16
|
+
const url = 'https://example.com/api'
|
|
17
|
+
|
|
18
|
+
beforeEach(() => {
|
|
19
|
+
jest.restoreAllMocks()
|
|
20
|
+
mockWorkerScope.mockReturnValue(false)
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
describe('submitData.jsonp', () => {
|
|
24
|
+
// This test requires a script tag to exist in the html set by this file's jest-environment-options header block.
|
|
25
|
+
test('should return an HTMLScriptElement when called from a web window environment', () => {
|
|
26
|
+
mockWorkerScope.mockReturnValue(false)
|
|
27
|
+
|
|
28
|
+
const jsonp = 'callback'
|
|
29
|
+
|
|
30
|
+
const result = submitData.jsonp({ url, jsonp })
|
|
31
|
+
|
|
32
|
+
expect(result).toBeInstanceOf(HTMLScriptElement)
|
|
33
|
+
expect(result.type).toBe('text/javascript')
|
|
34
|
+
expect(result.src).toBe(url + '&jsonp=' + jsonp)
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
test('should try to use importScripts when called from a worker scope', () => {
|
|
38
|
+
mockWorkerScope.mockReturnValueOnce(true)
|
|
39
|
+
|
|
40
|
+
const jsonp = 'callback'
|
|
41
|
+
|
|
42
|
+
global.importScripts = jest.fn()
|
|
43
|
+
|
|
44
|
+
submitData.jsonp({ url, jsonp })
|
|
45
|
+
|
|
46
|
+
expect(importScripts).toHaveBeenCalledWith(url + '&jsonp=' + jsonp)
|
|
47
|
+
|
|
48
|
+
delete global.importScripts
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
test('should fall back to an xhrGet call and return false when called from a worker scope', () => {
|
|
52
|
+
mockWorkerScope.mockReturnValueOnce(true)
|
|
53
|
+
|
|
54
|
+
const jsonp = 'callback'
|
|
55
|
+
|
|
56
|
+
jest.spyOn(submitData, 'xhrGet').mockImplementation(jest.fn())
|
|
57
|
+
|
|
58
|
+
const result = submitData.jsonp({ url, jsonp })
|
|
59
|
+
|
|
60
|
+
expect(result).toBe(false)
|
|
61
|
+
expect(submitData.xhrGet).toHaveBeenCalledTimes(1)
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
test('should not throw an error if any error occurs during execution', () => {
|
|
65
|
+
jest.spyOn(document, 'createElement').mockImplementation(() => { throw new Error('message') })
|
|
66
|
+
|
|
67
|
+
const jsonp = 'callback'
|
|
68
|
+
|
|
69
|
+
expect(() => {
|
|
70
|
+
submitData.jsonp({ url, jsonp })
|
|
71
|
+
}).not.toThrow()
|
|
72
|
+
})
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
describe('submitData.xhrGet', () => {
|
|
76
|
+
test('should return an XMLHttpRequest object', () => {
|
|
77
|
+
const result = submitData.xhrGet({ url })
|
|
78
|
+
expect(result).toBeInstanceOf(XMLHttpRequest)
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
test('should not throw an error if URL is not provided', () => {
|
|
82
|
+
expect(() => {
|
|
83
|
+
submitData.xhrGet({})
|
|
84
|
+
}).not.toThrow()
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
test('should not throw an error if an invalid URL is provided', () => {
|
|
88
|
+
expect(() => {
|
|
89
|
+
submitData.xhrGet({ url: 'invalid url' })
|
|
90
|
+
}).not.toThrow()
|
|
91
|
+
})
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
describe('submitData.xhr', () => {
|
|
95
|
+
test('should return an XMLHttpRequest object', () => {
|
|
96
|
+
const result = submitData.xhrGet({ url })
|
|
97
|
+
expect(result).toBeInstanceOf(XMLHttpRequest)
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
test('should not throw an error if URL is not provided', () => {
|
|
101
|
+
expect(() => {
|
|
102
|
+
submitData.xhr({})
|
|
103
|
+
}).not.toThrow()
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
test('should not throw an error if an invalid URL is provided', () => {
|
|
107
|
+
expect(() => {
|
|
108
|
+
submitData.xhr({ url: 'invalid url' })
|
|
109
|
+
}).not.toThrow()
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
test('should send a POST request by default', () => {
|
|
113
|
+
jest.spyOn(XMLHttpRequest.prototype, 'open')
|
|
114
|
+
|
|
115
|
+
submitData.xhr({ url })
|
|
116
|
+
|
|
117
|
+
expect(XMLHttpRequest.prototype.open).toHaveBeenCalledWith('POST', url, true)
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
test('should send a GET request if specified', () => {
|
|
121
|
+
jest.spyOn(XMLHttpRequest.prototype, 'open')
|
|
122
|
+
|
|
123
|
+
submitData.xhr({ url, method: 'GET' })
|
|
124
|
+
|
|
125
|
+
expect(XMLHttpRequest.prototype.open).toHaveBeenCalledWith('GET', url, true)
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
// This test requires a same-origin url to be set by this file's jest-environment-options header block.
|
|
129
|
+
test('should send a request synchronously if specified', () => {
|
|
130
|
+
jest.spyOn(XMLHttpRequest.prototype, 'open')
|
|
131
|
+
|
|
132
|
+
submitData.xhr({ url, sync: true })
|
|
133
|
+
|
|
134
|
+
expect(XMLHttpRequest.prototype.open).toHaveBeenCalledWith('POST', url, false)
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
test('should set custom headers if provided', () => {
|
|
138
|
+
const headers = [{ key: 'Content-Type', value: 'application/json' }]
|
|
139
|
+
|
|
140
|
+
jest.spyOn(XMLHttpRequest.prototype, 'setRequestHeader')
|
|
141
|
+
|
|
142
|
+
submitData.xhr({ url, headers })
|
|
143
|
+
|
|
144
|
+
headers.forEach((header) => {
|
|
145
|
+
expect(XMLHttpRequest.prototype.setRequestHeader).toHaveBeenCalledWith(header.key, header.value)
|
|
146
|
+
})
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
test('should send a request with the specified body', () => {
|
|
150
|
+
const body = JSON.stringify({ key: 'value' })
|
|
151
|
+
|
|
152
|
+
jest.spyOn(XMLHttpRequest.prototype, 'send')
|
|
153
|
+
|
|
154
|
+
submitData.xhr({ url, body })
|
|
155
|
+
|
|
156
|
+
expect(XMLHttpRequest.prototype.send).toHaveBeenCalledWith(body)
|
|
157
|
+
})
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
describe('submitData.img', () => {
|
|
161
|
+
test('should return an HTMLImageElement', () => {
|
|
162
|
+
const imageUrl = 'https://example.com/image.png'
|
|
163
|
+
|
|
164
|
+
const result = submitData.img({ url: imageUrl })
|
|
165
|
+
|
|
166
|
+
expect(result).toBeInstanceOf(HTMLImageElement)
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
test('should not throw an error if URL is not provided', () => {
|
|
170
|
+
expect(() => {
|
|
171
|
+
submitData.img({})
|
|
172
|
+
}).not.toThrow()
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
test('should set the src attribute of the image element to the provided URL', () => {
|
|
176
|
+
const imageUrl = 'https://example.com/image.png'
|
|
177
|
+
|
|
178
|
+
const result = submitData.img({ url: imageUrl })
|
|
179
|
+
|
|
180
|
+
expect(result.src).toBe(imageUrl)
|
|
181
|
+
})
|
|
182
|
+
})
|
|
183
|
+
|
|
184
|
+
describe('submitData.beacon', () => {
|
|
185
|
+
test('should return true when beacon request succeeds', () => {
|
|
186
|
+
const body = JSON.stringify({ key: 'value' })
|
|
187
|
+
|
|
188
|
+
window.navigator.sendBeacon = {
|
|
189
|
+
bind: jest.fn(() => () => true)
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const result = submitData.beacon({ url, body })
|
|
193
|
+
|
|
194
|
+
expect(result).toBe(true)
|
|
195
|
+
})
|
|
196
|
+
|
|
197
|
+
test('should return false when beacon request fails', () => {
|
|
198
|
+
const body = JSON.stringify({ key: 'value' })
|
|
199
|
+
|
|
200
|
+
window.navigator.sendBeacon = {
|
|
201
|
+
bind: jest.fn(() => () => { throw new Error('message') })
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const result = submitData.beacon({ url, body })
|
|
205
|
+
|
|
206
|
+
expect(result).toBe(false)
|
|
207
|
+
})
|
|
208
|
+
|
|
209
|
+
test('should error if sendBeacon is not supported', () => {
|
|
210
|
+
const body = JSON.stringify({ key: 'value' })
|
|
211
|
+
|
|
212
|
+
window.navigator.sendBeacon = undefined
|
|
213
|
+
|
|
214
|
+
expect(() => {
|
|
215
|
+
submitData.beacon({ url, body })
|
|
216
|
+
}).toThrow()
|
|
217
|
+
})
|
|
218
|
+
})
|
|
@@ -3,34 +3,25 @@
|
|
|
3
3
|
* SPDX-License-Identifier: Apache-2.0
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
/**
|
|
7
|
+
* Applies a function to properties of a specified type in an object, recursively.
|
|
8
|
+
*
|
|
9
|
+
* @param {Object} obj - The object to apply the function to.
|
|
10
|
+
* @param {Function} fn - The function to apply to matching properties.
|
|
11
|
+
* @param {string} [type='string'] - The type of properties to apply the function to.
|
|
12
|
+
* @param {Array<string>} [ignoreKeys=[]] - The keys of properties to ignore and not modify.
|
|
13
|
+
* @returns {Object} - The object with function recursively applied.
|
|
14
|
+
*/
|
|
15
|
+
export function applyFnToProps (obj, fn, type = 'string', ignoreKeys = []) {
|
|
8
16
|
if (!obj || typeof obj !== 'object') return obj
|
|
9
|
-
type = type || 'string'
|
|
10
|
-
ignoreKeys = ignoreKeys || []
|
|
11
|
-
return traverse(obj)
|
|
12
|
-
function traverse (obj) {
|
|
13
|
-
for (var property in obj) {
|
|
14
|
-
// eslint-disable-next-line
|
|
15
|
-
if (obj.hasOwnProperty(property)) {
|
|
16
|
-
if (typeof obj[property] === 'object') {
|
|
17
|
-
traverse(obj[property])
|
|
18
|
-
} else {
|
|
19
|
-
if (typeof obj[property] === type && !shouldIgnore(property)) obj[property] = fn(obj[property])
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
return obj
|
|
24
|
-
}
|
|
25
17
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
break
|
|
32
|
-
}
|
|
18
|
+
Object.keys(obj).forEach(property => {
|
|
19
|
+
if (typeof obj[property] === 'object') {
|
|
20
|
+
applyFnToProps(obj[property], fn, type, ignoreKeys)
|
|
21
|
+
} else {
|
|
22
|
+
if (typeof obj[property] === type && !ignoreKeys.includes(property)) obj[property] = fn(obj[property])
|
|
33
23
|
}
|
|
34
|
-
|
|
35
|
-
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
return obj
|
|
36
27
|
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { applyFnToProps } from './traverse'
|
|
2
|
+
|
|
3
|
+
test.each(['not an object', null, undefined])('should return input unchanged when input is %p', (input) => {
|
|
4
|
+
const result = applyFnToProps(input, jest.fn())
|
|
5
|
+
expect(result).toEqual(input)
|
|
6
|
+
})
|
|
7
|
+
|
|
8
|
+
test('should apply the provided function only to properties of the specified type', () => {
|
|
9
|
+
const obj = {
|
|
10
|
+
stringProp: 'Hello',
|
|
11
|
+
numberProp: 42,
|
|
12
|
+
arrayProp: [1, 2, 3],
|
|
13
|
+
objectProp: { foo: 'bar' }
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const fn = jest.fn((value) => value.toUpperCase())
|
|
17
|
+
|
|
18
|
+
const result = applyFnToProps(obj, fn, 'string')
|
|
19
|
+
|
|
20
|
+
expect(result.stringProp).toBe('HELLO')
|
|
21
|
+
expect(result.numberProp).toBe(42)
|
|
22
|
+
expect(result.arrayProp).toEqual([1, 2, 3])
|
|
23
|
+
expect(result.objectProp).toEqual({ foo: 'BAR' })
|
|
24
|
+
|
|
25
|
+
expect(fn).toHaveBeenCalledTimes(2)
|
|
26
|
+
expect(fn).toHaveBeenCalledWith('Hello')
|
|
27
|
+
expect(fn).toHaveBeenCalledWith('bar')
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
test('should ignore properties specified in ignoreKeys', () => {
|
|
31
|
+
const obj = {
|
|
32
|
+
a: 1,
|
|
33
|
+
b: 2,
|
|
34
|
+
c: 3
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const fn = jest.fn((value) => value + 1)
|
|
38
|
+
|
|
39
|
+
const ignoreKeys = ['c']
|
|
40
|
+
|
|
41
|
+
const result = applyFnToProps(obj, fn, 'number', ignoreKeys)
|
|
42
|
+
|
|
43
|
+
expect(result.a).toBe(2)
|
|
44
|
+
expect(result.b).toBe(3)
|
|
45
|
+
expect(result.c).toBe(3)
|
|
46
|
+
|
|
47
|
+
expect(fn).toHaveBeenCalledTimes(2)
|
|
48
|
+
expect(fn).toHaveBeenCalledWith(1)
|
|
49
|
+
expect(fn).toHaveBeenCalledWith(2)
|
|
50
|
+
})
|
|
@@ -13,6 +13,7 @@ import { windowAddEventListener } from '../../../common/event-listener/event-lis
|
|
|
13
13
|
import { isBrowserScope } from '../../../common/util/global-scope'
|
|
14
14
|
import { AggregateBase } from '../../utils/aggregate-base'
|
|
15
15
|
import { stringify } from '../../../common/util/stringify'
|
|
16
|
+
import { endpointMap } from './endpoint-map'
|
|
16
17
|
|
|
17
18
|
export class Aggregate extends AggregateBase {
|
|
18
19
|
static featureName = FEATURE_NAME
|
|
@@ -125,7 +126,7 @@ export class Aggregate extends AggregateBase {
|
|
|
125
126
|
// Capture per-agent bytes sent for each endpoint (see harvest) and RUM call (see page_view_event aggregator).
|
|
126
127
|
Object.keys(agentRuntime.bytesSent).forEach(endpoint => {
|
|
127
128
|
this.storeSupportabilityMetrics(
|
|
128
|
-
`PageSession/Endpoint/${endpoint
|
|
129
|
+
`PageSession/Endpoint/${endpointMap[endpoint]}/BytesSent`,
|
|
129
130
|
agentRuntime.bytesSent[endpoint]
|
|
130
131
|
)
|
|
131
132
|
})
|
|
@@ -133,7 +134,7 @@ export class Aggregate extends AggregateBase {
|
|
|
133
134
|
// Capture per-agent query bytes sent for each endpoint (see harvest) and RUM call (see page_view_event aggregator).
|
|
134
135
|
Object.keys(agentRuntime.bytesSent).forEach(endpoint => {
|
|
135
136
|
this.storeSupportabilityMetrics(
|
|
136
|
-
`PageSession/Endpoint/${endpoint
|
|
137
|
+
`PageSession/Endpoint/${endpointMap[endpoint]}/QueryBytesSent`,
|
|
137
138
|
agentRuntime.queryBytesSent[endpoint]
|
|
138
139
|
)
|
|
139
140
|
})
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { InstrumentBase } from '../../utils/instrument-base'
|
|
2
|
-
import { insertSupportMetrics } from './workers-helper'
|
|
3
2
|
import { FEATURE_NAME, SUPPORTABILITY_METRIC_CHANNEL } from '../constants'
|
|
4
3
|
import { handle } from '../../../common/event-emitter/handle'
|
|
5
4
|
import { FEATURE_NAMES } from '../../../loaders/features/features'
|
|
@@ -8,7 +7,6 @@ export class Instrument extends InstrumentBase {
|
|
|
8
7
|
static featureName = FEATURE_NAME
|
|
9
8
|
constructor (agentIdentifier, aggregator, auto = true) {
|
|
10
9
|
super(agentIdentifier, aggregator, FEATURE_NAME, auto)
|
|
11
|
-
insertSupportMetrics(tag => handle(SUPPORTABILITY_METRIC_CHANNEL, [tag], undefined, FEATURE_NAMES.metrics, this.ee))
|
|
12
10
|
this.importAggregator()
|
|
13
11
|
}
|
|
14
12
|
}
|
|
@@ -2,22 +2,19 @@ import { handle } from '../../../common/event-emitter/handle'
|
|
|
2
2
|
import { FEATURE_NAMES } from '../../../loaders/features/features'
|
|
3
3
|
import { isiOS } from '../../../common/browser-version/ios-version'
|
|
4
4
|
import { onTTFB } from 'web-vitals'
|
|
5
|
-
import { mapOwn } from '../../../common/util/map-own'
|
|
6
|
-
import { param, fromArray } from '../../../common/url/encode'
|
|
7
5
|
import { addPT, addPN } from '../../../common/timing/nav-timing'
|
|
8
6
|
import { stringify } from '../../../common/util/stringify'
|
|
9
7
|
import { paintMetrics } from '../../../common/metrics/paint-metrics'
|
|
10
|
-
import { submitData } from '../../../common/util/submit-data'
|
|
11
8
|
import { getConfigurationValue, getInfo, getRuntime } from '../../../common/config/config'
|
|
12
|
-
import {
|
|
9
|
+
import { Harvest } from '../../../common/harvest/harvest'
|
|
13
10
|
import * as CONSTANTS from '../constants'
|
|
14
11
|
import { getActivatedFeaturesFlags } from './initialized-features'
|
|
15
12
|
import { globalScope, isBrowserScope } from '../../../common/util/global-scope'
|
|
16
13
|
import { drain } from '../../../common/drain/drain'
|
|
14
|
+
import { activateFeatures } from '../../../common/util/feature-flags'
|
|
15
|
+
import { warn } from '../../../common/util/console'
|
|
17
16
|
import { AggregateBase } from '../../utils/aggregate-base'
|
|
18
17
|
|
|
19
|
-
const jsonp = 'NREUM.setToken'
|
|
20
|
-
|
|
21
18
|
export class Aggregate extends AggregateBase {
|
|
22
19
|
static featureName = CONSTANTS.FEATURE_NAME
|
|
23
20
|
constructor (agentIdentifier, aggregator) {
|
|
@@ -55,10 +52,12 @@ export class Aggregate extends AggregateBase {
|
|
|
55
52
|
|
|
56
53
|
sendRum () {
|
|
57
54
|
const info = getInfo(this.agentIdentifier)
|
|
55
|
+
const agentRuntime = getRuntime(this.agentIdentifier)
|
|
56
|
+
const harvester = new Harvest(this)
|
|
57
|
+
|
|
58
58
|
if (!info.beacon) return
|
|
59
59
|
if (info.queueTime) this.aggregator.store('measures', 'qt', { value: info.queueTime })
|
|
60
60
|
if (info.applicationTime) this.aggregator.store('measures', 'ap', { value: info.applicationTime })
|
|
61
|
-
const agentRuntime = getRuntime(this.agentIdentifier)
|
|
62
61
|
|
|
63
62
|
// These 3 values should've been recorded after load and before this func runs. They are part of the minimum required for PageView events to be created.
|
|
64
63
|
// Following PR #428, which demands that all agents send RUM call, these need to be sent even outside of the main window context where PerformanceTiming
|
|
@@ -67,27 +66,27 @@ export class Aggregate extends AggregateBase {
|
|
|
67
66
|
this.aggregator.store('measures', 'fe', { value: isBrowserScope ? agentRuntime[CONSTANTS.FBTWL] : 0 })
|
|
68
67
|
this.aggregator.store('measures', 'dc', { value: isBrowserScope ? agentRuntime[CONSTANTS.FBTDC] : 0 })
|
|
69
68
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
69
|
+
const queryParameters = {
|
|
70
|
+
tt: info.ttGuid,
|
|
71
|
+
us: info.user,
|
|
72
|
+
ac: info.account,
|
|
73
|
+
pr: info.product,
|
|
74
|
+
af: getActivatedFeaturesFlags(this.agentIdentifier).join(','),
|
|
75
|
+
...(
|
|
76
|
+
Object.entries(this.aggregator.get('measures') || {}).reduce((aggregator, [metricName, measure]) => {
|
|
77
|
+
aggregator[metricName] = measure.params?.value
|
|
78
|
+
return aggregator
|
|
79
|
+
}, {})
|
|
80
|
+
),
|
|
81
|
+
xx: info.extra,
|
|
82
|
+
ua: info.userAttributes,
|
|
83
|
+
at: info.atts
|
|
84
|
+
}
|
|
85
85
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
chunksForQueryString.push(param('af', getActivatedFeaturesFlags(this.agentIdentifier).join(',')))
|
|
86
|
+
let body
|
|
87
|
+
if (typeof info.jsAttributes === 'object' && Object.keys(info.jsAttributes).length > 0) {
|
|
88
|
+
body = { ja: info.jsAttributes }
|
|
89
|
+
}
|
|
91
90
|
|
|
92
91
|
if (globalScope.performance) {
|
|
93
92
|
if (typeof PerformanceNavigationTiming !== 'undefined') { // Navigation Timing level 2 API that replaced PerformanceTiming & PerformanceNavigation
|
|
@@ -96,13 +95,13 @@ export class Aggregate extends AggregateBase {
|
|
|
96
95
|
timing: addPT(agentRuntime.offset, navTimingEntry, {}),
|
|
97
96
|
navigation: addPN(navTimingEntry, {})
|
|
98
97
|
})
|
|
99
|
-
|
|
98
|
+
queryParameters.perf = stringify(perf)
|
|
100
99
|
} else if (typeof PerformanceTiming !== 'undefined') { // Safari pre-15 did not support level 2 timing
|
|
101
100
|
const perf = ({
|
|
102
101
|
timing: addPT(agentRuntime.offset, globalScope.performance.timing, {}, true),
|
|
103
102
|
navigation: addPN(globalScope.performance.navigation, {})
|
|
104
103
|
})
|
|
105
|
-
|
|
104
|
+
queryParameters.perf = stringify(perf)
|
|
106
105
|
}
|
|
107
106
|
}
|
|
108
107
|
|
|
@@ -112,35 +111,33 @@ export class Aggregate extends AggregateBase {
|
|
|
112
111
|
if (!entry.startTime || entry.startTime <= 0) return
|
|
113
112
|
|
|
114
113
|
if (entry.name === 'first-paint') {
|
|
115
|
-
|
|
114
|
+
queryParameters.fp = String(Math.floor(entry.startTime))
|
|
116
115
|
} else if (entry.name === 'first-contentful-paint') {
|
|
117
|
-
|
|
116
|
+
queryParameters.fcp = String(Math.floor(entry.startTime))
|
|
118
117
|
}
|
|
119
118
|
paintMetrics[entry.name] = Math.floor(entry.startTime) // this is consumed by Spa module
|
|
120
119
|
})
|
|
121
120
|
} catch (e) {}
|
|
122
121
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
agentRuntime.bytesSent[protocol] = 0 // Set to zero for now until RUM is moved to POST
|
|
134
|
-
|
|
135
|
-
// Capture query bytes sent to RUM call endpoint (currently `1`) as a supportability metric. See metrics aggregator (on unload).
|
|
136
|
-
agentRuntime.queryBytesSent[protocol] = (agentRuntime.queryBytesSent[protocol] || 0) + queryString?.length || 0
|
|
122
|
+
harvester.send({
|
|
123
|
+
endpoint: 'rum',
|
|
124
|
+
payload: { qs: queryParameters, body },
|
|
125
|
+
opts: { needResponse: true, sendEmptyBody: true },
|
|
126
|
+
cbFinished: ({ status, responseText }) => {
|
|
127
|
+
if (status >= 400) {
|
|
128
|
+
// Adding retry logic for the rum call will be a separate change
|
|
129
|
+
this.ee.abort()
|
|
130
|
+
return
|
|
131
|
+
}
|
|
137
132
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
133
|
+
try {
|
|
134
|
+
activateFeatures(JSON.parse(responseText), this.agentIdentifier)
|
|
135
|
+
drain(this.agentIdentifier, this.featureName)
|
|
136
|
+
} catch (err) {
|
|
137
|
+
this.ee.abort()
|
|
138
|
+
warn('RUM call failed. Agent shutting down.')
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
141
|
})
|
|
142
|
-
// Usually `drain` is invoked automatically after processing feature flags contained in the JSONP callback from
|
|
143
|
-
// ingest (see `activateFeatures`), so when JSONP cannot execute (as with module workers), we drain manually.
|
|
144
|
-
if (!isValidJsonp) drain(this.agentIdentifier, CONSTANTS.FEATURE_NAME)
|
|
145
142
|
}
|
|
146
143
|
}
|
|
@@ -29,7 +29,6 @@ export function configure (agentIdentifier, opts = {}, loaderType, forceDrain) {
|
|
|
29
29
|
gosNREUMInitializedAgents(agentIdentifier, api, 'api')
|
|
30
30
|
gosNREUMInitializedAgents(agentIdentifier, exposed, 'exposed')
|
|
31
31
|
addToNREUM('activatedFeatures', activatedFeatures)
|
|
32
|
-
addToNREUM('setToken', (flags) => activateFeatures(flags, agentIdentifier))
|
|
33
32
|
|
|
34
33
|
return api
|
|
35
34
|
}
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
Object.defineProperty(exports, "__esModule", {
|
|
4
|
-
value: true
|
|
5
|
-
});
|
|
6
|
-
exports.sHash = sHash;
|
|
7
|
-
/*
|
|
8
|
-
* Copyright 2020 New Relic Corporation. All rights reserved.
|
|
9
|
-
* SPDX-License-Identifier: Apache-2.0
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
function sHash(s) {
|
|
13
|
-
var i;
|
|
14
|
-
var h = 0;
|
|
15
|
-
for (i = 0; i < s.length; i++) {
|
|
16
|
-
h += (i + 1) * s.charCodeAt(i);
|
|
17
|
-
}
|
|
18
|
-
return Math.abs(h);
|
|
19
|
-
}
|