635 words
3 minutes
Annotated code for a demo of WebSocket chat in Deno Deploy
Annotated code for a demo of WebSocket chat in Deno Deploy
Deno Deploy is a hosted Deno service that promises a multi-tenant JavaScript engine running in 25 data centers across the world.
Today this demo by Ondřej Žára showed up on Hacker News, which implements “a multi-datacenter chat, client+server in 23 lines of TS”.
Here’s my annotated copy of the code, which I wrote while figuring out how it works.
// listenAndServe is the Deno standard mechanism for creating an HTTP server// https://deno.land/manual/examples/http_server#using-the-codestdhttpcode-libraryimport { listenAndServe } from "https://deno.land/std/http/server.ts"
// Set of all of the currently open WebSocket connections from browsersconst sockets = new Set<WebSocket>(),/*BroadcastChannel is a concept that is unique to the Deno Deploy environment.
https://deno.com/deploy/docs/runtime-broadcast-channel/
It is modelled after the browser API of the same name.
It sets up a channel between ALL instances of the server-side script runningin every one of the Deno Deploy global network of data centers.
The argument is the name of the channel, which apparently can be an empty string.*/ channel = new BroadcastChannel(""), headers = {"Content-type": "text/html"},/*This is the bare-bones HTML for the browser side of the application
It creates a WebSocket connection back to the host, and sets it up so anymessage that arrives via that WebSocket will be appended to the textContentof the pre element on the page.
The input element has an onkeyup that checks for the Enter key and sendsthe value of that element over the WebSocket channel to the server.*/ html = `<script>let ws = new WebSocket("wss://"+location.host)ws.onmessage = e => pre.textContent += e.data+"\\n"</script><input onkeyup="event.key=='Enter'&&ws.send(this.value)"><pre id=pre>`
/*This bit does the broadcast work: any time a message is received from theBroadcastChannel it is forwarded on to every single one of the currentlyattached WebSocket connections, using the data in that "sockets" set.
Additionally, this covers the case of messages coming from a client connectedto THIS instance - these are also sent to the channel (see code below), buthere it spots that the message event's e.target is NOT the current instanceand sends the message to that channel so it broadcast to the other data centers.*/channel.onmessage = e => { (e.target != channel) && channel.postMessage(e.data) sockets.forEach(s => s.send(e.data))}
/*I tried removing the await here and the demo still worked.
But https://deno.land/std@0.113.0/http/server.ts#L224 shows that this functionis indeed an async that returns a Promise.*/await listenAndServe(":8080", (r: Request) => { try { /* Deno.upgradeWebSocket is a relatively new feature, added in Deno v1.12 in July 2021: https://deno.com/blog/v1.12#server-side-websocket-support-in-native-http
It gives you back a response that you should return to the client in order to finish establishing the WebSocket connection, and a socket object which you can then use for further WebSocket communication. */ const { socket, response } = Deno.upgradeWebSocket(r) // Add it to the set so we can send to all of them later sockets.add(socket) /* This is a sneaky hack: when a message arrives from the WebSocket we pass it directly to the BroadcastChannel - then use the e.target != channel check above to broadcast it on to every other global instance. */ socket.onmessage = channel.onmessage // When browser disconnects, remove the socket from the set of sockets socket.onclose = _ => sockets.delete(socket) return response } catch { /* I added code here to catch(e) and display e.toString() which showed me that the exception caught here is:
exception: TypeError: Invalid Header: 'upgrade' header must be 'websocket'
This is an exception thrown by Deno.upgradeWebSocket(r) if the incoming request does not include the "upgrade: websocket" HTTP header, which is added by browsers when using new WebSocket("wss://...")
So here we return the HTML and headers for the application itself. */ return new Response(html, {headers}) }})
Relevant links:
Annotated code for a demo of WebSocket chat in Deno Deploy
https://mranv.pages.dev/posts/annotated-code-for-a-demo-of-websocket-chat-in-deno-deploy/