@newrelic/browser-agent 0.1.231 → 1.232.1

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 (256) 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/url/canonicalize-url.js +32 -0
  27. package/dist/cjs/common/url/canonicalize-url.test.js +42 -0
  28. package/dist/cjs/common/url/clean-url.js +10 -3
  29. package/dist/cjs/common/util/data-size.js +6 -0
  30. package/dist/cjs/common/util/data-size.test.js +47 -0
  31. package/dist/cjs/common/util/global-scope.js +4 -2
  32. package/dist/cjs/common/util/invoke.js +73 -0
  33. package/dist/cjs/common/util/invoke.test.js +49 -0
  34. package/dist/cjs/common/util/obfuscate.js +0 -4
  35. package/dist/cjs/common/window/page-visibility.js +3 -1
  36. package/dist/cjs/common/wrap/wrap-fetch.js +1 -3
  37. package/dist/cjs/common/wrap/wrap-function.js +1 -3
  38. package/dist/cjs/common/wrap/wrap-timer.js +1 -1
  39. package/dist/cjs/features/ajax/aggregate/index.js +2 -2
  40. package/dist/cjs/features/ajax/instrument/index.js +1 -1
  41. package/dist/cjs/features/jserrors/aggregate/canonical-function-name.js +12 -4
  42. package/dist/cjs/features/jserrors/aggregate/compute-stack-trace.js +93 -10
  43. package/dist/cjs/features/jserrors/aggregate/compute-stack-trace.test.js +164 -38
  44. package/dist/cjs/features/jserrors/aggregate/index.js +25 -46
  45. package/dist/cjs/features/jserrors/instrument/index.js +0 -2
  46. package/dist/cjs/features/metrics/aggregate/index.js +13 -2
  47. package/dist/cjs/features/page_action/aggregate/index.js +2 -2
  48. package/dist/cjs/features/page_view_event/aggregate/index.js +6 -3
  49. package/dist/cjs/features/page_view_timing/aggregate/index.js +6 -6
  50. package/dist/cjs/features/session_trace/aggregate/index.js +3 -5
  51. package/dist/cjs/features/spa/aggregate/index.js +6 -5
  52. package/dist/cjs/features/utils/agent-session.js +73 -0
  53. package/dist/cjs/features/utils/feature-base.js +1 -1
  54. package/dist/cjs/features/utils/instrument-base.js +7 -2
  55. package/dist/cjs/features/utils/lazy-loader.js +1 -1
  56. package/dist/cjs/loaders/agent.js +1 -1
  57. package/dist/cjs/loaders/api/api.js +1 -4
  58. package/dist/cjs/loaders/api/apiAsync.js +3 -2
  59. package/dist/cjs/loaders/configure/configure.js +0 -6
  60. package/dist/esm/common/config/state/configurable.js +26 -20
  61. package/dist/esm/common/config/state/init.js +8 -0
  62. package/dist/esm/common/config/state/runtime.js +24 -26
  63. package/dist/esm/common/constants/env.cdn.js +1 -1
  64. package/dist/esm/common/constants/env.npm.js +1 -1
  65. package/dist/esm/common/context/shared-context.js +2 -1
  66. package/dist/esm/common/event-emitter/contextual-ee.test.js +2 -2
  67. package/dist/esm/common/event-emitter/register-handler.test.js +1 -1
  68. package/dist/esm/common/event-listener/event-listener-opts.js +4 -2
  69. package/dist/esm/common/harvest/harvest-scheduler.js +14 -11
  70. package/dist/esm/common/harvest/harvest.js +3 -1
  71. package/dist/esm/common/session/constants.js +3 -0
  72. package/dist/esm/common/session/session-entity.js +271 -0
  73. package/dist/esm/common/session/session-entity.test.js +434 -0
  74. package/dist/esm/common/storage/first-party-cookies.js +28 -0
  75. package/dist/esm/common/storage/local-memory.js +28 -0
  76. package/dist/esm/common/storage/local-memory.test.js +18 -0
  77. package/dist/esm/common/storage/local-storage.js +26 -0
  78. package/dist/esm/common/storage/local-storage.test.js +12 -0
  79. package/dist/esm/common/timer/interaction-timer.js +71 -0
  80. package/dist/esm/common/timer/interaction-timer.test.js +214 -0
  81. package/dist/esm/common/timer/timer.js +25 -0
  82. package/dist/esm/common/timer/timer.test.js +103 -0
  83. package/dist/esm/common/unload/eol.js +1 -1
  84. package/dist/esm/common/url/canonicalize-url.js +27 -0
  85. package/dist/esm/common/url/canonicalize-url.test.js +38 -0
  86. package/dist/esm/common/url/clean-url.js +10 -3
  87. package/dist/esm/common/util/data-size.js +7 -0
  88. package/dist/esm/common/util/data-size.test.js +45 -0
  89. package/dist/esm/common/util/global-scope.js +1 -0
  90. package/dist/esm/common/util/invoke.js +66 -0
  91. package/dist/esm/common/util/invoke.test.js +47 -0
  92. package/dist/esm/common/util/obfuscate.js +0 -4
  93. package/dist/esm/common/window/page-visibility.js +3 -1
  94. package/dist/esm/common/wrap/wrap-fetch.js +1 -2
  95. package/dist/esm/common/wrap/wrap-function.js +1 -2
  96. package/dist/esm/common/wrap/wrap-timer.js +1 -1
  97. package/dist/esm/features/ajax/aggregate/index.js +2 -2
  98. package/dist/esm/features/ajax/instrument/index.js +1 -1
  99. package/dist/esm/features/jserrors/aggregate/canonical-function-name.js +12 -4
  100. package/dist/esm/features/jserrors/aggregate/compute-stack-trace.js +93 -10
  101. package/dist/esm/features/jserrors/aggregate/compute-stack-trace.test.js +149 -25
  102. package/dist/esm/features/jserrors/aggregate/index.js +26 -46
  103. package/dist/esm/features/jserrors/instrument/index.js +0 -1
  104. package/dist/esm/features/metrics/aggregate/index.js +14 -3
  105. package/dist/esm/features/page_action/aggregate/index.js +2 -2
  106. package/dist/esm/features/page_view_event/aggregate/index.js +6 -3
  107. package/dist/esm/features/page_view_timing/aggregate/index.js +6 -6
  108. package/dist/esm/features/session_trace/aggregate/index.js +3 -4
  109. package/dist/esm/features/spa/aggregate/index.js +6 -5
  110. package/dist/esm/features/utils/agent-session.js +67 -0
  111. package/dist/esm/features/utils/feature-base.js +1 -1
  112. package/dist/esm/features/utils/instrument-base.js +7 -2
  113. package/dist/esm/features/utils/lazy-loader.js +1 -1
  114. package/dist/esm/loaders/agent.js +1 -1
  115. package/dist/esm/loaders/api/api.js +2 -5
  116. package/dist/esm/loaders/api/apiAsync.js +2 -1
  117. package/dist/esm/loaders/configure/configure.js +2 -8
  118. package/dist/types/common/config/state/configurable.d.ts.map +1 -1
  119. package/dist/types/common/config/state/init.d.ts.map +1 -1
  120. package/dist/types/common/config/state/runtime.d.ts.map +1 -1
  121. package/dist/types/common/context/shared-context.d.ts.map +1 -1
  122. package/dist/types/common/event-listener/event-listener-opts.d.ts +2 -2
  123. package/dist/types/common/event-listener/event-listener-opts.d.ts.map +1 -1
  124. package/dist/types/common/harvest/harvest-scheduler.d.ts +1 -0
  125. package/dist/types/common/harvest/harvest-scheduler.d.ts.map +1 -1
  126. package/dist/types/common/harvest/harvest.d.ts.map +1 -1
  127. package/dist/types/common/session/constants.d.ts +4 -0
  128. package/dist/types/common/session/constants.d.ts.map +1 -0
  129. package/dist/types/common/session/session-entity.d.ts +72 -0
  130. package/dist/types/common/session/session-entity.d.ts.map +1 -0
  131. package/dist/types/common/storage/first-party-cookies.d.ts +8 -0
  132. package/dist/types/common/storage/first-party-cookies.d.ts.map +1 -0
  133. package/dist/types/common/storage/local-memory.d.ts +8 -0
  134. package/dist/types/common/storage/local-memory.d.ts.map +1 -0
  135. package/dist/types/common/storage/local-storage.d.ts +6 -0
  136. package/dist/types/common/storage/local-storage.d.ts.map +1 -0
  137. package/dist/types/common/timer/interaction-timer.d.ts +11 -0
  138. package/dist/types/common/timer/interaction-timer.d.ts.map +1 -0
  139. package/dist/types/common/timer/timer.d.ts +12 -0
  140. package/dist/types/common/timer/timer.d.ts.map +1 -0
  141. package/dist/types/common/url/canonicalize-url.d.ts +9 -0
  142. package/dist/types/common/url/canonicalize-url.d.ts.map +1 -0
  143. package/dist/types/common/url/clean-url.d.ts +7 -1
  144. package/dist/types/common/url/clean-url.d.ts.map +1 -1
  145. package/dist/types/common/util/data-size.d.ts +7 -1
  146. package/dist/types/common/util/data-size.d.ts.map +1 -1
  147. package/dist/types/common/util/global-scope.d.ts +1 -0
  148. package/dist/types/common/util/global-scope.d.ts.map +1 -1
  149. package/dist/types/common/util/invoke.d.ts +35 -0
  150. package/dist/types/common/util/invoke.d.ts.map +1 -0
  151. package/dist/types/common/util/obfuscate.d.ts.map +1 -1
  152. package/dist/types/common/window/page-visibility.d.ts +1 -1
  153. package/dist/types/common/window/page-visibility.d.ts.map +1 -1
  154. package/dist/types/common/wrap/wrap-fetch.d.ts.map +1 -1
  155. package/dist/types/common/wrap/wrap-function.d.ts.map +1 -1
  156. package/dist/types/features/ajax/aggregate/index.d.ts +2 -2
  157. package/dist/types/features/ajax/aggregate/index.d.ts.map +1 -1
  158. package/dist/types/features/jserrors/aggregate/canonical-function-name.d.ts +8 -1
  159. package/dist/types/features/jserrors/aggregate/canonical-function-name.d.ts.map +1 -1
  160. package/dist/types/features/jserrors/aggregate/compute-stack-trace.d.ts +48 -19
  161. package/dist/types/features/jserrors/aggregate/compute-stack-trace.d.ts.map +1 -1
  162. package/dist/types/features/jserrors/aggregate/index.d.ts +14 -5
  163. package/dist/types/features/jserrors/aggregate/index.d.ts.map +1 -1
  164. package/dist/types/features/jserrors/instrument/index.d.ts.map +1 -1
  165. package/dist/types/features/metrics/aggregate/index.d.ts +2 -2
  166. package/dist/types/features/metrics/aggregate/index.d.ts.map +1 -1
  167. package/dist/types/features/page_action/aggregate/index.d.ts +3 -3
  168. package/dist/types/features/page_action/aggregate/index.d.ts.map +1 -1
  169. package/dist/types/features/page_view_event/aggregate/index.d.ts +2 -2
  170. package/dist/types/features/page_view_event/aggregate/index.d.ts.map +1 -1
  171. package/dist/types/features/page_view_timing/aggregate/index.d.ts +2 -2
  172. package/dist/types/features/page_view_timing/aggregate/index.d.ts.map +1 -1
  173. package/dist/types/features/session_trace/aggregate/index.d.ts +2 -2
  174. package/dist/types/features/session_trace/aggregate/index.d.ts.map +1 -1
  175. package/dist/types/features/spa/aggregate/index.d.ts +2 -2
  176. package/dist/types/features/spa/aggregate/index.d.ts.map +1 -1
  177. package/dist/types/features/utils/agent-session.d.ts +2 -0
  178. package/dist/types/features/utils/agent-session.d.ts.map +1 -0
  179. package/dist/types/features/utils/instrument-base.d.ts.map +1 -1
  180. package/dist/types/features/utils/lazy-loader.d.ts +2 -2
  181. package/dist/types/features/utils/lazy-loader.d.ts.map +1 -1
  182. package/dist/types/loaders/api/api.d.ts.map +1 -1
  183. package/dist/types/loaders/api/apiAsync.d.ts.map +1 -1
  184. package/dist/types/loaders/configure/configure.d.ts.map +1 -1
  185. package/package.json +9 -8
  186. package/src/common/config/state/configurable.js +26 -19
  187. package/src/common/config/state/init.js +7 -0
  188. package/src/common/config/state/runtime.js +22 -27
  189. package/src/common/context/shared-context.js +2 -1
  190. package/src/common/event-emitter/contextual-ee.test.js +2 -2
  191. package/src/common/event-emitter/register-handler.test.js +1 -1
  192. package/src/common/event-listener/event-listener-opts.js +4 -4
  193. package/src/common/harvest/harvest-scheduler.js +12 -8
  194. package/src/common/harvest/harvest.js +3 -1
  195. package/src/common/session/constants.js +3 -0
  196. package/src/common/session/session-entity.js +271 -0
  197. package/src/common/session/session-entity.test.js +317 -0
  198. package/src/common/storage/first-party-cookies.js +31 -0
  199. package/src/common/storage/local-memory.js +30 -0
  200. package/src/common/storage/local-memory.test.js +19 -0
  201. package/src/common/storage/local-storage.js +28 -0
  202. package/src/common/storage/local-storage.test.js +17 -0
  203. package/src/common/timer/interaction-timer.js +75 -0
  204. package/src/common/timer/interaction-timer.test.js +167 -0
  205. package/src/common/timer/timer.js +31 -0
  206. package/src/common/timer/timer.test.js +100 -0
  207. package/src/common/unload/eol.js +1 -1
  208. package/src/common/url/canonicalize-url.js +28 -0
  209. package/src/common/url/canonicalize-url.test.js +34 -0
  210. package/src/common/url/clean-url.js +10 -3
  211. package/src/common/util/data-size.js +6 -0
  212. package/src/common/util/data-size.test.js +50 -0
  213. package/src/common/util/global-scope.js +2 -0
  214. package/src/common/util/invoke.js +55 -0
  215. package/src/common/util/invoke.test.js +65 -0
  216. package/src/common/util/obfuscate.js +0 -4
  217. package/src/common/window/page-visibility.js +2 -2
  218. package/src/common/wrap/wrap-fetch.js +1 -2
  219. package/src/common/wrap/wrap-function.js +1 -2
  220. package/src/common/wrap/wrap-timer.js +1 -1
  221. package/src/features/ajax/aggregate/index.js +2 -2
  222. package/src/features/ajax/instrument/index.js +1 -1
  223. package/src/features/jserrors/aggregate/canonical-function-name.js +12 -4
  224. package/src/features/jserrors/aggregate/compute-stack-trace.js +85 -11
  225. package/src/features/jserrors/aggregate/compute-stack-trace.test.js +141 -24
  226. package/src/features/jserrors/aggregate/index.js +24 -50
  227. package/src/features/jserrors/instrument/index.js +0 -1
  228. package/src/features/metrics/aggregate/index.js +18 -3
  229. package/src/features/page_action/aggregate/index.js +2 -2
  230. package/src/features/page_view_event/aggregate/index.js +6 -3
  231. package/src/features/page_view_timing/aggregate/index.js +6 -6
  232. package/src/features/session_trace/aggregate/index.js +3 -4
  233. package/src/features/spa/aggregate/index.js +5 -5
  234. package/src/features/utils/agent-session.js +68 -0
  235. package/src/features/utils/feature-base.js +1 -1
  236. package/src/features/utils/instrument-base.js +5 -2
  237. package/src/features/utils/lazy-loader.js +1 -1
  238. package/src/loaders/agent.js +1 -1
  239. package/src/loaders/api/api.js +2 -5
  240. package/src/loaders/api/apiAsync.js +2 -1
  241. package/src/loaders/configure/configure.js +2 -7
  242. package/dist/cjs/common/util/single.js +0 -23
  243. package/dist/cjs/common/window/session-storage.js +0 -87
  244. package/dist/cjs/features/utils/aggregate-base.js +0 -13
  245. package/dist/esm/common/util/single.js +0 -16
  246. package/dist/esm/common/window/session-storage.js +0 -77
  247. package/dist/esm/features/utils/aggregate-base.js +0 -6
  248. package/dist/types/common/util/single.d.ts +0 -2
  249. package/dist/types/common/util/single.d.ts.map +0 -1
  250. package/dist/types/common/window/session-storage.d.ts +0 -18
  251. package/dist/types/common/window/session-storage.d.ts.map +0 -1
  252. package/dist/types/features/utils/aggregate-base.d.ts +0 -4
  253. package/dist/types/features/utils/aggregate-base.d.ts.map +0 -1
  254. package/src/common/util/single.js +0 -18
  255. package/src/common/window/session-storage.js +0 -75
  256. package/src/features/utils/aggregate-base.js +0 -7
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@newrelic/browser-agent",
3
- "version": "0.1.231",
3
+ "version": "1.232.1",
4
4
  "private": false,
