@newrelic/browser-agent 1.261.2 → 1.263.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 (103) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/dist/cjs/common/config/state/configurable.js +4 -4
  3. package/dist/cjs/common/config/state/init.js +5 -3
  4. package/dist/cjs/common/constants/env.cdn.js +1 -1
  5. package/dist/cjs/common/constants/env.npm.js +1 -1
  6. package/dist/cjs/common/context/shared-context.js +2 -2
  7. package/dist/cjs/common/drain/drain.js +22 -16
  8. package/dist/cjs/common/harvest/harvest.js +1 -1
  9. package/dist/cjs/common/session/session-entity.js +2 -2
  10. package/dist/cjs/common/timing/time-keeper.js +13 -2
  11. package/dist/cjs/common/util/console.js +3 -4
  12. package/dist/cjs/common/util/obfuscate.js +3 -3
  13. package/dist/cjs/common/wrap/wrap-logger.js +2 -0
  14. package/dist/cjs/common/wrap/wrap-xhr.js +1 -1
  15. package/dist/cjs/features/logging/aggregate/index.js +27 -2
  16. package/dist/cjs/features/logging/constants.js +2 -5
  17. package/dist/cjs/features/logging/shared/utils.js +2 -18
  18. package/dist/cjs/features/metrics/aggregate/index.js +16 -0
  19. package/dist/cjs/features/page_view_event/aggregate/index.js +20 -4
  20. package/dist/cjs/features/session_replay/aggregate/index.js +1 -1
  21. package/dist/cjs/features/session_replay/shared/recorder.js +1 -1
  22. package/dist/cjs/features/session_trace/aggregate/index.js +1 -1
  23. package/dist/cjs/features/spa/aggregate/index.js +1 -1
  24. package/dist/cjs/features/utils/aggregate-base.js +4 -3
  25. package/dist/cjs/features/utils/instrument-base.js +2 -2
  26. package/dist/cjs/loaders/agent-base.js +1 -1
  27. package/dist/cjs/loaders/agent.js +3 -4
  28. package/dist/cjs/loaders/api/api.js +13 -16
  29. package/dist/cjs/loaders/micro-agent.js +5 -5
  30. package/dist/esm/common/config/state/configurable.js +4 -4
  31. package/dist/esm/common/config/state/init.js +5 -3
  32. package/dist/esm/common/constants/env.cdn.js +1 -1
  33. package/dist/esm/common/constants/env.npm.js +1 -1
  34. package/dist/esm/common/context/shared-context.js +2 -2
  35. package/dist/esm/common/drain/drain.js +22 -16
  36. package/dist/esm/common/harvest/harvest.js +1 -1
  37. package/dist/esm/common/session/session-entity.js +2 -2
  38. package/dist/esm/common/timing/time-keeper.js +12 -2
  39. package/dist/esm/common/util/console.js +3 -4
  40. package/dist/esm/common/util/obfuscate.js +3 -3
  41. package/dist/esm/common/wrap/wrap-logger.js +2 -0
  42. package/dist/esm/common/wrap/wrap-xhr.js +1 -1
  43. package/dist/esm/features/logging/aggregate/index.js +28 -3
  44. package/dist/esm/features/logging/constants.js +1 -4
  45. package/dist/esm/features/logging/shared/utils.js +2 -18
  46. package/dist/esm/features/metrics/aggregate/index.js +16 -0
  47. package/dist/esm/features/page_view_event/aggregate/index.js +21 -5
  48. package/dist/esm/features/session_replay/aggregate/index.js +1 -1
  49. package/dist/esm/features/session_replay/shared/recorder.js +1 -1
  50. package/dist/esm/features/session_trace/aggregate/index.js +1 -1
  51. package/dist/esm/features/spa/aggregate/index.js +1 -1
  52. package/dist/esm/features/utils/aggregate-base.js +4 -3
  53. package/dist/esm/features/utils/instrument-base.js +2 -2
  54. package/dist/esm/loaders/agent-base.js +1 -1
  55. package/dist/esm/loaders/agent.js +3 -4
  56. package/dist/esm/loaders/api/api.js +15 -18
  57. package/dist/esm/loaders/micro-agent.js +5 -5
  58. package/dist/types/common/config/state/init.d.ts.map +1 -1
  59. package/dist/types/common/drain/drain.d.ts.map +1 -1
  60. package/dist/types/common/timing/time-keeper.d.ts +2 -1
  61. package/dist/types/common/timing/time-keeper.d.ts.map +1 -1
  62. package/dist/types/common/util/console.d.ts +1 -1
  63. package/dist/types/common/util/console.d.ts.map +1 -1
  64. package/dist/types/common/wrap/wrap-logger.d.ts.map +1 -1
  65. package/dist/types/features/logging/aggregate/index.d.ts +1 -1
  66. package/dist/types/features/logging/aggregate/index.d.ts.map +1 -1
  67. package/dist/types/features/logging/constants.d.ts +0 -3
  68. package/dist/types/features/logging/constants.d.ts.map +1 -1
  69. package/dist/types/features/logging/shared/utils.d.ts +1 -1
  70. package/dist/types/features/logging/shared/utils.d.ts.map +1 -1
  71. package/dist/types/features/metrics/aggregate/index.d.ts.map +1 -1
  72. package/dist/types/features/page_view_event/aggregate/index.d.ts.map +1 -1
  73. package/dist/types/features/session_replay/shared/recorder.d.ts.map +1 -1
  74. package/dist/types/features/utils/aggregate-base.d.ts.map +1 -1
  75. package/dist/types/loaders/agent.d.ts.map +1 -1
  76. package/dist/types/loaders/api/api.d.ts.map +1 -1
  77. package/package.json +1 -1
  78. package/src/common/config/state/configurable.js +4 -4
  79. package/src/common/config/state/init.js +4 -3
  80. package/src/common/context/shared-context.js +2 -2
  81. package/src/common/drain/drain.js +21 -16
  82. package/src/common/harvest/harvest.js +1 -1
  83. package/src/common/session/session-entity.js +2 -2
  84. package/src/common/timing/time-keeper.js +14 -2
  85. package/src/common/util/console.js +3 -4
  86. package/src/common/util/obfuscate.js +3 -3
  87. package/src/common/wrap/wrap-logger.js +2 -0
  88. package/src/common/wrap/wrap-xhr.js +1 -1
  89. package/src/features/logging/aggregate/index.js +30 -3
  90. package/src/features/logging/constants.js +0 -4
  91. package/src/features/logging/shared/utils.js +2 -19
  92. package/src/features/metrics/aggregate/index.js +12 -0
  93. package/src/features/page_view_event/aggregate/index.js +22 -5
  94. package/src/features/session_replay/aggregate/index.js +1 -1
  95. package/src/features/session_replay/shared/recorder.js +1 -1
  96. package/src/features/session_trace/aggregate/index.js +1 -1
  97. package/src/features/spa/aggregate/index.js +1 -1
  98. package/src/features/utils/aggregate-base.js +4 -3
  99. package/src/features/utils/instrument-base.js +2 -2
  100. package/src/loaders/agent-base.js +1 -1
  101. package/src/loaders/agent.js +3 -4
  102. package/src/loaders/api/api.js +15 -18
  103. package/src/loaders/micro-agent.js +5 -5
