@newrelic/browser-agent 0.1.231 → 1.232.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 (210) hide show
  1. package/README.md +2 -2
  2. package/dist/cjs/common/config/state/configurable.js +27 -21
  3. package/dist/cjs/common/config/state/init.js +8 -0
  4. package/dist/cjs/common/config/state/runtime.js +24 -26
  5. package/dist/cjs/common/constants/env.cdn.js +1 -1
  6. package/dist/cjs/common/constants/env.npm.js +1 -1
  7. package/dist/cjs/common/context/shared-context.js +2 -1
  8. package/dist/cjs/common/event-emitter/contextual-ee.test.js +2 -2
  9. package/dist/cjs/common/event-emitter/register-handler.test.js +1 -1
  10. package/dist/cjs/common/event-listener/event-listener-opts.js +4 -2
  11. package/dist/cjs/common/harvest/harvest-scheduler.js +14 -11
  12. package/dist/cjs/common/harvest/harvest.js +3 -1
  13. package/dist/cjs/common/session/constants.js +12 -0
  14. package/dist/cjs/common/session/session-entity.js +278 -0
  15. package/dist/cjs/common/session/session-entity.test.js +436 -0
  16. package/dist/cjs/common/storage/first-party-cookies.js +35 -0
  17. package/dist/cjs/common/storage/local-memory.js +35 -0
  18. package/dist/cjs/common/storage/local-memory.test.js +20 -0
  19. package/dist/cjs/common/storage/local-storage.js +33 -0
  20. package/dist/cjs/common/storage/local-storage.test.js +14 -0
  21. package/dist/cjs/common/timer/interaction-timer.js +78 -0
  22. package/dist/cjs/common/timer/interaction-timer.test.js +216 -0
  23. package/dist/cjs/common/timer/timer.js +32 -0
  24. package/dist/cjs/common/timer/timer.test.js +105 -0
  25. package/dist/cjs/common/unload/eol.js +2 -2
  26. package/dist/cjs/common/util/data-size.js +6 -0
  27. package/dist/cjs/common/util/data-size.test.js +47 -0
  28. package/dist/cjs/common/util/invoke.js +73 -0
  29. package/dist/cjs/common/util/invoke.test.js +49 -0
  30. package/dist/cjs/common/util/obfuscate.js +0 -4
  31. package/dist/cjs/common/window/page-visibility.js +3 -1
  32. package/dist/cjs/common/wrap/wrap-timer.js +1 -1
  33. package/dist/cjs/features/ajax/aggregate/index.js +2 -2
  34. package/dist/cjs/features/jserrors/aggregate/index.js +3 -3
  35. package/dist/cjs/features/metrics/aggregate/index.js +13 -2
  36. package/dist/cjs/features/page_action/aggregate/index.js +2 -2
  37. package/dist/cjs/features/page_view_event/aggregate/index.js +6 -3
  38. package/dist/cjs/features/page_view_timing/aggregate/index.js +6 -6
  39. package/dist/cjs/features/session_trace/aggregate/index.js +2 -2
  40. package/dist/cjs/features/spa/aggregate/index.js +6 -5
  41. package/dist/cjs/features/utils/agent-session.js +73 -0
  42. package/dist/cjs/features/utils/feature-base.js +1 -1
  43. package/dist/cjs/features/utils/instrument-base.js +7 -2
  44. package/dist/cjs/features/utils/lazy-loader.js +1 -1
  45. package/dist/cjs/loaders/agent.js +1 -1
  46. package/dist/cjs/loaders/api/api.js +1 -4
  47. package/dist/cjs/loaders/api/apiAsync.js +3 -2
  48. package/dist/cjs/loaders/configure/configure.js +0 -6
  49. package/dist/esm/common/config/state/configurable.js +26 -20
  50. package/dist/esm/common/config/state/init.js +8 -0
  51. package/dist/esm/common/config/state/runtime.js +24 -26
  52. package/dist/esm/common/constants/env.cdn.js +1 -1
  53. package/dist/esm/common/constants/env.npm.js +1 -1
  54. package/dist/esm/common/context/shared-context.js +2 -1
  55. package/dist/esm/common/event-emitter/contextual-ee.test.js +2 -2
  56. package/dist/esm/common/event-emitter/register-handler.test.js +1 -1
  57. package/dist/esm/common/event-listener/event-listener-opts.js +4 -2
  58. package/dist/esm/common/harvest/harvest-scheduler.js +14 -11
  59. package/dist/esm/common/harvest/harvest.js +3 -1
  60. package/dist/esm/common/session/constants.js +3 -0
  61. package/dist/esm/common/session/session-entity.js +271 -0
  62. package/dist/esm/common/session/session-entity.test.js +434 -0
  63. package/dist/esm/common/storage/first-party-cookies.js +28 -0
  64. package/dist/esm/common/storage/local-memory.js +28 -0
  65. package/dist/esm/common/storage/local-memory.test.js +18 -0
  66. package/dist/esm/common/storage/local-storage.js +26 -0
  67. package/dist/esm/common/storage/local-storage.test.js +12 -0
  68. package/dist/esm/common/timer/interaction-timer.js +71 -0
  69. package/dist/esm/common/timer/interaction-timer.test.js +214 -0
  70. package/dist/esm/common/timer/timer.js +25 -0
  71. package/dist/esm/common/timer/timer.test.js +103 -0
  72. package/dist/esm/common/unload/eol.js +1 -1
  73. package/dist/esm/common/util/data-size.js +7 -0
  74. package/dist/esm/common/util/data-size.test.js +45 -0
  75. package/dist/esm/common/util/invoke.js +66 -0
  76. package/dist/esm/common/util/invoke.test.js +47 -0
  77. package/dist/esm/common/util/obfuscate.js +0 -4
  78. package/dist/esm/common/window/page-visibility.js +3 -1
  79. package/dist/esm/common/wrap/wrap-timer.js +1 -1
  80. package/dist/esm/features/ajax/aggregate/index.js +2 -2
  81. package/dist/esm/features/jserrors/aggregate/index.js +3 -3
  82. package/dist/esm/features/metrics/aggregate/index.js +14 -3
  83. package/dist/esm/features/page_action/aggregate/index.js +2 -2
  84. package/dist/esm/features/page_view_event/aggregate/index.js +6 -3
  85. package/dist/esm/features/page_view_timing/aggregate/index.js +6 -6
  86. package/dist/esm/features/session_trace/aggregate/index.js +2 -2
  87. package/dist/esm/features/spa/aggregate/index.js +6 -5
  88. package/dist/esm/features/utils/agent-session.js +67 -0
  89. package/dist/esm/features/utils/feature-base.js +1 -1
  90. package/dist/esm/features/utils/instrument-base.js +7 -2
  91. package/dist/esm/features/utils/lazy-loader.js +1 -1
  92. package/dist/esm/loaders/agent.js +1 -1
  93. package/dist/esm/loaders/api/api.js +2 -5
  94. package/dist/esm/loaders/api/apiAsync.js +2 -1
  95. package/dist/esm/loaders/configure/configure.js +2 -8
  96. package/dist/types/common/config/state/configurable.d.ts.map +1 -1
  97. package/dist/types/common/config/state/init.d.ts.map +1 -1
  98. package/dist/types/common/config/state/runtime.d.ts.map +1 -1
  99. package/dist/types/common/context/shared-context.d.ts.map +1 -1
  100. package/dist/types/common/event-listener/event-listener-opts.d.ts +2 -2
  101. package/dist/types/common/event-listener/event-listener-opts.d.ts.map +1 -1
  102. package/dist/types/common/harvest/harvest-scheduler.d.ts +1 -0
  103. package/dist/types/common/harvest/harvest-scheduler.d.ts.map +1 -1
  104. package/dist/types/common/harvest/harvest.d.ts.map +1 -1
  105. package/dist/types/common/session/constants.d.ts +4 -0
  106. package/dist/types/common/session/constants.d.ts.map +1 -0
  107. package/dist/types/common/session/session-entity.d.ts +72 -0
  108. package/dist/types/common/session/session-entity.d.ts.map +1 -0
  109. package/dist/types/common/storage/first-party-cookies.d.ts +8 -0
  110. package/dist/types/common/storage/first-party-cookies.d.ts.map +1 -0
  111. package/dist/types/common/storage/local-memory.d.ts +8 -0
  112. package/dist/types/common/storage/local-memory.d.ts.map +1 -0
  113. package/dist/types/common/storage/local-storage.d.ts +6 -0
  114. package/dist/types/common/storage/local-storage.d.ts.map +1 -0
  115. package/dist/types/common/timer/interaction-timer.d.ts +11 -0
  116. package/dist/types/common/timer/interaction-timer.d.ts.map +1 -0
  117. package/dist/types/common/timer/timer.d.ts +12 -0
  118. package/dist/types/common/timer/timer.d.ts.map +1 -0
  119. package/dist/types/common/util/data-size.d.ts +7 -1
  120. package/dist/types/common/util/data-size.d.ts.map +1 -1
  121. package/dist/types/common/util/invoke.d.ts +35 -0
  122. package/dist/types/common/util/invoke.d.ts.map +1 -0
  123. package/dist/types/common/util/obfuscate.d.ts.map +1 -1
  124. package/dist/types/common/window/page-visibility.d.ts +1 -1
  125. package/dist/types/common/window/page-visibility.d.ts.map +1 -1
  126. package/dist/types/features/ajax/aggregate/index.d.ts +2 -2
  127. package/dist/types/features/ajax/aggregate/index.d.ts.map +1 -1
  128. package/dist/types/features/jserrors/aggregate/index.d.ts +2 -2
  129. package/dist/types/features/jserrors/aggregate/index.d.ts.map +1 -1
  130. package/dist/types/features/metrics/aggregate/index.d.ts +2 -2
  131. package/dist/types/features/metrics/aggregate/index.d.ts.map +1 -1
  132. package/dist/types/features/page_action/aggregate/index.d.ts +2 -2
  133. package/dist/types/features/page_action/aggregate/index.d.ts.map +1 -1
  134. package/dist/types/features/page_view_event/aggregate/index.d.ts +2 -2
  135. package/dist/types/features/page_view_event/aggregate/index.d.ts.map +1 -1
  136. package/dist/types/features/page_view_timing/aggregate/index.d.ts +2 -2
  137. package/dist/types/features/page_view_timing/aggregate/index.d.ts.map +1 -1
  138. package/dist/types/features/session_trace/aggregate/index.d.ts +2 -2
  139. package/dist/types/features/session_trace/aggregate/index.d.ts.map +1 -1
  140. package/dist/types/features/spa/aggregate/index.d.ts +2 -2
  141. package/dist/types/features/spa/aggregate/index.d.ts.map +1 -1
  142. package/dist/types/features/utils/agent-session.d.ts +2 -0
  143. package/dist/types/features/utils/agent-session.d.ts.map +1 -0
  144. package/dist/types/features/utils/instrument-base.d.ts.map +1 -1
  145. package/dist/types/features/utils/lazy-loader.d.ts +2 -2
  146. package/dist/types/features/utils/lazy-loader.d.ts.map +1 -1
  147. package/dist/types/loaders/api/api.d.ts.map +1 -1
  148. package/dist/types/loaders/api/apiAsync.d.ts.map +1 -1
  149. package/dist/types/loaders/configure/configure.d.ts.map +1 -1
  150. package/package.json +6 -5
  151. package/src/common/config/state/configurable.js +26 -19
  152. package/src/common/config/state/init.js +7 -0
  153. package/src/common/config/state/runtime.js +22 -27
  154. package/src/common/context/shared-context.js +2 -1
  155. package/src/common/event-emitter/contextual-ee.test.js +2 -2
  156. package/src/common/event-emitter/register-handler.test.js +1 -1
  157. package/src/common/event-listener/event-listener-opts.js +4 -4
  158. package/src/common/harvest/harvest-scheduler.js +12 -8
  159. package/src/common/harvest/harvest.js +3 -1
  160. package/src/common/session/constants.js +3 -0
  161. package/src/common/session/session-entity.js +271 -0
  162. package/src/common/session/session-entity.test.js +317 -0
  163. package/src/common/storage/first-party-cookies.js +31 -0
  164. package/src/common/storage/local-memory.js +30 -0
  165. package/src/common/storage/local-memory.test.js +19 -0
  166. package/src/common/storage/local-storage.js +28 -0
  167. package/src/common/storage/local-storage.test.js +17 -0
  168. package/src/common/timer/interaction-timer.js +75 -0
  169. package/src/common/timer/interaction-timer.test.js +167 -0
  170. package/src/common/timer/timer.js +31 -0
  171. package/src/common/timer/timer.test.js +100 -0
  172. package/src/common/unload/eol.js +1 -1
  173. package/src/common/util/data-size.js +6 -0
  174. package/src/common/util/data-size.test.js +50 -0
  175. package/src/common/util/invoke.js +55 -0
  176. package/src/common/util/invoke.test.js +65 -0
  177. package/src/common/util/obfuscate.js +0 -4
  178. package/src/common/window/page-visibility.js +2 -2
  179. package/src/common/wrap/wrap-timer.js +1 -1
  180. package/src/features/ajax/aggregate/index.js +2 -2
  181. package/src/features/jserrors/aggregate/index.js +3 -3
  182. package/src/features/metrics/aggregate/index.js +18 -3
  183. package/src/features/page_action/aggregate/index.js +2 -2
  184. package/src/features/page_view_event/aggregate/index.js +6 -3
  185. package/src/features/page_view_timing/aggregate/index.js +6 -6
  186. package/src/features/session_trace/aggregate/index.js +2 -2
  187. package/src/features/spa/aggregate/index.js +5 -5
  188. package/src/features/utils/agent-session.js +68 -0
  189. package/src/features/utils/feature-base.js +1 -1
  190. package/src/features/utils/instrument-base.js +5 -2
  191. package/src/features/utils/lazy-loader.js +1 -1
  192. package/src/loaders/agent.js +1 -1
  193. package/src/loaders/api/api.js +2 -5
  194. package/src/loaders/api/apiAsync.js +2 -1
  195. package/src/loaders/configure/configure.js +2 -7
  196. package/dist/cjs/common/util/single.js +0 -23
  197. package/dist/cjs/common/window/session-storage.js +0 -87
  198. package/dist/cjs/features/utils/aggregate-base.js +0 -13
  199. package/dist/esm/common/util/single.js +0 -16
  200. package/dist/esm/common/window/session-storage.js +0 -77
  201. package/dist/esm/features/utils/aggregate-base.js +0 -6
  202. package/dist/types/common/util/single.d.ts +0 -2
  203. package/dist/types/common/util/single.d.ts.map +0 -1
  204. package/dist/types/common/window/session-storage.d.ts +0 -18
  205. package/dist/types/common/window/session-storage.d.ts.map +0 -1
  206. package/dist/types/features/utils/aggregate-base.d.ts +0 -4
  207. package/dist/types/features/utils/aggregate-base.d.ts.map +0 -1
  208. package/src/common/util/single.js +0 -18
  209. package/src/common/window/session-storage.js +0 -75
  210. package/src/features/utils/aggregate-base.js +0 -7
