@@ -16,9 +16,68 @@ export interface ConnectivityCheckResult {
1616 message : string ;
1717}
1818
19+ /**
20+ * Test a single endpoint and return the result
21+ */
22+ async function testEndpoint (
23+ url : string ,
24+ description : string ,
25+ ) : Promise < EndpointResult > {
26+ const startTime = Date . now ( ) ;
27+ try {
28+ const controller = new AbortController ( ) ;
29+ const timeoutId = setTimeout ( ( ) => controller . abort ( ) , 3000 ) ;
30+
31+ const response = await fetch ( url , {
32+ method : 'HEAD' ,
33+ signal : controller . signal ,
34+ redirect : 'manual' ,
35+ } ) ;
36+
37+ clearTimeout ( timeoutId ) ;
38+ const latencyMs = Date . now ( ) - startTime ;
39+
40+ return {
41+ endpoint : `${ description } (${ url } )` ,
42+ success : true ,
43+ statusCode : response . status ,
44+ latencyMs,
45+ } ;
46+ } catch ( error ) {
47+ const latencyMs = Date . now ( ) - startTime ;
48+ let errorMessage = 'Unknown error' ;
49+
50+ if ( error instanceof Error ) {
51+ if ( error . name === 'AbortError' ) {
52+ errorMessage = 'Request timeout (>3s)' ;
53+ } else if ( error . message . includes ( 'fetch failed' ) ) {
54+ errorMessage = 'Network request failed (DNS/connection error)' ;
55+ } else if ( error . message . includes ( 'ENOTFOUND' ) ) {
56+ errorMessage = 'DNS resolution failed' ;
57+ } else if ( error . message . includes ( 'ECONNREFUSED' ) ) {
58+ errorMessage = 'Connection refused' ;
59+ } else if ( error . message . includes ( 'ETIMEDOUT' ) ) {
60+ errorMessage = 'Connection timeout' ;
61+ } else if ( error . message . includes ( 'ENETUNREACH' ) ) {
62+ errorMessage = 'Network unreachable' ;
63+ } else {
64+ errorMessage = error . message ;
65+ }
66+ }
67+
68+ return {
69+ endpoint : `${ description } (${ url } )` ,
70+ success : false ,
71+ error : errorMessage ,
72+ latencyMs,
73+ } ;
74+ }
75+ }
76+
1977/**
2078 * Check if the system has internet connectivity by testing against
21- * multiple reliable third-party endpoints with detailed diagnostics.
79+ * multiple reliable third-party endpoints in parallel.
80+ * Returns as soon as one endpoint succeeds, reducing latency significantly.
2281 */
2382export async function checkInternetConnectivity ( ) : Promise < ConnectivityCheckResult > {
2483 const testEndpoints = [
@@ -30,79 +89,40 @@ export async function checkInternetConnectivity(): Promise<ConnectivityCheckResu
3089 { url : 'https://1.1.1.1/' , description : 'Cloudflare DNS' } ,
3190 ] ;
3291
33- const endpointResults : EndpointResult [ ] = [ ] ;
34- let anySuccess = false ;
35-
36- for ( const { url, description } of testEndpoints ) {
37- const startTime = Date . now ( ) ;
38- try {
39- const controller = new AbortController ( ) ;
40- const timeoutId = setTimeout ( ( ) => controller . abort ( ) , 3000 ) ;
41-
42- const response = await fetch ( url , {
43- method : 'HEAD' ,
44- signal : controller . signal ,
45- redirect : 'manual' ,
46- } ) ;
47-
48- clearTimeout ( timeoutId ) ;
49- const latencyMs = Date . now ( ) - startTime ;
50-
51- if ( response ) {
52- anySuccess = true ;
53- endpointResults . push ( {
54- endpoint : `${ description } (${ url } )` ,
55- success : true ,
56- statusCode : response . status ,
57- latencyMs,
58- } ) ;
59- break ;
60- }
61- } catch ( error ) {
62- const latencyMs = Date . now ( ) - startTime ;
63- let errorMessage = 'Unknown error' ;
64-
65- if ( error instanceof Error ) {
66- if ( error . name === 'AbortError' ) {
67- errorMessage = 'Request timeout (>3s)' ;
68- } else if ( error . message . includes ( 'fetch failed' ) ) {
69- errorMessage = 'Network request failed (DNS/connection error)' ;
70- } else if ( error . message . includes ( 'ENOTFOUND' ) ) {
71- errorMessage = 'DNS resolution failed' ;
72- } else if ( error . message . includes ( 'ECONNREFUSED' ) ) {
73- errorMessage = 'Connection refused' ;
74- } else if ( error . message . includes ( 'ETIMEDOUT' ) ) {
75- errorMessage = 'Connection timeout' ;
76- } else if ( error . message . includes ( 'ENETUNREACH' ) ) {
77- errorMessage = 'Network unreachable' ;
78- } else {
79- errorMessage = error . message ;
80- }
92+ // Test all endpoints in parallel
93+ const endpointPromises = testEndpoints . map ( ( { url, description } ) =>
94+ testEndpoint ( url , description ) ,
95+ ) ;
96+
97+ // Use Promise.any to return on first success, or collect all failures
98+ try {
99+ // Create promises that only resolve on success
100+ const successPromises = endpointPromises . map ( async ( promise ) => {
101+ const result = await promise ;
102+ if ( result . success ) {
103+ return result ;
81104 }
105+ throw result ; // Throw failures so Promise.any continues to next
106+ } ) ;
82107
83- endpointResults . push ( {
84- endpoint : `${ description } (${ url } )` ,
85- success : false ,
86- error : errorMessage ,
87- latencyMs,
88- } ) ;
89- }
90- }
108+ const successResult = await Promise . any ( successPromises ) ;
91109
92- let message : string ;
93- if ( anySuccess ) {
94- const successfulEndpoint = endpointResults . find ( ( r ) => r . success ) ;
95- message = `Internet connectivity verified via ${ successfulEndpoint ?. endpoint } (${ successfulEndpoint ?. latencyMs } ms)` ;
96- } else {
110+ return {
111+ connected : true ,
112+ endpointResults : [ successResult ] ,
113+ message : `Internet connectivity verified via ${ successResult . endpoint } (${ successResult . latencyMs } ms)` ,
114+ } ;
115+ } catch ( aggregateError ) {
116+ // All endpoints failed - collect all results
117+ const endpointResults = await Promise . all ( endpointPromises ) ;
97118 const testedEndpoints = endpointResults . map ( ( r ) => r . endpoint ) . join ( ', ' ) ;
98- message = `No internet connectivity detected. Tested endpoints: ${ testedEndpoints } ` ;
99- }
100119
101- return {
102- connected : anySuccess ,
103- endpointResults,
104- message,
105- } ;
120+ return {
121+ connected : false ,
122+ endpointResults,
123+ message : `No internet connectivity detected. Tested endpoints: ${ testedEndpoints } ` ,
124+ } ;
125+ }
106126}
107127
108128/**
0 commit comments