@bugsnag/browser-performance
Advanced tools
Comparing version 0.0.2-alpha.2 to 0.0.2-alpha.3
@@ -23,12 +23,13 @@ class FullPageLoadPlugin { | ||
} | ||
const route = configuration.routingProvider.resolveRoute(new URL(this.location.href)); | ||
const startTime = 0; // TODO: Ensure this correctly resolves to timeOrigin | ||
const span = this.spanFactory.startSpan(`[FullPageLoad]${route}`, startTime); | ||
// Browser attributes | ||
span.setAttribute('bugsnag.span.category', 'full_page_load'); | ||
span.setAttribute('bugsnag.browser.page.referrer', this.document.referrer); | ||
span.setAttribute('bugsnag.browser.page.route', route); | ||
const url = new URL(this.location.href); | ||
this.onSettle((endTime) => { | ||
// note: we don't need to check if we were backgrounded here | ||
// as the span factory already checks for backgrounding on all open spans | ||
if (this.wasBackgrounded) | ||
return; | ||
const route = configuration.routingProvider.resolveRoute(url); | ||
const startTime = 0; | ||
const span = this.spanFactory.startSpan(`[FullPageLoad]${route}`, startTime); | ||
// Browser attributes | ||
span.setAttribute('bugsnag.span.category', 'full_page_load'); | ||
span.setAttribute('bugsnag.browser.page.referrer', this.document.referrer); | ||
span.setAttribute('bugsnag.browser.page.route', route); | ||
this.webVitals.attachTo(span); | ||
@@ -35,0 +36,0 @@ this.spanFactory.endSpan(span, endTime); |
import { createClient } from '@bugsnag/core-performance'; | ||
import { FullPageLoadPlugin } from './auto-instrumentation/full-page-load-plugin.js'; | ||
import { NetworkRequestPlugin } from './auto-instrumentation/network-request-plugin.js'; | ||
import { RouteChangePlugin } from './auto-instrumentation/route-change-plugin.js'; | ||
import createBrowserBackgroundingListener from './backgrounding-listener.js'; | ||
import createClock from './clock.js'; | ||
import { createSchema } from './config.js'; | ||
import { createDefaultRoutingProvider } from './default-routing-provider.js'; | ||
import createBrowserDeliveryFactory from './delivery.js'; | ||
import idGenerator from './id-generator.js'; | ||
import createOnSettle from './on-settle/index.js'; | ||
import createFetchRequestTracker from './request-tracker/request-tracker-fetch.js'; | ||
import createXmlHttpRequestTracker from './request-tracker/request-tracker-xhr.js'; | ||
import createResourceAttributesSource from './resource-attributes-source.js'; | ||
import createSpanAttributesSource from './span-attributes-source.js'; | ||
import createFetchRequestTracker from './request-tracker/request-tracker-fetch.js'; | ||
import createXmlHttpRequestTracker from './request-tracker/request-tracker-xhr.js'; | ||
import { NetworkRequestPlugin } from './auto-instrumentation/network-request-plugin.js'; | ||
import { WebVitals } from './web-vitals.js'; | ||
@@ -24,2 +26,3 @@ | ||
const onSettle = createOnSettle(clock, window, fetchRequestTracker, xhrRequestTracker, performance); | ||
const DefaultRoutingProvider = createDefaultRoutingProvider(onSettle, window.location); | ||
const BugsnagPerformance = createClient({ | ||
@@ -32,10 +35,11 @@ backgroundingListener, | ||
idGenerator, | ||
schema: createSchema(window.location.hostname), | ||
schema: createSchema(window.location.hostname, new DefaultRoutingProvider()), | ||
plugins: (spanFactory) => [ | ||
onSettle, | ||
new FullPageLoadPlugin(document, window.location, spanFactory, webVitals, onSettle, backgroundingListener), | ||
new NetworkRequestPlugin(spanFactory, fetchRequestTracker, xhrRequestTracker) | ||
new NetworkRequestPlugin(spanFactory, fetchRequestTracker, xhrRequestTracker), | ||
new RouteChangePlugin(spanFactory, clock, window.location) | ||
] | ||
}); | ||
export { BugsnagPerformance as default }; | ||
export { DefaultRoutingProvider, BugsnagPerformance as default, onSettle }; |
import { schema, isStringOrRegExpArray } from '@bugsnag/core-performance'; | ||
import { DefaultRoutingProvider, isRoutingProvider } from './routing-provider.js'; | ||
import { isRoutingProvider } from './routing-provider.js'; | ||
function createSchema(hostname) { | ||
function createSchema(hostname, defaultRoutingProvider) { | ||
return Object.assign(Object.assign({}, schema), { releaseStage: Object.assign(Object.assign({}, schema.releaseStage), { defaultValue: hostname === 'localhost' ? 'development' : 'production' }), autoInstrumentFullPageLoads: { | ||
@@ -13,4 +13,8 @@ defaultValue: true, | ||
validate: (value) => value === true || value === false | ||
}, autoInstrumentRouteChanges: { | ||
defaultValue: true, | ||
message: 'should be true|false', | ||
validate: (value) => value === true || value === false | ||
}, routingProvider: { | ||
defaultValue: new DefaultRoutingProvider(), | ||
defaultValue: defaultRoutingProvider, | ||
message: 'should be a routing provider', | ||
@@ -17,0 +21,0 @@ validate: isRoutingProvider |
@@ -1,2 +0,1 @@ | ||
export { default } from './browser.js'; | ||
export { DefaultRoutingProvider } from './routing-provider.js'; | ||
export { DefaultRoutingProvider, default, onSettle } from './browser.js'; |
@@ -25,3 +25,4 @@ import { Settler } from './settler.js'; | ||
// there's only ever one navigation entry | ||
const entry = performance.getEntriesByType('navigation')[0]; | ||
// PLAT-10204 Prevent snags occuring due to DOM scanning bots like BuiltWith https://builtwith.com/biup | ||
const entry = typeof performance.getEntriesByType === 'function' ? performance.getEntriesByType('navigation')[0] : undefined; | ||
let settledTime; | ||
@@ -28,0 +29,0 @@ if (isPerformanceNavigationTiming(entry)) { |
@@ -5,3 +5,3 @@ import { ResourceAttributes } from '@bugsnag/core-performance'; | ||
return function resourceAttributesSource(config) { | ||
const attributes = new ResourceAttributes(config.releaseStage, config.appVersion, 'bugsnag.performance.browser', '0.0.2-alpha.1'); | ||
const attributes = new ResourceAttributes(config.releaseStage, config.appVersion, 'bugsnag.performance.browser', '0.0.2-alpha.2'); | ||
attributes.set('browser.user_agent', navigator.userAgent); | ||
@@ -8,0 +8,0 @@ // chromium only |
import { isObject } from '@bugsnag/core-performance'; | ||
const defaultRouteResolver = (url) => url.pathname; | ||
class DefaultRoutingProvider { | ||
constructor(resolveRoute = defaultRouteResolver) { | ||
this.resolveRoute = resolveRoute; | ||
} | ||
} | ||
const isRoutingProvider = (value) => isObject(value) && | ||
typeof value.resolveRoute === 'function'; | ||
typeof value.resolveRoute === 'function' && | ||
typeof value.listenForRouteChanges === 'function'; | ||
export { DefaultRoutingProvider, isRoutingProvider }; | ||
export { isRoutingProvider }; |
@@ -0,3 +1,10 @@ | ||
export declare const onSettle: import("./on-settle").OnSettlePlugin; | ||
export declare const DefaultRoutingProvider: { | ||
new (resolveRoute?: import("./routing-provider").RouteResolver): { | ||
resolveRoute: import("./routing-provider").RouteResolver; | ||
listenForRouteChanges(startRouteChangeSpan: import("./routing-provider").StartRouteChangeCallback): void; | ||
}; | ||
}; | ||
declare const BugsnagPerformance: import("@bugsnag/core-performance").BugsnagPerformance<import("./config").BrowserConfiguration>; | ||
export default BugsnagPerformance; | ||
//# sourceMappingURL=browser.d.ts.map |
@@ -6,2 +6,3 @@ import { type ConfigOption, type Configuration, type CoreSchema } from '@bugsnag/core-performance'; | ||
autoInstrumentNetworkRequests: ConfigOption<boolean>; | ||
autoInstrumentRouteChanges: ConfigOption<boolean>; | ||
routingProvider: ConfigOption<RoutingProvider>; | ||
@@ -14,2 +15,3 @@ settleIgnoreUrls: ConfigOption<Array<string | RegExp>>; | ||
autoInstrumentNetworkRequests?: boolean; | ||
autoInstrumentRouteChanges?: boolean; | ||
routingProvider?: RoutingProvider; | ||
@@ -19,3 +21,3 @@ settleIgnoreUrls?: Array<string | RegExp>; | ||
} | ||
export declare function createSchema(hostname: string): BrowserSchema; | ||
export declare function createSchema(hostname: string, defaultRoutingProvider: RoutingProvider): BrowserSchema; | ||
//# sourceMappingURL=config.d.ts.map |
@@ -1,4 +0,4 @@ | ||
export { default } from './browser'; | ||
export { DefaultRoutingProvider, type RoutingProvider, type RouteResolver } from './routing-provider'; | ||
export { type Configuration } from '@bugsnag/core-performance'; | ||
export { DefaultRoutingProvider, default, onSettle } from './browser'; | ||
export { type BrowserConfiguration } from './config'; | ||
export { type RouteResolver, type RoutingProvider } from './routing-provider'; | ||
//# sourceMappingURL=index.d.ts.map |
@@ -0,10 +1,9 @@ | ||
import { type Span, type Time } from '@bugsnag/core-performance'; | ||
export type StartRouteChangeCallback = (newRoute: string, routeChangeTime?: Time) => Span; | ||
export interface RoutingProvider { | ||
resolveRoute: RouteResolver; | ||
resolveRoute: (url: URL) => string; | ||
listenForRouteChanges: (startRouteChangeSpan: StartRouteChangeCallback) => void; | ||
} | ||
export type RouteResolver = (url: URL) => string; | ||
export declare class DefaultRoutingProvider implements RoutingProvider { | ||
resolveRoute: RouteResolver; | ||
constructor(resolveRoute?: RouteResolver); | ||
} | ||
export declare const isRoutingProvider: (value: unknown) => value is RoutingProvider; | ||
//# sourceMappingURL=routing-provider.d.ts.map |
@@ -11,6 +11,7 @@ import { type Clock, type SpanInternal } from '@bugsnag/core-performance'; | ||
export declare class WebVitals { | ||
private performance; | ||
private clock; | ||
private readonly performance; | ||
private readonly clock; | ||
private readonly observers; | ||
private largestContentfulPaint; | ||
private observer; | ||
private cumulativeLayoutShift; | ||
constructor(performance: PerformanceWithNavigationTiming, clock: Clock, PerformanceObserverClass?: typeof PerformanceObserver); | ||
@@ -21,5 +22,6 @@ attachTo(span: SpanInternal): void; | ||
private firstInputDelay; | ||
private cumulativeLayoutShift; | ||
private observeLargestContentfulPaint; | ||
private observeLayoutShift; | ||
} | ||
export {}; | ||
//# sourceMappingURL=web-vitals.d.ts.map |
@@ -5,11 +5,11 @@ class WebVitals { | ||
this.clock = clock; | ||
if (PerformanceObserverClass && | ||
Array.isArray(PerformanceObserverClass.supportedEntryTypes) && | ||
PerformanceObserverClass.supportedEntryTypes.includes('largest-contentful-paint')) { | ||
this.observer = new PerformanceObserverClass((list) => { | ||
const entries = list.getEntries(); | ||
const lastEntry = entries[entries.length - 1]; // Use the latest LCP candidate | ||
this.largestContentfulPaint = lastEntry.startTime; | ||
}); | ||
this.observer.observe({ type: 'largest-contentful-paint', buffered: true }); | ||
this.observers = []; | ||
if (PerformanceObserverClass && Array.isArray(PerformanceObserverClass.supportedEntryTypes)) { | ||
const supportedEntryTypes = PerformanceObserverClass.supportedEntryTypes; | ||
if (supportedEntryTypes.includes('largest-contentful-paint')) { | ||
this.observeLargestContentfulPaint(PerformanceObserverClass); | ||
} | ||
if (supportedEntryTypes.includes('layout-shift')) { | ||
this.observeLayoutShift(PerformanceObserverClass); | ||
} | ||
} | ||
@@ -31,5 +31,4 @@ } | ||
} | ||
const cumulativeLayoutShift = this.cumulativeLayoutShift(); | ||
if (cumulativeLayoutShift) { | ||
span.setAttribute('bugsnag.metrics.cls', cumulativeLayoutShift); | ||
if (this.cumulativeLayoutShift) { | ||
span.setAttribute('bugsnag.metrics.cls', this.cumulativeLayoutShift); | ||
} | ||
@@ -39,4 +38,6 @@ if (this.largestContentfulPaint) { | ||
} | ||
if (this.observer) { | ||
this.observer.disconnect(); | ||
// as there is only 1 page load span, we don't need to keep observing | ||
// performance events, so can disconnect from any observers we've registered | ||
for (const observer of this.observers) { | ||
observer.disconnect(); | ||
} | ||
@@ -75,32 +76,48 @@ } | ||
} | ||
cumulativeLayoutShift() { | ||
observeLargestContentfulPaint(PerformanceObserverClass) { | ||
const observer = new PerformanceObserverClass((list) => { | ||
const entries = list.getEntries(); | ||
if (entries.length > 0) { | ||
// Use the latest LCP candidate | ||
this.largestContentfulPaint = entries[entries.length - 1].startTime; | ||
} | ||
}); | ||
observer.observe({ type: 'largest-contentful-paint', buffered: true }); | ||
this.observers.push(observer); | ||
} | ||
observeLayoutShift(PerformanceObserverClass) { | ||
let session; | ||
for (const entry of this.performance.getEntriesByType('layout-shift')) { | ||
// ignore entries with recent input as it's likely the layout shifted due | ||
// to user input and this metric only cares about unexpected layout | ||
// shifts | ||
if (entry.hadRecentInput) { | ||
continue; | ||
const observer = new PerformanceObserverClass((list) => { | ||
for (const entry of list.getEntries()) { | ||
// ignore entries with recent input as it's likely the layout shifted due | ||
// to user input and this metric only cares about unexpected layout | ||
// shifts | ||
if (entry.hadRecentInput) { | ||
continue; | ||
} | ||
// include this entry in the current session if we have a current session | ||
// and this entry fits into the session window (it occurred less than 1 | ||
// second after the previous entry and the session duration is less than | ||
// 5 seconds), otherwise start a new session | ||
if (session && | ||
entry.startTime - session.previousStartTime < 1000 && | ||
entry.startTime - session.firstStartTime < 5000) { | ||
session.value += entry.value; | ||
session.previousStartTime = entry.startTime; | ||
} | ||
else { | ||
session = { | ||
value: entry.value, | ||
firstStartTime: entry.startTime, | ||
previousStartTime: entry.startTime | ||
}; | ||
} | ||
} | ||
// include this entry in the current session if we have a current session | ||
// and this entry fits into the session window (it occurred less than 1 | ||
// second after the previous entry and the session duration is less than | ||
// 5 seconds), otherwise start a new session | ||
if (session && | ||
entry.startTime - session.previousStartTime < 1000 && | ||
entry.startTime - session.firstStartTime < 5000) { | ||
session.value += entry.value; | ||
session.previousStartTime = entry.startTime; | ||
(this.cumulativeLayoutShift === undefined || session.value > this.cumulativeLayoutShift)) { | ||
this.cumulativeLayoutShift = session.value; | ||
} | ||
else { | ||
session = { | ||
value: entry.value, | ||
firstStartTime: entry.startTime, | ||
previousStartTime: entry.startTime | ||
}; | ||
} | ||
} | ||
if (session) { | ||
return session.value; | ||
} | ||
}); | ||
observer.observe({ type: 'layout-shift', buffered: true }); | ||
this.observers.push(observer); | ||
} | ||
@@ -107,0 +124,0 @@ } |
{ | ||
"name": "@bugsnag/browser-performance", | ||
"version": "0.0.2-alpha.2", | ||
"version": "0.0.2-alpha.3", | ||
"description": "BugSnag performance monitoring for browsers", | ||
@@ -22,3 +22,3 @@ "homepage": "https://www.bugsnag.com/", | ||
"dependencies": { | ||
"@bugsnag/core-performance": "^0.0.2-alpha.2" | ||
"@bugsnag/core-performance": "^0.0.2-alpha.3" | ||
}, | ||
@@ -31,3 +31,3 @@ "type": "module", | ||
], | ||
"gitHead": "c45e1fd9589027021cb56e8e8b41ed9c1999d25f" | ||
"gitHead": "27db39a417a02d300bbc2ff8226f22d37a2b3d70" | ||
} |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
65291
82
1061