Skip to content

Commit 6c8a275

Browse files
committed
poc:sw
1 parent 4db99ca commit 6c8a275

File tree

10 files changed

+2150
-3
lines changed

10 files changed

+2150
-3
lines changed

packages/clerk-js/rspack.config.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,16 @@ const common = ({ mode, variant, disableRHC = false }) => {
6161
CLERK_ENV: mode,
6262
NODE_ENV: mode,
6363
}),
64+
// Copy SharedWorker script to dist folder
65+
isProduction(mode) &&
66+
new rspack.CopyRspackPlugin({
67+
patterns: [
68+
{
69+
from: path.resolve(__dirname, 'src/utils/clerk-shared-worker.js'),
70+
to: 'clerk-shared-worker.js',
71+
},
72+
],
73+
}),
6474
process.env.RSDOCTOR &&
6575
new RsdoctorRspackPlugin({
6676
mode: process.env.RSDOCTOR === 'brief' ? 'brief' : 'normal',

packages/clerk-js/src/core/clerk.ts

Lines changed: 275 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,8 @@ import { assertNoLegacyProp } from '../utils/assertNoLegacyProp';
114114
import { CLERK_ENVIRONMENT_STORAGE_ENTRY, SafeLocalStorage } from '../utils/localStorage';
115115
import { memoizeListenerCallback } from '../utils/memoizeStateListenerCallback';
116116
import { RedirectUrls } from '../utils/redirectUrls';
117+
import { ClerkSharedWorkerManager } from '../utils/sharedWorker';
118+
import type { SharedWorkerOptions } from '../utils/sharedWorkerUtils';
117119
import { AuthCookieService } from './auth/AuthCookieService';
118120
import { CaptchaHeartbeat } from './auth/CaptchaHeartbeat';
119121
import { CLERK_SATELLITE_URL, CLERK_SUFFIXED_COOKIES, CLERK_SYNCED, ERROR_CODES } from './constants';
@@ -218,6 +220,7 @@ export class Clerk implements ClerkInterface {
218220
#touchThrottledUntil = 0;
219221
#componentNavigationContext: __internal_ComponentNavigationContext | null = null;
220222
#publicEventBus = createClerkEventBus();
223+
#sharedWorkerManager?: ClerkSharedWorkerManager;
221224

222225
public __internal_getCachedResources:
223226
| (() => Promise<{ client: ClientJSONSnapshot | null; environment: EnvironmentJSONSnapshot | null }>)
@@ -2360,6 +2363,9 @@ export class Clerk implements ClerkInterface {
23602363
this.#handleImpersonationFab();
23612364
this.#handleKeylessPrompt();
23622365

2366+
// Initialize SharedWorker if configured
2367+
void this.#initializeSharedWorker();
2368+
23632369
this.#publicEventBus.emit(clerkEvents.Status, initializationDegradedCounter > 0 ? 'degraded' : 'ready');
23642370
};
23652371

@@ -2397,6 +2403,9 @@ export class Clerk implements ClerkInterface {
23972403
this.#componentControls = Clerk.mountComponentRenderer(this, this.environment, this.#options);
23982404
}
23992405

2406+
// Initialize SharedWorker if configured
2407+
void this.#initializeSharedWorker();
2408+
24002409
this.#publicEventBus.emit(clerkEvents.Status, initializationDegradedCounter > 0 ? 'degraded' : 'ready');
24012410
};
24022411

@@ -2582,6 +2591,195 @@ export class Clerk implements ClerkInterface {
25822591
return this.buildUrlWithAuth(url);
25832592
};
25842593

2594+
#initializeSharedWorker = async (): Promise<void> => {
2595+
if (!this.#options.sharedWorker) {
2596+
return;
2597+
}
2598+
2599+
const sharedWorkerOptions = this.#options.sharedWorker;
2600+
2601+
if (sharedWorkerOptions.autoStart === false) {
2602+
logger.logOnce('Clerk: SharedWorker autoStart is disabled');
2603+
return;
2604+
}
2605+
2606+
try {
2607+
const clerkInstanceId = `clerk-${this.publishableKey.slice(-8)}-${Date.now()}`;
2608+
2609+
this.#sharedWorkerManager = new ClerkSharedWorkerManager(
2610+
sharedWorkerOptions as SharedWorkerOptions,
2611+
clerkInstanceId,
2612+
);
2613+
2614+
const worker = await this.#sharedWorkerManager.initialize();
2615+
2616+
if (worker) {
2617+
this.#setupSharedWorkerEventForwarding();
2618+
logger.logOnce('Clerk: SharedWorker initialized successfully');
2619+
}
2620+
} catch (error) {
2621+
logger.warnOnce(`Clerk: Failed to initialize SharedWorker: ${error}`);
2622+
}
2623+
};
2624+
2625+
#setupSharedWorkerEventForwarding = (): void => {
2626+
if (!this.#sharedWorkerManager) {
2627+
return;
2628+
}
2629+
2630+
this.addListener(resources => {
2631+
this.#sharedWorkerManager?.postClerkEvent('clerk:state_change', {
2632+
isSignedIn: this.isSignedIn,
2633+
user: resources.user?.id || null,
2634+
session: resources.session?.id || null,
2635+
organization: resources.organization?.id || null,
2636+
});
2637+
});
2638+
2639+
eventBus.on(events.UserSignOut, () => {
2640+
this.#sharedWorkerManager?.postClerkEvent('clerk:sign_out', {
2641+
timestamp: Date.now(),
2642+
});
2643+
});
2644+
2645+
eventBus.on(events.SessionTokenResolved, () => {
2646+
this.#sharedWorkerManager?.postClerkEvent('clerk:session_update', {
2647+
sessionId: this.session?.id,
2648+
timestamp: Date.now(),
2649+
});
2650+
});
2651+
2652+
eventBus.on(events.TokenUpdate, payload => {
2653+
this.#sharedWorkerManager?.postClerkEvent('clerk:token_update', {
2654+
hasToken: !!payload.token,
2655+
timestamp: Date.now(),
2656+
});
2657+
});
2658+
2659+
eventBus.on(events.EnvironmentUpdate, () => {
2660+
this.#sharedWorkerManager?.postClerkEvent('clerk:environment_update', {
2661+
timestamp: Date.now(),
2662+
});
2663+
});
2664+
};
2665+
2666+
/**
2667+
* Returns the SharedWorker manager instance if available.
2668+
* @public
2669+
*/
2670+
public getSharedWorkerManager(): ClerkSharedWorkerManager | undefined {
2671+
return this.#sharedWorkerManager;
2672+
}
2673+
2674+
/**
2675+
* Manually initializes the SharedWorker if not already initialized.
2676+
* Useful when autoStart is disabled.
2677+
* @public
2678+
*/
2679+
public async initializeSharedWorker(): Promise<SharedWorker | null> {
2680+
if (!this.#options.sharedWorker) {
2681+
logger.warnOnce('Clerk: No SharedWorker configuration provided');
2682+
return null;
2683+
}
2684+
2685+
if (this.#sharedWorkerManager?.isActive()) {
2686+
logger.logOnce('Clerk: SharedWorker is already initialized');
2687+
return this.#sharedWorkerManager.getWorker();
2688+
}
2689+
2690+
await this.#initializeSharedWorker();
2691+
return this.#sharedWorkerManager?.getWorker() || null;
2692+
}
2693+
2694+
/**
2695+
* Terminates the SharedWorker if active.
2696+
* @public
2697+
*/
2698+
public terminateSharedWorker(): void {
2699+
if (this.#sharedWorkerManager) {
2700+
this.#sharedWorkerManager.terminate();
2701+
this.#sharedWorkerManager = undefined;
2702+
logger.logOnce('Clerk: SharedWorker terminated');
2703+
}
2704+
}
2705+
2706+
/**
2707+
* Gets connection information for the current tab's SharedWorker.
2708+
* @public
2709+
*/
2710+
public getSharedWorkerConnectionInfo(): { tabId: string; instanceId: string; isActive: boolean } | null {
2711+
if (this.#sharedWorkerManager) {
2712+
return this.#sharedWorkerManager.getConnectionInfo();
2713+
}
2714+
return null;
2715+
}
2716+
2717+
/**
2718+
* Runs debugging diagnostics on the SharedWorker connection.
2719+
* Useful for troubleshooting SharedWorker issues.
2720+
* @public
2721+
*/
2722+
public debugSharedWorker(): void {
2723+
if (this.#sharedWorkerManager) {
2724+
this.#sharedWorkerManager.debug();
2725+
} else {
2726+
logger.warnOnce('Clerk: No SharedWorker manager available for debugging');
2727+
}
2728+
}
2729+
2730+
/**
2731+
* Sends a message to another tab via the SharedWorker.
2732+
* The receiving tab will console log the message.
2733+
* @param targetTabId - The ID of the tab to send the message to
2734+
* @param message - The message to send
2735+
* @public
2736+
*/
2737+
public sendTabMessage(targetTabId: string, message: any): void {
2738+
if (!this.#sharedWorkerManager) {
2739+
logger.warnOnce('Clerk: SharedWorker not initialized. Cannot send tab message.');
2740+
return;
2741+
}
2742+
2743+
if (!this.#sharedWorkerManager.isActive()) {
2744+
logger.warnOnce('Clerk: SharedWorker is not active. Cannot send tab message.');
2745+
return;
2746+
}
2747+
2748+
this.#sharedWorkerManager.sendTabMessage(targetTabId, message);
2749+
}
2750+
2751+
/**
2752+
* Gets the current tab ID for this Clerk instance.
2753+
* @returns The tab ID string or null if SharedWorker is not initialized
2754+
* @public
2755+
*/
2756+
public getTabId(): string | null {
2757+
if (!this.#sharedWorkerManager) {
2758+
return null;
2759+
}
2760+
2761+
return this.#sharedWorkerManager.getTabId();
2762+
}
2763+
2764+
/**
2765+
* Requests the status of all connected tabs from the SharedWorker.
2766+
* The response will be logged to the console.
2767+
* @public
2768+
*/
2769+
public getConnectedTabs(): void {
2770+
if (!this.#sharedWorkerManager) {
2771+
logger.warnOnce('Clerk: SharedWorker not initialized. Cannot get connected tabs.');
2772+
return;
2773+
}
2774+
2775+
if (!this.#sharedWorkerManager.isActive()) {
2776+
logger.warnOnce('Clerk: SharedWorker is not active. Cannot get connected tabs.');
2777+
return;
2778+
}
2779+
2780+
this.#sharedWorkerManager.getTabStatus();
2781+
}
2782+
25852783
assertComponentsReady(controls: unknown): asserts controls is ReturnType<MountComponentRenderer> {
25862784
if (!Clerk.mountComponentRenderer) {
25872785
throw new Error('ClerkJS was loaded without UI components.');
@@ -2655,4 +2853,81 @@ export class Clerk implements ClerkInterface {
26552853

26562854
return allowedProtocols;
26572855
}
2856+
2857+
/**
2858+
* Gets access to the SharedWorker manager for tab-to-tab communication.
2859+
* Provides methods like sendTabMessage, getTabId, getTabStatus, etc.
2860+
* @returns The SharedWorker manager instance or a temporary proxy if still initializing
2861+
* @public
2862+
*/
2863+
public get sharedWorker(): ClerkSharedWorkerManager | undefined {
2864+
// If the SharedWorker manager is ready, return it
2865+
if (this.#sharedWorkerManager) {
2866+
return this.#sharedWorkerManager;
2867+
}
2868+
2869+
// If SharedWorker is configured and initialization is in progress,
2870+
// return a temporary proxy with helpful methods
2871+
if (this.#options.sharedWorker) {
2872+
const proxy = {
2873+
getTabStatus: () => {
2874+
console.warn(
2875+
'[Clerk] SharedWorker is still initializing. Use window.Clerk.sharedWorker.getTabStatus() in a moment.',
2876+
);
2877+
return undefined;
2878+
},
2879+
ping: () => {
2880+
console.warn('[Clerk] SharedWorker is still initializing. Use window.Clerk.sharedWorker.ping() in a moment.');
2881+
},
2882+
sendTabMessage: (_targetTabId: string, _message: any) => {
2883+
console.warn('[Clerk] SharedWorker is still initializing. Cannot send tab message yet.');
2884+
},
2885+
getTabId: () => {
2886+
console.warn('[Clerk] SharedWorker is still initializing. Tab ID not available yet.');
2887+
return null;
2888+
},
2889+
getConnectionInfo: () => {
2890+
console.warn('[Clerk] SharedWorker is still initializing. Connection info not available yet.');
2891+
return null;
2892+
},
2893+
isActive: () => {
2894+
return false; // Not active yet during initialization
2895+
},
2896+
debug: () => {
2897+
console.log('[Clerk] SharedWorker Debug - Status: Initializing...');
2898+
console.log(' - SharedWorker configuration is present');
2899+
console.log(' - Auto-initialization is in progress');
2900+
console.log(' - Please wait a moment and try again');
2901+
},
2902+
getInitializationStatus: () => {
2903+
return {
2904+
isComplete: false,
2905+
isActive: false,
2906+
initializationTime: null,
2907+
tabId: null,
2908+
instanceId: `clerk-${this.publishableKey.slice(-8)}-pending`,
2909+
};
2910+
},
2911+
terminate: () => {
2912+
console.warn('[Clerk] SharedWorker is still initializing. Cannot terminate yet.');
2913+
},
2914+
getWorker: () => {
2915+
return null; // No worker available yet
2916+
},
2917+
testConnection: () => {
2918+
console.log('🔍 [Clerk] SharedWorker Test - Status: Initializing...');
2919+
console.log(' - Cannot test connection while initializing');
2920+
console.log(' - Please wait for initialization to complete');
2921+
},
2922+
} as any;
2923+
2924+
// Add a helpful property to indicate this is a temporary proxy
2925+
proxy._isInitializingProxy = true;
2926+
2927+
return proxy;
2928+
}
2929+
2930+
// No SharedWorker configuration found
2931+
return undefined;
2932+
}
26582933
}

0 commit comments

Comments
 (0)