@newrelic/browser-agent 1.237.1 → 1.238.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 (106) hide show
  1. package/dist/cjs/common/constants/env.cdn.js +1 -1
  2. package/dist/cjs/common/constants/env.npm.js +1 -1
  3. package/dist/cjs/common/deny-list/deny-list.js +1 -1
  4. package/dist/cjs/common/event-emitter/contextual-ee.js +18 -26
  5. package/dist/cjs/common/event-emitter/event-context.js +12 -0
  6. package/dist/cjs/common/harvest/harvest.js +2 -1
  7. package/dist/cjs/common/ids/bundle-id.js +19 -0
  8. package/dist/cjs/common/wrap/wrap-events.js +3 -2
  9. package/dist/cjs/common/wrap/wrap-fetch.js +1 -3
  10. package/dist/cjs/common/wrap/wrap-function.js +15 -46
  11. package/dist/cjs/common/wrap/wrap-mutation.js +1 -2
  12. package/dist/cjs/common/wrap/wrap-promise.js +2 -3
  13. package/dist/cjs/common/wrap/wrap-xhr.js +23 -27
  14. package/dist/cjs/features/ajax/instrument/distributed-tracing.js +0 -4
  15. package/dist/cjs/features/ajax/instrument/index.js +19 -9
  16. package/dist/cjs/features/session_replay/aggregate/index.js +2 -0
  17. package/dist/cjs/features/session_trace/aggregate/index.js +14 -8
  18. package/dist/cjs/loaders/agent-base.js +12 -0
  19. package/dist/cjs/loaders/api/api.js +14 -1
  20. package/dist/cjs/loaders/api/interaction-types.js +11 -4
  21. package/dist/esm/common/constants/env.cdn.js +1 -1
  22. package/dist/esm/common/constants/env.npm.js +1 -1
  23. package/dist/esm/common/deny-list/deny-list.js +1 -1
  24. package/dist/esm/common/event-emitter/contextual-ee.js +16 -23
  25. package/dist/esm/common/event-emitter/event-context.js +5 -0
  26. package/dist/esm/common/harvest/harvest.js +2 -1
  27. package/dist/esm/common/ids/bundle-id.js +13 -0
  28. package/dist/esm/common/wrap/wrap-events.js +4 -3
  29. package/dist/esm/common/wrap/wrap-fetch.js +2 -4
  30. package/dist/esm/common/wrap/wrap-function.js +15 -44
  31. package/dist/esm/common/wrap/wrap-mutation.js +2 -3
  32. package/dist/esm/common/wrap/wrap-promise.js +3 -4
  33. package/dist/esm/common/wrap/wrap-xhr.js +23 -27
  34. package/dist/esm/features/ajax/instrument/distributed-tracing.js +0 -4
  35. package/dist/esm/features/ajax/instrument/index.js +20 -10
  36. package/dist/esm/features/session_replay/aggregate/index.js +2 -0
  37. package/dist/esm/features/session_trace/aggregate/index.js +14 -8
  38. package/dist/esm/loaders/agent-base.js +12 -0
  39. package/dist/esm/loaders/api/api.js +14 -1
  40. package/dist/esm/loaders/api/interaction-types.js +11 -4
  41. package/dist/types/common/event-emitter/contextual-ee.d.ts +22 -2
  42. package/dist/types/common/event-emitter/contextual-ee.d.ts.map +1 -1
  43. package/dist/types/common/event-emitter/event-context.d.ts +5 -0
  44. package/dist/types/common/event-emitter/event-context.d.ts.map +1 -0
  45. package/dist/types/common/event-emitter/register-handler.d.ts +1 -1
  46. package/dist/types/common/harvest/harvest.d.ts.map +1 -1
  47. package/dist/types/common/ids/bundle-id.d.ts +5 -0
  48. package/dist/types/common/ids/bundle-id.d.ts.map +1 -0
  49. package/dist/types/common/session/session-entity.d.ts +6 -6
  50. package/dist/types/common/window/nreum.d.ts +2 -2
  51. package/dist/types/common/wrap/wrap-events.d.ts.map +1 -1
  52. package/dist/types/common/wrap/wrap-fetch.d.ts.map +1 -1
  53. package/dist/types/common/wrap/wrap-function.d.ts +1 -19
  54. package/dist/types/common/wrap/wrap-function.d.ts.map +1 -1
  55. package/dist/types/common/wrap/wrap-mutation.d.ts.map +1 -1
  56. package/dist/types/common/wrap/wrap-promise.d.ts.map +1 -1
  57. package/dist/types/common/wrap/wrap-xhr.d.ts.map +1 -1
  58. package/dist/types/features/ajax/instrument/distributed-tracing.d.ts +1 -1
  59. package/dist/types/features/ajax/instrument/distributed-tracing.d.ts.map +1 -1
  60. package/dist/types/features/metrics/aggregate/endpoint-map.d.ts +5 -5
  61. package/dist/types/features/session_replay/aggregate/index.d.ts.map +1 -1
  62. package/dist/types/features/session_trace/aggregate/index.d.ts +5 -0
  63. package/dist/types/features/session_trace/aggregate/index.d.ts.map +1 -1
  64. package/dist/types/loaders/agent-base.d.ts +9 -0
  65. package/dist/types/loaders/agent-base.d.ts.map +1 -1
  66. package/dist/types/loaders/api/api.d.ts +6 -0
  67. package/dist/types/loaders/api/api.d.ts.map +1 -1
  68. package/dist/types/loaders/api/interaction-types.d.ts +18 -7
  69. package/dist/types/loaders/api/interaction-types.d.ts.map +1 -1
  70. package/dist/types/loaders/configure/configure.d.ts +1 -0
  71. package/dist/types/loaders/configure/configure.d.ts.map +1 -1
  72. package/dist/types/loaders/features/features.d.ts +9 -9
  73. package/dist/types/loaders/micro-agent.d.ts +1 -1
  74. package/dist/types/loaders/micro-agent.d.ts.map +1 -1
  75. package/package.json +22 -27
  76. package/src/common/deny-list/deny-list.js +1 -1
  77. package/src/common/deny-list/deny-list.test.js +103 -30
  78. package/src/common/event-emitter/{contextual-ee.test.js → contextual-ee.component-test.js} +15 -32
  79. package/src/common/event-emitter/contextual-ee.js +20 -31
  80. package/src/common/event-emitter/event-context.js +5 -0
  81. package/src/common/harvest/harvest.js +2 -1
  82. package/src/common/harvest/harvest.test.js +1 -1
  83. package/src/common/ids/__mocks__/bundle-id.js +2 -0
  84. package/src/common/ids/__mocks__/unique-id.js +17 -0
  85. package/src/common/ids/bundle-id.js +13 -0
  86. package/src/common/url/__mocks__/parse-url.js +15 -0
  87. package/src/common/util/__mocks__/get-or-set.js +5 -0
  88. package/src/common/window/__mocks__/nreum.js +10 -0
  89. package/src/common/wrap/wrap-events.js +4 -3
  90. package/src/common/wrap/wrap-fetch.js +2 -4
  91. package/src/common/wrap/wrap-function.js +16 -44
  92. package/src/common/wrap/wrap-mutation.js +2 -3
  93. package/src/common/wrap/{wrap-promise.test.js → wrap-promise.component-test.js} +2 -32
  94. package/src/common/wrap/wrap-promise.js +3 -4
  95. package/src/common/wrap/wrap-xhr.js +24 -28
  96. package/src/features/ajax/instrument/distributed-tracing.js +0 -4
  97. package/src/features/ajax/instrument/distributed-tracing.test.js +375 -0
  98. package/src/features/ajax/instrument/index.js +22 -10
  99. package/src/features/session_replay/aggregate/index.js +2 -0
  100. package/src/features/session_trace/aggregate/index.js +16 -7
  101. package/src/loaders/agent-base.js +12 -0
  102. package/src/loaders/api/api.component-test.js +45 -0
  103. package/src/loaders/api/api.js +14 -1
  104. package/src/loaders/api/interaction-types.js +11 -4
  105. /package/src/common/url/{encode.component-test.js → encode.test.js} +0 -0
  106. /package/src/common/url/{protocol.component-test.js → protocol.test.js} +0 -0
