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
7 changes: 7 additions & 0 deletions .changeset/hungry-bottles-flow.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
---
'@clerk/nextjs': patch
'@clerk/clerk-react': patch
'@clerk/remix': patch
---

Export `EmailLinkErrorCode` from `/errors` module
5 changes: 5 additions & 0 deletions .changeset/light-buckets-check.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
---
'@clerk/remix': patch
---

Introduce `/errors` module. This path exports all error-related APIs such as `isClerkAPIResponseError`, `isEmailLinkError`, `isKnownError`, `isMetamaskError`, `EmailLinkErrorCode`
8 changes: 7 additions & 1 deletion packages/nextjs/src/client-boundary/hooks.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -13,4 +13,10 @@ export {
useUser,
} from '@clerk/clerk-react';

export { isClerkAPIResponseError, isEmailLinkError, isKnownError, isMetamaskError } from '@clerk/clerk-react/errors';
export {
isClerkAPIResponseError,
isEmailLinkError,
isKnownError,
isMetamaskError,
EmailLinkErrorCode,
} from '@clerk/clerk-react/errors';
8 changes: 7 additions & 1 deletion packages/nextjs/src/errors.ts
Original file line numberDiff line numberDiff line change
@@ -1 +1,7 @@
export { isClerkAPIResponseError, isEmailLinkError, isKnownError, isMetamaskError } from './client-boundary/hooks';
export {
isClerkAPIResponseError,
isEmailLinkError,
isKnownError,
isMetamaskError,
EmailLinkErrorCode,
} from './client-boundary/hooks';
8 changes: 7 additions & 1 deletion packages/react/src/errors.ts
Original file line numberDiff line numberDiff line change
@@ -1 +1,7 @@
export { isClerkAPIResponseError, isEmailLinkError, isKnownError, isMetamaskError } from '@clerk/shared/error';
export {
isClerkAPIResponseError,
isEmailLinkError,
isKnownError,
isMetamaskError,
EmailLinkErrorCode,
} from '@clerk/shared/error';
4 changes: 4 additions & 0 deletions packages/remix/api.server/package.json
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
{
"main": "../dist/api/index.js",
"types": "../dist/api/index.d.ts"
}
4 changes: 4 additions & 0 deletions packages/remix/errors/package.json
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
{
"main": "../dist/errors.js",
"types": "../dist/errors.d.ts"
}
4 changes: 4 additions & 0 deletions packages/remix/package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -36,6 +36,10 @@
"./api.server": {
"types": "./dist/api/index.d.ts",
"default": "./dist/api/index.js"
},
"./errors": {
"types": "./dist/errors.d.ts",
"default": "./dist/errors.js"
}
},
"main": "dist/index.js",
Expand Down
91 changes: 7 additions & 84 deletions packages/remix/src/errors.ts

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🙃 Wdyt about introducing a different structure to support subpath errors and error messages? Eg

// remix/src/errors/index.ts
export {
isClerkAPIResponseError,
isEmailLinkError,
isKnownError,
isMetamaskError,
EmailLinkErrorCode,
} from '@clerk/clerk-react/errors';
// remix/src/errors/messages.ts
const createErrorMessage = (msg: string) => {
// ...
}
// ... all the error messages that are being moved to utils/errors with this PR

I would prefer if we kept all the errors in the same place and avoid adding them to utils "bucket".

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

Definitely, but we cannot do this using a single errors.ts as this is the top-level entry file for the publicly available errors.

This PR changes the top-level API. I will open follow-up PR that aligns the strategy used in clerk/remix with the other packages as well.

Having a internalErrors could be enough, or we could always have a ./errors/ containing internal error utils and a ./errors.ts top level entry point for the public APIs

Original file line numberDiff line numberDiff line change
@@ -1,84 +1,7 @@
const createErrorMessage = (msg: string) => {
return `🔒 Clerk: ${msg.trim()}

For more info, check out the docs: https://clerk.com/docs,
or come say hi in our discord server: https://clerk.com/discord

`;
};

const ssrExample = `Use 'rootAuthLoader' as your root loader. Then, simply wrap the App component with ClerkApp and make it the default export.
Example:

import { ClerkApp } from '@clerk/remix';
import { rootAuthLoader } from '@clerk/remix/ssr.server';

export const loader: LoaderFunction = args => rootAuthLoader(args)

function App() {
return (
<html lang='en'>
...
</html>
);
}

export default ClerkApp(App, { publishableKey: '...' });
`;

export const invalidClerkStatePropError = createErrorMessage(`
You're trying to pass an invalid object in "<ClerkProvider clerkState={...}>".

${ssrExample}
`);

export const noClerkStateError = createErrorMessage(`
Looks like you didn't pass 'clerkState' to "<ClerkProvider clerkState={...}>".

${ssrExample}
`);

export const noLoaderArgsPassedInGetAuth = createErrorMessage(`
You're calling 'getAuth()' from a loader, without providing the loader args object.
Example:

export const loader: LoaderFunction = async (args) => {
const { sessionId } = await getAuth(args);
...
};
`);

export const invalidRootLoaderCallbackReturn = createErrorMessage(`
You're returning an invalid response from the 'rootAuthLoader' called from the loader in root.tsx.
You can only return plain objects, responses created using the Remix 'json()' and 'redirect()' helpers,
custom redirect 'Response' instances (status codes in the range of 300 to 400),
or custom json 'Response' instances (containing a body that is a valid json string).
If you want to return a primitive value or an array, you can always wrap the response with an object.

Example:

export const loader: LoaderFunction = args => rootAuthLoader(args, ({ auth }) => {
const { userId } = auth;
const posts: Post[] = database.getPostsByUserId(userId);

return json({ data: posts })
// or
return new Response(JSON.stringify({ data: posts }), { headers: { 'Content-Type': 'application/json' } });
// or
return { data: posts };
})
`);

export const noSecretKeyError = createErrorMessage(`
A secretKey must be provided in order to use SSR and the exports from @clerk/remix/api.');
If your runtime supports environment variables, you can add a CLERK_SECRET_KEY variable to your config.
Otherwise, you can pass a secretKey parameter to rootAuthLoader or getAuth.
`);

export const satelliteAndMissingProxyUrlAndDomain = createErrorMessage(
`Missing domain and proxyUrl. A satellite application needs to specify a domain or a proxyUrl`,
);

export const satelliteAndMissingSignInUrl = createErrorMessage(`
Invalid signInUrl. A satellite application requires a signInUrl for development instances.
Check if signInUrl is missing from your configuration or if it is not an absolute URL.`);
export {
isClerkAPIResponseError,
isEmailLinkError,
isKnownError,
isMetamaskError,
EmailLinkErrorCode,
} from '@clerk/clerk-react/errors';
2 changes: 1 addition & 1 deletion packages/remix/src/ssr/getAuth.ts
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
import { stripPrivateDataFromObject } from '@clerk/backend/internal';

import { noLoaderArgsPassedInGetAuth } from '../errors';
import { noLoaderArgsPassedInGetAuth } from '../utils/errors';
import { authenticateRequest } from './authenticateRequest';
import { loadOptions } from './loadOptions';
import type { GetAuthReturn, LoaderFunctionArgs, RootAuthLoaderOptions } from './types';
Expand Down
4 changes: 2 additions & 2 deletions packages/remix/src/ssr/loadOptions.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -5,8 +5,8 @@ import { isDevelopmentFromSecretKey } from '@clerk/shared/keys';
import { isHttpOrHttps, isProxyUrlRelative } from '@clerk/shared/proxy';
import { isTruthy } from '@clerk/shared/underscore';

import { noSecretKeyError, satelliteAndMissingProxyUrlAndDomain, satelliteAndMissingSignInUrl } from '../errors';
import { getEnvVariable } from '../utils';
import { noSecretKeyError, satelliteAndMissingProxyUrlAndDomain, satelliteAndMissingSignInUrl } from '../utils/errors';
import { getEnvVariable } from '../utils/utils';

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

😓 this seems kind of weird, that's why i suggested the errors restructuring.

import type { LoaderFunctionArgs, RootAuthLoaderOptions } from './types';

