@statewalker/httpeers-member 0.1.4

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.
Files changed (95) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +203 -0
  3. package/dist/browser-platform.d.ts +30 -0
  4. package/dist/browser-platform.d.ts.map +1 -0
  5. package/dist/browser-platform.js +30 -0
  6. package/dist/browser-profile.d.ts +85 -0
  7. package/dist/browser-profile.d.ts.map +1 -0
  8. package/dist/browser-profile.js +120 -0
  9. package/dist/browser.d.ts +48 -0
  10. package/dist/browser.d.ts.map +1 -0
  11. package/dist/browser.js +51 -0
  12. package/dist/connection-kind.d.ts +36 -0
  13. package/dist/connection-kind.d.ts.map +1 -0
  14. package/dist/connection-kind.js +52 -0
  15. package/dist/describe-error.d.ts +18 -0
  16. package/dist/describe-error.d.ts.map +1 -0
  17. package/dist/describe-error.js +72 -0
  18. package/dist/edge-control.d.ts +50 -0
  19. package/dist/edge-control.d.ts.map +1 -0
  20. package/dist/edge-control.js +69 -0
  21. package/dist/edge-dispatch.d.ts +188 -0
  22. package/dist/edge-dispatch.d.ts.map +1 -0
  23. package/dist/edge-dispatch.js +142 -0
  24. package/dist/edge-guard.d.ts +45 -0
  25. package/dist/edge-guard.d.ts.map +1 -0
  26. package/dist/edge-guard.js +62 -0
  27. package/dist/edge.d.ts +97 -0
  28. package/dist/edge.d.ts.map +1 -0
  29. package/dist/edge.js +104 -0
  30. package/dist/gateway.d.ts +49 -0
  31. package/dist/gateway.d.ts.map +1 -0
  32. package/dist/gateway.js +102 -0
  33. package/dist/identity.d.ts +78 -0
  34. package/dist/identity.d.ts.map +1 -0
  35. package/dist/identity.js +117 -0
  36. package/dist/index.d.ts +39 -0
  37. package/dist/index.d.ts.map +1 -0
  38. package/dist/index.js +37 -0
  39. package/dist/join-blob.d.ts +125 -0
  40. package/dist/join-blob.d.ts.map +1 -0
  41. package/dist/join-blob.js +195 -0
  42. package/dist/join.d.ts +343 -0
  43. package/dist/join.d.ts.map +1 -0
  44. package/dist/join.js +409 -0
  45. package/dist/kv.d.ts +20 -0
  46. package/dist/kv.d.ts.map +1 -0
  47. package/dist/kv.js +10 -0
  48. package/dist/mesh-config.d.ts +26 -0
  49. package/dist/mesh-config.d.ts.map +1 -0
  50. package/dist/mesh-config.js +39 -0
  51. package/dist/mesh-memory.d.ts +55 -0
  52. package/dist/mesh-memory.d.ts.map +1 -0
  53. package/dist/mesh-memory.js +74 -0
  54. package/dist/node.d.ts +29 -0
  55. package/dist/node.d.ts.map +1 -0
  56. package/dist/node.js +42 -0
  57. package/dist/page-wake.d.ts +24 -0
  58. package/dist/page-wake.d.ts.map +1 -0
  59. package/dist/page-wake.js +31 -0
  60. package/dist/peer-request.d.ts +29 -0
  61. package/dist/peer-request.d.ts.map +1 -0
  62. package/dist/peer-request.js +11 -0
  63. package/dist/reset.d.ts +56 -0
  64. package/dist/reset.d.ts.map +1 -0
  65. package/dist/reset.js +103 -0
  66. package/dist/session.d.ts +214 -0
  67. package/dist/session.d.ts.map +1 -0
  68. package/dist/session.js +403 -0
  69. package/dist/start-member.d.ts +176 -0
  70. package/dist/start-member.d.ts.map +1 -0
  71. package/dist/start-member.js +307 -0
  72. package/package.json +120 -0
  73. package/src/browser-platform.ts +53 -0
  74. package/src/browser-profile.ts +179 -0
  75. package/src/browser.ts +103 -0
  76. package/src/connection-kind.ts +53 -0
  77. package/src/describe-error.ts +76 -0
  78. package/src/edge-control.ts +94 -0
  79. package/src/edge-dispatch.ts +295 -0
  80. package/src/edge-guard.ts +67 -0
  81. package/src/edge.ts +175 -0
  82. package/src/gateway.ts +141 -0
  83. package/src/identity.ts +146 -0
  84. package/src/index.ts +95 -0
  85. package/src/join-blob.ts +229 -0
  86. package/src/join.ts +632 -0
  87. package/src/kv.ts +21 -0
  88. package/src/mesh-config.ts +41 -0
  89. package/src/mesh-memory.ts +104 -0
  90. package/src/node.ts +49 -0
  91. package/src/page-wake.ts +59 -0
  92. package/src/peer-request.ts +39 -0
  93. package/src/reset.ts +127 -0
  94. package/src/session.ts +579 -0
  95. package/src/start-member.ts +476 -0
