diff --git a/.github/workflows/end_to_end_tests.yml b/.github/workflows/end_to_end_tests.yml index e369fe2f..aa2d5764 100644 --- a/.github/workflows/end_to_end_tests.yml +++ b/.github/workflows/end_to_end_tests.yml @@ -21,6 +21,7 @@ jobs: working-directory: tests - name: Install Playwright run: npx playwright install --with-deps + working-directory: tests - name: 'Setup .env file' run: cp .env.template .env - name: Install messages dependencies diff --git a/front/package.json b/front/package.json index e2f44b30..187a10f8 100644 --- a/front/package.json +++ b/front/package.json @@ -45,6 +45,7 @@ "deep-copy-ts": "^0.5.0", "dompurify" : "^2.3.6", "easystarjs": "^0.4.4", + "fast-deep-equal": "^3.1.3", "google-protobuf": "^3.13.0", "phaser": "3.55.1", "phaser-animated-tiles": "workadventure/phaser-animated-tiles#da68bbededd605925621dd4f03bd27e69284b254", diff --git a/front/packages/iframe-api-typings/package.json b/front/packages/iframe-api-typings/package.json index fa35851c..d6874921 100644 --- a/front/packages/iframe-api-typings/package.json +++ b/front/packages/iframe-api-typings/package.json @@ -9,5 +9,8 @@ "license": "MIT", "publishConfig": { "access": "public" + }, + "dependencies": { + "rxjs": "^6.6.3" } } diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index 4b91c6da..a10cbcd9 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -178,6 +178,7 @@ export class GameScene extends DirtyScene { private localVolumeStoreUnsubscriber: Unsubscriber | undefined; private followUsersColorStoreUnsubscribe!: Unsubscriber; + private currentPlayerGroupIdStoreUnsubscribe!: Unsubscriber; private privacyShutdownStoreUnsubscribe!: Unsubscriber; private userIsJitsiDominantSpeakerStoreUnsubscriber!: Unsubscriber; private jitsiParticipantsCountStoreUnsubscriber!: Unsubscriber; @@ -222,8 +223,8 @@ export class GameScene extends DirtyScene { private loader: Loader; private lastCameraEvent: WasCameraUpdatedEvent | undefined; private firstCameraUpdateSent: boolean = false; - private showVoiceIndicatorChangeMessageSent: boolean = false; private currentPlayerGroupId?: number; + private showVoiceIndicatorChangeMessageSent: boolean = false; private jitsiDominantSpeaker: boolean = false; private jitsiParticipantsCount: number = 0; public readonly superLoad: SuperLoaderPlugin; @@ -858,6 +859,10 @@ export class GameScene extends DirtyScene { this.currentPlayerGroupId = message.groupId; }); + this.connection.groupUsersUpdateMessageStream.subscribe((message) => { + this.currentPlayerGroupId = message.groupId; + }); + /** * Triggered when we receive the JWT token to connect to Jitsi */ diff --git a/front/src/Stores/MediaStore.ts b/front/src/Stores/MediaStore.ts index a1d4faaa..534fa53b 100644 --- a/front/src/Stores/MediaStore.ts +++ b/front/src/Stores/MediaStore.ts @@ -11,6 +11,7 @@ import { peerStore } from "./PeerStore"; import { privacyShutdownStore } from "./PrivacyShutdownStore"; import { MediaStreamConstraintsError } from "./Errors/MediaStreamConstraintsError"; import { SoundMeter } from "../Phaser/Components/SoundMeter"; +import deepEqual from "fast-deep-equal"; /** * A store that contains the camera state requested by the user (on or off). @@ -314,10 +315,10 @@ export const mediaStreamConstraintsStore = derived( currentAudioConstraint = false; } - // Let's make the changes only if the new value is different from the old one. + // Let's make the changes only if the new value is different from the old one.tile if ( - previousComputedVideoConstraint != currentVideoConstraint || - previousComputedAudioConstraint != currentAudioConstraint + !deepEqual(previousComputedVideoConstraint, currentVideoConstraint) || + !deepEqual(previousComputedAudioConstraint, currentAudioConstraint) ) { previousComputedVideoConstraint = currentVideoConstraint; previousComputedAudioConstraint = currentAudioConstraint; diff --git a/pusher/src/Controller/AuthenticateController.ts b/pusher/src/Controller/AuthenticateController.ts index 29920405..04ec76fd 100644 --- a/pusher/src/Controller/AuthenticateController.ts +++ b/pusher/src/Controller/AuthenticateController.ts @@ -1,11 +1,12 @@ import { v4 } from "uuid"; import { BaseHttpController } from "./BaseHttpController"; -import { adminApi, FetchMemberDataByUuidResponse } from "../Services/AdminApi"; +import { adminApi } 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 { RegisterData } from "../Messages/JsonMessages/RegisterData"; +import { adminService } from "../Services/AdminService"; export interface TokenInterface { userUuid: string; @@ -166,10 +167,11 @@ export class AuthenticateController extends BaseHttpController { //Get user data from Admin Back Office //This is very important to create User Local in LocalStorage in WorkAdventure - const resUserData = await this.getUserByUserIdentifier( + const resUserData = await adminService.fetchMemberDataByUuid( authTokenData.identifier, playUri as string, - IPAddress + IPAddress, + [] ); if (authTokenData.accessToken == undefined) { @@ -221,7 +223,7 @@ export class AuthenticateController extends BaseHttpController { //Get user data from Admin Back Office //This is very important to create User Local in LocalStorage in WorkAdventure - const data = await this.getUserByUserIdentifier(sub, playUri as string, IPAddress); + const data = await adminService.fetchMemberDataByUuid(sub, playUri as string, IPAddress, []); return res.json({ ...data, authToken, username: userInfo?.username, locale: userInfo?.locale, userUuid : sub }); } catch (e) { @@ -430,34 +432,4 @@ export class AuthenticateController extends BaseHttpController { } }); } - - /** - * - * @param email - * @param playUri - * @param IPAddress - * @return FetchMemberDataByUuidResponse|object - * @private - */ - private async getUserByUserIdentifier( - email: string, - playUri: string, - IPAddress: string - ): Promise { - let data: FetchMemberDataByUuidResponse = { - email: email, - userUuid: email, - tags: [], - messages: [], - visitCardUrl: null, - textures: [], - userRoomToken: undefined, - }; - try { - data = await adminApi.fetchMemberDataByUuid(email, playUri, IPAddress, []); - } catch (err) { - console.error("openIDCallback => fetchMemberDataByUuid", err); - } - return data; - } } diff --git a/pusher/src/Controller/MapController.ts b/pusher/src/Controller/MapController.ts index 1fe4cc8b..ff1ba9ad 100644 --- a/pusher/src/Controller/MapController.ts +++ b/pusher/src/Controller/MapController.ts @@ -160,12 +160,12 @@ export class MapController extends BaseHttpController { } } } - const mapDetails = isMapDetailsData.safeParse( + const mapDetails = isMapDetailsData.parse( await adminApi.fetchMapDetails(query.playUri as string, userId) ); - if (mapDetails.success && DISABLE_ANONYMOUS) { - mapDetails.data.authenticationMandatory = true; + if (DISABLE_ANONYMOUS) { + mapDetails.authenticationMandatory = true; } res.json(mapDetails); diff --git a/pusher/src/Services/AdminApi.ts b/pusher/src/Services/AdminApi.ts index 75ac002c..5e7fff37 100644 --- a/pusher/src/Services/AdminApi.ts +++ b/pusher/src/Services/AdminApi.ts @@ -6,6 +6,7 @@ import { AdminApiData, isAdminApiData } from "../Messages/JsonMessages/AdminApiD import { z } from "zod"; import { isWokaDetail } from "../Messages/JsonMessages/PlayerTextures"; import qs from "qs"; +import { AdminInterface } from "./AdminInterface"; export interface AdminBannedData { is_banned: boolean; @@ -25,7 +26,7 @@ export const isFetchMemberDataByUuidResponse = z.object({ export type FetchMemberDataByUuidResponse = z.infer; -class AdminApi { +class AdminApi implements AdminInterface { /** * @var playUri: is url of the room * @var userId: can to be undefined or email or uuid @@ -65,7 +66,7 @@ class AdminApi { } async fetchMemberDataByUuid( - userIdentifier: string | null, + userIdentifier: string, playUri: string, ipAddress: string, characterLayers: string[] diff --git a/pusher/src/Services/AdminInterface.ts b/pusher/src/Services/AdminInterface.ts new file mode 100644 index 00000000..66121316 --- /dev/null +++ b/pusher/src/Services/AdminInterface.ts @@ -0,0 +1,10 @@ +import { FetchMemberDataByUuidResponse } from "./AdminApi"; + +export interface AdminInterface { + fetchMemberDataByUuid( + userIdentifier: string, + playUri: string, + ipAddress: string, + characterLayers: string[] + ): Promise; +} diff --git a/pusher/src/Services/AdminService.ts b/pusher/src/Services/AdminService.ts new file mode 100644 index 00000000..8d21a105 --- /dev/null +++ b/pusher/src/Services/AdminService.ts @@ -0,0 +1,5 @@ +import { ADMIN_API_URL } from "../Enum/EnvironmentVariable"; +import { adminApi } from "./AdminApi"; +import { localAdmin } from "./LocalAdmin"; + +export const adminService = ADMIN_API_URL ? adminApi : localAdmin; diff --git a/pusher/src/Services/LocalAdmin.ts b/pusher/src/Services/LocalAdmin.ts new file mode 100644 index 00000000..4e2c41a0 --- /dev/null +++ b/pusher/src/Services/LocalAdmin.ts @@ -0,0 +1,29 @@ +import { FetchMemberDataByUuidResponse } from "./AdminApi"; +import { AdminInterface } from "./AdminInterface"; + +/** + * A local class mocking a real admin if no admin is configured. + */ +class LocalAdmin implements AdminInterface { + fetchMemberDataByUuid( + userIdentifier: string, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + playUri: string, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + ipAddress: string, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + characterLayers: string[] + ): Promise { + return Promise.resolve({ + email: userIdentifier, + userUuid: userIdentifier, + tags: [], + messages: [], + visitCardUrl: null, + textures: [], + userRoomToken: undefined, + }); + } +} + +export const localAdmin = new LocalAdmin(); diff --git a/pusher/src/Services/SocketManager.ts b/pusher/src/Services/SocketManager.ts index e31183bc..f26ecf9c 100644 --- a/pusher/src/Services/SocketManager.ts +++ b/pusher/src/Services/SocketManager.ts @@ -121,7 +121,13 @@ export class SocketManager implements ZoneEventListener { } }) .on("end", () => { - console.warn("Admin connection lost to back server"); + console.warn( + "Admin connection lost to back server '" + + apiClient.getChannel().getTarget() + + "' for room '" + + roomId + + "'" + ); // Let's close the front connection if the back connection is closed. This way, we can retry connecting from the start. if (!client.disconnecting) { this.closeWebsocketConnection(client, 1011, "Admin Connection lost to back server"); @@ -129,7 +135,14 @@ export class SocketManager implements ZoneEventListener { console.log("A user left"); }) .on("error", (err: Error) => { - console.error("Error in connection to back server:", err); + console.error( + "Error in connection to back server '" + + apiClient.getChannel().getTarget() + + "' for room '" + + roomId + + "':", + err + ); if (!client.disconnecting) { this.closeWebsocketConnection(client, 1011, "Error while connecting to back server"); } @@ -186,7 +199,7 @@ export class SocketManager implements ZoneEventListener { joinRoomMessage.addCharacterlayer(characterLayerMessage); } - console.log("Calling joinRoom"); + console.log("Calling joinRoom '" + client.roomId + "'"); const apiClient = await apiClientRepository.getClient(client.roomId); const streamToPusher = apiClient.joinRoom(); clientEventsEmitter.emitClientJoin(client.userUuid, client.roomId); @@ -214,7 +227,13 @@ export class SocketManager implements ZoneEventListener { } }) .on("end", () => { - console.warn("Connection lost to back server"); + console.warn( + "Connection lost to back server '" + + apiClient.getChannel().getTarget() + + "' for room '" + + client.roomId + + "'" + ); // Let's close the front connection if the back connection is closed. This way, we can retry connecting from the start. if (!client.disconnecting) { this.closeWebsocketConnection(client, 1011, "Connection lost to back server"); @@ -222,7 +241,14 @@ export class SocketManager implements ZoneEventListener { console.log("A user left"); }) .on("error", (err: Error) => { - console.error("Error in connection to back server:", err); + console.error( + "Error in connection to back server '" + + apiClient.getChannel().getTarget() + + "' for room '" + + client.roomId + + "':", + err + ); if (!client.disconnecting) { this.closeWebsocketConnection(client, 1011, "Error while connecting to back server"); } diff --git a/tests/package-lock.json b/tests/package-lock.json index 80220130..287f6dd9 100644 --- a/tests/package-lock.json +++ b/tests/package-lock.json @@ -5,7 +5,7 @@ "packages": { "": { "devDependencies": { - "@playwright/test": "^1.20.0", + "@playwright/test": "~1.21.0", "@types/dockerode": "^3.3.0", "axios": "^0.24.0", "dockerode": "^3.3.1", @@ -854,9 +854,9 @@ } }, "node_modules/@playwright/test": { - "version": "1.20.0", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.20.0.tgz", - "integrity": "sha512-UpI5HTcgNLckR0kqXqwNvbcIXtRaDxk+hnO0OBwPSjfbBjRfRgAJ2ClA/b30C5E3UW5dJa17zhsy2qrk66l5cg==", + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.21.0.tgz", + "integrity": "sha512-jvgN3ZeAG6rw85z4u9Rc4uyj6qIaYlq2xrOtS7J2+CDYhzKOttab9ix9ELcvBOCHuQ6wgTfxfJYdh6DRZmQ9hg==", "dev": true, "dependencies": { "@babel/code-frame": "7.16.7", @@ -882,13 +882,13 @@ "debug": "4.3.3", "expect": "27.2.5", "jest-matcher-utils": "27.2.5", - "json5": "2.2.0", + "json5": "2.2.1", "mime": "3.0.0", "minimatch": "3.0.4", "ms": "2.1.3", "open": "8.4.0", "pirates": "4.0.4", - "playwright-core": "1.20.0", + "playwright-core": "1.21.0", "rimraf": "3.0.2", "source-map-support": "0.4.18", "stack-utils": "2.0.5", @@ -1007,9 +1007,9 @@ "dev": true }, "node_modules/@types/yauzl": { - "version": "2.9.2", - "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.9.2.tgz", - "integrity": "sha512-8uALY5LTvSuHgloDVUvWP3pIauILm+8/0pDMokuDYIoNsOkSwd5AiHBTSEJjKTDcZr5z8UpgOWZkxBF4iJftoA==", + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-Cn6WYCm0tXv8p6k+A8PvbDG763EDpBoTzHdA+Q/MF6H3sapGjCm9NzoaJncJS9tUKSuCoDs9XHxYYsQDgxR6kw==", "dev": true, "optional": true, "dependencies": { @@ -2071,13 +2071,10 @@ } }, "node_modules/json5": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz", - "integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", + "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", "dev": true, - "dependencies": { - "minimist": "^1.2.5" - }, "bin": { "json5": "lib/cli.js" }, @@ -2279,9 +2276,9 @@ } }, "node_modules/playwright-core": { - "version": "1.20.0", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.20.0.tgz", - "integrity": "sha512-d25IRcdooS278Cijlp8J8A5fLQZ+/aY3dKRJvgX5yjXA69N0huIUdnh3xXSgn+LsQ9DCNmB7Ngof3eY630jgdA==", + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.21.0.tgz", + "integrity": "sha512-yDGVs9qaaW6WiefgR7wH1CGt9D6D/X4U3jNpIzH0FjjrrWLCOYQo78Tu3SkW8X+/kWlBpj49iWf3QNSxhYc12Q==", "dev": true, "dependencies": { "colors": "1.4.0", @@ -3340,9 +3337,9 @@ } }, "@playwright/test": { - "version": "1.20.0", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.20.0.tgz", - "integrity": "sha512-UpI5HTcgNLckR0kqXqwNvbcIXtRaDxk+hnO0OBwPSjfbBjRfRgAJ2ClA/b30C5E3UW5dJa17zhsy2qrk66l5cg==", + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.21.0.tgz", + "integrity": "sha512-jvgN3ZeAG6rw85z4u9Rc4uyj6qIaYlq2xrOtS7J2+CDYhzKOttab9ix9ELcvBOCHuQ6wgTfxfJYdh6DRZmQ9hg==", "dev": true, "requires": { "@babel/code-frame": "7.16.7", @@ -3368,13 +3365,13 @@ "debug": "4.3.3", "expect": "27.2.5", "jest-matcher-utils": "27.2.5", - "json5": "2.2.0", + "json5": "2.2.1", "mime": "3.0.0", "minimatch": "3.0.4", "ms": "2.1.3", "open": "8.4.0", "pirates": "4.0.4", - "playwright-core": "1.20.0", + "playwright-core": "1.21.0", "rimraf": "3.0.2", "source-map-support": "0.4.18", "stack-utils": "2.0.5", @@ -3489,9 +3486,9 @@ "dev": true }, "@types/yauzl": { - "version": "2.9.2", - "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.9.2.tgz", - "integrity": "sha512-8uALY5LTvSuHgloDVUvWP3pIauILm+8/0pDMokuDYIoNsOkSwd5AiHBTSEJjKTDcZr5z8UpgOWZkxBF4iJftoA==", + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-Cn6WYCm0tXv8p6k+A8PvbDG763EDpBoTzHdA+Q/MF6H3sapGjCm9NzoaJncJS9tUKSuCoDs9XHxYYsQDgxR6kw==", "dev": true, "optional": true, "requires": { @@ -4269,13 +4266,10 @@ "dev": true }, "json5": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz", - "integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==", - "dev": true, - "requires": { - "minimist": "^1.2.5" - } + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", + "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", + "dev": true }, "micromatch": { "version": "4.0.4", @@ -4425,9 +4419,9 @@ } }, "playwright-core": { - "version": "1.20.0", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.20.0.tgz", - "integrity": "sha512-d25IRcdooS278Cijlp8J8A5fLQZ+/aY3dKRJvgX5yjXA69N0huIUdnh3xXSgn+LsQ9DCNmB7Ngof3eY630jgdA==", + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.21.0.tgz", + "integrity": "sha512-yDGVs9qaaW6WiefgR7wH1CGt9D6D/X4U3jNpIzH0FjjrrWLCOYQo78Tu3SkW8X+/kWlBpj49iWf3QNSxhYc12Q==", "dev": true, "requires": { "colors": "1.4.0", diff --git a/tests/package.json b/tests/package.json index 1699c1fa..25e8e101 100644 --- a/tests/package.json +++ b/tests/package.json @@ -1,6 +1,6 @@ { "devDependencies": { - "@playwright/test": "^1.20.0", + "@playwright/test": "~1.21.0", "@types/dockerode": "^3.3.0", "axios": "^0.24.0", "dockerode": "^3.3.1", diff --git a/uploader/tsconfig.json b/uploader/tsconfig.json index 22bfef91..5f6d4c8e 100644 --- a/uploader/tsconfig.json +++ b/uploader/tsconfig.json @@ -40,17 +40,17 @@ /* Module Resolution Options */ "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ //"baseUrl": ".", /* Base directory to resolve non-absolute module names. */ - "paths": { - "_Controller/*": [ - "src/Controller/*" - ], - "_Model/*": [ - "src/Model/*" - ], - "_Enum/*": [ - "src/Enum/*" - ] - }, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ + // "paths": { + // "_Controller/*": [ + // "src/Controller/*" + // ], + // "_Model/*": [ + // "src/Model/*" + // ], + // "_Enum/*": [ + // "src/Enum/*" + // ] + // }, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ // "typeRoots": [], /* List of folders to include type definitions from. */ // "types": [], /* Type declaration files to be included in compilation. */