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
6 changes: 6 additions & 0 deletions .changeset/mean-poets-bow.md
Original file line numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 numberDiff line numberDiff line change
Expand Up@@ -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;
Expand All@@ -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;
Expand All@@ -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
Expand DownExpand Up@@ -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
Expand DownExpand Up@@ -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;
/**
Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
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;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
fix(clerk-react): Missing methods and properties from IsomorphicClerk by panteliselef · Pull Request #2226 · clerk/javascript · GitHub
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
6 changes: 6 additions & 0 deletions .changeset/mean-poets-bow.md
Original file line numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 numberDiff line numberDiff line change
Expand Up@@ -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;
Expand All@@ -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;
Expand All@@ -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
Expand DownExpand Up@@ -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
Expand DownExpand Up@@ -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;
/**
Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' fix(clerk-react): Missing methods and properties from IsomorphicClerk by panteliselef · Pull Request #2226 · clerk/javascript · GitHub
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
6 changes: 6 additions & 0 deletions .changeset/mean-poets-bow.md
Original file line numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 numberDiff line numberDiff line change
Expand Up@@ -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;
Expand All@@ -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;
Expand All@@ -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
Expand DownExpand Up@@ -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
Expand DownExpand Up@@ -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;
/**
Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' fix(clerk-react): Missing methods and properties from IsomorphicClerk by panteliselef · Pull Request #2226 · clerk/javascript · GitHub
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
6 changes: 6 additions & 0 deletions .changeset/mean-poets-bow.md
Original file line numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 numberDiff line numberDiff line change
Expand Up@@ -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;
Expand All@@ -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;
Expand All@@ -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
Expand DownExpand Up@@ -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
Expand DownExpand Up@@ -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;
/**
Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' fix(clerk-react): Missing methods and properties from IsomorphicClerk by panteliselef · Pull Request #2226 · clerk/javascript · GitHub
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
6 changes: 6 additions & 0 deletions .changeset/mean-poets-bow.md
Original file line numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 numberDiff line numberDiff line change
Expand Up@@ -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;
Expand All@@ -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;
Expand All@@ -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
Expand DownExpand Up@@ -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
Expand DownExpand Up@@ -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;
/**
Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' fix(clerk-react): Missing methods and properties from IsomorphicClerk by panteliselef · Pull Request #2226 · clerk/javascript · GitHub
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
6 changes: 6 additions & 0 deletions .changeset/mean-poets-bow.md
Original file line numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 numberDiff line numberDiff line change
Expand Up@@ -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;
Expand All@@ -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;
Expand All@@ -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
Expand DownExpand Up@@ -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
Expand DownExpand Up@@ -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;
/**
Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' fix(clerk-react): Missing methods and properties from IsomorphicClerk by panteliselef · Pull Request #2226 · clerk/javascript · GitHub
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
6 changes: 6 additions & 0 deletions .changeset/mean-poets-bow.md
Original file line numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 numberDiff line numberDiff line change
Expand Up@@ -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;
Expand All@@ -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;
Expand All@@ -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
Expand DownExpand Up@@ -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
Expand DownExpand Up@@ -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;
/**
Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); fix(clerk-react): Missing methods and properties from IsomorphicClerk by panteliselef · Pull Request #2226 · clerk/javascript · GitHub
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
6 changes: 6 additions & 0 deletions .changeset/mean-poets-bow.md
Original file line numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 numberDiff line numberDiff line change
Expand Up@@ -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;
Expand All@@ -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;
Expand All@@ -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
Expand DownExpand Up@@ -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
Expand DownExpand Up@@ -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;
/**
Expand Down