@xrayradar/node 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,92 @@
1
+ import { Scope, ClientOptions, Transport, Level, EventPayload } from '@xrayradar/core';
2
+
3
+ declare class NodeClient {
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 _originalUncaughtException;
11
+ private _originalUnhandledRejection;
12
+ constructor(options?: ClientOptions);
13
+ init(options?: ClientOptions): this;
14
+ captureException(error: Error, options?: {
15
+ level?: Level;
16
+ message?: string;
17
+ }): string | null;
18
+ captureMessage(message: string, options?: {
19
+ level?: Level;
20
+ }): string | null;
21
+ addBreadcrumb(message: string, options?: {
22
+ category?: string;
23
+ level?: Level;
24
+ data?: Record<string, unknown>;
25
+ type?: string;
26
+ }): void;
27
+ clearBreadcrumbs(): void;
28
+ setUser(user: {
29
+ id?: string;
30
+ username?: string;
31
+ email?: string;
32
+ [key: string]: unknown;
33
+ } | null): void;
34
+ setTag(key: string, value: string): void;
35
+ setExtra(key: string, value: unknown): void;
36
+ setContext(key: string, data: Record<string, unknown>): void;
37
+ flush(): void;
38
+ close(): void;
39
+ private _installGlobalHandlers;
40
+ private _removeGlobalHandlers;
41
+ }
42
+ declare function init(options?: ClientOptions): NodeClient;
43
+ declare function getClient(): NodeClient | null;
44
+ declare function resetGlobal(): void;
45
+ declare function captureException(error: Error, options?: {
46
+ level?: Level;
47
+ message?: string;
48
+ }): string | null;
49
+ declare function captureMessage(message: string, options?: {
50
+ level?: Level;
51
+ }): string | null;
52
+ declare function addBreadcrumb(message: string, options?: {
53
+ category?: string;
54
+ level?: Level;
55
+ data?: Record<string, unknown>;
56
+ type?: string;
57
+ }): void;
58
+ declare function setUser(user: {
59
+ id?: string;
60
+ username?: string;
61
+ email?: string;
62
+ [key: string]: unknown;
63
+ } | null): void;
64
+ declare function setTag(key: string, value: string): void;
65
+ declare function setExtra(key: string, value: unknown): void;
66
+ declare function setContext(key: string, data: Record<string, unknown>): void;
67
+
68
+ declare class HttpTransport implements Transport {
69
+ private serverUrl;
70
+ private projectId;
71
+ private authToken;
72
+ private timeout;
73
+ constructor(dsn: string, options?: {
74
+ authToken?: string;
75
+ timeout?: number;
76
+ });
77
+ sendEvent(event: EventPayload): Promise<void>;
78
+ flush(): void;
79
+ private truncatePayload;
80
+ }
81
+
82
+ interface DsnParts {
83
+ serverUrl: string;
84
+ projectId: string;
85
+ }
86
+ /**
87
+ * Parse DSN in the form: https://host[:port]/project_id
88
+ * Same contract as Python SDK and server.
89
+ */
90
+ declare function parseDsn(dsn: string): DsnParts;
91
+
92
+ export { type DsnParts, HttpTransport, NodeClient, addBreadcrumb, captureException, captureMessage, getClient, init, parseDsn, resetGlobal, setContext, setExtra, setTag, setUser };
@@ -0,0 +1,92 @@
1
+ import { Scope, ClientOptions, Transport, Level, EventPayload } from '@xrayradar/core';
2
+
3
+ declare class NodeClient {
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 _originalUncaughtException;
11
+ private _originalUnhandledRejection;
12
+ constructor(options?: ClientOptions);
13
+ init(options?: ClientOptions): this;
14
+ captureException(error: Error, options?: {
15
+ level?: Level;
16
+ message?: string;
17
+ }): string | null;
18
+ captureMessage(message: string, options?: {
19
+ level?: Level;
20
+ }): string | null;
21
+ addBreadcrumb(message: string, options?: {
22
+ category?: string;
23
+ level?: Level;
24
+ data?: Record<string, unknown>;
25
+ type?: string;
26
+ }): void;
27
+ clearBreadcrumbs(): void;
28
+ setUser(user: {
29
+ id?: string;
30
+ username?: string;
31
+ email?: string;
32
+ [key: string]: unknown;
33
+ } | null): void;
34
+ setTag(key: string, value: string): void;
35
+ setExtra(key: string, value: unknown): void;
36
+ setContext(key: string, data: Record<string, unknown>): void;
37
+ flush(): void;
38
+ close(): void;
39
+ private _installGlobalHandlers;
40
+ private _removeGlobalHandlers;
41
+ }
42
+ declare function init(options?: ClientOptions): NodeClient;
43
+ declare function getClient(): NodeClient | null;
44
+ declare function resetGlobal(): void;
45
+ declare function captureException(error: Error, options?: {
46
+ level?: Level;
47
+ message?: string;
48
+ }): string | null;
49
+ declare function captureMessage(message: string, options?: {
50
+ level?: Level;
51
+ }): string | null;
52
+ declare function addBreadcrumb(message: string, options?: {
53
+ category?: string;
54
+ level?: Level;
55
+ data?: Record<string, unknown>;
56
+ type?: string;
57
+ }): void;
58
+ declare function setUser(user: {
59
+ id?: string;
60
+ username?: string;
61
+ email?: string;
62
+ [key: string]: unknown;
63
+ } | null): void;
64
+ declare function setTag(key: string, value: string): void;
65
+ declare function setExtra(key: string, value: unknown): void;
66
+ declare function setContext(key: string, data: Record<string, unknown>): void;
67
+
68
+ declare class HttpTransport implements Transport {
69
+ private serverUrl;
70
+ private projectId;
71
+ private authToken;
72
+ private timeout;
73
+ constructor(dsn: string, options?: {
74
+ authToken?: string;
75
+ timeout?: number;
76
+ });
77
+ sendEvent(event: EventPayload): Promise<void>;
78
+ flush(): void;
79
+ private truncatePayload;
80
+ }
81
+
82
+ interface DsnParts {
83
+ serverUrl: string;
84
+ projectId: string;
85
+ }
86
+ /**
87
+ * Parse DSN in the form: https://host[:port]/project_id
88
+ * Same contract as Python SDK and server.
89
+ */
90
+ declare function parseDsn(dsn: string): DsnParts;
91
+
92
+ export { type DsnParts, HttpTransport, NodeClient, addBreadcrumb, captureException, captureMessage, getClient, init, parseDsn, resetGlobal, setContext, setExtra, setTag, setUser };
package/dist/index.js ADDED
@@ -0,0 +1,355 @@
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
+ HttpTransport: () => HttpTransport,
24
+ NodeClient: () => NodeClient,
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
+ this.authToken = options?.authToken ?? process.env["XRAYRADAR_AUTH_TOKEN"] ?? "";
79
+ this.timeout = options?.timeout ?? 1e4;
80
+ }
81
+ async sendEvent(event) {
82
+ const payload = JSON.stringify(event);
83
+ if (Buffer.byteLength(payload, "utf8") > MAX_PAYLOAD_SIZE) {
84
+ event = this.truncatePayload(event);
85
+ }
86
+ const url = `${this.serverUrl}/api/${this.projectId}/store/`;
87
+ const headers = {
88
+ "Content-Type": "application/json",
89
+ "User-Agent": `xrayradar/${(0, import_core.getSdkInfo)().version}`
90
+ };
91
+ if (this.authToken) {
92
+ headers["X-Xrayradar-Token"] = this.authToken;
93
+ }
94
+ const res = await fetch(url, {
95
+ method: "POST",
96
+ headers,
97
+ body: JSON.stringify(event),
98
+ signal: AbortSignal.timeout(this.timeout)
99
+ });
100
+ if (res.status === 429) {
101
+ const retryAfter = res.headers.get("Retry-After") ?? "60";
102
+ throw new Error(
103
+ `Rate limited by XrayRadar server. Retry after ${retryAfter} seconds.`
104
+ );
105
+ }
106
+ if (!res.ok) {
107
+ const text = await res.text();
108
+ const detail = text.slice(0, 200);
109
+ throw new Error(
110
+ `Failed to send event to XrayRadar: HTTP ${res.status}${detail ? ` - ${detail}` : ""}`
111
+ );
112
+ }
113
+ }
114
+ flush() {
115
+ }
116
+ truncatePayload(event) {
117
+ const out = { ...event };
118
+ if (out.message && out.message.length > 1e3) {
119
+ out.message = out.message.slice(0, 997) + "...";
120
+ }
121
+ if (out.breadcrumbs && out.breadcrumbs.length > 100) {
122
+ out.breadcrumbs = out.breadcrumbs.slice(-100);
123
+ }
124
+ if (out.exception?.values) {
125
+ for (const v of out.exception.values) {
126
+ if (v.stacktrace?.frames && v.stacktrace.frames.length > 50) {
127
+ v.stacktrace.frames = v.stacktrace.frames.slice(0, 50);
128
+ }
129
+ }
130
+ }
131
+ return out;
132
+ }
133
+ };
134
+
135
+ // src/client.ts
136
+ var globalClient = null;
137
+ var NodeClient = class {
138
+ constructor(options = {}) {
139
+ this._originalUncaughtException = null;
140
+ this._originalUnhandledRejection = null;
141
+ this.scope = new import_core2.Scope();
142
+ this.scope.maxBreadcrumbs = options.maxBreadcrumbs ?? 100;
143
+ this._enabled = Boolean(options.dsn || options.transport || options.debug);
144
+ this._transport = options.transport ?? (options.dsn ? new HttpTransport(options.dsn, { authToken: options.authToken }) : { sendEvent: () => {
145
+ } });
146
+ this.options = {
147
+ dsn: options.dsn ?? "",
148
+ authToken: options.authToken ?? "",
149
+ debug: options.debug ?? false,
150
+ environment: options.environment ?? process.env["XRAYRADAR_ENVIRONMENT"] ?? "development",
151
+ release: options.release ?? process.env["XRAYRADAR_RELEASE"] ?? "",
152
+ serverName: options.serverName ?? process.env["XRAYRADAR_SERVER_NAME"] ?? "",
153
+ sampleRate: Math.max(0, Math.min(1, options.sampleRate ?? 1)),
154
+ maxBreadcrumbs: options.maxBreadcrumbs ?? 100,
155
+ beforeSend: options.beforeSend ?? ((e) => e),
156
+ transport: this._transport
157
+ };
158
+ this.scope.applyToContext({
159
+ environment: this.options.environment,
160
+ release: this.options.release,
161
+ server_name: this.options.serverName || void 0
162
+ });
163
+ if (this._enabled && this.options.dsn && !options.transport) {
164
+ this._installGlobalHandlers();
165
+ }
166
+ }
167
+ init(options) {
168
+ if (options?.dsn) this.options.dsn = options.dsn;
169
+ if (options?.authToken !== void 0) this.options.authToken = options.authToken;
170
+ if (options?.environment !== void 0) {
171
+ this.options.environment = options.environment;
172
+ this.scope.applyToContext({ environment: options.environment });
173
+ }
174
+ if (options?.release !== void 0) {
175
+ this.options.release = options.release;
176
+ this.scope.applyToContext({ release: options.release });
177
+ }
178
+ if (options?.serverName !== void 0) {
179
+ this.options.serverName = options.serverName;
180
+ this.scope.applyToContext({ server_name: options.serverName });
181
+ }
182
+ if (options?.sampleRate !== void 0) this.options.sampleRate = Math.max(0, Math.min(1, options.sampleRate));
183
+ if (options?.maxBreadcrumbs !== void 0) {
184
+ this.options.maxBreadcrumbs = options.maxBreadcrumbs;
185
+ this.scope.maxBreadcrumbs = options.maxBreadcrumbs;
186
+ }
187
+ if (options?.beforeSend !== void 0) this.options.beforeSend = options.beforeSend;
188
+ return this;
189
+ }
190
+ captureException(error, options) {
191
+ if (!this._enabled) return null;
192
+ if (!(0, import_core2.shouldSample)(this.options.sampleRate)) return null;
193
+ const level = options?.level ?? "error";
194
+ let payload = (0, import_core2.eventFromException)(
195
+ error,
196
+ level,
197
+ options?.message,
198
+ this.scope
199
+ );
200
+ const after = this.options.beforeSend(payload);
201
+ if (after === null) return null;
202
+ const applyAndSend = (resolved) => {
203
+ if (this.options.debug) console.warn("[XrayRadar]", resolved);
204
+ const p2 = this._transport.sendEvent(resolved);
205
+ if (p2 && typeof p2.catch === "function") {
206
+ p2.catch(() => {
207
+ });
208
+ }
209
+ };
210
+ if (after instanceof Promise) {
211
+ after.then((res) => res !== null && applyAndSend(res));
212
+ return null;
213
+ }
214
+ payload = after;
215
+ if (this.options.debug) console.warn("[XrayRadar]", payload);
216
+ const p = this._transport.sendEvent(payload);
217
+ if (p && typeof p.catch === "function") {
218
+ p.catch(() => {
219
+ });
220
+ }
221
+ return payload.event_id;
222
+ }
223
+ captureMessage(message, options) {
224
+ if (!this._enabled) return null;
225
+ if (!(0, import_core2.shouldSample)(this.options.sampleRate)) return null;
226
+ const level = (0, import_core2.normalizeLevel)(options?.level ?? "error");
227
+ let payload = (0, import_core2.eventFromMessage)(message, level, this.scope);
228
+ const after = this.options.beforeSend(payload);
229
+ if (after === null) return null;
230
+ const applyAndSend = (resolved) => {
231
+ if (this.options.debug) console.warn("[XrayRadar]", resolved);
232
+ const p2 = this._transport.sendEvent(resolved);
233
+ if (p2 && typeof p2.catch === "function") {
234
+ p2.catch(() => {
235
+ });
236
+ }
237
+ };
238
+ if (after instanceof Promise) {
239
+ after.then((res) => res !== null && applyAndSend(res));
240
+ return null;
241
+ }
242
+ payload = after;
243
+ if (this.options.debug) console.warn("[XrayRadar]", payload);
244
+ const p = this._transport.sendEvent(payload);
245
+ if (p && typeof p.catch === "function") {
246
+ p.catch(() => {
247
+ });
248
+ }
249
+ return payload.event_id;
250
+ }
251
+ addBreadcrumb(message, options) {
252
+ if (!this._enabled) return;
253
+ this.scope.addBreadcrumb(message, options);
254
+ }
255
+ clearBreadcrumbs() {
256
+ this.scope.clearBreadcrumbs();
257
+ }
258
+ setUser(user) {
259
+ if (!this._enabled) return;
260
+ this.scope.setUser(user);
261
+ }
262
+ setTag(key, value) {
263
+ if (!this._enabled) return;
264
+ this.scope.setTag(key, value);
265
+ }
266
+ setExtra(key, value) {
267
+ if (!this._enabled) return;
268
+ this.scope.setExtra(key, value);
269
+ }
270
+ setContext(key, data) {
271
+ if (!this._enabled) return;
272
+ this.scope.setContext(key, data);
273
+ }
274
+ flush() {
275
+ if (this._transport.flush) this._transport.flush();
276
+ }
277
+ close() {
278
+ this.flush();
279
+ if (this._transport.close) this._transport.close();
280
+ this._removeGlobalHandlers();
281
+ }
282
+ _installGlobalHandlers() {
283
+ this._originalUncaughtException = process.listeners("uncaughtException").pop() ?? null;
284
+ this._originalUnhandledRejection = process.listeners("unhandledRejection").pop() ?? null;
285
+ process.on("uncaughtException", (err, origin) => {
286
+ this.captureException(err);
287
+ if (this._originalUncaughtException) this._originalUncaughtException(err, origin);
288
+ else process.exit(1);
289
+ });
290
+ process.on("unhandledRejection", (reason, promise) => {
291
+ const err = reason instanceof Error ? reason : new Error(String(reason));
292
+ this.captureException(err);
293
+ if (this._originalUnhandledRejection) this._originalUnhandledRejection(reason, promise);
294
+ });
295
+ }
296
+ _removeGlobalHandlers() {
297
+ process.removeAllListeners("uncaughtException");
298
+ process.removeAllListeners("unhandledRejection");
299
+ if (this._originalUncaughtException) process.on("uncaughtException", this._originalUncaughtException);
300
+ if (this._originalUnhandledRejection) process.on("unhandledRejection", this._originalUnhandledRejection);
301
+ }
302
+ };
303
+ function init(options) {
304
+ const client = new NodeClient(options);
305
+ globalClient = client;
306
+ return client;
307
+ }
308
+ function getClient() {
309
+ return globalClient;
310
+ }
311
+ function resetGlobal() {
312
+ if (globalClient) {
313
+ globalClient.close();
314
+ globalClient = null;
315
+ }
316
+ }
317
+ function captureException(error, options) {
318
+ const client = getClient();
319
+ return client ? client.captureException(error, options) : null;
320
+ }
321
+ function captureMessage(message, options) {
322
+ const client = getClient();
323
+ return client ? client.captureMessage(message, options) : null;
324
+ }
325
+ function addBreadcrumb(message, options) {
326
+ getClient()?.addBreadcrumb(message, options);
327
+ }
328
+ function setUser(user) {
329
+ getClient()?.setUser(user);
330
+ }
331
+ function setTag(key, value) {
332
+ getClient()?.setTag(key, value);
333
+ }
334
+ function setExtra(key, value) {
335
+ getClient()?.setExtra(key, value);
336
+ }
337
+ function setContext(key, data) {
338
+ getClient()?.setContext(key, data);
339
+ }
340
+ // Annotate the CommonJS export names for ESM import in node:
341
+ 0 && (module.exports = {
342
+ HttpTransport,
343
+ NodeClient,
344
+ addBreadcrumb,
345
+ captureException,
346
+ captureMessage,
347
+ getClient,
348
+ init,
349
+ parseDsn,
350
+ resetGlobal,
351
+ setContext,
352
+ setExtra,
353
+ setTag,
354
+ setUser
355
+ });
package/dist/index.mjs ADDED
@@ -0,0 +1,322 @@
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
+ this.authToken = options?.authToken ?? process.env["XRAYRADAR_AUTH_TOKEN"] ?? "";
47
+ this.timeout = options?.timeout ?? 1e4;
48
+ }
49
+ async sendEvent(event) {
50
+ const payload = JSON.stringify(event);
51
+ if (Buffer.byteLength(payload, "utf8") > MAX_PAYLOAD_SIZE) {
52
+ event = this.truncatePayload(event);
53
+ }
54
+ const url = `${this.serverUrl}/api/${this.projectId}/store/`;
55
+ const headers = {
56
+ "Content-Type": "application/json",
57
+ "User-Agent": `xrayradar/${getSdkInfo().version}`
58
+ };
59
+ if (this.authToken) {
60
+ headers["X-Xrayradar-Token"] = this.authToken;
61
+ }
62
+ const res = await fetch(url, {
63
+ method: "POST",
64
+ headers,
65
+ body: JSON.stringify(event),
66
+ signal: AbortSignal.timeout(this.timeout)
67
+ });
68
+ if (res.status === 429) {
69
+ const retryAfter = res.headers.get("Retry-After") ?? "60";
70
+ throw new Error(
71
+ `Rate limited by XrayRadar server. Retry after ${retryAfter} seconds.`
72
+ );
73
+ }
74
+ if (!res.ok) {
75
+ const text = await res.text();
76
+ const detail = text.slice(0, 200);
77
+ throw new Error(
78
+ `Failed to send event to XrayRadar: HTTP ${res.status}${detail ? ` - ${detail}` : ""}`
79
+ );
80
+ }
81
+ }
82
+ flush() {
83
+ }
84
+ truncatePayload(event) {
85
+ const out = { ...event };
86
+ if (out.message && out.message.length > 1e3) {
87
+ out.message = out.message.slice(0, 997) + "...";
88
+ }
89
+ if (out.breadcrumbs && out.breadcrumbs.length > 100) {
90
+ out.breadcrumbs = out.breadcrumbs.slice(-100);
91
+ }
92
+ if (out.exception?.values) {
93
+ for (const v of out.exception.values) {
94
+ if (v.stacktrace?.frames && v.stacktrace.frames.length > 50) {
95
+ v.stacktrace.frames = v.stacktrace.frames.slice(0, 50);
96
+ }
97
+ }
98
+ }
99
+ return out;
100
+ }
101
+ };
102
+
103
+ // src/client.ts
104
+ var globalClient = null;
105
+ var NodeClient = class {
106
+ constructor(options = {}) {
107
+ this._originalUncaughtException = null;
108
+ this._originalUnhandledRejection = null;
109
+ this.scope = new Scope();
110
+ this.scope.maxBreadcrumbs = options.maxBreadcrumbs ?? 100;
111
+ this._enabled = Boolean(options.dsn || options.transport || options.debug);
112
+ this._transport = options.transport ?? (options.dsn ? new HttpTransport(options.dsn, { authToken: options.authToken }) : { sendEvent: () => {
113
+ } });
114
+ this.options = {
115
+ dsn: options.dsn ?? "",
116
+ authToken: options.authToken ?? "",
117
+ debug: options.debug ?? false,
118
+ environment: options.environment ?? process.env["XRAYRADAR_ENVIRONMENT"] ?? "development",
119
+ release: options.release ?? process.env["XRAYRADAR_RELEASE"] ?? "",
120
+ serverName: options.serverName ?? process.env["XRAYRADAR_SERVER_NAME"] ?? "",
121
+ sampleRate: Math.max(0, Math.min(1, options.sampleRate ?? 1)),
122
+ maxBreadcrumbs: options.maxBreadcrumbs ?? 100,
123
+ beforeSend: options.beforeSend ?? ((e) => e),
124
+ transport: this._transport
125
+ };
126
+ this.scope.applyToContext({
127
+ environment: this.options.environment,
128
+ release: this.options.release,
129
+ server_name: this.options.serverName || void 0
130
+ });
131
+ if (this._enabled && this.options.dsn && !options.transport) {
132
+ this._installGlobalHandlers();
133
+ }
134
+ }
135
+ init(options) {
136
+ if (options?.dsn) this.options.dsn = options.dsn;
137
+ if (options?.authToken !== void 0) this.options.authToken = options.authToken;
138
+ if (options?.environment !== void 0) {
139
+ this.options.environment = options.environment;
140
+ this.scope.applyToContext({ environment: options.environment });
141
+ }
142
+ if (options?.release !== void 0) {
143
+ this.options.release = options.release;
144
+ this.scope.applyToContext({ release: options.release });
145
+ }
146
+ if (options?.serverName !== void 0) {
147
+ this.options.serverName = options.serverName;
148
+ this.scope.applyToContext({ server_name: options.serverName });
149
+ }
150
+ if (options?.sampleRate !== void 0) this.options.sampleRate = Math.max(0, Math.min(1, options.sampleRate));
151
+ if (options?.maxBreadcrumbs !== void 0) {
152
+ this.options.maxBreadcrumbs = options.maxBreadcrumbs;
153
+ this.scope.maxBreadcrumbs = options.maxBreadcrumbs;
154
+ }
155
+ if (options?.beforeSend !== void 0) this.options.beforeSend = options.beforeSend;
156
+ return this;
157
+ }
158
+ captureException(error, options) {
159
+ if (!this._enabled) return null;
160
+ if (!shouldSample(this.options.sampleRate)) return null;
161
+ const level = options?.level ?? "error";
162
+ let payload = eventFromException(
163
+ error,
164
+ level,
165
+ options?.message,
166
+ this.scope
167
+ );
168
+ const after = this.options.beforeSend(payload);
169
+ if (after === null) return null;
170
+ const applyAndSend = (resolved) => {
171
+ if (this.options.debug) console.warn("[XrayRadar]", resolved);
172
+ const p2 = this._transport.sendEvent(resolved);
173
+ if (p2 && typeof p2.catch === "function") {
174
+ p2.catch(() => {
175
+ });
176
+ }
177
+ };
178
+ if (after instanceof Promise) {
179
+ after.then((res) => res !== null && applyAndSend(res));
180
+ return null;
181
+ }
182
+ payload = after;
183
+ if (this.options.debug) console.warn("[XrayRadar]", payload);
184
+ const p = this._transport.sendEvent(payload);
185
+ if (p && typeof p.catch === "function") {
186
+ p.catch(() => {
187
+ });
188
+ }
189
+ return payload.event_id;
190
+ }
191
+ captureMessage(message, options) {
192
+ if (!this._enabled) return null;
193
+ if (!shouldSample(this.options.sampleRate)) return null;
194
+ const level = normalizeLevel(options?.level ?? "error");
195
+ let payload = eventFromMessage(message, level, this.scope);
196
+ const after = this.options.beforeSend(payload);
197
+ if (after === null) return null;
198
+ const applyAndSend = (resolved) => {
199
+ if (this.options.debug) console.warn("[XrayRadar]", resolved);
200
+ const p2 = this._transport.sendEvent(resolved);
201
+ if (p2 && typeof p2.catch === "function") {
202
+ p2.catch(() => {
203
+ });
204
+ }
205
+ };
206
+ if (after instanceof Promise) {
207
+ after.then((res) => res !== null && applyAndSend(res));
208
+ return null;
209
+ }
210
+ payload = after;
211
+ if (this.options.debug) console.warn("[XrayRadar]", payload);
212
+ const p = this._transport.sendEvent(payload);
213
+ if (p && typeof p.catch === "function") {
214
+ p.catch(() => {
215
+ });
216
+ }
217
+ return payload.event_id;
218
+ }
219
+ addBreadcrumb(message, options) {
220
+ if (!this._enabled) return;
221
+ this.scope.addBreadcrumb(message, options);
222
+ }
223
+ clearBreadcrumbs() {
224
+ this.scope.clearBreadcrumbs();
225
+ }
226
+ setUser(user) {
227
+ if (!this._enabled) return;
228
+ this.scope.setUser(user);
229
+ }
230
+ setTag(key, value) {
231
+ if (!this._enabled) return;
232
+ this.scope.setTag(key, value);
233
+ }
234
+ setExtra(key, value) {
235
+ if (!this._enabled) return;
236
+ this.scope.setExtra(key, value);
237
+ }
238
+ setContext(key, data) {
239
+ if (!this._enabled) return;
240
+ this.scope.setContext(key, data);
241
+ }
242
+ flush() {
243
+ if (this._transport.flush) this._transport.flush();
244
+ }
245
+ close() {
246
+ this.flush();
247
+ if (this._transport.close) this._transport.close();
248
+ this._removeGlobalHandlers();
249
+ }
250
+ _installGlobalHandlers() {
251
+ this._originalUncaughtException = process.listeners("uncaughtException").pop() ?? null;
252
+ this._originalUnhandledRejection = process.listeners("unhandledRejection").pop() ?? null;
253
+ process.on("uncaughtException", (err, origin) => {
254
+ this.captureException(err);
255
+ if (this._originalUncaughtException) this._originalUncaughtException(err, origin);
256
+ else process.exit(1);
257
+ });
258
+ process.on("unhandledRejection", (reason, promise) => {
259
+ const err = reason instanceof Error ? reason : new Error(String(reason));
260
+ this.captureException(err);
261
+ if (this._originalUnhandledRejection) this._originalUnhandledRejection(reason, promise);
262
+ });
263
+ }
264
+ _removeGlobalHandlers() {
265
+ process.removeAllListeners("uncaughtException");
266
+ process.removeAllListeners("unhandledRejection");
267
+ if (this._originalUncaughtException) process.on("uncaughtException", this._originalUncaughtException);
268
+ if (this._originalUnhandledRejection) process.on("unhandledRejection", this._originalUnhandledRejection);
269
+ }
270
+ };
271
+ function init(options) {
272
+ const client = new NodeClient(options);
273
+ globalClient = client;
274
+ return client;
275
+ }
276
+ function getClient() {
277
+ return globalClient;
278
+ }
279
+ function resetGlobal() {
280
+ if (globalClient) {
281
+ globalClient.close();
282
+ globalClient = null;
283
+ }
284
+ }
285
+ function captureException(error, options) {
286
+ const client = getClient();
287
+ return client ? client.captureException(error, options) : null;
288
+ }
289
+ function captureMessage(message, options) {
290
+ const client = getClient();
291
+ return client ? client.captureMessage(message, options) : null;
292
+ }
293
+ function addBreadcrumb(message, options) {
294
+ getClient()?.addBreadcrumb(message, options);
295
+ }
296
+ function setUser(user) {
297
+ getClient()?.setUser(user);
298
+ }
299
+ function setTag(key, value) {
300
+ getClient()?.setTag(key, value);
301
+ }
302
+ function setExtra(key, value) {
303
+ getClient()?.setExtra(key, value);
304
+ }
305
+ function setContext(key, data) {
306
+ getClient()?.setContext(key, data);
307
+ }
308
+ export {
309
+ HttpTransport,
310
+ NodeClient,
311
+ addBreadcrumb,
312
+ captureException,
313
+ captureMessage,
314
+ getClient,
315
+ init,
316
+ parseDsn,
317
+ resetGlobal,
318
+ setContext,
319
+ setExtra,
320
+ setTag,
321
+ setUser
322
+ };
package/package.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "@xrayradar/node",
3
+ "version": "0.1.0",
4
+ "description": "XrayRadar SDK for Node.js",
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
+ "@types/node": "^20.10.0"
30
+ },
31
+ "engines": {
32
+ "node": ">=20"
33
+ }
34
+ }