11import { URL } from 'url-ponyfill'
22import { Cors } from './Cors.js'
33import { Flag } from './Flag.js'
4+ import { Hotlink } from './Hotlink.js'
45import { IPNSCheck } from './Ipns.js'
56import { Log } from './Log.js'
67import { Origin } from './Origin.js'
78import { Status } from './Status.js'
89import { Trustless } from './Trustless.js'
910import { UiComponent } from './UiComponent.js'
10- import { HASH_TO_TEST } from './constants.js'
11+ import { HASH_TO_TEST , SLOW_TESTS_DELAY } from './constants.js'
12+ import { detectCrossDomainRedirect } from './detectCrossDomainRedirect.js'
1113import { gatewayHostname } from './gatewayHostname.js'
1214import type { Results } from './Results.js'
15+ import type { RedirectDetectionResult } from './detectCrossDomainRedirect.js'
1316
1417const log = new Log ( 'GatewayNode' )
1518
1619class GatewayNode extends UiComponent /* implements Checkable */ {
1720 // tag: Tag
1821 status : Status
22+ hotlink : Hotlink
1923 cors : Cors
2024 ipns : IPNSCheck
2125 origin : Origin
@@ -28,6 +32,9 @@ class GatewayNode extends UiComponent /* implements Checkable */ {
2832 checkingTime : number
2933
3034 atLeastOneSuccess = false
35+ crossDomainRedirect : string | null = null
36+ // Shared redirect detection result (run once, used by all tests)
37+ redirectDetection : RedirectDetectionResult = { confirmedTarget : null , possibleRedirect : false , errorStatus : null }
3138
3239 constructor ( readonly parent : Results , gateway : string , index : unknown ) {
3340 super ( parent , 'div' , 'Node' )
@@ -39,6 +46,9 @@ class GatewayNode extends UiComponent /* implements Checkable */ {
3946 this . status = new Status ( this )
4047 this . tag . append ( this . status . tag )
4148
49+ this . hotlink = new Hotlink ( this )
50+ this . tag . append ( this . hotlink . tag )
51+
4252 this . cors = new Cors ( this )
4353 this . tag . append ( this . cors . tag )
4454
@@ -73,42 +83,54 @@ class GatewayNode extends UiComponent /* implements Checkable */ {
7383
7484 public async check ( ) : Promise < void > {
7585 this . checkingTime = Date . now ( )
76- // const onFailedCheck = () => { this.status.down = true }
77- // const onSuccessfulCheck = () => { this.status.up = true }
86+ const onSuccess = this . onSuccessfulCheck . bind ( this )
87+
7888 void this . flag . check ( ) . then ( ( ) => { log . debug ( this . gateway , 'Flag success' ) } )
79- const onlineChecks = [
80- // this.flag.check().then(() => log.debug(this.gateway, 'Flag success')),
81- this . status . check ( ) . then ( ( ) => { log . debug ( this . gateway , 'Status success' ) } ) . then ( this . onSuccessfulCheck . bind ( this ) ) ,
82- this . cors . check ( ) . then ( ( ) => { log . debug ( this . gateway , 'CORS success' ) } ) . then ( this . onSuccessfulCheck . bind ( this ) ) ,
83- this . ipns . check ( ) . then ( ( ) => { log . debug ( this . gateway , 'IPNS success' ) } ) . then ( this . onSuccessfulCheck . bind ( this ) ) ,
84- this . origin . check ( ) . then ( ( ) => { log . debug ( this . gateway , 'Origin success' ) } ) . then ( this . onSuccessfulCheck . bind ( this ) ) ,
85- this . trustless . check ( ) . then (
86- ( ) => { log . debug ( this . gateway , 'Trustless success' ) } ) . then ( this . onSuccessfulCheck . bind ( this ) )
87- ]
88-
89- // we care only about the fastest method to return a success
90- // Promise.race(onlineChecks).catch((err) => {
91- // log.error('Promise race error', err)
92- // })
93-
94- // await Promise.all(onlineChecks).catch(onFailedCheck)
95- await Promise . allSettled ( onlineChecks ) . then ( ( results ) => results . map ( ( result ) => {
96- return result . status
97- } ) ) . then ( ( statusArray ) => {
98- if ( statusArray . includes ( 'fulfilled' ) ) {
99- // At least promise was successful, which means the gateway is online
100- log . debug ( `For gateway '${ this . gateway } ', at least one promise was successfully fulfilled` )
101- log . debug ( this . gateway , 'this.status.up: ' , this . status . up )
102- log . debug ( this . gateway , 'this.status.down: ' , this . status . down )
103- this . status . up = true
104- log . debug ( this . gateway , 'this.status.up: ' , this . status . up )
105- log . debug ( this . gateway , 'this.status.down: ' , this . status . down )
106- } else {
107- // No promise was successful, the gateway is down.
108- this . status . down = true
109- log . debug ( `For gateway '${ this . gateway } ', all promises were rejected` )
110- }
89+
90+ // Start Hotlink immediately (fastest, lightest test - browser-native img loading)
91+ const hotlinkPromise = this . hotlink . check ( )
92+ . then ( ( ) => { log . debug ( this . gateway , 'Hotlink success' ) } )
93+ . then ( onSuccess )
94+ . catch ( ( ) => { log . debug ( this . gateway , 'Hotlink failed' ) } )
95+
96+ // Delay other tests to give Hotlink exclusive network access
97+ const delayedTestsPromise = new Promise < void > ( ( resolve ) => {
98+ setTimeout ( ( ) => {
99+ // Run redirect detection once, share result with all tests
100+ const testUrl = `${ this . gateway } /ipfs/${ HASH_TO_TEST } ?now=${ Date . now ( ) } #x-ipfs-companion-no-redirect`
101+ void detectCrossDomainRedirect ( testUrl , this . gateway ) . then ( ( result ) => {
102+ this . redirectDetection = result
103+ if ( result . confirmedTarget != null ) {
104+ this . crossDomainRedirect = result . confirmedTarget
105+ }
106+ // Now start all tests (they use this.redirectDetection)
107+ const otherTests = [
108+ this . cors . check ( )
109+ . then ( ( ) => { log . debug ( this . gateway , 'CORS success' ) } )
110+ . then ( onSuccess ) ,
111+ this . ipns . check ( )
112+ . then ( ( ) => { log . debug ( this . gateway , 'IPNS success' ) } )
113+ . then ( onSuccess ) ,
114+ this . origin . check ( )
115+ . then ( ( ) => { log . debug ( this . gateway , 'Origin success' ) } )
116+ . then ( onSuccess ) ,
117+ this . trustless . check ( )
118+ . then ( ( ) => { log . debug ( this . gateway , 'Trustless success' ) } )
119+ . then ( onSuccess )
120+ ]
121+ void Promise . allSettled ( otherTests ) . then ( ( ) => { resolve ( ) } )
122+ } )
123+ } , SLOW_TESTS_DELAY )
111124 } )
125+
126+ // Wait for both Hotlink and delayed tests to complete
127+ await Promise . allSettled ( [ hotlinkPromise , delayedTestsPromise ] )
128+
129+ // Set status.down only if ALL tests failed
130+ if ( ! this . atLeastOneSuccess ) {
131+ this . status . down = true
132+ log . debug ( `Gateway '${ this . gateway } ' - all tests failed` )
133+ }
112134 }
113135
114136 private onSuccessfulCheck ( ) : void {
@@ -119,21 +141,18 @@ class GatewayNode extends UiComponent /* implements Checkable */ {
119141 const url = this . link . url
120142 if ( url != null ) {
121143 const host = gatewayHostname ( url )
122- // const anchor = document.createElement('a')
123- // anchor.title = host
124- // anchor.href = `${url.toString()}#x-ipfs-companion-no-redirect`
125- // anchor.target = '_blank'
126- // anchor.textContent = host
127- // this.flag.tag.element.remove()
128- // this.link.textContent = ''
129- // this.link.append(this.flag.tag.element, anchor)
130144 this . link . innerHTML = `<a title="${ host } " href="${ url . toString ( ) } #x-ipfs-companion-no-redirect" target="_blank">${ host } </a>`
131145 }
132146 const ms = Date . now ( ) - this . checkingTime
133147 this . tag . style . order = ms . toString ( )
134148 const s = ( ms / 1000 ) . toFixed ( 2 )
135149 this . took . textContent = `${ s } s`
136150 }
151+ // Update status if a later test detected a redirect
152+ // (e.g., Hotlink succeeded first, then CORS detected redirect)
153+ if ( this . crossDomainRedirect != null ) {
154+ this . status . updateForRedirect ( )
155+ }
137156 }
138157
139158 // private checked () {
0 commit comments