@statewalker/webrun-http-browser 0.5.0 → 0.6.2
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.
- package/README.md +127 -5
- package/dist/index.js +92 -20
- package/dist/relay/index-sw.d.ts +86 -1
- package/dist/relay/index-sw.d.ts.map +1 -1
- package/dist/relay/index.d.ts +9 -2
- package/dist/relay/index.d.ts.map +1 -1
- package/dist/relay/mount-table.d.ts +48 -0
- package/dist/relay/mount-table.d.ts.map +1 -0
- package/dist/relay/split-service-url.d.ts.map +1 -1
- package/dist/relay-sw.js +275 -49
- package/dist/relay-worker.d.ts +20 -0
- package/dist/relay-worker.d.ts.map +1 -0
- package/dist/relay-worker.js +1899 -0
- package/dist/sw/sw-dispatcher.d.ts +8 -0
- package/dist/sw/sw-dispatcher.d.ts.map +1 -1
- package/dist/sw.js +10 -1
- package/package.json +19 -10
- package/src/relay/index-sw.ts +288 -37
- package/src/relay/index.ts +60 -4
- package/src/relay/mount-table.ts +115 -0
- package/src/relay/split-service-url.ts +73 -13
- package/src/relay-sw.ts +11 -3
- package/src/relay-worker.ts +20 -0
- package/src/sw/sw-dispatcher.ts +10 -1
- package/public-relay/heartbeat.js +0 -31
package/dist/relay-sw.js
CHANGED
|
@@ -1533,6 +1533,51 @@
|
|
|
1533
1533
|
})(request);
|
|
1534
1534
|
}
|
|
1535
1535
|
//#endregion
|
|
1536
|
+
//#region src/relay/mount-table.ts
|
|
1537
|
+
/** `/` stays `/`; `/peers` and `/peers/` both become `/peers/`. */
|
|
1538
|
+
function normalise(path) {
|
|
1539
|
+
if (path === "" || path === "/") return "/";
|
|
1540
|
+
const withSlash = path.startsWith("/") ? path : `/${path}`;
|
|
1541
|
+
return withSlash.endsWith("/") ? withSlash : `${withSlash}/`;
|
|
1542
|
+
}
|
|
1543
|
+
/** Does `prefix` own `pathname`? `/peers/` owns `/peers/`, `/peers` and `/peers/x`. */
|
|
1544
|
+
function owns(prefix, pathname) {
|
|
1545
|
+
if (prefix === "/") return true;
|
|
1546
|
+
if (pathname.startsWith(prefix)) return true;
|
|
1547
|
+
return `${pathname}/` === prefix;
|
|
1548
|
+
}
|
|
1549
|
+
function newMountTable(options = {}) {
|
|
1550
|
+
const entries = /* @__PURE__ */ new Map();
|
|
1551
|
+
let seq = 0;
|
|
1552
|
+
return {
|
|
1553
|
+
set(key, spec) {
|
|
1554
|
+
entries.set(key, {
|
|
1555
|
+
key,
|
|
1556
|
+
prefix: spec.path == null ? "" : normalise(spec.path),
|
|
1557
|
+
match: spec.match,
|
|
1558
|
+
seq: seq++
|
|
1559
|
+
});
|
|
1560
|
+
},
|
|
1561
|
+
remove(key) {
|
|
1562
|
+
entries.delete(key);
|
|
1563
|
+
},
|
|
1564
|
+
excludes(url) {
|
|
1565
|
+
return options.exclude?.(url) === true;
|
|
1566
|
+
},
|
|
1567
|
+
find(url) {
|
|
1568
|
+
if (options.exclude?.(url) === true) return void 0;
|
|
1569
|
+
let best;
|
|
1570
|
+
for (const entry of entries.values()) {
|
|
1571
|
+
if (entry.prefix === "" || !owns(entry.prefix, url.pathname)) continue;
|
|
1572
|
+
if (best == null || entry.prefix.length > best.prefix.length) best = entry;
|
|
1573
|
+
}
|
|
1574
|
+
if (best != null) return best.key;
|
|
1575
|
+
const predicates = [...entries.values()].filter((entry) => entry.match != null).sort((a, b) => a.seq - b.seq);
|
|
1576
|
+
for (const entry of predicates) if (entry.match?.(url) === true) return entry.key;
|
|
1577
|
+
}
|
|
1578
|
+
};
|
|
1579
|
+
}
|
|
1580
|
+
//#endregion
|
|
1536
1581
|
//#region src/relay/split-service-url.ts
|
|
1537
1582
|
/**
|
|
1538
1583
|
* Splits a URL of the form `<base>/<separator><key>/<path>` into parts.
|
|
@@ -1540,36 +1585,165 @@
|
|
|
1540
1585
|
*/
|
|
1541
1586
|
function splitServiceUrl(url, separator = "~") {
|
|
1542
1587
|
const str = `${url}`;
|
|
1543
|
-
const
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1588
|
+
const empty = {
|
|
1589
|
+
url: str,
|
|
1590
|
+
key: "",
|
|
1591
|
+
baseUrl: "",
|
|
1592
|
+
path: ""
|
|
1593
|
+
};
|
|
1594
|
+
const hashIdx = str.indexOf("#");
|
|
1595
|
+
const queryIdx = str.indexOf("?");
|
|
1596
|
+
let strippedEnd = str.length;
|
|
1597
|
+
if (hashIdx >= 0) strippedEnd = Math.min(strippedEnd, hashIdx);
|
|
1598
|
+
if (queryIdx >= 0) strippedEnd = Math.min(strippedEnd, queryIdx);
|
|
1599
|
+
const stripped = str.slice(0, strippedEnd);
|
|
1600
|
+
let prefixEnd = 0;
|
|
1601
|
+
const schemeMatch = stripped.match(/^[a-zA-Z][a-zA-Z0-9+\-.]*:\/\//);
|
|
1602
|
+
if (schemeMatch) {
|
|
1603
|
+
prefixEnd = schemeMatch[0].length;
|
|
1604
|
+
const slashIdx = stripped.indexOf("/", prefixEnd);
|
|
1605
|
+
if (slashIdx >= 0) prefixEnd = slashIdx;
|
|
1606
|
+
else return empty;
|
|
1607
|
+
} else if (stripped.startsWith("//")) {
|
|
1608
|
+
prefixEnd = 2;
|
|
1609
|
+
const slashIdx = stripped.indexOf("/", prefixEnd);
|
|
1610
|
+
if (slashIdx >= 0) prefixEnd = slashIdx;
|
|
1611
|
+
else return empty;
|
|
1612
|
+
}
|
|
1613
|
+
const prefix = stripped.slice(0, prefixEnd);
|
|
1614
|
+
const pathPart = stripped.slice(prefixEnd);
|
|
1615
|
+
let keyStart;
|
|
1616
|
+
let rooted = false;
|
|
1617
|
+
if (prefix === "") {
|
|
1618
|
+
if (pathPart.startsWith(`/${separator}`)) {
|
|
1619
|
+
rooted = true;
|
|
1620
|
+
keyStart = separator.length + 1;
|
|
1621
|
+
} else if (pathPart.startsWith(separator)) keyStart = separator.length;
|
|
1622
|
+
else return empty;
|
|
1623
|
+
} else {
|
|
1624
|
+
if (!pathPart.startsWith(`/${separator}`)) return empty;
|
|
1625
|
+
keyStart = separator.length + 1;
|
|
1556
1626
|
}
|
|
1627
|
+
const rest = pathPart.slice(keyStart);
|
|
1628
|
+
const slash = rest.indexOf("/");
|
|
1629
|
+
const key = slash < 0 ? rest : rest.slice(0, slash);
|
|
1630
|
+
if (key === "") return empty;
|
|
1557
1631
|
return {
|
|
1558
1632
|
url: str,
|
|
1559
1633
|
key,
|
|
1560
|
-
baseUrl
|
|
1561
|
-
path
|
|
1634
|
+
baseUrl: `${prefix === "" ? rooted ? "/" : "" : `${prefix}/`}${separator}${key}${slash < 0 ? "" : "/"}`,
|
|
1635
|
+
path: slash < 0 ? "" : rest.slice(slash + 1)
|
|
1562
1636
|
};
|
|
1563
1637
|
}
|
|
1564
1638
|
//#endregion
|
|
1565
1639
|
//#region src/relay/index-sw.ts
|
|
1566
1640
|
/**
|
|
1641
|
+
* One stored registry entry, whatever shape it is on disk.
|
|
1642
|
+
*
|
|
1643
|
+
* BEFORE MOUNTS THE VALUE WAS A BARE CLIENT ID. A browser that ran the earlier
|
|
1644
|
+
* worker still holds that shape, and reading it as an object would drop the id
|
|
1645
|
+
* and quietly unregister every service the visitor had.
|
|
1646
|
+
*/
|
|
1647
|
+
function readStoredEntry(value) {
|
|
1648
|
+
if (typeof value === "string") return { clientId: value };
|
|
1649
|
+
if (typeof value !== "object" || value === null) return void 0;
|
|
1650
|
+
const { clientId, path } = value;
|
|
1651
|
+
if (typeof clientId !== "string" || clientId === "") return void 0;
|
|
1652
|
+
return typeof path === "string" ? {
|
|
1653
|
+
clientId,
|
|
1654
|
+
path
|
|
1655
|
+
} : { clientId };
|
|
1656
|
+
}
|
|
1657
|
+
/**
|
|
1658
|
+
* Which service, if any, should answer `url`.
|
|
1659
|
+
*
|
|
1660
|
+
* `undefined` means NOT THE RELAY'S, and the caller must not call
|
|
1661
|
+
* `respondWith`: the request then goes to the network, which is how a host
|
|
1662
|
+
* keeps serving its own files from its own origin. Answering 404 here instead
|
|
1663
|
+
* would make a root mount fatal.
|
|
1664
|
+
*/
|
|
1665
|
+
function resolveServiceKey(url, table, selfOrigin) {
|
|
1666
|
+
if (url.origin !== selfOrigin) return void 0;
|
|
1667
|
+
if (table.excludes(url)) return void 0;
|
|
1668
|
+
const mounted = table.find(url);
|
|
1669
|
+
if (mounted != null) return mounted;
|
|
1670
|
+
const { key } = splitServiceUrl(url);
|
|
1671
|
+
return key === "" ? void 0 : key;
|
|
1672
|
+
}
|
|
1673
|
+
/**
|
|
1674
|
+
* Waits for the mount table to be restored from the registry before routing
|
|
1675
|
+
* `url` — but a restore failure must never wedge every fetch. `restored`
|
|
1676
|
+
* rejecting (blocked storage, quota, private-mode edge cases) would otherwise
|
|
1677
|
+
* propagate straight to `respondWith` on every request, including ones that
|
|
1678
|
+
* should reach the network, which breaks the one rule this file exists to
|
|
1679
|
+
* uphold. So: log the failure and route with whatever the in-memory table
|
|
1680
|
+
* already holds — possibly empty, never fatal.
|
|
1681
|
+
*/
|
|
1682
|
+
async function resolveAfterRestore(restored, url, table, selfOrigin) {
|
|
1683
|
+
try {
|
|
1684
|
+
await restored;
|
|
1685
|
+
} catch (error) {
|
|
1686
|
+
console.error("[relay] failed to restore mounts from the registry", error);
|
|
1687
|
+
}
|
|
1688
|
+
return resolveServiceKey(url, table, selfOrigin);
|
|
1689
|
+
}
|
|
1690
|
+
/**
|
|
1691
|
+
* What REGISTER does to the mount table: set it when `path` is given, or
|
|
1692
|
+
* remove any earlier mount when it is not. A path-less re-registration
|
|
1693
|
+
* reverts a service to `/~<key>/` addressing, and a stale prefix left behind
|
|
1694
|
+
* would keep routing requests to a mount that no longer exists.
|
|
1695
|
+
*
|
|
1696
|
+
* A REGISTRATION ONLY TOUCHES WHAT A REGISTRATION MADE. `hostKeys` names the
|
|
1697
|
+
* mounts the host declared itself, in `options.mounts`. Those are the host's
|
|
1698
|
+
* build-time decision and a page may not undo it: the documented static flow
|
|
1699
|
+
* -- host declares `{ key: "app", path: "/" }`, page calls `initHttpService(h,
|
|
1700
|
+
* { key: "app", port })` with no path -- would otherwise have its very first
|
|
1701
|
+
* registration delete the host's own mount and leave the origin unmounted. A
|
|
1702
|
+
* path-ful REGISTER naming a host key is ignored for the same reason (it would
|
|
1703
|
+
* replace a `match` predicate with a prefix of the page's choosing).
|
|
1704
|
+
*/
|
|
1705
|
+
function applyRegisteredMount(table, key, path, hostKeys = /* @__PURE__ */ new Set()) {
|
|
1706
|
+
if (hostKeys.has(key)) return;
|
|
1707
|
+
if (path != null) table.set(key, { path });
|
|
1708
|
+
else table.remove(key);
|
|
1709
|
+
}
|
|
1710
|
+
/**
|
|
1711
|
+
* What UNREGISTER does to the mount table: drop the mount a registration
|
|
1712
|
+
* made. A host-declared mount stays, for the reason `applyRegisteredMount`
|
|
1713
|
+
* gives -- a page tearing down its service must not take the host's table
|
|
1714
|
+
* with it.
|
|
1715
|
+
*/
|
|
1716
|
+
function removeRegisteredMount(table, key, hostKeys = /* @__PURE__ */ new Set()) {
|
|
1717
|
+
if (hostKeys.has(key)) return;
|
|
1718
|
+
table.remove(key);
|
|
1719
|
+
}
|
|
1720
|
+
/**
|
|
1721
|
+
* May `candidateId` take the key?
|
|
1722
|
+
*
|
|
1723
|
+
* `last-wins` is what the relay has always done and stays the default. With
|
|
1724
|
+
* `first-wins`, a LIVE holder keeps its key: on an origin whose name is
|
|
1725
|
+
* guessable, a second page proves nothing by existing. A holder that reloaded
|
|
1726
|
+
* is no longer live, so a host's own re-registration is never blocked.
|
|
1727
|
+
*/
|
|
1728
|
+
function mayRegister(args) {
|
|
1729
|
+
if (args.takeover === "last-wins") return true;
|
|
1730
|
+
if (args.current == null || !args.isCurrentLive) return true;
|
|
1731
|
+
return args.current.clientId === args.candidateId;
|
|
1732
|
+
}
|
|
1733
|
+
/**
|
|
1567
1734
|
* Boots the relay ServiceWorker: routes fetches shaped `<origin>/~<key>/…` to
|
|
1568
1735
|
* the client that registered `key`, and exposes REGISTER/UNREGISTER/CONNECT
|
|
1569
1736
|
* channel calls used by the page-side relay client.
|
|
1570
1737
|
*/
|
|
1571
|
-
function startRelayServiceWorker(self) {
|
|
1738
|
+
function startRelayServiceWorker(self, options = {}) {
|
|
1572
1739
|
const [register, clear] = newRegistry();
|
|
1740
|
+
const mounts = newMountTable({ exclude: options.exclude });
|
|
1741
|
+
const hostKeys = /* @__PURE__ */ new Set();
|
|
1742
|
+
for (const { key, ...spec } of options.mounts ?? []) {
|
|
1743
|
+
mounts.set(key, spec);
|
|
1744
|
+
hostKeys.add(key);
|
|
1745
|
+
}
|
|
1746
|
+
const takeover = options.takeover ?? "last-wins";
|
|
1573
1747
|
if (typeof self.skipWaiting === "function") self.addEventListener("install", (e) => {
|
|
1574
1748
|
e.waitUntil(self.skipWaiting());
|
|
1575
1749
|
});
|
|
@@ -1577,15 +1751,35 @@
|
|
|
1577
1751
|
e.waitUntil(self.clients.claim());
|
|
1578
1752
|
});
|
|
1579
1753
|
const clientsRegistry = newClientsRegistry({ self });
|
|
1754
|
+
const restored = clientsRegistry.restoreMounts(mounts, hostKeys);
|
|
1755
|
+
let restoreSettled = false;
|
|
1756
|
+
const latch = () => {
|
|
1757
|
+
restoreSettled = true;
|
|
1758
|
+
};
|
|
1759
|
+
restored.then(latch, latch);
|
|
1580
1760
|
register(handleClaimRequests(self));
|
|
1581
1761
|
register(handleChannelCalls(self, "REGISTER", async (event, data) => {
|
|
1582
1762
|
const source = event.source;
|
|
1583
1763
|
if (!source) return false;
|
|
1584
|
-
const { key } = data;
|
|
1585
|
-
|
|
1764
|
+
const { key, path } = data;
|
|
1765
|
+
if (options.canRegister != null && !await options.canRegister(source, key)) throw new Error(`this client may not register "${key}"`);
|
|
1766
|
+
if (takeover === "first-wins") {
|
|
1767
|
+
const current = await clientsRegistry.getMount(key);
|
|
1768
|
+
const isCurrentLive = current != null && await clientsRegistry.getClient(key) != null;
|
|
1769
|
+
if (!mayRegister({
|
|
1770
|
+
current,
|
|
1771
|
+
candidateId: source.id,
|
|
1772
|
+
isCurrentLive,
|
|
1773
|
+
takeover
|
|
1774
|
+
})) throw new Error(`"${key}" is already served by another client`);
|
|
1775
|
+
}
|
|
1776
|
+
const added = await clientsRegistry.addClient(key, source, path);
|
|
1777
|
+
applyRegisteredMount(mounts, key, path, hostKeys);
|
|
1778
|
+
return added;
|
|
1586
1779
|
}));
|
|
1587
1780
|
register(handleChannelCalls(self, "UNREGISTER", async (_event, data) => {
|
|
1588
1781
|
const { key } = data;
|
|
1782
|
+
removeRegisteredMount(mounts, key, hostKeys);
|
|
1589
1783
|
return await clientsRegistry.removeClient(key);
|
|
1590
1784
|
}));
|
|
1591
1785
|
register(handleChannelCalls(self, "CONNECT", async (_event, data, port) => {
|
|
@@ -1594,30 +1788,43 @@
|
|
|
1594
1788
|
if (!client) throw new Error(`Target client was not found. Target key: "${key}".`);
|
|
1595
1789
|
return await callChannel(client, "CONNECT", data, port);
|
|
1596
1790
|
}));
|
|
1791
|
+
/** Answers `request` as the service registered under `key`. */
|
|
1792
|
+
async function serve(key, request, url) {
|
|
1793
|
+
const params = splitServiceUrl(url);
|
|
1794
|
+
try {
|
|
1795
|
+
const channel = new MessageChannel();
|
|
1796
|
+
const client = await clientsRegistry.getClient(key);
|
|
1797
|
+
if (!client) throw HttpError.errorResourceGone(params);
|
|
1798
|
+
if (!await callChannel(client, "CONNECT", {
|
|
1799
|
+
type: "http",
|
|
1800
|
+
key
|
|
1801
|
+
}, channel.port2)) throw HttpError.errorForbidden(params);
|
|
1802
|
+
const response = await sendHttpRequest(channel.port1, request);
|
|
1803
|
+
return options.decorateResponse?.(response, request) ?? response;
|
|
1804
|
+
} catch (error) {
|
|
1805
|
+
const httpError = HttpError.fromError(error);
|
|
1806
|
+
const errorOptions = httpError.getResponseOptions(params);
|
|
1807
|
+
const errorResponse = new Response(JSON.stringify(errorOptions), {
|
|
1808
|
+
status: httpError.status ?? 500,
|
|
1809
|
+
statusText: httpError.statusText ?? "Internal Error",
|
|
1810
|
+
headers: { "Content-Type": "application/json" }
|
|
1811
|
+
});
|
|
1812
|
+
return options.decorateResponse?.(errorResponse, request) ?? errorResponse;
|
|
1813
|
+
}
|
|
1814
|
+
}
|
|
1597
1815
|
const fetchListener = (event) => {
|
|
1598
1816
|
const request = event.request;
|
|
1599
|
-
const
|
|
1600
|
-
|
|
1601
|
-
|
|
1817
|
+
const url = new URL(request.url);
|
|
1818
|
+
if (restoreSettled) {
|
|
1819
|
+
const key = resolveServiceKey(url, mounts, self.location.origin);
|
|
1820
|
+
if (key == null) return;
|
|
1821
|
+
event.respondWith(serve(key, request, url));
|
|
1822
|
+
return;
|
|
1823
|
+
}
|
|
1602
1824
|
event.respondWith((async () => {
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
if (!client) throw HttpError.errorResourceGone(params);
|
|
1607
|
-
if (!await callChannel(client, "CONNECT", {
|
|
1608
|
-
type: "http",
|
|
1609
|
-
key
|
|
1610
|
-
}, channel.port2)) throw HttpError.errorForbidden(params);
|
|
1611
|
-
return await sendHttpRequest(channel.port1, request);
|
|
1612
|
-
} catch (error) {
|
|
1613
|
-
const httpError = HttpError.fromError(error);
|
|
1614
|
-
const options = httpError.getResponseOptions(params);
|
|
1615
|
-
return new Response(JSON.stringify(options), {
|
|
1616
|
-
status: httpError.status ?? 500,
|
|
1617
|
-
statusText: httpError.statusText ?? "Internal Error",
|
|
1618
|
-
headers: { "Content-Type": "application/json" }
|
|
1619
|
-
});
|
|
1620
|
-
}
|
|
1825
|
+
const key = await resolveAfterRestore(restored, url, mounts, self.location.origin);
|
|
1826
|
+
if (key == null) return await fetch(request);
|
|
1827
|
+
return await serve(key, request, url);
|
|
1621
1828
|
})());
|
|
1622
1829
|
};
|
|
1623
1830
|
self.addEventListener("fetch", fetchListener);
|
|
@@ -1629,7 +1836,11 @@
|
|
|
1629
1836
|
async function loadClientsIndex() {
|
|
1630
1837
|
if (!_index) {
|
|
1631
1838
|
const entries = await get(key) ?? [];
|
|
1632
|
-
_index =
|
|
1839
|
+
_index = {};
|
|
1840
|
+
for (const [clientKey, value] of entries) {
|
|
1841
|
+
const entry = readStoredEntry(value);
|
|
1842
|
+
if (entry) _index[clientKey] = entry;
|
|
1843
|
+
}
|
|
1633
1844
|
}
|
|
1634
1845
|
return _index;
|
|
1635
1846
|
}
|
|
@@ -1638,11 +1849,14 @@
|
|
|
1638
1849
|
await set(key, Object.entries(index));
|
|
1639
1850
|
return index;
|
|
1640
1851
|
}
|
|
1641
|
-
async function addClient(clientKey, client) {
|
|
1852
|
+
async function addClient(clientKey, client, path) {
|
|
1642
1853
|
const index = await loadClientsIndex();
|
|
1643
|
-
const
|
|
1644
|
-
if (
|
|
1645
|
-
index[clientKey] = clientId
|
|
1854
|
+
const current = index[clientKey];
|
|
1855
|
+
if (current?.clientId === client.id && current.path === path) return false;
|
|
1856
|
+
index[clientKey] = path == null ? { clientId: client.id } : {
|
|
1857
|
+
clientId: client.id,
|
|
1858
|
+
path
|
|
1859
|
+
};
|
|
1646
1860
|
await storeClientsIndex();
|
|
1647
1861
|
return true;
|
|
1648
1862
|
}
|
|
@@ -1653,11 +1867,21 @@
|
|
|
1653
1867
|
await storeClientsIndex();
|
|
1654
1868
|
return true;
|
|
1655
1869
|
}
|
|
1870
|
+
async function getMount(clientKey) {
|
|
1871
|
+
return (await loadClientsIndex())[clientKey];
|
|
1872
|
+
}
|
|
1873
|
+
async function restoreMounts(table, hostKeys = /* @__PURE__ */ new Set()) {
|
|
1874
|
+
const index = await loadClientsIndex();
|
|
1875
|
+
for (const [clientKey, entry] of Object.entries(index)) {
|
|
1876
|
+
if (hostKeys.has(clientKey)) continue;
|
|
1877
|
+
if (entry.path != null) table.set(clientKey, { path: entry.path });
|
|
1878
|
+
}
|
|
1879
|
+
}
|
|
1656
1880
|
async function getClient(clientKey) {
|
|
1657
1881
|
const index = await loadClientsIndex();
|
|
1658
|
-
const
|
|
1659
|
-
if (!
|
|
1660
|
-
const client = await self.clients.get(clientId);
|
|
1882
|
+
const entry = index[clientKey];
|
|
1883
|
+
if (!entry) return void 0;
|
|
1884
|
+
const client = await self.clients.get(entry.clientId);
|
|
1661
1885
|
if (!client) {
|
|
1662
1886
|
delete index[clientKey];
|
|
1663
1887
|
await storeClientsIndex();
|
|
@@ -1666,12 +1890,14 @@
|
|
|
1666
1890
|
}
|
|
1667
1891
|
return {
|
|
1668
1892
|
getClient,
|
|
1893
|
+
getMount,
|
|
1669
1894
|
addClient,
|
|
1670
|
-
removeClient
|
|
1895
|
+
removeClient,
|
|
1896
|
+
restoreMounts
|
|
1671
1897
|
};
|
|
1672
1898
|
}
|
|
1673
1899
|
//#endregion
|
|
1674
1900
|
//#region src/relay-sw.ts
|
|
1675
|
-
startRelayServiceWorker(self);
|
|
1901
|
+
startRelayServiceWorker(self, self.RELAY_OPTIONS ?? {});
|
|
1676
1902
|
//#endregion
|
|
1677
1903
|
})();
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The relay ServiceWorker runtime, for a host that builds its own worker.
|
|
3
|
+
*
|
|
4
|
+
* TWO WAYS TO SHIP THE RELAY WORKER, and this is the typed one. A host that
|
|
5
|
+
* takes the prebuilt bundle loads `@statewalker/webrun-http-browser/relay-sw`
|
|
6
|
+
* through classic `importScripts` and passes its options in
|
|
7
|
+
* `self.RELAY_OPTIONS`, which cannot be typed from the outside because that
|
|
8
|
+
* bundle ships no declarations. A host that bundles its own worker imports
|
|
9
|
+
* this module instead and calls `startRelayServiceWorker(self, { … })`
|
|
10
|
+
* directly, with `RelayServiceWorkerOptions` to type the options — including
|
|
11
|
+
* `self.RELAY_OPTIONS`, for a host writing the tiny loader script by hand.
|
|
12
|
+
*
|
|
13
|
+
* Kept as its own entry rather than re-exported from the package root: this
|
|
14
|
+
* code only runs inside a ServiceWorker, and the root entry is what pages
|
|
15
|
+
* import.
|
|
16
|
+
*/
|
|
17
|
+
export type { RelayServiceWorkerOptions } from "./relay/index-sw.js";
|
|
18
|
+
export { startRelayServiceWorker } from "./relay/index-sw.js";
|
|
19
|
+
export type { MountSpec } from "./relay/mount-table.js";
|
|
20
|
+
//# sourceMappingURL=relay-worker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"relay-worker.d.ts","sourceRoot":"","sources":["../src/relay-worker.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,YAAY,EAAE,yBAAyB,EAAE,MAAM,qBAAqB,CAAC;AACrE,OAAO,EAAE,uBAAuB,EAAE,MAAM,qBAAqB,CAAC;AAC9D,YAAY,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC"}
|