Open
Show file tree
Hide file tree
Changes from all commits
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
31 changes: 30 additions & 1 deletion e2e/core/node-integration.test.ts
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
import { TrackJS, timestamp } from "@trackjs/core";
import { TrackJS, timestamp, userAgent } from "@trackjs/core";
import { test, expect, beforeEach } from "vitest";
import { MockTransport } from "./mocks/transport";
import type { NetworkTelemetry, Transport, TransportRequest, TransportResponse } from "@trackjs/core";
Expand DownExpand Up@@ -49,6 +49,35 @@ test('TrackJS.track() can track errors after install', async () => {
});
});

test('TrackJS.track() with environment', async () => {
const transport = new MockTransport();

TrackJS.initialize({
token: 'test token',
transport,
dependencies: {
"foo": "1.2.3"
},
originalUrl: "original-url",
referrerUrl: "referrer-url",
userAgent: userAgent("Node", "12.1", "windows", "x64", "11.2"),
});

await TrackJS.track(new Error('Oops'));

expect(transport.sentRequests).toHaveLength(1);
expect(JSON.parse(transport.sentRequests[0]?.data as string)).toMatchObject({
environment: expect.objectContaining({
originalUrl: "original-url",
referrer: "referrer-url",
dependencies: {
"foo": "1.2.3"
},
userAgent: "Node/12.1 (windows x64 11.2)"
})
});
})

test('TrackJS.track() with custom metadata', async () => {
const transport = new MockTransport();

Expand Down
2 changes: 1 addition & 1 deletion packages/core/package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -43,6 +43,6 @@
"clean": "rimraf dist",
"prepublishOnly": "npm run build",
"test": "vitest run",
"test:watch": "vitest"
"dev": "vitest"
}
}
19 changes: 8 additions & 11 deletions packages/core/src/client.ts
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
import { Metadata } from "./metadata";
import { TelemetryLog } from "./telemetryLog";
import { timestamp, serialize, isError } from "./utils";

