@@ -114,6 +114,8 @@ import { assertNoLegacyProp } from '../utils/assertNoLegacyProp';
114
114
import { CLERK_ENVIRONMENT_STORAGE_ENTRY , SafeLocalStorage } from '../utils/localStorage' ;
115
115
import { memoizeListenerCallback } from '../utils/memoizeStateListenerCallback' ;
116
116
import { RedirectUrls } from '../utils/redirectUrls' ;
117
+ import { ClerkSharedWorkerManager } from '../utils/sharedWorker' ;
118
+ import type { SharedWorkerOptions } from '../utils/sharedWorkerUtils' ;
117
119
import { AuthCookieService } from './auth/AuthCookieService' ;
118
120
import { CaptchaHeartbeat } from './auth/CaptchaHeartbeat' ;
119
121
import { CLERK_SATELLITE_URL , CLERK_SUFFIXED_COOKIES , CLERK_SYNCED , ERROR_CODES } from './constants' ;
@@ -218,6 +220,7 @@ export class Clerk implements ClerkInterface {
218
220
#touchThrottledUntil = 0 ;
219
221
#componentNavigationContext: __internal_ComponentNavigationContext | null = null ;
220
222
#publicEventBus = createClerkEventBus ( ) ;
223
+ #sharedWorkerManager?: ClerkSharedWorkerManager ;
221
224
222
225
public __internal_getCachedResources :
223
226
| ( ( ) => Promise < { client : ClientJSONSnapshot | null ; environment : EnvironmentJSONSnapshot | null } > )
@@ -2360,6 +2363,9 @@ export class Clerk implements ClerkInterface {
2360
2363
this . #handleImpersonationFab( ) ;
2361
2364
this . #handleKeylessPrompt( ) ;
2362
2365
2366
+ // Initialize SharedWorker if configured
2367
+ void this . #initializeSharedWorker( ) ;
2368
+
2363
2369
this . #publicEventBus. emit ( clerkEvents . Status , initializationDegradedCounter > 0 ? 'degraded' : 'ready' ) ;
2364
2370
} ;
2365
2371
@@ -2397,6 +2403,9 @@ export class Clerk implements ClerkInterface {
2397
2403
this . #componentControls = Clerk . mountComponentRenderer ( this , this . environment , this . #options) ;
2398
2404
}
2399
2405
2406
+ // Initialize SharedWorker if configured
2407
+ void this . #initializeSharedWorker( ) ;
2408
+
2400
2409
this . #publicEventBus. emit ( clerkEvents . Status , initializationDegradedCounter > 0 ? 'degraded' : 'ready' ) ;
2401
2410
} ;
2402
2411
@@ -2582,6 +2591,195 @@ export class Clerk implements ClerkInterface {
2582
2591
return this . buildUrlWithAuth ( url ) ;
2583
2592
} ;
2584
2593
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
+
2585
2783
assertComponentsReady ( controls : unknown ) : asserts controls is ReturnType < MountComponentRenderer > {
2586
2784
if ( ! Clerk . mountComponentRenderer ) {
2587
2785
throw new Error ( 'ClerkJS was loaded without UI components.' ) ;
@@ -2655,4 +2853,81 @@ export class Clerk implements ClerkInterface {
2655
2853
2656
2854
return allowedProtocols ;
2657
2855
}
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
+ }
2658
2933
}
0 commit comments