Merge pull request #280 from thecodingmachine/protobuf

Switching communication from JSON to protobuf
This commit is contained in:
David Négrier 2020-09-25 15:55:47 +02:00 committed by GitHub
commit 783d58d3cb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
60 changed files with 4630 additions and 430 deletions

View File

@ -1 +1,3 @@
DEBUG_MODE=false DEBUG_MODE=false
JITSI_URL=meet.jit.si
ADMIN_API_TOKEN=123

View File

@ -26,7 +26,7 @@ jobs:
uses: docker/build-push-action@v1 uses: docker/build-push-action@v1
with: with:
dockerfile: front/Dockerfile dockerfile: front/Dockerfile
path: front/ path: ./
username: ${{ secrets.DOCKER_USERNAME }} username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }} password: ${{ secrets.DOCKER_PASSWORD }}
repository: thecodingmachine/workadventure-front repository: thecodingmachine/workadventure-front
@ -49,7 +49,7 @@ jobs:
uses: docker/build-push-action@v1 uses: docker/build-push-action@v1
with: with:
dockerfile: back/Dockerfile dockerfile: back/Dockerfile
path: back/ path: ./
username: ${{ secrets.DOCKER_USERNAME }} username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }} password: ${{ secrets.DOCKER_PASSWORD }}
repository: thecodingmachine/workadventure-back repository: thecodingmachine/workadventure-back

View File

@ -20,12 +20,25 @@ jobs:
- name: "Setup NodeJS" - name: "Setup NodeJS"
uses: actions/setup-node@v1 uses: actions/setup-node@v1
with: with:
node-version: '12.x' node-version: '14.x'
- name: Install Protoc
uses: arduino/setup-protoc@v1
with:
version: '3.x'
- name: "Install dependencies" - name: "Install dependencies"
run: yarn install run: yarn install
working-directory: "front" working-directory: "front"
- name: "Install messages dependencies"
run: yarn install
working-directory: "messages"
- name: "Build proto messages"
run: yarn run proto && yarn run copy-to-front
working-directory: "messages"
- name: "Build" - name: "Build"
run: yarn run build run: yarn run build
env: env:
@ -54,10 +67,23 @@ jobs:
with: with:
node-version: '12.x' node-version: '12.x'
- name: Install Protoc
uses: arduino/setup-protoc@v1
with:
version: '3.x'
- name: "Install dependencies" - name: "Install dependencies"
run: yarn install run: yarn install
working-directory: "back" working-directory: "back"
- name: "Install messages dependencies"
run: yarn install
working-directory: "messages"
- name: "Build proto messages"
run: yarn run proto && yarn run copy-to-back
working-directory: "messages"
- name: "Build" - name: "Build"
run: yarn run tsc run: yarn run tsc
working-directory: "back" working-directory: "back"

View File

@ -1,6 +1,12 @@
FROM thecodingmachine/workadventure-back-base:latest as builder
WORKDIR /var/www/messages
COPY --chown=docker:docker messages .
RUN yarn install && yarn proto
FROM thecodingmachine/nodejs:12 FROM thecodingmachine/nodejs:12
COPY --chown=docker:docker . . COPY --chown=docker:docker back .
COPY --from=builder --chown=docker:docker /var/www/messages/generated /usr/src/app/src/Messages/generated
RUN yarn install RUN yarn install
ENV NODE_ENV=production ENV NODE_ENV=production

View File

@ -36,15 +36,12 @@
}, },
"homepage": "https://github.com/thecodingmachine/workadventure#readme", "homepage": "https://github.com/thecodingmachine/workadventure#readme",
"dependencies": { "dependencies": {
"@types/express": "^4.17.4",
"@types/http-status-codes": "^1.2.0",
"@types/jsonwebtoken": "^8.3.8",
"@types/socket.io": "^2.1.4",
"@types/uuidv4": "^5.0.0",
"axios": "^0.20.0", "axios": "^0.20.0",
"body-parser": "^1.19.0", "body-parser": "^1.19.0",
"circular-json": "^0.5.9",
"express": "^4.17.1", "express": "^4.17.1",
"generic-type-guard": "^3.2.0", "generic-type-guard": "^3.2.0",
"google-protobuf": "^3.13.0",
"http-status-codes": "^1.4.0", "http-status-codes": "^1.4.0",
"jsonwebtoken": "^8.5.1", "jsonwebtoken": "^8.5.1",
"prom-client": "^12.0.0", "prom-client": "^12.0.0",
@ -55,7 +52,14 @@
"uuidv4": "^6.0.7" "uuidv4": "^6.0.7"
}, },
"devDependencies": { "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/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/eslint-plugin": "^2.26.0",
"@typescript-eslint/parser": "^2.26.0", "@typescript-eslint/parser": "^2.26.0",
"eslint": "^6.8.0", "eslint": "^6.8.0",

View File

@ -8,6 +8,7 @@ import * as http from "http";
import {MapController} from "./Controller/MapController"; import {MapController} from "./Controller/MapController";
import {PrometheusController} from "./Controller/PrometheusController"; import {PrometheusController} from "./Controller/PrometheusController";
import {AdminController} from "./Controller/AdminController"; import {AdminController} from "./Controller/AdminController";
import {DebugController} from "./Controller/DebugController";
class App { class App {
public app: Application; public app: Application;
@ -17,6 +18,7 @@ class App {
public mapController: MapController; public mapController: MapController;
public prometheusController: PrometheusController; public prometheusController: PrometheusController;
private adminController: AdminController; private adminController: AdminController;
private debugController: DebugController;
constructor() { constructor() {
this.app = express(); this.app = express();
@ -35,6 +37,7 @@ class App {
this.mapController = new MapController(this.app); this.mapController = new MapController(this.app);
this.prometheusController = new PrometheusController(this.app, this.ioSocketController); this.prometheusController = new PrometheusController(this.app, this.ioSocketController);
this.adminController = new AdminController(this.app); this.adminController = new AdminController(this.app);
this.debugController = new DebugController(this.app, this.ioSocketController);
} }
// TODO add session user // TODO add session user

View File

@ -6,7 +6,7 @@ import { uuid } from 'uuidv4';
export interface TokenInterface { export interface TokenInterface {
name: string, name: string,
userId: string userUuid: string
} }
export class AuthenticateController { export class AuthenticateController {
@ -28,12 +28,12 @@ export class AuthenticateController {
}); });
}*/ }*/
//TODO check user email for The Coding Machine game //TODO check user email for The Coding Machine game
const userId = uuid(); const userUuid = uuid();
const token = Jwt.sign({name: param.name, userId: userId} as TokenInterface, SECRET_KEY, {expiresIn: '24h'}); const token = Jwt.sign({name: param.name, userUuid: userUuid} as TokenInterface, SECRET_KEY, {expiresIn: '24h'});
return res.status(OK).send({ return res.status(OK).send({
token: token, token: token,
mapUrlStart: URL_ROOM_STARTED, mapUrlStart: URL_ROOM_STARTED,
userId: userId, userId: userUuid,
}); });
}); });
} }

View File

@ -0,0 +1,41 @@
import {Application, Request, Response} from "express";
import {OK} from "http-status-codes";
import {ADMIN_API_TOKEN} from "../Enum/EnvironmentVariable";
import {IoSocketController} from "_Controller/IoSocketController";
import {stringify} from "circular-json";
export class DebugController {
constructor(private App : Application, private ioSocketController: IoSocketController) {
this.getDump();
}
getDump(){
this.App.get("/dump", (req: Request, res: Response) => {
if (req.query.token !== ADMIN_API_TOKEN) {
return res.status(401).send('Invalid token sent!');
}
return res.status(OK).contentType('application/json').send(stringify(
this.ioSocketController.getWorlds(),
(key: unknown, value: unknown) => {
if(value instanceof Map) {
const obj: any = {}; // eslint-disable-line @typescript-eslint/no-explicit-any
for (const [mapKey, mapValue] of value.entries()) {
obj[mapKey] = mapValue;
}
return obj;
} else if(value instanceof Set) {
const obj: Array<unknown> = [];
for (const [setKey, setValue] of value.entries()) {
obj.push(setValue);
}
return obj;
} else {
return value;
}
}
));
});
}
}

View File

