mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-11-04 20:02:26 +00:00
Merge pull request #574 from PR0M3TH3AN/codex/fix-webtorrent-fallback-for-video-playback
Handle WebTorrent fallback without service worker
This commit is contained in:
22 files changed
+278
-82
No files matched your search
@@ -8,8 +8,8 @@ import {
|
||||
isDevMode,
|
||||
} from "./config.js";
|
||||
import { nostrClient } from "./nostr.js";
|
||||
import {
|
||||
import { devLogger, userLogger } from "./utils/logger.js";
|
||||
import {
|
||||
buildAdminListEvent,
|
||||
ADMIN_LIST_IDENTIFIERS,
|
||||
} from "./nostrEventSchemas.js";
|
||||
@@ -151,8 +151,10 @@ function readJsonListFromStorage(key) {
|
||||
const parsed = JSON.parse(raw);
|
||||
return Array.isArray(parsed) ? parsed : null;
|
||||
} catch (error) {
|
||||
devLogger.warn(`[adminListStore] Failed to parse legacy list for ${key:`, error);
|
||||
}
|
||||
devLogger.warn(
|
||||
`[adminListStore] Failed to parse legacy list for ${key}:`,
|
||||
error,
|
||||
);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -198,8 +200,10 @@ function clearLegacyStorageFor(listKey) {
|
||||
try {
|
||||
localStorage.removeItem(key);
|
||||
} catch (error) {
|
||||
devLogger.warn(`[adminListStore] Failed to clear legacy storage for ${key:`, error);
|
||||
}
|
||||
devLogger.warn(
|
||||
`[adminListStore] Failed to clear legacy storage for ${key}:`,
|
||||
error,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -40,8 +40,8 @@ import {
|
||||
requestInvoice
|
||||
} from "./payments/lnurl.js";
|
||||
import { getPlatformLightningAddress } from "./payments/platformAddress.js";
|
||||
import {
|
||||
import { userLogger } from "./utils/logger.js";
|
||||
import {
|
||||
ensureWallet,
|
||||
sendPayment as sendWalletPayment
|
||||
} from "./payments/nwcClient.js";
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
// js/config.js
|
||||
|
||||
import {
|
||||
import { userLogger } from "./utils/logger.js";
|
||||
import {
|
||||
IS_DEV_MODE,
|
||||
ADMIN_SUPER_NPUB,
|
||||
ADMIN_DM_IMAGE_URL,
|
||||
|
||||
+1
-1
@@ -13,8 +13,8 @@ import {
|
||||
initThemeController,
|
||||
refreshThemeControls,
|
||||
} from "./themeController.js";
|
||||
import {
|
||||
import { devLogger, userLogger } from "./utils/logger.js";
|
||||
import {
|
||||
prepareStaticModal,
|
||||
openStaticModal,
|
||||
closeStaticModal,
|
||||
|
||||
+45
-38
@@ -1620,8 +1620,7 @@ function normalizePointerInput(pointer) {
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
devLogger.warn(`[nostr] Failed to decode pointer ${trimmed:`, err);
|
||||
}
|
||||
devLogger.warn(`[nostr] Failed to decode pointer ${trimmed}:`, err);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5681,17 +5680,18 @@ export class NostrClient {
|
||||
|
||||
this.restoreLocalData();
|
||||
|
||||
try {
|
||||
await this.ensurePool();
|
||||
const results = await this.connectToRelays();
|
||||
const successfulRelays = results
|
||||
.filter((r) => r.success)
|
||||
.map((r) => r.url);
|
||||
if (successfulRelays.length === 0) {
|
||||
throw new Error("No relays connected");
|
||||
}
|
||||
devLogger.log(`Connected to ${successfulRelays.length relay(s)`);
|
||||
}
|
||||
try {
|
||||
await this.ensurePool();
|
||||
const results = await this.connectToRelays();
|
||||
const successfulRelays = results
|
||||
.filter((r) => r.success)
|
||||
.map((r) => r.url);
|
||||
if (successfulRelays.length === 0) {
|
||||
throw new Error("No relays connected");
|
||||
}
|
||||
devLogger.log(
|
||||
`Connected to ${successfulRelays.length} relay(s)`,
|
||||
);
|
||||
} catch (err) {
|
||||
userLogger.error("Nostr init failed:", err);
|
||||
throw err;
|
||||
@@ -6159,12 +6159,11 @@ export class NostrClient {
|
||||
}
|
||||
}
|
||||
|
||||
if (!signerPubkey && signedEvent && typeof signedEvent.pubkey === "string") {
|
||||
signerPubkey = signedEvent.pubkey;
|
||||
}
|
||||
if (!signerPubkey && signedEvent && typeof signedEvent.pubkey === "string") {
|
||||
signerPubkey = signedEvent.pubkey;
|
||||
}
|
||||
|
||||
devLogger.log(`Signed ${devLogLabel event:`, signedEvent);
|
||||
}
|
||||
devLogger.log(`Signed ${devLogLabel} event:`, signedEvent);
|
||||
|
||||
let targetRelays = sanitizeRelayList(
|
||||
Array.isArray(relaysOverride) && relaysOverride.length
|
||||
@@ -7373,11 +7372,13 @@ export class NostrClient {
|
||||
try {
|
||||
const events = await this.pool.list([url], [filter]);
|
||||
return Array.isArray(events) ? events : [];
|
||||
} catch (error) {
|
||||
devLogger.warn(`[nostr] NIP-71 fetch failed on ${url:`, error);
|
||||
} catch (error) {
|
||||
devLogger.warn(
|
||||
`[nostr] NIP-71 fetch failed on ${url}:`,
|
||||
error,
|
||||
);
|
||||
return [];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
@@ -8538,14 +8539,18 @@ export class NostrClient {
|
||||
});
|
||||
const count = this.extractCountValue(frame?.[2]);
|
||||
return { url, ok: true, frame, count };
|
||||
} catch (error) {
|
||||
const isUnsupported = error?.code === "count-unsupported";
|
||||
if (isUnsupported) {
|
||||
this.countUnsupportedRelays.add(url);
|
||||
} else devLogger.warn(`[nostr] COUNT request failed on ${url:`, error);
|
||||
} catch (error) {
|
||||
const isUnsupported = error?.code === "count-unsupported";
|
||||
if (isUnsupported) {
|
||||
this.countUnsupportedRelays.add(url);
|
||||
} else {
|
||||
devLogger.warn(
|
||||
`[nostr] COUNT request failed on ${url}:`,
|
||||
error,
|
||||
);
|
||||
}
|
||||
return { url, ok: false, error, unsupported: isUnsupported };
|
||||
}
|
||||
return { url, ok: false, error, unsupported: isUnsupported };
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
@@ -8735,16 +8740,18 @@ export class NostrClient {
|
||||
|
||||
try {
|
||||
const perRelay = await Promise.all(
|
||||
this.relays.map(async (url) => {
|
||||
try {
|
||||
const events = await this.pool.list([url], [filter]);
|
||||
return events || [];
|
||||
} catch (err) {
|
||||
devLogger.warn(`[nostr] History fetch failed on ${url:`, err);
|
||||
this.relays.map(async (url) => {
|
||||
try {
|
||||
const events = await this.pool.list([url], [filter]);
|
||||
return events || [];
|
||||
} catch (err) {
|
||||
devLogger.warn(
|
||||
`[nostr] History fetch failed on ${url}:`,
|
||||
err,
|
||||
);
|
||||
return [];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
})
|
||||
})
|
||||
);
|
||||
|
||||
const merged = perRelay.flat();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import {
|
||||
import { devLogger } from "./utils/logger.js";
|
||||
import {
|
||||
ADMIN_LIST_NAMESPACE,
|
||||
isDevMode,
|
||||
WATCH_HISTORY_KIND,
|
||||
@@ -830,8 +830,9 @@ function resolveAdminNoteType(listKey) {
|
||||
export function buildAdminListEvent(listKey, { pubkey, created_at, hexPubkeys = [] }) {
|
||||
const schema = getNostrEventSchema(resolveAdminNoteType(listKey));
|
||||
if (!schema) {
|
||||
devLogger.warn(`[nostrEventSchemas] Unknown admin list key: ${listKey`);
|
||||
}
|
||||
devLogger.warn(
|
||||
`[nostrEventSchemas] Unknown admin list key: ${listKey}`,
|
||||
);
|
||||
return {
|
||||
kind: 30000,
|
||||
pubkey,
|
||||
|
||||
@@ -10,8 +10,8 @@ import {
|
||||
} from "./lnurl.js";
|
||||
import { ensureWallet, sendPayment } from "./nwcClient.js";
|
||||
import { getPlatformLightningAddress } from "./platformAddress.js";
|
||||
import {
|
||||
import { userLogger } from "../utils/logger.js";
|
||||
import {
|
||||
clampPercent,
|
||||
parsePercentValue,
|
||||
resolvePlatformFeePercent,
|
||||
|
||||
+1
-1
@@ -1,8 +1,8 @@
|
||||
import { isDevMode } from "./config.js";
|
||||
import { DEFAULT_RELAY_URLS, nostrClient } from "./nostr.js";
|
||||
import { buildRelayListEvent } from "./nostrEventSchemas.js";
|
||||
import {
|
||||
import { devLogger, userLogger } from "./utils/logger.js";
|
||||
import {
|
||||
publishEventToRelays,
|
||||
assertAnyRelayAccepted,
|
||||
} from "./nostrPublish.js";
|
||||
|
||||
@@ -6,8 +6,8 @@ import {
|
||||
getPubkey,
|
||||
getCurrentUserNpub,
|
||||
} from "../state/appState.js";
|
||||
import {
|
||||
import { userLogger } from "../utils/logger.js";
|
||||
import {
|
||||
getSavedProfiles,
|
||||
getActiveProfilePubkey,
|
||||
setActiveProfilePubkey,
|
||||
|
||||
@@ -4,8 +4,8 @@ import {
|
||||
} from "../nostr.js";
|
||||
import { accessControl } from "../accessControl.js";
|
||||
import { ALLOW_NSFW_CONTENT } from "../config.js";
|
||||
import {
|
||||
import { userLogger } from "../utils/logger.js";
|
||||
import {
|
||||
getVideosMap as getStoredVideosMap,
|
||||
setVideosMap as setStoredVideosMap,
|
||||
getVideoSubscription as getStoredVideoSubscription,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { isDevMode } from "../config.js";
|
||||
import { pointerArrayToKey } from "../utils/pointer.js";
|
||||
import {
|
||||
import { devLogger, userLogger } from "../utils/logger.js";
|
||||
import {
|
||||
ingestLocalViewEvent as defaultIngestLocalViewEvent,
|
||||
} from "../viewCounter.js";
|
||||
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
import {
|
||||
import { userLogger } from "../utils/logger.js";
|
||||
import {
|
||||
readUrlHealthFromStorage,
|
||||
removeUrlHealthFromStorage,
|
||||
writeUrlHealthToStorage,
|
||||
|
||||
@@ -2,8 +2,8 @@ import { normalizeDesignSystemContext } from "../../designSystem.js";
|
||||
import { updateVideoCardSourceVisibility } from "../../utils/cardSourceVisibility.js";
|
||||
import positionFloatingPanel from "../utils/positionFloatingPanel.js";
|
||||
import { createFloatingPanelStyles } from "../utils/floatingPanelStyles.js";
|
||||
import {
|
||||
import { userLogger } from "../../utils/logger.js";
|
||||
import {
|
||||
getPopupOffsetPx,
|
||||
getPopupViewportPaddingPx,
|
||||
} from "../../designSystem/metrics.js";
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { normalizeDesignSystemContext } from "../designSystem.js";
|
||||
import positionFloatingPanel from "./utils/positionFloatingPanel.js";
|
||||
import { createFloatingPanelStyles } from "./utils/floatingPanelStyles.js";
|
||||
import {
|
||||
import { userLogger } from "../utils/logger.js";
|
||||
import {
|
||||
getPopupOffsetPx,
|
||||
getPopupViewportPaddingPx,
|
||||
} from "../designSystem/metrics.js";
|
||||
|
||||
@@ -350,13 +350,17 @@ export default class WatchHistoryController {
|
||||
try {
|
||||
const result = this.watchHistoryService.snapshot(undefined, { reason });
|
||||
return Promise.resolve(result).catch((error) => {
|
||||
devLogger.warn(`[${context] Watch history flush failed:`, error);
|
||||
}
|
||||
devLogger.warn(
|
||||
`[${context}] Watch history flush failed:`,
|
||||
error,
|
||||
);
|
||||
throw error;
|
||||
});
|
||||
} catch (error) {
|
||||
devLogger.warn(`[${context] Failed to queue watch history flush:`, error);
|
||||
}
|
||||
devLogger.warn(
|
||||
`[${context}] Failed to queue watch history flush:`,
|
||||
error,
|
||||
);
|
||||
return Promise.reject(error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,8 +18,8 @@ import {
|
||||
requestInvoice,
|
||||
} from "../payments/lnurl.js";
|
||||
import { getPlatformLightningAddress } from "../payments/platformAddress.js";
|
||||
import {
|
||||
import { devLogger, userLogger } from "../utils/logger.js";
|
||||
import {
|
||||
ensureWallet as ensureWalletDefault,
|
||||
sendPayment as sendPaymentDefault,
|
||||
} from "../payments/nwcClient.js";
|
||||
|
||||
+1
-1
@@ -1,8 +1,8 @@
|
||||
// js/userBlocks.js
|
||||
import { nostrClient } from "./nostr.js";
|
||||
import { buildBlockListEvent, BLOCK_LIST_IDENTIFIER } from "./nostrEventSchemas.js";
|
||||
import {
|
||||
import { userLogger } from "./utils/logger.js";
|
||||
import {
|
||||
publishEventToRelays,
|
||||
assertAnyRelayAccepted,
|
||||
} from "./nostrPublish.js";
|
||||
|
||||
@@ -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;
|
||||
+1
-1
@@ -16,8 +16,8 @@ import {
|
||||
VIEW_COUNT_CACHE_TTL_MS,
|
||||
VIEW_COUNT_DEDUPE_WINDOW_SECONDS,
|
||||
} from "./config.js";
|
||||
import {
|
||||
import { userLogger } from "./utils/logger.js";
|
||||
import {
|
||||
countVideoViewEvents,
|
||||
listVideoViewEvents,
|
||||
subscribeVideoViewEvents,
|
||||
|
||||
+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