@xrayradar/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/dist/index.d.mts +90 -0
- package/dist/index.d.ts +90 -0
- package/dist/index.js +320 -0
- package/dist/index.mjs +287 -0
- package/package.json +30 -0
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { Scope, ClientOptions, Transport, Level, EventPayload } from '@xrayradar/core';
|
|
2
|
+
|
|
3
|
+
declare class BrowserClient {
|
|
4
|
+
readonly scope: Scope;
|
|
5
|
+
readonly options: Required<Pick<ClientOptions, "dsn" | "authToken" | "debug" | "environment" | "release" | "serverName" | "sampleRate" | "maxBreadcrumbs" | "beforeSend">> & {
|
|
6
|
+
transport: Transport;
|
|
7
|
+
};
|
|
8
|
+
private _transport;
|
|
9
|
+
private _enabled;
|
|
10
|
+
private _onError;
|
|
11
|
+
private _onUnhandledRejection;
|
|
12
|
+
constructor(options?: ClientOptions);
|
|
13
|
+
captureException(error: Error, options?: {
|
|
14
|
+
level?: Level;
|
|
15
|
+
message?: string;
|
|
16
|
+
}): string | null;
|
|
17
|
+
captureMessage(message: string, options?: {
|
|
18
|
+
level?: Level;
|
|
19
|
+
}): string | null;
|
|
20
|
+
addBreadcrumb(message: string, options?: {
|
|
21
|
+
category?: string;
|
|
22
|
+
level?: Level;
|
|
23
|
+
data?: Record<string, unknown>;
|
|
24
|
+
type?: string;
|
|
25
|
+
}): void;
|
|
26
|
+
clearBreadcrumbs(): void;
|
|
27
|
+
setUser(user: {
|
|
28
|
+
id?: string;
|
|
29
|
+
username?: string;
|
|
30
|
+
email?: string;
|
|
31
|
+
[key: string]: unknown;
|
|
32
|
+
} | null): void;
|
|
33
|
+
setTag(key: string, value: string): void;
|
|
34
|
+
setExtra(key: string, value: unknown): void;
|
|
35
|
+
setContext(key: string, data: Record<string, unknown>): void;
|
|
36
|
+
flush(): void;
|
|
37
|
+
close(): void;
|
|
38
|
+
private _installGlobalHandlers;
|
|
39
|
+
private _removeGlobalHandlers;
|
|
40
|
+
}
|
|
41
|
+
declare function init(options?: ClientOptions): BrowserClient;
|
|
42
|
+
declare function getClient(): BrowserClient | null;
|
|
43
|
+
declare function resetGlobal(): void;
|
|
44
|
+
declare function captureException(error: Error, options?: {
|
|
45
|
+
level?: Level;
|
|
46
|
+
message?: string;
|
|
47
|
+
}): string | null;
|
|
48
|
+
declare function captureMessage(message: string, options?: {
|
|
49
|
+
level?: Level;
|
|
50
|
+
}): string | null;
|
|
51
|
+
declare function addBreadcrumb(message: string, options?: {
|
|
52
|
+
category?: string;
|
|
53
|
+
level?: Level;
|
|
54
|
+
data?: Record<string, unknown>;
|
|
55
|
+
type?: string;
|
|
56
|
+
}): void;
|
|
57
|
+
declare function setUser(user: {
|
|
58
|
+
id?: string;
|
|
59
|
+
username?: string;
|
|
60
|
+
email?: string;
|
|
61
|
+
[key: string]: unknown;
|
|
62
|
+
} | null): void;
|
|
63
|
+
declare function setTag(key: string, value: string): void;
|
|
64
|
+
declare function setExtra(key: string, value: unknown): void;
|
|
65
|
+
declare function setContext(key: string, data: Record<string, unknown>): void;
|
|
66
|
+
|
|
67
|
+
declare class HttpTransport implements Transport {
|
|
68
|
+
private serverUrl;
|
|
69
|
+
private projectId;
|
|
70
|
+
private authToken;
|
|
71
|
+
private timeout;
|
|
72
|
+
constructor(dsn: string, options?: {
|
|
73
|
+
authToken?: string;
|
|
74
|
+
timeout?: number;
|
|
75
|
+
});
|
|
76
|
+
sendEvent(event: EventPayload): void;
|
|
77
|
+
flush(): void;
|
|
78
|
+
private truncatePayload;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
interface DsnParts {
|
|
82
|
+
serverUrl: string;
|
|
83
|
+
projectId: string;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Parse DSN: https://host[:port]/project_id
|
|
87
|
+
*/
|
|
88
|
+
declare function parseDsn(dsn: string): DsnParts;
|
|
89
|
+
|
|
90
|
+
export { BrowserClient, type DsnParts, HttpTransport, addBreadcrumb, captureException, captureMessage, getClient, init, parseDsn, resetGlobal, setContext, setExtra, setTag, setUser };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { Scope, ClientOptions, Transport, Level, EventPayload } from '@xrayradar/core';
|
|
2
|
+
|
|
3
|
+
declare class BrowserClient {
|
|
4
|
+
readonly scope: Scope;
|
|
5
|
+
readonly options: Required<Pick<ClientOptions, "dsn" | "authToken" | "debug" | "environment" | "release" | "serverName" | "sampleRate" | "maxBreadcrumbs" | "beforeSend">> & {
|
|
6
|
+
transport: Transport;
|
|
7
|
+
};
|
|
8
|
+
private _transport;
|
|
9
|
+
private _enabled;
|
|
10
|
+
private _onError;
|
|
11
|
+
private _onUnhandledRejection;
|
|
12
|
+
constructor(options?: ClientOptions);
|
|
13
|
+
captureException(error: Error, options?: {
|
|
14
|
+
level?: Level;
|
|
15
|
+
message?: string;
|
|
16
|
+
}): string | null;
|
|
17
|
+
captureMessage(message: string, options?: {
|
|
18
|
+
level?: Level;
|
|
19
|
+
}): string | null;
|
|
20
|
+
addBreadcrumb(message: string, options?: {
|
|
21
|
+
category?: string;
|
|
22
|
+
level?: Level;
|
|
23
|
+
data?: Record<string, unknown>;
|
|
24
|
+
type?: string;
|
|
25
|
+
}): void;
|
|
26
|
+
clearBreadcrumbs(): void;
|
|
27
|
+
setUser(user: {
|
|
28
|
+
id?: string;
|
|
29
|
+
username?: string;
|
|
30
|
+
email?: string;
|
|
31
|
+
[key: string]: unknown;
|
|
32
|
+
} | null): void;
|
|
33
|
+
setTag(key: string, value: string): void;
|
|
34
|
+
setExtra(key: string, value: unknown): void;
|
|
35
|
+
setContext(key: string, data: Record<string, unknown>): void;
|
|
36
|
+
flush(): void;
|
|
37
|
+
close(): void;
|
|
38
|
+
private _installGlobalHandlers;
|
|
39
|
+
private _removeGlobalHandlers;
|
|
40
|
+
}
|
|
41
|
+
declare function init(options?: ClientOptions): BrowserClient;
|
|
42
|
+
declare function getClient(): BrowserClient | null;
|
|
43
|
+
declare function resetGlobal(): void;
|
|
44
|
+
declare function captureException(error: Error, options?: {
|
|
45
|
+
level?: Level;
|
|
46
|
+
message?: string;
|
|
47
|
+
}): string | null;
|
|
48
|
+
declare function captureMessage(message: string, options?: {
|
|
49
|
+
level?: Level;
|
|
50
|
+
}): string | null;
|
|
51
|
+
declare function addBreadcrumb(message: string, options?: {
|
|
52
|
+
category?: string;
|
|
53
|
+
level?: Level;
|
|
54
|
+
data?: Record<string, unknown>;
|
|
55
|
+
type?: string;
|
|
56
|
+
}): void;
|
|
57
|
+
declare function setUser(user: {
|
|
58
|
+
id?: string;
|
|
59
|
+
username?: string;
|
|
60
|
+
email?: string;
|
|
61
|
+
[key: string]: unknown;
|
|
62
|
+
} | null): void;
|
|
63
|
+
declare function setTag(key: string, value: string): void;
|
|
64
|
+
declare function setExtra(key: string, value: unknown): void;
|
|
65
|
+
declare function setContext(key: string, data: Record<string, unknown>): void;
|
|
66
|
+
|
|
67
|
+
declare class HttpTransport implements Transport {
|
|
68
|
+
private serverUrl;
|
|
69
|
+
private projectId;
|
|
70
|
+
private authToken;
|
|
71
|
+
private timeout;
|
|
72
|
+
constructor(dsn: string, options?: {
|
|
73
|
+
authToken?: string;
|
|
74
|
+
timeout?: number;
|
|
75
|
+
});
|
|
76
|
+
sendEvent(event: EventPayload): void;
|
|
77
|
+
flush(): void;
|
|
78
|
+
private truncatePayload;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
interface DsnParts {
|
|
82
|
+
serverUrl: string;
|
|
83
|
+
projectId: string;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Parse DSN: https://host[:port]/project_id
|
|
87
|
+
*/
|
|
88
|
+
declare function parseDsn(dsn: string): DsnParts;
|
|
89
|
+
|
|
90
|
+
export { BrowserClient, type DsnParts, HttpTransport, addBreadcrumb, captureException, captureMessage, getClient, init, parseDsn, resetGlobal, setContext, setExtra, setTag, setUser };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,320 @@
|
|
|
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
|
+
BrowserClient: () => BrowserClient,
|
|
24
|
+
HttpTransport: () => HttpTransport,
|
|
25
|
+
addBreadcrumb: () => addBreadcrumb,
|
|
26
|
+
captureException: () => captureException,
|
|
27
|
+
captureMessage: () => captureMessage,
|
|
28
|
+
getClient: () => getClient,
|
|
29
|
+
init: () => init,
|
|
30
|
+
parseDsn: () => parseDsn,
|
|
31
|
+
resetGlobal: () => resetGlobal,
|
|
32
|
+
setContext: () => setContext,
|
|
33
|
+
setExtra: () => setExtra,
|
|
34
|
+
setTag: () => setTag,
|
|
35
|
+
setUser: () => setUser
|
|
36
|
+
});
|
|
37
|
+
module.exports = __toCommonJS(index_exports);
|
|
38
|
+
|
|
39
|
+
// src/client.ts
|
|
40
|
+
var import_core2 = require("@xrayradar/core");
|
|
41
|
+
|
|
42
|
+
// src/transport.ts
|
|
43
|
+
var import_core = require("@xrayradar/core");
|
|
44
|
+
|
|
45
|
+
// src/dsn.ts
|
|
46
|
+
function parseDsn(dsn) {
|
|
47
|
+
let url;
|
|
48
|
+
try {
|
|
49
|
+
url = new URL(dsn);
|
|
50
|
+
} catch {
|
|
51
|
+
throw new Error(
|
|
52
|
+
`Invalid DSN format. Expected: https://xrayradar.com/your_project_id`
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
if (!url.protocol || !url.host) {
|
|
56
|
+
throw new Error(
|
|
57
|
+
`Invalid DSN format. Expected: https://xrayradar.com/your_project_id`
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
const pathParts = url.pathname.replace(/^\/+|\/+$/g, "").split("/");
|
|
61
|
+
const projectId = pathParts[pathParts.length - 1];
|
|
62
|
+
if (!projectId) {
|
|
63
|
+
throw new Error(
|
|
64
|
+
`Missing project ID in DSN. Expected: https://xrayradar.com/your_project_id`
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
const serverUrl = `${url.protocol}//${url.host}`;
|
|
68
|
+
return { serverUrl, projectId };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// src/transport.ts
|
|
72
|
+
var MAX_PAYLOAD_SIZE = 100 * 1024;
|
|
73
|
+
var HttpTransport = class {
|
|
74
|
+
constructor(dsn, options) {
|
|
75
|
+
const { serverUrl, projectId } = parseDsn(dsn);
|
|
76
|
+
this.serverUrl = serverUrl;
|
|
77
|
+
this.projectId = projectId;
|
|
78
|
+
const fromGlobal = typeof globalThis !== "undefined" ? globalThis.XRAYRADAR_AUTH_TOKEN : void 0;
|
|
79
|
+
this.authToken = (typeof fromGlobal === "string" ? fromGlobal : options?.authToken) ?? "";
|
|
80
|
+
this.timeout = options?.timeout ?? 1e4;
|
|
81
|
+
}
|
|
82
|
+
sendEvent(event) {
|
|
83
|
+
const payload = JSON.stringify(event);
|
|
84
|
+
if (new Blob([payload]).size > MAX_PAYLOAD_SIZE) {
|
|
85
|
+
event = this.truncatePayload(event);
|
|
86
|
+
}
|
|
87
|
+
const url = `${this.serverUrl}/api/${this.projectId}/store/`;
|
|
88
|
+
const headers = {
|
|
89
|
+
"Content-Type": "application/json",
|
|
90
|
+
"User-Agent": `xrayradar/${(0, import_core.getSdkInfo)().version}`
|
|
91
|
+
};
|
|
92
|
+
if (this.authToken) {
|
|
93
|
+
headers["X-Xrayradar-Token"] = this.authToken;
|
|
94
|
+
}
|
|
95
|
+
const controller = new AbortController();
|
|
96
|
+
const id = setTimeout(() => controller.abort(), this.timeout);
|
|
97
|
+
fetch(url, {
|
|
98
|
+
method: "POST",
|
|
99
|
+
headers,
|
|
100
|
+
body: JSON.stringify(event),
|
|
101
|
+
signal: controller.signal,
|
|
102
|
+
keepalive: true
|
|
103
|
+
}).then((res) => {
|
|
104
|
+
clearTimeout(id);
|
|
105
|
+
if (res.status === 429) {
|
|
106
|
+
console.warn("[XrayRadar] Rate limited by server.");
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
if (!res.ok) {
|
|
110
|
+
res.text().then((t) => console.warn("[XrayRadar] Send failed:", res.status, t.slice(0, 200)));
|
|
111
|
+
}
|
|
112
|
+
}).catch((err) => {
|
|
113
|
+
clearTimeout(id);
|
|
114
|
+
console.warn("[XrayRadar] Send failed:", err);
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
flush() {
|
|
118
|
+
}
|
|
119
|
+
truncatePayload(event) {
|
|
120
|
+
const out = { ...event };
|
|
121
|
+
if (out.message && out.message.length > 1e3) {
|
|
122
|
+
out.message = out.message.slice(0, 997) + "...";
|
|
123
|
+
}
|
|
124
|
+
if (out.breadcrumbs && out.breadcrumbs.length > 100) {
|
|
125
|
+
out.breadcrumbs = out.breadcrumbs.slice(-100);
|
|
126
|
+
}
|
|
127
|
+
if (out.exception?.values) {
|
|
128
|
+
for (const v of out.exception.values) {
|
|
129
|
+
if (v.stacktrace?.frames && v.stacktrace.frames.length > 50) {
|
|
130
|
+
v.stacktrace.frames = v.stacktrace.frames.slice(0, 50);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return out;
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
// src/client.ts
|
|
139
|
+
var globalClient = null;
|
|
140
|
+
var BrowserClient = class {
|
|
141
|
+
constructor(options = {}) {
|
|
142
|
+
this._onError = null;
|
|
143
|
+
this._onUnhandledRejection = null;
|
|
144
|
+
this.scope = new import_core2.Scope();
|
|
145
|
+
this.scope.maxBreadcrumbs = options.maxBreadcrumbs ?? 100;
|
|
146
|
+
this._enabled = Boolean(options.dsn || options.transport || options.debug);
|
|
147
|
+
this._transport = options.transport ?? (options.dsn ? new HttpTransport(options.dsn, { authToken: options.authToken }) : { sendEvent: () => {
|
|
148
|
+
} });
|
|
149
|
+
const authToken = options.authToken ?? (typeof globalThis !== "undefined" ? globalThis.XRAYRADAR_AUTH_TOKEN : "") ?? "";
|
|
150
|
+
this.options = {
|
|
151
|
+
dsn: options.dsn ?? "",
|
|
152
|
+
authToken: typeof authToken === "string" ? authToken : "",
|
|
153
|
+
debug: options.debug ?? false,
|
|
154
|
+
environment: options.environment ?? "development",
|
|
155
|
+
release: options.release ?? "",
|
|
156
|
+
serverName: options.serverName ?? "",
|
|
157
|
+
sampleRate: Math.max(0, Math.min(1, options.sampleRate ?? 1)),
|
|
158
|
+
maxBreadcrumbs: options.maxBreadcrumbs ?? 100,
|
|
159
|
+
beforeSend: options.beforeSend ?? ((e) => e),
|
|
160
|
+
transport: this._transport
|
|
161
|
+
};
|
|
162
|
+
this.scope.applyToContext({
|
|
163
|
+
environment: this.options.environment,
|
|
164
|
+
release: this.options.release,
|
|
165
|
+
server_name: this.options.serverName
|
|
166
|
+
});
|
|
167
|
+
if (this._enabled && this.options.dsn && !options.transport) {
|
|
168
|
+
this._installGlobalHandlers();
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
captureException(error, options) {
|
|
172
|
+
if (!this._enabled) return null;
|
|
173
|
+
if (!(0, import_core2.shouldSample)(this.options.sampleRate)) return null;
|
|
174
|
+
const level = options?.level ?? "error";
|
|
175
|
+
let payload = (0, import_core2.eventFromException)(
|
|
176
|
+
error,
|
|
177
|
+
level,
|
|
178
|
+
options?.message,
|
|
179
|
+
this.scope
|
|
180
|
+
);
|
|
181
|
+
const after = this.options.beforeSend(payload);
|
|
182
|
+
if (after === null) return null;
|
|
183
|
+
const applyAndSend = (resolved) => {
|
|
184
|
+
if (this.options.debug) console.warn("[XrayRadar]", resolved);
|
|
185
|
+
this._transport.sendEvent(resolved);
|
|
186
|
+
};
|
|
187
|
+
if (after instanceof Promise) {
|
|
188
|
+
after.then((res) => res !== null && applyAndSend(res));
|
|
189
|
+
return null;
|
|
190
|
+
}
|
|
191
|
+
payload = after;
|
|
192
|
+
if (this.options.debug) console.warn("[XrayRadar]", payload);
|
|
193
|
+
this._transport.sendEvent(payload);
|
|
194
|
+
return payload.event_id;
|
|
195
|
+
}
|
|
196
|
+
captureMessage(message, options) {
|
|
197
|
+
if (!this._enabled) return null;
|
|
198
|
+
if (!(0, import_core2.shouldSample)(this.options.sampleRate)) return null;
|
|
199
|
+
const level = (0, import_core2.normalizeLevel)(options?.level ?? "error");
|
|
200
|
+
let payload = (0, import_core2.eventFromMessage)(message, level, this.scope);
|
|
201
|
+
const after = this.options.beforeSend(payload);
|
|
202
|
+
if (after === null) return null;
|
|
203
|
+
const applyAndSend = (resolved) => {
|
|
204
|
+
if (this.options.debug) console.warn("[XrayRadar]", resolved);
|
|
205
|
+
this._transport.sendEvent(resolved);
|
|
206
|
+
};
|
|
207
|
+
if (after instanceof Promise) {
|
|
208
|
+
after.then((res) => res !== null && applyAndSend(res));
|
|
209
|
+
return null;
|
|
210
|
+
}
|
|
211
|
+
payload = after;
|
|
212
|
+
if (this.options.debug) console.warn("[XrayRadar]", payload);
|
|
213
|
+
this._transport.sendEvent(payload);
|
|
214
|
+
return payload.event_id;
|
|
215
|
+
}
|
|
216
|
+
addBreadcrumb(message, options) {
|
|
217
|
+
if (!this._enabled) return;
|
|
218
|
+
this.scope.addBreadcrumb(message, options);
|
|
219
|
+
}
|
|
220
|
+
clearBreadcrumbs() {
|
|
221
|
+
this.scope.clearBreadcrumbs();
|
|
222
|
+
}
|
|
223
|
+
setUser(user) {
|
|
224
|
+
if (!this._enabled) return;
|
|
225
|
+
this.scope.setUser(user);
|
|
226
|
+
}
|
|
227
|
+
setTag(key, value) {
|
|
228
|
+
if (!this._enabled) return;
|
|
229
|
+
this.scope.setTag(key, value);
|
|
230
|
+
}
|
|
231
|
+
setExtra(key, value) {
|
|
232
|
+
if (!this._enabled) return;
|
|
233
|
+
this.scope.setExtra(key, value);
|
|
234
|
+
}
|
|
235
|
+
setContext(key, data) {
|
|
236
|
+
if (!this._enabled) return;
|
|
237
|
+
this.scope.setContext(key, data);
|
|
238
|
+
}
|
|
239
|
+
flush() {
|
|
240
|
+
if (this._transport.flush) this._transport.flush();
|
|
241
|
+
}
|
|
242
|
+
close() {
|
|
243
|
+
this.flush();
|
|
244
|
+
if (this._transport.close) this._transport.close();
|
|
245
|
+
this._removeGlobalHandlers();
|
|
246
|
+
}
|
|
247
|
+
_installGlobalHandlers() {
|
|
248
|
+
this._onError = (event) => {
|
|
249
|
+
const err = event.error ?? new Error(event.message ?? "Unknown error");
|
|
250
|
+
this.captureException(err instanceof Error ? err : new Error(String(err)));
|
|
251
|
+
};
|
|
252
|
+
this._onUnhandledRejection = (event) => {
|
|
253
|
+
const err = event.reason instanceof Error ? event.reason : new Error(String(event.reason));
|
|
254
|
+
this.captureException(err);
|
|
255
|
+
};
|
|
256
|
+
if (typeof window !== "undefined") {
|
|
257
|
+
window.addEventListener("error", this._onError);
|
|
258
|
+
window.addEventListener("unhandledrejection", this._onUnhandledRejection);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
_removeGlobalHandlers() {
|
|
262
|
+
if (typeof window !== "undefined" && this._onError && this._onUnhandledRejection) {
|
|
263
|
+
window.removeEventListener("error", this._onError);
|
|
264
|
+
window.removeEventListener("unhandledrejection", this._onUnhandledRejection);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
};
|
|
268
|
+
function init(options) {
|
|
269
|
+
const client = new BrowserClient(options);
|
|
270
|
+
globalClient = client;
|
|
271
|
+
return client;
|
|
272
|
+
}
|
|
273
|
+
function getClient() {
|
|
274
|
+
return globalClient;
|
|
275
|
+
}
|
|
276
|
+
function resetGlobal() {
|
|
277
|
+
if (globalClient) {
|
|
278
|
+
globalClient.close();
|
|
279
|
+
globalClient = null;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
function captureException(error, options) {
|
|
283
|
+
const client = getClient();
|
|
284
|
+
return client ? client.captureException(error, options) : null;
|
|
285
|
+
}
|
|
286
|
+
function captureMessage(message, options) {
|
|
287
|
+
const client = getClient();
|
|
288
|
+
return client ? client.captureMessage(message, options) : null;
|
|
289
|
+
}
|
|
290
|
+
function addBreadcrumb(message, options) {
|
|
291
|
+
getClient()?.addBreadcrumb(message, options);
|
|
292
|
+
}
|
|
293
|
+
function setUser(user) {
|
|
294
|
+
getClient()?.setUser(user);
|
|
295
|
+
}
|
|
296
|
+
function setTag(key, value) {
|
|
297
|
+
getClient()?.setTag(key, value);
|
|
298
|
+
}
|
|
299
|
+
function setExtra(key, value) {
|
|
300
|
+
getClient()?.setExtra(key, value);
|
|
301
|
+
}
|
|
302
|
+
function setContext(key, data) {
|
|
303
|
+
getClient()?.setContext(key, data);
|
|
304
|
+
}
|
|
305
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
306
|
+
0 && (module.exports = {
|
|
307
|
+
BrowserClient,
|
|
308
|
+
HttpTransport,
|
|
309
|
+
addBreadcrumb,
|
|
310
|
+
captureException,
|
|
311
|
+
captureMessage,
|
|
312
|
+
getClient,
|
|
313
|
+
init,
|
|
314
|
+
parseDsn,
|
|
315
|
+
resetGlobal,
|
|
316
|
+
setContext,
|
|
317
|
+
setExtra,
|
|
318
|
+
setTag,
|
|
319
|
+
setUser
|
|
320
|
+
});
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
// src/client.ts
|
|
2
|
+
import {
|
|
3
|
+
Scope,
|
|
4
|
+
eventFromException,
|
|
5
|
+
eventFromMessage,
|
|
6
|
+
shouldSample,
|
|
7
|
+
normalizeLevel
|
|
8
|
+
} from "@xrayradar/core";
|
|
9
|
+
|
|
10
|
+
// src/transport.ts
|
|
11
|
+
import { getSdkInfo } from "@xrayradar/core";
|
|
12
|
+
|
|
13
|
+
// src/dsn.ts
|
|
14
|
+
function parseDsn(dsn) {
|
|
15
|
+
let url;
|
|
16
|
+
try {
|
|
17
|
+
url = new URL(dsn);
|
|
18
|
+
} catch {
|
|
19
|
+
throw new Error(
|
|
20
|
+
`Invalid DSN format. Expected: https://xrayradar.com/your_project_id`
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
if (!url.protocol || !url.host) {
|
|
24
|
+
throw new Error(
|
|
25
|
+
`Invalid DSN format. Expected: https://xrayradar.com/your_project_id`
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
const pathParts = url.pathname.replace(/^\/+|\/+$/g, "").split("/");
|
|
29
|
+
const projectId = pathParts[pathParts.length - 1];
|
|
30
|
+
if (!projectId) {
|
|
31
|
+
throw new Error(
|
|
32
|
+
`Missing project ID in DSN. Expected: https://xrayradar.com/your_project_id`
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
const serverUrl = `${url.protocol}//${url.host}`;
|
|
36
|
+
return { serverUrl, projectId };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// src/transport.ts
|
|
40
|
+
var MAX_PAYLOAD_SIZE = 100 * 1024;
|
|
41
|
+
var HttpTransport = class {
|
|
42
|
+
constructor(dsn, options) {
|
|
43
|
+
const { serverUrl, projectId } = parseDsn(dsn);
|
|
44
|
+
this.serverUrl = serverUrl;
|
|
45
|
+
this.projectId = projectId;
|
|
46
|
+
const fromGlobal = typeof globalThis !== "undefined" ? globalThis.XRAYRADAR_AUTH_TOKEN : void 0;
|
|
47
|
+
this.authToken = (typeof fromGlobal === "string" ? fromGlobal : options?.authToken) ?? "";
|
|
48
|
+
this.timeout = options?.timeout ?? 1e4;
|
|
49
|
+
}
|
|
50
|
+
sendEvent(event) {
|
|
51
|
+
const payload = JSON.stringify(event);
|
|
52
|
+
if (new Blob([payload]).size > MAX_PAYLOAD_SIZE) {
|
|
53
|
+
event = this.truncatePayload(event);
|
|
54
|
+
}
|
|
55
|
+
const url = `${this.serverUrl}/api/${this.projectId}/store/`;
|
|
56
|
+
const headers = {
|
|
57
|
+
"Content-Type": "application/json",
|
|
58
|
+
"User-Agent": `xrayradar/${getSdkInfo().version}`
|
|
59
|
+
};
|
|
60
|
+
if (this.authToken) {
|
|
61
|
+
headers["X-Xrayradar-Token"] = this.authToken;
|
|
62
|
+
}
|
|
63
|
+
const controller = new AbortController();
|
|
64
|
+
const id = setTimeout(() => controller.abort(), this.timeout);
|
|
65
|
+
fetch(url, {
|
|
66
|
+
method: "POST",
|
|
67
|
+
headers,
|
|
68
|
+
body: JSON.stringify(event),
|
|
69
|
+
signal: controller.signal,
|
|
70
|
+
keepalive: true
|
|
71
|
+
}).then((res) => {
|
|
72
|
+
clearTimeout(id);
|
|
73
|
+
if (res.status === 429) {
|
|
74
|
+
console.warn("[XrayRadar] Rate limited by server.");
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
if (!res.ok) {
|
|
78
|
+
res.text().then((t) => console.warn("[XrayRadar] Send failed:", res.status, t.slice(0, 200)));
|
|
79
|
+
}
|
|
80
|
+
}).catch((err) => {
|
|
81
|
+
clearTimeout(id);
|
|
82
|
+
console.warn("[XrayRadar] Send failed:", err);
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
flush() {
|
|
86
|
+
}
|
|
87
|
+
truncatePayload(event) {
|
|
88
|
+
const out = { ...event };
|
|
89
|
+
if (out.message && out.message.length > 1e3) {
|
|
90
|
+
out.message = out.message.slice(0, 997) + "...";
|
|
91
|
+
}
|
|
92
|
+
if (out.breadcrumbs && out.breadcrumbs.length > 100) {
|
|
93
|
+
out.breadcrumbs = out.breadcrumbs.slice(-100);
|
|
94
|
+
}
|
|
95
|
+
if (out.exception?.values) {
|
|
96
|
+
for (const v of out.exception.values) {
|
|
97
|
+
if (v.stacktrace?.frames && v.stacktrace.frames.length > 50) {
|
|
98
|
+
v.stacktrace.frames = v.stacktrace.frames.slice(0, 50);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return out;
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
// src/client.ts
|
|
107
|
+
var globalClient = null;
|
|
108
|
+
var BrowserClient = class {
|
|
109
|
+
constructor(options = {}) {
|
|
110
|
+
this._onError = null;
|
|
111
|
+
this._onUnhandledRejection = null;
|
|
112
|
+
this.scope = new Scope();
|
|
113
|
+
this.scope.maxBreadcrumbs = options.maxBreadcrumbs ?? 100;
|
|
114
|
+
this._enabled = Boolean(options.dsn || options.transport || options.debug);
|
|
115
|
+
this._transport = options.transport ?? (options.dsn ? new HttpTransport(options.dsn, { authToken: options.authToken }) : { sendEvent: () => {
|
|
116
|
+
} });
|
|
117
|
+
const authToken = options.authToken ?? (typeof globalThis !== "undefined" ? globalThis.XRAYRADAR_AUTH_TOKEN : "") ?? "";
|
|
118
|
+
this.options = {
|
|
119
|
+
dsn: options.dsn ?? "",
|
|
120
|
+
authToken: typeof authToken === "string" ? authToken : "",
|
|
121
|
+
debug: options.debug ?? false,
|
|
122
|
+
environment: options.environment ?? "development",
|
|
123
|
+
release: options.release ?? "",
|
|
124
|
+
serverName: options.serverName ?? "",
|
|
125
|
+
sampleRate: Math.max(0, Math.min(1, options.sampleRate ?? 1)),
|
|
126
|
+
maxBreadcrumbs: options.maxBreadcrumbs ?? 100,
|
|
127
|
+
beforeSend: options.beforeSend ?? ((e) => e),
|
|
128
|
+
transport: this._transport
|
|
129
|
+
};
|
|
130
|
+
this.scope.applyToContext({
|
|
131
|
+
environment: this.options.environment,
|
|
132
|
+
release: this.options.release,
|
|
133
|
+
server_name: this.options.serverName
|
|
134
|
+
});
|
|
135
|
+
if (this._enabled && this.options.dsn && !options.transport) {
|
|
136
|
+
this._installGlobalHandlers();
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
captureException(error, options) {
|
|
140
|
+
if (!this._enabled) return null;
|
|
141
|
+
if (!shouldSample(this.options.sampleRate)) return null;
|
|
142
|
+
const level = options?.level ?? "error";
|
|
143
|
+
let payload = eventFromException(
|
|
144
|
+
error,
|
|
145
|
+
level,
|
|
146
|
+
options?.message,
|
|
147
|
+
this.scope
|
|
148
|
+
);
|
|
149
|
+
const after = this.options.beforeSend(payload);
|
|
150
|
+
if (after === null) return null;
|
|
151
|
+
const applyAndSend = (resolved) => {
|
|
152
|
+
if (this.options.debug) console.warn("[XrayRadar]", resolved);
|
|
153
|
+
this._transport.sendEvent(resolved);
|
|
154
|
+
};
|
|
155
|
+
if (after instanceof Promise) {
|
|
156
|
+
after.then((res) => res !== null && applyAndSend(res));
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
payload = after;
|
|
160
|
+
if (this.options.debug) console.warn("[XrayRadar]", payload);
|
|
161
|
+
this._transport.sendEvent(payload);
|
|
162
|
+
return payload.event_id;
|
|
163
|
+
}
|
|
164
|
+
captureMessage(message, options) {
|
|
165
|
+
if (!this._enabled) return null;
|
|
166
|
+
if (!shouldSample(this.options.sampleRate)) return null;
|
|
167
|
+
const level = normalizeLevel(options?.level ?? "error");
|
|
168
|
+
let payload = eventFromMessage(message, level, this.scope);
|
|
169
|
+
const after = this.options.beforeSend(payload);
|
|
170
|
+
if (after === null) return null;
|
|
171
|
+
const applyAndSend = (resolved) => {
|
|
172
|
+
if (this.options.debug) console.warn("[XrayRadar]", resolved);
|
|
173
|
+
this._transport.sendEvent(resolved);
|
|
174
|
+
};
|
|
175
|
+
if (after instanceof Promise) {
|
|
176
|
+
after.then((res) => res !== null && applyAndSend(res));
|
|
177
|
+
return null;
|
|
178
|
+
}
|
|
179
|
+
payload = after;
|
|
180
|
+
if (this.options.debug) console.warn("[XrayRadar]", payload);
|
|
181
|
+
this._transport.sendEvent(payload);
|
|
182
|
+
return payload.event_id;
|
|
183
|
+
}
|
|
184
|
+
addBreadcrumb(message, options) {
|
|
185
|
+
if (!this._enabled) return;
|
|
186
|
+
this.scope.addBreadcrumb(message, options);
|
|
187
|
+
}
|
|
188
|
+
clearBreadcrumbs() {
|
|
189
|
+
this.scope.clearBreadcrumbs();
|
|
190
|
+
}
|
|
191
|
+
setUser(user) {
|
|
192
|
+
if (!this._enabled) return;
|
|
193
|
+
this.scope.setUser(user);
|
|
194
|
+
}
|
|
195
|
+
setTag(key, value) {
|
|
196
|
+
if (!this._enabled) return;
|
|
197
|
+
this.scope.setTag(key, value);
|
|
198
|
+
}
|
|
199
|
+
setExtra(key, value) {
|
|
200
|
+
if (!this._enabled) return;
|
|
201
|
+
this.scope.setExtra(key, value);
|
|
202
|
+
}
|
|
203
|
+
setContext(key, data) {
|
|
204
|
+
if (!this._enabled) return;
|
|
205
|
+
this.scope.setContext(key, data);
|
|
206
|
+
}
|
|
207
|
+
flush() {
|
|
208
|
+
if (this._transport.flush) this._transport.flush();
|
|
209
|
+
}
|
|
210
|
+
close() {
|
|
211
|
+
this.flush();
|
|
212
|
+
if (this._transport.close) this._transport.close();
|
|
213
|
+
this._removeGlobalHandlers();
|
|
214
|
+
}
|
|
215
|
+
_installGlobalHandlers() {
|
|
216
|
+
this._onError = (event) => {
|
|
217
|
+
const err = event.error ?? new Error(event.message ?? "Unknown error");
|
|
218
|
+
this.captureException(err instanceof Error ? err : new Error(String(err)));
|
|
219
|
+
};
|
|
220
|
+
this._onUnhandledRejection = (event) => {
|
|
221
|
+
const err = event.reason instanceof Error ? event.reason : new Error(String(event.reason));
|
|
222
|
+
this.captureException(err);
|
|
223
|
+
};
|
|
224
|
+
if (typeof window !== "undefined") {
|
|
225
|
+
window.addEventListener("error", this._onError);
|
|
226
|
+
window.addEventListener("unhandledrejection", this._onUnhandledRejection);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
_removeGlobalHandlers() {
|
|
230
|
+
if (typeof window !== "undefined" && this._onError && this._onUnhandledRejection) {
|
|
231
|
+
window.removeEventListener("error", this._onError);
|
|
232
|
+
window.removeEventListener("unhandledrejection", this._onUnhandledRejection);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
};
|
|
236
|
+
function init(options) {
|
|
237
|
+
const client = new BrowserClient(options);
|
|
238
|
+
globalClient = client;
|
|
239
|
+
return client;
|
|
240
|
+
}
|
|
241
|
+
function getClient() {
|
|
242
|
+
return globalClient;
|
|
243
|
+
}
|
|
244
|
+
function resetGlobal() {
|
|
245
|
+
if (globalClient) {
|
|
246
|
+
globalClient.close();
|
|
247
|
+
globalClient = null;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
function captureException(error, options) {
|
|
251
|
+
const client = getClient();
|
|
252
|
+
return client ? client.captureException(error, options) : null;
|
|
253
|
+
}
|
|
254
|
+
function captureMessage(message, options) {
|
|
255
|
+
const client = getClient();
|
|
256
|
+
return client ? client.captureMessage(message, options) : null;
|
|
257
|
+
}
|
|
258
|
+
function addBreadcrumb(message, options) {
|
|
259
|
+
getClient()?.addBreadcrumb(message, options);
|
|
260
|
+
}
|
|
261
|
+
function setUser(user) {
|
|
262
|
+
getClient()?.setUser(user);
|
|
263
|
+
}
|
|
264
|
+
function setTag(key, value) {
|
|
265
|
+
getClient()?.setTag(key, value);
|
|
266
|
+
}
|
|
267
|
+
function setExtra(key, value) {
|
|
268
|
+
getClient()?.setExtra(key, value);
|
|
269
|
+
}
|
|
270
|
+
function setContext(key, data) {
|
|
271
|
+
getClient()?.setContext(key, data);
|
|
272
|
+
}
|
|
273
|
+
export {
|
|
274
|
+
BrowserClient,
|
|
275
|
+
HttpTransport,
|
|
276
|
+
addBreadcrumb,
|
|
277
|
+
captureException,
|
|
278
|
+
captureMessage,
|
|
279
|
+
getClient,
|
|
280
|
+
init,
|
|
281
|
+
parseDsn,
|
|
282
|
+
resetGlobal,
|
|
283
|
+
setContext,
|
|
284
|
+
setExtra,
|
|
285
|
+
setTag,
|
|
286
|
+
setUser
|
|
287
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@xrayradar/browser",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "XrayRadar SDK for browser",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"module": "./dist/index.mjs",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.mjs",
|
|
12
|
+
"require": "./dist/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": ["dist"],
|
|
16
|
+
"scripts": {
|
|
17
|
+
"build": "tsup src/index.ts --format cjs,esm --dts --clean",
|
|
18
|
+
"test": "vitest run --passWithNoTests",
|
|
19
|
+
"lint": "eslint src",
|
|
20
|
+
"clean": "rm -rf dist"
|
|
21
|
+
},
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"@xrayradar/core": "*"
|
|
24
|
+
},
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"tsup": "^8.0.0",
|
|
27
|
+
"typescript": "^5.3.3",
|
|
28
|
+
"vitest": "^4.0.0"
|
|
29
|
+
}
|
|
30
|
+
}
|