From af5701a16cbfc09fab51655dfc8316b151912130 Mon Sep 17 00:00:00 2001 From: Nikos Polykandriotis Date: Mon, 16 Oct 2023 17:56:26 +0300 Subject: [PATCH] chore(backend,shared): Include clerkTraceId for backend api errors clerkTraceId is used when available and defaults to cloudflares CF Ray id when its missing. --- packages/backend/src/api/request.ts | 41 ++++++++++++++--------------- packages/shared/src/errors/Error.ts | 15 ++++++++--- 2 files changed, 32 insertions(+), 24 deletions(-) diff --git a/packages/backend/src/api/request.ts b/packages/backend/src/api/request.ts index fee69a3948..77d96799f0 100644 --- a/packages/backend/src/api/request.ts +++ b/packages/backend/src/api/request.ts @@ -1,3 +1,4 @@ +import { ClerkAPIResponseError } from '@clerk/shared'; import type { ClerkAPIError, ClerkAPIErrorJSON } from '@clerk/types'; import deepmerge from 'deepmerge'; import snakecaseKeys from 'snakecase-keys'; @@ -37,6 +38,7 @@ export type ClerkBackendApiResponse = | { data: null; errors: ClerkAPIError[]; + clerkTraceId?: string; }; export type RequestFunction = ReturnType; @@ -52,13 +54,14 @@ const withLegacyReturn = (cb: any): LegacyRequestFunction => async (...args) => { // @ts-ignore - const { data, errors, status, statusText } = await cb(...args); + const { data, errors, status, statusText, clerkTraceId } = await cb(...args); if (errors === null) { return data; } else { throw new ClerkAPIResponseError(statusText || '', { data: errors, status: status || '', + clerkTraceId, }); } }; @@ -161,6 +164,7 @@ export function buildRequest(options: CreateBackendApiOptions) { message: err.message || 'Unexpected error', }, ], + clerkTraceId: getTraceId(err, res?.headers), }; } @@ -171,6 +175,7 @@ export function buildRequest(options: CreateBackendApiOptions) { // @ts-expect-error status: res?.status, statusText: res?.statusText, + clerkTraceId: getTraceId(err, res?.headers), }; } }; @@ -178,6 +183,20 @@ export function buildRequest(options: CreateBackendApiOptions) { return withLegacyReturn(request); } +// Returns either clerk_trace_id if present in response json, otherwise defaults to CF-Ray header +function getTraceId(data: unknown, headers?: Headers): string | undefined { + let traceId: unknown = headers?.get('cf-ray'); + if (!!data && typeof data === 'object' && 'clerk_trace_id' in data) { + traceId = data.clerk_trace_id; + } + + if (typeof traceId !== 'string') { + return; + } + + return traceId; +} + function parseErrors(data: unknown): ClerkAPIError[] { if (!!data && typeof data === 'object' && 'errors' in data) { const errors = data.errors as ClerkAPIErrorJSON[]; @@ -197,23 +216,3 @@ function parseError(error: ClerkAPIErrorJSON): ClerkAPIError { }, }; } - -class ClerkAPIResponseError extends Error { - clerkError: true; - - status: number; - message: string; - - errors: ClerkAPIError[]; - - constructor(message: string, { data, status }: { data: ClerkAPIError[]; status: number }) { - super(message); - - Object.setPrototypeOf(this, ClerkAPIResponseError.prototype); - - this.clerkError = true; - this.message = message; - this.status = status; - this.errors = data; - } -} diff --git a/packages/shared/src/errors/Error.ts b/packages/shared/src/errors/Error.ts index 066dffa627..f6d8c34718 100644 --- a/packages/shared/src/errors/Error.ts +++ b/packages/shared/src/errors/Error.ts @@ -5,6 +5,7 @@ import { deprecated } from '../utils'; interface ClerkAPIResponseOptions { data: ClerkAPIErrorJSON[]; status: number; + clerkTraceId?: string; } // For a comprehensive Metamask error list, please see @@ -70,24 +71,32 @@ export class ClerkAPIResponseError extends Error { status: number; message: string; + clerkTraceId?: string; errors: ClerkAPIError[]; - constructor(message: string, { data, status }: ClerkAPIResponseOptions) { + constructor(message: string, { data, status, clerkTraceId }: ClerkAPIResponseOptions) { super(message); Object.setPrototypeOf(this, ClerkAPIResponseError.prototype); this.status = status; this.message = message; + this.clerkTraceId = clerkTraceId; this.clerkError = true; this.errors = parseErrors(data); } public toString = () => { - return `[${this.name}]\nMessage:${this.message}\nStatus:${this.status}\nSerialized errors: ${this.errors.map(e => - JSON.stringify(e), + let message = `[${this.name}]\nMessage:${this.message}\nStatus:${this.status}\nSerialized errors: ${this.errors.map( + e => JSON.stringify(e), )}`; + + if (this.clerkTraceId) { + message += `\nClerk trace id: ${this.clerkTraceId}`; + } + + return message; }; }