@crashlensorg/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.
@@ -0,0 +1,24 @@
1
+ import { CrashOptions, CrashLens } from '@crashlensorg/core';
2
+ export { Breadcrumb, CaptureParams, CrashLens, CrashLensUser, CrashOptions, DeviceInfo, Severity } from '@crashlensorg/core';
3
+
4
+ interface BrowserInitOptions extends CrashOptions {
5
+ /** Captura window.onerror/unhandledrejection (padrão: true). */
6
+ enableGlobalHandlers?: boolean;
7
+ /** Breadcrumbs de fetch/XHR (padrão: true). */
8
+ enableHttpBreadcrumbs?: boolean;
9
+ /** Breadcrumbs de console (padrão: true). */
10
+ enableConsoleBreadcrumbs?: boolean;
11
+ /** Breadcrumbs de navegação (padrão: true). */
12
+ enableNavigationBreadcrumbs?: boolean;
13
+ /** Coleta info do browser automaticamente (padrão: true). */
14
+ collectBrowserInfo?: boolean;
15
+ /** Rastreia sessões (padrão: true). */
16
+ enableSessionTracking?: boolean;
17
+ }
18
+ interface BrowserInstance extends CrashLens {
19
+ /** Remove listeners e restaura os instrumentos do browser. */
20
+ cleanup(): void;
21
+ }
22
+ declare function initBrowser(options: BrowserInitOptions): BrowserInstance;
23
+
24
+ export { type BrowserInitOptions, type BrowserInstance, initBrowser };
@@ -0,0 +1,24 @@
1
+ import { CrashOptions, CrashLens } from '@crashlensorg/core';
2
+ export { Breadcrumb, CaptureParams, CrashLens, CrashLensUser, CrashOptions, DeviceInfo, Severity } from '@crashlensorg/core';
3
+
4
+ interface BrowserInitOptions extends CrashOptions {
5
+ /** Captura window.onerror/unhandledrejection (padrão: true). */
6
+ enableGlobalHandlers?: boolean;
7
+ /** Breadcrumbs de fetch/XHR (padrão: true). */
8
+ enableHttpBreadcrumbs?: boolean;
9
+ /** Breadcrumbs de console (padrão: true). */
10
+ enableConsoleBreadcrumbs?: boolean;
11
+ /** Breadcrumbs de navegação (padrão: true). */
12
+ enableNavigationBreadcrumbs?: boolean;
13
+ /** Coleta info do browser automaticamente (padrão: true). */
14
+ collectBrowserInfo?: boolean;
15
+ /** Rastreia sessões (padrão: true). */
16
+ enableSessionTracking?: boolean;
17
+ }
18
+ interface BrowserInstance extends CrashLens {
19
+ /** Remove listeners e restaura os instrumentos do browser. */
20
+ cleanup(): void;
21
+ }
22
+ declare function initBrowser(options: BrowserInitOptions): BrowserInstance;
23
+
24
+ export { type BrowserInitOptions, type BrowserInstance, initBrowser };
package/dist/index.js ADDED
@@ -0,0 +1,279 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ CrashLens: () => import_core.CrashLens,
24
+ initBrowser: () => initBrowser
25
+ });
26
+ module.exports = __toCommonJS(index_exports);
27
+ var import_core = require("@crashlensorg/core");
28
+
29
+ // src/device.ts
30
+ function parseUserAgent(ua) {
31
+ const nameMap = [
32
+ [/Edg\//, "Edge"],
33
+ [/OPR\//, "Opera"],
34
+ [/Chrome\//, "Chrome"],
35
+ [/Firefox\//, "Firefox"],
36
+ [/Safari\//, "Safari"],
37
+ [/MSIE /, "IE"],
38
+ [/Trident\//, "IE"]
39
+ ];
40
+ for (const [re, name] of nameMap) {
41
+ if (re.test(ua)) {
42
+ const part = ua.split(" ").find((p) => re.test(p));
43
+ const version = part?.split("/")[1]?.match(/^[\d.]+/)?.[0];
44
+ return { browserName: name, browserVersion: version };
45
+ }
46
+ }
47
+ return {};
48
+ }
49
+ var STORAGE_KEY = "crashlens_device_id";
50
+ function getOrCreateDeviceId() {
51
+ try {
52
+ const existing = localStorage.getItem(STORAGE_KEY);
53
+ if (existing) return existing;
54
+ const id = typeof crypto !== "undefined" && typeof crypto.randomUUID === "function" ? crypto.randomUUID() : Math.random().toString(36).slice(2);
55
+ localStorage.setItem(STORAGE_KEY, id);
56
+ return id;
57
+ } catch {
58
+ return Math.random().toString(36).slice(2);
59
+ }
60
+ }
61
+ function collectBrowserInfo() {
62
+ const nav = navigator;
63
+ const ua = nav?.userAgent ?? "";
64
+ const { browserName, browserVersion } = parseUserAgent(ua);
65
+ const web = {
66
+ browserName,
67
+ browserVersion,
68
+ userAgent: ua,
69
+ platform: nav?.platform,
70
+ language: nav?.language,
71
+ vendor: nav?.vendor,
72
+ screenResolution: typeof screen !== "undefined" ? `${screen.width}x${screen.height}` : void 0
73
+ };
74
+ return {
75
+ deviceId: getOrCreateDeviceId(),
76
+ deviceOs: "web",
77
+ deviceType: "web",
78
+ deviceName: browserName,
79
+ deviceModel: browserName,
80
+ cpuArchitecture: void 0,
81
+ locale: nav?.language,
82
+ screenResolution: web.screenResolution,
83
+ platformInfo: { web }
84
+ };
85
+ }
86
+
87
+ // src/integrations.ts
88
+ function installGlobalErrorHandlers(hooks) {
89
+ const onError = (event) => {
90
+ let error = event.error;
91
+ if (!(error instanceof Error) && event.message) {
92
+ error = new Error(event.message);
93
+ error.colno = event.colno;
94
+ }
95
+ hooks.onError(error ?? new Error(event.message || "window.onerror"), {
96
+ source: event.filename,
97
+ line: event.lineno,
98
+ column: event.colno
99
+ });
100
+ };
101
+ const onRejection = (event) => {
102
+ hooks.onUnhandledRejection(event.reason ?? event.reason, {});
103
+ };
104
+ window.addEventListener("error", onError);
105
+ window.addEventListener("unhandledrejection", onRejection);
106
+ return () => {
107
+ window.removeEventListener("error", onError);
108
+ window.removeEventListener("unhandledrejection", onRejection);
109
+ };
110
+ }
111
+ function initGlobalHandlers(client) {
112
+ return installGlobalErrorHandlers({
113
+ onError: (error, ctx) => {
114
+ client.captureException(error, {
115
+ handled: false,
116
+ context: "window.onerror",
117
+ tags: { source: "window.onerror" },
118
+ extra: { ...ctx }
119
+ });
120
+ },
121
+ onUnhandledRejection: (reason, _ctx) => {
122
+ client.captureException(reason, {
123
+ handled: false,
124
+ context: "unhandledrejection",
125
+ tags: { source: "unhandledrejection" }
126
+ });
127
+ }
128
+ });
129
+ }
130
+
131
+ // src/http-breadcrumbs.ts
132
+ var xhrMeta = /* @__PURE__ */ new WeakMap();
133
+ function instrumentHttpBreadcrumbs(client) {
134
+ const addRequest = (method, url, status) => {
135
+ client.addBreadcrumb({
136
+ message: `${method} ${url}`,
137
+ type: "request",
138
+ category: "http",
139
+ data: { status, url }
140
+ });
141
+ };
142
+ const originalFetch = window.fetch.bind(window);
143
+ const wrappedFetch = (input, init) => {
144
+ let method = init?.method ?? "GET";
145
+ let url;
146
+ if (typeof input === "string") {
147
+ url = input;
148
+ } else if (input instanceof URL) {
149
+ url = input.toString();
150
+ } else {
151
+ url = input.url;
152
+ method = input.method ?? method;
153
+ }
154
+ const promise = originalFetch(input, init);
155
+ promise.then((res) => {
156
+ addRequest(method, url, res.status);
157
+ return res;
158
+ }).catch(() => addRequest(method, url));
159
+ return promise;
160
+ };
161
+ window.fetch = wrappedFetch;
162
+ const originalOpen = XMLHttpRequest.prototype.open;
163
+ const originalSend = XMLHttpRequest.prototype.send;
164
+ XMLHttpRequest.prototype.open = function(method, url) {
165
+ xhrMeta.set(this, { method, url });
166
+ originalOpen.call(this, method, url, true);
167
+ };
168
+ XMLHttpRequest.prototype.send = function(body) {
169
+ this.addEventListener("loadend", () => {
170
+ const meta = xhrMeta.get(this);
171
+ if (meta) addRequest(meta.method ?? "GET", meta.url ?? "", this.status);
172
+ });
173
+ originalSend.call(this, body);
174
+ };
175
+ return () => {
176
+ window.fetch = originalFetch;
177
+ XMLHttpRequest.prototype.open = originalOpen;
178
+ XMLHttpRequest.prototype.send = originalSend;
179
+ };
180
+ }
181
+
182
+ // src/console-breadcrumbs.ts
183
+ function instrumentConsoleBreadcrumbs(client) {
184
+ const restore = [];
185
+ const names = ["log", "error", "warn", "info", "debug", "trace"];
186
+ for (const name of names) {
187
+ const original = console[name]?.bind(console);
188
+ if (!original) continue;
189
+ const level = name === "error" ? "error" : name === "warn" ? "warning" : name === "info" ? "info" : "debug";
190
+ const wrapped = (...args) => {
191
+ const message = args.map((a) => {
192
+ if (a instanceof Error) return a.stack || a.message;
193
+ if (typeof a === "string") return a;
194
+ try {
195
+ return JSON.stringify(a);
196
+ } catch {
197
+ return String(a);
198
+ }
199
+ }).join(" ");
200
+ client.addBreadcrumb({
201
+ message: message || level,
202
+ type: "log",
203
+ category: "console",
204
+ data: { level, args }
205
+ });
206
+ return original(...args);
207
+ };
208
+ console[name] = wrapped;
209
+ restore.push(() => {
210
+ console[name] = original;
211
+ });
212
+ }
213
+ return () => {
214
+ for (const r of restore) r();
215
+ };
216
+ }
217
+
218
+ // src/navigation-breadcrumbs.ts
219
+ function instrumentNavigationBreadcrumbs(client) {
220
+ if (typeof window === "undefined" || !window.history) return () => void 0;
221
+ const add = (url) => {
222
+ client.addBreadcrumb({ message: url, type: "navigation", category: "navigation" });
223
+ };
224
+ const originalPush = window.history.pushState.bind(window.history);
225
+ const originalReplace = window.history.replaceState.bind(window.history);
226
+ window.history.pushState = (data, unused, url) => {
227
+ originalPush(data, unused, url);
228
+ add(window.location.href);
229
+ };
230
+ window.history.replaceState = (data, unused, url) => {
231
+ originalReplace(data, unused, url);
232
+ add(window.location.href);
233
+ };
234
+ return () => {
235
+ window.history.pushState = originalPush;
236
+ window.history.replaceState = originalReplace;
237
+ };
238
+ }
239
+
240
+ // src/index.ts
241
+ function initBrowser(options) {
242
+ const deviceInfo = options.deviceInfo ?? (options.collectBrowserInfo === false ? void 0 : collectBrowserInfo());
243
+ const client = new import_core.CrashLens({
244
+ ...options,
245
+ runtime: options.runtime ?? "browser",
246
+ runtimeVersion: options.runtimeVersion,
247
+ deviceInfo,
248
+ enableSessionTracking: options.enableSessionTracking ?? true
249
+ });
250
+ const cleanups = [];
251
+ if (options.enableGlobalHandlers !== false) {
252
+ cleanups.push(initGlobalHandlers(client));
253
+ }
254
+ if (options.enableHttpBreadcrumbs !== false) {
255
+ cleanups.push(instrumentHttpBreadcrumbs(client));
256
+ }
257
+ if (options.enableConsoleBreadcrumbs !== false) {
258
+ cleanups.push(instrumentConsoleBreadcrumbs(client));
259
+ }
260
+ if (options.enableNavigationBreadcrumbs !== false) {
261
+ cleanups.push(instrumentNavigationBreadcrumbs(client));
262
+ }
263
+ void client.init();
264
+ const instance = client;
265
+ instance.cleanup = () => {
266
+ for (const c of cleanups) {
267
+ try {
268
+ c();
269
+ } catch {
270
+ }
271
+ }
272
+ };
273
+ return instance;
274
+ }
275
+ // Annotate the CommonJS export names for ESM import in node:
276
+ 0 && (module.exports = {
277
+ CrashLens,
278
+ initBrowser
279
+ });
package/dist/index.mjs ADDED
@@ -0,0 +1,253 @@
1
+ // src/index.ts
2
+ import { CrashLens } from "@crashlensorg/core";
3
+
4
+ // src/device.ts
5
+ function parseUserAgent(ua) {
6
+ const nameMap = [
7
+ [/Edg\//, "Edge"],
8
+ [/OPR\//, "Opera"],
9
+ [/Chrome\//, "Chrome"],
10
+ [/Firefox\//, "Firefox"],
11
+ [/Safari\//, "Safari"],
12
+ [/MSIE /, "IE"],
13
+ [/Trident\//, "IE"]
14
+ ];
15
+ for (const [re, name] of nameMap) {
16
+ if (re.test(ua)) {
17
+ const part = ua.split(" ").find((p) => re.test(p));
18
+ const version = part?.split("/")[1]?.match(/^[\d.]+/)?.[0];
19
+ return { browserName: name, browserVersion: version };
20
+ }
21
+ }
22
+ return {};
23
+ }
24
+ var STORAGE_KEY = "crashlens_device_id";
25
+ function getOrCreateDeviceId() {
26
+ try {
27
+ const existing = localStorage.getItem(STORAGE_KEY);
28
+ if (existing) return existing;
29
+ const id = typeof crypto !== "undefined" && typeof crypto.randomUUID === "function" ? crypto.randomUUID() : Math.random().toString(36).slice(2);
30
+ localStorage.setItem(STORAGE_KEY, id);
31
+ return id;
32
+ } catch {
33
+ return Math.random().toString(36).slice(2);
34
+ }
35
+ }
36
+ function collectBrowserInfo() {
37
+ const nav = navigator;
38
+ const ua = nav?.userAgent ?? "";
39
+ const { browserName, browserVersion } = parseUserAgent(ua);
40
+ const web = {
41
+ browserName,
42
+ browserVersion,
43
+ userAgent: ua,
44
+ platform: nav?.platform,
45
+ language: nav?.language,
46
+ vendor: nav?.vendor,
47
+ screenResolution: typeof screen !== "undefined" ? `${screen.width}x${screen.height}` : void 0
48
+ };
49
+ return {
50
+ deviceId: getOrCreateDeviceId(),
51
+ deviceOs: "web",
52
+ deviceType: "web",
53
+ deviceName: browserName,
54
+ deviceModel: browserName,
55
+ cpuArchitecture: void 0,
56
+ locale: nav?.language,
57
+ screenResolution: web.screenResolution,
58
+ platformInfo: { web }
59
+ };
60
+ }
61
+
62
+ // src/integrations.ts
63
+ function installGlobalErrorHandlers(hooks) {
64
+ const onError = (event) => {
65
+ let error = event.error;
66
+ if (!(error instanceof Error) && event.message) {
67
+ error = new Error(event.message);
68
+ error.colno = event.colno;
69
+ }
70
+ hooks.onError(error ?? new Error(event.message || "window.onerror"), {
71
+ source: event.filename,
72
+ line: event.lineno,
73
+ column: event.colno
74
+ });
75
+ };
76
+ const onRejection = (event) => {
77
+ hooks.onUnhandledRejection(event.reason ?? event.reason, {});
78
+ };
79
+ window.addEventListener("error", onError);
80
+ window.addEventListener("unhandledrejection", onRejection);
81
+ return () => {
82
+ window.removeEventListener("error", onError);
83
+ window.removeEventListener("unhandledrejection", onRejection);
84
+ };
85
+ }
86
+ function initGlobalHandlers(client) {
87
+ return installGlobalErrorHandlers({
88
+ onError: (error, ctx) => {
89
+ client.captureException(error, {
90
+ handled: false,
91
+ context: "window.onerror",
92
+ tags: { source: "window.onerror" },
93
+ extra: { ...ctx }
94
+ });
95
+ },
96
+ onUnhandledRejection: (reason, _ctx) => {
97
+ client.captureException(reason, {
98
+ handled: false,
99
+ context: "unhandledrejection",
100
+ tags: { source: "unhandledrejection" }
101
+ });
102
+ }
103
+ });
104
+ }
105
+
106
+ // src/http-breadcrumbs.ts
107
+ var xhrMeta = /* @__PURE__ */ new WeakMap();
108
+ function instrumentHttpBreadcrumbs(client) {
109
+ const addRequest = (method, url, status) => {
110
+ client.addBreadcrumb({
111
+ message: `${method} ${url}`,
112
+ type: "request",
113
+ category: "http",
114
+ data: { status, url }
115
+ });
116
+ };
117
+ const originalFetch = window.fetch.bind(window);
118
+ const wrappedFetch = (input, init) => {
119
+ let method = init?.method ?? "GET";
120
+ let url;
121
+ if (typeof input === "string") {
122
+ url = input;
123
+ } else if (input instanceof URL) {
124
+ url = input.toString();
125
+ } else {
126
+ url = input.url;
127
+ method = input.method ?? method;
128
+ }
129
+ const promise = originalFetch(input, init);
130
+ promise.then((res) => {
131
+ addRequest(method, url, res.status);
132
+ return res;
133
+ }).catch(() => addRequest(method, url));
134
+ return promise;
135
+ };
136
+ window.fetch = wrappedFetch;
137
+ const originalOpen = XMLHttpRequest.prototype.open;
138
+ const originalSend = XMLHttpRequest.prototype.send;
139
+ XMLHttpRequest.prototype.open = function(method, url) {
140
+ xhrMeta.set(this, { method, url });
141
+ originalOpen.call(this, method, url, true);
142
+ };
143
+ XMLHttpRequest.prototype.send = function(body) {
144
+ this.addEventListener("loadend", () => {
145
+ const meta = xhrMeta.get(this);
146
+ if (meta) addRequest(meta.method ?? "GET", meta.url ?? "", this.status);
147
+ });
148
+ originalSend.call(this, body);
149
+ };
150
+ return () => {
151
+ window.fetch = originalFetch;
152
+ XMLHttpRequest.prototype.open = originalOpen;
153
+ XMLHttpRequest.prototype.send = originalSend;
154
+ };
155
+ }
156
+
157
+ // src/console-breadcrumbs.ts
158
+ function instrumentConsoleBreadcrumbs(client) {
159
+ const restore = [];
160
+ const names = ["log", "error", "warn", "info", "debug", "trace"];
161
+ for (const name of names) {
162
+ const original = console[name]?.bind(console);
163
+ if (!original) continue;
164
+ const level = name === "error" ? "error" : name === "warn" ? "warning" : name === "info" ? "info" : "debug";
165
+ const wrapped = (...args) => {
166
+ const message = args.map((a) => {
167
+ if (a instanceof Error) return a.stack || a.message;
168
+ if (typeof a === "string") return a;
169
+ try {
170
+ return JSON.stringify(a);
171
+ } catch {
172
+ return String(a);
173
+ }
174
+ }).join(" ");
175
+ client.addBreadcrumb({
176
+ message: message || level,
177
+ type: "log",
178
+ category: "console",
179
+ data: { level, args }
180
+ });
181
+ return original(...args);
182
+ };
183
+ console[name] = wrapped;
184
+ restore.push(() => {
185
+ console[name] = original;
186
+ });
187
+ }
188
+ return () => {
189
+ for (const r of restore) r();
190
+ };
191
+ }
192
+
193
+ // src/navigation-breadcrumbs.ts
194
+ function instrumentNavigationBreadcrumbs(client) {
195
+ if (typeof window === "undefined" || !window.history) return () => void 0;
196
+ const add = (url) => {
197
+ client.addBreadcrumb({ message: url, type: "navigation", category: "navigation" });
198
+ };
199
+ const originalPush = window.history.pushState.bind(window.history);
200
+ const originalReplace = window.history.replaceState.bind(window.history);
201
+ window.history.pushState = (data, unused, url) => {
202
+ originalPush(data, unused, url);
203
+ add(window.location.href);
204
+ };
205
+ window.history.replaceState = (data, unused, url) => {
206
+ originalReplace(data, unused, url);
207
+ add(window.location.href);
208
+ };
209
+ return () => {
210
+ window.history.pushState = originalPush;
211
+ window.history.replaceState = originalReplace;
212
+ };
213
+ }
214
+
215
+ // src/index.ts
216
+ function initBrowser(options) {
217
+ const deviceInfo = options.deviceInfo ?? (options.collectBrowserInfo === false ? void 0 : collectBrowserInfo());
218
+ const client = new CrashLens({
219
+ ...options,
220
+ runtime: options.runtime ?? "browser",
221
+ runtimeVersion: options.runtimeVersion,
222
+ deviceInfo,
223
+ enableSessionTracking: options.enableSessionTracking ?? true
224
+ });
225
+ const cleanups = [];
226
+ if (options.enableGlobalHandlers !== false) {
227
+ cleanups.push(initGlobalHandlers(client));
228
+ }
229
+ if (options.enableHttpBreadcrumbs !== false) {
230
+ cleanups.push(instrumentHttpBreadcrumbs(client));
231
+ }
232
+ if (options.enableConsoleBreadcrumbs !== false) {
233
+ cleanups.push(instrumentConsoleBreadcrumbs(client));
234
+ }
235
+ if (options.enableNavigationBreadcrumbs !== false) {
236
+ cleanups.push(instrumentNavigationBreadcrumbs(client));
237
+ }
238
+ void client.init();
239
+ const instance = client;
240
+ instance.cleanup = () => {
241
+ for (const c of cleanups) {
242
+ try {
243
+ c();
244
+ } catch {
245
+ }
246
+ }
247
+ };
248
+ return instance;
249
+ }
250
+ export {
251
+ CrashLens,
252
+ initBrowser
253
+ };
package/package.json ADDED
@@ -0,0 +1,28 @@
1
+ {
2
+ "name": "@crashlensorg/browser",
3
+ "version": "0.1.0",
4
+ "description": "CrashLens SDK para o browser — window.onerror, unhandledrejection, fetch/XHR, breadcrumbs e info de dispositivo web",
5
+ "license": "MIT",
6
+ "main": "./dist/index.js",
7
+ "module": "./dist/index.mjs",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.mjs",
13
+ "require": "./dist/index.js"
14
+ }
15
+ },
16
+ "files": ["dist"],
17
+ "publishConfig": {
18
+ "access": "public"
19
+ },
20
+ "scripts": {
21
+ "build": "tsup src/index.ts --format cjs,esm --dts --clean",
22
+ "typecheck": "tsc --noEmit"
23
+ },
24
+ "dependencies": {
25
+ "@crashlensorg/core": "0.1.0"
26
+ },
27
+ "keywords": ["crashlens", "browser", "react", "javascript", "error-monitoring", "sentry-alternative"]
28
+ }