You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

115 lines
3.4 KiB

import WebSocket from 'ws';
import net from 'net';
const DEFAULT_REMOTE_SERVER = 'wss://ingress.e4mc.link';
const DEFAULT_TCP_ADDRESS = 'localhost';
const DEFAULT_TCP_PORT = 25565;
interface DomainAssignedMessage {
DomainAssigned: string;
}
interface ChannelOpenMessage {
ChannelOpen: [number, string];
}
interface ChannelClosedMessage {
ChannelClosed: number;
}
type ServerMessage = DomainAssignedMessage | ChannelOpenMessage | ChannelClosedMessage;
class e4mcClient {
private tcpAddress: string;
private tcpPort: number;
private channels: Map<number, net.Socket>;
private pendingData: Map<number, Buffer[]>;
private ws: WebSocket;
private onDomainAssigned: ((domain: string) => void) | undefined;
constructor(onDomainAssigned?: (domain: string) => void, tcpPort?: number, tcpAddress?: string, remoteServer?: string) {
this.tcpAddress = tcpAddress ?? DEFAULT_TCP_ADDRESS;
this.tcpPort = tcpPort ?? DEFAULT_TCP_PORT;
this.channels = new Map();
this.pendingData = new Map();
this.onDomainAssigned = onDomainAssigned;
this.ws = new WebSocket(remoteServer ?? DEFAULT_REMOTE_SERVER);
this.ws.on('open', () => this.onOpen());
this.ws.on('message', (data: Buffer, isBinary: boolean) => this.onMessage(data, isBinary));
}
onOpen() {
console.log('Connected to the remote server.');
}
onMessage(data: Buffer, isBinary: boolean) {
if (!isBinary) {
const message: ServerMessage = JSON.parse(data.toString());
if ('DomainAssigned' in message) {
console.log('Domain assigned:', message.DomainAssigned);
if (this.onDomainAssigned) {
this.onDomainAssigned(message.DomainAssigned);
}
} else if ('ChannelOpen' in message) {
this.onChannelOpen(...message.ChannelOpen);
} else if ('ChannelClosed' in message) {
this.onChannelClosed(message.ChannelClosed);
}
} else {
this.onBinaryMessage(data);
}
}
onChannelOpen(channelId: number, clientInfo: string) {
console.log(`Opening channel ${channelId} for ${clientInfo}`);
const tcpSocket = net.createConnection({ host: this.tcpAddress, port: this.tcpPort }, () => {
console.log(`TCP connection established for channel ${channelId}`);
});
tcpSocket.on('data', (chunk) => {
const data = Buffer.concat([Buffer.from([channelId]), chunk]);
this.ws.send(data);
});
tcpSocket.on('close', () => {
console.log(`TCP connection closed for channel ${channelId}`);
this.ws.send(JSON.stringify({ ChannelClosed: channelId }));
this.channels.delete(channelId);
});
this.channels.set(channelId, tcpSocket);
if (this.pendingData.has(channelId)) {
this.pendingData.get(channelId)?.forEach((chunk) => {
tcpSocket.write(chunk);
});
this.pendingData.delete(channelId);
}
}
onChannelClosed(channelId: number) {
console.log(`Closing channel ${channelId}`);
if (this.channels.has(channelId)) {
this.channels.get(channelId)?.end();
this.channels.delete(channelId);
}
}
onBinaryMessage(data: Buffer) {
const channelId = data.readUInt8(0);
const payload = data.slice(1);
if (this.channels.has(channelId)) {
this.channels.get(channelId)?.write(payload);
} else {
if (!this.pendingData.has(channelId)) {
this.pendingData.set(channelId, []);
}
this.pendingData.get(channelId)?.push(payload);
}
}
}
export default e4mcClient;