77 * SSE connection.
88 */
99
10- import type { Protocol } from './utils' ;
11- import { fetchWithTimeout , isLocalNetworkHost , resolveBaseUrl , resolveProtocol } from './utils' ;
10+ import type { BrowserKind , Protocol } from './utils' ;
11+ import { detectBrowser , fetchWithTimeout , isLocalNetworkHost , resolveBaseUrl , resolveProtocol } from './utils' ;
1212
1313/**
1414 * Lightweight probe to check if an ECA server is listening on a given port.
@@ -38,15 +38,63 @@ export async function probePort(
3838}
3939
4040/** True when the page is served over HTTPS and the target is plain HTTP on a private IP. */
41- function isMixedContentScenario ( host : string , protocol ?: Protocol ) : boolean {
41+ export function isMixedContentScenario ( host : string , protocol ?: Protocol ) : boolean {
4242 return globalThis . location ?. protocol === 'https:'
4343 && resolveProtocol ( host , protocol ) === 'http'
4444 && isLocalNetworkHost ( host ) ;
4545}
4646
47- const MIXED_CONTENT_HINT =
48- 'Your browser may be blocking this request (HTTPS → HTTP on a private network). '
49- + 'Check that you\'ve allowed Local Network Access for this site in your browser settings.' ;
47+ /**
48+ * Return a browser-specific explanation for mixed-content failures.
49+ *
50+ * Chrome's `targetAddressSpace` triggers its own Local Network Access
51+ * prompt, so it gets a short nudge. Firefox and Safari have **no**
52+ * programmatic escape hatch — the only realistic options are switching
53+ * to a Chromium-based browser or loading the page over plain HTTP.
54+ */
55+ function mixedContentHintFor ( browser : BrowserKind ) : string {
56+ switch ( browser ) {
57+ case 'chrome' :
58+ return 'Allow the Local Network Access prompt in your browser, then retry.' ;
59+ case 'firefox' :
60+ return 'Firefox blocks HTTPS pages from connecting to private HTTP servers. '
61+ + 'Use a Chromium-based browser (Chrome, Edge, Brave) or access this page over HTTP instead.' ;
62+ case 'safari' :
63+ return 'Safari blocks HTTPS pages from connecting to private HTTP servers. '
64+ + 'Use a Chromium-based browser (Chrome, Edge, Brave) or access this page over HTTP instead.' ;
65+ default :
66+ return 'Your browser may be blocking this request (HTTPS → HTTP on a private network). '
67+ + 'Try using a Chromium-based browser (Chrome, Edge, Brave) or access this page over HTTP instead.' ;
68+ }
69+ }
70+
71+ /**
72+ * Proactive mixed-content warning for the ConnectForm.
73+ *
74+ * Returns a user-facing hint string when the host/protocol combination
75+ * will trigger mixed-content blocking, or `null` when no warning is
76+ * needed (page served over HTTP, target is public, or Chrome which
77+ * handles it via the LNA prompt automatically).
78+ */
79+ export function getMixedContentWarning ( host : string , protocol ?: Protocol ) : string | null {
80+ if ( ! isMixedContentScenario ( host , protocol ) ) return null ;
81+ const browser = detectBrowser ( ) ;
82+ // Chrome handles this via targetAddressSpace + LNA prompt — no warning needed
83+ if ( browser === 'chrome' ) return null ;
84+ return mixedContentHintFor ( browser ) ;
85+ }
86+
87+ /**
88+ * Check whether a connection error is likely caused by mixed-content
89+ * blocking and return a helpful hint, or `null` if unrelated.
90+ *
91+ * Used by RemoteSession to decorate post-connect errors (e.g. Safari's
92+ * `TypeError` when the SSE fetch is silently blocked).
93+ */
94+ export function getMixedContentErrorHint ( host : string , protocol ?: Protocol ) : string | null {
95+ if ( ! isMixedContentScenario ( host , protocol ) ) return null ;
96+ return mixedContentHintFor ( detectBrowser ( ) ) ;
97+ }
5098
5199/**
52100 * Test whether a host is reachable and the password is valid.
@@ -58,7 +106,7 @@ const MIXED_CONTENT_HINT =
58106 */
59107export async function testConnection ( host : string , password : string , protocol ?: Protocol ) : Promise < string | null > {
60108 const baseUrl = resolveBaseUrl ( host , protocol ) ;
61- const mixedContent = isMixedContentScenario ( host , protocol ) ;
109+ const mixedContentHint = getMixedContentErrorHint ( host , protocol ) ;
62110
63111 // 1. Test host reachability (health endpoint — no auth)
64112 try {
@@ -70,12 +118,12 @@ export async function testConnection(host: string, password: string, protocol?:
70118 }
71119 } catch ( err : any ) {
72120 if ( err . name === 'AbortError' ) {
73- return mixedContent
74- ? `Connection timed out. ${ MIXED_CONTENT_HINT } `
121+ return mixedContentHint
122+ ? `Connection timed out. ${ mixedContentHint } `
75123 : 'Connection timed out. Check the address and try again.' ;
76124 }
77- return mixedContent
78- ? `Could not reach host. ${ MIXED_CONTENT_HINT } `
125+ return mixedContentHint
126+ ? `Could not reach host. ${ mixedContentHint } `
79127 : 'Could not reach host. Check the address and try again.' ;
80128 }
81129
0 commit comments