Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore(repo): Move error helpers to shared package #1308

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .changeset/beige-tips-divide.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
'@clerk/nextjs': minor
'@clerk/shared': minor
'@clerk/clerk-react': minor
'@clerk/clerk-expo': minor
---

Export error helpers from the shared package to the framework specific packages
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,10 @@ exports[`public exports should not include a breaking change 1`] = `
"WithSession",
"WithUser",
"__internal__setErrorThrowerOptions",
"isClerkAPIResponseError",
"isKnownError",
"isMagicLinkError",
"isMetamaskError",
"useAuth",
"useClerk",
"useMagicLink",
Expand Down
101 changes: 12 additions & 89 deletions packages/clerk-js/src/core/resources/Error.ts
Original file line number Diff line number Diff line change
@@ -1,89 +1,12 @@
import type { ClerkAPIError, ClerkAPIErrorJSON } from '@clerk/types';

interface ClerkAPIResponseOptions {
data: ClerkAPIErrorJSON[];
status: number;
}

// For a comprehensive Metamask error list, please see
// https://docs.metamask.io/guide/ethereum-provider.html#errors
export interface MetamaskError extends Error {
code: 4001 | 32602 | 32603;
message: string;
data?: unknown;
}

export function isKnownError(error: any) {
return isClerkAPIResponseError(error) || isMetamaskError(error);
}

export function isClerkAPIResponseError(err: any): err is ClerkAPIResponseError {
return 'clerkError' in err;
}

export function isMetamaskError(err: any): err is MetamaskError {
return 'code' in err && [4001, 32602, 32603].includes(err.code) && 'message' in err;
}

export function parseErrors(data: ClerkAPIErrorJSON[] = []): ClerkAPIError[] {
return data.length > 0 ? data.map(parseError) : [];
}

export function parseError(error: ClerkAPIErrorJSON): ClerkAPIError {
return {
code: error.code,
message: error.message,
longMessage: error.long_message,
meta: {
paramName: error?.meta?.param_name,
sessionId: error?.meta?.session_id,
emailAddresses: error?.meta?.email_addresses,
},
};
}

export class ClerkAPIResponseError extends Error {
clerkError: true;

status: number;
message: string;

errors: ClerkAPIError[];

constructor(message: string, { data, status }: ClerkAPIResponseOptions) {
super(message);

Object.setPrototypeOf(this, ClerkAPIResponseError.prototype);

this.status = status;
this.message = message;
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),
)}`;
};
}

export class MagicLinkError extends Error {
code: string;

constructor(code: string) {
super(code);
this.code = code;
Object.setPrototypeOf(this, MagicLinkError.prototype);
}
}
// Check if the error is a MagicLinkError.

export function isMagicLinkError(err: Error): err is MagicLinkError {
return err instanceof MagicLinkError;
}

export const MagicLinkErrorCode = {
Expired: 'expired',
Failed: 'failed',
};
export {
isKnownError,
isMagicLinkError,
isMetamaskError,
isClerkAPIResponseError,
MagicLinkErrorCode,
parseError,
parseErrors,
MagicLinkError,
ClerkAPIResponseError,
} from '@clerk/shared';
export type { MetamaskError } from '@clerk/shared';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❓ Why not use them directly from the @clerk/shared and drop this file?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We export them twice so I thought it was better to have the logic here.

2 changes: 1 addition & 1 deletion packages/clerk-js/src/ui/common/EmailLinkVerify.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { isMagicLinkError, MagicLinkErrorCode } from '@clerk/shared';
import React from 'react';

import { isMagicLinkError, MagicLinkErrorCode } from '../../core/resources/Error';
import type { VerificationStatus } from '../../utils/getClerkQueryParam';
import { completeSignUpFlow } from '../components/SignUp/util';
import { useCoreClerk, useCoreSignUp } from '../contexts';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { ClerkAPIResponseError } from '../../../core/resources/Error';
import { ClerkAPIResponseError } from '@clerk/shared';

import { isVerificationExpiredError, VerificationErrorMessage, verificationErrorMessage } from '../verification';

