@volter/world-console 3.0.6 → 3.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1752,27 +1752,45 @@ function Thumb({ url, title }: { url: string; title: string }): React.JSX.Elemen
1752
1752
  * was the moment before the first of them beside how it is now. "Before" is a read-only copy of the World as of then (a
1753
1753
  * branch the World makes for an hour, removed when this closes), its site read at the copy's own address. */
1754
1754
  function Compare({ name, token, frame, onClose, reads }: { name: string; token: string; frame: BoardFrame; onClose: () => void; /** the board's reads so far: its changes are read again with each */ reads: number }): React.JSX.Element {
1755
- const [before, setBefore] = useState<{ state: 'opening' } | { state: 'ok'; url: string; at: string } | { state: 'none'; why: string }>({ state: 'opening' });
1755
+ const [before, setBefore] = useState<{ state: 'opening' } | { state: 'ok'; url: string; at: string; /** a vendor's page: opened as a page of its own, never framed */ opens?: boolean } | { state: 'none'; why: string }>({ state: 'opening' });
1756
1756
  // its changes, read for this comparison alone
1757
1757
  const listed = useDoor<{ total: number; acts: BoardAct[] }>(token, `/-/${name}/board/changes?frame=${encodeURIComponent(frameKey(frame))}`, reads);
1758
1758
  useEffect(() => {
1759
1759
  if (!frame.since) { setBefore({ state: 'none', why: 'Nothing here has changed since the World was last cut.' }); return; }
1760
- if (!frame.at || !('site' in frame.at)) { setBefore({ state: 'none', why: `A ${vendorName(frame.vendor)} page as it was needs a signed-in copy of the World; its changes are listed below.` }); return; }
1760
+ if (!frame.at) { setBefore({ state: 'none', why: `${vendorName(frame.vendor)}'s twin draws no page for this ${frame.kind} yet; its changes are listed below.` }); return; }
1761
+ const shown = frame.at;
1761
1762
  const at = new Date(Date.parse(frame.since) - 1).toISOString();
1762
1763
  let made: string | null = null; let closed = false;
1763
1764
  // a copy is made once the comparison has stayed open a moment: one passed through on the way to another makes none
1764
1765
  const wait = setTimeout(() => void (async () => {
1765
1766
  const r = await door(token, `/-/${name}/branches`, { method: 'POST', body: JSON.stringify({ at: { instant: at }, ttl: 3600 }), signal: AbortSignal.timeout(60000) }).catch(() => null);
1766
1767
  if (!r?.ok) { setBefore({ state: 'none', why: `The World made no copy of itself as it was then${r ? `: ${await failure(r)}` : ''}.` }); return; }
1767
- const b = (await r.json()) as { name: string; origin?: string };
1768
+ const b = (await r.json()) as { name: string; origin?: string; readToken?: string };
1768
1769
  made = b.name;
1769
1770
  if (closed) { void door(token, `/-/${name}/branches/${b.name}`, { method: 'DELETE' }).catch(() => null); return; }
1770
1771
  const o = httpOrigin(b.origin);
1771
1772
  if (!o) { setBefore({ state: 'none', why: 'This World shows sites only at its own local address.' }); return; }
1772
- const u = new URL(frame.url);
1773
- setBefore({ state: 'ok', url: `${o.protocol}//${u.hostname}.${o.host}${u.pathname}${u.search}`, at });
1773
+ // a site at the copy's own address, framed; a vendor's page in the copy is behind the copy's session, which opens
1774
+ // only on the copy's own pages (another site's frame is refused it): this World shows it instead, or, on a host
1775
+ // that cannot, the copy's hand-off page (served-world, `session/hand-off`) opens it as a page of its own
1776
+ if ('site' in shown) { const u = new URL(frame.url); setBefore({ state: 'ok', url: `${o.protocol}//${u.hostname}.${o.host}${u.pathname}${u.search}`, at }); return; }
1777
+ // shown by this World beside its own page, as a branch of it (its `copies` door), where its host holds the copy;
1778
+ // else opened at the copy's own address
1779
+ const id = b.name.slice(b.name.indexOf('/') + 1);
1780
+ const beside = `${WORLDS}/-/${name}/copies/${id}/${frame.vendor}${shown.screen}`;
1781
+ // the copy's own answer may be a 404 page (an account not there yet); the door's refusals are not
1782
+ const here = await fetch(beside, { method: 'HEAD', credentials: 'same-origin' }).then((r) => ![401, 403, 501, 503].includes(r.status), () => false);
1783
+ if (closed) return;
1784
+ if (here) { setBefore({ state: 'ok', url: beside, at }); return; }
1785
+ if (!b.readToken) { setBefore({ state: 'none', why: 'The copy of the World gave no read token to open its pages with.' }); return; }
1786
+ setBefore({ state: 'ok', url: `${o.origin}/-/${b.name}/session/hand-off#${new URLSearchParams({ token: b.readToken, land: `/${b.name}/${frame.vendor}${shown.screen}` }).toString()}`, at, opens: true });
1774
1787
  })(), 500);
1775
- return () => { closed = true; clearTimeout(wait); if (made) void door(token, `/-/${name}/branches/${made}`, { method: 'DELETE' }).catch(() => null); };
1788
+ // the copy goes with the comparison, and with the page: a reload or a closed tab runs no cleanup of its own
1789
+ const drop = (): void => { if (made) void door(token, `/-/${name}/branches/${made}`, { method: 'DELETE', keepalive: true }).catch(() => null); made = null; };
1790
+ // a page kept for back and forward keeps its copy: it may be shown again
1791
+ const left = (e: PageTransitionEvent): void => { if (!e.persisted) drop(); };
1792
+ window.addEventListener('pagehide', left);
1793
+ return () => { closed = true; clearTimeout(wait); window.removeEventListener('pagehide', left); drop(); };
1776
1794
  }, [name, token, frame.vendor, frame.id, frame.since]);
1777
1795
  const now = frameUrl(name, frame);
1778
1796
  return (
@@ -1785,7 +1803,9 @@ function Compare({ name, token, frame, onClose, reads }: { name: string; token:
1785
1803
  <div className="compare-sides">
1786
1804
  <figure>
1787
1805
  <figcaption>Before{before.state === 'ok' ? ` · as of ${when(before.at)}` : ''}</figcaption>
1788
- {before.state === 'ok' ? <Thumb url={before.url} title={`${frame.title} before`} /> : before.state === 'opening' ? <p className="mode compare-note" role="status">Opening the World as it was…</p> : <p className="mode compare-note">{before.why}</p>}
1806
+ {before.state === 'ok' ? (before.opens
1807
+ ? <p className="mode compare-note"><a href={before.url} target="_blank" rel="noopener noreferrer">Open {frame.title} as it was →</a><br />A read-only copy of the World, kept while this comparison is open.</p>
1808
+ : <Thumb url={before.url} title={`${frame.title} before`} />) : before.state === 'opening' ? <p className="mode compare-note" role="status">Opening the World as it was…</p> : <p className="mode compare-note">{before.why}</p>}
1789
1809
  </figure>
1790
1810
  <figure>
1791
1811
  <figcaption>Now</figcaption>