@p-r-d-i-f-y/monitor 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,67 @@
1
+ # @p-r-d-i-f-y/monitor
2
+
3
+ Lightweight SDK for PRDify Engineering monitoring: error capture, distributed tracing, and session replay.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install @p-r-d-i-f-y/monitor
9
+ ```
10
+
11
+ Copy your project DSN from **Engineering → Monitoring** in PRDify.
12
+
13
+ ## Browser
14
+
15
+ ```ts
16
+ import { initBrowserMonitor } from '@p-r-d-i-f-y/monitor/browser';
17
+
18
+ initBrowserMonitor({
19
+ dsn: 'http://localhost:4000/developer/monitoring/ingest#<public-key>@<project-id>',
20
+ apiUrl: 'http://localhost:4000',
21
+ environment: 'production',
22
+ release: '1.0.0',
23
+ });
24
+ ```
25
+
26
+ Browser init automatically records page spans and enables session replay (clicks, inputs with masked values, navigation).
27
+
28
+ ## Node / NestJS
29
+
30
+ ```ts
31
+ import { initNodeMonitor } from '@p-r-d-i-f-y/monitor/node';
32
+
33
+ initNodeMonitor({
34
+ dsn: process.env.PRDIFY_MONITOR_DSN!,
35
+ apiUrl: process.env.PRDIFY_MONITOR_API_URL,
36
+ environment: process.env.NODE_ENV,
37
+ });
38
+ ```
39
+
40
+ ## Manual spans
41
+
42
+ ```ts
43
+ import { createMonitorClient } from '@p-r-d-i-f-y/monitor';
44
+
45
+ const client = createMonitorClient({ dsn, apiUrl });
46
+ const span = client.startTimedSpan('db.query', '/users');
47
+ await doWork();
48
+ await span.end('ok');
49
+ ```
50
+
51
+ ## Ingest endpoints
52
+
53
+ | Endpoint | Purpose |
54
+ |----------|---------|
55
+ | `POST /developer/monitoring/ingest` | Errors and breadcrumbs |
56
+ | `POST /developer/monitoring/spans` | Trace spans |
57
+ | `POST /developer/monitoring/replay` | Session replay events |
58
+
59
+ Send `X-PRDify-DSN: <public-key>` on every request.
60
+
61
+ ## Publish
62
+
63
+ ```bash
64
+ cd packages/monitor
65
+ npm run build
66
+ npm publish --access public
67
+ ```
@@ -0,0 +1,77 @@
1
+ import { type MonitorClientOptions } from './core';
2
+ export declare function initBrowserMonitor(options: MonitorClientOptions): {
3
+ addBreadcrumb: (crumb: import("./core").MonitorBreadcrumb) => void;
4
+ captureEvent: (payload: import("./core").MonitorEventPayload) => Promise<{
5
+ ok: false;
6
+ status?: undefined;
7
+ } | {
8
+ ok: boolean;
9
+ status: number;
10
+ }>;
11
+ captureException: (error: unknown, extra?: Partial<import("./core").MonitorEventPayload>) => Promise<{
12
+ ok: false;
13
+ status?: undefined;
14
+ } | {
15
+ ok: boolean;
16
+ status: number;
17
+ }>;
18
+ recordSpan: (span: import("./core").SpanPayload) => Promise<{
19
+ ok: false;
20
+ status?: undefined;
21
+ } | {
22
+ ok: boolean;
23
+ status: number;
24
+ }>;
25
+ startTimedSpan: (operation: string, route?: string, parentSpanId?: string) => {
26
+ spanId: string;
27
+ traceId: string;
28
+ end: (status?: string, metadata?: Record<string, unknown>) => Promise<void>;
29
+ };
30
+ getTraceId: () => string;
31
+ getPublicKey: () => string | null;
32
+ getIngestUrl: () => string;
33
+ } | null;
34
+ export declare function getBrowserMonitor(): {
35
+ addBreadcrumb: (crumb: import("./core").MonitorBreadcrumb) => void;
36
+ captureEvent: (payload: import("./core").MonitorEventPayload) => Promise<{
37
+ ok: false;
38
+ status?: undefined;
39
+ } | {
40
+ ok: boolean;
41
+ status: number;
42
+ }>;
43
+ captureException: (error: unknown, extra?: Partial<import("./core").MonitorEventPayload>) => Promise<{
44
+ ok: false;
45
+ status?: undefined;
46
+ } | {
47
+ ok: boolean;
48
+ status: number;
49
+ }>;
50
+ recordSpan: (span: import("./core").SpanPayload) => Promise<{
51
+ ok: false;
52
+ status?: undefined;
53
+ } | {
54
+ ok: boolean;
55
+ status: number;
56
+ }>;
57
+ startTimedSpan: (operation: string, route?: string, parentSpanId?: string) => {
58
+ spanId: string;
59
+ traceId: string;
60
+ end: (status?: string, metadata?: Record<string, unknown>) => Promise<void>;
61
+ };
62
+ getTraceId: () => string;
63
+ getPublicKey: () => string | null;
64
+ getIngestUrl: () => string;
65
+ } | null;
66
+ export declare function captureBrowserException(error: unknown, route?: string): Promise<{
67
+ ok: false;
68
+ status?: undefined;
69
+ } | {
70
+ ok: boolean;
71
+ status: number;
72
+ }> | Promise<{
73
+ ok: false;
74
+ }>;
75
+ export * from './core';
76
+ export * from './replay';
77
+ //# sourceMappingURL=browser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browser.d.ts","sourceRoot":"","sources":["../src/browser.ts"],"names":[],"mappings":"AAAA,OAAO,EAAuB,KAAK,oBAAoB,EAAE,MAAM,QAAQ,CAAC;AAKxE,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;SAoD/D;AAED,wBAAgB,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;SAEhC;AAED,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,CAAC,EAAE,MAAM;;;;;;;;GAMrE;AAED,cAAc,QAAQ,CAAC;AACvB,cAAc,UAAU,CAAC"}
@@ -0,0 +1,65 @@
1
+ import { createMonitorClient } from './core';
2
+ import { enableSessionReplay } from './replay';
3
+ var browserClient = null;
4
+ export function initBrowserMonitor(options) {
5
+ if (typeof window === 'undefined')
6
+ return null;
7
+ if (browserClient)
8
+ return browserClient;
9
+ var client = createMonitorClient(options);
10
+ browserClient = client;
11
+ window.addEventListener('error', function (event) {
12
+ var _a;
13
+ client.addBreadcrumb({
14
+ category: 'ui',
15
+ message: "Error at ".concat(event.filename || 'unknown', ":").concat(event.lineno || 0),
16
+ level: 'error',
17
+ });
18
+ void client.captureException((_a = event.error) !== null && _a !== void 0 ? _a : event.message, {
19
+ route: window.location.pathname,
20
+ severity: 'error',
21
+ source: 'browser',
22
+ }).catch(function () { return undefined; });
23
+ });
24
+ window.addEventListener('unhandledrejection', function (event) {
25
+ client.addBreadcrumb({
26
+ category: 'promise',
27
+ message: 'Unhandled promise rejection',
28
+ level: 'error',
29
+ });
30
+ void client.captureException(event.reason, {
31
+ route: window.location.pathname,
32
+ severity: 'error',
33
+ source: 'browser',
34
+ }).catch(function () { return undefined; });
35
+ });
36
+ client.addBreadcrumb({
37
+ category: 'navigation',
38
+ message: "Loaded ".concat(window.location.pathname),
39
+ level: 'info',
40
+ });
41
+ void client.recordSpan({
42
+ traceId: client.getTraceId(),
43
+ spanId: "".concat(Date.now().toString(16), "page"),
44
+ operation: "PAGE ".concat(window.location.pathname),
45
+ route: window.location.pathname,
46
+ status: 'ok',
47
+ durationMs: 0,
48
+ startedAt: new Date().toISOString(),
49
+ }).catch(function () { return undefined; });
50
+ enableSessionReplay(client, options);
51
+ return client;
52
+ }
53
+ export function getBrowserMonitor() {
54
+ return browserClient;
55
+ }
56
+ export function captureBrowserException(error, route) {
57
+ if (!browserClient)
58
+ return Promise.resolve({ ok: false });
59
+ return browserClient.captureException(error, {
60
+ route: route !== null && route !== void 0 ? route : (typeof window !== 'undefined' ? window.location.pathname : undefined),
61
+ source: 'browser',
62
+ });
63
+ }
64
+ export * from './core';
65
+ export * from './replay';
package/dist/core.d.ts ADDED
@@ -0,0 +1,74 @@
1
+ export type MonitorBreadcrumb = {
2
+ category?: string;
3
+ type?: string;
4
+ message: string;
5
+ level?: string;
6
+ data?: Record<string, unknown>;
7
+ };
8
+ export type MonitorEventPayload = {
9
+ message: string;
10
+ stack?: string;
11
+ source?: string;
12
+ severity?: 'info' | 'warning' | 'error' | 'critical';
13
+ environment?: string;
14
+ traceId?: string;
15
+ release?: string;
16
+ route?: string;
17
+ breadcrumbs?: MonitorBreadcrumb[];
18
+ metadata?: Record<string, unknown>;
19
+ };
20
+ export type MonitorClientOptions = {
21
+ dsn: string;
22
+ apiUrl?: string;
23
+ environment?: string;
24
+ release?: string;
25
+ traceId?: string;
26
+ };
27
+ export declare function parsePublicKeyFromDsn(dsn: string): string | null;
28
+ export declare function resolveIngestUrl(dsn: string, apiUrl?: string): string;
29
+ export declare function resolveSpansIngestUrl(dsn: string, apiUrl?: string): string;
30
+ export type SpanPayload = {
31
+ traceId: string;
32
+ spanId: string;
33
+ parentSpanId?: string;
34
+ operation: string;
35
+ route?: string;
36
+ status?: string;
37
+ durationMs: number;
38
+ startedAt: string;
39
+ metadata?: Record<string, unknown>;
40
+ };
41
+ export declare function createMonitorClient(options: MonitorClientOptions): {
42
+ addBreadcrumb: (crumb: MonitorBreadcrumb) => void;
43
+ captureEvent: (payload: MonitorEventPayload) => Promise<{
44
+ ok: false;
45
+ status?: undefined;
46
+ } | {
47
+ ok: boolean;
48
+ status: number;
49
+ }>;
50
+ captureException: (error: unknown, extra?: Partial<MonitorEventPayload>) => Promise<{
51
+ ok: false;
52
+ status?: undefined;
53
+ } | {
54
+ ok: boolean;
55
+ status: number;
56
+ }>;
57
+ recordSpan: (span: SpanPayload) => Promise<{
58
+ ok: false;
59
+ status?: undefined;
60
+ } | {
61
+ ok: boolean;
62
+ status: number;
63
+ }>;
64
+ startTimedSpan: (operation: string, route?: string, parentSpanId?: string) => {
65
+ spanId: string;
66
+ traceId: string;
67
+ end: (status?: string, metadata?: Record<string, unknown>) => Promise<void>;
68
+ };
69
+ getTraceId: () => string;
70
+ getPublicKey: () => string | null;
71
+ getIngestUrl: () => string;
72
+ };
73
+ export type MonitorClient = ReturnType<typeof createMonitorClient>;
74
+ //# sourceMappingURL=core.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"core.d.ts","sourceRoot":"","sources":["../src/core.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,iBAAiB,GAAG;IAC9B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAChC,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,GAAG,UAAU,CAAC;IACrD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,iBAAiB,EAAE,CAAC;IAClC,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IACjC,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAQhE;AAED,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAOrE;AAED,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAE1E;AAED,MAAM,MAAM,WAAW,GAAG;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC,CAAC;AAUF,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,oBAAoB;2BAOjC,iBAAiB;4BAKV,mBAAmB;;;;;;;8BAyBjB,OAAO,UAAU,OAAO,CAAC,mBAAmB,CAAC;;;;;;;uBAYpD,WAAW;;;;;;;gCAkBR,MAAM,UAAU,MAAM,iBAAiB,MAAM;;;uBAOxD,MAAM,aAAoB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;;;;;EA0B1E;AAED,MAAM,MAAM,aAAa,GAAG,UAAU,CAAC,OAAO,mBAAmB,CAAC,CAAC"}
package/dist/core.js ADDED
@@ -0,0 +1,211 @@
1
+ var __assign = (this && this.__assign) || function () {
2
+ __assign = Object.assign || function(t) {
3
+ for (var s, i = 1, n = arguments.length; i < n; i++) {
4
+ s = arguments[i];
5
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
6
+ t[p] = s[p];
7
+ }
8
+ return t;
9
+ };
10
+ return __assign.apply(this, arguments);
11
+ };
12
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
13
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
14
+ return new (P || (P = Promise))(function (resolve, reject) {
15
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
16
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
17
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
18
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
19
+ });
20
+ };
21
+ var __generator = (this && this.__generator) || function (thisArg, body) {
22
+ var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
23
+ return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
24
+ function verb(n) { return function (v) { return step([n, v]); }; }
25
+ function step(op) {
26
+ if (f) throw new TypeError("Generator is already executing.");
27
+ while (g && (g = 0, op[0] && (_ = 0)), _) try {
28
+ if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
29
+ if (y = 0, t) op = [op[0] & 2, t.value];
30
+ switch (op[0]) {
31
+ case 0: case 1: t = op; break;
32
+ case 4: _.label++; return { value: op[1], done: false };
33
+ case 5: _.label++; y = op[1]; op = [0]; continue;
34
+ case 7: op = _.ops.pop(); _.trys.pop(); continue;
35
+ default:
36
+ if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
37
+ if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
38
+ if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
39
+ if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
40
+ if (t[2]) _.ops.pop();
41
+ _.trys.pop(); continue;
42
+ }
43
+ op = body.call(thisArg, _);
44
+ } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
45
+ if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
46
+ }
47
+ };
48
+ var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
49
+ if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
50
+ if (ar || !(i in from)) {
51
+ if (!ar) ar = Array.prototype.slice.call(from, 0, i);
52
+ ar[i] = from[i];
53
+ }
54
+ }
55
+ return to.concat(ar || Array.prototype.slice.call(from));
56
+ };
57
+ export function parsePublicKeyFromDsn(dsn) {
58
+ var trimmed = dsn.trim();
59
+ if (!trimmed)
60
+ return null;
61
+ var hashIndex = trimmed.indexOf('#');
62
+ var fragment = hashIndex >= 0 ? trimmed.slice(hashIndex + 1) : trimmed;
63
+ var atIndex = fragment.indexOf('@');
64
+ var key = atIndex >= 0 ? fragment.slice(0, atIndex) : fragment;
65
+ return key.trim() || null;
66
+ }
67
+ export function resolveIngestUrl(dsn, apiUrl) {
68
+ if (apiUrl === null || apiUrl === void 0 ? void 0 : apiUrl.trim()) {
69
+ return "".concat(apiUrl.replace(/\/+$/, ''), "/developer/monitoring/ingest");
70
+ }
71
+ var beforeHash = dsn.trim().split('#')[0];
72
+ if (beforeHash.startsWith('http'))
73
+ return beforeHash.replace(/\/+$/, '');
74
+ return 'http://localhost:4000/developer/monitoring/ingest';
75
+ }
76
+ export function resolveSpansIngestUrl(dsn, apiUrl) {
77
+ return resolveIngestUrl(dsn, apiUrl).replace(/\/ingest$/, '/spans');
78
+ }
79
+ function randomSpanId() {
80
+ return Math.random().toString(16).slice(2, 10);
81
+ }
82
+ function randomTraceId() {
83
+ return "".concat(Date.now().toString(16)).concat(Math.random().toString(16).slice(2, 10));
84
+ }
85
+ export function createMonitorClient(options) {
86
+ var _this = this;
87
+ var _a;
88
+ var publicKey = parsePublicKeyFromDsn(options.dsn);
89
+ var ingestUrl = resolveIngestUrl(options.dsn, options.apiUrl);
90
+ var spansUrl = resolveSpansIngestUrl(options.dsn, options.apiUrl);
91
+ var breadcrumbs = [];
92
+ var activeTraceId = (_a = options.traceId) !== null && _a !== void 0 ? _a : randomTraceId();
93
+ var addBreadcrumb = function (crumb) {
94
+ breadcrumbs.push(crumb);
95
+ if (breadcrumbs.length > 50)
96
+ breadcrumbs.shift();
97
+ };
98
+ var captureEvent = function (payload) { return __awaiter(_this, void 0, void 0, function () {
99
+ var response, _a;
100
+ var _b, _c, _d, _e, _f;
101
+ return __generator(this, function (_g) {
102
+ switch (_g.label) {
103
+ case 0:
104
+ if (!publicKey)
105
+ return [2 /*return*/, { ok: false }];
106
+ _g.label = 1;
107
+ case 1:
108
+ _g.trys.push([1, 3, , 4]);
109
+ return [4 /*yield*/, fetch(ingestUrl, {
110
+ method: 'POST',
111
+ headers: {
112
+ 'Content-Type': 'application/json',
113
+ 'X-PRDify-DSN': publicKey,
114
+ },
115
+ body: JSON.stringify(__assign(__assign({}, payload), { environment: (_b = payload.environment) !== null && _b !== void 0 ? _b : options.environment, release: (_c = payload.release) !== null && _c !== void 0 ? _c : options.release, traceId: (_e = (_d = payload.traceId) !== null && _d !== void 0 ? _d : options.traceId) !== null && _e !== void 0 ? _e : activeTraceId, breadcrumbs: __spreadArray(__spreadArray([], breadcrumbs, true), ((_f = payload.breadcrumbs) !== null && _f !== void 0 ? _f : []), true), source: payload.source || 'sdk' })),
116
+ keepalive: true,
117
+ })];
118
+ case 2:
119
+ response = _g.sent();
120
+ return [2 /*return*/, { ok: response.ok, status: response.status }];
121
+ case 3:
122
+ _a = _g.sent();
123
+ return [2 /*return*/, { ok: false, status: 0 }];
124
+ case 4: return [2 /*return*/];
125
+ }
126
+ });
127
+ }); };
128
+ var captureException = function (error, extra) { return __awaiter(_this, void 0, void 0, function () {
129
+ var message, stack;
130
+ return __generator(this, function (_a) {
131
+ message = error instanceof Error ? error.message : String(error);
132
+ stack = error instanceof Error ? error.stack : undefined;
133
+ return [2 /*return*/, captureEvent(__assign({ message: message, stack: stack, severity: 'error', traceId: activeTraceId }, extra))];
134
+ });
135
+ }); };
136
+ var recordSpan = function (span) { return __awaiter(_this, void 0, void 0, function () {
137
+ var response, _a;
138
+ return __generator(this, function (_b) {
139
+ switch (_b.label) {
140
+ case 0:
141
+ if (!publicKey)
142
+ return [2 /*return*/, { ok: false }];
143
+ _b.label = 1;
144
+ case 1:
145
+ _b.trys.push([1, 3, , 4]);
146
+ return [4 /*yield*/, fetch(spansUrl, {
147
+ method: 'POST',
148
+ headers: {
149
+ 'Content-Type': 'application/json',
150
+ 'X-PRDify-DSN': publicKey,
151
+ },
152
+ body: JSON.stringify({ spans: [span] }),
153
+ keepalive: true,
154
+ })];
155
+ case 2:
156
+ response = _b.sent();
157
+ return [2 /*return*/, { ok: response.ok, status: response.status }];
158
+ case 3:
159
+ _a = _b.sent();
160
+ return [2 /*return*/, { ok: false, status: 0 }];
161
+ case 4: return [2 /*return*/];
162
+ }
163
+ });
164
+ }); };
165
+ var startTimedSpan = function (operation, route, parentSpanId) {
166
+ var spanId = randomSpanId();
167
+ var startedAt = new Date().toISOString();
168
+ var startedMs = Date.now();
169
+ return {
170
+ spanId: spanId,
171
+ traceId: activeTraceId,
172
+ end: function () {
173
+ var args_1 = [];
174
+ for (var _i = 0; _i < arguments.length; _i++) {
175
+ args_1[_i] = arguments[_i];
176
+ }
177
+ return __awaiter(_this, __spreadArray([], args_1, true), void 0, function (status, metadata) {
178
+ if (status === void 0) { status = 'ok'; }
179
+ return __generator(this, function (_a) {
180
+ switch (_a.label) {
181
+ case 0: return [4 /*yield*/, recordSpan({
182
+ traceId: activeTraceId,
183
+ spanId: spanId,
184
+ parentSpanId: parentSpanId,
185
+ operation: operation,
186
+ route: route,
187
+ status: status,
188
+ durationMs: Date.now() - startedMs,
189
+ startedAt: startedAt,
190
+ metadata: metadata,
191
+ })];
192
+ case 1:
193
+ _a.sent();
194
+ return [2 /*return*/];
195
+ }
196
+ });
197
+ });
198
+ },
199
+ };
200
+ };
201
+ return {
202
+ addBreadcrumb: addBreadcrumb,
203
+ captureEvent: captureEvent,
204
+ captureException: captureException,
205
+ recordSpan: recordSpan,
206
+ startTimedSpan: startTimedSpan,
207
+ getTraceId: function () { return activeTraceId; },
208
+ getPublicKey: function () { return publicKey; },
209
+ getIngestUrl: function () { return ingestUrl; },
210
+ };
211
+ }
@@ -0,0 +1,5 @@
1
+ export * from './core';
2
+ export * from './browser';
3
+ export * from './node';
4
+ export * from './replay';
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,QAAQ,CAAC;AACvB,cAAc,WAAW,CAAC;AAC1B,cAAc,QAAQ,CAAC;AACvB,cAAc,UAAU,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,4 @@
1
+ export * from './core';
2
+ export * from './browser';
3
+ export * from './node';
4
+ export * from './replay';
package/dist/node.d.ts ADDED
@@ -0,0 +1,67 @@
1
+ import { type MonitorClientOptions } from './core';
2
+ export declare function initNodeMonitor(options: MonitorClientOptions): {
3
+ addBreadcrumb: (crumb: import("./core").MonitorBreadcrumb) => void;
4
+ captureEvent: (payload: import("./core").MonitorEventPayload) => Promise<{
5
+ ok: false;
6
+ status?: undefined;
7
+ } | {
8
+ ok: boolean;
9
+ status: number;
10
+ }>;
11
+ captureException: (error: unknown, extra?: Partial<import("./core").MonitorEventPayload>) => Promise<{
12
+ ok: false;
13
+ status?: undefined;
14
+ } | {
15
+ ok: boolean;
16
+ status: number;
17
+ }>;
18
+ recordSpan: (span: import("./core").SpanPayload) => Promise<{
19
+ ok: false;
20
+ status?: undefined;
21
+ } | {
22
+ ok: boolean;
23
+ status: number;
24
+ }>;
25
+ startTimedSpan: (operation: string, route?: string, parentSpanId?: string) => {
26
+ spanId: string;
27
+ traceId: string;
28
+ end: (status?: string, metadata?: Record<string, unknown>) => Promise<void>;
29
+ };
30
+ getTraceId: () => string;
31
+ getPublicKey: () => string | null;
32
+ getIngestUrl: () => string;
33
+ };
34
+ export declare function getNodeMonitor(): {
35
+ addBreadcrumb: (crumb: import("./core").MonitorBreadcrumb) => void;
36
+ captureEvent: (payload: import("./core").MonitorEventPayload) => Promise<{
37
+ ok: false;
38
+ status?: undefined;
39
+ } | {
40
+ ok: boolean;
41
+ status: number;
42
+ }>;
43
+ captureException: (error: unknown, extra?: Partial<import("./core").MonitorEventPayload>) => Promise<{
44
+ ok: false;
45
+ status?: undefined;
46
+ } | {
47
+ ok: boolean;
48
+ status: number;
49
+ }>;
50
+ recordSpan: (span: import("./core").SpanPayload) => Promise<{
51
+ ok: false;
52
+ status?: undefined;
53
+ } | {
54
+ ok: boolean;
55
+ status: number;
56
+ }>;
57
+ startTimedSpan: (operation: string, route?: string, parentSpanId?: string) => {
58
+ spanId: string;
59
+ traceId: string;
60
+ end: (status?: string, metadata?: Record<string, unknown>) => Promise<void>;
61
+ };
62
+ getTraceId: () => string;
63
+ getPublicKey: () => string | null;
64
+ getIngestUrl: () => string;
65
+ } | null;
66
+ export * from './core';
67
+ //# sourceMappingURL=node.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"node.d.ts","sourceRoot":"","sources":["../src/node.ts"],"names":[],"mappings":"AAAA,OAAO,EAAuB,KAAK,oBAAoB,EAAE,MAAM,QAAQ,CAAC;AAIxE,wBAAgB,eAAe,CAAC,OAAO,EAAE,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAoB5D;AAED,wBAAgB,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;SAE7B;AAED,cAAc,QAAQ,CAAC"}
package/dist/node.js ADDED
@@ -0,0 +1,25 @@
1
+ import { createMonitorClient } from './core';
2
+ var nodeClient = null;
3
+ export function initNodeMonitor(options) {
4
+ if (nodeClient)
5
+ return nodeClient;
6
+ var client = createMonitorClient(options);
7
+ nodeClient = client;
8
+ process.on('uncaughtException', function (error) {
9
+ void client.captureException(error, {
10
+ route: 'process:uncaughtException',
11
+ source: 'node',
12
+ });
13
+ });
14
+ process.on('unhandledRejection', function (reason) {
15
+ void client.captureException(reason, {
16
+ route: 'process:unhandledRejection',
17
+ source: 'node',
18
+ });
19
+ });
20
+ return client;
21
+ }
22
+ export function getNodeMonitor() {
23
+ return nodeClient;
24
+ }
25
+ export * from './core';
@@ -0,0 +1,15 @@
1
+ import type { MonitorClient } from './core';
2
+ export type ReplayEvent = {
3
+ type: 'navigation' | 'click' | 'input' | 'scroll' | 'error' | 'custom';
4
+ timestamp: string;
5
+ route?: string;
6
+ message?: string;
7
+ data?: Record<string, unknown>;
8
+ };
9
+ export declare function enableSessionReplay(client: MonitorClient, options: {
10
+ dsn: string;
11
+ apiUrl?: string;
12
+ flushIntervalMs?: number;
13
+ sessionId?: string;
14
+ }): () => void;
15
+ //# sourceMappingURL=replay.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"replay.d.ts","sourceRoot":"","sources":["../src/replay.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,QAAQ,CAAC;AAG5C,MAAM,MAAM,WAAW,GAAG;IACxB,IAAI,EAAE,YAAY,GAAG,OAAO,GAAG,OAAO,GAAG,QAAQ,GAAG,OAAO,GAAG,QAAQ,CAAC;IACvE,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAChC,CAAC;AAeF,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,aAAa,EACrB,OAAO,EAAE;IACP,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,cA+GF"}
package/dist/replay.js ADDED
@@ -0,0 +1,170 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ var __generator = (this && this.__generator) || function (thisArg, body) {
11
+ var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
12
+ return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
13
+ function verb(n) { return function (v) { return step([n, v]); }; }
14
+ function step(op) {
15
+ if (f) throw new TypeError("Generator is already executing.");
16
+ while (g && (g = 0, op[0] && (_ = 0)), _) try {
17
+ if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
18
+ if (y = 0, t) op = [op[0] & 2, t.value];
19
+ switch (op[0]) {
20
+ case 0: case 1: t = op; break;
21
+ case 4: _.label++; return { value: op[1], done: false };
22
+ case 5: _.label++; y = op[1]; op = [0]; continue;
23
+ case 7: op = _.ops.pop(); _.trys.pop(); continue;
24
+ default:
25
+ if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
26
+ if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
27
+ if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
28
+ if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
29
+ if (t[2]) _.ops.pop();
30
+ _.trys.pop(); continue;
31
+ }
32
+ op = body.call(thisArg, _);
33
+ } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
34
+ if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
35
+ }
36
+ };
37
+ import { parsePublicKeyFromDsn, resolveIngestUrl } from './core';
38
+ function resolveReplayUrl(dsn, apiUrl) {
39
+ return resolveIngestUrl(dsn, apiUrl).replace(/\/ingest$/, '/replay');
40
+ }
41
+ function randomSessionId() {
42
+ return "sess_".concat(Date.now().toString(16)).concat(Math.random().toString(16).slice(2, 8));
43
+ }
44
+ function maskValue(value) {
45
+ if (value.length <= 2)
46
+ return '**';
47
+ return "".concat(value.slice(0, 1)).concat('*'.repeat(Math.min(value.length - 1, 8)));
48
+ }
49
+ export function enableSessionReplay(client, options) {
50
+ var _this = this;
51
+ var _a, _b;
52
+ if (typeof window === 'undefined' || typeof document === 'undefined')
53
+ return function () { return undefined; };
54
+ var publicKey = parsePublicKeyFromDsn(options.dsn);
55
+ var replayUrl = resolveReplayUrl(options.dsn, options.apiUrl);
56
+ var sessionId = (_a = options.sessionId) !== null && _a !== void 0 ? _a : randomSessionId();
57
+ var events = [];
58
+ var startedAt = Date.now();
59
+ var flushing = false;
60
+ var record = function (event) {
61
+ events.push(event);
62
+ if (events.length >= 40) {
63
+ void flush();
64
+ }
65
+ };
66
+ var flush = function () { return __awaiter(_this, void 0, void 0, function () {
67
+ var batch, _a;
68
+ return __generator(this, function (_b) {
69
+ switch (_b.label) {
70
+ case 0:
71
+ if (!publicKey || flushing || !events.length)
72
+ return [2 /*return*/];
73
+ flushing = true;
74
+ batch = events.splice(0, events.length);
75
+ _b.label = 1;
76
+ case 1:
77
+ _b.trys.push([1, 3, 4, 5]);
78
+ return [4 /*yield*/, fetch(replayUrl, {
79
+ method: 'POST',
80
+ headers: {
81
+ 'Content-Type': 'application/json',
82
+ 'X-PRDify-DSN': publicKey,
83
+ },
84
+ body: JSON.stringify({
85
+ sessionId: sessionId,
86
+ traceId: client.getTraceId(),
87
+ route: window.location.pathname,
88
+ userAgent: navigator.userAgent,
89
+ durationMs: Date.now() - startedAt,
90
+ hasError: batch.some(function (event) { return event.type === 'error'; }),
91
+ events: batch,
92
+ }),
93
+ keepalive: true,
94
+ })];
95
+ case 2:
96
+ _b.sent();
97
+ return [3 /*break*/, 5];
98
+ case 3:
99
+ _a = _b.sent();
100
+ events.unshift.apply(events, batch);
101
+ return [3 /*break*/, 5];
102
+ case 4:
103
+ flushing = false;
104
+ return [7 /*endfinally*/];
105
+ case 5: return [2 /*return*/];
106
+ }
107
+ });
108
+ }); };
109
+ var onClick = function (event) {
110
+ var _a, _b, _c;
111
+ var target = event.target;
112
+ record({
113
+ type: 'click',
114
+ timestamp: new Date().toISOString(),
115
+ route: window.location.pathname,
116
+ message: (_b = (_a = target === null || target === void 0 ? void 0 : target.tagName) === null || _a === void 0 ? void 0 : _a.toLowerCase()) !== null && _b !== void 0 ? _b : 'element',
117
+ data: {
118
+ id: (target === null || target === void 0 ? void 0 : target.id) || undefined,
119
+ className: (target === null || target === void 0 ? void 0 : target.className) || undefined,
120
+ text: ((_c = target === null || target === void 0 ? void 0 : target.textContent) === null || _c === void 0 ? void 0 : _c.trim().slice(0, 80)) || undefined,
121
+ },
122
+ });
123
+ };
124
+ var onInput = function (event) {
125
+ var _a;
126
+ var target = event.target;
127
+ if (!target)
128
+ return;
129
+ record({
130
+ type: 'input',
131
+ timestamp: new Date().toISOString(),
132
+ route: window.location.pathname,
133
+ message: target.name || target.id || target.tagName.toLowerCase(),
134
+ data: {
135
+ value: maskValue((_a = target.value) !== null && _a !== void 0 ? _a : ''),
136
+ },
137
+ });
138
+ };
139
+ var onError = function () {
140
+ record({
141
+ type: 'error',
142
+ timestamp: new Date().toISOString(),
143
+ route: window.location.pathname,
144
+ message: 'Captured error during session',
145
+ });
146
+ void flush();
147
+ };
148
+ record({
149
+ type: 'navigation',
150
+ timestamp: new Date().toISOString(),
151
+ route: window.location.pathname,
152
+ message: "Session started on ".concat(window.location.pathname),
153
+ });
154
+ document.addEventListener('click', onClick, true);
155
+ document.addEventListener('input', onInput, true);
156
+ window.addEventListener('error', onError);
157
+ window.addEventListener('beforeunload', function () {
158
+ void flush();
159
+ });
160
+ var interval = window.setInterval(function () {
161
+ void flush();
162
+ }, (_b = options.flushIntervalMs) !== null && _b !== void 0 ? _b : 12000);
163
+ return function () {
164
+ document.removeEventListener('click', onClick, true);
165
+ document.removeEventListener('input', onInput, true);
166
+ window.removeEventListener('error', onError);
167
+ window.clearInterval(interval);
168
+ void flush();
169
+ };
170
+ }
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "@p-r-d-i-f-y/monitor",
3
+ "version": "0.1.0",
4
+ "description": "PRDify error monitoring, tracing, and session replay SDK",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js"
12
+ },
13
+ "./browser": {
14
+ "types": "./dist/browser.d.ts",
15
+ "import": "./dist/browser.js"
16
+ },
17
+ "./node": {
18
+ "types": "./dist/node.d.ts",
19
+ "import": "./dist/node.js"
20
+ }
21
+ },
22
+ "files": [
23
+ "dist",
24
+ "README.md"
25
+ ],
26
+ "scripts": {
27
+ "build": "tsc -p tsconfig.json",
28
+ "prepublishOnly": "npm run build"
29
+ },
30
+ "keywords": [
31
+ "monitoring",
32
+ "errors",
33
+ "tracing",
34
+ "session-replay",
35
+ "prdify"
36
+ ],
37
+ "license": "MIT",
38
+ "publishConfig": {
39
+ "access": "public"
40
+ },
41
+ "devDependencies": {
42
+ "@types/node": "^22.13.10",
43
+ "typescript": "^5.8.2"
44
+ }
45
+ }