Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
2 changes: 1 addition & 1 deletion package.json
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"
},
Expand Down
15 changes: 2 additions & 13 deletions src/beacon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import type { NavigationTimingData } from "./metric/navigation-timing";
import * as PROPS from "./minification";
import now from "./now";
import { getPageRestoreTime, getZeroTime, msSincePageInit } from "./timing";
import { postJson } from "./transport";
import { VERSION } from "./version";

type BeaconOptions = {
Expand All @@ -23,18 +24,6 @@ type BeaconOptions = {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export type CollectorFunction = (config: UserConfig) => any;

const sendBeaconFallback = (url: string | URL, data?: BodyInit | null) => {
const xhr = new XMLHttpRequest();
xhr.open("POST", url, true);
xhr.setRequestHeader("content-type", "application/json");
xhr.send(String(data));

return true;
};

const sendBeacon =
"sendBeacon" in navigator ? navigator.sendBeacon.bind(navigator) : sendBeaconFallback;

/**
* Some values should only be reported if they are non-zero. The exception to this is when the page
* was prerendered or restored from BF cache
Expand Down Expand Up @@ -213,7 +202,7 @@ export class Beacon {
);

try {
if (sendBeacon(beaconUrl, JSON.stringify(payload))) {
if (postJson(beaconUrl, JSON.stringify(payload))) {
this.isSent = true;
this.logger.logEvent(LogEvent.PostBeaconSent, [beaconUrl, payload]);
Events.emit("beacon", payload);
Expand Down
4 changes: 3 additions & 1 deletion src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import type { UrlPatternMapping } from "./url-matcher";
export interface ConfigObject {
allowEmptyPostBeacon: boolean;
auto: boolean;
errorBeaconDelay: number;
beaconUrl: string;
beaconUrlFallback?: string;
beaconUrlV2: string;
Expand Down Expand Up @@ -48,13 +49,14 @@ export function fromObject(obj: UserConfig): ConfigObject {
return {
allowEmptyPostBeacon: getProperty(obj, "allowEmptyPostBeacon", false),
auto: autoMode,
errorBeaconDelay: getProperty(obj, "errorBeaconDelay", 200),

Copy link
Copy Markdown
Contributor Author

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 interactionBeaconDelay below.

beaconUrl: getProperty(obj, "beaconUrl", luxOrigin + "/lux/"),
beaconUrlFallback: getProperty(obj, "beaconUrlFallback"),
beaconUrlV2: getProperty(obj, "beaconUrlV2", "https://beacon.speedcurve.com/store"),
conversions: getProperty(obj, "conversions"),
cookieDomain: getProperty(obj, "cookieDomain"),
customerid: getProperty(obj, "customerid"),
errorBeaconUrl: getProperty(obj, "errorBeaconUrl", luxOrigin + "/error/"),
errorBeaconUrl: getProperty(obj, "errorBeaconUrl", "https://beacon.speedcurve.com/store/error"),
interactionBeaconDelay: getProperty(obj, "interactionBeaconDelay", 200),
jspagelabel: getProperty(obj, "jspagelabel"),
label: getProperty(obj, "label"),
Expand Down
77 changes: 77 additions & 0 deletions src/error-beacon.ts
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;
}
58 changes: 25 additions & 33 deletions src/lux.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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(),
});
}
}
}
Expand Down Expand Up @@ -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 {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The 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. navigator.connection is only ever slow-2g, 2g, 3g, or 4g. The SDK turns these into "friendly" values by upper-casing them, with the exception being "Slow 2G". The previous logic was overly complicated for such a simple transform.

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.
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 : "") +
Expand Down
4 changes: 2 additions & 2 deletions src/performance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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") {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The 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

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The 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;
}

/**
Expand Down
15 changes: 15 additions & 0 deletions src/transport.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
const sendBeaconFallback = (url: string, data: string) => {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The 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);
}
Loading
Loading