dreaction-react 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,203 @@
1
+ /**
2
+ * Provides a global error handler to report errors.
3
+ */
4
+ import {
5
+ InferFeatures,
6
+ LoggerPlugin,
7
+ DReactionCore,
8
+ assertHasLoggerPlugin,
9
+ Plugin,
10
+ } from 'dreaction-client-core';
11
+
12
+ export interface ErrorStackFrame {
13
+ fileName: string;
14
+ functionName: string;
15
+ lineNumber: number;
16
+ columnNumber?: number | null;
17
+ }
18
+
19
+ export interface TrackGlobalErrorsOptions {
20
+ veto?: (frame: ErrorStackFrame) => boolean;
21
+ }
22
+
23
+ // defaults
24
+ const PLUGIN_DEFAULTS: TrackGlobalErrorsOptions = {
25
+ veto: undefined,
26
+ };
27
+
28
+ const objectifyError = (error: Error) => {
29
+ const objectifiedError = {} as Record<string, unknown>;
30
+ Object.getOwnPropertyNames(error).forEach((key) => {
31
+ objectifiedError[key] = (error as any)[key];
32
+ });
33
+ return objectifiedError;
34
+ };
35
+
36
+ /**
37
+ * Parse error stack trace
38
+ */
39
+ const parseErrorStack = (error: Error): ErrorStackFrame[] => {
40
+ if (!error.stack) {
41
+ return [];
42
+ }
43
+
44
+ const frames: ErrorStackFrame[] = [];
45
+ const lines = error.stack.split('\n');
46
+
47
+ // Skip the first line (error message)
48
+ for (let i = 1; i < lines.length; i++) {
49
+ const line = lines[i].trim();
50
+
51
+ // Try to match different stack trace formats
52
+ // Format: at functionName (fileName:lineNumber:columnNumber)
53
+ let match = line.match(/at\s+(.+?)\s+\((.+?):(\d+):(\d+)\)/);
54
+ if (match) {
55
+ frames.push({
56
+ functionName: match[1],
57
+ fileName: match[2],
58
+ lineNumber: parseInt(match[3], 10),
59
+ columnNumber: parseInt(match[4], 10),
60
+ });
61
+ continue;
62
+ }
63
+
64
+ // Format: at fileName:lineNumber:columnNumber
65
+ match = line.match(/at\s+(.+?):(\d+):(\d+)/);
66
+ if (match) {
67
+ frames.push({
68
+ functionName: '<anonymous>',
69
+ fileName: match[1],
70
+ lineNumber: parseInt(match[2], 10),
71
+ columnNumber: parseInt(match[3], 10),
72
+ });
73
+ continue;
74
+ }
75
+
76
+ // Format: functionName@fileName:lineNumber:columnNumber (Firefox)
77
+ match = line.match(/(.+?)@(.+?):(\d+):(\d+)/);
78
+ if (match) {
79
+ frames.push({
80
+ functionName: match[1] || '<anonymous>',
81
+ fileName: match[2],
82
+ lineNumber: parseInt(match[3], 10),
83
+ columnNumber: parseInt(match[4], 10),
84
+ });
85
+ continue;
86
+ }
87
+ }
88
+
89
+ return frames;
90
+ };
91
+
92
+ /**
93
+ * Track global errors and send them to DReaction logger.
94
+ */
95
+ const trackGlobalErrors =
96
+ (options?: TrackGlobalErrorsOptions) => (dreaction: DReactionCore) => {
97
+ // make sure we have the logger plugin
98
+ assertHasLoggerPlugin(dreaction);
99
+ const client = dreaction as DReactionCore &
100
+ InferFeatures<DReactionCore, LoggerPlugin>;
101
+
102
+ // setup configuration
103
+ const config = Object.assign({}, PLUGIN_DEFAULTS, options || {});
104
+
105
+ let originalWindowOnError: OnErrorEventHandler;
106
+ let unhandledRejectionHandler:
107
+ | ((event: PromiseRejectionEvent) => void)
108
+ | null = null;
109
+
110
+ // manually fire an error
111
+ function reportError(error: Error, stack?: ErrorStackFrame[]) {
112
+ try {
113
+ let prettyStackFrames = stack || parseErrorStack(error);
114
+
115
+ // does the dev want us to keep each frame?
116
+ if (config.veto) {
117
+ prettyStackFrames = prettyStackFrames.filter((frame) =>
118
+ config?.veto?.(frame)
119
+ );
120
+ }
121
+
122
+ client.error(error.message, prettyStackFrames);
123
+ } catch (e) {
124
+ client.error('Unable to parse stack trace from error object', []);
125
+ client.debug(objectifyError(e as Error));
126
+ }
127
+ }
128
+
129
+ // the dreaction plugin interface
130
+ return {
131
+ onConnect: () => {
132
+ if (typeof window === 'undefined') return;
133
+
134
+ // Intercept window.onerror
135
+ originalWindowOnError = window.onerror;
136
+ window.onerror = function (
137
+ message: string | Event,
138
+ source?: string,
139
+ lineno?: number,
140
+ colno?: number,
141
+ error?: Error
142
+ ) {
143
+ if (error) {
144
+ reportError(error);
145
+ } else if (typeof message === 'string') {
146
+ const syntheticError = new Error(message);
147
+ const frames: ErrorStackFrame[] = [];
148
+ if (source) {
149
+ frames.push({
150
+ fileName: source,
151
+ functionName: '<unknown>',
152
+ lineNumber: lineno || 0,
153
+ columnNumber: colno || 0,
154
+ });
155
+ }
156
+ reportError(syntheticError, frames);
157
+ }
158
+
159
+ // Call original handler
160
+ if (originalWindowOnError) {
161
+ return originalWindowOnError.apply(this, arguments as any);
162
+ }
163
+ return false;
164
+ };
165
+
166
+ // Intercept unhandled promise rejections
167
+ unhandledRejectionHandler = (event: PromiseRejectionEvent) => {
168
+ const error =
169
+ event.reason instanceof Error
170
+ ? event.reason
171
+ : new Error(String(event.reason));
172
+ reportError(error);
173
+ };
174
+ window.addEventListener(
175
+ 'unhandledrejection',
176
+ unhandledRejectionHandler
177
+ );
178
+ },
179
+
180
+ onDisconnect: () => {
181
+ if (typeof window === 'undefined') return;
182
+
183
+ // Restore original handlers
184
+ if (originalWindowOnError) {
185
+ window.onerror = originalWindowOnError;
186
+ }
187
+
188
+ if (unhandledRejectionHandler) {
189
+ window.removeEventListener(
190
+ 'unhandledrejection',
191
+ unhandledRejectionHandler
192
+ );
193
+ }
194
+ },
195
+
196
+ // attach these functions to the DReaction
197
+ features: {
198
+ reportError,
199
+ },
200
+ } satisfies Plugin<DReactionCore>;
201
+ };
202
+
203
+ export default trackGlobalErrors;
@@ -0,0 +1,53 @@
1
+ import {
2
+ InferFeatures,
3
+ LoggerPlugin,
4
+ DReactionCore,
5
+ assertHasLoggerPlugin,
6
+ Plugin,
7
+ } from 'dreaction-client-core';
8
+
9
+ /**
10
+ * Track calls to console.log, console.warn, and console.debug and send them to DReaction logger
11
+ */
12
+ const trackGlobalLogs = () => (dreaction: DReactionCore) => {
13
+ assertHasLoggerPlugin(dreaction);
14
+ const client = dreaction as DReactionCore &
15
+ InferFeatures<DReactionCore, LoggerPlugin>;
16
+
17
+ const originalConsoleLog = console.log;
18
+ const originalConsoleWarn = console.warn;
19
+ const originalConsoleDebug = console.debug;
20
+ const originalConsoleInfo = console.info;
21
+
22
+ return {
23
+ onConnect: () => {
24
+ console.log = (...args: Parameters<typeof console.log>) => {
25
+ originalConsoleLog(...args);
26
+ client.log(...args);
27
+ };
28
+ console.info = (...args: Parameters<typeof console.info>) => {
29
+ originalConsoleInfo(...args);
30
+ client.log(...args);
31
+ };
32
+ console.warn = (...args: Parameters<typeof console.warn>) => {
33
+ originalConsoleWarn(...args);
34
+ client.warn(args[0]);
35
+ };
36
+
37
+ console.debug = (...args: Parameters<typeof console.debug>) => {
38
+ originalConsoleDebug(...args);
39
+ client.debug(args[0]);
40
+ };
41
+
42
+ // console.error is taken care of by ./trackGlobalErrors.ts
43
+ },
44
+ onDisconnect: () => {
45
+ console.log = originalConsoleLog;
46
+ console.warn = originalConsoleWarn;
47
+ console.debug = originalConsoleDebug;
48
+ console.info = originalConsoleInfo;
49
+ },
50
+ } satisfies Plugin<DReactionCore>;
51
+ };
52
+
53
+ export default trackGlobalLogs;