
In our previous look at livecoding NodeJS from Caffeine, we implemented tweetcoding. Now let’s try another exercise, creating WebSockets that tunnel between web browsers. This gives us a very simple version of peer-to-peer networking, similar to WebRTC.
Once again we’ll start with Caffeine running in a web browser, and a NodeJS server running the node-livecode package. Our approach will be to use the NodeJS server as a relay. Web browsers that want to establish a publicly-available server can register there, and browser that want to use such a server can connect there. We’ll implement the following node-livecode instructions:
- initialize, to initialize the structures we’ll need for the other instructions
- create server credential, which creates a credential that a server browser can use to register a WebSocket as a server
- install server, which registers a WebSocket as a server
- connect to server, which a client browser can use to connect to a registered server
- forward to client, which forwards data from a server to a client
- forward to server, which forwards data from a client to a server
In Smalltalk, we’ll make a subclass of NodeJSLivecodingClient called NodeJSTunnelingClient, and give it an overriding implementation of configureServerAt:withCredential:, for injecting new instructions into our NodeJS server:
configureServerAt: url withCredential: credential
"Add JavaScript functions as protocol instructions to the
node-livecoding server at url, using the given credential."
^(super configureServerAt: url withCredential: credential)
addInstruction: 'initialize'
from: '
function () {
global.servers = []
global.clients = []
global.serverCredentials = []
global.delimiter = ''', Delimiter, '''
return ''initialized tunnel relay''}';
invoke: 'initialize';
addInstruction: 'create server credential'
from: '
function () {
var credential = Math.floor(Math.random() * 10000)
serverCredentials.push(credential)
this.send((serverCredentials.length - 1) + '' '' + credential)
return ''created server credential''}';
addInstruction: 'install server'
from: '
function (serverID, credential) {
if (serverCredentials[serverID] == credential) {
servers[serverID] = this
this.send(''1'')
return ''installed server''}
else {
debugger;
this.send(''0'')
return ''bad credential''}}';
addInstruction: 'connect to server'
from: '
function (serverID, port, req) {
if (servers[serverID]) {
clients.push(this)
servers[serverID].send(''connected:atPort:for: '' + (clients.length - 1) + delimiter + port + delimiter + req.connection.remoteAddress.toString())
this.send(''1'')
return ''connected client''}
else {
this.send(''0'')
return ''server not connected''}}';
addInstruction: 'forward to client'
from: '
function (channel, data) {
if (clients[channel]) {
clients[channel].send(''from:data: '' + servers.indexOf(this) + delimiter + data)
this.send(''1'')
return ''sent data to client''}
else {
this.send(''0'')
return ''no such client channel''}}';
addInstruction: 'forward to server'
from: '
function (channel, data) {
if (servers[channel]) {
servers[channel].send(''from:data: '' + clients.indexOf(this) + delimiter + data)
this.send(''1'')
return (''sent data to server'')}
else {
this.send(''0'')
return ''no such server channel''}}'
We’ll send that message immediately, configuring our NodeJS server:
NodeJSTunnelingClient
configureServerAt: 'wss://yourserver:8087'
withCredential: 'shared secret';
closeConfigurator
On the NodeJS console, we see the following messages:
server: received command 'add instruction'
server: adding instruction 'initialize'
server: received command 'initialize'
server: evaluating added instruction 'initialize'
server: initialized tunnel relay
server: received command 'add instruction'
server: adding instruction 'create server credential'
server: received command 'add instruction'
server: adding instruction 'install server'
server: received command 'add instruction'
server: adding instruction 'connect to server'
server: received command 'add instruction'
server: adding instruction 'forward to client'
server: received command 'add instruction'
server: adding instruction 'forward to server'
Now our NodeJS server is a tunneling relay, and we can connect servers and clients through it. We’ll make a new ForwardingWebSocket class hierarchy:
Object
ForwardingWebSocket
ForwardingClientWebSocket
ForwardingServerWebSocket
Instances of ForwardingClientWebSocket and ForwardingServerWebSocket use a NodeJSTunnelingClient to invoke our tunneling instructions.
We create a new ForwardingServerWebSocket with newThrough:, which requests new server credentials from the tunneling relay, and uses them to install a new server. Another new class, PeerToPeerWebSocket, provides the public message interface for the framework. There are two instantiation messages:
- toPort:atServerWithID:throughURL: creates an outgoing client that uses a ForwardingClientWebSocket to connect to a server and exchange data
- throughChannel:of: creates an incoming client that uses a ForwardingServerWebSocket to exchange data with a remote outgoing client.
Incoming clients are used by ForwardingServerWebSockets to represent their incoming connections. Each ForwardingServerWebSocket can provide services over a range of ports, as a normal IP server would. To connect, a client needs the websocket URL of the tunneling relay, a port, and the server ID assigned by the relay.
As usual, you can examine and try out this code by clearing your browser’s caches for caffeine.js.org (including IndexedDB), and visiting https://caffeine.js.org/. With browsers able to communicate directly, there are many interesting things we can build, including games, chat applications, and team development tools. What would you like to build?
Like this:
Like Loading...