import type { Options } from './abstract' ; import { ClientRouter } from './client' ; import type { Navigation , HookName } from '../types' ; import { logger } from '../logger' ; import { RouteTree } from '../tree/tree' ; const DELAY_CHECK_INTERVAL = 50 ; export class Router extends ClientRouter { protected delayedNavigation : Navigation ; protected delayedPromise : Promise < void > ; protected delayedResolve : ( ) => void ; protected delayedReject : ( error : Error ) => void ; constructor ( options ) { super ( options ) ; this . tree = new RouteTree ( options . routes ) ; this . history . setTree ( this . tree ) ; } async rehydrate ( navigation ) { return this . resolveIfDelayFound ( super . rehydrate ( navigation ) ) ; } async start ( ) { await super . start ( ) ; if ( this . delayedNavigation ) { const { delayedNavigation } = this ; this . delayedNavigation = null ; return this . flattenDelayedNavigation ( delayedNavigation ) ; } } protected async run ( navigation ) { if ( ! this . started ) { this . delayNavigation ( navigation ) ; return ; } if ( this . currentNavigation ) { return this . delayNavigation ( navigation ) ; } return this . flattenDelayedNavigation ( navigation ) ; } protected delayNavigation ( navigation ) { this . delayedNavigation = navigation ; logger . info ( { event : 'delay-navigation' , navigation , } ) ; if ( this . delayedPromise ) { return this . delayedPromise ; } this . delayedPromise = new Promise ( ( resolve , reject ) => { this . delayedResolve = resolve ; this . delayedReject = reject ; } ) ; return this . delayedPromise ; } protected commitNavigation ( navigation ) { if ( navigation . cancelled ) { logger . debug ( { event : 'delay-ignore-commit' , navigation , } ) ; return ; } return super . commitNavigation ( navigation ) ; } protected async runGuards ( navigation ) { if ( navigation . cancelled ) { logger . debug ( { event : 'delay-ignore-guards' , navigation , } ) ; return ; } return super . runGuards ( navigation ) ; } protected async runHooks ( hookName , navigation ) { if ( navigation . cancelled && this . lastNavigation !== navigation ) { logger . debug ( { event : 'delay-ignore-hooks' , navigation , } ) ; return ; } try { await super . runHooks ( hookName , navigation ) ; } catch ( error ) { return this . notfound ( navigation ) ; } } private async resolveIfDelayFound ( task ) { let delayResolve ; const timer = setInterval ( ( ) => { if ( this . delayedNavigation ) { if ( this . delayedNavigation . type === 'navigate' || this . delayedNavigation . type === this . currentNavigation ?. type ) { logger . info ( { event : 'delay-navigation-found' , navigation : this . delayedNavigation , } ) ; if ( this . currentNavigation ) { this . currentNavigation . cancelled = true ; this . currentNavigation = null ; } delayResolve ( ) ; } else { this . delayedResolve ( ) ; } } } , DELAY_CHECK_INTERVAL ) ; await Promise . race ( [ task , new Promise ( ( resolve ) => { delayResolve = resolve ; } ) , ] ) ; clearInterval ( timer ) ; delayResolve ( ) ; } private async flattenDelayedNavigation ( navigation ) { const flatten = async ( nav ) => { await this . resolveIfDelayFound ( super . run ( nav ) ) ; if ( this . delayedNavigation ) { const { delayedNavigation } = this ; logger . info ( { event : 'delay-navigation-run' , navigation : delayedNavigation , } ) ; this . delayedNavigation = null ; return flatten ( delayedNavigation ) ; } } ; return flatten ( navigation ) . then ( ( ) => { this . delayedResolve ?. ( ) ; } , ( err ) => { this . delayedReject ?. ( err ) ; } ) . finally ( ( ) => { this . delayedPromise = null ; this . delayedResolve = null ; this . delayedReject = null ; } ) ; } }