refactor: decouple from site-specific CDN references #5
@@ -29,18 +29,19 @@ function buildModule(code) {
|
|||||||
return `(async () => {\n${code}\n})();\n`;
|
return `(async () => {\n${code}\n})();\n`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// A fresh mock of the chrome APIs the worker touches. `onBeforeSendHeaders`,
|
// A fresh mock of the chrome APIs the worker touches. `onBeforeRequest` and
|
||||||
// `onBeforeRequest`, and `tabs.onRemoved` capture their listener so the test can
|
// `tabs.onRemoved` capture their listener, and `tabs.get` serves the source
|
||||||
// drive them; `declarativeNetRequest.updateSessionRules` records the live rules.
|
// tab's URL (the captured referer); `declarativeNetRequest.updateSessionRules`
|
||||||
|
// records the live rules.
|
||||||
function makeChromeEnv() {
|
function makeChromeEnv() {
|
||||||
const storage = {};
|
const storage = {};
|
||||||
const handlers = { onMessage: [] };
|
const handlers = { onMessage: [] };
|
||||||
|
const tabUrls = {};
|
||||||
let sessionRules = {};
|
let sessionRules = {};
|
||||||
|
|
||||||
const webRequest = {
|
const webRequest = {
|
||||||
onBeforeSendHeaders: { addListener: (cb, filter) => { handlers.onBeforeSendHeaders = cb; handlers.onBeforeSendHeadersFilter = filter; } },
|
|
||||||
onBeforeRequest: { addListener: (cb) => { handlers.onBeforeRequest = cb; } },
|
onBeforeRequest: { addListener: (cb) => { handlers.onBeforeRequest = cb; } },
|
||||||
};
|
};
|
||||||
|
|
||||||
const chrome = {
|
const chrome = {
|
||||||
storage: {
|
storage: {
|
||||||
@@ -62,8 +63,9 @@ function makeChromeEnv() {
|
|||||||
},
|
},
|
||||||
webRequest,
|
webRequest,
|
||||||
tabs: {
|
tabs: {
|
||||||
|
get: async (tabId) => tabUrls[tabId] ? { tabId, url: tabUrls[tabId] } : undefined,
|
||||||
onRemoved: { addListener: (cb) => { handlers.onRemoved = cb; } },
|
onRemoved: { addListener: (cb) => { handlers.onRemoved = cb; } },
|
||||||
},
|
},
|
||||||
runtime: { onMessage: { addListener: (cb) => { handlers.onMessage.push(cb); } } },
|
runtime: { onMessage: { addListener: (cb) => { handlers.onMessage.push(cb); } } },
|
||||||
declarativeNetRequest: {
|
declarativeNetRequest: {
|
||||||
updateSessionRules: async ({ removeRuleIds = [], addRules = [] } = {}) => {
|
updateSessionRules: async ({ removeRuleIds = [], addRules = [] } = {}) => {
|
||||||
@@ -76,7 +78,9 @@ function makeChromeEnv() {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
return { chrome, handlers, storage, getSessionRules: () => sessionRules };
|
return { chrome, handlers, storage, tabUrls,
|
||||||
|
setTabUrl: (tabId, url) => { tabUrls[tabId] = url; },
|
||||||
|
getSessionRules: () => sessionRules };
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load the worker into a fresh environment. `data:` URLs are cached by Node, so
|
// Load the worker into a fresh environment. `data:` URLs are cached by Node, so
|
||||||
@@ -92,12 +96,12 @@ async function loadBackground(env) {
|
|||||||
return module;
|
return module;
|
||||||
}
|
}
|
||||||
|
|
||||||
function videoDetails(tabId, url, referer) {
|
// Drive a referer capture the way the worker now does it: the worker reads the
|
||||||
return {
|
// source tab's URL (via chrome.tabs.get) as the provider referer, so we set the
|
||||||
tabId,
|
// tab's URL and fire an onBeforeRequest for a video URL on that tab.
|
||||||
url,
|
async function capture(env, videoUrl, referer) {
|
||||||
requestHeaders: referer ? [{ name: 'Referer', value: referer }] : [],
|
env.setTabUrl(11, referer);
|
||||||
};
|
await env.handlers.onBeforeRequest({ tabId: 11, url: videoUrl });
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('PR#5: per-host referer capture + proxy race', () => {
|
describe('PR#5: per-host referer capture + proxy race', () => {
|
||||||
@@ -108,10 +112,8 @@ describe('PR#5: per-host referer capture + proxy race', () => {
|
|||||||
global.chrome = env.chrome;
|
global.chrome = env.chrome;
|
||||||
await loadBackground(env);
|
await loadBackground(env);
|
||||||
|
|
||||||
// A live source tab captures the provider referer for its CDN host.
|
// A live source tab captures the provider referer for its CDN host.
|
||||||
const cap = env.handlers.onBeforeSendHeaders;
|
await capture(env, 'https://cdnA.example.com/hls/live/seg-1.ts', 'https://pageA.example.com/watch');
|
||||||
if (!cap) throw new Error('onBeforeSendHeaders listener was not registered');
|
|
||||||
await cap(videoDetails(11, 'https://cdnA.example.com/hls/live/seg-1.ts', 'https://pageA.example.com/watch'));
|
|
||||||
|
|
||||||
const rA = env.storage['goon_referers']?.['example.com'];
|
const rA = env.storage['goon_referers']?.['example.com'];
|
||||||
if (!rA) throw new Error('referer was not captured for cdnA');
|
if (!rA) throw new Error('referer was not captured for cdnA');
|
||||||
@@ -136,9 +138,7 @@ describe('PR#5: per-host referer capture + proxy race', () => {
|
|||||||
global.chrome = env.chrome;
|
global.chrome = env.chrome;
|
||||||
await loadBackground(env);
|
await loadBackground(env);
|
||||||
|
|
||||||
await env.handlers.onBeforeSendHeaders(
|
await capture(env, 'https://cdnB.example.com/video/x/master.m3u8', 'https://pageB.example.com/v');
|
||||||
videoDetails(11, 'https://cdnB.example.com/video/x/master.m3u8', 'https://pageB.example.com/v')
|
|
||||||
);
|
|
||||||
|
|
||||||
// The very tab that captured the referer now closes.
|
// The very tab that captured the referer now closes.
|
||||||
await env.handlers.onRemoved(11);
|
await env.handlers.onRemoved(11);
|
||||||
@@ -252,11 +252,10 @@ describe('PR#5: per-host referer capture + proxy race', () => {
|
|||||||
});
|
});
|
||||||
await loadBackground(env);
|
await loadBackground(env);
|
||||||
|
|
||||||
// Capture the provider referer for hv-h.phncdn.com (one edge subdomain).
|
// Capture the provider referer for hv-h.phncdn.com (one edge subdomain).
|
||||||
await env.handlers.onBeforeSendHeaders(
|
await capture(env, 'https://hv-h.phncdn.com/hls/live/seg-1.ts', 'https://provider.example.com/watch');
|
||||||
videoDetails(11, 'https://hv-h.phncdn.com/hls/live/seg-1.ts', 'https://provider.example.com/watch'));
|
|
||||||
|
|
||||||
// Proxy a SIBLING subdomain ev-h.phncdn.com with NO referer in the message:
|
// Proxy a SIBLING subdomain ev-h.phncdn.com with NO referer in the message:
|
||||||
// the SW must resolve it from the domain-keyed store.
|
// the SW must resolve it from the domain-keyed store.
|
||||||
const handler = env.handlers.onMessage[env.handlers.onMessage.length - 1];
|
const handler = env.handlers.onMessage[env.handlers.onMessage.length - 1];
|
||||||
let resp;
|
let resp;
|
||||||
@@ -314,9 +313,8 @@ describe('PR#5: per-host referer capture + proxy race', () => {
|
|||||||
global.chrome = env.chrome;
|
global.chrome = env.chrome;
|
||||||
await loadBackground(env);
|
await loadBackground(env);
|
||||||
|
|
||||||
// Capture for hv-h.phncdn.com only.
|
// Capture for hv-h.phncdn.com only.
|
||||||
await env.handlers.onBeforeSendHeaders(
|
await capture(env, 'https://hv-h.phncdn.com/hls/live/seg-1.ts', 'https://provider.example.com/watch');
|
||||||
videoDetails(11, 'https://hv-h.phncdn.com/hls/live/seg-1.ts', 'https://provider.example.com/watch'));
|
|
||||||
|
|
||||||
const handler = env.handlers.onMessage[env.handlers.onMessage.length - 1];
|
const handler = env.handlers.onMessage[env.handlers.onMessage.length - 1];
|
||||||
let resp;
|
let resp;
|
||||||
@@ -339,20 +337,28 @@ describe('PR#5: per-host referer capture + proxy race', () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Regression for the live 412 bug: the capture listener MUST request
|
// Regression for the live 412 bug: the worker captures the provider referer
|
||||||
// requestHeaders via extraInfoSpec, or details.requestHeaders is undefined and
|
// from the source tab's URL via chrome.tabs.get inside onBeforeRequest. The
|
||||||
// readReferer() returns '' for every request — so no referer is ever stored,
|
// old path read request headers via onBeforeSendHeaders + extraInfoSpec, which
|
||||||
// every entry stays referer-less, and the phncdn proxy 412s. The callback-level
|
// is rejected by some Chromium builds and fails the whole service worker, so it
|
||||||
// tests above pass even without this (they hand requestHeaders in directly), so
|
// was removed; this asserts the new tab-URL capture is wired up and actually
|
||||||
// this asserts the registration itself.
|
// stores a referer.
|
||||||
it('the onBeforeSendHeaders capture listener requests requestHeaders via extraInfoSpec', async () => {
|
it('the onBeforeRequest capture reads the referer from the source tab URL', async () => {
|
||||||
const env = makeChromeEnv();
|
const env = makeChromeEnv();
|
||||||
global.chrome = env.chrome;
|
global.chrome = env.chrome;
|
||||||
await loadBackground(env);
|
await loadBackground(env);
|
||||||
const filter = env.handlers.onBeforeSendHeadersFilter;
|
|
||||||
if (!filter) throw new Error('onBeforeSendHeaders listener registered no filter object');
|
const cap = env.handlers.onBeforeRequest;
|
||||||
if (!Array.isArray(filter.extraInfoSpec) || !filter.extraInfoSpec.includes('requestHeaders')) {
|
if (!cap) throw new Error('onBeforeRequest listener was not registered');
|
||||||
throw new Error('capture listener did not request requestHeaders via extraInfoSpec: ' + JSON.stringify(filter));
|
|
||||||
|
// A video request for cdnA from a tab whose URL IS the provider referer.
|
||||||
|
env.setTabUrl(11, 'https://pageA.example.com/watch');
|
||||||
|
await cap({ tabId: 11, url: 'https://cdnA.example.com/hls/live/seg-1.ts' });
|
||||||
|
|
||||||
|
const rA = env.storage['goon_referers']?.['example.com'];
|
||||||
|
if (!rA) throw new Error('referer was not captured from the source tab URL');
|
||||||
|
if (rA.referer !== 'https://pageA.example.com/watch') {
|
||||||
|
throw new Error('captured referer wrong: ' + JSON.stringify(rA));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
Reference in new issue
Block a user