@@ -15,7 +15,6 @@ var _instrument = require("../features/page_view_event/instrument");
15
15
  var _aggregator = require("../common/aggregate/aggregator");
16
16
  var _nreum = require("../common/window/nreum");
17
17
  var _console = require("../common/util/console");
18
- var _stringify = require("../common/util/stringify");
19
18
  var _runtime = require("../common/constants/runtime");
20
19
  // important side effects
21
20
 
@@ -39,7 +38,7 @@ class Agent extends _agentBase.AgentBase {
39
38
  if (!_runtime.globalScope) {
40
39
  // We could not determine the runtime environment. Short-circuite the agent here
41
40
  // to avoid possible exceptions later that may cause issues with customer's application.
42
- (0, _console.warn)('Failed to initialize the agent. Could not determine the runtime environment.');
41
+ (0, _console.warn)(21);
43
42
  return;
44
43
  }
45
44
  this.sharedAggregator = new _aggregator.Aggregator({
@@ -78,11 +77,11 @@ class Agent extends _agentBase.AgentBase {
78
77
  if (!this.runSoftNavOverSpa && InstrumentCtor.featureName === _features.FEATURE_NAMES.softNav) return;
79
78
  const dependencies = (0, _featureDependencies.getFeatureDependencyNames)(InstrumentCtor.featureName);
80
79
  const hasAllDeps = dependencies.every(featName => featName in this.features); // any other feature(s) this depends on should've been initialized on prior iterations by priority order
81
- if (!hasAllDeps) (0, _console.warn)("".concat(InstrumentCtor.featureName, " is enabled but one or more dependent features has not been initialized (").concat((0, _stringify.stringify)(dependencies), "). This may cause unintended consequences or missing data..."));
80
+ if (!hasAllDeps) (0, _console.warn)(36, InstrumentCtor.featureName);
82
81
  this.features[InstrumentCtor.featureName] = new InstrumentCtor(this.agentIdentifier, this.sharedAggregator);
83
82
  });
84
83
  } catch (err) {
85
- (0, _console.warn)('Failed to initialize all enabled instrument classes (agent aborted) -', err);
84
+ (0, _console.warn)(22, err);
86
85
  for (const featName in this.features) {
87
86
  // this.features hold only features that have been instantiated
88
87
  this.features[featName].abortHandler?.();
@@ -43,7 +43,9 @@ function setTopLevelCallers() {
43
43
  }
44
44
  let returnVals = [];
45
45
  Object.values(nr.initializedAgents).forEach(val => {
46
- if (val.exposed && val.api[fnName]) {
46
+ if (!val || !val.api) {
47
+ (0, _console.warn)(38, fnName);
48
+ } else if (val.exposed && val.api[fnName]) {
47
49
  returnVals.push(val.api[fnName](...args));
48
50
  }
49
51
  });
@@ -68,23 +70,18 @@ function setAPI(agentIdentifier, forceDrain) {
68
70
  customAttributes = {},
69
71
  level = _constants4.LOG_LEVELS.INFO
70
72
  } = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
71
- if (!customAttributes || typeof customAttributes !== 'object') customAttributes = {};
72
- if (typeof message !== 'string' || !message) return (0, _console.warn)(_constants4.LOGGING_IGNORED + 'invalid message');
73
- if (!(0, _utils.isValidLogLevel)(level)) return (0, _console.warn)(_constants4.LOGGING_LEVEL_FAILURE_MESSAGE + level, _constants4.LOG_LEVELS);
74
- if (message.length > _constants4.MAX_PAYLOAD_SIZE) return (0, _console.warn)(_constants4.LOGGING_IGNORED + '> ' + _constants4.MAX_PAYLOAD_SIZE + ' bytes: ', message.slice(0, 25) + '...');
75
- (0, _utils.bufferLog)(instanceEE, message, customAttributes, level.toUpperCase());
73
+ (0, _handle.handle)(_constants.SUPPORTABILITY_METRIC_CHANNEL, ['API/log/called'], undefined, _features.FEATURE_NAMES.metrics, instanceEE);
74
+ (0, _utils.bufferLog)(instanceEE, message, customAttributes, level);
76
75
  };
77
76
  apiInterface.wrapLogger = function (parent, functionName) {
78
77
  let {
79
78
  customAttributes = {},
80
79
  level = _constants4.LOG_LEVELS.INFO
81
80
  } = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
82
- if (!customAttributes || typeof customAttributes !== 'object') customAttributes = {};
83
- if (!(typeof parent === 'object' && !!parent && typeof functionName === 'string' && !!functionName && typeof parent[functionName] === 'function' && typeof customAttributes === 'object')) return (0, _console.warn)(_constants4.LOGGING_FAILURE_MESSAGE + 'invalid argument(s)');
84
- if (!(0, _utils.isValidLogLevel)(level)) return (0, _console.warn)(_constants4.LOGGING_FAILURE_MESSAGE + _constants4.LOGGING_LEVEL_FAILURE_MESSAGE + level, _constants4.LOG_LEVELS);
81
+ (0, _handle.handle)(_constants.SUPPORTABILITY_METRIC_CHANNEL, ['API/wrapLogger/called'], undefined, _features.FEATURE_NAMES.metrics, instanceEE);
85
82
  (0, _wrapLogger.wrapLogger)(instanceEE, parent, functionName, {
86
83
  customAttributes,
87
- level: level.toUpperCase()
84
+ level
88
85
  });
89
86
  };
90
87
 
@@ -126,11 +123,11 @@ function setAPI(agentIdentifier, forceDrain) {
126
123
  apiInterface.setCustomAttribute = function (name, value) {
127
124
  let persistAttribute = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
128
125
  if (typeof name !== 'string') {
129
- (0, _console.warn)("Failed to execute setCustomAttribute.\nName must be a string type, but a type of <".concat(typeof name, "> was provided."));
126
+ (0, _console.warn)(39, typeof name);
130
127
  return;
131
128
  }
132
129
  if (!(['string', 'number', 'boolean'].includes(typeof value) || value === null)) {
133
- (0, _console.warn)("Failed to execute setCustomAttribute.\nNon-null value must be a string, number or boolean type, but a type of <".concat(typeof value, "> was provided."));
130
+ (0, _console.warn)(40, typeof value);
134
131
  return;
135
132
  }
136
133
  return appendJsAttribute(name, value, 'setCustomAttribute', persistAttribute);
@@ -142,7 +139,7 @@ function setAPI(agentIdentifier, forceDrain) {
142
139
  */
143
140
  apiInterface.setUserId = function (value) {
144
141
  if (!(typeof value === 'string' || value === null)) {
145
- (0, _console.warn)("Failed to execute setUserId.\nNon-null value must be a string type, but a type of <".concat(typeof value, "> was provided."));
142
+ (0, _console.warn)(41, typeof value);
146
143
  return;
147
144
  }
148
145
  return appendJsAttribute('enduser.id', value, 'setUserId', true);
@@ -155,7 +152,7 @@ function setAPI(agentIdentifier, forceDrain) {
155
152
  */
156
153
  apiInterface.setApplicationVersion = function (value) {
157
154
  if (!(typeof value === 'string' || value === null)) {
158
- (0, _console.warn)("Failed to execute setApplicationVersion. Expected <String | null>, but got <".concat(typeof value, ">."));
155
+ (0, _console.warn)(42, typeof value);
159
156
  return;
160
157
  }
161
158
  return appendJsAttribute('application.version', value, 'setApplicationVersion', false);
@@ -165,7 +162,7 @@ function setAPI(agentIdentifier, forceDrain) {
165
162
  (0, _handle.handle)(_constants.SUPPORTABILITY_METRIC_CHANNEL, ['API/start/called'], undefined, _features.FEATURE_NAMES.metrics, instanceEE);
166
163
  instanceEE.emit('manual-start-all');
167
164
  } catch (err) {
168
- (0, _console.warn)('An unexpected issue occurred', err);
165
+ (0, _console.warn)(23, err);
169
166
  }
170
167
  };
171
168
  apiInterface[_constants2.SR_EVENT_EMITTER_TYPES.RECORD] = function () {
@@ -234,7 +231,7 @@ function setAPI(agentIdentifier, forceDrain) {
234
231
  setAPI(agentIdentifier);
235
232
  (0, _drain.drain)(agentIdentifier, 'api');
236
233
  }).catch(err => {
237
- (0, _console.warn)('Downloading runtime APIs failed...', err);
234
+ (0, _console.warn)(27, err);
238
235
  instanceEE.abort();
239
236
  });
240
237
  }
@@ -65,11 +65,11 @@ class MicroAgent extends _agentBase.AgentBase {
65
65
  const featNames = nonAutoFeatures;
66
66
  if (features === undefined) features = featNames;else {
67
67
  features = Array.isArray(features) && features.length ? features : [features];
68
- if (features.some(f => !featNames.includes(f))) return (0, _console.warn)("Invalid feature name supplied. Acceptable feature names are: ".concat(featNames));
68
+ if (features.some(f => !featNames.includes(f))) return (0, _console.warn)(37, featNames);
69
69
  if (!features.includes(_features.FEATURE_NAMES.pageViewEvent)) features.push(_features.FEATURE_NAMES.pageViewEvent);
70
70
  }
71
71
  } catch (err) {
72
- (0, _console.warn)('An unexpected issue occurred', err);
72
+ (0, _console.warn)(23, err);
73
73
  }
74
74
  try {
75
75
  const enabledFeatures = (0, _enabledFeatures.getEnabledFeatures)(this.agentIdentifier);
@@ -77,7 +77,7 @@ class MicroAgent extends _agentBase.AgentBase {
77
77
  // a biproduct of doing this is that the "session manager" is automatically handled through importing this feature
78
78
  this.features.page_view_event = new _instrument.Instrument(this.agentIdentifier, this.sharedAggregator);
79
79
  } catch (err) {
80
- (0, _console.warn)('Something prevented the agent from instrumenting.', err);
80
+ (0, _console.warn)(24, err);
81
81
  }
82
82
  (0, _load.onWindowLoad)(() => {
83
83
  // these features do not import an "instrument" file, meaning they are only hooked up to the API.
@@ -93,13 +93,13 @@ class MicroAgent extends _agentBase.AgentBase {
93
93
  Aggregate
94
94
  } = _ref2;
95
95
  this.features[f] = new Aggregate(this.agentIdentifier, this.sharedAggregator);
96
- }).catch(err => (0, _console.warn)('Something prevented the agent from being downloaded.', err));
96
+ }).catch(err => (0, _console.warn)(25, err));
97
97
  }
98
98
  });
99
99
  });
100
100
  return true;
101
101
  } catch (err) {
102
- (0, _console.warn)('Failed to initialize instrument classes.', err);
102
+ (0, _console.warn)(26, err);
103
103
  return false;
104
104
  }
