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();