diff --git a/packages/stack-server/src/app/api/v1/auth/signup/route.tsx b/packages/stack-server/src/app/api/v1/auth/signup/route.tsx index 72d8db106..e9664afa9 100644 --- a/packages/stack-server/src/app/api/v1/auth/signup/route.tsx +++ b/packages/stack-server/src/app/api/v1/auth/signup/route.tsx @@ -10,7 +10,7 @@ import { getProject } from "@/lib/projects"; import { validateUrl } from "@/utils/url"; import { getPasswordError } from "@stackframe/stack-shared/dist/helpers/password"; import { getApiKeySet, publishableClientKeyHeaderSchema } from "@/lib/api-keys"; -import { StatusError } from "@stackframe/stack-shared/dist/utils/errors"; +import { StatusError, captureError } from "@stackframe/stack-shared/dist/utils/errors"; import { KnownErrors } from "@stackframe/stack-shared"; const postSchema = yup.object({ @@ -97,7 +97,7 @@ export const POST = deprecatedSmartRouteHandler(async (req: NextRequest) => { try { await sendVerificationEmail(projectId, newUser.projectUserId, emailVerificationRedirectUrl); } catch (error) { - console.error(error); + captureError("send-verification-email-on-sign-up", error); } return NextResponse.json({ diff --git a/packages/stack-shared/src/utils/promises.tsx b/packages/stack-shared/src/utils/promises.tsx index e75b640d1..26dd1e560 100644 --- a/packages/stack-shared/src/utils/promises.tsx +++ b/packages/stack-shared/src/utils/promises.tsx @@ -52,10 +52,11 @@ export function resolved(value: T): ReactPromise { /** * Like Promise.resolve(...), but also adds the status and value properties for use with React's `use` hook. */ -export function rejected(reason: unknown): ReactPromise { - return Object.assign(Promise.reject(reason), { +export function rejected(reason: unknown, options: { disableErrorWrapping?: boolean } = {}): ReactPromise { + const actualReason = options.disableErrorWrapping ? reason : createReactPromiseErrorWrapper(reason); + return Object.assign(Promise.reject(actualReason), { status: "rejected", - reason, + reason: actualReason, } as const); } @@ -63,23 +64,41 @@ export function neverResolve(): ReactPromise { return pending(new Promise(() => {})); } -export function pending(promise: Promise): ReactPromise { - const res = Object.assign(promise, { - status: "pending", - } as Pick, "status"> & { value: T, reason: unknown }); - res.then( +export function pending(promise: Promise, options: { disableErrorWrapping?: boolean } = {}): ReactPromise { + const res = promise.then( value => { res.status = "fulfilled"; - res.value = value; + (res as any).value = value; + return value; }, - reason => { + actualReason => { res.status = "rejected"; - res.reason = reason; + const reason = options.disableErrorWrapping ? actualReason : createReactPromiseErrorWrapper(actualReason); + (res as any).reason = reason; + throw reason; }, - ); + ) as ReactPromise; + res.status = "pending"; return res; } +function createReactPromiseErrorWrapper(error: unknown): Error { + return new ReactPromiseErrorWrapper(error); +} + +class ReactPromiseErrorWrapper extends StackAssertionError { + public readonly wrappedErrorMessage: string; + constructor(public readonly error: unknown) { + const wrappedErrorMessage = error instanceof ReactPromiseErrorWrapper ? error.wrappedErrorMessage : `${error}`; + super( + `Error occured while creating a ReactPromise: ${wrappedErrorMessage}\n\nSee the \`cause\` property for the original error. This error is a wrapper around the original error to preserve the stack trace (which would go lost when using Stack's pending(...) or rejected(...) functions).`, + { cause: error } + ); + this.wrappedErrorMessage = wrappedErrorMessage; + this.name = "ReactPromiseErrorWrapper"; + } +} + export async function wait(ms: number) { return await new Promise(resolve => setTimeout(resolve, ms)); } diff --git a/packages/stack/src/lib/stack-app.ts b/packages/stack/src/lib/stack-app.ts index a5dd39f5a..bb312a739 100644 --- a/packages/stack/src/lib/stack-app.ts +++ b/packages/stack/src/lib/stack-app.ts @@ -310,7 +310,7 @@ class _StackClientAppImpl this.getUser(), { ignoreErrors: true }); + runAsynchronously(this.getUser(), { ignoreErrors: true }); } if (options.currentProjectJson !== undefined) { this._currentProjectCache.forceSetCachedValue([], options.currentProjectJson);