package/dist/node.d.ts ADDED
@@ -0,0 +1,29 @@
1
+ /**
2
+ * The NODE half of `MemberPlatform` — the counterpart to
3
+ * `./browser-profile.ts`'s `createBrowserNode`.
4
+ *
5
+ * WHAT DIFFERS FROM THE BROWSER PROFILE, AND WHAT DELIBERATELY DOES NOT:
6
+ *
7
+ * - `tcp()` is added. It is the one transport a page cannot have and the
8
+ * one a Node member wants, for the direct member-to-member hop a page
9
+ * makes over WebRTC instead.
10
+ * - The LISTEN ADDRESSES ARE THE SAME — `/p2p-circuit` and `/webrtc`,
11
+ * plus a loopback TCP port. If a Node member listened differently it
12
+ * would reserve somewhere else, and the rung would be testing a
13
+ * different design rather than the same one on another platform.
14
+ * - The connection gater is permissive, which the browser profile also
15
+ * does for loopback and private addresses (`dialNeedsPermissiveGater`).
16
+ * Node has no browser default to relax, so this is the same policy
17
+ * arrived at from the other side.
18
+ * - There is no `mountEdge`: `MemberHandle.fetch` is the edge under Node.
19
+ * - There is no `watchWake`: a Node process gets no wake events, so the
20
+ * supervisor's backstop timer is the whole story.
21
+ */
22
+ import type { Ed25519PrivateKey, Libp2p } from "@libp2p/interface";
23
+ import type { MemberPlatform } from "./start-member.js";
24
+ export declare function createNodeMemberNode(init: {
25
+ privateKey?: Ed25519PrivateKey;
26
+ }): Promise<Libp2p>;
27
+ /** The whole Node platform: a node factory, and nothing else. */
28
+ export declare const nodePlatform: MemberPlatform;
29
+ //# sourceMappingURL=node.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"node.d.ts","sourceRoot":"","sources":["../src/node.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAMH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAKnE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAExD,wBAAsB,oBAAoB,CAAC,IAAI,EAAE;IAC/C,UAAU,CAAC,EAAE,iBAAiB,CAAC;CAChC,GAAG,OAAO,CAAC,MAAM,CAAC,CAUlB;AAED,iEAAiE;AACjE,eAAO,MAAM,YAAY,EAAE,cAAqD,CAAC"}
package/dist/node.js ADDED
@@ -0,0 +1,42 @@
1
+ /**
2
+ * The NODE half of `MemberPlatform` — the counterpart to
3
+ * `./browser-profile.ts`'s `createBrowserNode`.
4
+ *
5
+ * WHAT DIFFERS FROM THE BROWSER PROFILE, AND WHAT DELIBERATELY DOES NOT:
6
+ *
7
+ * - `tcp()` is added. It is the one transport a page cannot have and the
8
+ * one a Node member wants, for the direct member-to-member hop a page
9
+ * makes over WebRTC instead.
10
+ * - The LISTEN ADDRESSES ARE THE SAME — `/p2p-circuit` and `/webrtc`,
11
+ * plus a loopback TCP port. If a Node member listened differently it
12
+ * would reserve somewhere else, and the rung would be testing a
13
+ * different design rather than the same one on another platform.
14
+ * - The connection gater is permissive, which the browser profile also
15
+ * does for loopback and private addresses (`dialNeedsPermissiveGater`).
16
+ * Node has no browser default to relax, so this is the same policy
17
+ * arrived at from the other side.
18
+ * - There is no `mountEdge`: `MemberHandle.fetch` is the edge under Node.
19
+ * - There is no `watchWake`: a Node process gets no wake events, so the
20
+ * supervisor's backstop timer is the whole story.
21
+ */
22
+ import { noise } from "@chainsafe/libp2p-noise";
23
+ import { yamux } from "@chainsafe/libp2p-yamux";
24
+ import { circuitRelayTransport } from "@libp2p/circuit-relay-v2";
25
+ import { identify } from "@libp2p/identify";
26
+ import { tcp } from "@libp2p/tcp";
27
+ import { webRTC } from "@libp2p/webrtc";
28
+ import { webSockets } from "@libp2p/websockets";
29
+ import { createLibp2p } from "libp2p";
30
+ export async function createNodeMemberNode(init) {
31
+ return createLibp2p({
32
+ ...(init.privateKey != null ? { privateKey: init.privateKey } : {}),
33
+ addresses: { listen: ["/ip4/127.0.0.1/tcp/0", "/p2p-circuit", "/webrtc"] },
34
+ transports: [webSockets(), circuitRelayTransport(), webRTC(), tcp()],
35
+ connectionEncrypters: [noise()],
36
+ streamMuxers: [yamux()],
37
+ connectionGater: { denyDialMultiaddr: async () => false },
38
+ services: { identify: identify() },
39
+ });
40
+ }
41
+ /** The whole Node platform: a node factory, and nothing else. */
42
+ export const nodePlatform = { createNode: createNodeMemberNode };
@@ -0,0 +1,24 @@
1
+ /**
2
+ * The page events that mean "the relay link may have died while nothing was
3
+ * watching, and may be reachable again now" -- turned into one callback.
4
+ */
5
+ /** The parts of `window` this reads. */
6
+ export type WakeWindow = Pick<EventTarget, "addEventListener" | "removeEventListener">;
7
+ /** The parts of `document` this reads. */
8
+ export type WakeDocument = Pick<EventTarget, "addEventListener" | "removeEventListener"> & {
9
+ readonly visibilityState: string;
10
+ };
11
+ export interface WatchPageWakeInit {
12
+ win?: WakeWindow;
13
+ doc?: WakeDocument;
14
+ }
15
+ /**
16
+ * Call `onWake` whenever the page comes back; returns the unsubscribe.
17
+ *
18
+ * Why these events, and not a timer: a suspended page's timers are
19
+ * throttled or stopped, so the relay supervisor's own backoff may not fire
20
+ * for minutes after the page is usable again. See
21
+ * `tests/browser-page-wake.test.ts` for what each event means.
22
+ */
23
+ export declare function watchPageWake(onWake: () => void, init?: WatchPageWakeInit): () => void;
24
+ //# sourceMappingURL=page-wake.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"page-wake.d.ts","sourceRoot":"","sources":["../src/page-wake.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,wCAAwC;AACxC,MAAM,MAAM,UAAU,GAAG,IAAI,CAAC,WAAW,EAAE,kBAAkB,GAAG,qBAAqB,CAAC,CAAC;AAEvF,0CAA0C;AAC1C,MAAM,MAAM,YAAY,GAAG,IAAI,CAAC,WAAW,EAAE,kBAAkB,GAAG,qBAAqB,CAAC,GAAG;IACzF,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;CAClC,CAAC;AAEF,MAAM,WAAW,iBAAiB;IAChC,GAAG,CAAC,EAAE,UAAU,CAAC;IACjB,GAAG,CAAC,EAAE,YAAY,CAAC;CACpB;AAeD;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,IAAI,EAAE,IAAI,GAAE,iBAAsB,GAAG,MAAM,IAAI,CAmB1F"}
@@ -0,0 +1,31 @@
1
+ /**
2
+ * The page events that mean "the relay link may have died while nothing was
3
+ * watching, and may be reachable again now" -- turned into one callback.
4
+ */
5
+ /**
6
+ * Call `onWake` whenever the page comes back; returns the unsubscribe.
7
+ *
8
+ * Why these events, and not a timer: a suspended page's timers are
9
+ * throttled or stopped, so the relay supervisor's own backoff may not fire
10
+ * for minutes after the page is usable again. See
11
+ * `tests/browser-page-wake.test.ts` for what each event means.
12
+ */
13
+ export function watchPageWake(onWake, init = {}) {
14
+ const win = init.win ?? window;
15
+ const doc = init.doc ?? document;
16
+ const wake = () => onWake();
17
+ const onVisibility = () => {
18
+ if (doc.visibilityState === "visible")
19
+ onWake();
20
+ };
21
+ win.addEventListener("online", wake);
22
+ win.addEventListener("pageshow", wake);
23
+ doc.addEventListener("resume", wake);
24
+ doc.addEventListener("visibilitychange", onVisibility);
25
+ return () => {
26
+ win.removeEventListener("online", wake);
27
+ win.removeEventListener("pageshow", wake);
28
+ doc.removeEventListener("resume", wake);
29
+ doc.removeEventListener("visibilitychange", onVisibility);
30
+ };
31
+ }
@@ -0,0 +1,29 @@
1
+ /**
2
+ * A `Request` addressed at a mesh path.
3
+ *
4
+ * `Peer.call(peerId, request)` takes a whole `Request`, because that is the
5
+ * contract every other seam in this system speaks — the prototype's peer took
6
+ * `(peerId, path, init)` instead, which meant it owned a second, narrower idea
7
+ * of what a call is and could not carry anything a `Request` can (a stream, an
8
+ * `AbortSignal`, a `Headers` instance a caller already had).
9
+ *
10
+ * The origin is a placeholder and is never dialled: the far side routes on the
11
+ * pathname alone. It is spelled `peer.local` rather than `localhost` so that a
12
+ * stray log line cannot be mistaken for a loopback request that really went out.
13
+ */
14
+ export interface PeerRequestInit extends RequestInit {
15
+ /**
16
+ * A membership token, placed in `MESH_TOKEN_HEADER` -- never
17
+ * `Authorization`, which stays the caller's to use for the application it
18
+ * is calling.
19
+ *
20
+ * The prototype's `peer.call` took this as its own option and built the
21
+ * header internally. Keeping it here rather than making every call site
22
+ * write the header keeps those sites reading as intent — and keeps ONE
23
+ * place that decides the scheme, which is what a token's format being
24
+ * changed later will want.
25
+ */
26
+ token?: string;
27
+ }
28
+ export declare function peerRequest(path: string, init?: PeerRequestInit): Request;
29
+ //# sourceMappingURL=peer-request.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"peer-request.d.ts","sourceRoot":"","sources":["../src/peer-request.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,eAAgB,SAAQ,WAAW;IAClD;;;;;;;;;;OAUG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,GAAE,eAAoB,GAAG,OAAO,CAQ7E"}
@@ -0,0 +1,11 @@
1
+ import { setMeshToken } from "@statewalker/httpeers-core";
2
+ export function peerRequest(path, init = {}) {
3
+ const { token, headers, ...rest } = init;
4
+ const merged = new Headers(headers);
5
+ if (token != null)
6
+ setMeshToken(merged, token);
7
+ return new Request(`http://peer.local${path.startsWith("/") ? path : `/${path}`}`, {
8
+ ...rest,
9
+ headers: merged,
10
+ });
11
+ }
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Getting a stuck origin back to a first visit — and NOTHING ELSE IN IT.
3
+ *
4
+ * ITS OWN ENTRY POINT ON PURPOSE. This is what a rescue page loads when the
5
+ * app will not start, so it must not depend on the app starting. Importing it
6
+ * through `./browser` would drag in the session, the member lifecycle,
7
+ * `httpeers-access` and its Biscuit engine, crypto and transport code -- all to
8
+ * unregister a ServiceWorker. If any of that is what broke, the rescue page
9
+ * breaks with it.
10
+ *
11
+ * This was measured when the engine was WebAssembly: importing
12
+ * `@statewalker/httpeers-member/browser` failed outright until the wasm loader
13
+ * was wired. The engine is pure TypeScript now, but the rule stands for every
14
+ * other reason an app fails to start.
15
+ *
16
+ * So: zero imports. Everything here is a platform global.
17
+ */
18
+ /** What a reset actually removed, so a page can report it rather than guess. */
19
+ export interface ResetResult {
20
+ /** ServiceWorker registrations unregistered on this origin. */
21
+ serviceWorkers: number;
22
+ /** IndexedDB databases deleted, by name. */
23
+ databases: string[];
24
+ }
25
+ /**
26
+ * Put this origin back to a first-visit state: no worker, no databases, no
27
+ * stored values.
28
+ *
29
+ * WHY THIS EXISTS AT ALL. A ServiceWorker is the stickiest thing a static site
30
+ * can install, and the obvious remedies do not work — measured in a real
31
+ * browser, not inferred:
32
+ *
33
+ * - **A reload does not remove one.** Registration is durable state on the
34
+ * ORIGIN, independent of any page's lifetime.
35
+ * - **`unregister()` does not evict the worker already controlling this
36
+ * page.** It removes the registration; the active worker keeps serving
37
+ * open clients until they are unloaded. So a reset control that reports
38
+ * success without reloading is still being served by the thing it says it
39
+ * removed. **Reload after calling this.**
40
+ * - **Unregistering alone is a half-reset.** The identity that makes a page
41
+ * "the same peer" is in IndexedDB. Clear only the worker and the page
42
+ * comes back as the same member, which is exactly the outcome people
43
+ * report as "it did not work".
44
+ *
45
+ * EVERYTHING ON THE ORIGIN, NOT A LIST OF NAMES. The state spans idb-keyval's
46
+ * store (identity, remembered mesh), the hub's database, the libp2p key store,
47
+ * the ServiceWorker adapter's claimed-keys table and `localStorage` — five
48
+ * places across three packages today. A hand-maintained list would go stale
49
+ * the first time one moved, silently, leaving the half-reset above. An origin
50
+ * here belongs to one app, so "all of it" is both simpler and correct.
51
+ *
52
+ * NEVER THROWS. It runs on a rescue page that must work when everything else
53
+ * is broken, including on a first visit with nothing to remove.
54
+ */
55
+ export declare function resetBrowserState(): Promise<ResetResult>;
56
+ //# sourceMappingURL=reset.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reset.d.ts","sourceRoot":"","sources":["../src/reset.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAoBH,gFAAgF;AAChF,MAAM,WAAW,WAAW;IAC1B,+DAA+D;IAC/D,cAAc,EAAE,MAAM,CAAC;IACvB,4CAA4C;IAC5C,SAAS,EAAE,MAAM,EAAE,CAAC;CACrB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,WAAW,CAAC,CAoD9D"}
package/dist/reset.js ADDED
@@ -0,0 +1,103 @@
1
+ /**
2
+ * Getting a stuck origin back to a first visit — and NOTHING ELSE IN IT.
3
+ *
4
+ * ITS OWN ENTRY POINT ON PURPOSE. This is what a rescue page loads when the
5
+ * app will not start, so it must not depend on the app starting. Importing it
6
+ * through `./browser` would drag in the session, the member lifecycle,
7
+ * `httpeers-access` and its Biscuit engine, crypto and transport code -- all to
8
+ * unregister a ServiceWorker. If any of that is what broke, the rescue page
9
+ * breaks with it.
10
+ *
11
+ * This was measured when the engine was WebAssembly: importing
12
+ * `@statewalker/httpeers-member/browser` failed outright until the wasm loader
13
+ * was wired. The engine is pure TypeScript now, but the rule stands for every
14
+ * other reason an app fails to start.
15
+ *
16
+ * So: zero imports. Everything here is a platform global.
17
+ */
18
+ /**
19
+ * Put this origin back to a first-visit state: no worker, no databases, no
20
+ * stored values.
21
+ *
22
+ * WHY THIS EXISTS AT ALL. A ServiceWorker is the stickiest thing a static site
23
+ * can install, and the obvious remedies do not work — measured in a real
24
+ * browser, not inferred:
25
+ *
26
+ * - **A reload does not remove one.** Registration is durable state on the
27
+ * ORIGIN, independent of any page's lifetime.
28
+ * - **`unregister()` does not evict the worker already controlling this
29
+ * page.** It removes the registration; the active worker keeps serving
30
+ * open clients until they are unloaded. So a reset control that reports
31
+ * success without reloading is still being served by the thing it says it
32
+ * removed. **Reload after calling this.**
33
+ * - **Unregistering alone is a half-reset.** The identity that makes a page
34
+ * "the same peer" is in IndexedDB. Clear only the worker and the page
35
+ * comes back as the same member, which is exactly the outcome people
36
+ * report as "it did not work".
37
+ *
38
+ * EVERYTHING ON THE ORIGIN, NOT A LIST OF NAMES. The state spans idb-keyval's
39
+ * store (identity, remembered mesh), the hub's database, the libp2p key store,
40
+ * the ServiceWorker adapter's claimed-keys table and `localStorage` — five
41
+ * places across three packages today. A hand-maintained list would go stale
42
+ * the first time one moved, silently, leaving the half-reset above. An origin
43
+ * here belongs to one app, so "all of it" is both simpler and correct.
44
+ *
45
+ * NEVER THROWS. It runs on a rescue page that must work when everything else
46
+ * is broken, including on a first visit with nothing to remove.
47
+ */
48
+ export async function resetBrowserState() {
49
+ let serviceWorkers = 0;
50
+ try {
51
+ const registrations = (await navigator.serviceWorker?.getRegistrations()) ?? [];
52
+ for (const registration of registrations) {
53
+ try {
54
+ await registration.unregister();
55
+ serviceWorkers += 1;
56
+ }
57
+ catch {
58
+ /* one that refuses must not stop the rest */
59
+ }
60
+ }
61
+ }
62
+ catch {
63
+ /* no ServiceWorker support, or an insecure context */
64
+ }
65
+ const databases = [];
66
+ try {
67
+ // `indexedDB.databases()` is unsupported in Firefox, where enumeration is
68
+ // impossible and this half is a no-op. The worker and the stores below are
69
+ // still cleared, so a reset there is partial rather than absent -- worth
70
+ // knowing, and better than refusing to run.
71
+ const found = (await indexedDB.databases?.()) ?? [];
72
+ for (const { name } of found) {
73
+ if (name == null || name === "")
74
+ continue;
75
+ await new Promise((resolve) => {
76
+ const request = indexedDB.deleteDatabase(name);
77
+ request.onsuccess = () => resolve();
78
+ request.onerror = () => resolve();
79
+ // A database still open in another tab blocks. Resolving anyway keeps
80
+ // the reset moving; the page reload that follows closes this tab's
81
+ // handle, and the delete completes then.
82
+ request.onblocked = () => resolve();
83
+ });
84
+ databases.push(name);
85
+ }
86
+ }
87
+ catch {
88
+ /* enumeration unsupported */
89
+ }
90
+ try {
91
+ localStorage?.clear();
92
+ }
93
+ catch {
94
+ /* blocked site data */
95
+ }
96
+ try {
97
+ sessionStorage?.clear();
98
+ }
99
+ catch {
100
+ /* blocked site data */
101
+ }
102
+ return { serviceWorkers, databases };
103
+ }
@@ -0,0 +1,214 @@
1
+ /**
2
+ * The page session: one persistent identity, resume-or-redeem on start, and
3
+ * the three operator controls -- join, disconnect, reset identity -- with
4
+ * no DOM anywhere in it.
5
+ *
6
+ * WHY EVERY CONSUMER PAGE SHARES THIS. Two pages differ in exactly one
7
+ * thing: what they mount and advertise. Everything else -- read the saved
8
+ * identity, try to resume, say which of the two "cannot resume" situations
9
+ * this is, offer an invitation field, disconnect without surrendering
10
+ * membership, reset the key -- is identical between them, down to the
11
+ * wording, and duplicating it would have been duplicating the wording too.
12
+ *
13
+ * NO DOM, AND NOTHING BROWSER-ONLY IMPORTED. This module decides things --
14
+ * which mesh to try, resume or redeem, what an operator is told -- and
15
+ * deciding is exactly what is worth testing, so `tests/session.test.ts`
16
+ * exercises every branch under Node with no browser, no relay and no
17
+ * ServiceWorker. That is only possible because the platform, the two stores
18
+ * and the reload are INJECTED rather than defaulted: `browserPlatform`
19
+ * reaches `@libp2p/webrtc`, whose Node build loads a native module, so a
20
+ * default here would have made importing this module a browser-only act.
21
+ * `./browser.ts`'s `createSession` is where those defaults live instead.
22
+ *
23
+ * Pages render `SessionState` and call the four methods; they hold no state
24
+ * of their own.
25
+ *
26
+ * THE CONFIG IS RESOLVED HERE, because `startMember` requires one and will
27
+ * not fetch anything itself (see `StartMemberInit.config`). That moved the
28
+ * `httpeers.json` read up into this module, which is the only place that
29
+ * knows which of the three candidates -- a blob's mesh, the remembered one,
30
+ * the deployment's -- applies to the attempt at hand.
31
+ *
32
+ * WHICH MESH IS TRIED, AND THE ONE RULE THAT MUST NOT BEND. When someone
33
+ * supplies an invitation, the invitation decides: a join blob names its own
34
+ * mesh, and a BARE invitation id still means "the mesh `httpeers.json`
35
+ * names", i.e. the Node hub's -- the contract `./join-blob.ts` states and
36
+ * the reason both deployments keep working. Only a RESUME (nobody supplied
37
+ * anything) consults `./mesh-memory.ts`, and only because a mesh whose hub
38
+ * is a browser page cannot be named any other way after the `?join=` query
39
+ * has gone. See `./mesh-memory.ts`.
40
+ *
41
+ * DISCONNECT IS NOT LEAVING. `MemberHandle.stop()` drops presence and
42
+ * keeps membership, which is what makes the page resumable afterwards with
43
+ * no new invitation; see that method's own doc comment. This module never
44
+ * calls anything that surrenders membership, and the one control that
45
+ * destroys something -- `resetIdentity` -- says what it costs and is named
46
+ * separately.
47
+ */
48
+ import type { Ed25519PrivateKey } from "@libp2p/interface";
49
+ import type { RuleSet } from "@statewalker/httpeers-access";
50
+ import type { MeshConfig as HttpeersConfig, Mounts } from "@statewalker/httpeers-core";
51
+ import type { IdentityStoreInit } from "./identity.js";
52
+ import type { AdvertisementInput } from "./join.js";
53
+ import type { MeshMemory } from "./mesh-memory.js";
54
+ import type { HubLink, JoinMethod, MemberHandle, MemberPlatform, MemberState, StartMemberInit } from "./start-member.js";
55
+ /** Why this page is asking for an invitation. The two that matter are the first two -- see `SessionPhase`. */
56
+ export type NeedsInvitationReason =
57
+ /** This browser has never held an identity for this origin: a genuine first run. */
58
+ "no-identity"
59
+ /** This browser holds an identity, the hub was reachable, and it does not know it. */
60
+ | "unknown-identity"
61
+ /** An invitation was supplied and the hub refused it (spent, expired, unknown). */
62
+ | "invitation-refused"
63
+ /** Nothing was supplied and nothing is remembered, so there is no mesh to even try. */
64
+ | "no-mesh";
65
+ /**
66
+ * Where the session is. Every one of these is a distinct thing to say on
67
+ * screen, which is the only reason there are six.
68
+ *
69
+ * `needs-invitation` CARRIES ITS REASON because "no saved identity yet" and
70
+ * "saved identity, but this hub does not know it" are not the same
71
+ * situation and must not read the same (Task 28, requirement 3). The first
72
+ * is a page that has never run; the second is a hub reset or a revoked
73
+ * membership, and an operator told only "please paste an invitation" would
74
+ * have no way to tell which had happened -- or that their saved identity is
75
+ * intact and simply unrecognised.
76
+ *
77
+ * `blocked` is the duplicate-identity refusal, kept apart from `failed`
78
+ * because it is not a fault to retry: two tabs are running one libp2p
79
+ * identity, and something has to give before this page can proceed.
80
+ */
81
+ /**
82
+ * What `starting` reports.
83
+ *
84
+ * `"loading-config"` is the SESSION'S OWN step, not the member's: resolving
85
+ * which mesh to try can mean an `httpeers.json` fetch, which is the one part
86
+ * of a start that happens before `startMember` exists to have a state. Every
87
+ * other value is `MemberState` verbatim, forwarded.
88
+ */
89
+ export type SessionStartState = "loading-config" | MemberState;
90
+ export type SessionPhase = {
91
+ kind: "checking";
92
+ } | {
93
+ kind: "starting";
94
+ peerState: SessionStartState;
95
+ } | {
96
+ kind: "live";
97
+ joinedBy: JoinMethod;
98
+ note: string | null;
99
+ } | {
100
+ kind: "needs-invitation";
101
+ reason: NeedsInvitationReason;
102
+ message: string;
103
+ } | {
104
+ kind: "disconnected";
105
+ message: string;
106
+ } | {
107
+ kind: "blocked";
108
+ message: string;
109
+ } | {
110
+ kind: "failed";
111
+ message: string;
112
+ };
113
+ /** Which controls make sense in the current phase. Derived here, once, rather than by each page re-deriving it from `phase.kind`. */
114
+ export interface SessionControls {
115
+ join: boolean;
116
+ disconnect: boolean;
117
+ reconnect: boolean;
118
+ reset: boolean;
119
+ }
120
+ export interface SessionState {
121
+ phase: SessionPhase;
122
+ /** The peerId this origin holds, or `null` when it has never had one. Known before anything is dialled. */
123
+ identity: string | null;
124
+ /** Live only while `phase.kind === "live"`. */
125
+ handle: MemberHandle | null;
126
+ /** How the live member reaches its hub (`MemberHandle.hubLink`, fixed for the member's run); `null` unless live. */
127
+ hubLink: HubLink | null;
128
+ controls: SessionControls;
129
+ }
130
+ /** `startMember`'s shape, as a seam -- see the module comment. */
131
+ export type StartPeer = (init: StartMemberInit) => Promise<MemberHandle>;
132
+ /** The identity store's three operations, as a seam. Defaults to `./identity.ts` over the real IndexedDB. */
133
+ export interface IdentityStore {
134
+ read(): Promise<Ed25519PrivateKey | null>;
135
+ loadOrCreate(): Promise<Ed25519PrivateKey>;
136
+ clear(): Promise<void>;
137
+ }
138
+ export declare function createIdentityStore(init: IdentityStoreInit): IdentityStore;
139
+ export interface PeerSessionInit {
140
+ /** Everything `startMember` needs that is this PAGE's own, and nothing that is the session's. */
141
+ key: string;
142
+ mounts: Mounts;
143
+ /** This page's own rule set, already built -- see `StartMemberInit.rules`. */
144
+ rules: RuleSet;
145
+ advertisements?: () => AdvertisementInput[];
146
+ /**
147
+ * How this host builds a node, mounts an edge and hears about wakes.
148
+ * REQUIRED, and deliberately: see the module comment on why a default
149
+ * would make importing this module browser-only.
150
+ */
151
+ platform: MemberPlatform;
152
+ /** This page's `location.search`, read once at `start()`. */
153
+ search: string;
154
+ /** Fired on every state change, including the first. */
155
+ onChange: (state: SessionState) => void;
156
+ /** Where this origin's key lives. Required for the same reason `./mesh-memory.ts`'s backend is. */
157
+ identity: IdentityStore;
158
+ /** The mesh this origin last joined. Required for the same reason. */
159
+ meshMemory: MeshMemory;
160
+ /**
161
+ * Reloading the host after a reset. REQUIRED because `location` is the one
162
+ * DOM global this module would otherwise have to name; `./browser.ts`
163
+ * supplies it.
164
+ */
165
+ reload: () => void;
166
+ startPeer?: StartPeer;
167
+ /**
168
+ * The mesh this DEPLOYMENT names (`httpeers.json`).
169
+ *
170
+ * READ FOR TWO REASONS. It is the fallback mesh -- what a bare invitation
171
+ * id means, and what a page with an identity but no remembered mesh tries.
172
+ * And it explains a failure: when a page cannot resume against the mesh it
173
+ * REMEMBERS and the deployment now names a different hub, that is almost
174
+ * always a re-run of `pnpm bootstrap` (or a switch between the Node hub and
175
+ * a hub page), and saying so is the difference between a two-minute fix and
176
+ * an hour.
177
+ */
178
+ readDeploymentConfig?: () => Promise<HttpeersConfig | null>;
179
+ }
180
+ export interface PeerSession {
181
+ /** Read the saved identity and try to resume. Call once, on page load. */
182
+ start(): Promise<void>;
183
+ /** Join with what someone typed or pasted: a blob, a bare invitation id, or a whole join link. */
184
+ join(text: string): Promise<void>;
185
+ /** Stop this peer: drop presence, KEEP membership -- see `MemberHandle.stop`. */
186
+ disconnect(): Promise<void>;
187
+ /** Come back after a `disconnect`, by resuming. No invitation involved. */
188
+ reconnect(): Promise<void>;
189
+ /** Forget this origin's key, so the next run is a different peer. Destructive -- see below. */
190
+ resetIdentity(): Promise<void>;
191
+ state(): SessionState;
192
+ }
193
+ /**
194
+ * Where a deployment publishes the mesh it names.
195
+ *
196
+ * A SITE-ABSOLUTE PATH, so every page of an origin reads the same file
197
+ * whatever directory it was served from. It left with `peer-runtime.ts` and
198
+ * is declared here because this module is now the only thing that reads it.
199
+ */
200
+ export declare const DEFAULT_HTTPEERS_CONFIG_URL = "/httpeers.json";
201
+ /**
202
+ * The sentence added to a "this hub does not know you" message when the
203
+ * deployment has visibly moved underneath the page.
204
+ *
205
+ * THE CASE THIS EXISTS FOR: `pnpm bootstrap` was re-run, so `httpeers.json`
206
+ * now names a hub with a different peerId than the one this page last
207
+ * joined. Everything about the page is fine, the hub it is talking to is
208
+ * fine, and the membership is simply in a mesh that no longer exists. With
209
+ * nothing said, that presents as "please paste an invitation" for reasons
210
+ * an operator cannot see. Exported for its own test.
211
+ */
212
+ export declare function describeMeshDrift(tried: string, deployment: HttpeersConfig | null): string | null;
213
+ export declare function createPeerSession(init: PeerSessionInit): PeerSession;
214
+ //# sourceMappingURL=session.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"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"}