Skip to content
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

feat: Origin of agent webpack chunks now changeable #659

Merged
merged 27 commits into from
Sep 12, 2023
Merged
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
f099ea8
Allow agent chunk domain to be configured NR-149705
cwli24 Aug 23, 2023
7d6c282
Add e2e test for assets proxy
cwli24 Aug 24, 2023
a7cf585
Make public-path in npm a dummy
cwli24 Aug 24, 2023
1150208
Improve asset url validation
cwli24 Aug 24, 2023
6b4324a
PR comments
cwli24 Aug 24, 2023
cfe0b75
Merge commit 'd233e1428e1a7f3fe8b5ff1803bc1a306be01246' into proxy-as…
cwli24 Aug 24, 2023
052229a
Fix linting
cwli24 Aug 24, 2023
20daa9f
Update src/common/config/state/init.js
cwli24 Aug 24, 2023
887e56b
Merge commit '6cb823842fab406a33b9698edee1932c29204df3' into proxy-as…
cwli24 Aug 30, 2023
f790ff6
Include path in asset url
cwli24 Aug 30, 2023
5e2fb08
Allow only https unless retro ssl is set false in validation
cwli24 Aug 30, 2023
2116c25
Fix e2e test
cwli24 Aug 31, 2023
9fa7616
Fix wdio test
cwli24 Sep 1, 2023
ca6e005
Revert prod arg to wdio
cwli24 Sep 1, 2023
41bbb15
Merge commit '85336a43595bbf3d2793aafe665a47650a20ed21' into proxy-as…
cwli24 Sep 1, 2023
6e28f92
Put beacon under same url validation
cwli24 Sep 1, 2023
8e4b232
Fix behavior in IE & add beacon proxy tests
cwli24 Sep 1, 2023
c0e182a
Add sm on proxy & test
cwli24 Sep 1, 2023
028c8c6
Workaround local testing for ie11
cwli24 Sep 1, 2023
d781a96
Remove validation & rework proxy
cwli24 Sep 6, 2023
021b3fa
Merge commit '71d47d6e3cac3b0a2f1bbc23447f7eb272679d3f' into proxy-sm
cwli24 Sep 6, 2023
2537f97
Update the proxy e2e tests
cwli24 Sep 6, 2023
5c51476
pr comments
cwli24 Sep 11, 2023
87ab6f3
Merge commit 'a34edb982d9f6879b2a066cfaadea7dd568ad932' into proxy-as…
cwli24 Sep 11, 2023
afa8bc3
Merge commit '2004f5fc7fbec53db5dd2bcbf1180cdb0aba5a9e' into proxy-as…
cwli24 Sep 11, 2023
029cdfd
Get micro agent to work bc of ee issue
cwli24 Sep 12, 2023
6da45ab
tweak
cwli24 Sep 12, 2023
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
6 changes: 4 additions & 2 deletions babel.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,8 @@ module.exports = function (api, ...args) {
[
'./tools/babel/plugins/transform-import',
{
'(constants/)env$': '$1env.npm'
'(constants/)env$': '$1env.npm',
'(configure/)public-path$': '$1public-path.npm'
}
]
]
Expand All @@ -112,7 +113,8 @@ module.exports = function (api, ...args) {
[
'./tools/babel/plugins/transform-import',
{
'(constants/)env$': '$1env.npm'
'(constants/)env$': '$1env.npm',
'(configure/)public-path$': '$1public-path.npm'
}
]
]
Expand Down
4 changes: 4 additions & 0 deletions src/common/config/state/init.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ const model = () => {
maskInputOptions: { password: true }
}
return {
proxy: {
assets: undefined, // if this value is set, it will be used to overwrite the webpack asset path used to fetch assets
beacon: undefined // likewise for the url to which we send analytics
},
privacy: { cookies_enabled: true }, // *cli - per discussion, default should be true
ajax: { deny_list: undefined, block_internal: true, enabled: true, harvestTimeSeconds: 10, autoStart: true },
distributed_tracing: {
Expand Down
6 changes: 3 additions & 3 deletions src/common/constants/runtime.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export const initiallyHidden = Boolean(globalScope?.document?.visibilityState ==

export const initialLocation = '' + globalScope?.location

export const isiOS = /iPad|iPhone|iPod/.test(navigator.userAgent)
export const isiOS = /iPad|iPhone|iPod/.test(globalScope.navigator?.userAgent)

/**
* Shared Web Workers introduced in iOS 16.0+ and n/a in 15.6-
Expand All @@ -60,7 +60,7 @@ export const isiOS = /iPad|iPhone|iPod/.test(navigator.userAgent)
export const iOSBelow16 = (isiOS && typeof SharedWorker === 'undefined')

export const ffVersion = (() => {
const match = navigator.userAgent.match(/Firefox[/\s](\d+\.\d+)/)
const match = globalScope.navigator?.userAgent?.match(/Firefox[/\s](\d+\.\d+)/)
if (Array.isArray(match) && match.length >= 2) {
return +match[1]
}
Expand All @@ -70,6 +70,6 @@ export const ffVersion = (() => {

export const isIE = Boolean(isBrowserScope && window.document.documentMode) // deprecated property that only works in IE

export const supportsSendBeacon = !!navigator.sendBeacon
export const supportsSendBeacon = !!globalScope.navigator?.sendBeacon

export const offset = Math.floor(globalScope?.performance?.timeOrigin || globalScope?.performance?.timing?.navigationStart || Date.now())
8 changes: 8 additions & 0 deletions src/common/constants/runtime.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -106,10 +106,12 @@ test.each([
{ userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 13.4; rv:109.0) Gecko/20100101 Firefox/114.0', expected: false }
])('should set isiOS to $expected for $userAgent', async ({ userAgent, expected }) => {
global.navigator.userAgent = userAgent
global.window = { navigator: global.navigator, document: true }

const runtime = await import('./runtime')

expect(runtime.isiOS).toEqual(expected)
delete global.window
})

test.each([
Expand All @@ -127,12 +129,14 @@ test.each([
global.SharedWorker = class SharedWorker {}
}
global.navigator.userAgent = userAgent
global.window = { navigator: global.navigator, document: true }

const runtime = await import('./runtime')

delete global.SharedWorker

expect(runtime.iOSBelow16).toEqual(expected)
delete global.window
})

test.each([
Expand All @@ -144,10 +148,12 @@ test.each([
{ userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 13.4; rv:109.0) Gecko/20100101 Firefox/114.0', expected: 114 }
])('should set ffVersion to $expected for $userAgent', async ({ userAgent, expected }) => {
global.navigator.userAgent = userAgent
global.window = { navigator: global.navigator, document: true }

const runtime = await import('./runtime')

expect(runtime.ffVersion).toEqual(expected)
delete global.window
})

test('should set supportsSendBeacon to false', async () => {
Expand All @@ -161,8 +167,10 @@ test('should set supportsSendBeacon to false', async () => {

test('should set supportsSendBeacon to true', async () => {
global.navigator.sendBeacon = jest.fn()
global.window = { navigator: global.navigator, document: true }

const runtime = await import('./runtime')

expect(runtime.supportsSendBeacon).toEqual(true)
delete global.window
})
10 changes: 6 additions & 4 deletions src/common/harvest/harvest.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { obj as encodeObj, param as encodeParam } from '../url/encode'
import { stringify } from '../util/stringify'
import * as submitData from '../util/submit-data'
import { getLocation } from '../url/location'
import { getInfo, getConfigurationValue, getRuntime } from '../config/config'
import { getInfo, getConfigurationValue, getRuntime, getConfiguration } from '../config/config'
import { cleanURL } from '../url/clean-url'
import { now } from '../timing/now'
import { eventListenerOpts } from '../event-listener/event-listener-opts'
Expand All @@ -33,7 +33,6 @@ export class Harvest extends SharedContext {

this.tooManyRequestsDelay = getConfigurationValue(this.sharedContext.agentIdentifier, 'harvest.tooManyRequestsDelay') || 60
this.obfuscator = new Obfuscator(this.sharedContext)
this.getScheme = () => (getConfigurationValue(this.sharedContext.agentIdentifier, 'ssl') === false) ? 'http' : 'https'

this._events = {}
}
Expand Down Expand Up @@ -96,10 +95,13 @@ export class Harvest extends SharedContext {
return false
}

const init = getConfiguration(this.sharedContext.agentIdentifier)
const protocol = init.ssl === false ? 'http' : 'https'
const perceviedBeacon = init.proxy.beacon || info.errorBeacon
const endpointURLPart = endpoint !== 'rum' ? `/${endpoint}` : ''
let url = `${this.getScheme()}://${info.errorBeacon}${endpointURLPart}/1/${info.licenseKey}`
let url = `${protocol}://${perceviedBeacon}${endpointURLPart}/1/${info.licenseKey}`
if (customUrl) url = customUrl
if (raw) url = `${this.getScheme()}://${info.errorBeacon}/${endpoint}`
if (raw) url = `${protocol}://${perceviedBeacon}/${endpoint}`

const baseParams = !raw && includeBaseParams ? this.baseQueryString() : ''
let payloadParams = encodeObj(qs, agentRuntime.maxBytes)
Expand Down
17 changes: 17 additions & 0 deletions src/common/harvest/harvest.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,10 @@ describe('_send', () => {
jest.mocked(configModule.getRuntime).mockReturnValue({
maxBytes: Infinity
})
jest.mocked(configModule.getConfiguration).mockReturnValue({
ssl: undefined,
proxy: {}
})

spec = {
endpoint: faker.datatype.uuid(),
Expand Down Expand Up @@ -223,6 +227,19 @@ describe('_send', () => {
})
})

test('able to use and send to proxy when defined', () => {
jest.mocked(configModule.getConfiguration).mockReturnValue({ proxy: { beacon: 'some_other_string' } })
const result = harvestInstance._send(spec)

expect(result).toEqual(true)
expect(submitMethod).toHaveBeenCalledWith({
body: JSON.stringify(spec.payload.body),
headers: [{ key: 'content-type', value: 'text/plain' }],
sync: undefined,
url: expect.stringContaining(`https://some_other_string/${spec.endpoint}/1/${licenseKey}?`)
})
})

test('should use the custom defined url', () => {
spec.customUrl = faker.internet.url()

Expand Down
6 changes: 5 additions & 1 deletion src/features/ajax/aggregate/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,9 @@ export class Aggregate extends AggregateBase {

this.drain()

const beacon = getInfo(agentIdentifier).errorBeacon
const proxyBeacon = agentInit.proxy.beacon

function storeXhr (params, metrics, startTime, endTime, type) {
metrics.time = startTime

Expand All @@ -86,7 +89,8 @@ export class Aggregate extends AggregateBase {
if (!allAjaxIsEnabled) return

if (!shouldCollectEvent(params)) {
if (params.hostname === getInfo(agentIdentifier).errorBeacon) {
if (params.hostname === beacon || (proxyBeacon && params.hostname === proxyBeacon)) {
// This doesn't make a distinction if the same-domain request is going to a different port or path...
handle(SUPPORTABILITY_METRIC_CHANNEL, ['Ajax/Events/Excluded/Agent'], undefined, FEATURE_NAMES.metrics, ee)
} else {
handle(SUPPORTABILITY_METRIC_CHANNEL, ['Ajax/Events/Excluded/App'], undefined, FEATURE_NAMES.metrics, ee)
Expand Down
6 changes: 1 addition & 5 deletions src/features/page_view_event/aggregate/index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { globalScope, isBrowserScope } from '../../../common/constants/runtime'
import { addPT, addPN } from '../../../common/timing/nav-timing'
import { stringify } from '../../../common/util/stringify'
import { getConfigurationValue, getInfo, getRuntime } from '../../../common/config/config'
import { getInfo, getRuntime } from '../../../common/config/config'
import { Harvest } from '../../../common/harvest/harvest'
import * as CONSTANTS from '../constants'
import { getActivatedFeaturesFlags } from './initialized-features'
Expand Down Expand Up @@ -36,10 +36,6 @@ export class Aggregate extends AggregateBase {
}
}

getScheme () {
return getConfigurationValue(this.agentIdentifier, 'ssl') === false ? 'http' : 'https'
}

sendRum () {
const info = getInfo(this.agentIdentifier)
const agentRuntime = getRuntime(this.agentIdentifier)
Expand Down
2 changes: 2 additions & 0 deletions src/loaders/agent.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
// important side effects
import './configure/public-path'
// loader files
import { AgentBase } from './agent-base'
import { getEnabledFeatures } from './features/enabled-features'
Expand Down
9 changes: 5 additions & 4 deletions src/loaders/api/apiAsync.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { FEATURE_NAMES } from '../features/features'
import { getConfigurationValue, getInfo, getRuntime } from '../../common/config/config'
import { getConfiguration, getInfo, getRuntime } from '../../common/config/config'
import { ee } from '../../common/event-emitter/contextual-ee'
import { handle } from '../../common/event-emitter/handle'
import { registerHandler } from '../../common/event-emitter/register-handler'
Expand All @@ -12,8 +12,6 @@ export function setAPI (agentIdentifier) {
var instanceEE = ee.get(agentIdentifier)
var cycle = 0

var scheme = (getConfigurationValue(agentIdentifier, 'ssl') === false) ? 'http' : 'https'

var api = {
finished: single(finished),
setErrorHandler,
Expand Down Expand Up @@ -66,7 +64,10 @@ export function setAPI (agentIdentifier) {
const agentInfo = getInfo(agentIdentifier)
if (!agentInfo.beacon) return

var url = scheme + '://' + agentInfo.beacon + '/1/' + agentInfo.licenseKey
const agentInit = getConfiguration(agentIdentifier)
const scheme = agentInit.ssl === false ? 'http' : 'https'
const beacon = agentInit.proxy.beacon || agentInfo.beacon
let url = `${scheme}://${beacon}/1/${agentInfo.licenseKey}`

url += '?a=' + agentInfo.applicationID + '&'
url += 't=' + requestName + '&'
Expand Down
31 changes: 24 additions & 7 deletions src/loaders/configure/configure.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,13 @@ import { addToNREUM, gosCDN, gosNREUMInitializedAgents } from '../../common/wind
import { getConfiguration, setConfiguration, setInfo, setLoaderConfig, setRuntime } from '../../common/config/config'
import { activatedFeatures } from '../../common/util/feature-flags'
import { isWorkerScope } from '../../common/constants/runtime'
import { redefinePublicPath } from './public-path'
import { handle } from '../../common/event-emitter/handle'
import { SUPPORTABILITY_METRIC_CHANNEL } from '../../features/metrics/constants'
import { ee } from '../../common/event-emitter/contextual-ee'
import { FEATURE_NAMES } from '../features/features'

let alreadySetOnce = false // the configure() function can run multiple times in agent lifecycle

export function configure (agentIdentifier, opts = {}, loaderType, forceDrain) {
// eslint-disable-next-line camelcase
Expand All @@ -14,6 +21,7 @@ export function configure (agentIdentifier, opts = {}, loaderType, forceDrain) {
// eslint-disable-next-line camelcase
loader_config = nr.loader_config
}
const agentEE = ee.get(agentIdentifier)

setConfiguration(agentIdentifier, init || {})
// eslint-disable-next-line camelcase
Expand All @@ -26,14 +34,23 @@ export function configure (agentIdentifier, opts = {}, loaderType, forceDrain) {
setInfo(agentIdentifier, info)

const updatedInit = getConfiguration(agentIdentifier)
const internalTrafficList = [info.beacon, info.errorBeacon]
if (!alreadySetOnce) {
alreadySetOnce = true
if (updatedInit.proxy.assets) {
redefinePublicPath(updatedInit.proxy.assets + '/') // much like the info.beacon & init.proxy.beacon, this input should not end in a slash, but one is needed for webpack concat
handle(SUPPORTABILITY_METRIC_CHANNEL, ['Config/AssetsUrl/Changed'], undefined, FEATURE_NAMES.metrics, agentEE)
internalTrafficList.push(updatedInit.proxy.assets)
}
if (updatedInit.proxy.beacon) {
handle(SUPPORTABILITY_METRIC_CHANNEL, ['Config/BeaconUrl/Changed'], undefined, FEATURE_NAMES.metrics, agentEE)
internalTrafficList.push(updatedInit.proxy.beacon)
}
}

runtime.denyList = [
...(updatedInit.ajax?.deny_list || []),
...(updatedInit.ajax?.block_internal
? [
info.beacon,
info.errorBeacon
]
: [])
...(updatedInit.ajax.deny_list || []),
...(updatedInit.ajax.block_internal ? internalTrafficList : [])
]
setRuntime(agentIdentifier, runtime)

Expand Down
6 changes: 6 additions & 0 deletions src/loaders/configure/public-path.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// Set the default CDN or remote for fetching the assets; NPM shouldn't change this var.

export const redefinePublicPath = (url) => {
// There's no URL validation here, so caller should check arg if need be.
__webpack_public_path__ = url // eslint-disable-line
}
4 changes: 4 additions & 0 deletions src/loaders/configure/public-path.npm.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@

export const redefinePublicPath = () => {
// We don't support setting public path in webpack via NPM build.
}
56 changes: 56 additions & 0 deletions tests/specs/proxy.e2e.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { notIE, notSafari, notIOS } from '../../tools/browser-matcher/common-matchers.mjs'

describe('Using proxy servers -', () => {
cwli24 marked this conversation as resolved.
Show resolved Hide resolved
it.withBrowsersMatching(notIE)('setting an assets url changes where agent fetches its chunks from', async () => {
const { host, port } = browser.testHandle.assetServerConfig
/** This is where we expect the agent to fetch its chunk. '/build/' is necessary within the URL because the way our asset server
* handles each test requests -- see /testing-server/plugins/test-handle/index.js. */
const assetServerChangedUrl = `http://${host}:${port}/build/fakepath`
let url = await browser.testHandle.assetURL('instrumented.html', { init: { proxy: { assets: assetServerChangedUrl } } })

// Expecting a resource fetch to https://bam-test-1.nr-local.net:<asset port>/build/fakepath/nr-spa.min.js for chunk
await browser.setTimeout({ pageLoad: 10000 }) // not expecting RUM to be called, etc.
await browser.url(url)
await browser.pause(500) // give it a bit of time to process
let resources = await browser.execute(function () { // IE11 hates this for some reason
return performance.getEntriesByType('resource')
})

expect(resources.some(entry => entry.name.includes('/build/fakepath/nr-spa'))).toBeTruthy()
})

// Safari does not include resource entries for failed-to-load script & ajax requests, so it's totally excluded.
it.withBrowsersMatching([notIE, notSafari, notIOS])('setting a beacon url changes RUM call destination', async () => {
let url = await browser.testHandle.assetURL('instrumented.html', { init: { proxy: { beacon: 'localhost:1234' } } })
await browser.setTimeout({ pageLoad: 10000 })
await browser.url(url)
if (browser.capabilities.browserName === 'firefox') await browser.pause(10000) // for some reason firefox takes longer to fail & create entry, maybe it's the localhost
else await browser.pause(5000) // takes RUM a while to get sent (< 3s but better more stable)

let resources = await browser.execute(function () {
return performance.getEntriesByType('resource')
})
expect(resources.some(entry => entry.name.startsWith('http://localhost:1234/1/'))).toBeTruthy()
})

it.withBrowsersMatching(notIE)('should send SM when beacon is changed', async () => {
const { host: bamHost, port: bamPort } = browser.testHandle.bamServerConfig
const { host: assetHost, port: assetPort } = browser.testHandle.assetServerConfig
// Even though the beacon isn't actually changed, this should still trigger the agent to emit sm due to difference between bam-test url vs actual default.
let url = await browser.testHandle.assetURL('instrumented.html', { init: { proxy: { beacon: `${bamHost}:${bamPort}`, assets: `http://${assetHost}:${assetPort}/build` } } })
await browser.url(url).then(() => browser.waitForAgentLoad())
const [unloadSupportMetricsResults] = await Promise.all([
browser.testHandle.expectSupportMetrics(),
await browser.url(await browser.testHandle.assetURL('/')) // Setup expects before navigating
])

const supportabilityMetrics = unloadSupportMetricsResults.request.body.sm || []
expect(supportabilityMetrics).toEqual(expect.arrayContaining([{
params: { name: 'Config/BeaconUrl/Changed' },
stats: { c: 1 }
}, {
params: { name: 'Config/AssetsUrl/Changed' },
stats: { c: 1 }
}]))
})
})
1 change: 1 addition & 0 deletions tools/browser-matcher/common-matchers.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ export const notSafari = new SpecMatcher()
.include('firefox')
.include('ios')
.include('android')
.include('ie')

export const onlyChrome = new SpecMatcher()
.include('chrome')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,14 @@ async function getLoaderContent (request, reply, testServer) {
testServer.config.polyfills ? '-polyfills' : ''
}.min.js`
)
const loaderFileStats = await fs.promises.stat(loaderFilePath)

if (!loaderFileStats.isFile()) {
let file
try {
file = (await fs.promises.readFile(loaderFilePath)).toString()
} catch (_) {
throw new Error(`Could not find loader file ${loaderFilePath}`)
}

return (await fs.promises.readFile(loaderFilePath)).toString()
return file
patrickhousley marked this conversation as resolved.
Show resolved Hide resolved
}

/**
Expand Down
Loading