@newrelic/browser-agent 1.239.1 → 1.240.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 (167) hide show
  1. package/README.md +4 -0
  2. package/dist/cjs/cdn/pro.js +3 -2
  3. package/dist/cjs/cdn/spa.js +4 -3
  4. package/dist/cjs/common/config/state/init.js +6 -0
  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/constants/runtime.js +9 -5
  8. package/dist/cjs/common/harvest/harvest.js +5 -3
  9. package/dist/cjs/common/vitals/constants.js +17 -0
  10. package/dist/cjs/common/vitals/cumulative-layout-shift.js +27 -0
  11. package/dist/cjs/common/vitals/first-contentful-paint.js +49 -0
  12. package/dist/cjs/common/vitals/first-input-delay.js +32 -0
  13. package/dist/{esm/features/page_view_timing → cjs/common/vitals}/first-paint.js +19 -17
  14. package/dist/cjs/common/vitals/interaction-to-next-paint.js +29 -0
  15. package/dist/cjs/common/vitals/largest-contentful-paint.js +41 -0
  16. package/dist/cjs/common/vitals/long-task.js +64 -0
  17. package/dist/cjs/common/vitals/time-to-first-byte.js +36 -0
  18. package/dist/cjs/common/vitals/vital-metric.js +71 -0
  19. package/dist/cjs/features/ajax/aggregate/index.js +4 -1
  20. package/dist/cjs/features/metrics/aggregate/index.js +7 -0
  21. package/dist/cjs/features/page_view_event/aggregate/index.js +18 -40
  22. package/dist/cjs/features/page_view_event/constants.js +2 -8
  23. package/dist/cjs/features/page_view_event/instrument/index.js +0 -22
  24. package/dist/cjs/features/page_view_timing/aggregate/index.js +27 -138
  25. package/dist/cjs/features/page_view_timing/instrument/index.js +0 -3
  26. package/dist/cjs/features/session_trace/aggregate/index.js +13 -1
  27. package/dist/cjs/features/spa/aggregate/index.js +4 -3
  28. package/dist/cjs/loaders/agent.js +3 -0
  29. package/dist/cjs/loaders/api/api.js +2 -0
  30. package/dist/cjs/loaders/api/apiAsync.js +4 -2
  31. package/dist/cjs/loaders/configure/configure.js +13 -1
  32. package/dist/cjs/loaders/configure/public-path.js +13 -0
  33. package/dist/cjs/loaders/configure/public-path.npm.js +10 -0
  34. package/dist/esm/cdn/pro.js +2 -1
  35. package/dist/esm/cdn/spa.js +2 -1
  36. package/dist/esm/common/config/state/init.js +6 -0
  37. package/dist/esm/common/constants/env.cdn.js +1 -1
  38. package/dist/esm/common/constants/env.npm.js +1 -1
  39. package/dist/esm/common/constants/runtime.js +5 -3
  40. package/dist/esm/common/harvest/harvest.js +6 -4
  41. package/dist/esm/common/vitals/constants.js +10 -0
  42. package/dist/esm/common/vitals/cumulative-layout-shift.js +20 -0
  43. package/dist/esm/common/vitals/first-contentful-paint.js +41 -0
  44. package/dist/esm/common/vitals/first-input-delay.js +25 -0
  45. package/dist/{cjs/features/page_view_timing → esm/common/vitals}/first-paint.js +12 -24
  46. package/dist/esm/common/vitals/interaction-to-next-paint.js +22 -0
  47. package/dist/esm/common/vitals/largest-contentful-paint.js +34 -0
  48. package/dist/esm/common/vitals/long-task.js +57 -0
  49. package/dist/esm/common/vitals/time-to-first-byte.js +29 -0
  50. package/dist/esm/common/vitals/vital-metric.js +64 -0
  51. package/dist/esm/features/ajax/aggregate/index.js +4 -1
  52. package/dist/esm/features/metrics/aggregate/index.js +8 -1
  53. package/dist/esm/features/page_view_event/aggregate/index.js +20 -42
  54. package/dist/esm/features/page_view_event/constants.js +1 -4
  55. package/dist/esm/features/page_view_event/instrument/index.js +0 -22
  56. package/dist/esm/features/page_view_timing/aggregate/index.js +28 -139
  57. package/dist/esm/features/page_view_timing/instrument/index.js +0 -3
  58. package/dist/esm/features/session_trace/aggregate/index.js +13 -1
  59. package/dist/esm/features/spa/aggregate/index.js +4 -3
  60. package/dist/esm/loaders/agent.js +2 -0
  61. package/dist/esm/loaders/api/api.js +2 -0
  62. package/dist/esm/loaders/api/apiAsync.js +5 -3
  63. package/dist/esm/loaders/configure/configure.js +13 -1
  64. package/dist/esm/loaders/configure/public-path.js +6 -0
  65. package/dist/esm/loaders/configure/public-path.npm.js +3 -0
  66. package/dist/types/common/config/state/init.d.ts.map +1 -1
  67. package/dist/types/common/constants/runtime.d.ts +3 -1
  68. package/dist/types/common/constants/runtime.d.ts.map +1 -1
  69. package/dist/types/common/harvest/harvest.d.ts +0 -1
  70. package/dist/types/common/harvest/harvest.d.ts.map +1 -1
  71. package/dist/types/common/vitals/constants.d.ts +11 -0
  72. package/dist/types/common/vitals/constants.d.ts.map +1 -0
  73. package/dist/types/common/vitals/cumulative-layout-shift.d.ts +3 -0
  74. package/dist/types/common/vitals/cumulative-layout-shift.d.ts.map +1 -0
  75. package/dist/types/common/vitals/first-contentful-paint.d.ts +3 -0
  76. package/dist/types/common/vitals/first-contentful-paint.d.ts.map +1 -0
  77. package/dist/types/common/vitals/first-input-delay.d.ts +3 -0
  78. package/dist/types/common/vitals/first-input-delay.d.ts.map +1 -0
  79. package/dist/types/common/vitals/first-paint.d.ts +3 -0
  80. package/dist/types/common/vitals/first-paint.d.ts.map +1 -0
  81. package/dist/types/common/vitals/interaction-to-next-paint.d.ts +3 -0
  82. package/dist/types/common/vitals/interaction-to-next-paint.d.ts.map +1 -0
  83. package/dist/types/common/vitals/largest-contentful-paint.d.ts +3 -0
  84. package/dist/types/common/vitals/largest-contentful-paint.d.ts.map +1 -0
  85. package/dist/types/common/vitals/long-task.d.ts +3 -0
  86. package/dist/types/common/vitals/long-task.d.ts.map +1 -0
  87. package/dist/types/common/vitals/time-to-first-byte.d.ts +3 -0
  88. package/dist/types/common/vitals/time-to-first-byte.d.ts.map +1 -0
  89. package/dist/types/common/vitals/vital-metric.d.ts +18 -0
  90. package/dist/types/common/vitals/vital-metric.d.ts.map +1 -0
  91. package/dist/types/features/ajax/aggregate/index.d.ts.map +1 -1
  92. package/dist/types/features/metrics/aggregate/index.d.ts.map +1 -1
  93. package/dist/types/features/page_view_event/aggregate/index.d.ts +3 -2
  94. package/dist/types/features/page_view_event/aggregate/index.d.ts.map +1 -1
  95. package/dist/types/features/page_view_event/constants.d.ts +0 -3
  96. package/dist/types/features/page_view_event/constants.d.ts.map +1 -1
  97. package/dist/types/features/page_view_event/instrument/index.d.ts.map +1 -1
  98. package/dist/types/features/page_view_timing/aggregate/index.d.ts +1 -3
  99. package/dist/types/features/page_view_timing/aggregate/index.d.ts.map +1 -1
  100. package/dist/types/features/page_view_timing/instrument/index.d.ts.map +1 -1
  101. package/dist/types/features/session_trace/aggregate/index.d.ts +9 -1
  102. package/dist/types/features/session_trace/aggregate/index.d.ts.map +1 -1
  103. package/dist/types/features/spa/aggregate/index.d.ts.map +1 -1
  104. package/dist/types/loaders/agent.d.ts.map +1 -1
  105. package/dist/types/loaders/api/api.d.ts.map +1 -1
  106. package/dist/types/loaders/api/apiAsync.d.ts.map +1 -1
  107. package/dist/types/loaders/configure/configure.d.ts.map +1 -1
  108. package/dist/types/loaders/configure/public-path.d.ts +2 -0
  109. package/dist/types/loaders/configure/public-path.d.ts.map +1 -0
  110. package/dist/types/loaders/configure/public-path.npm.d.ts +2 -0
  111. package/dist/types/loaders/configure/public-path.npm.d.ts.map +1 -0
  112. package/package.json +1 -1
  113. package/src/cdn/pro.js +2 -0
  114. package/src/cdn/spa.js +2 -0
  115. package/src/common/config/state/init.js +4 -0
  116. package/src/common/constants/runtime.js +7 -3
  117. package/src/common/constants/runtime.test.js +8 -0
  118. package/src/common/harvest/harvest.js +6 -4
  119. package/src/common/harvest/harvest.test.js +17 -0
  120. package/src/common/vitals/__mocks__/web-vitals.js +19 -0
  121. package/src/common/vitals/constants.js +10 -0
  122. package/src/common/vitals/cumulative-layout-shift.js +13 -0
  123. package/src/common/vitals/cumulative-layout-shift.test.js +71 -0
  124. package/src/common/vitals/first-contentful-paint.js +31 -0
  125. package/src/common/vitals/first-contentful-paint.test.js +124 -0
  126. package/src/common/vitals/first-input-delay.js +20 -0
  127. package/src/common/vitals/first-input-delay.test.js +88 -0
  128. package/src/{features/page_view_timing → common/vitals}/first-paint.js +11 -17
  129. package/src/common/vitals/first-paint.test.js +127 -0
  130. package/src/common/vitals/interaction-to-next-paint.js +13 -0
  131. package/src/common/vitals/interaction-to-next-paint.test.js +74 -0
  132. package/src/common/vitals/largest-contentful-paint.js +29 -0
  133. package/src/common/vitals/largest-contentful-paint.test.js +94 -0
  134. package/src/common/vitals/long-task.js +52 -0
  135. package/src/common/vitals/long-task.test.js +122 -0
  136. package/src/common/vitals/time-to-first-byte.js +21 -0
  137. package/src/common/vitals/time-to-first-byte.test.js +147 -0
  138. package/src/common/vitals/vital-metric.js +60 -0
  139. package/src/common/vitals/vital-metric.test.js +171 -0
  140. package/src/features/ajax/aggregate/index.js +5 -1
  141. package/src/features/metrics/aggregate/index.js +6 -1
  142. package/src/features/page_view_event/aggregate/index.js +20 -43
  143. package/src/features/page_view_event/constants.js +0 -3
  144. package/src/features/page_view_event/instrument/index.js +0 -21
  145. package/src/features/page_view_timing/aggregate/index.component-test.js +86 -0
  146. package/src/features/page_view_timing/aggregate/index.js +24 -102
  147. package/src/features/page_view_timing/instrument/index.js +0 -3
  148. package/src/features/session_trace/aggregate/index.js +15 -1
  149. package/src/features/spa/aggregate/index.js +4 -3
  150. package/src/loaders/agent.js +2 -0
  151. package/src/loaders/api/api.js +2 -0
  152. package/src/loaders/api/apiAsync.js +5 -4
  153. package/src/loaders/configure/configure.js +15 -7
  154. package/src/loaders/configure/public-path.js +6 -0
  155. package/src/loaders/configure/public-path.npm.js +4 -0
  156. package/dist/cjs/common/metrics/paint-metrics.js +0 -13
  157. package/dist/cjs/features/page_view_timing/long-tasks.js +0 -75
  158. package/dist/esm/common/metrics/paint-metrics.js +0 -6
  159. package/dist/esm/features/page_view_timing/long-tasks.js +0 -69
  160. package/dist/types/common/metrics/paint-metrics.d.ts +0 -2
  161. package/dist/types/common/metrics/paint-metrics.d.ts.map +0 -1
  162. package/dist/types/features/page_view_timing/first-paint.d.ts +0 -2
  163. package/dist/types/features/page_view_timing/first-paint.d.ts.map +0 -1
  164. package/dist/types/features/page_view_timing/long-tasks.d.ts +0 -2
  165. package/dist/types/features/page_view_timing/long-tasks.d.ts.map +0 -1
  166. package/src/common/metrics/paint-metrics.js +0 -6
  167. package/src/features/page_view_timing/long-tasks.js +0 -60
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/session_trace/aggregate/index.js"],"names":[],"mappings":"AAiCA;IACE,2BAAiC;IAGjC,iEAyHC;IAvHC,kBAA+C;IAK/C,sBAAiD;IACjD,yBAAc;IACd,sBAAe;IACf,8BAAkB;IAClB,iCAAqB;IACrB,wBAA0G;IAC1G,wBAA4G;IAC5G;;4EAEwE;IACxE,kCAAyB;IAGzB,0CAAsC;IAuGxC,sEAcC;IA6CD,oDAOC;IAGD,oCAwBC;IAGD,uEAkBC;IAED,oDAKC;IAED,wBAwBC;IAED,uCAuBC;IAGD,gDASC;IAID,qCAkBC;IAGD,qEAUC;IAGD,mEAUC;IAGD,yBAeC;IAED;;;;OAIG;IACH,2BAHW,MAAM,GACJ,MAAM,CAsBlB;IAGD;;;;;;;;;;MAwBC;IAED,mEA6BC;;CACF;8BAne6B,4BAA4B;6BAH7B,2BAA2B"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/session_trace/aggregate/index.js"],"names":[],"mappings":"AAiCA;IACE,2BAAiC;IAGjC,iEAyHC;IAvHC,kBAA+C;IAK/C,sBAAiD;IACjD,yBAAc;IACd,sBAAe;IACf,8BAAkB;IAClB,iCAAqB;IACrB,wBAA0G;IAC1G,wBAA4G;IAC5G;;4EAEwE;IACxE,kCAAyB;IAGzB,0CAAsC;IAuGxC,sEAcC;IA6CD,oDAOC;IAGD,oCAwBC;IAGD,uEAkBC;IAED,oDAKC;IAED,wBAwBC;IAED,uCAuBC;IAGD,gDASC;IAID,qCAkBC;IAGD,qEAUC;IAGD,mEAUC;IAGD,yBAeC;IAED;;;;OAIG;IACH,2BAHW,MAAM,GACJ,MAAM,CAsBlB;IAGD;;;;;;YA2BM;0GAC8F;;YAE9F;0FAC8E;;YAE9E,4IAA4I;;;;;;MAKjJ;IAED,mEA6BC;;CACF;8BAjf6B,4BAA4B;6BAH7B,2BAA2B"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/spa/aggregate/index.js"],"names":[],"mappings":"AA0BA;IACE,2BAAiC;IACjC,mDA8rBC;IA3rBC;;;;;;;;;;;;;;;;MAgBC;IAED,uBAAsC;CA0qBzC;8BAvsB6B,4BAA4B;2BAJ/B,cAAc"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/spa/aggregate/index.js"],"names":[],"mappings":"AA2BA;IACE,2BAAiC;IACjC,mDA8rBC;IA3rBC;;;;;;;;;;;;;;;;MAgBC;IAED,uBAAsC;CA0qBzC;8BAzsB6B,4BAA4B;2BAJ/B,cAAc"}
@@ -1 +1 @@
1
- {"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["../../../src/loaders/agent.js"],"names":[],"mappings":"AAiBA;;GAEG;AAEH;;;GAGG;AACH;IACE,oDAuBC;IAbC,oCAAsC;IACtC,yCAAiF;IACjF,yBAAkB;IAElB,sCAAsD;IAWxD;;;;;MAOC;IAED,yBAgCC;IAID;;;;;;;;;OASG;IACH,6BAPW;QAAC,MAAM,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAC,QASrF;IAED;;;;OAIG;IACH,0BAFW,MAAM,QAIhB;IAED;;;;OAIG;IACH,eAFa,mBAAmB,CAI/B;CACF;kCA7GY,OAAO,yBAAyB,EAAE,mBAAmB;0BAjBxC,cAAc;2BAQb,gCAAgC"}
1
+ {"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["../../../src/loaders/agent.js"],"names":[],"mappings":"AAmBA;;GAEG;AAEH;;;GAGG;AACH;IACE,oDAuBC;IAbC,oCAAsC;IACtC,yCAAiF;IACjF,yBAAkB;IAElB,sCAAsD;IAWxD;;;;;MAOC;IAED,yBAgCC;IAID;;;;;;;;;OASG;IACH,6BAPW;QAAC,MAAM,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAC,QASrF;IAED;;;;OAIG;IACH,0BAFW,MAAM,QAIhB;IAED;;;;OAIG;IACH,eAFa,mBAAmB,CAI/B;CACF;kCA7GY,OAAO,yBAAyB,EAAE,mBAAmB;0BAjBxC,cAAc;2BAQb,gCAAgC"}
@@ -1 +1 @@
1
- {"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../../../../src/loaders/api/api.js"],"names":[],"mappings":"AAkBA,2CAoBC;AAED;;;;;IA0DE;;;;OAIG;qBAFQ,MAAM;IAWjB;;;;OAIG;iCAFQ,MAAM,GAAC,IAAI;;;;EAwFvB;AAzLD,0CAA0C"}
1
+ {"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../../../../src/loaders/api/api.js"],"names":[],"mappings":"AAkBA,2CAoBC;AAED;;;;;IA0DE;;;;OAIG;qBAFQ,MAAM;IAWjB;;;;OAIG;iCAFQ,MAAM,GAAC,IAAI;;;;EA0FvB;AA3LD,0CAA0C"}
@@ -1 +1 @@
1
- {"version":3,"file":"apiAsync.d.ts","sourceRoot":"","sources":["../../../../src/loaders/api/apiAsync.js"],"names":[],"mappings":"AAUA,mDAiFC"}
1
+ {"version":3,"file":"apiAsync.d.ts","sourceRoot":"","sources":["../../../../src/loaders/api/apiAsync.js"],"names":[],"mappings":"AAUA,mDAkFC"}
@@ -1 +1 @@
1
- {"version":3,"file":"configure.d.ts","sourceRoot":"","sources":["../../../../src/loaders/configure/configure.js"],"names":[],"mappings":"AAMA;;;;;;;;;;EAwCC"}
1
+ {"version":3,"file":"configure.d.ts","sourceRoot":"","sources":["../../../../src/loaders/configure/configure.js"],"names":[],"mappings":"AASA;;;;;;;;;;EA6CC"}
@@ -0,0 +1,2 @@
1
+ export function redefinePublicPath(url: any): void;
2
+ //# sourceMappingURL=public-path.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"public-path.d.ts","sourceRoot":"","sources":["../../../../src/loaders/configure/public-path.js"],"names":[],"mappings":"AAEO,mDAGN"}
@@ -0,0 +1,2 @@
1
+ export function redefinePublicPath(): void;
2
+ //# sourceMappingURL=public-path.npm.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"public-path.npm.d.ts","sourceRoot":"","sources":["../../../../src/loaders/configure/public-path.npm.js"],"names":[],"mappings":"AACO,2CAEN"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@newrelic/browser-agent",
3
- "version": "1.239.1",
3
+ "version": "1.240.0",
4
4
  "private": false,
