diff --git a/.changeset/mean-poets-bow.md b/.changeset/mean-poets-bow.md
new file mode 100644
index 0000000000..1c4c7d93e1
--- /dev/null
+++ b/.changeset/mean-poets-bow.md
@@ -0,0 +1,6 @@
+---
+'@clerk/clerk-js': patch
+'@clerk/clerk-react': patch
+---
+
+Sync IsomorphicClerk with the clerk singleton and the LoadedClerk interface. IsomorphicClerk now extends from LoadedClerk.
diff --git a/packages/clerk-js/src/core/clerk.ts b/packages/clerk-js/src/core/clerk.ts
index f085cd0ba9..59e5b219f8 100644
--- a/packages/clerk-js/src/core/clerk.ts
+++ b/packages/clerk-js/src/core/clerk.ts
@@ -154,13 +154,13 @@ export default class Clerk implements ClerkInterface {
version: __PKG_VERSION__,
};
- public client?: ClientResource;
- public session?: ActiveSessionResource | null;
- public organization?: OrganizationResource | null;
- public user?: UserResource | null;
+ public client: ClientResource | undefined;
+ public session: ActiveSessionResource | null | undefined;
+ public organization: OrganizationResource | null | undefined;
+ public user: UserResource | null | undefined;
public __internal_country?: string | null;
public readonly frontendApi: string;
- public readonly publishableKey?: string;
+ public readonly publishableKey: string | undefined;
protected internal_last_error: ClerkAPIError | null = null;
@@ -191,6 +191,10 @@ export default class Clerk implements ClerkInterface {
return Clerk.version;
}
+ get sdkMetadata(): SDKMetadata {
+ return Clerk.sdkMetadata;
+ }
+
get loaded(): boolean {
return this.#isReady;
}
diff --git a/packages/react/src/errors.ts b/packages/react/src/errors.ts
index 0e881c3de3..521817ecd7 100644
--- a/packages/react/src/errors.ts
+++ b/packages/react/src/errors.ts
@@ -30,7 +30,7 @@ export const invalidStateError =
'Clerk: Invalid state. Feel free to submit a bug or reach out to support here: https://clerk.com/support';
export const unsupportedNonBrowserDomainOrProxyUrlFunction =
- 'Clerk: Unsupported usage of domain or proxyUrl. The usage of domain or proxyUrl as function is not supported in non-browser environments.';
+ 'Clerk: Unsupported usage of isSatellite, domain or proxyUrl. The usage of isSatellite, domain or proxyUrl as function is not supported in non-browser environments.';
export const userProfilePageRenderedError =
'Clerk: component needs to be a direct child of `` or ``.';
diff --git a/packages/react/src/isomorphicClerk.ts b/packages/react/src/isomorphicClerk.ts
index 9a30c028fe..bdfd174830 100644
--- a/packages/react/src/isomorphicClerk.ts
+++ b/packages/react/src/isomorphicClerk.ts
@@ -5,6 +5,7 @@ import type {
ActiveSessionResource,
AuthenticateWithMetamaskParams,
BeforeEmitCallback,
+ BuildUrlWithAuthParams,
Clerk,
ClientResource,
CreateOrganizationParams,
@@ -13,10 +14,16 @@ import type {
HandleEmailLinkVerificationParams,
HandleMagicLinkVerificationParams,
HandleOAuthCallbackParams,
+ InstanceType,
ListenerCallback,
+ LoadedClerk,
OrganizationListProps,
OrganizationMembershipResource,
+ OrganizationProfileProps,
OrganizationResource,
+ OrganizationSwitcherProps,
+ RedirectOptions,
+ SDKMetadata,
SetActiveParams,
SignInProps,
SignInRedirectOptions,
@@ -30,7 +37,6 @@ import type {
UserProfileProps,
UserResource,
} from '@clerk/types';
-import type { OrganizationProfileProps, OrganizationSwitcherProps } from '@clerk/types';
import { unsupportedNonBrowserDomainOrProxyUrlFunction } from './errors';
import type {
@@ -57,10 +63,84 @@ type MethodName = {
type MethodCallback = () => Promise | unknown;
-export default class IsomorphicClerk {
+type IsomorphicLoadedClerk = Omit<
+ LoadedClerk,
+ /**
+ * Override ClerkJS methods in order to support premountMethodCalls
+ */
+ | 'buildSignInUrl'
+ | 'buildSignUpUrl'
+ | 'buildUserProfileUrl'
+ | 'buildCreateOrganizationUrl'
+ | 'buildOrganizationProfileUrl'
+ | 'buildHomeUrl'
+ | 'buildUrlWithAuth'
+ | 'redirectWithAuth'
+ | 'redirectToSignIn'
+ | 'redirectToSignUp'
+ | 'handleRedirectCallback'
+ | 'handleUnauthenticated'
+ | 'authenticateWithMetamask'
+ | 'createOrganization'
+ | 'getOrganization'
+ | 'mountUserButton'
+ | 'mountOrganizationList'
+ | 'mountOrganizationSwitcher'
+ | 'mountOrganizationProfile'
+ | 'mountCreateOrganization'
+ | 'mountSignUp'
+ | 'mountSignIn'
+ | 'mountUserProfile'
+ | 'client'
+ | 'getOrganizationMemberships'
+> & {
+ // TODO: Align return type
+ redirectWithAuth: (...args: Parameters) => void;
+ // TODO: Align return type
+ redirectToSignIn: (options: SignInRedirectOptions) => void;
+ // TODO: Align return type
+ redirectToSignUp: (options: SignUpRedirectOptions) => void;
+ // TODO: Align return type and parms
+ handleRedirectCallback: (params: HandleOAuthCallbackParams) => void;
+ handleUnauthenticated: () => void;
+ // TODO: Align Promise unknown
+ authenticateWithMetamask: (params: AuthenticateWithMetamaskParams) => Promise;
+ // TODO: Align return type (maybe not possible or correct)
+ createOrganization: (params: CreateOrganizationParams) => Promise;
+ // TODO: Align return type (maybe not possible or correct)
+ getOrganization: (organizationId: string) => Promise;
+
+ // TODO: Align return type
+ buildSignInUrl: (opts?: RedirectOptions) => string | void;
+ // TODO: Align return type
+ buildSignUpUrl: (opts?: RedirectOptions) => string | void;
+ // TODO: Align return type
+ buildUserProfileUrl: () => string | void;
+ // TODO: Align return type
+ buildCreateOrganizationUrl: () => string | void;
+ // TODO: Align return type
+ buildOrganizationProfileUrl: () => string | void;
+ // TODO: Align return type
+ buildHomeUrl: () => string | void;
+ // TODO: Align return type
+ buildUrlWithAuth: (to: string, opts?: BuildUrlWithAuthParams | undefined) => string | void;
+
+ // TODO: Align optional props
+ mountUserButton: (node: HTMLDivElement, props: UserButtonProps) => void;
+ mountOrganizationList: (node: HTMLDivElement, props: OrganizationListProps) => void;
+ mountOrganizationSwitcher: (node: HTMLDivElement, props: OrganizationSwitcherProps) => void;
+ mountOrganizationProfile: (node: HTMLDivElement, props: OrganizationProfileProps) => void;
+ mountCreateOrganization: (node: HTMLDivElement, props: CreateOrganizationProps) => void;
+ mountSignUp: (node: HTMLDivElement, props: SignUpProps) => void;
+ mountSignIn: (node: HTMLDivElement, props: SignInProps) => void;
+ mountUserProfile: (node: HTMLDivElement, props: UserProfileProps) => void;
+ client: ClientResource | undefined;
+
+ getOrganizationMemberships: () => Promise;
+};
+
+export default class IsomorphicClerk implements IsomorphicLoadedClerk {
private readonly mode: 'browser' | 'server';
- private readonly frontendApi?: string;
- private readonly publishableKey?: string;
private readonly options: IsomorphicClerkOptions;
private readonly Clerk: ClerkProp;
private clerkjs: BrowserClerk | HeadlessBrowserClerk | null = null;
@@ -83,6 +163,12 @@ export default class IsomorphicClerk {
#loaded = false;
#domain: DomainOrProxyUrl['domain'];
#proxyUrl: DomainOrProxyUrl['proxyUrl'];
+ #frontendApi: string | undefined;
+ #publishableKey: string | undefined;
+
+ get publishableKey(): string | undefined {
+ return this.#publishableKey;
+ }
get loaded(): boolean {
return this.#loaded;
@@ -127,8 +213,8 @@ export default class IsomorphicClerk {
constructor(options: IsomorphicClerkOptions) {
const { Clerk = null, frontendApi, publishableKey } = options || {};
- this.frontendApi = frontendApi;
- this.publishableKey = publishableKey;
+ this.#frontendApi = frontendApi;
+ this.#publishableKey = publishableKey;
this.#proxyUrl = options?.proxyUrl;
this.#domain = options?.domain;
this.options = options;
@@ -137,6 +223,108 @@ export default class IsomorphicClerk {
void this.loadClerkJS();
}
+ get sdkMetadata(): SDKMetadata | undefined {
+ return this.clerkjs?.sdkMetadata || this.options.sdkMetadata || undefined;
+ }
+
+ get instanceType(): InstanceType | undefined {
+ return this.clerkjs?.instanceType;
+ }
+
+ get frontendApi(): string {
+ return this.clerkjs?.frontendApi || this.#frontendApi || '';
+ }
+
+ get isStandardBrowser(): boolean {
+ return this.clerkjs?.isStandardBrowser || this.options.standardBrowser || false;
+ }
+
+ get isSatellite(): boolean {
+ // This getter can run in environments where window is not available.
+ // In those cases we should expect and use domain as a string
+ if (typeof window !== 'undefined' && window.location) {
+ return handleValueOrFn(this.options.isSatellite, new URL(window.location.href), false);
+ }
+ if (typeof this.options.isSatellite === 'function') {
+ throw new Error(unsupportedNonBrowserDomainOrProxyUrlFunction);
+ }
+ return false;
+ }
+
+ isReady = (): boolean => Boolean(this.clerkjs?.isReady());
+
+ buildSignInUrl = (opts?: RedirectOptions): string | void => {
+ const callback = () => this.clerkjs?.buildSignInUrl(opts) || '';
+ if (this.clerkjs && this.#loaded) {
+ return callback();
+ } else {
+ this.premountMethodCalls.set('buildSignInUrl', callback);
+ }
+ };
+
+ buildSignUpUrl = (opts?: RedirectOptions): string | void => {
+ const callback = () => this.clerkjs?.buildSignUpUrl(opts) || '';
+ if (this.clerkjs && this.#loaded) {
+ return callback();
+ } else {
+ this.premountMethodCalls.set('buildSignUpUrl', callback);
+ }
+ };
+
+ buildUserProfileUrl = (): string | void => {
+ const callback = () => this.clerkjs?.buildUserProfileUrl() || '';
+ if (this.clerkjs && this.#loaded) {
+ return callback();
+ } else {
+ this.premountMethodCalls.set('buildUserProfileUrl', callback);
+ }
+ };
+
+ buildCreateOrganizationUrl = (): string | void => {
+ const callback = () => this.clerkjs?.buildCreateOrganizationUrl() || '';
+ if (this.clerkjs && this.#loaded) {
+ return callback();
+ } else {
+ this.premountMethodCalls.set('buildCreateOrganizationUrl', callback);
+ }
+ };
+
+ buildOrganizationProfileUrl = (): string | void => {
+ const callback = () => this.clerkjs?.buildOrganizationProfileUrl() || '';
+ if (this.clerkjs && this.#loaded) {
+ return callback();
+ } else {
+ this.premountMethodCalls.set('buildOrganizationProfileUrl', callback);
+ }
+ };
+
+ buildHomeUrl = (): string | void => {
+ const callback = () => this.clerkjs?.buildHomeUrl() || '';
+ if (this.clerkjs && this.#loaded) {
+ return callback();
+ } else {
+ this.premountMethodCalls.set('buildHomeUrl', callback);
+ }
+ };
+
+ buildUrlWithAuth = (to: string, opts?: BuildUrlWithAuthParams | undefined): string | void => {
+ const callback = () => this.clerkjs?.buildUrlWithAuth(to, opts) || '';
+ if (this.clerkjs && this.#loaded) {
+ return callback();
+ } else {
+ this.premountMethodCalls.set('buildUrlWithAuth', callback);
+ }
+ };
+
+ handleUnauthenticated = (): void => {
+ const callback = () => this.clerkjs?.handleUnauthenticated();
+ if (this.clerkjs && this.#loaded) {
+ void callback();
+ } else {
+ this.premountMethodCalls.set('handleUnauthenticated', callback);
+ }
+ };
+
async loadClerkJS(): Promise {
if (this.mode !== 'browser' || this.#loaded) {
return;
diff --git a/packages/types/src/clerk.ts b/packages/types/src/clerk.ts
index 044d1b936a..6f9ed2c406 100644
--- a/packages/types/src/clerk.ts
+++ b/packages/types/src/clerk.ts
@@ -60,14 +60,17 @@ export interface Clerk {
/**
* Clerk SDK version number.
*/
- version?: string;
+ version: string | undefined;
/**
* If present, contains information about the SDK that the host application is using.
* For example, if Clerk is loaded through `@clerk/nextjs`, this would be `{ name: '@clerk/nextjs', version: '1.0.0' }`
*/
- sdkMetadata?: SDKMetadata;
+ sdkMetadata: SDKMetadata | undefined;
+ /**
+ * If true the bootstrapping of Clerk.load() has completed successfully.
+ */
loaded: boolean;
/**
@@ -77,10 +80,10 @@ export interface Clerk {
frontendApi: string;
/** Clerk Publishable Key string. */
- publishableKey?: string;
+ publishableKey: string | undefined;
/** Clerk Proxy url string. */
- proxyUrl?: string;
+ proxyUrl: string | undefined;
/** Clerk Satellite Frontend API string. */
domain: string;
@@ -89,22 +92,22 @@ export interface Clerk {
isSatellite: boolean;
/** Clerk Instance type is defined from the Publishable key */
- instanceType?: InstanceType;
+ instanceType: InstanceType | undefined;
/** Clerk flag for loading Clerk in a standard browser setup */
- isStandardBrowser?: boolean;
+ isStandardBrowser: boolean | undefined;
/** Client handling most Clerk operations. */
- client?: ClientResource;
+ client: ClientResource | undefined;
/** Active Session. */
- session?: ActiveSessionResource | null;
+ session: ActiveSessionResource | null | undefined;
/** Active Organization */
- organization?: OrganizationResource | null;
+ organization: OrganizationResource | null | undefined;
/** Current User. */
- user?: UserResource | null;
+ user: UserResource | null | undefined;
/**
* Signs out the current user on single-session instances, or all users on multi-session instances
@@ -790,8 +793,8 @@ export type UserButtonProps = {
*/
showName?: boolean;
/**
- Controls the default state of the UserButton
- */
+ * Controls the default state of the UserButton
+ */
defaultOpen?: boolean;
/**
* Full URL or path to navigate after sign out is complete
@@ -851,8 +854,8 @@ type LooseExtractedParams = `:${T}` | (string & NonNullable