From 2d8997c6d7bbfc8a96afbe2c7ba21f72a39da6d9 Mon Sep 17 00:00:00 2001 From: TabascoEye Date: Tue, 11 May 2021 17:38:28 +0200 Subject: [PATCH 01/18] turning noise suppresion back on turning AGC off was a good idea, disabling noise suppresion with it was not. => should all end up in the "settings" menu in the end --- front/src/WebRtc/MediaManager.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/front/src/WebRtc/MediaManager.ts b/front/src/WebRtc/MediaManager.ts index 85060a86..2bed2c68 100644 --- a/front/src/WebRtc/MediaManager.ts +++ b/front/src/WebRtc/MediaManager.ts @@ -20,7 +20,7 @@ const audioConstraint: boolean|MediaTrackConstraints = { //TODO: make these values configurable in the game settings menu and store them in localstorage autoGainControl: false, echoCancellation: true, - noiseSuppression: false + noiseSuppression: true }; export type UpdatedLocalStreamCallback = (media: MediaStream|null) => void; From 80a698d89127b7ddf44a5117d11e9ccb495375eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Thu, 3 Jun 2021 14:53:58 +0200 Subject: [PATCH 02/18] Preventing crash on iOS On iOS, audio elements must be triggered by a user gesture. We are catching exceptions thrown in order not to crash. In the future, we need to find another way to play sounds. --- front/src/WebRtc/MediaManager.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/front/src/WebRtc/MediaManager.ts b/front/src/WebRtc/MediaManager.ts index 7b527962..7cdf1de0 100644 --- a/front/src/WebRtc/MediaManager.ts +++ b/front/src/WebRtc/MediaManager.ts @@ -129,7 +129,11 @@ export class MediaManager { } addActiveVideo(user: UserSimplePeerInterface, userName: string = ""){ - this.webrtcInAudio.play(); + try { + this.webrtcInAudio.play(); + } catch(e) { + console.error(e); + } const userId = ''+user.userId userName = userName.toUpperCase(); @@ -278,7 +282,11 @@ export class MediaManager { } playWebrtcOutSound(): void { - this.webrtcOutAudio.play(); + try { + this.webrtcOutAudio.play(); + } catch(e) { + console.error(e); + } } isConnecting(userId: string): void { From 50e994c674213e1f0cdc9956cc8c725fd0f504e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Thu, 3 Jun 2021 15:40:44 +0200 Subject: [PATCH 03/18] Attempt to switch bubble sound playing into Phaser In iOS, we cannot trigger a playing sound if it does not start from a user gesture. This is a huge bummer for a notification sound! This is an attempt to switch sound playing to Phaser, which is using under the hood the WebAudio API. This might solve the issue. --- front/dist/index.tmpl.html | 6 ------ front/src/Phaser/Game/GameScene.ts | 22 ++++++++++++++++++++++ front/src/WebRtc/MediaManager.ts | 20 -------------------- front/src/WebRtc/SimplePeer.ts | 1 - 4 files changed, 22 insertions(+), 27 deletions(-) diff --git a/front/dist/index.tmpl.html b/front/dist/index.tmpl.html index 7f541145..aa63229f 100644 --- a/front/dist/index.tmpl.html +++ b/front/dist/index.tmpl.html @@ -91,12 +91,6 @@
- - diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index 2de7747f..b4f79401 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -160,6 +160,7 @@ export class GameScene extends DirtyScene implements CenterListener { private createPromise: Promise; private createPromiseResolve!: (value?: void | PromiseLike) => void; private iframeSubscriptionList! : Array; + private peerStoreUnsubscribe!: () => void; MapUrlFile: string; RoomId: string; instance: string; @@ -228,6 +229,11 @@ export class GameScene extends DirtyScene implements CenterListener { this.load.image(joystickBaseKey, joystickBaseImg); this.load.image(joystickThumbKey, joystickThumbImg); } + this.load.audio('audio-webrtc-in', '/resources/objects/webrtc-in.mp3'); + this.load.audio('audio-webrtc-out', '/resources/objects/webrtc-out.mp3'); + //this.load.audio('audio-report-message', '/resources/objects/report-message.mp3'); + this.sound.pauseOnBlur = false; + this.load.on(FILE_LOAD_ERROR, (file: {src: string}) => { // If we happen to be in HTTP and we are trying to load a URL in HTTPS only... (this happens only in dev environments) if (window.location.protocol === 'http:' && file.src === this.MapUrlFile && file.src.startsWith('http:') && this.originalMapUrl === undefined) { @@ -519,6 +525,21 @@ export class GameScene extends DirtyScene implements CenterListener { } this.emoteManager = new EmoteManager(this); + + let oldPeerNumber = 0; + this.peerStoreUnsubscribe = peerStore.subscribe((peers) => { + const newPeerNumber = peers.size; + if (newPeerNumber > oldPeerNumber) { + this.sound.play('audio-webrtc-in', { + volume: 0.2 + }); + } else if (newPeerNumber < oldPeerNumber) { + this.sound.play('audio-webrtc-out', { + volume: 0.2 + }); + } + oldPeerNumber = newPeerNumber; + }); } /** @@ -960,6 +981,7 @@ ${escapedMessage} this.userInputManager.destroy(); this.pinchManager?.destroy(); this.emoteManager.destroy(); + this.peerStoreUnsubscribe(); mediaManager.hideGameOverlay(); diff --git a/front/src/WebRtc/MediaManager.ts b/front/src/WebRtc/MediaManager.ts index 7cdf1de0..7b537920 100644 --- a/front/src/WebRtc/MediaManager.ts +++ b/front/src/WebRtc/MediaManager.ts @@ -23,10 +23,8 @@ export type HelpCameraSettingsCallBack = () => void; export class MediaManager { private remoteVideo: Map = new Map(); - webrtcInAudio: HTMLAudioElement; //FIX ME SOUNDMETER: check stalability of sound meter calculation //mySoundMeterElement: HTMLDivElement; - private webrtcOutAudio: HTMLAudioElement; startScreenSharingCallBacks : Set = new Set(); stopScreenSharingCallBacks : Set = new Set(); showReportModalCallBacks : Set = new Set(); @@ -44,11 +42,6 @@ export class MediaManager { constructor() { - this.webrtcInAudio = HtmlUtils.getElementByIdOrFail('audio-webrtc-in'); - this.webrtcOutAudio = HtmlUtils.getElementByIdOrFail('audio-webrtc-out'); - this.webrtcInAudio.volume = 0.2; - this.webrtcOutAudio.volume = 0.2; - this.pingCameraStatus(); //FIX ME SOUNDMETER: check stability of sound meter calculation @@ -129,11 +122,6 @@ export class MediaManager { } addActiveVideo(user: UserSimplePeerInterface, userName: string = ""){ - try { - this.webrtcInAudio.play(); - } catch(e) { - console.error(e); - } const userId = ''+user.userId userName = userName.toUpperCase(); @@ -281,14 +269,6 @@ export class MediaManager { this.removeActiveVideo(this.getScreenSharingId(userId)) } - playWebrtcOutSound(): void { - try { - this.webrtcOutAudio.play(); - } catch(e) { - console.error(e); - } - } - isConnecting(userId: string): void { const connectingSpinnerDiv = this.getSpinner(userId); if (connectingSpinnerDiv === null) { diff --git a/front/src/WebRtc/SimplePeer.ts b/front/src/WebRtc/SimplePeer.ts index 9193f18b..2a502bab 100644 --- a/front/src/WebRtc/SimplePeer.ts +++ b/front/src/WebRtc/SimplePeer.ts @@ -246,7 +246,6 @@ export class SimplePeer { * This is triggered twice. Once by the server, and once by a remote client disconnecting */ private closeConnection(userId : number) { - mediaManager.playWebrtcOutSound(); try { const peer = this.PeerConnectionArray.get(userId); if (peer === undefined) { From 46666d17dcbe8ef51e8541fd102eeba7074c607c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Thu, 3 Jun 2021 15:56:06 +0200 Subject: [PATCH 04/18] Making sure the report audio sound does not break the application on iOS --- front/src/Administration/TypeMessage.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/front/src/Administration/TypeMessage.ts b/front/src/Administration/TypeMessage.ts index 07f330fd..d735ba58 100644 --- a/front/src/Administration/TypeMessage.ts +++ b/front/src/Administration/TypeMessage.ts @@ -44,7 +44,13 @@ export class TypeMessageExt implements TypeMessageInterface{ mainSectionDiv.appendChild(div); const reportMessageAudio = HtmlUtils.getElementByIdOrFail('report-message'); - reportMessageAudio.play(); + // FIXME: this will fail on iOS + // We should move the sound playing into the GameScene and listen to the event of a report using a store + try { + reportMessageAudio.play(); + } catch (e) { + console.error(e); + } this.nbSecond = this.maxNbSecond; setTimeout((c) => { From fffd36267de55498892d5c1ed4903ffda51ee655 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?gr=C3=A9goire=20parant?= Date: Thu, 3 Jun 2021 17:22:31 +0200 Subject: [PATCH 05/18] Hot Custom Characters Scene (#1113) --- front/src/Phaser/Login/SelectCharacterScene.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/front/src/Phaser/Login/SelectCharacterScene.ts b/front/src/Phaser/Login/SelectCharacterScene.ts index 0cfbbdef..4eed17fe 100644 --- a/front/src/Phaser/Login/SelectCharacterScene.ts +++ b/front/src/Phaser/Login/SelectCharacterScene.ts @@ -244,7 +244,7 @@ export class SelectCharacterScene extends AbstractCharacterScene { update(time: number, delta: number): void { if(this.lazyloadingAttempt){ - this.createCurrentPlayer(); + this.moveUser(); this.lazyloadingAttempt = false; } } From c5f3cfe87cee53381f614dce068e3addd7246e25 Mon Sep 17 00:00:00 2001 From: kharhamel Date: Tue, 1 Jun 2021 15:35:25 +0200 Subject: [PATCH 06/18] FEATURE: clicking on another player show a contact card when possible --- CHANGELOG.md | 5 ++ back/src/Model/GameRoom.ts | 5 +- back/src/RoomManager.ts | 4 +- back/src/Services/AdminApi.ts | 22 +++++++ back/src/Services/SocketManager.ts | 18 +++++- front/src/Components/App.svelte | 5 ++ .../src/Components/VisitCard/VisitCard.svelte | 64 +++++++++++++++++++ front/src/Connexion/RoomConnection.ts | 15 ++++- front/src/Phaser/Entity/RemotePlayer.ts | 8 ++- front/src/Phaser/Game/GameScene.ts | 5 +- front/src/Stores/GameStore.ts | 4 +- messages/protos/messages.proto | 11 ++++ pusher/src/Controller/IoSocketController.ts | 10 ++- pusher/src/Services/SocketManager.ts | 16 ++++- 14 files changed, 183 insertions(+), 9 deletions(-) create mode 100644 back/src/Services/AdminApi.ts create mode 100644 front/src/Components/VisitCard/VisitCard.svelte diff --git a/CHANGELOG.md b/CHANGELOG.md index b9b0a926..1dd2c973 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,11 @@ - We now create a GameObject.Text instead of GameObject.BitmapText - now use the 'Press Start 2P' font family and added an outline - As a result, we can now allow non-standard letters like french accents or chinese characters! + +- Added the contact card feature. (@Kharhamel) + - Click on another player to see its contact info. + - Premium-only feature unfortunately. I need to find a way to make it available for all. + - If no contact data is found (either because the user is anonymous or because no admin backend), display an error card. - Mobile support has been improved - WorkAdventure automatically sets the zoom level based on the viewport size to ensure a sensible size of the map is visible, whatever the viewport used diff --git a/back/src/Model/GameRoom.ts b/back/src/Model/GameRoom.ts index be3e5cd3..22ea8ca5 100644 --- a/back/src/Model/GameRoom.ts +++ b/back/src/Model/GameRoom.ts @@ -89,7 +89,10 @@ export class GameRoom { public getUserByUuid(uuid: string): User|undefined { return this.usersByUuid.get(uuid); } - + public getUserById(id: number): User|undefined { + return this.users.get(id); + } + public join(socket : UserSocket, joinRoomMessage: JoinRoomMessage): User { const positionMessage = joinRoomMessage.getPositionmessage(); if (positionMessage === undefined) { diff --git a/back/src/RoomManager.ts b/back/src/RoomManager.ts index 19266687..a0f983e0 100644 --- a/back/src/RoomManager.ts +++ b/back/src/RoomManager.ts @@ -11,7 +11,7 @@ import { JoinRoomMessage, PlayGlobalMessage, PusherToBackMessage, - QueryJitsiJwtMessage, RefreshRoomPromptMessage, + QueryJitsiJwtMessage, RefreshRoomPromptMessage, RequestVisitCardMessage, ServerToAdminClientMessage, ServerToClientMessage, SilentMessage, @@ -74,6 +74,8 @@ const roomManager: IRoomManagerServer = { socketManager.handleQueryJitsiJwtMessage(user, message.getQueryjitsijwtmessage() as QueryJitsiJwtMessage); } else if (message.hasEmotepromptmessage()){ socketManager.handleEmoteEventMessage(room, user, message.getEmotepromptmessage() as EmotePromptMessage); + } else if (message.hasRequestvisitcardmessage()) { + socketManager.handleRequestVisitCardMessage(room, user, message.getRequestvisitcardmessage() as RequestVisitCardMessage); }else if (message.hasSendusermessage()) { const sendUserMessage = message.getSendusermessage(); if(sendUserMessage !== undefined) { diff --git a/back/src/Services/AdminApi.ts b/back/src/Services/AdminApi.ts new file mode 100644 index 00000000..09b092bf --- /dev/null +++ b/back/src/Services/AdminApi.ts @@ -0,0 +1,22 @@ +import {ADMIN_API_TOKEN, ADMIN_API_URL} from "../Enum/EnvironmentVariable"; +import Axios from "axios"; + + +class AdminApi { + + fetchVisitCardUrl(membershipUuid: string): Promise { + if (ADMIN_API_URL) { + return Axios.get(ADMIN_API_URL + '/api/membership/'+membershipUuid, + {headers: {"Authorization": `${ADMIN_API_TOKEN}`}} + ).then((res) => { + return res.data; + }).catch(() => { + return 'INVALID'; + }); + } else { + return Promise.resolve('INVALID') + } + } +} + +export const adminApi = new AdminApi(); diff --git a/back/src/Services/SocketManager.ts b/back/src/Services/SocketManager.ts index c58b3d9f..f8fe7cd3 100644 --- a/back/src/Services/SocketManager.ts +++ b/back/src/Services/SocketManager.ts @@ -27,7 +27,7 @@ import { WorldFullWarningMessage, UserLeftZoneMessage, EmoteEventMessage, - BanUserMessage, RefreshRoomMessage, EmotePromptMessage, + BanUserMessage, RefreshRoomMessage, EmotePromptMessage, RequestVisitCardMessage, VisitCardMessage, } from "../Messages/generated/messages_pb"; import {User, UserSocket} from "../Model/User"; import {ProtobufUtils} from "../Model/Websocket/ProtobufUtils"; @@ -51,6 +51,7 @@ import {Zone} from "_Model/Zone"; import Debug from "debug"; import {Admin} from "_Model/Admin"; import crypto from "crypto"; +import {adminApi} from "./AdminApi"; const debug = Debug('sockermanager'); @@ -769,6 +770,21 @@ export class SocketManager { emoteEventMessage.setActoruserid(user.id); room.emitEmoteEvent(user, emoteEventMessage); } + + async handleRequestVisitCardMessage(room: GameRoom, user: User, requestvisitcardmessage: RequestVisitCardMessage): Promise { + const targetUser = room.getUserById(requestvisitcardmessage.getTargetuserid()); + if (!targetUser) { + throw 'Could not find user for id '+requestvisitcardmessage.getTargetuserid(); + } + const url = await adminApi.fetchVisitCardUrl(targetUser.uuid); + + const visitCardMessage = new VisitCardMessage(); + visitCardMessage.setUrl(url); + const clientMessage = new ServerToClientMessage(); + clientMessage.setVisitcardmessage(visitCardMessage); + + user.socket.write(clientMessage); + } } export const socketManager = new SocketManager(); diff --git a/front/src/Components/App.svelte b/front/src/Components/App.svelte index 97cb5071..11d125b1 100644 --- a/front/src/Components/App.svelte +++ b/front/src/Components/App.svelte @@ -13,6 +13,8 @@ import LoginScene from "./Login/LoginScene.svelte"; import {loginSceneVisibleStore} from "../Stores/LoginSceneStore"; import EnableCameraScene from "./EnableCamera/EnableCameraScene.svelte"; + import VisitCard from "./VisitCard/VisitCard.svelte"; + import {requestVisitCardsStore} from "../Stores/GameStore"; import {Game} from "../Phaser/Game/Game"; import {helpCameraSettingsVisibleStore} from "../Stores/HelpCameraSettingsStore"; @@ -73,4 +75,7 @@ {/if} + {#if $requestVisitCardsStore} + + {/if} diff --git a/front/src/Components/VisitCard/VisitCard.svelte b/front/src/Components/VisitCard/VisitCard.svelte new file mode 100644 index 00000000..8c3706b0 --- /dev/null +++ b/front/src/Components/VisitCard/VisitCard.svelte @@ -0,0 +1,64 @@ + + + + + +
+ {#if visitCardUrl === 'INVALID'} +
+
+

Sorry

+

This user doesn't have a contact card.

+
+ +
+

Maybe he is offline, or this feature is deactivated.

+
+
+ {:else} + + {/if} +
+ +
+ +
diff --git a/front/src/Connexion/RoomConnection.ts b/front/src/Connexion/RoomConnection.ts index a64e5bfd..ae9a1986 100644 --- a/front/src/Connexion/RoomConnection.ts +++ b/front/src/Connexion/RoomConnection.ts @@ -30,7 +30,7 @@ import { EmoteEventMessage, EmotePromptMessage, SendUserMessage, - BanUserMessage + BanUserMessage, RequestVisitCardMessage } from "../Messages/generated/messages_pb" import type {UserSimplePeerInterface} from "../WebRtc/SimplePeer"; @@ -50,6 +50,7 @@ import {worldFullMessageStream} from "./WorldFullMessageStream"; import {worldFullWarningStream} from "./WorldFullWarningStream"; import {connectionManager} from "./ConnectionManager"; import {emoteEventStream} from "./EmoteEventStream"; +import {requestVisitCardsStore} from "../Stores/GameStore"; const manualPingDelay = 20000; @@ -203,6 +204,8 @@ export class RoomConnection implements RoomConnection { adminMessagesService.onSendusermessage(message.getBanusermessage() as BanUserMessage); } else if (message.hasWorldfullwarningmessage()) { worldFullWarningStream.onMessage(); + } else if (message.hasVisitcardmessage()) { + requestVisitCardsStore.set(message?.getVisitcardmessage()?.getUrl() as unknown as string); } else if (message.hasRefreshroommessage()) { //todo: implement a way to notify the user the room was refreshed. } else { @@ -617,4 +620,14 @@ export class RoomConnection implements RoomConnection { this.socket.send(clientToServerMessage.serializeBinary().buffer); } + + public requestVisitCardUrl(targetUserId: number): void { + const message = new RequestVisitCardMessage(); + message.setTargetuserid(targetUserId); + + const clientToServerMessage = new ClientToServerMessage(); + clientToServerMessage.setRequestvisitcardmessage(message); + + this.socket.send(clientToServerMessage.serializeBinary().buffer); + } } diff --git a/front/src/Phaser/Entity/RemotePlayer.ts b/front/src/Phaser/Entity/RemotePlayer.ts index 4e00f102..c2384357 100644 --- a/front/src/Phaser/Entity/RemotePlayer.ts +++ b/front/src/Phaser/Entity/RemotePlayer.ts @@ -3,6 +3,8 @@ import type {PointInterface} from "../../Connexion/ConnexionModels"; import {Character} from "../Entity/Character"; import type {PlayerAnimationDirections} from "../Player/Animation"; +export const playerClickedEvent = 'playerClickedEvent'; + /** * Class representing the sprite of a remote player (a player that plays on another computer) */ @@ -25,6 +27,10 @@ export class RemotePlayer extends Character { //set data this.userId = userId; + + this.on('pointerdown', () => { + this.emit(playerClickedEvent, this.userId); + }) } updatePosition(position: PointInterface): void { @@ -40,6 +46,6 @@ export class RemotePlayer extends Character { } isClickable(): boolean { - return false; //todo: make remote players clickable if they are logged in. + return true; //todo: make remote players clickable if they are logged in. } } diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index b4f79401..e6e40df6 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -29,7 +29,7 @@ import type {AddPlayerInterface} from "./AddPlayerInterface"; import {PlayerAnimationDirections} from "../Player/Animation"; import {PlayerMovement} from "./PlayerMovement"; import {PlayersPositionInterpolator} from "./PlayersPositionInterpolator"; -import {RemotePlayer} from "../Entity/RemotePlayer"; +import {playerClickedEvent, RemotePlayer} from "../Entity/RemotePlayer"; import {Queue} from 'queue-typescript'; import {SimplePeer, UserSimplePeerInterface} from "../../WebRtc/SimplePeer"; import {ReconnectingSceneName} from "../Reconnecting/ReconnectingScene"; @@ -1379,6 +1379,9 @@ ${escapedMessage} addPlayerData.companion, addPlayerData.companion !== null ? lazyLoadCompanionResource(this.load, addPlayerData.companion) : undefined ); + player.on(playerClickedEvent, (userID:number) => { + this.connection?.requestVisitCardUrl(userID); + }) this.MapPlayers.add(player); this.MapPlayersByKey.set(player.userId, player); player.updatePosition(addPlayerData.position); diff --git a/front/src/Stores/GameStore.ts b/front/src/Stores/GameStore.ts index ee975f23..8899aa12 100644 --- a/front/src/Stores/GameStore.ts +++ b/front/src/Stores/GameStore.ts @@ -1,3 +1,5 @@ -import { derived, writable, Writable } from "svelte/store"; +import { writable } from "svelte/store"; export const userMovingStore = writable(false); + +export const requestVisitCardsStore = writable(null); diff --git a/messages/protos/messages.proto b/messages/protos/messages.proto index 3a5afb57..3cf0767d 100644 --- a/messages/protos/messages.proto +++ b/messages/protos/messages.proto @@ -75,6 +75,14 @@ message EmoteEventMessage { string emote = 2; } +message RequestVisitCardMessage { + int32 targetUserId = 1; +} + +message VisitCardMessage { + string url = 1; +} + message QueryJitsiJwtMessage { string jitsiRoom = 1; string tag = 2; // FIXME: rather than reading the tag from the query, we should read it from the current map! @@ -94,6 +102,7 @@ message ClientToServerMessage { ReportPlayerMessage reportPlayerMessage = 11; QueryJitsiJwtMessage queryJitsiJwtMessage = 12; EmotePromptMessage emotePromptMessage = 13; + RequestVisitCardMessage requestVisitCardMessage = 14; } } @@ -259,6 +268,7 @@ message ServerToClientMessage { RefreshRoomMessage refreshRoomMessage = 17; WorldConnexionMessage worldConnexionMessage = 18; EmoteEventMessage emoteEventMessage = 19; + VisitCardMessage visitCardMessage = 20; } } @@ -330,6 +340,7 @@ message PusherToBackMessage { SendUserMessage sendUserMessage = 12; BanUserMessage banUserMessage = 13; EmotePromptMessage emotePromptMessage = 14; + RequestVisitCardMessage requestVisitCardMessage = 15; } } diff --git a/pusher/src/Controller/IoSocketController.ts b/pusher/src/Controller/IoSocketController.ts index 6d120f50..d79b39a3 100644 --- a/pusher/src/Controller/IoSocketController.ts +++ b/pusher/src/Controller/IoSocketController.ts @@ -13,7 +13,12 @@ import { PlayGlobalMessage, ReportPlayerMessage, EmoteEventMessage, - QueryJitsiJwtMessage, SendUserMessage, ServerToClientMessage, CompanionMessage, EmotePromptMessage + QueryJitsiJwtMessage, + SendUserMessage, + ServerToClientMessage, + CompanionMessage, + EmotePromptMessage, + RequestVisitCardMessage } from "../Messages/generated/messages_pb"; import {UserMovesMessage} from "../Messages/generated/messages_pb"; import {TemplatedApp} from "uWebSockets.js" @@ -333,6 +338,9 @@ export class IoSocketController { socketManager.handleQueryJitsiJwtMessage(client, message.getQueryjitsijwtmessage() as QueryJitsiJwtMessage); } else if (message.hasEmotepromptmessage()){ socketManager.handleEmotePromptMessage(client, message.getEmotepromptmessage() as EmotePromptMessage); + } else if (message.hasRequestvisitcardmessage()) { + + socketManager.handleRequestVisitCardMessage(client, message.getRequestvisitcardmessage() as RequestVisitCardMessage); } /* Ok is false if backpressure was built up, wait for drain */ diff --git a/pusher/src/Services/SocketManager.ts b/pusher/src/Services/SocketManager.ts index 3bf8467a..c1727a4b 100644 --- a/pusher/src/Services/SocketManager.ts +++ b/pusher/src/Services/SocketManager.ts @@ -24,7 +24,13 @@ import { AdminPusherToBackMessage, ServerToAdminClientMessage, EmoteEventMessage, - UserJoinedRoomMessage, UserLeftRoomMessage, AdminMessage, BanMessage, RefreshRoomMessage, EmotePromptMessage + UserJoinedRoomMessage, + UserLeftRoomMessage, + AdminMessage, + BanMessage, + RefreshRoomMessage, + EmotePromptMessage, + RequestVisitCardMessage } from "../Messages/generated/messages_pb"; import {ProtobufUtils} from "../Model/Websocket/ProtobufUtils"; import {JITSI_ISS, SECRET_JITSI_KEY} from "../Enum/EnvironmentVariable"; @@ -294,6 +300,7 @@ export class SocketManager implements ZoneEventListener { throw 'reported socket user not found'; } //TODO report user on admin application + //todo: move to back because this fail if the reported player is in another pusher. await adminApi.reportPlayer(reportedSocket.userUuid, reportPlayerMessage.getReportcomment(), client.userUuid, client.roomId.split('/')[2]) } catch (e) { console.error('An error occurred on "handleReportMessage"'); @@ -597,6 +604,13 @@ export class SocketManager implements ZoneEventListener { client.backConnection.write(pusherToBackMessage); } + + handleRequestVisitCardMessage(client: ExSocketInterface, requestVisitCardMessage: RequestVisitCardMessage) { + const pusherToBackMessage = new PusherToBackMessage(); + pusherToBackMessage.setRequestvisitcardmessage(requestVisitCardMessage); + + client.backConnection.write(pusherToBackMessage); + } } export const socketManager = new SocketManager(); From 2f98bbaa0e8fd72850d4d0ddd4c6255981c74a2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Thu, 3 Jun 2021 18:34:55 +0200 Subject: [PATCH 07/18] Adding auto deploy to develop branch --- .github/workflows/build-and-deploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-and-deploy.yml b/.github/workflows/build-and-deploy.yml index 48a7bae9..3e4b0fff 100644 --- a/.github/workflows/build-and-deploy.yml +++ b/.github/workflows/build-and-deploy.yml @@ -2,7 +2,7 @@ name: Build, push and deploy Docker image on: push: - branches: [master] + branches: [master, develop] release: types: [created] pull_request: From eec15b38bb5721eb7637217b3b0d4a361f19f9b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?gr=C3=A9goire=20parant?= Date: Thu, 3 Jun 2021 20:05:39 +0200 Subject: [PATCH 08/18] Hot Fix open & close iframe (#1115) --- front/src/WebRtc/CoWebsiteManager.ts | 21 ++++++++++++++++----- front/src/WebRtc/MediaManager.ts | 15 +++++++++++---- 2 files changed, 27 insertions(+), 9 deletions(-) diff --git a/front/src/WebRtc/CoWebsiteManager.ts b/front/src/WebRtc/CoWebsiteManager.ts index 95885a53..ef34f913 100644 --- a/front/src/WebRtc/CoWebsiteManager.ts +++ b/front/src/WebRtc/CoWebsiteManager.ts @@ -11,7 +11,7 @@ enum iframeStates { const cowebsiteDivId = 'cowebsite'; // the id of the whole container. const cowebsiteMainDomId = 'cowebsite-main'; // the id of the parent div of the iframe. const cowebsiteAsideDomId = 'cowebsite-aside'; // the id of the parent div of the iframe. -const cowebsiteCloseButtonId = 'cowebsite-close'; +export const cowebsiteCloseButtonId = 'cowebsite-close'; const cowebsiteFullScreenButtonId = 'cowebsite-fullscreen'; const cowebsiteOpenFullScreenImageId = 'cowebsite-fullscreen-open'; const cowebsiteCloseFullScreenImageId = 'cowebsite-fullscreen-close'; @@ -64,10 +64,15 @@ class CoWebsiteManager { this.initResizeListeners(); - HtmlUtils.getElementByIdOrFail(cowebsiteCloseButtonId).addEventListener('click', () => { + const buttonCloseFrame = HtmlUtils.getElementByIdOrFail(cowebsiteCloseButtonId); + buttonCloseFrame.addEventListener('click', () => { + buttonCloseFrame.blur(); this.closeCoWebsite(); }); - HtmlUtils.getElementByIdOrFail(cowebsiteFullScreenButtonId).addEventListener('click', () => { + + const buttonFullScreenFrame = HtmlUtils.getElementByIdOrFail(cowebsiteFullScreenButtonId); + buttonFullScreenFrame.addEventListener('click', () => { + buttonFullScreenFrame.blur(); this.fullscreen(); }); } @@ -152,7 +157,10 @@ class CoWebsiteManager { setTimeout(() => { this.fire(); }, animationTime) - }).catch(() => this.closeCoWebsite()); + }).catch((err) => { + console.error('Error loadCoWebsite => ', err); + this.closeCoWebsite() + }); } /** @@ -166,7 +174,10 @@ class CoWebsiteManager { setTimeout(() => { this.fire(); }, animationTime); - }).catch(() => this.closeCoWebsite()); + }).catch((err) => { + console.error('Error insertCoWebsite => ', err); + this.closeCoWebsite(); + }); } public closeCoWebsite(): Promise { diff --git a/front/src/WebRtc/MediaManager.ts b/front/src/WebRtc/MediaManager.ts index 2bed2c68..80a102a4 100644 --- a/front/src/WebRtc/MediaManager.ts +++ b/front/src/WebRtc/MediaManager.ts @@ -5,6 +5,7 @@ import {UserInputManager} from "../Phaser/UserInput/UserInputManager"; import {localUserStore} from "../Connexion/LocalUserStore"; import {UserSimplePeerInterface} from "./SimplePeer"; import {SoundMeter} from "../Phaser/Components/SoundMeter"; +import {cowebsiteCloseButtonId} from "./CoWebsiteManager"; declare const navigator:any; // eslint-disable-line @typescript-eslint/no-explicit-any @@ -208,22 +209,28 @@ export class MediaManager { const gameOverlay = HtmlUtils.getElementByIdOrFail('game-overlay'); gameOverlay.classList.add('active'); - const buttonCloseFrame = HtmlUtils.getElementByIdOrFail('cowebsite-close'); + const buttonCloseFrame = HtmlUtils.getElementByIdOrFail(cowebsiteCloseButtonId); const functionTrigger = () => { this.triggerCloseJitsiFrameButton(); } - buttonCloseFrame.removeEventListener('click', functionTrigger); + buttonCloseFrame.removeEventListener('click', () => { + buttonCloseFrame.blur(); + functionTrigger(); + }); } public hideGameOverlay(): void { const gameOverlay = HtmlUtils.getElementByIdOrFail('game-overlay'); gameOverlay.classList.remove('active'); - const buttonCloseFrame = HtmlUtils.getElementByIdOrFail('cowebsite-close'); + const buttonCloseFrame = HtmlUtils.getElementByIdOrFail(cowebsiteCloseButtonId); const functionTrigger = () => { this.triggerCloseJitsiFrameButton(); } - buttonCloseFrame.addEventListener('click', functionTrigger); + buttonCloseFrame.addEventListener('click', () => { + buttonCloseFrame.blur(); + functionTrigger(); + }); } public isGameOverlayVisible(): boolean { From 247d508d094e4c264075c93410624da9cd494ad7 Mon Sep 17 00:00:00 2001 From: GRL Date: Fri, 4 Jun 2021 10:01:08 +0200 Subject: [PATCH 09/18] create PR to test iphone compatibility --- front/src/Phaser/Login/CustomizeScene.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/front/src/Phaser/Login/CustomizeScene.ts b/front/src/Phaser/Login/CustomizeScene.ts index 3d85cdd5..cc0c7208 100644 --- a/front/src/Phaser/Login/CustomizeScene.ts +++ b/front/src/Phaser/Login/CustomizeScene.ts @@ -236,6 +236,9 @@ export class CustomizeScene extends AbstractCharacterScene { } } + + + public onResize(): void { this.moveLayers(); From b7ac3b8fada3668297e4897e5263d7f7b71aac68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Fri, 4 Jun 2021 10:07:12 +0200 Subject: [PATCH 10/18] Generating HTML link using DOM manipulation rather that string manipulation --- front/dist/resources/style/style.css | 6 +++++- front/src/WebRtc/HtmlUtils.ts | 7 ++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/front/dist/resources/style/style.css b/front/dist/resources/style/style.css index 213b00f2..f1098af4 100644 --- a/front/dist/resources/style/style.css +++ b/front/dist/resources/style/style.css @@ -381,7 +381,7 @@ body { max-height: 25%; } - + } #game { @@ -1115,6 +1115,10 @@ div.modal-report-user{ color: white; } +.discussion .messages .message p a:visited{ + color: white; +} + .discussion .send-message{ position: absolute; bottom: 45px; diff --git a/front/src/WebRtc/HtmlUtils.ts b/front/src/WebRtc/HtmlUtils.ts index 86f38216..942e553f 100644 --- a/front/src/WebRtc/HtmlUtils.ts +++ b/front/src/WebRtc/HtmlUtils.ts @@ -35,7 +35,12 @@ export class HtmlUtils { const urlRegex = /(https?:\/\/[^\s]+)/g; text = HtmlUtils.escapeHtml(text); return text.replace(urlRegex, (url: string) => { - return '' + url + ''; + const link = document.createElement('a'); + link.href = url; + link.target = "_blank"; + const text = document.createTextNode(url); + link.appendChild(text); + return link.outerHTML; }); } From f4ed527fe8b8603dbb0db0e9feb21c47e975fcc4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Thu, 3 Jun 2021 14:10:31 +0200 Subject: [PATCH 11/18] Adding a global error message This error message should be used for non fatal errors (otherwise, use the ErrorScene). It is implemented using Svelte and the new "$errorStore". Use `errorStore.addErrorMessage` to display the error popup with the message. This PR uses this error message to display a popup explaining the browser is too old for WebRTC. --- front/src/Components/App.svelte | 7 +++ front/src/Components/UI/ErrorDialog.svelte | 44 +++++++++++++++++++ front/src/Stores/ErrorStore.ts | 23 ++++++++++ front/src/Stores/Errors/BrowserTooOldError.ts | 8 ++++ front/src/Stores/MediaStore.ts | 12 ++++- 5 files changed, 93 insertions(+), 1 deletion(-) create mode 100644 front/src/Components/UI/ErrorDialog.svelte create mode 100644 front/src/Stores/ErrorStore.ts create mode 100644 front/src/Stores/Errors/BrowserTooOldError.ts diff --git a/front/src/Components/App.svelte b/front/src/Components/App.svelte index 11d125b1..a39f2dc7 100644 --- a/front/src/Components/App.svelte +++ b/front/src/Components/App.svelte @@ -9,6 +9,7 @@ import {selectCharacterSceneVisibleStore} from "../Stores/SelectCharacterStore"; import SelectCharacterScene from "./selectCharacter/SelectCharacterScene.svelte"; import {customCharacterSceneVisibleStore} from "../Stores/CustomCharacterStore"; + import {errorStore} from "../Stores/ErrorStore"; import CustomCharacterScene from "./CustomCharacterScene/CustomCharacterScene.svelte"; import LoginScene from "./Login/LoginScene.svelte"; import {loginSceneVisibleStore} from "../Stores/LoginSceneStore"; @@ -21,6 +22,7 @@ import HelpCameraSettingsPopup from "./HelpCameraSettings/HelpCameraSettingsPopup.svelte"; import AudioPlaying from "./UI/AudioPlaying.svelte"; import {soundPlayingStore} from "../Stores/SoundPlayingStore"; + import ErrorDialog from "./UI/ErrorDialog.svelte"; export let game: Game; @@ -78,4 +80,9 @@ {#if $requestVisitCardsStore} {/if} + {#if $errorStore.length > 0} +
+ +
+ {/if} diff --git a/front/src/Components/UI/ErrorDialog.svelte b/front/src/Components/UI/ErrorDialog.svelte new file mode 100644 index 00000000..5c684a92 --- /dev/null +++ b/front/src/Components/UI/ErrorDialog.svelte @@ -0,0 +1,44 @@ + + + +

Error

+
+ {#each $errorStore as error} +

{error}

+ {/each} +
+
+ +
+
+ + diff --git a/front/src/Stores/ErrorStore.ts b/front/src/Stores/ErrorStore.ts new file mode 100644 index 00000000..eaab8a13 --- /dev/null +++ b/front/src/Stores/ErrorStore.ts @@ -0,0 +1,23 @@ +import {writable} from "svelte/store"; + +/** + * A store that contains a list of error messages to be displayed. + */ +function createErrorStore() { + const { subscribe, set, update } = writable([]); + + return { + subscribe, + addErrorMessage: (e: string|Error): void => { + update((messages) => { + messages.push(e.toString()); + return messages; + }); + }, + clearMessages: (): void => { + set([]); + } + }; +} + +export const errorStore = createErrorStore(); diff --git a/front/src/Stores/Errors/BrowserTooOldError.ts b/front/src/Stores/Errors/BrowserTooOldError.ts new file mode 100644 index 00000000..bf934443 --- /dev/null +++ b/front/src/Stores/Errors/BrowserTooOldError.ts @@ -0,0 +1,8 @@ +export class BrowserTooOldError extends Error { + static NAME = 'BrowserTooOldError'; + + constructor() { + super('Unable to access your camera or microphone. Your browser is too old. Please consider upgrading your browser or try using a recent version of Chrome.'); + this.name = BrowserTooOldError.NAME; + } +} diff --git a/front/src/Stores/MediaStore.ts b/front/src/Stores/MediaStore.ts index 7d1911a4..2c5fc796 100644 --- a/front/src/Stores/MediaStore.ts +++ b/front/src/Stores/MediaStore.ts @@ -4,6 +4,8 @@ import {localUserStore} from "../Connexion/LocalUserStore"; import {ITiledMapGroupLayer, ITiledMapObjectLayer, ITiledMapTileLayer} from "../Phaser/Map/ITiledMap"; import {userMovingStore} from "./GameStore"; import {HtmlUtils} from "../WebRtc/HtmlUtils"; +import {BrowserTooOldError} from "./Errors/BrowserTooOldError"; +import {errorStore} from "./ErrorStore"; /** * A store that contains the camera state requested by the user (on or off). @@ -423,7 +425,7 @@ export const localStreamStore = derived, LocalS //throw new Error('Unable to access your camera or microphone. Your browser is too old.'); set({ type: 'error', - error: new Error('Unable to access your camera or microphone. Your browser is too old. Please consider upgrading your browser or try using a recent version of Chrome.'), + error: new BrowserTooOldError(), constraints }); return; @@ -594,3 +596,11 @@ microphoneListStore.subscribe((devices) => { audioConstraintStore.setDeviceId(undefined); } }); + +localStreamStore.subscribe(streamResult => { + if (streamResult.type === 'error') { + if (streamResult.error.name === BrowserTooOldError.NAME) { + errorStore.addErrorMessage(streamResult.error); + } + } +}); From 2905a5570ca921462732731566372bad928ef284 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Thu, 3 Jun 2021 14:29:09 +0200 Subject: [PATCH 12/18] Removing commented code --- front/src/Stores/MediaStore.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/front/src/Stores/MediaStore.ts b/front/src/Stores/MediaStore.ts index 2c5fc796..13425afe 100644 --- a/front/src/Stores/MediaStore.ts +++ b/front/src/Stores/MediaStore.ts @@ -422,7 +422,6 @@ export const localStreamStore = derived, LocalS }); return; } else { - //throw new Error('Unable to access your camera or microphone. Your browser is too old.'); set({ type: 'error', error: new BrowserTooOldError(), From fd4f09588e1d6e46605b3019b39905f4c5486390 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Fri, 4 Jun 2021 14:16:21 +0200 Subject: [PATCH 13/18] Adding "playsinline" tag in remote