🎩 You're Invited:Meet the Socket team at Black Hat in Las Vegas, August 3-6.RSVP
Sign In

@glassanalytics/browser

Package Overview
Dependencies
Maintainers
1
Versions
4
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@glassanalytics/browser - npm Package Compare versions

Comparing version
0.1.2
to
0.1.4
+20
-2
dist/index.d.ts

@@ -24,2 +24,14 @@ import { Value, FlagDefinition } from '@glassanalytics/core';

captureConsole?: boolean;
/**
* Record console output INTO the session replay (surfaced in the player's
* Console inspector). Independent of `captureConsole` (which turns errors into
* Glass events). Default on when replay is on.
*/
recordConsole?: boolean;
/**
* Record network requests (fetch/XHR) into the session replay (player's Network
* inspector). Captures ONLY method, URL, status and timing — never headers or
* bodies — and never the Glass ingest host itself. Default on when replay is on.
*/
recordNetwork?: boolean;
/** Fraction of sessions recorded (client-side). */

@@ -30,2 +42,8 @@ sampleRate?: number;

maskTextSelector?: string;
/**
* Extra CSS selector for subtrees to exclude from replay (shown as a
* placeholder). Glass ALWAYS also blocks `.glass-block` / `[data-glass-block]`
* (and rrweb's `rr-block`) — mark decorative/animated elements with those to
* keep them out of recordings without flooding the stream.
*/
blockSelector?: string;

@@ -41,3 +59,3 @@ respectDoNotTrack?: boolean;

* Optional flag DEFINITIONS for local (offline / property-aware) bucketing via
* `@glassanalytics/core` evaluateFlag. The hosted server intentionally does NOT leak the
* `@glass/core` evaluateFlag. The hosted server intentionally does NOT leak the
* rule set, so this is for self-host / advanced setups that ship definitions.

@@ -153,3 +171,3 @@ */

private resolve;
/** Local bucketing via @glassanalytics/core when definitions were provided to init(). */
/** Local bucketing via @glass/core when definitions were provided to init(). */
private evaluateLocal;

@@ -156,0 +174,0 @@ isFeatureEnabledSync(key: string, opts?: FlagOptions): boolean;

@@ -122,5 +122,104 @@ import { randomToken, prefixedId, evaluateFlag, INGEST_AUTH_SCHEME, replaySegmentPath } from '@glassanalytics/core';

