@@ -2824,29 +2824,77 @@ class TransportSelector {
28242824 environment . supportsModules = 'noModule' in HTMLScriptElement . prototype ;
28252825 environment . isLocalhost = window . location . hostname === 'localhost' || window . location . hostname === '127.0.0.1' ;
28262826
2827+ // Enhanced HTML export detection
2828+ environment . isHtmlExport = this . _detectHtmlExport ( ) ;
2829+ environment . hasProjectFs = typeof fetch !== 'undefined' && this . _checkForProjectFs ( ) ;
2830+
28272831 return environment ;
28282832 }
28292833
2834+ static _detectHtmlExport ( ) {
2835+ try {
2836+ // Check for HTML export indicators
2837+ return (
2838+ // Check for explicit client type setting
2839+ window . __PRESWALD_CLIENT_TYPE === 'comlink' ||
2840+ // Check for project_fs.json presence (HTML export artifact)
2841+ document . querySelector ( 'script' ) . textContent ?. includes ( 'project_fs.json' ) ||
2842+ // Check for Pyodide-related scripts
2843+ document . querySelector ( 'script[src*="pyodide"]' ) ||
2844+ // Check for inline script setting client type
2845+ Array . from ( document . querySelectorAll ( 'script' ) ) . some ( script =>
2846+ script . textContent ?. includes ( 'window.__PRESWALD_CLIENT_TYPE' )
2847+ ) ||
2848+ // Check for static file serving pattern (no dynamic server endpoints)
2849+ window . location . protocol === 'file:' ||
2850+ // Check if we're being served from a static file server with typical HTML export structure
2851+ ( window . location . pathname . endsWith ( '.html' ) || window . location . pathname . endsWith ( '/' ) )
2852+ ) ;
2853+ } catch ( error ) {
2854+ console . warn ( '[TransportSelector] Error detecting HTML export environment:' , error ) ;
2855+ return false ;
2856+ }
2857+ }
2858+
2859+ static _checkForProjectFs ( ) {
2860+ // Quick check to see if project_fs.json is available (HTML export indicator)
2861+ try {
2862+ // Don't actually fetch, just check if we can construct the URL
2863+ const projectFsUrl = new URL ( 'project_fs.json' , window . location . origin + window . location . pathname ) ;
2864+ return projectFsUrl . href !== null ;
2865+ } catch {
2866+ return false ;
2867+ }
2868+ }
2869+
28302870 static selectForEnvironment ( environment , config ) {
2831- // Priority-based selection with fallbacks
2871+ // Priority 1: HTML export environment should always use Comlink
2872+ if ( environment . isHtmlExport && environment . hasWorkers && config . enableWorkers !== false ) {
2873+ console . log ( '[TransportSelector] HTML export environment detected, using Comlink transport' ) ;
2874+ return TransportType . COMLINK ;
2875+ }
2876+
2877+ // Priority 2: Embedded contexts (iframes) should use PostMessage
28322878 if ( environment . isEmbedded && environment . hasPostMessage ) {
28332879 return TransportType . POST_MESSAGE ;
28342880 }
28352881
2836- if ( environment . hasWebSocket && ! environment . isEmbedded ) {
2882+ // Priority 3: Regular web applications with server connectivity use WebSocket
2883+ if ( environment . hasWebSocket && ! environment . isEmbedded && ! environment . isHtmlExport ) {
28372884 return TransportType . WEBSOCKET ;
28382885 }
28392886
2887+ // Priority 4: Worker-based applications (development/testing)
28402888 if ( environment . hasWorkers && config . enableWorkers !== false ) {
28412889 return TransportType . COMLINK ;
28422890 }
28432891
2844- // Fallback to PostMessage if available
2892+ // Priority 5: Fallback to PostMessage if available
28452893 if ( environment . hasPostMessage ) {
28462894 return TransportType . POST_MESSAGE ;
28472895 }
28482896
2849- // Last resort fallback
2897+ // Last resort fallback (should rarely be reached)
28502898 console . warn ( '[TransportSelector] No optimal transport found, defaulting to WebSocket' ) ;
28512899 return TransportType . WEBSOCKET ;
28522900 }
@@ -3271,8 +3319,211 @@ const serverConfigPromise = initializeServerConfiguration();
32713319/**
32723320 * Create the default communication layer with intelligent server resolution
32733321 * This is the main export that most applications will use
3322+ *
3323+ * For HTML exports, we defer creation until the DOM is ready to ensure
3324+ * window.__PRESWALD_CLIENT_TYPE is set correctly.
32743325 */
3275- export const comm = createCommunicationLayer ( ) ;
3326+ let _globalCommInstance = null ;
3327+
3328+ function getOrCreateCommunicationLayer ( ) {
3329+ if ( _globalCommInstance ) {
3330+ return _globalCommInstance ;
3331+ }
3332+
3333+ try {
3334+ // Enhanced HTML export detection using multiple strategies
3335+ const isHtmlExport = detectHtmlExportEnvironment ( ) ;
3336+ const clientType = window . __PRESWALD_CLIENT_TYPE ;
3337+
3338+ console . log ( `[WebSocket Module] Environment analysis:` , {
3339+ isHtmlExport,
3340+ clientType,
3341+ pathname : window . location . pathname ,
3342+ protocol : window . location . protocol ,
3343+ port : window . location . port ,
3344+ hasProjectFsInUrl : window . location . href . includes ( 'project_fs.json' )
3345+ } ) ;
3346+
3347+ // Priority 1: Explicit client type 'comlink' always uses Comlink
3348+ if ( clientType === 'comlink' || clientType === TransportType . COMLINK ) {
3349+ console . log ( `[WebSocket Module] Using Comlink transport due to explicit client type: ${ clientType } ` ) ;
3350+ _globalCommInstance = createCommunicationLayer ( {
3351+ transport : TransportType . COMLINK ,
3352+ enableUrlParams : false ,
3353+ enableStorage : false
3354+ } ) ;
3355+ }
3356+ // Priority 2: Check if we have a server available (even in HTML export environment)
3357+ else if ( isHtmlExport ) {
3358+ console . log ( `[WebSocket Module] HTML export environment detected, checking server availability...` ) ;
3359+
3360+ // Check if we can reach a server (quick check)
3361+ const hasServerConnection = checkServerAvailability ( ) ;
3362+
3363+ if ( hasServerConnection ) {
3364+ console . log ( `[WebSocket Module] Server available in HTML export environment, using WebSocket transport` ) ;
3365+ _globalCommInstance = createCommunicationLayer ( {
3366+ transport : TransportType . WEBSOCKET
3367+ } ) ;
3368+ } else {
3369+ console . log ( `[WebSocket Module] No server available in HTML export environment, using Comlink transport` ) ;
3370+ _globalCommInstance = createCommunicationLayer ( {
3371+ transport : TransportType . COMLINK ,
3372+ enableUrlParams : false ,
3373+ enableStorage : false
3374+ } ) ;
3375+ }
3376+ }
3377+ // Priority 3: Other explicit client types
3378+ else if ( clientType && clientType !== TransportType . AUTO ) {
3379+ console . log ( `[WebSocket Module] Explicit client type specified: ${ clientType } ` ) ;
3380+ _globalCommInstance = createCommunicationLayer ( { transport : clientType } ) ;
3381+ }
3382+ // Priority 4: Default auto-detection
3383+ else {
3384+ console . log ( `[WebSocket Module] Using auto-detection for transport selection` ) ;
3385+ _globalCommInstance = createCommunicationLayer ( ) ;
3386+ }
3387+
3388+ return _globalCommInstance ;
3389+ } catch ( error ) {
3390+ console . error ( '[WebSocket Module] Error creating communication layer:' , error ) ;
3391+ // Fallback to basic creation
3392+ _globalCommInstance = createCommunicationLayer ( ) ;
3393+ return _globalCommInstance ;
3394+ }
3395+ }
3396+
3397+ function checkServerAvailability ( ) {
3398+ try {
3399+ // Quick synchronous check to see if we're in a server environment
3400+ // Look for indicators that suggest a server is available
3401+
3402+ // 1. Check if we're on a known server port that's responding
3403+ const currentPort = window . location . port ;
3404+ const isServerPort = [ '8501' , '8000' , '3000' , '5000' ] . includes ( currentPort ) ;
3405+
3406+ // 2. Check if localStorage has a server URL (indicates previous server connection)
3407+ const storedServerUrl = localStorage . getItem ( 'preswald_server_url' ) ;
3408+
3409+ // 3. Check if we can access server-specific endpoints (quick check)
3410+ const hasServerEndpoints = window . location . pathname . includes ( '/api/' ) ||
3411+ window . location . pathname . includes ( '/ws/' ) ||
3412+ window . location . search . includes ( 'server=' ) ;
3413+
3414+ // 4. Check if project_fs.json is NOT available locally (indicates server environment)
3415+ const hasLocalProjectFs = checkForProjectFsSync ( ) ;
3416+
3417+ console . log ( `[WebSocket Module] Server availability check:` , {
3418+ isServerPort,
3419+ storedServerUrl : ! ! storedServerUrl ,
3420+ hasServerEndpoints,
3421+ hasLocalProjectFs
3422+ } ) ;
3423+
3424+ // If we have server indicators and no local project_fs.json, likely server environment
3425+ const hasServerConnection = ( isServerPort || storedServerUrl || hasServerEndpoints ) && ! hasLocalProjectFs ;
3426+
3427+ return hasServerConnection ;
3428+ } catch ( error ) {
3429+ console . warn ( '[WebSocket Module] Error checking server availability:' , error ) ;
3430+ return false ;
3431+ }
3432+ }
3433+
3434+ function detectHtmlExportEnvironment ( ) {
3435+ try {
3436+ // Primary detection: Check for explicit client type first
3437+ if ( window . __PRESWALD_CLIENT_TYPE === 'comlink' ) {
3438+ console . log ( '[WebSocket Module] HTML export detected via explicit client type: comlink' ) ;
3439+ return true ;
3440+ }
3441+
3442+ // Secondary detection: Check for project_fs.json existence (synchronous check)
3443+ const hasProjectFs = checkForProjectFsSync ( ) ;
3444+ if ( hasProjectFs ) {
3445+ console . log ( '[WebSocket Module] HTML export detected via project_fs.json presence' ) ;
3446+ return true ;
3447+ }
3448+
3449+ // Tertiary detection: Multiple indicators approach
3450+ const indicators = [
3451+ // 1. Check for typical HTML export URL patterns
3452+ window . location . pathname . endsWith ( '.html' ) ||
3453+ window . location . pathname . endsWith ( '/' ) ||
3454+ window . location . protocol === 'file:' ,
3455+
3456+ // 2. Check for HTML export script indicators in head
3457+ Array . from ( document . head ?. querySelectorAll ( 'script' ) || [ ] ) . some ( script =>
3458+ script . textContent ?. includes ( 'window.__PRESWALD_CLIENT_TYPE' ) ||
3459+ script . textContent ?. includes ( 'project_fs.json' )
3460+ ) ,
3461+
3462+ // 3. Check for absence of typical server endpoints
3463+ ! window . location . pathname . includes ( '/api/' ) &&
3464+ ! window . location . pathname . includes ( '/ws/' ) &&
3465+ ! window . location . search . includes ( 'server=' ) ,
3466+
3467+ // 4. Check if running from static file server (common HTML export pattern)
3468+ window . location . port && [ '8080' , '8081' , '3000' , '5000' ] . includes ( window . location . port ) &&
3469+ ! window . location . pathname . includes ( 'dev' ) ,
3470+
3471+ // 5. Check for Pyodide or worker-related assets
3472+ Array . from ( document . querySelectorAll ( 'script[src]' ) ) . some ( script =>
3473+ script . src . includes ( 'pyodide' ) || script . src . includes ( 'worker' )
3474+ )
3475+ ] ;
3476+
3477+ // Consider it an HTML export if multiple indicators are present
3478+ const positiveIndicators = indicators . filter ( Boolean ) . length ;
3479+ const isHtmlExport = positiveIndicators >= 2 ;
3480+
3481+ if ( isHtmlExport ) {
3482+ console . log ( `[WebSocket Module] HTML export detected with ${ positiveIndicators } indicators:` , {
3483+ clientType : window . __PRESWALD_CLIENT_TYPE ,
3484+ pathname : window . location . pathname ,
3485+ protocol : window . location . protocol ,
3486+ port : window . location . port ,
3487+ hasProjectFsScript : indicators [ 1 ] ,
3488+ noServerEndpoints : indicators [ 2 ] ,
3489+ staticFileServer : indicators [ 3 ] ,
3490+ hasWorkerAssets : indicators [ 4 ]
3491+ } ) ;
3492+ }
3493+
3494+ return isHtmlExport ;
3495+ } catch ( error ) {
3496+ console . warn ( '[WebSocket Module] Error detecting HTML export environment:' , error ) ;
3497+ return false ;
3498+ }
3499+ }
3500+
3501+ function checkForProjectFsSync ( ) {
3502+ try {
3503+ // Check if project_fs.json is accessible (this is the most reliable indicator)
3504+ const xhr = new XMLHttpRequest ( ) ;
3505+ xhr . open ( 'HEAD' , './project_fs.json' , false ) ; // Synchronous request
3506+ xhr . send ( ) ;
3507+ return xhr . status === 200 ;
3508+ } catch ( error ) {
3509+ // If we get a CORS error or network error, we might still be in HTML export
3510+ // but served from a different origin. Check for other indicators.
3511+ return false ;
3512+ }
3513+ }
3514+
3515+ export const comm = new Proxy ( { } , {
3516+ get ( target , prop ) {
3517+ const instance = getOrCreateCommunicationLayer ( ) ;
3518+ const value = instance [ prop ] ;
3519+ return typeof value === 'function' ? value . bind ( instance ) : value ;
3520+ } ,
3521+ set ( target , prop , value ) {
3522+ const instance = getOrCreateCommunicationLayer ( ) ;
3523+ instance [ prop ] = value ;
3524+ return true ;
3525+ }
3526+ } ) ;
32763527
32773528/**
32783529 * Create a communication layer with explicit server URL configuration
0 commit comments