Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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
21 changes: 16 additions & 5 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,17 +24,28 @@ export type ClerkContextProviderState = Resources;

export function ClerkContextProvider(props: ClerkContextProvider) {
const { isomorphicClerkOptions, initialState, children } = props;
const { isomorphicClerk: clerk, clerkStatus } = useLoadedIsomorphicClerk(isomorphicClerkOptions);
const [updatedState, setState] = React.useState<ClerkContextProviderState | null>(null);
const { isomorphicClerk: clerk, clerkStatus } = useLoadedIsomorphicClerk({
...isomorphicClerkOptions,
// __internal_setResources: (resources: Resources) => setState(resources),
});

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

const state = useDeferredValue(updatedState || defaultState);

// const state = updatedState || defaultState;

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

const derivedState = deriveState(clerk.loaded, state, initialState);
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 @@
*/
__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 Expand Up @@ -1175,7 +1179,7 @@
*/
windowNavigate: (to: URL | string) => void;
},
) => Promise<unknown> | unknown;

Check warning on line 1182 in packages/types/src/clerk.ts

View workflow job for this annotation

GitHub Actions / Static analysis

'unknown' overrides all other types in this union type

export type WithoutRouting<T> = Omit<T, 'path' | 'routing'>;

Expand Down
Loading