feat(webseed): carry multiple web seeds through publish (slice 1/3)

Foundation for declaring more than one web seed per video so the WebTorrent
swarm can pull pieces from an independent backup origin when the primary CDN
is overloaded — multiple `ws=` params are the standard BEP19 carrier and the
playback path already attaches every webseed it finds in the magnet.

- magnetShared: add extractAllWebSeeds(), which returns every `ws=` URL
  (deduped, in order) instead of only the first like extractMagnetHints —
  so a magnet with several webseeds can be round-tripped without dropping the
  extras.
- videoNotePayload: normalizeWebSeedList() accepts the ws field as an array OR
  a newline/comma-delimited string and feeds the full, deduped set into the
  magnet (plus the hosted CDN url, as before). A single ws string still works
  unchanged; the content `ws` field still reflects the primary seed.

Upload/Edit UI to author and manage the list lands in slices 2 and 3.

Mutation-verified (array-drop, split-drop, first-only, dedupe-loss all fail
the new tests); build + full unit suite green.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
thePR0M3TH3ANandClaude Opus 4.8 committed 2026-06-19 08:21:18 -04:00
1 parent 512b6c3f6a
commit cf98d06cca
3 files changed
+170 -5

No files matched your search

+35
View File
@@ -421,3 +421,38 @@ export function extractMagnetHints(rawValue) {
}
return hints;
}
/**
* Extract every web seed (`ws=`) URL from a magnet, in order, deduped
* case-insensitively. Unlike {@link extractMagnetHints} (which returns only the
* first `ws`), this surfaces the full set so callers can round-trip a magnet
* that carries multiple independent webseeds without silently dropping the
* extras.
*
* @param {string} rawValue
* @returns {string[]} Deduped web seed URLs in magnet order.
*/
export function extractAllWebSeeds(rawValue) {
const { params } = normalizeMagnetInput(rawValue);
const seeds = [];
const seen = new Set();
for (const param of params) {
if (param.lowerKey !== "ws") {
continue;
}
const value =
typeof (param.decoded || param.value) === "string"
? (param.decoded || param.value).trim()
: "";
if (!value) {
continue;
}
const key = value.toLowerCase();
if (seen.has(key)) {
continue;
}
seen.add(key);
seeds.push(value);
}
return seeds;
}
+38 -5
View File
@@ -34,6 +34,42 @@ function normalizeString(value) {
return String(value ?? "").trim();
}
/**
* Normalize a web seed field into an ordered, deduped list of trimmed URLs.
* Accepts an array (multi-webseed forms) or a string that may carry several
* URLs separated by newlines/commas (a single textarea field). Preserving the
* full list lets a video declare multiple independent webseeds (e.g. a CDN
* plus a backup origin) instead of collapsing to a single `ws`.
*
* @param {string|string[]} value
* @returns {string[]}
*/
function normalizeWebSeedList(value) {
const out = [];
const seen = new Set();
const push = (entry) => {
if (typeof entry !== "string") {
return;
}
const trimmed = entry.trim();
if (!trimmed) {
return;
}
const key = trimmed.toLowerCase();
if (seen.has(key)) {
return;
}
seen.add(key);
out.push(trimmed);
};
if (Array.isArray(value)) {
value.forEach(push);
} else if (typeof value === "string") {
value.split(/[\r\n,]+/).forEach(push);
}
return out;
}
function normalizeSha256Hex(value) {
if (value === undefined || value === null) {
return "";
@@ -342,7 +378,7 @@ export function normalizeVideoNotePayload(input) {
const magnet = normalizeString(legacyPayload?.magnet || "");
const thumbnail = normalizeString(legacyPayload?.thumbnail || "");
const description = normalizeString(legacyPayload?.description || "");
const ws = normalizeString(legacyPayload?.ws || "");
const wsValues = normalizeWebSeedList(legacyPayload?.ws);
const xs = normalizeString(legacyPayload?.xs || "");
const storagePointer = normalizeStoragePointer(
legacyPayload?.storagePointer || legacyPayload?.storage || ""
@@ -415,10 +451,7 @@ export function normalizeVideoNotePayload(input) {
}
if (legacyFormData.magnet) {
const wsList = [];
if (ws) {
wsList.push(ws);
}
const wsList = [...wsValues];
if (legacyFormData.url && /^https?:\/\//i.test(legacyFormData.url)) {
wsList.push(legacyFormData.url);
}
+97
View File
@@ -0,0 +1,97 @@
// A video may legitimately declare more than one web seed (e.g. the primary CDN
// plus an independent backup origin) so the WebTorrent swarm can pull pieces
// from either when one is overloaded. Multiple `ws=` params are the standard
// BEP19 carrier and playback already attaches every one of them, so the
// authoring/normalization layer must preserve the full set — never collapse it
// to a single seed.
import test from "node:test";
import assert from "node:assert/strict";
import { extractAllWebSeeds } from "../js/magnetShared.js";
import { normalizeVideoNotePayload } from "../js/services/videoNotePayload.js";
const BTIH = "a".repeat(40);
const MAGNET = `magnet:?xt=urn:btih:${BTIH}`;
const SEED_A = "https://cdn-a.example.com/video.mp4";
const SEED_B = "https://cdn-b.example.com/video.mp4";
test("extractAllWebSeeds returns every ws= in order, deduped", () => {
const magnet = `${MAGNET}&ws=${encodeURIComponent(SEED_A)}&ws=${encodeURIComponent(
SEED_B,
)}&ws=${encodeURIComponent(SEED_A)}`;
assert.deepEqual(extractAllWebSeeds(magnet), [SEED_A, SEED_B]);
});
test("extractAllWebSeeds returns [] when there are no web seeds", () => {
assert.deepEqual(extractAllWebSeeds(MAGNET), []);
assert.deepEqual(extractAllWebSeeds(""), []);
assert.deepEqual(extractAllWebSeeds(null), []);
});
test("an array of web seeds all survive into the published magnet", () => {
const { payload, errors } = normalizeVideoNotePayload({
title: "Multi seed",
magnet: MAGNET,
ws: [SEED_A, SEED_B],
});
assert.deepEqual(errors, []);
const seeds = extractAllWebSeeds(payload.legacyFormData.magnet);
assert.ok(seeds.includes(SEED_A), "first webseed must be carried in the magnet");
assert.ok(seeds.includes(SEED_B), "second webseed must be carried in the magnet");
});
test("a newline/comma-delimited ws field is split into multiple seeds", () => {
const { payload } = normalizeVideoNotePayload({
title: "Multi seed text",
magnet: MAGNET,
ws: `${SEED_A}\n${SEED_B}`,
});
const seeds = extractAllWebSeeds(payload.legacyFormData.magnet);
assert.ok(seeds.includes(SEED_A));
assert.ok(seeds.includes(SEED_B));
});
test("the hosted CDN url is added as a webseed alongside the explicit ones", () => {
const cdn = "https://cdn.example.com/video.mp4";
const { payload } = normalizeVideoNotePayload({
title: "Cdn plus seed",
url: cdn,
magnet: MAGNET,
ws: SEED_A,
});
const seeds = extractAllWebSeeds(payload.legacyFormData.magnet);
assert.ok(seeds.includes(SEED_A), "explicit webseed preserved");
assert.ok(seeds.includes(cdn), "hosted url also attached as a webseed");
});
test("a single webseed string still works (back-compat)", () => {
const { payload } = normalizeVideoNotePayload({
title: "Single seed",
magnet: MAGNET,
ws: SEED_A,
});
const seeds = extractAllWebSeeds(payload.legacyFormData.magnet);
assert.deepEqual(seeds, [SEED_A]);
// The single content `ws` field still reflects the primary seed.
assert.equal(payload.legacyFormData.ws, SEED_A);
});
test("duplicate webseeds are not written twice", () => {
const { payload } = normalizeVideoNotePayload({
title: "Dupe seed",
magnet: MAGNET,
ws: [SEED_A, SEED_A, " " + SEED_A + " "],
});
const seeds = extractAllWebSeeds(payload.legacyFormData.magnet);
assert.equal(
seeds.filter((s) => s === SEED_A).length,
1,
"the same webseed must appear at most once",
);
});