@@ -0,0 +1,214 @@
1
+ import { InteractionTimer } from './interaction-timer';
2
+ jest.useFakeTimers();
3
+ let now;
4
+ beforeEach(() => {
5
+ now = Date.now();
6
+ jest.setSystemTime(now);
7
+ });
8
+ afterEach(() => {
9
+ jest.clearAllMocks();
10
+ });
11
+ describe('constructor', () => {
12
+ test('appropriate properties are set with valid values -- no refresh', () => {
13
+ const timer = new InteractionTimer({
14
+ onEnd: jest.fn()
15
+ }, 100);
16
+ const requiredKeys = ['onEnd', 'initialMs', 'startTimestamp', 'timer'];
17
+ expect(requiredKeys.every(rk => !!timer[rk])).toBeTruthy();
18
+ });
19
+ test('appropriate properties are set with valid values -- with refresh', () => {
20
+ const timer = new InteractionTimer({
21
+ onEnd: jest.fn()
22
+ }, 100);
23
+ const requiredKeys = ['onEnd', 'refresh', 'initialMs', 'startTimestamp', 'timer'];
24
+ expect(requiredKeys.every(rk => !!timer[rk])).toBeTruthy();
25
+ });
26
+ test('required keys are enforced', () => {
27
+ try {
28
+ new InteractionTimer({}, 100);
29
+ } catch (e) {
30
+ expect(e).toEqual(new Error('onEnd handler is required'));
31
+ }
32
+ try {
33
+ new InteractionTimer({
34
+ onEnd: jest.fn()
35
+ });
36
+ } catch (e) {
37
+ expect(e).toEqual(new Error('ms duration is required'));
38
+ }
39
+ });
40
+ test('refresh type timers set event listeners', () => {
41
+ // eslint-disable-next-line
42
+ let ee = {
43
+ on: jest.fn().mockImplementation((evt, cb) => {
44
+ cb([{
45
+ type: 'click'
46
+ }]);
47
+ })
48
+ };
49
+ let it = new InteractionTimer({
50
+ onEnd: jest.fn(),
51
+ onRefresh: jest.fn(),
52
+ ee
53
+ }, 100);
54
+ // scroll, keypress, click
55
+ expect(ee.on).toHaveBeenCalledTimes(1);
56
+ expect(it.onRefresh).toHaveBeenCalledTimes(1);
57
+
58
+ // eslint-disable-next-line
59
+ ee = {
60
+ on: jest.fn().mockImplementation((evt, cb) => {
61
+ cb([{
62
+ type: 'scroll'
63
+ }]);
64
+ })
65
+ };
66
+ it = new InteractionTimer({
67
+ onEnd: jest.fn(),
68
+ onRefresh: jest.fn(),
69
+ ee
70
+ }, 100);
71
+ // scroll, keypress, click
72
+ expect(ee.on).toHaveBeenCalledTimes(1);
73
+ expect(it.onRefresh).toHaveBeenCalledTimes(1);
74
+
75
+ // eslint-disable-next-line
76
+ ee = {
77
+ on: jest.fn().mockImplementation((evt, cb) => {
78
+ cb([{
79
+ type: 'keydown'
80
+ }]);
81
+ })
82
+ };
83
+ it = new InteractionTimer({
84
+ onEnd: jest.fn(),
85
+ onRefresh: jest.fn(),
86
+ ee
87
+ }, 100);
88
+ // scroll, keypress, click
89
+ expect(ee.on).toHaveBeenCalledTimes(1);
90
+ expect(it.onRefresh).toHaveBeenCalledTimes(1);
91
+ const aelSpy = jest.spyOn(document, 'addEventListener');
92
+ // eslint-disable-next-line
93
+ ee = {
94
+ on: jest.fn().mockImplementation((evt, cb) => {
95
+ cb([{
96
+ type: 'keydown'
97
+ }]);
98
+ })
99
+ };
100
+ it = new InteractionTimer({
101
+ onEnd: jest.fn(),
102
+ onRefresh: jest.fn(),
103
+ ee
104
+ }, 100);
105
+ // visibility change
106
+ expect(aelSpy).toHaveBeenCalledTimes(1);
107
+ });
108
+ });
109
+ describe('create()', () => {
110
+ test('Create sets a timeout that can execute a cb', () => {
111
+ const timer = new InteractionTimer({
112
+ onEnd: jest.fn()
113
+ }, 100);
114
+ expect(timer.timer).toBeTruthy();
115
+ expect(timer.onEnd).toHaveBeenCalledTimes(0);
116
+ jest.runOnlyPendingTimers();
117
+ expect(timer.onEnd).toHaveBeenCalledTimes(1);
118
+ });
119
+ test('Create can fallback to use defaults', () => {
120
+ let called = 0;
121
+ const timer1 = new InteractionTimer({
122
+ onEnd: jest.fn()
123
+ }, 100);
124
+ timer1.create();
125
+ const timer2 = new InteractionTimer({
126
+ onEnd: jest.fn()
127
+ }, 100);
128
+ timer2.create(timer2.onEnd);
129
+ const timer3 = new InteractionTimer({
130
+ onEnd: jest.fn()
131
+ }, 100);
132
+ timer3.create(undefined, 100);
133
+ jest.runAllTimers(200);
134
+ expect(timer1.onEnd).toHaveBeenCalledTimes(1);
135
+ expect(timer2.onEnd).toHaveBeenCalledTimes(1);
136
+ expect(timer3.onEnd).toHaveBeenCalledTimes(1);
137
+ });
138
+ });
139
+ describe('refresh()', () => {
140
+ test('refresh prevents the callback from firing', () => {
141
+ const timer = new InteractionTimer({
142
+ onEnd: jest.fn(),
143
+ onRefresh: jest.fn()
144
+ }, 100);
145
+ expect(timer.onEnd).toHaveBeenCalledTimes(0);
146
+ jest.advanceTimersByTime(75);
147
+ expect(timer.onEnd).toHaveBeenCalledTimes(0);
148
+ timer.refresh();
149
+ jest.advanceTimersByTime(75);
150
+ expect(timer.onEnd).toHaveBeenCalledTimes(0);
151
+ jest.advanceTimersByTime(100);
152
+ expect(timer.onEnd).toHaveBeenCalledTimes(1);
153
+ });
154
+ test('refresh executes a callback for consumers', () => {
155
+ const timer = new InteractionTimer({
156
+ onEnd: jest.fn(),
157
+ onRefresh: jest.fn()
158
+ }, 100);
159
+ timer.refresh();
160
+ expect(timer.onRefresh).toHaveBeenCalledTimes(1);
161
+ });
162
+ });
163
+ describe('pause()', () => {
164
+ test('pause prevents the callback from firing', () => {
165
+ const timer = new InteractionTimer({
166
+ onEnd: jest.fn()
167
+ }, 100);
168
+ expect(timer.onEnd).toHaveBeenCalledTimes(0);
169
+ timer.pause();
170
+ jest.advanceTimersByTime(150);
171
+ expect(timer.onEnd).toHaveBeenCalledTimes(0);
172
+ });
173
+ test('pause sets remainingMs timestamp', () => {
174
+ const timer = new InteractionTimer({
175
+ onEnd: jest.fn()
176
+ }, 100);
177
+ expect(timer.remainingMs).toEqual(undefined);
178
+ timer.pause();
179
+ expect(timer.remainingMs).toEqual(timer.initialMs - (now - timer.startTimestamp));
180
+ });
181
+ });
182
+ describe('clear()', () => {
183
+ test('clear prevents the callback from firing and deletes the pointer', () => {
184
+ const timer = new InteractionTimer({
185
+ onEnd: jest.fn()
186
+ }, 100);
187
+ expect(timer.onEnd).toHaveBeenCalledTimes(0);
188
+ timer.clear();
189
+ jest.advanceTimersByTime(150);
190
+ expect(timer.onEnd).toHaveBeenCalledTimes(0);
191
+ expect(timer.timer).toEqual(null);
192
+ });
193
+ });
194
+ describe('end()', () => {
195
+ test('end clears the callback and calls the onEnd callback', () => {
196
+ const timer = new InteractionTimer({
197
+ onEnd: jest.fn()
198
+ }, 100);
199
+ expect(timer.onEnd).toHaveBeenCalledTimes(0);
200
+ timer.end();
201
+ expect(timer.onEnd).toHaveBeenCalledTimes(1);
202
+ expect(timer.timer).toEqual(null);
203
+ });
204
+ });
205
+ describe('isValid', () => {
206
+ test('isValid validates timeStamps', () => {
207
+ const timer = new InteractionTimer({
208
+ onEnd: jest.fn()
209
+ }, 100);
210
+ expect(timer.isValid()).toEqual(true);
211
+ timer.startTimestamp -= 100;
212
+ expect(timer.isValid()).toEqual(false);
213
+ });
214
+ });
@@ -0,0 +1,25 @@
1
+ export class Timer {
2
+ constructor(opts, ms) {
3
+ if (!opts.onEnd) throw new Error('onEnd handler is required');
4
+ if (!ms) throw new Error('ms duration is required');
5
+ this.onEnd = opts.onEnd;
6
+ this.initialMs = ms;
7
+ this.startTimestamp = Date.now();
8
+ this.timer = this.create(this.onEnd, ms);
9
+ }
10
+ create(cb, ms) {
11
+ if (this.timer) this.clear();
12
+ return setTimeout(() => cb ? cb() : this.onEnd(), ms || this.initialMs);
13
+ }
14
+ clear() {
15
+ clearTimeout(this.timer);
16
+ this.timer = null;
17
+ }
18
+ end() {
19
+ this.clear();
20
+ this.onEnd();
21
+ }
22
+ isValid() {
23
+ return this.initialMs - (Date.now() - this.startTimestamp) > 0;
24
+ }
25
+ }
@@ -0,0 +1,103 @@
1
+ import { Timer } from './timer';
2
+ jest.useFakeTimers();
3
+ let now;
4
+ beforeEach(() => {
5
+ now = Date.now();
6
+ jest.setSystemTime(now);
7
+ });
8
+ afterEach(() => {
9
+ jest.clearAllMocks();
10
+ });
11
+ describe('constructor', () => {
12
+ test('appropriate properties are set with valid values -- no refresh', () => {
13
+ const timer = new Timer({
14
+ onEnd: jest.fn()
15
+ }, 100);
16
+ const requiredKeys = ['onEnd', 'initialMs', 'startTimestamp', 'timer'];
17
+ expect(requiredKeys.every(rk => !!timer[rk])).toBeTruthy();
18
+ });
19
+ test('appropriate properties are set with valid values -- with refresh', () => {
20
+ const timer = new Timer({
21
+ onEnd: jest.fn()
22
+ }, 100);
23
+ const requiredKeys = ['onEnd', 'initialMs', 'startTimestamp', 'timer'];
24
+ expect(requiredKeys.every(rk => !!timer[rk])).toBeTruthy();
25
+ });
26
+ test('required keys are enforced', () => {
27
+ try {
28
+ new Timer({}, 100);
29
+ } catch (e) {
30
+ expect(e).toEqual(new Error('onEnd handler is required'));
31
+ }
32
+ try {
33
+ new Timer({
34
+ onEnd: jest.fn()
35
+ });
36
+ } catch (e) {
37
+ expect(e).toEqual(new Error('ms duration is required'));
38
+ }
39
+ });
40
+ });
41
+ describe('create()', () => {
42
+ test('Create sets a timeout that can execute a cb', () => {
43
+ const timer = new Timer({
44
+ onEnd: jest.fn()
45
+ }, 100);
46
+ expect(timer.timer).toBeTruthy();
47
+ expect(timer.onEnd).toHaveBeenCalledTimes(0);
48
+ jest.runOnlyPendingTimers();
49
+ expect(timer.onEnd).toHaveBeenCalledTimes(1);
50
+ });
51
+ test('Create can fallback to use defaults', () => {
52
+ let called = 0;
53
+ const timer1 = new Timer({
54
+ onEnd: jest.fn()
55
+ }, 100);
56
+ timer1.create();
57
+ const timer2 = new Timer({
58
+ onEnd: jest.fn()
59
+ }, 100);
60
+ timer2.create(timer2.onEnd);
61
+ const timer3 = new Timer({
62
+ onEnd: jest.fn()
63
+ }, 100);
64
+ timer3.create(undefined, 100);
65
+ jest.runAllTimers(200);
66
+ expect(timer1.onEnd).toHaveBeenCalledTimes(1);
67
+ expect(timer2.onEnd).toHaveBeenCalledTimes(1);
68
+ expect(timer3.onEnd).toHaveBeenCalledTimes(1);
69
+ });
70
+ });
71
+ describe('clear()', () => {
72
+ test('clear prevents the callback from firing and deletes the pointer', () => {
73
+ const timer = new Timer({
74
+ onEnd: jest.fn()
75
+ }, 100);
76
+ expect(timer.onEnd).toHaveBeenCalledTimes(0);
77
+ timer.clear();
78
+ jest.advanceTimersByTime(150);
79
+ expect(timer.onEnd).toHaveBeenCalledTimes(0);
80
+ expect(timer.timer).toEqual(null);
81
+ });
82
+ });
83
+ describe('end()', () => {
84
+ test('end clears the callback and calls the onEnd callback', () => {
85
+ const timer = new Timer({
86
+ onEnd: jest.fn()
87
+ }, 100);
88
+ expect(timer.onEnd).toHaveBeenCalledTimes(0);
89
+ timer.end();
90
+ expect(timer.onEnd).toHaveBeenCalledTimes(1);
91
+ expect(timer.timer).toEqual(null);
92
+ });
93
+ });
94
+ describe('isValid', () => {
95
+ test('isValid validates timeStamps', () => {
96
+ const timer = new Timer({
97
+ onEnd: jest.fn()
98
+ }, 100);
99
+ expect(timer.isValid()).toEqual(true);
100
+ timer.startTimestamp -= 100;
101
+ expect(timer.isValid()).toEqual(false);
102
+ });
103
+ });
@@ -4,7 +4,7 @@
4
4
  */
