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_SECRET=
|
||||
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
|
||||
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_PORT = parseInt(process.env.REDIS_PORT || "6379") || 6379;
|
||||
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 {
|
||||
MINIMUM_DISTANCE,
|
||||
|
@ -26,6 +26,7 @@ import { VariablesManager } from "../Services/VariablesManager";
|
||||
import { ADMIN_API_URL } from "../Enum/EnvironmentVariable";
|
||||
import { LocalUrlError } from "../Services/LocalUrlError";
|
||||
import { emitErrorOnRoomSocket } from "../Services/MessageHelpers";
|
||||
import { VariableError } from "../Services/VariableError";
|
||||
import log from "../Services/Logger";
|
||||
|
||||
export type ConnectCallback = (user: User, group: Group) => void;
|
||||
@ -337,30 +338,62 @@ export class GameRoom {
|
||||
// First, let's check if "user" is allowed to modify the variable.
|
||||
const variableManager = await this.getVariableManager();
|
||||
|
||||
const readableBy = variableManager.setVariable(name, value, user);
|
||||
try {
|
||||
const readableBy = variableManager.setVariable(name, value, user);
|
||||
|
||||
// If the variable was not changed, let's not dispatch anything.
|
||||
if (readableBy === false) {
|
||||
return;
|
||||
}
|
||||
// If the variable was not changed, let's not dispatch anything.
|
||||
if (readableBy === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: should we batch those every 100ms?
|
||||
const variableMessage = new VariableWithTagMessage();
|
||||
variableMessage.setName(name);
|
||||
variableMessage.setValue(value);
|
||||
if (readableBy) {
|
||||
variableMessage.setReadableby(readableBy);
|
||||
}
|
||||
// TODO: should we batch those every 100ms?
|
||||
const variableMessage = new VariableWithTagMessage();
|
||||
variableMessage.setName(name);
|
||||
variableMessage.setValue(value);
|
||||
if (readableBy) {
|
||||
variableMessage.setReadableby(readableBy);
|
||||
}
|
||||
|
||||
const subMessage = new SubToPusherRoomMessage();
|
||||
subMessage.setVariablemessage(variableMessage);
|
||||
const subMessage = new SubToPusherRoomMessage();
|
||||
subMessage.setVariablemessage(variableMessage);
|
||||
|
||||
const batchMessage = new BatchToPusherRoomMessage();
|
||||
batchMessage.addPayload(subMessage);
|
||||
const batchMessage = new BatchToPusherRoomMessage();
|
||||
batchMessage.addPayload(subMessage);
|
||||
|
||||
// Dispatch the message on the room listeners
|
||||
for (const socket of this.roomListeners) {
|
||||
socket.write(batchMessage);
|
||||
// Dispatch the message on the room listeners
|
||||
for (const socket of this.roomListeners) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -450,9 +483,11 @@ export class GameRoom {
|
||||
}
|
||||
|
||||
private variableManagerPromise: Promise<VariablesManager> | undefined;
|
||||
private variableManagerLastLoad: Date | undefined;
|
||||
|
||||
private getVariableManager(): Promise<VariablesManager> {
|
||||
if (!this.variableManagerPromise) {
|
||||
this.variableManagerLastLoad = new Date();
|
||||
this.variableManagerPromise = this.getMap()
|
||||
.then((map) => {
|
||||
const variablesManager = new VariablesManager(this.roomUrl, map);
|
||||
|
@ -5,13 +5,14 @@ import { promisify } from "util";
|
||||
import { LocalUrlError } from "./LocalUrlError";
|
||||
import { ITiledMap } from "@workadventure/tiled-map-type-guard";
|
||||
import { isTiledMap } from "@workadventure/tiled-map-type-guard/dist";
|
||||
import { STORE_VARIABLES_FOR_LOCAL_MAPS } from "../Enum/EnvironmentVariable";
|
||||
import log from "./Logger";
|
||||
|
||||
class MapFetcher {
|
||||
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)
|
||||
|
||||
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');
|
||||
}
|
||||
|
||||
|
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 { variablesRepository } from "./Repository/VariablesRepository";
|
||||
import { redisClient } from "./RedisClient";
|
||||
import { VariableError } from "./VariableError";
|
||||
import log from "./Logger";
|
||||
|
||||
interface Variable {
|
||||
@ -175,11 +176,13 @@ export class VariablesManager {
|
||||
if (this.variableObjects) {
|
||||
variableObject = this.variableObjects.get(name);
|
||||
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)) {
|
||||
throw new Error(
|
||||
throw new VariableError(
|
||||
'Trying to set a variable "' +
|
||||
name +
|
||||
'". User "' +
|
||||
|
@ -38,6 +38,7 @@ services:
|
||||
JITSI_URL: $JITSI_URL
|
||||
JITSI_PRIVATE_MODE: "$JITSI_PRIVATE_MODE"
|
||||
PUSHER_URL: //pusher.${DOMAIN}
|
||||
ICON_URL: //icon.${DOMAIN}
|
||||
TURN_SERVER: "${TURN_SERVER}"
|
||||
TURN_USER: "${TURN_USER}"
|
||||
TURN_PASSWORD: "${TURN_PASSWORD}"
|
||||
@ -98,3 +99,15 @@ services:
|
||||
- "traefik.http.routers.back-ssl.service=back"
|
||||
- "traefik.http.routers.back-ssl.tls.certresolver=myresolver"
|
||||
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 {
|
||||
"ADMIN_API_URL": adminUrl,
|
||||
"ADMIN_API_TOKEN": env.ADMIN_API_TOKEN,
|
||||
"ADMIN_SOCKETS_TOKEN": env.ADMIN_SOCKETS_TOKEN,
|
||||
} else {})
|
||||
},
|
||||
"front": {
|
||||
|
@ -30,6 +30,7 @@ services:
|
||||
UPLOADER_URL: /uploader
|
||||
ADMIN_URL: /admin
|
||||
MAPS_URL: /maps
|
||||
ICON_URL: /icon
|
||||
STARTUP_COMMAND_1: ./templater.sh
|
||||
STARTUP_COMMAND_2: yarn install
|
||||
TURN_SERVER: "turn:localhost:3478,turns:localhost:5349"
|
||||
@ -40,6 +41,7 @@ services:
|
||||
TURN_USER: ""
|
||||
TURN_PASSWORD: ""
|
||||
START_ROOM_URL: "$START_ROOM_URL"
|
||||
DISABLE_ANONYMOUS: "$DISABLE_ANONYMOUS"
|
||||
command: yarn run start
|
||||
volumes:
|
||||
- ./front:/usr/src/app
|
||||
@ -60,6 +62,8 @@ services:
|
||||
environment:
|
||||
DEBUG: "*"
|
||||
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_KEY: yourSecretKey
|
||||
ADMIN_API_TOKEN: "$ADMIN_API_TOKEN"
|
||||
@ -70,6 +74,9 @@ services:
|
||||
OPID_CLIENT_ID: $OPID_CLIENT_ID
|
||||
OPID_CLIENT_SECRET: $OPID_CLIENT_SECRET
|
||||
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:
|
||||
- ./pusher:/usr/src/app
|
||||
labels:
|
||||
@ -117,6 +124,8 @@ services:
|
||||
environment:
|
||||
DEBUG: "*"
|
||||
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_JITSI_KEY: "$SECRET_JITSI_KEY"
|
||||
ALLOW_ARTILLERY: "true"
|
||||
@ -177,6 +186,20 @@ services:
|
||||
redis:
|
||||
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:
|
||||
# image: coturn/coturn:4.5.2
|
||||
# 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
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
networks:
|
||||
default:
|
||||
aliases:
|
||||
- 'play.workadventure.localhost'
|
||||
- 'pusher.workadventure.localhost'
|
||||
- 'maps.workadventure.localhost'
|
||||
|
||||
front:
|
||||
image: thecodingmachine/nodejs:14
|
||||
@ -29,6 +35,7 @@ services:
|
||||
PUSHER_URL: //pusher.workadventure.localhost
|
||||
UPLOADER_URL: //uploader.workadventure.localhost
|
||||
ADMIN_URL: //workadventure.localhost
|
||||
ICON_URL: //icon.workadventure.localhost
|
||||
STARTUP_COMMAND_1: ./templater.sh
|
||||
STARTUP_COMMAND_2: yarn install
|
||||
STUN_SERVER: "stun:stun.l.google.com:19302"
|
||||
@ -42,6 +49,8 @@ services:
|
||||
START_ROOM_URL: "$START_ROOM_URL"
|
||||
MAX_PER_GROUP: "$MAX_PER_GROUP"
|
||||
MAX_USERNAME_LENGTH: "$MAX_USERNAME_LENGTH"
|
||||
DISABLE_ANONYMOUS: "$DISABLE_ANONYMOUS"
|
||||
OPID_LOGIN_SCREEN_PROVIDER: "$OPID_LOGIN_SCREEN_PROVIDER"
|
||||
command: yarn run start
|
||||
volumes:
|
||||
- ./front:/usr/src/app
|
||||
@ -60,6 +69,8 @@ services:
|
||||
environment:
|
||||
DEBUG: "socket:*"
|
||||
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_KEY: yourSecretKey
|
||||
ADMIN_API_TOKEN: "$ADMIN_API_TOKEN"
|
||||
@ -70,6 +81,9 @@ services:
|
||||
OPID_CLIENT_ID: $OPID_CLIENT_ID
|
||||
OPID_CLIENT_SECRET: $OPID_CLIENT_SECRET
|
||||
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:
|
||||
- ./pusher:/usr/src/app
|
||||
labels:
|
||||
@ -111,6 +125,8 @@ services:
|
||||
environment:
|
||||
DEBUG: "*"
|
||||
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_JITSI_KEY: "$SECRET_JITSI_KEY"
|
||||
ALLOW_ARTILLERY: "true"
|
||||
@ -121,6 +137,7 @@ services:
|
||||
MAX_PER_GROUP: "MAX_PER_GROUP"
|
||||
REDIS_HOST: redis
|
||||
NODE_ENV: development
|
||||
STORE_VARIABLES_FOR_LOCAL_MAPS: "true"
|
||||
volumes:
|
||||
- ./back:/usr/src/app
|
||||
labels:
|
||||
@ -177,6 +194,17 @@ services:
|
||||
- "traefik.http.routers.redisinsight-ssl.tls=true"
|
||||
- "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:
|
||||
# image: coturn/coturn:4.5.2
|
||||
# command:
|
||||
|
@ -15,7 +15,7 @@ When controls are disabled, the user cannot move anymore using keyboard input. T
|
||||
Example:
|
||||
|
||||
```javascript
|
||||
WA.room.onEnterZone('myZone', () => {
|
||||
WA.room.onEnterLayer('myZone').subscribe(() => {
|
||||
WA.controls.disablePlayerControls();
|
||||
WA.ui.openPopup("popupRectangle", 'This is an imporant message!', [{
|
||||
label: "Got it!",
|
||||
@ -25,5 +25,5 @@ WA.room.onEnterZone('myZone', () => {
|
||||
popup.close();
|
||||
}
|
||||
}]);
|
||||
});
|
||||
})
|
||||
```
|
||||
|
@ -49,7 +49,7 @@ Example:
|
||||
let helloWorldPopup;
|
||||
|
||||
// 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!', [{
|
||||
label: "Close",
|
||||
className: "primary",
|
||||
@ -57,13 +57,13 @@ helloWorldPopup = WA.room.onEnterZone('myZone', () => {
|
||||
// Close the popup when the "Close" button is pressed.
|
||||
popup.close();
|
||||
}
|
||||
});
|
||||
}]);
|
||||
}]);
|
||||
});
|
||||
|
||||
// Close the popup when we leave the zone.
|
||||
WA.room.onLeaveZone('myZone', () => {
|
||||
WA.room.onLeaveLayer("myZone").subscribe(() => {
|
||||
helloWorldPopup.close();
|
||||
});
|
||||
})
|
||||
```
|
||||
|
||||
### Add custom menu
|
||||
|
@ -4,7 +4,7 @@ declare let window: any;
|
||||
|
||||
class AnalyticsClient {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
private posthogPromise: Promise<any>;
|
||||
private posthogPromise: Promise<any> | undefined;
|
||||
|
||||
constructor() {
|
||||
if (POSTHOG_API_KEY && POSTHOG_URL) {
|
||||
@ -14,65 +14,68 @@ class AnalyticsClient {
|
||||
window.posthog = posthog;
|
||||
return posthog;
|
||||
});
|
||||
} else {
|
||||
this.posthogPromise = Promise.reject();
|
||||
}
|
||||
}
|
||||
|
||||
identifyUser(uuid: string, email: string | null) {
|
||||
this.posthogPromise
|
||||
.then((posthog) => {
|
||||
posthog.identify(uuid, { uuid, email, wa: true });
|
||||
})
|
||||
.catch();
|
||||
this.posthogPromise?.then((posthog) => {
|
||||
posthog.identify(uuid, { uuid, email, wa: true });
|
||||
});
|
||||
}
|
||||
|
||||
loggedWithSso() {
|
||||
this.posthogPromise
|
||||
.then((posthog) => {
|
||||
posthog.capture("wa-logged-sso");
|
||||
})
|
||||
.catch();
|
||||
this.posthogPromise?.then((posthog) => {
|
||||
posthog.capture("wa-logged-sso");
|
||||
});
|
||||
}
|
||||
|
||||
loggedWithToken() {
|
||||
this.posthogPromise
|
||||
.then((posthog) => {
|
||||
posthog.capture("wa-logged-token");
|
||||
})
|
||||
.catch();
|
||||
this.posthogPromise?.then((posthog) => {
|
||||
posthog.capture("wa-logged-token");
|
||||
});
|
||||
}
|
||||
|
||||
enteredRoom(roomId: string, roomGroup: string | null) {
|
||||
this.posthogPromise
|
||||
.then((posthog) => {
|
||||
posthog.capture("$pageView", { roomId, roomGroup });
|
||||
})
|
||||
.catch();
|
||||
this.posthogPromise?.then((posthog) => {
|
||||
posthog.capture("$pageView", { roomId, roomGroup });
|
||||
posthog.capture("enteredRoom");
|
||||
});
|
||||
}
|
||||
|
||||
openedMenu() {
|
||||
this.posthogPromise
|
||||
.then((posthog) => {
|
||||
posthog.capture("wa-opened-menu");
|
||||
})
|
||||
.catch();
|
||||
this.posthogPromise?.then((posthog) => {
|
||||
posthog.capture("wa-opened-menu");
|
||||
});
|
||||
}
|
||||
|
||||
launchEmote(emote: string) {
|
||||
this.posthogPromise
|
||||
.then((posthog) => {
|
||||
posthog.capture("wa-emote-launch", { emote });
|
||||
})
|
||||
.catch();
|
||||
this.posthogPromise?.then((posthog) => {
|
||||
posthog.capture("wa-emote-launch", { emote });
|
||||
});
|
||||
}
|
||||
|
||||
enteredJitsi(roomName: string, roomId: string) {
|
||||
this.posthogPromise
|
||||
.then((posthog) => {
|
||||
posthog.capture("wa-entered-jitsi", { roomName, roomId });
|
||||
})
|
||||
.catch();
|
||||
this.posthogPromise?.then((posthog) => {
|
||||
posthog.capture("wa-entered-jitsi", { roomName, roomId });
|
||||
});
|
||||
}
|
||||
|
||||
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();
|
||||
|
@ -49,7 +49,7 @@ class IframeListener {
|
||||
public readonly openTabStream = this._openTabStream.asObservable();
|
||||
|
||||
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();
|
||||
public readonly disablePlayerControlStream = this._disablePlayerControlStream.asObservable();
|
||||
@ -269,7 +269,7 @@ class IframeListener {
|
||||
|
||||
registerScript(scriptUrl: string): Promise<void> {
|
||||
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") {
|
||||
// Using external iframe mode (
|
||||
|
@ -18,13 +18,18 @@
|
||||
'--font': 'Press Start 2P'
|
||||
},
|
||||
emojisPerRow: isMobile() ? 6 : 8,
|
||||
autoFocusSearch: false
|
||||
autoFocusSearch: false,
|
||||
style: 'twemoji',
|
||||
});
|
||||
//the timeout is here to prevent the menu from flashing
|
||||
setTimeout(() => picker.showPicker(emojiContainer), 100);
|
||||
|
||||
picker.on("emoji", (selection) => {
|
||||
emoteStore.set(selection.emoji);
|
||||
emoteStore.set({
|
||||
unicode: selection.emoji,
|
||||
url: selection.url,
|
||||
name: selection.name
|
||||
});
|
||||
});
|
||||
|
||||
picker.on("hidden", () => {
|
||||
|
@ -4,7 +4,6 @@
|
||||
|
||||
let gameScene = gameManager.getCurrentGameScene();
|
||||
|
||||
let HTMLShareLink: HTMLInputElement;
|
||||
let expandedMapCopyright = 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>
|
||||
|
||||
<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>
|
||||
<section class="container-overflow">
|
||||
<h3>{mapName}</h3>
|
||||
@ -93,24 +66,6 @@
|
||||
div.about-room-main {
|
||||
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 {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
@ -126,21 +81,11 @@
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
overflow-y: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 800px), only screen and (max-height: 800px) {
|
||||
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 {
|
||||
height: calc(100% - 120px);
|
||||
}
|
||||
|
@ -1,10 +1,60 @@
|
||||
<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";
|
||||
</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">
|
||||
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 {
|
||||
border: none;
|
||||
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 SettingsSubMenu from "./SettingsSubMenu.svelte";
|
||||
import ProfileSubMenu from "./ProfileSubMenu.svelte";
|
||||
import CreateMapSubMenu from "./CreateMapSubMenu.svelte";
|
||||
import AboutRoomSubMenu from "./AboutRoomSubMenu.svelte";
|
||||
import GlobalMessageSubMenu from "./GlobalMessagesSubMenu.svelte";
|
||||
import ContactSubMenu from "./ContactSubMenu.svelte";
|
||||
import CustomSubMenu from "./CustomSubMenu.svelte"
|
||||
import GuestSubMenu from "./GuestSubMenu.svelte";
|
||||
import {
|
||||
checkSubMenuToShow,
|
||||
customMenuIframe,
|
||||
@ -19,21 +19,21 @@
|
||||
import type {Unsubscriber} from "svelte/store";
|
||||
import {sendMenuClickedEvent} from "../../Api/iframe/Ui/MenuItem";
|
||||
|
||||
let activeSubMenu: string = SubMenusInterface.settings;
|
||||
let activeComponent: typeof SettingsSubMenu | typeof CustomSubMenu = SettingsSubMenu;
|
||||
let activeSubMenu: string = SubMenusInterface.profile;
|
||||
let activeComponent: typeof ProfileSubMenu | typeof CustomSubMenu = ProfileSubMenu;
|
||||
let props: { url: string, allowApi: boolean };
|
||||
let unsubscriberSubMenuStore: Unsubscriber;
|
||||
|
||||
onMount(() => {
|
||||
unsubscriberSubMenuStore = subMenusStore.subscribe(() => {
|
||||
if(!get(subMenusStore).includes(activeSubMenu)) {
|
||||
switchMenu(SubMenusInterface.settings);
|
||||
switchMenu(SubMenusInterface.profile);
|
||||
}
|
||||
})
|
||||
|
||||
checkSubMenuToShow();
|
||||
|
||||
switchMenu(SubMenusInterface.settings);
|
||||
switchMenu(SubMenusInterface.profile);
|
||||
})
|
||||
|
||||
onDestroy(() => {
|
||||
@ -52,8 +52,8 @@
|
||||
case SubMenusInterface.profile:
|
||||
activeComponent = ProfileSubMenu;
|
||||
break;
|
||||
case SubMenusInterface.createMap:
|
||||
activeComponent = CreateMapSubMenu;
|
||||
case SubMenusInterface.invite:
|
||||
activeComponent = GuestSubMenu;
|
||||
break;
|
||||
case SubMenusInterface.aboutRoom:
|
||||
activeComponent = AboutRoomSubMenu;
|
||||
|
@ -11,15 +11,9 @@
|
||||
function showChat(){
|
||||
chatVisibilityStore.set(true);
|
||||
}
|
||||
|
||||
function onKeyDown(e: KeyboardEvent) {
|
||||
if (e.key === "Tab") {
|
||||
showMenu();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:window on:keydown={onKeyDown}/>
|
||||
<svelte:window/>
|
||||
|
||||
<main class="menuIcon">
|
||||
<img src={logoWA} alt="open menu" class="nes-pointer" on:click|preventDefault={showMenu}>
|
||||
|
@ -12,6 +12,10 @@
|
||||
import {localUserStore} from "../../Connexion/LocalUserStore";
|
||||
import {EnableCameraScene, EnableCameraSceneName} from "../../Phaser/Login/EnableCameraScene";
|
||||
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(){
|
||||
@ -55,54 +59,107 @@
|
||||
</script>
|
||||
|
||||
<div class="customize-main">
|
||||
{#if $userIsConnected}
|
||||
<div class="submenu">
|
||||
<section>
|
||||
{#if PROFILE_URL != undefined}
|
||||
<iframe title="profile" src="{getProfileUrl()}"></iframe>
|
||||
{/if}
|
||||
<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>
|
||||
<section>
|
||||
<button type="button" class="nes-btn" on:click|preventDefault={logOut}>Log out</button>
|
||||
</section>
|
||||
{:else}
|
||||
<section>
|
||||
<a type="button" class="nes-btn" href="/login">Sign in</a>
|
||||
</section>
|
||||
{/if}
|
||||
<section>
|
||||
<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 class="content">
|
||||
{#if $userIsConnected}
|
||||
<section>
|
||||
{#if PROFILE_URL != undefined}
|
||||
<iframe title="profile" src="{getProfileUrl()}"></iframe>
|
||||
{/if}
|
||||
</section>
|
||||
<section>
|
||||
<button type="button" class="nes-btn" on:click|preventDefault={logOut}>Log out</button>
|
||||
</section>
|
||||
{:else}
|
||||
<section>
|
||||
<a type="button" class="nes-btn" href="/login">Sign in</a>
|
||||
</section>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
div.customize-main{
|
||||
section {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
margin-bottom: 20px;
|
||||
width: 100%;
|
||||
display: inline-flex;
|
||||
|
||||
iframe{
|
||||
width: 100%;
|
||||
height: 50vh;
|
||||
border: none;
|
||||
}
|
||||
div.submenu{
|
||||
height: 100%;
|
||||
width: 50px;
|
||||
|
||||
button {
|
||||
height: 50px;
|
||||
width: 250px;
|
||||
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 {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
margin-bottom: 20px;
|
||||
|
||||
iframe {
|
||||
width: 100%;
|
||||
height: 50vh;
|
||||
border: none;
|
||||
}
|
||||
|
||||
button {
|
||||
height: 50px;
|
||||
width: 250px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 800px) {
|
||||
div.customize-main section button {
|
||||
div.customize-main.content section button {
|
||||
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();
|
||||
localUserStore.setAuthToken(null);
|
||||
|
||||
//TODO fix me to redirect this URL by pusher
|
||||
if (!this._currentRoom || !this._currentRoom.iframeAuthentication) {
|
||||
loginSceneVisibleIframeStore.set(false);
|
||||
return null;
|
||||
@ -79,6 +78,16 @@ class ConnectionManager {
|
||||
const connexionType = urlManager.getGameConnexionType();
|
||||
this.connexionType = connexionType;
|
||||
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) {
|
||||
this._currentRoom = await Room.createRoom(new URL(localUserStore.getLastRoomUrl()));
|
||||
if (this.loadOpenIDScreen() !== null) {
|
||||
@ -87,15 +96,19 @@ class ConnectionManager {
|
||||
urlManager.pushRoomIdToUrl(this._currentRoom);
|
||||
} else if (connexionType === GameConnexionTypes.jwt) {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const code = urlParams.get("code");
|
||||
const state = urlParams.get("state");
|
||||
if (!state || !localUserStore.verifyState(state)) {
|
||||
throw "Could not validate state!";
|
||||
|
||||
if (!token) {
|
||||
const code = urlParams.get("code");
|
||||
const state = urlParams.get("state");
|
||||
if (!state || !localUserStore.verifyState(state)) {
|
||||
throw "Could not validate state!";
|
||||
}
|
||||
if (!code) {
|
||||
throw "No Auth code provided";
|
||||
}
|
||||
localUserStore.setCode(code);
|
||||
}
|
||||
if (!code) {
|
||||
throw "No Auth code provided";
|
||||
}
|
||||
localUserStore.setCode(code);
|
||||
|
||||
this._currentRoom = await Room.createRoom(new URL(localUserStore.getLastRoomUrl()));
|
||||
try {
|
||||
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
|
||||
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.
|
||||
if (!this.authToken && !this._currentRoom.authenticationMandatory) {
|
||||
await this.anonymousLogin();
|
||||
} else {
|
||||
try {
|
||||
await this.checkAuthUserConnexion();
|
||||
analyticsClient.loggedWithSso();
|
||||
} catch (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
|
||||
@ -199,13 +218,15 @@ class ConnectionManager {
|
||||
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();
|
||||
return Promise.resolve(this._currentRoom);
|
||||
}
|
||||
|
||||
public async anonymousLogin(isBenchmark: boolean = false): Promise<void> {
|
||||
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;
|
||||
if (!isBenchmark) {
|
||||
// In benchmark, we don't have a local storage.
|
||||
@ -279,20 +300,25 @@ class ConnectionManager {
|
||||
//set connected store for menu at false
|
||||
userIsConnected.set(false);
|
||||
|
||||
const token = localUserStore.getAuthToken();
|
||||
const state = localUserStore.getState();
|
||||
const code = localUserStore.getCode();
|
||||
if (!state || !localUserStore.verifyState(state)) {
|
||||
throw "Could not validate state!";
|
||||
}
|
||||
if (!code) {
|
||||
throw "No Auth code provided";
|
||||
}
|
||||
const nonce = localUserStore.getNonce();
|
||||
const token = localUserStore.getAuthToken();
|
||||
const { authToken } = await Axios.get(`${PUSHER_URL}/login-callback`, { params: { code, nonce, token } }).then(
|
||||
(res) => res.data
|
||||
);
|
||||
|
||||
if (!token) {
|
||||
if (!state || !localUserStore.verifyState(state)) {
|
||||
throw "Could not validate state!";
|
||||
}
|
||||
if (!code) {
|
||||
throw "No Auth code provided";
|
||||
}
|
||||
}
|
||||
const { authToken, userUuid, textures, email } = await Axios.get(`${PUSHER_URL}/login-callback`, {
|
||||
params: { code, nonce, token, playUri: this.currentRoom?.key },
|
||||
}).then((res) => res.data);
|
||||
localUserStore.setAuthToken(authToken);
|
||||
this.localUser = new LocalUser(userUuid, textures, email);
|
||||
localUserStore.saveUser(this.localUser);
|
||||
this.authToken = authToken;
|
||||
|
||||
//user connected, set connected store for menu at true
|
||||
|
@ -122,10 +122,12 @@ class LocalUserStore {
|
||||
|
||||
setLastRoomUrl(roomUrl: string): void {
|
||||
localStorage.setItem(lastRoomUrl, roomUrl.toString());
|
||||
caches.open(cacheAPIIndex).then((cache) => {
|
||||
const stringResponse = new Response(JSON.stringify({ roomUrl }));
|
||||
cache.put(`/${lastRoomUrl}`, stringResponse);
|
||||
});
|
||||
if ("caches" in window) {
|
||||
caches.open(cacheAPIIndex).then((cache) => {
|
||||
const stringResponse = new Response(JSON.stringify({ roomUrl }));
|
||||
cache.put(`/${lastRoomUrl}`, stringResponse);
|
||||
});
|
||||
}
|
||||
}
|
||||
getLastRoomUrl(): string {
|
||||
return (
|
||||
@ -133,6 +135,9 @@ class LocalUserStore {
|
||||
);
|
||||
}
|
||||
getLastRoomUrlCacheApi(): Promise<string | undefined> {
|
||||
if (!("caches" in window)) {
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
return caches.open(cacheAPIIndex).then((cache) => {
|
||||
return cache.match(`/${lastRoomUrl}`).then((res) => {
|
||||
return res?.json().then((data) => {
|
||||
@ -165,8 +170,15 @@ class LocalUserStore {
|
||||
|
||||
verifyState(value: string): boolean {
|
||||
const oldValue = localStorage.getItem(state);
|
||||
if (!oldValue) {
|
||||
localStorage.setItem(state, value);
|
||||
return true;
|
||||
}
|
||||
return oldValue === value;
|
||||
}
|
||||
setState(value: string) {
|
||||
localStorage.setItem(state, value);
|
||||
}
|
||||
getState(): string | null {
|
||||
return localStorage.getItem(state);
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
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 { localUserStore } from "./LocalUserStore";
|
||||
|
||||
@ -14,8 +14,8 @@ export interface RoomRedirect {
|
||||
export class Room {
|
||||
public readonly id: string;
|
||||
public readonly isPublic: boolean;
|
||||
private _authenticationMandatory: boolean = false;
|
||||
private _iframeAuthentication?: string;
|
||||
private _authenticationMandatory: boolean = DISABLE_ANONYMOUS;
|
||||
private _iframeAuthentication?: string = OPID_LOGIN_SCREEN_PROVIDER;
|
||||
private _mapUrl: string | undefined;
|
||||
private _textures: CharacterTexture[] | undefined;
|
||||
private instance: string | undefined;
|
||||
@ -89,27 +89,37 @@ export class Room {
|
||||
}
|
||||
|
||||
private async getMapDetail(): Promise<MapDetail | RoomRedirect> {
|
||||
const result = await Axios.get(`${PUSHER_URL}/map`, {
|
||||
params: {
|
||||
playUri: this.roomUrl.toString(),
|
||||
authToken: localUserStore.getAuthToken(),
|
||||
},
|
||||
});
|
||||
try {
|
||||
const result = await Axios.get(`${PUSHER_URL}/map`, {
|
||||
params: {
|
||||
playUri: this.roomUrl.toString(),
|
||||
authToken: localUserStore.getAuthToken(),
|
||||
},
|
||||
});
|
||||
|
||||
const data = result.data;
|
||||
if (data.redirectUrl) {
|
||||
return {
|
||||
redirectUrl: data.redirectUrl as string,
|
||||
};
|
||||
const data = result.data;
|
||||
if (data.redirectUrl) {
|
||||
return {
|
||||
redirectUrl: data.redirectUrl as string,
|
||||
};
|
||||
}
|
||||
console.log("Map ", this.id, " resolves to URL ", data.mapUrl);
|
||||
this._mapUrl = data.mapUrl;
|
||||
this._textures = data.textures;
|
||||
this._group = data.group;
|
||||
this._authenticationMandatory = data.authenticationMandatory || DISABLE_ANONYMOUS;
|
||||
this._iframeAuthentication = data.iframeAuthentication || OPID_LOGIN_SCREEN_PROVIDER;
|
||||
this._contactPage = data.contactPage || CONTACT_URL;
|
||||
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;
|
||||
}
|
||||
console.log("Map ", this.id, " resolves to URL ", data.mapUrl);
|
||||
this._mapUrl = data.mapUrl;
|
||||
this._textures = data.textures;
|
||||
this._group = data.group;
|
||||
this._authenticationMandatory = data.authenticationMandatory || false;
|
||||
this._iframeAuthentication = data.iframeAuthentication;
|
||||
this._contactPage = data.contactPage || CONTACT_URL;
|
||||
return new MapDetail(data.mapUrl, data.textures);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -4,6 +4,7 @@ const START_ROOM_URL: string =
|
||||
const PUSHER_URL = process.env.PUSHER_URL || "//pusher.workadventure.localhost";
|
||||
export const ADMIN_URL = process.env.ADMIN_URL || "//workadventu.re";
|
||||
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 TURN_SERVER: string = process.env.TURN_SERVER || "";
|
||||
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 POSTHOG_API_KEY: string = (process.env.POSTHOG_API_KEY as string) || "";
|
||||
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;
|
||||
|
||||
@ -32,6 +35,7 @@ export {
|
||||
DISABLE_NOTIFICATIONS,
|
||||
PUSHER_URL,
|
||||
UPLOADER_URL,
|
||||
ICON_URL,
|
||||
POSITION_DELAY,
|
||||
MAX_EXTRAPOLATION_TIME,
|
||||
STUN_SERVER,
|
||||
|
@ -6,73 +6,124 @@ const TextName: string = "Loading...";
|
||||
const LogoResource: string = "static/images/logo.png";
|
||||
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 padding: number = 5;
|
||||
const loadingBarHeight: number = 16;
|
||||
const padding: number = 5;
|
||||
|
||||
const promiseLoadLogoTexture = new Promise<Phaser.GameObjects.Image>((res) => {
|
||||
if (scene.load.textureManager.exists(LogoNameIndex)) {
|
||||
return res(
|
||||
scene.add.image(scene.game.renderer.width / 2, scene.game.renderer.height / 2 - 150, LogoNameIndex)
|
||||
);
|
||||
} else {
|
||||
//add loading if logo image is not ready
|
||||
loadingText = scene.add.text(scene.game.renderer.width / 2, scene.game.renderer.height / 2 - 50, TextName);
|
||||
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;
|
||||
}
|
||||
scene.load.spritesheet(LogoNameIndex, LogoResource, LogoFrame);
|
||||
scene.load.once(`filecomplete-spritesheet-${LogoNameIndex}`, () => {
|
||||
if (loadingText) {
|
||||
loadingText.destroy();
|
||||
|
||||
const loadingBarWidth: number = Math.floor(this.scene.game.renderer.width / 3);
|
||||
|
||||
const promiseLoadLogoTexture = new Promise<Phaser.GameObjects.Image>((res) => {
|
||||
if (this.scene.load.textureManager.exists(LogoNameIndex)) {
|
||||
return res(
|
||||
(this.logo = this.scene.add.image(
|
||||
this.scene.game.renderer.width / 2,
|
||||
this.scene.game.renderer.height / 2 - 150,
|
||||
LogoNameIndex
|
||||
))
|
||||
);
|
||||
} else {
|
||||
//add loading if logo image is not ready
|
||||
this.loadingText = this.scene.add.text(
|
||||
this.scene.game.renderer.width / 2,
|
||||
this.scene.game.renderer.height / 2 - 50,
|
||||
TextName
|
||||
);
|
||||
}
|
||||
return res(
|
||||
scene.add.image(scene.game.renderer.width / 2, scene.game.renderer.height / 2 - 150, LogoNameIndex)
|
||||
);
|
||||
this.scene.load.spritesheet(LogoNameIndex, LogoResource, LogoFrame);
|
||||
this.scene.load.once(`filecomplete-spritesheet-${LogoNameIndex}`, () => {
|
||||
if (this.loadingText) {
|
||||
this.loadingText.destroy();
|
||||
}
|
||||
return res(
|
||||
(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();
|
||||
const progress = scene.add.graphics();
|
||||
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
|
||||
);
|
||||
this.progressContainer = this.scene.add.graphics();
|
||||
this.progress = this.scene.add.graphics();
|
||||
this.progressContainer.fillStyle(0x444444, 0.8);
|
||||
|
||||
scene.load.on("progress", (value: number) => {
|
||||
progress.clear();
|
||||
progress.fillStyle(0xbbbbbb, 1);
|
||||
progress.fillRect(
|
||||
(scene.game.renderer.width - loadingBarWidth) / 2,
|
||||
scene.game.renderer.height / 2 + 50,
|
||||
loadingBarWidth * value,
|
||||
this.resize();
|
||||
|
||||
this.scene.load.on("progress", (value: number) => {
|
||||
this.progressAmount = value;
|
||||
this.drawProgress();
|
||||
});
|
||||
this.scene.load.on("complete", () => {
|
||||
if (this.loadingText) {
|
||||
this.loadingText.destroy();
|
||||
}
|
||||
promiseLoadLogoTexture.then((resLoadingImage: Phaser.GameObjects.Image) => {
|
||||
resLoadingImage.destroy();
|
||||
});
|
||||
this.progress.destroy();
|
||||
this.progressContainer.destroy();
|
||||
if (this.scene instanceof DirtyScene) {
|
||||
this.scene.markDirty();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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
|
||||
);
|
||||
});
|
||||
scene.load.on("complete", () => {
|
||||
if (loadingText) {
|
||||
loadingText.destroy();
|
||||
}
|
||||
promiseLoadLogoTexture.then((resLoadingImage: Phaser.GameObjects.Image) => {
|
||||
resLoadingImage.destroy();
|
||||
});
|
||||
progress.destroy();
|
||||
progressContainer.destroy();
|
||||
if (scene instanceof DirtyScene) {
|
||||
scene.markDirty();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const removeLoader = (scene: Phaser.Scene): void => {
|
||||
if (scene.load.textureManager.exists(LogoNameIndex)) {
|
||||
scene.load.textureManager.remove(LogoNameIndex);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ import { SpeechBubble } from "./SpeechBubble";
|
||||
import Text = Phaser.GameObjects.Text;
|
||||
import Container = Phaser.GameObjects.Container;
|
||||
import Sprite = Phaser.GameObjects.Sprite;
|
||||
import DOMElement = Phaser.GameObjects.DOMElement;
|
||||
import { TextureError } from "../../Exception/TextureError";
|
||||
import { Companion } from "../Companion/Companion";
|
||||
import type { GameScene } from "../Game/GameScene";
|
||||
@ -33,7 +34,7 @@ export abstract class Character extends Container {
|
||||
//private teleportation: Sprite;
|
||||
private invisible: boolean;
|
||||
public companion?: Companion;
|
||||
private emote: Phaser.GameObjects.Text | null = null;
|
||||
private emote: Phaser.GameObjects.DOMElement | null = null;
|
||||
private emoteTween: Phaser.Tweens.Tween | null = null;
|
||||
scene: GameScene;
|
||||
|
||||
@ -300,8 +301,9 @@ export abstract class Character extends Container {
|
||||
playEmote(emote: string) {
|
||||
this.cancelPreviousEmote();
|
||||
const emoteY = -45;
|
||||
this.playerName.setVisible(false);
|
||||
this.emote = new Text(this.scene, -10, 0, emote, { fontSize: "20px" });
|
||||
const image = new Image(16, 16);
|
||||
image.src = emote;
|
||||
this.emote = new DOMElement(this.scene, -1, 0, image, "z-index:10;");
|
||||
this.emote.setAlpha(0);
|
||||
this.add(this.emote);
|
||||
this.createStartTransition(emoteY);
|
||||
|
@ -162,6 +162,7 @@ export class EmbeddedWebsiteManager {
|
||||
|
||||
const iframe = document.createElement("iframe");
|
||||
iframe.src = absoluteUrl;
|
||||
iframe.tabIndex = -1;
|
||||
iframe.style.width = embeddedWebsiteEvent.position.width + "px";
|
||||
iframe.style.height = embeddedWebsiteEvent.position.height + "px";
|
||||
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 { Queue } from "queue-typescript";
|
||||
import {
|
||||
Box,
|
||||
ON_ACTION_TRIGGER_BUTTON,
|
||||
} from "../../WebRtc/LayoutManager";
|
||||
import { Box, ON_ACTION_TRIGGER_BUTTON } from "../../WebRtc/LayoutManager";
|
||||
import { CoWebsite, coWebsiteManager } from "../../WebRtc/CoWebsiteManager";
|
||||
import type { UserMovedMessage } from "../../Messages/generated/messages_pb";
|
||||
import { ProtobufClientUtils } from "../../Network/ProtobufClientUtils";
|
||||
@ -31,7 +28,7 @@ import { localUserStore } from "../../Connexion/LocalUserStore";
|
||||
import { HtmlUtils } from "../../WebRtc/HtmlUtils";
|
||||
import { mediaManager } from "../../WebRtc/MediaManager";
|
||||
import { SimplePeer } from "../../WebRtc/SimplePeer";
|
||||
import { addLoader, removeLoader } from "../Components/Loader";
|
||||
import { Loader } from "../Components/Loader";
|
||||
import { lazyLoadPlayerCharacterTextures, loadCustomTexture } from "../Entity/PlayerTexturesLoadingManager";
|
||||
import { RemotePlayer } from "../Entity/RemotePlayer";
|
||||
import type { ActionableItem } from "../Items/ActionableItem";
|
||||
@ -91,6 +88,7 @@ import { analyticsClient } from "../../Administration/AnalyticsClient";
|
||||
import { get } from "svelte/store";
|
||||
import { contactPageStore } from "../../Stores/MenuStore";
|
||||
import { GameMapProperties } from "./GameMapProperties";
|
||||
import SpriteSheetFile = Phaser.Loader.FileTypes.SpriteSheetFile;
|
||||
|
||||
export interface GameSceneInitInterface {
|
||||
initPosition: PointInterface | null;
|
||||
@ -205,6 +203,7 @@ export class GameScene extends DirtyScene {
|
||||
private sharedVariablesManager!: SharedVariablesManager;
|
||||
private objectsByType = new Map<string, ITiledMapObject[]>();
|
||||
private embeddedWebsiteManager!: EmbeddedWebsiteManager;
|
||||
private loader: Loader;
|
||||
|
||||
constructor(private room: Room, MapUrlFile: string, customKey?: string | undefined) {
|
||||
super({
|
||||
@ -223,6 +222,7 @@ export class GameScene extends DirtyScene {
|
||||
this.connectionAnswerPromise = new Promise<RoomJoinedMessageInterface>((resolve, reject): void => {
|
||||
this.connectionAnswerPromiseResolve = resolve;
|
||||
});
|
||||
this.loader = new Loader(this);
|
||||
}
|
||||
|
||||
//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.
|
||||
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
|
||||
removeLoader(this);
|
||||
this.loader.removeLoader();
|
||||
|
||||
//display an error scene
|
||||
this.scene.start(ErrorSceneName, {
|
||||
@ -332,7 +333,7 @@ export class GameScene extends DirtyScene {
|
||||
});
|
||||
|
||||
//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.
|
||||
@ -497,7 +498,10 @@ export class GameScene extends DirtyScene {
|
||||
object.properties,
|
||||
'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
|
||||
this.embeddedWebsiteManager.createEmbeddedWebsite(
|
||||
@ -614,10 +618,10 @@ export class GameScene extends DirtyScene {
|
||||
oldPeerNumber = newPeerNumber;
|
||||
});
|
||||
|
||||
this.emoteUnsubscribe = emoteStore.subscribe((emoteKey) => {
|
||||
if (emoteKey) {
|
||||
this.CurrentPlayer?.playEmote(emoteKey);
|
||||
this.connection?.emitEmoteEvent(emoteKey);
|
||||
this.emoteUnsubscribe = emoteStore.subscribe((emote) => {
|
||||
if (emote) {
|
||||
this.CurrentPlayer?.playEmote(emote.url);
|
||||
this.connection?.emitEmoteEvent(emote.url);
|
||||
emoteStore.set(null);
|
||||
}
|
||||
});
|
||||
@ -763,14 +767,14 @@ export class GameScene extends DirtyScene {
|
||||
this.gameMap.setPosition(this.CurrentPlayer.x, this.CurrentPlayer.y);
|
||||
|
||||
// Init layer change listener
|
||||
this.gameMap.onEnterLayer(layers => {
|
||||
layers.forEach(layer => {
|
||||
this.gameMap.onEnterLayer((layers) => {
|
||||
layers.forEach((layer) => {
|
||||
iframeListener.sendEnterLayerEvent(layer.name);
|
||||
});
|
||||
});
|
||||
|
||||
this.gameMap.onLeaveLayer(layers => {
|
||||
layers.forEach(layer => {
|
||||
this.gameMap.onLeaveLayer((layers) => {
|
||||
layers.forEach((layer) => {
|
||||
iframeListener.sendLeaveLayerEvent(layer.name);
|
||||
});
|
||||
});
|
||||
@ -1830,6 +1834,8 @@ ${escapedMessage}
|
||||
right: camera.scrollX + camera.width,
|
||||
bottom: camera.scrollY + camera.height,
|
||||
});
|
||||
|
||||
this.loader.resize();
|
||||
}
|
||||
private getObjectLayerData(objectName: string): ITiledMapObject | undefined {
|
||||
for (const layer of this.mapFile.layers) {
|
||||
@ -1868,7 +1874,8 @@ ${escapedMessage}
|
||||
public startJitsi(roomName: string, jwt?: string): void {
|
||||
const allProps = this.gameMap.getCurrentProperties();
|
||||
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(
|
||||
allProps.get(GameMapProperties.JITSI_INTERFACE_CONFIG) as string | undefined,
|
||||
|
@ -4,7 +4,7 @@ import { loadAllLayers } from "../Entity/PlayerTexturesLoadingManager";
|
||||
import Sprite = Phaser.GameObjects.Sprite;
|
||||
import { gameManager } from "../Game/GameManager";
|
||||
import { localUserStore } from "../../Connexion/LocalUserStore";
|
||||
import { addLoader } from "../Components/Loader";
|
||||
import { Loader } from "../Components/Loader";
|
||||
import type { BodyResourceDescriptionInterface } from "../Entity/PlayerTextures";
|
||||
import { AbstractCharacterScene } from "./AbstractCharacterScene";
|
||||
import { areCharacterLayersValid } from "../../Connexion/LocalUser";
|
||||
@ -14,6 +14,7 @@ import { waScaleManager } from "../Services/WaScaleManager";
|
||||
import { isMobile } from "../../Enum/EnvironmentVariable";
|
||||
import { CustomizedCharacter } from "../Entity/CustomizedCharacter";
|
||||
import { get } from "svelte/store";
|
||||
import { analyticsClient } from "../../Administration/AnalyticsClient";
|
||||
|
||||
export const CustomizeSceneName = "CustomizeScene";
|
||||
|
||||
@ -29,10 +30,13 @@ export class CustomizeScene extends AbstractCharacterScene {
|
||||
private moveHorizontally: number = 0;
|
||||
private moveVertically: number = 0;
|
||||
|
||||
private loader: Loader;
|
||||
|
||||
constructor() {
|
||||
super({
|
||||
key: CustomizeSceneName,
|
||||
});
|
||||
this.loader = new Loader(this);
|
||||
}
|
||||
|
||||
preload() {
|
||||
@ -54,7 +58,7 @@ export class CustomizeScene extends AbstractCharacterScene {
|
||||
this.lazyloadingAttempt = false;
|
||||
|
||||
//this function must stay at the end of preload function
|
||||
addLoader(this);
|
||||
this.loader.addLoader();
|
||||
}
|
||||
|
||||
create() {
|
||||
@ -278,6 +282,8 @@ export class CustomizeScene extends AbstractCharacterScene {
|
||||
return;
|
||||
}
|
||||
|
||||
analyticsClient.validationWoka("CustomizeWoka");
|
||||
|
||||
gameManager.setCharacterLayers(layers);
|
||||
this.scene.sleep(CustomizeSceneName);
|
||||
waScaleManager.restoreZoom();
|
||||
|
@ -2,6 +2,7 @@ import { gameManager } from "../Game/GameManager";
|
||||
import { ResizableScene } from "./ResizableScene";
|
||||
import { enableCameraSceneVisibilityStore } from "../../Stores/MediaStore";
|
||||
import { localUserStore } from "../../Connexion/LocalUserStore";
|
||||
import { analyticsClient } from "../../Administration/AnalyticsClient";
|
||||
|
||||
export const EnableCameraSceneName = "EnableCameraScene";
|
||||
|
||||
@ -27,6 +28,8 @@ export class EnableCameraScene extends ResizableScene {
|
||||
update(time: number, delta: number): void {}
|
||||
|
||||
public login(): void {
|
||||
analyticsClient.validationVideo();
|
||||
|
||||
enableCameraSceneVisibilityStore.hideEnableCameraScene();
|
||||
|
||||
this.scene.sleep(EnableCameraSceneName);
|
||||
|
@ -4,6 +4,7 @@ import { loginSceneVisibleIframeStore, loginSceneVisibleStore } from "../../Stor
|
||||
import { localUserStore } from "../../Connexion/LocalUserStore";
|
||||
import { connectionManager } from "../../Connexion/ConnectionManager";
|
||||
import { gameManager } from "../Game/GameManager";
|
||||
import { analyticsClient } from "../../Administration/AnalyticsClient";
|
||||
|
||||
export const LoginSceneName = "LoginScene";
|
||||
|
||||
@ -34,6 +35,8 @@ export class LoginScene extends ResizableScene {
|
||||
}
|
||||
|
||||
public login(name: string): void {
|
||||
analyticsClient.validationName();
|
||||
|
||||
name = name.trim();
|
||||
gameManager.setPlayerName(name);
|
||||
|
||||
|
@ -4,7 +4,7 @@ import { EnableCameraSceneName } from "./EnableCameraScene";
|
||||
import { CustomizeSceneName } from "./CustomizeScene";
|
||||
import { localUserStore } from "../../Connexion/LocalUserStore";
|
||||
import { loadAllDefaultModels } from "../Entity/PlayerTexturesLoadingManager";
|
||||
import { addLoader } from "../Components/Loader";
|
||||
import { Loader } from "../Components/Loader";
|
||||
import type { BodyResourceDescriptionInterface } from "../Entity/PlayerTextures";
|
||||
import { AbstractCharacterScene } from "./AbstractCharacterScene";
|
||||
import { areCharacterLayersValid } from "../../Connexion/LocalUser";
|
||||
@ -13,6 +13,7 @@ import { PinchManager } from "../UserInput/PinchManager";
|
||||
import { selectCharacterSceneVisibleStore } from "../../Stores/SelectCharacterStore";
|
||||
import { waScaleManager } from "../Services/WaScaleManager";
|
||||
import { isMobile } from "../../Enum/EnvironmentVariable";
|
||||
import { analyticsClient } from "../../Administration/AnalyticsClient";
|
||||
|
||||
//todo: put this constants in a dedicated file
|
||||
export const SelectCharacterSceneName = "SelectCharacterScene";
|
||||
@ -30,11 +31,13 @@ export class SelectCharacterScene extends AbstractCharacterScene {
|
||||
protected pointerTimer: number = 0;
|
||||
|
||||
protected lazyloadingAttempt = true; //permit to update texture loaded after renderer
|
||||
private loader: Loader;
|
||||
|
||||
constructor() {
|
||||
super({
|
||||
key: SelectCharacterSceneName,
|
||||
});
|
||||
this.loader = new Loader(this);
|
||||
}
|
||||
|
||||
preload() {
|
||||
@ -48,7 +51,7 @@ export class SelectCharacterScene extends AbstractCharacterScene {
|
||||
this.lazyloadingAttempt = false;
|
||||
|
||||
//this function must stay at the end of preload function
|
||||
addLoader(this);
|
||||
this.loader.addLoader();
|
||||
}
|
||||
|
||||
create() {
|
||||
@ -98,6 +101,9 @@ export class SelectCharacterScene extends AbstractCharacterScene {
|
||||
if (!this.selectedPlayer) {
|
||||
return;
|
||||
}
|
||||
|
||||
analyticsClient.validationWoka("SelectWoka");
|
||||
|
||||
this.scene.stop(SelectCharacterSceneName);
|
||||
waScaleManager.restoreZoom();
|
||||
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 { ResizableScene } from "./ResizableScene";
|
||||
import { EnableCameraSceneName } from "./EnableCameraScene";
|
||||
@ -22,11 +22,13 @@ export class SelectCompanionScene extends ResizableScene {
|
||||
private currentCompanion = 0;
|
||||
private pointerClicked: boolean = false;
|
||||
private pointerTimer: number = 0;
|
||||
private loader: Loader;
|
||||
|
||||
constructor() {
|
||||
super({
|
||||
key: SelectCompanionSceneName,
|
||||
});
|
||||
this.loader = new Loader(this);
|
||||
}
|
||||
|
||||
preload() {
|
||||
@ -35,7 +37,7 @@ export class SelectCompanionScene extends ResizableScene {
|
||||
});
|
||||
|
||||
//this function must stay at the end of preload function
|
||||
addLoader(this);
|
||||
this.loader.addLoader();
|
||||
}
|
||||
|
||||
create() {
|
||||
|
@ -3,8 +3,6 @@ import type { GameScene } from "../Game/GameScene";
|
||||
import { UserInputEvent, UserInputManager } from "../UserInput/UserInputManager";
|
||||
import { Character } from "../Entity/Character";
|
||||
import { userMovingStore } from "../../Stores/GameStore";
|
||||
import { get } from "svelte/store";
|
||||
import { emoteMenuStore } from "../../Stores/EmoteStore";
|
||||
|
||||
export const hasMovedEventName = "hasMoved";
|
||||
export const requestEmoteEventName = "requestEmote";
|
||||
@ -68,10 +66,24 @@ export class Player extends Character {
|
||||
} else if (this.wasMoving && moving) {
|
||||
// slow joystick movement
|
||||
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) {
|
||||
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) {
|
||||
|
@ -67,6 +67,9 @@ function createChatMessagesStore() {
|
||||
});
|
||||
},
|
||||
addPersonnalMessage(text: string) {
|
||||
//post message iframe listener
|
||||
iframeListener.sendUserInputChat(text);
|
||||
|
||||
newChatMessageStore.set(text);
|
||||
update((list) => {
|
||||
const lastMessage = list[list.length - 1];
|
||||
|
@ -1,5 +1,11 @@
|
||||
import { writable } from "svelte/store";
|
||||
|
||||
export interface Emoji {
|
||||
unicode: string;
|
||||
url: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
function createEmoteMenuStore() {
|
||||
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();
|
||||
|
@ -34,20 +34,20 @@ export const warningContainerStore = createWarningContainerStore();
|
||||
export enum SubMenusInterface {
|
||||
settings = "Settings",
|
||||
profile = "Profile",
|
||||
createMap = "Create a Map",
|
||||
aboutRoom = "About the Room",
|
||||
invite = "Invite",
|
||||
aboutRoom = "Credit",
|
||||
globalMessages = "Global Messages",
|
||||
contact = "Contact",
|
||||
}
|
||||
|
||||
function createSubMenusStore() {
|
||||
const { subscribe, update } = writable<string[]>([
|
||||
SubMenusInterface.settings,
|
||||
SubMenusInterface.profile,
|
||||
SubMenusInterface.createMap,
|
||||
SubMenusInterface.aboutRoom,
|
||||
SubMenusInterface.globalMessages,
|
||||
SubMenusInterface.contact,
|
||||
SubMenusInterface.settings,
|
||||
SubMenusInterface.invite,
|
||||
SubMenusInterface.aboutRoom,
|
||||
]);
|
||||
|
||||
return {
|
||||
|
@ -3,6 +3,7 @@ import { Subject } from "rxjs";
|
||||
import { iframeListener } from "../Api/IframeListener";
|
||||
import { touchScreenManager } from "../Touch/TouchScreenManager";
|
||||
import { waScaleManager } from "../Phaser/Services/WaScaleManager";
|
||||
import { ICON_URL } from "../Enum/EnvironmentVariable";
|
||||
|
||||
enum iframeStates {
|
||||
closed = 1,
|
||||
@ -27,16 +28,16 @@ interface TouchMoveCoordinates {
|
||||
y: number;
|
||||
}
|
||||
|
||||
export type CoWebsite = {
|
||||
iframe: HTMLIFrameElement,
|
||||
icon: HTMLDivElement,
|
||||
position: number
|
||||
}
|
||||
export type CoWebsite = {
|
||||
iframe: HTMLIFrameElement;
|
||||
icon: HTMLDivElement;
|
||||
position: number;
|
||||
};
|
||||
|
||||
type CoWebsiteSlot = {
|
||||
container: HTMLElement,
|
||||
position: number
|
||||
}
|
||||
container: HTMLElement;
|
||||
position: number;
|
||||
};
|
||||
|
||||
class CoWebsiteManager {
|
||||
private openedMain: iframeStates = iframeStates.closed;
|
||||
@ -61,7 +62,7 @@ class CoWebsiteManager {
|
||||
|
||||
private slots: CoWebsiteSlot[];
|
||||
|
||||
private resizeObserver = new ResizeObserver(entries => {
|
||||
private resizeObserver = new ResizeObserver((entries) => {
|
||||
this.resizeAllIframes();
|
||||
});
|
||||
|
||||
@ -108,23 +109,23 @@ class CoWebsiteManager {
|
||||
this.slots = [
|
||||
{
|
||||
container: this.cowebsiteMainDom,
|
||||
position: 0
|
||||
position: 0,
|
||||
},
|
||||
{
|
||||
container: HtmlUtils.getElementByIdOrFail<HTMLDivElement>('cowebsite-slot-1'),
|
||||
position: 1
|
||||
container: HtmlUtils.getElementByIdOrFail<HTMLDivElement>("cowebsite-slot-1"),
|
||||
position: 1,
|
||||
},
|
||||
{
|
||||
container: HtmlUtils.getElementByIdOrFail<HTMLDivElement>('cowebsite-slot-2'),
|
||||
position: 2
|
||||
container: HtmlUtils.getElementByIdOrFail<HTMLDivElement>("cowebsite-slot-2"),
|
||||
position: 2,
|
||||
},
|
||||
{
|
||||
container: HtmlUtils.getElementByIdOrFail<HTMLDivElement>('cowebsite-slot-3'),
|
||||
position: 3
|
||||
container: HtmlUtils.getElementByIdOrFail<HTMLDivElement>("cowebsite-slot-3"),
|
||||
position: 3,
|
||||
},
|
||||
{
|
||||
container: HtmlUtils.getElementByIdOrFail<HTMLDivElement>('cowebsite-slot-4'),
|
||||
position: 4
|
||||
container: HtmlUtils.getElementByIdOrFail<HTMLDivElement>("cowebsite-slot-4"),
|
||||
position: 4,
|
||||
},
|
||||
];
|
||||
|
||||
@ -163,8 +164,10 @@ class CoWebsiteManager {
|
||||
}
|
||||
|
||||
private isSmallScreen(): boolean {
|
||||
return window.matchMedia("(max-aspect-ratio: 1/1)").matches ||
|
||||
window.matchMedia("(max-width:960px) and (max-height:768px)").matches;
|
||||
return (
|
||||
window.matchMedia("(max-aspect-ratio: 1/1)").matches ||
|
||||
window.matchMedia("(max-width:960px) and (max-height:768px)").matches
|
||||
);
|
||||
}
|
||||
|
||||
private initResizeListeners(touchMode: boolean) {
|
||||
@ -235,12 +238,12 @@ class CoWebsiteManager {
|
||||
|
||||
private initActionsListeners() {
|
||||
this.slots.forEach((slot: CoWebsiteSlot) => {
|
||||
const expandButton = slot.container.querySelector('.expand');
|
||||
const highlightButton = slot.container.querySelector('.hightlight');
|
||||
const closeButton = slot.container.querySelector('.close');
|
||||
const expandButton = slot.container.querySelector(".expand");
|
||||
const highlightButton = slot.container.querySelector(".hightlight");
|
||||
const closeButton = slot.container.querySelector(".close");
|
||||
|
||||
if (expandButton) {
|
||||
expandButton.addEventListener('click', (event) => {
|
||||
expandButton.addEventListener("click", (event) => {
|
||||
event.preventDefault();
|
||||
const coWebsite = this.getCoWebsiteByPosition(slot.position);
|
||||
|
||||
@ -253,7 +256,7 @@ class CoWebsiteManager {
|
||||
}
|
||||
|
||||
if (highlightButton) {
|
||||
highlightButton.addEventListener('click', (event) => {
|
||||
highlightButton.addEventListener("click", (event) => {
|
||||
event.preventDefault();
|
||||
const coWebsite = this.getCoWebsiteByPosition(slot.position);
|
||||
|
||||
@ -266,7 +269,7 @@ class CoWebsiteManager {
|
||||
}
|
||||
|
||||
if (closeButton) {
|
||||
closeButton.addEventListener('click', (event) => {
|
||||
closeButton.addEventListener("click", (event) => {
|
||||
event.preventDefault();
|
||||
const coWebsite = this.getCoWebsiteByPosition(slot.position);
|
||||
|
||||
@ -284,37 +287,37 @@ class CoWebsiteManager {
|
||||
return this.coWebsites;
|
||||
}
|
||||
|
||||
public getCoWebsiteById(coWebsiteId: string): CoWebsite|undefined {
|
||||
public getCoWebsiteById(coWebsiteId: string): CoWebsite | undefined {
|
||||
return this.coWebsites.find((coWebsite: CoWebsite) => coWebsite.iframe.id === coWebsiteId);
|
||||
}
|
||||
|
||||
private getSlotByPosition(position: number): CoWebsiteSlot|undefined {
|
||||
private getSlotByPosition(position: number): CoWebsiteSlot | undefined {
|
||||
return this.slots.find((slot: CoWebsiteSlot) => slot.position === position);
|
||||
}
|
||||
|
||||
private getCoWebsiteByPosition(position: number): CoWebsite|undefined {
|
||||
private getCoWebsiteByPosition(position: number): CoWebsite | undefined {
|
||||
return this.coWebsites.find((coWebsite: CoWebsite) => coWebsite.position === position);
|
||||
}
|
||||
|
||||
private setIframeOffset(coWebsite: CoWebsite, slot: CoWebsiteSlot) {
|
||||
const bounding = slot.container.getBoundingClientRect();
|
||||
|
||||
if (coWebsite.iframe.classList.contains('thumbnail')) {
|
||||
coWebsite.iframe.style.width = ((bounding.right - bounding.left) * 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.left = (bounding.left - (Math.floor(bounding.width * 0.5))) + 'px';
|
||||
if (coWebsite.iframe.classList.contains("thumbnail")) {
|
||||
coWebsite.iframe.style.width = (bounding.right - bounding.left) * 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.left = bounding.left - Math.floor(bounding.width * 0.5) + "px";
|
||||
} else {
|
||||
coWebsite.iframe.style.top = bounding.top + 'px';
|
||||
coWebsite.iframe.style.left = bounding.left + 'px';
|
||||
coWebsite.iframe.style.width = (bounding.right - bounding.left) + 'px';
|
||||
coWebsite.iframe.style.height = (bounding.bottom - bounding.top) + 'px';
|
||||
coWebsite.iframe.style.top = bounding.top + "px";
|
||||
coWebsite.iframe.style.left = bounding.left + "px";
|
||||
coWebsite.iframe.style.width = bounding.right - bounding.left + "px";
|
||||
coWebsite.iframe.style.height = bounding.bottom - bounding.top + "px";
|
||||
}
|
||||
}
|
||||
|
||||
private resizeAllIframes() {
|
||||
this.coWebsites.forEach((coWebsite: CoWebsite) => {
|
||||
const slot = this.getSlotByPosition(coWebsite.position);
|
||||
const slot = this.getSlotByPosition(coWebsite.position);
|
||||
|
||||
if (slot) {
|
||||
this.setIframeOffset(coWebsite, slot);
|
||||
@ -333,38 +336,48 @@ class CoWebsiteManager {
|
||||
coWebsite.iframe.scrolling = newPosition === 0 || newPosition === 1 ? "yes" : "no";
|
||||
|
||||
if (newPosition === 0) {
|
||||
coWebsite.iframe.classList.add('main');
|
||||
coWebsite.iframe.classList.add("main");
|
||||
coWebsite.icon.style.display = "none";
|
||||
} else {
|
||||
coWebsite.iframe.classList.remove('main');
|
||||
coWebsite.iframe.classList.remove("main");
|
||||
coWebsite.icon.style.display = "flex";
|
||||
}
|
||||
|
||||
if (newPosition === 1) {
|
||||
coWebsite.iframe.classList.add('sub-main');
|
||||
coWebsite.iframe.classList.add("sub-main");
|
||||
} else {
|
||||
coWebsite.iframe.classList.remove('sub-main');
|
||||
coWebsite.iframe.classList.remove("sub-main");
|
||||
}
|
||||
|
||||
if (newPosition >= 2) {
|
||||
coWebsite.iframe.classList.add('thumbnail');
|
||||
coWebsite.iframe.classList.add("thumbnail");
|
||||
} else {
|
||||
coWebsite.iframe.classList.remove('thumbnail');
|
||||
coWebsite.iframe.classList.remove("thumbnail");
|
||||
}
|
||||
|
||||
coWebsite.position = newPosition;
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
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) {
|
||||
const nextCoWebsite = this.getCoWebsiteByPosition(coWebsite.position + 1);
|
||||
|
||||
@ -384,11 +397,7 @@ class CoWebsiteManager {
|
||||
|
||||
this.moveCoWebsite(coWebsite, newPosition);
|
||||
|
||||
if (newPosition === 4 ||
|
||||
!currentCoWebsite ||
|
||||
currentCoWebsite.iframe.id === coWebsite.iframe.id
|
||||
)
|
||||
{
|
||||
if (newPosition === 4 || !currentCoWebsite || currentCoWebsite.iframe.id === coWebsite.iframe.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -411,26 +420,26 @@ class CoWebsiteManager {
|
||||
if (coWebsite.position > 0) {
|
||||
const slot = this.getSlotByPosition(coWebsite.position);
|
||||
if (slot) {
|
||||
slot.container.style.display = 'none';
|
||||
slot.container.style.display = "none";
|
||||
}
|
||||
}
|
||||
|
||||
const previousCoWebsite = this.coWebsites.find((coWebsiteToCheck: CoWebsite) =>
|
||||
coWebsite.position + 1 === coWebsiteToCheck.position
|
||||
const previousCoWebsite = this.coWebsites.find(
|
||||
(coWebsiteToCheck: CoWebsite) => coWebsite.position + 1 === coWebsiteToCheck.position
|
||||
);
|
||||
|
||||
if (previousCoWebsite) {
|
||||
this.moveLeftPreviousCoWebsite(previousCoWebsite, coWebsite.position);
|
||||
}
|
||||
|
||||
this.displayCowebsiteContainer();
|
||||
|
||||
coWebsite.icon.remove();
|
||||
coWebsite.iframe.remove();
|
||||
}
|
||||
|
||||
public searchJitsi(): CoWebsite|undefined {
|
||||
return this.coWebsites.find((coWebsite : CoWebsite) =>
|
||||
coWebsite.iframe.id.toLowerCase().includes('jitsi')
|
||||
);
|
||||
public searchJitsi(): CoWebsite | undefined {
|
||||
return this.coWebsites.find((coWebsite: CoWebsite) => coWebsite.iframe.id.toLowerCase().includes("jitsi"));
|
||||
}
|
||||
|
||||
private generateCoWebsiteIcon(iframe: HTMLIFrameElement): HTMLDivElement {
|
||||
@ -439,7 +448,7 @@ class CoWebsiteManager {
|
||||
icon.style.display = "none";
|
||||
|
||||
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);
|
||||
iconImage.alt = url.hostname;
|
||||
|
||||
@ -456,27 +465,30 @@ class CoWebsiteManager {
|
||||
widthPercent?: number,
|
||||
position?: number
|
||||
): Promise<CoWebsite> {
|
||||
return this.addCoWebsite(
|
||||
(iframeBuffer) => {
|
||||
const iframe = document.createElement("iframe");
|
||||
iframe.src = new URL(url, base).toString();
|
||||
|
||||
return this.addCoWebsite((iframeBuffer) => {
|
||||
const iframe = document.createElement("iframe");
|
||||
iframe.src = new URL(url, base).toString()
|
||||
if (allowPolicy) {
|
||||
iframe.allow = allowPolicy;
|
||||
}
|
||||
|
||||
if (allowPolicy) {
|
||||
iframe.allow = allowPolicy;
|
||||
}
|
||||
if (allowApi) {
|
||||
iframeListener.registerIframe(iframe);
|
||||
}
|
||||
|
||||
if (allowApi) {
|
||||
iframeListener.registerIframe(iframe);
|
||||
}
|
||||
iframeBuffer.appendChild(iframe);
|
||||
|
||||
iframeBuffer.appendChild(iframe);
|
||||
|
||||
return iframe;
|
||||
}, widthPercent, position);
|
||||
return iframe;
|
||||
},
|
||||
widthPercent,
|
||||
position
|
||||
);
|
||||
}
|
||||
|
||||
public async addCoWebsite(
|
||||
callback: (iframeBuffer: HTMLDivElement) => PromiseLike<HTMLIFrameElement>|HTMLIFrameElement,
|
||||
callback: (iframeBuffer: HTMLDivElement) => PromiseLike<HTMLIFrameElement> | HTMLIFrameElement,
|
||||
widthPercent?: number,
|
||||
position?: number
|
||||
): Promise<CoWebsite> {
|
||||
@ -484,10 +496,10 @@ class CoWebsiteManager {
|
||||
if (this.coWebsites.length < 1) {
|
||||
this.loadMain();
|
||||
} 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");
|
||||
|
||||
if (!iframe.id) {
|
||||
@ -533,14 +545,14 @@ class CoWebsiteManager {
|
||||
|
||||
setTimeout(() => {
|
||||
this.fire();
|
||||
position !== undefined ?
|
||||
this.moveRightPreviousCoWebsite(coWebsite, coWebsite.position) :
|
||||
this.moveCoWebsite(coWebsite, coWebsite.position);
|
||||
position !== undefined
|
||||
? this.moveRightPreviousCoWebsite(coWebsite, coWebsite.position)
|
||||
: this.moveCoWebsite(coWebsite, coWebsite.position);
|
||||
}, animationTime);
|
||||
} else {
|
||||
position !== undefined ?
|
||||
this.moveRightPreviousCoWebsite(coWebsite, coWebsite.position) :
|
||||
this.moveCoWebsite(coWebsite, coWebsite.position);
|
||||
position !== undefined
|
||||
? this.moveRightPreviousCoWebsite(coWebsite, coWebsite.position)
|
||||
: this.moveCoWebsite(coWebsite, coWebsite.position);
|
||||
}
|
||||
|
||||
return resolve(coWebsite);
|
||||
@ -583,8 +595,7 @@ class CoWebsiteManager {
|
||||
}
|
||||
|
||||
public closeCoWebsites(): Promise<void> {
|
||||
this.currentOperationPromise = this.currentOperationPromise
|
||||
.then(() => {
|
||||
this.currentOperationPromise = this.currentOperationPromise.then(() => {
|
||||
this.coWebsites.forEach((coWebsite: CoWebsite) => {
|
||||
this.closeCoWebsite(coWebsite);
|
||||
});
|
||||
|
@ -65,6 +65,7 @@
|
||||
|
||||
&-container {
|
||||
position: absolute;
|
||||
display: none;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
|
||||
|
@ -7,7 +7,6 @@ import MiniCssExtractPlugin from "mini-css-extract-plugin";
|
||||
import sveltePreprocess from "svelte-preprocess";
|
||||
import ForkTsCheckerWebpackPlugin from "fork-ts-checker-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 buildNpmTypingsForApi = !!process.env.BUILD_TYPINGS;
|
||||
@ -193,6 +192,7 @@ module.exports = {
|
||||
ADMIN_URL: undefined,
|
||||
CONTACT_URL: null,
|
||||
PROFILE_URL: null,
|
||||
ICON_URL: null,
|
||||
DEBUG_MODE: null,
|
||||
STUN_SERVER: null,
|
||||
TURN_SERVER: null,
|
||||
@ -207,6 +207,8 @@ module.exports = {
|
||||
POSTHOG_API_KEY: null,
|
||||
POSTHOG_URL: null,
|
||||
NODE_ENV: mode,
|
||||
DISABLE_ANONYMOUS: false,
|
||||
OPID_LOGIN_SCREEN_PROVIDER: null,
|
||||
}),
|
||||
],
|
||||
} as Configuration & WebpackDevServer.Configuration;
|
||||
|
@ -1,16 +1,18 @@
|
||||
WA.onInit().then(() => {
|
||||
let message;
|
||||
let message;
|
||||
|
||||
WA.room.onEnterZone("carpet", () => {
|
||||
message = WA.ui.displayActionMessage({
|
||||
message: "This is a test message. Press space to display a chat message. Walk out to hide the message.",
|
||||
callback: () => {
|
||||
WA.chat.sendChatMessage("Hello world!", "The bot");
|
||||
}
|
||||
});
|
||||
WA.room.onEnterLayer("carpet").subscribe(() => {
|
||||
message = WA.ui.displayActionMessage({
|
||||
message:
|
||||
"This is a test message. Press space to display a chat message. Walk out to hide the message.",
|
||||
callback: () => {
|
||||
WA.chat.sendChatMessage("Hello world!", "The bot");
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
WA.room.onLeaveZone("carpet", () => {
|
||||
message && message.remove();
|
||||
});
|
||||
|
||||
WA.room.onLeaveLayer("carpet").subscribe(() => {
|
||||
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 { App as uwsApp } from "./Server/sifrr.server";
|
||||
import { AdminController } from "./Controller/AdminController";
|
||||
import { OpenIdProfileController } from "./Controller/OpenIdProfileController";
|
||||
|
||||
class App {
|
||||
public app: uwsApp;
|
||||
@ -15,6 +16,7 @@ class App {
|
||||
public prometheusController: PrometheusController;
|
||||
private debugController: DebugController;
|
||||
private adminController: AdminController;
|
||||
private openIdProfileController: OpenIdProfileController;
|
||||
|
||||
constructor() {
|
||||
this.app = new uwsApp();
|
||||
@ -26,6 +28,7 @@ class App {
|
||||
this.prometheusController = new PrometheusController(this.app);
|
||||
this.debugController = new DebugController(this.app);
|
||||
this.adminController = new AdminController(this.app);
|
||||
this.openIdProfileController = new OpenIdProfileController(this.app);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,10 +1,11 @@
|
||||
import { v4 } from "uuid";
|
||||
import { HttpRequest, HttpResponse, TemplatedApp } from "uWebSockets.js";
|
||||
import { BaseController } from "./BaseController";
|
||||
import { adminApi } from "../Services/AdminApi";
|
||||
import { adminApi, FetchMemberDataByUuidResponse } from "../Services/AdminApi";
|
||||
import { AuthTokenData, jwtTokenManager } from "../Services/JWTTokenManager";
|
||||
import { parse } from "query-string";
|
||||
import { openIDClient } from "../Services/OpenIDClient";
|
||||
import { DISABLE_ANONYMOUS } from "../Enum/EnvironmentVariable";
|
||||
import log from "../Services/Logger";
|
||||
|
||||
export interface TokenInterface {
|
||||
@ -56,19 +57,37 @@ export class AuthenticateController extends BaseController {
|
||||
res.onAborted(() => {
|
||||
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 {
|
||||
//verify connected by token
|
||||
if (token != undefined) {
|
||||
try {
|
||||
const authTokenData: AuthTokenData = jwtTokenManager.verifyJWTToken(token as string, false);
|
||||
if (authTokenData.hydraAccessToken == undefined) {
|
||||
|
||||
//Get user data from Admin Back Office
|
||||
//This is very important to create User Local in LocalStorage in WorkAdventure
|
||||
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");
|
||||
this.addCorsHeaders(res);
|
||||
return res.end(JSON.stringify({ ...resUserData, authToken: token }));
|
||||
}
|
||||
throw Error("Token cannot to be check on Hydra");
|
||||
}
|
||||
await openIDClient.checkTokenAuth(authTokenData.hydraAccessToken);
|
||||
|
||||
const resCheckTokenAuth = await openIDClient.checkTokenAuth(authTokenData.accessToken);
|
||||
res.writeStatus("200");
|
||||
this.addCorsHeaders(res);
|
||||
return res.end(JSON.stringify({ authToken: token }));
|
||||
return res.end(JSON.stringify({ ...resCheckTokenAuth, ...resUserData, authToken: token }));
|
||||
} catch (err) {
|
||||
log.info("User was not connected", err);
|
||||
}
|
||||
@ -80,10 +99,15 @@ export class AuthenticateController extends BaseController {
|
||||
if (!email) {
|
||||
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");
|
||||
this.addCorsHeaders(res);
|
||||
return res.end(JSON.stringify({ authToken }));
|
||||
return res.end(JSON.stringify({ ...data, authToken }));
|
||||
} catch (e) {
|
||||
log.error("openIDCallback => ERROR", e);
|
||||
return this.errorToResponse(e, res);
|
||||
@ -100,10 +124,10 @@ export class AuthenticateController extends BaseController {
|
||||
|
||||
try {
|
||||
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");
|
||||
}
|
||||
await openIDClient.logoutUser(authTokenData.hydraAccessToken);
|
||||
await openIDClient.logoutUser(authTokenData.accessToken);
|
||||
} catch (error) {
|
||||
log.error("openIDCallback => logout-callback", error);
|
||||
} finally {
|
||||
@ -176,16 +200,21 @@ export class AuthenticateController extends BaseController {
|
||||
log.warn("Login request was aborted");
|
||||
});
|
||||
|
||||
const userUuid = v4();
|
||||
const authToken = jwtTokenManager.createAuthToken(userUuid);
|
||||
res.writeStatus("200 OK");
|
||||
this.addCorsHeaders(res);
|
||||
res.end(
|
||||
JSON.stringify({
|
||||
authToken,
|
||||
userUuid,
|
||||
})
|
||||
);
|
||||
if (DISABLE_ANONYMOUS) {
|
||||
res.writeStatus("403 FORBIDDEN");
|
||||
res.end();
|
||||
} else {
|
||||
const userUuid = v4();
|
||||
const authToken = jwtTokenManager.createAuthToken(userUuid);
|
||||
res.writeStatus("200 OK");
|
||||
this.addCorsHeaders(res);
|
||||
res.end(
|
||||
JSON.stringify({
|
||||
authToken,
|
||||
userUuid,
|
||||
})
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -197,20 +226,20 @@ export class AuthenticateController extends BaseController {
|
||||
res.onAborted(() => {
|
||||
log.warn("/message request was aborted");
|
||||
});
|
||||
const { userIdentify, token } = parse(req.getQuery());
|
||||
const { token } = parse(req.getQuery());
|
||||
try {
|
||||
//verify connected by token
|
||||
if (token != undefined) {
|
||||
try {
|
||||
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");
|
||||
}
|
||||
await openIDClient.checkTokenAuth(authTokenData.hydraAccessToken);
|
||||
await openIDClient.checkTokenAuth(authTokenData.accessToken);
|
||||
|
||||
//get login profile
|
||||
res.writeStatus("302");
|
||||
res.writeHeader("Location", adminApi.getProfileUrl(authTokenData.hydraAccessToken));
|
||||
res.writeHeader("Location", adminApi.getProfileUrl(authTokenData.accessToken));
|
||||
this.addCorsHeaders(res);
|
||||
// eslint-disable-next-line no-unsafe-finally
|
||||
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 { FRONT_URL } from "../Enum/EnvironmentVariable";
|
||||
import log from "../Services/Logger";
|
||||
|
||||
export class BaseController {
|
||||
protected addCorsHeaders(res: HttpResponse): void {
|
||||
res.writeHeader("access-control-allow-headers", "Origin, X-Requested-With, Content-Type, Accept");
|
||||
res.writeHeader("access-control-allow-methods", "GET, POST, OPTIONS, PUT, PATCH, DELETE");
|
||||
res.writeHeader("access-control-allow-origin", "*");
|
||||
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 { SocketManager, socketManager } from "../Services/SocketManager";
|
||||
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 { ExAdminSocketInterface } from "_Model/Websocket/ExAdminSocketInterface";
|
||||
import { v4 } from "uuid";
|
||||
import { CharacterTexture } from "../Services/AdminApi/CharacterTexture";
|
||||
import log from "../Services/Logger";
|
||||
|
||||
@ -49,15 +48,19 @@ export class IoSocketController {
|
||||
const websocketProtocol = req.getHeader("sec-websocket-protocol");
|
||||
const websocketExtensions = req.getHeader("sec-websocket-extensions");
|
||||
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);
|
||||
res.writeStatus("401 Unauthorized").end("Incorrect token");
|
||||
return;
|
||||
}
|
||||
const roomId = query.roomId;
|
||||
if (typeof roomId !== "string") {
|
||||
log.error("Received");
|
||||
res.writeStatus("400 Bad Request").end("Missing room id");
|
||||
if (typeof roomId !== "string" || !authorizedRoomIds.includes(roomId)) {
|
||||
log.error("Invalid room id");
|
||||
res.writeStatus("403 Bad Request").end("Invalid room id");
|
||||
return;
|
||||
}
|
||||
|
||||
@ -71,8 +74,6 @@ export class IoSocketController {
|
||||
},
|
||||
message: (ws, arrayBuffer, isBinary): void => {
|
||||
try {
|
||||
const roomId = ws.roomId as string;
|
||||
|
||||
//TODO refactor message type and data
|
||||
const message: { event: string; message: { type: string; message: unknown; userUuid: string } } =
|
||||
JSON.parse(new TextDecoder("utf-8").decode(new Uint8Array(arrayBuffer)));
|
||||
@ -176,6 +177,11 @@ export class IoSocketController {
|
||||
|
||||
const tokenData =
|
||||
token && typeof token === "string" ? jwtTokenManager.verifyJWTToken(token) : null;
|
||||
|
||||
if (DISABLE_ANONYMOUS && !tokenData) {
|
||||
throw new Error("Expecting token");
|
||||
}
|
||||
|
||||
const userIdentifier = tokenData ? tokenData.identifier : "";
|
||||
|
||||
let memberTags: string[] = [];
|
||||
@ -184,6 +190,7 @@ export class IoSocketController {
|
||||
let memberTextures: CharacterTexture[] = [];
|
||||
const room = await socketManager.getOrCreateRoom(roomId);
|
||||
let userData: FetchMemberDataByUuidResponse = {
|
||||
email: userIdentifier,
|
||||
userUuid: userIdentifier,
|
||||
tags: [],
|
||||
visitCardUrl: null,
|
||||
|
@ -2,9 +2,9 @@ import { HttpRequest, HttpResponse, TemplatedApp } from "uWebSockets.js";
|
||||
import { BaseController } from "./BaseController";
|
||||
import { parse } from "query-string";
|
||||
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 { MapDetailsData } from "../Services/AdminApi/MapDetailsData";
|
||||
import { isMapDetailsData, MapDetailsData } from "../Services/AdminApi/MapDetailsData";
|
||||
import { socketManager } from "../Services/SocketManager";
|
||||
import { AuthTokenData, jwtTokenManager } from "../Services/JWTTokenManager";
|
||||
import { v4 } from "uuid";
|
||||
@ -21,7 +21,6 @@ export class MapController extends BaseController {
|
||||
getMapUrl() {
|
||||
this.App.options("/map", (res: HttpResponse, req: HttpRequest) => {
|
||||
this.addCorsHeaders(res);
|
||||
|
||||
res.end();
|
||||
});
|
||||
|
||||
@ -65,6 +64,7 @@ export class MapController extends BaseController {
|
||||
tags: [],
|
||||
textures: [],
|
||||
contactPage: undefined,
|
||||
authenticationMandatory: DISABLE_ANONYMOUS,
|
||||
} as MapDetailsData)
|
||||
);
|
||||
|
||||
@ -80,14 +80,26 @@ export class MapController extends BaseController {
|
||||
authTokenData = jwtTokenManager.verifyJWTToken(query.authToken as string);
|
||||
userId = authTokenData.identifier;
|
||||
} catch (e) {
|
||||
// Decode token, in this case we don't need to create new token.
|
||||
authTokenData = jwtTokenManager.verifyJWTToken(query.authToken as string, true);
|
||||
userId = authTokenData.identifier;
|
||||
log.info("JWT expire, but decoded", userId);
|
||||
try {
|
||||
// Decode token, in this case we don't need to create new token.
|
||||
authTokenData = jwtTokenManager.verifyJWTToken(query.authToken as string, true);
|
||||
userId = authTokenData.identifier;
|
||||
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);
|
||||
|
||||
if (isMapDetailsData(mapDetails) && DISABLE_ANONYMOUS) {
|
||||
mapDetails.authenticationMandatory = true;
|
||||
}
|
||||
|
||||
res.writeStatus("200 OK");
|
||||
this.addCorsHeaders(res);
|
||||
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_URL = process.env.ADMIN_URL || "";
|
||||
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 JITSI_URL: string | undefined = process.env.JITSI_URL === "" ? undefined : process.env.JITSI_URL;
|
||||
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_SECRET = process.env.OPID_CLIENT_SECRET || "";
|
||||
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 {
|
||||
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 { GameRoomPolicyTypes } from "_Model/PusherRoom";
|
||||
import { CharacterTexture } from "./AdminApi/CharacterTexture";
|
||||
@ -22,6 +22,7 @@ export interface AdminBannedData {
|
||||
}
|
||||
|
||||
export interface FetchMemberDataByUuidResponse {
|
||||
email: string;
|
||||
userUuid: string;
|
||||
tags: string[];
|
||||
visitCardUrl: string | null;
|
||||
@ -142,13 +143,19 @@ class AdminApi {
|
||||
});
|
||||
}
|
||||
|
||||
/*TODO add constant to use profile companny*/
|
||||
/**
|
||||
*
|
||||
* @param accessToken
|
||||
*/
|
||||
getProfileUrl(accessToken: string): string {
|
||||
if (!ADMIN_URL) {
|
||||
if (!OPID_PROFILE_SCREEN_PROVIDER) {
|
||||
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),
|
||||
textures: tg.isArray(isCharacterTexture),
|
||||
contactPage: tg.isUnion(tg.isString, tg.isUndefined),
|
||||
authenticationMandatory: tg.isUnion(tg.isBoolean, tg.isUndefined),
|
||||
})
|
||||
.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 Jwt, { verify } from "jsonwebtoken";
|
||||
import { TokenInterface } from "../Controller/AuthenticateController";
|
||||
@ -6,13 +6,20 @@ import { adminApi, AdminBannedData } from "../Services/AdminApi";
|
||||
|
||||
export interface AuthTokenData {
|
||||
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";
|
||||
|
||||
class JWTTokenManager {
|
||||
public createAuthToken(identifier: string, hydraAccessToken?: string) {
|
||||
return Jwt.sign({ identifier, hydraAccessToken }, SECRET_KEY, { expiresIn: "30d" });
|
||||
public verifyAdminSocketToken(token: string): AdminSocketTokenData {
|
||||
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 {
|
||||
|
@ -1,7 +1,10 @@
|
||||
import { Issuer, Client, IntrospectionResponse } from "openid-client";
|
||||
import { OPID_CLIENT_ID, OPID_CLIENT_SECRET, OPID_CLIENT_ISSUER, FRONT_URL } from "../Enum/EnvironmentVariable";
|
||||
|
||||
const opidRedirectUri = FRONT_URL + "/jwt";
|
||||
import {
|
||||
OPID_CLIENT_ID,
|
||||
OPID_CLIENT_SECRET,
|
||||
OPID_CLIENT_ISSUER,
|
||||
OPID_CLIENT_REDIRECT_URL,
|
||||
} from "../Enum/EnvironmentVariable";
|
||||
|
||||
class OpenIDClient {
|
||||
private issuerPromise: Promise<Client> | null = null;
|
||||
@ -12,7 +15,7 @@ class OpenIDClient {
|
||||
return new issuer.Client({
|
||||
client_id: OPID_CLIENT_ID,
|
||||
client_secret: OPID_CLIENT_SECRET,
|
||||
redirect_uris: [opidRedirectUri],
|
||||
redirect_uris: [OPID_CLIENT_REDIRECT_URL],
|
||||
response_types: ["code"],
|
||||
});
|
||||
});
|
||||
@ -35,7 +38,7 @@ class OpenIDClient {
|
||||
|
||||
public getUserInfo(code: string, nonce: string): Promise<{ email: string; sub: string; access_token: string }> {
|
||||
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 {
|
||||
...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