@dcasegr/skein 0.0.0-stage → 0.1.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 +525 -2
- package/bin/skein-host.js +22 -0
- package/bin/skein.js +8 -0
- package/dist/client/admin-cli.d.ts +10 -0
- package/dist/client/admin-cli.js +370 -0
- package/dist/client/admin.d.ts +139 -0
- package/dist/client/admin.js +256 -0
- package/dist/client/bundle.d.ts +9 -0
- package/dist/client/bundle.js +34 -0
- package/dist/client/cli.d.ts +44 -0
- package/dist/client/cli.js +306 -0
- package/dist/client/client.d.ts +161 -0
- package/dist/client/client.js +237 -0
- package/dist/client/config.d.ts +18 -0
- package/dist/client/config.js +31 -0
- package/dist/client/conversation.d.ts +62 -0
- package/dist/client/conversation.js +113 -0
- package/dist/client/raw.d.ts +94 -0
- package/dist/client/raw.js +154 -0
- package/dist/client/target.d.ts +34 -0
- package/dist/client/target.js +158 -0
- package/dist/dev/cli.d.ts +8 -0
- package/dist/dev/cli.js +248 -0
- package/dist/dev/scan.d.ts +15 -0
- package/dist/dev/scan.js +173 -0
- package/dist/envelope-cbor.d.ts +110 -0
- package/dist/envelope-cbor.js +160 -0
- package/dist/envelope.d.ts +38 -0
- package/dist/envelope.js +101 -0
- package/dist/host/arc.d.ts +131 -0
- package/dist/host/arc.js +425 -0
- package/dist/host/billing.d.ts +102 -0
- package/dist/host/billing.js +167 -0
- package/dist/host/boot.d.ts +246 -0
- package/dist/host/boot.js +643 -0
- package/dist/host/cli.d.ts +57 -0
- package/dist/host/cli.js +1253 -0
- package/dist/host/clock.d.ts +3 -0
- package/dist/host/clock.js +8 -0
- package/dist/host/control.d.ts +45 -0
- package/dist/host/control.js +170 -0
- package/dist/host/cron.d.ts +99 -0
- package/dist/host/cron.js +196 -0
- package/dist/host/deliver.d.ts +53 -0
- package/dist/host/deliver.js +251 -0
- package/dist/host/deploy.d.ts +29 -0
- package/dist/host/deploy.js +55 -0
- package/dist/host/door-answer.d.ts +24 -0
- package/dist/host/door-answer.js +21 -0
- package/dist/host/events.d.ts +89 -0
- package/dist/host/events.js +222 -0
- package/dist/host/fake-arcade.d.ts +40 -0
- package/dist/host/fake-arcade.js +149 -0
- package/dist/host/fake-discovery.d.ts +4 -0
- package/dist/host/fake-discovery.js +58 -0
- package/dist/host/feeds.d.ts +127 -0
- package/dist/host/feeds.js +422 -0
- package/dist/host/frontdoor.d.ts +92 -0
- package/dist/host/frontdoor.js +172 -0
- package/dist/host/genesis.d.ts +259 -0
- package/dist/host/genesis.js +353 -0
- package/dist/host/handles.d.ts +40 -0
- package/dist/host/handles.js +88 -0
- package/dist/host/hostenv.d.ts +30 -0
- package/dist/host/hostenv.js +83 -0
- package/dist/host/image-chain.d.ts +127 -0
- package/dist/host/image-chain.js +440 -0
- package/dist/host/install.d.ts +22 -0
- package/dist/host/install.js +139 -0
- package/dist/host/instances.d.ts +227 -0
- package/dist/host/instances.js +430 -0
- package/dist/host/kernel.d.ts +246 -0
- package/dist/host/kernel.js +603 -0
- package/dist/host/liveness.d.ts +65 -0
- package/dist/host/liveness.js +155 -0
- package/dist/host/manifest.d.ts +160 -0
- package/dist/host/manifest.js +597 -0
- package/dist/host/p2p.d.ts +337 -0
- package/dist/host/p2p.js +1002 -0
- package/dist/host/packet.d.ts +121 -0
- package/dist/host/packet.js +562 -0
- package/dist/host/plan.d.ts +154 -0
- package/dist/host/plan.js +418 -0
- package/dist/host/providers.d.ts +238 -0
- package/dist/host/providers.js +565 -0
- package/dist/host/roster.d.ts +72 -0
- package/dist/host/roster.js +172 -0
- package/dist/host/router.d.ts +782 -0
- package/dist/host/router.js +2411 -0
- package/dist/host/signer.d.ts +49 -0
- package/dist/host/signer.js +167 -0
- package/dist/host/store-sqlite.d.ts +39 -0
- package/dist/host/store-sqlite.js +170 -0
- package/dist/host/stores.d.ts +43 -0
- package/dist/host/stores.js +342 -0
- package/dist/host/sync.d.ts +121 -0
- package/dist/host/sync.js +467 -0
- package/dist/host/testbeef.d.ts +21 -0
- package/dist/host/testbeef.js +37 -0
- package/dist/host/testhost.d.ts +149 -0
- package/dist/host/testhost.js +211 -0
- package/dist/host/vcdiff.d.ts +9 -0
- package/dist/host/vcdiff.js +210 -0
- package/dist/host/wasm-kernel.d.ts +87 -0
- package/dist/host/wasm-kernel.js +296 -0
- package/dist/host/wasm-worker.d.ts +1 -0
- package/dist/host/wasm-worker.js +186 -0
- package/dist/peers/cron.d.ts +33 -0
- package/dist/peers/cron.js +75 -0
- package/dist/peers/infer-main.d.ts +2 -0
- package/dist/peers/infer-main.js +74 -0
- package/dist/peers/infer.d.ts +163 -0
- package/dist/peers/infer.js +367 -0
- package/dist/runtime/beef.d.ts +28 -0
- package/dist/runtime/beef.js +107 -0
- package/dist/runtime/bitcoin.d.ts +36 -0
- package/dist/runtime/bitcoin.js +141 -0
- package/dist/runtime/cid.d.ts +14 -0
- package/dist/runtime/cid.js +51 -0
- package/dist/runtime/dispatch.d.ts +92 -0
- package/dist/runtime/dispatch.js +142 -0
- package/dist/runtime/envelope.d.ts +60 -0
- package/dist/runtime/envelope.js +101 -0
- package/dist/runtime/heads.d.ts +25 -0
- package/dist/runtime/heads.js +37 -0
- package/dist/runtime/identity.d.ts +28 -0
- package/dist/runtime/identity.js +67 -0
- package/dist/runtime/index-store.d.ts +152 -0
- package/dist/runtime/index-store.js +715 -0
- package/dist/runtime/log.d.ts +62 -0
- package/dist/runtime/log.js +104 -0
- package/dist/runtime/programs.d.ts +30 -0
- package/dist/runtime/programs.js +61 -0
- package/dist/runtime/records.d.ts +135 -0
- package/dist/runtime/records.js +149 -0
- package/dist/runtime/sqlite.d.ts +33 -0
- package/dist/runtime/sqlite.js +604 -0
- package/dist/runtime/store.d.ts +141 -0
- package/dist/runtime/store.js +24 -0
- package/dist/runtime/syscalls.d.ts +9 -0
- package/dist/runtime/syscalls.js +41 -0
- package/dist/runtime/tree.d.ts +60 -0
- package/dist/runtime/tree.js +196 -0
- package/dist/runtime/types.d.ts +100 -0
- package/dist/runtime/types.js +7 -0
- package/dist/testapps.d.ts +101 -0
- package/dist/testapps.js +275 -0
- package/dist/testkit.d.ts +25 -0
- package/dist/testkit.js +48 -0
- package/dist/wallet.d.ts +35 -0
- package/dist/wallet.js +87 -0
- package/images/README.md +68 -0
- package/images/default/README.md +38 -0
- package/images/default/bin/frontdoor.cid +1 -0
- package/images/default/bin/frontdoor.json +5 -0
- package/images/default/bin/messagebox.cid +1 -0
- package/images/default/bin/messagebox.json +8 -0
- package/images/default/bin/wallet.cid +1 -0
- package/images/default/bin/wallet.json +14 -0
- package/images/default/etc/config.json +1 -0
- package/images/default/etc/dispatch.json +82 -0
- package/images/host/etc/apps.json +8 -0
- package/kernel-zig/README.md +1247 -0
- package/kernel-zig/web/client.js +65 -0
- package/kernel-zig/web/kernel.d.ts +59 -0
- package/kernel-zig/web/kernel.js +329 -0
- package/kernel-zig/web/store.js +67 -0
- package/kernel-zig/web/worker.js +79 -0
- package/kernel-zig/zig-out/web/skein-kernel.wasm +0 -0
- package/package.json +77 -4
- package/wasm/README.md +35 -0
- package/wasm/frontdoor.wasm +0 -0
- package/wasm/messagebox.wasm +0 -0
- package/wasm/resolve.wasm +0 -0
- package/wasm/wallet.wasm +0 -0
- package/wasm/wire-probe.wasm +0 -0
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
// The `deliver` intention (#157): a message to a `mailbox` recipient, carried
|
|
2
|
+
// by the HOST — a step never waits on the outside world. The instance's
|
|
3
|
+
// delivery thread (programs/messagebox/deliver.zig) emits
|
|
4
|
+
//
|
|
5
|
+
// {kind: "event", event: "deliver", app?, transport: "mailbox", url, message: <the mail record>,
|
|
6
|
+
// nonce: bytes(32)} the nonce: sha256(thread ‖ step ‖ place) — the kernel's
|
|
7
|
+
//
|
|
8
|
+
// and awaits it. The host makes the exchange the kernel's authfetch used to
|
|
9
|
+
// make in the step (kernel-zig/src/authfetch.zig before #157):
|
|
10
|
+
//
|
|
11
|
+
// 1. BRC-103's handshake at <url>/.well-known/auth, the session kept per
|
|
12
|
+
// (instance, base URL) in memory — made again when the server answers
|
|
13
|
+
// 401 (it forgot the session) — its answer's signature checked;
|
|
14
|
+
// 2. the BRC-104 request: POST <url>/sendMessage, BRC-231 CBOR {message:
|
|
15
|
+
// {recipient, messageBox, body, subject?, nonce}} (the BRC-33 message and
|
|
16
|
+
// the emit's own subject and nonce: the recipient keeps the record under
|
|
17
|
+
// the same CID), the request id = the event's nonce (so is the request's
|
|
18
|
+
// x-bsv-auth-nonce: everything about it derives from the emit's place),
|
|
19
|
+
// signed by the instance's key through its wallet (the signer the kernel
|
|
20
|
+
// asks), outside any step;
|
|
21
|
+
// 3. the answer, as the record the instance admits (box `deliver`): {kind:
|
|
22
|
+
// "delivery", replyTo: <the event>, peer, nonce: <our session nonce>,
|
|
23
|
+
// yourNonce: <the answer's nonce>, signature, status, headers?: <the
|
|
24
|
+
// answer's signed headers>, body} — it stands on its own at the door
|
|
25
|
+
// (door.zig deliveryAnswer: the server's signature over it).
|
|
26
|
+
//
|
|
27
|
+
// No answer, or an unsigned one (something in front of the server): nothing
|
|
28
|
+
// to admit — the thread rests, and the kernel offers the intention again at
|
|
29
|
+
// its next start. No timers, no retries but the one fresh handshake a 401 on a
|
|
30
|
+
// held session calls for (BRC-103's own).
|
|
31
|
+
import * as dagCbor from "@ipld/dag-cbor";
|
|
32
|
+
export const AUTH_PROTOCOL = [2, "auth message signature"];
|
|
33
|
+
const VERSION = "0.1";
|
|
34
|
+
const hex = (b) => Buffer.from(b).toString("hex");
|
|
35
|
+
const b64 = (b) => Buffer.from(b).toString("base64");
|
|
36
|
+
// ---------------------------------------------------------------- SimplifiedFetchTransport's framing
|
|
37
|
+
class W {
|
|
38
|
+
parts = [];
|
|
39
|
+
varint(v) {
|
|
40
|
+
if (v < 0) {
|
|
41
|
+
this.parts.push(0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff);
|
|
42
|
+
return;
|
|
43
|
+
} // -1: absent
|
|
44
|
+
if (v < 0xfd)
|
|
45
|
+
this.parts.push(v);
|
|
46
|
+
else if (v <= 0xffff)
|
|
47
|
+
this.parts.push(0xfd, v & 0xff, v >> 8);
|
|
48
|
+
else if (v <= 0xffffffff) {
|
|
49
|
+
const b = Buffer.alloc(4);
|
|
50
|
+
b.writeUInt32LE(v);
|
|
51
|
+
this.parts.push(0xfe, ...b);
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
const b = Buffer.alloc(8);
|
|
55
|
+
b.writeBigUInt64LE(BigInt(v));
|
|
56
|
+
this.parts.push(0xff, ...b);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
bytes(b) { for (const x of b)
|
|
60
|
+
this.parts.push(x); }
|
|
61
|
+
field(b) { if (b.length === 0)
|
|
62
|
+
this.varint(-1);
|
|
63
|
+
else {
|
|
64
|
+
this.varint(b.length);
|
|
65
|
+
this.bytes(b);
|
|
66
|
+
} }
|
|
67
|
+
out() { return Uint8Array.from(this.parts); }
|
|
68
|
+
}
|
|
69
|
+
const utf8 = (s) => new TextEncoder().encode(s);
|
|
70
|
+
/** The headers a request signs: x-bsv-* (not x-bsv-auth*), content-type (its media type), authorization; lower-cased, sorted. */
|
|
71
|
+
function signedRequestHeaders(h) {
|
|
72
|
+
const out = [];
|
|
73
|
+
for (const [k0, v0] of Object.entries(h)) {
|
|
74
|
+
const k = k0.toLowerCase();
|
|
75
|
+
let v = v0;
|
|
76
|
+
if (k === "content-type")
|
|
77
|
+
v = v.split(";")[0].trim();
|
|
78
|
+
if ((k.startsWith("x-bsv-") || k === "content-type" || k === "authorization") && !k.startsWith("x-bsv-auth"))
|
|
79
|
+
out.push([k, v]);
|
|
80
|
+
}
|
|
81
|
+
return out.sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0));
|
|
82
|
+
}
|
|
83
|
+
/** The payload a request signs (authfetch.zig requestPayload). */
|
|
84
|
+
export function requestPayload(rid, method, path, query, headers, body) {
|
|
85
|
+
const w = new W();
|
|
86
|
+
w.bytes(rid);
|
|
87
|
+
const m = utf8(method);
|
|
88
|
+
w.varint(m.length);
|
|
89
|
+
w.bytes(m);
|
|
90
|
+
w.field(utf8(path));
|
|
91
|
+
w.field(utf8(query));
|
|
92
|
+
const hs = signedRequestHeaders(headers);
|
|
93
|
+
w.varint(hs.length);
|
|
94
|
+
for (const [k, v] of hs) {
|
|
95
|
+
const kb = utf8(k), vb = utf8(v);
|
|
96
|
+
w.varint(kb.length);
|
|
97
|
+
w.bytes(kb);
|
|
98
|
+
w.varint(vb.length);
|
|
99
|
+
w.bytes(vb);
|
|
100
|
+
}
|
|
101
|
+
w.field(body);
|
|
102
|
+
return w.out();
|
|
103
|
+
}
|
|
104
|
+
/** The answer's signed headers: x-bsv-* but x-bsv-auth-*, and authorization; lower-cased. */
|
|
105
|
+
export function signedResponseHeaders(h) {
|
|
106
|
+
const out = {};
|
|
107
|
+
for (const [k0, v] of Object.entries(h)) {
|
|
108
|
+
const k = k0.toLowerCase();
|
|
109
|
+
if ((k.startsWith("x-bsv-") && !k.startsWith("x-bsv-auth")) || k === "authorization")
|
|
110
|
+
out[k] = v;
|
|
111
|
+
}
|
|
112
|
+
return out;
|
|
113
|
+
}
|
|
114
|
+
/** The payload a response signs (authfetch.zig responsePayload): request id, status, the signed headers sorted, the body. */
|
|
115
|
+
export function responsePayload(rid, status, headers, body) {
|
|
116
|
+
const w = new W();
|
|
117
|
+
w.bytes(rid);
|
|
118
|
+
w.varint(status);
|
|
119
|
+
const hs = Object.entries(headers).sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0));
|
|
120
|
+
w.varint(hs.length);
|
|
121
|
+
for (const [k, v] of hs) {
|
|
122
|
+
const kb = utf8(k), vb = utf8(v);
|
|
123
|
+
w.varint(kb.length);
|
|
124
|
+
w.bytes(kb);
|
|
125
|
+
w.varint(vb.length);
|
|
126
|
+
w.bytes(vb);
|
|
127
|
+
}
|
|
128
|
+
w.field(body);
|
|
129
|
+
return w.out();
|
|
130
|
+
}
|
|
131
|
+
export class Deliverer {
|
|
132
|
+
o;
|
|
133
|
+
/** BRC-103 sessions, per instance and server base URL, in memory. */
|
|
134
|
+
sessions = new Map();
|
|
135
|
+
constructor(o) { this.o = o; }
|
|
136
|
+
/**
|
|
137
|
+
* Carry one `deliver` event for instance `handle` (its wallet: the instance's key), its mail record
|
|
138
|
+
* and the body's dag-cbor bytes: the answer to admit, or undefined (no answer that stands).
|
|
139
|
+
*/
|
|
140
|
+
async carry(handle, wallet, eventCid, ev, mail, body) {
|
|
141
|
+
if (ev.transport !== "mailbox")
|
|
142
|
+
throw new Error(`deliver: no transport ${ev.transport} here`);
|
|
143
|
+
if (!(ev.nonce instanceof Uint8Array) || ev.nonce.length !== 32)
|
|
144
|
+
throw new Error("deliver: the event has no 32-byte nonce");
|
|
145
|
+
const base = ev.url.replace(/\/+$/, "");
|
|
146
|
+
const identity = (await wallet.getPublicKey({ identityKey: true })).publicKey;
|
|
147
|
+
const key = `${handle} ${base}`;
|
|
148
|
+
const message = { recipient: mail.recipient, messageBox: mail.box, body, nonce: mail.nonce };
|
|
149
|
+
if (mail.subject)
|
|
150
|
+
message.subject = mail.subject;
|
|
151
|
+
const reqBody = dagCbor.encode({ message });
|
|
152
|
+
for (let fresh = false;; fresh = true) {
|
|
153
|
+
let s = this.sessions.get(key);
|
|
154
|
+
const held = !!s && !fresh;
|
|
155
|
+
if (!s || fresh) {
|
|
156
|
+
s = await this.handshake(handle, wallet, identity, base);
|
|
157
|
+
if (!s)
|
|
158
|
+
return undefined;
|
|
159
|
+
this.sessions.set(key, s);
|
|
160
|
+
}
|
|
161
|
+
const r = await this.send(handle, wallet, identity, base, s, ev.nonce, reqBody);
|
|
162
|
+
if (!r)
|
|
163
|
+
return undefined;
|
|
164
|
+
if (r.status === 401 && held) {
|
|
165
|
+
this.sessions.delete(key);
|
|
166
|
+
continue;
|
|
167
|
+
} // the server forgot the session: shake hands again, once
|
|
168
|
+
const sig = r.headers["x-bsv-auth-signature"];
|
|
169
|
+
if (!sig) {
|
|
170
|
+
this.o.log?.(handle, `deliver → ${base}: HTTP ${r.status}, unsigned: nothing to admit (the thread rests; offered again at the next start)`);
|
|
171
|
+
return undefined;
|
|
172
|
+
}
|
|
173
|
+
const from = r.headers["x-bsv-auth-identity-key"] ?? "";
|
|
174
|
+
if (from !== s.server) {
|
|
175
|
+
this.o.log?.(handle, `deliver → ${base}: the answer is from another identity than the session's: nothing to admit`);
|
|
176
|
+
return undefined;
|
|
177
|
+
}
|
|
178
|
+
const headers = signedResponseHeaders(r.headers);
|
|
179
|
+
return {
|
|
180
|
+
kind: "delivery", replyTo: eventCid, peer: Uint8Array.from(Buffer.from(s.server, "hex")), nonce: s.ours, yourNonce: r.headers["x-bsv-auth-nonce"] ?? "",
|
|
181
|
+
signature: Uint8Array.from(Buffer.from(sig, "hex")), status: r.status, ...(Object.keys(headers).length ? { headers } : {}), body: r.body,
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
/** BRC-103: our nonce to <base>/.well-known/auth; the answer's signature over both nonces checked. */
|
|
186
|
+
async handshake(handle, wallet, identity, base) {
|
|
187
|
+
const first = globalThis.crypto.getRandomValues(new Uint8Array(16)).map((b) => 33 + (b % 94)); // printable: the keyID reads the same as UTF-8 on every side
|
|
188
|
+
const { hmac } = await wallet.createHmac({ protocolID: [2, "server hmac"], keyID: new TextDecoder().decode(first), data: [...first], counterparty: "self" });
|
|
189
|
+
const ours = b64([...first, ...hmac]);
|
|
190
|
+
const body = JSON.stringify({ version: VERSION, messageType: "initialRequest", identityKey: identity, initialNonce: ours, requestedCertificates: { certifiers: [], types: {} } });
|
|
191
|
+
let r;
|
|
192
|
+
try {
|
|
193
|
+
r = await this.o.http({ method: "POST", url: `${base}/.well-known/auth`, headers: { "content-type": "application/json" }, body: utf8(body) }, handle);
|
|
194
|
+
}
|
|
195
|
+
catch (e) {
|
|
196
|
+
this.o.log?.(handle, `deliver → ${base}: handshake: ${e.message} (the thread rests)`);
|
|
197
|
+
return undefined;
|
|
198
|
+
}
|
|
199
|
+
if (r.status !== 200) {
|
|
200
|
+
this.o.log?.(handle, `deliver → ${base}: handshake: HTTP ${r.status} (the thread rests)`);
|
|
201
|
+
return undefined;
|
|
202
|
+
}
|
|
203
|
+
let j;
|
|
204
|
+
try {
|
|
205
|
+
j = JSON.parse(new TextDecoder().decode(r.body));
|
|
206
|
+
}
|
|
207
|
+
catch {
|
|
208
|
+
this.o.log?.(handle, `deliver → ${base}: handshake: the answer is not JSON`);
|
|
209
|
+
return undefined;
|
|
210
|
+
}
|
|
211
|
+
if (typeof j.identityKey !== "string" || typeof j.initialNonce !== "string" || j.yourNonce !== ours || !Array.isArray(j.signature)) {
|
|
212
|
+
this.o.log?.(handle, `deliver → ${base}: handshake: a malformed answer`);
|
|
213
|
+
return undefined;
|
|
214
|
+
}
|
|
215
|
+
const data = [...Buffer.from(ours, "base64"), ...Buffer.from(j.initialNonce, "base64")];
|
|
216
|
+
try {
|
|
217
|
+
const { valid } = await wallet.verifySignature({ protocolID: AUTH_PROTOCOL, keyID: `${ours} ${j.initialNonce}`, counterparty: j.identityKey, data, signature: j.signature });
|
|
218
|
+
if (!valid)
|
|
219
|
+
throw new Error("invalid");
|
|
220
|
+
}
|
|
221
|
+
catch {
|
|
222
|
+
this.o.log?.(handle, `deliver → ${base}: handshake: the server's signature does not verify`);
|
|
223
|
+
return undefined;
|
|
224
|
+
}
|
|
225
|
+
return { server: j.identityKey, ours, theirs: j.initialNonce };
|
|
226
|
+
}
|
|
227
|
+
/** BRC-104: the request signed on the session (request id and nonce = the event's nonce). Undefined: no answer. */
|
|
228
|
+
async send(handle, wallet, identity, base, s, rid, body) {
|
|
229
|
+
const u = new URL(base);
|
|
230
|
+
const path = `${u.pathname === "/" ? "" : u.pathname}/sendMessage`;
|
|
231
|
+
const headers = { "content-type": "application/cbor" };
|
|
232
|
+
const nonce = b64(rid);
|
|
233
|
+
const payload = requestPayload(rid, "POST", path, "", headers, body);
|
|
234
|
+
const { signature } = await wallet.createSignature({ protocolID: AUTH_PROTOCOL, keyID: `${nonce} ${s.theirs}`, counterparty: s.server, data: [...payload] });
|
|
235
|
+
const all = {
|
|
236
|
+
...headers, "x-bsv-auth-version": VERSION, "x-bsv-auth-identity-key": identity, "x-bsv-auth-nonce": nonce,
|
|
237
|
+
"x-bsv-auth-your-nonce": s.theirs, "x-bsv-auth-signature": hex(signature), "x-bsv-auth-request-id": nonce,
|
|
238
|
+
};
|
|
239
|
+
try {
|
|
240
|
+
const r = await this.o.http({ method: "POST", url: `${u.origin}${path}`, headers: all, body }, handle);
|
|
241
|
+
const lower = {};
|
|
242
|
+
for (const [k, v] of Object.entries(r.headers))
|
|
243
|
+
lower[k.toLowerCase()] = v;
|
|
244
|
+
return { status: r.status, headers: lower, body: r.body };
|
|
245
|
+
}
|
|
246
|
+
catch (e) {
|
|
247
|
+
this.o.log?.(handle, `deliver → ${base}: ${e.message} (the thread rests; offered again at the next start)`);
|
|
248
|
+
return undefined;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { Store } from "../runtime/store.ts";
|
|
2
|
+
/** What goes in by default: the personality and its file-only skills (#24), not the code around them. */
|
|
3
|
+
export declare const DEFAULT_ONLY: string[];
|
|
4
|
+
/**
|
|
5
|
+
* scan's `ignore` for `--only`: a path is kept if a pattern names it or a
|
|
6
|
+
* directory above it (a pattern naming a directory takes all of it), or if it
|
|
7
|
+
* is a directory on the way to what a pattern names. Patterns are
|
|
8
|
+
* "/"-separated from the directory's root, `*` and `?` within one segment.
|
|
9
|
+
* .git and node_modules are never kept.
|
|
10
|
+
*/
|
|
11
|
+
export declare function onlyIgnore(dir: string, patterns: string[]): (rel: string) => boolean;
|
|
12
|
+
/**
|
|
13
|
+
* An address-book entry (#70): how to reach a key — its transport
|
|
14
|
+
* (`mailbox`: a messagebox URL over BRC-103/104; `libp2p`: a peer ID or
|
|
15
|
+
* `topic:<name>`; `local`: a host provider's name) and address, and
|
|
16
|
+
* optionally its handle (#126: no role). The instance keeps them under the head `peers` (programs/resolve);
|
|
17
|
+
* `source` says who wrote one (`genesis`, `admin`: the `peers` box,
|
|
18
|
+
* `resolve`: its own BRC-169 lookup, `claim`).
|
|
19
|
+
*/
|
|
20
|
+
export interface AddressEntry {
|
|
21
|
+
key: string;
|
|
22
|
+
transport: "mailbox" | "libp2p" | "local";
|
|
23
|
+
address: string;
|
|
24
|
+
handle?: string;
|
|
25
|
+
domain?: string;
|
|
26
|
+
source?: string;
|
|
27
|
+
}
|
|
28
|
+
/** The row's address book as its store holds it now (empty with no store or no entries). */
|
|
29
|
+
export declare function addressBook(store: Store | undefined): Promise<AddressEntry[]>;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// A directory into an instance (#23) and its address book (#40, #70): the
|
|
2
|
+
// pieces the owner's messages are built from (#124: src/client/admin.ts
|
|
3
|
+
// planDeploy, planPeers; `skein deploy|peers`). A deploy is the
|
|
4
|
+
// directory's git objects in ≤ 1 MiB bundles to the instance's `objects` box,
|
|
5
|
+
// the root tree on the last — the kernel sets `main` from it when there is
|
|
6
|
+
// none — then `head {name: "main", tree}` when `main` is another tree. Which
|
|
7
|
+
// files go in is `onlyIgnore` (default DEFAULT_ONLY: the personality and its
|
|
8
|
+
// skills, #24). The owner's wallet sends them; nothing here does.
|
|
9
|
+
import { lstatSync } from "node:fs";
|
|
10
|
+
import { join } from "node:path";
|
|
11
|
+
import { defaultIgnore } from "../dev/scan.js";
|
|
12
|
+
import { headTree } from "../runtime/heads.js";
|
|
13
|
+
/** What goes in by default: the personality and its file-only skills (#24), not the code around them. */
|
|
14
|
+
export const DEFAULT_ONLY = ["SOUL.md", "IDENTITY.md", "skills"];
|
|
15
|
+
const segment = (p) => new RegExp(`^${p.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, "[^/]*").replace(/\?/g, "[^/]")}$`);
|
|
16
|
+
/**
|
|
17
|
+
* scan's `ignore` for `--only`: a path is kept if a pattern names it or a
|
|
18
|
+
* directory above it (a pattern naming a directory takes all of it), or if it
|
|
19
|
+
* is a directory on the way to what a pattern names. Patterns are
|
|
20
|
+
* "/"-separated from the directory's root, `*` and `?` within one segment.
|
|
21
|
+
* .git and node_modules are never kept.
|
|
22
|
+
*/
|
|
23
|
+
export function onlyIgnore(dir, patterns) {
|
|
24
|
+
const pats = patterns.map((p) => p.split("/").filter(Boolean).map(segment)).filter((p) => p.length);
|
|
25
|
+
return (rel) => {
|
|
26
|
+
if (defaultIgnore(rel))
|
|
27
|
+
return true;
|
|
28
|
+
const segs = rel.split("/");
|
|
29
|
+
for (const p of pats) {
|
|
30
|
+
if (!p.slice(0, segs.length).every((re, i) => re.test(segs[i])))
|
|
31
|
+
continue;
|
|
32
|
+
if (segs.length >= p.length)
|
|
33
|
+
return false;
|
|
34
|
+
if (lstatSync(join(dir, rel)).isDirectory())
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
return true;
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
const hexKey = (k) => k instanceof Uint8Array ? Buffer.from(k).toString("hex") : typeof k === "string" ? k : "";
|
|
41
|
+
/** The row's address book as its store holds it now (empty with no store or no entries). */
|
|
42
|
+
export async function addressBook(store) {
|
|
43
|
+
if (!store || !(await store.log.tip()))
|
|
44
|
+
return [];
|
|
45
|
+
const root = await headTree(store, "peers");
|
|
46
|
+
if (!root)
|
|
47
|
+
return [];
|
|
48
|
+
const t = await store.get(root);
|
|
49
|
+
const out = [];
|
|
50
|
+
for (const e of t.peers ?? []) {
|
|
51
|
+
const p = await store.get(e.peer);
|
|
52
|
+
out.push({ key: hexKey(p.key ?? e.key), transport: p.transport ?? "mailbox", address: p.address ?? "", ...(p.handle ? { handle: p.handle } : {}), ...(p.domain ? { domain: p.domain } : {}), ...(p.source ? { source: p.source } : {}) });
|
|
53
|
+
}
|
|
54
|
+
return out;
|
|
55
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What the kernel's door answered without writing an entry (#143): a filter's rejection or answer
|
|
3
|
+
* (a read route's), or the gate's refusal. `admit` throws it: the request was never admitted.
|
|
4
|
+
*/
|
|
5
|
+
export interface DoorAnswer {
|
|
6
|
+
kind: "reject" | "answer";
|
|
7
|
+
status: number;
|
|
8
|
+
code?: string;
|
|
9
|
+
reason?: string;
|
|
10
|
+
type?: string;
|
|
11
|
+
headers?: Record<string, string>;
|
|
12
|
+
body?: Uint8Array;
|
|
13
|
+
/** Who the filters said it was from, if they got that far (the host's meter). */
|
|
14
|
+
principal?: Uint8Array;
|
|
15
|
+
/** The filters' fuel (an app's filter runs as a call): the host's meter. */
|
|
16
|
+
fuel: number;
|
|
17
|
+
}
|
|
18
|
+
/** A request the kernel's door turned away or answered (#143): nothing was written. */
|
|
19
|
+
export declare class DoorAnswered extends Error {
|
|
20
|
+
readonly answer: DoorAnswer;
|
|
21
|
+
constructor(answer: DoorAnswer);
|
|
22
|
+
}
|
|
23
|
+
/** The kernel's `answered` frame as a DoorAnswer (absent fields left out). */
|
|
24
|
+
export declare function doorAnswer(x: Record<string, unknown>): DoorAnswer;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// What the kernel's door answers without writing an entry (#143): a filter's rejection or answer
|
|
2
|
+
// (a read route's), or the gate's refusal — the `admit` frame's `{answered: {…}}`. No node APIs: the
|
|
3
|
+
// node host (kernel.ts) and the browser host (web/kernel/host.ts) both read it.
|
|
4
|
+
/** A request the kernel's door turned away or answered (#143): nothing was written. */
|
|
5
|
+
export class DoorAnswered extends Error {
|
|
6
|
+
answer;
|
|
7
|
+
constructor(answer) {
|
|
8
|
+
super(`${answer.kind === "answer" ? "answered" : "turned away"} at the door: ${answer.status}${answer.reason ? ` ${answer.reason}` : ""}`);
|
|
9
|
+
this.answer = answer;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
/** The kernel's `answered` frame as a DoorAnswer (absent fields left out). */
|
|
13
|
+
export function doorAnswer(x) {
|
|
14
|
+
return {
|
|
15
|
+
kind: x.kind === "answer" ? "answer" : "reject", status: Number(x.status ?? 500), fuel: Number(x.fuel ?? 0),
|
|
16
|
+
...(typeof x.code === "string" ? { code: x.code } : {}), ...(typeof x.reason === "string" ? { reason: x.reason } : {}),
|
|
17
|
+
...(typeof x.type === "string" ? { type: x.type } : {}),
|
|
18
|
+
...(x.headers && typeof x.headers === "object" ? { headers: x.headers } : {}),
|
|
19
|
+
...(x.body instanceof Uint8Array ? { body: x.body } : {}), ...(x.principal instanceof Uint8Array ? { principal: x.principal } : {}),
|
|
20
|
+
};
|
|
21
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import type { CID } from "multiformats/cid";
|
|
2
|
+
import type { Store } from "../runtime/store.ts";
|
|
3
|
+
/** The store a fold reads (a store file opened read-only), closed after. */
|
|
4
|
+
export type EventStore = Pick<Store, "get" | "chains" | "edges" | "bytes"> & {
|
|
5
|
+
close(): unknown;
|
|
6
|
+
};
|
|
7
|
+
/** One emitted event as the stream sends it. */
|
|
8
|
+
export interface Delivered {
|
|
9
|
+
/** Its place: `<n>.<seq>.<i>.<thread>`. */
|
|
10
|
+
id: string;
|
|
11
|
+
n: number;
|
|
12
|
+
event: string;
|
|
13
|
+
app: string;
|
|
14
|
+
topic: string;
|
|
15
|
+
/** The record as DAG-JSON. */
|
|
16
|
+
data: string;
|
|
17
|
+
}
|
|
18
|
+
/** A subscription's request: the app, the event's name, the topics; and where it resumes (Last-Event-ID). */
|
|
19
|
+
export interface EventsQuery {
|
|
20
|
+
app: string;
|
|
21
|
+
event: string;
|
|
22
|
+
topics: string[];
|
|
23
|
+
after?: string;
|
|
24
|
+
}
|
|
25
|
+
/** The query of GET /<app>/.events (`event`, `topic` repeated) and Last-Event-ID, or why it is not one. */
|
|
26
|
+
export declare function eventsQuery(app: string, params: URLSearchParams, lastEventId: string | undefined): EventsQuery | {
|
|
27
|
+
problem: string;
|
|
28
|
+
};
|
|
29
|
+
/** An event id's parts, or undefined. */
|
|
30
|
+
export declare function placeOf(id: string): {
|
|
31
|
+
n: number;
|
|
32
|
+
seq: number;
|
|
33
|
+
i: number;
|
|
34
|
+
thread: string;
|
|
35
|
+
} | undefined;
|
|
36
|
+
/** One event as an SSE message. */
|
|
37
|
+
export declare function sseMessage(d: Delivered): string;
|
|
38
|
+
/** Every event record a stream serves in the log, in log order, as delivered (resume and the current value). */
|
|
39
|
+
export declare function foldEvents(store: EventStore): Promise<Delivered[]>;
|
|
40
|
+
/** What a subscription is sent first (folded from the log): after its Last-Event-ID, else the last per topic. */
|
|
41
|
+
export declare function backlog(all: Delivered[], q: EventsQuery): Delivered[];
|
|
42
|
+
/** An event's place in the log, as the kernel's `emit` notice carries it beside the record (#148). */
|
|
43
|
+
export interface Place {
|
|
44
|
+
n: number;
|
|
45
|
+
seq: number;
|
|
46
|
+
i: number;
|
|
47
|
+
thread: CID;
|
|
48
|
+
}
|
|
49
|
+
/** The id of the event at `p`: `<n>.<seq>.<i>.<thread>` — the fold's (foldEvents), free from the notice. */
|
|
50
|
+
export declare const idOf: (p: Place) => string;
|
|
51
|
+
/** A handed-over event as delivered, or undefined when it is not one a stream serves (no app, no topic). */
|
|
52
|
+
export declare function deliveredOf(rec: Record<string, unknown>, p: Place): Delivered | undefined;
|
|
53
|
+
export interface EventStreamsOptions {
|
|
54
|
+
/** The instance's store, read-only (undefined: none). */
|
|
55
|
+
open(handle: string): EventStore | undefined;
|
|
56
|
+
}
|
|
57
|
+
/** The registry of open subscriptions, per instance (#148). */
|
|
58
|
+
export declare class EventStreams {
|
|
59
|
+
private subs;
|
|
60
|
+
/** Per instance: the ids handed over lately, in order: one handed over again is not sent again. */
|
|
61
|
+
private sent;
|
|
62
|
+
private readonly o;
|
|
63
|
+
constructor(o: EventStreamsOptions);
|
|
64
|
+
/** Whether `handle` has an open subscription. */
|
|
65
|
+
open(handle: string): boolean;
|
|
66
|
+
/** How many subscriptions `handle` has open (tests). */
|
|
67
|
+
count(handle: string): number;
|
|
68
|
+
/**
|
|
69
|
+
* Open a subscription for `handle`: its backlog folded from the log and sent, then each new
|
|
70
|
+
* matching event as it is handed over (what came during the fold, after it, once). `send` writes
|
|
71
|
+
* one event; `close` ends the stream (the host's: an uninstall, the instance stopping). The
|
|
72
|
+
* returned function forgets it (the client went).
|
|
73
|
+
*/
|
|
74
|
+
subscribe(handle: string, q: EventsQuery, send: (d: Delivered) => void, close: () => void): Promise<() => void>;
|
|
75
|
+
/**
|
|
76
|
+
* An event handed over for `handle` (after its step's commit), with its place in the log: sent
|
|
77
|
+
* as it is to each open subscription it matches — no log read. One handed over again (a start
|
|
78
|
+
* re-offers what a waiting thread awaits) is not sent again.
|
|
79
|
+
*/
|
|
80
|
+
emitted(handle: string, rec: Record<string, unknown>, place: Place): void;
|
|
81
|
+
/** The apps `handle` has installed now (rows in its dispatch table): the others' subscriptions close. */
|
|
82
|
+
keep(handle: string, apps: Set<string>): void;
|
|
83
|
+
/** Close every subscription of `handle` (it stops). */
|
|
84
|
+
drop(handle: string): void;
|
|
85
|
+
/** Close them all (the host shuts down). */
|
|
86
|
+
stop(): void;
|
|
87
|
+
private remove;
|
|
88
|
+
private end;
|
|
89
|
+
}
|