The peers protocol is the internal P2P transport that lets every component of the Weble IoT Universal Gateway — drivers, logics, the web server, the UI, and remote integrations — share modules and exchange events as if they all lived inside the same Node.js process.
This page documents the client-side primitives you will use when writing code against the API. The wire format itself is an implementation detail and is not covered here.
var peers = require('peers')('myClientName')
The argument is the peer name you advertise on the mesh. It must be unique among the currently-connected peers. By convention:
'supervision' — it owns all the core modules.'driver_<type>_<id>'.'metricsExporter', 'mobileBackend', …).The function returned is both an object and a function. As an object it has methods (share, connect, listen, …). As a function it takes a peer name and returns a remote-peer handle:
peers.share('myModule', myObject) // expose myModule to other peers
peers('supervision').require('gateways', cb) // load the 'gateways' module from the 'supervision' peer
peers('supervision').on('newValue 4/7/25', cb) // listen to events from the 'supervision' peer
External clients never call peers.connect() directly — the connection is set up for you by the bootstrap returned from POST /auth. See Getting started for the full flow. After bootstrap, the peer object you receive is already connected and reconnects automatically if the link drops.
requirepeer('supervision').require('gateways', function(err, gateways){
if(err) return console.error(err)
gateways.writeValue('4/7/25', 1, function(err){
if(err) console.error('write failed', err)
})
})
require does a round-trip to the remote peer to ask for the list of methods of the named module. It returns a local proxy object with the same method names — calling any of them serializes the arguments, sends them over the socket, awaits the remote callback, and invokes your local callback with the result.
A few important things to know:
function(err, ...results){}.require the same module multiple times — each call returns the same proxy object after the first round trip.require and the new method appears.
shareIf your peer wants to expose its own functions to others on the mesh:
peers.share('temperatureSensor', {
read : function(callback){
callback(null, 21.7)
},
setUnit : function(unit, callback){
// ...
callback(null)
}
})
Any other peer can now require('temperatureSensor', cb) and get a proxy with read and setUnit.
The optional third argument of share is a function called each time a peer requires the module. It receives the id of that peer. It cannot refuse access.
on, once, emit, removeListenerEvery peer carries a shared EventEmitter that other peers can listen to. This is how the gateway pushes live updates (new values, gateway state changes, route status changes, …) to clients.
// Listen to a specific event from a remote peer
peer('supervision').on('newValue 4/7/25', function(value, value_string, value_date){
console.log('value changed', value)
})
// Listen once
peer('supervision').once('gatewayState 12', function(state){ ... })
// Stop listening (always do this when the listener is no longer needed!)
peer('supervision').removeListener('newValue 4/7/25', myHandler)
// Emit on your own emitter — other peers can listen for this
peers.peerEvents().emit('myCustomEvent', 'arg1', 'arg2')
// emit() returns true if the call was actually delivered
var ok = peer('supervision').emit('hello', 'world')
Common event patterns used by the core modules:
| Event | Emitted by | Description |
|---|---|---|
newValue <addressKey> |
gateways |
Address value changed |
gatewayState <gatewayId> |
gateways |
Gateway connection state changed |
addressInserted / addressUpdated / addressDeleted |
gateways |
Address lifecycle |
gatewayInserted / gatewayUpdated / gatewayDeleted |
gateways |
Gateway lifecycle |
routeStatus |
gateways |
Route status changed |
loggerInserted / loggerUpdated / loggerDeleted |
loggers |
Logger lifecycle |
Every event emitted on the main mesh is automatically forwarded to every peer that has called .on(eventName) on it — there is no explicit subscription step.
The peers client itself emits events about its own connections:
peers.on('connect', function(peerName){ ... })
peers.on('connect supervision', function(){ ... }) // shortcut for a specific peer
peers.on('disconnect', function(peerName){ ... })
peers.on('disconnect supervision', function(){ ... })
peers.on('error', function(peerName, err){ ... })
Use the 'connect supervision' shortcut to know when the gateway is reachable for the first time — it is the safest place to start require'ing modules:
peers.connect('/tmp/supervision.sock')
peers.once('connect supervision', function(){
peers('supervision').require('gateways', function(err, gateways){
// ...
})
})
This socket path is for processes running on the gateway itself. External clients connect as described in section 2.
peers.connectedPeers() // → ['supervision', 'driver_modbus_1', 'webserver', ...]
peer('supervision').isConnected() // → true / false
peers.listenerCount() // total active listeners — useful for leak detection
Arguments and return values are serialized automatically. In addition to plain JSON values, you can pass:
Buffer instances — round-trip as binary on both sides.Error instances — reconstructed with message and stack preserved.Date objects.
Long-running peers clients almost always leak through forgotten event listeners. The best practice is:
on() with removeListener(). If your code path adds a listener, the matching cleanup path must remove it.once() when you only need a single notification.peers.listenerCount() in long-running services. A growing number is a leak.'connect supervision' — otherwise reconnects multiply your handlers.function attach(){
peer('supervision').require('gateways', function(err, gateways){
gateways.on('newValue 4/7/25', onValue)
})
}
function detach(){
peer('supervision').require('gateways', function(err, gateways){
gateways.removeListener('newValue 4/7/25', onValue)
})
}
peers.on('connect supervision', attach)
peers.on('disconnect supervision', detach)