Merge branch 'develop' of github.com:thecodingmachine/workadventure into develop
This commit is contained in:
commit
73ef14bfc0
16
.github/workflows/build-and-release-desktop.yml
vendored
16
.github/workflows/build-and-release-desktop.yml
vendored
@ -60,6 +60,10 @@ jobs:
|
|||||||
run: yarn build
|
run: yarn build
|
||||||
working-directory: "desktop/local-app"
|
working-directory: "desktop/local-app"
|
||||||
|
|
||||||
|
- name: "Set desktop app version"
|
||||||
|
run: node helpers/set-version.js
|
||||||
|
working-directory: "desktop/electron"
|
||||||
|
|
||||||
- name: "Install dependencies"
|
- name: "Install dependencies"
|
||||||
run: yarn install --froze-lockfile
|
run: yarn install --froze-lockfile
|
||||||
working-directory: "desktop/electron"
|
working-directory: "desktop/electron"
|
||||||
@ -68,15 +72,19 @@ jobs:
|
|||||||
run: yarn build
|
run: yarn build
|
||||||
working-directory: "desktop/electron"
|
working-directory: "desktop/electron"
|
||||||
|
|
||||||
- name: "Build app"
|
- name: "Install electron tools"
|
||||||
run: yarn bundle --publish never
|
run: yarn electron-builder install-app-deps
|
||||||
|
working-directory: "desktop/electron"
|
||||||
|
|
||||||
|
- name: "Build app for testing"
|
||||||
|
run: yarn electron-builder --publish never
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
working-directory: "desktop/electron"
|
working-directory: "desktop/electron"
|
||||||
if: ${{ github.event_name != 'release' }}
|
if: ${{ github.event_name != 'release' }}
|
||||||
|
|
||||||
- name: "Build & publish App"
|
- name: "Build & release app"
|
||||||
run: yarn release
|
run: yarn electron-builder --publish always
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
working-directory: "desktop/electron"
|
working-directory: "desktop/electron"
|
||||||
|
@ -27,8 +27,8 @@
|
|||||||
"no-unused-vars": "off",
|
"no-unused-vars": "off",
|
||||||
"@typescript-eslint/no-explicit-any": "error",
|
"@typescript-eslint/no-explicit-any": "error",
|
||||||
"@typescript-eslint/no-unused-vars": [
|
"@typescript-eslint/no-unused-vars": [
|
||||||
"error"
|
"error", { "args": "none", "caughtErrors": "all", "varsIgnorePattern": "_exhaustiveCheck" }
|
||||||
],
|
],
|
||||||
"no-throw-literal": "error"
|
"no-throw-literal": "error"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -559,11 +559,7 @@ export class GameRoom {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
mapUrl,
|
mapUrl,
|
||||||
policy_type: 1,
|
|
||||||
tags: [],
|
|
||||||
authenticationMandatory: null,
|
authenticationMandatory: null,
|
||||||
roomSlug: null,
|
|
||||||
contactPage: null,
|
|
||||||
group: null,
|
group: null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -71,7 +71,6 @@ export class Zone {
|
|||||||
/**
|
/**
|
||||||
* Notify listeners of this zone that this user entered
|
* Notify listeners of this zone that this user entered
|
||||||
*/
|
*/
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
||||||
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);
|
||||||
|
@ -4,12 +4,10 @@ import { VariablesRepositoryInterface } from "./VariablesRepositoryInterface";
|
|||||||
* Mock class in charge of NOT saving/loading variables from the data store
|
* Mock class in charge of NOT saving/loading variables from the data store
|
||||||
*/
|
*/
|
||||||
export class VoidVariablesRepository implements VariablesRepositoryInterface {
|
export class VoidVariablesRepository implements VariablesRepositoryInterface {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
||||||
loadVariables(roomUrl: string): Promise<{ [key: string]: string }> {
|
loadVariables(roomUrl: string): Promise<{ [key: string]: string }> {
|
||||||
return Promise.resolve({});
|
return Promise.resolve({});
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
||||||
saveVariable(roomUrl: string, key: string, value: string): Promise<number> {
|
saveVariable(roomUrl: string, key: string, value: string): Promise<number> {
|
||||||
return Promise.resolve(0);
|
return Promise.resolve(0);
|
||||||
}
|
}
|
||||||
|
@ -30,7 +30,7 @@ ADMIN_API_URL=
|
|||||||
DATA_DIR=./wa
|
DATA_DIR=./wa
|
||||||
|
|
||||||
# The URL used by default, in the form: "/_/global/map/url.json"
|
# The URL used by default, in the form: "/_/global/map/url.json"
|
||||||
START_ROOM_URL=/_/global/maps.workadventu.re/Floor0/floor0.json
|
START_ROOM_URL=/_/global/thecodingmachine.github.io/workadventure-map-starter-kit/map.json
|
||||||
|
|
||||||
# If you want to have a contact page in your menu,
|
# If you want to have a contact page in your menu,
|
||||||
# you MUST set CONTACT_URL to the URL of the page that you want
|
# you MUST set CONTACT_URL to the URL of the page that you want
|
||||||
|
34
contrib/docker/README.md
Normal file
34
contrib/docker/README.md
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
# Deploying WorkAdventure in production
|
||||||
|
|
||||||
|
This directory contains a sample production deployment of WorkAdventure using docker-compose.
|
||||||
|
|
||||||
|
Every production environment is different and this docker-compose file will not
|
||||||
|
fit all use cases. But it is intended to be a good starting point for you
|
||||||
|
to build your own deployment.
|
||||||
|
|
||||||
|
In this docker-compose file, you will find:
|
||||||
|
|
||||||
|
- A reverse-proxy (Traefik) that dispatches requests to the WorkAdventure containers and handles HTTPS certificates using LetsEncrypt
|
||||||
|
- A front container (nginx) that servers static files (HTML/JS/CSS)
|
||||||
|
- A pusher container (NodeJS) that is the point of entry for users (you can start many if you want to increase performance)
|
||||||
|
- A back container (NodeJS) that shares your rooms information
|
||||||
|
- An icon container to fetch the favicon of sites imported in iframes
|
||||||
|
- A Redis server to store values from variables originating from the Scripting API
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
graph LR
|
||||||
|
A[Browser] --> B(Traefik)
|
||||||
|
subgraph docker-compose
|
||||||
|
B --> C(Front)
|
||||||
|
B --> D(Pusher)
|
||||||
|
B --> E(Icon)
|
||||||
|
D --> F(Back)
|
||||||
|
F --> G(Redis)
|
||||||
|
end
|
||||||
|
A .-> H(Map)
|
||||||
|
F .-> H
|
||||||
|
```
|
||||||
|
|
||||||
|
**Important**: the default docker-compose file does **not** contain a container dedicated to hosting maps. The documentation and
|
||||||
|
tutorials are relying on GitHub Pages to host the maps. If you want to self-host your maps, you will need to add a simple
|
||||||
|
HTTP server (nginx / Apache, ...) and properly configure the [CORS settings as explained in the documentation](../../docs/maps/hosting.md).
|
@ -95,6 +95,7 @@ services:
|
|||||||
- JITSI_ISS
|
- JITSI_ISS
|
||||||
- MAX_PER_GROUP
|
- MAX_PER_GROUP
|
||||||
- STORE_VARIABLES_FOR_LOCAL_MAPS
|
- STORE_VARIABLES_FOR_LOCAL_MAPS
|
||||||
|
- REDIS_HOST=redis
|
||||||
labels:
|
labels:
|
||||||
- "traefik.http.routers.back.rule=Host(`${BACK_HOST}`)"
|
- "traefik.http.routers.back.rule=Host(`${BACK_HOST}`)"
|
||||||
- "traefik.http.routers.back.entryPoints=web"
|
- "traefik.http.routers.back.entryPoints=web"
|
||||||
@ -117,3 +118,11 @@ services:
|
|||||||
- "traefik.http.routers.icon-ssl.service=icon"
|
- "traefik.http.routers.icon-ssl.service=icon"
|
||||||
- "traefik.http.routers.icon-ssl.tls=true"
|
- "traefik.http.routers.icon-ssl.tls=true"
|
||||||
- "traefik.http.routers.icon-ssl.tls.certresolver=myresolver"
|
- "traefik.http.routers.icon-ssl.tls.certresolver=myresolver"
|
||||||
|
|
||||||
|
redis:
|
||||||
|
image: redis:6
|
||||||
|
volumes:
|
||||||
|
- redisdata:/data
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
redisdata:
|
||||||
|
@ -28,5 +28,4 @@ publish:
|
|||||||
provider: github
|
provider: github
|
||||||
owner: thecodingmachine
|
owner: thecodingmachine
|
||||||
repo: workadventure
|
repo: workadventure
|
||||||
vPrefixedTagName: false
|
releaseType: release
|
||||||
releaseType: draft
|
|
||||||
|
18
desktop/electron/helpers/set-version.js
Normal file
18
desktop/electron/helpers/set-version.js
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
const path = require('path');
|
||||||
|
const fs = require('fs');
|
||||||
|
|
||||||
|
let version = '0.0.0';
|
||||||
|
|
||||||
|
if (process.env.GITHUB_REF.startsWith('refs/tags/v')) {
|
||||||
|
version = process.env.GITHUB_REF.replace('refs/tags/v', '');
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Version:', version);
|
||||||
|
|
||||||
|
const packageJsonPath = path.resolve(__dirname, '..', 'package.json');
|
||||||
|
|
||||||
|
let data = fs.readFileSync(packageJsonPath, 'utf8');
|
||||||
|
|
||||||
|
data = data.replace('managedbyci', version);
|
||||||
|
|
||||||
|
fs.writeFileSync(packageJsonPath, data);
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "workadventure-desktop",
|
"name": "workadventure-desktop",
|
||||||
"version": "1.0.0",
|
"version": "managedbyci",
|
||||||
"description": "Desktop application for WorkAdventure",
|
"description": "Desktop application for WorkAdventure",
|
||||||
"author": "thecodingmachine",
|
"author": "thecodingmachine",
|
||||||
"main": "dist/main.js",
|
"main": "dist/main.js",
|
||||||
@ -11,7 +11,6 @@
|
|||||||
"dev": "yarn build --watch --onSuccess 'yarn electron dist/main.js'",
|
"dev": "yarn build --watch --onSuccess 'yarn electron dist/main.js'",
|
||||||
"dev:local-app": "cd ../local-app && yarn && yarn dev",
|
"dev:local-app": "cd ../local-app && yarn && yarn dev",
|
||||||
"bundle": "yarn build:local-app && yarn build && electron-builder install-app-deps && electron-builder",
|
"bundle": "yarn build:local-app && yarn build && electron-builder install-app-deps && electron-builder",
|
||||||
"release": "yarn bundle",
|
|
||||||
"typecheck": "tsc --noEmit",
|
"typecheck": "tsc --noEmit",
|
||||||
"test": "exit 0",
|
"test": "exit 0",
|
||||||
"lint": "yarn eslint src/ . --ext .ts",
|
"lint": "yarn eslint src/ . --ext .ts",
|
||||||
@ -32,13 +31,13 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/auto-launch": "^5.0.2",
|
"@types/auto-launch": "^5.0.2",
|
||||||
"@typescript-eslint/eslint-plugin": "^2.26.0",
|
"@typescript-eslint/eslint-plugin": "^5.18.0",
|
||||||
"@typescript-eslint/parser": "^2.26.0",
|
"@typescript-eslint/parser": "^5.18.0",
|
||||||
"electron": "^17.0.1",
|
"electron": "^18.0.3",
|
||||||
"electron-builder": "^22.14.13",
|
"electron-builder": "^22.14.13",
|
||||||
"eslint": "^6.8.0",
|
"eslint": "^8.12.0",
|
||||||
"prettier": "^2.5.1",
|
"prettier": "^2.6.2",
|
||||||
"tsup": "^5.11.13",
|
"tsup": "^5.12.4",
|
||||||
"typescript": "^3.8.3"
|
"typescript": "^4.6.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,7 @@ import { setLogLevel } from "./log";
|
|||||||
import "./serve"; // prepare custom url scheme
|
import "./serve"; // prepare custom url scheme
|
||||||
import { loadShortcuts } from "./shortcuts";
|
import { loadShortcuts } from "./shortcuts";
|
||||||
|
|
||||||
function init() {
|
async function init() {
|
||||||
const appLock = app.requestSingleInstanceLock();
|
const appLock = app.requestSingleInstanceLock();
|
||||||
|
|
||||||
if (!appLock) {
|
if (!appLock) {
|
||||||
@ -21,7 +21,7 @@ function init() {
|
|||||||
|
|
||||||
app.on("second-instance", () => {
|
app.on("second-instance", () => {
|
||||||
// re-create window if closed
|
// re-create window if closed
|
||||||
createWindow();
|
void createWindow();
|
||||||
|
|
||||||
const mainWindow = getWindow();
|
const mainWindow = getWindow();
|
||||||
|
|
||||||
@ -36,15 +36,15 @@ function init() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// This method will be called when Electron has finished loading
|
// This method will be called when Electron has finished loading
|
||||||
app.whenReady().then(async () => {
|
await app.whenReady().then(async () => {
|
||||||
await settings.init();
|
await settings.init();
|
||||||
|
|
||||||
setLogLevel(settings.get("log_level") || "info");
|
setLogLevel(settings.get("log_level") || "info");
|
||||||
|
|
||||||
autoUpdater.init();
|
await autoUpdater.init();
|
||||||
|
|
||||||
// enable auto launch
|
// enable auto launch
|
||||||
updateAutoLaunch();
|
await updateAutoLaunch();
|
||||||
|
|
||||||
// load ipc handler
|
// load ipc handler
|
||||||
ipc();
|
ipc();
|
||||||
@ -72,7 +72,7 @@ function init() {
|
|||||||
// On macOS it's common to re-create a window in the app when the
|
// On macOS it's common to re-create a window in the app when the
|
||||||
// dock icon is clicked and there are no other windows open.
|
// dock icon is clicked and there are no other windows open.
|
||||||
if (BrowserWindow.getAllWindows().length === 0) {
|
if (BrowserWindow.getAllWindows().length === 0) {
|
||||||
createWindow();
|
void createWindow();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -38,32 +38,35 @@ export async function manualRequestUpdateCheck() {
|
|||||||
isManualRequestedUpdate = false;
|
isManualRequestedUpdate = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
function init() {
|
async function init() {
|
||||||
autoUpdater.logger = log;
|
autoUpdater.logger = log;
|
||||||
|
|
||||||
autoUpdater.on("update-downloaded", ({ releaseNotes, releaseName }) => {
|
autoUpdater.on(
|
||||||
(async () => {
|
"update-downloaded",
|
||||||
const dialogOpts = {
|
({ releaseNotes, releaseName }: { releaseNotes: string; releaseName: string }) => {
|
||||||
type: "question",
|
void (async () => {
|
||||||
buttons: ["Install and Restart", "Install Later"],
|
const dialogOpts = {
|
||||||
defaultId: 0,
|
type: "question",
|
||||||
title: "WorkAdventure - Update",
|
buttons: ["Install and Restart", "Install Later"],
|
||||||
message: process.platform === "win32" ? releaseNotes : releaseName,
|
defaultId: 0,
|
||||||
detail: "A new version has been downloaded. Restart the application to apply the updates.",
|
title: "WorkAdventure - Update",
|
||||||
};
|
message: process.platform === "win32" ? releaseNotes : releaseName,
|
||||||
|
detail: "A new version has been downloaded. Restart the application to apply the updates.",
|
||||||
|
};
|
||||||
|
|
||||||
const { response } = await dialog.showMessageBox(dialogOpts);
|
const { response } = await dialog.showMessageBox(dialogOpts);
|
||||||
if (response === 0) {
|
if (response === 0) {
|
||||||
await sleep(1000);
|
await sleep(1000);
|
||||||
|
|
||||||
autoUpdater.quitAndInstall();
|
autoUpdater.quitAndInstall();
|
||||||
|
|
||||||
// Force app to quit. This is just a workaround, ideally autoUpdater.quitAndInstall() should relaunch the app.
|
// Force app to quit. This is just a workaround, ideally autoUpdater.quitAndInstall() should relaunch the app.
|
||||||
// app.confirmedExitPrompt = true;
|
// app.confirmedExitPrompt = true;
|
||||||
app.quit();
|
app.quit();
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
});
|
}
|
||||||
|
);
|
||||||
|
|
||||||
if (process.platform === "linux" && !process.env.APPIMAGE) {
|
if (process.platform === "linux" && !process.env.APPIMAGE) {
|
||||||
autoUpdater.autoDownload = false;
|
autoUpdater.autoDownload = false;
|
||||||
@ -85,7 +88,7 @@ function init() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
checkForUpdates();
|
await checkForUpdates();
|
||||||
|
|
||||||
// run update check every hour again
|
// run update check every hour again
|
||||||
setInterval(() => checkForUpdates, 1000 * 60 * 1);
|
setInterval(() => checkForUpdates, 1000 * 60 * 1);
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { ipcMain, app } from "electron";
|
import { ipcMain, app, desktopCapturer } from "electron";
|
||||||
import electronIsDev from "electron-is-dev";
|
import electronIsDev from "electron-is-dev";
|
||||||
import { createAndShowNotification } from "./notification";
|
import { createAndShowNotification } from "./notification";
|
||||||
import { Server } from "./preload-local-app/types";
|
import { Server } from "./preload-local-app/types";
|
||||||
@ -30,10 +30,18 @@ export default () => {
|
|||||||
ipcMain.handle("get-version", () => (electronIsDev ? "dev" : app.getVersion()));
|
ipcMain.handle("get-version", () => (electronIsDev ? "dev" : app.getVersion()));
|
||||||
|
|
||||||
// app ipc
|
// app ipc
|
||||||
ipcMain.on("app:notify", (event, txt) => {
|
ipcMain.on("app:notify", (event, txt: string) => {
|
||||||
createAndShowNotification({ body: txt });
|
createAndShowNotification({ body: txt });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
ipcMain.handle("app:getDesktopCapturerSources", async (event, options: Electron.SourcesOptions) => {
|
||||||
|
return (await desktopCapturer.getSources(options)).map((source) => ({
|
||||||
|
id: source.id,
|
||||||
|
name: source.name,
|
||||||
|
thumbnailURL: source.thumbnail.toDataURL(),
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
// local-app ipc
|
// local-app ipc
|
||||||
ipcMain.handle("local-app:showLocalApp", () => {
|
ipcMain.handle("local-app:showLocalApp", () => {
|
||||||
hideAppView();
|
hideAppView();
|
||||||
@ -43,7 +51,7 @@ export default () => {
|
|||||||
return settings.get("servers");
|
return settings.get("servers");
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcMain.handle("local-app:selectServer", (event, serverId: string) => {
|
ipcMain.handle("local-app:selectServer", async (event, serverId: string) => {
|
||||||
const servers = settings.get("servers") || [];
|
const servers = settings.get("servers") || [];
|
||||||
const selectedServer = servers.find((s) => s._id === serverId);
|
const selectedServer = servers.find((s) => s._id === serverId);
|
||||||
|
|
||||||
@ -51,7 +59,7 @@ export default () => {
|
|||||||
return new Error("Server not found");
|
return new Error("Server not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
showAppView(selectedServer.url);
|
await showAppView(selectedServer.url);
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -15,6 +15,7 @@ function onError(e: Error) {
|
|||||||
function onRejection(reason: Error) {
|
function onRejection(reason: Error) {
|
||||||
if (reason instanceof Error) {
|
if (reason instanceof Error) {
|
||||||
let _reason = reason;
|
let _reason = reason;
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||||
const errPrototype = Object.getPrototypeOf(reason);
|
const errPrototype = Object.getPrototypeOf(reason);
|
||||||
const nameProperty = Object.getOwnPropertyDescriptor(errPrototype, "name");
|
const nameProperty = Object.getOwnPropertyDescriptor(errPrototype, "name");
|
||||||
|
|
||||||
|
@ -2,4 +2,4 @@ import app from "./app";
|
|||||||
import log from "./log";
|
import log from "./log";
|
||||||
|
|
||||||
log.init();
|
log.init();
|
||||||
app.init();
|
void app.init();
|
||||||
|
@ -8,6 +8,7 @@ const api: WorkAdventureDesktopApi = {
|
|||||||
notify: (txt) => ipcRenderer.send("app:notify", txt),
|
notify: (txt) => ipcRenderer.send("app:notify", txt),
|
||||||
onMuteToggle: (callback) => ipcRenderer.on("app:on-mute-toggle", callback),
|
onMuteToggle: (callback) => ipcRenderer.on("app:on-mute-toggle", callback),
|
||||||
onCameraToggle: (callback) => ipcRenderer.on("app:on-camera-toggle", callback),
|
onCameraToggle: (callback) => ipcRenderer.on("app:on-camera-toggle", callback),
|
||||||
|
getDesktopCapturerSources: (options) => ipcRenderer.invoke("app:getDesktopCapturerSources", options),
|
||||||
};
|
};
|
||||||
|
|
||||||
contextBridge.exposeInMainWorld("WAD", api);
|
contextBridge.exposeInMainWorld("WAD", api);
|
||||||
|
@ -1,3 +1,15 @@
|
|||||||
|
// copy of Electron.SourcesOptions to avoid Electron dependency in front
|
||||||
|
export interface SourcesOptions {
|
||||||
|
types: string[];
|
||||||
|
thumbnailSize?: { height: number; width: number };
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DesktopCapturerSource {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
thumbnailURL: string;
|
||||||
|
}
|
||||||
|
|
||||||
export type WorkAdventureDesktopApi = {
|
export type WorkAdventureDesktopApi = {
|
||||||
desktop: boolean;
|
desktop: boolean;
|
||||||
isDevelopment: () => Promise<boolean>;
|
isDevelopment: () => Promise<boolean>;
|
||||||
@ -5,4 +17,5 @@ export type WorkAdventureDesktopApi = {
|
|||||||
notify: (txt: string) => void;
|
notify: (txt: string) => void;
|
||||||
onMuteToggle: (callback: () => void) => void;
|
onMuteToggle: (callback: () => void) => void;
|
||||||
onCameraToggle: (callback: () => void) => void;
|
onCameraToggle: (callback: () => void) => void;
|
||||||
|
getDesktopCapturerSources: (options: SourcesOptions) => Promise<DesktopCapturerSource[]>;
|
||||||
};
|
};
|
||||||
|
@ -36,14 +36,14 @@ export function createTray() {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Check for updates",
|
label: "Check for updates",
|
||||||
async click() {
|
click() {
|
||||||
await autoUpdater.manualRequestUpdateCheck();
|
void autoUpdater.manualRequestUpdateCheck();
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Open Logs",
|
label: "Open Logs",
|
||||||
click() {
|
click() {
|
||||||
log.openLog();
|
void log.openLog();
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -115,7 +115,7 @@ export async function createWindow() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function showAppView(url?: string) {
|
export async function showAppView(url?: string) {
|
||||||
if (!appView) {
|
if (!appView) {
|
||||||
throw new Error("App view not found");
|
throw new Error("App view not found");
|
||||||
}
|
}
|
||||||
@ -130,7 +130,7 @@ export function showAppView(url?: string) {
|
|||||||
mainWindow.addBrowserView(appView);
|
mainWindow.addBrowserView(appView);
|
||||||
|
|
||||||
if (url && url !== appViewUrl) {
|
if (url && url !== appViewUrl) {
|
||||||
appView.webContents.loadURL(url);
|
await appView.webContents.loadURL(url);
|
||||||
appViewUrl = url;
|
appViewUrl = url;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -36,7 +36,7 @@ module.exports = {
|
|||||||
"eol-last": ["error", "always"],
|
"eol-last": ["error", "always"],
|
||||||
"@typescript-eslint/no-explicit-any": "error",
|
"@typescript-eslint/no-explicit-any": "error",
|
||||||
"no-throw-literal": "error",
|
"no-throw-literal": "error",
|
||||||
"@typescript-eslint/no-unused-vars": ["error"],
|
"@typescript-eslint/no-unused-vars": ["error", { "args": "none", "caughtErrors": "all", "varsIgnorePattern": "_exhaustiveCheck" }],
|
||||||
// TODO: remove those ignored rules and write a stronger code!
|
// TODO: remove those ignored rules and write a stronger code!
|
||||||
"@typescript-eslint/no-unsafe-call": "off",
|
"@typescript-eslint/no-unsafe-call": "off",
|
||||||
"@typescript-eslint/restrict-plus-operands": "off",
|
"@typescript-eslint/restrict-plus-operands": "off",
|
||||||
|
@ -42,8 +42,8 @@
|
|||||||
"buffer": "^6.0.3",
|
"buffer": "^6.0.3",
|
||||||
"cancelable-promise": "^4.2.1",
|
"cancelable-promise": "^4.2.1",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
"deep-copy-ts": "^0.5.0",
|
|
||||||
"dompurify" : "^2.3.6",
|
"dompurify" : "^2.3.6",
|
||||||
|
"deep-copy-ts": "^0.5.4",
|
||||||
"easystarjs": "^0.4.4",
|
"easystarjs": "^0.4.4",
|
||||||
"fast-deep-equal": "^3.1.3",
|
"fast-deep-equal": "^3.1.3",
|
||||||
"google-protobuf": "^3.13.0",
|
"google-protobuf": "^3.13.0",
|
||||||
|
@ -297,7 +297,6 @@ class IframeListener {
|
|||||||
handleMenuUnregisterEvent(iframeEvent.data.name);
|
handleMenuUnregisterEvent(iframeEvent.data.name);
|
||||||
} else {
|
} else {
|
||||||
// Keep the line below. It will throw an error if we forget to handle one of the possible values.
|
// Keep the line below. It will throw an error if we forget to handle one of the possible values.
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
||||||
const _exhaustiveCheck: never = iframeEvent;
|
const _exhaustiveCheck: never = iframeEvent;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@ import { WorkAdventureDesktopApi } from "@wa-preload-app";
|
|||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface Window {
|
interface Window {
|
||||||
WAD: WorkAdventureDesktopApi;
|
WAD?: WorkAdventureDesktopApi;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -88,7 +88,6 @@ export function createState(target: "global" | "player"): WorkadventureStateComm
|
|||||||
}
|
}
|
||||||
return target.loadVariable(p.toString());
|
return target.loadVariable(p.toString());
|
||||||
},
|
},
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
||||||
set(target: WorkadventureStateCommands, p: PropertyKey, value: unknown, receiver: unknown): boolean {
|
set(target: WorkadventureStateCommands, p: PropertyKey, value: unknown, receiver: unknown): boolean {
|
||||||
// Note: when using "set", there is no way to wait, so we ignore the return of the promise.
|
// Note: when using "set", there is no way to wait, so we ignore the return of the promise.
|
||||||
// User must use WA.state.saveVariable to have error message.
|
// User must use WA.state.saveVariable to have error message.
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
import type { Game } from "../Phaser/Game/Game";
|
import type { Game } from "../Phaser/Game/Game";
|
||||||
import { chatVisibilityStore } from "../Stores/ChatStore";
|
import { chatVisibilityStore } from "../Stores/ChatStore";
|
||||||
import { errorStore } from "../Stores/ErrorStore";
|
import { errorStore } from "../Stores/ErrorStore";
|
||||||
|
import { errorScreenStore } from "../Stores/ErrorScreenStore";
|
||||||
import { loginSceneVisibleStore } from "../Stores/LoginSceneStore";
|
import { loginSceneVisibleStore } from "../Stores/LoginSceneStore";
|
||||||
import { enableCameraSceneVisibilityStore } from "../Stores/MediaStore";
|
import { enableCameraSceneVisibilityStore } from "../Stores/MediaStore";
|
||||||
import { selectCharacterSceneVisibleStore } from "../Stores/SelectCharacterStore";
|
import { selectCharacterSceneVisibleStore } from "../Stores/SelectCharacterStore";
|
||||||
@ -13,11 +14,16 @@
|
|||||||
import SelectCharacterScene from "./selectCharacter/SelectCharacterScene.svelte";
|
import SelectCharacterScene from "./selectCharacter/SelectCharacterScene.svelte";
|
||||||
import SelectCompanionScene from "./SelectCompanion/SelectCompanionScene.svelte";
|
import SelectCompanionScene from "./SelectCompanion/SelectCompanionScene.svelte";
|
||||||
import ErrorDialog from "./UI/ErrorDialog.svelte";
|
import ErrorDialog from "./UI/ErrorDialog.svelte";
|
||||||
|
import ErrorScreen from "./UI/ErrorScreen.svelte";
|
||||||
|
|
||||||
export let game: Game;
|
export let game: Game;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if $errorStore.length > 0}
|
{#if $errorScreenStore !== undefined}
|
||||||
|
<div>
|
||||||
|
<ErrorScreen />
|
||||||
|
</div>
|
||||||
|
{:else if $errorStore.length > 0}
|
||||||
<div>
|
<div>
|
||||||
<ErrorDialog />
|
<ErrorDialog />
|
||||||
</div>
|
</div>
|
||||||
|
@ -38,6 +38,7 @@
|
|||||||
import { actionsMenuStore } from "../Stores/ActionsMenuStore";
|
import { actionsMenuStore } from "../Stores/ActionsMenuStore";
|
||||||
import ActionsMenu from "./ActionsMenu/ActionsMenu.svelte";
|
import ActionsMenu from "./ActionsMenu/ActionsMenu.svelte";
|
||||||
import Lazy from "./Lazy.svelte";
|
import Lazy from "./Lazy.svelte";
|
||||||
|
import { showDesktopCapturerSourcePicker } from "../Stores/ScreenSharingStore";
|
||||||
|
|
||||||
let mainLayout: HTMLDivElement;
|
let mainLayout: HTMLDivElement;
|
||||||
|
|
||||||
@ -119,6 +120,11 @@
|
|||||||
|
|
||||||
<Lazy when={$emoteMenuStore} component={() => import("./EmoteMenu/EmoteMenu.svelte")} />
|
<Lazy when={$emoteMenuStore} component={() => import("./EmoteMenu/EmoteMenu.svelte")} />
|
||||||
|
|
||||||
|
<Lazy
|
||||||
|
when={$showDesktopCapturerSourcePicker}
|
||||||
|
component={() => import("./Video/DesktopCapturerSourcePicker.svelte")}
|
||||||
|
/>
|
||||||
|
|
||||||
{#if hasEmbedScreen}
|
{#if hasEmbedScreen}
|
||||||
<EmbedScreensContainer />
|
<EmbedScreensContainer />
|
||||||
{/if}
|
{/if}
|
||||||
|
144
front/src/Components/UI/ErrorScreen.svelte
Normal file
144
front/src/Components/UI/ErrorScreen.svelte
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { fly } from "svelte/transition";
|
||||||
|
import { errorScreenStore } from "../../Stores/ErrorScreenStore";
|
||||||
|
import { gameManager } from "../../Phaser/Game/GameManager";
|
||||||
|
import { get } from "svelte/store";
|
||||||
|
import { onDestroy } from "svelte";
|
||||||
|
|
||||||
|
import logoImg from "../images/logo-min-white.png";
|
||||||
|
let logo = gameManager?.currentStartedRoom?.loginSceneLogo ?? logoImg;
|
||||||
|
import reload from "../images/reload.png";
|
||||||
|
let errorScreen = get(errorScreenStore);
|
||||||
|
|
||||||
|
function click() {
|
||||||
|
window.location.reload();
|
||||||
|
}
|
||||||
|
let details = errorScreen.details;
|
||||||
|
let timeVar = errorScreen.timeToRetry ?? 0;
|
||||||
|
|
||||||
|
if (errorScreen.type === "retry") {
|
||||||
|
let interval = setInterval(() => {
|
||||||
|
if (timeVar <= 1000) click();
|
||||||
|
timeVar -= 1000;
|
||||||
|
}, 1000);
|
||||||
|
onDestroy(() => clearInterval(interval));
|
||||||
|
}
|
||||||
|
|
||||||
|
$: detailsStylized = (details ?? "").replace("{time}", `${timeVar / 1000}`);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<main class="errorScreen" transition:fly={{ y: -200, duration: 500 }}>
|
||||||
|
<div style="width: 90%;">
|
||||||
|
<img src={logo} alt="WorkAdventure" class="logo" />
|
||||||
|
<div><img src={$errorScreenStore.image} alt="" class="icon" /></div>
|
||||||
|
{#if $errorScreenStore.type !== "retry"}<h2>{$errorScreenStore.title}</h2>{/if}
|
||||||
|
<p>{$errorScreenStore.subtitle}</p>
|
||||||
|
{#if $errorScreenStore.type !== "retry"}<p class="code">Code : {$errorScreenStore.code}</p>{/if}
|
||||||
|
<p class="details">
|
||||||
|
{detailsStylized}{#if $errorScreenStore.type === "retry"}<div class="loading" />{/if}
|
||||||
|
</p>
|
||||||
|
{#if $errorScreenStore.type === "retry" && $errorScreenStore.canRetryManual}
|
||||||
|
<button type="button" class="nes-btn is-primary button" on:click={click}>
|
||||||
|
<img src={reload} alt="" class="reload" />
|
||||||
|
{$errorScreenStore.buttonTitle}
|
||||||
|
</button>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
main.errorScreen {
|
||||||
|
pointer-events: auto;
|
||||||
|
width: 100%;
|
||||||
|
background-color: #000000;
|
||||||
|
color: #ffffff;
|
||||||
|
text-align: center;
|
||||||
|
position: absolute;
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
height: 100%;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
min-width: 300px;
|
||||||
|
z-index: 700;
|
||||||
|
overflow-y: scroll;
|
||||||
|
padding: 20px 0;
|
||||||
|
.logo {
|
||||||
|
width: 50%;
|
||||||
|
margin-bottom: 50px;
|
||||||
|
max-height: 25vh;
|
||||||
|
max-width: 50vw;
|
||||||
|
}
|
||||||
|
.icon {
|
||||||
|
height: 125px;
|
||||||
|
margin-bottom: 25px;
|
||||||
|
max-height: 25vh;
|
||||||
|
max-width: 50vw;
|
||||||
|
}
|
||||||
|
h2 {
|
||||||
|
font-family: "Press Start 2P";
|
||||||
|
padding: 5px;
|
||||||
|
font-size: 30px;
|
||||||
|
}
|
||||||
|
p {
|
||||||
|
font-family: "Press Start 2P";
|
||||||
|
}
|
||||||
|
p.code {
|
||||||
|
font-size: 12px;
|
||||||
|
opacity: 0.6;
|
||||||
|
user-select: text;
|
||||||
|
}
|
||||||
|
p.details {
|
||||||
|
font-size: 12px;
|
||||||
|
max-width: 80%;
|
||||||
|
margin: 0 auto 35px auto;
|
||||||
|
}
|
||||||
|
.loading {
|
||||||
|
display: inline-block;
|
||||||
|
min-width: 20px;
|
||||||
|
position: relative;
|
||||||
|
margin-left: 5px;
|
||||||
|
}
|
||||||
|
.loading:after {
|
||||||
|
overflow: hidden;
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: bottom;
|
||||||
|
-webkit-animation: ellipsis steps(4, end) 900ms infinite;
|
||||||
|
animation: ellipsis steps(4, end) 900ms infinite;
|
||||||
|
content: "\2026";
|
||||||
|
width: 0;
|
||||||
|
font-family: "Press Start 2P";
|
||||||
|
font-size: 16px;
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: -19px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes ellipsis {
|
||||||
|
to {
|
||||||
|
width: 1.25em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@-webkit-keyframes ellipsis {
|
||||||
|
to {
|
||||||
|
width: 1.25em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.button {
|
||||||
|
cursor: pointer;
|
||||||
|
font-family: "Press Start 2P";
|
||||||
|
font-size: 14px;
|
||||||
|
.reload {
|
||||||
|
margin-top: -4px;
|
||||||
|
width: 22px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
171
front/src/Components/Video/DesktopCapturerSourcePicker.svelte
Normal file
171
front/src/Components/Video/DesktopCapturerSourcePicker.svelte
Normal file
@ -0,0 +1,171 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { fly } from "svelte/transition";
|
||||||
|
import {
|
||||||
|
desktopCapturerSourcePromiseResolve,
|
||||||
|
showDesktopCapturerSourcePicker,
|
||||||
|
} from "../../Stores/ScreenSharingStore";
|
||||||
|
import { onDestroy, onMount } from "svelte";
|
||||||
|
import type { DesktopCapturerSource } from "@wa-preload-app";
|
||||||
|
|
||||||
|
let desktopCapturerSources: DesktopCapturerSource[] = [];
|
||||||
|
let interval: ReturnType<typeof setInterval>;
|
||||||
|
|
||||||
|
async function getDesktopCapturerSources() {
|
||||||
|
if (!window.WAD) {
|
||||||
|
throw new Error("This component can only be used in the desktop app");
|
||||||
|
}
|
||||||
|
desktopCapturerSources = await window.WAD.getDesktopCapturerSources({
|
||||||
|
thumbnailSize: {
|
||||||
|
height: 144,
|
||||||
|
width: 256,
|
||||||
|
},
|
||||||
|
types: ["screen", "window"],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onMount(async () => {
|
||||||
|
await getDesktopCapturerSources();
|
||||||
|
interval = setInterval(() => {
|
||||||
|
void getDesktopCapturerSources();
|
||||||
|
}, 1000);
|
||||||
|
});
|
||||||
|
|
||||||
|
onDestroy(() => {
|
||||||
|
clearInterval(interval);
|
||||||
|
});
|
||||||
|
|
||||||
|
function selectDesktopCapturerSource(source: DesktopCapturerSource) {
|
||||||
|
if (!desktopCapturerSourcePromiseResolve) {
|
||||||
|
throw new Error("desktopCapturerSourcePromiseResolve is not defined");
|
||||||
|
}
|
||||||
|
desktopCapturerSourcePromiseResolve(source);
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
|
||||||
|
function cancel() {
|
||||||
|
if (!desktopCapturerSourcePromiseResolve) {
|
||||||
|
throw new Error("desktopCapturerSourcePromiseResolve is not defined");
|
||||||
|
}
|
||||||
|
desktopCapturerSourcePromiseResolve(null);
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
|
||||||
|
function close() {
|
||||||
|
$showDesktopCapturerSourcePicker = false;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="source-picker nes-container is-rounded" transition:fly={{ y: -50, duration: 500 }}>
|
||||||
|
<button type="button" class="nes-btn is-error close" on:click={cancel}>×</button>
|
||||||
|
<h2>Select a Screen or Window to share!</h2>
|
||||||
|
<section class="streams">
|
||||||
|
{#each desktopCapturerSources as source}
|
||||||
|
<div
|
||||||
|
class="media-box nes-container is-rounded clickable"
|
||||||
|
on:click|preventDefault={() => selectDesktopCapturerSource(source)}
|
||||||
|
>
|
||||||
|
<img src={source.thumbnailURL} alt={source.name} />
|
||||||
|
<div class="container">
|
||||||
|
{source.name}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.source-picker {
|
||||||
|
position: absolute;
|
||||||
|
pointer-events: auto;
|
||||||
|
background: #eceeee;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
margin-top: 4%;
|
||||||
|
height: 80vh;
|
||||||
|
width: 80vw;
|
||||||
|
max-width: 1024px;
|
||||||
|
z-index: 900;
|
||||||
|
text-align: center;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
background-color: #333333;
|
||||||
|
color: whitesmoke;
|
||||||
|
|
||||||
|
.nes-btn.is-error.close {
|
||||||
|
position: absolute;
|
||||||
|
top: -20px;
|
||||||
|
right: -20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-family: "Press Start 2P";
|
||||||
|
}
|
||||||
|
|
||||||
|
section.streams {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 10px;
|
||||||
|
overflow-y: auto;
|
||||||
|
justify-content: center;
|
||||||
|
align-content: flex-start;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.media-box {
|
||||||
|
position: relative;
|
||||||
|
padding: 0;
|
||||||
|
width: calc(100% / 3 - 20px);
|
||||||
|
max-width: 256px;
|
||||||
|
padding-bottom: calc(min((100% / 3 - 20px), 256px) * (144px / 256px));
|
||||||
|
max-height: 144px;
|
||||||
|
justify-content: center;
|
||||||
|
background-color: #000;
|
||||||
|
background-clip: padding-box;
|
||||||
|
|
||||||
|
&.clickable * {
|
||||||
|
cursor: url("../../../style/images/cursor_pointer.png"), pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
transform: scale(1.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
max-width: 100%;
|
||||||
|
max-height: 100%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.nes-container.is-rounded {
|
||||||
|
border-image-outset: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.container {
|
||||||
|
position: absolute;
|
||||||
|
width: 90%;
|
||||||
|
height: auto;
|
||||||
|
left: 5%;
|
||||||
|
top: calc(100% - 28px);
|
||||||
|
text-align: center;
|
||||||
|
padding: 2px 36px;
|
||||||
|
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow-x: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
font-size: 14px;
|
||||||
|
margin: 2px;
|
||||||
|
background-color: white;
|
||||||
|
color: #333333;
|
||||||
|
border: solid 3px black;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
BIN
front/src/Components/images/logo-min-white.png
Normal file
BIN
front/src/Components/images/logo-min-white.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 7.4 KiB |
BIN
front/src/Components/images/reload.png
Normal file
BIN
front/src/Components/images/reload.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.6 KiB |
@ -2,18 +2,19 @@
|
|||||||
import type { Game } from "../../Phaser/Game/Game";
|
import type { Game } from "../../Phaser/Game/Game";
|
||||||
import { SelectCharacterScene, SelectCharacterSceneName } from "../../Phaser/Login/SelectCharacterScene";
|
import { SelectCharacterScene, SelectCharacterSceneName } from "../../Phaser/Login/SelectCharacterScene";
|
||||||
import LL from "../../i18n/i18n-svelte";
|
import LL from "../../i18n/i18n-svelte";
|
||||||
import { customizeAvailableStore } from "../../Stores/SelectCharacterSceneStore";
|
import { customizeAvailableStore, selectedCollection } from "../../Stores/SelectCharacterSceneStore";
|
||||||
|
|
||||||
export let game: Game;
|
export let game: Game;
|
||||||
|
|
||||||
const selectCharacterScene = game.scene.getScene(SelectCharacterSceneName) as SelectCharacterScene;
|
const selectCharacterScene = game.scene.getScene(SelectCharacterSceneName) as SelectCharacterScene;
|
||||||
|
const showArrows = selectCharacterScene.getCollectionKeysSize() > 1;
|
||||||
|
|
||||||
function selectLeft() {
|
function selectLeft() {
|
||||||
selectCharacterScene.moveToLeft();
|
selectCharacterScene.selectPreviousCollection();
|
||||||
}
|
}
|
||||||
|
|
||||||
function selectRight() {
|
function selectRight() {
|
||||||
selectCharacterScene.moveToRight();
|
selectCharacterScene.selectNextCollection();
|
||||||
}
|
}
|
||||||
|
|
||||||
function cameraScene() {
|
function cameraScene() {
|
||||||
@ -25,83 +26,72 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<form class="selectCharacterScene">
|
<section class="text-center">
|
||||||
<section class="text-center">
|
<h2>{$LL.woka.selectWoka.title()}</h2>
|
||||||
<h2>{$LL.woka.selectWoka.title()}</h2>
|
</section>
|
||||||
<button class="selectCharacterButton selectCharacterButtonLeft nes-btn" on:click|preventDefault={selectLeft}>
|
<section class="category">
|
||||||
<
|
{#if showArrows}
|
||||||
</button>
|
<button class="selectCharacterButton nes-btn" on:click|preventDefault={selectLeft}> < </button>
|
||||||
<button class="selectCharacterButton selectCharacterButtonRight nes-btn" on:click|preventDefault={selectRight}>
|
<strong class="category-text">{$selectedCollection}</strong>
|
||||||
>
|
<button class="selectCharacterButton nes-btn" on:click|preventDefault={selectRight}> > </button>
|
||||||
</button>
|
{/if}
|
||||||
</section>
|
</section>
|
||||||
<section class="action">
|
<section class="action">
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
class="selectCharacterSceneFormSubmit nes-btn is-primary"
|
||||||
|
on:click|preventDefault={cameraScene}>{$LL.woka.selectWoka.continue()}</button
|
||||||
|
>
|
||||||
|
{#if $customizeAvailableStore}
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
class="selectCharacterSceneFormSubmit nes-btn is-primary"
|
class="selectCharacterSceneFormCustomYourOwnSubmit nes-btn"
|
||||||
on:click|preventDefault={cameraScene}>{$LL.woka.selectWoka.continue()}</button
|
on:click|preventDefault={customizeScene}>{$LL.woka.selectWoka.customize()}</button
|
||||||
>
|
>
|
||||||
{#if $customizeAvailableStore}
|
{/if}
|
||||||
<button
|
</section>
|
||||||
type="submit"
|
|
||||||
class="selectCharacterSceneFormCustomYourOwnSubmit nes-btn"
|
|
||||||
on:click|preventDefault={customizeScene}>{$LL.woka.selectWoka.customize()}</button
|
|
||||||
>
|
|
||||||
{/if}
|
|
||||||
</section>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@import "../../../style/breakpoints.scss";
|
@import "../../../style/breakpoints.scss";
|
||||||
|
|
||||||
form.selectCharacterScene {
|
section {
|
||||||
|
font-family: "Press Start 2P";
|
||||||
|
color: #ebeeee;
|
||||||
|
margin: 5px;
|
||||||
|
|
||||||
|
&.category {
|
||||||
|
text-align: center;
|
||||||
|
margin-top: 8vh;
|
||||||
|
.category-text {
|
||||||
|
font-family: "Press Start 2P";
|
||||||
|
display: inline-block;
|
||||||
|
width: 65%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.action {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 2vh;
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-family: "Press Start 2P";
|
||||||
|
margin: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.text-center {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
button.selectCharacterButton {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
font-family: "Press Start 2P";
|
font-family: "Press Start 2P";
|
||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
color: #ebeeee;
|
|
||||||
|
|
||||||
section {
|
|
||||||
margin: 10px;
|
|
||||||
|
|
||||||
&.action {
|
|
||||||
text-align: center;
|
|
||||||
margin-top: 55vh;
|
|
||||||
}
|
|
||||||
|
|
||||||
h2 {
|
|
||||||
font-family: "Press Start 2P";
|
|
||||||
margin: 1px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.text-center {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
button.selectCharacterButton {
|
|
||||||
position: absolute;
|
|
||||||
top: 33vh;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
button {
|
|
||||||
font-family: "Press Start 2P";
|
|
||||||
|
|
||||||
&.selectCharacterButtonLeft {
|
|
||||||
left: 33vw;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.selectCharacterButtonRight {
|
|
||||||
right: 33vw;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@include media-breakpoint-up(md) {
|
|
||||||
form.selectCharacterScene button.selectCharacterButtonLeft {
|
|
||||||
left: 5vw;
|
|
||||||
}
|
|
||||||
form.selectCharacterScene button.selectCharacterButtonRight {
|
|
||||||
right: 5vw;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -25,6 +25,7 @@ import {
|
|||||||
TokenExpiredMessage,
|
TokenExpiredMessage,
|
||||||
WorldConnexionMessage,
|
WorldConnexionMessage,
|
||||||
ErrorMessage as ErrorMessageTsProto,
|
ErrorMessage as ErrorMessageTsProto,
|
||||||
|
ErrorScreenMessage as ErrorScreenMessageTsProto,
|
||||||
UserMovedMessage as UserMovedMessageTsProto,
|
UserMovedMessage as UserMovedMessageTsProto,
|
||||||
GroupUpdateMessage as GroupUpdateMessageTsProto,
|
GroupUpdateMessage as GroupUpdateMessageTsProto,
|
||||||
GroupDeleteMessage as GroupDeleteMessageTsProto,
|
GroupDeleteMessage as GroupDeleteMessageTsProto,
|
||||||
@ -47,6 +48,7 @@ import { Subject } from "rxjs";
|
|||||||
import { selectCharacterSceneVisibleStore } from "../Stores/SelectCharacterStore";
|
import { selectCharacterSceneVisibleStore } from "../Stores/SelectCharacterStore";
|
||||||
import { gameManager } from "../Phaser/Game/GameManager";
|
import { gameManager } from "../Phaser/Game/GameManager";
|
||||||
import { SelectCharacterScene, SelectCharacterSceneName } from "../Phaser/Login/SelectCharacterScene";
|
import { SelectCharacterScene, SelectCharacterSceneName } from "../Phaser/Login/SelectCharacterScene";
|
||||||
|
import { errorScreenStore } from "../Stores/ErrorScreenStore";
|
||||||
|
|
||||||
const manualPingDelay = 20000;
|
const manualPingDelay = 20000;
|
||||||
|
|
||||||
@ -62,6 +64,9 @@ export class RoomConnection implements RoomConnection {
|
|||||||
private readonly _errorMessageStream = new Subject<ErrorMessageTsProto>();
|
private readonly _errorMessageStream = new Subject<ErrorMessageTsProto>();
|
||||||
public readonly errorMessageStream = this._errorMessageStream.asObservable();
|
public readonly errorMessageStream = this._errorMessageStream.asObservable();
|
||||||
|
|
||||||
|
private readonly _errorScreenMessageStream = new Subject<ErrorScreenMessageTsProto>();
|
||||||
|
public readonly errorScreenMessageStream = this._errorScreenMessageStream.asObservable();
|
||||||
|
|
||||||
private readonly _roomJoinedMessageStream = new Subject<{
|
private readonly _roomJoinedMessageStream = new Subject<{
|
||||||
connection: RoomConnection;
|
connection: RoomConnection;
|
||||||
room: RoomJoinedMessageInterface;
|
room: RoomJoinedMessageInterface;
|
||||||
@ -298,8 +303,7 @@ export class RoomConnection implements RoomConnection {
|
|||||||
}
|
}
|
||||||
default: {
|
default: {
|
||||||
// Security check: if we forget a "case", the line below will catch the error at compile-time.
|
// Security check: if we forget a "case", the line below will catch the error at compile-time.
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
const _exhaustiveCheck: never = subMessage;
|
||||||
const tmp: never = subMessage;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -477,10 +481,16 @@ export class RoomConnection implements RoomConnection {
|
|||||||
console.error("An error occurred server side: " + message.errorMessage.message);
|
console.error("An error occurred server side: " + message.errorMessage.message);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case "errorScreenMessage": {
|
||||||
|
this._errorScreenMessageStream.next(message.errorScreenMessage);
|
||||||
|
if (message.errorScreenMessage.code !== "retry") this.closed = true;
|
||||||
|
console.error("An error occurred server side: " + message.errorScreenMessage.code);
|
||||||
|
errorScreenStore.setError(message.errorScreenMessage);
|
||||||
|
break;
|
||||||
|
}
|
||||||
default: {
|
default: {
|
||||||
// Security check: if we forget a "case", the line below will catch the error at compile-time.
|
// Security check: if we forget a "case", the line below will catch the error at compile-time.
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
const _exhaustiveCheck: never = message;
|
||||||
const tmp: never = message;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
35
front/src/Phaser/Components/SelectWoka/WokaSlot.ts
Normal file
35
front/src/Phaser/Components/SelectWoka/WokaSlot.ts
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import { GridItem } from "@home-based-studio/phaser3-utils";
|
||||||
|
|
||||||
|
export class WokaSlot extends GridItem {
|
||||||
|
private sprite: Phaser.GameObjects.Sprite;
|
||||||
|
private selection: Phaser.GameObjects.Rectangle;
|
||||||
|
|
||||||
|
private readonly SIZE: number = 50;
|
||||||
|
|
||||||
|
constructor(scene: Phaser.Scene, spriteKey: string, id?: string) {
|
||||||
|
super(scene, id);
|
||||||
|
|
||||||
|
this.sprite = this.scene.add.sprite(0, 0, spriteKey);
|
||||||
|
this.selection = this.scene.add
|
||||||
|
.rectangle(0, 0, this.SIZE, this.SIZE)
|
||||||
|
.setStrokeStyle(1, 0xffffff)
|
||||||
|
.setVisible(false);
|
||||||
|
|
||||||
|
this.add([this.selection, this.sprite]);
|
||||||
|
this.setSize(this.SIZE, this.SIZE);
|
||||||
|
this.setInteractive({ cursor: "pointer" });
|
||||||
|
this.scene.input.setDraggable(this);
|
||||||
|
|
||||||
|
this.bindEventHandlers();
|
||||||
|
|
||||||
|
this.scene.add.existing(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public getSprite(): Phaser.GameObjects.Sprite {
|
||||||
|
return this.sprite;
|
||||||
|
}
|
||||||
|
|
||||||
|
public select(select: boolean = true): void {
|
||||||
|
this.selection.setVisible(select);
|
||||||
|
}
|
||||||
|
}
|
@ -36,14 +36,16 @@ export enum PlayerTexturesKey {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class PlayerTextures {
|
export class PlayerTextures {
|
||||||
private PLAYER_RESOURCES: BodyResourceDescriptionListInterface = {};
|
private wokaResources: BodyResourceDescriptionListInterface = {};
|
||||||
private COLOR_RESOURCES: BodyResourceDescriptionListInterface = {};
|
private colorResources: BodyResourceDescriptionListInterface = {};
|
||||||
private EYES_RESOURCES: BodyResourceDescriptionListInterface = {};
|
private eyesResources: BodyResourceDescriptionListInterface = {};
|
||||||
private HAIR_RESOURCES: BodyResourceDescriptionListInterface = {};
|
private hairResources: BodyResourceDescriptionListInterface = {};
|
||||||
private CLOTHES_RESOURCES: BodyResourceDescriptionListInterface = {};
|
private clothesResources: BodyResourceDescriptionListInterface = {};
|
||||||
private HATS_RESOURCES: BodyResourceDescriptionListInterface = {};
|
private hatsResources: BodyResourceDescriptionListInterface = {};
|
||||||
private ACCESSORIES_RESOURCES: BodyResourceDescriptionListInterface = {};
|
private accessoriesResources: BodyResourceDescriptionListInterface = {};
|
||||||
private LAYERS: BodyResourceDescriptionListInterface[] = [];
|
private layers: BodyResourceDescriptionListInterface[] = [];
|
||||||
|
|
||||||
|
private wokaCollections = new Map<string, BodyResourceDescriptionInterface[]>();
|
||||||
|
|
||||||
public loadPlayerTexturesMetadata(metadata: WokaList): void {
|
public loadPlayerTexturesMetadata(metadata: WokaList): void {
|
||||||
this.mapTexturesMetadataIntoResources(metadata);
|
this.mapTexturesMetadataIntoResources(metadata);
|
||||||
@ -52,43 +54,53 @@ export class PlayerTextures {
|
|||||||
public getTexturesResources(key: PlayerTexturesKey): BodyResourceDescriptionListInterface {
|
public getTexturesResources(key: PlayerTexturesKey): BodyResourceDescriptionListInterface {
|
||||||
switch (key) {
|
switch (key) {
|
||||||
case PlayerTexturesKey.Accessory:
|
case PlayerTexturesKey.Accessory:
|
||||||
return this.ACCESSORIES_RESOURCES;
|
return this.accessoriesResources;
|
||||||
case PlayerTexturesKey.Body:
|
case PlayerTexturesKey.Body:
|
||||||
return this.COLOR_RESOURCES;
|
return this.colorResources;
|
||||||
case PlayerTexturesKey.Clothes:
|
case PlayerTexturesKey.Clothes:
|
||||||
return this.CLOTHES_RESOURCES;
|
return this.clothesResources;
|
||||||
case PlayerTexturesKey.Eyes:
|
case PlayerTexturesKey.Eyes:
|
||||||
return this.EYES_RESOURCES;
|
return this.eyesResources;
|
||||||
case PlayerTexturesKey.Hair:
|
case PlayerTexturesKey.Hair:
|
||||||
return this.HAIR_RESOURCES;
|
return this.hairResources;
|
||||||
case PlayerTexturesKey.Hat:
|
case PlayerTexturesKey.Hat:
|
||||||
return this.HATS_RESOURCES;
|
return this.hatsResources;
|
||||||
case PlayerTexturesKey.Woka:
|
case PlayerTexturesKey.Woka:
|
||||||
return this.PLAYER_RESOURCES;
|
return this.wokaResources;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public getLayers(): BodyResourceDescriptionListInterface[] {
|
public getLayers(): BodyResourceDescriptionListInterface[] {
|
||||||
return this.LAYERS;
|
return this.layers;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getCollectionsKeys(): string[] {
|
||||||
|
return Array.from(this.wokaCollections.keys());
|
||||||
|
}
|
||||||
|
|
||||||
|
public getWokaCollectionTextures(key: string): BodyResourceDescriptionInterface[] {
|
||||||
|
return this.wokaCollections.get(key) ?? [];
|
||||||
}
|
}
|
||||||
|
|
||||||
private mapTexturesMetadataIntoResources(metadata: WokaList): void {
|
private mapTexturesMetadataIntoResources(metadata: WokaList): void {
|
||||||
this.PLAYER_RESOURCES = this.getMappedResources(metadata.woka);
|
this.wokaResources = this.getMappedResources(metadata.woka);
|
||||||
this.COLOR_RESOURCES = this.getMappedResources(metadata.body);
|
this.colorResources = this.getMappedResources(metadata.body);
|
||||||
this.EYES_RESOURCES = this.getMappedResources(metadata.eyes);
|
this.eyesResources = this.getMappedResources(metadata.eyes);
|
||||||
this.HAIR_RESOURCES = this.getMappedResources(metadata.hair);
|
this.hairResources = this.getMappedResources(metadata.hair);
|
||||||
this.CLOTHES_RESOURCES = this.getMappedResources(metadata.clothes);
|
this.clothesResources = this.getMappedResources(metadata.clothes);
|
||||||
this.HATS_RESOURCES = this.getMappedResources(metadata.hat);
|
this.hatsResources = this.getMappedResources(metadata.hat);
|
||||||
this.ACCESSORIES_RESOURCES = this.getMappedResources(metadata.accessory);
|
this.accessoriesResources = this.getMappedResources(metadata.accessory);
|
||||||
|
|
||||||
this.LAYERS = [
|
this.layers = [
|
||||||
this.COLOR_RESOURCES,
|
this.colorResources,
|
||||||
this.EYES_RESOURCES,
|
this.eyesResources,
|
||||||
this.HAIR_RESOURCES,
|
this.hairResources,
|
||||||
this.CLOTHES_RESOURCES,
|
this.clothesResources,
|
||||||
this.HATS_RESOURCES,
|
this.hatsResources,
|
||||||
this.ACCESSORIES_RESOURCES,
|
this.accessoriesResources,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
this.mapWokaCollections(metadata.woka);
|
||||||
}
|
}
|
||||||
|
|
||||||
private getMappedResources(category: WokaPartType): BodyResourceDescriptionListInterface {
|
private getMappedResources(category: WokaPartType): BodyResourceDescriptionListInterface {
|
||||||
@ -103,6 +115,19 @@ export class PlayerTextures {
|
|||||||
}
|
}
|
||||||
return resources;
|
return resources;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private mapWokaCollections(category: WokaPartType): void {
|
||||||
|
if (!category) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (const collection of category.collections) {
|
||||||
|
const textures: BodyResourceDescriptionInterface[] = [];
|
||||||
|
for (const texture of collection.textures) {
|
||||||
|
textures.push({ id: texture.id, img: texture.url });
|
||||||
|
}
|
||||||
|
this.wokaCollections.set(collection.name, textures);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const OBJECTS: BodyResourceDescriptionInterface[] = [
|
export const OBJECTS: BodyResourceDescriptionInterface[] = [
|
||||||
|
@ -97,7 +97,6 @@ export class CameraManager extends Phaser.Events.EventEmitter {
|
|||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
||||||
this.camera.pan(setTo.x, setTo.y, duration, Easing.SineEaseOut, true, (camera, progress, x, y) => {
|
this.camera.pan(setTo.x, setTo.y, duration, Easing.SineEaseOut, true, (camera, progress, x, y) => {
|
||||||
if (this.cameraMode === CameraMode.Positioned) {
|
if (this.cameraMode === CameraMode.Positioned) {
|
||||||
this.waScaleManager.zoomModifier = currentZoomModifier + progress * zoomModifierChange;
|
this.waScaleManager.zoomModifier = currentZoomModifier + progress * zoomModifierChange;
|
||||||
@ -139,7 +138,6 @@ export class CameraManager extends Phaser.Events.EventEmitter {
|
|||||||
this.emit(CameraManagerEvent.CameraUpdate, this.getCameraUpdateEventData());
|
this.emit(CameraManagerEvent.CameraUpdate, this.getCameraUpdateEventData());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
||||||
this.camera.pan(focusOn.x, focusOn.y, duration, Easing.SineEaseOut, true, (camera, progress, x, y) => {
|
this.camera.pan(focusOn.x, focusOn.y, duration, Easing.SineEaseOut, true, (camera, progress, x, y) => {
|
||||||
this.waScaleManager.zoomModifier = currentZoomModifier + progress * zoomModifierChange;
|
this.waScaleManager.zoomModifier = currentZoomModifier + progress * zoomModifierChange;
|
||||||
if (progress === 1) {
|
if (progress === 1) {
|
||||||
|
@ -177,8 +177,6 @@ export class GameScene extends DirtyScene {
|
|||||||
|
|
||||||
private localVolumeStoreUnsubscriber: Unsubscriber | undefined;
|
private localVolumeStoreUnsubscriber: Unsubscriber | undefined;
|
||||||
private followUsersColorStoreUnsubscribe!: Unsubscriber;
|
private followUsersColorStoreUnsubscribe!: Unsubscriber;
|
||||||
private currentPlayerGroupIdStoreUnsubscribe!: Unsubscriber;
|
|
||||||
private privacyShutdownStoreUnsubscribe!: Unsubscriber;
|
|
||||||
private userIsJitsiDominantSpeakerStoreUnsubscriber!: Unsubscriber;
|
private userIsJitsiDominantSpeakerStoreUnsubscriber!: Unsubscriber;
|
||||||
private jitsiParticipantsCountStoreUnsubscriber!: Unsubscriber;
|
private jitsiParticipantsCountStoreUnsubscriber!: Unsubscriber;
|
||||||
|
|
||||||
@ -1593,7 +1591,6 @@ export class GameScene extends DirtyScene {
|
|||||||
this.emoteUnsubscribe();
|
this.emoteUnsubscribe();
|
||||||
this.emoteMenuUnsubscribe();
|
this.emoteMenuUnsubscribe();
|
||||||
this.followUsersColorStoreUnsubscribe();
|
this.followUsersColorStoreUnsubscribe();
|
||||||
this.privacyShutdownStoreUnsubscribe();
|
|
||||||
this.biggestAvailableAreaStoreUnsubscribe();
|
this.biggestAvailableAreaStoreUnsubscribe();
|
||||||
this.userIsJitsiDominantSpeakerStoreUnsubscriber();
|
this.userIsJitsiDominantSpeakerStoreUnsubscriber();
|
||||||
this.jitsiParticipantsCountStoreUnsubscriber();
|
this.jitsiParticipantsCountStoreUnsubscriber();
|
||||||
@ -1733,7 +1730,6 @@ export class GameScene extends DirtyScene {
|
|||||||
private createCollisionWithPlayer() {
|
private createCollisionWithPlayer() {
|
||||||
//add collision layer
|
//add collision layer
|
||||||
for (const phaserLayer of this.gameMap.phaserLayers) {
|
for (const phaserLayer of this.gameMap.phaserLayers) {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
||||||
this.physics.add.collider(this.CurrentPlayer, phaserLayer, (object1: GameObject, object2: GameObject) => {
|
this.physics.add.collider(this.CurrentPlayer, phaserLayer, (object1: GameObject, object2: GameObject) => {
|
||||||
//this.CurrentPlayer.say("Collision with layer : "+ (object2 as Tile).layer.name)
|
//this.CurrentPlayer.say("Collision with layer : "+ (object2 as Tile).layer.name)
|
||||||
});
|
});
|
||||||
@ -1784,11 +1780,9 @@ export class GameScene extends DirtyScene {
|
|||||||
emoteMenuStore.openEmoteMenu();
|
emoteMenuStore.openEmoteMenu();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
||||||
this.CurrentPlayer.on(Phaser.Input.Events.POINTER_OVER, (pointer: Phaser.Input.Pointer) => {
|
this.CurrentPlayer.on(Phaser.Input.Events.POINTER_OVER, (pointer: Phaser.Input.Pointer) => {
|
||||||
this.CurrentPlayer.pointerOverOutline(0x365dff);
|
this.CurrentPlayer.pointerOverOutline(0x365dff);
|
||||||
});
|
});
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
||||||
this.CurrentPlayer.on(Phaser.Input.Events.POINTER_OUT, (pointer: Phaser.Input.Pointer) => {
|
this.CurrentPlayer.on(Phaser.Input.Events.POINTER_OUT, (pointer: Phaser.Input.Pointer) => {
|
||||||
this.CurrentPlayer.pointerOutOutline();
|
this.CurrentPlayer.pointerOutOutline();
|
||||||
});
|
});
|
||||||
@ -1890,8 +1884,7 @@ export class GameScene extends DirtyScene {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default: {
|
default: {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
const _exhaustiveCheck: never = event;
|
||||||
const tmp: never = event;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -28,7 +28,7 @@ export class TexturesHelper {
|
|||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
rt.destroy();
|
rt.destroy();
|
||||||
throw new Error("Could not get the snapshot");
|
throw new Error("Could not get the snapshot: " + error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,4 +11,8 @@ export abstract class AbstractCharacterScene extends ResizableScene {
|
|||||||
this.playerTextures = new PlayerTextures();
|
this.playerTextures = new PlayerTextures();
|
||||||
this.superLoad = new SuperLoaderPlugin(this);
|
this.superLoad = new SuperLoaderPlugin(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
preload() {
|
||||||
|
this.input.dragDistanceThreshold = 10;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -54,7 +54,7 @@ export class CustomizeScene extends AbstractCharacterScene {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public preload(): void {
|
public preload(): void {
|
||||||
this.input.dragDistanceThreshold = 10;
|
super.preload();
|
||||||
|
|
||||||
this.load.image("iconClothes", "/resources/icons/icon_clothes.png");
|
this.load.image("iconClothes", "/resources/icons/icon_clothes.png");
|
||||||
this.load.image("iconAccessory", "/resources/icons/icon_accessory.png");
|
this.load.image("iconAccessory", "/resources/icons/icon_accessory.png");
|
||||||
@ -112,7 +112,6 @@ export class CustomizeScene extends AbstractCharacterScene {
|
|||||||
this.onResize();
|
this.onResize();
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
||||||
public update(time: number, dt: number): void {
|
public update(time: number, dt: number): void {
|
||||||
this.customWokaPreviewer.update();
|
this.customWokaPreviewer.update();
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,5 @@ export class EmptyScene extends Scene {
|
|||||||
|
|
||||||
create() {}
|
create() {}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
||||||
update(time: number, delta: number): void {}
|
update(time: number, delta: number): void {}
|
||||||
}
|
}
|
||||||
|
@ -24,7 +24,6 @@ export class EnableCameraScene extends ResizableScene {
|
|||||||
|
|
||||||
public onResize(): void {}
|
public onResize(): void {}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
||||||
update(time: number, delta: number): void {}
|
update(time: number, delta: number): void {}
|
||||||
|
|
||||||
public login(): void {
|
public login(): void {
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
import { gameManager } from "../Game/GameManager";
|
import { gameManager } from "../Game/GameManager";
|
||||||
import { Scene } from "phaser";
|
import { Scene } from "phaser";
|
||||||
import { ErrorScene } from "../Reconnecting/ErrorScene";
|
import { ErrorScene } from "../Reconnecting/ErrorScene";
|
||||||
import { WAError } from "../Reconnecting/WAError";
|
|
||||||
import { waScaleManager } from "../Services/WaScaleManager";
|
import { waScaleManager } from "../Services/WaScaleManager";
|
||||||
import { ReconnectingTextures } from "../Reconnecting/ReconnectingScene";
|
import { ReconnectingTextures } from "../Reconnecting/ReconnectingScene";
|
||||||
import LL from "../../i18n/i18n-svelte";
|
|
||||||
import { get } from "svelte/store";
|
|
||||||
import { localeDetector } from "../../i18n/locales";
|
import { localeDetector } from "../../i18n/locales";
|
||||||
|
import { errorScreenStore } from "../../Stores/ErrorScreenStore";
|
||||||
|
import { isErrorApiData } from "../../Messages/JsonMessages/ErrorApiData";
|
||||||
|
import { connectionManager } from "../../Connexion/ConnectionManager";
|
||||||
|
|
||||||
export const EntrySceneName = "EntryScene";
|
export const EntrySceneName = "EntryScene";
|
||||||
|
|
||||||
@ -47,27 +47,13 @@ export class EntryScene extends Scene {
|
|||||||
this.scene.start(nextSceneName);
|
this.scene.start(nextSceneName);
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
const $LL = get(LL);
|
const errorType = isErrorApiData.safeParse(err?.response?.data);
|
||||||
if (err.response && err.response.status == 404) {
|
if (errorType.success) {
|
||||||
ErrorScene.showError(
|
if (errorType.data.type === "unauthorized") {
|
||||||
new WAError(
|
void connectionManager.logout();
|
||||||
$LL.error.accessLink.title(),
|
} else if (errorType.data.type === "redirect") {
|
||||||
$LL.error.accessLink.subTitle(),
|
window.location.assign(errorType.data.urlToRedirect);
|
||||||
$LL.error.accessLink.details()
|
} else errorScreenStore.setError(err?.response?.data);
|
||||||
),
|
|
||||||
this.scene
|
|
||||||
);
|
|
||||||
} else if (err.response && err.response.status == 403) {
|
|
||||||
ErrorScene.showError(
|
|
||||||
new WAError(
|
|
||||||
$LL.error.connectionRejected.title(),
|
|
||||||
$LL.error.connectionRejected.subTitle({
|
|
||||||
error: err.response.data ? ". \n\r \n\r" + `${err.response.data}` : "",
|
|
||||||
}),
|
|
||||||
$LL.error.connectionRejected.details()
|
|
||||||
),
|
|
||||||
this.scene
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
ErrorScene.showError(err, this.scene);
|
ErrorScene.showError(err, this.scene);
|
||||||
}
|
}
|
||||||
|
@ -49,7 +49,6 @@ export class LoginScene extends ResizableScene {
|
|||||||
loginSceneVisibleStore.set(false);
|
loginSceneVisibleStore.set(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
||||||
update(time: number, delta: number): void {}
|
update(time: number, delta: number): void {}
|
||||||
|
|
||||||
public onResize(): void {}
|
public onResize(): void {}
|
||||||
|
@ -1,58 +0,0 @@
|
|||||||
import { SelectCharacterScene } from "./SelectCharacterScene";
|
|
||||||
|
|
||||||
export class SelectCharacterMobileScene extends SelectCharacterScene {
|
|
||||||
create() {
|
|
||||||
super.create();
|
|
||||||
this.onResize();
|
|
||||||
this.selectedRectangle.destroy();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected defineSetupPlayer(num: number) {
|
|
||||||
const deltaX = 30;
|
|
||||||
const deltaY = 2;
|
|
||||||
let [playerX, playerY] = this.getCharacterPosition();
|
|
||||||
let playerVisible = true;
|
|
||||||
let playerScale = 1.5;
|
|
||||||
let playerOpacity = 1;
|
|
||||||
|
|
||||||
if (this.currentSelectUser !== num) {
|
|
||||||
playerVisible = false;
|
|
||||||
}
|
|
||||||
if (num === this.currentSelectUser + 1) {
|
|
||||||
playerY -= deltaY;
|
|
||||||
playerX += deltaX;
|
|
||||||
playerScale = 0.8;
|
|
||||||
playerOpacity = 0.6;
|
|
||||||
playerVisible = true;
|
|
||||||
}
|
|
||||||
if (num === this.currentSelectUser + 2) {
|
|
||||||
playerY -= deltaY;
|
|
||||||
playerX += deltaX * 2;
|
|
||||||
playerScale = 0.8;
|
|
||||||
playerOpacity = 0.6;
|
|
||||||
playerVisible = true;
|
|
||||||
}
|
|
||||||
if (num === this.currentSelectUser - 1) {
|
|
||||||
playerY -= deltaY;
|
|
||||||
playerX -= deltaX;
|
|
||||||
playerScale = 0.8;
|
|
||||||
playerOpacity = 0.6;
|
|
||||||
playerVisible = true;
|
|
||||||
}
|
|
||||||
if (num === this.currentSelectUser - 2) {
|
|
||||||
playerY -= deltaY;
|
|
||||||
playerX -= deltaX * 2;
|
|
||||||
playerScale = 0.8;
|
|
||||||
playerOpacity = 0.6;
|
|
||||||
playerVisible = true;
|
|
||||||
}
|
|
||||||
return { playerX, playerY, playerScale, playerOpacity, playerVisible };
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns pixel position by on column and row number
|
|
||||||
*/
|
|
||||||
protected getCharacterPosition(): [number, number] {
|
|
||||||
return [this.game.renderer.width / 2, this.game.renderer.height / 3];
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,5 +1,4 @@
|
|||||||
import { gameManager } from "../Game/GameManager";
|
import { gameManager } from "../Game/GameManager";
|
||||||
import Rectangle = Phaser.GameObjects.Rectangle;
|
|
||||||
import { EnableCameraSceneName } from "./EnableCameraScene";
|
import { EnableCameraSceneName } from "./EnableCameraScene";
|
||||||
import { CustomizeSceneName } from "./CustomizeScene";
|
import { CustomizeSceneName } from "./CustomizeScene";
|
||||||
import { localUserStore } from "../../Connexion/LocalUserStore";
|
import { localUserStore } from "../../Connexion/LocalUserStore";
|
||||||
@ -13,25 +12,25 @@ import { PinchManager } from "../UserInput/PinchManager";
|
|||||||
import { selectCharacterSceneVisibleStore } from "../../Stores/SelectCharacterStore";
|
import { selectCharacterSceneVisibleStore } from "../../Stores/SelectCharacterStore";
|
||||||
import { waScaleManager } from "../Services/WaScaleManager";
|
import { waScaleManager } from "../Services/WaScaleManager";
|
||||||
import { analyticsClient } from "../../Administration/AnalyticsClient";
|
import { analyticsClient } from "../../Administration/AnalyticsClient";
|
||||||
import { isMediaBreakpointUp } from "../../Utils/BreakpointsUtils";
|
|
||||||
import { PUSHER_URL } from "../../Enum/EnvironmentVariable";
|
import { PUSHER_URL } from "../../Enum/EnvironmentVariable";
|
||||||
import { customizeAvailableStore } from "../../Stores/SelectCharacterSceneStore";
|
import { customizeAvailableStore, selectedCollection } from "../../Stores/SelectCharacterSceneStore";
|
||||||
|
import { DraggableGrid } from "@home-based-studio/phaser3-utils";
|
||||||
|
import { WokaSlot } from "../Components/SelectWoka/WokaSlot";
|
||||||
|
import { DraggableGridEvent } from "@home-based-studio/phaser3-utils/lib/utils/gui/containers/grids/DraggableGrid";
|
||||||
import { wokaList } from "../../Messages/JsonMessages/PlayerTextures";
|
import { wokaList } from "../../Messages/JsonMessages/PlayerTextures";
|
||||||
|
|
||||||
//todo: put this constants in a dedicated file
|
//todo: put this constants in a dedicated file
|
||||||
export const SelectCharacterSceneName = "SelectCharacterScene";
|
export const SelectCharacterSceneName = "SelectCharacterScene";
|
||||||
|
|
||||||
export class SelectCharacterScene extends AbstractCharacterScene {
|
export class SelectCharacterScene extends AbstractCharacterScene {
|
||||||
protected readonly nbCharactersPerRow = 6;
|
protected selectedWoka!: Phaser.GameObjects.Sprite | null; // null if we are selecting the "customize" option
|
||||||
protected selectedPlayer!: Phaser.Physics.Arcade.Sprite | null; // null if we are selecting the "customize" option
|
|
||||||
protected players: Array<Phaser.Physics.Arcade.Sprite> = new Array<Phaser.Physics.Arcade.Sprite>();
|
|
||||||
protected playerModels!: BodyResourceDescriptionInterface[];
|
protected playerModels!: BodyResourceDescriptionInterface[];
|
||||||
|
|
||||||
protected selectedRectangle!: Rectangle;
|
private charactersDraggableGrid!: DraggableGrid;
|
||||||
|
private collectionKeys!: string[];
|
||||||
protected currentSelectUser = 0;
|
private selectedCollectionIndex!: number;
|
||||||
protected pointerClicked: boolean = false;
|
private selectedGridItemIndex?: number;
|
||||||
protected pointerTimer: number = 0;
|
private gridRowsCount: number = 1;
|
||||||
|
|
||||||
protected lazyloadingAttempt = true; //permit to update texture loaded after renderer
|
protected lazyloadingAttempt = true; //permit to update texture loaded after renderer
|
||||||
private loader: Loader;
|
private loader: Loader;
|
||||||
@ -44,7 +43,8 @@ export class SelectCharacterScene extends AbstractCharacterScene {
|
|||||||
this.playerTextures = new PlayerTextures();
|
this.playerTextures = new PlayerTextures();
|
||||||
}
|
}
|
||||||
|
|
||||||
preload() {
|
public preload() {
|
||||||
|
super.preload();
|
||||||
const wokaMetadataKey = "woka-list" + gameManager.currentStartedRoom.href;
|
const wokaMetadataKey = "woka-list" + gameManager.currentStartedRoom.href;
|
||||||
this.cache.json.remove(wokaMetadataKey);
|
this.cache.json.remove(wokaMetadataKey);
|
||||||
|
|
||||||
@ -67,228 +67,236 @@ export class SelectCharacterScene extends AbstractCharacterScene {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
.catch((e) => console.error(e));
|
.catch((e) => console.error(e));
|
||||||
|
this.playerModels = loadAllDefaultModels(this.load, this.playerTextures);
|
||||||
|
this.lazyloadingAttempt = false;
|
||||||
|
|
||||||
//this function must stay at the end of preload function
|
//this function must stay at the end of preload function
|
||||||
this.loader.addLoader();
|
this.loader.addLoader();
|
||||||
}
|
}
|
||||||
|
|
||||||
create() {
|
public create() {
|
||||||
|
waScaleManager.zoomModifier = 1;
|
||||||
|
this.selectedWoka = null;
|
||||||
|
this.selectedCollectionIndex = 0;
|
||||||
|
this.collectionKeys = this.playerTextures.getCollectionsKeys();
|
||||||
|
selectedCollection.set(this.getSelectedCollectionName());
|
||||||
|
|
||||||
customizeAvailableStore.set(this.isCustomizationAvailable());
|
customizeAvailableStore.set(this.isCustomizationAvailable());
|
||||||
selectCharacterSceneVisibleStore.set(true);
|
selectCharacterSceneVisibleStore.set(true);
|
||||||
this.events.addListener("wake", () => {
|
|
||||||
waScaleManager.saveZoom();
|
|
||||||
waScaleManager.zoomModifier = isMediaBreakpointUp("md") ? 2 : 1;
|
|
||||||
selectCharacterSceneVisibleStore.set(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (touchScreenManager.supportTouchScreen) {
|
if (touchScreenManager.supportTouchScreen) {
|
||||||
new PinchManager(this);
|
new PinchManager(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
waScaleManager.saveZoom();
|
this.charactersDraggableGrid = new DraggableGrid(this, {
|
||||||
waScaleManager.zoomModifier = isMediaBreakpointUp("md") ? 2 : 1;
|
position: { x: 0, y: 0 },
|
||||||
|
maskPosition: { x: 0, y: 0 },
|
||||||
const rectangleXStart = this.game.renderer.width / 2 - (this.nbCharactersPerRow / 2) * 32 + 16;
|
dimension: { x: 485, y: 165 },
|
||||||
this.selectedRectangle = this.add.rectangle(rectangleXStart, 90, 32, 32).setStrokeStyle(2, 0xffffff);
|
horizontal: true,
|
||||||
this.selectedRectangle.setDepth(2);
|
repositionToCenter: true,
|
||||||
|
itemsInRow: 1,
|
||||||
/*create user*/
|
margin: {
|
||||||
this.createCurrentPlayer();
|
left: ((innerWidth - 200) / waScaleManager.getActualZoom()) * 0.5,
|
||||||
|
right: ((innerWidth - 200) / waScaleManager.getActualZoom()) * 0.5,
|
||||||
this.input.keyboard.on("keyup-ENTER", () => {
|
},
|
||||||
return this.nextSceneToCameraScene();
|
spacing: 5,
|
||||||
|
debug: {
|
||||||
|
showDraggableSpace: false,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
this.input.keyboard.on("keydown-RIGHT", () => {
|
this.bindEventHandlers();
|
||||||
this.moveToRight();
|
|
||||||
});
|
this.onResize();
|
||||||
this.input.keyboard.on("keydown-LEFT", () => {
|
|
||||||
this.moveToLeft();
|
|
||||||
});
|
|
||||||
this.input.keyboard.on("keydown-UP", () => {
|
|
||||||
this.moveToUp();
|
|
||||||
});
|
|
||||||
this.input.keyboard.on("keydown-DOWN", () => {
|
|
||||||
this.moveToDown();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public nextSceneToCameraScene(): void {
|
public nextSceneToCameraScene(): void {
|
||||||
if (this.selectedPlayer !== null && !areCharacterLayersValid([this.selectedPlayer.texture.key])) {
|
if (this.selectedWoka !== null && !areCharacterLayersValid([this.selectedWoka.texture.key])) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!this.selectedPlayer) {
|
if (!this.selectedWoka) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
analyticsClient.validationWoka("SelectWoka");
|
analyticsClient.validationWoka("SelectWoka");
|
||||||
|
|
||||||
|
gameManager.setCharacterLayers([this.selectedWoka.texture.key]);
|
||||||
|
this.selectedWoka = null;
|
||||||
this.scene.stop(SelectCharacterSceneName);
|
this.scene.stop(SelectCharacterSceneName);
|
||||||
waScaleManager.restoreZoom();
|
|
||||||
gameManager.setCharacterLayers([this.selectedPlayer.texture.key]);
|
|
||||||
gameManager.tryResumingGame(EnableCameraSceneName);
|
gameManager.tryResumingGame(EnableCameraSceneName);
|
||||||
this.players = [];
|
|
||||||
selectCharacterSceneVisibleStore.set(false);
|
selectCharacterSceneVisibleStore.set(false);
|
||||||
this.events.removeListener("wake");
|
this.events.removeListener("wake");
|
||||||
}
|
}
|
||||||
|
|
||||||
public nextSceneToCustomizeScene(): void {
|
public nextSceneToCustomizeScene(): void {
|
||||||
if (this.selectedPlayer !== null && !areCharacterLayersValid([this.selectedPlayer.texture.key])) {
|
if (this.selectedWoka !== null && !areCharacterLayersValid([this.selectedWoka.texture.key])) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
this.selectedWoka = null;
|
||||||
this.scene.sleep(SelectCharacterSceneName);
|
this.scene.sleep(SelectCharacterSceneName);
|
||||||
waScaleManager.restoreZoom();
|
|
||||||
this.scene.run(CustomizeSceneName);
|
this.scene.run(CustomizeSceneName);
|
||||||
selectCharacterSceneVisibleStore.set(false);
|
selectCharacterSceneVisibleStore.set(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
createCurrentPlayer(): void {
|
public update(): void {
|
||||||
for (let i = 0; i < this.playerModels.length; i++) {
|
|
||||||
const playerResource = this.playerModels[i];
|
|
||||||
//check already exist texture
|
|
||||||
if (this.players.find((c) => c.texture.key === playerResource.id)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const [middleX, middleY] = this.getCharacterPosition();
|
|
||||||
const player = this.physics.add.sprite(middleX, middleY, playerResource.id, 0);
|
|
||||||
this.setUpPlayer(player, i);
|
|
||||||
this.anims.create({
|
|
||||||
key: playerResource.id,
|
|
||||||
frames: this.anims.generateFrameNumbers(playerResource.id, { start: 0, end: 11 }),
|
|
||||||
frameRate: 8,
|
|
||||||
repeat: -1,
|
|
||||||
});
|
|
||||||
player.setInteractive().on("pointerdown", () => {
|
|
||||||
if (this.pointerClicked) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (this.currentSelectUser === i) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
//To not trigger two time the pointerdown events :
|
|
||||||
// We set a boolean to true so that pointerdown events does nothing when the boolean is true
|
|
||||||
// We set a timer that we decrease in update function to not trigger the pointerdown events twice
|
|
||||||
this.pointerClicked = true;
|
|
||||||
this.pointerTimer = 250;
|
|
||||||
this.currentSelectUser = i;
|
|
||||||
this.moveUser();
|
|
||||||
});
|
|
||||||
this.players.push(player);
|
|
||||||
}
|
|
||||||
if (this.currentSelectUser >= this.players.length) {
|
|
||||||
this.currentSelectUser = 0;
|
|
||||||
}
|
|
||||||
this.selectedPlayer = this.players[this.currentSelectUser];
|
|
||||||
this.selectedPlayer.play(this.playerModels[this.currentSelectUser].id);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected moveUser() {
|
|
||||||
for (let i = 0; i < this.players.length; i++) {
|
|
||||||
const player = this.players[i];
|
|
||||||
this.setUpPlayer(player, i);
|
|
||||||
}
|
|
||||||
this.updateSelectedPlayer();
|
|
||||||
}
|
|
||||||
|
|
||||||
public moveToLeft() {
|
|
||||||
if (this.currentSelectUser === 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.currentSelectUser -= 1;
|
|
||||||
this.moveUser();
|
|
||||||
}
|
|
||||||
|
|
||||||
public moveToRight() {
|
|
||||||
if (this.currentSelectUser === this.players.length - 1) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.currentSelectUser += 1;
|
|
||||||
this.moveUser();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected moveToUp() {
|
|
||||||
if (this.currentSelectUser < this.nbCharactersPerRow) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.currentSelectUser -= this.nbCharactersPerRow;
|
|
||||||
this.moveUser();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected moveToDown() {
|
|
||||||
if (this.currentSelectUser + this.nbCharactersPerRow > this.players.length - 1) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.currentSelectUser += this.nbCharactersPerRow;
|
|
||||||
this.moveUser();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected defineSetupPlayer(num: number) {
|
|
||||||
const deltaX = 32;
|
|
||||||
const deltaY = 32;
|
|
||||||
let [playerX, playerY] = this.getCharacterPosition(); // player X and player y are middle of the
|
|
||||||
|
|
||||||
playerX = playerX - deltaX * 2.5 + deltaX * (num % this.nbCharactersPerRow); // calcul position on line users
|
|
||||||
playerY = playerY - deltaY * 2 + deltaY * Math.floor(num / this.nbCharactersPerRow); // calcul position on column users
|
|
||||||
|
|
||||||
const playerVisible = true;
|
|
||||||
const playerScale = 1;
|
|
||||||
const playerOpacity = 1;
|
|
||||||
|
|
||||||
// if selected
|
|
||||||
if (num === this.currentSelectUser) {
|
|
||||||
this.selectedRectangle.setX(playerX);
|
|
||||||
this.selectedRectangle.setY(playerY);
|
|
||||||
}
|
|
||||||
|
|
||||||
return { playerX, playerY, playerScale, playerOpacity, playerVisible };
|
|
||||||
}
|
|
||||||
|
|
||||||
protected setUpPlayer(player: Phaser.Physics.Arcade.Sprite, num: number) {
|
|
||||||
const { playerX, playerY, playerScale, playerOpacity, playerVisible } = this.defineSetupPlayer(num);
|
|
||||||
player.setBounce(0.2);
|
|
||||||
player.setCollideWorldBounds(false);
|
|
||||||
player.setVisible(playerVisible);
|
|
||||||
player.setScale(playerScale, playerScale);
|
|
||||||
player.setAlpha(playerOpacity);
|
|
||||||
player.setX(playerX);
|
|
||||||
player.setY(playerY);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns pixel position by on column and row number
|
|
||||||
*/
|
|
||||||
protected getCharacterPosition(): [number, number] {
|
|
||||||
return [this.game.renderer.width / 2, this.game.renderer.height / 2.5];
|
|
||||||
}
|
|
||||||
|
|
||||||
protected updateSelectedPlayer(): void {
|
|
||||||
this.selectedPlayer?.anims?.pause(this.selectedPlayer?.anims.currentAnim.frames[0]);
|
|
||||||
const player = this.players[this.currentSelectUser];
|
|
||||||
player?.play(this.playerModels[this.currentSelectUser].id);
|
|
||||||
this.selectedPlayer = player;
|
|
||||||
localUserStore.setPlayerCharacterIndex(this.currentSelectUser);
|
|
||||||
}
|
|
||||||
|
|
||||||
update(time: number, delta: number): void {
|
|
||||||
// pointerTimer is set to 250 when pointerdown events is trigger
|
|
||||||
// After 250ms, pointerClicked is set to false and the pointerdown events can be trigger again
|
|
||||||
this.pointerTimer -= delta;
|
|
||||||
if (this.pointerTimer <= 0) {
|
|
||||||
this.pointerClicked = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.lazyloadingAttempt) {
|
if (this.lazyloadingAttempt) {
|
||||||
//re-render players list
|
|
||||||
this.createCurrentPlayer();
|
|
||||||
this.moveUser();
|
|
||||||
this.lazyloadingAttempt = false;
|
this.lazyloadingAttempt = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public onResize(): void {
|
public onResize(): void {
|
||||||
//move position of user
|
this.handleCharactersGridOnResize();
|
||||||
this.moveUser();
|
}
|
||||||
|
|
||||||
|
public getSelectedCollectionName(): string {
|
||||||
|
return this.collectionKeys[this.selectedCollectionIndex] ?? "";
|
||||||
|
}
|
||||||
|
|
||||||
|
public getCollectionKeysSize(): number {
|
||||||
|
return this.playerTextures.getCollectionsKeys().length;
|
||||||
|
}
|
||||||
|
|
||||||
|
public selectPreviousCollection(): void {
|
||||||
|
this.selectedCollectionIndex = (this.selectedCollectionIndex + 1) % this.collectionKeys.length;
|
||||||
|
selectedCollection.set(this.getSelectedCollectionName());
|
||||||
|
this.populateGrid();
|
||||||
|
}
|
||||||
|
|
||||||
|
public selectNextCollection(): void {
|
||||||
|
if (this.collectionKeys.length === 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.selectedCollectionIndex =
|
||||||
|
this.selectedCollectionIndex - 1 < 0 ? this.collectionKeys.length - 1 : this.selectedCollectionIndex - 1;
|
||||||
|
selectedCollection.set(this.getSelectedCollectionName());
|
||||||
|
this.populateGrid();
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleCharactersGridOnResize(): void {
|
||||||
|
const ratio = innerHeight / innerWidth;
|
||||||
|
this.gridRowsCount = ratio > 1 || innerHeight > 900 ? 2 : 1;
|
||||||
|
const gridHeight = this.gridRowsCount === 2 ? 210 : 105;
|
||||||
|
const gridWidth = innerWidth / waScaleManager.getActualZoom();
|
||||||
|
const gridPos = {
|
||||||
|
x: this.cameras.main.worldView.x + this.cameras.main.width / 2,
|
||||||
|
y: this.cameras.main.worldView.y + this.cameras.main.height * (ratio > 1 ? 0.5 : 0.575),
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.charactersDraggableGrid.changeDraggableSpacePosAndSize(
|
||||||
|
gridPos,
|
||||||
|
{ x: gridWidth, y: gridHeight },
|
||||||
|
gridPos
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
console.warn(error);
|
||||||
|
}
|
||||||
|
this.charactersDraggableGrid.setItemsInRow(this.gridRowsCount);
|
||||||
|
this.populateGrid();
|
||||||
|
}
|
||||||
|
|
||||||
|
private populateGrid(): void {
|
||||||
|
const wokaDimension = 100;
|
||||||
|
|
||||||
|
this.selectedWoka = null;
|
||||||
|
this.charactersDraggableGrid.clearAllItems();
|
||||||
|
const textures = this.playerTextures.getWokaCollectionTextures(this.getSelectedCollectionName());
|
||||||
|
for (let i = 0; i < textures.length; i += 1) {
|
||||||
|
const slot = new WokaSlot(this, textures[i].id).setDisplaySize(wokaDimension, wokaDimension);
|
||||||
|
this.charactersDraggableGrid.addItem(slot);
|
||||||
|
}
|
||||||
|
this.charactersDraggableGrid.moveContentToBeginning();
|
||||||
|
void this.charactersDraggableGrid.moveContentTo(0.5, textures.length * 50);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bindEventHandlers(): void {
|
||||||
|
this.bindKeyboardEventHandlers();
|
||||||
|
this.events.addListener("wake", () => {
|
||||||
|
selectCharacterSceneVisibleStore.set(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.input.keyboard.on("keyup-ENTER", () => {
|
||||||
|
return this.nextSceneToCameraScene();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.charactersDraggableGrid.on(DraggableGridEvent.ItemClicked, (item: WokaSlot) => {
|
||||||
|
this.selectGridItem(item);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private selectGridItem(item: WokaSlot): void {
|
||||||
|
this.selectedGridItemIndex = this.charactersDraggableGrid.getAllItems().indexOf(item);
|
||||||
|
if (this.charactersDraggableGrid.getDraggableSpaceWidth() < this.charactersDraggableGrid.getGridSize().x) {
|
||||||
|
void this.charactersDraggableGrid.centerOnItem(this.selectedGridItemIndex, 500);
|
||||||
|
}
|
||||||
|
this.charactersDraggableGrid.getAllItems().forEach((slot) => (slot as WokaSlot).select(false));
|
||||||
|
this.selectedWoka?.stop()?.setFrame(0);
|
||||||
|
this.selectedWoka = item.getSprite();
|
||||||
|
const wokaKey = this.selectedWoka.texture.key;
|
||||||
|
this.createWokaAnimation(wokaKey);
|
||||||
|
this.selectedWoka.play(wokaKey);
|
||||||
|
item.select(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bindKeyboardEventHandlers(): void {
|
||||||
|
this.input.keyboard.on("keyup-SPACE", () => {
|
||||||
|
this.selectNextCollection();
|
||||||
|
});
|
||||||
|
this.input.keyboard.on("keydown-LEFT", () => {
|
||||||
|
this.selectNextGridItem(true, true);
|
||||||
|
});
|
||||||
|
this.input.keyboard.on("keydown-RIGHT", () => {
|
||||||
|
this.selectNextGridItem(false, true);
|
||||||
|
});
|
||||||
|
this.input.keyboard.on("keydown-UP", () => {
|
||||||
|
this.selectNextGridItem(true, false);
|
||||||
|
});
|
||||||
|
this.input.keyboard.on("keydown-DOWN", () => {
|
||||||
|
this.selectNextGridItem(false, false);
|
||||||
|
});
|
||||||
|
this.input.keyboard.on("keydown-W", () => {
|
||||||
|
this.selectNextGridItem(true, false);
|
||||||
|
});
|
||||||
|
this.input.keyboard.on("keydown-S", () => {
|
||||||
|
this.selectNextGridItem(false, false);
|
||||||
|
});
|
||||||
|
this.input.keyboard.on("keydown-A", () => {
|
||||||
|
this.selectNextGridItem(true, true);
|
||||||
|
});
|
||||||
|
this.input.keyboard.on("keydown-D", () => {
|
||||||
|
this.selectNextGridItem(false, true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private selectNextGridItem(previous: boolean = false, horizontally: boolean): void {
|
||||||
|
if (this.selectedGridItemIndex === undefined) {
|
||||||
|
this.selectedGridItemIndex = 0;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
previous
|
||||||
|
? this.selectedGridItemIndex > 0
|
||||||
|
: this.selectedGridItemIndex < this.charactersDraggableGrid.getAllItems().length - 1
|
||||||
|
) {
|
||||||
|
// NOTE: getItemsInRowCount() not working properly. Fix on lib side needed
|
||||||
|
const jump = horizontally ? this.gridRowsCount : 1;
|
||||||
|
const item = this.charactersDraggableGrid.getAllItems()[
|
||||||
|
this.selectedGridItemIndex + (previous ? -jump : jump)
|
||||||
|
] as WokaSlot;
|
||||||
|
if (!item) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.selectedGridItemIndex += previous ? -1 : 1;
|
||||||
|
this.selectGridItem(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private createWokaAnimation(key: string): void {
|
||||||
|
this.anims.create({
|
||||||
|
key,
|
||||||
|
frames: this.anims.generateFrameNumbers(key, { start: 0, end: 11 }),
|
||||||
|
frameRate: 8,
|
||||||
|
repeat: -1,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private isCustomizationAvailable(): boolean {
|
private isCustomizationAvailable(): boolean {
|
||||||
|
@ -3,7 +3,6 @@ import Image = Phaser.GameObjects.Image;
|
|||||||
import Sprite = Phaser.GameObjects.Sprite;
|
import Sprite = Phaser.GameObjects.Sprite;
|
||||||
import Text = Phaser.GameObjects.Text;
|
import Text = Phaser.GameObjects.Text;
|
||||||
import ScenePlugin = Phaser.Scenes.ScenePlugin;
|
import ScenePlugin = Phaser.Scenes.ScenePlugin;
|
||||||
import { WAError } from "./WAError";
|
|
||||||
import Axios from "axios";
|
import Axios from "axios";
|
||||||
|
|
||||||
export const ErrorSceneName = "ErrorScene";
|
export const ErrorSceneName = "ErrorScene";
|
||||||
@ -88,12 +87,6 @@ export class ErrorScene extends Phaser.Scene {
|
|||||||
title: "An error occurred",
|
title: "An error occurred",
|
||||||
subTitle: error,
|
subTitle: error,
|
||||||
});
|
});
|
||||||
} else if (error instanceof WAError) {
|
|
||||||
scene.start(ErrorSceneName, {
|
|
||||||
title: error.title,
|
|
||||||
subTitle: error.subTitle,
|
|
||||||
message: error.details,
|
|
||||||
});
|
|
||||||
} else if (Axios.isAxiosError(error) && error.response) {
|
} else if (Axios.isAxiosError(error) && error.response) {
|
||||||
// Axios HTTP error
|
// Axios HTTP error
|
||||||
// client received an error response (5xx, 4xx)
|
// client received an error response (5xx, 4xx)
|
||||||
|
@ -1,26 +1,65 @@
|
|||||||
export class WAError extends Error {
|
export class WAError extends Error {
|
||||||
|
private _type: string;
|
||||||
|
private _code: string;
|
||||||
private _title: string;
|
private _title: string;
|
||||||
private _subTitle: string;
|
private _subtitle: string;
|
||||||
private _details: string;
|
private _details: string;
|
||||||
|
private _timeToRetry: number;
|
||||||
|
private _canRetryManual: boolean;
|
||||||
|
private _urlToRedirect: string;
|
||||||
|
private _buttonTitle: string;
|
||||||
|
|
||||||
constructor(title: string, subTitle: string, details: string) {
|
constructor(
|
||||||
super(title + " - " + subTitle + " - " + details);
|
type: string,
|
||||||
|
code: string,
|
||||||
|
title: string,
|
||||||
|
subtitle: string,
|
||||||
|
details: string,
|
||||||
|
timeToRetry: number,
|
||||||
|
canRetryManual: boolean,
|
||||||
|
urlToRedirect: string,
|
||||||
|
buttonTitle: string
|
||||||
|
) {
|
||||||
|
super(title + " - " + subtitle + " - " + details);
|
||||||
|
|
||||||
|
this._type = type;
|
||||||
|
this._code = code;
|
||||||
this._title = title;
|
this._title = title;
|
||||||
this._subTitle = subTitle;
|
this._subtitle = subtitle;
|
||||||
this._details = details;
|
this._details = details;
|
||||||
|
this._timeToRetry = timeToRetry;
|
||||||
|
this._canRetryManual = canRetryManual;
|
||||||
|
this._urlToRedirect = urlToRedirect;
|
||||||
|
this._buttonTitle = buttonTitle;
|
||||||
// Set the prototype explicitly.
|
// Set the prototype explicitly.
|
||||||
Object.setPrototypeOf(this, WAError.prototype);
|
Object.setPrototypeOf(this, WAError.prototype);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get type(): string {
|
||||||
|
return this._type;
|
||||||
|
}
|
||||||
|
get code(): string {
|
||||||
|
return this._code;
|
||||||
|
}
|
||||||
get title(): string {
|
get title(): string {
|
||||||
return this._title;
|
return this._title;
|
||||||
}
|
}
|
||||||
|
get subtitle(): string {
|
||||||
get subTitle(): string {
|
return this._subtitle;
|
||||||
return this._subTitle;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get details(): string {
|
get details(): string {
|
||||||
return this._details;
|
return this._details;
|
||||||
}
|
}
|
||||||
|
get timeToRetry(): number {
|
||||||
|
return this._timeToRetry;
|
||||||
|
}
|
||||||
|
get buttonTitle(): string {
|
||||||
|
return this._buttonTitle;
|
||||||
|
}
|
||||||
|
get urlToRedirect(): string {
|
||||||
|
return this._urlToRedirect;
|
||||||
|
}
|
||||||
|
get canRetryManual(): boolean {
|
||||||
|
return this._canRetryManual;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,6 @@ export class GameSceneUserInputHandler implements UserInputHandlerInterface {
|
|||||||
gameObjects: Phaser.GameObjects.GameObject[],
|
gameObjects: Phaser.GameObjects.GameObject[],
|
||||||
deltaX: number,
|
deltaX: number,
|
||||||
deltaY: number,
|
deltaY: number,
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
||||||
deltaZ: number
|
deltaZ: number
|
||||||
): void {
|
): void {
|
||||||
this.gameScene.zoomByFactor(1 - (deltaY / 53) * 0.1);
|
this.gameScene.zoomByFactor(1 - (deltaY / 53) * 0.1);
|
||||||
@ -68,7 +67,7 @@ export class GameSceneUserInputHandler implements UserInputHandlerInterface {
|
|||||||
this.lastY = pointer.y;
|
this.lastY = pointer.y;
|
||||||
}
|
}
|
||||||
|
|
||||||
public handlePointerDownEvent(pointer: Phaser.Input.Pointer, gameObjects: Phaser.GameObjects.GameObject[]): void { }
|
public handlePointerDownEvent(pointer: Phaser.Input.Pointer, gameObjects: Phaser.GameObjects.GameObject[]): void {}
|
||||||
|
|
||||||
public handleSpaceKeyUpEvent(event: Event): Event {
|
public handleSpaceKeyUpEvent(event: Event): Event {
|
||||||
const activatableManager = this.gameScene.getActivatablesManager();
|
const activatableManager = this.gameScene.getActivatablesManager();
|
||||||
|
18
front/src/Stores/ErrorScreenStore.ts
Normal file
18
front/src/Stores/ErrorScreenStore.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import { writable } from "svelte/store";
|
||||||
|
import { ErrorScreenMessage } from "../Messages/ts-proto-generated/protos/messages";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A store that contains one error of type WAError to be displayed.
|
||||||
|
*/
|
||||||
|
function createErrorScreenStore() {
|
||||||
|
const { subscribe, set } = writable<ErrorScreenMessage>(undefined);
|
||||||
|
|
||||||
|
return {
|
||||||
|
subscribe,
|
||||||
|
setError: (e: ErrorScreenMessage): void => {
|
||||||
|
set(e);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const errorScreenStore = createErrorScreenStore();
|
@ -2,6 +2,7 @@ import { derived, Readable, readable, writable } from "svelte/store";
|
|||||||
import { peerStore } from "./PeerStore";
|
import { peerStore } from "./PeerStore";
|
||||||
import type { LocalStreamStoreValue } from "./MediaStore";
|
import type { LocalStreamStoreValue } from "./MediaStore";
|
||||||
import { myCameraVisibilityStore } from "./MyCameraStoreVisibility";
|
import { myCameraVisibilityStore } from "./MyCameraStoreVisibility";
|
||||||
|
import type { DesktopCapturerSource } from "@wa-preload-app";
|
||||||
|
|
||||||
declare const navigator: any; // eslint-disable-line @typescript-eslint/no-explicit-any
|
declare const navigator: any; // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||||
|
|
||||||
@ -91,6 +92,25 @@ export const screenSharingConstraintsStore = derived(
|
|||||||
} as MediaStreamConstraints
|
} as MediaStreamConstraints
|
||||||
);
|
);
|
||||||
|
|
||||||
|
async function getDesktopCapturerSources() {
|
||||||
|
showDesktopCapturerSourcePicker.set(true);
|
||||||
|
const source = await new Promise<DesktopCapturerSource | null>((resolve) => {
|
||||||
|
desktopCapturerSourcePromiseResolve = resolve;
|
||||||
|
});
|
||||||
|
if (source === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return navigator.mediaDevices.getUserMedia({
|
||||||
|
audio: false,
|
||||||
|
video: {
|
||||||
|
mandatory: {
|
||||||
|
chromeMediaSource: "desktop",
|
||||||
|
chromeMediaSourceId: source.id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A store containing the MediaStream object for ScreenSharing (or null if nothing requested, or Error if an error occurred)
|
* A store containing the MediaStream object for ScreenSharing (or null if nothing requested, or Error if an error occurred)
|
||||||
*/
|
*/
|
||||||
@ -110,7 +130,9 @@ export const screenSharingLocalStreamStore = derived<Readable<MediaStreamConstra
|
|||||||
}
|
}
|
||||||
|
|
||||||
let currentStreamPromise: Promise<MediaStream>;
|
let currentStreamPromise: Promise<MediaStream>;
|
||||||
if (navigator.getDisplayMedia) {
|
if (window.WAD?.getDesktopCapturerSources) {
|
||||||
|
currentStreamPromise = getDesktopCapturerSources();
|
||||||
|
} else if (navigator.getDisplayMedia) {
|
||||||
currentStreamPromise = navigator.getDisplayMedia({ constraints });
|
currentStreamPromise = navigator.getDisplayMedia({ constraints });
|
||||||
} else if (navigator.mediaDevices && navigator.mediaDevices.getDisplayMedia) {
|
} else if (navigator.mediaDevices && navigator.mediaDevices.getDisplayMedia) {
|
||||||
currentStreamPromise = navigator.mediaDevices.getDisplayMedia({ constraints });
|
currentStreamPromise = navigator.mediaDevices.getDisplayMedia({ constraints });
|
||||||
@ -200,3 +222,7 @@ export const screenSharingLocalMedia = readable<ScreenSharingLocalMedia | null>(
|
|||||||
unsubscribe();
|
unsubscribe();
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const showDesktopCapturerSourcePicker = writable(false);
|
||||||
|
|
||||||
|
export let desktopCapturerSourcePromiseResolve: ((source: DesktopCapturerSource | null) => void) | undefined;
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
import { writable } from "svelte/store";
|
import { writable } from "svelte/store";
|
||||||
|
|
||||||
export const customizeAvailableStore = writable(false);
|
export const customizeAvailableStore = writable(false);
|
||||||
|
|
||||||
|
export const selectedCollection = writable<string>();
|
||||||
|
@ -14,15 +14,6 @@ export function getColorRgbFromHue(hue: number): { r: number; g: number; b: numb
|
|||||||
return hsv_to_rgb(hue, 0.5, 0.95);
|
return hsv_to_rgb(hue, 0.5, 0.95);
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
||||||
function stringToDouble(string: string): number {
|
|
||||||
let num = 1;
|
|
||||||
for (const char of string.split("")) {
|
|
||||||
num *= char.charCodeAt(0);
|
|
||||||
}
|
|
||||||
return (num % 255) / 255;
|
|
||||||
}
|
|
||||||
|
|
||||||
//todo: test this.
|
//todo: test this.
|
||||||
function hsv_to_rgb(hue: number, saturation: number, brightness: number): { r: number; g: number; b: number } {
|
function hsv_to_rgb(hue: number, saturation: number, brightness: number): { r: number; g: number; b: number } {
|
||||||
const h_i = Math.floor(hue * 6);
|
const h_i = Math.floor(hue * 6);
|
||||||
|
@ -17,14 +17,12 @@ import { localUserStore } from "./Connexion/LocalUserStore";
|
|||||||
import { ErrorScene } from "./Phaser/Reconnecting/ErrorScene";
|
import { ErrorScene } from "./Phaser/Reconnecting/ErrorScene";
|
||||||
import { iframeListener } from "./Api/IframeListener";
|
import { iframeListener } from "./Api/IframeListener";
|
||||||
import { desktopApi } from "./Api/desktop/index";
|
import { desktopApi } from "./Api/desktop/index";
|
||||||
import { SelectCharacterMobileScene } from "./Phaser/Login/SelectCharacterMobileScene";
|
|
||||||
import { HdpiManager } from "./Phaser/Services/HdpiManager";
|
import { HdpiManager } from "./Phaser/Services/HdpiManager";
|
||||||
import { waScaleManager } from "./Phaser/Services/WaScaleManager";
|
import { waScaleManager } from "./Phaser/Services/WaScaleManager";
|
||||||
import { Game } from "./Phaser/Game/Game";
|
import { Game } from "./Phaser/Game/Game";
|
||||||
import App from "./Components/App.svelte";
|
import App from "./Components/App.svelte";
|
||||||
import { HtmlUtils } from "./WebRtc/HtmlUtils";
|
import { HtmlUtils } from "./WebRtc/HtmlUtils";
|
||||||
import WebGLRenderer = Phaser.Renderer.WebGL.WebGLRenderer;
|
import WebGLRenderer = Phaser.Renderer.WebGL.WebGLRenderer;
|
||||||
import { isMediaBreakpointUp } from "./Utils/BreakpointsUtils";
|
|
||||||
|
|
||||||
const { width, height } = coWebsiteManager.getGameSize();
|
const { width, height } = coWebsiteManager.getGameSize();
|
||||||
const valueGameQuality = localUserStore.getGameQualityValue();
|
const valueGameQuality = localUserStore.getGameQualityValue();
|
||||||
@ -91,7 +89,7 @@ const config: GameConfig = {
|
|||||||
scene: [
|
scene: [
|
||||||
EntryScene,
|
EntryScene,
|
||||||
LoginScene,
|
LoginScene,
|
||||||
isMediaBreakpointUp("md") ? SelectCharacterMobileScene : SelectCharacterScene,
|
SelectCharacterScene,
|
||||||
SelectCompanionScene,
|
SelectCompanionScene,
|
||||||
EnableCameraScene,
|
EnableCameraScene,
|
||||||
ReconnectingScene,
|
ReconnectingScene,
|
||||||
|
@ -806,10 +806,10 @@ debug@~3.1.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
ms "2.0.0"
|
ms "2.0.0"
|
||||||
|
|
||||||
deep-copy-ts@^0.5.0:
|
deep-copy-ts@^0.5.4:
|
||||||
version "0.5.0"
|
version "0.5.4"
|
||||||
resolved "https://registry.yarnpkg.com/deep-copy-ts/-/deep-copy-ts-0.5.0.tgz#b9493d8e2bae85ef7d659c16eb707c13efb84499"
|
resolved "https://registry.yarnpkg.com/deep-copy-ts/-/deep-copy-ts-0.5.4.tgz#e81b15797e4075cb3a690a1a7ac30179f2d72562"
|
||||||
integrity sha512-/3cgBcMkznRf5BM8wu6YWz3SQUkHzgh/v1TZFjevztLj9sMjFvNFBtpN4uUtPzw/rA/TldyD6c6LRL1zno4+YA==
|
integrity sha512-YJbPjw0YqdosorpCsa6copy1p/gJsFT9Q6Zq0tLi7D0nXh6Y/usjeIQZfkzV3HVuqY0Hl/5gM7TwgIbIWvEjlA==
|
||||||
|
|
||||||
deep-equal@^1.0.1:
|
deep-equal@^1.0.1:
|
||||||
version "1.1.1"
|
version "1.1.1"
|
||||||
|
46
messages/JsonMessages/ErrorApiData.ts
Normal file
46
messages/JsonMessages/ErrorApiData.ts
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
/*
|
||||||
|
* WARNING! The original file is in /messages/JsonMessages.
|
||||||
|
* All other files are automatically copied from this file on container startup / build
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const isErrorApiErrorData = z.object({
|
||||||
|
// @ts-ignore
|
||||||
|
type: z.literal("error"),
|
||||||
|
code: z.string(),
|
||||||
|
title: z.string(),
|
||||||
|
subtitle: z.string(),
|
||||||
|
details: z.string(),
|
||||||
|
image: z.string(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const isErrorApiRetryData = z.object({
|
||||||
|
type: z.literal("retry"),
|
||||||
|
code: z.string(),
|
||||||
|
title: z.string(),
|
||||||
|
subtitle: z.string(),
|
||||||
|
details: z.string(),
|
||||||
|
image: z.string(),
|
||||||
|
buttonTitle: z.optional(z.nullable(z.string())),
|
||||||
|
timeToRetry: z.number(),
|
||||||
|
canRetryManual: z.boolean(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const isErrorApiRedirectData = z.object({
|
||||||
|
type: z.literal("redirect"),
|
||||||
|
urlToRedirect: z.string(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const isErrorApiUnauthorizedData = z.object({
|
||||||
|
type: z.literal("unauthorized"),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const isErrorApiData = z.discriminatedUnion("type", [
|
||||||
|
isErrorApiErrorData,
|
||||||
|
isErrorApiRetryData,
|
||||||
|
isErrorApiRedirectData,
|
||||||
|
isErrorApiUnauthorizedData,
|
||||||
|
]);
|
||||||
|
|
||||||
|
export type ErrorApiData = z.infer<typeof isErrorApiData>;
|
@ -7,13 +7,10 @@ import { z } from "zod";
|
|||||||
|
|
||||||
export const isMapDetailsData = z.object({
|
export const isMapDetailsData = z.object({
|
||||||
mapUrl: z.string(),
|
mapUrl: z.string(),
|
||||||
policy_type: z.number(),
|
|
||||||
tags: z.array(z.string()),
|
|
||||||
authenticationMandatory: z.optional(z.nullable(z.boolean())),
|
authenticationMandatory: z.optional(z.nullable(z.boolean())),
|
||||||
roomSlug: z.nullable(z.string()), // deprecated
|
|
||||||
contactPage: z.nullable(z.string()),
|
|
||||||
group: z.nullable(z.string()),
|
group: z.nullable(z.string()),
|
||||||
|
|
||||||
|
contactPage: z.optional(z.nullable(z.string())),
|
||||||
iframeAuthentication: z.optional(z.nullable(z.string())),
|
iframeAuthentication: z.optional(z.nullable(z.string())),
|
||||||
// The date (in ISO 8601 format) at which the room will expire
|
// The date (in ISO 8601 format) at which the room will expire
|
||||||
expireOn: z.optional(z.string()),
|
expireOn: z.optional(z.string()),
|
||||||
|
@ -217,10 +217,29 @@ message UserLeftMessage {
|
|||||||
int32 userId = 1;
|
int32 userId = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ErrorMessage is only used to console.error the message in the front
|
||||||
|
*/
|
||||||
message ErrorMessage {
|
message ErrorMessage {
|
||||||
string message = 1;
|
string message = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ErrorScreenMessage is used to show the ErrorScreen in the front
|
||||||
|
*/
|
||||||
|
message ErrorScreenMessage {
|
||||||
|
string type = 1;
|
||||||
|
google.protobuf.StringValue code = 2;
|
||||||
|
google.protobuf.StringValue title = 3;
|
||||||
|
google.protobuf.StringValue subtitle = 4;
|
||||||
|
google.protobuf.StringValue details = 5;
|
||||||
|
google.protobuf.Int32Value timeToRetry = 6;
|
||||||
|
google.protobuf.BoolValue canRetryManual = 7;
|
||||||
|
google.protobuf.StringValue urlToRedirect = 8;
|
||||||
|
google.protobuf.StringValue buttonTitle = 9;
|
||||||
|
google.protobuf.StringValue image = 10;
|
||||||
|
}
|
||||||
|
|
||||||
message ItemStateMessage {
|
message ItemStateMessage {
|
||||||
int32 itemId = 1;
|
int32 itemId = 1;
|
||||||
string stateJson = 2;
|
string stateJson = 2;
|
||||||
@ -332,6 +351,7 @@ message ServerToClientMessage {
|
|||||||
FollowAbortMessage followAbortMessage = 23;
|
FollowAbortMessage followAbortMessage = 23;
|
||||||
InvalidTextureMessage invalidTextureMessage = 24;
|
InvalidTextureMessage invalidTextureMessage = 24;
|
||||||
GroupUsersUpdateMessage groupUsersUpdateMessage = 25;
|
GroupUsersUpdateMessage groupUsersUpdateMessage = 25;
|
||||||
|
ErrorScreenMessage errorScreenMessage = 26;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,5 +4,6 @@
|
|||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"prepare": "husky install"
|
"prepare": "husky install"
|
||||||
}
|
},
|
||||||
|
"dependencies": {}
|
||||||
}
|
}
|
||||||
|
@ -27,8 +27,8 @@
|
|||||||
"no-unused-vars": "off",
|
"no-unused-vars": "off",
|
||||||
"@typescript-eslint/no-explicit-any": "error",
|
"@typescript-eslint/no-explicit-any": "error",
|
||||||
"@typescript-eslint/no-unused-vars": [
|
"@typescript-eslint/no-unused-vars": [
|
||||||
"error"
|
"error", { "args": "none", "caughtErrors": "all", "varsIgnorePattern": "_exhaustiveCheck" }
|
||||||
],
|
],
|
||||||
"no-throw-literal": "error"
|
"no-throw-literal": "error"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -40,6 +40,7 @@
|
|||||||
},
|
},
|
||||||
"homepage": "https://github.com/thecodingmachine/workadventure#readme",
|
"homepage": "https://github.com/thecodingmachine/workadventure#readme",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@anatine/zod-openapi": "^1.3.0",
|
||||||
"axios": "^0.21.2",
|
"axios": "^0.21.2",
|
||||||
"circular-json": "^0.5.9",
|
"circular-json": "^0.5.9",
|
||||||
"debug": "^4.3.1",
|
"debug": "^4.3.1",
|
||||||
@ -48,6 +49,7 @@
|
|||||||
"hyper-express": "^5.8.1",
|
"hyper-express": "^5.8.1",
|
||||||
"jsonwebtoken": "^8.5.1",
|
"jsonwebtoken": "^8.5.1",
|
||||||
"mkdirp": "^1.0.4",
|
"mkdirp": "^1.0.4",
|
||||||
|
"openapi3-ts": "^2.0.2",
|
||||||
"openid-client": "^4.7.4",
|
"openid-client": "^4.7.4",
|
||||||
"prom-client": "^12.0.0",
|
"prom-client": "^12.0.0",
|
||||||
"qs": "^6.10.3",
|
"qs": "^6.10.3",
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { v4 } from "uuid";
|
import { v4 } from "uuid";
|
||||||
import { BaseHttpController } from "./BaseHttpController";
|
import { BaseHttpController } from "./BaseHttpController";
|
||||||
import { adminApi, FetchMemberDataByUuidResponse } from "../Services/AdminApi";
|
import { FetchMemberDataByUuidResponse } from "../Services/AdminApi";
|
||||||
import { AuthTokenData, jwtTokenManager } from "../Services/JWTTokenManager";
|
import { AuthTokenData, jwtTokenManager } from "../Services/JWTTokenManager";
|
||||||
import { parse } from "query-string";
|
import { parse } from "query-string";
|
||||||
import { openIDClient } from "../Services/OpenIDClient";
|
import { openIDClient } from "../Services/OpenIDClient";
|
||||||
@ -172,7 +172,8 @@ export class AuthenticateController extends BaseHttpController {
|
|||||||
authTokenData.identifier,
|
authTokenData.identifier,
|
||||||
playUri as string,
|
playUri as string,
|
||||||
IPAddress,
|
IPAddress,
|
||||||
[]
|
[],
|
||||||
|
req.header("accept-language")
|
||||||
);
|
);
|
||||||
|
|
||||||
if (authTokenData.accessToken == undefined) {
|
if (authTokenData.accessToken == undefined) {
|
||||||
@ -224,7 +225,13 @@ export class AuthenticateController extends BaseHttpController {
|
|||||||
|
|
||||||
//Get user data from Admin Back Office
|
//Get user data from Admin Back Office
|
||||||
//This is very important to create User Local in LocalStorage in WorkAdventure
|
//This is very important to create User Local in LocalStorage in WorkAdventure
|
||||||
const data = await adminService.fetchMemberDataByUuid(sub, playUri as string, IPAddress, []);
|
const data = await adminService.fetchMemberDataByUuid(
|
||||||
|
sub,
|
||||||
|
playUri as string,
|
||||||
|
IPAddress,
|
||||||
|
[],
|
||||||
|
req.header("accept-language")
|
||||||
|
);
|
||||||
|
|
||||||
return res.json({ ...data, authToken, username: userInfo?.username, locale: userInfo?.locale, userUuid : sub });
|
return res.json({ ...data, authToken, username: userInfo?.username, locale: userInfo?.locale, userUuid : sub });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -327,13 +334,18 @@ export class AuthenticateController extends BaseHttpController {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
if (typeof organizationMemberToken != "string") throw new Error("No organization token");
|
if (typeof organizationMemberToken != "string") throw new Error("No organization token");
|
||||||
const data = await adminApi.fetchMemberDataByToken(organizationMemberToken, playUri);
|
const data = await adminService.fetchMemberDataByToken(
|
||||||
|
organizationMemberToken,
|
||||||
|
playUri,
|
||||||
|
req.header("accept-language")
|
||||||
|
);
|
||||||
const userUuid = data.userUuid;
|
const userUuid = data.userUuid;
|
||||||
const email = data.email;
|
const email = data.email;
|
||||||
const roomUrl = data.roomUrl;
|
const roomUrl = data.roomUrl;
|
||||||
const mapUrlStart = data.mapUrlStart;
|
const mapUrlStart = data.mapUrlStart;
|
||||||
|
|
||||||
const authToken = jwtTokenManager.createAuthToken(email || userUuid);
|
const authToken = jwtTokenManager.createAuthToken(email || userUuid);
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
authToken,
|
authToken,
|
||||||
userUuid,
|
userUuid,
|
||||||
@ -419,7 +431,7 @@ export class AuthenticateController extends BaseHttpController {
|
|||||||
|
|
||||||
//get login profile
|
//get login profile
|
||||||
res.status(302);
|
res.status(302);
|
||||||
res.setHeader("Location", adminApi.getProfileUrl(authTokenData.accessToken));
|
res.setHeader("Location", adminService.getProfileUrl(authTokenData.accessToken));
|
||||||
res.send("");
|
res.send("");
|
||||||
return;
|
return;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -485,7 +497,8 @@ export class AuthenticateController extends BaseHttpController {
|
|||||||
* @param email
|
* @param email
|
||||||
* @param playUri
|
* @param playUri
|
||||||
* @param IPAddress
|
* @param IPAddress
|
||||||
* @return FetchMemberDataByUuidResponse|object
|
* @return
|
||||||
|
|object
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
private async getUserByUserIdentifier(
|
private async getUserByUserIdentifier(
|
||||||
@ -503,7 +516,7 @@ export class AuthenticateController extends BaseHttpController {
|
|||||||
userRoomToken: undefined,
|
userRoomToken: undefined,
|
||||||
};
|
};
|
||||||
try {
|
try {
|
||||||
data = await adminApi.fetchMemberDataByUuid(email, playUri, IPAddress, []);
|
data = await adminService.fetchMemberDataByUuid(email, playUri, IPAddress, []);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("openIDCallback => fetchMemberDataByUuid", err);
|
console.error("openIDCallback => fetchMemberDataByUuid", err);
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { Server } from "hyper-express";
|
import { Server } from "hyper-express";
|
||||||
import Response from "hyper-express/types/components/http/Response";
|
import Response from "hyper-express/types/components/http/Response";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
|
import { isErrorApiData } from "../Messages/JsonMessages/ErrorApiData";
|
||||||
|
|
||||||
export class BaseHttpController {
|
export class BaseHttpController {
|
||||||
constructor(protected app: Server) {
|
constructor(protected app: Server) {
|
||||||
@ -31,12 +32,15 @@ export class BaseHttpController {
|
|||||||
|
|
||||||
if (axios.isAxiosError(e) && e.response) {
|
if (axios.isAxiosError(e) && e.response) {
|
||||||
res.status(e.response.status);
|
res.status(e.response.status);
|
||||||
res.send(
|
const errorType = isErrorApiData.safeParse(e?.response?.data);
|
||||||
"An error occurred: " +
|
if (!errorType.success) {
|
||||||
e.response.status +
|
res.send(
|
||||||
" " +
|
"An error occurred: " +
|
||||||
(e.response.data && e.response.data.message ? e.response.data.message : e.response.statusText)
|
e.response.status +
|
||||||
);
|
" " +
|
||||||
|
(e.response.data && e.response.data.message ? e.response.data.message : e.response.statusText)
|
||||||
|
);
|
||||||
|
} else res.json(errorType.data);
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
res.status(500);
|
res.status(500);
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import { ExSocketInterface } from "../Model/Websocket/ExSocketInterface";
|
import { ExSocketInterface } from "../Model/Websocket/ExSocketInterface";
|
||||||
import { GameRoomPolicyTypes } from "../Model/PusherRoom";
|
|
||||||
import { PointInterface } from "../Model/Websocket/PointInterface";
|
import { PointInterface } from "../Model/Websocket/PointInterface";
|
||||||
import {
|
import {
|
||||||
SetPlayerDetailsMessage,
|
SetPlayerDetailsMessage,
|
||||||
@ -25,7 +24,7 @@ import {
|
|||||||
import { UserMovesMessage } from "../Messages/generated/messages_pb";
|
import { UserMovesMessage } from "../Messages/generated/messages_pb";
|
||||||
import { parse } from "query-string";
|
import { parse } from "query-string";
|
||||||
import { AdminSocketTokenData, jwtTokenManager, tokenInvalidException } from "../Services/JWTTokenManager";
|
import { AdminSocketTokenData, jwtTokenManager, tokenInvalidException } from "../Services/JWTTokenManager";
|
||||||
import { adminApi, FetchMemberDataByUuidResponse } from "../Services/AdminApi";
|
import { FetchMemberDataByUuidResponse } from "../Services/AdminApi";
|
||||||
import { socketManager } from "../Services/SocketManager";
|
import { socketManager } from "../Services/SocketManager";
|
||||||
import { emitInBatch } from "../Services/IoSocketHelpers";
|
import { emitInBatch } from "../Services/IoSocketHelpers";
|
||||||
import { ADMIN_API_URL, ADMIN_SOCKETS_TOKEN, DISABLE_ANONYMOUS, SOCKET_IDLE_TIMER } from "../Enum/EnvironmentVariable";
|
import { ADMIN_API_URL, ADMIN_SOCKETS_TOKEN, DISABLE_ANONYMOUS, SOCKET_IDLE_TIMER } from "../Enum/EnvironmentVariable";
|
||||||
@ -39,6 +38,8 @@ import { localWokaService } from "../Services/LocalWokaService";
|
|||||||
import { WebSocket } from "uWebSockets.js";
|
import { WebSocket } from "uWebSockets.js";
|
||||||
import { WokaDetail } from "../Messages/JsonMessages/PlayerTextures";
|
import { WokaDetail } from "../Messages/JsonMessages/PlayerTextures";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
import { adminService } from "../Services/AdminService";
|
||||||
|
import { ErrorApiData, isErrorApiData } from "../Messages/JsonMessages/ErrorApiData";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The object passed between the "open" and the "upgrade" methods when opening a websocket
|
* The object passed between the "open" and the "upgrade" methods when opening a websocket
|
||||||
@ -66,13 +67,21 @@ interface UpgradeData {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
interface UpgradeFailedData {
|
interface UpgradeFailedInvalidData {
|
||||||
rejected: true;
|
rejected: true;
|
||||||
reason: "tokenInvalid" | "textureInvalid" | null;
|
reason: "tokenInvalid" | "textureInvalid" | null;
|
||||||
message: string;
|
message: string;
|
||||||
roomId: string;
|
roomId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface UpgradeFailedErrorData {
|
||||||
|
rejected: true;
|
||||||
|
reason: "error";
|
||||||
|
error: ErrorApiData;
|
||||||
|
}
|
||||||
|
|
||||||
|
type UpgradeFailedData = UpgradeFailedErrorData | UpgradeFailedInvalidData;
|
||||||
|
|
||||||
export class IoSocketController {
|
export class IoSocketController {
|
||||||
private nextUserId: number = 1;
|
private nextUserId: number = 1;
|
||||||
|
|
||||||
@ -234,6 +243,7 @@ export class IoSocketController {
|
|||||||
const websocketProtocol = req.getHeader("sec-websocket-protocol");
|
const websocketProtocol = req.getHeader("sec-websocket-protocol");
|
||||||
const websocketExtensions = req.getHeader("sec-websocket-extensions");
|
const websocketExtensions = req.getHeader("sec-websocket-extensions");
|
||||||
const IPAddress = req.getHeader("x-forwarded-for");
|
const IPAddress = req.getHeader("x-forwarded-for");
|
||||||
|
const locale = req.getHeader("accept-language");
|
||||||
|
|
||||||
const roomId = query.roomId;
|
const roomId = query.roomId;
|
||||||
try {
|
try {
|
||||||
@ -285,7 +295,6 @@ export class IoSocketController {
|
|||||||
let memberMessages: unknown;
|
let memberMessages: unknown;
|
||||||
let memberUserRoomToken: string | undefined;
|
let memberUserRoomToken: string | undefined;
|
||||||
let memberTextures: WokaDetail[] = [];
|
let memberTextures: WokaDetail[] = [];
|
||||||
const room = await socketManager.getOrCreateRoom(roomId);
|
|
||||||
let userData: FetchMemberDataByUuidResponse = {
|
let userData: FetchMemberDataByUuidResponse = {
|
||||||
email: userIdentifier,
|
email: userIdentifier,
|
||||||
userUuid: userIdentifier,
|
userUuid: userIdentifier,
|
||||||
@ -302,61 +311,52 @@ export class IoSocketController {
|
|||||||
if (ADMIN_API_URL) {
|
if (ADMIN_API_URL) {
|
||||||
try {
|
try {
|
||||||
try {
|
try {
|
||||||
userData = await adminApi.fetchMemberDataByUuid(
|
userData = await adminService.fetchMemberDataByUuid(
|
||||||
userIdentifier,
|
userIdentifier,
|
||||||
roomId,
|
roomId,
|
||||||
IPAddress,
|
IPAddress,
|
||||||
characterLayers
|
characterLayers,
|
||||||
|
locale
|
||||||
);
|
);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (Axios.isAxiosError(err)) {
|
if (Axios.isAxiosError(err)) {
|
||||||
if (err?.response?.status == 404) {
|
const errorType = isErrorApiData.safeParse(err?.response?.data);
|
||||||
// If we get an HTTP 404, the token is invalid. Let's perform an anonymous login!
|
if (errorType.success) {
|
||||||
|
|
||||||
console.warn(
|
|
||||||
'Cannot find user with email "' +
|
|
||||||
(userIdentifier || "anonymous") +
|
|
||||||
'". Performing an anonymous login instead.'
|
|
||||||
);
|
|
||||||
} else if (err?.response?.status == 403) {
|
|
||||||
// If we get an HTTP 403, the world is full. We need to broadcast a special error to the client.
|
|
||||||
// we finish immediately the upgrade then we will close the socket as soon as it starts opening.
|
|
||||||
return res.upgrade(
|
return res.upgrade(
|
||||||
{
|
{
|
||||||
rejected: true,
|
rejected: true,
|
||||||
message: err?.response?.data.message,
|
reason: "error",
|
||||||
status: err?.response?.status,
|
status: err?.response?.status,
|
||||||
roomId,
|
error: errorType.data,
|
||||||
},
|
} as UpgradeFailedData,
|
||||||
|
websocketKey,
|
||||||
|
websocketProtocol,
|
||||||
|
websocketExtensions,
|
||||||
|
context
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return res.upgrade(
|
||||||
|
{
|
||||||
|
rejected: true,
|
||||||
|
reason: null,
|
||||||
|
status: 500,
|
||||||
|
message: err?.response?.data,
|
||||||
|
roomId: roomId,
|
||||||
|
} as UpgradeFailedData,
|
||||||
websocketKey,
|
websocketKey,
|
||||||
websocketProtocol,
|
websocketProtocol,
|
||||||
websocketExtensions,
|
websocketExtensions,
|
||||||
context
|
context
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
throw err;
|
|
||||||
}
|
}
|
||||||
|
throw err;
|
||||||
}
|
}
|
||||||
memberMessages = userData.messages;
|
memberMessages = userData.messages;
|
||||||
memberTags = userData.tags;
|
memberTags = userData.tags;
|
||||||
memberVisitCardUrl = userData.visitCardUrl;
|
memberVisitCardUrl = userData.visitCardUrl;
|
||||||
memberTextures = userData.textures;
|
memberTextures = userData.textures;
|
||||||
memberUserRoomToken = userData.userRoomToken;
|
memberUserRoomToken = userData.userRoomToken;
|
||||||
|
|
||||||
if (
|
|
||||||
room.policyType === GameRoomPolicyTypes.USE_TAGS_POLICY &&
|
|
||||||
(userData.anonymous === true || !room.canAccess(memberTags))
|
|
||||||
) {
|
|
||||||
throw new Error("Insufficient privileges to access this room");
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
room.policyType === GameRoomPolicyTypes.MEMBERS_ONLY_POLICY &&
|
|
||||||
userData.anonymous === true
|
|
||||||
) {
|
|
||||||
throw new Error("Use the login URL to connect");
|
|
||||||
}
|
|
||||||
|
|
||||||
characterLayerObjs = memberTextures;
|
characterLayerObjs = memberTextures;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(
|
console.log(
|
||||||
@ -480,8 +480,8 @@ export class IoSocketController {
|
|||||||
socketManager.emitTokenExpiredMessage(ws);
|
socketManager.emitTokenExpiredMessage(ws);
|
||||||
} else if (ws.reason === "textureInvalid") {
|
} else if (ws.reason === "textureInvalid") {
|
||||||
socketManager.emitInvalidTextureMessage(ws);
|
socketManager.emitInvalidTextureMessage(ws);
|
||||||
} else if (ws.message === "World is full") {
|
} else if (ws.reason === "error") {
|
||||||
socketManager.emitWorldFullMessage(ws);
|
socketManager.emitErrorScreenMessage(ws, ws.error);
|
||||||
} else {
|
} else {
|
||||||
socketManager.emitConnexionErrorMessage(ws, ws.message);
|
socketManager.emitConnexionErrorMessage(ws, ws.message);
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,8 @@
|
|||||||
import { adminApi } from "../Services/AdminApi";
|
import { DISABLE_ANONYMOUS } from "../Enum/EnvironmentVariable";
|
||||||
import { ADMIN_API_URL, DISABLE_ANONYMOUS } from "../Enum/EnvironmentVariable";
|
import { isMapDetailsData } from "../Messages/JsonMessages/MapDetailsData";
|
||||||
import { GameRoomPolicyTypes } from "../Model/PusherRoom";
|
|
||||||
import { isMapDetailsData, MapDetailsData } from "../Messages/JsonMessages/MapDetailsData";
|
|
||||||
import { AuthTokenData, jwtTokenManager } from "../Services/JWTTokenManager";
|
|
||||||
import { InvalidTokenError } from "./InvalidTokenError";
|
|
||||||
import { parse } from "query-string";
|
import { parse } from "query-string";
|
||||||
import { BaseHttpController } from "./BaseHttpController";
|
import { BaseHttpController } from "./BaseHttpController";
|
||||||
|
import { adminService } from "../Services/AdminService";
|
||||||
|
|
||||||
export class MapController extends BaseHttpController {
|
export class MapController extends BaseHttpController {
|
||||||
// Returns a map mapping map name to file name of the map
|
// Returns a map mapping map name to file name of the map
|
||||||
@ -107,66 +104,17 @@ export class MapController extends BaseHttpController {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If no admin URL is set, let's react on '/_/[instance]/[map url]' URLs
|
|
||||||
if (!ADMIN_API_URL) {
|
|
||||||
const roomUrl = new URL(query.playUri);
|
|
||||||
|
|
||||||
const match = /\/_\/[^/]+\/(.+)/.exec(roomUrl.pathname);
|
|
||||||
if (!match) {
|
|
||||||
res.status(404);
|
|
||||||
res.json({});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const mapUrl = roomUrl.protocol + "//" + match[1];
|
|
||||||
|
|
||||||
res.json({
|
|
||||||
mapUrl,
|
|
||||||
policy_type: GameRoomPolicyTypes.ANONYMOUS_POLICY,
|
|
||||||
roomSlug: null, // Deprecated
|
|
||||||
group: null,
|
|
||||||
tags: [],
|
|
||||||
contactPage: null,
|
|
||||||
authenticationMandatory: DISABLE_ANONYMOUS,
|
|
||||||
} as MapDetailsData);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
try {
|
try {
|
||||||
let userId: string | undefined = undefined;
|
|
||||||
if (query.authToken != undefined) {
|
|
||||||
let authTokenData: AuthTokenData;
|
|
||||||
try {
|
|
||||||
authTokenData = jwtTokenManager.verifyJWTToken(query.authToken as string);
|
|
||||||
userId = authTokenData.identifier;
|
|
||||||
} catch (e) {
|
|
||||||
try {
|
|
||||||
// Decode token, in this case we don't need to create new token.
|
|
||||||
authTokenData = jwtTokenManager.verifyJWTToken(query.authToken as string, true);
|
|
||||||
userId = authTokenData.identifier;
|
|
||||||
console.info("JWT expire, but decoded", userId);
|
|
||||||
} catch (e) {
|
|
||||||
if (e instanceof InvalidTokenError) {
|
|
||||||
// The token was not good, redirect user on login page
|
|
||||||
res.status(401);
|
|
||||||
res.send("Token decrypted error");
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
this.castErrorToResponse(e, res);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const mapDetails = isMapDetailsData.parse(
|
const mapDetails = isMapDetailsData.parse(
|
||||||
await adminApi.fetchMapDetails(query.playUri as string, userId)
|
await adminService.fetchMapDetails(
|
||||||
|
query.playUri as string,
|
||||||
|
query.authToken as string,
|
||||||
|
req.header("accept-language")
|
||||||
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
if (DISABLE_ANONYMOUS) {
|
if (DISABLE_ANONYMOUS) mapDetails.authenticationMandatory = true;
|
||||||
mapDetails.authenticationMandatory = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
res.json(mapDetails);
|
res.json(mapDetails);
|
||||||
return;
|
return;
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import { ExSocketInterface } from "../Model/Websocket/ExSocketInterface";
|
import { ExSocketInterface } from "../Model/Websocket/ExSocketInterface";
|
||||||
import { PositionDispatcher } from "./PositionDispatcher";
|
import { PositionDispatcher } from "./PositionDispatcher";
|
||||||
import { ViewportInterface } from "../Model/Websocket/ViewportMessage";
|
import { ViewportInterface } from "../Model/Websocket/ViewportMessage";
|
||||||
import { arrayIntersect } from "../Services/ArrayHelper";
|
|
||||||
import { ZoneEventListener } from "../Model/Zone";
|
import { ZoneEventListener } from "../Model/Zone";
|
||||||
import { apiClientRepository } from "../Services/ApiClientRepository";
|
import { apiClientRepository } from "../Services/ApiClientRepository";
|
||||||
import {
|
import {
|
||||||
@ -24,8 +23,6 @@ export enum GameRoomPolicyTypes {
|
|||||||
|
|
||||||
export class PusherRoom {
|
export class PusherRoom {
|
||||||
private readonly positionNotifier: PositionDispatcher;
|
private readonly positionNotifier: PositionDispatcher;
|
||||||
public tags: string[];
|
|
||||||
public policyType: GameRoomPolicyTypes;
|
|
||||||
private versionNumber: number = 1;
|
private versionNumber: number = 1;
|
||||||
private backConnection!: ClientReadableStream<BatchToPusherRoomMessage>;
|
private backConnection!: ClientReadableStream<BatchToPusherRoomMessage>;
|
||||||
private isClosing: boolean = false;
|
private isClosing: boolean = false;
|
||||||
@ -33,9 +30,6 @@ export class PusherRoom {
|
|||||||
//public readonly variables = new Map<string, string>();
|
//public readonly variables = new Map<string, string>();
|
||||||
|
|
||||||
constructor(public readonly roomUrl: string, private socketListener: ZoneEventListener) {
|
constructor(public readonly roomUrl: string, private socketListener: ZoneEventListener) {
|
||||||
this.tags = [];
|
|
||||||
this.policyType = GameRoomPolicyTypes.ANONYMOUS_POLICY;
|
|
||||||
|
|
||||||
// A zone is 10 sprites wide.
|
// A zone is 10 sprites wide.
|
||||||
this.positionNotifier = new PositionDispatcher(this.roomUrl, 320, 320, this.socketListener);
|
this.positionNotifier = new PositionDispatcher(this.roomUrl, 320, 320, this.socketListener);
|
||||||
}
|
}
|
||||||
@ -53,10 +47,6 @@ export class PusherRoom {
|
|||||||
this.listeners.delete(socket);
|
this.listeners.delete(socket);
|
||||||
}
|
}
|
||||||
|
|
||||||
public canAccess(userTags: string[]): boolean {
|
|
||||||
return arrayIntersect(userTags, this.tags);
|
|
||||||
}
|
|
||||||
|
|
||||||
public isEmpty(): boolean {
|
public isEmpty(): boolean {
|
||||||
return this.positionNotifier.isEmpty();
|
return this.positionNotifier.isEmpty();
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,8 @@ import { z } from "zod";
|
|||||||
import { isWokaDetail } from "../Messages/JsonMessages/PlayerTextures";
|
import { isWokaDetail } from "../Messages/JsonMessages/PlayerTextures";
|
||||||
import qs from "qs";
|
import qs from "qs";
|
||||||
import { AdminInterface } from "./AdminInterface";
|
import { AdminInterface } from "./AdminInterface";
|
||||||
|
import { AuthTokenData, jwtTokenManager } from "./JWTTokenManager";
|
||||||
|
import { InvalidTokenError } from "../Controller/InvalidTokenError";
|
||||||
|
|
||||||
export interface AdminBannedData {
|
export interface AdminBannedData {
|
||||||
is_banned: boolean;
|
is_banned: boolean;
|
||||||
@ -20,6 +22,7 @@ export const isFetchMemberDataByUuidResponse = z.object({
|
|||||||
visitCardUrl: z.nullable(z.string()),
|
visitCardUrl: z.nullable(z.string()),
|
||||||
textures: z.array(isWokaDetail),
|
textures: z.array(isWokaDetail),
|
||||||
messages: z.array(z.unknown()),
|
messages: z.array(z.unknown()),
|
||||||
|
|
||||||
anonymous: z.optional(z.boolean()),
|
anonymous: z.optional(z.boolean()),
|
||||||
userRoomToken: z.optional(z.string()),
|
userRoomToken: z.optional(z.string()),
|
||||||
});
|
});
|
||||||
@ -27,14 +30,38 @@ export const isFetchMemberDataByUuidResponse = z.object({
|
|||||||
export type FetchMemberDataByUuidResponse = z.infer<typeof isFetchMemberDataByUuidResponse>;
|
export type FetchMemberDataByUuidResponse = z.infer<typeof isFetchMemberDataByUuidResponse>;
|
||||||
|
|
||||||
class AdminApi implements AdminInterface {
|
class AdminApi implements AdminInterface {
|
||||||
/**
|
async fetchMapDetails(
|
||||||
* @var playUri: is url of the room
|
playUri: string,
|
||||||
* @var userId: can to be undefined or email or uuid
|
authToken?: string,
|
||||||
* @return MapDetailsData|RoomRedirect
|
locale?: string
|
||||||
*/
|
): Promise<MapDetailsData | RoomRedirect> {
|
||||||
async fetchMapDetails(playUri: string, userId?: string): Promise<MapDetailsData | RoomRedirect> {
|
let userId: string | undefined = undefined;
|
||||||
if (!ADMIN_API_URL) {
|
if (authToken != undefined) {
|
||||||
return Promise.reject(new Error("No admin backoffice set!"));
|
let authTokenData: AuthTokenData;
|
||||||
|
try {
|
||||||
|
authTokenData = jwtTokenManager.verifyJWTToken(authToken);
|
||||||
|
userId = authTokenData.identifier;
|
||||||
|
//eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
} catch (e) {
|
||||||
|
try {
|
||||||
|
// Decode token, in this case we don't need to create new token.
|
||||||
|
authTokenData = jwtTokenManager.verifyJWTToken(authToken, true);
|
||||||
|
userId = authTokenData.identifier;
|
||||||
|
console.info("JWT expire, but decoded:", userId);
|
||||||
|
} catch (e) {
|
||||||
|
if (e instanceof InvalidTokenError) {
|
||||||
|
throw new Error("Token decrypted error");
|
||||||
|
// The token was not good, redirect user on login page
|
||||||
|
//res.status(401);
|
||||||
|
//res.send("Token decrypted error");
|
||||||
|
//return;
|
||||||
|
} else {
|
||||||
|
throw new Error("Error on decryption of token :" + e);
|
||||||
|
//this.castErrorToResponse(e, res);
|
||||||
|
//return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const params: { playUri: string; userId?: string } = {
|
const params: { playUri: string; userId?: string } = {
|
||||||
@ -43,7 +70,7 @@ class AdminApi implements AdminInterface {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const res = await Axios.get<unknown, AxiosResponse<unknown>>(ADMIN_API_URL + "/api/map", {
|
const res = await Axios.get<unknown, AxiosResponse<unknown>>(ADMIN_API_URL + "/api/map", {
|
||||||
headers: { Authorization: `${ADMIN_API_TOKEN}` },
|
headers: { Authorization: `${ADMIN_API_TOKEN}`, "Accept-Language": locale ?? "en" },
|
||||||
params,
|
params,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -69,11 +96,9 @@ class AdminApi implements AdminInterface {
|
|||||||
userIdentifier: string,
|
userIdentifier: string,
|
||||||
playUri: string,
|
playUri: string,
|
||||||
ipAddress: string,
|
ipAddress: string,
|
||||||
characterLayers: string[]
|
characterLayers: string[],
|
||||||
|
locale?: string
|
||||||
): Promise<FetchMemberDataByUuidResponse> {
|
): Promise<FetchMemberDataByUuidResponse> {
|
||||||
if (!ADMIN_API_URL) {
|
|
||||||
return Promise.reject(new Error("No admin backoffice set!"));
|
|
||||||
}
|
|
||||||
const res = await Axios.get<unknown, AxiosResponse<unknown>>(ADMIN_API_URL + "/api/room/access", {
|
const res = await Axios.get<unknown, AxiosResponse<unknown>>(ADMIN_API_URL + "/api/room/access", {
|
||||||
params: {
|
params: {
|
||||||
userIdentifier,
|
userIdentifier,
|
||||||
@ -81,7 +106,7 @@ class AdminApi implements AdminInterface {
|
|||||||
ipAddress,
|
ipAddress,
|
||||||
characterLayers,
|
characterLayers,
|
||||||
},
|
},
|
||||||
headers: { Authorization: `${ADMIN_API_TOKEN}` },
|
headers: { Authorization: `${ADMIN_API_TOKEN}`, "Accept-Language": locale ?? "en" },
|
||||||
paramsSerializer: (p) => {
|
paramsSerializer: (p) => {
|
||||||
return qs.stringify(p, { arrayFormat: "brackets" });
|
return qs.stringify(p, { arrayFormat: "brackets" });
|
||||||
},
|
},
|
||||||
@ -100,14 +125,15 @@ class AdminApi implements AdminInterface {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async fetchMemberDataByToken(organizationMemberToken: string, playUri: string | null): Promise<AdminApiData> {
|
async fetchMemberDataByToken(
|
||||||
if (!ADMIN_API_URL) {
|
organizationMemberToken: string,
|
||||||
return Promise.reject(new Error("No admin backoffice set!"));
|
playUri: string | null,
|
||||||
}
|
locale?: string
|
||||||
|
): Promise<AdminApiData> {
|
||||||
//todo: this call can fail if the corresponding world is not activated or if the token is invalid. Handle that case.
|
//todo: this call can fail if the corresponding world is not activated or if the token is invalid. Handle that case.
|
||||||
const res = await Axios.get(ADMIN_API_URL + "/api/login-url/" + organizationMemberToken, {
|
const res = await Axios.get(ADMIN_API_URL + "/api/login-url/" + organizationMemberToken, {
|
||||||
params: { playUri },
|
params: { playUri },
|
||||||
headers: { Authorization: `${ADMIN_API_TOKEN}` },
|
headers: { Authorization: `${ADMIN_API_TOKEN}`, "Accept-Language": locale ?? "en" },
|
||||||
});
|
});
|
||||||
|
|
||||||
const adminApiData = isAdminApiData.safeParse(res.data);
|
const adminApiData = isAdminApiData.safeParse(res.data);
|
||||||
@ -125,11 +151,9 @@ class AdminApi implements AdminInterface {
|
|||||||
reportedUserUuid: string,
|
reportedUserUuid: string,
|
||||||
reportedUserComment: string,
|
reportedUserComment: string,
|
||||||
reporterUserUuid: string,
|
reporterUserUuid: string,
|
||||||
reportWorldSlug: string
|
reportWorldSlug: string,
|
||||||
|
locale?: string
|
||||||
) {
|
) {
|
||||||
if (!ADMIN_API_URL) {
|
|
||||||
return Promise.reject(new Error("No admin backoffice set!"));
|
|
||||||
}
|
|
||||||
return Axios.post(
|
return Axios.post(
|
||||||
`${ADMIN_API_URL}/api/report`,
|
`${ADMIN_API_URL}/api/report`,
|
||||||
{
|
{
|
||||||
@ -139,15 +163,17 @@ class AdminApi implements AdminInterface {
|
|||||||
reportWorldSlug,
|
reportWorldSlug,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
headers: { Authorization: `${ADMIN_API_TOKEN}` },
|
headers: { Authorization: `${ADMIN_API_TOKEN}`, "Accept-Language": locale ?? "en" },
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async verifyBanUser(userUuid: string, ipAddress: string, roomUrl: string): Promise<AdminBannedData> {
|
async verifyBanUser(
|
||||||
if (!ADMIN_API_URL) {
|
userUuid: string,
|
||||||
return Promise.reject(new Error("No admin backoffice set!"));
|
ipAddress: string,
|
||||||
}
|
roomUrl: string,
|
||||||
|
locale?: string
|
||||||
|
): Promise<AdminBannedData> {
|
||||||
//todo: this call can fail if the corresponding world is not activated or if the token is invalid. Handle that case.
|
//todo: this call can fail if the corresponding world is not activated or if the token is invalid. Handle that case.
|
||||||
return Axios.get(
|
return Axios.get(
|
||||||
ADMIN_API_URL +
|
ADMIN_API_URL +
|
||||||
@ -158,28 +184,20 @@ class AdminApi implements AdminInterface {
|
|||||||
encodeURIComponent(userUuid) +
|
encodeURIComponent(userUuid) +
|
||||||
"&roomUrl=" +
|
"&roomUrl=" +
|
||||||
encodeURIComponent(roomUrl),
|
encodeURIComponent(roomUrl),
|
||||||
{ headers: { Authorization: `${ADMIN_API_TOKEN}` } }
|
{ headers: { Authorization: `${ADMIN_API_TOKEN}`, "Accept-Language": locale ?? "en" } }
|
||||||
).then((data) => {
|
).then((data) => {
|
||||||
return data.data;
|
return data.data;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async getUrlRoomsFromSameWorld(roomUrl: string): Promise<string[]> {
|
async getUrlRoomsFromSameWorld(roomUrl: string, locale?: string): Promise<string[]> {
|
||||||
if (!ADMIN_API_URL) {
|
|
||||||
return Promise.reject(new Error("No admin backoffice set!"));
|
|
||||||
}
|
|
||||||
|
|
||||||
return Axios.get(ADMIN_API_URL + "/api/room/sameWorld" + "?roomUrl=" + encodeURIComponent(roomUrl), {
|
return Axios.get(ADMIN_API_URL + "/api/room/sameWorld" + "?roomUrl=" + encodeURIComponent(roomUrl), {
|
||||||
headers: { Authorization: `${ADMIN_API_TOKEN}` },
|
headers: { Authorization: `${ADMIN_API_TOKEN}`, "Accept-Language": locale ?? "en" },
|
||||||
}).then((data) => {
|
}).then((data) => {
|
||||||
return data.data;
|
return data.data;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param accessToken
|
|
||||||
*/
|
|
||||||
getProfileUrl(accessToken: string): string {
|
getProfileUrl(accessToken: string): string {
|
||||||
if (!OPID_PROFILE_SCREEN_PROVIDER) {
|
if (!OPID_PROFILE_SCREEN_PROVIDER) {
|
||||||
throw new Error("No admin backoffice set!");
|
throw new Error("No admin backoffice set!");
|
||||||
@ -187,7 +205,7 @@ class AdminApi implements AdminInterface {
|
|||||||
return `${OPID_PROFILE_SCREEN_PROVIDER}?accessToken=${accessToken}`;
|
return `${OPID_PROFILE_SCREEN_PROVIDER}?accessToken=${accessToken}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
async logoutOauth(token: string) {
|
async logoutOauth(token: string): Promise<void> {
|
||||||
await Axios.get(ADMIN_API_URL + `/oauth/logout?token=${token}`);
|
await Axios.get(ADMIN_API_URL + `/oauth/logout?token=${token}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,82 @@
|
|||||||
import { FetchMemberDataByUuidResponse } from "./AdminApi";
|
import { AdminBannedData, FetchMemberDataByUuidResponse } from "./AdminApi";
|
||||||
|
import { MapDetailsData } from "../Messages/JsonMessages/MapDetailsData";
|
||||||
|
import { RoomRedirect } from "../Messages/JsonMessages/RoomRedirect";
|
||||||
|
import { AdminApiData } from "../Messages/JsonMessages/AdminApiData";
|
||||||
|
|
||||||
export interface AdminInterface {
|
export interface AdminInterface {
|
||||||
|
/**
|
||||||
|
* @var playUri: is url of the room
|
||||||
|
* @var userIdentifier: can to be undefined or email or uuid
|
||||||
|
* @var ipAddress
|
||||||
|
* @var characterLayers
|
||||||
|
* @return MapDetailsData|RoomRedirect
|
||||||
|
*/
|
||||||
fetchMemberDataByUuid(
|
fetchMemberDataByUuid(
|
||||||
userIdentifier: string,
|
userIdentifier: string,
|
||||||
playUri: string,
|
playUri: string,
|
||||||
ipAddress: string,
|
ipAddress: string,
|
||||||
characterLayers: string[]
|
characterLayers: string[],
|
||||||
|
locale?: string
|
||||||
): Promise<FetchMemberDataByUuidResponse>;
|
): Promise<FetchMemberDataByUuidResponse>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var playUri: is url of the room
|
||||||
|
* @var userId: can to be undefined or email or uuid
|
||||||
|
* @return MapDetailsData|RoomRedirect
|
||||||
|
*/
|
||||||
|
fetchMapDetails(playUri: string, authToken?: string, locale?: string): Promise<MapDetailsData | RoomRedirect>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param locale
|
||||||
|
* @param organizationMemberToken
|
||||||
|
* @param playUri
|
||||||
|
* @return AdminApiData
|
||||||
|
*/
|
||||||
|
fetchMemberDataByToken(
|
||||||
|
organizationMemberToken: string,
|
||||||
|
playUri: string | null,
|
||||||
|
locale?: string
|
||||||
|
): Promise<AdminApiData>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param locale
|
||||||
|
* @param reportedUserUuid
|
||||||
|
* @param reportedUserComment
|
||||||
|
* @param reporterUserUuid
|
||||||
|
* @param reportWorldSlug
|
||||||
|
*/
|
||||||
|
reportPlayer(
|
||||||
|
reportedUserUuid: string,
|
||||||
|
reportedUserComment: string,
|
||||||
|
reporterUserUuid: string,
|
||||||
|
reportWorldSlug: string,
|
||||||
|
locale?: string
|
||||||
|
): Promise<unknown>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param locale
|
||||||
|
* @param userUuid
|
||||||
|
* @param ipAddress
|
||||||
|
* @param roomUrl
|
||||||
|
* @return AdminBannedData
|
||||||
|
*/
|
||||||
|
verifyBanUser(userUuid: string, ipAddress: string, roomUrl: string, locale?: string): Promise<AdminBannedData>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param locale
|
||||||
|
* @param roomUrl
|
||||||
|
* @return string[]
|
||||||
|
*/
|
||||||
|
getUrlRoomsFromSameWorld(roomUrl: string, locale?: string): Promise<string[]>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param accessToken
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
getProfileUrl(accessToken: string): string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param token
|
||||||
|
*/
|
||||||
|
logoutOauth(token: string): Promise<void>;
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,10 @@
|
|||||||
import { FetchMemberDataByUuidResponse } from "./AdminApi";
|
import { AdminBannedData, FetchMemberDataByUuidResponse } from "./AdminApi";
|
||||||
import { AdminInterface } from "./AdminInterface";
|
import { AdminInterface } from "./AdminInterface";
|
||||||
|
import { MapDetailsData } from "../Messages/JsonMessages/MapDetailsData";
|
||||||
|
import { RoomRedirect } from "../Messages/JsonMessages/RoomRedirect";
|
||||||
|
import { GameRoomPolicyTypes } from "../Model/PusherRoom";
|
||||||
|
import { DISABLE_ANONYMOUS } from "../Enum/EnvironmentVariable";
|
||||||
|
import { AdminApiData } from "../Messages/JsonMessages/AdminApiData";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A local class mocking a real admin if no admin is configured.
|
* A local class mocking a real admin if no admin is configured.
|
||||||
@ -7,12 +12,10 @@ import { AdminInterface } from "./AdminInterface";
|
|||||||
class LocalAdmin implements AdminInterface {
|
class LocalAdmin implements AdminInterface {
|
||||||
fetchMemberDataByUuid(
|
fetchMemberDataByUuid(
|
||||||
userIdentifier: string,
|
userIdentifier: string,
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
||||||
playUri: string,
|
playUri: string,
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
||||||
ipAddress: string,
|
ipAddress: string,
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
characterLayers: string[],
|
||||||
characterLayers: string[]
|
locale?: string
|
||||||
): Promise<FetchMemberDataByUuidResponse> {
|
): Promise<FetchMemberDataByUuidResponse> {
|
||||||
return Promise.resolve({
|
return Promise.resolve({
|
||||||
email: userIdentifier,
|
email: userIdentifier,
|
||||||
@ -24,6 +27,70 @@ class LocalAdmin implements AdminInterface {
|
|||||||
userRoomToken: undefined,
|
userRoomToken: undefined,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fetchMapDetails(playUri: string, authToken?: string, locale?: string): Promise<MapDetailsData | RoomRedirect> {
|
||||||
|
const roomUrl = new URL(playUri);
|
||||||
|
|
||||||
|
const match = /\/_\/[^/]+\/(.+)/.exec(roomUrl.pathname);
|
||||||
|
if (!match) {
|
||||||
|
throw new Error("URL format is not good");
|
||||||
|
}
|
||||||
|
|
||||||
|
const mapUrl = roomUrl.protocol + "//" + match[1];
|
||||||
|
|
||||||
|
return Promise.resolve({
|
||||||
|
mapUrl,
|
||||||
|
policy_type: GameRoomPolicyTypes.ANONYMOUS_POLICY,
|
||||||
|
tags: [],
|
||||||
|
authenticationMandatory: DISABLE_ANONYMOUS,
|
||||||
|
roomSlug: null,
|
||||||
|
contactPage: null,
|
||||||
|
group: null,
|
||||||
|
iframeAuthentication: null,
|
||||||
|
loadingLogo: null,
|
||||||
|
loginSceneLogo: null,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async fetchMemberDataByToken(
|
||||||
|
organizationMemberToken: string,
|
||||||
|
playUri: string | null,
|
||||||
|
locale?: string
|
||||||
|
): Promise<AdminApiData> {
|
||||||
|
return Promise.reject(new Error("No admin backoffice set!"));
|
||||||
|
}
|
||||||
|
|
||||||
|
reportPlayer(
|
||||||
|
reportedUserUuid: string,
|
||||||
|
reportedUserComment: string,
|
||||||
|
reporterUserUuid: string,
|
||||||
|
reportWorldSlug: string,
|
||||||
|
locale?: string
|
||||||
|
) {
|
||||||
|
return Promise.reject(new Error("No admin backoffice set!"));
|
||||||
|
}
|
||||||
|
|
||||||
|
async verifyBanUser(
|
||||||
|
userUuid: string,
|
||||||
|
ipAddress: string,
|
||||||
|
roomUrl: string,
|
||||||
|
locale?: string
|
||||||
|
): Promise<AdminBannedData> {
|
||||||
|
return Promise.reject(new Error("No admin backoffice set!"));
|
||||||
|
}
|
||||||
|
|
||||||
|
async getUrlRoomsFromSameWorld(roomUrl: string, locale?: string): Promise<string[]> {
|
||||||
|
return Promise.reject(new Error("No admin backoffice set!"));
|
||||||
|
}
|
||||||
|
|
||||||
|
getProfileUrl(accessToken: string): string {
|
||||||
|
new Error("No admin backoffice set!");
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
async logoutOauth(token: string): Promise<void> {
|
||||||
|
return Promise.reject(new Error("No admin backoffice set!"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const localAdmin = new LocalAdmin();
|
export const localAdmin = new LocalAdmin();
|
||||||
|
@ -5,7 +5,6 @@ class LocalWokaService implements WokaServiceInterface {
|
|||||||
/**
|
/**
|
||||||
* Returns the list of all available Wokas & Woka Parts for the current user.
|
* Returns the list of all available Wokas & Woka Parts for the current user.
|
||||||
*/
|
*/
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
||||||
async getWokaList(roomId: string, token: string): Promise<WokaList | undefined> {
|
async getWokaList(roomId: string, token: string): Promise<WokaList | undefined> {
|
||||||
const wokaData: WokaList = await require("../../data/woka.json");
|
const wokaData: WokaList = await require("../../data/woka.json");
|
||||||
if (!wokaData) {
|
if (!wokaData) {
|
||||||
|
@ -39,10 +39,10 @@ import {
|
|||||||
PlayerDetailsUpdatedMessage,
|
PlayerDetailsUpdatedMessage,
|
||||||
LockGroupPromptMessage,
|
LockGroupPromptMessage,
|
||||||
InvalidTextureMessage,
|
InvalidTextureMessage,
|
||||||
|
ErrorScreenMessage,
|
||||||
} from "../Messages/generated/messages_pb";
|
} from "../Messages/generated/messages_pb";
|
||||||
import { ProtobufUtils } from "../Model/Websocket/ProtobufUtils";
|
import { ProtobufUtils } from "../Model/Websocket/ProtobufUtils";
|
||||||
import { ADMIN_API_URL, JITSI_ISS, JITSI_URL, SECRET_JITSI_KEY, PUSHER_FORCE_ROOM_UPDATE } from "../Enum/EnvironmentVariable";
|
import { JITSI_ISS, JITSI_URL, SECRET_JITSI_KEY } from "../Enum/EnvironmentVariable";
|
||||||
import { adminApi } from "./AdminApi";
|
|
||||||
import { emitInBatch } from "./IoSocketHelpers";
|
import { emitInBatch } from "./IoSocketHelpers";
|
||||||
import Jwt from "jsonwebtoken";
|
import Jwt from "jsonwebtoken";
|
||||||
import { clientEventsEmitter } from "./ClientEventsEmitter";
|
import { clientEventsEmitter } from "./ClientEventsEmitter";
|
||||||
@ -52,7 +52,9 @@ import { GroupDescriptor, UserDescriptor, ZoneEventListener } from "../Model/Zon
|
|||||||
import Debug from "debug";
|
import Debug from "debug";
|
||||||
import { ExAdminSocketInterface } from "../Model/Websocket/ExAdminSocketInterface";
|
import { ExAdminSocketInterface } from "../Model/Websocket/ExAdminSocketInterface";
|
||||||
import { compressors } from "hyper-express";
|
import { compressors } from "hyper-express";
|
||||||
import { isMapDetailsData } from "../Messages/JsonMessages/MapDetailsData";
|
import { adminService } from "./AdminService";
|
||||||
|
import { ErrorApiData } from "../Messages/JsonMessages/ErrorApiData";
|
||||||
|
import { BoolValue, Int32Value, StringValue } from "google-protobuf/google/protobuf/wrappers_pb";
|
||||||
|
|
||||||
const debug = Debug("socket");
|
const debug = Debug("socket");
|
||||||
|
|
||||||
@ -382,7 +384,8 @@ export class SocketManager implements ZoneEventListener {
|
|||||||
|
|
||||||
async handleReportMessage(client: ExSocketInterface, reportPlayerMessage: ReportPlayerMessage) {
|
async handleReportMessage(client: ExSocketInterface, reportPlayerMessage: ReportPlayerMessage) {
|
||||||
try {
|
try {
|
||||||
await adminApi.reportPlayer(
|
await adminService.reportPlayer(
|
||||||
|
"en",
|
||||||
reportPlayerMessage.getReporteduseruuid(),
|
reportPlayerMessage.getReporteduseruuid(),
|
||||||
reportPlayerMessage.getReportcomment(),
|
reportPlayerMessage.getReportcomment(),
|
||||||
client.userUuid,
|
client.userUuid,
|
||||||
@ -458,30 +461,12 @@ export class SocketManager implements ZoneEventListener {
|
|||||||
let room = this.rooms.get(roomUrl);
|
let room = this.rooms.get(roomUrl);
|
||||||
if (room === undefined) {
|
if (room === undefined) {
|
||||||
room = new PusherRoom(roomUrl, this);
|
room = new PusherRoom(roomUrl, this);
|
||||||
if (ADMIN_API_URL) {
|
|
||||||
await this.updateRoomWithAdminData(room);
|
|
||||||
}
|
|
||||||
await room.init();
|
await room.init();
|
||||||
this.rooms.set(roomUrl, room);
|
this.rooms.set(roomUrl, room);
|
||||||
} else if (PUSHER_FORCE_ROOM_UPDATE && ADMIN_API_URL) {
|
|
||||||
await this.updateRoomWithAdminData(room);
|
|
||||||
}
|
}
|
||||||
return room;
|
return room;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async updateRoomWithAdminData(room: PusherRoom): Promise<void> {
|
|
||||||
const data = await adminApi.fetchMapDetails(room.roomUrl);
|
|
||||||
const mapDetailsData = isMapDetailsData.safeParse(data);
|
|
||||||
|
|
||||||
if (mapDetailsData.success) {
|
|
||||||
room.tags = mapDetailsData.data.tags;
|
|
||||||
room.policyType = Number(mapDetailsData.data.policy_type);
|
|
||||||
} else {
|
|
||||||
// TODO: if the updated room data is actually a redirect, we need to take everybody on the map
|
|
||||||
// and redirect everybody to the new location (so we need to close the connection for everybody)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public getWorlds(): Map<string, PusherRoom> {
|
public getWorlds(): Map<string, PusherRoom> {
|
||||||
return this.rooms;
|
return this.rooms;
|
||||||
}
|
}
|
||||||
@ -670,12 +655,39 @@ export class SocketManager implements ZoneEventListener {
|
|||||||
client.send(serverToClientMessage.serializeBinary().buffer, true);
|
client.send(serverToClientMessage.serializeBinary().buffer, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public emitErrorScreenMessage(client: compressors.WebSocket, errorApi: ErrorApiData) {
|
||||||
|
const errorMessage = new ErrorScreenMessage();
|
||||||
|
errorMessage.setType(errorApi.type);
|
||||||
|
if (errorApi.type == "retry" || errorApi.type == "error") {
|
||||||
|
errorMessage.setCode(new StringValue().setValue(errorApi.code));
|
||||||
|
errorMessage.setTitle(new StringValue().setValue(errorApi.title));
|
||||||
|
errorMessage.setSubtitle(new StringValue().setValue(errorApi.subtitle));
|
||||||
|
errorMessage.setDetails(new StringValue().setValue(errorApi.details));
|
||||||
|
errorMessage.setImage(new StringValue().setValue(errorApi.image));
|
||||||
|
}
|
||||||
|
if (errorApi.type == "retry") {
|
||||||
|
if (errorApi.buttonTitle) errorMessage.setButtontitle(new StringValue().setValue(errorApi.buttonTitle));
|
||||||
|
if (errorApi.canRetryManual !== undefined)
|
||||||
|
errorMessage.setCanretrymanual(new BoolValue().setValue(errorApi.canRetryManual));
|
||||||
|
if (errorApi.timeToRetry)
|
||||||
|
errorMessage.setTimetoretry(new Int32Value().setValue(Number(errorApi.timeToRetry)));
|
||||||
|
}
|
||||||
|
if (errorApi.type == "redirect" && errorApi.urlToRedirect)
|
||||||
|
errorMessage.setUrltoredirect(new StringValue().setValue(errorApi.urlToRedirect));
|
||||||
|
|
||||||
|
const serverToClientMessage = new ServerToClientMessage();
|
||||||
|
serverToClientMessage.setErrorscreenmessage(errorMessage);
|
||||||
|
|
||||||
|
//if (!client.disconnecting) {
|
||||||
|
client.send(serverToClientMessage.serializeBinary().buffer, true);
|
||||||
|
//}
|
||||||
|
}
|
||||||
|
|
||||||
private refreshRoomData(roomId: string, versionNumber: number): void {
|
private refreshRoomData(roomId: string, versionNumber: number): void {
|
||||||
const room = this.rooms.get(roomId);
|
const room = this.rooms.get(roomId);
|
||||||
//this function is run for every users connected to the room, so we need to make sure the room wasn't already refreshed.
|
//this function is run for every users connected to the room, so we need to make sure the room wasn't already refreshed.
|
||||||
if (!room || !room.needsUpdate(versionNumber)) return;
|
if (!room || !room.needsUpdate(versionNumber)) return;
|
||||||
|
//TODO check right of user in admin
|
||||||
this.updateRoomWithAdminData(room);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handleEmotePromptMessage(client: ExSocketInterface, emoteEventmessage: EmotePromptMessage) {
|
handleEmotePromptMessage(client: ExSocketInterface, emoteEventmessage: EmotePromptMessage) {
|
||||||
@ -697,7 +709,7 @@ export class SocketManager implements ZoneEventListener {
|
|||||||
let tabUrlRooms: string[];
|
let tabUrlRooms: string[];
|
||||||
|
|
||||||
if (playGlobalMessageEvent.getBroadcasttoworld()) {
|
if (playGlobalMessageEvent.getBroadcasttoworld()) {
|
||||||
tabUrlRooms = await adminApi.getUrlRoomsFromSameWorld(clientRoomUrl);
|
tabUrlRooms = await adminService.getUrlRoomsFromSameWorld("en", clientRoomUrl);
|
||||||
} else {
|
} else {
|
||||||
tabUrlRooms = [clientRoomUrl];
|
tabUrlRooms = [clientRoomUrl];
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,14 @@
|
|||||||
# yarn lockfile v1
|
# yarn lockfile v1
|
||||||
|
|
||||||
|
|
||||||
|
"@anatine/zod-openapi@^1.3.0":
|
||||||
|
version "1.3.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@anatine/zod-openapi/-/zod-openapi-1.3.0.tgz#b5b38c3d821b79674226aa7b327c88c371860d0d"
|
||||||
|
integrity sha512-l54DypUdDsIq1Uwjv4ib9IBkTXMKZQLUj7qvdFL51EExC5LdSSqOlTOyaVVZZGYgWPKM7ZjGklhdoknLz4EC+w==
|
||||||
|
dependencies:
|
||||||
|
ts-deepmerge "^1.1.0"
|
||||||
|
validator "^13.7.0"
|
||||||
|
|
||||||
"@apidevtools/json-schema-ref-parser@^9.0.6":
|
"@apidevtools/json-schema-ref-parser@^9.0.6":
|
||||||
version "9.0.9"
|
version "9.0.9"
|
||||||
resolved "https://registry.yarnpkg.com/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-9.0.9.tgz#d720f9256e3609621280584f2b47ae165359268b"
|
resolved "https://registry.yarnpkg.com/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-9.0.9.tgz#d720f9256e3609621280584f2b47ae165359268b"
|
||||||
@ -1926,6 +1934,13 @@ onetime@^5.1.0, onetime@^5.1.2:
|
|||||||
dependencies:
|
dependencies:
|
||||||
mimic-fn "^2.1.0"
|
mimic-fn "^2.1.0"
|
||||||
|
|
||||||
|
openapi3-ts@^2.0.2:
|
||||||
|
version "2.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/openapi3-ts/-/openapi3-ts-2.0.2.tgz#a200dd838bf24c9086c8eedcfeb380b7eb31e82a"
|
||||||
|
integrity sha512-TxhYBMoqx9frXyOgnRHufjQfPXomTIHYKhSKJ6jHfj13kS8OEIhvmE8CTuQyKtjjWttAjX5DPxM1vmalEpo8Qw==
|
||||||
|
dependencies:
|
||||||
|
yaml "^1.10.2"
|
||||||
|
|
||||||
openid-client@^4.7.4:
|
openid-client@^4.7.4:
|
||||||
version "4.9.1"
|
version "4.9.1"
|
||||||
resolved "https://registry.yarnpkg.com/openid-client/-/openid-client-4.9.1.tgz#4f00a9d1566c0fa08f0dd5986cf0e6b1e5d14186"
|
resolved "https://registry.yarnpkg.com/openid-client/-/openid-client-4.9.1.tgz#4f00a9d1566c0fa08f0dd5986cf0e6b1e5d14186"
|
||||||
@ -2563,6 +2578,11 @@ tree-kill@^1.2.2:
|
|||||||
resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.2.tgz#4ca09a9092c88b73a7cdc5e8a01b507b0790a0cc"
|
resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.2.tgz#4ca09a9092c88b73a7cdc5e8a01b507b0790a0cc"
|
||||||
integrity sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==
|
integrity sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==
|
||||||
|
|
||||||
|
ts-deepmerge@^1.1.0:
|
||||||
|
version "1.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/ts-deepmerge/-/ts-deepmerge-1.1.0.tgz#4236ae102199affe2e77690dcf198a420160eef2"
|
||||||
|
integrity sha512-VvwaV/6RyYMwT9d8dClmfHIsG2PCdm6WY430QKOIbPRR50Y/1Q2ilp4i2XEZeHFcNqfaYnAQzpyUC6XA0AqqBg==
|
||||||
|
|
||||||
ts-node-dev@^1.1.8:
|
ts-node-dev@^1.1.8:
|
||||||
version "1.1.8"
|
version "1.1.8"
|
||||||
resolved "https://registry.yarnpkg.com/ts-node-dev/-/ts-node-dev-1.1.8.tgz#95520d8ab9d45fffa854d6668e2f8f9286241066"
|
resolved "https://registry.yarnpkg.com/ts-node-dev/-/ts-node-dev-1.1.8.tgz#95520d8ab9d45fffa854d6668e2f8f9286241066"
|
||||||
@ -2689,7 +2709,7 @@ v8-compile-cache@^2.0.3:
|
|||||||
resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee"
|
resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee"
|
||||||
integrity sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==
|
integrity sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==
|
||||||
|
|
||||||
validator@^13.6.0:
|
validator@^13.6.0, validator@^13.7.0:
|
||||||
version "13.7.0"
|
version "13.7.0"
|
||||||
resolved "https://registry.yarnpkg.com/validator/-/validator-13.7.0.tgz#4f9658ba13ba8f3d82ee881d3516489ea85c0857"
|
resolved "https://registry.yarnpkg.com/validator/-/validator-13.7.0.tgz#4f9658ba13ba8f3d82ee881d3516489ea85c0857"
|
||||||
integrity sha512-nYXQLCBkpJ8X6ltALua9dRrZDHVYxjJ1wgskNt1lH9fzGjs3tgojGSCBjmEPwkWS1y29+DrizMTW19Pr9uB2nw==
|
integrity sha512-nYXQLCBkpJ8X6ltALua9dRrZDHVYxjJ1wgskNt1lH9fzGjs3tgojGSCBjmEPwkWS1y29+DrizMTW19Pr9uB2nw==
|
||||||
@ -2796,7 +2816,7 @@ yaml@2.0.0-1:
|
|||||||
resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.0.0-1.tgz#8c3029b3ee2028306d5bcf396980623115ff8d18"
|
resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.0.0-1.tgz#8c3029b3ee2028306d5bcf396980623115ff8d18"
|
||||||
integrity sha512-W7h5dEhywMKenDJh2iX/LABkbFnBxasD27oyXWDS/feDsxiw0dD5ncXdYXgkvAsXIY2MpW/ZKkr9IU30DBdMNQ==
|
integrity sha512-W7h5dEhywMKenDJh2iX/LABkbFnBxasD27oyXWDS/feDsxiw0dD5ncXdYXgkvAsXIY2MpW/ZKkr9IU30DBdMNQ==
|
||||||
|
|
||||||
yaml@^1.10.0:
|
yaml@^1.10.0, yaml@^1.10.2:
|
||||||
version "1.10.2"
|
version "1.10.2"
|
||||||
resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b"
|
resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b"
|
||||||
integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==
|
integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==
|
||||||
|
@ -11,8 +11,9 @@ export async function login(
|
|||||||
await page.fill('input[name="loginSceneName"]', userName);
|
await page.fill('input[name="loginSceneName"]', userName);
|
||||||
await page.click('button.loginSceneFormSubmit');
|
await page.click('button.loginSceneFormSubmit');
|
||||||
|
|
||||||
|
await page.waitForTimeout(1000);
|
||||||
for (let i = 0; i < characterNumber; i++) {
|
for (let i = 0; i < characterNumber; i++) {
|
||||||
await page.click('button.selectCharacterButtonRight');
|
await page.keyboard.press('ArrowRight');
|
||||||
}
|
}
|
||||||
|
|
||||||
await page.click('button.selectCharacterSceneFormSubmit');
|
await page.click('button.selectCharacterSceneFormSubmit');
|
||||||
|
@ -83,7 +83,9 @@ test.describe('Variables', () => {
|
|||||||
);
|
);
|
||||||
// Redis will reconnect automatically and will store the variable on reconnect!
|
// Redis will reconnect automatically and will store the variable on reconnect!
|
||||||
// So we should see the new value.
|
// So we should see the new value.
|
||||||
await expect(textField).toHaveValue('value set while Redis stopped');
|
await expect(textField).toHaveValue('value set while Redis stopped', {
|
||||||
|
timeout: 60000,
|
||||||
|
});
|
||||||
|
|
||||||
// Now, let's try to kill / reboot the back
|
// Now, let's try to kill / reboot the back
|
||||||
await rebootBack();
|
await rebootBack();
|
||||||
|
Loading…
Reference in New Issue
Block a user