UNPKG

rwsdk

Version:

Build fast, server-driven webapps on Cloudflare with SSR, RSC, and realtime

125 lines (124 loc) 3.43 kB
const listeners = new Set(); let nextNavigationId = 0; let currentUrl = readWindowUrl(); let pendingNavigation = null; let snapshot = createSnapshot(); function readWindowUrl() { if (typeof window !== "undefined" && window.location?.href) { return new URL(window.location.href); } return new URL("http://localhost/"); } function cloneUrl(url) { return new URL(url.href); } function createSnapshot() { return { currentUrl: cloneUrl(currentUrl), pending: pendingNavigation ? { id: pendingNavigation.id, currentUrl: cloneUrl(pendingNavigation.currentUrl), pendingUrl: cloneUrl(pendingNavigation.pendingUrl), promise: pendingNavigation.promise, } : null, }; } function updateSnapshot() { snapshot = createSnapshot(); } function notifyListeners() { for (const listener of listeners) { listener(); } } function toUrl(url) { if (url instanceof URL) { return cloneUrl(url); } return new URL(url, readWindowUrl()); } function toNavigationDocumentHref(url) { const nextUrl = toUrl(url); nextUrl.hash = ""; return nextUrl.href; } export function isSameNavigationDocumentUrl(left, right) { return toNavigationDocumentHref(left) === toNavigationDocumentHref(right); } function createDeferred() { let resolve; const promise = new Promise((resolvePromise) => { resolve = resolvePromise; }); return { promise, resolve }; } export function subscribeNavigationState(listener) { listeners.add(listener); return () => { listeners.delete(listener); }; } export function getNavigationSnapshot() { return snapshot; } export function beginPendingNavigation(url) { const supersededNavigation = pendingNavigation; const targetUrl = toUrl(url); const { promise, resolve } = createDeferred(); pendingNavigation = { id: ++nextNavigationId, currentUrl: cloneUrl(currentUrl), pendingUrl: targetUrl, promise, resolve, }; updateSnapshot(); notifyListeners(); supersededNavigation?.resolve(); return pendingNavigation; } export function isPendingNavigationCommit(href) { if (!pendingNavigation) { return false; } if (href === undefined) { return true; } return isSameNavigationDocumentUrl(href, pendingNavigation.pendingUrl); } export function commitPendingNavigation(href) { if (!isPendingNavigationCommit(href)) { return false; } const committedNavigation = pendingNavigation; pendingNavigation = null; currentUrl = cloneUrl(committedNavigation.pendingUrl); updateSnapshot(); notifyListeners(); committedNavigation.resolve(); return true; } export function abortPendingNavigation(id) { if (!pendingNavigation) { return false; } if (id !== undefined && pendingNavigation.id !== id) { return false; } const abortedNavigation = pendingNavigation; pendingNavigation = null; updateSnapshot(); notifyListeners(); abortedNavigation.resolve(); return true; } export function resetNavigationStateForTests(href = "http://localhost/") { pendingNavigation?.resolve(); pendingNavigation = null; nextNavigationId = 0; currentUrl = new URL(href); updateSnapshot(); notifyListeners(); }