@dash0/sdk-web
Version:
Dash0's Web SDK to collect telemetry from end-users' web browsers
131 lines (130 loc) • 5.17 kB
JavaScript
import { vars } from "../vars";
import { DEPLOYMENT_ENVIRONMENT_NAME, DEPLOYMENT_ID, DEPLOYMENT_NAME, PAGE_LOAD_ID, SERVICE_NAME, SERVICE_VERSION, USER_AGENT, } from "../semantic-conventions";
import { fetch, generateUniqueId, PAGE_LOAD_ID_BYTES, warn, debug, perf, nav, win, NO_VALUE_FALLBACK, pick, } from "../utils";
import { trackSessions } from "./session";
import { startWebVitalsInstrumentation } from "../instrumentations/web-vitals";
import { startErrorInstrumentation } from "../instrumentations/errors";
import { addAttribute } from "../utils/otel";
import { instrumentFetch } from "../instrumentations/http/fetch";
import { startNavigationInstrumentation } from "../instrumentations/navigation";
import { merge } from "ts-deepmerge";
import { initializeTabId } from "../utils/tab-id";
let hasBeenInitialised = false;
export function init(opts) {
if (hasBeenInitialised) {
debug("Dash0 SDK is being reinitialized, skipping ...");
return;
}
if (!isClient()) {
debug("Looks like we are not running in a browser context. Stopping Dash0 Web SDK initialization.");
return;
}
if (!isSupported()) {
debug("Stopping Dash0 Web SDK initialization. This browser does not support the necessary APIs");
return;
}
vars.endpoints = opts.endpoint instanceof Array ? opts.endpoint : [opts.endpoint];
if (vars.endpoints.length === 0) {
warn("No telemetry endpoint configured. Aborting Dash0 Web SDK initialization process.");
return;
}
Object.assign(vars, merge(vars, pick(opts, [
"ignoreUrls",
"ignoreErrorMessages",
"wrapEventHandlers",
"wrapTimers",
"propagateTraceHeadersCorsURLs",
"maxWaitForResourceTimingsMillis",
"maxToleranceForResourceTimingsMillis",
"headersToCapture",
"pageViewInstrumentation",
])));
initializeResourceAttributes(opts);
initializeSignalAttributes(opts);
initializeTabId();
trackSessions(opts.sessionInactivityTimeoutMillis, opts.sessionTerminationTimeoutMillis);
startNavigationInstrumentation();
startWebVitalsInstrumentation();
startErrorInstrumentation();
instrumentFetch();
hasBeenInitialised = true;
}
function initializeResourceAttributes(opts) {
addAttribute(vars.resource.attributes, SERVICE_NAME, opts["serviceName"]);
if (opts.serviceVersion) {
addAttribute(vars.resource.attributes, SERVICE_VERSION, opts["serviceVersion"]);
}
const env = detectEnvironment(opts);
if (env) {
addAttribute(vars.resource.attributes, DEPLOYMENT_ENVIRONMENT_NAME, env);
}
const deploymentName = detectDeploymentName(opts);
if (deploymentName) {
addAttribute(vars.resource.attributes, DEPLOYMENT_NAME, deploymentName);
}
const deploymentId = detectDeploymentId(opts);
if (deploymentId) {
addAttribute(vars.resource.attributes, DEPLOYMENT_ID, deploymentId);
}
}
function initializeSignalAttributes(opts) {
addAttribute(vars.signalAttributes, PAGE_LOAD_ID, generateUniqueId(PAGE_LOAD_ID_BYTES));
addAttribute(vars.signalAttributes, USER_AGENT, nav?.userAgent ?? NO_VALUE_FALLBACK);
if (opts.additionalSignalAttributes) {
Object.entries(opts.additionalSignalAttributes).forEach(([key, value]) => {
addAttribute(vars.signalAttributes, key, value);
});
}
}
function isSupported() {
return typeof fetch === "function" && perf && perf.getEntriesByType;
}
function isClient() {
return win != null;
}
function detectEnvironment(opts) {
// if there is a manually specified value we use that
if (opts.environment) {
return opts.environment;
}
// if process isn't defined access to it causes an exception, but we can't check for its present due to how
// plugins like webpack define work.
try {
// vercel
// @ts-expect-error -- we need to access like this to allow webpack in the nextjs build to replace this
return process?.env?.NEXT_PUBLIC_VERCEL_ENV;
}
catch (_ignored) {
return undefined;
}
}
function detectDeploymentName(opts) {
if (opts.deploymentName) {
return opts.deploymentName;
}
// if process isn't defined access to it causes an exception, but we can't check for its present due to how
// plugins like webpack define work.
try {
// vercel
// @ts-expect-error -- we need to access like this to allow webpack in the nextjs build to replace this
return process?.env?.NEXT_PUBLIC_VERCEL_TARGET_ENV;
}
catch (_ignored) {
return undefined;
}
}
function detectDeploymentId(opts) {
if (opts.deploymentId) {
return opts.deploymentId;
}
// if process isn't defined access to it causes an exception, but we can't check for its present due to how
// plugins like webpack define work.
try {
// vercel
// @ts-expect-error -- we need to access like this to allow webpack in the nextjs build to replace this
return process?.env?.NEXT_PUBLIC_VERCEL_BRANCH_URL;
}
catch (_ignored) {
return undefined;
}
}