@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.
Files changed (176) hide show
  1. package/README.md +525 -2
  2. package/bin/skein-host.js +22 -0
  3. package/bin/skein.js +8 -0
  4. package/dist/client/admin-cli.d.ts +10 -0
  5. package/dist/client/admin-cli.js +370 -0
  6. package/dist/client/admin.d.ts +139 -0
  7. package/dist/client/admin.js +256 -0
  8. package/dist/client/bundle.d.ts +9 -0
  9. package/dist/client/bundle.js +34 -0
  10. package/dist/client/cli.d.ts +44 -0
  11. package/dist/client/cli.js +306 -0
  12. package/dist/client/client.d.ts +161 -0
  13. package/dist/client/client.js +237 -0
  14. package/dist/client/config.d.ts +18 -0
  15. package/dist/client/config.js +31 -0
  16. package/dist/client/conversation.d.ts +62 -0
  17. package/dist/client/conversation.js +113 -0
  18. package/dist/client/raw.d.ts +94 -0
  19. package/dist/client/raw.js +154 -0
  20. package/dist/client/target.d.ts +34 -0
  21. package/dist/client/target.js +158 -0
  22. package/dist/dev/cli.d.ts +8 -0
  23. package/dist/dev/cli.js +248 -0
  24. package/dist/dev/scan.d.ts +15 -0
  25. package/dist/dev/scan.js +173 -0
  26. package/dist/envelope-cbor.d.ts +110 -0
  27. package/dist/envelope-cbor.js +160 -0
  28. package/dist/envelope.d.ts +38 -0
  29. package/dist/envelope.js +101 -0
  30. package/dist/host/arc.d.ts +131 -0
  31. package/dist/host/arc.js +425 -0
  32. package/dist/host/billing.d.ts +102 -0
  33. package/dist/host/billing.js +167 -0
  34. package/dist/host/boot.d.ts +246 -0
  35. package/dist/host/boot.js +643 -0
  36. package/dist/host/cli.d.ts +57 -0
  37. package/dist/host/cli.js +1253 -0
  38. package/dist/host/clock.d.ts +3 -0
  39. package/dist/host/clock.js +8 -0
  40. package/dist/host/control.d.ts +45 -0
  41. package/dist/host/control.js +170 -0
  42. package/dist/host/cron.d.ts +99 -0
  43. package/dist/host/cron.js +196 -0
  44. package/dist/host/deliver.d.ts +53 -0
  45. package/dist/host/deliver.js +251 -0
  46. package/dist/host/deploy.d.ts +29 -0
  47. package/dist/host/deploy.js +55 -0
  48. package/dist/host/door-answer.d.ts +24 -0
  49. package/dist/host/door-answer.js +21 -0
  50. package/dist/host/events.d.ts +89 -0
  51. package/dist/host/events.js +222 -0
  52. package/dist/host/fake-arcade.d.ts +40 -0
  53. package/dist/host/fake-arcade.js +149 -0
  54. package/dist/host/fake-discovery.d.ts +4 -0
  55. package/dist/host/fake-discovery.js +58 -0
  56. package/dist/host/feeds.d.ts +127 -0
  57. package/dist/host/feeds.js +422 -0
  58. package/dist/host/frontdoor.d.ts +92 -0
  59. package/dist/host/frontdoor.js +172 -0
  60. package/dist/host/genesis.d.ts +259 -0
  61. package/dist/host/genesis.js +353 -0
  62. package/dist/host/handles.d.ts +40 -0
  63. package/dist/host/handles.js +88 -0
  64. package/dist/host/hostenv.d.ts +30 -0
  65. package/dist/host/hostenv.js +83 -0
  66. package/dist/host/image-chain.d.ts +127 -0
  67. package/dist/host/image-chain.js +440 -0
  68. package/dist/host/install.d.ts +22 -0
  69. package/dist/host/install.js +139 -0
  70. package/dist/host/instances.d.ts +227 -0
  71. package/dist/host/instances.js +430 -0
  72. package/dist/host/kernel.d.ts +246 -0
  73. package/dist/host/kernel.js +603 -0
  74. package/dist/host/liveness.d.ts +65 -0
  75. package/dist/host/liveness.js +155 -0
  76. package/dist/host/manifest.d.ts +160 -0
  77. package/dist/host/manifest.js +597 -0
  78. package/dist/host/p2p.d.ts +337 -0
  79. package/dist/host/p2p.js +1002 -0
  80. package/dist/host/packet.d.ts +121 -0
  81. package/dist/host/packet.js +562 -0
  82. package/dist/host/plan.d.ts +154 -0
  83. package/dist/host/plan.js +418 -0
  84. package/dist/host/providers.d.ts +238 -0
  85. package/dist/host/providers.js +565 -0
  86. package/dist/host/roster.d.ts +72 -0
  87. package/dist/host/roster.js +172 -0
  88. package/dist/host/router.d.ts +782 -0
  89. package/dist/host/router.js +2411 -0
  90. package/dist/host/signer.d.ts +49 -0
  91. package/dist/host/signer.js +167 -0
  92. package/dist/host/store-sqlite.d.ts +39 -0
  93. package/dist/host/store-sqlite.js +170 -0
  94. package/dist/host/stores.d.ts +43 -0
  95. package/dist/host/stores.js +342 -0
  96. package/dist/host/sync.d.ts +121 -0
  97. package/dist/host/sync.js +467 -0
  98. package/dist/host/testbeef.d.ts +21 -0
  99. package/dist/host/testbeef.js +37 -0
  100. package/dist/host/testhost.d.ts +149 -0
  101. package/dist/host/testhost.js +211 -0
  102. package/dist/host/vcdiff.d.ts +9 -0
  103. package/dist/host/vcdiff.js +210 -0
  104. package/dist/host/wasm-kernel.d.ts +87 -0
  105. package/dist/host/wasm-kernel.js +296 -0
  106. package/dist/host/wasm-worker.d.ts +1 -0
  107. package/dist/host/wasm-worker.js +186 -0
  108. package/dist/peers/cron.d.ts +33 -0
  109. package/dist/peers/cron.js +75 -0
  110. package/dist/peers/infer-main.d.ts +2 -0
  111. package/dist/peers/infer-main.js +74 -0
  112. package/dist/peers/infer.d.ts +163 -0
  113. package/dist/peers/infer.js +367 -0
  114. package/dist/runtime/beef.d.ts +28 -0
  115. package/dist/runtime/beef.js +107 -0
  116. package/dist/runtime/bitcoin.d.ts +36 -0
  117. package/dist/runtime/bitcoin.js +141 -0
  118. package/dist/runtime/cid.d.ts +14 -0
  119. package/dist/runtime/cid.js +51 -0
  120. package/dist/runtime/dispatch.d.ts +92 -0
  121. package/dist/runtime/dispatch.js +142 -0
  122. package/dist/runtime/envelope.d.ts +60 -0
  123. package/dist/runtime/envelope.js +101 -0
  124. package/dist/runtime/heads.d.ts +25 -0
  125. package/dist/runtime/heads.js +37 -0
  126. package/dist/runtime/identity.d.ts +28 -0
  127. package/dist/runtime/identity.js +67 -0
  128. package/dist/runtime/index-store.d.ts +152 -0
  129. package/dist/runtime/index-store.js +715 -0
  130. package/dist/runtime/log.d.ts +62 -0
  131. package/dist/runtime/log.js +104 -0
  132. package/dist/runtime/programs.d.ts +30 -0
  133. package/dist/runtime/programs.js +61 -0
  134. package/dist/runtime/records.d.ts +135 -0
  135. package/dist/runtime/records.js +149 -0
  136. package/dist/runtime/sqlite.d.ts +33 -0
  137. package/dist/runtime/sqlite.js +604 -0
  138. package/dist/runtime/store.d.ts +141 -0
  139. package/dist/runtime/store.js +24 -0
  140. package/dist/runtime/syscalls.d.ts +9 -0
  141. package/dist/runtime/syscalls.js +41 -0
  142. package/dist/runtime/tree.d.ts +60 -0
  143. package/dist/runtime/tree.js +196 -0
  144. package/dist/runtime/types.d.ts +100 -0
  145. package/dist/runtime/types.js +7 -0
  146. package/dist/testapps.d.ts +101 -0
  147. package/dist/testapps.js +275 -0
  148. package/dist/testkit.d.ts +25 -0
  149. package/dist/testkit.js +48 -0
  150. package/dist/wallet.d.ts +35 -0
  151. package/dist/wallet.js +87 -0
  152. package/images/README.md +68 -0
  153. package/images/default/README.md +38 -0
  154. package/images/default/bin/frontdoor.cid +1 -0
  155. package/images/default/bin/frontdoor.json +5 -0
  156. package/images/default/bin/messagebox.cid +1 -0
  157. package/images/default/bin/messagebox.json +8 -0
  158. package/images/default/bin/wallet.cid +1 -0
  159. package/images/default/bin/wallet.json +14 -0
  160. package/images/default/etc/config.json +1 -0
  161. package/images/default/etc/dispatch.json +82 -0
  162. package/images/host/etc/apps.json +8 -0
  163. package/kernel-zig/README.md +1247 -0
  164. package/kernel-zig/web/client.js +65 -0
  165. package/kernel-zig/web/kernel.d.ts +59 -0
  166. package/kernel-zig/web/kernel.js +329 -0
  167. package/kernel-zig/web/store.js +67 -0
  168. package/kernel-zig/web/worker.js +79 -0
  169. package/kernel-zig/zig-out/web/skein-kernel.wasm +0 -0
  170. package/package.json +77 -4
  171. package/wasm/README.md +35 -0
  172. package/wasm/frontdoor.wasm +0 -0
  173. package/wasm/messagebox.wasm +0 -0
  174. package/wasm/resolve.wasm +0 -0
  175. package/wasm/wallet.wasm +0 -0
  176. package/wasm/wire-probe.wasm +0 -0