5
5
  import { ffVersion } from '../browser-version/firefox-version';
6
6
  import { windowAddEventListener } from '../event-listener/event-listener-opts';
7
- import { single } from '../util/single';
7
+ import { single } from '../util/invoke';
8
8
  import { globalScope, isWorkerScope, isBrowserScope } from '../util/global-scope';
9
9
  import { subscribeToVisibilityChange } from '../window/page-visibility';
10
10
  if (isWorkerScope) {
@@ -4,6 +4,13 @@
4
4
  */
5
5
 
6
6
  import { stringify } from './stringify';
7
+
8
+ /**
9
+ * Returns the size of the provided data. Designed for measuring XHR responses.
10
+ *
11
+ * @param {*} data - The data to be measured.
12
+ * @returns {(number|undefined)} - The size of the data or undefined if size cannot be determined.
13
+ */
7
14
  export function dataSize(data) {
8
15
  if (typeof data === 'string' && data.length) return data.length;
9
16
  if (typeof data !== 'object') return undefined;
@@ -0,0 +1,45 @@
1
+ import { dataSize } from './data-size';
2
+ describe('dataSize', () => {
3
+ test('returns length of string', () => {
4
+ const str = 'Hello, world!';
5
+ expect(dataSize(str)).toBe(str.length);
6
+ });
7
+ test('returns undefined for non-object, number, or empty string', () => {
8
+ expect(dataSize(Infinity)).toBeUndefined();
9
+ expect(dataSize(12345)).toBeUndefined(); // might not actually be by design, but this is how it works today
10
+ expect(dataSize('')).toBeUndefined();
11
+ });
12
+ test('returns byte length of ArrayBuffer object', () => {
13
+ const buffer = new ArrayBuffer(8);
14
+ expect(dataSize(buffer)).toBe(8);
15
+ });
16
+ test('returns size of Blob object', () => {
17
+ const blob = new Blob(['Hello, world!'], {
18
+ type: 'text/plain'
19
+ });
20
+ expect(dataSize(blob)).toBe(blob.size);
21
+ });
22
+ test('returns undefined for FormData object', () => {
23
+ const formData = new FormData();
24
+ expect(dataSize(formData)).toBeUndefined();
25
+ });
26
+ test('returns length of JSON string representation of object', () => {
27
+ const obj = {
28
+ str: 'Hello, world!',
29
+ num: 12345,
30
+ nestedObj: {
31
+ arr: [1, 2, 3]
32
+ }
33
+ };
34
+ const expectedSize = JSON.stringify(obj).length;
35
+ expect(dataSize(obj)).toBe(expectedSize);
36
+ });
37
+ test('returns undefined for object with toJSON method that throws an error', () => {
38
+ const obj = {
39
+ toJSON: () => {
40
+ throw new Error('Error in toJSON');
41
+ }
42
+ };
43
+ expect(dataSize(obj)).toBeUndefined();
44
+ });
45
+ });
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Reduce the invocation of the supplied function so that it is only invoked
3
+ * once within a given timeout.
4
+ *
5
+ * If `wait` is `0`, the function will be invoked during the next tick.
6
+ * If `options.leading` is false or not provided, the function will be invoked
7
+ * N milliseconds after the last invocation of the returned function where
8
+ * N is the `timeout` value.
9
+ * If `options.leading` is true, the function will be invoked immediately upon
10
+ * the first invocation of the returned function and not again for N milliseconds
11
+ * where N is the `timeout` value.
12
+ * @param {function} func Function whose invocation should be limited so it is only invoked
13
+ * once within a given timeout period.
14
+ * @param {number} timeout Time in milliseconds that the function should only be invoked
15
+ * once within.
16
+ * @param {object} options Debounce options
17
+ * @param {boolean} options.leading Forces the function to be invoked on the first
18
+ * invocation of the returned function instead of N milliseconds after the last
19
+ * invocation.
20
+ * @returns {function} A wrapping function that will ensure the provided function
21
+ * is invoked only once within the given timeout.
22
+ */
23
+ export function debounce(func) {
24
+ var _this = this;
25
+ let timeout = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 500;
26
+ let options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
27
+ const leading = options?.leading || false;
28
+ let timer;
29
+ return function () {
30
+ for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
31
+ args[_key] = arguments[_key];
32
+ }
33
+ if (leading && timer === undefined) {
34
+ func.apply(_this, args);
35
+ timer = setTimeout(() => timer = clearTimeout(timer), timeout);
36
+ }
37
+ if (!leading) {
38
+ clearTimeout(timer);
39
+ timer = setTimeout(() => {
40
+ func.apply(_this, args);
41
+ }, timeout);
42
+ }
43
+ };
44
+ }
45
+
46
+ /**
47
+ * Reduce the invocation of the supplied function so that it is only invoked
48
+ * once.
49
+ * @param {function} func Function whose invocation should be limited so it is only invoked
50
+ * once.
51
+ * @returns {function} A wrapping function that will ensure the provided function
52
+ * is invoked only once.
53
+ */
54
+ export function single(func) {
55
+ var _this2 = this;
56
+ let called = false;
57
+ return function () {
58
+ if (!called) {
59
+ called = true;
60
+ for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
61
+ args[_key2] = arguments[_key2];
62
+ }
63
+ func.apply(_this2, args);
64
+ }
65
+ };
66
+ }
@@ -0,0 +1,47 @@
1
+ import { debounce, single } from './invoke';
2
+ jest.useFakeTimers();
3
+ describe('debounce', () => {
4
+ test('should run the supplied function after 100ms', () => {
5
+ let mockCallback = jest.fn();
6
+ let debouncedMethod = debounce(mockCallback, 100);
7
+ execFnTimes(debouncedMethod, 100);
8
+ expect(mockCallback).not.toHaveBeenCalled();
9
+ jest.advanceTimersByTime(1000);
10
+ expect(mockCallback).toHaveBeenCalledTimes(1);
11
+ });
12
+ test('should rerun the supplied function when called again after 100ms', () => {
13
+ let mockCallback = jest.fn();
14
+ let debouncedMethod = debounce(mockCallback, 100);
15
+ execFnTimes(debouncedMethod, 100);
16
+ jest.advanceTimersByTime(200);
17
+ execFnTimes(debouncedMethod, 100);
18
+ jest.advanceTimersByTime(2000);
19
+ expect(mockCallback).toHaveBeenCalledTimes(2);
20
+ });
21
+ test('should run the supplied function on the first event and debounce subsequent events', () => {
22
+ let mockCallback = jest.fn();
23
+ let debouncedMethod = debounce(mockCallback, 100, {
24
+ leading: true
25
+ });
26
+ execFnTimes(debouncedMethod, 100);
27
+ expect(mockCallback).toHaveBeenCalledTimes(1);
28
+ jest.advanceTimersByTime(200);
29
+ expect(mockCallback).toHaveBeenCalledTimes(1);
30
+ execFnTimes(debouncedMethod, 100);
31
+ jest.advanceTimersByTime(200);
32
+ expect(mockCallback).toHaveBeenCalledTimes(2);
33
+ });
34
+ });
35
+ describe('single', () => {
36
+ test('should run the supplied function only once', () => {
37
+ let mockCallback = jest.fn();
38
+ let singleMethod = single(mockCallback, 100);
39
+ execFnTimes(singleMethod, 100);
40
+ expect(mockCallback).toHaveBeenCalledTimes(1);
41
+ });
42
+ });
43
+ function execFnTimes(fn, count) {
44
+ for (let i = 0; i < count; i++) {
45
+ fn();
46
+ }
47
+ }
@@ -7,10 +7,6 @@ var fileProtocolRule = {
7
7
  replacement: 'file://OBFUSCATED'
8
8
  };