export const loadOptions = (args: LoaderFunctionArgs, overrides: RootAuthLoaderOptions = {}) => {
Expand Down
2 changes: 1 addition & 1 deletion packages/remix/src/ssr/rootAuthLoader.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -2,7 +2,7 @@ import { decorateObjectWithResources } from '@clerk/backend/internal';
import type { defer } from '@remix-run/server-runtime';
import { isDeferredData } from '@remix-run/server-runtime/dist/responses';

import { invalidRootLoaderCallbackReturn } from '../errors';
import { invalidRootLoaderCallbackReturn } from '../utils/errors';
import { authenticateRequest } from './authenticateRequest';
import { loadOptions } from './loadOptions';
import type { LoaderFunctionArgs, LoaderFunctionReturn, RootAuthLoaderCallback, RootAuthLoaderOptions } from './types';
Expand Down
2 changes: 1 addition & 1 deletion packages/remix/src/ssr/utils.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -5,7 +5,7 @@ import type { AppLoadContext, defer } from '@remix-run/server-runtime';
import { json } from '@remix-run/server-runtime';
import cookie from 'cookie';

import { getEnvVariable } from '../utils';
import { getEnvVariable } from '../utils/utils';

export function isResponse(value: any): value is Response {
return (
Expand Down
84 changes: 84 additions & 0 deletions packages/remix/src/utils/errors.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
const createErrorMessage = (msg: string) => {
return `🔒 Clerk: ${msg.trim()}

For more info, check out the docs: https://clerk.com/docs,
or come say hi in our discord server: https://clerk.com/discord

`;
};

const ssrExample = `Use 'rootAuthLoader' as your root loader. Then, simply wrap the App component with ClerkApp and make it the default export.
Example:

import { ClerkApp } from '@clerk/remix';
import { rootAuthLoader } from '@clerk/remix/ssr.server';

export const loader: LoaderFunction = args => rootAuthLoader(args)

function App() {
return (
<html lang='en'>
...
</html>
);
}

export default ClerkApp(App, { publishableKey: '...' });
`;

export const invalidClerkStatePropError = createErrorMessage(`
You're trying to pass an invalid object in "<ClerkProvider clerkState={...}>".

${ssrExample}
`);

export const noClerkStateError = createErrorMessage(`
Looks like you didn't pass 'clerkState' to "<ClerkProvider clerkState={...}>".

${ssrExample}
`);

export const noLoaderArgsPassedInGetAuth = createErrorMessage(`
You're calling 'getAuth()' from a loader, without providing the loader args object.
Example:

export const loader: LoaderFunction = async (args) => {
const { sessionId } = await getAuth(args);
...
};
`);

export const invalidRootLoaderCallbackReturn = createErrorMessage(`
You're returning an invalid response from the 'rootAuthLoader' called from the loader in root.tsx.
You can only return plain objects, responses created using the Remix 'json()' and 'redirect()' helpers,
custom redirect 'Response' instances (status codes in the range of 300 to 400),
or custom json 'Response' instances (containing a body that is a valid json string).
If you want to return a primitive value or an array, you can always wrap the response with an object.

Example:

export const loader: LoaderFunction = args => rootAuthLoader(args, ({ auth }) => {
const { userId } = auth;
const posts: Post[] = database.getPostsByUserId(userId);

return json({ data: posts })
// or
return new Response(JSON.stringify({ data: posts }), { headers: { 'Content-Type': 'application/json' } });
// or
return { data: posts };
})
`);

export const noSecretKeyError = createErrorMessage(`
A secretKey must be provided in order to use SSR and the exports from @clerk/remix/api.');
If your runtime supports environment variables, you can add a CLERK_SECRET_KEY variable to your config.
Otherwise, you can pass a secretKey parameter to rootAuthLoader or getAuth.
`);

export const satelliteAndMissingProxyUrlAndDomain = createErrorMessage(
`Missing domain and proxyUrl. A satellite application needs to specify a domain or a proxyUrl`,
);

export const satelliteAndMissingSignInUrl = createErrorMessage(`
Invalid signInUrl. A satellite application requires a signInUrl for development instances.
Check if signInUrl is missing from your configuration or if it is not an absolute URL.`);
1 change: 1 addition & 0 deletions packages/remix/src/utils/index.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
export * from './utils';
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
import type { AppLoadContext } from '@remix-run/server-runtime';

import type { ClerkState } from './client/types';
import type { ClerkState } from '../client/types';
import { invalidClerkStatePropError, noClerkStateError } from './errors';

export function warnForSsr(val: ClerkState | undefined) {
Expand Down
4 changes: 4 additions & 0 deletions packages/remix/ssr.server/package.json
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
{
"main": "../dist/ssr/index.js",
"types": "../dist/ssr/index.d.ts"
}
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content
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
7 changes: 7 additions & 0 deletions .changeset/hungry-bottles-flow.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
---
'@clerk/nextjs': patch
'@clerk/clerk-react': patch
'@clerk/remix': patch
---

Export `EmailLinkErrorCode` from `/errors` module
5 changes: 5 additions & 0 deletions .changeset/light-buckets-check.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
---
'@clerk/remix': patch
---

Introduce `/errors` module. This path exports all error-related APIs such as `isClerkAPIResponseError`, `isEmailLinkError`, `isKnownError`, `isMetamaskError`, `EmailLinkErrorCode`
8 changes: 7 additions & 1 deletion packages/nextjs/src/client-boundary/hooks.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -13,4 +13,10 @@ export {
useUser,
} from '@clerk/clerk-react';

export { isClerkAPIResponseError, isEmailLinkError, isKnownError, isMetamaskError } from '@clerk/clerk-react/errors';
export {
isClerkAPIResponseError,
isEmailLinkError,
isKnownError,
isMetamaskError,
EmailLinkErrorCode,
} from '@clerk/clerk-react/errors';
8 changes: 7 additions & 1 deletion packages/nextjs/src/errors.ts
Original file line numberDiff line numberDiff line change
@@ -1 +1,7 @@
export { isClerkAPIResponseError, isEmailLinkError, isKnownError, isMetamaskError } from './client-boundary/hooks';
export {
isClerkAPIResponseError,
isEmailLinkError,
isKnownError,
isMetamaskError,
EmailLinkErrorCode,
} from './client-boundary/hooks';
8 changes: 7 additions & 1 deletion packages/react/src/errors.ts
Original file line numberDiff line numberDiff line change
@@ -1 +1,7 @@
export { isClerkAPIResponseError, isEmailLinkError, isKnownError, isMetamaskError } from '@clerk/shared/error';
export {
isClerkAPIResponseError,
isEmailLinkError,
isKnownError,
isMetamaskError,
EmailLinkErrorCode,
} from '@clerk/shared/error';
4 changes: 4 additions & 0 deletions packages/remix/api.server/package.json
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
{
"main": "../dist/api/index.js",
"types": "../dist/api/index.d.ts"
}
4 changes: 4 additions & 0 deletions packages/remix/errors/package.json
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
{
"main": "../dist/errors.js",
"types": "../dist/errors.d.ts"
}
4 changes: 4 additions & 0 deletions packages/remix/package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -36,6 +36,10 @@
"./api.server": {
"types": "./dist/api/index.d.ts",
"default": "./dist/api/index.js"
},
"./errors": {
"types": "./dist/errors.d.ts",
"default": "./dist/errors.js"
}
},
"main": "dist/index.js",
Expand Down
91 changes: 7 additions & 84 deletions packages/remix/src/errors.ts

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🙃 Wdyt about introducing a different structure to support subpath errors and error messages? Eg

// remix/src/errors/index.ts
export {
isClerkAPIResponseError,
isEmailLinkError,
isKnownError,
isMetamaskError,
EmailLinkErrorCode,
} from '@clerk/clerk-react/errors';
// remix/src/errors/messages.ts
const createErrorMessage = (msg: string) => {
// ...
}
// ... all the error messages that are being moved to utils/errors with this PR

I would prefer if we kept all the errors in the same place and avoid adding them to utils "bucket".

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

Definitely, but we cannot do this using a single errors.ts as this is the top-level entry file for the publicly available errors.

This PR changes the top-level API. I will open follow-up PR that aligns the strategy used in clerk/remix with the other packages as well.

Having a internalErrors could be enough, or we could always have a ./errors/ containing internal error utils and a ./errors.ts top level entry point for the public APIs

Original file line numberDiff line numberDiff line change
@@ -1,84 +1,7 @@
const createErrorMessage = (msg: string) => {
return `🔒 Clerk: ${msg.trim()}

For more info, check out the docs: https://clerk.com/docs,
or come say hi in our discord server: https://clerk.com/discord

`;
};

const ssrExample = `Use 'rootAuthLoader' as your root loader. Then, simply wrap the App component with ClerkApp and make it the default export.
Example:

import { ClerkApp } from '@clerk/remix';
import { rootAuthLoader } from '@clerk/remix/ssr.server';

export const loader: LoaderFunction = args => rootAuthLoader(args)

function App() {
return (
<html lang='en'>
...
</html>
);
}

export default ClerkApp(App, { publishableKey: '...' });
`;

export const invalidClerkStatePropError = createErrorMessage(`
You're trying to pass an invalid object in "<ClerkProvider clerkState={...}>".

${ssrExample}
`);

export const noClerkStateError = createErrorMessage(`
Looks like you didn't pass 'clerkState' to "<ClerkProvider clerkState={...}>".

${ssrExample}
`);

export const noLoaderArgsPassedInGetAuth = createErrorMessage(`
You're calling 'getAuth()' from a loader, without providing the loader args object.
Example:

export const loader: LoaderFunction = async (args) => {
const { sessionId } = await getAuth(args);
...
};
`);

export const invalidRootLoaderCallbackReturn = createErrorMessage(`
You're returning an invalid response from the 'rootAuthLoader' called from the loader in root.tsx.
You can only return plain objects, responses created using the Remix 'json()' and 'redirect()' helpers,
custom redirect 'Response' instances (status codes in the range of 300 to 400),
or custom json 'Response' instances (containing a body that is a valid json string).
If you want to return a primitive value or an array, you can always wrap the response with an object.

Example:

export const loader: LoaderFunction = args => rootAuthLoader(args, ({ auth }) => {
const { userId } = auth;
const posts: Post[] = database.getPostsByUserId(userId);

return json({ data: posts })
// or
return new Response(JSON.stringify({ data: posts }), { headers: { 'Content-Type': 'application/json' } });
// or
return { data: posts };
})
`);

export const noSecretKeyError = createErrorMessage(`
A secretKey must be provided in order to use SSR and the exports from @clerk/remix/api.');
If your runtime supports environment variables, you can add a CLERK_SECRET_KEY variable to your config.
Otherwise, you can pass a secretKey parameter to rootAuthLoader or getAuth.
`);

export const satelliteAndMissingProxyUrlAndDomain = createErrorMessage(
`Missing domain and proxyUrl. A satellite application needs to specify a domain or a proxyUrl`,
);

export const satelliteAndMissingSignInUrl = createErrorMessage(`
Invalid signInUrl. A satellite application requires a signInUrl for development instances.
Check if signInUrl is missing from your configuration or if it is not an absolute URL.`);
export {
isClerkAPIResponseError,
isEmailLinkError,
isKnownError,
isMetamaskError,
EmailLinkErrorCode,
} from '@clerk/clerk-react/errors';
2 changes: 1 addition & 1 deletion packages/remix/src/ssr/getAuth.ts
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
import { stripPrivateDataFromObject } from '@clerk/backend/internal';

import { noLoaderArgsPassedInGetAuth } from '../errors';
import { noLoaderArgsPassedInGetAuth } from '../utils/errors';
import { authenticateRequest } from './authenticateRequest';
import { loadOptions } from './loadOptions';
import type { GetAuthReturn, LoaderFunctionArgs, RootAuthLoaderOptions } from './types';
Expand Down
4 changes: 2 additions & 2 deletions packages/remix/src/ssr/loadOptions.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -5,8 +5,8 @@ import { isDevelopmentFromSecretKey } from '@clerk/shared/keys';
import { isHttpOrHttps, isProxyUrlRelative } from '@clerk/shared/proxy';
import { isTruthy } from '@clerk/shared/underscore';

import { noSecretKeyError, satelliteAndMissingProxyUrlAndDomain, satelliteAndMissingSignInUrl } from '../errors';
import { getEnvVariable } from '../utils';
import { noSecretKeyError, satelliteAndMissingProxyUrlAndDomain, satelliteAndMissingSignInUrl } from '../utils/errors';
import { getEnvVariable } from '../utils/utils';

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

😓 this seems kind of weird, that's why i suggested the errors restructuring.

import type { LoaderFunctionArgs, RootAuthLoaderOptions } from './types';

export const loadOptions = (args: LoaderFunctionArgs, overrides: RootAuthLoaderOptions = {}) => {
Expand Down
2 changes: 1 addition & 1 deletion packages/remix/src/ssr/rootAuthLoader.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -2,7 +2,7 @@ import { decorateObjectWithResources } from '@clerk/backend/internal';
import type { defer } from '@remix-run/server-runtime';
import { isDeferredData } from '@remix-run/server-runtime/dist/responses';

import { invalidRootLoaderCallbackReturn } from '../errors';
import { invalidRootLoaderCallbackReturn } from '../utils/errors';
import { authenticateRequest } from './authenticateRequest';
import { loadOptions } from './loadOptions';
import type { LoaderFunctionArgs, LoaderFunctionReturn, RootAuthLoaderCallback, RootAuthLoaderOptions } from './types';
Expand Down
2 changes: 1 addition & 1 deletion packages/remix/src/ssr/utils.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -5,7 +5,7 @@ import type { AppLoadContext, defer } from '@remix-run/server-runtime';
import { json } from '@remix-run/server-runtime';
import cookie from 'cookie';

import { getEnvVariable } from '../utils';
import { getEnvVariable } from '../utils/utils';

export function isResponse(value: any): value is Response {
return (
Expand Down
84 changes: 84 additions & 0 deletions packages/remix/src/utils/errors.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
const createErrorMessage = (msg: string) => {
return `🔒 Clerk: ${msg.trim()}

For more info, check out the docs: https://clerk.com/docs,
or come say hi in our discord server: https://clerk.com/discord

`;
};

const ssrExample = `Use 'rootAuthLoader' as your root loader. Then, simply wrap the App component with ClerkApp and make it the default export.
Example:

import { ClerkApp } from '@clerk/remix';
import { rootAuthLoader } from '@clerk/remix/ssr.server';

export const loader: LoaderFunction = args => rootAuthLoader(args)

function App() {
return (
<html lang='en'>
...
</html>
);
}

export default ClerkApp(App, { publishableKey: '...' });
`;

export const invalidClerkStatePropError = createErrorMessage(`
You're trying to pass an invalid object in "<ClerkProvider clerkState={...}>".

${ssrExample}
`);

export const noClerkStateError = createErrorMessage(`
Looks like you didn't pass 'clerkState' to "<ClerkProvider clerkState={...}>".

${ssrExample}
`);

export const noLoaderArgsPassedInGetAuth = createErrorMessage(`
You're calling 'getAuth()' from a loader, without providing the loader args object.
Example:

export const loader: LoaderFunction = async (args) => {
const { sessionId } = await getAuth(args);
...
};
`);

export const invalidRootLoaderCallbackReturn = createErrorMessage(`
You're returning an invalid response from the 'rootAuthLoader' called from the loader in root.tsx.
You can only return plain objects, responses created using the Remix 'json()' and 'redirect()' helpers,
custom redirect 'Response' instances (status codes in the range of 300 to 400),
or custom json 'Response' instances (containing a body that is a valid json string).
If you want to return a primitive value or an array, you can always wrap the response with an object.

Example:

export const loader: LoaderFunction = args => rootAuthLoader(args, ({ auth }) => {
const { userId } = auth;
const posts: Post[] = database.getPostsByUserId(userId);

return json({ data: posts })
// or
return new Response(JSON.stringify({ data: posts }), { headers: { 'Content-Type': 'application/json' } });
// or
return { data: posts };
})
`);

export const noSecretKeyError = createErrorMessage(`
A secretKey must be provided in order to use SSR and the exports from @clerk/remix/api.');
If your runtime supports environment variables, you can add a CLERK_SECRET_KEY variable to your config.
Otherwise, you can pass a secretKey parameter to rootAuthLoader or getAuth.
`);

export const satelliteAndMissingProxyUrlAndDomain = createErrorMessage(
`Missing domain and proxyUrl. A satellite application needs to specify a domain or a proxyUrl`,
);

export const satelliteAndMissingSignInUrl = createErrorMessage(`
Invalid signInUrl. A satellite application requires a signInUrl for development instances.
Check if signInUrl is missing from your configuration or if it is not an absolute URL.`);
1 change: 1 addition & 0 deletions packages/remix/src/utils/index.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
export * from './utils';
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
import type { AppLoadContext } from '@remix-run/server-runtime';

import type { ClerkState } from './client/types';
import type { ClerkState } from '../client/types';
import { invalidClerkStatePropError, noClerkStateError } from './errors';

export function warnForSsr(val: ClerkState | undefined) {
Expand Down
4 changes: 4 additions & 0 deletions packages/remix/ssr.server/package.json
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
{
"main": "../dist/ssr/index.js",
"types": "../dist/ssr/index.d.ts"
}
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
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
7 changes: 7 additions & 0 deletions .changeset/hungry-bottles-flow.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
---
'@clerk/nextjs': patch
'@clerk/clerk-react': patch
'@clerk/remix': patch
---

Export `EmailLinkErrorCode` from `/errors` module
5 changes: 5 additions & 0 deletions .changeset/light-buckets-check.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
---
'@clerk/remix': patch
---

Introduce `/errors` module. This path exports all error-related APIs such as `isClerkAPIResponseError`, `isEmailLinkError`, `isKnownError`, `isMetamaskError`, `EmailLinkErrorCode`
8 changes: 7 additions & 1 deletion packages/nextjs/src/client-boundary/hooks.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -13,4 +13,10 @@ export {
useUser,
} from '@clerk/clerk-react';

export { isClerkAPIResponseError, isEmailLinkError, isKnownError, isMetamaskError } from '@clerk/clerk-react/errors';
export {
isClerkAPIResponseError,
isEmailLinkError,
isKnownError,
isMetamaskError,
EmailLinkErrorCode,
} from '@clerk/clerk-react/errors';
8 changes: 7 additions & 1 deletion packages/nextjs/src/errors.ts
Original file line numberDiff line numberDiff line change
@@ -1 +1,7 @@
export { isClerkAPIResponseError, isEmailLinkError, isKnownError, isMetamaskError } from './client-boundary/hooks';
export {
isClerkAPIResponseError,
isEmailLinkError,
isKnownError,
isMetamaskError,
EmailLinkErrorCode,
} from './client-boundary/hooks';
8 changes: 7 additions & 1 deletion packages/react/src/errors.ts
Original file line numberDiff line numberDiff line change
@@ -1 +1,7 @@
export { isClerkAPIResponseError, isEmailLinkError, isKnownError, isMetamaskError } from '@clerk/shared/error';
export {
isClerkAPIResponseError,
isEmailLinkError,
isKnownError,
isMetamaskError,
EmailLinkErrorCode,
} from '@clerk/shared/error';
4 changes: 4 additions & 0 deletions packages/remix/api.server/package.json
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
{
"main": "../dist/api/index.js",
"types": "../dist/api/index.d.ts"
}
4 changes: 4 additions & 0 deletions packages/remix/errors/package.json
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
{
"main": "../dist/errors.js",
"types": "../dist/errors.d.ts"
}
4 changes: 4 additions & 0 deletions packages/remix/package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -36,6 +36,10 @@
"./api.server": {
"types": "./dist/api/index.d.ts",
"default": "./dist/api/index.js"
},
"./errors": {
"types": "./dist/errors.d.ts",
"default": "./dist/errors.js"
}
},
"main": "dist/index.js",
Expand Down
91 changes: 7 additions & 84 deletions packages/remix/src/errors.ts

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🙃 Wdyt about introducing a different structure to support subpath errors and error messages? Eg

// remix/src/errors/index.ts
export {
isClerkAPIResponseError,
isEmailLinkError,
isKnownError,
isMetamaskError,
EmailLinkErrorCode,
} from '@clerk/clerk-react/errors';
// remix/src/errors/messages.ts
const createErrorMessage = (msg: string) => {
// ...
}
// ... all the error messages that are being moved to utils/errors with this PR

I would prefer if we kept all the errors in the same place and avoid adding them to utils "bucket".

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

Definitely, but we cannot do this using a single errors.ts as this is the top-level entry file for the publicly available errors.

This PR changes the top-level API. I will open follow-up PR that aligns the strategy used in clerk/remix with the other packages as well.

Having a internalErrors could be enough, or we could always have a ./errors/ containing internal error utils and a ./errors.ts top level entry point for the public APIs

Original file line numberDiff line numberDiff line change
@@ -1,84 +1,7 @@
const createErrorMessage = (msg: string) => {
return `🔒 Clerk: ${msg.trim()}

For more info, check out the docs: https://clerk.com/docs,
or come say hi in our discord server: https://clerk.com/discord

`;
};

const ssrExample = `Use 'rootAuthLoader' as your root loader. Then, simply wrap the App component with ClerkApp and make it the default export.
Example:

import { ClerkApp } from '@clerk/remix';
import { rootAuthLoader } from '@clerk/remix/ssr.server';

export const loader: LoaderFunction = args => rootAuthLoader(args)

function App() {
return (
<html lang='en'>
...
</html>
);
}

export default ClerkApp(App, { publishableKey: '...' });
`;

export const invalidClerkStatePropError = createErrorMessage(`
You're trying to pass an invalid object in "<ClerkProvider clerkState={...}>".

${ssrExample}
`);

export const noClerkStateError = createErrorMessage(`
Looks like you didn't pass 'clerkState' to "<ClerkProvider clerkState={...}>".

${ssrExample}
`);

export const noLoaderArgsPassedInGetAuth = createErrorMessage(`
You're calling 'getAuth()' from a loader, without providing the loader args object.
Example:

export const loader: LoaderFunction = async (args) => {
const { sessionId } = await getAuth(args);
...
};
`);

export const invalidRootLoaderCallbackReturn = createErrorMessage(`
You're returning an invalid response from the 'rootAuthLoader' called from the loader in root.tsx.
You can only return plain objects, responses created using the Remix 'json()' and 'redirect()' helpers,
custom redirect 'Response' instances (status codes in the range of 300 to 400),
or custom json 'Response' instances (containing a body that is a valid json string).
If you want to return a primitive value or an array, you can always wrap the response with an object.

Example:

export const loader: LoaderFunction = args => rootAuthLoader(args, ({ auth }) => {
const { userId } = auth;
const posts: Post[] = database.getPostsByUserId(userId);

return json({ data: posts })
// or
return new Response(JSON.stringify({ data: posts }), { headers: { 'Content-Type': 'application/json' } });
// or
return { data: posts };
})
`);

export const noSecretKeyError = createErrorMessage(`
A secretKey must be provided in order to use SSR and the exports from @clerk/remix/api.');
If your runtime supports environment variables, you can add a CLERK_SECRET_KEY variable to your config.
Otherwise, you can pass a secretKey parameter to rootAuthLoader or getAuth.
`);

export const satelliteAndMissingProxyUrlAndDomain = createErrorMessage(
`Missing domain and proxyUrl. A satellite application needs to specify a domain or a proxyUrl`,
);

export const satelliteAndMissingSignInUrl = createErrorMessage(`
Invalid signInUrl. A satellite application requires a signInUrl for development instances.
Check if signInUrl is missing from your configuration or if it is not an absolute URL.`);
export {
isClerkAPIResponseError,
isEmailLinkError,
isKnownError,
isMetamaskError,
EmailLinkErrorCode,
} from '@clerk/clerk-react/errors';
2 changes: 1 addition & 1 deletion packages/remix/src/ssr/getAuth.ts
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
import { stripPrivateDataFromObject } from '@clerk/backend/internal';

import { noLoaderArgsPassedInGetAuth } from '../errors';
import { noLoaderArgsPassedInGetAuth } from '../utils/errors';
import { authenticateRequest } from './authenticateRequest';
import { loadOptions } from './loadOptions';
import type { GetAuthReturn, LoaderFunctionArgs, RootAuthLoaderOptions } from './types';
Expand Down
4 changes: 2 additions & 2 deletions packages/remix/src/ssr/loadOptions.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -5,8 +5,8 @@ import { isDevelopmentFromSecretKey } from '@clerk/shared/keys';
import { isHttpOrHttps, isProxyUrlRelative } from '@clerk/shared/proxy';
import { isTruthy } from '@clerk/shared/underscore';

import { noSecretKeyError, satelliteAndMissingProxyUrlAndDomain, satelliteAndMissingSignInUrl } from '../errors';
import { getEnvVariable } from '../utils';
import { noSecretKeyError, satelliteAndMissingProxyUrlAndDomain, satelliteAndMissingSignInUrl } from '../utils/errors';
import { getEnvVariable } from '../utils/utils';

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

😓 this seems kind of weird, that's why i suggested the errors restructuring.

import type { LoaderFunctionArgs, RootAuthLoaderOptions } from './types';

export const loadOptions = (args: LoaderFunctionArgs, overrides: RootAuthLoaderOptions = {}) => {
Expand Down
2 changes: 1 addition & 1 deletion packages/remix/src/ssr/rootAuthLoader.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -2,7 +2,7 @@ import { decorateObjectWithResources } from '@clerk/backend/internal';
import type { defer } from '@remix-run/server-runtime';
import { isDeferredData } from '@remix-run/server-runtime/dist/responses';

import { invalidRootLoaderCallbackReturn } from '../errors';
import { invalidRootLoaderCallbackReturn } from '../utils/errors';
import { authenticateRequest } from './authenticateRequest';
import { loadOptions } from './loadOptions';
import type { LoaderFunctionArgs, LoaderFunctionReturn, RootAuthLoaderCallback, RootAuthLoaderOptions } from './types';
Expand Down
2 changes: 1 addition & 1 deletion packages/remix/src/ssr/utils.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -5,7 +5,7 @@ import type { AppLoadContext, defer } from '@remix-run/server-runtime';
import { json } from '@remix-run/server-runtime';
import cookie from 'cookie';

import { getEnvVariable } from '../utils';
import { getEnvVariable } from '../utils/utils';

export function isResponse(value: any): value is Response {
return (
Expand Down
84 changes: 84 additions & 0 deletions packages/remix/src/utils/errors.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
const createErrorMessage = (msg: string) => {
return `🔒 Clerk: ${msg.trim()}

For more info, check out the docs: https://clerk.com/docs,
or come say hi in our discord server: https://clerk.com/discord

`;
};

const ssrExample = `Use 'rootAuthLoader' as your root loader. Then, simply wrap the App component with ClerkApp and make it the default export.
Example:

import { ClerkApp } from '@clerk/remix';
import { rootAuthLoader } from '@clerk/remix/ssr.server';

export const loader: LoaderFunction = args => rootAuthLoader(args)

function App() {
return (
<html lang='en'>
...
</html>
);
}

export default ClerkApp(App, { publishableKey: '...' });
`;

export const invalidClerkStatePropError = createErrorMessage(`
You're trying to pass an invalid object in "<ClerkProvider clerkState={...}>".

${ssrExample}
`);

export const noClerkStateError = createErrorMessage(`
Looks like you didn't pass 'clerkState' to "<ClerkProvider clerkState={...}>".

${ssrExample}
`);

export const noLoaderArgsPassedInGetAuth = createErrorMessage(`
You're calling 'getAuth()' from a loader, without providing the loader args object.
Example:

export const loader: LoaderFunction = async (args) => {
const { sessionId } = await getAuth(args);
...
};
`);

export const invalidRootLoaderCallbackReturn = createErrorMessage(`
You're returning an invalid response from the 'rootAuthLoader' called from the loader in root.tsx.
You can only return plain objects, responses created using the Remix 'json()' and 'redirect()' helpers,
custom redirect 'Response' instances (status codes in the range of 300 to 400),
or custom json 'Response' instances (containing a body that is a valid json string).
If you want to return a primitive value or an array, you can always wrap the response with an object.

Example:

export const loader: LoaderFunction = args => rootAuthLoader(args, ({ auth }) => {
const { userId } = auth;
const posts: Post[] = database.getPostsByUserId(userId);

return json({ data: posts })
// or
return new Response(JSON.stringify({ data: posts }), { headers: { 'Content-Type': 'application/json' } });
// or
return { data: posts };
})
`);

export const noSecretKeyError = createErrorMessage(`
A secretKey must be provided in order to use SSR and the exports from @clerk/remix/api.');
If your runtime supports environment variables, you can add a CLERK_SECRET_KEY variable to your config.
Otherwise, you can pass a secretKey parameter to rootAuthLoader or getAuth.
`);

export const satelliteAndMissingProxyUrlAndDomain = createErrorMessage(
`Missing domain and proxyUrl. A satellite application needs to specify a domain or a proxyUrl`,
);

export const satelliteAndMissingSignInUrl = createErrorMessage(`
Invalid signInUrl. A satellite application requires a signInUrl for development instances.
Check if signInUrl is missing from your configuration or if it is not an absolute URL.`);
1 change: 1 addition & 0 deletions packages/remix/src/utils/index.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
export * from './utils';
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
import type { AppLoadContext } from '@remix-run/server-runtime';

import type { ClerkState } from './client/types';
import type { ClerkState } from '../client/types';
import { invalidClerkStatePropError, noClerkStateError } from './errors';

export function warnForSsr(val: ClerkState | undefined) {
Expand Down
4 changes: 4 additions & 0 deletions packages/remix/ssr.server/package.json
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
{
"main": "../dist/ssr/index.js",
"types": "../dist/ssr/index.d.ts"
}
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
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
7 changes: 7 additions & 0 deletions .changeset/hungry-bottles-flow.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
---
'@clerk/nextjs': patch
'@clerk/clerk-react': patch
'@clerk/remix': patch
---

Export `EmailLinkErrorCode` from `/errors` module
5 changes: 5 additions & 0 deletions .changeset/light-buckets-check.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
---
'@clerk/remix': patch
---

Introduce `/errors` module. This path exports all error-related APIs such as `isClerkAPIResponseError`, `isEmailLinkError`, `isKnownError`, `isMetamaskError`, `EmailLinkErrorCode`
8 changes: 7 additions & 1 deletion packages/nextjs/src/client-boundary/hooks.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -13,4 +13,10 @@ export {
useUser,
} from '@clerk/clerk-react';

export { isClerkAPIResponseError, isEmailLinkError, isKnownError, isMetamaskError } from '@clerk/clerk-react/errors';
export {
isClerkAPIResponseError,
isEmailLinkError,
isKnownError,
isMetamaskError,
EmailLinkErrorCode,
} from '@clerk/clerk-react/errors';
8 changes: 7 additions & 1 deletion packages/nextjs/src/errors.ts
Original file line numberDiff line numberDiff line change
@@ -1 +1,7 @@
export { isClerkAPIResponseError, isEmailLinkError, isKnownError, isMetamaskError } from './client-boundary/hooks';
export {
isClerkAPIResponseError,
isEmailLinkError,
isKnownError,
isMetamaskError,
EmailLinkErrorCode,
} from './client-boundary/hooks';
8 changes: 7 additions & 1 deletion packages/react/src/errors.ts
Original file line numberDiff line numberDiff line change
@@ -1 +1,7 @@
export { isClerkAPIResponseError, isEmailLinkError, isKnownError, isMetamaskError } from '@clerk/shared/error';
export {
isClerkAPIResponseError,
isEmailLinkError,
isKnownError,
isMetamaskError,
EmailLinkErrorCode,
} from '@clerk/shared/error';
4 changes: 4 additions & 0 deletions packages/remix/api.server/package.json
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
{
"main": "../dist/api/index.js",
"types": "../dist/api/index.d.ts"
}
4 changes: 4 additions & 0 deletions packages/remix/errors/package.json
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
{
"main": "../dist/errors.js",
"types": "../dist/errors.d.ts"
}
4 changes: 4 additions & 0 deletions packages/remix/package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -36,6 +36,10 @@
"./api.server": {
"types": "./dist/api/index.d.ts",
"default": "./dist/api/index.js"
},
"./errors": {
"types": "./dist/errors.d.ts",
"default": "./dist/errors.js"
}
},
"main": "dist/index.js",
Expand Down
91 changes: 7 additions & 84 deletions packages/remix/src/errors.ts

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🙃 Wdyt about introducing a different structure to support subpath errors and error messages? Eg

// remix/src/errors/index.ts
export {
isClerkAPIResponseError,
isEmailLinkError,
isKnownError,
isMetamaskError,
EmailLinkErrorCode,
} from '@clerk/clerk-react/errors';
// remix/src/errors/messages.ts
const createErrorMessage = (msg: string) => {
// ...
}
// ... all the error messages that are being moved to utils/errors with this PR

I would prefer if we kept all the errors in the same place and avoid adding them to utils "bucket".

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

Definitely, but we cannot do this using a single errors.ts as this is the top-level entry file for the publicly available errors.

This PR changes the top-level API. I will open follow-up PR that aligns the strategy used in clerk/remix with the other packages as well.

Having a internalErrors could be enough, or we could always have a ./errors/ containing internal error utils and a ./errors.ts top level entry point for the public APIs

Original file line numberDiff line numberDiff line change
@@ -1,84 +1,7 @@
const createErrorMessage = (msg: string) => {
return `🔒 Clerk: ${msg.trim()}

For more info, check out the docs: https://clerk.com/docs,
or come say hi in our discord server: https://clerk.com/discord

`;
};

const ssrExample = `Use 'rootAuthLoader' as your root loader. Then, simply wrap the App component with ClerkApp and make it the default export.
Example:

import { ClerkApp } from '@clerk/remix';
import { rootAuthLoader } from '@clerk/remix/ssr.server';

export const loader: LoaderFunction = args => rootAuthLoader(args)

function App() {
return (
<html lang='en'>
...
</html>
);
}

export default ClerkApp(App, { publishableKey: '...' });
`;

export const invalidClerkStatePropError = createErrorMessage(`
You're trying to pass an invalid object in "<ClerkProvider clerkState={...}>".

${ssrExample}
`);

export const noClerkStateError = createErrorMessage(`
Looks like you didn't pass 'clerkState' to "<ClerkProvider clerkState={...}>".

${ssrExample}
`);

export const noLoaderArgsPassedInGetAuth = createErrorMessage(`
You're calling 'getAuth()' from a loader, without providing the loader args object.
Example:

export const loader: LoaderFunction = async (args) => {
const { sessionId } = await getAuth(args);
...
};
`);

export const invalidRootLoaderCallbackReturn = createErrorMessage(`
You're returning an invalid response from the 'rootAuthLoader' called from the loader in root.tsx.
You can only return plain objects, responses created using the Remix 'json()' and 'redirect()' helpers,
custom redirect 'Response' instances (status codes in the range of 300 to 400),
or custom json 'Response' instances (containing a body that is a valid json string).
If you want to return a primitive value or an array, you can always wrap the response with an object.

Example:

export const loader: LoaderFunction = args => rootAuthLoader(args, ({ auth }) => {
const { userId } = auth;
const posts: Post[] = database.getPostsByUserId(userId);

return json({ data: posts })
// or
return new Response(JSON.stringify({ data: posts }), { headers: { 'Content-Type': 'application/json' } });
// or
return { data: posts };
})
`);

export const noSecretKeyError = createErrorMessage(`
A secretKey must be provided in order to use SSR and the exports from @clerk/remix/api.');
If your runtime supports environment variables, you can add a CLERK_SECRET_KEY variable to your config.
Otherwise, you can pass a secretKey parameter to rootAuthLoader or getAuth.
`);

export const satelliteAndMissingProxyUrlAndDomain = createErrorMessage(
`Missing domain and proxyUrl. A satellite application needs to specify a domain or a proxyUrl`,
);

export const satelliteAndMissingSignInUrl = createErrorMessage(`
Invalid signInUrl. A satellite application requires a signInUrl for development instances.
Check if signInUrl is missing from your configuration or if it is not an absolute URL.`);
export {
isClerkAPIResponseError,
isEmailLinkError,
isKnownError,
isMetamaskError,
EmailLinkErrorCode,
} from '@clerk/clerk-react/errors';
2 changes: 1 addition & 1 deletion packages/remix/src/ssr/getAuth.ts
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
import { stripPrivateDataFromObject } from '@clerk/backend/internal';

import { noLoaderArgsPassedInGetAuth } from '../errors';
import { noLoaderArgsPassedInGetAuth } from '../utils/errors';
import { authenticateRequest } from './authenticateRequest';
import { loadOptions } from './loadOptions';
import type { GetAuthReturn, LoaderFunctionArgs, RootAuthLoaderOptions } from './types';
Expand Down
4 changes: 2 additions & 2 deletions packages/remix/src/ssr/loadOptions.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -5,8 +5,8 @@ import { isDevelopmentFromSecretKey } from '@clerk/shared/keys';
import { isHttpOrHttps, isProxyUrlRelative } from '@clerk/shared/proxy';
import { isTruthy } from '@clerk/shared/underscore';

import { noSecretKeyError, satelliteAndMissingProxyUrlAndDomain, satelliteAndMissingSignInUrl } from '../errors';
import { getEnvVariable } from '../utils';
import { noSecretKeyError, satelliteAndMissingProxyUrlAndDomain, satelliteAndMissingSignInUrl } from '../utils/errors';
import { getEnvVariable } from '../utils/utils';

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

😓 this seems kind of weird, that's why i suggested the errors restructuring.

import type { LoaderFunctionArgs, RootAuthLoaderOptions } from './types';

export const loadOptions = (args: LoaderFunctionArgs, overrides: RootAuthLoaderOptions = {}) => {
Expand Down
2 changes: 1 addition & 1 deletion packages/remix/src/ssr/rootAuthLoader.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -2,7 +2,7 @@ import { decorateObjectWithResources } from '@clerk/backend/internal';
import type { defer } from '@remix-run/server-runtime';
import { isDeferredData } from '@remix-run/server-runtime/dist/responses';

import { invalidRootLoaderCallbackReturn } from '../errors';
import { invalidRootLoaderCallbackReturn } from '../utils/errors';
import { authenticateRequest } from './authenticateRequest';
import { loadOptions } from './loadOptions';
import type { LoaderFunctionArgs, LoaderFunctionReturn, RootAuthLoaderCallback, RootAuthLoaderOptions } from './types';
Expand Down
2 changes: 1 addition & 1 deletion packages/remix/src/ssr/utils.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -5,7 +5,7 @@ import type { AppLoadContext, defer } from '@remix-run/server-runtime';
import { json } from '@remix-run/server-runtime';
import cookie from 'cookie';

import { getEnvVariable } from '../utils';
import { getEnvVariable } from '../utils/utils';

export function isResponse(value: any): value is Response {
return (
Expand Down
84 changes: 84 additions & 0 deletions packages/remix/src/utils/errors.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
const createErrorMessage = (msg: string) => {
return `🔒 Clerk: ${msg.trim()}

For more info, check out the docs: https://clerk.com/docs,
or come say hi in our discord server: https://clerk.com/discord

`;
};

const ssrExample = `Use 'rootAuthLoader' as your root loader. Then, simply wrap the App component with ClerkApp and make it the default export.
Example:

import { ClerkApp } from '@clerk/remix';
import { rootAuthLoader } from '@clerk/remix/ssr.server';

export const loader: LoaderFunction = args => rootAuthLoader(args)

function App() {
return (
<html lang='en'>
...
</html>
);
}

export default ClerkApp(App, { publishableKey: '...' });
`;

export const invalidClerkStatePropError = createErrorMessage(`
You're trying to pass an invalid object in "<ClerkProvider clerkState={...}>".

${ssrExample}
`);

export const noClerkStateError = createErrorMessage(`
Looks like you didn't pass 'clerkState' to "<ClerkProvider clerkState={...}>".

${ssrExample}
`);

export const noLoaderArgsPassedInGetAuth = createErrorMessage(`
You're calling 'getAuth()' from a loader, without providing the loader args object.
Example:

export const loader: LoaderFunction = async (args) => {
const { sessionId } = await getAuth(args);
...
};
`);

export const invalidRootLoaderCallbackReturn = createErrorMessage(`
You're returning an invalid response from the 'rootAuthLoader' called from the loader in root.tsx.
You can only return plain objects, responses created using the Remix 'json()' and 'redirect()' helpers,
custom redirect 'Response' instances (status codes in the range of 300 to 400),
or custom json 'Response' instances (containing a body that is a valid json string).
If you want to return a primitive value or an array, you can always wrap the response with an object.

Example:

export const loader: LoaderFunction = args => rootAuthLoader(args, ({ auth }) => {
const { userId } = auth;
const posts: Post[] = database.getPostsByUserId(userId);

return json({ data: posts })
// or
return new Response(JSON.stringify({ data: posts }), { headers: { 'Content-Type': 'application/json' } });
// or
return { data: posts };
})
`);

export const noSecretKeyError = createErrorMessage(`
A secretKey must be provided in order to use SSR and the exports from @clerk/remix/api.');
If your runtime supports environment variables, you can add a CLERK_SECRET_KEY variable to your config.
Otherwise, you can pass a secretKey parameter to rootAuthLoader or getAuth.
`);

export const satelliteAndMissingProxyUrlAndDomain = createErrorMessage(
`Missing domain and proxyUrl. A satellite application needs to specify a domain or a proxyUrl`,
);

export const satelliteAndMissingSignInUrl = createErrorMessage(`
Invalid signInUrl. A satellite application requires a signInUrl for development instances.
Check if signInUrl is missing from your configuration or if it is not an absolute URL.`);
1 change: 1 addition & 0 deletions packages/remix/src/utils/index.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
export * from './utils';
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
import type { AppLoadContext } from '@remix-run/server-runtime';

import type { ClerkState } from './client/types';
import type { ClerkState } from '../client/types';
import { invalidClerkStatePropError, noClerkStateError } from './errors';

export function warnForSsr(val: ClerkState | undefined) {
Expand Down
4 changes: 4 additions & 0 deletions packages/remix/ssr.server/package.json
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
{
"main": "../dist/ssr/index.js",
"types": "../dist/ssr/index.d.ts"
}
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content
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
7 changes: 7 additions & 0 deletions .changeset/hungry-bottles-flow.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
---
'@clerk/nextjs': patch
'@clerk/clerk-react': patch
'@clerk/remix': patch
---

Export `EmailLinkErrorCode` from `/errors` module
5 changes: 5 additions & 0 deletions .changeset/light-buckets-check.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
---
'@clerk/remix': patch
---

Introduce `/errors` module. This path exports all error-related APIs such as `isClerkAPIResponseError`, `isEmailLinkError`, `isKnownError`, `isMetamaskError`, `EmailLinkErrorCode`
8 changes: 7 additions & 1 deletion packages/nextjs/src/client-boundary/hooks.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -13,4 +13,10 @@ export {
useUser,
} from '@clerk/clerk-react';

export { isClerkAPIResponseError, isEmailLinkError, isKnownError, isMetamaskError } from '@clerk/clerk-react/errors';
export {
isClerkAPIResponseError,
isEmailLinkError,
isKnownError,
isMetamaskError,
EmailLinkErrorCode,
} from '@clerk/clerk-react/errors';
8 changes: 7 additions & 1 deletion packages/nextjs/src/errors.ts
Original file line numberDiff line numberDiff line change
@@ -1 +1,7 @@
export { isClerkAPIResponseError, isEmailLinkError, isKnownError, isMetamaskError } from './client-boundary/hooks';
export {
isClerkAPIResponseError,
isEmailLinkError,
isKnownError,
isMetamaskError,
EmailLinkErrorCode,
} from './client-boundary/hooks';
8 changes: 7 additions & 1 deletion packages/react/src/errors.ts
Original file line numberDiff line numberDiff line change
@@ -1 +1,7 @@
export { isClerkAPIResponseError, isEmailLinkError, isKnownError, isMetamaskError } from '@clerk/shared/error';
export {
isClerkAPIResponseError,
isEmailLinkError,
isKnownError,
isMetamaskError,
EmailLinkErrorCode,
} from '@clerk/shared/error';
4 changes: 4 additions & 0 deletions packages/remix/api.server/package.json
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
{
"main": "../dist/api/index.js",
"types": "../dist/api/index.d.ts"
}
4 changes: 4 additions & 0 deletions packages/remix/errors/package.json
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
{
"main": "../dist/errors.js",
"types": "../dist/errors.d.ts"
}
4 changes: 4 additions & 0 deletions packages/remix/package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -36,6 +36,10 @@
"./api.server": {
"types": "./dist/api/index.d.ts",
"default": "./dist/api/index.js"
},
"./errors": {
"types": "./dist/errors.d.ts",
"default": "./dist/errors.js"
}
},
"main": "dist/index.js",
Expand Down
91 changes: 7 additions & 84 deletions packages/remix/src/errors.ts

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🙃 Wdyt about introducing a different structure to support subpath errors and error messages? Eg

// remix/src/errors/index.ts
export {
isClerkAPIResponseError,
isEmailLinkError,
isKnownError,
isMetamaskError,
EmailLinkErrorCode,
} from '@clerk/clerk-react/errors';
// remix/src/errors/messages.ts
const createErrorMessage = (msg: string) => {
// ...
}
// ... all the error messages that are being moved to utils/errors with this PR

I would prefer if we kept all the errors in the same place and avoid adding them to utils "bucket".

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

Definitely, but we cannot do this using a single errors.ts as this is the top-level entry file for the publicly available errors.

This PR changes the top-level API. I will open follow-up PR that aligns the strategy used in clerk/remix with the other packages as well.

Having a internalErrors could be enough, or we could always have a ./errors/ containing internal error utils and a ./errors.ts top level entry point for the public APIs

Original file line numberDiff line numberDiff line change
@@ -1,84 +1,7 @@
const createErrorMessage = (msg: string) => {
return `🔒 Clerk: ${msg.trim()}

For more info, check out the docs: https://clerk.com/docs,
or come say hi in our discord server: https://clerk.com/discord

`;
};

const ssrExample = `Use 'rootAuthLoader' as your root loader. Then, simply wrap the App component with ClerkApp and make it the default export.
Example:

import { ClerkApp } from '@clerk/remix';
import { rootAuthLoader } from '@clerk/remix/ssr.server';

export const loader: LoaderFunction = args => rootAuthLoader(args)

function App() {
return (
<html lang='en'>
...
</html>
);
}

export default ClerkApp(App, { publishableKey: '...' });
`;

export const invalidClerkStatePropError = createErrorMessage(`
You're trying to pass an invalid object in "<ClerkProvider clerkState={...}>".

${ssrExample}
`);

export const noClerkStateError = createErrorMessage(`
Looks like you didn't pass 'clerkState' to "<ClerkProvider clerkState={...}>".

${ssrExample}
`);

export const noLoaderArgsPassedInGetAuth = createErrorMessage(`
You're calling 'getAuth()' from a loader, without providing the loader args object.
Example:

export const loader: LoaderFunction = async (args) => {
const { sessionId } = await getAuth(args);
...
};
`);

export const invalidRootLoaderCallbackReturn = createErrorMessage(`
You're returning an invalid response from the 'rootAuthLoader' called from the loader in root.tsx.
You can only return plain objects, responses created using the Remix 'json()' and 'redirect()' helpers,
custom redirect 'Response' instances (status codes in the range of 300 to 400),
or custom json 'Response' instances (containing a body that is a valid json string).
If you want to return a primitive value or an array, you can always wrap the response with an object.

Example:

export const loader: LoaderFunction = args => rootAuthLoader(args, ({ auth }) => {
const { userId } = auth;
const posts: Post[] = database.getPostsByUserId(userId);

return json({ data: posts })
// or
return new Response(JSON.stringify({ data: posts }), { headers: { 'Content-Type': 'application/json' } });
// or
return { data: posts };
})
`);

export const noSecretKeyError = createErrorMessage(`
A secretKey must be provided in order to use SSR and the exports from @clerk/remix/api.');
If your runtime supports environment variables, you can add a CLERK_SECRET_KEY variable to your config.
Otherwise, you can pass a secretKey parameter to rootAuthLoader or getAuth.
`);

export const satelliteAndMissingProxyUrlAndDomain = createErrorMessage(
`Missing domain and proxyUrl. A satellite application needs to specify a domain or a proxyUrl`,
);

export const satelliteAndMissingSignInUrl = createErrorMessage(`
Invalid signInUrl. A satellite application requires a signInUrl for development instances.
Check if signInUrl is missing from your configuration or if it is not an absolute URL.`);
export {
isClerkAPIResponseError,
isEmailLinkError,
isKnownError,
isMetamaskError,
EmailLinkErrorCode,
} from '@clerk/clerk-react/errors';
2 changes: 1 addition & 1 deletion packages/remix/src/ssr/getAuth.ts
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
import { stripPrivateDataFromObject } from '@clerk/backend/internal';

import { noLoaderArgsPassedInGetAuth } from '../errors';
import { noLoaderArgsPassedInGetAuth } from '../utils/errors';
import { authenticateRequest } from './authenticateRequest';
import { loadOptions } from './loadOptions';
import type { GetAuthReturn, LoaderFunctionArgs, RootAuthLoaderOptions } from './types';
Expand Down
4 changes: 2 additions & 2 deletions packages/remix/src/ssr/loadOptions.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -5,8 +5,8 @@ import { isDevelopmentFromSecretKey } from '@clerk/shared/keys';
import { isHttpOrHttps, isProxyUrlRelative } from '@clerk/shared/proxy';
import { isTruthy } from '@clerk/shared/underscore';

import { noSecretKeyError, satelliteAndMissingProxyUrlAndDomain, satelliteAndMissingSignInUrl } from '../errors';
import { getEnvVariable } from '../utils';
import { noSecretKeyError, satelliteAndMissingProxyUrlAndDomain, satelliteAndMissingSignInUrl } from '../utils/errors';
import { getEnvVariable } from '../utils/utils';

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

😓 this seems kind of weird, that's why i suggested the errors restructuring.

import type { LoaderFunctionArgs, RootAuthLoaderOptions } from './types';

export const loadOptions = (args: LoaderFunctionArgs, overrides: RootAuthLoaderOptions = {}) => {
Expand Down
2 changes: 1 addition & 1 deletion packages/remix/src/ssr/rootAuthLoader.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -2,7 +2,7 @@ import { decorateObjectWithResources } from '@clerk/backend/internal';
import type { defer } from '@remix-run/server-runtime';
import { isDeferredData } from '@remix-run/server-runtime/dist/responses';

import { invalidRootLoaderCallbackReturn } from '../errors';
import { invalidRootLoaderCallbackReturn } from '../utils/errors';
import { authenticateRequest } from './authenticateRequest';
import { loadOptions } from './loadOptions';
import type { LoaderFunctionArgs, LoaderFunctionReturn, RootAuthLoaderCallback, RootAuthLoaderOptions } from './types';
Expand Down
2 changes: 1 addition & 1 deletion packages/remix/src/ssr/utils.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -5,7 +5,7 @@ import type { AppLoadContext, defer } from '@remix-run/server-runtime';
import { json } from '@remix-run/server-runtime';
import cookie from 'cookie';

import { getEnvVariable } from '../utils';
import { getEnvVariable } from '../utils/utils';

export function isResponse(value: any): value is Response {
return (
Expand Down
84 changes: 84 additions & 0 deletions packages/remix/src/utils/errors.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
const createErrorMessage = (msg: string) => {
return `🔒 Clerk: ${msg.trim()}

For more info, check out the docs: https://clerk.com/docs,
or come say hi in our discord server: https://clerk.com/discord

`;
};

const ssrExample = `Use 'rootAuthLoader' as your root loader. Then, simply wrap the App component with ClerkApp and make it the default export.
Example:

import { ClerkApp } from '@clerk/remix';
import { rootAuthLoader } from '@clerk/remix/ssr.server';

export const loader: LoaderFunction = args => rootAuthLoader(args)

function App() {
return (
<html lang='en'>
...
</html>
);
}

export default ClerkApp(App, { publishableKey: '...' });
`;

export const invalidClerkStatePropError = createErrorMessage(`
You're trying to pass an invalid object in "<ClerkProvider clerkState={...}>".

${ssrExample}
`);

export const noClerkStateError = createErrorMessage(`
Looks like you didn't pass 'clerkState' to "<ClerkProvider clerkState={...}>".

${ssrExample}
`);

export const noLoaderArgsPassedInGetAuth = createErrorMessage(`
You're calling 'getAuth()' from a loader, without providing the loader args object.
Example:

export const loader: LoaderFunction = async (args) => {
const { sessionId } = await getAuth(args);
...
};
`);

export const invalidRootLoaderCallbackReturn = createErrorMessage(`
You're returning an invalid response from the 'rootAuthLoader' called from the loader in root.tsx.
You can only return plain objects, responses created using the Remix 'json()' and 'redirect()' helpers,
custom redirect 'Response' instances (status codes in the range of 300 to 400),
or custom json 'Response' instances (containing a body that is a valid json string).
If you want to return a primitive value or an array, you can always wrap the response with an object.

Example:

export const loader: LoaderFunction = args => rootAuthLoader(args, ({ auth }) => {
const { userId } = auth;
const posts: Post[] = database.getPostsByUserId(userId);

return json({ data: posts })
// or
return new Response(JSON.stringify({ data: posts }), { headers: { 'Content-Type': 'application/json' } });
// or
return { data: posts };
})
`);

export const noSecretKeyError = createErrorMessage(`
A secretKey must be provided in order to use SSR and the exports from @clerk/remix/api.');
If your runtime supports environment variables, you can add a CLERK_SECRET_KEY variable to your config.
Otherwise, you can pass a secretKey parameter to rootAuthLoader or getAuth.
`);

export const satelliteAndMissingProxyUrlAndDomain = createErrorMessage(
`Missing domain and proxyUrl. A satellite application needs to specify a domain or a proxyUrl`,
);

export const satelliteAndMissingSignInUrl = createErrorMessage(`
Invalid signInUrl. A satellite application requires a signInUrl for development instances.
Check if signInUrl is missing from your configuration or if it is not an absolute URL.`);
1 change: 1 addition & 0 deletions packages/remix/src/utils/index.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
export * from './utils';
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
import type { AppLoadContext } from '@remix-run/server-runtime';

import type { ClerkState } from './client/types';
import type { ClerkState } from '../client/types';
import { invalidClerkStatePropError, noClerkStateError } from './errors';

export function warnForSsr(val: ClerkState | undefined) {
Expand Down
4 changes: 4 additions & 0 deletions packages/remix/ssr.server/package.json
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
{
"main": "../dist/ssr/index.js",
"types": "../dist/ssr/index.d.ts"
}
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
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
7 changes: 7 additions & 0 deletions .changeset/hungry-bottles-flow.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
---
'@clerk/nextjs': patch
'@clerk/clerk-react': patch
'@clerk/remix': patch
---

Export `EmailLinkErrorCode` from `/errors` module
5 changes: 5 additions & 0 deletions .changeset/light-buckets-check.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
---
'@clerk/remix': patch
---

Introduce `/errors` module. This path exports all error-related APIs such as `isClerkAPIResponseError`, `isEmailLinkError`, `isKnownError`, `isMetamaskError`, `EmailLinkErrorCode`
8 changes: 7 additions & 1 deletion packages/nextjs/src/client-boundary/hooks.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -13,4 +13,10 @@ export {
useUser,
} from '@clerk/clerk-react';

export { isClerkAPIResponseError, isEmailLinkError, isKnownError, isMetamaskError } from '@clerk/clerk-react/errors';
export {
isClerkAPIResponseError,
isEmailLinkError,
isKnownError,
isMetamaskError,
EmailLinkErrorCode,
} from '@clerk/clerk-react/errors';
8 changes: 7 additions & 1 deletion packages/nextjs/src/errors.ts
Original file line numberDiff line numberDiff line change
@@ -1 +1,7 @@
export { isClerkAPIResponseError, isEmailLinkError, isKnownError, isMetamaskError } from './client-boundary/hooks';
export {
isClerkAPIResponseError,
isEmailLinkError,
isKnownError,
isMetamaskError,
EmailLinkErrorCode,
} from './client-boundary/hooks';
8 changes: 7 additions & 1 deletion packages/react/src/errors.ts
Original file line numberDiff line numberDiff line change
@@ -1 +1,7 @@
export { isClerkAPIResponseError, isEmailLinkError, isKnownError, isMetamaskError } from '@clerk/shared/error';
export {
isClerkAPIResponseError,
isEmailLinkError,
isKnownError,
isMetamaskError,
EmailLinkErrorCode,
} from '@clerk/shared/error';
4 changes: 4 additions & 0 deletions packages/remix/api.server/package.json
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
{
"main": "../dist/api/index.js",
"types": "../dist/api/index.d.ts"
}
4 changes: 4 additions & 0 deletions packages/remix/errors/package.json
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
{
"main": "../dist/errors.js",
"types": "../dist/errors.d.ts"
}
4 changes: 4 additions & 0 deletions packages/remix/package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -36,6 +36,10 @@
"./api.server": {
"types": "./dist/api/index.d.ts",
"default": "./dist/api/index.js"
},
"./errors": {
"types": "./dist/errors.d.ts",
"default": "./dist/errors.js"
}
},
"main": "dist/index.js",
Expand Down
91 changes: 7 additions & 84 deletions packages/remix/src/errors.ts

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🙃 Wdyt about introducing a different structure to support subpath errors and error messages? Eg

// remix/src/errors/index.ts
export {
isClerkAPIResponseError,
isEmailLinkError,
isKnownError,
isMetamaskError,
EmailLinkErrorCode,
} from '@clerk/clerk-react/errors';
// remix/src/errors/messages.ts
const createErrorMessage = (msg: string) => {
// ...
}
// ... all the error messages that are being moved to utils/errors with this PR

I would prefer if we kept all the errors in the same place and avoid adding them to utils "bucket".

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

Definitely, but we cannot do this using a single errors.ts as this is the top-level entry file for the publicly available errors.

This PR changes the top-level API. I will open follow-up PR that aligns the strategy used in clerk/remix with the other packages as well.

Having a internalErrors could be enough, or we could always have a ./errors/ containing internal error utils and a ./errors.ts top level entry point for the public APIs

Original file line numberDiff line numberDiff line change
@@ -1,84 +1,7 @@
const createErrorMessage = (msg: string) => {
return `🔒 Clerk: ${msg.trim()}

For more info, check out the docs: https://clerk.com/docs,
or come say hi in our discord server: https://clerk.com/discord

`;
};

const ssrExample = `Use 'rootAuthLoader' as your root loader. Then, simply wrap the App component with ClerkApp and make it the default export.
Example:

import { ClerkApp } from '@clerk/remix';
import { rootAuthLoader } from '@clerk/remix/ssr.server';

export const loader: LoaderFunction = args => rootAuthLoader(args)

function App() {
return (
<html lang='en'>
...
</html>
);
}

export default ClerkApp(App, { publishableKey: '...' });
`;

export const invalidClerkStatePropError = createErrorMessage(`
You're trying to pass an invalid object in "<ClerkProvider clerkState={...}>".

${ssrExample}
`);

export const noClerkStateError = createErrorMessage(`
Looks like you didn't pass 'clerkState' to "<ClerkProvider clerkState={...}>".

${ssrExample}
`);

export const noLoaderArgsPassedInGetAuth = createErrorMessage(`
You're calling 'getAuth()' from a loader, without providing the loader args object.
Example:

export const loader: LoaderFunction = async (args) => {
const { sessionId } = await getAuth(args);
...
};
`);

export const invalidRootLoaderCallbackReturn = createErrorMessage(`
You're returning an invalid response from the 'rootAuthLoader' called from the loader in root.tsx.
You can only return plain objects, responses created using the Remix 'json()' and 'redirect()' helpers,
custom redirect 'Response' instances (status codes in the range of 300 to 400),
or custom json 'Response' instances (containing a body that is a valid json string).
If you want to return a primitive value or an array, you can always wrap the response with an object.

Example:

export const loader: LoaderFunction = args => rootAuthLoader(args, ({ auth }) => {
const { userId } = auth;
const posts: Post[] = database.getPostsByUserId(userId);

return json({ data: posts })
// or
return new Response(JSON.stringify({ data: posts }), { headers: { 'Content-Type': 'application/json' } });
// or
return { data: posts };
})
`);

export const noSecretKeyError = createErrorMessage(`
A secretKey must be provided in order to use SSR and the exports from @clerk/remix/api.');
If your runtime supports environment variables, you can add a CLERK_SECRET_KEY variable to your config.
Otherwise, you can pass a secretKey parameter to rootAuthLoader or getAuth.
`);

export const satelliteAndMissingProxyUrlAndDomain = createErrorMessage(
`Missing domain and proxyUrl. A satellite application needs to specify a domain or a proxyUrl`,
);

export const satelliteAndMissingSignInUrl = createErrorMessage(`
Invalid signInUrl. A satellite application requires a signInUrl for development instances.
Check if signInUrl is missing from your configuration or if it is not an absolute URL.`);
export {
isClerkAPIResponseError,
isEmailLinkError,
isKnownError,
isMetamaskError,
EmailLinkErrorCode,
} from '@clerk/clerk-react/errors';
2 changes: 1 addition & 1 deletion packages/remix/src/ssr/getAuth.ts
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
import { stripPrivateDataFromObject } from '@clerk/backend/internal';

import { noLoaderArgsPassedInGetAuth } from '../errors';
import { noLoaderArgsPassedInGetAuth } from '../utils/errors';
import { authenticateRequest } from './authenticateRequest';
import { loadOptions } from './loadOptions';
import type { GetAuthReturn, LoaderFunctionArgs, RootAuthLoaderOptions } from './types';
Expand Down
4 changes: 2 additions & 2 deletions packages/remix/src/ssr/loadOptions.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -5,8 +5,8 @@ import { isDevelopmentFromSecretKey } from '@clerk/shared/keys';
import { isHttpOrHttps, isProxyUrlRelative } from '@clerk/shared/proxy';
import { isTruthy } from '@clerk/shared/underscore';

import { noSecretKeyError, satelliteAndMissingProxyUrlAndDomain, satelliteAndMissingSignInUrl } from '../errors';
import { getEnvVariable } from '../utils';
import { noSecretKeyError, satelliteAndMissingProxyUrlAndDomain, satelliteAndMissingSignInUrl } from '../utils/errors';
import { getEnvVariable } from '../utils/utils';

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

😓 this seems kind of weird, that's why i suggested the errors restructuring.

import type { LoaderFunctionArgs, RootAuthLoaderOptions } from './types';

export const loadOptions = (args: LoaderFunctionArgs, overrides: RootAuthLoaderOptions = {}) => {
Expand Down
2 changes: 1 addition & 1 deletion packages/remix/src/ssr/rootAuthLoader.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -2,7 +2,7 @@ import { decorateObjectWithResources } from '@clerk/backend/internal';
import type { defer } from '@remix-run/server-runtime';
import { isDeferredData } from '@remix-run/server-runtime/dist/responses';

import { invalidRootLoaderCallbackReturn } from '../errors';
import { invalidRootLoaderCallbackReturn } from '../utils/errors';
import { authenticateRequest } from './authenticateRequest';
import { loadOptions } from './loadOptions';
import type { LoaderFunctionArgs, LoaderFunctionReturn, RootAuthLoaderCallback, RootAuthLoaderOptions } from './types';
Expand Down
2 changes: 1 addition & 1 deletion packages/remix/src/ssr/utils.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -5,7 +5,7 @@ import type { AppLoadContext, defer } from '@remix-run/server-runtime';
import { json } from '@remix-run/server-runtime';
import cookie from 'cookie';

import { getEnvVariable } from '../utils';
import { getEnvVariable } from '../utils/utils';

export function isResponse(value: any): value is Response {
return (
Expand Down
84 changes: 84 additions & 0 deletions packages/remix/src/utils/errors.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
const createErrorMessage = (msg: string) => {
return `🔒 Clerk: ${msg.trim()}

For more info, check out the docs: https://clerk.com/docs,
or come say hi in our discord server: https://clerk.com/discord

`;
};

const ssrExample = `Use 'rootAuthLoader' as your root loader. Then, simply wrap the App component with ClerkApp and make it the default export.
Example:

import { ClerkApp } from '@clerk/remix';
import { rootAuthLoader } from '@clerk/remix/ssr.server';

export const loader: LoaderFunction = args => rootAuthLoader(args)

function App() {
return (
<html lang='en'>
...
</html>
);
}

export default ClerkApp(App, { publishableKey: '...' });
`;

export const invalidClerkStatePropError = createErrorMessage(`
You're trying to pass an invalid object in "<ClerkProvider clerkState={...}>".

${ssrExample}
`);

export const noClerkStateError = createErrorMessage(`
Looks like you didn't pass 'clerkState' to "<ClerkProvider clerkState={...}>".

${ssrExample}
`);

export const noLoaderArgsPassedInGetAuth = createErrorMessage(`
You're calling 'getAuth()' from a loader, without providing the loader args object.
Example:

export const loader: LoaderFunction = async (args) => {
const { sessionId } = await getAuth(args);
...
};
`);

export const invalidRootLoaderCallbackReturn = createErrorMessage(`
You're returning an invalid response from the 'rootAuthLoader' called from the loader in root.tsx.
You can only return plain objects, responses created using the Remix 'json()' and 'redirect()' helpers,
custom redirect 'Response' instances (status codes in the range of 300 to 400),
or custom json 'Response' instances (containing a body that is a valid json string).
If you want to return a primitive value or an array, you can always wrap the response with an object.

Example:

export const loader: LoaderFunction = args => rootAuthLoader(args, ({ auth }) => {
const { userId } = auth;
const posts: Post[] = database.getPostsByUserId(userId);

return json({ data: posts })
// or
return new Response(JSON.stringify({ data: posts }), { headers: { 'Content-Type': 'application/json' } });
// or
return { data: posts };
})
`);

export const noSecretKeyError = createErrorMessage(`
A secretKey must be provided in order to use SSR and the exports from @clerk/remix/api.');
If your runtime supports environment variables, you can add a CLERK_SECRET_KEY variable to your config.
Otherwise, you can pass a secretKey parameter to rootAuthLoader or getAuth.
`);

export const satelliteAndMissingProxyUrlAndDomain = createErrorMessage(
`Missing domain and proxyUrl. A satellite application needs to specify a domain or a proxyUrl`,
);

export const satelliteAndMissingSignInUrl = createErrorMessage(`
Invalid signInUrl. A satellite application requires a signInUrl for development instances.
Check if signInUrl is missing from your configuration or if it is not an absolute URL.`);
1 change: 1 addition & 0 deletions packages/remix/src/utils/index.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
export * from './utils';
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
import type { AppLoadContext } from '@remix-run/server-runtime';

import type { ClerkState } from './client/types';
import type { ClerkState } from '../client/types';
import { invalidClerkStatePropError, noClerkStateError } from './errors';

export function warnForSsr(val: ClerkState | undefined) {
Expand Down
4 changes: 4 additions & 0 deletions packages/remix/ssr.server/package.json
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
{
"main": "../dist/ssr/index.js",
"types": "../dist/ssr/index.d.ts"
}
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
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
7 changes: 7 additions & 0 deletions .changeset/hungry-bottles-flow.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
---
'@clerk/nextjs': patch
'@clerk/clerk-react': patch
'@clerk/remix': patch
---

Export `EmailLinkErrorCode` from `/errors` module
5 changes: 5 additions & 0 deletions .changeset/light-buckets-check.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
---
'@clerk/remix': patch
---

Introduce `/errors` module. This path exports all error-related APIs such as `isClerkAPIResponseError`, `isEmailLinkError`, `isKnownError`, `isMetamaskError`, `EmailLinkErrorCode`
8 changes: 7 additions & 1 deletion packages/nextjs/src/client-boundary/hooks.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -13,4 +13,10 @@ export {
useUser,
} from '@clerk/clerk-react';

export { isClerkAPIResponseError, isEmailLinkError, isKnownError, isMetamaskError } from '@clerk/clerk-react/errors';
export {
isClerkAPIResponseError,
isEmailLinkError,
isKnownError,
isMetamaskError,
EmailLinkErrorCode,
} from '@clerk/clerk-react/errors';
8 changes: 7 additions & 1 deletion packages/nextjs/src/errors.ts
Original file line numberDiff line numberDiff line change
@@ -1 +1,7 @@
export { isClerkAPIResponseError, isEmailLinkError, isKnownError, isMetamaskError } from './client-boundary/hooks';
export {
isClerkAPIResponseError,
isEmailLinkError,
isKnownError,
isMetamaskError,
EmailLinkErrorCode,
} from './client-boundary/hooks';
8 changes: 7 additions & 1 deletion packages/react/src/errors.ts
Original file line numberDiff line numberDiff line change
@@ -1 +1,7 @@
export { isClerkAPIResponseError, isEmailLinkError, isKnownError, isMetamaskError } from '@clerk/shared/error';
export {
isClerkAPIResponseError,
isEmailLinkError,
isKnownError,
isMetamaskError,
EmailLinkErrorCode,
} from '@clerk/shared/error';
4 changes: 4 additions & 0 deletions packages/remix/api.server/package.json
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
{
"main": "../dist/api/index.js",
"types": "../dist/api/index.d.ts"
}
4 changes: 4 additions & 0 deletions packages/remix/errors/package.json
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
{
"main": "../dist/errors.js",
"types": "../dist/errors.d.ts"
}
4 changes: 4 additions & 0 deletions packages/remix/package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -36,6 +36,10 @@
"./api.server": {
"types": "./dist/api/index.d.ts",
"default": "./dist/api/index.js"
},
"./errors": {
"types": "./dist/errors.d.ts",
"default": "./dist/errors.js"
}
},
"main": "dist/index.js",
Expand Down
91 changes: 7 additions & 84 deletions packages/remix/src/errors.ts

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🙃 Wdyt about introducing a different structure to support subpath errors and error messages? Eg

// remix/src/errors/index.ts
export {
isClerkAPIResponseError,
isEmailLinkError,
isKnownError,
isMetamaskError,
EmailLinkErrorCode,
} from '@clerk/clerk-react/errors';
// remix/src/errors/messages.ts
const createErrorMessage = (msg: string) => {
// ...
}
// ... all the error messages that are being moved to utils/errors with this PR

I would prefer if we kept all the errors in the same place and avoid adding them to utils "bucket".

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

Definitely, but we cannot do this using a single errors.ts as this is the top-level entry file for the publicly available errors.

This PR changes the top-level API. I will open follow-up PR that aligns the strategy used in clerk/remix with the other packages as well.

Having a internalErrors could be enough, or we could always have a ./errors/ containing internal error utils and a ./errors.ts top level entry point for the public APIs

Original file line numberDiff line numberDiff line change
@@ -1,84 +1,7 @@
const createErrorMessage = (msg: string) => {
return `🔒 Clerk: ${msg.trim()}

For more info, check out the docs: https://clerk.com/docs,
or come say hi in our discord server: https://clerk.com/discord

`;
};

const ssrExample = `Use 'rootAuthLoader' as your root loader. Then, simply wrap the App component with ClerkApp and make it the default export.
Example:

import { ClerkApp } from '@clerk/remix';
import { rootAuthLoader } from '@clerk/remix/ssr.server';

export const loader: LoaderFunction = args => rootAuthLoader(args)

function App() {
return (
<html lang='en'>
...
</html>
);
}

export default ClerkApp(App, { publishableKey: '...' });
`;

export const invalidClerkStatePropError = createErrorMessage(`
You're trying to pass an invalid object in "<ClerkProvider clerkState={...}>".

${ssrExample}
`);

export const noClerkStateError = createErrorMessage(`
Looks like you didn't pass 'clerkState' to "<ClerkProvider clerkState={...}>".

${ssrExample}
`);

export const noLoaderArgsPassedInGetAuth = createErrorMessage(`
You're calling 'getAuth()' from a loader, without providing the loader args object.
Example:

export const loader: LoaderFunction = async (args) => {
const { sessionId } = await getAuth(args);
...
};
`);

export const invalidRootLoaderCallbackReturn = createErrorMessage(`
You're returning an invalid response from the 'rootAuthLoader' called from the loader in root.tsx.
You can only return plain objects, responses created using the Remix 'json()' and 'redirect()' helpers,
custom redirect 'Response' instances (status codes in the range of 300 to 400),
or custom json 'Response' instances (containing a body that is a valid json string).
If you want to return a primitive value or an array, you can always wrap the response with an object.

Example:

export const loader: LoaderFunction = args => rootAuthLoader(args, ({ auth }) => {
const { userId } = auth;
const posts: Post[] = database.getPostsByUserId(userId);

return json({ data: posts })
// or
return new Response(JSON.stringify({ data: posts }), { headers: { 'Content-Type': 'application/json' } });
// or
return { data: posts };
})
`);

export const noSecretKeyError = createErrorMessage(`
A secretKey must be provided in order to use SSR and the exports from @clerk/remix/api.');
If your runtime supports environment variables, you can add a CLERK_SECRET_KEY variable to your config.
Otherwise, you can pass a secretKey parameter to rootAuthLoader or getAuth.
`);

export const satelliteAndMissingProxyUrlAndDomain = createErrorMessage(
`Missing domain and proxyUrl. A satellite application needs to specify a domain or a proxyUrl`,
);

export const satelliteAndMissingSignInUrl = createErrorMessage(`
Invalid signInUrl. A satellite application requires a signInUrl for development instances.
Check if signInUrl is missing from your configuration or if it is not an absolute URL.`);
export {
isClerkAPIResponseError,
isEmailLinkError,
isKnownError,
isMetamaskError,
EmailLinkErrorCode,
} from '@clerk/clerk-react/errors';
2 changes: 1 addition & 1 deletion packages/remix/src/ssr/getAuth.ts
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
import { stripPrivateDataFromObject } from '@clerk/backend/internal';

import { noLoaderArgsPassedInGetAuth } from '../errors';
import { noLoaderArgsPassedInGetAuth } from '../utils/errors';
import { authenticateRequest } from './authenticateRequest';
import { loadOptions } from './loadOptions';
import type { GetAuthReturn, LoaderFunctionArgs, RootAuthLoaderOptions } from './types';
Expand Down
4 changes: 2 additions & 2 deletions packages/remix/src/ssr/loadOptions.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -5,8 +5,8 @@ import { isDevelopmentFromSecretKey } from '@clerk/shared/keys';
import { isHttpOrHttps, isProxyUrlRelative } from '@clerk/shared/proxy';
import { isTruthy } from '@clerk/shared/underscore';

import { noSecretKeyError, satelliteAndMissingProxyUrlAndDomain, satelliteAndMissingSignInUrl } from '../errors';
import { getEnvVariable } from '../utils';
import { noSecretKeyError, satelliteAndMissingProxyUrlAndDomain, satelliteAndMissingSignInUrl } from '../utils/errors';
import { getEnvVariable } from '../utils/utils';

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

😓 this seems kind of weird, that's why i suggested the errors restructuring.

import type { LoaderFunctionArgs, RootAuthLoaderOptions } from './types';

export const loadOptions = (args: LoaderFunctionArgs, overrides: RootAuthLoaderOptions = {}) => {
Expand Down
2 changes: 1 addition & 1 deletion packages/remix/src/ssr/rootAuthLoader.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -2,7 +2,7 @@ import { decorateObjectWithResources } from '@clerk/backend/internal';
import type { defer } from '@remix-run/server-runtime';
import { isDeferredData } from '@remix-run/server-runtime/dist/responses';

import { invalidRootLoaderCallbackReturn } from '../errors';
import { invalidRootLoaderCallbackReturn } from '../utils/errors';
import { authenticateRequest } from './authenticateRequest';
import { loadOptions } from './loadOptions';
import type { LoaderFunctionArgs, LoaderFunctionReturn, RootAuthLoaderCallback, RootAuthLoaderOptions } from './types';
Expand Down
2 changes: 1 addition & 1 deletion packages/remix/src/ssr/utils.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -5,7 +5,7 @@ import type { AppLoadContext, defer } from '@remix-run/server-runtime';
import { json } from '@remix-run/server-runtime';
import cookie from 'cookie';

import { getEnvVariable } from '../utils';
import { getEnvVariable } from '../utils/utils';

export function isResponse(value: any): value is Response {
return (
Expand Down
84 changes: 84 additions & 0 deletions packages/remix/src/utils/errors.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
const createErrorMessage = (msg: string) => {
return `🔒 Clerk: ${msg.trim()}

For more info, check out the docs: https://clerk.com/docs,
or come say hi in our discord server: https://clerk.com/discord

`;
};

const ssrExample = `Use 'rootAuthLoader' as your root loader. Then, simply wrap the App component with ClerkApp and make it the default export.
Example:

import { ClerkApp } from '@clerk/remix';
import { rootAuthLoader } from '@clerk/remix/ssr.server';

export const loader: LoaderFunction = args => rootAuthLoader(args)

function App() {
return (
<html lang='en'>
...
</html>
);
}

export default ClerkApp(App, { publishableKey: '...' });
`;

export const invalidClerkStatePropError = createErrorMessage(`
You're trying to pass an invalid object in "<ClerkProvider clerkState={...}>".

${ssrExample}
`);

export const noClerkStateError = createErrorMessage(`
Looks like you didn't pass 'clerkState' to "<ClerkProvider clerkState={...}>".

${ssrExample}
`);

export const noLoaderArgsPassedInGetAuth = createErrorMessage(`
You're calling 'getAuth()' from a loader, without providing the loader args object.
Example:

export const loader: LoaderFunction = async (args) => {
const { sessionId } = await getAuth(args);
...
};
`);

export const invalidRootLoaderCallbackReturn = createErrorMessage(`
You're returning an invalid response from the 'rootAuthLoader' called from the loader in root.tsx.
You can only return plain objects, responses created using the Remix 'json()' and 'redirect()' helpers,
custom redirect 'Response' instances (status codes in the range of 300 to 400),
or custom json 'Response' instances (containing a body that is a valid json string).
If you want to return a primitive value or an array, you can always wrap the response with an object.

Example:

export const loader: LoaderFunction = args => rootAuthLoader(args, ({ auth }) => {
const { userId } = auth;
const posts: Post[] = database.getPostsByUserId(userId);

return json({ data: posts })
// or
return new Response(JSON.stringify({ data: posts }), { headers: { 'Content-Type': 'application/json' } });
// or
return { data: posts };
})
`);

export const noSecretKeyError = createErrorMessage(`
A secretKey must be provided in order to use SSR and the exports from @clerk/remix/api.');
If your runtime supports environment variables, you can add a CLERK_SECRET_KEY variable to your config.
Otherwise, you can pass a secretKey parameter to rootAuthLoader or getAuth.
`);

export const satelliteAndMissingProxyUrlAndDomain = createErrorMessage(
`Missing domain and proxyUrl. A satellite application needs to specify a domain or a proxyUrl`,
);

export const satelliteAndMissingSignInUrl = createErrorMessage(`
Invalid signInUrl. A satellite application requires a signInUrl for development instances.
Check if signInUrl is missing from your configuration or if it is not an absolute URL.`);
1 change: 1 addition & 0 deletions packages/remix/src/utils/index.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
export * from './utils';
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
import type { AppLoadContext } from '@remix-run/server-runtime';

import type { ClerkState } from './client/types';
import type { ClerkState } from '../client/types';
import { invalidClerkStatePropError, noClerkStateError } from './errors';

export function warnForSsr(val: ClerkState | undefined) {
Expand Down
4 changes: 4 additions & 0 deletions packages/remix/ssr.server/package.json
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
{
"main": "../dist/ssr/index.js",
"types": "../dist/ssr/index.d.ts"
}
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content
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
7 changes: 7 additions & 0 deletions .changeset/hungry-bottles-flow.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
---
'@clerk/nextjs': patch
'@clerk/clerk-react': patch
'@clerk/remix': patch
---

Export `EmailLinkErrorCode` from `/errors` module
5 changes: 5 additions & 0 deletions .changeset/light-buckets-check.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
---
'@clerk/remix': patch
---

Introduce `/errors` module. This path exports all error-related APIs such as `isClerkAPIResponseError`, `isEmailLinkError`, `isKnownError`, `isMetamaskError`, `EmailLinkErrorCode`
8 changes: 7 additions & 1 deletion packages/nextjs/src/client-boundary/hooks.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -13,4 +13,10 @@ export {
useUser,
} from '@clerk/clerk-react';

export { isClerkAPIResponseError, isEmailLinkError, isKnownError, isMetamaskError } from '@clerk/clerk-react/errors';
export {
isClerkAPIResponseError,
isEmailLinkError,
isKnownError,
isMetamaskError,
EmailLinkErrorCode,
} from '@clerk/clerk-react/errors';
8 changes: 7 additions & 1 deletion packages/nextjs/src/errors.ts
Original file line numberDiff line numberDiff line change
@@ -1 +1,7 @@
export { isClerkAPIResponseError, isEmailLinkError, isKnownError, isMetamaskError } from './client-boundary/hooks';
export {
isClerkAPIResponseError,
isEmailLinkError,
isKnownError,
isMetamaskError,
EmailLinkErrorCode,
} from './client-boundary/hooks';
8 changes: 7 additions & 1 deletion packages/react/src/errors.ts
Original file line numberDiff line numberDiff line change
@@ -1 +1,7 @@
export { isClerkAPIResponseError, isEmailLinkError, isKnownError, isMetamaskError } from '@clerk/shared/error';
export {
isClerkAPIResponseError,
isEmailLinkError,
isKnownError,
isMetamaskError,
EmailLinkErrorCode,
} from '@clerk/shared/error';
4 changes: 4 additions & 0 deletions packages/remix/api.server/package.json
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
{
"main": "../dist/api/index.js",
"types": "../dist/api/index.d.ts"
}
4 changes: 4 additions & 0 deletions packages/remix/errors/package.json
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
{
"main": "../dist/errors.js",
"types": "../dist/errors.d.ts"
}
4 changes: 4 additions & 0 deletions packages/remix/package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -36,6 +36,10 @@
"./api.server": {
"types": "./dist/api/index.d.ts",
"default": "./dist/api/index.js"
},
"./errors": {
"types": "./dist/errors.d.ts",
"default": "./dist/errors.js"
}
},
"main": "dist/index.js",
Expand Down
91 changes: 7 additions & 84 deletions packages/remix/src/errors.ts

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🙃 Wdyt about introducing a different structure to support subpath errors and error messages? Eg

// remix/src/errors/index.ts
export {
isClerkAPIResponseError,
isEmailLinkError,
isKnownError,
isMetamaskError,
EmailLinkErrorCode,
} from '@clerk/clerk-react/errors';
// remix/src/errors/messages.ts
const createErrorMessage = (msg: string) => {
// ...
}
// ... all the error messages that are being moved to utils/errors with this PR

I would prefer if we kept all the errors in the same place and avoid adding them to utils "bucket".

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

Definitely, but we cannot do this using a single errors.ts as this is the top-level entry file for the publicly available errors.

This PR changes the top-level API. I will open follow-up PR that aligns the strategy used in clerk/remix with the other packages as well.

Having a internalErrors could be enough, or we could always have a ./errors/ containing internal error utils and a ./errors.ts top level entry point for the public APIs

Original file line numberDiff line numberDiff line change
@@ -1,84 +1,7 @@
const createErrorMessage = (msg: string) => {
return `🔒 Clerk: ${msg.trim()}

For more info, check out the docs: https://clerk.com/docs,
or come say hi in our discord server: https://clerk.com/discord

`;
};

const ssrExample = `Use 'rootAuthLoader' as your root loader. Then, simply wrap the App component with ClerkApp and make it the default export.
Example:

import { ClerkApp } from '@clerk/remix';
import { rootAuthLoader } from '@clerk/remix/ssr.server';

export const loader: LoaderFunction = args => rootAuthLoader(args)

function App() {
return (
<html lang='en'>
...
</html>
);
}

export default ClerkApp(App, { publishableKey: '...' });
`;

export const invalidClerkStatePropError = createErrorMessage(`
You're trying to pass an invalid object in "<ClerkProvider clerkState={...}>".

${ssrExample}
`);

export const noClerkStateError = createErrorMessage(`
Looks like you didn't pass 'clerkState' to "<ClerkProvider clerkState={...}>".

${ssrExample}
`);

export const noLoaderArgsPassedInGetAuth = createErrorMessage(`
You're calling 'getAuth()' from a loader, without providing the loader args object.
Example:

export const loader: LoaderFunction = async (args) => {
const { sessionId } = await getAuth(args);
...
};
`);

export const invalidRootLoaderCallbackReturn = createErrorMessage(`
You're returning an invalid response from the 'rootAuthLoader' called from the loader in root.tsx.
You can only return plain objects, responses created using the Remix 'json()' and 'redirect()' helpers,
custom redirect 'Response' instances (status codes in the range of 300 to 400),
or custom json 'Response' instances (containing a body that is a valid json string).
If you want to return a primitive value or an array, you can always wrap the response with an object.

Example:

export const loader: LoaderFunction = args => rootAuthLoader(args, ({ auth }) => {
const { userId } = auth;
const posts: Post[] = database.getPostsByUserId(userId);

return json({ data: posts })
// or
return new Response(JSON.stringify({ data: posts }), { headers: { 'Content-Type': 'application/json' } });
// or
return { data: posts };
})
`);

export const noSecretKeyError = createErrorMessage(`
A secretKey must be provided in order to use SSR and the exports from @clerk/remix/api.');
If your runtime supports environment variables, you can add a CLERK_SECRET_KEY variable to your config.
Otherwise, you can pass a secretKey parameter to rootAuthLoader or getAuth.
`);

export const satelliteAndMissingProxyUrlAndDomain = createErrorMessage(
`Missing domain and proxyUrl. A satellite application needs to specify a domain or a proxyUrl`,
);

export const satelliteAndMissingSignInUrl = createErrorMessage(`
Invalid signInUrl. A satellite application requires a signInUrl for development instances.
Check if signInUrl is missing from your configuration or if it is not an absolute URL.`);
export {
isClerkAPIResponseError,
isEmailLinkError,
isKnownError,
isMetamaskError,
EmailLinkErrorCode,
} from '@clerk/clerk-react/errors';
2 changes: 1 addition & 1 deletion packages/remix/src/ssr/getAuth.ts
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
import { stripPrivateDataFromObject } from '@clerk/backend/internal';

import { noLoaderArgsPassedInGetAuth } from '../errors';
import { noLoaderArgsPassedInGetAuth } from '../utils/errors';
import { authenticateRequest } from './authenticateRequest';
import { loadOptions } from './loadOptions';
import type { GetAuthReturn, LoaderFunctionArgs, RootAuthLoaderOptions } from './types';
Expand Down
4 changes: 2 additions & 2 deletions packages/remix/src/ssr/loadOptions.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -5,8 +5,8 @@ import { isDevelopmentFromSecretKey } from '@clerk/shared/keys';
import { isHttpOrHttps, isProxyUrlRelative } from '@clerk/shared/proxy';
import { isTruthy } from '@clerk/shared/underscore';

import { noSecretKeyError, satelliteAndMissingProxyUrlAndDomain, satelliteAndMissingSignInUrl } from '../errors';
import { getEnvVariable } from '../utils';
import { noSecretKeyError, satelliteAndMissingProxyUrlAndDomain, satelliteAndMissingSignInUrl } from '../utils/errors';
import { getEnvVariable } from '../utils/utils';

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

😓 this seems kind of weird, that's why i suggested the errors restructuring.

import type { LoaderFunctionArgs, RootAuthLoaderOptions } from './types';

export const loadOptions = (args: LoaderFunctionArgs, overrides: RootAuthLoaderOptions = {}) => {
Expand Down
2 changes: 1 addition & 1 deletion packages/remix/src/ssr/rootAuthLoader.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -2,7 +2,7 @@ import { decorateObjectWithResources } from '@clerk/backend/internal';
import type { defer } from '@remix-run/server-runtime';
import { isDeferredData } from '@remix-run/server-runtime/dist/responses';

import { invalidRootLoaderCallbackReturn } from '../errors';
import { invalidRootLoaderCallbackReturn } from '../utils/errors';
import { authenticateRequest } from './authenticateRequest';
import { loadOptions } from './loadOptions';
import type { LoaderFunctionArgs, LoaderFunctionReturn, RootAuthLoaderCallback, RootAuthLoaderOptions } from './types';
Expand Down
2 changes: 1 addition & 1 deletion packages/remix/src/ssr/utils.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -5,7 +5,7 @@ import type { AppLoadContext, defer } from '@remix-run/server-runtime';
import { json } from '@remix-run/server-runtime';
import cookie from 'cookie';

import { getEnvVariable } from '../utils';
import { getEnvVariable } from '../utils/utils';

export function isResponse(value: any): value is Response {
return (
Expand Down
84 changes: 84 additions & 0 deletions packages/remix/src/utils/errors.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
const createErrorMessage = (msg: string) => {
return `🔒 Clerk: ${msg.trim()}

For more info, check out the docs: https://clerk.com/docs,
or come say hi in our discord server: https://clerk.com/discord

`;
};

const ssrExample = `Use 'rootAuthLoader' as your root loader. Then, simply wrap the App component with ClerkApp and make it the default export.
Example:

import { ClerkApp } from '@clerk/remix';
import { rootAuthLoader } from '@clerk/remix/ssr.server';

export const loader: LoaderFunction = args => rootAuthLoader(args)

function App() {
return (
<html lang='en'>
...
</html>
);
}

export default ClerkApp(App, { publishableKey: '...' });
`;

export const invalidClerkStatePropError = createErrorMessage(`
You're trying to pass an invalid object in "<ClerkProvider clerkState={...}>".

${ssrExample}
`);

export const noClerkStateError = createErrorMessage(`
Looks like you didn't pass 'clerkState' to "<ClerkProvider clerkState={...}>".

${ssrExample}
`);

export const noLoaderArgsPassedInGetAuth = createErrorMessage(`
You're calling 'getAuth()' from a loader, without providing the loader args object.
Example:

export const loader: LoaderFunction = async (args) => {
const { sessionId } = await getAuth(args);
...
};
`);

export const invalidRootLoaderCallbackReturn = createErrorMessage(`
You're returning an invalid response from the 'rootAuthLoader' called from the loader in root.tsx.
You can only return plain objects, responses created using the Remix 'json()' and 'redirect()' helpers,
custom redirect 'Response' instances (status codes in the range of 300 to 400),
or custom json 'Response' instances (containing a body that is a valid json string).
If you want to return a primitive value or an array, you can always wrap the response with an object.

Example:

export const loader: LoaderFunction = args => rootAuthLoader(args, ({ auth }) => {
const { userId } = auth;
const posts: Post[] = database.getPostsByUserId(userId);

return json({ data: posts })
// or
return new Response(JSON.stringify({ data: posts }), { headers: { 'Content-Type': 'application/json' } });
// or
return { data: posts };
})
`);

export const noSecretKeyError = createErrorMessage(`
A secretKey must be provided in order to use SSR and the exports from @clerk/remix/api.');
If your runtime supports environment variables, you can add a CLERK_SECRET_KEY variable to your config.
Otherwise, you can pass a secretKey parameter to rootAuthLoader or getAuth.
`);

export const satelliteAndMissingProxyUrlAndDomain = createErrorMessage(
`Missing domain and proxyUrl. A satellite application needs to specify a domain or a proxyUrl`,
);

export const satelliteAndMissingSignInUrl = createErrorMessage(`
Invalid signInUrl. A satellite application requires a signInUrl for development instances.
Check if signInUrl is missing from your configuration or if it is not an absolute URL.`);
1 change: 1 addition & 0 deletions packages/remix/src/utils/index.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
export * from './utils';
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
import type { AppLoadContext } from '@remix-run/server-runtime';

import type { ClerkState } from './client/types';
import type { ClerkState } from '../client/types';
import { invalidClerkStatePropError, noClerkStateError } from './errors';

export function warnForSsr(val: ClerkState | undefined) {
Expand Down
4 changes: 4 additions & 0 deletions packages/remix/ssr.server/package.json
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
{
"main": "../dist/ssr/index.js",
"types": "../dist/ssr/index.d.ts"
}