5
5
  "author": "New Relic Browser Agent Team <browser-agent@newrelic.com>",
6
6
  "description": "Tests for the New Relic JavaScript agent",
@@ -108,6 +108,7 @@
108
108
  "cdn:build:current": "npm run cdn:fresh-build && PUBLISH=CURRENT npm run cdn:webpack && npm run cdn:cleanup",
109
109
  "cdn:build:nr": "npm run cdn:fresh-build && PUBLISH=PROD SOURCEMAPS=false npm run cdn:webpack && npm run cdn:cleanup",
110
110
  "cdn:build:pr": "npm run cdn:fresh-build && PUBLISH=PR npm run cdn:webpack && npm run cdn:cleanup",
111
+ "cdn:build:experiment": "npm run cdn:fresh-build && PUBLISH=EXPERIMENT npm run cdn:webpack && npm run cdn:cleanup",
111
112
  "cdn:cleanup": "node ./tools/scripts/post-build-cleanup",
112
113
  "cdn:clone": "node ./tools/scripts/clone",
113
114
  "cdn:fresh-build": "rm -rf ./build",
@@ -115,17 +116,16 @@
115
116
  "cdn:watch:extension": "jung -r ./src -F '.*\\.test\\.js' --run -- npm run cdn:build:extension",
116
117
  "cdn:webpack": "npx webpack --progress --config ./webpack.config.js",