9
9
  export class Obfuscator extends SharedContext {
10
- constructor(parent) {
11
- super(parent); // gets any allowed properties from the parent and stores them in `sharedContext`
12
- }
13
-
14
10
  shouldObfuscate() {
15
11
  return getRules(this.sharedContext.agentIdentifier).length > 0;
16
12
  }
@@ -12,7 +12,9 @@ import { documentAddEventListener } from '../event-listener/event-listener-opts'
12
12
  */
13
13
  export function subscribeToVisibilityChange(cb) {
14
14
  let toHiddenOnly = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
15
- documentAddEventListener('visibilitychange', handleVisibilityChange);
15
+ let capture = arguments.length > 2 ? arguments[2] : undefined;
16
+ let abortSignal = arguments.length > 3 ? arguments[3] : undefined;
17
+ documentAddEventListener('visibilitychange', handleVisibilityChange, capture, abortSignal);
16
18
  return;
17
19
  function handleVisibilityChange() {
18
20
  if (toHiddenOnly) {
@@ -25,7 +25,7 @@ const TIMER_FNS = [SET_TIMEOUT, 'setImmediate', SET_INTERVAL, CLEAR_TIMEOUT, 'cl
25
25
  * @param {Object} sharedEE - The shared event emitter on which a new scoped event emitter will be based.
26
26
  * @returns {Object} Scoped event emitter with a debug ID of `timer`.
27
27
  */
28
- //eslint-disable-next-line
28
+ // eslint-disable-next-line
29
29
  export function wrapTimer(sharedEE) {
30
30
  const ee = scopedEE(sharedEE);
31
31
 
@@ -9,12 +9,12 @@ import { handle } from '../../../common/event-emitter/handle';
9
9
  import { getConfigurationValue, getInfo } from '../../../common/config/config';
10
10
  import { HarvestScheduler } from '../../../common/harvest/harvest-scheduler';
11
11
  import { setDenyList, shouldCollectEvent } from '../../../common/deny-list/deny-list';
12
- import { AggregateBase } from '../../utils/aggregate-base';
13
12
  import { FEATURE_NAME } from '../constants';
14
13
  import { drain } from '../../../common/drain/drain';
15
14
  import { FEATURE_NAMES } from '../../../loaders/features/features';
16
15
  import { SUPPORTABILITY_METRIC_CHANNEL } from '../../metrics/constants';
17
- export class Aggregate extends AggregateBase {
16
+ import { FeatureBase } from '../../utils/feature-base';
17
+ export class Aggregate extends FeatureBase {
18
18
  static featureName = FEATURE_NAME;
19
19
  constructor(agentIdentifier, aggregator) {
20
20
  super(agentIdentifier, aggregator, FEATURE_NAME);
@@ -16,11 +16,11 @@ import { mapOwn } from '../../../common/util/map-own';
16
16
  import { getInfo, getConfigurationValue, getRuntime } from '../../../common/config/config';
17
17
  import { now } from '../../../common/timing/now';
18
18
  import { globalScope } from '../../../common/util/global-scope';
19
- import { AggregateBase } from '../../utils/aggregate-base';
20
19
  import { FEATURE_NAME } from '../constants';
21
20
  import { drain } from '../../../common/drain/drain';
22
21
  import { FEATURE_NAMES } from '../../../loaders/features/features';
23
- export class Aggregate extends AggregateBase {
22
+ import { FeatureBase } from '../../utils/feature-base';
23
+ export class Aggregate extends FeatureBase {
24
24
  static featureName = FEATURE_NAME;
25
25
  constructor(agentIdentifier, aggregator) {
26
26
  var _this;
@@ -210,6 +210,7 @@ export class Aggregate extends AggregateBase {
210
210
 
211
211
  // still send EE events for other features such as above, but stop this one from aggregating internal data
212
212
  if (this.blocked) return;
213
+ var att = getInfo(this.agentIdentifier).jsAttributes;
213
214
  if (params._interactionId != null) {
214
215
  // hold on to the error until the interaction finishes
215
216
  this.errorCache[params._interactionId] = this.errorCache[params._interactionId] || [];
@@ -217,7 +218,6 @@ export class Aggregate extends AggregateBase {
217
218
  } else {
218
219
  // store custom attributes
219
220
  var customParams = {};
220
- var att = getInfo(this.agentIdentifier).jsAttributes;
221
221
  mapOwn(att, setCustom);
222
222
  if (customAttributes) {
223
223
  mapOwn(customAttributes, setCustom);
@@ -1,7 +1,6 @@
1
- import { getRuntime } from '../../../common/config/config';
1
+ import { getRuntime, getInfo } from '../../../common/config/config';
2
2
  import { registerHandler } from '../../../common/event-emitter/register-handler';
3
3
  import { HarvestScheduler } from '../../../common/harvest/harvest-scheduler';
4
- import { AggregateBase } from '../../utils/aggregate-base';
5
4
  import { FEATURE_NAME, SUPPORTABILITY_METRIC, CUSTOM_METRIC, SUPPORTABILITY_METRIC_CHANNEL, CUSTOM_METRIC_CHANNEL } from '../constants';
6
5
  import { drain } from '../../../common/drain/drain';
7
6
  import { getFrameworks } from '../../../common/metrics/framework-detection';
@@ -11,7 +10,9 @@ import { VERSION } from "../../../common/constants/env.npm";
11
10
  import { onDOMContentLoaded } from '../../../common/window/load';
12
11
  import { windowAddEventListener } from '../../../common/event-listener/event-listener-opts';
13
12
  import { isBrowserScope } from '../../../common/util/global-scope';
14
- export class Aggregate extends AggregateBase {
13
+ import { FeatureBase } from '../../utils/feature-base';
14
+ import { stringify } from '../../../common/util/stringify';
15
+ export class Aggregate extends FeatureBase {
15
16
  static featureName = FEATURE_NAME;
16
17
  constructor(agentIdentifier, aggregator) {
17
18
  super(agentIdentifier, aggregator, FEATURE_NAME);
@@ -103,6 +104,7 @@ export class Aggregate extends AggregateBase {
103
104
  try {
104
105
  if (this.resourcesSent) return;
105
106
  const agentRuntime = getRuntime(this.agentIdentifier);
107
+ const info = getInfo(this.agentIdentifier);
106
108
  // make sure this only gets sent once
107
109
  this.resourcesSent = true;
108
110
  // differentiate between internal+external and ajax+non-ajax
@@ -128,10 +130,19 @@ export class Aggregate extends AggregateBase {
128
130
  this.storeSupportabilityMetrics("PageSession/Endpoint/".concat(endpoint.charAt(0).toUpperCase() + endpoint.slice(1), "/BytesSent"), agentRuntime.bytesSent[endpoint]);
129
131
  });
130
132
 
133
+ // Capture per-agent query bytes sent for each endpoint (see harvest) and RUM call (see page_view_event aggregator).
134
+ Object.keys(agentRuntime.bytesSent).forEach(endpoint => {
135
+ this.storeSupportabilityMetrics("PageSession/Endpoint/".concat(endpoint.charAt(0).toUpperCase() + endpoint.slice(1), "/QueryBytesSent"), agentRuntime.queryBytesSent[endpoint]);
136
+ });
137
+
131
138
  // Capture metrics for session trace if active (`ptid` is set when returned by replay ingest).
132
139
  if (agentRuntime.ptid) {
133
140
  this.storeSupportabilityMetrics('PageSession/Feature/SessionTrace/DurationMs', Math.round(performance.now()));
134
141
  }
142
+
143
+ // Capture metrics for size of custom attributes
144
+ const jsAttributes = stringify(info.jsAttributes);
145
+ this.storeSupportabilityMetrics('PageSession/Feature/CustomData/Bytes', jsAttributes === '{}' ? 0 : jsAttributes.length);
135
146
  } catch (e) {
136
147
  // do nothing
137
148
  }
@@ -9,11 +9,11 @@ import { registerHandler as register } from '../../../common/event-emitter/regis
9
9
  import { HarvestScheduler } from '../../../common/harvest/harvest-scheduler';
10
10
  import { cleanURL } from '../../../common/url/clean-url';
11
11
  import { getConfigurationValue, getInfo, getRuntime } from '../../../common/config/config';
12
- import { AggregateBase } from '../../utils/aggregate-base';
13
12
  import { FEATURE_NAME } from '../constants';
14
13
  import { drain } from '../../../common/drain/drain';
15
14
  import { isBrowserScope } from '../../../common/util/global-scope';
16
- export class Aggregate extends AggregateBase {
15
+ import { FeatureBase } from '../../utils/feature-base';
16
+ export class Aggregate extends FeatureBase {
17
17
  static featureName = FEATURE_NAME;
18
18
  constructor(agentIdentifier, aggregator) {
19
19
  var _this;