Skip to content

feat: Erase .api property on agent instance #1425

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Mar 28, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/features/utils/aggregate-base.js
Original file line number Diff line number Diff line change
Expand Up @@ -136,14 +136,14 @@ export class AggregateBase extends FeatureBase {
} catch (err) {
// do nothing
}
configure({ agentIdentifier: this.agentIdentifier }, {
configure(existingAgent, {
...cdn,
info: {
...cdn.info,
jsAttributes
},
runtime: existingAgent.runtime
})
}, existingAgent.runtime.loaderType)
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/features/utils/nr1-debugger.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { gosCDN } from '../../common/window/nreum'
const debugId = 1
const newrelic = gosCDN()
export function debugNR1 (agentIdentifier, location, event, otherprops = {}, debugName = 'SR') {
const api = agentIdentifier ? newrelic.initializedAgents[agentIdentifier].api.addPageAction : newrelic.addPageAction
const api = agentIdentifier ? newrelic.initializedAgents[agentIdentifier].addPageAction : newrelic.addPageAction
let url
try {
const locURL = new URL(window.location)
Expand Down
4 changes: 2 additions & 2 deletions src/loaders/agent-base.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@
* @param {...any} args
*/
#callMethod (methodName, ...args) {
if (typeof this.api?.[methodName] !== 'function') warn(35, methodName)
else return this.api[methodName](...args)
if (this[methodName] === AgentBase.prototype[methodName] || this[methodName] === MicroAgentBase.prototype[methodName]) warn(35, methodName)
else return this[methodName](...args)

Check warning on line 24 in src/loaders/agent-base.js

View check run for this annotation

Codecov / codecov/patch

src/loaders/agent-base.js#L24

Added line #L24 was not covered by tests
}

/**
Expand Down
5 changes: 4 additions & 1 deletion src/loaders/agent.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,10 @@ export class Agent extends AgentBase {
}
}

get api () {
return this
}

run () {
// Attempt to initialize all the requested features (sequentially in prio order & synchronously), with any failure aborting the whole process.
try {
Expand Down Expand Up @@ -102,7 +106,6 @@ export class Agent extends AgentBase {
}

const newrelic = gosNREUM()
delete newrelic.initializedAgents[this.agentIdentifier]?.api // prevent further calls to agent-specific APIs (see "configure.js")
delete newrelic.initializedAgents[this.agentIdentifier]?.features // GC mem used internally by features
delete this.sharedAggregator
// Keep the initialized agent object with its configs for troubleshooting purposes.
Expand Down
114 changes: 55 additions & 59 deletions src/loaders/api/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,8 @@
* SPDX-License-Identifier: Apache-2.0
*/
import { FEATURE_NAMES } from '../features/features'
import { getInfo, setInfo } from '../../common/config/info'
import { getRuntime } from '../../common/config/runtime'
import { setInfo } from '../../common/config/info'
import { handle } from '../../common/event-emitter/handle'
import { ee } from '../../common/event-emitter/contextual-ee'
import { drain, registerDrain } from '../../common/drain/drain'
import { onWindowLoad } from '../../common/window/load'
import { isBrowserScope } from '../../common/constants/runtime'
Expand All @@ -31,11 +29,11 @@

function caller (fnName, ...args) {
let returnVals = []
Object.values(nr.initializedAgents).forEach(val => {
if (!val || !val.api || !val.runtime) {
Object.values(nr.initializedAgents).forEach(agt => {
if (!agt || !agt.runtime) {
warn(38, fnName)
} else if (val.exposed && val.api[fnName] && val.runtime.loaderType !== 'micro-agent') {
returnVals.push(val.api[fnName](...args))
} else if (agt.exposed && agt[fnName] && agt.runtime.loaderType !== 'micro-agent') {
returnVals.push(agt[fnName](...args))
}
})
return returnVals[0]
Expand All @@ -44,42 +42,40 @@

const replayRunning = {}

export function setAPI (agentIdentifier, forceDrain, runSoftNavOverSpa = false) {
if (!forceDrain) registerDrain(agentIdentifier, 'api')
const apiInterface = {}
var instanceEE = ee.get(agentIdentifier)
var tracerEE = instanceEE.get('tracer')
export function setAPI (agent, forceDrain) {
if (!forceDrain) registerDrain(agent.agentIdentifier, 'api')
const tracerEE = agent.ee.get('tracer')

replayRunning[agentIdentifier] = MODE.OFF
replayRunning[agent.agentIdentifier] = MODE.OFF

instanceEE.on(SR_EVENT_EMITTER_TYPES.REPLAY_RUNNING, (isRunning) => {
replayRunning[agentIdentifier] = isRunning
agent.ee.on(SR_EVENT_EMITTER_TYPES.REPLAY_RUNNING, (isRunning) => {
replayRunning[agent.agentIdentifier] = isRunning
})

var prefix = 'api-'
var spaPrefix = prefix + 'ixn-'
const prefix = 'api-'
const spaPrefix = prefix + 'ixn-'

apiInterface.log = function (message, { customAttributes = {}, level = LOG_LEVELS.INFO } = {}) {
handle(SUPPORTABILITY_METRIC_CHANNEL, ['API/log/called'], undefined, FEATURE_NAMES.metrics, instanceEE)
bufferLog(instanceEE, message, customAttributes, level)
agent.log = function (message, { customAttributes = {}, level = LOG_LEVELS.INFO } = {}) {
handle(SUPPORTABILITY_METRIC_CHANNEL, ['API/log/called'], undefined, FEATURE_NAMES.metrics, agent.ee)
bufferLog(agent.ee, message, customAttributes, level)
}

apiInterface.wrapLogger = (parent, functionName, { customAttributes = {}, level = LOG_LEVELS.INFO } = {}) => {
handle(SUPPORTABILITY_METRIC_CHANNEL, ['API/wrapLogger/called'], undefined, FEATURE_NAMES.metrics, instanceEE)
wrapLogger(instanceEE, parent, functionName, { customAttributes, level })
agent.wrapLogger = (parent, functionName, { customAttributes = {}, level = LOG_LEVELS.INFO } = {}) => {
handle(SUPPORTABILITY_METRIC_CHANNEL, ['API/wrapLogger/called'], undefined, FEATURE_NAMES.metrics, agent.ee)
wrapLogger(agent.ee, parent, functionName, { customAttributes, level })
}

// Setup stub functions that queue calls for later processing.
asyncApiMethods.forEach(fnName => { apiInterface[fnName] = apiCall(prefix, fnName, true, 'api') })
asyncApiMethods.forEach(fnName => { agent[fnName] = apiCall(prefix, fnName, true, 'api') })

apiInterface.addPageAction = apiCall(prefix, 'addPageAction', true, FEATURE_NAMES.genericEvents)
agent.addPageAction = apiCall(prefix, 'addPageAction', true, FEATURE_NAMES.genericEvents)

apiInterface.recordCustomEvent = apiCall(prefix, 'recordCustomEvent', true, FEATURE_NAMES.genericEvents)
agent.recordCustomEvent = apiCall(prefix, 'recordCustomEvent', true, FEATURE_NAMES.genericEvents)

apiInterface.setPageViewName = function (name, host) {
agent.setPageViewName = function (name, host) {
if (typeof name !== 'string') return
if (name.charAt(0) !== '/') name = '/' + name
getRuntime(agentIdentifier).customTransaction = (host || 'http://custom.transaction') + name
agent.runtime.customTransaction = (host || 'http://custom.transaction') + name
return apiCall(prefix, 'setPageViewName', true)()
}

Expand All @@ -92,15 +88,15 @@
* @returns @see apiCall
*/
function appendJsAttribute (key, value, apiName, addToBrowserStorage) {
const currentInfo = getInfo(agentIdentifier)
const currentInfo = agent.info
if (value === null) {
delete currentInfo.jsAttributes[key]
} else {
setInfo(agentIdentifier, { ...currentInfo, jsAttributes: { ...currentInfo.jsAttributes, [key]: value } })
setInfo(agent.agentIdentifier, { ...currentInfo, jsAttributes: { ...currentInfo.jsAttributes, [key]: value } })
}
return apiCall(prefix, apiName, true, (!!addToBrowserStorage || value === null) ? 'session' : undefined)(key, value)
}
apiInterface.setCustomAttribute = function (name, value, persistAttribute = false) {
agent.setCustomAttribute = function (name, value, persistAttribute = false) {
if (typeof name !== 'string') {
warn(39, typeof name)
return
Expand All @@ -116,7 +112,7 @@
* @param {string} value - unique user identifier; a null user id suggests none should exist
* @returns @see apiCall
*/
apiInterface.setUserId = function (value) {
agent.setUserId = function (value) {
if (!(typeof value === 'string' || value === null)) {
warn(41, typeof value)
return
Expand All @@ -129,34 +125,34 @@
* @param {string|null} value - Application version -- if null, will "unset" the value
* @returns @see apiCall
*/
apiInterface.setApplicationVersion = function (value) {
agent.setApplicationVersion = function (value) {
if (!(typeof value === 'string' || value === null)) {
warn(42, typeof value)
return
}
return appendJsAttribute('application.version', value, 'setApplicationVersion', false)
}

apiInterface.start = () => {
agent.start = () => {
try {
handle(SUPPORTABILITY_METRIC_CHANNEL, ['API/start/called'], undefined, FEATURE_NAMES.metrics, instanceEE)
instanceEE.emit('manual-start-all')
handle(SUPPORTABILITY_METRIC_CHANNEL, ['API/start/called'], undefined, FEATURE_NAMES.metrics, agent.ee)
agent.ee.emit('manual-start-all')
} catch (err) {
warn(23, err)
}
}

apiInterface[SR_EVENT_EMITTER_TYPES.RECORD] = function () {
handle(SUPPORTABILITY_METRIC_CHANNEL, ['API/recordReplay/called'], undefined, FEATURE_NAMES.metrics, instanceEE)
handle(SR_EVENT_EMITTER_TYPES.RECORD, [], undefined, FEATURE_NAMES.sessionReplay, instanceEE)
agent[SR_EVENT_EMITTER_TYPES.RECORD] = function () {
handle(SUPPORTABILITY_METRIC_CHANNEL, ['API/recordReplay/called'], undefined, FEATURE_NAMES.metrics, agent.ee)
handle(SR_EVENT_EMITTER_TYPES.RECORD, [], undefined, FEATURE_NAMES.sessionReplay, agent.ee)

Check warning on line 147 in src/loaders/api/api.js

View check run for this annotation

Codecov / codecov/patch

src/loaders/api/api.js#L146-L147

Added lines #L146 - L147 were not covered by tests
}

apiInterface[SR_EVENT_EMITTER_TYPES.PAUSE] = function () {
handle(SUPPORTABILITY_METRIC_CHANNEL, ['API/pauseReplay/called'], undefined, FEATURE_NAMES.metrics, instanceEE)
handle(SR_EVENT_EMITTER_TYPES.PAUSE, [], undefined, FEATURE_NAMES.sessionReplay, instanceEE)
agent[SR_EVENT_EMITTER_TYPES.PAUSE] = function () {
handle(SUPPORTABILITY_METRIC_CHANNEL, ['API/pauseReplay/called'], undefined, FEATURE_NAMES.metrics, agent.ee)

Check warning on line 151 in src/loaders/api/api.js

View check run for this annotation

Codecov / codecov/patch

src/loaders/api/api.js#L151

Added line #L151 was not covered by tests
handle(SR_EVENT_EMITTER_TYPES.PAUSE, [], undefined, FEATURE_NAMES.sessionReplay, agent.ee)
}

apiInterface.interaction = function (options) {
agent.interaction = function (options) {
return new InteractionHandle().get(typeof options === 'object' ? options : {})
}

Expand All @@ -167,9 +163,9 @@
var contextStore = {}
var ixn = this
var hasCb = typeof cb === 'function'
handle(SUPPORTABILITY_METRIC_CHANNEL, ['API/createTracer/called'], undefined, FEATURE_NAMES.metrics, instanceEE)
handle(SUPPORTABILITY_METRIC_CHANNEL, ['API/createTracer/called'], undefined, FEATURE_NAMES.metrics, agent.ee)
// Soft navigations won't support Tracer nodes, but this fn should still work the same otherwise (e.g., run the orig cb).
if (!runSoftNavOverSpa) handle(spaPrefix + 'tracer', [now(), name, contextStore], ixn, FEATURE_NAMES.spa, instanceEE)
if (!agent.runSoftNavOverSpa) handle(spaPrefix + 'tracer', [now(), name, contextStore], ixn, FEATURE_NAMES.spa, agent.ee)
return function () {
tracerEE.emit((hasCb ? '' : 'no-') + 'fn-start', [now(), ixn, hasCb], contextStore)
if (hasCb) {
Expand All @@ -189,30 +185,30 @@
}

;['actionText', 'setName', 'setAttribute', 'save', 'ignore', 'onEnd', 'getContext', 'end', 'get'].forEach(name => {
InteractionApiProto[name] = apiCall(spaPrefix, name, undefined, runSoftNavOverSpa ? FEATURE_NAMES.softNav : FEATURE_NAMES.spa)
InteractionApiProto[name] = apiCall(spaPrefix, name, undefined, agent.runSoftNavOverSpa ? FEATURE_NAMES.softNav : FEATURE_NAMES.spa)
})
apiInterface.setCurrentRouteName = runSoftNavOverSpa ? apiCall(spaPrefix, 'routeName', undefined, FEATURE_NAMES.softNav) : apiCall(prefix, 'routeName', true, FEATURE_NAMES.spa)
agent.setCurrentRouteName = agent.runSoftNavOverSpa ? apiCall(spaPrefix, 'routeName', undefined, FEATURE_NAMES.softNav) : apiCall(prefix, 'routeName', true, FEATURE_NAMES.spa)

function apiCall (prefix, name, notSpa, bufferGroup) {
return function () {
handle(SUPPORTABILITY_METRIC_CHANNEL, ['API/' + name + '/called'], undefined, FEATURE_NAMES.metrics, instanceEE)
handle(SUPPORTABILITY_METRIC_CHANNEL, ['API/' + name + '/called'], undefined, FEATURE_NAMES.metrics, agent.ee)
dispatchGlobalEvent({
agentIdentifier,
loaded: !!activatedFeatures?.[agentIdentifier],
agentIdentifier: agent.agentIdentifier,
loaded: !!activatedFeatures?.[agent.agentIdentifier],
type: 'data',
name: 'api',
feature: prefix + name,
data: { notSpa, bufferGroup }
})
if (bufferGroup) handle(prefix + name, [notSpa ? now() : performance.now(), ...arguments], notSpa ? null : this, bufferGroup, instanceEE) // no bufferGroup means only the SM is emitted
if (bufferGroup) handle(prefix + name, [notSpa ? now() : performance.now(), ...arguments], notSpa ? null : this, bufferGroup, agent.ee) // no bufferGroup means only the SM is emitted
return notSpa ? undefined : this // returns the InteractionHandle which allows these methods to be chained
}
}

apiInterface.noticeError = function (err, customAttributes) {
agent.noticeError = function (err, customAttributes) {
if (typeof err === 'string') err = new Error(err)
handle(SUPPORTABILITY_METRIC_CHANNEL, ['API/noticeError/called'], undefined, FEATURE_NAMES.metrics, instanceEE)
handle('err', [err, now(), false, customAttributes, !!replayRunning[agentIdentifier]], undefined, FEATURE_NAMES.jserrors, instanceEE)
handle(SUPPORTABILITY_METRIC_CHANNEL, ['API/noticeError/called'], undefined, FEATURE_NAMES.metrics, agent.ee)
handle('err', [err, now(), false, customAttributes, !!replayRunning[agent.agentIdentifier]], undefined, FEATURE_NAMES.jserrors, agent.ee)
}

// theres no window.load event on non-browser scopes, lazy load immediately
Expand All @@ -221,14 +217,14 @@
else onWindowLoad(() => lazyLoad(), true)

function lazyLoad () {
import(/* webpackChunkName: "async-api" */'./apiAsync').then(({ setAPI }) => {
setAPI(agentIdentifier)
drain(agentIdentifier, 'api')
import(/* webpackChunkName: "async-api" */'./apiAsync').then(({ setAsyncAPI }) => {
setAsyncAPI(agent)
drain(agent.agentIdentifier, 'api')
}).catch((err) => {
warn(27, err)
instanceEE.abort()
agent.ee.abort()
})
}

return apiInterface
return true
}
32 changes: 14 additions & 18 deletions src/loaders/api/apiAsync.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,58 +3,54 @@
* SPDX-License-Identifier: Apache-2.0
*/
import { FEATURE_NAMES } from '../features/features'
import { getRuntime } from '../../common/config/runtime'
import { ee } from '../../common/event-emitter/contextual-ee'
import { handle } from '../../common/event-emitter/handle'
import { registerHandler } from '../../common/event-emitter/register-handler'
import { single } from '../../common/util/invoke'
import { CUSTOM_METRIC_CHANNEL } from '../../features/metrics/constants'
import { originTime } from '../../common/constants/runtime'

export function setAPI (agentIdentifier) {
var instanceEE = ee.get(agentIdentifier)

var api = {
export function setAsyncAPI (agent) {
const api = {
finished: single(finished),
setErrorHandler,
addToTrace,
addRelease
}

// Hook all of the api functions up to the queues/stubs created in loader/api.js
Object.entries(api).forEach(([fnName, fnCall]) => registerHandler('api-' + fnName, fnCall, 'api', instanceEE))
Object.entries(api).forEach(([fnName, fnCall]) => registerHandler('api-' + fnName, fnCall, 'api', agent.ee))

// All API functions get passed the time they were called as their
// first parameter. These functions can be called asynchronously.

function finished (t, providedTime) {
var time = providedTime ? providedTime - originTime : t
handle(CUSTOM_METRIC_CHANNEL, ['finished', { time }], undefined, FEATURE_NAMES.metrics, instanceEE)
const time = providedTime ? providedTime - originTime : t
handle(CUSTOM_METRIC_CHANNEL, ['finished', { time }], undefined, FEATURE_NAMES.metrics, agent.ee)
addToTrace(t, { name: 'finished', start: time + originTime, origin: 'nr' })
handle('api-addPageAction', [time, 'finished'], undefined, FEATURE_NAMES.genericEvents, instanceEE)
handle('api-addPageAction', [time, 'finished'], undefined, FEATURE_NAMES.genericEvents, agent.ee)
}

function addToTrace (t, evt) {
function addToTrace (_, evt) {
if (!(evt && typeof evt === 'object' && evt.name && evt.start)) return

var report = {
const report = {
n: evt.name,
s: evt.start - originTime,
e: (evt.end || evt.start) - originTime,
o: evt.origin || '',
t: 'api'
}

handle('bstApi', [report], undefined, FEATURE_NAMES.sessionTrace, instanceEE)
handle('bstApi', [report], undefined, FEATURE_NAMES.sessionTrace, agent.ee)
}

function setErrorHandler (t, handler) {
getRuntime(agentIdentifier).onerror = handler
function setErrorHandler (_, handler) {
agent.runtime.onerror = handler
}

var releaseCount = 0
function addRelease (t, name, id) {
let releaseCount = 0
function addRelease (_, name, id) {
if (++releaseCount > 10) return
getRuntime(agentIdentifier).releaseIds[name.slice(-200)] = ('' + id).slice(-200)
agent.runtime.releaseIds[name.slice(-200)] = ('' + id).slice(-200)
}
}
Loading
Loading