Skip to content

Commit

Permalink
fix(clerk-react): Missing methods and properties from IsomorphicClerk (
Browse files Browse the repository at this point in the history
…#2226)

* fix(clerk-react): Missing methods and properties from IsomorphicClerk

* fix(clerk-react): Fill out missing methods and properties from IsomorphicClerk (#2228)

* fix(clerk-react): Align implementation of isSatellite

* fix(clerk-react): Add changeset

* chore(clerk-react): Remove todos

* fix(clerk-react): Replace optional Clerk properties with undefined

* fix(clerk-js): Add missing sdkMetadata getter

---------

Co-authored-by: Tom Milewski <[email protected]>
  • Loading branch information
panteliselef and tmilewski authored Nov 29, 2023
1 parent d6a7ea6 commit 6e54b1b
Show file tree
Hide file tree
Showing 5 changed files with 212 additions and 18 deletions.
6 changes: 6 additions & 0 deletions .changeset/mean-poets-bow.md
Original file line number Diff line number Diff line change
@@ -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.
12 changes: 8 additions & 4 deletions packages/clerk-js/src/core/clerk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -180,6 +180,10 @@ export class Clerk implements ClerkInterface {
return Clerk.version;
}

get sdkMetadata(): SDKMetadata {
return Clerk.sdkMetadata;
}

get loaded(): boolean {
return this.#isReady;
}
Expand Down
2 changes: 1 addition & 1 deletion packages/react/src/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
'<UserProfile.Page /> component needs to be a direct child of `<UserProfile />` or `<UserButton />`.';
Expand Down
183 changes: 182 additions & 1 deletion packages/react/src/isomorphicClerk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,23 @@ import type { TelemetryCollector } from '@clerk/shared/telemetry';
import type {
ActiveSessionResource,
AuthenticateWithMetamaskParams,
BuildUrlWithAuthParams,
Clerk,
ClientResource,
CreateOrganizationParams,
CreateOrganizationProps,
DomainOrProxyUrl,
HandleEmailLinkVerificationParams,
HandleOAuthCallbackParams,
InstanceType,
ListenerCallback,
LoadedClerk,
OrganizationListProps,
OrganizationProfileProps,
OrganizationResource,
OrganizationSwitcherProps,
RedirectOptions,
SDKMetadata,
SetActiveParams,
SignInProps,
SignInRedirectOptions,
Expand All @@ -28,6 +33,7 @@ import type {
UserButtonProps,
UserProfileProps,
UserResource,
Without,
} from '@clerk/types';

import { unsupportedNonBrowserDomainOrProxyUrlFunction } from './errors';
Expand Down Expand Up @@ -57,7 +63,80 @@ type MethodName<T> = {

type MethodCallback = () => Promise<unknown> | 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<Clerk['redirectWithAuth']>) => 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<void>;
// TODO: Align return type (maybe not possible or correct)
createOrganization: (params: CreateOrganizationParams) => Promise<OrganizationResource | void>;
// TODO: Align return type (maybe not possible or correct)
getOrganization: (organizationId: string) => Promise<OrganizationResource | void>;

// 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;
Expand Down Expand Up @@ -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<HeadlessBrowserClerk | BrowserClerk | undefined> {
if (this.mode !== 'browser' || this.#loaded) {
return;
Expand Down
27 changes: 15 additions & 12 deletions packages/types/src/clerk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,14 +54,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;
Expand All @@ -70,7 +73,7 @@ export interface Clerk {
publishableKey: string;

/** Clerk Proxy url string. */
proxyUrl?: string;
proxyUrl: string | undefined;

/** Clerk Satellite Frontend API string. */
domain: string;
Expand All @@ -79,22 +82,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
Expand Down Expand Up @@ -766,8 +769,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
Expand Down Expand Up @@ -819,7 +822,7 @@ type CreateOrganizationMode =
export type OrganizationSwitcherProps = CreateOrganizationMode &
OrganizationProfileMode & {
/**
Controls the default state of the OrganizationSwitcher
* Controls the default state of the OrganizationSwitcher
*/
defaultOpen?: boolean;
/**
Expand Down

0 comments on commit 6e54b1b

Please sign in to comment.