Skip to content

Commit f573928

Browse files
committed
Fix chrome lna
1 parent b30984f commit f573928

2 files changed

Lines changed: 50 additions & 5 deletions

File tree

src/bridge/connection.ts

Lines changed: 44 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,57 @@
88
*/
99

1010
import type { Protocol } from './utils';
11-
import { fetchWithTimeout, resolveBaseUrl } from './utils';
11+
import { fetchWithTimeout, isLocalNetworkHost, resolveBaseUrl } from './utils';
12+
13+
/**
14+
* Pre-request Chrome's Local Network Access (LNA) permission for a host.
15+
*
16+
* When `https://web.eca.dev` fetches a private IP, Chrome gates the
17+
* request behind a user permission prompt. This function triggers that
18+
* prompt **once** before port scanning so that all subsequent probes
19+
* succeed without blocking on user interaction.
20+
*
21+
* For non-local hosts this is a no-op.
22+
*
23+
* @returns true if the host is non-local or the LNA permission was granted.
24+
*/
25+
export async function requestLocalNetworkAccess(
26+
host: string,
27+
protocol: Protocol = 'http',
28+
): Promise<boolean> {
29+
if (!isLocalNetworkHost(host)) return true;
30+
31+
try {
32+
// Fire a single throwaway fetch to trigger the LNA prompt.
33+
// We use port 7777 (first discovery port) — the server may or may
34+
// not be there, but the prompt still fires for the hostname.
35+
// 30s timeout: user needs time to read and click "Allow".
36+
await fetchWithTimeout(
37+
`${protocol}://${host}:7777/api/v1/health`,
38+
undefined,
39+
30_000,
40+
);
41+
return true;
42+
} catch {
43+
// Even if this fetch fails (e.g. nothing on port 7777), the LNA
44+
// permission may still have been granted for the origin — Chrome
45+
// remembers the grant regardless of the HTTP outcome.
46+
return true;
47+
}
48+
}
1249

1350
/**
1451
* Lightweight probe to check if an ECA server is listening on a given port.
1552
*
1653
* Used by auto-discovery to quickly scan a port range without requiring
17-
* full authentication. Uses `mode: 'no-cors'` to bypass CORS preflight
18-
* issues — we only need to know "is something responding on /health?".
54+
* full authentication.
1955
*
2056
* Tries both HTTP and HTTPS in parallel to handle protocol mismatches
2157
* (e.g. user selected HTTPS but server runs HTTP, or vice-versa).
58+
*
59+
* NOTE: Call {@link requestLocalNetworkAccess} once before scanning
60+
* so that Chrome's LNA permission is already granted and these fast
61+
* probes aren't blocked by the permission prompt.
2262
*/
2363
export async function probePort(
2464
host: string,
@@ -31,7 +71,7 @@ export async function probePort(
3171
const results = await Promise.allSettled(
3272
protocols.map(async (proto) => {
3373
const url = `${proto}://${host}:${port}/api/v1/health`;
34-
await fetchWithTimeout(url, { mode: 'no-cors' }, 3_000);
74+
await fetchWithTimeout(url, undefined, 3_000);
3575
}),
3676
);
3777
return results.some((r) => r.status === 'fulfilled');

src/pages/RemoteProduct.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
*/
1212

1313
import { useCallback, useEffect, useRef, useState } from 'react';
14-
import { probePort, testConnection } from '../bridge/connection';
14+
import { probePort, requestLocalNetworkAccess, testConnection } from '../bridge/connection';
1515
import type { WebBridge } from '../bridge/transport';
1616
import type { ChatEntry, WorkspaceFolder } from '../bridge/types';
1717
import type { Protocol } from '../bridge/utils';
@@ -137,6 +137,11 @@ export function RemoteProduct() {
137137
const ports: number[] = [];
138138
for (let p = DISCOVERY_PORT_START; p <= DISCOVERY_PORT_END; p++) ports.push(p);
139139

140+
// On local networks, trigger Chrome's LNA permission prompt before
141+
// scanning so the user can grant access without racing the 3s probe timeouts.
142+
await requestLocalNetworkAccess(host, protocol);
143+
if (abort.signal.aborted) return;
144+
140145
const progress: DiscoveryProgress = { total: ports.length, checked: 0, found: [] };
141146
setDiscovery({ ...progress });
142147

0 commit comments

Comments
 (0)