systemquery/src/lib/agent/Peer.mjs

165 lines
4.4 KiB
JavaScript

"use strict";
import { EventEmitter, once } from 'events';
import log from '../io/NamespacedLog.mjs'; const l = log("peer");
import Connection from '../transport/Connection.mjs';
class Peer extends EventEmitter {
get address() { return this.connection.address; }
get port() { return this.connection.port; }
get remote_endpoint() {
return { address: this.address, port: this.port };
}
get id_short() {
if(this.id === null) return null;
return this.id.substring(0, 7);
}
get info() {
return {
address: this.address,
port: this.port,
id: this.id,
listening_address: this.listening_address,
listening_port: this.listening_port
};
}
constructor(server, connection) {
super();
this.id = null;
/**
* The parent server this Peer is part of.
* @type {PeerServer}
*/
this.server = server;
/**
* The underlying Connection.
* @type {Connection}
*/
this.connection = connection;
// If a message with the event name "end" is received, close our side
// of the connection
this.once(`message-end`, this.destroy);
}
///////////////////////////////////////////////////////////////////////////
/**
* Accepts an existing connection as a new Peer.
* @param {Connection} connection The Connection to accept.
* @return {Promise<HelloMsg>} A Promise that resolves once the initial handshake is complete.
*/
async __accept(connection) {
this.connection = connection;
const [ msg ] = await once(this.connection, "message-hello");
if(!this.__handle_hello(msg))
await this.destroy();
await this.__send_hello();
this.emit("connect");
}
/**
* Initiates the handshake after opening a new connection.
* @return {Promise<HelloMsg>} A Promise that resolves after the initial peer handshake is complete.
*/
__initiate() {
return new Promise((resolve, reject) => {
this.connection.once("error", reject);
this.connection.once("message-hello", async (msg) => {
if(!this.__handle_hello(msg))
await this.destroy();
this.connection.off("error", reject);
this.emit("connect");
resolve();
});
this.__send_hello(); // Set and forget
});
}
/**
* Handles a given hello messaage from this peer.
* @param {Object} msg The hello message to process.
* @return {boolean} Whether the peer should stay connected or not.
*/
__handle_hello(msg) {
this.id = msg.id;
this.listening_address = msg.listening_address;
this.listening_port = msg.listening_port;
if(this.id === this.server.our_id) {
l.warn(`Our id (${this.server.our_id}) is equal to that of the remote (${this.id}), killing connection`);
return false;
}
this.connection.on("message", this.__handle_message.bind(this));
return true;
}
__handle_message(event_name, msg) {
this.emit("message", event_name, msg);
this.emit(`message-${event_name}`, msg);
}
/**
* Sends a hello message to this peer.
* @return {Promise} A Promise that resolves when the sending of the message is complete.
*/
async __send_hello() {
await this.send("hello", {
id: this.server.our_id,
listening_address: this.server.host,
listening_port: this.server.port
});
}
/**
* Sends a message to this peer.
* @param {string} event_name The event name to attach to the message.
* @param {Object} msg The message to send. Can be of any type, but an Object is recommended for increased flexibility later down the line.
* @return {Promise} A Promise that resolves whent he message has been sent.
*/
async send(event_name, msg) {
await this.connection.send(event_name, msg);
}
///////////////////////////////////////////////////////////////////////////
/**
* Closes the connection to this peer.
* Once called, in order to reinstate the connection it's best to
* instantiate a new Peer class instance.
* @return {Promise} A Promise that resolves once the connection is closed.
*/
async destroy() {
// TODO: If sending an end message takes too long, we should just ignore it and move on
await this.send("end", "goodbye");
await this.connection.destroy();
this.emit("destroy");
this.removeAllListeners();
}
}
Peer.Initiate = async function(server, address, port) {
const conn = await Connection.Create(server.secret_join, address, port);
const peer = new Peer(server, conn);
await peer.__initiate();
return peer;
}
Peer.Accept = async function(server, connection) {
const peer = new Peer(server);
await peer.__accept(connection);
return peer;
}
export default Peer;