diff --git a/apps/web/pages/api/integrations/subscriptions/webhook.ts b/apps/web/pages/api/integrations/subscriptions/webhook.ts index c2c9f8c565..624f3488d9 100644 --- a/apps/web/pages/api/integrations/subscriptions/webhook.ts +++ b/apps/web/pages/api/integrations/subscriptions/webhook.ts @@ -4,8 +4,8 @@ import type Stripe from "stripe"; import stripe from "@calcom/features/ee/payments/server/stripe"; import { IS_PRODUCTION } from "@calcom/lib/constants"; -import { getErrorFromUnknown } from "@calcom/lib/errors"; import { HttpError as HttpCode } from "@calcom/lib/http-error"; +import { getServerErrorFromUnknown } from "@calcom/lib/server/getServerErrorFromUnknown"; import { prisma } from "@calcom/prisma"; export const config = { @@ -108,13 +108,13 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) }); } } catch (_err) { - const err = getErrorFromUnknown(_err); + const err = getServerErrorFromUnknown(_err); if (!err.message.includes("No credential found with subscription ID")) { console.error(`Webhook Error: ${err.message}`); } - res.status(err.statusCode ?? 500).send({ + res.status(err.statusCode).send({ message: err.message, - stack: IS_PRODUCTION ? undefined : err.stack, + stack: IS_PRODUCTION ? undefined : err.cause?.stack, }); return; } diff --git a/packages/app-store/alby/api/webhook.ts b/packages/app-store/alby/api/webhook.ts index 760b4c7315..a16f9af219 100644 --- a/packages/app-store/alby/api/webhook.ts +++ b/packages/app-store/alby/api/webhook.ts @@ -6,8 +6,8 @@ import { handlePaymentSuccess } from "@calcom/app-store/_utils/payments/handlePa import { albyCredentialKeysSchema } from "@calcom/app-store/alby/lib"; import parseInvoice from "@calcom/app-store/alby/lib/parseInvoice"; import { IS_PRODUCTION } from "@calcom/lib/constants"; -import { getErrorFromUnknown } from "@calcom/lib/errors"; import { HttpError as HttpCode } from "@calcom/lib/http-error"; +import { getServerErrorFromUnknown } from "@calcom/lib/server/getServerErrorFromUnknown"; import prisma from "@calcom/prisma"; export const config = { @@ -90,11 +90,11 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) return await handlePaymentSuccess(payment.id, payment.bookingId); } catch (_err) { - const err = getErrorFromUnknown(_err); + const err = getServerErrorFromUnknown(_err); console.error(`Webhook Error: ${err.message}`); - return res.status(err.statusCode || 500).send({ + return res.status(err.statusCode).send({ message: err.message, - stack: IS_PRODUCTION ? undefined : err.stack, + stack: IS_PRODUCTION ? undefined : err.cause?.stack, }); } } diff --git a/packages/app-store/btcpayserver/api/webhook.ts b/packages/app-store/btcpayserver/api/webhook.ts index 05465a1bb9..c95a43b536 100644 --- a/packages/app-store/btcpayserver/api/webhook.ts +++ b/packages/app-store/btcpayserver/api/webhook.ts @@ -5,8 +5,8 @@ import { z } from "zod"; import { handlePaymentSuccess } from "@calcom/app-store/_utils/payments/handlePaymentSuccess"; import { IS_PRODUCTION } from "@calcom/lib/constants"; -import { getErrorFromUnknown } from "@calcom/lib/errors"; import { HttpError as HttpCode } from "@calcom/lib/http-error"; +import { getServerErrorFromUnknown } from "@calcom/lib/server/getServerErrorFromUnknown"; import { PrismaBookingPaymentRepository as BookingPaymentRepository } from "@calcom/lib/server/repository/PrismaBookingPaymentRepository"; import appConfig from "../config.json"; @@ -90,11 +90,10 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) await handlePaymentSuccess(payment.id, payment.bookingId); return res.status(200).json({ success: true }); } catch (_err) { - const err = getErrorFromUnknown(_err); - const statusCode = err instanceof HttpCode ? err.statusCode : 500; - return res.status(statusCode).send({ + const err = getServerErrorFromUnknown(_err); + return res.status(err.statusCode).send({ message: err.message, - stack: IS_PRODUCTION ? undefined : err.stack, + stack: IS_PRODUCTION ? undefined : err.cause?.stack, }); } } diff --git a/packages/app-store/hitpay/api/webhook.ts b/packages/app-store/hitpay/api/webhook.ts index ccf818c8c3..c472801304 100644 --- a/packages/app-store/hitpay/api/webhook.ts +++ b/packages/app-store/hitpay/api/webhook.ts @@ -4,8 +4,8 @@ import type z from "zod"; import { handlePaymentSuccess } from "@calcom/app-store/_utils/payments/handlePaymentSuccess"; import { IS_PRODUCTION } from "@calcom/lib/constants"; -import { getErrorFromUnknown } from "@calcom/lib/errors"; import { HttpError as HttpCode } from "@calcom/lib/http-error"; +import { getServerErrorFromUnknown } from "@calcom/lib/server/getServerErrorFromUnknown"; import prisma from "@calcom/prisma"; import type { hitpayCredentialKeysSchema } from "../lib/hitpayCredentialKeysSchema"; @@ -111,11 +111,11 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) } return await handlePaymentSuccess(payment.id, payment.bookingId); } catch (_err) { - const err = getErrorFromUnknown(_err); + const err = getServerErrorFromUnknown(_err); console.error(`Webhook Error: ${err.message}`); return res.status(200).send({ message: err.message, - stack: IS_PRODUCTION ? undefined : err.stack, + stack: IS_PRODUCTION ? undefined : err.cause?.stack, }); } } diff --git a/packages/app-store/paypal/api/webhook.ts b/packages/app-store/paypal/api/webhook.ts index 796276d603..f049b87812 100644 --- a/packages/app-store/paypal/api/webhook.ts +++ b/packages/app-store/paypal/api/webhook.ts @@ -6,8 +6,8 @@ import { handlePaymentSuccess } from "@calcom/app-store/_utils/payments/handlePa import { paypalCredentialKeysSchema } from "@calcom/app-store/paypal/lib"; import Paypal from "@calcom/app-store/paypal/lib/Paypal"; import { IS_PRODUCTION } from "@calcom/lib/constants"; -import { getErrorFromUnknown } from "@calcom/lib/errors"; import { HttpError as HttpCode } from "@calcom/lib/http-error"; +import { getServerErrorFromUnknown } from "@calcom/lib/server/getServerErrorFromUnknown"; import prisma from "@calcom/prisma"; export const config = { @@ -92,11 +92,11 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) return await handlePaypalPaymentSuccess(parsedPayload, bodyAsString, parseHeaders.data); } } catch (_err) { - const err = getErrorFromUnknown(_err); + const err = getServerErrorFromUnknown(_err); console.error(`Webhook Error: ${err.message}`); res.status(200).send({ message: err.message, - stack: IS_PRODUCTION ? undefined : err.stack, + stack: IS_PRODUCTION ? undefined : err.cause?.stack, }); return; } diff --git a/packages/app-store/stripepayment/lib/PaymentService.ts b/packages/app-store/stripepayment/lib/PaymentService.ts index 8045d27fb9..cb1d1bc42b 100644 --- a/packages/app-store/stripepayment/lib/PaymentService.ts +++ b/packages/app-store/stripepayment/lib/PaymentService.ts @@ -5,9 +5,9 @@ import z from "zod"; import { sendAwaitingPaymentEmailAndSMS } from "@calcom/emails/email-manager"; import { BookingRepository } from "@calcom/features/bookings/repositories/BookingRepository"; import { ErrorCode } from "@calcom/lib/errorCodes"; -import { getErrorFromUnknown } from "@calcom/lib/errors"; import { ErrorWithCode } from "@calcom/lib/errors"; import logger from "@calcom/lib/logger"; +import { getServerErrorFromUnknown } from "@calcom/lib/server/getServerErrorFromUnknown"; import { safeStringify } from "@calcom/lib/safeStringify"; import prisma from "@calcom/prisma"; import type { Booking, Payment, PaymentOption, Prisma } from "@calcom/prisma/client"; @@ -368,7 +368,7 @@ export class PaymentService implements IAbstractPaymentService { }); return updatedPayment; } catch (e) { - const err = getErrorFromUnknown(e); + const err = getServerErrorFromUnknown(e); throw err; } } diff --git a/packages/app-store/vital/api/webhook.ts b/packages/app-store/vital/api/webhook.ts index 73599a104c..50ae8d7168 100644 --- a/packages/app-store/vital/api/webhook.ts +++ b/packages/app-store/vital/api/webhook.ts @@ -3,9 +3,9 @@ import queue from "queue"; import dayjs from "@calcom/dayjs"; import { IS_PRODUCTION } from "@calcom/lib/constants"; -import { getErrorFromUnknown } from "@calcom/lib/errors"; import { HttpError as HttpCode } from "@calcom/lib/http-error"; import logger from "@calcom/lib/logger"; +import { getServerErrorFromUnknown } from "@calcom/lib/server/getServerErrorFromUnknown"; import prisma from "@calcom/prisma"; import type { Prisma } from "@calcom/prisma/client"; import { BookingStatus } from "@calcom/prisma/enums"; @@ -100,7 +100,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) return; } - if (!event.data.hasOwnProperty(parameterFilter)) { + if (!Object.prototype.hasOwnProperty.call(event.data, parameterFilter)) { res.status(500).json({ message: "Selected param not available" }); return; } @@ -141,7 +141,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) ); } await q.start(); - } catch (error) { + } catch { throw new Error("Failed to reschedule bookings"); } } @@ -155,11 +155,11 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) } return res.status(200).json({ body: req.body }); } catch (_err) { - const err = getErrorFromUnknown(_err); + const err = getServerErrorFromUnknown(_err); console.error(`Webhook Error: ${err.message}`); - res.status(err.statusCode ?? 500).send({ + res.status(err.statusCode).send({ message: err.message, - stack: IS_PRODUCTION ? undefined : err.stack, + stack: IS_PRODUCTION ? undefined : err.cause?.stack, }); return; } diff --git a/packages/emails/templates/_base-email.ts b/packages/emails/templates/_base-email.ts index a9b192c777..703e9669bf 100644 --- a/packages/emails/templates/_base-email.ts +++ b/packages/emails/templates/_base-email.ts @@ -3,9 +3,9 @@ import { z } from "zod"; import dayjs from "@calcom/dayjs"; import { FeaturesRepository } from "@calcom/features/flags/features.repository"; -import { getErrorFromUnknown } from "@calcom/lib/errors"; import isSmsCalEmail from "@calcom/lib/isSmsCalEmail"; import { serverConfig } from "@calcom/lib/serverConfig"; +import { getServerErrorFromUnknown } from "@calcom/lib/server/getServerErrorFromUnknown"; import { setTestEmail } from "@calcom/lib/testEmails"; import { prisma } from "@calcom/prisma"; @@ -77,7 +77,7 @@ export default class BaseEmail { payloadWithUnEscapedSubject, (_err, info) => { if (_err) { - const err = getErrorFromUnknown(_err); + const err = getServerErrorFromUnknown(_err); this.printNodeMailerError(err); reject(err); } else { diff --git a/packages/features/bookings/lib/service/RegularBookingService.ts b/packages/features/bookings/lib/service/RegularBookingService.ts index 8b5fd15f44..36ccdebba6 100644 --- a/packages/features/bookings/lib/service/RegularBookingService.ts +++ b/packages/features/bookings/lib/service/RegularBookingService.ts @@ -61,7 +61,7 @@ import { groupHostsByGroupId } from "@calcom/lib/bookings/hostGroupUtils"; import { shouldIgnoreContactOwner } from "@calcom/lib/bookings/routing/utils"; import { DEFAULT_GROUP_ID } from "@calcom/lib/constants"; import { ErrorCode } from "@calcom/lib/errorCodes"; -import { getErrorFromUnknown, ErrorWithCode } from "@calcom/lib/errors"; +import { ErrorWithCode } from "@calcom/lib/errors"; import { extractBaseEmail } from "@calcom/lib/extract-base-email"; import getOrgIdFromMemberOrTeamId from "@calcom/lib/getOrgIdFromMemberOrTeamId"; import { getTeamIdFromEventType } from "@calcom/lib/getTeamIdFromEventType"; @@ -69,6 +69,7 @@ import { HttpError } from "@calcom/lib/http-error"; import { criticalLogger } from "@calcom/lib/logger.server"; import { getPiiFreeCalendarEvent, getPiiFreeEventType } from "@calcom/lib/piiFreeData"; import { safeStringify } from "@calcom/lib/safeStringify"; +import { getServerErrorFromUnknown } from "@calcom/lib/server/getServerErrorFromUnknown"; import { getTranslation } from "@calcom/lib/server/i18n"; import { getTimeFormatStringFromUserTimeFormat } from "@calcom/lib/timeFormat"; import { distributedTracing } from "@calcom/lib/tracing/factory"; @@ -1844,9 +1845,9 @@ async function handler( }; } } catch (_err) { - const err = getErrorFromUnknown(_err); + const err = getServerErrorFromUnknown(_err); tracingLogger.error(`Booking ${eventTypeId} failed`, "Error when saving booking to db", err.message); - if (err.code === "P2002") { + if (err.cause && typeof err.cause === "object" && "code" in err.cause && err.cause.code === "P2002") { throw new HttpError({ statusCode: 409, message: ErrorCode.BookingConflict, diff --git a/packages/features/ee/payments/api/webhook.ts b/packages/features/ee/payments/api/webhook.ts index 28b9d6e1cf..f3832de3b5 100644 --- a/packages/features/ee/payments/api/webhook.ts +++ b/packages/features/ee/payments/api/webhook.ts @@ -14,10 +14,10 @@ import stripe from "@calcom/features/ee/payments/server/stripe"; import { getPlatformParams } from "@calcom/features/platform-oauth-client/get-platform-params"; import { PlatformOAuthClientRepository } from "@calcom/features/platform-oauth-client/platform-oauth-client.repository"; import { IS_PRODUCTION } from "@calcom/lib/constants"; -import { getErrorFromUnknown } from "@calcom/lib/errors"; import { HttpError as HttpCode } from "@calcom/lib/http-error"; import logger from "@calcom/lib/logger"; import { safeStringify } from "@calcom/lib/safeStringify"; +import { getServerErrorFromUnknown } from "@calcom/lib/server/getServerErrorFromUnknown"; import { prisma } from "@calcom/prisma"; import type { Prisma } from "@calcom/prisma/client"; import { BookingStatus } from "@calcom/prisma/enums"; @@ -180,11 +180,11 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) }); } } catch (_err) { - const err = getErrorFromUnknown(_err); + const err = getServerErrorFromUnknown(_err); console.error(`Webhook Error: ${err.message}`); - res.status(err.statusCode ?? 500).send({ + res.status(err.statusCode).send({ message: err.message, - stack: IS_PRODUCTION ? undefined : err.stack, + stack: IS_PRODUCTION ? undefined : err.cause?.stack, }); return; } diff --git a/packages/lib/errors.ts b/packages/lib/errors.ts index a97dcbf618..e7575ffdea 100644 --- a/packages/lib/errors.ts +++ b/packages/lib/errors.ts @@ -13,17 +13,34 @@ export class ErrorWithCode extends Error { get(_, prop: string) { if (prop in ErrorCode) { const code = ErrorCode[prop as keyof typeof ErrorCode]; - return (message?: string, data?: Record) => new ErrorWithCode(code, message, data); + return (message?: string, data?: Record) => new ErrorWithCode(code, message, data); } throw new Error(`Unknown error code: ${prop}`); }, }) as unknown as Record< keyof typeof ErrorCode, - (message?: string, data?: Record) => ErrorWithCode + (message?: string, data?: Record) => ErrorWithCode >; } } +/** + * Converts unknown error types to Error objects. + * + * @deprecated For server-side code, use `getServerErrorFromUnknown` from `@calcom/lib/server/getServerErrorFromUnknown` instead. + * This function should only be used in client-side or isomorphic code (React components, shared utilities). + * + * Use this function when: + * - You're in a React component that runs on the client + * - You're in shared/isomorphic code that cannot import server-only dependencies + * - You only need a basic Error object without HTTP status code mapping + * + * For server-side error handling (API routes, tRPC handlers, webhooks), use `getServerErrorFromUnknown` + * which provides proper HTTP status code mapping, error redaction, and handles Zod/Prisma/Stripe errors. + * + * @param cause - The unknown error to convert + * @returns An Error object with optional statusCode and code properties + */ export function getErrorFromUnknown(cause: unknown): Error & { statusCode?: number; code?: string } { if (cause instanceof Error) { return cause; diff --git a/packages/lib/server/getServerErrorFromUnknown.ts b/packages/lib/server/getServerErrorFromUnknown.ts index dcbdae850b..32cdcebfa7 100644 --- a/packages/lib/server/getServerErrorFromUnknown.ts +++ b/packages/lib/server/getServerErrorFromUnknown.ts @@ -33,6 +33,12 @@ function parseZodErrorIssues(issues: ZodIssue[]): string { .join("; "); } +/** + * Converts unknown error types to HttpError with proper status code mapping and error redaction. + * SERVER-ONLY: This function imports Prisma and Stripe schemas and should only be used in server-side code. + * Use in API routes, tRPC handlers, webhooks, and server-side services. + * For client-side code, use getErrorFromUnknown from @calcom/lib/errors instead. + */ export function getServerErrorFromUnknown(cause: unknown): HttpError { if (isZodError(cause)) { return new HttpError({