@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.
Files changed (196) hide show
  1. package/README.md +1 -1
  2. package/dist/cjs/common/constants/env.cdn.js +1 -1
  3. package/dist/cjs/common/constants/env.npm.js +1 -1
  4. package/dist/cjs/common/constants/shared-channel.js +19 -0
  5. package/dist/cjs/common/harvest/harvest-scheduler.js +21 -5
  6. package/dist/cjs/common/session/{session-entity.test.js → session-entity.component-test.js} +79 -42
  7. package/dist/cjs/common/session/session-entity.js +19 -11
  8. package/dist/cjs/common/timer/interaction-timer.js +1 -1
  9. package/dist/cjs/common/url/canonicalize-url.test.js +26 -30
  10. package/dist/cjs/common/util/data-size.test.js +37 -20
  11. package/dist/cjs/common/util/feature-flags.js +23 -12
  12. package/dist/cjs/common/util/feature-flags.test.js +84 -0
  13. package/dist/cjs/common/util/global-scope.js +1 -32
  14. package/dist/cjs/common/util/global-scope.test.js +72 -0
  15. package/dist/cjs/common/util/obfuscate.component-test.js +129 -0
  16. package/dist/cjs/common/util/obfuscate.js +2 -2
  17. package/dist/cjs/common/util/submit-data.js +3 -3
  18. package/dist/cjs/common/util/submit-data.test.js +145 -121
  19. package/dist/cjs/common/wrap/wrap-raf.js +1 -1
  20. package/dist/cjs/common/wrap/wrap-timer.js +1 -1
  21. package/dist/cjs/features/jserrors/aggregate/index.js +4 -0
  22. package/dist/cjs/features/jserrors/instrument/index.js +2 -15
  23. package/dist/cjs/features/session_replay/aggregate/index.component-test.js +457 -0
  24. package/dist/cjs/features/session_replay/aggregate/index.js +99 -82
  25. package/dist/cjs/features/session_replay/replay-mode.js +28 -0
  26. package/dist/cjs/features/session_trace/aggregate/index.js +222 -99
  27. package/dist/cjs/features/session_trace/constants.js +1 -3
  28. package/dist/cjs/features/session_trace/instrument/index.js +0 -16
  29. package/dist/cjs/features/spa/constants.js +0 -1
  30. package/dist/cjs/features/utils/agent-session.js +20 -36
  31. package/dist/cjs/features/utils/agent-session.test.js +211 -0
  32. package/dist/cjs/features/utils/aggregate-base.js +7 -12
  33. package/dist/cjs/features/utils/aggregate-base.test.js +110 -0
  34. package/dist/cjs/features/utils/feature-base.test.js +42 -0
  35. package/dist/cjs/features/utils/handler-cache.js +28 -23
  36. package/dist/cjs/features/utils/handler-cache.test.js +53 -0
  37. package/dist/cjs/features/utils/instrument-base.js +58 -39
  38. package/dist/cjs/features/utils/instrument-base.test.js +179 -0
  39. package/dist/cjs/features/utils/lazy-feature-loader.test.js +30 -0
  40. package/dist/cjs/loaders/agent.js +0 -1
  41. package/dist/cjs/loaders/api/api.js +1 -1
  42. package/dist/cjs/loaders/features/featureDependencies.js +2 -0
  43. package/dist/esm/common/constants/env.cdn.js +1 -1
  44. package/dist/esm/common/constants/env.npm.js +1 -1
  45. package/dist/esm/common/constants/shared-channel.js +12 -0
  46. package/dist/esm/common/harvest/harvest-scheduler.js +21 -5
  47. package/dist/esm/common/session/{session-entity.test.js → session-entity.component-test.js} +77 -40
  48. package/dist/esm/common/session/session-entity.js +17 -11
  49. package/dist/esm/common/timer/interaction-timer.js +1 -1
  50. package/dist/esm/common/url/canonicalize-url.test.js +25 -29
  51. package/dist/esm/common/util/data-size.test.js +35 -20
  52. package/dist/esm/common/util/feature-flags.js +23 -12
  53. package/dist/esm/common/util/feature-flags.test.js +80 -0
  54. package/dist/esm/common/util/global-scope.js +1 -29
  55. package/dist/esm/common/util/global-scope.test.js +70 -0
  56. package/dist/esm/common/util/obfuscate.component-test.js +125 -0
  57. package/dist/esm/common/util/obfuscate.js +2 -2
  58. package/dist/esm/common/util/submit-data.js +3 -3
  59. package/dist/esm/common/util/submit-data.test.js +143 -121
  60. package/dist/esm/common/wrap/wrap-raf.js +1 -1
  61. package/dist/esm/common/wrap/wrap-timer.js +1 -1
  62. package/dist/esm/features/jserrors/aggregate/index.js +4 -0
  63. package/dist/esm/features/jserrors/instrument/index.js +2 -15
  64. package/dist/esm/features/session_replay/aggregate/index.component-test.js +453 -0
  65. package/dist/esm/features/session_replay/aggregate/index.js +92 -78
  66. package/dist/esm/features/session_replay/replay-mode.js +23 -0
  67. package/dist/esm/features/session_trace/aggregate/index.js +223 -100
  68. package/dist/esm/features/session_trace/constants.js +0 -1
  69. package/dist/esm/features/session_trace/instrument/index.js +1 -17
  70. package/dist/esm/features/spa/constants.js +0 -1
  71. package/dist/esm/features/utils/agent-session.js +21 -37
  72. package/dist/esm/features/utils/agent-session.test.js +207 -0
  73. package/dist/esm/features/utils/aggregate-base.js +7 -12
  74. package/dist/esm/features/utils/aggregate-base.test.js +108 -0
  75. package/dist/esm/features/utils/feature-base.test.js +40 -0
  76. package/dist/esm/features/utils/handler-cache.js +28 -23
  77. package/dist/esm/features/utils/handler-cache.test.js +51 -0
  78. package/dist/esm/features/utils/instrument-base.js +58 -39
  79. package/dist/esm/features/utils/instrument-base.test.js +175 -0
  80. package/dist/esm/features/utils/lazy-feature-loader.test.js +29 -0
  81. package/dist/esm/loaders/agent.js +0 -1
  82. package/dist/esm/loaders/api/api.js +2 -2
  83. package/dist/esm/loaders/features/featureDependencies.js +2 -0
  84. package/dist/types/common/constants/shared-channel.d.ts +5 -0
  85. package/dist/types/common/constants/shared-channel.d.ts.map +1 -0
  86. package/dist/types/common/harvest/harvest-scheduler.component-test.d.ts +2 -0
  87. package/dist/types/common/harvest/harvest-scheduler.component-test.d.ts.map +1 -0
  88. package/dist/types/common/harvest/harvest-scheduler.d.ts +4 -0
  89. package/dist/types/common/harvest/harvest-scheduler.d.ts.map +1 -1
  90. package/dist/types/common/harvest/harvest.component-test.d.ts +2 -0
  91. package/dist/types/common/harvest/harvest.component-test.d.ts.map +1 -0
  92. package/dist/types/common/session/session-entity.component-test.d.ts +2 -0
  93. package/dist/types/common/session/session-entity.component-test.d.ts.map +1 -0
  94. package/dist/types/common/session/session-entity.d.ts +9 -5
  95. package/dist/types/common/session/session-entity.d.ts.map +1 -1
  96. package/dist/types/common/timer/interaction-timer.component-test.d.ts +2 -0
  97. package/dist/types/common/timer/interaction-timer.component-test.d.ts.map +1 -0
  98. package/dist/types/common/url/encode.component-test.d.ts +2 -0
  99. package/dist/types/common/url/encode.component-test.d.ts.map +1 -0
  100. package/dist/types/common/url/protocol.component-test.d.ts +2 -0
  101. package/dist/types/common/url/protocol.component-test.d.ts.map +1 -0
  102. package/dist/types/common/util/feature-flags.d.ts +1 -0
  103. package/dist/types/common/util/feature-flags.d.ts.map +1 -1
  104. package/dist/types/common/util/global-scope.d.ts +0 -9
  105. package/dist/types/common/util/global-scope.d.ts.map +1 -1
  106. package/dist/types/common/util/obfuscate.component-test.d.ts +2 -0
  107. package/dist/types/common/util/obfuscate.component-test.d.ts.map +1 -0
  108. package/dist/types/features/jserrors/aggregate/index.d.ts +1 -0
  109. package/dist/types/features/jserrors/aggregate/index.d.ts.map +1 -1
  110. package/dist/types/features/session_replay/aggregate/index.component-test.d.ts +2 -0
  111. package/dist/types/features/session_replay/aggregate/index.component-test.d.ts.map +1 -0
  112. package/dist/types/features/session_replay/aggregate/index.d.ts +14 -5
  113. package/dist/types/features/session_replay/aggregate/index.d.ts.map +1 -1
  114. package/dist/types/features/session_replay/instrument/index.d.ts.map +1 -1
  115. package/dist/types/features/session_replay/replay-mode.d.ts +9 -0
  116. package/dist/types/features/session_replay/replay-mode.d.ts.map +1 -0
  117. package/dist/types/features/session_trace/aggregate/index.d.ts +21 -3
  118. package/dist/types/features/session_trace/aggregate/index.d.ts.map +1 -1
  119. package/dist/types/features/session_trace/constants.d.ts +0 -1
  120. package/dist/types/features/session_trace/constants.d.ts.map +1 -1
  121. package/dist/types/features/session_trace/instrument/index.d.ts +0 -2
  122. package/dist/types/features/session_trace/instrument/index.d.ts.map +1 -1
  123. package/dist/types/features/spa/constants.d.ts.map +1 -1
  124. package/dist/types/features/utils/agent-session.d.ts.map +1 -1
  125. package/dist/types/features/utils/aggregate-base.d.ts +6 -1
  126. package/dist/types/features/utils/aggregate-base.d.ts.map +1 -1
  127. package/dist/types/features/utils/handler-cache.d.ts +12 -11
  128. package/dist/types/features/utils/handler-cache.d.ts.map +1 -1
  129. package/dist/types/features/utils/instrument-base.d.ts +17 -1
  130. package/dist/types/features/utils/instrument-base.d.ts.map +1 -1
  131. package/dist/types/loaders/agent.d.ts.map +1 -1
  132. package/dist/types/loaders/features/featureDependencies.d.ts.map +1 -1
  133. package/package.json +9 -7
  134. package/src/common/constants/shared-channel.js +13 -0
  135. package/src/common/harvest/harvest-scheduler.js +17 -6
  136. package/src/common/session/{session-entity.test.js → session-entity.component-test.js} +70 -47
  137. package/src/common/session/session-entity.js +15 -12
  138. package/src/common/timer/interaction-timer.js +1 -1
  139. package/src/common/url/canonicalize-url.test.js +32 -21
  140. package/src/common/util/data-size.test.js +27 -20
  141. package/src/common/util/feature-flags.js +24 -12
  142. package/src/common/util/feature-flags.test.js +98 -0
  143. package/src/common/util/global-scope.js +0 -26
  144. package/src/common/util/global-scope.test.js +87 -0
  145. package/src/common/util/obfuscate.component-test.js +173 -0
  146. package/src/common/util/obfuscate.js +2 -2
  147. package/src/common/util/submit-data.js +3 -3
  148. package/src/common/util/submit-data.test.js +123 -115
  149. package/src/common/wrap/wrap-raf.js +1 -1
  150. package/src/common/wrap/wrap-timer.js +1 -1
  151. package/src/features/jserrors/aggregate/index.js +5 -0
  152. package/src/features/jserrors/instrument/index.js +2 -15
  153. package/src/features/session_replay/aggregate/index.component-test.js +368 -0
  154. package/src/features/session_replay/aggregate/index.js +96 -71
  155. package/src/features/session_replay/instrument/index.js +0 -1
  156. package/src/features/session_replay/replay-mode.js +23 -0
  157. package/src/features/session_trace/aggregate/index.js +198 -79
  158. package/src/features/session_trace/constants.js +0 -1
  159. package/src/features/session_trace/instrument/index.js +2 -19
  160. package/src/features/spa/constants.js +0 -1
  161. package/src/features/utils/agent-session.js +22 -34
  162. package/src/features/utils/agent-session.test.js +194 -0
  163. package/src/features/utils/aggregate-base.js +12 -9
  164. package/src/features/utils/aggregate-base.test.js +122 -0
  165. package/src/features/utils/feature-base.test.js +45 -0
  166. package/src/features/utils/handler-cache.js +29 -23
  167. package/src/features/utils/handler-cache.test.js +72 -0
  168. package/src/features/utils/instrument-base.js +45 -29
  169. package/src/features/utils/instrument-base.test.js +190 -0
  170. package/src/features/utils/lazy-feature-loader.test.js +37 -0
  171. package/src/loaders/agent.js +0 -1
  172. package/src/loaders/api/api.js +2 -2
  173. package/src/loaders/features/featureDependencies.js +2 -0
  174. package/dist/cjs/common/storage/local-memory.js +0 -35
  175. package/dist/cjs/common/storage/local-memory.test.js +0 -20
  176. package/dist/esm/common/storage/local-memory.js +0 -28
  177. package/dist/esm/common/storage/local-memory.test.js +0 -18
  178. package/dist/types/common/storage/local-memory.d.ts +0 -8
  179. package/dist/types/common/storage/local-memory.d.ts.map +0 -1
  180. package/src/common/storage/local-memory.js +0 -30
  181. package/src/common/storage/local-memory.test.js +0 -19
  182. /package/dist/cjs/common/harvest/{harvest-scheduler.test.js → harvest-scheduler.component-test.js} +0 -0
  183. /package/dist/cjs/common/harvest/{harvest.test.js → harvest.component-test.js} +0 -0
  184. /package/dist/cjs/common/timer/{interaction-timer.test.js → interaction-timer.component-test.js} +0 -0
  185. /package/dist/cjs/common/url/{encode.test.js → encode.component-test.js} +0 -0
  186. /package/dist/cjs/common/url/{protocol.test.js → protocol.component-test.js} +0 -0
  187. /package/dist/esm/common/harvest/{harvest-scheduler.test.js → harvest-scheduler.component-test.js} +0 -0
  188. /package/dist/esm/common/harvest/{harvest.test.js → harvest.component-test.js} +0 -0
  189. /package/dist/esm/common/timer/{interaction-timer.test.js → interaction-timer.component-test.js} +0 -0
  190. /package/dist/esm/common/url/{encode.test.js → encode.component-test.js} +0 -0
  191. /package/dist/esm/common/url/{protocol.test.js → protocol.component-test.js} +0 -0
  192. /package/src/common/harvest/{harvest-scheduler.test.js → harvest-scheduler.component-test.js} +0 -0
  193. /package/src/common/harvest/{harvest.test.js → harvest.component-test.js} +0 -0
  194. /package/src/common/timer/{interaction-timer.test.js → interaction-timer.component-test.js} +0 -0
  195. /package/src/common/url/{encode.test.js → encode.component-test.js} +0 -0
  196. /package/src/common/url/{protocol.test.js → protocol.component-test.js} +0 -0