@@ -0,0 +1,94 @@
1
+ import { AuthFetch, type WalletInterface } from "@bsv/sdk";
2
+ import { CID } from "multiformats/cid";
3
+ export interface Listed {
4
+ /** The message's id: its record's CID (text). */
5
+ messageId: string;
6
+ /** The sender's identity key (hex). */
7
+ sender: string;
8
+ /** The body's dag-cbor bytes. */
9
+ body: Uint8Array;
10
+ /** The body decoded. */
11
+ value: unknown;
12
+ /** The message itself: its mail record as the messagebox keeps it. */
13
+ message?: Record<string, unknown>;
14
+ }
15
+ /**
16
+ * A claim (#127), signed by `wallet` and naming no recipient: a message in
17
+ * box `claim`, its body {messagebox?, handle?, domain?} (the owner's mailbox
18
+ * entry, if any), signed by its sender ([2, "metanet handles envelope"], key
19
+ * "send", counterparty anyone) — the one message a client signs itself (#126
20
+ * step 4: no session of the instance's carries it). Signed before the instance it claims exists;
21
+ * the host forwards it into that instance as its first entry, and the kernel
22
+ * takes the owner from the signer. {message, body: its dag-cbor bytes}.
23
+ */
24
+ export declare function signClaim(wallet: WalletInterface, body?: {
25
+ messagebox?: string;
26
+ handle?: string;
27
+ domain?: string;
28
+ }, originator?: string): Promise<{
29
+ message: Record<string, unknown>;
30
+ body: Uint8Array;
31
+ }>;
32
+ /**
33
+ * A message signed by `wallet` in its own process (#142): the mail record {kind: "mail", op: "put",
34
+ * sender, recipient?, box, body: <the body's CID>, nonce, signature}, signed as every message is
35
+ * ([2, "metanet handles envelope"], key "send", counterparty anyone, over the record's dag-cbor
36
+ * without `signature`) — what the front door checks on a message carried in `local` (a provider's
37
+ * answer, a forwarded claim, the operator's message over the host's control socket). `recipient`
38
+ * (hex) names the instance; a claim names none (#127). {message, body: the body's dag-cbor bytes}.
39
+ */
40
+ export declare function signMessage(wallet: WalletInterface, m: {
41
+ recipient?: string;
42
+ box: string;
43
+ body: Uint8Array;
44
+ }, originator?: string): Promise<{
45
+ message: Record<string, unknown>;
46
+ body: Uint8Array;
47
+ }>;
48
+ export declare class RawBox {
49
+ readonly url: string;
50
+ readonly af: AuthFetch;
51
+ private readonly fetchFn;
52
+ private readonly wallet;
53
+ private readonly originator?;
54
+ private peer?;
55
+ constructor(wallet: WalletInterface, url: string, o?: {
56
+ fetch?: typeof fetch;
57
+ originator?: string;
58
+ });
59
+ /**
60
+ * The session's peer, made here rather than by AuthFetch (#83): the stock
61
+ * AuthFetch's peer keeps the SDK's default cap on a message's payload
62
+ * (16 MiB counted at four per byte: about 4 MiB), and an install's
63
+ * `objects` message carries a module whole (the shell's coreutils is
64
+ * 10 MB). The peer takes `maxGeneralPayloadBytes: null`, the SDK's own
65
+ * option for leaving the capacity to the transport; the receiving front
66
+ * door judges what it takes. Such a request takes seconds to check and
67
+ * send, so it waits up to five minutes (the SDK's default is 30 s).
68
+ */
69
+ private ensurePeer;
70
+ /** The session, made (the handshake happens on the first request). */
71
+ ready(): Promise<void>;
72
+ /** The identity key (hex) the other side's session answers as, once a request has been answered. */
73
+ peerIdentity(): string | undefined;
74
+ private post;
75
+ /**
76
+ * A message to `recipient` (hex) in `box`: `body` a record (encoded here) or its dag-cbor bytes. Its id.
77
+ * The BRC-33 message alone (#126 step 4): the session proves who sends it, and nothing in it is signed.
78
+ */
79
+ send(recipient: string, box: string, body: unknown): Promise<{
80
+ id: CID;
81
+ }>;
82
+ /**
83
+ * A BRC-33 request with a JSON body on the session (`path` under the box URL, e.g. "/sendMessage"):
84
+ * what any BRC-100 wallet's BRC-104 client sends (`1sat authfetch POST <origin>/sendMessage --body @file`,
85
+ * the owner's admin messages as JSON, src/client/admin.ts messageJson). The status and the answer's text.
86
+ */
87
+ postJson(path: string, json: string): Promise<{
88
+ status: number;
89
+ text: string;
90
+ }>;
91
+ /** The messages in the caller's `box`, oldest first. */
92
+ list(box: string): Promise<Listed[]>;
93
+ ack(ids: string[]): Promise<void>;
94
+ }
@@ -0,0 +1,154 @@
1
+ // Raw BRC-33 on a BRC-104 session (#40): what a client speaks to an
2
+ // instance's front door once it knows where it is. No BRC-169 envelope: the
3
+ // session proves who sends, the URL is the recipient's messagebox, the body is
4
+ // dag-cbor (BRC-231: an application/cbor request, recipient and body as
5
+ // bytes). One AuthFetch per box URL — the stock AuthFetch keeps one session
6
+ // per origin, so a URL with a `/@<handle>` path prefix (a host's dev form)
7
+ // gets its handshake sent under the prefix too.
8
+ //
9
+ // const mb = new RawBox(wallet, "http://127.0.0.1:8100/@martha") (or http://martha.localhost:8100)
10
+ // await mb.send(martha, "chat", {text: "hi"}) → {id}: the message's id (what a reply's replyTo names)
11
+ // await mb.list("chat") → [{messageId, sender, body, value}]
12
+ // await mb.ack([messageId])
13
+ import { AuthFetch, Peer, SimplifiedFetchTransport } from "@bsv/sdk";
14
+ import * as dagCbor from "@ipld/dag-cbor";
15
+ import { CID } from "multiformats/cid";
16
+ import { encode } from "../runtime/cid.js";
17
+ const hexOf = (b) => Buffer.from(b).toString("hex");
18
+ /**
19
+ * A claim (#127), signed by `wallet` and naming no recipient: a message in
20
+ * box `claim`, its body {messagebox?, handle?, domain?} (the owner's mailbox
21
+ * entry, if any), signed by its sender ([2, "metanet handles envelope"], key
22
+ * "send", counterparty anyone) — the one message a client signs itself (#126
23
+ * step 4: no session of the instance's carries it). Signed before the instance it claims exists;
24
+ * the host forwards it into that instance as its first entry, and the kernel
25
+ * takes the owner from the signer. {message, body: its dag-cbor bytes}.
26
+ */
27
+ export async function signClaim(wallet, body = {}, originator) {
28
+ return await signMessage(wallet, { box: "claim", body: dagCbor.encode(body) }, originator);
29
+ }
30
+ /**
31
+ * A message signed by `wallet` in its own process (#142): the mail record {kind: "mail", op: "put",
32
+ * sender, recipient?, box, body: <the body's CID>, nonce, signature}, signed as every message is
33
+ * ([2, "metanet handles envelope"], key "send", counterparty anyone, over the record's dag-cbor
34
+ * without `signature`) — what the front door checks on a message carried in `local` (a provider's
35
+ * answer, a forwarded claim, the operator's message over the host's control socket). `recipient`
36
+ * (hex) names the instance; a claim names none (#127). {message, body: the body's dag-cbor bytes}.
37
+ */
38
+ export async function signMessage(wallet, m, originator) {
39
+ const bytes = m.body;
40
+ const me = (await wallet.getPublicKey({ identityKey: true }, originator)).publicKey;
41
+ const unsigned = {
42
+ kind: "mail", op: "put", sender: Uint8Array.from(Buffer.from(me, "hex")),
43
+ ...(m.recipient ? { recipient: Uint8Array.from(Buffer.from(m.recipient, "hex")) } : {}),
44
+ box: m.box, body: encode(dagCbor.decode(bytes)).cid, nonce: globalThis.crypto.getRandomValues(new Uint8Array(16)),
45
+ };
46
+ const { signature } = await wallet.createSignature({ protocolID: [2, "metanet handles envelope"], keyID: "send", counterparty: "anyone", data: [...dagCbor.encode(unsigned)] }, originator);
47
+ return { message: { ...unsigned, signature: Uint8Array.from(signature) }, body: bytes };
48
+ }
49
+ export class RawBox {
50
+ url;
51
+ af;
52
+ fetchFn;
53
+ wallet;
54
+ originator;
55
+ peer;
56
+ constructor(wallet, url, o = {}) {
57
+ this.url = url.replace(/\/+$/, "");
58
+ const u = new URL(this.url);
59
+ const prefix = u.pathname === "/" ? "" : u.pathname;
60
+ // A bound fetch: the browser's must be called on the window.
61
+ const base = o.fetch ?? ((input, init) => globalThis.fetch(input, init));
62
+ // AuthFetch shakes hands at <origin>/.well-known/auth: under the path prefix, if the URL has one.
63
+ const f = prefix
64
+ ? ((input, init) => {
65
+ const s = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
66
+ const w = new URL(s);
67
+ if (w.origin === u.origin && w.pathname === "/.well-known/auth")
68
+ w.pathname = `${prefix}/.well-known/auth`;
69
+ return base(input instanceof Request ? new Request(w, input) : w.href, init);
70
+ })
71
+ : base;
72
+ this.af = new AuthFetch(wallet, undefined, undefined, o.originator, {}, f);
73
+ this.fetchFn = f;
74
+ this.wallet = wallet;
75
+ this.originator = o.originator;
76
+ }
77
+ /**
78
+ * The session's peer, made here rather than by AuthFetch (#83): the stock
79
+ * AuthFetch's peer keeps the SDK's default cap on a message's payload
80
+ * (16 MiB counted at four per byte: about 4 MiB), and an install's
81
+ * `objects` message carries a module whole (the shell's coreutils is
82
+ * 10 MB). The peer takes `maxGeneralPayloadBytes: null`, the SDK's own
83
+ * option for leaving the capacity to the transport; the receiving front
84
+ * door judges what it takes. Such a request takes seconds to check and
85
+ * send, so it waits up to five minutes (the SDK's default is 30 s).
86
+ */
87
+ ensurePeer(wallet, originator) {
88
+ this.peer ??= (async () => {
89
+ const origin = new URL(this.url).origin;
90
+ const peer = new Peer(wallet, new SimplifiedFetchTransport(origin, this.fetchFn, { requestTimeoutMs: 300_000 }), undefined, undefined, undefined, originator, { maxGeneralPayloadBytes: null });
91
+ await peer.ready;
92
+ this.af.peers[origin] = { peer, pendingCertificateRequests: [] };
93
+ })();
94
+ return this.peer;
95
+ }
96
+ /** The session, made (the handshake happens on the first request). */
97
+ async ready() { await this.ensurePeer(this.wallet, this.originator); }
98
+ /** The identity key (hex) the other side's session answers as, once a request has been answered. */
99
+ peerIdentity() {
100
+ const p = this.af.peers[new URL(this.url).origin];
101
+ return p?.identityKey;
102
+ }
103
+ async post(path, body) {
104
+ await this.ensurePeer(this.wallet, this.originator);
105
+ const r = await this.af.fetch(`${this.url}${path}`, { method: "POST", headers: { "content-type": "application/cbor" }, body: dagCbor.encode(body) });
106
+ const bytes = new Uint8Array(await r.arrayBuffer());
107
+ let v = {};
108
+ try {
109
+ v = dagCbor.decode(bytes);
110
+ }
111
+ catch {
112
+ v = { status: "error", description: new TextDecoder().decode(bytes) };
113
+ }
114
+ return { status: r.status, v };
115
+ }
116
+ /**
117
+ * A message to `recipient` (hex) in `box`: `body` a record (encoded here) or its dag-cbor bytes. Its id.
118
+ * The BRC-33 message alone (#126 step 4): the session proves who sends it, and nothing in it is signed.
119
+ */
120
+ async send(recipient, box, body) {
121
+ const bytes = body instanceof Uint8Array ? body : dagCbor.encode(body);
122
+ const to = Uint8Array.from(Buffer.from(recipient, "hex"));
123
+ const r = await this.post("/sendMessage", { message: { recipient: to, messageBox: box, body: bytes } });
124
+ if (r.status !== 200)
125
+ throw new Error(`sendMessage ${box}: HTTP ${r.status} ${String(r.v.code ?? "")} ${String(r.v.description ?? "")}`.trim());
126
+ return { id: CID.parse(String(r.v.id ?? r.v.messageId)) };
127
+ }
128
+ /**
129
+ * A BRC-33 request with a JSON body on the session (`path` under the box URL, e.g. "/sendMessage"):
130
+ * what any BRC-100 wallet's BRC-104 client sends (`1sat authfetch POST <origin>/sendMessage --body @file`,
131
+ * the owner's admin messages as JSON, src/client/admin.ts messageJson). The status and the answer's text.
132
+ */
133
+ async postJson(path, json) {
134
+ await this.ensurePeer(this.wallet, this.originator);
135
+ const r = await this.af.fetch(`${this.url}${path}`, { method: "POST", headers: { "content-type": "application/json" }, body: json });
136
+ return { status: r.status, text: await r.text() };
137
+ }
138
+ /** The messages in the caller's `box`, oldest first. */
139
+ async list(box) {
140
+ const r = await this.post("/listMessages", { messageBox: box });
141
+ if (r.status !== 200)
142
+ throw new Error(`listMessages ${box}: HTTP ${r.status} ${String(r.v.description ?? "")}`);
143
+ return (r.v.messages ?? []).map((m) => ({
144
+ messageId: m.messageId, sender: hexOf(m.sender), body: m.body, value: dagCbor.decode(m.body), ...(m.message ? { message: m.message } : {}),
145
+ }));
146
+ }
147
+ async ack(ids) {
148
+ if (!ids.length)
149
+ return;
150
+ const r = await this.post("/acknowledgeMessage", { messageIds: ids });
151
+ if (r.status !== 200)
152
+ throw new Error(`acknowledgeMessage: HTTP ${r.status} ${String(r.v.description ?? "")}`);
153
+ }
154
+ }
@@ -0,0 +1,34 @@
1
+ import type { CID } from "multiformats/cid";
2
+ import type { WalletInterface } from "@bsv/sdk";
3
+ import { type Vars } from "../host/hostenv.ts";
4
+ import type { InstanceView } from "../host/plan.ts";
5
+ /** A thread at rest: its last update. */
6
+ export interface ThreadEnd {
7
+ state: string;
8
+ result?: {
9
+ stdout?: Uint8Array;
10
+ [k: string]: unknown;
11
+ };
12
+ error?: unknown;
13
+ }
14
+ export interface Target {
15
+ /** Where the messages go, for the operator's eyes. */
16
+ where: string;
17
+ /** The instance's identity key (hex). */
18
+ identity: string;
19
+ /** A fresh view of the instance (plan.ts InstanceView). */
20
+ view(): Promise<InstanceView>;
21
+ /** One message from the operator into `box`, `body` a record value: its id (the message record's CID). */
22
+ send(box: string, body: unknown): Promise<CID>;
23
+ /** The claim (#127): the operator's message in box `claim` (on the host: naming no recipient, as a forwarded claim). */
24
+ claim(body: Record<string, unknown>): Promise<CID>;
25
+ /** The thread the message `id` launched, once it comes to rest. */
26
+ answer(id: CID, timeoutMs?: number): Promise<ThreadEnd>;
27
+ close(): Promise<void>;
28
+ }
29
+ /** An instance on this host machine (`--instance <handle>`): host.db's row, its store read-only, the control socket. */
30
+ export declare function localTarget(vars: Vars, handle: string, wallet: WalletInterface): Target;
31
+ /** An instance at `origin`: one BRC-104 session for its messages and its explorer's reads (the operator's, as its owner). */
32
+ export declare function remoteTarget(origin: string, wallet: WalletInterface): Promise<Target>;
33
+ /** A step's answer as the program wrote it to stdout (dag-cbor, else DAG-JSON). */
34
+ export declare function stdoutOf(u: ThreadEnd): Record<string, unknown> | undefined;
@@ -0,0 +1,158 @@
1
+ // Where the operator's messages go (#142). Signing is the client's own
2
+ // (raw.ts signMessage, with the operator's key: hostenv.ts operatorKey);
3
+ // delivery is only a question of which host the message is going to:
4
+ //
5
+ // --instance <handle> an instance on this host machine: its row in
6
+ // $SKEIN_HOME/host.db, its store read read-only (the
7
+ // plan's view, a thread's answer), each message signed
8
+ // here and handed to the running host over its control
9
+ // socket ($SKEIN_HOME/host.sock, op `message`: appended
10
+ // as a signed `local` request, the path a forwarded
11
+ // claim takes)
12
+ // <origin> an instance anywhere: one BRC-104 session (@bsv/sdk's
13
+ // Peer/AuthFetch, raw.ts RawBox) carries the messages
14
+ // (dag-cbor bodies) and the explorer's reads
15
+ //
16
+ // No other process is started: no wallet command, no handshake per message.
17
+ import { existsSync } from "node:fs";
18
+ import { join } from "node:path";
19
+ import * as dagCbor from "@ipld/dag-cbor";
20
+ import * as dagJson from "@ipld/dag-json";
21
+ import { encode } from "../runtime/cid.js";
22
+ import { storeSpec } from "../runtime/index-store.js";
23
+ import { openStoreFile, storeExists } from "../host/stores.js";
24
+ import { CONTROL_SOCKET, controlRequest } from "../host/control.js";
25
+ import { homeOf } from "../host/hostenv.js";
26
+ import { HostDb } from "../host/instances.js";
27
+ import { instanceView } from "../host/install.js";
28
+ import { explorerView } from "./admin.js";
29
+ import { RawBox, signMessage } from "./raw.js";
30
+ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
31
+ const b64 = (b) => Buffer.from(b).toString("base64");
32
+ /** Poll `look` (500 ms, doubling to 5 s) until it answers, or fail past `timeoutMs`. */
33
+ async function until(what, look, timeoutMs) {
34
+ const deadline = Date.now() + timeoutMs;
35
+ for (let wait = 250;; wait = Math.min(wait * 2, 5000)) {
36
+ const v = await look();
37
+ if (v !== undefined)
38
+ return v;
39
+ if (Date.now() > deadline)
40
+ throw new Error(`${what}: no answer in ${Math.round(timeoutMs / 1000)} s`);
41
+ await sleep(wait);
42
+ }
43
+ }
44
+ const atRest = (u) => !!u && (u.state === "finished" || u.state === "errored");
45
+ /** An instance on this host machine (`--instance <handle>`): host.db's row, its store read-only, the control socket. */
46
+ export function localTarget(vars, handle, wallet) {
47
+ const home = homeOf(vars);
48
+ const dbPath = join(home, "host.db");
49
+ if (!existsSync(dbPath))
50
+ throw new Error(`--instance ${handle}: no host here (${dbPath}; SKEIN_HOME)`);
51
+ const db = new HostDb(dbPath, { readOnly: true });
52
+ const row = db.get(handle);
53
+ db.close();
54
+ if (!row)
55
+ throw new Error(`--instance ${handle}: no such instance on this host`);
56
+ if (!row.identity)
57
+ throw new Error(`--instance ${handle}: it has no identity yet (never booted)`);
58
+ const sock = join(home, CONTROL_SOCKET);
59
+ const opened = [];
60
+ const openOnce = () => {
61
+ if (!storeExists(storeSpec(row)))
62
+ throw new Error(`${handle}: no store at ${storeSpec(row)} yet`);
63
+ return openStoreFile(storeSpec(row), { readOnly: true });
64
+ };
65
+ // A view's store stays open while the view is used (until close).
66
+ const open = () => { const s = openOnce(); opened.push(s); return s; };
67
+ const deliver = async (m) => {
68
+ const a = await controlRequest(sock, { op: "message", handle, message: b64(dagCbor.encode(m.message)), body: b64(m.body) }, 600_000);
69
+ if (!a)
70
+ throw new Error(`no host answers on ${sock}: start it (skein-host run)`);
71
+ if (!a.ok)
72
+ throw new Error(a.error);
73
+ return encode(m.message).cid;
74
+ };
75
+ return {
76
+ where: `${handle} (this host)`,
77
+ identity: row.identity,
78
+ view: async () => await instanceView(open()),
79
+ send: async (box, body) => await deliver(await signMessage(wallet, { recipient: row.identity, box, body: dagCbor.encode(body) })),
80
+ claim: async (body) => await deliver(await signMessage(wallet, { box: "claim", body: dagCbor.encode(body) })),
81
+ answer: (id, timeoutMs = 300_000) => until(`the thread of ${id}`, async () => {
82
+ const s = openOnce();
83
+ try {
84
+ for (const e of await s.edges.refsTo(id)) {
85
+ if (e.rel !== "launched-by" || !e.from)
86
+ continue;
87
+ const u = await s.get(await s.chains.tip(e.from)).catch(() => undefined);
88
+ if (atRest(u))
89
+ return u;
90
+ }
91
+ return undefined;
92
+ }
93
+ finally {
94
+ s.close();
95
+ }
96
+ }, timeoutMs),
97
+ close: async () => { for (const s of opened.splice(0))
98
+ s.close(); },
99
+ };
100
+ }
101
+ /** An instance at `origin`: one BRC-104 session for its messages and its explorer's reads (the operator's, as its owner). */
102
+ export async function remoteTarget(origin, wallet) {
103
+ const url = origin.replace(/\/+$/, "");
104
+ const box = new RawBox(wallet, url);
105
+ const get = async (path) => {
106
+ await box.ready();
107
+ return await box.af.fetch(`${url}${path}`, { method: "GET" });
108
+ };
109
+ const read = async (path) => {
110
+ const r = await get(`/explore${path}`);
111
+ const bytes = new Uint8Array(await r.arrayBuffer());
112
+ if (r.status === 404)
113
+ return undefined;
114
+ if (r.status !== 200)
115
+ throw new Error(`GET ${url}/explore${path}: ${r.status} ${new TextDecoder().decode(bytes).slice(0, 200)}${r.status === 403 ? " (the explorer is the owner's: is the operator's key this instance's owner?)" : ""}`);
116
+ return dagJson.decode(bytes);
117
+ };
118
+ // Who answers: the session's peer (the instance's identity), learnt on the first signed request.
119
+ const first = await get("/explore");
120
+ await first.arrayBuffer();
121
+ const identity = box.peerIdentity() ?? first.headers.get("x-bsv-auth-identity-key") ?? "";
122
+ if (!/^0[23][0-9a-f]{64}$/.test(identity))
123
+ throw new Error(`${url}: no BRC-104 identity answered (is it a skein's origin?)`);
124
+ return {
125
+ where: url,
126
+ identity,
127
+ view: async () => await explorerView(read),
128
+ send: async (b, body) => (await box.send(identity, b, body)).id,
129
+ claim: async (body) => (await box.send(identity, "claim", body)).id,
130
+ answer: (id, timeoutMs = 300_000) => until(`the thread of ${id}`, async () => {
131
+ const e = await read(`/edges/${id}?rel=launched-by`);
132
+ for (const edge of e?.edges ?? []) {
133
+ const t = await read(`/thread/${edge.from}`);
134
+ const last = t?.updates?.at(-1)?.record;
135
+ if (atRest(last))
136
+ return last;
137
+ }
138
+ return undefined;
139
+ }, timeoutMs),
140
+ close: async () => { },
141
+ };
142
+ }
143
+ /** A step's answer as the program wrote it to stdout (dag-cbor, else DAG-JSON). */
144
+ export function stdoutOf(u) {
145
+ const b = u.result?.stdout;
146
+ if (!(b instanceof Uint8Array) || !b.length)
147
+ return undefined;
148
+ try {
149
+ return dagCbor.decode(b);
150
+ }
151
+ catch { /* not dag-cbor */ }
152
+ try {
153
+ return dagJson.decode(b);
154
+ }
155
+ catch {
156
+ return undefined;
157
+ }
158
+ }
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env -S node --experimental-strip-types --no-warnings
2
+ import type { CID } from "multiformats/cid";
3
+ import type { SqliteStore } from "../runtime/sqlite.ts";
4
+ export declare const dbPath: () => string;
5
+ /** Put the pinned wasm modules into the store under their CIDs (the shell's are the shell app's, #83: installed with it). */
6
+ export declare function install(store: Pick<SqliteStore, "has" | "putBlock">): Promise<string[]>;
7
+ export declare function resolveCid(store: Pick<SqliteStore, "findByPrefix">, s: string): Promise<CID>;
8
+ export declare function main(argv: string[]): Promise<number>;