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,109 @@
1
+ import { NextRequest } from 'next/server';
2
+ import { S as SitePongConfig } from '../breadcrumbs-DwXK5gNn.mjs';
3
+ export { a as addBreadcrumb } from '../breadcrumbs-DwXK5gNn.mjs';
4
+ export { captureServerException, initServer } from '../server/index.mjs';
5
+
6
+ /**
7
+ * Next.js integration for SitePong
8
+ *
9
+ * @example
10
+ * ```ts
11
+ * // next.config.js
12
+ * const { withSitePong } = require('@sitepong/react/nextjs');
13
+ *
14
+ * module.exports = withSitePong({
15
+ * // your Next.js config
16
+ * });
17
+ * ```
18
+ *
19
+ * @example
20
+ * ```ts
21
+ * // app/layout.tsx
22
+ * import { SitePongProvider } from '@sitepong/react';
23
+ *
24
+ * export default function RootLayout({ children }) {
25
+ * return (
26
+ * <html>
27
+ * <body>
28
+ * <SitePongProvider dsn={process.env.NEXT_PUBLIC_SITEPONG_DSN}>
29
+ * {children}
30
+ * </SitePongProvider>
31
+ * </body>
32
+ * </html>
33
+ * );
34
+ * }
35
+ * ```
36
+ */
37
+
38
+ /**
39
+ * Initialize SitePong for Next.js
40
+ * Call this in your instrumentation.ts file
41
+ *
42
+ * @example
43
+ * ```ts
44
+ * // instrumentation.ts
45
+ * import { initNextJs } from '@sitepong/react/nextjs';
46
+ *
47
+ * export function register() {
48
+ * initNextJs({
49
+ * dsn: process.env.SITEPONG_DSN,
50
+ * });
51
+ * }
52
+ * ```
53
+ */
54
+ declare function initNextJs(config: SitePongConfig): void;
55
+ /**
56
+ * Wrap a Next.js API route handler to capture errors
57
+ *
58
+ * @example
59
+ * ```ts
60
+ * // app/api/users/route.ts
61
+ * import { wrapApiHandler } from '@sitepong/react/nextjs';
62
+ *
63
+ * export const GET = wrapApiHandler(async (request) => {
64
+ * const users = await getUsers();
65
+ * return Response.json(users);
66
+ * });
67
+ * ```
68
+ */
69
+ declare function wrapApiHandler<T extends (request: NextRequest, context?: unknown) => Promise<Response>>(handler: T): T;
70
+ /**
71
+ * Wrap a Next.js Server Action to capture errors
72
+ *
73
+ * @example
74
+ * ```ts
75
+ * // app/actions.ts
76
+ * 'use server';
77
+ * import { wrapServerAction } from '@sitepong/react/nextjs';
78
+ *
79
+ * export const createUser = wrapServerAction(async (formData: FormData) => {
80
+ * // Your server action logic
81
+ * });
82
+ * ```
83
+ */
84
+ declare function wrapServerAction<T extends (...args: unknown[]) => Promise<unknown>>(action: T, actionName?: string): T;
85
+ /**
86
+ * Global error handler for Next.js App Router
87
+ *
88
+ * @example
89
+ * ```tsx
90
+ * // app/error.tsx
91
+ * 'use client';
92
+ * import { NextErrorComponent } from '@sitepong/react/nextjs';
93
+ *
94
+ * export default NextErrorComponent;
95
+ * ```
96
+ */
97
+ declare function NextErrorComponent({ error, reset, }: {
98
+ error: Error & {
99
+ digest?: string;
100
+ };
101
+ reset: () => void;
102
+ }): any;
103
+ /**
104
+ * Next.js config wrapper (optional)
105
+ * Adds source map upload and other enhancements
106
+ */
107
+ declare function withSitePong(nextConfig?: Record<string, unknown>): Record<string, unknown>;
108
+
109
+ export { NextErrorComponent, initNextJs, withSitePong, wrapApiHandler, wrapServerAction };
@@ -0,0 +1,109 @@
1
+ import { NextRequest } from 'next/server';
2
+ import { S as SitePongConfig } from '../breadcrumbs-DwXK5gNn.js';
3
+ export { a as addBreadcrumb } from '../breadcrumbs-DwXK5gNn.js';
4
+ export { captureServerException, initServer } from '../server/index.js';
5
+
6
+ /**
7
+ * Next.js integration for SitePong
8
+ *
9
+ * @example
10
+ * ```ts
11
+ * // next.config.js
12
+ * const { withSitePong } = require('@sitepong/react/nextjs');
13
+ *
14
+ * module.exports = withSitePong({
15
+ * // your Next.js config
16
+ * });
17
+ * ```
18
+ *
19
+ * @example
20
+ * ```ts
21
+ * // app/layout.tsx
22
+ * import { SitePongProvider } from '@sitepong/react';
23
+ *
24
+ * export default function RootLayout({ children }) {
25
+ * return (
26
+ * <html>
27
+ * <body>
28
+ * <SitePongProvider dsn={process.env.NEXT_PUBLIC_SITEPONG_DSN}>
29
+ * {children}
30
+ * </SitePongProvider>
31
+ * </body>
32
+ * </html>
33
+ * );
34
+ * }
35
+ * ```
36
+ */
37
+
38
+ /**
39
+ * Initialize SitePong for Next.js
40
+ * Call this in your instrumentation.ts file
41
+ *
42
+ * @example
43
+ * ```ts
44
+ * // instrumentation.ts
45
+ * import { initNextJs } from '@sitepong/react/nextjs';
46
+ *
47
+ * export function register() {
48
+ * initNextJs({
49
+ * dsn: process.env.SITEPONG_DSN,
50
+ * });
51
+ * }
52
+ * ```
53
+ */
54
+ declare function initNextJs(config: SitePongConfig): void;
55
+ /**
56
+ * Wrap a Next.js API route handler to capture errors
57
+ *
58
+ * @example
59
+ * ```ts
60
+ * // app/api/users/route.ts
61
+ * import { wrapApiHandler } from '@sitepong/react/nextjs';
62
+ *
63
+ * export const GET = wrapApiHandler(async (request) => {
64
+ * const users = await getUsers();
65
+ * return Response.json(users);
66
+ * });
67
+ * ```
68
+ */
69
+ declare function wrapApiHandler<T extends (request: NextRequest, context?: unknown) => Promise<Response>>(handler: T): T;
70
+ /**
71
+ * Wrap a Next.js Server Action to capture errors
72
+ *
73
+ * @example
74
+ * ```ts
75
+ * // app/actions.ts
76
+ * 'use server';
77
+ * import { wrapServerAction } from '@sitepong/react/nextjs';
78
+ *
79
+ * export const createUser = wrapServerAction(async (formData: FormData) => {
80
+ * // Your server action logic
81
+ * });
82
+ * ```
83
+ */
84
+ declare function wrapServerAction<T extends (...args: unknown[]) => Promise<unknown>>(action: T, actionName?: string): T;
85
+ /**
86
+ * Global error handler for Next.js App Router
87
+ *
88
+ * @example
89
+ * ```tsx
90
+ * // app/error.tsx
91
+ * 'use client';
92
+ * import { NextErrorComponent } from '@sitepong/react/nextjs';
93
+ *
94
+ * export default NextErrorComponent;
95
+ * ```
96
+ */
97
+ declare function NextErrorComponent({ error, reset, }: {
98
+ error: Error & {
99
+ digest?: string;
100
+ };
101
+ reset: () => void;
102
+ }): any;
103
+ /**
104
+ * Next.js config wrapper (optional)
105
+ * Adds source map upload and other enhancements
106
+ */
107
+ declare function withSitePong(nextConfig?: Record<string, unknown>): Record<string, unknown>;
108
+
109
+ export { NextErrorComponent, initNextJs, withSitePong, wrapApiHandler, wrapServerAction };
@@ -0,0 +1,440 @@
1
+ 'use strict';
2
+
3
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
4
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
5
+ }) : x)(function(x) {
6
+ if (typeof require !== "undefined") return require.apply(this, arguments);
7
+ throw Error('Dynamic require of "' + x + '" is not supported');
8
+ });
9
+
10
+ // src/core/dsn.ts
11
+ function parseDSN(dsn) {
12
+ const match = dsn.match(
13
+ /^(https?):\/\/([^@]+)@([^/]+)\/(.+)$/
14
+ );
15
+ if (!match) {
16
+ throw new Error(
17
+ `Invalid DSN: ${dsn}. Expected format: https://<public_key>@<host>/<project_id>`
18
+ );
19
+ }
20
+ const [, protocol, publicKey, host, projectId] = match;
21
+ return {
22
+ protocol,
23
+ publicKey,
24
+ host,
25
+ projectId
26
+ };
27
+ }
28
+ function getApiEndpoint(config) {
29
+ if (config.apiEndpoint) {
30
+ return config.apiEndpoint;
31
+ }
32
+ if (config.dsn) {
33
+ const parsed = parseDSN(config.dsn);
34
+ return `${parsed.protocol}://${parsed.host}`;
35
+ }
36
+ return "https://api.sitepong.com";
37
+ }
38
+ function getAuthHeaders(config) {
39
+ if (config.dsn) {
40
+ const parsed = parseDSN(config.dsn);
41
+ return {
42
+ "X-SitePong-Key": parsed.publicKey,
43
+ "X-SitePong-Project": parsed.projectId
44
+ };
45
+ }
46
+ if (config.apiKey) {
47
+ return {
48
+ Authorization: `Bearer ${config.apiKey}`
49
+ };
50
+ }
51
+ return {};
52
+ }
53
+ function validateConfig(config) {
54
+ if (!config.dsn && !config.apiKey) {
55
+ throw new Error(
56
+ "SitePong: You must provide either a DSN or an API key"
57
+ );
58
+ }
59
+ if (config.dsn && config.apiKey) {
60
+ console.warn(
61
+ "SitePong: Both DSN and API key provided. DSN will be used."
62
+ );
63
+ }
64
+ if (config.sampleRate !== void 0) {
65
+ if (config.sampleRate < 0 || config.sampleRate > 1) {
66
+ throw new Error(
67
+ "SitePong: sampleRate must be between 0 and 1"
68
+ );
69
+ }
70
+ }
71
+ }
72
+
73
+ // src/utils/platform.ts
74
+ function detectPlatform() {
75
+ if (typeof navigator !== "undefined" && navigator.product === "ReactNative") {
76
+ return "react-native";
77
+ }
78
+ if (typeof process !== "undefined" && process.versions && process.versions.node) {
79
+ return "node";
80
+ }
81
+ if (typeof window !== "undefined" && typeof document !== "undefined") {
82
+ return "browser";
83
+ }
84
+ return "unknown";
85
+ }
86
+ var currentPlatform = detectPlatform();
87
+ function isReactNative() {
88
+ return currentPlatform === "react-native";
89
+ }
90
+
91
+ // src/utils/stacktrace.ts
92
+ function parseStackTrace(error) {
93
+ if (!error.stack) {
94
+ return [];
95
+ }
96
+ const lines = error.stack.split("\n");
97
+ const frames = [];
98
+ for (const line of lines) {
99
+ const frame = parseStackLine(line);
100
+ if (frame) {
101
+ frames.push(frame);
102
+ }
103
+ }
104
+ return frames;
105
+ }
106
+ function parseStackLine(line) {
107
+ const chromeMatch = line.match(
108
+ /^\s*at\s+(?:(.+?)\s+\()?(?:(.+?):(\d+):(\d+))\)?$/
109
+ );
110
+ if (chromeMatch) {
111
+ return {
112
+ function: chromeMatch[1] || "anonymous",
113
+ filename: chromeMatch[2],
114
+ lineno: parseInt(chromeMatch[3], 10),
115
+ colno: parseInt(chromeMatch[4], 10),
116
+ in_app: !chromeMatch[2]?.includes("node_modules")
117
+ };
118
+ }
119
+ const firefoxMatch = line.match(/^(.+?)@(.+?):(\d+):(\d+)$/);
120
+ if (firefoxMatch) {
121
+ return {
122
+ function: firefoxMatch[1] || "anonymous",
123
+ filename: firefoxMatch[2],
124
+ lineno: parseInt(firefoxMatch[3], 10),
125
+ colno: parseInt(firefoxMatch[4], 10),
126
+ in_app: !firefoxMatch[2]?.includes("node_modules")
127
+ };
128
+ }
129
+ return null;
130
+ }
131
+ function generateEventId() {
132
+ const hex = "0123456789abcdef";
133
+ let id = "";
134
+ for (let i = 0; i < 32; i++) {
135
+ id += hex[Math.floor(Math.random() * 16)];
136
+ }
137
+ return id;
138
+ }
139
+ function getBrowserContext() {
140
+ if (isReactNative()) {
141
+ return {
142
+ browser: { name: "React Native", version: "unknown" },
143
+ os: { name: "unknown", version: "unknown" },
144
+ runtime: { name: "React Native", version: "unknown" }
145
+ };
146
+ }
147
+ if (typeof process !== "undefined" && process.versions?.node) {
148
+ return {
149
+ browser: { name: "Node.js", version: process.versions.node },
150
+ os: {
151
+ name: process.platform || "unknown",
152
+ version: process.version || "unknown"
153
+ },
154
+ runtime: { name: "Node.js", version: process.versions.node }
155
+ };
156
+ }
157
+ if (typeof navigator === "undefined") {
158
+ return {
159
+ browser: { name: "unknown", version: "unknown" },
160
+ os: { name: "unknown", version: "unknown" }
161
+ };
162
+ }
163
+ const ua = navigator.userAgent;
164
+ let browserName = "unknown";
165
+ let browserVersion = "unknown";
166
+ if (ua.includes("Firefox/")) {
167
+ browserName = "Firefox";
168
+ browserVersion = ua.match(/Firefox\/(\d+)/)?.[1] || "unknown";
169
+ } else if (ua.includes("Edg/")) {
170
+ browserName = "Edge";
171
+ browserVersion = ua.match(/Edg\/(\d+)/)?.[1] || "unknown";
172
+ } else if (ua.includes("Chrome/")) {
173
+ browserName = "Chrome";
174
+ browserVersion = ua.match(/Chrome\/(\d+)/)?.[1] || "unknown";
175
+ } else if (ua.includes("Safari/") && !ua.includes("Chrome")) {
176
+ browserName = "Safari";
177
+ browserVersion = ua.match(/Version\/(\d+)/)?.[1] || "unknown";
178
+ }
179
+ let osName = "unknown";
180
+ let osVersion = "unknown";
181
+ if (ua.includes("Windows")) {
182
+ osName = "Windows";
183
+ osVersion = ua.match(/Windows NT (\d+\.\d+)/)?.[1] || "unknown";
184
+ } else if (ua.includes("Mac OS X")) {
185
+ osName = "macOS";
186
+ osVersion = ua.match(/Mac OS X (\d+[._]\d+)/)?.[1]?.replace("_", ".") || "unknown";
187
+ } else if (ua.includes("Linux")) {
188
+ osName = "Linux";
189
+ } else if (ua.includes("Android")) {
190
+ osName = "Android";
191
+ osVersion = ua.match(/Android (\d+)/)?.[1] || "unknown";
192
+ } else if (ua.includes("iOS") || ua.includes("iPhone") || ua.includes("iPad")) {
193
+ osName = "iOS";
194
+ osVersion = ua.match(/OS (\d+)/)?.[1] || "unknown";
195
+ }
196
+ return {
197
+ browser: { name: browserName, version: browserVersion },
198
+ os: { name: osName, version: osVersion }
199
+ };
200
+ }
201
+
202
+ // src/core/breadcrumbs.ts
203
+ var MAX_BREADCRUMBS = 100;
204
+ var BreadcrumbManager = class {
205
+ constructor() {
206
+ this.breadcrumbs = [];
207
+ this.maxBreadcrumbs = MAX_BREADCRUMBS;
208
+ }
209
+ setMaxBreadcrumbs(max) {
210
+ this.maxBreadcrumbs = max;
211
+ this.trim();
212
+ }
213
+ add(breadcrumb) {
214
+ const entry = {
215
+ ...breadcrumb,
216
+ timestamp: Date.now()
217
+ };
218
+ this.breadcrumbs.push(entry);
219
+ this.trim();
220
+ }
221
+ getAll() {
222
+ return [...this.breadcrumbs];
223
+ }
224
+ clear() {
225
+ this.breadcrumbs = [];
226
+ }
227
+ trim() {
228
+ if (this.breadcrumbs.length > this.maxBreadcrumbs) {
229
+ this.breadcrumbs = this.breadcrumbs.slice(-this.maxBreadcrumbs);
230
+ }
231
+ }
232
+ };
233
+ var breadcrumbs = new BreadcrumbManager();
234
+ function addBreadcrumb(breadcrumb) {
235
+ breadcrumbs.add(breadcrumb);
236
+ }
237
+ function getBreadcrumbs() {
238
+ return breadcrumbs.getAll();
239
+ }
240
+
241
+ // src/server/index.ts
242
+ var serverConfig = null;
243
+ function initServer(config) {
244
+ validateConfig(config);
245
+ serverConfig = {
246
+ enabled: true,
247
+ debug: false,
248
+ sampleRate: 1,
249
+ ...config
250
+ };
251
+ if (serverConfig.debug) {
252
+ console.log("[SitePong] Server SDK initialized");
253
+ }
254
+ }
255
+ async function captureServerException(error, context) {
256
+ if (!serverConfig?.enabled) {
257
+ return null;
258
+ }
259
+ const err = error instanceof Error ? error : new Error(String(error));
260
+ const event = createServerErrorEvent(err, context);
261
+ await sendServerEvent(event);
262
+ return event.event_id;
263
+ }
264
+ function createServerErrorEvent(error, context) {
265
+ const browserContext = getBrowserContext();
266
+ return {
267
+ event_id: generateEventId(),
268
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
269
+ level: context?.level || "error",
270
+ message: error.message,
271
+ exception: {
272
+ type: error.name,
273
+ value: error.message,
274
+ stacktrace: parseStackTrace(error)
275
+ },
276
+ tags: { ...context?.tags, runtime: "server" },
277
+ extra: { ...context?.extra },
278
+ user: context?.user,
279
+ breadcrumbs: getBreadcrumbs(),
280
+ context: {
281
+ runtime: browserContext.runtime || { name: "Node.js", version: process.version }
282
+ },
283
+ environment: serverConfig?.environment,
284
+ release: serverConfig?.release,
285
+ request: context?.request
286
+ };
287
+ }
288
+ async function sendServerEvent(event) {
289
+ if (!serverConfig) return;
290
+ const endpoint = getApiEndpoint(serverConfig);
291
+ const headers = getAuthHeaders(serverConfig);
292
+ try {
293
+ const response = await fetch(`${endpoint}/api/errors`, {
294
+ method: "POST",
295
+ headers: {
296
+ "Content-Type": "application/json",
297
+ ...headers
298
+ },
299
+ body: JSON.stringify({
300
+ title: event.exception?.type || "Error",
301
+ message: event.message,
302
+ stack: event.exception?.stacktrace?.map(
303
+ (frame) => ` at ${frame.function || "anonymous"} (${frame.filename}:${frame.lineno}:${frame.colno})`
304
+ ).join("\n"),
305
+ level: event.level,
306
+ timestamp: event.timestamp,
307
+ environment: event.environment,
308
+ release: event.release,
309
+ tags: event.tags,
310
+ extra: event.extra,
311
+ user: event.user,
312
+ breadcrumbs: event.breadcrumbs,
313
+ context: event.context,
314
+ request: event.request
315
+ })
316
+ });
317
+ if (serverConfig.debug) {
318
+ if (response.ok) {
319
+ console.log("[SitePong] Server event sent:", event.event_id);
320
+ } else {
321
+ console.error("[SitePong] Failed to send server event:", response.status);
322
+ }
323
+ }
324
+ } catch (err) {
325
+ if (serverConfig.debug) {
326
+ console.error("[SitePong] Error sending server event:", err);
327
+ }
328
+ }
329
+ }
330
+
331
+ // src/integrations/nextjs.ts
332
+ function initNextJs(config) {
333
+ initServer(config);
334
+ }
335
+ function wrapApiHandler(handler) {
336
+ return (async (request, context) => {
337
+ addBreadcrumb({
338
+ type: "http",
339
+ category: "http.request",
340
+ message: `${request.method} ${request.url}`,
341
+ level: "info",
342
+ data: {
343
+ method: request.method,
344
+ url: request.url
345
+ }
346
+ });
347
+ try {
348
+ return await handler(request, context);
349
+ } catch (error) {
350
+ await captureServerException(error, {
351
+ request: {
352
+ url: request.url,
353
+ method: request.method,
354
+ headers: Object.fromEntries(request.headers.entries())
355
+ },
356
+ tags: {
357
+ "next.runtime": "edge"
358
+ }
359
+ });
360
+ throw error;
361
+ }
362
+ });
363
+ }
364
+ function wrapServerAction(action, actionName) {
365
+ return (async (...args) => {
366
+ addBreadcrumb({
367
+ type: "default",
368
+ category: "server-action",
369
+ message: `Server Action: ${actionName || action.name || "anonymous"}`,
370
+ level: "info"
371
+ });
372
+ try {
373
+ return await action(...args);
374
+ } catch (error) {
375
+ await captureServerException(error, {
376
+ tags: {
377
+ "next.server_action": actionName || action.name || "anonymous"
378
+ }
379
+ });
380
+ throw error;
381
+ }
382
+ });
383
+ }
384
+ function NextErrorComponent({
385
+ error,
386
+ reset
387
+ }) {
388
+ const React = __require("react");
389
+ React.useEffect(() => {
390
+ console.error("[SitePong] Page error:", error);
391
+ }, [error]);
392
+ return React.createElement(
393
+ "div",
394
+ {
395
+ style: {
396
+ padding: "40px",
397
+ textAlign: "center",
398
+ fontFamily: "system-ui, sans-serif"
399
+ }
400
+ },
401
+ React.createElement("h2", null, "Something went wrong!"),
402
+ React.createElement(
403
+ "p",
404
+ { style: { color: "#666", marginBottom: "20px" } },
405
+ error.message || "An unexpected error occurred"
406
+ ),
407
+ React.createElement(
408
+ "button",
409
+ {
410
+ onClick: reset,
411
+ style: {
412
+ padding: "10px 20px",
413
+ cursor: "pointer",
414
+ backgroundColor: "#6366f1",
415
+ color: "white",
416
+ border: "none",
417
+ borderRadius: "6px"
418
+ }
419
+ },
420
+ "Try again"
421
+ )
422
+ );
423
+ }
424
+ function withSitePong(nextConfig = {}) {
425
+ return {
426
+ ...nextConfig
427
+ // Future: Add source map upload, etc.
428
+ };
429
+ }
430
+
431
+ exports.NextErrorComponent = NextErrorComponent;
432
+ exports.addBreadcrumb = addBreadcrumb;
433
+ exports.captureServerException = captureServerException;
434
+ exports.initNextJs = initNextJs;
435
+ exports.initServer = initServer;
436
+ exports.withSitePong = withSitePong;
437
+ exports.wrapApiHandler = wrapApiHandler;
438
+ exports.wrapServerAction = wrapServerAction;
439
+ //# sourceMappingURL=index.js.map
440
+ //# sourceMappingURL=index.js.map