@newrelic/browser-agent 1.290.1 → 1.291.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 (66) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/dist/cjs/common/constants/env.cdn.js +1 -1
  3. package/dist/cjs/common/constants/env.npm.js +1 -1
  4. package/dist/cjs/common/harvest/harvester.js +0 -7
  5. package/dist/cjs/common/session/session-entity.js +5 -5
  6. package/dist/cjs/common/util/console.js +12 -0
  7. package/dist/cjs/features/generic_events/aggregate/index.js +18 -2
  8. package/dist/cjs/features/generic_events/instrument/index.js +2 -0
  9. package/dist/cjs/features/session_replay/aggregate/index.js +3 -0
  10. package/dist/cjs/features/session_replay/shared/recorder.js +0 -4
  11. package/dist/cjs/features/session_trace/aggregate/index.js +7 -3
  12. package/dist/cjs/features/session_trace/aggregate/trace/storage.js +36 -23
  13. package/dist/cjs/loaders/api/addToTrace.js +8 -0
  14. package/dist/cjs/loaders/api/constants.js +3 -2
  15. package/dist/cjs/loaders/api/measure.js +60 -0
  16. package/dist/cjs/loaders/api-base.js +11 -0
  17. package/dist/esm/common/constants/env.cdn.js +1 -1
  18. package/dist/esm/common/constants/env.npm.js +1 -1
  19. package/dist/esm/common/harvest/harvester.js +0 -7
  20. package/dist/esm/common/session/session-entity.js +5 -5
  21. package/dist/esm/common/util/console.js +13 -0
  22. package/dist/esm/features/generic_events/aggregate/index.js +18 -2
  23. package/dist/esm/features/generic_events/instrument/index.js +2 -0
  24. package/dist/esm/features/session_replay/aggregate/index.js +3 -0
  25. package/dist/esm/features/session_replay/shared/recorder.js +0 -4
  26. package/dist/esm/features/session_trace/aggregate/index.js +7 -3
  27. package/dist/esm/features/session_trace/aggregate/trace/storage.js +36 -23
  28. package/dist/esm/loaders/api/addToTrace.js +8 -0
  29. package/dist/esm/loaders/api/constants.js +2 -1
  30. package/dist/esm/loaders/api/measure.js +53 -0
  31. package/dist/esm/loaders/api-base.js +12 -1
  32. package/dist/tsconfig.tsbuildinfo +1 -1
  33. package/dist/types/common/harvest/harvester.d.ts.map +1 -1
  34. package/dist/types/common/session/session-entity.d.ts +0 -1
  35. package/dist/types/common/session/session-entity.d.ts.map +1 -1
  36. package/dist/types/common/util/console.d.ts +0 -4
  37. package/dist/types/common/util/console.d.ts.map +1 -1
  38. package/dist/types/features/generic_events/aggregate/index.d.ts.map +1 -1
  39. package/dist/types/features/generic_events/instrument/index.d.ts.map +1 -1
  40. package/dist/types/features/session_replay/aggregate/index.d.ts.map +1 -1
  41. package/dist/types/features/session_replay/shared/recorder.d.ts.map +1 -1
  42. package/dist/types/features/session_trace/aggregate/index.d.ts +1 -1
  43. package/dist/types/features/session_trace/aggregate/index.d.ts.map +1 -1
  44. package/dist/types/features/session_trace/aggregate/trace/storage.d.ts +1 -3
  45. package/dist/types/features/session_trace/aggregate/trace/storage.d.ts.map +1 -1
  46. package/dist/types/loaders/api/addToTrace.d.ts.map +1 -1
  47. package/dist/types/loaders/api/constants.d.ts +1 -0
  48. package/dist/types/loaders/api/constants.d.ts.map +1 -1
  49. package/dist/types/loaders/api/measure.d.ts +2 -0
  50. package/dist/types/loaders/api/measure.d.ts.map +1 -0
  51. package/dist/types/loaders/api-base.d.ts +13 -0
  52. package/dist/types/loaders/api-base.d.ts.map +1 -1
  53. package/package.json +1 -1
  54. package/src/common/harvest/harvester.js +0 -5
  55. package/src/common/session/session-entity.js +5 -6
  56. package/src/common/util/console.js +13 -0
  57. package/src/features/generic_events/aggregate/index.js +17 -2
  58. package/src/features/generic_events/instrument/index.js +2 -0
  59. package/src/features/session_replay/aggregate/index.js +4 -0
  60. package/src/features/session_replay/shared/recorder.js +0 -4
  61. package/src/features/session_trace/aggregate/index.js +7 -3
  62. package/src/features/session_trace/aggregate/trace/storage.js +37 -23
  63. package/src/loaders/api/addToTrace.js +6 -0
  64. package/src/loaders/api/constants.js +1 -0
  65. package/src/loaders/api/measure.js +53 -0
  66. package/src/loaders/api-base.js +12 -1
