@freqhole/reliquary 0.0.0
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/dist/automerge/acl-filtering.d.ts +87 -0
- package/dist/automerge/acl-filtering.d.ts.map +1 -0
- package/dist/automerge/acl-filtering.js +138 -0
- package/dist/automerge/acl-filtering.test.d.ts +2 -0
- package/dist/automerge/acl-filtering.test.d.ts.map +1 -0
- package/dist/automerge/acl-filtering.test.js +302 -0
- package/dist/automerge/index.d.ts +5 -0
- package/dist/automerge/index.d.ts.map +1 -0
- package/dist/automerge/index.js +7 -0
- package/dist/automerge/iroh-network-adapter.d.ts +178 -0
- package/dist/automerge/iroh-network-adapter.d.ts.map +1 -0
- package/dist/automerge/iroh-network-adapter.js +691 -0
- package/dist/automerge/iroh-network-adapter.test.d.ts +2 -0
- package/dist/automerge/iroh-network-adapter.test.d.ts.map +1 -0
- package/dist/automerge/iroh-network-adapter.test.js +818 -0
- package/dist/automerge/types.d.ts +66 -0
- package/dist/automerge/types.d.ts.map +1 -0
- package/dist/automerge/types.js +10 -0
- package/dist/blobs/bytes-backend.d.ts +41 -0
- package/dist/blobs/bytes-backend.d.ts.map +1 -0
- package/dist/blobs/bytes-backend.js +252 -0
- package/dist/blobs/bytes-backend.test.d.ts +2 -0
- package/dist/blobs/bytes-backend.test.d.ts.map +1 -0
- package/dist/blobs/bytes-backend.test.js +241 -0
- package/dist/blobs/db.d.ts +8 -0
- package/dist/blobs/db.d.ts.map +1 -0
- package/dist/blobs/db.js +141 -0
- package/dist/blobs/db.test.d.ts +2 -0
- package/dist/blobs/db.test.d.ts.map +1 -0
- package/dist/blobs/db.test.js +120 -0
- package/dist/blobs/index.d.ts +5 -0
- package/dist/blobs/index.d.ts.map +1 -0
- package/dist/blobs/index.js +2 -0
- package/dist/blobs/store.d.ts +47 -0
- package/dist/blobs/store.d.ts.map +1 -0
- package/dist/blobs/store.js +263 -0
- package/dist/blobs/store.test.d.ts +2 -0
- package/dist/blobs/store.test.d.ts.map +1 -0
- package/dist/blobs/store.test.js +377 -0
- package/dist/blobs/types.d.ts +55 -0
- package/dist/blobs/types.d.ts.map +1 -0
- package/dist/blobs/types.js +8 -0
- package/dist/ensure/client.d.ts +19 -0
- package/dist/ensure/client.d.ts.map +1 -0
- package/dist/ensure/client.js +74 -0
- package/dist/ensure/client.test.d.ts +2 -0
- package/dist/ensure/client.test.d.ts.map +1 -0
- package/dist/ensure/client.test.js +133 -0
- package/dist/ensure/index.d.ts +4 -0
- package/dist/ensure/index.d.ts.map +1 -0
- package/dist/ensure/index.js +4 -0
- package/dist/ensure/responder.d.ts +20 -0
- package/dist/ensure/responder.d.ts.map +1 -0
- package/dist/ensure/responder.js +100 -0
- package/dist/ensure/responder.test.d.ts +2 -0
- package/dist/ensure/responder.test.d.ts.map +1 -0
- package/dist/ensure/responder.test.js +191 -0
- package/dist/ensure/types.d.ts +19 -0
- package/dist/ensure/types.d.ts.map +1 -0
- package/dist/ensure/types.js +4 -0
- package/dist/ensure/types.test.d.ts +2 -0
- package/dist/ensure/types.test.d.ts.map +1 -0
- package/dist/ensure/types.test.js +81 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/index.test.d.ts +2 -0
- package/dist/index.test.d.ts.map +1 -0
- package/dist/index.test.js +7 -0
- package/dist/solid/blob-url.d.ts +10 -0
- package/dist/solid/blob-url.d.ts.map +1 -0
- package/dist/solid/blob-url.js +34 -0
- package/dist/solid/blob-url.test.d.ts +2 -0
- package/dist/solid/blob-url.test.d.ts.map +1 -0
- package/dist/solid/blob-url.test.js +62 -0
- package/dist/solid/connection-summary.d.ts +25 -0
- package/dist/solid/connection-summary.d.ts.map +1 -0
- package/dist/solid/connection-summary.js +29 -0
- package/dist/solid/connection-summary.test.d.ts +2 -0
- package/dist/solid/connection-summary.test.d.ts.map +1 -0
- package/dist/solid/connection-summary.test.js +57 -0
- package/dist/solid/doc-store.d.ts +29 -0
- package/dist/solid/doc-store.d.ts.map +1 -0
- package/dist/solid/doc-store.js +79 -0
- package/dist/solid/doc-store.test.d.ts +2 -0
- package/dist/solid/doc-store.test.d.ts.map +1 -0
- package/dist/solid/doc-store.test.js +234 -0
- package/dist/solid/index.d.ts +4 -0
- package/dist/solid/index.d.ts.map +1 -0
- package/dist/solid/index.js +9 -0
- package/dist/solid/transfer-progress.d.ts +21 -0
- package/dist/solid/transfer-progress.d.ts.map +1 -0
- package/dist/solid/transfer-progress.js +32 -0
- package/dist/solid/transfer-progress.test.d.ts +2 -0
- package/dist/solid/transfer-progress.test.d.ts.map +1 -0
- package/dist/solid/transfer-progress.test.js +76 -0
- package/dist/testing/blob-fixtures.d.ts +19 -0
- package/dist/testing/blob-fixtures.d.ts.map +1 -0
- package/dist/testing/blob-fixtures.js +46 -0
- package/dist/testing/blob-fixtures.test.d.ts +2 -0
- package/dist/testing/blob-fixtures.test.d.ts.map +1 -0
- package/dist/testing/blob-fixtures.test.js +46 -0
- package/dist/testing/idb-harness.d.ts +10 -0
- package/dist/testing/idb-harness.d.ts.map +1 -0
- package/dist/testing/idb-harness.js +17 -0
- package/dist/testing/idb-harness.test.d.ts +2 -0
- package/dist/testing/idb-harness.test.d.ts.map +1 -0
- package/dist/testing/idb-harness.test.js +48 -0
- package/dist/testing/index.d.ts +7 -0
- package/dist/testing/index.d.ts.map +1 -0
- package/dist/testing/index.js +11 -0
- package/dist/testing/media.d.ts +15 -0
- package/dist/testing/media.d.ts.map +1 -0
- package/dist/testing/media.js +37 -0
- package/dist/testing/media.test.d.ts +2 -0
- package/dist/testing/media.test.d.ts.map +1 -0
- package/dist/testing/media.test.js +47 -0
- package/dist/testing/mock-blob-fetch.d.ts +33 -0
- package/dist/testing/mock-blob-fetch.d.ts.map +1 -0
- package/dist/testing/mock-blob-fetch.js +39 -0
- package/dist/testing/mock-blob-fetch.test.d.ts +2 -0
- package/dist/testing/mock-blob-fetch.test.d.ts.map +1 -0
- package/dist/testing/mock-blob-fetch.test.js +65 -0
- package/dist/testing/mock-node.d.ts +36 -0
- package/dist/testing/mock-node.d.ts.map +1 -0
- package/dist/testing/mock-node.js +97 -0
- package/dist/testing/mock-node.test.d.ts +2 -0
- package/dist/testing/mock-node.test.d.ts.map +1 -0
- package/dist/testing/mock-node.test.js +66 -0
- package/dist/testing/streams.d.ts +68 -0
- package/dist/testing/streams.d.ts.map +1 -0
- package/dist/testing/streams.js +99 -0
- package/dist/testing/streams.test.d.ts +2 -0
- package/dist/testing/streams.test.d.ts.map +1 -0
- package/dist/testing/streams.test.js +82 -0
- package/dist/transfer/cancellation.d.ts +8 -0
- package/dist/transfer/cancellation.d.ts.map +1 -0
- package/dist/transfer/cancellation.js +19 -0
- package/dist/transfer/index.d.ts +8 -0
- package/dist/transfer/index.d.ts.map +1 -0
- package/dist/transfer/index.js +4 -0
- package/dist/transfer/prefetch.d.ts +38 -0
- package/dist/transfer/prefetch.d.ts.map +1 -0
- package/dist/transfer/prefetch.js +61 -0
- package/dist/transfer/prefetch.test.d.ts +2 -0
- package/dist/transfer/prefetch.test.d.ts.map +1 -0
- package/dist/transfer/prefetch.test.js +136 -0
- package/dist/transfer/serve.d.ts +66 -0
- package/dist/transfer/serve.d.ts.map +1 -0
- package/dist/transfer/serve.js +126 -0
- package/dist/transfer/serve.test.d.ts +2 -0
- package/dist/transfer/serve.test.d.ts.map +1 -0
- package/dist/transfer/serve.test.js +101 -0
- package/dist/transfer/snatch.d.ts +39 -0
- package/dist/transfer/snatch.d.ts.map +1 -0
- package/dist/transfer/snatch.js +289 -0
- package/dist/transfer/snatch.test.d.ts +2 -0
- package/dist/transfer/snatch.test.d.ts.map +1 -0
- package/dist/transfer/snatch.test.js +337 -0
- package/dist/transfer/types.d.ts +85 -0
- package/dist/transfer/types.d.ts.map +1 -0
- package/dist/transfer/types.js +15 -0
- package/dist/utils/hash.d.ts +13 -0
- package/dist/utils/hash.d.ts.map +1 -0
- package/dist/utils/hash.js +31 -0
- package/dist/utils/hash.test.d.ts +2 -0
- package/dist/utils/hash.test.d.ts.map +1 -0
- package/dist/utils/hash.test.js +56 -0
- package/dist/utils/image-utils.d.ts +28 -0
- package/dist/utils/image-utils.d.ts.map +1 -0
- package/dist/utils/image-utils.js +113 -0
- package/dist/utils/image-utils.test.d.ts +2 -0
- package/dist/utils/image-utils.test.d.ts.map +1 -0
- package/dist/utils/image-utils.test.js +181 -0
- package/dist/utils/index.d.ts +4 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +3 -0
- package/dist/utils/log.d.ts +22 -0
- package/dist/utils/log.d.ts.map +1 -0
- package/dist/utils/log.js +100 -0
- package/dist/utils/log.test.d.ts +2 -0
- package/dist/utils/log.test.d.ts.map +1 -0
- package/dist/utils/log.test.js +113 -0
- package/dist/worker/blob-worker-client.d.ts +118 -0
- package/dist/worker/blob-worker-client.d.ts.map +1 -0
- package/dist/worker/blob-worker-client.js +361 -0
- package/dist/worker/blob-worker-client.test.d.ts +2 -0
- package/dist/worker/blob-worker-client.test.d.ts.map +1 -0
- package/dist/worker/blob-worker-client.test.js +137 -0
- package/dist/worker/blob-worker-logic.d.ts +120 -0
- package/dist/worker/blob-worker-logic.d.ts.map +1 -0
- package/dist/worker/blob-worker-logic.js +368 -0
- package/dist/worker/blob-worker-logic.test.d.ts +2 -0
- package/dist/worker/blob-worker-logic.test.d.ts.map +1 -0
- package/dist/worker/blob-worker-logic.test.js +191 -0
- package/dist/worker/blob-worker.d.ts +21 -0
- package/dist/worker/blob-worker.d.ts.map +1 -0
- package/dist/worker/blob-worker.js +44 -0
- package/dist/worker/index.d.ts +12 -0
- package/dist/worker/index.d.ts.map +1 -0
- package/dist/worker/index.js +6 -0
- package/dist/worker/midden-blake3.d.ts +23 -0
- package/dist/worker/midden-blake3.d.ts.map +1 -0
- package/dist/worker/midden-blake3.js +47 -0
- package/dist/worker/midden-blake3.test.d.ts +2 -0
- package/dist/worker/midden-blake3.test.d.ts.map +1 -0
- package/dist/worker/midden-blake3.test.js +25 -0
- package/dist/worker/midden-not-bundled.stub.d.ts +1 -0
- package/dist/worker/midden-not-bundled.stub.d.ts.map +1 -0
- package/dist/worker/midden-not-bundled.stub.js +10 -0
- package/dist/worker/midden-stub.d.ts +10 -0
- package/dist/worker/midden-stub.d.ts.map +1 -0
- package/dist/worker/midden-stub.js +31 -0
- package/dist/worker/midden-stub.test.d.ts +2 -0
- package/dist/worker/midden-stub.test.d.ts.map +1 -0
- package/dist/worker/midden-stub.test.js +25 -0
- package/dist/worker/midden-worker-client.d.ts +103 -0
- package/dist/worker/midden-worker-client.d.ts.map +1 -0
- package/dist/worker/midden-worker-client.js +246 -0
- package/dist/worker/midden-worker-client.test.d.ts +2 -0
- package/dist/worker/midden-worker-client.test.d.ts.map +1 -0
- package/dist/worker/midden-worker-client.test.js +151 -0
- package/dist/worker/midden-worker-contract.d.ts +87 -0
- package/dist/worker/midden-worker-contract.d.ts.map +1 -0
- package/dist/worker/midden-worker-contract.js +30 -0
- package/package.json +90 -0
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
// tests for the ensure-blob client
|
|
2
|
+
import { describe, expect, it, vi } from "vitest";
|
|
3
|
+
import { createMockBiStream } from "../testing/index.js";
|
|
4
|
+
import { ensureBlobOverAlpn } from "./client.js";
|
|
5
|
+
import { DEFAULT_ENSURE_ALPN } from "./types.js";
|
|
6
|
+
describe("ensure/client", () => {
|
|
7
|
+
function encodeResponse(msg) {
|
|
8
|
+
return new TextEncoder().encode(JSON.stringify(msg));
|
|
9
|
+
}
|
|
10
|
+
function createMockNode() {
|
|
11
|
+
const stream = createMockBiStream("remote-peer", "freqhole/1");
|
|
12
|
+
return {
|
|
13
|
+
open_bi: vi.fn(async () => stream),
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
describe("ensureBlobOverAlpn", () => {
|
|
17
|
+
it("dials the peer with the default ALPN when none provided", async () => {
|
|
18
|
+
const node = createMockNode();
|
|
19
|
+
const peerAddr = "peer123";
|
|
20
|
+
const blake3Hash = "af1349b9f5f9a1a6a0404dea36dcc9499bcb25c9adc112b7cc9a93cae41f3262";
|
|
21
|
+
const response = {
|
|
22
|
+
type: "ensure_blob_response",
|
|
23
|
+
id: 1,
|
|
24
|
+
available: true,
|
|
25
|
+
};
|
|
26
|
+
const promise = ensureBlobOverAlpn(node, peerAddr, blake3Hash);
|
|
27
|
+
await new Promise((r) => setTimeout(r, 10));
|
|
28
|
+
const stream = (await node.open_bi("", ""));
|
|
29
|
+
stream.pushMessage(encodeResponse(response));
|
|
30
|
+
const result = await promise;
|
|
31
|
+
expect(node.open_bi).toHaveBeenCalledWith(peerAddr, DEFAULT_ENSURE_ALPN);
|
|
32
|
+
expect(result).toBe(true);
|
|
33
|
+
});
|
|
34
|
+
it("dials the peer with a custom ALPN when provided", async () => {
|
|
35
|
+
const node = createMockNode();
|
|
36
|
+
const peerAddr = "peer456";
|
|
37
|
+
const blake3Hash = "0000000000000000000000000000000000000000000000000000000000000001";
|
|
38
|
+
const customAlpn = "custom/1";
|
|
39
|
+
const response = {
|
|
40
|
+
type: "ensure_blob_response",
|
|
41
|
+
id: 1,
|
|
42
|
+
available: false,
|
|
43
|
+
};
|
|
44
|
+
const promise = ensureBlobOverAlpn(node, peerAddr, blake3Hash, customAlpn);
|
|
45
|
+
await new Promise((r) => setTimeout(r, 10));
|
|
46
|
+
const stream = (await node.open_bi("", ""));
|
|
47
|
+
stream.pushMessage(encodeResponse(response));
|
|
48
|
+
const result = await promise;
|
|
49
|
+
expect(node.open_bi).toHaveBeenCalledWith(peerAddr, customAlpn);
|
|
50
|
+
expect(result).toBe(false);
|
|
51
|
+
});
|
|
52
|
+
it("returns true when peer reports available=true", async () => {
|
|
53
|
+
const node = createMockNode();
|
|
54
|
+
const response = {
|
|
55
|
+
type: "ensure_blob_response",
|
|
56
|
+
id: 1,
|
|
57
|
+
available: true,
|
|
58
|
+
};
|
|
59
|
+
const promise = ensureBlobOverAlpn(node, "peer789", "af1349b9f5f9a1a6a0404dea36dcc9499bcb25c9adc112b7cc9a93cae41f3262");
|
|
60
|
+
await new Promise((r) => setTimeout(r, 10));
|
|
61
|
+
const stream = (await node.open_bi("", ""));
|
|
62
|
+
stream.pushMessage(encodeResponse(response));
|
|
63
|
+
const result = await promise;
|
|
64
|
+
expect(result).toBe(true);
|
|
65
|
+
});
|
|
66
|
+
it("returns false when peer reports available=false", async () => {
|
|
67
|
+
const node = createMockNode();
|
|
68
|
+
const response = {
|
|
69
|
+
type: "ensure_blob_response",
|
|
70
|
+
id: 1,
|
|
71
|
+
available: false,
|
|
72
|
+
};
|
|
73
|
+
const promise = ensureBlobOverAlpn(node, "peer-unavail", "0000000000000000000000000000000000000000000000000000000000000002");
|
|
74
|
+
await new Promise((r) => setTimeout(r, 10));
|
|
75
|
+
const stream = (await node.open_bi("", ""));
|
|
76
|
+
stream.pushMessage(encodeResponse(response));
|
|
77
|
+
const result = await promise;
|
|
78
|
+
expect(result).toBe(false);
|
|
79
|
+
});
|
|
80
|
+
it("returns false when peer reports an error", async () => {
|
|
81
|
+
const node = createMockNode();
|
|
82
|
+
const response = {
|
|
83
|
+
type: "ensure_blob_response",
|
|
84
|
+
id: 1,
|
|
85
|
+
available: false,
|
|
86
|
+
error: "not authorized",
|
|
87
|
+
};
|
|
88
|
+
const promise = ensureBlobOverAlpn(node, "peer-denied", "af1349b9f5f9a1a6a0404dea36dcc9499bcb25c9adc112b7cc9a93cae41f3262");
|
|
89
|
+
await new Promise((r) => setTimeout(r, 10));
|
|
90
|
+
const stream = (await node.open_bi("", ""));
|
|
91
|
+
stream.pushMessage(encodeResponse(response));
|
|
92
|
+
const result = await promise;
|
|
93
|
+
expect(result).toBe(false);
|
|
94
|
+
});
|
|
95
|
+
it("throws when connection fails", async () => {
|
|
96
|
+
const node = {
|
|
97
|
+
open_bi: vi.fn(async () => {
|
|
98
|
+
throw new Error("connection refused");
|
|
99
|
+
}),
|
|
100
|
+
};
|
|
101
|
+
await expect(ensureBlobOverAlpn(node, "peer-unreachable", "af1349b9f5f9a1a6a0404dea36dcc9499bcb25c9adc112b7cc9a93cae41f3262")).rejects.toThrow("failed to connect to peer");
|
|
102
|
+
});
|
|
103
|
+
it("throws when stream does not support read_to_end", async () => {
|
|
104
|
+
const stream = createMockBiStream("peer", "freqhole/1");
|
|
105
|
+
stream.read_to_end = undefined;
|
|
106
|
+
const node = {
|
|
107
|
+
open_bi: vi.fn(async () => stream),
|
|
108
|
+
};
|
|
109
|
+
await expect(ensureBlobOverAlpn(node, "peer-noread", "af1349b9f5f9a1a6a0404dea36dcc9499bcb25c9adc112b7cc9a93cae41f3262")).rejects.toThrow("stream does not support read_to_end");
|
|
110
|
+
});
|
|
111
|
+
it("throws when response is malformed JSON", async () => {
|
|
112
|
+
const stream = createMockBiStream("peer-badresp", "freqhole/1");
|
|
113
|
+
const node = {
|
|
114
|
+
open_bi: vi.fn(async () => stream),
|
|
115
|
+
};
|
|
116
|
+
const promise = ensureBlobOverAlpn(node, "peer-badresp", "af1349b9f5f9a1a6a0404dea36dcc9499bcb25c9adc112b7cc9a93cae41f3262");
|
|
117
|
+
await new Promise((r) => setTimeout(r, 10));
|
|
118
|
+
stream.pushMessage(new TextEncoder().encode("not json {{{"));
|
|
119
|
+
await expect(promise).rejects.toThrow("failed to parse response");
|
|
120
|
+
});
|
|
121
|
+
it("throws when response has unexpected type", async () => {
|
|
122
|
+
const stream = createMockBiStream("peer-wrongtype", "freqhole/1");
|
|
123
|
+
const node = {
|
|
124
|
+
open_bi: vi.fn(async () => stream),
|
|
125
|
+
};
|
|
126
|
+
const promise = ensureBlobOverAlpn(node, "peer-wrongtype", "af1349b9f5f9a1a6a0404dea36dcc9499bcb25c9adc112b7cc9a93cae41f3262");
|
|
127
|
+
await new Promise((r) => setTimeout(r, 10));
|
|
128
|
+
const wrongType = { type: "ensure_blob_request", id: 99, blake3_hash: "test" };
|
|
129
|
+
stream.pushMessage(new TextEncoder().encode(JSON.stringify(wrongType)));
|
|
130
|
+
await expect(promise).rejects.toThrow("unexpected response type");
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
});
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { DEFAULT_ENSURE_ALPN, type PeerMessage, type EnsureBlobRequest, type EnsureBlobResponse, } from "./types.js";
|
|
2
|
+
export { createEnsureBlobHandler, type EnsureBlobHandlerDeps } from "./responder.js";
|
|
3
|
+
export { ensureBlobOverAlpn, type EnsureCapableNode } from "./client.js";
|
|
4
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/ensure/index.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,mBAAmB,EACnB,KAAK,WAAW,EAChB,KAAK,iBAAiB,EACtB,KAAK,kBAAkB,GACxB,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,uBAAuB,EAAE,KAAK,qBAAqB,EAAE,MAAM,gBAAgB,CAAC;AAErF,OAAO,EAAE,kBAAkB,EAAE,KAAK,iBAAiB,EAAE,MAAM,aAAa,CAAC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { BiStreamLike } from "../automerge/types.js";
|
|
2
|
+
/** dependencies for the ensure-blob handler, provided by the app. */
|
|
3
|
+
export interface EnsureBlobHandlerDeps {
|
|
4
|
+
/** check if a blob with the given blake3 hash is available. */
|
|
5
|
+
hasBlob: (blake3Hash: string) => Promise<boolean>;
|
|
6
|
+
/** optional per-blob access control. when provided, called before
|
|
7
|
+
* `hasBlob` to decide if this peer may ask for this specific blob.
|
|
8
|
+
* defaults to always allowing. */
|
|
9
|
+
allow?: (peerId: string, blake3Hash: string) => Promise<boolean>;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* creates an ALPN handler for the ensure-blob protocol. the returned
|
|
13
|
+
* function accepts a bidirectional stream, reads an ensure-blob request,
|
|
14
|
+
* optionally gates it via `deps.allow`, checks `deps.hasBlob`, and writes
|
|
15
|
+
* back a response.
|
|
16
|
+
*
|
|
17
|
+
* suitable for passing directly to `IrohNetworkAdapter.registerAlpnHandler(alpn, handler)`.
|
|
18
|
+
*/
|
|
19
|
+
export declare function createEnsureBlobHandler(deps: EnsureBlobHandlerDeps): (stream: BiStreamLike) => void;
|
|
20
|
+
//# sourceMappingURL=responder.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"responder.d.ts","sourceRoot":"","sources":["../../src/ensure/responder.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAK1D,qEAAqE;AACrE,MAAM,WAAW,qBAAqB;IACpC,+DAA+D;IAC/D,OAAO,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAClD;;uCAEmC;IACnC,KAAK,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;CAClE;AAED;;;;;;;GAOG;AACH,wBAAgB,uBAAuB,CACrC,IAAI,EAAE,qBAAqB,GAC1B,CAAC,MAAM,EAAE,YAAY,KAAK,IAAI,CAYhC"}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
// responder side of the ensure-blob protocol: receives requests, gates
|
|
2
|
+
// access, checks if blobs exist, and sends responses.
|
|
3
|
+
import { log } from "../utils/log.js";
|
|
4
|
+
const TAG = "ensure.responder";
|
|
5
|
+
/**
|
|
6
|
+
* creates an ALPN handler for the ensure-blob protocol. the returned
|
|
7
|
+
* function accepts a bidirectional stream, reads an ensure-blob request,
|
|
8
|
+
* optionally gates it via `deps.allow`, checks `deps.hasBlob`, and writes
|
|
9
|
+
* back a response.
|
|
10
|
+
*
|
|
11
|
+
* suitable for passing directly to `IrohNetworkAdapter.registerAlpnHandler(alpn, handler)`.
|
|
12
|
+
*/
|
|
13
|
+
export function createEnsureBlobHandler(deps) {
|
|
14
|
+
return (stream) => {
|
|
15
|
+
handleStream(stream, deps).catch((err) => {
|
|
16
|
+
const peerId = stream.peer_node_id();
|
|
17
|
+
const peerShort = peerId.slice(0, 16);
|
|
18
|
+
log.warn(TAG, `stream error from peer ${peerShort}:`, err instanceof Error ? err.message : err);
|
|
19
|
+
});
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
async function handleStream(stream, deps) {
|
|
23
|
+
const peerId = stream.peer_node_id();
|
|
24
|
+
const peerShort = peerId.slice(0, 16);
|
|
25
|
+
if (!stream.read_to_end) {
|
|
26
|
+
log.warn(TAG, `stream from peer ${peerShort} does not support read_to_end`);
|
|
27
|
+
stream.close();
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
const msgBytes = await stream.read_to_end(64 * 1024);
|
|
31
|
+
let msg;
|
|
32
|
+
try {
|
|
33
|
+
msg = JSON.parse(new TextDecoder().decode(msgBytes));
|
|
34
|
+
}
|
|
35
|
+
catch (err) {
|
|
36
|
+
log.warn(TAG, `failed to parse request from peer ${peerShort}:`, err instanceof Error ? err.message : err);
|
|
37
|
+
stream.close();
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
if (msg.type !== "ensure_blob_request") {
|
|
41
|
+
log.debug(TAG, `ignoring non-request message from peer ${peerShort}: ${msg.type}`);
|
|
42
|
+
stream.close();
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
const request = msg;
|
|
46
|
+
const response = await handleEnsureBlobRequest(request, peerId, deps);
|
|
47
|
+
await sendResponse(stream, response);
|
|
48
|
+
}
|
|
49
|
+
async function handleEnsureBlobRequest(request, peerId, deps) {
|
|
50
|
+
const { id, blake3_hash } = request;
|
|
51
|
+
if (blake3_hash.length !== 64) {
|
|
52
|
+
return {
|
|
53
|
+
type: "ensure_blob_response",
|
|
54
|
+
id,
|
|
55
|
+
available: false,
|
|
56
|
+
error: `expected 64-char blake3 hex, got ${blake3_hash.length}`,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
if (deps.allow) {
|
|
60
|
+
const allowed = await deps.allow(peerId, blake3_hash);
|
|
61
|
+
if (!allowed) {
|
|
62
|
+
log.info(TAG, `denied peer ${peerId.slice(0, 16)} access to blob ${blake3_hash.slice(0, 16)}`);
|
|
63
|
+
return {
|
|
64
|
+
type: "ensure_blob_response",
|
|
65
|
+
id,
|
|
66
|
+
available: false,
|
|
67
|
+
error: "not authorized",
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
let available;
|
|
72
|
+
try {
|
|
73
|
+
available = await deps.hasBlob(blake3_hash);
|
|
74
|
+
}
|
|
75
|
+
catch (err) {
|
|
76
|
+
log.warn(TAG, `hasBlob check failed for blake3=${blake3_hash.slice(0, 16)}:`, err instanceof Error ? err.message : err);
|
|
77
|
+
return {
|
|
78
|
+
type: "ensure_blob_response",
|
|
79
|
+
id,
|
|
80
|
+
available: false,
|
|
81
|
+
error: "hasBlob check failed",
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
return {
|
|
85
|
+
type: "ensure_blob_response",
|
|
86
|
+
id,
|
|
87
|
+
available,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
async function sendResponse(stream, response) {
|
|
91
|
+
const bytes = new TextEncoder().encode(JSON.stringify(response));
|
|
92
|
+
if (stream.write_raw_and_finish) {
|
|
93
|
+
await stream.write_raw_and_finish(bytes);
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
log.warn(TAG, "stream does not support write_raw_and_finish, falling back to write_message");
|
|
97
|
+
await stream.write_message(bytes);
|
|
98
|
+
stream.close();
|
|
99
|
+
}
|
|
100
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"responder.test.d.ts","sourceRoot":"","sources":["../../src/ensure/responder.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
// tests for the ensure-blob responder (server side)
|
|
2
|
+
import { describe, expect, it, vi } from "vitest";
|
|
3
|
+
import { createMockBiStream } from "../testing/index.js";
|
|
4
|
+
import { createEnsureBlobHandler } from "./responder.js";
|
|
5
|
+
describe("ensure/responder", () => {
|
|
6
|
+
function createMockDeps(overrides) {
|
|
7
|
+
return {
|
|
8
|
+
hasBlob: vi.fn(async () => true),
|
|
9
|
+
...overrides,
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
function encodeRequest(msg) {
|
|
13
|
+
return new TextEncoder().encode(JSON.stringify(msg));
|
|
14
|
+
}
|
|
15
|
+
function decodeResponse(bytes) {
|
|
16
|
+
return JSON.parse(new TextDecoder().decode(bytes));
|
|
17
|
+
}
|
|
18
|
+
describe("createEnsureBlobHandler", () => {
|
|
19
|
+
it("returns a function", () => {
|
|
20
|
+
const deps = createMockDeps();
|
|
21
|
+
const handler = createEnsureBlobHandler(deps);
|
|
22
|
+
expect(typeof handler).toBe("function");
|
|
23
|
+
});
|
|
24
|
+
it("handles a valid request when blob is available", async () => {
|
|
25
|
+
const hasBlob = vi.fn(async () => true);
|
|
26
|
+
const deps = createMockDeps({ hasBlob });
|
|
27
|
+
const handler = createEnsureBlobHandler(deps);
|
|
28
|
+
const stream = createMockBiStream("peer123", "freqhole/1");
|
|
29
|
+
const request = {
|
|
30
|
+
type: "ensure_blob_request",
|
|
31
|
+
id: 1,
|
|
32
|
+
blake3_hash: "af1349b9f5f9a1a6a0404dea36dcc9499bcb25c9adc112b7cc9a93cae41f3262",
|
|
33
|
+
};
|
|
34
|
+
handler(stream);
|
|
35
|
+
stream.pushMessage(encodeRequest(request));
|
|
36
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
37
|
+
expect(hasBlob).toHaveBeenCalledWith(request.blake3_hash);
|
|
38
|
+
expect(stream._written.length).toBe(1);
|
|
39
|
+
const response = decodeResponse(stream._written[0]);
|
|
40
|
+
expect(response.type).toBe("ensure_blob_response");
|
|
41
|
+
expect(response.id).toBe(1);
|
|
42
|
+
expect(response.available).toBe(true);
|
|
43
|
+
expect(response.error).toBeUndefined();
|
|
44
|
+
});
|
|
45
|
+
it("handles a valid request when blob is not available", async () => {
|
|
46
|
+
const hasBlob = vi.fn(async () => false);
|
|
47
|
+
const deps = createMockDeps({ hasBlob });
|
|
48
|
+
const handler = createEnsureBlobHandler(deps);
|
|
49
|
+
const stream = createMockBiStream("peer456", "freqhole/1");
|
|
50
|
+
const request = {
|
|
51
|
+
type: "ensure_blob_request",
|
|
52
|
+
id: 2,
|
|
53
|
+
blake3_hash: "0000000000000000000000000000000000000000000000000000000000000001",
|
|
54
|
+
};
|
|
55
|
+
handler(stream);
|
|
56
|
+
stream.pushMessage(encodeRequest(request));
|
|
57
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
58
|
+
expect(hasBlob).toHaveBeenCalledWith(request.blake3_hash);
|
|
59
|
+
expect(stream._written.length).toBe(1);
|
|
60
|
+
const response = decodeResponse(stream._written[0]);
|
|
61
|
+
expect(response.type).toBe("ensure_blob_response");
|
|
62
|
+
expect(response.id).toBe(2);
|
|
63
|
+
expect(response.available).toBe(false);
|
|
64
|
+
expect(response.error).toBeUndefined();
|
|
65
|
+
});
|
|
66
|
+
it("rejects requests with invalid blake3 hash length", async () => {
|
|
67
|
+
const hasBlob = vi.fn(async () => true);
|
|
68
|
+
const deps = createMockDeps({ hasBlob });
|
|
69
|
+
const handler = createEnsureBlobHandler(deps);
|
|
70
|
+
const stream = createMockBiStream("peer789", "freqhole/1");
|
|
71
|
+
const request = {
|
|
72
|
+
type: "ensure_blob_request",
|
|
73
|
+
id: 3,
|
|
74
|
+
blake3_hash: "tooshort",
|
|
75
|
+
};
|
|
76
|
+
handler(stream);
|
|
77
|
+
stream.pushMessage(encodeRequest(request));
|
|
78
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
79
|
+
expect(hasBlob).not.toHaveBeenCalled();
|
|
80
|
+
expect(stream._written.length).toBe(1);
|
|
81
|
+
const response = decodeResponse(stream._written[0]);
|
|
82
|
+
expect(response.type).toBe("ensure_blob_response");
|
|
83
|
+
expect(response.id).toBe(3);
|
|
84
|
+
expect(response.available).toBe(false);
|
|
85
|
+
expect(response.error).toContain("expected 64-char blake3 hex");
|
|
86
|
+
});
|
|
87
|
+
it("gates requests via allow callback when provided", async () => {
|
|
88
|
+
const hasBlob = vi.fn(async () => true);
|
|
89
|
+
const allow = vi.fn(async () => false);
|
|
90
|
+
const deps = createMockDeps({ hasBlob, allow });
|
|
91
|
+
const handler = createEnsureBlobHandler(deps);
|
|
92
|
+
const stream = createMockBiStream("peer-denied", "freqhole/1");
|
|
93
|
+
const request = {
|
|
94
|
+
type: "ensure_blob_request",
|
|
95
|
+
id: 4,
|
|
96
|
+
blake3_hash: "af1349b9f5f9a1a6a0404dea36dcc9499bcb25c9adc112b7cc9a93cae41f3262",
|
|
97
|
+
};
|
|
98
|
+
handler(stream);
|
|
99
|
+
stream.pushMessage(encodeRequest(request));
|
|
100
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
101
|
+
expect(allow).toHaveBeenCalledWith("peer-denied", request.blake3_hash);
|
|
102
|
+
expect(hasBlob).not.toHaveBeenCalled();
|
|
103
|
+
expect(stream._written.length).toBe(1);
|
|
104
|
+
const response = decodeResponse(stream._written[0]);
|
|
105
|
+
expect(response.type).toBe("ensure_blob_response");
|
|
106
|
+
expect(response.id).toBe(4);
|
|
107
|
+
expect(response.available).toBe(false);
|
|
108
|
+
expect(response.error).toBe("not authorized");
|
|
109
|
+
});
|
|
110
|
+
it("allows requests when allow callback returns true", async () => {
|
|
111
|
+
const hasBlob = vi.fn(async () => true);
|
|
112
|
+
const allow = vi.fn(async () => true);
|
|
113
|
+
const deps = createMockDeps({ hasBlob, allow });
|
|
114
|
+
const handler = createEnsureBlobHandler(deps);
|
|
115
|
+
const stream = createMockBiStream("peer-allowed", "freqhole/1");
|
|
116
|
+
const request = {
|
|
117
|
+
type: "ensure_blob_request",
|
|
118
|
+
id: 5,
|
|
119
|
+
blake3_hash: "af1349b9f5f9a1a6a0404dea36dcc9499bcb25c9adc112b7cc9a93cae41f3262",
|
|
120
|
+
};
|
|
121
|
+
handler(stream);
|
|
122
|
+
stream.pushMessage(encodeRequest(request));
|
|
123
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
124
|
+
expect(allow).toHaveBeenCalledWith("peer-allowed", request.blake3_hash);
|
|
125
|
+
expect(hasBlob).toHaveBeenCalledWith(request.blake3_hash);
|
|
126
|
+
expect(stream._written.length).toBe(1);
|
|
127
|
+
const response = decodeResponse(stream._written[0]);
|
|
128
|
+
expect(response.type).toBe("ensure_blob_response");
|
|
129
|
+
expect(response.id).toBe(5);
|
|
130
|
+
expect(response.available).toBe(true);
|
|
131
|
+
});
|
|
132
|
+
it("handles hasBlob throwing an error", async () => {
|
|
133
|
+
const hasBlob = vi.fn(async () => {
|
|
134
|
+
throw new Error("database connection lost");
|
|
135
|
+
});
|
|
136
|
+
const deps = createMockDeps({ hasBlob });
|
|
137
|
+
const handler = createEnsureBlobHandler(deps);
|
|
138
|
+
const stream = createMockBiStream("peer-error", "freqhole/1");
|
|
139
|
+
const request = {
|
|
140
|
+
type: "ensure_blob_request",
|
|
141
|
+
id: 6,
|
|
142
|
+
blake3_hash: "af1349b9f5f9a1a6a0404dea36dcc9499bcb25c9adc112b7cc9a93cae41f3262",
|
|
143
|
+
};
|
|
144
|
+
handler(stream);
|
|
145
|
+
stream.pushMessage(encodeRequest(request));
|
|
146
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
147
|
+
expect(hasBlob).toHaveBeenCalled();
|
|
148
|
+
expect(stream._written.length).toBe(1);
|
|
149
|
+
const response = decodeResponse(stream._written[0]);
|
|
150
|
+
expect(response.type).toBe("ensure_blob_response");
|
|
151
|
+
expect(response.id).toBe(6);
|
|
152
|
+
expect(response.available).toBe(false);
|
|
153
|
+
expect(response.error).toBe("hasBlob check failed");
|
|
154
|
+
});
|
|
155
|
+
it("handles malformed JSON gracefully", async () => {
|
|
156
|
+
const hasBlob = vi.fn(async () => true);
|
|
157
|
+
const deps = createMockDeps({ hasBlob });
|
|
158
|
+
const handler = createEnsureBlobHandler(deps);
|
|
159
|
+
const stream = createMockBiStream("peer-badmsg", "freqhole/1");
|
|
160
|
+
const malformed = new TextEncoder().encode("not json {{{");
|
|
161
|
+
handler(stream);
|
|
162
|
+
stream.pushMessage(malformed);
|
|
163
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
164
|
+
expect(hasBlob).not.toHaveBeenCalled();
|
|
165
|
+
expect(stream._closed).toBe(true);
|
|
166
|
+
});
|
|
167
|
+
it("ignores non-request message types", async () => {
|
|
168
|
+
const hasBlob = vi.fn(async () => true);
|
|
169
|
+
const deps = createMockDeps({ hasBlob });
|
|
170
|
+
const handler = createEnsureBlobHandler(deps);
|
|
171
|
+
const stream = createMockBiStream("peer-wrongtype", "freqhole/1");
|
|
172
|
+
const wrongType = new TextEncoder().encode(JSON.stringify({ type: "ensure_blob_response", id: 99, available: false }));
|
|
173
|
+
handler(stream);
|
|
174
|
+
stream.pushMessage(wrongType);
|
|
175
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
176
|
+
expect(hasBlob).not.toHaveBeenCalled();
|
|
177
|
+
expect(stream._closed).toBe(true);
|
|
178
|
+
});
|
|
179
|
+
it("handles streams without read_to_end support gracefully", async () => {
|
|
180
|
+
const hasBlob = vi.fn(async () => true);
|
|
181
|
+
const deps = createMockDeps({ hasBlob });
|
|
182
|
+
const handler = createEnsureBlobHandler(deps);
|
|
183
|
+
const stream = createMockBiStream("peer-noread", "freqhole/1");
|
|
184
|
+
stream.read_to_end = undefined;
|
|
185
|
+
handler(stream);
|
|
186
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
187
|
+
expect(hasBlob).not.toHaveBeenCalled();
|
|
188
|
+
expect(stream._closed).toBe(true);
|
|
189
|
+
});
|
|
190
|
+
});
|
|
191
|
+
});
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/** default ALPN for ensure-blob protocol requests, shared across all apps. */
|
|
2
|
+
export declare const DEFAULT_ENSURE_ALPN = "freqhole/1";
|
|
3
|
+
/** discriminated union of all ensure-protocol messages. */
|
|
4
|
+
export type PeerMessage = EnsureBlobRequest | EnsureBlobResponse;
|
|
5
|
+
/** request that a peer stage a blob (by blake3 hash) in its iroh-blobs
|
|
6
|
+
* store for verified transfer. */
|
|
7
|
+
export interface EnsureBlobRequest {
|
|
8
|
+
type: "ensure_blob_request";
|
|
9
|
+
id: number;
|
|
10
|
+
blake3_hash: string;
|
|
11
|
+
}
|
|
12
|
+
/** response to an `EnsureBlobRequest`. */
|
|
13
|
+
export interface EnsureBlobResponse {
|
|
14
|
+
type: "ensure_blob_response";
|
|
15
|
+
id: number;
|
|
16
|
+
available: boolean;
|
|
17
|
+
error?: string;
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/ensure/types.ts"],"names":[],"mappings":"AAGA,8EAA8E;AAC9E,eAAO,MAAM,mBAAmB,eAAe,CAAC;AAEhD,2DAA2D;AAC3D,MAAM,MAAM,WAAW,GAAG,iBAAiB,GAAG,kBAAkB,CAAC;AAEjE;mCACmC;AACnC,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,qBAAqB,CAAC;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,0CAA0C;AAC1C,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,sBAAsB,CAAC;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,OAAO,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
// message types for the ensure-blob protocol. matches the rust `reliquary::ensure::PeerMessage`
|
|
2
|
+
// enum's wire format exactly: `#[serde(tag = "type", rename_all = "snake_case")]`.
|
|
3
|
+
/** default ALPN for ensure-blob protocol requests, shared across all apps. */
|
|
4
|
+
export const DEFAULT_ENSURE_ALPN = "freqhole/1";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.test.d.ts","sourceRoot":"","sources":["../../src/ensure/types.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
// tests for the ensure-blob protocol message types
|
|
2
|
+
import { describe, expect, it } from "vitest";
|
|
3
|
+
import { DEFAULT_ENSURE_ALPN } from "./types.js";
|
|
4
|
+
describe("ensure/types", () => {
|
|
5
|
+
describe("DEFAULT_ENSURE_ALPN", () => {
|
|
6
|
+
it("exports the correct ALPN string", () => {
|
|
7
|
+
expect(DEFAULT_ENSURE_ALPN).toBe("freqhole/1");
|
|
8
|
+
});
|
|
9
|
+
});
|
|
10
|
+
describe("EnsureBlobRequest", () => {
|
|
11
|
+
it("serializes with snake_case type tag", () => {
|
|
12
|
+
const msg = {
|
|
13
|
+
type: "ensure_blob_request",
|
|
14
|
+
id: 42,
|
|
15
|
+
blake3_hash: "af1349b9f5f9a1a6a0404dea36dcc9499bcb25c9adc112b7cc9a93cae41f3262",
|
|
16
|
+
};
|
|
17
|
+
const json = JSON.stringify(msg);
|
|
18
|
+
expect(json).toContain("ensure_blob_request");
|
|
19
|
+
expect(json).toContain("blake3_hash");
|
|
20
|
+
});
|
|
21
|
+
it("roundtrips through JSON", () => {
|
|
22
|
+
const original = {
|
|
23
|
+
type: "ensure_blob_request",
|
|
24
|
+
id: 7,
|
|
25
|
+
blake3_hash: "0000000000000000000000000000000000000000000000000000000000000001",
|
|
26
|
+
};
|
|
27
|
+
const json = JSON.stringify(original);
|
|
28
|
+
const parsed = JSON.parse(json);
|
|
29
|
+
expect(parsed.type).toBe("ensure_blob_request");
|
|
30
|
+
expect(parsed.id).toBe(7);
|
|
31
|
+
expect(parsed.blake3_hash).toBe("0000000000000000000000000000000000000000000000000000000000000001");
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
describe("EnsureBlobResponse", () => {
|
|
35
|
+
it("serializes with snake_case type tag", () => {
|
|
36
|
+
const msg = {
|
|
37
|
+
type: "ensure_blob_response",
|
|
38
|
+
id: 42,
|
|
39
|
+
available: true,
|
|
40
|
+
};
|
|
41
|
+
const json = JSON.stringify(msg);
|
|
42
|
+
expect(json).toContain("ensure_blob_response");
|
|
43
|
+
});
|
|
44
|
+
it("roundtrips through JSON when available=true", () => {
|
|
45
|
+
const original = {
|
|
46
|
+
type: "ensure_blob_response",
|
|
47
|
+
id: 10,
|
|
48
|
+
available: true,
|
|
49
|
+
};
|
|
50
|
+
const json = JSON.stringify(original);
|
|
51
|
+
const parsed = JSON.parse(json);
|
|
52
|
+
expect(parsed.type).toBe("ensure_blob_response");
|
|
53
|
+
expect(parsed.id).toBe(10);
|
|
54
|
+
expect(parsed.available).toBe(true);
|
|
55
|
+
expect(parsed.error).toBeUndefined();
|
|
56
|
+
});
|
|
57
|
+
it("roundtrips through JSON when available=false with error", () => {
|
|
58
|
+
const original = {
|
|
59
|
+
type: "ensure_blob_response",
|
|
60
|
+
id: 20,
|
|
61
|
+
available: false,
|
|
62
|
+
error: "not authorized",
|
|
63
|
+
};
|
|
64
|
+
const json = JSON.stringify(original);
|
|
65
|
+
const parsed = JSON.parse(json);
|
|
66
|
+
expect(parsed.type).toBe("ensure_blob_response");
|
|
67
|
+
expect(parsed.id).toBe(20);
|
|
68
|
+
expect(parsed.available).toBe(false);
|
|
69
|
+
expect(parsed.error).toBe("not authorized");
|
|
70
|
+
});
|
|
71
|
+
it("omits error field when undefined", () => {
|
|
72
|
+
const msg = {
|
|
73
|
+
type: "ensure_blob_response",
|
|
74
|
+
id: 5,
|
|
75
|
+
available: true,
|
|
76
|
+
};
|
|
77
|
+
const json = JSON.stringify(msg);
|
|
78
|
+
expect(json).not.toContain("error");
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
});
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,iBAAiB,UAAU,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.test.d.ts","sourceRoot":"","sources":["../src/index.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type Resource } from "solid-js";
|
|
2
|
+
/**
|
|
3
|
+
* fetches a blob via `getBlob` and exposes its object url as a solid
|
|
4
|
+
* resource. `null` while loading, on a null blob, or after an error.
|
|
5
|
+
* every previous url is revoked as soon as it's superseded (by a refetch)
|
|
6
|
+
* or the resource is disposed - a caller never needs to manage
|
|
7
|
+
* `URL.revokeObjectURL` itself.
|
|
8
|
+
*/
|
|
9
|
+
export declare function createBlobUrl(getBlob: () => Promise<Blob | null>): Resource<string | null>;
|
|
10
|
+
//# sourceMappingURL=blob-url.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"blob-url.d.ts","sourceRoot":"","sources":["../../src/solid/blob-url.ts"],"names":[],"mappings":"AASA,OAAO,EAA2C,KAAK,QAAQ,EAAE,MAAM,UAAU,CAAC;AAElF;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC,GAAG,QAAQ,CAAC,MAAM,GAAG,IAAI,CAAC,CAkB1F"}
|