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 68f4cb3eba..43a403bf7f 100644
--- a/packages/clerk-js/src/core/clerk.ts
+++ b/packages/clerk-js/src/core/clerk.ts
@@ -146,10 +146,10 @@ export 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 telemetry?: TelemetryCollector;
@@ -180,6 +180,10 @@ export 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 9ab1690fe6..498e47d280 100644
--- a/packages/react/src/errors.ts
+++ b/packages/react/src/errors.ts
@@ -23,7 +23,7 @@ export const invalidStateError =
'Invalid state. Feel free to submit a bug or reach out to support here: https://clerk.com/support';
export const unsupportedNonBrowserDomainOrProxyUrlFunction =
- 'Unsupported usage of domain or proxyUrl. The usage of domain or proxyUrl as function is not supported in non-browser environments.';
+ '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 =
' component needs to be a direct child of `` or ``.';
diff --git a/packages/react/src/isomorphicClerk.ts b/packages/react/src/isomorphicClerk.ts
index 6ce2d3f79c..c86e580bbe 100644
--- a/packages/react/src/isomorphicClerk.ts
+++ b/packages/react/src/isomorphicClerk.ts
@@ -4,6 +4,7 @@ import type { TelemetryCollector } from '@clerk/shared/telemetry';
import type {
ActiveSessionResource,
AuthenticateWithMetamaskParams,
+ BuildUrlWithAuthParams,
Clerk,
ClientResource,
CreateOrganizationParams,
@@ -11,11 +12,15 @@ import type {
DomainOrProxyUrl,
HandleEmailLinkVerificationParams,
HandleOAuthCallbackParams,
+ InstanceType,
ListenerCallback,
+ LoadedClerk,
OrganizationListProps,
OrganizationProfileProps,
OrganizationResource,
OrganizationSwitcherProps,
+ RedirectOptions,
+ SDKMetadata,
SetActiveParams,
SignInProps,
SignInRedirectOptions,
@@ -28,6 +33,7 @@ import type {
UserButtonProps,
UserProfileProps,
UserResource,
+ Without,
} from '@clerk/types';
import { unsupportedNonBrowserDomainOrProxyUrlFunction } from './errors';
@@ -57,7 +63,80 @@ type MethodName = {
type MethodCallback = () => Promise | unknown;
-export class IsomorphicClerk {
+type IsomorphicLoadedClerk = Without<
+ 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'
+> & {
+ // 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;
+};
+
+export class IsomorphicClerk implements IsomorphicLoadedClerk {
private readonly mode: 'browser' | 'server';
private readonly options: IsomorphicClerkOptions;
private readonly Clerk: ClerkProp;
@@ -144,6 +223,108 @@ export 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 || '';
+ }
+
+ 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') {
+ return errorThrower.throw(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 c38968c9d0..49ccab2843 100644
--- a/packages/types/src/clerk.ts
+++ b/packages/types/src/clerk.ts
@@ -59,14 +59,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;
frontendApi: string;
@@ -75,7 +78,7 @@ export interface Clerk {
publishableKey: string;
/** Clerk Proxy url string. */
- proxyUrl?: string;
+ proxyUrl: string | undefined;
/** Clerk Satellite Frontend API string. */
domain: string;
@@ -84,22 +87,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
@@ -760,8 +763,8 @@ export type UserButtonProps = UserButtonProfileMode & {
*/
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
@@ -813,7 +816,7 @@ type CreateOrganizationMode =
export type OrganizationSwitcherProps = CreateOrganizationMode &
OrganizationProfileMode & {
/**
- Controls the default state of the OrganizationSwitcher
+ * Controls the default state of the OrganizationSwitcher
*/
defaultOpen?: boolean;
/**