Skip to content
Open
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
8 changes: 8 additions & 0 deletions .changeset/spotty-shrimps-rule.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
'@clerk/clerk-js': minor
'@clerk/nextjs': minor
'@clerk/clerk-react': minor
'@clerk/types': minor
---

wip
61 changes: 57 additions & 4 deletions packages/clerk-js/src/core/clerk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import {
TelemetryCollector,
} from '@clerk/shared/telemetry';
import { addClerkPrefix, isAbsoluteUrl, stripScheme } from '@clerk/shared/url';
import { allSettled, handleValueOrFn, noop } from '@clerk/shared/utils';
import { allSettled, createDeferredPromise, handleValueOrFn, noop } from '@clerk/shared/utils';
import type {
__experimental_CheckoutInstance,
__experimental_CheckoutOptions,
Expand Down Expand Up @@ -261,6 +261,21 @@ export class Clerk implements ClerkInterface {
public __internal_isWebAuthnSupported: (() => boolean) | undefined;
public __internal_isWebAuthnAutofillSupported: (() => Promise<boolean>) | undefined;
public __internal_isWebAuthnPlatformAuthenticatorSupported: (() => Promise<boolean>) | undefined;
public __internal_startTransition = (cb: () => Promise<void> | void): void => {
const sT = this.#options.__internal_startTransition;
// console.log('sT', sT);
if (sT) {
sT(cb);
} else {
void cb();
}
};
public __internal_setResources: ListenerCallback = (resources: Resources) => {
const sR = this.#options.__internal_setResources;
if (sR) {
sR(resources);
}
};

public __internal_setActiveInProgress = false;

Expand Down Expand Up @@ -1400,6 +1415,17 @@ export class Clerk implements ClerkInterface {
newSession?.currentTask &&
this.#options.taskUrls?.[newSession?.currentTask.key];

const navigatePromise = createDeferredPromise();

const transitionSafe = async (promise: Promise<unknown> | unknown): Promise<void> => {
if (this.#options.__internal_startTransition) {
void Promise.resolve(promise).then(navigatePromise.resolve);
} else {
await promise;
}
};

// this.__internal_startTransition(async () => {
if (!beforeEmit && (redirectUrl || taskUrl || setActiveNavigate)) {
await tracker.track(async () => {
if (!this.client) {
Expand All @@ -1408,29 +1434,43 @@ export class Clerk implements ClerkInterface {
}

if (newSession?.status !== 'pending') {
// this.__internal_startTransition(() => {
this.#setTransitiveState();
// });
}

if (taskUrl) {
const taskUrlWithRedirect = redirectUrl
? buildURL({ base: taskUrl, hashSearchParams: { redirectUrl } }, { stringify: true })
: taskUrl;

await this.navigate(taskUrlWithRedirect);
} else if (setActiveNavigate && newSession) {
await setActiveNavigate({ session: newSession });
// this.__internal_startTransition(() => {
await transitionSafe(setActiveNavigate({ session: newSession }));
// void Promise.resolve(setActiveNavigate({ session: newSession })).then(navigatePromise.resolve);
// });
} else if (redirectUrl) {
// if (!this.client) {
// return;
// }
if (this.client.isEligibleForTouch()) {
const absoluteRedirectUrl = new URL(redirectUrl, window.location.href);
const redirectUrlWithAuth = this.buildUrlWithAuth(
this.client.buildTouchUrl({ redirectUrl: absoluteRedirectUrl }),
);
await this.navigate(redirectUrlWithAuth);
await transitionSafe(this.navigate(redirectUrlWithAuth));
// void this.navigate(redirectUrlWithAuth).then(navigatePromise.resolve);
}
await this.navigate(redirectUrl);
await transitionSafe(this.navigate(redirectUrl));
// void this.navigate(redirectUrl).then(navigatePromise.resolve);
}
});
} else {
navigatePromise.resolve();
}

// ATTENTION: This breaks for transitions but should be fine.
//3. Check if hard reloading (onbeforeunload). If not, set the user/session and emit
if (tracker.isUnloading()) {
return;
Expand All @@ -1439,6 +1479,8 @@ export class Clerk implements ClerkInterface {
this.#setAccessors(newSession);
this.#emit();

// Await the navigation and the state update
await navigatePromise.promise;
// Do not revalidate server cache for pending sessions to avoid unmount of `SignIn/SignUp` AIOs when navigating to task
// newSession can be mutated by the time we get here (org change session touch)
if (newSession?.status !== 'pending') {
Expand All @@ -1447,6 +1489,7 @@ export class Clerk implements ClerkInterface {
} finally {
this.__internal_setActiveInProgress = false;
}
console.log('setActive done');
};

public addListener = (listener: ListenerCallback): UnsubscribeCallback => {
Expand Down Expand Up @@ -1507,6 +1550,8 @@ export class Clerk implements ClerkInterface {
const customNavigate =
options?.replace && this.#options.routerReplace ? this.#options.routerReplace : this.#options.routerPush;

// console.log('customNavigate', customNavigate);

debugLogger.info(`Clerk is navigating to: ${toURL}`);
if (this.#options.routerDebug) {
console.log(`Clerk is navigating to: ${toURL}`);
Expand Down Expand Up @@ -2810,6 +2855,13 @@ export class Clerk implements ClerkInterface {
organization: this.organization,
});
}

// this.__internal_setResources?.({
// client: this.client,
// session: this.session,
// user: this.user,
// organization: this.organization,
// });
}
};

Expand All @@ -2828,6 +2880,7 @@ export class Clerk implements ClerkInterface {
this.session = undefined;
this.organization = undefined;
this.user = undefined;
console.log('setTransitiveState');
this.#emit();
};

Expand Down
2 changes: 2 additions & 0 deletions packages/nextjs/src/app-router/client/ClerkProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,8 @@ const NextClientClerkProvider = (props: NextClerkProviderProps) => {
routerPush: push,
// @ts-expect-error Error because of the stricter types of internal `replace`
routerReplace: replace,
// @ts-expect-error Error because of the stricter types of internal `startTransition`
__internal_startTransition: startTransition,
});

return (
Expand Down
23 changes: 17 additions & 6 deletions packages/react/src/contexts/ClerkContextProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
UserContext,
} from '@clerk/shared/react';
import type { ClientResource, InitialState, Resources } from '@clerk/types';
import React from 'react';
import React, { useDeferredValue } from 'react';

import { IsomorphicClerk } from '../isomorphicClerk';
import type { IsomorphicClerkOptions } from '../types';
Expand All @@ -24,20 +24,30 @@ export type ClerkContextProviderState = Resources;

export function ClerkContextProvider(props: ClerkContextProvider) {
const { isomorphicClerkOptions, initialState, children } = props;
const { isomorphicClerk: clerk, clerkStatus } = useLoadedIsomorphicClerk(isomorphicClerkOptions);
const { isomorphicClerk: clerk, clerkStatus } = useLoadedIsomorphicClerk({
...isomorphicClerkOptions,
// __internal_setResources: (resources: Resources) => setState(resources),
});

const [state, setState] = React.useState<ClerkContextProviderState>({
const [updatedState, setState] = React.useState<ClerkContextProviderState>({
client: clerk.client as ClientResource,
session: clerk.session,
user: clerk.user,
organization: clerk.organization,
});

const state = useDeferredValue(updatedState);
// const state = updatedState || defaultState;

React.useEffect(() => {
return clerk.addListener(e => setState({ ...e }));
return clerk.addListener(e => {
console.log('[Listener]', e.organization?.id);
setState(e);
});
}, []);
Comment on lines 42 to 47
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Remove console.log before merging or gate it for development.

The console.log statement is useful for spike/debugging but should be removed or conditionally enabled for production code.

Apply this diff to remove the debug log:

 React.useEffect(() => {
   return clerk.addListener(e => {
-    console.log('[Listener]', e.organization?.id);
     setState(e);
   });
 }, []);

Alternatively, gate it with a development flag if logging is needed for debugging:

 React.useEffect(() => {
   return clerk.addListener(e => {
-    console.log('[Listener]', e.organization?.id);
+    if (process.env.NODE_ENV === 'development') {
+      console.log('[Listener]', e.organization?.id);
+    }
     setState(e);
   });
 }, []);
🤖 Prompt for AI Agents
In packages/react/src/contexts/ClerkContextProvider.tsx around lines 42 to 47,
remove the stray console.log('[Listener]', e.organization?.id) or gate it behind
a development-only check; either delete the console.log line entirely or wrap it
in a conditional such as if (process.env.NODE_ENV === 'development') { /* log */
} (or use the project’s __DEV__/isDevelopment flag) so no debug logging is
emitted in production.


const derivedState = deriveState(clerk.loaded, state, initialState);
const _derivedState = deriveState(clerk.loaded, state, initialState);
const derivedState = useDeferredValue(_derivedState);
const clerkCtx = React.useMemo(
() => ({ value: clerk }),
[
Expand Down Expand Up @@ -113,7 +123,8 @@ export function ClerkContextProvider(props: ClerkContextProvider) {

const useLoadedIsomorphicClerk = (options: IsomorphicClerkOptions) => {
const isomorphicClerkRef = React.useRef(IsomorphicClerk.getOrCreateInstance(options));
const [clerkStatus, setClerkStatus] = React.useState(isomorphicClerkRef.current.status);
const [_clerkStatus, setClerkStatus] = React.useState(isomorphicClerkRef.current.status);
const clerkStatus = useDeferredValue(_clerkStatus);

React.useEffect(() => {
void isomorphicClerkRef.current.__unstable__updateProps({ appearance: options.appearance });
Expand Down
4 changes: 4 additions & 0 deletions packages/types/src/clerk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1103,6 +1103,10 @@ export type ClerkOptions = ClerkOptionsNavigation &
*/
__internal_keyless_dismissPrompt?: (() => Promise<void>) | null;

__internal_startTransition?: (cb: () => Promise<void> | void) => void;

__internal_setResources?: (resources: Resources) => void;

/**
* Customize the URL paths users are redirected to after sign-in or sign-up when specific
* session tasks need to be completed.
Expand Down
Loading