@lobstar/core
Version:
A simple network and lobby manager for multiplayer web games
195 lines (194 loc) • 6.79 kB
JavaScript
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;
}
}
}