This commit is contained in:
1 parent
7b43d260a2
commit
4357ce4094
9 files changed
+337
-100
No files matched your search
@@ -1147,6 +1147,7 @@ async function scanVideos(tabsToScan) {
|
||||
}
|
||||
|
||||
const items = [...seen.values()];
|
||||
await attachReferers(items);
|
||||
const skipped = scrapeResults.filter(r => r === null).length;
|
||||
|
||||
log('scanVideos complete: scanned=', tabResults.length, 'skipped=', skipped, 'unique items=', items.length);
|
||||
@@ -1157,6 +1158,31 @@ async function scanVideos(tabsToScan) {
|
||||
return { items, scanned: tabResults.length, skipped };
|
||||
}
|
||||
|
||||
/**
|
||||
* Bind the provider referer the SW captured (domain-aware) to each item so it
|
||||
* survives the watch-later flow and covers the sibling edge subdomains a segment
|
||||
* may resolve to. Best-effort: playback still falls back to the SW's live store.
|
||||
*/
|
||||
async function attachReferers(items) {
|
||||
const hostOf = (url) => { try { return new URL(url).host.toLowerCase(); } catch { return ''; } };
|
||||
const hosts = [];
|
||||
for (const it of items) {
|
||||
const host = hostOf(it.hlsManifest || it.src || '');
|
||||
if (host) hosts.push(host);
|
||||
}
|
||||
if (hosts.length === 0) return;
|
||||
let referers = {};
|
||||
try {
|
||||
const resp = await chrome.runtime.sendMessage({ type: 'GOON_GET_REFERERS', hosts });
|
||||
if (resp && resp.referers) referers = resp.referers;
|
||||
} catch { /* referer is best-effort; the SW still resolves it at play time */ }
|
||||
if (!Object.keys(referers).length) return;
|
||||
for (const it of items) {
|
||||
const host = hostOf(it.hlsManifest || it.src || '');
|
||||
if (host && referers[host]) it.referer = referers[host];
|
||||
}
|
||||
}
|
||||
|
||||
/** Store video entries in chrome.storage.local. */
|
||||
async function storeVideos(items) {
|
||||
const stored = items.map(v => ({
|
||||
@@ -1165,6 +1191,7 @@ async function storeVideos(items) {
|
||||
tab: v.tab,
|
||||
mediaType: v.mediaType || 'video',
|
||||
hlsManifest: v.hlsManifest || undefined,
|
||||
referer: v.referer || undefined,
|
||||
}));
|
||||
await chrome.storage.local.set({
|
||||
[STORAGE_KEY]: stored,
|
||||
|
||||
@@ -33,6 +33,11 @@
|
||||
"type": "string",
|
||||
"enum": ["direct", "hls"],
|
||||
"description": "Media delivery format. Defaults to 'direct' when omitted. Set to 'hls' for HTTP Live Streaming."
|
||||
},
|
||||
"referer": {
|
||||
"type": ["string", "null"],
|
||||
"format": "uri",
|
||||
"description": "Provider Referer captured at scan time for the manifest's CDN; the proxy sends it so a referer-protecting CDN accepts sibling edge subdomains. Optional."
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
|
||||
+33
-9
@@ -178,7 +178,7 @@ const panelsEl = document.getElementById('panels');
|
||||
// Route through the service worker to bypass the CDN's CORS/Referer
|
||||
// restrictions. The SW adds the required Referer and returns base64.
|
||||
console.log('[goon-viewer] Proxying via SW:', (url || '').substring(0, 120));
|
||||
chrome.runtime.sendMessage({ type: 'GOON_PROXY_FETCH', url, referer: originForUrl(url) }, (resp) => {
|
||||
chrome.runtime.sendMessage({ type: 'GOON_PROXY_FETCH', url, referer: refererForUrl(url) }, (resp) => {
|
||||
if (self._aborted) return;
|
||||
const lastErr = chrome.runtime.lastError;
|
||||
if (lastErr || !resp || !resp.ok) {
|
||||
@@ -264,19 +264,34 @@ HlsLoader.canSupportLoader = function () { return true; };
|
||||
// hardcode a particular CDN.
|
||||
const PROXY_HOSTS_KEY = 'goon_proxy_hosts';
|
||||
const proxyHosts = new Set();
|
||||
// Provider referers bound to a stored entry at scan time, keyed by the entry's
|
||||
// manifest registrable domain. A single provider referer covers every edge
|
||||
// subdomain, so a segment on a sibling subhost resolves to the same referer.
|
||||
const refererByDomain = new Map();
|
||||
(async () => {
|
||||
try {
|
||||
const stored = await chrome.storage.local.get(PROXY_HOSTS_KEY);
|
||||
const hosts = stored[PROXY_HOSTS_KEY];
|
||||
if (Array.isArray(hosts)) for (const h of hosts) proxyHosts.add(String(h).toLowerCase());
|
||||
if (Array.isArray(hosts)) for (const h of hosts) proxyHosts.add(registrableDomainOf(String(h).toLowerCase()));
|
||||
} catch { /* ignore */ }
|
||||
})();
|
||||
|
||||
// Origin for a URL's host — used as the Referer to request when a proxy fetch
|
||||
// is made for that host (the SW also captures the live referer per host).
|
||||
function originForUrl(url) {
|
||||
if (!url) return '';
|
||||
try { return new URL(url).origin + '/'; } catch { return ''; }
|
||||
// Registrable domain of a CDN host — the label a single provider referer
|
||||
// applies to (a referer-protecting CDN serves from many edge subdomains that
|
||||
// share one referer). Last-two-labels heuristic; mirrors the SW's copy.
|
||||
function registrableDomainOf(host) {
|
||||
if (!host) return '';
|
||||
const parts = host.split('.');
|
||||
if (parts.length < 2) return host;
|
||||
return parts.slice(-2).join('.');
|
||||
}
|
||||
|
||||
// The provider referer to send for a proxied request: the referer bound to the
|
||||
// entry at scan time (domain-keyed), or '' when none was captured. No self-origin
|
||||
// fallback — a self-origin referer is guaranteed-wrong for hotlink protection.
|
||||
function refererForUrl(url) {
|
||||
const ref = refererByDomain.get(registrableDomainOf(hostOfUrl(url)));
|
||||
return ref || '';
|
||||
}
|
||||
|
||||
// Whether a given video URL should be proxied: its host is a known
|
||||
@@ -284,7 +299,7 @@ HlsLoader.canSupportLoader = function () { return true; };
|
||||
function needsProxy(url) {
|
||||
if (!url || typeof url !== 'string') return false;
|
||||
const host = hostOfUrl(url);
|
||||
return host ? proxyHosts.has(host) : false;
|
||||
return host ? proxyHosts.has(registrableDomainOf(host)) : false;
|
||||
}
|
||||
|
||||
// host of a URL (lowercased); '' when malformed.
|
||||
@@ -1184,11 +1199,20 @@ async function init() {
|
||||
// Probe CORS on each video URL. Hosts that block extension-origin
|
||||
// requests are learned as referer-protected and persisted so the viewer
|
||||
// routes them through the service-worker proxy.
|
||||
// Bind each entry's scan-time referer to its manifest's registrable
|
||||
// domain, so the proxy sends it for every edge subdomain the provider
|
||||
// serves from (segments often resolve to a sibling subhost).
|
||||
for (const entry of videoUrls) {
|
||||
const ref = entry && entry.referer;
|
||||
if (!ref) continue;
|
||||
const dom = registrableDomainOf(hostOfUrl(entry.src || entry.hlsManifest || ''));
|
||||
if (dom) refererByDomain.set(dom, ref);
|
||||
}
|
||||
const urlsToTest = videoUrls.map(v => v.src || v.hlsManifest || 'none').filter(u => u !== 'none' && typeof u === 'string');
|
||||
const blockedHosts = new Set();
|
||||
for (const url of [...new Set(urlsToTest)]) {
|
||||
const corsOk = await corsCheck(url);
|
||||
if (!corsOk) blockedHosts.add(hostOfUrl(url));
|
||||
if (!corsOk) blockedHosts.add(registrableDomainOf(hostOfUrl(url)));
|
||||
console.log('[goon-viewer] CORS', corsOk ? 'OK' : 'BLOCKED:', url.substring(0, 120));
|
||||
}
|
||||
if (blockedHosts.size > 0) {
|
||||
|
||||
Reference in new issue
Block a user