rwsdk
Version:
Build fast, server-driven webapps on Cloudflare with SSR, RSC, and realtime
125 lines (124 loc) • 3.43 kB
JavaScript
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();
}