mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-27 07:52:24 +00:00
feat(webseed): upload form accepts multiple web seeds (slice 2/3)
Let uploaders declare a backup origin at creation time so the swarm can keep streaming when the primary CDN is overloaded. - upload-modal.html: the single "Web Seed (ws)" input becomes a "Web seeds (one per line)" textarea with help text explaining the backup-origin use case and the byte-identical-file requirement. - UploadModal: in upload mode the R2/CDN url stays the primary webseed and any textarea entries follow it (passed as [primaryUrl, textarea]); external mode already flowed the field through. All parsing stays in the payload layer. - videoNotePayload.normalizeWebSeedList now splits every entry (array OR string) on newlines/commas, so the upload-modal's [url, "a\nb"] shape and a plain multi-line string both expand + dedupe correctly. Mutation-verified (array entries no longer split fails the upload-shape test); build + full unit suite green. Lints relevant to the change (inline-styles, tokens, innerHTML) pass. Note: npm run lint also reports pre-existing file-size violations in unrelated files (SignerManager, watchHistory, playbackService, webtorrent, etc.) that grew in earlier commits — none are touched here. Flagging separately. Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
1 parent
cf98d06cca
commit
ce7ef9ef2d
4 files changed
+44
-13
No files matched your search
@@ -376,8 +376,9 @@
|
||||
<!-- Additional Technical Sections (Web Seeds, etc) -->
|
||||
<div class="grid gap-3 md:grid-cols-2">
|
||||
<div class="space-y-2">
|
||||
<label class="block text-xs font-medium text-muted-strong">Web Seed (ws)</label>
|
||||
<input id="input-ws" type="url" class="input text-sm" placeholder="https://..." />
|
||||
<label class="block text-xs font-medium text-muted-strong">Web seeds (one per line)</label>
|
||||
<textarea id="input-ws" rows="2" class="textarea text-sm" placeholder="https://cdn.example.com/video.mp4 https://backup.example.com/video.mp4"></textarea>
|
||||
<p class="text-xs text-muted">Add a backup origin so the swarm can keep streaming if the primary is overloaded. Each must serve the exact same file.</p>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<label class="block text-xs font-medium text-muted-strong">Torrent File (xs)</label>
|
||||
|
||||
@@ -47,25 +47,30 @@ function normalizeString(value) {
|
||||
function normalizeWebSeedList(value) {
|
||||
const out = [];
|
||||
const seen = new Set();
|
||||
// Each entry may itself carry several URLs separated by newlines/commas (a
|
||||
// textarea field), so split every entry regardless of whether the caller
|
||||
// passed an array or a single string.
|
||||
const push = (entry) => {
|
||||
if (typeof entry !== "string") {
|
||||
return;
|
||||
}
|
||||
const trimmed = entry.trim();
|
||||
if (!trimmed) {
|
||||
return;
|
||||
for (const piece of entry.split(/[\r\n,]+/)) {
|
||||
const trimmed = piece.trim();
|
||||
if (!trimmed) {
|
||||
continue;
|
||||
}
|
||||
const key = trimmed.toLowerCase();
|
||||
if (seen.has(key)) {
|
||||
continue;
|
||||
}
|
||||
seen.add(key);
|
||||
out.push(trimmed);
|
||||
}
|
||||
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);
|
||||
push(value);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
@@ -1073,7 +1073,13 @@ export class UploadModal {
|
||||
metadata.storagePointer =
|
||||
this.videoUploadState.storagePointer ||
|
||||
deriveStoragePointerFromUrl(metadata.url, providerLabel);
|
||||
metadata.ws = this.videoUploadState.url; // WebSeed is same as R2 URL
|
||||
// The R2/CDN url is the primary webseed; any additional backup
|
||||
// webseeds the uploader entered (one per line) follow it.
|
||||
// normalizeVideoNotePayload splits + dedupes this list.
|
||||
metadata.ws = [
|
||||
this.videoUploadState.url,
|
||||
this.inputs.ws?.value || "",
|
||||
];
|
||||
metadata.xs = this.torrentState.url || "";
|
||||
|
||||
// NIP-71 IMETA for the main video
|
||||
|
||||
@@ -81,6 +81,25 @@ test("a single webseed string still works (back-compat)", () => {
|
||||
assert.equal(payload.legacyFormData.ws, SEED_A);
|
||||
});
|
||||
|
||||
test("upload-modal shape ([primaryUrl, textarea]) yields primary-first, all split", () => {
|
||||
// This mirrors exactly what UploadModal hands over in upload mode: the CDN
|
||||
// url as the primary entry, followed by the raw multi-line textarea value.
|
||||
const cdn = "https://cdn.example.com/video.mp4";
|
||||
const { payload } = normalizeVideoNotePayload({
|
||||
title: "Upload shape",
|
||||
url: cdn,
|
||||
magnet: MAGNET,
|
||||
ws: [cdn, `${SEED_A}\n${SEED_B}`],
|
||||
});
|
||||
|
||||
const seeds = extractAllWebSeeds(payload.legacyFormData.magnet);
|
||||
assert.ok(seeds.includes(cdn), "CDN url carried");
|
||||
assert.ok(seeds.includes(SEED_A), "first backup webseed carried");
|
||||
assert.ok(seeds.includes(SEED_B), "second backup webseed carried");
|
||||
// CDN stays the primary (first) seed.
|
||||
assert.equal(payload.legacyFormData.ws, cdn);
|
||||
});
|
||||
|
||||
test("duplicate webseeds are not written twice", () => {
|
||||
const { payload } = normalizeVideoNotePayload({
|
||||
title: "Dupe seed",
|
||||
|
||||
Reference in new issue
Block a user