A Node process can answer and dial PeerJS browser peers with node-datachannel and the PeerServer protocol written by hand
Fact
A server can be a PeerJS data peer without the PeerJS client or a browser: open the PeerServer WebSocket itself and answer offers with node-datachannel, which ships prebuilt binaries as per-platform optional dependencies (no compiler, no install script). Browsers using PeerJS 1.5 cannot tell it apart from another browser, and Chrome's default mDNS-hidden host candidates still connect.
Why it matters
It lets an always-on machine (a home server, a bot, a store-and-forward service) join a peer-to-peer app under a fixed id, with no open ports, tunnel or HTTPS certificate, reusing the app's signaling and TURN servers.
How to apply
- Connect
wss://host:port/<path>peerjs?key=peerjs&id=<id>&token=<random>(the cloud is0.peerjs.com:443, path/). Send{"type":"HEARTBEAT"}every 5 s.OPENmeans registered;ID-TAKENmeans try again later. A dial arrives as
OFFERwithsrcandpayload: { sdp: { type, sdp }, type: "data", connectionId, label, serialization, reliable }. Createnew PeerConnection(connectionId, { iceServers }), callsetRemoteDescription(payload.sdp.sdp, "offer"), and send what the callbacks produce, withdst: src:onLocalDescription(sdp, type)→ANSWER,payload: { sdp: { type, sdp }, type: "data", connectionId }onLocalCandidate(candidate, mid)→CANDIDATE,payload: { candidate: { candidate, sdpMid: mid, sdpMLineIndex: 0 }, type: "data", connectionId }
- Feed incoming
CANDIDATEpayloads toaddRemoteCandidate(c.candidate, c.sdpMid). The browser opened the channel; it arrives inonDataChannel. - To dial a browser instead, register under any random id, then
createDataChannel(connectionId)(making the channel starts the offer) and sendonLocalDescriptionasOFFER,payload: { sdp: { type, sdp }, type: "data", connectionId, label: connectionId, serialization: "raw", reliable: true }. The browser'speer.on('connection')fires as for any PeerJS caller; itsANSWERgoes tosetRemoteDescription(sdp.sdp, "answer"), andEXPIREmeans nobody holds that id. - With
serialization: "raw"the channel carries the page's strings as they are; chunk long ones yourself, as in the browser.