@ -10,22 +10,35 @@ import {Group} from "../Model/Group";
import {User} from "../Model/User"; import {User} from "../Model/User";
import {isSetPlayerDetailsMessage,} from "../Model/Websocket/SetPlayerDetailsMessage"; import {isSetPlayerDetailsMessage,} from "../Model/Websocket/SetPlayerDetailsMessage";
import {MessageUserJoined} from "../Model/Websocket/MessageUserJoined"; import {MessageUserJoined} from "../Model/Websocket/MessageUserJoined";
import {MessageUserMoved} from "../Model/Websocket/MessageUserMoved";
import si from "systeminformation"; import si from "systeminformation";
import {Gauge} from "prom-client"; import {Gauge} from "prom-client";
import {TokenInterface} from "../Controller/AuthenticateController"; import {TokenInterface} from "../Controller/AuthenticateController";
import {isJoinRoomMessageInterface} from "../Model/Websocket/JoinRoomMessage"; import {isJoinRoomMessageInterface} from "../Model/Websocket/JoinRoomMessage";
import {isPointInterface, PointInterface} from "../Model/Websocket/PointInterface"; import {PointInterface} from "../Model/Websocket/PointInterface";
import {isWebRtcSignalMessageInterface} from "../Model/Websocket/WebRtcSignalMessage"; import {isWebRtcSignalMessageInterface} from "../Model/Websocket/WebRtcSignalMessage";
import {UserInGroupInterface} from "../Model/Websocket/UserInGroupInterface"; import {UserInGroupInterface} from "../Model/Websocket/UserInGroupInterface";
import {isItemEventMessageInterface} from "../Model/Websocket/ItemEventMessage"; import {isItemEventMessageInterface} from "../Model/Websocket/ItemEventMessage";
import {uuid} from 'uuidv4'; import {uuid} from 'uuidv4';
import {isUserMovesInterface} from "../Model/Websocket/UserMovesMessage";
import {isViewport} from "../Model/Websocket/ViewportMessage";
import {GroupUpdateInterface} from "_Model/Websocket/GroupUpdateInterface"; import {GroupUpdateInterface} from "_Model/Websocket/GroupUpdateInterface";
import {Movable} from "../Model/Movable"; import {Movable} from "../Model/Movable";
import {
PositionMessage,
SetPlayerDetailsMessage,
SubMessage,
UserMovedMessage,
BatchMessage,
GroupUpdateMessage,
PointMessage,
GroupDeleteMessage,
UserJoinedMessage,
UserLeftMessage,
ItemEventMessage, ViewportMessage
} from "../Messages/generated/messages_pb";
import {UserMovesMessage} from "../Messages/generated/messages_pb";
import Direction = PositionMessage.Direction;
import {ProtobufUtils} from "../Model/Websocket/ProtobufUtils";
enum SockerIoEvent { enum SocketIoEvent {
CONNECTION = "connection", CONNECTION = "connection",
DISCONNECT = "disconnect", DISCONNECT = "disconnect",
JOIN_ROOM = "join-room", // bi-directional JOIN_ROOM = "join-room", // bi-directional
@ -46,13 +59,13 @@ enum SockerIoEvent {
BATCH = "batch", BATCH = "batch",
} }
function emitInBatch(socket: ExSocketInterface, event: string | symbol, payload: unknown): void { function emitInBatch(socket: ExSocketInterface, event: string, payload: SubMessage): void {
socket.batchedMessages.push({ event, payload}); socket.batchedMessages.addPayload(payload);
if (socket.batchTimeout === null) { if (socket.batchTimeout === null) {
socket.batchTimeout = setTimeout(() => { socket.batchTimeout = setTimeout(() => {
socket.emit(SockerIoEvent.BATCH, socket.batchedMessages); socket./*binary(true).*/emit(SocketIoEvent.BATCH, socket.batchedMessages.serializeBinary().buffer);
socket.batchedMessages = []; socket.batchedMessages = new BatchMessage();
socket.batchTimeout = null; socket.batchTimeout = null;
}, 100); }, 100);
} }
@ -61,9 +74,10 @@ function emitInBatch(socket: ExSocketInterface, event: string | symbol, payload:
export class IoSocketController { export class IoSocketController {
public readonly Io: socketIO.Server; public readonly Io: socketIO.Server;
private Worlds: Map<string, World> = new Map<string, World>(); private Worlds: Map<string, World> = new Map<string, World>();
private sockets: Map<string, ExSocketInterface> = new Map<string, ExSocketInterface>(); private sockets: Map<number, ExSocketInterface> = new Map<number, ExSocketInterface>();
private nbClientsGauge: Gauge<string>; private nbClientsGauge: Gauge<string>;
private nbClientsPerRoomGauge: Gauge<string>; private nbClientsPerRoomGauge: Gauge<string>;
private nextUserId: number = 1;
constructor(server: http.Server) { constructor(server: http.Server) {
this.Io = socketIO(server); this.Io = socketIO(server);
@ -81,7 +95,7 @@ export class IoSocketController {
// Authentication with token. it will be decoded and stored in the socket. // 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. // Completely commented for now, as we do not use the "/login" route at all.
this.Io.use((socket: Socket, next) => { this.Io.use((socket: Socket, next) => {
console.log(socket.handshake.query.token); //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.'); console.error('An authentication error happened, a user tried to connect without a token.');
return next(new Error('Authentication error')); return next(new Error('Authentication error'));
@ -89,7 +103,9 @@ export class IoSocketController {
if(socket.handshake.query.token === 'test'){ if(socket.handshake.query.token === 'test'){
if (ALLOW_ARTILLERY) { if (ALLOW_ARTILLERY) {
(socket as ExSocketInterface).token = socket.handshake.query.token; (socket as ExSocketInterface).token = socket.handshake.query.token;
(socket as ExSocketInterface).userId = uuid(); (socket as ExSocketInterface).userId = this.nextUserId;
(socket as ExSocketInterface).userUuid = uuid();
this.nextUserId++;
(socket as ExSocketInterface).isArtillery = true; (socket as ExSocketInterface).isArtillery = true;
console.log((socket as ExSocketInterface).userId); console.log((socket as ExSocketInterface).userId);
next(); next();
@ -115,7 +131,9 @@ export class IoSocketController {
} }
(socket as ExSocketInterface).token = socket.handshake.query.token; (socket as ExSocketInterface).token = socket.handshake.query.token;
(socket as ExSocketInterface).userId = tokenDecoded.userId; (socket as ExSocketInterface).userId = this.nextUserId;
(socket as ExSocketInterface).userUuid = tokenDecoded.userUuid;
this.nextUserId++;
next(); next();
}); });
}); });
@ -124,7 +142,7 @@ export class IoSocketController {
} }
private isValidToken(token: object): token is TokenInterface { private isValidToken(token: object): token is TokenInterface {
if (typeof((token as TokenInterface).userId) !== 'string') { if (typeof((token as TokenInterface).userUuid) !== 'string') {
return false; return false;
} }
if (typeof((token as TokenInterface).name) !== 'string') { if (typeof((token as TokenInterface).name) !== 'string') {
@ -150,11 +168,11 @@ export class IoSocketController {
} }
ioConnection() { ioConnection() {
this.Io.on(SockerIoEvent.CONNECTION, (socket: Socket) => { this.Io.on(SocketIoEvent.CONNECTION, (socket: Socket) => {
const client : ExSocketInterface = socket as ExSocketInterface; const client : ExSocketInterface = socket as ExSocketInterface;
client.batchedMessages = []; client.batchedMessages = new BatchMessage();
client.batchTimeout = null; client.batchTimeout = null;
client.emitInBatch = (event: string | symbol, payload: unknown): void => { client.emitInBatch = (event: string, payload: SubMessage): void => {
emitInBatch(client, event, payload); emitInBatch(client, event, payload);
} }
this.sockets.set(client.userId, client); this.sockets.set(client.userId, client);
@ -163,8 +181,8 @@ export class IoSocketController {
const srvSockets = this.Io.sockets.sockets; const srvSockets = this.Io.sockets.sockets;
this.nbClientsGauge.inc(); this.nbClientsGauge.inc();
console.log(new Date().toISOString() + ' A user joined (', Object.keys(srvSockets).length, ' connected users)'); 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(' Current load: ', data.avgload));
si.currentLoad().then(data => console.log(' CPU: ', data.currentload, '%')); //si.currentLoad().then(data => console.log(' CPU: ', data.currentload, '%'));
// End log server load // End log server load
/*join-rom event permit to join one room. /*join-rom event permit to join one room.
@ -175,11 +193,11 @@ export class IoSocketController {
x: user x position on map x: user x position on map
y: user y position on map y: user y position on map
*/ */
socket.on(SockerIoEvent.JOIN_ROOM, (message: unknown, answerFn): void => { socket.on(SocketIoEvent.JOIN_ROOM, (message: unknown, answerFn): void => {
console.log(SockerIoEvent.JOIN_ROOM, message); //console.log(SocketIoEvent.JOIN_ROOM, message);
try { try {
if (!isJoinRoomMessageInterface(message)) { if (!isJoinRoomMessageInterface(message)) {
socket.emit(SockerIoEvent.MESSAGE_ERROR, {message: 'Invalid JOIN_ROOM message.'}); socket.emit(SocketIoEvent.MESSAGE_ERROR, {message: 'Invalid JOIN_ROOM message.'});
console.warn('Invalid JOIN_ROOM message received: ', message); console.warn('Invalid JOIN_ROOM message received: ', message);
return; return;
} }
@ -243,17 +261,19 @@ export class IoSocketController {
} }
}); });
socket.on(SockerIoEvent.SET_VIEWPORT, (message: unknown): void => { socket.on(SocketIoEvent.SET_VIEWPORT, (message: unknown): void => {
try { try {
//console.log('SET_VIEWPORT') if (!(message instanceof Buffer)) {
if (!isViewport(message)) { socket.emit(SocketIoEvent.MESSAGE_ERROR, {message: 'Invalid SET_VIEWPORT message. Expecting binary buffer.'});
socket.emit(SockerIoEvent.MESSAGE_ERROR, {message: 'Invalid SET_VIEWPORT message.'}); console.warn('Invalid SET_VIEWPORT message received (expecting binary buffer): ', message);
console.warn('Invalid SET_VIEWPORT message received: ', message);
return; return;
} }
const viewportMessage = ViewportMessage.deserializeBinary(new Uint8Array(message as ArrayBuffer));
const viewport = viewportMessage.toObject();
const Client = (socket as ExSocketInterface); const Client = (socket as ExSocketInterface);
Client.viewport = message; Client.viewport = viewport;
const world = this.Worlds.get(Client.roomId); const world = this.Worlds.get(Client.roomId);
if (!world) { if (!world) {
@ -267,20 +287,55 @@ export class IoSocketController {
} }
}); });
socket.on(SockerIoEvent.USER_POSITION, (userMovesMessage: unknown): void => { socket.on(SocketIoEvent.USER_POSITION, (message: unknown): void => {
//console.log(SockerIoEvent.USER_POSITION, userMovesMessage); //console.log(SockerIoEvent.USER_POSITION, userMovesMessage);
try { try {
if (!isUserMovesInterface(userMovesMessage)) { if (!(message instanceof Buffer)) {
socket.emit(SockerIoEvent.MESSAGE_ERROR, {message: 'Invalid USER_POSITION message.'}); socket.emit(SocketIoEvent.MESSAGE_ERROR, {message: 'Invalid USER_POSITION message. Expecting binary buffer.'});
console.warn('Invalid USER_POSITION message received: ', userMovesMessage); console.warn('Invalid USER_POSITION message received (expecting binary buffer): ', message);
return; return;
} }
const userMovesMessage = UserMovesMessage.deserializeBinary(new Uint8Array(message as ArrayBuffer));
const userMoves = userMovesMessage.toObject();
const position = userMoves.position;
if (position === undefined) {
throw new Error('Position not found in message');
}
const viewport = userMoves.viewport;
if (viewport === undefined) {
throw new Error('Viewport not found in message');
}
let direction: string;
switch (position.direction) {
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");
}
const Client = (socket as ExSocketInterface); const Client = (socket as ExSocketInterface);
// sending to all clients in room except sender // sending to all clients in room except sender
Client.position = userMovesMessage.position; Client.position = {
Client.viewport = userMovesMessage.viewport; x: position.x,
y: position.y,
direction,
moving: position.moving,
};
Client.viewport = viewport;
// update position in the world // update position in the world
const world = this.Worlds.get(Client.roomId); const world = this.Worlds.get(Client.roomId);
@ -296,15 +351,15 @@ export class IoSocketController {
} }
}); });
socket.on(SockerIoEvent.WEBRTC_SIGNAL, (data: unknown) => { socket.on(SocketIoEvent.WEBRTC_SIGNAL, (data: unknown) => {
this.emitVideo((socket as ExSocketInterface), data); this.emitVideo((socket as ExSocketInterface), data);
}); });
socket.on(SockerIoEvent.WEBRTC_SCREEN_SHARING_SIGNAL, (data: unknown) => { socket.on(SocketIoEvent.WEBRTC_SCREEN_SHARING_SIGNAL, (data: unknown) => {
this.emitScreenSharing((socket as ExSocketInterface), data); this.emitScreenSharing((socket as ExSocketInterface), data);
}); });
socket.on(SockerIoEvent.DISCONNECT, () => { socket.on(SocketIoEvent.DISCONNECT, () => {
const Client = (socket as ExSocketInterface); const Client = (socket as ExSocketInterface);
try { try {
//leave room //leave room
@ -328,16 +383,27 @@ export class IoSocketController {
const srvSockets = this.Io.sockets.sockets; const srvSockets = this.Io.sockets.sockets;
this.nbClientsGauge.dec(); this.nbClientsGauge.dec();
console.log('A user left (', Object.keys(srvSockets).length, ' connected users)'); 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('Current load: ', data.avgload));
si.currentLoad().then(data => console.log('CPU: ', data.currentload, '%')); //si.currentLoad().then(data => console.log('CPU: ', data.currentload, '%'));
// End log server load // End log server load
}); });
// Let's send the user id to the user // Let's send the user id to the user
socket.on(SockerIoEvent.SET_PLAYER_DETAILS, (playerDetails: unknown, answerFn) => { socket.on(SocketIoEvent.SET_PLAYER_DETAILS, (message: unknown, answerFn) => {
console.log(SockerIoEvent.SET_PLAYER_DETAILS, playerDetails); //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));
const playerDetails = {
name: playerDetailsMessage.getName(),
characterLayers: playerDetailsMessage.getCharacterlayersList()
};
//console.log(SocketIoEvent.SET_PLAYER_DETAILS, playerDetails);
if (!isSetPlayerDetailsMessage(playerDetails)) { if (!isSetPlayerDetailsMessage(playerDetails)) {
socket.emit(SockerIoEvent.MESSAGE_ERROR, {message: 'Invalid SET_PLAYER_DETAILS message.'}); socket.emit(SocketIoEvent.MESSAGE_ERROR, {message: 'Invalid SET_PLAYER_DETAILS message.'});
console.warn('Invalid SET_PLAYER_DETAILS message received: ', playerDetails); console.warn('Invalid SET_PLAYER_DETAILS message received: ', playerDetails);
return; return;
} }
@ -350,10 +416,10 @@ export class IoSocketController {
} }
}); });
socket.on(SockerIoEvent.SET_SILENT, (silent: unknown) => { socket.on(SocketIoEvent.SET_SILENT, (silent: unknown) => {
console.log(SockerIoEvent.SET_SILENT, silent); //console.log(SocketIoEvent.SET_SILENT, silent);
if (typeof silent !== "boolean") { if (typeof silent !== "boolean") {
socket.emit(SockerIoEvent.MESSAGE_ERROR, {message: 'Invalid SET_SILENT message.'}); socket.emit(SocketIoEvent.MESSAGE_ERROR, {message: 'Invalid SET_SILENT message.'});
console.warn('Invalid SET_SILENT message received: ', silent); console.warn('Invalid SET_SILENT message received: ', silent);
return; return;
} }
@ -374,22 +440,42 @@ export class IoSocketController {
} }
}); });
socket.on(SockerIoEvent.ITEM_EVENT, (itemEvent: unknown) => { socket.on(SocketIoEvent.ITEM_EVENT, (message: unknown) => {
if (!isItemEventMessageInterface(itemEvent)) { if (!(message instanceof Buffer)) {
socket.emit(SockerIoEvent.MESSAGE_ERROR, {message: 'Invalid ITEM_EVENT message.'}); socket.emit(SocketIoEvent.MESSAGE_ERROR, {message: 'Invalid ITEM_EVENT message. Expecting binary buffer.'});
console.warn('Invalid ITEM_EVENT message received: ', itemEvent); console.warn('Invalid ITEM_EVENT message received (expecting binary buffer): ', message);
return; return;
} }
const itemEventMessage = ItemEventMessage.deserializeBinary(new Uint8Array(message));
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 { try {
const Client = (socket as ExSocketInterface); const Client = (socket as ExSocketInterface);
socket.to(Client.roomId).emit(SockerIoEvent.ITEM_EVENT, itemEvent); //socket.to(Client.roomId).emit(SocketIoEvent.ITEM_EVENT, itemEvent);
const world = this.Worlds.get(Client.roomId); const world = this.Worlds.get(Client.roomId);
if (!world) { if (!world) {
console.error("Could not find world with id '", Client.roomId, "'"); console.error("Could not find world with id '", Client.roomId, "'");
return; return;
} }
const subMessage = new SubMessage();
subMessage.setItemeventmessage(itemEventMessage);
// Let's send the event without using the SocketIO room.
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);
}
world.setItemState(itemEvent.itemId, itemEvent.state); world.setItemState(itemEvent.itemId, itemEvent.state);
} catch (e) { } catch (e) {
console.error('An error occurred on "item_event"'); console.error('An error occurred on "item_event"');
@ -401,7 +487,7 @@ export class IoSocketController {
emitVideo(socket: ExSocketInterface, data: unknown){ emitVideo(socket: ExSocketInterface, data: unknown){
if (!isWebRtcSignalMessageInterface(data)) { if (!isWebRtcSignalMessageInterface(data)) {
socket.emit(SockerIoEvent.MESSAGE_ERROR, {message: 'Invalid WEBRTC_SIGNAL message.'}); socket.emit(SocketIoEvent.MESSAGE_ERROR, {message: 'Invalid WEBRTC_SIGNAL message.'});
console.warn('Invalid WEBRTC_SIGNAL message received: ', data); console.warn('Invalid WEBRTC_SIGNAL message received: ', data);
return; return;
} }
@ -411,7 +497,7 @@ export class IoSocketController {
console.warn("While exchanging a WebRTC signal: client with id ", data.receiverId, " does not exist. This might be a race condition."); console.warn("While exchanging a WebRTC signal: client with id ", data.receiverId, " does not exist. This might be a race condition.");
return; return;
} }
return client.emit(SockerIoEvent.WEBRTC_SIGNAL, { return client.emit(SocketIoEvent.WEBRTC_SIGNAL, {
userId: socket.userId, userId: socket.userId,
signal: data.signal signal: data.signal
}); });
@ -419,7 +505,7 @@ export class IoSocketController {
emitScreenSharing(socket: ExSocketInterface, data: unknown){ emitScreenSharing(socket: ExSocketInterface, data: unknown){
if (!isWebRtcSignalMessageInterface(data)) { if (!isWebRtcSignalMessageInterface(data)) {
socket.emit(SockerIoEvent.MESSAGE_ERROR, {message: 'Invalid WEBRTC_SCREEN_SHARING message.'}); socket.emit(SocketIoEvent.MESSAGE_ERROR, {message: 'Invalid WEBRTC_SCREEN_SHARING message.'});
console.warn('Invalid WEBRTC_SCREEN_SHARING message received: ', data); console.warn('Invalid WEBRTC_SCREEN_SHARING message received: ', data);
return; return;
} }
@ -429,13 +515,13 @@ export class IoSocketController {
console.warn("While exchanging a WEBRTC_SCREEN_SHARING signal: client with id ", data.receiverId, " does not exist. This might be a race condition."); console.warn("While exchanging a WEBRTC_SCREEN_SHARING signal: client with id ", data.receiverId, " does not exist. This might be a race condition.");
return; return;
} }
return client.emit(SockerIoEvent.WEBRTC_SCREEN_SHARING_SIGNAL, { return client.emit(SocketIoEvent.WEBRTC_SCREEN_SHARING_SIGNAL, {
userId: socket.userId, userId: socket.userId,
signal: data.signal signal: data.signal
}); });
} }
searchClientByIdOrFail(userId: string): ExSocketInterface { searchClientByIdOrFail(userId: number): ExSocketInterface {
const client: ExSocketInterface|undefined = this.sockets.get(userId); const client: ExSocketInterface|undefined = this.sockets.get(userId);
if (client === undefined) { if (client === undefined) {
throw new Error("Could not find user with id " + userId); throw new Error("Could not find user with id " + userId);
@ -474,22 +560,27 @@ export class IoSocketController {
//check and create new world for a room //check and create new world for a room
let world = this.Worlds.get(roomId) let world = this.Worlds.get(roomId)
if(world === undefined){ if(world === undefined){
world = new World((user1: string, group: Group) => { world = new World((user1: number, group: Group) => {
this.connectedUser(user1, group); this.connectedUser(user1, group);
}, (user1: string, group: Group) => { }, (user1: number, group: Group) => {
this.disConnectedUser(user1, group); this.disConnectedUser(user1, group);
}, MINIMUM_DISTANCE, GROUP_RADIUS, (thing: Movable, listener: User) => { }, MINIMUM_DISTANCE, GROUP_RADIUS, (thing: Movable, listener: User) => {
const clientListener = this.searchClientByIdOrFail(listener.id); const clientListener = this.searchClientByIdOrFail(listener.id);
if (thing instanceof User) { if (thing instanceof User) {
const clientUser = this.searchClientByIdOrFail(thing.id); const clientUser = this.searchClientByIdOrFail(thing.id);
const messageUserJoined = new MessageUserJoined(clientUser.userId, clientUser.name, clientUser.characterLayers, clientUser.position);
clientListener.emit(SockerIoEvent.JOIN_ROOM, messageUserJoined); const userJoinedMessage = new UserJoinedMessage();
userJoinedMessage.setUserid(clientUser.userId);
userJoinedMessage.setName(clientUser.name);
userJoinedMessage.setCharacterlayersList(clientUser.characterLayers);
userJoinedMessage.setPosition(ProtobufUtils.toPositionMessage(clientUser.position));
const subMessage = new SubMessage();
subMessage.setUserjoinedmessage(userJoinedMessage);
emitInBatch(clientListener, SocketIoEvent.JOIN_ROOM, subMessage);
} else if (thing instanceof Group) { } else if (thing instanceof Group) {
clientListener.emit(SockerIoEvent.GROUP_CREATE_UPDATE, { this.emitCreateUpdateGroupEvent(clientListener, thing);
position: thing.getPosition(),
groupId: thing.getId()
} as GroupUpdateInterface);
} else { } else {
console.error('Unexpected type for Movable.'); console.error('Unexpected type for Movable.');
} }
@ -498,13 +589,17 @@ export class IoSocketController {
if (thing instanceof User) { if (thing instanceof User) {
const clientUser = this.searchClientByIdOrFail(thing.id); const clientUser = this.searchClientByIdOrFail(thing.id);
clientListener.emitInBatch(SockerIoEvent.USER_MOVED, new MessageUserMoved(clientUser.userId, clientUser.position)); const userMovedMessage = new UserMovedMessage();
userMovedMessage.setUserid(clientUser.userId);
userMovedMessage.setPosition(ProtobufUtils.toPositionMessage(clientUser.position));
const subMessage = new SubMessage();
subMessage.setUsermovedmessage(userMovedMessage);
clientListener.emitInBatch(SocketIoEvent.USER_MOVED, subMessage);
//console.log("Sending USER_MOVED event"); //console.log("Sending USER_MOVED event");
} else if (thing instanceof Group) { } else if (thing instanceof Group) {
clientListener.emit(SockerIoEvent.GROUP_CREATE_UPDATE, { this.emitCreateUpdateGroupEvent(clientListener, thing);
position: thing.getPosition(),
groupId: thing.getId()
} as GroupUpdateInterface);
} else { } else {
console.error('Unexpected type for Movable.'); console.error('Unexpected type for Movable.');
} }
@ -512,10 +607,9 @@ export class IoSocketController {
const clientListener = this.searchClientByIdOrFail(listener.id); const clientListener = this.searchClientByIdOrFail(listener.id);
if (thing instanceof User) { if (thing instanceof User) {
const clientUser = this.searchClientByIdOrFail(thing.id); const clientUser = this.searchClientByIdOrFail(thing.id);
clientListener.emit(SockerIoEvent.USER_LEFT, clientUser.userId); this.emitUserLeftEvent(clientListener, clientUser.userId);
//console.log("Sending USER_LEFT event");
} else if (thing instanceof Group) { } else if (thing instanceof Group) {
clientListener.emit(SockerIoEvent.GROUP_DELETE, thing.getId()); this.emitDeleteGroupEvent(clientListener, thing.getId());
} else { } else {
console.error('Unexpected type for Movable.'); console.error('Unexpected type for Movable.');
} }
@ -526,16 +620,52 @@ export class IoSocketController {
// Dispatch groups position to newly connected user // Dispatch groups position to newly connected user
world.getGroups().forEach((group: Group) => { world.getGroups().forEach((group: Group) => {
Client.emit(SockerIoEvent.GROUP_CREATE_UPDATE, { this.emitCreateUpdateGroupEvent(Client, group);
position: group.getPosition(),
groupId: group.getId()
} as GroupUpdateInterface);
}); });
//join world //join world
world.join(Client, Client.position); world.join(Client, Client.position);
return world; return world;
} }
private emitCreateUpdateGroupEvent(socket: Socket, group: Group): void {
const position = group.getPosition();
const pointMessage = new PointMessage();
pointMessage.setX(Math.floor(position.x));
pointMessage.setY(Math.floor(position.y));
const groupUpdateMessage = new GroupUpdateMessage();
groupUpdateMessage.setGroupid(group.getId());
groupUpdateMessage.setPosition(pointMessage);
const subMessage = new SubMessage();
subMessage.setGroupupdatemessage(groupUpdateMessage);
const client : ExSocketInterface = socket as ExSocketInterface;
emitInBatch(client, SocketIoEvent.GROUP_CREATE_UPDATE, subMessage);
//socket.emit(SocketIoEvent.GROUP_CREATE_UPDATE, groupUpdateMessage.serializeBinary().buffer);
}
private emitDeleteGroupEvent(socket: Socket, groupId: number): void {
const groupDeleteMessage = new GroupDeleteMessage();
groupDeleteMessage.setGroupid(groupId);
const subMessage = new SubMessage();
subMessage.setGroupdeletemessage(groupDeleteMessage);
const client : ExSocketInterface = socket as ExSocketInterface;
emitInBatch(client, SocketIoEvent.GROUP_DELETE, subMessage);
}
private emitUserLeftEvent(socket: Socket, userId: number): void {
const userLeftMessage = new UserLeftMessage();
userLeftMessage.setUserid(userId);
const subMessage = new SubMessage();
subMessage.setUserleftmessage(userLeftMessage);
const client : ExSocketInterface = socket as ExSocketInterface;
emitInBatch(client, SocketIoEvent.USER_LEFT, subMessage);
}
/** /**
* *
* @param socket * @param socket
@ -571,7 +701,7 @@ export class IoSocketController {
return tabs; return tabs;
}, []); }, []);
client.emit(SockerIoEvent.WEBRTC_START, {clients: peerClients, roomId: roomId}); client.emit(SocketIoEvent.WEBRTC_START, {clients: peerClients, roomId: roomId});
}); });
} }
@ -593,19 +723,19 @@ export class IoSocketController {
**/ **/
//connected user //connected user
connectedUser(userId: string, group: Group) { connectedUser(userId: number, group: Group) {
/*let Client = this.sockets.get(userId); /*let Client = this.sockets.get(userId);
if (Client === undefined) { if (Client === undefined) {
return; return;
}*/ }*/
const Client = this.searchClientByIdOrFail(userId); const Client = this.searchClientByIdOrFail(userId);
this.joinWebRtcRoom(Client, group.getId()); this.joinWebRtcRoom(Client, "webrtcroom"+group.getId());
} }
//disconnect user //disconnect user
disConnectedUser(userId: string, group: Group) { disConnectedUser(userId: number, group: Group) {
const Client = this.searchClientByIdOrFail(userId); const Client = this.searchClientByIdOrFail(userId);
Client.to(group.getId()).emit(SockerIoEvent.WEBRTC_DISCONNECT, { Client.to("webrtcroom"+group.getId()).emit(SocketIoEvent.WEBRTC_DISCONNECT, {
userId: userId userId: userId
}); });
@ -615,7 +745,7 @@ export class IoSocketController {
// the other player will try connecting until a timeout happens (during this time, the connection icon will be displayed for nothing). // the other player will try connecting until a timeout happens (during this time, the connection icon will be displayed for nothing).
// So we also send the disconnect event to the other player. // So we also send the disconnect event to the other player.
for (const user of group.getUsers()) { for (const user of group.getUsers()) {
Client.emit(SockerIoEvent.WEBRTC_DISCONNECT, { Client.emit(SocketIoEvent.WEBRTC_DISCONNECT, {
userId: user.id userId: user.id
}); });
} }
@ -627,4 +757,8 @@ export class IoSocketController {
Client.leave(Client.webRtcRoomId); Client.leave(Client.webRtcRoomId);
delete Client.webRtcRoomId; delete Client.webRtcRoomId;
} }
public getWorlds(): Map<string, World> {
return this.Worlds;
}
} }

1
back/src/Messages/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/generated/

View File

@ -3,32 +3,36 @@ import { User } from "./User";
import {PositionInterface} from "_Model/PositionInterface"; import {PositionInterface} from "_Model/PositionInterface";
import {uuid} from "uuidv4"; import {uuid} from "uuidv4";
import {Movable} from "_Model/Movable"; import {Movable} from "_Model/Movable";
import {PositionNotifier} from "_Model/PositionNotifier";
export class Group implements Movable { export class Group implements Movable {
static readonly MAX_PER_GROUP = 4; static readonly MAX_PER_GROUP = 4;
private id: string; private static nextId: number = 1;
private id: number;
private users: Set<User>; private users: Set<User>;
private connectCallback: ConnectCallback; private x!: number;
private disconnectCallback: DisconnectCallback; private y!: number;
constructor(users: User[], connectCallback: ConnectCallback, disconnectCallback: DisconnectCallback) { constructor(users: User[], private connectCallback: ConnectCallback, private disconnectCallback: DisconnectCallback, private positionNotifier: PositionNotifier) {
this.users = new Set<User>(); this.users = new Set<User>();
this.connectCallback = connectCallback; this.id = Group.nextId;
this.disconnectCallback = disconnectCallback; Group.nextId++;
this.id = uuid();
users.forEach((user: User) => { users.forEach((user: User) => {
this.join(user); this.join(user);
}); });
this.updatePosition();
} }
getUsers(): User[] { getUsers(): User[] {
return Array.from(this.users.values()); return Array.from(this.users.values());
} }
getId() : string{ getId() : number {
return this.id; return this.id;
} }
@ -36,19 +40,40 @@ export class Group implements Movable {
* Returns the barycenter of all users (i.e. the center of the group) * Returns the barycenter of all users (i.e. the center of the group)
*/ */
getPosition(): PositionInterface { getPosition(): PositionInterface {
return {
x: this.x,
y: this.y
};
}
/**
* Computes the barycenter of all users (i.e. the center of the group)
*/
updatePosition(): void {
const oldX = this.x;
const oldY = this.y;
let x = 0; let x = 0;
let y = 0; let y = 0;
// Let's compute the barycenter of all users. // Let's compute the barycenter of all users.
this.users.forEach((user: User) => { this.users.forEach((user: User) => {
x += user.position.x; const position = user.getPosition();
y += user.position.y; x += position.x;
y += position.y;
}); });
x /= this.users.size; x /= this.users.size;
y /= this.users.size; y /= this.users.size;
return { if (this.users.size === 0) {
x, throw new Error("EMPTY GROUP FOUND!!!");
y }
}; this.x = x;
this.y = y;
if (oldX === undefined) {
this.positionNotifier.enter(this);
} else {
this.positionNotifier.updatePosition(this, {x, y}, {x: oldX, y: oldY});
}
} }
isFull(): boolean { isFull(): boolean {
@ -75,6 +100,10 @@ export class Group implements Movable {
} }
user.group = undefined; user.group = undefined;
if (this.users.size !== 0) {
this.updatePosition();
}
// Broadcast on the right event // Broadcast on the right event
this.disconnectCallback(user.id, this); this.disconnectCallback(user.id, this);
} }

View File

@ -74,6 +74,13 @@ export class PositionNotifier {
return things; return things;
} }
public enter(thing: Movable): void {
const position = thing.getPosition();
const zoneDesc = this.getZoneDescriptorFromCoordinates(position.x, position.y);
const zone = this.getZone(zoneDesc.i, zoneDesc.j);
zone.enter(thing, null, position);
}
public updatePosition(thing: Movable, newPosition: PositionInterface, oldPosition: PositionInterface): void { public updatePosition(thing: Movable, newPosition: PositionInterface, oldPosition: PositionInterface): void {
// Did we change zone? // Did we change zone?
const oldZoneDesc = this.getZoneDescriptorFromCoordinates(oldPosition.x, oldPosition.y); const oldZoneDesc = this.getZoneDescriptorFromCoordinates(oldPosition.x, oldPosition.y);
@ -117,7 +124,7 @@ export class PositionNotifier {
let zone = this.zones[j][i]; let zone = this.zones[j][i];
if (zone === undefined) { if (zone === undefined) {
zone = new Zone(this.onUserEnters, this.onUserMoves, this.onUserLeaves); zone = new Zone(this.onUserEnters, this.onUserMoves, this.onUserLeaves, i, j);
this.zones[j][i] = zone; this.zones[j][i] = zone;
} }
return zone; return zone;

View File

@ -3,21 +3,30 @@ import { PointInterface } from "./Websocket/PointInterface";
import {Zone} from "_Model/Zone"; import {Zone} from "_Model/Zone";
import {Movable} from "_Model/Movable"; import {Movable} from "_Model/Movable";
import {PositionInterface} from "_Model/PositionInterface"; import {PositionInterface} from "_Model/PositionInterface";
import {PositionNotifier} from "_Model/PositionNotifier";
export class User implements Movable { export class User implements Movable {
public listenedZones: Set<Zone>; public listenedZones: Set<Zone>;
public group?: Group; public group?: Group;
public constructor( public constructor(
public id: string, public id: number,
public position: PointInterface, private position: PointInterface,
public silent: boolean, public silent: boolean,
private positionNotifier: PositionNotifier
) { ) {
this.listenedZones = new Set<Zone>(); this.listenedZones = new Set<Zone>();
this.positionNotifier.enter(this);
} }
public getPosition(): PositionInterface { public getPosition(): PointInterface {
return this.position; return this.position;
} }
public setPosition(position: PointInterface): void {
const oldPosition = this.position;
this.position = position;
this.positionNotifier.updatePosition(this, position, oldPosition);
}
} }

View File

@ -3,12 +3,14 @@ import {PointInterface} from "./PointInterface";
import {Identificable} from "./Identificable"; import {Identificable} from "./Identificable";
import {TokenInterface} from "../../Controller/AuthenticateController"; import {TokenInterface} from "../../Controller/AuthenticateController";
import {ViewportInterface} from "_Model/Websocket/ViewportMessage"; import {ViewportInterface} from "_Model/Websocket/ViewportMessage";
import {BatchMessage, SubMessage} from "../../Messages/generated/messages_pb";
export interface ExSocketInterface extends Socket, Identificable { export interface ExSocketInterface extends Socket, Identificable {
token: string; token: string;
roomId: string; roomId: string;
webRtcRoomId: string; webRtcRoomId: string|undefined;
userId: string; userId: number; // A temporary (autoincremented) identifier for this user
userUuid: string; // A unique identifier for this user
name: string; name: string;
characterLayers: string[]; characterLayers: string[];
position: PointInterface; position: PointInterface;
@ -17,7 +19,7 @@ export interface ExSocketInterface extends Socket, Identificable {
/** /**
* Pushes an event that will be sent in the next batch of events * Pushes an event that will be sent in the next batch of events
*/ */
emitInBatch: (event: string | symbol, payload: unknown) => void; emitInBatch: (event: string, payload: SubMessage) => void;
batchedMessages: Array<{ event: string | symbol, payload: unknown }>; batchedMessages: BatchMessage;
batchTimeout: NodeJS.Timeout|null; batchTimeout: NodeJS.Timeout|null;
} }

View File

@ -2,5 +2,5 @@ import {PositionInterface} from "_Model/PositionInterface";
export interface GroupUpdateInterface { export interface GroupUpdateInterface {
position: PositionInterface, position: PositionInterface,
groupId: string, groupId: number,
} }

View File

@ -1,3 +1,3 @@
export interface Identificable { export interface Identificable {
userId: string; userId: number;
} }

View File

@ -1,6 +1,6 @@
import {PointInterface} from "_Model/Websocket/PointInterface"; import {PointInterface} from "_Model/Websocket/PointInterface";
export class MessageUserJoined { export class MessageUserJoined {
constructor(public userId: string, public name: string, public characterLayers: string[], public position: PointInterface) { constructor(public userId: number, public name: string, public characterLayers: string[], public position: PointInterface) {
} }
} }

View File

@ -1,6 +0,0 @@
import {PointInterface} from "./PointInterface";
export class MessageUserMoved {
constructor(public userId: string, public position: PointInterface) {
}
}

View File

@ -6,6 +6,6 @@ export class Point implements PointInterface{
} }
export class MessageUserPosition { export class MessageUserPosition {
constructor(public userId: string, public name: string, public characterLayers: string[], public position: PointInterface) { constructor(public userId: number, public name: string, public characterLayers: string[], public position: PointInterface) {
} }
} }

View File

@ -0,0 +1,55 @@
import {PointInterface} from "./PointInterface";
import {ItemEventMessage, PositionMessage} from "../../Messages/generated/messages_pb";
import {ExSocketInterface} from "_Model/Websocket/ExSocketInterface";
import Direction = PositionMessage.Direction;
import {ItemEventMessageInterface} from "_Model/Websocket/ItemEventMessage";
export class ProtobufUtils {
public static toPositionMessage(point: PointInterface): PositionMessage {
let direction: PositionMessage.DirectionMap[keyof PositionMessage.DirectionMap];
switch (point.direction) {
case 'up':
direction = Direction.UP;
break;
case 'down':
direction = Direction.DOWN;
break;
case 'left':
direction = Direction.LEFT;
break;
case 'right':
direction = Direction.RIGHT;
break;
default:
throw new Error('unexpected direction');
}
const position = new PositionMessage();
position.setX(point.x);
position.setY(point.y);
position.setMoving(point.moving);
position.setDirection(direction);
return position;
}
public static toItemEvent(itemEventMessage: ItemEventMessage): ItemEventMessageInterface {
return {
itemId: itemEventMessage.getItemid(),
event: itemEventMessage.getEvent(),
parameters: JSON.parse(itemEventMessage.getParametersjson()),
state: JSON.parse(itemEventMessage.getStatejson()),
}
}
public static toItemEventProtobuf(itemEvent: ItemEventMessageInterface): ItemEventMessage {
const itemEventMessage = new ItemEventMessage();
itemEventMessage.setItemid(itemEvent.itemId);
itemEventMessage.setEvent(itemEvent.event);
itemEventMessage.setParametersjson(JSON.stringify(itemEvent.parameters));
itemEventMessage.setStatejson(JSON.stringify(itemEvent.state));
return itemEventMessage;
}
}

View File

@ -1,5 +1,5 @@
export interface UserInGroupInterface { export interface UserInGroupInterface {
userId: string, userId: number,
name: string, name: string,
initiator: boolean initiator: boolean
} }

View File

@ -1,11 +0,0 @@
import * as tg from "generic-type-guard";
import {isPointInterface} from "./PointInterface";
import {isViewport} from "./ViewportMessage";
export const isUserMovesInterface =
new tg.IsInterface().withProperties({
position: isPointInterface,
viewport: isViewport,
}).get();
export type UserMovesInterface = tg.GuardedType<typeof isUserMovesInterface>;

View File

@ -7,12 +7,12 @@ export const isSignalData =
export const isWebRtcSignalMessageInterface = export const isWebRtcSignalMessageInterface =
new tg.IsInterface().withProperties({ new tg.IsInterface().withProperties({
receiverId: tg.isString, receiverId: tg.isNumber,
signal: isSignalData signal: isSignalData
}).get(); }).get();
export const isWebRtcScreenSharingStartMessageInterface = export const isWebRtcScreenSharingStartMessageInterface =
new tg.IsInterface().withProperties({ new tg.IsInterface().withProperties({
userId: tg.isString, userId: tg.isNumber,
roomId: tg.isString roomId: tg.isString
}).get(); }).get();
export type WebRtcSignalMessageInterface = tg.GuardedType<typeof isWebRtcSignalMessageInterface>; export type WebRtcSignalMessageInterface = tg.GuardedType<typeof isWebRtcSignalMessageInterface>;

View File

@ -11,15 +11,15 @@ import {PositionNotifier} from "./PositionNotifier";
import {ViewportInterface} from "_Model/Websocket/ViewportMessage"; import {ViewportInterface} from "_Model/Websocket/ViewportMessage";
import {Movable} from "_Model/Movable"; import {Movable} from "_Model/Movable";
export type ConnectCallback = (user: string, group: Group) => void; export type ConnectCallback = (user: number, group: Group) => void;
export type DisconnectCallback = (user: string, group: Group) => void; export type DisconnectCallback = (user: number, group: Group) => void;
export class World { export class World {
private readonly minDistance: number; private readonly minDistance: number;
private readonly groupRadius: number; private readonly groupRadius: number;
// Users, sorted by ID // Users, sorted by ID
private readonly users: Map<string, User>; private readonly users: Map<number, User>;
private readonly groups: Set<Group>; private readonly groups: Set<Group>;
private readonly connectCallback: ConnectCallback; private readonly connectCallback: ConnectCallback;
@ -37,7 +37,7 @@ export class World {
onMoves: MovesCallback, onMoves: MovesCallback,
onLeaves: LeavesCallback) onLeaves: LeavesCallback)
{ {
this.users = new Map<string, User>(); this.users = new Map<number, User>();
this.groups = new Set<Group>(); this.groups = new Set<Group>();
this.connectCallback = connectCallback; this.connectCallback = connectCallback;
this.disconnectCallback = disconnectCallback; this.disconnectCallback = disconnectCallback;
@ -51,14 +51,16 @@ export class World {
return Array.from(this.groups.values()); return Array.from(this.groups.values());
} }
public getUsers(): Map<string, User> { public getUsers(): Map<number, User> {
return this.users; return this.users;
} }
public join(socket : Identificable, userPosition: PointInterface): void { public join(socket : Identificable, userPosition: PointInterface): void {
this.users.set(socket.userId, new User(socket.userId, userPosition, false)); const user = new User(socket.userId, userPosition, false, this.positionNotifier);
this.users.set(socket.userId, user);
// Let's call update position to trigger the join / leave room // Let's call update position to trigger the join / leave room
this.updatePosition(socket, userPosition); //this.updatePosition(socket, userPosition);
this.updateUserGroup(user);
} }
public leave(user : Identificable){ public leave(user : Identificable){
@ -87,11 +89,13 @@ export class World {
return; return;
} }
this.positionNotifier.updatePosition(user, userPosition, user.position); user.setPosition(userPosition);
const oldGroupPosition = user.group?.getPosition(); this.updateUserGroup(user);
}
user.position = userPosition; private updateUserGroup(user: User): void {
user.group?.updatePosition();
if (user.silent) { if (user.silent) {
return; return;
@ -111,7 +115,7 @@ export class World {
const group: Group = new Group([ const group: Group = new Group([
user, user,
closestUser closestUser
], this.connectCallback, this.disconnectCallback); ], this.connectCallback, this.disconnectCallback, this.positionNotifier);
this.groups.add(group); this.groups.add(group);
} }
} }
@ -119,16 +123,11 @@ export class World {
} else { } else {
// If the user is part of a group: // If the user is part of a group:
// should he leave the group? // should he leave the group?
const distance = World.computeDistanceBetweenPositions(user.position, user.group.getPosition()); const distance = World.computeDistanceBetweenPositions(user.getPosition(), user.group.getPosition());
if (distance > this.groupRadius) { if (distance > this.groupRadius) {
this.leaveGroup(user); this.leaveGroup(user);
} }
} }
// At the very end, if the user is part of a group, let's call the callback to update group position
if (typeof user.group !== 'undefined') {
this.positionNotifier.updatePosition(user.group, user.group.getPosition(), oldGroupPosition ? oldGroupPosition : user.group.getPosition());
}
} }
setSilent(socket: Identificable, silent: boolean) { setSilent(socket: Identificable, silent: boolean) {
@ -147,7 +146,7 @@ export class World {
} }
if (!silent) { if (!silent) {
// If we are back to life, let's trigger a position update to see if we can join some group. // If we are back to life, let's trigger a position update to see if we can join some group.
this.updatePosition(socket, user.position); this.updatePosition(socket, user.getPosition());
} }
} }
@ -158,9 +157,10 @@ export class World {
*/ */
private leaveGroup(user: User): void { private leaveGroup(user: User): void {
const group = user.group; const group = user.group;
if (typeof group === 'undefined') { if (group === undefined) {
throw new Error("The user is part of no group"); throw new Error("The user is part of no group");
} }
const oldPosition = group.getPosition();
group.leave(user); group.leave(user);
if (group.isEmpty()) { if (group.isEmpty()) {
this.positionNotifier.leave(group); this.positionNotifier.leave(group);
@ -170,7 +170,8 @@ export class World {
} }
this.groups.delete(group); this.groups.delete(group);
} else { } else {
this.positionNotifier.updatePosition(group, group.getPosition(), group.getPosition()); group.updatePosition();
//this.positionNotifier.updatePosition(group, group.getPosition(), oldPosition);
} }
} }
@ -244,7 +245,7 @@ export class World {
if (group.isFull()) { if (group.isFull()) {
return; return;
} }
const distance = World.computeDistanceBetweenPositions(user.position, group.getPosition()); const distance = World.computeDistanceBetweenPositions(user.getPosition(), group.getPosition());
if(distance <= minimumDistanceFound && distance <= this.groupRadius) { if(distance <= minimumDistanceFound && distance <= this.groupRadius) {
minimumDistanceFound = distance; minimumDistanceFound = distance;
matchingItem = group; matchingItem = group;
@ -256,7 +257,9 @@ export class World {
public static computeDistance(user1: User, user2: User): number public static computeDistance(user1: User, user2: User): number
{ {
return Math.sqrt(Math.pow(user2.position.x - user1.position.x, 2) + Math.pow(user2.position.y - user1.position.y, 2)); const user1Position = user1.getPosition();
const user2Position = user2.getPosition();
return Math.sqrt(Math.pow(user2Position.x - user1Position.x, 2) + Math.pow(user2Position.y - user1Position.y, 2));
} }
public static computeDistanceBetweenPositions(position1: PositionInterface, position2: PositionInterface): number public static computeDistanceBetweenPositions(position1: PositionInterface, position2: PositionInterface): number

View File

@ -1,6 +1,7 @@
import {User} from "./User"; import {User} from "./User";
import {PositionInterface} from "_Model/PositionInterface"; import {PositionInterface} from "_Model/PositionInterface";
import {Movable} from "_Model/Movable"; import {Movable} from "./Movable";
import {Group} from "./Group";
export type EntersCallback = (thing: Movable, listener: User) => void; export type EntersCallback = (thing: Movable, listener: User) => void;
export type MovesCallback = (thing: Movable, position: PositionInterface, listener: User) => void; export type MovesCallback = (thing: Movable, position: PositionInterface, listener: User) => void;
@ -10,14 +11,27 @@ export class Zone {
private things: Set<Movable> = new Set<Movable>(); private things: Set<Movable> = new Set<Movable>();
private listeners: Set<User> = new Set<User>(); private listeners: Set<User> = new Set<User>();
constructor(private onEnters: EntersCallback, private onMoves: MovesCallback, private onLeaves: LeavesCallback) { /**
* @param x For debugging purpose only
* @param y For debugging purpose only
*/
constructor(private onEnters: EntersCallback, private onMoves: MovesCallback, private onLeaves: LeavesCallback, private x: number, private y: number) {
} }
/** /**
* A user/thing leaves the zone * A user/thing leaves the zone
*/ */
public leave(thing: Movable, newZone: Zone|null) { public leave(thing: Movable, newZone: Zone|null) {
this.things.delete(thing); const result = this.things.delete(thing);
if (!result) {
if (thing instanceof User) {
throw new Error('Could not find user in zone '+thing.id);
}
if (thing instanceof Group) {
throw new Error('Could not find group '+thing.getId()+' in zone ('+this.x+','+this.y+'). Position of group: ('+thing.getPosition().x+','+thing.getPosition().y+')');
}
}
this.notifyLeft(thing, newZone); this.notifyLeft(thing, newZone);
} }
@ -34,13 +48,13 @@ export class Zone {
public enter(thing: Movable, oldZone: Zone|null, position: PositionInterface) { public enter(thing: Movable, oldZone: Zone|null, position: PositionInterface) {
this.things.add(thing); this.things.add(thing);
this.notifyUserEnter(thing, oldZone, position); this.notifyEnter(thing, oldZone, position);
} }
/** /**
* Notify listeners of this zone that this user entered * Notify listeners of this zone that this user entered
*/ */
private notifyUserEnter(thing: Movable, oldZone: Zone|null, position: PositionInterface) { private notifyEnter(thing: Movable, oldZone: Zone|null, position: PositionInterface) {
for (const listener of this.listeners) { for (const listener of this.listeners) {
if (listener === thing) { if (listener === thing) {
continue; continue;
@ -56,8 +70,7 @@ export class Zone {
public move(thing: Movable, position: PositionInterface) { public move(thing: Movable, position: PositionInterface) {
if (!this.things.has(thing)) { if (!this.things.has(thing)) {
this.things.add(thing); this.things.add(thing);
const foo = this.things; this.notifyEnter(thing, null, position);
this.notifyUserEnter(thing, null, position);
return; return;
} }

View File

@ -9,14 +9,6 @@ import {Zone} from "_Model/Zone";
import {Movable} from "_Model/Movable"; import {Movable} from "_Model/Movable";
import {PositionInterface} from "_Model/PositionInterface"; import {PositionInterface} from "_Model/PositionInterface";
function move(user: User, x: number, y: number, positionNotifier: PositionNotifier): void {
positionNotifier.updatePosition(user, {
x,
y
}, user.position);
user.position.x = x;
user.position.y = y;
}
describe("PositionNotifier", () => { describe("PositionNotifier", () => {
it("should receive notifications when player moves", () => { it("should receive notifications when player moves", () => {
@ -32,19 +24,19 @@ describe("PositionNotifier", () => {
leaveTriggered = true; leaveTriggered = true;
}); });
const user1 = new User("1", { const user1 = new User(1, {
x: 500, x: 500,
y: 500, y: 500,
moving: false, moving: false,
direction: 'down' direction: 'down'
}, false); }, false, positionNotifier);
const user2 = new User("2", { const user2 = new User(2, {
x: -9999, x: -9999,
y: -9999, y: -9999,
moving: false, moving: false,
direction: 'down' direction: 'down'
}, false); }, false, positionNotifier);
positionNotifier.setViewport(user1, { positionNotifier.setViewport(user1, {
left: 200, left: 200,
@ -53,21 +45,21 @@ describe("PositionNotifier", () => {
bottom: 500 bottom: 500
}); });
move(user2, 500, 500, positionNotifier); user2.setPosition({x: 500, y: 500, direction: 'down', moving: false});
expect(enterTriggered).toBe(true); expect(enterTriggered).toBe(true);
expect(moveTriggered).toBe(false); expect(moveTriggered).toBe(false);
enterTriggered = false; enterTriggered = false;
// Move inside the zone // Move inside the zone
move(user2, 501, 500, positionNotifier); user2.setPosition({x:501, y:500, direction: 'down', moving: false});
expect(enterTriggered).toBe(false); expect(enterTriggered).toBe(false);
expect(moveTriggered).toBe(true); expect(moveTriggered).toBe(true);
moveTriggered = false; moveTriggered = false;
// Move out of the zone in a zone that we don't track // Move out of the zone in a zone that we don't track
move(user2, 901, 500, positionNotifier); user2.setPosition({x: 901, y: 500, direction: 'down', moving: false});
expect(enterTriggered).toBe(false); expect(enterTriggered).toBe(false);
expect(moveTriggered).toBe(false); expect(moveTriggered).toBe(false);
@ -75,14 +67,14 @@ describe("PositionNotifier", () => {
leaveTriggered = false; leaveTriggered = false;
// Move back in // Move back in
move(user2, 500, 500, positionNotifier); user2.setPosition({x: 500, y: 500, direction: 'down', moving: false});
expect(enterTriggered).toBe(true); expect(enterTriggered).toBe(true);
expect(moveTriggered).toBe(false); expect(moveTriggered).toBe(false);
expect(leaveTriggered).toBe(false); expect(leaveTriggered).toBe(false);
enterTriggered = false; enterTriggered = false;
// Move out of the zone in a zone that we do track // Move out of the zone in a zone that we do track
move(user2, 200, 500, positionNotifier); user2.setPosition({x: 200, y: 500, direction: 'down', moving: false});
expect(enterTriggered).toBe(false); expect(enterTriggered).toBe(false);
expect(moveTriggered).toBe(true); expect(moveTriggered).toBe(true);
expect(leaveTriggered).toBe(false); expect(leaveTriggered).toBe(false);
@ -110,19 +102,19 @@ describe("PositionNotifier", () => {
leaveTriggered = true; leaveTriggered = true;
}); });
const user1 = new User("1", { const user1 = new User(1, {
x: 500, x: 500,
y: 500, y: 500,
moving: false, moving: false,
direction: 'down' direction: 'down'
}, false); }, false, positionNotifier);
const user2 = new User("2", { const user2 = new User(2, {
x: -9999, x: 0,
y: -9999, y: 0,
moving: false, moving: false,
direction: 'down' direction: 'down'
}, false); }, false, positionNotifier);
let newUsers = positionNotifier.setViewport(user1, { let newUsers = positionNotifier.setViewport(user1, {
left: 200, left: 200,
@ -131,14 +123,16 @@ describe("PositionNotifier", () => {
bottom: 500 bottom: 500
}); });
expect(newUsers.length).toBe(0); expect(newUsers.length).toBe(2);
move(user2, 500, 500, positionNotifier);
expect(enterTriggered).toBe(true); expect(enterTriggered).toBe(true);
expect(moveTriggered).toBe(false);
enterTriggered = false; enterTriggered = false;
user2.setPosition({x: 500, y: 500, direction: 'down', moving: false});
expect(enterTriggered).toBe(false);
expect(moveTriggered).toBe(true);
moveTriggered = false;
// Move the viewport but the user stays inside. // Move the viewport but the user stays inside.
positionNotifier.setViewport(user1, { positionNotifier.setViewport(user1, {
left: 201, left: 201,
@ -176,6 +170,6 @@ describe("PositionNotifier", () => {
expect(moveTriggered).toBe(false); expect(moveTriggered).toBe(false);
expect(leaveTriggered).toBe(false); expect(leaveTriggered).toBe(false);
enterTriggered = false; enterTriggered = false;
expect(newUsers.length).toBe(1); expect(newUsers.length).toBe(2);
}); });
}) })

View File

@ -6,55 +6,55 @@ import { Group } from "../src/Model/Group";
describe("World", () => { describe("World", () => {
it("should connect user1 and user2", () => { it("should connect user1 and user2", () => {
let connectCalledNumber: number = 0; let connectCalledNumber: number = 0;
const connect: ConnectCallback = (user: string, group: Group): void => { const connect: ConnectCallback = (user: number, group: Group): void => {
connectCalledNumber++; connectCalledNumber++;
} }
const disconnect: DisconnectCallback = (user: string, group: Group): void => { const disconnect: DisconnectCallback = (user: number, group: Group): void => {
} }
const world = new World(connect, disconnect, 160, 160, () => {}, () => {}, () => {}); const world = new World(connect, disconnect, 160, 160, () => {}, () => {}, () => {});
world.join({ userId: "foo" }, new Point(100, 100)); world.join({ userId: 1 }, new Point(100, 100));
world.join({ userId: "bar" }, new Point(500, 100)); world.join({ userId: 2 }, new Point(500, 100));
world.updatePosition({ userId: "bar" }, new Point(261, 100)); world.updatePosition({ userId: 2 }, new Point(261, 100));
expect(connectCalledNumber).toBe(0); expect(connectCalledNumber).toBe(0);
world.updatePosition({ userId: "bar" }, new Point(101, 100)); world.updatePosition({ userId: 2 }, new Point(101, 100));
expect(connectCalledNumber).toBe(2); expect(connectCalledNumber).toBe(2);
world.updatePosition({ userId: "bar" }, new Point(102, 100)); world.updatePosition({ userId: 2 }, new Point(102, 100));
expect(connectCalledNumber).toBe(2); expect(connectCalledNumber).toBe(2);
}); });
it("should connect 3 users", () => { it("should connect 3 users", () => {
let connectCalled: boolean = false; let connectCalled: boolean = false;
const connect: ConnectCallback = (user: string, group: Group): void => { const connect: ConnectCallback = (user: number, group: Group): void => {
connectCalled = true; connectCalled = true;
} }
const disconnect: DisconnectCallback = (user: string, group: Group): void => { const disconnect: DisconnectCallback = (user: number, group: Group): void => {
} }
const world = new World(connect, disconnect, 160, 160, () => {}, () => {}, () => {}); const world = new World(connect, disconnect, 160, 160, () => {}, () => {}, () => {});
world.join({ userId: "foo" }, new Point(100, 100)); world.join({ userId: 1 }, new Point(100, 100));
world.join({ userId: "bar" }, new Point(200, 100)); world.join({ userId: 2 }, new Point(200, 100));
expect(connectCalled).toBe(true); expect(connectCalled).toBe(true);
connectCalled = false; connectCalled = false;
// baz joins at the outer limit of the group // baz joins at the outer limit of the group
world.join({ userId: "baz" }, new Point(311, 100)); world.join({ userId: 3 }, new Point(311, 100));
expect(connectCalled).toBe(false); expect(connectCalled).toBe(false);
world.updatePosition({ userId: "baz" }, new Point(309, 100)); world.updatePosition({ userId: 3 }, new Point(309, 100));
expect(connectCalled).toBe(true); expect(connectCalled).toBe(true);
}); });
@ -62,27 +62,27 @@ describe("World", () => {
it("should disconnect user1 and user2", () => { it("should disconnect user1 and user2", () => {
let connectCalled: boolean = false; let connectCalled: boolean = false;
let disconnectCallNumber: number = 0; let disconnectCallNumber: number = 0;
const connect: ConnectCallback = (user: string, group: Group): void => { const connect: ConnectCallback = (user: number, group: Group): void => {
connectCalled = true; connectCalled = true;
} }
const disconnect: DisconnectCallback = (user: string, group: Group): void => { const disconnect: DisconnectCallback = (user: number, group: Group): void => {
disconnectCallNumber++; disconnectCallNumber++;
} }
const world = new World(connect, disconnect, 160, 160, () => {}, () => {}, () => {}); const world = new World(connect, disconnect, 160, 160, () => {}, () => {}, () => {});
world.join({ userId: "foo" }, new Point(100, 100)); world.join({ userId: 1 }, new Point(100, 100));
world.join({ userId: "bar" }, new Point(259, 100)); world.join({ userId: 2 }, new Point(259, 100));
expect(connectCalled).toBe(true); expect(connectCalled).toBe(true);
expect(disconnectCallNumber).toBe(0); expect(disconnectCallNumber).toBe(0);
world.updatePosition({ userId: "bar" }, new Point(100+160+160+1, 100)); world.updatePosition({ userId: 2 }, new Point(100+160+160+1, 100));
expect(disconnectCallNumber).toBe(2); expect(disconnectCallNumber).toBe(2);
world.updatePosition({ userId: "bar" }, new Point(262, 100)); world.updatePosition({ userId: 2 }, new Point(262, 100));
expect(disconnectCallNumber).toBe(2); expect(disconnectCallNumber).toBe(2);
}); });

View File

@ -7,7 +7,7 @@
"downlevelIteration": true, "downlevelIteration": true,
"module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */ "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */
// "lib": [], /* Specify library files to be included in the compilation. */ // "lib": [], /* Specify library files to be included in the compilation. */
// "allowJs": true, /* Allow javascript files to be compiled. */ "allowJs": true, /* Allow javascript files to be compiled. */
// "checkJs": true, /* Report errors in .js files. */ // "checkJs": true, /* Report errors in .js files. */
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
// "declaration": true, /* Generates corresponding '.d.ts' file. */ // "declaration": true, /* Generates corresponding '.d.ts' file. */

View File

@ -27,6 +27,11 @@
"@types/connect" "*" "@types/connect" "*"
"@types/node" "*" "@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"
integrity sha512-7+kYB7x5a7nFWW1YPBh3KxhwKfiaI4PbZ1RvzBU91LZy7lWJO822CI+pqzSre/DZ7KsCuMKdHnLHHFu8AyXbQg==
"@types/color-name@^1.1.1": "@types/color-name@^1.1.1":
version "1.1.1" version "1.1.1"
resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0" resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0"
@ -57,6 +62,11 @@
"@types/qs" "*" "@types/qs" "*"
"@types/serve-static" "*" "@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"
integrity sha512-FRwj40euE2bYkG+0X5w2nEA8yAzgJRcEa7RBd0Gsdkb9/tPM2pctVVAvnOUTbcXo2VmIHPo0Ae94Gl9vRHfKzg==
"@types/http-status-codes@^1.2.0": "@types/http-status-codes@^1.2.0":
version "1.2.0" version "1.2.0"
resolved "https://registry.yarnpkg.com/@types/http-status-codes/-/http-status-codes-1.2.0.tgz#6e5244835aaf7164dd306f1d4d2dfdbb2159d909" resolved "https://registry.yarnpkg.com/@types/http-status-codes/-/http-status-codes-1.2.0.tgz#6e5244835aaf7164dd306f1d4d2dfdbb2159d909"
@ -354,6 +364,11 @@ chardet@^0.7.0:
version "0.7.0" version "0.7.0"
resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e"
circular-json@^0.5.9:
version "0.5.9"
resolved "https://registry.yarnpkg.com/circular-json/-/circular-json-0.5.9.tgz#932763ae88f4f7dead7a0d09c8a51a4743a53b1d"
integrity sha512-4ivwqHpIFJZBuhN3g/pEcdbnGUywkBblloGbkglyloVjjR3uT6tieI89MVOfbP2tHX5sgb01FuLgAOzebNlJNQ==
cli-cursor@^3.1.0: cli-cursor@^3.1.0:
version "3.1.0" version "3.1.0"
resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-3.1.0.tgz#264305a7ae490d1d03bf0c9ba7c925d1753af307" resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-3.1.0.tgz#264305a7ae490d1d03bf0c9ba7c925d1753af307"
@ -834,6 +849,11 @@ globals@^12.1.0:
dependencies: dependencies:
type-fest "^0.8.1" type-fest "^0.8.1"
google-protobuf@^3.13.0:
version "3.13.0"
resolved "https://registry.yarnpkg.com/google-protobuf/-/google-protobuf-3.13.0.tgz#909c5983d75dd6101ed57c79e0528d000cdc3251"
integrity sha512-ZIf3qfLFayVrPvAjeKKxO5FRF1/NwRxt6Dko+fWEMuHwHbZx8/fcaAao9b0wCM6kr8qeg2te8XTpyuvKuD9aKw==
graceful-fs@^4.1.2: graceful-fs@^4.1.2:
version "4.2.3" version "4.2.3"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.3.tgz#4a12ff1b60376ef09862c2093edd908328be8423" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.3.tgz#4a12ff1b60376ef09862c2093edd908328be8423"

3
benchmark/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
/node_modules/
/artillery_output.html
/artillery_output.json

View File

@ -1,17 +0,0 @@
#!/bin/bash
npm run start &
pid1=$!
npm run start:nooutput &
pid2=$!
npm run start:nooutput &
pid3=$!
npm run start:nooutput &
pid4=$!
wait $pid1
wait $pid2
wait $pid3
wait $pid4

View File

@ -0,0 +1,15 @@
#!/bin/bash
yarn run start &
pid1=$!
yarn run start &
pid2=$!
yarn run start &
pid3=$!
yarn run start &
pid4=$!
wait $pid1
wait $pid2
wait $pid3
wait $pid4

46
benchmark/index.ts Normal file
View File

@ -0,0 +1,46 @@
import {Connection} from "../front/src/Connection";
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
async function startOneUser(): Promise<void> {
const connection = await Connection.createConnection('foo', ['male3']);
await connection.joinARoom('global__maps.workadventure.localhost/Floor0/floor0', 783, 170, 'down', false, {
top: 0,
bottom: 200,
left: 500,
right: 800
});
console.log(connection.getUserId());
let angle = Math.random() * Math.PI * 2;
for (let i = 0; i < 100; i++) {
const x = Math.floor(320 + 1472/2 * (1 + Math.sin(angle)));
const y = Math.floor(200 + 1090/2 * (1 + Math.cos(angle)));
connection.sharePosition(x, y, 'down', true, {
top: y - 200,
bottom: y + 200,
left: x - 320,
right: x + 320
})
angle += 0.05;
await sleep(200);
}
await sleep(10000);
connection.closeConnection();
}
(async () => {
for (let userNo = 0; userNo < 40; userNo++) {
startOneUser();
// Wait 0.5s between adding users
await sleep(500);
}
})();

1970
benchmark/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -3,8 +3,7 @@
"version": "1.0.0", "version": "1.0.0",
"description": "Load testing for WorkAdventure", "description": "Load testing for WorkAdventure",
"scripts": { "scripts": {
"start": "artillery run socketio-load-test.yaml -o artillery_output.json && artillery report --output artillery_output.html artillery_output.json", "start": "ts-node ./index.ts"
"start:nooutput": "artillery run socketio-load-test.yaml"
}, },
"contributors": [ "contributors": [
{ {
@ -22,6 +21,11 @@
], ],
"license": "SEE LICENSE IN LICENSE.txt", "license": "SEE LICENSE IN LICENSE.txt",
"dependencies": { "dependencies": {
"artillery": "^1.6.1" "socket.io-client": "^2.3.0",
"ts-node-dev": "^1.0.0-pre.62",
"typescript": "^4.0.2"
},
"devDependencies": {
"@types/socket.io-client": "^1.4.33"
} }
} }

View File

@ -1,54 +0,0 @@
config:
target: "http://api.workadventure.localhost/"
socketio:
transports: ["websocket"]
query:
token: "test"
phases:
- duration: 20
arrivalRate: 2
processor: "./socketioLoadTest.js"
scenarios:
- name: "Connects and moves player for 20 seconds"
weight: 90
engine: "socketio"
flow:
- emit:
channel: "set-player-details"
data:
name: 'TEST'
characterLayers: ['male3']
- think: 1
- emit:
channel: "join-room"
data:
roomId: 'global__api.workadventure.localhost/map/files/Floor0/floor0'
position:
x: 783
y: 170
direction: 'down'
moving: false
viewport:
left: 500
top: 0
right: 800
bottom: 200
- think: 1
- loop:
- function: "setYRandom"
- emit:
channel: "user-position"
data:
position:
x: "{{ x }}"
y: "{{ y }}"
direction: 'down'
moving: false
viewport:
left: "{{ left }}"
top: "{{ top }}"
right: "{{ right }}"
bottom: "{{ bottom }}"
- think: 0.2
count: 100
- think: 10

View File

@ -1,20 +0,0 @@
'use strict';
module.exports = {
setYRandom
};
function setYRandom(context, events, done) {
if (context.angle === undefined) {
context.angle = Math.random() * Math.PI * 2;
}
context.angle += 0.05;
context.vars.x = 320 + 1472/2 * (1 + Math.sin(context.angle));
context.vars.y = 200 + 1090/2 * (1 + Math.cos(context.angle));
context.vars.left = context.vars.x - 320;
context.vars.top = context.vars.y - 200;
context.vars.right = context.vars.x + 320;
context.vars.bottom = context.vars.y + 200;
return done();
}

693
benchmark/yarn.lock Normal file
View File

@ -0,0 +1,693 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# 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/strip-bom@^3.0.0":
version "3.0.0"
resolved "https://registry.yarnpkg.com/@types/strip-bom/-/strip-bom-3.0.0.tgz#14a8ec3956c2e81edb7520790aecf21c290aebd2"
"@types/strip-json-comments@0.0.30":
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"
anymatch@~3.1.1:
version "3.1.1"
resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.1.tgz#c55ecf02185e2469259399310c173ce31233b142"
dependencies:
normalize-path "^3.0.0"
picomatch "^2.0.4"
arg@^4.1.0:
version "4.1.3"
resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089"
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"
dependencies:
balanced-match "^1.0.0"
concat-map "0.0.1"
braces@~3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107"
dependencies:
fill-range "^7.0.1"
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"
dependencies:
camelcase "^2.0.0"
map-obj "^1.0.0"
camelcase@^2.0.0:
version "2.1.1"
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-2.1.1.tgz#7c1d16d679a1bbe59ca02cacecfb011e201f5a1f"
chokidar@^3.4.0:
version "3.4.2"
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.4.2.tgz#38dc8e658dec3809741eb3ef7bb0a47fe424232d"
dependencies:
anymatch "~3.1.1"
braces "~3.0.2"
glob-parent "~5.1.0"
is-binary-path "~2.1.0"
is-glob "~4.0.1"
normalize-path "~3.0.0"
readdirp "~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"
currently-unhandled@^0.4.1:
version "0.4.1"
resolved "https://registry.yarnpkg.com/currently-unhandled/-/currently-unhandled-0.4.1.tgz#988df33feab191ef799a61369dd76c17adf957ea"
dependencies:
array-find-index "^1.0.1"
dateformat@~1.0.4-1.2.3:
version "1.0.12"
resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-1.0.12.tgz#9f124b67594c937ff706932e4a642cca8dbbfee9"
dependencies:
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"
diff@^4.0.1:
version "4.0.2"
resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d"
dynamic-dedupe@^0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/dynamic-dedupe/-/dynamic-dedupe-0.3.0.tgz#06e44c223f5e4e94d78ef9db23a6515ce2f962a1"
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"
dependencies:
is-arrayish "^0.2.1"
fill-range@^7.0.1:
version "7.0.1"
resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40"
dependencies:
to-regex-range "^5.0.1"
find-up@^1.0.0:
version "1.1.2"
resolved "https://registry.yarnpkg.com/find-up/-/find-up-1.1.2.tgz#6b2e9822b1a2ce0a60ab64d610eccad53cb24d0f"
dependencies:
path-exists "^2.0.0"
pinkie-promise "^2.0.0"
fs.realpath@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
fsevents@~2.1.2:
version "2.1.3"
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.1.3.tgz#fb738703ae8d2f9fe900c33836ddebee8b97f23e"
get-stdin@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-4.0.1.tgz#b968c6b0a04384324902e8bf1a5df32579a450fe"
glob-parent@~5.1.0:
version "5.1.1"
resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.1.tgz#b6c1ef417c4e5663ea498f1c45afac6916bbc229"
dependencies:
is-glob "^4.0.1"
glob@^7.1.3:
version "7.1.6"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6"
dependencies:
fs.realpath "^1.0.0"
inflight "^1.0.4"
inherits "2"
minimatch "^3.0.4"
once "^1.3.0"
path-is-absolute "^1.0.0"
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"
indent-string@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-2.1.0.tgz#8e2d48348742121b4a8218b7a137e9a52049dc80"
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"
dependencies:
once "^1.3.0"
wrappy "1"
inherits@2:
version "2.0.4"
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
is-arrayish@^0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d"
is-binary-path@~2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09"
dependencies:
binary-extensions "^2.0.0"
is-extglob@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2"
is-finite@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/is-finite/-/is-finite-1.1.0.tgz#904135c77fb42c0641d6aa1bcdbc4daa8da082f3"
is-glob@^4.0.1, is-glob@~4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc"
dependencies:
is-extglob "^2.1.1"
is-number@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b"
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"
dependencies:
graceful-fs "^4.1.2"
parse-json "^2.2.0"
pify "^2.0.0"
pinkie-promise "^2.0.0"
strip-bom "^2.0.0"
loud-rejection@^1.0.0:
version "1.6.0"
resolved "https://registry.yarnpkg.com/loud-rejection/-/loud-rejection-1.6.0.tgz#5b46f80147edee578870f086d04821cf998e551f"
dependencies:
currently-unhandled "^0.4.1"
signal-exit "^3.0.0"
make-error@^1.1.1:
version "1.3.6"
resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2"
map-obj@^1.0.0, map-obj@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-1.0.1.tgz#d933ceb9205d82bdcf4886f6742bdc2b4dea146d"
meow@^3.3.0:
version "3.7.0"
resolved "https://registry.yarnpkg.com/meow/-/meow-3.7.0.tgz#72cb668b425228290abbfa856892587308a801fb"
dependencies:
camelcase-keys "^2.0.0"
decamelize "^1.1.2"
loud-rejection "^1.0.0"
map-obj "^1.0.1"
minimist "^1.1.3"
normalize-package-data "^2.3.4"
object-assign "^4.0.1"
read-pkg-up "^1.0.1"
redent "^1.0.0"
trim-newlines "^1.0.0"
minimatch@^3.0.4:
version "3.0.4"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
dependencies:
brace-expansion "^1.1.7"
minimist@^1.1.3, minimist@^1.2.5:
version "1.2.5"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602"
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"
dependencies:
hosted-git-info "^2.1.4"
resolve "^1.10.0"
semver "2 || 3 || 4 || 5"
validate-npm-package-license "^3.0.1"
normalize-path@^3.0.0, normalize-path@~3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65"
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"
dependencies:
wrappy "1"
parse-json@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-2.2.0.tgz#f480f40434ef80741f8469099f8dea18f55a4dc9"
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"
dependencies:
pinkie-promise "^2.0.0"
path-is-absolute@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
path-parse@^1.0.6:
version "1.0.6"
resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c"
path-type@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/path-type/-/path-type-1.1.0.tgz#59c44f7ee491da704da415da5a4070ba4f8fe441"
dependencies:
graceful-fs "^4.1.2"
pify "^2.0.0"
pinkie-promise "^2.0.0"
picomatch@^2.0.4, picomatch@^2.2.1:
version "2.2.2"
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad"
pify@^2.0.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c"
pinkie-promise@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa"
dependencies:
pinkie "^2.0.0"
pinkie@^2.0.0:
version "2.0.4"
resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870"
read-pkg-up@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-1.0.1.tgz#9d63c13276c065918d57f002a57f40a1b643fb02"
dependencies:
find-up "^1.0.0"
read-pkg "^1.0.0"
read-pkg@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-1.1.0.tgz#f5ffaa5ecd29cb31c0474bca7d756b6bb29e3f28"
dependencies:
load-json-file "^1.0.0"
normalize-package-data "^2.3.2"
path-type "^1.0.0"
readdirp@~3.4.0:
version "3.4.0"
resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.4.0.tgz#9fdccdf9e9155805449221ac645e8303ab5b9ada"
dependencies:
picomatch "^2.2.1"
redent@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/redent/-/redent-1.0.0.tgz#cf916ab1fd5f1f16dfb20822dd6ec7f730c2afde"
dependencies:
indent-string "^2.1.0"
strip-indent "^1.0.1"
repeating@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/repeating/-/repeating-2.0.1.tgz#5214c53a926d3552707527fbab415dbc08d06dda"
dependencies:
is-finite "^1.0.0"
resolve@^1.0.0, resolve@^1.10.0:
version "1.17.0"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.17.0.tgz#b25941b54968231cc2d1bb76a79cb7f2c0bf8444"
dependencies:
path-parse "^1.0.6"
rimraf@^2.6.1:
version "2.7.1"
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec"
dependencies:
glob "^7.1.3"
"semver@2 || 3 || 4 || 5":
version "5.7.1"
resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
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"
dependencies:
buffer-from "^1.0.0"
source-map "^0.6.0"
source-map@^0.6.0:
version "0.6.1"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
spdx-correct@^3.0.0:
version "3.1.1"
resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.1.tgz#dece81ac9c1e6713e5f7d1b6f17d468fa53d89a9"
dependencies:
spdx-expression-parse "^3.0.0"
spdx-license-ids "^3.0.0"
spdx-exceptions@^2.1.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz#3f28ce1a77a00372683eade4a433183527a2163d"
spdx-expression-parse@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz#cf70f50482eefdc98e3ce0a6833e4a53ceeba679"
dependencies:
spdx-exceptions "^2.1.0"
spdx-license-ids "^3.0.0"
spdx-license-ids@^3.0.0:
version "3.0.6"
resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.6.tgz#c80757383c28abf7296744998cbc106ae8b854ce"
strip-bom@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-2.0.0.tgz#6219a85616520491f35788bdbf1447a99c7e6b0e"
dependencies:
is-utf8 "^0.2.0"
strip-bom@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3"
strip-indent@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-1.0.1.tgz#0c7962a6adefa7bbd4ac366460a638552ae1a0a2"
dependencies:
get-stdin "^4.0.1"
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"
dependencies:
is-number "^7.0.0"
tree-kill@^1.2.2:
version "1.2.2"
resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.2.tgz#4ca09a9092c88b73a7cdc5e8a01b507b0790a0cc"
trim-newlines@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-1.0.0.tgz#5887966bb582a4503a41eb524f7d35011815a613"
ts-node-dev@^1.0.0-pre.62:
version "1.0.0-pre.62"
resolved "https://registry.yarnpkg.com/ts-node-dev/-/ts-node-dev-1.0.0-pre.62.tgz#835644c43669b659a880379b9d06df86cef665ad"
dependencies:
chokidar "^3.4.0"
dateformat "~1.0.4-1.2.3"
dynamic-dedupe "^0.3.0"
minimist "^1.2.5"
mkdirp "^1.0.4"
resolve "^1.0.0"
rimraf "^2.6.1"
source-map-support "^0.5.12"
tree-kill "^1.2.2"
ts-node "^8.10.2"
tsconfig "^7.0.0"
ts-node@^8.10.2:
version "8.10.2"
resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-8.10.2.tgz#eee03764633b1234ddd37f8db9ec10b75ec7fb8d"
dependencies:
arg "^4.1.0"
diff "^4.0.1"
make-error "^1.1.1"
source-map-support "^0.5.17"
yn "3.1.1"
tsconfig@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/tsconfig/-/tsconfig-7.0.0.tgz#84538875a4dc216e5c4a5432b3a4dec3d54e91b7"
dependencies:
"@types/strip-bom" "^3.0.0"
"@types/strip-json-comments" "0.0.30"
strip-bom "^3.0.0"
strip-json-comments "^2.0.0"
typescript@^4.0.2:
version "4.0.2"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.0.2.tgz#7ea7c88777c723c681e33bf7988be5d008d05ac2"
validate-npm-package-license@^3.0.1:
version "3.0.4"
resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a"
dependencies:
spdx-correct "^3.0.0"
spdx-expression-parse "^3.0.0"
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"
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

@ -73,6 +73,7 @@ services:
STARTUP_COMMAND_1: yarn install STARTUP_COMMAND_1: yarn install
SECRET_KEY: yourSecretKey SECRET_KEY: yourSecretKey
ALLOW_ARTILLERY: "true" ALLOW_ARTILLERY: "true"
ADMIN_API_TOKEN: "$ADMIN_API_TOKEN"
volumes: volumes:
- ./back:/usr/src/app - ./back:/usr/src/app
labels: labels:
@ -101,3 +102,13 @@ services:
- "traefik.http.routers.website-ssl.entryPoints=websecure" - "traefik.http.routers.website-ssl.entryPoints=websecure"
- "traefik.http.routers.website-ssl.tls=true" - "traefik.http.routers.website-ssl.tls=true"
- "traefik.http.routers.website-ssl.service=website" - "traefik.http.routers.website-ssl.service=website"
messages:
image: thecodingmachine/workadventure-back-base:latest
environment:
STARTUP_COMMAND_1: yarn install
STARTUP_COMMAND_2: yarn run proto:watch
volumes:
- ./messages:/usr/src/app
- ./back:/usr/src/back
- ./front:/usr/src/front

View File

@ -1,7 +1,13 @@
FROM thecodingmachine/workadventure-back-base:latest as builder
WORKDIR /var/www/messages
COPY --chown=docker:docker messages .
RUN yarn install && yarn proto
# we are rebuilding on each deploy to cope with the API_URL environment URL # we are rebuilding on each deploy to cope with the API_URL environment URL
FROM thecodingmachine/nodejs:14-apache FROM thecodingmachine/nodejs:14-apache
COPY --chown=docker:docker . . COPY --chown=docker:docker front .
COPY --from=builder --chown=docker:docker /var/www/messages/generated /var/www/html/src/Messages/generated
RUN yarn install RUN yarn install
ENV NODE_ENV=production ENV NODE_ENV=production

View File

@ -4,6 +4,7 @@
"main": "index.js", "main": "index.js",
"license": "SEE LICENSE IN LICENSE.txt", "license": "SEE LICENSE IN LICENSE.txt",
"devDependencies": { "devDependencies": {
"@types/google-protobuf": "^3.7.3",
"@types/jasmine": "^3.5.10", "@types/jasmine": "^3.5.10",
"@typescript-eslint/eslint-plugin": "^2.26.0", "@typescript-eslint/eslint-plugin": "^2.26.0",
"@typescript-eslint/parser": "^2.26.0", "@typescript-eslint/parser": "^2.26.0",
@ -23,6 +24,7 @@
"@types/simple-peer": "^9.6.0", "@types/simple-peer": "^9.6.0",
"@types/socket.io-client": "^1.4.32", "@types/socket.io-client": "^1.4.32",
"generic-type-guard": "^3.2.0", "generic-type-guard": "^3.2.0",
"google-protobuf": "^3.13.0",
"phaser": "^3.22.0", "phaser": "^3.22.0",
"queue-typescript": "^1.0.1", "queue-typescript": "^1.0.1",
"simple-peer": "^9.6.2", "simple-peer": "^9.6.2",

View File

@ -1,13 +1,21 @@
import Axios from "axios"; import Axios from "axios";
import {API_URL} from "./Enum/EnvironmentVariable"; import {API_URL} from "./Enum/EnvironmentVariable";
import {MessageUI} from "./Logger/MessageUI"; import {MessageUI} from "./Logger/MessageUI";
import {SetPlayerDetailsMessage} from "./Messages/SetPlayerDetailsMessage"; import {
BatchMessage, GroupDeleteMessage, GroupUpdateMessage, ItemEventMessage,
PositionMessage,
SetPlayerDetailsMessage, UserJoinedMessage, UserLeftMessage, UserMovedMessage,
UserMovesMessage,
ViewportMessage
} from "./Messages/generated/messages_pb"
const SocketIo = require('socket.io-client'); const SocketIo = require('socket.io-client');
import Socket = SocketIOClient.Socket; import Socket = SocketIOClient.Socket;
import {PlayerAnimationNames} from "./Phaser/Player/Animation"; import {PlayerAnimationNames} from "./Phaser/Player/Animation";
import {UserSimplePeerInterface} from "./WebRtc/SimplePeer"; import {UserSimplePeerInterface} from "./WebRtc/SimplePeer";
import {SignalData} from "simple-peer"; import {SignalData} from "simple-peer";
import Direction = PositionMessage.Direction;
import {ProtobufClientUtils} from "./Network/ProtobufClientUtils";
enum EventMessage{ enum EventMessage{
WEBRTC_SIGNAL = "webrtc-signal", WEBRTC_SIGNAL = "webrtc-signal",
@ -46,19 +54,19 @@ export class Point implements PointInterface{
} }
export interface MessageUserPositionInterface { export interface MessageUserPositionInterface {
userId: string; userId: number;
name: string; name: string;
characterLayers: string[]; characterLayers: string[];
position: PointInterface; position: PointInterface;
} }
export interface MessageUserMovedInterface { export interface MessageUserMovedInterface {
userId: string; userId: number;
position: PointInterface; position: PointInterface;
} }
export interface MessageUserJoined { export interface MessageUserJoined {
userId: string; userId: number;
name: string; name: string;
characterLayers: string[]; characterLayers: string[];
position: PointInterface position: PointInterface
@ -71,7 +79,7 @@ export interface PositionInterface {
export interface GroupCreatedUpdatedMessageInterface { export interface GroupCreatedUpdatedMessageInterface {
position: PositionInterface, position: PositionInterface,
groupId: string groupId: number
} }
export interface WebRtcStartMessageInterface { export interface WebRtcStartMessageInterface {
@ -80,16 +88,16 @@ export interface WebRtcStartMessageInterface {
} }
export interface WebRtcDisconnectMessageInterface { export interface WebRtcDisconnectMessageInterface {
userId: string userId: number
} }
export interface WebRtcSignalSentMessageInterface { export interface WebRtcSignalSentMessageInterface {
receiverId: string, receiverId: number,
signal: SignalData signal: SignalData
} }
export interface WebRtcSignalReceivedMessageInterface { export interface WebRtcSignalReceivedMessageInterface {
userId: string, userId: number,
signal: SignalData signal: SignalData
} }
@ -105,11 +113,6 @@ export interface ViewportInterface {
bottom: number, bottom: number,
} }
export interface UserMovesInterface {
position: PositionInterface,
viewport: ViewportInterface,
}
export interface BatchedMessageInterface { export interface BatchedMessageInterface {
event: string, event: string,
payload: unknown payload: unknown
@ -130,7 +133,8 @@ export interface RoomJoinedMessageInterface {
export class Connection implements Connection { export class Connection implements Connection {
private readonly socket: Socket; private readonly socket: Socket;
private userId: string|null = null; private userId: number|null = null;
private batchCallbacks: Map<string, Function[]> = new Map<string, Function[]>();
private constructor(token: string) { private constructor(token: string) {
@ -148,11 +152,40 @@ export class Connection implements Connection {
/** /**
* Messages inside batched messages are extracted and sent to listeners directly. * Messages inside batched messages are extracted and sent to listeners directly.
*/ */
this.socket.on(EventMessage.BATCH, (batchedMessages: BatchedMessageInterface[]) => { this.socket.on(EventMessage.BATCH, (batchedMessagesBinary: ArrayBuffer) => {
for (const message of batchedMessages) { const batchMessage = BatchMessage.deserializeBinary(new Uint8Array(batchedMessagesBinary));
const listeners = this.socket.listeners(message.event);
for (const message of batchMessage.getPayloadList()) {
let event: string;
let payload;
if (message.hasUsermovedmessage()) {
event = EventMessage.USER_MOVED;
payload = message.getUsermovedmessage();
} else if (message.hasGroupupdatemessage()) {
event = EventMessage.GROUP_CREATE_UPDATE;
payload = message.getGroupupdatemessage();
} else if (message.hasGroupdeletemessage()) {
event = EventMessage.GROUP_DELETE;
payload = message.getGroupdeletemessage();
} else if (message.hasUserjoinedmessage()) {
event = EventMessage.JOIN_ROOM;
payload = message.getUserjoinedmessage();
} else if (message.hasUserleftmessage()) {
event = EventMessage.USER_LEFT;
payload = message.getUserleftmessage();
} else if (message.hasItemeventmessage()) {
event = EventMessage.ITEM_EVENT;
payload = message.getItemeventmessage();
} else {
throw new Error('Unexpected batch message type');
}
const listeners = this.batchCallbacks.get(event);
if (listeners === undefined) {
continue;
}
for (const listener of listeners) { for (const listener of listeners) {
listener(message.payload); listener(payload);
} }
} }
}) })
@ -170,10 +203,10 @@ export class Connection implements Connection {
reject(error); reject(error);
}); });
connection.socket.emit(EventMessage.SET_PLAYER_DETAILS, { const message = new SetPlayerDetailsMessage();
name: name, message.setName(name);
characterLayers: characterLayersSelected message.setCharacterlayersList(characterLayersSelected);
} as SetPlayerDetailsMessage, (id: string) => { connection.socket.emit(EventMessage.SET_PLAYER_DETAILS, message.serializeBinary().buffer, (id: number) => {
connection.userId = id; connection.userId = id;
}); });
@ -213,8 +246,42 @@ export class Connection implements Connection {
if(!this.socket){ if(!this.socket){
return; return;
} }
const point = new Point(x, y, direction, moving); const positionMessage = new PositionMessage();
this.socket.emit(EventMessage.USER_POSITION, { position: point, viewport } as UserMovesInterface); positionMessage.setX(Math.floor(x));
positionMessage.setY(Math.floor(y));
let directionEnum: PositionMessage.DirectionMap[keyof PositionMessage.DirectionMap];
switch (direction) {
case 'up':
directionEnum = Direction.UP;
break;
case 'down':
directionEnum = Direction.DOWN;
break;
case 'left':
directionEnum = Direction.LEFT;
break;
case 'right':
directionEnum = Direction.RIGHT;
break;
default:
throw new Error("Unexpected direction");
}
positionMessage.setDirection(directionEnum);
positionMessage.setMoving(moving);
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));
const userMovesMessage = new UserMovesMessage();
userMovesMessage.setPosition(positionMessage);
userMovesMessage.setViewport(viewportMessage);
//console.log('Sending position ', positionMessage.getX(), positionMessage.getY());
this.socket.emit(EventMessage.USER_POSITION, userMovesMessage.serializeBinary().buffer);
} }
public setSilent(silent: boolean): void { public setSilent(silent: boolean): void {
@ -222,41 +289,89 @@ export class Connection implements Connection {
} }
public setViewport(viewport: ViewportInterface): void { public setViewport(viewport: ViewportInterface): void {
this.socket.emit(EventMessage.SET_VIEWPORT, viewport); const viewportMessage = new ViewportMessage();
viewportMessage.setTop(Math.round(viewport.top));
viewportMessage.setBottom(Math.round(viewport.bottom));
viewportMessage.setLeft(Math.round(viewport.left));
viewportMessage.setRight(Math.round(viewport.right));
this.socket.emit(EventMessage.SET_VIEWPORT, viewportMessage.serializeBinary().buffer);
} }
public onUserJoins(callback: (message: MessageUserJoined) => void): void { public onUserJoins(callback: (message: MessageUserJoined) => void): void {
this.socket.on(EventMessage.JOIN_ROOM, callback); this.onBatchMessage(EventMessage.JOIN_ROOM, (message: UserJoinedMessage) => {
const position = message.getPosition();
if (position === undefined) {
throw new Error('Invalid JOIN_ROOM message');
}
const messageUserJoined: MessageUserJoined = {
userId: message.getUserid(),
name: message.getName(),
characterLayers: message.getCharacterlayersList(),
position: ProtobufClientUtils.toPointInterface(position)
}
callback(messageUserJoined);
});
} }
public onUserMoved(callback: (message: MessageUserMovedInterface) => void): void { public onUserMoved(callback: (message: UserMovedMessage) => void): void {
this.socket.on(EventMessage.USER_MOVED, callback); this.onBatchMessage(EventMessage.USER_MOVED, callback);
//this.socket.on(EventMessage.USER_MOVED, callback);
} }
public onUserLeft(callback: (userId: string) => void): void { /**
this.socket.on(EventMessage.USER_LEFT, callback); * Registers a listener on a message that is part of a batch
*/
private onBatchMessage(eventName: string, callback: Function): void {
let callbacks = this.batchCallbacks.get(eventName);
if (callbacks === undefined) {
callbacks = new Array<Function>();
this.batchCallbacks.set(eventName, callbacks);
}
callbacks.push(callback);
}
public onUserLeft(callback: (userId: number) => void): void {
this.onBatchMessage(EventMessage.USER_LEFT, (message: UserLeftMessage) => {
callback(message.getUserid());
});
} }
public onGroupUpdatedOrCreated(callback: (groupCreateUpdateMessage: GroupCreatedUpdatedMessageInterface) => void): void { public onGroupUpdatedOrCreated(callback: (groupCreateUpdateMessage: GroupCreatedUpdatedMessageInterface) => void): void {
this.socket.on(EventMessage.GROUP_CREATE_UPDATE, callback); this.onBatchMessage(EventMessage.GROUP_CREATE_UPDATE, (message: GroupUpdateMessage) => {
const position = message.getPosition();
if (position === undefined) {
throw new Error('Missing position in GROUP_CREATE_UPDATE');
}
const groupCreateUpdateMessage: GroupCreatedUpdatedMessageInterface = {
groupId: message.getGroupid(),
position: position.toObject()
}
//console.log('Group position: ', position.toObject());
callback(groupCreateUpdateMessage);
});
} }
public onGroupDeleted(callback: (groupId: string) => void): void { public onGroupDeleted(callback: (groupId: number) => void): void {
this.socket.on(EventMessage.GROUP_DELETE, callback) this.onBatchMessage(EventMessage.GROUP_DELETE, (message: GroupDeleteMessage) => {
callback(message.getGroupid());
});
} }
public onConnectError(callback: (error: object) => void): void { public onConnectError(callback: (error: object) => void): void {
this.socket.on(EventMessage.CONNECT_ERROR, callback) this.socket.on(EventMessage.CONNECT_ERROR, callback)
} }
public sendWebrtcSignal(signal: unknown, receiverId : string) { public sendWebrtcSignal(signal: unknown, receiverId: number) {
return this.socket.emit(EventMessage.WEBRTC_SIGNAL, { return this.socket.emit(EventMessage.WEBRTC_SIGNAL, {
receiverId: receiverId, receiverId: receiverId,
signal: signal signal: signal
} as WebRtcSignalSentMessageInterface); } as WebRtcSignalSentMessageInterface);
} }
public sendWebrtcScreenSharingSignal(signal: unknown, receiverId : string) { 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, receiverId: receiverId,
signal: signal signal: signal
@ -286,7 +401,7 @@ export class Connection implements Connection {
} }
public getUserId(): string|null { public getUserId(): number|null {
return this.userId; return this.userId;
} }
@ -294,16 +409,24 @@ export class Connection implements Connection {
this.socket.on(EventMessage.WEBRTC_DISCONNECT, callback); this.socket.on(EventMessage.WEBRTC_DISCONNECT, callback);
} }
emitActionableEvent(itemId: number, event: string, state: unknown, parameters: unknown) { emitActionableEvent(itemId: number, event: string, state: unknown, parameters: unknown): void {
return this.socket.emit(EventMessage.ITEM_EVENT, { const itemEventMessage = new ItemEventMessage();
itemId, itemEventMessage.setItemid(itemId);
event, itemEventMessage.setEvent(event);
state, itemEventMessage.setStatejson(JSON.stringify(state));
parameters itemEventMessage.setParametersjson(JSON.stringify(parameters));
});
this.socket.emit(EventMessage.ITEM_EVENT, itemEventMessage.serializeBinary().buffer);
} }
onActionableEvent(callback: (message: ItemEventMessageInterface) => void): void { onActionableEvent(callback: (message: ItemEventMessageInterface) => void): void {
this.socket.on(EventMessage.ITEM_EVENT, callback); this.onBatchMessage(EventMessage.ITEM_EVENT, (message: ItemEventMessage) => {
callback({
itemId: message.getItemid(),
event: message.getEvent(),
parameters: JSON.parse(message.getParametersjson()),
state: JSON.parse(message.getStatejson())
});
});
} }
} }

1
front/src/Messages/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/generated/

View File

@ -1,4 +0,0 @@
export interface SetPlayerDetailsMessage {
name: string,
characterLayers: string[]
}

View File

@ -0,0 +1,34 @@
import {PositionMessage} from "../Messages/generated/messages_pb";
import {PointInterface} from "../Connection";
import Direction = PositionMessage.Direction;
export class ProtobufClientUtils {
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(),
};
}
}

View File

@ -6,10 +6,10 @@ import {Character} from "../Entity/Character";
* Class representing the sprite of a remote player (a player that plays on another computer) * Class representing the sprite of a remote player (a player that plays on another computer)
*/ */
export class RemotePlayer extends Character { export class RemotePlayer extends Character {
userId: string; userId: number;
constructor( constructor(
userId: string, userId: number,
Scene: GameScene, Scene: GameScene,
x: number, x: number,
y: number, y: number,

View File

@ -1,7 +1,7 @@
import {PointInterface} from "../../Connection"; import {PointInterface} from "../../Connection";
export interface AddPlayerInterface { export interface AddPlayerInterface {
userId: string; userId: number;
name: string; name: string;
characterLayers: string[]; characterLayers: string[];
position: PointInterface; position: PointInterface;

View File

@ -40,6 +40,8 @@ import {FourOFourSceneName} from "../Reconnecting/FourOFourScene";
import {ItemFactoryInterface} from "../Items/ItemFactoryInterface"; import {ItemFactoryInterface} from "../Items/ItemFactoryInterface";
import {ActionableItem} from "../Items/ActionableItem"; import {ActionableItem} from "../Items/ActionableItem";
import {UserInputManager} from "../UserInput/UserInputManager"; import {UserInputManager} from "../UserInput/UserInputManager";
import {UserMovedMessage} from "../../Messages/generated/messages_pb";
import {ProtobufClientUtils} from "../../Network/ProtobufClientUtils";
export enum Textures { export enum Textures {
@ -63,7 +65,7 @@ interface AddPlayerEventInterface {
interface RemovePlayerEventInterface { interface RemovePlayerEventInterface {
type: 'RemovePlayerEvent' type: 'RemovePlayerEvent'
userId: string userId: number
} }
interface UserMovedEventInterface { interface UserMovedEventInterface {
@ -78,7 +80,7 @@ interface GroupCreatedUpdatedEventInterface {
interface DeleteGroupEventInterface { interface DeleteGroupEventInterface {
type: 'DeleteGroupEvent' type: 'DeleteGroupEvent'
groupId: string groupId: number
} }
export class GameScene extends Phaser.Scene implements CenterListener { export class GameScene extends Phaser.Scene implements CenterListener {
@ -86,12 +88,12 @@ export class GameScene extends Phaser.Scene implements CenterListener {
Terrains : Array<Phaser.Tilemaps.Tileset>; Terrains : Array<Phaser.Tilemaps.Tileset>;
CurrentPlayer!: CurrentGamerInterface; CurrentPlayer!: CurrentGamerInterface;
MapPlayers!: Phaser.Physics.Arcade.Group; MapPlayers!: Phaser.Physics.Arcade.Group;
MapPlayersByKey : Map<string, RemotePlayer> = new Map<string, RemotePlayer>(); MapPlayersByKey : Map<number, RemotePlayer> = new Map<number, RemotePlayer>();
Map!: Phaser.Tilemaps.Tilemap; Map!: Phaser.Tilemaps.Tilemap;
Layers!: Array<Phaser.Tilemaps.StaticTilemapLayer>; Layers!: Array<Phaser.Tilemaps.StaticTilemapLayer>;
Objects!: Array<Phaser.Physics.Arcade.Sprite>; Objects!: Array<Phaser.Physics.Arcade.Sprite>;
mapFile!: ITiledMap; mapFile!: ITiledMap;
groups: Map<string, Sprite>; groups: Map<number, Sprite>;
startX!: number; startX!: number;
startY!: number; startY!: number;
circleTexture!: CanvasTexture; circleTexture!: CanvasTexture;
@ -147,7 +149,7 @@ export class GameScene extends Phaser.Scene implements CenterListener {
this.GameManager = gameManager; this.GameManager = gameManager;
this.Terrains = []; this.Terrains = [];
this.groups = new Map<string, Sprite>(); this.groups = new Map<number, Sprite>();
this.instance = instance; this.instance = instance;
this.MapKey = MapKey; this.MapKey = MapKey;
@ -213,11 +215,22 @@ export class GameScene extends Phaser.Scene implements CenterListener {
this.addPlayer(userMessage); this.addPlayer(userMessage);
}); });
connection.onUserMoved((message: MessageUserMovedInterface) => { connection.onUserMoved((message: UserMovedMessage) => {
this.updatePlayerPosition(message); const position = message.getPosition();
if (position === undefined) {
throw new Error('Position missing from UserMovedMessage');
}
//console.log('Received position ', position.getX(), position.getY(), "from user", message.getUserid());
const messageUserMoved: MessageUserMovedInterface = {
userId: message.getUserid(),
position: ProtobufClientUtils.toPointInterface(position)
}
this.updatePlayerPosition(messageUserMoved);
}); });
connection.onUserLeft((userId: string) => { connection.onUserLeft((userId: number) => {
this.removePlayer(userId); this.removePlayer(userId);
}); });
@ -225,7 +238,7 @@ export class GameScene extends Phaser.Scene implements CenterListener {
this.shareGroupPosition(groupPositionMessage); this.shareGroupPosition(groupPositionMessage);
}) })
connection.onGroupDeleted((groupId: string) => { connection.onGroupDeleted((groupId: number) => {
try { try {
this.deleteGroup(groupId); this.deleteGroup(groupId);
} catch (e) { } catch (e) {
@ -271,7 +284,7 @@ export class GameScene extends Phaser.Scene implements CenterListener {
self.presentationModeSprite.setVisible(true); self.presentationModeSprite.setVisible(true);
self.chatModeSprite.setVisible(true); self.chatModeSprite.setVisible(true);
}, },
onDisconnect(userId: string) { onDisconnect(userId: number) {
if (self.simplePeer.getNbConnections() === 0) { if (self.simplePeer.getNbConnections() === 0) {
self.presentationModeSprite.setVisible(false); self.presentationModeSprite.setVisible(false);
self.chatModeSprite.setVisible(false); self.chatModeSprite.setVisible(false);
@ -918,7 +931,7 @@ export class GameScene extends Phaser.Scene implements CenterListener {
// Let's move all users // Let's move all users
const updatedPlayersPositions = this.playersPositionInterpolator.getUpdatedPositions(time); const updatedPlayersPositions = this.playersPositionInterpolator.getUpdatedPositions(time);
updatedPlayersPositions.forEach((moveEvent: HasMovedEvent, userId: string) => { updatedPlayersPositions.forEach((moveEvent: HasMovedEvent, userId: number) => {
const player : RemotePlayer | undefined = this.MapPlayersByKey.get(userId); const player : RemotePlayer | undefined = this.MapPlayersByKey.get(userId);
if (player === undefined) { if (player === undefined) {
throw new Error('Cannot find player with ID "' + userId +'"'); throw new Error('Cannot find player with ID "' + userId +'"');
@ -973,7 +986,7 @@ export class GameScene extends Phaser.Scene implements CenterListener {
player.destroy(); player.destroy();
this.MapPlayers.remove(player); this.MapPlayers.remove(player);
}); });
this.MapPlayersByKey = new Map<string, RemotePlayer>(); this.MapPlayersByKey = new Map<number, RemotePlayer>();
// load map // load map
usersPosition.forEach((userPosition : MessageUserPositionInterface) => { usersPosition.forEach((userPosition : MessageUserPositionInterface) => {
@ -1030,14 +1043,14 @@ export class GameScene extends Phaser.Scene implements CenterListener {
/** /**
* Called by the connexion when a player is removed from the map * Called by the connexion when a player is removed from the map
*/ */
public removePlayer(userId: string) { public removePlayer(userId: number) {
this.pendingEvents.enqueue({ this.pendingEvents.enqueue({
type: "RemovePlayerEvent", type: "RemovePlayerEvent",
userId userId
}); });
} }
private doRemovePlayer(userId: string) { private doRemovePlayer(userId: number) {
const player = this.MapPlayersByKey.get(userId); const player = this.MapPlayersByKey.get(userId);
if (player === undefined) { if (player === undefined) {
console.error('Cannot find user with id ', userId); console.error('Cannot find user with id ', userId);
@ -1067,6 +1080,7 @@ export class GameScene extends Phaser.Scene implements CenterListener {
// We do not update the player position directly (because it is sent only every 200ms). // We do not update the player position directly (because it is sent only every 200ms).
// Instead we use the PlayersPositionInterpolator that will do a smooth animation over the next 200ms. // Instead we use the PlayersPositionInterpolator that will do a smooth animation over the next 200ms.
const playerMovement = new PlayerMovement({ x: player.x, y: player.y }, this.currentTick, message.position, this.currentTick + POSITION_DELAY); const playerMovement = new PlayerMovement({ x: player.x, y: player.y }, this.currentTick, message.position, this.currentTick + POSITION_DELAY);
//console.log('Target position: ', player.x, player.y);
this.playersPositionInterpolator.updatePlayerPosition(player.userId, playerMovement); this.playersPositionInterpolator.updatePlayerPosition(player.userId, playerMovement);
} }
@ -1096,14 +1110,14 @@ export class GameScene extends Phaser.Scene implements CenterListener {
} }
} }
deleteGroup(groupId: string): void { deleteGroup(groupId: number): void {
this.pendingEvents.enqueue({ this.pendingEvents.enqueue({
type: "DeleteGroupEvent", type: "DeleteGroupEvent",
groupId groupId
}); });
} }
doDeleteGroup(groupId: string): void { doDeleteGroup(groupId: number): void {
const group = this.groups.get(groupId); const group = this.groups.get(groupId);
if(!group){ if(!group){
return; return;

View File

@ -20,12 +20,13 @@ export class PlayerMovement {
public getPosition(tick: number): HasMovedEvent { public getPosition(tick: number): HasMovedEvent {
// Special case: end position reached and end position is not moving // Special case: end position reached and end position is not moving
if (tick >= this.endTick && this.endPosition.moving === false) { if (tick >= this.endTick && this.endPosition.moving === false) {
//console.log('Movement finished ', this.endPosition)
return this.endPosition; return this.endPosition;
} }
const x = (this.endPosition.x - this.startPosition.x) * ((tick - this.startTick) / (this.endTick - this.startTick)) + this.startPosition.x; const x = (this.endPosition.x - this.startPosition.x) * ((tick - this.startTick) / (this.endTick - this.startTick)) + this.startPosition.x;
const y = (this.endPosition.y - this.startPosition.y) * ((tick - this.startTick) / (this.endTick - this.startTick)) + this.startPosition.y; const y = (this.endPosition.y - this.startPosition.y) * ((tick - this.startTick) / (this.endTick - this.startTick)) + this.startPosition.y;
//console.log('Computed position ', x, y)
return { return {
x, x,
y, y,

View File

@ -6,19 +6,19 @@ import {PlayerMovement} from "./PlayerMovement";
import {HasMovedEvent} from "./GameManager"; import {HasMovedEvent} from "./GameManager";
export class PlayersPositionInterpolator { export class PlayersPositionInterpolator {
playerMovements: Map<string, PlayerMovement> = new Map<string, PlayerMovement>(); playerMovements: Map<number, PlayerMovement> = new Map<number, PlayerMovement>();
updatePlayerPosition(userId: string, playerMovement: PlayerMovement) : void { updatePlayerPosition(userId: number, playerMovement: PlayerMovement) : void {
this.playerMovements.set(userId, playerMovement); this.playerMovements.set(userId, playerMovement);
} }
removePlayer(userId: string): void { removePlayer(userId: number): void {
this.playerMovements.delete(userId); this.playerMovements.delete(userId);
} }
getUpdatedPositions(tick: number) : Map<string, HasMovedEvent> { getUpdatedPositions(tick: number) : Map<number, HasMovedEvent> {
const positions = new Map<string, HasMovedEvent>(); const positions = new Map<number, HasMovedEvent>();
this.playerMovements.forEach((playerMovement: PlayerMovement, userId: string) => { this.playerMovements.forEach((playerMovement: PlayerMovement, userId: number) => {
if (playerMovement.isOutdated(tick)) { if (playerMovement.isOutdated(tick)) {
//console.log("outdated") //console.log("outdated")
this.playerMovements.delete(userId); this.playerMovements.delete(userId);

View File

@ -343,7 +343,7 @@ export class MediaManager {
* *
* @param userId * @param userId
*/ */
addActiveVideo(userId : string, userName: string = ""){ addActiveVideo(userId: string, userName: string = ""){
this.webrtcInAudio.play(); this.webrtcInAudio.play();
userName = userName.toUpperCase(); userName = userName.toUpperCase();
@ -368,7 +368,7 @@ export class MediaManager {
* *
* @param userId * @param userId
*/ */
addScreenSharingActiveVideo(userId : string, divImportance: DivImportance = DivImportance.Important){ addScreenSharingActiveVideo(userId: string, divImportance: DivImportance = DivImportance.Important){
//this.webrtcInAudio.play(); //this.webrtcInAudio.play();
userId = `screen-sharing-${userId}`; userId = `screen-sharing-${userId}`;
@ -387,7 +387,7 @@ export class MediaManager {
* *
* @param userId * @param userId
*/ */
disabledMicrophoneByUserId(userId: string){ disabledMicrophoneByUserId(userId: number){
const element = document.getElementById(`microphone-${userId}`); const element = document.getElementById(`microphone-${userId}`);
if(!element){ if(!element){
return; return;
@ -399,7 +399,7 @@ export class MediaManager {
* *
* @param userId * @param userId
*/ */
enabledMicrophoneByUserId(userId: string){ enabledMicrophoneByUserId(userId: number){
const element = document.getElementById(`microphone-${userId}`); const element = document.getElementById(`microphone-${userId}`);
if(!element){ if(!element){
return; return;
@ -411,7 +411,7 @@ export class MediaManager {
* *
* @param userId * @param userId
*/ */
disabledVideoByUserId(userId: string) { disabledVideoByUserId(userId: number) {
let element = document.getElementById(`${userId}`); let element = document.getElementById(`${userId}`);
if (element) { if (element) {
element.style.opacity = "0"; element.style.opacity = "0";
@ -426,7 +426,7 @@ export class MediaManager {
* *
* @param userId * @param userId
*/ */
enabledVideoByUserId(userId: string){ enabledVideoByUserId(userId: number){
let element = document.getElementById(`${userId}`); let element = document.getElementById(`${userId}`);
if(element){ if(element){
element.style.opacity = "1"; element.style.opacity = "1";
@ -442,7 +442,7 @@ export class MediaManager {
* @param userId * @param userId
* @param stream * @param stream
*/ */
addStreamRemoteVideo(userId : string, stream : MediaStream){ addStreamRemoteVideo(userId: string, stream : MediaStream){
const remoteVideo = this.remoteVideo.get(userId); const remoteVideo = this.remoteVideo.get(userId);
if (remoteVideo === undefined) { if (remoteVideo === undefined) {
console.error('Unable to find video for ', userId); console.error('Unable to find video for ', userId);
@ -450,7 +450,7 @@ export class MediaManager {
} }
remoteVideo.srcObject = stream; remoteVideo.srcObject = stream;
} }
addStreamRemoteScreenSharing(userId : string, stream : MediaStream){ addStreamRemoteScreenSharing(userId: string, stream : MediaStream){
// In the case of screen sharing (going both ways), we may need to create the HTML element if it does not exist yet // In the case of screen sharing (going both ways), we may need to create the HTML element if it does not exist yet
const remoteVideo = this.remoteVideo.get(`screen-sharing-${userId}`); const remoteVideo = this.remoteVideo.get(`screen-sharing-${userId}`);
if (remoteVideo === undefined) { if (remoteVideo === undefined) {
@ -464,15 +464,15 @@ export class MediaManager {
* *
* @param userId * @param userId
*/ */
removeActiveVideo(userId : string){ removeActiveVideo(userId: string){
layoutManager.remove(userId); layoutManager.remove(userId);
this.remoteVideo.delete(userId); this.remoteVideo.delete(userId);
} }
removeActiveScreenSharingVideo(userId : string) { removeActiveScreenSharingVideo(userId: string) {
this.removeActiveVideo(`screen-sharing-${userId}`) this.removeActiveVideo(`screen-sharing-${userId}`)
} }
isConnecting(userId : string): void { isConnecting(userId: string): void {
const connectingSpinnerDiv = this.getSpinner(userId); const connectingSpinnerDiv = this.getSpinner(userId);
if (connectingSpinnerDiv === null) { if (connectingSpinnerDiv === null) {
return; return;
@ -480,7 +480,7 @@ export class MediaManager {
connectingSpinnerDiv.style.display = 'block'; connectingSpinnerDiv.style.display = 'block';
} }
isConnected(userId : string): void { isConnected(userId: string): void {
const connectingSpinnerDiv = this.getSpinner(userId); const connectingSpinnerDiv = this.getSpinner(userId);
if (connectingSpinnerDiv === null) { if (connectingSpinnerDiv === null) {
return; return;
@ -488,7 +488,7 @@ export class MediaManager {
connectingSpinnerDiv.style.display = 'none'; connectingSpinnerDiv.style.display = 'none';
} }
isError(userId : string): void { isError(userId: string): void {
console.log("isError", `div-${userId}`); console.log("isError", `div-${userId}`);
const element = document.getElementById(`div-${userId}`); const element = document.getElementById(`div-${userId}`);
if(!element){ if(!element){
@ -500,12 +500,12 @@ export class MediaManager {
} }
errorDiv.style.display = 'block'; errorDiv.style.display = 'block';
} }
isErrorScreenSharing(userId : string): void { isErrorScreenSharing(userId: string): void {
this.isError(`screen-sharing-${userId}`); this.isError(`screen-sharing-${userId}`);
} }
private getSpinner(userId : string): HTMLDivElement|null { private getSpinner(userId: string): HTMLDivElement|null {
const element = document.getElementById(`div-${userId}`); const element = document.getElementById(`div-${userId}`);
if(!element){ if(!element){
return null; return null;

View File

@ -14,7 +14,7 @@ export class ScreenSharingPeer extends Peer {
*/ */
private isReceivingStream:boolean = false; private isReceivingStream:boolean = false;
constructor(private userId: string, initiator: boolean, private connection: Connection) { constructor(private userId: number, initiator: boolean, private connection: Connection) {
super({ super({
initiator: initiator ? initiator : false, initiator: initiator ? initiator : false,
reconnectTimer: 10000, reconnectTimer: 10000,
@ -52,7 +52,7 @@ export class ScreenSharingPeer extends Peer {
if (message.streamEnded !== true) { if (message.streamEnded !== true) {
console.error('Unexpected message on screen sharing peer connection'); console.error('Unexpected message on screen sharing peer connection');
} }
mediaManager.removeActiveScreenSharingVideo(this.userId); mediaManager.removeActiveScreenSharingVideo("" + this.userId);
}); });
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
@ -63,7 +63,7 @@ export class ScreenSharingPeer extends Peer {
this.on('connect', () => { this.on('connect', () => {
// FIXME: we need to put the loader on the screen sharing connection // FIXME: we need to put the loader on the screen sharing connection
mediaManager.isConnected(this.userId); mediaManager.isConnected("" + this.userId);
console.info(`connect => ${this.userId}`); console.info(`connect => ${this.userId}`);
}); });
@ -86,10 +86,10 @@ export class ScreenSharingPeer extends Peer {
//console.log(`ScreenSharingPeer::stream => ${this.userId}`, stream); //console.log(`ScreenSharingPeer::stream => ${this.userId}`, stream);
//console.log(`stream => ${this.userId} => `, stream); //console.log(`stream => ${this.userId} => `, stream);
if(!stream){ if(!stream){
mediaManager.removeActiveScreenSharingVideo(this.userId); mediaManager.removeActiveScreenSharingVideo("" + this.userId);
this.isReceivingStream = false; this.isReceivingStream = false;
} else { } else {
mediaManager.addStreamRemoteScreenSharing(this.userId, stream); mediaManager.addStreamRemoteScreenSharing("" + this.userId, stream);
this.isReceivingStream = true; this.isReceivingStream = true;
} }
} }
@ -100,7 +100,7 @@ export class ScreenSharingPeer extends Peer {
public destroy(error?: Error): void { public destroy(error?: Error): void {
try { try {
mediaManager.removeActiveScreenSharingVideo(this.userId); mediaManager.removeActiveScreenSharingVideo("" + this.userId);
// FIXME: I don't understand why "Closing connection with" message is displayed TWICE before "Nb users in peerConnectionArray" // FIXME: I don't understand why "Closing connection with" message is displayed TWICE before "Nb users in peerConnectionArray"
// I do understand the method closeConnection is called twice, but I don't understand how they manage to run in parallel. // I do understand the method closeConnection is called twice, but I don't understand how they manage to run in parallel.
//console.log('Closing connection with '+userId); //console.log('Closing connection with '+userId);

View File

@ -16,7 +16,7 @@ import {VideoPeer} from "./VideoPeer";
const Peer: SimplePeerNamespace.SimplePeer = require('simple-peer'); const Peer: SimplePeerNamespace.SimplePeer = require('simple-peer');
export interface UserSimplePeerInterface{ export interface UserSimplePeerInterface{
userId: string; userId: number;
name?: string; name?: string;
initiator?: boolean; initiator?: boolean;
} }
@ -24,7 +24,7 @@ export interface UserSimplePeerInterface{
export interface PeerConnectionListener { export interface PeerConnectionListener {
onConnect(user: UserSimplePeerInterface): void; onConnect(user: UserSimplePeerInterface): void;
onDisconnect(userId: string): void; onDisconnect(userId: number): void;
} }
/** /**
@ -35,8 +35,8 @@ export class SimplePeer {
private WebRtcRoomId: string; private WebRtcRoomId: string;
private Users: Array<UserSimplePeerInterface> = new Array<UserSimplePeerInterface>(); private Users: Array<UserSimplePeerInterface> = new Array<UserSimplePeerInterface>();
private PeerScreenSharingConnectionArray: Map<string, ScreenSharingPeer> = new Map<string, ScreenSharingPeer>(); private PeerScreenSharingConnectionArray: Map<number, ScreenSharingPeer> = new Map<number, ScreenSharingPeer>();
private PeerConnectionArray: Map<string, VideoPeer> = new Map<string, VideoPeer>(); private PeerConnectionArray: Map<number, VideoPeer> = new Map<number, VideoPeer>();
private readonly sendLocalVideoStreamCallback: UpdatedLocalStreamCallback; private readonly sendLocalVideoStreamCallback: UpdatedLocalStreamCallback;
private readonly sendLocalScreenSharingStreamCallback: StartScreenSharingCallback; private readonly sendLocalScreenSharingStreamCallback: StartScreenSharingCallback;
private readonly stopLocalScreenSharingStreamCallback: StopScreenSharingCallback; private readonly stopLocalScreenSharingStreamCallback: StopScreenSharingCallback;
@ -140,8 +140,8 @@ export class SimplePeer {
} }
} }
mediaManager.removeActiveVideo(user.userId); mediaManager.removeActiveVideo("" + user.userId);
mediaManager.addActiveVideo(user.userId, name); mediaManager.addActiveVideo("" + user.userId, name);
const peer = new VideoPeer(user.userId, user.initiator ? user.initiator : false, this.Connection); const peer = new VideoPeer(user.userId, user.initiator ? user.initiator : false, this.Connection);
// When a connection is established to a video stream, and if a screen sharing is taking place, // When a connection is established to a video stream, and if a screen sharing is taking place,
@ -171,8 +171,8 @@ export class SimplePeer {
// We should display the screen sharing ONLY if we are not initiator // We should display the screen sharing ONLY if we are not initiator
if (!user.initiator) { if (!user.initiator) {
mediaManager.removeActiveScreenSharingVideo(user.userId); mediaManager.removeActiveScreenSharingVideo("" + user.userId);
mediaManager.addScreenSharingActiveVideo(user.userId); mediaManager.addScreenSharingActiveVideo("" + user.userId);
} }
const peer = new ScreenSharingPeer(user.userId, user.initiator ? user.initiator : false, this.Connection); const peer = new ScreenSharingPeer(user.userId, user.initiator ? user.initiator : false, this.Connection);
@ -189,7 +189,7 @@ export class SimplePeer {
* *
* @param userId * @param userId
*/ */
private closeConnection(userId : string) { private closeConnection(userId : number) {
try { try {
//mediaManager.removeActiveVideo(userId); //mediaManager.removeActiveVideo(userId);
const peer = this.PeerConnectionArray.get(userId); const peer = this.PeerConnectionArray.get(userId);
@ -217,9 +217,9 @@ export class SimplePeer {
* *
* @param userId * @param userId
*/ */
private closeScreenSharingConnection(userId : string) { private closeScreenSharingConnection(userId : number) {
try { try {
mediaManager.removeActiveScreenSharingVideo(userId); mediaManager.removeActiveScreenSharingVideo("" + userId);
const peer = this.PeerScreenSharingConnectionArray.get(userId); const peer = this.PeerScreenSharingConnectionArray.get(userId);
if (peer === undefined) { if (peer === undefined) {
console.warn("Tried to close connection for user "+userId+" but could not find user") console.warn("Tried to close connection for user "+userId+" but could not find user")
@ -293,7 +293,7 @@ export class SimplePeer {
* *
* @param userId * @param userId
*/ */
private pushVideoToRemoteUser(userId : string) { private pushVideoToRemoteUser(userId : number) {
try { try {
const PeerConnection = this.PeerConnectionArray.get(userId); const PeerConnection = this.PeerConnectionArray.get(userId);
if (!PeerConnection) { if (!PeerConnection) {
@ -314,7 +314,7 @@ export class SimplePeer {
} }
} }
private pushScreenSharingToRemoteUser(userId : string) { private pushScreenSharingToRemoteUser(userId : number) {
const PeerConnection = this.PeerScreenSharingConnectionArray.get(userId); const PeerConnection = this.PeerScreenSharingConnectionArray.get(userId);
if (!PeerConnection) { if (!PeerConnection) {
throw new Error('While pushing screen sharing, cannot find user with ID ' + userId); throw new Error('While pushing screen sharing, cannot find user with ID ' + userId);
@ -359,7 +359,7 @@ export class SimplePeer {
} }
} }
private sendLocalScreenSharingStreamToUser(userId: string): void { private sendLocalScreenSharingStreamToUser(userId: number): void {
// If a connection already exists with user (because it is already sharing a screen with us... let's use this connection) // If a connection already exists with user (because it is already sharing a screen with us... let's use this connection)
if (this.PeerScreenSharingConnectionArray.has(userId)) { if (this.PeerScreenSharingConnectionArray.has(userId)) {
this.pushScreenSharingToRemoteUser(userId); this.pushScreenSharingToRemoteUser(userId);
@ -376,7 +376,7 @@ export class SimplePeer {
} }
} }
private stopLocalScreenSharingStreamToUser(userId: string, stream: MediaStream): void { private stopLocalScreenSharingStreamToUser(userId: number, stream: MediaStream): void {
const PeerConnectionScreenSharing = this.PeerScreenSharingConnectionArray.get(userId); const PeerConnectionScreenSharing = this.PeerScreenSharingConnectionArray.get(userId);
if (!PeerConnectionScreenSharing) { if (!PeerConnectionScreenSharing) {
throw new Error('Weird, screen sharing connection to user ' + userId + 'not found') throw new Error('Weird, screen sharing connection to user ' + userId + 'not found')

View File

@ -9,7 +9,7 @@ const Peer: SimplePeerNamespace.SimplePeer = require('simple-peer');
* A peer connection used to transmit video / audio signals between 2 peers. * A peer connection used to transmit video / audio signals between 2 peers.
*/ */
export class VideoPeer extends Peer { export class VideoPeer extends Peer {
constructor(private userId: string, initiator: boolean, private connection: Connection) { constructor(private userId: number, initiator: boolean, private connection: Connection) {
super({ super({
initiator: initiator ? initiator : false, initiator: initiator ? initiator : false,
reconnectTimer: 10000, reconnectTimer: 10000,
@ -63,11 +63,11 @@ export class VideoPeer extends Peer {
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
this.on('error', (err: any) => { this.on('error', (err: any) => {
console.error(`error => ${this.userId} => ${err.code}`, err); console.error(`error => ${this.userId} => ${err.code}`, err);
mediaManager.isError(userId); mediaManager.isError("" + userId);
}); });
this.on('connect', () => { this.on('connect', () => {
mediaManager.isConnected(this.userId); mediaManager.isConnected("" + this.userId);
console.info(`connect => ${this.userId}`); console.info(`connect => ${this.userId}`);
}); });
@ -108,7 +108,7 @@ export class VideoPeer extends Peer {
mediaManager.disabledVideoByUserId(this.userId); mediaManager.disabledVideoByUserId(this.userId);
mediaManager.disabledMicrophoneByUserId(this.userId); mediaManager.disabledMicrophoneByUserId(this.userId);
} else { } else {
mediaManager.addStreamRemoteVideo(this.userId, stream); mediaManager.addStreamRemoteVideo("" + this.userId, stream);
} }
} }
@ -117,7 +117,7 @@ export class VideoPeer extends Peer {
*/ */
public destroy(error?: Error): void { public destroy(error?: Error): void {
try { try {
mediaManager.removeActiveVideo(this.userId); mediaManager.removeActiveVideo("" + this.userId);
// FIXME: I don't understand why "Closing connection with" message is displayed TWICE before "Nb users in peerConnectionArray" // FIXME: I don't understand why "Closing connection with" message is displayed TWICE before "Nb users in peerConnectionArray"
// I do understand the method closeConnection is called twice, but I don't understand how they manage to run in parallel. // I do understand the method closeConnection is called twice, but I don't understand how they manage to run in parallel.
//console.log('Closing connection with '+userId); //console.log('Closing connection with '+userId);

View File

@ -66,6 +66,11 @@
"@types/minimatch" "*" "@types/minimatch" "*"
"@types/node" "*" "@types/node" "*"
"@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"
integrity sha512-FRwj40euE2bYkG+0X5w2nEA8yAzgJRcEa7RBd0Gsdkb9/tPM2pctVVAvnOUTbcXo2VmIHPo0Ae94Gl9vRHfKzg==
"@types/html-minifier-terser@^5.0.0": "@types/html-minifier-terser@^5.0.0":
version "5.1.0" version "5.1.0"
resolved "https://registry.yarnpkg.com/@types/html-minifier-terser/-/html-minifier-terser-5.1.0.tgz#551a4589b6ee2cc9c1dff08056128aec29b94880" resolved "https://registry.yarnpkg.com/@types/html-minifier-terser/-/html-minifier-terser-5.1.0.tgz#551a4589b6ee2cc9c1dff08056128aec29b94880"
@ -2237,6 +2242,11 @@ globby@^6.1.0:
pify "^2.0.0" pify "^2.0.0"
pinkie-promise "^2.0.0" pinkie-promise "^2.0.0"
google-protobuf@^3.13.0:
version "3.13.0"
resolved "https://registry.yarnpkg.com/google-protobuf/-/google-protobuf-3.13.0.tgz#909c5983d75dd6101ed57c79e0528d000cdc3251"
integrity sha512-ZIf3qfLFayVrPvAjeKKxO5FRF1/NwRxt6Dko+fWEMuHwHbZx8/fcaAao9b0wCM6kr8qeg2te8XTpyuvKuD9aKw==
graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2: graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2:
version "4.2.4" version "4.2.4"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb"

1
messages/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/node_modules/

2
messages/generated/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
*
!.gitignore

92
messages/messages.proto Normal file
View File

@ -0,0 +1,92 @@
syntax = "proto3";
/*********** PARTIAL MESSAGES **************/
message PositionMessage {
int32 x = 1;
int32 y = 2;
enum Direction {
UP = 0;
RIGHT = 1;
DOWN = 2;
LEFT = 3;
}
Direction direction = 3;
bool moving = 4;
}
message PointMessage {
int32 x = 1;
int32 y = 2;
}
message ViewportMessage {
int32 left = 1;
int32 top = 2;
int32 right = 3;
int32 bottom = 4;
}
/*********** CLIENT TO SERVER MESSAGES *************/
message SetPlayerDetailsMessage {
string name = 1;
repeated string characterLayers = 2;
}
message UserMovesMessage {
PositionMessage position = 1;
ViewportMessage viewport = 2;
}
/************ BI-DIRECTIONAL MESSAGES **************/
message ItemEventMessage {
int32 itemId = 1;
string event = 2;
string stateJson = 3;
string parametersJson = 4;
}
/*********** SERVER TO CLIENT MESSAGES *************/
message UserMovedMessage {
int32 userId = 1;
PositionMessage position = 2;
}
message SubMessage {
oneof message {
UserMovedMessage userMovedMessage = 1;
GroupUpdateMessage groupUpdateMessage = 2;
GroupDeleteMessage groupDeleteMessage = 3;
UserJoinedMessage userJoinedMessage = 4;
UserLeftMessage userLeftMessage = 5;
ItemEventMessage itemEventMessage = 6;
}
}
message BatchMessage {
string event = 1;
repeated SubMessage payload = 2;
}
message GroupUpdateMessage {
int32 groupId = 1;
PointMessage position = 2;
}
message GroupDeleteMessage {
int32 groupId = 1;
}
message UserJoinedMessage {
int32 userId = 1;
string name = 2;
repeated string characterLayers = 3;
PositionMessage position = 4;
}
message UserLeftMessage {
int32 userId = 1;
}

46
messages/package.json Normal file
View File

@ -0,0 +1,46 @@
{
"name": "workadventure-messages",
"version": "1.0.0",
"description": "",
"main": "generated/src/proto/messages_pb.js",
"scripts": {
"proto": "protoc --plugin=\"protoc-gen-ts=./node_modules/.bin/protoc-gen-ts\" --js_out=\"import_style=commonjs,binary:generated\" --ts_out=\"generated\" messages.proto",
"copy-to-back": "rm -rf ../back/src/Messages/generated && cp -rf generated/ ../back/src/Messages/generated",
"copy-to-front": "rm -rf ../front/src/Messages/generated && cp -rf generated/ ../front/src/Messages/generated",
"proto-all": "yarn run proto && yarn run copy-to-back && yarn run copy-to-front",
"proto:watch": "yarn run proto-all; inotifywait -q -m -e close_write messages.proto | while read -r filename event; do yarn run proto-all; done"
},
"repository": {
"type": "git",
"url": "git+https://github.com/thecodingmachine/workadventure.git"
},
"contributors": [
{
"name": "Grégoire Parant",
"email": "g.parant@thecodingmachine.com"
},
{
"name": "David Négrier",
"email": "d.negrier@thecodingmachine.com"
},
{
"name": "Arthmaël Poly",
"email": "a.poly@thecodingmachine.com"
}
],
"license": "SEE LICENSE IN LICENSE.txt",
"bugs": {
"url": "https://github.com/thecodingmachine/workadventure/issues"
},
"homepage": "https://github.com/thecodingmachine/workadventure#readme",
"dependencies": {
"google-protobuf": "^3.13.0",
"typescript": "^3.8.3"
},
"devDependencies": {
"ts-node-dev": "^1.0.0-pre.44",
"@types/google-protobuf": "^3.7.3",
"concurrently": "^5.3.0",
"ts-protoc-gen": "^0.13.0"
}
}

889
messages/yarn.lock Normal file
View File

@ -0,0 +1,889 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
"@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"
integrity sha512-FRwj40euE2bYkG+0X5w2nEA8yAzgJRcEa7RBd0Gsdkb9/tPM2pctVVAvnOUTbcXo2VmIHPo0Ae94Gl9vRHfKzg==
"@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"
integrity sha1-FKjsOVbC6B7bdSB5CuzyHCkK69I=
"@types/strip-json-comments@0.0.30":
version "0.0.30"
resolved "https://registry.yarnpkg.com/@types/strip-json-comments/-/strip-json-comments-0.0.30.tgz#9aa30c04db212a9a0649d6ae6fd50accc40748a1"
integrity sha512-7NQmHra/JILCd1QqpSzl8+mJRc8ZHz3uDm8YV1Ks9IhK0epEiTw8aIErbvH9PI+6XbqhyIQy3462nEsn7UVzjQ==
ansi-regex@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997"
integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==
ansi-styles@^3.2.0, ansi-styles@^3.2.1:
version "3.2.1"
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d"
integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==
dependencies:
color-convert "^1.9.0"
anymatch@~3.1.1:
version "3.1.1"
resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.1.tgz#c55ecf02185e2469259399310c173ce31233b142"
integrity sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==
dependencies:
normalize-path "^3.0.0"
picomatch "^2.0.4"
arg@^4.1.0:
version "4.1.3"
resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089"
integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==
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"
integrity sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=
balanced-match@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c=
binary-extensions@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.1.0.tgz#30fa40c9e7fe07dbc895678cd287024dea241dd9"
integrity sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ==
brace-expansion@^1.1.7:
version "1.1.11"
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==
dependencies:
balanced-match "^1.0.0"
concat-map "0.0.1"
braces@~3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107"
integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==
dependencies:
fill-range "^7.0.1"
buffer-from@^1.0.0:
version "1.1.1"
resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef"
integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==
camelcase-keys@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/camelcase-keys/-/camelcase-keys-2.1.0.tgz#308beeaffdf28119051efa1d932213c91b8f92e7"
integrity sha1-MIvur/3ygRkFHvodkyITyRuPkuc=
dependencies:
camelcase "^2.0.0"
map-obj "^1.0.0"
camelcase@^2.0.0:
version "2.1.1"
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-2.1.1.tgz#7c1d16d679a1bbe59ca02cacecfb011e201f5a1f"
integrity sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=
camelcase@^5.0.0:
version "5.3.1"
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320"
integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==
chalk@^2.4.2:
version "2.4.2"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==
dependencies:
ansi-styles "^3.2.1"
escape-string-regexp "^1.0.5"
supports-color "^5.3.0"
chokidar@^3.4.0:
version "3.4.2"
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.4.2.tgz#38dc8e658dec3809741eb3ef7bb0a47fe424232d"
integrity sha512-IZHaDeBeI+sZJRX7lGcXsdzgvZqKv6sECqsbErJA4mHWfpRrD8B97kSFN4cQz6nGBGiuFia1MKR4d6c1o8Cv7A==
dependencies:
anymatch "~3.1.1"
braces "~3.0.2"
glob-parent "~5.1.0"
is-binary-path "~2.1.0"
is-glob "~4.0.1"
normalize-path "~3.0.0"
readdirp "~3.4.0"
optionalDependencies:
fsevents "~2.1.2"
cliui@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/cliui/-/cliui-5.0.0.tgz#deefcfdb2e800784aa34f46fa08e06851c7bbbc5"
integrity sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==
dependencies:
string-width "^3.1.0"
strip-ansi "^5.2.0"
wrap-ansi "^5.1.0"
color-convert@^1.9.0:
version "1.9.3"
resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8"
integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==
dependencies:
color-name "1.1.3"
color-name@1.1.3:
version "1.1.3"
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25"
integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=
concat-map@0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=
concurrently@^5.3.0:
version "5.3.0"
resolved "https://registry.yarnpkg.com/concurrently/-/concurrently-5.3.0.tgz#7500de6410d043c912b2da27de3202cb489b1e7b"
integrity sha512-8MhqOB6PWlBfA2vJ8a0bSFKATOdWlHiQlk11IfmQBPaHVP8oP2gsh2MObE6UR3hqDHqvaIvLTyceNW6obVuFHQ==
dependencies:
chalk "^2.4.2"
date-fns "^2.0.1"
lodash "^4.17.15"
read-pkg "^4.0.1"
rxjs "^6.5.2"
spawn-command "^0.0.2-1"
supports-color "^6.1.0"
tree-kill "^1.2.2"
yargs "^13.3.0"
currently-unhandled@^0.4.1:
version "0.4.1"
resolved "https://registry.yarnpkg.com/currently-unhandled/-/currently-unhandled-0.4.1.tgz#988df33feab191ef799a61369dd76c17adf957ea"
integrity sha1-mI3zP+qxke95mmE2nddsF635V+o=
dependencies:
array-find-index "^1.0.1"
date-fns@^2.0.1:
version "2.16.1"
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.16.1.tgz#05775792c3f3331da812af253e1a935851d3834b"
integrity sha512-sAJVKx/FqrLYHAQeN7VpJrPhagZc9R4ImZIWYRFZaaohR3KzmuK88touwsSwSVT8Qcbd4zoDsnGfX4GFB4imyQ==
dateformat@~1.0.4-1.2.3:
version "1.0.12"
resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-1.0.12.tgz#9f124b67594c937ff706932e4a642cca8dbbfee9"
integrity sha1-nxJLZ1lMk3/3BpMuSmQsyo27/uk=
dependencies:
get-stdin "^4.0.1"
meow "^3.3.0"
decamelize@^1.1.2, decamelize@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=
diff@^4.0.1:
version "4.0.2"
resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d"
integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==
dynamic-dedupe@^0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/dynamic-dedupe/-/dynamic-dedupe-0.3.0.tgz#06e44c223f5e4e94d78ef9db23a6515ce2f962a1"
integrity sha1-BuRMIj9eTpTXjvnbI6ZRXOL5YqE=
dependencies:
xtend "^4.0.0"
emoji-regex@^7.0.1:
version "7.0.3"
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156"
integrity sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==
error-ex@^1.2.0, error-ex@^1.3.1:
version "1.3.2"
resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf"
integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==
dependencies:
is-arrayish "^0.2.1"
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"
integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=
fill-range@^7.0.1:
version "7.0.1"
resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40"
integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==
dependencies:
to-regex-range "^5.0.1"
find-up@^1.0.0:
version "1.1.2"
resolved "https://registry.yarnpkg.com/find-up/-/find-up-1.1.2.tgz#6b2e9822b1a2ce0a60ab64d610eccad53cb24d0f"
integrity sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=
dependencies:
path-exists "^2.0.0"
pinkie-promise "^2.0.0"
find-up@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73"
integrity sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==
dependencies:
locate-path "^3.0.0"
fs.realpath@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8=
fsevents@~2.1.2:
version "2.1.3"
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.1.3.tgz#fb738703ae8d2f9fe900c33836ddebee8b97f23e"
integrity sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==
get-caller-file@^2.0.1:
version "2.0.5"
resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e"
integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==
get-stdin@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-4.0.1.tgz#b968c6b0a04384324902e8bf1a5df32579a450fe"
integrity sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=
glob-parent@~5.1.0:
version "5.1.1"
resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.1.tgz#b6c1ef417c4e5663ea498f1c45afac6916bbc229"
integrity sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==
dependencies:
is-glob "^4.0.1"
glob@^7.1.3:
version "7.1.6"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6"
integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==
dependencies:
fs.realpath "^1.0.0"
inflight "^1.0.4"
inherits "2"
minimatch "^3.0.4"
once "^1.3.0"
path-is-absolute "^1.0.0"
google-protobuf@^3.13.0, google-protobuf@^3.6.1:
version "3.13.0"
resolved "https://registry.yarnpkg.com/google-protobuf/-/google-protobuf-3.13.0.tgz#909c5983d75dd6101ed57c79e0528d000cdc3251"
integrity sha512-ZIf3qfLFayVrPvAjeKKxO5FRF1/NwRxt6Dko+fWEMuHwHbZx8/fcaAao9b0wCM6kr8qeg2te8XTpyuvKuD9aKw==
graceful-fs@^4.1.2:
version "4.2.4"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb"
integrity sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==
has-flag@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd"
integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0=
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"
integrity sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==
indent-string@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-2.1.0.tgz#8e2d48348742121b4a8218b7a137e9a52049dc80"
integrity sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=
dependencies:
repeating "^2.0.0"
inflight@^1.0.4:
version "1.0.6"
resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"
integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=
dependencies:
once "^1.3.0"
wrappy "1"
inherits@2:
version "2.0.4"
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
is-arrayish@^0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d"
integrity sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=
is-binary-path@~2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09"
integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==
dependencies:
binary-extensions "^2.0.0"
is-extglob@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2"
integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=
is-finite@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/is-finite/-/is-finite-1.1.0.tgz#904135c77fb42c0641d6aa1bcdbc4daa8da082f3"
integrity sha512-cdyMtqX/BOqqNBBiKlIVkytNHm49MtMlYyn1zxzvJKWmFMlGzm+ry5BBfYyeY9YmNKbRSo/o7OX9w9ale0wg3w==
is-fullwidth-code-point@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f"
integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=
is-glob@^4.0.1, is-glob@~4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc"
integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==
dependencies:
is-extglob "^2.1.1"
is-number@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b"
integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==
is-utf8@^0.2.0:
version "0.2.1"
resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72"
integrity sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=
json-parse-better-errors@^1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9"
integrity sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==
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"
integrity sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=
dependencies:
graceful-fs "^4.1.2"
parse-json "^2.2.0"
pify "^2.0.0"
pinkie-promise "^2.0.0"
strip-bom "^2.0.0"
locate-path@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e"
integrity sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==
dependencies:
p-locate "^3.0.0"
path-exists "^3.0.0"
lodash@^4.17.15:
version "4.17.20"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52"
integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==
loud-rejection@^1.0.0:
version "1.6.0"
resolved "https://registry.yarnpkg.com/loud-rejection/-/loud-rejection-1.6.0.tgz#5b46f80147edee578870f086d04821cf998e551f"
integrity sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=
dependencies:
currently-unhandled "^0.4.1"
signal-exit "^3.0.0"
make-error@^1.1.1:
version "1.3.6"
resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2"
integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==
map-obj@^1.0.0, map-obj@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-1.0.1.tgz#d933ceb9205d82bdcf4886f6742bdc2b4dea146d"
integrity sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=
meow@^3.3.0:
version "3.7.0"
resolved "https://registry.yarnpkg.com/meow/-/meow-3.7.0.tgz#72cb668b425228290abbfa856892587308a801fb"
integrity sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=
dependencies:
camelcase-keys "^2.0.0"
decamelize "^1.1.2"
loud-rejection "^1.0.0"
map-obj "^1.0.1"
minimist "^1.1.3"
normalize-package-data "^2.3.4"
object-assign "^4.0.1"
read-pkg-up "^1.0.1"
redent "^1.0.0"
trim-newlines "^1.0.0"
minimatch@^3.0.4:
version "3.0.4"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==
dependencies:
brace-expansion "^1.1.7"
minimist@^1.1.3, minimist@^1.2.5:
version "1.2.5"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602"
integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==
mkdirp@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e"
integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==
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"
integrity sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==
dependencies:
hosted-git-info "^2.1.4"
resolve "^1.10.0"
semver "2 || 3 || 4 || 5"
validate-npm-package-license "^3.0.1"
normalize-path@^3.0.0, normalize-path@~3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65"
integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==
object-assign@^4.0.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=
once@^1.3.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E=
dependencies:
wrappy "1"
p-limit@^2.0.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1"
integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==
dependencies:
p-try "^2.0.0"
p-locate@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-3.0.0.tgz#322d69a05c0264b25997d9f40cd8a891ab0064a4"
integrity sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==
dependencies:
p-limit "^2.0.0"
p-try@^2.0.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6"
integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==
parse-json@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-2.2.0.tgz#f480f40434ef80741f8469099f8dea18f55a4dc9"
integrity sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=
dependencies:
error-ex "^1.2.0"
parse-json@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-4.0.0.tgz#be35f5425be1f7f6c747184f98a788cb99477ee0"
integrity sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=
dependencies:
error-ex "^1.3.1"
json-parse-better-errors "^1.0.1"
path-exists@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-2.1.0.tgz#0feb6c64f0fc518d9a754dd5efb62c7022761f4b"
integrity sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=
dependencies:
pinkie-promise "^2.0.0"
path-exists@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515"
integrity sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=
path-is-absolute@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18=
path-parse@^1.0.6:
version "1.0.6"
resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c"
integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==
path-type@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/path-type/-/path-type-1.1.0.tgz#59c44f7ee491da704da415da5a4070ba4f8fe441"
integrity sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=
dependencies:
graceful-fs "^4.1.2"
pify "^2.0.0"
pinkie-promise "^2.0.0"
picomatch@^2.0.4, picomatch@^2.2.1:
version "2.2.2"
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad"
integrity sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==
pify@^2.0.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c"
integrity sha1-7RQaasBDqEnqWISY59yosVMw6Qw=
pify@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176"
integrity sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=
pinkie-promise@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa"
integrity sha1-ITXW36ejWMBprJsXh3YogihFD/o=
dependencies:
pinkie "^2.0.0"
pinkie@^2.0.0:
version "2.0.4"
resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870"
integrity sha1-clVrgM+g1IqXToDnckjoDtT3+HA=
read-pkg-up@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-1.0.1.tgz#9d63c13276c065918d57f002a57f40a1b643fb02"
integrity sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=
dependencies:
find-up "^1.0.0"
read-pkg "^1.0.0"
read-pkg@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-1.1.0.tgz#f5ffaa5ecd29cb31c0474bca7d756b6bb29e3f28"
integrity sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=
dependencies:
load-json-file "^1.0.0"
normalize-package-data "^2.3.2"
path-type "^1.0.0"
read-pkg@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-4.0.1.tgz#963625378f3e1c4d48c85872b5a6ec7d5d093237"
integrity sha1-ljYlN48+HE1IyFhytabsfV0JMjc=
dependencies:
normalize-package-data "^2.3.2"
parse-json "^4.0.0"
pify "^3.0.0"
readdirp@~3.4.0:
version "3.4.0"
resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.4.0.tgz#9fdccdf9e9155805449221ac645e8303ab5b9ada"
integrity sha512-0xe001vZBnJEK+uKcj8qOhyAKPzIT+gStxWr3LCB0DwcXR5NZJ3IaC+yGnHCYzB/S7ov3m3EEbZI2zeNvX+hGQ==
dependencies:
picomatch "^2.2.1"
redent@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/redent/-/redent-1.0.0.tgz#cf916ab1fd5f1f16dfb20822dd6ec7f730c2afde"
integrity sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=
dependencies:
indent-string "^2.1.0"
strip-indent "^1.0.1"
repeating@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/repeating/-/repeating-2.0.1.tgz#5214c53a926d3552707527fbab415dbc08d06dda"
integrity sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=
dependencies:
is-finite "^1.0.0"
require-directory@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42"
integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I=
require-main-filename@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b"
integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==
resolve@^1.0.0, resolve@^1.10.0:
version "1.17.0"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.17.0.tgz#b25941b54968231cc2d1bb76a79cb7f2c0bf8444"
integrity sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==
dependencies:
path-parse "^1.0.6"
rimraf@^2.6.1:
version "2.7.1"
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec"
integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==
dependencies:
glob "^7.1.3"
rxjs@^6.5.2:
version "6.6.3"
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.3.tgz#8ca84635c4daa900c0d3967a6ee7ac60271ee552"
integrity sha512-trsQc+xYYXZ3urjOiJOuCOa5N3jAZ3eiSpQB5hIT8zGlL2QfnHLJ2r7GMkBGuIausdJN1OneaI6gQlsqNHHmZQ==
dependencies:
tslib "^1.9.0"
"semver@2 || 3 || 4 || 5":
version "5.7.1"
resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==
set-blocking@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7"
integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc=
signal-exit@^3.0.0:
version "3.0.3"
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c"
integrity sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==
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"
integrity sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==
dependencies:
buffer-from "^1.0.0"
source-map "^0.6.0"
source-map@^0.6.0:
version "0.6.1"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
spawn-command@^0.0.2-1:
version "0.0.2-1"
resolved "https://registry.yarnpkg.com/spawn-command/-/spawn-command-0.0.2-1.tgz#62f5e9466981c1b796dc5929937e11c9c6921bd0"
integrity sha1-YvXpRmmBwbeW3Fkpk34RycaSG9A=
spdx-correct@^3.0.0:
version "3.1.1"
resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.1.tgz#dece81ac9c1e6713e5f7d1b6f17d468fa53d89a9"
integrity sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==
dependencies:
spdx-expression-parse "^3.0.0"
spdx-license-ids "^3.0.0"
spdx-exceptions@^2.1.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz#3f28ce1a77a00372683eade4a433183527a2163d"
integrity sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==
spdx-expression-parse@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz#cf70f50482eefdc98e3ce0a6833e4a53ceeba679"
integrity sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==
dependencies:
spdx-exceptions "^2.1.0"
spdx-license-ids "^3.0.0"
spdx-license-ids@^3.0.0:
version "3.0.6"
resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.6.tgz#c80757383c28abf7296744998cbc106ae8b854ce"
integrity sha512-+orQK83kyMva3WyPf59k1+Y525csj5JejicWut55zeTWANuN17qSiSLUXWtzHeNWORSvT7GLDJ/E/XiIWoXBTw==
string-width@^3.0.0, string-width@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961"
integrity sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==
dependencies:
emoji-regex "^7.0.1"
is-fullwidth-code-point "^2.0.0"
strip-ansi "^5.1.0"
strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0:
version "5.2.0"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae"
integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==
dependencies:
ansi-regex "^4.1.0"
strip-bom@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-2.0.0.tgz#6219a85616520491f35788bdbf1447a99c7e6b0e"
integrity sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=
dependencies:
is-utf8 "^0.2.0"
strip-bom@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3"
integrity sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=
strip-indent@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-1.0.1.tgz#0c7962a6adefa7bbd4ac366460a638552ae1a0a2"
integrity sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI=
dependencies:
get-stdin "^4.0.1"
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"
integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo=
supports-color@^5.3.0:
version "5.5.0"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f"
integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==
dependencies:
has-flag "^3.0.0"
supports-color@^6.1.0:
version "6.1.0"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.1.0.tgz#0764abc69c63d5ac842dd4867e8d025e880df8f3"
integrity sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==
dependencies:
has-flag "^3.0.0"
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"
integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==
dependencies:
is-number "^7.0.0"
tree-kill@^1.2.2:
version "1.2.2"
resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.2.tgz#4ca09a9092c88b73a7cdc5e8a01b507b0790a0cc"
integrity sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==
trim-newlines@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-1.0.0.tgz#5887966bb582a4503a41eb524f7d35011815a613"
integrity sha1-WIeWa7WCpFA6QetST301ARgVphM=
ts-node-dev@^1.0.0-pre.44:
version "1.0.0-pre.62"
resolved "https://registry.yarnpkg.com/ts-node-dev/-/ts-node-dev-1.0.0-pre.62.tgz#835644c43669b659a880379b9d06df86cef665ad"
integrity sha512-hfsEuCqUZOVnZ86l7A3icxD1nFt1HEmLVbx4YOHCkrbSHPBNWcw+IczAPZo3zz7YiOm9vs0xG6OENNrkgm89tQ==
dependencies:
chokidar "^3.4.0"
dateformat "~1.0.4-1.2.3"
dynamic-dedupe "^0.3.0"
minimist "^1.2.5"
mkdirp "^1.0.4"
resolve "^1.0.0"
rimraf "^2.6.1"
source-map-support "^0.5.12"
tree-kill "^1.2.2"
ts-node "^8.10.2"
tsconfig "^7.0.0"
ts-node@^8.10.2:
version "8.10.2"
resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-8.10.2.tgz#eee03764633b1234ddd37f8db9ec10b75ec7fb8d"
integrity sha512-ISJJGgkIpDdBhWVu3jufsWpK3Rzo7bdiIXJjQc0ynKxVOVcg2oIrf2H2cejminGrptVc6q6/uynAHNCuWGbpVA==
dependencies:
arg "^4.1.0"
diff "^4.0.1"
make-error "^1.1.1"
source-map-support "^0.5.17"
yn "3.1.1"
ts-protoc-gen@^0.13.0:
version "0.13.0"
resolved "https://registry.yarnpkg.com/ts-protoc-gen/-/ts-protoc-gen-0.13.0.tgz#2763ae4e4a1a7d7001d53d2f3043357c691701ea"
integrity sha512-j18X4rkDBbG/ZHUJy88WFeZP6mStGow5uREaohowlHXTu3/N7WcpyPhb7Vh6wN38ERmc/AkT9gqT98+vtlRhJA==
dependencies:
google-protobuf "^3.6.1"
tsconfig@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/tsconfig/-/tsconfig-7.0.0.tgz#84538875a4dc216e5c4a5432b3a4dec3d54e91b7"
integrity sha512-vZXmzPrL+EmC4T/4rVlT2jNVMWCi/O4DIiSj3UHg1OE5kCKbk4mfrXc6dZksLgRM/TZlKnousKH9bbTazUWRRw==
dependencies:
"@types/strip-bom" "^3.0.0"
"@types/strip-json-comments" "0.0.30"
strip-bom "^3.0.0"
strip-json-comments "^2.0.0"
tslib@^1.9.0:
version "1.13.0"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.13.0.tgz#c881e13cc7015894ed914862d276436fa9a47043"
integrity sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q==
typescript@^3.8.3:
version "3.9.7"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.7.tgz#98d600a5ebdc38f40cb277522f12dc800e9e25fa"
integrity sha512-BLbiRkiBzAwsjut4x/dsibSTB6yWpwT5qWmC2OfuCg3GgVQCSgMs4vEctYPhsaGtd0AeuuHMkjZ2h2WG8MSzRw==
validate-npm-package-license@^3.0.1:
version "3.0.4"
resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a"
integrity sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==
dependencies:
spdx-correct "^3.0.0"
spdx-expression-parse "^3.0.0"
which-module@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a"
integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=
wrap-ansi@^5.1.0:
version "5.1.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-5.1.0.tgz#1fd1f67235d5b6d0fee781056001bfb694c03b09"
integrity sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==
dependencies:
ansi-styles "^3.2.0"
string-width "^3.0.0"
strip-ansi "^5.0.0"
wrappy@1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=
xtend@^4.0.0:
version "4.0.2"
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"
integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==
y18n@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.0.tgz#95ef94f85ecc81d007c264e190a120f0a3c8566b"
integrity sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==
yargs-parser@^13.1.2:
version "13.1.2"
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.1.2.tgz#130f09702ebaeef2650d54ce6e3e5706f7a4fb38"
integrity sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==
dependencies:
camelcase "^5.0.0"
decamelize "^1.2.0"
yargs@^13.3.0:
version "13.3.2"
resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.3.2.tgz#ad7ffefec1aa59565ac915f82dccb38a9c31a2dd"
integrity sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==
dependencies:
cliui "^5.0.0"
find-up "^3.0.0"
get-caller-file "^2.0.1"
require-directory "^2.1.1"
require-main-filename "^2.0.0"
set-blocking "^2.0.0"
string-width "^3.0.0"
which-module "^2.0.0"
y18n "^4.0.0"
yargs-parser "^13.1.2"
yn@3.1.1:
version "3.1.1"
resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50"
integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==