@guckdev/browser 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,40 @@
1
+ # Guck (Browser SDK)
2
+
3
+ Browser SDK for emitting Guck telemetry to a local MCP HTTP ingest endpoint.
4
+
5
+ ## Usage
6
+
7
+ Start the MCP server with HTTP ingest enabled:
8
+
9
+ ```sh
10
+ guck mcp --http-port 7331
11
+ ```
12
+
13
+ Create a client and emit events:
14
+
15
+ ```ts
16
+ import { createBrowserClient } from "@guckdev/browser";
17
+
18
+ const client = createBrowserClient({
19
+ endpoint: "http://localhost:7331/guck/emit",
20
+ service: "web-ui",
21
+ sessionId: "dev-1",
22
+ });
23
+
24
+ await client.emit({ message: "hello from the browser" });
25
+ ```
26
+
27
+ ## Auto-capture console + errors
28
+
29
+ ```ts
30
+ const { stop } = client.installAutoCapture();
31
+
32
+ console.error("boom");
33
+
34
+ // call stop() to restore console and listeners
35
+ stop();
36
+ ```
37
+
38
+ Notes:
39
+ - The HTTP ingest endpoint is CORS-enabled by default.
40
+ - If your page is served over HTTPS, posting to an HTTP localhost endpoint may be blocked by mixed-content rules.
@@ -0,0 +1,46 @@
1
+ type GuckLevel = "trace" | "debug" | "info" | "warn" | "error" | "fatal";
2
+ type GuckSource = {
3
+ kind: "sdk";
4
+ file?: string;
5
+ line?: number;
6
+ };
7
+ type GuckEvent = {
8
+ id: string;
9
+ ts: string;
10
+ level: GuckLevel;
11
+ type: string;
12
+ service: string;
13
+ run_id: string;
14
+ session_id?: string;
15
+ message?: string;
16
+ data?: Record<string, unknown>;
17
+ tags?: Record<string, string>;
18
+ trace_id?: string;
19
+ span_id?: string;
20
+ source?: GuckSource;
21
+ };
22
+ type BrowserClientOptions = {
23
+ endpoint: string;
24
+ service?: string;
25
+ sessionId?: string;
26
+ runId?: string;
27
+ tags?: Record<string, string>;
28
+ headers?: Record<string, string>;
29
+ enabled?: boolean;
30
+ keepalive?: boolean;
31
+ fetch?: typeof fetch;
32
+ onError?: (error: unknown) => void;
33
+ };
34
+ type AutoCaptureOptions = {
35
+ captureConsole?: boolean;
36
+ captureErrors?: boolean;
37
+ };
38
+ type BrowserClient = {
39
+ emit: (event: Partial<GuckEvent>) => Promise<void>;
40
+ installAutoCapture: (opts?: AutoCaptureOptions) => {
41
+ stop: () => void;
42
+ };
43
+ };
44
+ export declare const createBrowserClient: (options: BrowserClientOptions) => BrowserClient;
45
+ export type { GuckEvent, GuckLevel, BrowserClientOptions, AutoCaptureOptions, BrowserClient };
46
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,KAAK,SAAS,GAAG,OAAO,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,OAAO,CAAC;AAEzE,KAAK,UAAU,GAAG;IAChB,IAAI,EAAE,KAAK,CAAC;IACZ,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,KAAK,SAAS,GAAG;IACf,EAAE,EAAE,MAAM,CAAC;IACX,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,SAAS,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC9B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,UAAU,CAAC;CACrB,CAAC;AAEF,KAAK,oBAAoB,GAAG;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC9B,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;IACrB,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;CACpC,CAAC;AAEF,KAAK,kBAAkB,GAAG;IACxB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB,CAAC;AAEF,KAAK,aAAa,GAAG;IACnB,IAAI,EAAE,CAAC,KAAK,EAAE,OAAO,CAAC,SAAS,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACnD,kBAAkB,EAAE,CAAC,IAAI,CAAC,EAAE,kBAAkB,KAAK;QAAE,IAAI,EAAE,MAAM,IAAI,CAAA;KAAE,CAAC;CACzE,CAAC;AAgIF,eAAO,MAAM,mBAAmB,YAAa,oBAAoB,KAAG,aAoJnE,CAAC;AAEF,YAAY,EAAE,SAAS,EAAE,SAAS,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,aAAa,EAAE,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,246 @@
1
+ const normalizeLevel = (level) => {
2
+ if (!level) {
3
+ return "info";
4
+ }
5
+ const lower = level.toLowerCase();
6
+ if (lower === "trace" ||
7
+ lower === "debug" ||
8
+ lower === "info" ||
9
+ lower === "warn" ||
10
+ lower === "error" ||
11
+ lower === "fatal") {
12
+ return lower;
13
+ }
14
+ return "info";
15
+ };
16
+ const randomId = () => {
17
+ if (typeof crypto !== "undefined" && "randomUUID" in crypto) {
18
+ return crypto.randomUUID();
19
+ }
20
+ return `guck-${Math.random().toString(16).slice(2)}${Date.now().toString(16)}`;
21
+ };
22
+ const serializeError = (value) => {
23
+ if (!value) {
24
+ return undefined;
25
+ }
26
+ if (value instanceof Error) {
27
+ return {
28
+ name: value.name,
29
+ message: value.message,
30
+ stack: value.stack,
31
+ };
32
+ }
33
+ if (typeof value === "object") {
34
+ const record = value;
35
+ return {
36
+ name: typeof record.name === "string" ? record.name : undefined,
37
+ message: typeof record.message === "string" ? record.message : undefined,
38
+ stack: typeof record.stack === "string" ? record.stack : undefined,
39
+ };
40
+ }
41
+ return { message: String(value) };
42
+ };
43
+ const toSerializable = (value, seen) => {
44
+ if (value === null || value === undefined) {
45
+ return value;
46
+ }
47
+ if (value instanceof Error) {
48
+ return serializeError(value);
49
+ }
50
+ if (value instanceof Date) {
51
+ return value.toISOString();
52
+ }
53
+ if (typeof value === "bigint") {
54
+ return value.toString();
55
+ }
56
+ if (typeof value === "function") {
57
+ return `[Function${value.name ? ` ${value.name}` : ""}]`;
58
+ }
59
+ if (typeof value !== "object") {
60
+ return value;
61
+ }
62
+ if (seen.has(value)) {
63
+ return "[Circular]";
64
+ }
65
+ seen.add(value);
66
+ if (Array.isArray(value)) {
67
+ return value.map((entry) => toSerializable(entry, seen));
68
+ }
69
+ const output = {};
70
+ for (const [key, entry] of Object.entries(value)) {
71
+ output[key] = toSerializable(entry, seen);
72
+ }
73
+ return output;
74
+ };
75
+ const formatArg = (value) => {
76
+ if (typeof value === "string") {
77
+ return value;
78
+ }
79
+ if (value instanceof Error) {
80
+ return value.stack ?? value.message;
81
+ }
82
+ try {
83
+ return JSON.stringify(toSerializable(value, new WeakSet())) ?? "";
84
+ }
85
+ catch {
86
+ return String(value);
87
+ }
88
+ };
89
+ const formatArgs = (args) => {
90
+ if (!args.length) {
91
+ return "";
92
+ }
93
+ return args.map((arg) => formatArg(arg)).join(" ");
94
+ };
95
+ const mapConsoleLevel = (method) => {
96
+ switch (method) {
97
+ case "trace":
98
+ return "trace";
99
+ case "debug":
100
+ return "debug";
101
+ case "warn":
102
+ return "warn";
103
+ case "error":
104
+ return "error";
105
+ case "info":
106
+ case "log":
107
+ default:
108
+ return "info";
109
+ }
110
+ };
111
+ export const createBrowserClient = (options) => {
112
+ if (!options?.endpoint) {
113
+ throw new Error("[guck] endpoint is required");
114
+ }
115
+ const endpoint = options.endpoint;
116
+ const service = options.service ?? "guck";
117
+ const sessionId = options.sessionId;
118
+ const runId = options.runId ?? randomId();
119
+ const tags = options.tags;
120
+ const headers = options.headers ?? {};
121
+ const enabled = options.enabled ?? true;
122
+ const keepalive = options.keepalive ?? true;
123
+ const fetcher = options.fetch ?? fetch;
124
+ const onError = options.onError;
125
+ const emit = async (input) => {
126
+ if (!enabled) {
127
+ return;
128
+ }
129
+ const event = {
130
+ id: input.id ?? randomId(),
131
+ ts: input.ts ?? new Date().toISOString(),
132
+ level: normalizeLevel(input.level),
133
+ type: input.type ?? "log",
134
+ service: input.service ?? service,
135
+ run_id: input.run_id ?? runId,
136
+ session_id: input.session_id ?? sessionId,
137
+ message: input.message,
138
+ data: input.data,
139
+ tags: input.tags ?? tags,
140
+ trace_id: input.trace_id,
141
+ span_id: input.span_id,
142
+ source: input.source ?? { kind: "sdk" },
143
+ };
144
+ const response = await fetcher(endpoint, {
145
+ method: "POST",
146
+ headers: {
147
+ "content-type": "application/json",
148
+ ...headers,
149
+ },
150
+ body: JSON.stringify(event),
151
+ keepalive,
152
+ });
153
+ if (!response.ok) {
154
+ throw new Error(`[guck] HTTP ${response.status} ${response.statusText}`);
155
+ }
156
+ };
157
+ const installAutoCapture = (opts = {}) => {
158
+ const captureConsole = opts.captureConsole ?? true;
159
+ const captureErrors = opts.captureErrors ?? true;
160
+ const targetWindow = typeof window === "undefined" ? undefined : window;
161
+ if (!captureConsole && !captureErrors) {
162
+ return { stop: () => { } };
163
+ }
164
+ const originals = {};
165
+ const listeners = [];
166
+ const safeEmit = (payload) => {
167
+ void emit(payload).catch((error) => {
168
+ if (onError) {
169
+ onError(error);
170
+ }
171
+ });
172
+ };
173
+ if (captureConsole) {
174
+ const methods = ["log", "info", "warn", "error", "debug", "trace"];
175
+ for (const method of methods) {
176
+ const original = console[method];
177
+ originals[method] = original;
178
+ console[method] = (...args) => {
179
+ original.apply(console, args);
180
+ safeEmit({
181
+ type: "console",
182
+ level: mapConsoleLevel(method),
183
+ message: formatArgs(args),
184
+ data: { args: args.map((arg) => toSerializable(arg, new WeakSet())) },
185
+ });
186
+ };
187
+ }
188
+ }
189
+ if (captureErrors && targetWindow) {
190
+ const errorListener = (event) => {
191
+ const message = event.message || event.error?.message || "";
192
+ const source = {
193
+ kind: "sdk",
194
+ file: event.filename || undefined,
195
+ line: typeof event.lineno === "number" ? event.lineno : undefined,
196
+ };
197
+ safeEmit({
198
+ type: "window.error",
199
+ level: "error",
200
+ message,
201
+ source,
202
+ data: {
203
+ filename: event.filename || undefined,
204
+ lineno: typeof event.lineno === "number" ? event.lineno : undefined,
205
+ colno: typeof event.colno === "number" ? event.colno : undefined,
206
+ error: serializeError(event.error),
207
+ },
208
+ });
209
+ };
210
+ targetWindow.addEventListener("error", errorListener);
211
+ listeners.push(() => targetWindow.removeEventListener("error", errorListener));
212
+ const rejectionListener = (event) => {
213
+ const reason = event.reason;
214
+ const message = typeof reason === "string"
215
+ ? reason
216
+ : reason instanceof Error
217
+ ? reason.message
218
+ : formatArg(reason);
219
+ safeEmit({
220
+ type: "unhandledrejection",
221
+ level: "error",
222
+ message,
223
+ data: {
224
+ reason: toSerializable(reason, new WeakSet()),
225
+ },
226
+ });
227
+ };
228
+ targetWindow.addEventListener("unhandledrejection", rejectionListener);
229
+ listeners.push(() => targetWindow.removeEventListener("unhandledrejection", rejectionListener));
230
+ }
231
+ const stop = () => {
232
+ for (const method of Object.keys(originals)) {
233
+ const original = originals[method];
234
+ if (original) {
235
+ console[method] = original;
236
+ }
237
+ }
238
+ for (const remove of listeners) {
239
+ remove();
240
+ }
241
+ };
242
+ return { stop };
243
+ };
244
+ return { emit, installAutoCapture };
245
+ };
246
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "@guckdev/browser",
3
+ "version": "0.1.0",
4
+ "description": "Guck browser SDK",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "https://github.com/tillkolter/guck-mcp"
8
+ },
9
+ "license": "MIT",
10
+ "type": "module",
11
+ "exports": {
12
+ ".": {
13
+ "types": "./dist/index.d.ts",
14
+ "import": "./dist/index.js"
15
+ }
16
+ },
17
+ "files": [
18
+ "dist",
19
+ "src",
20
+ "tsconfig.json"
21
+ ],
22
+ "publishConfig": {
23
+ "access": "public"
24
+ },
25
+ "scripts": {
26
+ "build": "tsc -p tsconfig.json",
27
+ "build:watch": "tsc -p tsconfig.json --watch",
28
+ "test": "pnpm run build && node --test test/*.test.js",
29
+ "prepare": "pnpm run build"
30
+ },
31
+ "engines": {
32
+ "node": ">=18"
33
+ },
34
+ "devDependencies": {
35
+ "@types/node": "25.2.2"
36
+ }
37
+ }
package/src/index.ts ADDED
@@ -0,0 +1,324 @@
1
+ type GuckLevel = "trace" | "debug" | "info" | "warn" | "error" | "fatal";
2
+
3
+ type GuckSource = {
4
+ kind: "sdk";
5
+ file?: string;
6
+ line?: number;
7
+ };
8
+
9
+ type GuckEvent = {
10
+ id: string;
11
+ ts: string;
12
+ level: GuckLevel;
13
+ type: string;
14
+ service: string;
15
+ run_id: string;
16
+ session_id?: string;
17
+ message?: string;
18
+ data?: Record<string, unknown>;
19
+ tags?: Record<string, string>;
20
+ trace_id?: string;
21
+ span_id?: string;
22
+ source?: GuckSource;
23
+ };
24
+
25
+ type BrowserClientOptions = {
26
+ endpoint: string;
27
+ service?: string;
28
+ sessionId?: string;
29
+ runId?: string;
30
+ tags?: Record<string, string>;
31
+ headers?: Record<string, string>;
32
+ enabled?: boolean;
33
+ keepalive?: boolean;
34
+ fetch?: typeof fetch;
35
+ onError?: (error: unknown) => void;
36
+ };
37
+
38
+ type AutoCaptureOptions = {
39
+ captureConsole?: boolean;
40
+ captureErrors?: boolean;
41
+ };
42
+
43
+ type BrowserClient = {
44
+ emit: (event: Partial<GuckEvent>) => Promise<void>;
45
+ installAutoCapture: (opts?: AutoCaptureOptions) => { stop: () => void };
46
+ };
47
+
48
+ type ConsoleMethod = "log" | "info" | "warn" | "error" | "debug" | "trace";
49
+
50
+ type ErrorPayload = {
51
+ name?: string;
52
+ message?: string;
53
+ stack?: string;
54
+ };
55
+
56
+ const normalizeLevel = (level?: string): GuckLevel => {
57
+ if (!level) {
58
+ return "info";
59
+ }
60
+ const lower = level.toLowerCase();
61
+ if (
62
+ lower === "trace" ||
63
+ lower === "debug" ||
64
+ lower === "info" ||
65
+ lower === "warn" ||
66
+ lower === "error" ||
67
+ lower === "fatal"
68
+ ) {
69
+ return lower;
70
+ }
71
+ return "info";
72
+ };
73
+
74
+ const randomId = (): string => {
75
+ if (typeof crypto !== "undefined" && "randomUUID" in crypto) {
76
+ return crypto.randomUUID();
77
+ }
78
+ return `guck-${Math.random().toString(16).slice(2)}${Date.now().toString(16)}`;
79
+ };
80
+
81
+ const serializeError = (value: unknown): ErrorPayload | undefined => {
82
+ if (!value) {
83
+ return undefined;
84
+ }
85
+ if (value instanceof Error) {
86
+ return {
87
+ name: value.name,
88
+ message: value.message,
89
+ stack: value.stack,
90
+ };
91
+ }
92
+ if (typeof value === "object") {
93
+ const record = value as Record<string, unknown>;
94
+ return {
95
+ name: typeof record.name === "string" ? record.name : undefined,
96
+ message: typeof record.message === "string" ? record.message : undefined,
97
+ stack: typeof record.stack === "string" ? record.stack : undefined,
98
+ };
99
+ }
100
+ return { message: String(value) };
101
+ };
102
+
103
+ const toSerializable = (value: unknown, seen: WeakSet<object>): unknown => {
104
+ if (value === null || value === undefined) {
105
+ return value;
106
+ }
107
+ if (value instanceof Error) {
108
+ return serializeError(value);
109
+ }
110
+ if (value instanceof Date) {
111
+ return value.toISOString();
112
+ }
113
+ if (typeof value === "bigint") {
114
+ return value.toString();
115
+ }
116
+ if (typeof value === "function") {
117
+ return `[Function${value.name ? ` ${value.name}` : ""}]`;
118
+ }
119
+ if (typeof value !== "object") {
120
+ return value;
121
+ }
122
+ if (seen.has(value)) {
123
+ return "[Circular]";
124
+ }
125
+ seen.add(value);
126
+ if (Array.isArray(value)) {
127
+ return value.map((entry) => toSerializable(entry, seen));
128
+ }
129
+ const output: Record<string, unknown> = {};
130
+ for (const [key, entry] of Object.entries(value)) {
131
+ output[key] = toSerializable(entry, seen);
132
+ }
133
+ return output;
134
+ };
135
+
136
+ const formatArg = (value: unknown): string => {
137
+ if (typeof value === "string") {
138
+ return value;
139
+ }
140
+ if (value instanceof Error) {
141
+ return value.stack ?? value.message;
142
+ }
143
+ try {
144
+ return JSON.stringify(toSerializable(value, new WeakSet<object>())) ?? "";
145
+ } catch {
146
+ return String(value);
147
+ }
148
+ };
149
+
150
+ const formatArgs = (args: unknown[]): string => {
151
+ if (!args.length) {
152
+ return "";
153
+ }
154
+ return args.map((arg) => formatArg(arg)).join(" ");
155
+ };
156
+
157
+ const mapConsoleLevel = (method: ConsoleMethod): GuckLevel => {
158
+ switch (method) {
159
+ case "trace":
160
+ return "trace";
161
+ case "debug":
162
+ return "debug";
163
+ case "warn":
164
+ return "warn";
165
+ case "error":
166
+ return "error";
167
+ case "info":
168
+ case "log":
169
+ default:
170
+ return "info";
171
+ }
172
+ };
173
+
174
+ export const createBrowserClient = (options: BrowserClientOptions): BrowserClient => {
175
+ if (!options?.endpoint) {
176
+ throw new Error("[guck] endpoint is required");
177
+ }
178
+ const endpoint = options.endpoint;
179
+ const service = options.service ?? "guck";
180
+ const sessionId = options.sessionId;
181
+ const runId = options.runId ?? randomId();
182
+ const tags = options.tags;
183
+ const headers = options.headers ?? {};
184
+ const enabled = options.enabled ?? true;
185
+ const keepalive = options.keepalive ?? true;
186
+ const fetcher = options.fetch ?? fetch;
187
+ const onError = options.onError;
188
+
189
+ const emit = async (input: Partial<GuckEvent>): Promise<void> => {
190
+ if (!enabled) {
191
+ return;
192
+ }
193
+ const event: GuckEvent = {
194
+ id: input.id ?? randomId(),
195
+ ts: input.ts ?? new Date().toISOString(),
196
+ level: normalizeLevel(input.level),
197
+ type: input.type ?? "log",
198
+ service: input.service ?? service,
199
+ run_id: input.run_id ?? runId,
200
+ session_id: input.session_id ?? sessionId,
201
+ message: input.message,
202
+ data: input.data,
203
+ tags: input.tags ?? tags,
204
+ trace_id: input.trace_id,
205
+ span_id: input.span_id,
206
+ source: input.source ?? { kind: "sdk" },
207
+ };
208
+
209
+ const response = await fetcher(endpoint, {
210
+ method: "POST",
211
+ headers: {
212
+ "content-type": "application/json",
213
+ ...headers,
214
+ },
215
+ body: JSON.stringify(event),
216
+ keepalive,
217
+ });
218
+
219
+ if (!response.ok) {
220
+ throw new Error(`[guck] HTTP ${response.status} ${response.statusText}`);
221
+ }
222
+ };
223
+
224
+ const installAutoCapture = (opts: AutoCaptureOptions = {}): { stop: () => void } => {
225
+ const captureConsole = opts.captureConsole ?? true;
226
+ const captureErrors = opts.captureErrors ?? true;
227
+ const targetWindow = typeof window === "undefined" ? undefined : window;
228
+
229
+ if (!captureConsole && !captureErrors) {
230
+ return { stop: () => {} };
231
+ }
232
+
233
+ const originals: Partial<Record<ConsoleMethod, (...args: unknown[]) => void>> = {};
234
+ const listeners: Array<() => void> = [];
235
+
236
+ const safeEmit = (payload: Partial<GuckEvent>) => {
237
+ void emit(payload).catch((error) => {
238
+ if (onError) {
239
+ onError(error);
240
+ }
241
+ });
242
+ };
243
+
244
+ if (captureConsole) {
245
+ const methods: ConsoleMethod[] = ["log", "info", "warn", "error", "debug", "trace"];
246
+ for (const method of methods) {
247
+ const original = console[method];
248
+ originals[method] = original;
249
+ console[method] = (...args: unknown[]) => {
250
+ original.apply(console, args);
251
+ safeEmit({
252
+ type: "console",
253
+ level: mapConsoleLevel(method),
254
+ message: formatArgs(args),
255
+ data: { args: args.map((arg) => toSerializable(arg, new WeakSet<object>())) },
256
+ });
257
+ };
258
+ }
259
+ }
260
+
261
+ if (captureErrors && targetWindow) {
262
+ const errorListener = (event: ErrorEvent) => {
263
+ const message = event.message || event.error?.message || "";
264
+ const source: GuckSource = {
265
+ kind: "sdk",
266
+ file: event.filename || undefined,
267
+ line: typeof event.lineno === "number" ? event.lineno : undefined,
268
+ };
269
+ safeEmit({
270
+ type: "window.error",
271
+ level: "error",
272
+ message,
273
+ source,
274
+ data: {
275
+ filename: event.filename || undefined,
276
+ lineno: typeof event.lineno === "number" ? event.lineno : undefined,
277
+ colno: typeof event.colno === "number" ? event.colno : undefined,
278
+ error: serializeError(event.error),
279
+ },
280
+ });
281
+ };
282
+ targetWindow.addEventListener("error", errorListener);
283
+ listeners.push(() => targetWindow.removeEventListener("error", errorListener));
284
+
285
+ const rejectionListener = (event: PromiseRejectionEvent) => {
286
+ const reason = event.reason;
287
+ const message =
288
+ typeof reason === "string"
289
+ ? reason
290
+ : reason instanceof Error
291
+ ? reason.message
292
+ : formatArg(reason);
293
+ safeEmit({
294
+ type: "unhandledrejection",
295
+ level: "error",
296
+ message,
297
+ data: {
298
+ reason: toSerializable(reason, new WeakSet<object>()),
299
+ },
300
+ });
301
+ };
302
+ targetWindow.addEventListener("unhandledrejection", rejectionListener);
303
+ listeners.push(() => targetWindow.removeEventListener("unhandledrejection", rejectionListener));
304
+ }
305
+
306
+ const stop = () => {
307
+ for (const method of Object.keys(originals) as ConsoleMethod[]) {
308
+ const original = originals[method];
309
+ if (original) {
310
+ console[method] = original;
311
+ }
312
+ }
313
+ for (const remove of listeners) {
314
+ remove();
315
+ }
316
+ };
317
+
318
+ return { stop };
319
+ };
320
+
321
+ return { emit, installAutoCapture };
322
+ };
323
+
324
+ export type { GuckEvent, GuckLevel, BrowserClientOptions, AutoCaptureOptions, BrowserClient };
package/tsconfig.json ADDED
@@ -0,0 +1,20 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "NodeNext",
5
+ "moduleResolution": "NodeNext",
6
+ "rootDir": "src",
7
+ "outDir": "dist",
8
+ "declaration": true,
9
+ "declarationMap": true,
10
+ "sourceMap": true,
11
+ "strict": true,
12
+ "noUncheckedIndexedAccess": true,
13
+ "esModuleInterop": true,
14
+ "forceConsistentCasingInFileNames": true,
15
+ "skipLibCheck": true,
16
+ "lib": ["DOM", "ES2022"],
17
+ "types": []
18
+ },
19
+ "include": ["src/**/*.ts"]
20
+ }