@clienwork/errors 1.0.0 → 1.0.2

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 CHANGED
@@ -1,8 +1,15 @@
1
1
  # @clienwork/errors
2
2
 
3
- Clienwork 에러 수집 SDK
3
+ [![npm version](https://img.shields.io/npm/v/@clienwork/errors.svg)](https://www.npmjs.com/package/@clienwork/errors)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
4
5
 
5
- ## 설치
6
+ Error collection SDK for [Clienwork](https://clienwork.com)
7
+
8
+ Automatically collect and monitor errors from your frontend and backend applications. View error logs, analyze stack traces, and receive Slack notifications from the Clienwork dashboard.
9
+
10
+ **[Get Started](https://clienwork.com)** | **[Documentation](https://clienwork.com/docs)** | **[Dashboard](https://app.clienwork.com)**
11
+
12
+ ## Installation
6
13
 
7
14
  ```bash
8
15
  npm install @clienwork/errors
@@ -12,44 +19,44 @@ yarn add @clienwork/errors
12
19
  pnpm add @clienwork/errors
13
20
  ```
14
21
 
15
- ## 프론트엔드 사용법
22
+ ## Frontend Usage
16
23
 
17
- ### 초기화
24
+ ### Initialization
18
25
 
19
26
  ```typescript
20
27
  import { init } from '@clienwork/errors'
21
28
 
22
29
  init({
23
- apiToken: 'clw_err_xxx', // 프로젝트 설정에서 발급받은 토큰
24
- source: 'frontend', // 기본값
30
+ apiToken: 'clw_err_xxx', // Token from your project settings
31
+ source: 'frontend', // Default
25
32
  })
26
33
  ```
27
34
 
28
- ### 수동 에러 리포트
35
+ ### Manual Error Reporting
29
36
 
30
37
  ```typescript
31
38
  import { report } from '@clienwork/errors'
32
39
 
33
40
  try {
34
- // 위험한 작업
41
+ // Risky operation
35
42
  } catch (error) {
36
43
  report(error, { context: 'checkout' })
37
44
  }
38
45
  ```
39
46
 
40
- ### 사용자 식별
47
+ ### User Identification
41
48
 
42
49
  ```typescript
43
50
  import { setUser, clearUser } from '@clienwork/errors'
44
51
 
45
- // 로그인
52
+ // On login
46
53
  setUser('user-123')
47
54
 
48
- // 로그아웃
55
+ // On logout
49
56
  clearUser()
50
57
  ```
51
58
 
52
- ## React 사용법
59
+ ## React Usage
53
60
 
54
61
  ### Error Boundary
55
62
 
@@ -59,7 +66,7 @@ import { ClienworkErrorBoundary } from '@clienwork/errors/react'
59
66
  function App() {
60
67
  return (
61
68
  <ClienworkErrorBoundary
62
- fallback={<div>오류가 발생했습니다.</div>}
69
+ fallback={<div>Something went wrong.</div>}
63
70
  >
64
71
  <MyComponent />
65
72
  </ClienworkErrorBoundary>
@@ -92,12 +99,12 @@ function MyComponent() {
92
99
  ```tsx
93
100
  import { withErrorBoundary } from '@clienwork/errors/react'
94
101
 
95
- const SafeComponent = withErrorBoundary(MyComponent, <div>오류 발생</div>)
102
+ const SafeComponent = withErrorBoundary(MyComponent, <div>Error occurred</div>)
96
103
  ```
97
104
 
98
- ## 백엔드 사용법 (Node.js)
105
+ ## Backend Usage (Node.js)
99
106
 
100
- ### 기본 사용법
107
+ ### Basic Usage
101
108
 
102
109
  ```typescript
103
110
  import { createBackendReporter } from '@clienwork/errors'
@@ -107,13 +114,13 @@ const errorReporter = createBackendReporter({
107
114
  })
108
115
 
109
116
  try {
110
- // 위험한 작업
117
+ // Risky operation
111
118
  } catch (error) {
112
119
  await errorReporter.report(error, { endpoint: '/api/users' })
113
120
  }
114
121
  ```
115
122
 
116
- ### Express 미들웨어
123
+ ### Express Middleware
117
124
 
118
125
  ```typescript
119
126
  import { createBackendReporter, expressErrorHandler } from '@clienwork/errors'
@@ -144,22 +151,28 @@ export async function GET(request: Request) {
144
151
  }
145
152
  ```
146
153
 
147
- ## 설정 옵션
154
+ ## Configuration Options
148
155
 
149
156
  ```typescript
150
157
  init({
151
- apiToken: 'clw_err_xxx', // 필수: API 토큰
152
- endpoint: 'https://...', // 선택: 커스텀 엔드포인트
153
- source: 'frontend', // 선택: 'frontend' | 'backend'
154
- enabled: true, // 선택: 활성화 여부 (개발 환경에서 비활성화 가능)
155
- defaultMetadata: {}, // 선택: 모든 에러에 포함될 기본 메타데이터
156
- beforeSend: (error) => { // 선택: 전송 에러 수정/필터링
157
- // null 반환 전송 취소
158
+ apiToken: 'clw_err_xxx', // Required: API token
159
+ endpoint: 'https://...', // Optional: Custom endpoint
160
+ source: 'frontend', // Optional: 'frontend' | 'backend'
161
+ enabled: true, // Optional: Enable/disable (useful for dev environment)
162
+ defaultMetadata: {}, // Optional: Default metadata included in all errors
163
+ beforeSend: (error) => { // Optional: Modify/filter errors before sending
164
+ // Return null to cancel sending
158
165
  return error
159
166
  },
160
167
  })
161
168
  ```
162
169
 
163
- ## 라이선스
170
+ ## Support
171
+
172
+ - [Clienwork Homepage](https://clienwork.com)
173
+ - [Contact Us](mailto:support@clienwork.com)
174
+ - [GitHub Issues](https://github.com/clienwork/clienwork/issues)
175
+
176
+ ## License
164
177
 
165
- MIT
178
+ MIT - [Clienwork](https://clienwork.com)
package/dist/index.d.mts CHANGED
@@ -10,6 +10,7 @@ interface ErrorReportOptions {
10
10
  userId?: string;
11
11
  metadata?: Record<string, unknown>;
12
12
  fingerprint?: string;
13
+ release?: string;
13
14
  }
14
15
  interface ClienworkErrorConfig {
15
16
  apiToken: string;
@@ -18,6 +19,7 @@ interface ClienworkErrorConfig {
18
19
  defaultMetadata?: Record<string, unknown>;
19
20
  enabled?: boolean;
20
21
  beforeSend?: (error: ErrorReportOptions) => ErrorReportOptions | null;
22
+ release?: string;
21
23
  }
22
24
  interface ErrorReporterInstance {
23
25
  report: (error: Error | ErrorReportOptions, metadata?: Record<string, unknown>) => Promise<void>;
package/dist/index.d.ts CHANGED
@@ -10,6 +10,7 @@ interface ErrorReportOptions {
10
10
  userId?: string;
11
11
  metadata?: Record<string, unknown>;
12
12
  fingerprint?: string;
13
+ release?: string;
13
14
  }
14
15
  interface ClienworkErrorConfig {
15
16
  apiToken: string;
@@ -18,6 +19,7 @@ interface ClienworkErrorConfig {
18
19
  defaultMetadata?: Record<string, unknown>;
19
20
  enabled?: boolean;
20
21
  beforeSend?: (error: ErrorReportOptions) => ErrorReportOptions | null;
22
+ release?: string;
21
23
  }
22
24
  interface ErrorReporterInstance {
23
25
  report: (error: Error | ErrorReportOptions, metadata?: Record<string, unknown>) => Promise<void>;
package/dist/index.js CHANGED
@@ -43,7 +43,8 @@ var ErrorReporter = class {
43
43
  source: config.source || "frontend",
44
44
  defaultMetadata: config.defaultMetadata || {},
45
45
  enabled: config.enabled ?? true,
46
- beforeSend: config.beforeSend
46
+ beforeSend: config.beforeSend,
47
+ release: config.release
47
48
  };
48
49
  if (typeof window !== "undefined" && this.config.source === "frontend") {
49
50
  this.setupGlobalHandlers();
@@ -110,6 +111,9 @@ var ErrorReporter = class {
110
111
  reportData.url = reportData.url || window.location.href;
111
112
  reportData.userAgent = reportData.userAgent || navigator.userAgent;
112
113
  }
114
+ if (this.config.release) {
115
+ reportData.release = reportData.release || this.config.release;
116
+ }
113
117
  if (this.config.beforeSend) {
114
118
  const modified = this.config.beforeSend(reportData);
115
119
  if (modified === null) return;
package/dist/index.mjs CHANGED
@@ -1,13 +1,139 @@
1
- import {
2
- ErrorReporter,
3
- clearUser,
4
- createBackendReporter,
5
- expressErrorHandler,
6
- getReporter,
7
- init,
8
- report,
9
- setUser
10
- } from "./chunk-HLDC7IDZ.mjs";
1
+ // src/reporter.ts
2
+ var DEFAULT_ENDPOINT = "https://app.clienwork.com/api/errors";
3
+ var ErrorReporter = class {
4
+ constructor(config) {
5
+ this.userId = null;
6
+ this.metadata = {};
7
+ this.config = {
8
+ apiToken: config.apiToken,
9
+ endpoint: config.endpoint || DEFAULT_ENDPOINT,
10
+ source: config.source || "frontend",
11
+ defaultMetadata: config.defaultMetadata || {},
12
+ enabled: config.enabled ?? true,
13
+ beforeSend: config.beforeSend,
14
+ release: config.release
15
+ };
16
+ if (typeof window !== "undefined" && this.config.source === "frontend") {
17
+ this.setupGlobalHandlers();
18
+ }
19
+ }
20
+ setupGlobalHandlers() {
21
+ window.addEventListener("error", (event) => {
22
+ this.report({
23
+ message: event.message,
24
+ stack: event.error?.stack,
25
+ url: event.filename,
26
+ metadata: {
27
+ lineno: event.lineno,
28
+ colno: event.colno
29
+ }
30
+ });
31
+ });
32
+ window.addEventListener("unhandledrejection", (event) => {
33
+ const error = event.reason;
34
+ if (error instanceof Error) {
35
+ this.report(error);
36
+ } else {
37
+ this.report({
38
+ message: String(error),
39
+ metadata: { type: "unhandledrejection" }
40
+ });
41
+ }
42
+ });
43
+ }
44
+ setUser(userId) {
45
+ this.userId = userId;
46
+ }
47
+ clearUser() {
48
+ this.userId = null;
49
+ }
50
+ setMetadata(metadata) {
51
+ this.metadata = { ...this.metadata, ...metadata };
52
+ }
53
+ async report(error, additionalMetadata) {
54
+ if (!this.config.enabled) return;
55
+ let reportData;
56
+ if (error instanceof Error) {
57
+ reportData = {
58
+ message: error.message,
59
+ stack: error.stack,
60
+ source: this.config.source
61
+ };
62
+ } else {
63
+ reportData = {
64
+ ...error,
65
+ source: error.source || this.config.source
66
+ };
67
+ }
68
+ reportData.metadata = {
69
+ ...this.config.defaultMetadata,
70
+ ...this.metadata,
71
+ ...reportData.metadata,
72
+ ...additionalMetadata
73
+ };
74
+ if (this.userId) {
75
+ reportData.userId = this.userId;
76
+ }
77
+ if (typeof window !== "undefined") {
78
+ reportData.url = reportData.url || window.location.href;
79
+ reportData.userAgent = reportData.userAgent || navigator.userAgent;
80
+ }
81
+ if (this.config.release) {
82
+ reportData.release = reportData.release || this.config.release;
83
+ }
84
+ if (this.config.beforeSend) {
85
+ const modified = this.config.beforeSend(reportData);
86
+ if (modified === null) return;
87
+ reportData = modified;
88
+ }
89
+ try {
90
+ await fetch(this.config.endpoint, {
91
+ method: "POST",
92
+ headers: {
93
+ "Content-Type": "application/json",
94
+ Authorization: `Bearer ${this.config.apiToken}`
95
+ },
96
+ body: JSON.stringify(reportData)
97
+ });
98
+ } catch (e) {
99
+ console.warn("[ClienworkErrors] Failed to report error:", e);
100
+ }
101
+ }
102
+ };
103
+ var instance = null;
104
+ function init(config) {
105
+ instance = new ErrorReporter(config);
106
+ return instance;
107
+ }
108
+ function getReporter() {
109
+ return instance;
110
+ }
111
+ function report(error, metadata) {
112
+ if (!instance) {
113
+ console.warn("[ClienworkErrors] Not initialized. Call init() first.");
114
+ return Promise.resolve();
115
+ }
116
+ return instance.report(error, metadata);
117
+ }
118
+ function setUser(userId) {
119
+ instance?.setUser(userId);
120
+ }
121
+ function clearUser() {
122
+ instance?.clearUser();
123
+ }
124
+ function createBackendReporter(config) {
125
+ return new ErrorReporter({ ...config, source: "backend" });
126
+ }
127
+ function expressErrorHandler(reporter) {
128
+ return (err, req, res, next) => {
129
+ reporter.report(err, {
130
+ path: req.path,
131
+ method: req.method,
132
+ userAgent: req.headers?.["user-agent"]
133
+ });
134
+ next();
135
+ };
136
+ }
11
137
  export {
12
138
  ErrorReporter,
13
139
  clearUser,
package/dist/react.d.mts CHANGED
@@ -1,6 +1,51 @@
1
1
  import * as React from 'react';
2
- import { ErrorReportOptions } from './index.mjs';
3
- export { ClienworkErrorConfig, clearUser, getReporter, init, report, setUser } from './index.mjs';
2
+
3
+ type ErrorSource = 'frontend' | 'backend';
4
+ type ErrorLevel = 'error' | 'warning';
5
+ interface ErrorReportOptions {
6
+ message: string;
7
+ stack?: string;
8
+ level?: ErrorLevel;
9
+ source?: ErrorSource;
10
+ url?: string;
11
+ userAgent?: string;
12
+ userId?: string;
13
+ metadata?: Record<string, unknown>;
14
+ fingerprint?: string;
15
+ release?: string;
16
+ }
17
+ interface ClienworkErrorConfig {
18
+ apiToken: string;
19
+ endpoint?: string;
20
+ source?: ErrorSource;
21
+ defaultMetadata?: Record<string, unknown>;
22
+ enabled?: boolean;
23
+ beforeSend?: (error: ErrorReportOptions) => ErrorReportOptions | null;
24
+ release?: string;
25
+ }
26
+ interface ErrorReporterInstance {
27
+ report: (error: Error | ErrorReportOptions, metadata?: Record<string, unknown>) => Promise<void>;
28
+ setUser: (userId: string) => void;
29
+ clearUser: () => void;
30
+ setMetadata: (metadata: Record<string, unknown>) => void;
31
+ }
32
+
33
+ declare class ErrorReporter implements ErrorReporterInstance {
34
+ private config;
35
+ private userId;
36
+ private metadata;
37
+ constructor(config: ClienworkErrorConfig);
38
+ private setupGlobalHandlers;
39
+ setUser(userId: string): void;
40
+ clearUser(): void;
41
+ setMetadata(metadata: Record<string, unknown>): void;
42
+ report(error: Error | ErrorReportOptions, additionalMetadata?: Record<string, unknown>): Promise<void>;
43
+ }
44
+ declare function init(config: ClienworkErrorConfig): ErrorReporter;
45
+ declare function getReporter(): ErrorReporter | null;
46
+ declare function report(error: Error | ErrorReportOptions, metadata?: Record<string, unknown>): Promise<void>;
47
+ declare function setUser(userId: string): void;
48
+ declare function clearUser(): void;
4
49
 
5
50
  interface ErrorBoundaryProps {
6
51
  children: React.ReactNode;
@@ -22,4 +67,4 @@ declare function useErrorReporter(): {
22
67
  };
23
68
  declare function withErrorBoundary<P extends object>(Component: React.ComponentType<P>, fallback?: ErrorBoundaryProps['fallback']): React.FC<P>;
24
69
 
25
- export { ClienworkErrorBoundary, ErrorReportOptions, useErrorReporter, withErrorBoundary };
70
+ export { ClienworkErrorBoundary, type ClienworkErrorConfig, type ErrorReportOptions, clearUser, getReporter, init, report, setUser, useErrorReporter, withErrorBoundary };
package/dist/react.d.ts CHANGED
@@ -1,6 +1,51 @@
1
1
  import * as React from 'react';
2
- import { ErrorReportOptions } from './index.js';
3
- export { ClienworkErrorConfig, clearUser, getReporter, init, report, setUser } from './index.js';
2
+
3
+ type ErrorSource = 'frontend' | 'backend';
4
+ type ErrorLevel = 'error' | 'warning';
5
+ interface ErrorReportOptions {
6
+ message: string;
7
+ stack?: string;
8
+ level?: ErrorLevel;
9
+ source?: ErrorSource;
10
+ url?: string;
11
+ userAgent?: string;
12
+ userId?: string;
13
+ metadata?: Record<string, unknown>;
14
+ fingerprint?: string;
15
+ release?: string;
16
+ }
17
+ interface ClienworkErrorConfig {
18
+ apiToken: string;
19
+ endpoint?: string;
20
+ source?: ErrorSource;
21
+ defaultMetadata?: Record<string, unknown>;
22
+ enabled?: boolean;
23
+ beforeSend?: (error: ErrorReportOptions) => ErrorReportOptions | null;
24
+ release?: string;
25
+ }
26
+ interface ErrorReporterInstance {
27
+ report: (error: Error | ErrorReportOptions, metadata?: Record<string, unknown>) => Promise<void>;
28
+ setUser: (userId: string) => void;
29
+ clearUser: () => void;
30
+ setMetadata: (metadata: Record<string, unknown>) => void;
31
+ }
32
+
33
+ declare class ErrorReporter implements ErrorReporterInstance {
34
+ private config;
35
+ private userId;
36
+ private metadata;
37
+ constructor(config: ClienworkErrorConfig);
38
+ private setupGlobalHandlers;
39
+ setUser(userId: string): void;
40
+ clearUser(): void;
41
+ setMetadata(metadata: Record<string, unknown>): void;
42
+ report(error: Error | ErrorReportOptions, additionalMetadata?: Record<string, unknown>): Promise<void>;
43
+ }
44
+ declare function init(config: ClienworkErrorConfig): ErrorReporter;
45
+ declare function getReporter(): ErrorReporter | null;
46
+ declare function report(error: Error | ErrorReportOptions, metadata?: Record<string, unknown>): Promise<void>;
47
+ declare function setUser(userId: string): void;
48
+ declare function clearUser(): void;
4
49
 
5
50
  interface ErrorBoundaryProps {
6
51
  children: React.ReactNode;
@@ -22,4 +67,4 @@ declare function useErrorReporter(): {
22
67
  };
23
68
  declare function withErrorBoundary<P extends object>(Component: React.ComponentType<P>, fallback?: ErrorBoundaryProps['fallback']): React.FC<P>;
24
69
 
25
- export { ClienworkErrorBoundary, ErrorReportOptions, useErrorReporter, withErrorBoundary };
70
+ export { ClienworkErrorBoundary, type ClienworkErrorConfig, type ErrorReportOptions, clearUser, getReporter, init, report, setUser, useErrorReporter, withErrorBoundary };
package/dist/react.js CHANGED
@@ -54,7 +54,8 @@ var ErrorReporter = class {
54
54
  source: config.source || "frontend",
55
55
  defaultMetadata: config.defaultMetadata || {},
56
56
  enabled: config.enabled ?? true,
57
- beforeSend: config.beforeSend
57
+ beforeSend: config.beforeSend,
58
+ release: config.release
58
59
  };
59
60
  if (typeof window !== "undefined" && this.config.source === "frontend") {
60
61
  this.setupGlobalHandlers();
@@ -121,6 +122,9 @@ var ErrorReporter = class {
121
122
  reportData.url = reportData.url || window.location.href;
122
123
  reportData.userAgent = reportData.userAgent || navigator.userAgent;
123
124
  }
125
+ if (this.config.release) {
126
+ reportData.release = reportData.release || this.config.release;
127
+ }
124
128
  if (this.config.beforeSend) {
125
129
  const modified = this.config.beforeSend(reportData);
126
130
  if (modified === null) return;
package/dist/react.mjs CHANGED
@@ -1,13 +1,131 @@
1
- import {
2
- clearUser,
3
- getReporter,
4
- init,
5
- report,
6
- setUser
7
- } from "./chunk-HLDC7IDZ.mjs";
8
-
9
1
  // src/react.ts
10
2
  import * as React from "react";
3
+
4
+ // src/reporter.ts
5
+ var DEFAULT_ENDPOINT = "https://app.clienwork.com/api/errors";
6
+ var ErrorReporter = class {
7
+ constructor(config) {
8
+ this.userId = null;
9
+ this.metadata = {};
10
+ this.config = {
11
+ apiToken: config.apiToken,
12
+ endpoint: config.endpoint || DEFAULT_ENDPOINT,
13
+ source: config.source || "frontend",
14
+ defaultMetadata: config.defaultMetadata || {},
15
+ enabled: config.enabled ?? true,
16
+ beforeSend: config.beforeSend,
17
+ release: config.release
18
+ };
19
+ if (typeof window !== "undefined" && this.config.source === "frontend") {
20
+ this.setupGlobalHandlers();
21
+ }
22
+ }
23
+ setupGlobalHandlers() {
24
+ window.addEventListener("error", (event) => {
25
+ this.report({
26
+ message: event.message,
27
+ stack: event.error?.stack,
28
+ url: event.filename,
29
+ metadata: {
30
+ lineno: event.lineno,
31
+ colno: event.colno
32
+ }
33
+ });
34
+ });
35
+ window.addEventListener("unhandledrejection", (event) => {
36
+ const error = event.reason;
37
+ if (error instanceof Error) {
38
+ this.report(error);
39
+ } else {
40
+ this.report({
41
+ message: String(error),
42
+ metadata: { type: "unhandledrejection" }
43
+ });
44
+ }
45
+ });
46
+ }
47
+ setUser(userId) {
48
+ this.userId = userId;
49
+ }
50
+ clearUser() {
51
+ this.userId = null;
52
+ }
53
+ setMetadata(metadata) {
54
+ this.metadata = { ...this.metadata, ...metadata };
55
+ }
56
+ async report(error, additionalMetadata) {
57
+ if (!this.config.enabled) return;
58
+ let reportData;
59
+ if (error instanceof Error) {
60
+ reportData = {
61
+ message: error.message,
62
+ stack: error.stack,
63
+ source: this.config.source
64
+ };
65
+ } else {
66
+ reportData = {
67
+ ...error,
68
+ source: error.source || this.config.source
69
+ };
70
+ }
71
+ reportData.metadata = {
72
+ ...this.config.defaultMetadata,
73
+ ...this.metadata,
74
+ ...reportData.metadata,
75
+ ...additionalMetadata
76
+ };
77
+ if (this.userId) {
78
+ reportData.userId = this.userId;
79
+ }
80
+ if (typeof window !== "undefined") {
81
+ reportData.url = reportData.url || window.location.href;
82
+ reportData.userAgent = reportData.userAgent || navigator.userAgent;
83
+ }
84
+ if (this.config.release) {
85
+ reportData.release = reportData.release || this.config.release;
86
+ }
87
+ if (this.config.beforeSend) {
88
+ const modified = this.config.beforeSend(reportData);
89
+ if (modified === null) return;
90
+ reportData = modified;
91
+ }
92
+ try {
93
+ await fetch(this.config.endpoint, {
94
+ method: "POST",
95
+ headers: {
96
+ "Content-Type": "application/json",
97
+ Authorization: `Bearer ${this.config.apiToken}`
98
+ },
99
+ body: JSON.stringify(reportData)
100
+ });
101
+ } catch (e) {
102
+ console.warn("[ClienworkErrors] Failed to report error:", e);
103
+ }
104
+ }
105
+ };
106
+ var instance = null;
107
+ function init(config) {
108
+ instance = new ErrorReporter(config);
109
+ return instance;
110
+ }
111
+ function getReporter() {
112
+ return instance;
113
+ }
114
+ function report(error, metadata) {
115
+ if (!instance) {
116
+ console.warn("[ClienworkErrors] Not initialized. Call init() first.");
117
+ return Promise.resolve();
118
+ }
119
+ return instance.report(error, metadata);
120
+ }
121
+ function setUser(userId) {
122
+ instance?.setUser(userId);
123
+ }
124
+ function clearUser() {
125
+ instance?.clearUser();
126
+ }
127
+
128
+ // src/react.ts
11
129
  var ClienworkErrorBoundary = class extends React.Component {
12
130
  constructor(props) {
13
131
  super(props);
@@ -0,0 +1,41 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Clienwork Source Maps Upload CLI
4
+ *
5
+ * Upload source maps to Clienwork for stack trace parsing.
6
+ *
7
+ * Usage:
8
+ * npx clienwork-upload-sourcemaps --release <version> --include <glob>
9
+ *
10
+ * Environment:
11
+ * CLIENWORK_ERROR_TOKEN - API token for authentication
12
+ *
13
+ * Examples:
14
+ * npx clienwork-upload-sourcemaps --release v1.0.0 --include ".next/**\/*.map"
15
+ * npx clienwork-upload-sourcemaps --release $VERCEL_GIT_COMMIT_SHA --include "dist/**\/*.map"
16
+ */
17
+ interface UploadOptions {
18
+ apiToken: string;
19
+ release: string;
20
+ include: string[];
21
+ endpoint?: string;
22
+ dryRun?: boolean;
23
+ }
24
+ interface UploadResult {
25
+ success: boolean;
26
+ uploaded: Array<{
27
+ filename: string;
28
+ release: string;
29
+ size: number;
30
+ }>;
31
+ errors?: Array<{
32
+ filename: string;
33
+ error: string;
34
+ }>;
35
+ }
36
+ /**
37
+ * Upload source maps to Clienwork
38
+ */
39
+ declare function uploadSourceMaps(options: UploadOptions): Promise<UploadResult>;
40
+
41
+ export { type UploadOptions, type UploadResult, uploadSourceMaps };
@@ -0,0 +1,41 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Clienwork Source Maps Upload CLI
4
+ *
5
+ * Upload source maps to Clienwork for stack trace parsing.
6
+ *
7
+ * Usage:
8
+ * npx clienwork-upload-sourcemaps --release <version> --include <glob>
9
+ *
10
+ * Environment:
11
+ * CLIENWORK_ERROR_TOKEN - API token for authentication
12
+ *
13
+ * Examples:
14
+ * npx clienwork-upload-sourcemaps --release v1.0.0 --include ".next/**\/*.map"
15
+ * npx clienwork-upload-sourcemaps --release $VERCEL_GIT_COMMIT_SHA --include "dist/**\/*.map"
16
+ */
17
+ interface UploadOptions {
18
+ apiToken: string;
19
+ release: string;
20
+ include: string[];
21
+ endpoint?: string;
22
+ dryRun?: boolean;
23
+ }
24
+ interface UploadResult {
25
+ success: boolean;
26
+ uploaded: Array<{
27
+ filename: string;
28
+ release: string;
29
+ size: number;
30
+ }>;
31
+ errors?: Array<{
32
+ filename: string;
33
+ error: string;
34
+ }>;
35
+ }
36
+ /**
37
+ * Upload source maps to Clienwork
38
+ */
39
+ declare function uploadSourceMaps(options: UploadOptions): Promise<UploadResult>;
40
+
41
+ export { type UploadOptions, type UploadResult, uploadSourceMaps };
@@ -0,0 +1,227 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __export = (target, all) => {
10
+ for (var name in all)
11
+ __defProp(target, name, { get: all[name], enumerable: true });
12
+ };
13
+ var __copyProps = (to, from, except, desc) => {
14
+ if (from && typeof from === "object" || typeof from === "function") {
15
+ for (let key of __getOwnPropNames(from))
16
+ if (!__hasOwnProp.call(to, key) && key !== except)
17
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
18
+ }
19
+ return to;
20
+ };
21
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
22
+ // If the importer is in node compatibility mode or this is not an ESM
23
+ // file that has been converted to a CommonJS file using a Babel-
24
+ // compatible transform (i.e. "__esModule" has not been set), then set
25
+ // "default" to the CommonJS "module.exports" for node compatibility.
26
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
27
+ mod
28
+ ));
29
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
30
+
31
+ // src/upload-sourcemaps.ts
32
+ var upload_sourcemaps_exports = {};
33
+ __export(upload_sourcemaps_exports, {
34
+ uploadSourceMaps: () => uploadSourceMaps
35
+ });
36
+ module.exports = __toCommonJS(upload_sourcemaps_exports);
37
+ var fs = __toESM(require("fs"));
38
+ var path = __toESM(require("path"));
39
+ var DEFAULT_ENDPOINT = "https://app.clienwork.com/api/source-maps";
40
+ function matchGlob(pattern, filePath) {
41
+ const regexPattern = pattern.replace(/\./g, "\\.").replace(/\*\*/g, "{{GLOBSTAR}}").replace(/\*/g, "[^/]*").replace(/\{\{GLOBSTAR\}\}/g, ".*");
42
+ const regex = new RegExp(`^${regexPattern}$`);
43
+ return regex.test(filePath);
44
+ }
45
+ function findFiles(patterns, basePath = process.cwd()) {
46
+ const results = [];
47
+ function walk(dir, baseDir) {
48
+ try {
49
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
50
+ for (const entry of entries) {
51
+ const fullPath = path.join(dir, entry.name);
52
+ const relativePath = path.relative(baseDir, fullPath).replace(/\\/g, "/");
53
+ if (entry.isDirectory()) {
54
+ if (entry.name !== "node_modules" && !entry.name.startsWith(".")) {
55
+ walk(fullPath, baseDir);
56
+ }
57
+ } else if (entry.isFile() && entry.name.endsWith(".map")) {
58
+ for (const pattern of patterns) {
59
+ if (matchGlob(pattern, relativePath)) {
60
+ results.push(fullPath);
61
+ break;
62
+ }
63
+ }
64
+ }
65
+ }
66
+ } catch (error) {
67
+ }
68
+ }
69
+ walk(basePath, basePath);
70
+ return results;
71
+ }
72
+ async function uploadSourceMaps(options) {
73
+ const { apiToken, release, include, endpoint = DEFAULT_ENDPOINT, dryRun = false } = options;
74
+ console.log(`[Clienwork] Uploading source maps for release: ${release}`);
75
+ const files = findFiles(include);
76
+ if (files.length === 0) {
77
+ console.log("[Clienwork] No source map files found");
78
+ return { success: true, uploaded: [] };
79
+ }
80
+ console.log(`[Clienwork] Found ${files.length} source map files`);
81
+ if (dryRun) {
82
+ console.log("[Clienwork] Dry run mode - files would be uploaded:");
83
+ files.forEach((f) => console.log(` - ${path.relative(process.cwd(), f)}`));
84
+ return {
85
+ success: true,
86
+ uploaded: files.map((f) => ({
87
+ filename: path.basename(f),
88
+ release,
89
+ size: fs.statSync(f).size
90
+ }))
91
+ };
92
+ }
93
+ const boundary = "----ClienworkBoundary" + Math.random().toString(36).substring(2);
94
+ const parts = [];
95
+ parts.push(
96
+ Buffer.from(
97
+ `--${boundary}\r
98
+ Content-Disposition: form-data; name="release"\r
99
+ \r
100
+ ${release}\r
101
+ `
102
+ )
103
+ );
104
+ for (const file of files) {
105
+ const filename = path.basename(file);
106
+ const content = fs.readFileSync(file);
107
+ parts.push(
108
+ Buffer.from(
109
+ `--${boundary}\r
110
+ Content-Disposition: form-data; name="files"; filename="${filename}"\r
111
+ Content-Type: application/json\r
112
+ \r
113
+ `
114
+ )
115
+ );
116
+ parts.push(content);
117
+ parts.push(Buffer.from("\r\n"));
118
+ }
119
+ parts.push(Buffer.from(`--${boundary}--\r
120
+ `));
121
+ const body = Buffer.concat(parts);
122
+ try {
123
+ const response = await fetch(endpoint, {
124
+ method: "POST",
125
+ headers: {
126
+ Authorization: `Bearer ${apiToken}`,
127
+ "Content-Type": `multipart/form-data; boundary=${boundary}`
128
+ },
129
+ body
130
+ });
131
+ const result = await response.json();
132
+ if (result.success) {
133
+ console.log(`[Clienwork] Uploaded ${result.uploaded.length} source maps`);
134
+ result.uploaded.forEach((f) => console.log(` \u2713 ${f.filename} (${formatBytes(f.size)})`));
135
+ if (result.errors && result.errors.length > 0) {
136
+ console.warn("[Clienwork] Some files failed:");
137
+ result.errors.forEach((e) => console.warn(` \u2717 ${e.filename}: ${e.error}`));
138
+ }
139
+ return result;
140
+ } else {
141
+ throw new Error(result.error || "Upload failed");
142
+ }
143
+ } catch (error) {
144
+ console.error("[Clienwork] Upload failed:", error);
145
+ throw error;
146
+ }
147
+ }
148
+ function formatBytes(bytes) {
149
+ if (bytes < 1024) return `${bytes} B`;
150
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
151
+ return `${(bytes / 1024 / 1024).toFixed(1)} MB`;
152
+ }
153
+ function parseArgs(args) {
154
+ const result = {
155
+ include: [],
156
+ dryRun: false
157
+ };
158
+ for (let i = 0; i < args.length; i++) {
159
+ const arg = args[i];
160
+ if (arg === "--release" || arg === "-r") {
161
+ result.release = args[++i];
162
+ } else if (arg === "--include" || arg === "-i") {
163
+ result.include.push(args[++i]);
164
+ } else if (arg === "--dry-run") {
165
+ result.dryRun = true;
166
+ } else if (arg === "--help" || arg === "-h") {
167
+ console.log(`
168
+ Clienwork Source Maps Upload CLI
169
+
170
+ Usage:
171
+ clienwork-upload-sourcemaps --release <version> --include <glob>
172
+
173
+ Options:
174
+ -r, --release <version> Release version (required)
175
+ -i, --include <glob> Glob pattern for source map files (can be used multiple times)
176
+ --dry-run Show what would be uploaded without uploading
177
+ -h, --help Show this help message
178
+
179
+ Environment Variables:
180
+ CLIENWORK_ERROR_TOKEN API token for authentication (required)
181
+ CLIENWORK_RELEASE Default release version if --release not specified
182
+ CLIENWORK_ENDPOINT Custom API endpoint (optional)
183
+
184
+ Examples:
185
+ clienwork-upload-sourcemaps --release v1.0.0 --include ".next/**/*.map"
186
+ clienwork-upload-sourcemaps --release \${VERCEL_GIT_COMMIT_SHA} --include "dist/**/*.map"
187
+ `);
188
+ process.exit(0);
189
+ }
190
+ }
191
+ if (result.include.length === 0) {
192
+ result.include = [".next/**/*.map", "dist/**/*.map", "build/**/*.map"];
193
+ }
194
+ return result;
195
+ }
196
+ async function main() {
197
+ const args = parseArgs(process.argv.slice(2));
198
+ const apiToken = process.env.CLIENWORK_ERROR_TOKEN;
199
+ if (!apiToken) {
200
+ console.error("[Clienwork] Error: CLIENWORK_ERROR_TOKEN environment variable is required");
201
+ process.exit(1);
202
+ }
203
+ const release = args.release || process.env.CLIENWORK_RELEASE;
204
+ if (!release) {
205
+ console.error("[Clienwork] Error: --release option or CLIENWORK_RELEASE environment variable is required");
206
+ process.exit(1);
207
+ }
208
+ try {
209
+ await uploadSourceMaps({
210
+ apiToken,
211
+ release,
212
+ include: args.include,
213
+ endpoint: process.env.CLIENWORK_ENDPOINT,
214
+ dryRun: args.dryRun
215
+ });
216
+ process.exit(0);
217
+ } catch (error) {
218
+ process.exit(1);
219
+ }
220
+ }
221
+ if (require.main === module) {
222
+ main();
223
+ }
224
+ // Annotate the CommonJS export names for ESM import in node:
225
+ 0 && (module.exports = {
226
+ uploadSourceMaps
227
+ });
@@ -0,0 +1,199 @@
1
+ #!/usr/bin/env node
2
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
3
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
4
+ }) : x)(function(x) {
5
+ if (typeof require !== "undefined") return require.apply(this, arguments);
6
+ throw Error('Dynamic require of "' + x + '" is not supported');
7
+ });
8
+
9
+ // src/upload-sourcemaps.ts
10
+ import * as fs from "fs";
11
+ import * as path from "path";
12
+ var DEFAULT_ENDPOINT = "https://app.clienwork.com/api/source-maps";
13
+ function matchGlob(pattern, filePath) {
14
+ const regexPattern = pattern.replace(/\./g, "\\.").replace(/\*\*/g, "{{GLOBSTAR}}").replace(/\*/g, "[^/]*").replace(/\{\{GLOBSTAR\}\}/g, ".*");
15
+ const regex = new RegExp(`^${regexPattern}$`);
16
+ return regex.test(filePath);
17
+ }
18
+ function findFiles(patterns, basePath = process.cwd()) {
19
+ const results = [];
20
+ function walk(dir, baseDir) {
21
+ try {
22
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
23
+ for (const entry of entries) {
24
+ const fullPath = path.join(dir, entry.name);
25
+ const relativePath = path.relative(baseDir, fullPath).replace(/\\/g, "/");
26
+ if (entry.isDirectory()) {
27
+ if (entry.name !== "node_modules" && !entry.name.startsWith(".")) {
28
+ walk(fullPath, baseDir);
29
+ }
30
+ } else if (entry.isFile() && entry.name.endsWith(".map")) {
31
+ for (const pattern of patterns) {
32
+ if (matchGlob(pattern, relativePath)) {
33
+ results.push(fullPath);
34
+ break;
35
+ }
36
+ }
37
+ }
38
+ }
39
+ } catch (error) {
40
+ }
41
+ }
42
+ walk(basePath, basePath);
43
+ return results;
44
+ }
45
+ async function uploadSourceMaps(options) {
46
+ const { apiToken, release, include, endpoint = DEFAULT_ENDPOINT, dryRun = false } = options;
47
+ console.log(`[Clienwork] Uploading source maps for release: ${release}`);
48
+ const files = findFiles(include);
49
+ if (files.length === 0) {
50
+ console.log("[Clienwork] No source map files found");
51
+ return { success: true, uploaded: [] };
52
+ }
53
+ console.log(`[Clienwork] Found ${files.length} source map files`);
54
+ if (dryRun) {
55
+ console.log("[Clienwork] Dry run mode - files would be uploaded:");
56
+ files.forEach((f) => console.log(` - ${path.relative(process.cwd(), f)}`));
57
+ return {
58
+ success: true,
59
+ uploaded: files.map((f) => ({
60
+ filename: path.basename(f),
61
+ release,
62
+ size: fs.statSync(f).size
63
+ }))
64
+ };
65
+ }
66
+ const boundary = "----ClienworkBoundary" + Math.random().toString(36).substring(2);
67
+ const parts = [];
68
+ parts.push(
69
+ Buffer.from(
70
+ `--${boundary}\r
71
+ Content-Disposition: form-data; name="release"\r
72
+ \r
73
+ ${release}\r
74
+ `
75
+ )
76
+ );
77
+ for (const file of files) {
78
+ const filename = path.basename(file);
79
+ const content = fs.readFileSync(file);
80
+ parts.push(
81
+ Buffer.from(
82
+ `--${boundary}\r
83
+ Content-Disposition: form-data; name="files"; filename="${filename}"\r
84
+ Content-Type: application/json\r
85
+ \r
86
+ `
87
+ )
88
+ );
89
+ parts.push(content);
90
+ parts.push(Buffer.from("\r\n"));
91
+ }
92
+ parts.push(Buffer.from(`--${boundary}--\r
93
+ `));
94
+ const body = Buffer.concat(parts);
95
+ try {
96
+ const response = await fetch(endpoint, {
97
+ method: "POST",
98
+ headers: {
99
+ Authorization: `Bearer ${apiToken}`,
100
+ "Content-Type": `multipart/form-data; boundary=${boundary}`
101
+ },
102
+ body
103
+ });
104
+ const result = await response.json();
105
+ if (result.success) {
106
+ console.log(`[Clienwork] Uploaded ${result.uploaded.length} source maps`);
107
+ result.uploaded.forEach((f) => console.log(` \u2713 ${f.filename} (${formatBytes(f.size)})`));
108
+ if (result.errors && result.errors.length > 0) {
109
+ console.warn("[Clienwork] Some files failed:");
110
+ result.errors.forEach((e) => console.warn(` \u2717 ${e.filename}: ${e.error}`));
111
+ }
112
+ return result;
113
+ } else {
114
+ throw new Error(result.error || "Upload failed");
115
+ }
116
+ } catch (error) {
117
+ console.error("[Clienwork] Upload failed:", error);
118
+ throw error;
119
+ }
120
+ }
121
+ function formatBytes(bytes) {
122
+ if (bytes < 1024) return `${bytes} B`;
123
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
124
+ return `${(bytes / 1024 / 1024).toFixed(1)} MB`;
125
+ }
126
+ function parseArgs(args) {
127
+ const result = {
128
+ include: [],
129
+ dryRun: false
130
+ };
131
+ for (let i = 0; i < args.length; i++) {
132
+ const arg = args[i];
133
+ if (arg === "--release" || arg === "-r") {
134
+ result.release = args[++i];
135
+ } else if (arg === "--include" || arg === "-i") {
136
+ result.include.push(args[++i]);
137
+ } else if (arg === "--dry-run") {
138
+ result.dryRun = true;
139
+ } else if (arg === "--help" || arg === "-h") {
140
+ console.log(`
141
+ Clienwork Source Maps Upload CLI
142
+
143
+ Usage:
144
+ clienwork-upload-sourcemaps --release <version> --include <glob>
145
+
146
+ Options:
147
+ -r, --release <version> Release version (required)
148
+ -i, --include <glob> Glob pattern for source map files (can be used multiple times)
149
+ --dry-run Show what would be uploaded without uploading
150
+ -h, --help Show this help message
151
+
152
+ Environment Variables:
153
+ CLIENWORK_ERROR_TOKEN API token for authentication (required)
154
+ CLIENWORK_RELEASE Default release version if --release not specified
155
+ CLIENWORK_ENDPOINT Custom API endpoint (optional)
156
+
157
+ Examples:
158
+ clienwork-upload-sourcemaps --release v1.0.0 --include ".next/**/*.map"
159
+ clienwork-upload-sourcemaps --release \${VERCEL_GIT_COMMIT_SHA} --include "dist/**/*.map"
160
+ `);
161
+ process.exit(0);
162
+ }
163
+ }
164
+ if (result.include.length === 0) {
165
+ result.include = [".next/**/*.map", "dist/**/*.map", "build/**/*.map"];
166
+ }
167
+ return result;
168
+ }
169
+ async function main() {
170
+ const args = parseArgs(process.argv.slice(2));
171
+ const apiToken = process.env.CLIENWORK_ERROR_TOKEN;
172
+ if (!apiToken) {
173
+ console.error("[Clienwork] Error: CLIENWORK_ERROR_TOKEN environment variable is required");
174
+ process.exit(1);
175
+ }
176
+ const release = args.release || process.env.CLIENWORK_RELEASE;
177
+ if (!release) {
178
+ console.error("[Clienwork] Error: --release option or CLIENWORK_RELEASE environment variable is required");
179
+ process.exit(1);
180
+ }
181
+ try {
182
+ await uploadSourceMaps({
183
+ apiToken,
184
+ release,
185
+ include: args.include,
186
+ endpoint: process.env.CLIENWORK_ENDPOINT,
187
+ dryRun: args.dryRun
188
+ });
189
+ process.exit(0);
190
+ } catch (error) {
191
+ process.exit(1);
192
+ }
193
+ }
194
+ if (__require.main === module) {
195
+ main();
196
+ }
197
+ export {
198
+ uploadSourceMaps
199
+ };
package/package.json CHANGED
@@ -1,10 +1,13 @@
1
1
  {
2
2
  "name": "@clienwork/errors",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "Error collection SDK for Clienwork",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
7
7
  "types": "dist/index.d.ts",
8
+ "bin": {
9
+ "clienwork-upload-sourcemaps": "./dist/upload-sourcemaps.js"
10
+ },
8
11
  "exports": {
9
12
  ".": {
10
13
  "types": "./dist/index.d.ts",
@@ -15,14 +18,19 @@
15
18
  "types": "./dist/react.d.ts",
16
19
  "import": "./dist/react.mjs",
17
20
  "require": "./dist/react.js"
21
+ },
22
+ "./upload-sourcemaps": {
23
+ "types": "./dist/upload-sourcemaps.d.ts",
24
+ "import": "./dist/upload-sourcemaps.mjs",
25
+ "require": "./dist/upload-sourcemaps.js"
18
26
  }
19
27
  },
20
28
  "files": [
21
29
  "dist"
22
30
  ],
23
31
  "scripts": {
24
- "build": "tsup src/index.ts src/react.ts --format cjs,esm --dts --clean",
25
- "dev": "tsup src/index.ts src/react.ts --format cjs,esm --dts --watch",
32
+ "build": "tsup src/index.ts --format cjs,esm --dts --clean && tsup src/react.ts --format cjs,esm --dts && tsup src/upload-sourcemaps.ts --format cjs,esm --dts",
33
+ "dev": "tsup src/index.ts src/react.ts src/upload-sourcemaps.ts --format cjs,esm --dts --watch",
26
34
  "prepublishOnly": "npm run build"
27
35
  },
28
36
  "peerDependencies": {
@@ -34,6 +42,7 @@
34
42
  }
35
43
  },
36
44
  "devDependencies": {
45
+ "@types/node": "^20.0.0",
37
46
  "react": "^18.0.0",
38
47
  "tsup": "^8.0.0",
39
48
  "typescript": "^5.0.0"
@@ -45,8 +54,13 @@
45
54
  "clienwork"
46
55
  ],
47
56
  "license": "MIT",
57
+ "homepage": "https://clienwork.com",
48
58
  "repository": {
49
59
  "type": "git",
50
60
  "url": "https://github.com/clienwork/clienwork"
51
- }
61
+ },
62
+ "bugs": {
63
+ "url": "https://github.com/clienwork/clienwork/issues"
64
+ },
65
+ "author": "Clienwork <support@clienwork.com>"
52
66
  }
@@ -1,143 +0,0 @@
1
- // src/reporter.ts
2
- var DEFAULT_ENDPOINT = "https://app.clienwork.com/api/errors";
3
- var ErrorReporter = class {
4
- constructor(config) {
5
- this.userId = null;
6
- this.metadata = {};
7
- this.config = {
8
- apiToken: config.apiToken,
9
- endpoint: config.endpoint || DEFAULT_ENDPOINT,
10
- source: config.source || "frontend",
11
- defaultMetadata: config.defaultMetadata || {},
12
- enabled: config.enabled ?? true,
13
- beforeSend: config.beforeSend
14
- };
15
- if (typeof window !== "undefined" && this.config.source === "frontend") {
16
- this.setupGlobalHandlers();
17
- }
18
- }
19
- setupGlobalHandlers() {
20
- window.addEventListener("error", (event) => {
21
- this.report({
22
- message: event.message,
23
- stack: event.error?.stack,
24
- url: event.filename,
25
- metadata: {
26
- lineno: event.lineno,
27
- colno: event.colno
28
- }
29
- });
30
- });
31
- window.addEventListener("unhandledrejection", (event) => {
32
- const error = event.reason;
33
- if (error instanceof Error) {
34
- this.report(error);
35
- } else {
36
- this.report({
37
- message: String(error),
38
- metadata: { type: "unhandledrejection" }
39
- });
40
- }
41
- });
42
- }
43
- setUser(userId) {
44
- this.userId = userId;
45
- }
46
- clearUser() {
47
- this.userId = null;
48
- }
49
- setMetadata(metadata) {
50
- this.metadata = { ...this.metadata, ...metadata };
51
- }
52
- async report(error, additionalMetadata) {
53
- if (!this.config.enabled) return;
54
- let reportData;
55
- if (error instanceof Error) {
56
- reportData = {
57
- message: error.message,
58
- stack: error.stack,
59
- source: this.config.source
60
- };
61
- } else {
62
- reportData = {
63
- ...error,
64
- source: error.source || this.config.source
65
- };
66
- }
67
- reportData.metadata = {
68
- ...this.config.defaultMetadata,
69
- ...this.metadata,
70
- ...reportData.metadata,
71
- ...additionalMetadata
72
- };
73
- if (this.userId) {
74
- reportData.userId = this.userId;
75
- }
76
- if (typeof window !== "undefined") {
77
- reportData.url = reportData.url || window.location.href;
78
- reportData.userAgent = reportData.userAgent || navigator.userAgent;
79
- }
80
- if (this.config.beforeSend) {
81
- const modified = this.config.beforeSend(reportData);
82
- if (modified === null) return;
83
- reportData = modified;
84
- }
85
- try {
86
- await fetch(this.config.endpoint, {
87
- method: "POST",
88
- headers: {
89
- "Content-Type": "application/json",
90
- Authorization: `Bearer ${this.config.apiToken}`
91
- },
92
- body: JSON.stringify(reportData)
93
- });
94
- } catch (e) {
95
- console.warn("[ClienworkErrors] Failed to report error:", e);
96
- }
97
- }
98
- };
99
- var instance = null;
100
- function init(config) {
101
- instance = new ErrorReporter(config);
102
- return instance;
103
- }
104
- function getReporter() {
105
- return instance;
106
- }
107
- function report(error, metadata) {
108
- if (!instance) {
109
- console.warn("[ClienworkErrors] Not initialized. Call init() first.");
110
- return Promise.resolve();
111
- }
112
- return instance.report(error, metadata);
113
- }
114
- function setUser(userId) {
115
- instance?.setUser(userId);
116
- }
117
- function clearUser() {
118
- instance?.clearUser();
119
- }
120
- function createBackendReporter(config) {
121
- return new ErrorReporter({ ...config, source: "backend" });
122
- }
123
- function expressErrorHandler(reporter) {
124
- return (err, req, res, next) => {
125
- reporter.report(err, {
126
- path: req.path,
127
- method: req.method,
128
- userAgent: req.headers?.["user-agent"]
129
- });
130
- next();
131
- };
132
- }
133
-
134
- export {
135
- ErrorReporter,
136
- init,
137
- getReporter,
138
- report,
139
- setUser,
140
- clearUser,
141
- createBackendReporter,
142
- expressErrorHandler
143
- };