import {defer} from "../defer"; import {NavigateEvent, Navigation, NavigationEventMap} from "../spec/navigation"; import {getNavigation} from "../get-navigation"; import {isPromise} from "../is"; export function createRepeatingPromise(fn: () => Promise): Promise { let promise: Promise | undefined; function getPromise() { if (promise) return promise; const current = promise = fn(); promise.finally(() => { if (promise === current) { promise = undefined; } }); return promise; } return { get [Symbol.toStringTag]() { return "[Promise Repeating]"; }, then(onResolve, onReject) { return getPromise().then(onResolve, onReject) }, catch(onReject) { return getPromise().catch(onReject) }, finally(onFinally) { return getPromise().finally(onFinally) } }; } export function createNavigationEvent(type: T, navigation: Navigation = getNavigation()): Promise[T]> { return createRepeatingPromise(getNavigationPromise); function getNavigationPromise() { return createNavigationPromise(type, navigation); } } export type NavigationEventsMap = { [P in keyof NavigationEventMap]: Promise[P]> } export function createNavigationEvents(navigation: Navigation = getNavigation()): NavigationEventsMap { return { navigate: createNavigationEvent("navigate", navigation), navigateerror: createNavigationEvent("navigateerror", navigation), navigatesuccess: createNavigationEvent("navigatesuccess", navigation), entrieschange: createNavigationEvent("entrieschange", navigation), currententrychange: createNavigationEvent("currententrychange", navigation) } } export async function createNavigationPromise( type: T, navigation: Navigation = getNavigation(), onEventFn?: (event: NavigationEventMap[T]) => void | unknown ): Promise[T]> { const { promise, resolve, reject } = defer[T]>(); navigation.addEventListener( type, onEvent, { once: true } ); if (type !== "navigate") { navigation.addEventListener( "navigate", onNavigate, { once: true } ) } if (type !== "navigateerror") { navigation.addEventListener( "navigateerror", onError, { once: true } ); } return promise; function removeListeners() { navigation.removeEventListener( type, onEvent ); if (type !== "navigate") { navigation.removeEventListener( "navigate", onNavigate ) } if (type !== "navigateerror") { navigation.removeEventListener( "navigateerror", onError ); } } function onEvent(event: NavigationEventMap[T]) { removeListeners(); if (onEventFn) { try { const result = onEventFn(event); if (isPromise(result)) { return result.then( () => resolve(event), reject ); } } catch (error) { return reject(error); } } else if (isNavigateEvent(event)) { onNavigate(event); } resolve(event); } function isNavigateEvent(event: NavigationEventMap[keyof NavigationEventMap]): event is NavigateEvent { return event.type === "navigate"; } function onNavigate(event: NavigateEvent) { event.intercept(); } function onError(event: NavigationEventMap["navigateerror"]) { removeListeners(); reject(event.error); } }