mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-27 16:02:26 +00:00
Add magnet normalization helper and tests
This commit is contained in:
1 parent
88eaff3107
commit
bfee3b7a2a
3 files changed
+417
-149
No files matched your search
@@ -5,6 +5,7 @@ import { nostrClient } from "./nostr.js";
|
||||
import { torrentClient } from "./webtorrent.js";
|
||||
import { isDevMode } from "./config.js";
|
||||
import { isWhitelistEnabled } from "./config.js";
|
||||
import { normalizeAndAugmentMagnet } from "./magnetUtils.js";
|
||||
import {
|
||||
initialWhitelist,
|
||||
initialBlacklist,
|
||||
@@ -21,103 +22,6 @@ function fakeDecrypt(str) {
|
||||
const UNSUPPORTED_BTITH_MESSAGE =
|
||||
"This magnet link is missing a compatible BitTorrent v1 info hash.";
|
||||
|
||||
const DEFAULT_WSS_TRACKERS = [
|
||||
"wss://tracker.openwebtorrent.com",
|
||||
"wss://tracker.btorrent.xyz",
|
||||
"wss://tracker.fastcast.nz",
|
||||
];
|
||||
|
||||
/**
|
||||
* Append optional ws/xs parameters to a magnet URI when valid.
|
||||
*
|
||||
* Always appends the default WSS tracker list, injects any provided hosted
|
||||
* video URL as an `xs=` hint, and de-duplicates repeated query parameters.
|
||||
*/
|
||||
function augmentMagnet(raw, ws, xs, hostedUrl) {
|
||||
const magnet = (raw || "").trim();
|
||||
if (!magnet) {
|
||||
return "";
|
||||
}
|
||||
|
||||
let parsed;
|
||||
try {
|
||||
parsed = new URL(magnet);
|
||||
} catch (err) {
|
||||
return magnet;
|
||||
}
|
||||
|
||||
if (parsed.protocol !== "magnet:") {
|
||||
return magnet;
|
||||
}
|
||||
|
||||
const appendIfMissing = (key, value, { normalize } = {}) => {
|
||||
if (!value) return;
|
||||
const trimmed = typeof value === "string" ? value.trim() : "";
|
||||
if (!trimmed) return;
|
||||
const normalizer =
|
||||
typeof normalize === "function"
|
||||
? normalize
|
||||
: (val) => (typeof val === "string" ? val.trim() : "");
|
||||
const candidate = normalizer(trimmed);
|
||||
if (!candidate) return;
|
||||
const existing = parsed.searchParams
|
||||
.getAll(key)
|
||||
.map((existingValue) => normalizer(existingValue));
|
||||
if (!existing.includes(candidate)) {
|
||||
parsed.searchParams.append(key, trimmed);
|
||||
}
|
||||
};
|
||||
|
||||
const sanitizeHostedUrl = (value) => {
|
||||
const trimmed = typeof value === "string" ? value.trim() : "";
|
||||
if (!trimmed) {
|
||||
return "";
|
||||
}
|
||||
try {
|
||||
const parsedUrl = new URL(trimmed);
|
||||
if (parsedUrl.protocol === "http:" || parsedUrl.protocol === "https:") {
|
||||
return parsedUrl.toString();
|
||||
}
|
||||
} catch (err) {
|
||||
return "";
|
||||
}
|
||||
return "";
|
||||
};
|
||||
|
||||
const normalizedHostedSeed = sanitizeHostedUrl(hostedUrl);
|
||||
|
||||
appendIfMissing("ws", ws);
|
||||
appendIfMissing("xs", xs);
|
||||
appendIfMissing("xs", normalizedHostedSeed);
|
||||
|
||||
DEFAULT_WSS_TRACKERS.forEach((tracker) => {
|
||||
appendIfMissing("tr", tracker, {
|
||||
normalize: (value) => (typeof value === "string" ? value.trim().toLowerCase() : ""),
|
||||
});
|
||||
});
|
||||
|
||||
const dedupeParam = (key, normalizer = (value) => value.trim()) => {
|
||||
const seen = new Set();
|
||||
const values = parsed.searchParams.getAll(key);
|
||||
parsed.searchParams.delete(key);
|
||||
for (const value of values) {
|
||||
const trimmed = typeof value === "string" ? value.trim() : "";
|
||||
if (!trimmed) continue;
|
||||
const normalized = normalizer(trimmed);
|
||||
if (!normalized) continue;
|
||||
if (seen.has(normalized)) continue;
|
||||
seen.add(normalized);
|
||||
parsed.searchParams.append(key, trimmed);
|
||||
}
|
||||
};
|
||||
|
||||
dedupeParam("tr", (value) => value.trim().toLowerCase());
|
||||
dedupeParam("ws");
|
||||
dedupeParam("xs");
|
||||
|
||||
return parsed.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Basic validation for BitTorrent magnet URIs.
|
||||
*
|
||||
@@ -1003,12 +907,21 @@ class bitvidApp {
|
||||
return;
|
||||
}
|
||||
|
||||
formData.magnet = augmentMagnet(
|
||||
formData.magnet,
|
||||
ws,
|
||||
xs,
|
||||
sanitizedHostedUrl
|
||||
);
|
||||
const webSeedCandidates = [];
|
||||
if (ws) {
|
||||
webSeedCandidates.push(ws);
|
||||
}
|
||||
if (sanitizedHostedUrl) {
|
||||
webSeedCandidates.push(sanitizedHostedUrl);
|
||||
}
|
||||
|
||||
const magnetAugmentation = normalizeAndAugmentMagnet(formData.magnet, {
|
||||
webSeed: webSeedCandidates,
|
||||
torrentUrl: xs,
|
||||
logger: (message) => this.log(message),
|
||||
});
|
||||
|
||||
formData.magnet = magnetAugmentation.magnet;
|
||||
|
||||
try {
|
||||
await nostrClient.publishVideo(formData, this.pubkey);
|
||||
@@ -2033,14 +1946,17 @@ class bitvidApp {
|
||||
}
|
||||
}
|
||||
|
||||
async playHttp(url, { fallbackMagnet = "" } = {}) {
|
||||
async playHttp(url, { fallbackMagnet = "", fallbackRawMagnet = "" } = {}) {
|
||||
if (!this.modalVideo) {
|
||||
throw new Error("No modal video element available for playback.");
|
||||
}
|
||||
|
||||
const trimmedMagnet = isValidMagnetUri(fallbackMagnet)
|
||||
const normalizedFallback = isValidMagnetUri(fallbackMagnet)
|
||||
? fallbackMagnet.trim()
|
||||
: "";
|
||||
const rawFallback = isValidMagnetUri(fallbackRawMagnet)
|
||||
? fallbackRawMagnet.trim()
|
||||
: "";
|
||||
|
||||
this.resetTorrentStats();
|
||||
|
||||
@@ -2058,7 +1974,7 @@ class bitvidApp {
|
||||
once: true,
|
||||
});
|
||||
|
||||
if (trimmedMagnet) {
|
||||
if (normalizedFallback) {
|
||||
this.modalVideo.addEventListener(
|
||||
"error",
|
||||
async () => {
|
||||
@@ -2067,7 +1983,9 @@ class bitvidApp {
|
||||
this.modalStatus.textContent = "Switching to WebTorrent...";
|
||||
}
|
||||
try {
|
||||
await this.playViaWebTorrent(trimmedMagnet);
|
||||
await this.playViaWebTorrent(normalizedFallback, {
|
||||
fallbackMagnet: rawFallback,
|
||||
});
|
||||
this.autoplayModalVideo();
|
||||
} catch (torrentErr) {
|
||||
console.error("WebTorrent fallback failed:", torrentErr);
|
||||
@@ -2082,45 +2000,75 @@ class bitvidApp {
|
||||
this.modalVideo.load();
|
||||
}
|
||||
|
||||
async playViaWebTorrent(magnet) {
|
||||
const trimmedMagnet =
|
||||
typeof magnet === "string" ? magnet.trim() : "";
|
||||
if (!trimmedMagnet) {
|
||||
throw new Error("No magnet URI provided for torrent playback.");
|
||||
}
|
||||
if (!isValidMagnetUri(trimmedMagnet)) {
|
||||
if (this.modalStatus) {
|
||||
this.modalStatus.textContent = UNSUPPORTED_BTITH_MESSAGE;
|
||||
async playViaWebTorrent(magnet, { fallbackMagnet = "" } = {}) {
|
||||
const attemptStream = async (candidate) => {
|
||||
const trimmedCandidate =
|
||||
typeof candidate === "string" ? candidate.trim() : "";
|
||||
if (!trimmedCandidate) {
|
||||
throw new Error("No magnet URI provided for torrent playback.");
|
||||
}
|
||||
if (!isValidMagnetUri(trimmedCandidate)) {
|
||||
if (this.modalStatus) {
|
||||
this.modalStatus.textContent = UNSUPPORTED_BTITH_MESSAGE;
|
||||
}
|
||||
throw new Error(UNSUPPORTED_BTITH_MESSAGE);
|
||||
}
|
||||
if (!this.modalVideo) {
|
||||
throw new Error(
|
||||
"No modal video element available for torrent playback."
|
||||
);
|
||||
}
|
||||
throw new Error(UNSUPPORTED_BTITH_MESSAGE);
|
||||
}
|
||||
if (!this.modalVideo) {
|
||||
throw new Error("No modal video element available for torrent playback.");
|
||||
}
|
||||
|
||||
let cacheBustedMagnet = trimmedMagnet;
|
||||
let cacheBustedMagnet = trimmedCandidate;
|
||||
try {
|
||||
const parsed = new URL(trimmedCandidate);
|
||||
parsed.searchParams.set("ts", Date.now().toString());
|
||||
cacheBustedMagnet = parsed.toString();
|
||||
} catch (err) {
|
||||
const separator = trimmedCandidate.includes("?") ? "&" : "?";
|
||||
cacheBustedMagnet = `${trimmedCandidate}${separator}ts=${Date.now()}`;
|
||||
}
|
||||
|
||||
await torrentClient.cleanup();
|
||||
this.resetTorrentStats();
|
||||
|
||||
if (this.modalStatus) {
|
||||
this.modalStatus.textContent = "Streaming via WebTorrent";
|
||||
}
|
||||
|
||||
const torrentInstance = await torrentClient.streamVideo(
|
||||
cacheBustedMagnet,
|
||||
this.modalVideo
|
||||
);
|
||||
this.startTorrentStatusMirrors(torrentInstance);
|
||||
return torrentInstance;
|
||||
};
|
||||
|
||||
const primaryTrimmed =
|
||||
typeof magnet === "string" ? magnet.trim() : "";
|
||||
const fallbackTrimmed =
|
||||
typeof fallbackMagnet === "string" ? fallbackMagnet.trim() : "";
|
||||
const hasFallback =
|
||||
!!fallbackTrimmed && fallbackTrimmed !== primaryTrimmed;
|
||||
|
||||
try {
|
||||
const parsed = new URL(trimmedMagnet);
|
||||
parsed.searchParams.set("ts", Date.now().toString());
|
||||
cacheBustedMagnet = parsed.toString();
|
||||
} catch (err) {
|
||||
const separator = trimmedMagnet.includes("?") ? "&" : "?";
|
||||
cacheBustedMagnet = `${trimmedMagnet}${separator}ts=${Date.now()}`;
|
||||
return await attemptStream(primaryTrimmed);
|
||||
} catch (primaryError) {
|
||||
if (!hasFallback) {
|
||||
throw primaryError;
|
||||
}
|
||||
this.log(
|
||||
`[playViaWebTorrent] Normalized magnet failed: ${primaryError.message}`
|
||||
);
|
||||
this.log(
|
||||
"[playViaWebTorrent] Primary magnet failed, retrying original string."
|
||||
);
|
||||
try {
|
||||
return await attemptStream(fallbackTrimmed);
|
||||
} catch (fallbackError) {
|
||||
throw fallbackError;
|
||||
}
|
||||
}
|
||||
|
||||
await torrentClient.cleanup();
|
||||
this.resetTorrentStats();
|
||||
|
||||
if (this.modalStatus) {
|
||||
this.modalStatus.textContent = "Streaming via WebTorrent";
|
||||
}
|
||||
|
||||
const torrentInstance = await torrentClient.streamVideo(
|
||||
cacheBustedMagnet,
|
||||
this.modalVideo
|
||||
);
|
||||
this.startTorrentStatusMirrors(torrentInstance);
|
||||
return torrentInstance;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2137,14 +2085,24 @@ class bitvidApp {
|
||||
const trimmedMagnet = typeof magnet === "string" ? magnet.trim() : "";
|
||||
const magnetSupported = isValidMagnetUri(trimmedMagnet);
|
||||
const sanitizedMagnet = magnetSupported ? trimmedMagnet : "";
|
||||
const augmentedMagnet = sanitizedMagnet
|
||||
? augmentMagnet(sanitizedMagnet, undefined, undefined, sanitizedUrl)
|
||||
const magnetNormalization = sanitizedMagnet
|
||||
? normalizeAndAugmentMagnet(sanitizedMagnet, {
|
||||
webSeed: sanitizedUrl ? [sanitizedUrl] : [],
|
||||
logger: (message) => this.log(message),
|
||||
})
|
||||
: { magnet: "", didChange: false };
|
||||
const magnetForPlayback = sanitizedMagnet
|
||||
? magnetNormalization.magnet || sanitizedMagnet
|
||||
: "";
|
||||
const fallbackMagnet = magnetNormalization.didChange
|
||||
? sanitizedMagnet
|
||||
: "";
|
||||
const magnetForPlayback = augmentedMagnet || sanitizedMagnet;
|
||||
const magnetProvided = trimmedMagnet.length > 0;
|
||||
|
||||
if (this.currentVideo) {
|
||||
this.currentVideo.magnet = magnetForPlayback;
|
||||
this.currentVideo.normalizedMagnet = magnetForPlayback;
|
||||
this.currentVideo.normalizedMagnetFallback = fallbackMagnet;
|
||||
if (!magnetForPlayback) {
|
||||
this.currentVideo.torrentSupported = false;
|
||||
}
|
||||
@@ -2174,9 +2132,8 @@ class bitvidApp {
|
||||
this.resetTorrentStats();
|
||||
|
||||
if (magnetForPlayback) {
|
||||
const magnetChanged = magnetForPlayback !== sanitizedMagnet;
|
||||
const label = magnetChanged
|
||||
? "[playVideoWithFallback] Using augmented magnet URI:"
|
||||
const label = magnetNormalization.didChange
|
||||
? "[playVideoWithFallback] Using normalized magnet URI:"
|
||||
: "[playVideoWithFallback] Using magnet URI:";
|
||||
this.log(`${label} ${magnetForPlayback}`);
|
||||
}
|
||||
@@ -2189,9 +2146,12 @@ class bitvidApp {
|
||||
if (urlPlayable) {
|
||||
await this.playHttp(sanitizedUrl, {
|
||||
fallbackMagnet: magnetForPlayback,
|
||||
fallbackRawMagnet: fallbackMagnet,
|
||||
});
|
||||
} else if (magnetForPlayback) {
|
||||
await this.playViaWebTorrent(magnetForPlayback);
|
||||
await this.playViaWebTorrent(magnetForPlayback, {
|
||||
fallbackMagnet,
|
||||
});
|
||||
} else {
|
||||
const message = magnetProvided && !magnetSupported
|
||||
? UNSUPPORTED_BTITH_MESSAGE
|
||||
|
||||
@@ -0,0 +1,213 @@
|
||||
// js/magnetUtils.js
|
||||
|
||||
export const DEFAULT_WSS_TRACKERS = [
|
||||
"wss://tracker.openwebtorrent.com",
|
||||
"wss://tracker.btorrent.xyz",
|
||||
"wss://tracker.fastcast.nz",
|
||||
];
|
||||
|
||||
const HEX_INFO_HASH = /^[0-9a-f]{40}$/i;
|
||||
const BTIH_PREFIX = "urn:btih:";
|
||||
const ENCODED_BTih_PATTERN = /xt=urn%3Abtih%3A([0-9a-z]+)/gi;
|
||||
|
||||
function normalizeForComparison(value) {
|
||||
if (typeof value !== "string") {
|
||||
return "";
|
||||
}
|
||||
const trimmed = value.trim();
|
||||
if (!trimmed) {
|
||||
return "";
|
||||
}
|
||||
try {
|
||||
const parsed = new URL(trimmed);
|
||||
const pathname = parsed.pathname.replace(/\/?$/, "");
|
||||
const normalizedPath = pathname ? pathname : "";
|
||||
return (
|
||||
`${parsed.protocol}//${parsed.host}${normalizedPath}${parsed.search}${parsed.hash}`
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
);
|
||||
} catch (err) {
|
||||
return trimmed.replace(/\/?$/, "").toLowerCase();
|
||||
}
|
||||
}
|
||||
|
||||
function sanitizeHttpUrl(candidate) {
|
||||
if (typeof candidate !== "string") {
|
||||
return "";
|
||||
}
|
||||
const trimmed = candidate.trim();
|
||||
if (!trimmed) {
|
||||
return "";
|
||||
}
|
||||
try {
|
||||
const parsed = new URL(trimmed);
|
||||
if (parsed.protocol === "http:" || parsed.protocol === "https:") {
|
||||
return parsed.toString();
|
||||
}
|
||||
} catch (err) {
|
||||
return "";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
export function normalizeAndAugmentMagnet(
|
||||
rawValue,
|
||||
{
|
||||
webSeed,
|
||||
torrentUrl,
|
||||
xs,
|
||||
extraTrackers = [],
|
||||
logger,
|
||||
appProtocol,
|
||||
} = {}
|
||||
) {
|
||||
const log = typeof logger === "function" ? logger : () => {};
|
||||
const initial = typeof rawValue === "string" ? rawValue.trim() : "";
|
||||
if (!initial) {
|
||||
return { magnet: "", didChange: false };
|
||||
}
|
||||
|
||||
let working = initial;
|
||||
let didMutate = false;
|
||||
const bareHashMatch = HEX_INFO_HASH.test(working);
|
||||
if (bareHashMatch) {
|
||||
working = `magnet:?xt=${BTIH_PREFIX}${working.toLowerCase()}`;
|
||||
didMutate = true;
|
||||
}
|
||||
|
||||
const decodedXt = working.replace(ENCODED_BTih_PATTERN, (_, hash) => {
|
||||
didMutate = true;
|
||||
return `xt=${BTIH_PREFIX}${hash}`;
|
||||
});
|
||||
working = decodedXt;
|
||||
|
||||
let parsed;
|
||||
try {
|
||||
parsed = new URL(working);
|
||||
} catch (err) {
|
||||
return {
|
||||
magnet: working,
|
||||
didChange: didMutate || working !== initial,
|
||||
};
|
||||
}
|
||||
|
||||
if (parsed.protocol !== "magnet:") {
|
||||
return {
|
||||
magnet: working,
|
||||
didChange: didMutate || working !== initial,
|
||||
};
|
||||
}
|
||||
|
||||
const torrentHint = typeof torrentUrl === "string" && torrentUrl.trim()
|
||||
? torrentUrl
|
||||
: typeof xs === "string"
|
||||
? xs.trim()
|
||||
: "";
|
||||
|
||||
const safeTorrentHint = sanitizeHttpUrl(torrentHint);
|
||||
const seedInputs = Array.isArray(webSeed)
|
||||
? webSeed
|
||||
: typeof webSeed === "string"
|
||||
? [webSeed]
|
||||
: [];
|
||||
|
||||
const resolvedProtocol = typeof appProtocol === "string" && appProtocol
|
||||
? appProtocol.toLowerCase()
|
||||
: typeof window !== "undefined" && window.location && window.location.protocol
|
||||
? window.location.protocol.toLowerCase()
|
||||
: "https:";
|
||||
|
||||
const existingTrackers = new Set(
|
||||
parsed.searchParams
|
||||
.getAll("tr")
|
||||
.map((value) => normalizeForComparison(value))
|
||||
.filter(Boolean)
|
||||
);
|
||||
|
||||
const trackerCandidates = [
|
||||
...DEFAULT_WSS_TRACKERS,
|
||||
...extraTrackers,
|
||||
];
|
||||
|
||||
for (const tracker of trackerCandidates) {
|
||||
if (typeof tracker !== "string") {
|
||||
continue;
|
||||
}
|
||||
const trimmedTracker = tracker.trim();
|
||||
if (!trimmedTracker) {
|
||||
continue;
|
||||
}
|
||||
if (!/^wss:\/\//i.test(trimmedTracker)) {
|
||||
continue;
|
||||
}
|
||||
const normalizedTracker = normalizeForComparison(trimmedTracker);
|
||||
if (!normalizedTracker || existingTrackers.has(normalizedTracker)) {
|
||||
continue;
|
||||
}
|
||||
parsed.searchParams.append("tr", trimmedTracker);
|
||||
existingTrackers.add(normalizedTracker);
|
||||
didMutate = true;
|
||||
}
|
||||
|
||||
if (safeTorrentHint) {
|
||||
const existingXs = new Set(
|
||||
parsed.searchParams
|
||||
.getAll("xs")
|
||||
.map((value) => normalizeForComparison(value))
|
||||
.filter(Boolean)
|
||||
);
|
||||
const normalizedXs = normalizeForComparison(safeTorrentHint);
|
||||
if (normalizedXs && !existingXs.has(normalizedXs)) {
|
||||
parsed.searchParams.append("xs", safeTorrentHint);
|
||||
didMutate = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (seedInputs.length) {
|
||||
const existingWs = new Set(
|
||||
parsed.searchParams
|
||||
.getAll("ws")
|
||||
.map((value) => normalizeForComparison(value))
|
||||
.filter(Boolean)
|
||||
);
|
||||
for (const seedInput of seedInputs) {
|
||||
if (typeof seedInput !== "string") {
|
||||
continue;
|
||||
}
|
||||
const trimmedSeed = seedInput.trim();
|
||||
if (!trimmedSeed) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
const parsedSeed = new URL(trimmedSeed);
|
||||
const seedProtocol = parsedSeed.protocol;
|
||||
const allowHttpSeed = resolvedProtocol === "http:";
|
||||
if (
|
||||
seedProtocol === "https:" ||
|
||||
(seedProtocol === "http:" && allowHttpSeed)
|
||||
) {
|
||||
const seedValue = parsedSeed.toString();
|
||||
const normalizedSeed = normalizeForComparison(seedValue);
|
||||
if (normalizedSeed && !existingWs.has(normalizedSeed)) {
|
||||
parsed.searchParams.append("ws", seedValue);
|
||||
existingWs.add(normalizedSeed);
|
||||
didMutate = true;
|
||||
}
|
||||
} else if (seedProtocol === "http:") {
|
||||
log(
|
||||
`[normalizeAndAugmentMagnet] Skipping insecure web seed: ${trimmedSeed}`
|
||||
);
|
||||
}
|
||||
} catch (err) {
|
||||
// Ignore invalid web seed values silently.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const finalMagnet = parsed.toString();
|
||||
return {
|
||||
magnet: finalMagnet,
|
||||
didChange: didMutate || finalMagnet !== initial,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
import assert from "node:assert/strict";
|
||||
import {
|
||||
DEFAULT_WSS_TRACKERS,
|
||||
normalizeAndAugmentMagnet,
|
||||
} from "../js/magnetUtils.js";
|
||||
|
||||
function getParamValues(magnet, key) {
|
||||
const parsed = new URL(magnet);
|
||||
return parsed.searchParams.getAll(key);
|
||||
}
|
||||
|
||||
(function testBareInfoHashNormalization() {
|
||||
const infoHash = "0123456789abcdef0123456789abcdef01234567";
|
||||
const result = normalizeAndAugmentMagnet(infoHash);
|
||||
assert.ok(result.didChange, "Bare info hash should mark didChange true");
|
||||
const xtValues = getParamValues(result.magnet, "xt");
|
||||
assert.deepEqual(xtValues, [`urn:btih:${infoHash}`]);
|
||||
const trackerValues = getParamValues(result.magnet, "tr");
|
||||
for (const tracker of DEFAULT_WSS_TRACKERS) {
|
||||
assert.ok(
|
||||
trackerValues.includes(tracker),
|
||||
`Expected tracker ${tracker} to be appended`
|
||||
);
|
||||
}
|
||||
})();
|
||||
|
||||
(function testEncodedXtDecoding() {
|
||||
const infoHash = "abcdefabcdefabcdefabcdefabcdefabcdefabcd";
|
||||
const encodedMagnet = `magnet:?xt=urn%3Abtih%3A${infoHash}&dn=Example`;
|
||||
const result = normalizeAndAugmentMagnet(encodedMagnet);
|
||||
const xtValues = getParamValues(result.magnet, "xt");
|
||||
assert.deepEqual(xtValues, [`urn:btih:${infoHash}`]);
|
||||
})();
|
||||
|
||||
(function testDuplicateTrackerFiltering() {
|
||||
const infoHash = "fedcba9876543210fedcba9876543210fedcba98";
|
||||
const baseMagnet =
|
||||
`magnet:?xt=urn:btih:${infoHash}` +
|
||||
"&tr=wss://tracker.openwebtorrent.com" +
|
||||
"&tr=WsS://tracker.openwebtorrent.com/" +
|
||||
"&tr=https://not-allowed.example";
|
||||
const result = normalizeAndAugmentMagnet(baseMagnet, {
|
||||
extraTrackers: [
|
||||
"wss://tracker.fastcast.nz",
|
||||
"http://tracker.invalid",
|
||||
"wss://tracker.openwebtorrent.com",
|
||||
],
|
||||
});
|
||||
const trackers = getParamValues(result.magnet, "tr");
|
||||
const openwebCount = trackers.filter(
|
||||
(value) => value === "wss://tracker.openwebtorrent.com"
|
||||
).length;
|
||||
assert.equal(openwebCount, 1, "Duplicate WSS tracker should be collapsed");
|
||||
assert.ok(
|
||||
trackers.includes("wss://tracker.fastcast.nz"),
|
||||
"Expected extra WSS tracker to be preserved"
|
||||
);
|
||||
assert.ok(
|
||||
!trackers.includes("http://tracker.invalid"),
|
||||
"Non-WSS tracker candidates should be ignored"
|
||||
);
|
||||
})();
|
||||
|
||||
(function testWebSeedHandlingWithLogging() {
|
||||
const infoHash = "0123456789abcdef0123456789abcdef01234567";
|
||||
const messages = [];
|
||||
const result = normalizeAndAugmentMagnet(`magnet:?xt=urn:btih:${infoHash}`, {
|
||||
webSeed: [
|
||||
"https://cdn.example.com/video.mp4",
|
||||
"http://cdn.example.com/legacy.mp4",
|
||||
"https://cdn.example.com/video.mp4",
|
||||
],
|
||||
logger: (msg) => messages.push(msg),
|
||||
appProtocol: "https:",
|
||||
});
|
||||
const seeds = getParamValues(result.magnet, "ws");
|
||||
assert.deepEqual(seeds, ["https://cdn.example.com/video.mp4"]);
|
||||
assert.equal(messages.length, 1, "HTTP seed should have been skipped with a log");
|
||||
assert.ok(
|
||||
messages[0].includes("Skipping insecure web seed"),
|
||||
"Expected skip message for HTTP seed"
|
||||
);
|
||||
})();
|
||||
|
||||
(function testHttpWebSeedAllowedOnHttpOrigin() {
|
||||
const infoHash = "abcdefabcdefabcdefabcdefabcdefabcdefabcd";
|
||||
const result = normalizeAndAugmentMagnet(`magnet:?xt=urn:btih:${infoHash}`, {
|
||||
webSeed: "http://cdn.example.com/video.mp4",
|
||||
appProtocol: "http:",
|
||||
});
|
||||
const seeds = getParamValues(result.magnet, "ws");
|
||||
assert.deepEqual(seeds, ["http://cdn.example.com/video.mp4"]);
|
||||
})();
|
||||
|
||||
console.log("magnet-utils tests passed");
|
||||
Reference in new issue
Block a user