@decentnetwork/lan 0.1.184 → 0.1.185
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.
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -121,42 +121,57 @@ export class HlsPrefetcher {
|
|
|
121
121
|
const whole = () => this.#fetcher(u, { host: u.host }, "prefetch");
|
|
122
122
|
if (this.#stripes <= 1)
|
|
123
123
|
return whole();
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
}
|
|
129
|
-
catch {
|
|
130
|
-
return whole();
|
|
131
|
-
}
|
|
132
|
-
if (first.status === 200)
|
|
133
|
-
return first; // origin ignored Range → whole body already
|
|
134
|
-
if (first.status !== 206)
|
|
124
|
+
// Tiny probe to learn total size + confirm Range support (hedged so a dead
|
|
125
|
+
// exit doesn't stall the whole segment before parallelism starts).
|
|
126
|
+
const probe = await this.#fetchRange(u, "bytes=0-1", 2000, 3);
|
|
127
|
+
if (!probe || probe.status !== 206)
|
|
135
128
|
return whole();
|
|
136
|
-
const total = contentRangeTotal(
|
|
137
|
-
const outHeaders = { ...
|
|
129
|
+
const total = contentRangeTotal(probe.headers["content-range"]);
|
|
130
|
+
const outHeaders = { ...probe.headers };
|
|
138
131
|
delete outHeaders["content-range"];
|
|
139
|
-
if (!total
|
|
140
|
-
return
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
132
|
+
if (!total)
|
|
133
|
+
return whole();
|
|
134
|
+
if (total <= this.#stripeChunkBytes) {
|
|
135
|
+
// Small segment — one range fetch (hedged) is enough.
|
|
136
|
+
const one = await this.#fetchRange(u, `bytes=0-${total - 1}`, 6000, 3);
|
|
137
|
+
return one && one.body.length === total ? { status: 200, headers: outHeaders, body: one.body } : whole();
|
|
138
|
+
}
|
|
139
|
+
const n = Math.max(2, Math.min(this.#stripes, Math.ceil(total / this.#stripeChunkBytes)));
|
|
140
|
+
const chunk = Math.ceil(total / n);
|
|
141
|
+
// Timeout scales with chunk size: allow ~0.3 Mbps before we give up on an
|
|
142
|
+
// exit and retry the SAME range on another (kills the dead-exit straggler
|
|
143
|
+
// that otherwise holds the whole segment hostage).
|
|
144
|
+
const rangeTimeout = Math.max(3000, Math.round((chunk * 8) / 300)); // ms at 0.3 Mbps
|
|
144
145
|
const parts = await Promise.all(Array.from({ length: n }, (_, i) => {
|
|
145
|
-
const start =
|
|
146
|
+
const start = i * chunk;
|
|
146
147
|
const end = Math.min(start + chunk - 1, total - 1);
|
|
147
148
|
if (start > end)
|
|
148
149
|
return Promise.resolve(Buffer.alloc(0));
|
|
149
|
-
return this.#
|
|
150
|
-
.then((r) => (
|
|
151
|
-
.catch(() => null);
|
|
150
|
+
return this.#fetchRange(u, `bytes=${start}-${end}`, rangeTimeout, 3)
|
|
151
|
+
.then((r) => (r && r.body.length === end - start + 1 ? r.body : null));
|
|
152
152
|
}));
|
|
153
153
|
if (parts.some((p) => p === null))
|
|
154
|
-
return whole(); // a
|
|
155
|
-
const body = Buffer.concat(
|
|
154
|
+
return whole(); // a range never landed → refetch whole for integrity
|
|
155
|
+
const body = Buffer.concat(parts);
|
|
156
156
|
if (body.length !== total)
|
|
157
157
|
return whole();
|
|
158
158
|
return { status: 200, headers: outHeaders, body };
|
|
159
159
|
}
|
|
160
|
+
/** Fetch one byte-range with a slow-transfer TIMEOUT + retry on another exit.
|
|
161
|
+
* A stripe stuck on a dead exit (0.0 Mbps) times out and is re-issued (the
|
|
162
|
+
* router rotates to a different exit), so one bad exit can't stall a segment. */
|
|
163
|
+
async #fetchRange(u, range, timeoutMs, tries) {
|
|
164
|
+
for (let t = 0; t < tries; t++) {
|
|
165
|
+
const got = await Promise.race([
|
|
166
|
+
this.#fetcher(u, { host: u.host, range }, "stripe").catch(() => null),
|
|
167
|
+
new Promise((res) => setTimeout(() => res("timeout"), timeoutMs)),
|
|
168
|
+
]);
|
|
169
|
+
if (got && got !== "timeout" && (got.status === 206 || got.status === 200))
|
|
170
|
+
return got;
|
|
171
|
+
// timed out (slow exit) or errored → loop; the next fetch rotates exits.
|
|
172
|
+
}
|
|
173
|
+
return null;
|
|
174
|
+
}
|
|
160
175
|
get stats() {
|
|
161
176
|
return { hits: this.#hits, misses: this.#misses, entries: this.#cache.size, bytes: this.#cacheBytes };
|
|
162
177
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@decentnetwork/lan",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.185",
|
|
4
4
|
"description": "Private virtual LAN for self-hosted services and AI agents, built on Elastos Carrier. NAT-traversal, name service, ACL, all over a peer-to-peer mesh — no public IP required.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|