Merge branch 'develop' of github.com:thecodingmachine/workadventure into improve_logging
This commit is contained in:
commit
a1107bd20e
@ -22,6 +22,10 @@ MAX_USERNAME_LENGTH=8
|
|||||||
OPID_CLIENT_ID=
|
OPID_CLIENT_ID=
|
||||||
OPID_CLIENT_SECRET=
|
OPID_CLIENT_SECRET=
|
||||||
OPID_CLIENT_ISSUER=
|
OPID_CLIENT_ISSUER=
|
||||||
|
OPID_CLIENT_REDIRECT_URL=
|
||||||
|
OPID_LOGIN_SCREEN_PROVIDER=http://pusher.workadventure.localhost/login-screen
|
||||||
|
OPID_PROFILE_SCREEN_PROVIDER=
|
||||||
|
DISABLE_ANONYMOUS=
|
||||||
|
|
||||||
# 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
|
# 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
|
||||||
CONTACT_URL=
|
CONTACT_URL=
|
51
.github/workflows/end_to_end_tests.yml
vendored
Normal file
51
.github/workflows/end_to_end_tests.yml
vendored
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
# https://help.github.com/en/categories/automating-your-workflow-with-github-actions
|
||||||
|
|
||||||
|
name: "End to end tests"
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
- develop
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
|
||||||
|
end-to-end-tests:
|
||||||
|
name: "End-to-end testcafe tests"
|
||||||
|
|
||||||
|
runs-on: "ubuntu-latest"
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: "Checkout"
|
||||||
|
uses: "actions/checkout@v2.0.0"
|
||||||
|
|
||||||
|
- name: "Setup .env file"
|
||||||
|
run: cp .env.template .env
|
||||||
|
|
||||||
|
- name: "Edit ownership of file for test cases"
|
||||||
|
run: sudo chown 1000:1000 -R .
|
||||||
|
|
||||||
|
- name: "Start environment"
|
||||||
|
run: docker-compose up -d
|
||||||
|
|
||||||
|
- name: "Wait for environment to build (and downloading testcafe image)"
|
||||||
|
run: (docker-compose -f docker-compose.testcafe.yml pull &) && docker-compose logs -f --tail=0 front | grep -m 1 "Compiled successfully"
|
||||||
|
|
||||||
|
- name: "Run tests"
|
||||||
|
run: docker-compose -f docker-compose.testcafe.yml up --exit-code-from testcafe
|
||||||
|
|
||||||
|
- name: Upload failed tests
|
||||||
|
if: ${{ failure() }}
|
||||||
|
uses: actions/upload-artifact@v2
|
||||||
|
with:
|
||||||
|
name: my-artifact
|
||||||
|
path: './tests/screenshots/'
|
||||||
|
|
||||||
|
- name: Display state
|
||||||
|
if: ${{ failure() }}
|
||||||
|
run: docker-compose ps
|
||||||
|
|
||||||
|
- name: Display logs
|
||||||
|
if: ${{ failure() }}
|
||||||
|
run: docker-compose logs
|
@ -14,6 +14,7 @@ export const MAX_PER_GROUP = parseInt(process.env.MAX_PER_GROUP || "4");
|
|||||||
export const REDIS_HOST = process.env.REDIS_HOST || undefined;
|
export const REDIS_HOST = process.env.REDIS_HOST || undefined;
|
||||||
export const REDIS_PORT = parseInt(process.env.REDIS_PORT || "6379") || 6379;
|
export const REDIS_PORT = parseInt(process.env.REDIS_PORT || "6379") || 6379;
|
||||||
export const REDIS_PASSWORD = process.env.REDIS_PASSWORD || undefined;
|
export const REDIS_PASSWORD = process.env.REDIS_PASSWORD || undefined;
|
||||||
|
export const STORE_VARIABLES_FOR_LOCAL_MAPS = process.env.STORE_VARIABLES_FOR_LOCAL_MAPS === "true";
|
||||||
|
|
||||||
export {
|
export {
|
||||||
MINIMUM_DISTANCE,
|
MINIMUM_DISTANCE,
|
||||||
|
@ -26,6 +26,7 @@ import { VariablesManager } from "../Services/VariablesManager";
|
|||||||
import { ADMIN_API_URL } from "../Enum/EnvironmentVariable";
|
import { ADMIN_API_URL } from "../Enum/EnvironmentVariable";
|
||||||
import { LocalUrlError } from "../Services/LocalUrlError";
|
import { LocalUrlError } from "../Services/LocalUrlError";
|
||||||
import { emitErrorOnRoomSocket } from "../Services/MessageHelpers";
|
import { emitErrorOnRoomSocket } from "../Services/MessageHelpers";
|
||||||
|
import { VariableError } from "../Services/VariableError";
|
||||||
import log from "../Services/Logger";
|
import log from "../Services/Logger";
|
||||||
|
|
||||||
export type ConnectCallback = (user: User, group: Group) => void;
|
export type ConnectCallback = (user: User, group: Group) => void;
|
||||||
@ -337,6 +338,7 @@ export class GameRoom {
|
|||||||
// First, let's check if "user" is allowed to modify the variable.
|
// First, let's check if "user" is allowed to modify the variable.
|
||||||
const variableManager = await this.getVariableManager();
|
const variableManager = await this.getVariableManager();
|
||||||
|
|
||||||
|
try {
|
||||||
const readableBy = variableManager.setVariable(name, value, user);
|
const readableBy = variableManager.setVariable(name, value, user);
|
||||||
|
|
||||||
// If the variable was not changed, let's not dispatch anything.
|
// If the variable was not changed, let's not dispatch anything.
|
||||||
@ -362,6 +364,37 @@ export class GameRoom {
|
|||||||
for (const socket of this.roomListeners) {
|
for (const socket of this.roomListeners) {
|
||||||
socket.write(batchMessage);
|
socket.write(batchMessage);
|
||||||
}
|
}
|
||||||
|
} catch (e) {
|
||||||
|
if (e instanceof VariableError) {
|
||||||
|
// Ok, we have an error setting a variable. Either the user is trying to hack the map... or the map
|
||||||
|
// is not up to date. So let's try to reload the map from scratch.
|
||||||
|
if (this.variableManagerLastLoad === undefined) {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
const lastLoaded = new Date().getTime() - this.variableManagerLastLoad.getTime();
|
||||||
|
if (lastLoaded < 10000) {
|
||||||
|
console.log(
|
||||||
|
'An error occurred while setting the "' +
|
||||||
|
name +
|
||||||
|
"\" variable. But we tried to reload the map less than 10 seconds ago, so let's fail."
|
||||||
|
);
|
||||||
|
// Do not try to reload if we tried to reload less than 10 seconds ago.
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset the variable manager
|
||||||
|
this.variableManagerPromise = undefined;
|
||||||
|
this.mapPromise = undefined;
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
'An error occurred while setting the "' + name + "\" variable. Let's reload the map and try again"
|
||||||
|
);
|
||||||
|
// Try to set the variable again!
|
||||||
|
await this.setVariable(name, value, user);
|
||||||
|
} else {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public addZoneListener(call: ZoneSocket, x: number, y: number): Set<Movable> {
|
public addZoneListener(call: ZoneSocket, x: number, y: number): Set<Movable> {
|
||||||
@ -450,9 +483,11 @@ export class GameRoom {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private variableManagerPromise: Promise<VariablesManager> | undefined;
|
private variableManagerPromise: Promise<VariablesManager> | undefined;
|
||||||
|
private variableManagerLastLoad: Date | undefined;
|
||||||
|
|
||||||
private getVariableManager(): Promise<VariablesManager> {
|
private getVariableManager(): Promise<VariablesManager> {
|
||||||
if (!this.variableManagerPromise) {
|
if (!this.variableManagerPromise) {
|
||||||
|
this.variableManagerLastLoad = new Date();
|
||||||
this.variableManagerPromise = this.getMap()
|
this.variableManagerPromise = this.getMap()
|
||||||
.then((map) => {
|
.then((map) => {
|
||||||
const variablesManager = new VariablesManager(this.roomUrl, map);
|
const variablesManager = new VariablesManager(this.roomUrl, map);
|
||||||
|
@ -5,13 +5,14 @@ import { promisify } from "util";
|
|||||||
import { LocalUrlError } from "./LocalUrlError";
|
import { LocalUrlError } from "./LocalUrlError";
|
||||||
import { ITiledMap } from "@workadventure/tiled-map-type-guard";
|
import { ITiledMap } from "@workadventure/tiled-map-type-guard";
|
||||||
import { isTiledMap } from "@workadventure/tiled-map-type-guard/dist";
|
import { isTiledMap } from "@workadventure/tiled-map-type-guard/dist";
|
||||||
|
import { STORE_VARIABLES_FOR_LOCAL_MAPS } from "../Enum/EnvironmentVariable";
|
||||||
import log from "./Logger";
|
import log from "./Logger";
|
||||||
|
|
||||||
class MapFetcher {
|
class MapFetcher {
|
||||||
async fetchMap(mapUrl: string): Promise<ITiledMap> {
|
async fetchMap(mapUrl: string): Promise<ITiledMap> {
|
||||||
// Before trying to make the query, let's verify the map is actually on the open internet (and not a local test map)
|
// Before trying to make the query, let's verify the map is actually on the open internet (and not a local test map)
|
||||||
|
|
||||||
if (await this.isLocalUrl(mapUrl)) {
|
if ((await this.isLocalUrl(mapUrl)) && !STORE_VARIABLES_FOR_LOCAL_MAPS) {
|
||||||
throw new LocalUrlError('URL for map "' + mapUrl + '" targets a local map');
|
throw new LocalUrlError('URL for map "' + mapUrl + '" targets a local map');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
9
back/src/Services/VariableError.ts
Normal file
9
back/src/Services/VariableError.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
/**
|
||||||
|
* Errors related to variable handling.
|
||||||
|
*/
|
||||||
|
export class VariableError extends Error {
|
||||||
|
constructor(message: string) {
|
||||||
|
super(message);
|
||||||
|
Object.setPrototypeOf(this, VariableError.prototype);
|
||||||
|
}
|
||||||
|
}
|
@ -10,6 +10,7 @@ import {
|
|||||||
import { User } from "_Model/User";
|
import { User } from "_Model/User";
|
||||||
import { variablesRepository } from "./Repository/VariablesRepository";
|
import { variablesRepository } from "./Repository/VariablesRepository";
|
||||||
import { redisClient } from "./RedisClient";
|
import { redisClient } from "./RedisClient";
|
||||||
|
import { VariableError } from "./VariableError";
|
||||||
import log from "./Logger";
|
import log from "./Logger";
|
||||||
|
|
||||||
interface Variable {
|
interface Variable {
|
||||||
@ -175,11 +176,13 @@ export class VariablesManager {
|
|||||||
if (this.variableObjects) {
|
if (this.variableObjects) {
|
||||||
variableObject = this.variableObjects.get(name);
|
variableObject = this.variableObjects.get(name);
|
||||||
if (variableObject === undefined) {
|
if (variableObject === undefined) {
|
||||||
throw new Error('Trying to set a variable "' + name + '" that is not defined as an object in the map.');
|
throw new VariableError(
|
||||||
|
'Trying to set a variable "' + name + '" that is not defined as an object in the map.'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (variableObject.writableBy && !user.tags.includes(variableObject.writableBy)) {
|
if (variableObject.writableBy && !user.tags.includes(variableObject.writableBy)) {
|
||||||
throw new Error(
|
throw new VariableError(
|
||||||
'Trying to set a variable "' +
|
'Trying to set a variable "' +
|
||||||
name +
|
name +
|
||||||
'". User "' +
|
'". User "' +
|
||||||
|
@ -38,6 +38,7 @@ services:
|
|||||||
JITSI_URL: $JITSI_URL
|
JITSI_URL: $JITSI_URL
|
||||||
JITSI_PRIVATE_MODE: "$JITSI_PRIVATE_MODE"
|
JITSI_PRIVATE_MODE: "$JITSI_PRIVATE_MODE"
|
||||||
PUSHER_URL: //pusher.${DOMAIN}
|
PUSHER_URL: //pusher.${DOMAIN}
|
||||||
|
ICON_URL: //icon.${DOMAIN}
|
||||||
TURN_SERVER: "${TURN_SERVER}"
|
TURN_SERVER: "${TURN_SERVER}"
|
||||||
TURN_USER: "${TURN_USER}"
|
TURN_USER: "${TURN_USER}"
|
||||||
TURN_PASSWORD: "${TURN_PASSWORD}"
|
TURN_PASSWORD: "${TURN_PASSWORD}"
|
||||||
@ -98,3 +99,15 @@ services:
|
|||||||
- "traefik.http.routers.back-ssl.service=back"
|
- "traefik.http.routers.back-ssl.service=back"
|
||||||
- "traefik.http.routers.back-ssl.tls.certresolver=myresolver"
|
- "traefik.http.routers.back-ssl.tls.certresolver=myresolver"
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|
||||||
|
icon:
|
||||||
|
image: matthiasluedtke/iconserver:v3.13.0
|
||||||
|
labels:
|
||||||
|
- "traefik.http.routers.icon.rule=Host(`icon.${DOMAIN}`)"
|
||||||
|
- "traefik.http.routers.icon.entryPoints=web,traefik"
|
||||||
|
- "traefik.http.services.icon.loadbalancer.server.port=8080"
|
||||||
|
- "traefik.http.routers.icon-ssl.rule=Host(`icon.${DOMAIN}`)"
|
||||||
|
- "traefik.http.routers.icon-ssl.entryPoints=websecure"
|
||||||
|
- "traefik.http.routers.icon-ssl.tls=true"
|
||||||
|
- "traefik.http.routers.icon-ssl.service=icon"
|
||||||
|
- "traefik.http.routers.icon-ssl.tls.certresolver=myresolver"
|
||||||
|
@ -62,6 +62,7 @@
|
|||||||
} + (if adminUrl != null then {
|
} + (if adminUrl != null then {
|
||||||
"ADMIN_API_URL": adminUrl,
|
"ADMIN_API_URL": adminUrl,
|
||||||
"ADMIN_API_TOKEN": env.ADMIN_API_TOKEN,
|
"ADMIN_API_TOKEN": env.ADMIN_API_TOKEN,
|
||||||
|
"ADMIN_SOCKETS_TOKEN": env.ADMIN_SOCKETS_TOKEN,
|
||||||
} else {})
|
} else {})
|
||||||
},
|
},
|
||||||
"front": {
|
"front": {
|
||||||
|
@ -30,6 +30,7 @@ services:
|
|||||||
UPLOADER_URL: /uploader
|
UPLOADER_URL: /uploader
|
||||||
ADMIN_URL: /admin
|
ADMIN_URL: /admin
|
||||||
MAPS_URL: /maps
|
MAPS_URL: /maps
|
||||||
|
ICON_URL: /icon
|
||||||
STARTUP_COMMAND_1: ./templater.sh
|
STARTUP_COMMAND_1: ./templater.sh
|
||||||
STARTUP_COMMAND_2: yarn install
|
STARTUP_COMMAND_2: yarn install
|
||||||
TURN_SERVER: "turn:localhost:3478,turns:localhost:5349"
|
TURN_SERVER: "turn:localhost:3478,turns:localhost:5349"
|
||||||
@ -40,6 +41,7 @@ services:
|
|||||||
TURN_USER: ""
|
TURN_USER: ""
|
||||||
TURN_PASSWORD: ""
|
TURN_PASSWORD: ""
|
||||||
START_ROOM_URL: "$START_ROOM_URL"
|
START_ROOM_URL: "$START_ROOM_URL"
|
||||||
|
DISABLE_ANONYMOUS: "$DISABLE_ANONYMOUS"
|
||||||
command: yarn run start
|
command: yarn run start
|
||||||
volumes:
|
volumes:
|
||||||
- ./front:/usr/src/app
|
- ./front:/usr/src/app
|
||||||
@ -60,6 +62,8 @@ services:
|
|||||||
environment:
|
environment:
|
||||||
DEBUG: "*"
|
DEBUG: "*"
|
||||||
STARTUP_COMMAND_1: yarn install
|
STARTUP_COMMAND_1: yarn install
|
||||||
|
# wait for files generated by "messages" container to exists
|
||||||
|
STARTUP_COMMAND_2: while [ ! -f /usr/src/app/src/Messages/generated/messages_pb.js ]; do sleep 1; done
|
||||||
SECRET_JITSI_KEY: "$SECRET_JITSI_KEY"
|
SECRET_JITSI_KEY: "$SECRET_JITSI_KEY"
|
||||||
SECRET_KEY: yourSecretKey
|
SECRET_KEY: yourSecretKey
|
||||||
ADMIN_API_TOKEN: "$ADMIN_API_TOKEN"
|
ADMIN_API_TOKEN: "$ADMIN_API_TOKEN"
|
||||||
@ -70,6 +74,9 @@ services:
|
|||||||
OPID_CLIENT_ID: $OPID_CLIENT_ID
|
OPID_CLIENT_ID: $OPID_CLIENT_ID
|
||||||
OPID_CLIENT_SECRET: $OPID_CLIENT_SECRET
|
OPID_CLIENT_SECRET: $OPID_CLIENT_SECRET
|
||||||
OPID_CLIENT_ISSUER: $OPID_CLIENT_ISSUER
|
OPID_CLIENT_ISSUER: $OPID_CLIENT_ISSUER
|
||||||
|
OPID_CLIENT_REDIRECT_URL: $OPID_CLIENT_REDIRECT_URL
|
||||||
|
OPID_PROFILE_SCREEN_PROVIDER: $OPID_PROFILE_SCREEN_PROVIDER
|
||||||
|
DISABLE_ANONYMOUS: $DISABLE_ANONYMOUS
|
||||||
volumes:
|
volumes:
|
||||||
- ./pusher:/usr/src/app
|
- ./pusher:/usr/src/app
|
||||||
labels:
|
labels:
|
||||||
@ -117,6 +124,8 @@ services:
|
|||||||
environment:
|
environment:
|
||||||
DEBUG: "*"
|
DEBUG: "*"
|
||||||
STARTUP_COMMAND_1: yarn install
|
STARTUP_COMMAND_1: yarn install
|
||||||
|
# wait for files generated by "messages" container to exists
|
||||||
|
STARTUP_COMMAND_2: while [ ! -f /usr/src/app/src/Messages/generated/messages_pb.js ]; do sleep 1; done
|
||||||
SECRET_KEY: yourSecretKey
|
SECRET_KEY: yourSecretKey
|
||||||
SECRET_JITSI_KEY: "$SECRET_JITSI_KEY"
|
SECRET_JITSI_KEY: "$SECRET_JITSI_KEY"
|
||||||
ALLOW_ARTILLERY: "true"
|
ALLOW_ARTILLERY: "true"
|
||||||
@ -177,6 +186,20 @@ services:
|
|||||||
redis:
|
redis:
|
||||||
image: redis:6
|
image: redis:6
|
||||||
|
|
||||||
|
icon:
|
||||||
|
image: matthiasluedtke/iconserver:v3.13.0
|
||||||
|
labels:
|
||||||
|
- "traefik.http.middlewares.strip-icon-prefix.stripprefix.prefixes=/icon"
|
||||||
|
- "traefik.http.routers.icon.rule=PathPrefix(`/icon`)"
|
||||||
|
- "traefik.http.routers.icon.middlewares=strip-icon-prefix@docker"
|
||||||
|
- "traefik.http.routers.icon.entryPoints=web"
|
||||||
|
- "traefik.http.services.icon.loadbalancer.server.port=8080"
|
||||||
|
- "traefik.http.routers.icon-ssl.rule=PathPrefix(`/icon`)"
|
||||||
|
- "traefik.http.routers.icon-ssl.middlewares=strip-icon-prefix@docker"
|
||||||
|
- "traefik.http.routers.icon-ssl.entryPoints=websecure"
|
||||||
|
- "traefik.http.routers.icon-ssl.tls=true"
|
||||||
|
- "traefik.http.routers.icon-ssl.service=icon"
|
||||||
|
|
||||||
# coturn:
|
# coturn:
|
||||||
# image: coturn/coturn:4.5.2
|
# image: coturn/coturn:4.5.2
|
||||||
# command:
|
# command:
|
||||||
|
12
docker-compose.testcafe.yml
Normal file
12
docker-compose.testcafe.yml
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
version: "3"
|
||||||
|
services:
|
||||||
|
testcafe:
|
||||||
|
image: testcafe/testcafe:1.17.1
|
||||||
|
working_dir: /tests
|
||||||
|
environment:
|
||||||
|
BROWSER: "chromium --use-fake-device-for-media-stream"
|
||||||
|
volumes:
|
||||||
|
- ./tests:/tests
|
||||||
|
- ./maps:/maps
|
||||||
|
# security_opt:
|
||||||
|
# - seccomp:unconfined
|
@ -17,6 +17,12 @@ services:
|
|||||||
- front
|
- front
|
||||||
volumes:
|
volumes:
|
||||||
- /var/run/docker.sock:/var/run/docker.sock
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
|
networks:
|
||||||
|
default:
|
||||||
|
aliases:
|
||||||
|
- 'play.workadventure.localhost'
|
||||||
|
- 'pusher.workadventure.localhost'
|
||||||
|
- 'maps.workadventure.localhost'
|
||||||
|
|
||||||
front:
|
front:
|
||||||
image: thecodingmachine/nodejs:14
|
image: thecodingmachine/nodejs:14
|
||||||
@ -29,6 +35,7 @@ services:
|
|||||||
PUSHER_URL: //pusher.workadventure.localhost
|
PUSHER_URL: //pusher.workadventure.localhost
|
||||||
UPLOADER_URL: //uploader.workadventure.localhost
|
UPLOADER_URL: //uploader.workadventure.localhost
|
||||||
ADMIN_URL: //workadventure.localhost
|
ADMIN_URL: //workadventure.localhost
|
||||||
|
ICON_URL: //icon.workadventure.localhost
|
||||||
STARTUP_COMMAND_1: ./templater.sh
|
STARTUP_COMMAND_1: ./templater.sh
|
||||||
STARTUP_COMMAND_2: yarn install
|
STARTUP_COMMAND_2: yarn install
|
||||||
STUN_SERVER: "stun:stun.l.google.com:19302"
|
STUN_SERVER: "stun:stun.l.google.com:19302"
|
||||||
@ -42,6 +49,8 @@ services:
|
|||||||
START_ROOM_URL: "$START_ROOM_URL"
|
START_ROOM_URL: "$START_ROOM_URL"
|
||||||
MAX_PER_GROUP: "$MAX_PER_GROUP"
|
MAX_PER_GROUP: "$MAX_PER_GROUP"
|
||||||
MAX_USERNAME_LENGTH: "$MAX_USERNAME_LENGTH"
|
MAX_USERNAME_LENGTH: "$MAX_USERNAME_LENGTH"
|
||||||
|
DISABLE_ANONYMOUS: "$DISABLE_ANONYMOUS"
|
||||||
|
OPID_LOGIN_SCREEN_PROVIDER: "$OPID_LOGIN_SCREEN_PROVIDER"
|
||||||
command: yarn run start
|
command: yarn run start
|
||||||
volumes:
|
volumes:
|
||||||
- ./front:/usr/src/app
|
- ./front:/usr/src/app
|
||||||
@ -60,6 +69,8 @@ services:
|
|||||||
environment:
|
environment:
|
||||||
DEBUG: "socket:*"
|
DEBUG: "socket:*"
|
||||||
STARTUP_COMMAND_1: yarn install
|
STARTUP_COMMAND_1: yarn install
|
||||||
|
# wait for files generated by "messages" container to exists
|
||||||
|
STARTUP_COMMAND_2: while [ ! -f /usr/src/app/src/Messages/generated/messages_pb.js ]; do sleep 1; done
|
||||||
SECRET_JITSI_KEY: "$SECRET_JITSI_KEY"
|
SECRET_JITSI_KEY: "$SECRET_JITSI_KEY"
|
||||||
SECRET_KEY: yourSecretKey
|
SECRET_KEY: yourSecretKey
|
||||||
ADMIN_API_TOKEN: "$ADMIN_API_TOKEN"
|
ADMIN_API_TOKEN: "$ADMIN_API_TOKEN"
|
||||||
@ -70,6 +81,9 @@ services:
|
|||||||
OPID_CLIENT_ID: $OPID_CLIENT_ID
|
OPID_CLIENT_ID: $OPID_CLIENT_ID
|
||||||
OPID_CLIENT_SECRET: $OPID_CLIENT_SECRET
|
OPID_CLIENT_SECRET: $OPID_CLIENT_SECRET
|
||||||
OPID_CLIENT_ISSUER: $OPID_CLIENT_ISSUER
|
OPID_CLIENT_ISSUER: $OPID_CLIENT_ISSUER
|
||||||
|
OPID_CLIENT_REDIRECT_URL: $OPID_CLIENT_REDIRECT_URL
|
||||||
|
OPID_PROFILE_SCREEN_PROVIDER: $OPID_PROFILE_SCREEN_PROVIDER
|
||||||
|
DISABLE_ANONYMOUS: $DISABLE_ANONYMOUS
|
||||||
volumes:
|
volumes:
|
||||||
- ./pusher:/usr/src/app
|
- ./pusher:/usr/src/app
|
||||||
labels:
|
labels:
|
||||||
@ -111,6 +125,8 @@ services:
|
|||||||
environment:
|
environment:
|
||||||
DEBUG: "*"
|
DEBUG: "*"
|
||||||
STARTUP_COMMAND_1: yarn install
|
STARTUP_COMMAND_1: yarn install
|
||||||
|
# wait for files generated by "messages" container to exists
|
||||||
|
STARTUP_COMMAND_2: while [ ! -f /usr/src/app/src/Messages/generated/messages_pb.js ]; do sleep 1; done
|
||||||
SECRET_KEY: yourSecretKey
|
SECRET_KEY: yourSecretKey
|
||||||
SECRET_JITSI_KEY: "$SECRET_JITSI_KEY"
|
SECRET_JITSI_KEY: "$SECRET_JITSI_KEY"
|
||||||
ALLOW_ARTILLERY: "true"
|
ALLOW_ARTILLERY: "true"
|
||||||
@ -121,6 +137,7 @@ services:
|
|||||||
MAX_PER_GROUP: "MAX_PER_GROUP"
|
MAX_PER_GROUP: "MAX_PER_GROUP"
|
||||||
REDIS_HOST: redis
|
REDIS_HOST: redis
|
||||||
NODE_ENV: development
|
NODE_ENV: development
|
||||||
|
STORE_VARIABLES_FOR_LOCAL_MAPS: "true"
|
||||||
volumes:
|
volumes:
|
||||||
- ./back:/usr/src/app
|
- ./back:/usr/src/app
|
||||||
labels:
|
labels:
|
||||||
@ -177,6 +194,17 @@ services:
|
|||||||
- "traefik.http.routers.redisinsight-ssl.tls=true"
|
- "traefik.http.routers.redisinsight-ssl.tls=true"
|
||||||
- "traefik.http.routers.redisinsight-ssl.service=redisinsight"
|
- "traefik.http.routers.redisinsight-ssl.service=redisinsight"
|
||||||
|
|
||||||
|
icon:
|
||||||
|
image: matthiasluedtke/iconserver:v3.13.0
|
||||||
|
labels:
|
||||||
|
- "traefik.http.routers.icon.rule=Host(`icon.workadventure.localhost`)"
|
||||||
|
- "traefik.http.routers.icon.entryPoints=web"
|
||||||
|
- "traefik.http.services.icon.loadbalancer.server.port=8080"
|
||||||
|
- "traefik.http.routers.icon-ssl.rule=Host(`icon.workadventure.localhost`)"
|
||||||
|
- "traefik.http.routers.icon-ssl.entryPoints=websecure"
|
||||||
|
- "traefik.http.routers.icon-ssl.tls=true"
|
||||||
|
- "traefik.http.routers.icon-ssl.service=icon"
|
||||||
|
|
||||||
# coturn:
|
# coturn:
|
||||||
# image: coturn/coturn:4.5.2
|
# image: coturn/coturn:4.5.2
|
||||||
# command:
|
# command:
|
||||||
|
@ -15,7 +15,7 @@ When controls are disabled, the user cannot move anymore using keyboard input. T
|
|||||||
Example:
|
Example:
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
WA.room.onEnterZone('myZone', () => {
|
WA.room.onEnterLayer('myZone').subscribe(() => {
|
||||||
WA.controls.disablePlayerControls();
|
WA.controls.disablePlayerControls();
|
||||||
WA.ui.openPopup("popupRectangle", 'This is an imporant message!', [{
|
WA.ui.openPopup("popupRectangle", 'This is an imporant message!', [{
|
||||||
label: "Got it!",
|
label: "Got it!",
|
||||||
@ -25,5 +25,5 @@ WA.room.onEnterZone('myZone', () => {
|
|||||||
popup.close();
|
popup.close();
|
||||||
}
|
}
|
||||||
}]);
|
}]);
|
||||||
});
|
})
|
||||||
```
|
```
|
||||||
|
@ -49,7 +49,7 @@ Example:
|
|||||||
let helloWorldPopup;
|
let helloWorldPopup;
|
||||||
|
|
||||||
// Open the popup when we enter a given zone
|
// Open the popup when we enter a given zone
|
||||||
helloWorldPopup = WA.room.onEnterZone('myZone', () => {
|
helloWorldPopup = WA.room.onEnterLayer("myZone").subscribe(() => {
|
||||||
WA.ui.openPopup("popupRectangle", 'Hello world!', [{
|
WA.ui.openPopup("popupRectangle", 'Hello world!', [{
|
||||||
label: "Close",
|
label: "Close",
|
||||||
className: "primary",
|
className: "primary",
|
||||||
@ -57,13 +57,13 @@ helloWorldPopup = WA.room.onEnterZone('myZone', () => {
|
|||||||
// Close the popup when the "Close" button is pressed.
|
// Close the popup when the "Close" button is pressed.
|
||||||
popup.close();
|
popup.close();
|
||||||
}
|
}
|
||||||
});
|
|
||||||
}]);
|
}]);
|
||||||
|
});
|
||||||
|
|
||||||
// Close the popup when we leave the zone.
|
// Close the popup when we leave the zone.
|
||||||
WA.room.onLeaveZone('myZone', () => {
|
WA.room.onLeaveLayer("myZone").subscribe(() => {
|
||||||
helloWorldPopup.close();
|
helloWorldPopup.close();
|
||||||
});
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
### Add custom menu
|
### Add custom menu
|
||||||
|
@ -4,7 +4,7 @@ declare let window: any;
|
|||||||
|
|
||||||
class AnalyticsClient {
|
class AnalyticsClient {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
private posthogPromise: Promise<any>;
|
private posthogPromise: Promise<any> | undefined;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
if (POSTHOG_API_KEY && POSTHOG_URL) {
|
if (POSTHOG_API_KEY && POSTHOG_URL) {
|
||||||
@ -14,65 +14,68 @@ class AnalyticsClient {
|
|||||||
window.posthog = posthog;
|
window.posthog = posthog;
|
||||||
return posthog;
|
return posthog;
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
this.posthogPromise = Promise.reject();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
identifyUser(uuid: string, email: string | null) {
|
identifyUser(uuid: string, email: string | null) {
|
||||||
this.posthogPromise
|
this.posthogPromise?.then((posthog) => {
|
||||||
.then((posthog) => {
|
|
||||||
posthog.identify(uuid, { uuid, email, wa: true });
|
posthog.identify(uuid, { uuid, email, wa: true });
|
||||||
})
|
});
|
||||||
.catch();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
loggedWithSso() {
|
loggedWithSso() {
|
||||||
this.posthogPromise
|
this.posthogPromise?.then((posthog) => {
|
||||||
.then((posthog) => {
|
|
||||||
posthog.capture("wa-logged-sso");
|
posthog.capture("wa-logged-sso");
|
||||||
})
|
});
|
||||||
.catch();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
loggedWithToken() {
|
loggedWithToken() {
|
||||||
this.posthogPromise
|
this.posthogPromise?.then((posthog) => {
|
||||||
.then((posthog) => {
|
|
||||||
posthog.capture("wa-logged-token");
|
posthog.capture("wa-logged-token");
|
||||||
})
|
});
|
||||||
.catch();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
enteredRoom(roomId: string, roomGroup: string | null) {
|
enteredRoom(roomId: string, roomGroup: string | null) {
|
||||||
this.posthogPromise
|
this.posthogPromise?.then((posthog) => {
|
||||||
.then((posthog) => {
|
|
||||||
posthog.capture("$pageView", { roomId, roomGroup });
|
posthog.capture("$pageView", { roomId, roomGroup });
|
||||||
})
|
posthog.capture("enteredRoom");
|
||||||
.catch();
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
openedMenu() {
|
openedMenu() {
|
||||||
this.posthogPromise
|
this.posthogPromise?.then((posthog) => {
|
||||||
.then((posthog) => {
|
|
||||||
posthog.capture("wa-opened-menu");
|
posthog.capture("wa-opened-menu");
|
||||||
})
|
});
|
||||||
.catch();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
launchEmote(emote: string) {
|
launchEmote(emote: string) {
|
||||||
this.posthogPromise
|
this.posthogPromise?.then((posthog) => {
|
||||||
.then((posthog) => {
|
|
||||||
posthog.capture("wa-emote-launch", { emote });
|
posthog.capture("wa-emote-launch", { emote });
|
||||||
})
|
});
|
||||||
.catch();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
enteredJitsi(roomName: string, roomId: string) {
|
enteredJitsi(roomName: string, roomId: string) {
|
||||||
this.posthogPromise
|
this.posthogPromise?.then((posthog) => {
|
||||||
.then((posthog) => {
|
|
||||||
posthog.capture("wa-entered-jitsi", { roomName, roomId });
|
posthog.capture("wa-entered-jitsi", { roomName, roomId });
|
||||||
})
|
});
|
||||||
.catch();
|
}
|
||||||
|
|
||||||
|
validationName() {
|
||||||
|
this.posthogPromise?.then((posthog) => {
|
||||||
|
posthog.capture("wa-name-validation");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
validationWoka(scene: string) {
|
||||||
|
this.posthogPromise?.then((posthog) => {
|
||||||
|
posthog.capture("wa-woka-validation", { scene });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
validationVideo() {
|
||||||
|
this.posthogPromise?.then((posthog) => {
|
||||||
|
posthog.capture("wa-video-validation");
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export const analyticsClient = new AnalyticsClient();
|
export const analyticsClient = new AnalyticsClient();
|
||||||
|
@ -49,7 +49,7 @@ class IframeListener {
|
|||||||
public readonly openTabStream = this._openTabStream.asObservable();
|
public readonly openTabStream = this._openTabStream.asObservable();
|
||||||
|
|
||||||
private readonly _loadPageStream: Subject<string> = new Subject();
|
private readonly _loadPageStream: Subject<string> = new Subject();
|
||||||
public readonly loadPageStream = this._loadPageStream.asObservable()
|
public readonly loadPageStream = this._loadPageStream.asObservable();
|
||||||
|
|
||||||
private readonly _disablePlayerControlStream: Subject<void> = new Subject();
|
private readonly _disablePlayerControlStream: Subject<void> = new Subject();
|
||||||
public readonly disablePlayerControlStream = this._disablePlayerControlStream.asObservable();
|
public readonly disablePlayerControlStream = this._disablePlayerControlStream.asObservable();
|
||||||
@ -269,7 +269,7 @@ class IframeListener {
|
|||||||
|
|
||||||
registerScript(scriptUrl: string): Promise<void> {
|
registerScript(scriptUrl: string): Promise<void> {
|
||||||
return new Promise<void>((resolve, reject) => {
|
return new Promise<void>((resolve, reject) => {
|
||||||
console.log("Loading map related script at ", scriptUrl);
|
console.info("Loading map related script at ", scriptUrl);
|
||||||
|
|
||||||
if (!process.env.NODE_ENV || process.env.NODE_ENV === "development") {
|
if (!process.env.NODE_ENV || process.env.NODE_ENV === "development") {
|
||||||
// Using external iframe mode (
|
// Using external iframe mode (
|
||||||
|
@ -18,13 +18,18 @@
|
|||||||
'--font': 'Press Start 2P'
|
'--font': 'Press Start 2P'
|
||||||
},
|
},
|
||||||
emojisPerRow: isMobile() ? 6 : 8,
|
emojisPerRow: isMobile() ? 6 : 8,
|
||||||
autoFocusSearch: false
|
autoFocusSearch: false,
|
||||||
|
style: 'twemoji',
|
||||||
});
|
});
|
||||||
//the timeout is here to prevent the menu from flashing
|
//the timeout is here to prevent the menu from flashing
|
||||||
setTimeout(() => picker.showPicker(emojiContainer), 100);
|
setTimeout(() => picker.showPicker(emojiContainer), 100);
|
||||||
|
|
||||||
picker.on("emoji", (selection) => {
|
picker.on("emoji", (selection) => {
|
||||||
emoteStore.set(selection.emoji);
|
emoteStore.set({
|
||||||
|
unicode: selection.emoji,
|
||||||
|
url: selection.url,
|
||||||
|
name: selection.name
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
picker.on("hidden", () => {
|
picker.on("hidden", () => {
|
||||||
|
@ -4,7 +4,6 @@
|
|||||||
|
|
||||||
let gameScene = gameManager.getCurrentGameScene();
|
let gameScene = gameManager.getCurrentGameScene();
|
||||||
|
|
||||||
let HTMLShareLink: HTMLInputElement;
|
|
||||||
let expandedMapCopyright = false;
|
let expandedMapCopyright = false;
|
||||||
let expandedTilesetCopyright = false;
|
let expandedTilesetCopyright = false;
|
||||||
|
|
||||||
@ -38,35 +37,9 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
function copyLink() {
|
|
||||||
HTMLShareLink.select();
|
|
||||||
document.execCommand('copy');
|
|
||||||
}
|
|
||||||
|
|
||||||
async function shareLink() {
|
|
||||||
const shareData = {url: location.toString()};
|
|
||||||
|
|
||||||
try {
|
|
||||||
await navigator.share(shareData);
|
|
||||||
} catch (err) {
|
|
||||||
console.error('Error: ' + err);
|
|
||||||
copyLink();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="about-room-main">
|
<div class="about-room-main">
|
||||||
<section class="share-url not-mobile">
|
|
||||||
<h3>Share the link of the room !</h3>
|
|
||||||
<input type="text" readonly bind:this={HTMLShareLink} value={location.toString()}>
|
|
||||||
<button type="button" class="nes-btn is-primary" on:click={copyLink}>Copy</button>
|
|
||||||
</section>
|
|
||||||
<section class="is-mobile">
|
|
||||||
<h3>Share the link of the room !</h3>
|
|
||||||
<input type="hidden" readonly bind:this={HTMLShareLink} value={location.toString()}>
|
|
||||||
<button type="button" class="nes-btn is-primary" on:click={shareLink}>Share</button>
|
|
||||||
</section>
|
|
||||||
<h2>Information on the map</h2>
|
<h2>Information on the map</h2>
|
||||||
<section class="container-overflow">
|
<section class="container-overflow">
|
||||||
<h3>{mapName}</h3>
|
<h3>{mapName}</h3>
|
||||||
@ -93,24 +66,6 @@
|
|||||||
div.about-room-main {
|
div.about-room-main {
|
||||||
height: calc(100% - 56px);
|
height: calc(100% - 56px);
|
||||||
|
|
||||||
section.share-url {
|
|
||||||
text-align: center;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
|
|
||||||
input {
|
|
||||||
width: 85%;
|
|
||||||
border-radius: 32px;
|
|
||||||
padding: 3px;
|
|
||||||
}
|
|
||||||
input::selection {
|
|
||||||
background-color: #209cee;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
section.is-mobile {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
h2, h3 {
|
h2, h3 {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
@ -131,16 +86,6 @@
|
|||||||
|
|
||||||
@media only screen and (max-width: 800px), only screen and (max-height: 800px) {
|
@media only screen and (max-width: 800px), only screen and (max-height: 800px) {
|
||||||
div.about-room-main {
|
div.about-room-main {
|
||||||
section.share-url.not-mobile {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
section.is-mobile {
|
|
||||||
display: block;
|
|
||||||
text-align: center;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
section.container-overflow {
|
section.container-overflow {
|
||||||
height: calc(100% - 120px);
|
height: calc(100% - 120px);
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,60 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
|
||||||
|
function goToGettingStarted() {
|
||||||
|
const sparkHost = "https://workadventu.re/getting-started";
|
||||||
|
window.open(sparkHost, "_blank");
|
||||||
|
}
|
||||||
|
|
||||||
|
function goToBuildingMap() {
|
||||||
|
const sparkHost = "https://workadventu.re/map-building/";
|
||||||
|
window.open(sparkHost, "_blank");
|
||||||
|
}
|
||||||
|
|
||||||
import {contactPageStore} from "../../Stores/MenuStore";
|
import {contactPageStore} from "../../Stores/MenuStore";
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<iframe title="contact" src="{$contactPageStore}" allow="clipboard-read; clipboard-write self {$contactPageStore}" allowfullscreen></iframe>
|
<div class="create-map-main">
|
||||||
|
<section class="container-overflow">
|
||||||
|
<section>
|
||||||
|
<h3>Getting started</h3>
|
||||||
|
<p>
|
||||||
|
WorkAdventure allows you to create an online space to communicate spontaneously with others.
|
||||||
|
And it all starts with creating your own space. Choose from a large selection of prefabricated maps by our team.
|
||||||
|
</p>
|
||||||
|
<button type="button" class="nes-btn is-primary" on:click={goToGettingStarted}>Getting started</button>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h3>Create your map</h3>
|
||||||
|
<p>You can also create your own custom map by following the step of the documentation.</p>
|
||||||
|
<button type="button" class="nes-btn" on:click={goToBuildingMap}>Create your map</button>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<iframe title="contact"
|
||||||
|
src="{$contactPageStore}"
|
||||||
|
allow="clipboard-read; clipboard-write self {$contactPageStore}"
|
||||||
|
allowfullscreen></iframe>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
div.create-map-main {
|
||||||
|
height: calc(100% - 56px);
|
||||||
|
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
section {
|
||||||
|
margin-bottom: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
section.container-overflow {
|
||||||
|
height: 100%;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
iframe {
|
iframe {
|
||||||
border: none;
|
border: none;
|
||||||
height: calc(100% - 56px);
|
height: calc(100% - 56px);
|
||||||
|
@ -1,51 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
|
|
||||||
function goToGettingStarted() {
|
|
||||||
const sparkHost = "https://workadventu.re/getting-started";
|
|
||||||
window.open(sparkHost, "_blank");
|
|
||||||
}
|
|
||||||
|
|
||||||
function goToBuildingMap() {
|
|
||||||
const sparkHost = "https://workadventu.re/map-building/";
|
|
||||||
window.open(sparkHost, "_blank");
|
|
||||||
}
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="create-map-main">
|
|
||||||
<section class="container-overflow">
|
|
||||||
<section>
|
|
||||||
<h3>Getting started</h3>
|
|
||||||
<p>
|
|
||||||
WorkAdventure allows you to create an online space to communicate spontaneously with others.
|
|
||||||
And it all starts with creating your own space. Choose from a large selection of prefabricated maps by our team.
|
|
||||||
</p>
|
|
||||||
<button type="button" class="nes-btn is-primary" on:click={goToGettingStarted}>Getting started</button>
|
|
||||||
</section>
|
|
||||||
<section>
|
|
||||||
<h3>Create your map</h3>
|
|
||||||
<p>You can also create your own custom map by following the step of the documentation.</p>
|
|
||||||
<button type="button" class="nes-btn" on:click={goToBuildingMap}>Create your map</button>
|
|
||||||
</section>
|
|
||||||
</section>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style lang="scss">
|
|
||||||
div.create-map-main {
|
|
||||||
height: calc(100% - 56px);
|
|
||||||
|
|
||||||
text-align: center;
|
|
||||||
|
|
||||||
section {
|
|
||||||
margin-bottom: 50px;
|
|
||||||
}
|
|
||||||
|
|
||||||
section.container-overflow {
|
|
||||||
height: 100%;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
overflow: auto;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
</style>
|
|
75
front/src/Components/Menu/GuestSubMenu.svelte
Normal file
75
front/src/Components/Menu/GuestSubMenu.svelte
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
function copyLink() {
|
||||||
|
const input: HTMLInputElement = document.getElementById('input-share-link') as HTMLInputElement;
|
||||||
|
input.focus();
|
||||||
|
input.select();
|
||||||
|
document.execCommand('copy');
|
||||||
|
}
|
||||||
|
|
||||||
|
async function shareLink() {
|
||||||
|
const shareData = {url: location.toString()};
|
||||||
|
|
||||||
|
try {
|
||||||
|
await navigator.share(shareData);
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error: ' + err);
|
||||||
|
copyLink();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="guest-main">
|
||||||
|
<section class="container-overflow">
|
||||||
|
<section class="share-url not-mobile">
|
||||||
|
<h3>Share the link of the room !</h3>
|
||||||
|
<input type="text" readonly id="input-share-link" value={location.toString()}>
|
||||||
|
<button type="button" class="nes-btn is-primary" on:click={copyLink}>Copy</button>
|
||||||
|
</section>
|
||||||
|
<section class="is-mobile">
|
||||||
|
<h3>Share the link of the room !</h3>
|
||||||
|
<input type="hidden" readonly id="input-share-link" value={location.toString()}>
|
||||||
|
<button type="button" class="nes-btn is-primary" on:click={shareLink}>Share</button>
|
||||||
|
</section>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
div.guest-main {
|
||||||
|
height: calc(100% - 56px);
|
||||||
|
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
section {
|
||||||
|
margin-bottom: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
section.container-overflow {
|
||||||
|
height: 100%;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
section.is-mobile {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-width: 900px), only screen and (max-height: 600px) {
|
||||||
|
div.guest-main {
|
||||||
|
section.share-url.not-mobile {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
section.is-mobile {
|
||||||
|
display: block;
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
section.container-overflow {
|
||||||
|
height: calc(100% - 120px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
@ -2,11 +2,11 @@
|
|||||||
import {fly} from "svelte/transition";
|
import {fly} from "svelte/transition";
|
||||||
import SettingsSubMenu from "./SettingsSubMenu.svelte";
|
import SettingsSubMenu from "./SettingsSubMenu.svelte";
|
||||||
import ProfileSubMenu from "./ProfileSubMenu.svelte";
|
import ProfileSubMenu from "./ProfileSubMenu.svelte";
|
||||||
import CreateMapSubMenu from "./CreateMapSubMenu.svelte";
|
|
||||||
import AboutRoomSubMenu from "./AboutRoomSubMenu.svelte";
|
import AboutRoomSubMenu from "./AboutRoomSubMenu.svelte";
|
||||||
import GlobalMessageSubMenu from "./GlobalMessagesSubMenu.svelte";
|
import GlobalMessageSubMenu from "./GlobalMessagesSubMenu.svelte";
|
||||||
import ContactSubMenu from "./ContactSubMenu.svelte";
|
import ContactSubMenu from "./ContactSubMenu.svelte";
|
||||||
import CustomSubMenu from "./CustomSubMenu.svelte"
|
import CustomSubMenu from "./CustomSubMenu.svelte"
|
||||||
|
import GuestSubMenu from "./GuestSubMenu.svelte";
|
||||||
import {
|
import {
|
||||||
checkSubMenuToShow,
|
checkSubMenuToShow,
|
||||||
customMenuIframe,
|
customMenuIframe,
|
||||||
@ -19,21 +19,21 @@
|
|||||||
import type {Unsubscriber} from "svelte/store";
|
import type {Unsubscriber} from "svelte/store";
|
||||||
import {sendMenuClickedEvent} from "../../Api/iframe/Ui/MenuItem";
|
import {sendMenuClickedEvent} from "../../Api/iframe/Ui/MenuItem";
|
||||||
|
|
||||||
let activeSubMenu: string = SubMenusInterface.settings;
|
let activeSubMenu: string = SubMenusInterface.profile;
|
||||||
let activeComponent: typeof SettingsSubMenu | typeof CustomSubMenu = SettingsSubMenu;
|
let activeComponent: typeof ProfileSubMenu | typeof CustomSubMenu = ProfileSubMenu;
|
||||||
let props: { url: string, allowApi: boolean };
|
let props: { url: string, allowApi: boolean };
|
||||||
let unsubscriberSubMenuStore: Unsubscriber;
|
let unsubscriberSubMenuStore: Unsubscriber;
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
unsubscriberSubMenuStore = subMenusStore.subscribe(() => {
|
unsubscriberSubMenuStore = subMenusStore.subscribe(() => {
|
||||||
if(!get(subMenusStore).includes(activeSubMenu)) {
|
if(!get(subMenusStore).includes(activeSubMenu)) {
|
||||||
switchMenu(SubMenusInterface.settings);
|
switchMenu(SubMenusInterface.profile);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
checkSubMenuToShow();
|
checkSubMenuToShow();
|
||||||
|
|
||||||
switchMenu(SubMenusInterface.settings);
|
switchMenu(SubMenusInterface.profile);
|
||||||
})
|
})
|
||||||
|
|
||||||
onDestroy(() => {
|
onDestroy(() => {
|
||||||
@ -52,8 +52,8 @@
|
|||||||
case SubMenusInterface.profile:
|
case SubMenusInterface.profile:
|
||||||
activeComponent = ProfileSubMenu;
|
activeComponent = ProfileSubMenu;
|
||||||
break;
|
break;
|
||||||
case SubMenusInterface.createMap:
|
case SubMenusInterface.invite:
|
||||||
activeComponent = CreateMapSubMenu;
|
activeComponent = GuestSubMenu;
|
||||||
break;
|
break;
|
||||||
case SubMenusInterface.aboutRoom:
|
case SubMenusInterface.aboutRoom:
|
||||||
activeComponent = AboutRoomSubMenu;
|
activeComponent = AboutRoomSubMenu;
|
||||||
|
@ -11,15 +11,9 @@
|
|||||||
function showChat(){
|
function showChat(){
|
||||||
chatVisibilityStore.set(true);
|
chatVisibilityStore.set(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
function onKeyDown(e: KeyboardEvent) {
|
|
||||||
if (e.key === "Tab") {
|
|
||||||
showMenu();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:window on:keydown={onKeyDown}/>
|
<svelte:window/>
|
||||||
|
|
||||||
<main class="menuIcon">
|
<main class="menuIcon">
|
||||||
<img src={logoWA} alt="open menu" class="nes-pointer" on:click|preventDefault={showMenu}>
|
<img src={logoWA} alt="open menu" class="nes-pointer" on:click|preventDefault={showMenu}>
|
||||||
|
@ -12,6 +12,10 @@
|
|||||||
import {localUserStore} from "../../Connexion/LocalUserStore";
|
import {localUserStore} from "../../Connexion/LocalUserStore";
|
||||||
import {EnableCameraScene, EnableCameraSceneName} from "../../Phaser/Login/EnableCameraScene";
|
import {EnableCameraScene, EnableCameraSceneName} from "../../Phaser/Login/EnableCameraScene";
|
||||||
import {enableCameraSceneVisibilityStore} from "../../Stores/MediaStore";
|
import {enableCameraSceneVisibilityStore} from "../../Stores/MediaStore";
|
||||||
|
import btnProfileSubMenuCamera from "../images/btn-menu-profile-camera.svg";
|
||||||
|
import btnProfileSubMenuIdentity from "../images/btn-menu-profile-identity.svg";
|
||||||
|
import btnProfileSubMenuCompanion from "../images/btn-menu-profile-companion.svg";
|
||||||
|
import btnProfileSubMenuWoka from "../images/btn-menu-profile-woka.svg";
|
||||||
|
|
||||||
|
|
||||||
function disableMenuStores(){
|
function disableMenuStores(){
|
||||||
@ -55,6 +59,28 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="customize-main">
|
<div class="customize-main">
|
||||||
|
<div class="submenu">
|
||||||
|
<section>
|
||||||
|
<button type="button" class="nes-btn" on:click|preventDefault={openEditNameScene}>
|
||||||
|
<img src={btnProfileSubMenuIdentity} alt="Edit your name">
|
||||||
|
<span class="btn-hover">Edit your name</span>
|
||||||
|
</button>
|
||||||
|
<button type="button" class="nes-btn" on:click|preventDefault={openEditSkinScene}>
|
||||||
|
<img src={btnProfileSubMenuWoka} alt="Edit your WOKA">
|
||||||
|
<span class="btn-hover">Edit your WOKA</span>
|
||||||
|
</button>
|
||||||
|
<button type="button" class="nes-btn" on:click|preventDefault={openEditCompanionScene}>
|
||||||
|
<img src={btnProfileSubMenuCompanion} alt="Edit your companion">
|
||||||
|
<span class="btn-hover">Edit your companion</span>
|
||||||
|
</button>
|
||||||
|
<button type="button" class="nes-btn" on:click|preventDefault={openEnableCameraScene}>
|
||||||
|
<img src={btnProfileSubMenuCamera} alt="Edit your camera">
|
||||||
|
<span class="btn-hover">Edit your camera</span>
|
||||||
|
</button>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="content">
|
||||||
{#if $userIsConnected}
|
{#if $userIsConnected}
|
||||||
<section>
|
<section>
|
||||||
{#if PROFILE_URL != undefined}
|
{#if PROFILE_URL != undefined}
|
||||||
@ -69,18 +95,48 @@
|
|||||||
<a type="button" class="nes-btn" href="/login">Sign in</a>
|
<a type="button" class="nes-btn" href="/login">Sign in</a>
|
||||||
</section>
|
</section>
|
||||||
{/if}
|
{/if}
|
||||||
<section>
|
</div>
|
||||||
<button type="button" class="nes-btn" on:click|preventDefault={openEditNameScene}>Edit Name</button>
|
|
||||||
<button type="button" class="nes-btn" on:click|preventDefault={openEditSkinScene}>Edit Skin</button>
|
|
||||||
<button type="button" class="nes-btn" on:click|preventDefault={openEditCompanionScene}>Edit Companion</button>
|
|
||||||
</section>
|
|
||||||
<section>
|
|
||||||
<button type="button" class="nes-btn" on:click|preventDefault={openEnableCameraScene}>Setup camera</button>
|
|
||||||
</section>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
div.customize-main{
|
div.customize-main{
|
||||||
|
width: 100%;
|
||||||
|
display: inline-flex;
|
||||||
|
|
||||||
|
div.submenu{
|
||||||
|
height: 100%;
|
||||||
|
width: 50px;
|
||||||
|
|
||||||
|
button {
|
||||||
|
transition: all .5s ease;
|
||||||
|
text-align: left;
|
||||||
|
white-space: nowrap;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
max-height: 44px;
|
||||||
|
|
||||||
|
img {
|
||||||
|
height: 26px;
|
||||||
|
width: 26px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
span.btn-hover{
|
||||||
|
display: none;
|
||||||
|
font-family: "Press Start 2P";
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover{
|
||||||
|
width: auto;
|
||||||
|
|
||||||
|
span.btn-hover {
|
||||||
|
display: initial;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
div.content {
|
||||||
|
width: 100%;
|
||||||
section {
|
section {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
@ -100,9 +156,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@media only screen and (max-width: 800px) {
|
@media only screen and (max-width: 800px) {
|
||||||
div.customize-main section button {
|
div.customize-main.content section button {
|
||||||
width: 130px;
|
width: 130px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
1
front/src/Components/images/btn-menu-profile-camera.svg
Normal file
1
front/src/Components/images/btn-menu-profile-camera.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg id="Calque_1" data-name="Calque 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 50 50"><g id="btn_setup_camera" data-name="btn setup camera"><circle cx="24.71" cy="13.11" r="1.46"/><path d="M8.65,23.34H40.78V2.89H31.16L28.24,0h-7L18.27,2.89H8.65ZM32,8.73h2.92v2.92H32Zm-7.31,0a4.39,4.39,0,1,1-4.38,4.38A4.39,4.39,0,0,1,24.71,8.73Z"/><path d="M2.81,44H5.73v5.84h8.76V44h5.84V46.9h2.92V44h5.84V46.9H32V44h5.84V46.9h2.92V44h5.84V41.06h-33l-3.52-3.53L6.58,41.06H2.81Z"/><path d="M2.81,32.3H8.65v2.92h2.92V32.3h5.84v2.92h2.92V32.3h5.84v2.92h2.92V32.3h5.85v5.84H43.7V32.3h2.92V29.37H42.84l-3.52-3.52-3.53,3.52h-33Z"/></g></svg>
|
After Width: | Height: | Size: 629 B |
@ -0,0 +1 @@
|
|||||||
|
<svg id="Calque_1" data-name="Calque 1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 50 50"><g id="btn_setup_companion" data-name="btn setup companion"><g id="iNhyGC.tif"><image id="Layer_0" data-name="Layer 0" width="15" height="30" transform="translate(13.21 0.25) scale(1.65)" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAfCAYAAADupU20AAAACXBIWXMAAAbEAAAGxAGo1xHEAAAB20lEQVQ4T51UO04DMRScRYmEhGWlApRsBcUap4VUXICSjpqaY3ACSgpOgERQKnpoNjXRhjrkANZGikRhiuDH8w8BI7l5n/H4fVxYaxFC66FnnM1eiyjoC53QoPXQjkYnkS1HUnAFWg+tUhXatsVi8Q4AUKqClBJ1PU0q8RTMZq+Fk++CU4o4oiekbjHGhCbCVtbzS/xIwGuSQ/GbNgL5VkYEWg9tWQ4ghCCb60qKJGqjS5ZSkt0YkyWhGjjZYTIASClJUfg8r4hKVWiaOYwx0WmaOZSqEKIDfEu/PDvGHYCmmUeBSlW4PDvGTdt6o00Krq8uAIDkl+UASlUoy4Fnd3EOHXc7AOzu7QN4o/l3ie79G/+G3Kkojo50svI5hB3pAOnK5xDG0TLV9ZSMqe3jft4NKuLt/Qu6qzXG40m0fcYYjMcTdFdr3N6/eL4tYDOqhwc9AMDT4wNScPbDg563XFTEfr8P+3C6MZ4/RwTct1wuqYiFtTa5A3U9RXe1xsfONtUktRNUxMXi3Rua0egExhgi5MkcRFCWA3I6IgD0Xu7jJEQghKDE8BaX6OI4vE9VCEE38r3nqx7CW2c+9yFy00oE/x1lInDTlyPi3eCgP5F/VanPM+dPfut/wScEgxZt/KHdLgAAAABJRU5ErkJggg=="/></g></g></svg>
|
After Width: | Height: | Size: 1.1 KiB |
@ -0,0 +1 @@
|
|||||||
|
<svg id="Calque_1" data-name="Calque 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 50 50"><g id="btn_setup_identity" data-name="btn setup identity"><path d="M31.94,6l-4.35,6.39a3.63,3.63,0,1,1-3.07-1.7,3.71,3.71,0,0,1,.67.06l4-5.89a16,16,0,0,0-9.37,0L17,.76a1.45,1.45,0,0,0-2.4,1.64L17.09,6A16,16,0,0,0,8.56,20.15V33.69a16,16,0,0,0,31.91,0V20.15A16,16,0,0,0,31.94,6Zm-.65,32.49H17.75a1.45,1.45,0,1,1,0-2.9H31.29a1.45,1.45,0,0,1,0,2.9Zm0-5.8H17.75a1.45,1.45,0,1,1,0-2.9H31.29a1.45,1.45,0,1,1,0,2.9Zm0-5.8H17.75a1.45,1.45,0,1,1,0-2.9H31.29a1.45,1.45,0,1,1,0,2.9Z"/><path d="M34.42,2.4A1.45,1.45,0,1,0,32,.76L29.21,4.89A16.38,16.38,0,0,1,31.94,6Z"/></g></svg>
|
After Width: | Height: | Size: 661 B |
1
front/src/Components/images/btn-menu-profile-woka.svg
Normal file
1
front/src/Components/images/btn-menu-profile-woka.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg id="Calque_1" data-name="Calque 1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 50 50"><g id="btn_setup_woka" data-name="btn setup woka"><g id="NP8bMB.tif"><image id="Layer_0" data-name="Layer 0" width="23" height="29" transform="translate(4.61 0.1) scale(1.71)" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABcAAAAdCAYAAABBsffGAAAACXBIWXMAAAZ1AAAGdQGEn07tAAACCUlEQVRIS61WS4rDMAx9abLoxuqi4MIYSk+Q+59iBnqCUlBhDNnYmy4aMosgj+3YyRTmQReVpSdZP6eZpglrMOajqsD8aGpnANDUyIW0bTsAwPF4DGfDMAAAxvEFoO6kSG7Mx9S2XSBUSi10vPcAZkfj+Co6WJDHxCXSHN77qoOuZJATE9FCxzkH4PdW1n4vdHbxH4k6Rom4JG/bblH8hBxIo64RC+RcKZUUXLAgF2wRC9b0AvlaP7+DmGcnAq1PdYs3oPUpOKim5T+wA+YJk1aS6XsXYmftd+j3EPnWnvgrYp4kLcyPZhxf8N6HIdmCcw7e+79PaDxta60mxKXpBLLdEk/oOL6wtmPinRLbxNEHciHu+34zohhan6CUAhHher0mDpK0xMQSrTEGAJIaSKqYGUqpsH77vsfX12fQ62aCOeo8YmMMDodDUHbOgYgSGTMnN5QFxvxoQrdcLhcA633+fD6rZ2InPADQSdREBGYOT1fuxFqL/X4Pa22QMXPy5HnvYYwJ0e+AOVc5oXSJcw7OOWitQUTQWgdZvmrFXvhCWpxzSaWHYQiFk6GSnxSQiAKhDGBc+I750cgWK60AIsLtdlukSbooRs5V/bQ4n8+TpCbveeltGaT7/V7cS9WVK4WVFADpzUQueiWs7nOlFIwxi/GvyXOskm+9o1vn1ZwD6XsYp6Qmz/ED0qpw9h1b2uQAAAAASUVORK5CYII="/></g></g></svg>
|
After Width: | Height: | Size: 1.1 KiB |
@ -41,7 +41,6 @@ class ConnectionManager {
|
|||||||
const nonce = localUserStore.generateNonce();
|
const nonce = localUserStore.generateNonce();
|
||||||
localUserStore.setAuthToken(null);
|
localUserStore.setAuthToken(null);
|
||||||
|
|
||||||
//TODO fix me to redirect this URL by pusher
|
|
||||||
if (!this._currentRoom || !this._currentRoom.iframeAuthentication) {
|
if (!this._currentRoom || !this._currentRoom.iframeAuthentication) {
|
||||||
loginSceneVisibleIframeStore.set(false);
|
loginSceneVisibleIframeStore.set(false);
|
||||||
return null;
|
return null;
|
||||||
@ -79,6 +78,16 @@ class ConnectionManager {
|
|||||||
const connexionType = urlManager.getGameConnexionType();
|
const connexionType = urlManager.getGameConnexionType();
|
||||||
this.connexionType = connexionType;
|
this.connexionType = connexionType;
|
||||||
this._currentRoom = null;
|
this._currentRoom = null;
|
||||||
|
|
||||||
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
|
const token = urlParams.get("token");
|
||||||
|
if (token) {
|
||||||
|
this.authToken = token;
|
||||||
|
localUserStore.setAuthToken(token);
|
||||||
|
//token was saved, clear url
|
||||||
|
urlParams.delete("token");
|
||||||
|
}
|
||||||
|
|
||||||
if (connexionType === GameConnexionTypes.login) {
|
if (connexionType === GameConnexionTypes.login) {
|
||||||
this._currentRoom = await Room.createRoom(new URL(localUserStore.getLastRoomUrl()));
|
this._currentRoom = await Room.createRoom(new URL(localUserStore.getLastRoomUrl()));
|
||||||
if (this.loadOpenIDScreen() !== null) {
|
if (this.loadOpenIDScreen() !== null) {
|
||||||
@ -87,6 +96,8 @@ class ConnectionManager {
|
|||||||
urlManager.pushRoomIdToUrl(this._currentRoom);
|
urlManager.pushRoomIdToUrl(this._currentRoom);
|
||||||
} else if (connexionType === GameConnexionTypes.jwt) {
|
} else if (connexionType === GameConnexionTypes.jwt) {
|
||||||
const urlParams = new URLSearchParams(window.location.search);
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
|
|
||||||
|
if (!token) {
|
||||||
const code = urlParams.get("code");
|
const code = urlParams.get("code");
|
||||||
const state = urlParams.get("state");
|
const state = urlParams.get("state");
|
||||||
if (!state || !localUserStore.verifyState(state)) {
|
if (!state || !localUserStore.verifyState(state)) {
|
||||||
@ -96,6 +107,8 @@ class ConnectionManager {
|
|||||||
throw "No Auth code provided";
|
throw "No Auth code provided";
|
||||||
}
|
}
|
||||||
localUserStore.setCode(code);
|
localUserStore.setCode(code);
|
||||||
|
}
|
||||||
|
|
||||||
this._currentRoom = await Room.createRoom(new URL(localUserStore.getLastRoomUrl()));
|
this._currentRoom = await Room.createRoom(new URL(localUserStore.getLastRoomUrl()));
|
||||||
try {
|
try {
|
||||||
await this.checkAuthUserConnexion();
|
await this.checkAuthUserConnexion();
|
||||||
@ -164,14 +177,20 @@ class ConnectionManager {
|
|||||||
//before set token of user we must load room and all information. For example the mandatory authentication could be require on current room
|
//before set token of user we must load room and all information. For example the mandatory authentication could be require on current room
|
||||||
this._currentRoom = await Room.createRoom(new URL(roomPath));
|
this._currentRoom = await Room.createRoom(new URL(roomPath));
|
||||||
|
|
||||||
|
//defined last room url this room path
|
||||||
|
localUserStore.setLastRoomUrl(this._currentRoom.key);
|
||||||
|
|
||||||
//todo: add here some kind of warning if authToken has expired.
|
//todo: add here some kind of warning if authToken has expired.
|
||||||
if (!this.authToken && !this._currentRoom.authenticationMandatory) {
|
if (!this.authToken && !this._currentRoom.authenticationMandatory) {
|
||||||
await this.anonymousLogin();
|
await this.anonymousLogin();
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
await this.checkAuthUserConnexion();
|
await this.checkAuthUserConnexion();
|
||||||
|
analyticsClient.loggedWithSso();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
|
this.loadOpenIDScreen();
|
||||||
|
return Promise.reject(new Error("You will be redirect on login page"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.localUser = localUserStore.getLocalUser() as LocalUser; //if authToken exist in localStorage then localUser cannot be null
|
this.localUser = localUserStore.getLocalUser() as LocalUser; //if authToken exist in localStorage then localUser cannot be null
|
||||||
@ -199,13 +218,15 @@ class ConnectionManager {
|
|||||||
analyticsClient.identifyUser(this.localUser.uuid, this.localUser.email);
|
analyticsClient.identifyUser(this.localUser.uuid, this.localUser.email);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//clean history with new URL
|
||||||
|
window.history.pushState({}, document.title, window.location.pathname);
|
||||||
this.serviceWorker = new _ServiceWorker();
|
this.serviceWorker = new _ServiceWorker();
|
||||||
return Promise.resolve(this._currentRoom);
|
return Promise.resolve(this._currentRoom);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async anonymousLogin(isBenchmark: boolean = false): Promise<void> {
|
public async anonymousLogin(isBenchmark: boolean = false): Promise<void> {
|
||||||
const data = await Axios.post(`${PUSHER_URL}/anonymLogin`).then((res) => res.data);
|
const data = await Axios.post(`${PUSHER_URL}/anonymLogin`).then((res) => res.data);
|
||||||
this.localUser = new LocalUser(data.userUuid, []);
|
this.localUser = new LocalUser(data.userUuid, [], data.email);
|
||||||
this.authToken = data.authToken;
|
this.authToken = data.authToken;
|
||||||
if (!isBenchmark) {
|
if (!isBenchmark) {
|
||||||
// In benchmark, we don't have a local storage.
|
// In benchmark, we don't have a local storage.
|
||||||
@ -279,20 +300,25 @@ class ConnectionManager {
|
|||||||
//set connected store for menu at false
|
//set connected store for menu at false
|
||||||
userIsConnected.set(false);
|
userIsConnected.set(false);
|
||||||
|
|
||||||
|
const token = localUserStore.getAuthToken();
|
||||||
const state = localUserStore.getState();
|
const state = localUserStore.getState();
|
||||||
const code = localUserStore.getCode();
|
const code = localUserStore.getCode();
|
||||||
|
const nonce = localUserStore.getNonce();
|
||||||
|
|
||||||
|
if (!token) {
|
||||||
if (!state || !localUserStore.verifyState(state)) {
|
if (!state || !localUserStore.verifyState(state)) {
|
||||||
throw "Could not validate state!";
|
throw "Could not validate state!";
|
||||||
}
|
}
|
||||||
if (!code) {
|
if (!code) {
|
||||||
throw "No Auth code provided";
|
throw "No Auth code provided";
|
||||||
}
|
}
|
||||||
const nonce = localUserStore.getNonce();
|
}
|
||||||
const token = localUserStore.getAuthToken();
|
const { authToken, userUuid, textures, email } = await Axios.get(`${PUSHER_URL}/login-callback`, {
|
||||||
const { authToken } = await Axios.get(`${PUSHER_URL}/login-callback`, { params: { code, nonce, token } }).then(
|
params: { code, nonce, token, playUri: this.currentRoom?.key },
|
||||||
(res) => res.data
|
}).then((res) => res.data);
|
||||||
);
|
|
||||||
localUserStore.setAuthToken(authToken);
|
localUserStore.setAuthToken(authToken);
|
||||||
|
this.localUser = new LocalUser(userUuid, textures, email);
|
||||||
|
localUserStore.saveUser(this.localUser);
|
||||||
this.authToken = authToken;
|
this.authToken = authToken;
|
||||||
|
|
||||||
//user connected, set connected store for menu at true
|
//user connected, set connected store for menu at true
|
||||||
|
@ -122,17 +122,22 @@ class LocalUserStore {
|
|||||||
|
|
||||||
setLastRoomUrl(roomUrl: string): void {
|
setLastRoomUrl(roomUrl: string): void {
|
||||||
localStorage.setItem(lastRoomUrl, roomUrl.toString());
|
localStorage.setItem(lastRoomUrl, roomUrl.toString());
|
||||||
|
if ("caches" in window) {
|
||||||
caches.open(cacheAPIIndex).then((cache) => {
|
caches.open(cacheAPIIndex).then((cache) => {
|
||||||
const stringResponse = new Response(JSON.stringify({ roomUrl }));
|
const stringResponse = new Response(JSON.stringify({ roomUrl }));
|
||||||
cache.put(`/${lastRoomUrl}`, stringResponse);
|
cache.put(`/${lastRoomUrl}`, stringResponse);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
getLastRoomUrl(): string {
|
getLastRoomUrl(): string {
|
||||||
return (
|
return (
|
||||||
localStorage.getItem(lastRoomUrl) ?? window.location.protocol + "//" + window.location.host + START_ROOM_URL
|
localStorage.getItem(lastRoomUrl) ?? window.location.protocol + "//" + window.location.host + START_ROOM_URL
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
getLastRoomUrlCacheApi(): Promise<string | undefined> {
|
getLastRoomUrlCacheApi(): Promise<string | undefined> {
|
||||||
|
if (!("caches" in window)) {
|
||||||
|
return Promise.resolve(undefined);
|
||||||
|
}
|
||||||
return caches.open(cacheAPIIndex).then((cache) => {
|
return caches.open(cacheAPIIndex).then((cache) => {
|
||||||
return cache.match(`/${lastRoomUrl}`).then((res) => {
|
return cache.match(`/${lastRoomUrl}`).then((res) => {
|
||||||
return res?.json().then((data) => {
|
return res?.json().then((data) => {
|
||||||
@ -165,8 +170,15 @@ class LocalUserStore {
|
|||||||
|
|
||||||
verifyState(value: string): boolean {
|
verifyState(value: string): boolean {
|
||||||
const oldValue = localStorage.getItem(state);
|
const oldValue = localStorage.getItem(state);
|
||||||
|
if (!oldValue) {
|
||||||
|
localStorage.setItem(state, value);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
return oldValue === value;
|
return oldValue === value;
|
||||||
}
|
}
|
||||||
|
setState(value: string) {
|
||||||
|
localStorage.setItem(state, value);
|
||||||
|
}
|
||||||
getState(): string | null {
|
getState(): string | null {
|
||||||
return localStorage.getItem(state);
|
return localStorage.getItem(state);
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import Axios from "axios";
|
import Axios from "axios";
|
||||||
import { CONTACT_URL, PUSHER_URL } from "../Enum/EnvironmentVariable";
|
import { CONTACT_URL, PUSHER_URL, DISABLE_ANONYMOUS, OPID_LOGIN_SCREEN_PROVIDER } from "../Enum/EnvironmentVariable";
|
||||||
import type { CharacterTexture } from "./LocalUser";
|
import type { CharacterTexture } from "./LocalUser";
|
||||||
import { localUserStore } from "./LocalUserStore";
|
import { localUserStore } from "./LocalUserStore";
|
||||||
|
|
||||||
@ -14,8 +14,8 @@ export interface RoomRedirect {
|
|||||||
export class Room {
|
export class Room {
|
||||||
public readonly id: string;
|
public readonly id: string;
|
||||||
public readonly isPublic: boolean;
|
public readonly isPublic: boolean;
|
||||||
private _authenticationMandatory: boolean = false;
|
private _authenticationMandatory: boolean = DISABLE_ANONYMOUS;
|
||||||
private _iframeAuthentication?: string;
|
private _iframeAuthentication?: string = OPID_LOGIN_SCREEN_PROVIDER;
|
||||||
private _mapUrl: string | undefined;
|
private _mapUrl: string | undefined;
|
||||||
private _textures: CharacterTexture[] | undefined;
|
private _textures: CharacterTexture[] | undefined;
|
||||||
private instance: string | undefined;
|
private instance: string | undefined;
|
||||||
@ -89,6 +89,7 @@ export class Room {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async getMapDetail(): Promise<MapDetail | RoomRedirect> {
|
private async getMapDetail(): Promise<MapDetail | RoomRedirect> {
|
||||||
|
try {
|
||||||
const result = await Axios.get(`${PUSHER_URL}/map`, {
|
const result = await Axios.get(`${PUSHER_URL}/map`, {
|
||||||
params: {
|
params: {
|
||||||
playUri: this.roomUrl.toString(),
|
playUri: this.roomUrl.toString(),
|
||||||
@ -106,10 +107,19 @@ export class Room {
|
|||||||
this._mapUrl = data.mapUrl;
|
this._mapUrl = data.mapUrl;
|
||||||
this._textures = data.textures;
|
this._textures = data.textures;
|
||||||
this._group = data.group;
|
this._group = data.group;
|
||||||
this._authenticationMandatory = data.authenticationMandatory || false;
|
this._authenticationMandatory = data.authenticationMandatory || DISABLE_ANONYMOUS;
|
||||||
this._iframeAuthentication = data.iframeAuthentication;
|
this._iframeAuthentication = data.iframeAuthentication || OPID_LOGIN_SCREEN_PROVIDER;
|
||||||
this._contactPage = data.contactPage || CONTACT_URL;
|
this._contactPage = data.contactPage || CONTACT_URL;
|
||||||
return new MapDetail(data.mapUrl, data.textures);
|
return new MapDetail(data.mapUrl, data.textures);
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Error => getMapDetail", e, e.response);
|
||||||
|
//TODO fix me and manage Error class
|
||||||
|
if (e.response?.data === "Token decrypted error") {
|
||||||
|
localUserStore.setAuthToken(null);
|
||||||
|
window.location.assign("/login");
|
||||||
|
}
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -4,6 +4,7 @@ const START_ROOM_URL: string =
|
|||||||
const PUSHER_URL = process.env.PUSHER_URL || "//pusher.workadventure.localhost";
|
const PUSHER_URL = process.env.PUSHER_URL || "//pusher.workadventure.localhost";
|
||||||
export const ADMIN_URL = process.env.ADMIN_URL || "//workadventu.re";
|
export const ADMIN_URL = process.env.ADMIN_URL || "//workadventu.re";
|
||||||
const UPLOADER_URL = process.env.UPLOADER_URL || "//uploader.workadventure.localhost";
|
const UPLOADER_URL = process.env.UPLOADER_URL || "//uploader.workadventure.localhost";
|
||||||
|
const ICON_URL = process.env.ICON_URL || "//icon.workadventure.localhost";
|
||||||
const STUN_SERVER: string = process.env.STUN_SERVER || "stun:stun.l.google.com:19302";
|
const STUN_SERVER: string = process.env.STUN_SERVER || "stun:stun.l.google.com:19302";
|
||||||
const TURN_SERVER: string = process.env.TURN_SERVER || "";
|
const TURN_SERVER: string = process.env.TURN_SERVER || "";
|
||||||
const SKIP_RENDER_OPTIMIZATIONS: boolean = process.env.SKIP_RENDER_OPTIMIZATIONS == "true";
|
const SKIP_RENDER_OPTIMIZATIONS: boolean = process.env.SKIP_RENDER_OPTIMIZATIONS == "true";
|
||||||
@ -22,6 +23,8 @@ export const CONTACT_URL = process.env.CONTACT_URL || undefined;
|
|||||||
export const PROFILE_URL = process.env.PROFILE_URL || undefined;
|
export const PROFILE_URL = process.env.PROFILE_URL || undefined;
|
||||||
export const POSTHOG_API_KEY: string = (process.env.POSTHOG_API_KEY as string) || "";
|
export const POSTHOG_API_KEY: string = (process.env.POSTHOG_API_KEY as string) || "";
|
||||||
export const POSTHOG_URL = process.env.POSTHOG_URL || undefined;
|
export const POSTHOG_URL = process.env.POSTHOG_URL || undefined;
|
||||||
|
export const DISABLE_ANONYMOUS: boolean = process.env.DISABLE_ANONYMOUS === "true";
|
||||||
|
export const OPID_LOGIN_SCREEN_PROVIDER = process.env.OPID_LOGIN_SCREEN_PROVIDER;
|
||||||
|
|
||||||
export const isMobile = (): boolean => window.innerWidth <= 800 || window.innerHeight <= 600;
|
export const isMobile = (): boolean => window.innerWidth <= 800 || window.innerHeight <= 600;
|
||||||
|
|
||||||
@ -32,6 +35,7 @@ export {
|
|||||||
DISABLE_NOTIFICATIONS,
|
DISABLE_NOTIFICATIONS,
|
||||||
PUSHER_URL,
|
PUSHER_URL,
|
||||||
UPLOADER_URL,
|
UPLOADER_URL,
|
||||||
|
ICON_URL,
|
||||||
POSITION_DELAY,
|
POSITION_DELAY,
|
||||||
MAX_EXTRAPOLATION_TIME,
|
MAX_EXTRAPOLATION_TIME,
|
||||||
STUN_SERVER,
|
STUN_SERVER,
|
||||||
|
@ -6,73 +6,124 @@ const TextName: string = "Loading...";
|
|||||||
const LogoResource: string = "static/images/logo.png";
|
const LogoResource: string = "static/images/logo.png";
|
||||||
const LogoFrame: ImageFrameConfig = { frameWidth: 310, frameHeight: 60 };
|
const LogoFrame: ImageFrameConfig = { frameWidth: 310, frameHeight: 60 };
|
||||||
|
|
||||||
export const addLoader = (scene: Phaser.Scene): void => {
|
|
||||||
// If there is nothing to load, do not display the loader.
|
|
||||||
if (scene.load.list.entries.length === 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let loadingText: Phaser.GameObjects.Text | null = null;
|
|
||||||
const loadingBarWidth: number = Math.floor(scene.game.renderer.width / 3);
|
|
||||||
const loadingBarHeight: number = 16;
|
const loadingBarHeight: number = 16;
|
||||||
const padding: number = 5;
|
const padding: number = 5;
|
||||||
|
|
||||||
|
export class Loader {
|
||||||
|
private progressContainer!: Phaser.GameObjects.Graphics;
|
||||||
|
private progress!: Phaser.GameObjects.Graphics;
|
||||||
|
private progressAmount: number = 0;
|
||||||
|
private logo: Phaser.GameObjects.Image | undefined;
|
||||||
|
private loadingText: Phaser.GameObjects.Text | null = null;
|
||||||
|
|
||||||
|
public constructor(private scene: Phaser.Scene) {}
|
||||||
|
|
||||||
|
public addLoader(): void {
|
||||||
|
// If there is nothing to load, do not display the loader.
|
||||||
|
if (this.scene.load.list.entries.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const loadingBarWidth: number = Math.floor(this.scene.game.renderer.width / 3);
|
||||||
|
|
||||||
const promiseLoadLogoTexture = new Promise<Phaser.GameObjects.Image>((res) => {
|
const promiseLoadLogoTexture = new Promise<Phaser.GameObjects.Image>((res) => {
|
||||||
if (scene.load.textureManager.exists(LogoNameIndex)) {
|
if (this.scene.load.textureManager.exists(LogoNameIndex)) {
|
||||||
return res(
|
return res(
|
||||||
scene.add.image(scene.game.renderer.width / 2, scene.game.renderer.height / 2 - 150, LogoNameIndex)
|
(this.logo = this.scene.add.image(
|
||||||
|
this.scene.game.renderer.width / 2,
|
||||||
|
this.scene.game.renderer.height / 2 - 150,
|
||||||
|
LogoNameIndex
|
||||||
|
))
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
//add loading if logo image is not ready
|
//add loading if logo image is not ready
|
||||||
loadingText = scene.add.text(scene.game.renderer.width / 2, scene.game.renderer.height / 2 - 50, TextName);
|
this.loadingText = this.scene.add.text(
|
||||||
|
this.scene.game.renderer.width / 2,
|
||||||
|
this.scene.game.renderer.height / 2 - 50,
|
||||||
|
TextName
|
||||||
|
);
|
||||||
}
|
}
|
||||||
scene.load.spritesheet(LogoNameIndex, LogoResource, LogoFrame);
|
this.scene.load.spritesheet(LogoNameIndex, LogoResource, LogoFrame);
|
||||||
scene.load.once(`filecomplete-spritesheet-${LogoNameIndex}`, () => {
|
this.scene.load.once(`filecomplete-spritesheet-${LogoNameIndex}`, () => {
|
||||||
if (loadingText) {
|
if (this.loadingText) {
|
||||||
loadingText.destroy();
|
this.loadingText.destroy();
|
||||||
}
|
}
|
||||||
return res(
|
return res(
|
||||||
scene.add.image(scene.game.renderer.width / 2, scene.game.renderer.height / 2 - 150, LogoNameIndex)
|
(this.logo = this.scene.add.image(
|
||||||
|
this.scene.game.renderer.width / 2,
|
||||||
|
this.scene.game.renderer.height / 2 - 150,
|
||||||
|
LogoNameIndex
|
||||||
|
))
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const progressContainer = scene.add.graphics();
|
this.progressContainer = this.scene.add.graphics();
|
||||||
const progress = scene.add.graphics();
|
this.progress = this.scene.add.graphics();
|
||||||
progressContainer.fillStyle(0x444444, 0.8);
|
this.progressContainer.fillStyle(0x444444, 0.8);
|
||||||
progressContainer.fillRect(
|
|
||||||
(scene.game.renderer.width - loadingBarWidth) / 2 - padding,
|
|
||||||
scene.game.renderer.height / 2 + 50 - padding,
|
|
||||||
loadingBarWidth + padding * 2,
|
|
||||||
loadingBarHeight + padding * 2
|
|
||||||
);
|
|
||||||
|
|
||||||
scene.load.on("progress", (value: number) => {
|
this.resize();
|
||||||
progress.clear();
|
|
||||||
progress.fillStyle(0xbbbbbb, 1);
|
this.scene.load.on("progress", (value: number) => {
|
||||||
progress.fillRect(
|
this.progressAmount = value;
|
||||||
(scene.game.renderer.width - loadingBarWidth) / 2,
|
this.drawProgress();
|
||||||
scene.game.renderer.height / 2 + 50,
|
|
||||||
loadingBarWidth * value,
|
|
||||||
loadingBarHeight
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
scene.load.on("complete", () => {
|
this.scene.load.on("complete", () => {
|
||||||
if (loadingText) {
|
if (this.loadingText) {
|
||||||
loadingText.destroy();
|
this.loadingText.destroy();
|
||||||
}
|
}
|
||||||
promiseLoadLogoTexture.then((resLoadingImage: Phaser.GameObjects.Image) => {
|
promiseLoadLogoTexture.then((resLoadingImage: Phaser.GameObjects.Image) => {
|
||||||
resLoadingImage.destroy();
|
resLoadingImage.destroy();
|
||||||
});
|
});
|
||||||
progress.destroy();
|
this.progress.destroy();
|
||||||
progressContainer.destroy();
|
this.progressContainer.destroy();
|
||||||
if (scene instanceof DirtyScene) {
|
if (this.scene instanceof DirtyScene) {
|
||||||
scene.markDirty();
|
this.scene.markDirty();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
|
||||||
|
|
||||||
export const removeLoader = (scene: Phaser.Scene): void => {
|
|
||||||
if (scene.load.textureManager.exists(LogoNameIndex)) {
|
|
||||||
scene.load.textureManager.remove(LogoNameIndex);
|
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
public removeLoader(): void {
|
||||||
|
if (this.scene.load.textureManager.exists(LogoNameIndex)) {
|
||||||
|
this.scene.load.textureManager.remove(LogoNameIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public resize(): void {
|
||||||
|
const loadingBarWidth: number = Math.floor(this.scene.game.renderer.width / 3);
|
||||||
|
|
||||||
|
this.progressContainer.clear();
|
||||||
|
this.progressContainer.fillStyle(0x444444, 0.8);
|
||||||
|
this.progressContainer.fillRect(
|
||||||
|
(this.scene.game.renderer.width - loadingBarWidth) / 2 - padding,
|
||||||
|
this.scene.game.renderer.height / 2 + 50 - padding,
|
||||||
|
loadingBarWidth + padding * 2,
|
||||||
|
loadingBarHeight + padding * 2
|
||||||
|
);
|
||||||
|
|
||||||
|
this.drawProgress();
|
||||||
|
|
||||||
|
if (this.loadingText) {
|
||||||
|
this.loadingText.x = this.scene.game.renderer.width / 2;
|
||||||
|
this.loadingText.y = this.scene.game.renderer.height / 2 - 50;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.logo) {
|
||||||
|
this.logo.x = this.scene.game.renderer.width / 2;
|
||||||
|
this.logo.y = this.scene.game.renderer.height / 2 - 150;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private drawProgress() {
|
||||||
|
const loadingBarWidth: number = Math.floor(this.scene.game.renderer.width / 3);
|
||||||
|
|
||||||
|
this.progress.clear();
|
||||||
|
this.progress.fillStyle(0xbbbbbb, 1);
|
||||||
|
this.progress.fillRect(
|
||||||
|
(this.scene.game.renderer.width - loadingBarWidth) / 2,
|
||||||
|
this.scene.game.renderer.height / 2 + 50,
|
||||||
|
loadingBarWidth * this.progressAmount,
|
||||||
|
loadingBarHeight
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -3,6 +3,7 @@ import { SpeechBubble } from "./SpeechBubble";
|
|||||||
import Text = Phaser.GameObjects.Text;
|
import Text = Phaser.GameObjects.Text;
|
||||||
import Container = Phaser.GameObjects.Container;
|
import Container = Phaser.GameObjects.Container;
|
||||||
import Sprite = Phaser.GameObjects.Sprite;
|
import Sprite = Phaser.GameObjects.Sprite;
|
||||||
|
import DOMElement = Phaser.GameObjects.DOMElement;
|
||||||
import { TextureError } from "../../Exception/TextureError";
|
import { TextureError } from "../../Exception/TextureError";
|
||||||
import { Companion } from "../Companion/Companion";
|
import { Companion } from "../Companion/Companion";
|
||||||
import type { GameScene } from "../Game/GameScene";
|
import type { GameScene } from "../Game/GameScene";
|
||||||
@ -33,7 +34,7 @@ export abstract class Character extends Container {
|
|||||||
//private teleportation: Sprite;
|
//private teleportation: Sprite;
|
||||||
private invisible: boolean;
|
private invisible: boolean;
|
||||||
public companion?: Companion;
|
public companion?: Companion;
|
||||||
private emote: Phaser.GameObjects.Text | null = null;
|
private emote: Phaser.GameObjects.DOMElement | null = null;
|
||||||
private emoteTween: Phaser.Tweens.Tween | null = null;
|
private emoteTween: Phaser.Tweens.Tween | null = null;
|
||||||
scene: GameScene;
|
scene: GameScene;
|
||||||
|
|
||||||
@ -300,8 +301,9 @@ export abstract class Character extends Container {
|
|||||||
playEmote(emote: string) {
|
playEmote(emote: string) {
|
||||||
this.cancelPreviousEmote();
|
this.cancelPreviousEmote();
|
||||||
const emoteY = -45;
|
const emoteY = -45;
|
||||||
this.playerName.setVisible(false);
|
const image = new Image(16, 16);
|
||||||
this.emote = new Text(this.scene, -10, 0, emote, { fontSize: "20px" });
|
image.src = emote;
|
||||||
|
this.emote = new DOMElement(this.scene, -1, 0, image, "z-index:10;");
|
||||||
this.emote.setAlpha(0);
|
this.emote.setAlpha(0);
|
||||||
this.add(this.emote);
|
this.add(this.emote);
|
||||||
this.createStartTransition(emoteY);
|
this.createStartTransition(emoteY);
|
||||||
|
@ -162,6 +162,7 @@ export class EmbeddedWebsiteManager {
|
|||||||
|
|
||||||
const iframe = document.createElement("iframe");
|
const iframe = document.createElement("iframe");
|
||||||
iframe.src = absoluteUrl;
|
iframe.src = absoluteUrl;
|
||||||
|
iframe.tabIndex = -1;
|
||||||
iframe.style.width = embeddedWebsiteEvent.position.width + "px";
|
iframe.style.width = embeddedWebsiteEvent.position.width + "px";
|
||||||
iframe.style.height = embeddedWebsiteEvent.position.height + "px";
|
iframe.style.height = embeddedWebsiteEvent.position.height + "px";
|
||||||
iframe.style.margin = "0";
|
iframe.style.margin = "0";
|
||||||
|
@ -15,10 +15,7 @@ import type {
|
|||||||
import { DEBUG_MODE, JITSI_PRIVATE_MODE, MAX_PER_GROUP, POSITION_DELAY } from "../../Enum/EnvironmentVariable";
|
import { DEBUG_MODE, JITSI_PRIVATE_MODE, MAX_PER_GROUP, POSITION_DELAY } from "../../Enum/EnvironmentVariable";
|
||||||
|
|
||||||
import { Queue } from "queue-typescript";
|
import { Queue } from "queue-typescript";
|
||||||
import {
|
import { Box, ON_ACTION_TRIGGER_BUTTON } from "../../WebRtc/LayoutManager";
|
||||||
Box,
|
|
||||||
ON_ACTION_TRIGGER_BUTTON,
|
|
||||||
} from "../../WebRtc/LayoutManager";
|
|
||||||
import { CoWebsite, coWebsiteManager } from "../../WebRtc/CoWebsiteManager";
|
import { CoWebsite, coWebsiteManager } from "../../WebRtc/CoWebsiteManager";
|
||||||
import type { UserMovedMessage } from "../../Messages/generated/messages_pb";
|
import type { UserMovedMessage } from "../../Messages/generated/messages_pb";
|
||||||
import { ProtobufClientUtils } from "../../Network/ProtobufClientUtils";
|
import { ProtobufClientUtils } from "../../Network/ProtobufClientUtils";
|
||||||
@ -31,7 +28,7 @@ import { localUserStore } from "../../Connexion/LocalUserStore";
|
|||||||
import { HtmlUtils } from "../../WebRtc/HtmlUtils";
|
import { HtmlUtils } from "../../WebRtc/HtmlUtils";
|
||||||
import { mediaManager } from "../../WebRtc/MediaManager";
|
import { mediaManager } from "../../WebRtc/MediaManager";
|
||||||
import { SimplePeer } from "../../WebRtc/SimplePeer";
|
import { SimplePeer } from "../../WebRtc/SimplePeer";
|
||||||
import { addLoader, removeLoader } from "../Components/Loader";
|
import { Loader } from "../Components/Loader";
|
||||||
import { lazyLoadPlayerCharacterTextures, loadCustomTexture } from "../Entity/PlayerTexturesLoadingManager";
|
import { lazyLoadPlayerCharacterTextures, loadCustomTexture } from "../Entity/PlayerTexturesLoadingManager";
|
||||||
import { RemotePlayer } from "../Entity/RemotePlayer";
|
import { RemotePlayer } from "../Entity/RemotePlayer";
|
||||||
import type { ActionableItem } from "../Items/ActionableItem";
|
import type { ActionableItem } from "../Items/ActionableItem";
|
||||||
@ -91,6 +88,7 @@ import { analyticsClient } from "../../Administration/AnalyticsClient";
|
|||||||
import { get } from "svelte/store";
|
import { get } from "svelte/store";
|
||||||
import { contactPageStore } from "../../Stores/MenuStore";
|
import { contactPageStore } from "../../Stores/MenuStore";
|
||||||
import { GameMapProperties } from "./GameMapProperties";
|
import { GameMapProperties } from "./GameMapProperties";
|
||||||
|
import SpriteSheetFile = Phaser.Loader.FileTypes.SpriteSheetFile;
|
||||||
|
|
||||||
export interface GameSceneInitInterface {
|
export interface GameSceneInitInterface {
|
||||||
initPosition: PointInterface | null;
|
initPosition: PointInterface | null;
|
||||||
@ -205,6 +203,7 @@ export class GameScene extends DirtyScene {
|
|||||||
private sharedVariablesManager!: SharedVariablesManager;
|
private sharedVariablesManager!: SharedVariablesManager;
|
||||||
private objectsByType = new Map<string, ITiledMapObject[]>();
|
private objectsByType = new Map<string, ITiledMapObject[]>();
|
||||||
private embeddedWebsiteManager!: EmbeddedWebsiteManager;
|
private embeddedWebsiteManager!: EmbeddedWebsiteManager;
|
||||||
|
private loader: Loader;
|
||||||
|
|
||||||
constructor(private room: Room, MapUrlFile: string, customKey?: string | undefined) {
|
constructor(private room: Room, MapUrlFile: string, customKey?: string | undefined) {
|
||||||
super({
|
super({
|
||||||
@ -223,6 +222,7 @@ export class GameScene extends DirtyScene {
|
|||||||
this.connectionAnswerPromise = new Promise<RoomJoinedMessageInterface>((resolve, reject): void => {
|
this.connectionAnswerPromise = new Promise<RoomJoinedMessageInterface>((resolve, reject): void => {
|
||||||
this.connectionAnswerPromiseResolve = resolve;
|
this.connectionAnswerPromiseResolve = resolve;
|
||||||
});
|
});
|
||||||
|
this.loader = new Loader(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
//hook preload scene
|
//hook preload scene
|
||||||
@ -296,9 +296,10 @@ export class GameScene extends DirtyScene {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//once preloading is over, we don't want loading errors to crash the game, so we need to disable this behavior after preloading.
|
//once preloading is over, we don't want loading errors to crash the game, so we need to disable this behavior after preloading.
|
||||||
if (this.preloading) {
|
//if SpriteSheetFile (WOKA file) don't display error and give an access for user
|
||||||
|
if (this.preloading && !(file instanceof SpriteSheetFile)) {
|
||||||
//remove loader in progress
|
//remove loader in progress
|
||||||
removeLoader(this);
|
this.loader.removeLoader();
|
||||||
|
|
||||||
//display an error scene
|
//display an error scene
|
||||||
this.scene.start(ErrorSceneName, {
|
this.scene.start(ErrorSceneName, {
|
||||||
@ -332,7 +333,7 @@ export class GameScene extends DirtyScene {
|
|||||||
});
|
});
|
||||||
|
|
||||||
//this function must stay at the end of preload function
|
//this function must stay at the end of preload function
|
||||||
addLoader(this);
|
this.loader.addLoader();
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: we need to put a "unknown" instead of a "any" and validate the structure of the JSON we are receiving.
|
// FIXME: we need to put a "unknown" instead of a "any" and validate the structure of the JSON we are receiving.
|
||||||
@ -497,7 +498,10 @@ export class GameScene extends DirtyScene {
|
|||||||
object.properties,
|
object.properties,
|
||||||
'in the "' + object.name + '" object of type "website"'
|
'in the "' + object.name + '" object of type "website"'
|
||||||
);
|
);
|
||||||
const allowApi = PropertyUtils.findBooleanProperty(GameMapProperties.ALLOW_API, object.properties);
|
const allowApi = PropertyUtils.findBooleanProperty(
|
||||||
|
GameMapProperties.ALLOW_API,
|
||||||
|
object.properties
|
||||||
|
);
|
||||||
|
|
||||||
// TODO: add a "allow" property to iframe
|
// TODO: add a "allow" property to iframe
|
||||||
this.embeddedWebsiteManager.createEmbeddedWebsite(
|
this.embeddedWebsiteManager.createEmbeddedWebsite(
|
||||||
@ -614,10 +618,10 @@ export class GameScene extends DirtyScene {
|
|||||||
oldPeerNumber = newPeerNumber;
|
oldPeerNumber = newPeerNumber;
|
||||||
});
|
});
|
||||||
|
|
||||||
this.emoteUnsubscribe = emoteStore.subscribe((emoteKey) => {
|
this.emoteUnsubscribe = emoteStore.subscribe((emote) => {
|
||||||
if (emoteKey) {
|
if (emote) {
|
||||||
this.CurrentPlayer?.playEmote(emoteKey);
|
this.CurrentPlayer?.playEmote(emote.url);
|
||||||
this.connection?.emitEmoteEvent(emoteKey);
|
this.connection?.emitEmoteEvent(emote.url);
|
||||||
emoteStore.set(null);
|
emoteStore.set(null);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -763,14 +767,14 @@ export class GameScene extends DirtyScene {
|
|||||||
this.gameMap.setPosition(this.CurrentPlayer.x, this.CurrentPlayer.y);
|
this.gameMap.setPosition(this.CurrentPlayer.x, this.CurrentPlayer.y);
|
||||||
|
|
||||||
// Init layer change listener
|
// Init layer change listener
|
||||||
this.gameMap.onEnterLayer(layers => {
|
this.gameMap.onEnterLayer((layers) => {
|
||||||
layers.forEach(layer => {
|
layers.forEach((layer) => {
|
||||||
iframeListener.sendEnterLayerEvent(layer.name);
|
iframeListener.sendEnterLayerEvent(layer.name);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
this.gameMap.onLeaveLayer(layers => {
|
this.gameMap.onLeaveLayer((layers) => {
|
||||||
layers.forEach(layer => {
|
layers.forEach((layer) => {
|
||||||
iframeListener.sendLeaveLayerEvent(layer.name);
|
iframeListener.sendLeaveLayerEvent(layer.name);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -1830,6 +1834,8 @@ ${escapedMessage}
|
|||||||
right: camera.scrollX + camera.width,
|
right: camera.scrollX + camera.width,
|
||||||
bottom: camera.scrollY + camera.height,
|
bottom: camera.scrollY + camera.height,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.loader.resize();
|
||||||
}
|
}
|
||||||
private getObjectLayerData(objectName: string): ITiledMapObject | undefined {
|
private getObjectLayerData(objectName: string): ITiledMapObject | undefined {
|
||||||
for (const layer of this.mapFile.layers) {
|
for (const layer of this.mapFile.layers) {
|
||||||
@ -1868,7 +1874,8 @@ ${escapedMessage}
|
|||||||
public startJitsi(roomName: string, jwt?: string): void {
|
public startJitsi(roomName: string, jwt?: string): void {
|
||||||
const allProps = this.gameMap.getCurrentProperties();
|
const allProps = this.gameMap.getCurrentProperties();
|
||||||
const jitsiConfig = this.safeParseJSONstring(
|
const jitsiConfig = this.safeParseJSONstring(
|
||||||
allProps.get(GameMapProperties.JITSI_CONFIG) as string | undefined, GameMapProperties.JITSI_CONFIG
|
allProps.get(GameMapProperties.JITSI_CONFIG) as string | undefined,
|
||||||
|
GameMapProperties.JITSI_CONFIG
|
||||||
);
|
);
|
||||||
const jitsiInterfaceConfig = this.safeParseJSONstring(
|
const jitsiInterfaceConfig = this.safeParseJSONstring(
|
||||||
allProps.get(GameMapProperties.JITSI_INTERFACE_CONFIG) as string | undefined,
|
allProps.get(GameMapProperties.JITSI_INTERFACE_CONFIG) as string | undefined,
|
||||||
|
@ -4,7 +4,7 @@ import { loadAllLayers } from "../Entity/PlayerTexturesLoadingManager";
|
|||||||
import Sprite = Phaser.GameObjects.Sprite;
|
import Sprite = Phaser.GameObjects.Sprite;
|
||||||
import { gameManager } from "../Game/GameManager";
|
import { gameManager } from "../Game/GameManager";
|
||||||
import { localUserStore } from "../../Connexion/LocalUserStore";
|
import { localUserStore } from "../../Connexion/LocalUserStore";
|
||||||
import { addLoader } from "../Components/Loader";
|
import { Loader } from "../Components/Loader";
|
||||||
import type { BodyResourceDescriptionInterface } from "../Entity/PlayerTextures";
|
import type { BodyResourceDescriptionInterface } from "../Entity/PlayerTextures";
|
||||||
import { AbstractCharacterScene } from "./AbstractCharacterScene";
|
import { AbstractCharacterScene } from "./AbstractCharacterScene";
|
||||||
import { areCharacterLayersValid } from "../../Connexion/LocalUser";
|
import { areCharacterLayersValid } from "../../Connexion/LocalUser";
|
||||||
@ -14,6 +14,7 @@ import { waScaleManager } from "../Services/WaScaleManager";
|
|||||||
import { isMobile } from "../../Enum/EnvironmentVariable";
|
import { isMobile } from "../../Enum/EnvironmentVariable";
|
||||||
import { CustomizedCharacter } from "../Entity/CustomizedCharacter";
|
import { CustomizedCharacter } from "../Entity/CustomizedCharacter";
|
||||||
import { get } from "svelte/store";
|
import { get } from "svelte/store";
|
||||||
|
import { analyticsClient } from "../../Administration/AnalyticsClient";
|
||||||
|
|
||||||
export const CustomizeSceneName = "CustomizeScene";
|
export const CustomizeSceneName = "CustomizeScene";
|
||||||
|
|
||||||
@ -29,10 +30,13 @@ export class CustomizeScene extends AbstractCharacterScene {
|
|||||||
private moveHorizontally: number = 0;
|
private moveHorizontally: number = 0;
|
||||||
private moveVertically: number = 0;
|
private moveVertically: number = 0;
|
||||||
|
|
||||||
|
private loader: Loader;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super({
|
super({
|
||||||
key: CustomizeSceneName,
|
key: CustomizeSceneName,
|
||||||
});
|
});
|
||||||
|
this.loader = new Loader(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
preload() {
|
preload() {
|
||||||
@ -54,7 +58,7 @@ export class CustomizeScene extends AbstractCharacterScene {
|
|||||||
this.lazyloadingAttempt = false;
|
this.lazyloadingAttempt = false;
|
||||||
|
|
||||||
//this function must stay at the end of preload function
|
//this function must stay at the end of preload function
|
||||||
addLoader(this);
|
this.loader.addLoader();
|
||||||
}
|
}
|
||||||
|
|
||||||
create() {
|
create() {
|
||||||
@ -278,6 +282,8 @@ export class CustomizeScene extends AbstractCharacterScene {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
analyticsClient.validationWoka("CustomizeWoka");
|
||||||
|
|
||||||
gameManager.setCharacterLayers(layers);
|
gameManager.setCharacterLayers(layers);
|
||||||
this.scene.sleep(CustomizeSceneName);
|
this.scene.sleep(CustomizeSceneName);
|
||||||
waScaleManager.restoreZoom();
|
waScaleManager.restoreZoom();
|
||||||
|
@ -2,6 +2,7 @@ import { gameManager } from "../Game/GameManager";
|
|||||||
import { ResizableScene } from "./ResizableScene";
|
import { ResizableScene } from "./ResizableScene";
|
||||||
import { enableCameraSceneVisibilityStore } from "../../Stores/MediaStore";
|
import { enableCameraSceneVisibilityStore } from "../../Stores/MediaStore";
|
||||||
import { localUserStore } from "../../Connexion/LocalUserStore";
|
import { localUserStore } from "../../Connexion/LocalUserStore";
|
||||||
|
import { analyticsClient } from "../../Administration/AnalyticsClient";
|
||||||
|
|
||||||
export const EnableCameraSceneName = "EnableCameraScene";
|
export const EnableCameraSceneName = "EnableCameraScene";
|
||||||
|
|
||||||
@ -27,6 +28,8 @@ export class EnableCameraScene extends ResizableScene {
|
|||||||
update(time: number, delta: number): void {}
|
update(time: number, delta: number): void {}
|
||||||
|
|
||||||
public login(): void {
|
public login(): void {
|
||||||
|
analyticsClient.validationVideo();
|
||||||
|
|
||||||
enableCameraSceneVisibilityStore.hideEnableCameraScene();
|
enableCameraSceneVisibilityStore.hideEnableCameraScene();
|
||||||
|
|
||||||
this.scene.sleep(EnableCameraSceneName);
|
this.scene.sleep(EnableCameraSceneName);
|
||||||
|
@ -4,6 +4,7 @@ import { loginSceneVisibleIframeStore, loginSceneVisibleStore } from "../../Stor
|
|||||||
import { localUserStore } from "../../Connexion/LocalUserStore";
|
import { localUserStore } from "../../Connexion/LocalUserStore";
|
||||||
import { connectionManager } from "../../Connexion/ConnectionManager";
|
import { connectionManager } from "../../Connexion/ConnectionManager";
|
||||||
import { gameManager } from "../Game/GameManager";
|
import { gameManager } from "../Game/GameManager";
|
||||||
|
import { analyticsClient } from "../../Administration/AnalyticsClient";
|
||||||
|
|
||||||
export const LoginSceneName = "LoginScene";
|
export const LoginSceneName = "LoginScene";
|
||||||
|
|
||||||
@ -34,6 +35,8 @@ export class LoginScene extends ResizableScene {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public login(name: string): void {
|
public login(name: string): void {
|
||||||
|
analyticsClient.validationName();
|
||||||
|
|
||||||
name = name.trim();
|
name = name.trim();
|
||||||
gameManager.setPlayerName(name);
|
gameManager.setPlayerName(name);
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@ import { EnableCameraSceneName } from "./EnableCameraScene";
|
|||||||
import { CustomizeSceneName } from "./CustomizeScene";
|
import { CustomizeSceneName } from "./CustomizeScene";
|
||||||
import { localUserStore } from "../../Connexion/LocalUserStore";
|
import { localUserStore } from "../../Connexion/LocalUserStore";
|
||||||
import { loadAllDefaultModels } from "../Entity/PlayerTexturesLoadingManager";
|
import { loadAllDefaultModels } from "../Entity/PlayerTexturesLoadingManager";
|
||||||
import { addLoader } from "../Components/Loader";
|
import { Loader } from "../Components/Loader";
|
||||||
import type { BodyResourceDescriptionInterface } from "../Entity/PlayerTextures";
|
import type { BodyResourceDescriptionInterface } from "../Entity/PlayerTextures";
|
||||||
import { AbstractCharacterScene } from "./AbstractCharacterScene";
|
import { AbstractCharacterScene } from "./AbstractCharacterScene";
|
||||||
import { areCharacterLayersValid } from "../../Connexion/LocalUser";
|
import { areCharacterLayersValid } from "../../Connexion/LocalUser";
|
||||||
@ -13,6 +13,7 @@ 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 { isMobile } from "../../Enum/EnvironmentVariable";
|
import { isMobile } from "../../Enum/EnvironmentVariable";
|
||||||
|
import { analyticsClient } from "../../Administration/AnalyticsClient";
|
||||||
|
|
||||||
//todo: put this constants in a dedicated file
|
//todo: put this constants in a dedicated file
|
||||||
export const SelectCharacterSceneName = "SelectCharacterScene";
|
export const SelectCharacterSceneName = "SelectCharacterScene";
|
||||||
@ -30,11 +31,13 @@ export class SelectCharacterScene extends AbstractCharacterScene {
|
|||||||
protected pointerTimer: number = 0;
|
protected pointerTimer: number = 0;
|
||||||
|
|
||||||
protected lazyloadingAttempt = true; //permit to update texture loaded after renderer
|
protected lazyloadingAttempt = true; //permit to update texture loaded after renderer
|
||||||
|
private loader: Loader;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super({
|
super({
|
||||||
key: SelectCharacterSceneName,
|
key: SelectCharacterSceneName,
|
||||||
});
|
});
|
||||||
|
this.loader = new Loader(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
preload() {
|
preload() {
|
||||||
@ -48,7 +51,7 @@ export class SelectCharacterScene extends AbstractCharacterScene {
|
|||||||
this.lazyloadingAttempt = false;
|
this.lazyloadingAttempt = false;
|
||||||
|
|
||||||
//this function must stay at the end of preload function
|
//this function must stay at the end of preload function
|
||||||
addLoader(this);
|
this.loader.addLoader();
|
||||||
}
|
}
|
||||||
|
|
||||||
create() {
|
create() {
|
||||||
@ -98,6 +101,9 @@ export class SelectCharacterScene extends AbstractCharacterScene {
|
|||||||
if (!this.selectedPlayer) {
|
if (!this.selectedPlayer) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
analyticsClient.validationWoka("SelectWoka");
|
||||||
|
|
||||||
this.scene.stop(SelectCharacterSceneName);
|
this.scene.stop(SelectCharacterSceneName);
|
||||||
waScaleManager.restoreZoom();
|
waScaleManager.restoreZoom();
|
||||||
gameManager.setCharacterLayers([this.selectedPlayer.texture.key]);
|
gameManager.setCharacterLayers([this.selectedPlayer.texture.key]);
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { addLoader } from "../Components/Loader";
|
import { Loader } from "../Components/Loader";
|
||||||
import { gameManager } from "../Game/GameManager";
|
import { gameManager } from "../Game/GameManager";
|
||||||
import { ResizableScene } from "./ResizableScene";
|
import { ResizableScene } from "./ResizableScene";
|
||||||
import { EnableCameraSceneName } from "./EnableCameraScene";
|
import { EnableCameraSceneName } from "./EnableCameraScene";
|
||||||
@ -22,11 +22,13 @@ export class SelectCompanionScene extends ResizableScene {
|
|||||||
private currentCompanion = 0;
|
private currentCompanion = 0;
|
||||||
private pointerClicked: boolean = false;
|
private pointerClicked: boolean = false;
|
||||||
private pointerTimer: number = 0;
|
private pointerTimer: number = 0;
|
||||||
|
private loader: Loader;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super({
|
super({
|
||||||
key: SelectCompanionSceneName,
|
key: SelectCompanionSceneName,
|
||||||
});
|
});
|
||||||
|
this.loader = new Loader(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
preload() {
|
preload() {
|
||||||
@ -35,7 +37,7 @@ export class SelectCompanionScene extends ResizableScene {
|
|||||||
});
|
});
|
||||||
|
|
||||||
//this function must stay at the end of preload function
|
//this function must stay at the end of preload function
|
||||||
addLoader(this);
|
this.loader.addLoader();
|
||||||
}
|
}
|
||||||
|
|
||||||
create() {
|
create() {
|
||||||
|
@ -3,8 +3,6 @@ import type { GameScene } from "../Game/GameScene";
|
|||||||
import { UserInputEvent, UserInputManager } from "../UserInput/UserInputManager";
|
import { UserInputEvent, UserInputManager } from "../UserInput/UserInputManager";
|
||||||
import { Character } from "../Entity/Character";
|
import { Character } from "../Entity/Character";
|
||||||
import { userMovingStore } from "../../Stores/GameStore";
|
import { userMovingStore } from "../../Stores/GameStore";
|
||||||
import { get } from "svelte/store";
|
|
||||||
import { emoteMenuStore } from "../../Stores/EmoteStore";
|
|
||||||
|
|
||||||
export const hasMovedEventName = "hasMoved";
|
export const hasMovedEventName = "hasMoved";
|
||||||
export const requestEmoteEventName = "requestEmote";
|
export const requestEmoteEventName = "requestEmote";
|
||||||
@ -68,10 +66,24 @@ export class Player extends Character {
|
|||||||
} else if (this.wasMoving && moving) {
|
} else if (this.wasMoving && moving) {
|
||||||
// slow joystick movement
|
// slow joystick movement
|
||||||
this.move(0, 0);
|
this.move(0, 0);
|
||||||
this.emit(hasMovedEventName, { moving, direction: this.previousDirection, x: this.x, y: this.y, oldX: x, oldY: y });
|
this.emit(hasMovedEventName, {
|
||||||
|
moving,
|
||||||
|
direction: this.previousDirection,
|
||||||
|
x: this.x,
|
||||||
|
y: this.y,
|
||||||
|
oldX: x,
|
||||||
|
oldY: y,
|
||||||
|
});
|
||||||
} else if (this.wasMoving && !moving) {
|
} else if (this.wasMoving && !moving) {
|
||||||
this.stop();
|
this.stop();
|
||||||
this.emit(hasMovedEventName, { moving, direction: this.previousDirection, x: this.x, y: this.y, oldX: x, oldY: y });
|
this.emit(hasMovedEventName, {
|
||||||
|
moving,
|
||||||
|
direction: this.previousDirection,
|
||||||
|
x: this.x,
|
||||||
|
y: this.y,
|
||||||
|
oldX: x,
|
||||||
|
oldY: y,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (direction !== null) {
|
if (direction !== null) {
|
||||||
|
@ -67,6 +67,9 @@ function createChatMessagesStore() {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
addPersonnalMessage(text: string) {
|
addPersonnalMessage(text: string) {
|
||||||
|
//post message iframe listener
|
||||||
|
iframeListener.sendUserInputChat(text);
|
||||||
|
|
||||||
newChatMessageStore.set(text);
|
newChatMessageStore.set(text);
|
||||||
update((list) => {
|
update((list) => {
|
||||||
const lastMessage = list[list.length - 1];
|
const lastMessage = list[list.length - 1];
|
||||||
|
@ -1,5 +1,11 @@
|
|||||||
import { writable } from "svelte/store";
|
import { writable } from "svelte/store";
|
||||||
|
|
||||||
|
export interface Emoji {
|
||||||
|
unicode: string;
|
||||||
|
url: string;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
function createEmoteMenuStore() {
|
function createEmoteMenuStore() {
|
||||||
const { subscribe, set } = writable(false);
|
const { subscribe, set } = writable(false);
|
||||||
|
|
||||||
@ -14,5 +20,5 @@ function createEmoteMenuStore() {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export const emoteStore = writable<string | null>(null);
|
export const emoteStore = writable<Emoji | null>(null);
|
||||||
export const emoteMenuStore = createEmoteMenuStore();
|
export const emoteMenuStore = createEmoteMenuStore();
|
||||||
|
@ -34,20 +34,20 @@ export const warningContainerStore = createWarningContainerStore();
|
|||||||
export enum SubMenusInterface {
|
export enum SubMenusInterface {
|
||||||
settings = "Settings",
|
settings = "Settings",
|
||||||
profile = "Profile",
|
profile = "Profile",
|
||||||
createMap = "Create a Map",
|
invite = "Invite",
|
||||||
aboutRoom = "About the Room",
|
aboutRoom = "Credit",
|
||||||
globalMessages = "Global Messages",
|
globalMessages = "Global Messages",
|
||||||
contact = "Contact",
|
contact = "Contact",
|
||||||
}
|
}
|
||||||
|
|
||||||
function createSubMenusStore() {
|
function createSubMenusStore() {
|
||||||
const { subscribe, update } = writable<string[]>([
|
const { subscribe, update } = writable<string[]>([
|
||||||
SubMenusInterface.settings,
|
|
||||||
SubMenusInterface.profile,
|
SubMenusInterface.profile,
|
||||||
SubMenusInterface.createMap,
|
|
||||||
SubMenusInterface.aboutRoom,
|
|
||||||
SubMenusInterface.globalMessages,
|
SubMenusInterface.globalMessages,
|
||||||
SubMenusInterface.contact,
|
SubMenusInterface.contact,
|
||||||
|
SubMenusInterface.settings,
|
||||||
|
SubMenusInterface.invite,
|
||||||
|
SubMenusInterface.aboutRoom,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -3,6 +3,7 @@ import { Subject } from "rxjs";
|
|||||||
import { iframeListener } from "../Api/IframeListener";
|
import { iframeListener } from "../Api/IframeListener";
|
||||||
import { touchScreenManager } from "../Touch/TouchScreenManager";
|
import { touchScreenManager } from "../Touch/TouchScreenManager";
|
||||||
import { waScaleManager } from "../Phaser/Services/WaScaleManager";
|
import { waScaleManager } from "../Phaser/Services/WaScaleManager";
|
||||||
|
import { ICON_URL } from "../Enum/EnvironmentVariable";
|
||||||
|
|
||||||
enum iframeStates {
|
enum iframeStates {
|
||||||
closed = 1,
|
closed = 1,
|
||||||
@ -28,15 +29,15 @@ interface TouchMoveCoordinates {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type CoWebsite = {
|
export type CoWebsite = {
|
||||||
iframe: HTMLIFrameElement,
|
iframe: HTMLIFrameElement;
|
||||||
icon: HTMLDivElement,
|
icon: HTMLDivElement;
|
||||||
position: number
|
position: number;
|
||||||
}
|
};
|
||||||
|
|
||||||
type CoWebsiteSlot = {
|
type CoWebsiteSlot = {
|
||||||
container: HTMLElement,
|
container: HTMLElement;
|
||||||
position: number
|
position: number;
|
||||||
}
|
};
|
||||||
|
|
||||||
class CoWebsiteManager {
|
class CoWebsiteManager {
|
||||||
private openedMain: iframeStates = iframeStates.closed;
|
private openedMain: iframeStates = iframeStates.closed;
|
||||||
@ -61,7 +62,7 @@ class CoWebsiteManager {
|
|||||||
|
|
||||||
private slots: CoWebsiteSlot[];
|
private slots: CoWebsiteSlot[];
|
||||||
|
|
||||||
private resizeObserver = new ResizeObserver(entries => {
|
private resizeObserver = new ResizeObserver((entries) => {
|
||||||
this.resizeAllIframes();
|
this.resizeAllIframes();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -108,23 +109,23 @@ class CoWebsiteManager {
|
|||||||
this.slots = [
|
this.slots = [
|
||||||
{
|
{
|
||||||
container: this.cowebsiteMainDom,
|
container: this.cowebsiteMainDom,
|
||||||
position: 0
|
position: 0,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
container: HtmlUtils.getElementByIdOrFail<HTMLDivElement>('cowebsite-slot-1'),
|
container: HtmlUtils.getElementByIdOrFail<HTMLDivElement>("cowebsite-slot-1"),
|
||||||
position: 1
|
position: 1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
container: HtmlUtils.getElementByIdOrFail<HTMLDivElement>('cowebsite-slot-2'),
|
container: HtmlUtils.getElementByIdOrFail<HTMLDivElement>("cowebsite-slot-2"),
|
||||||
position: 2
|
position: 2,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
container: HtmlUtils.getElementByIdOrFail<HTMLDivElement>('cowebsite-slot-3'),
|
container: HtmlUtils.getElementByIdOrFail<HTMLDivElement>("cowebsite-slot-3"),
|
||||||
position: 3
|
position: 3,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
container: HtmlUtils.getElementByIdOrFail<HTMLDivElement>('cowebsite-slot-4'),
|
container: HtmlUtils.getElementByIdOrFail<HTMLDivElement>("cowebsite-slot-4"),
|
||||||
position: 4
|
position: 4,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -163,8 +164,10 @@ class CoWebsiteManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private isSmallScreen(): boolean {
|
private isSmallScreen(): boolean {
|
||||||
return window.matchMedia("(max-aspect-ratio: 1/1)").matches ||
|
return (
|
||||||
window.matchMedia("(max-width:960px) and (max-height:768px)").matches;
|
window.matchMedia("(max-aspect-ratio: 1/1)").matches ||
|
||||||
|
window.matchMedia("(max-width:960px) and (max-height:768px)").matches
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private initResizeListeners(touchMode: boolean) {
|
private initResizeListeners(touchMode: boolean) {
|
||||||
@ -235,12 +238,12 @@ class CoWebsiteManager {
|
|||||||
|
|
||||||
private initActionsListeners() {
|
private initActionsListeners() {
|
||||||
this.slots.forEach((slot: CoWebsiteSlot) => {
|
this.slots.forEach((slot: CoWebsiteSlot) => {
|
||||||
const expandButton = slot.container.querySelector('.expand');
|
const expandButton = slot.container.querySelector(".expand");
|
||||||
const highlightButton = slot.container.querySelector('.hightlight');
|
const highlightButton = slot.container.querySelector(".hightlight");
|
||||||
const closeButton = slot.container.querySelector('.close');
|
const closeButton = slot.container.querySelector(".close");
|
||||||
|
|
||||||
if (expandButton) {
|
if (expandButton) {
|
||||||
expandButton.addEventListener('click', (event) => {
|
expandButton.addEventListener("click", (event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
const coWebsite = this.getCoWebsiteByPosition(slot.position);
|
const coWebsite = this.getCoWebsiteByPosition(slot.position);
|
||||||
|
|
||||||
@ -253,7 +256,7 @@ class CoWebsiteManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (highlightButton) {
|
if (highlightButton) {
|
||||||
highlightButton.addEventListener('click', (event) => {
|
highlightButton.addEventListener("click", (event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
const coWebsite = this.getCoWebsiteByPosition(slot.position);
|
const coWebsite = this.getCoWebsiteByPosition(slot.position);
|
||||||
|
|
||||||
@ -266,7 +269,7 @@ class CoWebsiteManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (closeButton) {
|
if (closeButton) {
|
||||||
closeButton.addEventListener('click', (event) => {
|
closeButton.addEventListener("click", (event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
const coWebsite = this.getCoWebsiteByPosition(slot.position);
|
const coWebsite = this.getCoWebsiteByPosition(slot.position);
|
||||||
|
|
||||||
@ -299,16 +302,16 @@ class CoWebsiteManager {
|
|||||||
private setIframeOffset(coWebsite: CoWebsite, slot: CoWebsiteSlot) {
|
private setIframeOffset(coWebsite: CoWebsite, slot: CoWebsiteSlot) {
|
||||||
const bounding = slot.container.getBoundingClientRect();
|
const bounding = slot.container.getBoundingClientRect();
|
||||||
|
|
||||||
if (coWebsite.iframe.classList.contains('thumbnail')) {
|
if (coWebsite.iframe.classList.contains("thumbnail")) {
|
||||||
coWebsite.iframe.style.width = ((bounding.right - bounding.left) * 2) + 'px';
|
coWebsite.iframe.style.width = (bounding.right - bounding.left) * 2 + "px";
|
||||||
coWebsite.iframe.style.height = ((bounding.bottom - bounding.top) * 2) + 'px';
|
coWebsite.iframe.style.height = (bounding.bottom - bounding.top) * 2 + "px";
|
||||||
coWebsite.iframe.style.top = (bounding.top - (Math.floor(bounding.height * 0.5))) + 'px';
|
coWebsite.iframe.style.top = bounding.top - Math.floor(bounding.height * 0.5) + "px";
|
||||||
coWebsite.iframe.style.left = (bounding.left - (Math.floor(bounding.width * 0.5))) + 'px';
|
coWebsite.iframe.style.left = bounding.left - Math.floor(bounding.width * 0.5) + "px";
|
||||||
} else {
|
} else {
|
||||||
coWebsite.iframe.style.top = bounding.top + 'px';
|
coWebsite.iframe.style.top = bounding.top + "px";
|
||||||
coWebsite.iframe.style.left = bounding.left + 'px';
|
coWebsite.iframe.style.left = bounding.left + "px";
|
||||||
coWebsite.iframe.style.width = (bounding.right - bounding.left) + 'px';
|
coWebsite.iframe.style.width = bounding.right - bounding.left + "px";
|
||||||
coWebsite.iframe.style.height = (bounding.bottom - bounding.top) + 'px';
|
coWebsite.iframe.style.height = bounding.bottom - bounding.top + "px";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -333,38 +336,48 @@ class CoWebsiteManager {
|
|||||||
coWebsite.iframe.scrolling = newPosition === 0 || newPosition === 1 ? "yes" : "no";
|
coWebsite.iframe.scrolling = newPosition === 0 || newPosition === 1 ? "yes" : "no";
|
||||||
|
|
||||||
if (newPosition === 0) {
|
if (newPosition === 0) {
|
||||||
coWebsite.iframe.classList.add('main');
|
coWebsite.iframe.classList.add("main");
|
||||||
coWebsite.icon.style.display = "none";
|
coWebsite.icon.style.display = "none";
|
||||||
} else {
|
} else {
|
||||||
coWebsite.iframe.classList.remove('main');
|
coWebsite.iframe.classList.remove("main");
|
||||||
coWebsite.icon.style.display = "flex";
|
coWebsite.icon.style.display = "flex";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (newPosition === 1) {
|
if (newPosition === 1) {
|
||||||
coWebsite.iframe.classList.add('sub-main');
|
coWebsite.iframe.classList.add("sub-main");
|
||||||
} else {
|
} else {
|
||||||
coWebsite.iframe.classList.remove('sub-main');
|
coWebsite.iframe.classList.remove("sub-main");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (newPosition >= 2) {
|
if (newPosition >= 2) {
|
||||||
coWebsite.iframe.classList.add('thumbnail');
|
coWebsite.iframe.classList.add("thumbnail");
|
||||||
} else {
|
} else {
|
||||||
coWebsite.iframe.classList.remove('thumbnail');
|
coWebsite.iframe.classList.remove("thumbnail");
|
||||||
}
|
}
|
||||||
|
|
||||||
coWebsite.position = newPosition;
|
coWebsite.position = newPosition;
|
||||||
|
|
||||||
if (oldSlot && !this.getCoWebsiteByPosition(oldSlot.position)) {
|
if (oldSlot && !this.getCoWebsiteByPosition(oldSlot.position)) {
|
||||||
oldSlot.container.style.display = 'none';
|
oldSlot.container.style.display = "none";
|
||||||
}
|
}
|
||||||
|
|
||||||
newSlot.container.style.display = 'block';
|
this.displayCowebsiteContainer();
|
||||||
|
|
||||||
coWebsite.iframe.classList.remove('pixel');
|
newSlot.container.style.display = "block";
|
||||||
|
|
||||||
|
coWebsite.iframe.classList.remove("pixel");
|
||||||
|
|
||||||
this.resizeAllIframes();
|
this.resizeAllIframes();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private displayCowebsiteContainer() {
|
||||||
|
if (this.coWebsites.find((cowebsite) => cowebsite.position > 0)) {
|
||||||
|
this.cowebsiteContainerDom.style.display = "block";
|
||||||
|
} else {
|
||||||
|
this.cowebsiteContainerDom.style.display = "none";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private moveLeftPreviousCoWebsite(coWebsite: CoWebsite, newPosition: number) {
|
private moveLeftPreviousCoWebsite(coWebsite: CoWebsite, newPosition: number) {
|
||||||
const nextCoWebsite = this.getCoWebsiteByPosition(coWebsite.position + 1);
|
const nextCoWebsite = this.getCoWebsiteByPosition(coWebsite.position + 1);
|
||||||
|
|
||||||
@ -384,11 +397,7 @@ class CoWebsiteManager {
|
|||||||
|
|
||||||
this.moveCoWebsite(coWebsite, newPosition);
|
this.moveCoWebsite(coWebsite, newPosition);
|
||||||
|
|
||||||
if (newPosition === 4 ||
|
if (newPosition === 4 || !currentCoWebsite || currentCoWebsite.iframe.id === coWebsite.iframe.id) {
|
||||||
!currentCoWebsite ||
|
|
||||||
currentCoWebsite.iframe.id === coWebsite.iframe.id
|
|
||||||
)
|
|
||||||
{
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -411,26 +420,26 @@ class CoWebsiteManager {
|
|||||||
if (coWebsite.position > 0) {
|
if (coWebsite.position > 0) {
|
||||||
const slot = this.getSlotByPosition(coWebsite.position);
|
const slot = this.getSlotByPosition(coWebsite.position);
|
||||||
if (slot) {
|
if (slot) {
|
||||||
slot.container.style.display = 'none';
|
slot.container.style.display = "none";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const previousCoWebsite = this.coWebsites.find((coWebsiteToCheck: CoWebsite) =>
|
const previousCoWebsite = this.coWebsites.find(
|
||||||
coWebsite.position + 1 === coWebsiteToCheck.position
|
(coWebsiteToCheck: CoWebsite) => coWebsite.position + 1 === coWebsiteToCheck.position
|
||||||
);
|
);
|
||||||
|
|
||||||
if (previousCoWebsite) {
|
if (previousCoWebsite) {
|
||||||
this.moveLeftPreviousCoWebsite(previousCoWebsite, coWebsite.position);
|
this.moveLeftPreviousCoWebsite(previousCoWebsite, coWebsite.position);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.displayCowebsiteContainer();
|
||||||
|
|
||||||
coWebsite.icon.remove();
|
coWebsite.icon.remove();
|
||||||
coWebsite.iframe.remove();
|
coWebsite.iframe.remove();
|
||||||
}
|
}
|
||||||
|
|
||||||
public searchJitsi(): CoWebsite | undefined {
|
public searchJitsi(): CoWebsite | undefined {
|
||||||
return this.coWebsites.find((coWebsite : CoWebsite) =>
|
return this.coWebsites.find((coWebsite: CoWebsite) => coWebsite.iframe.id.toLowerCase().includes("jitsi"));
|
||||||
coWebsite.iframe.id.toLowerCase().includes('jitsi')
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private generateCoWebsiteIcon(iframe: HTMLIFrameElement): HTMLDivElement {
|
private generateCoWebsiteIcon(iframe: HTMLIFrameElement): HTMLDivElement {
|
||||||
@ -439,7 +448,7 @@ class CoWebsiteManager {
|
|||||||
icon.style.display = "none";
|
icon.style.display = "none";
|
||||||
|
|
||||||
const iconImage = document.createElement("img");
|
const iconImage = document.createElement("img");
|
||||||
iconImage.src = `https://www.google.com/s2/favicons?sz=64&domain_url=${iframe.src}`;
|
iconImage.src = `${ICON_URL}/icon?url=${iframe.src}&size=16..30..256`;
|
||||||
const url = new URL(iframe.src);
|
const url = new URL(iframe.src);
|
||||||
iconImage.alt = url.hostname;
|
iconImage.alt = url.hostname;
|
||||||
|
|
||||||
@ -456,10 +465,10 @@ class CoWebsiteManager {
|
|||||||
widthPercent?: number,
|
widthPercent?: number,
|
||||||
position?: number
|
position?: number
|
||||||
): Promise<CoWebsite> {
|
): Promise<CoWebsite> {
|
||||||
|
return this.addCoWebsite(
|
||||||
return this.addCoWebsite((iframeBuffer) => {
|
(iframeBuffer) => {
|
||||||
const iframe = document.createElement("iframe");
|
const iframe = document.createElement("iframe");
|
||||||
iframe.src = new URL(url, base).toString()
|
iframe.src = new URL(url, base).toString();
|
||||||
|
|
||||||
if (allowPolicy) {
|
if (allowPolicy) {
|
||||||
iframe.allow = allowPolicy;
|
iframe.allow = allowPolicy;
|
||||||
@ -472,7 +481,10 @@ class CoWebsiteManager {
|
|||||||
iframeBuffer.appendChild(iframe);
|
iframeBuffer.appendChild(iframe);
|
||||||
|
|
||||||
return iframe;
|
return iframe;
|
||||||
}, widthPercent, position);
|
},
|
||||||
|
widthPercent,
|
||||||
|
position
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async addCoWebsite(
|
public async addCoWebsite(
|
||||||
@ -484,10 +496,10 @@ class CoWebsiteManager {
|
|||||||
if (this.coWebsites.length < 1) {
|
if (this.coWebsites.length < 1) {
|
||||||
this.loadMain();
|
this.loadMain();
|
||||||
} else if (this.coWebsites.length === 5) {
|
} else if (this.coWebsites.length === 5) {
|
||||||
throw new Error('Too many we')
|
throw new Error("Too many we");
|
||||||
}
|
}
|
||||||
|
|
||||||
Promise.resolve(callback(this.cowebsiteBufferDom)).then(iframe =>{
|
Promise.resolve(callback(this.cowebsiteBufferDom)).then((iframe) => {
|
||||||
iframe?.classList.add("pixel");
|
iframe?.classList.add("pixel");
|
||||||
|
|
||||||
if (!iframe.id) {
|
if (!iframe.id) {
|
||||||
@ -533,14 +545,14 @@ class CoWebsiteManager {
|
|||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.fire();
|
this.fire();
|
||||||
position !== undefined ?
|
position !== undefined
|
||||||
this.moveRightPreviousCoWebsite(coWebsite, coWebsite.position) :
|
? this.moveRightPreviousCoWebsite(coWebsite, coWebsite.position)
|
||||||
this.moveCoWebsite(coWebsite, coWebsite.position);
|
: this.moveCoWebsite(coWebsite, coWebsite.position);
|
||||||
}, animationTime);
|
}, animationTime);
|
||||||
} else {
|
} else {
|
||||||
position !== undefined ?
|
position !== undefined
|
||||||
this.moveRightPreviousCoWebsite(coWebsite, coWebsite.position) :
|
? this.moveRightPreviousCoWebsite(coWebsite, coWebsite.position)
|
||||||
this.moveCoWebsite(coWebsite, coWebsite.position);
|
: this.moveCoWebsite(coWebsite, coWebsite.position);
|
||||||
}
|
}
|
||||||
|
|
||||||
return resolve(coWebsite);
|
return resolve(coWebsite);
|
||||||
@ -583,8 +595,7 @@ class CoWebsiteManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public closeCoWebsites(): Promise<void> {
|
public closeCoWebsites(): Promise<void> {
|
||||||
this.currentOperationPromise = this.currentOperationPromise
|
this.currentOperationPromise = this.currentOperationPromise.then(() => {
|
||||||
.then(() => {
|
|
||||||
this.coWebsites.forEach((coWebsite: CoWebsite) => {
|
this.coWebsites.forEach((coWebsite: CoWebsite) => {
|
||||||
this.closeCoWebsite(coWebsite);
|
this.closeCoWebsite(coWebsite);
|
||||||
});
|
});
|
||||||
|
@ -65,6 +65,7 @@
|
|||||||
|
|
||||||
&-container {
|
&-container {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
display: none;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
|
@ -7,7 +7,6 @@ import MiniCssExtractPlugin from "mini-css-extract-plugin";
|
|||||||
import sveltePreprocess from "svelte-preprocess";
|
import sveltePreprocess from "svelte-preprocess";
|
||||||
import ForkTsCheckerWebpackPlugin from "fork-ts-checker-webpack-plugin";
|
import ForkTsCheckerWebpackPlugin from "fork-ts-checker-webpack-plugin";
|
||||||
import NodePolyfillPlugin from "node-polyfill-webpack-plugin";
|
import NodePolyfillPlugin from "node-polyfill-webpack-plugin";
|
||||||
import { POSTHOG_API_KEY, PROFILE_URL } from "./src/Enum/EnvironmentVariable";
|
|
||||||
|
|
||||||
const mode = process.env.NODE_ENV ?? "development";
|
const mode = process.env.NODE_ENV ?? "development";
|
||||||
const buildNpmTypingsForApi = !!process.env.BUILD_TYPINGS;
|
const buildNpmTypingsForApi = !!process.env.BUILD_TYPINGS;
|
||||||
@ -193,6 +192,7 @@ module.exports = {
|
|||||||
ADMIN_URL: undefined,
|
ADMIN_URL: undefined,
|
||||||
CONTACT_URL: null,
|
CONTACT_URL: null,
|
||||||
PROFILE_URL: null,
|
PROFILE_URL: null,
|
||||||
|
ICON_URL: null,
|
||||||
DEBUG_MODE: null,
|
DEBUG_MODE: null,
|
||||||
STUN_SERVER: null,
|
STUN_SERVER: null,
|
||||||
TURN_SERVER: null,
|
TURN_SERVER: null,
|
||||||
@ -207,6 +207,8 @@ module.exports = {
|
|||||||
POSTHOG_API_KEY: null,
|
POSTHOG_API_KEY: null,
|
||||||
POSTHOG_URL: null,
|
POSTHOG_URL: null,
|
||||||
NODE_ENV: mode,
|
NODE_ENV: mode,
|
||||||
|
DISABLE_ANONYMOUS: false,
|
||||||
|
OPID_LOGIN_SCREEN_PROVIDER: null,
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
} as Configuration & WebpackDevServer.Configuration;
|
} as Configuration & WebpackDevServer.Configuration;
|
||||||
|
@ -1,16 +1,18 @@
|
|||||||
WA.onInit().then(() => {
|
WA.onInit().then(() => {
|
||||||
let message;
|
let message;
|
||||||
|
|
||||||
WA.room.onEnterZone("carpet", () => {
|
WA.room.onEnterLayer("carpet").subscribe(() => {
|
||||||
message = WA.ui.displayActionMessage({
|
message = WA.ui.displayActionMessage({
|
||||||
message: "This is a test message. Press space to display a chat message. Walk out to hide the message.",
|
message:
|
||||||
|
"This is a test message. Press space to display a chat message. Walk out to hide the message.",
|
||||||
callback: () => {
|
callback: () => {
|
||||||
WA.chat.sendChatMessage("Hello world!", "The bot");
|
WA.chat.sendChatMessage("Hello world!", "The bot");
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
WA.room.onLeaveZone("carpet", () => {
|
|
||||||
|
WA.room.onLeaveLayer("carpet").subscribe(() => {
|
||||||
message && message.remove();
|
message && message.remove();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
1
maps/tests/Variables/Cache/.gitignore
vendored
Normal file
1
maps/tests/Variables/Cache/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
variables_tmp.json
|
5
maps/tests/Variables/Cache/script.js
Normal file
5
maps/tests/Variables/Cache/script.js
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
WA.onInit().then(async () => {
|
||||||
|
console.log('Trying to set variable "myvar". This should work, even if the cache was busted.');
|
||||||
|
await WA.state.saveVariable('myvar', {'foo': 'bar'});
|
||||||
|
console.log('SUCCESS!');
|
||||||
|
});
|
82
maps/tests/Variables/Cache/variables_cache_1.json
Normal file
82
maps/tests/Variables/Cache/variables_cache_1.json
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
{ "compressionlevel":-1,
|
||||||
|
"height":10,
|
||||||
|
"infinite":false,
|
||||||
|
"layers":[
|
||||||
|
{
|
||||||
|
"data":[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
|
||||||
|
"height":10,
|
||||||
|
"id":1,
|
||||||
|
"name":"floor",
|
||||||
|
"opacity":1,
|
||||||
|
"type":"tilelayer",
|
||||||
|
"visible":true,
|
||||||
|
"width":10,
|
||||||
|
"x":0,
|
||||||
|
"y":0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"data":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||||
|
"height":10,
|
||||||
|
"id":2,
|
||||||
|
"name":"start",
|
||||||
|
"opacity":1,
|
||||||
|
"type":"tilelayer",
|
||||||
|
"visible":true,
|
||||||
|
"width":10,
|
||||||
|
"x":0,
|
||||||
|
"y":0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"draworder":"topdown",
|
||||||
|
"id":3,
|
||||||
|
"name":"floorLayer",
|
||||||
|
"objects":[
|
||||||
|
{
|
||||||
|
"height":67,
|
||||||
|
"id":3,
|
||||||
|
"name":"",
|
||||||
|
"rotation":0,
|
||||||
|
"text":
|
||||||
|
{
|
||||||
|
"fontfamily":"Sans Serif",
|
||||||
|
"pixelsize":11,
|
||||||
|
"text":"Test:\nThis test is to be run automatically using testcafe",
|
||||||
|
"wrap":true
|
||||||
|
},
|
||||||
|
"type":"",
|
||||||
|
"visible":true,
|
||||||
|
"width":252.4375,
|
||||||
|
"x":2.78125,
|
||||||
|
"y":2.5
|
||||||
|
}],
|
||||||
|
"opacity":1,
|
||||||
|
"type":"objectgroup",
|
||||||
|
"visible":true,
|
||||||
|
"x":0,
|
||||||
|
"y":0
|
||||||
|
}],
|
||||||
|
"nextlayerid":8,
|
||||||
|
"nextobjectid":11,
|
||||||
|
"orientation":"orthogonal",
|
||||||
|
"renderorder":"right-down",
|
||||||
|
"tiledversion":"2021.03.23",
|
||||||
|
"tileheight":32,
|
||||||
|
"tilesets":[
|
||||||
|
{
|
||||||
|
"columns":11,
|
||||||
|
"firstgid":1,
|
||||||
|
"image":"..\/..\/tileset1.png",
|
||||||
|
"imageheight":352,
|
||||||
|
"imagewidth":352,
|
||||||
|
"margin":0,
|
||||||
|
"name":"tileset1",
|
||||||
|
"spacing":0,
|
||||||
|
"tilecount":121,
|
||||||
|
"tileheight":32,
|
||||||
|
"tilewidth":32
|
||||||
|
}],
|
||||||
|
"tilewidth":32,
|
||||||
|
"type":"map",
|
||||||
|
"version":1.5,
|
||||||
|
"width":10
|
||||||
|
}
|
126
maps/tests/Variables/Cache/variables_cache_2.json
Normal file
126
maps/tests/Variables/Cache/variables_cache_2.json
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
{ "compressionlevel":-1,
|
||||||
|
"height":10,
|
||||||
|
"infinite":false,
|
||||||
|
"layers":[
|
||||||
|
{
|
||||||
|
"data":[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
|
||||||
|
"height":10,
|
||||||
|
"id":1,
|
||||||
|
"name":"floor",
|
||||||
|
"opacity":1,
|
||||||
|
"type":"tilelayer",
|
||||||
|
"visible":true,
|
||||||
|
"width":10,
|
||||||
|
"x":0,
|
||||||
|
"y":0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"data":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||||
|
"height":10,
|
||||||
|
"id":2,
|
||||||
|
"name":"start",
|
||||||
|
"opacity":1,
|
||||||
|
"type":"tilelayer",
|
||||||
|
"visible":true,
|
||||||
|
"width":10,
|
||||||
|
"x":0,
|
||||||
|
"y":0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"draworder":"topdown",
|
||||||
|
"id":3,
|
||||||
|
"name":"floorLayer",
|
||||||
|
"objects":[
|
||||||
|
{
|
||||||
|
"height":67,
|
||||||
|
"id":3,
|
||||||
|
"name":"",
|
||||||
|
"rotation":0,
|
||||||
|
"text":
|
||||||
|
{
|
||||||
|
"fontfamily":"Sans Serif",
|
||||||
|
"pixelsize":11,
|
||||||
|
"text":"Test:\nThis test is to be run automatically using testcafe\n\n(2nd file)",
|
||||||
|
"wrap":true
|
||||||
|
},
|
||||||
|
"type":"",
|
||||||
|
"visible":true,
|
||||||
|
"width":252.4375,
|
||||||
|
"x":2.78125,
|
||||||
|
"y":2.5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"height":0,
|
||||||
|
"id":9,
|
||||||
|
"name":"myvar",
|
||||||
|
"point":true,
|
||||||
|
"properties":[
|
||||||
|
{
|
||||||
|
"name":"default",
|
||||||
|
"type":"string",
|
||||||
|
"value":"{}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"jsonSchema",
|
||||||
|
"type":"string",
|
||||||
|
"value":"{}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"persist",
|
||||||
|
"type":"bool",
|
||||||
|
"value":true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"readableBy",
|
||||||
|
"type":"string",
|
||||||
|
"value":""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"writableBy",
|
||||||
|
"type":"string",
|
||||||
|
"value":""
|
||||||
|
}],
|
||||||
|
"rotation":0,
|
||||||
|
"type":"variable",
|
||||||
|
"visible":true,
|
||||||
|
"width":0,
|
||||||
|
"x":88.8149900876127,
|
||||||
|
"y":147.75212636695
|
||||||
|
}],
|
||||||
|
"opacity":1,
|
||||||
|
"type":"objectgroup",
|
||||||
|
"visible":true,
|
||||||
|
"x":0,
|
||||||
|
"y":0
|
||||||
|
}],
|
||||||
|
"nextlayerid":8,
|
||||||
|
"nextobjectid":11,
|
||||||
|
"orientation":"orthogonal",
|
||||||
|
"properties":[
|
||||||
|
{
|
||||||
|
"name":"script",
|
||||||
|
"type":"string",
|
||||||
|
"value":"script.js"
|
||||||
|
}],
|
||||||
|
"renderorder":"right-down",
|
||||||
|
"tiledversion":"2021.03.23",
|
||||||
|
"tileheight":32,
|
||||||
|
"tilesets":[
|
||||||
|
{
|
||||||
|
"columns":11,
|
||||||
|
"firstgid":1,
|
||||||
|
"image":"..\/..\/tileset1.png",
|
||||||
|
"imageheight":352,
|
||||||
|
"imagewidth":352,
|
||||||
|
"margin":0,
|
||||||
|
"name":"tileset1",
|
||||||
|
"spacing":0,
|
||||||
|
"tilecount":121,
|
||||||
|
"tileheight":32,
|
||||||
|
"tilewidth":32
|
||||||
|
}],
|
||||||
|
"tilewidth":32,
|
||||||
|
"type":"map",
|
||||||
|
"version":1.5,
|
||||||
|
"width":10
|
||||||
|
}
|
@ -6,6 +6,7 @@ import { PrometheusController } from "./Controller/PrometheusController";
|
|||||||
import { DebugController } from "./Controller/DebugController";
|
import { DebugController } from "./Controller/DebugController";
|
||||||
import { App as uwsApp } from "./Server/sifrr.server";
|
import { App as uwsApp } from "./Server/sifrr.server";
|
||||||
import { AdminController } from "./Controller/AdminController";
|
import { AdminController } from "./Controller/AdminController";
|
||||||
|
import { OpenIdProfileController } from "./Controller/OpenIdProfileController";
|
||||||
|
|
||||||
class App {
|
class App {
|
||||||
public app: uwsApp;
|
public app: uwsApp;
|
||||||
@ -15,6 +16,7 @@ class App {
|
|||||||
public prometheusController: PrometheusController;
|
public prometheusController: PrometheusController;
|
||||||
private debugController: DebugController;
|
private debugController: DebugController;
|
||||||
private adminController: AdminController;
|
private adminController: AdminController;
|
||||||
|
private openIdProfileController: OpenIdProfileController;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.app = new uwsApp();
|
this.app = new uwsApp();
|
||||||
@ -26,6 +28,7 @@ class App {
|
|||||||
this.prometheusController = new PrometheusController(this.app);
|
this.prometheusController = new PrometheusController(this.app);
|
||||||
this.debugController = new DebugController(this.app);
|
this.debugController = new DebugController(this.app);
|
||||||
this.adminController = new AdminController(this.app);
|
this.adminController = new AdminController(this.app);
|
||||||
|
this.openIdProfileController = new OpenIdProfileController(this.app);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
import { v4 } from "uuid";
|
import { v4 } from "uuid";
|
||||||
import { HttpRequest, HttpResponse, TemplatedApp } from "uWebSockets.js";
|
import { HttpRequest, HttpResponse, TemplatedApp } from "uWebSockets.js";
|
||||||
import { BaseController } from "./BaseController";
|
import { BaseController } from "./BaseController";
|
||||||
import { adminApi } from "../Services/AdminApi";
|
import { adminApi, 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";
|
||||||
|
import { DISABLE_ANONYMOUS } from "../Enum/EnvironmentVariable";
|
||||||
import log from "../Services/Logger";
|
import log from "../Services/Logger";
|
||||||
|
|
||||||
export interface TokenInterface {
|
export interface TokenInterface {
|
||||||
@ -56,19 +57,37 @@ export class AuthenticateController extends BaseController {
|
|||||||
res.onAborted(() => {
|
res.onAborted(() => {
|
||||||
log.warn("/message request was aborted");
|
log.warn("/message request was aborted");
|
||||||
});
|
});
|
||||||
const { code, nonce, token } = parse(req.getQuery());
|
const IPAddress = req.getHeader("x-forwarded-for");
|
||||||
|
const { code, nonce, token, playUri } = parse(req.getQuery());
|
||||||
try {
|
try {
|
||||||
//verify connected by token
|
//verify connected by token
|
||||||
if (token != undefined) {
|
if (token != undefined) {
|
||||||
try {
|
try {
|
||||||
const authTokenData: AuthTokenData = jwtTokenManager.verifyJWTToken(token as string, false);
|
const authTokenData: AuthTokenData = jwtTokenManager.verifyJWTToken(token as string, false);
|
||||||
if (authTokenData.hydraAccessToken == undefined) {
|
|
||||||
throw Error("Token cannot to be check on Hydra");
|
//Get user data from Admin Back Office
|
||||||
}
|
//This is very important to create User Local in LocalStorage in WorkAdventure
|
||||||
await openIDClient.checkTokenAuth(authTokenData.hydraAccessToken);
|
const resUserData = await this.getUserByUserIdentifier(
|
||||||
|
authTokenData.identifier,
|
||||||
|
playUri as string,
|
||||||
|
IPAddress
|
||||||
|
);
|
||||||
|
|
||||||
|
if (authTokenData.accessToken == undefined) {
|
||||||
|
//if not nonce and code, user connected in anonymous
|
||||||
|
//get data with identifier and return token
|
||||||
|
if (!code && !nonce) {
|
||||||
res.writeStatus("200");
|
res.writeStatus("200");
|
||||||
this.addCorsHeaders(res);
|
this.addCorsHeaders(res);
|
||||||
return res.end(JSON.stringify({ authToken: token }));
|
return res.end(JSON.stringify({ ...resUserData, authToken: token }));
|
||||||
|
}
|
||||||
|
throw Error("Token cannot to be check on Hydra");
|
||||||
|
}
|
||||||
|
|
||||||
|
const resCheckTokenAuth = await openIDClient.checkTokenAuth(authTokenData.accessToken);
|
||||||
|
res.writeStatus("200");
|
||||||
|
this.addCorsHeaders(res);
|
||||||
|
return res.end(JSON.stringify({ ...resCheckTokenAuth, ...resUserData, authToken: token }));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
log.info("User was not connected", err);
|
log.info("User was not connected", err);
|
||||||
}
|
}
|
||||||
@ -80,10 +99,15 @@ export class AuthenticateController extends BaseController {
|
|||||||
if (!email) {
|
if (!email) {
|
||||||
throw new Error("No email in the response");
|
throw new Error("No email in the response");
|
||||||
}
|
}
|
||||||
const authToken = jwtTokenManager.createAuthToken(email, userInfo.access_token);
|
const authToken = jwtTokenManager.createAuthToken(email, userInfo?.access_token);
|
||||||
|
|
||||||
|
//Get user data from Admin Back Office
|
||||||
|
//This is very important to create User Local in LocalStorage in WorkAdventure
|
||||||
|
const data = await this.getUserByUserIdentifier(email, playUri as string, IPAddress);
|
||||||
|
|
||||||
res.writeStatus("200");
|
res.writeStatus("200");
|
||||||
this.addCorsHeaders(res);
|
this.addCorsHeaders(res);
|
||||||
return res.end(JSON.stringify({ authToken }));
|
return res.end(JSON.stringify({ ...data, authToken }));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log.error("openIDCallback => ERROR", e);
|
log.error("openIDCallback => ERROR", e);
|
||||||
return this.errorToResponse(e, res);
|
return this.errorToResponse(e, res);
|
||||||
@ -100,10 +124,10 @@ export class AuthenticateController extends BaseController {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const authTokenData: AuthTokenData = jwtTokenManager.verifyJWTToken(token as string, false);
|
const authTokenData: AuthTokenData = jwtTokenManager.verifyJWTToken(token as string, false);
|
||||||
if (authTokenData.hydraAccessToken == undefined) {
|
if (authTokenData.accessToken == undefined) {
|
||||||
throw Error("Token cannot to be logout on Hydra");
|
throw Error("Token cannot to be logout on Hydra");
|
||||||
}
|
}
|
||||||
await openIDClient.logoutUser(authTokenData.hydraAccessToken);
|
await openIDClient.logoutUser(authTokenData.accessToken);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.error("openIDCallback => logout-callback", error);
|
log.error("openIDCallback => logout-callback", error);
|
||||||
} finally {
|
} finally {
|
||||||
@ -176,6 +200,10 @@ export class AuthenticateController extends BaseController {
|
|||||||
log.warn("Login request was aborted");
|
log.warn("Login request was aborted");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (DISABLE_ANONYMOUS) {
|
||||||
|
res.writeStatus("403 FORBIDDEN");
|
||||||
|
res.end();
|
||||||
|
} else {
|
||||||
const userUuid = v4();
|
const userUuid = v4();
|
||||||
const authToken = jwtTokenManager.createAuthToken(userUuid);
|
const authToken = jwtTokenManager.createAuthToken(userUuid);
|
||||||
res.writeStatus("200 OK");
|
res.writeStatus("200 OK");
|
||||||
@ -186,6 +214,7 @@ export class AuthenticateController extends BaseController {
|
|||||||
userUuid,
|
userUuid,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -197,20 +226,20 @@ export class AuthenticateController extends BaseController {
|
|||||||
res.onAborted(() => {
|
res.onAborted(() => {
|
||||||
log.warn("/message request was aborted");
|
log.warn("/message request was aborted");
|
||||||
});
|
});
|
||||||
const { userIdentify, token } = parse(req.getQuery());
|
const { token } = parse(req.getQuery());
|
||||||
try {
|
try {
|
||||||
//verify connected by token
|
//verify connected by token
|
||||||
if (token != undefined) {
|
if (token != undefined) {
|
||||||
try {
|
try {
|
||||||
const authTokenData: AuthTokenData = jwtTokenManager.verifyJWTToken(token as string, false);
|
const authTokenData: AuthTokenData = jwtTokenManager.verifyJWTToken(token as string, false);
|
||||||
if (authTokenData.hydraAccessToken == undefined) {
|
if (authTokenData.accessToken == undefined) {
|
||||||
throw Error("Token cannot to be check on Hydra");
|
throw Error("Token cannot to be check on Hydra");
|
||||||
}
|
}
|
||||||
await openIDClient.checkTokenAuth(authTokenData.hydraAccessToken);
|
await openIDClient.checkTokenAuth(authTokenData.accessToken);
|
||||||
|
|
||||||
//get login profile
|
//get login profile
|
||||||
res.writeStatus("302");
|
res.writeStatus("302");
|
||||||
res.writeHeader("Location", adminApi.getProfileUrl(authTokenData.hydraAccessToken));
|
res.writeHeader("Location", adminApi.getProfileUrl(authTokenData.accessToken));
|
||||||
this.addCorsHeaders(res);
|
this.addCorsHeaders(res);
|
||||||
// eslint-disable-next-line no-unsafe-finally
|
// eslint-disable-next-line no-unsafe-finally
|
||||||
return res.end();
|
return res.end();
|
||||||
@ -224,4 +253,33 @@ export class AuthenticateController extends BaseController {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param email
|
||||||
|
* @param playUri
|
||||||
|
* @param IPAddress
|
||||||
|
* @return FetchMemberDataByUuidResponse|object
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private async getUserByUserIdentifier(
|
||||||
|
email: string,
|
||||||
|
playUri: string,
|
||||||
|
IPAddress: string
|
||||||
|
): Promise<FetchMemberDataByUuidResponse | object> {
|
||||||
|
let data: FetchMemberDataByUuidResponse = {
|
||||||
|
email: email,
|
||||||
|
userUuid: email,
|
||||||
|
tags: [],
|
||||||
|
messages: [],
|
||||||
|
visitCardUrl: null,
|
||||||
|
textures: [],
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
data = await adminApi.fetchMemberDataByUuid(email, playUri, IPAddress);
|
||||||
|
} catch (err) {
|
||||||
|
console.error("openIDCallback => fetchMemberDataByUuid", err);
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
import { HttpResponse } from "uWebSockets.js";
|
import { HttpResponse } from "uWebSockets.js";
|
||||||
|
import { FRONT_URL } from "../Enum/EnvironmentVariable";
|
||||||
import log from "../Services/Logger";
|
import log from "../Services/Logger";
|
||||||
|
|
||||||
export class BaseController {
|
export class BaseController {
|
||||||
protected addCorsHeaders(res: HttpResponse): void {
|
protected addCorsHeaders(res: HttpResponse): void {
|
||||||
res.writeHeader("access-control-allow-headers", "Origin, X-Requested-With, Content-Type, Accept");
|
res.writeHeader("access-control-allow-headers", "Origin, X-Requested-With, Content-Type, Accept");
|
||||||
res.writeHeader("access-control-allow-methods", "GET, POST, OPTIONS, PUT, PATCH, DELETE");
|
res.writeHeader("access-control-allow-methods", "GET, POST, OPTIONS, PUT, PATCH, DELETE");
|
||||||
res.writeHeader("access-control-allow-origin", "*");
|
res.writeHeader("access-control-allow-origin", FRONT_URL);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -26,10 +26,9 @@ import { jwtTokenManager, tokenInvalidException } from "../Services/JWTTokenMana
|
|||||||
import { adminApi, FetchMemberDataByUuidResponse } from "../Services/AdminApi";
|
import { adminApi, FetchMemberDataByUuidResponse } from "../Services/AdminApi";
|
||||||
import { SocketManager, socketManager } from "../Services/SocketManager";
|
import { SocketManager, socketManager } from "../Services/SocketManager";
|
||||||
import { emitInBatch } from "../Services/IoSocketHelpers";
|
import { emitInBatch } from "../Services/IoSocketHelpers";
|
||||||
import { ADMIN_API_TOKEN, ADMIN_API_URL, SOCKET_IDLE_TIMER } from "../Enum/EnvironmentVariable";
|
import { ADMIN_SOCKETS_TOKEN, ADMIN_API_URL, DISABLE_ANONYMOUS, SOCKET_IDLE_TIMER } from "../Enum/EnvironmentVariable";
|
||||||
import { Zone } from "_Model/Zone";
|
import { Zone } from "_Model/Zone";
|
||||||
import { ExAdminSocketInterface } from "_Model/Websocket/ExAdminSocketInterface";
|
import { ExAdminSocketInterface } from "_Model/Websocket/ExAdminSocketInterface";
|
||||||
import { v4 } from "uuid";
|
|
||||||
import { CharacterTexture } from "../Services/AdminApi/CharacterTexture";
|
import { CharacterTexture } from "../Services/AdminApi/CharacterTexture";
|
||||||
import log from "../Services/Logger";
|
import log from "../Services/Logger";
|
||||||
|
|
||||||
@ -49,15 +48,19 @@ 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 token = query.token;
|
const token = query.token;
|
||||||
if (token !== ADMIN_API_TOKEN) {
|
let authorizedRoomIds: string[];
|
||||||
|
try {
|
||||||
|
const data = jwtTokenManager.verifyAdminSocketToken(token as string);
|
||||||
|
authorizedRoomIds = data.authorizedRoomIds;
|
||||||
|
} catch (e) {
|
||||||
log.info("Admin access refused for token: " + token);
|
log.info("Admin access refused for token: " + token);
|
||||||
res.writeStatus("401 Unauthorized").end("Incorrect token");
|
res.writeStatus("401 Unauthorized").end("Incorrect token");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const roomId = query.roomId;
|
const roomId = query.roomId;
|
||||||
if (typeof roomId !== "string") {
|
if (typeof roomId !== "string" || !authorizedRoomIds.includes(roomId)) {
|
||||||
log.error("Received");
|
log.error("Invalid room id");
|
||||||
res.writeStatus("400 Bad Request").end("Missing room id");
|
res.writeStatus("403 Bad Request").end("Invalid room id");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -71,8 +74,6 @@ export class IoSocketController {
|
|||||||
},
|
},
|
||||||
message: (ws, arrayBuffer, isBinary): void => {
|
message: (ws, arrayBuffer, isBinary): void => {
|
||||||
try {
|
try {
|
||||||
const roomId = ws.roomId as string;
|
|
||||||
|
|
||||||
//TODO refactor message type and data
|
//TODO refactor message type and data
|
||||||
const message: { event: string; message: { type: string; message: unknown; userUuid: string } } =
|
const message: { event: string; message: { type: string; message: unknown; userUuid: string } } =
|
||||||
JSON.parse(new TextDecoder("utf-8").decode(new Uint8Array(arrayBuffer)));
|
JSON.parse(new TextDecoder("utf-8").decode(new Uint8Array(arrayBuffer)));
|
||||||
@ -176,6 +177,11 @@ export class IoSocketController {
|
|||||||
|
|
||||||
const tokenData =
|
const tokenData =
|
||||||
token && typeof token === "string" ? jwtTokenManager.verifyJWTToken(token) : null;
|
token && typeof token === "string" ? jwtTokenManager.verifyJWTToken(token) : null;
|
||||||
|
|
||||||
|
if (DISABLE_ANONYMOUS && !tokenData) {
|
||||||
|
throw new Error("Expecting token");
|
||||||
|
}
|
||||||
|
|
||||||
const userIdentifier = tokenData ? tokenData.identifier : "";
|
const userIdentifier = tokenData ? tokenData.identifier : "";
|
||||||
|
|
||||||
let memberTags: string[] = [];
|
let memberTags: string[] = [];
|
||||||
@ -184,6 +190,7 @@ export class IoSocketController {
|
|||||||
let memberTextures: CharacterTexture[] = [];
|
let memberTextures: CharacterTexture[] = [];
|
||||||
const room = await socketManager.getOrCreateRoom(roomId);
|
const room = await socketManager.getOrCreateRoom(roomId);
|
||||||
let userData: FetchMemberDataByUuidResponse = {
|
let userData: FetchMemberDataByUuidResponse = {
|
||||||
|
email: userIdentifier,
|
||||||
userUuid: userIdentifier,
|
userUuid: userIdentifier,
|
||||||
tags: [],
|
tags: [],
|
||||||
visitCardUrl: null,
|
visitCardUrl: null,
|
||||||
|
@ -2,9 +2,9 @@ import { HttpRequest, HttpResponse, TemplatedApp } from "uWebSockets.js";
|
|||||||
import { BaseController } from "./BaseController";
|
import { BaseController } from "./BaseController";
|
||||||
import { parse } from "query-string";
|
import { parse } from "query-string";
|
||||||
import { adminApi } from "../Services/AdminApi";
|
import { adminApi } from "../Services/AdminApi";
|
||||||
import { ADMIN_API_URL } from "../Enum/EnvironmentVariable";
|
import { ADMIN_API_URL, DISABLE_ANONYMOUS, FRONT_URL } from "../Enum/EnvironmentVariable";
|
||||||
import { GameRoomPolicyTypes } from "../Model/PusherRoom";
|
import { GameRoomPolicyTypes } from "../Model/PusherRoom";
|
||||||
import { MapDetailsData } from "../Services/AdminApi/MapDetailsData";
|
import { isMapDetailsData, MapDetailsData } from "../Services/AdminApi/MapDetailsData";
|
||||||
import { socketManager } from "../Services/SocketManager";
|
import { socketManager } from "../Services/SocketManager";
|
||||||
import { AuthTokenData, jwtTokenManager } from "../Services/JWTTokenManager";
|
import { AuthTokenData, jwtTokenManager } from "../Services/JWTTokenManager";
|
||||||
import { v4 } from "uuid";
|
import { v4 } from "uuid";
|
||||||
@ -21,7 +21,6 @@ export class MapController extends BaseController {
|
|||||||
getMapUrl() {
|
getMapUrl() {
|
||||||
this.App.options("/map", (res: HttpResponse, req: HttpRequest) => {
|
this.App.options("/map", (res: HttpResponse, req: HttpRequest) => {
|
||||||
this.addCorsHeaders(res);
|
this.addCorsHeaders(res);
|
||||||
|
|
||||||
res.end();
|
res.end();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -65,6 +64,7 @@ export class MapController extends BaseController {
|
|||||||
tags: [],
|
tags: [],
|
||||||
textures: [],
|
textures: [],
|
||||||
contactPage: undefined,
|
contactPage: undefined,
|
||||||
|
authenticationMandatory: DISABLE_ANONYMOUS,
|
||||||
} as MapDetailsData)
|
} as MapDetailsData)
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -80,14 +80,26 @@ export class MapController extends BaseController {
|
|||||||
authTokenData = jwtTokenManager.verifyJWTToken(query.authToken as string);
|
authTokenData = jwtTokenManager.verifyJWTToken(query.authToken as string);
|
||||||
userId = authTokenData.identifier;
|
userId = authTokenData.identifier;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
try {
|
||||||
// Decode token, in this case we don't need to create new token.
|
// Decode token, in this case we don't need to create new token.
|
||||||
authTokenData = jwtTokenManager.verifyJWTToken(query.authToken as string, true);
|
authTokenData = jwtTokenManager.verifyJWTToken(query.authToken as string, true);
|
||||||
userId = authTokenData.identifier;
|
userId = authTokenData.identifier;
|
||||||
log.info("JWT expire, but decoded", userId);
|
log.info("JWT expire, but decoded", userId);
|
||||||
|
} catch (e) {
|
||||||
|
// The token was not good, redirect user on login page
|
||||||
|
res.writeStatus("500");
|
||||||
|
res.writeHeader("Access-Control-Allow-Origin", FRONT_URL);
|
||||||
|
res.end("Token decrypted error");
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const mapDetails = await adminApi.fetchMapDetails(query.playUri as string, userId);
|
const mapDetails = await adminApi.fetchMapDetails(query.playUri as string, userId);
|
||||||
|
|
||||||
|
if (isMapDetailsData(mapDetails) && DISABLE_ANONYMOUS) {
|
||||||
|
mapDetails.authenticationMandatory = true;
|
||||||
|
}
|
||||||
|
|
||||||
res.writeStatus("200 OK");
|
res.writeStatus("200 OK");
|
||||||
this.addCorsHeaders(res);
|
this.addCorsHeaders(res);
|
||||||
res.end(JSON.stringify(mapDetails));
|
res.end(JSON.stringify(mapDetails));
|
||||||
|
80
pusher/src/Controller/OpenIdProfileController.ts
Normal file
80
pusher/src/Controller/OpenIdProfileController.ts
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
import { BaseController } from "./BaseController";
|
||||||
|
import { HttpRequest, HttpResponse, TemplatedApp } from "uWebSockets.js";
|
||||||
|
import { parse } from "query-string";
|
||||||
|
import { openIDClient } from "../Services/OpenIDClient";
|
||||||
|
import { AuthTokenData, jwtTokenManager } from "../Services/JWTTokenManager";
|
||||||
|
import { adminApi } from "../Services/AdminApi";
|
||||||
|
import { OPID_CLIENT_ISSUER } from "../Enum/EnvironmentVariable";
|
||||||
|
import { IntrospectionResponse } from "openid-client";
|
||||||
|
|
||||||
|
export class OpenIdProfileController extends BaseController {
|
||||||
|
constructor(private App: TemplatedApp) {
|
||||||
|
super();
|
||||||
|
this.profileOpenId();
|
||||||
|
}
|
||||||
|
|
||||||
|
profileOpenId() {
|
||||||
|
//eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||||
|
this.App.get("/profile", async (res: HttpResponse, req: HttpRequest) => {
|
||||||
|
res.onAborted(() => {
|
||||||
|
console.warn("/message request was aborted");
|
||||||
|
});
|
||||||
|
|
||||||
|
const { accessToken } = parse(req.getQuery());
|
||||||
|
if (!accessToken) {
|
||||||
|
throw Error("Access token expected cannot to be check on Hydra");
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const resCheckTokenAuth = await openIDClient.checkTokenAuth(accessToken as string);
|
||||||
|
if (!resCheckTokenAuth.email) {
|
||||||
|
throw "Email was not found";
|
||||||
|
}
|
||||||
|
res.end(
|
||||||
|
this.buildHtml(
|
||||||
|
OPID_CLIENT_ISSUER,
|
||||||
|
resCheckTokenAuth.email as string,
|
||||||
|
resCheckTokenAuth.picture as string | undefined
|
||||||
|
)
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("profileCallback => ERROR", error);
|
||||||
|
this.errorToResponse(error, res);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
buildHtml(domain: string, email: string, pictureUrl?: string) {
|
||||||
|
return `
|
||||||
|
<!DOCTYPE>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<style>
|
||||||
|
*{
|
||||||
|
font-family: PixelFont-7, monospace;
|
||||||
|
}
|
||||||
|
body{
|
||||||
|
text-align: center;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
section{
|
||||||
|
margin: 20px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<section>
|
||||||
|
<img src="${pictureUrl ? pictureUrl : "/images/profile"}">
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
Profile validated by domain: <span style="font-weight: bold">${domain}</span>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
Your email: <span style="font-weight: bold">${email}</span>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
@ -4,6 +4,7 @@ const API_URL = process.env.API_URL || "";
|
|||||||
const ADMIN_API_URL = process.env.ADMIN_API_URL || "";
|
const ADMIN_API_URL = process.env.ADMIN_API_URL || "";
|
||||||
const ADMIN_URL = process.env.ADMIN_URL || "";
|
const ADMIN_URL = process.env.ADMIN_URL || "";
|
||||||
const ADMIN_API_TOKEN = process.env.ADMIN_API_TOKEN || "myapitoken";
|
const ADMIN_API_TOKEN = process.env.ADMIN_API_TOKEN || "myapitoken";
|
||||||
|
export const ADMIN_SOCKETS_TOKEN = process.env.ADMIN_SOCKETS_TOKEN || "myapitoken";
|
||||||
const CPU_OVERHEAT_THRESHOLD = Number(process.env.CPU_OVERHEAT_THRESHOLD) || 80;
|
const CPU_OVERHEAT_THRESHOLD = Number(process.env.CPU_OVERHEAT_THRESHOLD) || 80;
|
||||||
const JITSI_URL: string | undefined = process.env.JITSI_URL === "" ? undefined : process.env.JITSI_URL;
|
const JITSI_URL: string | undefined = process.env.JITSI_URL === "" ? undefined : process.env.JITSI_URL;
|
||||||
const JITSI_ISS = process.env.JITSI_ISS || "";
|
const JITSI_ISS = process.env.JITSI_ISS || "";
|
||||||
@ -15,6 +16,9 @@ export const FRONT_URL = process.env.FRONT_URL || "http://localhost";
|
|||||||
export const OPID_CLIENT_ID = process.env.OPID_CLIENT_ID || "";
|
export const OPID_CLIENT_ID = process.env.OPID_CLIENT_ID || "";
|
||||||
export const OPID_CLIENT_SECRET = process.env.OPID_CLIENT_SECRET || "";
|
export const OPID_CLIENT_SECRET = process.env.OPID_CLIENT_SECRET || "";
|
||||||
export const OPID_CLIENT_ISSUER = process.env.OPID_CLIENT_ISSUER || "";
|
export const OPID_CLIENT_ISSUER = process.env.OPID_CLIENT_ISSUER || "";
|
||||||
|
export const OPID_CLIENT_REDIRECT_URL = process.env.OPID_CLIENT_REDIRECT_URL || FRONT_URL + "/jwt";
|
||||||
|
export const OPID_PROFILE_SCREEN_PROVIDER = process.env.OPID_PROFILE_SCREEN_PROVIDER || ADMIN_URL + "/profile";
|
||||||
|
export const DISABLE_ANONYMOUS: boolean = process.env.DISABLE_ANONYMOUS === "true";
|
||||||
|
|
||||||
export {
|
export {
|
||||||
SECRET_KEY,
|
SECRET_KEY,
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { ADMIN_API_TOKEN, ADMIN_API_URL, ADMIN_URL } from "../Enum/EnvironmentVariable";
|
import { ADMIN_API_TOKEN, ADMIN_API_URL, ADMIN_URL, OPID_PROFILE_SCREEN_PROVIDER } from "../Enum/EnvironmentVariable";
|
||||||
import Axios from "axios";
|
import Axios from "axios";
|
||||||
import { GameRoomPolicyTypes } from "_Model/PusherRoom";
|
import { GameRoomPolicyTypes } from "_Model/PusherRoom";
|
||||||
import { CharacterTexture } from "./AdminApi/CharacterTexture";
|
import { CharacterTexture } from "./AdminApi/CharacterTexture";
|
||||||
@ -22,6 +22,7 @@ export interface AdminBannedData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface FetchMemberDataByUuidResponse {
|
export interface FetchMemberDataByUuidResponse {
|
||||||
|
email: string;
|
||||||
userUuid: string;
|
userUuid: string;
|
||||||
tags: string[];
|
tags: string[];
|
||||||
visitCardUrl: string | null;
|
visitCardUrl: string | null;
|
||||||
@ -142,13 +143,19 @@ class AdminApi {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/*TODO add constant to use profile companny*/
|
/**
|
||||||
|
*
|
||||||
|
* @param accessToken
|
||||||
|
*/
|
||||||
getProfileUrl(accessToken: string): string {
|
getProfileUrl(accessToken: string): string {
|
||||||
if (!ADMIN_URL) {
|
if (!OPID_PROFILE_SCREEN_PROVIDER) {
|
||||||
throw new Error("No admin backoffice set!");
|
throw new Error("No admin backoffice set!");
|
||||||
}
|
}
|
||||||
|
return `${OPID_PROFILE_SCREEN_PROVIDER}?accessToken=${accessToken}`;
|
||||||
|
}
|
||||||
|
|
||||||
return ADMIN_URL + `/profile?token=${accessToken}`;
|
async logoutOauth(token: string) {
|
||||||
|
await Axios.get(ADMIN_API_URL + `/oauth/logout?token=${token}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,6 +16,7 @@ export const isMapDetailsData = new tg.IsInterface()
|
|||||||
tags: tg.isArray(tg.isString),
|
tags: tg.isArray(tg.isString),
|
||||||
textures: tg.isArray(isCharacterTexture),
|
textures: tg.isArray(isCharacterTexture),
|
||||||
contactPage: tg.isUnion(tg.isString, tg.isUndefined),
|
contactPage: tg.isUnion(tg.isString, tg.isUndefined),
|
||||||
|
authenticationMandatory: tg.isUnion(tg.isBoolean, tg.isUndefined),
|
||||||
})
|
})
|
||||||
.get();
|
.get();
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { ADMIN_API_URL, ALLOW_ARTILLERY, SECRET_KEY } from "../Enum/EnvironmentVariable";
|
import { ADMIN_API_URL, ADMIN_SOCKETS_TOKEN, ALLOW_ARTILLERY, SECRET_KEY } from "../Enum/EnvironmentVariable";
|
||||||
import { uuid } from "uuidv4";
|
import { uuid } from "uuidv4";
|
||||||
import Jwt, { verify } from "jsonwebtoken";
|
import Jwt, { verify } from "jsonwebtoken";
|
||||||
import { TokenInterface } from "../Controller/AuthenticateController";
|
import { TokenInterface } from "../Controller/AuthenticateController";
|
||||||
@ -6,13 +6,20 @@ import { adminApi, AdminBannedData } from "../Services/AdminApi";
|
|||||||
|
|
||||||
export interface AuthTokenData {
|
export interface AuthTokenData {
|
||||||
identifier: string; //will be a email if logged in or an uuid if anonymous
|
identifier: string; //will be a email if logged in or an uuid if anonymous
|
||||||
hydraAccessToken?: string;
|
accessToken?: string;
|
||||||
|
}
|
||||||
|
export interface AdminSocketTokenData {
|
||||||
|
authorizedRoomIds: string[]; //the list of rooms the client is authorized to read from.
|
||||||
}
|
}
|
||||||
export const tokenInvalidException = "tokenInvalid";
|
export const tokenInvalidException = "tokenInvalid";
|
||||||
|
|
||||||
class JWTTokenManager {
|
class JWTTokenManager {
|
||||||
public createAuthToken(identifier: string, hydraAccessToken?: string) {
|
public verifyAdminSocketToken(token: string): AdminSocketTokenData {
|
||||||
return Jwt.sign({ identifier, hydraAccessToken }, SECRET_KEY, { expiresIn: "30d" });
|
return Jwt.verify(token, ADMIN_SOCKETS_TOKEN) as AdminSocketTokenData;
|
||||||
|
}
|
||||||
|
|
||||||
|
public createAuthToken(identifier: string, accessToken?: string) {
|
||||||
|
return Jwt.sign({ identifier, accessToken }, SECRET_KEY, { expiresIn: "30d" });
|
||||||
}
|
}
|
||||||
|
|
||||||
public verifyJWTToken(token: string, ignoreExpiration: boolean = false): AuthTokenData {
|
public verifyJWTToken(token: string, ignoreExpiration: boolean = false): AuthTokenData {
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
import { Issuer, Client, IntrospectionResponse } from "openid-client";
|
import { Issuer, Client, IntrospectionResponse } from "openid-client";
|
||||||
import { OPID_CLIENT_ID, OPID_CLIENT_SECRET, OPID_CLIENT_ISSUER, FRONT_URL } from "../Enum/EnvironmentVariable";
|
import {
|
||||||
|
OPID_CLIENT_ID,
|
||||||
const opidRedirectUri = FRONT_URL + "/jwt";
|
OPID_CLIENT_SECRET,
|
||||||
|
OPID_CLIENT_ISSUER,
|
||||||
|
OPID_CLIENT_REDIRECT_URL,
|
||||||
|
} from "../Enum/EnvironmentVariable";
|
||||||
|
|
||||||
class OpenIDClient {
|
class OpenIDClient {
|
||||||
private issuerPromise: Promise<Client> | null = null;
|
private issuerPromise: Promise<Client> | null = null;
|
||||||
@ -12,7 +15,7 @@ class OpenIDClient {
|
|||||||
return new issuer.Client({
|
return new issuer.Client({
|
||||||
client_id: OPID_CLIENT_ID,
|
client_id: OPID_CLIENT_ID,
|
||||||
client_secret: OPID_CLIENT_SECRET,
|
client_secret: OPID_CLIENT_SECRET,
|
||||||
redirect_uris: [opidRedirectUri],
|
redirect_uris: [OPID_CLIENT_REDIRECT_URL],
|
||||||
response_types: ["code"],
|
response_types: ["code"],
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -35,7 +38,7 @@ class OpenIDClient {
|
|||||||
|
|
||||||
public getUserInfo(code: string, nonce: string): Promise<{ email: string; sub: string; access_token: string }> {
|
public getUserInfo(code: string, nonce: string): Promise<{ email: string; sub: string; access_token: string }> {
|
||||||
return this.initClient().then((client) => {
|
return this.initClient().then((client) => {
|
||||||
return client.callback(opidRedirectUri, { code }, { nonce }).then((tokenSet) => {
|
return client.callback(OPID_CLIENT_REDIRECT_URL, { code }, { nonce }).then((tokenSet) => {
|
||||||
return client.userinfo(tokenSet).then((res) => {
|
return client.userinfo(tokenSet).then((res) => {
|
||||||
return {
|
return {
|
||||||
...res,
|
...res,
|
||||||
|
1
tests/.gitignore
vendored
Normal file
1
tests/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
/node_modules
|
13
tests/.testcaferc.js
Normal file
13
tests/.testcaferc.js
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
const BROWSER = process.env.BROWSER || "chrome --use-fake-ui-for-media-stream --use-fake-device-for-media-stream";
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
"browsers": BROWSER,
|
||||||
|
"hostname": "localhost",
|
||||||
|
//"skipJsErrors": true,
|
||||||
|
"src": "tests/",
|
||||||
|
"screenshots": {
|
||||||
|
"path": "screenshots/",
|
||||||
|
"takeOnFails": true,
|
||||||
|
"thumbnails": false,
|
||||||
|
}
|
||||||
|
}
|
18
tests/README.md
Normal file
18
tests/README.md
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
End-to-end tests
|
||||||
|
|
||||||
|
This directory contains automated end to end tests.
|
||||||
|
|
||||||
|
To run them locally:
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ npm install
|
||||||
|
$ npm test
|
||||||
|
```
|
||||||
|
|
||||||
|
Alternatively, you can use docker-compose to run the tests:
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker-compose -f docker-compose.testcafe.yml up --exit-code-from testcafe
|
||||||
|
```
|
||||||
|
|
||||||
|
Note: by default, tests are running in Chrome locally and in Chromium in the Docker image.
|
8915
tests/package-lock.json
generated
Normal file
8915
tests/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
8
tests/package.json
Normal file
8
tests/package.json
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"devDependencies": {
|
||||||
|
"testcafe": "^1.17.1"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"test": "testcafe"
|
||||||
|
}
|
||||||
|
}
|
1
tests/screenshots/.gitignore
vendored
Normal file
1
tests/screenshots/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
*
|
67
tests/tests/test.ts
Normal file
67
tests/tests/test.ts
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
const fs = require('fs')
|
||||||
|
|
||||||
|
fixture `Variables`
|
||||||
|
.page `http://play.workadventure.localhost/_/global/maps.workadventure.localhost/tests/Variables/Cache/variables_tmp.json`;
|
||||||
|
|
||||||
|
test("Test that variables cache in the back don't prevent setting a variable in case the map changes", async t => {
|
||||||
|
// Let's start by visiting a map that DOES not have the variable.
|
||||||
|
fs.copyFileSync('../maps/tests/Variables/Cache/variables_cache_1.json', '../maps/tests/Variables/Cache/variables_tmp.json');
|
||||||
|
|
||||||
|
await t
|
||||||
|
.typeText('input[name="loginSceneName"]', 'foo')
|
||||||
|
.click('button.loginSceneFormSubmit')
|
||||||
|
.click('button.selectCharacterButtonRight')
|
||||||
|
.click('button.selectCharacterButtonRight')
|
||||||
|
.click('button.selectCharacterSceneFormSubmit')
|
||||||
|
.click('button.letsgo');
|
||||||
|
//.takeScreenshot('before_switch.png');
|
||||||
|
|
||||||
|
// Let's REPLACE the map by a map that has a new variable
|
||||||
|
// At this point, the back server contains a cache of the old map (with no variables)
|
||||||
|
fs.copyFileSync('../maps/tests/Variables/Cache/variables_cache_2.json', '../maps/tests/Variables/Cache/variables_tmp.json');
|
||||||
|
await t.openWindow('http://play.workadventure.localhost/_/global/maps.workadventure.localhost/tests/Variables/Cache/variables_tmp.json');
|
||||||
|
|
||||||
|
await t.resizeWindow(960, 800);
|
||||||
|
|
||||||
|
await t
|
||||||
|
.typeText('input[name="loginSceneName"]', 'foo')
|
||||||
|
.click('button.loginSceneFormSubmit')
|
||||||
|
.click('button.selectCharacterButtonRight')
|
||||||
|
.click('button.selectCharacterButtonRight')
|
||||||
|
.click('button.selectCharacterSceneFormSubmit')
|
||||||
|
.click('button.letsgo');
|
||||||
|
//.takeScreenshot('after_switch.png');
|
||||||
|
|
||||||
|
const messages = await t.getBrowserConsoleMessages();
|
||||||
|
|
||||||
|
const logs = messages['log'];
|
||||||
|
|
||||||
|
// Let's check we successfully manage to save the variable value.
|
||||||
|
await assertLogMessage(t, 'SUCCESS!');
|
||||||
|
|
||||||
|
t.ctx.passed = true;
|
||||||
|
}).after(async t => {
|
||||||
|
if (!t.ctx.passed) {
|
||||||
|
console.log("Test failed. Browser logs:")
|
||||||
|
console.log(await t.getBrowserConsoleMessages());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tries to find a given log message in the logs (for 10 seconds)
|
||||||
|
*/
|
||||||
|
async function assertLogMessage(t, message: string): Promise<void> {
|
||||||
|
let i = 0;
|
||||||
|
let logs: string[]|undefined;
|
||||||
|
do {
|
||||||
|
const messages = await t.getBrowserConsoleMessages();
|
||||||
|
logs = messages['log'];
|
||||||
|
if (logs.find((str) => str === message)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
await t.wait(1000);
|
||||||
|
i++;
|
||||||
|
} while (i < 10);
|
||||||
|
|
||||||
|
await t.expect(logs).contains(message);
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user