diff --git a/.env.template b/.env.template index 715ebeec..0bd7bf6d 100644 --- a/.env.template +++ b/.env.template @@ -22,6 +22,10 @@ MAX_USERNAME_LENGTH=8 OPID_CLIENT_ID= OPID_CLIENT_SECRET= OPID_CLIENT_ISSUER= +OPID_CLIENT_REDIRECT_URL= +OPID_LOGIN_SCREEN_PROVIDER=http://pusher.workadventure.localhost/login-screen +OPID_PROFILE_SCREEN_PROVIDER= +DISABLE_ANONYMOUS= # If you want to have a contact page in your menu, you MUST set CONTACT_URL to the URL of the page that you want CONTACT_URL= \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 18f26012..e33a4f97 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,13 @@ ## Version develop +### Updates +- Added multi Co-Website management + ### Bugfix - Moving a discussion over a user will now add this user to the discussion - Being in a silent zone new forces mediaConstraints to false (#1508) - Fixes for the emote menu (#1501) +- Fixing chat message attributed to wrong user (#1507 #1528) ## Version 1.5.0 ### Updates diff --git a/README.md b/README.md index dd109ada..427c514c 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ Note: on some OSes, you will need to add this line to your `/etc/hosts` file: ``` Note: If on the first run you get a page with "network error". Try to ``docker-compose stop`` , then ``docker-compose start``. -Note 2: If you are still getting "network error". Make sure you are authorizing the self-signed certificate by entering https://pusher.workadventure.testing and accepting them. +Note 2: If you are still getting "network error". Make sure you are authorizing the self-signed certificate by entering https://pusher.workadventure.localhost and accepting them. ### MacOS developers, your environment with Vagrant diff --git a/back/package.json b/back/package.json index 76039479..aacd0c96 100644 --- a/back/package.json +++ b/back/package.json @@ -40,7 +40,7 @@ }, "homepage": "https://github.com/thecodingmachine/workadventure#readme", "dependencies": { - "@workadventure/tiled-map-type-guard": "^1.0.2", + "@workadventure/tiled-map-type-guard": "^1.0.3", "axios": "^0.21.2", "busboy": "^0.3.1", "circular-json": "^0.5.9", diff --git a/back/yarn.lock b/back/yarn.lock index 74218f8f..ff9cb180 100644 --- a/back/yarn.lock +++ b/back/yarn.lock @@ -194,10 +194,10 @@ semver "^7.3.2" tsutils "^3.17.1" -"@workadventure/tiled-map-type-guard@^1.0.2": - version "1.0.2" - resolved "https://registry.yarnpkg.com/@workadventure/tiled-map-type-guard/-/tiled-map-type-guard-1.0.2.tgz#4171550f6cd71be19791faef48360d65d698bcb0" - integrity sha512-RCtygGV5y9cb7QoyGMINBE9arM5pyXjkxvXgA5uXEv4GDbXKorhFim/rHgwbVR+eFnVF3rDgWbRnk3DIaHt+lQ== +"@workadventure/tiled-map-type-guard@^1.0.3": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@workadventure/tiled-map-type-guard/-/tiled-map-type-guard-1.0.3.tgz#62c2061cacbe1360b84162af0b7e4639ed8bfa7e" + integrity sha512-pUMxBBZHYAFkpnGWZAVAE8+M+Wn9UtzqZhXvBBBbB1gEakHIka7ahdTGfh0DgRaWrVszVXOP3tf49Dhdmn9pDg== dependencies: generic-type-guard "^3.4.1" diff --git a/docker-compose.single-domain.yaml b/docker-compose.single-domain.yaml index 4e85d702..cd38a0f9 100644 --- a/docker-compose.single-domain.yaml +++ b/docker-compose.single-domain.yaml @@ -40,6 +40,7 @@ services: TURN_USER: "" TURN_PASSWORD: "" START_ROOM_URL: "$START_ROOM_URL" + DISABLE_ANONYMOUS: "$DISABLE_ANONYMOUS" command: yarn run start volumes: - ./front:/usr/src/app @@ -70,6 +71,9 @@ services: OPID_CLIENT_ID: $OPID_CLIENT_ID OPID_CLIENT_SECRET: $OPID_CLIENT_SECRET OPID_CLIENT_ISSUER: $OPID_CLIENT_ISSUER + OPID_CLIENT_REDIRECT_URL: $OPID_CLIENT_REDIRECT_URL + OPID_PROFILE_SCREEN_PROVIDER: $OPID_PROFILE_SCREEN_PROVIDER + DISABLE_ANONYMOUS: $DISABLE_ANONYMOUS volumes: - ./pusher:/usr/src/app labels: diff --git a/docker-compose.yaml b/docker-compose.yaml index 4b3904dd..0e22fa91 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -29,6 +29,7 @@ services: PUSHER_URL: //pusher.workadventure.localhost UPLOADER_URL: //uploader.workadventure.localhost ADMIN_URL: //workadventure.localhost + ICON_URL: //icon.workadventure.localhost STARTUP_COMMAND_1: ./templater.sh STARTUP_COMMAND_2: yarn install STUN_SERVER: "stun:stun.l.google.com:19302" @@ -42,6 +43,8 @@ services: START_ROOM_URL: "$START_ROOM_URL" MAX_PER_GROUP: "$MAX_PER_GROUP" MAX_USERNAME_LENGTH: "$MAX_USERNAME_LENGTH" + DISABLE_ANONYMOUS: "$DISABLE_ANONYMOUS" + OPID_LOGIN_SCREEN_PROVIDER: "$OPID_LOGIN_SCREEN_PROVIDER" command: yarn run start volumes: - ./front:/usr/src/app @@ -70,6 +73,9 @@ services: OPID_CLIENT_ID: $OPID_CLIENT_ID OPID_CLIENT_SECRET: $OPID_CLIENT_SECRET OPID_CLIENT_ISSUER: $OPID_CLIENT_ISSUER + OPID_CLIENT_REDIRECT_URL: $OPID_CLIENT_REDIRECT_URL + OPID_PROFILE_SCREEN_PROVIDER: $OPID_PROFILE_SCREEN_PROVIDER + DISABLE_ANONYMOUS: $DISABLE_ANONYMOUS volumes: - ./pusher:/usr/src/app labels: @@ -177,6 +183,17 @@ services: - "traefik.http.routers.redisinsight-ssl.tls=true" - "traefik.http.routers.redisinsight-ssl.service=redisinsight" + icon: + image: matthiasluedtke/iconserver:v3.13.0 + labels: + - "traefik.http.routers.icon.rule=Host(`icon.workadventure.localhost`)" + - "traefik.http.routers.icon.entryPoints=web" + - "traefik.http.services.icon.loadbalancer.server.port=8080" + - "traefik.http.routers.icon-ssl.rule=Host(`icon.workadventure.localhost`)" + - "traefik.http.routers.icon-ssl.entryPoints=websecure" + - "traefik.http.routers.icon-ssl.tls=true" + - "traefik.http.routers.icon-ssl.service=icon" + # coturn: # image: coturn/coturn:4.5.2 # command: diff --git a/docs/maps/api-deprecated.md b/docs/maps/api-deprecated.md index f2b582a5..ffa8af9e 100644 --- a/docs/maps/api-deprecated.md +++ b/docs/maps/api-deprecated.md @@ -3,19 +3,21 @@ The list of functions below is **deprecated**. You should not use those but. use the replacement functions. -- Method `WA.sendChatMessage` is deprecated. It has been renamed to `WA.chat.sendChatMessage`. -- Method `WA.disablePlayerControls` is deprecated. It has been renamed to `WA.controls.disablePlayerControls`. -- Method `WA.restorePlayerControls` is deprecated. It has been renamed to `WA.controls.restorePlayerControls`. +- Method `WA.sendChatMessage` is deprecated. It has been renamed to [`WA.chat.sendChatMessage`](api-chat.md#sending-a-message-in-the-chat). +- Method `WA.disablePlayerControls` is deprecated. It has been renamed to [`WA.controls.disablePlayerControls`](api-controls.md#disabling--restoring-controls). +- Method `WA.restorePlayerControls` is deprecated. It has been renamed to [`WA.controls.restorePlayerControls`](api-controls.md#disabling--restoring-controls). - Method `WA.displayBubble` is deprecated. It has been renamed to `WA.ui.displayBubble`. - Method `WA.removeBubble` is deprecated. It has been renamed to `WA.ui.removeBubble`. -- Method `WA.openTab` is deprecated. It has been renamed to `WA.nav.openTab`. -- Method `WA.loadSound` is deprecated. It has been renamed to `WA.sound.loadSound`. -- Method `WA.goToPage` is deprecated. It has been renamed to `WA.nav.goToPage`. -- Method `WA.goToRoom` is deprecated. It has been renamed to `WA.nav.goToRoom`. -- Method `WA.openCoWebSite` is deprecated. It has been renamed to `WA.nav.openCoWebSite`. -- Method `WA.closeCoWebSite` is deprecated. It has been renamed to `WA.nav.closeCoWebSite`. -- Method `WA.openPopup` is deprecated. It has been renamed to `WA.ui.openPopup`. -- Method `WA.onChatMessage` is deprecated. It has been renamed to `WA.chat.onChatMessage`. -- Method `WA.onEnterZone` is deprecated. It has been renamed to `WA.room.onEnterZone`. -- Method `WA.onLeaveZone` is deprecated. It has been renamed to `WA.room.onLeaveZone`. -- Method `WA.ui.registerMenuCommand` parameter `callback` is deprecated. Use `WA.ui.registerMenuCommand(commandDescriptor: string, options: MenuOptions)`. \ No newline at end of file +- Method `WA.openTab` is deprecated. It has been renamed to [`WA.nav.openTab`](api-nav.md#opening-a-web-page-in-a-new-tab). +- Method `WA.loadSound` is deprecated. It has been renamed to [`WA.sound.loadSound`](api-sound.md#load-a-sound-from-an-url). +- Method `WA.goToPage` is deprecated. It has been renamed to [`WA.nav.goToPage`](api-nav.md#opening-a-web-page-in-the-current-tab). +- Method `WA.goToRoom` is deprecated. It has been renamed to [`WA.nav.goToRoom`](api-nav.md#going-to-a-different-map-from-the-script). +- Method `WA.openCoWebSite` is deprecated. It has been renamed to [`WA.nav.openCoWebSite`](api-nav.md#openingclosing-web-page-in-co-websites). +- Method `WA.closeCoWebSite` is deprecated. It has been remove and [replace by a function close](api-nav.md#openingclosing-web-page-in-co-websites). +- Method `WA.openPopup` is deprecated. It has been renamed to [`WA.ui.openPopup`](api-ui.md#opening-a-popup). +- Method `WA.onChatMessage` is deprecated. It has been renamed to [`WA.chat.onChatMessage`](api-chat.md#listening-to-messages-from-the-chat). +- Method `WA.onEnterZone` is deprecated. It has been renamed to [`WA.room.onEnterZone`](api-room.md#detecting-when-the-user-entersleaves-a-layer). +- Method `WA.onLeaveZone` is deprecated. It has been renamed to [`WA.room.onLeaveZone`](api-room.md#detecting-when-the-user-entersleaves-a-layer). +- Method `WA.ui.registerMenuCommand` parameter `callback` is deprecated. Use [`WA.ui.registerMenuCommand(commandDescriptor: string, options: MenuOptions)`](api-ui.md#add-custom-menu). +- Method `WA.room.onEnterZone` is deprecated. Use instead [`WA.room.onEnterLayer`](api-room.md#detecting-when-the-user-entersleaves-a-layer). +- Method `WA.room.onLeaveZone` is deprecated. Use instead [`WA.room.onLeaveLayer`](api-room.md#detecting-when-the-user-entersleaves-a-layer). \ No newline at end of file diff --git a/docs/maps/api-nav.md b/docs/maps/api-nav.md index f5721063..47ee416e 100644 --- a/docs/maps/api-nav.md +++ b/docs/maps/api-nav.md @@ -49,19 +49,34 @@ WA.nav.goToRoom('../otherMap/map.json'); WA.nav.goToRoom("/_/global/.json#start-layer-2") ``` -### Opening/closing a web page in an iFrame +### Opening/closing web page in Co-Websites ``` -WA.nav.openCoWebSite(url: string, allowApi: boolean = false, allowPolicy: string = ""): void -WA.nav.closeCoWebSite(): void +WA.nav.openCoWebSite(url: string, allowApi: boolean = false, allowPolicy: string = "", position: number = 0): Promise ``` -Opens the webpage at "url" in an iFrame (on the right side of the screen) or close that iFrame. `allowApi` allows the webpage to use the "IFrame API" and execute script (it is equivalent to putting the `openWebsiteAllowApi` property in the map). `allowPolicy` grants additional access rights to the iFrame. The `allowPolicy` parameter is turned into an [`allow` feature policy in the iFrame](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe#attr-allow). +Opens the webpage at "url" in an iFrame (on the right side of the screen) or close that iFrame. `allowApi` allows the webpage to use the "IFrame API" and execute script (it is equivalent to putting the `openWebsiteAllowApi` property in the map). `allowPolicy` grants additional access rights to the iFrame. The `allowPolicy` parameter is turned into an [`allow` feature policy in the iFrame](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe#attr-allow), position in whitch slot the web page will be open. +You can have only 5 co-wbesites open simultaneously. Example: ```javascript -WA.nav.openCoWebSite('https://www.wikipedia.org/'); +const coWebsite = await WA.nav.openCoWebSite('https://www.wikipedia.org/'); +const coWebsiteWorkAdventure = await WA.nav.openCoWebSite('https://workadventu.re/', true, "", 1); // ... -WA.nav.closeCoWebSite(); +coWebsite.close(); +``` + +### Get all Co-Websites + +``` +WA.nav.getCoWebSites(): Promise +``` + +Get all opened co-websites with their ids and positions. + +Example: + +```javascript +const coWebsites = await WA.nav.getCowebSites(); ``` diff --git a/docs/maps/api-player.md b/docs/maps/api-player.md index ed73c32d..39a13d9e 100644 --- a/docs/maps/api-player.md +++ b/docs/maps/api-player.md @@ -68,7 +68,9 @@ The event has the following attributes : * **moving (boolean):** **true** when the current player is moving, **false** otherwise. * **direction (string):** **"right"** | **"left"** | **"down"** | **"top"** the direction where the current player is moving. * **x (number):** coordinate X of the current player. -* **y (number):** coordinate Y of the current player. +* **y (number):** coordinate Y of the current player. +* **oldX (number):** old coordinate X of the current player. +* **oldY (number):** old coordinate Y of the current player. **callback:** the function that will be called when the current player is moving. It contains the event. diff --git a/docs/maps/api-room.md b/docs/maps/api-room.md index d1a26d2f..72947df8 100644 --- a/docs/maps/api-room.md +++ b/docs/maps/api-room.md @@ -17,35 +17,27 @@ The name of the layers of this map are : * `bottom/build/carpet` * `wall` -### Detecting when the user enters/leaves a zone +### Detecting when the user enters/leaves a layer ``` -WA.room.onEnterZone(name: string, callback: () => void): void -WA.room.onLeaveZone(name: string, callback: () => void): void +WA.room.onEnterLayer(name: string): Subscription +WA.room.onLeaveLayer(name: string): Subscription ``` -Listens to the position of the current user. The event is triggered when the user enters or leaves a given zone. The name of the zone is stored in the map, on a dedicated layer with the `zone` property. +Listens to the position of the current user. The event is triggered when the user enters or leaves a given layer. -
-
- -
The `zone` property, applied on a layer
-
-
- -* **name**: the name of the zone, as defined in the `zone` property. -* **callback**: the function that will be called when a user enters or leaves the zone. +* **name**: the name of the layer who as defined in Tiled. Example: ```javascript -WA.room.onEnterZone('myZone', () => { +WA.room.onEnterLayer('myLayer').subscribe(() => { WA.chat.sendChatMessage("Hello!", 'Mr Robot'); -}) +}); -WA.room.onLeaveZone('myZone', () => { +WA.room.onLeaveLayer('myLayer').subscribe(() => { WA.chat.sendChatMessage("Goodbye!", 'Mr Robot'); -}) +}); ``` ### Show / Hide a layer @@ -71,7 +63,7 @@ WA.room.setProperty(layerName : string, propertyName : string, propertyValue : s Set the value of the `propertyName` property of the layer `layerName` at `propertyValue`. If the property doesn't exist, create the property `propertyName` and set the value of the property at `propertyValue`. -Note : +Note : To unset a property from a layer, use `setProperty` with `propertyValue` set to `undefined`. Example : @@ -131,7 +123,7 @@ console.log("Map generated with Tiled version ", map.tiledversion); Check the [Tiled documentation to learn more about the format of the JSON map](https://doc.mapeditor.org/en/stable/reference/json-map-format/). -### Changing tiles +### Changing tiles ``` WA.room.setTiles(tiles: TileDescriptor[]): void ``` @@ -144,7 +136,7 @@ If `tile` is a string, it's not the id of the tile but the value of the property -`TileDescriptor` has the following attributes : +`TileDescriptor` has the following attributes : * **x (number) :** The coordinate x of the tile that you want to replace. * **y (number) :** The coordinate y of the tile that you want to replace. * **tile (number | string) :** The id of the tile that will be placed in the map. @@ -154,7 +146,7 @@ If `tile` is a string, it's not the id of the tile but the value of the property Note: If you want to unset a tile, use `setTiles` with `tile` set to `null`. -Example : +Example : ```javascript WA.room.setTiles([ {x: 6, y: 4, tile: 'blue', layer: 'setTiles'}, @@ -246,7 +238,7 @@ const website = WA.room.website.create({ WA.room.website.delete(name: string): Promise ``` -Use `WA.room.website.delete` to completely remove an embedded website from your map. +Use `WA.room.website.delete` to completely remove an embedded website from your map. ### The EmbeddedWebsite class @@ -271,7 +263,7 @@ When you modify a property of an `EmbeddedWebsite` instance, the iframe is autom {.alert.alert-warning} -The websites you add/edit/delete via the scripting API are only shown locally. If you want them +The websites you add/edit/delete via the scripting API are only shown locally. If you want them to be displayed for every player, you can use [variables](api-start.md) to share a common state between all users. diff --git a/docs/maps/opening-a-website.md b/docs/maps/opening-a-website.md index ec6c82d4..64b19f1c 100644 --- a/docs/maps/opening-a-website.md +++ b/docs/maps/opening-a-website.md @@ -5,7 +5,7 @@ ## The openWebsite property -On your map, you can define special zones. When a player will pass over these zones, a website will open (as an iframe +On your map, you can define special zones. When a player will pass over these zones, a website will open (as an iframe on the right side of the screen) In order to create a zone that opens websites: @@ -16,7 +16,7 @@ In order to create a zone that opens websites: * You may also use "`openTab`" property (of type "`string`") to open in a new tab instead. {.alert.alert-warning} -A website can explicitly forbid another website from loading it in an iFrame using +A website can explicitly forbid another website from loading it in an iFrame using the [X-Frame-Options HTTP header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options). ## Integrating a Youtube video @@ -64,3 +64,13 @@ For instance, if you want an iFrame to be able to go in fullscreen, you will use
The generated iFrame will have the allow attribute set to: <iframe allow="fullscreen">
+ +### Open a Jitsi with a co-website + +Cowebsites allow you to have several sites open at the same time. + +If you want to open a Jitsi and another page it's easy! + +You have just to [add a Jitsi to the map](meeting-rooms.md) and [add a co-website](opening-a-website.md#the-openwebsite-property) on the same layer. + +It's done! diff --git a/front/dist/index.tmpl.html b/front/dist/index.tmpl.html index 0c89b611..3b43a5ef 100644 --- a/front/dist/index.tmpl.html +++ b/front/dist/index.tmpl.html @@ -37,6 +37,54 @@
+
+
+
+
+ + +
+
+
+ +
+
+
+
+ +
+
+ + +
+
+
+ +
+
+
+ +
+
+ + +
+
+
+ +
+
+
+ +
+
+ + +
+
+
+
+
@@ -48,19 +96,24 @@
+
diff --git a/front/src/Api/Events/ChangeLayerEvent.ts b/front/src/Api/Events/ChangeLayerEvent.ts new file mode 100644 index 00000000..77ff8ede --- /dev/null +++ b/front/src/Api/Events/ChangeLayerEvent.ts @@ -0,0 +1,11 @@ +import * as tg from "generic-type-guard"; + +export const isChangeLayerEvent = new tg.IsInterface() + .withProperties({ + name: tg.isString, + }) + .get(); +/** + * A message sent from the game to the iFrame when a user enters or leaves a layer. + */ +export type ChangeLayerEvent = tg.GuardedType; diff --git a/front/src/Api/Events/CloseCoWebsiteEvent.ts b/front/src/Api/Events/CloseCoWebsiteEvent.ts new file mode 100644 index 00000000..94167d5e --- /dev/null +++ b/front/src/Api/Events/CloseCoWebsiteEvent.ts @@ -0,0 +1,12 @@ +import * as tg from "generic-type-guard"; + +export const isCloseCoWebsite = new tg.IsInterface() + .withProperties({ + id: tg.isOptional(tg.isString) + }) + .get(); + +/** + * A message sent from the iFrame to the game to add a message in the chat. + */ +export type CloseCoWebsiteEvent = tg.GuardedType; diff --git a/front/src/Api/Events/HasPlayerMovedEvent.ts b/front/src/Api/Events/HasPlayerMovedEvent.ts index 87b45482..a3f1aa21 100644 --- a/front/src/Api/Events/HasPlayerMovedEvent.ts +++ b/front/src/Api/Events/HasPlayerMovedEvent.ts @@ -6,6 +6,8 @@ export const isHasPlayerMovedEvent = new tg.IsInterface() moving: tg.isBoolean, x: tg.isNumber, y: tg.isNumber, + oldX: tg.isOptional(tg.isNumber), + oldY: tg.isOptional(tg.isNumber), }) .get(); diff --git a/front/src/Api/Events/IframeEvent.ts b/front/src/Api/Events/IframeEvent.ts index 861acc22..abb492c5 100644 --- a/front/src/Api/Events/IframeEvent.ts +++ b/front/src/Api/Events/IframeEvent.ts @@ -5,11 +5,10 @@ import type { ClosePopupEvent } from "./ClosePopupEvent"; import type { EnterLeaveEvent } from "./EnterLeaveEvent"; import type { GoToPageEvent } from "./GoToPageEvent"; import type { LoadPageEvent } from "./LoadPageEvent"; -import type { OpenCoWebSiteEvent } from "./OpenCoWebSiteEvent"; +import { isCoWebsite, isOpenCoWebsiteEvent } from "./OpenCoWebsiteEvent"; import type { OpenPopupEvent } from "./OpenPopupEvent"; import type { OpenTabEvent } from "./OpenTabEvent"; import type { UserInputChatEvent } from "./UserInputChatEvent"; -import type { MapDataEvent } from "./MapDataEvent"; import type { LayerEvent } from "./LayerEvent"; import type { SetPropertyEvent } from "./setPropertyEvent"; import type { LoadSoundEvent } from "./LoadSoundEvent"; @@ -27,12 +26,10 @@ import type { LoadTilesetEvent } from "./LoadTilesetEvent"; import { isLoadTilesetEvent } from "./LoadTilesetEvent"; import type { MessageReferenceEvent, - removeActionMessage, - triggerActionMessage, - TriggerActionMessageEvent, } from "./ui/TriggerActionMessageEvent"; import { isMessageReferenceEvent, isTriggerActionMessageEvent } from "./ui/TriggerActionMessageEvent"; import type { MenuRegisterEvent, UnregisterMenuEvent } from "./ui/MenuRegisterEvent"; +import type { ChangeLayerEvent } from "./ChangeLayerEvent"; export interface TypedMessageEvent extends MessageEvent { data: T; @@ -48,8 +45,6 @@ export type IframeEventMap = { closePopup: ClosePopupEvent; openTab: OpenTabEvent; goToPage: GoToPageEvent; - openCoWebSite: OpenCoWebSiteEvent; - closeCoWebSite: null; disablePlayerControls: null; restorePlayerControls: null; displayBubble: null; @@ -81,6 +76,8 @@ export interface IframeResponseEventMap { userInputChat: UserInputChatEvent; enterEvent: EnterLeaveEvent; leaveEvent: EnterLeaveEvent; + enterLayerEvent: ChangeLayerEvent; + leaveLayerEvent: ChangeLayerEvent; buttonClickedEvent: ButtonClickedEvent; hasPlayerMoved: HasPlayerMovedEvent; menuItemClicked: MenuItemClickedEvent; @@ -118,6 +115,22 @@ export const iframeQueryMapTypeGuards = { query: isLoadTilesetEvent, answer: tg.isNumber, }, + openCoWebsite: { + query: isOpenCoWebsiteEvent, + answer: isCoWebsite + }, + getCoWebsites: { + query: tg.isUndefined, + answer: tg.isArray(isCoWebsite) + }, + closeCoWebsite: { + query: tg.isString, + answer: tg.isUndefined + }, + closeCoWebsites: { + query: tg.isUndefined, + answer: tg.isUndefined + }, triggerActionMessage: { query: isTriggerActionMessageEvent, answer: tg.isUndefined, diff --git a/front/src/Api/Events/OpenCoWebSiteEvent.ts b/front/src/Api/Events/OpenCoWebSiteEvent.ts deleted file mode 100644 index 7b5e6070..00000000 --- a/front/src/Api/Events/OpenCoWebSiteEvent.ts +++ /dev/null @@ -1,14 +0,0 @@ -import * as tg from "generic-type-guard"; - -export const isOpenCoWebsite = new tg.IsInterface() - .withProperties({ - url: tg.isString, - allowApi: tg.isBoolean, - allowPolicy: tg.isString, - }) - .get(); - -/** - * A message sent from the iFrame to the game to add a message in the chat. - */ -export type OpenCoWebSiteEvent = tg.GuardedType; diff --git a/front/src/Api/Events/OpenCoWebsiteEvent.ts b/front/src/Api/Events/OpenCoWebsiteEvent.ts new file mode 100644 index 00000000..9c02b7a3 --- /dev/null +++ b/front/src/Api/Events/OpenCoWebsiteEvent.ts @@ -0,0 +1,22 @@ +import * as tg from "generic-type-guard"; + +export const isOpenCoWebsiteEvent = new tg.IsInterface() + .withProperties({ + url: tg.isString, + allowApi: tg.isOptional(tg.isBoolean), + allowPolicy: tg.isOptional(tg.isString), + position: tg.isOptional(tg.isNumber) + }) + .get(); + +export const isCoWebsite = new tg.IsInterface() + .withProperties({ + id: tg.isString, + position: tg.isNumber, + }) + .get(); + +/** + * A message sent from the iFrame to the game to add a message in the chat. + */ +export type OpenCoWebsiteEvent = tg.GuardedType; diff --git a/front/src/Api/IframeListener.ts b/front/src/Api/IframeListener.ts index 668b2e94..871ec3b9 100644 --- a/front/src/Api/IframeListener.ts +++ b/front/src/Api/IframeListener.ts @@ -1,6 +1,5 @@ import { Subject } from "rxjs"; -import type * as tg from "generic-type-guard"; -import { ChatEvent, isChatEvent } from "./Events/ChatEvent"; +import { isChatEvent } from "./Events/ChatEvent"; import { HtmlUtils } from "../WebRtc/HtmlUtils"; import type { EnterLeaveEvent } from "./Events/EnterLeaveEvent"; import { isOpenPopupEvent, OpenPopupEvent } from "./Events/OpenPopupEvent"; @@ -8,18 +7,15 @@ import { isOpenTabEvent, OpenTabEvent } from "./Events/OpenTabEvent"; import type { ButtonClickedEvent } from "./Events/ButtonClickedEvent"; import { ClosePopupEvent, isClosePopupEvent } from "./Events/ClosePopupEvent"; import { scriptUtils } from "./ScriptUtils"; -import { GoToPageEvent, isGoToPageEvent } from "./Events/GoToPageEvent"; -import { isOpenCoWebsite, OpenCoWebSiteEvent } from "./Events/OpenCoWebSiteEvent"; +import { isGoToPageEvent } from "./Events/GoToPageEvent"; +import { isCloseCoWebsite, CloseCoWebsiteEvent } from "./Events/CloseCoWebsiteEvent"; import { IframeErrorAnswerEvent, - IframeEvent, - IframeEventMap, IframeQueryMap, IframeResponseEvent, IframeResponseEventMap, isIframeEventWrapper, isIframeQueryWrapper, - TypedMessageEvent, } from "./Events/IframeEvent"; import type { UserInputChatEvent } from "./Events/UserInputChatEvent"; import { isPlaySoundEvent, PlaySoundEvent } from "./Events/PlaySoundEvent"; @@ -33,8 +29,8 @@ import { isMenuRegisterEvent, isUnregisterMenuEvent } from "./Events/ui/MenuRegi import { SetTilesEvent, isSetTilesEvent } from "./Events/SetTilesEvent"; import type { SetVariableEvent } from "./Events/SetVariableEvent"; import { ModifyEmbeddedWebsiteEvent, isEmbeddedWebsiteEvent } from "./Events/EmbeddedWebsiteEvent"; -import { EmbeddedWebsite } from "./iframe/Room/EmbeddedWebsite"; import { handleMenuRegistrationEvent, handleMenuUnregisterEvent } from "../Stores/MenuStore"; +import type { ChangeLayerEvent } from "./Events/ChangeLayerEvent"; type AnswererCallback = ( query: IframeQueryMap[T]["query"], @@ -53,10 +49,7 @@ class IframeListener { public readonly openTabStream = this._openTabStream.asObservable(); private readonly _loadPageStream: Subject = new Subject(); - public readonly loadPageStream = this._loadPageStream.asObservable(); - - private readonly _openCoWebSiteStream: Subject = new Subject(); - public readonly openCoWebSiteStream = this._openCoWebSiteStream.asObservable(); + public readonly loadPageStream = this._loadPageStream.asObservable() private readonly _disablePlayerControlStream: Subject = new Subject(); public readonly disablePlayerControlStream = this._disablePlayerControlStream.asObservable(); @@ -138,8 +131,6 @@ class IframeListener { return; } - foundSrc = this.getBaseUrl(foundSrc, message.source); - if (isIframeQueryWrapper(payload)) { const queryId = payload.id; const query = payload.query; @@ -224,15 +215,6 @@ class IframeListener { this._stopSoundStream.next(payload.data); } else if (payload.type === "loadSound" && isLoadSoundEvent(payload.data)) { this._loadSoundStream.next(payload.data); - } else if (payload.type === "openCoWebSite" && isOpenCoWebsite(payload.data)) { - scriptUtils.openCoWebsite( - payload.data.url, - foundSrc, - payload.data.allowApi, - payload.data.allowPolicy - ); - } else if (payload.type === "closeCoWebSite") { - scriptUtils.closeCoWebSite(); } else if (payload.type === "disablePlayerControls") { this._disablePlayerControlStream.next(); } else if (payload.type === "restorePlayerControls") { @@ -252,6 +234,9 @@ class IframeListener { this.iframeCloseCallbacks.get(iframe)?.push(() => { handleMenuUnregisterEvent(dataName); }); + + foundSrc = this.getBaseUrl(foundSrc, message.source); + handleMenuRegistrationEvent( payload.data.name, payload.data.iframe, @@ -354,6 +339,20 @@ class IframeListener { return src; } + public getBaseUrlFromSource(source: MessageEventSource): string { + let foundSrc: string | undefined; + let iframe: HTMLIFrameElement | undefined; + + for (iframe of this.iframes) { + if (iframe.contentWindow === source) { + foundSrc = iframe.src; + break; + } + } + + return this.getBaseUrl(foundSrc ?? "", source); + } + private static getIFrameId(scriptUrl: string): string { return "script" + btoa(scriptUrl); } @@ -397,6 +396,24 @@ class IframeListener { }); } + sendEnterLayerEvent(layerName: string) { + this.postMessage({ + type: "enterLayerEvent", + data: { + name: layerName, + } as ChangeLayerEvent, + }); + } + + sendLeaveLayerEvent(layerName: string) { + this.postMessage({ + type: "leaveLayerEvent", + data: { + name: layerName, + } as ChangeLayerEvent, + }); + } + hasPlayerMoved(event: HasPlayerMovedEvent) { if (this.sendPlayerMove) { this.postMessage({ diff --git a/front/src/Api/ScriptUtils.ts b/front/src/Api/ScriptUtils.ts index ad6dcc0f..10a80c92 100644 --- a/front/src/Api/ScriptUtils.ts +++ b/front/src/Api/ScriptUtils.ts @@ -1,4 +1,4 @@ -import { coWebsiteManager } from "../WebRtc/CoWebsiteManager"; +import { coWebsiteManager, CoWebsite } from "../WebRtc/CoWebsiteManager"; import { playersStore } from "../Stores/PlayersStore"; import { chatMessagesStore } from "../Stores/ChatStore"; import type { ChatEvent } from "./Events/ChatEvent"; @@ -12,14 +12,6 @@ class ScriptUtils { window.location.href = url; } - public openCoWebsite(url: string, base: string, api: boolean, policy: string) { - coWebsiteManager.loadCoWebsite(url, base, api, policy); - } - - public closeCoWebSite() { - coWebsiteManager.closeCoWebsite(); - } - public sendAnonymousChat(chatEvent: ChatEvent) { const userId = playersStore.addFacticePlayer(chatEvent.author); chatMessagesStore.addExternalMessage(userId, chatEvent.message); diff --git a/front/src/Api/iframe/nav.ts b/front/src/Api/iframe/nav.ts index f051a7c0..5acfa2a5 100644 --- a/front/src/Api/iframe/nav.ts +++ b/front/src/Api/iframe/nav.ts @@ -1,8 +1,15 @@ -import type { GoToPageEvent } from "../Events/GoToPageEvent"; -import type { OpenTabEvent } from "../Events/OpenTabEvent"; -import { IframeApiContribution, sendToWorkadventure } from "./IframeApiContribution"; -import type { OpenCoWebSiteEvent } from "../Events/OpenCoWebSiteEvent"; -import type { LoadPageEvent } from "../Events/LoadPageEvent"; +import { IframeApiContribution, sendToWorkadventure, queryWorkadventure } from "./IframeApiContribution"; + +export class CoWebsite { + constructor(private readonly id: string, public readonly position: number) {} + + close() { + return queryWorkadventure({ + type: "closeCoWebsite", + data: this.id, + }); + } +} export class WorkadventureNavigationCommands extends IframeApiContribution { callbacks = []; @@ -34,21 +41,34 @@ export class WorkadventureNavigationCommands extends IframeApiContribution { + const result = await queryWorkadventure({ + type: "openCoWebsite", data: { url, allowApi, allowPolicy, + position, }, }); + return new CoWebsite(result.id, result.position); } - closeCoWebSite(): void { - sendToWorkadventure({ - type: "closeCoWebSite", - data: null, + async getCoWebSites(): Promise { + const result = await queryWorkadventure({ + type: "getCoWebsites", + data: undefined + }); + return result.map((cowebsiteEvent) => new CoWebsite(cowebsiteEvent.id, cowebsiteEvent.position)); + } + + /** + * @deprecated Use closeCoWebsites instead to close all co-websites + */ + closeCoWebSite() { + return queryWorkadventure({ + type: "closeCoWebsites", + data: undefined, }); } } diff --git a/front/src/Api/iframe/room.ts b/front/src/Api/iframe/room.ts index 22df49c9..5eeadffe 100644 --- a/front/src/Api/iframe/room.ts +++ b/front/src/Api/iframe/room.ts @@ -1,6 +1,7 @@ import { Subject } from "rxjs"; import { EnterLeaveEvent, isEnterLeaveEvent } from "../Events/EnterLeaveEvent"; +import { ChangeLayerEvent, isChangeLayerEvent } from "../Events/ChangeLayerEvent"; import { IframeApiContribution, queryWorkadventure, sendToWorkadventure } from "./IframeApiContribution"; import { apiCallback } from "./registeredCallbacks"; @@ -12,6 +13,9 @@ import website from "./website"; const enterStreams: Map> = new Map>(); const leaveStreams: Map> = new Map>(); +const enterLayerStreams: Map> = new Map>(); +const leaveLayerStreams: Map> = new Map>(); + interface TileDescriptor { x: number; y: number; @@ -47,8 +51,25 @@ export class WorkadventureRoomCommands extends IframeApiContribution { + enterLayerStreams.get(payloadData.name)?.next(); + }, + }), + apiCallback({ + type: "leaveLayerEvent", + typeChecker: isChangeLayerEvent, + callback: (payloadData) => { + leaveLayerStreams.get(payloadData.name)?.next(); + }, + }), ]; + /** + * @deprecated Use onEnterLayer instead + */ onEnterZone(name: string, callback: () => void): void { let subject = enterStreams.get(name); if (subject === undefined) { @@ -57,6 +78,10 @@ export class WorkadventureRoomCommands extends IframeApiContribution void): void { let subject = leaveStreams.get(name); if (subject === undefined) { @@ -65,12 +90,35 @@ export class WorkadventureRoomCommands extends IframeApiContribution { + let subject = enterLayerStreams.get(layerName); + if (subject === undefined) { + subject = new Subject(); + enterLayerStreams.set(layerName, subject); + } + + return subject; + } + + onLeaveLayer(layerName: string): Subject { + let subject = leaveLayerStreams.get(layerName); + if (subject === undefined) { + subject = new Subject(); + leaveLayerStreams.set(layerName, subject); + } + + return subject; + } + showLayer(layerName: string): void { sendToWorkadventure({ type: "showLayer", data: { name: layerName } }); } + hideLayer(layerName: string): void { sendToWorkadventure({ type: "hideLayer", data: { name: layerName } }); } + setProperty(layerName: string, propertyName: string, propertyValue: string | number | boolean | undefined): void { sendToWorkadventure({ type: "setProperty", @@ -81,10 +129,12 @@ export class WorkadventureRoomCommands extends IframeApiContribution { const event = await queryWorkadventure({ type: "getMapData", data: undefined }); return event.data as ITiledMap; } + setTiles(tiles: TileDescriptor[]) { sendToWorkadventure({ type: "setTiles", diff --git a/front/src/Components/Chat/ChatPlayerName.svelte b/front/src/Components/Chat/ChatPlayerName.svelte index 9b0630c0..6611a253 100644 --- a/front/src/Components/Chat/ChatPlayerName.svelte +++ b/front/src/Components/Chat/ChatPlayerName.svelte @@ -1,6 +1,6 @@ @@ -63,6 +64,7 @@

Share the link of the room !

+

Information on the map

diff --git a/front/src/Components/Menu/ContactSubMenu.svelte b/front/src/Components/Menu/ContactSubMenu.svelte index 61ecc56e..3cf1f5fb 100644 --- a/front/src/Components/Menu/ContactSubMenu.svelte +++ b/front/src/Components/Menu/ContactSubMenu.svelte @@ -1,10 +1,60 @@ - +
+
+
+

Getting started

+

+ WorkAdventure allows you to create an online space to communicate spontaneously with others. + And it all starts with creating your own space. Choose from a large selection of prefabricated maps by our team. +

+ +
+ +
+

Create your map

+

You can also create your own custom map by following the step of the documentation.

+ +
+ + +
+
diff --git a/front/src/Components/Menu/GuestSubMenu.svelte b/front/src/Components/Menu/GuestSubMenu.svelte new file mode 100644 index 00000000..13a7981a --- /dev/null +++ b/front/src/Components/Menu/GuestSubMenu.svelte @@ -0,0 +1,75 @@ + + +
+
+ +
+

Share the link of the room !

+ + +
+
+
+ + \ No newline at end of file diff --git a/front/src/Components/Menu/Menu.svelte b/front/src/Components/Menu/Menu.svelte index 6cbef9c1..83715304 100644 --- a/front/src/Components/Menu/Menu.svelte +++ b/front/src/Components/Menu/Menu.svelte @@ -2,11 +2,11 @@ import {fly} from "svelte/transition"; import SettingsSubMenu from "./SettingsSubMenu.svelte"; import ProfileSubMenu from "./ProfileSubMenu.svelte"; - import CreateMapSubMenu from "./CreateMapSubMenu.svelte"; import AboutRoomSubMenu from "./AboutRoomSubMenu.svelte"; import GlobalMessageSubMenu from "./GlobalMessagesSubMenu.svelte"; import ContactSubMenu from "./ContactSubMenu.svelte"; import CustomSubMenu from "./CustomSubMenu.svelte" + import GuestSubMenu from "./GuestSubMenu.svelte"; import { checkSubMenuToShow, customMenuIframe, @@ -19,21 +19,21 @@ import type {Unsubscriber} from "svelte/store"; import {sendMenuClickedEvent} from "../../Api/iframe/Ui/MenuItem"; - let activeSubMenu: string = SubMenusInterface.settings; - let activeComponent: typeof SettingsSubMenu | typeof CustomSubMenu = SettingsSubMenu; + let activeSubMenu: string = SubMenusInterface.profile; + let activeComponent: typeof ProfileSubMenu | typeof CustomSubMenu = ProfileSubMenu; let props: { url: string, allowApi: boolean }; let unsubscriberSubMenuStore: Unsubscriber; onMount(() => { unsubscriberSubMenuStore = subMenusStore.subscribe(() => { if(!get(subMenusStore).includes(activeSubMenu)) { - switchMenu(SubMenusInterface.settings); + switchMenu(SubMenusInterface.profile); } }) checkSubMenuToShow(); - switchMenu(SubMenusInterface.settings); + switchMenu(SubMenusInterface.profile); }) onDestroy(() => { @@ -52,8 +52,8 @@ case SubMenusInterface.profile: activeComponent = ProfileSubMenu; break; - case SubMenusInterface.createMap: - activeComponent = CreateMapSubMenu; + case SubMenusInterface.invite: + activeComponent = GuestSubMenu; break; case SubMenusInterface.aboutRoom: activeComponent = AboutRoomSubMenu; @@ -124,6 +124,7 @@ top: 10%; position: relative; + z-index: 80; margin: auto; display: grid; diff --git a/front/src/Components/Menu/MenuIcon.svelte b/front/src/Components/Menu/MenuIcon.svelte index 02d6eb53..4b37238f 100644 --- a/front/src/Components/Menu/MenuIcon.svelte +++ b/front/src/Components/Menu/MenuIcon.svelte @@ -11,15 +11,9 @@ function showChat(){ chatVisibilityStore.set(true); } - - function onKeyDown(e: KeyboardEvent) { - if (e.key === "Tab") { - showMenu(); - } - } - +
open menu @@ -29,6 +23,8 @@ + + +
+
+ +
+
+ Profile validated by domain: ${domain} +
+
+ Your email: ${email} +
+
+ + + `; + } +} diff --git a/pusher/src/Enum/EnvironmentVariable.ts b/pusher/src/Enum/EnvironmentVariable.ts index ad369a17..3b55579f 100644 --- a/pusher/src/Enum/EnvironmentVariable.ts +++ b/pusher/src/Enum/EnvironmentVariable.ts @@ -16,6 +16,9 @@ export const FRONT_URL = process.env.FRONT_URL || "http://localhost"; export const OPID_CLIENT_ID = process.env.OPID_CLIENT_ID || ""; export const OPID_CLIENT_SECRET = process.env.OPID_CLIENT_SECRET || ""; export const OPID_CLIENT_ISSUER = process.env.OPID_CLIENT_ISSUER || ""; +export const OPID_CLIENT_REDIRECT_URL = process.env.OPID_CLIENT_REDIRECT_URL || FRONT_URL + "/jwt"; +export const OPID_PROFILE_SCREEN_PROVIDER = process.env.OPID_PROFILE_SCREEN_PROVIDER || ADMIN_URL + "/profile"; +export const DISABLE_ANONYMOUS = process.env.DISABLE_ANONYMOUS || false; export { SECRET_KEY, diff --git a/pusher/src/Services/AdminApi.ts b/pusher/src/Services/AdminApi.ts index e53d00ae..6e1848eb 100644 --- a/pusher/src/Services/AdminApi.ts +++ b/pusher/src/Services/AdminApi.ts @@ -1,4 +1,4 @@ -import { ADMIN_API_TOKEN, ADMIN_API_URL, ADMIN_URL } from "../Enum/EnvironmentVariable"; +import { ADMIN_API_TOKEN, ADMIN_API_URL, ADMIN_URL, OPID_PROFILE_SCREEN_PROVIDER } from "../Enum/EnvironmentVariable"; import Axios from "axios"; import { GameRoomPolicyTypes } from "_Model/PusherRoom"; import { CharacterTexture } from "./AdminApi/CharacterTexture"; @@ -142,13 +142,19 @@ class AdminApi { }); } - /*TODO add constant to use profile companny*/ + /** + * + * @param accessToken + */ getProfileUrl(accessToken: string): string { - if (!ADMIN_URL) { + if (!OPID_PROFILE_SCREEN_PROVIDER) { throw new Error("No admin backoffice set!"); } + return `${OPID_PROFILE_SCREEN_PROVIDER}?accessToken=${accessToken}`; + } - return ADMIN_URL + `/profile?token=${accessToken}`; + async logoutOauth(token: string) { + await Axios.get(ADMIN_API_URL + `/oauth/logout?token=${token}`); } } diff --git a/pusher/src/Services/AdminApi/MapDetailsData.ts b/pusher/src/Services/AdminApi/MapDetailsData.ts index 278b81bb..7a1f57ff 100644 --- a/pusher/src/Services/AdminApi/MapDetailsData.ts +++ b/pusher/src/Services/AdminApi/MapDetailsData.ts @@ -16,6 +16,7 @@ export const isMapDetailsData = new tg.IsInterface() tags: tg.isArray(tg.isString), textures: tg.isArray(isCharacterTexture), contactPage: tg.isUnion(tg.isString, tg.isUndefined), + authenticationMandatory: tg.isUnion(tg.isBoolean, tg.isUndefined), }) .get(); diff --git a/pusher/src/Services/JWTTokenManager.ts b/pusher/src/Services/JWTTokenManager.ts index fe418475..40b5b824 100644 --- a/pusher/src/Services/JWTTokenManager.ts +++ b/pusher/src/Services/JWTTokenManager.ts @@ -6,7 +6,7 @@ import { adminApi, AdminBannedData } from "../Services/AdminApi"; export interface AuthTokenData { identifier: string; //will be a email if logged in or an uuid if anonymous - hydraAccessToken?: string; + accessToken?: string; } export interface AdminSocketTokenData { authorizedRoomIds: string[]; //the list of rooms the client is authorized to read from. @@ -18,8 +18,8 @@ class JWTTokenManager { return Jwt.verify(token, ADMIN_SOCKETS_TOKEN) as AdminSocketTokenData; } - public createAuthToken(identifier: string, hydraAccessToken?: string) { - return Jwt.sign({ identifier, hydraAccessToken }, SECRET_KEY, { expiresIn: "30d" }); + public createAuthToken(identifier: string, accessToken?: string) { + return Jwt.sign({ identifier, accessToken }, SECRET_KEY, { expiresIn: "30d" }); } public verifyJWTToken(token: string, ignoreExpiration: boolean = false): AuthTokenData { diff --git a/pusher/src/Services/OpenIDClient.ts b/pusher/src/Services/OpenIDClient.ts index c9137ad5..13bf6f76 100644 --- a/pusher/src/Services/OpenIDClient.ts +++ b/pusher/src/Services/OpenIDClient.ts @@ -1,7 +1,10 @@ import { Issuer, Client, IntrospectionResponse } from "openid-client"; -import { OPID_CLIENT_ID, OPID_CLIENT_SECRET, OPID_CLIENT_ISSUER, FRONT_URL } from "../Enum/EnvironmentVariable"; - -const opidRedirectUri = FRONT_URL + "/jwt"; +import { + OPID_CLIENT_ID, + OPID_CLIENT_SECRET, + OPID_CLIENT_ISSUER, + OPID_CLIENT_REDIRECT_URL, +} from "../Enum/EnvironmentVariable"; class OpenIDClient { private issuerPromise: Promise | null = null; @@ -12,7 +15,7 @@ class OpenIDClient { return new issuer.Client({ client_id: OPID_CLIENT_ID, client_secret: OPID_CLIENT_SECRET, - redirect_uris: [opidRedirectUri], + redirect_uris: [OPID_CLIENT_REDIRECT_URL], response_types: ["code"], }); }); @@ -35,7 +38,7 @@ class OpenIDClient { public getUserInfo(code: string, nonce: string): Promise<{ email: string; sub: string; access_token: string }> { return this.initClient().then((client) => { - return client.callback(opidRedirectUri, { code }, { nonce }).then((tokenSet) => { + return client.callback(OPID_CLIENT_REDIRECT_URL, { code }, { nonce }).then((tokenSet) => { return client.userinfo(tokenSet).then((res) => { return { ...res, diff --git a/pusher/src/Services/SocketManager.ts b/pusher/src/Services/SocketManager.ts index 119596fd..1761f1bd 100644 --- a/pusher/src/Services/SocketManager.ts +++ b/pusher/src/Services/SocketManager.ts @@ -231,12 +231,12 @@ export class SocketManager implements ZoneEventListener { try { client.viewport = viewport; - const world = this.rooms.get(client.roomId); - if (!world) { + const room = this.rooms.get(client.roomId); + if (!room) { console.error("In SET_VIEWPORT, could not find world with id '", client.roomId, "'"); return; } - world.setViewport(client, client.viewport); + room.setViewport(client, client.viewport); } catch (e) { console.error('An error occurred on "SET_VIEWPORT" event'); console.error(e);