105
105
  }
@@ -1,8 +1,8 @@
1
1
  import { warn } from '../../util/console';
2
2
  export function getModeledObject(obj, model) {
3
3
  try {
4
- if (!obj || typeof obj !== 'object') return warn('Setting a Configurable requires an object as input');
5
- if (!model || typeof model !== 'object') return warn('Setting a Configurable requires a model to set its initial properties');
4
+ if (!obj || typeof obj !== 'object') return warn(3);
5
+ if (!model || typeof model !== 'object') return warn(4);
6
6
  // allow getters and setters to pass from model to target
7
7
  const output = Object.create(Object.getPrototypeOf(model), Object.getOwnPropertyDescriptors(model));
8
8
  const target = Object.keys(output).length === 0 ? obj : output;
@@ -15,11 +15,11 @@ export function getModeledObject(obj, model) {
15
15
  }
16
16
  if (Array.isArray(obj[key]) && Array.isArray(model[key])) output[key] = Array.from(new Set([...obj[key], ...model[key]]));else if (typeof obj[key] === 'object' && typeof model[key] === 'object') output[key] = getModeledObject(obj[key], model[key]);else output[key] = obj[key];
17
17
  } catch (e) {
18
- warn('An error occurred while setting a property of a Configurable', e);
18
+ warn(1, e);
19
19
  }
20
20
  }
