Skip to content

Commit 42a2ae5

Browse files
williazzqhanam
andauthored
feat: Ignore resources with non-http scheme (#419)
--------- Co-authored-by: Quinn Hanam <[email protected]>
1 parent 00ee4b4 commit 42a2ae5

File tree

8 files changed

+176
-52
lines changed

8 files changed

+176
-52
lines changed

docs/configuration.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,3 +194,21 @@ const awsRum: AwsRum = new AwsRum(
194194
| Name | Type | Default | Description |
195195
| --- | --- | --- | --- |
196196
| eventLimit | Number | `10` | The maximum number of resources to record load timing. <br/><br/>There may be many similar resources on a page (e.g., images) and recording all resources may add expense without adding value. The web client records all HTML files and JavaScript files, while recording a sample of stylesheets, images and fonts. Increasing the event limit increases the maximum number of sampled resources. |
197+
| ignore | Function(event: PerformanceEntry) : any | `(entry: PerformanceEntry) => entry.entryType === 'resource' && !/^https?:/.test(entry.name)` | A function which accepts a [PerformanceEntry](https://developer.mozilla.org/en-US/docs/Web/API/PerformanceEntry) and returns a value that coerces to true when the PerformanceEntry should be ignored.</br></br> By default, [PerformanceResourceTiming](https://developer.mozilla.org/en-US/docs/Web/API/PerformanceResourceTiming) entries with URLs that do not have http(s) schemes are ignored. This causes resources loaded by browser extensions to be ignored. |
198+
199+
For example, the following telemetry config array causes the web client to ignore all resource entries.
200+
201+
```javascript
202+
telemetries: [
203+
[
204+
'errors',
205+
'http',
206+
'performance',
207+
{
208+
ignore: (entry: PerformanceEntry) => {
209+
return entry.entryType === 'resource';
210+
}
211+
}
212+
],
213+
]
214+
```

src/orchestration/Orchestration.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -506,7 +506,7 @@ export class Orchestration {
506506
},
507507
[TelemetryEnum.Performance]: (config: object): InternalPlugin[] => {
508508
return [
509-
new NavigationPlugin(),
509+
new NavigationPlugin(config),
510510
new ResourcePlugin(config),
511511
new WebVitalsPlugin()
512512
];

src/plugins/event-plugins/NavigationPlugin.ts

Lines changed: 34 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
import { InternalPlugin } from '../InternalPlugin';
22
import { NavigationEvent } from '../../events/navigation-event';
33
import { PERFORMANCE_NAVIGATION_EVENT_TYPE } from '../utils/constant';
4+
import {
5+
PartialPerformancePluginConfig,
6+
PerformancePluginConfig,
7+
defaultPerformancePluginConfig
8+
} from '../utils/performance-utils';
49

510
export const NAVIGATION_EVENT_PLUGIN_ID = 'navigation';
611

@@ -13,8 +18,10 @@ const LOAD = 'load';
1318
* For RUM, these event types are inter-dependent. So they are recorded under one plugin.
1419
*/
1520
export class NavigationPlugin extends InternalPlugin {
16-
constructor() {
21+
private config: PerformancePluginConfig;
22+
constructor(config?: PartialPerformancePluginConfig) {
1723
super(NAVIGATION_EVENT_PLUGIN_ID);
24+
this.config = { ...defaultPerformancePluginConfig, ...config };
1825
}
1926

2027
enable(): void {
@@ -70,13 +77,14 @@ export class NavigationPlugin extends InternalPlugin {
7077
this.performanceNavigationEventHandlerTimingLevel1();
7178
} else {
7279
const navigationObserver = new PerformanceObserver((list) => {
73-
list.getEntries().forEach((event) => {
74-
if (event.entryType === NAVIGATION) {
80+
list.getEntries()
81+
.filter((e) => e.entryType === NAVIGATION)
82+
.filter((e) => !this.config.ignore(e))
83+
.forEach((event) => {
7584
this.performanceNavigationEventHandlerTimingLevel2(
76-
event
85+
event as PerformanceNavigationTiming
7786
);
78-
}
79-
});
87+
});
8088
});
8189
navigationObserver.observe({
8290
entryTypes: [NAVIGATION]
@@ -184,11 +192,20 @@ export class NavigationPlugin extends InternalPlugin {
184192
/**
185193
* W3C specification: https://www.w3.org/TR/navigation-timing-2/#bib-navigation-timing
186194
*/
187-
performanceNavigationEventHandlerTimingLevel2 = (entryData: any): void => {
195+
performanceNavigationEventHandlerTimingLevel2 = (
196+
entryData: PerformanceNavigationTiming
197+
): void => {
188198
const eventDataNavigationTimingLevel2: NavigationEvent = {
189199
version: '1.0.0',
190-
initiatorType: entryData.initiatorType,
191-
navigationType: entryData.type,
200+
initiatorType: entryData.initiatorType as
201+
| 'navigation'
202+
| 'route_change',
203+
navigationType: entryData.type as
204+
| 'back_forward'
205+
| 'navigate'
206+
| 'reload'
207+
| 'reserved'
208+
| undefined,
192209
startTime: entryData.startTime,
193210
unloadEventStart: entryData.unloadEventStart,
194211
promptForUnload:
@@ -262,10 +279,14 @@ export class NavigationPlugin extends InternalPlugin {
262279
protected onload(): void {
263280
if (this.enabled) {
264281
if (this.hasTheWindowLoadEventFired()) {
265-
const navData = window.performance.getEntriesByType(
266-
NAVIGATION
267-
)[0] as PerformanceNavigationTiming;
268-
this.performanceNavigationEventHandlerTimingLevel2(navData);
282+
window.performance
283+
.getEntriesByType(NAVIGATION)
284+
.filter((e) => !this.config.ignore(e))
285+
.forEach((event) =>
286+
this.performanceNavigationEventHandlerTimingLevel2(
287+
event
288+
)
289+
);
269290
} else {
270291
window.addEventListener(LOAD, this.eventListener);
271292
}

src/plugins/event-plugins/ResourcePlugin.ts

Lines changed: 19 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -6,44 +6,26 @@ import {
66
} from '../../utils/common-utils';
77
import { ResourceEvent } from '../../events/resource-event';
88
import { PERFORMANCE_RESOURCE_EVENT_TYPE } from '../utils/constant';
9+
import {
10+
defaultPerformancePluginConfig,
11+
PartialPerformancePluginConfig,
12+
PerformancePluginConfig
13+
} from '../utils/performance-utils';
914

1015
export const RESOURCE_EVENT_PLUGIN_ID = 'resource';
1116

1217
const RESOURCE = 'resource';
1318
const LOAD = 'load';
1419

15-
export type PartialResourcePluginConfig = {
16-
eventLimit?: number;
17-
recordAllTypes?: ResourceType[];
18-
sampleTypes?: ResourceType[];
19-
};
20-
21-
export type ResourcePluginConfig = {
22-
eventLimit: number;
23-
recordAllTypes: ResourceType[];
24-
sampleTypes: ResourceType[];
25-
};
26-
27-
export const defaultConfig = {
28-
eventLimit: 10,
29-
recordAllTypes: [ResourceType.DOCUMENT, ResourceType.SCRIPT],
30-
sampleTypes: [
31-
ResourceType.STYLESHEET,
32-
ResourceType.IMAGE,
33-
ResourceType.FONT,
34-
ResourceType.OTHER
35-
]
36-
};
37-
3820
/**
3921
* This plugin records resource performance timing events generated during every page load/re-load.
4022
*/
4123
export class ResourcePlugin extends InternalPlugin {
42-
private config: ResourcePluginConfig;
24+
private config: PerformancePluginConfig;
4325

44-
constructor(config?: PartialResourcePluginConfig) {
26+
constructor(config?: PartialPerformancePluginConfig) {
4527
super(RESOURCE_EVENT_PLUGIN_ID);
46-
this.config = { ...defaultConfig, ...config };
28+
this.config = { ...defaultPerformancePluginConfig, ...config };
4729
}
4830

4931
enable(): void {
@@ -72,6 +54,7 @@ export class ResourcePlugin extends InternalPlugin {
7254
const resourceObserver = new PerformanceObserver((list) => {
7355
list.getEntries()
7456
.filter((e) => e.entryType === RESOURCE)
57+
.filter((e) => !this.config.ignore(e))
7558
.forEach((event) => {
7659
// Out of n resource events, x events are recorded using Observer API
7760
const type: ResourceType = getResourceFileType(event.name);
@@ -90,14 +73,16 @@ export class ResourcePlugin extends InternalPlugin {
9073
// Note: IE11 browser does not support Performance Observer API. Handle the failure gracefully
9174
const events = performance.getEntriesByType(RESOURCE);
9275
if (events !== undefined && events.length > 0) {
93-
events.forEach((event) => {
94-
const type: ResourceType = getResourceFileType(event.name);
95-
if (this.config.recordAllTypes.includes(type)) {
96-
recordAll.push(event);
97-
} else if (this.config.sampleTypes.includes(type)) {
98-
sample.push(event);
99-
}
100-
});
76+
events
77+
.filter((e) => !this.config.ignore(e))
78+
.forEach((event) => {
79+
const type: ResourceType = getResourceFileType(event.name);
80+
if (this.config.recordAllTypes.includes(type)) {
81+
recordAll.push(event);
82+
} else if (this.config.sampleTypes.includes(type)) {
83+
sample.push(event);
84+
}
85+
});
10186
}
10287

10388
// Record events for resources in recordAllTypes

src/plugins/event-plugins/__tests__/NavigationPlugin.test.ts

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,10 @@ import {
1010
import { NavigationPlugin } from '../NavigationPlugin';
1111
import { context, record } from '../../../test-utils/test-utils';
1212
import { PERFORMANCE_NAVIGATION_EVENT_TYPE } from '../../utils/constant';
13+
import { PartialPerformancePluginConfig } from 'plugins/utils/performance-utils';
1314

14-
const buildNavigationPlugin = () => {
15-
return new NavigationPlugin();
15+
const buildNavigationPlugin = (config?: PartialPerformancePluginConfig) => {
16+
return new NavigationPlugin(config);
1617
};
1718

1819
describe('NavigationPlugin tests', () => {
@@ -105,6 +106,19 @@ describe('NavigationPlugin tests', () => {
105106
// Assert
106107
expect(record).toHaveBeenCalledTimes(0);
107108
});
109+
test('when entry is ignored then level 2 navigation is not recorded', async () => {
110+
// enables plugin by default
111+
const plugin: NavigationPlugin = buildNavigationPlugin({
112+
ignore: (event) => true
113+
});
114+
115+
plugin.load(context);
116+
window.dispatchEvent(new Event('load'));
117+
plugin.disable();
118+
119+
// Assert
120+
expect(record).not.toHaveBeenCalled();
121+
});
108122

109123
test('when window.load fires after plugin loads then navigation timing is recorded', async () => {
110124
// Setting up new mocked window that has not loaded

src/plugins/event-plugins/__tests__/ResourcePlugin.test.ts

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import {
1111
putRumEventsGammaDocument,
1212
dataPlaneDocument
1313
} from '../../../test-utils/mock-data';
14-
import { PartialResourcePluginConfig, ResourcePlugin } from '../ResourcePlugin';
14+
import { ResourcePlugin } from '../ResourcePlugin';
1515
import { mockRandom } from 'jest-mock-random';
1616
import {
1717
context,
@@ -23,8 +23,9 @@ import {
2323
import { PERFORMANCE_RESOURCE_EVENT_TYPE } from '../../utils/constant';
2424
import { ResourceEvent } from '../../../events/resource-event';
2525
import { PluginContext } from '../../types';
26+
import { PartialPerformancePluginConfig } from 'plugins/utils/performance-utils';
2627

27-
const buildResourcePlugin = (config?: PartialResourcePluginConfig) => {
28+
const buildResourcePlugin = (config?: PartialPerformancePluginConfig) => {
2829
return new ResourcePlugin(config);
2930
};
3031

@@ -250,4 +251,20 @@ describe('ResourcePlugin tests', () => {
250251
})
251252
);
252253
});
254+
255+
test('when entry is ignored then resource is not recorded', async () => {
256+
// Setup
257+
mockPerformanceObjectWithResources();
258+
mockPerformanceObserver();
259+
const plugin = buildResourcePlugin({
260+
ignore: (entry: PerformanceEntry) => true
261+
});
262+
263+
// Run
264+
plugin.load(context);
265+
window.dispatchEvent(new Event('load'));
266+
plugin.disable();
267+
268+
expect(record).not.toHaveBeenCalled();
269+
});
253270
});
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { defaultIgnore } from '../performance-utils';
2+
3+
describe('performance-utils', () => {
4+
describe('defaultPerformanceIgnore', () => {
5+
test('when entry has a non-http URL schema then entry is ignored', async () => {
6+
const mockEntry = {
7+
name: 'chrome-extension://localhost',
8+
entryType: 'resource'
9+
} as PerformanceResourceTiming;
10+
const result = defaultIgnore(mockEntry);
11+
expect(result).toBe(true);
12+
});
13+
test('when entry is has an http URL schema then entry is not ignored', async () => {
14+
const mockEntry = {
15+
name: 'http://localhost',
16+
entryType: 'resource'
17+
} as PerformanceResourceTiming;
18+
const result = defaultIgnore(mockEntry);
19+
expect(result).toBe(false);
20+
});
21+
test('when entry is has an https URL schema then entry is not ignored', async () => {
22+
const mockEntry = {
23+
name: 'https://localhost',
24+
entryType: 'resource'
25+
} as PerformanceResourceTiming;
26+
const result = defaultIgnore(mockEntry);
27+
expect(result).toBe(false);
28+
});
29+
30+
test('when entry is not PerformanceResourceTiming then the entry is not ignored', async () => {
31+
const mockEntry = {
32+
name: 'chrome-extension://localhost',
33+
entryType: 'navigation'
34+
} as PerformanceNavigationTiming;
35+
const result = defaultIgnore(mockEntry);
36+
expect(result).toBe(false);
37+
});
38+
});
39+
});
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { ResourceType } from '../../utils/common-utils';
2+
3+
export const defaultIgnore = (entry: PerformanceEntry) =>
4+
entry.entryType === 'resource' && !/^https?:/.test(entry.name);
5+
6+
export type PartialPerformancePluginConfig = {
7+
eventLimit?: number;
8+
ignore?: (event: PerformanceEntry) => any;
9+
recordAllTypes?: ResourceType[];
10+
sampleTypes?: ResourceType[];
11+
};
12+
13+
export type PerformancePluginConfig = {
14+
eventLimit: number;
15+
ignore: (event: PerformanceEntry) => any;
16+
recordAllTypes: ResourceType[];
17+
sampleTypes: ResourceType[];
18+
};
19+
20+
export const defaultPerformancePluginConfig = {
21+
eventLimit: 10,
22+
ignore: defaultIgnore,
23+
recordAllTypes: [ResourceType.DOCUMENT, ResourceType.SCRIPT],
24+
sampleTypes: [
25+
ResourceType.STYLESHEET,
26+
ResourceType.IMAGE,
27+
ResourceType.FONT,
28+
ResourceType.OTHER
29+
]
30+
};

0 commit comments

Comments
 (0)