@@ -3,6 +3,8 @@
3
3
  * SPDX-License-Identifier: Apache-2.0
4
4
  */
5
5
 
6
+ import { dispatchGlobalEvent } from '../dispatch/global-event'
7
+
6
8
  /* eslint no-console: ["error", { allow: ["debug"] }] */
7
9
 
8
10
  /**
@@ -14,4 +16,15 @@
14
16
  export function warn (code, secondary) {
15
17
  if (typeof console.debug !== 'function') return
16
18
  console.debug(`New Relic Warning: https://github.com/newrelic/newrelic-browser-agent/blob/main/docs/warning-codes.md#${code}`, secondary)
19
+ dispatchGlobalEvent({
20
+ agentIdentifier: null,
21
+ drained: null,
22
+ type: 'data',
23
+ name: 'warn',
24
+ feature: 'warn',
25
+ data: {
26
+ code,
27
+ secondary
28
+ }
29
+ })
17
30
  }
@@ -143,7 +143,7 @@ export class Aggregate extends AggregateBase {
143
143
  ...detailObj,
144
144
  eventType: 'BrowserPerformance',
145
145
  timestamp: this.toEpoch(entry.startTime),
146
- entryName: cleanURL(entry.name),
146
+ entryName: entry.name,
147
147
  entryDuration: entry.duration,
148
148
  entryType: type
149
149
  })
@@ -208,7 +208,7 @@ export class Aggregate extends AggregateBase {
208
208
  ...entryObject,
209
209
  eventType: 'BrowserPerformance',
210
210
  timestamp: Math.floor(agentRef.runtime.timeKeeper.correctRelativeTimestamp(entryObject.startTime)),
211
- entryName: name,
211
+ entryName: cleanURL(name),
212
212
  entryDuration: duration,
213
213
  firstParty
214
214
  }
@@ -220,6 +220,21 @@ export class Aggregate extends AggregateBase {
220
220
  }, this.featureName, this.ee)
221
221
  }
222
222
 
223
+ registerHandler('api-measure', (args, n) => {
224
+ const { start, duration, customAttributes } = args
225
+
226
+ const event = {
227
+ ...customAttributes,
228
+ eventType: 'BrowserPerformance',
229
+ timestamp: Math.floor(agentRef.runtime.timeKeeper.correctRelativeTimestamp(start)),
230
+ entryName: n,
231
+ entryDuration: duration,
232
+ entryType: 'measure'
233
+ }
234
+
235
+ this.addEvent(event)
236
+ }, this.featureName, this.ee)
237
+
223
238
  agentRef.runtime.harvester.triggerHarvestFor(this)
224
239
  this.drain()
225
240
  })
@@ -11,6 +11,7 @@ import { setupAddPageActionAPI } from '../../../loaders/api/addPageAction'
11
11
  import { setupFinishedAPI } from '../../../loaders/api/finished'
12
12
  import { setupRecordCustomEventAPI } from '../../../loaders/api/recordCustomEvent'
13
13
  import { setupRegisterAPI } from '../../../loaders/api/register'
14
+ import { setupMeasureAPI } from '../../../loaders/api/measure'
14
15
  import { InstrumentBase } from '../../utils/instrument-base'
15
16
  import { FEATURE_NAME, OBSERVED_EVENTS, OBSERVED_WINDOW_EVENTS } from '../constants'
16
17
 
@@ -32,6 +33,7 @@ export class Instrument extends InstrumentBase {
32
33
  setupRecordCustomEventAPI(agentRef)
33
34
  setupFinishedAPI(agentRef)
34
35
  setupRegisterAPI(agentRef)
36
+ setupMeasureAPI(agentRef)
35
37
 
36
38
  if (isBrowserScope) {
37
39
  if (agentRef.init.user_actions.enabled) {
@@ -262,6 +262,10 @@ export class Aggregate extends AggregateBase {
262
262
  if (recorderEvents.type === 'preloaded') this.agentRef.runtime.harvester.triggerHarvestFor(this)
263
263
  payloadOutput.payload = payload
264
264
 
265
+ if (!this.agentRef.runtime.session.state.traceHarvestStarted) {
266
+ warn(59, JSON.stringify(this.agentRef.runtime.session.state))
267
+ }
268
+
265
269
  return [payloadOutput]
266
270
  }
267
271
 
@@ -155,10 +155,6 @@ export class Recorder {
155
155
  /** Store a payload in the buffer (this.#events). This should be the callback to the recording lib noticing a mutation */
