158 lines
4.0 KiB
TypeScript
158 lines
4.0 KiB
TypeScript
import { logger } from "../../logger";
|
|
import { ErrorType, ServerError, ServerResult } from "../types";
|
|
import { pbAdmin } from "../../pocketbase/client";
|
|
import { getRequest } from "@tanstack/react-start/server";
|
|
|
|
export const createServerError = (
|
|
type: ErrorType,
|
|
message: string,
|
|
userMessage: string,
|
|
statusCode?: number,
|
|
context?: Record<string, any>
|
|
): ServerError => ({
|
|
code: type,
|
|
message,
|
|
userMessage,
|
|
statusCode,
|
|
context,
|
|
});
|
|
|
|
export const toServerResult = async <T>(
|
|
serverFn: () => Promise<T>
|
|
): Promise<ServerResult<T>> => {
|
|
const startTime = Date.now();
|
|
|
|
try {
|
|
const data = await serverFn();
|
|
return { success: true, data };
|
|
} catch (error) {
|
|
if (error && typeof error === 'object' && 'options' in error) {
|
|
const redirectError = error as any;
|
|
if (redirectError.options?.to && redirectError.options?.statusCode) {
|
|
logger.info('toServerResult: Re-throwing TanStack Router redirect', redirectError.options);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
if (error instanceof Response && error.status === 440) {
|
|
logger.info('toServerResult: Re-throwing 440 Response for session refresh');
|
|
throw error;
|
|
}
|
|
|
|
const duration = Date.now() - startTime;
|
|
logger.error('Server Fn Error', error);
|
|
|
|
const mappedError = mapKnownError(error);
|
|
|
|
let fnName = 'unknown';
|
|
try {
|
|
const request = getRequest();
|
|
const url = new URL(request.url);
|
|
|
|
const functionId = url.searchParams.get('_serverFnId') || url.pathname;
|
|
|
|
if (functionId.includes('--')) {
|
|
const match = functionId.match(/--([^_]+)_/);
|
|
fnName = match?.[1] || functionId.split('--')[1]?.split('_')[0] || 'unknown';
|
|
} else {
|
|
fnName = serverFn.name || 'unknown';
|
|
}
|
|
} catch {
|
|
fnName = serverFn.name || 'unknown';
|
|
}
|
|
|
|
try {
|
|
await pbAdmin.authPromise;
|
|
await pbAdmin.createActivity({
|
|
name: fnName,
|
|
duration,
|
|
success: false,
|
|
error: mappedError.message,
|
|
arguments: {
|
|
errorType: mappedError.code,
|
|
statusCode: mappedError.statusCode,
|
|
userMessage: mappedError.userMessage,
|
|
},
|
|
});
|
|
} catch (activityError) {
|
|
}
|
|
|
|
return { success: false, error: mappedError };
|
|
}
|
|
};
|
|
|
|
const mapKnownError = (error: unknown): ServerError => {
|
|
if (error && typeof error === "object" && "status" in error) {
|
|
const pbError = error as {
|
|
status: number;
|
|
message: string;
|
|
data?: unknown;
|
|
};
|
|
|
|
switch (pbError.status) {
|
|
case 400:
|
|
return createServerError(
|
|
ErrorType.VALIDATION,
|
|
pbError.message,
|
|
"Invalid request",
|
|
400,
|
|
{ originalError: pbError.data }
|
|
);
|
|
case 401:
|
|
return createServerError(
|
|
ErrorType.UNAUTHORIZED,
|
|
pbError.message,
|
|
"You are not authorized to perform this action",
|
|
401
|
|
);
|
|
case 403:
|
|
return createServerError(
|
|
ErrorType.FORBIDDEN,
|
|
pbError.message,
|
|
"Access denied",
|
|
403
|
|
);
|
|
case 404:
|
|
return createServerError(
|
|
ErrorType.NOT_FOUND,
|
|
pbError.message,
|
|
"The requested resource was not found",
|
|
404
|
|
);
|
|
default:
|
|
return createServerError(
|
|
ErrorType.POCKETBASE,
|
|
pbError.message,
|
|
"Something went wrong. Please try again.",
|
|
pbError.status
|
|
);
|
|
}
|
|
}
|
|
|
|
if (error instanceof TypeError && error.message.includes("fetch")) {
|
|
return createServerError(
|
|
ErrorType.NETWORK,
|
|
error.message,
|
|
"Network error. Please check your connection and try again."
|
|
);
|
|
}
|
|
|
|
if (error instanceof Error) {
|
|
return createServerError(
|
|
ErrorType.UNKNOWN,
|
|
error.message,
|
|
"An unexpected error occurred. Please try again.",
|
|
undefined,
|
|
{ stack: error.stack }
|
|
);
|
|
}
|
|
|
|
return createServerError(
|
|
ErrorType.UNKNOWN,
|
|
String(error),
|
|
"An unexpected error occurred. Please try again.",
|
|
undefined,
|
|
{ originalError: error }
|
|
);
|
|
};
|