Deno Native Messaging Host - fetch duplex
See Fetch body streams are not full duplex #1254.
Installation and usage on Chrome and Chromium
- Navigate to
chrome://extensions
. - Toggle
Developer mode
. - Click
Load unpacked
. - Select
native-messaging-deno
folder. - Note the generated extension ID.
- Open
nm_deno.json
in a text editor, set"path"
to absolute path ofnm_deno.js
andchrome-extension://<ID>/
using ID from 5 in"allowed_origins"
array. - Copy the file to Chrome or Chromium configuration folder, e.g., Chromium on *nix
~/.config/chromium/NativeMessagingHosts
; Chrome dev channel on *nix~/.config/google-chrome-unstable/NativeMessagingHosts
. - Make sure
deno
executable andnm_deno.js
are executable. To downloaddeno
executable into the cloned GitHub directory that is used as the local unpacked extension directory (and optionally strip symbolic information from thedeno
executable to reduce size) you can run
wget --show-progress \
--progress=bar \
--output-document deno.zip \
https://github.com/denoland/deno/releases/latest/download/deno-x86_64-unknown-linux-gnu.zip \
&& unzip deno.zip \
&& rm deno.zip \
&& strip deno
- This uses Deno's
fetch()
for full duplex streaming (writing to the uploadedReadableStream
and reading from theReadableStream
from theResponse
body
using a single request). Each native message passed to the host from the Web page is written to thewritable
side of aTransformStream()
where thereadable
side is set as value ofbody
in second parameter passed toRequest()
passed tofetch()
. To test navigate to an origin listed in"matches"
array in"externally_connectable"
in manifest.json, in source code, Sources => Snippets, or inconsole
run something like
if (port) {
port = null;
};
// Where <ID> is the generated unpacked extension ID
var port = chrome.runtime.connect('<ID>');
port.onMessage.addListener((message) => {
console.log(message);
});
port.onDisconnect.addListener((message) => {
console.log(message);
});
// Set in Request() passed to fetch() with readable side of TransformStream set as body
port.postMessage({url:'https://comfortable-deer-52.deno.dev', method:'post'});
await new Promise((resolve) => setTimeout(resolve, 200));
port.postMessage({a:'b',c:'d'}); // Transformed to uppercase {A: 'B', C: 'D'}
// Close WritableStreamDefaultWriter from writable side of TransformStream
port.postMessage(`CLOSE_STREAM`);
// Abort the request, reload the extension.
// port.postMessage(`ABORT_STREAM`);
Note: Deno Deploy times out the server (the same server code used for local development commented in nm_deno.js
) in ~5 1/2 minutes.
It looks like Deno Deploy behaves similar to Chromium/Chrome MV3 extension ServiceWorker
where without a persistent Native Messaging connection or other means, e.g., https://github.com/guest271314/persistent-serviceworker the ServiceWorker
becomes inactive in ~5 minutes.
So we have to write to the writable
side of TransformStream
to keep the readable
side that is uploaded via POST
active in the server.
Something like
var bool = 1;
async function keepalive() {
while (bool) {
port.postMessage(null);
await new Promise((resolve) => setTimeout(resolve, 1000 * 15));
}
return 'Done streaming';
};
await new Promise((resolve) => setTimeout(resolve, 200));
keepalive().then(console.log).catch(console.warn);
For differences between OS and browser implementations see Chrome incompatibilities.
Do What the Fuck You Want to Public License WTFPLv2