Merge remote-tracking branch 'remotes/upstream/develop' into trigger-message-refv3
2
.github/workflows/build-and-deploy.yml
vendored
@ -199,4 +199,4 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
with:
|
with:
|
||||||
msg: Environment deployed at https://play-${{ env.GITHUB_HEAD_REF_SLUG }}.test.workadventu.re
|
msg: "Environment deployed at https://play-${{ env.GITHUB_HEAD_REF_SLUG }}.test.workadventu.re \nTests available at https://maps-${{ env.GITHUB_HEAD_REF_SLUG }}.test.workadventu.re/tests"
|
||||||
|
13
.github/workflows/continuous_integration.yml
vendored
@ -64,6 +64,11 @@ jobs:
|
|||||||
run: yarn test
|
run: yarn test
|
||||||
working-directory: "front"
|
working-directory: "front"
|
||||||
|
|
||||||
|
# We will enable prettier checks on front in a few month, when most PRs without prettier have been merged
|
||||||
|
# - name: "Prettier"
|
||||||
|
# run: yarn run pretty-check
|
||||||
|
# working-directory: "front"
|
||||||
|
|
||||||
continuous-integration-pusher:
|
continuous-integration-pusher:
|
||||||
name: "Continuous Integration Pusher"
|
name: "Continuous Integration Pusher"
|
||||||
|
|
||||||
@ -107,6 +112,10 @@ jobs:
|
|||||||
run: yarn test
|
run: yarn test
|
||||||
working-directory: "pusher"
|
working-directory: "pusher"
|
||||||
|
|
||||||
|
- name: "Prettier"
|
||||||
|
run: yarn run pretty-check
|
||||||
|
working-directory: "pusher"
|
||||||
|
|
||||||
continuous-integration-back:
|
continuous-integration-back:
|
||||||
name: "Continuous Integration Back"
|
name: "Continuous Integration Back"
|
||||||
|
|
||||||
@ -150,3 +159,7 @@ jobs:
|
|||||||
run: yarn test
|
run: yarn test
|
||||||
working-directory: "back"
|
working-directory: "back"
|
||||||
|
|
||||||
|
- name: "Prettier"
|
||||||
|
run: yarn run pretty-check
|
||||||
|
working-directory: "back"
|
||||||
|
|
||||||
|
13
.github/workflows/push-to-npm.yml
vendored
@ -2,6 +2,7 @@ name: Push @workadventure/iframe-api-typings to NPM
|
|||||||
on:
|
on:
|
||||||
release:
|
release:
|
||||||
types: [created]
|
types: [created]
|
||||||
|
push:
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
@ -13,10 +14,6 @@ jobs:
|
|||||||
node-version: '14.x'
|
node-version: '14.x'
|
||||||
registry-url: 'https://registry.npmjs.org'
|
registry-url: 'https://registry.npmjs.org'
|
||||||
|
|
||||||
- name: Edit tsconfig.json to add declarations
|
|
||||||
run: "sed -i 's/\"declaration\": false/\"declaration\": true/g' tsconfig.json"
|
|
||||||
working-directory: "front"
|
|
||||||
|
|
||||||
- name: Replace version number
|
- name: Replace version number
|
||||||
run: 'sed -i "s#VERSION_PLACEHOLDER#${GITHUB_REF/refs\/tags\//}#g" package.json'
|
run: 'sed -i "s#VERSION_PLACEHOLDER#${GITHUB_REF/refs\/tags\//}#g" package.json'
|
||||||
working-directory: "front/packages/iframe-api-typings"
|
working-directory: "front/packages/iframe-api-typings"
|
||||||
@ -47,15 +44,18 @@ jobs:
|
|||||||
working-directory: "front"
|
working-directory: "front"
|
||||||
|
|
||||||
- name: "Build"
|
- name: "Build"
|
||||||
run: yarn run build
|
run: yarn run build-typings
|
||||||
env:
|
env:
|
||||||
API_URL: "localhost:8080"
|
PUSHER_URL: "//localhost:8080"
|
||||||
working-directory: "front"
|
working-directory: "front"
|
||||||
|
|
||||||
# We build the front to generate the typings of iframe_api, then we copy those typings in a separate package.
|
# We build the front to generate the typings of iframe_api, then we copy those typings in a separate package.
|
||||||
- name: Copy typings to package dir
|
- name: Copy typings to package dir
|
||||||
run: cp front/dist/src/iframe_api.d.ts front/packages/iframe-api-typings/iframe_api.d.ts
|
run: cp front/dist/src/iframe_api.d.ts front/packages/iframe-api-typings/iframe_api.d.ts
|
||||||
|
|
||||||
|
- name: Copy typings to package dir (2)
|
||||||
|
run: cp -R front/dist/src/Api front/packages/iframe-api-typings/Api
|
||||||
|
|
||||||
- name: Install dependencies in package
|
- name: Install dependencies in package
|
||||||
run: yarn install
|
run: yarn install
|
||||||
working-directory: "front/packages/iframe-api-typings"
|
working-directory: "front/packages/iframe-api-typings"
|
||||||
@ -65,3 +65,4 @@ jobs:
|
|||||||
working-directory: "front/packages/iframe-api-typings"
|
working-directory: "front/packages/iframe-api-typings"
|
||||||
env:
|
env:
|
||||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||||
|
if: ${{ github.event_name == 'release' }}
|
||||||
|
3
.gitignore
vendored
@ -7,4 +7,5 @@ docker-compose.override.yaml
|
|||||||
maps/yarn.lock
|
maps/yarn.lock
|
||||||
maps/dist/computer.js
|
maps/dist/computer.js
|
||||||
maps/dist/computer.js.map
|
maps/dist/computer.js.map
|
||||||
/node_modules/
|
node_modules
|
||||||
|
_
|
13
CHANGELOG.md
@ -15,6 +15,19 @@
|
|||||||
- Use `WA.room.getCurrentUser(): Promise<User>` to get the ID, name and tags of the current player
|
- Use `WA.room.getCurrentUser(): Promise<User>` to get the ID, name and tags of the current player
|
||||||
- Use `WA.room.getCurrentRoom(): Promise<Room>` to get the ID, JSON map file, url of the map of the current room and the layer where the current player started
|
- Use `WA.room.getCurrentRoom(): Promise<Room>` to get the ID, JSON map file, url of the map of the current room and the layer where the current player started
|
||||||
- Use `WA.ui.registerMenuCommand(): void` to add a custom menu
|
- Use `WA.ui.registerMenuCommand(): void` to add a custom menu
|
||||||
|
- Use `WA.room.setTiles(): void` to change an array of tiles
|
||||||
|
|
||||||
|
## Version 1.4.3 - 1.4.4 - 1.4.5
|
||||||
|
|
||||||
|
## Bugfixes
|
||||||
|
|
||||||
|
- Fixing the generation of @workadventure/iframe-api-typings
|
||||||
|
|
||||||
|
## Version 1.4.2
|
||||||
|
|
||||||
|
## Updates
|
||||||
|
|
||||||
|
- A script in an iframe opened by another script can use the IFrame API.
|
||||||
|
|
||||||
## Version 1.4.1
|
## Version 1.4.1
|
||||||
|
|
||||||
|
@ -42,10 +42,19 @@ Before committing, be sure to install the "Prettier" precommit hook that will re
|
|||||||
In order to enable the "Prettier" precommit hook, at the root of the project, run:
|
In order to enable the "Prettier" precommit hook, at the root of the project, run:
|
||||||
|
|
||||||
```console
|
```console
|
||||||
$ yarn run install
|
$ yarn install
|
||||||
$ yarn run prepare
|
$ yarn run prepare
|
||||||
```
|
```
|
||||||
|
|
||||||
|
If you don't have the precommit hook installed (or if you committed code before installing the precommit hook), you will need
|
||||||
|
to run code linting manually:
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker-compose exec front yarn run pretty
|
||||||
|
$ docker-compose exec pusher yarn run pretty
|
||||||
|
$ docker-compose exec back yarn run pretty
|
||||||
|
```
|
||||||
|
|
||||||
### Providing tests
|
### Providing tests
|
||||||
|
|
||||||
WorkAdventure is based on a video game engine (Phaser), and video games are not the easiest programs to unit test.
|
WorkAdventure is based on a video game engine (Phaser), and video games are not the easiest programs to unit test.
|
||||||
|
4479
back/package-lock.json
generated
Normal file
@ -10,8 +10,8 @@
|
|||||||
"runprod": "node --max-old-space-size=4096 ./dist/server.js",
|
"runprod": "node --max-old-space-size=4096 ./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",
|
||||||
"lint": "node_modules/.bin/eslint src/ . --ext .ts",
|
"lint": "DEBUG= node_modules/.bin/eslint src/ . --ext .ts",
|
||||||
"fix": "node_modules/.bin/eslint --fix src/ . --ext .ts",
|
"fix": "DEBUG= node_modules/.bin/eslint --fix src/ . --ext .ts",
|
||||||
"precommit": "lint-staged",
|
"precommit": "lint-staged",
|
||||||
"pretty": "yarn prettier --write 'src/**/*.{ts,tsx}'",
|
"pretty": "yarn prettier --write 'src/**/*.{ts,tsx}'",
|
||||||
"pretty-check": "yarn prettier --check 'src/**/*.{ts,tsx}'"
|
"pretty-check": "yarn prettier --check 'src/**/*.{ts,tsx}'"
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
// lib/app.ts
|
// lib/app.ts
|
||||||
import {PrometheusController} from "./Controller/PrometheusController";
|
import { PrometheusController } from "./Controller/PrometheusController";
|
||||||
import {DebugController} from "./Controller/DebugController";
|
import { DebugController } from "./Controller/DebugController";
|
||||||
import {App as uwsApp} from "./Server/sifrr.server";
|
import { App as uwsApp } from "./Server/sifrr.server";
|
||||||
|
|
||||||
class App {
|
class App {
|
||||||
public app: uwsApp;
|
public app: uwsApp;
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
import {HttpResponse} from "uWebSockets.js";
|
import { HttpResponse } from "uWebSockets.js";
|
||||||
|
|
||||||
|
|
||||||
export class BaseController {
|
export class BaseController {
|
||||||
protected addCorsHeaders(res: HttpResponse): void {
|
protected addCorsHeaders(res: HttpResponse): void {
|
||||||
res.writeHeader('access-control-allow-headers', 'Origin, X-Requested-With, Content-Type, Accept');
|
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-methods", "GET, POST, OPTIONS, PUT, PATCH, DELETE");
|
||||||
res.writeHeader('access-control-allow-origin', '*');
|
res.writeHeader("access-control-allow-origin", "*");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,53 +1,54 @@
|
|||||||
import {ADMIN_API_TOKEN} from "../Enum/EnvironmentVariable";
|
import { ADMIN_API_TOKEN } from "../Enum/EnvironmentVariable";
|
||||||
import {stringify} from "circular-json";
|
import { stringify } from "circular-json";
|
||||||
import {HttpRequest, HttpResponse} from "uWebSockets.js";
|
import { HttpRequest, HttpResponse } from "uWebSockets.js";
|
||||||
import { parse } from 'query-string';
|
import { parse } from "query-string";
|
||||||
import {App} from "../Server/sifrr.server";
|
import { App } from "../Server/sifrr.server";
|
||||||
import {socketManager} from "../Services/SocketManager";
|
import { socketManager } from "../Services/SocketManager";
|
||||||
|
|
||||||
export class DebugController {
|
export class DebugController {
|
||||||
constructor(private App : App) {
|
constructor(private App: App) {
|
||||||
this.getDump();
|
this.getDump();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getDump() {
|
||||||
getDump(){
|
|
||||||
this.App.get("/dump", (res: HttpResponse, req: HttpRequest) => {
|
this.App.get("/dump", (res: HttpResponse, req: HttpRequest) => {
|
||||||
const query = parse(req.getQuery());
|
const query = parse(req.getQuery());
|
||||||
|
|
||||||
if (query.token !== ADMIN_API_TOKEN) {
|
if (query.token !== ADMIN_API_TOKEN) {
|
||||||
return res.status(401).send('Invalid token sent!');
|
return res.status(401).send("Invalid token sent!");
|
||||||
}
|
}
|
||||||
|
|
||||||
return res.writeStatus('200 OK').writeHeader('Content-Type', 'application/json').end(stringify(
|
return res
|
||||||
socketManager.getWorlds(),
|
.writeStatus("200 OK")
|
||||||
(key: unknown, value: unknown) => {
|
.writeHeader("Content-Type", "application/json")
|
||||||
if (key === 'listeners') {
|
.end(
|
||||||
return 'Listeners';
|
stringify(socketManager.getWorlds(), (key: unknown, value: unknown) => {
|
||||||
}
|
if (key === "listeners") {
|
||||||
if (key === 'socket') {
|
return "Listeners";
|
||||||
return 'Socket';
|
|
||||||
}
|
|
||||||
if (key === 'batchedMessages') {
|
|
||||||
return 'BatchedMessages';
|
|
||||||
}
|
|
||||||
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;
|
if (key === "socket") {
|
||||||
} else if(value instanceof Set) {
|
return "Socket";
|
||||||
|
}
|
||||||
|
if (key === "batchedMessages") {
|
||||||
|
return "BatchedMessages";
|
||||||
|
}
|
||||||
|
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> = [];
|
const obj: Array<unknown> = [];
|
||||||
for (const [setKey, setValue] of value.entries()) {
|
for (const [setKey, setValue] of value.entries()) {
|
||||||
obj.push(setValue);
|
obj.push(setValue);
|
||||||
}
|
}
|
||||||
return obj;
|
return obj;
|
||||||
} else {
|
} else {
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
));
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import {App} from "../Server/sifrr.server";
|
import { App } from "../Server/sifrr.server";
|
||||||
import {HttpRequest, HttpResponse} from "uWebSockets.js";
|
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: App) {
|
constructor(private App: App) {
|
||||||
@ -14,7 +14,7 @@ export class PrometheusController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private metrics(res: HttpResponse, req: HttpRequest): void {
|
private metrics(res: HttpResponse, req: HttpRequest): void {
|
||||||
res.writeHeader('Content-Type', register.contentType);
|
res.writeHeader("Content-Type", register.contentType);
|
||||||
res.end(register.metrics());
|
res.end(register.metrics());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,17 +1,17 @@
|
|||||||
const MINIMUM_DISTANCE = process.env.MINIMUM_DISTANCE ? Number(process.env.MINIMUM_DISTANCE) : 64;
|
const MINIMUM_DISTANCE = process.env.MINIMUM_DISTANCE ? Number(process.env.MINIMUM_DISTANCE) : 64;
|
||||||
const GROUP_RADIUS = process.env.GROUP_RADIUS ? Number(process.env.GROUP_RADIUS) : 48;
|
const GROUP_RADIUS = process.env.GROUP_RADIUS ? Number(process.env.GROUP_RADIUS) : 48;
|
||||||
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 || '';
|
const ADMIN_API_URL = process.env.ADMIN_API_URL || "";
|
||||||
const ADMIN_API_TOKEN = process.env.ADMIN_API_TOKEN || 'myapitoken';
|
const ADMIN_API_TOKEN = process.env.ADMIN_API_TOKEN || "myapitoken";
|
||||||
const CPU_OVERHEAT_THRESHOLD = Number(process.env.CPU_OVERHEAT_THRESHOLD) || 80;
|
const CPU_OVERHEAT_THRESHOLD = Number(process.env.CPU_OVERHEAT_THRESHOLD) || 80;
|
||||||
const JITSI_URL : string|undefined = (process.env.JITSI_URL === '') ? undefined : process.env.JITSI_URL;
|
const JITSI_URL: string | undefined = process.env.JITSI_URL === "" ? undefined : process.env.JITSI_URL;
|
||||||
const JITSI_ISS = process.env.JITSI_ISS || '';
|
const JITSI_ISS = process.env.JITSI_ISS || "";
|
||||||
const SECRET_JITSI_KEY = process.env.SECRET_JITSI_KEY || '';
|
const SECRET_JITSI_KEY = process.env.SECRET_JITSI_KEY || "";
|
||||||
const HTTP_PORT = parseInt(process.env.HTTP_PORT || '8080') || 8080;
|
const HTTP_PORT = parseInt(process.env.HTTP_PORT || "8080") || 8080;
|
||||||
const GRPC_PORT = parseInt(process.env.GRPC_PORT || '50051') || 50051;
|
const GRPC_PORT = parseInt(process.env.GRPC_PORT || "50051") || 50051;
|
||||||
export const SOCKET_IDLE_TIMER = parseInt(process.env.SOCKET_IDLE_TIMER as string) || 30; // maximum time (in second) without activity before a socket is closed
|
export const SOCKET_IDLE_TIMER = parseInt(process.env.SOCKET_IDLE_TIMER as string) || 30; // maximum time (in second) without activity before a socket is closed
|
||||||
export const TURN_STATIC_AUTH_SECRET = process.env.TURN_STATIC_AUTH_SECRET || '';
|
export const TURN_STATIC_AUTH_SECRET = process.env.TURN_STATIC_AUTH_SECRET || "";
|
||||||
export const MAX_PER_GROUP = parseInt(process.env.MAX_PER_GROUP || '4');
|
export const MAX_PER_GROUP = parseInt(process.env.MAX_PER_GROUP || "4");
|
||||||
|
|
||||||
export {
|
export {
|
||||||
MINIMUM_DISTANCE,
|
MINIMUM_DISTANCE,
|
||||||
@ -24,5 +24,5 @@ export {
|
|||||||
CPU_OVERHEAT_THRESHOLD,
|
CPU_OVERHEAT_THRESHOLD,
|
||||||
JITSI_URL,
|
JITSI_URL,
|
||||||
JITSI_ISS,
|
JITSI_ISS,
|
||||||
SECRET_JITSI_KEY
|
SECRET_JITSI_KEY,
|
||||||
}
|
};
|
||||||
|
@ -1,15 +1,12 @@
|
|||||||
import {
|
import {
|
||||||
ServerToAdminClientMessage,
|
ServerToAdminClientMessage,
|
||||||
UserJoinedRoomMessage, UserLeftRoomMessage
|
UserJoinedRoomMessage,
|
||||||
|
UserLeftRoomMessage,
|
||||||
} from "../Messages/generated/messages_pb";
|
} from "../Messages/generated/messages_pb";
|
||||||
import {AdminSocket} from "../RoomManager";
|
import { AdminSocket } from "../RoomManager";
|
||||||
|
|
||||||
|
|
||||||
export class Admin {
|
export class Admin {
|
||||||
public constructor(
|
public constructor(private readonly socket: AdminSocket) {}
|
||||||
private readonly socket: AdminSocket
|
|
||||||
) {
|
|
||||||
}
|
|
||||||
|
|
||||||
public sendUserJoin(uuid: string, name: string, ip: string): void {
|
public sendUserJoin(uuid: string, name: string, ip: string): void {
|
||||||
const serverToAdminClientMessage = new ServerToAdminClientMessage();
|
const serverToAdminClientMessage = new ServerToAdminClientMessage();
|
||||||
@ -24,7 +21,7 @@ export class Admin {
|
|||||||
this.socket.write(serverToAdminClientMessage);
|
this.socket.write(serverToAdminClientMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
public sendUserLeft(uuid: string/*, name: string, ip: string*/): void {
|
public sendUserLeft(uuid: string /*, name: string, ip: string*/): void {
|
||||||
const serverToAdminClientMessage = new ServerToAdminClientMessage();
|
const serverToAdminClientMessage = new ServerToAdminClientMessage();
|
||||||
|
|
||||||
const userLeftRoomMessage = new UserLeftRoomMessage();
|
const userLeftRoomMessage = new UserLeftRoomMessage();
|
||||||
|
@ -1,16 +1,16 @@
|
|||||||
import {PointInterface} from "./Websocket/PointInterface";
|
import { PointInterface } from "./Websocket/PointInterface";
|
||||||
import {Group} from "./Group";
|
import { Group } from "./Group";
|
||||||
import {User, UserSocket} from "./User";
|
import { User, UserSocket } from "./User";
|
||||||
import {PositionInterface} from "_Model/PositionInterface";
|
import { PositionInterface } from "_Model/PositionInterface";
|
||||||
import {EmoteCallback, EntersCallback, LeavesCallback, MovesCallback} from "_Model/Zone";
|
import { EmoteCallback, EntersCallback, LeavesCallback, MovesCallback } from "_Model/Zone";
|
||||||
import {PositionNotifier} from "./PositionNotifier";
|
import { PositionNotifier } from "./PositionNotifier";
|
||||||
import {Movable} from "_Model/Movable";
|
import { Movable } from "_Model/Movable";
|
||||||
import {extractDataFromPrivateRoomId, extractRoomSlugPublicRoomId, isRoomAnonymous} from "./RoomIdentifier";
|
import { extractDataFromPrivateRoomId, extractRoomSlugPublicRoomId, isRoomAnonymous } from "./RoomIdentifier";
|
||||||
import {arrayIntersect} from "../Services/ArrayHelper";
|
import { arrayIntersect } from "../Services/ArrayHelper";
|
||||||
import {EmoteEventMessage, JoinRoomMessage} from "../Messages/generated/messages_pb";
|
import { EmoteEventMessage, JoinRoomMessage } from "../Messages/generated/messages_pb";
|
||||||
import {ProtobufUtils} from "../Model/Websocket/ProtobufUtils";
|
import { ProtobufUtils } from "../Model/Websocket/ProtobufUtils";
|
||||||
import {ZoneSocket} from "src/RoomManager";
|
import { ZoneSocket } from "src/RoomManager";
|
||||||
import {Admin} from "../Model/Admin";
|
import { Admin } from "../Model/Admin";
|
||||||
|
|
||||||
export type ConnectCallback = (user: User, group: Group) => void;
|
export type ConnectCallback = (user: User, group: Group) => void;
|
||||||
export type DisconnectCallback = (user: User, group: Group) => void;
|
export type DisconnectCallback = (user: User, group: Group) => void;
|
||||||
@ -39,33 +39,33 @@ export class GameRoom {
|
|||||||
private readonly positionNotifier: PositionNotifier;
|
private readonly positionNotifier: PositionNotifier;
|
||||||
public readonly roomId: string;
|
public readonly roomId: string;
|
||||||
public readonly roomSlug: string;
|
public readonly roomSlug: string;
|
||||||
public readonly worldSlug: string = '';
|
public readonly worldSlug: string = "";
|
||||||
public readonly organizationSlug: string = '';
|
public readonly organizationSlug: string = "";
|
||||||
private versionNumber:number = 1;
|
private versionNumber: number = 1;
|
||||||
private nextUserId: number = 1;
|
private nextUserId: number = 1;
|
||||||
|
|
||||||
constructor(roomId: string,
|
constructor(
|
||||||
connectCallback: ConnectCallback,
|
roomId: string,
|
||||||
disconnectCallback: DisconnectCallback,
|
connectCallback: ConnectCallback,
|
||||||
minDistance: number,
|
disconnectCallback: DisconnectCallback,
|
||||||
groupRadius: number,
|
minDistance: number,
|
||||||
onEnters: EntersCallback,
|
groupRadius: number,
|
||||||
onMoves: MovesCallback,
|
onEnters: EntersCallback,
|
||||||
onLeaves: LeavesCallback,
|
onMoves: MovesCallback,
|
||||||
onEmote: EmoteCallback,
|
onLeaves: LeavesCallback,
|
||||||
|
onEmote: EmoteCallback
|
||||||
) {
|
) {
|
||||||
this.roomId = roomId;
|
this.roomId = roomId;
|
||||||
|
|
||||||
if (isRoomAnonymous(roomId)) {
|
if (isRoomAnonymous(roomId)) {
|
||||||
this.roomSlug = extractRoomSlugPublicRoomId(this.roomId);
|
this.roomSlug = extractRoomSlugPublicRoomId(this.roomId);
|
||||||
} else {
|
} else {
|
||||||
const {organizationSlug, worldSlug, roomSlug} = extractDataFromPrivateRoomId(this.roomId);
|
const { organizationSlug, worldSlug, roomSlug } = extractDataFromPrivateRoomId(this.roomId);
|
||||||
this.roomSlug = roomSlug;
|
this.roomSlug = roomSlug;
|
||||||
this.organizationSlug = organizationSlug;
|
this.organizationSlug = organizationSlug;
|
||||||
this.worldSlug = worldSlug;
|
this.worldSlug = worldSlug;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
this.users = new Map<number, User>();
|
this.users = new Map<number, User>();
|
||||||
this.usersByUuid = new Map<string, User>();
|
this.usersByUuid = new Map<string, User>();
|
||||||
this.admins = new Set<Admin>();
|
this.admins = new Set<Admin>();
|
||||||
@ -86,21 +86,22 @@ export class GameRoom {
|
|||||||
return this.users;
|
return this.users;
|
||||||
}
|
}
|
||||||
|
|
||||||
public getUserByUuid(uuid: string): User|undefined {
|
public getUserByUuid(uuid: string): User | undefined {
|
||||||
return this.usersByUuid.get(uuid);
|
return this.usersByUuid.get(uuid);
|
||||||
}
|
}
|
||||||
public getUserById(id: number): User|undefined {
|
public getUserById(id: number): User | undefined {
|
||||||
return this.users.get(id);
|
return this.users.get(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
public join(socket : UserSocket, joinRoomMessage: JoinRoomMessage): User {
|
public join(socket: UserSocket, joinRoomMessage: JoinRoomMessage): User {
|
||||||
const positionMessage = joinRoomMessage.getPositionmessage();
|
const positionMessage = joinRoomMessage.getPositionmessage();
|
||||||
if (positionMessage === undefined) {
|
if (positionMessage === undefined) {
|
||||||
throw new Error('Missing position message');
|
throw new Error("Missing position message");
|
||||||
}
|
}
|
||||||
const position = ProtobufUtils.toPointInterface(positionMessage);
|
const position = ProtobufUtils.toPointInterface(positionMessage);
|
||||||
|
|
||||||
const user = new User(this.nextUserId,
|
const user = new User(
|
||||||
|
this.nextUserId,
|
||||||
joinRoomMessage.getUseruuid(),
|
joinRoomMessage.getUseruuid(),
|
||||||
joinRoomMessage.getIpaddress(),
|
joinRoomMessage.getIpaddress(),
|
||||||
position,
|
position,
|
||||||
@ -126,12 +127,12 @@ export class GameRoom {
|
|||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
public leave(user : User){
|
public leave(user: User) {
|
||||||
const userObj = this.users.get(user.id);
|
const userObj = this.users.get(user.id);
|
||||||
if (userObj === undefined) {
|
if (userObj === undefined) {
|
||||||
console.warn('User ', user.id, 'does not belong to this game room! It should!');
|
console.warn("User ", user.id, "does not belong to this game room! It should!");
|
||||||
}
|
}
|
||||||
if (userObj !== undefined && typeof userObj.group !== 'undefined') {
|
if (userObj !== undefined && typeof userObj.group !== "undefined") {
|
||||||
this.leaveGroup(userObj);
|
this.leaveGroup(userObj);
|
||||||
}
|
}
|
||||||
this.users.delete(user.id);
|
this.users.delete(user.id);
|
||||||
@ -143,7 +144,7 @@ export class GameRoom {
|
|||||||
|
|
||||||
// Notify admins
|
// Notify admins
|
||||||
for (const admin of this.admins) {
|
for (const admin of this.admins) {
|
||||||
admin.sendUserLeft(user.uuid/*, user.name, user.IPAddress*/);
|
admin.sendUserLeft(user.uuid /*, user.name, user.IPAddress*/);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -151,7 +152,7 @@ export class GameRoom {
|
|||||||
return this.users.size === 0 && this.admins.size === 0;
|
return this.users.size === 0 && this.admins.size === 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public updatePosition(user : User, userPosition: PointInterface): void {
|
public updatePosition(user: User, userPosition: PointInterface): void {
|
||||||
user.setPosition(userPosition);
|
user.setPosition(userPosition);
|
||||||
|
|
||||||
this.updateUserGroup(user);
|
this.updateUserGroup(user);
|
||||||
@ -173,22 +174,24 @@ export class GameRoom {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const closestItem: User|Group|null = this.searchClosestAvailableUserOrGroup(user);
|
const closestItem: User | Group | null = this.searchClosestAvailableUserOrGroup(user);
|
||||||
|
|
||||||
if (closestItem !== null) {
|
if (closestItem !== null) {
|
||||||
if (closestItem instanceof Group) {
|
if (closestItem instanceof Group) {
|
||||||
// Let's join the group!
|
// Let's join the group!
|
||||||
closestItem.join(user);
|
closestItem.join(user);
|
||||||
} else {
|
} else {
|
||||||
const closestUser : User = closestItem;
|
const closestUser: User = closestItem;
|
||||||
const group: Group = new Group(this.roomId,[
|
const group: Group = new Group(
|
||||||
user,
|
this.roomId,
|
||||||
closestUser
|
[user, closestUser],
|
||||||
], this.connectCallback, this.disconnectCallback, this.positionNotifier);
|
this.connectCallback,
|
||||||
|
this.disconnectCallback,
|
||||||
|
this.positionNotifier
|
||||||
|
);
|
||||||
this.groups.add(group);
|
this.groups.add(group);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} 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?
|
||||||
@ -229,7 +232,9 @@ export class GameRoom {
|
|||||||
this.positionNotifier.leave(group);
|
this.positionNotifier.leave(group);
|
||||||
group.destroy();
|
group.destroy();
|
||||||
if (!this.groups.has(group)) {
|
if (!this.groups.has(group)) {
|
||||||
throw new Error("Could not find group "+group.getId()+" referenced by user "+user.id+" in World.");
|
throw new Error(
|
||||||
|
"Could not find group " + group.getId() + " referenced by user " + user.id + " in World."
|
||||||
|
);
|
||||||
}
|
}
|
||||||
this.groups.delete(group);
|
this.groups.delete(group);
|
||||||
//todo: is the group garbage collected?
|
//todo: is the group garbage collected?
|
||||||
@ -247,16 +252,15 @@ export class GameRoom {
|
|||||||
* OR
|
* OR
|
||||||
* - close enough to a group (distance <= groupRadius)
|
* - close enough to a group (distance <= groupRadius)
|
||||||
*/
|
*/
|
||||||
private searchClosestAvailableUserOrGroup(user: User): User|Group|null
|
private searchClosestAvailableUserOrGroup(user: User): User | Group | null {
|
||||||
{
|
|
||||||
let minimumDistanceFound: number = Math.max(this.minDistance, this.groupRadius);
|
let minimumDistanceFound: number = Math.max(this.minDistance, this.groupRadius);
|
||||||
let matchingItem: User | Group | null = null;
|
let matchingItem: User | Group | null = null;
|
||||||
this.users.forEach((currentUser, userId) => {
|
this.users.forEach((currentUser, userId) => {
|
||||||
// Let's only check users that are not part of a group
|
// Let's only check users that are not part of a group
|
||||||
if (typeof currentUser.group !== 'undefined') {
|
if (typeof currentUser.group !== "undefined") {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if(currentUser === user) {
|
if (currentUser === user) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (currentUser.silent) {
|
if (currentUser.silent) {
|
||||||
@ -265,7 +269,7 @@ export class GameRoom {
|
|||||||
|
|
||||||
const distance = GameRoom.computeDistance(user, currentUser); // compute distance between peers.
|
const distance = GameRoom.computeDistance(user, currentUser); // compute distance between peers.
|
||||||
|
|
||||||
if(distance <= minimumDistanceFound && distance <= this.minDistance) {
|
if (distance <= minimumDistanceFound && distance <= this.minDistance) {
|
||||||
minimumDistanceFound = distance;
|
minimumDistanceFound = distance;
|
||||||
matchingItem = currentUser;
|
matchingItem = currentUser;
|
||||||
}
|
}
|
||||||
@ -276,7 +280,7 @@ export class GameRoom {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const distance = GameRoom.computeDistanceBetweenPositions(user.getPosition(), group.getPosition());
|
const distance = GameRoom.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;
|
||||||
}
|
}
|
||||||
@ -285,15 +289,15 @@ export class GameRoom {
|
|||||||
return matchingItem;
|
return matchingItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static computeDistance(user1: User, user2: User): number
|
public static computeDistance(user1: User, user2: User): number {
|
||||||
{
|
|
||||||
const user1Position = user1.getPosition();
|
const user1Position = user1.getPosition();
|
||||||
const user2Position = user2.getPosition();
|
const user2Position = user2.getPosition();
|
||||||
return Math.sqrt(Math.pow(user2Position.x - user1Position.x, 2) + Math.pow(user2Position.y - user1Position.y, 2));
|
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 {
|
||||||
{
|
|
||||||
return Math.sqrt(Math.pow(position2.x - position1.x, 2) + Math.pow(position2.y - position1.y, 2));
|
return Math.sqrt(Math.pow(position2.x - position1.x, 2) + Math.pow(position2.y - position1.y, 2));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -325,9 +329,9 @@ export class GameRoom {
|
|||||||
public adminLeave(admin: Admin): void {
|
public adminLeave(admin: Admin): void {
|
||||||
this.admins.delete(admin);
|
this.admins.delete(admin);
|
||||||
}
|
}
|
||||||
|
|
||||||
public incrementVersion(): number {
|
public incrementVersion(): number {
|
||||||
this.versionNumber++
|
this.versionNumber++;
|
||||||
return this.versionNumber;
|
return this.versionNumber;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,13 +1,12 @@
|
|||||||
import { ConnectCallback, DisconnectCallback } from "./GameRoom";
|
import { ConnectCallback, DisconnectCallback } from "./GameRoom";
|
||||||
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 "_Model/Movable";
|
||||||
import {PositionNotifier} from "_Model/PositionNotifier";
|
import { PositionNotifier } from "_Model/PositionNotifier";
|
||||||
import {gaugeManager} from "../Services/GaugeManager";
|
import { gaugeManager } from "../Services/GaugeManager";
|
||||||
import {MAX_PER_GROUP} from "../Enum/EnvironmentVariable";
|
import { MAX_PER_GROUP } from "../Enum/EnvironmentVariable";
|
||||||
|
|
||||||
export class Group implements Movable {
|
export class Group implements Movable {
|
||||||
|
|
||||||
private static nextId: number = 1;
|
private static nextId: number = 1;
|
||||||
|
|
||||||
private id: number;
|
private id: number;
|
||||||
@ -18,8 +17,13 @@ export class Group implements Movable {
|
|||||||
private wasDestroyed: boolean = false;
|
private wasDestroyed: boolean = false;
|
||||||
private roomId: string;
|
private roomId: string;
|
||||||
|
|
||||||
|
constructor(
|
||||||
constructor(roomId: string, users: User[], private connectCallback: ConnectCallback, private disconnectCallback: DisconnectCallback, private positionNotifier: PositionNotifier) {
|
roomId: string,
|
||||||
|
users: User[],
|
||||||
|
private connectCallback: ConnectCallback,
|
||||||
|
private disconnectCallback: DisconnectCallback,
|
||||||
|
private positionNotifier: PositionNotifier
|
||||||
|
) {
|
||||||
this.roomId = roomId;
|
this.roomId = roomId;
|
||||||
this.users = new Set<User>();
|
this.users = new Set<User>();
|
||||||
this.id = Group.nextId;
|
this.id = Group.nextId;
|
||||||
@ -43,7 +47,7 @@ export class Group implements Movable {
|
|||||||
return Array.from(this.users.values());
|
return Array.from(this.users.values());
|
||||||
}
|
}
|
||||||
|
|
||||||
getId() : number {
|
getId(): number {
|
||||||
return this.id;
|
return this.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -53,7 +57,7 @@ export class Group implements Movable {
|
|||||||
getPosition(): PositionInterface {
|
getPosition(): PositionInterface {
|
||||||
return {
|
return {
|
||||||
x: this.x,
|
x: this.x,
|
||||||
y: this.y
|
y: this.y,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -83,7 +87,7 @@ export class Group implements Movable {
|
|||||||
if (oldX === undefined) {
|
if (oldX === undefined) {
|
||||||
this.positionNotifier.enter(this);
|
this.positionNotifier.enter(this);
|
||||||
} else {
|
} else {
|
||||||
this.positionNotifier.updatePosition(this, {x, y}, {x: oldX, y: oldY});
|
this.positionNotifier.updatePosition(this, { x, y }, { x: oldX, y: oldY });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -95,19 +99,17 @@ export class Group implements Movable {
|
|||||||
return this.users.size <= 1;
|
return this.users.size <= 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
join(user: User): void
|
join(user: User): void {
|
||||||
{
|
|
||||||
// Broadcast on the right event
|
// Broadcast on the right event
|
||||||
this.connectCallback(user, this);
|
this.connectCallback(user, this);
|
||||||
this.users.add(user);
|
this.users.add(user);
|
||||||
user.group = this;
|
user.group = this;
|
||||||
}
|
}
|
||||||
|
|
||||||
leave(user: User): void
|
leave(user: User): void {
|
||||||
{
|
|
||||||
const success = this.users.delete(user);
|
const success = this.users.delete(user);
|
||||||
if (success === false) {
|
if (success === false) {
|
||||||
throw new Error("Could not find user "+user.id+" in the group "+this.id);
|
throw new Error("Could not find user " + user.id + " in the group " + this.id);
|
||||||
}
|
}
|
||||||
user.group = undefined;
|
user.group = undefined;
|
||||||
|
|
||||||
@ -123,8 +125,7 @@ export class Group implements Movable {
|
|||||||
* Let's kick everybody out.
|
* Let's kick everybody out.
|
||||||
* Usually used when there is only one user left.
|
* Usually used when there is only one user left.
|
||||||
*/
|
*/
|
||||||
destroy(): void
|
destroy(): void {
|
||||||
{
|
|
||||||
if (this.hasEditedGauge) gaugeManager.decNbGroupsPerRoomGauge(this.roomId);
|
if (this.hasEditedGauge) gaugeManager.decNbGroupsPerRoomGauge(this.roomId);
|
||||||
for (const user of this.users) {
|
for (const user of this.users) {
|
||||||
this.leave(user);
|
this.leave(user);
|
||||||
@ -132,7 +133,7 @@ export class Group implements Movable {
|
|||||||
this.wasDestroyed = true;
|
this.wasDestroyed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
get getSize(){
|
get getSize() {
|
||||||
return this.users.size;
|
return this.users.size;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import {PositionInterface} from "_Model/PositionInterface";
|
import { PositionInterface } from "_Model/PositionInterface";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A physical object that can be placed into a Zone
|
* A physical object that can be placed into a Zone
|
||||||
*/
|
*/
|
||||||
export interface Movable {
|
export interface Movable {
|
||||||
getPosition(): PositionInterface
|
getPosition(): PositionInterface;
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
export interface PositionInterface {
|
export interface PositionInterface {
|
||||||
x: number,
|
x: number;
|
||||||
y: number
|
y: number;
|
||||||
}
|
}
|
||||||
|
@ -8,12 +8,12 @@
|
|||||||
* The PositionNotifier is important for performance. It allows us to send the position of players only to a restricted
|
* The PositionNotifier is important for performance. It allows us to send the position of players only to a restricted
|
||||||
* number of players around the current player.
|
* number of players around the current player.
|
||||||
*/
|
*/
|
||||||
import {EmoteCallback, EntersCallback, LeavesCallback, MovesCallback, Zone} from "./Zone";
|
import { EmoteCallback, EntersCallback, LeavesCallback, MovesCallback, Zone } from "./Zone";
|
||||||
import {Movable} from "_Model/Movable";
|
import { Movable } from "_Model/Movable";
|
||||||
import {PositionInterface} from "_Model/PositionInterface";
|
import { PositionInterface } from "_Model/PositionInterface";
|
||||||
import {ZoneSocket} from "../RoomManager";
|
import { ZoneSocket } from "../RoomManager";
|
||||||
import {User} from "_Model/User";
|
import { User } from "_Model/User";
|
||||||
import {EmoteEventMessage} from "../Messages/generated/messages_pb";
|
import { EmoteEventMessage } from "../Messages/generated/messages_pb";
|
||||||
|
|
||||||
interface ZoneDescriptor {
|
interface ZoneDescriptor {
|
||||||
i: number;
|
i: number;
|
||||||
@ -21,19 +21,24 @@ interface ZoneDescriptor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class PositionNotifier {
|
export class PositionNotifier {
|
||||||
|
|
||||||
// TODO: we need a way to clean the zones if noone is in the zone and noone listening (to free memory!)
|
// TODO: we need a way to clean the zones if noone is in the zone and noone listening (to free memory!)
|
||||||
|
|
||||||
private zones: Zone[][] = [];
|
private zones: Zone[][] = [];
|
||||||
|
|
||||||
constructor(private zoneWidth: number, private zoneHeight: number, private onUserEnters: EntersCallback, private onUserMoves: MovesCallback, private onUserLeaves: LeavesCallback, private onEmote: EmoteCallback) {
|
constructor(
|
||||||
}
|
private zoneWidth: number,
|
||||||
|
private zoneHeight: number,
|
||||||
|
private onUserEnters: EntersCallback,
|
||||||
|
private onUserMoves: MovesCallback,
|
||||||
|
private onUserLeaves: LeavesCallback,
|
||||||
|
private onEmote: EmoteCallback
|
||||||
|
) {}
|
||||||
|
|
||||||
private getZoneDescriptorFromCoordinates(x: number, y: number): ZoneDescriptor {
|
private getZoneDescriptorFromCoordinates(x: number, y: number): ZoneDescriptor {
|
||||||
return {
|
return {
|
||||||
i: Math.floor(x / this.zoneWidth),
|
i: Math.floor(x / this.zoneWidth),
|
||||||
j: Math.floor(y / this.zoneHeight),
|
j: Math.floor(y / this.zoneHeight),
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public enter(thing: Movable): void {
|
public enter(thing: Movable): void {
|
||||||
@ -100,6 +105,5 @@ export class PositionNotifier {
|
|||||||
const zoneDesc = this.getZoneDescriptorFromCoordinates(user.getPosition().x, user.getPosition().y);
|
const zoneDesc = this.getZoneDescriptorFromCoordinates(user.getPosition().x, user.getPosition().y);
|
||||||
const zone = this.getZone(zoneDesc.i, zoneDesc.j);
|
const zone = this.getZone(zoneDesc.i, zoneDesc.j);
|
||||||
zone.emitEmoteEvent(emoteEventMessage);
|
zone.emitEmoteEvent(emoteEventMessage);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,30 +1,30 @@
|
|||||||
//helper functions to parse room IDs
|
//helper functions to parse room IDs
|
||||||
|
|
||||||
export const isRoomAnonymous = (roomID: string): boolean => {
|
export const isRoomAnonymous = (roomID: string): boolean => {
|
||||||
if (roomID.startsWith('_/')) {
|
if (roomID.startsWith("_/")) {
|
||||||
return true;
|
return true;
|
||||||
} else if(roomID.startsWith('@/')) {
|
} else if (roomID.startsWith("@/")) {
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
throw new Error('Incorrect room ID: '+roomID);
|
throw new Error("Incorrect room ID: " + roomID);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
export const extractRoomSlugPublicRoomId = (roomId: string): string => {
|
export const extractRoomSlugPublicRoomId = (roomId: string): string => {
|
||||||
const idParts = roomId.split('/');
|
const idParts = roomId.split("/");
|
||||||
if (idParts.length < 3) throw new Error('Incorrect roomId: '+roomId);
|
if (idParts.length < 3) throw new Error("Incorrect roomId: " + roomId);
|
||||||
return idParts.slice(2).join('/');
|
return idParts.slice(2).join("/");
|
||||||
}
|
};
|
||||||
export interface extractDataFromPrivateRoomIdResponse {
|
export interface extractDataFromPrivateRoomIdResponse {
|
||||||
organizationSlug: string;
|
organizationSlug: string;
|
||||||
worldSlug: string;
|
worldSlug: string;
|
||||||
roomSlug: string;
|
roomSlug: string;
|
||||||
}
|
}
|
||||||
export const extractDataFromPrivateRoomId = (roomId: string): extractDataFromPrivateRoomIdResponse => {
|
export const extractDataFromPrivateRoomId = (roomId: string): extractDataFromPrivateRoomIdResponse => {
|
||||||
const idParts = roomId.split('/');
|
const idParts = roomId.split("/");
|
||||||
if (idParts.length < 4) throw new Error('Incorrect roomId: '+roomId);
|
if (idParts.length < 4) throw new Error("Incorrect roomId: " + roomId);
|
||||||
const organizationSlug = idParts[1];
|
const organizationSlug = idParts[1];
|
||||||
const worldSlug = idParts[2];
|
const worldSlug = idParts[2];
|
||||||
const roomSlug = idParts[3];
|
const roomSlug = idParts[3];
|
||||||
return {organizationSlug, worldSlug, roomSlug}
|
return { organizationSlug, worldSlug, roomSlug };
|
||||||
}
|
};
|
||||||
|
@ -1,11 +1,17 @@
|
|||||||
import { Group } from "./Group";
|
import { Group } from "./Group";
|
||||||
import { PointInterface } from "./Websocket/PointInterface";
|
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 {PositionNotifier} from "_Model/PositionNotifier";
|
import { PositionNotifier } from "_Model/PositionNotifier";
|
||||||
import {ServerDuplexStream} from "grpc";
|
import { ServerDuplexStream } from "grpc";
|
||||||
import {BatchMessage, CompanionMessage, PusherToBackMessage, ServerToClientMessage, SubMessage} from "../Messages/generated/messages_pb";
|
import {
|
||||||
import {CharacterLayer} from "_Model/Websocket/CharacterLayer";
|
BatchMessage,
|
||||||
|
CompanionMessage,
|
||||||
|
PusherToBackMessage,
|
||||||
|
ServerToClientMessage,
|
||||||
|
SubMessage,
|
||||||
|
} from "../Messages/generated/messages_pb";
|
||||||
|
import { CharacterLayer } from "_Model/Websocket/CharacterLayer";
|
||||||
|
|
||||||
export type UserSocket = ServerDuplexStream<PusherToBackMessage, ServerToClientMessage>;
|
export type UserSocket = ServerDuplexStream<PusherToBackMessage, ServerToClientMessage>;
|
||||||
|
|
||||||
@ -22,7 +28,7 @@ export class User implements Movable {
|
|||||||
private positionNotifier: PositionNotifier,
|
private positionNotifier: PositionNotifier,
|
||||||
public readonly socket: UserSocket,
|
public readonly socket: UserSocket,
|
||||||
public readonly tags: string[],
|
public readonly tags: string[],
|
||||||
public readonly visitCardUrl: string|null,
|
public readonly visitCardUrl: string | null,
|
||||||
public readonly name: string,
|
public readonly name: string,
|
||||||
public readonly characterLayers: CharacterLayer[],
|
public readonly characterLayers: CharacterLayer[],
|
||||||
public readonly companion?: CompanionMessage
|
public readonly companion?: CompanionMessage
|
||||||
@ -42,9 +48,8 @@ export class User implements Movable {
|
|||||||
this.positionNotifier.updatePosition(this, position, oldPosition);
|
this.positionNotifier.updatePosition(this, position, oldPosition);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private batchedMessages: BatchMessage = new BatchMessage();
|
private batchedMessages: BatchMessage = new BatchMessage();
|
||||||
private batchTimeout: NodeJS.Timeout|null = null;
|
private batchTimeout: NodeJS.Timeout | null = null;
|
||||||
|
|
||||||
public emitInBatch(payload: SubMessage): void {
|
public emitInBatch(payload: SubMessage): void {
|
||||||
this.batchedMessages.addPayload(payload);
|
this.batchedMessages.addPayload(payload);
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
export interface CharacterLayer {
|
export interface CharacterLayer {
|
||||||
name: string,
|
name: string;
|
||||||
url: string|undefined
|
url: string | undefined;
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
import * as tg from "generic-type-guard";
|
import * as tg from "generic-type-guard";
|
||||||
|
|
||||||
export const isItemEventMessageInterface =
|
export const isItemEventMessageInterface = new tg.IsInterface()
|
||||||
new tg.IsInterface().withProperties({
|
.withProperties({
|
||||||
itemId: tg.isNumber,
|
itemId: tg.isNumber,
|
||||||
event: tg.isString,
|
event: tg.isString,
|
||||||
state: tg.isUnknown,
|
state: tg.isUnknown,
|
||||||
parameters: tg.isUnknown,
|
parameters: tg.isUnknown,
|
||||||
}).get();
|
})
|
||||||
|
.get();
|
||||||
export type ItemEventMessageInterface = tg.GuardedType<typeof isItemEventMessageInterface>;
|
export type ItemEventMessageInterface = tg.GuardedType<typeof isItemEventMessageInterface>;
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
import {PointInterface} from "./PointInterface";
|
import { PointInterface } from "./PointInterface";
|
||||||
|
|
||||||
export class Point implements PointInterface{
|
export class Point implements PointInterface {
|
||||||
constructor(public x : number, public y : number, public direction : string = "none", public moving : boolean = false) {
|
constructor(
|
||||||
}
|
public x: number,
|
||||||
|
public y: number,
|
||||||
|
public direction: string = "none",
|
||||||
|
public moving: boolean = false
|
||||||
|
) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,11 +7,12 @@ import * as tg from "generic-type-guard";
|
|||||||
readonly moving: boolean;
|
readonly moving: boolean;
|
||||||
}*/
|
}*/
|
||||||
|
|
||||||
export const isPointInterface =
|
export const isPointInterface = new tg.IsInterface()
|
||||||
new tg.IsInterface().withProperties({
|
.withProperties({
|
||||||
x: tg.isNumber,
|
x: tg.isNumber,
|
||||||
y: tg.isNumber,
|
y: tg.isNumber,
|
||||||
direction: tg.isString,
|
direction: tg.isString,
|
||||||
moving: tg.isBoolean
|
moving: tg.isBoolean,
|
||||||
}).get();
|
})
|
||||||
|
.get();
|
||||||
export type PointInterface = tg.GuardedType<typeof isPointInterface>;
|
export type PointInterface = tg.GuardedType<typeof isPointInterface>;
|
||||||
|
@ -1,34 +1,33 @@
|
|||||||
import {PointInterface} from "./PointInterface";
|
import { PointInterface } from "./PointInterface";
|
||||||
import {
|
import {
|
||||||
CharacterLayerMessage,
|
CharacterLayerMessage,
|
||||||
ItemEventMessage,
|
ItemEventMessage,
|
||||||
PointMessage,
|
PointMessage,
|
||||||
PositionMessage
|
PositionMessage,
|
||||||
} from "../../Messages/generated/messages_pb";
|
} from "../../Messages/generated/messages_pb";
|
||||||
import {CharacterLayer} from "_Model/Websocket/CharacterLayer";
|
import { CharacterLayer } from "_Model/Websocket/CharacterLayer";
|
||||||
import Direction = PositionMessage.Direction;
|
import Direction = PositionMessage.Direction;
|
||||||
import {ItemEventMessageInterface} from "_Model/Websocket/ItemEventMessage";
|
import { ItemEventMessageInterface } from "_Model/Websocket/ItemEventMessage";
|
||||||
import {PositionInterface} from "_Model/PositionInterface";
|
import { PositionInterface } from "_Model/PositionInterface";
|
||||||
|
|
||||||
export class ProtobufUtils {
|
export class ProtobufUtils {
|
||||||
|
|
||||||
public static toPositionMessage(point: PointInterface): PositionMessage {
|
public static toPositionMessage(point: PointInterface): PositionMessage {
|
||||||
let direction: Direction;
|
let direction: Direction;
|
||||||
switch (point.direction) {
|
switch (point.direction) {
|
||||||
case 'up':
|
case "up":
|
||||||
direction = Direction.UP;
|
direction = Direction.UP;
|
||||||
break;
|
break;
|
||||||
case 'down':
|
case "down":
|
||||||
direction = Direction.DOWN;
|
direction = Direction.DOWN;
|
||||||
break;
|
break;
|
||||||
case 'left':
|
case "left":
|
||||||
direction = Direction.LEFT;
|
direction = Direction.LEFT;
|
||||||
break;
|
break;
|
||||||
case 'right':
|
case "right":
|
||||||
direction = Direction.RIGHT;
|
direction = Direction.RIGHT;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new Error('unexpected direction');
|
throw new Error("unexpected direction");
|
||||||
}
|
}
|
||||||
|
|
||||||
const position = new PositionMessage();
|
const position = new PositionMessage();
|
||||||
@ -44,16 +43,16 @@ export class ProtobufUtils {
|
|||||||
let direction: string;
|
let direction: string;
|
||||||
switch (position.getDirection()) {
|
switch (position.getDirection()) {
|
||||||
case Direction.UP:
|
case Direction.UP:
|
||||||
direction = 'up';
|
direction = "up";
|
||||||
break;
|
break;
|
||||||
case Direction.DOWN:
|
case Direction.DOWN:
|
||||||
direction = 'down';
|
direction = "down";
|
||||||
break;
|
break;
|
||||||
case Direction.LEFT:
|
case Direction.LEFT:
|
||||||
direction = 'left';
|
direction = "left";
|
||||||
break;
|
break;
|
||||||
case Direction.RIGHT:
|
case Direction.RIGHT:
|
||||||
direction = 'right';
|
direction = "right";
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new Error("Unexpected direction");
|
throw new Error("Unexpected direction");
|
||||||
@ -82,7 +81,7 @@ export class ProtobufUtils {
|
|||||||
event: itemEventMessage.getEvent(),
|
event: itemEventMessage.getEvent(),
|
||||||
parameters: JSON.parse(itemEventMessage.getParametersjson()),
|
parameters: JSON.parse(itemEventMessage.getParametersjson()),
|
||||||
state: JSON.parse(itemEventMessage.getStatejson()),
|
state: JSON.parse(itemEventMessage.getStatejson()),
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public static toItemEventProtobuf(itemEvent: ItemEventMessageInterface): ItemEventMessage {
|
public static toItemEventProtobuf(itemEvent: ItemEventMessageInterface): ItemEventMessage {
|
||||||
@ -96,7 +95,7 @@ export class ProtobufUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static toCharacterLayerMessages(characterLayers: CharacterLayer[]): CharacterLayerMessage[] {
|
public static toCharacterLayerMessages(characterLayers: CharacterLayer[]): CharacterLayerMessage[] {
|
||||||
return characterLayers.map(function(characterLayer): CharacterLayerMessage {
|
return characterLayers.map(function (characterLayer): CharacterLayerMessage {
|
||||||
const message = new CharacterLayerMessage();
|
const message = new CharacterLayerMessage();
|
||||||
message.setName(characterLayer.name);
|
message.setName(characterLayer.name);
|
||||||
if (characterLayer.url) {
|
if (characterLayer.url) {
|
||||||
@ -107,7 +106,7 @@ export class ProtobufUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static toCharacterLayerObjects(characterLayers: CharacterLayerMessage[]): CharacterLayer[] {
|
public static toCharacterLayerObjects(characterLayers: CharacterLayerMessage[]): CharacterLayer[] {
|
||||||
return characterLayers.map(function(characterLayer): CharacterLayer {
|
return characterLayers.map(function (characterLayer): CharacterLayer {
|
||||||
const url = characterLayer.getUrl();
|
const url = characterLayer.getUrl();
|
||||||
return {
|
return {
|
||||||
name: characterLayer.getName(),
|
name: characterLayer.getName(),
|
||||||
|
@ -1,35 +1,52 @@
|
|||||||
import {User} from "./User";
|
import { User } from "./User";
|
||||||
import {PositionInterface} from "_Model/PositionInterface";
|
import { PositionInterface } from "_Model/PositionInterface";
|
||||||
import {Movable} from "./Movable";
|
import { Movable } from "./Movable";
|
||||||
import {Group} from "./Group";
|
import { Group } from "./Group";
|
||||||
import {ZoneSocket} from "../RoomManager";
|
import { ZoneSocket } from "../RoomManager";
|
||||||
import {EmoteEventMessage} from "../Messages/generated/messages_pb";
|
import { EmoteEventMessage } from "../Messages/generated/messages_pb";
|
||||||
|
|
||||||
export type EntersCallback = (thing: Movable, fromZone: Zone|null, listener: ZoneSocket) => void;
|
export type EntersCallback = (thing: Movable, fromZone: Zone | null, listener: ZoneSocket) => void;
|
||||||
export type MovesCallback = (thing: Movable, position: PositionInterface, listener: ZoneSocket) => void;
|
export type MovesCallback = (thing: Movable, position: PositionInterface, listener: ZoneSocket) => void;
|
||||||
export type LeavesCallback = (thing: Movable, newZone: Zone|null, listener: ZoneSocket) => void;
|
export type LeavesCallback = (thing: Movable, newZone: Zone | null, listener: ZoneSocket) => void;
|
||||||
export type EmoteCallback = (emoteEventMessage: EmoteEventMessage, listener: ZoneSocket) => void;
|
export type EmoteCallback = (emoteEventMessage: EmoteEventMessage, listener: ZoneSocket) => void;
|
||||||
|
|
||||||
export class Zone {
|
export class Zone {
|
||||||
private things: Set<Movable> = new Set<Movable>();
|
private things: Set<Movable> = new Set<Movable>();
|
||||||
private listeners: Set<ZoneSocket> = new Set<ZoneSocket>();
|
private listeners: Set<ZoneSocket> = new Set<ZoneSocket>();
|
||||||
|
|
||||||
|
constructor(
|
||||||
constructor(private onEnters: EntersCallback, private onMoves: MovesCallback, private onLeaves: LeavesCallback, private onEmote: EmoteCallback, public readonly x: number, public readonly y: number) { }
|
private onEnters: EntersCallback,
|
||||||
|
private onMoves: MovesCallback,
|
||||||
|
private onLeaves: LeavesCallback,
|
||||||
|
private onEmote: EmoteCallback,
|
||||||
|
public readonly x: number,
|
||||||
|
public readonly 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) {
|
||||||
const result = this.things.delete(thing);
|
const result = this.things.delete(thing);
|
||||||
if (!result) {
|
if (!result) {
|
||||||
if (thing instanceof User) {
|
if (thing instanceof User) {
|
||||||
throw new Error('Could not find user in zone '+thing.id);
|
throw new Error("Could not find user in zone " + thing.id);
|
||||||
}
|
}
|
||||||
if (thing instanceof Group) {
|
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+')');
|
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);
|
||||||
}
|
}
|
||||||
@ -37,13 +54,13 @@ export class Zone {
|
|||||||
/**
|
/**
|
||||||
* Notify listeners of this zone that this user/thing left
|
* Notify listeners of this zone that this user/thing left
|
||||||
*/
|
*/
|
||||||
private notifyLeft(thing: Movable, newZone: Zone|null) {
|
private notifyLeft(thing: Movable, newZone: Zone | null) {
|
||||||
for (const listener of this.listeners) {
|
for (const listener of this.listeners) {
|
||||||
this.onLeaves(thing, newZone, listener);
|
this.onLeaves(thing, newZone, listener);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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.notifyEnter(thing, oldZone, position);
|
this.notifyEnter(thing, oldZone, position);
|
||||||
}
|
}
|
||||||
@ -51,13 +68,12 @@ export class Zone {
|
|||||||
/**
|
/**
|
||||||
* Notify listeners of this zone that this user entered
|
* Notify listeners of this zone that this user entered
|
||||||
*/
|
*/
|
||||||
private notifyEnter(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) {
|
||||||
this.onEnters(thing, oldZone, listener);
|
this.onEnters(thing, oldZone, listener);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
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);
|
||||||
@ -67,7 +83,7 @@ export class Zone {
|
|||||||
|
|
||||||
for (const listener of this.listeners) {
|
for (const listener of this.listeners) {
|
||||||
//if (listener !== thing) {
|
//if (listener !== thing) {
|
||||||
this.onMoves(thing,position, listener);
|
this.onMoves(thing, position, listener);
|
||||||
//}
|
//}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -89,6 +105,5 @@ export class Zone {
|
|||||||
for (const listener of this.listeners) {
|
for (const listener of this.listeners) {
|
||||||
this.onEmote(emoteEventMessage, listener);
|
this.onEmote(emoteEventMessage, listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import {IRoomManagerServer} from "./Messages/generated/messages_grpc_pb";
|
import { IRoomManagerServer } from "./Messages/generated/messages_grpc_pb";
|
||||||
import {
|
import {
|
||||||
AdminGlobalMessage,
|
AdminGlobalMessage,
|
||||||
AdminMessage,
|
AdminMessage,
|
||||||
@ -11,92 +11,114 @@ import {
|
|||||||
JoinRoomMessage,
|
JoinRoomMessage,
|
||||||
PlayGlobalMessage,
|
PlayGlobalMessage,
|
||||||
PusherToBackMessage,
|
PusherToBackMessage,
|
||||||
QueryJitsiJwtMessage, RefreshRoomPromptMessage,
|
QueryJitsiJwtMessage,
|
||||||
|
RefreshRoomPromptMessage,
|
||||||
ServerToAdminClientMessage,
|
ServerToAdminClientMessage,
|
||||||
ServerToClientMessage,
|
ServerToClientMessage,
|
||||||
SilentMessage,
|
SilentMessage,
|
||||||
UserMovesMessage,
|
UserMovesMessage,
|
||||||
WebRtcSignalToServerMessage, WorldFullWarningToRoomMessage,
|
WebRtcSignalToServerMessage,
|
||||||
ZoneMessage
|
WorldFullWarningToRoomMessage,
|
||||||
|
ZoneMessage,
|
||||||
} from "./Messages/generated/messages_pb";
|
} from "./Messages/generated/messages_pb";
|
||||||
import {sendUnaryData, ServerDuplexStream, ServerUnaryCall, ServerWritableStream} from "grpc";
|
import { sendUnaryData, ServerDuplexStream, ServerUnaryCall, ServerWritableStream } from "grpc";
|
||||||
import {socketManager} from "./Services/SocketManager";
|
import { socketManager } from "./Services/SocketManager";
|
||||||
import {emitError} from "./Services/MessageHelpers";
|
import { emitError } from "./Services/MessageHelpers";
|
||||||
import {User, UserSocket} from "./Model/User";
|
import { User, UserSocket } from "./Model/User";
|
||||||
import {GameRoom} from "./Model/GameRoom";
|
import { GameRoom } from "./Model/GameRoom";
|
||||||
import Debug from "debug";
|
import Debug from "debug";
|
||||||
import {Admin} from "./Model/Admin";
|
import { Admin } from "./Model/Admin";
|
||||||
|
|
||||||
const debug = Debug('roommanager');
|
const debug = Debug("roommanager");
|
||||||
|
|
||||||
export type AdminSocket = ServerDuplexStream<AdminPusherToBackMessage, ServerToAdminClientMessage>;
|
export type AdminSocket = ServerDuplexStream<AdminPusherToBackMessage, ServerToAdminClientMessage>;
|
||||||
export type ZoneSocket = ServerWritableStream<ZoneMessage, ServerToClientMessage>;
|
export type ZoneSocket = ServerWritableStream<ZoneMessage, ServerToClientMessage>;
|
||||||
|
|
||||||
const roomManager: IRoomManagerServer = {
|
const roomManager: IRoomManagerServer = {
|
||||||
joinRoom: (call: UserSocket): void => {
|
joinRoom: (call: UserSocket): void => {
|
||||||
console.log('joinRoom called');
|
console.log("joinRoom called");
|
||||||
|
|
||||||
let room: GameRoom|null = null;
|
let room: GameRoom | null = null;
|
||||||
let user: User|null = null;
|
let user: User | null = null;
|
||||||
|
|
||||||
call.on('data', (message: PusherToBackMessage) => {
|
call.on("data", (message: PusherToBackMessage) => {
|
||||||
try {
|
try {
|
||||||
if (room === null || user === null) {
|
if (room === null || user === null) {
|
||||||
if (message.hasJoinroommessage()) {
|
if (message.hasJoinroommessage()) {
|
||||||
socketManager.handleJoinRoom(call, message.getJoinroommessage() as JoinRoomMessage).then(({room: gameRoom, user: myUser}) => {
|
socketManager
|
||||||
if (call.writable) {
|
.handleJoinRoom(call, message.getJoinroommessage() as JoinRoomMessage)
|
||||||
room = gameRoom;
|
.then(({ room: gameRoom, user: myUser }) => {
|
||||||
user = myUser;
|
if (call.writable) {
|
||||||
} else {
|
room = gameRoom;
|
||||||
//Connexion may have been closed before the init was finished, so we have to manually disconnect the user.
|
user = myUser;
|
||||||
socketManager.leaveRoom(gameRoom, myUser);
|
} else {
|
||||||
}
|
//Connexion may have been closed before the init was finished, so we have to manually disconnect the user.
|
||||||
});
|
socketManager.leaveRoom(gameRoom, myUser);
|
||||||
|
}
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
throw new Error('The first message sent MUST be of type JoinRoomMessage');
|
throw new Error("The first message sent MUST be of type JoinRoomMessage");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (message.hasJoinroommessage()) {
|
if (message.hasJoinroommessage()) {
|
||||||
throw new Error('Cannot call JoinRoomMessage twice!');
|
throw new Error("Cannot call JoinRoomMessage twice!");
|
||||||
} else if (message.hasUsermovesmessage()) {
|
} else if (message.hasUsermovesmessage()) {
|
||||||
socketManager.handleUserMovesMessage(room, user, message.getUsermovesmessage() as UserMovesMessage);
|
socketManager.handleUserMovesMessage(
|
||||||
|
room,
|
||||||
|
user,
|
||||||
|
message.getUsermovesmessage() as UserMovesMessage
|
||||||
|
);
|
||||||
} else if (message.hasSilentmessage()) {
|
} else if (message.hasSilentmessage()) {
|
||||||
socketManager.handleSilentMessage(room, user, message.getSilentmessage() as SilentMessage);
|
socketManager.handleSilentMessage(room, user, message.getSilentmessage() as SilentMessage);
|
||||||
} else if (message.hasItemeventmessage()) {
|
} else if (message.hasItemeventmessage()) {
|
||||||
socketManager.handleItemEvent(room, user, message.getItemeventmessage() as ItemEventMessage);
|
socketManager.handleItemEvent(room, user, message.getItemeventmessage() as ItemEventMessage);
|
||||||
} else if (message.hasWebrtcsignaltoservermessage()) {
|
} else if (message.hasWebrtcsignaltoservermessage()) {
|
||||||
socketManager.emitVideo(room, user, message.getWebrtcsignaltoservermessage() as WebRtcSignalToServerMessage);
|
socketManager.emitVideo(
|
||||||
|
room,
|
||||||
|
user,
|
||||||
|
message.getWebrtcsignaltoservermessage() as WebRtcSignalToServerMessage
|
||||||
|
);
|
||||||
} else if (message.hasWebrtcscreensharingsignaltoservermessage()) {
|
} else if (message.hasWebrtcscreensharingsignaltoservermessage()) {
|
||||||
socketManager.emitScreenSharing(room, user, message.getWebrtcscreensharingsignaltoservermessage() as WebRtcSignalToServerMessage);
|
socketManager.emitScreenSharing(
|
||||||
|
room,
|
||||||
|
user,
|
||||||
|
message.getWebrtcscreensharingsignaltoservermessage() as WebRtcSignalToServerMessage
|
||||||
|
);
|
||||||
} else if (message.hasPlayglobalmessage()) {
|
} else if (message.hasPlayglobalmessage()) {
|
||||||
socketManager.emitPlayGlobalMessage(room, message.getPlayglobalmessage() as PlayGlobalMessage);
|
socketManager.emitPlayGlobalMessage(room, message.getPlayglobalmessage() as PlayGlobalMessage);
|
||||||
} else if (message.hasQueryjitsijwtmessage()){
|
} else if (message.hasQueryjitsijwtmessage()) {
|
||||||
socketManager.handleQueryJitsiJwtMessage(user, message.getQueryjitsijwtmessage() as QueryJitsiJwtMessage);
|
socketManager.handleQueryJitsiJwtMessage(
|
||||||
} else if (message.hasEmotepromptmessage()){
|
user,
|
||||||
socketManager.handleEmoteEventMessage(room, user, message.getEmotepromptmessage() as EmotePromptMessage);
|
message.getQueryjitsijwtmessage() as QueryJitsiJwtMessage
|
||||||
}else if (message.hasSendusermessage()) {
|
);
|
||||||
|
} else if (message.hasEmotepromptmessage()) {
|
||||||
|
socketManager.handleEmoteEventMessage(
|
||||||
|
room,
|
||||||
|
user,
|
||||||
|
message.getEmotepromptmessage() as EmotePromptMessage
|
||||||
|
);
|
||||||
|
} else if (message.hasSendusermessage()) {
|
||||||
const sendUserMessage = message.getSendusermessage();
|
const sendUserMessage = message.getSendusermessage();
|
||||||
if(sendUserMessage !== undefined) {
|
if (sendUserMessage !== undefined) {
|
||||||
socketManager.handlerSendUserMessage(user, sendUserMessage);
|
socketManager.handlerSendUserMessage(user, sendUserMessage);
|
||||||
}
|
}
|
||||||
}else if (message.hasBanusermessage()) {
|
} else if (message.hasBanusermessage()) {
|
||||||
const banUserMessage = message.getBanusermessage();
|
const banUserMessage = message.getBanusermessage();
|
||||||
if(banUserMessage !== undefined) {
|
if (banUserMessage !== undefined) {
|
||||||
socketManager.handlerBanUserMessage(room, user, banUserMessage);
|
socketManager.handlerBanUserMessage(room, user, banUserMessage);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
throw new Error('Unhandled message type');
|
throw new Error("Unhandled message type");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
emitError(call, e);
|
emitError(call, e);
|
||||||
call.end();
|
call.end();
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
call.on('end', () => {
|
call.on("end", () => {
|
||||||
debug('joinRoom ended');
|
debug("joinRoom ended");
|
||||||
if (user !== null && room !== null) {
|
if (user !== null && room !== null) {
|
||||||
socketManager.leaveRoom(room, user);
|
socketManager.leaveRoom(room, user);
|
||||||
}
|
}
|
||||||
@ -105,41 +127,40 @@ const roomManager: IRoomManagerServer = {
|
|||||||
user = null;
|
user = null;
|
||||||
});
|
});
|
||||||
|
|
||||||
call.on('error', (err: Error) => {
|
call.on("error", (err: Error) => {
|
||||||
console.error('An error occurred in joinRoom stream:', err);
|
console.error("An error occurred in joinRoom stream:", err);
|
||||||
});
|
});
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
listenZone(call: ZoneSocket): void {
|
listenZone(call: ZoneSocket): void {
|
||||||
debug('listenZone called');
|
debug("listenZone called");
|
||||||
const zoneMessage = call.request;
|
const zoneMessage = call.request;
|
||||||
|
|
||||||
socketManager.addZoneListener(call, zoneMessage.getRoomid(), zoneMessage.getX(), zoneMessage.getY());
|
socketManager.addZoneListener(call, zoneMessage.getRoomid(), zoneMessage.getX(), zoneMessage.getY());
|
||||||
|
|
||||||
call.on('cancelled', () => {
|
call.on("cancelled", () => {
|
||||||
debug('listenZone cancelled');
|
debug("listenZone cancelled");
|
||||||
socketManager.removeZoneListener(call, zoneMessage.getRoomid(), zoneMessage.getX(), zoneMessage.getY());
|
socketManager.removeZoneListener(call, zoneMessage.getRoomid(), zoneMessage.getX(), zoneMessage.getY());
|
||||||
call.end();
|
call.end();
|
||||||
})
|
});
|
||||||
|
|
||||||
call.on('close', () => {
|
call.on("close", () => {
|
||||||
debug('listenZone connection closed');
|
debug("listenZone connection closed");
|
||||||
socketManager.removeZoneListener(call, zoneMessage.getRoomid(), zoneMessage.getX(), zoneMessage.getY());
|
socketManager.removeZoneListener(call, zoneMessage.getRoomid(), zoneMessage.getX(), zoneMessage.getY());
|
||||||
}).on('error', (e) => {
|
}).on("error", (e) => {
|
||||||
console.error('An error occurred in listenZone stream:', e);
|
console.error("An error occurred in listenZone stream:", e);
|
||||||
socketManager.removeZoneListener(call, zoneMessage.getRoomid(), zoneMessage.getX(), zoneMessage.getY());
|
socketManager.removeZoneListener(call, zoneMessage.getRoomid(), zoneMessage.getX(), zoneMessage.getY());
|
||||||
call.end();
|
call.end();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
adminRoom(call: AdminSocket): void {
|
adminRoom(call: AdminSocket): void {
|
||||||
console.log('adminRoom called');
|
console.log("adminRoom called");
|
||||||
|
|
||||||
const admin = new Admin(call);
|
const admin = new Admin(call);
|
||||||
let room: GameRoom|null = null;
|
let room: GameRoom | null = null;
|
||||||
|
|
||||||
call.on('data', (message: AdminPusherToBackMessage) => {
|
call.on("data", (message: AdminPusherToBackMessage) => {
|
||||||
try {
|
try {
|
||||||
if (room === null) {
|
if (room === null) {
|
||||||
if (message.hasSubscribetoroom()) {
|
if (message.hasSubscribetoroom()) {
|
||||||
@ -148,18 +169,17 @@ const roomManager: IRoomManagerServer = {
|
|||||||
room = gameRoom;
|
room = gameRoom;
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
throw new Error('The first message sent MUST be of type JoinRoomMessage');
|
throw new Error("The first message sent MUST be of type JoinRoomMessage");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
emitError(call, e);
|
emitError(call, e);
|
||||||
call.end();
|
call.end();
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
call.on('end', () => {
|
call.on("end", () => {
|
||||||
debug('joinRoom ended');
|
debug("joinRoom ended");
|
||||||
if (room !== null) {
|
if (room !== null) {
|
||||||
socketManager.leaveAdminRoom(room, admin);
|
socketManager.leaveAdminRoom(room, admin);
|
||||||
}
|
}
|
||||||
@ -167,18 +187,21 @@ const roomManager: IRoomManagerServer = {
|
|||||||
room = null;
|
room = null;
|
||||||
});
|
});
|
||||||
|
|
||||||
call.on('error', (err: Error) => {
|
call.on("error", (err: Error) => {
|
||||||
console.error('An error occurred in joinAdminRoom stream:', err);
|
console.error("An error occurred in joinAdminRoom stream:", err);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
sendAdminMessage(call: ServerUnaryCall<AdminMessage>, callback: sendUnaryData<EmptyMessage>): void {
|
sendAdminMessage(call: ServerUnaryCall<AdminMessage>, callback: sendUnaryData<EmptyMessage>): void {
|
||||||
|
socketManager.sendAdminMessage(
|
||||||
socketManager.sendAdminMessage(call.request.getRoomid(), call.request.getRecipientuuid(), call.request.getMessage());
|
call.request.getRoomid(),
|
||||||
|
call.request.getRecipientuuid(),
|
||||||
|
call.request.getMessage()
|
||||||
|
);
|
||||||
|
|
||||||
callback(null, new EmptyMessage());
|
callback(null, new EmptyMessage());
|
||||||
},
|
},
|
||||||
sendGlobalAdminMessage(call: ServerUnaryCall<AdminGlobalMessage>, callback: sendUnaryData<EmptyMessage>): void {
|
sendGlobalAdminMessage(call: ServerUnaryCall<AdminGlobalMessage>, callback: sendUnaryData<EmptyMessage>): void {
|
||||||
throw new Error('Not implemented yet');
|
throw new Error("Not implemented yet");
|
||||||
// TODO
|
// TODO
|
||||||
callback(null, new EmptyMessage());
|
callback(null, new EmptyMessage());
|
||||||
},
|
},
|
||||||
@ -192,14 +215,20 @@ const roomManager: IRoomManagerServer = {
|
|||||||
socketManager.sendAdminRoomMessage(call.request.getRoomid(), call.request.getMessage());
|
socketManager.sendAdminRoomMessage(call.request.getRoomid(), call.request.getMessage());
|
||||||
callback(null, new EmptyMessage());
|
callback(null, new EmptyMessage());
|
||||||
},
|
},
|
||||||
sendWorldFullWarningToRoom(call: ServerUnaryCall<WorldFullWarningToRoomMessage>, callback: sendUnaryData<EmptyMessage>): void {
|
sendWorldFullWarningToRoom(
|
||||||
|
call: ServerUnaryCall<WorldFullWarningToRoomMessage>,
|
||||||
|
callback: sendUnaryData<EmptyMessage>
|
||||||
|
): void {
|
||||||
socketManager.dispatchWorlFullWarning(call.request.getRoomid());
|
socketManager.dispatchWorlFullWarning(call.request.getRoomid());
|
||||||
callback(null, new EmptyMessage());
|
callback(null, new EmptyMessage());
|
||||||
},
|
},
|
||||||
sendRefreshRoomPrompt(call: ServerUnaryCall<RefreshRoomPromptMessage>, callback: sendUnaryData<EmptyMessage>): void {
|
sendRefreshRoomPrompt(
|
||||||
|
call: ServerUnaryCall<RefreshRoomPromptMessage>,
|
||||||
|
callback: sendUnaryData<EmptyMessage>
|
||||||
|
): void {
|
||||||
socketManager.dispatchRoomRefresh(call.request.getRoomid());
|
socketManager.dispatchRoomRefresh(call.request.getRoomid());
|
||||||
callback(null, new EmptyMessage());
|
callback(null, new EmptyMessage());
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export {roomManager};
|
export { roomManager };
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
import { App as _App, AppOptions } from 'uWebSockets.js';
|
import { App as _App, AppOptions } from "uWebSockets.js";
|
||||||
import BaseApp from './baseapp';
|
import BaseApp from "./baseapp";
|
||||||
import { extend } from './utils';
|
import { extend } from "./utils";
|
||||||
import { UwsApp } from './types';
|
import { UwsApp } from "./types";
|
||||||
|
|
||||||
class App extends (<UwsApp>_App) {
|
class App extends (<UwsApp>_App) {
|
||||||
constructor(options: AppOptions = {}) {
|
constructor(options: AppOptions = {}) {
|
||||||
super(options); // eslint-disable-line constructor-super
|
super(options); // eslint-disable-line constructor-super
|
||||||
extend(this, new BaseApp());
|
extend(this, new BaseApp());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default App;
|
export default App;
|
||||||
|
@ -1,116 +1,109 @@
|
|||||||
import { Readable } from 'stream';
|
import { Readable } from "stream";
|
||||||
import { us_listen_socket_close, TemplatedApp, HttpResponse, HttpRequest } from 'uWebSockets.js';
|
import { us_listen_socket_close, TemplatedApp, HttpResponse, HttpRequest } from "uWebSockets.js";
|
||||||
|
|
||||||
import formData from './formdata';
|
import formData from "./formdata";
|
||||||
import { stob } from './utils';
|
import { stob } from "./utils";
|
||||||
import { Handler } from './types';
|
import { Handler } from "./types";
|
||||||
import {join} from "path";
|
import { join } from "path";
|
||||||
|
|
||||||
const contTypes = ['application/x-www-form-urlencoded', 'multipart/form-data'];
|
const contTypes = ["application/x-www-form-urlencoded", "multipart/form-data"];
|
||||||
const noOp = () => true;
|
const noOp = () => true;
|
||||||
|
|
||||||
const handleBody = (res: HttpResponse, req: HttpRequest) => {
|
const handleBody = (res: HttpResponse, req: HttpRequest) => {
|
||||||
const contType = req.getHeader('content-type');
|
const contType = req.getHeader("content-type");
|
||||||
|
|
||||||
res.bodyStream = function() {
|
res.bodyStream = function () {
|
||||||
const stream = new Readable();
|
const stream = new Readable();
|
||||||
stream._read = noOp; // eslint-disable-line @typescript-eslint/unbound-method
|
stream._read = noOp; // eslint-disable-line @typescript-eslint/unbound-method
|
||||||
|
|
||||||
this.onData((ab: ArrayBuffer, isLast: boolean) => {
|
this.onData((ab: ArrayBuffer, isLast: boolean) => {
|
||||||
// uint and then slicing is bit faster than slice and then uint
|
// 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
|
stream.push(new Uint8Array(ab.slice((ab as any).byteOffset, ab.byteLength))); // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||||
if (isLast) {
|
if (isLast) {
|
||||||
stream.push(null);
|
stream.push(null);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return stream;
|
return stream;
|
||||||
};
|
};
|
||||||
|
|
||||||
res.body = () => stob(res.bodyStream());
|
res.body = () => stob(res.bodyStream());
|
||||||
|
|
||||||
if (contType.includes('application/json'))
|
if (contType.includes("application/json")) res.json = async () => JSON.parse(await res.body());
|
||||||
res.json = async () => JSON.parse(await res.body());
|
if (contTypes.map((t) => contType.includes(t)).includes(true)) res.formData = formData.bind(res, contType);
|
||||||
if (contTypes.map(t => contType.includes(t)).includes(true))
|
|
||||||
res.formData = formData.bind(res, contType);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class BaseApp {
|
class BaseApp {
|
||||||
_sockets = new Map();
|
_sockets = new Map();
|
||||||
ws!: TemplatedApp['ws'];
|
ws!: TemplatedApp["ws"];
|
||||||
get!: TemplatedApp['get'];
|
get!: TemplatedApp["get"];
|
||||||
_post!: TemplatedApp['post'];
|
_post!: TemplatedApp["post"];
|
||||||
_put!: TemplatedApp['put'];
|
_put!: TemplatedApp["put"];
|
||||||
_patch!: TemplatedApp['patch'];
|
_patch!: TemplatedApp["patch"];
|
||||||
_listen!: TemplatedApp['listen'];
|
_listen!: TemplatedApp["listen"];
|
||||||
|
|
||||||
post(pattern: string, handler: Handler) {
|
post(pattern: string, handler: Handler) {
|
||||||
if (typeof handler !== 'function')
|
if (typeof handler !== "function") throw Error(`handler should be a function, given ${typeof handler}.`);
|
||||||
throw Error(`handler should be a function, given ${typeof handler}.`);
|
this._post(pattern, (res, req) => {
|
||||||
this._post(pattern, (res, req) => {
|
handleBody(res, req);
|
||||||
handleBody(res, req);
|
handler(res, req);
|
||||||
handler(res, req);
|
});
|
||||||
});
|
return this;
|
||||||
return this;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
put(pattern: string, handler: Handler) {
|
put(pattern: string, handler: Handler) {
|
||||||
if (typeof handler !== 'function')
|
if (typeof handler !== "function") throw Error(`handler should be a function, given ${typeof handler}.`);
|
||||||
throw Error(`handler should be a function, given ${typeof handler}.`);
|
this._put(pattern, (res, req) => {
|
||||||
this._put(pattern, (res, req) => {
|
handleBody(res, req);
|
||||||
handleBody(res, req);
|
|
||||||
|
|
||||||
handler(res, req);
|
handler(res, req);
|
||||||
});
|
});
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
patch(pattern: string, handler: Handler) {
|
patch(pattern: string, handler: Handler) {
|
||||||
if (typeof handler !== 'function')
|
if (typeof handler !== "function") throw Error(`handler should be a function, given ${typeof handler}.`);
|
||||||
throw Error(`handler should be a function, given ${typeof handler}.`);
|
this._patch(pattern, (res, req) => {
|
||||||
this._patch(pattern, (res, req) => {
|
handleBody(res, req);
|
||||||
handleBody(res, req);
|
|
||||||
|
|
||||||
handler(res, req);
|
handler(res, req);
|
||||||
});
|
});
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
listen(h: string | number, p: Function | number = noOp, cb?: Function) {
|
listen(h: string | number, p: Function | number = noOp, cb?: Function) {
|
||||||
if (typeof p === 'number' && typeof h === 'string') {
|
if (typeof p === "number" && typeof h === "string") {
|
||||||
this._listen(h, p, socket => {
|
this._listen(h, p, (socket) => {
|
||||||
this._sockets.set(p, socket);
|
this._sockets.set(p, socket);
|
||||||
if (cb === undefined) {
|
if (cb === undefined) {
|
||||||
throw new Error('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)");
|
||||||
}
|
}
|
||||||
cb(socket);
|
|
||||||
});
|
return this;
|
||||||
} 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));
|
||||||
close(port: null | number = null) {
|
this._sockets.delete(port);
|
||||||
if (port) {
|
} else {
|
||||||
this._sockets.has(port) && us_listen_socket_close(this._sockets.get(port));
|
this._sockets.forEach((app) => {
|
||||||
this._sockets.delete(port);
|
us_listen_socket_close(app);
|
||||||
} else {
|
});
|
||||||
this._sockets.forEach(app => {
|
this._sockets.clear();
|
||||||
us_listen_socket_close(app);
|
}
|
||||||
});
|
return this;
|
||||||
this._sockets.clear();
|
|
||||||
}
|
}
|
||||||
return this;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default BaseApp;
|
export default BaseApp;
|
||||||
|
@ -1,100 +1,99 @@
|
|||||||
import { createWriteStream } from 'fs';
|
import { createWriteStream } from "fs";
|
||||||
import { join, dirname } from 'path';
|
import { join, dirname } from "path";
|
||||||
import Busboy from 'busboy';
|
import Busboy from "busboy";
|
||||||
import mkdirp from 'mkdirp';
|
import mkdirp from "mkdirp";
|
||||||
|
|
||||||
function formData(
|
function formData(
|
||||||
contType: string,
|
contType: string,
|
||||||
options: busboy.BusboyConfig & {
|
options: busboy.BusboyConfig & {
|
||||||
abortOnLimit?: boolean;
|
abortOnLimit?: boolean;
|
||||||
tmpDir?: string;
|
tmpDir?: string;
|
||||||
onFile?: (
|
onFile?: (
|
||||||
fieldname: string,
|
fieldname: string,
|
||||||
file: NodeJS.ReadableStream,
|
file: NodeJS.ReadableStream,
|
||||||
filename: string,
|
filename: string,
|
||||||
encoding: string,
|
encoding: string,
|
||||||
mimetype: string
|
mimetype: string
|
||||||
) => string;
|
) => string;
|
||||||
onField?: (fieldname: string, value: any) => void; // eslint-disable-line @typescript-eslint/no-explicit-any
|
onField?: (fieldname: string, value: any) => void; // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||||
filename?: (oldName: string) => string;
|
filename?: (oldName: string) => string;
|
||||||
} = {}
|
} = {}
|
||||||
) {
|
) {
|
||||||
console.log('Enter form data');
|
console.log("Enter form data");
|
||||||
options.headers = {
|
options.headers = {
|
||||||
'content-type': contType
|
"content-type": contType,
|
||||||
};
|
};
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const busb = new Busboy(options);
|
const busb = new Busboy(options);
|
||||||
const ret = {};
|
const ret = {};
|
||||||
|
|
||||||
this.bodyStream().pipe(busb);
|
this.bodyStream().pipe(busb);
|
||||||
|
|
||||||
busb.on('limit', () => {
|
busb.on("limit", () => {
|
||||||
if (options.abortOnLimit) {
|
if (options.abortOnLimit) {
|
||||||
reject(Error('limit'));
|
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);
|
||||||
});
|
});
|
||||||
|
|
||||||
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(
|
function setRetValue(
|
||||||
ret: { [x: string]: any }, // eslint-disable-line @typescript-eslint/no-explicit-any
|
ret: { [x: string]: any }, // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||||
fieldname: string,
|
fieldname: string,
|
||||||
value: { filename: string; encoding: string; mimetype: string; filePath?: string } | any // eslint-disable-line @typescript-eslint/no-explicit-any
|
value: { filename: string; encoding: string; mimetype: string; filePath?: string } | any // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||||
) {
|
) {
|
||||||
if (fieldname.endsWith('[]')) {
|
if (fieldname.endsWith("[]")) {
|
||||||
fieldname = fieldname.slice(0, fieldname.length - 2);
|
fieldname = fieldname.slice(0, fieldname.length - 2);
|
||||||
if (Array.isArray(ret[fieldname])) {
|
if (Array.isArray(ret[fieldname])) {
|
||||||
ret[fieldname].push(value);
|
ret[fieldname].push(value);
|
||||||
|
} else {
|
||||||
|
ret[fieldname] = [value];
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
ret[fieldname] = [value];
|
if (Array.isArray(ret[fieldname])) {
|
||||||
|
ret[fieldname].push(value);
|
||||||
|
} else if (ret[fieldname]) {
|
||||||
|
ret[fieldname] = [ret[fieldname], 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;
|
export default formData;
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
import { SSLApp as _SSLApp, AppOptions } from 'uWebSockets.js';
|
import { SSLApp as _SSLApp, AppOptions } from "uWebSockets.js";
|
||||||
import BaseApp from './baseapp';
|
import BaseApp from "./baseapp";
|
||||||
import { extend } from './utils';
|
import { extend } from "./utils";
|
||||||
import { UwsApp } from './types';
|
import { UwsApp } from "./types";
|
||||||
|
|
||||||
class SSLApp extends (<UwsApp>_SSLApp) {
|
class SSLApp extends (<UwsApp>_SSLApp) {
|
||||||
constructor(options: AppOptions) {
|
constructor(options: AppOptions) {
|
||||||
super(options); // eslint-disable-line constructor-super
|
super(options); // eslint-disable-line constructor-super
|
||||||
extend(this, new BaseApp());
|
extend(this, new BaseApp());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default SSLApp;
|
export default SSLApp;
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import { AppOptions, TemplatedApp, HttpResponse, HttpRequest } from 'uWebSockets.js';
|
import { AppOptions, TemplatedApp, HttpResponse, HttpRequest } from "uWebSockets.js";
|
||||||
|
|
||||||
export type UwsApp = {
|
export type UwsApp = {
|
||||||
(options: AppOptions): TemplatedApp;
|
(options: AppOptions): TemplatedApp;
|
||||||
new (options: AppOptions): TemplatedApp;
|
new (options: AppOptions): TemplatedApp;
|
||||||
prototype: TemplatedApp;
|
prototype: TemplatedApp;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Handler = (res: HttpResponse, req: HttpRequest) => void;
|
export type Handler = (res: HttpResponse, req: HttpRequest) => void;
|
||||||
|
@ -1,37 +1,36 @@
|
|||||||
import { ReadStream } from 'fs';
|
import { ReadStream } from "fs";
|
||||||
|
|
||||||
function extend(who: any, from: any, overwrite = true) { // eslint-disable-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
const ownProps = Object.getOwnPropertyNames(Object.getPrototypeOf(from)).concat(
|
function extend(who: any, from: any, overwrite = true) {
|
||||||
Object.keys(from)
|
const ownProps = Object.getOwnPropertyNames(Object.getPrototypeOf(from)).concat(Object.keys(from));
|
||||||
);
|
ownProps.forEach((prop) => {
|
||||||
ownProps.forEach(prop => {
|
if (prop === "constructor" || from[prop] === undefined) return;
|
||||||
if (prop === 'constructor' || from[prop] === undefined) return;
|
if (who[prop] && overwrite) {
|
||||||
if (who[prop] && overwrite) {
|
who[`_${prop}`] = who[prop];
|
||||||
who[`_${prop}`] = who[prop];
|
}
|
||||||
}
|
if (typeof from[prop] === "function") who[prop] = from[prop].bind(who);
|
||||||
if (typeof from[prop] === 'function') who[prop] = from[prop].bind(who);
|
else who[prop] = from[prop];
|
||||||
else who[prop] = from[prop];
|
});
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function stob(stream: ReadStream): Promise<Buffer> {
|
function stob(stream: ReadStream): Promise<Buffer> {
|
||||||
return new Promise(resolve => {
|
return new Promise((resolve) => {
|
||||||
const buffers: Buffer[] = [];
|
const buffers: Buffer[] = [];
|
||||||
stream.on('data', buffers.push.bind(buffers));
|
stream.on("data", buffers.push.bind(buffers));
|
||||||
|
|
||||||
stream.on('end', () => {
|
stream.on("end", () => {
|
||||||
switch (buffers.length) {
|
switch (buffers.length) {
|
||||||
case 0:
|
case 0:
|
||||||
resolve(Buffer.allocUnsafe(0));
|
resolve(Buffer.allocUnsafe(0));
|
||||||
break;
|
break;
|
||||||
case 1:
|
case 1:
|
||||||
resolve(buffers[0]);
|
resolve(buffers[0]);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
resolve(Buffer.concat(buffers));
|
resolve(Buffer.concat(buffers));
|
||||||
}
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export { extend, stob };
|
export { extend, stob };
|
||||||
|
@ -1,19 +1,19 @@
|
|||||||
import { parse } from 'query-string';
|
import { parse } from "query-string";
|
||||||
import { HttpRequest } from 'uWebSockets.js';
|
import { HttpRequest } from "uWebSockets.js";
|
||||||
import App from './server/app';
|
import App from "./server/app";
|
||||||
import SSLApp from './server/sslapp';
|
import SSLApp from "./server/sslapp";
|
||||||
import * as types from './server/types';
|
import * as types from "./server/types";
|
||||||
|
|
||||||
const getQuery = (req: HttpRequest) => {
|
const getQuery = (req: HttpRequest) => {
|
||||||
return parse(req.getQuery());
|
return parse(req.getQuery());
|
||||||
};
|
};
|
||||||
|
|
||||||
export { App, SSLApp, getQuery };
|
export { App, SSLApp, getQuery };
|
||||||
export * from './server/types';
|
export * from "./server/types";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
App,
|
App,
|
||||||
SSLApp,
|
SSLApp,
|
||||||
getQuery,
|
getQuery,
|
||||||
...types
|
...types,
|
||||||
};
|
};
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
export const arrayIntersect = (array1: string[], array2: string[]) : boolean => {
|
export const arrayIntersect = (array1: string[], array2: string[]): boolean => {
|
||||||
return array1.filter(value => array2.includes(value)).length > 0;
|
return array1.filter((value) => array2.includes(value)).length > 0;
|
||||||
}
|
};
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
const EventEmitter = require('events');
|
const EventEmitter = require("events");
|
||||||
|
|
||||||
const clientJoinEvent = 'clientJoin';
|
const clientJoinEvent = "clientJoin";
|
||||||
const clientLeaveEvent = 'clientLeave';
|
const clientLeaveEvent = "clientLeave";
|
||||||
|
|
||||||
class ClientEventsEmitter extends EventEmitter {
|
class ClientEventsEmitter extends EventEmitter {
|
||||||
emitClientJoin(clientUUid: string, roomId: string): void {
|
emitClientJoin(clientUUid: string, roomId: string): void {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import {CPU_OVERHEAT_THRESHOLD} from "../Enum/EnvironmentVariable";
|
import { CPU_OVERHEAT_THRESHOLD } from "../Enum/EnvironmentVariable";
|
||||||
|
|
||||||
function secNSec2ms(secNSec: Array<number>|number) {
|
function secNSec2ms(secNSec: Array<number> | number) {
|
||||||
if (Array.isArray(secNSec)) {
|
if (Array.isArray(secNSec)) {
|
||||||
return secNSec[0] * 1000 + secNSec[1] / 1000000;
|
return secNSec[0] * 1000 + secNSec[1] / 1000000;
|
||||||
}
|
}
|
||||||
@ -12,17 +12,17 @@ class CpuTracker {
|
|||||||
private overHeating: boolean = false;
|
private overHeating: boolean = false;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
let time = process.hrtime.bigint()
|
let time = process.hrtime.bigint();
|
||||||
let usage = process.cpuUsage()
|
let usage = process.cpuUsage();
|
||||||
setInterval(() => {
|
setInterval(() => {
|
||||||
const elapTime = process.hrtime.bigint();
|
const elapTime = process.hrtime.bigint();
|
||||||
const elapUsage = process.cpuUsage(usage)
|
const elapUsage = process.cpuUsage(usage);
|
||||||
usage = process.cpuUsage()
|
usage = process.cpuUsage();
|
||||||
|
|
||||||
const elapTimeMS = elapTime - time;
|
const elapTimeMS = elapTime - time;
|
||||||
const elapUserMS = secNSec2ms(elapUsage.user)
|
const elapUserMS = secNSec2ms(elapUsage.user);
|
||||||
const elapSystMS = secNSec2ms(elapUsage.system)
|
const elapSystMS = secNSec2ms(elapUsage.system);
|
||||||
this.cpuPercent = Math.round(100 * (elapUserMS + elapSystMS) / Number(elapTimeMS) * 1000000)
|
this.cpuPercent = Math.round(((100 * (elapUserMS + elapSystMS)) / Number(elapTimeMS)) * 1000000);
|
||||||
|
|
||||||
time = elapTime;
|
time = elapTime;
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import {Counter, Gauge} from "prom-client";
|
import { Counter, Gauge } from "prom-client";
|
||||||
|
|
||||||
//this class should manage all the custom metrics used by prometheus
|
//this class should manage all the custom metrics used by prometheus
|
||||||
class GaugeManager {
|
class GaugeManager {
|
||||||
@ -10,29 +10,29 @@ class GaugeManager {
|
|||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.nbRoomsGauge = new Gauge({
|
this.nbRoomsGauge = new Gauge({
|
||||||
name: 'workadventure_nb_rooms',
|
name: "workadventure_nb_rooms",
|
||||||
help: 'Number of active rooms'
|
help: "Number of active rooms",
|
||||||
});
|
});
|
||||||
this.nbClientsGauge = new Gauge({
|
this.nbClientsGauge = new Gauge({
|
||||||
name: 'workadventure_nb_sockets',
|
name: "workadventure_nb_sockets",
|
||||||
help: 'Number of connected sockets',
|
help: "Number of connected sockets",
|
||||||
labelNames: [ ]
|
labelNames: [],
|
||||||
});
|
});
|
||||||
this.nbClientsPerRoomGauge = new Gauge({
|
this.nbClientsPerRoomGauge = new Gauge({
|
||||||
name: 'workadventure_nb_clients_per_room',
|
name: "workadventure_nb_clients_per_room",
|
||||||
help: 'Number of clients per room',
|
help: "Number of clients per room",
|
||||||
labelNames: [ 'room' ]
|
labelNames: ["room"],
|
||||||
});
|
});
|
||||||
|
|
||||||
this.nbGroupsPerRoomCounter = new Counter({
|
this.nbGroupsPerRoomCounter = new Counter({
|
||||||
name: 'workadventure_counter_groups_per_room',
|
name: "workadventure_counter_groups_per_room",
|
||||||
help: 'Counter of groups per room',
|
help: "Counter of groups per room",
|
||||||
labelNames: [ 'room' ]
|
labelNames: ["room"],
|
||||||
});
|
});
|
||||||
this.nbGroupsPerRoomGauge = new Gauge({
|
this.nbGroupsPerRoomGauge = new Gauge({
|
||||||
name: 'workadventure_nb_groups_per_room',
|
name: "workadventure_nb_groups_per_room",
|
||||||
help: 'Number of groups per room',
|
help: "Number of groups per room",
|
||||||
labelNames: [ 'room' ]
|
labelNames: ["room"],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -54,13 +54,13 @@ class GaugeManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
incNbGroupsPerRoomGauge(roomId: string): void {
|
incNbGroupsPerRoomGauge(roomId: string): void {
|
||||||
this.nbGroupsPerRoomCounter.inc({ room: roomId })
|
this.nbGroupsPerRoomCounter.inc({ room: roomId });
|
||||||
this.nbGroupsPerRoomGauge.inc({ room: roomId })
|
this.nbGroupsPerRoomGauge.inc({ room: roomId });
|
||||||
}
|
}
|
||||||
|
|
||||||
decNbGroupsPerRoomGauge(roomId: string): void {
|
decNbGroupsPerRoomGauge(roomId: string): void {
|
||||||
this.nbGroupsPerRoomGauge.dec({ room: roomId })
|
this.nbGroupsPerRoomGauge.dec({ room: roomId });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const gaugeManager = new GaugeManager();
|
export const gaugeManager = new GaugeManager();
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import {ErrorMessage, ServerToClientMessage} from "../Messages/generated/messages_pb";
|
import { ErrorMessage, ServerToClientMessage } from "../Messages/generated/messages_pb";
|
||||||
import {UserSocket} from "_Model/User";
|
import { UserSocket } from "_Model/User";
|
||||||
|
|
||||||
export function emitError(Client: UserSocket, message: string): void {
|
export function emitError(Client: UserSocket, message: string): void {
|
||||||
const errorMessage = new ErrorMessage();
|
const errorMessage = new ErrorMessage();
|
||||||
@ -9,7 +9,7 @@ export function emitError(Client: UserSocket, message: string): void {
|
|||||||
serverToClientMessage.setErrormessage(errorMessage);
|
serverToClientMessage.setErrormessage(errorMessage);
|
||||||
|
|
||||||
//if (!Client.disconnecting) {
|
//if (!Client.disconnecting) {
|
||||||
Client.write(serverToClientMessage);
|
Client.write(serverToClientMessage);
|
||||||
//}
|
//}
|
||||||
console.warn(message);
|
console.warn(message);
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import {GameRoom} from "../Model/GameRoom";
|
import { GameRoom } from "../Model/GameRoom";
|
||||||
import {
|
import {
|
||||||
ItemEventMessage,
|
ItemEventMessage,
|
||||||
ItemStateMessage,
|
ItemStateMessage,
|
||||||
@ -27,39 +27,39 @@ import {
|
|||||||
WorldFullWarningMessage,
|
WorldFullWarningMessage,
|
||||||
UserLeftZoneMessage,
|
UserLeftZoneMessage,
|
||||||
EmoteEventMessage,
|
EmoteEventMessage,
|
||||||
BanUserMessage, RefreshRoomMessage, EmotePromptMessage,
|
BanUserMessage,
|
||||||
|
RefreshRoomMessage,
|
||||||
|
EmotePromptMessage,
|
||||||
} from "../Messages/generated/messages_pb";
|
} from "../Messages/generated/messages_pb";
|
||||||
import {User, UserSocket} from "../Model/User";
|
import { User, UserSocket } from "../Model/User";
|
||||||
import {ProtobufUtils} from "../Model/Websocket/ProtobufUtils";
|
import { ProtobufUtils } from "../Model/Websocket/ProtobufUtils";
|
||||||
import {Group} from "../Model/Group";
|
import { Group } from "../Model/Group";
|
||||||
import {cpuTracker} from "./CpuTracker";
|
import { cpuTracker } from "./CpuTracker";
|
||||||
import {
|
import {
|
||||||
GROUP_RADIUS,
|
GROUP_RADIUS,
|
||||||
JITSI_ISS,
|
JITSI_ISS,
|
||||||
MINIMUM_DISTANCE,
|
MINIMUM_DISTANCE,
|
||||||
SECRET_JITSI_KEY,
|
SECRET_JITSI_KEY,
|
||||||
TURN_STATIC_AUTH_SECRET
|
TURN_STATIC_AUTH_SECRET,
|
||||||
} from "../Enum/EnvironmentVariable";
|
} from "../Enum/EnvironmentVariable";
|
||||||
import {Movable} from "../Model/Movable";
|
import { Movable } from "../Model/Movable";
|
||||||
import {PositionInterface} from "../Model/PositionInterface";
|
import { PositionInterface } from "../Model/PositionInterface";
|
||||||
import Jwt from "jsonwebtoken";
|
import Jwt from "jsonwebtoken";
|
||||||
import {JITSI_URL} from "../Enum/EnvironmentVariable";
|
import { JITSI_URL } from "../Enum/EnvironmentVariable";
|
||||||
import {clientEventsEmitter} from "./ClientEventsEmitter";
|
import { clientEventsEmitter } from "./ClientEventsEmitter";
|
||||||
import {gaugeManager} from "./GaugeManager";
|
import { gaugeManager } from "./GaugeManager";
|
||||||
import {ZoneSocket} from "../RoomManager";
|
import { ZoneSocket } from "../RoomManager";
|
||||||
import {Zone} from "_Model/Zone";
|
import { Zone } from "_Model/Zone";
|
||||||
import Debug from "debug";
|
import Debug from "debug";
|
||||||
import {Admin} from "_Model/Admin";
|
import { Admin } from "_Model/Admin";
|
||||||
import crypto from "crypto";
|
import crypto from "crypto";
|
||||||
|
|
||||||
|
const debug = Debug("sockermanager");
|
||||||
const debug = Debug('sockermanager');
|
|
||||||
|
|
||||||
function emitZoneMessage(subMessage: SubToPusherMessage, socket: ZoneSocket): void {
|
function emitZoneMessage(subMessage: SubToPusherMessage, socket: ZoneSocket): void {
|
||||||
// TODO: should we batch those every 100ms?
|
// TODO: should we batch those every 100ms?
|
||||||
const batchMessage = new BatchToPusherMessage();
|
const batchMessage = new BatchToPusherMessage();
|
||||||
batchMessage.addPayload(subMessage);
|
batchMessage.addPayload(subMessage);
|
||||||
|
|
||||||
|
|
||||||
socket.write(batchMessage);
|
socket.write(batchMessage);
|
||||||
}
|
}
|
||||||
@ -68,7 +68,6 @@ export class SocketManager {
|
|||||||
private rooms: Map<string, GameRoom> = new Map<string, GameRoom>();
|
private rooms: Map<string, GameRoom> = new Map<string, GameRoom>();
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
|
|
||||||
clientEventsEmitter.registerToClientJoin((clientUUid: string, roomId: string) => {
|
clientEventsEmitter.registerToClientJoin((clientUUid: string, roomId: string) => {
|
||||||
gaugeManager.incNbClientPerRoomGauge(roomId);
|
gaugeManager.incNbClientPerRoomGauge(roomId);
|
||||||
});
|
});
|
||||||
@ -77,16 +76,18 @@ export class SocketManager {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public async handleJoinRoom(socket: UserSocket, joinRoomMessage: JoinRoomMessage): Promise<{ room: GameRoom; user: User }> {
|
public async handleJoinRoom(
|
||||||
|
socket: UserSocket,
|
||||||
|
joinRoomMessage: JoinRoomMessage
|
||||||
|
): Promise<{ room: GameRoom; user: User }> {
|
||||||
//join new previous room
|
//join new previous room
|
||||||
const {room, user} = await this.joinRoom(socket, joinRoomMessage);
|
const { room, user } = await this.joinRoom(socket, joinRoomMessage);
|
||||||
|
|
||||||
if (!socket.writable) {
|
if (!socket.writable) {
|
||||||
console.warn('Socket was aborted');
|
console.warn("Socket was aborted");
|
||||||
return {
|
return {
|
||||||
room,
|
room,
|
||||||
user
|
user,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
const roomJoinedMessage = new RoomJoinedMessage();
|
const roomJoinedMessage = new RoomJoinedMessage();
|
||||||
@ -108,9 +109,8 @@ export class SocketManager {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
room,
|
room,
|
||||||
user
|
user,
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handleUserMovesMessage(room: GameRoom, user: User, userMovesMessage: UserMovesMessage) {
|
handleUserMovesMessage(room: GameRoom, user: User, userMovesMessage: UserMovesMessage) {
|
||||||
@ -124,13 +124,12 @@ export class SocketManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (position === undefined) {
|
if (position === undefined) {
|
||||||
throw new Error('Position not found in message');
|
throw new Error("Position not found in message");
|
||||||
}
|
}
|
||||||
const viewport = userMoves.viewport;
|
const viewport = userMoves.viewport;
|
||||||
if (viewport === undefined) {
|
if (viewport === undefined) {
|
||||||
throw new Error('Viewport not found in message');
|
throw new Error("Viewport not found in message");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// update position in the world
|
// update position in the world
|
||||||
room.updatePosition(user, ProtobufUtils.toPointInterface(position));
|
room.updatePosition(user, ProtobufUtils.toPointInterface(position));
|
||||||
@ -189,7 +188,11 @@ export class SocketManager {
|
|||||||
//send only at user
|
//send only at user
|
||||||
const remoteUser = room.getUsers().get(data.getReceiverid());
|
const remoteUser = room.getUsers().get(data.getReceiverid());
|
||||||
if (remoteUser === undefined) {
|
if (remoteUser === undefined) {
|
||||||
console.warn("While exchanging a WebRTC signal: client with id ", data.getReceiverid(), " does not exist. This might be a race condition.");
|
console.warn(
|
||||||
|
"While exchanging a WebRTC signal: client with id ",
|
||||||
|
data.getReceiverid(),
|
||||||
|
" does not exist. This might be a race condition."
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -197,8 +200,8 @@ export class SocketManager {
|
|||||||
webrtcSignalToClient.setUserid(user.id);
|
webrtcSignalToClient.setUserid(user.id);
|
||||||
webrtcSignalToClient.setSignal(data.getSignal());
|
webrtcSignalToClient.setSignal(data.getSignal());
|
||||||
// TODO: only compute credentials if data.signal.type === "offer"
|
// TODO: only compute credentials if data.signal.type === "offer"
|
||||||
if (TURN_STATIC_AUTH_SECRET !== '') {
|
if (TURN_STATIC_AUTH_SECRET !== "") {
|
||||||
const {username, password} = this.getTURNCredentials(''+user.id, TURN_STATIC_AUTH_SECRET);
|
const { username, password } = this.getTURNCredentials("" + user.id, TURN_STATIC_AUTH_SECRET);
|
||||||
webrtcSignalToClient.setWebrtcusername(username);
|
webrtcSignalToClient.setWebrtcusername(username);
|
||||||
webrtcSignalToClient.setWebrtcpassword(password);
|
webrtcSignalToClient.setWebrtcpassword(password);
|
||||||
}
|
}
|
||||||
@ -207,7 +210,7 @@ export class SocketManager {
|
|||||||
serverToClientMessage.setWebrtcsignaltoclientmessage(webrtcSignalToClient);
|
serverToClientMessage.setWebrtcsignaltoclientmessage(webrtcSignalToClient);
|
||||||
|
|
||||||
//if (!client.disconnecting) {
|
//if (!client.disconnecting) {
|
||||||
remoteUser.socket.write(serverToClientMessage);
|
remoteUser.socket.write(serverToClientMessage);
|
||||||
//}
|
//}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -215,7 +218,11 @@ export class SocketManager {
|
|||||||
//send only at user
|
//send only at user
|
||||||
const remoteUser = room.getUsers().get(data.getReceiverid());
|
const remoteUser = room.getUsers().get(data.getReceiverid());
|
||||||
if (remoteUser === undefined) {
|
if (remoteUser === undefined) {
|
||||||
console.warn("While exchanging a WEBRTC_SCREEN_SHARING signal: client with id ", data.getReceiverid(), " does not exist. This might be a race condition.");
|
console.warn(
|
||||||
|
"While exchanging a WEBRTC_SCREEN_SHARING signal: client with id ",
|
||||||
|
data.getReceiverid(),
|
||||||
|
" does not exist. This might be a race condition."
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -223,8 +230,8 @@ export class SocketManager {
|
|||||||
webrtcSignalToClient.setUserid(user.id);
|
webrtcSignalToClient.setUserid(user.id);
|
||||||
webrtcSignalToClient.setSignal(data.getSignal());
|
webrtcSignalToClient.setSignal(data.getSignal());
|
||||||
// TODO: only compute credentials if data.signal.type === "offer"
|
// TODO: only compute credentials if data.signal.type === "offer"
|
||||||
if (TURN_STATIC_AUTH_SECRET !== '') {
|
if (TURN_STATIC_AUTH_SECRET !== "") {
|
||||||
const {username, password} = this.getTURNCredentials(''+user.id, TURN_STATIC_AUTH_SECRET);
|
const { username, password } = this.getTURNCredentials("" + user.id, TURN_STATIC_AUTH_SECRET);
|
||||||
webrtcSignalToClient.setWebrtcusername(username);
|
webrtcSignalToClient.setWebrtcusername(username);
|
||||||
webrtcSignalToClient.setWebrtcpassword(password);
|
webrtcSignalToClient.setWebrtcpassword(password);
|
||||||
}
|
}
|
||||||
@ -233,11 +240,11 @@ export class SocketManager {
|
|||||||
serverToClientMessage.setWebrtcscreensharingsignaltoclientmessage(webrtcSignalToClient);
|
serverToClientMessage.setWebrtcscreensharingsignaltoclientmessage(webrtcSignalToClient);
|
||||||
|
|
||||||
//if (!client.disconnecting) {
|
//if (!client.disconnecting) {
|
||||||
remoteUser.socket.write(serverToClientMessage);
|
remoteUser.socket.write(serverToClientMessage);
|
||||||
//}
|
//}
|
||||||
}
|
}
|
||||||
|
|
||||||
leaveRoom(room: GameRoom, user: User){
|
leaveRoom(room: GameRoom, user: User) {
|
||||||
// leave previous room and world
|
// leave previous room and world
|
||||||
try {
|
try {
|
||||||
//user leave previous world
|
//user leave previous world
|
||||||
@ -249,33 +256,39 @@ export class SocketManager {
|
|||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
clientEventsEmitter.emitClientLeave(user.uuid, room.roomId);
|
clientEventsEmitter.emitClientLeave(user.uuid, room.roomId);
|
||||||
console.log('A user left');
|
console.log("A user left");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async getOrCreateRoom(roomId: string): Promise<GameRoom> {
|
async getOrCreateRoom(roomId: string): Promise<GameRoom> {
|
||||||
//check and create new world for a room
|
//check and create new world for a room
|
||||||
let world = this.rooms.get(roomId)
|
let world = this.rooms.get(roomId);
|
||||||
if(world === undefined){
|
if (world === undefined) {
|
||||||
world = new GameRoom(
|
world = new GameRoom(
|
||||||
roomId,
|
roomId,
|
||||||
(user: User, group: Group) => this.joinWebRtcRoom(user, group),
|
(user: User, group: Group) => this.joinWebRtcRoom(user, group),
|
||||||
(user: User, group: Group) => this.disConnectedUser(user, group),
|
(user: User, group: Group) => this.disConnectedUser(user, group),
|
||||||
MINIMUM_DISTANCE,
|
MINIMUM_DISTANCE,
|
||||||
GROUP_RADIUS,
|
GROUP_RADIUS,
|
||||||
(thing: Movable, fromZone: Zone|null, listener: ZoneSocket) => this.onZoneEnter(thing, fromZone, listener),
|
(thing: Movable, fromZone: Zone | null, listener: ZoneSocket) =>
|
||||||
(thing: Movable, position:PositionInterface, listener: ZoneSocket) => this.onClientMove(thing, position, listener),
|
this.onZoneEnter(thing, fromZone, listener),
|
||||||
(thing: Movable, newZone: Zone|null, listener: ZoneSocket) => this.onClientLeave(thing, newZone, listener),
|
(thing: Movable, position: PositionInterface, listener: ZoneSocket) =>
|
||||||
(emoteEventMessage:EmoteEventMessage, listener: ZoneSocket) => this.onEmote(emoteEventMessage, listener),
|
this.onClientMove(thing, position, listener),
|
||||||
|
(thing: Movable, newZone: Zone | null, listener: ZoneSocket) =>
|
||||||
|
this.onClientLeave(thing, newZone, listener),
|
||||||
|
(emoteEventMessage: EmoteEventMessage, listener: ZoneSocket) =>
|
||||||
|
this.onEmote(emoteEventMessage, listener)
|
||||||
);
|
);
|
||||||
gaugeManager.incNbRoomGauge();
|
gaugeManager.incNbRoomGauge();
|
||||||
this.rooms.set(roomId, world);
|
this.rooms.set(roomId, world);
|
||||||
}
|
}
|
||||||
return Promise.resolve(world)
|
return Promise.resolve(world);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async joinRoom(socket: UserSocket, joinRoomMessage: JoinRoomMessage): Promise<{ room: GameRoom; user: User }> {
|
private async joinRoom(
|
||||||
|
socket: UserSocket,
|
||||||
|
joinRoomMessage: JoinRoomMessage
|
||||||
|
): Promise<{ room: GameRoom; user: User }> {
|
||||||
const roomId = joinRoomMessage.getRoomid();
|
const roomId = joinRoomMessage.getRoomid();
|
||||||
|
|
||||||
const room = await socketManager.getOrCreateRoom(roomId);
|
const room = await socketManager.getOrCreateRoom(roomId);
|
||||||
@ -284,15 +297,15 @@ export class SocketManager {
|
|||||||
const user = room.join(socket, joinRoomMessage);
|
const user = room.join(socket, joinRoomMessage);
|
||||||
|
|
||||||
clientEventsEmitter.emitClientJoin(user.uuid, roomId);
|
clientEventsEmitter.emitClientJoin(user.uuid, roomId);
|
||||||
console.log(new Date().toISOString() + ' A user joined');
|
console.log(new Date().toISOString() + " A user joined");
|
||||||
return {room, user};
|
return { room, user };
|
||||||
}
|
}
|
||||||
|
|
||||||
private onZoneEnter(thing: Movable, fromZone: Zone|null, listener: ZoneSocket) {
|
private onZoneEnter(thing: Movable, fromZone: Zone | null, listener: ZoneSocket) {
|
||||||
if (thing instanceof User) {
|
if (thing instanceof User) {
|
||||||
const userJoinedZoneMessage = new UserJoinedZoneMessage();
|
const userJoinedZoneMessage = new UserJoinedZoneMessage();
|
||||||
if (!Number.isInteger(thing.id)) {
|
if (!Number.isInteger(thing.id)) {
|
||||||
throw new Error('clientUser.userId is not an integer '+thing.id);
|
throw new Error("clientUser.userId is not an integer " + thing.id);
|
||||||
}
|
}
|
||||||
userJoinedZoneMessage.setUserid(thing.id);
|
userJoinedZoneMessage.setUserid(thing.id);
|
||||||
userJoinedZoneMessage.setName(thing.name);
|
userJoinedZoneMessage.setName(thing.name);
|
||||||
@ -312,11 +325,11 @@ export class SocketManager {
|
|||||||
} else if (thing instanceof Group) {
|
} else if (thing instanceof Group) {
|
||||||
this.emitCreateUpdateGroupEvent(listener, fromZone, thing);
|
this.emitCreateUpdateGroupEvent(listener, fromZone, thing);
|
||||||
} else {
|
} else {
|
||||||
console.error('Unexpected type for Movable.');
|
console.error("Unexpected type for Movable.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private onClientMove(thing: Movable, position:PositionInterface, listener: ZoneSocket): void {
|
private onClientMove(thing: Movable, position: PositionInterface, listener: ZoneSocket): void {
|
||||||
if (thing instanceof User) {
|
if (thing instanceof User) {
|
||||||
const userMovedMessage = new UserMovedMessage();
|
const userMovedMessage = new UserMovedMessage();
|
||||||
userMovedMessage.setUserid(thing.id);
|
userMovedMessage.setUserid(thing.id);
|
||||||
@ -331,21 +344,20 @@ export class SocketManager {
|
|||||||
} else if (thing instanceof Group) {
|
} else if (thing instanceof Group) {
|
||||||
this.emitCreateUpdateGroupEvent(listener, null, thing);
|
this.emitCreateUpdateGroupEvent(listener, null, thing);
|
||||||
} else {
|
} else {
|
||||||
console.error('Unexpected type for Movable.');
|
console.error("Unexpected type for Movable.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private onClientLeave(thing: Movable, newZone: Zone|null, listener: ZoneSocket) {
|
private onClientLeave(thing: Movable, newZone: Zone | null, listener: ZoneSocket) {
|
||||||
if (thing instanceof User) {
|
if (thing instanceof User) {
|
||||||
this.emitUserLeftEvent(listener, thing.id, newZone);
|
this.emitUserLeftEvent(listener, thing.id, newZone);
|
||||||
} else if (thing instanceof Group) {
|
} else if (thing instanceof Group) {
|
||||||
this.emitDeleteGroupEvent(listener, thing.getId(), newZone);
|
this.emitDeleteGroupEvent(listener, thing.getId(), newZone);
|
||||||
} else {
|
} else {
|
||||||
console.error('Unexpected type for Movable.');
|
console.error("Unexpected type for Movable.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private onEmote(emoteEventMessage: EmoteEventMessage, client: ZoneSocket) {
|
private onEmote(emoteEventMessage: EmoteEventMessage, client: ZoneSocket) {
|
||||||
const subMessage = new SubToPusherMessage();
|
const subMessage = new SubToPusherMessage();
|
||||||
subMessage.setEmoteeventmessage(emoteEventMessage);
|
subMessage.setEmoteeventmessage(emoteEventMessage);
|
||||||
@ -353,7 +365,7 @@ export class SocketManager {
|
|||||||
emitZoneMessage(subMessage, client);
|
emitZoneMessage(subMessage, client);
|
||||||
}
|
}
|
||||||
|
|
||||||
private emitCreateUpdateGroupEvent(client: ZoneSocket, fromZone: Zone|null, group: Group): void {
|
private emitCreateUpdateGroupEvent(client: ZoneSocket, fromZone: Zone | null, group: Group): void {
|
||||||
const position = group.getPosition();
|
const position = group.getPosition();
|
||||||
const pointMessage = new PointMessage();
|
const pointMessage = new PointMessage();
|
||||||
pointMessage.setX(Math.floor(position.x));
|
pointMessage.setX(Math.floor(position.x));
|
||||||
@ -371,7 +383,7 @@ export class SocketManager {
|
|||||||
//client.emitInBatch(subMessage);
|
//client.emitInBatch(subMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
private emitDeleteGroupEvent(client: ZoneSocket, groupId: number, newZone: Zone|null): void {
|
private emitDeleteGroupEvent(client: ZoneSocket, groupId: number, newZone: Zone | null): void {
|
||||||
const groupDeleteMessage = new GroupLeftZoneMessage();
|
const groupDeleteMessage = new GroupLeftZoneMessage();
|
||||||
groupDeleteMessage.setGroupid(groupId);
|
groupDeleteMessage.setGroupid(groupId);
|
||||||
groupDeleteMessage.setTozone(this.toProtoZone(newZone));
|
groupDeleteMessage.setTozone(this.toProtoZone(newZone));
|
||||||
@ -383,7 +395,7 @@ export class SocketManager {
|
|||||||
//user.emitInBatch(subMessage);
|
//user.emitInBatch(subMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
private emitUserLeftEvent(client: ZoneSocket, userId: number, newZone: Zone|null): void {
|
private emitUserLeftEvent(client: ZoneSocket, userId: number, newZone: Zone | null): void {
|
||||||
const userLeftMessage = new UserLeftZoneMessage();
|
const userLeftMessage = new UserLeftZoneMessage();
|
||||||
userLeftMessage.setUserid(userId);
|
userLeftMessage.setUserid(userId);
|
||||||
userLeftMessage.setTozone(this.toProtoZone(newZone));
|
userLeftMessage.setTozone(this.toProtoZone(newZone));
|
||||||
@ -394,7 +406,7 @@ export class SocketManager {
|
|||||||
emitZoneMessage(subMessage, client);
|
emitZoneMessage(subMessage, client);
|
||||||
}
|
}
|
||||||
|
|
||||||
private toProtoZone(zone: Zone|null): ProtoZone|undefined {
|
private toProtoZone(zone: Zone | null): ProtoZone | undefined {
|
||||||
if (zone !== null) {
|
if (zone !== null) {
|
||||||
const zoneMessage = new ProtoZone();
|
const zoneMessage = new ProtoZone();
|
||||||
zoneMessage.setX(zone.x);
|
zoneMessage.setX(zone.x);
|
||||||
@ -405,7 +417,6 @@ export class SocketManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private joinWebRtcRoom(user: User, group: Group) {
|
private joinWebRtcRoom(user: User, group: Group) {
|
||||||
|
|
||||||
for (const otherUser of group.getUsers()) {
|
for (const otherUser of group.getUsers()) {
|
||||||
if (user === otherUser) {
|
if (user === otherUser) {
|
||||||
continue;
|
continue;
|
||||||
@ -416,8 +427,8 @@ export class SocketManager {
|
|||||||
webrtcStartMessage1.setUserid(otherUser.id);
|
webrtcStartMessage1.setUserid(otherUser.id);
|
||||||
webrtcStartMessage1.setName(otherUser.name);
|
webrtcStartMessage1.setName(otherUser.name);
|
||||||
webrtcStartMessage1.setInitiator(true);
|
webrtcStartMessage1.setInitiator(true);
|
||||||
if (TURN_STATIC_AUTH_SECRET !== '') {
|
if (TURN_STATIC_AUTH_SECRET !== "") {
|
||||||
const {username, password} = this.getTURNCredentials(''+otherUser.id, TURN_STATIC_AUTH_SECRET);
|
const { username, password } = this.getTURNCredentials("" + otherUser.id, TURN_STATIC_AUTH_SECRET);
|
||||||
webrtcStartMessage1.setWebrtcusername(username);
|
webrtcStartMessage1.setWebrtcusername(username);
|
||||||
webrtcStartMessage1.setWebrtcpassword(password);
|
webrtcStartMessage1.setWebrtcpassword(password);
|
||||||
}
|
}
|
||||||
@ -426,16 +437,16 @@ export class SocketManager {
|
|||||||
serverToClientMessage1.setWebrtcstartmessage(webrtcStartMessage1);
|
serverToClientMessage1.setWebrtcstartmessage(webrtcStartMessage1);
|
||||||
|
|
||||||
//if (!user.socket.disconnecting) {
|
//if (!user.socket.disconnecting) {
|
||||||
user.socket.write(serverToClientMessage1);
|
user.socket.write(serverToClientMessage1);
|
||||||
//console.log('Sending webrtcstart initiator to '+user.socket.userId)
|
//console.log('Sending webrtcstart initiator to '+user.socket.userId)
|
||||||
//}
|
//}
|
||||||
|
|
||||||
const webrtcStartMessage2 = new WebRtcStartMessage();
|
const webrtcStartMessage2 = new WebRtcStartMessage();
|
||||||
webrtcStartMessage2.setUserid(user.id);
|
webrtcStartMessage2.setUserid(user.id);
|
||||||
webrtcStartMessage2.setName(user.name);
|
webrtcStartMessage2.setName(user.name);
|
||||||
webrtcStartMessage2.setInitiator(false);
|
webrtcStartMessage2.setInitiator(false);
|
||||||
if (TURN_STATIC_AUTH_SECRET !== '') {
|
if (TURN_STATIC_AUTH_SECRET !== "") {
|
||||||
const {username, password} = this.getTURNCredentials(''+user.id, TURN_STATIC_AUTH_SECRET);
|
const { username, password } = this.getTURNCredentials("" + user.id, TURN_STATIC_AUTH_SECRET);
|
||||||
webrtcStartMessage2.setWebrtcusername(username);
|
webrtcStartMessage2.setWebrtcusername(username);
|
||||||
webrtcStartMessage2.setWebrtcpassword(password);
|
webrtcStartMessage2.setWebrtcpassword(password);
|
||||||
}
|
}
|
||||||
@ -444,10 +455,9 @@ export class SocketManager {
|
|||||||
serverToClientMessage2.setWebrtcstartmessage(webrtcStartMessage2);
|
serverToClientMessage2.setWebrtcstartmessage(webrtcStartMessage2);
|
||||||
|
|
||||||
//if (!otherUser.socket.disconnecting) {
|
//if (!otherUser.socket.disconnecting) {
|
||||||
otherUser.socket.write(serverToClientMessage2);
|
otherUser.socket.write(serverToClientMessage2);
|
||||||
//console.log('Sending webrtcstart to '+otherUser.socket.userId)
|
//console.log('Sending webrtcstart to '+otherUser.socket.userId)
|
||||||
//}
|
//}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -456,17 +466,17 @@ export class SocketManager {
|
|||||||
* and the Coturn server.
|
* and the Coturn server.
|
||||||
* The Coturn server should be initialized with parameters: `--use-auth-secret --static-auth-secret=MySecretKey`
|
* The Coturn server should be initialized with parameters: `--use-auth-secret --static-auth-secret=MySecretKey`
|
||||||
*/
|
*/
|
||||||
private getTURNCredentials(name: string, secret: string): {username: string, password: string} {
|
private getTURNCredentials(name: string, secret: string): { username: string; password: string } {
|
||||||
const unixTimeStamp = Math.floor(Date.now()/1000) + 4*3600; // this credential would be valid for the next 4 hours
|
const unixTimeStamp = Math.floor(Date.now() / 1000) + 4 * 3600; // this credential would be valid for the next 4 hours
|
||||||
const username = [unixTimeStamp, name].join(':');
|
const username = [unixTimeStamp, name].join(":");
|
||||||
const hmac = crypto.createHmac('sha1', secret);
|
const hmac = crypto.createHmac("sha1", secret);
|
||||||
hmac.setEncoding('base64');
|
hmac.setEncoding("base64");
|
||||||
hmac.write(username);
|
hmac.write(username);
|
||||||
hmac.end();
|
hmac.end();
|
||||||
const password = hmac.read();
|
const password = hmac.read();
|
||||||
return {
|
return {
|
||||||
username: username,
|
username: username,
|
||||||
password: password
|
password: password,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -489,10 +499,9 @@ export class SocketManager {
|
|||||||
serverToClientMessage1.setWebrtcdisconnectmessage(webrtcDisconnectMessage1);
|
serverToClientMessage1.setWebrtcdisconnectmessage(webrtcDisconnectMessage1);
|
||||||
|
|
||||||
//if (!otherUser.socket.disconnecting) {
|
//if (!otherUser.socket.disconnecting) {
|
||||||
otherUser.socket.write(serverToClientMessage1);
|
otherUser.socket.write(serverToClientMessage1);
|
||||||
//}
|
//}
|
||||||
|
|
||||||
|
|
||||||
const webrtcDisconnectMessage2 = new WebRtcDisconnectMessage();
|
const webrtcDisconnectMessage2 = new WebRtcDisconnectMessage();
|
||||||
webrtcDisconnectMessage2.setUserid(otherUser.id);
|
webrtcDisconnectMessage2.setUserid(otherUser.id);
|
||||||
|
|
||||||
@ -500,7 +509,7 @@ export class SocketManager {
|
|||||||
serverToClientMessage2.setWebrtcdisconnectmessage(webrtcDisconnectMessage2);
|
serverToClientMessage2.setWebrtcdisconnectmessage(webrtcDisconnectMessage2);
|
||||||
|
|
||||||
//if (!user.socket.disconnecting) {
|
//if (!user.socket.disconnecting) {
|
||||||
user.socket.write(serverToClientMessage2);
|
user.socket.write(serverToClientMessage2);
|
||||||
//}
|
//}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -517,40 +526,41 @@ export class SocketManager {
|
|||||||
console.error('An error occurred on "emitPlayGlobalMessage" event');
|
console.error('An error occurred on "emitPlayGlobalMessage" event');
|
||||||
console.error(e);
|
console.error(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public getWorlds(): Map<string, GameRoom> {
|
public getWorlds(): Map<string, GameRoom> {
|
||||||
return this.rooms;
|
return this.rooms;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public handleQueryJitsiJwtMessage(user: User, queryJitsiJwtMessage: QueryJitsiJwtMessage) {
|
public handleQueryJitsiJwtMessage(user: User, queryJitsiJwtMessage: QueryJitsiJwtMessage) {
|
||||||
const room = queryJitsiJwtMessage.getJitsiroom();
|
const room = queryJitsiJwtMessage.getJitsiroom();
|
||||||
const tag = queryJitsiJwtMessage.getTag(); // FIXME: this is not secure. We should load the JSON for the current room and check rights associated to room instead.
|
const tag = queryJitsiJwtMessage.getTag(); // FIXME: this is not secure. We should load the JSON for the current room and check rights associated to room instead.
|
||||||
|
|
||||||
if (SECRET_JITSI_KEY === '') {
|
if (SECRET_JITSI_KEY === "") {
|
||||||
throw new Error('You must set the SECRET_JITSI_KEY key to the secret to generate JWT tokens for Jitsi.');
|
throw new Error("You must set the SECRET_JITSI_KEY key to the secret to generate JWT tokens for Jitsi.");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Let's see if the current client has
|
// Let's see if the current client has
|
||||||
const isAdmin = user.tags.includes(tag);
|
const isAdmin = user.tags.includes(tag);
|
||||||
|
|
||||||
const jwt = Jwt.sign({
|
const jwt = Jwt.sign(
|
||||||
"aud": "jitsi",
|
{
|
||||||
"iss": JITSI_ISS,
|
aud: "jitsi",
|
||||||
"sub": JITSI_URL,
|
iss: JITSI_ISS,
|
||||||
"room": room,
|
sub: JITSI_URL,
|
||||||
"moderator": isAdmin
|
room: room,
|
||||||
}, SECRET_JITSI_KEY, {
|
moderator: isAdmin,
|
||||||
expiresIn: '1d',
|
},
|
||||||
algorithm: "HS256",
|
SECRET_JITSI_KEY,
|
||||||
header:
|
{
|
||||||
{
|
expiresIn: "1d",
|
||||||
"alg": "HS256",
|
algorithm: "HS256",
|
||||||
"typ": "JWT"
|
header: {
|
||||||
}
|
alg: "HS256",
|
||||||
});
|
typ: "JWT",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
const sendJitsiJwtMessage = new SendJitsiJwtMessage();
|
const sendJitsiJwtMessage = new SendJitsiJwtMessage();
|
||||||
sendJitsiJwtMessage.setJitsiroom(room);
|
sendJitsiJwtMessage.setJitsiroom(room);
|
||||||
@ -562,7 +572,7 @@ export class SocketManager {
|
|||||||
user.socket.write(serverToClientMessage);
|
user.socket.write(serverToClientMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
public handlerSendUserMessage(user: User, sendUserMessageToSend: SendUserMessage){
|
public handlerSendUserMessage(user: User, sendUserMessageToSend: SendUserMessage) {
|
||||||
const sendUserMessage = new SendUserMessage();
|
const sendUserMessage = new SendUserMessage();
|
||||||
sendUserMessage.setMessage(sendUserMessageToSend.getMessage());
|
sendUserMessage.setMessage(sendUserMessageToSend.getMessage());
|
||||||
sendUserMessage.setType(sendUserMessageToSend.getType());
|
sendUserMessage.setType(sendUserMessageToSend.getType());
|
||||||
@ -572,7 +582,7 @@ export class SocketManager {
|
|||||||
user.socket.write(serverToClientMessage);
|
user.socket.write(serverToClientMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
public handlerBanUserMessage(room: GameRoom, user: User, banUserMessageToSend: BanUserMessage){
|
public handlerBanUserMessage(room: GameRoom, user: User, banUserMessageToSend: BanUserMessage) {
|
||||||
const banUserMessage = new BanUserMessage();
|
const banUserMessage = new BanUserMessage();
|
||||||
banUserMessage.setMessage(banUserMessageToSend.getMessage());
|
banUserMessage.setMessage(banUserMessageToSend.getMessage());
|
||||||
banUserMessage.setType(banUserMessageToSend.getType());
|
banUserMessage.setType(banUserMessageToSend.getType());
|
||||||
@ -592,7 +602,7 @@ export class SocketManager {
|
|||||||
public addZoneListener(call: ZoneSocket, roomId: string, x: number, y: number): void {
|
public addZoneListener(call: ZoneSocket, roomId: string, x: number, y: number): void {
|
||||||
const room = this.rooms.get(roomId);
|
const room = this.rooms.get(roomId);
|
||||||
if (!room) {
|
if (!room) {
|
||||||
console.error("In addZoneListener, could not find room with id '" + roomId + "'");
|
console.error("In addZoneListener, could not find room with id '" + roomId + "'");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -636,7 +646,7 @@ export class SocketManager {
|
|||||||
removeZoneListener(call: ZoneSocket, roomId: string, x: number, y: number) {
|
removeZoneListener(call: ZoneSocket, roomId: string, x: number, y: number) {
|
||||||
const room = this.rooms.get(roomId);
|
const room = this.rooms.get(roomId);
|
||||||
if (!room) {
|
if (!room) {
|
||||||
console.error("In removeZoneListener, could not find room with id '" + roomId + "'");
|
console.error("In removeZoneListener, could not find room with id '" + roomId + "'");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -651,7 +661,7 @@ export class SocketManager {
|
|||||||
return room;
|
return room;
|
||||||
}
|
}
|
||||||
|
|
||||||
public leaveAdminRoom(room: GameRoom, admin: Admin){
|
public leaveAdminRoom(room: GameRoom, admin: Admin) {
|
||||||
room.adminLeave(admin);
|
room.adminLeave(admin);
|
||||||
if (room.isEmpty()) {
|
if (room.isEmpty()) {
|
||||||
this.rooms.delete(room.roomId);
|
this.rooms.delete(room.roomId);
|
||||||
@ -663,19 +673,27 @@ export class SocketManager {
|
|||||||
public sendAdminMessage(roomId: string, recipientUuid: string, message: string): void {
|
public sendAdminMessage(roomId: string, recipientUuid: string, message: string): void {
|
||||||
const room = this.rooms.get(roomId);
|
const room = this.rooms.get(roomId);
|
||||||
if (!room) {
|
if (!room) {
|
||||||
console.error("In sendAdminMessage, could not find room with id '" + roomId + "'. Maybe the room was closed a few milliseconds ago and there was a race condition?");
|
console.error(
|
||||||
|
"In sendAdminMessage, could not find room with id '" +
|
||||||
|
roomId +
|
||||||
|
"'. Maybe the room was closed a few milliseconds ago and there was a race condition?"
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const recipient = room.getUserByUuid(recipientUuid);
|
const recipient = room.getUserByUuid(recipientUuid);
|
||||||
if (recipient === undefined) {
|
if (recipient === undefined) {
|
||||||
console.error("In sendAdminMessage, could not find user with id '" + recipientUuid + "'. Maybe the user left the room a few milliseconds ago and there was a race condition?");
|
console.error(
|
||||||
|
"In sendAdminMessage, could not find user with id '" +
|
||||||
|
recipientUuid +
|
||||||
|
"'. Maybe the user left the room a few milliseconds ago and there was a race condition?"
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const sendUserMessage = new SendUserMessage();
|
const sendUserMessage = new SendUserMessage();
|
||||||
sendUserMessage.setMessage(message);
|
sendUserMessage.setMessage(message);
|
||||||
sendUserMessage.setType('ban'); //todo: is the type correct?
|
sendUserMessage.setType("ban"); //todo: is the type correct?
|
||||||
|
|
||||||
const serverToClientMessage = new ServerToClientMessage();
|
const serverToClientMessage = new ServerToClientMessage();
|
||||||
serverToClientMessage.setSendusermessage(sendUserMessage);
|
serverToClientMessage.setSendusermessage(sendUserMessage);
|
||||||
@ -686,13 +704,21 @@ export class SocketManager {
|
|||||||
public banUser(roomId: string, recipientUuid: string, message: string): void {
|
public banUser(roomId: string, recipientUuid: string, message: string): void {
|
||||||
const room = this.rooms.get(roomId);
|
const room = this.rooms.get(roomId);
|
||||||
if (!room) {
|
if (!room) {
|
||||||
console.error("In banUser, could not find room with id '" + roomId + "'. Maybe the room was closed a few milliseconds ago and there was a race condition?");
|
console.error(
|
||||||
|
"In banUser, could not find room with id '" +
|
||||||
|
roomId +
|
||||||
|
"'. Maybe the room was closed a few milliseconds ago and there was a race condition?"
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const recipient = room.getUserByUuid(recipientUuid);
|
const recipient = room.getUserByUuid(recipientUuid);
|
||||||
if (recipient === undefined) {
|
if (recipient === undefined) {
|
||||||
console.error("In banUser, could not find user with id '" + recipientUuid + "'. Maybe the user left the room a few milliseconds ago and there was a race condition?");
|
console.error(
|
||||||
|
"In banUser, could not find user with id '" +
|
||||||
|
recipientUuid +
|
||||||
|
"'. Maybe the user left the room a few milliseconds ago and there was a race condition?"
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -701,7 +727,7 @@ export class SocketManager {
|
|||||||
|
|
||||||
const banUserMessage = new BanUserMessage();
|
const banUserMessage = new BanUserMessage();
|
||||||
banUserMessage.setMessage(message);
|
banUserMessage.setMessage(message);
|
||||||
banUserMessage.setType('banned');
|
banUserMessage.setType("banned");
|
||||||
|
|
||||||
const serverToClientMessage = new ServerToClientMessage();
|
const serverToClientMessage = new ServerToClientMessage();
|
||||||
serverToClientMessage.setBanusermessage(banUserMessage);
|
serverToClientMessage.setBanusermessage(banUserMessage);
|
||||||
@ -711,19 +737,22 @@ export class SocketManager {
|
|||||||
recipient.socket.end();
|
recipient.socket.end();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
sendAdminRoomMessage(roomId: string, message: string) {
|
sendAdminRoomMessage(roomId: string, message: string) {
|
||||||
const room = this.rooms.get(roomId);
|
const room = this.rooms.get(roomId);
|
||||||
if (!room) {
|
if (!room) {
|
||||||
//todo: this should cause the http call to return a 500
|
//todo: this should cause the http call to return a 500
|
||||||
console.error("In sendAdminRoomMessage, could not find room with id '" + roomId + "'. Maybe the room was closed a few milliseconds ago and there was a race condition?");
|
console.error(
|
||||||
|
"In sendAdminRoomMessage, could not find room with id '" +
|
||||||
|
roomId +
|
||||||
|
"'. Maybe the room was closed a few milliseconds ago and there was a race condition?"
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
room.getUsers().forEach((recipient) => {
|
room.getUsers().forEach((recipient) => {
|
||||||
const sendUserMessage = new SendUserMessage();
|
const sendUserMessage = new SendUserMessage();
|
||||||
sendUserMessage.setMessage(message);
|
sendUserMessage.setMessage(message);
|
||||||
sendUserMessage.setType('message');
|
sendUserMessage.setType("message");
|
||||||
|
|
||||||
const clientMessage = new ServerToClientMessage();
|
const clientMessage = new ServerToClientMessage();
|
||||||
clientMessage.setSendusermessage(sendUserMessage);
|
clientMessage.setSendusermessage(sendUserMessage);
|
||||||
@ -732,14 +761,18 @@ export class SocketManager {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatchWorlFullWarning(roomId: string,): void {
|
dispatchWorlFullWarning(roomId: string): void {
|
||||||
const room = this.rooms.get(roomId);
|
const room = this.rooms.get(roomId);
|
||||||
if (!room) {
|
if (!room) {
|
||||||
//todo: this should cause the http call to return a 500
|
//todo: this should cause the http call to return a 500
|
||||||
console.error("In sendAdminRoomMessage, could not find room with id '" + roomId + "'. Maybe the room was closed a few milliseconds ago and there was a race condition?");
|
console.error(
|
||||||
|
"In sendAdminRoomMessage, could not find room with id '" +
|
||||||
|
roomId +
|
||||||
|
"'. Maybe the room was closed a few milliseconds ago and there was a race condition?"
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
room.getUsers().forEach((recipient) => {
|
room.getUsers().forEach((recipient) => {
|
||||||
const worldFullMessage = new WorldFullWarningMessage();
|
const worldFullMessage = new WorldFullWarningMessage();
|
||||||
|
|
||||||
@ -750,17 +783,17 @@ export class SocketManager {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatchRoomRefresh(roomId: string,): void {
|
dispatchRoomRefresh(roomId: string): void {
|
||||||
const room = this.rooms.get(roomId);
|
const room = this.rooms.get(roomId);
|
||||||
if (!room) {
|
if (!room) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const versionNumber = room.incrementVersion();
|
const versionNumber = room.incrementVersion();
|
||||||
room.getUsers().forEach((recipient) => {
|
room.getUsers().forEach((recipient) => {
|
||||||
const worldFullMessage = new RefreshRoomMessage();
|
const worldFullMessage = new RefreshRoomMessage();
|
||||||
worldFullMessage.setRoomid(roomId)
|
worldFullMessage.setRoomid(roomId);
|
||||||
worldFullMessage.setVersionnumber(versionNumber)
|
worldFullMessage.setVersionnumber(versionNumber);
|
||||||
|
|
||||||
const clientMessage = new ServerToClientMessage();
|
const clientMessage = new ServerToClientMessage();
|
||||||
clientMessage.setRefreshroommessage(worldFullMessage);
|
clientMessage.setRefreshroommessage(worldFullMessage);
|
||||||
|
@ -52,11 +52,11 @@ WA.nav.goToRoom("/_/global/<path to global map>.json#start-layer-2")
|
|||||||
### Opening/closing a web page in an iFrame
|
### Opening/closing a web page in an iFrame
|
||||||
|
|
||||||
```
|
```
|
||||||
WA.nav.openCoWebSite(url: string): void
|
WA.nav.openCoWebSite(url: string, allowApi: boolean = false, allowPolicy: string = ""): void
|
||||||
WA.nav.closeCoWebSite(): void
|
WA.nav.closeCoWebSite(): void
|
||||||
```
|
```
|
||||||
|
|
||||||
Opens the webpage at "url" in an iFrame (on the right side of the screen) or close that iFrame.
|
Opens the webpage at "url" in an iFrame (on the right side of the screen) or close that iFrame. `allowApi` allows the webpage to use the "IFrame API" and execute script (it is equivalent to putting the `openWebsiteAllowApi` property in the map). `allowPolicy` grants additional access rights to the iFrame. The `allowPolicy` parameter is turned into an [`allow` feature policy in the iFrame](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe#attr-allow).
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
@ -65,4 +65,3 @@ WA.nav.openCoWebSite('https://www.wikipedia.org/');
|
|||||||
// ...
|
// ...
|
||||||
WA.nav.closeCoWebSite();
|
WA.nav.closeCoWebSite();
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -9,4 +9,4 @@
|
|||||||
- [Sound functions](api-sound.md)
|
- [Sound functions](api-sound.md)
|
||||||
- [Controls functions](api-controls.md)
|
- [Controls functions](api-controls.md)
|
||||||
|
|
||||||
- [List of deprecated functions](api-deprecated.md)
|
- [List of deprecated functions](api-deprecated.md)
|
||||||
|
@ -81,7 +81,7 @@ WA.room.getCurrentRoom(): Promise<Room>
|
|||||||
```
|
```
|
||||||
Return a promise that resolves to a `Room` object with the following attributes :
|
Return a promise that resolves to a `Room` object with the following attributes :
|
||||||
* **id (string) :** ID of the current room
|
* **id (string) :** ID of the current room
|
||||||
* **map (ITiledMap) :** contains the JSON map file with the properties that were setted by the script if `setProperty` was called.
|
* **map (ITiledMap) :** contains the JSON map file with the properties that were set by the script if `setProperty` was called.
|
||||||
* **mapUrl (string) :** Url of the JSON map file
|
* **mapUrl (string) :** Url of the JSON map file
|
||||||
* **startLayer (string | null) :** Name of the layer where the current user started, only if different from `start` layer
|
* **startLayer (string | null) :** Name of the layer where the current user started, only if different from `start` layer
|
||||||
|
|
||||||
@ -112,3 +112,35 @@ WA.room.getCurrentUser().then((user) => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Changing tiles
|
||||||
|
```
|
||||||
|
WA.room.setTiles(tiles: TileDescriptor[]): void
|
||||||
|
```
|
||||||
|
Replace the tile at the `x` and `y` coordinates in the layer named `layer` by the tile with the id `tile`.
|
||||||
|
|
||||||
|
If `tile` is a string, it's not the id of the tile but the value of the property `name`.
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<img src="https://workadventu.re/img/docs/nameIndexProperty.png" class="figure-img img-fluid rounded" alt="" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
`TileDescriptor` has the following attributes :
|
||||||
|
* **x (number) :** The coordinate x of the tile that you want to replace.
|
||||||
|
* **y (number) :** The coordinate y of the tile that you want to replace.
|
||||||
|
* **tile (number | string) :** The id of the tile that will be placed in the map.
|
||||||
|
* **layer (string) :** The name of the layer where the tile will be placed.
|
||||||
|
|
||||||
|
**Important !** : If you use `tile` as a number, be sure to add the `firstgid` of the tileset of the tile that you want to the id of the tile in Tiled Editor.
|
||||||
|
|
||||||
|
|
||||||
|
Example :
|
||||||
|
```javascript
|
||||||
|
WA.room.setTiles([
|
||||||
|
{x: 6, y: 4, tile: 'blue', layer: 'setTiles'},
|
||||||
|
{x: 7, y: 4, tile: 109, layer: 'setTiles'},
|
||||||
|
{x: 8, y: 4, tile: 109, layer: 'setTiles'},
|
||||||
|
{x: 9, y: 4, tile: 'blue', layer: 'setTiles'}
|
||||||
|
]);
|
||||||
|
```
|
||||||
|
BIN
front/dist/resources/objects/layout_modes.png
vendored
Before Width: | Height: | Size: 297 B |
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 2.2 KiB |
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 880 B |
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 933 B |
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 5.0 KiB After Width: | Height: | Size: 2.0 KiB |
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 978 B |
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 985 B |
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 5.0 KiB After Width: | Height: | Size: 2.2 KiB |
BIN
front/dist/static/images/favicons/apple-icon.png
vendored
Before Width: | Height: | Size: 5.0 KiB After Width: | Height: | Size: 2.2 KiB |
BIN
front/dist/static/images/favicons/favicon-16x16.png
vendored
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 713 B |
BIN
front/dist/static/images/favicons/favicon-32x32.png
vendored
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 848 B |
BIN
front/dist/static/images/favicons/favicon-96x96.png
vendored
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 1.3 KiB |
BIN
front/dist/static/images/favicons/favicon.ico
vendored
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 9.9 KiB After Width: | Height: | Size: 3.5 KiB |
BIN
front/dist/static/images/favicons/ms-icon-70x70.png
vendored
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 1.1 KiB |
@ -60,12 +60,13 @@
|
|||||||
"templater": "cross-env ./templater.sh",
|
"templater": "cross-env ./templater.sh",
|
||||||
"serve": "cross-env TS_NODE_PROJECT=\"tsconfig-for-webpack.json\" webpack serve --open",
|
"serve": "cross-env TS_NODE_PROJECT=\"tsconfig-for-webpack.json\" webpack serve --open",
|
||||||
"build": "cross-env TS_NODE_PROJECT=\"tsconfig-for-webpack.json\" NODE_ENV=production webpack",
|
"build": "cross-env TS_NODE_PROJECT=\"tsconfig-for-webpack.json\" NODE_ENV=production webpack",
|
||||||
"test": "TS_NODE_PROJECT=\"tsconfig-for-jasmine.json\" ts-node node_modules/jasmine/bin/jasmine --config=jasmine.json",
|
"build-typings": "cross-env TS_NODE_PROJECT=\"tsconfig-for-webpack.json\" NODE_ENV=production BUILD_TYPINGS=1 webpack",
|
||||||
|
"test": "cross-env TS_NODE_PROJECT=\"tsconfig-for-jasmine.json\" ts-node node_modules/jasmine/bin/jasmine --config=jasmine.json",
|
||||||
"lint": "node_modules/.bin/eslint src/ . --ext .ts",
|
"lint": "node_modules/.bin/eslint src/ . --ext .ts",
|
||||||
"fix": "node_modules/.bin/eslint --fix src/ . --ext .ts",
|
"fix": "node_modules/.bin/eslint --fix src/ . --ext .ts",
|
||||||
"precommit": "lint-staged",
|
"precommit": "lint-staged",
|
||||||
"svelte-check-watch": "svelte-check --fail-on-warnings --fail-on-hints --compiler-warnings \"a11y-no-onchange:ignore,a11y-autofocus:ignore\" --watch",
|
"svelte-check-watch": "svelte-check --fail-on-warnings --fail-on-hints --compiler-warnings \"a11y-no-onchange:ignore,a11y-autofocus:ignore,a11y-media-has-caption:ignore\" --watch",
|
||||||
"svelte-check": "svelte-check --fail-on-warnings --fail-on-hints --compiler-warnings \"a11y-no-onchange:ignore,a11y-autofocus:ignore\"",
|
"svelte-check": "svelte-check --fail-on-warnings --fail-on-hints --compiler-warnings \"a11y-no-onchange:ignore,a11y-autofocus:ignore,a11y-media-has-caption:ignore\"",
|
||||||
"pretty": "yarn prettier --write 'src/**/*.{ts,tsx}'",
|
"pretty": "yarn prettier --write 'src/**/*.{ts,tsx}'",
|
||||||
"pretty-check": "yarn prettier --check 'src/**/*.{ts,tsx}'"
|
"pretty-check": "yarn prettier --check 'src/**/*.{ts,tsx}'"
|
||||||
},
|
},
|
||||||
|
@ -1,13 +1,12 @@
|
|||||||
import * as tg from "generic-type-guard";
|
import * as tg from "generic-type-guard";
|
||||||
|
|
||||||
|
export const isDataLayerEvent = new tg.IsInterface()
|
||||||
|
.withProperties({
|
||||||
export const isDataLayerEvent =
|
data: tg.isObject,
|
||||||
new tg.IsInterface().withProperties({
|
})
|
||||||
data: tg.isObject
|
.get();
|
||||||
}).get();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A message sent from the game to the iFrame when the data of the layers change after the iFrame send a message to the game that it want to listen to the data of the layers
|
* A message sent from the game to the iFrame when the data of the layers change after the iFrame send a message to the game that it want to listen to the data of the layers
|
||||||
*/
|
*/
|
||||||
export type DataLayerEvent = tg.GuardedType<typeof isDataLayerEvent>;
|
export type DataLayerEvent = tg.GuardedType<typeof isDataLayerEvent>;
|
||||||
|
@ -1,14 +1,15 @@
|
|||||||
import * as tg from "generic-type-guard";
|
import * as tg from "generic-type-guard";
|
||||||
|
|
||||||
export const isGameStateEvent =
|
export const isGameStateEvent = new tg.IsInterface()
|
||||||
new tg.IsInterface().withProperties({
|
.withProperties({
|
||||||
roomId: tg.isString,
|
roomId: tg.isString,
|
||||||
mapUrl: tg.isString,
|
mapUrl: tg.isString,
|
||||||
nickname: tg.isUnion(tg.isString, tg.isNull),
|
nickname: tg.isUnion(tg.isString, tg.isNull),
|
||||||
uuid: tg.isUnion(tg.isString, tg.isUndefined),
|
uuid: tg.isUnion(tg.isString, tg.isUndefined),
|
||||||
startLayerName: tg.isUnion(tg.isString, tg.isNull),
|
startLayerName: tg.isUnion(tg.isString, tg.isNull),
|
||||||
tags : tg.isArray(tg.isString),
|
tags: tg.isArray(tg.isString),
|
||||||
}).get();
|
})
|
||||||
|
.get();
|
||||||
/**
|
/**
|
||||||
* A message sent from the game to the iFrame when the gameState is received by the script
|
* A message sent from the game to the iFrame when the gameState is received by the script
|
||||||
*/
|
*/
|
||||||
|
@ -1,19 +1,17 @@
|
|||||||
import * as tg from "generic-type-guard";
|
import * as tg from "generic-type-guard";
|
||||||
|
|
||||||
|
export const isHasPlayerMovedEvent = new tg.IsInterface()
|
||||||
|
.withProperties({
|
||||||
export const isHasPlayerMovedEvent =
|
direction: tg.isElementOf("right", "left", "up", "down"),
|
||||||
new tg.IsInterface().withProperties({
|
|
||||||
direction: tg.isElementOf('right', 'left', 'up', 'down'),
|
|
||||||
moving: tg.isBoolean,
|
moving: tg.isBoolean,
|
||||||
x: tg.isNumber,
|
x: tg.isNumber,
|
||||||
y: tg.isNumber
|
y: tg.isNumber,
|
||||||
}).get();
|
})
|
||||||
|
.get();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A message sent from the game to the iFrame to notify a movement from the current player.
|
* A message sent from the game to the iFrame to notify a movement from the current player.
|
||||||
*/
|
*/
|
||||||
export type HasPlayerMovedEvent = tg.GuardedType<typeof isHasPlayerMovedEvent>;
|
export type HasPlayerMovedEvent = tg.GuardedType<typeof isHasPlayerMovedEvent>;
|
||||||
|
|
||||||
|
export type HasPlayerMovedEventCallback = (event: HasPlayerMovedEvent) => void;
|
||||||
export type HasPlayerMovedEventCallback = (event: HasPlayerMovedEvent) => void
|
|
||||||
|
@ -1,78 +1,83 @@
|
|||||||
|
import type { GameStateEvent } from "./GameStateEvent";
|
||||||
import type { GameStateEvent } from './GameStateEvent';
|
import type { ButtonClickedEvent } from "./ButtonClickedEvent";
|
||||||
import type { ButtonClickedEvent } from './ButtonClickedEvent';
|
import type { ChatEvent } from "./ChatEvent";
|
||||||
import type { ChatEvent } from './ChatEvent';
|
import type { ClosePopupEvent } from "./ClosePopupEvent";
|
||||||
import type { ClosePopupEvent } from './ClosePopupEvent';
|
import type { EnterLeaveEvent } from "./EnterLeaveEvent";
|
||||||
import type { EnterLeaveEvent } from './EnterLeaveEvent';
|
import type { GoToPageEvent } from "./GoToPageEvent";
|
||||||
import type { GoToPageEvent } from './GoToPageEvent';
|
import type { LoadPageEvent } from "./LoadPageEvent";
|
||||||
import type { LoadPageEvent } from './LoadPageEvent';
|
import type { OpenCoWebSiteEvent } from "./OpenCoWebSiteEvent";
|
||||||
import type { OpenCoWebSiteEvent } from './OpenCoWebSiteEvent';
|
import type { OpenPopupEvent } from "./OpenPopupEvent";
|
||||||
import type { OpenPopupEvent } from './OpenPopupEvent';
|
import type { OpenTabEvent } from "./OpenTabEvent";
|
||||||
import type { OpenTabEvent } from './OpenTabEvent';
|
import type { UserInputChatEvent } from "./UserInputChatEvent";
|
||||||
import type { UserInputChatEvent } from './UserInputChatEvent';
|
|
||||||
import type { DataLayerEvent } from "./DataLayerEvent";
|
import type { DataLayerEvent } from "./DataLayerEvent";
|
||||||
import type { LayerEvent } from './LayerEvent';
|
import type { LayerEvent } from "./LayerEvent";
|
||||||
import type { SetPropertyEvent } from "./setPropertyEvent";
|
import type { SetPropertyEvent } from "./setPropertyEvent";
|
||||||
import type { LoadSoundEvent } from "./LoadSoundEvent";
|
import type { LoadSoundEvent } from "./LoadSoundEvent";
|
||||||
import type { PlaySoundEvent } from "./PlaySoundEvent";
|
import type { PlaySoundEvent } from "./PlaySoundEvent";
|
||||||
import type { MenuItemClickedEvent } from "./ui/MenuItemClickedEvent";
|
import type { MenuItemClickedEvent } from "./ui/MenuItemClickedEvent";
|
||||||
import type { MenuItemRegisterEvent } from './ui/MenuItemRegisterEvent';
|
import type { MenuItemRegisterEvent } from "./ui/MenuItemRegisterEvent";
|
||||||
import type { HasPlayerMovedEvent } from "./HasPlayerMovedEvent";
|
import type { HasPlayerMovedEvent } from "./HasPlayerMovedEvent";
|
||||||
import type { MessageReferenceEvent, TriggerMessageEvent } from '../iframe/TriggerMessageEvent';
|
import type { SetTilesEvent } from "./SetTilesEvent";
|
||||||
|
import type {
|
||||||
|
MessageReferenceEvent,
|
||||||
|
removeTriggerMessage,
|
||||||
|
triggerMessage,
|
||||||
|
TriggerMessageEvent,
|
||||||
|
} from "./ui/TriggerMessageEvent";
|
||||||
|
|
||||||
export interface TypedMessageEvent<T> extends MessageEvent {
|
export interface TypedMessageEvent<T> extends MessageEvent {
|
||||||
data: T
|
data: T;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List event types sent from an iFrame to WorkAdventure
|
||||||
|
*/
|
||||||
export type IframeEventMap = {
|
export type IframeEventMap = {
|
||||||
//getState: GameStateEvent,
|
loadPage: LoadPageEvent;
|
||||||
// updateTile: UpdateTileEvent
|
chat: ChatEvent;
|
||||||
loadPage: LoadPageEvent
|
openPopup: OpenPopupEvent;
|
||||||
chat: ChatEvent,
|
closePopup: ClosePopupEvent;
|
||||||
openPopup: OpenPopupEvent
|
openTab: OpenTabEvent;
|
||||||
closePopup: ClosePopupEvent
|
goToPage: GoToPageEvent;
|
||||||
openTab: OpenTabEvent
|
openCoWebSite: OpenCoWebSiteEvent;
|
||||||
goToPage: GoToPageEvent
|
closeCoWebSite: null;
|
||||||
openCoWebSite: OpenCoWebSiteEvent
|
disablePlayerControls: null;
|
||||||
closeCoWebSite: null
|
restorePlayerControls: null;
|
||||||
disablePlayerControls: null
|
displayBubble: null;
|
||||||
restorePlayerControls: null
|
removeBubble: null;
|
||||||
displayBubble: null
|
onPlayerMove: undefined;
|
||||||
removeBubble: null
|
showLayer: LayerEvent;
|
||||||
onPlayerMove: undefined
|
hideLayer: LayerEvent;
|
||||||
showLayer: LayerEvent
|
setProperty: SetPropertyEvent;
|
||||||
hideLayer: LayerEvent
|
getDataLayer: undefined;
|
||||||
setProperty: SetPropertyEvent
|
loadSound: LoadSoundEvent;
|
||||||
getDataLayer: undefined
|
playSound: PlaySoundEvent;
|
||||||
loadSound: LoadSoundEvent
|
stopSound: null;
|
||||||
playSound: PlaySoundEvent
|
getState: undefined;
|
||||||
stopSound: null,
|
registerMenuCommand: MenuItemRegisterEvent;
|
||||||
getState: undefined,
|
setTiles: SetTilesEvent;
|
||||||
registerMenuCommand: MenuItemRegisterEvent
|
|
||||||
|
|
||||||
triggerMessage: TriggerMessageEvent
|
triggerMessage: TriggerMessageEvent;
|
||||||
removeTriggerMessage: MessageReferenceEvent
|
removeTriggerMessage: MessageReferenceEvent;
|
||||||
}
|
};
|
||||||
export interface IframeEvent<T extends keyof IframeEventMap> {
|
export interface IframeEvent<T extends keyof IframeEventMap> {
|
||||||
type: T;
|
type: T;
|
||||||
data: IframeEventMap[T];
|
data: IframeEventMap[T];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
export const isIframeEventWrapper = (event: any): event is IframeEvent<keyof IframeEventMap> => typeof event.type === 'string';
|
export const isIframeEventWrapper = (event: any): event is IframeEvent<keyof IframeEventMap> =>
|
||||||
|
typeof event.type === "string";
|
||||||
|
|
||||||
export interface IframeResponseEventMap {
|
export interface IframeResponseEventMap {
|
||||||
userInputChat: UserInputChatEvent
|
userInputChat: UserInputChatEvent;
|
||||||
enterEvent: EnterLeaveEvent
|
enterEvent: EnterLeaveEvent;
|
||||||
leaveEvent: EnterLeaveEvent
|
leaveEvent: EnterLeaveEvent;
|
||||||
buttonClickedEvent: ButtonClickedEvent
|
buttonClickedEvent: ButtonClickedEvent;
|
||||||
gameState: GameStateEvent
|
hasPlayerMoved: HasPlayerMovedEvent;
|
||||||
hasPlayerMoved: HasPlayerMovedEvent
|
dataLayer: DataLayerEvent;
|
||||||
dataLayer: DataLayerEvent
|
menuItemClicked: MenuItemClickedEvent;
|
||||||
menuItemClicked: MenuItemClickedEvent
|
messageTriggered: MessageReferenceEvent;
|
||||||
messageTriggered: MessageReferenceEvent
|
|
||||||
}
|
}
|
||||||
export interface IframeResponseEvent<T extends keyof IframeResponseEventMap> {
|
export interface IframeResponseEvent<T extends keyof IframeResponseEventMap> {
|
||||||
type: T;
|
type: T;
|
||||||
@ -80,4 +85,67 @@ export interface IframeResponseEvent<T extends keyof IframeResponseEventMap> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
export const isIframeResponseEventWrapper = (event: { type?: string }): event is IframeResponseEvent<keyof IframeResponseEventMap> => typeof event.type === 'string';
|
export const isIframeResponseEventWrapper = (event: {
|
||||||
|
type?: string;
|
||||||
|
}): event is IframeResponseEvent<keyof IframeResponseEventMap> => typeof event.type === "string";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List event types sent from an iFrame to WorkAdventure that expect a unique answer from WorkAdventure along the type for the answer from WorkAdventure to the iFrame
|
||||||
|
*/
|
||||||
|
export type IframeQueryMap = {
|
||||||
|
getState: {
|
||||||
|
query: undefined;
|
||||||
|
answer: GameStateEvent;
|
||||||
|
};
|
||||||
|
|
||||||
|
[triggerMessage]: {
|
||||||
|
query: TriggerMessageEvent;
|
||||||
|
answer: void;
|
||||||
|
};
|
||||||
|
|
||||||
|
[removeTriggerMessage]: {
|
||||||
|
query: MessageReferenceEvent;
|
||||||
|
answer: void;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface IframeQuery<T extends keyof IframeQueryMap> {
|
||||||
|
type: T;
|
||||||
|
data: IframeQueryMap[T]["query"];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IframeQueryWrapper<T extends keyof IframeQueryMap> {
|
||||||
|
id: number;
|
||||||
|
query: IframeQuery<T>;
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
export const isIframeQuery = (event: any): event is IframeQuery<keyof IframeQueryMap> => typeof event.type === "string";
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
export const isIframeQueryWrapper = (event: any): event is IframeQueryWrapper<keyof IframeQueryMap> =>
|
||||||
|
typeof event.id === "number" && isIframeQuery(event.query);
|
||||||
|
|
||||||
|
export interface IframeAnswerEvent<T extends keyof IframeQueryMap> {
|
||||||
|
id: number;
|
||||||
|
type: T;
|
||||||
|
data: IframeQueryMap[T]["answer"];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const isIframeAnswerEvent = (event: {
|
||||||
|
type?: string;
|
||||||
|
id?: number;
|
||||||
|
}): event is IframeAnswerEvent<keyof IframeQueryMap> => typeof event.type === "string" && typeof event.id === "number";
|
||||||
|
|
||||||
|
export interface IframeErrorAnswerEvent {
|
||||||
|
id: number;
|
||||||
|
type: keyof IframeQueryMap;
|
||||||
|
error: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const isIframeErrorAnswerEvent = (event: {
|
||||||
|
type?: string;
|
||||||
|
id?: number;
|
||||||
|
error?: string;
|
||||||
|
}): event is IframeErrorAnswerEvent =>
|
||||||
|
typeof event.type === "string" && typeof event.id === "number" && typeof event.error === "string";
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
import * as tg from "generic-type-guard";
|
import * as tg from "generic-type-guard";
|
||||||
|
|
||||||
export const isLayerEvent =
|
export const isLayerEvent = new tg.IsInterface()
|
||||||
new tg.IsInterface().withProperties({
|
.withProperties({
|
||||||
name: tg.isString,
|
name: tg.isString,
|
||||||
}).get();
|
})
|
||||||
|
.get();
|
||||||
/**
|
/**
|
||||||
* A message sent from the iFrame to the game to show/hide a layer.
|
* A message sent from the iFrame to the game to show/hide a layer.
|
||||||
*/
|
*/
|
||||||
|
@ -1,13 +1,12 @@
|
|||||||
import * as tg from "generic-type-guard";
|
import * as tg from "generic-type-guard";
|
||||||
|
|
||||||
|
export const isLoadPageEvent = new tg.IsInterface()
|
||||||
|
.withProperties({
|
||||||
export const isLoadPageEvent =
|
|
||||||
new tg.IsInterface().withProperties({
|
|
||||||
url: tg.isString,
|
url: tg.isString,
|
||||||
}).get();
|
})
|
||||||
|
.get();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A message sent from the iFrame to the game to add a message in the chat.
|
* A message sent from the iFrame to the game to add a message in the chat.
|
||||||
*/
|
*/
|
||||||
export type LoadPageEvent = tg.GuardedType<typeof isLoadPageEvent>;
|
export type LoadPageEvent = tg.GuardedType<typeof isLoadPageEvent>;
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
import * as tg from "generic-type-guard";
|
import * as tg from "generic-type-guard";
|
||||||
|
|
||||||
|
export const isOpenCoWebsite = new tg.IsInterface()
|
||||||
|
.withProperties({
|
||||||
export const isOpenCoWebsite =
|
|
||||||
new tg.IsInterface().withProperties({
|
|
||||||
url: tg.isString,
|
url: tg.isString,
|
||||||
}).get();
|
allowApi: tg.isBoolean,
|
||||||
|
allowPolicy: tg.isString,
|
||||||
|
})
|
||||||
|
.get();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A message sent from the iFrame to the game to add a message in the chat.
|
* A message sent from the iFrame to the game to add a message in the chat.
|
||||||
|
16
front/src/Api/Events/SetTilesEvent.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import * as tg from "generic-type-guard";
|
||||||
|
|
||||||
|
export const isSetTilesEvent = tg.isArray(
|
||||||
|
new tg.IsInterface()
|
||||||
|
.withProperties({
|
||||||
|
x: tg.isNumber,
|
||||||
|
y: tg.isNumber,
|
||||||
|
tile: tg.isUnion(tg.isNumber, tg.isString),
|
||||||
|
layer: tg.isString,
|
||||||
|
})
|
||||||
|
.get()
|
||||||
|
);
|
||||||
|
/**
|
||||||
|
* A message sent from the iFrame to the game to set one or many tiles.
|
||||||
|
*/
|
||||||
|
export type SetTilesEvent = tg.GuardedType<typeof isSetTilesEvent>;
|
@ -1,12 +1,13 @@
|
|||||||
import * as tg from "generic-type-guard";
|
import * as tg from "generic-type-guard";
|
||||||
|
|
||||||
export const isSetPropertyEvent =
|
export const isSetPropertyEvent = new tg.IsInterface()
|
||||||
new tg.IsInterface().withProperties({
|
.withProperties({
|
||||||
layerName: tg.isString,
|
layerName: tg.isString,
|
||||||
propertyName: tg.isString,
|
propertyName: tg.isString,
|
||||||
propertyValue: tg.isUnion(tg.isString, tg.isUnion(tg.isNumber, tg.isUnion(tg.isBoolean, tg.isUndefined)))
|
propertyValue: tg.isUnion(tg.isString, tg.isUnion(tg.isNumber, tg.isUnion(tg.isBoolean, tg.isUndefined))),
|
||||||
}).get();
|
})
|
||||||
|
.get();
|
||||||
/**
|
/**
|
||||||
* A message sent from the iFrame to the game to change the value of the property of the layer
|
* A message sent from the iFrame to the game to change the value of the property of the layer
|
||||||
*/
|
*/
|
||||||
export type SetPropertyEvent = tg.GuardedType<typeof isSetPropertyEvent>;
|
export type SetPropertyEvent = tg.GuardedType<typeof isSetPropertyEvent>;
|
||||||
|
@ -1,12 +1,11 @@
|
|||||||
import * as tg from "generic-type-guard";
|
import * as tg from "generic-type-guard";
|
||||||
|
|
||||||
export const isMenuItemClickedEvent =
|
export const isMenuItemClickedEvent = new tg.IsInterface()
|
||||||
new tg.IsInterface().withProperties({
|
.withProperties({
|
||||||
menuItem: tg.isString
|
menuItem: tg.isString,
|
||||||
}).get();
|
})
|
||||||
|
.get();
|
||||||
/**
|
/**
|
||||||
* A message sent from the game to the iFrame when a menu item is clicked.
|
* A message sent from the game to the iFrame when a menu item is clicked.
|
||||||
*/
|
*/
|
||||||
export type MenuItemClickedEvent = tg.GuardedType<typeof isMenuItemClickedEvent>;
|
export type MenuItemClickedEvent = tg.GuardedType<typeof isMenuItemClickedEvent>;
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,25 +1,26 @@
|
|||||||
import * as tg from "generic-type-guard";
|
import * as tg from "generic-type-guard";
|
||||||
import { Subject } from 'rxjs';
|
import { Subject } from "rxjs";
|
||||||
|
|
||||||
export const isMenuItemRegisterEvent =
|
export const isMenuItemRegisterEvent = new tg.IsInterface()
|
||||||
new tg.IsInterface().withProperties({
|
.withProperties({
|
||||||
menutItem: tg.isString
|
menutItem: tg.isString,
|
||||||
}).get();
|
})
|
||||||
|
.get();
|
||||||
/**
|
/**
|
||||||
* A message sent from the iFrame to the game to add a new menu item.
|
* A message sent from the iFrame to the game to add a new menu item.
|
||||||
*/
|
*/
|
||||||
export type MenuItemRegisterEvent = tg.GuardedType<typeof isMenuItemRegisterEvent>;
|
export type MenuItemRegisterEvent = tg.GuardedType<typeof isMenuItemRegisterEvent>;
|
||||||
|
|
||||||
export const isMenuItemRegisterIframeEvent =
|
export const isMenuItemRegisterIframeEvent = new tg.IsInterface()
|
||||||
new tg.IsInterface().withProperties({
|
.withProperties({
|
||||||
type: tg.isSingletonString("registerMenuCommand"),
|
type: tg.isSingletonString("registerMenuCommand"),
|
||||||
data: isMenuItemRegisterEvent
|
data: isMenuItemRegisterEvent,
|
||||||
}).get();
|
})
|
||||||
|
.get();
|
||||||
|
|
||||||
const _registerMenuCommandStream: Subject<string> = new Subject();
|
const _registerMenuCommandStream: Subject<string> = new Subject();
|
||||||
export const registerMenuCommandStream = _registerMenuCommandStream.asObservable();
|
export const registerMenuCommandStream = _registerMenuCommandStream.asObservable();
|
||||||
|
|
||||||
export function handleMenuItemRegistrationEvent(event: MenuItemRegisterEvent) {
|
export function handleMenuItemRegistrationEvent(event: MenuItemRegisterEvent) {
|
||||||
_registerMenuCommandStream.next(event.menutItem)
|
_registerMenuCommandStream.next(event.menutItem);
|
||||||
}
|
}
|
||||||
|
@ -1,42 +1,35 @@
|
|||||||
import { Subject } from 'rxjs';
|
import { Subject } from "rxjs";
|
||||||
import { iframeListener } from '../../IframeListener';
|
import { iframeListener } from "../../IframeListener";
|
||||||
import { isMessageReferenceEvent, isTriggerMessageEvent, MessageReferenceEvent, removeTriggerMessage, triggerMessage, TriggerMessageEvent } from './TriggerMessageEvent';
|
import {
|
||||||
|
isMessageReferenceEvent,
|
||||||
|
isTriggerMessageEvent,
|
||||||
|
MessageReferenceEvent,
|
||||||
|
removeTriggerMessage,
|
||||||
|
triggerMessage,
|
||||||
|
TriggerMessageEvent,
|
||||||
|
} from "./TriggerMessageEvent";
|
||||||
import * as tg from "generic-type-guard";
|
import * as tg from "generic-type-guard";
|
||||||
export function sendMessageTriggeredEvent(uuid: string) {
|
export function sendMessageTriggeredEvent(uuid: string) {
|
||||||
iframeListener.postMessage({
|
iframeListener.postMessage({
|
||||||
'type': 'messageTriggered',
|
type: "messageTriggered",
|
||||||
'data': {
|
data: {
|
||||||
uuid,
|
uuid,
|
||||||
} as MessageReferenceEvent
|
} as MessageReferenceEvent,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const _triggerMessageEvent: Subject<TriggerMessageEvent> = new Subject();
|
const isTriggerMessageEventObject = new tg.IsInterface()
|
||||||
const _removeTriggerMessageEvent: Subject<MessageReferenceEvent> = new Subject();
|
.withProperties({
|
||||||
|
type: tg.isSingletonString(triggerMessage),
|
||||||
|
data: isTriggerMessageEvent,
|
||||||
|
})
|
||||||
|
.get();
|
||||||
|
|
||||||
export const triggerMessageEvent = _triggerMessageEvent.asObservable();
|
const isTriggerMessageRemoveEventObject = new tg.IsInterface()
|
||||||
|
.withProperties({
|
||||||
|
type: tg.isSingletonString(removeTriggerMessage),
|
||||||
|
data: isMessageReferenceEvent,
|
||||||
|
})
|
||||||
|
.get();
|
||||||
|
|
||||||
export const removeTriggerMessageEvent = _removeTriggerMessageEvent.asObservable();
|
export const isTriggerMessageHandlerEvent = tg.isUnion(isTriggerMessageEventObject, isTriggerMessageRemoveEventObject);
|
||||||
|
|
||||||
const isTriggerMessageEventObject = new tg.IsInterface().withProperties({
|
|
||||||
type: tg.isSingletonString(triggerMessage),
|
|
||||||
data: isTriggerMessageEvent
|
|
||||||
}).get()
|
|
||||||
const isTriggerMessageRemoveEventObject = new tg.IsInterface().withProperties({
|
|
||||||
type: tg.isSingletonString(removeTriggerMessage),
|
|
||||||
data: isMessageReferenceEvent
|
|
||||||
}).get()
|
|
||||||
|
|
||||||
|
|
||||||
export const isTriggerMessageHandlerEvent = tg.isUnion(isTriggerMessageEventObject, isTriggerMessageRemoveEventObject)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export function triggerMessageEventHandler(event: tg.GuardedType<typeof isTriggerMessageHandlerEvent>) {
|
|
||||||
if (isTriggerMessageEventObject(event)) {
|
|
||||||
_triggerMessageEvent.next(event.data)
|
|
||||||
} else if (isTriggerMessageRemoveEventObject(event)) {
|
|
||||||
_removeTriggerMessageEvent.next(event.data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { Subject } from "rxjs";
|
import { Subject } from "rxjs";
|
||||||
|
import type * as tg from "generic-type-guard";
|
||||||
import { ChatEvent, isChatEvent } from "./Events/ChatEvent";
|
import { ChatEvent, isChatEvent } from "./Events/ChatEvent";
|
||||||
import { HtmlUtils } from "../WebRtc/HtmlUtils";
|
import { HtmlUtils } from "../WebRtc/HtmlUtils";
|
||||||
import type { EnterLeaveEvent } from "./Events/EnterLeaveEvent";
|
import type { EnterLeaveEvent } from "./Events/EnterLeaveEvent";
|
||||||
@ -10,34 +11,40 @@ import { scriptUtils } from "./ScriptUtils";
|
|||||||
import { GoToPageEvent, isGoToPageEvent } from "./Events/GoToPageEvent";
|
import { GoToPageEvent, isGoToPageEvent } from "./Events/GoToPageEvent";
|
||||||
import { isOpenCoWebsite, OpenCoWebSiteEvent } from "./Events/OpenCoWebSiteEvent";
|
import { isOpenCoWebsite, OpenCoWebSiteEvent } from "./Events/OpenCoWebSiteEvent";
|
||||||
import {
|
import {
|
||||||
|
IframeErrorAnswerEvent,
|
||||||
IframeEvent,
|
IframeEvent,
|
||||||
IframeEventMap,
|
IframeEventMap,
|
||||||
|
IframeQuery,
|
||||||
|
IframeQueryMap,
|
||||||
IframeResponseEvent,
|
IframeResponseEvent,
|
||||||
IframeResponseEventMap,
|
IframeResponseEventMap,
|
||||||
isIframeEventWrapper,
|
isIframeEventWrapper,
|
||||||
TypedMessageEvent
|
isIframeQueryWrapper,
|
||||||
|
TypedMessageEvent,
|
||||||
} from "./Events/IframeEvent";
|
} from "./Events/IframeEvent";
|
||||||
import type { UserInputChatEvent } from "./Events/UserInputChatEvent";
|
import type { UserInputChatEvent } from "./Events/UserInputChatEvent";
|
||||||
//import { isLoadPageEvent } from './Events/LoadPageEvent';
|
|
||||||
import { isPlaySoundEvent, PlaySoundEvent } from "./Events/PlaySoundEvent";
|
import { isPlaySoundEvent, PlaySoundEvent } from "./Events/PlaySoundEvent";
|
||||||
import { isStopSoundEvent, StopSoundEvent } from "./Events/StopSoundEvent";
|
import { isStopSoundEvent, StopSoundEvent } from "./Events/StopSoundEvent";
|
||||||
import { isLoadSoundEvent, LoadSoundEvent } from "./Events/LoadSoundEvent";
|
import { isLoadSoundEvent, LoadSoundEvent } from "./Events/LoadSoundEvent";
|
||||||
import { isSetPropertyEvent, SetPropertyEvent } from "./Events/setPropertyEvent";
|
import { isSetPropertyEvent, SetPropertyEvent } from "./Events/setPropertyEvent";
|
||||||
import { isLayerEvent, LayerEvent } from "./Events/LayerEvent";
|
import { isLayerEvent, LayerEvent } from "./Events/LayerEvent";
|
||||||
import { isMenuItemRegisterEvent, } from "./Events/ui/MenuItemRegisterEvent";
|
import { isMenuItemRegisterEvent } from "./Events/ui/MenuItemRegisterEvent";
|
||||||
import type { DataLayerEvent } from "./Events/DataLayerEvent";
|
import type { DataLayerEvent } from "./Events/DataLayerEvent";
|
||||||
import type { GameStateEvent } from "./Events/GameStateEvent";
|
import type { GameStateEvent } from "./Events/GameStateEvent";
|
||||||
import type { HasPlayerMovedEvent } from "./Events/HasPlayerMovedEvent";
|
import type { HasPlayerMovedEvent } from "./Events/HasPlayerMovedEvent";
|
||||||
import { isLoadPageEvent } from "./Events/LoadPageEvent";
|
import { isLoadPageEvent } from "./Events/LoadPageEvent";
|
||||||
import { handleMenuItemRegistrationEvent, isMenuItemRegisterIframeEvent } from "./Events/ui/MenuItemRegisterEvent";
|
import { handleMenuItemRegistrationEvent, isMenuItemRegisterIframeEvent } from "./Events/ui/MenuItemRegisterEvent";
|
||||||
import { isTriggerMessageHandlerEvent, triggerMessageEventHandler } from './Events/ui/TriggerMessageEventHandler';
|
import { SetTilesEvent, isSetTilesEvent } from "./Events/SetTilesEvent";
|
||||||
|
|
||||||
|
type AnswererCallback<T extends keyof IframeQueryMap> = (
|
||||||
|
query: IframeQueryMap[T]["query"]
|
||||||
|
) => IframeQueryMap[T]["answer"] | Promise<IframeQueryMap[T]["answer"]>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Listens to messages from iframes and turn those messages into easy to use observables.
|
* Listens to messages from iframes and turn those messages into easy to use observables.
|
||||||
* Also allows to send messages to those iframes.
|
* Also allows to send messages to those iframes.
|
||||||
*/
|
*/
|
||||||
class IframeListener {
|
class IframeListener {
|
||||||
|
|
||||||
private readonly _chatStream: Subject<ChatEvent> = new Subject();
|
private readonly _chatStream: Subject<ChatEvent> = new Subject();
|
||||||
public readonly chatStream = this._chatStream.asObservable();
|
public readonly chatStream = this._chatStream.asObservable();
|
||||||
|
|
||||||
@ -83,9 +90,6 @@ class IframeListener {
|
|||||||
private readonly _setPropertyStream: Subject<SetPropertyEvent> = new Subject();
|
private readonly _setPropertyStream: Subject<SetPropertyEvent> = new Subject();
|
||||||
public readonly setPropertyStream = this._setPropertyStream.asObservable();
|
public readonly setPropertyStream = this._setPropertyStream.asObservable();
|
||||||
|
|
||||||
private readonly _gameStateStream: Subject<void> = new Subject();
|
|
||||||
public readonly gameStateStream = this._gameStateStream.asObservable();
|
|
||||||
|
|
||||||
private readonly _dataLayerChangeStream: Subject<void> = new Subject();
|
private readonly _dataLayerChangeStream: Subject<void> = new Subject();
|
||||||
public readonly dataLayerChangeStream = this._dataLayerChangeStream.asObservable();
|
public readonly dataLayerChangeStream = this._dataLayerChangeStream.asObservable();
|
||||||
|
|
||||||
@ -104,113 +108,170 @@ class IframeListener {
|
|||||||
private readonly _loadSoundStream: Subject<LoadSoundEvent> = new Subject();
|
private readonly _loadSoundStream: Subject<LoadSoundEvent> = new Subject();
|
||||||
public readonly loadSoundStream = this._loadSoundStream.asObservable();
|
public readonly loadSoundStream = this._loadSoundStream.asObservable();
|
||||||
|
|
||||||
|
private readonly _setTilesStream: Subject<SetTilesEvent> = new Subject();
|
||||||
|
public readonly setTilesStream = this._setTilesStream.asObservable();
|
||||||
|
|
||||||
private readonly iframes = new Set<HTMLIFrameElement>();
|
private readonly iframes = new Set<HTMLIFrameElement>();
|
||||||
private readonly iframeCloseCallbacks = new Map<HTMLIFrameElement, (() => void)[]>();
|
private readonly iframeCloseCallbacks = new Map<HTMLIFrameElement, (() => void)[]>();
|
||||||
private readonly scripts = new Map<string, HTMLIFrameElement>();
|
private readonly scripts = new Map<string, HTMLIFrameElement>();
|
||||||
private sendPlayerMove: boolean = false;
|
private sendPlayerMove: boolean = false;
|
||||||
|
|
||||||
|
private answerers: {
|
||||||
|
[key in keyof IframeQueryMap]?: AnswererCallback<key>;
|
||||||
|
} = {};
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
window.addEventListener("message", (message: TypedMessageEvent<IframeEvent<keyof IframeEventMap>>) => {
|
window.addEventListener(
|
||||||
// Do we trust the sender of this message?
|
"message",
|
||||||
// Let's only accept messages from the iframe that are allowed.
|
<T extends keyof IframeEventMap, U extends keyof IframeQueryMap>(
|
||||||
// Note: maybe we could restrict on the domain too for additional security (in case the iframe goes to another domain).
|
message: TypedMessageEvent<IframeEvent<T | U>>
|
||||||
let foundSrc: string | undefined;
|
) => {
|
||||||
|
// Do we trust the sender of this message?
|
||||||
|
// Let's only accept messages from the iframe that are allowed.
|
||||||
|
// Note: maybe we could restrict on the domain too for additional security (in case the iframe goes to another domain).
|
||||||
|
let foundSrc: string | undefined;
|
||||||
|
|
||||||
let iframe: HTMLIFrameElement;
|
let iframe: HTMLIFrameElement | undefined;
|
||||||
for (iframe of this.iframes) {
|
for (iframe of this.iframes) {
|
||||||
if (iframe.contentWindow === message.source) {
|
if (iframe.contentWindow === message.source) {
|
||||||
foundSrc = iframe.src;
|
foundSrc = iframe.src;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (foundSrc === undefined) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const payload = message.data;
|
|
||||||
if (isIframeEventWrapper(payload)) {
|
|
||||||
if (payload.type === 'showLayer' && isLayerEvent(payload.data)) {
|
|
||||||
this._showLayerStream.next(payload.data);
|
|
||||||
} else if (payload.type === 'hideLayer' && isLayerEvent(payload.data)) {
|
|
||||||
this._hideLayerStream.next(payload.data);
|
|
||||||
} else if (payload.type === 'setProperty' && isSetPropertyEvent(payload.data)) {
|
|
||||||
this._setPropertyStream.next(payload.data);
|
|
||||||
} else if (payload.type === 'chat' && isChatEvent(payload.data)) {
|
|
||||||
this._chatStream.next(payload.data);
|
|
||||||
} else if (payload.type === 'openPopup' && isOpenPopupEvent(payload.data)) {
|
|
||||||
this._openPopupStream.next(payload.data);
|
|
||||||
} else if (payload.type === 'closePopup' && isClosePopupEvent(payload.data)) {
|
|
||||||
this._closePopupStream.next(payload.data);
|
|
||||||
}
|
|
||||||
else if (payload.type === 'openTab' && isOpenTabEvent(payload.data)) {
|
|
||||||
scriptUtils.openTab(payload.data.url);
|
|
||||||
}
|
|
||||||
else if (payload.type === 'goToPage' && isGoToPageEvent(payload.data)) {
|
|
||||||
scriptUtils.goToPage(payload.data.url);
|
|
||||||
}
|
|
||||||
else if (payload.type === 'loadPage' && isLoadPageEvent(payload.data)) {
|
|
||||||
this._loadPageStream.next(payload.data.url);
|
|
||||||
}
|
|
||||||
else if (payload.type === 'playSound' && isPlaySoundEvent(payload.data)) {
|
|
||||||
this._playSoundStream.next(payload.data);
|
|
||||||
}
|
|
||||||
else if (payload.type === 'stopSound' && isStopSoundEvent(payload.data)) {
|
|
||||||
this._stopSoundStream.next(payload.data);
|
|
||||||
}
|
|
||||||
else if (payload.type === 'loadSound' && isLoadSoundEvent(payload.data)) {
|
|
||||||
this._loadSoundStream.next(payload.data);
|
|
||||||
}
|
|
||||||
else if (payload.type === 'openCoWebSite' && isOpenCoWebsite(payload.data)) {
|
|
||||||
scriptUtils.openCoWebsite(payload.data.url, foundSrc);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (payload.type === 'closeCoWebSite') {
|
const payload = message.data;
|
||||||
scriptUtils.closeCoWebSite();
|
|
||||||
|
if (foundSrc === undefined || iframe === undefined) {
|
||||||
|
if (isIframeEventWrapper(payload)) {
|
||||||
|
console.warn(
|
||||||
|
"It seems an iFrame is trying to communicate with WorkAdventure but was not explicitly granted the permission to do so. " +
|
||||||
|
"If you are looking to use the WorkAdventure Scripting API inside an iFrame, you should allow the " +
|
||||||
|
'iFrame to communicate with WorkAdventure by using the "openWebsiteAllowApi" property in your map (or passing "true" as a second' +
|
||||||
|
"parameter to WA.nav.openCoWebSite())"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (payload.type === 'disablePlayerControls') {
|
foundSrc = this.getBaseUrl(foundSrc, message.source);
|
||||||
this._disablePlayerControlStream.next();
|
|
||||||
}
|
|
||||||
else if (payload.type === 'restorePlayerControls') {
|
|
||||||
this._enablePlayerControlStream.next();
|
|
||||||
} else if (payload.type === 'displayBubble') {
|
|
||||||
this._displayBubbleStream.next();
|
|
||||||
} else if (payload.type === 'removeBubble') {
|
|
||||||
this._removeBubbleStream.next();
|
|
||||||
} else if (payload.type == "getState") {
|
|
||||||
this._gameStateStream.next();
|
|
||||||
} else if (payload.type == "onPlayerMove") {
|
|
||||||
this.sendPlayerMove = true
|
|
||||||
} else if (payload.type == "getDataLayer") {
|
|
||||||
this._dataLayerChangeStream.next();
|
|
||||||
} else if (isMenuItemRegisterIframeEvent(payload)) {
|
|
||||||
const data = payload.data.menutItem;
|
|
||||||
// @ts-ignore
|
|
||||||
this.iframeCloseCallbacks.get(iframe).push(() => {
|
|
||||||
this._unregisterMenuCommandStream.next(data);
|
|
||||||
})
|
|
||||||
handleMenuItemRegistrationEvent(payload.data)
|
|
||||||
} else if (isTriggerMessageHandlerEvent(payload)) {
|
|
||||||
triggerMessageEventHandler(payload)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, false);
|
|
||||||
|
|
||||||
|
if (isIframeQueryWrapper(payload)) {
|
||||||
|
const queryId = payload.id;
|
||||||
|
const query = payload.query as IframeQuery<U>;
|
||||||
|
|
||||||
|
const answerer = this.answerers[query.type] as AnswererCallback<U> | undefined;
|
||||||
|
if (answerer === undefined) {
|
||||||
|
const errorMsg =
|
||||||
|
'The iFrame sent a message of type "' +
|
||||||
|
query.type +
|
||||||
|
'" but there is no service configured to answer these messages.';
|
||||||
|
console.error(errorMsg);
|
||||||
|
iframe.contentWindow?.postMessage(
|
||||||
|
{
|
||||||
|
id: queryId,
|
||||||
|
type: query.type,
|
||||||
|
error: errorMsg,
|
||||||
|
} as IframeErrorAnswerEvent,
|
||||||
|
"*"
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Promise.resolve(answerer(query.data))
|
||||||
|
.then((value) => {
|
||||||
|
iframe?.contentWindow?.postMessage(
|
||||||
|
{
|
||||||
|
id: queryId,
|
||||||
|
type: query.type,
|
||||||
|
data: value,
|
||||||
|
},
|
||||||
|
"*"
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.catch((reason) => {
|
||||||
|
console.error("An error occurred while responding to an iFrame query.", reason);
|
||||||
|
let reasonMsg: string;
|
||||||
|
if (reason instanceof Error) {
|
||||||
|
reasonMsg = reason.message;
|
||||||
|
} else {
|
||||||
|
reasonMsg = reason.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
iframe?.contentWindow?.postMessage(
|
||||||
|
{
|
||||||
|
id: queryId,
|
||||||
|
type: query.type,
|
||||||
|
error: reasonMsg,
|
||||||
|
} as IframeErrorAnswerEvent,
|
||||||
|
"*"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
} else if (isIframeEventWrapper(payload)) {
|
||||||
|
if (payload.type === "showLayer" && isLayerEvent(payload.data)) {
|
||||||
|
this._showLayerStream.next(payload.data);
|
||||||
|
} else if (payload.type === "hideLayer" && isLayerEvent(payload.data)) {
|
||||||
|
this._hideLayerStream.next(payload.data);
|
||||||
|
} else if (payload.type === "setProperty" && isSetPropertyEvent(payload.data)) {
|
||||||
|
this._setPropertyStream.next(payload.data);
|
||||||
|
} else if (payload.type === "chat" && isChatEvent(payload.data)) {
|
||||||
|
this._chatStream.next(payload.data);
|
||||||
|
} else if (payload.type === "openPopup" && isOpenPopupEvent(payload.data)) {
|
||||||
|
this._openPopupStream.next(payload.data);
|
||||||
|
} else if (payload.type === "closePopup" && isClosePopupEvent(payload.data)) {
|
||||||
|
this._closePopupStream.next(payload.data);
|
||||||
|
} else if (payload.type === "openTab" && isOpenTabEvent(payload.data)) {
|
||||||
|
scriptUtils.openTab(payload.data.url);
|
||||||
|
} else if (payload.type === "goToPage" && isGoToPageEvent(payload.data)) {
|
||||||
|
scriptUtils.goToPage(payload.data.url);
|
||||||
|
} else if (payload.type === "loadPage" && isLoadPageEvent(payload.data)) {
|
||||||
|
this._loadPageStream.next(payload.data.url);
|
||||||
|
} else if (payload.type === "playSound" && isPlaySoundEvent(payload.data)) {
|
||||||
|
this._playSoundStream.next(payload.data);
|
||||||
|
} else if (payload.type === "stopSound" && isStopSoundEvent(payload.data)) {
|
||||||
|
this._stopSoundStream.next(payload.data);
|
||||||
|
} else if (payload.type === "loadSound" && isLoadSoundEvent(payload.data)) {
|
||||||
|
this._loadSoundStream.next(payload.data);
|
||||||
|
} else if (payload.type === "openCoWebSite" && isOpenCoWebsite(payload.data)) {
|
||||||
|
scriptUtils.openCoWebsite(
|
||||||
|
payload.data.url,
|
||||||
|
foundSrc,
|
||||||
|
payload.data.allowApi,
|
||||||
|
payload.data.allowPolicy
|
||||||
|
);
|
||||||
|
} else if (payload.type === "closeCoWebSite") {
|
||||||
|
scriptUtils.closeCoWebSite();
|
||||||
|
} else if (payload.type === "disablePlayerControls") {
|
||||||
|
this._disablePlayerControlStream.next();
|
||||||
|
} else if (payload.type === "restorePlayerControls") {
|
||||||
|
this._enablePlayerControlStream.next();
|
||||||
|
} else if (payload.type === "displayBubble") {
|
||||||
|
this._displayBubbleStream.next();
|
||||||
|
} else if (payload.type === "removeBubble") {
|
||||||
|
this._removeBubbleStream.next();
|
||||||
|
} else if (payload.type == "onPlayerMove") {
|
||||||
|
this.sendPlayerMove = true;
|
||||||
|
} else if (payload.type == "getDataLayer") {
|
||||||
|
this._dataLayerChangeStream.next();
|
||||||
|
} else if (isMenuItemRegisterIframeEvent(payload)) {
|
||||||
|
const data = payload.data.menutItem;
|
||||||
|
// @ts-ignore
|
||||||
|
this.iframeCloseCallbacks.get(iframe).push(() => {
|
||||||
|
this._unregisterMenuCommandStream.next(data);
|
||||||
|
});
|
||||||
|
handleMenuItemRegistrationEvent(payload.data);
|
||||||
|
} else if (payload.type == "setTiles" && isSetTilesEvent(payload.data)) {
|
||||||
|
this._setTilesStream.next(payload.data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
false
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
sendDataLayerEvent(dataLayerEvent: DataLayerEvent) {
|
sendDataLayerEvent(dataLayerEvent: DataLayerEvent) {
|
||||||
this.postMessage({
|
this.postMessage({
|
||||||
'type': 'dataLayer',
|
type: "dataLayer",
|
||||||
'data': dataLayerEvent
|
data: dataLayerEvent,
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
sendGameStateEvent(gameStateEvent: GameStateEvent) {
|
|
||||||
this.postMessage({
|
|
||||||
'type': 'gameState',
|
|
||||||
'data': gameStateEvent
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -223,25 +284,25 @@ class IframeListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
unregisterIframe(iframe: HTMLIFrameElement): void {
|
unregisterIframe(iframe: HTMLIFrameElement): void {
|
||||||
this.iframeCloseCallbacks.get(iframe)?.forEach(callback => {
|
this.iframeCloseCallbacks.get(iframe)?.forEach((callback) => {
|
||||||
callback();
|
callback();
|
||||||
});
|
});
|
||||||
this.iframes.delete(iframe);
|
this.iframes.delete(iframe);
|
||||||
}
|
}
|
||||||
|
|
||||||
registerScript(scriptUrl: string): void {
|
registerScript(scriptUrl: string): void {
|
||||||
console.log('Loading map related script at ', scriptUrl)
|
console.log("Loading map related script at ", scriptUrl);
|
||||||
|
|
||||||
if (!process.env.NODE_ENV || process.env.NODE_ENV === 'development') {
|
if (!process.env.NODE_ENV || process.env.NODE_ENV === "development") {
|
||||||
// Using external iframe mode (
|
// Using external iframe mode (
|
||||||
const iframe = document.createElement('iframe');
|
const iframe = document.createElement("iframe");
|
||||||
iframe.id = IframeListener.getIFrameId(scriptUrl);
|
iframe.id = IframeListener.getIFrameId(scriptUrl);
|
||||||
iframe.style.display = 'none';
|
iframe.style.display = "none";
|
||||||
iframe.src = '/iframe.html?script=' + encodeURIComponent(scriptUrl);
|
iframe.src = "/iframe.html?script=" + encodeURIComponent(scriptUrl);
|
||||||
|
|
||||||
// We are putting a sandbox on this script because it will run in the same domain as the main website.
|
// We are putting a sandbox on this script because it will run in the same domain as the main website.
|
||||||
iframe.sandbox.add('allow-scripts');
|
iframe.sandbox.add("allow-scripts");
|
||||||
iframe.sandbox.add('allow-top-navigation-by-user-activation');
|
iframe.sandbox.add("allow-top-navigation-by-user-activation");
|
||||||
|
|
||||||
document.body.prepend(iframe);
|
document.body.prepend(iframe);
|
||||||
|
|
||||||
@ -249,36 +310,50 @@ class IframeListener {
|
|||||||
this.registerIframe(iframe);
|
this.registerIframe(iframe);
|
||||||
} else {
|
} else {
|
||||||
// production code
|
// production code
|
||||||
const iframe = document.createElement('iframe');
|
const iframe = document.createElement("iframe");
|
||||||
iframe.id = IframeListener.getIFrameId(scriptUrl);
|
iframe.id = IframeListener.getIFrameId(scriptUrl);
|
||||||
iframe.style.display = 'none';
|
iframe.style.display = "none";
|
||||||
|
|
||||||
// We are putting a sandbox on this script because it will run in the same domain as the main website.
|
// We are putting a sandbox on this script because it will run in the same domain as the main website.
|
||||||
iframe.sandbox.add('allow-scripts');
|
iframe.sandbox.add("allow-scripts");
|
||||||
iframe.sandbox.add('allow-top-navigation-by-user-activation');
|
iframe.sandbox.add("allow-top-navigation-by-user-activation");
|
||||||
|
|
||||||
//iframe.src = "data:text/html;charset=utf-8," + escape(html);
|
//iframe.src = "data:text/html;charset=utf-8," + escape(html);
|
||||||
iframe.srcdoc = '<!doctype html>\n' +
|
iframe.srcdoc =
|
||||||
'\n' +
|
"<!doctype html>\n" +
|
||||||
|
"\n" +
|
||||||
'<html lang="en">\n' +
|
'<html lang="en">\n' +
|
||||||
'<head>\n' +
|
"<head>\n" +
|
||||||
'<script src="' + window.location.protocol + '//' + window.location.host + '/iframe_api.js" ></script>\n' +
|
'<script src="' +
|
||||||
'<script src="' + scriptUrl + '" ></script>\n' +
|
window.location.protocol +
|
||||||
'<title></title>\n' +
|
"//" +
|
||||||
'</head>\n' +
|
window.location.host +
|
||||||
'</html>\n';
|
'/iframe_api.js" ></script>\n' +
|
||||||
|
'<script src="' +
|
||||||
|
scriptUrl +
|
||||||
|
'" ></script>\n' +
|
||||||
|
"<title></title>\n" +
|
||||||
|
"</head>\n" +
|
||||||
|
"</html>\n";
|
||||||
|
|
||||||
document.body.prepend(iframe);
|
document.body.prepend(iframe);
|
||||||
|
|
||||||
this.scripts.set(scriptUrl, iframe);
|
this.scripts.set(scriptUrl, iframe);
|
||||||
this.registerIframe(iframe);
|
this.registerIframe(iframe);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private getBaseUrl(src: string, source: MessageEventSource | null): string {
|
||||||
|
for (const script of this.scripts) {
|
||||||
|
if (script[1].contentWindow === source) {
|
||||||
|
return script[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return src;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static getIFrameId(scriptUrl: string): string {
|
private static getIFrameId(scriptUrl: string): string {
|
||||||
return 'script' + btoa(scriptUrl);
|
return "script" + btoa(scriptUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
unregisterScript(scriptUrl: string): void {
|
unregisterScript(scriptUrl: string): void {
|
||||||
@ -295,47 +370,47 @@ class IframeListener {
|
|||||||
|
|
||||||
sendUserInputChat(message: string) {
|
sendUserInputChat(message: string) {
|
||||||
this.postMessage({
|
this.postMessage({
|
||||||
'type': 'userInputChat',
|
type: "userInputChat",
|
||||||
'data': {
|
data: {
|
||||||
'message': message,
|
message: message,
|
||||||
} as UserInputChatEvent
|
} as UserInputChatEvent,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
sendEnterEvent(name: string) {
|
sendEnterEvent(name: string) {
|
||||||
this.postMessage({
|
this.postMessage({
|
||||||
'type': 'enterEvent',
|
type: "enterEvent",
|
||||||
'data': {
|
data: {
|
||||||
"name": name
|
name: name,
|
||||||
} as EnterLeaveEvent
|
} as EnterLeaveEvent,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
sendLeaveEvent(name: string) {
|
sendLeaveEvent(name: string) {
|
||||||
this.postMessage({
|
this.postMessage({
|
||||||
'type': 'leaveEvent',
|
type: "leaveEvent",
|
||||||
'data': {
|
data: {
|
||||||
"name": name
|
name: name,
|
||||||
} as EnterLeaveEvent
|
} as EnterLeaveEvent,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
hasPlayerMoved(event: HasPlayerMovedEvent) {
|
hasPlayerMoved(event: HasPlayerMovedEvent) {
|
||||||
if (this.sendPlayerMove) {
|
if (this.sendPlayerMove) {
|
||||||
this.postMessage({
|
this.postMessage({
|
||||||
'type': 'hasPlayerMoved',
|
type: "hasPlayerMoved",
|
||||||
'data': event
|
data: event,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sendButtonClickedEvent(popupId: number, buttonId: number): void {
|
sendButtonClickedEvent(popupId: number, buttonId: number): void {
|
||||||
this.postMessage({
|
this.postMessage({
|
||||||
'type': 'buttonClickedEvent',
|
type: "buttonClickedEvent",
|
||||||
'data': {
|
data: {
|
||||||
popupId,
|
popupId,
|
||||||
buttonId
|
buttonId,
|
||||||
} as ButtonClickedEvent
|
} as ButtonClickedEvent,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -344,10 +419,30 @@ class IframeListener {
|
|||||||
*/
|
*/
|
||||||
public postMessage(message: IframeResponseEvent<keyof IframeResponseEventMap>) {
|
public postMessage(message: IframeResponseEvent<keyof IframeResponseEventMap>) {
|
||||||
for (const iframe of this.iframes) {
|
for (const iframe of this.iframes) {
|
||||||
iframe.contentWindow?.postMessage(message, '*');
|
iframe.contentWindow?.postMessage(message, "*");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers a callback that can be used to respond to some query (as defined in the IframeQueryMap type).
|
||||||
|
*
|
||||||
|
* Important! There can be only one "answerer" so registering a new one will unregister the old one.
|
||||||
|
*
|
||||||
|
* @param key The "type" of the query we are answering
|
||||||
|
* @param callback
|
||||||
|
*/
|
||||||
|
public registerAnswerer<T extends keyof IframeQueryMap, Guard extends tg.TypeGuard<IframeQueryMap[T]["query"]>>(
|
||||||
|
key: T,
|
||||||
|
callback: AnswererCallback<T>,
|
||||||
|
typeChecker?: Guard
|
||||||
|
): void {
|
||||||
|
//@ts-ignore
|
||||||
|
this.answerers[key] = callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
public unregisterAnswerer(key: keyof IframeQueryMap): void {
|
||||||
|
delete this.answerers[key];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const iframeListener = new IframeListener();
|
export const iframeListener = new IframeListener();
|
||||||
|
@ -1,21 +1,19 @@
|
|||||||
import {coWebsiteManager} from "../WebRtc/CoWebsiteManager";
|
import { coWebsiteManager } from "../WebRtc/CoWebsiteManager";
|
||||||
|
|
||||||
class ScriptUtils {
|
class ScriptUtils {
|
||||||
|
public openTab(url: string) {
|
||||||
public openTab(url : string){
|
|
||||||
window.open(url);
|
window.open(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
public goToPage(url : string){
|
public goToPage(url: string) {
|
||||||
window.location.href = url;
|
window.location.href = url;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public openCoWebsite(url: string, base: string) {
|
public openCoWebsite(url: string, base: string, api: boolean, policy: string) {
|
||||||
coWebsiteManager.loadCoWebsite(url, base);
|
coWebsiteManager.loadCoWebsite(url, base, api, policy);
|
||||||
}
|
}
|
||||||
|
|
||||||
public closeCoWebSite(){
|
public closeCoWebSite() {
|
||||||
coWebsiteManager.closeCoWebsite();
|
coWebsiteManager.closeCoWebsite();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,20 +1,66 @@
|
|||||||
import type * as tg from "generic-type-guard";
|
import type * as tg from "generic-type-guard";
|
||||||
import type { IframeEvent, IframeEventMap, IframeResponseEventMap } from '../Events/IframeEvent';
|
import type {
|
||||||
|
IframeEvent,
|
||||||
|
IframeEventMap,
|
||||||
|
IframeQuery,
|
||||||
|
IframeQueryMap,
|
||||||
|
IframeResponseEventMap,
|
||||||
|
} from "../Events/IframeEvent";
|
||||||
|
import type { IframeQueryWrapper } from "../Events/IframeEvent";
|
||||||
|
|
||||||
export function sendToWorkadventure(content: IframeEvent<keyof IframeEventMap>) {
|
export function sendToWorkadventure(content: IframeEvent<keyof IframeEventMap>) {
|
||||||
window.parent.postMessage(content, "*")
|
window.parent.postMessage(content, "*");
|
||||||
}
|
}
|
||||||
type GuardedType<Guard extends tg.TypeGuard<unknown>> = Guard extends tg.TypeGuard<infer T> ? T : never
|
|
||||||
|
|
||||||
export interface IframeCallback<Key extends keyof IframeResponseEventMap, T = IframeResponseEventMap[Key], Guard = tg.TypeGuard<T>> {
|
let queryNumber = 0;
|
||||||
|
|
||||||
typeChecker: Guard,
|
export const answerPromises = new Map<
|
||||||
callback: (payloadData: T) => void
|
number,
|
||||||
|
{
|
||||||
|
resolve: (
|
||||||
|
value:
|
||||||
|
| IframeQueryMap[keyof IframeQueryMap]["answer"]
|
||||||
|
| PromiseLike<IframeQueryMap[keyof IframeQueryMap]["answer"]>
|
||||||
|
) => void;
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
reject: (reason?: any) => void;
|
||||||
|
}
|
||||||
|
>();
|
||||||
|
|
||||||
|
export function queryWorkadventure<T extends keyof IframeQueryMap>(
|
||||||
|
content: IframeQuery<T>
|
||||||
|
): Promise<IframeQueryMap[T]["answer"]> {
|
||||||
|
return new Promise<IframeQueryMap[T]["answer"]>((resolve, reject) => {
|
||||||
|
window.parent.postMessage(
|
||||||
|
{
|
||||||
|
id: queryNumber,
|
||||||
|
query: content,
|
||||||
|
} as IframeQueryWrapper<T>,
|
||||||
|
"*"
|
||||||
|
);
|
||||||
|
|
||||||
|
answerPromises.set(queryNumber, {
|
||||||
|
resolve,
|
||||||
|
reject,
|
||||||
|
});
|
||||||
|
|
||||||
|
queryNumber++;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
type GuardedType<Guard extends tg.TypeGuard<unknown>> = Guard extends tg.TypeGuard<infer T> ? T : never;
|
||||||
|
|
||||||
|
export interface IframeCallback<
|
||||||
|
Key extends keyof IframeResponseEventMap,
|
||||||
|
T = IframeResponseEventMap[Key],
|
||||||
|
Guard = tg.TypeGuard<T>
|
||||||
|
> {
|
||||||
|
typeChecker: Guard;
|
||||||
|
callback: (payloadData: T) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IframeCallbackContribution<Key extends keyof IframeResponseEventMap> extends IframeCallback<Key> {
|
export interface IframeCallbackContribution<Key extends keyof IframeResponseEventMap> extends IframeCallback<Key> {
|
||||||
|
type: Key;
|
||||||
type: Key
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -23,9 +69,10 @@ export interface IframeCallbackContribution<Key extends keyof IframeResponseEven
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export abstract class IframeApiContribution<T extends {
|
export abstract class IframeApiContribution<
|
||||||
callbacks: Array<IframeCallbackContribution<keyof IframeResponseEventMap>>,
|
T extends {
|
||||||
}> {
|
callbacks: Array<IframeCallbackContribution<keyof IframeResponseEventMap>>;
|
||||||
|
}
|
||||||
abstract callbacks: T["callbacks"]
|
> {
|
||||||
|
abstract callbacks: T["callbacks"];
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import type { MenuItemClickedEvent } from '../../Events/ui/MenuItemClickedEvent';
|
import type { MenuItemClickedEvent } from "../../Events/ui/MenuItemClickedEvent";
|
||||||
import { iframeListener } from '../../IframeListener';
|
import { iframeListener } from "../../IframeListener";
|
||||||
|
|
||||||
export function sendMenuClickedEvent(menuItem: string) {
|
export function sendMenuClickedEvent(menuItem: string) {
|
||||||
iframeListener.postMessage({
|
iframeListener.postMessage({
|
||||||
'type': 'menuItemClicked',
|
type: "menuItemClicked",
|
||||||
'data': {
|
data: {
|
||||||
menuItem: menuItem,
|
menuItem: menuItem,
|
||||||
} as MenuItemClickedEvent
|
} as MenuItemClickedEvent,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1,22 +1,25 @@
|
|||||||
|
import {
|
||||||
import { removeTriggerMessage, triggerMessage, TriggerMessageEvent } from '../../Events/ui/TriggerMessageEvent';
|
MessageReferenceEvent,
|
||||||
import { sendToWorkadventure } from '../IframeApiContribution';
|
removeTriggerMessage,
|
||||||
|
triggerMessage,
|
||||||
|
TriggerMessageEvent,
|
||||||
|
} from "../../Events/ui/TriggerMessageEvent";
|
||||||
|
import { queryWorkadventure } from "../IframeApiContribution";
|
||||||
function uuidv4() {
|
function uuidv4() {
|
||||||
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
|
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
|
||||||
const r = Math.random() * 16 | 0, v = c === 'x' ? r : (r & 0x3 | 0x8);
|
const r = (Math.random() * 16) | 0,
|
||||||
|
v = c === "x" ? r : (r & 0x3) | 0x8;
|
||||||
return v.toString(16);
|
return v.toString(16);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export let triggerMessageInstance: TriggerMessage | undefined = undefined
|
export let triggerMessageInstance: TriggerMessage | undefined = undefined;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export class TriggerMessage {
|
export class TriggerMessage {
|
||||||
uuid: string
|
uuid: string;
|
||||||
|
|
||||||
constructor(private message: string, private callback: () => void) {
|
constructor(private message: string, private callback: () => void) {
|
||||||
this.uuid = uuidv4()
|
this.uuid = uuidv4();
|
||||||
if (triggerMessageInstance) {
|
if (triggerMessageInstance) {
|
||||||
triggerMessageInstance.remove();
|
triggerMessageInstance.remove();
|
||||||
}
|
}
|
||||||
@ -24,28 +27,24 @@ export class TriggerMessage {
|
|||||||
this.create();
|
this.create();
|
||||||
}
|
}
|
||||||
|
|
||||||
create(): this {
|
async create() {
|
||||||
sendToWorkadventure({
|
await queryWorkadventure({
|
||||||
type: triggerMessage,
|
type: triggerMessage,
|
||||||
data: {
|
data: {
|
||||||
message: this.message,
|
message: this.message,
|
||||||
uuid: this.uuid
|
uuid: this.uuid,
|
||||||
} as TriggerMessageEvent
|
} as TriggerMessageEvent,
|
||||||
})
|
});
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
remove() {
|
|
||||||
sendToWorkadventure({
|
|
||||||
type: removeTriggerMessage,
|
|
||||||
data: {
|
|
||||||
uuid: this.uuid
|
|
||||||
} as TriggerMessageEvent
|
|
||||||
})
|
|
||||||
triggerMessageInstance = undefined
|
|
||||||
}
|
|
||||||
|
|
||||||
trigger() {
|
|
||||||
this.callback();
|
this.callback();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
async remove() {
|
||||||
|
await queryWorkadventure({
|
||||||
|
type: removeTriggerMessage,
|
||||||
|
data: {
|
||||||
|
uuid: this.uuid,
|
||||||
|
} as MessageReferenceEvent,
|
||||||
|
});
|
||||||
|
triggerMessageInstance = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,30 +1,30 @@
|
|||||||
import type { ChatEvent } from '../Events/ChatEvent'
|
import type { ChatEvent } from "../Events/ChatEvent";
|
||||||
import { isUserInputChatEvent, UserInputChatEvent } from '../Events/UserInputChatEvent'
|
import { isUserInputChatEvent, UserInputChatEvent } from "../Events/UserInputChatEvent";
|
||||||
import { IframeApiContribution, sendToWorkadventure } from './IframeApiContribution'
|
import { IframeApiContribution, sendToWorkadventure } from "./IframeApiContribution";
|
||||||
import { apiCallback } from "./registeredCallbacks";
|
import { apiCallback } from "./registeredCallbacks";
|
||||||
import {Subject} from "rxjs";
|
import { Subject } from "rxjs";
|
||||||
|
|
||||||
const chatStream = new Subject<string>();
|
const chatStream = new Subject<string>();
|
||||||
|
|
||||||
class WorkadventureChatCommands extends IframeApiContribution<WorkadventureChatCommands> {
|
export class WorkadventureChatCommands extends IframeApiContribution<WorkadventureChatCommands> {
|
||||||
|
callbacks = [
|
||||||
callbacks = [apiCallback({
|
apiCallback({
|
||||||
callback: (event: UserInputChatEvent) => {
|
callback: (event: UserInputChatEvent) => {
|
||||||
chatStream.next(event.message);
|
chatStream.next(event.message);
|
||||||
},
|
},
|
||||||
type: "userInputChat",
|
type: "userInputChat",
|
||||||
typeChecker: isUserInputChatEvent
|
typeChecker: isUserInputChatEvent,
|
||||||
})]
|
}),
|
||||||
|
];
|
||||||
|
|
||||||
sendChatMessage(message: string, author: string) {
|
sendChatMessage(message: string, author: string) {
|
||||||
sendToWorkadventure({
|
sendToWorkadventure({
|
||||||
type: 'chat',
|
type: "chat",
|
||||||
data: {
|
data: {
|
||||||
'message': message,
|
message: message,
|
||||||
'author': author
|
author: author,
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -35,4 +35,4 @@ class WorkadventureChatCommands extends IframeApiContribution<WorkadventureChatC
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default new WorkadventureChatCommands()
|
export default new WorkadventureChatCommands();
|
||||||
|
@ -1,16 +1,15 @@
|
|||||||
import { IframeApiContribution, sendToWorkadventure } from './IframeApiContribution';
|
import { IframeApiContribution, sendToWorkadventure } from "./IframeApiContribution";
|
||||||
|
|
||||||
class WorkadventureControlsCommands extends IframeApiContribution<WorkadventureControlsCommands> {
|
export class WorkadventureControlsCommands extends IframeApiContribution<WorkadventureControlsCommands> {
|
||||||
callbacks = []
|
callbacks = [];
|
||||||
|
|
||||||
disablePlayerControls(): void {
|
disablePlayerControls(): void {
|
||||||
sendToWorkadventure({ 'type': 'disablePlayerControls', data: null });
|
sendToWorkadventure({ type: "disablePlayerControls", data: null });
|
||||||
}
|
}
|
||||||
|
|
||||||
restorePlayerControls(): void {
|
restorePlayerControls(): void {
|
||||||
sendToWorkadventure({ 'type': 'restorePlayerControls', data: null });
|
sendToWorkadventure({ type: "restorePlayerControls", data: null });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export default new WorkadventureControlsCommands();
|
export default new WorkadventureControlsCommands();
|
||||||
|
@ -1,57 +1,56 @@
|
|||||||
import type { GoToPageEvent } from '../Events/GoToPageEvent';
|
import type { GoToPageEvent } from "../Events/GoToPageEvent";
|
||||||
import type { OpenTabEvent } from '../Events/OpenTabEvent';
|
import type { OpenTabEvent } from "../Events/OpenTabEvent";
|
||||||
import { IframeApiContribution, sendToWorkadventure } from './IframeApiContribution';
|
import { IframeApiContribution, sendToWorkadventure } from "./IframeApiContribution";
|
||||||
import type {OpenCoWebSiteEvent} from "../Events/OpenCoWebSiteEvent";
|
import type { OpenCoWebSiteEvent } from "../Events/OpenCoWebSiteEvent";
|
||||||
import type {LoadPageEvent} from "../Events/LoadPageEvent";
|
import type { LoadPageEvent } from "../Events/LoadPageEvent";
|
||||||
|
|
||||||
|
|
||||||
class WorkadventureNavigationCommands extends IframeApiContribution<WorkadventureNavigationCommands> {
|
|
||||||
callbacks = []
|
|
||||||
|
|
||||||
|
export class WorkadventureNavigationCommands extends IframeApiContribution<WorkadventureNavigationCommands> {
|
||||||
|
callbacks = [];
|
||||||
|
|
||||||
openTab(url: string): void {
|
openTab(url: string): void {
|
||||||
sendToWorkadventure({
|
sendToWorkadventure({
|
||||||
"type": 'openTab',
|
type: "openTab",
|
||||||
"data": {
|
data: {
|
||||||
url
|
url,
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
goToPage(url: string): void {
|
goToPage(url: string): void {
|
||||||
sendToWorkadventure({
|
sendToWorkadventure({
|
||||||
"type": 'goToPage',
|
type: "goToPage",
|
||||||
"data": {
|
data: {
|
||||||
url
|
url,
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
goToRoom(url: string): void {
|
goToRoom(url: string): void {
|
||||||
sendToWorkadventure({
|
sendToWorkadventure({
|
||||||
"type": 'loadPage',
|
type: "loadPage",
|
||||||
"data": {
|
data: {
|
||||||
url
|
url,
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
openCoWebSite(url: string): void {
|
openCoWebSite(url: string, allowApi: boolean = false, allowPolicy: string = ""): void {
|
||||||
sendToWorkadventure({
|
sendToWorkadventure({
|
||||||
"type": 'openCoWebSite',
|
type: "openCoWebSite",
|
||||||
"data": {
|
data: {
|
||||||
url
|
url,
|
||||||
}
|
allowApi,
|
||||||
|
allowPolicy,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
closeCoWebSite(): void {
|
closeCoWebSite(): void {
|
||||||
sendToWorkadventure({
|
sendToWorkadventure({
|
||||||
"type": 'closeCoWebSite',
|
type: "closeCoWebSite",
|
||||||
data: null
|
data: null,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export default new WorkadventureNavigationCommands();
|
export default new WorkadventureNavigationCommands();
|
||||||
|
@ -1,29 +1,29 @@
|
|||||||
import {IframeApiContribution, sendToWorkadventure} from "./IframeApiContribution";
|
import { IframeApiContribution, sendToWorkadventure } from "./IframeApiContribution";
|
||||||
import type {HasPlayerMovedEvent, HasPlayerMovedEventCallback} from "../Events/HasPlayerMovedEvent";
|
import type { HasPlayerMovedEvent, HasPlayerMovedEventCallback } from "../Events/HasPlayerMovedEvent";
|
||||||
import {Subject} from "rxjs";
|
import { Subject } from "rxjs";
|
||||||
import {apiCallback} from "./registeredCallbacks";
|
import { apiCallback } from "./registeredCallbacks";
|
||||||
import {isHasPlayerMovedEvent} from "../Events/HasPlayerMovedEvent";
|
import { isHasPlayerMovedEvent } from "../Events/HasPlayerMovedEvent";
|
||||||
|
|
||||||
const moveStream = new Subject<HasPlayerMovedEvent>();
|
const moveStream = new Subject<HasPlayerMovedEvent>();
|
||||||
|
|
||||||
class WorkadventurePlayerCommands extends IframeApiContribution<WorkadventurePlayerCommands> {
|
export class WorkadventurePlayerCommands extends IframeApiContribution<WorkadventurePlayerCommands> {
|
||||||
callbacks = [
|
callbacks = [
|
||||||
apiCallback({
|
apiCallback({
|
||||||
type: 'hasPlayerMoved',
|
type: "hasPlayerMoved",
|
||||||
typeChecker: isHasPlayerMovedEvent,
|
typeChecker: isHasPlayerMovedEvent,
|
||||||
callback: (payloadData) => {
|
callback: (payloadData) => {
|
||||||
moveStream.next(payloadData);
|
moveStream.next(payloadData);
|
||||||
}
|
},
|
||||||
}),
|
}),
|
||||||
]
|
];
|
||||||
|
|
||||||
onPlayerMove(callback: HasPlayerMovedEventCallback): void {
|
onPlayerMove(callback: HasPlayerMovedEventCallback): void {
|
||||||
moveStream.subscribe(callback);
|
moveStream.subscribe(callback);
|
||||||
sendToWorkadventure({
|
sendToWorkadventure({
|
||||||
type: 'onPlayerMove',
|
type: "onPlayerMove",
|
||||||
data: null
|
data: null,
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default new WorkadventurePlayerCommands();
|
export default new WorkadventurePlayerCommands();
|
||||||
|
@ -1,87 +1,81 @@
|
|||||||
import { Subject } from "rxjs";
|
import { Subject } from "rxjs";
|
||||||
import { EnterLeaveEvent, isEnterLeaveEvent } from '../Events/EnterLeaveEvent';
|
|
||||||
import {IframeApiContribution, sendToWorkadventure} from './IframeApiContribution';
|
import { isDataLayerEvent } from "../Events/DataLayerEvent";
|
||||||
|
import { EnterLeaveEvent, isEnterLeaveEvent } from "../Events/EnterLeaveEvent";
|
||||||
|
import { isGameStateEvent } from "../Events/GameStateEvent";
|
||||||
|
|
||||||
|
import { IframeApiContribution, queryWorkadventure, sendToWorkadventure } from "./IframeApiContribution";
|
||||||
import { apiCallback } from "./registeredCallbacks";
|
import { apiCallback } from "./registeredCallbacks";
|
||||||
import type {LayerEvent} from "../Events/LayerEvent";
|
|
||||||
import type {SetPropertyEvent} from "../Events/setPropertyEvent";
|
import type { ITiledMap } from "../../Phaser/Map/ITiledMap";
|
||||||
import type {GameStateEvent} from "../Events/GameStateEvent";
|
import type { DataLayerEvent } from "../Events/DataLayerEvent";
|
||||||
import type {ITiledMap} from "../../Phaser/Map/ITiledMap";
|
import type { GameStateEvent } from "../Events/GameStateEvent";
|
||||||
import type {DataLayerEvent} from "../Events/DataLayerEvent";
|
|
||||||
import {isGameStateEvent} from "../Events/GameStateEvent";
|
|
||||||
import {isDataLayerEvent} from "../Events/DataLayerEvent";
|
|
||||||
|
|
||||||
const enterStreams: Map<string, Subject<EnterLeaveEvent>> = new Map<string, Subject<EnterLeaveEvent>>();
|
const enterStreams: Map<string, Subject<EnterLeaveEvent>> = new Map<string, Subject<EnterLeaveEvent>>();
|
||||||
const leaveStreams: Map<string, Subject<EnterLeaveEvent>> = new Map<string, Subject<EnterLeaveEvent>>();
|
const leaveStreams: Map<string, Subject<EnterLeaveEvent>> = new Map<string, Subject<EnterLeaveEvent>>();
|
||||||
const dataLayerResolver = new Subject<DataLayerEvent>();
|
const dataLayerResolver = new Subject<DataLayerEvent>();
|
||||||
const stateResolvers = new Subject<GameStateEvent>();
|
const stateResolvers = new Subject<GameStateEvent>();
|
||||||
|
|
||||||
let immutableData: GameStateEvent;
|
let immutableDataPromise: Promise<GameStateEvent> | undefined = undefined;
|
||||||
|
|
||||||
interface Room {
|
interface Room {
|
||||||
id: string,
|
id: string;
|
||||||
mapUrl: string,
|
mapUrl: string;
|
||||||
map: ITiledMap,
|
map: ITiledMap;
|
||||||
startLayer: string | null
|
startLayer: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface User {
|
interface User {
|
||||||
id: string | undefined,
|
id: string | undefined;
|
||||||
nickName: string | null,
|
nickName: string | null;
|
||||||
tags: string[]
|
tags: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface TileDescriptor {
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
tile: number | string;
|
||||||
|
layer: string;
|
||||||
|
}
|
||||||
|
|
||||||
function getGameState(): Promise<GameStateEvent> {
|
function getGameState(): Promise<GameStateEvent> {
|
||||||
if (immutableData) {
|
if (immutableDataPromise === undefined) {
|
||||||
return Promise.resolve(immutableData);
|
immutableDataPromise = queryWorkadventure({ type: "getState", data: undefined });
|
||||||
}
|
|
||||||
else {
|
|
||||||
return new Promise<GameStateEvent>((resolver, thrower) => {
|
|
||||||
stateResolvers.subscribe(resolver);
|
|
||||||
sendToWorkadventure({type: "getState", data: null});
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
return immutableDataPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getDataLayer(): Promise<DataLayerEvent> {
|
function getDataLayer(): Promise<DataLayerEvent> {
|
||||||
return new Promise<DataLayerEvent>((resolver, thrower) => {
|
return new Promise<DataLayerEvent>((resolver, thrower) => {
|
||||||
dataLayerResolver.subscribe(resolver);
|
dataLayerResolver.subscribe(resolver);
|
||||||
sendToWorkadventure({type: "getDataLayer", data: null})
|
sendToWorkadventure({ type: "getDataLayer", data: null });
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
class WorkadventureRoomCommands extends IframeApiContribution<WorkadventureRoomCommands> {
|
export class WorkadventureRoomCommands extends IframeApiContribution<WorkadventureRoomCommands> {
|
||||||
callbacks = [
|
callbacks = [
|
||||||
apiCallback({
|
apiCallback({
|
||||||
callback: (payloadData: EnterLeaveEvent) => {
|
callback: (payloadData: EnterLeaveEvent) => {
|
||||||
enterStreams.get(payloadData.name)?.next();
|
enterStreams.get(payloadData.name)?.next();
|
||||||
},
|
},
|
||||||
type: "enterEvent",
|
type: "enterEvent",
|
||||||
typeChecker: isEnterLeaveEvent
|
typeChecker: isEnterLeaveEvent,
|
||||||
}),
|
}),
|
||||||
apiCallback({
|
apiCallback({
|
||||||
type: "leaveEvent",
|
type: "leaveEvent",
|
||||||
typeChecker: isEnterLeaveEvent,
|
typeChecker: isEnterLeaveEvent,
|
||||||
callback: (payloadData) => {
|
callback: (payloadData) => {
|
||||||
leaveStreams.get(payloadData.name)?.next();
|
leaveStreams.get(payloadData.name)?.next();
|
||||||
}
|
},
|
||||||
}),
|
|
||||||
apiCallback({
|
|
||||||
type: "gameState",
|
|
||||||
typeChecker: isGameStateEvent,
|
|
||||||
callback: (payloadData) => {
|
|
||||||
stateResolvers.next(payloadData);
|
|
||||||
}
|
|
||||||
}),
|
}),
|
||||||
apiCallback({
|
apiCallback({
|
||||||
type: "dataLayer",
|
type: "dataLayer",
|
||||||
typeChecker: isDataLayerEvent,
|
typeChecker: isDataLayerEvent,
|
||||||
callback: (payloadData) => {
|
callback: (payloadData) => {
|
||||||
dataLayerResolver.next(payloadData);
|
dataLayerResolver.next(payloadData);
|
||||||
}
|
},
|
||||||
}),
|
}),
|
||||||
]
|
];
|
||||||
|
|
||||||
|
|
||||||
onEnterZone(name: string, callback: () => void): void {
|
onEnterZone(name: string, callback: () => void): void {
|
||||||
let subject = enterStreams.get(name);
|
let subject = enterStreams.get(name);
|
||||||
@ -90,7 +84,6 @@ class WorkadventureRoomCommands extends IframeApiContribution<WorkadventureRoomC
|
|||||||
enterStreams.set(name, subject);
|
enterStreams.set(name, subject);
|
||||||
}
|
}
|
||||||
subject.subscribe(callback);
|
subject.subscribe(callback);
|
||||||
|
|
||||||
}
|
}
|
||||||
onLeaveZone(name: string, callback: () => void): void {
|
onLeaveZone(name: string, callback: () => void): void {
|
||||||
let subject = leaveStreams.get(name);
|
let subject = leaveStreams.get(name);
|
||||||
@ -101,35 +94,44 @@ class WorkadventureRoomCommands extends IframeApiContribution<WorkadventureRoomC
|
|||||||
subject.subscribe(callback);
|
subject.subscribe(callback);
|
||||||
}
|
}
|
||||||
showLayer(layerName: string): void {
|
showLayer(layerName: string): void {
|
||||||
sendToWorkadventure({type: 'showLayer', data: {'name': layerName}});
|
sendToWorkadventure({ type: "showLayer", data: { name: layerName } });
|
||||||
}
|
}
|
||||||
hideLayer(layerName: string): void {
|
hideLayer(layerName: string): void {
|
||||||
sendToWorkadventure({type: 'hideLayer', data: {'name': layerName}});
|
sendToWorkadventure({ type: "hideLayer", data: { name: layerName } });
|
||||||
}
|
}
|
||||||
setProperty(layerName: string, propertyName: string, propertyValue: string | number | boolean | undefined): void {
|
setProperty(layerName: string, propertyName: string, propertyValue: string | number | boolean | undefined): void {
|
||||||
sendToWorkadventure({
|
sendToWorkadventure({
|
||||||
type: 'setProperty',
|
type: "setProperty",
|
||||||
data: {
|
data: {
|
||||||
'layerName': layerName,
|
layerName: layerName,
|
||||||
'propertyName': propertyName,
|
propertyName: propertyName,
|
||||||
'propertyValue': propertyValue,
|
propertyValue: propertyValue,
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
getCurrentRoom(): Promise<Room> {
|
getCurrentRoom(): Promise<Room> {
|
||||||
return getGameState().then((gameState) => {
|
return getGameState().then((gameState) => {
|
||||||
return getDataLayer().then((mapJson) => {
|
return getDataLayer().then((mapJson) => {
|
||||||
return {id: gameState.roomId, map: mapJson.data as ITiledMap, mapUrl: gameState.mapUrl, startLayer: gameState.startLayerName};
|
return {
|
||||||
})
|
id: gameState.roomId,
|
||||||
})
|
map: mapJson.data as ITiledMap,
|
||||||
|
mapUrl: gameState.mapUrl,
|
||||||
|
startLayer: gameState.startLayerName,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
getCurrentUser(): Promise<User> {
|
getCurrentUser(): Promise<User> {
|
||||||
return getGameState().then((gameState) => {
|
return getGameState().then((gameState) => {
|
||||||
return {id: gameState.uuid, nickName: gameState.nickname, tags: gameState.tags};
|
return { id: gameState.uuid, nickName: gameState.nickname, tags: gameState.tags };
|
||||||
})
|
});
|
||||||
|
}
|
||||||
|
setTiles(tiles: TileDescriptor[]) {
|
||||||
|
sendToWorkadventure({
|
||||||
|
type: "setTiles",
|
||||||
|
data: tiles,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export default new WorkadventureRoomCommands();
|
export default new WorkadventureRoomCommands();
|
||||||
|
@ -1,17 +1,15 @@
|
|||||||
import type { LoadSoundEvent } from '../Events/LoadSoundEvent';
|
import type { LoadSoundEvent } from "../Events/LoadSoundEvent";
|
||||||
import type { PlaySoundEvent } from '../Events/PlaySoundEvent';
|
import type { PlaySoundEvent } from "../Events/PlaySoundEvent";
|
||||||
import type { StopSoundEvent } from '../Events/StopSoundEvent';
|
import type { StopSoundEvent } from "../Events/StopSoundEvent";
|
||||||
import { IframeApiContribution, sendToWorkadventure } from './IframeApiContribution';
|
import { IframeApiContribution, sendToWorkadventure } from "./IframeApiContribution";
|
||||||
import {Sound} from "./Sound/Sound";
|
import { Sound } from "./Sound/Sound";
|
||||||
|
|
||||||
class WorkadventureSoundCommands extends IframeApiContribution<WorkadventureSoundCommands> {
|
export class WorkadventureSoundCommands extends IframeApiContribution<WorkadventureSoundCommands> {
|
||||||
callbacks = []
|
callbacks = [];
|
||||||
|
|
||||||
loadSound(url: string): Sound {
|
loadSound(url: string): Sound {
|
||||||
return new Sound(url);
|
return new Sound(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export default new WorkadventureSoundCommands();
|
export default new WorkadventureSoundCommands();
|
||||||
|
@ -1,20 +1,29 @@
|
|||||||
import { isButtonClickedEvent } from '../Events/ButtonClickedEvent';
|
import { isButtonClickedEvent } from "../Events/ButtonClickedEvent";
|
||||||
import { isMenuItemClickedEvent } from '../Events/ui/MenuItemClickedEvent';
|
import { isMenuItemClickedEvent } from "../Events/ui/MenuItemClickedEvent";
|
||||||
import { isMessageReferenceEvent } from '../Events/ui/TriggerMessageEvent';
|
import { IframeApiContribution, sendToWorkadventure } from "./IframeApiContribution";
|
||||||
import { IframeApiContribution, sendToWorkadventure } from './IframeApiContribution';
|
|
||||||
import { apiCallback } from "./registeredCallbacks";
|
import { apiCallback } from "./registeredCallbacks";
|
||||||
import type { ButtonClickedCallback, ButtonDescriptor } from "./Ui/ButtonDescriptor";
|
import type { ButtonClickedCallback, ButtonDescriptor } from "./Ui/ButtonDescriptor";
|
||||||
import { Popup } from "./Ui/Popup";
|
import { Popup } from "./Ui/Popup";
|
||||||
import { TriggerMessage, triggerMessageInstance } from './Ui/TriggerMessage';
|
import { TriggerMessage } from "./Ui/TriggerMessage";
|
||||||
|
|
||||||
let popupId = 0;
|
let popupId = 0;
|
||||||
const popups: Map<number, Popup> = new Map<number, Popup>();
|
const popups: Map<number, Popup> = new Map<number, Popup>();
|
||||||
const popupCallbacks: Map<number, Map<number, ButtonClickedCallback>> = new Map<number, Map<number, ButtonClickedCallback>>();
|
const popupCallbacks: Map<number, Map<number, ButtonClickedCallback>> = new Map<
|
||||||
|
number,
|
||||||
|
Map<number, ButtonClickedCallback>
|
||||||
|
>();
|
||||||
|
|
||||||
const menuCallbacks: Map<string, (command: string) => void> = new Map()
|
const menuCallbacks: Map<string, (command: string) => void> = new Map();
|
||||||
|
|
||||||
class WorkAdventureUiCommands extends IframeApiContribution<WorkAdventureUiCommands> {
|
interface ZonedPopupOptions {
|
||||||
|
zone: string;
|
||||||
|
objectLayerName?: string;
|
||||||
|
popupText: string;
|
||||||
|
delay?: number;
|
||||||
|
popupOptions: Array<ButtonDescriptor>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class WorkAdventureUiCommands extends IframeApiContribution<WorkAdventureUiCommands> {
|
||||||
callbacks = [
|
callbacks = [
|
||||||
apiCallback({
|
apiCallback({
|
||||||
type: "buttonClickedEvent",
|
type: "buttonClickedEvent",
|
||||||
@ -28,28 +37,20 @@ class WorkAdventureUiCommands extends IframeApiContribution<WorkAdventureUiComma
|
|||||||
if (callback) {
|
if (callback) {
|
||||||
callback(popup);
|
callback(popup);
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
}),
|
}),
|
||||||
apiCallback({
|
apiCallback({
|
||||||
type: "menuItemClicked",
|
type: "menuItemClicked",
|
||||||
typeChecker: isMenuItemClickedEvent,
|
typeChecker: isMenuItemClickedEvent,
|
||||||
callback: event => {
|
callback: (event) => {
|
||||||
const callback = menuCallbacks.get(event.menuItem);
|
const callback = menuCallbacks.get(event.menuItem);
|
||||||
if (callback) {
|
if (callback) {
|
||||||
callback(event.menuItem)
|
callback(event.menuItem);
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
}),
|
}),
|
||||||
apiCallback({
|
|
||||||
type: "messageTriggered",
|
|
||||||
typeChecker: isMessageReferenceEvent,
|
|
||||||
callback: event => {
|
|
||||||
triggerMessageInstance?.trigger();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
||||||
openPopup(targetObject: string, message: string, buttons: ButtonDescriptor[]): Popup {
|
openPopup(targetObject: string, message: string, buttons: ButtonDescriptor[]): Popup {
|
||||||
popupId++;
|
popupId++;
|
||||||
const popup = new Popup(popupId);
|
const popup = new Popup(popupId);
|
||||||
@ -67,41 +68,42 @@ class WorkAdventureUiCommands extends IframeApiContribution<WorkAdventureUiComma
|
|||||||
}
|
}
|
||||||
|
|
||||||
sendToWorkadventure({
|
sendToWorkadventure({
|
||||||
'type': 'openPopup',
|
type: "openPopup",
|
||||||
'data': {
|
data: {
|
||||||
popupId,
|
popupId,
|
||||||
targetObject,
|
targetObject,
|
||||||
message,
|
message,
|
||||||
buttons: buttons.map((button) => {
|
buttons: buttons.map((button) => {
|
||||||
return {
|
return {
|
||||||
label: button.label,
|
label: button.label,
|
||||||
className: button.className
|
className: button.className,
|
||||||
};
|
};
|
||||||
})
|
}),
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
popups.set(popupId, popup)
|
popups.set(popupId, popup);
|
||||||
return popup;
|
return popup;
|
||||||
}
|
}
|
||||||
|
|
||||||
registerMenuCommand(commandDescriptor: string, callback: (commandDescriptor: string) => void) {
|
registerMenuCommand(commandDescriptor: string, callback: (commandDescriptor: string) => void) {
|
||||||
menuCallbacks.set(commandDescriptor, callback);
|
menuCallbacks.set(commandDescriptor, callback);
|
||||||
sendToWorkadventure({
|
sendToWorkadventure({
|
||||||
'type': 'registerMenuCommand',
|
type: "registerMenuCommand",
|
||||||
'data': {
|
data: {
|
||||||
menutItem: commandDescriptor
|
menutItem: commandDescriptor,
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
displayBubble(): void {
|
displayBubble(): void {
|
||||||
sendToWorkadventure({ 'type': 'displayBubble', data: null });
|
sendToWorkadventure({ type: "displayBubble", data: null });
|
||||||
}
|
}
|
||||||
|
|
||||||
removeBubble(): void {
|
removeBubble(): void {
|
||||||
sendToWorkadventure({ 'type': 'removeBubble', data: null });
|
sendToWorkadventure({ type: "removeBubble", data: null });
|
||||||
}
|
}
|
||||||
|
|
||||||
triggerMessage(message: string, callback: () => void): TriggerMessage {
|
triggerMessage(message: string, callback: () => void): TriggerMessage {
|
||||||
return new TriggerMessage(message, callback);
|
return new TriggerMessage(message, callback);
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<script lang="typescript">
|
<script lang="typescript">
|
||||||
import {enableCameraSceneVisibilityStore, gameOverlayVisibilityStore} from "../Stores/MediaStore";
|
import {enableCameraSceneVisibilityStore} from "../Stores/MediaStore";
|
||||||
import CameraControls from "./CameraControls.svelte";
|
import CameraControls from "./CameraControls.svelte";
|
||||||
import MyCamera from "./MyCamera.svelte";
|
import MyCamera from "./MyCamera.svelte";
|
||||||
import SelectCompanionScene from "./SelectCompanion/SelectCompanionScene.svelte";
|
import SelectCompanionScene from "./SelectCompanion/SelectCompanionScene.svelte";
|
||||||
@ -21,10 +21,13 @@
|
|||||||
import AudioPlaying from "./UI/AudioPlaying.svelte";
|
import AudioPlaying from "./UI/AudioPlaying.svelte";
|
||||||
import {soundPlayingStore} from "../Stores/SoundPlayingStore";
|
import {soundPlayingStore} from "../Stores/SoundPlayingStore";
|
||||||
import ErrorDialog from "./UI/ErrorDialog.svelte";
|
import ErrorDialog from "./UI/ErrorDialog.svelte";
|
||||||
|
import VideoOverlay from "./Video/VideoOverlay.svelte";
|
||||||
|
import {gameOverlayVisibilityStore} from "../Stores/GameOverlayStoreVisibility";
|
||||||
import {consoleGlobalMessageManagerVisibleStore} from "../Stores/ConsoleGlobalMessageManagerStore";
|
import {consoleGlobalMessageManagerVisibleStore} from "../Stores/ConsoleGlobalMessageManagerStore";
|
||||||
import ConsoleGlobalMessageManager from "./ConsoleGlobalMessageManager/ConsoleGlobalMessageManager.svelte";
|
import ConsoleGlobalMessageManager from "./ConsoleGlobalMessageManager/ConsoleGlobalMessageManager.svelte";
|
||||||
|
|
||||||
export let game: Game;
|
export let game: Game;
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
@ -68,6 +71,7 @@
|
|||||||
-->
|
-->
|
||||||
{#if $gameOverlayVisibilityStore}
|
{#if $gameOverlayVisibilityStore}
|
||||||
<div>
|
<div>
|
||||||
|
<VideoOverlay></VideoOverlay>
|
||||||
<MyCamera></MyCamera>
|
<MyCamera></MyCamera>
|
||||||
<CameraControls></CameraControls>
|
<CameraControls></CameraControls>
|
||||||
</div>
|
</div>
|
||||||
|
@ -7,6 +7,11 @@
|
|||||||
import cinemaCloseImg from "./images/cinema-close.svg";
|
import cinemaCloseImg from "./images/cinema-close.svg";
|
||||||
import microphoneImg from "./images/microphone.svg";
|
import microphoneImg from "./images/microphone.svg";
|
||||||
import microphoneCloseImg from "./images/microphone-close.svg";
|
import microphoneCloseImg from "./images/microphone-close.svg";
|
||||||
|
import layoutPresentationImg from "./images/layout-presentation.svg";
|
||||||
|
import layoutChatImg from "./images/layout-chat.svg";
|
||||||
|
import {layoutModeStore} from "../Stores/StreamableCollectionStore";
|
||||||
|
import {LayoutMode} from "../WebRtc/LayoutManager";
|
||||||
|
import {peerStore} from "../Stores/PeerStore";
|
||||||
|
|
||||||
function screenSharingClick(): void {
|
function screenSharingClick(): void {
|
||||||
if ($requestedScreenSharingState === true) {
|
if ($requestedScreenSharingState === true) {
|
||||||
@ -32,10 +37,24 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function switchLayoutMode() {
|
||||||
|
if ($layoutModeStore === LayoutMode.Presentation) {
|
||||||
|
$layoutModeStore = LayoutMode.VideoChat;
|
||||||
|
} else {
|
||||||
|
$layoutModeStore = LayoutMode.Presentation;
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<div class="btn-cam-action">
|
<div class="btn-cam-action">
|
||||||
|
<div class="btn-layout" on:click={switchLayoutMode} class:hide={$peerStore.size === 0}>
|
||||||
|
{#if $layoutModeStore === LayoutMode.Presentation }
|
||||||
|
<img src={layoutPresentationImg} style="padding: 2px" alt="Switch to mosaic mode">
|
||||||
|
{:else}
|
||||||
|
<img src={layoutChatImg} style="padding: 2px" alt="Switch to presentation mode">
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
<div class="btn-monitor" on:click={screenSharingClick} class:hide={!$screenSharingAvailableStore} class:enabled={$requestedScreenSharingState}>
|
<div class="btn-monitor" on:click={screenSharingClick} class:hide={!$screenSharingAvailableStore} class:enabled={$requestedScreenSharingState}>
|
||||||
{#if $requestedScreenSharingState}
|
{#if $requestedScreenSharingState}
|
||||||
<img src={monitorImg} alt="Start screen sharing">
|
<img src={monitorImg} alt="Start screen sharing">
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
<script lang="typescript">
|
<script lang="typescript">
|
||||||
import type { Game } from "../../Phaser/Game/Game";
|
import type { Game } from "../../Phaser/Game/Game";
|
||||||
import {CustomizeScene, CustomizeSceneName} from "../../Phaser/Login/CustomizeScene";
|
import {CustomizeScene, CustomizeSceneName} from "../../Phaser/Login/CustomizeScene";
|
||||||
|
import {activeRowStore} from "../../Stores/CustomCharacterStore";
|
||||||
|
|
||||||
export let game: Game;
|
export let game: Game;
|
||||||
|
|
||||||
const customCharacterScene = game.scene.getScene(CustomizeSceneName) as CustomizeScene;
|
const customCharacterScene = game.scene.getScene(CustomizeSceneName) as CustomizeScene;
|
||||||
let activeRow = customCharacterScene.activeRow;
|
|
||||||
|
|
||||||
function selectLeft() {
|
function selectLeft() {
|
||||||
customCharacterScene.moveCursorHorizontally(-1);
|
customCharacterScene.moveCursorHorizontally(-1);
|
||||||
@ -17,12 +17,10 @@
|
|||||||
|
|
||||||
function selectUp() {
|
function selectUp() {
|
||||||
customCharacterScene.moveCursorVertically(-1);
|
customCharacterScene.moveCursorVertically(-1);
|
||||||
activeRow = customCharacterScene.activeRow;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function selectDown() {
|
function selectDown() {
|
||||||
customCharacterScene.moveCursorVertically(1);
|
customCharacterScene.moveCursorVertically(1);
|
||||||
activeRow = customCharacterScene.activeRow;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function previousScene() {
|
function previousScene() {
|
||||||
@ -44,16 +42,16 @@
|
|||||||
<button class="customCharacterSceneButton customCharacterSceneButtonRight nes-btn" on:click|preventDefault={ selectRight }> > </button>
|
<button class="customCharacterSceneButton customCharacterSceneButtonRight nes-btn" on:click|preventDefault={ selectRight }> > </button>
|
||||||
</section>
|
</section>
|
||||||
<section class="action">
|
<section class="action">
|
||||||
{#if activeRow === 0}
|
{#if $activeRowStore === 0}
|
||||||
<button type="submit" class="customCharacterSceneFormBack nes-btn" on:click|preventDefault={ previousScene }>Return</button>
|
<button type="submit" class="customCharacterSceneFormBack nes-btn" on:click|preventDefault={ previousScene }>Return</button>
|
||||||
{/if}
|
{/if}
|
||||||
{#if activeRow !== 0}
|
{#if $activeRowStore !== 0}
|
||||||
<button type="submit" class="customCharacterSceneFormBack nes-btn" on:click|preventDefault={ selectUp }>Back <img src="resources/objects/arrow_up_black.png" alt=""/></button>
|
<button type="submit" class="customCharacterSceneFormBack nes-btn" on:click|preventDefault={ selectUp }>Back <img src="resources/objects/arrow_up_black.png" alt=""/></button>
|
||||||
{/if}
|
{/if}
|
||||||
{#if activeRow === 5}
|
{#if $activeRowStore === 5}
|
||||||
<button type="submit" class="customCharacterSceneFormSubmit nes-btn is-primary" on:click|preventDefault={ finish }>Finish</button>
|
<button type="submit" class="customCharacterSceneFormSubmit nes-btn is-primary" on:click|preventDefault={ finish }>Finish</button>
|
||||||
{/if}
|
{/if}
|
||||||
{#if activeRow !== 5}
|
{#if $activeRowStore !== 5}
|
||||||
<button type="submit" class="customCharacterSceneFormSubmit nes-btn is-primary" on:click|preventDefault={ selectDown }>Next <img src="resources/objects/arrow_down.png" alt=""/></button>
|
<button type="submit" class="customCharacterSceneFormSubmit nes-btn is-primary" on:click|preventDefault={ selectDown }>Next <img src="resources/objects/arrow_down.png" alt=""/></button>
|
||||||
{/if}
|
{/if}
|
||||||
</section>
|
</section>
|
||||||
|
@ -58,7 +58,7 @@
|
|||||||
|
|
||||||
|
|
||||||
<div class="horizontal-sound-meter" class:active={display}>
|
<div class="horizontal-sound-meter" class:active={display}>
|
||||||
{#each [...Array(NB_BARS).keys()] as i}
|
{#each [...Array(NB_BARS).keys()] as i (i)}
|
||||||
<div style={color(i, volume)}></div>
|
<div style={color(i, volume)}></div>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
|
@ -6,8 +6,6 @@
|
|||||||
export let stream: MediaStream|null;
|
export let stream: MediaStream|null;
|
||||||
let volume = 0;
|
let volume = 0;
|
||||||
|
|
||||||
const NB_BARS = 5;
|
|
||||||
|
|
||||||
let timeout: ReturnType<typeof setTimeout>;
|
let timeout: ReturnType<typeof setTimeout>;
|
||||||
const soundMeter = new SoundMeter();
|
const soundMeter = new SoundMeter();
|
||||||
let display = false;
|
let display = false;
|
||||||
@ -23,7 +21,7 @@
|
|||||||
|
|
||||||
timeout = setInterval(() => {
|
timeout = setInterval(() => {
|
||||||
try{
|
try{
|
||||||
volume = parseInt((soundMeter.getVolume() / 100 * NB_BARS).toFixed(0));
|
volume = soundMeter.getVolume();
|
||||||
//console.log(volume);
|
//console.log(volume);
|
||||||
}catch(err){
|
}catch(err){
|
||||||
|
|
||||||
@ -45,9 +43,9 @@
|
|||||||
|
|
||||||
|
|
||||||
<div class="sound-progress" class:active={display}>
|
<div class="sound-progress" class:active={display}>
|
||||||
<span class:active={volume > 1}></span>
|
|
||||||
<span class:active={volume > 2}></span>
|
|
||||||
<span class:active={volume > 3}></span>
|
|
||||||
<span class:active={volume > 4}></span>
|
|
||||||
<span class:active={volume > 5}></span>
|
<span class:active={volume > 5}></span>
|
||||||
|
<span class:active={volume > 10}></span>
|
||||||
|
<span class:active={volume > 15}></span>
|
||||||
|
<span class:active={volume > 40}></span>
|
||||||
|
<span class:active={volume > 70}></span>
|
||||||
</div>
|
</div>
|
||||||
|