Skip to content

Commit dab3ca0

Browse files
Prettified.
1 parent 1cd46bc commit dab3ca0

File tree

1 file changed

+115
-57
lines changed

1 file changed

+115
-57
lines changed

services/errorReporting.service.ts

Lines changed: 115 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,73 +1,131 @@
1-
import { Platform } from "react-native";
2-
import { ErrorInfo } from "react";
1+
import { Platform } from 'react-native';
2+
import { ErrorInfo } from 'react';
33

44
export interface ReportOptions {
5-
isFatal?: boolean;
6-
errorInfo?: ErrorInfo;
5+
isFatal?: boolean;
6+
errorInfo?: ErrorInfo;
77
}
88

99
export interface ErrorReport {
10-
name: string;
11-
cause?: string;
12-
message: string;
13-
stack?: string;
14-
timestamp: string;
15-
platform: string;
16-
options?: ReportOptions;
10+
name: string;
11+
cause?: string;
12+
message: string;
13+
stack?: string;
14+
timestamp: string;
15+
platform: string;
16+
options?: ReportOptions;
1717
}
1818

19-
const ERROR_REPORTING_ENDPOINT = "https://new.codebuilder.org/api/errors";
19+
const ERROR_REPORTING_ENDPOINT = 'https://new.codebuilder.org/api/errors';
20+
21+
// Circuit breaker implementation
22+
const circuitBreaker = {
23+
failureCount: 0,
24+
lastFailureTime: 0,
25+
isOpen: false,
26+
failureThreshold: 3,
27+
resetTimeout: 60000, // 1 minute in ms
28+
baseDelay: 5000, // 5 seconds initial delay
29+
maxDelay: 3600000, // 1 hour maximum delay
30+
shouldPreventApiCall(): boolean {
31+
if (this.isOpen) {
32+
const now = Date.now();
33+
const timeSinceLastFailure = now - this.lastFailureTime;
34+
const currentBackoff = Math.min(this.baseDelay * Math.pow(2, this.failureCount - 1), this.maxDelay);
35+
if (timeSinceLastFailure > currentBackoff) {
36+
// Allow one test request
37+
return false;
38+
}
39+
return true;
40+
}
41+
return false;
42+
},
43+
recordSuccess(): void {
44+
if (this.failureCount > 0) {
45+
console.log('Error reporting API is back online');
46+
}
47+
this.failureCount = 0;
48+
this.isOpen = false;
49+
},
50+
recordFailure(): void {
51+
const now = Date.now();
52+
this.failureCount++;
53+
this.lastFailureTime = now;
54+
if (this.failureCount >= this.failureThreshold) {
55+
this.isOpen = true;
56+
const backoffTime = Math.min(this.baseDelay * Math.pow(2, this.failureCount - 1), this.maxDelay);
57+
console.log(`Circuit opened after ${this.failureCount} failures. Will retry in ${backoffTime / 1000}s`);
58+
}
59+
},
60+
};
61+
62+
// Flag to prevent recursive error reporting
63+
let isReportingError = false;
64+
// Count nested error reporting attempts to detect potential infinite loops
65+
let nestedReportCount = 0;
66+
const MAX_NESTED_REPORTS = 3;
2067

2168
// Wrap fetch in a safe function so it never throws
22-
export const safeReport = async (
23-
error: Error,
24-
options?: ReportOptions
25-
): Promise<void> => {
26-
// Build normalized report
27-
const report: ErrorReport = {
28-
name: error.name,
29-
message: error.message || String(error),
30-
stack: error.stack || undefined,
31-
cause: error.cause ? String(error.cause) : undefined,
32-
timestamp: new Date().toISOString(),
33-
platform: Platform.OS,
34-
options,
35-
};
3669

37-
try {
38-
const response = await fetch(ERROR_REPORTING_ENDPOINT, {
39-
method: "POST",
40-
headers: { "Content-Type": "application/json" },
41-
body: JSON.stringify(report),
42-
});
70+
export const safeReport = async (error: Error, options?: ReportOptions): Promise<void> => {
71+
// Prevent recursive error reporting
72+
if (isReportingError) {
73+
nestedReportCount++;
74+
console.log(`Already reporting an error, skipping to prevent recursion (depth: ${nestedReportCount})`);
4375

44-
if (!response.ok) {
45-
console.error(
46-
"Failed to send error report:",
47-
`Status: ${response.status}`,
48-
`StatusText: ${response.statusText}`,
49-
response,
50-
report
51-
);
76+
// If we detect too many nested error reports, something is wrong
77+
if (nestedReportCount >= MAX_NESTED_REPORTS) {
78+
console.log('Too many nested error reports - likely an infinite loop. Breaking the cycle.');
79+
return;
80+
}
81+
return;
82+
}
83+
// Check if circuit breaker is open
84+
if (circuitBreaker.shouldPreventApiCall()) {
85+
console.log('Error reporting circuit is open, skipping API call');
86+
return;
87+
}
88+
89+
// Build normalized report
90+
const report: ErrorReport = {
91+
name: error.name,
92+
message: error.message || String(error),
93+
stack: error.stack || undefined,
94+
cause: error.cause ? String(error.cause) : undefined,
95+
timestamp: new Date().toISOString(),
96+
platform: Platform.OS,
97+
options,
98+
};
99+
100+
try {
101+
isReportingError = true;
102+
const response = await fetch(ERROR_REPORTING_ENDPOINT, {
103+
method: 'POST',
104+
headers: { 'Content-Type': 'application/json' },
105+
body: JSON.stringify(report),
106+
});
107+
108+
if (!response.ok) {
109+
// Use console.log to avoid triggering global error handler
110+
console.log('Failed to send error report:', `Status: ${response.status}`, `StatusText: ${response.statusText}`, response, report);
111+
circuitBreaker.recordFailure();
112+
} else {
113+
circuitBreaker.recordSuccess();
114+
}
115+
} catch (e) {
116+
// Use console.log to avoid triggering global error handler
117+
console.log('Error sending error report (swallowed):', e);
118+
circuitBreaker.recordFailure();
119+
} finally {
120+
isReportingError = false;
121+
nestedReportCount = 0; // Reset the nested count when we're done
52122
}
53-
} catch (e) {
54-
console.error("Error sending error report (swallowed):", e);
55-
}
56123
};
57124

58125
// Original API for backward compatibility
59-
export const reportError = (
60-
maybeError: unknown,
61-
options?: ReportOptions
62-
): void => {
63-
const error =
64-
maybeError instanceof Error
65-
? maybeError
66-
: new Error(
67-
typeof maybeError === "string"
68-
? maybeError
69-
: JSON.stringify(maybeError) || "Unknown non-Error thrown"
70-
);
71-
// Fire-and-forget
72-
void safeReport(error, options);
126+
127+
export const reportError = (maybeError: unknown, options?: ReportOptions): void => {
128+
const error = maybeError instanceof Error ? maybeError : new Error(typeof maybeError === 'string' ? maybeError : JSON.stringify(maybeError) || 'Unknown non-Error thrown');
129+
// Fire-and-forget
130+
void safeReport(error, options);
73131
};

0 commit comments

Comments
 (0)