Merge branch 'develop' of github.com:thecodingmachine/workadventure into feature/global-message
+ migrating to protobuf messages # Conflicts: # back/src/App.ts # back/src/Controller/IoSocketController.ts # back/yarn.lock # front/src/Connection.ts # front/src/Phaser/Game/GameScene.ts # front/src/Phaser/Login/EnableCameraScene.ts # front/src/WebRtc/SimplePeer.ts
This commit is contained in:
commit
d3fa901691
@ -1 +1,3 @@
|
|||||||
DEBUG_MODE=false
|
DEBUG_MODE=false
|
||||||
|
JITSI_URL=meet.jit.si
|
||||||
|
ADMIN_API_TOKEN=123
|
||||||
|
4
.github/workflows/build-and-deploy.yml
vendored
4
.github/workflows/build-and-deploy.yml
vendored
@ -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
|
||||||
|
28
.github/workflows/continuous_integration.yml
vendored
28
.github/workflows/continuous_integration.yml
vendored
@ -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"
|
||||||
|
@ -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
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"tsc": "tsc",
|
"tsc": "tsc",
|
||||||
"dev": "ts-node-dev --respawn --transpileOnly ./server.ts",
|
"dev": "ts-node-dev --respawn ./server.ts",
|
||||||
"prod": "tsc && node ./dist/server.js",
|
"prod": "tsc && node ./dist/server.js",
|
||||||
"profile": "tsc && node --prof ./dist/server.js",
|
"profile": "tsc && node --prof ./dist/server.js",
|
||||||
"test": "ts-node node_modules/jasmine/bin/jasmine --config=jasmine.json",
|
"test": "ts-node node_modules/jasmine/bin/jasmine --config=jasmine.json",
|
||||||
@ -36,27 +36,34 @@
|
|||||||
},
|
},
|
||||||
"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",
|
||||||
"express": "^4.17.1",
|
"busboy": "^0.3.1",
|
||||||
|
"circular-json": "^0.5.9",
|
||||||
"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",
|
||||||
|
"iterall": "^1.3.0",
|
||||||
"jsonwebtoken": "^8.5.1",
|
"jsonwebtoken": "^8.5.1",
|
||||||
|
"mkdirp": "^1.0.4",
|
||||||
"multer": "^1.4.2",
|
"multer": "^1.4.2",
|
||||||
"prom-client": "^12.0.0",
|
"prom-client": "^12.0.0",
|
||||||
"socket.io": "^2.3.0",
|
"query-string": "^6.13.3",
|
||||||
"systeminformation": "^4.26.5",
|
"systeminformation": "^4.26.5",
|
||||||
"ts-node-dev": "^1.0.0-pre.44",
|
"ts-node-dev": "^1.0.0-pre.44",
|
||||||
"typescript": "^3.8.3",
|
"typescript": "^3.8.3",
|
||||||
|
"uWebSockets.js": "uNetworking/uWebSockets.js#v18.5.0",
|
||||||
"uuidv4": "^6.0.7"
|
"uuidv4": "^6.0.7"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@types/busboy": "^0.2.3",
|
||||||
|
"@types/circular-json": "^0.4.0",
|
||||||
|
"@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/mkdirp": "^1.0.1",
|
||||||
|
"@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",
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
// lib/server.ts
|
// lib/server.ts
|
||||||
import App from "./src/App";
|
import App from "./src/App";
|
||||||
App.listen(8080, () => console.log(`Example app listening on port 8080!`))
|
App.listen(8080, () => console.log(`WorkAdventure starting on port 8080!`))
|
||||||
|
@ -1,61 +1,32 @@
|
|||||||
// lib/app.ts
|
// lib/app.ts
|
||||||
import {IoSocketController} from "./Controller/IoSocketController"; //TODO fix import by "_Controller/..."
|
import {IoSocketController} from "./Controller/IoSocketController"; //TODO fix import by "_Controller/..."
|
||||||
import {AuthenticateController} from "./Controller/AuthenticateController"; //TODO fix import by "_Controller/..."
|
import {AuthenticateController} from "./Controller/AuthenticateController"; //TODO fix import by "_Controller/..."
|
||||||
import express from "express";
|
|
||||||
import {Application, Request, Response} from 'express';
|
|
||||||
import bodyParser = require('body-parser');
|
|
||||||
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 {FileController} from "./Controller/FileController";
|
import {FileController} from "./Controller/FileController";
|
||||||
import {AdminController} from "./Controller/AdminController";
|
import {DebugController} from "./Controller/DebugController";
|
||||||
|
import {App as uwsApp} from "./Server/sifrr.server";
|
||||||
|
|
||||||
class App {
|
class App {
|
||||||
public app: Application;
|
public app: uwsApp;
|
||||||
public server: http.Server;
|
|
||||||
public ioSocketController: IoSocketController;
|
public ioSocketController: IoSocketController;
|
||||||
public authenticateController: AuthenticateController;
|
public authenticateController: AuthenticateController;
|
||||||
public fileController: FileController;
|
public fileController: FileController;
|
||||||
public mapController: MapController;
|
public mapController: MapController;
|
||||||
public prometheusController: PrometheusController;
|
public prometheusController: PrometheusController;
|
||||||
private adminController: AdminController;
|
private debugController: DebugController;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.app = express();
|
this.app = new uwsApp();
|
||||||
|
|
||||||
//config server http
|
|
||||||
this.server = http.createServer(this.app);
|
|
||||||
|
|
||||||
this.config();
|
|
||||||
this.crossOrigin();
|
|
||||||
|
|
||||||
//TODO add middleware with access token to secure api
|
|
||||||
|
|
||||||
//create socket controllers
|
//create socket controllers
|
||||||
this.ioSocketController = new IoSocketController(this.server);
|
this.ioSocketController = new IoSocketController(this.app);
|
||||||
this.authenticateController = new AuthenticateController(this.app);
|
this.authenticateController = new AuthenticateController(this.app);
|
||||||
this.fileController = new FileController(this.app);
|
this.fileController = new FileController(this.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.debugController = new DebugController(this.app, this.ioSocketController);
|
||||||
}
|
|
||||||
|
|
||||||
// TODO add session user
|
|
||||||
private config(): void {
|
|
||||||
this.app.use(bodyParser.json());
|
|
||||||
this.app.use(bodyParser.urlencoded({extended: false}));
|
|
||||||
}
|
|
||||||
|
|
||||||
private crossOrigin(){
|
|
||||||
this.app.use((req: Request, res: Response, next) => {
|
|
||||||
res.setHeader("Access-Control-Allow-Origin", "*"); // update to match the domain you will make the request from
|
|
||||||
// Request methods you wish to allow
|
|
||||||
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, PATCH, DELETE');
|
|
||||||
// Request headers you wish to allow
|
|
||||||
res.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
|
|
||||||
next();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default new App().server;
|
export default new App().app;
|
||||||
|
@ -1,36 +0,0 @@
|
|||||||
import {Application, Request, Response} from "express";
|
|
||||||
import {OK} from "http-status-codes";
|
|
||||||
import {ADMIN_API_TOKEN, ADMIN_API_URL} from "../Enum/EnvironmentVariable";
|
|
||||||
import Axios from "axios";
|
|
||||||
|
|
||||||
export class AdminController {
|
|
||||||
App : Application;
|
|
||||||
|
|
||||||
constructor(App : Application) {
|
|
||||||
this.App = App;
|
|
||||||
this.getLoginUrlByToken();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
getLoginUrlByToken(){
|
|
||||||
this.App.get("/register/:token", async (req: Request, res: Response) => {
|
|
||||||
if (!ADMIN_API_URL) {
|
|
||||||
return res.status(500).send('No admin backoffice set!');
|
|
||||||
}
|
|
||||||
const token:string = req.params.token;
|
|
||||||
|
|
||||||
let response = null
|
|
||||||
try {
|
|
||||||
response = await Axios.get(ADMIN_API_URL+'/api/login-url/'+token, { headers: {"Authorization" : `${ADMIN_API_TOKEN}`} })
|
|
||||||
} catch (e) {
|
|
||||||
console.log(e.message)
|
|
||||||
return res.status(e.status || 500).send('An error happened');
|
|
||||||
}
|
|
||||||
|
|
||||||
const organizationSlug = response.data.organizationSlug;
|
|
||||||
const worldSlug = response.data.worldSlug;
|
|
||||||
const roomSlug = response.data.roomSlug;
|
|
||||||
return res.status(OK).send({organizationSlug, worldSlug, roomSlug});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,40 +1,95 @@
|
|||||||
import {Application, Request, Response} from "express";
|
|
||||||
import Jwt from "jsonwebtoken";
|
import Jwt from "jsonwebtoken";
|
||||||
import {BAD_REQUEST, OK} from "http-status-codes";
|
import {ADMIN_API_TOKEN, ADMIN_API_URL, SECRET_KEY, URL_ROOM_STARTED} from "../Enum/EnvironmentVariable"; //TODO fix import by "_Enum/..."
|
||||||
import {SECRET_KEY, URL_ROOM_STARTED} from "../Enum/EnvironmentVariable"; //TODO fix import by "_Enum/..."
|
|
||||||
import { uuid } from 'uuidv4';
|
import { uuid } from 'uuidv4';
|
||||||
|
import {HttpRequest, HttpResponse, TemplatedApp} from "uWebSockets.js";
|
||||||
|
import {BaseController} from "./BaseController";
|
||||||
|
import Axios from "axios";
|
||||||
|
|
||||||
export interface TokenInterface {
|
export interface TokenInterface {
|
||||||
name: string,
|
userUuid: string
|
||||||
userId: string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class AuthenticateController {
|
interface AdminApiData {
|
||||||
App : Application;
|
organizationSlug: string
|
||||||
|
worldSlug: string
|
||||||
|
roomSlug: string
|
||||||
|
mapUrlStart: string
|
||||||
|
userUuid: string
|
||||||
|
}
|
||||||
|
|
||||||
constructor(App : Application) {
|
export class AuthenticateController extends BaseController {
|
||||||
this.App = App;
|
|
||||||
|
constructor(private App : TemplatedApp) {
|
||||||
|
super();
|
||||||
this.login();
|
this.login();
|
||||||
}
|
}
|
||||||
|
|
||||||
//permit to login on application. Return token to connect on Websocket IO.
|
//permit to login on application. Return token to connect on Websocket IO.
|
||||||
login(){
|
login(){
|
||||||
// For now, let's completely forget the /login route.
|
this.App.options("/login", (res: HttpResponse, req: HttpRequest) => {
|
||||||
this.App.post("/login", (req: Request, res: Response) => {
|
this.addCorsHeaders(res);
|
||||||
const param = req.body;
|
|
||||||
/*if(!param.name){
|
res.end();
|
||||||
return res.status(BAD_REQUEST).send({
|
|
||||||
message: "email parameter is empty"
|
|
||||||
});
|
|
||||||
}*/
|
|
||||||
//TODO check user email for The Coding Machine game
|
|
||||||
const userId = uuid();
|
|
||||||
const token = Jwt.sign({name: param.name, userId: userId} as TokenInterface, SECRET_KEY, {expiresIn: '24h'});
|
|
||||||
return res.status(OK).send({
|
|
||||||
token: token,
|
|
||||||
mapUrlStart: URL_ROOM_STARTED,
|
|
||||||
userId: userId,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.App.post("/login", (res: HttpResponse, req: HttpRequest) => {
|
||||||
|
(async () => {
|
||||||
|
this.addCorsHeaders(res);
|
||||||
|
|
||||||
|
res.onAborted(() => {
|
||||||
|
console.warn('Login request was aborted');
|
||||||
|
})
|
||||||
|
const host = req.getHeader('host');
|
||||||
|
const param = await res.json();
|
||||||
|
|
||||||
|
//todo: what to do if the organizationMemberToken is already used?
|
||||||
|
const organizationMemberToken:string|null = param.organizationMemberToken;
|
||||||
|
|
||||||
|
try {
|
||||||
|
let userUuid;
|
||||||
|
let mapUrlStart;
|
||||||
|
let newUrl: string|null = null;
|
||||||
|
|
||||||
|
if (organizationMemberToken) {
|
||||||
|
if (!ADMIN_API_URL) {
|
||||||
|
return res.status(401).send('No admin backoffice set!');
|
||||||
|
}
|
||||||
|
//todo: this call can fail if the corresponding world is not activated or if the token is invalid. Handle that case.
|
||||||
|
const data = await Axios.get(ADMIN_API_URL+'/api/login-url/'+organizationMemberToken,
|
||||||
|
{ headers: {"Authorization" : `${ADMIN_API_TOKEN}`} }
|
||||||
|
).then((res): AdminApiData => res.data);
|
||||||
|
|
||||||
|
userUuid = data.userUuid;
|
||||||
|
mapUrlStart = data.mapUrlStart;
|
||||||
|
newUrl = this.getNewUrlOnAdminAuth(data)
|
||||||
|
} else {
|
||||||
|
userUuid = uuid();
|
||||||
|
mapUrlStart = host.replace('api.', 'maps.') + URL_ROOM_STARTED;
|
||||||
|
newUrl = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const authToken = Jwt.sign({userUuid: userUuid}, SECRET_KEY, {expiresIn: '24h'});
|
||||||
|
res.writeStatus("200 OK").end(JSON.stringify({
|
||||||
|
authToken,
|
||||||
|
userUuid,
|
||||||
|
mapUrlStart,
|
||||||
|
newUrl,
|
||||||
|
}));
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
console.log("An error happened", e)
|
||||||
|
res.writeStatus(e.status || "500 Internal Server Error").end('An error happened');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
})();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private getNewUrlOnAdminAuth(data:AdminApiData): string {
|
||||||
|
const organizationSlug = data.organizationSlug;
|
||||||
|
const worldSlug = data.worldSlug;
|
||||||
|
const roomSlug = data.roomSlug;
|
||||||
|
return '/@/'+organizationSlug+'/'+worldSlug+'/'+roomSlug;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
10
back/src/Controller/BaseController.ts
Normal file
10
back/src/Controller/BaseController.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import {HttpResponse} from "uWebSockets.js";
|
||||||
|
|
||||||
|
|
||||||
|
export class BaseController {
|
||||||
|
protected addCorsHeaders(res: HttpResponse): void {
|
||||||
|
res.writeHeader('access-control-allow-headers', 'Origin, X-Requested-With, Content-Type, Accept');
|
||||||
|
res.writeHeader('access-control-allow-methods', 'GET, POST, OPTIONS, PUT, PATCH, DELETE');
|
||||||
|
res.writeHeader('access-control-allow-origin', '*');
|
||||||
|
}
|
||||||
|
}
|
44
back/src/Controller/DebugController.ts
Normal file
44
back/src/Controller/DebugController.ts
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
import {ADMIN_API_TOKEN} from "../Enum/EnvironmentVariable";
|
||||||
|
import {IoSocketController} from "_Controller/IoSocketController";
|
||||||
|
import {stringify} from "circular-json";
|
||||||
|
import {HttpRequest, HttpResponse} from "uWebSockets.js";
|
||||||
|
import { parse } from 'query-string';
|
||||||
|
import {App} from "../Server/sifrr.server";
|
||||||
|
|
||||||
|
export class DebugController {
|
||||||
|
constructor(private App : App, private ioSocketController: IoSocketController) {
|
||||||
|
this.getDump();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
getDump(){
|
||||||
|
this.App.get("/dump", (res: HttpResponse, req: HttpRequest) => {
|
||||||
|
const query = parse(req.getQuery());
|
||||||
|
|
||||||
|
if (query.token !== ADMIN_API_TOKEN) {
|
||||||
|
return res.status(401).send('Invalid token sent!');
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.writeStatus('200 OK').writeHeader('Content-Type', 'application/json').end(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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -1,59 +1,161 @@
|
|||||||
const multer = require('multer');
|
import {App} from "../Server/sifrr.server";
|
||||||
import {Application, Request, RequestHandler, Response} from "express";
|
|
||||||
import {OK} from "http-status-codes";
|
|
||||||
import {URL_ROOM_STARTED} from "_Enum/EnvironmentVariable";
|
|
||||||
import {uuid} from "uuidv4";
|
import {uuid} from "uuidv4";
|
||||||
import fs from "fs";
|
import {HttpRequest, HttpResponse} from "uWebSockets.js";
|
||||||
|
import {BaseController} from "./BaseController";
|
||||||
|
import { Readable } from 'stream'
|
||||||
|
|
||||||
const upload = multer({ dest: 'dist/files/' });
|
interface UploadedFileBuffer {
|
||||||
|
buffer: Buffer,
|
||||||
class FileUpload{
|
expireDate: Date
|
||||||
path: string
|
|
||||||
constructor(path : string) {
|
|
||||||
this.path = path;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface RequestFileHandlerInterface extends Request{
|
export class FileController extends BaseController {
|
||||||
file: FileUpload
|
private uploadedFileBuffers: Map<string, UploadedFileBuffer> = new Map<string, UploadedFileBuffer>();
|
||||||
}
|
|
||||||
|
|
||||||
export class FileController {
|
constructor(private App : App) {
|
||||||
App : Application;
|
super();
|
||||||
|
|
||||||
constructor(App : Application) {
|
|
||||||
this.App = App;
|
this.App = App;
|
||||||
this.uploadAudioMessage();
|
this.uploadAudioMessage();
|
||||||
this.downloadAudioMessage();
|
this.downloadAudioMessage();
|
||||||
|
|
||||||
|
// Cleanup every 1 minute
|
||||||
|
setInterval(this.cleanup.bind(this), 60000);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clean memory from old files
|
||||||
|
*/
|
||||||
|
cleanup(): void {
|
||||||
|
const now = new Date();
|
||||||
|
for (const [id, file] of this.uploadedFileBuffers) {
|
||||||
|
if (file.expireDate < now) {
|
||||||
|
this.uploadedFileBuffers.delete(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
uploadAudioMessage(){
|
uploadAudioMessage(){
|
||||||
this.App.post("/upload-audio-message", (upload.single('file') as RequestHandler), (req: Request, res: Response) => {
|
this.App.options("/upload-audio-message", (res: HttpResponse, req: HttpRequest) => {
|
||||||
//TODO check user connected and admin role
|
this.addCorsHeaders(res);
|
||||||
//TODO upload audio message
|
|
||||||
|
res.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.App.post("/upload-audio-message", (res: HttpResponse, req: HttpRequest) => {
|
||||||
|
(async () => {
|
||||||
|
this.addCorsHeaders(res);
|
||||||
|
|
||||||
|
res.onAborted(() => {
|
||||||
|
console.warn('upload-audio-message request was aborted');
|
||||||
|
})
|
||||||
|
|
||||||
|
try {
|
||||||
const audioMessageId = uuid();
|
const audioMessageId = uuid();
|
||||||
|
|
||||||
fs.copyFileSync((req as RequestFileHandlerInterface).file.path, `dist/files/${audioMessageId}`);
|
const params = await res.formData({
|
||||||
fs.unlinkSync((req as RequestFileHandlerInterface).file.path);
|
onFile: (fieldname: string,
|
||||||
|
file: NodeJS.ReadableStream,
|
||||||
|
filename: string,
|
||||||
|
encoding: string,
|
||||||
|
mimetype: string) => {
|
||||||
|
(async () => {
|
||||||
|
console.log('READING FILE', fieldname)
|
||||||
|
|
||||||
return res.status(OK).send({
|
const chunks: Buffer[] = []
|
||||||
|
for await (let chunk of file) {
|
||||||
|
if (!(chunk instanceof Buffer)) {
|
||||||
|
throw new Error('Unexpected chunk');
|
||||||
|
}
|
||||||
|
chunks.push(chunk)
|
||||||
|
}
|
||||||
|
// Let's expire in 1 minute.
|
||||||
|
const expireDate = new Date();
|
||||||
|
expireDate.setMinutes(expireDate.getMinutes() + 1);
|
||||||
|
this.uploadedFileBuffers.set(audioMessageId, {
|
||||||
|
buffer: Buffer.concat(chunks),
|
||||||
|
expireDate
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
res.writeStatus("200 OK").end(JSON.stringify({
|
||||||
id: audioMessageId,
|
id: audioMessageId,
|
||||||
path: `/download-audio-message/${audioMessageId}`
|
path: `/download-audio-message/${audioMessageId}`
|
||||||
});
|
}));
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
console.log("An error happened", e)
|
||||||
|
res.writeStatus(e.status || "500 Internal Server Error").end('An error happened');
|
||||||
|
}
|
||||||
|
})();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
downloadAudioMessage(){
|
downloadAudioMessage(){
|
||||||
this.App.get("/download-audio-message/:id", (req: Request, res: Response) => {
|
this.App.options("/download-audio-message/*", (res: HttpResponse, req: HttpRequest) => {
|
||||||
//TODO check user connected and admin role
|
this.addCorsHeaders(res);
|
||||||
//TODO upload audio message
|
|
||||||
const audiMessageId = req.params.id;
|
|
||||||
|
|
||||||
const fs = require('fs');
|
res.end();
|
||||||
const path = `dist/files/${audiMessageId}`;
|
});
|
||||||
const file = fs.createReadStream(path);
|
|
||||||
res.writeHead(200);
|
this.App.get("/download-audio-message/:id", (res: HttpResponse, req: HttpRequest) => {
|
||||||
file.pipe(res);
|
(async () => {
|
||||||
|
this.addCorsHeaders(res);
|
||||||
|
|
||||||
|
res.onAborted(() => {
|
||||||
|
console.warn('upload-audio-message request was aborted');
|
||||||
|
})
|
||||||
|
|
||||||
|
const id = req.getParameter(0);
|
||||||
|
|
||||||
|
const file = this.uploadedFileBuffers.get(id);
|
||||||
|
if (file === undefined) {
|
||||||
|
res.writeStatus("404 Not found").end("Cannot find file");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const readable = new Readable()
|
||||||
|
readable._read = () => {} // _read is required but you can noop it
|
||||||
|
readable.push(file.buffer);
|
||||||
|
readable.push(null);
|
||||||
|
|
||||||
|
const size = file.buffer.byteLength;
|
||||||
|
|
||||||
|
res.writeStatus("200 OK");
|
||||||
|
|
||||||
|
readable.on('data', buffer => {
|
||||||
|
const chunk = buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength),
|
||||||
|
lastOffset = res.getWriteOffset();
|
||||||
|
|
||||||
|
// First try
|
||||||
|
const [ok, done] = res.tryEnd(chunk, size);
|
||||||
|
|
||||||
|
if (done) {
|
||||||
|
readable.destroy();
|
||||||
|
} else if (!ok) {
|
||||||
|
// pause because backpressure
|
||||||
|
readable.pause();
|
||||||
|
|
||||||
|
// Save unsent chunk for later
|
||||||
|
res.ab = chunk;
|
||||||
|
res.abOffset = lastOffset;
|
||||||
|
|
||||||
|
// Register async handlers for drainage
|
||||||
|
res.onWritable(offset => {
|
||||||
|
const [ok, done] = res.tryEnd(res.ab.slice(offset - res.abOffset), size);
|
||||||
|
if (done) {
|
||||||
|
readable.destroy();
|
||||||
|
} else if (ok) {
|
||||||
|
readable.resume();
|
||||||
|
}
|
||||||
|
return ok;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
})();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
File diff suppressed because it is too large
Load Diff
@ -1,29 +1,34 @@
|
|||||||
import express from "express";
|
|
||||||
import {Application, Request, Response} from "express";
|
|
||||||
import {OK} from "http-status-codes";
|
import {OK} from "http-status-codes";
|
||||||
import {URL_ROOM_STARTED} from "../Enum/EnvironmentVariable";
|
import {URL_ROOM_STARTED} from "../Enum/EnvironmentVariable";
|
||||||
|
import {HttpRequest, HttpResponse, TemplatedApp} from "uWebSockets.js";
|
||||||
|
import {BaseController} from "./BaseController";
|
||||||
|
|
||||||
export class MapController {
|
//todo: delete this
|
||||||
App: Application;
|
export class MapController extends BaseController{
|
||||||
|
|
||||||
constructor(App: Application) {
|
constructor(private App : TemplatedApp) {
|
||||||
|
super();
|
||||||
this.App = App;
|
this.App = App;
|
||||||
this.getStartMap();
|
this.getStartMap();
|
||||||
this.assetMaps();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
assetMaps() {
|
|
||||||
this.App.use('/map/files', express.static('src/Assets/Maps'));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns a map mapping map name to file name of the map
|
// Returns a map mapping map name to file name of the map
|
||||||
getStartMap() {
|
getStartMap() {
|
||||||
this.App.get("/start-map", (req: Request, res: Response) => {
|
this.App.options("/start-map", (res: HttpResponse, req: HttpRequest) => {
|
||||||
const url = req.headers.host?.replace('api.', 'maps.') + URL_ROOM_STARTED;
|
this.addCorsHeaders(res);
|
||||||
res.status(OK).send({
|
|
||||||
|
res.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.App.get("/start-map", (res: HttpResponse, req: HttpRequest) => {
|
||||||
|
this.addCorsHeaders(res);
|
||||||
|
|
||||||
|
const url = req.getHeader('host').replace('api.', 'maps.') + URL_ROOM_STARTED;
|
||||||
|
res.writeStatus("200 OK").end(JSON.stringify({
|
||||||
mapUrlStart: url,
|
mapUrlStart: url,
|
||||||
startInstance: "global"
|
startInstance: "global"
|
||||||
});
|
}));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
import {Application, Request, Response} from "express";
|
import {App} from "../Server/sifrr.server";
|
||||||
import {IoSocketController} from "_Controller/IoSocketController";
|
import {IoSocketController} from "_Controller/IoSocketController";
|
||||||
|
import {HttpRequest, HttpResponse} from "uWebSockets.js";
|
||||||
const register = require('prom-client').register;
|
const register = require('prom-client').register;
|
||||||
const collectDefaultMetrics = require('prom-client').collectDefaultMetrics;
|
const collectDefaultMetrics = require('prom-client').collectDefaultMetrics;
|
||||||
|
|
||||||
export class PrometheusController {
|
export class PrometheusController {
|
||||||
constructor(private App: Application, private ioSocketController: IoSocketController) {
|
constructor(private App: App, private ioSocketController: IoSocketController) {
|
||||||
collectDefaultMetrics({
|
collectDefaultMetrics({
|
||||||
timeout: 10000,
|
timeout: 10000,
|
||||||
gcDurationBuckets: [0.001, 0.01, 0.1, 1, 2, 5], // These are the default buckets.
|
gcDurationBuckets: [0.001, 0.01, 0.1, 1, 2, 5], // These are the default buckets.
|
||||||
@ -13,8 +14,8 @@ export class PrometheusController {
|
|||||||
this.App.get("/metrics", this.metrics.bind(this));
|
this.App.get("/metrics", this.metrics.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
private metrics(req: Request, res: Response): void {
|
private metrics(res: HttpResponse, req: HttpRequest): void {
|
||||||
res.set('Content-Type', register.contentType);
|
res.writeHeader('Content-Type', register.contentType);
|
||||||
res.end(register.metrics());
|
res.end(register.metrics());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ const GROUP_RADIUS = process.env.GROUP_RADIUS ? Number(process.env.GROUP_RADIUS)
|
|||||||
const ALLOW_ARTILLERY = process.env.ALLOW_ARTILLERY ? process.env.ALLOW_ARTILLERY == 'true' : false;
|
const ALLOW_ARTILLERY = process.env.ALLOW_ARTILLERY ? process.env.ALLOW_ARTILLERY == 'true' : false;
|
||||||
const ADMIN_API_URL = process.env.ADMIN_API_URL || null;
|
const ADMIN_API_URL = process.env.ADMIN_API_URL || null;
|
||||||
const ADMIN_API_TOKEN = process.env.ADMIN_API_TOKEN || null;
|
const ADMIN_API_TOKEN = process.env.ADMIN_API_TOKEN || null;
|
||||||
|
const CPU_OVERHEAT_THRESHOLD = Number(process.env.CPU_OVERHEAT_THRESHOLD) || 80;
|
||||||
|
|
||||||
export {
|
export {
|
||||||
SECRET_KEY,
|
SECRET_KEY,
|
||||||
@ -14,4 +15,5 @@ export {
|
|||||||
ADMIN_API_TOKEN,
|
ADMIN_API_TOKEN,
|
||||||
GROUP_RADIUS,
|
GROUP_RADIUS,
|
||||||
ALLOW_ARTILLERY,
|
ALLOW_ARTILLERY,
|
||||||
|
CPU_OVERHEAT_THRESHOLD,
|
||||||
}
|
}
|
1
back/src/Messages/.gitignore
vendored
Normal file
1
back/src/Messages/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
/generated/
|
@ -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 {
|
||||||
@ -62,7 +87,7 @@ export class Group implements Movable {
|
|||||||
join(user: User): void
|
join(user: User): void
|
||||||
{
|
{
|
||||||
// Broadcast on the right event
|
// Broadcast on the right event
|
||||||
this.connectCallback(user.id, this);
|
this.connectCallback(user, this);
|
||||||
this.users.add(user);
|
this.users.add(user);
|
||||||
user.group = this;
|
user.group = this;
|
||||||
}
|
}
|
||||||
@ -75,8 +100,12 @@ 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, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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;
|
||||||
|
@ -3,21 +3,32 @@ 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";
|
||||||
|
import {ExSocketInterface} from "_Model/Websocket/ExSocketInterface";
|
||||||
|
|
||||||
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,
|
||||||
|
public readonly socket: ExSocketInterface
|
||||||
) {
|
) {
|
||||||
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,23 +1,23 @@
|
|||||||
import {Socket} from "socket.io";
|
|
||||||
import {PointInterface} from "./PointInterface";
|
import {PointInterface} from "./PointInterface";
|
||||||
import {Identificable} from "./Identificable";
|
import {Identificable} from "./Identificable";
|
||||||
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";
|
||||||
|
import {WebSocket} from "uWebSockets.js"
|
||||||
|
|
||||||
export interface ExSocketInterface extends Socket, Identificable {
|
export interface ExSocketInterface extends WebSocket, Identificable {
|
||||||
token: string;
|
token: string;
|
||||||
roomId: string;
|
roomId: string;
|
||||||
webRtcRoomId: string;
|
userId: number; // A temporary (autoincremented) identifier for this user
|
||||||
userId: string;
|
userUuid: string; // A unique identifier for this user
|
||||||
name: string;
|
name: string;
|
||||||
characterLayers: string[];
|
characterLayers: string[];
|
||||||
position: PointInterface;
|
position: PointInterface;
|
||||||
viewport: ViewportInterface;
|
viewport: ViewportInterface;
|
||||||
isArtillery: boolean; // Whether this socket is opened by Artillery for load testing (hack)
|
|
||||||
/**
|
/**
|
||||||
* 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: (payload: SubMessage) => void;
|
||||||
batchedMessages: Array<{ event: string | symbol, payload: unknown }>;
|
batchedMessages: BatchMessage;
|
||||||
batchTimeout: NodeJS.Timeout|null;
|
batchTimeout: NodeJS.Timeout|null;
|
||||||
|
disconnecting: boolean
|
||||||
}
|
}
|
||||||
|
@ -2,5 +2,5 @@ import {PositionInterface} from "_Model/PositionInterface";
|
|||||||
|
|
||||||
export interface GroupUpdateInterface {
|
export interface GroupUpdateInterface {
|
||||||
position: PositionInterface,
|
position: PositionInterface,
|
||||||
groupId: string,
|
groupId: number,
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
export interface Identificable {
|
export interface Identificable {
|
||||||
userId: string;
|
userId: number;
|
||||||
}
|
}
|
||||||
|
@ -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) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +0,0 @@
|
|||||||
import {PointInterface} from "./PointInterface";
|
|
||||||
|
|
||||||
export class MessageUserMoved {
|
|
||||||
constructor(public userId: string, public position: PointInterface) {
|
|
||||||
}
|
|
||||||
}
|
|
@ -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) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
92
back/src/Model/Websocket/ProtobufUtils.ts
Normal file
92
back/src/Model/Websocket/ProtobufUtils.ts
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
import {PointInterface} from "./PointInterface";
|
||||||
|
import {ItemEventMessage, PointMessage, PositionMessage} from "../../Messages/generated/messages_pb";
|
||||||
|
import {ExSocketInterface} from "_Model/Websocket/ExSocketInterface";
|
||||||
|
import Direction = PositionMessage.Direction;
|
||||||
|
import {ItemEventMessageInterface} from "_Model/Websocket/ItemEventMessage";
|
||||||
|
import {PositionInterface} from "_Model/PositionInterface";
|
||||||
|
|
||||||
|
export class ProtobufUtils {
|
||||||
|
|
||||||
|
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 toPointInterface(position: PositionMessage): PointInterface {
|
||||||
|
let direction: string;
|
||||||
|
switch (position.getDirection()) {
|
||||||
|
case Direction.UP:
|
||||||
|
direction = 'up';
|
||||||
|
break;
|
||||||
|
case Direction.DOWN:
|
||||||
|
direction = 'down';
|
||||||
|
break;
|
||||||
|
case Direction.LEFT:
|
||||||
|
direction = 'left';
|
||||||
|
break;
|
||||||
|
case Direction.RIGHT:
|
||||||
|
direction = 'right';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Error("Unexpected direction");
|
||||||
|
}
|
||||||
|
|
||||||
|
// sending to all clients in room except sender
|
||||||
|
return {
|
||||||
|
x: position.getX(),
|
||||||
|
y: position.getY(),
|
||||||
|
direction,
|
||||||
|
moving: position.getMoving(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static toPointMessage(point: PositionInterface): PointMessage {
|
||||||
|
const position = new PointMessage();
|
||||||
|
position.setX(Math.floor(point.x));
|
||||||
|
position.setY(Math.floor(point.y));
|
||||||
|
|
||||||
|
return position;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static toItemEvent(itemEventMessage: ItemEventMessage): ItemEventMessageInterface {
|
||||||
|
return {
|
||||||
|
itemId: itemEventMessage.getItemid(),
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
export interface UserInGroupInterface {
|
export interface UserInGroupInterface {
|
||||||
userId: string,
|
userId: number,
|
||||||
name: string,
|
name: string,
|
||||||
initiator: boolean
|
initiator: boolean
|
||||||
}
|
}
|
||||||
|
@ -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>;
|
|
@ -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>;
|
||||||
|
@ -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: User, group: Group) => void;
|
||||||
export type DisconnectCallback = (user: string, group: Group) => void;
|
export type DisconnectCallback = (user: User, 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 : ExSocketInterface, userPosition: PointInterface): void {
|
||||||
this.users.set(socket.userId, new User(socket.userId, userPosition, false));
|
const user = new User(socket.userId, userPosition, false, this.positionNotifier, socket);
|
||||||
|
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){
|
||||||
@ -72,8 +74,8 @@ export class World {
|
|||||||
this.users.delete(user.userId);
|
this.users.delete(user.userId);
|
||||||
|
|
||||||
if (userObj !== undefined) {
|
if (userObj !== undefined) {
|
||||||
this.positionNotifier.leave(userObj);
|
|
||||||
this.positionNotifier.removeViewport(userObj);
|
this.positionNotifier.removeViewport(userObj);
|
||||||
|
this.positionNotifier.leave(userObj);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -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
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
13
back/src/Server/server/app.ts
Normal file
13
back/src/Server/server/app.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import { App as _App, AppOptions } from 'uWebSockets.js';
|
||||||
|
import BaseApp from './baseapp';
|
||||||
|
import { extend } from './utils';
|
||||||
|
import { UwsApp } from './types';
|
||||||
|
|
||||||
|
class App extends (<UwsApp>_App) {
|
||||||
|
constructor(options: AppOptions = {}) {
|
||||||
|
super(options); // eslint-disable-line constructor-super
|
||||||
|
extend(this, new BaseApp());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default App;
|
116
back/src/Server/server/baseapp.ts
Normal file
116
back/src/Server/server/baseapp.ts
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
import { Readable } from 'stream';
|
||||||
|
import { us_listen_socket_close, TemplatedApp, HttpResponse, HttpRequest } from 'uWebSockets.js';
|
||||||
|
|
||||||
|
import formData from './formdata';
|
||||||
|
import { stob } from './utils';
|
||||||
|
import { Handler } from './types';
|
||||||
|
import {join} from "path";
|
||||||
|
|
||||||
|
const contTypes = ['application/x-www-form-urlencoded', 'multipart/form-data'];
|
||||||
|
const noOp = () => true;
|
||||||
|
|
||||||
|
const handleBody = (res: HttpResponse, req: HttpRequest) => {
|
||||||
|
const contType = req.getHeader('content-type');
|
||||||
|
|
||||||
|
res.bodyStream = function() {
|
||||||
|
const stream = new Readable();
|
||||||
|
stream._read = noOp; // eslint-disable-line @typescript-eslint/unbound-method
|
||||||
|
|
||||||
|
this.onData((ab: ArrayBuffer, isLast: boolean) => {
|
||||||
|
// uint and then slicing is bit faster than slice and then uint
|
||||||
|
stream.push(new Uint8Array(ab.slice((ab as any).byteOffset, ab.byteLength))); // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||||
|
if (isLast) {
|
||||||
|
stream.push(null);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return stream;
|
||||||
|
};
|
||||||
|
|
||||||
|
res.body = () => stob(res.bodyStream());
|
||||||
|
|
||||||
|
if (contType.includes('application/json'))
|
||||||
|
res.json = async () => JSON.parse(await res.body());
|
||||||
|
if (contTypes.map(t => contType.indexOf(t) > -1).indexOf(true) > -1)
|
||||||
|
res.formData = formData.bind(res, contType);
|
||||||
|
};
|
||||||
|
|
||||||
|
class BaseApp {
|
||||||
|
_sockets = new Map();
|
||||||
|
ws!: TemplatedApp['ws'];
|
||||||
|
get!: TemplatedApp['get'];
|
||||||
|
_post!: TemplatedApp['post'];
|
||||||
|
_put!: TemplatedApp['put'];
|
||||||
|
_patch!: TemplatedApp['patch'];
|
||||||
|
_listen!: TemplatedApp['listen'];
|
||||||
|
|
||||||
|
post(pattern: string, handler: Handler) {
|
||||||
|
if (typeof handler !== 'function')
|
||||||
|
throw Error(`handler should be a function, given ${typeof handler}.`);
|
||||||
|
this._post(pattern, (res, req) => {
|
||||||
|
handleBody(res, req);
|
||||||
|
handler(res, req);
|
||||||
|
});
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
put(pattern: string, handler: Handler) {
|
||||||
|
if (typeof handler !== 'function')
|
||||||
|
throw Error(`handler should be a function, given ${typeof handler}.`);
|
||||||
|
this._put(pattern, (res, req) => {
|
||||||
|
handleBody(res, req);
|
||||||
|
|
||||||
|
handler(res, req);
|
||||||
|
});
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
patch(pattern: string, handler: Handler) {
|
||||||
|
if (typeof handler !== 'function')
|
||||||
|
throw Error(`handler should be a function, given ${typeof handler}.`);
|
||||||
|
this._patch(pattern, (res, req) => {
|
||||||
|
handleBody(res, req);
|
||||||
|
|
||||||
|
handler(res, req);
|
||||||
|
});
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
listen(h: string | number, p: Function | number = noOp, cb?: Function) {
|
||||||
|
if (typeof p === 'number' && typeof h === 'string') {
|
||||||
|
this._listen(h, p, socket => {
|
||||||
|
this._sockets.set(p, socket);
|
||||||
|
if (cb === undefined) {
|
||||||
|
throw new Error('cb undefined');
|
||||||
|
}
|
||||||
|
cb(socket);
|
||||||
|
});
|
||||||
|
} else if (typeof h === 'number' && typeof p === 'function') {
|
||||||
|
this._listen(h, socket => {
|
||||||
|
this._sockets.set(h, socket);
|
||||||
|
p(socket);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
throw Error(
|
||||||
|
'Argument types: (host: string, port: number, cb?: Function) | (port: number, cb?: Function)'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
close(port: null | number = null) {
|
||||||
|
if (port) {
|
||||||
|
this._sockets.has(port) && us_listen_socket_close(this._sockets.get(port));
|
||||||
|
this._sockets.delete(port);
|
||||||
|
} else {
|
||||||
|
this._sockets.forEach(app => {
|
||||||
|
us_listen_socket_close(app);
|
||||||
|
});
|
||||||
|
this._sockets.clear();
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default BaseApp;
|
100
back/src/Server/server/formdata.ts
Normal file
100
back/src/Server/server/formdata.ts
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
import { createWriteStream } from 'fs';
|
||||||
|
import { join, dirname } from 'path';
|
||||||
|
import Busboy from 'busboy';
|
||||||
|
import mkdirp from 'mkdirp';
|
||||||
|
|
||||||
|
function formData(
|
||||||
|
contType: string,
|
||||||
|
options: busboy.BusboyConfig & {
|
||||||
|
abortOnLimit?: boolean;
|
||||||
|
tmpDir?: string;
|
||||||
|
onFile?: (
|
||||||
|
fieldname: string,
|
||||||
|
file: NodeJS.ReadableStream,
|
||||||
|
filename: string,
|
||||||
|
encoding: string,
|
||||||
|
mimetype: string
|
||||||
|
) => string;
|
||||||
|
onField?: (fieldname: string, value: any) => void;
|
||||||
|
filename?: (oldName: string) => string;
|
||||||
|
} = {}
|
||||||
|
) {
|
||||||
|
console.log('Enter form data');
|
||||||
|
options.headers = {
|
||||||
|
'content-type': contType
|
||||||
|
};
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const busb = new Busboy(options);
|
||||||
|
const ret = {};
|
||||||
|
|
||||||
|
this.bodyStream().pipe(busb);
|
||||||
|
|
||||||
|
busb.on('limit', () => {
|
||||||
|
if (options.abortOnLimit) {
|
||||||
|
reject(Error('limit'));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
busb.on('file', function(fieldname, file, filename, encoding, mimetype) {
|
||||||
|
const value: { filePath: string|undefined, filename: string, encoding:string, mimetype: string } = {
|
||||||
|
filename,
|
||||||
|
encoding,
|
||||||
|
mimetype,
|
||||||
|
filePath: undefined
|
||||||
|
};
|
||||||
|
|
||||||
|
if (typeof options.tmpDir === 'string') {
|
||||||
|
if (typeof options.filename === 'function') filename = options.filename(filename);
|
||||||
|
const fileToSave = join(options.tmpDir, filename);
|
||||||
|
mkdirp(dirname(fileToSave));
|
||||||
|
|
||||||
|
file.pipe(createWriteStream(fileToSave));
|
||||||
|
value.filePath = fileToSave;
|
||||||
|
}
|
||||||
|
if (typeof options.onFile === 'function') {
|
||||||
|
value.filePath =
|
||||||
|
options.onFile(fieldname, file, filename, encoding, mimetype) || value.filePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
setRetValue(ret, fieldname, value);
|
||||||
|
});
|
||||||
|
|
||||||
|
busb.on('field', function(fieldname, value) {
|
||||||
|
if (typeof options.onField === 'function') options.onField(fieldname, value);
|
||||||
|
|
||||||
|
setRetValue(ret, fieldname, value);
|
||||||
|
});
|
||||||
|
|
||||||
|
busb.on('finish', function() {
|
||||||
|
resolve(ret);
|
||||||
|
});
|
||||||
|
|
||||||
|
busb.on('error', reject);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function setRetValue(
|
||||||
|
ret: { [x: string]: any },
|
||||||
|
fieldname: string,
|
||||||
|
value: { filename: string; encoding: string; mimetype: string; filePath?: string } | any
|
||||||
|
) {
|
||||||
|
if (fieldname.slice(-2) === '[]') {
|
||||||
|
fieldname = fieldname.slice(0, fieldname.length - 2);
|
||||||
|
if (Array.isArray(ret[fieldname])) {
|
||||||
|
ret[fieldname].push(value);
|
||||||
|
} else {
|
||||||
|
ret[fieldname] = [value];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (Array.isArray(ret[fieldname])) {
|
||||||
|
ret[fieldname].push(value);
|
||||||
|
} else if (ret[fieldname]) {
|
||||||
|
ret[fieldname] = [ret[fieldname], value];
|
||||||
|
} else {
|
||||||
|
ret[fieldname] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default formData;
|
13
back/src/Server/server/sslapp.ts
Normal file
13
back/src/Server/server/sslapp.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import { SSLApp as _SSLApp, AppOptions } from 'uWebSockets.js';
|
||||||
|
import BaseApp from './baseapp';
|
||||||
|
import { extend } from './utils';
|
||||||
|
import { UwsApp } from './types';
|
||||||
|
|
||||||
|
class SSLApp extends (<UwsApp>_SSLApp) {
|
||||||
|
constructor(options: AppOptions) {
|
||||||
|
super(options); // eslint-disable-line constructor-super
|
||||||
|
extend(this, new BaseApp());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SSLApp;
|
11
back/src/Server/server/types.ts
Normal file
11
back/src/Server/server/types.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import { AppOptions, TemplatedApp, HttpResponse, HttpRequest } from 'uWebSockets.js';
|
||||||
|
|
||||||
|
export type UwsApp = {
|
||||||
|
(options: AppOptions): TemplatedApp;
|
||||||
|
new (options: AppOptions): TemplatedApp;
|
||||||
|
prototype: TemplatedApp;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type Handler = (res: HttpResponse, req: HttpRequest) => void;
|
||||||
|
|
||||||
|
export {};
|
37
back/src/Server/server/utils.ts
Normal file
37
back/src/Server/server/utils.ts
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import { ReadStream } from 'fs';
|
||||||
|
|
||||||
|
function extend(who: any, from: any, overwrite = true) { // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||||
|
const ownProps = Object.getOwnPropertyNames(Object.getPrototypeOf(from)).concat(
|
||||||
|
Object.keys(from)
|
||||||
|
);
|
||||||
|
ownProps.forEach(prop => {
|
||||||
|
if (prop === 'constructor' || from[prop] === undefined) return;
|
||||||
|
if (who[prop] && overwrite) {
|
||||||
|
who[`_${prop}`] = who[prop];
|
||||||
|
}
|
||||||
|
if (typeof from[prop] === 'function') who[prop] = from[prop].bind(who);
|
||||||
|
else who[prop] = from[prop];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function stob(stream: ReadStream): Promise<Buffer> {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
const buffers: Buffer[] = [];
|
||||||
|
stream.on('data', buffers.push.bind(buffers));
|
||||||
|
|
||||||
|
stream.on('end', () => {
|
||||||
|
switch (buffers.length) {
|
||||||
|
case 0:
|
||||||
|
resolve(Buffer.allocUnsafe(0));
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
resolve(buffers[0]);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
resolve(Buffer.concat(buffers));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export { extend, stob };
|
19
back/src/Server/sifrr.server.ts
Normal file
19
back/src/Server/sifrr.server.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import { parse } from 'query-string';
|
||||||
|
import { HttpRequest } from 'uWebSockets.js';
|
||||||
|
import App from './server/app';
|
||||||
|
import SSLApp from './server/sslapp';
|
||||||
|
import * as types from './server/types';
|
||||||
|
|
||||||
|
const getQuery = (req: HttpRequest) => {
|
||||||
|
return parse(req.getQuery());
|
||||||
|
};
|
||||||
|
|
||||||
|
export { App, SSLApp, getQuery };
|
||||||
|
export * from './server/types';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
App,
|
||||||
|
SSLApp,
|
||||||
|
getQuery,
|
||||||
|
...types
|
||||||
|
};
|
55
back/src/Services/CpuTracker.ts
Normal file
55
back/src/Services/CpuTracker.ts
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
import {CPU_OVERHEAT_THRESHOLD} from "../Enum/EnvironmentVariable";
|
||||||
|
|
||||||
|
function secNSec2ms(secNSec: Array<number>|number) {
|
||||||
|
if (Array.isArray(secNSec)) {
|
||||||
|
return secNSec[0] * 1000 + secNSec[1] / 1000000;
|
||||||
|
}
|
||||||
|
return secNSec / 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
class CpuTracker {
|
||||||
|
private cpuPercent: number = 0;
|
||||||
|
private overHeating: boolean = false;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
let time = process.hrtime.bigint()
|
||||||
|
let usage = process.cpuUsage()
|
||||||
|
setInterval(() => {
|
||||||
|
const elapTime = process.hrtime.bigint();
|
||||||
|
const elapUsage = process.cpuUsage(usage)
|
||||||
|
usage = process.cpuUsage()
|
||||||
|
|
||||||
|
const elapTimeMS = elapTime - time;
|
||||||
|
const elapUserMS = secNSec2ms(elapUsage.user)
|
||||||
|
const elapSystMS = secNSec2ms(elapUsage.system)
|
||||||
|
this.cpuPercent = Math.round(100 * (elapUserMS + elapSystMS) / Number(elapTimeMS) * 1000000)
|
||||||
|
|
||||||
|
time = elapTime;
|
||||||
|
|
||||||
|
if (!this.overHeating && this.cpuPercent > CPU_OVERHEAT_THRESHOLD) {
|
||||||
|
this.overHeating = true;
|
||||||
|
console.warn('CPU high threshold alert. Going in "overheat" mode');
|
||||||
|
} else if (this.overHeating && this.cpuPercent <= CPU_OVERHEAT_THRESHOLD) {
|
||||||
|
this.overHeating = false;
|
||||||
|
console.log('CPU is back to normal. Canceling "overheat" mode');
|
||||||
|
}
|
||||||
|
|
||||||
|
/*console.log('elapsed time ms: ', elapTimeMS)
|
||||||
|
console.log('elapsed user ms: ', elapUserMS)
|
||||||
|
console.log('elapsed system ms:', elapSystMS)
|
||||||
|
console.log('cpu percent: ', this.cpuPercent)*/
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
public getCpuPercent(): number {
|
||||||
|
return this.cpuPercent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public isOverHeating(): boolean {
|
||||||
|
return this.overHeating;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const cpuTracker = new CpuTracker();
|
||||||
|
|
||||||
|
export { cpuTracker };
|
@ -8,15 +8,8 @@ import {PointInterface} from "../src/Model/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 {ExSocketInterface} from "_Model/Websocket/ExSocketInterface";
|
||||||
|
|
||||||
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 +25,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, {} as ExSocketInterface);
|
||||||
|
|
||||||
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, {} as ExSocketInterface);
|
||||||
|
|
||||||
positionNotifier.setViewport(user1, {
|
positionNotifier.setViewport(user1, {
|
||||||
left: 200,
|
left: 200,
|
||||||
@ -53,21 +46,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 +68,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 +103,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, {} as ExSocketInterface);
|
||||||
|
|
||||||
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, {} as ExSocketInterface);
|
||||||
|
|
||||||
let newUsers = positionNotifier.setViewport(user1, {
|
let newUsers = positionNotifier.setViewport(user1, {
|
||||||
left: 200,
|
left: 200,
|
||||||
@ -131,14 +124,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 +171,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);
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
|
@ -2,59 +2,67 @@ import "jasmine";
|
|||||||
import {World, ConnectCallback, DisconnectCallback } from "../src/Model/World";
|
import {World, ConnectCallback, DisconnectCallback } from "../src/Model/World";
|
||||||
import {Point} from "../src/Model/Websocket/MessageUserPosition";
|
import {Point} from "../src/Model/Websocket/MessageUserPosition";
|
||||||
import { Group } from "../src/Model/Group";
|
import { Group } from "../src/Model/Group";
|
||||||
|
import {ExSocketInterface} from "_Model/Websocket/ExSocketInterface";
|
||||||
|
import {User} from "_Model/User";
|
||||||
|
|
||||||
|
function createMockUser(userId: number): ExSocketInterface {
|
||||||
|
return {
|
||||||
|
userId
|
||||||
|
} as ExSocketInterface;
|
||||||
|
}
|
||||||
|
|
||||||
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: User, group: Group): void => {
|
||||||
connectCalledNumber++;
|
connectCalledNumber++;
|
||||||
}
|
}
|
||||||
const disconnect: DisconnectCallback = (user: string, group: Group): void => {
|
const disconnect: DisconnectCallback = (user: User, 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(createMockUser(1), new Point(100, 100));
|
||||||
|
|
||||||
world.join({ userId: "bar" }, new Point(500, 100));
|
world.join(createMockUser(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: User, group: Group): void => {
|
||||||
connectCalled = true;
|
connectCalled = true;
|
||||||
}
|
}
|
||||||
const disconnect: DisconnectCallback = (user: string, group: Group): void => {
|
const disconnect: DisconnectCallback = (user: User, 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(createMockUser(1), new Point(100, 100));
|
||||||
|
|
||||||
world.join({ userId: "bar" }, new Point(200, 100));
|
world.join(createMockUser(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(createMockUser(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 +70,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: User, group: Group): void => {
|
||||||
connectCalled = true;
|
connectCalled = true;
|
||||||
}
|
}
|
||||||
const disconnect: DisconnectCallback = (user: string, group: Group): void => {
|
const disconnect: DisconnectCallback = (user: User, 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(createMockUser(1), new Point(100, 100));
|
||||||
|
|
||||||
world.join({ userId: "bar" }, new Point(259, 100));
|
world.join(createMockUser(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);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -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. */
|
||||||
@ -31,7 +31,7 @@
|
|||||||
// "strictFunctionTypes": true, /* Enable strict checking of function types. */
|
// "strictFunctionTypes": true, /* Enable strict checking of function types. */
|
||||||
// "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
|
// "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
|
||||||
// "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
|
// "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
|
||||||
// "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
|
"noImplicitThis": false, /* Raise error on 'this' expressions with an implied 'any' type. */ // Disabled because of sifrr server that is monkey patching HttpResponse
|
||||||
// "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
|
// "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
|
||||||
|
|
||||||
/* Additional Checks */
|
/* Additional Checks */
|
||||||
|
1821
back/yarn.lock
1821
back/yarn.lock
File diff suppressed because it is too large
Load Diff
3
benchmark/.gitignore
vendored
Normal file
3
benchmark/.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
/node_modules/
|
||||||
|
/artillery_output.html
|
||||||
|
/artillery_output.json
|
@ -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
|
|
||||||
|
|
||||||
|
|
15
benchmark/benchmark_multi_core.sh
Executable file
15
benchmark/benchmark_multi_core.sh
Executable 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
|
56
benchmark/index.ts
Normal file
56
benchmark/index.ts
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
import {RoomConnection} from "../front/src/Connexion/RoomConnection";
|
||||||
|
import {connectionManager} from "../front/src/Connexion/ConnectionManager";
|
||||||
|
import * as WebSocket from "ws"
|
||||||
|
|
||||||
|
function sleep(ms) {
|
||||||
|
return new Promise(resolve => setTimeout(resolve, ms));
|
||||||
|
}
|
||||||
|
|
||||||
|
RoomConnection.setWebsocketFactory((url: string) => {
|
||||||
|
return new WebSocket(url);
|
||||||
|
});
|
||||||
|
|
||||||
|
async function startOneUser(): Promise<void> {
|
||||||
|
const connection = await connectionManager.connectToRoomSocket();
|
||||||
|
connection.emitPlayerDetailsMessage('foo', ['male3']);
|
||||||
|
|
||||||
|
await connection.joinARoom('global__maps.workadventure.localhost/Floor0/floor0', 783, 170, 'down', true, {
|
||||||
|
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 () => {
|
||||||
|
connectionManager.initBenchmark();
|
||||||
|
|
||||||
|
|
||||||
|
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
1970
benchmark/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -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,10 @@
|
|||||||
],
|
],
|
||||||
"license": "SEE LICENSE IN LICENSE.txt",
|
"license": "SEE LICENSE IN LICENSE.txt",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"artillery": "^1.6.1"
|
"@types/ws": "^7.2.6",
|
||||||
}
|
"ts-node-dev": "^1.0.0-pre.62",
|
||||||
|
"typescript": "^4.0.2",
|
||||||
|
"ws": "^7.3.1"
|
||||||
|
},
|
||||||
|
"devDependencies": {}
|
||||||
}
|
}
|
||||||
|
@ -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
|
|
@ -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();
|
|
||||||
}
|
|
528
benchmark/yarn.lock
Normal file
528
benchmark/yarn.lock
Normal file
@ -0,0 +1,528 @@
|
|||||||
|
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||||
|
# yarn lockfile v1
|
||||||
|
|
||||||
|
|
||||||
|
"@types/node@*":
|
||||||
|
version "14.11.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/node/-/node-14.11.2.tgz#2de1ed6670439387da1c9f549a2ade2b0a799256"
|
||||||
|
|
||||||
|
"@types/strip-bom@^3.0.0":
|
||||||
|
version "3.0.0"
|
||||||
|
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"
|
||||||
|
|
||||||
|
"@types/ws@^7.2.6":
|
||||||
|
version "7.2.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/ws/-/ws-7.2.6.tgz#516cbfb818310f87b43940460e065eb912a4178d"
|
||||||
|
dependencies:
|
||||||
|
"@types/node" "*"
|
||||||
|
|
||||||
|
anymatch@~3.1.1:
|
||||||
|
version "3.1.1"
|
||||||
|
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"
|
||||||
|
|
||||||
|
balanced-match@^1.0.0:
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
|
||||||
|
|
||||||
|
binary-extensions@^2.0.0:
|
||||||
|
version "2.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.1.0.tgz#30fa40c9e7fe07dbc895678cd287024dea241dd9"
|
||||||
|
|
||||||
|
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"
|
||||||
|
|
||||||
|
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"
|
||||||
|
|
||||||
|
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"
|
||||||
|
|
||||||
|
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"
|
||||||
|
|
||||||
|
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"
|
||||||
|
|
||||||
|
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"
|
||||||
|
|
||||||
|
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"
|
||||||
|
|
||||||
|
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"
|
||||||
|
|
||||||
|
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"
|
||||||
|
|
||||||
|
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"
|
||||||
|
|
||||||
|
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"
|
||||||
|
|
||||||
|
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-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@^7.3.1:
|
||||||
|
version "7.3.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/ws/-/ws-7.3.1.tgz#d0547bf67f7ce4f12a72dfe31262c68d7dc551c8"
|
||||||
|
|
||||||
|
xtend@^4.0.0:
|
||||||
|
version "4.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"
|
||||||
|
|
||||||
|
yn@3.1.1:
|
||||||
|
version "3.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50"
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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",
|
||||||
"quill": "1.3.6",
|
"quill": "1.3.6",
|
||||||
|
@ -3,8 +3,9 @@ import {GameScene} from "../Phaser/Game/GameScene";
|
|||||||
const Quill = require("quill");
|
const Quill = require("quill");
|
||||||
|
|
||||||
import {HtmlUtils} from "../WebRtc/HtmlUtils";
|
import {HtmlUtils} from "../WebRtc/HtmlUtils";
|
||||||
import {Connection, GlobalMessageInterface} from "../Connection";
|
|
||||||
import {UserInputManager} from "../Phaser/UserInput/UserInputManager";
|
import {UserInputManager} from "../Phaser/UserInput/UserInputManager";
|
||||||
|
import {RoomConnection} from "../Connexion/RoomConnection";
|
||||||
|
import {PlayGlobalMessageInterface} from "../Connexion/ConnexionModels";
|
||||||
|
|
||||||
export const CLASS_CONSOLE_MESSAGE = 'main-console';
|
export const CLASS_CONSOLE_MESSAGE = 'main-console';
|
||||||
export const INPUT_CONSOLE_MESSAGE = 'input-send-text';
|
export const INPUT_CONSOLE_MESSAGE = 'input-send-text';
|
||||||
@ -21,14 +22,12 @@ interface EventTargetFiles extends EventTarget {
|
|||||||
|
|
||||||
export class ConsoleGlobalMessageManager {
|
export class ConsoleGlobalMessageManager {
|
||||||
|
|
||||||
private Connection: Connection;
|
|
||||||
private divMainConsole: HTMLDivElement;
|
private divMainConsole: HTMLDivElement;
|
||||||
private buttonMainConsole: HTMLDivElement;
|
private buttonMainConsole: HTMLDivElement;
|
||||||
private activeConsole: boolean = false;
|
private activeConsole: boolean = false;
|
||||||
private userInputManager!: UserInputManager;
|
private userInputManager!: UserInputManager;
|
||||||
|
|
||||||
constructor(Connection: Connection, userInputManager : UserInputManager) {
|
constructor(private Connection: RoomConnection, userInputManager : UserInputManager) {
|
||||||
this.Connection = Connection;
|
|
||||||
this.buttonMainConsole = document.createElement('div');
|
this.buttonMainConsole = document.createElement('div');
|
||||||
this.buttonMainConsole.classList.add('console');
|
this.buttonMainConsole.classList.add('console');
|
||||||
this.divMainConsole = document.createElement('div');
|
this.divMainConsole = document.createElement('div');
|
||||||
@ -240,8 +239,8 @@ export class ConsoleGlobalMessageManager {
|
|||||||
if(!quillEditor){
|
if(!quillEditor){
|
||||||
throw "Error get quill node";
|
throw "Error get quill node";
|
||||||
}
|
}
|
||||||
const GlobalMessage : GlobalMessageInterface = {
|
const GlobalMessage : PlayGlobalMessageInterface = {
|
||||||
id: 1,
|
id: "1", // FIXME: use another ID?
|
||||||
message: quillEditor.innerHTML,
|
message: quillEditor.innerHTML,
|
||||||
type: MESSAGE_TYPE
|
type: MESSAGE_TYPE
|
||||||
};
|
};
|
||||||
@ -260,8 +259,8 @@ export class ConsoleGlobalMessageManager {
|
|||||||
fd.append('file', selectedFile);
|
fd.append('file', selectedFile);
|
||||||
const res = await this.Connection.uploadAudio(fd);
|
const res = await this.Connection.uploadAudio(fd);
|
||||||
|
|
||||||
const GlobalMessage : GlobalMessageInterface = {
|
const GlobalMessage : PlayGlobalMessageInterface = {
|
||||||
id: (res as {id: number}).id,
|
id: (res as {id: string}).id,
|
||||||
message: (res as {path: string}).path,
|
message: (res as {path: string}).path,
|
||||||
type: AUDIO_TYPE
|
type: AUDIO_TYPE
|
||||||
};
|
};
|
||||||
|
@ -1,30 +1,28 @@
|
|||||||
import {HtmlUtils} from "./../WebRtc/HtmlUtils";
|
import {HtmlUtils} from "./../WebRtc/HtmlUtils";
|
||||||
import {Connection, GlobalMessageInterface} from "../Connection";
|
|
||||||
import {AUDIO_TYPE, MESSAGE_TYPE} from "./ConsoleGlobalMessageManager";
|
import {AUDIO_TYPE, MESSAGE_TYPE} from "./ConsoleGlobalMessageManager";
|
||||||
import {API_URL} from "../Enum/EnvironmentVariable";
|
import {API_URL} from "../Enum/EnvironmentVariable";
|
||||||
|
import {RoomConnection} from "../Connexion/RoomConnection";
|
||||||
|
import {PlayGlobalMessageInterface} from "../Connexion/ConnexionModels";
|
||||||
|
|
||||||
export class GlobalMessageManager {
|
export class GlobalMessageManager {
|
||||||
|
|
||||||
private Connection: Connection;
|
constructor(private Connection: RoomConnection) {
|
||||||
|
|
||||||
constructor(Connection: Connection) {
|
|
||||||
this.Connection = Connection;
|
|
||||||
this.initialise();
|
this.initialise();
|
||||||
}
|
}
|
||||||
|
|
||||||
initialise(){
|
initialise(){
|
||||||
//receive signal to show message
|
//receive signal to show message
|
||||||
this.Connection.receivePlayGlobalMessage((message: GlobalMessageInterface) => {
|
this.Connection.receivePlayGlobalMessage((message: PlayGlobalMessageInterface) => {
|
||||||
this.playMessage(message);
|
this.playMessage(message);
|
||||||
});
|
});
|
||||||
|
|
||||||
//receive signal to close message
|
//receive signal to close message
|
||||||
this.Connection.receiveStopGlobalMessage((message: GlobalMessageInterface) => {
|
this.Connection.receiveStopGlobalMessage((messageId: string) => {
|
||||||
this.stopMessage(message.id);
|
this.stopMessage(messageId);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private playMessage(message : GlobalMessageInterface){
|
private playMessage(message : PlayGlobalMessageInterface){
|
||||||
const previousMessage = document.getElementById(this.getHtmlMessageId(message.id));
|
const previousMessage = document.getElementById(this.getHtmlMessageId(message.id));
|
||||||
if(previousMessage){
|
if(previousMessage){
|
||||||
previousMessage.remove();
|
previousMessage.remove();
|
||||||
@ -39,7 +37,7 @@ export class GlobalMessageManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private playAudioMessage(messageId : number, urlMessage: string){
|
private playAudioMessage(messageId : string, urlMessage: string){
|
||||||
//delete previous elements
|
//delete previous elements
|
||||||
const previousDivAudio = document.getElementsByClassName('audio-playing');
|
const previousDivAudio = document.getElementsByClassName('audio-playing');
|
||||||
for(let i = 0; i < previousDivAudio.length; i++){
|
for(let i = 0; i < previousDivAudio.length; i++){
|
||||||
@ -80,7 +78,7 @@ export class GlobalMessageManager {
|
|||||||
mainSectionDiv.appendChild(messageAudio);
|
mainSectionDiv.appendChild(messageAudio);
|
||||||
}
|
}
|
||||||
|
|
||||||
private playTextMessage(messageId : number, htmlMessage: string){
|
private playTextMessage(messageId : string, htmlMessage: string){
|
||||||
//add button to clear message
|
//add button to clear message
|
||||||
const buttonText = document.createElement('p');
|
const buttonText = document.createElement('p');
|
||||||
buttonText.id = 'button-clear-message';
|
buttonText.id = 'button-clear-message';
|
||||||
@ -113,12 +111,11 @@ export class GlobalMessageManager {
|
|||||||
mainSectionDiv.appendChild(messageContainer);
|
mainSectionDiv.appendChild(messageContainer);
|
||||||
}
|
}
|
||||||
|
|
||||||
private stopMessage(messageId: number){
|
private stopMessage(messageId: string){
|
||||||
HtmlUtils.removeElementByIdOrFail<HTMLDivElement>(this.getHtmlMessageId(messageId));
|
HtmlUtils.removeElementByIdOrFail<HTMLDivElement>(this.getHtmlMessageId(messageId));
|
||||||
}
|
}
|
||||||
|
|
||||||
private getHtmlMessageId(messageId: number) : string{
|
private getHtmlMessageId(messageId: string) : string{
|
||||||
return `message-${messageId}`;
|
return `message-${messageId}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,338 +0,0 @@
|
|||||||
import Axios from "axios";
|
|
||||||
import {API_URL} from "./Enum/EnvironmentVariable";
|
|
||||||
import {SetPlayerDetailsMessage} from "./Messages/SetPlayerDetailsMessage";
|
|
||||||
|
|
||||||
const SocketIo = require('socket.io-client');
|
|
||||||
import Socket = SocketIOClient.Socket;
|
|
||||||
import {PlayerAnimationNames} from "./Phaser/Player/Animation";
|
|
||||||
import {UserSimplePeerInterface} from "./WebRtc/SimplePeer";
|
|
||||||
import {SignalData} from "simple-peer";
|
|
||||||
|
|
||||||
enum EventMessage{
|
|
||||||
WEBRTC_SIGNAL = "webrtc-signal",
|
|
||||||
WEBRTC_SCREEN_SHARING_SIGNAL = "webrtc-screen-sharing-signal",
|
|
||||||
WEBRTC_START = "webrtc-start",
|
|
||||||
JOIN_ROOM = "join-room", // bi-directional
|
|
||||||
USER_POSITION = "user-position", // From client to server
|
|
||||||
USER_MOVED = "user-moved", // From server to client
|
|
||||||
USER_LEFT = "user-left", // From server to client
|
|
||||||
MESSAGE_ERROR = "message-error",
|
|
||||||
WEBRTC_DISCONNECT = "webrtc-disconect",
|
|
||||||
GROUP_CREATE_UPDATE = "group-create-update",
|
|
||||||
GROUP_DELETE = "group-delete",
|
|
||||||
SET_PLAYER_DETAILS = "set-player-details", // Send the name and character to the server (on connect), receive back the id.
|
|
||||||
ITEM_EVENT = 'item-event',
|
|
||||||
|
|
||||||
CONNECT_ERROR = "connect_error",
|
|
||||||
SET_SILENT = "set_silent", // Set or unset the silent mode for this user.
|
|
||||||
SET_VIEWPORT = "set-viewport",
|
|
||||||
BATCH = "batch",
|
|
||||||
|
|
||||||
PLAY_GLOBAL_MESSAGE = "play-global-message",
|
|
||||||
STOP_GLOBAL_MESSAGE = "stop-global-message",
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface PointInterface {
|
|
||||||
x: number;
|
|
||||||
y: number;
|
|
||||||
direction : string;
|
|
||||||
moving: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Point implements PointInterface{
|
|
||||||
constructor(public x : number, public y : number, public direction : string = PlayerAnimationNames.WalkDown, public moving : boolean = false) {
|
|
||||||
if(x === null || y === null){
|
|
||||||
throw Error("position x and y cannot be null");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface MessageUserPositionInterface {
|
|
||||||
userId: string;
|
|
||||||
name: string;
|
|
||||||
characterLayers: string[];
|
|
||||||
position: PointInterface;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface MessageUserMovedInterface {
|
|
||||||
userId: string;
|
|
||||||
position: PointInterface;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface MessageUserJoined {
|
|
||||||
userId: string;
|
|
||||||
name: string;
|
|
||||||
characterLayers: string[];
|
|
||||||
position: PointInterface
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface PositionInterface {
|
|
||||||
x: number,
|
|
||||||
y: number
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface GroupCreatedUpdatedMessageInterface {
|
|
||||||
position: PositionInterface,
|
|
||||||
groupId: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface WebRtcStartMessageInterface {
|
|
||||||
roomId: string,
|
|
||||||
clients: UserSimplePeerInterface[]
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface WebRtcDisconnectMessageInterface {
|
|
||||||
userId: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface WebRtcSignalSentMessageInterface {
|
|
||||||
receiverId: string,
|
|
||||||
signal: SignalData
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface WebRtcSignalReceivedMessageInterface {
|
|
||||||
userId: string,
|
|
||||||
signal: SignalData
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface StartMapInterface {
|
|
||||||
mapUrlStart: string,
|
|
||||||
startInstance: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ViewportInterface {
|
|
||||||
left: number,
|
|
||||||
top: number,
|
|
||||||
right: number,
|
|
||||||
bottom: number,
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface UserMovesInterface {
|
|
||||||
position: PositionInterface,
|
|
||||||
viewport: ViewportInterface,
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface BatchedMessageInterface {
|
|
||||||
event: string,
|
|
||||||
payload: unknown
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ItemEventMessageInterface {
|
|
||||||
itemId: number,
|
|
||||||
event: string,
|
|
||||||
state: unknown,
|
|
||||||
parameters: unknown
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface RoomJoinedMessageInterface {
|
|
||||||
users: MessageUserPositionInterface[],
|
|
||||||
groups: GroupCreatedUpdatedMessageInterface[],
|
|
||||||
items: { [itemId: number] : unknown }
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface GlobalMessageInterface {
|
|
||||||
id: number
|
|
||||||
type: string
|
|
||||||
message: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Connection implements Connection {
|
|
||||||
private readonly socket: Socket;
|
|
||||||
private userId: string|null = null;
|
|
||||||
|
|
||||||
private constructor(token: string) {
|
|
||||||
|
|
||||||
this.socket = SocketIo(`${API_URL}`, {
|
|
||||||
query: {
|
|
||||||
token: token
|
|
||||||
},
|
|
||||||
reconnection: false // Reconnection is handled by the application itself
|
|
||||||
});
|
|
||||||
|
|
||||||
this.socket.on(EventMessage.MESSAGE_ERROR, (message: string) => {
|
|
||||||
console.error(EventMessage.MESSAGE_ERROR, message);
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Messages inside batched messages are extracted and sent to listeners directly.
|
|
||||||
*/
|
|
||||||
this.socket.on(EventMessage.BATCH, (batchedMessages: BatchedMessageInterface[]) => {
|
|
||||||
for (const message of batchedMessages) {
|
|
||||||
const listeners = this.socket.listeners(message.event);
|
|
||||||
for (const listener of listeners) {
|
|
||||||
listener(message.payload);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
public static createConnection(name: string, characterLayersSelected: string[]): Promise<Connection> {
|
|
||||||
return Axios.post(`${API_URL}/login`, {name: name})
|
|
||||||
.then((res) => {
|
|
||||||
|
|
||||||
return new Promise<Connection>((resolve, reject) => {
|
|
||||||
const connection = new Connection(res.data.token);
|
|
||||||
|
|
||||||
connection.onConnectError((error: object) => {
|
|
||||||
console.log('An error occurred while connecting to socket server. Retrying');
|
|
||||||
reject(error);
|
|
||||||
});
|
|
||||||
|
|
||||||
connection.socket.emit(EventMessage.SET_PLAYER_DETAILS, {
|
|
||||||
name: name,
|
|
||||||
characterLayers: characterLayersSelected
|
|
||||||
} as SetPlayerDetailsMessage, (id: string) => {
|
|
||||||
connection.userId = id;
|
|
||||||
});
|
|
||||||
|
|
||||||
resolve(connection);
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
// Let's retry in 4-6 seconds
|
|
||||||
return new Promise<Connection>((resolve, reject) => {
|
|
||||||
setTimeout(() => {
|
|
||||||
Connection.createConnection(name, characterLayersSelected).then((connection) => resolve(connection))
|
|
||||||
.catch((error) => reject(error));
|
|
||||||
}, 4000 + Math.floor(Math.random() * 2000) );
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public uploadAudio(file : FormData){
|
|
||||||
return Axios.post(`${API_URL}/upload-audio-message`, file).then((res: {data:{}}) => {
|
|
||||||
return res.data;
|
|
||||||
}).catch((err) => {
|
|
||||||
console.error(err);
|
|
||||||
throw err;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public closeConnection(): void {
|
|
||||||
this.socket?.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public joinARoom(roomId: string, startX: number, startY: number, direction: string, moving: boolean, viewport: ViewportInterface): Promise<RoomJoinedMessageInterface> {
|
|
||||||
const promise = new Promise<RoomJoinedMessageInterface>((resolve, reject) => {
|
|
||||||
this.socket.emit(EventMessage.JOIN_ROOM, {
|
|
||||||
roomId,
|
|
||||||
position: {x: startX, y: startY, direction, moving },
|
|
||||||
viewport,
|
|
||||||
}, (roomJoinedMessage: RoomJoinedMessageInterface) => {
|
|
||||||
resolve(roomJoinedMessage);
|
|
||||||
});
|
|
||||||
})
|
|
||||||
return promise;
|
|
||||||
}
|
|
||||||
|
|
||||||
public sharePosition(x : number, y : number, direction : string, moving: boolean, viewport: ViewportInterface) : void{
|
|
||||||
if(!this.socket){
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const point = new Point(x, y, direction, moving);
|
|
||||||
this.socket.emit(EventMessage.USER_POSITION, { position: point, viewport } as UserMovesInterface);
|
|
||||||
}
|
|
||||||
|
|
||||||
public setSilent(silent: boolean): void {
|
|
||||||
this.socket.emit(EventMessage.SET_SILENT, silent);
|
|
||||||
}
|
|
||||||
|
|
||||||
public setViewport(viewport: ViewportInterface): void {
|
|
||||||
this.socket.emit(EventMessage.SET_VIEWPORT, viewport);
|
|
||||||
}
|
|
||||||
|
|
||||||
public onUserJoins(callback: (message: MessageUserJoined) => void): void {
|
|
||||||
this.socket.on(EventMessage.JOIN_ROOM, callback);
|
|
||||||
}
|
|
||||||
|
|
||||||
public onUserMoved(callback: (message: MessageUserMovedInterface) => void): void {
|
|
||||||
this.socket.on(EventMessage.USER_MOVED, callback);
|
|
||||||
}
|
|
||||||
|
|
||||||
public onUserLeft(callback: (userId: string) => void): void {
|
|
||||||
this.socket.on(EventMessage.USER_LEFT, callback);
|
|
||||||
}
|
|
||||||
|
|
||||||
public onGroupUpdatedOrCreated(callback: (groupCreateUpdateMessage: GroupCreatedUpdatedMessageInterface) => void): void {
|
|
||||||
this.socket.on(EventMessage.GROUP_CREATE_UPDATE, callback);
|
|
||||||
}
|
|
||||||
|
|
||||||
public onGroupDeleted(callback: (groupId: string) => void): void {
|
|
||||||
this.socket.on(EventMessage.GROUP_DELETE, callback)
|
|
||||||
}
|
|
||||||
|
|
||||||
public onConnectError(callback: (error: object) => void): void {
|
|
||||||
this.socket.on(EventMessage.CONNECT_ERROR, callback)
|
|
||||||
}
|
|
||||||
|
|
||||||
public sendWebrtcSignal(signal: unknown, receiverId : string) {
|
|
||||||
return this.socket.emit(EventMessage.WEBRTC_SIGNAL, {
|
|
||||||
receiverId: receiverId,
|
|
||||||
signal: signal
|
|
||||||
} as WebRtcSignalSentMessageInterface);
|
|
||||||
}
|
|
||||||
|
|
||||||
public sendWebrtcScreenSharingSignal(signal: unknown, receiverId : string) {
|
|
||||||
return this.socket.emit(EventMessage.WEBRTC_SCREEN_SHARING_SIGNAL, {
|
|
||||||
receiverId: receiverId,
|
|
||||||
signal: signal
|
|
||||||
} as WebRtcSignalSentMessageInterface);
|
|
||||||
}
|
|
||||||
|
|
||||||
public receiveWebrtcStart(callback: (message: WebRtcStartMessageInterface) => void) {
|
|
||||||
this.socket.on(EventMessage.WEBRTC_START, callback);
|
|
||||||
}
|
|
||||||
|
|
||||||
public receiveWebrtcSignal(callback: (message: WebRtcSignalReceivedMessageInterface) => void) {
|
|
||||||
return this.socket.on(EventMessage.WEBRTC_SIGNAL, callback);
|
|
||||||
}
|
|
||||||
|
|
||||||
public receiveWebrtcScreenSharingSignal(callback: (message: WebRtcSignalReceivedMessageInterface) => void) {
|
|
||||||
return this.socket.on(EventMessage.WEBRTC_SCREEN_SHARING_SIGNAL, callback);
|
|
||||||
}
|
|
||||||
|
|
||||||
public onServerDisconnected(callback: (reason: string) => void): void {
|
|
||||||
this.socket.on('disconnect', (reason: string) => {
|
|
||||||
if (reason === 'io client disconnect') {
|
|
||||||
// The client asks for disconnect, let's not trigger any event.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
callback(reason);
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public getUserId(): string|null {
|
|
||||||
return this.userId;
|
|
||||||
}
|
|
||||||
|
|
||||||
disconnectMessage(callback: (message: WebRtcDisconnectMessageInterface) => void): void {
|
|
||||||
this.socket.on(EventMessage.WEBRTC_DISCONNECT, callback);
|
|
||||||
}
|
|
||||||
|
|
||||||
emitActionableEvent(itemId: number, event: string, state: unknown, parameters: unknown) {
|
|
||||||
return this.socket.emit(EventMessage.ITEM_EVENT, {
|
|
||||||
itemId,
|
|
||||||
event,
|
|
||||||
state,
|
|
||||||
parameters
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
onActionableEvent(callback: (message: ItemEventMessageInterface) => void): void {
|
|
||||||
this.socket.on(EventMessage.ITEM_EVENT, callback);
|
|
||||||
}
|
|
||||||
|
|
||||||
public receivePlayGlobalMessage(callback: (message: GlobalMessageInterface) => void) {
|
|
||||||
return this.socket.on(EventMessage.PLAY_GLOBAL_MESSAGE, callback);
|
|
||||||
}
|
|
||||||
|
|
||||||
public receiveStopGlobalMessage(callback: (message: GlobalMessageInterface) => void) {
|
|
||||||
return this.socket.on(EventMessage.STOP_GLOBAL_MESSAGE, callback);
|
|
||||||
}
|
|
||||||
|
|
||||||
public emitGlobalMessage(message: GlobalMessageInterface){
|
|
||||||
return this.socket.emit(EventMessage.PLAY_GLOBAL_MESSAGE, message);
|
|
||||||
}
|
|
||||||
}
|
|
69
front/src/Connexion/ConnectionManager.ts
Normal file
69
front/src/Connexion/ConnectionManager.ts
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
import Axios from "axios";
|
||||||
|
import {API_URL} from "../Enum/EnvironmentVariable";
|
||||||
|
import {RoomConnection} from "./RoomConnection";
|
||||||
|
|
||||||
|
interface LoginApiData {
|
||||||
|
authToken: string
|
||||||
|
userUuid: string
|
||||||
|
mapUrlStart: string
|
||||||
|
newUrl: string
|
||||||
|
}
|
||||||
|
|
||||||
|
class ConnectionManager {
|
||||||
|
private initPromise: Promise<LoginApiData> = Promise.reject();
|
||||||
|
private mapUrlStart: string|null = null;
|
||||||
|
|
||||||
|
private authToken:string|null = null;
|
||||||
|
private userUuid: string|null = null;
|
||||||
|
|
||||||
|
public async init(): Promise<void> {
|
||||||
|
const match = /\/register\/(.+)/.exec(window.location.toString());
|
||||||
|
const organizationMemberToken = match ? match[1] : null;
|
||||||
|
this.initPromise = Axios.post(`${API_URL}/login`, {organizationMemberToken}).then(res => res.data);
|
||||||
|
const data = await this.initPromise
|
||||||
|
this.authToken = data.authToken;
|
||||||
|
this.userUuid = data.userUuid;
|
||||||
|
this.mapUrlStart = data.mapUrlStart;
|
||||||
|
const newUrl = data.newUrl;
|
||||||
|
|
||||||
|
if (newUrl) {
|
||||||
|
history.pushState({}, '', newUrl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public initBenchmark(): void {
|
||||||
|
this.authToken = 'test';
|
||||||
|
}
|
||||||
|
|
||||||
|
public connectToRoomSocket(): Promise<RoomConnection> {
|
||||||
|
return new Promise<RoomConnection>((resolve, reject) => {
|
||||||
|
const connection = new RoomConnection(this.authToken as string);
|
||||||
|
connection.onConnectError((error: object) => {
|
||||||
|
console.log('An error occurred while connecting to socket server. Retrying');
|
||||||
|
reject(error);
|
||||||
|
});
|
||||||
|
connection.onConnect(() => {
|
||||||
|
resolve(connection);
|
||||||
|
})
|
||||||
|
}).catch((err) => {
|
||||||
|
// Let's retry in 4-6 seconds
|
||||||
|
return new Promise<RoomConnection>((resolve, reject) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
//todo: allow a way to break recurrsion?
|
||||||
|
this.connectToRoomSocket().then((connection) => resolve(connection));
|
||||||
|
}, 4000 + Math.floor(Math.random() * 2000) );
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public getMapUrlStart(): Promise<string> {
|
||||||
|
return this.initPromise.then(() => {
|
||||||
|
if (!this.mapUrlStart) {
|
||||||
|
throw new Error('No map url set!');
|
||||||
|
}
|
||||||
|
return this.mapUrlStart;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const connectionManager = new ConnectionManager();
|
126
front/src/Connexion/ConnexionModels.ts
Normal file
126
front/src/Connexion/ConnexionModels.ts
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
import {PlayerAnimationNames} from "../Phaser/Player/Animation";
|
||||||
|
import {UserSimplePeerInterface} from "../WebRtc/SimplePeer";
|
||||||
|
import {SignalData} from "simple-peer";
|
||||||
|
|
||||||
|
export enum EventMessage{
|
||||||
|
WEBRTC_SIGNAL = "webrtc-signal",
|
||||||
|
WEBRTC_SCREEN_SHARING_SIGNAL = "webrtc-screen-sharing-signal",
|
||||||
|
WEBRTC_START = "webrtc-start",
|
||||||
|
JOIN_ROOM = "join-room", // bi-directional
|
||||||
|
USER_POSITION = "user-position", // From client to server
|
||||||
|
USER_MOVED = "user-moved", // From server to client
|
||||||
|
USER_LEFT = "user-left", // From server to client
|
||||||
|
MESSAGE_ERROR = "message-error",
|
||||||
|
WEBRTC_DISCONNECT = "webrtc-disconect",
|
||||||
|
GROUP_CREATE_UPDATE = "group-create-update",
|
||||||
|
GROUP_DELETE = "group-delete",
|
||||||
|
SET_PLAYER_DETAILS = "set-player-details", // Send the name and character to the server (on connect), receive back the id.
|
||||||
|
ITEM_EVENT = 'item-event',
|
||||||
|
|
||||||
|
CONNECT_ERROR = "connect_error",
|
||||||
|
SET_SILENT = "set_silent", // Set or unset the silent mode for this user.
|
||||||
|
SET_VIEWPORT = "set-viewport",
|
||||||
|
BATCH = "batch",
|
||||||
|
|
||||||
|
PLAY_GLOBAL_MESSAGE = "play-global-message",
|
||||||
|
STOP_GLOBAL_MESSAGE = "stop-global-message",
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PointInterface {
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
direction : string;
|
||||||
|
moving: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Point implements PointInterface{
|
||||||
|
constructor(public x : number, public y : number, public direction : string = PlayerAnimationNames.WalkDown, public moving : boolean = false) {
|
||||||
|
if(x === null || y === null){
|
||||||
|
throw Error("position x and y cannot be null");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MessageUserPositionInterface {
|
||||||
|
userId: number;
|
||||||
|
name: string;
|
||||||
|
characterLayers: string[];
|
||||||
|
position: PointInterface;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MessageUserMovedInterface {
|
||||||
|
userId: number;
|
||||||
|
position: PointInterface;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MessageUserJoined {
|
||||||
|
userId: number;
|
||||||
|
name: string;
|
||||||
|
characterLayers: string[];
|
||||||
|
position: PointInterface
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PositionInterface {
|
||||||
|
x: number,
|
||||||
|
y: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GroupCreatedUpdatedMessageInterface {
|
||||||
|
position: PositionInterface,
|
||||||
|
groupId: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface WebRtcStartMessageInterface {
|
||||||
|
roomId: string,
|
||||||
|
clients: UserSimplePeerInterface[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface WebRtcDisconnectMessageInterface {
|
||||||
|
userId: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface WebRtcSignalSentMessageInterface {
|
||||||
|
receiverId: number,
|
||||||
|
signal: SignalData
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface WebRtcSignalReceivedMessageInterface {
|
||||||
|
userId: number,
|
||||||
|
signal: SignalData
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface StartMapInterface {
|
||||||
|
mapUrlStart: string,
|
||||||
|
startInstance: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ViewportInterface {
|
||||||
|
left: number,
|
||||||
|
top: number,
|
||||||
|
right: number,
|
||||||
|
bottom: number,
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BatchedMessageInterface {
|
||||||
|
event: string,
|
||||||
|
payload: unknown
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ItemEventMessageInterface {
|
||||||
|
itemId: number,
|
||||||
|
event: string,
|
||||||
|
state: unknown,
|
||||||
|
parameters: unknown
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RoomJoinedMessageInterface {
|
||||||
|
users: MessageUserPositionInterface[],
|
||||||
|
groups: GroupCreatedUpdatedMessageInterface[],
|
||||||
|
items: { [itemId: number] : unknown }
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PlayGlobalMessageInterface {
|
||||||
|
id: string
|
||||||
|
type: string
|
||||||
|
message: string
|
||||||
|
}
|
476
front/src/Connexion/RoomConnection.ts
Normal file
476
front/src/Connexion/RoomConnection.ts
Normal file
@ -0,0 +1,476 @@
|
|||||||
|
import {API_URL} from "../Enum/EnvironmentVariable";
|
||||||
|
import Axios from "axios";
|
||||||
|
import {
|
||||||
|
BatchMessage,
|
||||||
|
ClientToServerMessage,
|
||||||
|
GroupDeleteMessage,
|
||||||
|
GroupUpdateMessage,
|
||||||
|
ItemEventMessage,
|
||||||
|
JoinRoomMessage, PlayGlobalMessage,
|
||||||
|
PositionMessage,
|
||||||
|
RoomJoinedMessage,
|
||||||
|
ServerToClientMessage,
|
||||||
|
SetPlayerDetailsMessage,
|
||||||
|
SetUserIdMessage,
|
||||||
|
SilentMessage, StopGlobalMessage,
|
||||||
|
UserJoinedMessage,
|
||||||
|
UserLeftMessage,
|
||||||
|
UserMovedMessage,
|
||||||
|
UserMovesMessage,
|
||||||
|
ViewportMessage,
|
||||||
|
WebRtcDisconnectMessage,
|
||||||
|
WebRtcSignalToClientMessage,
|
||||||
|
WebRtcSignalToServerMessage,
|
||||||
|
WebRtcStartMessage
|
||||||
|
} from "../Messages/generated/messages_pb"
|
||||||
|
|
||||||
|
import {UserSimplePeerInterface} from "../WebRtc/SimplePeer";
|
||||||
|
import Direction = PositionMessage.Direction;
|
||||||
|
import {ProtobufClientUtils} from "../Network/ProtobufClientUtils";
|
||||||
|
import {
|
||||||
|
EventMessage,
|
||||||
|
GroupCreatedUpdatedMessageInterface, ItemEventMessageInterface,
|
||||||
|
MessageUserJoined, PlayGlobalMessageInterface,
|
||||||
|
RoomJoinedMessageInterface,
|
||||||
|
ViewportInterface, WebRtcDisconnectMessageInterface,
|
||||||
|
WebRtcSignalReceivedMessageInterface,
|
||||||
|
WebRtcSignalSentMessageInterface,
|
||||||
|
WebRtcStartMessageInterface
|
||||||
|
} from "./ConnexionModels";
|
||||||
|
|
||||||
|
export class RoomConnection implements RoomConnection {
|
||||||
|
private readonly socket: WebSocket;
|
||||||
|
private userId: number|null = null;
|
||||||
|
private listeners: Map<string, Function[]> = new Map<string, Function[]>();
|
||||||
|
private static websocketFactory: null|((url: string)=>any) = null; // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||||
|
|
||||||
|
public static setWebsocketFactory(websocketFactory: (url: string)=>any): void { // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||||
|
RoomConnection.websocketFactory = websocketFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public constructor(token: string) {
|
||||||
|
let url = API_URL.replace('http://', 'ws://').replace('https://', 'wss://');
|
||||||
|
url += '?token='+token;
|
||||||
|
|
||||||
|
if (RoomConnection.websocketFactory) {
|
||||||
|
this.socket = RoomConnection.websocketFactory(url);
|
||||||
|
} else {
|
||||||
|
this.socket = new WebSocket(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.socket.binaryType = 'arraybuffer';
|
||||||
|
|
||||||
|
this.socket.onopen = (ev) => {
|
||||||
|
//console.log('WS connected');
|
||||||
|
};
|
||||||
|
|
||||||
|
this.socket.onmessage = (messageEvent) => {
|
||||||
|
const arrayBuffer: ArrayBuffer = messageEvent.data;
|
||||||
|
const message = ServerToClientMessage.deserializeBinary(new Uint8Array(arrayBuffer));
|
||||||
|
|
||||||
|
if (message.hasBatchmessage()) {
|
||||||
|
for (const subMessage of (message.getBatchmessage() as BatchMessage).getPayloadList()) {
|
||||||
|
let event: string;
|
||||||
|
let payload;
|
||||||
|
if (subMessage.hasUsermovedmessage()) {
|
||||||
|
event = EventMessage.USER_MOVED;
|
||||||
|
payload = subMessage.getUsermovedmessage();
|
||||||
|
} else if (subMessage.hasGroupupdatemessage()) {
|
||||||
|
event = EventMessage.GROUP_CREATE_UPDATE;
|
||||||
|
payload = subMessage.getGroupupdatemessage();
|
||||||
|
} else if (subMessage.hasGroupdeletemessage()) {
|
||||||
|
event = EventMessage.GROUP_DELETE;
|
||||||
|
payload = subMessage.getGroupdeletemessage();
|
||||||
|
} else if (subMessage.hasUserjoinedmessage()) {
|
||||||
|
event = EventMessage.JOIN_ROOM;
|
||||||
|
payload = subMessage.getUserjoinedmessage();
|
||||||
|
} else if (subMessage.hasUserleftmessage()) {
|
||||||
|
event = EventMessage.USER_LEFT;
|
||||||
|
payload = subMessage.getUserleftmessage();
|
||||||
|
} else if (subMessage.hasItemeventmessage()) {
|
||||||
|
event = EventMessage.ITEM_EVENT;
|
||||||
|
payload = subMessage.getItemeventmessage();
|
||||||
|
} else {
|
||||||
|
throw new Error('Unexpected batch message type');
|
||||||
|
}
|
||||||
|
|
||||||
|
this.dispatch(event, payload);
|
||||||
|
}
|
||||||
|
} else if (message.hasRoomjoinedmessage()) {
|
||||||
|
const roomJoinedMessage = message.getRoomjoinedmessage() as RoomJoinedMessage;
|
||||||
|
|
||||||
|
const users: Array<MessageUserJoined> = roomJoinedMessage.getUserList().map(this.toMessageUserJoined.bind(this));
|
||||||
|
const groups: Array<GroupCreatedUpdatedMessageInterface> = roomJoinedMessage.getGroupList().map(this.toGroupCreatedUpdatedMessage.bind(this));
|
||||||
|
const items: { [itemId: number] : unknown } = {};
|
||||||
|
for (const item of roomJoinedMessage.getItemList()) {
|
||||||
|
items[item.getItemid()] = JSON.parse(item.getStatejson());
|
||||||
|
}
|
||||||
|
|
||||||
|
this.resolveJoinRoom({
|
||||||
|
users,
|
||||||
|
groups,
|
||||||
|
items
|
||||||
|
})
|
||||||
|
} else if (message.hasSetuseridmessage()) {
|
||||||
|
this.userId = (message.getSetuseridmessage() as SetUserIdMessage).getUserid();
|
||||||
|
} else if (message.hasErrormessage()) {
|
||||||
|
console.error(EventMessage.MESSAGE_ERROR, message.getErrormessage()?.getMessage());
|
||||||
|
} else if (message.hasWebrtcsignaltoclientmessage()) {
|
||||||
|
this.dispatch(EventMessage.WEBRTC_SIGNAL, message.getWebrtcsignaltoclientmessage());
|
||||||
|
} else if (message.hasWebrtcscreensharingsignaltoclientmessage()) {
|
||||||
|
this.dispatch(EventMessage.WEBRTC_SCREEN_SHARING_SIGNAL, message.getWebrtcscreensharingsignaltoclientmessage());
|
||||||
|
} else if (message.hasWebrtcstartmessage()) {
|
||||||
|
this.dispatch(EventMessage.WEBRTC_START, message.getWebrtcstartmessage());
|
||||||
|
} else if (message.hasWebrtcdisconnectmessage()) {
|
||||||
|
this.dispatch(EventMessage.WEBRTC_DISCONNECT, message.getWebrtcdisconnectmessage());
|
||||||
|
} else if (message.hasPlayglobalmessage()) {
|
||||||
|
this.dispatch(EventMessage.PLAY_GLOBAL_MESSAGE, message.getPlayglobalmessage());
|
||||||
|
} else if (message.hasStopglobalmessage()) {
|
||||||
|
this.dispatch(EventMessage.STOP_GLOBAL_MESSAGE, message.getStopglobalmessage());
|
||||||
|
} else {
|
||||||
|
throw new Error('Unknown message received');
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private dispatch(event: string, payload: unknown): void {
|
||||||
|
const listeners = this.listeners.get(event);
|
||||||
|
if (listeners === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (const listener of listeners) {
|
||||||
|
listener(payload);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public emitPlayerDetailsMessage(userName: string, characterLayersSelected: string[]) {
|
||||||
|
const message = new SetPlayerDetailsMessage();
|
||||||
|
message.setName(userName);
|
||||||
|
message.setCharacterlayersList(characterLayersSelected);
|
||||||
|
|
||||||
|
const clientToServerMessage = new ClientToServerMessage();
|
||||||
|
clientToServerMessage.setSetplayerdetailsmessage(message);
|
||||||
|
|
||||||
|
this.socket.send(clientToServerMessage.serializeBinary().buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
public closeConnection(): void {
|
||||||
|
this.socket?.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
private resolveJoinRoom!: (value?: (RoomJoinedMessageInterface | PromiseLike<RoomJoinedMessageInterface> | undefined)) => void;
|
||||||
|
|
||||||
|
public joinARoom(roomId: string, startX: number, startY: number, direction: string, moving: boolean, viewport: ViewportInterface): Promise<RoomJoinedMessageInterface> {
|
||||||
|
const promise = new Promise<RoomJoinedMessageInterface>((resolve, reject) => {
|
||||||
|
this.resolveJoinRoom = resolve;
|
||||||
|
|
||||||
|
const positionMessage = this.toPositionMessage(startX, startY, direction, moving);
|
||||||
|
const viewportMessage = this.toViewportMessage(viewport);
|
||||||
|
|
||||||
|
const joinRoomMessage = new JoinRoomMessage();
|
||||||
|
joinRoomMessage.setRoomid(roomId);
|
||||||
|
joinRoomMessage.setPosition(positionMessage);
|
||||||
|
joinRoomMessage.setViewport(viewportMessage);
|
||||||
|
|
||||||
|
//console.log('Sending position ', positionMessage.getX(), positionMessage.getY());
|
||||||
|
const clientToServerMessage = new ClientToServerMessage();
|
||||||
|
clientToServerMessage.setJoinroommessage(joinRoomMessage);
|
||||||
|
|
||||||
|
this.socket.send(clientToServerMessage.serializeBinary().buffer);
|
||||||
|
})
|
||||||
|
return promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
private toPositionMessage(x : number, y : number, direction : string, moving: boolean): PositionMessage {
|
||||||
|
const positionMessage = new PositionMessage();
|
||||||
|
positionMessage.setX(Math.floor(x));
|
||||||
|
positionMessage.setY(Math.floor(y));
|
||||||
|
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);
|
||||||
|
|
||||||
|
return positionMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
private toViewportMessage(viewport: ViewportInterface): ViewportMessage {
|
||||||
|
const viewportMessage = new ViewportMessage();
|
||||||
|
viewportMessage.setLeft(Math.floor(viewport.left));
|
||||||
|
viewportMessage.setRight(Math.floor(viewport.right));
|
||||||
|
viewportMessage.setTop(Math.floor(viewport.top));
|
||||||
|
viewportMessage.setBottom(Math.floor(viewport.bottom));
|
||||||
|
|
||||||
|
return viewportMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
public sharePosition(x : number, y : number, direction : string, moving: boolean, viewport: ViewportInterface) : void{
|
||||||
|
if(!this.socket){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const positionMessage = this.toPositionMessage(x, y, direction, moving);
|
||||||
|
|
||||||
|
const viewportMessage = this.toViewportMessage(viewport);
|
||||||
|
|
||||||
|
const userMovesMessage = new UserMovesMessage();
|
||||||
|
userMovesMessage.setPosition(positionMessage);
|
||||||
|
userMovesMessage.setViewport(viewportMessage);
|
||||||
|
|
||||||
|
//console.log('Sending position ', positionMessage.getX(), positionMessage.getY());
|
||||||
|
const clientToServerMessage = new ClientToServerMessage();
|
||||||
|
clientToServerMessage.setUsermovesmessage(userMovesMessage);
|
||||||
|
|
||||||
|
this.socket.send(clientToServerMessage.serializeBinary().buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
public setSilent(silent: boolean): void {
|
||||||
|
const silentMessage = new SilentMessage();
|
||||||
|
silentMessage.setSilent(silent);
|
||||||
|
|
||||||
|
const clientToServerMessage = new ClientToServerMessage();
|
||||||
|
clientToServerMessage.setSilentmessage(silentMessage);
|
||||||
|
|
||||||
|
this.socket.send(clientToServerMessage.serializeBinary().buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
public setViewport(viewport: ViewportInterface): void {
|
||||||
|
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));
|
||||||
|
|
||||||
|
const clientToServerMessage = new ClientToServerMessage();
|
||||||
|
clientToServerMessage.setViewportmessage(viewportMessage);
|
||||||
|
|
||||||
|
this.socket.send(clientToServerMessage.serializeBinary().buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
public onUserJoins(callback: (message: MessageUserJoined) => void): void {
|
||||||
|
this.onMessage(EventMessage.JOIN_ROOM, (message: UserJoinedMessage) => {
|
||||||
|
callback(this.toMessageUserJoined(message));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: move this to protobuf utils
|
||||||
|
private toMessageUserJoined(message: UserJoinedMessage): MessageUserJoined {
|
||||||
|
const position = message.getPosition();
|
||||||
|
if (position === undefined) {
|
||||||
|
throw new Error('Invalid JOIN_ROOM message');
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
userId: message.getUserid(),
|
||||||
|
name: message.getName(),
|
||||||
|
characterLayers: message.getCharacterlayersList(),
|
||||||
|
position: ProtobufClientUtils.toPointInterface(position)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public onUserMoved(callback: (message: UserMovedMessage) => void): void {
|
||||||
|
this.onMessage(EventMessage.USER_MOVED, callback);
|
||||||
|
//this.socket.on(EventMessage.USER_MOVED, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers a listener on a message that is part of a batch
|
||||||
|
*/
|
||||||
|
private onMessage(eventName: string, callback: Function): void {
|
||||||
|
let callbacks = this.listeners.get(eventName);
|
||||||
|
if (callbacks === undefined) {
|
||||||
|
callbacks = new Array<Function>();
|
||||||
|
this.listeners.set(eventName, callbacks);
|
||||||
|
}
|
||||||
|
callbacks.push(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
public onUserLeft(callback: (userId: number) => void): void {
|
||||||
|
this.onMessage(EventMessage.USER_LEFT, (message: UserLeftMessage) => {
|
||||||
|
callback(message.getUserid());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public onGroupUpdatedOrCreated(callback: (groupCreateUpdateMessage: GroupCreatedUpdatedMessageInterface) => void): void {
|
||||||
|
this.onMessage(EventMessage.GROUP_CREATE_UPDATE, (message: GroupUpdateMessage) => {
|
||||||
|
callback(this.toGroupCreatedUpdatedMessage(message));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private toGroupCreatedUpdatedMessage(message: GroupUpdateMessage): GroupCreatedUpdatedMessageInterface {
|
||||||
|
const position = message.getPosition();
|
||||||
|
if (position === undefined) {
|
||||||
|
throw new Error('Missing position in GROUP_CREATE_UPDATE');
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
groupId: message.getGroupid(),
|
||||||
|
position: position.toObject()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public onGroupDeleted(callback: (groupId: number) => void): void {
|
||||||
|
this.onMessage(EventMessage.GROUP_DELETE, (message: GroupDeleteMessage) => {
|
||||||
|
callback(message.getGroupid());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public onConnectError(callback: (error: Event) => void): void {
|
||||||
|
this.socket.addEventListener('error', callback)
|
||||||
|
}
|
||||||
|
|
||||||
|
public onConnect(callback: (event: Event) => void): void {
|
||||||
|
this.socket.addEventListener('open', callback)
|
||||||
|
}
|
||||||
|
|
||||||
|
public sendWebrtcSignal(signal: unknown, receiverId: number) {
|
||||||
|
const webRtcSignal = new WebRtcSignalToServerMessage();
|
||||||
|
webRtcSignal.setReceiverid(receiverId);
|
||||||
|
webRtcSignal.setSignal(JSON.stringify(signal));
|
||||||
|
|
||||||
|
const clientToServerMessage = new ClientToServerMessage();
|
||||||
|
clientToServerMessage.setWebrtcsignaltoservermessage(webRtcSignal);
|
||||||
|
|
||||||
|
this.socket.send(clientToServerMessage.serializeBinary().buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
public sendWebrtcScreenSharingSignal(signal: unknown, receiverId: number) {
|
||||||
|
const webRtcSignal = new WebRtcSignalToServerMessage();
|
||||||
|
webRtcSignal.setReceiverid(receiverId);
|
||||||
|
webRtcSignal.setSignal(JSON.stringify(signal));
|
||||||
|
|
||||||
|
const clientToServerMessage = new ClientToServerMessage();
|
||||||
|
clientToServerMessage.setWebrtcscreensharingsignaltoservermessage(webRtcSignal);
|
||||||
|
|
||||||
|
this.socket.send(clientToServerMessage.serializeBinary().buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
public receiveWebrtcStart(callback: (message: UserSimplePeerInterface) => void) {
|
||||||
|
this.onMessage(EventMessage.WEBRTC_START, (message: WebRtcStartMessage) => {
|
||||||
|
callback({
|
||||||
|
userId: message.getUserid(),
|
||||||
|
name: message.getName(),
|
||||||
|
initiator: message.getInitiator()
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public receiveWebrtcSignal(callback: (message: WebRtcSignalReceivedMessageInterface) => void) {
|
||||||
|
this.onMessage(EventMessage.WEBRTC_SIGNAL, (message: WebRtcSignalToClientMessage) => {
|
||||||
|
callback({
|
||||||
|
userId: message.getUserid(),
|
||||||
|
signal: JSON.parse(message.getSignal())
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public receiveWebrtcScreenSharingSignal(callback: (message: WebRtcSignalReceivedMessageInterface) => void) {
|
||||||
|
this.onMessage(EventMessage.WEBRTC_SCREEN_SHARING_SIGNAL, (message: WebRtcSignalToClientMessage) => {
|
||||||
|
callback({
|
||||||
|
userId: message.getUserid(),
|
||||||
|
signal: JSON.parse(message.getSignal())
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public onServerDisconnected(callback: (event: CloseEvent) => void): void {
|
||||||
|
this.socket.addEventListener('close', (event) => {
|
||||||
|
console.log('Socket closed with code '+event.code+". Reason: "+event.reason);
|
||||||
|
if (event.code === 1000) {
|
||||||
|
// Normal closure case
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
callback(event);
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public getUserId(): number|null {
|
||||||
|
return this.userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
disconnectMessage(callback: (message: WebRtcDisconnectMessageInterface) => void): void {
|
||||||
|
this.onMessage(EventMessage.WEBRTC_DISCONNECT, (message: WebRtcDisconnectMessage) => {
|
||||||
|
callback({
|
||||||
|
userId: message.getUserid()
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
emitActionableEvent(itemId: number, event: string, state: unknown, parameters: unknown): void {
|
||||||
|
const itemEventMessage = new ItemEventMessage();
|
||||||
|
itemEventMessage.setItemid(itemId);
|
||||||
|
itemEventMessage.setEvent(event);
|
||||||
|
itemEventMessage.setStatejson(JSON.stringify(state));
|
||||||
|
itemEventMessage.setParametersjson(JSON.stringify(parameters));
|
||||||
|
|
||||||
|
const clientToServerMessage = new ClientToServerMessage();
|
||||||
|
clientToServerMessage.setItemeventmessage(itemEventMessage);
|
||||||
|
|
||||||
|
this.socket.send(clientToServerMessage.serializeBinary().buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
onActionableEvent(callback: (message: ItemEventMessageInterface) => void): void {
|
||||||
|
this.onMessage(EventMessage.ITEM_EVENT, (message: ItemEventMessage) => {
|
||||||
|
callback({
|
||||||
|
itemId: message.getItemid(),
|
||||||
|
event: message.getEvent(),
|
||||||
|
parameters: JSON.parse(message.getParametersjson()),
|
||||||
|
state: JSON.parse(message.getStatejson())
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public uploadAudio(file : FormData){
|
||||||
|
return Axios.post(`${API_URL}/upload-audio-message`, file).then((res: {data:{}}) => {
|
||||||
|
return res.data;
|
||||||
|
}).catch((err) => {
|
||||||
|
console.error(err);
|
||||||
|
throw err;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public receivePlayGlobalMessage(callback: (message: PlayGlobalMessageInterface) => void) {
|
||||||
|
return this.onMessage(EventMessage.PLAY_GLOBAL_MESSAGE, (message: PlayGlobalMessage) => {
|
||||||
|
callback({
|
||||||
|
id: message.getId(),
|
||||||
|
type: message.getType(),
|
||||||
|
message: message.getMessage(),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public receiveStopGlobalMessage(callback: (messageId: string) => void) {
|
||||||
|
return this.onMessage(EventMessage.STOP_GLOBAL_MESSAGE, (message: StopGlobalMessage) => {
|
||||||
|
callback(message.getId());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public emitGlobalMessage(message: PlayGlobalMessageInterface){
|
||||||
|
console.log('emitGlobalMessage', message);
|
||||||
|
const playGlobalMessage = new PlayGlobalMessage();
|
||||||
|
playGlobalMessage.setId(message.id);
|
||||||
|
playGlobalMessage.setType(message.type);
|
||||||
|
playGlobalMessage.setMessage(message.message);
|
||||||
|
|
||||||
|
const clientToServerMessage = new ClientToServerMessage();
|
||||||
|
clientToServerMessage.setPlayglobalmessage(playGlobalMessage);
|
||||||
|
|
||||||
|
this.socket.send(clientToServerMessage.serializeBinary().buffer);
|
||||||
|
}
|
||||||
|
}
|
@ -7,7 +7,7 @@ const JITSI_URL : string|undefined = (process.env.JITSI_URL === '') ? undefined
|
|||||||
const RESOLUTION = 3;
|
const RESOLUTION = 3;
|
||||||
const ZOOM_LEVEL = 1/*3/4*/;
|
const ZOOM_LEVEL = 1/*3/4*/;
|
||||||
const POSITION_DELAY = 200; // Wait 200ms between sending position events
|
const POSITION_DELAY = 200; // Wait 200ms between sending position events
|
||||||
const MAX_EXTRAPOLATION_TIME = 250; // Extrapolate a maximum of 250ms if no new movement is sent by the player
|
const MAX_EXTRAPOLATION_TIME = 100; // Extrapolate a maximum of 250ms if no new movement is sent by the player
|
||||||
|
|
||||||
export {
|
export {
|
||||||
DEBUG_MODE,
|
DEBUG_MODE,
|
||||||
|
1
front/src/Messages/.gitignore
vendored
Normal file
1
front/src/Messages/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
/generated/
|
@ -1,4 +0,0 @@
|
|||||||
export interface SetPlayerDetailsMessage {
|
|
||||||
name: string,
|
|
||||||
characterLayers: string[]
|
|
||||||
}
|
|
34
front/src/Network/ProtobufClientUtils.ts
Normal file
34
front/src/Network/ProtobufClientUtils.ts
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import {PositionMessage} from "../Messages/generated/messages_pb";
|
||||||
|
import Direction = PositionMessage.Direction;
|
||||||
|
import {PointInterface} from "../Connexion/ConnexionModels";
|
||||||
|
|
||||||
|
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(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@ -1,15 +1,15 @@
|
|||||||
import {GameScene} from "../Game/GameScene";
|
import {GameScene} from "../Game/GameScene";
|
||||||
import {PointInterface} from "../../Connection";
|
import {PointInterface} from "../../Connexion/ConnexionModels";
|
||||||
import {Character} from "../Entity/Character";
|
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,
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import {PointInterface} from "../../Connection";
|
import {PointInterface} from "../../Connexion/ConnexionModels";
|
||||||
|
|
||||||
export interface AddPlayerInterface {
|
export interface AddPlayerInterface {
|
||||||
userId: string;
|
userId: number;
|
||||||
name: string;
|
name: string;
|
||||||
characterLayers: string[];
|
characterLayers: string[];
|
||||||
position: PointInterface;
|
position: PointInterface;
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
import {GameScene} from "./GameScene";
|
import {GameScene} from "./GameScene";
|
||||||
import {
|
import {
|
||||||
StartMapInterface
|
StartMapInterface
|
||||||
} from "../../Connection";
|
} from "../../Connexion/ConnexionModels";
|
||||||
import Axios from "axios";
|
import Axios from "axios";
|
||||||
import {API_URL} from "../../Enum/EnvironmentVariable";
|
import {API_URL} from "../../Enum/EnvironmentVariable";
|
||||||
|
import {connectionManager} from "../../Connexion/ConnectionManager";
|
||||||
|
|
||||||
export interface HasMovedEvent {
|
export interface HasMovedEvent {
|
||||||
direction: string;
|
direction: string;
|
||||||
@ -29,12 +30,11 @@ export class GameManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
loadStartMap() : Promise<StartMapInterface> {
|
loadStartMap() : Promise<StartMapInterface> {
|
||||||
return Axios.get(`${API_URL}/start-map`)
|
return connectionManager.getMapUrlStart().then(mapUrlStart => {
|
||||||
.then((res) => {
|
return {
|
||||||
return res.data;
|
mapUrlStart: mapUrlStart,
|
||||||
}).catch((err) => {
|
startInstance: "global", //todo: is this property still usefull?
|
||||||
console.error(err);
|
}
|
||||||
throw err;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import {GameManager, gameManager, HasMovedEvent} from "./GameManager";
|
import {GameManager, gameManager, HasMovedEvent} from "./GameManager";
|
||||||
import {
|
import {
|
||||||
Connection,
|
|
||||||
GroupCreatedUpdatedMessageInterface,
|
GroupCreatedUpdatedMessageInterface,
|
||||||
MessageUserJoined,
|
MessageUserJoined,
|
||||||
MessageUserMovedInterface,
|
MessageUserMovedInterface,
|
||||||
@ -8,7 +7,7 @@ import {
|
|||||||
PointInterface,
|
PointInterface,
|
||||||
PositionInterface,
|
PositionInterface,
|
||||||
RoomJoinedMessageInterface
|
RoomJoinedMessageInterface
|
||||||
} from "../../Connection";
|
} from "../../Connexion/ConnexionModels";
|
||||||
import {CurrentGamerInterface, hasMovedEventName, Player} from "../Player/Player";
|
import {CurrentGamerInterface, hasMovedEventName, Player} from "../Player/Player";
|
||||||
import {DEBUG_MODE, JITSI_URL, POSITION_DELAY, RESOLUTION, ZOOM_LEVEL} from "../../Enum/EnvironmentVariable";
|
import {DEBUG_MODE, JITSI_URL, POSITION_DELAY, RESOLUTION, ZOOM_LEVEL} from "../../Enum/EnvironmentVariable";
|
||||||
import {
|
import {
|
||||||
@ -40,6 +39,10 @@ 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";
|
||||||
|
import {connectionManager} from "../../Connexion/ConnectionManager";
|
||||||
|
import {RoomConnection} from "../../Connexion/RoomConnection";
|
||||||
import {GlobalMessageManager} from "../../Administration/GlobalMessageManager";
|
import {GlobalMessageManager} from "../../Administration/GlobalMessageManager";
|
||||||
import {ConsoleGlobalMessageManager} from "../../Administration/ConsoleGlobalMessageManager";
|
import {ConsoleGlobalMessageManager} from "../../Administration/ConsoleGlobalMessageManager";
|
||||||
|
|
||||||
@ -65,7 +68,7 @@ interface AddPlayerEventInterface {
|
|||||||
|
|
||||||
interface RemovePlayerEventInterface {
|
interface RemovePlayerEventInterface {
|
||||||
type: 'RemovePlayerEvent'
|
type: 'RemovePlayerEvent'
|
||||||
userId: string
|
userId: number
|
||||||
}
|
}
|
||||||
|
|
||||||
interface UserMovedEventInterface {
|
interface UserMovedEventInterface {
|
||||||
@ -80,7 +83,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 {
|
||||||
@ -88,23 +91,23 @@ 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;
|
||||||
pendingEvents: Queue<InitUserPositionEventInterface|AddPlayerEventInterface|RemovePlayerEventInterface|UserMovedEventInterface|GroupCreatedUpdatedEventInterface|DeleteGroupEventInterface> = new Queue<InitUserPositionEventInterface|AddPlayerEventInterface|RemovePlayerEventInterface|UserMovedEventInterface|GroupCreatedUpdatedEventInterface|DeleteGroupEventInterface>();
|
pendingEvents: Queue<InitUserPositionEventInterface|AddPlayerEventInterface|RemovePlayerEventInterface|UserMovedEventInterface|GroupCreatedUpdatedEventInterface|DeleteGroupEventInterface> = new Queue<InitUserPositionEventInterface|AddPlayerEventInterface|RemovePlayerEventInterface|UserMovedEventInterface|GroupCreatedUpdatedEventInterface|DeleteGroupEventInterface>();
|
||||||
private initPosition: PositionInterface|null = null;
|
private initPosition: PositionInterface|null = null;
|
||||||
private playersPositionInterpolator = new PlayersPositionInterpolator();
|
private playersPositionInterpolator = new PlayersPositionInterpolator();
|
||||||
private connection!: Connection;
|
private connection!: RoomConnection;
|
||||||
private simplePeer!: SimplePeer;
|
private simplePeer!: SimplePeer;
|
||||||
private GlobalMessageManager!: GlobalMessageManager;
|
private GlobalMessageManager!: GlobalMessageManager;
|
||||||
private ConsoleGlobalMessageManager!: ConsoleGlobalMessageManager;
|
private ConsoleGlobalMessageManager!: ConsoleGlobalMessageManager;
|
||||||
private connectionPromise!: Promise<Connection>
|
private connectionPromise!: Promise<RoomConnection>
|
||||||
private connectionAnswerPromise: Promise<RoomJoinedMessageInterface>;
|
private connectionAnswerPromise: Promise<RoomJoinedMessageInterface>;
|
||||||
private connectionAnswerPromiseResolve!: (value?: RoomJoinedMessageInterface | PromiseLike<RoomJoinedMessageInterface>) => void;
|
private connectionAnswerPromiseResolve!: (value?: RoomJoinedMessageInterface | PromiseLike<RoomJoinedMessageInterface>) => void;
|
||||||
// A promise that will resolve when the "create" method is called (signaling loading is ended)
|
// A promise that will resolve when the "create" method is called (signaling loading is ended)
|
||||||
@ -151,7 +154,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;
|
||||||
@ -204,9 +207,11 @@ export class GameScene extends Phaser.Scene implements CenterListener {
|
|||||||
|
|
||||||
this.load.bitmapFont('main_font', 'resources/fonts/arcade.png', 'resources/fonts/arcade.xml');
|
this.load.bitmapFont('main_font', 'resources/fonts/arcade.png', 'resources/fonts/arcade.xml');
|
||||||
|
|
||||||
this.connectionPromise = Connection.createConnection(gameManager.getPlayerName(), gameManager.getCharacterSelected()).then((connection : Connection) => {
|
this.connectionPromise = connectionManager.connectToRoomSocket().then((connection : RoomConnection) => {
|
||||||
this.connection = connection;
|
this.connection = connection;
|
||||||
|
|
||||||
|
this.connection.emitPlayerDetailsMessage(gameManager.getPlayerName(), gameManager.getCharacterSelected())
|
||||||
|
|
||||||
connection.onUserJoins((message: MessageUserJoined) => {
|
connection.onUserJoins((message: MessageUserJoined) => {
|
||||||
const userMessage: AddPlayerInterface = {
|
const userMessage: AddPlayerInterface = {
|
||||||
userId: message.userId,
|
userId: message.userId,
|
||||||
@ -217,11 +222,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);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -229,7 +245,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) {
|
||||||
@ -277,7 +293,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);
|
||||||
@ -773,7 +789,7 @@ export class GameScene extends Phaser.Scene implements CenterListener {
|
|||||||
this.createCollisionObject();
|
this.createCollisionObject();
|
||||||
|
|
||||||
//join room
|
//join room
|
||||||
this.connectionPromise.then((connection: Connection) => {
|
this.connectionPromise.then((connection: RoomConnection) => {
|
||||||
const camera = this.cameras.main;
|
const camera = this.cameras.main;
|
||||||
connection.joinARoom(this.RoomId,
|
connection.joinARoom(this.RoomId,
|
||||||
this.startX,
|
this.startX,
|
||||||
@ -926,7 +942,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 +'"');
|
||||||
@ -981,7 +997,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) => {
|
||||||
@ -1038,14 +1054,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);
|
||||||
@ -1075,6 +1091,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);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1104,14 +1121,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;
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import {HasMovedEvent} from "./GameManager";
|
import {HasMovedEvent} from "./GameManager";
|
||||||
import {MAX_EXTRAPOLATION_TIME} from "../../Enum/EnvironmentVariable";
|
import {MAX_EXTRAPOLATION_TIME} from "../../Enum/EnvironmentVariable";
|
||||||
import {PositionInterface} from "../../Connection";
|
import {PositionInterface} from "../../Connexion/ConnexionModels";
|
||||||
|
|
||||||
export class PlayerMovement {
|
export class PlayerMovement {
|
||||||
public constructor(private startPosition: PositionInterface, private startTick: number, private endPosition: HasMovedEvent, private endTick: number) {
|
public constructor(private startPosition: PositionInterface, private startTick: number, private endPosition: HasMovedEvent, private endTick: number) {
|
||||||
@ -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,
|
||||||
|
@ -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);
|
||||||
|
@ -2,7 +2,7 @@ import {gameManager} from "../Game/GameManager";
|
|||||||
import {TextField} from "../Components/TextField";
|
import {TextField} from "../Components/TextField";
|
||||||
import Image = Phaser.GameObjects.Image;
|
import Image = Phaser.GameObjects.Image;
|
||||||
import {GameSceneInitInterface} from "../Game/GameScene";
|
import {GameSceneInitInterface} from "../Game/GameScene";
|
||||||
import {StartMapInterface} from "../../Connection";
|
import {StartMapInterface} from "../../Connexion/ConnexionModels";
|
||||||
import {mediaManager} from "../../WebRtc/MediaManager";
|
import {mediaManager} from "../../WebRtc/MediaManager";
|
||||||
import {RESOLUTION} from "../../Enum/EnvironmentVariable";
|
import {RESOLUTION} from "../../Enum/EnvironmentVariable";
|
||||||
import {SoundMeter} from "../Components/SoundMeter";
|
import {SoundMeter} from "../Components/SoundMeter";
|
||||||
|
@ -3,8 +3,6 @@ import {TextField} from "../Components/TextField";
|
|||||||
import Image = Phaser.GameObjects.Image;
|
import Image = Phaser.GameObjects.Image;
|
||||||
import Rectangle = Phaser.GameObjects.Rectangle;
|
import Rectangle = Phaser.GameObjects.Rectangle;
|
||||||
import {PLAYER_RESOURCES, PlayerResourceDescriptionInterface} from "../Entity/Character";
|
import {PLAYER_RESOURCES, PlayerResourceDescriptionInterface} from "../Entity/Character";
|
||||||
import {GameSceneInitInterface} from "../Game/GameScene";
|
|
||||||
import {StartMapInterface} from "../../Connection";
|
|
||||||
import {EnableCameraSceneName} from "./EnableCameraScene";
|
import {EnableCameraSceneName} from "./EnableCameraScene";
|
||||||
import {CustomizeSceneName} from "./CustomizeScene";
|
import {CustomizeSceneName} from "./CustomizeScene";
|
||||||
|
|
||||||
|
@ -1,9 +1,7 @@
|
|||||||
import {PlayerAnimationNames} from "./Animation";
|
import {PlayerAnimationNames} from "./Animation";
|
||||||
import {GameScene, Textures} from "../Game/GameScene";
|
import {GameScene} from "../Game/GameScene";
|
||||||
import {MessageUserPositionInterface, PointInterface} from "../../Connection";
|
import {UserInputEvent, UserInputManager} from "../UserInput/UserInputManager";
|
||||||
import {ActiveEventList, UserInputEvent, UserInputManager} from "../UserInput/UserInputManager";
|
|
||||||
import {Character} from "../Entity/Character";
|
import {Character} from "../Entity/Character";
|
||||||
import {OutlinePipeline} from "../Shaders/OutlinePipeline";
|
|
||||||
|
|
||||||
|
|
||||||
export const hasMovedEventName = "hasMoved";
|
export const hasMovedEventName = "hasMoved";
|
||||||
|
@ -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;
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import * as SimplePeerNamespace from "simple-peer";
|
import * as SimplePeerNamespace from "simple-peer";
|
||||||
import {mediaManager} from "./MediaManager";
|
import {mediaManager} from "./MediaManager";
|
||||||
import {Connection} from "../Connection";
|
|
||||||
import {TURN_SERVER, TURN_USER, TURN_PASSWORD} from "../Enum/EnvironmentVariable";
|
import {TURN_SERVER, TURN_USER, TURN_PASSWORD} from "../Enum/EnvironmentVariable";
|
||||||
|
import {RoomConnection} from "../Connexion/RoomConnection";
|
||||||
|
|
||||||
const Peer: SimplePeerNamespace.SimplePeer = require('simple-peer');
|
const Peer: SimplePeerNamespace.SimplePeer = require('simple-peer');
|
||||||
|
|
||||||
@ -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: RoomConnection) {
|
||||||
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);
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
import {
|
import {
|
||||||
Connection,
|
|
||||||
WebRtcDisconnectMessageInterface,
|
WebRtcDisconnectMessageInterface,
|
||||||
WebRtcSignalReceivedMessageInterface,
|
WebRtcSignalReceivedMessageInterface,
|
||||||
WebRtcStartMessageInterface
|
WebRtcStartMessageInterface
|
||||||
} from "../Connection";
|
} from "../Connexion/ConnexionModels";
|
||||||
import {
|
import {
|
||||||
mediaManager,
|
mediaManager,
|
||||||
StartScreenSharingCallback,
|
StartScreenSharingCallback,
|
||||||
@ -12,9 +11,10 @@ import {
|
|||||||
} from "./MediaManager";
|
} from "./MediaManager";
|
||||||
import {ScreenSharingPeer} from "./ScreenSharingPeer";
|
import {ScreenSharingPeer} from "./ScreenSharingPeer";
|
||||||
import {VideoPeer} from "./VideoPeer";
|
import {VideoPeer} from "./VideoPeer";
|
||||||
|
import {RoomConnection} from "../Connexion/RoomConnection";
|
||||||
|
|
||||||
export interface UserSimplePeerInterface{
|
export interface UserSimplePeerInterface{
|
||||||
userId: string;
|
userId: number;
|
||||||
name?: string;
|
name?: string;
|
||||||
initiator?: boolean;
|
initiator?: boolean;
|
||||||
}
|
}
|
||||||
@ -22,25 +22,25 @@ 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class manages connections to all the peers in the same group as me.
|
* This class manages connections to all the peers in the same group as me.
|
||||||
*/
|
*/
|
||||||
export class SimplePeer {
|
export class SimplePeer {
|
||||||
private Connection: Connection;
|
private Connection: RoomConnection;
|
||||||
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;
|
||||||
private readonly peerConnectionListeners: Array<PeerConnectionListener> = new Array<PeerConnectionListener>();
|
private readonly peerConnectionListeners: Array<PeerConnectionListener> = new Array<PeerConnectionListener>();
|
||||||
|
|
||||||
constructor(Connection: Connection, WebRtcRoomId: string = "test-webrtc") {
|
constructor(Connection: RoomConnection, WebRtcRoomId: string = "test-webrtc") {
|
||||||
this.Connection = Connection;
|
this.Connection = Connection;
|
||||||
this.WebRtcRoomId = WebRtcRoomId;
|
this.WebRtcRoomId = WebRtcRoomId;
|
||||||
// We need to go through this weird bound function pointer in order to be able to "free" this reference later.
|
// We need to go through this weird bound function pointer in order to be able to "free" this reference later.
|
||||||
@ -80,7 +80,7 @@ export class SimplePeer {
|
|||||||
mediaManager.getCamera().then(() => {
|
mediaManager.getCamera().then(() => {
|
||||||
|
|
||||||
//receive message start
|
//receive message start
|
||||||
this.Connection.receiveWebrtcStart((message: WebRtcStartMessageInterface) => {
|
this.Connection.receiveWebrtcStart((message: UserSimplePeerInterface) => {
|
||||||
this.receiveWebrtcStart(message);
|
this.receiveWebrtcStart(message);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -93,17 +93,22 @@ export class SimplePeer {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private receiveWebrtcStart(data: WebRtcStartMessageInterface) {
|
private receiveWebrtcStart(user: UserSimplePeerInterface) {
|
||||||
this.WebRtcRoomId = data.roomId;
|
//this.WebRtcRoomId = data.roomId;
|
||||||
this.Users = data.clients;
|
this.Users.push(user);
|
||||||
// Note: the clients array contain the list of all clients (even the ones we are already connected to in case a user joints a group)
|
// Note: the clients array contain the list of all clients (even the ones we are already connected to in case a user joints a group)
|
||||||
// So we can receive a request we already had before. (which will abort at the first line of createPeerConnection)
|
// So we can receive a request we already had before. (which will abort at the first line of createPeerConnection)
|
||||||
// TODO: refactor this to only send a message to connect to one user (rather than several users).
|
// TODO: refactor this to only send a message to connect to one user (rather than several users). => DONE
|
||||||
// This would be symmetrical to the way we handle disconnection.
|
// This would be symmetrical to the way we handle disconnection.
|
||||||
//console.log('Start message', data);
|
//console.log('Start message', data);
|
||||||
|
|
||||||
//start connection
|
//start connection
|
||||||
this.startWebRtc();
|
//this.startWebRtc();
|
||||||
|
console.log('receiveWebrtcStart. Initiator: ', user.initiator)
|
||||||
|
if(!user.initiator){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.createPeerConnection(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -127,6 +132,7 @@ export class SimplePeer {
|
|||||||
if(
|
if(
|
||||||
this.PeerConnectionArray.has(user.userId)
|
this.PeerConnectionArray.has(user.userId)
|
||||||
){
|
){
|
||||||
|
console.log('Peer connection already exists to user '+user.userId)
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -138,8 +144,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,
|
||||||
@ -169,8 +175,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);
|
||||||
@ -187,7 +193,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);
|
||||||
@ -215,9 +221,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")
|
||||||
@ -291,7 +297,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) {
|
||||||
@ -312,7 +318,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);
|
||||||
@ -357,7 +363,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);
|
||||||
@ -374,7 +380,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')
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import * as SimplePeerNamespace from "simple-peer";
|
import * as SimplePeerNamespace from "simple-peer";
|
||||||
import {mediaManager} from "./MediaManager";
|
import {mediaManager} from "./MediaManager";
|
||||||
import {Connection} from "../Connection";
|
|
||||||
import {TURN_PASSWORD, TURN_SERVER, TURN_USER} from "../Enum/EnvironmentVariable";
|
import {TURN_PASSWORD, TURN_SERVER, TURN_USER} from "../Enum/EnvironmentVariable";
|
||||||
|
import {RoomConnection} from "../Connexion/RoomConnection";
|
||||||
|
|
||||||
const Peer: SimplePeerNamespace.SimplePeer = require('simple-peer');
|
const Peer: SimplePeerNamespace.SimplePeer = require('simple-peer');
|
||||||
|
|
||||||
@ -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: RoomConnection) {
|
||||||
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);
|
||||||
|
@ -11,11 +11,10 @@ import WebGLRenderer = Phaser.Renderer.WebGL.WebGLRenderer;
|
|||||||
import {OutlinePipeline} from "./Phaser/Shaders/OutlinePipeline";
|
import {OutlinePipeline} from "./Phaser/Shaders/OutlinePipeline";
|
||||||
import {CustomizeScene} from "./Phaser/Login/CustomizeScene";
|
import {CustomizeScene} from "./Phaser/Login/CustomizeScene";
|
||||||
import {CoWebsiteManager} from "./WebRtc/CoWebsiteManager";
|
import {CoWebsiteManager} from "./WebRtc/CoWebsiteManager";
|
||||||
import {redirectIfToken} from "./register";
|
import {connectionManager} from "./Connexion/ConnectionManager";
|
||||||
|
|
||||||
//CoWebsiteManager.loadCoWebsite('https://thecodingmachine.com');
|
//CoWebsiteManager.loadCoWebsite('https://thecodingmachine.com');
|
||||||
let connectionData //todo: do something with this data
|
connectionManager.init();
|
||||||
redirectIfToken().then(res => connectionData = res);
|
|
||||||
|
|
||||||
// Load Jitsi if the environment variable is set.
|
// Load Jitsi if the environment variable is set.
|
||||||
if (JITSI_URL) {
|
if (JITSI_URL) {
|
||||||
|
@ -1,29 +0,0 @@
|
|||||||
import Axios from "axios";
|
|
||||||
import {API_URL} from "./Enum/EnvironmentVariable";
|
|
||||||
declare let history:History;
|
|
||||||
|
|
||||||
//todo: better naming
|
|
||||||
export interface ConnexionData {
|
|
||||||
organizationSlug: string,
|
|
||||||
worldSlug: string,
|
|
||||||
roomSlug: string,
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function redirectIfToken(): Promise<ConnexionData | null> {
|
|
||||||
const match = /\/register\/(.+)/.exec(window.location.toString());
|
|
||||||
if (!match) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
let res = null;
|
|
||||||
try {
|
|
||||||
res = await Axios.get(`${API_URL}/register/`+match[1])
|
|
||||||
} catch (e) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const organizationSlug = res.data.organizationSlug;
|
|
||||||
const worldSlug = res.data.worldSlug;
|
|
||||||
const roomSlug = res.data.roomSlug;
|
|
||||||
const connexionUrl = '/@/'+organizationSlug+'/'+worldSlug+'/'+roomSlug;
|
|
||||||
history.pushState({}, '', connexionUrl);
|
|
||||||
return {organizationSlug, worldSlug, roomSlug};
|
|
||||||
}
|
|
@ -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"
|
||||||
@ -2257,6 +2262,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
1
messages/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
/node_modules/
|
2
messages/generated/.gitignore
vendored
Normal file
2
messages/generated/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
*
|
||||||
|
!.gitignore
|
182
messages/messages.proto
Normal file
182
messages/messages.proto
Normal file
@ -0,0 +1,182 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
message SilentMessage {
|
||||||
|
bool silent = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*********** CLIENT TO SERVER MESSAGES *************/
|
||||||
|
|
||||||
|
message SetPlayerDetailsMessage {
|
||||||
|
string name = 1;
|
||||||
|
repeated string characterLayers = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message JoinRoomMessage {
|
||||||
|
string roomId = 1;
|
||||||
|
PositionMessage position = 2;
|
||||||
|
ViewportMessage viewport = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message UserMovesMessage {
|
||||||
|
PositionMessage position = 1;
|
||||||
|
ViewportMessage viewport = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message WebRtcSignalToServerMessage {
|
||||||
|
int32 receiverId = 1;
|
||||||
|
string signal = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ClientToServerMessage {
|
||||||
|
oneof message {
|
||||||
|
JoinRoomMessage joinRoomMessage = 1;
|
||||||
|
UserMovesMessage userMovesMessage = 2;
|
||||||
|
SilentMessage silentMessage = 3;
|
||||||
|
ViewportMessage viewportMessage = 4;
|
||||||
|
ItemEventMessage itemEventMessage = 5;
|
||||||
|
SetPlayerDetailsMessage setPlayerDetailsMessage = 6;
|
||||||
|
WebRtcSignalToServerMessage webRtcSignalToServerMessage = 7;
|
||||||
|
WebRtcSignalToServerMessage webRtcScreenSharingSignalToServerMessage = 8;
|
||||||
|
PlayGlobalMessage playGlobalMessage = 9;
|
||||||
|
StopGlobalMessage stopGlobalMessage = 10;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/************ BI-DIRECTIONAL MESSAGES **************/
|
||||||
|
|
||||||
|
message ItemEventMessage {
|
||||||
|
int32 itemId = 1;
|
||||||
|
string event = 2;
|
||||||
|
string stateJson = 3;
|
||||||
|
string parametersJson = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
message PlayGlobalMessage {
|
||||||
|
string id = 1;
|
||||||
|
string type = 2;
|
||||||
|
string message = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message StopGlobalMessage {
|
||||||
|
string id = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*********** 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ErrorMessage {
|
||||||
|
string message = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message SetUserIdMessage {
|
||||||
|
int32 userId = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ItemStateMessage {
|
||||||
|
int32 itemId = 1;
|
||||||
|
string stateJson = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message RoomJoinedMessage {
|
||||||
|
repeated UserJoinedMessage user = 1;
|
||||||
|
repeated GroupUpdateMessage group = 2;
|
||||||
|
repeated ItemStateMessage item = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message WebRtcStartMessage {
|
||||||
|
int32 userId = 1;
|
||||||
|
string name = 2;
|
||||||
|
bool initiator = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message WebRtcDisconnectMessage {
|
||||||
|
int32 userId = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message WebRtcSignalToClientMessage {
|
||||||
|
int32 userId = 1;
|
||||||
|
string signal = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ServerToClientMessage {
|
||||||
|
oneof message {
|
||||||
|
BatchMessage batchMessage = 1;
|
||||||
|
ErrorMessage errorMessage = 2;
|
||||||
|
RoomJoinedMessage roomJoinedMessage = 3;
|
||||||
|
SetUserIdMessage setUserIdMessage = 4; // TODO: merge this with RoomJoinedMessage ?
|
||||||
|
WebRtcStartMessage webRtcStartMessage = 5;
|
||||||
|
WebRtcSignalToClientMessage webRtcSignalToClientMessage = 6;
|
||||||
|
WebRtcSignalToClientMessage webRtcScreenSharingSignalToClientMessage = 7;
|
||||||
|
WebRtcDisconnectMessage webRtcDisconnectMessage = 8;
|
||||||
|
PlayGlobalMessage playGlobalMessage = 9;
|
||||||
|
StopGlobalMessage stopGlobalMessage = 10;
|
||||||
|
}
|
||||||
|
}
|
46
messages/package.json
Normal file
46
messages/package.json
Normal 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
889
messages/yarn.lock
Normal 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==
|
Loading…
Reference in New Issue
Block a user