@newrelic/browser-agent 1.302.0-rc.1 → 1.302.0-rc.11

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 (117) hide show
  1. package/dist/cjs/common/config/init-types.js +2 -0
  2. package/dist/cjs/common/config/init.js +3 -0
  3. package/dist/cjs/common/constants/env.cdn.js +1 -1
  4. package/dist/cjs/common/constants/env.npm.js +1 -1
  5. package/dist/cjs/common/harvest/harvester.js +13 -9
  6. package/dist/cjs/common/harvest/types.js +0 -1
  7. package/dist/cjs/common/session/session-entity.js +4 -2
  8. package/dist/cjs/common/util/mfe.js +8 -5
  9. package/dist/cjs/common/wrap/wrap-promise.js +10 -5
  10. package/dist/cjs/features/generic_events/aggregate/index.js +4 -4
  11. package/dist/cjs/features/logging/aggregate/index.js +1 -2
  12. package/dist/cjs/features/page_view_event/aggregate/index.js +84 -22
  13. package/dist/cjs/features/page_view_event/instrument/index.js +0 -4
  14. package/dist/cjs/features/session_replay/aggregate/index.js +3 -2
  15. package/dist/cjs/features/session_replay/constants.js +2 -6
  16. package/dist/cjs/features/session_replay/instrument/index.js +3 -2
  17. package/dist/cjs/features/utils/agent-session.js +13 -0
  18. package/dist/cjs/features/utils/instrument-base.js +7 -8
  19. package/dist/cjs/interfaces/registered-entity.js +21 -0
  20. package/dist/cjs/loaders/agent.js +2 -0
  21. package/dist/cjs/loaders/api/consent.js +24 -0
  22. package/dist/cjs/loaders/api/constants.js +3 -2
  23. package/dist/cjs/loaders/api/measure.js +36 -35
  24. package/dist/cjs/loaders/api/recordCustomEvent.js +5 -3
  25. package/dist/cjs/loaders/api/register-api-types.js +14 -10
  26. package/dist/cjs/loaders/api/register.js +25 -10
  27. package/dist/cjs/loaders/api-base.js +14 -7
  28. package/dist/esm/common/config/init-types.js +2 -0
  29. package/dist/esm/common/config/init.js +3 -0
  30. package/dist/esm/common/constants/env.cdn.js +1 -1
  31. package/dist/esm/common/constants/env.npm.js +1 -1
  32. package/dist/esm/common/harvest/harvester.js +13 -9
  33. package/dist/esm/common/harvest/types.js +0 -1
  34. package/dist/esm/common/session/session-entity.js +4 -2
  35. package/dist/esm/common/util/mfe.js +7 -5
  36. package/dist/esm/common/wrap/wrap-promise.js +10 -5
  37. package/dist/esm/features/generic_events/aggregate/index.js +4 -4
  38. package/dist/esm/features/logging/aggregate/index.js +1 -2
  39. package/dist/esm/features/page_view_event/aggregate/index.js +84 -22
  40. package/dist/esm/features/page_view_event/instrument/index.js +0 -4
  41. package/dist/esm/features/session_replay/aggregate/index.js +4 -3
  42. package/dist/esm/features/session_replay/constants.js +1 -5
  43. package/dist/esm/features/session_replay/instrument/index.js +4 -3
  44. package/dist/esm/features/utils/agent-session.js +13 -0
  45. package/dist/esm/features/utils/instrument-base.js +7 -8
  46. package/dist/esm/interfaces/registered-entity.js +21 -0
  47. package/dist/esm/loaders/agent.js +2 -0
  48. package/dist/esm/loaders/api/consent.js +17 -0
  49. package/dist/esm/loaders/api/constants.js +2 -1
  50. package/dist/esm/loaders/api/measure.js +35 -35
  51. package/dist/esm/loaders/api/recordCustomEvent.js +4 -3
  52. package/dist/esm/loaders/api/register-api-types.js +14 -10
  53. package/dist/esm/loaders/api/register.js +26 -10
  54. package/dist/esm/loaders/api-base.js +15 -8
  55. package/dist/tsconfig.tsbuildinfo +1 -1
  56. package/dist/types/common/config/init-types.d.ts +6 -0
  57. package/dist/types/common/config/init.d.ts.map +1 -1
  58. package/dist/types/common/harvest/harvester.d.ts.map +1 -1
  59. package/dist/types/common/harvest/types.d.ts +0 -2
  60. package/dist/types/common/harvest/types.d.ts.map +1 -1
  61. package/dist/types/common/session/session-entity.d.ts.map +1 -1
  62. package/dist/types/common/util/mfe.d.ts +3 -0
  63. package/dist/types/common/util/mfe.d.ts.map +1 -1
  64. package/dist/types/common/wrap/wrap-promise.d.ts.map +1 -1
  65. package/dist/types/features/page_view_event/aggregate/index.d.ts +22 -3
  66. package/dist/types/features/page_view_event/aggregate/index.d.ts.map +1 -1
  67. package/dist/types/features/page_view_event/instrument/index.d.ts.map +1 -1
  68. package/dist/types/features/session_replay/aggregate/index.d.ts.map +1 -1
  69. package/dist/types/features/session_replay/constants.d.ts +1 -5
  70. package/dist/types/features/session_replay/constants.d.ts.map +1 -1
  71. package/dist/types/features/session_replay/instrument/index.d.ts.map +1 -1
  72. package/dist/types/features/utils/agent-session.d.ts.map +1 -1
  73. package/dist/types/features/utils/instrument-base.d.ts +1 -0
  74. package/dist/types/features/utils/instrument-base.d.ts.map +1 -1
  75. package/dist/types/interfaces/registered-entity.d.ts +25 -0
  76. package/dist/types/interfaces/registered-entity.d.ts.map +1 -1
  77. package/dist/types/loaders/agent.d.ts.map +1 -1
  78. package/dist/types/loaders/api/consent.d.ts +2 -0
  79. package/dist/types/loaders/api/consent.d.ts.map +1 -0
  80. package/dist/types/loaders/api/constants.d.ts +1 -0
  81. package/dist/types/loaders/api/constants.d.ts.map +1 -1
  82. package/dist/types/loaders/api/measure.d.ts +3 -0
  83. package/dist/types/loaders/api/measure.d.ts.map +1 -1
  84. package/dist/types/loaders/api/recordCustomEvent.d.ts +1 -0
  85. package/dist/types/loaders/api/recordCustomEvent.d.ts.map +1 -1
  86. package/dist/types/loaders/api/register-api-types.d.ts +43 -12
  87. package/dist/types/loaders/api/register-api-types.d.ts.map +1 -1
  88. package/dist/types/loaders/api/register.d.ts +0 -16
  89. package/dist/types/loaders/api/register.d.ts.map +1 -1
  90. package/dist/types/loaders/api-base.d.ts +20 -15
  91. package/dist/types/loaders/api-base.d.ts.map +1 -1
  92. package/package.json +2 -2
  93. package/src/common/config/init-types.js +2 -0
  94. package/src/common/config/init.js +1 -0
  95. package/src/common/harvest/harvester.js +11 -8
  96. package/src/common/harvest/types.js +0 -1
  97. package/src/common/session/session-entity.js +6 -2
  98. package/src/common/util/mfe.js +10 -4
  99. package/src/common/wrap/wrap-promise.js +16 -6
  100. package/src/features/generic_events/aggregate/index.js +4 -4
  101. package/src/features/logging/aggregate/index.js +1 -1
  102. package/src/features/page_view_event/aggregate/index.js +79 -15
  103. package/src/features/page_view_event/instrument/index.js +0 -4
  104. package/src/features/session_replay/aggregate/index.js +4 -3
  105. package/src/features/session_replay/constants.js +1 -5
  106. package/src/features/session_replay/instrument/index.js +4 -3
  107. package/src/features/utils/agent-session.js +12 -0
  108. package/src/features/utils/instrument-base.js +7 -9
  109. package/src/interfaces/registered-entity.js +21 -0
  110. package/src/loaders/agent.js +2 -0
  111. package/src/loaders/api/consent.js +18 -0
  112. package/src/loaders/api/constants.js +1 -0
  113. package/src/loaders/api/measure.js +34 -33
  114. package/src/loaders/api/recordCustomEvent.js +5 -3
  115. package/src/loaders/api/register-api-types.js +14 -10
  116. package/src/loaders/api/register.js +17 -10
  117. package/src/loaders/api-base.js +15 -8
