async function post_message_to_client(role, message, ports) { (await clients.matchAll()).forEach(client => { if (new URL(client.url).searchParams.get('role') === role) { client.postMessage(message, ports); } }); } async function post_message_to_child(message, ports) { await post_message_to_client('child', message, ports); } function ping_message(data) { return { type: 'ping', data }; } self.onmessage = event => { const message = ping_message(event.data); post_message_to_child(message); post_message_to_parent(message); } async function post_message_to_parent(message, ports) { await post_message_to_client('parent', message, ports); } function fetch_message(key) { return { type: 'fetch', key }; } // Send a message to the parent along with a MessagePort to respond // with. function report_fetch_request(key) { const channel = new MessageChannel(); const reply = new Promise(resolve => { channel.port1.onmessage = resolve; }).then(event => event.data); return post_message_to_parent(fetch_message(key), [channel.port2]).then(() => reply); } function respond_with_script(script) { return new Response(new Blob(script, { type: 'text/javascript' })); } // Whenever a controlled document requests a URL with a 'key' search // parameter we report the request to the parent frame and wait for // a response. The content of the response is then used to respond to // the fetch request. addEventListener('fetch', event => { let key = new URL(event.request.url).searchParams.get('key'); if (key) { event.respondWith(report_fetch_request(key).then(respond_with_script)); } });