"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); } 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; /** * A list of other peers known to this peer. * May or may not actually be up to date. * @type {{address:string,port:number}[]} */ this.known_peers = []; this.once("connect", () => { l.log(`${this.connection.address}:${this.connection.port} connected`); }); } /////////////////////////////////////////////////////////////////////////// /** * Accepts an existing connection as a new Peer. * @param {Connection} connection The Connection to accept. * @return {Promise} 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(); this.emit("connect"); } /** * Initiates the handshake after opening a new connection. * @return {Promise} A Promise that resolves after the initial peer handshake is complete. */ async __initiate() { this.connection.once("message-hello", async (msg) => { if(!this.__handle_hello(msg)) await this.destroy(); this.emit("connect"); }); await this.__send_hello(); } /** * 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) { l.debug(`handling hello`); this.id = msg.id; this.known_peers = msg.peers; 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; } return true; } /** * 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, peers: this.server.peers() }); } /** * 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() { 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;