mirror of
https://github.com/clusterio/clusterio.git
synced 2026-11-12 23:04:52 +09:00
230 lines
5.4 KiB
JavaScript
230 lines
5.4 KiB
JavaScript
"use strict";
|
|
const events = require("events");
|
|
const io = require("socket.io-client");
|
|
|
|
const schema = require("lib/schema");
|
|
|
|
|
|
/**
|
|
* Connector for master server clients
|
|
*/
|
|
class SocketIOClientConnector extends events.EventEmitter {
|
|
constructor(url, token) {
|
|
super();
|
|
|
|
this._url = url;
|
|
this._token = token;
|
|
|
|
this._seq = 1;
|
|
this._state = "new";
|
|
}
|
|
|
|
_check(expectedStates) {
|
|
if (!expectedStates.includes(this._state)) {
|
|
throw new Error(
|
|
`Expected state ${expectedStates} but state is ${this._state}`
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Send a message over the socket
|
|
*
|
|
* @returns the sequence number of the message sent
|
|
*/
|
|
send(type, data) {
|
|
this._socket.send({ seq: this._seq, type, data });
|
|
return this._seq++;
|
|
}
|
|
|
|
/**
|
|
* Close the connection with the given reason.
|
|
*
|
|
* Sends a close message and disconnects the connector.
|
|
*/
|
|
close(reason) {
|
|
this.send('close', { reason });
|
|
this.disconnect();
|
|
}
|
|
|
|
/**
|
|
* Connect to the master server
|
|
*/
|
|
async connect() {
|
|
this._check(["new"]);
|
|
this._state = "handshake";
|
|
|
|
// Open socket.io connection to master
|
|
console.log(`SOCKET | connecting to ${this._url}`)
|
|
let url = new URL(this._url);
|
|
url.searchParams.append('token', this._token);
|
|
|
|
// I really have to question the choice of having the path connected
|
|
// to be passed as separate parameter instead of the namespace.
|
|
let urlPath = url.pathname + "socket.io";
|
|
url.pathname = "";
|
|
this._socket = io(url.href, {
|
|
path: urlPath,
|
|
reconnectionAttempts: 2,
|
|
|
|
// For now we do not verify TLS certificates since the default setup is
|
|
// to create a self-signed certificate.
|
|
rejectUnauthorized: false,
|
|
});
|
|
|
|
this._attachSocketHandlers();
|
|
|
|
// Wait for the connection to be ready
|
|
await events.once(this, 'ready');
|
|
}
|
|
|
|
_attachSocketHandlers() {
|
|
this._socket.on("error", err => {
|
|
console.error("SOCKET | Error:", err);
|
|
this._socket.close();
|
|
});
|
|
|
|
/*this._socket.on("connect_error", err => {
|
|
console.log("SOCKET | Connect error:", err);
|
|
});*/
|
|
this._socket.on("connect_timeout", () => {
|
|
console.log("SOCKET | Connect timeout");
|
|
});
|
|
this._socket.on("reconnect", attempt => {
|
|
console.log(`SOCKET | Reconnect no. ${attempt} succeeded`);
|
|
});
|
|
this._socket.on("reconnect_attempt", () => {
|
|
console.log("SOCKET | Attempting reconnect");
|
|
});
|
|
this._socket.on("reconnecting", attempt => {
|
|
console.log(`SOCKET | Attempting reconnect no. ${attempt}`);
|
|
});
|
|
/*this._socket.on("reconnect_error", err => {
|
|
console.log("SOCKET | Reconnect error:", err);
|
|
});*/
|
|
this._socket.on("reconnect_failed", () => {
|
|
console.log("SOCKET | Reconnecting failed");
|
|
});
|
|
/*this._socket.on("ping", () => {
|
|
console.log("SOCKET | Ping");
|
|
});
|
|
this._socket.on("pong", (ms) => {
|
|
// XXX Latency might be useful
|
|
console.log(`SOCKET | Pong latency ${ms}ms`);
|
|
});*/
|
|
this._socket.on("connect", () => {
|
|
console.log("SOCKET | connected");
|
|
});
|
|
|
|
// Handle messages
|
|
this._socket.on("message", message => {
|
|
if (this._state === "handshake") {
|
|
this._processHandshake(message);
|
|
} else if (this._state === "ready") {
|
|
this.emit("message", message);
|
|
} else {
|
|
throw new Error(`Received message in unexpected state ${this._state}`);
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Immediatly close the connection
|
|
*/
|
|
disconnect() {
|
|
this._socket.close();
|
|
}
|
|
|
|
_processHandshake(message) {
|
|
if (!schema.serverHandshake(message)) {
|
|
console.log("SOCKET | closing after received invalid handshake:", message);
|
|
this.close("Invalid handshake");
|
|
return;
|
|
}
|
|
|
|
let { seq, type, data } = message;
|
|
if (type == 'hello') {
|
|
console.log(`SOCKET | received hello from master version ${data.version}`);
|
|
this.register();
|
|
|
|
} else if (type === 'ready') {
|
|
console.log("SOCKET | received ready from master");
|
|
this._state = "ready";
|
|
this.emit('ready');
|
|
|
|
} else if (type === 'close') {
|
|
console.log(`SOCKET | received close from server: ${data.reason}`);
|
|
this.emit('error', new Error(`server closed during handshake: ${data.reason}`));
|
|
this._socket.close();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Register the link with the server
|
|
*
|
|
* This function is expected to be overriden by sub-classes and send the
|
|
* register message over the socket when invoked.
|
|
*/
|
|
register() {
|
|
throw Error("Abstract function");
|
|
}
|
|
}
|
|
|
|
class VirtualConnector extends events.EventEmitter {
|
|
constructor() {
|
|
super();
|
|
|
|
this.other = this;
|
|
this._seq = 1;
|
|
}
|
|
|
|
/**
|
|
* Create a pair of virtual connector hooked into each other
|
|
*
|
|
* Creates two virtual connectors that are liked to each othes such that
|
|
* a message sent on one is received by the other.
|
|
*
|
|
* @returns {Array} two virtuarl connectors.
|
|
*/
|
|
static makePair() {
|
|
let first = new this();
|
|
let second = new this();
|
|
first.other = second;
|
|
second.other = first;
|
|
return [first, second];
|
|
}
|
|
|
|
/**
|
|
* Send a message to the other end of the connector
|
|
*
|
|
* @returns the sequence number of the message sent
|
|
*/
|
|
send(type, data) {
|
|
this.other.emit('message', { seq: this._seq, type, data });
|
|
return this._seq++;
|
|
}
|
|
|
|
/**
|
|
* Close the connection with the given reason.
|
|
*
|
|
* Sends a close message to the other end of the connector with the
|
|
* given reason. Doesn't actually disconnect as virtual connectors
|
|
* doen't have a concept of disconnecting.
|
|
*/
|
|
close(reason) {
|
|
this.send('close', { reason });
|
|
}
|
|
|
|
/**
|
|
* Does nothing
|
|
*
|
|
* Virtual connectors do not have a concept of disconnecting.
|
|
*/
|
|
disconnect() { }
|
|
}
|
|
|
|
module.exports = {
|
|
SocketIOClientConnector,
|
|
VirtualConnector,
|
|
};
|