@@ -1 +1 @@
1
- {"version":3,"file":"api-base.d.ts","sourceRoot":"","sources":["../../../src/loaders/api-base.js"],"names":[],"mappings":"AAOA;;GAEG;AACH;IAQE;;;;;OAKG;IACH,oBAHW,MAAM,eACN,MAAM,OAIhB;IAED;;;;;;;;;;;OAWG;IACH,iBALG;QAAuB,UAAU,EAAzB,MAAM;QACS,aAAa,EAA5B,MAAM;QACU,UAAU,GAA1B,MAAM,YAAC;KACf,GAAU,MAAM,CAIlB;IAED;;;;;OAKG;IACH,6BAHW,MAAM,eACN,MAAM,OAIhB;IAED;;;;;OAKG;IACH,sBAHW,MAAM,SACN,MAAM,OAIhB;IAED;;;;;;OAMG;IACH,yBAJW,MAAM,SACN,MAAM,GAAC,MAAM,GAAC,OAAO,GAAC,IAAI,YAC1B,OAAO,OAIjB;IAED;;;;;OAKG;IACH,mBAHW,KAAK,GAAC,MAAM,qBACZ,MAAM,OAIhB;IAED;;;;OAIG;IACH,iBAFW,MAAM,GAAC,IAAI,OAIrB;IAED;;;;;;;OAOG;IACH,6BAJW,MAAM,GAAC,IAAI,OAMrB;IAED;;;;OAIG;IACH,0BAFW,CAAC,KAAK,EAAE,KAAK,GAAC,MAAM,KAAK,OAAO,GAAG;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,OAI9D;IAED;;;;;OAKG;IACH,iBAHW,MAAM,MACN,MAAM,OAIhB;IAED;;;;;MAKE;IACF,aAHW,MAAM,YACN;QAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,OAAO,GAAC,OAAO,GAAC,OAAO,GAAC,MAAM,GAAC,MAAM,CAAA;KAAC,OAIpF;IAED;;;OAGG;IACH,aAEC;IAED;;;;OAIG;IACH,qBAFW,MAAM,OAIhB;IAED;;;;OAIG;IACH,oBAEC;IAED;;;;;OAKG;IACH,mBAEC;IAED;;;;;;;;;;OAUG;IACH,6BARW;QAAC,IAAI,EAAE,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,OAUrF;IAED;;;;;OAKG;IACH,0BAHW,MAAM,OAKhB;IAED;;;;;;;;MAQE;IACF,mBALG;QAAuB,UAAU;KACjC,GAAU,mBAAmB,CAM/B;IAED;;;;;;MAME;IACF,mBAJW,MAAM,gBACN,MAAM,YACN;QAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,OAAO,GAAC,OAAO,GAAC,OAAO,GAAC,MAAM,GAAC,MAAM,CAAA;KAAC,OAIpF;IAED;;;;;;OAMG;IACH,cAJW,MAAM,WACN,MAAM,OAAC,GACL;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,gBAAgB,EAAE,MAAM,CAAA;KAAC,CAIpF;;CACF;kCA9NY,OAAO,yBAAyB,EAAE,mBAAmB"}
1
+ {"version":3,"file":"api-base.d.ts","sourceRoot":"","sources":["../../../src/loaders/api-base.js"],"names":[],"mappings":"AAOA;;GAEG;AACH;IAQE;;;;;OAKG;IACH,oBAHW,MAAM,eACN,MAAM,OAIhB;IAED;;;;;;;;OAQG;IACH,iBAHW,OAAO,0BAA0B,EAAE,sBAAsB,GAC1D,OAAO,0BAA0B,EAAE,WAAW,CAIvD;IAED;;;;;OAKG;IACH,6BAHW,MAAM,eACN,MAAM,OAIhB;IAED;;;;;OAKG;IACH,sBAHW,MAAM,SACN,MAAM,OAIhB;IAED;;;;;;OAMG;IACH,yBAJW,MAAM,SACN,MAAM,GAAC,MAAM,GAAC,OAAO,GAAC,IAAI,YAC1B,OAAO,OAIjB;IAED;;;;;OAKG;IACH,mBAHW,KAAK,GAAC,MAAM,qBACZ,MAAM,OAIhB;IAED;;;;OAIG;IACH,iBAFW,MAAM,GAAC,IAAI,OAIrB;IAED;;;;;;;OAOG;IACH,6BAJW,MAAM,GAAC,IAAI,OAMrB;IAED;;;;OAIG;IACH,0BAFW,CAAC,KAAK,EAAE,KAAK,GAAC,MAAM,KAAK,OAAO,GAAG;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,OAI9D;IAED;;;;;OAKG;IACH,iBAHW,MAAM,MACN,MAAM,OAIhB;IAED;;;;;MAKE;IACF,aAHW,MAAM,YACN;QAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,OAAO,GAAC,OAAO,GAAC,OAAO,GAAC,MAAM,GAAC,MAAM,CAAA;KAAC,OAIpF;IAED;;;OAGG;IACH,aAEC;IAED;;;;OAIG;IACH,qBAFW,MAAM,OAIhB;IAED;;;;OAIG;IACH,oBAEC;IAED;;;;;OAKG;IACH,mBAEC;IAED;;;;;;;;;;OAUG;IACH,6BARW;QAAC,IAAI,EAAE,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,OAUrF;IAED;;;;;OAKG;IACH,0BAHW,MAAM,OAKhB;IAED;;;;;;;;MAQE;IACF,mBALG;QAAuB,UAAU;KACjC,GAAU,mBAAmB,CAM/B;IAED;;;;;;MAME;IACF,mBAJW,MAAM,gBACN,MAAM,YACN;QAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,OAAO,GAAC,OAAO,GAAC,OAAO,GAAC,MAAM,GAAC,MAAM,CAAA;KAAC,OAIpF;IAED;;;;;;OAMG;IACH,cAJW,MAAM,YACN;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,gBAAgB,EAAE,MAAM,CAAA;KAAC,GACtE;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,gBAAgB,EAAE,MAAM,CAAA;KAAC,CAIpF;IAED;;;;;OAKG;IACH,gBAFW,OAAO,OAAC,OAIlB;;CACF;kCArOY,OAAO,yBAAyB,EAAE,mBAAmB"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@newrelic/browser-agent",
3
- "version": "1.302.0-rc.1",
3
+ "version": "1.302.0-rc.11",
4
4
  "private": false,
5
5
  "author": "New Relic Browser Agent Team <browser-agent@newrelic.com>",
6
6
  "description": "New Relic Browser Agent",
@@ -170,7 +170,7 @@
170
170
  "runtime": {
171
171
  "name": "node",
172
172
  "onFail": "error",
173
- "version": "22.11.0"
173
+ "version": ">=22.11.0"
174
174
  },
175
175
  "packageManager": {
176
176
  "name": "npm",
@@ -84,6 +84,8 @@
84
84
  * @property {boolean} [spa.enabled] - Turn on/off the single page application feature (on by default). NOTE: the SPA feature is deprecated and under removal procedure.
85
85
  * @property {boolean} [spa.autoStart] - If true, the agent will automatically start the single page application feature. Otherwise, it will be in a deferred state until the `start` API method is called.
86
86
  * @property {boolean} [ssl] - If explicitly false, the agent will use HTTP instead of HTTPS. This setting should NOT be used.
87
+ * @property {Object} [browser_consent_mode]
88
+ * @property {boolean} [browser_consent_mode.enabled] - If true, the agent will use consent mode for whether to allow or disallow data harvest.
87
89
  * @property {Object} [user_actions]
88
90
  * @property {boolean} [user_actions.enabled] - Must be true to allow UserAction events to be captured.
89
91
  * @property {Array<string>} [user_actions.elementAttributes] - List of HTML Element properties to be captured with UserAction events' target elements. This may help to identify the source element being interacted with in the UI.
@@ -53,6 +53,7 @@ const InitModelFn = () => {
53
53
  set allow_registered_children (val) { hiddenState.experimental.allow_registered_children = val },
54
54
  duplicate_registered_data: false
55
55
  },
56
+ browser_consent_mode: { enabled: false },
56
57
  distributed_tracing: {
57
58
  enabled: undefined,
58
59
  exclude_newrelic_header: undefined,
@@ -18,8 +18,10 @@ import { getSubmitMethod, xhr as xhrMethod, xhrFetch as fetchMethod } from '../u
18
18
  import { activatedFeatures } from '../util/feature-flags'
19
19
  import { dispatchGlobalEvent } from '../dispatch/global-event'
20
20
 
21
- const RETRY_FAILED = 'Harvester/Retry/Failed/'
22
- const RETRY_SUCCEEDED = 'Harvester/Retry/Succeeded/'
21
+ const RETRY = 'Harvester/Retry/'
22
+ const RETRY_ATTEMPTED = RETRY + 'Attempted/'
23
+ const RETRY_FAILED = RETRY + 'Failed/'
24
+ const RETRY_SUCCEEDED = RETRY + 'Succeeded/'
23
25
 
24
26
  export class Harvester {
25
27
  #started = false
@@ -57,12 +59,13 @@ export class Harvester {
57
59
  triggerHarvestFor (aggregateInst, localOpts = {}) {
58
60
  const output = { ranSend: false, payload: undefined, endpointVersion: aggregateInst.harvestEndpointVersion || 1 }
59
61
  if (aggregateInst.blocked) return output
62
+ if (this.agentRef.init?.browser_consent_mode?.enabled && !this.agentRef.runtime?.session?.state?.consent) return output
60
63
 
61
64
  const submitMethod = getSubmitMethod(localOpts)
62
65
  if (!submitMethod) return output
63
66
 
64
67
  const shouldRetryOnFail = !localOpts.isFinalHarvest && submitMethod === xhrMethod // always retry all features harvests except for final
65
- output.payload = !localOpts.directSend ? aggregateInst.makeHarvestPayload(shouldRetryOnFail, localOpts) : localOpts.directSend?.payload // features like PVE can define the payload directly, bypassing the makeHarvestPayload logic
68
+ output.payload = aggregateInst.makeHarvestPayload(shouldRetryOnFail, localOpts)
66
69
 
67
70
  if (!output.payload) return output
68
71
 
@@ -86,7 +89,9 @@ export class Harvester {
86
89
  */
87
90
  function cbFinished (result) {
88
91
  if (aggregateInst.harvestOpts.prevAttemptCode) { // this means we just retried a harvest that last failed
89
- handle(SUPPORTABILITY_METRIC_CHANNEL, [(result.retry ? RETRY_FAILED : RETRY_SUCCEEDED) + aggregateInst.harvestOpts.prevAttemptCode], undefined, FEATURE_NAMES.metrics, aggregateInst.ee)
92
+ const reportSM = (message) => handle(SUPPORTABILITY_METRIC_CHANNEL, [message], undefined, FEATURE_NAMES.metrics, aggregateInst.ee)
93
+ reportSM(RETRY_ATTEMPTED + aggregateInst.featureName)
94
+ reportSM((result.retry ? RETRY_FAILED : RETRY_SUCCEEDED) + aggregateInst.harvestOpts.prevAttemptCode)
90
95
  delete aggregateInst.harvestOpts.prevAttemptCode // always reset last observation so we don't falsely report again next harvest
91
96
  // In case this re-attempt failed again, that'll be handled (re-marked again) next.
92
97
  }
@@ -155,8 +160,7 @@ export function send (agentRef, { endpoint, payload, localOpts = {}, submitMetho
155
160
  result.addEventListener('loadend', function () {
156
161
  // `this` here in block refers to the XHR object in this scope, do not change the anon function to an arrow function
157
162
  // status 0 refers to a local error, such as CORS or network failure, or a blocked request by the browser (e.g. adblocker)
158
- const cbResult = { sent: this.status !== 0, status: this.status, retry: shouldRetry(this.status), fullUrl, xhr: this }
159
- if (localOpts.needResponse) cbResult.responseText = this.responseText
163
+ const cbResult = { sent: this.status !== 0, status: this.status, retry: shouldRetry(this.status), fullUrl, xhr: this, responseText: this.responseText }
160
164
  cbFinished(cbResult)
161
165
 
162
166
  /** temporary audit of consistency of harvest metadata flags */
@@ -165,8 +169,7 @@ export function send (agentRef, { endpoint, payload, localOpts = {}, submitMetho
165
169
  } else if (submitMethod === fetchMethod) {
166
170
  result.then(async function (response) {
167
171
  const status = response.status
168
- const cbResult = { sent: true, status, retry: shouldRetry(status), fullUrl, fetchResponse: response }
169
- if (localOpts.needResponse) cbResult.responseText = await response.text()
172
+ const cbResult = { sent: true, status, retry: shouldRetry(status), fullUrl, fetchResponse: response, responseText: await response.text() }
170
173
  cbFinished(cbResult)
171
174
  /** temporary audit of consistency of harvest metadata flags */
172
175
  if (!shouldRetry(status)) trackHarvestMetadata()
@@ -24,7 +24,6 @@
24
24
  * @property {HarvestEndpointIdentifier} endpoint The endpoint to use (jserrors, events, resources etc.)
25
25
  * @property {HarvestPayload} payload Object representing payload.
26
26
  * @property {object} localOpts Additional options for sending data
27
- * @property {boolean} localOpts.needResponse Specify whether the caller expects a response data.
28
27
  * @property {boolean} localOpts.isFinalHarvest Specify whether the call is a final harvest during page unload.
29
28
  * @property {boolean} localOpts.sendEmptyBody Specify whether the call should be made even if the body is empty. Useful for rum calls.
30
29
  * @property {boolean} localOpts.forceNoRetry Don't save the buffered data in the case of a need to retry the transmission.
@@ -32,7 +32,8 @@ const model = {
32
32
  loggingMode: LOGGING_MODE.OFF,
33
33
  serverTimeDiff: null, // set by TimeKeeper; "undefined" value will not be stringified and stored but "null" will
34
34
  custom: {},
35
- numOfResets: 0
35
+ numOfResets: 0,
36
+ consent: false // set by consent() API call
36
37
  }
37
38
 
38
39
  export class SessionEntity {
@@ -77,7 +78,10 @@ export class SessionEntity {
77
78
 
78
79
  setup ({ value = generateRandomHexString(16), expiresMs = DEFAULT_EXPIRES_MS, inactiveMs = DEFAULT_INACTIVE_MS, numOfResets = 0 }) {
79
80
  /** Ensure that certain properties are preserved across a reset if already set */
80
- const persistentAttributes = { serverTimeDiff: this.state.serverTimeDiff || model.serverTimeDiff }
81
+ const persistentAttributes = {
82
+ serverTimeDiff: this.state.serverTimeDiff || model.serverTimeDiff,
83
+ consent: this.state.consent || model.consent
84
+ }
81
85
  this.state = {}
82
86
  this.sync({ ...model, ...persistentAttributes })
83
87
 
@@ -11,8 +11,14 @@ export function isValidMFETarget (target = {}) {
11
11
  return !!(target.id && target.name)
12
12
  }
13
13
 
14
+ export function hasValidValue (val) {
15
+ return (typeof val === 'string' && val.trim().length < 501) || (typeof val === 'number')
16
+ }
17
+
14
18
  /**
15
19
  * When given a valid target, returns an object with the MFE payload attributes. Returns an empty object otherwise.
20
+ * @note Field names may change as the schema is finalized
21
+ *
16
22
  * @param {Object} [target] the registered target
17
23
  * @param {AggregateInstance} [aggregateInstance] the aggregate instance calling the method
18
24
  * @returns {{'mfe.id': *, 'mfe.name': String}|{}} returns an empty object if args are not supplied or the aggregate instance is not supporting version 2
@@ -27,9 +33,9 @@ export function getVersion2Attributes (target, aggregateInstance) {
27
33
  }
28
34
  }
29
35
  return {
30
- 'mfe.id': target.id, // these field names may change as the schema is finalized
31
- 'mfe.name': target.name, // these field names may change as the schema is finalized
32
- eventSource: 'MicroFrontendBrowserAgent', // these field names may change as the schema is finalized
33
- 'parent.id': containerAgentEntityGuid
36
+ 'mfe.id': target.id,
37
+ 'mfe.name': target.name,
38
+ eventSource: target.eventSource,
39
+ 'parent.id': target.parent?.id || containerAgentEntityGuid
34
40
  }
35
41
  }
@@ -140,13 +140,23 @@ export function wrapPromise (sharedEE) {
140
140
 
141
141
  promiseEE.on('propagate', function (val, overwrite, trigger) {
142
142
  if (!this.getCtx || overwrite) {
143
- this.getCtx = function () {
144
- // eslint-disable-next-line
145
- if (val instanceof Promise) {
146
- var store = promiseEE.context(val)
143
+ const selfStore = this
144
+ const parentStore =
145
+ val instanceof Promise ? promiseEE.context(val) : null
146
+ let cachedCtx
147
+
148
+ this.getCtx = function getCtx () {
149
+ if (cachedCtx) return cachedCtx
150
+
151
+ if (parentStore && parentStore !== selfStore) {
152
+ cachedCtx =
153
+ typeof parentStore.getCtx === 'function'
154
+ ? parentStore.getCtx()
155
+ : parentStore
156
+ } else {
157
+ cachedCtx = selfStore
147
158
  }
148
-
149
- return store && store.getCtx ? store.getCtx() : this
159
+ return cachedCtx
150
160
  }
151
161
  }
152
162
  })
@@ -33,13 +33,13 @@ export class Aggregate extends AggregateBase {
33
33
 
34
34
  this.#trackSupportabilityMetrics()
35
35
 
36
- registerHandler('api-recordCustomEvent', (timestamp, eventType, attributes) => {
36
+ registerHandler('api-recordCustomEvent', (timestamp, eventType, attributes, target) => {
37
37
  if (RESERVED_EVENT_TYPES.includes(eventType)) return warn(46)
38
38
  this.addEvent({
39
39
  eventType,
40
40
  timestamp: this.toEpoch(timestamp),
41
41
  ...attributes
42
- })
42
+ }, target)
43
43
  }, this.featureName, this.ee)
44
44
 
45
45
  if (agentRef.init.page_action.enabled) {
@@ -227,7 +227,7 @@ export class Aggregate extends AggregateBase {
227
227
  }, this.featureName, this.ee)
228
228
  }
229
229
 
230
- registerHandler('api-measure', (args, n) => {
230
+ registerHandler('api-measure', (args, n, target) => {
231
231
  const { start, duration, customAttributes } = args
232
232
 
233
233
  const event = {
@@ -239,7 +239,7 @@ export class Aggregate extends AggregateBase {
239
239
  entryType: 'measure'
240
240
  }
241
241
 
242
- this.addEvent(event)
242
+ this.addEvent(event, target)
243
243
  }, this.featureName, this.ee)
244
244
 
245
245
  this.drain()
@@ -116,7 +116,7 @@ export class Aggregate extends AggregateBase {
116
116
  common: {
117
117
  /** Attributes in the `common` section are added to `all` logs generated in the payload */
118
118
  attributes: {
119
- ...this.agentRef.info.jsAttributes, // user-provided custom attributes
119
+ ...(applyFnToProps(this.agentRef.info.jsAttributes, this.obfuscator.obfuscateString.bind(this.obfuscator), 'string')),
120
120
  ...(this.harvestEndpointVersion === 1 && {
121
121
  'entity.guid': this.agentRef.runtime.appMetadata.agents[0].entityGuid,
122
122
  appId: this.agentRef.info.applicationID
@@ -17,15 +17,22 @@ import { timeToFirstByte } from '../../../common/vitals/time-to-first-byte'
17
17
  import { now } from '../../../common/timing/now'
18
18
  import { TimeKeeper } from '../../../common/timing/time-keeper'
19
19
  import { applyFnToProps } from '../../../common/util/traverse'
20
+ import { send } from '../../../common/harvest/harvester'
21
+ import { FEATURE_NAMES, FEATURE_TO_ENDPOINT } from '../../../loaders/features/features'
22
+ import { getSubmitMethod } from '../../../common/util/submit-data'
20
23
 
21
24
  export class Aggregate extends AggregateBase {
22
25
  static featureName = CONSTANTS.FEATURE_NAME
26
+
23
27
  constructor (agentRef) {
24
28
  super(agentRef, CONSTANTS.FEATURE_NAME)
25
29
 
30
+ this.sentRum = false // flag to facilitate calling sendRum() once externally (by the consent API in agent-session.js)
31
+
26
32
  this.timeToFirstByte = 0
27
33
  this.firstByteToWindowLoad = 0 // our "frontend" duration
28
34
  this.firstByteToDomContent = 0 // our "dom processing" duration
35
+ this.retries = 0
29
36
 
30
37
  if (!isValid(agentRef.info)) {
31
38
  this.ee.abort()
@@ -52,9 +59,8 @@ export class Aggregate extends AggregateBase {
52
59
  *
53
60
  * @param {Function} cb A function to run once the RUM call has finished - Defaults to activateFeatures
54
61
  * @param {*} customAttributes custom attributes to attach to the RUM call - Defaults to info.js
55
- * @param {*} target The target to harvest to
56
62
  */
57
- sendRum (customAttributes = this.agentRef.info.jsAttributes, target = { licenseKey: this.agentRef.info.licenseKey, applicationID: this.agentRef.info.applicationID }) {
63
+ sendRum (customAttributes = this.agentRef.info.jsAttributes) {
58
64
  const info = this.agentRef.info
59
65
  const measures = {}
60
66
 
@@ -107,24 +113,26 @@ export class Aggregate extends AggregateBase {
107
113
  queryParameters.fp = firstPaint.current.value
108
114
  queryParameters.fcp = firstContentfulPaint.current.value
109
115
 
110
- const timeKeeper = this.agentRef.runtime.timeKeeper
111
- if (timeKeeper?.ready) {
112
- queryParameters.timestamp = Math.floor(timeKeeper.correctRelativeTimestamp(now()))
116
+ this.queryStringsBuilder = () => { // this will be called by AggregateBase.makeHarvestPayload every time harvest is triggered to be qs
117
+ this.rumStartTime = now() // this should be reset at the beginning of each RUM call for proper timeKeeper calculation in coordination with postHarvestCleanup
118
+ const timeKeeper = this.agentRef.runtime.timeKeeper
119
+ if (timeKeeper?.ready) {
120
+ queryParameters.timestamp = Math.floor(timeKeeper.correctRelativeTimestamp(this.rumStartTime))
121
+ }
122
+ return queryParameters
113
123
  }
124
+ this.events.add(body)
114
125
 
115
- this.rumStartTime = now()
116
-
117
- this.agentRef.runtime.harvester.triggerHarvestFor(this, {
118
- directSend: {
119
- target,
120
- payload: { qs: queryParameters, body }
121
- },
122
- needResponse: true,
126
+ if (this.agentRef.runtime.harvester.triggerHarvestFor(this, {
123
127
  sendEmptyBody: true
124
- })
128
+ }).ranSend) this.sentRum = true
129
+ }
130
+
131
+ serializer (eventBuffer) { // this is necessary because PVE sends a single item rather than an array; in the case of undefined, this prevents sending [null] as body
132
+ return eventBuffer[0]
125
133
  }
126
134
 
127
- postHarvestCleanup ({ status, responseText, xhr }) {
135
+ postHarvestCleanup ({ sent, status, responseText, xhr, retry }) {
128
136
  const rumEndTime = now()
129
137
  let app, flags
130
138
  try {
@@ -134,8 +142,63 @@ export class Aggregate extends AggregateBase {
134
142
  warn(53, error)
135
143
  }
136
144
 
145
+ super.postHarvestCleanup({ sent, retry }) // this will set isRetrying & re-buffer the body if request is to be retried
146
+ if (this.isRetrying && this.retries++ < 1) { // Only retry once
147
+ setTimeout(() => this.agentRef.runtime.harvester.triggerHarvestFor(this, {
148
+ sendEmptyBody: true
149
+ }), 5000) // Retry sending the RUM event after 5 seconds
150
+ return
151
+ }
137
152
  if (status >= 400 || status === 0) {
138
153
  warn(18, status)
154
+ this.blocked = true
155
+
156
+ // Get estimated payload size of our backlog
157
+ const textEncoder = new TextEncoder()
158
+ const payloadSize = Object.values(newrelic.ee.backlog).reduce((acc, value) => {
159
+ if (!value) return acc
160
+
161
+ const encoded = textEncoder.encode(value)
162
+ return acc + encoded.byteLength
163
+ }, 0)
164
+
165
+ // Send SMs about failed RUM request
166
+ const body = {
167
+ sm: [{
168
+ params: {
169
+ name: `Browser/Supportability/BCS/Error/${status}`
170
+ },
171
+ stats: {
172
+ c: 1
173
+ }
174
+ },
175
+ {
176
+ params: {
177
+ name: 'Browser/Supportability/BCS/Error/Dropped/Bytes'
178
+ },
179
+ stats: {
180
+ c: 1,
181
+ t: payloadSize
182
+ }
183
+ },
184
+ {
185
+ params: {
186
+ name: 'Browser/Supportability/BCS/Error/Duration/Ms'
187
+ },
188
+ stats: {
189
+ c: 1,
190
+ t: rumEndTime - this.rumStartTime
191
+ }
192
+ }]
193
+ }
194
+
195
+ send(this.agentRef, {
196
+ endpoint: FEATURE_TO_ENDPOINT[FEATURE_NAMES.metrics],
197
+ payload: { body },
198
+ submitMethod: getSubmitMethod(),
199
+ featureName: FEATURE_NAMES.metrics
200
+ })
201
+
139
202
  // Adding retry logic for the rum call will be a separate change; this.blocked will need to be changed since that prevents another triggerHarvestFor()
140
203
  this.ee.abort()
141
204
  return
@@ -155,6 +218,7 @@ export class Aggregate extends AggregateBase {
155
218
  }
156
219
  } catch (error) {
157
220
  this.ee.abort()
221
+ this.blocked = true
158
222
  warn(17, error)
159
223
  return
160
224
  }
@@ -2,7 +2,6 @@
2
2
  * Copyright 2020-2025 New Relic, Inc. All rights reserved.
3
3
  * SPDX-License-Identifier: Apache-2.0
4
4
  */
5
- import { handle } from '../../../common/event-emitter/handle'
6
5
  import { setupSetPageViewNameAPI } from '../../../loaders/api/setPageViewName'
7
6
  import { InstrumentBase } from '../../utils/instrument-base'
8
7
  import * as CONSTANTS from '../constants'
@@ -21,9 +20,6 @@ export class Instrument extends InstrumentBase {
21
20
  /** feature specific APIs */
22
21
  setupSetPageViewNameAPI(agentRef)
23
22
 
24
- /** messages from the register API that can trigger a new RUM call */
25
- this.ee.on('api-send-rum', (attrs, target) => handle('send-rum', [attrs, target], undefined, this.featureName, this.ee))
26
-
27
23
  this.importAggregator(agentRef, () => import(/* webpackChunkName: "page_view_event-aggregate" */ '../aggregate'))
28
24
  }
29
25
 
@@ -7,7 +7,7 @@
7
7
  */
8
8
 
9
9
  import { registerHandler } from '../../../common/event-emitter/register-handler'
10
- import { ABORT_REASONS, FEATURE_NAME, QUERY_PARAM_PADDING, RRWEB_EVENT_TYPES, SR_EVENT_EMITTER_TYPES, TRIGGERS } from '../constants'
10
+ import { ABORT_REASONS, ERROR_DURING_REPLAY, FEATURE_NAME, QUERY_PARAM_PADDING, RRWEB_EVENT_TYPES, TRIGGERS } from '../constants'
11
11
  import { AggregateBase } from '../../utils/aggregate-base'
12
12
  import { sharedChannel } from '../../../common/constants/shared-channel'
13
13
  import { obj as encodeObj } from '../../../common/url/encode'
@@ -21,6 +21,7 @@ import { now } from '../../../common/timing/now'
21
21
  import { MAX_PAYLOAD_SIZE } from '../../../common/constants/agent-constants'
22
22
  import { cleanURL } from '../../../common/url/clean-url'
23
23
  import { canEnableSessionTracking } from '../../utils/feature-gates'
24
+ import { PAUSE_REPLAY } from '../../../loaders/api/constants'
24
25
 
25
26
  export class Aggregate extends AggregateBase {
26
27
  static featureName = FEATURE_NAME
@@ -77,11 +78,11 @@ export class Aggregate extends AggregateBase {
77
78
  this.mode = data.sessionReplayMode
78
79
  })
79
80
 
80
- registerHandler(SR_EVENT_EMITTER_TYPES.PAUSE, () => {
81
+ registerHandler(PAUSE_REPLAY, () => {
81
82
  this.forceStop(this.mode === MODE.FULL)
82
83
  }, this.featureName, this.ee)
83
84
 
84
- registerHandler(SR_EVENT_EMITTER_TYPES.ERROR_DURING_REPLAY, e => {
85
+ registerHandler(ERROR_DURING_REPLAY, e => {
85
86
  this.handleError(e)
86
87
  }, this.featureName, this.ee)
87
88
 
@@ -7,11 +7,7 @@ import { FEATURE_NAMES } from '../../loaders/features/features'
7
7
 
8
8
  export const FEATURE_NAME = FEATURE_NAMES.sessionReplay
9
9
 
10
- export const SR_EVENT_EMITTER_TYPES = {
11
- RECORD: 'recordReplay',
12
- PAUSE: 'pauseReplay',
13
- ERROR_DURING_REPLAY: 'errorDuringReplay'
14
- }
10
+ export const ERROR_DURING_REPLAY = 'errorDuringReplay'
15
11
 
16
12
  export const AVG_COMPRESSION = 0.12
17
13
  export const RRWEB_EVENT_TYPES = {
@@ -10,9 +10,10 @@ import { handle } from '../../../common/event-emitter/handle'
10
10
  import { DEFAULT_KEY, MODE, PREFIX } from '../../../common/session/constants'
11
11
  import { InstrumentBase } from '../../utils/instrument-base'
12
12
  import { hasReplayPrerequisite, isPreloadAllowed } from '../shared/utils'
13
- import { FEATURE_NAME, SR_EVENT_EMITTER_TYPES, TRIGGERS } from '../constants'
13
+ import { ERROR_DURING_REPLAY, FEATURE_NAME, TRIGGERS } from '../constants'
14
14
  import { setupRecordReplayAPI } from '../../../loaders/api/recordReplay'
15
15
  import { setupPauseReplayAPI } from '../../../loaders/api/pauseReplay'
16
+ import { RECORD_REPLAY } from '../../../loaders/api/constants'
16
17
 
17
18
  export class Instrument extends InstrumentBase {
18
19
  static featureName = FEATURE_NAME
@@ -34,7 +35,7 @@ export class Instrument extends InstrumentBase {
34
35
  } catch (err) { }
35
36
 
36
37
  if (hasReplayPrerequisite(agentRef.init)) {
37
- this.ee.on(SR_EVENT_EMITTER_TYPES.RECORD, () => this.#apiStartOrRestartReplay())
38
+ this.ee.on(RECORD_REPLAY, () => this.#apiStartOrRestartReplay())
38
39
  }
39
40
 
40
41
  if (this.#canPreloadRecorder(session)) {
@@ -50,7 +51,7 @@ export class Instrument extends InstrumentBase {
50
51
  if (this.blocked) return
51
52
  if (this.agentRef.runtime.isRecording) {
52
53
  this.errorNoticed = true
53
- handle(SR_EVENT_EMITTER_TYPES.ERROR_DURING_REPLAY, [e], undefined, this.featureName, this.ee)
54
+ handle(ERROR_DURING_REPLAY, [e], undefined, this.featureName, this.ee)
54
55
  }
55
56
  })
56
57
  }
@@ -56,6 +56,18 @@ export function setupAgentSession (agentRef) {
56
56
  agentRef.runtime.session.syncCustomAttribute(key, value)
57
57
  }, 'session', sharedEE)
58
58
 
59
+ registerHandler('api-consent', (accept) => {
60
+ agentRef.runtime.session.write({ consent: accept === undefined ? true : accept })
61
+
62
+ // call sendRum if it wasn't called yet
63
+ agentRef.features.page_view_event.onAggregateImported.then((loaded) => {
64
+ const pveAgg = agentRef.features.page_view_event.featAggregate
65
+ if (loaded && !pveAgg.sentRum) {
66
+ pveAgg.sendRum()
67
+ }
68
+ })
69
+ }, 'session', sharedEE)
70
+
59
71
  drain(agentRef.agentIdentifier, 'session')
60
72
 
61
73
  return agentRef.runtime.session
@@ -49,7 +49,10 @@ export class InstrumentBase extends FeatureBase {
49
49
  * @type {Promise} Assigned immediately after @see importAggregator runs. Serves as a signal for when the inner async fn finishes execution. Useful for features to await
50
50
  * one another if there are inter-features dependencies.
51
51
  */
52
- this.onAggregateImported = undefined
52
+ this.loadedSuccessfully = undefined
53
+ this.onAggregateImported = new Promise(resolve => {
54
+ this.loadedSuccessfully = resolve
55
+ })
53
56
 
54
57
  /**
55
58
  * used in conjunction with newrelic.start() to defer harvesting in features
@@ -83,11 +86,6 @@ export class InstrumentBase extends FeatureBase {
83
86
  importAggregator (agentRef, fetchAggregator, argsObjFromInstrument = {}) {
84
87
  if (this.featAggregate) return
85
88
 
86
- let loadedSuccessfully
87
- this.onAggregateImported = new Promise(resolve => {
88
- loadedSuccessfully = resolve
89
- })
90
-
91
89
  const importLater = async () => {
92
90
  // wait for the deferred promise to resolve before proceeding
93
91
  // this will resolve immediately if the feature is auto-started,
@@ -113,20 +111,20 @@ export class InstrumentBase extends FeatureBase {
113
111
  try {
114
112
  if (!this.#shouldImportAgg(this.featureName, session, agentRef.init)) {
115
113
  drain(this.agentIdentifier, this.featureName)
116
- loadedSuccessfully(false) // aggregate module isn't loaded at all
114
+ this.loadedSuccessfully(false) // aggregate module isn't loaded at all
117
115
  return
118
116
  }
119
117
  const { Aggregate } = await fetchAggregator()
120
118
  this.featAggregate = new Aggregate(agentRef, argsObjFromInstrument)
121
119
 
122
120
  agentRef.runtime.harvester.initializedAggregates.push(this.featAggregate) // "subscribe" the feature to future harvest intervals (PVE will start the timer)
123
- loadedSuccessfully(true)
121
+ this.loadedSuccessfully(true)
124
122
  } catch (e) {
125
123
  warn(34, e)
126
124
  this.abortHandler?.() // undo any important alterations made to the page
127
125
  // not supported yet but nice to do: "abort" this agent's EE for this feature specifically
128
126
  drain(this.agentIdentifier, this.featureName, true)
129
- loadedSuccessfully(false)
127
+ this.loadedSuccessfully(false)
130
128
  if (this.ee) this.ee.abort()
131
129
  }
132
130
  }
@@ -48,6 +48,27 @@ export class RegisteredEntity {
48
48
  warn(35, 'addPageAction')
49
49
  }
50
50
 
51
+ /**
52
+ * Records a custom event with a specified eventType and attributes.
53
+ * {@link https://docs.newrelic.com/docs/browser/new-relic-browser/browser-apis/recordCustomEvent/}
54
+ * @param {string} eventType The eventType to store the event as.
55
+ * @param {Object} [attributes] JSON object with one or more key/value pairs. For example: {key:"value"}.
56
+ */
57
+ recordCustomEvent (eventType, attributes) {
58
+ warn(35, 'recordCustomEvent')
59
+ }
60
+
61
+ /**
62
+ * Measures a task that is recorded as a BrowserPerformance event.
63
+ * {@link https://docs.newrelic.com/docs/browser/new-relic-browser/browser-apis/measure/}
64
+ * @param {string} name The name of the task
65
+ * @param {{start: number, end: number, duration: number, customAttributes: object}} [options] An object used to control the way the measure API operates
66
+ * @returns {{start: number, end: number, duration: number, customAttributes: object}} Measurement details
67
+ */
68
+ measure (name, options) {
69
+ warn(35, 'measure')
70
+ }
71
+
51
72
  /**
52
73
  * Adds a user-defined attribute name and value to subsequent events on the page for the registered target. Note -- the persist flag does not work with the register API.
53
74
  * {@link https://docs.newrelic.com/docs/browser/new-relic-browser/browser-apis/setcustomattribute/}
@@ -23,6 +23,7 @@ import { setupSetCustomAttributeAPI } from './api/setCustomAttribute'
23
23
  import { setupSetUserIdAPI } from './api/setUserId'
24
24
  import { setupSetApplicationVersionAPI } from './api/setApplicationVersion'
25
25
  import { setupStartAPI } from './api/start'
26
+ import { setupConsentAPI } from './api/consent'
26
27
 
27
28
  /**
28
29
  * @typedef {Object} AgentOptions
@@ -70,6 +71,7 @@ export class Agent extends AgentBase {
70
71
  setupSetUserIdAPI(this)
71
72
  setupSetApplicationVersionAPI(this)
72
73
  setupStartAPI(this)
74
+ setupConsentAPI(this)
73
75
 
74
76
  this.run()
75
77
  }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Copyright 2020-2025 New Relic, Inc. All rights reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+ import { prefix, CONSENT } from './constants'
6
+ import { setupAPI } from './sharedHandlers'
7
+ import { handle } from '../../common/event-emitter/handle'
8
+ import { warn } from '../../common/util/console'
9
+
10
+ export function setupConsentAPI (agent) {
11
+ setupAPI(CONSENT, function (accept) {
12
+ if (accept !== undefined && typeof accept !== 'boolean') {
13
+ warn(65, typeof accept)
14
+ return
15
+ }
16
+ handle(prefix + CONSENT, [accept], undefined, 'session', agent.ee)
17
+ }, agent)
18
+ }
@@ -25,3 +25,4 @@ export const SET_USER_ID = 'setUserId'
25
25
  export const START = 'start'
26
26
  export const WRAP_LOGGER = 'wrapLogger'
27
27
  export const MEASURE = 'measure'
28
+ export const CONSENT = 'consent'