UNPKG

@lobstar/core

Version:

A simple network and lobby manager for multiplayer web games

195 lines (194 loc) 6.79 kB
import Peer from "peerjs"; import mitt from "mitt"; export class NetworkManager { constructor({ debug, peerOptions } = {}) { this.debug = debug || false; this.peer = null; this.peerOptions = peerOptions || {}; this.connections = new Map(); // Map of peerId -> connection this.emitter = mitt(); } /** * Initialize the network manager, optionally with a specific peer ID. * If no ID is provided, PeerJS will generate one. * @param {string} [peerId] - Optional identifier for this peer */ async initialize(peerId) { // If peerId is provided, use it; otherwise, let PeerJS generate one by passing options first. this.peer = peerId ? new Peer(peerId, this.peerOptions) : new Peer(this.peerOptions); // PeerJS assigns the ID const actualId = this.peer.id; this.log(`Initializing PeerJS${peerId ? ` with requested ID: ${peerId}` : ""}. Actual ID: ${actualId}`); return new Promise((resolve, reject) => { this.peer.on("open", () => { this.log("PeerJS connection to signaling server opened successfully."); this.setupEventListeners(); resolve(); }); this.peer.on("error", (error) => { console.error("PeerJS main error:", error); this.emitter.emit("error", { error, context: "peer_initialization" }); reject(error); }); }); } log(message) { if (this.debug) { console.log(`[NetworkManager] ${message}`); } } /** * Set up event listeners for the peer */ setupEventListeners() { // Handle incoming connections this.peer.on("connection", (connection) => { this.log(`Incoming connection request from ${connection.peer}`); this.handleNewConnection(connection); }); // Handle disconnections this.peer.on("disconnected", () => { console.warn("PeerJS disconnected from signaling server. Attempting to reconnect..."); this.peer.reconnect(); }); // Handle underlying socket close this.peer.on("close", () => { console.error("PeerJS connection permanently closed. Cannot reconnect."); }); } /** * Connect to another peer * @param {string} peerId - ID of the peer to connect to * @param {unknown} [metadata] - Optional metadata to send with connection */ connectToPeer(peerId, metadata) { this.log(`Connecting to peer ${peerId}`); const connection = this.peer.connect(peerId, { metadata }); this.handleNewConnection(connection); } /** * Handle a new connection (both incoming and outgoing) * @param {DataConnection} connection - The PeerJS connection object */ handleNewConnection(connection) { const metadata = connection.metadata; connection.on("open", () => { this.log(`Connection established with ${connection.peer}`); this.connections.set(connection.peer, connection); // Emit connection event this.emitter.emit("connection", { peerId: connection.peer, metadata, }); // Set up message handler for this connection connection.on("data", (data) => { this.emitter.emit("message", { data, peerId: connection.peer, }); }); connection.on("close", () => { this.log(`Connection closed with ${connection.peer}`); this.connections.delete(connection.peer); this.emitter.emit("disconnection", { peerId: connection.peer, }); }); connection.on("error", (error) => { console.error(`Connection error with ${connection.peer}:`, error); this.emitter.emit("error", { error, context: `connection_${connection.peer}`, }); }); }); } /** * Broadcast a message to all connected peers * @param {unknown} data - The data to broadcast */ broadcast(data, excludeSelf = true) { this.connections.forEach((connection) => { if (excludeSelf && connection.peer === this.peer.id) { return; } connection.send(data); }); } /** * Send a message to a specific peer * @param {string} peerId - ID of the target peer * @param {unknown} data - The data to send * @returns {boolean} Whether the message was sent successfully */ sendToPeer(peerId, data) { const connection = this.connections.get(peerId); if (connection) { connection.send(data); return true; } return false; } /** * Register an event listener * @param {K} event - Event name to listen for * @param {function} handler - Event handler function */ on(event, handler) { this.emitter.on(event, handler); } /** * Remove an event listener * @param {K} event - Event name to remove listener from * @param {function} handler - Event handler to remove */ off(event, handler) { this.emitter.off(event, handler); } /** * Get the current peer ID * @returns {string} The peer ID, or null if not initialized */ getPeerId() { return this.peer ? this.peer.id : null; } /** * Get all connected peer IDs * @returns {string[]} Array of connected peer IDs */ getConnectedPeers() { return Array.from(this.connections.keys()); } /** * Disconnect a specific peer connection. * @param {string} peerId - ID of the peer to disconnect * @returns {boolean} Whether a connection was found and closed */ disconnectPeer(peerId) { const connection = this.connections.get(peerId); if (connection) { this.log(`Closing connection with peer ${peerId}`); connection.close(); this.connections.delete(peerId); return true; } else { this.log(`Cannot disconnect peer ${peerId}: No active connection found.`); return false; } } /** * Clean up resources */ destroy() { this.connections.forEach((connection) => connection.close()); this.connections.clear(); this.emitter.all.clear(); if (this.peer) { this.peer.destroy(); this.peer = null; } } }