117
118
  "packages:bundle": "webpack -c tools/jil/webpack.modular.js",
118
- "test-server": "node ./tools/jil/bin/server",
119
- "sauce:connect": "node ./tools/wdio/bin/sauce",
120
- "sauce:get-browsers": "node ./tools/jil/util/sauce-browsers && node ./tools/wdio/util/sauce-browsers",
119
+ "test-server": "node ./tools/wdio/bin/server",
120
+ "sauce:connect": "node ./tools/saucelabs/bin.mjs",
121
+ "sauce:get-browsers": "node ./tools/browsers-lists/sauce-browsers.mjs",
121
122
  "tools:test-builds": "npm --prefix ./tools/test-builds run build-all",
122
123
  "third-party-updates": "oss third-party manifest --includeOptDeps && oss third-party notices --includeOptDeps",
123
124
  "prepare": "husky install",
124
125
  "npm:build:esm": "npx babel --env-name npm-esm --out-dir dist/esm --out-file-extension .js ./src",
125
126
  "npm:build:cjs": "npx babel --env-name npm-cjs --out-dir dist/cjs --out-file-extension .js ./src",
126
127
  "npm:build:types": "npx tsc -b",
127
- "npm:pack": "mkdir -p temp && export PKG_NAME=$(npm pack --pack-destination temp) && echo ./temp/$PKG_NAME",
128
- "sync:version": "node tools/scripts/sync-version.js"
128
+ "npm:pack": "mkdir -p temp && export PKG_NAME=$(npm pack --pack-destination temp) && echo ./temp/$PKG_NAME"
129
129
  },
