feat: packets!
This commit is contained in:
parent
9a4bb4ce08
commit
6da4e21835
|
@ -1,36 +1,30 @@
|
|||
import {EventEmitter} from 'events';
|
||||
import io from 'socket.io-client';
|
||||
|
||||
const exceptions = [
|
||||
'connect',
|
||||
];
|
||||
import {SocketIoParser, allPackets, idFromPacket} from '@avocado/packet';
|
||||
|
||||
class SocketClient extends EventEmitter {
|
||||
|
||||
constructor(address) {
|
||||
super();
|
||||
this.socket = io(address, {
|
||||
parser: SocketIoParser,
|
||||
path: '/avocado',
|
||||
});
|
||||
this.socket.on('connect', () => {
|
||||
this.emit('connect');
|
||||
});
|
||||
this.socket.on('message', (...args) => {
|
||||
this.emit('message', ...args);
|
||||
for (const Packet of allPackets()) {
|
||||
const id = idFromPacket(Packet);
|
||||
this.socket.on(id, (packet) => {
|
||||
this.emit('packet', packet);
|
||||
});
|
||||
}
|
||||
|
||||
on(name, fn) {
|
||||
if (-1 === exceptions.indexOf(name)) {
|
||||
super.on(name, fn);
|
||||
}
|
||||
else {
|
||||
this.socket.on(name, fn);
|
||||
}
|
||||
}
|
||||
|
||||
send(...args) {
|
||||
this.socket.send(...args);
|
||||
send(packet) {
|
||||
const id = idFromPacket(packet.constructor);
|
||||
this.socket.emit(id, packet.data);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
24
packages/packet/index.js
Normal file
24
packages/packet/index.js
Normal file
|
@ -0,0 +1,24 @@
|
|||
export class Packet {
|
||||
|
||||
constructor(data) {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
static get schema() {
|
||||
return {
|
||||
_id: 'uint8',
|
||||
nsp: 'string',
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export {
|
||||
allPackets,
|
||||
idFromPacket,
|
||||
packetFromId,
|
||||
registerPacket,
|
||||
} from './registry';
|
||||
|
||||
import * as SocketIoParser from './socket.io-parser';
|
||||
export {SocketIoParser};
|
12
packages/packet/package.json
Normal file
12
packages/packet/package.json
Normal file
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"name": "@avocado/packet",
|
||||
"version": "1.0.0",
|
||||
"main": "index.js",
|
||||
"author": "cha0s",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@avocado/core": "1.x",
|
||||
"@avocado/mixins": "1.x",
|
||||
"schemapack": "1.4.2"
|
||||
}
|
||||
}
|
21
packages/packet/registry.js
Normal file
21
packages/packet/registry.js
Normal file
|
@ -0,0 +1,21 @@
|
|||
let eventId = 0;
|
||||
const packetToIdMap = new Map();
|
||||
const idToPacketMap = new Map();
|
||||
export function allPackets() {
|
||||
return Array.from(idToPacketMap.values());
|
||||
}
|
||||
export function idFromPacket(Packet) {
|
||||
return packetToIdMap.get(Packet);
|
||||
}
|
||||
export function packetFromId(id) {
|
||||
return idToPacketMap.get(id);
|
||||
}
|
||||
export function registerPacket(Packet) {
|
||||
if (packetToIdMap.has(Packet)) {
|
||||
return;
|
||||
}
|
||||
const id = eventId++;
|
||||
packetToIdMap.set(Packet, id);
|
||||
idToPacketMap.set(id, Packet);
|
||||
return id;
|
||||
}
|
109
packages/packet/socket.io-parser.js
Normal file
109
packages/packet/socket.io-parser.js
Normal file
|
@ -0,0 +1,109 @@
|
|||
import schemapack from 'schemapack';
|
||||
|
||||
import {compose} from '@avocado/core';
|
||||
import {EventEmitter} from '@avocado/mixins';
|
||||
|
||||
import {allPackets, idFromPacket, packetFromId} from './registry';
|
||||
|
||||
/**
|
||||
* Packet types (see https://github.com/socketio/socket.io-protocol)
|
||||
*/
|
||||
const TYPES = {
|
||||
EVENT: 2,
|
||||
ERROR: 4,
|
||||
BINARY_EVENT: 5,
|
||||
};
|
||||
|
||||
const errorPacket = {
|
||||
type: TYPES.ERROR,
|
||||
data: 'parser error',
|
||||
};
|
||||
|
||||
let schemas = undefined;
|
||||
function schemaFromId(id) {
|
||||
if (!schemas) {
|
||||
schemas = {};
|
||||
for (const Packet of allPackets()) {
|
||||
const id_ = idFromPacket(Packet);
|
||||
schemas[id_] = schemapack.build(Packet.schema);
|
||||
}
|
||||
}
|
||||
return schemas[id];
|
||||
}
|
||||
|
||||
class Encoder {
|
||||
|
||||
encode(packet, callback) {
|
||||
switch (packet.type) {
|
||||
case TYPES.EVENT:
|
||||
case TYPES.BINARY_EVENT:
|
||||
return callback([this.pack(packet)]);
|
||||
default:
|
||||
return callback([JSON.stringify(packet)]);
|
||||
}
|
||||
}
|
||||
|
||||
pack(packet) {
|
||||
const eventId = packet.data[0];
|
||||
const schema = schemaFromId(eventId);
|
||||
if (!schema) {
|
||||
throw new Error('unknown schema with id: ' + eventId);
|
||||
}
|
||||
return schema.encode({
|
||||
_id: eventId,
|
||||
data: packet.data[1],
|
||||
nsp: packet.nsp,
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const decorateDecoder = compose(
|
||||
EventEmitter,
|
||||
);
|
||||
|
||||
class Decoder extends decorateDecoder(class {}) {
|
||||
|
||||
add(obj) {
|
||||
if (typeof obj === 'string') {
|
||||
this.parseJSON(obj);
|
||||
}
|
||||
else {
|
||||
this.parseBinary(obj);
|
||||
}
|
||||
}
|
||||
|
||||
destroy() {}
|
||||
|
||||
parseBinary(obj) {
|
||||
const view = new Uint8Array(obj);
|
||||
const packetId = view[0];
|
||||
try {
|
||||
const schema = schemaFromId(packetId);
|
||||
if (!schema) {
|
||||
throw new Error(`unknown schema with id: ${packetId}`);
|
||||
}
|
||||
const {data, nsp} = schema.decode(obj);
|
||||
const Packet = packetFromId(packetId);
|
||||
this.emit('decoded', {
|
||||
type: TYPES.EVENT,
|
||||
data: [packetId, new Packet(data)],
|
||||
nsp,
|
||||
});
|
||||
} catch (e) {
|
||||
this.emit('decoded', errorPacket);
|
||||
}
|
||||
}
|
||||
|
||||
parseJSON(obj) {
|
||||
try {
|
||||
this.emit('decoded', JSON.parse(obj));
|
||||
}
|
||||
catch (e) {
|
||||
this.emit('decoded', errorPacket);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export {Decoder, Encoder};
|
|
@ -1,21 +1,40 @@
|
|||
const {EventEmitter} = require('events');
|
||||
const SocketServer = require('socket.io');
|
||||
|
||||
import {SocketIoParser, allPackets, idFromPacket} from '@avocado/packet';
|
||||
|
||||
export class Server extends EventEmitter {
|
||||
|
||||
constructor(httpServer) {
|
||||
super();
|
||||
this.io = new SocketServer(httpServer, {
|
||||
parser: SocketIoParser,
|
||||
path: '/avocado',
|
||||
serveClient: false,
|
||||
});
|
||||
this.io.on('connect', (socket) => {
|
||||
this.emit('connect', socket);
|
||||
this.emit('connect', new ServerSocket(socket));
|
||||
});
|
||||
}
|
||||
|
||||
broadcast(message) {
|
||||
this.io.send(message);
|
||||
}
|
||||
|
||||
class ServerSocket extends EventEmitter {
|
||||
|
||||
constructor(socket) {
|
||||
super();
|
||||
this.socket = socket;
|
||||
for (const Packet of allPackets()) {
|
||||
const id = idFromPacket(Packet);
|
||||
this.socket.on(id, (packet) => {
|
||||
this.emit('packet', packet);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
send(packet) {
|
||||
const id = idFromPacket(packet.constructor);
|
||||
this.socket.emit(id, packet.data);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -49,61 +49,60 @@ export class Packer {
|
|||
packSteps(steps) {
|
||||
const preparedSteps = this.prepareSteps(steps);
|
||||
const messageLength = this.computeMessageLength(preparedSteps);
|
||||
const packedSteps = new ArrayBuffer(messageLength);
|
||||
const view = new DataView(packedSteps);
|
||||
const packedSteps = Buffer.allocUnsafe(messageLength);
|
||||
let caret = 0;
|
||||
// Number of steps.
|
||||
view.setUint32(caret, preparedSteps.length);
|
||||
packedSteps.writeUInt32LE(preparedSteps.length, caret)
|
||||
caret += 4;
|
||||
preparedSteps.forEach(({op, path, value}) => {
|
||||
// Op.
|
||||
view.setUint8(caret, op);
|
||||
packedSteps.writeUInt8(op, caret)
|
||||
caret += 1;
|
||||
// Path.
|
||||
view.setUint32(caret, path);
|
||||
packedSteps.writeUInt32LE(path, caret)
|
||||
caret += 4;
|
||||
if (PACKER_STEP_OP_REMOVE === op) {
|
||||
return;
|
||||
}
|
||||
// Value.
|
||||
view.setUint8(caret, value.type);
|
||||
packedSteps.writeUInt8(value.type, caret)
|
||||
caret += 1;
|
||||
switch (value.type) {
|
||||
case PACKER_STEP_VALUE_BYTE:
|
||||
view.setInt8(caret, value.data);
|
||||
packedSteps.writeInt8(value.data, caret)
|
||||
caret += 1;
|
||||
break;
|
||||
case PACKER_STEP_VALUE_UBYTE:
|
||||
view.setUint8(caret, value.data);
|
||||
packedSteps.writeUInt8(value.data, caret)
|
||||
caret += 1;
|
||||
break;
|
||||
case PACKER_STEP_VALUE_SHORT:
|
||||
view.setInt16(caret, value.data);
|
||||
packedSteps.writeInt16LE(value.data, caret)
|
||||
caret += 2;
|
||||
break;
|
||||
case PACKER_STEP_VALUE_USHORT:
|
||||
view.setUint16(caret, value.data);
|
||||
packedSteps.writeUInt16LE(value.data, caret)
|
||||
caret += 2;
|
||||
break;
|
||||
case PACKER_STEP_VALUE_INT:
|
||||
view.setInt32(caret, value.data);
|
||||
packedSteps.writeInt32LE(value.data, caret)
|
||||
caret += 4;
|
||||
break;
|
||||
case PACKER_STEP_VALUE_UINT:
|
||||
view.setUint32(caret, value.data);
|
||||
packedSteps.writeUInt32LE(value.data, caret)
|
||||
caret += 4;
|
||||
break;
|
||||
case PACKER_STEP_VALUE_FLOAT:
|
||||
view.setFloat32(caret, value.data);
|
||||
packedSteps.writeFloatLE(value.data, caret);
|
||||
caret += 4;
|
||||
break;
|
||||
case PACKER_STEP_VALUE_STRING:
|
||||
case PACKER_STEP_VALUE_JSON:
|
||||
const stringLength = value.data.length;
|
||||
view.setUint32(caret, stringLength);
|
||||
packedSteps.writeUInt32LE(stringLength, caret)
|
||||
caret += 4;
|
||||
for (let i = 0; i < stringLength; ++i) {
|
||||
view.setUint8(caret, value.data.charCodeAt(i));
|
||||
packedSteps.writeUInt8(value.data.charCodeAt(i), caret)
|
||||
caret += 1;
|
||||
}
|
||||
break;
|
||||
|
@ -222,15 +221,14 @@ export class Unpacker {
|
|||
}
|
||||
|
||||
unpack(packedSteps) {
|
||||
const view = new DataView(packedSteps);
|
||||
let caret = 0;
|
||||
const numberOfSteps = view.getUint32(caret);
|
||||
const numberOfSteps = packedSteps.readUInt32LE(caret);
|
||||
caret += 4;
|
||||
const steps = new Array(numberOfSteps);
|
||||
for (let i = 0; i < numberOfSteps; ++i) {
|
||||
const packedOp = view.getUint8(caret);
|
||||
const packedOp = packedSteps.readUInt8(caret);
|
||||
caret += 1;
|
||||
const packedPath = view.getUint32(caret);
|
||||
const packedPath = packedSteps.readUInt32LE(caret);
|
||||
caret += 4;
|
||||
steps[i] = {
|
||||
op: this.constructor.unpackOp(packedOp),
|
||||
|
@ -239,45 +237,45 @@ export class Unpacker {
|
|||
if (PACKER_STEP_OP_REMOVE === packedOp) {
|
||||
continue;
|
||||
}
|
||||
const valueType = view.getUint8(caret);
|
||||
const valueType = packedSteps.readUInt8(caret);
|
||||
caret += 1;
|
||||
let value;
|
||||
switch (valueType) {
|
||||
case PACKER_STEP_VALUE_BYTE:
|
||||
value = view.getInt8(caret);
|
||||
value = packedSteps.readInt8(caret);
|
||||
caret += 1;
|
||||
break;
|
||||
case PACKER_STEP_VALUE_UBYTE:
|
||||
value = view.getUint8(caret);
|
||||
value = packedSteps.readUInt8(caret);
|
||||
caret += 1;
|
||||
break;
|
||||
case PACKER_STEP_VALUE_SHORT:
|
||||
value = view.getInt16(caret);
|
||||
value = packedSteps.readInt16LE(caret);
|
||||
caret += 2;
|
||||
break;
|
||||
case PACKER_STEP_VALUE_USHORT:
|
||||
value = view.getUint16(caret);
|
||||
value = packedSteps.readUInt16LE(caret);
|
||||
caret += 2;
|
||||
break;
|
||||
case PACKER_STEP_VALUE_INT:
|
||||
value = view.getInt32(caret);
|
||||
value = packedSteps.readInt32LE(caret);
|
||||
caret += 4;
|
||||
break;
|
||||
case PACKER_STEP_VALUE_UINT:
|
||||
value = view.getUint32(caret);
|
||||
value = packedSteps.readUInt32LE(caret);
|
||||
caret += 4;
|
||||
break;
|
||||
case PACKER_STEP_VALUE_FLOAT:
|
||||
value = view.getFloat32(caret);
|
||||
value = packedSteps.readFloatLE(caret);
|
||||
caret += 4;
|
||||
break;
|
||||
case PACKER_STEP_VALUE_STRING:
|
||||
case PACKER_STEP_VALUE_JSON:
|
||||
const stringLength = view.getUint32(caret);
|
||||
const stringLength = packedSteps.readUInt32LE(caret);
|
||||
caret += 4;
|
||||
value = '';
|
||||
for (let j = 0; j < stringLength; ++j) {
|
||||
value += String.fromCharCode(view.getUint8(caret));
|
||||
value += String.fromCharCode(packedSteps.readUInt8(caret));
|
||||
caret += 1;
|
||||
}
|
||||
value = decodeURIComponent(value);
|
||||
|
|
Loading…
Reference in New Issue
Block a user