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/dist/index.tmpl.html b/front/dist/index.tmpl.html index 3ce6789a..b6c8fdd9 100644 --- a/front/dist/index.tmpl.html +++ b/front/dist/index.tmpl.html @@ -112,12 +112,6 @@
- - 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) => { diff --git a/front/src/Components/App.svelte b/front/src/Components/App.svelte index 97cb5071..a39f2dc7 100644 --- a/front/src/Components/App.svelte +++ b/front/src/Components/App.svelte @@ -9,16 +9,20 @@ 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"; 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"; 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; @@ -73,4 +77,12 @@ {/if} + {#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..3244de24 --- /dev/null +++ b/front/src/Components/UI/ErrorDialog.svelte @@ -0,0 +1,48 @@ + + +
+

Error

+
+ {#each $errorStore as error} +

{error}

+ {/each} +
+
+ +
+
+ + 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 abfda56a..c1416f2b 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 f16ee988..cf041c46 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -31,7 +31,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"; @@ -184,6 +184,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; @@ -253,6 +254,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) { @@ -557,6 +563,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; + }); } /** @@ -1046,6 +1067,7 @@ ${escapedMessage} this.userInputManager.destroy(); this.pinchManager?.destroy(); this.emoteManager.destroy(); + this.peerStoreUnsubscribe(); mediaManager.hideGameOverlay(); @@ -1455,6 +1477,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/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(); 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; } } diff --git a/front/src/Phaser/Services/WaScaleManager.ts b/front/src/Phaser/Services/WaScaleManager.ts index 4e0e9208..acbecc38 100644 --- a/front/src/Phaser/Services/WaScaleManager.ts +++ b/front/src/Phaser/Services/WaScaleManager.ts @@ -60,7 +60,6 @@ class WaScaleManager { public saveZoom(): void { this._saveZoom = this.hdpiManager.zoomModifier; - console.log(this._saveZoom); } public restoreZoom(): void{ diff --git a/front/src/Stores/ErrorStore.ts b/front/src/Stores/ErrorStore.ts new file mode 100644 index 00000000..2f1e3e40 --- /dev/null +++ b/front/src/Stores/ErrorStore.ts @@ -0,0 +1,33 @@ +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: string[]) => { + let message: string; + if (e instanceof Error) { + message = e.message; + } else { + message = e; + } + + if (!messages.includes(message)) { + messages.push(message); + } + + 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/Errors/WebviewOnOldIOS.ts b/front/src/Stores/Errors/WebviewOnOldIOS.ts new file mode 100644 index 00000000..06c03f0e --- /dev/null +++ b/front/src/Stores/Errors/WebviewOnOldIOS.ts @@ -0,0 +1,8 @@ +export class WebviewOnOldIOS extends Error { + static NAME = 'WebviewOnOldIOS'; + + constructor() { + super('Your iOS version cannot use video/audio in the browser unless you are using Safari. Please switch to Safari or upgrade iOS to 14.3 or above.'); + this.name = WebviewOnOldIOS.NAME; + } +} 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/front/src/Stores/MediaStore.ts b/front/src/Stores/MediaStore.ts index 7d1911a4..d622511e 100644 --- a/front/src/Stores/MediaStore.ts +++ b/front/src/Stores/MediaStore.ts @@ -4,6 +4,10 @@ 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"; +import {isIOS} from "../WebRtc/DeviceUtils"; +import {WebviewOnOldIOS} from "./Errors/WebviewOnOldIOS"; /** * A store that contains the camera state requested by the user (on or off). @@ -419,11 +423,17 @@ export const localStreamStore = derived, LocalS constraints }); return; - } else { - //throw new Error('Unable to access your camera or microphone. Your browser is too old.'); + } else if (isIOS()) { 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 WebviewOnOldIOS(), + constraints + }); + return; + } else { + set({ + type: 'error', + error: new BrowserTooOldError(), constraints }); return; @@ -594,3 +604,11 @@ microphoneListStore.subscribe((devices) => { audioConstraintStore.setDeviceId(undefined); } }); + +localStreamStore.subscribe(streamResult => { + if (streamResult.type === 'error') { + if (streamResult.error.name === BrowserTooOldError.NAME || streamResult.error.name === WebviewOnOldIOS.NAME) { + errorStore.addErrorMessage(streamResult.error); + } + } +}); diff --git a/front/src/WebRtc/CoWebsiteManager.ts b/front/src/WebRtc/CoWebsiteManager.ts index 30b843dc..6c4224f9 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/DeviceUtils.ts b/front/src/WebRtc/DeviceUtils.ts new file mode 100644 index 00000000..91a3dc31 --- /dev/null +++ b/front/src/WebRtc/DeviceUtils.ts @@ -0,0 +1,12 @@ +export function isIOS(): boolean { + return [ + 'iPad Simulator', + 'iPhone Simulator', + 'iPod Simulator', + 'iPad', + 'iPhone', + 'iPod' + ].includes(navigator.platform) + // iPad on iOS 13 detection + || (navigator.userAgent.includes("Mac") && "ontouchend" in document) +} 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; }); } diff --git a/front/src/WebRtc/MediaManager.ts b/front/src/WebRtc/MediaManager.ts index 7b527962..efc9660a 100644 --- a/front/src/WebRtc/MediaManager.ts +++ b/front/src/WebRtc/MediaManager.ts @@ -21,12 +21,12 @@ export type ReportCallback = (message: string) => void; export type ShowReportCallBack = (userId: string, userName: string|undefined) => void; export type HelpCameraSettingsCallBack = () => void; +import {cowebsiteCloseButtonId} from "./CoWebsiteManager"; + 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 +44,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 @@ -70,6 +65,7 @@ export class MediaManager { } }); + let isScreenSharing = false; screenSharingLocalStreamStore.subscribe((result) => { if (result.type === 'error') { console.error(result.error); @@ -80,10 +76,14 @@ export class MediaManager { } if (result.stream !== null) { + isScreenSharing = true; this.addScreenSharingActiveVideo('me', DivImportance.Normal); HtmlUtils.getElementByIdOrFail('screen-sharing-me').srcObject = result.stream; } else { - this.removeActiveScreenSharingVideo('me'); + if (isScreenSharing) { + isScreenSharing = false; + this.removeActiveScreenSharingVideo('me'); + } } }); @@ -106,11 +106,14 @@ 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(); + }); gameOverlayVisibilityStore.showGameOverlay(); } @@ -119,17 +122,19 @@ export class MediaManager { 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(); + }); gameOverlayVisibilityStore.hideGameOverlay(); } addActiveVideo(user: UserSimplePeerInterface, userName: string = ""){ - this.webrtcInAudio.play(); const userId = ''+user.userId userName = userName.toUpperCase(); @@ -145,7 +150,7 @@ export class MediaManager { Report/Block - +
@@ -182,7 +187,7 @@ export class MediaManager { userId = this.getScreenSharingId(userId); const html = `
- +
`; @@ -277,10 +282,6 @@ export class MediaManager { this.removeActiveVideo(this.getScreenSharingId(userId)) } - playWebrtcOutSound(): void { - this.webrtcOutAudio.play(); - } - 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) { diff --git a/front/style/style.scss b/front/style/style.scss index 6d439bac..ce93e156 100644 --- a/front/style/style.scss +++ b/front/style/style.scss @@ -1146,6 +1146,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/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();