mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
fix: resolve 5 Sentry production errors (668 total events)
- Filter known client-error noise (rate limit, empty body, unsupported media type, content-length mismatch, premature close) from Sentry via beforeSend to stop 644 events of non-actionable noise - Sanitize x-output-filename header to prevent TypeError on non-ASCII filenames in optimize-for-web preview (23 events) - Handle EPIPE on Python dispatcher stdin write with graceful fallback to per-request spawning instead of crashing (NODE-W) - Map EACCES on storage directory/file write to proper 503 status instead of generic 500 (NODE-P, 3 events)
This commit is contained in:
@@ -39,6 +39,16 @@ export async function initAnalytics(): Promise<void> {
|
|||||||
}
|
}
|
||||||
if (event.exception?.values) {
|
if (event.exception?.values) {
|
||||||
for (const ex of event.exception.values) {
|
for (const ex of event.exception.values) {
|
||||||
|
if (
|
||||||
|
ex.value &&
|
||||||
|
(ex.value.includes("Rate limit exceeded") ||
|
||||||
|
ex.value.includes("Body cannot be empty") ||
|
||||||
|
ex.value.includes("Unsupported Media Type") ||
|
||||||
|
ex.value.includes("Request body size did not match") ||
|
||||||
|
ex.value.includes("Premature close"))
|
||||||
|
) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
if (ex.value) {
|
if (ex.value) {
|
||||||
ex.value = ex.value
|
ex.value = ex.value
|
||||||
.replace(FILE_EXT_PATTERN, ".[REDACTED]")
|
.replace(FILE_EXT_PATTERN, ".[REDACTED]")
|
||||||
|
|||||||
@@ -56,7 +56,18 @@ let storageReady = false;
|
|||||||
|
|
||||||
export async function ensureStorageDir(): Promise<void> {
|
export async function ensureStorageDir(): Promise<void> {
|
||||||
if (storageReady) return;
|
if (storageReady) return;
|
||||||
|
try {
|
||||||
await mkdir(env.FILES_STORAGE_PATH, { recursive: true });
|
await mkdir(env.FILES_STORAGE_PATH, { recursive: true });
|
||||||
|
} catch (e) {
|
||||||
|
if (e instanceof Error && (e as NodeJS.ErrnoException).code === "EACCES") {
|
||||||
|
const err = new Error("Storage directory is not writable") as Error & {
|
||||||
|
statusCode: number;
|
||||||
|
};
|
||||||
|
err.statusCode = 503;
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
storageReady = true;
|
storageReady = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,7 +81,18 @@ export async function saveFile(buffer: Buffer, originalName: string): Promise<st
|
|||||||
ext = ".bin";
|
ext = ".bin";
|
||||||
}
|
}
|
||||||
const storedName = `${randomUUID()}${ext}`;
|
const storedName = `${randomUUID()}${ext}`;
|
||||||
|
try {
|
||||||
await writeFile(join(env.FILES_STORAGE_PATH, storedName), buffer);
|
await writeFile(join(env.FILES_STORAGE_PATH, storedName), buffer);
|
||||||
|
} catch (e) {
|
||||||
|
if (e instanceof Error && (e as NodeJS.ErrnoException).code === "EACCES") {
|
||||||
|
const err = new Error("Storage directory is not writable") as Error & {
|
||||||
|
statusCode: number;
|
||||||
|
};
|
||||||
|
err.statusCode = 503;
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
return storedName;
|
return storedName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -159,7 +159,8 @@ export function registerOptimizeForWeb(app: FastifyInstance) {
|
|||||||
reply.header("Content-Type", result.contentType);
|
reply.header("Content-Type", result.contentType);
|
||||||
reply.header("X-Original-Size", String(fileBuffer.length));
|
reply.header("X-Original-Size", String(fileBuffer.length));
|
||||||
reply.header("X-Processed-Size", String(result.buffer.length));
|
reply.header("X-Processed-Size", String(result.buffer.length));
|
||||||
reply.header("X-Output-Filename", encodeURIComponent(result.filename));
|
const safeFilename = encodeURIComponent(result.filename).replace(/[^ -~]/g, "");
|
||||||
|
reply.header("X-Output-Filename", safeFilename);
|
||||||
return reply.send(result.buffer);
|
return reply.send(result.buffer);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const message = err instanceof Error ? err.message : "Preview processing failed";
|
const message = err instanceof Error ? err.message : "Preview processing failed";
|
||||||
|
|||||||
@@ -153,6 +153,8 @@ function startDispatcher(): ChildProcess | null {
|
|||||||
env: buildMinimalEnv(),
|
env: buildMinimalEnv(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
child.stdin?.on("error", () => {});
|
||||||
|
|
||||||
let stderrBuffer = "";
|
let stderrBuffer = "";
|
||||||
|
|
||||||
child.stderr?.on("data", (chunk: Buffer) => {
|
child.stderr?.on("data", (chunk: Buffer) => {
|
||||||
@@ -328,7 +330,13 @@ function dispatcherRun(
|
|||||||
});
|
});
|
||||||
|
|
||||||
const request = JSON.stringify({ id, script: scriptName.replace(".py", ""), args });
|
const request = JSON.stringify({ id, script: scriptName.replace(".py", ""), args });
|
||||||
|
try {
|
||||||
proc.stdin!.write(request + "\n");
|
proc.stdin!.write(request + "\n");
|
||||||
|
} catch {
|
||||||
|
pendingRequests.delete(id);
|
||||||
|
clearTimeout(timer);
|
||||||
|
rejectPromise(new Error("Python dispatcher stdin closed unexpectedly"));
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -537,7 +545,10 @@ export function runPythonWithProgress(
|
|||||||
const dispatcherPromise = dispatcherRun(scriptName, args, options);
|
const dispatcherPromise = dispatcherRun(scriptName, args, options);
|
||||||
if (dispatcherPromise) {
|
if (dispatcherPromise) {
|
||||||
return dispatcherPromise.catch((err: Error) => {
|
return dispatcherPromise.catch((err: Error) => {
|
||||||
if (err.message === "Python dispatcher exited unexpectedly") {
|
if (
|
||||||
|
err.message === "Python dispatcher exited unexpectedly" ||
|
||||||
|
err.message === "Python dispatcher stdin closed unexpectedly"
|
||||||
|
) {
|
||||||
console.warn(
|
console.warn(
|
||||||
`[bridge] Dispatcher crashed during ${scriptName}, retrying with per-request process`,
|
`[bridge] Dispatcher crashed during ${scriptName}, retrying with per-request process`,
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user