import type {
CapturePayload,
Options,
Expand DownExpand Up@@ -75,14 +74,12 @@ export class Client {

environment: {
age: 0,
dependencies: {
"foo": "bar"
},
originalUrl: "",
referrer: "",
userAgent: 'node/22.0 (osx x64 123)',
viewportHeight: -1,
viewportWidth: -1
dependencies: structuredClone(this.options.dependencies),
originalUrl: this.options.originalUrl,
referrer: this.options.referrerUrl,
userAgent: this.options.userAgent,
viewportHeight: this.options.viewportHeight,
viewportWidth: this.options.viewportWidth
},

metadata: payloadMetadata.get(),
Expand All@@ -92,8 +89,8 @@ export class Client {
network: this.telemetry.get("net"),
visitor: this.telemetry.get("vis"),

agentPlatform: "",
version: '0.0.0',
agentPlatform: this.options.agent,
version: this.options.agentVersion,
throttled: 0,
};
}
Expand Down
6 changes: 6 additions & 0 deletions packages/core/src/constants.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
export const MAX_METADATA_LENGTH = 500;
export const MAX_TELEMETRY_LOG_SIZE = 30;
export const MAX_TELEMETRY_MESSAGE_LENGTH = 10_000;
export const MAX_TELEMETRY_URI_LENGTH = 1_000;
export const MAX_TELEMETRY_ATTRIBUTES = 20;
export const MAX_TELEMETRY_ATTRIBUTE_VALUE = 500;
2 changes: 1 addition & 1 deletion packages/core/src/index.ts
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
export * as TrackJS from './trackjs';
export { timestamp, uuid } from './utils/';
export { timestamp, userAgent, uuid } from './utils/';

export type * from './types/';
7 changes: 2 additions & 5 deletions packages/core/src/metadata.ts
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
import { MAX_METADATA_LENGTH } from "./constants";
import { truncate } from "./utils";

const MAX_METADATA_LENGTH = 500;

export class Metadata {

private store: Map<string, string> = new Map();
Expand DownExpand Up@@ -30,9 +29,7 @@ export class Metadata {

public clone(): Metadata {
const cloned = new Metadata();
for (const [key, value] of this.store.entries()) {
cloned.store.set(key, value);
}
cloned.store = structuredClone(this.store);
return cloned;
}

Expand Down
40 changes: 25 additions & 15 deletions packages/core/src/telemetryLog.ts
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,21 @@
import type { ConsoleTelemetry, NavigationTelemetry, NetworkTelemetry, Telemetry, TelemetryType, VisitorTelemetry } from "./types";
import { truncate } from "./utils";
import {
MAX_TELEMETRY_ATTRIBUTE_VALUE,
MAX_TELEMETRY_ATTRIBUTES,
MAX_TELEMETRY_LOG_SIZE,
MAX_TELEMETRY_MESSAGE_LENGTH,
MAX_TELEMETRY_URI_LENGTH
} from "./constants";

import type {
ConsoleTelemetry,
NavigationTelemetry,
NetworkTelemetry,
Telemetry,
TelemetryType,
VisitorTelemetry
} from "./types";

const MAX_LOG_SIZE = 30;
const MAX_MESSAGE_LENGTH = 10_000;
const MAX_URI_LENGTH = 1_000;
const MAX_ATTRIBUTES = 20;
const MAX_ATTRIBUTE_VALUE = 500;

export class TelemetryLog {

Expand DownExpand Up@@ -36,8 +46,8 @@ export class TelemetryLog {
}
this.store.push({ type, telemetry});

if (this.store.length > MAX_LOG_SIZE) {
this.store = this.store.slice(this.store.length - MAX_LOG_SIZE);
if (this.store.length > MAX_TELEMETRY_LOG_SIZE) {
this.store = this.store.slice(this.store.length - MAX_TELEMETRY_LOG_SIZE);
}
}

Expand All@@ -47,7 +57,7 @@ export class TelemetryLog {

public clone(): TelemetryLog {
const cloned = new TelemetryLog();
cloned.store = this.store.slice(0);
cloned.store = structuredClone(this.store);
return cloned;
}

Expand All@@ -64,18 +74,18 @@ export class TelemetryLog {
}

export function _normalizeConsoleTelemetry(telemetry: any) : ConsoleTelemetry {
telemetry.message = truncate(telemetry.message, MAX_MESSAGE_LENGTH);
telemetry.message = truncate(telemetry.message, MAX_TELEMETRY_MESSAGE_LENGTH);
return telemetry;
}

export function _normalizeNavigationTelemetry(telemetry: any) : NavigationTelemetry {
telemetry.from = truncate(telemetry.from, MAX_URI_LENGTH);
telemetry.to = truncate(telemetry.to, MAX_URI_LENGTH);
telemetry.from = truncate(telemetry.from, MAX_TELEMETRY_URI_LENGTH);
telemetry.to = truncate(telemetry.to, MAX_TELEMETRY_URI_LENGTH);
return telemetry;
}

export function _normalizeNetworkTelemetry(telemetry: any) : NetworkTelemetry {
telemetry.url = truncate(telemetry.url, MAX_URI_LENGTH);
telemetry.url = truncate(telemetry.url, MAX_TELEMETRY_URI_LENGTH);
return telemetry;
}

Expand All@@ -84,10 +94,10 @@ export function _normalizeVisitorTelemetry(telemetry: any) : VisitorTelemetry {
const currentAttributes: attributes = telemetry.element?.attributes || {};
let normalizedAttributes: attributes = {};

const limitedAttributes = Object.entries(currentAttributes).slice(0, MAX_ATTRIBUTES);
const limitedAttributes = Object.entries(currentAttributes).slice(0, MAX_TELEMETRY_ATTRIBUTES);

for (const [key, value] of limitedAttributes) {
normalizedAttributes[key] = truncate(value, MAX_ATTRIBUTE_VALUE);
normalizedAttributes[key] = truncate(value, MAX_TELEMETRY_ATTRIBUTE_VALUE);
}

(telemetry.element || {}).attributes = normalizedAttributes;
Expand Down
26 changes: 15 additions & 11 deletions packages/core/src/trackjs.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -18,17 +18,25 @@ let config: Options | null = null;
let client: Client | null = null;

const defaultOptions: Options = {
application: '',
agent: "core",
agentVersion: "{{AGENT_VERSION}}",
application: "",
dependencies: {},
correlationId: uuid(),
errorURL: 'https://capture.trackjs.com/capture/node',
errorURL: "https://capture.trackjs.com/capture/node",
metadata: {},
onError: () => true,
originalUrl: "",
referrerUrl: "",
serializer: [],
sessionId: '',
token: '',
sessionId: "",
token: "",
transport: new FetchTransport(),
userId: '',
version: ''
userAgent: "",
userId: "",
version: "",
viewportHeight: -1,
viewportWidth: -1
};

/**
Expand DownExpand Up@@ -74,7 +82,7 @@ export function initialize(options: Partial<Options> & { token: string }): void
}

/**
* Adds a object set of key-value pairs to metadata for any future errors.
* Adds a object map of key-value pairs to metadata for any future errors.
* Keys and values will be truncated to 500 characters.
*
* @param metadata - object with string values to be added as metadata.
Expand DownExpand Up@@ -163,10 +171,6 @@ export function addTelemetry(type: TelemetryType, telemetry: Telemetry): void {
return client!.addTelemetry(type, telemetry);
}

export function addDependencies(...args: [dependencies: Record<string, string>]): void {
throw new Error("not implemented");
}

/**
* Track and error or error-like object to the TrackJS error monitoring service.
*
Expand Down
71 changes: 68 additions & 3 deletions packages/core/src/types/options.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -39,6 +39,20 @@ export interface TrackOptions {

export interface Options {

/**
* The name of the TrackJS agent package
*
* @default "core"
*/
agent: string,

/**
* The version of the TrackJS agent package
*
* @default "{{AGENT_VERSION}}"
*/
agentVersion: string,

/**
* TrackJS Application key.
*
Expand All@@ -53,6 +67,14 @@ export interface Options {
*/
correlationId: string;

/**
* Dependency package names and version for the current environment.
*
* @default {}
* @example { "node": "22.12.0" }
*/
dependencies: { [name: string]: string };

/**
* URL destination override for capturing errors.
*
Expand All@@ -69,15 +91,32 @@ export interface Options {

/**
* Custom handler to manipulate or suppress errors captured by the agent.
*
* @param payload error payload to be sent to TrackJS.
* @returns false will suppress the error from being sent.
*
* @default (payload) => true
*/
onError: (payload: CapturePayload) => boolean;

/**
* Custom functions for serializing objects to strings. Will execute before the default serializer.
* When environment has a user interface, the URI location where the application
* was started, or the URL of the page when it was first loaded.
*
* @default ""
*/
originalUrl: string

/**
* When environment has a user interface, the URI location where the user came
* from before landing on this page or screen.
*
* @default ""
*/
referrerUrl: string

/**
* Custom functions for serializing objects to strings. Will execute before the
* default serializer.
*
* @default []
*/
Expand All@@ -96,12 +135,23 @@ export interface Options {
token: string;

/**
* Custom transport function for sending data. Required if the current environment does not support `fetch`.
* Custom transport function for sending data. Required if the current
* environment does not support `fetch`.
*
* @default FetchTransport
*/
transport: Transport;

/**
* User-Agent string describing the user's running environment, browser, process,
* operating system, arch, and version. For non-browser environments, this can
* be constructed with the userAgent() util function.
*
* @see {@link userAgent}
* @defaults ""
*/
userAgent: string

/**
* Customer-generated Id representing the current user.
*
Expand All@@ -116,4 +166,19 @@ export interface Options {
*/
version: string;

/**
* When environment has a user interface, the height of the user viewport in
* pixels.
*
* @default -1
*/
viewportHeight: number

/**
* When environment has a user interface, the width of the user viewport in
* pixels.
*
* @default -1
*/
viewportWidth: number
}
1 change: 1 addition & 0 deletions packages/core/src/utils/index.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -2,4 +2,5 @@ export * from './isType';
export * from './serialize';
export * from './timestamp';
export * from './truncate';
export * from './userAgent';
export * from './uuid';
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
31 changes: 30 additions & 1 deletion e2e/core/node-integration.test.ts
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
import { TrackJS, timestamp } from "@trackjs/core";
import { TrackJS, timestamp, userAgent } from "@trackjs/core";
import { test, expect, beforeEach } from "vitest";
import { MockTransport } from "./mocks/transport";
import type { NetworkTelemetry, Transport, TransportRequest, TransportResponse } from "@trackjs/core";
Expand DownExpand Up@@ -49,6 +49,35 @@ test('TrackJS.track() can track errors after install', async () => {
});
});

test('TrackJS.track() with environment', async () => {
const transport = new MockTransport();

TrackJS.initialize({
token: 'test token',
transport,
dependencies: {
"foo": "1.2.3"
},
originalUrl: "original-url",
referrerUrl: "referrer-url",
userAgent: userAgent("Node", "12.1", "windows", "x64", "11.2"),
});

await TrackJS.track(new Error('Oops'));

expect(transport.sentRequests).toHaveLength(1);
expect(JSON.parse(transport.sentRequests[0]?.data as string)).toMatchObject({
environment: expect.objectContaining({
originalUrl: "original-url",
referrer: "referrer-url",
dependencies: {
"foo": "1.2.3"
},
userAgent: "Node/12.1 (windows x64 11.2)"
})
});
})

test('TrackJS.track() with custom metadata', async () => {
const transport = new MockTransport();

Expand Down
2 changes: 1 addition & 1 deletion packages/core/package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -43,6 +43,6 @@
"clean": "rimraf dist",
"prepublishOnly": "npm run build",
"test": "vitest run",
"test:watch": "vitest"
"dev": "vitest"
}
}
19 changes: 8 additions & 11 deletions packages/core/src/client.ts
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
import { Metadata } from "./metadata";
import { TelemetryLog } from "./telemetryLog";
import { timestamp, serialize, isError } from "./utils";

import type {
CapturePayload,
Options,
Expand DownExpand Up@@ -75,14 +74,12 @@ export class Client {

environment: {
age: 0,
dependencies: {
"foo": "bar"
},
originalUrl: "",
referrer: "",
userAgent: 'node/22.0 (osx x64 123)',
viewportHeight: -1,
viewportWidth: -1
dependencies: structuredClone(this.options.dependencies),
originalUrl: this.options.originalUrl,
referrer: this.options.referrerUrl,
userAgent: this.options.userAgent,
viewportHeight: this.options.viewportHeight,
viewportWidth: this.options.viewportWidth
},

metadata: payloadMetadata.get(),
Expand All@@ -92,8 +89,8 @@ export class Client {
network: this.telemetry.get("net"),
visitor: this.telemetry.get("vis"),

agentPlatform: "",
version: '0.0.0',
agentPlatform: this.options.agent,
version: this.options.agentVersion,
throttled: 0,
};
}
Expand Down
6 changes: 6 additions & 0 deletions packages/core/src/constants.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
export const MAX_METADATA_LENGTH = 500;
export const MAX_TELEMETRY_LOG_SIZE = 30;
export const MAX_TELEMETRY_MESSAGE_LENGTH = 10_000;
export const MAX_TELEMETRY_URI_LENGTH = 1_000;
export const MAX_TELEMETRY_ATTRIBUTES = 20;
export const MAX_TELEMETRY_ATTRIBUTE_VALUE = 500;
2 changes: 1 addition & 1 deletion packages/core/src/index.ts
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
export * as TrackJS from './trackjs';
export { timestamp, uuid } from './utils/';
export { timestamp, userAgent, uuid } from './utils/';

export type * from './types/';
7 changes: 2 additions & 5 deletions packages/core/src/metadata.ts
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
import { MAX_METADATA_LENGTH } from "./constants";
import { truncate } from "./utils";

const MAX_METADATA_LENGTH = 500;

export class Metadata {

private store: Map<string, string> = new Map();
Expand DownExpand Up@@ -30,9 +29,7 @@ export class Metadata {

public clone(): Metadata {
const cloned = new Metadata();
for (const [key, value] of this.store.entries()) {
cloned.store.set(key, value);
}
cloned.store = structuredClone(this.store);
return cloned;
}

Expand Down
40 changes: 25 additions & 15 deletions packages/core/src/telemetryLog.ts
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,21 @@
import type { ConsoleTelemetry, NavigationTelemetry, NetworkTelemetry, Telemetry, TelemetryType, VisitorTelemetry } from "./types";
import { truncate } from "./utils";
import {
MAX_TELEMETRY_ATTRIBUTE_VALUE,
MAX_TELEMETRY_ATTRIBUTES,
MAX_TELEMETRY_LOG_SIZE,
MAX_TELEMETRY_MESSAGE_LENGTH,
MAX_TELEMETRY_URI_LENGTH
} from "./constants";

import type {
ConsoleTelemetry,
NavigationTelemetry,
NetworkTelemetry,
Telemetry,
TelemetryType,
VisitorTelemetry
} from "./types";

const MAX_LOG_SIZE = 30;
const MAX_MESSAGE_LENGTH = 10_000;
const MAX_URI_LENGTH = 1_000;
const MAX_ATTRIBUTES = 20;
const MAX_ATTRIBUTE_VALUE = 500;

export class TelemetryLog {

Expand DownExpand Up@@ -36,8 +46,8 @@ export class TelemetryLog {
}
this.store.push({ type, telemetry});

if (this.store.length > MAX_LOG_SIZE) {
this.store = this.store.slice(this.store.length - MAX_LOG_SIZE);
if (this.store.length > MAX_TELEMETRY_LOG_SIZE) {
this.store = this.store.slice(this.store.length - MAX_TELEMETRY_LOG_SIZE);
}
}

Expand All@@ -47,7 +57,7 @@ export class TelemetryLog {

public clone(): TelemetryLog {
const cloned = new TelemetryLog();
cloned.store = this.store.slice(0);
cloned.store = structuredClone(this.store);
return cloned;
}

Expand All@@ -64,18 +74,18 @@ export class TelemetryLog {
}

export function _normalizeConsoleTelemetry(telemetry: any) : ConsoleTelemetry {
telemetry.message = truncate(telemetry.message, MAX_MESSAGE_LENGTH);
telemetry.message = truncate(telemetry.message, MAX_TELEMETRY_MESSAGE_LENGTH);
return telemetry;
}

export function _normalizeNavigationTelemetry(telemetry: any) : NavigationTelemetry {
telemetry.from = truncate(telemetry.from, MAX_URI_LENGTH);
telemetry.to = truncate(telemetry.to, MAX_URI_LENGTH);
telemetry.from = truncate(telemetry.from, MAX_TELEMETRY_URI_LENGTH);
telemetry.to = truncate(telemetry.to, MAX_TELEMETRY_URI_LENGTH);
return telemetry;
}

export function _normalizeNetworkTelemetry(telemetry: any) : NetworkTelemetry {
telemetry.url = truncate(telemetry.url, MAX_URI_LENGTH);
telemetry.url = truncate(telemetry.url, MAX_TELEMETRY_URI_LENGTH);
return telemetry;
}

Expand All@@ -84,10 +94,10 @@ export function _normalizeVisitorTelemetry(telemetry: any) : VisitorTelemetry {
const currentAttributes: attributes = telemetry.element?.attributes || {};
let normalizedAttributes: attributes = {};

const limitedAttributes = Object.entries(currentAttributes).slice(0, MAX_ATTRIBUTES);
const limitedAttributes = Object.entries(currentAttributes).slice(0, MAX_TELEMETRY_ATTRIBUTES);

for (const [key, value] of limitedAttributes) {
normalizedAttributes[key] = truncate(value, MAX_ATTRIBUTE_VALUE);
normalizedAttributes[key] = truncate(value, MAX_TELEMETRY_ATTRIBUTE_VALUE);
}

(telemetry.element || {}).attributes = normalizedAttributes;
Expand Down
26 changes: 15 additions & 11 deletions packages/core/src/trackjs.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -18,17 +18,25 @@ let config: Options | null = null;
let client: Client | null = null;

const defaultOptions: Options = {
application: '',
agent: "core",
agentVersion: "{{AGENT_VERSION}}",
application: "",
dependencies: {},
correlationId: uuid(),
errorURL: 'https://capture.trackjs.com/capture/node',
errorURL: "https://capture.trackjs.com/capture/node",
metadata: {},
onError: () => true,
originalUrl: "",
referrerUrl: "",
serializer: [],
sessionId: '',
token: '',
sessionId: "",
token: "",
transport: new FetchTransport(),
userId: '',
version: ''
userAgent: "",
userId: "",
version: "",
viewportHeight: -1,
viewportWidth: -1
};

/**
Expand DownExpand Up@@ -74,7 +82,7 @@ export function initialize(options: Partial<Options> & { token: string }): void
}

/**
* Adds a object set of key-value pairs to metadata for any future errors.
* Adds a object map of key-value pairs to metadata for any future errors.
* Keys and values will be truncated to 500 characters.
*
* @param metadata - object with string values to be added as metadata.
Expand DownExpand Up@@ -163,10 +171,6 @@ export function addTelemetry(type: TelemetryType, telemetry: Telemetry): void {
return client!.addTelemetry(type, telemetry);
}

export function addDependencies(...args: [dependencies: Record<string, string>]): void {
throw new Error("not implemented");
}

/**
* Track and error or error-like object to the TrackJS error monitoring service.
*
Expand Down
71 changes: 68 additions & 3 deletions packages/core/src/types/options.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -39,6 +39,20 @@ export interface TrackOptions {

export interface Options {

/**
* The name of the TrackJS agent package
*
* @default "core"
*/
agent: string,

/**
* The version of the TrackJS agent package
*
* @default "{{AGENT_VERSION}}"
*/
agentVersion: string,

/**
* TrackJS Application key.
*
Expand All@@ -53,6 +67,14 @@ export interface Options {
*/
correlationId: string;

/**
* Dependency package names and version for the current environment.
*
* @default {}
* @example { "node": "22.12.0" }
*/
dependencies: { [name: string]: string };

/**
* URL destination override for capturing errors.
*
Expand All@@ -69,15 +91,32 @@ export interface Options {

/**
* Custom handler to manipulate or suppress errors captured by the agent.
*
* @param payload error payload to be sent to TrackJS.
* @returns false will suppress the error from being sent.
*
* @default (payload) => true
*/
onError: (payload: CapturePayload) => boolean;

/**
* Custom functions for serializing objects to strings. Will execute before the default serializer.
* When environment has a user interface, the URI location where the application
* was started, or the URL of the page when it was first loaded.
*
* @default ""
*/
originalUrl: string

/**
* When environment has a user interface, the URI location where the user came
* from before landing on this page or screen.
*
* @default ""
*/
referrerUrl: string

/**
* Custom functions for serializing objects to strings. Will execute before the
* default serializer.
*
* @default []
*/
Expand All@@ -96,12 +135,23 @@ export interface Options {
token: string;

/**
* Custom transport function for sending data. Required if the current environment does not support `fetch`.
* Custom transport function for sending data. Required if the current
* environment does not support `fetch`.
*
* @default FetchTransport
*/
transport: Transport;

/**
* User-Agent string describing the user's running environment, browser, process,
* operating system, arch, and version. For non-browser environments, this can
* be constructed with the userAgent() util function.
*
* @see {@link userAgent}
* @defaults ""
*/
userAgent: string

/**
* Customer-generated Id representing the current user.
*
Expand All@@ -116,4 +166,19 @@ export interface Options {
*/
version: string;

/**
* When environment has a user interface, the height of the user viewport in
* pixels.
*
* @default -1
*/
viewportHeight: number

/**
* When environment has a user interface, the width of the user viewport in
* pixels.
*
* @default -1
*/
viewportWidth: number
}
1 change: 1 addition & 0 deletions packages/core/src/utils/index.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -2,4 +2,5 @@ export * from './isType';
export * from './serialize';
export * from './timestamp';
export * from './truncate';
export * from './userAgent';
export * from './uuid';
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
31 changes: 30 additions & 1 deletion e2e/core/node-integration.test.ts
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
import { TrackJS, timestamp } from "@trackjs/core";
import { TrackJS, timestamp, userAgent } from "@trackjs/core";
import { test, expect, beforeEach } from "vitest";
import { MockTransport } from "./mocks/transport";
import type { NetworkTelemetry, Transport, TransportRequest, TransportResponse } from "@trackjs/core";
Expand DownExpand Up@@ -49,6 +49,35 @@ test('TrackJS.track() can track errors after install', async () => {
});
});

test('TrackJS.track() with environment', async () => {
const transport = new MockTransport();

TrackJS.initialize({
token: 'test token',
transport,
dependencies: {
"foo": "1.2.3"
},
originalUrl: "original-url",
referrerUrl: "referrer-url",
userAgent: userAgent("Node", "12.1", "windows", "x64", "11.2"),
});

await TrackJS.track(new Error('Oops'));

expect(transport.sentRequests).toHaveLength(1);
expect(JSON.parse(transport.sentRequests[0]?.data as string)).toMatchObject({
environment: expect.objectContaining({
originalUrl: "original-url",
referrer: "referrer-url",
dependencies: {
"foo": "1.2.3"
},
userAgent: "Node/12.1 (windows x64 11.2)"
})
});
})

test('TrackJS.track() with custom metadata', async () => {
const transport = new MockTransport();

Expand Down
2 changes: 1 addition & 1 deletion packages/core/package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -43,6 +43,6 @@
"clean": "rimraf dist",
"prepublishOnly": "npm run build",
"test": "vitest run",
"test:watch": "vitest"
"dev": "vitest"
}
}
19 changes: 8 additions & 11 deletions packages/core/src/client.ts
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
import { Metadata } from "./metadata";
import { TelemetryLog } from "./telemetryLog";
import { timestamp, serialize, isError } from "./utils";

import type {
CapturePayload,
Options,
Expand DownExpand Up@@ -75,14 +74,12 @@ export class Client {

environment: {
age: 0,
dependencies: {
"foo": "bar"
},
originalUrl: "",
referrer: "",
userAgent: 'node/22.0 (osx x64 123)',
viewportHeight: -1,
viewportWidth: -1
dependencies: structuredClone(this.options.dependencies),
originalUrl: this.options.originalUrl,
referrer: this.options.referrerUrl,
userAgent: this.options.userAgent,
viewportHeight: this.options.viewportHeight,
viewportWidth: this.options.viewportWidth
},

metadata: payloadMetadata.get(),
Expand All@@ -92,8 +89,8 @@ export class Client {
network: this.telemetry.get("net"),
visitor: this.telemetry.get("vis"),

agentPlatform: "",
version: '0.0.0',
agentPlatform: this.options.agent,
version: this.options.agentVersion,
throttled: 0,
};
}
Expand Down
6 changes: 6 additions & 0 deletions packages/core/src/constants.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
export const MAX_METADATA_LENGTH = 500;
export const MAX_TELEMETRY_LOG_SIZE = 30;
export const MAX_TELEMETRY_MESSAGE_LENGTH = 10_000;
export const MAX_TELEMETRY_URI_LENGTH = 1_000;
export const MAX_TELEMETRY_ATTRIBUTES = 20;
export const MAX_TELEMETRY_ATTRIBUTE_VALUE = 500;
2 changes: 1 addition & 1 deletion packages/core/src/index.ts
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
export * as TrackJS from './trackjs';
export { timestamp, uuid } from './utils/';
export { timestamp, userAgent, uuid } from './utils/';

export type * from './types/';
7 changes: 2 additions & 5 deletions packages/core/src/metadata.ts
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
import { MAX_METADATA_LENGTH } from "./constants";
import { truncate } from "./utils";

const MAX_METADATA_LENGTH = 500;

export class Metadata {

private store: Map<string, string> = new Map();
Expand DownExpand Up@@ -30,9 +29,7 @@ export class Metadata {

public clone(): Metadata {
const cloned = new Metadata();
for (const [key, value] of this.store.entries()) {
cloned.store.set(key, value);
}
cloned.store = structuredClone(this.store);
return cloned;
}

Expand Down
40 changes: 25 additions & 15 deletions packages/core/src/telemetryLog.ts
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,21 @@
import type { ConsoleTelemetry, NavigationTelemetry, NetworkTelemetry, Telemetry, TelemetryType, VisitorTelemetry } from "./types";
import { truncate } from "./utils";
import {
MAX_TELEMETRY_ATTRIBUTE_VALUE,
MAX_TELEMETRY_ATTRIBUTES,
MAX_TELEMETRY_LOG_SIZE,
MAX_TELEMETRY_MESSAGE_LENGTH,
MAX_TELEMETRY_URI_LENGTH
} from "./constants";

import type {
ConsoleTelemetry,
NavigationTelemetry,
NetworkTelemetry,
Telemetry,
TelemetryType,
VisitorTelemetry
} from "./types";

const MAX_LOG_SIZE = 30;
const MAX_MESSAGE_LENGTH = 10_000;
const MAX_URI_LENGTH = 1_000;
const MAX_ATTRIBUTES = 20;
const MAX_ATTRIBUTE_VALUE = 500;

export class TelemetryLog {

Expand DownExpand Up@@ -36,8 +46,8 @@ export class TelemetryLog {
}
this.store.push({ type, telemetry});

if (this.store.length > MAX_LOG_SIZE) {
this.store = this.store.slice(this.store.length - MAX_LOG_SIZE);
if (this.store.length > MAX_TELEMETRY_LOG_SIZE) {
this.store = this.store.slice(this.store.length - MAX_TELEMETRY_LOG_SIZE);
}
}

Expand All@@ -47,7 +57,7 @@ export class TelemetryLog {

public clone(): TelemetryLog {
const cloned = new TelemetryLog();
cloned.store = this.store.slice(0);
cloned.store = structuredClone(this.store);
return cloned;
}

Expand All@@ -64,18 +74,18 @@ export class TelemetryLog {
}

export function _normalizeConsoleTelemetry(telemetry: any) : ConsoleTelemetry {
telemetry.message = truncate(telemetry.message, MAX_MESSAGE_LENGTH);
telemetry.message = truncate(telemetry.message, MAX_TELEMETRY_MESSAGE_LENGTH);
return telemetry;
}

export function _normalizeNavigationTelemetry(telemetry: any) : NavigationTelemetry {
telemetry.from = truncate(telemetry.from, MAX_URI_LENGTH);
telemetry.to = truncate(telemetry.to, MAX_URI_LENGTH);
telemetry.from = truncate(telemetry.from, MAX_TELEMETRY_URI_LENGTH);
telemetry.to = truncate(telemetry.to, MAX_TELEMETRY_URI_LENGTH);
return telemetry;
}

export function _normalizeNetworkTelemetry(telemetry: any) : NetworkTelemetry {
telemetry.url = truncate(telemetry.url, MAX_URI_LENGTH);
telemetry.url = truncate(telemetry.url, MAX_TELEMETRY_URI_LENGTH);
return telemetry;
}

Expand All@@ -84,10 +94,10 @@ export function _normalizeVisitorTelemetry(telemetry: any) : VisitorTelemetry {
const currentAttributes: attributes = telemetry.element?.attributes || {};
let normalizedAttributes: attributes = {};

const limitedAttributes = Object.entries(currentAttributes).slice(0, MAX_ATTRIBUTES);
const limitedAttributes = Object.entries(currentAttributes).slice(0, MAX_TELEMETRY_ATTRIBUTES);

for (const [key, value] of limitedAttributes) {
normalizedAttributes[key] = truncate(value, MAX_ATTRIBUTE_VALUE);
normalizedAttributes[key] = truncate(value, MAX_TELEMETRY_ATTRIBUTE_VALUE);
}

(telemetry.element || {}).attributes = normalizedAttributes;
Expand Down
26 changes: 15 additions & 11 deletions packages/core/src/trackjs.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -18,17 +18,25 @@ let config: Options | null = null;
let client: Client | null = null;

const defaultOptions: Options = {
application: '',
agent: "core",
agentVersion: "{{AGENT_VERSION}}",
application: "",
dependencies: {},
correlationId: uuid(),
errorURL: 'https://capture.trackjs.com/capture/node',
errorURL: "https://capture.trackjs.com/capture/node",
metadata: {},
onError: () => true,
originalUrl: "",
referrerUrl: "",
serializer: [],
sessionId: '',
token: '',
sessionId: "",
token: "",
transport: new FetchTransport(),
userId: '',
version: ''
userAgent: "",
userId: "",
version: "",
viewportHeight: -1,
viewportWidth: -1
};

/**
Expand DownExpand Up@@ -74,7 +82,7 @@ export function initialize(options: Partial<Options> & { token: string }): void
}

/**
* Adds a object set of key-value pairs to metadata for any future errors.
* Adds a object map of key-value pairs to metadata for any future errors.
* Keys and values will be truncated to 500 characters.
*
* @param metadata - object with string values to be added as metadata.
Expand DownExpand Up@@ -163,10 +171,6 @@ export function addTelemetry(type: TelemetryType, telemetry: Telemetry): void {
return client!.addTelemetry(type, telemetry);
}

export function addDependencies(...args: [dependencies: Record<string, string>]): void {
throw new Error("not implemented");
}

/**
* Track and error or error-like object to the TrackJS error monitoring service.
*
Expand Down
71 changes: 68 additions & 3 deletions packages/core/src/types/options.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -39,6 +39,20 @@ export interface TrackOptions {

export interface Options {

/**
* The name of the TrackJS agent package
*
* @default "core"
*/
agent: string,

/**
* The version of the TrackJS agent package
*
* @default "{{AGENT_VERSION}}"
*/
agentVersion: string,

/**
* TrackJS Application key.
*
Expand All@@ -53,6 +67,14 @@ export interface Options {
*/
correlationId: string;

/**
* Dependency package names and version for the current environment.
*
* @default {}
* @example { "node": "22.12.0" }
*/
dependencies: { [name: string]: string };

/**
* URL destination override for capturing errors.
*
Expand All@@ -69,15 +91,32 @@ export interface Options {

/**
* Custom handler to manipulate or suppress errors captured by the agent.
*
* @param payload error payload to be sent to TrackJS.
* @returns false will suppress the error from being sent.
*
* @default (payload) => true
*/
onError: (payload: CapturePayload) => boolean;

/**
* Custom functions for serializing objects to strings. Will execute before the default serializer.
* When environment has a user interface, the URI location where the application
* was started, or the URL of the page when it was first loaded.
*
* @default ""
*/
originalUrl: string

/**
* When environment has a user interface, the URI location where the user came
* from before landing on this page or screen.
*
* @default ""
*/
referrerUrl: string

/**
* Custom functions for serializing objects to strings. Will execute before the
* default serializer.
*
* @default []
*/
Expand All@@ -96,12 +135,23 @@ export interface Options {
token: string;

/**
* Custom transport function for sending data. Required if the current environment does not support `fetch`.
* Custom transport function for sending data. Required if the current
* environment does not support `fetch`.
*
* @default FetchTransport
*/
transport: Transport;

/**
* User-Agent string describing the user's running environment, browser, process,
* operating system, arch, and version. For non-browser environments, this can
* be constructed with the userAgent() util function.
*
* @see {@link userAgent}
* @defaults ""
*/
userAgent: string

/**
* Customer-generated Id representing the current user.
*
Expand All@@ -116,4 +166,19 @@ export interface Options {
*/
version: string;

/**
* When environment has a user interface, the height of the user viewport in
* pixels.
*
* @default -1
*/
viewportHeight: number

/**
* When environment has a user interface, the width of the user viewport in
* pixels.
*
* @default -1
*/
viewportWidth: number
}
1 change: 1 addition & 0 deletions packages/core/src/utils/index.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -2,4 +2,5 @@ export * from './isType';
export * from './serialize';
export * from './timestamp';
export * from './truncate';
export * from './userAgent';
export * from './uuid';
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
31 changes: 30 additions & 1 deletion e2e/core/node-integration.test.ts
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
import { TrackJS, timestamp } from "@trackjs/core";
import { TrackJS, timestamp, userAgent } from "@trackjs/core";
import { test, expect, beforeEach } from "vitest";
import { MockTransport } from "./mocks/transport";
import type { NetworkTelemetry, Transport, TransportRequest, TransportResponse } from "@trackjs/core";
Expand DownExpand Up@@ -49,6 +49,35 @@ test('TrackJS.track() can track errors after install', async () => {
});
});

test('TrackJS.track() with environment', async () => {
const transport = new MockTransport();

TrackJS.initialize({
token: 'test token',
transport,
dependencies: {
"foo": "1.2.3"
},
originalUrl: "original-url",
referrerUrl: "referrer-url",
userAgent: userAgent("Node", "12.1", "windows", "x64", "11.2"),
});

await TrackJS.track(new Error('Oops'));

expect(transport.sentRequests).toHaveLength(1);
expect(JSON.parse(transport.sentRequests[0]?.data as string)).toMatchObject({
environment: expect.objectContaining({
originalUrl: "original-url",
referrer: "referrer-url",
dependencies: {
"foo": "1.2.3"
},
userAgent: "Node/12.1 (windows x64 11.2)"
})
});
})

test('TrackJS.track() with custom metadata', async () => {
const transport = new MockTransport();

Expand Down
2 changes: 1 addition & 1 deletion packages/core/package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -43,6 +43,6 @@
"clean": "rimraf dist",
"prepublishOnly": "npm run build",
"test": "vitest run",
"test:watch": "vitest"
"dev": "vitest"
}
}
19 changes: 8 additions & 11 deletions packages/core/src/client.ts
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
import { Metadata } from "./metadata";
import { TelemetryLog } from "./telemetryLog";
import { timestamp, serialize, isError } from "./utils";

import type {
CapturePayload,
Options,
Expand DownExpand Up@@ -75,14 +74,12 @@ export class Client {

environment: {
age: 0,
dependencies: {
"foo": "bar"
},
originalUrl: "",
referrer: "",
userAgent: 'node/22.0 (osx x64 123)',
viewportHeight: -1,
viewportWidth: -1
dependencies: structuredClone(this.options.dependencies),
originalUrl: this.options.originalUrl,
referrer: this.options.referrerUrl,
userAgent: this.options.userAgent,
viewportHeight: this.options.viewportHeight,
viewportWidth: this.options.viewportWidth
},

metadata: payloadMetadata.get(),
Expand All@@ -92,8 +89,8 @@ export class Client {
network: this.telemetry.get("net"),
visitor: this.telemetry.get("vis"),

agentPlatform: "",
version: '0.0.0',
agentPlatform: this.options.agent,
version: this.options.agentVersion,
throttled: 0,
};
}
Expand Down
6 changes: 6 additions & 0 deletions packages/core/src/constants.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
export const MAX_METADATA_LENGTH = 500;
export const MAX_TELEMETRY_LOG_SIZE = 30;
export const MAX_TELEMETRY_MESSAGE_LENGTH = 10_000;
export const MAX_TELEMETRY_URI_LENGTH = 1_000;
export const MAX_TELEMETRY_ATTRIBUTES = 20;
export const MAX_TELEMETRY_ATTRIBUTE_VALUE = 500;
2 changes: 1 addition & 1 deletion packages/core/src/index.ts
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
export * as TrackJS from './trackjs';
export { timestamp, uuid } from './utils/';
export { timestamp, userAgent, uuid } from './utils/';

export type * from './types/';
7 changes: 2 additions & 5 deletions packages/core/src/metadata.ts
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
import { MAX_METADATA_LENGTH } from "./constants";
import { truncate } from "./utils";

const MAX_METADATA_LENGTH = 500;

export class Metadata {

private store: Map<string, string> = new Map();
Expand DownExpand Up@@ -30,9 +29,7 @@ export class Metadata {

public clone(): Metadata {
const cloned = new Metadata();
for (const [key, value] of this.store.entries()) {
cloned.store.set(key, value);
}
cloned.store = structuredClone(this.store);
return cloned;
}

Expand Down
40 changes: 25 additions & 15 deletions packages/core/src/telemetryLog.ts
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,21 @@
import type { ConsoleTelemetry, NavigationTelemetry, NetworkTelemetry, Telemetry, TelemetryType, VisitorTelemetry } from "./types";
import { truncate } from "./utils";
import {
MAX_TELEMETRY_ATTRIBUTE_VALUE,
MAX_TELEMETRY_ATTRIBUTES,
MAX_TELEMETRY_LOG_SIZE,
MAX_TELEMETRY_MESSAGE_LENGTH,
MAX_TELEMETRY_URI_LENGTH
} from "./constants";

import type {
ConsoleTelemetry,
NavigationTelemetry,
NetworkTelemetry,
Telemetry,
TelemetryType,
VisitorTelemetry
} from "./types";

const MAX_LOG_SIZE = 30;
const MAX_MESSAGE_LENGTH = 10_000;
const MAX_URI_LENGTH = 1_000;
const MAX_ATTRIBUTES = 20;
const MAX_ATTRIBUTE_VALUE = 500;

export class TelemetryLog {

Expand DownExpand Up@@ -36,8 +46,8 @@ export class TelemetryLog {
}
this.store.push({ type, telemetry});

if (this.store.length > MAX_LOG_SIZE) {
this.store = this.store.slice(this.store.length - MAX_LOG_SIZE);
if (this.store.length > MAX_TELEMETRY_LOG_SIZE) {
this.store = this.store.slice(this.store.length - MAX_TELEMETRY_LOG_SIZE);
}
}

Expand All@@ -47,7 +57,7 @@ export class TelemetryLog {

public clone(): TelemetryLog {
const cloned = new TelemetryLog();
cloned.store = this.store.slice(0);
cloned.store = structuredClone(this.store);
return cloned;
}

Expand All@@ -64,18 +74,18 @@ export class TelemetryLog {
}

export function _normalizeConsoleTelemetry(telemetry: any) : ConsoleTelemetry {
telemetry.message = truncate(telemetry.message, MAX_MESSAGE_LENGTH);
telemetry.message = truncate(telemetry.message, MAX_TELEMETRY_MESSAGE_LENGTH);
return telemetry;
}

export function _normalizeNavigationTelemetry(telemetry: any) : NavigationTelemetry {
telemetry.from = truncate(telemetry.from, MAX_URI_LENGTH);
telemetry.to = truncate(telemetry.to, MAX_URI_LENGTH);
telemetry.from = truncate(telemetry.from, MAX_TELEMETRY_URI_LENGTH);
telemetry.to = truncate(telemetry.to, MAX_TELEMETRY_URI_LENGTH);
return telemetry;
}

export function _normalizeNetworkTelemetry(telemetry: any) : NetworkTelemetry {
telemetry.url = truncate(telemetry.url, MAX_URI_LENGTH);
telemetry.url = truncate(telemetry.url, MAX_TELEMETRY_URI_LENGTH);
return telemetry;
}

Expand All@@ -84,10 +94,10 @@ export function _normalizeVisitorTelemetry(telemetry: any) : VisitorTelemetry {
const currentAttributes: attributes = telemetry.element?.attributes || {};
let normalizedAttributes: attributes = {};

const limitedAttributes = Object.entries(currentAttributes).slice(0, MAX_ATTRIBUTES);
const limitedAttributes = Object.entries(currentAttributes).slice(0, MAX_TELEMETRY_ATTRIBUTES);

for (const [key, value] of limitedAttributes) {
normalizedAttributes[key] = truncate(value, MAX_ATTRIBUTE_VALUE);
normalizedAttributes[key] = truncate(value, MAX_TELEMETRY_ATTRIBUTE_VALUE);
}

(telemetry.element || {}).attributes = normalizedAttributes;
Expand Down
26 changes: 15 additions & 11 deletions packages/core/src/trackjs.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -18,17 +18,25 @@ let config: Options | null = null;
let client: Client | null = null;

const defaultOptions: Options = {
application: '',
agent: "core",
agentVersion: "{{AGENT_VERSION}}",
application: "",
dependencies: {},
correlationId: uuid(),
errorURL: 'https://capture.trackjs.com/capture/node',
errorURL: "https://capture.trackjs.com/capture/node",
metadata: {},
onError: () => true,
originalUrl: "",
referrerUrl: "",
serializer: [],
sessionId: '',
token: '',
sessionId: "",
token: "",
transport: new FetchTransport(),
userId: '',
version: ''
userAgent: "",
userId: "",
version: "",
viewportHeight: -1,
viewportWidth: -1
};

/**
Expand DownExpand Up@@ -74,7 +82,7 @@ export function initialize(options: Partial<Options> & { token: string }): void
}

/**
* Adds a object set of key-value pairs to metadata for any future errors.
* Adds a object map of key-value pairs to metadata for any future errors.
* Keys and values will be truncated to 500 characters.
*
* @param metadata - object with string values to be added as metadata.
Expand DownExpand Up@@ -163,10 +171,6 @@ export function addTelemetry(type: TelemetryType, telemetry: Telemetry): void {
return client!.addTelemetry(type, telemetry);
}

export function addDependencies(...args: [dependencies: Record<string, string>]): void {
throw new Error("not implemented");
}

/**
* Track and error or error-like object to the TrackJS error monitoring service.
*
Expand Down
71 changes: 68 additions & 3 deletions packages/core/src/types/options.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -39,6 +39,20 @@ export interface TrackOptions {

export interface Options {

/**
* The name of the TrackJS agent package
*
* @default "core"
*/
agent: string,

/**
* The version of the TrackJS agent package
*
* @default "{{AGENT_VERSION}}"
*/
agentVersion: string,

/**
* TrackJS Application key.
*
Expand All@@ -53,6 +67,14 @@ export interface Options {
*/
correlationId: string;

/**
* Dependency package names and version for the current environment.
*
* @default {}
* @example { "node": "22.12.0" }
*/
dependencies: { [name: string]: string };

/**
* URL destination override for capturing errors.
*
Expand All@@ -69,15 +91,32 @@ export interface Options {

/**
* Custom handler to manipulate or suppress errors captured by the agent.
*
* @param payload error payload to be sent to TrackJS.
* @returns false will suppress the error from being sent.
*
* @default (payload) => true
*/
onError: (payload: CapturePayload) => boolean;

/**
* Custom functions for serializing objects to strings. Will execute before the default serializer.
* When environment has a user interface, the URI location where the application
* was started, or the URL of the page when it was first loaded.
*
* @default ""
*/
originalUrl: string

/**
* When environment has a user interface, the URI location where the user came
* from before landing on this page or screen.
*
* @default ""
*/
referrerUrl: string

/**
* Custom functions for serializing objects to strings. Will execute before the
* default serializer.
*
* @default []
*/
Expand All@@ -96,12 +135,23 @@ export interface Options {
token: string;

/**
* Custom transport function for sending data. Required if the current environment does not support `fetch`.
* Custom transport function for sending data. Required if the current
* environment does not support `fetch`.
*
* @default FetchTransport
*/
transport: Transport;

/**
* User-Agent string describing the user's running environment, browser, process,
* operating system, arch, and version. For non-browser environments, this can
* be constructed with the userAgent() util function.
*
* @see {@link userAgent}
* @defaults ""
*/
userAgent: string

/**
* Customer-generated Id representing the current user.
*
Expand All@@ -116,4 +166,19 @@ export interface Options {
*/
version: string;

/**
* When environment has a user interface, the height of the user viewport in
* pixels.
*
* @default -1
*/
viewportHeight: number

/**
* When environment has a user interface, the width of the user viewport in
* pixels.
*
* @default -1
*/
viewportWidth: number
}
1 change: 1 addition & 0 deletions packages/core/src/utils/index.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -2,4 +2,5 @@ export * from './isType';
export * from './serialize';
export * from './timestamp';
export * from './truncate';
export * from './userAgent';
export * from './uuid';
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
31 changes: 30 additions & 1 deletion e2e/core/node-integration.test.ts
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
import { TrackJS, timestamp } from "@trackjs/core";
import { TrackJS, timestamp, userAgent } from "@trackjs/core";
import { test, expect, beforeEach } from "vitest";
import { MockTransport } from "./mocks/transport";
import type { NetworkTelemetry, Transport, TransportRequest, TransportResponse } from "@trackjs/core";
Expand DownExpand Up@@ -49,6 +49,35 @@ test('TrackJS.track() can track errors after install', async () => {
});
});

test('TrackJS.track() with environment', async () => {
const transport = new MockTransport();

TrackJS.initialize({
token: 'test token',
transport,
dependencies: {
"foo": "1.2.3"
},
originalUrl: "original-url",
referrerUrl: "referrer-url",
userAgent: userAgent("Node", "12.1", "windows", "x64", "11.2"),
});

await TrackJS.track(new Error('Oops'));

expect(transport.sentRequests).toHaveLength(1);
expect(JSON.parse(transport.sentRequests[0]?.data as string)).toMatchObject({
environment: expect.objectContaining({
originalUrl: "original-url",
referrer: "referrer-url",
dependencies: {
"foo": "1.2.3"
},
userAgent: "Node/12.1 (windows x64 11.2)"
})
});
})

test('TrackJS.track() with custom metadata', async () => {
const transport = new MockTransport();

Expand Down
2 changes: 1 addition & 1 deletion packages/core/package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -43,6 +43,6 @@
"clean": "rimraf dist",
"prepublishOnly": "npm run build",
"test": "vitest run",
"test:watch": "vitest"
"dev": "vitest"
}
}
19 changes: 8 additions & 11 deletions packages/core/src/client.ts
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
import { Metadata } from "./metadata";
import { TelemetryLog } from "./telemetryLog";
import { timestamp, serialize, isError } from "./utils";

import type {
CapturePayload,
Options,
Expand DownExpand Up@@ -75,14 +74,12 @@ export class Client {

environment: {
age: 0,
dependencies: {
"foo": "bar"
},
originalUrl: "",
referrer: "",
userAgent: 'node/22.0 (osx x64 123)',
viewportHeight: -1,
viewportWidth: -1
dependencies: structuredClone(this.options.dependencies),
originalUrl: this.options.originalUrl,
referrer: this.options.referrerUrl,
userAgent: this.options.userAgent,
viewportHeight: this.options.viewportHeight,
viewportWidth: this.options.viewportWidth
},

metadata: payloadMetadata.get(),
Expand All@@ -92,8 +89,8 @@ export class Client {
network: this.telemetry.get("net"),
visitor: this.telemetry.get("vis"),

agentPlatform: "",
version: '0.0.0',
agentPlatform: this.options.agent,
version: this.options.agentVersion,
throttled: 0,
};
}
Expand Down
6 changes: 6 additions & 0 deletions packages/core/src/constants.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
export const MAX_METADATA_LENGTH = 500;
export const MAX_TELEMETRY_LOG_SIZE = 30;
export const MAX_TELEMETRY_MESSAGE_LENGTH = 10_000;
export const MAX_TELEMETRY_URI_LENGTH = 1_000;
export const MAX_TELEMETRY_ATTRIBUTES = 20;
export const MAX_TELEMETRY_ATTRIBUTE_VALUE = 500;
2 changes: 1 addition & 1 deletion packages/core/src/index.ts
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
export * as TrackJS from './trackjs';
export { timestamp, uuid } from './utils/';
export { timestamp, userAgent, uuid } from './utils/';

export type * from './types/';
7 changes: 2 additions & 5 deletions packages/core/src/metadata.ts
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
import { MAX_METADATA_LENGTH } from "./constants";
import { truncate } from "./utils";

const MAX_METADATA_LENGTH = 500;

export class Metadata {

private store: Map<string, string> = new Map();
Expand DownExpand Up@@ -30,9 +29,7 @@ export class Metadata {

public clone(): Metadata {
const cloned = new Metadata();
for (const [key, value] of this.store.entries()) {
cloned.store.set(key, value);
}
cloned.store = structuredClone(this.store);
return cloned;
}

Expand Down
40 changes: 25 additions & 15 deletions packages/core/src/telemetryLog.ts
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,21 @@
import type { ConsoleTelemetry, NavigationTelemetry, NetworkTelemetry, Telemetry, TelemetryType, VisitorTelemetry } from "./types";
import { truncate } from "./utils";
import {
MAX_TELEMETRY_ATTRIBUTE_VALUE,
MAX_TELEMETRY_ATTRIBUTES,
MAX_TELEMETRY_LOG_SIZE,
MAX_TELEMETRY_MESSAGE_LENGTH,
MAX_TELEMETRY_URI_LENGTH
} from "./constants";

import type {
ConsoleTelemetry,
NavigationTelemetry,
NetworkTelemetry,
Telemetry,
TelemetryType,
VisitorTelemetry
} from "./types";

const MAX_LOG_SIZE = 30;
const MAX_MESSAGE_LENGTH = 10_000;
const MAX_URI_LENGTH = 1_000;
const MAX_ATTRIBUTES = 20;
const MAX_ATTRIBUTE_VALUE = 500;

export class TelemetryLog {

Expand DownExpand Up@@ -36,8 +46,8 @@ export class TelemetryLog {
}
this.store.push({ type, telemetry});

if (this.store.length > MAX_LOG_SIZE) {
this.store = this.store.slice(this.store.length - MAX_LOG_SIZE);
if (this.store.length > MAX_TELEMETRY_LOG_SIZE) {
this.store = this.store.slice(this.store.length - MAX_TELEMETRY_LOG_SIZE);
}
}

Expand All@@ -47,7 +57,7 @@ export class TelemetryLog {

public clone(): TelemetryLog {
const cloned = new TelemetryLog();
cloned.store = this.store.slice(0);
cloned.store = structuredClone(this.store);
return cloned;
}

Expand All@@ -64,18 +74,18 @@ export class TelemetryLog {
}

export function _normalizeConsoleTelemetry(telemetry: any) : ConsoleTelemetry {
telemetry.message = truncate(telemetry.message, MAX_MESSAGE_LENGTH);
telemetry.message = truncate(telemetry.message, MAX_TELEMETRY_MESSAGE_LENGTH);
return telemetry;
}

export function _normalizeNavigationTelemetry(telemetry: any) : NavigationTelemetry {
telemetry.from = truncate(telemetry.from, MAX_URI_LENGTH);
telemetry.to = truncate(telemetry.to, MAX_URI_LENGTH);
telemetry.from = truncate(telemetry.from, MAX_TELEMETRY_URI_LENGTH);
telemetry.to = truncate(telemetry.to, MAX_TELEMETRY_URI_LENGTH);
return telemetry;
}

export function _normalizeNetworkTelemetry(telemetry: any) : NetworkTelemetry {
telemetry.url = truncate(telemetry.url, MAX_URI_LENGTH);
telemetry.url = truncate(telemetry.url, MAX_TELEMETRY_URI_LENGTH);
return telemetry;
}

Expand All@@ -84,10 +94,10 @@ export function _normalizeVisitorTelemetry(telemetry: any) : VisitorTelemetry {
const currentAttributes: attributes = telemetry.element?.attributes || {};
let normalizedAttributes: attributes = {};

const limitedAttributes = Object.entries(currentAttributes).slice(0, MAX_ATTRIBUTES);
const limitedAttributes = Object.entries(currentAttributes).slice(0, MAX_TELEMETRY_ATTRIBUTES);

for (const [key, value] of limitedAttributes) {
normalizedAttributes[key] = truncate(value, MAX_ATTRIBUTE_VALUE);
normalizedAttributes[key] = truncate(value, MAX_TELEMETRY_ATTRIBUTE_VALUE);
}

(telemetry.element || {}).attributes = normalizedAttributes;
Expand Down
26 changes: 15 additions & 11 deletions packages/core/src/trackjs.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -18,17 +18,25 @@ let config: Options | null = null;
let client: Client | null = null;

const defaultOptions: Options = {
application: '',
agent: "core",
agentVersion: "{{AGENT_VERSION}}",
application: "",
dependencies: {},
correlationId: uuid(),
errorURL: 'https://capture.trackjs.com/capture/node',
errorURL: "https://capture.trackjs.com/capture/node",
metadata: {},
onError: () => true,
originalUrl: "",
referrerUrl: "",
serializer: [],
sessionId: '',
token: '',
sessionId: "",
token: "",
transport: new FetchTransport(),
userId: '',
version: ''
userAgent: "",
userId: "",
version: "",
viewportHeight: -1,
viewportWidth: -1
};

/**
Expand DownExpand Up@@ -74,7 +82,7 @@ export function initialize(options: Partial<Options> & { token: string }): void
}

/**
* Adds a object set of key-value pairs to metadata for any future errors.
* Adds a object map of key-value pairs to metadata for any future errors.
* Keys and values will be truncated to 500 characters.
*
* @param metadata - object with string values to be added as metadata.
Expand DownExpand Up@@ -163,10 +171,6 @@ export function addTelemetry(type: TelemetryType, telemetry: Telemetry): void {
return client!.addTelemetry(type, telemetry);
}

export function addDependencies(...args: [dependencies: Record<string, string>]): void {
throw new Error("not implemented");
}

/**
* Track and error or error-like object to the TrackJS error monitoring service.
*
Expand Down
71 changes: 68 additions & 3 deletions packages/core/src/types/options.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -39,6 +39,20 @@ export interface TrackOptions {

export interface Options {

/**
* The name of the TrackJS agent package
*
* @default "core"
*/
agent: string,

/**
* The version of the TrackJS agent package
*
* @default "{{AGENT_VERSION}}"
*/
agentVersion: string,

/**
* TrackJS Application key.
*
Expand All@@ -53,6 +67,14 @@ export interface Options {
*/
correlationId: string;

/**
* Dependency package names and version for the current environment.
*
* @default {}
* @example { "node": "22.12.0" }
*/
dependencies: { [name: string]: string };

/**
* URL destination override for capturing errors.
*
Expand All@@ -69,15 +91,32 @@ export interface Options {

/**
* Custom handler to manipulate or suppress errors captured by the agent.
*
* @param payload error payload to be sent to TrackJS.
* @returns false will suppress the error from being sent.
*
* @default (payload) => true
*/
onError: (payload: CapturePayload) => boolean;

/**
* Custom functions for serializing objects to strings. Will execute before the default serializer.
* When environment has a user interface, the URI location where the application
* was started, or the URL of the page when it was first loaded.
*
* @default ""
*/
originalUrl: string

/**
* When environment has a user interface, the URI location where the user came
* from before landing on this page or screen.
*
* @default ""
*/
referrerUrl: string

/**
* Custom functions for serializing objects to strings. Will execute before the
* default serializer.
*
* @default []
*/
Expand All@@ -96,12 +135,23 @@ export interface Options {
token: string;

/**
* Custom transport function for sending data. Required if the current environment does not support `fetch`.
* Custom transport function for sending data. Required if the current
* environment does not support `fetch`.
*
* @default FetchTransport
*/
transport: Transport;

/**
* User-Agent string describing the user's running environment, browser, process,
* operating system, arch, and version. For non-browser environments, this can
* be constructed with the userAgent() util function.
*
* @see {@link userAgent}
* @defaults ""
*/
userAgent: string

/**
* Customer-generated Id representing the current user.
*
Expand All@@ -116,4 +166,19 @@ export interface Options {
*/
version: string;

/**
* When environment has a user interface, the height of the user viewport in
* pixels.
*
* @default -1
*/
viewportHeight: number

/**
* When environment has a user interface, the width of the user viewport in
* pixels.
*
* @default -1
*/
viewportWidth: number
}
1 change: 1 addition & 0 deletions packages/core/src/utils/index.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -2,4 +2,5 @@ export * from './isType';
export * from './serialize';
export * from './timestamp';
export * from './truncate';
export * from './userAgent';
export * from './uuid';
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
31 changes: 30 additions & 1 deletion e2e/core/node-integration.test.ts
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
import { TrackJS, timestamp } from "@trackjs/core";
import { TrackJS, timestamp, userAgent } from "@trackjs/core";
import { test, expect, beforeEach } from "vitest";
import { MockTransport } from "./mocks/transport";
import type { NetworkTelemetry, Transport, TransportRequest, TransportResponse } from "@trackjs/core";
Expand DownExpand Up@@ -49,6 +49,35 @@ test('TrackJS.track() can track errors after install', async () => {
});
});

test('TrackJS.track() with environment', async () => {
const transport = new MockTransport();

TrackJS.initialize({
token: 'test token',
transport,
dependencies: {
"foo": "1.2.3"
},
originalUrl: "original-url",
referrerUrl: "referrer-url",
userAgent: userAgent("Node", "12.1", "windows", "x64", "11.2"),
});

await TrackJS.track(new Error('Oops'));

expect(transport.sentRequests).toHaveLength(1);
expect(JSON.parse(transport.sentRequests[0]?.data as string)).toMatchObject({
environment: expect.objectContaining({
originalUrl: "original-url",
referrer: "referrer-url",
dependencies: {
"foo": "1.2.3"
},
userAgent: "Node/12.1 (windows x64 11.2)"
})
});
})

test('TrackJS.track() with custom metadata', async () => {
const transport = new MockTransport();

Expand Down
2 changes: 1 addition & 1 deletion packages/core/package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -43,6 +43,6 @@
"clean": "rimraf dist",
"prepublishOnly": "npm run build",
"test": "vitest run",
"test:watch": "vitest"
"dev": "vitest"
}
}
19 changes: 8 additions & 11 deletions packages/core/src/client.ts
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
import { Metadata } from "./metadata";
import { TelemetryLog } from "./telemetryLog";
import { timestamp, serialize, isError } from "./utils";

import type {
CapturePayload,
Options,
Expand DownExpand Up@@ -75,14 +74,12 @@ export class Client {

environment: {
age: 0,
dependencies: {
"foo": "bar"
},
originalUrl: "",
referrer: "",
userAgent: 'node/22.0 (osx x64 123)',
viewportHeight: -1,
viewportWidth: -1
dependencies: structuredClone(this.options.dependencies),
originalUrl: this.options.originalUrl,
referrer: this.options.referrerUrl,
userAgent: this.options.userAgent,
viewportHeight: this.options.viewportHeight,
viewportWidth: this.options.viewportWidth
},

metadata: payloadMetadata.get(),
Expand All@@ -92,8 +89,8 @@ export class Client {
network: this.telemetry.get("net"),
visitor: this.telemetry.get("vis"),

agentPlatform: "",
version: '0.0.0',
agentPlatform: this.options.agent,
version: this.options.agentVersion,
throttled: 0,
};
}
Expand Down
6 changes: 6 additions & 0 deletions packages/core/src/constants.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
export const MAX_METADATA_LENGTH = 500;
export const MAX_TELEMETRY_LOG_SIZE = 30;
export const MAX_TELEMETRY_MESSAGE_LENGTH = 10_000;
export const MAX_TELEMETRY_URI_LENGTH = 1_000;
export const MAX_TELEMETRY_ATTRIBUTES = 20;
export const MAX_TELEMETRY_ATTRIBUTE_VALUE = 500;
2 changes: 1 addition & 1 deletion packages/core/src/index.ts
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
export * as TrackJS from './trackjs';
export { timestamp, uuid } from './utils/';
export { timestamp, userAgent, uuid } from './utils/';

export type * from './types/';
7 changes: 2 additions & 5 deletions packages/core/src/metadata.ts
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
import { MAX_METADATA_LENGTH } from "./constants";
import { truncate } from "./utils";

const MAX_METADATA_LENGTH = 500;

export class Metadata {

private store: Map<string, string> = new Map();
Expand DownExpand Up@@ -30,9 +29,7 @@ export class Metadata {

public clone(): Metadata {
const cloned = new Metadata();
for (const [key, value] of this.store.entries()) {
cloned.store.set(key, value);
}
cloned.store = structuredClone(this.store);
return cloned;
}

Expand Down
40 changes: 25 additions & 15 deletions packages/core/src/telemetryLog.ts
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,21 @@
import type { ConsoleTelemetry, NavigationTelemetry, NetworkTelemetry, Telemetry, TelemetryType, VisitorTelemetry } from "./types";
import { truncate } from "./utils";
import {
MAX_TELEMETRY_ATTRIBUTE_VALUE,
MAX_TELEMETRY_ATTRIBUTES,
MAX_TELEMETRY_LOG_SIZE,
MAX_TELEMETRY_MESSAGE_LENGTH,
MAX_TELEMETRY_URI_LENGTH
} from "./constants";

import type {
ConsoleTelemetry,
NavigationTelemetry,
NetworkTelemetry,
Telemetry,
TelemetryType,
VisitorTelemetry
} from "./types";

const MAX_LOG_SIZE = 30;
const MAX_MESSAGE_LENGTH = 10_000;
const MAX_URI_LENGTH = 1_000;
const MAX_ATTRIBUTES = 20;
const MAX_ATTRIBUTE_VALUE = 500;

export class TelemetryLog {

Expand DownExpand Up@@ -36,8 +46,8 @@ export class TelemetryLog {
}
this.store.push({ type, telemetry});

if (this.store.length > MAX_LOG_SIZE) {
this.store = this.store.slice(this.store.length - MAX_LOG_SIZE);
if (this.store.length > MAX_TELEMETRY_LOG_SIZE) {
this.store = this.store.slice(this.store.length - MAX_TELEMETRY_LOG_SIZE);
}
}

Expand All@@ -47,7 +57,7 @@ export class TelemetryLog {

public clone(): TelemetryLog {
const cloned = new TelemetryLog();
cloned.store = this.store.slice(0);
cloned.store = structuredClone(this.store);
return cloned;
}

Expand All@@ -64,18 +74,18 @@ export class TelemetryLog {
}

export function _normalizeConsoleTelemetry(telemetry: any) : ConsoleTelemetry {
telemetry.message = truncate(telemetry.message, MAX_MESSAGE_LENGTH);
telemetry.message = truncate(telemetry.message, MAX_TELEMETRY_MESSAGE_LENGTH);
return telemetry;
}

export function _normalizeNavigationTelemetry(telemetry: any) : NavigationTelemetry {
telemetry.from = truncate(telemetry.from, MAX_URI_LENGTH);
telemetry.to = truncate(telemetry.to, MAX_URI_LENGTH);
telemetry.from = truncate(telemetry.from, MAX_TELEMETRY_URI_LENGTH);
telemetry.to = truncate(telemetry.to, MAX_TELEMETRY_URI_LENGTH);
return telemetry;
}

export function _normalizeNetworkTelemetry(telemetry: any) : NetworkTelemetry {
telemetry.url = truncate(telemetry.url, MAX_URI_LENGTH);
telemetry.url = truncate(telemetry.url, MAX_TELEMETRY_URI_LENGTH);
return telemetry;
}

Expand All@@ -84,10 +94,10 @@ export function _normalizeVisitorTelemetry(telemetry: any) : VisitorTelemetry {
const currentAttributes: attributes = telemetry.element?.attributes || {};
let normalizedAttributes: attributes = {};

const limitedAttributes = Object.entries(currentAttributes).slice(0, MAX_ATTRIBUTES);
const limitedAttributes = Object.entries(currentAttributes).slice(0, MAX_TELEMETRY_ATTRIBUTES);

for (const [key, value] of limitedAttributes) {
normalizedAttributes[key] = truncate(value, MAX_ATTRIBUTE_VALUE);
normalizedAttributes[key] = truncate(value, MAX_TELEMETRY_ATTRIBUTE_VALUE);
}

(telemetry.element || {}).attributes = normalizedAttributes;
Expand Down
26 changes: 15 additions & 11 deletions packages/core/src/trackjs.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -18,17 +18,25 @@ let config: Options | null = null;
let client: Client | null = null;

const defaultOptions: Options = {
application: '',
agent: "core",
agentVersion: "{{AGENT_VERSION}}",
application: "",
dependencies: {},
correlationId: uuid(),
errorURL: 'https://capture.trackjs.com/capture/node',
errorURL: "https://capture.trackjs.com/capture/node",
metadata: {},
onError: () => true,
originalUrl: "",
referrerUrl: "",
serializer: [],
sessionId: '',
token: '',
sessionId: "",
token: "",
transport: new FetchTransport(),
userId: '',
version: ''
userAgent: "",
userId: "",
version: "",
viewportHeight: -1,
viewportWidth: -1
};

/**
Expand DownExpand Up@@ -74,7 +82,7 @@ export function initialize(options: Partial<Options> & { token: string }): void
}

/**
* Adds a object set of key-value pairs to metadata for any future errors.
* Adds a object map of key-value pairs to metadata for any future errors.
* Keys and values will be truncated to 500 characters.
*
* @param metadata - object with string values to be added as metadata.
Expand DownExpand Up@@ -163,10 +171,6 @@ export function addTelemetry(type: TelemetryType, telemetry: Telemetry): void {
return client!.addTelemetry(type, telemetry);
}

export function addDependencies(...args: [dependencies: Record<string, string>]): void {
throw new Error("not implemented");
}

/**
* Track and error or error-like object to the TrackJS error monitoring service.
*
Expand Down
71 changes: 68 additions & 3 deletions packages/core/src/types/options.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -39,6 +39,20 @@ export interface TrackOptions {

export interface Options {

/**
* The name of the TrackJS agent package
*
* @default "core"
*/
agent: string,

/**
* The version of the TrackJS agent package
*
* @default "{{AGENT_VERSION}}"
*/
agentVersion: string,

/**
* TrackJS Application key.
*
Expand All@@ -53,6 +67,14 @@ export interface Options {
*/
correlationId: string;

/**
* Dependency package names and version for the current environment.
*
* @default {}
* @example { "node": "22.12.0" }
*/
dependencies: { [name: string]: string };

/**
* URL destination override for capturing errors.
*
Expand All@@ -69,15 +91,32 @@ export interface Options {

/**
* Custom handler to manipulate or suppress errors captured by the agent.
*
* @param payload error payload to be sent to TrackJS.
* @returns false will suppress the error from being sent.
*
* @default (payload) => true
*/
onError: (payload: CapturePayload) => boolean;

/**
* Custom functions for serializing objects to strings. Will execute before the default serializer.
* When environment has a user interface, the URI location where the application
* was started, or the URL of the page when it was first loaded.
*
* @default ""
*/
originalUrl: string

/**
* When environment has a user interface, the URI location where the user came
* from before landing on this page or screen.
*
* @default ""
*/
referrerUrl: string

/**
* Custom functions for serializing objects to strings. Will execute before the
* default serializer.
*
* @default []
*/
Expand All@@ -96,12 +135,23 @@ export interface Options {
token: string;

/**
* Custom transport function for sending data. Required if the current environment does not support `fetch`.
* Custom transport function for sending data. Required if the current
* environment does not support `fetch`.
*
* @default FetchTransport
*/
transport: Transport;

/**
* User-Agent string describing the user's running environment, browser, process,
* operating system, arch, and version. For non-browser environments, this can
* be constructed with the userAgent() util function.
*
* @see {@link userAgent}
* @defaults ""
*/
userAgent: string

/**
* Customer-generated Id representing the current user.
*
Expand All@@ -116,4 +166,19 @@ export interface Options {
*/
version: string;

/**
* When environment has a user interface, the height of the user viewport in
* pixels.
*
* @default -1
*/
viewportHeight: number

/**
* When environment has a user interface, the width of the user viewport in
* pixels.
*
* @default -1
*/
viewportWidth: number
}
1 change: 1 addition & 0 deletions packages/core/src/utils/index.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -2,4 +2,5 @@ export * from './isType';
export * from './serialize';
export * from './timestamp';
export * from './truncate';
export * from './userAgent';
export * from './uuid';
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
31 changes: 30 additions & 1 deletion e2e/core/node-integration.test.ts
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
import { TrackJS, timestamp } from "@trackjs/core";
import { TrackJS, timestamp, userAgent } from "@trackjs/core";
import { test, expect, beforeEach } from "vitest";
import { MockTransport } from "./mocks/transport";
import type { NetworkTelemetry, Transport, TransportRequest, TransportResponse } from "@trackjs/core";
Expand DownExpand Up@@ -49,6 +49,35 @@ test('TrackJS.track() can track errors after install', async () => {
});
});

test('TrackJS.track() with environment', async () => {
const transport = new MockTransport();

TrackJS.initialize({
token: 'test token',
transport,
dependencies: {
"foo": "1.2.3"
},
originalUrl: "original-url",
referrerUrl: "referrer-url",
userAgent: userAgent("Node", "12.1", "windows", "x64", "11.2"),
});

await TrackJS.track(new Error('Oops'));

expect(transport.sentRequests).toHaveLength(1);
expect(JSON.parse(transport.sentRequests[0]?.data as string)).toMatchObject({
environment: expect.objectContaining({
originalUrl: "original-url",
referrer: "referrer-url",
dependencies: {
"foo": "1.2.3"
},
userAgent: "Node/12.1 (windows x64 11.2)"
})
});
})

test('TrackJS.track() with custom metadata', async () => {
const transport = new MockTransport();

Expand Down
2 changes: 1 addition & 1 deletion packages/core/package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -43,6 +43,6 @@
"clean": "rimraf dist",
"prepublishOnly": "npm run build",
"test": "vitest run",
"test:watch": "vitest"
"dev": "vitest"
}
}
19 changes: 8 additions & 11 deletions packages/core/src/client.ts
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
import { Metadata } from "./metadata";
import { TelemetryLog } from "./telemetryLog";
import { timestamp, serialize, isError } from "./utils";

import type {
CapturePayload,
Options,
Expand DownExpand Up@@ -75,14 +74,12 @@ export class Client {

environment: {
age: 0,
dependencies: {
"foo": "bar"
},
originalUrl: "",
referrer: "",
userAgent: 'node/22.0 (osx x64 123)',
viewportHeight: -1,
viewportWidth: -1
dependencies: structuredClone(this.options.dependencies),
originalUrl: this.options.originalUrl,
referrer: this.options.referrerUrl,
userAgent: this.options.userAgent,
viewportHeight: this.options.viewportHeight,
viewportWidth: this.options.viewportWidth
},

metadata: payloadMetadata.get(),
Expand All@@ -92,8 +89,8 @@ export class Client {
network: this.telemetry.get("net"),
visitor: this.telemetry.get("vis"),

agentPlatform: "",
version: '0.0.0',
agentPlatform: this.options.agent,
version: this.options.agentVersion,
throttled: 0,
};
}
Expand Down
6 changes: 6 additions & 0 deletions packages/core/src/constants.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
export const MAX_METADATA_LENGTH = 500;
export const MAX_TELEMETRY_LOG_SIZE = 30;
export const MAX_TELEMETRY_MESSAGE_LENGTH = 10_000;
export const MAX_TELEMETRY_URI_LENGTH = 1_000;
export const MAX_TELEMETRY_ATTRIBUTES = 20;
export const MAX_TELEMETRY_ATTRIBUTE_VALUE = 500;
2 changes: 1 addition & 1 deletion packages/core/src/index.ts
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
export * as TrackJS from './trackjs';
export { timestamp, uuid } from './utils/';
export { timestamp, userAgent, uuid } from './utils/';

export type * from './types/';
7 changes: 2 additions & 5 deletions packages/core/src/metadata.ts
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
import { MAX_METADATA_LENGTH } from "./constants";
import { truncate } from "./utils";

const MAX_METADATA_LENGTH = 500;

export class Metadata {

private store: Map<string, string> = new Map();
Expand DownExpand Up@@ -30,9 +29,7 @@ export class Metadata {

public clone(): Metadata {
const cloned = new Metadata();
for (const [key, value] of this.store.entries()) {
cloned.store.set(key, value);
}
cloned.store = structuredClone(this.store);
return cloned;
}

Expand Down
40 changes: 25 additions & 15 deletions packages/core/src/telemetryLog.ts
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,21 @@
import type { ConsoleTelemetry, NavigationTelemetry, NetworkTelemetry, Telemetry, TelemetryType, VisitorTelemetry } from "./types";
import { truncate } from "./utils";
import {
MAX_TELEMETRY_ATTRIBUTE_VALUE,
MAX_TELEMETRY_ATTRIBUTES,
MAX_TELEMETRY_LOG_SIZE,
MAX_TELEMETRY_MESSAGE_LENGTH,
MAX_TELEMETRY_URI_LENGTH
} from "./constants";

import type {
ConsoleTelemetry,
NavigationTelemetry,
NetworkTelemetry,
Telemetry,
TelemetryType,
VisitorTelemetry
} from "./types";

const MAX_LOG_SIZE = 30;
const MAX_MESSAGE_LENGTH = 10_000;
const MAX_URI_LENGTH = 1_000;
const MAX_ATTRIBUTES = 20;
const MAX_ATTRIBUTE_VALUE = 500;

export class TelemetryLog {

Expand DownExpand Up@@ -36,8 +46,8 @@ export class TelemetryLog {
}
this.store.push({ type, telemetry});

if (this.store.length > MAX_LOG_SIZE) {
this.store = this.store.slice(this.store.length - MAX_LOG_SIZE);
if (this.store.length > MAX_TELEMETRY_LOG_SIZE) {
this.store = this.store.slice(this.store.length - MAX_TELEMETRY_LOG_SIZE);
}
}

Expand All@@ -47,7 +57,7 @@ export class TelemetryLog {

public clone(): TelemetryLog {
const cloned = new TelemetryLog();
cloned.store = this.store.slice(0);
cloned.store = structuredClone(this.store);
return cloned;
}

Expand All@@ -64,18 +74,18 @@ export class TelemetryLog {
}

export function _normalizeConsoleTelemetry(telemetry: any) : ConsoleTelemetry {
telemetry.message = truncate(telemetry.message, MAX_MESSAGE_LENGTH);
telemetry.message = truncate(telemetry.message, MAX_TELEMETRY_MESSAGE_LENGTH);
return telemetry;
}

export function _normalizeNavigationTelemetry(telemetry: any) : NavigationTelemetry {
telemetry.from = truncate(telemetry.from, MAX_URI_LENGTH);
telemetry.to = truncate(telemetry.to, MAX_URI_LENGTH);
telemetry.from = truncate(telemetry.from, MAX_TELEMETRY_URI_LENGTH);
telemetry.to = truncate(telemetry.to, MAX_TELEMETRY_URI_LENGTH);
return telemetry;
}

export function _normalizeNetworkTelemetry(telemetry: any) : NetworkTelemetry {
telemetry.url = truncate(telemetry.url, MAX_URI_LENGTH);
telemetry.url = truncate(telemetry.url, MAX_TELEMETRY_URI_LENGTH);
return telemetry;
}

Expand All@@ -84,10 +94,10 @@ export function _normalizeVisitorTelemetry(telemetry: any) : VisitorTelemetry {
const currentAttributes: attributes = telemetry.element?.attributes || {};
let normalizedAttributes: attributes = {};

const limitedAttributes = Object.entries(currentAttributes).slice(0, MAX_ATTRIBUTES);
const limitedAttributes = Object.entries(currentAttributes).slice(0, MAX_TELEMETRY_ATTRIBUTES);

for (const [key, value] of limitedAttributes) {
normalizedAttributes[key] = truncate(value, MAX_ATTRIBUTE_VALUE);
normalizedAttributes[key] = truncate(value, MAX_TELEMETRY_ATTRIBUTE_VALUE);
}

(telemetry.element || {}).attributes = normalizedAttributes;
Expand Down
26 changes: 15 additions & 11 deletions packages/core/src/trackjs.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -18,17 +18,25 @@ let config: Options | null = null;
let client: Client | null = null;

const defaultOptions: Options = {
application: '',
agent: "core",
agentVersion: "{{AGENT_VERSION}}",
application: "",
dependencies: {},
correlationId: uuid(),
errorURL: 'https://capture.trackjs.com/capture/node',
errorURL: "https://capture.trackjs.com/capture/node",
metadata: {},
onError: () => true,
originalUrl: "",
referrerUrl: "",
serializer: [],
sessionId: '',
token: '',
sessionId: "",
token: "",
transport: new FetchTransport(),
userId: '',
version: ''
userAgent: "",
userId: "",
version: "",
viewportHeight: -1,
viewportWidth: -1
};

/**
Expand DownExpand Up@@ -74,7 +82,7 @@ export function initialize(options: Partial<Options> & { token: string }): void
}

/**
* Adds a object set of key-value pairs to metadata for any future errors.
* Adds a object map of key-value pairs to metadata for any future errors.
* Keys and values will be truncated to 500 characters.
*
* @param metadata - object with string values to be added as metadata.
Expand DownExpand Up@@ -163,10 +171,6 @@ export function addTelemetry(type: TelemetryType, telemetry: Telemetry): void {
return client!.addTelemetry(type, telemetry);
}

export function addDependencies(...args: [dependencies: Record<string, string>]): void {
throw new Error("not implemented");
}

/**
* Track and error or error-like object to the TrackJS error monitoring service.
*
Expand Down
71 changes: 68 additions & 3 deletions packages/core/src/types/options.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -39,6 +39,20 @@ export interface TrackOptions {

export interface Options {

/**
* The name of the TrackJS agent package
*
* @default "core"
*/
agent: string,

/**
* The version of the TrackJS agent package
*
* @default "{{AGENT_VERSION}}"
*/
agentVersion: string,

/**
* TrackJS Application key.
*
Expand All@@ -53,6 +67,14 @@ export interface Options {
*/
correlationId: string;

/**
* Dependency package names and version for the current environment.
*
* @default {}
* @example { "node": "22.12.0" }
*/
dependencies: { [name: string]: string };

/**
* URL destination override for capturing errors.
*
Expand All@@ -69,15 +91,32 @@ export interface Options {

/**
* Custom handler to manipulate or suppress errors captured by the agent.
*
* @param payload error payload to be sent to TrackJS.
* @returns false will suppress the error from being sent.
*
* @default (payload) => true
*/
onError: (payload: CapturePayload) => boolean;

/**
* Custom functions for serializing objects to strings. Will execute before the default serializer.
* When environment has a user interface, the URI location where the application
* was started, or the URL of the page when it was first loaded.
*
* @default ""
*/
originalUrl: string

/**
* When environment has a user interface, the URI location where the user came
* from before landing on this page or screen.
*
* @default ""
*/
referrerUrl: string

/**
* Custom functions for serializing objects to strings. Will execute before the
* default serializer.
*
* @default []
*/
Expand All@@ -96,12 +135,23 @@ export interface Options {
token: string;

/**
* Custom transport function for sending data. Required if the current environment does not support `fetch`.
* Custom transport function for sending data. Required if the current
* environment does not support `fetch`.
*
* @default FetchTransport
*/
transport: Transport;

/**
* User-Agent string describing the user's running environment, browser, process,
* operating system, arch, and version. For non-browser environments, this can
* be constructed with the userAgent() util function.
*
* @see {@link userAgent}
* @defaults ""
*/
userAgent: string

/**
* Customer-generated Id representing the current user.
*
Expand All@@ -116,4 +166,19 @@ export interface Options {
*/
version: string;

/**
* When environment has a user interface, the height of the user viewport in
* pixels.
*
* @default -1
*/
viewportHeight: number

/**
* When environment has a user interface, the width of the user viewport in
* pixels.
*
* @default -1
*/
viewportWidth: number
}
1 change: 1 addition & 0 deletions packages/core/src/utils/index.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -2,4 +2,5 @@ export * from './isType';
export * from './serialize';
export * from './timestamp';
export * from './truncate';
export * from './userAgent';
export * from './uuid';
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
31 changes: 30 additions & 1 deletion e2e/core/node-integration.test.ts
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
import { TrackJS, timestamp } from "@trackjs/core";
import { TrackJS, timestamp, userAgent } from "@trackjs/core";
import { test, expect, beforeEach } from "vitest";
import { MockTransport } from "./mocks/transport";
import type { NetworkTelemetry, Transport, TransportRequest, TransportResponse } from "@trackjs/core";
Expand DownExpand Up@@ -49,6 +49,35 @@ test('TrackJS.track() can track errors after install', async () => {
});
});

test('TrackJS.track() with environment', async () => {
const transport = new MockTransport();

TrackJS.initialize({
token: 'test token',
transport,
dependencies: {
"foo": "1.2.3"
},
originalUrl: "original-url",
referrerUrl: "referrer-url",
userAgent: userAgent("Node", "12.1", "windows", "x64", "11.2"),
});

await TrackJS.track(new Error('Oops'));

expect(transport.sentRequests).toHaveLength(1);
expect(JSON.parse(transport.sentRequests[0]?.data as string)).toMatchObject({
environment: expect.objectContaining({
originalUrl: "original-url",
referrer: "referrer-url",
dependencies: {
"foo": "1.2.3"
},
userAgent: "Node/12.1 (windows x64 11.2)"
})
});
})

test('TrackJS.track() with custom metadata', async () => {
const transport = new MockTransport();

Expand Down
2 changes: 1 addition & 1 deletion packages/core/package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -43,6 +43,6 @@
"clean": "rimraf dist",
"prepublishOnly": "npm run build",
"test": "vitest run",
"test:watch": "vitest"
"dev": "vitest"
}
}
19 changes: 8 additions & 11 deletions packages/core/src/client.ts
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
import { Metadata } from "./metadata";
import { TelemetryLog } from "./telemetryLog";
import { timestamp, serialize, isError } from "./utils";

import type {
CapturePayload,
Options,
Expand DownExpand Up@@ -75,14 +74,12 @@ export class Client {

environment: {
age: 0,
dependencies: {
"foo": "bar"
},
originalUrl: "",
referrer: "",
userAgent: 'node/22.0 (osx x64 123)',
viewportHeight: -1,
viewportWidth: -1
dependencies: structuredClone(this.options.dependencies),
originalUrl: this.options.originalUrl,
referrer: this.options.referrerUrl,
userAgent: this.options.userAgent,
viewportHeight: this.options.viewportHeight,
viewportWidth: this.options.viewportWidth
},

metadata: payloadMetadata.get(),
Expand All@@ -92,8 +89,8 @@ export class Client {
network: this.telemetry.get("net"),
visitor: this.telemetry.get("vis"),

agentPlatform: "",
version: '0.0.0',
agentPlatform: this.options.agent,
version: this.options.agentVersion,
throttled: 0,
};
}
Expand Down
6 changes: 6 additions & 0 deletions packages/core/src/constants.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
export const MAX_METADATA_LENGTH = 500;
export const MAX_TELEMETRY_LOG_SIZE = 30;
export const MAX_TELEMETRY_MESSAGE_LENGTH = 10_000;
export const MAX_TELEMETRY_URI_LENGTH = 1_000;
export const MAX_TELEMETRY_ATTRIBUTES = 20;
export const MAX_TELEMETRY_ATTRIBUTE_VALUE = 500;
2 changes: 1 addition & 1 deletion packages/core/src/index.ts
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
export * as TrackJS from './trackjs';
export { timestamp, uuid } from './utils/';
export { timestamp, userAgent, uuid } from './utils/';

export type * from './types/';
7 changes: 2 additions & 5 deletions packages/core/src/metadata.ts
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
import { MAX_METADATA_LENGTH } from "./constants";
import { truncate } from "./utils";

const MAX_METADATA_LENGTH = 500;

export class Metadata {

private store: Map<string, string> = new Map();
Expand DownExpand Up@@ -30,9 +29,7 @@ export class Metadata {

public clone(): Metadata {
const cloned = new Metadata();
for (const [key, value] of this.store.entries()) {
cloned.store.set(key, value);
}
cloned.store = structuredClone(this.store);
return cloned;
}

Expand Down
40 changes: 25 additions & 15 deletions packages/core/src/telemetryLog.ts
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,21 @@
import type { ConsoleTelemetry, NavigationTelemetry, NetworkTelemetry, Telemetry, TelemetryType, VisitorTelemetry } from "./types";
import { truncate } from "./utils";
import {
MAX_TELEMETRY_ATTRIBUTE_VALUE,
MAX_TELEMETRY_ATTRIBUTES,
MAX_TELEMETRY_LOG_SIZE,
MAX_TELEMETRY_MESSAGE_LENGTH,
MAX_TELEMETRY_URI_LENGTH
} from "./constants";

import type {
ConsoleTelemetry,
NavigationTelemetry,
NetworkTelemetry,
Telemetry,
TelemetryType,
VisitorTelemetry
} from "./types";

const MAX_LOG_SIZE = 30;
const MAX_MESSAGE_LENGTH = 10_000;
const MAX_URI_LENGTH = 1_000;
const MAX_ATTRIBUTES = 20;
const MAX_ATTRIBUTE_VALUE = 500;

export class TelemetryLog {

Expand DownExpand Up@@ -36,8 +46,8 @@ export class TelemetryLog {
}
this.store.push({ type, telemetry});

if (this.store.length > MAX_LOG_SIZE) {
this.store = this.store.slice(this.store.length - MAX_LOG_SIZE);
if (this.store.length > MAX_TELEMETRY_LOG_SIZE) {
this.store = this.store.slice(this.store.length - MAX_TELEMETRY_LOG_SIZE);
}
}

Expand All@@ -47,7 +57,7 @@ export class TelemetryLog {

public clone(): TelemetryLog {
const cloned = new TelemetryLog();
cloned.store = this.store.slice(0);
cloned.store = structuredClone(this.store);
return cloned;
}

Expand All@@ -64,18 +74,18 @@ export class TelemetryLog {
}

export function _normalizeConsoleTelemetry(telemetry: any) : ConsoleTelemetry {
telemetry.message = truncate(telemetry.message, MAX_MESSAGE_LENGTH);
telemetry.message = truncate(telemetry.message, MAX_TELEMETRY_MESSAGE_LENGTH);
return telemetry;
}

export function _normalizeNavigationTelemetry(telemetry: any) : NavigationTelemetry {
telemetry.from = truncate(telemetry.from, MAX_URI_LENGTH);
telemetry.to = truncate(telemetry.to, MAX_URI_LENGTH);
telemetry.from = truncate(telemetry.from, MAX_TELEMETRY_URI_LENGTH);
telemetry.to = truncate(telemetry.to, MAX_TELEMETRY_URI_LENGTH);
return telemetry;
}

export function _normalizeNetworkTelemetry(telemetry: any) : NetworkTelemetry {
telemetry.url = truncate(telemetry.url, MAX_URI_LENGTH);
telemetry.url = truncate(telemetry.url, MAX_TELEMETRY_URI_LENGTH);
return telemetry;
}

Expand All@@ -84,10 +94,10 @@ export function _normalizeVisitorTelemetry(telemetry: any) : VisitorTelemetry {
const currentAttributes: attributes = telemetry.element?.attributes || {};
let normalizedAttributes: attributes = {};

const limitedAttributes = Object.entries(currentAttributes).slice(0, MAX_ATTRIBUTES);
const limitedAttributes = Object.entries(currentAttributes).slice(0, MAX_TELEMETRY_ATTRIBUTES);

for (const [key, value] of limitedAttributes) {
normalizedAttributes[key] = truncate(value, MAX_ATTRIBUTE_VALUE);
normalizedAttributes[key] = truncate(value, MAX_TELEMETRY_ATTRIBUTE_VALUE);
}

(telemetry.element || {}).attributes = normalizedAttributes;
Expand Down
26 changes: 15 additions & 11 deletions packages/core/src/trackjs.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -18,17 +18,25 @@ let config: Options | null = null;
let client: Client | null = null;

const defaultOptions: Options = {
application: '',
agent: "core",
agentVersion: "{{AGENT_VERSION}}",
application: "",
dependencies: {},
correlationId: uuid(),
errorURL: 'https://capture.trackjs.com/capture/node',
errorURL: "https://capture.trackjs.com/capture/node",
metadata: {},
onError: () => true,
originalUrl: "",
referrerUrl: "",
serializer: [],
sessionId: '',
token: '',
sessionId: "",
token: "",
transport: new FetchTransport(),
userId: '',
version: ''
userAgent: "",
userId: "",
version: "",
viewportHeight: -1,
viewportWidth: -1
};

/**
Expand DownExpand Up@@ -74,7 +82,7 @@ export function initialize(options: Partial<Options> & { token: string }): void
}

/**
* Adds a object set of key-value pairs to metadata for any future errors.
* Adds a object map of key-value pairs to metadata for any future errors.
* Keys and values will be truncated to 500 characters.
*
* @param metadata - object with string values to be added as metadata.
Expand DownExpand Up@@ -163,10 +171,6 @@ export function addTelemetry(type: TelemetryType, telemetry: Telemetry): void {
return client!.addTelemetry(type, telemetry);
}

export function addDependencies(...args: [dependencies: Record<string, string>]): void {
throw new Error("not implemented");
}

/**
* Track and error or error-like object to the TrackJS error monitoring service.
*
Expand Down
71 changes: 68 additions & 3 deletions packages/core/src/types/options.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -39,6 +39,20 @@ export interface TrackOptions {

export interface Options {

/**
* The name of the TrackJS agent package
*
* @default "core"
*/
agent: string,

/**
* The version of the TrackJS agent package
*
* @default "{{AGENT_VERSION}}"
*/
agentVersion: string,

/**
* TrackJS Application key.
*
Expand All@@ -53,6 +67,14 @@ export interface Options {
*/
correlationId: string;

/**
* Dependency package names and version for the current environment.
*
* @default {}
* @example { "node": "22.12.0" }
*/
dependencies: { [name: string]: string };

/**
* URL destination override for capturing errors.
*
Expand All@@ -69,15 +91,32 @@ export interface Options {

/**
* Custom handler to manipulate or suppress errors captured by the agent.
*
* @param payload error payload to be sent to TrackJS.
* @returns false will suppress the error from being sent.
*
* @default (payload) => true
*/
onError: (payload: CapturePayload) => boolean;

/**
* Custom functions for serializing objects to strings. Will execute before the default serializer.
* When environment has a user interface, the URI location where the application
* was started, or the URL of the page when it was first loaded.
*
* @default ""
*/
originalUrl: string

/**
* When environment has a user interface, the URI location where the user came
* from before landing on this page or screen.
*
* @default ""
*/
referrerUrl: string

/**
* Custom functions for serializing objects to strings. Will execute before the
* default serializer.
*
* @default []
*/
Expand All@@ -96,12 +135,23 @@ export interface Options {
token: string;

/**
* Custom transport function for sending data. Required if the current environment does not support `fetch`.
* Custom transport function for sending data. Required if the current
* environment does not support `fetch`.
*
* @default FetchTransport
*/
transport: Transport;

/**
* User-Agent string describing the user's running environment, browser, process,
* operating system, arch, and version. For non-browser environments, this can
* be constructed with the userAgent() util function.
*
* @see {@link userAgent}
* @defaults ""
*/
userAgent: string

/**
* Customer-generated Id representing the current user.
*
Expand All@@ -116,4 +166,19 @@ export interface Options {
*/
version: string;

/**
* When environment has a user interface, the height of the user viewport in
* pixels.
*
* @default -1
*/
viewportHeight: number

/**
* When environment has a user interface, the width of the user viewport in
* pixels.
*
* @default -1
*/
viewportWidth: number
}
1 change: 1 addition & 0 deletions packages/core/src/utils/index.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -2,4 +2,5 @@ export * from './isType';
export * from './serialize';
export * from './timestamp';
export * from './truncate';
export * from './userAgent';
export * from './uuid';
Loading