diff --git a/back/src/Services/SocketManager.ts b/back/src/Services/SocketManager.ts index 5efae800..8989df75 100644 --- a/back/src/Services/SocketManager.ts +++ b/back/src/Services/SocketManager.ts @@ -97,6 +97,7 @@ export class SocketManager { } const roomJoinedMessage = new RoomJoinedMessage(); roomJoinedMessage.setTagList(joinRoomMessage.getTagList()); + roomJoinedMessage.setUserroomtoken(joinRoomMessage.getUserroomtoken()); for (const [itemId, item] of room.getItemsState().entries()) { const itemStateMessage = new ItemStateMessage(); diff --git a/docs/maps/api-player.md b/docs/maps/api-player.md index 39a13d9e..9af0b1c2 100644 --- a/docs/maps/api-player.md +++ b/docs/maps/api-player.md @@ -58,6 +58,34 @@ WA.onInit().then(() => { }) ``` +### Get the user-room token of the player + +``` +WA.player.userRoomToken: string; +``` + +The user-room token is available from the `WA.player.userRoomToken` property. + +This token can be used by third party services to authenticate a player and prove that the player is in a given room. +The token is generated by the administration panel linked to WorkAdventure. The token is a string and is depending on your implementation of the administration panel. +In WorkAdventure SAAS version, the token is a JWT token that contains information such as the player's room ID and its associated membership ID. + +If you are using the self-hosted version of WorkAdventure and you developed your own administration panel, the token can be anything. +By default, self-hosted versions of WorkAdventure don't come with an administration panel, so the token string will be empty. + +{.alert.alert-info} +A typical use-case for the user-room token is providing logo upload capabilities in a map. +The token can be used as a way to authenticate a WorkAdventure player and ensure he is indeed in the map and authorized to upload a logo. + +{.alert.alert-info} +You need to wait for the end of the initialization before accessing `WA.player.userRoomToken` + +```typescript +WA.onInit().then(() => { + console.log('Token: ', WA.player.userRoomToken); +}) +``` + ### Listen to player movement ``` WA.player.onPlayerMove(callback: HasPlayerMovedEventCallback): void; diff --git a/front/src/Api/Events/GameStateEvent.ts b/front/src/Api/Events/GameStateEvent.ts index 112c2880..1f0f36ed 100644 --- a/front/src/Api/Events/GameStateEvent.ts +++ b/front/src/Api/Events/GameStateEvent.ts @@ -9,6 +9,7 @@ export const isGameStateEvent = new tg.IsInterface() startLayerName: tg.isUnion(tg.isString, tg.isNull), tags: tg.isArray(tg.isString), variables: tg.isObject, + userRoomToken: tg.isUnion(tg.isString, tg.isUndefined), }) .get(); /** diff --git a/front/src/Api/iframe/player.ts b/front/src/Api/iframe/player.ts index 078a1926..c46f3fbc 100644 --- a/front/src/Api/iframe/player.ts +++ b/front/src/Api/iframe/player.ts @@ -20,6 +20,12 @@ export const setTags = (_tags: string[]) => { let uuid: string | undefined; +let userRoomToken: string | undefined; + +export const setUserRoomToken = (token: string | undefined) => { + userRoomToken = token; +}; + export const setUuid = (_uuid: string | undefined) => { uuid = _uuid; }; @@ -67,6 +73,15 @@ export class WorkadventurePlayerCommands extends IframeApiContribution any) = null; // eslint-disable-line @typescript-eslint/no-explicit-any private closed: boolean = false; private tags: string[] = []; + private _userRoomToken: string | undefined; // eslint-disable-next-line @typescript-eslint/no-explicit-any public static setWebsocketFactory(websocketFactory: (url: string) => any): void { @@ -211,6 +212,7 @@ export class RoomConnection implements RoomConnection { this.userId = roomJoinedMessage.getCurrentuserid(); this.tags = roomJoinedMessage.getTagList(); + this._userRoomToken = roomJoinedMessage.getUserroomtoken(); this.dispatch(EventMessage.CONNECT, { connection: this, @@ -713,4 +715,8 @@ export class RoomConnection implements RoomConnection { public getAllTags(): string[] { return this.tags; } + + public get userRoomToken(): string | undefined { + return this._userRoomToken; + } } diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index aba64202..373a76c4 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -1167,6 +1167,7 @@ ${escapedMessage} roomId: this.roomUrl, tags: this.connection ? this.connection.getAllTags() : [], variables: this.sharedVariablesManager.variables, + userRoomToken: this.connection ? this.connection.userRoomToken : "", }; }); this.iframeSubscriptionList.push( diff --git a/front/src/iframe_api.ts b/front/src/iframe_api.ts index dcd10fdc..93415b0d 100644 --- a/front/src/iframe_api.ts +++ b/front/src/iframe_api.ts @@ -15,11 +15,11 @@ import ui from "./Api/iframe/ui"; import sound from "./Api/iframe/sound"; import room, { setMapURL, setRoomId } from "./Api/iframe/room"; import state, { initVariables } from "./Api/iframe/state"; -import player, { setPlayerName, setTags, setUuid } from "./Api/iframe/player"; +import player, { setPlayerName, setTags, setUserRoomToken, setUuid } from "./Api/iframe/player"; import type { ButtonDescriptor } from "./Api/iframe/Ui/ButtonDescriptor"; import type { Popup } from "./Api/iframe/Ui/Popup"; import type { Sound } from "./Api/iframe/Sound/Sound"; -import { answerPromises, queryWorkadventure, sendToWorkadventure } from "./Api/iframe/IframeApiContribution"; +import { answerPromises, queryWorkadventure } from "./Api/iframe/IframeApiContribution"; // Notify WorkAdventure that we are ready to receive data const initPromise = queryWorkadventure({ @@ -32,6 +32,7 @@ const initPromise = queryWorkadventure({ setTags(state.tags); setUuid(state.uuid); initVariables(state.variables as Map); + setUserRoomToken(state.userRoomToken); }); const wa = { diff --git a/maps/tests/Metadata/getCurrentRoom.js b/maps/tests/Metadata/getCurrentRoom.js index df3a995c..fa8e0226 100644 --- a/maps/tests/Metadata/getCurrentRoom.js +++ b/maps/tests/Metadata/getCurrentRoom.js @@ -4,6 +4,7 @@ WA.onInit().then(() => { console.log('Player name: ', WA.player.name); console.log('Player id: ', WA.player.id); console.log('Player tags: ', WA.player.tags); + console.log('Player token: ', WA.player.userRoomToken); }); WA.room.getTiledMap().then((data) => { diff --git a/messages/protos/messages.proto b/messages/protos/messages.proto index 7a4d74d9..117ab582 100644 --- a/messages/protos/messages.proto +++ b/messages/protos/messages.proto @@ -198,6 +198,7 @@ message RoomJoinedMessage { int32 currentUserId = 4; repeated string tag = 5; repeated VariableMessage variable = 6; + string userRoomToken = 7; } message WebRtcStartMessage { @@ -300,6 +301,7 @@ message JoinRoomMessage { string IPAddress = 7; CompanionMessage companion = 8; string visitCardUrl = 9; + string userRoomToken = 10; } message UserJoinedZoneMessage { diff --git a/pusher/src/Controller/AuthenticateController.ts b/pusher/src/Controller/AuthenticateController.ts index 47d35fab..8f09cc1a 100644 --- a/pusher/src/Controller/AuthenticateController.ts +++ b/pusher/src/Controller/AuthenticateController.ts @@ -287,6 +287,7 @@ export class AuthenticateController extends BaseController { messages: [], visitCardUrl: null, textures: [], + userRoomToken: undefined, }; try { data = await adminApi.fetchMemberDataByUuid(email, playUri, IPAddress); diff --git a/pusher/src/Controller/IoSocketController.ts b/pusher/src/Controller/IoSocketController.ts index 4c09638a..df29db57 100644 --- a/pusher/src/Controller/IoSocketController.ts +++ b/pusher/src/Controller/IoSocketController.ts @@ -238,6 +238,7 @@ export class IoSocketController { let memberTags: string[] = []; let memberVisitCardUrl: string | null = null; let memberMessages: unknown; + let memberUserRoomToken: string | undefined; let memberTextures: CharacterTexture[] = []; const room = await socketManager.getOrCreateRoom(roomId); let userData: FetchMemberDataByUuidResponse = { @@ -248,6 +249,7 @@ export class IoSocketController { textures: [], messages: [], anonymous: true, + userRoomToken: undefined, }; if (ADMIN_API_URL) { try { @@ -286,6 +288,8 @@ export class IoSocketController { memberTags = userData.tags; memberVisitCardUrl = userData.visitCardUrl; memberTextures = userData.textures; + memberUserRoomToken = userData.userRoomToken; + if ( room.policyType === GameRoomPolicyTypes.USE_TAGS_POLICY && (userData.anonymous === true || !room.canAccess(memberTags)) @@ -335,6 +339,7 @@ export class IoSocketController { messages: memberMessages, tags: memberTags, visitCardUrl: memberVisitCardUrl, + userRoomToken: memberUserRoomToken, textures: memberTextures, position: { x: x, diff --git a/pusher/src/Model/Websocket/ExSocketInterface.ts b/pusher/src/Model/Websocket/ExSocketInterface.ts index ff5ed211..411d88fa 100644 --- a/pusher/src/Model/Websocket/ExSocketInterface.ts +++ b/pusher/src/Model/Websocket/ExSocketInterface.ts @@ -44,4 +44,5 @@ export interface ExSocketInterface extends WebSocket, Identificable { textures: CharacterTexture[]; backConnection: BackConnection; listenedZones: Set; + userRoomToken: string | undefined; } diff --git a/pusher/src/Services/AdminApi.ts b/pusher/src/Services/AdminApi.ts index 416b9cb6..bc3b2172 100644 --- a/pusher/src/Services/AdminApi.ts +++ b/pusher/src/Services/AdminApi.ts @@ -29,6 +29,7 @@ export interface FetchMemberDataByUuidResponse { textures: CharacterTexture[]; messages: unknown[]; anonymous?: boolean; + userRoomToken: string | undefined; } class AdminApi { diff --git a/pusher/src/Services/SocketManager.ts b/pusher/src/Services/SocketManager.ts index 083840e4..4f4b086f 100644 --- a/pusher/src/Services/SocketManager.ts +++ b/pusher/src/Services/SocketManager.ts @@ -157,6 +157,11 @@ export class SocketManager implements ZoneEventListener { joinRoomMessage.setName(client.name); joinRoomMessage.setPositionmessage(ProtobufUtils.toPositionMessage(client.position)); joinRoomMessage.setTagList(client.tags); + + if (client.userRoomToken) { + joinRoomMessage.setUserroomtoken(client.userRoomToken); + } + if (client.visitCardUrl) { joinRoomMessage.setVisitcardurl(client.visitCardUrl); }