156
156
  store (event, isCheckout) {
157
157
  if (!event) return
158
- if (this.parent.agentRef.runtime.session?.isAfterSessionExpiry(event.timestamp)) {
159
- handle(SUPPORTABILITY_METRIC_CHANNEL, ['Session/Expired/SessionReplay/Seen'], undefined, FEATURE_NAMES.metrics, this.ee)
160
- return
161
- }
162
158
 
163
159
  if (!(this.parent instanceof AggregateBase) && this.#preloaded.length) this.currentBufferTarget = this.#preloaded[this.#preloaded.length - 1]
164
160
  else this.currentBufferTarget = this.#events
@@ -11,6 +11,7 @@ import { globalScope } from '../../../common/constants/runtime'
11
11
  import { MODE, SESSION_EVENTS } from '../../../common/session/constants'
12
12
  import { applyFnToProps } from '../../../common/util/traverse'
13
13
  import { cleanURL } from '../../../common/url/clean-url'
14
+ import { warn } from '../../../common/util/console'
14
15
 
15
16
  const ERROR_MODE_SECONDS_WINDOW = 30 * 1000 // sliding window of nodes to track when simply monitoring (but not harvesting) in error mode
16
17
  /** Reserved room for query param attrs */
@@ -85,7 +86,7 @@ export class Aggregate extends AggregateBase {
85
86
  registerHandler('bstResource', (...args) => this.events.storeResources(...args), this.featureName, this.ee)
86
87
  registerHandler('bstHist', (...args) => this.events.storeHist(...args), this.featureName, this.ee)
87
88
  registerHandler('bstXhrAgg', (...args) => this.events.storeXhrAgg(...args), this.featureName, this.ee)
88
- registerHandler('bstApi', (...args) => this.events.storeSTN(...args), this.featureName, this.ee)
89
+ registerHandler('bstApi', (...args) => this.events.storeNode(...args), this.featureName, this.ee)
89
90
  registerHandler('trace-jserror', (...args) => this.events.storeErrorAgg(...args), this.featureName, this.ee)
90
91
  registerHandler('pvtAdded', (...args) => this.events.processPVT(...args), this.featureName, this.ee)
91
92
 
@@ -97,10 +98,12 @@ export class Aggregate extends AggregateBase {
97
98
  }
98
99
  this.agentRef.runtime.session.write({ sessionTraceMode: this.mode })
99
100
  this.drain()
101
+ /** try to harvest immediately. This will not send if the trace is not running in FULL mode due to the pre-harvest checks. */
102
+ this.agentRef.runtime.harvester.triggerHarvestFor(this)
100
103
  }
101
104
 
102
105
  preHarvestChecks () {
103
- if (this.mode !== MODE.FULL) return // only allow harvest if running in full mode
106
+ if (this.blocked || this.mode !== MODE.FULL) return // only allow harvest if running in full mode
104
107
  if (!this.timeKeeper?.ready) return // this should likely never happen, but just to be safe, we should never harvest if we cant correct time
105
108
  if (!this.agentRef.runtime.session) return // session entity is required for trace to run and continue running
106
109
  if (this.sessionId !== this.agentRef.runtime.session.state.value || this.ptid !== this.agentRef.runtime.ptid) {
@@ -178,7 +181,8 @@ export class Aggregate extends AggregateBase {
178
181
  }
179
182
 
180
183
  /** Stop running for the remainder of the page lifecycle */
181
- abort () {
184
+ abort (code) {
185
+ warn(60, code)
182
186
  this.blocked = true
183
187
  this.mode = MODE.OFF
184
188
  this.agentRef.runtime.session.write({ sessionTraceMode: this.mode })
@@ -41,23 +41,18 @@ export class TraceStorage {
41
41
  this.parent = parent
42
42
  }
43
43
 
44
- isAfterSessionExpiry (entryTimestamp) {
45
- return this.parent.agentRef.runtime?.session?.isAfterSessionExpiry((this.parent.timeKeeper?.ready && this.parent.timeKeeper.convertRelativeTimestamp(entryTimestamp)) ?? undefined)
46
- }
47
-
48
- /** Central function called by all the other store__ & addToTrace API to append a trace node. */
49
- storeSTN (stn) {
50
- if (this.parent.blocked) return
44
+ #canStoreNewNode () {
45
+ if (this.parent.blocked) return false
51
46
  if (this.nodeCount >= MAX_NODES_PER_HARVEST) { // limit the amount of pending data awaiting next harvest
52
- if (this.parent.mode !== MODE.ERROR) return
47
+ if (this.parent.mode !== MODE.ERROR) return false
53
48
  const openedSpace = this.trimSTNs(ERROR_MODE_SECONDS_WINDOW) // but maybe we could make some space by discarding irrelevant nodes if we're in sessioned Error mode
54
- if (openedSpace === 0) return
55
- }
56
- if (this.isAfterSessionExpiry(stn.s)) {
57
- this.parent.reportSupportabilityMetric('Session/Expired/SessionTrace/Seen')
58
- return
49
+ if (openedSpace === 0) return false
59
50
  }
51
+ return true
52
+ }
60
53
 
54
+ /** Central internal function called by all the other store__ & addToTrace API to append a trace node. They MUST all have checked #canStoreNewNode before calling this func!! */
55
+ #storeSTN (stn) {
61
56
  if (this.trace[stn.n]) this.trace[stn.n].push(stn)
62
57
  else this.trace[stn.n] = [stn]
63
58
 
@@ -143,6 +138,11 @@ export class TraceStorage {
143
138
  }
144
139
  }
145
140
 
141
+ storeNode (node) {
142
+ if (!this.#canStoreNewNode()) return
143
+ this.#storeSTN(node)
144
+ }
145
+
146
146
  processPVT (name, value, attrs) {
147
147
  this.storeTiming({ [name]: value })
148
148
  }
@@ -168,13 +168,16 @@ export class TraceStorage {
168
168
  Math.floor(this.parent.timeKeeper.correctAbsoluteTimestamp(val))
169
169
  )
170
170
  }
171
- this.storeSTN(new TraceNode(key, val, val, 'document', 'timing'))
171
+ if (!this.#canStoreNewNode()) return // at any point when no new nodes can be stored, there's no point in processing the rest of the timing entries
172
+ this.#storeSTN(new TraceNode(key, val, val, 'document', 'timing'))
172
173
  }
173
174
  }
174
175
 
175
176
  // Tracks the events and their listener's duration on objects wrapped by wrap-events.
176
177
  storeEvent (currentEvent, target, start, end) {
177
178
  if (this.shouldIgnoreEvent(currentEvent, target)) return
179
+ if (!this.#canStoreNewNode()) return // need to check if adding node will succeed BEFORE storing event ref below (*cli Jun'25 - addressing memory leak in aborted ST issue #NR-420780)
180
+
178
181
  if (this.prevStoredEvents.has(currentEvent)) return // prevent multiple listeners of an event from creating duplicate trace nodes per occurrence. Cleared every harvest. near-zero chance for re-duplication after clearing per harvest since the timestamps of the event are considered for uniqueness.
179
182
  this.prevStoredEvents.add(currentEvent)
180
183
 
@@ -186,7 +189,7 @@ export class TraceStorage {
186
189
  } catch (e) {
187
190
  evt.o = eventOrigin(null, target, this.parent.ee)
188
191
  }
189
- this.storeSTN(evt)
192
+ this.#storeSTN(evt)
190
193
  }
191
194
 
192
195
  shouldIgnoreEvent (event, target) {
@@ -224,7 +227,8 @@ export class TraceStorage {
224
227
 
225
228
  // Tracks when the window history API specified by wrap-history is used.
226
229
  storeHist (path, old, time) {
227
- this.storeSTN(new TraceNode('history.pushState', time, time, path, old))
230
+ if (!this.#canStoreNewNode()) return
231
+ this.#storeSTN(new TraceNode('history.pushState', time, time, path, old))
228
232
  }
229
233
 
230
234
  #laststart = 0
@@ -232,14 +236,17 @@ export class TraceStorage {
232
236
  storeResources (resources) {
233
237
  if (!resources || resources.length === 0) return
234
238
 
235
- resources.forEach((currentResource) => {
236
- if ((currentResource.fetchStart | 0) <= this.#laststart) return // don't recollect already-seen resources
239
+ for (let i = 0; i < resources.length; i++) {
240
+ const currentResource = resources[i]
241
+ if ((currentResource.fetchStart | 0) <= this.#laststart) continue // don't recollect already-seen resources
242
+ if (!this.#canStoreNewNode()) break // stop processing if we can't store any more resource nodes anyways
237
243
 
238
244
  const { initiatorType, fetchStart, responseEnd, entryType } = currentResource
239
245
  const { protocol, hostname, port, pathname } = parseUrl(currentResource.name)
240
246
  const res = new TraceNode(initiatorType, fetchStart | 0, responseEnd | 0, `${protocol}://${hostname}:${port}${pathname}`, entryType)
241
- this.storeSTN(res)
242
- })
247
+
248
+ this.#storeSTN(res)
249
+ }
243
250
 
244
251
  this.#laststart = resources[resources.length - 1].fetchStart | 0
245
252
  }
@@ -247,13 +254,15 @@ export class TraceStorage {
247
254
  // JavascriptError (FEATURE) events pipes into ST here.
248
255
  storeErrorAgg (type, name, params, metrics) {
249
256
  if (type !== 'err') return // internal errors are purposefully ignored
250
- this.storeSTN(new TraceNode('error', metrics.time, metrics.time, params.message, params.stackHash))
257
+ if (!this.#canStoreNewNode()) return
258
+ this.#storeSTN(new TraceNode('error', metrics.time, metrics.time, params.message, params.stackHash))
251
259
  }
252
260
 
253
261
  // Ajax (FEATURE) events--XML & fetches--pipes into ST here.
254
262
  storeXhrAgg (type, name, params, metrics) {
255
263
  if (type !== 'xhr') return
256
- this.storeSTN(new TraceNode('Ajax', metrics.time, metrics.time + metrics.duration, `${params.status} ${params.method}: ${params.host}${params.pathname}`, 'ajax'))
264
+ if (!this.#canStoreNewNode()) return
265
+ this.#storeSTN(new TraceNode('Ajax', metrics.time, metrics.time + metrics.duration, `${params.status} ${params.method}: ${params.host}${params.pathname}`, 'ajax'))
257
266
  }
258
267
 
259
268
  /* Below are the interface expected & required of whatever storage is used across all features on an individual basis. This allows a common `.events` property on Trace shared with AggregateBase.
@@ -279,7 +288,12 @@ export class TraceStorage {
279
288
  }
280
289
 
281
290
  reloadSave () {
282
- Object.values(this.#backupTrace).forEach(stnsArray => stnsArray.forEach(stn => this.storeSTN(stn)))
291
+ for (const stnsArray of Object.values(this.#backupTrace)) {
292
+ for (const stn of stnsArray) {
293
+ if (!this.#canStoreNewNode()) return // stop attempting to re-store nodes
294
+ this.#storeSTN(stn)
295
+ }
296
+ }
283
297
  }
284
298
 
285
299
  clearSave () {
@@ -4,6 +4,7 @@
4
4
  */
5
5
  import { originTime } from '../../common/constants/runtime'
6
6
  import { handle } from '../../common/event-emitter/handle'
7
+ import { warn } from '../../common/util/console'
7
8
  import { FEATURE_NAMES } from '../features/features'
8
9
  import { ADD_TO_TRACE } from './constants'
9
10
  import { setupAPI } from './sharedHandlers'
@@ -20,6 +21,11 @@ export function setupAddToTraceAPI (agent) {
20
21
  t: 'api'
21
22
  }
22
23
 
24
+ if (report.s < 0 || report.e < 0 || report.e < report.s) {
25
+ warn(61, { start: report.s, end: report.e })
26
+ return
27
+ }
28
+
23
29
  handle('bstApi', [report], undefined, FEATURE_NAMES.sessionTrace, agent.ee)
24
30
  }, agent)
25
31
  }
@@ -24,3 +24,4 @@ export const SET_PAGE_VIEW_NAME = 'setPageViewName'
24
24
  export const SET_USER_ID = 'setUserId'
25
25
  export const START = 'start'
26
26
  export const WRAP_LOGGER = 'wrapLogger'
27
+ export const MEASURE = 'measure'
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Copyright 2020-2025 New Relic, Inc. All rights reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+ import { handle } from '../../common/event-emitter/handle'
6
+ import { now } from '../../common/timing/now'
7
+ import { warn } from '../../common/util/console'
8
+ import { FEATURE_NAMES } from '../features/features'
9
+ import { prefix, MEASURE } from './constants'
10
+ import { setupAPI } from './sharedHandlers'
11
+
12
+ export function setupMeasureAPI (agent) {
13
+ setupAPI(MEASURE, function (name, options) {
14
+ const n = now()
15
+ const { start, end, customAttributes } = options || {}
16
+ const returnObj = { customAttributes: customAttributes || {} }
17
+
18
+ if (typeof returnObj.customAttributes !== 'object' || typeof name !== 'string' || name.length === 0) {
19
+ warn(57)
20
+ return
21
+ }
22
+
23
+ /**
24
+ * getValueFromTiming - Helper function to extract a numeric value from a supplied option.
25
+ * @param {Number|PerformanceMark} [timing] The timing value
26
+ * @param {Number} [d] The default value to return if timing is invalid
27
+ * @returns {Number} The timing value or the default value
28
+ */
29
+ const getValueFromTiming = (timing, d) => {
30
+ if (timing == null) return d
31
+ if (typeof timing === 'number') return timing
32
+ if (timing instanceof PerformanceMark) return timing.startTime
33
+ return Number.NaN
34
+ }
35
+
36
+ returnObj.start = getValueFromTiming(start, 0)
37
+ returnObj.end = getValueFromTiming(end, n)
38
+ if (Number.isNaN(returnObj.start) || Number.isNaN(returnObj.end)) {
39
+ warn(57)
40
+ return
41
+ }
42
+
43
+ returnObj.duration = returnObj.end - returnObj.start
44
+ if (returnObj.duration < 0) {
45
+ warn(58)
46
+ return
47
+ }
48
+
49
+ handle(prefix + MEASURE, [returnObj, name], undefined, FEATURE_NAMES.genericEvents, agent.ee)
50
+
51
+ return returnObj
52
+ }, agent)
53
+ }
@@ -3,7 +3,7 @@
3
3
  * SPDX-License-Identifier: Apache-2.0
4
4
  */
5
5
  import { warn } from '../common/util/console'
6
- import { ADD_PAGE_ACTION, ADD_RELEASE, ADD_TO_TRACE, FINISHED, INTERACTION, LOG, NOTICE_ERROR, PAUSE_REPLAY, RECORD_CUSTOM_EVENT, RECORD_REPLAY, REGISTER, SET_APPLICATION_VERSION, SET_CURRENT_ROUTE_NAME, SET_CUSTOM_ATTRIBUTE, SET_ERROR_HANDLER, SET_PAGE_VIEW_NAME, SET_USER_ID, START, WRAP_LOGGER } from './api/constants'
6
+ import { ADD_PAGE_ACTION, ADD_RELEASE, ADD_TO_TRACE, FINISHED, INTERACTION, LOG, NOTICE_ERROR, PAUSE_REPLAY, RECORD_CUSTOM_EVENT, RECORD_REPLAY, REGISTER, SET_APPLICATION_VERSION, SET_CURRENT_ROUTE_NAME, SET_CUSTOM_ATTRIBUTE, SET_ERROR_HANDLER, SET_PAGE_VIEW_NAME, SET_USER_ID, START, WRAP_LOGGER, MEASURE } from './api/constants'
7
7
 
8
8
  /**
9
9
  * @typedef {import('./api/interaction-types').InteractionInstance} InteractionInstance
@@ -215,4 +215,15 @@ export class ApiBase {
215
215
  wrapLogger (parent, functionName, options) {
216
216
  return this.#callMethod(WRAP_LOGGER, parent, functionName, options)
217
217
  }
218
+
219
+ /**
220
+ * Measures a task that is recorded as a BrowserPerformance event.
221
+ * {@link https://docs.newrelic.com/docs/browser/new-relic-browser/browser-apis/measure/}
222
+ * @param {string} name The name of the task
223
+ * @param {object?} options An object used to control the way the measure API operates
224
+ * @returns {{start: number, end: number, duration: number, customAttributes: object}} Measurement details
225
+ */
226
+ measure (name, options) {
227
+ return this.#callMethod(MEASURE, name, options)
228
+ }
218
229
  }