describe('verification utils', () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { ClerkAPIResponseError } from '@clerk/shared';
import type { MembershipRole, OrganizationResource } from '@clerk/types';
import React from 'react';

import { ClerkAPIResponseError } from '../../../core/resources/Error';
import { Flex, Text } from '../../customizables';
import {
Alert,
Expand Down
3 changes: 3 additions & 0 deletions packages/expo/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@ export {
WithSession,
withUser,
WithUser,
isClerkAPIResponseError,
isMagicLinkError,
isMetamaskError,
isKnownError,
MagicLinkErrorCode,
} from '@clerk/clerk-react';

Expand Down
8 changes: 7 additions & 1 deletion packages/nextjs/src/client-boundary/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,10 @@ export {

export type { WithUserProp, WithSessionProp, WithClerkProp } from '@clerk/clerk-react';

export { isMagicLinkError, MagicLinkErrorCode } from '@clerk/clerk-react';
export {
isClerkAPIResponseError,
MagicLinkErrorCode,
isKnownError,
isMetamaskError,
isMagicLinkError,
} from '@clerk/clerk-react';
10 changes: 8 additions & 2 deletions packages/nextjs/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,6 @@ export {
useOrganizationList,
useOrganizations,
useMagicLink,
MagicLinkErrorCode,
isMagicLinkError,
withUser,
withSession,
withClerk,
Expand All @@ -59,6 +57,14 @@ export {

export type { WithUserProp, WithSessionProp, WithClerkProp } from './client-boundary/hooks';

export {
isClerkAPIResponseError,
isMagicLinkError,
isMetamaskError,
isKnownError,
MagicLinkErrorCode,
} from '@clerk/clerk-react';

/**
* Conditionally export components that exhibit different behavior
* when used in /app vs /pages.
Expand Down
21 changes: 8 additions & 13 deletions packages/react/src/errors.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
export {
isMagicLinkError,
MagicLinkErrorCode,
isClerkAPIResponseError,
isKnownError,
isMetamaskError,
} from '@clerk/shared';

export const noFrontendApiError = 'Clerk: You must add the frontendApi prop to your <ClerkProvider>';

export const noClerkProviderError = 'Clerk: You must wrap your application in a <ClerkProvider> component.';
Expand All @@ -16,19 +24,6 @@ export const hocChildrenNotAFunctionError = 'Clerk: Child of WithClerk must be a
export const multipleChildrenInButtonComponent = (name: string) =>
`Clerk: You've passed multiple children components to <${name}/>. You can only pass a single child component or text.`;

export const MagicLinkErrorCode = {
Expired: 'expired',
Failed: 'failed',
};

type MagicLinkError = {
code: 'expired' | 'failed';
};

export function isMagicLinkError(err: any): err is MagicLinkError {
return !!err && (err.code === MagicLinkErrorCode.Expired || err.code === MagicLinkErrorCode.Failed);
}

export const invalidStateError =
'Invalid state. Feel free to submit a bug or reach out to support here: https://clerk.com/support';

Expand Down
2 changes: 1 addition & 1 deletion packages/react/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,5 @@ export type {
WithSessionProp,
IsomorphicClerkOptions,
} from './types';
export { isMagicLinkError, MagicLinkErrorCode } from './errors';
export { MagicLinkErrorCode, isClerkAPIResponseError, isKnownError, isMetamaskError, isMagicLinkError } from './errors';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❓ Why not export them directly from the @clerk/shared instead of ./errors ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It just seemed cleaner to have all error related stuff in one place. Does the other way around seem better to you?

export { useMagicLink } from './hooks/useMagicLink';
89 changes: 89 additions & 0 deletions packages/shared/src/errors/Error.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import type { ClerkAPIError, ClerkAPIErrorJSON } from '@clerk/types';

interface ClerkAPIResponseOptions {
data: ClerkAPIErrorJSON[];
status: number;
}

// For a comprehensive Metamask error list, please see
// https://docs.metamask.io/guide/ethereum-provider.html#errors
export interface MetamaskError extends Error {
code: 4001 | 32602 | 32603;
message: string;
data?: unknown;
}

export function isKnownError(error: any) {
return isClerkAPIResponseError(error) || isMetamaskError(error);
}

export function isClerkAPIResponseError(err: any): err is ClerkAPIResponseError {
return 'clerkError' in err;
}

export function isMetamaskError(err: any): err is MetamaskError {
return 'code' in err && [4001, 32602, 32603].includes(err.code) && 'message' in err;
}

export function parseErrors(data: ClerkAPIErrorJSON[] = []): ClerkAPIError[] {
return data.length > 0 ? data.map(parseError) : [];
}

export function parseError(error: ClerkAPIErrorJSON): ClerkAPIError {
return {
code: error.code,
message: error.message,
longMessage: error.long_message,
meta: {
paramName: error?.meta?.param_name,
sessionId: error?.meta?.session_id,
emailAddresses: error?.meta?.email_addresses,
},
};
}

export class ClerkAPIResponseError extends Error {
clerkError: true;

status: number;
message: string;

errors: ClerkAPIError[];

constructor(message: string, { data, status }: ClerkAPIResponseOptions) {
super(message);

Object.setPrototypeOf(this, ClerkAPIResponseError.prototype);

this.status = status;
this.message = message;
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),
)}`;
};
}

export class MagicLinkError extends Error {
code: string;

constructor(code: string) {
super(code);
this.code = code;
Object.setPrototypeOf(this, MagicLinkError.prototype);
}
}
// Check if the error is a MagicLinkError.

export function isMagicLinkError(err: Error): err is MagicLinkError {
return err instanceof MagicLinkError;
}

export const MagicLinkErrorCode = {
Expired: 'expired',
Failed: 'failed',
};
2 changes: 2 additions & 0 deletions packages/shared/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
export { buildErrorThrower } from './errors/thrower';
export type { ErrorThrower, ErrorThrowerOptions } from './errors/thrower';

export * from './errors/Error';

/**
* Utils
*/
Expand Down