5
5
  "author": "New Relic Browser Agent Team <browser-agent@newrelic.com>",
6
6
  "description": "New Relic Browser Agent",
package/src/cdn/pro.js CHANGED
@@ -11,6 +11,7 @@ import { Instrument as InstrumentMetrics } from '../features/metrics/instrument'
11
11
  import { Instrument as InstrumentErrors } from '../features/jserrors/instrument'
12
12
  import { Instrument as InstrumentXhr } from '../features/ajax/instrument'
13
13
  import { Instrument as InstrumentSessionTrace } from '../features/session_trace/instrument'
14
+ import { Instrument as InstrumentSessionReplay } from '../features/session_replay/instrument'
14
15
  import { Instrument as InstrumentPageAction } from '../features/page_action/instrument'
15
16
 
16
17
  new Agent({
@@ -18,6 +19,7 @@ new Agent({
18
19
  InstrumentPageViewEvent,
19
20
  InstrumentPageViewTiming,
20
21
  InstrumentSessionTrace,
22
+ InstrumentSessionReplay,
21
23
  InstrumentXhr,
22
24
  InstrumentMetrics,
23
25
  InstrumentPageAction,
package/src/cdn/spa.js CHANGED
@@ -10,6 +10,7 @@ import { Instrument as InstrumentMetrics } from '../features/metrics/instrument'
10
10
  import { Instrument as InstrumentErrors } from '../features/jserrors/instrument'
11
11
  import { Instrument as InstrumentXhr } from '../features/ajax/instrument'
12
12
  import { Instrument as InstrumentSessionTrace } from '../features/session_trace/instrument'
13
+ import { Instrument as InstrumentSessionReplay } from '../features/session_replay/instrument'
13
14
  import { Instrument as InstrumentSpa } from '../features/spa/instrument'
14
15
  import { Instrument as InstrumentPageAction } from '../features/page_action/instrument'
15
16
 
@@ -19,6 +20,7 @@ new Agent({
19
20
  InstrumentPageViewEvent,
20
21
  InstrumentPageViewTiming,
21
22
  InstrumentSessionTrace,
23
+ InstrumentSessionReplay,
22
24
  InstrumentMetrics,
23
25
  InstrumentPageAction,
24
26
  InstrumentErrors,
@@ -8,6 +8,10 @@ const model = () => {
8
8
  maskInputOptions: { password: true }
9
9
  }
10
10
  return {
11
+ proxy: {
12
+ assets: undefined, // if this value is set, it will be used to overwrite the webpack asset path used to fetch assets
13
+ beacon: undefined // likewise for the url to which we send analytics
14
+ },
11
15
  privacy: { cookies_enabled: true }, // *cli - per discussion, default should be true
12
16
  ajax: { deny_list: undefined, block_internal: true, enabled: true, harvestTimeSeconds: 10, autoStart: true },
13
17
  distributed_tracing: {
@@ -44,9 +44,11 @@ export const globalScope = isBrowserScope
44
44
  globalThis
45
45
  ))
46
46
 
47
+ export const initiallyHidden = Boolean(globalScope?.document?.visibilityState === 'hidden')
48
+
47
49
  export const initialLocation = '' + globalScope?.location
48
50
 
49
- export const isiOS = /iPad|iPhone|iPod/.test(navigator.userAgent)
51
+ export const isiOS = /iPad|iPhone|iPod/.test(globalScope.navigator?.userAgent)
50
52
 
51
53
  /**
52
54
  * Shared Web Workers introduced in iOS 16.0+ and n/a in 15.6-
@@ -58,7 +60,7 @@ export const isiOS = /iPad|iPhone|iPod/.test(navigator.userAgent)
58
60
  export const iOSBelow16 = (isiOS && typeof SharedWorker === 'undefined')
59
61
 
60
62
  export const ffVersion = (() => {
61
- const match = navigator.userAgent.match(/Firefox[/\s](\d+\.\d+)/)
63
+ const match = globalScope.navigator?.userAgent?.match(/Firefox[/\s](\d+\.\d+)/)
62
64
  if (Array.isArray(match) && match.length >= 2) {
63
65
  return +match[1]
64
66
  }
@@ -68,4 +70,6 @@ export const ffVersion = (() => {
68
70
 
69
71
  export const isIE = Boolean(isBrowserScope && window.document.documentMode) // deprecated property that only works in IE
70
72
 
71
- export const supportsSendBeacon = !!navigator.sendBeacon
73
+ export const supportsSendBeacon = !!globalScope.navigator?.sendBeacon
74
+
75
+ export const offset = Math.floor(globalScope?.performance?.timeOrigin || globalScope?.performance?.timing?.navigationStart || Date.now())
@@ -106,10 +106,12 @@ test.each([
106
106
  { userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 13.4; rv:109.0) Gecko/20100101 Firefox/114.0', expected: false }
107
107
  ])('should set isiOS to $expected for $userAgent', async ({ userAgent, expected }) => {
108
108
  global.navigator.userAgent = userAgent
109
+ global.window = { navigator: global.navigator, document: true }
109
110
 
110
111
  const runtime = await import('./runtime')
111
112
 
112
113
  expect(runtime.isiOS).toEqual(expected)
114
+ delete global.window
113
115
  })
114
116
 
115
117
  test.each([
@@ -127,12 +129,14 @@ test.each([
127
129
  global.SharedWorker = class SharedWorker {}
128
130
  }
129
131
  global.navigator.userAgent = userAgent
132
+ global.window = { navigator: global.navigator, document: true }
130
133
 
131
134
  const runtime = await import('./runtime')
132
135
 
133
136
  delete global.SharedWorker
134
137
 
135
138
  expect(runtime.iOSBelow16).toEqual(expected)
139
+ delete global.window
136
140
  })
137
141
 
138
142
  test.each([
@@ -144,10 +148,12 @@ test.each([
144
148
  { userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 13.4; rv:109.0) Gecko/20100101 Firefox/114.0', expected: 114 }
145
149
  ])('should set ffVersion to $expected for $userAgent', async ({ userAgent, expected }) => {
146
150
  global.navigator.userAgent = userAgent
151
+ global.window = { navigator: global.navigator, document: true }
147
152
 
148
153
  const runtime = await import('./runtime')
149
154
 
150
155
  expect(runtime.ffVersion).toEqual(expected)
156
+ delete global.window
151
157
  })
152
158
 
153
159
  test('should set supportsSendBeacon to false', async () => {
@@ -161,8 +167,10 @@ test('should set supportsSendBeacon to false', async () => {
161
167
 
162
168
  test('should set supportsSendBeacon to true', async () => {
163
169
  global.navigator.sendBeacon = jest.fn()
170
+ global.window = { navigator: global.navigator, document: true }
164
171
 
165
172
  const runtime = await import('./runtime')
166
173
 
167
174
  expect(runtime.supportsSendBeacon).toEqual(true)
175
+ delete global.window
168
176
  })
@@ -7,7 +7,7 @@ import { obj as encodeObj, param as encodeParam } from '../url/encode'
7
7
  import { stringify } from '../util/stringify'
8
8
  import * as submitData from '../util/submit-data'
9
9
  import { getLocation } from '../url/location'
10
- import { getInfo, getConfigurationValue, getRuntime } from '../config/config'
10
+ import { getInfo, getConfigurationValue, getRuntime, getConfiguration } from '../config/config'
11
11
  import { cleanURL } from '../url/clean-url'
12
12
  import { now } from '../timing/now'
13
13
  import { eventListenerOpts } from '../event-listener/event-listener-opts'
@@ -33,7 +33,6 @@ export class Harvest extends SharedContext {
33
33
 
34
34
  this.tooManyRequestsDelay = getConfigurationValue(this.sharedContext.agentIdentifier, 'harvest.tooManyRequestsDelay') || 60
35
35
  this.obfuscator = new Obfuscator(this.sharedContext)
36
- this.getScheme = () => (getConfigurationValue(this.sharedContext.agentIdentifier, 'ssl') === false) ? 'http' : 'https'
37
36
 
38
37
  this._events = {}
39
38
  }
@@ -96,10 +95,13 @@ export class Harvest extends SharedContext {
96
95
  return false
97
96
  }
98
97
 
98
+ const init = getConfiguration(this.sharedContext.agentIdentifier)
99
+ const protocol = init.ssl === false ? 'http' : 'https'
100
+ const perceviedBeacon = init.proxy.beacon || info.errorBeacon
99
101
  const endpointURLPart = endpoint !== 'rum' ? `/${endpoint}` : ''
100
- let url = `${this.getScheme()}://${info.errorBeacon}${endpointURLPart}/1/${info.licenseKey}`
102
+ let url = `${protocol}://${perceviedBeacon}${endpointURLPart}/1/${info.licenseKey}`
101
103
  if (customUrl) url = customUrl
102
- if (raw) url = `${this.getScheme()}://${info.errorBeacon}/${endpoint}`
104
+ if (raw) url = `${protocol}://${perceviedBeacon}/${endpoint}`
103
105
 
104
106
  const baseParams = !raw && includeBaseParams ? this.baseQueryString() : ''
105
107
  let payloadParams = encodeObj(qs, agentRuntime.maxBytes)
@@ -159,6 +159,10 @@ describe('_send', () => {
159
159
  jest.mocked(configModule.getRuntime).mockReturnValue({
160
160
  maxBytes: Infinity
161
161
  })
162
+ jest.mocked(configModule.getConfiguration).mockReturnValue({
163
+ ssl: undefined,
164
+ proxy: {}
165
+ })
162
166
 
163
167
  spec = {
164
168
  endpoint: faker.datatype.uuid(),
@@ -223,6 +227,19 @@ describe('_send', () => {
223
227
  })
224
228
  })
225
229
 
230
+ test('able to use and send to proxy when defined', () => {
231
+ jest.mocked(configModule.getConfiguration).mockReturnValue({ proxy: { beacon: 'some_other_string' } })
232
+ const result = harvestInstance._send(spec)
233
+
234
+ expect(result).toEqual(true)
235
+ expect(submitMethod).toHaveBeenCalledWith({
236
+ body: JSON.stringify(spec.payload.body),
237
+ headers: [{ key: 'content-type', value: 'text/plain' }],
238
+ sync: undefined,
239
+ url: expect.stringContaining(`https://some_other_string/${spec.endpoint}/1/${licenseKey}?`)
240
+ })
241
+ })
242
+
226
243
  test('should use the custom defined url', () => {
227
244
  spec.customUrl = faker.internet.url()
228
245
 
@@ -0,0 +1,19 @@
1
+ const continuouslyReportMetric = c => {
2
+ let count = 0
3
+ const vital = { value: 1, entries: [{ startTime: 1, name: 'name', size: 1, id: `id${++count}`, url: 'url', element: { tagName: 'tagName' } }], id: 'id' }
4
+ // report a new metric every quarter second
5
+ const callcb = () => {
6
+ setTimeout(() => {
7
+ c(vital)
8
+ callcb()
9
+ }, 250)
10
+ }
11
+ callcb()
12
+ }
13
+
14
+ export const onCLS = c => continuouslyReportMetric(c)
15
+ export const onFCP = c => continuouslyReportMetric(c)
16
+ export const onFID = c => continuouslyReportMetric(c)
17
+ export const onINP = c => continuouslyReportMetric(c)
18
+ export const onLCP = c => continuouslyReportMetric(c)
19
+ export const onTTFB = c => continuouslyReportMetric(c)
@@ -0,0 +1,10 @@
1
+ export const VITAL_NAMES = {
2
+ FIRST_PAINT: 'fp',
3
+ FIRST_CONTENTFUL_PAINT: 'fcp',
4
+ FIRST_INPUT_DELAY: 'fi',
5
+ LARGEST_CONTENTFUL_PAINT: 'lcp',
6
+ CUMULATIVE_LAYOUT_SHIFT: 'cls',
7
+ INTERACTION_TO_NEXT_PAINT: 'inp',
8
+ LONG_TASK: 'lt',
9
+ TIME_TO_FIRST_BYTE: 'ttfb'
10
+ }
@@ -0,0 +1,13 @@
1
+ import { onCLS } from 'web-vitals'
2
+ import { VITAL_NAMES } from './constants'
3
+ import { VitalMetric } from './vital-metric'
4
+ import { isBrowserScope } from '../constants/runtime'
5
+
6
+ export const cumulativeLayoutShift = new VitalMetric(VITAL_NAMES.CUMULATIVE_LAYOUT_SHIFT, (x) => x)
7
+
8
+ if (isBrowserScope) {
9
+ onCLS(({ value, entries }) => {
10
+ if (cumulativeLayoutShift.roundingMethod(value) === cumulativeLayoutShift.current.value) return
11
+ cumulativeLayoutShift.update({ value, entries })
12
+ }, { reportAllChanges: true })
13
+ }
@@ -0,0 +1,71 @@
1
+ afterEach(() => {
2
+ jest.resetModules()
3
+ jest.resetAllMocks()
4
+ jest.clearAllMocks()
5
+ })
6
+
7
+ const getFreshCLSImport = async (codeToRun) => {
8
+ const { cumulativeLayoutShift } = await import('./cumulative-layout-shift')
9
+ codeToRun(cumulativeLayoutShift)
10
+ }
11
+
12
+ describe('cls', () => {
13
+ test('reports cls', (done) => {
14
+ getFreshCLSImport(metric => {
15
+ metric.subscribe(({ value }) => {
16
+ expect(value).toEqual(1)
17
+ done()
18
+ })
19
+ })
20
+ })
21
+ test('does NOT report if not browser scoped', (done) => {
22
+ jest.doMock('../constants/runtime', () => ({
23
+ __esModule: true,
24
+ isBrowserScope: false
25
+ }))
26
+
27
+ getFreshCLSImport(metric => {
28
+ metric.subscribe(() => {
29
+ console.log('should not have reported...')
30
+ expect(1).toEqual(2)
31
+ })
32
+ setTimeout(done, 1000)
33
+ })
34
+ })
35
+ test('multiple subs get same value', done => {
36
+ jest.doMock('../constants/runtime', () => ({
37
+ __esModule: true,
38
+ isBrowserScope: true
39
+ }))
40
+ let sub1, sub2
41
+ getFreshCLSImport(metric => {
42
+ const remove1 = metric.subscribe(({ entries }) => {
43
+ sub1 ??= entries[0].id
44
+ if (sub1 === sub2) { remove1(); remove2(); done() }
45
+ })
46
+
47
+ const remove2 = metric.subscribe(({ entries }) => {
48
+ sub2 ??= entries[0].id
49
+ if (sub1 === sub2) { remove1(); remove2(); done() }
50
+ })
51
+ })
52
+ })
53
+ test('reports only new values', (done) => {
54
+ jest.doMock('../constants/runtime', () => ({
55
+ __esModule: true,
56
+ isBrowserScope: true
57
+ }))
58
+ let triggered = 0
59
+ getFreshCLSImport(metric => {
60
+ metric.subscribe(({ value }) => {
61
+ triggered++
62
+ expect(value).toEqual(1)
63
+ expect(triggered).toEqual(1)
64
+ setTimeout(() => {
65
+ expect(triggered).toEqual(1)
66
+ done()
67
+ }, 1000)
68
+ })
69
+ })
70
+ })
71
+ })
@@ -0,0 +1,31 @@
1
+ import { onFCP } from 'web-vitals'
2
+ // eslint-disable-next-line camelcase
3
+ import { iOSBelow16, initiallyHidden, isBrowserScope } from '../constants/runtime'
4
+ import { VITAL_NAMES } from './constants'
5
+ import { VitalMetric } from './vital-metric'
6
+
7
+ export const firstContentfulPaint = new VitalMetric(VITAL_NAMES.FIRST_CONTENTFUL_PAINT)
8
+
9
+ /* First Contentful Paint - As of WV v3, it still imperfectly tries to detect document vis state asap and isn't supposed to report if page starts hidden. */
10
+ if (isBrowserScope) {
11
+ // eslint-disable-next-line camelcase
12
+ if (iOSBelow16) {
13
+ try {
14
+ if (!initiallyHidden) { // see ios-version.js for detail on this following bug case; tldr: buffered flag doesn't work but getEntriesByType does
15
+ const paintEntries = performance.getEntriesByType('paint')
16
+ paintEntries.forEach(entry => {
17
+ if (entry.name === 'first-contentful-paint') {
18
+ firstContentfulPaint.update({ value: Math.floor(entry.startTime), entries: paintEntries })
19
+ }
20
+ })
21
+ }
22
+ } catch (e) {
23
+ // ignore
24
+ }
25
+ } else {
26
+ onFCP(({ value, entries }) => {
27
+ if (initiallyHidden || firstContentfulPaint.isValid) return
28
+ firstContentfulPaint.update({ value, entries })
29
+ })
30
+ }
31
+ }
@@ -0,0 +1,124 @@
1
+ beforeEach(() => {
2
+ jest.resetModules()
3
+ jest.resetAllMocks()
4
+ jest.clearAllMocks()
5
+ })
6
+
7
+ const getFreshFCPImport = async (codeToRun) => {
8
+ const { firstContentfulPaint } = await import('./first-contentful-paint')
9
+ codeToRun(firstContentfulPaint)
10
+ }
11
+
12
+ describe('fcp', () => {
13
+ test('reports fcp from web-vitals', (done) => {
14
+ getFreshFCPImport(firstContentfulPaint => firstContentfulPaint.subscribe(({ value }) => {
15
+ expect(value).toEqual(1)
16
+ done()
17
+ }))
18
+ })
19
+
20
+ test('reports fcp from paintEntries if ios<16', (done) => {
21
+ jest.doMock('../constants/runtime', () => ({
22
+ __esModule: true,
23
+ iOSBelow16: true,
24
+ initiallyHidden: false,
25
+ isBrowserScope: true
26
+ }))
27
+ global.performance.getEntriesByType = jest.fn(() => [{ name: 'first-contentful-paint', startTime: 1 }])
28
+
29
+ getFreshFCPImport(firstContentfulPaint => firstContentfulPaint.subscribe(({ value }) => {
30
+ expect(value).toEqual(1)
31
+ done()
32
+ }))
33
+ })
34
+
35
+ test('does NOT report if not browser scoped', (done) => {
36
+ jest.doMock('../constants/runtime', () => ({
37
+ __esModule: true,
38
+ isBrowserScope: false
39
+ }))
40
+
41
+ getFreshFCPImport(metric => {
42
+ metric.subscribe(({ value, attrs }) => {
43
+ console.log('should not have reported...')
44
+ expect(1).toEqual(2)
45
+ })
46
+ setTimeout(done, 1000)
47
+ })
48
+ })
49
+
50
+ test('Does NOT report values from paintEntries other than fcp', (done) => {
51
+ jest.doMock('../constants/runtime', () => ({
52
+ __esModule: true,
53
+ iOSBelow16: true,
54
+ initiallyHidden: false,
55
+ isBrowserScope: true
56
+ }))
57
+ global.performance.getEntriesByType = jest.fn(() => [{ name: 'other-timing-name', startTime: 1 }])
58
+
59
+ getFreshFCPImport(firstContentfulPaint => {
60
+ firstContentfulPaint.subscribe(() => {
61
+ console.log('should not have reported')
62
+ expect(1).toEqual(2)
63
+ })
64
+ setTimeout(done, 1000)
65
+ })
66
+ })
67
+
68
+ test('Does NOT report fcp from paintEntries if ios<16 && initiallyHidden', (done) => {
69
+ jest.doMock('../constants/runtime', () => ({
70
+ __esModule: true,
71
+ iOSBelow16: true,
72
+ initiallyHidden: true,
73
+ isBrowserScope: true
74
+ }))
75
+ global.performance.getEntriesByType = jest.fn(() => [{ name: 'first-contentful-paint', startTime: 1 }])
76
+
77
+ getFreshFCPImport(firstContentfulPaint => {
78
+ firstContentfulPaint.subscribe(() => {
79
+ console.log('should not have reported....')
80
+ expect(1).toEqual(2)
81
+ })
82
+ setTimeout(done, 2000)
83
+ })
84
+ })
85
+
86
+ test('multiple subs get same value', done => {
87
+ jest.doMock('../constants/runtime', () => ({
88
+ __esModule: true,
89
+ isBrowserScope: true
90
+ }))
91
+ let sub1, sub2
92
+ getFreshFCPImport(metric => {
93
+ const remove1 = metric.subscribe(({ entries }) => {
94
+ sub1 ??= entries[0].id
95
+ if (sub1 === sub2) { remove1(); remove2(); done() }
96
+ })
97
+
98
+ const remove2 = metric.subscribe(({ entries }) => {
99
+ sub2 ??= entries[0].id
100
+ if (sub1 === sub2) { remove1(); remove2(); done() }
101
+ })
102
+ })
103
+ })
104
+
105
+ test('reports only once', (done) => {
106
+ jest.doMock('../constants/runtime', () => ({
107
+ __esModule: true,
108
+ iOSBelow16: false,
109
+ initiallyHidden: false,
110
+ isBrowserScope: true
111
+ }))
112
+ let triggered = 0
113
+ getFreshFCPImport(firstContentfulPaint => firstContentfulPaint.subscribe(({ value }) => {
114
+ triggered++
115
+ expect(value).toEqual(1)
116
+ expect(triggered).toEqual(1)
117
+ setTimeout(() => {
118
+ expect(triggered).toEqual(1)
119
+ done()
120
+ }, 1000)
121
+ })
122
+ )
123
+ })
124
+ })
@@ -0,0 +1,20 @@
1
+ import { onFID } from 'web-vitals'
2
+ import { VitalMetric } from './vital-metric'
3
+ import { VITAL_NAMES } from './constants'
4
+ import { initiallyHidden, isBrowserScope } from '../constants/runtime'
5
+
6
+ export const firstInputDelay = new VitalMetric(VITAL_NAMES.FIRST_INPUT_DELAY)
7
+
8
+ if (isBrowserScope) {
9
+ onFID(({ value, entries }) => {
10
+ if (initiallyHidden || firstInputDelay.isValid || entries.length === 0) return
11
+
12
+ // CWV will only report one (THE) first-input entry to us; fid isn't reported if there are no user interactions occurs before the *first* page hiding.
13
+ firstInputDelay.update({
14
+ value: entries[0].startTime,
15
+ entries,
16
+ attrs: { type: entries[0].name, fid: Math.round(value) },
17
+ shouldAddConnectionAttributes: true
18
+ })
19
+ })
20
+ }
@@ -0,0 +1,88 @@
1
+ beforeEach(() => {
2
+ jest.resetModules()
3
+ jest.resetAllMocks()
4
+ jest.clearAllMocks()
5
+ })
6
+
7
+ const getFreshFIDImport = async (codeToRun) => {
8
+ const { firstInputDelay } = await import('./first-input-delay')
9
+ codeToRun(firstInputDelay)
10
+ }
11
+
12
+ describe('fid', () => {
13
+ test('reports fcp from web-vitals', (done) => {
14
+ getFreshFIDImport(metric => metric.subscribe(({ value }) => {
15
+ expect(value).toEqual(1)
16
+ done()
17
+ }))
18
+ })
19
+
20
+ test('Does NOT report values if initiallyHidden', (done) => {
21
+ jest.doMock('../constants/runtime', () => ({
22
+ __esModule: true,
23
+ initiallyHidden: true,
24
+ isBrowserScope: true
25
+ }))
26
+
27
+ getFreshFIDImport(metric => {
28
+ metric.subscribe(() => {
29
+ console.log('should not have reported')
30
+ expect(1).toEqual(2)
31
+ })
32
+ setTimeout(done, 1000)
33
+ })
34
+ })
35
+
36
+ test('does NOT report if not browser scoped', (done) => {
37
+ jest.doMock('../constants/runtime', () => ({
38
+ __esModule: true,
39
+ isBrowserScope: false
40
+ }))
41
+
42
+ getFreshFIDImport(metric => {
43
+ metric.subscribe(() => {
44
+ console.log('should not have reported...')
45
+ expect(1).toEqual(2)
46
+ })
47
+ setTimeout(done, 1000)
48
+ })
49
+ })
50
+
51
+ test('multiple subs get same value', done => {
52
+ jest.doMock('../constants/runtime', () => ({
53
+ __esModule: true,
54
+ isBrowserScope: true
55
+ }))
56
+ let sub1, sub2
57
+ getFreshFIDImport(metric => {
58
+ const remove1 = metric.subscribe(({ entries }) => {
59
+ sub1 ??= entries[0].id
60
+ if (sub1 === sub2) { remove1(); remove2(); done() }
61
+ })
62
+
63
+ const remove2 = metric.subscribe(({ entries }) => {
64
+ sub2 ??= entries[0].id
65
+ if (sub1 === sub2) { remove1(); remove2(); done() }
66
+ })
67
+ })
68
+ })
69
+
70
+ test('reports only once', (done) => {
71
+ jest.doMock('../constants/runtime', () => ({
72
+ __esModule: true,
73
+ initiallyHidden: false,
74
+ isBrowserScope: true
75
+ }))
76
+ let triggered = 0
77
+ getFreshFIDImport(metric => metric.subscribe(({ value }) => {
78
+ triggered++
79
+ expect(value).toEqual(1)
80
+ expect(triggered).toEqual(1)
81
+ setTimeout(() => {
82
+ expect(triggered).toEqual(1)
83
+ done()
84
+ }, 1000)
85
+ })
86
+ )
87
+ })
88
+ })