@2003scape/rsc-client
Version:
runescape classic web client
239 lines (183 loc) • 6.56 kB
JavaScript
// a shim for java.net.Socket
// https://docs.oracle.com/javase/7/docs/api/java/net/Socket.html
require('buffer');
const WorkerSocket = require('./worker-socket');
const CLOSE_TIMEOUT = 5000;
class Socket {
constructor(host, port) {
this.host = host;
this.port = port;
this.client = null;
this.connected = false;
// amount of bytes are left to read since last read call (in total)
this.bytesAvailable = 0;
// the message buffers that arrive from the websocket
this.buffers = [];
// the current buffer we're reading
this.currentBuffer = null;
// amount of bytes we read in current buffer
this.offset = 0;
// amount of bytes left in current buffer
this.bytesLeft = 0;
}
connect() {
return new Promise((resolve, reject) => {
if (typeof this.host === 'string') {
this.client = new WebSocket(
`ws://${this.host}:${this.port}`,
'binary'
);
} else if (this.host.constructor.name === 'Worker') {
this.client = new WorkerSocket(this.host);
} else if (this.host.constructor.name === 'Peer') {
const peer = this.host;
const worker = {
onmessage() {},
postMessage(data) {
if (data.type === 'data') {
data.data = Buffer.from(data.data).toString(
'base64'
);
}
peer.send(JSON.stringify(data));
}
};
this.client = new WorkerSocket(worker);
peer.on('data', (data) => {
data = JSON.parse(data);
if (data.type === 'data') {
data.data = Buffer.from(data.data, 'base64');
}
worker.onmessage({ data });
});
peer.on('close', () => {
worker.onmessage({ data: { type: 'disconnect' } });
});
}
const closeTimeout = setTimeout(() => {
if (!this.connected) {
this.client.close();
reject(new Error('websocket connect timeout'));
}
}, CLOSE_TIMEOUT);
this.client.binaryType = 'arraybuffer';
const onError = (err) => {
if (this.onError) {
this.onError(err);
this.onError = undefined;
}
reject(err);
};
this.client.addEventListener('error', onError);
this.client.addEventListener('close', () => {
if (this.onClose) {
this.onClose(-1);
this.onClose = undefined;
}
this.connected = false;
this.clear();
});
this.client.addEventListener('message', (msg) => {
this.buffers.push(new Int8Array(msg.data));
this.bytesAvailable += msg.data.byteLength;
this.refreshCurrentBuffer();
if (this.onNextMessage) {
this.onNextMessage(msg.data.byteLength);
this.onNextMessage = undefined;
}
});
this.client.addEventListener('open', () => {
this.connected = true;
clearTimeout(closeTimeout);
resolve();
});
});
}
write(bytes, offset = 0, length = -1) {
if (!this.connected) {
throw new Error('attempting to write to closed socket');
}
length = length === -1 ? bytes.length : length;
this.client.send(bytes.slice(offset, offset + length));
}
refreshCurrentBuffer() {
if (this.bytesLeft === 0 && this.bytesAvailable > 0) {
this.currentBuffer = this.buffers.shift();
this.offset = 0;
if (this.currentBuffer && this.currentBuffer.length) {
this.bytesLeft = this.currentBuffer.length;
} else {
this.bytesLeft = 0;
}
}
}
// read the first byte available in the buffer, or wait for one to be sent
// if none are available.
async read() {
if (!this.connected) {
return -1;
}
if (this.bytesLeft > 0) {
this.bytesLeft--;
this.bytesAvailable--;
return this.currentBuffer[this.offset++] & 0xff;
}
const bytesRead = await new Promise((resolve, reject) => {
this.onClose = resolve;
this.onError = reject;
this.onNextMessage = resolve;
});
if (bytesRead === -1) {
return -1;
}
return await this.read();
}
// read multiple bytes (specified by `length`) and put them into the
// `destination` array at specified `offset` (0 by default).
async readBytes(destination, offset = 0, length = -1) {
if (!this.connected) {
return -1;
}
length = length === -1 ? destination.length : length;
if (this.bytesAvailable >= length) {
while (length > 0) {
destination[offset++] =
this.currentBuffer[this.offset++] & 0xff;
this.bytesLeft -= 1;
this.bytesAvailable -= 1;
length -= 1;
if (this.bytesLeft === 0) {
this.refreshCurrentBuffer();
}
}
return;
}
const bytesRead = await new Promise((resolve, reject) => {
this.onClose = resolve;
this.onError = reject;
this.onNextMessage = resolve;
});
if (bytesRead === -1) {
return -1;
}
return await this.readBytes(destination, offset, length);
}
close() {
if (!this.connected) {
return;
}
this.client.close();
}
available() {
return this.bytesAvailable;
}
clear() {
if (this.connected) {
this.client.close();
}
this.currentBuffer = null;
this.buffers.length = 0;
this.bytesLeft = 0;
}
}
module.exports = Socket;