@lat-murmeldjur/weeb_3 0.0.321001 → 0.0.323001
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 +35 -36
- package/package.json +1 -4
- package/service.js +20 -77
- package/snippets/weeb_3-03f860286800ffdb/static/hls_loader.js +0 -2
- package/weeb_3.d.ts +45 -268
- package/weeb_3.js +615 -1506
- package/weeb_3_bg.wasm +0 -0
- package/weeb_3_bg.wasm.d.ts +43 -123
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
`weeb-3` is a browser-side Swarm client built in Rust and compiled to WebAssembly.
|
|
4
4
|
The main `weeb-3` project is the full released browser client, published at [lat-murmeldjur.github.io/weeb-3](https://lat-murmeldjur.github.io/weeb-3), where the client is used together with its own interface.
|
|
5
5
|
|
|
6
|
-
This npm package is the library edition of that same client. Projects can use the API directly or mount the bundled browser interface with `renderInterface(container)`.
|
|
6
|
+
This npm package is the library edition of that same client. Projects can use the API directly, attach Swarm HLS playback to their own media element, or mount the bundled browser interface with `renderInterface(container)`.
|
|
7
7
|
|
|
8
8
|
Project repository: [github.com/lat-murmeldjur/weeb-3](https://github.com/lat-murmeldjur/weeb-3)
|
|
9
9
|
|
|
@@ -22,43 +22,29 @@ This package contains the browser-targeted WebAssembly build of the `weeb-3` cli
|
|
|
22
22
|
The main exports are:
|
|
23
23
|
|
|
24
24
|
- `Weeb3No103` as the higher-level client interface
|
|
25
|
-
- `BootstrapNode` for defining bootstrap peers
|
|
26
|
-
- `Weeb3` for lower-level direct access to the underlying client
|
|
27
25
|
|
|
28
26
|
The higher-level `Weeb3No103` interface provides the main methods used by the embedding example:
|
|
29
27
|
|
|
30
28
|
- `start(options?)`
|
|
31
|
-
- `connect()`
|
|
32
29
|
- `networkState()`
|
|
33
|
-
- `
|
|
34
|
-
- `switchTestnet()` / `switch_testnet()`
|
|
35
|
-
- `switchNetwork(mode)` / `switch_network(mode)`
|
|
36
|
-
- `connectProfile(mode)` / `connect_profile(mode)`
|
|
30
|
+
- `switchNetwork(mode)`
|
|
37
31
|
- `retrieve(address)`
|
|
38
32
|
- `upload(file, encryption, index_string, add_to_feed, feed_topic)`
|
|
39
33
|
- `uploadWithRedundancy(file, encryption, redundancy_level, index_string, add_to_feed, feed_topic)`
|
|
40
34
|
- `postUploadBytesWithRedundancy(bytes, mime, filename, encryption, redundancy_level, add_to_feed, feed_topic)`
|
|
41
|
-
- `
|
|
42
|
-
- `playHlsStream(owner, topic, media_type, index?)`
|
|
43
|
-
- `attachHlsStream(media, owner, topic, options)`
|
|
44
|
-
- `detachHlsStream()`
|
|
45
|
-
- `configureStreamingRoutes(service_worker_url, route_base)`
|
|
35
|
+
- `attachStream(media, owner, topic, start)`
|
|
46
36
|
- `renderInterface(container)`
|
|
47
37
|
- `resetStamp()`
|
|
48
38
|
- `postPushChunk(data, soc, chunk_address, stamp)`
|
|
49
39
|
|
|
50
40
|
Sequence-feed indexes use Bee's fixed-width eight-byte big-endian encoding.
|
|
51
41
|
|
|
52
|
-
## HLS streaming
|
|
53
|
-
|
|
54
|
-
HLS playback is an optional dapp integration, not a Bee/Swarm standard. `playHlsStream(...)` displays a stream in the bundled interface after `renderInterface(container)`. For an application-owned `<video>` or `<audio>` element, call `attachHlsStream(media, owner, topic, { start: "beginning" })`; use `"current-window"` for a rolling/live presentation. Call `detachHlsStream()` before removing or replacing that element. The Service Worker must be copied from the package to a same-origin URL whose scope contains the page, then configured with `configureStreamingRoutes(...)`. Canonical mainnet links use `/stream/{owner}/{topic}[/{index}]`; testnet inserts `/testnet` before `/stream`.
|
|
55
|
-
|
|
56
42
|
## Basic usage
|
|
57
43
|
|
|
58
44
|
Call `init()` once before creating a client instance so the WebAssembly module is loaded.
|
|
59
45
|
|
|
60
46
|
```js
|
|
61
|
-
import init, { Weeb3No103
|
|
47
|
+
import init, { Weeb3No103 } from "@lat-murmeldjur/weeb_3";
|
|
62
48
|
|
|
63
49
|
await init();
|
|
64
50
|
|
|
@@ -69,17 +55,12 @@ weeb3node.start();
|
|
|
69
55
|
console.log(await weeb3node.networkState());
|
|
70
56
|
|
|
71
57
|
// Switch explicitly between built-in profiles.
|
|
72
|
-
await weeb3node.switchTestnet();
|
|
73
|
-
await weeb3node.switchMainnet();
|
|
74
|
-
|
|
75
|
-
// Or use the generic form. Accepted values include:
|
|
76
|
-
// "mainnet", "gnosis", "1", "testnet", "sepolia", and "10".
|
|
77
58
|
await weeb3node.switchNetwork("testnet");
|
|
78
59
|
await weeb3node.switchNetwork("mainnet");
|
|
79
60
|
|
|
80
61
|
// You can still start with explicit browser-dialable bootnodes and network id.
|
|
81
62
|
const BOOTSTRAP_NODES = [
|
|
82
|
-
|
|
63
|
+
{ multiaddr: "/ip4/example/tcp/443/wss/p2p/examplePeerId", usable: true },
|
|
83
64
|
];
|
|
84
65
|
|
|
85
66
|
weeb3node.start({
|
|
@@ -91,41 +72,59 @@ weeb3node.start({
|
|
|
91
72
|
weeb3node.start({ testnet: true });
|
|
92
73
|
```
|
|
93
74
|
|
|
75
|
+
## Stream playback
|
|
76
|
+
|
|
77
|
+
The application creates and owns the media element. `"beginning"` reconstructs the earliest available feed timeline, while `"live"` opens the current authenticated feed head and starts at the player's safe live-sync position. Calling `attachStream` again switches the same element between them.
|
|
78
|
+
|
|
79
|
+
```js
|
|
80
|
+
import init, { Weeb3No103 } from "@lat-murmeldjur/weeb_3";
|
|
81
|
+
|
|
82
|
+
await init();
|
|
83
|
+
|
|
84
|
+
const node = new Weeb3No103();
|
|
85
|
+
const video = document.querySelector("video");
|
|
86
|
+
const owner = "6F2728386F8a47ef5EBe323721188e630Ff0FdE9";
|
|
87
|
+
const topic = "b347b89b-933c-424f-a3d1-403bdd270b25";
|
|
88
|
+
|
|
89
|
+
node.start();
|
|
90
|
+
await node.attachStream(video, owner, topic, "beginning");
|
|
91
|
+
|
|
92
|
+
// Later, for example from the application's own Live button:
|
|
93
|
+
await node.attachStream(video, owner, topic, "live");
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Public HLS feeds use mainnet. The embedding page must be below the `/weeb-3/` scope and serve the packaged worker at `/weeb-3/service.js`, or already be controlled by a worker implementing the same forwarding protocol.
|
|
97
|
+
|
|
94
98
|
## Example corresponding to `example.html`
|
|
95
99
|
|
|
96
100
|
This is a compact npm-import form of the same usage pattern shown in the project's `example.html`:
|
|
97
101
|
|
|
98
102
|
```js
|
|
99
|
-
import init, { Weeb3No103
|
|
103
|
+
import init, { Weeb3No103 } from "@lat-murmeldjur/weeb_3";
|
|
100
104
|
|
|
101
105
|
await init();
|
|
102
106
|
|
|
103
107
|
const weeb3node = new Weeb3No103();
|
|
104
108
|
|
|
105
|
-
await weeb3node.
|
|
109
|
+
await weeb3node.switchNetwork("mainnet");
|
|
106
110
|
|
|
107
111
|
const entries = await weeb3node.retrieve(
|
|
108
112
|
"695fceb3a8c212cd123e2e40d86ec08b52fe4fe6ca46687ce9ea69b8f05471f6aa25b5d4d41bf78b1db3479c048fd5fd8137ba844604821b71786196306b68e7"
|
|
109
113
|
);
|
|
110
114
|
```
|
|
111
115
|
|
|
112
|
-
##
|
|
116
|
+
## Upload API
|
|
113
117
|
|
|
114
118
|
Legacy upload methods use Bee's Medium level. Explicit upload levels are `0` None, `1` Medium, `2` Strong, `3` Insane, and `4` Paranoid. The generated TypeScript declaration exposes this as `UploadRedundancyLevel = 0 | 1 | 2 | 3 | 4`.
|
|
115
119
|
|
|
116
|
-
`renderInterface(container)` includes a Medium-default erasure-coding dropdown. A custom interface can
|
|
120
|
+
`renderInterface(container)` includes a Medium-default erasure-coding dropdown. A custom interface can pass the selected numeric level directly:
|
|
117
121
|
|
|
118
122
|
```js
|
|
119
|
-
import init, {
|
|
120
|
-
Weeb3No103,
|
|
121
|
-
defaultUploadRedundancyLevel,
|
|
122
|
-
uploadRedundancyOptions,
|
|
123
|
-
} from "@lat-murmeldjur/weeb_3";
|
|
123
|
+
import init, { Weeb3No103 } from "@lat-murmeldjur/weeb_3";
|
|
124
124
|
|
|
125
125
|
await init();
|
|
126
126
|
const node = new Weeb3No103();
|
|
127
|
-
const
|
|
128
|
-
const level = defaultUploadRedundancyLevel();
|
|
127
|
+
const level = 1;
|
|
129
128
|
|
|
130
129
|
const result = await node.uploadWithRedundancy(
|
|
131
130
|
file,
|
|
@@ -142,6 +141,6 @@ Retrieval reads the level encoded in the Swarm tree and uses parity when data sh
|
|
|
142
141
|
## Notes
|
|
143
142
|
|
|
144
143
|
- This package is meant for browser applications, not a plain Node.js runtime.
|
|
145
|
-
-
|
|
144
|
+
- `attachStream` uses the same runtime, connection buildup, retrieval, prefetch, accounting, hls.js or native-HLS player, and recovery logic as the built-in interface.
|
|
146
145
|
- The package does not publish the standalone site HTML, but `renderInterface(container)` embeds the same interface shell—including its erasure-coding selector—from the Wasm bundle.
|
|
147
146
|
- The full released browser client remains available in the main project repository and on the project site.
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@lat-murmeldjur/weeb_3",
|
|
3
3
|
"type": "module",
|
|
4
4
|
"description": "A Swarm client for browsers",
|
|
5
|
-
"version": "0.0.
|
|
5
|
+
"version": "0.0.323001",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"repository": {
|
|
8
8
|
"type": "git",
|
|
@@ -42,9 +42,6 @@
|
|
|
42
42
|
"types": "./weeb_3.d.ts",
|
|
43
43
|
"import": "./weeb_3.js",
|
|
44
44
|
"default": "./weeb_3.js"
|
|
45
|
-
},
|
|
46
|
-
"./service.js": {
|
|
47
|
-
"default": "./service.js"
|
|
48
45
|
}
|
|
49
46
|
},
|
|
50
47
|
"dependencies": {
|
package/service.js
CHANGED
|
@@ -6,13 +6,11 @@ const NETWORK_ROUTE_PREFIXES = ["", "mainnet/", "testnet/"];
|
|
|
6
6
|
const RAW_ROUTE_KINDS = [
|
|
7
7
|
["hls/bytes", "hls-bytes"],
|
|
8
8
|
["bytes", "bytes"],
|
|
9
|
-
["chunks", "chunk"]
|
|
10
|
-
["chunk", "chunk"]
|
|
9
|
+
["chunks", "chunk"]
|
|
11
10
|
];
|
|
12
11
|
const FETCH_TIMEOUT_MS = 240000;
|
|
13
12
|
const SERVICE_WORKER_MARKER = "forwarder-default20";
|
|
14
13
|
const SERVICE_WORKER_PROTOCOL = 5;
|
|
15
|
-
const DEBUG_SERVICE_WORKER = false;
|
|
16
14
|
const MIB_BYTES = 1024 * 1024;
|
|
17
15
|
const STREAM_STORAGE_WINDOW_BYTES = MIB_BYTES / 2;
|
|
18
16
|
const STREAM_LOOKAHEAD_CHUNKS = 8;
|
|
@@ -22,12 +20,6 @@ const CLIENT_RUNTIME_PROBE_TIMEOUT_MS = 1_500;
|
|
|
22
20
|
|
|
23
21
|
console.log(`weeb-3 service worker start ${SERVICE_WORKER_MARKER}`);
|
|
24
22
|
|
|
25
|
-
function debugLog(...args) {
|
|
26
|
-
if (DEBUG_SERVICE_WORKER) {
|
|
27
|
-
console.log(...args);
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
23
|
function logServiceWorkerVersion(reason) {
|
|
32
24
|
console.log(`weeb-3 service worker ${reason} ${SERVICE_WORKER_MARKER}`);
|
|
33
25
|
}
|
|
@@ -79,32 +71,17 @@ function isCanonicalStreamTopic(value) {
|
|
|
79
71
|
}
|
|
80
72
|
}
|
|
81
73
|
|
|
82
|
-
function isCanonicalStreamIndex(value) {
|
|
83
|
-
if (value === undefined) {
|
|
84
|
-
return true;
|
|
85
|
-
}
|
|
86
|
-
return /^[0-9]+$/.test(value) && BigInt(value) <= 18446744073709551615n;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
74
|
function isDirectShareShellPath(pathname) {
|
|
90
75
|
if (!pathname.startsWith(SCOPE_PATH)) {
|
|
91
76
|
return false;
|
|
92
77
|
}
|
|
93
78
|
|
|
94
79
|
const parts = pathname.substring(SCOPE_PATH.length).split("/");
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
if (kind !== "stream") {
|
|
101
|
-
return false;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
return (parts.length === 2 || parts.length === 3) &&
|
|
105
|
-
/^(?:0[xX])?[a-fA-F0-9]{40}$/.test(parts[0] || "") &&
|
|
106
|
-
isCanonicalStreamTopic(parts[1]) &&
|
|
107
|
-
isCanonicalStreamIndex(parts[2]);
|
|
80
|
+
const streamOffset = parts[0] === "live" ? 1 : 0;
|
|
81
|
+
return parts.length === streamOffset + 3 &&
|
|
82
|
+
parts[streamOffset] === "stream" &&
|
|
83
|
+
/^[a-fA-F0-9]{40}$/.test(parts[streamOffset + 1]) &&
|
|
84
|
+
isCanonicalStreamTopic(parts[streamOffset + 2]);
|
|
108
85
|
}
|
|
109
86
|
|
|
110
87
|
function isBzzUploadPath(pathname) {
|
|
@@ -138,21 +115,26 @@ function canonicalBzzResource(url) {
|
|
|
138
115
|
}
|
|
139
116
|
|
|
140
117
|
function canonicalRawResource(url) {
|
|
141
|
-
for (const [marker] of rawRouteMarkers()) {
|
|
118
|
+
for (const [marker, rawType] of rawRouteMarkers()) {
|
|
142
119
|
if (!url.pathname.startsWith(marker)) {
|
|
143
120
|
continue;
|
|
144
121
|
}
|
|
145
122
|
|
|
146
|
-
const
|
|
147
|
-
if (!
|
|
123
|
+
const encodedResource = url.pathname.substring(marker.length);
|
|
124
|
+
if (!encodedResource) {
|
|
148
125
|
return null;
|
|
149
126
|
}
|
|
150
127
|
|
|
128
|
+
let resource;
|
|
151
129
|
try {
|
|
152
|
-
|
|
130
|
+
resource = decodeURIComponent(encodedResource);
|
|
153
131
|
} catch (_) {
|
|
154
|
-
|
|
132
|
+
resource = encodedResource;
|
|
155
133
|
}
|
|
134
|
+
if (rawType === "hls-bytes" && !isSwarmReference(resource)) {
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
return resource;
|
|
156
138
|
}
|
|
157
139
|
|
|
158
140
|
return null;
|
|
@@ -180,15 +162,6 @@ function canonicalFeedResource(url) {
|
|
|
180
162
|
return null;
|
|
181
163
|
}
|
|
182
164
|
|
|
183
|
-
function isCanonicalRequest(request) {
|
|
184
|
-
try {
|
|
185
|
-
const url = new URL(request.url);
|
|
186
|
-
return canonicalBzzResource(url) !== null || canonicalRawResource(url) !== null;
|
|
187
|
-
} catch (_) {
|
|
188
|
-
return false;
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
|
|
192
165
|
function isAppShellNavigation(request) {
|
|
193
166
|
const headerDestination = request.headers.get("Sec-Fetch-Dest") || "";
|
|
194
167
|
return request.method === "GET" &&
|
|
@@ -295,9 +268,6 @@ self.addEventListener("fetch", (event) => {
|
|
|
295
268
|
return;
|
|
296
269
|
}
|
|
297
270
|
|
|
298
|
-
// This worker is safe to mount in an npm application: requests outside the
|
|
299
|
-
// explicit weeb-3 routes remain entirely under the host app/browser's
|
|
300
|
-
// normal fetch and caching policy.
|
|
301
271
|
});
|
|
302
272
|
|
|
303
273
|
function clientInScope(client) {
|
|
@@ -382,10 +352,7 @@ async function firstReadyClient(candidates, requiredNetworkId) {
|
|
|
382
352
|
return [];
|
|
383
353
|
}
|
|
384
354
|
|
|
385
|
-
//
|
|
386
|
-
// most one probe timeout. Preserve the originating client's fast path and
|
|
387
|
-
// candidate priority; the accounting-sensitive operation itself is still
|
|
388
|
-
// sent exactly once by messageFirstClient.
|
|
355
|
+
// Probe candidates concurrently without redispatching work.
|
|
389
356
|
const probes = candidates.map((candidate) => clientWeeb3NetworkId(candidate));
|
|
390
357
|
if (await probes[0] === requiredNetworkId) {
|
|
391
358
|
return [candidates[0]];
|
|
@@ -413,12 +380,7 @@ async function requestClients(event, requestUrl, requiredNetworkId) {
|
|
|
413
380
|
)
|
|
414
381
|
);
|
|
415
382
|
|
|
416
|
-
//
|
|
417
|
-
// player, whether that page is the bundled shell or an arbitrary npm host.
|
|
418
|
-
// Dispatch those directly: the Rust bridge validates networkId before
|
|
419
|
-
// retrieval, while a redundant liveness round-trip here can false-timeout
|
|
420
|
-
// when a large WASM retrieval wave briefly occupies the browser thread.
|
|
421
|
-
// Nested clients retain the probed fallback below.
|
|
383
|
+
// Direct top-level HLS requests skip the redundant liveness probe.
|
|
422
384
|
if (
|
|
423
385
|
directHlsRequest &&
|
|
424
386
|
eventClient &&
|
|
@@ -436,9 +398,6 @@ async function requestClients(event, requestUrl, requiredNetworkId) {
|
|
|
436
398
|
const seen = new Set();
|
|
437
399
|
const requestReference = requestUrlObject ? bzzReferenceFromUrl(requestUrlObject) : "";
|
|
438
400
|
|
|
439
|
-
// Prefer the top-level context that originated the request. A nested BZZ
|
|
440
|
-
// site frame does not host the Rust runtime and must fall through to its
|
|
441
|
-
// active app-shell tab below.
|
|
442
401
|
if (eventClient && isTopLevelClient(eventClient) && clientInScope(eventClient)) {
|
|
443
402
|
pushUniqueClient(candidates, seen, eventClient);
|
|
444
403
|
}
|
|
@@ -532,10 +491,7 @@ function messageFirstClient(clients, message, timeoutMs = FETCH_TIMEOUT_MS) {
|
|
|
532
491
|
});
|
|
533
492
|
}
|
|
534
493
|
|
|
535
|
-
//
|
|
536
|
-
// each request exactly once. A timeout merely detaches this response port;
|
|
537
|
-
// already-dispatched work is deliberately left to drain and is never replayed
|
|
538
|
-
// through another tab.
|
|
494
|
+
// Timeouts detach the port; dispatched accounting work is never replayed.
|
|
539
495
|
return messageClient(clients[0], message, timeoutMs);
|
|
540
496
|
}
|
|
541
497
|
|
|
@@ -638,16 +594,6 @@ function toUint8Array(body) {
|
|
|
638
594
|
return new Uint8Array();
|
|
639
595
|
}
|
|
640
596
|
|
|
641
|
-
function oneChunkResponseBody(body) {
|
|
642
|
-
const bytes = toUint8Array(body);
|
|
643
|
-
return new ReadableStream({
|
|
644
|
-
start(controller) {
|
|
645
|
-
controller.enqueue(bytes);
|
|
646
|
-
controller.close();
|
|
647
|
-
}
|
|
648
|
-
});
|
|
649
|
-
}
|
|
650
|
-
|
|
651
597
|
function responseHeaders(headerRows) {
|
|
652
598
|
const headers = new Headers();
|
|
653
599
|
for (const row of headerRows || []) {
|
|
@@ -774,7 +720,7 @@ async function forwardRequestToRust(request, event) {
|
|
|
774
720
|
return new Response(
|
|
775
721
|
request.method === "HEAD" || status === 304
|
|
776
722
|
? null
|
|
777
|
-
:
|
|
723
|
+
: toUint8Array(response.body),
|
|
778
724
|
{
|
|
779
725
|
status,
|
|
780
726
|
headers
|
|
@@ -788,12 +734,9 @@ async function forwardRequestToRust(request, event) {
|
|
|
788
734
|
}
|
|
789
735
|
|
|
790
736
|
function parseUploadRedundancyHeader(value) {
|
|
791
|
-
// Bee marks this header `omitempty`, so a present empty value has the same
|
|
792
|
-
// Medium default as an omitted header.
|
|
793
737
|
if (value === null || value === "") {
|
|
794
738
|
return 1;
|
|
795
739
|
}
|
|
796
|
-
// Bee parses an unsigned base-10 header, so reject Number()-only forms such as hex or exponents.
|
|
797
740
|
if (!/^[0-9]+$/.test(value)) {
|
|
798
741
|
return null;
|
|
799
742
|
}
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
// Dynamic `import()` has no CSP-safe Web API callable directly from Wasm.
|
|
2
|
-
// Keep this irreducible bridge tiny; player policy and lifecycle live in Rust.
|
|
3
1
|
export async function loadHls() {
|
|
4
2
|
const module = await import("hls.js");
|
|
5
3
|
return module.default ?? module.Hls;
|