sitepong 0.0.6 → 0.0.7

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,119 @@
1
+ /**
2
+ * SitePong SDK Types
3
+ */
4
+ interface SitePongConfig {
5
+ /** DSN in format: https://<public_key>@ingest.sitepong.com/<project_id> */
6
+ dsn?: string;
7
+ /** API key in format: sp_live_xxx or sp_test_xxx */
8
+ apiKey?: string;
9
+ /** Environment name (e.g., 'production', 'staging', 'development') */
10
+ environment?: string;
11
+ /** Release/version string */
12
+ release?: string;
13
+ /** Enable debug logging */
14
+ debug?: boolean;
15
+ /** Enable/disable the SDK entirely */
16
+ enabled?: boolean;
17
+ /** Sample rate for errors (0.0 to 1.0) */
18
+ sampleRate?: number;
19
+ /** Maximum number of breadcrumbs to keep */
20
+ maxBreadcrumbs?: number;
21
+ /** Hook to modify or filter events before sending */
22
+ beforeSend?: (event: ErrorEvent) => ErrorEvent | null;
23
+ /** Auto-capture configuration */
24
+ autoCapture?: {
25
+ errors?: boolean;
26
+ unhandledRejections?: boolean;
27
+ console?: boolean;
28
+ };
29
+ /** Custom API endpoint (for self-hosted) */
30
+ apiEndpoint?: string;
31
+ }
32
+ interface User {
33
+ id?: string;
34
+ email?: string;
35
+ username?: string;
36
+ [key: string]: unknown;
37
+ }
38
+ interface Breadcrumb {
39
+ type: "default" | "http" | "navigation" | "ui" | "console" | "error";
40
+ category?: string;
41
+ message?: string;
42
+ level?: "fatal" | "error" | "warning" | "info" | "debug";
43
+ timestamp: number;
44
+ data?: Record<string, unknown>;
45
+ }
46
+ interface ErrorEvent {
47
+ event_id: string;
48
+ timestamp: string;
49
+ level: "fatal" | "error" | "warning" | "info" | "debug";
50
+ message: string;
51
+ exception?: {
52
+ type: string;
53
+ value: string;
54
+ stacktrace?: StackFrame[];
55
+ };
56
+ tags?: Record<string, string>;
57
+ extra?: Record<string, unknown>;
58
+ user?: User;
59
+ breadcrumbs?: Breadcrumb[];
60
+ context?: {
61
+ browser?: BrowserContext;
62
+ os?: OSContext;
63
+ device?: DeviceContext;
64
+ [key: string]: unknown;
65
+ };
66
+ environment?: string;
67
+ release?: string;
68
+ request?: {
69
+ url?: string;
70
+ method?: string;
71
+ headers?: Record<string, string>;
72
+ };
73
+ }
74
+ interface StackFrame {
75
+ filename?: string;
76
+ function?: string;
77
+ lineno?: number;
78
+ colno?: number;
79
+ in_app?: boolean;
80
+ context_line?: string;
81
+ pre_context?: string[];
82
+ post_context?: string[];
83
+ }
84
+ interface BrowserContext {
85
+ name?: string;
86
+ version?: string;
87
+ }
88
+ interface OSContext {
89
+ name?: string;
90
+ version?: string;
91
+ }
92
+ interface DeviceContext {
93
+ family?: string;
94
+ model?: string;
95
+ brand?: string;
96
+ }
97
+ type LogLevel = "fatal" | "error" | "warning" | "info" | "debug";
98
+ interface CaptureContext {
99
+ tags?: Record<string, string>;
100
+ extra?: Record<string, unknown>;
101
+ user?: User;
102
+ level?: LogLevel;
103
+ fingerprint?: string[];
104
+ }
105
+
106
+ /**
107
+ * Add a breadcrumb to the trail
108
+ */
109
+ declare function addBreadcrumb(breadcrumb: Omit<Breadcrumb, "timestamp">): void;
110
+ /**
111
+ * Get all breadcrumbs
112
+ */
113
+ declare function getBreadcrumbs(): Breadcrumb[];
114
+ /**
115
+ * Clear all breadcrumbs
116
+ */
117
+ declare function clearBreadcrumbs(): void;
118
+
119
+ export { type CaptureContext as C, type ErrorEvent as E, type LogLevel as L, type SitePongConfig as S, type User as U, addBreadcrumb as a, clearBreadcrumbs as c, getBreadcrumbs as g };
@@ -0,0 +1,119 @@
1
+ /**
2
+ * SitePong SDK Types
3
+ */
4
+ interface SitePongConfig {
5
+ /** DSN in format: https://<public_key>@ingest.sitepong.com/<project_id> */
6
+ dsn?: string;
7
+ /** API key in format: sp_live_xxx or sp_test_xxx */
8
+ apiKey?: string;
9
+ /** Environment name (e.g., 'production', 'staging', 'development') */
10
+ environment?: string;
11
+ /** Release/version string */
12
+ release?: string;
13
+ /** Enable debug logging */
14
+ debug?: boolean;
15
+ /** Enable/disable the SDK entirely */
16
+ enabled?: boolean;
17
+ /** Sample rate for errors (0.0 to 1.0) */
18
+ sampleRate?: number;
19
+ /** Maximum number of breadcrumbs to keep */
20
+ maxBreadcrumbs?: number;
21
+ /** Hook to modify or filter events before sending */
22
+ beforeSend?: (event: ErrorEvent) => ErrorEvent | null;
23
+ /** Auto-capture configuration */
24
+ autoCapture?: {
25
+ errors?: boolean;
26
+ unhandledRejections?: boolean;
27
+ console?: boolean;
28
+ };
29
+ /** Custom API endpoint (for self-hosted) */
30
+ apiEndpoint?: string;
31
+ }
32
+ interface User {
33
+ id?: string;
34
+ email?: string;
35
+ username?: string;
36
+ [key: string]: unknown;
37
+ }
38
+ interface Breadcrumb {
39
+ type: "default" | "http" | "navigation" | "ui" | "console" | "error";
40
+ category?: string;
41
+ message?: string;
42
+ level?: "fatal" | "error" | "warning" | "info" | "debug";
43
+ timestamp: number;
44
+ data?: Record<string, unknown>;
45
+ }
46
+ interface ErrorEvent {
47
+ event_id: string;
48
+ timestamp: string;
49
+ level: "fatal" | "error" | "warning" | "info" | "debug";
50
+ message: string;
51
+ exception?: {
52
+ type: string;
53
+ value: string;
54
+ stacktrace?: StackFrame[];
55
+ };
56
+ tags?: Record<string, string>;
57
+ extra?: Record<string, unknown>;
58
+ user?: User;
59
+ breadcrumbs?: Breadcrumb[];
60
+ context?: {
61
+ browser?: BrowserContext;
62
+ os?: OSContext;
63
+ device?: DeviceContext;
64
+ [key: string]: unknown;
65
+ };
66
+ environment?: string;
67
+ release?: string;
68
+ request?: {
69
+ url?: string;
70
+ method?: string;
71
+ headers?: Record<string, string>;
72
+ };
73
+ }
74
+ interface StackFrame {
75
+ filename?: string;
76
+ function?: string;
77
+ lineno?: number;
78
+ colno?: number;
79
+ in_app?: boolean;
80
+ context_line?: string;
81
+ pre_context?: string[];
82
+ post_context?: string[];
83
+ }
84
+ interface BrowserContext {
85
+ name?: string;
86
+ version?: string;
87
+ }
88
+ interface OSContext {
89
+ name?: string;
90
+ version?: string;
91
+ }
92
+ interface DeviceContext {
93
+ family?: string;
94
+ model?: string;
95
+ brand?: string;
96
+ }
97
+ type LogLevel = "fatal" | "error" | "warning" | "info" | "debug";
98
+ interface CaptureContext {
99
+ tags?: Record<string, string>;
100
+ extra?: Record<string, unknown>;
101
+ user?: User;
102
+ level?: LogLevel;
103
+ fingerprint?: string[];
104
+ }
105
+
106
+ /**
107
+ * Add a breadcrumb to the trail
108
+ */
109
+ declare function addBreadcrumb(breadcrumb: Omit<Breadcrumb, "timestamp">): void;
110
+ /**
111
+ * Get all breadcrumbs
112
+ */
113
+ declare function getBreadcrumbs(): Breadcrumb[];
114
+ /**
115
+ * Clear all breadcrumbs
116
+ */
117
+ declare function clearBreadcrumbs(): void;
118
+
119
+ export { type CaptureContext as C, type ErrorEvent as E, type LogLevel as L, type SitePongConfig as S, type User as U, addBreadcrumb as a, clearBreadcrumbs as c, getBreadcrumbs as g };
@@ -0,0 +1,47 @@
1
+ import { RequestHandler, ErrorRequestHandler, Request, Response, NextFunction } from 'express';
2
+
3
+ /**
4
+ * Express.js integration for SitePong
5
+ *
6
+ * @example
7
+ * ```ts
8
+ * import express from 'express';
9
+ * import { sitePongMiddleware, sitePongErrorHandler } from '@sitepong/react/express';
10
+ *
11
+ * const app = express();
12
+ *
13
+ * // Add request tracking middleware
14
+ * app.use(sitePongMiddleware());
15
+ *
16
+ * // Your routes here...
17
+ *
18
+ * // Add error handler (must be last)
19
+ * app.use(sitePongErrorHandler());
20
+ * ```
21
+ */
22
+
23
+ /**
24
+ * Express middleware to track requests and add context
25
+ */
26
+ declare function sitePongMiddleware(): RequestHandler;
27
+ /**
28
+ * Express error handler middleware
29
+ * Must be added after all other middleware and routes
30
+ */
31
+ declare function sitePongErrorHandler(): ErrorRequestHandler;
32
+ /**
33
+ * Create a request handler that wraps async functions and catches errors
34
+ *
35
+ * @example
36
+ * ```ts
37
+ * import { wrapHandler } from '@sitepong/react/express';
38
+ *
39
+ * app.get('/api/users', wrapHandler(async (req, res) => {
40
+ * const users = await getUsers();
41
+ * res.json(users);
42
+ * }));
43
+ * ```
44
+ */
45
+ declare function wrapHandler<T extends (req: Request, res: Response, next: NextFunction) => Promise<unknown>>(handler: T): (req: Request, res: Response, next: NextFunction) => void;
46
+
47
+ export { sitePongErrorHandler, sitePongMiddleware, wrapHandler };
@@ -0,0 +1,47 @@
1
+ import { RequestHandler, ErrorRequestHandler, Request, Response, NextFunction } from 'express';
2
+
3
+ /**
4
+ * Express.js integration for SitePong
5
+ *
6
+ * @example
7
+ * ```ts
8
+ * import express from 'express';
9
+ * import { sitePongMiddleware, sitePongErrorHandler } from '@sitepong/react/express';
10
+ *
11
+ * const app = express();
12
+ *
13
+ * // Add request tracking middleware
14
+ * app.use(sitePongMiddleware());
15
+ *
16
+ * // Your routes here...
17
+ *
18
+ * // Add error handler (must be last)
19
+ * app.use(sitePongErrorHandler());
20
+ * ```
21
+ */
22
+
23
+ /**
24
+ * Express middleware to track requests and add context
25
+ */
26
+ declare function sitePongMiddleware(): RequestHandler;
27
+ /**
28
+ * Express error handler middleware
29
+ * Must be added after all other middleware and routes
30
+ */
31
+ declare function sitePongErrorHandler(): ErrorRequestHandler;
32
+ /**
33
+ * Create a request handler that wraps async functions and catches errors
34
+ *
35
+ * @example
36
+ * ```ts
37
+ * import { wrapHandler } from '@sitepong/react/express';
38
+ *
39
+ * app.get('/api/users', wrapHandler(async (req, res) => {
40
+ * const users = await getUsers();
41
+ * res.json(users);
42
+ * }));
43
+ * ```
44
+ */
45
+ declare function wrapHandler<T extends (req: Request, res: Response, next: NextFunction) => Promise<unknown>>(handler: T): (req: Request, res: Response, next: NextFunction) => void;
46
+
47
+ export { sitePongErrorHandler, sitePongMiddleware, wrapHandler };
@@ -0,0 +1,128 @@
1
+ 'use strict';
2
+
3
+ // src/core/dsn.ts
4
+
5
+ // src/utils/platform.ts
6
+ function detectPlatform() {
7
+ if (typeof navigator !== "undefined" && navigator.product === "ReactNative") {
8
+ return "react-native";
9
+ }
10
+ if (typeof process !== "undefined" && process.versions && process.versions.node) {
11
+ return "node";
12
+ }
13
+ if (typeof window !== "undefined" && typeof document !== "undefined") {
14
+ return "browser";
15
+ }
16
+ return "unknown";
17
+ }
18
+ detectPlatform();
19
+
20
+ // src/core/breadcrumbs.ts
21
+ var MAX_BREADCRUMBS = 100;
22
+ var BreadcrumbManager = class {
23
+ constructor() {
24
+ this.breadcrumbs = [];
25
+ this.maxBreadcrumbs = MAX_BREADCRUMBS;
26
+ }
27
+ setMaxBreadcrumbs(max) {
28
+ this.maxBreadcrumbs = max;
29
+ this.trim();
30
+ }
31
+ add(breadcrumb) {
32
+ const entry = {
33
+ ...breadcrumb,
34
+ timestamp: Date.now()
35
+ };
36
+ this.breadcrumbs.push(entry);
37
+ this.trim();
38
+ }
39
+ getAll() {
40
+ return [...this.breadcrumbs];
41
+ }
42
+ clear() {
43
+ this.breadcrumbs = [];
44
+ }
45
+ trim() {
46
+ if (this.breadcrumbs.length > this.maxBreadcrumbs) {
47
+ this.breadcrumbs = this.breadcrumbs.slice(-this.maxBreadcrumbs);
48
+ }
49
+ }
50
+ };
51
+ var breadcrumbs = new BreadcrumbManager();
52
+ function addBreadcrumb(breadcrumb) {
53
+ breadcrumbs.add(breadcrumb);
54
+ }
55
+ async function captureServerException(error, context) {
56
+ {
57
+ return null;
58
+ }
59
+ }
60
+
61
+ // src/integrations/express.ts
62
+ function sitePongMiddleware() {
63
+ return (req, _res, next) => {
64
+ addBreadcrumb({
65
+ type: "http",
66
+ category: "http.request",
67
+ message: `${req.method} ${req.url}`,
68
+ level: "info",
69
+ data: {
70
+ method: req.method,
71
+ url: req.url,
72
+ query: req.query
73
+ }
74
+ });
75
+ next();
76
+ };
77
+ }
78
+ function sitePongErrorHandler() {
79
+ return (err, req, res, next) => {
80
+ captureServerException(err, {
81
+ request: {
82
+ url: req.url,
83
+ method: req.method,
84
+ headers: normalizeHeaders(req.headers),
85
+ query: req.query,
86
+ body: req.body,
87
+ ip: req.ip
88
+ },
89
+ tags: {
90
+ "http.method": req.method,
91
+ "http.status_code": String(res.statusCode || 500)
92
+ }
93
+ });
94
+ next(err);
95
+ };
96
+ }
97
+ function normalizeHeaders(headers) {
98
+ const normalized = {};
99
+ for (const [key, value] of Object.entries(headers)) {
100
+ if (value) {
101
+ normalized[key] = Array.isArray(value) ? value.join(", ") : value;
102
+ }
103
+ }
104
+ delete normalized["authorization"];
105
+ delete normalized["cookie"];
106
+ delete normalized["x-api-key"];
107
+ return normalized;
108
+ }
109
+ function wrapHandler(handler) {
110
+ return (req, res, next) => {
111
+ Promise.resolve(handler(req, res, next)).catch((err) => {
112
+ captureServerException(err, {
113
+ request: {
114
+ url: req.url,
115
+ method: req.method,
116
+ headers: normalizeHeaders(req.headers)
117
+ }
118
+ });
119
+ next(err);
120
+ });
121
+ };
122
+ }
123
+
124
+ exports.sitePongErrorHandler = sitePongErrorHandler;
125
+ exports.sitePongMiddleware = sitePongMiddleware;
126
+ exports.wrapHandler = wrapHandler;
127
+ //# sourceMappingURL=index.js.map
128
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/utils/platform.ts","../../src/core/breadcrumbs.ts","../../src/server/index.ts","../../src/integrations/express.ts"],"names":[],"mappings":";;;;;AAUO,SAAS,cAAA,GAA2B;AAEzC,EAAA,IACE,OAAO,SAAA,KAAc,WAAA,IACrB,SAAA,CAAU,YAAY,aAAA,EACtB;AACA,IAAA,OAAO,cAAA;AAAA,EACT;AAGA,EAAA,IACE,OAAO,OAAA,KAAY,WAAA,IACnB,QAAQ,QAAA,IACR,OAAA,CAAQ,SAAS,IAAA,EACjB;AACA,IAAA,OAAO,MAAA;AAAA,EACT;AAGA,EAAA,IAAI,OAAO,MAAA,KAAW,WAAA,IAAe,OAAO,aAAa,WAAA,EAAa;AACpE,IAAA,OAAO,SAAA;AAAA,EACT;AAEA,EAAA,OAAO,SAAA;AACT;AAE+B,cAAA;;;AClC/B,IAAM,eAAA,GAAkB,GAAA;AAExB,IAAM,oBAAN,MAAwB;AAAA,EAAxB,WAAA,GAAA;AACE,IAAA,IAAA,CAAQ,cAA4B,EAAC;AACrC,IAAA,IAAA,CAAQ,cAAA,GAAyB,eAAA;AAAA,EAAA;AAAA,EAEjC,kBAAkB,GAAA,EAAmB;AACnC,IAAA,IAAA,CAAK,cAAA,GAAiB,GAAA;AACtB,IAAA,IAAA,CAAK,IAAA,EAAK;AAAA,EACZ;AAAA,EAEA,IAAI,UAAA,EAAiD;AACnD,IAAA,MAAM,KAAA,GAAoB;AAAA,MACxB,GAAG,UAAA;AAAA,MACH,SAAA,EAAW,KAAK,GAAA;AAAI,KACtB;AAEA,IAAA,IAAA,CAAK,WAAA,CAAY,KAAK,KAAK,CAAA;AAC3B,IAAA,IAAA,CAAK,IAAA,EAAK;AAAA,EACZ;AAAA,EAEA,MAAA,GAAuB;AACrB,IAAA,OAAO,CAAC,GAAG,IAAA,CAAK,WAAW,CAAA;AAAA,EAC7B;AAAA,EAEA,KAAA,GAAc;AACZ,IAAA,IAAA,CAAK,cAAc,EAAC;AAAA,EACtB;AAAA,EAEQ,IAAA,GAAa;AACnB,IAAA,IAAI,IAAA,CAAK,WAAA,CAAY,MAAA,GAAS,IAAA,CAAK,cAAA,EAAgB;AACjD,MAAA,IAAA,CAAK,cAAc,IAAA,CAAK,WAAA,CAAY,KAAA,CAAM,CAAC,KAAK,cAAc,CAAA;AAAA,IAChE;AAAA,EACF;AACF,CAAA;AAEO,IAAM,WAAA,GAAc,IAAI,iBAAA,EAAkB;AAK1C,SAAS,cACd,UAAA,EACM;AACN,EAAA,WAAA,CAAY,IAAI,UAAU,CAAA;AAC5B;ACQA,eAAsB,sBAAA,CACpB,OACA,OAAA,EAUwB;AACxB,EAA4B;AAC1B,IAAA,OAAO,IAAA;AAAA,EACT;AAOF;;;ACnDO,SAAS,kBAAA,GAAqC;AACnD,EAAA,OAAO,CAAC,GAAA,EAAc,IAAA,EAAgB,IAAA,KAA6B;AAEjE,IAAA,aAAA,CAAc;AAAA,MACZ,IAAA,EAAM,MAAA;AAAA,MACN,QAAA,EAAU,cAAA;AAAA,MACV,SAAS,CAAA,EAAG,GAAA,CAAI,MAAM,CAAA,CAAA,EAAI,IAAI,GAAG,CAAA,CAAA;AAAA,MACjC,KAAA,EAAO,MAAA;AAAA,MACP,IAAA,EAAM;AAAA,QACJ,QAAQ,GAAA,CAAI,MAAA;AAAA,QACZ,KAAK,GAAA,CAAI,GAAA;AAAA,QACT,OAAO,GAAA,CAAI;AAAA;AACb,KACD,CAAA;AAED,IAAA,IAAA,EAAK;AAAA,EACP,CAAA;AACF;AAMO,SAAS,oBAAA,GAA4C;AAC1D,EAAA,OAAO,CACL,GAAA,EACA,GAAA,EACA,GAAA,EACA,IAAA,KACS;AAET,IAAA,sBAAA,CAAuB,GAAA,EAAK;AAAA,MAC1B,OAAA,EAAS;AAAA,QACP,KAAK,GAAA,CAAI,GAAA;AAAA,QACT,QAAQ,GAAA,CAAI,MAAA;AAAA,QACZ,OAAA,EAAS,gBAAA,CAAiB,GAAA,CAAI,OAAwD,CAAA;AAAA,QACtF,OAAO,GAAA,CAAI,KAAA;AAAA,QACX,MAAM,GAAA,CAAI,IAAA;AAAA,QACV,IAAI,GAAA,CAAI;AAAA,OACV;AAAA,MACA,IAAA,EAAM;AAAA,QACJ,eAAe,GAAA,CAAI,MAAA;AAAA,QACnB,kBAAA,EAAoB,MAAA,CAAO,GAAA,CAAI,UAAA,IAAc,GAAG;AAAA;AAClD,KACD,CAAA;AAGD,IAAA,IAAA,CAAK,GAAG,CAAA;AAAA,EACV,CAAA;AACF;AAEA,SAAS,iBACP,OAAA,EACwB;AACxB,EAAA,MAAM,aAAqC,EAAC;AAE5C,EAAA,KAAA,MAAW,CAAC,GAAA,EAAK,KAAK,KAAK,MAAA,CAAO,OAAA,CAAQ,OAAO,CAAA,EAAG;AAClD,IAAA,IAAI,KAAA,EAAO;AACT,MAAA,UAAA,CAAW,GAAG,IAAI,KAAA,CAAM,OAAA,CAAQ,KAAK,CAAA,GAAI,KAAA,CAAM,IAAA,CAAK,IAAI,CAAA,GAAI,KAAA;AAAA,IAC9D;AAAA,EACF;AAGA,EAAA,OAAO,WAAW,eAAe,CAAA;AACjC,EAAA,OAAO,WAAW,QAAQ,CAAA;AAC1B,EAAA,OAAO,WAAW,WAAW,CAAA;AAE7B,EAAA,OAAO,UAAA;AACT;AAeO,SAAS,YACd,OAAA,EAC2D;AAC3D,EAAA,OAAO,CAAC,GAAA,EAAc,GAAA,EAAe,IAAA,KAA6B;AAChE,IAAA,OAAA,CAAQ,OAAA,CAAQ,QAAQ,GAAA,EAAK,GAAA,EAAK,IAAI,CAAC,CAAA,CAAE,KAAA,CAAM,CAAC,GAAA,KAAQ;AACtD,MAAA,sBAAA,CAAuB,GAAA,EAAK;AAAA,QAC1B,OAAA,EAAS;AAAA,UACP,KAAK,GAAA,CAAI,GAAA;AAAA,UACT,QAAQ,GAAA,CAAI,MAAA;AAAA,UACZ,OAAA,EAAS,gBAAA,CAAiB,GAAA,CAAI,OAAwD;AAAA;AACxF,OACD,CAAA;AACD,MAAA,IAAA,CAAK,GAAG,CAAA;AAAA,IACV,CAAC,CAAA;AAAA,EACH,CAAA;AACF","file":"index.js","sourcesContent":["/**\n * Platform detection and adapters for cross-platform support\n * Supports: Browser, React Native/Expo, Node.js\n */\n\nexport type Platform = \"browser\" | \"react-native\" | \"node\" | \"unknown\";\n\n/**\n * Detect the current platform\n */\nexport function detectPlatform(): Platform {\n // React Native detection\n if (\n typeof navigator !== \"undefined\" &&\n navigator.product === \"ReactNative\"\n ) {\n return \"react-native\";\n }\n\n // Node.js detection\n if (\n typeof process !== \"undefined\" &&\n process.versions &&\n process.versions.node\n ) {\n return \"node\";\n }\n\n // Browser detection\n if (typeof window !== \"undefined\" && typeof document !== \"undefined\") {\n return \"browser\";\n }\n\n return \"unknown\";\n}\n\nexport const currentPlatform = detectPlatform();\n\n/**\n * Check if running in browser\n */\nexport function isBrowser(): boolean {\n return currentPlatform === \"browser\";\n}\n\n/**\n * Check if running in React Native\n */\nexport function isReactNative(): boolean {\n return currentPlatform === \"react-native\";\n}\n\n/**\n * Check if running in Node.js\n */\nexport function isNode(): boolean {\n return currentPlatform === \"node\";\n}\n\n/**\n * Get device info based on platform\n */\nexport function getDeviceInfo(): {\n platform: Platform;\n userAgent?: string;\n deviceName?: string;\n osName?: string;\n osVersion?: string;\n appVersion?: string;\n} {\n const info: ReturnType<typeof getDeviceInfo> = {\n platform: currentPlatform,\n };\n\n if (isBrowser() && typeof navigator !== \"undefined\") {\n info.userAgent = navigator.userAgent;\n }\n\n // React Native device info would be injected via configuration\n // since we can't directly access native modules from here\n\n return info;\n}\n\n/**\n * Platform-agnostic storage interface\n */\nexport interface StorageAdapter {\n getItem(key: string): Promise<string | null>;\n setItem(key: string, value: string): Promise<void>;\n removeItem(key: string): Promise<void>;\n}\n\n/**\n * Get storage adapter for current platform\n */\nexport function getStorageAdapter(): StorageAdapter | null {\n if (isBrowser() && typeof localStorage !== \"undefined\") {\n return {\n getItem: async (key) => localStorage.getItem(key),\n setItem: async (key, value) => localStorage.setItem(key, value),\n removeItem: async (key) => localStorage.removeItem(key),\n };\n }\n\n // React Native AsyncStorage would be passed in via config\n // Node.js could use file-based storage\n\n return null;\n}\n\n/**\n * Platform-agnostic network request\n */\nexport async function platformFetch(\n url: string,\n options: RequestInit\n): Promise<Response> {\n // Use native fetch if available (browser, React Native with polyfill, Node 18+)\n if (typeof fetch !== \"undefined\") {\n return fetch(url, options);\n }\n\n throw new Error(\"No fetch implementation available\");\n}\n\n/**\n * Get current URL (browser only)\n */\nexport function getCurrentUrl(): string | null {\n if (isBrowser() && typeof window !== \"undefined\") {\n return window.location.href;\n }\n return null;\n}\n\n/**\n * Get referrer (browser only)\n */\nexport function getReferrer(): string | null {\n if (isBrowser() && typeof document !== \"undefined\") {\n return document.referrer || null;\n }\n return null;\n}\n","import type { Breadcrumb } from \"./types\";\n\nconst MAX_BREADCRUMBS = 100;\n\nclass BreadcrumbManager {\n private breadcrumbs: Breadcrumb[] = [];\n private maxBreadcrumbs: number = MAX_BREADCRUMBS;\n\n setMaxBreadcrumbs(max: number): void {\n this.maxBreadcrumbs = max;\n this.trim();\n }\n\n add(breadcrumb: Omit<Breadcrumb, \"timestamp\">): void {\n const entry: Breadcrumb = {\n ...breadcrumb,\n timestamp: Date.now(),\n };\n\n this.breadcrumbs.push(entry);\n this.trim();\n }\n\n getAll(): Breadcrumb[] {\n return [...this.breadcrumbs];\n }\n\n clear(): void {\n this.breadcrumbs = [];\n }\n\n private trim(): void {\n if (this.breadcrumbs.length > this.maxBreadcrumbs) {\n this.breadcrumbs = this.breadcrumbs.slice(-this.maxBreadcrumbs);\n }\n }\n}\n\nexport const breadcrumbs = new BreadcrumbManager();\n\n/**\n * Add a breadcrumb to the trail\n */\nexport function addBreadcrumb(\n breadcrumb: Omit<Breadcrumb, \"timestamp\">\n): void {\n breadcrumbs.add(breadcrumb);\n}\n\n/**\n * Get all breadcrumbs\n */\nexport function getBreadcrumbs(): Breadcrumb[] {\n return breadcrumbs.getAll();\n}\n\n/**\n * Clear all breadcrumbs\n */\nexport function clearBreadcrumbs(): void {\n breadcrumbs.clear();\n}\n","/**\n * Server-side exports for @sitepong/react\n * Use this for Next.js API routes, Express, and other Node.js servers\n */\n\nimport type { SitePongConfig, ErrorEvent, CaptureContext, LogLevel } from \"../core/types\";\nimport { validateConfig, getApiEndpoint, getAuthHeaders } from \"../core/dsn\";\nimport {\n parseStackTrace,\n generateEventId,\n getBrowserContext,\n} from \"../utils/stacktrace\";\nimport { getBreadcrumbs, addBreadcrumb, clearBreadcrumbs } from \"../core/breadcrumbs\";\n\n// Server-side client (stateless for serverless environments)\nlet serverConfig: SitePongConfig | null = null;\n\n/**\n * Initialize SitePong for server-side usage\n *\n * @example\n * ```ts\n * // In your server entry point or API route\n * import { initServer } from '@sitepong/react/server';\n *\n * initServer({\n * dsn: process.env.SITEPONG_DSN,\n * environment: process.env.NODE_ENV,\n * });\n * ```\n */\nexport function initServer(config: SitePongConfig): void {\n validateConfig(config);\n serverConfig = {\n enabled: true,\n debug: false,\n sampleRate: 1.0,\n ...config,\n };\n\n if (serverConfig.debug) {\n console.log(\"[SitePong] Server SDK initialized\");\n }\n}\n\n/**\n * Get the current server configuration\n */\nexport function getServerConfig(): SitePongConfig | null {\n return serverConfig;\n}\n\n/**\n * Capture an exception on the server\n */\nexport async function captureServerException(\n error: Error | unknown,\n context?: CaptureContext & {\n request?: {\n url?: string;\n method?: string;\n headers?: Record<string, string>;\n query?: Record<string, string>;\n body?: unknown;\n ip?: string;\n };\n }\n): Promise<string | null> {\n if (!serverConfig?.enabled) {\n return null;\n }\n\n const err = error instanceof Error ? error : new Error(String(error));\n const event = createServerErrorEvent(err, context);\n\n await sendServerEvent(event);\n return event.event_id;\n}\n\n/**\n * Capture a message on the server\n */\nexport async function captureServerMessage(\n message: string,\n level: LogLevel = \"info\",\n context?: CaptureContext\n): Promise<string | null> {\n if (!serverConfig?.enabled) {\n return null;\n }\n\n const event = createServerMessageEvent(message, level, context);\n await sendServerEvent(event);\n return event.event_id;\n}\n\nfunction createServerErrorEvent(\n error: Error,\n context?: CaptureContext & { request?: Record<string, unknown> }\n): ErrorEvent {\n const browserContext = getBrowserContext();\n\n return {\n event_id: generateEventId(),\n timestamp: new Date().toISOString(),\n level: context?.level || \"error\",\n message: error.message,\n exception: {\n type: error.name,\n value: error.message,\n stacktrace: parseStackTrace(error),\n },\n tags: { ...context?.tags, runtime: \"server\" },\n extra: { ...context?.extra },\n user: context?.user,\n breadcrumbs: getBreadcrumbs(),\n context: {\n runtime: browserContext.runtime || { name: \"Node.js\", version: process.version },\n },\n environment: serverConfig?.environment,\n release: serverConfig?.release,\n request: context?.request as ErrorEvent[\"request\"],\n };\n}\n\nfunction createServerMessageEvent(\n message: string,\n level: LogLevel,\n context?: CaptureContext\n): ErrorEvent {\n return {\n event_id: generateEventId(),\n timestamp: new Date().toISOString(),\n level,\n message,\n tags: { ...context?.tags, runtime: \"server\" },\n extra: { ...context?.extra },\n user: context?.user,\n breadcrumbs: getBreadcrumbs(),\n environment: serverConfig?.environment,\n release: serverConfig?.release,\n };\n}\n\nasync function sendServerEvent(event: ErrorEvent): Promise<void> {\n if (!serverConfig) return;\n\n const endpoint = getApiEndpoint(serverConfig);\n const headers = getAuthHeaders(serverConfig);\n\n try {\n const response = await fetch(`${endpoint}/api/errors`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n ...headers,\n },\n body: JSON.stringify({\n title: event.exception?.type || \"Error\",\n message: event.message,\n stack: event.exception?.stacktrace\n ?.map(\n (frame) =>\n ` at ${frame.function || \"anonymous\"} (${frame.filename}:${frame.lineno}:${frame.colno})`\n )\n .join(\"\\n\"),\n level: event.level,\n timestamp: event.timestamp,\n environment: event.environment,\n release: event.release,\n tags: event.tags,\n extra: event.extra,\n user: event.user,\n breadcrumbs: event.breadcrumbs,\n context: event.context,\n request: event.request,\n }),\n });\n\n if (serverConfig.debug) {\n if (response.ok) {\n console.log(\"[SitePong] Server event sent:\", event.event_id);\n } else {\n console.error(\"[SitePong] Failed to send server event:\", response.status);\n }\n }\n } catch (err) {\n if (serverConfig.debug) {\n console.error(\"[SitePong] Error sending server event:\", err);\n }\n }\n}\n\n// Re-export breadcrumb functions for server use\nexport { addBreadcrumb, getBreadcrumbs, clearBreadcrumbs };\n\n// Re-export types\nexport type { SitePongConfig, ErrorEvent, CaptureContext, LogLevel };\n","/**\n * Express.js integration for SitePong\n *\n * @example\n * ```ts\n * import express from 'express';\n * import { sitePongMiddleware, sitePongErrorHandler } from '@sitepong/react/express';\n *\n * const app = express();\n *\n * // Add request tracking middleware\n * app.use(sitePongMiddleware());\n *\n * // Your routes here...\n *\n * // Add error handler (must be last)\n * app.use(sitePongErrorHandler());\n * ```\n */\n\nimport type { Request, Response, NextFunction, RequestHandler, ErrorRequestHandler } from \"express\";\nimport { captureServerException, addBreadcrumb } from \"../server\";\n\n/**\n * Express middleware to track requests and add context\n */\nexport function sitePongMiddleware(): RequestHandler {\n return (req: Request, _res: Response, next: NextFunction): void => {\n // Add breadcrumb for the request\n addBreadcrumb({\n type: \"http\",\n category: \"http.request\",\n message: `${req.method} ${req.url}`,\n level: \"info\",\n data: {\n method: req.method,\n url: req.url,\n query: req.query,\n },\n });\n\n next();\n };\n}\n\n/**\n * Express error handler middleware\n * Must be added after all other middleware and routes\n */\nexport function sitePongErrorHandler(): ErrorRequestHandler {\n return (\n err: Error,\n req: Request,\n res: Response,\n next: NextFunction\n ): void => {\n // Capture the error\n captureServerException(err, {\n request: {\n url: req.url,\n method: req.method,\n headers: normalizeHeaders(req.headers as Record<string, string | string[] | undefined>),\n query: req.query as Record<string, string>,\n body: req.body,\n ip: req.ip,\n },\n tags: {\n \"http.method\": req.method,\n \"http.status_code\": String(res.statusCode || 500),\n },\n });\n\n // Continue to next error handler\n next(err);\n };\n}\n\nfunction normalizeHeaders(\n headers: Record<string, string | string[] | undefined>\n): Record<string, string> {\n const normalized: Record<string, string> = {};\n\n for (const [key, value] of Object.entries(headers)) {\n if (value) {\n normalized[key] = Array.isArray(value) ? value.join(\", \") : value;\n }\n }\n\n // Remove sensitive headers\n delete normalized[\"authorization\"];\n delete normalized[\"cookie\"];\n delete normalized[\"x-api-key\"];\n\n return normalized;\n}\n\n/**\n * Create a request handler that wraps async functions and catches errors\n *\n * @example\n * ```ts\n * import { wrapHandler } from '@sitepong/react/express';\n *\n * app.get('/api/users', wrapHandler(async (req, res) => {\n * const users = await getUsers();\n * res.json(users);\n * }));\n * ```\n */\nexport function wrapHandler<T extends (req: Request, res: Response, next: NextFunction) => Promise<unknown>>(\n handler: T\n): (req: Request, res: Response, next: NextFunction) => void {\n return (req: Request, res: Response, next: NextFunction): void => {\n Promise.resolve(handler(req, res, next)).catch((err) => {\n captureServerException(err, {\n request: {\n url: req.url,\n method: req.method,\n headers: normalizeHeaders(req.headers as Record<string, string | string[] | undefined>),\n },\n });\n next(err);\n });\n };\n}\n"]}
@@ -0,0 +1,124 @@
1
+ // src/core/dsn.ts
2
+
3
+ // src/utils/platform.ts
4
+ function detectPlatform() {
5
+ if (typeof navigator !== "undefined" && navigator.product === "ReactNative") {
6
+ return "react-native";
7
+ }
8
+ if (typeof process !== "undefined" && process.versions && process.versions.node) {
9
+ return "node";
10
+ }
11
+ if (typeof window !== "undefined" && typeof document !== "undefined") {
12
+ return "browser";
13
+ }
14
+ return "unknown";
15
+ }
16
+ detectPlatform();
17
+
18
+ // src/core/breadcrumbs.ts
19
+ var MAX_BREADCRUMBS = 100;
20
+ var BreadcrumbManager = class {
21
+ constructor() {
22
+ this.breadcrumbs = [];
23
+ this.maxBreadcrumbs = MAX_BREADCRUMBS;
24
+ }
25
+ setMaxBreadcrumbs(max) {
26
+ this.maxBreadcrumbs = max;
27
+ this.trim();
28
+ }
29
+ add(breadcrumb) {
30
+ const entry = {
31
+ ...breadcrumb,
32
+ timestamp: Date.now()
33
+ };
34
+ this.breadcrumbs.push(entry);
35
+ this.trim();
36
+ }
37
+ getAll() {
38
+ return [...this.breadcrumbs];
39
+ }
40
+ clear() {
41
+ this.breadcrumbs = [];
42
+ }
43
+ trim() {
44
+ if (this.breadcrumbs.length > this.maxBreadcrumbs) {
45
+ this.breadcrumbs = this.breadcrumbs.slice(-this.maxBreadcrumbs);
46
+ }
47
+ }
48
+ };
49
+ var breadcrumbs = new BreadcrumbManager();
50
+ function addBreadcrumb(breadcrumb) {
51
+ breadcrumbs.add(breadcrumb);
52
+ }
53
+ async function captureServerException(error, context) {
54
+ {
55
+ return null;
56
+ }
57
+ }
58
+
59
+ // src/integrations/express.ts
60
+ function sitePongMiddleware() {
61
+ return (req, _res, next) => {
62
+ addBreadcrumb({
63
+ type: "http",
64
+ category: "http.request",
65
+ message: `${req.method} ${req.url}`,
66
+ level: "info",
67
+ data: {
68
+ method: req.method,
69
+ url: req.url,
70
+ query: req.query
71
+ }
72
+ });
73
+ next();
74
+ };
75
+ }
76
+ function sitePongErrorHandler() {
77
+ return (err, req, res, next) => {
78
+ captureServerException(err, {
79
+ request: {
80
+ url: req.url,
81
+ method: req.method,
82
+ headers: normalizeHeaders(req.headers),
83
+ query: req.query,
84
+ body: req.body,
85
+ ip: req.ip
86
+ },
87
+ tags: {
88
+ "http.method": req.method,
89
+ "http.status_code": String(res.statusCode || 500)
90
+ }
91
+ });
92
+ next(err);
93
+ };
94
+ }
95
+ function normalizeHeaders(headers) {
96
+ const normalized = {};
97
+ for (const [key, value] of Object.entries(headers)) {
98
+ if (value) {
99
+ normalized[key] = Array.isArray(value) ? value.join(", ") : value;
100
+ }
101
+ }
102
+ delete normalized["authorization"];
103
+ delete normalized["cookie"];
104
+ delete normalized["x-api-key"];
105
+ return normalized;
106
+ }
107
+ function wrapHandler(handler) {
108
+ return (req, res, next) => {
109
+ Promise.resolve(handler(req, res, next)).catch((err) => {
110
+ captureServerException(err, {
111
+ request: {
112
+ url: req.url,
113
+ method: req.method,
114
+ headers: normalizeHeaders(req.headers)
115
+ }
116
+ });
117
+ next(err);
118
+ });
119
+ };
120
+ }
121
+
122
+ export { sitePongErrorHandler, sitePongMiddleware, wrapHandler };
123
+ //# sourceMappingURL=index.mjs.map
124
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/utils/platform.ts","../../src/core/breadcrumbs.ts","../../src/server/index.ts","../../src/integrations/express.ts"],"names":[],"mappings":"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","file":"index.mjs","sourcesContent":["/**\n * Platform detection and adapters for cross-platform support\n * Supports: Browser, React Native/Expo, Node.js\n */\n\nexport type Platform = \"browser\" | \"react-native\" | \"node\" | \"unknown\";\n\n/**\n * Detect the current platform\n */\nexport function detectPlatform(): Platform {\n // React Native detection\n if (\n typeof navigator !== \"undefined\" &&\n navigator.product === \"ReactNative\"\n ) {\n return \"react-native\";\n }\n\n // Node.js detection\n if (\n typeof process !== \"undefined\" &&\n process.versions &&\n process.versions.node\n ) {\n return \"node\";\n }\n\n // Browser detection\n if (typeof window !== \"undefined\" && typeof document !== \"undefined\") {\n return \"browser\";\n }\n\n return \"unknown\";\n}\n\nexport const currentPlatform = detectPlatform();\n\n/**\n * Check if running in browser\n */\nexport function isBrowser(): boolean {\n return currentPlatform === \"browser\";\n}\n\n/**\n * Check if running in React Native\n */\nexport function isReactNative(): boolean {\n return currentPlatform === \"react-native\";\n}\n\n/**\n * Check if running in Node.js\n */\nexport function isNode(): boolean {\n return currentPlatform === \"node\";\n}\n\n/**\n * Get device info based on platform\n */\nexport function getDeviceInfo(): {\n platform: Platform;\n userAgent?: string;\n deviceName?: string;\n osName?: string;\n osVersion?: string;\n appVersion?: string;\n} {\n const info: ReturnType<typeof getDeviceInfo> = {\n platform: currentPlatform,\n };\n\n if (isBrowser() && typeof navigator !== \"undefined\") {\n info.userAgent = navigator.userAgent;\n }\n\n // React Native device info would be injected via configuration\n // since we can't directly access native modules from here\n\n return info;\n}\n\n/**\n * Platform-agnostic storage interface\n */\nexport interface StorageAdapter {\n getItem(key: string): Promise<string | null>;\n setItem(key: string, value: string): Promise<void>;\n removeItem(key: string): Promise<void>;\n}\n\n/**\n * Get storage adapter for current platform\n */\nexport function getStorageAdapter(): StorageAdapter | null {\n if (isBrowser() && typeof localStorage !== \"undefined\") {\n return {\n getItem: async (key) => localStorage.getItem(key),\n setItem: async (key, value) => localStorage.setItem(key, value),\n removeItem: async (key) => localStorage.removeItem(key),\n };\n }\n\n // React Native AsyncStorage would be passed in via config\n // Node.js could use file-based storage\n\n return null;\n}\n\n/**\n * Platform-agnostic network request\n */\nexport async function platformFetch(\n url: string,\n options: RequestInit\n): Promise<Response> {\n // Use native fetch if available (browser, React Native with polyfill, Node 18+)\n if (typeof fetch !== \"undefined\") {\n return fetch(url, options);\n }\n\n throw new Error(\"No fetch implementation available\");\n}\n\n/**\n * Get current URL (browser only)\n */\nexport function getCurrentUrl(): string | null {\n if (isBrowser() && typeof window !== \"undefined\") {\n return window.location.href;\n }\n return null;\n}\n\n/**\n * Get referrer (browser only)\n */\nexport function getReferrer(): string | null {\n if (isBrowser() && typeof document !== \"undefined\") {\n return document.referrer || null;\n }\n return null;\n}\n","import type { Breadcrumb } from \"./types\";\n\nconst MAX_BREADCRUMBS = 100;\n\nclass BreadcrumbManager {\n private breadcrumbs: Breadcrumb[] = [];\n private maxBreadcrumbs: number = MAX_BREADCRUMBS;\n\n setMaxBreadcrumbs(max: number): void {\n this.maxBreadcrumbs = max;\n this.trim();\n }\n\n add(breadcrumb: Omit<Breadcrumb, \"timestamp\">): void {\n const entry: Breadcrumb = {\n ...breadcrumb,\n timestamp: Date.now(),\n };\n\n this.breadcrumbs.push(entry);\n this.trim();\n }\n\n getAll(): Breadcrumb[] {\n return [...this.breadcrumbs];\n }\n\n clear(): void {\n this.breadcrumbs = [];\n }\n\n private trim(): void {\n if (this.breadcrumbs.length > this.maxBreadcrumbs) {\n this.breadcrumbs = this.breadcrumbs.slice(-this.maxBreadcrumbs);\n }\n }\n}\n\nexport const breadcrumbs = new BreadcrumbManager();\n\n/**\n * Add a breadcrumb to the trail\n */\nexport function addBreadcrumb(\n breadcrumb: Omit<Breadcrumb, \"timestamp\">\n): void {\n breadcrumbs.add(breadcrumb);\n}\n\n/**\n * Get all breadcrumbs\n */\nexport function getBreadcrumbs(): Breadcrumb[] {\n return breadcrumbs.getAll();\n}\n\n/**\n * Clear all breadcrumbs\n */\nexport function clearBreadcrumbs(): void {\n breadcrumbs.clear();\n}\n","/**\n * Server-side exports for @sitepong/react\n * Use this for Next.js API routes, Express, and other Node.js servers\n */\n\nimport type { SitePongConfig, ErrorEvent, CaptureContext, LogLevel } from \"../core/types\";\nimport { validateConfig, getApiEndpoint, getAuthHeaders } from \"../core/dsn\";\nimport {\n parseStackTrace,\n generateEventId,\n getBrowserContext,\n} from \"../utils/stacktrace\";\nimport { getBreadcrumbs, addBreadcrumb, clearBreadcrumbs } from \"../core/breadcrumbs\";\n\n// Server-side client (stateless for serverless environments)\nlet serverConfig: SitePongConfig | null = null;\n\n/**\n * Initialize SitePong for server-side usage\n *\n * @example\n * ```ts\n * // In your server entry point or API route\n * import { initServer } from '@sitepong/react/server';\n *\n * initServer({\n * dsn: process.env.SITEPONG_DSN,\n * environment: process.env.NODE_ENV,\n * });\n * ```\n */\nexport function initServer(config: SitePongConfig): void {\n validateConfig(config);\n serverConfig = {\n enabled: true,\n debug: false,\n sampleRate: 1.0,\n ...config,\n };\n\n if (serverConfig.debug) {\n console.log(\"[SitePong] Server SDK initialized\");\n }\n}\n\n/**\n * Get the current server configuration\n */\nexport function getServerConfig(): SitePongConfig | null {\n return serverConfig;\n}\n\n/**\n * Capture an exception on the server\n */\nexport async function captureServerException(\n error: Error | unknown,\n context?: CaptureContext & {\n request?: {\n url?: string;\n method?: string;\n headers?: Record<string, string>;\n query?: Record<string, string>;\n body?: unknown;\n ip?: string;\n };\n }\n): Promise<string | null> {\n if (!serverConfig?.enabled) {\n return null;\n }\n\n const err = error instanceof Error ? error : new Error(String(error));\n const event = createServerErrorEvent(err, context);\n\n await sendServerEvent(event);\n return event.event_id;\n}\n\n/**\n * Capture a message on the server\n */\nexport async function captureServerMessage(\n message: string,\n level: LogLevel = \"info\",\n context?: CaptureContext\n): Promise<string | null> {\n if (!serverConfig?.enabled) {\n return null;\n }\n\n const event = createServerMessageEvent(message, level, context);\n await sendServerEvent(event);\n return event.event_id;\n}\n\nfunction createServerErrorEvent(\n error: Error,\n context?: CaptureContext & { request?: Record<string, unknown> }\n): ErrorEvent {\n const browserContext = getBrowserContext();\n\n return {\n event_id: generateEventId(),\n timestamp: new Date().toISOString(),\n level: context?.level || \"error\",\n message: error.message,\n exception: {\n type: error.name,\n value: error.message,\n stacktrace: parseStackTrace(error),\n },\n tags: { ...context?.tags, runtime: \"server\" },\n extra: { ...context?.extra },\n user: context?.user,\n breadcrumbs: getBreadcrumbs(),\n context: {\n runtime: browserContext.runtime || { name: \"Node.js\", version: process.version },\n },\n environment: serverConfig?.environment,\n release: serverConfig?.release,\n request: context?.request as ErrorEvent[\"request\"],\n };\n}\n\nfunction createServerMessageEvent(\n message: string,\n level: LogLevel,\n context?: CaptureContext\n): ErrorEvent {\n return {\n event_id: generateEventId(),\n timestamp: new Date().toISOString(),\n level,\n message,\n tags: { ...context?.tags, runtime: \"server\" },\n extra: { ...context?.extra },\n user: context?.user,\n breadcrumbs: getBreadcrumbs(),\n environment: serverConfig?.environment,\n release: serverConfig?.release,\n };\n}\n\nasync function sendServerEvent(event: ErrorEvent): Promise<void> {\n if (!serverConfig) return;\n\n const endpoint = getApiEndpoint(serverConfig);\n const headers = getAuthHeaders(serverConfig);\n\n try {\n const response = await fetch(`${endpoint}/api/errors`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n ...headers,\n },\n body: JSON.stringify({\n title: event.exception?.type || \"Error\",\n message: event.message,\n stack: event.exception?.stacktrace\n ?.map(\n (frame) =>\n ` at ${frame.function || \"anonymous\"} (${frame.filename}:${frame.lineno}:${frame.colno})`\n )\n .join(\"\\n\"),\n level: event.level,\n timestamp: event.timestamp,\n environment: event.environment,\n release: event.release,\n tags: event.tags,\n extra: event.extra,\n user: event.user,\n breadcrumbs: event.breadcrumbs,\n context: event.context,\n request: event.request,\n }),\n });\n\n if (serverConfig.debug) {\n if (response.ok) {\n console.log(\"[SitePong] Server event sent:\", event.event_id);\n } else {\n console.error(\"[SitePong] Failed to send server event:\", response.status);\n }\n }\n } catch (err) {\n if (serverConfig.debug) {\n console.error(\"[SitePong] Error sending server event:\", err);\n }\n }\n}\n\n// Re-export breadcrumb functions for server use\nexport { addBreadcrumb, getBreadcrumbs, clearBreadcrumbs };\n\n// Re-export types\nexport type { SitePongConfig, ErrorEvent, CaptureContext, LogLevel };\n","/**\n * Express.js integration for SitePong\n *\n * @example\n * ```ts\n * import express from 'express';\n * import { sitePongMiddleware, sitePongErrorHandler } from '@sitepong/react/express';\n *\n * const app = express();\n *\n * // Add request tracking middleware\n * app.use(sitePongMiddleware());\n *\n * // Your routes here...\n *\n * // Add error handler (must be last)\n * app.use(sitePongErrorHandler());\n * ```\n */\n\nimport type { Request, Response, NextFunction, RequestHandler, ErrorRequestHandler } from \"express\";\nimport { captureServerException, addBreadcrumb } from \"../server\";\n\n/**\n * Express middleware to track requests and add context\n */\nexport function sitePongMiddleware(): RequestHandler {\n return (req: Request, _res: Response, next: NextFunction): void => {\n // Add breadcrumb for the request\n addBreadcrumb({\n type: \"http\",\n category: \"http.request\",\n message: `${req.method} ${req.url}`,\n level: \"info\",\n data: {\n method: req.method,\n url: req.url,\n query: req.query,\n },\n });\n\n next();\n };\n}\n\n/**\n * Express error handler middleware\n * Must be added after all other middleware and routes\n */\nexport function sitePongErrorHandler(): ErrorRequestHandler {\n return (\n err: Error,\n req: Request,\n res: Response,\n next: NextFunction\n ): void => {\n // Capture the error\n captureServerException(err, {\n request: {\n url: req.url,\n method: req.method,\n headers: normalizeHeaders(req.headers as Record<string, string | string[] | undefined>),\n query: req.query as Record<string, string>,\n body: req.body,\n ip: req.ip,\n },\n tags: {\n \"http.method\": req.method,\n \"http.status_code\": String(res.statusCode || 500),\n },\n });\n\n // Continue to next error handler\n next(err);\n };\n}\n\nfunction normalizeHeaders(\n headers: Record<string, string | string[] | undefined>\n): Record<string, string> {\n const normalized: Record<string, string> = {};\n\n for (const [key, value] of Object.entries(headers)) {\n if (value) {\n normalized[key] = Array.isArray(value) ? value.join(\", \") : value;\n }\n }\n\n // Remove sensitive headers\n delete normalized[\"authorization\"];\n delete normalized[\"cookie\"];\n delete normalized[\"x-api-key\"];\n\n return normalized;\n}\n\n/**\n * Create a request handler that wraps async functions and catches errors\n *\n * @example\n * ```ts\n * import { wrapHandler } from '@sitepong/react/express';\n *\n * app.get('/api/users', wrapHandler(async (req, res) => {\n * const users = await getUsers();\n * res.json(users);\n * }));\n * ```\n */\nexport function wrapHandler<T extends (req: Request, res: Response, next: NextFunction) => Promise<unknown>>(\n handler: T\n): (req: Request, res: Response, next: NextFunction) => void {\n return (req: Request, res: Response, next: NextFunction): void => {\n Promise.resolve(handler(req, res, next)).catch((err) => {\n captureServerException(err, {\n request: {\n url: req.url,\n method: req.method,\n headers: normalizeHeaders(req.headers as Record<string, string | string[] | undefined>),\n },\n });\n next(err);\n });\n };\n}\n"]}
package/dist/index.js CHANGED
@@ -4656,7 +4656,7 @@ var startProfileSpan = sitepong.startProfileSpan.bind(sitepong);
4656
4656
  var getProfiles = sitepong.getProfiles.bind(sitepong);
4657
4657
  var getLatestProfile = sitepong.getLatestProfile.bind(sitepong);
4658
4658
  var flushProfiles = sitepong.flushProfiles.bind(sitepong);
4659
- var index_default = sitepong;
4659
+ var src_default = sitepong;
4660
4660
 
4661
4661
  exports.TracePropagator = TracePropagator;
4662
4662
  exports.areFlagsReady = areFlagsReady;
@@ -4669,7 +4669,7 @@ exports.cronStart = cronStart;
4669
4669
  exports.cronWrap = cronWrap;
4670
4670
  exports.dbTrack = dbTrack;
4671
4671
  exports.dbTrackSync = dbTrackSync;
4672
- exports.default = index_default;
4672
+ exports.default = src_default;
4673
4673
  exports.endSpan = endSpan;
4674
4674
  exports.endTransaction = endTransaction;
4675
4675
  exports.extractTrace = extractTrace;