holesail-server
Version:
Proxy any server peer-to-peer using Holepunching.
189 lines (168 loc) • 4.84 kB
JavaScript
// Importing required modules
const HyperDHT = require('hyperdht') // HyperDHT module for DHT functionality
const net = require('net') // Node.js net module for creating network clients and servers
const libNet = require('@holesail/hyper-cmd-lib-net') // Custom network library
const libKeys = require('hyper-cmd-lib-keys') // To generate a random preSeed for server seed.
const b4a = require('b4a')
const z32 = require('z32')
class HolesailServer {
constructor () {
this.dht = new HyperDHT()
this.stats = {}
this.server = null
this.keyPair = null
this.seed = null
this.state = null
this.connection = null
}
generateKeyPair (seed) {
// Allows us to keep the same keyPair everytime.
if (!seed) {
seed = libKeys.randomBytes(32).toString('hex')
}
// generate a seed from the buffer key
this.seed = Buffer.from(seed, 'hex')
// generate a keypair from the seed
this.keyPair = HyperDHT.keyPair(this.seed)
return this.keyPair
}
// start the client on port and the address specified
async start (args, callback) {
this.args = args
this.secure = args.secure === true
// generate the keypair
this.generateKeyPair(args.seed)
// this is needed for the secure mode to work and is implemented by HyperDHT
let privateFirewall = false
if (this.secure) {
privateFirewall = (remotePublicKey) => {
return !b4a.equals(remotePublicKey, this.keyPair.publicKey)
}
}
this.server = this.dht.createServer(
{
firewall: privateFirewall,
reusableSocket: true
},
(c) => {
if (!args.udp) {
this.handleTCP(c, args)
} else {
this.handleUDP(c, args)
}
}
)
// start listening on the keyPair
this.server.listen(this.keyPair).then(() => {
this.state = 'listening'
if (typeof callback === 'function') {
callback() // Invoke the callback after the server has started
}
})
// put host information on the dht
await this.put(
JSON.stringify({
host: this.args.host,
udp: this.args.udp,
port: this.args.port
})
)
}
// Handle TCP connections
handleTCP (c, args) {
// Connection handling using custom connection piper function
this.connection = libNet.connPiper(
c,
() => {
return net.connect({
port: +args.port,
host: args.address,
allowHalfOpen: true
})
},
{ isServer: true, compress: false },
this.stats
)
}
// Handle UDP connections
handleUDP (c, args) {
this.connection = libNet.udpPiper(
c,
() => {
return libNet.udpConnect({
port: +args.port,
host: args.address
})
},
{ isServer: true, compress: false },
this.stats
)
}
// Return the public/connection key
get key () {
if (this.secure) {
return z32.encode(this.seed)
} else {
return z32.encode(this.keyPair.publicKey)
}
}
// resume functionality
async resume () {
await this.dht.resume()
this.state = 'listening'
}
async pause () {
await this.dht.suspend()
this.state = 'paused'
}
// destroy the dht instance and free up resources
async destroy () {
if (this.dht) await this.dht.destroy()
this.dht = null
if (this.server) this.server = null
if (this.connection) this.connection = null
this.state = 'destroyed'
}
// put a mutable record on the dht, can be retrieved by any client using the keypair, max limit is 1KB
async put (data, opts = {}) {
data = b4a.isBuffer(data) ? data : Buffer.from(data)
if (opts.seq) {
await this.dht.mutablePut(this.keyPair, data, opts)
return opts.seq
}
const oldRecord = await this.get({ latest: true })
if (!oldRecord) {
const { seq } = await this.dht.mutablePut(this.keyPair, data, opts)
return seq
} else if (oldRecord.value === b4a.toString(data)) {
return oldRecord.seq
} else {
opts.seq = oldRecord.seq + 1
await this.dht.mutablePut(this.keyPair, data, opts)
return opts.seq
}
}
// get mutable record from dht
async get (opts = {}) {
const record = await this.dht.mutableGet(this.keyPair.publicKey, opts)
if (record) {
return { seq: record.seq, value: b4a.toString(record.value) }
}
return null
}
// return information about the server
get info () {
return {
type: 'server',
state: this.state,
secure: this.secure,
port: this.args.port,
host: this.args.host,
protocol: this.args.udp ? 'udp' : 'tcp',
seed: this.args.seed,
key: this.key,
publicKey: z32.encode(this.keyPair.publicKey)
}
}
}
module.exports = HolesailServer