21
21
  return output;
22
22
  } catch (err) {
23
- warn('An error occured while setting a Configurable', err);
23
+ warn(2, err);
24
24
  }
25
25
  }
@@ -109,6 +109,8 @@ const model = () => {
109
109
  // serialize images for collection without public asset url -- right now this is only useful for testing as it easily generates payloads too large to be harvested
110
110
  inline_stylesheet: true,
111
111
  // serialize css for collection without public asset url
112
+ fix_stylesheets: true,
113
+ // fetch missing stylesheet resources for inlining, only works if 'inline_stylesheet' is also true
112
114
  // recording config settings
113
115
  mask_all_inputs: true,
114
116
  // this has a getter/setter to facilitate validation of the selectors
@@ -116,7 +118,7 @@ const model = () => {
116
118
  return hiddenState.mask_selector;
117
119
  },
118
120
  set mask_text_selector(val) {
119
- if (isValidSelector(val)) hiddenState.mask_selector = "".concat(val, ",").concat(nrMask);else if (val === '' || val === null) hiddenState.mask_selector = nrMask;else warn('An invalid session_replay.mask_selector was provided. \'*\' will be used.', val);
121
+ if (isValidSelector(val)) hiddenState.mask_selector = "".concat(val, ",").concat(nrMask);else if (val === '' || val === null) hiddenState.mask_selector = nrMask;else warn(5, val);
120
122
  },
121
123
  // these properties only have getters because they are enforcable constants and should error if someone tries to override them
122
124
  get block_class() {
@@ -134,7 +136,7 @@ const model = () => {
134
136
  return hiddenState.block_selector;
135
137
  },
136
138
  set block_selector(val) {
137
- if (isValidSelector(val)) hiddenState.block_selector += ",".concat(val);else if (val !== '') warn('An invalid session_replay.block_selector was provided and will not be used', val);
139
+ if (isValidSelector(val)) hiddenState.block_selector += ",".concat(val);else if (val !== '') warn(6, val);
138
140
  },
139
141
  // password: must always be present and true no matter what customer sets
140
142
  get mask_input_options() {
@@ -144,7 +146,7 @@ const model = () => {
144
146
  if (val && typeof val === 'object') hiddenState.mask_input_options = {
145
147
  ...val,
146
148
  password: true
147
- };else warn('An invalid session_replay.mask_input_option was provided and will not be used', val);
149
+ };else warn(7, val);
148
150
  }
149
151
  },
150
152
  session_trace: {
@@ -6,7 +6,7 @@
6
6
  /**
7
7
  * Exposes the version of the agent
8
8
  */
9
- export const VERSION = "1.261.2";
9
+ export const VERSION = "1.263.0";
10
10
 
11
11
  /**
12
12
  * Exposes the build type of the agent
@@ -6,7 +6,7 @@
6
6
  /**
7
7
  * Exposes the version of the agent
8
8
  */
9
- export const VERSION = "1.261.2";
9
+ export const VERSION = "1.263.0";
10
10
 
11
11
  /**
12
12
  * Exposes the build type of the agent
@@ -6,7 +6,7 @@ const model = {
6
6
  export class SharedContext {
7
7
  constructor(context) {
8
8
  try {
9
- if (typeof context !== 'object') return warn('shared context requires an object as input');
9
+ if (typeof context !== 'object') return warn(8);
10
10
  this.sharedContext = {};
11
11
  Object.assign(this.sharedContext, model);
12
12
  Object.entries(context).forEach(_ref => {
@@ -14,7 +14,7 @@ export class SharedContext {
14
14
  if (Object.keys(model).includes(key)) this.sharedContext[key] = value;
15
15
  });
16
16
  } catch (err) {
17
- warn('An error occurred while setting SharedContext', err);
17
+ warn(9, err);
18
18
  }
19
19
  }
20
20
  }
@@ -34,8 +34,9 @@ export function registerDrain(agentIdentifier, group) {
34
34
  * @param {*} group - The named "bucket" to be removed from the registry
35
35
  */
36
36
  export function deregisterDrain(agentIdentifier, group) {
37
- curateRegistry(agentIdentifier);
37
+ if (!agentIdentifier || !registry[agentIdentifier]) return;
38
38
  if (registry[agentIdentifier].get(group)) registry[agentIdentifier].delete(group);
39
+ drainGroup(agentIdentifier, group, false);
39
40
  if (registry[agentIdentifier].size) checkCanDrainAll(agentIdentifier);
40
41
  }
41
42
 
@@ -95,27 +96,32 @@ function checkCanDrainAll(agentIdentifier) {
95
96
  * @param {*} group - The name of a particular feature's event "bucket".
96
97
  */
97
98
  function drainGroup(agentIdentifier, group) {
99
+ let activateGroup = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true;
98
100
  const baseEE = agentIdentifier ? ee.get(agentIdentifier) : ee;
99
101
  const handlers = defaultRegister.handlers; // other storage in registerHandler
100
- if (!baseEE.backlog || !handlers) return;
101
- var bufferedEventsInGroup = baseEE.backlog[group];
102
- var groupHandlers = handlers[group]; // each group in the registerHandler storage
103
- if (groupHandlers) {
104
- // We don't cache the length of the buffer while looping because events might still be added while processing.
105
- for (var i = 0; bufferedEventsInGroup && i < bufferedEventsInGroup.length; ++i) {
106
- // eslint-disable-line no-unmodified-loop-condition
107
- emitEvent(bufferedEventsInGroup[i], groupHandlers);
108
- }
109
- mapOwn(groupHandlers, function (eventType, handlerRegistrationList) {
110
- mapOwn(handlerRegistrationList, function (i, registration) {
111
- // registration is an array of: [targetEE, eventHandler]
112
- registration[0].on(eventType, registration[1]);
102
+ if (baseEE.aborted || !baseEE.backlog || !handlers) return;
103
+
104
+ // Only activated features being drained should run queued listeners on buffered events. Deactivated features only need to release memory.
105
+ if (activateGroup) {
106
+ const bufferedEventsInGroup = baseEE.backlog[group];
107
+ const groupHandlers = handlers[group]; // each group in the registerHandler storage
108
+ if (groupHandlers) {
109
+ // We don't cache the length of the buffer while looping because events might still be added while processing.
110
+ for (let i = 0; bufferedEventsInGroup && i < bufferedEventsInGroup.length; ++i) {
111
+ // eslint-disable-line no-unmodified-loop-condition
112
+ emitEvent(bufferedEventsInGroup[i], groupHandlers);
113
+ }
114
+ mapOwn(groupHandlers, function (eventType, handlerRegistrationList) {
115
+ mapOwn(handlerRegistrationList, function (i, registration) {
116
+ // registration is an array of: [targetEE, eventHandler]
117
+ registration[0].on(eventType, registration[1]);
118
+ });
113
119
  });
114
- });
120
+ }
115
121
  }
116
122
  if (!baseEE.isolatedBacklog) delete handlers[group];
117
123
  baseEE.backlog[group] = null;
118
- baseEE.emit('drain-' + group, []);
124
+ baseEE.emit('drain-' + group, []); // TODO: Code exists purely for a unit test and needs to be refined
119
125
  }
120
126
 
121
127
  /**
@@ -148,7 +148,7 @@ export class Harvest extends SharedContext {
148
148
  body = stringify(body);
149
149
  }
150
150
  /** Warn --once per endpoint-- if the agent tries to send large payloads */
151
- if (body.length > 750000 && (warnings[endpoint] = (warnings?.[endpoint] || 0) + 1) === 1) warn("The Browser Agent is attempting to send a very large payload to /".concat(endpoint, ". This is usually tied to large amounts of custom attributes. Please check your configurations."));
151
+ if (body.length > 750000 && (warnings[endpoint] = (warnings?.[endpoint] || 0) + 1) === 1) warn(28, endpoint);
152
152
  }
153
153
  if (!body || body.length === 0 || body === '{}' || body === '[]') {
154
154
  // If body is null, undefined, or an empty object or array, send an empty string instead
@@ -190,7 +190,7 @@ export class SessionEntity {
190
190
  }
191
191
  return obj;
192
192
  } catch (e) {
193
- warn('Failed to read from storage API', e);
193
+ warn(10, e);
194
194
  // storage is inaccessible
195
195
  return {};
196
196
  }
@@ -216,7 +216,7 @@ export class SessionEntity {
216
216
  return data;
217
217
  } catch (e) {
218
218
  // storage is inaccessible
219
- warn('Failed to write to the storage API', e);
219
+ warn(11, e);
220
220
  return null;
221
221
  }
222
222
  }
@@ -1,5 +1,6 @@
1
1
  import { originTime } from '../constants/runtime';
2
2
  import { getRuntime } from '../config/config';
3
+ const rfc2616Regex = /^(Mon|Tue|Wed|Thu|Fri|Sat|Sun), ([0-3][0-9]) (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) ([0-9]{4}) ([01][0-9]|2[0-3])(:[0-5][0-9]){2} GMT$/;
3
4
 
4
5
  /**
5
6
  * Class used to adjust the timestamp of harvested data to New Relic server time. This
@@ -29,7 +30,7 @@ export class TimeKeeper {
29
30
  /**
30
31
  * Represents whether the timekeeper is in a state that it can accurately convert
31
32
  * timestamps.
32
- * @type {number}
33
+ * @type {boolean}
33
34
  */
34
35
  #ready = false;
35
36
  constructor(agentIdentifier) {
@@ -42,6 +43,9 @@ export class TimeKeeper {
42
43
  get correctedOriginTime() {
43
44
  return this.#correctedOriginTime;
44
45
  }
46
+ get localTimeDiff() {
47
+ return this.#localTimeDiff;
48
+ }
45
49
 
46
50
  /**
47
51
  * Process a rum request to calculate NR server time.
@@ -50,12 +54,16 @@ export class TimeKeeper {
50
54
  * @param endTime {number} The end time of the RUM request
51
55
  */
52
56
  processRumRequest(rumRequest, startTime, endTime) {
53
- this.processStoredDiff();
57
+ this.processStoredDiff(); // Check session entity for stored time diff
54
58
  if (this.#ready) return; // Server time calculated from session entity
59
+
55
60
  const responseDateHeader = rumRequest.getResponseHeader('Date');
56
61
  if (!responseDateHeader) {
57
62
  throw new Error('Missing date header on rum response.');
58
63
  }
64
+ if (!rfc2616Regex.test(responseDateHeader)) {
65
+ throw new Error('Date header invalid format.');
66
+ }
59
67
  const medianRumOffset = (endTime - startTime) / 2;
60
68
  const serverOffset = startTime + medianRumOffset;
61
69
 
@@ -92,6 +100,8 @@ export class TimeKeeper {
92
100
 
93
101
  /** Process the session entity and use the info to set the main time calculations if present */
94
102
  processStoredDiff() {
103
+ if (this.#ready) return; // Time diff has already been calculated
104
+
95
105
  const storedServerTimeDiff = this.#session?.read()?.serverTimeDiff;
96
106
  if (typeof storedServerTimeDiff === 'number' && !isNaN(storedServerTimeDiff)) {
97
107
  this.#localTimeDiff = storedServerTimeDiff;
@@ -4,8 +4,7 @@
4
4
  * @param {*} [secondary] Secondary data to include, usually an error or object
5
5
  * @returns
6
6
  */
7
- export function warn(message, secondary) {
8
- if (typeof console.warn !== 'function') return;
9
- console.warn("New Relic: ".concat(message));
10
- if (secondary) console.warn(secondary);
7
+ export function warn(code, secondary) {
8
+ if (typeof console.debug !== 'function') return;
9
+ console.debug("New Relic Warning: https://github.com/newrelic/newrelic-browser-agent/blob/main/docs/warning-codes.md#".concat(code), secondary);
11
10
  }
@@ -45,15 +45,15 @@ export function validateRules(rules) {
45
45
  var invalidRegexDetected = false;
46
46
  for (var i = 0; i < rules.length; i++) {
47
47
  if (!('regex' in rules[i])) {
48
- warn('An obfuscation replacement rule was detected missing a "regex" value.');
48
+ warn(12);
49
49
  invalidRegexDetected = true;
50
50
  } else if (typeof rules[i].regex !== 'string' && !(rules[i].regex instanceof RegExp)) {
51
- warn('An obfuscation replacement rule contains a "regex" value with an invalid type (must be a string or RegExp)');
51
+ warn(13);
52
52
  invalidRegexDetected = true;
53
53
  }
54
54
  var replacement = rules[i].replacement;
55
55
  if (replacement && typeof replacement !== 'string') {
56
- warn('An obfuscation replacement rule contains a "replacement" value with an invalid type (must be a string)');
56
+ warn(14);
57
57
  invalidReplacementDetected = true;
58
58
  }
59
59
  }
@@ -9,6 +9,7 @@
9
9
 
10
10
  import { ee as baseEE, contextId } from '../event-emitter/contextual-ee';
11
11
  import { EventContext } from '../event-emitter/event-context';
12
+ import { warn } from '../util/console';
12
13
  import { createWrapperWithEmitter as wfn } from './wrap-function';
13
14
 
14
15
  /**
@@ -20,6 +21,7 @@ import { createWrapperWithEmitter as wfn } from './wrap-function';
20
21
  */
21
22
  // eslint-disable-next-line
22
23
  export function wrapLogger(sharedEE, parent, loggerFn, context) {
24
+ if (!(typeof parent === 'object' && !!parent && typeof loggerFn === 'string' && !!loggerFn && typeof parent[loggerFn] === 'function')) return warn(29);
23
25
  const ee = scopedEE(sharedEE);
24
26
  const wrapFn = wfn(ee);
25
27
 
@@ -49,7 +49,7 @@ export function wrapXhr(sharedEE) {
49
49
  ee.emit('new-xhr', [xhr], context);
50
50
  xhr.addEventListener(READY_STATE_CHANGE, wrapXHR(context), eventListenerOpts(false));
51
51
  } catch (e) {
52
- warn('An error occurred while intercepting XHR', e);
52
+ warn(15, e);
53
53
  try {
54
54
  ee.emit('internal-error', [e]);
55
55
  } catch (err) {
@@ -6,8 +6,9 @@ import { warn } from '../../../common/util/console';
6
6
  import { stringify } from '../../../common/util/stringify';
7
7
  import { SUPPORTABILITY_METRIC_CHANNEL } from '../../metrics/constants';
8
8
  import { AggregateBase } from '../../utils/aggregate-base';
9
- import { FEATURE_NAME, LOGGING_EVENT_EMITTER_CHANNEL, LOGGING_IGNORED, MAX_PAYLOAD_SIZE } from '../constants';
9
+ import { FEATURE_NAME, LOGGING_EVENT_EMITTER_CHANNEL, LOG_LEVELS, MAX_PAYLOAD_SIZE } from '../constants';
10
10
  import { Log } from '../shared/log';
11
+ import { isValidLogLevel } from '../shared/utils';
11
12
  export class Aggregate extends AggregateBase {
12
13
  static featureName = FEATURE_NAME;
13
14
  #agentRuntime;
@@ -38,13 +39,37 @@ export class Aggregate extends AggregateBase {
38
39
  this.drain();
39
40
  });
40
41
  }
41
- handleLog(timestamp, message, attributes, level) {
42
+ handleLog(timestamp, message) {
43
+ let attributes = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
44
+ let level = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : LOG_LEVELS.INFO;
42
45
  if (this.blocked) return;
46
+ if (!attributes || typeof attributes !== 'object') attributes = {};
47
+ if (typeof level === 'string') level = level.toUpperCase();
48
+ if (!isValidLogLevel(level)) return warn(30, level);
49
+ try {
50
+ if (typeof message !== 'string') {
51
+ const stringified = stringify(message);
52
+ /**
53
+ * Error instances convert to `{}` when stringified
54
+ * Symbol converts to '' when stringified
55
+ * other cases tbd
56
+ * */
57
+ if (!!stringified && stringified !== '{}') message = stringified;else message = String(message);
58
+ }
59
+ } catch (err) {
60
+ warn(16, message);
61
+ return;
62
+ }
63
+ if (typeof message !== 'string' || !message) return warn(32);
64
+ if (message.length > MAX_PAYLOAD_SIZE) {
65
+ handle(SUPPORTABILITY_METRIC_CHANNEL, ['Logging/Harvest/Failed/Seen', message.length]);
66
+ return warn(31, message.slice(0, 25) + '...');
67
+ }
43
68
  const log = new Log(this.#agentRuntime.timeKeeper.convertRelativeTimestamp(timestamp), message, attributes, level);
44
69
  const logBytes = log.message.length + stringify(log.attributes).length + log.level.length + 10; // timestamp == 10 chars
45
70
  if (logBytes > MAX_PAYLOAD_SIZE) {
46
71
  handle(SUPPORTABILITY_METRIC_CHANNEL, ['Logging/Harvest/Failed/Seen', logBytes]);
47
- return warn(LOGGING_IGNORED + '> ' + MAX_PAYLOAD_SIZE + ' bytes', log.message.slice(0, 25) + '...');
72
+ return warn(31, log.message.slice(0, 25) + '...');
48
73
  }
49
74
  if (this.estimatedBytes + logBytes >= MAX_PAYLOAD_SIZE) {
50
75
  handle(SUPPORTABILITY_METRIC_CHANNEL, ['Logging/Harvest/Early/Seen', this.estimatedBytes + logBytes]);
@@ -8,7 +8,4 @@ export const LOG_LEVELS = {
8
8
  };
9
9
  export const LOGGING_EVENT_EMITTER_CHANNEL = 'log';
10
10
  export const FEATURE_NAME = FEATURE_NAMES.logging;
11
- export const MAX_PAYLOAD_SIZE = 1000000;
12
- export const LOGGING_FAILURE_MESSAGE = 'failed to wrap logger: ';
13
- export const LOGGING_LEVEL_FAILURE_MESSAGE = 'invalid log level: ';
14
- export const LOGGING_IGNORED = 'ignored log: ';
11
+ export const MAX_PAYLOAD_SIZE = 1000000;
@@ -1,7 +1,5 @@
1
1
  import { handle } from '../../../common/event-emitter/handle';
2
2
  import { now } from '../../../common/timing/now';
3
- import { warn } from '../../../common/util/console';
4
- import { stringify } from '../../../common/util/stringify';
5
3
  import { FEATURE_NAMES } from '../../../loaders/features/features';
6
4
  import { SUPPORTABILITY_METRIC_CHANNEL } from '../../metrics/constants';
7
5
  import { LOGGING_EVENT_EMITTER_CHANNEL, LOG_LEVELS } from '../constants';
@@ -15,30 +13,16 @@ import { LOGGING_EVENT_EMITTER_CHANNEL, LOG_LEVELS } from '../constants';
15
13
  export function bufferLog(ee, message) {
16
14
  let customAttributes = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
17
15
  let level = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : LOG_LEVELS.INFO;
18
- try {
19
- if (typeof message !== 'string') {
20
- const stringified = stringify(message);
21
- /**
22
- * Error instances convert to `{}` when stringified
23
- * Symbol converts to '' when stringified
24
- * other cases tbd
25
- * */
26
- if (!!stringified && stringified !== '{}') message = stringified;else message = String(message);
27
- }
28
- } catch (err) {
29
- warn('could not cast log message to string', message);
30
- return;
31
- }
32
16
  handle(SUPPORTABILITY_METRIC_CHANNEL, ["API/logging/".concat(level.toLowerCase(), "/called")], undefined, FEATURE_NAMES.metrics, ee);
33
17
  handle(LOGGING_EVENT_EMITTER_CHANNEL, [now(), message, customAttributes, level], undefined, FEATURE_NAMES.logging, ee);
34
18
  }
35
19
 
36
20
  /**
37
21
  * Checks if a supplied log level is acceptable for use in generating a log event
38
- * @param {string} level
22
+ * @param {string} level -- must be cast to uppercase before running this test
39
23
  * @returns {boolean}
40
24
  */
41
25
  export function isValidLogLevel(level) {
42
26
  if (typeof level !== 'string') return false;
43
- return Object.values(LOG_LEVELS).some(logLevel => logLevel.toUpperCase() === level.toUpperCase());
27
+ return Object.values(LOG_LEVELS).some(logLevel => logLevel === level);
44
28
  }
@@ -102,6 +102,22 @@ export class Aggregate extends AggregateBase {
102
102
  // Check if proxy for either chunks or beacon is being used
103
103
  if (proxy.assets) this.storeSupportabilityMetrics('Config/AssetsUrl/Changed');
104
104
  if (proxy.beacon) this.storeSupportabilityMetrics('Config/BeaconUrl/Changed');
105
+ if (isBrowserScope && window.MutationObserver) {
106
+ this.storeSupportabilityMetrics('Generic/VideoElement/Added', window.document.querySelectorAll('video').length);
107
+ const mo = new MutationObserver(records => {
108
+ records.forEach(record => {
109
+ record.addedNodes.forEach(addedNode => {
110
+ if (addedNode instanceof HTMLVideoElement) {
111
+ this.storeSupportabilityMetrics('Generic/VideoElement/Added', 1);
112
+ }
113
+ });
114
+ });
115
+ });
116
+ mo.observe(window.document.body, {
117
+ childList: true,
118
+ subtree: true
119
+ });
120
+ }
105
121
  }
106
122
  eachSessionChecks() {
107
123
  if (!isBrowserScope) return;