@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.
- package/dist/index.d.mts +92 -0
- package/dist/index.d.ts +92 -0
- package/dist/index.js +355 -0
- package/dist/index.mjs +322 -0
- package/package.json +34 -0
package/dist/index.d.mts
ADDED
|
@@ -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.d.ts
ADDED
|
@@ -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
|
+
}
|