mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-27 07:52:24 +00:00
Handle WebTorrent fallback without service worker
This commit is contained in:
1 parent
e23a2c462d
commit
78ce75ef62
4 files changed
+198
-18
No files matched your search
@@ -81,6 +81,7 @@ import { pointerArrayToKey } from "./utils/pointer.js";
|
||||
import { resolveVideoPointer } from "./utils/videoPointer.js";
|
||||
import { isValidMagnetUri } from "./utils/magnetValidators.js";
|
||||
import { dedupeToNewestByRoot } from "./utils/videoDeduper.js";
|
||||
import { buildServiceWorkerFallbackStatus } from "./utils/serviceWorkerFallbackMessages.js";
|
||||
import {
|
||||
getVideoRootIdentifier,
|
||||
applyRootTimestampToVideosMap,
|
||||
@@ -5635,6 +5636,24 @@ class Application {
|
||||
this.modalVideo,
|
||||
{ urlList: sanitizedUrlList }
|
||||
);
|
||||
|
||||
if (torrentClient.isServiceWorkerUnavailable()) {
|
||||
const swError = torrentClient.getServiceWorkerInitError();
|
||||
const statusMessage = buildServiceWorkerFallbackStatus(swError);
|
||||
this.log(
|
||||
"[playViaWebTorrent] Service worker unavailable; streaming directly via WebTorrent.",
|
||||
swError
|
||||
);
|
||||
if (swError) {
|
||||
userLogger.warn(
|
||||
"[playViaWebTorrent] Service worker unavailable; direct streaming engaged.",
|
||||
swError
|
||||
);
|
||||
}
|
||||
if (this.videoModal) {
|
||||
this.videoModal.updateStatus(statusMessage);
|
||||
}
|
||||
}
|
||||
if (torrentInstance && torrentInstance.ready) {
|
||||
// Some browsers delay `playing` events for MediaSource-backed torrents.
|
||||
// Clearing the poster here prevents the historic "GIF stuck over the
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
// js/utils/serviceWorkerFallbackMessages.js
|
||||
|
||||
const BASE_STATUS_MESSAGE = "Streaming via WebTorrent";
|
||||
|
||||
function normalizeMessage(error) {
|
||||
if (!error) {
|
||||
return "";
|
||||
}
|
||||
if (error instanceof Error && typeof error.message === "string") {
|
||||
return error.message;
|
||||
}
|
||||
if (typeof error === "string") {
|
||||
return error;
|
||||
}
|
||||
try {
|
||||
return String(error);
|
||||
} catch (_err) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
export function buildServiceWorkerFallbackStatus(error) {
|
||||
const rawMessage = normalizeMessage(error);
|
||||
const normalized = rawMessage.toLowerCase();
|
||||
|
||||
if (!normalized) {
|
||||
return `${BASE_STATUS_MESSAGE} (service worker unavailable)`;
|
||||
}
|
||||
|
||||
if (normalized.includes("https or localhost required")) {
|
||||
return `${BASE_STATUS_MESSAGE} (serve over HTTPS to enable service worker)`;
|
||||
}
|
||||
|
||||
if (normalized.includes("not supported") || normalized.includes("disabled")) {
|
||||
return `${BASE_STATUS_MESSAGE} (browser disabled service workers)`;
|
||||
}
|
||||
|
||||
if (normalized.includes("brave shield")) {
|
||||
return `${BASE_STATUS_MESSAGE} (Brave Shields blocked the service worker)`;
|
||||
}
|
||||
|
||||
if (
|
||||
normalized.includes("failed to register") ||
|
||||
normalized.includes("failed to fetch") ||
|
||||
normalized.includes("blocked") ||
|
||||
normalized.includes("net::err_blocked_by_client")
|
||||
) {
|
||||
return `${BASE_STATUS_MESSAGE} (service worker blocked by browser or extension)`;
|
||||
}
|
||||
|
||||
if (normalized.includes("controller claim timeout")) {
|
||||
return `${BASE_STATUS_MESSAGE} (waiting for service worker control)`;
|
||||
}
|
||||
|
||||
return `${BASE_STATUS_MESSAGE} (service worker unavailable)`;
|
||||
}
|
||||
|
||||
export default buildServiceWorkerFallbackStatus;
|
||||
+64
-18
@@ -125,7 +125,9 @@ function toError(err) {
|
||||
}
|
||||
|
||||
export class TorrentClient {
|
||||
constructor() {
|
||||
constructor({ webTorrentClass } = {}) {
|
||||
this.WebTorrentClass =
|
||||
typeof webTorrentClass === "function" ? webTorrentClass : WebTorrent;
|
||||
// Reusable objects and flags
|
||||
this.client = null;
|
||||
this.currentTorrent = null;
|
||||
@@ -135,13 +137,16 @@ export class TorrentClient {
|
||||
this.swRegistration = null;
|
||||
this.serverCreated = false; // Indicates if we've called createServer on this.client
|
||||
|
||||
this.serviceWorkerDisabled = false;
|
||||
this.serviceWorkerError = null;
|
||||
|
||||
// Timeout for SW operations
|
||||
this.TIMEOUT_DURATION = 60000;
|
||||
}
|
||||
|
||||
ensureClientForProbe() {
|
||||
if (!this.probeClient) {
|
||||
this.probeClient = new WebTorrent();
|
||||
this.probeClient = new this.WebTorrentClass();
|
||||
}
|
||||
return this.probeClient;
|
||||
}
|
||||
@@ -288,8 +293,16 @@ export class TorrentClient {
|
||||
});
|
||||
}
|
||||
|
||||
log(msg) {
|
||||
devLogger.log(msg);
|
||||
log(...args) {
|
||||
devLogger.log(...args);
|
||||
}
|
||||
|
||||
isServiceWorkerUnavailable() {
|
||||
return this.serviceWorkerDisabled;
|
||||
}
|
||||
|
||||
getServiceWorkerInitError() {
|
||||
return this.serviceWorkerError || null;
|
||||
}
|
||||
|
||||
async isBrave() {
|
||||
@@ -307,21 +320,52 @@ export class TorrentClient {
|
||||
* Called once from streamVideo.
|
||||
*/
|
||||
async init() {
|
||||
// 1) If the client doesn't exist, create it
|
||||
if (!this.client) {
|
||||
this.client = new WebTorrent();
|
||||
this.client = new this.WebTorrentClass();
|
||||
}
|
||||
|
||||
// 2) If we haven’t registered the service worker yet, do it now
|
||||
if (!this.swRegistration) {
|
||||
this.swRegistration = await this.setupServiceWorker();
|
||||
} else {
|
||||
// Even with an existing registration we still wait for control so that a
|
||||
// transient controller drop (e.g. Chrome devtools unregister/reload) does
|
||||
// not resurrect the grey-screen regression mentioned in
|
||||
// waitForActiveController().
|
||||
this.requestClientsClaim(this.swRegistration);
|
||||
await this.waitForActiveController(this.swRegistration);
|
||||
if (this.serviceWorkerDisabled) {
|
||||
return {
|
||||
serviceWorkerReady: false,
|
||||
registration: null,
|
||||
error: this.serviceWorkerError,
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
if (!this.swRegistration) {
|
||||
this.swRegistration = await this.setupServiceWorker();
|
||||
} else {
|
||||
// Even with an existing registration we still wait for control so that a
|
||||
// transient controller drop (e.g. Chrome devtools unregister/reload) does
|
||||
// not resurrect the grey-screen regression mentioned in
|
||||
// waitForActiveController().
|
||||
this.requestClientsClaim(this.swRegistration);
|
||||
await this.waitForActiveController(this.swRegistration);
|
||||
}
|
||||
|
||||
return {
|
||||
serviceWorkerReady: !!this.swRegistration,
|
||||
registration: this.swRegistration,
|
||||
};
|
||||
} catch (error) {
|
||||
const normalizedError = toError(error);
|
||||
this.log(
|
||||
"[WebTorrent] Service worker setup failed; continuing without it:",
|
||||
normalizedError
|
||||
);
|
||||
userLogger.warn(
|
||||
"[WebTorrent] Service worker unavailable; falling back to direct streaming.",
|
||||
normalizedError
|
||||
);
|
||||
this.serviceWorkerDisabled = true;
|
||||
this.serviceWorkerError = normalizedError;
|
||||
this.swRegistration = null;
|
||||
return {
|
||||
serviceWorkerReady: false,
|
||||
registration: null,
|
||||
error: normalizedError,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -777,10 +821,12 @@ export class TorrentClient {
|
||||
async streamVideo(magnetURI, videoElement, opts = {}) {
|
||||
try {
|
||||
// 1) Make sure we have a WebTorrent client and a valid SW registration.
|
||||
await this.init();
|
||||
const initResult = await this.init();
|
||||
const serviceWorkerReady =
|
||||
!!(initResult?.serviceWorkerReady && this.swRegistration);
|
||||
|
||||
// 2) Create the server once if not already created.
|
||||
if (!this.serverCreated) {
|
||||
if (serviceWorkerReady && !this.serverCreated) {
|
||||
this.client.createServer({
|
||||
controller: this.swRegistration,
|
||||
pathPrefix: location.origin + "/webtorrent",
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
|
||||
import {
|
||||
buildServiceWorkerFallbackStatus,
|
||||
} from "../../js/utils/serviceWorkerFallbackMessages.js";
|
||||
|
||||
const BASE = "Streaming via WebTorrent";
|
||||
|
||||
test("returns generic message when error missing", () => {
|
||||
assert.strictEqual(
|
||||
buildServiceWorkerFallbackStatus(null),
|
||||
`${BASE} (service worker unavailable)`
|
||||
);
|
||||
});
|
||||
|
||||
test("identifies HTTPS requirement errors", () => {
|
||||
const error = new Error("HTTPS or localhost required");
|
||||
assert.strictEqual(
|
||||
buildServiceWorkerFallbackStatus(error),
|
||||
`${BASE} (serve over HTTPS to enable service worker)`
|
||||
);
|
||||
});
|
||||
|
||||
test("identifies disabled service worker errors", () => {
|
||||
const error = new Error("Service Worker not supported or disabled");
|
||||
assert.strictEqual(
|
||||
buildServiceWorkerFallbackStatus(error),
|
||||
`${BASE} (browser disabled service workers)`
|
||||
);
|
||||
});
|
||||
|
||||
test("identifies Brave specific guidance", () => {
|
||||
const error = new Error("Please enable Service Workers in Brave Shield settings");
|
||||
assert.strictEqual(
|
||||
buildServiceWorkerFallbackStatus(error),
|
||||
`${BASE} (Brave Shields blocked the service worker)`
|
||||
);
|
||||
});
|
||||
|
||||
test("identifies blocked script errors", () => {
|
||||
const error = new Error(
|
||||
"Failed to register ServiceWorker: TypeError: Failed to fetch"
|
||||
);
|
||||
assert.strictEqual(
|
||||
buildServiceWorkerFallbackStatus(error),
|
||||
`${BASE} (service worker blocked by browser or extension)`
|
||||
);
|
||||
});
|
||||
|
||||
test("identifies controller claim timeout", () => {
|
||||
const error = new Error("Service worker controller claim timeout");
|
||||
assert.strictEqual(
|
||||
buildServiceWorkerFallbackStatus(error),
|
||||
`${BASE} (waiting for service worker control)`
|
||||
);
|
||||
});
|
||||
Reference in new issue
Block a user