130
130
  "config": {
131
131
  "unsafe-perm": true
@@ -136,7 +136,6 @@
136
136
  },
137
137
  "dependencies": {
138
138
  "core-js": "^3.26.0",
139
- "lodash._slice": "^2.4.1",
140
139
  "web-vitals": "^3.1.0"
141
140
  },
142
141
  "devDependencies": {
@@ -146,6 +145,7 @@
146
145
  "@babel/eslint-parser": "^7.19.1",
147
146
  "@babel/plugin-syntax-import-assertions": "^7.20.0",
148
147
  "@babel/preset-env": "^7.20.2",
148
+ "@babel/register": "^7.21.0",
149
149
  "@faker-js/faker": "^7.6.0",
150
150
  "@fastify/cors": "^8.2.0",
151
151
  "@fastify/multipart": "^7.3.0",
@@ -183,6 +183,7 @@
183
183
  "fs-extra": "^11.1.0",
184
184
  "function-bind": "^1.1.1",
185
185
  "glob": "^7.0.5",
186
+ "gzip-size": "^7.0.0",
186
187
  "html-webpack-plugin": "^5.5.0",
187
188
  "husky": "^8.0.0",
188
189
  "jest": "^28.1.1",
@@ -223,4 +224,4 @@
223
224
  "README.md",
224
225
  "types.ts"
225
226
  ]
226
- }
227
+ }
@@ -3,24 +3,31 @@ import { warn } from '../../util/console'
3
3
 
4
4
  export class Configurable {
5
5
  constructor (obj, model) {
6
- try {
7
- if (!obj || typeof obj !== 'object') return warn('New setting a Configurable requires an object as input')
8
- if (!model || typeof model !== 'object') return warn('Setting a Configurable requires a model to set its initial properties')
9
- Object.assign(this, model)
10
- Object.entries(obj).forEach(([key, value]) => {
11
- const frozenAttrs = getFrozenAttributes(key)
12
- if (frozenAttrs.length && value && typeof value === 'object') {
13
- frozenAttrs.forEach(attr => {
14
- if (attr in value) {
15
- warn(`"${attr}" is a protected attribute and can not be changed in feature ${key}. It will have no effect.`)
16
- delete value[attr]
17
- }
18
- })
19
- }
20
- this[key] = value
21
- })
22
- } catch (err) {
23
- warn('An error occured while setting a Configurable', err)
24
- }
6
+ Object.assign(this, setValues(obj, model))
7
+ }
8
+ }
9
+
10
+ function setValues (obj, model) {
11
+ const state = {}
12
+ try {
13
+ if (!obj || typeof obj !== 'object') return warn('New setting a Configurable requires an object as input')
14
+ if (!model || typeof model !== 'object') return warn('Setting a Configurable requires a model to set its initial properties')
15
+ Object.assign(state, model)
16
+ Object.entries(obj).forEach(([key, value]) => {
17
+ if (!Object.keys(model).includes(key)) return
18
+ const frozenAttrs = getFrozenAttributes(key)
19
+ if (frozenAttrs.length && value && typeof value === 'object') {
20
+ frozenAttrs.forEach(attr => {
21
+ if (attr in value) {
22
+ warn(`"${attr}" is a protected attribute and can not be changed in feature ${key}. It will have no effect.`)
23
+ delete value[attr]
24
+ }
25
+ })
26
+ }
27
+ state[key] = value
28
+ })
29
+ return state
30
+ } catch (err) {
31
+ warn('An error occured while setting a Configurable', err)
25
32
  }
26
33
  }