@@ -1,31 +1,104 @@
1
- describe('Setting deny list', () => {
2
- let DlMod
3
- beforeEach(() => {
4
- jest.isolateModules(() => import('./deny-list.js').then(m => DlMod = m)) // give every test its own denyList (sandbox)
5
- })
6
-
7
- it('respects path', () => {
8
- DlMod.setDenyList(['bam.nr-data.net/somepath'])
9
-
10
- expect(DlMod.shouldCollectEvent({ hostname: 'bam.nr-data.net', pathname: '/somepath' })).toBeFalsy() // shouldCollectEvent returns 'false' when there IS a match...
11
- expect(DlMod.shouldCollectEvent({ hostname: 'bam.nr-data.net', port: '7890', protocol: 'https', host: 'bam.nr-data.net:7890', pathname: '/somepath' })).toBeFalsy()
12
- expect(DlMod.shouldCollectEvent({ hostname: 'bam.nr-data.com', pathname: '' })).toBeTruthy()
13
- expect(DlMod.shouldCollectEvent({ hostname: 'bam.nr-data.com', pathname: '/someotherpath' })).toBeTruthy()
14
- })
15
-
16
- it('ignores port', () => {
17
- DlMod.setDenyList(['bam.nr-data.net:1234'])
18
-
19
- expect(DlMod.shouldCollectEvent({ hostname: 'bam.nr-data.net', pathname: '', port: '4321' })).toBeFalsy()
20
- expect(DlMod.shouldCollectEvent({ hostname: 'bam.nr-data.net', port: '7890', protocol: 'http', host: 'bam.nr-data.net:7890', pathname: '/somepath' })).toBeFalsy()
21
- expect(DlMod.shouldCollectEvent({ hostname: 'bam.nr-data.com', pathname: '' })).toBeTruthy()
22
- })
23
-
24
- it('ignores protocol', () => {
25
- DlMod.setDenyList(['http://bam.nr-data.net'])
26
-
27
- expect(DlMod.shouldCollectEvent({ hostname: 'bam.nr-data.net', pathname: '', protocol: 'https' })).toBeFalsy()
28
- expect(DlMod.shouldCollectEvent({ hostname: 'bam.nr-data.net', port: '7890', protocol: 'https', host: 'bam.nr-data.net:7890', pathname: '/somepath' })).toBeFalsy()
29
- expect(DlMod.shouldCollectEvent({ hostname: 'bam.nr-data.com', pathname: '', protocol: 'http' })).toBeTruthy()
30
- })
1
+ jest.enableAutomock()
2
+ jest.unmock('./deny-list')
3
+
4
+ let denyListModule
5
+
6
+ beforeEach(async () => {
7
+ denyListModule = await import('./deny-list')
8
+ })
9
+
10
+ afterEach(() => {
11
+ jest.resetModules()
12
+ })
13
+
14
+ test('domain-only blocks all subdomains and all paths', () => {
15
+ denyListModule.setDenyList(['foo.com'])
16
+
17
+ expect(denyListModule.shouldCollectEvent({ hostname: 'foo.com', pathname: '/' })).toBeFalsy()
18
+ expect(denyListModule.shouldCollectEvent({ hostname: 'foo.com', pathname: '/a' })).toBeFalsy()
19
+ expect(denyListModule.shouldCollectEvent({ hostname: 'foo.com', pathname: '/a/b' })).toBeFalsy()
20
+
21
+ expect(denyListModule.shouldCollectEvent({ hostname: 'www.foo.com', pathname: '/' })).toBeFalsy()
22
+ expect(denyListModule.shouldCollectEvent({ hostname: 'a.b.foo.com', pathname: '/' })).toBeFalsy()
23
+ expect(denyListModule.shouldCollectEvent({ hostname: 'a.b.foo.com', pathname: '/c/d' })).toBeFalsy()
24
+
25
+ expect(denyListModule.shouldCollectEvent({ hostname: 'oo.com', pathname: '/' })).toBeTruthy()
26
+ expect(denyListModule.shouldCollectEvent({ hostname: 'bar.com', pathname: '/' })).toBeTruthy()
27
+ })
28
+
29
+ test('subdomain blocks further subdomains, but not parent domain', () => {
30
+ denyListModule.setDenyList(['bar.foo.com'])
31
+
32
+ expect(denyListModule.shouldCollectEvent({ hostname: 'bar.foo.com', pathname: '/' })).toBeFalsy()
33
+ expect(denyListModule.shouldCollectEvent({ hostname: 'bar.foo.com', pathname: '/a' })).toBeFalsy()
34
+ expect(denyListModule.shouldCollectEvent({ hostname: 'bar.foo.com', pathname: '/a/b' })).toBeFalsy()
35
+
36
+ expect(denyListModule.shouldCollectEvent({ hostname: 'a.bar.foo.com', pathname: '/' })).toBeFalsy()
37
+ expect(denyListModule.shouldCollectEvent({ hostname: 'a.bar.foo.com', pathname: '/a' })).toBeFalsy()
38
+ expect(denyListModule.shouldCollectEvent({ hostname: 'a.bar.foo.com', pathname: '/a/b' })).toBeFalsy()
39
+
40
+ expect(denyListModule.shouldCollectEvent({ hostname: 'foo.com', pathname: '/' })).toBeTruthy()
41
+ expect(denyListModule.shouldCollectEvent({ hostname: 'foo.com', pathname: '/a' })).toBeTruthy()
42
+ expect(denyListModule.shouldCollectEvent({ hostname: 'foo.com', pathname: '/a/b' })).toBeTruthy()
43
+
44
+ expect(denyListModule.shouldCollectEvent({ hostname: 'oo.com', pathname: '/' })).toBeTruthy()
45
+ expect(denyListModule.shouldCollectEvent({ hostname: 'bar.com', pathname: '/' })).toBeTruthy()
46
+ })
47
+
48
+ test('* blocks all domains', () => {
49
+ denyListModule.setDenyList(['*'])
50
+
51
+ expect(denyListModule.shouldCollectEvent({ hostname: 'foo.com', pathname: '/' })).toBeFalsy()
52
+ expect(denyListModule.shouldCollectEvent({ hostname: 'www.foo.com', pathname: '/' })).toBeFalsy()
53
+ expect(denyListModule.shouldCollectEvent({ hostname: 'bar.com', pathname: '/' })).toBeFalsy()
54
+ expect(denyListModule.shouldCollectEvent({ hostname: 'www.bar.com', pathname: '/' })).toBeFalsy()
55
+ })
56
+
57
+ test('respects path', () => {
58
+ denyListModule.setDenyList(['bam.nr-data.net/somepath'])
59
+
60
+ expect(denyListModule.shouldCollectEvent({ hostname: 'bam.nr-data.net', pathname: '/somepath' })).toBeFalsy()
61
+ expect(denyListModule.shouldCollectEvent({ hostname: 'bam.nr-data.net', port: '7890', protocol: 'https', host: 'bam.nr-data.net:7890', pathname: '/somepath' })).toBeFalsy()
62
+
63
+ expect(denyListModule.shouldCollectEvent({ hostname: 'bam.nr-data.com', pathname: '' })).toBeTruthy()
64
+ expect(denyListModule.shouldCollectEvent({ hostname: 'bam.nr-data.com', pathname: '/someotherpath' })).toBeTruthy()
65
+ expect(denyListModule.shouldCollectEvent({ hostname: 'bam.nr-data.com', pathname: '/some/otherpath' })).toBeTruthy()
66
+ })
67
+
68
+ test('ignores port', () => {
69
+ denyListModule.setDenyList(['bam.nr-data.net:1234'])
70
+
71
+ expect(denyListModule.shouldCollectEvent({ hostname: 'bam.nr-data.net', pathname: '', port: '4321' })).toBeFalsy()
72
+ expect(denyListModule.shouldCollectEvent({ hostname: 'bam.nr-data.net', port: '7890', protocol: 'http', host: 'bam.nr-data.net:7890', pathname: '/somepath' })).toBeFalsy()
73
+
74
+ expect(denyListModule.shouldCollectEvent({ hostname: 'bam.nr-data.com', pathname: '' })).toBeTruthy()
75
+ })
76
+
77
+ test('ignores protocol', () => {
78
+ denyListModule.setDenyList(['http://bam.nr-data.net'])
79
+
80
+ expect(denyListModule.shouldCollectEvent({ hostname: 'bam.nr-data.net', pathname: '', protocol: 'https' })).toBeFalsy()
81
+ expect(denyListModule.shouldCollectEvent({ hostname: 'bam.nr-data.net', port: '7890', protocol: 'https', host: 'bam.nr-data.net:7890', pathname: '/somepath' })).toBeFalsy()
82
+
83
+ expect(denyListModule.shouldCollectEvent({ hostname: 'bam.nr-data.com', pathname: '', protocol: 'http' })).toBeTruthy()
84
+ })
85
+
86
+ test.each([
87
+ null,
88
+ undefined,
89
+ '!@$%^*',
90
+ 'https://example.com/http://foo.bar/'
91
+ ])('ignores invalid deny list value %s', (denyListValue) => {
92
+ denyListModule.setDenyList([denyListValue])
93
+
94
+ expect(denyListModule.shouldCollectEvent({ hostname: 'foo.com', pathname: '/' })).toBeTruthy()
95
+ expect(denyListModule.shouldCollectEvent({ hostname: 'foo.com', pathname: '/a' })).toBeTruthy()
96
+ expect(denyListModule.shouldCollectEvent({ hostname: 'foo.com', pathname: '/a/b' })).toBeTruthy()
97
+
98
+ expect(denyListModule.shouldCollectEvent({ hostname: 'www.foo.com', pathname: '/' })).toBeTruthy()
99
+ expect(denyListModule.shouldCollectEvent({ hostname: 'a.b.foo.com', pathname: '/' })).toBeTruthy()
100
+ expect(denyListModule.shouldCollectEvent({ hostname: 'a.b.foo.com', pathname: '/c/d' })).toBeTruthy()
101
+
102
+ expect(denyListModule.shouldCollectEvent({ hostname: 'oo.com', pathname: '/' })).toBeTruthy()
103
+ expect(denyListModule.shouldCollectEvent({ hostname: 'bar.com', pathname: '/' })).toBeTruthy()
31
104
  })
@@ -24,19 +24,18 @@ afterEach(() => {
24
24
  })
25
25
 
26
26
  describe('global event-emitter', () => {
27
- test('it returns the event-emitter defined on window.NREUM', async () => {
28
- const mockEE = {}
29
- mockNREUM.ee = mockEE
30
-
27
+ test('it sets the global event-emitter on window.NREUM when it does not already exist', async () => {
31
28
  const { ee } = await import('./contextual-ee')
32
29
 
33
- expect(ee).toEqual(mockEE)
30
+ expect(ee).toEqual(mockNREUM.ee)
34
31
  })
35
32
 
36
- test('it sets the global event-emitter on window.NREUM', async () => {
33
+ test('it does not set the global event-emitter on window.NREUM when it already exists', async () => {
34
+ mockNREUM.ee = {}
35
+
37
36
  const { ee } = await import('./contextual-ee')
38
37
 
39
- expect(ee).toEqual(mockNREUM.ee)
38
+ expect(ee).not.toEqual(mockNREUM.ee)
40
39
  })
41
40
  })
42
41
 
@@ -79,7 +78,9 @@ describe('event-emitter context', () => {
79
78
 
80
79
  const result = ee.context()
81
80
 
82
- expect(result).toEqual({})
81
+ expect(result).toEqual(expect.objectContaining({
82
+ contextId: expect.stringContaining('nr@context:')
83
+ }))
83
84
  })
84
85
 
85
86
  test('it returns the same context', async () => {
@@ -87,7 +88,7 @@ describe('event-emitter context', () => {
87
88
 
88
89
  const result = ee.context()
89
90
 
90
- expect(result).toEqual(ee.context(result))
91
+ expect(result).toBe(ee.context(result))
91
92
  })
92
93
 
93
94
  test('it adds the context to the provided object', async () => {
@@ -95,8 +96,9 @@ describe('event-emitter context', () => {
95
96
 
96
97
  const obj = {}
97
98
  const result = ee.context(obj)
99
+ const ctxKey = Object.getOwnPropertyNames(obj).find(k => k.startsWith('nr@context'))
98
100
 
99
- expect(result).toEqual(obj['nr@context'])
101
+ expect(result).toBe(obj[ctxKey])
100
102
  })
101
103
  })
102
104
 
@@ -281,30 +283,11 @@ describe('event-emitter emit', () => {
281
283
  scopeEE,
282
284
  eventType,
283
285
  eventArgs,
284
- {}
286
+ expect.objectContaining({
287
+ contextId: expect.stringContaining('nr@context:')
288
+ })
285
289
  ])
286
290
  ]))
287
291
  expect(ee.backlog.feature).toEqual(scopeEE.backlog.feature)
288
292
  })
289
293
  })
290
-
291
- describe('getOrSetContext', () => {
292
- test('it returns a new context', async () => {
293
- const { getOrSetContext } = await import('./contextual-ee')
294
-
295
- const obj = {}
296
- const result = getOrSetContext(obj)
297
-
298
- expect(result).toEqual({})
299
- expect(result).toEqual(obj['nr@context'])
300
- })
301
-
302
- test('it returns the same context', async () => {
303
- const { getOrSetContext } = await import('./contextual-ee')
304
-
305
- const obj = {}
306
- const result = getOrSetContext(obj)
307
-
308
- expect(getOrSetContext(obj)).toEqual(result)
309
- })
310
- })
@@ -5,25 +5,22 @@
5
5
 
6
6
  import { gosNREUM } from '../window/nreum'
7
7
  import { getOrSet } from '../util/get-or-set'
8
- import { mapOwn } from '../util/map-own'
9
8
  import { getRuntime } from '../config/config'
9
+ import { EventContext } from './event-context'
10
+ import { bundleId } from '../ids/bundle-id'
10
11
 
11
- var ctxId = 'nr@context'
12
-
12
+ // create a unique id to store event context data for the current agent bundle
13
+ const contextId = `nr@context:${bundleId}`
13
14
  // create global emitter instance that can be shared among bundles
14
- let nr = gosNREUM()
15
- var globalInstance
15
+ const globalInstance = ee(undefined, 'globalEE')
16
16
 
17
- if (nr.ee) {
18
- globalInstance = nr.ee
19
- } else {
20
- globalInstance = ee(undefined, 'globalEE')
17
+ // Only override the exposed event emitter if one has not already been exposed
18
+ const nr = gosNREUM()
19
+ if (!nr.ee) {
21
20
  nr.ee = globalInstance
22
21
  }
23
22
 
24
- export { globalInstance as ee }
25
-
26
- function EventContext () { }
23
+ export { globalInstance as ee, contextId }
27
24
 
28
25
  function ee (old, debugId) {
29
26
  var handlers = {}
@@ -65,9 +62,9 @@ function ee (old, debugId) {
65
62
  if (contextOrStore && contextOrStore instanceof EventContext) {
66
63
  return contextOrStore
67
64
  } else if (contextOrStore) {
68
- return getOrSet(contextOrStore, ctxId, getNewContext)
65
+ return getOrSet(contextOrStore, contextId, () => new EventContext(contextId))
69
66
  } else {
70
- return getNewContext()
67
+ return new EventContext(contextId)
71
68
  }
72
69
  }
73
70
 
@@ -119,17 +116,18 @@ function ee (old, debugId) {
119
116
  }
120
117
 
121
118
  function bufferEventsByGroup (types, group) {
122
- var eventBuffer = getBuffer()
119
+ const eventBuffer = getBuffer()
120
+ group = group || 'feature'
123
121
 
124
122
  // do not buffer events if agent has been aborted
125
123
  if (emitter.aborted) return
126
- mapOwn(types, function (i, type) {
127
- group = group || 'feature'
128
- bufferGroupMap[type] = group
129
- if (!(group in eventBuffer)) {
130
- eventBuffer[group] = []
131
- }
132
- })
124
+ Object.entries(types || {})
125
+ .forEach(([_, type]) => {
126
+ bufferGroupMap[type] = group
127
+ if (!(group in eventBuffer)) {
128
+ eventBuffer[group] = []
129
+ }
130
+ })
133
131
  }
134
132
 
135
133
  function isBuffering (type) {
@@ -144,15 +142,6 @@ function ee (old, debugId) {
144
142
  }
145
143
  }
146
144
 
147
- // get context object from store object, or create if does not exist
148
- export function getOrSetContext (obj) {
149
- return getOrSet(obj, ctxId, getNewContext)
150
- }
151
-
152
- function getNewContext () {
153
- return new EventContext()
154
- }
155
-
156
145
  function abort () {
157
146
  globalInstance.aborted = true
158
147
  globalInstance.backlog = {}
@@ -0,0 +1,5 @@
1
+ export class EventContext {
2
+ constructor (contextId) {
3
+ this.contextId = contextId
4
+ }
5
+ }
@@ -47,7 +47,8 @@ export class Harvest extends SharedContext {
47
47
  sendX (spec = {}) {
48
48
  const submitMethod = submitData.getSubmitMethod({ isFinalHarvest: spec.opts?.unload })
49
49
  const options = {
50
- retry: !spec.opts?.unload && submitMethod === submitData.xhr
50
+ retry: !spec.opts?.unload && submitMethod === submitData.xhr,
51
+ isFinalHarvest: spec.opts?.unload === true
51
52
  }
52
53
  const payload = this.createPayload(spec.endpoint, options)
53
54
  const caller = this.obfuscator.shouldObfuscate() ? this.obfuscateAndSend.bind(this) : this._send.bind(this)
@@ -53,7 +53,7 @@ describe('sendX', () => {
53
53
 
54
54
  harvestInstance.sendX(spec)
55
55
 
56
- expect(harvestInstance.createPayload).toHaveBeenCalledWith(spec.endpoint, { retry: true })
56
+ expect(harvestInstance.createPayload).toHaveBeenCalledWith(spec.endpoint, { retry: true, isFinalHarvest: false })
57
57
  })
58
58
 
59
59
  test('should not use obfuscateAndSend', async () => {
@@ -0,0 +1,2 @@
1
+ import { faker } from '@faker-js/faker'
2
+ export const bundleId = faker.datatype.uuid()
@@ -0,0 +1,17 @@
1
+ import { faker } from '@faker-js/faker'
2
+
3
+ export function generateUuid () {
4
+ return faker.datatype.uuid()
5
+ }
6
+
7
+ export function generateRandomHexString (length) {
8
+ return faker.datatype.hexadecimal({ length, prefix: '' })
9
+ }
10
+
11
+ export function generateSpanId () {
12
+ return generateRandomHexString(16)
13
+ }
14
+
15
+ export function generateTraceId () {
16
+ return generateRandomHexString(32)
17
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * @file Contains a unique identifier for the running agent bundle
3
+ * when loaded.
4
+ * @copyright 2023 New Relic Corporation. All rights reserved.
5
+ * @license Apache-2.0
6
+ */
7
+
8
+ import { generateUuid } from './unique-id'
9
+
10
+ /**
11
+ * Provides a unique id for the current agent bundle
12
+ */
13
+ export const bundleId = generateUuid()
@@ -0,0 +1,15 @@
1
+ export function parseUrl (url) {
2
+ try {
3
+ const result = new URL(url)
4
+
5
+ return {
6
+ hostname: result.hostname,
7
+ port: result.port || result.protocol.startsWith('https') ? '443' : '80',
8
+ pathname: result.pathname,
9
+ protocol: result.protocol.replace(':', ''),
10
+ sameOrigin: false
11
+ }
12
+ } catch (err) {
13
+ return {}
14
+ }
15
+ }
@@ -0,0 +1,5 @@
1
+ export const getOrSet = jest.fn((obj, prop, getVal) => {
2
+ if (obj[prop]) return obj[prop]
3
+ obj[prop] = getVal()
4
+ return obj[prop]
5
+ })
@@ -0,0 +1,10 @@
1
+ export const defaults = {}
2
+ export const gosNREUM = jest.fn(() => ({}))
3
+ export const gosNREUMInfo = jest.fn(() => ({}))
4
+ export const gosNREUMLoaderConfig = jest.fn(() => ({}))
5
+ export const gosNREUMInit = jest.fn(() => ({}))
6
+ export const gosNREUMOriginals = jest.fn(() => ({}))
7
+ export const gosNREUMInitializedAgents = jest.fn(() => ({}))
8
+ export const addToNREUM = jest.fn(() => ({}))
9
+ export const NREUMinitialized = jest.fn(() => ({}))
10
+ export const gosCDN = jest.fn(() => ({}))
@@ -7,15 +7,16 @@
7
7
  * This module is used directly by: session_trace.
8
8
  * It is also called by -> wrapXhr <-, so see "wrap-xhr.js" for features that use this indirectly.
9
9
  */
10
- import { ee as baseEE } from '../event-emitter/contextual-ee'
10
+ import { ee as baseEE, contextId } from '../event-emitter/contextual-ee'
11
11
  import { createWrapperWithEmitter as wfn } from './wrap-function'
12
12
  import { getOrSet } from '../util/get-or-set'
13
13
  import { globalScope, isBrowserScope } from '../constants/runtime'
14
14
 
15
15
  const wrapped = {}
16
- const XHR = XMLHttpRequest
16
+ const XHR = globalScope.XMLHttpRequest
17
17
  const ADD_EVENT_LISTENER = 'addEventListener'
18
18
  const REMOVE_EVENT_LISTENER = 'removeEventListener'
19
+ const flag = `nr@wrapped:${contextId}`
19
20
 
20
21
  /**
21
22
  * Wraps `addEventListener` and `removeEventListener` on: global scope; the prototype of `XMLHttpRequest`, and
@@ -48,7 +49,7 @@ export function wrapEvents (sharedEE) {
48
49
  return
49
50
  }
50
51
 
51
- var wrapped = getOrSet(originalListener, 'nr@wrapped', function () {
52
+ var wrapped = getOrSet(originalListener, flag, function () {
52
53
  var listener = {
53
54
  object: wrapHandleEvent,
54
55
  function: originalListener
@@ -6,9 +6,8 @@
6
6
  * @file Wraps `fetch` and related methods for instrumentation.
7
7
  * This module is used by: ajax, spa.
8
8
  */
9
- import { ee as baseEE } from '../event-emitter/contextual-ee'
9
+ import { ee as baseEE, contextId } from '../event-emitter/contextual-ee'
10
10
  import { globalScope } from '../constants/runtime'
11
- import { flag } from './wrap-function'
12
11
 
13
12
  var prefix = 'fetch-'
14
13
  var bodyPrefix = prefix + 'body-'
@@ -16,7 +15,6 @@ var bodyMethods = ['arrayBuffer', 'blob', 'json', 'text', 'formData']
16
15
  var Req = globalScope.Request
17
16
  var Res = globalScope.Response
18
17
  var proto = 'prototype'
19
- var ctxId = 'nr@context'
20
18
 
21
19
  const wrapped = {}
22
20
 
@@ -76,7 +74,7 @@ export function wrapFetch (sharedEE) {
76
74
  // we are wrapping args in an array so we can preserve the reference
77
75
  ee.emit(prefix + 'before-start', [args], ctx)
78
76
  var dtPayload
79
- if (ctx[ctxId] && ctx[ctxId].dt) dtPayload = ctx[ctxId].dt
77
+ if (ctx[contextId] && ctx[contextId].dt) dtPayload = ctx[contextId].dt
80
78
 
81
79
  var origPromiseFromFetch = fn.apply(this, args)
82
80
 
@@ -7,7 +7,9 @@
7
7
  */
8
8
 
9
9
  import { ee } from '../event-emitter/contextual-ee'
10
- export const flag = 'nr@original'
10
+ import { bundleId } from '../ids/bundle-id'
11
+
12
+ export const flag = `nr@original:${bundleId}`
11
13
 
12
14
  /**
13
15
  * A convenience alias of `hasOwnProperty`.
@@ -107,26 +109,24 @@ export function createWrapperWithEmitter (emitter, always) {
107
109
 
108
110
  /**
109
111
  * Creates wrapper functions around each of an array of methods on a specified object.
110
- * @param {Object} obj - The object to which the specified methods belong.
111
- * @param {string[]} methods - An array of method names to be wrapped.
112
- * @param {string} [prefix=''] - A prefix to add to the names of emitted events. If `-` is the first character, also
113
- * adds the method name before the prefix.
114
- * @param {function|object} [getContext] - The function or object that will serve as the 'this' context for handlers
115
- * of events emitted by this wrapper.
116
- * @param {boolean} [bubble=false] - If `true`, emitted events should also bubble up to the old emitter upon which
117
- * the `emitter` in the current scope was based (if it defines one).
112
+ * @param {Object} obj The object to which the specified methods belong.
113
+ * @param {string[]} methods An array of method names to be wrapped.
114
+ * @param {string} [prefix=''] A prefix to add to the names of emitted events. If `-` is the first character, also
115
+ * adds the method name before the prefix.
116
+ * @param {function|object} [getContext] The function or object that will serve as the 'this' context for handlers
117
+ * of events emitted by this wrapper.
118
+ * @param {boolean} [bubble=false] If `true`, emitted events should also bubble up to the old emitter upon which
119
+ * the `emitter` in the current scope was based (if it defines one).
118
120
  */
119
121
  function inPlace (obj, methods, prefix, getContext, bubble) {
120
122
  if (!prefix) prefix = ''
123
+
121
124
  // If prefix starts with '-' set this boolean to add the method name to the prefix before passing each one to wrap.
122
- var prependMethodPrefix = (prefix.charAt(0) === '-')
123
- var fn
124
- var method
125
- var i
125
+ const prependMethodPrefix = (prefix.charAt(0) === '-')
126
126
 
127
- for (i = 0; i < methods.length; i++) {
128
- method = methods[i]
129
- fn = obj[method]
127
+ for (let i = 0; i < methods.length; i++) {
128
+ const method = methods[i]
129
+ const fn = obj[method]
130
130
 
131
131
  // Unless fn is both wrappable and unwrapped, bail so we don't add extra properties with undefined values.
132
132
  if (notWrappable(fn)) continue
@@ -217,31 +217,3 @@ function copy (from, to, emitter) {
217
217
  function notWrappable (fn) {
218
218
  return !(fn && fn instanceof Function && fn.apply && !fn[flag])
219
219
  }
220
-
221
- /**
222
- * Creates a wrapped version of a function using a specified wrapper function. The new wrapped function references the
223
- * original function. Also copies the properties of the original function to the wrapped function.
224
- * @param {function} fn - A function to be wrapped.
225
- * @param {function} wrapper - A higher order function that returns a new function, which executes the function passed
226
- * to the higher order function as an argument.
227
- * @returns {function} A wrapped function with an internal reference to the original function.
228
- */
229
- export function wrapFunction (fn, wrapper) {
230
- var wrapped = wrapper(fn)
231
- wrapped[flag] = fn
232
- copy(fn, wrapped, ee)
233
- return wrapped
234
- }
235
-
236
- /**
237
- * Replaces a function with a wrapped version of itself. To preserve object references, rather than taking the function
238
- * itself as an argument, takes the object on which the particular named function is a property.
239
- * @param {Object} obj - The object on which the named function is a property.
240
- * @param {string} fnName - The name of the function to be wrapped.
241
- * @param {function} wrapper - A higher order function that returns a new function, which executes the function passed
242
- * to the higher order function as an argument.
243
- */
244
- export function wrapInPlace (obj, fnName, wrapper) {
245
- var fn = obj[fnName]
246
- obj[fnName] = wrapFunction(fn, wrapper)
247
- }
@@ -9,8 +9,7 @@
9
9
 
10
10
  import { ee as baseEE } from '../event-emitter/contextual-ee'
11
11
  import { createWrapperWithEmitter as wfn } from './wrap-function'
12
- import { originals } from '../config/config'
13
- import { isBrowserScope } from '../constants/runtime'
12
+ import { globalScope, isBrowserScope } from '../constants/runtime'
14
13
 
15
14
  const wrapped = {}
16
15
 
@@ -30,7 +29,7 @@ export function wrapMutation (sharedEE) {
30
29
  wrapped[ee.debugId] = true // otherwise, first feature to wrap mutations
31
30
 
32
31
  var wrapFn = wfn(ee)
33
- var OriginalObserver = originals.MO
32
+ var OriginalObserver = globalScope.MutationObserver
34
33
 
35
34
  if (OriginalObserver) {
36
35
  window.MutationObserver = function WrappedMutationObserver (cb) {
@@ -1,35 +1,5 @@
1
1
  import { faker } from '@faker-js/faker'
2
2
  import { globalScope } from '../constants/runtime'
3
- import { originals } from '../config/config'
4
-
5
- jest.mock('./wrap-function', () => ({
6
- __esModule: true,
7
- createWrapperWithEmitter: jest.fn(() => (fn) => (...args) => {
8
- return fn.apply(null, args)
9
- })
10
- }))
11
- jest.mock('../event-emitter/contextual-ee', () => ({
12
- __esModule: true,
13
- getOrSetContext: jest.fn(() => ({})),
14
- ee: {
15
- get: jest.fn((name) => ({
16
- debugId: name,
17
- on: jest.fn(),
18
- context: jest.fn(() => ({})),
19
- emit: jest.fn()
20
- }))
21
- }
22
- }))
23
- jest.mock('../config/config', () => ({
24
- __esModule: true,
25
- originals: {}
26
- }))
27
- jest.mock('../constants/runtime', () => ({
28
- __esModule: true,
29
- globalScope: {
30
- NREUM: {}
31
- }
32
- }))
33
3
 
34
4
  let promiseConstructorCalls
35
5
 
@@ -38,7 +8,7 @@ beforeEach(async () => {
38
8
 
39
9
  // Proxy the global Promise to prevent the wrapping from
40
10
  // messing with Jest internal promises
41
- originals.PR = new Proxy(class extends Promise {}, {
11
+ window.Promise = new Proxy(class extends Promise {}, {
42
12
  construct (target, args) {
43
13
  promiseConstructorCalls.push(args)
44
14
 
@@ -57,7 +27,7 @@ test('should wrap promise constructor', async () => {
57
27
  const promiseInstance = new globalScope.Promise(jest.fn())
58
28
 
59
29
  expect(promiseInstance).toBeInstanceOf(Promise)
60
- expect(promiseConstructorCalls.length).toEqual(1)
30
+ expect(promiseConstructorCalls.length).toBeGreaterThan(0)
61
31
  expect(globalScope.Promise.toString()).toMatch(/\[native code\]/)
62
32
  expect(globalScope.Promise.name).toEqual('Promise')
63
33
  })
@@ -8,8 +8,7 @@
8
8
  */
9
9
 
10
10
  import { createWrapperWithEmitter as wrapFn, flag } from './wrap-function'
11
- import { ee as baseEE, getOrSetContext } from '../event-emitter/contextual-ee'
12
- import { originals } from '../config/config'
11
+ import { ee as baseEE } from '../event-emitter/contextual-ee'
13
12
  import { globalScope } from '../constants/runtime'
14
13
 
15
14
  const wrapped = {}
@@ -29,9 +28,9 @@ export function wrapPromise (sharedEE) {
29
28
  if (wrapped[promiseEE.debugId]) return promiseEE
30
29
  wrapped[promiseEE.debugId] = true // otherwise, first feature to wrap promise
31
30
 
32
- var getContext = getOrSetContext
31
+ var getContext = promiseEE.context
33
32
  var promiseWrapper = wrapFn(promiseEE)
34
- var prevPromiseObj = originals.PR
33
+ var prevPromiseObj = globalScope.Promise
35
34
 
36
35
  if (prevPromiseObj) { // ensure there's a Promise API (native or otherwise) to even wrap
37
36
  wrap()