plnkr.co using a ServiceWorker and WindowClient outside of an extension
https://plnkr.co/plunk/2PQK21kZTZvZ2oVi.A ReadableStream is posted to the server using fetch() with duplex set to half. Thereafter it's possible to keep that single connection established essentially indefinitely, read and write, due to the way ServiceWorker folks want onfetch and respondWith() to work - using Mojo. Another example
https://github.com/guest271314/persistent-serviceworker/tree/main/readablestream-fetch-respondwith.
In an local extension I'm using, something like this, where I open an iframe and use fetch() in the extension so Ican full-duplexstreamback to the arbitrary Web site - after a message from the Native Messaging host - using Transferable Streams
In the iframe. Notice that "r" Promise is fulfilled. It will not be fulfilled in any other case that I am aware of in any browser. Node.js, Deno, and Bun implementations of fetch all full-duplexstream by default. Not Chromium WHATWG Fetch implementation - outside of this case that I am aware of - due to WHATWG Fetch #1254. My ultimate goal in hacking ServiceWorker, among other Web API is to ultimately just full-duplex stream without networking at all, to and from a Native Messaging host, to avoid the current IPC and JSON-like implementation of Native Messaging.
const r = await fetch("./?stream", {
method: "POST",
headers: { "Content-Type": "text/plain" },
body: stream,
duplex: "half",
});
console.log(r);
// .then((r) =>
await r.body.pipeThrough(new TextDecoderStream()).pipeTo(
new WritableStream({
write(value) {
output.textContent = value;
console.log(input.value.length, output.value.length);
},
close() {
console.log("Stream closed");
},
abort(reason) {
console.log({ reason });
},
}),
)
In the ServiceWorker
addEventListener("fetch", async (e) => {
console.log(e.request.url, e.clientId, [...e.request.headers]);
if (e.request.url.includes("stream")) {
e.respondWith(
new Response(
e.request.body
.pipeThrough(new TextDecoderStream())
.pipeThrough(
new TransformStream({
async start() {
getClientState(e.clientId);
if (globalThis.port === null) {
readable = new ReadableStream({
start: (_) => {
return controller = _;
},
cancel(reason) {
console.log(reason);
},
});
reader = readable.getReader();
port = chrome.runtime.connectNative(
chrome.runtime.getManifest().short_name,
);
port.onMessage.addListener((message) => {
controller.enqueue(encoder.encode(message));
});
port.onDisconnect.addListener((e) => {
console.log(e);
if (chrome.runtime.lastError) {
console.log(chrome.runtime.lastError);
}
controller.close();
port = readable = controller = null;
});
}
},
async transform(value, c) {
console.log(value);
port.postMessage(value);
const { value: message, done } = await reader.read();
console.log(message, done);
c.enqueue(message);
},
flush() {
console.log("flush");
},
}),
),
),
);
}
});