@@ -1,4 +1,6 @@
1
1
 
2
+ import { DEFAULT_EXPIRES_MS, DEFAULT_INACTIVE_MS } from '../../session/constants'
3
+ import { globalScope } from '../../util/global-scope'
2
4
  import { gosNREUMInitializedAgents } from '../../window/nreum'
3
5
  import { Configurable } from './configurable'
4
6
 
@@ -13,6 +15,11 @@ const model = {
13
15
  cors_use_tracecontext_headers: undefined,
14
16
  allowed_origins: undefined
15
17
  },
18
+ session: {
19
+ domain: undefined, // used by first party cookies to set the top-level domain
20
+ expiresMs: DEFAULT_EXPIRES_MS,
21
+ inactiveMs: DEFAULT_INACTIVE_MS
22
+ },
16
23
  ssl: undefined,
17
24
  obfuscate: undefined,
18
25
  jserrors: { enabled: true, harvestTimeSeconds: 10 },
@@ -1,35 +1,30 @@
1
1
  import * as userAgent from '../../util/user-agent'
2
2
  import { Configurable } from './configurable'
3
3
  import { gosNREUMInitializedAgents } from '../../window/nreum'
4
- import { getCurrentSessionIdOrMakeNew } from '../../window/session-storage'
5
- import { getConfigurationValue } from '../config'
6
4
  import { globalScope } from '../../util/global-scope'
7
5
  import { BUILD_ENV, DIST_METHOD, VERSION } from '../../constants/env'
8
6
 