// src/network-plugin.ts
var NETWORK_PLUGIN_NAME = "rrweb/network@1";
function getGlassNetworkPlugin(options = {}) {
return {
name: NETWORK_PLUGIN_NAME,
options,
observer(cb, _win, opts) {
if (typeof window === "undefined") return () => {
};
const ignore = opts.ignoreUrlPrefix;
const teardowns = [];
const shouldIgnore = (url) => !url || (ignore ? url.startsWith(ignore) : false);
const record = (r) => {
try {
cb({ requests: [r] });
} catch {
}
};
if (typeof window.fetch === "function") {
const orig = window.fetch;
const patched = (input, init) => {
const url = typeof input === "string" ? input : input instanceof URL ? input.href : input?.url ?? "";
const method = (init?.method ?? (typeof input === "object" && input && "method" in input ? input.method : "GET")).toUpperCase();
const startTime = Date.now();
const p = orig(input, init);
if (!shouldIgnore(url)) {
p.then(
(res) => record({ type: "fetch", method, name: url, status: res.status, startTime, endTime: Date.now() }),
() => record({ type: "fetch", method, name: url, status: 0, startTime, endTime: Date.now() })
);
}
return p;
};
window.fetch = patched;
teardowns.push(() => {
window.fetch = orig;
});
}
if (typeof XMLHttpRequest !== "undefined") {
const proto = XMLHttpRequest.prototype;
const origOpen = proto.open;
const origSend = proto.send;
const meta = /* @__PURE__ */ new WeakMap();
proto.open = function open(method, url, ...rest) {
meta.set(this, {
method: String(method ?? "GET").toUpperCase(),
url: typeof url === "string" ? url : url.href
});
return origOpen.call(
this,
method,
url,
...rest
);
};
proto.send = function send(...args) {
const m = meta.get(this);
if (m && !shouldIgnore(m.url)) {
const startTime = Date.now();
this.addEventListener(
"loadend",
() => record({
type: "xhr",
method: m.method,
name: m.url,
status: this.status,
startTime,
endTime: Date.now()
}),
{ once: true }
);
}
return origSend.apply(this, args);
};
teardowns.push(() => {
proto.open = origOpen;
proto.send = origSend;
});
}
return () => {
for (const t of teardowns) {
try {
t();
} catch {
}
}
};
}
};
}
// src/replay.ts
var SENSITIVE_INPUT_TYPES = /* @__PURE__ */ new Set(["password", "email", "tel", "hidden"]);
var SENSITIVE_NAME_RE = /pass|secret|token|ssn|card|cvv|cvc|account|email|phone|tax/i;
var GLASS_BLOCK_SELECTOR = ".glass-block,[data-glass-block]";
var SAMPLING = {
mousemove: 50,
mouseInteraction: true,
scroll: 150,
media: 800,
input: "last"
};
var ReplayRecorder = class {

@@ -141,2 +240,3 @@ constructor(cfg, getSessionId) {

addCustom = null;
boundVisibility = null;
async start() {

@@ -148,2 +248,13 @@ if (this.stop) return;

this.addCustom = typeof rec.addCustomEvent === "function" ? rec.addCustomEvent.bind(record) : null;
const plugins = [];
if (this.cfg.recordConsole) {
try {
const { getRecordConsolePlugin } = await import('@rrweb/rrweb-plugin-console-record');
plugins.push(getRecordConsolePlugin());
} catch {
}
}
if (this.cfg.recordNetwork) {
plugins.push(getGlassNetworkPlugin({ ignoreUrlPrefix: this.cfg.ingestHost }));
}
this.stop = record({

@@ -154,6 +265,9 @@ emit: (event) => {

},
plugins: plugins.length ? plugins : void 0,
// Privacy defaults: never leak user input.
maskAllInputs: this.cfg.maskAllInputs,
maskTextSelector: this.cfg.maskTextSelector,
blockSelector: this.cfg.blockSelector,
// Merge the caller's blockSelector with Glass's always-on block convention.
blockSelector: this.cfg.blockSelector ? `${this.cfg.blockSelector},${GLASS_BLOCK_SELECTOR}` : GLASS_BLOCK_SELECTOR,
sampling: SAMPLING,
// ALWAYS mask sensitive fields regardless of maskAllInputs (defense in depth).

@@ -173,2 +287,11 @@ maskInputFn: (text, element) => {

window.addEventListener("pagehide", this.boundFinal);
if (typeof document !== "undefined") {
this.boundVisibility = () => {
const hidden = document.visibilityState === "hidden";
this.addEvent(hidden ? "$window_hidden" : "$window_visible", {
event: hidden ? "Window became hidden" : "Window became visible"
});
};
document.addEventListener("visibilitychange", this.boundVisibility);
}
}

@@ -233,2 +356,6 @@ /**

}
if (this.boundVisibility && typeof document !== "undefined") {
document.removeEventListener("visibilitychange", this.boundVisibility);
this.boundVisibility = null;
}
this.encoder.dispose();

@@ -586,2 +713,4 @@ }

captureConsole: config.captureConsole ?? false,
recordConsole: config.recordConsole ?? true,
recordNetwork: config.recordNetwork ?? true,
sampleRate: config.sampleRate ?? 1,

@@ -774,3 +903,9 @@ consent: config.consent ?? "implied",

const name = event ?? type;
this.replay.addEvent(name, { event: name, ...type === "error" ? { level: "error" } : {} });
this.replay.addEvent(name, {
event: name,
...type === "error" ? { level: "error" } : {},
// Stamp the URL on pageviews so the player's address bar follows SPA route
// changes — rrweb only emits a meta snapshot on full (hard) page loads.
...name === "$pageview" && typeof location !== "undefined" ? { href: location.href } : {}
});
}

@@ -984,3 +1119,3 @@ }

}
/** Local bucketing via @glassanalytics/core when definitions were provided to init(). */
/** Local bucketing via @glass/core when definitions were provided to init(). */
evaluateLocal(key) {

@@ -987,0 +1122,0 @@ const def = this.cfg.flagDefinitions.find((d) => d.key === key);

+2
-13
{
"name": "@glassanalytics/browser",
"version": "0.1.2",
"version": "0.1.4",
"description": "Glass browser SDK — session replay (rrweb), autocapture, identity, errors and edge-evaluated feature flags. Small, lazy, safe-by-default.",

@@ -18,18 +18,7 @@ "license": "Apache-2.0",

"sideEffects": false,
"scripts": {
"build": "tsup",
"dev": "tsup --watch",
"typecheck": "tsc --noEmit",
"test": "vitest run --passWithNoTests",
"clean": "rm -rf dist .turbo"
},
"dependencies": {
"@glassanalytics/core": "0.1.0",
"@rrweb/rrweb-plugin-console-record": "2.0.1",
"rrweb": "^2.0.0-alpha.18"
},
"devDependencies": {
"tsup": "^8.3.5",
"typescript": "^5.7.2",
"vitest": "^2.1.8"
}
}

Sorry, the diff of this file is too big to display