@@ -3,30 +3,30 @@
3
3
  * @jest-environment-options {"html": "<html><head><script></script></head><body></body></html>", "url": "https://example.com/"}
4
4
  */
5
5
 
6
+ import { faker } from '@faker-js/faker'
6
7
  import { submitData } from './submit-data'
8
+ import * as globalScope from './global-scope'
7
9
 
8
- const mockWorkerScope = jest.fn().mockImplementation(() => false)
9
- jest.mock('./global-scope', () => ({
10
- __esModule: true,
11
- get isWorkerScope () {
12
- return mockWorkerScope()
13
- }
14
- }))
10
+ jest.mock('./global-scope')
15
11
 
16
12
  const url = 'https://example.com/api'
17
13
 
18
- beforeEach(() => {
14
+ afterEach(() => {
19
15
  jest.restoreAllMocks()
20
- mockWorkerScope.mockReturnValue(false)
21
16
  })
22
17
 
23
18
  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)
19
+ beforeEach(() => {
20
+ jest.replaceProperty(globalScope, 'isWorkerScope', false)
21
+ })
27
22
 
28
- const jsonp = 'callback'
23
+ afterEach(() => {
24
+ delete global.importScripts
25
+ })
29
26
 
27
+ // This test requires a script tag to exist in the html set by this file's jest-environment-options header block.
28
+ test('should return an HTMLScriptElement when called from a web window environment', () => {
29
+ const jsonp = faker.datatype.uuid()
30
30
  const result = submitData.jsonp({ url, jsonp })
31
31
 
32
32
  expect(result).toBeInstanceOf(HTMLScriptElement)
@@ -35,125 +35,141 @@ describe('submitData.jsonp', () => {
35
35
  })
36
36
 
37
37
  test('should try to use importScripts when called from a worker scope', () => {
38
- mockWorkerScope.mockReturnValueOnce(true)
39
-
40
- const jsonp = 'callback'
41
-
38
+ jest.replaceProperty(globalScope, 'isWorkerScope', true)
42
39
  global.importScripts = jest.fn()
43
40
 
41
+ const jsonp = faker.datatype.uuid()
44
42
  submitData.jsonp({ url, jsonp })
45
43
 
46
- expect(importScripts).toHaveBeenCalledWith(url + '&jsonp=' + jsonp)
47
-
48
- delete global.importScripts
44
+ expect(global.importScripts).toHaveBeenCalledWith(url + '&jsonp=' + jsonp)
49
45
  })
50
46
 
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
-
47
+ test('should fall back to an xhrGet call and return false when importScripts throws an error', () => {
48
+ jest.replaceProperty(globalScope, 'isWorkerScope', true)
56
49
  jest.spyOn(submitData, 'xhrGet').mockImplementation(jest.fn())
50
+ global.importScripts = jest.fn().mockImplementation(() => { throw new Error(faker.lorem.sentence()) })
57
51
 
52
+ const jsonp = faker.datatype.uuid()
58
53
  const result = submitData.jsonp({ url, jsonp })
59
54
 
60
55
  expect(result).toBe(false)
61
- expect(submitData.xhrGet).toHaveBeenCalledTimes(1)
56
+ expect(global.importScripts).toHaveBeenCalledWith(url + '&jsonp=' + jsonp)
57
+ expect(submitData.xhrGet).toHaveBeenCalledWith({ url: url + '&jsonp=' + jsonp })
62
58
  })
63
59
 
64
- test('should not throw an error if any error occurs during execution', () => {
65
- jest.spyOn(document, 'createElement').mockImplementation(() => { throw new Error('message') })
60
+ test('should not throw an error when xhrGet throws an error', () => {
61
+ jest.replaceProperty(globalScope, 'isWorkerScope', true)
62
+ jest.spyOn(submitData, 'xhrGet').mockImplementation(() => { throw new Error(faker.lorem.sentence()) })
63
+ global.importScripts = jest.fn().mockImplementation(() => { throw new Error(faker.lorem.sentence()) })
66
64
 
67
- const jsonp = 'callback'
65
+ const jsonp = faker.datatype.uuid()
68
66
 
69
- expect(() => {
70
- submitData.jsonp({ url, jsonp })
71
- }).not.toThrow()
67
+ expect(() => submitData.jsonp({ url, jsonp })).not.toThrow()
72
68
  })
73
- })
74
69
 
75
- describe('submitData.xhrGet', () => {
76
- test('should return an XMLHttpRequest object', () => {
77
- const result = submitData.xhrGet({ url })
78
- expect(result).toBeInstanceOf(XMLHttpRequest)
79
- })
70
+ test('should not throw an error when element insertion fails', () => {
71
+ jest.spyOn(document, 'createElement').mockImplementation(() => { throw new Error(faker.lorem.sentence()) })
80
72
 
81
- test('should not throw an error if URL is not provided', () => {
82
- expect(() => {
83
- submitData.xhrGet({})
84
- }).not.toThrow()
85
- })
73
+ const jsonp = faker.datatype.uuid()
86
74
 
87
- test('should not throw an error if an invalid URL is provided', () => {
88
- expect(() => {
89
- submitData.xhrGet({ url: 'invalid url' })
90
- }).not.toThrow()
75
+ expect(() => submitData.jsonp({ url, jsonp })).not.toThrow()
91
76
  })
92
77
  })
93
78
 
94
- describe('submitData.xhr', () => {
95
- test('should return an XMLHttpRequest object', () => {
79
+ describe('submitData.xhrGet', () => {
80
+ test('xhrGet should call xhr with GET as the method', () => {
81
+ jest.spyOn(submitData, 'xhr').mockReturnValue(new XMLHttpRequest())
82
+
96
83
  const result = submitData.xhrGet({ url })
97
- expect(result).toBeInstanceOf(XMLHttpRequest)
98
- })
99
84
 
100
- test('should not throw an error if URL is not provided', () => {
101
- expect(() => {
102
- submitData.xhr({})
103
- }).not.toThrow()
85
+ expect(result).toBeInstanceOf(XMLHttpRequest)
86
+ expect(submitData.xhr).toHaveBeenCalledWith({ url, sync: false, method: 'GET' })
104
87
  })
88
+ })
105
89
 
106
- test('should not throw an error if an invalid URL is provided', () => {
107
- expect(() => {
108
- submitData.xhr({ url: 'invalid url' })
109
- }).not.toThrow()
90
+ describe('submitData.xhr', () => {
91
+ beforeEach(() => {
92
+ jest.spyOn(global, 'XMLHttpRequest').mockImplementation(function () {
93
+ this.prototype = XMLHttpRequest.prototype
94
+ this.open = jest.fn()
95
+ this.send = jest.fn()
96
+ this.setRequestHeader = jest.fn()
97
+
98
+ this._withCredentials = false
99
+ Object.defineProperty(this, 'withCredentials', {
100
+ get: jest.fn(() => this._withCredentials),
101
+ set: jest.fn((val) => this._withCredentials = val)
102
+ })
103
+ })
110
104
  })
111
105
 
112
- test('should send a POST request by default', () => {
113
- jest.spyOn(XMLHttpRequest.prototype, 'open')
106
+ test('should make and send an xhr with default values', () => {
107
+ const result = submitData.xhr({ url })
108
+ const xhr = jest.mocked(global.XMLHttpRequest).mock.instances[0]
114
109
 
115
- submitData.xhr({ url })
116
-
117
- expect(XMLHttpRequest.prototype.open).toHaveBeenCalledWith('POST', url, true)
110
+ expect(result).toBeInstanceOf(XMLHttpRequest)
111
+ expect(result.withCredentials).toBe(true)
112
+ expect(xhr.open).toHaveBeenCalledWith('POST', url, true)
113
+ expect(xhr.setRequestHeader).toHaveBeenCalledWith('content-type', 'text/plain')
114
+ expect(xhr.send).toHaveBeenCalledWith(null)
118
115
  })
119
116
 
120
- test('should send a GET request if specified', () => {
121
- jest.spyOn(XMLHttpRequest.prototype, 'open')
122
-
123
- submitData.xhr({ url, method: 'GET' })
117
+ test('should send the body when provided', () => {
118
+ const body = faker.lorem.paragraph()
119
+ submitData.xhr({ url, body })
120
+ const xhr = jest.mocked(global.XMLHttpRequest).mock.instances[0]
124
121
 
125
- expect(XMLHttpRequest.prototype.open).toHaveBeenCalledWith('GET', url, true)
122
+ expect(xhr.send).toHaveBeenCalledWith(body)
126
123
  })
127
124
 
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
-
125
+ test('should set async to false', () => {
132
126
  submitData.xhr({ url, sync: true })
127
+ const xhr = jest.mocked(global.XMLHttpRequest).mock.instances[0]
133
128
 
134
- expect(XMLHttpRequest.prototype.open).toHaveBeenCalledWith('POST', url, false)
129
+ expect(xhr.open).toHaveBeenCalledWith('POST', url, false)
135
130
  })
136
131
 
137
- test('should set custom headers if provided', () => {
138
- const headers = [{ key: 'Content-Type', value: 'application/json' }]
132
+ test('should use the provided method', () => {
133
+ submitData.xhr({ url, method: 'HEAD' })
134
+ const xhr = jest.mocked(global.XMLHttpRequest).mock.instances[0]
139
135
 
140
- jest.spyOn(XMLHttpRequest.prototype, 'setRequestHeader')
136
+ expect(xhr.open).toHaveBeenCalledWith('HEAD', url, true)
137
+ })
141
138
 
139
+ test('should use the provided headers', () => {
140
+ const headers = [{ key: faker.lorem.word(), value: faker.datatype.uuid() }]
142
141
  submitData.xhr({ url, headers })
142
+ const xhr = jest.mocked(global.XMLHttpRequest).mock.instances[0]
143
143
 
144
- headers.forEach((header) => {
145
- expect(XMLHttpRequest.prototype.setRequestHeader).toHaveBeenCalledWith(header.key, header.value)
146
- })
144
+ expect(xhr.setRequestHeader).not.toHaveBeenCalledWith('content-type', 'text/plain')
145
+ expect(xhr.setRequestHeader).toHaveBeenCalledWith(headers[0].key, headers[0].value)
147
146
  })
148
147
 
149
- test('should send a request with the specified body', () => {
150
- const body = JSON.stringify({ key: 'value' })
148
+ test('should not throw an error if withCredentials is not supported', () => {
149
+ jest.spyOn(global, 'XMLHttpRequest').mockImplementation(function () {
150
+ this.prototype = XMLHttpRequest.prototype
151
+ this.open = jest.fn()
152
+ this.send = jest.fn()
153
+ this.setRequestHeader = jest.fn()
154
+ })
151
155
 
152
- jest.spyOn(XMLHttpRequest.prototype, 'send')
156
+ expect(() => submitData.xhr({ url })).not.toThrow()
157
+ })
153
158
 
154
- submitData.xhr({ url, body })
159
+ test('should not throw an error if setRequestHeader throws an error', () => {
160
+ jest.spyOn(global, 'XMLHttpRequest').mockImplementation(function () {
161
+ this.prototype = XMLHttpRequest.prototype
162
+ this.open = jest.fn()
163
+ this.send = jest.fn()
164
+ this.setRequestHeader = jest.fn()
155
165
 
156
- expect(XMLHttpRequest.prototype.send).toHaveBeenCalledWith(body)
166
+ Object.defineProperty(this, 'withCredentials', {
167
+ get: jest.fn().mockImplementation(() => { throw new Error(faker.lorem.sentence()) }),
168
+ set: jest.fn().mockImplementation(() => { throw new Error(faker.lorem.sentence()) })
169
+ })
170
+ })
171
+
172
+ expect(() => submitData.xhr({ url })).not.toThrow()
157
173
  })
158
174
  })
159
175
 
@@ -164,55 +180,47 @@ describe('submitData.img', () => {
164
180
  const result = submitData.img({ url: imageUrl })
165
181
 
166
182
  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
183
  expect(result.src).toBe(imageUrl)
181
184
  })
182
185
  })
183
186
 
184
187
  describe('submitData.beacon', () => {
185
- test('should return true when beacon request succeeds', () => {
186
- const body = JSON.stringify({ key: 'value' })
188
+ afterEach(() => {
189
+ delete window.navigator.sendBeacon
190
+ })
187
191
 
188
- window.navigator.sendBeacon = {
189
- bind: jest.fn(() => () => true)
190
- }
192
+ test('should return true when beacon request succeeds', () => {
193
+ window.navigator.sendBeacon = jest.fn().mockReturnValue(true)
191
194
 
195
+ const body = faker.lorem.paragraph()
192
196
  const result = submitData.beacon({ url, body })
193
197
 
194
198
  expect(result).toBe(true)
199
+ expect(window.navigator.sendBeacon).toHaveBeenCalledWith(url, body)
195
200
  })
196
201
 
197
- test('should return false when beacon request fails', () => {
198
- const body = JSON.stringify({ key: 'value' })
202
+ test('should return false when beacon request returns false', () => {
203
+ window.navigator.sendBeacon = jest.fn().mockReturnValue(false)
199
204
 
200
- window.navigator.sendBeacon = {
201
- bind: jest.fn(() => () => { throw new Error('message') })
202
- }
205
+ const result = submitData.beacon({ url })
203
206
 
204
- const result = submitData.beacon({ url, body })
207
+ expect(result).toBe(false)
208
+ })
209
+
210
+ test('should return false when beacon request throws an error', () => {
211
+ window.navigator.sendBeacon = jest.fn(() => { throw new Error(faker.lorem.sentence()) })
212
+
213
+ const result = submitData.beacon({ url })
205
214
 
206
215
  expect(result).toBe(false)
207
216
  })
208
217
 
209
- test('should error if sendBeacon is not supported', () => {
210
- const body = JSON.stringify({ key: 'value' })
218
+ test('should always bind window.navigator to the sendBeacon function', () => {
219
+ window.navigator.sendBeacon = jest.fn().mockReturnValue(true)
220
+ window.navigator.sendBeacon.bind = jest.fn(() => {})
211
221
 
212
- window.navigator.sendBeacon = undefined
222
+ submitData.beacon({ url })
213
223
 
214
- expect(() => {
215
- submitData.beacon({ url, body })
216
- }).toThrow()
224
+ expect(window.navigator.sendBeacon.bind).toHaveBeenCalledWith(window.navigator)
217
225
  })
218
226
  })
@@ -4,7 +4,7 @@
4
4
  */
5
5
  /**
6
6
  * @file Wraps `window.requestAnimationFrame` for instrumentation.
7
- * This module is used by: jserror, session_trace.
7
+ * This module is used by: jserror.
8
8
  */
9
9
 
10
10
  import { ee as baseEE } from '../event-emitter/contextual-ee'
@@ -4,7 +4,7 @@
4
4
  */
5
5
  /**
6
6
  * @file Wraps native timeout and interval methods for instrumentation.
7
- * This module is used by: jserrors, session_trace, spa.
7
+ * This module is used by: jserrors, spa.
8
8
  */
9
9
 
10
10
  import { ee as baseEE } from '../event-emitter/contextual-ee'
@@ -32,6 +32,7 @@ export class Aggregate extends AggregateBase {
32
32
  super(agentIdentifier, aggregator, FEATURE_NAME)
33
33
 
34
34
  this.stackReported = {}
35
+ this.observedAt = {}
35
36
  this.pageviewReported = {}
36
37
  this.errorCache = {}
37
38
  this.currentBody
@@ -171,6 +172,7 @@ export class Aggregate extends AggregateBase {
171
172
  if (!this.stackReported[bucketHash]) {
172
173
  this.stackReported[bucketHash] = true
173
174
  params.stack_trace = truncateSize(stackInfo.stackString)
175
+ this.observedAt[bucketHash] = agentRuntime.offset + time
174
176
  } else {
175
177
  params.browser_stack_hash = stringHashCode(stackInfo.stackString)
176
178
  }
@@ -186,6 +188,9 @@ export class Aggregate extends AggregateBase {
186
188
  this.pageviewReported[bucketHash] = true
187
189
  }
188
190
 
191
+ if (agentRuntime?.session?.state?.sessionReplay) params.hasReplay = true
192
+ params.firstOccurrenceTimestamp = this.observedAt[bucketHash]
193
+
189
194
  var type = internal ? 'ierr' : 'err'
190
195
  var newMetrics = { time: time }
191
196
 
@@ -38,7 +38,7 @@ export class Instrument extends InstrumentBase {
38
38
  return true
39
39
  })
40
40
  this.thrown = true
41
- notice(err, undefined, thisInstrument.ee)
41
+ handle('err', [err, now()], undefined, FEATURE_NAMES.jserrors, thisInstrument.ee)
42
42
  }
43
43
  })
44
44
  thisInstrument.ee.on('fn-end', function () {
@@ -89,7 +89,7 @@ export class Instrument extends InstrumentBase {
89
89
 
90
90
  try {
91
91
  if (this.skipNext) this.skipNext -= 1
92
- else notice(errorObj || new UncaughtException(message, filename, lineno), true, this.ee)
92
+ else handle('err', [errorObj || new UncaughtException(message, filename, lineno), now()], undefined, FEATURE_NAMES.jserrors, this.ee)
93
93
  } catch (e) {
94
94
  try {
95
95
  handle('ierr', [e, now(), true], undefined, FEATURE_NAMES.jserrors, this.ee)
@@ -113,19 +113,6 @@ function UncaughtException (message, filename, lineno) {
113
113
  this.line = lineno
114
114
  }
115
115
 
116
- /**
117
- * Adds a timestamp and emits the 'err' event, which the error aggregator listens for
118
- * @param {Error} err
119
- * @param {boolean} doNotStamp
120
- * @param {ContextualEE} ee
121
- */
122
- function notice (err, doNotStamp, ee) {
123
- // by default add timestamp, unless specifically told not to
124
- // this is to preserve existing behavior
125
- var time = (!doNotStamp) ? now() : null
126
- handle('err', [err, time], undefined, FEATURE_NAMES.jserrors, ee)
127
- }
128
-
129
116
  /**
130
117
  * Attempts to cast an unhandledPromiseRejection reason (reject(...)) to an Error object
131
118
  * @param {*} reason - The reason property from an unhandled promise rejection