9
- const model = agentId => {
10
- return {
11
- buildEnv: BUILD_ENV,
12
- bytesSent: {},
13
- customTransaction: undefined,
14
- disabled: false,
15
- distMethod: DIST_METHOD,
16
- isolatedBacklog: false,
17
- loaderType: undefined,
18
- maxBytes: 30000,
19
- // The "timeOrigin" property is the new standard timestamp property shared across main frame and workers, but is not supported in some early Safari browsers (safari<15) + IE
20
- // ingest expects an integer value, and timeOrigin can return a float.
21
- offset: Math.floor(globalScope?.performance?.timeOrigin || globalScope?.performance?.timing?.navigationStart || Date.now()),
22
- onerror: undefined,
23
- origin: '' + globalScope.location,
24
- ptid: undefined,
25
- releaseIds: {},
26
- sessionId: getConfigurationValue(agentId, 'privacy.cookies_enabled') == true
27
- ? getCurrentSessionIdOrMakeNew()
28
- : null, // if cookies (now session tracking) is turned off or can't get session ID, this is null
29
- xhrWrappable: typeof globalScope.XMLHttpRequest?.prototype?.addEventListener === 'function',
30
- userAgent,
31
- version: VERSION
32
- }
7
+ const model = {
8
+ buildEnv: BUILD_ENV,
9
+ bytesSent: {}, // Used for SM to capture body bytes sent per endpoint
10
+ queryBytesSent: {}, // Used for SM to capture query parameter bytes sent per endpoint
11
+ customTransaction: undefined,
12
+ disabled: false,
13
+ distMethod: DIST_METHOD,
14
+ isolatedBacklog: false,
15
+ loaderType: undefined,
16
+ maxBytes: 30000,
17
+ // The "timeOrigin" property is the new standard timestamp property shared across main frame and workers, but is not supported in some early Safari browsers (safari<15) + IE
18
+ // ingest expects an integer value, and timeOrigin can return a float.
19
+ offset: Math.floor(globalScope?.performance?.timeOrigin || globalScope?.performance?.timing?.navigationStart || Date.now()),
20
+ onerror: undefined,
21
+ origin: '' + globalScope.location,
22
+ ptid: undefined,
23
+ releaseIds: {},
24
+ session: undefined,
25
+ xhrWrappable: typeof globalScope.XMLHttpRequest?.prototype?.addEventListener === 'function',
26
+ userAgent,
27
+ version: VERSION
33
28
  }
34
29
 
35
30
  const _cache = {}
@@ -42,6 +37,6 @@ export function getRuntime (id) {
42
37
 
43
38
  export function setRuntime (id, obj) {
44
39
  if (!id) throw new Error('All runtime objects require an agent identifier!')
45
- _cache[id] = new Configurable(obj, model(id))
40
+ _cache[id] = new Configurable(obj, model)
46
41
  gosNREUMInitializedAgents(id, _cache[id], 'runtime')
47
42
  }
@@ -2,7 +2,8 @@
2
2
  import { warn } from '../util/console'
3
3
 
4
4
  const model = {
5
- agentIdentifier: ''
5
+ agentIdentifier: '',
6
+ ee: undefined
6
7
  }
7
8
 
8
9
  export class SharedContext {
@@ -276,7 +276,7 @@ describe('event-emitter emit', () => {
276
276
 
277
277
  expect(mockScopeListener).toHaveBeenCalledTimes(1)
278
278
  expect(mockListener).not.toHaveBeenCalled()
279
- expect(scopeEE.backlog['feature']).toEqual(expect.arrayContaining([
279
+ expect(scopeEE.backlog.feature).toEqual(expect.arrayContaining([
280
280
  expect.arrayContaining([
281
281
  scopeEE,
282
282
  eventType,
@@ -284,7 +284,7 @@ describe('event-emitter emit', () => {
284
284
  {}
285
285
  ])
286
286
  ]))
287
- expect(ee.backlog['feature']).toEqual(scopeEE.backlog['feature'])
287
+ expect(ee.backlog.feature).toEqual(scopeEE.backlog.feature)
288
288
  })
289
289
  })
290
290
 
@@ -19,7 +19,7 @@ test('should default group to "feature"', async () => {
19
19
 
20
20
  registerHandler(eventType, eventHandler)
21
21
 
22
- expect(registerHandler.handlers['feature']).toEqual(expect.objectContaining({
22
+ expect(registerHandler.handlers.feature).toEqual(expect.objectContaining({
23
23
  [eventType]: [expect.arrayContaining([
24
24
  handleEE, eventHandler
25
25
  ])]
@@ -32,10 +32,10 @@ export function eventListenerOpts (useCapture, abortSignal) {
32
32
  }
33
33
 
34
34
  /** Do not use this within the worker context. */
35
- export function windowAddEventListener (event, listener, capture = false) {
36
- window.addEventListener(event, listener, eventListenerOpts(capture))
35
+ export function windowAddEventListener (event, listener, capture = false, abortSignal) {
36
+ window.addEventListener(event, listener, eventListenerOpts(capture, abortSignal))
37
37
  }
38
38
  /** Do not use this within the worker context. */
39
- export function documentAddEventListener (event, listener, capture = false) {
40
- document.addEventListener(event, listener, eventListenerOpts(capture))
39
+ export function documentAddEventListener (event, listener, capture = false, abortSignal) {
40
+ document.addEventListener(event, listener, eventListenerOpts(capture, abortSignal))
41
41
  }
@@ -23,13 +23,18 @@ export class HarvestScheduler extends SharedContext {
23
23
 
24
24
  this.harvest = new Harvest(this.sharedContext)
25
25
 
26
- subscribeToEOL(() => {
27
- if (this.aborted) return
26
+ // unload if EOL mechanism fires
27
+ subscribeToEOL(this.unload.bind(this), getConfigurationValue(this.sharedContext.agentIdentifier, 'allow_bfcache')) // TO DO: remove feature flag after rls stable
28
28
 
29
- // If opts.onUnload is defined, these are special actions to execute before attempting to send the final payload.
30
- if (this.opts.onUnload) this.opts.onUnload()
31
- this.runHarvest({ unload: true })
32
- }, getConfigurationValue(this.sharedContext.agentIdentifier, 'allow_bfcache')) // TO DO: remove feature flag after rls stable
29
+ // unload if session resets
30
+ this.sharedContext?.ee.on('session-reset', this.unload.bind(this))
31
+ }
32
+
33
+ unload () {
34
+ if (this.aborted) return
35
+ // If opts.onUnload is defined, these are special actions to execute before attempting to send the final payload.
36
+ if (this.opts.onUnload) this.opts.onUnload()
37
+ this.runHarvest({ unload: true })
33
38
  }
34
39
 
35
40
  startTimer (interval, initialDelay) {
@@ -76,8 +81,7 @@ export class HarvestScheduler extends SharedContext {
76
81
  }
77
82
  }
78
83
  } else {
79
- const runAfterSending = opts?.unload ? undefined : onHarvestFinished // don't bother running onFinish handler if this is the final harvest
80
- this.harvest.sendX(this.endpoint, opts, runAfterSending)
84
+ this.harvest.sendX(this.endpoint, opts, onHarvestFinished)
81
85
  }
82
86
 
83
87
  if (this.started) {
@@ -118,6 +118,8 @@ export class Harvest extends SharedContext {
118
118
 
119
119
  // Get bytes harvested per endpoint as a supportability metric. See metrics aggregator (on unload).
120
120
  agentRuntime.bytesSent[endpoint] = (agentRuntime.bytesSent[endpoint] || 0) + body?.length || 0
121
+ // Get query bytes harvested per endpoint as a supportability metric. See metrics aggregator (on unload).
122
+ agentRuntime.queryBytesSent[endpoint] = (agentRuntime.queryBytesSent[endpoint] || 0) + fullUrl.split('?').slice(-1)[0]?.length || 0
121
123
 
122
124
  /* Since workers don't support sendBeacon right now, or Image(), they can only use XHR method.
123
125
  Because they still do permit synch XHR, the idea is that at final harvest time (worker is closing),
@@ -167,7 +169,7 @@ export class Harvest extends SharedContext {
167
169
  encodeParam('ct', runtime.customTransaction),
168
170
  '&rst=' + now(),
169
171
  '&ck=0', // ck param DEPRECATED - still expected by backend
170
- '&s=' + (runtime.sessionId || '0'), // the 0 id encaps all untrackable and default traffic
172
+ '&s=' + (runtime.session?.value || '0'), // the 0 id encaps all untrackable and default traffic
171
173
  encodeParam('ref', ref),
172
174
  encodeParam('ptid', (runtime.ptid ? '' + runtime.ptid : ''))
173
175
  ].join(''))
@@ -0,0 +1,3 @@
1
+ export const PREFIX = 'NRBA'
2
+ export const DEFAULT_EXPIRES_MS = 14400000
3
+ export const DEFAULT_INACTIVE_MS = 1800000
@@ -0,0 +1,271 @@
1
+ import { generateRandomHexString } from '../ids/unique-id'
2
+ import { warn } from '../util/console'
3
+ import { stringify } from '../util/stringify'
4
+ import { ee } from '../event-emitter/contextual-ee'
5
+ import { Timer } from '../timer/timer'
6
+ import { isBrowserScope } from '../util/global-scope'
7
+ import { DEFAULT_EXPIRES_MS, DEFAULT_INACTIVE_MS, PREFIX } from './constants'
8
+ import { LocalMemory } from '../storage/local-memory'
9
+ import { InteractionTimer } from '../timer/interaction-timer'
10
+ import { wrapEvents } from '../wrap'
11
+ import { Configurable } from '../config/state/configurable'
12
+ import { handle } from '../event-emitter/handle'
13
+ import { SUPPORTABILITY_METRIC_CHANNEL } from '../../features/metrics/constants'
14
+ import { FEATURE_NAMES } from '../../loaders/features/features'
15
+
16
+ const model = {
17
+ value: '',
18
+ inactiveAt: 0,
19
+ expiresAt: 0,
20
+ updatedAt: Date.now(),
21
+ sessionReplayActive: false,
22
+ sessionTraceActive: false,
23
+ custom: {}
24
+ }
25
+
26
+ export class SessionEntity {
27
+ /**
28
+ * Create a self-managing Session Entity. This entity is scoped to the agent identifier which triggered it, allowing for multiple simultaneous session objects to exist.
29
+ * There is one "namespace" an agent can store data in LS -- NRBA_{key}. If there are two agents on one page, and they both use the same key, they could overwrite each other since they would both use the same namespace in LS by default.
30
+ * The value can be overridden in the constructor, but will default to a unique 16 character hex string
31
+ * expiresMs and inactiveMs are used to "expire" the session, but can be overridden in the constructor. Pass 0 to disable expiration timers.
32
+ */
33
+ constructor (opts) {
34
+ this.setup(opts)
35
+ }
36
+
37
+ setup ({ agentIdentifier, key, value = generateRandomHexString(16), expiresMs = DEFAULT_EXPIRES_MS, inactiveMs = DEFAULT_INACTIVE_MS, storageAPI = new LocalMemory() }) {
38
+ if (!agentIdentifier || !key) throw new Error('Missing Required Fields')
39
+ if (!isBrowserScope) this.storage = new LocalMemory()
40
+ else this.storage = storageAPI
41
+
42
+ this.sync(model)
43
+
44
+ this.agentIdentifier = agentIdentifier
45
+
46
+ // key is intended to act as the k=v pair
47
+ this.key = key
48
+ // value is intended to act as the primary value of the k=v pair
49
+ this.value = value
50
+
51
+ this.expiresMs = expiresMs
52
+ this.inactiveMs = inactiveMs
53
+
54
+ this.ee = ee.get(agentIdentifier)
55
+
56
+ wrapEvents(this.ee)
57
+
58
+ // the first time the session entity class is instantiated, we check the storage API for an existing
59
+ // object. If it exists, the values inside the object are used to inform the timers that run locally.
60
+ // if the initial read is empty, it allows us to set a "fresh" "new" session immediately.
61
+ // the local timers are used after the session is running to "expire" the session, allowing for pausing timers etc.
62
+ // the timestamps stored in the storage API can be checked at initial run, and when the page is restored, otherwise we lean
63
+ // on the local timers to expire the session
64
+ const initialRead = this.read()
65
+
66
+ // the set-up of the timer used to expire the session "naturally" at a certain time
67
+ // this gets ignored if the value is falsy, allowing for session entities that do not expire
68
+ if (expiresMs) {
69
+ this.expiresAt = initialRead?.expiresAt || this.getFutureTimestamp(expiresMs)
70
+ this.expiresTimer = new Timer({
71
+ // When the inactive timer ends, collect a SM and reset the session
72
+ onEnd: () => {
73
+ this.collectSM('expired', this)
74
+ this.collectSM('duration', this)
75
+ this.reset()
76
+ }
77
+ }, this.expiresAt - Date.now())
78
+ } else {
79
+ this.expiresAt = Infinity
80
+ }
81
+
82
+ // the set-up of the timer used to expire the session due to "inactivity" at a certain time
83
+ // this gets ignored if the value is falsy, allowing for session entities that do not expire
84
+ // this gets "refreshed" when "activity" is observed
85
+ if (inactiveMs) {
86
+ this.inactiveAt = initialRead?.inactiveAt || this.getFutureTimestamp(inactiveMs)
87
+ this.inactiveTimer = new InteractionTimer({
88
+ // When the inactive timer ends, collect a SM and reset the session
89
+ onEnd: () => {
90
+ this.collectSM('inactive', this)
91
+ this.collectSM('duration', this)
92
+ this.reset()
93
+ },
94
+ // When the inactive timer refreshes, it will update the storage values with an update timestamp
95
+ onRefresh: this.refresh.bind(this),
96
+ // When the inactive timer pauses, update the storage values with an update timestamp
97
+ onPause: () => this.write(new Configurable(this.read(), model)),
98
+ ee: this.ee,
99
+ refreshEvents: ['click', 'keydown', 'scroll']
100
+ }, this.inactiveAt - Date.now())
101
+ } else {
102
+ this.inactiveAt = Infinity
103
+ }
104
+
105
+ // The fact that the session is "new" or pre-existing is used in some places in the agent. Session Replay and Trace
106
+ // can use this info to inform whether to trust a new sampling decision vs continue a previous tracking effort.
107
+ this.isNew = !Object.keys(initialRead).length
108
+ // if its a "new" session, we write to storage API with the default values. These values may change over the lifespan of the agent run.
109
+ // we can use configurable here to help us know and manage what values are being used. -- see "model" above
110
+ if (this.isNew) this.write(new Configurable(this, model), true)
111
+ else this.sync(initialRead)
112
+
113
+ this.initialized = true
114
+ }
115
+
116
+ // This is the actual key appended to the storage API
117
+ get lookupKey () {
118
+ return `${PREFIX}_${this.key}`
119
+ }
120
+
121
+ sync (data) {
122
+ Object.assign(this, data)
123
+ }
124
+
125
+ /**
126
+ * Fetch the stored values from the storage API tied to this entity
127
+ * @returns {Object}
128
+ */
129
+ read () {
130
+ try {
131
+ const val = this.storage.get(this.lookupKey)
132
+ if (!val) return {}
133
+ // TODO - decompression would need to happen here if we decide to do it
134
+ const obj = typeof val === 'string' ? JSON.parse(val) : val
135
+ if (this.isInvalid(obj)) return {}
136
+ // if the session expires, collect a SM count before resetting
137
+ if (this.isExpired(obj.expiresAt)) {
138
+ this.collectSM('expired', this)
139
+ this.collectSM('duration', obj, true)
140
+ return this.reset()
141
+ }
142
+ // if "inactive" timer is expired at "read" time -- esp. initial read -- reset
143
+ // collect a SM count before resetting
144
+ if (this.isExpired(obj.inactiveAt)) {
145
+ this.collectSM('inactive', this)
146
+ this.collectSM('duration', obj, true)
147
+ return this.reset()
148
+ }
149
+
150
+ return obj
151
+ } catch (e) {
152
+ warn('Failed to read from storage API', e)
153
+ // storage is inaccessible
154
+ return {}
155
+ }
156
+ }
157
+
158
+ /**
159
+ * Store data to the storage API tied to this entity
160
+ * To preseve existing attributes, the output of ...session.read()
161
+ * should be appended to the data argument
162
+ * @param {Object} data
163
+ * @returns {Object}
164
+ */
165
+ write (data) {
166
+ try {
167
+ if (!data || typeof data !== 'object') return
168
+ // everytime we update, we can update a timestamp for sanity
169
+ data.updatedAt = Date.now()
170
+ this.sync(data)
171
+ // TODO - compression would need happen here if we decide to do it
172
+ this.storage.set(this.lookupKey, stringify(data))
173
+ return data
174
+ } catch (e) {
175
+ // storage is inaccessible
176
+ warn('Failed to write to the storage API', e)
177
+ return null
178
+ }
179
+ }
180
+
181
+ reset () {
182
+ // this method should set off a chain of actions across the features by emitting 'new-session'
183
+ // * send off pending payloads
184
+ // * stop recording (stn and sr)...
185
+ // * delete the session and start over
186
+ try {
187
+ if (this.initialized) this.ee.emit('session-reset')
188
+
189
+ this.storage.remove(this.lookupKey)
190
+ this.inactiveTimer?.abort?.()
191
+ this.expiresTimer?.clear?.()
192
+ delete this.custom
193
+ delete this.value
194
+
195
+ this.setup({
196
+ agentIdentifier: this.agentIdentifier,
197
+ key: this.key,
198
+ storageAPI: this.storage,
199
+ expiresMs: this.expiresMs,
200
+ inactiveMs: this.inactiveMs
201
+ })
202
+ return this.read()
203
+ } catch (e) {
204
+ return {}
205
+ }
206
+ }
207
+
208
+ /**
209
+ * Refresh the inactivity timer data
210
+ */
211
+ refresh () {
212
+ // read here & invalidate
213
+ const existingData = this.read()
214
+ this.inactiveAt = this.getFutureTimestamp(this.inactiveMs)
215
+ this.write({ ...existingData, inactiveAt: this.inactiveAt })
216
+ }
217
+
218
+ /**
219
+ * @param {number} timestamp
220
+ * @returns {boolean}
221
+ */
222
+ isExpired (timestamp) {
223
+ return Date.now() > timestamp
224
+ }
225
+
226
+ /**
227
+ * @param {Object} data
228
+ * @returns {boolean}
229
+ */
230
+ isInvalid (data) {
231
+ const requiredKeys = ['value', 'expiresAt', 'inactiveAt']
232
+ return !requiredKeys.every(x => Object.keys(data).includes(x))
233
+ }
234
+
235
+ collectSM (type, data, useUpdatedAt) {
236
+ let value, tag
237
+ if (type === 'duration') {
238
+ const startingTimestamp = data.expiresAt - data.expiresMs
239
+ const endingTimestamp = useUpdatedAt ? data.updatedAt : Date.now()
240
+ value = endingTimestamp - startingTimestamp
241
+ tag = 'Session/Duration/Ms'
242
+ }
243
+ if (type === 'expired') tag = 'Session/Expired/Seen'
244
+ if (type === 'inactive') tag = 'Session/Inactive/Seen'
245
+
246
+ if (tag) handle(SUPPORTABILITY_METRIC_CHANNEL, [tag, value], undefined, FEATURE_NAMES.metrics, this.ee)
247
+ }
248
+
249
+ /**
250
+ * @param {number} futureMs - The number of ms to use to generate a future timestamp
251
+ * @returns {number}
252
+ */
253
+ getFutureTimestamp (futureMs) {
254
+ return Date.now() + futureMs
255
+ }
256
+
257
+ syncCustomAttribute (key, value) {
258
+ if (!isBrowserScope) return
259
+ if (value === null) {
260
+ const curr = this.read()
261
+ if (curr.custom) {
262
+ delete curr.custom[key]
263
+ this.write({ ...curr })
264
+ }
265
+ } else {
266
+ const curr = this.read()
267
+ this.custom = { ...(curr?.custom || {}), [key]: value }
268
+ this.write({ ...curr, custom: this.custom })
269
+ }
270
+ }
271
+ }