Migrating to uWS

This commit is contained in:
David Négrier 2020-09-28 18:52:54 +02:00
parent 783d58d3cb
commit 6a4c0c8678
31 changed files with 2056 additions and 1123 deletions

View File

@ -38,27 +38,27 @@
"dependencies": {
"axios": "^0.20.0",
"body-parser": "^1.19.0",
"busboy": "^0.3.1",
"circular-json": "^0.5.9",
"express": "^4.17.1",
"generic-type-guard": "^3.2.0",
"google-protobuf": "^3.13.0",
"http-status-codes": "^1.4.0",
"iterall": "^1.3.0",
"jsonwebtoken": "^8.5.1",
"prom-client": "^12.0.0",
"socket.io": "^2.3.0",
"query-string": "^6.13.3",
"systeminformation": "^4.26.5",
"ts-node-dev": "^1.0.0-pre.44",
"typescript": "^3.8.3",
"uWebSockets.js": "uNetworking/uWebSockets.js#v18.5.0",
"uuidv4": "^6.0.7"
},
"devDependencies": {
"@types/circular-json": "^0.4.0",
"@types/express": "^4.17.4",
"@types/google-protobuf": "^3.7.3",
"@types/http-status-codes": "^1.2.0",
"@types/jasmine": "^3.5.10",
"@types/jsonwebtoken": "^8.3.8",
"@types/socket.io": "^2.1.4",
"@types/uuidv4": "^5.0.0",
"@typescript-eslint/eslint-plugin": "^2.26.0",
"@typescript-eslint/parser": "^2.26.0",

View File

@ -1,3 +1,3 @@
// lib/server.ts
import App from "./src/App";
App.listen(8080, () => console.log(`Example app listening on port 8080!`))
App.listen(8080, () => console.log(`WorkAdventure starting on port 8080!`))

View File

@ -9,10 +9,10 @@ import {MapController} from "./Controller/MapController";
import {PrometheusController} from "./Controller/PrometheusController";
import {AdminController} from "./Controller/AdminController";
import {DebugController} from "./Controller/DebugController";
import {App as uwsApp} from "./Server/sifrr.server";
class App {
public app: Application;
public server: http.Server;
public app: uwsApp;
public ioSocketController: IoSocketController;
public authenticateController: AuthenticateController;
public mapController: MapController;
@ -21,18 +21,25 @@ class App {
private debugController: DebugController;
constructor() {
this.app = express();
//config server http
this.server = http.createServer(this.app);
this.app = new uwsApp();
this.config();
this.crossOrigin();
//TODO add middleware with access token to secure api
// STUPID CORS IMPLEMENTATION.
// TODO: SECURE THIS
this.app.any('/*', (res, req) => {
res.writeHeader('access-control-allow-headers', 'Origin, X-Requested-With, Content-Type, Accept');
res.writeHeader('access-control-allow-methods', 'GET, POST, OPTIONS, PUT, PATCH, DELETE');
res.writeHeader('access-control-allow-origin', '*');
req.setYield(true);
});
//create socket controllers
this.ioSocketController = new IoSocketController(this.server);
this.ioSocketController = new IoSocketController(this.app);
this.authenticateController = new AuthenticateController(this.app);
this.mapController = new MapController(this.app);
this.prometheusController = new PrometheusController(this.app, this.ioSocketController);
@ -42,20 +49,20 @@ class App {
// TODO add session user
private config(): void {
this.app.use(bodyParser.json());
this.app.use(bodyParser.urlencoded({extended: false}));
/*this.app.use(bodyParser.json());
this.app.use(bodyParser.urlencoded({extended: false}));*/
}
private crossOrigin(){
this.app.use((req: Request, res: Response, next) => {
/*this.app.use((req: Request, res: Response, next) => {
res.setHeader("Access-Control-Allow-Origin", "*"); // update to match the domain you will make the request from
// Request methods you wish to allow
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, PATCH, DELETE');
// Request headers you wish to allow
res.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
next();
});
});*/
}
}
export default new App().server;
export default new App().app;

View File

@ -3,38 +3,58 @@ import Jwt from "jsonwebtoken";
import {BAD_REQUEST, OK} from "http-status-codes";
import {SECRET_KEY, URL_ROOM_STARTED} from "../Enum/EnvironmentVariable"; //TODO fix import by "_Enum/..."
import { uuid } from 'uuidv4';
import {HttpRequest, HttpResponse, TemplatedApp} from "uWebSockets.js";
import {BaseController} from "./BaseController";
export interface TokenInterface {
name: string,
userUuid: string
}
export class AuthenticateController {
App : Application;
export class AuthenticateController extends BaseController {
constructor(App : Application) {
this.App = App;
constructor(private App : TemplatedApp) {
super();
this.login();
}
onAbortedOrFinishedResponse(res: HttpResponse/*, readStream: any*/) {
console.log("ERROR! onAbortedOrFinishedResponse called!");
/*if (res.id == -1) {
console.log("ERROR! onAbortedOrFinishedResponse called twice for the same res!");
} else {
console.log('Stream was closed, openStreams: ' + --openStreams);
console.timeEnd(res.id);
readStream.destroy();
}*/
/* Mark this response already accounted for */
//res.id = -1;
}
//permit to login on application. Return token to connect on Websocket IO.
login(){
// For now, let's completely forget the /login route.
this.App.post("/login", (req: Request, res: Response) => {
const param = req.body;
/*if(!param.name){
return res.status(BAD_REQUEST).send({
message: "email parameter is empty"
this.App.options("/login", (res: HttpResponse, req: HttpRequest) => {
this.addCorsHeaders(res);
res.end();
});
}*/
//TODO check user email for The Coding Machine game
this.App.post("/login", async (res: HttpResponse, req: HttpRequest) => {
this.addCorsHeaders(res);
res.onAborted(() => {
console.warn('Login request was aborted');
})
const param = await res.json();
const userUuid = uuid();
const token = Jwt.sign({name: param.name, userUuid: userUuid} as TokenInterface, SECRET_KEY, {expiresIn: '24h'});
return res.status(OK).send({
res.writeStatus("200 OK").end(JSON.stringify({
token: token,
mapUrlStart: URL_ROOM_STARTED,
userId: userUuid,
});
}));
});
}
}

View File

@ -0,0 +1,10 @@
import {HttpResponse} from "uWebSockets.js";
export class BaseController {
protected addCorsHeaders(res: HttpResponse): void {
res.writeHeader('access-control-allow-headers', 'Origin, X-Requested-With, Content-Type, Accept');
res.writeHeader('access-control-allow-methods', 'GET, POST, OPTIONS, PUT, PATCH, DELETE');
res.writeHeader('access-control-allow-origin', '*');
}
}

View File

@ -1,5 +1,3 @@
import socketIO = require('socket.io');
import {Socket} from "socket.io";
import * as http from "http";
import {MessageUserPosition, Point} from "../Model/Websocket/MessageUserPosition"; //TODO fix import by "_Model/.."
import {ExSocketInterface} from "../Model/Websocket/ExSocketInterface"; //TODO fix import by "_Model/.."
@ -32,11 +30,19 @@ import {
GroupDeleteMessage,
UserJoinedMessage,
UserLeftMessage,
ItemEventMessage, ViewportMessage
ItemEventMessage,
ViewportMessage,
ClientToServerMessage,
JoinRoomMessage,
ErrorMessage,
RoomJoinedMessage,
ItemStateMessage,
ServerToClientMessage, SetUserIdMessage, SilentMessage
} from "../Messages/generated/messages_pb";
import {UserMovesMessage} from "../Messages/generated/messages_pb";
import Direction = PositionMessage.Direction;
import {ProtobufUtils} from "../Model/Websocket/ProtobufUtils";
import {App, TemplatedApp, WebSocket} from "uWebSockets.js"
enum SocketIoEvent {
CONNECTION = "connection",
@ -59,12 +65,18 @@ enum SocketIoEvent {
BATCH = "batch",
}
function emitInBatch(socket: ExSocketInterface, event: string, payload: SubMessage): void {
function emitInBatch(socket: ExSocketInterface, payload: SubMessage): void {
if (socket.disconnecting) {
return;
}
socket.batchedMessages.addPayload(payload);
if (socket.batchTimeout === null) {
socket.batchTimeout = setTimeout(() => {
socket./*binary(true).*/emit(SocketIoEvent.BATCH, socket.batchedMessages.serializeBinary().buffer);
const serverToClientMessage = new ServerToClientMessage();
serverToClientMessage.setBatchmessage(socket.batchedMessages);
socket.send(serverToClientMessage.serializeBinary().buffer, true);
socket.batchedMessages = new BatchMessage();
socket.batchTimeout = null;
}, 100);
@ -72,15 +84,14 @@ function emitInBatch(socket: ExSocketInterface, event: string, payload: SubMessa
}
export class IoSocketController {
public readonly Io: socketIO.Server;
private Worlds: Map<string, World> = new Map<string, World>();
private sockets: Map<number, ExSocketInterface> = new Map<number, ExSocketInterface>();
private nbClientsGauge: Gauge<string>;
private nbClientsPerRoomGauge: Gauge<string>;
private nextUserId: number = 1;
constructor(server: http.Server) {
this.Io = socketIO(server);
constructor(private readonly app: TemplatedApp) {
this.nbClientsGauge = new Gauge({
name: 'workadventure_nb_sockets',
help: 'Number of connected sockets',
@ -92,11 +103,39 @@ export class IoSocketController {
labelNames: [ 'room' ]
});
// Authentication with token. it will be decoded and stored in the socket.
// Completely commented for now, as we do not use the "/login" route at all.
this.Io.use((socket: Socket, next) => {
this.ioConnection();
}
private isValidToken(token: object): token is TokenInterface {
if (typeof((token as TokenInterface).userUuid) !== 'string') {
return false;
}
if (typeof((token as TokenInterface).name) !== 'string') {
return false;
}
return true;
}
/**
*
* @param token
*/
searchClientByToken(token: string): ExSocketInterface | null {
const clients: ExSocketInterface[] = Object.values(this.Io.sockets.sockets) as ExSocketInterface[];
for (let i = 0; i < clients.length; i++) {
const client = clients[i];
if (client.token !== token) {
continue
}
return client;
}
return null;
}
private authenticate(ws: WebSocket) {
//console.log(socket.handshake.query.token);
if (!socket.handshake.query || !socket.handshake.query.token) {
/*if (!socket.handshake.query || !socket.handshake.query.token) {
console.error('An authentication error happened, a user tried to connect without a token.');
return next(new Error('Authentication error'));
}
@ -135,75 +174,126 @@ export class IoSocketController {
(socket as ExSocketInterface).userUuid = tokenDecoded.userUuid;
this.nextUserId++;
next();
});
});
this.ioConnection();
}
private isValidToken(token: object): token is TokenInterface {
if (typeof((token as TokenInterface).userUuid) !== 'string') {
return false;
}
if (typeof((token as TokenInterface).name) !== 'string') {
return false;
}
return true;
}
/**
*
* @param token
*/
searchClientByToken(token: string): ExSocketInterface | null {
const clients: ExSocketInterface[] = Object.values(this.Io.sockets.sockets) as ExSocketInterface[];
for (let i = 0; i < clients.length; i++) {
const client = clients[i];
if (client.token !== token) {
continue
}
return client;
}
return null;
});*/
const socket = ws as ExSocketInterface;
socket.userId = this.nextUserId;
this.nextUserId++;
}
ioConnection() {
this.Io.on(SocketIoEvent.CONNECTION, (socket: Socket) => {
const client : ExSocketInterface = socket as ExSocketInterface;
this.app.ws('/*', {
/* Options */
//compression: uWS.SHARED_COMPRESSOR,
maxPayloadLength: 16 * 1024 * 1024,
idleTimeout: 10,
/* Handlers */
open: (ws) => {
this.authenticate(ws);
// TODO: close if authenticate is ko
const client : ExSocketInterface = ws as ExSocketInterface;
client.batchedMessages = new BatchMessage();
client.batchTimeout = null;
client.emitInBatch = (event: string, payload: SubMessage): void => {
emitInBatch(client, event, payload);
client.emitInBatch = (payload: SubMessage): void => {
emitInBatch(client, payload);
}
client.disconnecting = false;
this.sockets.set(client.userId, client);
// Let's log server load when a user joins
const srvSockets = this.Io.sockets.sockets;
this.nbClientsGauge.inc();
console.log(new Date().toISOString() + ' A user joined (', Object.keys(srvSockets).length, ' connected users)');
//si.currentLoad().then(data => console.log(' Current load: ', data.avgload));
//si.currentLoad().then(data => console.log(' CPU: ', data.currentload, '%'));
// End log server load
console.log(new Date().toISOString() + ' A user joined (', this.sockets.size, ' connected users)');
/*join-rom event permit to join one room.
message :
userId : user identification
roomId: room identification
position: position of user in map
x: user x position on map
y: user y position on map
*/
socket.on(SocketIoEvent.JOIN_ROOM, (message: unknown, answerFn): void => {
//console.log(SocketIoEvent.JOIN_ROOM, message);
try {
if (!isJoinRoomMessageInterface(message)) {
socket.emit(SocketIoEvent.MESSAGE_ERROR, {message: 'Invalid JOIN_ROOM message.'});
console.warn('Invalid JOIN_ROOM message received: ', message);
return;
},
message: (ws, arrayBuffer, isBinary) => {
const client = ws as ExSocketInterface;
const message = ClientToServerMessage.deserializeBinary(new Uint8Array(arrayBuffer));
if (message.hasJoinroommessage()) {
this.handleJoinRoom(client, message.getJoinroommessage() as JoinRoomMessage);
} else if (message.hasViewportmessage()) {
this.handleViewport(client, message.getViewportmessage() as ViewportMessage);
} else if (message.hasUsermovesmessage()) {
this.handleUserMovesMessage(client, message.getUsermovesmessage() as UserMovesMessage);
} else if (message.hasSetplayerdetailsmessage()) {
this.handleSetPlayerDetails(client, message.getSetplayerdetailsmessage() as SetPlayerDetailsMessage);
} else if (message.hasSilentmessage()) {
this.handleSilentMessage(client, message.getSilentmessage() as SilentMessage);
} else if (message.hasItemeventmessage()) {
this.handleItemEvent(client, message.getItemeventmessage() as ItemEventMessage);
}
const roomId = message.roomId;
const Client = (socket as ExSocketInterface);
/* Ok is false if backpressure was built up, wait for drain */
//let ok = ws.send(message, isBinary);
},
drain: (ws) => {
console.log('WebSocket backpressure: ' + ws.getBufferedAmount());
},
close: (ws, code, message) => {
const Client = (ws as ExSocketInterface);
try {
Client.disconnecting = true;
//leave room
this.leaveRoom(Client);
//leave webrtc room
//socket.leave(Client.webRtcRoomId);
//delete all socket information
delete Client.webRtcRoomId;
delete Client.roomId;
delete Client.token;
delete Client.position;
} catch (e) {
console.error('An error occurred on "disconnect"');
console.error(e);
}
this.sockets.delete(Client.userId);
// Let's log server load when a user leaves
this.nbClientsGauge.dec();
console.log('A user left (', this.sockets.size, ' connected users)');
}
})
// TODO: finish this!
/*this.Io.on(SocketIoEvent.CONNECTION, (socket: Socket) => {
socket.on(SocketIoEvent.WEBRTC_SIGNAL, (data: unknown) => {
this.emitVideo((socket as ExSocketInterface), data);
});
socket.on(SocketIoEvent.WEBRTC_SCREEN_SHARING_SIGNAL, (data: unknown) => {
this.emitScreenSharing((socket as ExSocketInterface), data);
});
});*/
}
private emitError(Client: ExSocketInterface, message: string): void {
const errorMessage = new ErrorMessage();
errorMessage.setMessage(message);
const serverToClientMessage = new ServerToClientMessage();
serverToClientMessage.setErrormessage(errorMessage);
if (!Client.disconnecting) {
Client.send(serverToClientMessage.serializeBinary().buffer);
}
console.warn(message);
}
private handleJoinRoom(Client: ExSocketInterface, message: JoinRoomMessage): void {
try {
/*if (!isJoinRoomMessageInterface(message.toObject())) {
console.log(message.toObject())
this.emitError(Client, 'Invalid JOIN_ROOM message received: ' + message.toObject().toString());
return;
}*/
const roomId = message.getRoomid();
if (Client.roomId === roomId) {
return;
@ -213,12 +303,11 @@ export class IoSocketController {
this.leaveRoom(Client);
//join new previous room
const world = this.joinRoom(Client, roomId, message.position);
const world = this.joinRoom(Client, roomId, ProtobufUtils.toPointInterface(message.getPosition() as PositionMessage));
const things = world.setViewport(Client, message.viewport);
const things = world.setViewport(Client, (message.getViewport() as ViewportMessage).toObject());
const listOfUsers: Array<MessageUserPosition> = [];
const listOfGroups: Array<GroupUpdateInterface> = [];
const roomJoinedMessage = new RoomJoinedMessage();
for (const thing of things) {
if (thing instanceof User) {
@ -228,75 +317,65 @@ export class IoSocketController {
continue;
}
listOfUsers.push(new MessageUserPosition(thing.id, player.name, player.characterLayers, player.position));
const userJoinedMessage = new UserJoinedMessage();
userJoinedMessage.setUserid(thing.id);
userJoinedMessage.setName(player.name);
userJoinedMessage.setCharacterlayersList(player.characterLayers);
userJoinedMessage.setPosition(ProtobufUtils.toPositionMessage(player.position));
roomJoinedMessage.addUser(userJoinedMessage);
} else if (thing instanceof Group) {
listOfGroups.push({
groupId: thing.getId(),
position: thing.getPosition(),
});
const groupUpdateMessage = new GroupUpdateMessage();
groupUpdateMessage.setGroupid(thing.getId());
groupUpdateMessage.setPosition(ProtobufUtils.toPointMessage(thing.getPosition()));
roomJoinedMessage.addGroup(groupUpdateMessage);
} else {
console.error("Unexpected type for Movable returned by setViewport");
}
}
const listOfItems: {[itemId: string]: unknown} = {};
for (const [itemId, item] of world.getItemsState().entries()) {
listOfItems[itemId] = item;
const itemStateMessage = new ItemStateMessage();
itemStateMessage.setItemid(itemId);
itemStateMessage.setStatejson(JSON.stringify(item));
roomJoinedMessage.addItem(itemStateMessage);
}
//console.warn('ANSWER PLAYER POSITIONS', listOfUsers);
if (answerFn === undefined && ALLOW_ARTILLERY === true) {
// For some reason, answerFn can be undefined if we use Artillery (?)
return;
}
const serverToClientMessage = new ServerToClientMessage();
serverToClientMessage.setRoomjoinedmessage(roomJoinedMessage);
answerFn({
users: listOfUsers,
groups: listOfGroups,
items: listOfItems
});
if (!Client.disconnecting) {
Client.send(serverToClientMessage.serializeBinary().buffer, true);
}
} catch (e) {
console.error('An error occurred on "join_room" event');
console.error(e);
}
});
socket.on(SocketIoEvent.SET_VIEWPORT, (message: unknown): void => {
try {
if (!(message instanceof Buffer)) {
socket.emit(SocketIoEvent.MESSAGE_ERROR, {message: 'Invalid SET_VIEWPORT message. Expecting binary buffer.'});
console.warn('Invalid SET_VIEWPORT message received (expecting binary buffer): ', message);
return;
}
const viewportMessage = ViewportMessage.deserializeBinary(new Uint8Array(message as ArrayBuffer));
private handleViewport(client: ExSocketInterface, viewportMessage: ViewportMessage) {
try {
const viewport = viewportMessage.toObject();
const Client = (socket as ExSocketInterface);
Client.viewport = viewport;
client.viewport = viewport;
const world = this.Worlds.get(Client.roomId);
const world = this.Worlds.get(client.roomId);
if (!world) {
console.error("In SET_VIEWPORT, could not find world with id '", Client.roomId, "'");
console.error("In SET_VIEWPORT, could not find world with id '", client.roomId, "'");
return;
}
world.setViewport(Client, Client.viewport);
world.setViewport(client, client.viewport);
} catch (e) {
console.error('An error occurred on "SET_VIEWPORT" event');
console.error(e);
}
});
socket.on(SocketIoEvent.USER_POSITION, (message: unknown): void => {
//console.log(SockerIoEvent.USER_POSITION, userMovesMessage);
try {
if (!(message instanceof Buffer)) {
socket.emit(SocketIoEvent.MESSAGE_ERROR, {message: 'Invalid USER_POSITION message. Expecting binary buffer.'});
console.warn('Invalid USER_POSITION message received (expecting binary buffer): ', message);
return;
}
const userMovesMessage = UserMovesMessage.deserializeBinary(new Uint8Array(message as ArrayBuffer));
private handleUserMovesMessage(client: ExSocketInterface, userMovesMessage: UserMovesMessage) {
//console.log(SockerIoEvent.USER_POSITION, userMovesMessage);
try {
const userMoves = userMovesMessage.toObject();
const position = userMoves.position;
@ -326,143 +405,76 @@ export class IoSocketController {
throw new Error("Unexpected direction");
}
const Client = (socket as ExSocketInterface);
// sending to all clients in room except sender
Client.position = {
client.position = {
x: position.x,
y: position.y,
direction,
moving: position.moving,
};
Client.viewport = viewport;
client.viewport = viewport;
// update position in the world
const world = this.Worlds.get(Client.roomId);
const world = this.Worlds.get(client.roomId);
if (!world) {
console.error("In USER_POSITION, could not find world with id '", Client.roomId, "'");
console.error("In USER_POSITION, could not find world with id '", client.roomId, "'");
return;
}
world.updatePosition(Client, Client.position);
world.setViewport(Client, Client.viewport);
world.updatePosition(client, client.position);
world.setViewport(client, client.viewport);
} catch (e) {
console.error('An error occurred on "user_position" event');
console.error(e);
}
});
socket.on(SocketIoEvent.WEBRTC_SIGNAL, (data: unknown) => {
this.emitVideo((socket as ExSocketInterface), data);
});
socket.on(SocketIoEvent.WEBRTC_SCREEN_SHARING_SIGNAL, (data: unknown) => {
this.emitScreenSharing((socket as ExSocketInterface), data);
});
socket.on(SocketIoEvent.DISCONNECT, () => {
const Client = (socket as ExSocketInterface);
try {
//leave room
this.leaveRoom(Client);
//leave webrtc room
//socket.leave(Client.webRtcRoomId);
//delete all socket information
delete Client.webRtcRoomId;
delete Client.roomId;
delete Client.token;
delete Client.position;
} catch (e) {
console.error('An error occurred on "disconnect"');
console.error(e);
}
this.sockets.delete(Client.userId);
// Let's log server load when a user leaves
const srvSockets = this.Io.sockets.sockets;
this.nbClientsGauge.dec();
console.log('A user left (', Object.keys(srvSockets).length, ' connected users)');
//si.currentLoad().then(data => console.log('Current load: ', data.avgload));
//si.currentLoad().then(data => console.log('CPU: ', data.currentload, '%'));
// End log server load
});
// Let's send the user id to the user
socket.on(SocketIoEvent.SET_PLAYER_DETAILS, (message: unknown, answerFn) => {
//console.log(SocketIoEvent.SET_PLAYER_DETAILS, message);
if (!(message instanceof Buffer)) {
socket.emit(SocketIoEvent.MESSAGE_ERROR, {message: 'Invalid SET_PLAYER_DETAILS message. Expecting binary buffer.'});
console.warn('Invalid SET_PLAYER_DETAILS message received (expecting binary buffer): ', message);
return;
}
const playerDetailsMessage = SetPlayerDetailsMessage.deserializeBinary(new Uint8Array(message));
private handleSetPlayerDetails(client: ExSocketInterface, playerDetailsMessage: SetPlayerDetailsMessage) {
const playerDetails = {
name: playerDetailsMessage.getName(),
characterLayers: playerDetailsMessage.getCharacterlayersList()
};
//console.log(SocketIoEvent.SET_PLAYER_DETAILS, playerDetails);
if (!isSetPlayerDetailsMessage(playerDetails)) {
socket.emit(SocketIoEvent.MESSAGE_ERROR, {message: 'Invalid SET_PLAYER_DETAILS message.'});
console.warn('Invalid SET_PLAYER_DETAILS message received: ', playerDetails);
this.emitError(client, 'Invalid SET_PLAYER_DETAILS message received: ');
return;
}
const Client = (socket as ExSocketInterface);
Client.name = playerDetails.name;
Client.characterLayers = playerDetails.characterLayers;
// Artillery fails when receiving an acknowledgement that is not a JSON object
if (!Client.isArtillery) {
answerFn(Client.userId);
}
});
client.name = playerDetails.name;
client.characterLayers = playerDetails.characterLayers;
socket.on(SocketIoEvent.SET_SILENT, (silent: unknown) => {
//console.log(SocketIoEvent.SET_SILENT, silent);
if (typeof silent !== "boolean") {
socket.emit(SocketIoEvent.MESSAGE_ERROR, {message: 'Invalid SET_SILENT message.'});
console.warn('Invalid SET_SILENT message received: ', silent);
return;
const setUserIdMessage = new SetUserIdMessage();
setUserIdMessage.setUserid(client.userId);
const serverToClientMessage = new ServerToClientMessage();
serverToClientMessage.setSetuseridmessage(setUserIdMessage);
if (!client.disconnecting) {
client.send(serverToClientMessage.serializeBinary().buffer, true);
}
}
private handleSilentMessage(client: ExSocketInterface, silentMessage: SilentMessage) {
try {
const Client = (socket as ExSocketInterface);
// update position in the world
const world = this.Worlds.get(Client.roomId);
const world = this.Worlds.get(client.roomId);
if (!world) {
console.error("In SET_SILENT, could not find world with id '", Client.roomId, "'");
console.error("In handleSilentMessage, could not find world with id '", client.roomId, "'");
return;
}
world.setSilent(Client, silent);
world.setSilent(client, silentMessage.getSilent());
} catch (e) {
console.error('An error occurred on "SET_SILENT"');
console.error('An error occurred on "handleSilentMessage"');
console.error(e);
}
});
socket.on(SocketIoEvent.ITEM_EVENT, (message: unknown) => {
if (!(message instanceof Buffer)) {
socket.emit(SocketIoEvent.MESSAGE_ERROR, {message: 'Invalid ITEM_EVENT message. Expecting binary buffer.'});
console.warn('Invalid ITEM_EVENT message received (expecting binary buffer): ', message);
return;
}
const itemEventMessage = ItemEventMessage.deserializeBinary(new Uint8Array(message));
private handleItemEvent(ws: ExSocketInterface, itemEventMessage: ItemEventMessage) {
const itemEvent = ProtobufUtils.toItemEvent(itemEventMessage);
/*if (!isItemEventMessageInterface(itemEvent)) {
socket.emit(SocketIoEvent.MESSAGE_ERROR, {message: 'Invalid ITEM_EVENT message.'});
console.warn('Invalid ITEM_EVENT message received: ', itemEvent);
return;
}*/
try {
const Client = (socket as ExSocketInterface);
//socket.to(Client.roomId).emit(SocketIoEvent.ITEM_EVENT, itemEvent);
const world = this.Worlds.get(Client.roomId);
const world = this.Worlds.get(ws.roomId);
if (!world) {
console.error("Could not find world with id '", Client.roomId, "'");
console.error("Could not find world with id '", ws.roomId, "'");
return;
}
@ -473,7 +485,7 @@ export class IoSocketController {
for (const user of world.getUsers().values()) {
const client = this.searchClientByIdOrFail(user.id);
//client.emit(SocketIoEvent.ITEM_EVENT, itemEvent);
emitInBatch(client, SocketIoEvent.ITEM_EVENT, subMessage);
emitInBatch(client, subMessage);
}
world.setItemState(itemEvent.itemId, itemEvent.state);
@ -481,8 +493,6 @@ export class IoSocketController {
console.error('An error occurred on "item_event"');
console.error(e);
}
});
});
}
emitVideo(socket: ExSocketInterface, data: unknown){
@ -542,7 +552,7 @@ export class IoSocketController {
}
}
//user leave previous room
Client.leave(Client.roomId);
//Client.leave(Client.roomId);
} finally {
this.nbClientsPerRoomGauge.dec({ room: Client.roomId });
delete Client.roomId;
@ -552,7 +562,7 @@ export class IoSocketController {
private joinRoom(Client : ExSocketInterface, roomId: string, position: PointInterface): World {
//join user in room
Client.join(roomId);
//Client.join(roomId);
this.nbClientsPerRoomGauge.inc({ room: roomId });
Client.roomId = roomId;
Client.position = position;
@ -570,6 +580,9 @@ export class IoSocketController {
const clientUser = this.searchClientByIdOrFail(thing.id);
const userJoinedMessage = new UserJoinedMessage();
if (!Number.isInteger(clientUser.userId)) {
throw new Error('clientUser.userId is not an integer '+clientUser.userId);
}
userJoinedMessage.setUserid(clientUser.userId);
userJoinedMessage.setName(clientUser.name);
userJoinedMessage.setCharacterlayersList(clientUser.characterLayers);
@ -578,7 +591,7 @@ export class IoSocketController {
const subMessage = new SubMessage();
subMessage.setUserjoinedmessage(userJoinedMessage);
emitInBatch(clientListener, SocketIoEvent.JOIN_ROOM, subMessage);
emitInBatch(clientListener, subMessage);
} else if (thing instanceof Group) {
this.emitCreateUpdateGroupEvent(clientListener, thing);
} else {
@ -596,7 +609,7 @@ export class IoSocketController {
const subMessage = new SubMessage();
subMessage.setUsermovedmessage(userMovedMessage);
clientListener.emitInBatch(SocketIoEvent.USER_MOVED, subMessage);
clientListener.emitInBatch(subMessage);
//console.log("Sending USER_MOVED event");
} else if (thing instanceof Group) {
this.emitCreateUpdateGroupEvent(clientListener, thing);
@ -627,7 +640,7 @@ export class IoSocketController {
return world;
}
private emitCreateUpdateGroupEvent(socket: Socket, group: Group): void {
private emitCreateUpdateGroupEvent(client: ExSocketInterface, group: Group): void {
const position = group.getPosition();
const pointMessage = new PointMessage();
pointMessage.setX(Math.floor(position.x));
@ -639,8 +652,7 @@ export class IoSocketController {
const subMessage = new SubMessage();
subMessage.setGroupupdatemessage(groupUpdateMessage);
const client : ExSocketInterface = socket as ExSocketInterface;
emitInBatch(client, SocketIoEvent.GROUP_CREATE_UPDATE, subMessage);
emitInBatch(client, subMessage);
//socket.emit(SocketIoEvent.GROUP_CREATE_UPDATE, groupUpdateMessage.serializeBinary().buffer);
}
@ -652,7 +664,7 @@ export class IoSocketController {
subMessage.setGroupdeletemessage(groupDeleteMessage);
const client : ExSocketInterface = socket as ExSocketInterface;
emitInBatch(client, SocketIoEvent.GROUP_DELETE, subMessage);
emitInBatch(client, subMessage);
}
private emitUserLeftEvent(socket: Socket, userId: number): void {
@ -663,7 +675,7 @@ export class IoSocketController {
subMessage.setUserleftmessage(userLeftMessage);
const client : ExSocketInterface = socket as ExSocketInterface;
emitInBatch(client, SocketIoEvent.USER_LEFT, subMessage);
emitInBatch(client, subMessage);
}
/**
@ -672,6 +684,10 @@ export class IoSocketController {
* @param roomId
*/
joinWebRtcRoom(socket: ExSocketInterface, roomId: string) {
// TODO: REBUILD THIS
return;
if (socket.webRtcRoomId === roomId) {
return;
}
@ -734,6 +750,9 @@ export class IoSocketController {
//disconnect user
disConnectedUser(userId: number, group: Group) {
// TODO: rebuild this
return;
const Client = this.searchClientByIdOrFail(userId);
Client.to("webrtcroom"+group.getId()).emit(SocketIoEvent.WEBRTC_DISCONNECT, {
userId: userId
@ -761,4 +780,5 @@ export class IoSocketController {
public getWorlds(): Map<string, World> {
return this.Worlds;
}
}

View File

@ -1,29 +1,33 @@
import express from "express";
import {Application, Request, Response} from "express";
import {OK} from "http-status-codes";
import {URL_ROOM_STARTED} from "../Enum/EnvironmentVariable";
import {HttpRequest, HttpResponse, TemplatedApp} from "uWebSockets.js";
import {BaseController} from "./BaseController";
export class MapController {
App: Application;
export class MapController extends BaseController{
constructor(App: Application) {
constructor(private App : TemplatedApp) {
super();
this.App = App;
this.getStartMap();
this.assetMaps();
}
assetMaps() {
this.App.use('/map/files', express.static('src/Assets/Maps'));
}
// Returns a map mapping map name to file name of the map
getStartMap() {
this.App.get("/start-map", (req: Request, res: Response) => {
const url = req.headers.host?.replace('api.', 'maps.') + URL_ROOM_STARTED;
res.status(OK).send({
this.App.options("/start-map", (res: HttpResponse, req: HttpRequest) => {
this.addCorsHeaders(res);
res.end();
});
this.App.get("/start-map", (res: HttpResponse, req: HttpRequest) => {
this.addCorsHeaders(res);
const url = req.getHeader('host').replace('api.', 'maps.') + URL_ROOM_STARTED;
res.writeStatus("200 OK").end(JSON.stringify({
mapUrlStart: url,
startInstance: "global"
});
}));
});
}
}

View File

@ -1,11 +1,11 @@
import {Socket} from "socket.io";
import {PointInterface} from "./PointInterface";
import {Identificable} from "./Identificable";
import {TokenInterface} from "../../Controller/AuthenticateController";
import {ViewportInterface} from "_Model/Websocket/ViewportMessage";
import {BatchMessage, SubMessage} from "../../Messages/generated/messages_pb";
import {WebSocket} from "uWebSockets.js"
export interface ExSocketInterface extends Socket, Identificable {
export interface ExSocketInterface extends WebSocket, Identificable {
token: string;
roomId: string;
webRtcRoomId: string|undefined;
@ -19,7 +19,8 @@ export interface ExSocketInterface extends Socket, Identificable {
/**
* Pushes an event that will be sent in the next batch of events
*/
emitInBatch: (event: string, payload: SubMessage) => void;
emitInBatch: (payload: SubMessage) => void;
batchedMessages: BatchMessage;
batchTimeout: NodeJS.Timeout|null;
disconnecting: boolean
}

View File

@ -1,8 +1,9 @@
import {PointInterface} from "./PointInterface";
import {ItemEventMessage, PositionMessage} from "../../Messages/generated/messages_pb";
import {ItemEventMessage, PointMessage, PositionMessage} from "../../Messages/generated/messages_pb";
import {ExSocketInterface} from "_Model/Websocket/ExSocketInterface";
import Direction = PositionMessage.Direction;
import {ItemEventMessageInterface} from "_Model/Websocket/ItemEventMessage";
import {PositionInterface} from "_Model/PositionInterface";
export class ProtobufUtils {
@ -34,6 +35,42 @@ export class ProtobufUtils {
return position;
}
public static toPointInterface(position: PositionMessage): PointInterface {
let direction: string;
switch (position.getDirection()) {
case Direction.UP:
direction = 'up';
break;
case Direction.DOWN:
direction = 'down';
break;
case Direction.LEFT:
direction = 'left';
break;
case Direction.RIGHT:
direction = 'right';
break;
default:
throw new Error("Unexpected direction");
}
// sending to all clients in room except sender
return {
x: position.getX(),
y: position.getY(),
direction,
moving: position.getMoving(),
};
}
public static toPointMessage(point: PositionInterface): PointMessage {
const position = new PointMessage();
position.setX(Math.floor(point.x));
position.setY(Math.floor(point.y));
return position;
}
public static toItemEvent(itemEventMessage: ItemEventMessage): ItemEventMessageInterface {
return {
itemId: itemEventMessage.getItemid(),

View File

@ -74,8 +74,8 @@ export class World {
this.users.delete(user.userId);
if (userObj !== undefined) {
this.positionNotifier.leave(userObj);
this.positionNotifier.removeViewport(userObj);
this.positionNotifier.leave(userObj);
}
}

View File

@ -0,0 +1,13 @@
import { App as _App, AppOptions } from 'uWebSockets.js';
import BaseApp from './baseapp';
import { extend } from './utils';
import { UwsApp } from './types';
class App extends (<UwsApp>_App) {
constructor(options: AppOptions = {}) {
super(options);
extend(this, new BaseApp());
}
}
export default App;

View File

@ -0,0 +1,220 @@
import { readdirSync, statSync } from 'fs';
import { join, relative } from 'path';
import { Readable } from 'stream';
import { us_listen_socket_close, TemplatedApp, HttpResponse, HttpRequest } from 'uWebSockets.js';
//import { watch } from 'chokidar';
import { wsConfig } from './livereload';
import sendFile from './sendfile';
import formData from './formdata';
import loadroutes from './loadroutes';
import { graphqlPost, graphqlWs } from './graphql';
import { stob } from './utils';
import { SendFileOptions, Handler } from './types';
const contTypes = ['application/x-www-form-urlencoded', 'multipart/form-data'];
const noOp = () => true;
const handleBody = (res: HttpResponse, req: HttpRequest) => {
const contType = req.getHeader('content-type');
res.bodyStream = function() {
const stream = new Readable();
stream._read = noOp;
this.onData((ab, isLast) => {
// uint and then slicing is bit faster than slice and then uint
stream.push(new Uint8Array(ab.slice(ab.byteOffset, ab.byteLength)));
if (isLast) {
stream.push(null);
}
});
return stream;
};
res.body = () => stob(res.bodyStream());
if (contType.indexOf('application/json') > -1)
res.json = async () => JSON.parse(await res.body());
if (contTypes.map(t => contType.indexOf(t) > -1).indexOf(true) > -1)
res.formData = formData.bind(res, contType);
};
class BaseApp {
_staticPaths = new Map();
//_watched = new Map();
_sockets = new Map();
__livereloadenabled = false;
ws!: TemplatedApp['ws'];
get!: TemplatedApp['get'];
_post!: TemplatedApp['post'];
_put!: TemplatedApp['put'];
_patch!: TemplatedApp['patch'];
_listen!: TemplatedApp['listen'];
file(pattern: string, filePath: string, options: SendFileOptions = {}) {
pattern=pattern.replace(/\\/g,'/');
if (this._staticPaths.has(pattern)) {
if (options.failOnDuplicateRoute)
throw Error(
`Error serving '${filePath}' for '${pattern}', already serving '${
this._staticPaths.get(pattern)[0]
}' file for this pattern.`
);
else if (!options.overwriteRoute) return this;
}
if (options.livereload && !this.__livereloadenabled) {
this.ws('/__sifrrLiveReload', wsConfig);
this.file('/livereload.js', join(__dirname, './livereloadjs.js'));
this.__livereloadenabled = true;
}
this._staticPaths.set(pattern, [filePath, options]);
this.get(pattern, this._serveStatic);
return this;
}
folder(prefix: string, folder: string, options: SendFileOptions, base: string = folder) {
// not a folder
if (!statSync(folder).isDirectory()) {
throw Error('Given path is not a directory: ' + folder);
}
// ensure slash in beginning and no trailing slash for prefix
if (prefix[0] !== '/') prefix = '/' + prefix;
if (prefix[prefix.length - 1] === '/') prefix = prefix.slice(0, -1);
// serve folder
const filter = options ? options.filter || noOp : noOp;
readdirSync(folder).forEach(file => {
// Absolute path
const filePath = join(folder, file);
// Return if filtered
if (!filter(filePath)) return;
if (statSync(filePath).isDirectory()) {
// Recursive if directory
this.folder(prefix, filePath, options, base);
} else {
this.file(prefix + '/' + relative(base, filePath), filePath, options);
}
});
/*if (options && options.watch) {
if (!this._watched.has(folder)) {
const w = watch(folder);
w.on('unlink', filePath => {
const url = '/' + relative(base, filePath);
this._staticPaths.delete(prefix + url);
});
w.on('add', filePath => {
const url = '/' + relative(base, filePath);
this.file(prefix + url, filePath, options);
});
this._watched.set(folder, w);
}
}*/
return this;
}
_serveStatic(res: HttpResponse, req: HttpRequest) {
res.onAborted(noOp);
const options = this._staticPaths.get(req.getUrl());
if (typeof options === 'undefined') {
res.writeStatus('404 Not Found');
res.end();
} else sendFile(res, req, options[0], options[1]);
}
post(pattern: string, handler: Handler) {
if (typeof handler !== 'function')
throw Error(`handler should be a function, given ${typeof handler}.`);
this._post(pattern, (res, req) => {
handleBody(res, req);
handler(res, req);
});
return this;
}
put(pattern: string, handler: Handler) {
if (typeof handler !== 'function')
throw Error(`handler should be a function, given ${typeof handler}.`);
this._put(pattern, (res, req) => {
handleBody(res, req);
handler(res, req);
});
return this;
}
patch(pattern: string, handler: Handler) {
if (typeof handler !== 'function')
throw Error(`handler should be a function, given ${typeof handler}.`);
this._patch(pattern, (res, req) => {
handleBody(res, req);
handler(res, req);
});
return this;
}
graphql(route: string, schema, graphqlOptions: any = {}, uwsOptions = {}, graphql) {
const handler = graphqlPost(schema, graphqlOptions, graphql);
this.post(route, handler);
this.ws(route, graphqlWs(schema, graphqlOptions, uwsOptions, graphql));
// this.get(route, handler);
if (graphqlOptions && graphqlOptions.graphiqlPath)
this.file(graphqlOptions.graphiqlPath, join(__dirname, './graphiql.html'));
return this;
}
load(dir: string, options) {
loadroutes.call(this, dir, options);
return this;
}
listen(h: string | number, p: Function | number = noOp, cb?: Function) {
if (typeof p === 'number' && typeof h === 'string') {
this._listen(h, p, socket => {
this._sockets.set(p, socket);
if (cb === undefined) {
throw new Error('cb undefined');
}
cb(socket);
});
} else if (typeof h === 'number' && typeof p === 'function') {
this._listen(h, socket => {
this._sockets.set(h, socket);
p(socket);
});
} else {
throw Error(
'Argument types: (host: string, port: number, cb?: Function) | (port: number, cb?: Function)'
);
}
return this;
}
close(port: null | number = null) {
//this._watched.forEach(v => v.close());
//this._watched.clear();
if (port) {
this._sockets.has(port) && us_listen_socket_close(this._sockets.get(port));
this._sockets.delete(port);
} else {
this._sockets.forEach(app => {
us_listen_socket_close(app);
});
this._sockets.clear();
}
return this;
}
}
export default BaseApp;

View File

@ -0,0 +1,48 @@
const noop = (a, b) => {};
export default class Cluster {
apps: any[];
listens = {};
// apps = [ { app: SifrrServerApp, port/ports: int } ]
constructor(apps) {
if (!Array.isArray(apps)) apps = [apps];
this.apps = apps;
}
listen(onListen = noop) {
for (let i = 0; i < this.apps.length; i++) {
const config = this.apps[i];
let { app, port, ports } = config;
if (!Array.isArray(ports) || ports.length === 0) {
ports = [port];
}
ports.forEach(p => {
if (typeof p !== 'number') throw Error(`Port should be a number, given ${p}`);
if (this.listens[p]) return;
app.listen(p, socket => {
onListen.call(app, socket, p);
});
this.listens[p] = app;
});
}
return this;
}
closeAll() {
Object.keys(this.listens).forEach(port => {
this.close(port);
});
return this;
}
close(port = null) {
if (port) {
this.listens[port] && this.listens[port].close(port);
delete this.listens[port];
} else {
this.closeAll();
}
return this;
}
}

View File

@ -0,0 +1,99 @@
import { createWriteStream } from 'fs';
import { join, dirname } from 'path';
import Busboy from 'busboy';
import mkdirp from 'mkdirp';
function formData(
contType: string,
options: busboy.BusboyConfig & {
abortOnLimit?: boolean;
tmpDir?: string;
onFile?: (
fieldname: string,
file: NodeJS.ReadableStream,
filename: string,
encoding: string,
mimetype: string
) => string;
onField?: (fieldname: string, value: any) => void;
filename?: (oldName: string) => string;
} = {}
) {
options.headers = {
'content-type': contType
};
return new Promise((resolve, reject) => {
const busb = new Busboy(options);
const ret = {};
this.bodyStream().pipe(busb);
busb.on('limit', () => {
if (options.abortOnLimit) {
reject(Error('limit'));
}
});
busb.on('file', function(fieldname, file, filename, encoding, mimetype) {
const value = {
filename,
encoding,
mimetype,
filePath: undefined
};
if (typeof options.tmpDir === 'string') {
if (typeof options.filename === 'function') filename = options.filename(filename);
const fileToSave = join(options.tmpDir, filename);
mkdirp(dirname(fileToSave));
file.pipe(createWriteStream(fileToSave));
value.filePath = fileToSave;
}
if (typeof options.onFile === 'function') {
value.filePath =
options.onFile(fieldname, file, filename, encoding, mimetype) || value.filePath;
}
setRetValue(ret, fieldname, value);
});
busb.on('field', function(fieldname, value) {
if (typeof options.onField === 'function') options.onField(fieldname, value);
setRetValue(ret, fieldname, value);
});
busb.on('finish', function() {
resolve(ret);
});
busb.on('error', reject);
});
}
function setRetValue(
ret: { [x: string]: any },
fieldname: string,
value: { filename: string; encoding: string; mimetype: string; filePath?: string } | any
) {
if (fieldname.slice(-2) === '[]') {
fieldname = fieldname.slice(0, fieldname.length - 2);
if (Array.isArray(ret[fieldname])) {
ret[fieldname].push(value);
} else {
ret[fieldname] = [value];
}
} else {
if (Array.isArray(ret[fieldname])) {
ret[fieldname].push(value);
} else if (ret[fieldname]) {
ret[fieldname] = [ret[fieldname], value];
} else {
ret[fieldname] = value;
}
}
}
export default formData;

View File

@ -0,0 +1,133 @@
<!--
* Copyright (c) 2019 GraphQL Contributors
* All rights reserved.
*
* This source code is licensed under the license found in the
* LICENSE file in the root directory of this source tree.
-->
<!DOCTYPE html>
<html>
<head>
<style>
body {
height: 100%;
margin: 0;
width: 100%;
overflow: hidden;
}
#graphiql {
height: 100vh;
}
</style>
<!--
This GraphiQL example depends on Promise and fetch, which are available in
modern browsers, but can be "polyfilled" for older browsers.
GraphiQL itself depends on React DOM.
If you do not want to rely on a CDN, you can host these files locally or
include them directly in your favored resource bunder.
-->
<script src="//cdn.jsdelivr.net/react/15.4.2/react.min.js"></script>
<script src="//cdn.jsdelivr.net/react/15.4.2/react-dom.min.js"></script>
<!--
These two files can be found in the npm module, however you may wish to
copy them directly into your environment, or perhaps include them in your
favored resource bundler.
-->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/graphiql/graphiql.css" />
<script src="https://cdn.jsdelivr.net/npm/graphiql/graphiql.js" charset="utf-8"></script>
<script src="https://cdn.jsdelivr.net/npm/@sifrr/fetch" charset="utf-8"></script>
</head>
<body>
<div id="graphiql">Loading...</div>
<script>
/**
* This GraphiQL example illustrates how to use some of GraphiQL's props
* in order to enable reading and updating the URL parameters, making
* link sharing of queries a little bit easier.
*
* This is only one example of this kind of feature, GraphiQL exposes
* various React params to enable interesting integrations.
*/
// Parse the search string to get url parameters.
var search = window.location.search;
var parameters = {};
search
.substr(1)
.split('&')
.forEach(function(entry) {
var eq = entry.indexOf('=');
if (eq >= 0) {
parameters[decodeURIComponent(entry.slice(0, eq))] = decodeURIComponent(
entry.slice(eq + 1)
);
}
});
// if variables was provided, try to format it.
if (parameters.variables) {
try {
parameters.variables = JSON.stringify(JSON.parse(parameters.variables), null, 2);
} catch (e) {
// Do nothing, we want to display the invalid JSON as a string, rather
// than present an error.
}
}
// When the query and variables string is edited, update the URL bar so
// that it can be easily shared
function onEditQuery(newQuery) {
parameters.query = newQuery;
updateURL();
}
function onEditVariables(newVariables) {
parameters.variables = newVariables;
updateURL();
}
function onEditOperationName(newOperationName) {
parameters.operationName = newOperationName;
updateURL();
}
function updateURL() {
var newSearch =
'?' +
Object.keys(parameters)
.filter(function(key) {
return Boolean(parameters[key]);
})
.map(function(key) {
return encodeURIComponent(key) + '=' + encodeURIComponent(parameters[key]);
})
.join('&');
history.replaceState(null, null, newSearch);
}
// Defines a GraphQL fetcher using the fetch API. You're not required to
// use fetch, and could instead implement graphQLFetcher however you like,
// as long as it returns a Promise or Observable.
function graphQLFetcher(graphQLParams) {
// When working locally, the example expects a GraphQL server at the path /graphql.
// In a PR preview, it connects to the Star Wars API externally.
// Change this to point wherever you host your GraphQL server.
const api = '/graphql';
return Sifrr.Fetch.graphql(api, {
...graphQLParams
});
}
// Render <GraphiQL /> into the body.
// See the README in the top level of this module to learn more about
// how you can customize GraphiQL by providing different values or
// additional child elements.
ReactDOM.render(
React.createElement(GraphiQL, {
fetcher: graphQLFetcher,
query: parameters.query,
variables: parameters.variables,
operationName: parameters.operationName,
onEditQuery: onEditQuery,
onEditVariables: onEditVariables,
onEditOperationName: onEditOperationName
}),
document.getElementById('graphiql')
);
</script>
</body>
</html>

View File

@ -0,0 +1,138 @@
import { parse } from 'query-string';
import { createAsyncIterator, forAwaitEach, isAsyncIterable } from 'iterall';
import { HttpResponse, HttpRequest } from 'uWebSockets.js';
// client -> server
const GQL_START = 'start';
const GQL_STOP = 'stop';
// server -> client
const GQL_DATA = 'data';
const GQL_QUERY = 'query';
async function getGraphqlParams(res: HttpResponse, req: HttpRequest) {
// query and variables
const queryParams = parse(req.getQuery());
let { query, variables, operationName } = queryParams;
if (typeof variables === 'string') variables = JSON.parse(variables);
// body
if (res && typeof res.json === 'function') {
const data = await res.json();
query = data.query || query;
variables = data.variables || variables;
operationName = data.operationName || operationName;
}
return {
source: query,
variableValues: variables,
operationName
};
}
function graphqlPost(schema, graphqlOptions: any = {}, graphql: any = {}) {
const execute = graphql.graphql || require('graphql').graphql;
return async (res: HttpResponse, req: HttpRequest) => {
res.onAborted(console.error);
res.writeHeader('content-type', 'application/json');
res.end(
JSON.stringify(
await execute({
schema,
...(await getGraphqlParams(res, req)),
...graphqlOptions,
contextValue: {
res,
req,
...(graphqlOptions &&
(graphqlOptions.contextValue ||
(graphqlOptions.contextFxn && (await graphqlOptions.contextFxn(res, req)))))
}
})
)
);
};
}
function stopGqsSubscription(operations, reqOpId) {
if (!reqOpId) return;
operations[reqOpId] && operations[reqOpId].return && operations[reqOpId].return();
delete operations[reqOpId];
}
function graphqlWs(schema, graphqlOptions: any = {}, uwsOptions: any = {}, graphql: any = {}) {
const subscribe = graphql.subscribe || require('graphql').subscribe;
const execute = graphql.graphql || require('graphql').graphql;
return {
open: (ws, req) => {
ws.req = req;
ws.operations = {};
ws.opId = 1;
},
message: async (ws, message) => {
const { type, payload = {}, id: reqOpId } = JSON.parse(Buffer.from(message).toString('utf8'));
let opId;
if (reqOpId) {
opId = reqOpId;
} else {
opId = ws.opId++;
}
const params = {
schema,
source: payload.query,
variableValues: payload.variables,
operationName: payload.operationName,
contextValue: {
ws,
...(graphqlOptions &&
(graphqlOptions.contextValue ||
(graphqlOptions.contextFxn && (await graphqlOptions.contextFxn(ws)))))
},
...graphqlOptions
};
switch (type) {
case GQL_START:
stopGqsSubscription(ws.operations, opId);
// eslint-disable-next-line no-case-declarations
let asyncIterable = await subscribe(
params.schema,
graphql.parse(params.source),
params.rootValue,
params.contextValue,
params.variableValues,
params.operationName
);
asyncIterable = isAsyncIterable(asyncIterable)
? asyncIterable
: createAsyncIterator([asyncIterable]);
forAwaitEach(asyncIterable, result =>
ws.send(
JSON.stringify({
id: opId,
type: GQL_DATA,
payload: result
})
)
);
break;
case GQL_STOP:
stopGqsSubscription(ws.operations, reqOpId);
break;
default:
ws.send(JSON.stringify({ payload: await execute(params), type: GQL_QUERY, id: opId }));
break;
}
},
idleTimeout: 24 * 60 * 60,
...uwsOptions
};
}
export { graphqlPost, graphqlWs };

View File

@ -0,0 +1,35 @@
import { WebSocketBehavior, WebSocket } from 'uWebSockets.js';
const websockets = {};
let id = 0;
const wsConfig: WebSocketBehavior = {
open: (ws: WebSocket & { id: number }, req) => {
websockets[id] = {
dirty: false
};
ws.id = id;
console.log('websocket connected: ', id);
id++;
},
message: ws => {
ws.send(JSON.stringify(websockets[ws.id].dirty));
websockets[ws.id].dirty = false;
},
close: (ws, code, message) => {
delete websockets[ws.id];
console.log(
`websocket disconnected with code ${code} and message ${message}:`,
ws.id,
websockets
);
}
};
const sendSignal = (type: string, path: string) => {
console.log(type, 'signal for file: ', path);
for (let i in websockets) websockets[i].dirty = true;
};
export default { websockets, wsConfig, sendSignal };
export { websockets, wsConfig, sendSignal };

View File

@ -0,0 +1,47 @@
const loc = window.location;
let path;
if (loc.protocol === 'https:') {
path = 'wss:';
} else {
path = 'ws:';
}
path += '//' + loc.host + '/__sifrrLiveReload';
let ws,
ttr = 500,
timeout;
function newWsConnection() {
ws = new WebSocket(path);
ws.onopen = function() {
ttr = 500;
checkMessage();
console.log('watching for file changes through sifrr-server livereload mode.');
};
ws.onmessage = function(event) {
if (JSON.parse(event.data)) {
console.log('Files changed, refreshing page.');
location.reload();
}
};
ws.onerror = e => {
console.error('Webosocket error: ', e);
console.log('Retrying after ', ttr / 4, 'ms');
ttr *= 4;
};
ws.onclose = e => {
console.error(`Webosocket closed with code \${e.code} error \${e.message}`);
};
}
function checkMessage() {
if (!ws) return;
if (ws.readyState === WebSocket.OPEN) ws.send('');
else if (ws.readyState === WebSocket.CLOSED) newWsConnection();
if (timeout) clearTimeout(timeout);
timeout = setTimeout(checkMessage, ttr);
}
newWsConnection();
setTimeout(checkMessage, ttr);

View File

@ -0,0 +1,42 @@
import { statSync, readdirSync } from 'fs';
import { join, extname } from 'path';
function loadRoutes(dir, { filter = () => true, basePath = '' } = {}) {
let files;
const paths = [];
if (statSync(dir).isDirectory()) {
files = readdirSync(dir)
.filter(filter)
.map(file => join(dir, file));
} else {
files = [dir];
}
files.forEach(file => {
if (statSync(file).isDirectory()) {
// Recursive if directory
paths.push(...loadRoutes.call(this, file, { filter, basePath }));
} else if (extname(file) === '.js') {
const routes = require(file);
let basePaths = routes.basePath || [''];
delete routes.basePath;
if (typeof basePaths === 'string') basePaths = [basePaths];
basePaths.forEach(basep => {
for (const method in routes) {
const methodRoutes = routes[method];
for (let r in methodRoutes) {
if (!Array.isArray(methodRoutes[r])) methodRoutes[r] = [methodRoutes[r]];
this[method](basePath + basep + r, ...methodRoutes[r]);
paths.push(basePath + basep + r);
}
}
});
}
});
return paths;
}
export default loadRoutes;

View File

@ -0,0 +1,176 @@
const mimes = {
'3gp': 'video/3gpp',
a: 'application/octet-stream',
ai: 'application/postscript',
aif: 'audio/x-aiff',
aiff: 'audio/x-aiff',
asc: 'application/pgp-signature',
asf: 'video/x-ms-asf',
asm: 'text/x-asm',
asx: 'video/x-ms-asf',
atom: 'application/atom+xml',
au: 'audio/basic',
avi: 'video/x-msvideo',
bat: 'application/x-msdownload',
bin: 'application/octet-stream',
bmp: 'image/bmp',
bz2: 'application/x-bzip2',
c: 'text/x-c',
cab: 'application/vnd.ms-cab-compressed',
cc: 'text/x-c',
chm: 'application/vnd.ms-htmlhelp',
class: 'application/octet-stream',
com: 'application/x-msdownload',
conf: 'text/plain',
cpp: 'text/x-c',
crt: 'application/x-x509-ca-cert',
css: 'text/css',
csv: 'text/csv',
cxx: 'text/x-c',
deb: 'application/x-debian-package',
der: 'application/x-x509-ca-cert',
diff: 'text/x-diff',
djv: 'image/vnd.djvu',
djvu: 'image/vnd.djvu',
dll: 'application/x-msdownload',
dmg: 'application/octet-stream',
doc: 'application/msword',
dot: 'application/msword',
dtd: 'application/xml-dtd',
dvi: 'application/x-dvi',
ear: 'application/java-archive',
eml: 'message/rfc822',
eps: 'application/postscript',
exe: 'application/x-msdownload',
f: 'text/x-fortran',
f77: 'text/x-fortran',
f90: 'text/x-fortran',
flv: 'video/x-flv',
for: 'text/x-fortran',
gem: 'application/octet-stream',
gemspec: 'text/x-script.ruby',
gif: 'image/gif',
gz: 'application/x-gzip',
h: 'text/x-c',
hh: 'text/x-c',
htm: 'text/html',
html: 'text/html',
ico: 'image/vnd.microsoft.icon',
ics: 'text/calendar',
ifb: 'text/calendar',
iso: 'application/octet-stream',
jar: 'application/java-archive',
java: 'text/x-java-source',
jnlp: 'application/x-java-jnlp-file',
jpeg: 'image/jpeg',
jpg: 'image/jpeg',
js: 'application/javascript',
json: 'application/json',
log: 'text/plain',
m3u: 'audio/x-mpegurl',
m4v: 'video/mp4',
man: 'text/troff',
mathml: 'application/mathml+xml',
mbox: 'application/mbox',
mdoc: 'text/troff',
me: 'text/troff',
mid: 'audio/midi',
midi: 'audio/midi',
mime: 'message/rfc822',
mjs: 'application/javascript',
mml: 'application/mathml+xml',
mng: 'video/x-mng',
mov: 'video/quicktime',
mp3: 'audio/mpeg',
mp4: 'video/mp4',
mp4v: 'video/mp4',
mpeg: 'video/mpeg',
mpg: 'video/mpeg',
ms: 'text/troff',
msi: 'application/x-msdownload',
odp: 'application/vnd.oasis.opendocument.presentation',
ods: 'application/vnd.oasis.opendocument.spreadsheet',
odt: 'application/vnd.oasis.opendocument.text',
ogg: 'application/ogg',
p: 'text/x-pascal',
pas: 'text/x-pascal',
pbm: 'image/x-portable-bitmap',
pdf: 'application/pdf',
pem: 'application/x-x509-ca-cert',
pgm: 'image/x-portable-graymap',
pgp: 'application/pgp-encrypted',
pkg: 'application/octet-stream',
pl: 'text/x-script.perl',
pm: 'text/x-script.perl-module',
png: 'image/png',
pnm: 'image/x-portable-anymap',
ppm: 'image/x-portable-pixmap',
pps: 'application/vnd.ms-powerpoint',
ppt: 'application/vnd.ms-powerpoint',
ps: 'application/postscript',
psd: 'image/vnd.adobe.photoshop',
py: 'text/x-script.python',
qt: 'video/quicktime',
ra: 'audio/x-pn-realaudio',
rake: 'text/x-script.ruby',
ram: 'audio/x-pn-realaudio',
rar: 'application/x-rar-compressed',
rb: 'text/x-script.ruby',
rdf: 'application/rdf+xml',
roff: 'text/troff',
rpm: 'application/x-redhat-package-manager',
rss: 'application/rss+xml',
rtf: 'application/rtf',
ru: 'text/x-script.ruby',
s: 'text/x-asm',
sgm: 'text/sgml',
sgml: 'text/sgml',
sh: 'application/x-sh',
sig: 'application/pgp-signature',
snd: 'audio/basic',
so: 'application/octet-stream',
svg: 'image/svg+xml',
svgz: 'image/svg+xml',
swf: 'application/x-shockwave-flash',
t: 'text/troff',
tar: 'application/x-tar',
tbz: 'application/x-bzip-compressed-tar',
tcl: 'application/x-tcl',
tex: 'application/x-tex',
texi: 'application/x-texinfo',
texinfo: 'application/x-texinfo',
text: 'text/plain',
tif: 'image/tiff',
tiff: 'image/tiff',
torrent: 'application/x-bittorrent',
tr: 'text/troff',
txt: 'text/plain',
vcf: 'text/x-vcard',
vcs: 'text/x-vcalendar',
vrml: 'model/vrml',
war: 'application/java-archive',
wav: 'audio/x-wav',
wma: 'audio/x-ms-wma',
wmv: 'video/x-ms-wmv',
wmx: 'video/x-ms-wmx',
wrl: 'model/vrml',
wsdl: 'application/wsdl+xml',
xbm: 'image/x-xbitmap',
xhtml: 'application/xhtml+xml',
xls: 'application/vnd.ms-excel',
xml: 'application/xml',
xpm: 'image/x-xpixmap',
xsl: 'application/xml',
xslt: 'application/xslt+xml',
yaml: 'text/yaml',
yml: 'text/yaml',
zip: 'application/zip',
default: 'text/html'
};
const getMime = (path: string): string => {
const i = path.lastIndexOf('.');
return mimes[path.substr(i + 1).toLowerCase()] || mimes['default'];
};
export { getMime, mimes };

View File

@ -0,0 +1,172 @@
import { watch, statSync, createReadStream } from 'fs';
import { createBrotliCompress, createGzip, createDeflate } from 'zlib';
const watchedPaths = new Set();
const compressions = {
br: createBrotliCompress,
gzip: createGzip,
deflate: createDeflate
};
import { writeHeaders } from './utils';
import { getMime } from './mime';
const bytes = 'bytes=';
import { stob } from './utils';
import { sendSignal } from './livereload';
import { SendFileOptions } from './types';
import { HttpResponse, HttpRequest } from 'uWebSockets.js';
function sendFile(res: HttpResponse, req: HttpRequest, path: string, options: SendFileOptions) {
if (options && options.livereload && !watchedPaths.has(path)) {
watchedPaths.add(path);
watch(path, sendSignal);
}
sendFileToRes(
res,
{
'if-modified-since': req.getHeader('if-modified-since'),
range: req.getHeader('range'),
'accept-encoding': req.getHeader('accept-encoding')
},
path,
options
);
}
function sendFileToRes(
res: HttpResponse,
reqHeaders: { [name: string]: string },
path: string,
{
lastModified = true,
headers = {},
compress = false,
compressionOptions = {
priority: ['gzip', 'br', 'deflate']
},
cache = false
}: { cache: any } & any = {}
) {
let { mtime, size } = statSync(path);
mtime.setMilliseconds(0);
const mtimeutc = mtime.toUTCString();
headers = Object.assign({}, headers);
// handling last modified
if (lastModified) {
// Return 304 if last-modified
if (reqHeaders['if-modified-since']) {
if (new Date(reqHeaders['if-modified-since']) >= mtime) {
res.writeStatus('304 Not Modified');
return res.end();
}
}
headers['last-modified'] = mtimeutc;
}
headers['content-type'] = getMime(path);
// write data
let start = 0,
end = size - 1;
if (reqHeaders.range) {
compress = false;
const parts = reqHeaders.range.replace(bytes, '').split('-');
start = parseInt(parts[0], 10);
end = parts[1] ? parseInt(parts[1], 10) : end;
headers['accept-ranges'] = 'bytes';
headers['content-range'] = `bytes ${start}-${end}/${size}`;
size = end - start + 1;
res.writeStatus('206 Partial Content');
}
// for size = 0
if (end < 0) end = 0;
let readStream = createReadStream(path, { start, end });
// Compression;
let compressed: boolean | string = false;
if (compress) {
const l = compressionOptions.priority.length;
for (let i = 0; i < l; i++) {
const type = compressionOptions.priority[i];
if (reqHeaders['accept-encoding'].indexOf(type) > -1) {
compressed = type;
const compressor = compressions[type](compressionOptions);
readStream.pipe(compressor);
readStream = compressor;
headers['content-encoding'] = compressionOptions.priority[i];
break;
}
}
}
res.onAborted(() => readStream.destroy());
writeHeaders(res, headers);
// check cache
if (cache) {
return cache.wrap(
`${path}_${mtimeutc}_${start}_${end}_${compressed}`,
cb => {
stob(readStream)
.then(b => cb(null, b))
.catch(cb);
},
{ ttl: 0 },
(err, buffer) => {
if (err) {
res.writeStatus('500 Internal server error');
res.end();
throw err;
}
res.end(buffer);
}
);
} else if (compressed) {
readStream.on('data', buffer => {
res.write(buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength));
});
} else {
readStream.on('data', buffer => {
const chunk = buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength),
lastOffset = res.getWriteOffset();
// First try
const [ok, done] = res.tryEnd(chunk, size);
if (done) {
readStream.destroy();
} else if (!ok) {
// pause because backpressure
readStream.pause();
// Save unsent chunk for later
res.ab = chunk;
res.abOffset = lastOffset;
// Register async handlers for drainage
res.onWritable(offset => {
const [ok, done] = res.tryEnd(res.ab.slice(offset - res.abOffset), size);
if (done) {
readStream.destroy();
} else if (ok) {
readStream.resume();
}
return ok;
});
}
});
}
readStream
.on('error', e => {
res.writeStatus('500 Internal server error');
res.end();
readStream.destroy();
throw e;
})
.on('end', () => {
res.end();
});
}
export default sendFile;

View File

@ -0,0 +1,13 @@
import { SSLApp as _SSLApp, AppOptions } from 'uWebSockets.js';
import BaseApp from './baseapp';
import { extend } from './utils';
import { UwsApp } from './types';
class SSLApp extends (<UwsApp>_SSLApp) {
constructor(options: AppOptions) {
super(options);
extend(this, new BaseApp());
}
}
export default SSLApp;

View File

@ -0,0 +1,26 @@
import { AppOptions, TemplatedApp, HttpResponse, HttpRequest } from 'uWebSockets.js';
export type UwsApp = {
(options: AppOptions): TemplatedApp;
new (options: AppOptions): TemplatedApp;
prototype: TemplatedApp;
};
export type SendFileOptions = {
failOnDuplicateRoute?: boolean;
overwriteRoute?: boolean;
watch?: boolean;
filter?: (path: string) => boolean;
livereload?: boolean;
lastModified?: boolean;
headers?: { [name: string]: string };
compress?: boolean;
compressionOptions?: {
priority?: 'gzip' | 'br' | 'deflate';
};
cache?: boolean;
};
export type Handler = (res: HttpResponse, req: HttpRequest) => void;
export {};

View File

@ -0,0 +1,52 @@
import { HttpResponse } from 'uWebSockets.js';
import { ReadStream } from 'fs';
function writeHeaders(
res: HttpResponse,
headers: { [name: string]: string } | string,
other?: string
) {
if (typeof headers === 'string') {
res.writeHeader(headers, other.toString());
} else {
for (const n in headers) {
res.writeHeader(n, headers[n].toString());
}
}
}
function extend(who: object, from: object, overwrite = true) {
const ownProps = Object.getOwnPropertyNames(Object.getPrototypeOf(from)).concat(
Object.keys(from)
);
ownProps.forEach(prop => {
if (prop === 'constructor' || from[prop] === undefined) return;
if (who[prop] && overwrite) {
who[`_${prop}`] = who[prop];
}
if (typeof from[prop] === 'function') who[prop] = from[prop].bind(who);
else who[prop] = from[prop];
});
}
function stob(stream: ReadStream): Promise<Buffer> {
return new Promise(resolve => {
const buffers = [];
stream.on('data', buffers.push.bind(buffers));
stream.on('end', () => {
switch (buffers.length) {
case 0:
resolve(Buffer.allocUnsafe(0));
break;
case 1:
resolve(buffers[0]);
break;
default:
resolve(Buffer.concat(buffers));
}
});
});
}
export { writeHeaders, extend, stob };

View File

@ -0,0 +1,30 @@
import { parse } from 'query-string';
import { HttpRequest } from 'uWebSockets.js';
import App from './server/app';
import SSLApp from './server/sslapp';
import { mimes, getMime } from './server/mime';
import { writeHeaders } from './server/utils';
import sendFile from './server/sendfile';
import Cluster from './server/cluster';
import livereload from './server/livereload';
import * as types from './server/types';
const getQuery = (req: HttpRequest) => {
return parse(req.getQuery());
};
export { App, SSLApp, mimes, getMime, writeHeaders, sendFile, Cluster, livereload, getQuery };
export * from './server/types';
export default {
App,
SSLApp,
mimes,
getMime,
writeHeaders,
sendFile,
Cluster,
livereload,
getQuery,
...types
};

View File

@ -20,13 +20,6 @@
chalk "^2.0.0"
js-tokens "^4.0.0"
"@types/body-parser@*":
version "1.19.0"
resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.0.tgz#0685b3c47eb3006ffed117cdd55164b61f80538f"
dependencies:
"@types/connect" "*"
"@types/node" "*"
"@types/circular-json@^0.4.0":
version "0.4.0"
resolved "https://registry.yarnpkg.com/@types/circular-json/-/circular-json-0.4.0.tgz#7401f7e218cfe87ad4c43690da5658b9acaf51be"
@ -36,32 +29,10 @@
version "1.1.1"
resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0"
"@types/connect@*":
version "3.4.33"
resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.33.tgz#31610c901eca573b8713c3330abc6e6b9f588546"
dependencies:
"@types/node" "*"
"@types/eslint-visitor-keys@^1.0.0":
version "1.0.0"
resolved "https://registry.yarnpkg.com/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#1ee30d79544ca84d68d4b3cdb0af4f205663dd2d"
"@types/express-serve-static-core@*":
version "4.17.3"
resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.3.tgz#dc8068ee3e354d7fba69feb86b3dfeee49b10f09"
dependencies:
"@types/node" "*"
"@types/range-parser" "*"
"@types/express@^4.17.4":
version "4.17.4"
resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.4.tgz#e78bf09f3f530889575f4da8a94cd45384520aac"
dependencies:
"@types/body-parser" "*"
"@types/express-serve-static-core" "*"
"@types/qs" "*"
"@types/serve-static" "*"
"@types/google-protobuf@^3.7.3":
version "3.7.3"
resolved "https://registry.yarnpkg.com/@types/google-protobuf/-/google-protobuf-3.7.3.tgz#429512e541bbd777f2c867692e6335ee08d1f6d4"
@ -87,35 +58,10 @@
dependencies:
"@types/node" "*"
"@types/mime@*":
version "2.0.1"
resolved "https://registry.yarnpkg.com/@types/mime/-/mime-2.0.1.tgz#dc488842312a7f075149312905b5e3c0b054c79d"
"@types/node@*":
version "13.11.0"
resolved "https://registry.yarnpkg.com/@types/node/-/node-13.11.0.tgz#390ea202539c61c8fa6ba4428b57e05bc36dc47b"
"@types/qs@*":
version "6.9.1"
resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.1.tgz#937fab3194766256ee09fcd40b781740758617e7"
"@types/range-parser@*":
version "1.2.3"
resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.3.tgz#7ee330ba7caafb98090bece86a5ee44115904c2c"
"@types/serve-static@*":
version "1.13.3"
resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.13.3.tgz#eb7e1c41c4468272557e897e9171ded5e2ded9d1"
dependencies:
"@types/express-serve-static-core" "*"
"@types/mime" "*"
"@types/socket.io@^2.1.4":
version "2.1.4"
resolved "https://registry.yarnpkg.com/@types/socket.io/-/socket.io-2.1.4.tgz#674e7bc193c5ccdadd4433f79f3660d31759e9ac"
dependencies:
"@types/node" "*"
"@types/strip-bom@^3.0.0":
version "3.0.0"
resolved "https://registry.yarnpkg.com/@types/strip-bom/-/strip-bom-3.0.0.tgz#14a8ec3956c2e81edb7520790aecf21c290aebd2"
@ -169,13 +115,6 @@
semver "^6.3.0"
tsutils "^3.17.1"
accepts@~1.3.4, accepts@~1.3.7:
version "1.3.7"
resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd"
dependencies:
mime-types "~2.1.24"
negotiator "0.6.2"
acorn-jsx@^5.2.0:
version "5.2.0"
resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.2.0.tgz#4c66069173d6fdd68ed85239fc256226182b2ebe"
@ -184,10 +123,6 @@ acorn@^7.1.1:
version "7.1.1"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.1.1.tgz#e35668de0b402f359de515c5482a1ab9f89a69bf"
after@0.8.2:
version "0.8.2"
resolved "https://registry.yarnpkg.com/after/-/after-0.8.2.tgz#fedb394f9f0e02aa9768e702bda23b505fae7e1f"
ajv@^6.10.0, ajv@^6.10.2:
version "6.12.0"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.0.tgz#06d60b96d87b8454a5adaba86e7854da629db4b7"
@ -238,22 +173,10 @@ array-find-index@^1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/array-find-index/-/array-find-index-1.0.2.tgz#df010aa1287e164bbda6f9723b0a96a1ec4187a1"
array-flatten@1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2"
arraybuffer.slice@~0.0.7:
version "0.0.7"
resolved "https://registry.yarnpkg.com/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz#3bbc4275dd584cc1b10809b89d4e8b63a69e7675"
astral-regex@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-1.0.0.tgz#6c8c3fb827dd43ee3918f27b82782ab7658a6fd9"
async-limiter@~1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.1.tgz#dd379e94f0db8310b08291f9d64c3209766617fd"
axios@^0.20.0:
version "0.20.0"
resolved "https://registry.yarnpkg.com/axios/-/axios-0.20.0.tgz#057ba30f04884694993a8cd07fa394cff11c50bd"
@ -261,38 +184,16 @@ axios@^0.20.0:
dependencies:
follow-redirects "^1.10.0"
backo2@1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/backo2/-/backo2-1.0.2.tgz#31ab1ac8b129363463e35b3ebb69f4dfcfba7947"
balanced-match@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
base64-arraybuffer@0.1.5:
version "0.1.5"
resolved "https://registry.yarnpkg.com/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz#73926771923b5a19747ad666aa5cd4bf9c6e9ce8"
base64id@2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/base64id/-/base64id-2.0.0.tgz#2770ac6bc47d312af97a8bf9a634342e0cd25cb6"
better-assert@~1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/better-assert/-/better-assert-1.0.2.tgz#40866b9e1b9e0b55b481894311e68faffaebc522"
dependencies:
callsite "1.0.0"
bintrees@1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/bintrees/-/bintrees-1.0.1.tgz#0e655c9b9c2435eaab68bf4027226d2b55a34524"
integrity sha1-DmVcm5wkNeqraL9AJyJtK1WjRSQ=
blob@0.0.5:
version "0.0.5"
resolved "https://registry.yarnpkg.com/blob/-/blob-0.0.5.tgz#d680eeef25f8cd91ad533f5b01eed48e64caf683"
body-parser@1.19.0, body-parser@^1.19.0:
body-parser@^1.19.0:
version "1.19.0"
resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.0.tgz#96b2709e57c9c4e09a6fd66a8fd979844f69f08a"
dependencies:
@ -322,14 +223,17 @@ buffer-from@^1.0.0:
version "1.1.1"
resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef"
busboy@^0.3.1:
version "0.3.1"
resolved "https://registry.yarnpkg.com/busboy/-/busboy-0.3.1.tgz#170899274c5bf38aae27d5c62b71268cd585fd1b"
integrity sha512-y7tTxhGKXcyBxRKAni+awqx8uqaJKrSFSNFSeRG5CsWNdmy2BIK+6VGWEW7TZnIO/533mtMEA4rOevQV815YJw==
dependencies:
dicer "0.3.0"
bytes@3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6"
callsite@1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/callsite/-/callsite-1.0.0.tgz#280398e5d664bd74038b6f0905153e6e8af1bc20"
callsites@^3.0.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73"
@ -399,44 +303,14 @@ color-name@~1.1.4:
version "1.1.4"
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
component-bind@1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/component-bind/-/component-bind-1.0.0.tgz#00c608ab7dcd93897c0009651b1d3a8e1e73bbd1"
component-emitter@1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.2.1.tgz#137918d6d78283f7df7a6b7c5a63e140e69425e6"
component-inherit@0.0.3:
version "0.0.3"
resolved "https://registry.yarnpkg.com/component-inherit/-/component-inherit-0.0.3.tgz#645fc4adf58b72b649d5cae65135619db26ff143"
concat-map@0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
content-disposition@0.5.3:
version "0.5.3"
resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.3.tgz#e130caf7e7279087c5616c2007d0485698984fbd"
dependencies:
safe-buffer "5.1.2"
content-type@~1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b"
cookie-signature@1.0.6:
version "1.0.6"
resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c"
cookie@0.3.1:
version "0.3.1"
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb"
cookie@0.4.0:
version "0.4.0"
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.0.tgz#beb437e7022b3b6d49019d088665303ebe9c14ba"
cross-spawn@^6.0.5:
version "6.0.5"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4"
@ -470,22 +344,21 @@ debug@2.6.9:
dependencies:
ms "2.0.0"
debug@^4.0.1, debug@^4.1.1, debug@~4.1.0:
debug@^4.0.1, debug@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791"
dependencies:
ms "^2.1.1"
debug@~3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
dependencies:
ms "2.0.0"
decamelize@^1.1.2:
version "1.2.0"
resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
decode-uri-component@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545"
integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=
deep-is@~0.1.3:
version "0.1.3"
resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34"
@ -494,9 +367,12 @@ depd@~1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9"
destroy@~1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80"
dicer@0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/dicer/-/dicer-0.3.0.tgz#eacd98b3bfbf92e8ab5c2fdb71aaac44bb06b872"
integrity sha512-MdceRRWqltEG2dZqO769g27N/3PXfcKl04VhYnBlo2YhH7zPi88VebsjTKclaOyiuMaGU72hTfw3VkUitGcVCA==
dependencies:
streamsearch "0.1.2"
diff@^4.0.1:
version "4.0.2"
@ -532,57 +408,12 @@ emoji-regex@^8.0.0:
version "8.0.0"
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37"
encodeurl@~1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59"
engine.io-client@~3.4.0:
version "3.4.0"
resolved "https://registry.yarnpkg.com/engine.io-client/-/engine.io-client-3.4.0.tgz#82a642b42862a9b3f7a188f41776b2deab643700"
dependencies:
component-emitter "1.2.1"
component-inherit "0.0.3"
debug "~4.1.0"
engine.io-parser "~2.2.0"
has-cors "1.1.0"
indexof "0.0.1"
parseqs "0.0.5"
parseuri "0.0.5"
ws "~6.1.0"
xmlhttprequest-ssl "~1.5.4"
yeast "0.1.2"
engine.io-parser@~2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/engine.io-parser/-/engine.io-parser-2.2.0.tgz#312c4894f57d52a02b420868da7b5c1c84af80ed"
dependencies:
after "0.8.2"
arraybuffer.slice "~0.0.7"
base64-arraybuffer "0.1.5"
blob "0.0.5"
has-binary2 "~1.0.2"
engine.io@~3.4.0:
version "3.4.0"
resolved "https://registry.yarnpkg.com/engine.io/-/engine.io-3.4.0.tgz#3a962cc4535928c252759a00f98519cb46c53ff3"
dependencies:
accepts "~1.3.4"
base64id "2.0.0"
cookie "0.3.1"
debug "~4.1.0"
engine.io-parser "~2.2.0"
ws "^7.1.2"
error-ex@^1.2.0:
version "1.3.2"
resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf"
dependencies:
is-arrayish "^0.2.1"
escape-html@~1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988"
escape-string-regexp@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
@ -688,45 +519,6 @@ esutils@^2.0.2:
version "2.0.3"
resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64"
etag@~1.8.1:
version "1.8.1"
resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887"
express@^4.17.1:
version "4.17.1"
resolved "https://registry.yarnpkg.com/express/-/express-4.17.1.tgz#4491fc38605cf51f8629d39c2b5d026f98a4c134"
dependencies:
accepts "~1.3.7"
array-flatten "1.1.1"
body-parser "1.19.0"
content-disposition "0.5.3"
content-type "~1.0.4"
cookie "0.4.0"
cookie-signature "1.0.6"
debug "2.6.9"
depd "~1.1.2"
encodeurl "~1.0.2"
escape-html "~1.0.3"
etag "~1.8.1"
finalhandler "~1.1.2"
fresh "0.5.2"
merge-descriptors "1.0.1"
methods "~1.1.2"
on-finished "~2.3.0"
parseurl "~1.3.3"
path-to-regexp "0.1.7"
proxy-addr "~2.0.5"
qs "6.7.0"
range-parser "~1.2.1"
safe-buffer "5.1.2"
send "0.17.1"
serve-static "1.14.1"
setprototypeof "1.1.1"
statuses "~1.5.0"
type-is "~1.6.18"
utils-merge "1.0.1"
vary "~1.1.2"
external-editor@^3.0.3:
version "3.1.0"
resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-3.1.0.tgz#cb03f740befae03ea4d283caed2741a83f335495"
@ -765,18 +557,6 @@ filewatcher@~3.0.0:
dependencies:
debounce "^1.0.0"
finalhandler@~1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.2.tgz#b7e7d000ffd11938d0fdb053506f6ebabe9f587d"
dependencies:
debug "2.6.9"
encodeurl "~1.0.2"
escape-html "~1.0.3"
on-finished "~2.3.0"
parseurl "~1.3.3"
statuses "~1.5.0"
unpipe "~1.0.0"
find-up@^1.0.0:
version "1.1.2"
resolved "https://registry.yarnpkg.com/find-up/-/find-up-1.1.2.tgz#6b2e9822b1a2ce0a60ab64d610eccad53cb24d0f"
@ -801,14 +581,6 @@ follow-redirects@^1.10.0:
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.13.0.tgz#b42e8d93a2a7eea5ed88633676d6597bc8e384db"
integrity sha512-aq6gF1BEKje4a9i9+5jimNFIpq4Q1WiwBToeRK5NvZBd/TRsmW8BsJfOEGkr76TbOyPVD3OVDN910EcUNtRYEA==
forwarded@~0.1.2:
version "0.1.2"
resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84"
fresh@0.5.2:
version "0.5.2"
resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7"
fs.realpath@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
@ -862,16 +634,6 @@ growly@^1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081"
has-binary2@~1.0.2:
version "1.0.3"
resolved "https://registry.yarnpkg.com/has-binary2/-/has-binary2-1.0.3.tgz#7776ac627f3ea77250cfc332dab7ddf5e4f5d11d"
dependencies:
isarray "2.0.1"
has-cors@1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/has-cors/-/has-cors-1.1.0.tgz#5e474793f7ea9843d1bb99c23eef49ff126fff39"
has-flag@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd"
@ -894,16 +656,6 @@ http-errors@1.7.2:
statuses ">= 1.5.0 < 2"
toidentifier "1.0.0"
http-errors@~1.7.2:
version "1.7.3"
resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.3.tgz#6c619e4f9c60308c38519498c14fbb10aacebb06"
dependencies:
depd "~1.1.2"
inherits "2.0.4"
setprototypeof "1.1.1"
statuses ">= 1.5.0 < 2"
toidentifier "1.0.0"
http-status-codes@*, http-status-codes@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/http-status-codes/-/http-status-codes-1.4.0.tgz#6e4c15d16ff3a9e2df03b89f3a55e1aae05fb477"
@ -935,10 +687,6 @@ indent-string@^2.1.0:
dependencies:
repeating "^2.0.0"
indexof@0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/indexof/-/indexof-0.0.1.tgz#82dc336d232b9062179d05ab3293a66059fd435d"
inflight@^1.0.4:
version "1.0.6"
resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"
@ -946,7 +694,7 @@ inflight@^1.0.4:
once "^1.3.0"
wrappy "1"
inherits@2, inherits@2.0.4:
inherits@2:
version "2.0.4"
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
@ -972,10 +720,6 @@ inquirer@^7.0.0:
strip-ansi "^6.0.0"
through "^2.3.6"
ipaddr.js@1.9.1:
version "1.9.1"
resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3"
is-arrayish@^0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d"
@ -1014,14 +758,15 @@ is-wsl@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-1.1.0.tgz#1f16e4aa22b04d1336b66188a66af3c600c3a66d"
isarray@2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.1.tgz#a37d94ed9cda2d59865c9f76fe596ee1f338741e"
isexe@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
iterall@^1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/iterall/-/iterall-1.3.0.tgz#afcb08492e2915cbd8a0884eb93a8c94d0d72fea"
integrity sha512-QZ9qOMdF+QLHxy1QIpUHUU1D5pS2CG2P69LF6L6CPjPYA/XMOmKV3PZpawHoAjHNyB0swdVTRxdYT4tbBbxqwg==
jasmine-core@~3.5.0:
version "3.5.0"
resolved "https://registry.yarnpkg.com/jasmine-core/-/jasmine-core-3.5.0.tgz#132c23e645af96d85c8bca13c8758b18429fc1e4"
@ -1165,14 +910,6 @@ meow@^3.3.0:
redent "^1.0.0"
trim-newlines "^1.0.0"
merge-descriptors@1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61"
methods@~1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee"
mime-db@1.43.0:
version "1.43.0"
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.43.0.tgz#0a12e0502650e473d735535050e7c8f4eb4fae58"
@ -1183,10 +920,6 @@ mime-types@~2.1.24:
dependencies:
mime-db "1.43.0"
mime@1.6.0:
version "1.6.0"
resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1"
mimic-fn@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b"
@ -1211,10 +944,6 @@ ms@2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
ms@2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a"
ms@^2.1.1:
version "2.1.2"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
@ -1227,10 +956,6 @@ natural-compare@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
negotiator@0.6.2:
version "0.6.2"
resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb"
nice-try@^1.0.4:
version "1.0.5"
resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366"
@ -1258,10 +983,6 @@ object-assign@^4.0.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
object-component@0.0.3:
version "0.0.3"
resolved "https://registry.yarnpkg.com/object-component/-/object-component-0.0.3.tgz#f0c69aa50efc95b866c186f400a33769cb2f1291"
on-finished@~2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947"
@ -1307,22 +1028,6 @@ parse-json@^2.2.0:
dependencies:
error-ex "^1.2.0"
parseqs@0.0.5:
version "0.0.5"
resolved "https://registry.yarnpkg.com/parseqs/-/parseqs-0.0.5.tgz#d5208a3738e46766e291ba2ea173684921a8b89d"
dependencies:
better-assert "~1.0.0"
parseuri@0.0.5:
version "0.0.5"
resolved "https://registry.yarnpkg.com/parseuri/-/parseuri-0.0.5.tgz#80204a50d4dbb779bfdc6ebe2778d90e4bce320a"
dependencies:
better-assert "~1.0.0"
parseurl@~1.3.3:
version "1.3.3"
resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4"
path-exists@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-2.1.0.tgz#0feb6c64f0fc518d9a754dd5efb62c7022761f4b"
@ -1341,10 +1046,6 @@ path-parse@^1.0.6:
version "1.0.6"
resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c"
path-to-regexp@0.1.7:
version "0.1.7"
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c"
path-type@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/path-type/-/path-type-1.1.0.tgz#59c44f7ee491da704da415da5a4070ba4f8fe441"
@ -1382,13 +1083,6 @@ prom-client@^12.0.0:
dependencies:
tdigest "^0.1.1"
proxy-addr@~2.0.5:
version "2.0.6"
resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.6.tgz#fdc2336505447d3f2f2c638ed272caf614bbb2bf"
dependencies:
forwarded "~0.1.2"
ipaddr.js "1.9.1"
punycode@^2.1.0:
version "2.1.1"
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
@ -1397,9 +1091,14 @@ qs@6.7.0:
version "6.7.0"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc"
range-parser@~1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031"
query-string@^6.13.3:
version "6.13.3"
resolved "https://registry.yarnpkg.com/query-string/-/query-string-6.13.3.tgz#57d1c20e828b0e562d66b7f71a4998bd57f84112"
integrity sha512-dldo2oHe3sg03iPshlHw/64nkaRUJKdS0FW85kmWQkmCkqUbNdNdgkgtAufJcEpjzrx6Q9EW9Y3xqx/rM9pGhw==
dependencies:
decode-uri-component "^0.2.0"
split-on-first "^1.0.0"
strict-uri-encode "^2.0.0"
raw-body@2.4.0:
version "2.4.0"
@ -1487,10 +1186,6 @@ rxjs@^6.5.3:
dependencies:
tslib "^1.9.0"
safe-buffer@5.1.2:
version "5.1.2"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
safe-buffer@^5.0.1:
version "5.2.0"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.0.tgz#b74daec49b1148f88c64b68d49b1e815c1f2f519"
@ -1507,33 +1202,6 @@ semver@^6.1.2, semver@^6.3.0:
version "6.3.0"
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
send@0.17.1:
version "0.17.1"
resolved "https://registry.yarnpkg.com/send/-/send-0.17.1.tgz#c1d8b059f7900f7466dd4938bdc44e11ddb376c8"
dependencies:
debug "2.6.9"
depd "~1.1.2"
destroy "~1.0.4"
encodeurl "~1.0.2"
escape-html "~1.0.3"
etag "~1.8.1"
fresh "0.5.2"
http-errors "~1.7.2"
mime "1.6.0"
ms "2.1.1"
on-finished "~2.3.0"
range-parser "~1.2.1"
statuses "~1.5.0"
serve-static@1.14.1:
version "1.14.1"
resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.14.1.tgz#666e636dc4f010f7ef29970a88a674320898b2f9"
dependencies:
encodeurl "~1.0.2"
escape-html "~1.0.3"
parseurl "~1.3.3"
send "0.17.1"
setprototypeof@1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.1.tgz#7e95acb24aa92f5885e0abef5ba131330d4ae683"
@ -1564,56 +1232,6 @@ slice-ansi@^2.1.0:
astral-regex "^1.0.0"
is-fullwidth-code-point "^2.0.0"
socket.io-adapter@~1.1.0:
version "1.1.2"
resolved "https://registry.yarnpkg.com/socket.io-adapter/-/socket.io-adapter-1.1.2.tgz#ab3f0d6f66b8fc7fca3959ab5991f82221789be9"
socket.io-client@2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/socket.io-client/-/socket.io-client-2.3.0.tgz#14d5ba2e00b9bcd145ae443ab96b3f86cbcc1bb4"
dependencies:
backo2 "1.0.2"
base64-arraybuffer "0.1.5"
component-bind "1.0.0"
component-emitter "1.2.1"
debug "~4.1.0"
engine.io-client "~3.4.0"
has-binary2 "~1.0.2"
has-cors "1.1.0"
indexof "0.0.1"
object-component "0.0.3"
parseqs "0.0.5"
parseuri "0.0.5"
socket.io-parser "~3.3.0"
to-array "0.1.4"
socket.io-parser@~3.3.0:
version "3.3.0"
resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-3.3.0.tgz#2b52a96a509fdf31440ba40fed6094c7d4f1262f"
dependencies:
component-emitter "1.2.1"
debug "~3.1.0"
isarray "2.0.1"
socket.io-parser@~3.4.0:
version "3.4.0"
resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-3.4.0.tgz#370bb4a151df2f77ce3345ff55a7072cc6e9565a"
dependencies:
component-emitter "1.2.1"
debug "~4.1.0"
isarray "2.0.1"
socket.io@^2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/socket.io/-/socket.io-2.3.0.tgz#cd762ed6a4faeca59bc1f3e243c0969311eb73fb"
dependencies:
debug "~4.1.0"
engine.io "~3.4.0"
has-binary2 "~1.0.2"
socket.io-adapter "~1.1.0"
socket.io-client "2.3.0"
socket.io-parser "~3.4.0"
source-map-support@^0.5.12, source-map-support@^0.5.6:
version "0.5.16"
resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.16.tgz#0ae069e7fe3ba7538c64c98515e35339eac5a042"
@ -1647,14 +1265,29 @@ spdx-license-ids@^3.0.0:
version "3.0.5"
resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz#3694b5804567a458d3c8045842a6358632f62654"
split-on-first@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/split-on-first/-/split-on-first-1.1.0.tgz#f610afeee3b12bce1d0c30425e76398b78249a5f"
integrity sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==
sprintf-js@~1.0.2:
version "1.0.3"
resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
"statuses@>= 1.5.0 < 2", statuses@~1.5.0:
"statuses@>= 1.5.0 < 2":
version "1.5.0"
resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c"
streamsearch@0.1.2:
version "0.1.2"
resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-0.1.2.tgz#808b9d0e56fc273d809ba57338e929919a1a9f1a"
integrity sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo=
strict-uri-encode@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz#b9c7330c7042862f6b142dc274bbcc5866ce3546"
integrity sha1-ucczDHBChi9rFC3CdLvMWGbONUY=
string-width@^3.0.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961"
@ -1753,10 +1386,6 @@ tmp@^0.0.33:
dependencies:
os-tmpdir "~1.0.2"
to-array@0.1.4:
version "0.1.4"
resolved "https://registry.yarnpkg.com/to-array/-/to-array-0.1.4.tgz#17e6c11f73dd4f3d74cda7a4ff3238e9ad9bf890"
toidentifier@1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553"
@ -1829,7 +1458,7 @@ type-fest@^0.8.1:
version "0.8.1"
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d"
type-is@~1.6.17, type-is@~1.6.18:
type-is@~1.6.17:
version "1.6.18"
resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131"
dependencies:
@ -1840,7 +1469,11 @@ typescript@^3.8.3:
version "3.8.3"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.8.3.tgz#409eb8544ea0335711205869ec458ab109ee1061"
unpipe@1.0.0, unpipe@~1.0.0:
uWebSockets.js@uNetworking/uWebSockets.js#v18.5.0:
version "18.5.0"
resolved "https://codeload.github.com/uNetworking/uWebSockets.js/tar.gz/9b1605d2db82981cafe69dbe356e10ce412f5805"
unpipe@1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec"
@ -1850,10 +1483,6 @@ uri-js@^4.2.2:
dependencies:
punycode "^2.1.0"
utils-merge@1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713"
uuid@7.0.3:
version "7.0.3"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-7.0.3.tgz#c5c9f2c8cf25dc0a372c4df1441c41f5bd0c680b"
@ -1875,10 +1504,6 @@ validate-npm-package-license@^3.0.1:
spdx-correct "^3.0.0"
spdx-expression-parse "^3.0.0"
vary@~1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
which@^1.2.9, which@^1.3.0:
version "1.3.1"
resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a"
@ -1899,28 +1524,10 @@ write@1.0.3:
dependencies:
mkdirp "^0.5.1"
ws@^7.1.2:
version "7.2.3"
resolved "https://registry.yarnpkg.com/ws/-/ws-7.2.3.tgz#a5411e1fb04d5ed0efee76d26d5c46d830c39b46"
ws@~6.1.0:
version "6.1.4"
resolved "https://registry.yarnpkg.com/ws/-/ws-6.1.4.tgz#5b5c8800afab925e94ccb29d153c8d02c1776ef9"
dependencies:
async-limiter "~1.0.0"
xmlhttprequest-ssl@~1.5.4:
version "1.5.5"
resolved "https://registry.yarnpkg.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.5.tgz#c2876b06168aadc40e57d97e81191ac8f4398b3e"
xtend@^4.0.0:
version "4.0.2"
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"
yeast@0.1.2:
version "0.1.2"
resolved "https://registry.yarnpkg.com/yeast/-/yeast-0.1.2.tgz#008e06d8094320c372dbc2f8ed76a0ca6c8ac419"
yn@3.1.1:
version "3.1.1"
resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50"

View File

@ -1,9 +1,14 @@
import {Connection} from "../front/src/Connection";
import * as WebSocket from "ws"
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
Connection.setWebsocketFactory((url: string) => {
return new WebSocket(url);
});
async function startOneUser(): Promise<void> {
const connection = await Connection.createConnection('foo', ['male3']);

View File

@ -21,11 +21,10 @@
],
"license": "SEE LICENSE IN LICENSE.txt",
"dependencies": {
"socket.io-client": "^2.3.0",
"@types/ws": "^7.2.6",
"ts-node-dev": "^1.0.0-pre.62",
"typescript": "^4.0.2"
"typescript": "^4.0.2",
"ws": "^7.3.1"
},
"devDependencies": {
"@types/socket.io-client": "^1.4.33"
}
"devDependencies": {}
}

View File

@ -2,9 +2,9 @@
# yarn lockfile v1
"@types/socket.io-client@^1.4.33":
version "1.4.33"
resolved "https://registry.yarnpkg.com/@types/socket.io-client/-/socket.io-client-1.4.33.tgz#8e705b9b3f7fba6cb329d27cd2eda222812adbf1"
"@types/node@*":
version "14.11.2"
resolved "https://registry.yarnpkg.com/@types/node/-/node-14.11.2.tgz#2de1ed6670439387da1c9f549a2ade2b0a799256"
"@types/strip-bom@^3.0.0":
version "3.0.0"
@ -14,9 +14,11 @@
version "0.0.30"
resolved "https://registry.yarnpkg.com/@types/strip-json-comments/-/strip-json-comments-0.0.30.tgz#9aa30c04db212a9a0649d6ae6fd50accc40748a1"
after@0.8.2:
version "0.8.2"
resolved "https://registry.yarnpkg.com/after/-/after-0.8.2.tgz#fedb394f9f0e02aa9768e702bda23b505fae7e1f"
"@types/ws@^7.2.6":
version "7.2.6"
resolved "https://registry.yarnpkg.com/@types/ws/-/ws-7.2.6.tgz#516cbfb818310f87b43940460e065eb912a4178d"
dependencies:
"@types/node" "*"
anymatch@~3.1.1:
version "3.1.1"
@ -33,40 +35,14 @@ array-find-index@^1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/array-find-index/-/array-find-index-1.0.2.tgz#df010aa1287e164bbda6f9723b0a96a1ec4187a1"
arraybuffer.slice@~0.0.7:
version "0.0.7"
resolved "https://registry.yarnpkg.com/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz#3bbc4275dd584cc1b10809b89d4e8b63a69e7675"
async-limiter@~1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.1.tgz#dd379e94f0db8310b08291f9d64c3209766617fd"
backo2@1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/backo2/-/backo2-1.0.2.tgz#31ab1ac8b129363463e35b3ebb69f4dfcfba7947"
balanced-match@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
base64-arraybuffer@0.1.5:
version "0.1.5"
resolved "https://registry.yarnpkg.com/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz#73926771923b5a19747ad666aa5cd4bf9c6e9ce8"
better-assert@~1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/better-assert/-/better-assert-1.0.2.tgz#40866b9e1b9e0b55b481894311e68faffaebc522"
dependencies:
callsite "1.0.0"
binary-extensions@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.1.0.tgz#30fa40c9e7fe07dbc895678cd287024dea241dd9"
blob@0.0.5:
version "0.0.5"
resolved "https://registry.yarnpkg.com/blob/-/blob-0.0.5.tgz#d680eeef25f8cd91ad533f5b01eed48e64caf683"
brace-expansion@^1.1.7:
version "1.1.11"
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
@ -84,10 +60,6 @@ buffer-from@^1.0.0:
version "1.1.1"
resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef"
callsite@1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/callsite/-/callsite-1.0.0.tgz#280398e5d664bd74038b6f0905153e6e8af1bc20"
camelcase-keys@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/camelcase-keys/-/camelcase-keys-2.1.0.tgz#308beeaffdf28119051efa1d932213c91b8f92e7"
@ -113,22 +85,6 @@ chokidar@^3.4.0:
optionalDependencies:
fsevents "~2.1.2"
component-bind@1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/component-bind/-/component-bind-1.0.0.tgz#00c608ab7dcd93897c0009651b1d3a8e1e73bbd1"
component-emitter@1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.2.1.tgz#137918d6d78283f7df7a6b7c5a63e140e69425e6"
component-emitter@~1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0"
component-inherit@0.0.3:
version "0.0.3"
resolved "https://registry.yarnpkg.com/component-inherit/-/component-inherit-0.0.3.tgz#645fc4adf58b72b649d5cae65135619db26ff143"
concat-map@0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
@ -146,18 +102,6 @@ dateformat@~1.0.4-1.2.3:
get-stdin "^4.0.1"
meow "^3.3.0"
debug@~3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
dependencies:
ms "2.0.0"
debug@~4.1.0:
version "4.1.1"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791"
dependencies:
ms "^2.1.1"
decamelize@^1.1.2:
version "1.2.0"
resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
@ -172,32 +116,6 @@ dynamic-dedupe@^0.3.0:
dependencies:
xtend "^4.0.0"
engine.io-client@~3.4.0:
version "3.4.3"
resolved "https://registry.yarnpkg.com/engine.io-client/-/engine.io-client-3.4.3.tgz#192d09865403e3097e3575ebfeb3861c4d01a66c"
dependencies:
component-emitter "~1.3.0"
component-inherit "0.0.3"
debug "~4.1.0"
engine.io-parser "~2.2.0"
has-cors "1.1.0"
indexof "0.0.1"
parseqs "0.0.5"
parseuri "0.0.5"
ws "~6.1.0"
xmlhttprequest-ssl "~1.5.4"
yeast "0.1.2"
engine.io-parser@~2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/engine.io-parser/-/engine.io-parser-2.2.0.tgz#312c4894f57d52a02b420868da7b5c1c84af80ed"
dependencies:
after "0.8.2"
arraybuffer.slice "~0.0.7"
base64-arraybuffer "0.1.5"
blob "0.0.5"
has-binary2 "~1.0.2"
error-ex@^1.2.0:
version "1.3.2"
resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf"
@ -250,16 +168,6 @@ graceful-fs@^4.1.2:
version "4.2.4"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb"
has-binary2@~1.0.2:
version "1.0.3"
resolved "https://registry.yarnpkg.com/has-binary2/-/has-binary2-1.0.3.tgz#7776ac627f3ea77250cfc332dab7ddf5e4f5d11d"
dependencies:
isarray "2.0.1"
has-cors@1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/has-cors/-/has-cors-1.1.0.tgz#5e474793f7ea9843d1bb99c23eef49ff126fff39"
hosted-git-info@^2.1.4:
version "2.8.8"
resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.8.tgz#7539bd4bc1e0e0a895815a2e0262420b12858488"
@ -270,10 +178,6 @@ indent-string@^2.1.0:
dependencies:
repeating "^2.0.0"
indexof@0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/indexof/-/indexof-0.0.1.tgz#82dc336d232b9062179d05ab3293a66059fd435d"
inflight@^1.0.4:
version "1.0.6"
resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"
@ -317,10 +221,6 @@ is-utf8@^0.2.0:
version "0.2.1"
resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72"
isarray@2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.1.tgz#a37d94ed9cda2d59865c9f76fe596ee1f338741e"
load-json-file@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-1.1.0.tgz#956905708d58b4bab4c2261b04f59f31c99374c0"
@ -375,14 +275,6 @@ mkdirp@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e"
ms@2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
ms@^2.1.1:
version "2.1.2"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
normalize-package-data@^2.3.2, normalize-package-data@^2.3.4:
version "2.5.0"
resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8"
@ -400,10 +292,6 @@ object-assign@^4.0.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
object-component@0.0.3:
version "0.0.3"
resolved "https://registry.yarnpkg.com/object-component/-/object-component-0.0.3.tgz#f0c69aa50efc95b866c186f400a33769cb2f1291"
once@^1.3.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
@ -416,18 +304,6 @@ parse-json@^2.2.0:
dependencies:
error-ex "^1.2.0"
parseqs@0.0.5:
version "0.0.5"
resolved "https://registry.yarnpkg.com/parseqs/-/parseqs-0.0.5.tgz#d5208a3738e46766e291ba2ea173684921a8b89d"
dependencies:
better-assert "~1.0.0"
parseuri@0.0.5:
version "0.0.5"
resolved "https://registry.yarnpkg.com/parseuri/-/parseuri-0.0.5.tgz#80204a50d4dbb779bfdc6ebe2778d90e4bce320a"
dependencies:
better-assert "~1.0.0"
path-exists@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-2.1.0.tgz#0feb6c64f0fc518d9a754dd5efb62c7022761f4b"
@ -522,33 +398,6 @@ signal-exit@^3.0.0:
version "3.0.3"
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c"
socket.io-client@^2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/socket.io-client/-/socket.io-client-2.3.0.tgz#14d5ba2e00b9bcd145ae443ab96b3f86cbcc1bb4"
dependencies:
backo2 "1.0.2"
base64-arraybuffer "0.1.5"
component-bind "1.0.0"
component-emitter "1.2.1"
debug "~4.1.0"
engine.io-client "~3.4.0"
has-binary2 "~1.0.2"
has-cors "1.1.0"
indexof "0.0.1"
object-component "0.0.3"
parseqs "0.0.5"
parseuri "0.0.5"
socket.io-parser "~3.3.0"
to-array "0.1.4"
socket.io-parser@~3.3.0:
version "3.3.0"
resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-3.3.0.tgz#2b52a96a509fdf31440ba40fed6094c7d4f1262f"
dependencies:
component-emitter "1.2.1"
debug "~3.1.0"
isarray "2.0.1"
source-map-support@^0.5.12, source-map-support@^0.5.17:
version "0.5.19"
resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61"
@ -602,10 +451,6 @@ strip-json-comments@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
to-array@0.1.4:
version "0.1.4"
resolved "https://registry.yarnpkg.com/to-array/-/to-array-0.1.4.tgz#17e6c11f73dd4f3d74cda7a4ff3238e9ad9bf890"
to-regex-range@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4"
@ -670,24 +515,14 @@ wrappy@1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
ws@~6.1.0:
version "6.1.4"
resolved "https://registry.yarnpkg.com/ws/-/ws-6.1.4.tgz#5b5c8800afab925e94ccb29d153c8d02c1776ef9"
dependencies:
async-limiter "~1.0.0"
xmlhttprequest-ssl@~1.5.4:
version "1.5.5"
resolved "https://registry.yarnpkg.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.5.tgz#c2876b06168aadc40e57d97e81191ac8f4398b3e"
ws@^7.3.1:
version "7.3.1"
resolved "https://registry.yarnpkg.com/ws/-/ws-7.3.1.tgz#d0547bf67f7ce4f12a72dfe31262c68d7dc551c8"
xtend@^4.0.0:
version "4.0.2"
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"
yeast@0.1.2:
version "0.1.2"
resolved "https://registry.yarnpkg.com/yeast/-/yeast-0.1.2.tgz#008e06d8094320c372dbc2f8ed76a0ca6c8ac419"
yn@3.1.1:
version "3.1.1"
resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50"

View File

@ -2,15 +2,13 @@ import Axios from "axios";
import {API_URL} from "./Enum/EnvironmentVariable";
import {MessageUI} from "./Logger/MessageUI";
import {
BatchMessage, GroupDeleteMessage, GroupUpdateMessage, ItemEventMessage,
PositionMessage,
SetPlayerDetailsMessage, UserJoinedMessage, UserLeftMessage, UserMovedMessage,
BatchMessage, ClientToServerMessage, GroupDeleteMessage, GroupUpdateMessage, ItemEventMessage, JoinRoomMessage,
PositionMessage, RoomJoinedMessage, ServerToClientMessage,
SetPlayerDetailsMessage, SetUserIdMessage, SilentMessage, UserJoinedMessage, UserLeftMessage, UserMovedMessage,
UserMovesMessage,
ViewportMessage
} from "./Messages/generated/messages_pb"
const SocketIo = require('socket.io-client');
import Socket = SocketIOClient.Socket;
import {PlayerAnimationNames} from "./Phaser/Player/Animation";
import {UserSimplePeerInterface} from "./WebRtc/SimplePeer";
import {SignalData} from "simple-peer";
@ -132,50 +130,57 @@ export interface RoomJoinedMessageInterface {
}
export class Connection implements Connection {
private readonly socket: Socket;
private readonly socket: WebSocket;
private userId: number|null = null;
private batchCallbacks: Map<string, Function[]> = new Map<string, Function[]>();
private static websocketFactory: null|((url: string)=>any) = null;
public static setWebsocketFactory(websocketFactory: (url: string)=>any): void {
Connection.websocketFactory = websocketFactory;
}
private constructor(token: string) {
let url = API_URL.replace('http://', 'ws://').replace('https://', 'wss://');
url += '?token='+token;
this.socket = SocketIo(`${API_URL}`, {
query: {
token: token
},
reconnection: false // Reconnection is handled by the application itself
});
if (Connection.websocketFactory) {
this.socket = Connection.websocketFactory(url);
} else {
this.socket = new WebSocket(url);
}
this.socket.on(EventMessage.MESSAGE_ERROR, (message: string) => {
console.error(EventMessage.MESSAGE_ERROR, message);
})
this.socket.binaryType = 'arraybuffer';
/**
* Messages inside batched messages are extracted and sent to listeners directly.
*/
this.socket.on(EventMessage.BATCH, (batchedMessagesBinary: ArrayBuffer) => {
const batchMessage = BatchMessage.deserializeBinary(new Uint8Array(batchedMessagesBinary));
this.socket.onopen = (ev) => {
console.log('WS connected');
};
for (const message of batchMessage.getPayloadList()) {
this.socket.onmessage = (messageEvent) => {
const arrayBuffer: ArrayBuffer = messageEvent.data;
const message = ServerToClientMessage.deserializeBinary(new Uint8Array(arrayBuffer));
if (message.hasBatchmessage()) {
for (const subMessage of (message.getBatchmessage() as BatchMessage).getPayloadList()) {
let event: string;
let payload;
if (message.hasUsermovedmessage()) {
if (subMessage.hasUsermovedmessage()) {
event = EventMessage.USER_MOVED;
payload = message.getUsermovedmessage();
} else if (message.hasGroupupdatemessage()) {
payload = subMessage.getUsermovedmessage();
} else if (subMessage.hasGroupupdatemessage()) {
event = EventMessage.GROUP_CREATE_UPDATE;
payload = message.getGroupupdatemessage();
} else if (message.hasGroupdeletemessage()) {
payload = subMessage.getGroupupdatemessage();
} else if (subMessage.hasGroupdeletemessage()) {
event = EventMessage.GROUP_DELETE;
payload = message.getGroupdeletemessage();
} else if (message.hasUserjoinedmessage()) {
payload = subMessage.getGroupdeletemessage();
} else if (subMessage.hasUserjoinedmessage()) {
event = EventMessage.JOIN_ROOM;
payload = message.getUserjoinedmessage();
} else if (message.hasUserleftmessage()) {
payload = subMessage.getUserjoinedmessage();
} else if (subMessage.hasUserleftmessage()) {
event = EventMessage.USER_LEFT;
payload = message.getUserleftmessage();
} else if (message.hasItemeventmessage()) {
payload = subMessage.getUserleftmessage();
} else if (subMessage.hasItemeventmessage()) {
event = EventMessage.ITEM_EVENT;
payload = message.getItemeventmessage();
payload = subMessage.getItemeventmessage();
} else {
throw new Error('Unexpected batch message type');
}
@ -188,7 +193,28 @@ export class Connection implements Connection {
listener(payload);
}
}
} else if (message.hasRoomjoinedmessage()) {
const roomJoinedMessage = message.getRoomjoinedmessage() as RoomJoinedMessage;
const users: Array<MessageUserJoined> = roomJoinedMessage.getUserList().map(this.toMessageUserJoined);
const groups: Array<GroupCreatedUpdatedMessageInterface> = roomJoinedMessage.getGroupList().map(this.toGroupCreatedUpdatedMessage);
let items: { [itemId: number] : unknown } = {};
for (const item of roomJoinedMessage.getItemList()) {
items[item.getItemid()] = JSON.parse(item.getStatejson());
}
this.resolveJoinRoom({
users,
groups,
items
})
} else if (message.hasSetuseridmessage()) {
this.userId = (message.getSetuseridmessage() as SetUserIdMessage).getUserid();
} else if (message.hasErrormessage()) {
console.error(EventMessage.MESSAGE_ERROR, message.getErrormessage()?.getMessage);
}
}
}
public static createConnection(name: string, characterLayersSelected: string[]): Promise<Connection> {
@ -203,18 +229,23 @@ export class Connection implements Connection {
reject(error);
});
connection.onConnect(() => {
const message = new SetPlayerDetailsMessage();
message.setName(name);
message.setCharacterlayersList(characterLayersSelected);
connection.socket.emit(EventMessage.SET_PLAYER_DETAILS, message.serializeBinary().buffer, (id: number) => {
connection.userId = id;
});
const clientToServerMessage = new ClientToServerMessage();
clientToServerMessage.setSetplayerdetailsmessage(message);
connection.socket.send(clientToServerMessage.serializeBinary().buffer);
resolve(connection);
});
});
})
.catch((err) => {
// Let's retry in 4-6 seconds
console.error('Connection failed. Retrying', err);
return new Promise<Connection>((resolve, reject) => {
setTimeout(() => {
Connection.createConnection(name, characterLayersSelected).then((connection) => resolve(connection))
@ -228,24 +259,30 @@ export class Connection implements Connection {
this.socket?.close();
}
private resolveJoinRoom!: (value?: (RoomJoinedMessageInterface | PromiseLike<RoomJoinedMessageInterface> | undefined)) => void;
public joinARoom(roomId: string, startX: number, startY: number, direction: string, moving: boolean, viewport: ViewportInterface): Promise<RoomJoinedMessageInterface> {
const promise = new Promise<RoomJoinedMessageInterface>((resolve, reject) => {
this.socket.emit(EventMessage.JOIN_ROOM, {
roomId,
position: {x: startX, y: startY, direction, moving },
viewport,
}, (roomJoinedMessage: RoomJoinedMessageInterface) => {
resolve(roomJoinedMessage);
});
this.resolveJoinRoom = resolve;
const positionMessage = this.toPositionMessage(startX, startY, direction, moving);
const viewportMessage = this.toViewportMessage(viewport);
const joinRoomMessage = new JoinRoomMessage();
joinRoomMessage.setRoomid(roomId);
joinRoomMessage.setPosition(positionMessage);
joinRoomMessage.setViewport(viewportMessage);
//console.log('Sending position ', positionMessage.getX(), positionMessage.getY());
const clientToServerMessage = new ClientToServerMessage();
clientToServerMessage.setJoinroommessage(joinRoomMessage);
this.socket.send(clientToServerMessage.serializeBinary().buffer);
})
return promise;
}
public sharePosition(x : number, y : number, direction : string, moving: boolean, viewport: ViewportInterface) : void{
if(!this.socket){
return;
}
private toPositionMessage(x : number, y : number, direction : string, moving: boolean): PositionMessage {
const positionMessage = new PositionMessage();
positionMessage.setX(Math.floor(x));
positionMessage.setY(Math.floor(y));
@ -269,23 +306,47 @@ export class Connection implements Connection {
positionMessage.setDirection(directionEnum);
positionMessage.setMoving(moving);
return positionMessage;
}
private toViewportMessage(viewport: ViewportInterface): ViewportMessage {
const viewportMessage = new ViewportMessage();
viewportMessage.setLeft(Math.floor(viewport.left));
viewportMessage.setRight(Math.floor(viewport.right));
viewportMessage.setTop(Math.floor(viewport.top));
viewportMessage.setBottom(Math.floor(viewport.bottom));
return viewportMessage;
}
public sharePosition(x : number, y : number, direction : string, moving: boolean, viewport: ViewportInterface) : void{
if(!this.socket){
return;
}
const positionMessage = this.toPositionMessage(x, y, direction, moving);
const viewportMessage = this.toViewportMessage(viewport);
const userMovesMessage = new UserMovesMessage();
userMovesMessage.setPosition(positionMessage);
userMovesMessage.setViewport(viewportMessage);
//console.log('Sending position ', positionMessage.getX(), positionMessage.getY());
const clientToServerMessage = new ClientToServerMessage();
clientToServerMessage.setUsermovesmessage(userMovesMessage);
this.socket.emit(EventMessage.USER_POSITION, userMovesMessage.serializeBinary().buffer);
this.socket.send(clientToServerMessage.serializeBinary().buffer);
}
public setSilent(silent: boolean): void {
this.socket.emit(EventMessage.SET_SILENT, silent);
const silentMessage = new SilentMessage();
silentMessage.setSilent(silent);
const clientToServerMessage = new ClientToServerMessage();
clientToServerMessage.setSilentmessage(silentMessage);
this.socket.send(clientToServerMessage.serializeBinary().buffer);
}
public setViewport(viewport: ViewportInterface): void {
@ -295,23 +356,30 @@ export class Connection implements Connection {
viewportMessage.setLeft(Math.round(viewport.left));
viewportMessage.setRight(Math.round(viewport.right));
this.socket.emit(EventMessage.SET_VIEWPORT, viewportMessage.serializeBinary().buffer);
const clientToServerMessage = new ClientToServerMessage();
clientToServerMessage.setViewportmessage(viewportMessage);
this.socket.send(clientToServerMessage.serializeBinary().buffer);
}
public onUserJoins(callback: (message: MessageUserJoined) => void): void {
this.onBatchMessage(EventMessage.JOIN_ROOM, (message: UserJoinedMessage) => {
callback(this.toMessageUserJoined(message));
});
}
// TODO: move this to protobuf utils
private toMessageUserJoined(message: UserJoinedMessage): MessageUserJoined {
const position = message.getPosition();
if (position === undefined) {
throw new Error('Invalid JOIN_ROOM message');
}
const messageUserJoined: MessageUserJoined = {
return {
userId: message.getUserid(),
name: message.getName(),
characterLayers: message.getCharacterlayersList(),
position: ProtobufClientUtils.toPointInterface(position)
}
callback(messageUserJoined);
});
}
public onUserMoved(callback: (message: UserMovedMessage) => void): void {
@ -339,19 +407,20 @@ export class Connection implements Connection {
public onGroupUpdatedOrCreated(callback: (groupCreateUpdateMessage: GroupCreatedUpdatedMessageInterface) => void): void {
this.onBatchMessage(EventMessage.GROUP_CREATE_UPDATE, (message: GroupUpdateMessage) => {
callback(this.toGroupCreatedUpdatedMessage(message));
});
}
private toGroupCreatedUpdatedMessage(message: GroupUpdateMessage): GroupCreatedUpdatedMessageInterface {
const position = message.getPosition();
if (position === undefined) {
throw new Error('Missing position in GROUP_CREATE_UPDATE');
}
const groupCreateUpdateMessage: GroupCreatedUpdatedMessageInterface = {
return {
groupId: message.getGroupid(),
position: position.toObject()
}
//console.log('Group position: ', position.toObject());
callback(groupCreateUpdateMessage);
});
}
public onGroupDeleted(callback: (groupId: number) => void): void {
@ -360,43 +429,51 @@ export class Connection implements Connection {
});
}
public onConnectError(callback: (error: object) => void): void {
this.socket.on(EventMessage.CONNECT_ERROR, callback)
public onConnectError(callback: (error: Event) => void): void {
this.socket.addEventListener('error', callback)
}
public onConnect(callback: (event: Event) => void): void {
this.socket.addEventListener('open', callback)
}
public sendWebrtcSignal(signal: unknown, receiverId: number) {
return this.socket.emit(EventMessage.WEBRTC_SIGNAL, {
/* return this.socket.emit(EventMessage.WEBRTC_SIGNAL, {
receiverId: receiverId,
signal: signal
} as WebRtcSignalSentMessageInterface);
} as WebRtcSignalSentMessageInterface);*/
}
public sendWebrtcScreenSharingSignal(signal: unknown, receiverId: number) {
return this.socket.emit(EventMessage.WEBRTC_SCREEN_SHARING_SIGNAL, {
/* return this.socket.emit(EventMessage.WEBRTC_SCREEN_SHARING_SIGNAL, {
receiverId: receiverId,
signal: signal
} as WebRtcSignalSentMessageInterface);
} as WebRtcSignalSentMessageInterface);*/
}
public receiveWebrtcStart(callback: (message: WebRtcStartMessageInterface) => void) {
this.socket.on(EventMessage.WEBRTC_START, callback);
// TODO
// this.socket.on(EventMessage.WEBRTC_START, callback);
}
public receiveWebrtcSignal(callback: (message: WebRtcSignalReceivedMessageInterface) => void) {
return this.socket.on(EventMessage.WEBRTC_SIGNAL, callback);
// TODO
// return this.socket.on(EventMessage.WEBRTC_SIGNAL, callback);
}
public receiveWebrtcScreenSharingSignal(callback: (message: WebRtcSignalReceivedMessageInterface) => void) {
return this.socket.on(EventMessage.WEBRTC_SCREEN_SHARING_SIGNAL, callback);
// TODO
// return this.socket.on(EventMessage.WEBRTC_SCREEN_SHARING_SIGNAL, callback);
}
public onServerDisconnected(callback: (reason: string) => void): void {
this.socket.on('disconnect', (reason: string) => {
if (reason === 'io client disconnect') {
// The client asks for disconnect, let's not trigger any event.
public onServerDisconnected(callback: (event: CloseEvent) => void): void {
this.socket.addEventListener('close', (event) => {
if (event.code === 1000) {
// Normal closure case
return;
}
callback(reason);
callback(event);
});
}
@ -406,7 +483,8 @@ export class Connection implements Connection {
}
disconnectMessage(callback: (message: WebRtcDisconnectMessageInterface) => void): void {
this.socket.on(EventMessage.WEBRTC_DISCONNECT, callback);
// TODO
// this.socket.on(EventMessage.WEBRTC_DISCONNECT, callback);
}
emitActionableEvent(itemId: number, event: string, state: unknown, parameters: unknown): void {
@ -416,7 +494,10 @@ export class Connection implements Connection {
itemEventMessage.setStatejson(JSON.stringify(state));
itemEventMessage.setParametersjson(JSON.stringify(parameters));
this.socket.emit(EventMessage.ITEM_EVENT, itemEventMessage.serializeBinary().buffer);
const clientToServerMessage = new ClientToServerMessage();
clientToServerMessage.setItemeventmessage(itemEventMessage);
this.socket.send(clientToServerMessage.serializeBinary().buffer);
}
onActionableEvent(callback: (message: ItemEventMessageInterface) => void): void {

View File

@ -27,6 +27,10 @@ message ViewportMessage {
int32 bottom = 4;
}
message SilentMessage {
bool silent = 1;
}
/*********** CLIENT TO SERVER MESSAGES *************/
message SetPlayerDetailsMessage {
@ -34,11 +38,29 @@ message SetPlayerDetailsMessage {
repeated string characterLayers = 2;
}
message JoinRoomMessage {
string roomId = 1;
PositionMessage position = 2;
ViewportMessage viewport = 3;
}
message UserMovesMessage {
PositionMessage position = 1;
ViewportMessage viewport = 2;
}
message ClientToServerMessage {
oneof message {
JoinRoomMessage joinRoomMessage = 1;
UserMovesMessage userMovesMessage = 2;
SilentMessage silentMessage = 3;
ViewportMessage viewportMessage = 4;
ItemEventMessage itemEventMessage = 5;
SetPlayerDetailsMessage setPlayerDetailsMessage = 6;
}
}
/************ BI-DIRECTIONAL MESSAGES **************/
message ItemEventMessage {
@ -90,3 +112,44 @@ message UserJoinedMessage {
message UserLeftMessage {
int32 userId = 1;
}
message ErrorMessage {
string message = 1;
}
message SetUserIdMessage {
int32 userId = 1;
}
message ItemStateMessage {
int32 itemId = 1;
string stateJson = 2;
}
message RoomJoinedMessage {
repeated UserJoinedMessage user = 1;
repeated GroupUpdateMessage group = 2;
repeated ItemStateMessage item = 3;
}
/*message WebRtcStartMessage {
int32 itemId = 1;
string event = 2;
string stateJson = 3;
string parametersJson = 4;
}*/
message ServerToClientMessage {
oneof message {
BatchMessage batchMessage = 1;
ErrorMessage errorMessage = 2;
RoomJoinedMessage roomJoinedMessage = 3;
SetUserIdMessage setUserIdMessage = 4; // TODO: merge this with RoomJoinedMessage ?
// WebRtcStartMessage webRtcStartMessage = 3;
// WebRtcSignalMessage webRtcSignalMessage = 4;
// WebRtcScreenSharingSignalMessage webRtcScreenSharingSignalMessage = 5;
// WebRtcDisconnectMessage webRtcDisconnectMessage = 6;
}
}