-
Notifications
You must be signed in to change notification settings - Fork 1
EMBR-10483: Switch to a buffered POST beacon for JS errors #82
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
Changes from 1 commit
ee33a18
e96123c
717485a
b59394c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,6 @@ | ||
| { | ||
| "name": "@speedcurve/lux", | ||
| "version": "4.4.3", | ||
| "version": "4.5.0", | ||
| "config": { | ||
| "snippetVersion": "2.0.0" | ||
| }, | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,77 @@ | ||
| import type { ConfigObject } from "./config"; | ||
| import { postJson } from "./transport"; | ||
|
|
||
| type ErrorBeaconContext = { | ||
| customerId: string; | ||
| pageId: string; | ||
| sessionId: string; | ||
| scriptVersion: string; | ||
| hostname: string; | ||
| pathname: string; | ||
| pageLabel: string; | ||
| connectionType: string | undefined; | ||
| deliveryType: string | undefined; | ||
| navigationType: number | undefined; | ||
| deviceMemory: number | undefined; | ||
| flags: number; | ||
| customData: Record<string, unknown>; | ||
| }; | ||
|
|
||
| type BufferedError = { | ||
| errorTime: number; | ||
| filename: string; | ||
| lineno: number; | ||
| colno: number; | ||
| message: string; | ||
| }; | ||
|
|
||
| let buffer: BufferedError[] = []; | ||
| let pendingContext: ErrorBeaconContext | null = null; | ||
| let pendingConfig: ConfigObject | null = null; | ||
| let flushTimer: ReturnType<typeof setTimeout> | null = null; | ||
|
|
||
| export function queueErrorBeacon( | ||
| config: ConfigObject, | ||
| error: ErrorEvent, | ||
| errorTime: number, | ||
| context: ErrorBeaconContext, | ||
| ): void { | ||
| // If the page context changed (e.g. SPA navigation), flush the current buffer first | ||
| if (pendingContext && pendingContext.pageId !== context.pageId) { | ||
| flushErrorBeacon(); | ||
| } | ||
|
|
||
| pendingContext = context; | ||
| pendingConfig = config; | ||
| buffer.push({ | ||
| errorTime, | ||
| filename: error.filename, | ||
| lineno: error.lineno, | ||
| colno: error.colno, | ||
| message: error.message, | ||
| }); | ||
|
|
||
| if (flushTimer === null) { | ||
| flushTimer = setTimeout(flushErrorBeacon, config.errorBeaconDelay); | ||
| } | ||
| } | ||
|
|
||
| function flushErrorBeacon(): void { | ||
| if (flushTimer !== null) { | ||
| clearTimeout(flushTimer); | ||
| flushTimer = null; | ||
| } | ||
|
|
||
| if (buffer.length === 0 || !pendingContext || !pendingConfig) { | ||
| return; | ||
| } | ||
|
|
||
| postJson( | ||
| pendingConfig.errorBeaconUrl, | ||
| JSON.stringify(Object.assign({}, pendingContext, { errors: buffer })), | ||
| ); | ||
|
|
||
| buffer = []; | ||
| pendingContext = null; | ||
| pendingConfig = null; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -12,6 +12,7 @@ import { SESSION_COOKIE_NAME } from "./cookie"; | |
| import * as CustomData from "./custom-data"; | ||
| import { onVisible, isVisible, wasPrerendered, wasRedirected } from "./document"; | ||
| import { getNodeSelector } from "./dom"; | ||
| import { queueErrorBeacon } from "./error-beacon"; | ||
| import * as Events from "./events"; | ||
| import Flags, { addFlag } from "./flags"; | ||
| import type { Command, LuxGlobal } from "./global"; | ||
|
|
@@ -86,28 +87,22 @@ LUX = (function () { | |
|
|
||
| if (isLuxError || (nErrors <= globalConfig.maxErrors && _sample())) { | ||
| // Sample & limit other errors. | ||
| // Send the error beacon. | ||
| new Image().src = | ||
| globalConfig.errorBeaconUrl + | ||
| "?v=" + | ||
| versionAsFloat() + | ||
| "&id=" + | ||
| getCustomerId() + | ||
| "&fn=" + | ||
| encodeURIComponent(e.filename) + | ||
| "&ln=" + | ||
| e.lineno + | ||
| "&cn=" + | ||
| e.colno + | ||
| "&msg=" + | ||
| encodeURIComponent(e.message) + | ||
| "&l=" + | ||
| encodeURIComponent(_getPageLabel()) + | ||
| (connectionType() ? "&ct=" + connectionType() : "") + | ||
| "&HN=" + | ||
| encodeURIComponent(document.location.hostname) + | ||
| "&PN=" + | ||
| encodeURIComponent(document.location.pathname); | ||
| queueErrorBeacon(globalConfig, e, msSincePageInit(), { | ||
| customerId: getCustomerId(), | ||
| pageId: gSyncId, | ||
| sessionId: gUid, | ||
| scriptVersion: VERSION, | ||
| hostname: document.location.hostname, | ||
| pathname: document.location.pathname, | ||
| pageLabel: _getPageLabel(), | ||
| connectionType: connectionType(), | ||
| deliveryType: deliveryType(), | ||
| navigationType: navigationType(), | ||
| deviceMemory: | ||
| typeof navigator.deviceMemory === "number" ? round(navigator.deviceMemory) : undefined, | ||
| flags: gFlags, | ||
| customData: CustomData.getAllCustomData(), | ||
| }); | ||
| } | ||
| } | ||
| } | ||
|
|
@@ -1298,24 +1293,20 @@ LUX = (function () { | |
| } | ||
|
|
||
| // Return the connection type based on Network Information API. | ||
| // Note this API is in flux. | ||
| function connectionType() { | ||
| function connectionType(): string | undefined { | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Originally the only change here was an explicit return type, but I felt the need to refactor the function body as well. |
||
| const c = navigator.connection; | ||
| let connType = ""; | ||
|
|
||
| if (c && c.effectiveType) { | ||
| connType = c.effectiveType; | ||
| const connType = c.effectiveType; | ||
|
|
||
| if ("slow-2g" === connType) { | ||
| connType = "Slow 2G"; | ||
| } else if ("2g" === connType || "3g" === connType || "4g" === connType || "5g" === connType) { | ||
| connType = connType.toUpperCase(); | ||
| } else { | ||
| connType = connType.charAt(0).toUpperCase() + connType.slice(1); | ||
| return "Slow 2G"; | ||
| } | ||
|
|
||
| return connType.toUpperCase(); | ||
| } | ||
|
|
||
| return connType; | ||
| return undefined; | ||
| } | ||
|
|
||
| // Return an array of image elements that are in the top viewport. | ||
|
|
@@ -1571,6 +1562,7 @@ LUX = (function () { | |
| const ds = docSize(); | ||
| const ct = connectionType(); | ||
| const dt = deliveryType(); | ||
| const navType = navigationType(); | ||
|
|
||
| // Note some page stat values (the `PS` query string) are non-numeric. To make extracting these | ||
| // values easier, we append an underscore "_" to the value. Values this is used for include | ||
|
|
@@ -1614,7 +1606,7 @@ LUX = (function () { | |
| "er" + | ||
| nErrors + | ||
| "nt" + | ||
| navigationType() + | ||
| (typeof navType !== "undefined" ? navType : "") + | ||
| (navigator.deviceMemory ? "dm" + round(navigator.deviceMemory) : "") + // device memory (GB) | ||
| (sIx ? "&IX=" + sIx : "") + | ||
| (typeof gFirstInputDelay !== "undefined" ? "&FID=" + gFirstInputDelay : "") + | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -16,12 +16,12 @@ export const timing = performance.timing || { | |
| // Older PerformanceTiming implementations allow for arbitrary keys to exist on the timing object | ||
| export type PerfTimingKey = keyof Omit<PerformanceTiming, "toJSON">; | ||
|
|
||
| export function navigationType() { | ||
| export function navigationType(): number | undefined { | ||
| if (performance.navigation && typeof performance.navigation.type !== "undefined") { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. probably a separate PR but just noting this has been deprecated: https://developer.mozilla.org/en-US/docs/Web/API/PerformanceNavigation/type
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @jpmunz yeah there's quite a bit of old code in this SDK. Some of it is intentional to support old browsers, but I am definitely leaning towards just removing it these days. I don't think anyone will mind if IE11 doesn't have a navigation type :) |
||
| return performance.navigation.type; | ||
| } | ||
|
|
||
| return ""; | ||
| return undefined; | ||
| } | ||
|
|
||
| /** | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| const sendBeaconFallback = (url: string, data: string) => { | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Extracted this from beacon.ts since it's now shared between two beacons. |
||
| const xhr = new XMLHttpRequest(); | ||
| xhr.open("POST", url, true); | ||
| xhr.setRequestHeader("content-type", "application/json"); | ||
| xhr.send(data); | ||
|
|
||
| return true; | ||
| }; | ||
|
|
||
| const sendBeaconImpl = | ||
| "sendBeacon" in navigator ? navigator.sendBeacon.bind(navigator) : sendBeaconFallback; | ||
|
|
||
| export function postJson(url: string, data: string): boolean { | ||
| return sendBeaconImpl(url, data); | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note this is similar to
interactionBeaconDelaybelow.