diff --git a/.github/workflows/continuous_integration.yml b/.github/workflows/continuous_integration.yml index 1ad73027..ea67c3c1 100644 --- a/.github/workflows/continuous_integration.yml +++ b/.github/workflows/continuous_integration.yml @@ -39,7 +39,7 @@ jobs: working-directory: "messages" - name: "Build proto messages" - run: yarn run proto && yarn run copy-to-front && yarn run json-copy-to-front + run: yarn run ts-proto && yarn run copy-to-front-ts-proto && yarn run json-copy-to-front working-directory: "messages" - name: "Create index.html" diff --git a/.github/workflows/end_to_end_tests.yml b/.github/workflows/end_to_end_tests.yml index a1cb5e5d..ea9ba41c 100644 --- a/.github/workflows/end_to_end_tests.yml +++ b/.github/workflows/end_to_end_tests.yml @@ -12,6 +12,7 @@ on: jobs: start-runner: + if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == false) name: Start self-hosted EC2 runner runs-on: ubuntu-latest outputs: @@ -109,12 +110,14 @@ jobs: if: ${{ always() }} # required to stop the runner even if the error happened in the previous jobs steps: - name: Configure AWS credentials + if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == false) uses: aws-actions/configure-aws-credentials@v1 with: aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} aws-region: ${{ secrets.AWS_REGION }} - name: Stop EC2 runner + if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == false) uses: machulav/ec2-github-runner@v2 with: mode: stop diff --git a/.github/workflows/push-to-npm.yml b/.github/workflows/push-to-npm.yml index 71f2824f..571a16e6 100644 --- a/.github/workflows/push-to-npm.yml +++ b/.github/workflows/push-to-npm.yml @@ -36,7 +36,7 @@ jobs: working-directory: "messages" - name: "Build proto messages" - run: yarn run proto && yarn run copy-to-front && yarn run json-copy-to-front + run: yarn run ts-proto && yarn run copy-to-front-ts-proto && yarn run json-copy-to-front working-directory: "messages" - name: "Create index.html" diff --git a/back/server.ts b/back/server.ts index 4ed443b8..1e474ec9 100644 --- a/back/server.ts +++ b/back/server.ts @@ -1,15 +1,15 @@ // lib/server.ts import App from "./src/App"; import grpc from "grpc"; -import {roomManager} from "./src/RoomManager"; -import {IRoomManagerServer, RoomManagerService} from "./src/Messages/generated/messages_grpc_pb"; -import {HTTP_PORT, GRPC_PORT} from "./src/Enum/EnvironmentVariable"; +import { roomManager } from "./src/RoomManager"; +import { IRoomManagerServer, RoomManagerService } from "./src/Messages/generated/messages_grpc_pb"; +import { HTTP_PORT, GRPC_PORT } from "./src/Enum/EnvironmentVariable"; -App.listen(HTTP_PORT, () => console.log(`WorkAdventure HTTP API starting on port %d!`, HTTP_PORT)) +App.listen(HTTP_PORT, () => console.log(`WorkAdventure HTTP API starting on port %d!`, HTTP_PORT)); const server = new grpc.Server(); server.addService(RoomManagerService, roomManager); server.bind(`0.0.0.0:${GRPC_PORT}`, grpc.ServerCredentials.createInsecure()); server.start(); -console.log('WorkAdventure HTTP/2 API starting on port %d!', GRPC_PORT); +console.log("WorkAdventure HTTP/2 API starting on port %d!", GRPC_PORT); diff --git a/back/src/RoomManager.ts b/back/src/RoomManager.ts index 3bb425b7..d375fbd8 100644 --- a/back/src/RoomManager.ts +++ b/back/src/RoomManager.ts @@ -106,11 +106,6 @@ const roomManager: IRoomManagerServer = { user, message.getWebrtcscreensharingsignaltoservermessage() as WebRtcSignalToServerMessage ); - } else if (message.hasPlayglobalmessage()) { - socketManager.emitPlayGlobalMessage( - room, - message.getPlayglobalmessage() as PlayGlobalMessage - ); } else if (message.hasQueryjitsijwtmessage()) { socketManager.handleQueryJitsiJwtMessage( user, diff --git a/back/src/Services/SocketManager.ts b/back/src/Services/SocketManager.ts index c9da7c96..9233811b 100644 --- a/back/src/Services/SocketManager.ts +++ b/back/src/Services/SocketManager.ts @@ -531,15 +531,6 @@ export class SocketManager { } } - emitPlayGlobalMessage(room: GameRoom, playGlobalMessage: PlayGlobalMessage) { - const serverToClientMessage = new ServerToClientMessage(); - serverToClientMessage.setPlayglobalmessage(playGlobalMessage); - - for (const [id, user] of room.getUsers().entries()) { - user.socket.write(serverToClientMessage); - } - } - public getWorlds(): Map> { return this.roomsPromises; } diff --git a/docs/maps/api-camera.md b/docs/maps/api-camera.md new file mode 100644 index 00000000..cb1fe72d --- /dev/null +++ b/docs/maps/api-camera.md @@ -0,0 +1,24 @@ +{.section-title.accent.text-primary} +# API Camera functions Reference + +### Listen to camera updates + +``` +WA.camera.onCameraUpdate(): Subscription +``` + +Listens to updates of the camera viewport. It will trigger for every update of the camera's properties (position or scale for instance). An event will be sent. + +The event has the following attributes : +* **x (number):** coordinate X of the camera's world view (the area looked at by the camera). +* **y (number):** coordinate Y of the camera's world view. +* **width (number):** the width of the camera's world view. +* **height (number):** the height of the camera's world view. + +**callback:** the function that will be called when the camera is updated. + +Example : +```javascript +const subscription = WA.camera.onCameraUpdate().subscribe((worldView) => console.log(worldView)); +//later... +subscription.unsubscribe(); \ No newline at end of file diff --git a/docs/maps/api-player.md b/docs/maps/api-player.md index 35d5f464..58d5701a 100644 --- a/docs/maps/api-player.md +++ b/docs/maps/api-player.md @@ -86,6 +86,27 @@ WA.onInit().then(() => { }) ``` +### Get the position of the player +``` +WA.player.getPosition(): Promise +``` +The player's current position is available using the `WA.player.getPosition()` function. + +`Position` has the following attributes : +* **x (number) :** The coordinate x of the current player's position. +* **y (number) :** The coordinate y of the current player's position. + + +{.alert.alert-info} +You need to wait for the end of the initialization before calling `WA.player.getPosition()` + +```typescript +WA.onInit().then(async () => { + console.log('Position: ', await WA.player.getPosition()); +}) +``` + + ### Listen to player movement ``` WA.player.onPlayerMove(callback: HasPlayerMovedEventCallback): void; @@ -107,6 +128,30 @@ Example : WA.player.onPlayerMove(console.log); ``` +## Player specific variables +Similarly to maps (see [API state related functions](api-state.md)), it is possible to store data **related to a specific player** in a "state". Such data will be stored using the local storage from the user's browser. Any value that is serializable in JSON can be stored. + +{.alert.alert-info} +In the future, player-related variables will be stored on the WorkAdventure server if the current player is logged. + +Any value that is serializable in JSON can be stored. + +### Setting a property +A player property can be set simply by assigning a value. + +Example: +```javascript +WA.player.state.toto = "value" //will set the "toto" key to "value" +``` + +### Reading a variable +A player variable can be read by calling its key from the player's state. + +Example: +```javascript +WA.player.state.toto //will retrieve the variable +``` + ### Set the outline color of the player ``` WA.player.setOutlineColor(red: number, green: number, blue: number): Promise; diff --git a/docs/maps/api-reference.md b/docs/maps/api-reference.md index d044668f..a0869075 100644 --- a/docs/maps/api-reference.md +++ b/docs/maps/api-reference.md @@ -10,5 +10,6 @@ - [UI functions](api-ui.md) - [Sound functions](api-sound.md) - [Controls functions](api-controls.md) +- [Camera functions](api-camera.md) - [List of deprecated functions](api-deprecated.md) diff --git a/docs/maps/api-room.md b/docs/maps/api-room.md index 72947df8..7d438a1f 100644 --- a/docs/maps/api-room.md +++ b/docs/maps/api-room.md @@ -1,8 +1,11 @@ {.section-title.accent.text-primary} + # API Room functions Reference ### Working with group layers -If you use group layers in your map, to reference a layer in a group you will need to use a `/` to join layer names together. + +If you use group layers in your map, to reference a layer in a group you will need to use a `/` to join layer names +together. Example :
@@ -12,6 +15,7 @@ Example :
The name of the layers of this map are : + * `entries/start` * `bottom/ground/under` * `bottom/build/carpet` @@ -26,29 +30,32 @@ 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 layer. -* **name**: the name of the layer who as defined in Tiled. +* **name**: the name of the layer who as defined in Tiled. Example: ```javascript WA.room.onEnterLayer('myLayer').subscribe(() => { - WA.chat.sendChatMessage("Hello!", 'Mr Robot'); + WA.chat.sendChatMessage("Hello!", 'Mr Robot'); }); WA.room.onLeaveLayer('myLayer').subscribe(() => { - WA.chat.sendChatMessage("Goodbye!", 'Mr Robot'); + WA.chat.sendChatMessage("Goodbye!", 'Mr Robot'); }); ``` ### Show / Hide a layer + ``` WA.room.showLayer(layerName : string): void WA.room.hideLayer(layerName : string) : void ``` -These 2 methods can be used to show and hide a layer. -if `layerName` is the name of a group layer, show/hide all the layer in that group layer. + +These 2 methods can be used to show and hide a layer. if `layerName` is the name of a group layer, show/hide all the +layer in that group layer. Example : + ```javascript WA.room.showLayer('bottom'); //... @@ -61,12 +68,14 @@ WA.room.hideLayer('bottom'); WA.room.setProperty(layerName : string, propertyName : string, propertyValue : string | number | boolean | undefined) : void; ``` -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`. +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 : To unset a property from a layer, use `setProperty` with `propertyValue` set to `undefined`. Example : + ```javascript WA.room.setProperty('wikiLayer', 'openWebsite', 'https://www.wikipedia.org/'); ``` @@ -79,13 +88,12 @@ WA.room.id: string; The ID of the current room is available from the `WA.room.id` property. -{.alert.alert-info} -You need to wait for the end of the initialization before accessing `WA.room.id` +{.alert.alert-info} You need to wait for the end of the initialization before accessing `WA.room.id` ```typescript WA.onInit().then(() => { - console.log('Room id: ', WA.room.id); - // Will output something like: 'https://play.workadventu.re/@/myorg/myworld/myroom', or 'https://play.workadventu.re/_/global/mymap.org/map.json" + console.log('Room id: ', WA.room.id); + // Will output something like: 'https://play.workadventu.re/@/myorg/myworld/myroom', or 'https://play.workadventu.re/_/global/mymap.org/map.json" }) ``` @@ -97,19 +105,17 @@ WA.room.mapURL: string; The URL of the map is available from the `WA.room.mapURL` property. -{.alert.alert-info} -You need to wait for the end of the initialization before accessing `WA.room.mapURL` +{.alert.alert-info} You need to wait for the end of the initialization before accessing `WA.room.mapURL` ```typescript WA.onInit().then(() => { - console.log('Map URL: ', WA.room.mapURL); - // Will output something like: 'https://mymap.org/map.json" + console.log('Map URL: ', WA.room.mapURL); + // Will output something like: 'https://mymap.org/map.json" }) ``` - - ### Getting map data + ``` WA.room.getTiledMap(): Promise ``` @@ -121,12 +127,16 @@ const map = await WA.room.getTiledMap(); 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/). +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 + ``` WA.room.setTiles(tiles: TileDescriptor[]): void ``` + Replace the tile at the `x` and `y` coordinates in the layer named `layer` by the tile with the id `tile`. If `tile` is a string, it's not the id of the tile but the value of the property `name`. @@ -137,43 +147,48 @@ If `tile` is a string, it's not the id of the tile but the value of the property `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. * **layer (string) :** The name of the layer where the tile will be placed. -**Important !** : If you use `tile` as a number, be sure to add the `firstgid` of the tileset of the tile that you want to the id of the tile in Tiled Editor. +**Important !** : If you use `tile` as a number, be sure to add the `firstgid` of the tileset of the tile that you want +to the id of the tile in Tiled Editor. Note: If you want to unset a tile, use `setTiles` with `tile` set to `null`. Example : + ```javascript WA.room.setTiles([ - {x: 6, y: 4, tile: 'blue', layer: 'setTiles'}, - {x: 7, y: 4, tile: 109, layer: 'setTiles'}, - {x: 8, y: 4, tile: 109, layer: 'setTiles'}, - {x: 9, y: 4, tile: 'blue', layer: 'setTiles'} - ]); + { x: 6, y: 4, tile: 'blue', layer: 'setTiles' }, + { x: 7, y: 4, tile: 109, layer: 'setTiles' }, + { x: 8, y: 4, tile: 109, layer: 'setTiles' }, + { x: 9, y: 4, tile: 'blue', layer: 'setTiles' } +]); ``` ### Loading a tileset + ``` WA.room.loadTileset(url: string): Promise ``` + Load a tileset in JSON format from an url and return the id of the first tile of the loaded tileset. You can create a tileset file in Tile Editor. ```javascript WA.room.loadTileset("Assets/Tileset.json").then((firstId) => { - WA.room.setTiles([{x: 4, y: 4, tile: firstId, layer: 'bottom'}]); + WA.room.setTiles([{ x: 4, y: 4, tile: firstId, layer: 'bottom' }]); }) ``` - ## Embedding websites in a map -You can use the scripting API to embed websites in a map, or to edit websites that are already embedded (using the ["website" objects](website-in-map.md)). +You can use the scripting API to embed websites in a map, or to edit websites that are already embedded (using +the ["website" objects](website-in-map.md)). ### Getting an instance of a website already embedded in the map @@ -181,8 +196,8 @@ You can use the scripting API to embed websites in a map, or to edit websites th WA.room.website.get(objectName: string): Promise ``` -You can get an instance of an embedded website by using the `WA.room.website.get()` method. -It returns a promise of an `EmbeddedWebsite` instance. +You can get an instance of an embedded website by using the `WA.room.website.get()` method. It returns a promise of +an `EmbeddedWebsite` instance. ```javascript // Get an existing website object where 'my_website' is the name of the object (on any layer object of the map) @@ -191,7 +206,6 @@ website.url = 'https://example.com'; website.visible = true; ``` - ### Adding a new website in a map ``` @@ -201,34 +215,38 @@ interface CreateEmbeddedWebsiteEvent { name: string; // A unique name for this iframe url: string; // The URL the iframe points to. position: { - x: number, // In pixels, relative to the map coordinates - y: number, // In pixels, relative to the map coordinates - width: number, // In pixels, sensitive to zoom level - height: number, // In pixels, sensitive to zoom level + x: number, // In "game" pixels, relative to the map or player coordinates, depending on origin + y: number, // In "game" pixels, relative to the map or player coordinates, depending on origin + width: number, // In "game" pixels + height: number, // In "game" pixels }, visible?: boolean, // Whether to display the iframe or not allowApi?: boolean, // Whether the scripting API should be available to the iframe allow?: string, // The list of feature policies allowed + origin: "player" | "map" // The origin used to place the x and y coordinates of the iframe's top-left corner, defaults to "map" + scale: number, // A ratio used to resize the iframe } ``` -You can create an instance of an embedded website by using the `WA.room.website.create()` method. -It returns an `EmbeddedWebsite` instance. +You can create an instance of an embedded website by using the `WA.room.website.create()` method. It returns +an `EmbeddedWebsite` instance. ```javascript // Create a new website object const website = WA.room.website.create({ - name: "my_website", - url: "https://example.com", - position: { - x: 64, - y: 128, - width: 320, - height: 240, - }, - visible: true, - allowApi: true, - allow: "fullscreen", + name: "my_website", + url: "https://example.com", + position: { + x: 64, + y: 128, + width: 320, + height: 240, + }, + visible: true, + allowApi: true, + allow: "fullscreen", + origin: "map", + scale: 1, }); ``` @@ -240,30 +258,28 @@ WA.room.website.delete(name: string): Promise Use `WA.room.website.delete` to completely remove an embedded website from your map. - ### The EmbeddedWebsite class Instances of the `EmbeddedWebsite` class represent the website displayed on the map. ```typescript class EmbeddedWebsite { - readonly name: string; - url: string; - visible: boolean; - allow: string; - allowApi: boolean; - x: number; // In pixels, relative to the map coordinates - y: number; // In pixels, relative to the map coordinates - width: number; // In pixels, sensitive to zoom level - height: number; // In pixels, sensitive to zoom level + readonly name: string; + url: string; + visible: boolean; + allow: string; + allowApi: boolean; + x: number; // In "game" pixels, relative to the map or player coordinates, depending on origin + y: number; // In "game" pixels, relative to the map or player coordinates, depending on origin + width: number; // In "game" pixels + height: number; // In "game" pixels + origin: "player" | "map"; + scale: number; } ``` When you modify a property of an `EmbeddedWebsite` instance, the iframe is automatically modified in the map. - -{.alert.alert-warning} -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. +{.alert.alert-warning} 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/front/.eslintrc.js b/front/.eslintrc.js index 117cb7e6..dc2b6bd6 100644 --- a/front/.eslintrc.js +++ b/front/.eslintrc.js @@ -35,7 +35,6 @@ module.exports = { "no-unused-vars": "off", "@typescript-eslint/no-explicit-any": "error", // TODO: remove those ignored rules and write a stronger code! - "@typescript-eslint/no-floating-promises": "off", "@typescript-eslint/no-unsafe-call": "off", "@typescript-eslint/restrict-plus-operands": "off", "@typescript-eslint/no-unsafe-assignment": "off", diff --git a/front/Dockerfile b/front/Dockerfile index f781a37c..49cf6046 100644 --- a/front/Dockerfile +++ b/front/Dockerfile @@ -1,13 +1,14 @@ FROM node:14.15.4-buster-slim@sha256:cbae886186467bbfd274b82a234a1cdfbbd31201c2a6ee63a6893eefcf3c6e76 as builder WORKDIR /usr/src COPY messages . -RUN yarn install && yarn proto +RUN yarn install && yarn ts-proto # we are rebuilding on each deploy to cope with the PUSHER_URL environment URL FROM thecodingmachine/nodejs:14-apache COPY --chown=docker:docker front . -COPY --from=builder --chown=docker:docker /usr/src/generated /var/www/html/src/Messages/generated +COPY --from=builder --chown=docker:docker /usr/src/ts-proto-generated/protos /var/www/html/src/Messages/ts-proto-generated +RUN sed -i 's/import { Observable } from "rxjs";/import type { Observable } from "rxjs";/g' /var/www/html/src/Messages/ts-proto-generated/messages.ts COPY --from=builder --chown=docker:docker /usr/src/JsonMessages /var/www/html/src/Messages/JsonMessages # Removing the iframe.html file from the final image as this adds a XSS attack. diff --git a/front/package.json b/front/package.json index eae92cd2..935c254f 100644 --- a/front/package.json +++ b/front/package.json @@ -62,6 +62,7 @@ "simple-peer": "^9.11.0", "socket.io-client": "^2.3.0", "standardized-audio-context": "^25.2.4", + "ts-proto": "^1.96.0", "uuidv4": "^6.2.10" }, "scripts": { diff --git a/front/src/Administration/AnalyticsClient.ts b/front/src/Administration/AnalyticsClient.ts index fb2b604b..4c1ca93a 100644 --- a/front/src/Administration/AnalyticsClient.ts +++ b/front/src/Administration/AnalyticsClient.ts @@ -18,64 +18,84 @@ class AnalyticsClient { } identifyUser(uuid: string, email: string | null) { - this.posthogPromise?.then((posthog) => { - posthog.identify(uuid, { uuid, email, wa: true }); - }); + this.posthogPromise + ?.then((posthog) => { + posthog.identify(uuid, { uuid, email, wa: true }); + }) + .catch((e) => console.error(e)); } loggedWithSso() { - this.posthogPromise?.then((posthog) => { - posthog.capture("wa-logged-sso"); - }); + this.posthogPromise + ?.then((posthog) => { + posthog.capture("wa-logged-sso"); + }) + .catch((e) => console.error(e)); } loggedWithToken() { - this.posthogPromise?.then((posthog) => { - posthog.capture("wa-logged-token"); - }); + this.posthogPromise + ?.then((posthog) => { + posthog.capture("wa-logged-token"); + }) + .catch((e) => console.error(e)); } enteredRoom(roomId: string, roomGroup: string | null) { - this.posthogPromise?.then((posthog) => { - posthog.capture("$pageView", { roomId, roomGroup }); - posthog.capture("enteredRoom"); - }); + this.posthogPromise + ?.then((posthog) => { + posthog.capture("$pageView", { roomId, roomGroup }); + posthog.capture("enteredRoom"); + }) + .catch((e) => console.error(e)); } openedMenu() { - this.posthogPromise?.then((posthog) => { - posthog.capture("wa-opened-menu"); - }); + this.posthogPromise + ?.then((posthog) => { + posthog.capture("wa-opened-menu"); + }) + .catch((e) => console.error(e)); } launchEmote(emote: string) { - this.posthogPromise?.then((posthog) => { - posthog.capture("wa-emote-launch", { emote }); - }); + this.posthogPromise + ?.then((posthog) => { + posthog.capture("wa-emote-launch", { emote }); + }) + .catch((e) => console.error(e)); } enteredJitsi(roomName: string, roomId: string) { - this.posthogPromise?.then((posthog) => { - posthog.capture("wa-entered-jitsi", { roomName, roomId }); - }); + this.posthogPromise + ?.then((posthog) => { + posthog.capture("wa-entered-jitsi", { roomName, roomId }); + }) + .catch((e) => console.error(e)); } validationName() { - this.posthogPromise?.then((posthog) => { - posthog.capture("wa-name-validation"); - }); + this.posthogPromise + ?.then((posthog) => { + posthog.capture("wa-name-validation"); + }) + .catch((e) => console.error(e)); } validationWoka(scene: string) { - this.posthogPromise?.then((posthog) => { - posthog.capture("wa-woka-validation", { scene }); - }); + this.posthogPromise + ?.then((posthog) => { + posthog.capture("wa-woka-validation", { scene }); + }) + .catch((e) => console.error(e)); } validationVideo() { - this.posthogPromise?.then((posthog) => { - posthog.capture("wa-video-validation"); - }); + this.posthogPromise + ?.then((posthog) => { + posthog.capture("wa-video-validation"); + }) + .catch((e) => console.error(e)); } } export const analyticsClient = new AnalyticsClient(); diff --git a/front/src/Api/Events/EmbeddedWebsiteEvent.ts b/front/src/Api/Events/EmbeddedWebsiteEvent.ts index 42630be1..57c24853 100644 --- a/front/src/Api/Events/EmbeddedWebsiteEvent.ts +++ b/front/src/Api/Events/EmbeddedWebsiteEvent.ts @@ -22,6 +22,8 @@ export const isEmbeddedWebsiteEvent = new tg.IsInterface() y: tg.isNumber, width: tg.isNumber, height: tg.isNumber, + origin: tg.isSingletonStringUnion("player", "map"), + scale: tg.isNumber, }) .get(); @@ -35,6 +37,8 @@ export const isCreateEmbeddedWebsiteEvent = new tg.IsInterface() visible: tg.isBoolean, allowApi: tg.isBoolean, allow: tg.isString, + origin: tg.isSingletonStringUnion("player", "map"), + scale: tg.isNumber, }) .get(); diff --git a/front/src/Api/Events/GameStateEvent.ts b/front/src/Api/Events/GameStateEvent.ts index 1f0f36ed..9755ba9e 100644 --- a/front/src/Api/Events/GameStateEvent.ts +++ b/front/src/Api/Events/GameStateEvent.ts @@ -10,6 +10,7 @@ export const isGameStateEvent = new tg.IsInterface() tags: tg.isArray(tg.isString), variables: tg.isObject, userRoomToken: tg.isUnion(tg.isString, tg.isUndefined), + playerVariables: tg.isObject, }) .get(); /** diff --git a/front/src/Api/Events/IframeEvent.ts b/front/src/Api/Events/IframeEvent.ts index 2871b93c..8fb488dc 100644 --- a/front/src/Api/Events/IframeEvent.ts +++ b/front/src/Api/Events/IframeEvent.ts @@ -30,6 +30,8 @@ import type { MenuRegisterEvent, UnregisterMenuEvent } from "./ui/MenuRegisterEv import type { ChangeLayerEvent } from "./ChangeLayerEvent"; import type { ChangeZoneEvent } from "./ChangeZoneEvent"; import { isColorEvent } from "./ColorEvent"; +import { isPlayerPosition } from "./PlayerPosition"; +import type { WasCameraUpdatedEvent } from "./WasCameraUpdatedEvent"; export interface TypedMessageEvent extends MessageEvent { data: T; @@ -50,6 +52,7 @@ export type IframeEventMap = { displayBubble: null; removeBubble: null; onPlayerMove: undefined; + onCameraUpdate: undefined; showLayer: LayerEvent; hideLayer: LayerEvent; setProperty: SetPropertyEvent; @@ -82,6 +85,7 @@ export interface IframeResponseEventMap { leaveZoneEvent: ChangeZoneEvent; buttonClickedEvent: ButtonClickedEvent; hasPlayerMoved: HasPlayerMovedEvent; + wasCameraUpdated: WasCameraUpdatedEvent; menuItemClicked: MenuItemClickedEvent; setVariable: SetVariableEvent; messageTriggered: MessageReferenceEvent; @@ -161,6 +165,10 @@ export const iframeQueryMapTypeGuards = { query: tg.isUndefined, answer: tg.isUndefined, }, + getPlayerPosition: { + query: tg.isUndefined, + answer: isPlayerPosition, + }, }; type GuardedType = T extends (x: unknown) => x is infer T ? T : never; diff --git a/front/src/Api/Events/PlayerPosition.ts b/front/src/Api/Events/PlayerPosition.ts new file mode 100644 index 00000000..54fac6fe --- /dev/null +++ b/front/src/Api/Events/PlayerPosition.ts @@ -0,0 +1,10 @@ +import * as tg from "generic-type-guard"; + +export const isPlayerPosition = new tg.IsInterface() + .withProperties({ + x: tg.isNumber, + y: tg.isNumber, + }) + .get(); + +export type PlayerPosition = tg.GuardedType; diff --git a/front/src/Api/Events/SetVariableEvent.ts b/front/src/Api/Events/SetVariableEvent.ts index 3e2303b3..80ac6f6e 100644 --- a/front/src/Api/Events/SetVariableEvent.ts +++ b/front/src/Api/Events/SetVariableEvent.ts @@ -4,6 +4,7 @@ export const isSetVariableEvent = new tg.IsInterface() .withProperties({ key: tg.isString, value: tg.isUnknown, + target: tg.isSingletonStringUnion("global", "player"), }) .get(); /** diff --git a/front/src/Api/Events/WasCameraUpdatedEvent.ts b/front/src/Api/Events/WasCameraUpdatedEvent.ts new file mode 100644 index 00000000..34e39a84 --- /dev/null +++ b/front/src/Api/Events/WasCameraUpdatedEvent.ts @@ -0,0 +1,19 @@ +import * as tg from "generic-type-guard"; + +export const isWasCameraUpdatedEvent = new tg.IsInterface() + .withProperties({ + x: tg.isNumber, + y: tg.isNumber, + width: tg.isNumber, + height: tg.isNumber, + zoom: tg.isNumber, + }) + .get(); + +/** + * A message sent from the game to the iFrame to notify a movement from the camera. + */ + +export type WasCameraUpdatedEvent = tg.GuardedType; + +export type WasCameraUpdatedEventCallback = (event: WasCameraUpdatedEvent) => void; diff --git a/front/src/Api/IframeListener.ts b/front/src/Api/IframeListener.ts index 67b49344..216a9510 100644 --- a/front/src/Api/IframeListener.ts +++ b/front/src/Api/IframeListener.ts @@ -31,6 +31,7 @@ import type { SetVariableEvent } from "./Events/SetVariableEvent"; import { ModifyEmbeddedWebsiteEvent, isEmbeddedWebsiteEvent } from "./Events/EmbeddedWebsiteEvent"; import { handleMenuRegistrationEvent, handleMenuUnregisterEvent } from "../Stores/MenuStore"; import type { ChangeLayerEvent } from "./Events/ChangeLayerEvent"; +import type { WasCameraUpdatedEvent } from "./Events/WasCameraUpdatedEvent"; import type { ChangeZoneEvent } from "./Events/ChangeZoneEvent"; type AnswererCallback = ( @@ -85,6 +86,9 @@ class IframeListener { private readonly _loadSoundStream: Subject = new Subject(); public readonly loadSoundStream = this._loadSoundStream.asObservable(); + private readonly _trackCameraUpdateStream: Subject = new Subject(); + public readonly trackCameraUpdateStream = this._trackCameraUpdateStream.asObservable(); + private readonly _setTilesStream: Subject = new Subject(); public readonly setTilesStream = this._setTilesStream.asObservable(); @@ -226,6 +230,8 @@ class IframeListener { this._removeBubbleStream.next(); } else if (payload.type == "onPlayerMove") { this.sendPlayerMove = true; + } else if (payload.type == "onCameraUpdate") { + this._trackCameraUpdateStream.next(); } else if (payload.type == "setTiles" && isSetTilesEvent(payload.data)) { this._setTilesStream.next(payload.data); } else if (payload.type == "modifyEmbeddedWebsite" && isEmbeddedWebsiteEvent(payload.data)) { @@ -442,6 +448,13 @@ class IframeListener { } } + sendCameraUpdated(event: WasCameraUpdatedEvent) { + this.postMessage({ + type: "wasCameraUpdated", + data: event, + }); + } + sendButtonClickedEvent(popupId: number, buttonId: number): void { this.postMessage({ type: "buttonClickedEvent", diff --git a/front/src/Api/iframe/Room/EmbeddedWebsite.ts b/front/src/Api/iframe/Room/EmbeddedWebsite.ts index 7b16890e..d9c2d986 100644 --- a/front/src/Api/iframe/Room/EmbeddedWebsite.ts +++ b/front/src/Api/iframe/Room/EmbeddedWebsite.ts @@ -12,6 +12,8 @@ export class EmbeddedWebsite { private _allow: string; private _allowApi: boolean; private _position: Rectangle; + private readonly origin: "map" | "player" | undefined; + private _scale: number; constructor(private config: CreateEmbeddedWebsiteEvent) { this.name = config.name; @@ -20,6 +22,12 @@ export class EmbeddedWebsite { this._allow = config.allow ?? ""; this._allowApi = config.allowApi ?? false; this._position = config.position; + this.origin = config.origin; + this._scale = config.scale ?? 1; + } + + public get url() { + return this._url; } public set url(url: string) { @@ -33,6 +41,10 @@ export class EmbeddedWebsite { }); } + public get visible() { + return this._visible; + } + public set visible(visible: boolean) { this._visible = visible; sendToWorkadventure({ @@ -44,6 +56,10 @@ export class EmbeddedWebsite { }); } + public get x() { + return this._position.x; + } + public set x(x: number) { this._position.x = x; sendToWorkadventure({ @@ -55,6 +71,10 @@ export class EmbeddedWebsite { }); } + public get y() { + return this._position.y; + } + public set y(y: number) { this._position.y = y; sendToWorkadventure({ @@ -66,6 +86,10 @@ export class EmbeddedWebsite { }); } + public get width() { + return this._position.width; + } + public set width(width: number) { this._position.width = width; sendToWorkadventure({ @@ -77,6 +101,10 @@ export class EmbeddedWebsite { }); } + public get height() { + return this._position.height; + } + public set height(height: number) { this._position.height = height; sendToWorkadventure({ @@ -87,4 +115,19 @@ export class EmbeddedWebsite { }, }); } + + public get scale(): number { + return this._scale; + } + + public set scale(scale: number) { + this._scale = scale; + sendToWorkadventure({ + type: "modifyEmbeddedWebsite", + data: { + name: this.name, + scale: this._scale, + }, + }); + } } diff --git a/front/src/Api/iframe/Ui/ActionMessage.ts b/front/src/Api/iframe/Ui/ActionMessage.ts index 912603b9..ff0908ff 100644 --- a/front/src/Api/iframe/Ui/ActionMessage.ts +++ b/front/src/Api/iframe/Ui/ActionMessage.ts @@ -26,7 +26,7 @@ export class ActionMessage { this.message = actionMessageOptions.message; this.type = actionMessageOptions.type ?? "message"; this.callback = actionMessageOptions.callback; - this.create(); + this.create().catch((e) => console.error(e)); } private async create() { diff --git a/front/src/Api/iframe/camera.ts b/front/src/Api/iframe/camera.ts new file mode 100644 index 00000000..a832290e --- /dev/null +++ b/front/src/Api/iframe/camera.ts @@ -0,0 +1,29 @@ +import { IframeApiContribution, sendToWorkadventure } from "./IframeApiContribution"; +import { Subject } from "rxjs"; +import type { WasCameraUpdatedEvent } from "../Events/WasCameraUpdatedEvent"; +import { apiCallback } from "./registeredCallbacks"; +import { isWasCameraUpdatedEvent } from "../Events/WasCameraUpdatedEvent"; + +const moveStream = new Subject(); + +export class WorkAdventureCameraCommands extends IframeApiContribution { + callbacks = [ + apiCallback({ + type: "wasCameraUpdated", + typeChecker: isWasCameraUpdatedEvent, + callback: (payloadData) => { + moveStream.next(payloadData); + }, + }), + ]; + + onCameraUpdate(): Subject { + sendToWorkadventure({ + type: "onCameraUpdate", + data: null, + }); + return moveStream; + } +} + +export default new WorkAdventureCameraCommands(); diff --git a/front/src/Api/iframe/player.ts b/front/src/Api/iframe/player.ts index 2d187bf5..0c71ae33 100644 --- a/front/src/Api/iframe/player.ts +++ b/front/src/Api/iframe/player.ts @@ -3,6 +3,7 @@ import type { HasPlayerMovedEvent, HasPlayerMovedEventCallback } from "../Events import { Subject } from "rxjs"; import { apiCallback } from "./registeredCallbacks"; import { isHasPlayerMovedEvent } from "../Events/HasPlayerMovedEvent"; +import { createState } from "./state"; const moveStream = new Subject(); @@ -31,6 +32,8 @@ export const setUuid = (_uuid: string | undefined) => { }; export class WorkadventurePlayerCommands extends IframeApiContribution { + readonly state = createState("player"); + callbacks = [ apiCallback({ type: "hasPlayerMoved", @@ -74,6 +77,13 @@ export class WorkadventurePlayerCommands extends IframeApiContribution { + return await queryWorkadventure({ + type: "getPlayerPosition", + data: undefined, + }); + } + get userRoomToken(): string | undefined { if (userRoomToken === undefined) { throw new Error( @@ -102,4 +112,9 @@ export class WorkadventurePlayerCommands extends IframeApiContribution(); -const variables = new Map(); -const variableSubscribers = new Map>(); - -export const initVariables = (_variables: Map): void => { - for (const [name, value] of _variables.entries()) { - // In case the user already decided to put values in the variables (before onInit), let's make sure onInit does not override this. - if (!variables.has(name)) { - variables.set(name, value); - } - } -}; - -setVariableResolvers.subscribe((event) => { - const oldValue = variables.get(event.key); - // If we are setting the same value, no need to do anything. - // No need to do this check since it is already performed in SharedVariablesManager - /*if (JSON.stringify(oldValue) === JSON.stringify(event.value)) { - return; - }*/ - - variables.set(event.key, event.value); - const subject = variableSubscribers.get(event.key); - if (subject !== undefined) { - subject.next(event.value); - } -}); - export class WorkadventureStateCommands extends IframeApiContribution { + private setVariableResolvers = new Subject(); + private variables = new Map(); + private variableSubscribers = new Map>(); + + constructor(private target: "global" | "player") { + super(); + + this.setVariableResolvers.subscribe((event) => { + const oldValue = this.variables.get(event.key); + // If we are setting the same value, no need to do anything. + // No need to do this check since it is already performed in SharedVariablesManager + /*if (JSON.stringify(oldValue) === JSON.stringify(event.value)) { + return; + }*/ + + this.variables.set(event.key, event.value); + const subject = this.variableSubscribers.get(event.key); + if (subject !== undefined) { + subject.next(event.value); + } + }); + } + callbacks = [ apiCallback({ type: "setVariable", typeChecker: isSetVariableEvent, callback: (payloadData) => { - setVariableResolvers.next(payloadData); + if (payloadData.target === this.target) { + this.setVariableResolvers.next(payloadData); + } }, }), ]; + // TODO: see how we can remove this method from types exposed to WA.state object + initVariables(_variables: Map): void { + for (const [name, value] of _variables.entries()) { + // In case the user already decided to put values in the variables (before onInit), let's make sure onInit does not override this. + if (!this.variables.has(name)) { + this.variables.set(name, value); + } + } + } + saveVariable(key: string, value: unknown): Promise { - variables.set(key, value); + this.variables.set(key, value); return queryWorkadventure({ type: "setVariable", data: { key, value, + target: this.target, }, }); } loadVariable(key: string): unknown { - return variables.get(key); + return this.variables.get(key); } hasVariable(key: string): boolean { - return variables.has(key); + return this.variables.has(key); } onVariableChange(key: string): Observable { - let subject = variableSubscribers.get(key); + let subject = this.variableSubscribers.get(key); if (subject === undefined) { subject = new Subject(); - variableSubscribers.set(key, subject); + this.variableSubscribers.set(key, subject); } return subject.asObservable(); } } -const proxyCommand = new Proxy(new WorkadventureStateCommands(), { - get(target: WorkadventureStateCommands, p: PropertyKey, receiver: unknown): unknown { - if (p in target) { - return Reflect.get(target, p, receiver); - } - return target.loadVariable(p.toString()); - }, - set(target: WorkadventureStateCommands, p: PropertyKey, value: unknown, receiver: unknown): boolean { - // Note: when using "set", there is no way to wait, so we ignore the return of the promise. - // User must use WA.state.saveVariable to have error message. - target.saveVariable(p.toString(), value); - return true; - }, - has(target: WorkadventureStateCommands, p: PropertyKey): boolean { - if (p in target) { +export function createState(target: "global" | "player"): WorkadventureStateCommands & { [key: string]: unknown } { + return new Proxy(new WorkadventureStateCommands(target), { + get(target: WorkadventureStateCommands, p: PropertyKey, receiver: unknown): unknown { + if (p in target) { + return Reflect.get(target, p, receiver); + } + return target.loadVariable(p.toString()); + }, + set(target: WorkadventureStateCommands, p: PropertyKey, value: unknown, receiver: unknown): boolean { + // Note: when using "set", there is no way to wait, so we ignore the return of the promise. + // User must use WA.state.saveVariable to have error message. + target.saveVariable(p.toString(), value).catch((e) => console.error(e)); return true; - } - return target.hasVariable(p.toString()); - }, -}) as WorkadventureStateCommands & { [key: string]: unknown }; - -export default proxyCommand; + }, + has(target: WorkadventureStateCommands, p: PropertyKey): boolean { + if (p in target) { + return true; + } + return target.hasVariable(p.toString()); + }, + }) as WorkadventureStateCommands & { [key: string]: unknown }; +} diff --git a/front/src/Api/iframe/website.ts b/front/src/Api/iframe/website.ts index 28abb19a..eab1bce3 100644 --- a/front/src/Api/iframe/website.ts +++ b/front/src/Api/iframe/website.ts @@ -1,8 +1,4 @@ -import type { LoadSoundEvent } from "../Events/LoadSoundEvent"; -import type { PlaySoundEvent } from "../Events/PlaySoundEvent"; -import type { StopSoundEvent } from "../Events/StopSoundEvent"; import { IframeApiContribution, queryWorkadventure, sendToWorkadventure } from "./IframeApiContribution"; -import { Sound } from "./Sound/Sound"; import { EmbeddedWebsite } from "./Room/EmbeddedWebsite"; import type { CreateEmbeddedWebsiteEvent } from "../Events/EmbeddedWebsiteEvent"; diff --git a/front/src/Components/App.svelte b/front/src/Components/App.svelte index e9db2b77..a1277ed2 100644 --- a/front/src/Components/App.svelte +++ b/front/src/Components/App.svelte @@ -23,6 +23,9 @@ import { chatVisibilityStore } from "../Stores/ChatStore"; import { helpCameraSettingsVisibleStore } from "../Stores/HelpCameraSettingsStore"; import HelpCameraSettingsPopup from "./HelpCameraSettings/HelpCameraSettingsPopup.svelte"; + import { showLimitRoomModalStore, showShareLinkMapModalStore } from "../Stores/ModalStore"; + import LimitRoomModal from "./Modal/LimitRoomModal.svelte"; + import ShareLinkMapModal from "./Modal/ShareLinkMapModal.svelte"; import AudioPlaying from "./UI/AudioPlaying.svelte"; import { soundPlayingStore } from "../Stores/SoundPlayingStore"; import ErrorDialog from "./UI/ErrorDialog.svelte"; @@ -136,6 +139,16 @@ {/if} + {#if $showLimitRoomModalStore} +
+ +
+ {/if} + {#if $showShareLinkMapModalStore} +
+ +
+ {/if} {#if $requestVisitCardsStore} {/if} diff --git a/front/src/Components/AudioManager/AudioManager.svelte b/front/src/Components/AudioManager/AudioManager.svelte index 4422d002..b62d8fbe 100644 --- a/front/src/Components/AudioManager/AudioManager.svelte +++ b/front/src/Components/AudioManager/AudioManager.svelte @@ -19,12 +19,13 @@ audioManagerVolumeStore.setVolume(volume); audioManagerVolumeStore.setMuted(localUserStore.getAudioPlayerMuted()); - unsubscriberFileStore = audioManagerFileStore.subscribe(() => { + unsubscriberFileStore = audioManagerFileStore.subscribe((src) => { HTMLAudioPlayer.pause(); + HTMLAudioPlayer.src = src; HTMLAudioPlayer.loop = get(audioManagerVolumeStore).loop; HTMLAudioPlayer.volume = get(audioManagerVolumeStore).volume; HTMLAudioPlayer.muted = get(audioManagerVolumeStore).muted; - HTMLAudioPlayer.play(); + void HTMLAudioPlayer.play(); }); unsubscriberVolumeStore = audioManagerVolumeStore.subscribe((audioManager: audioManagerVolume) => { const reduceVolume = audioManager.talking && audioManager.decreaseWhileTalking; @@ -148,9 +149,7 @@
- +
diff --git a/front/src/Components/Chat/ChatElement.svelte b/front/src/Components/Chat/ChatElement.svelte index 231e7260..4ac9c275 100644 --- a/front/src/Components/Chat/ChatElement.svelte +++ b/front/src/Components/Chat/ChatElement.svelte @@ -67,6 +67,7 @@ .messagePart { flex-grow: 1; max-width: 100%; + user-select: text; span.date { font-size: 80%; diff --git a/front/src/Components/FollowMenu/FollowMenu.svelte b/front/src/Components/FollowMenu/FollowMenu.svelte index 264b27ed..aaa23b3e 100644 --- a/front/src/Components/FollowMenu/FollowMenu.svelte +++ b/front/src/Components/FollowMenu/FollowMenu.svelte @@ -14,14 +14,11 @@ vim: ft=typescript } function sendFollowRequest() { - gameScene.connection?.emitFollowRequest(); - followRoleStore.set("leader"); - followStateStore.set("active"); + gameScene.CurrentPlayer.sendFollowRequest(); } function acceptFollowRequest() { - gameScene.CurrentPlayer.enableFollowing(); - gameScene.connection?.emitFollowConfirmation(); + gameScene.CurrentPlayer.startFollowing(); } function abortEnding() { @@ -42,23 +39,15 @@ vim: ft=typescript -{#if $followStateStore === "requesting"} +{#if $followStateStore === "requesting" && $followRoleStore === "follower"}
- {#if $followRoleStore === "follower"} -
-

Do you want to follow {name($followUsersStore[0])}?

-
-
- - -
- {:else if $followRoleStore === "leader"} -
-

Should never be displayed

-
- {/if} +
+

Do you want to follow {name($followUsersStore[0])}?

+
+
+ + +
{/if} diff --git a/front/src/Components/Menu/GlobalMessagesSubMenu.svelte b/front/src/Components/Menu/GlobalMessagesSubMenu.svelte index 524e5e50..e755a243 100644 --- a/front/src/Components/Menu/GlobalMessagesSubMenu.svelte +++ b/front/src/Components/Menu/GlobalMessagesSubMenu.svelte @@ -19,12 +19,12 @@ uploadAudioActive = true; } - function send() { + async function send(): Promise { if (inputSendTextActive) { - handleSendText.sendTextMessage(broadcastToWorld); + return handleSendText.sendTextMessage(broadcastToWorld); } if (uploadAudioActive) { - handleSendAudio.sendAudioMessage(broadcastToWorld); + return handleSendAudio.sendAudioMessage(broadcastToWorld); } } diff --git a/front/src/Components/Menu/GuestSubMenu.svelte b/front/src/Components/Menu/GuestSubMenu.svelte index 0ae25b75..408dcbce 100644 --- a/front/src/Components/Menu/GuestSubMenu.svelte +++ b/front/src/Components/Menu/GuestSubMenu.svelte @@ -21,12 +21,12 @@
-

Share the link of the room !

+

Share the link of the room!

diff --git a/front/src/Components/Menu/MenuIcon.svelte b/front/src/Components/Menu/MenuIcon.svelte index bb5a2df2..9d3f72dd 100644 --- a/front/src/Components/Menu/MenuIcon.svelte +++ b/front/src/Components/Menu/MenuIcon.svelte @@ -1,9 +1,14 @@
- open menu - open menu + {#if $limitMapStore} + open menu + open menu + {:else} + open menu + open menu + {/if}
diff --git a/front/src/Components/Modal/ShareLinkMapModal.svelte b/front/src/Components/Modal/ShareLinkMapModal.svelte new file mode 100644 index 00000000..42ac1294 --- /dev/null +++ b/front/src/Components/Modal/ShareLinkMapModal.svelte @@ -0,0 +1,90 @@ + + + + + diff --git a/front/src/Components/ReportMenu/ReportMenu.svelte b/front/src/Components/ReportMenu/ReportMenu.svelte index 8479a5b0..92601774 100644 --- a/front/src/Components/ReportMenu/ReportMenu.svelte +++ b/front/src/Components/ReportMenu/ReportMenu.svelte @@ -6,12 +6,11 @@ import type { Unsubscriber } from "svelte/store"; import { playersStore } from "../../Stores/PlayersStore"; import { connectionManager } from "../../Connexion/ConnectionManager"; - import { GameConnexionTypes } from "../../Url/UrlManager"; import { get } from "svelte/store"; let blockActive = true; let reportActive = !blockActive; - let anonymous: boolean = false; + let disableReport: boolean = false; let userUUID: string | undefined = playersStore.getPlayerById(get(showReportScreenStore).userId)?.userUuid; let userName = "No name"; let unsubscriber: Unsubscriber; @@ -26,7 +25,7 @@ } } }); - anonymous = connectionManager.getConnexionType === GameConnexionTypes.anonymous; + disableReport = !connectionManager.currentRoom?.canReport ?? true; }); onDestroy(() => { @@ -65,7 +64,7 @@
-
+
diff --git a/messages/JsonMessages/MapDetailsData.ts b/messages/JsonMessages/MapDetailsData.ts index 794c9bac..39866eee 100644 --- a/messages/JsonMessages/MapDetailsData.ts +++ b/messages/JsonMessages/MapDetailsData.ts @@ -20,6 +20,10 @@ export const isMapDetailsData = new tg.IsInterface() }) .withOptionalProperties({ iframeAuthentication: tg.isNullable(tg.isString), + // The date (in ISO 8601 format) at which the room will expire + expireOn: tg.isString, + // Whether the "report" feature is enabled or not on this room + canReport: tg.isBoolean, }) .get(); diff --git a/messages/package.json b/messages/package.json index 6095a626..e6f04ba5 100644 --- a/messages/package.json +++ b/messages/package.json @@ -4,13 +4,14 @@ "description": "", "scripts": { "proto": "grpc_tools_node_protoc --plugin=protoc-gen-ts=./node_modules/.bin/protoc-gen-ts --grpc_out=generated --js_out=\"import_style=commonjs,binary:generated\" --ts_out=generated -I ./protos protos/*.proto", + "ts-proto": "grpc_tools_node_protoc --plugin=./node_modules/.bin/protoc-gen-ts_proto --ts_proto_out=ts-proto-generated --ts_proto_opt=oneof=unions --ts_proto_opt=esModuleInterop=true protos/*.proto", "copy-to-back": "rm -rf ../back/src/Messages/generated && cp -rf generated/ ../back/src/Messages/generated", - "copy-to-front": "rm -rf ../front/src/Messages/generated && cp -rf generated/ ../front/src/Messages/generated", + "copy-to-front-ts-proto": "sed 's/import { Observable } from \"rxjs\";/import type { Observable } from \"rxjs\";/g' ts-proto-generated/protos/messages.ts > ../front/src/Messages/ts-proto-generated/messages.ts", "copy-to-pusher": "rm -rf ../pusher/src/Messages/generated && cp -rf generated/ ../pusher/src/Messages/generated", "json-copy-to-pusher": "rm -rf ../pusher/src/Messages/JsonMessages/* && cp -rf JsonMessages/* ../pusher/src/Messages/JsonMessages/", "json-copy-to-front": "rm -rf ../front/src/Messages/JsonMessages/* && cp -rf JsonMessages/* ../front/src/Messages/JsonMessages/", "precommit": "lint-staged", - "proto-all": "yarn run proto && yarn run copy-to-back && yarn run copy-to-front && yarn run copy-to-pusher && yarn run json-copy-to-pusher && yarn run json-copy-to-front", + "proto-all": "yarn run proto && yarn run ts-proto && yarn run copy-to-back && yarn run copy-to-front-ts-proto && yarn run copy-to-pusher && yarn run json-copy-to-pusher && yarn run json-copy-to-front", "proto:watch": "yarn run proto-all; inotifywait -q -m -e close_write protos/messages.proto JsonMessages/ | while read -r filename event; do yarn run proto-all; done", "pretty": "yarn prettier --write 'JsonMessages/**/*.ts'", "pretty-check": "yarn prettier --check 'JsonMessages/**/*.ts'" @@ -18,7 +19,8 @@ "dependencies": { "generic-type-guard": "^3.5.0", "google-protobuf": "^3.13.0", - "grpc": "^1.24.4" + "grpc": "^1.24.4", + "ts-proto": "^1.96.0" }, "devDependencies": { "@types/google-protobuf": "^3.7.4", diff --git a/messages/protos/messages.proto b/messages/protos/messages.proto index 3c05037a..8ac7bbf0 100644 --- a/messages/protos/messages.proto +++ b/messages/protos/messages.proto @@ -296,8 +296,6 @@ message ServerToClientMessage { WebRtcSignalToClientMessage webRtcSignalToClientMessage = 5; WebRtcSignalToClientMessage webRtcScreenSharingSignalToClientMessage = 6; WebRtcDisconnectMessage webRtcDisconnectMessage = 7; - PlayGlobalMessage playGlobalMessage = 8; - StopGlobalMessage stopGlobalMessage = 9; TeleportMessageMessage teleportMessageMessage = 10; SendJitsiJwtMessage sendJitsiJwtMessage = 11; SendUserMessage sendUserMessage = 12; @@ -390,8 +388,6 @@ message PusherToBackMessage { SetPlayerDetailsMessage setPlayerDetailsMessage = 5; WebRtcSignalToServerMessage webRtcSignalToServerMessage = 6; WebRtcSignalToServerMessage webRtcScreenSharingSignalToServerMessage = 7; - PlayGlobalMessage playGlobalMessage = 8; - StopGlobalMessage stopGlobalMessage = 9; ReportPlayerMessage reportPlayerMessage = 10; QueryJitsiJwtMessage queryJitsiJwtMessage = 11; SendUserMessage sendUserMessage = 12; diff --git a/messages/ts-proto-generated/.gitignore b/messages/ts-proto-generated/.gitignore new file mode 100644 index 00000000..d6b7ef32 --- /dev/null +++ b/messages/ts-proto-generated/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/messages/yarn.lock b/messages/yarn.lock index 5d040c6f..8ba2fe90 100644 --- a/messages/yarn.lock +++ b/messages/yarn.lock @@ -174,6 +174,11 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.7.tgz#8ea1e8f8eae2430cf440564b98c6dfce1ec5945d" integrity sha512-Zw1vhUSQZYw+7u5dAwNbIA9TuTotpzY/OF7sJM9FqPOF3SPjKnxrjoTktXDZgUjybf4cWVBP7O8wvKdSaGHweg== +"@types/node@>=13.7.0": + version "17.0.5" + resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.5.tgz#57ca67ec4e57ad9e4ef5a6bab48a15387a1c83e0" + integrity sha512-w3mrvNXLeDYV1GKTZorGJQivK6XLCoGwpnyJFbJVK/aTBQUxOCaa/GlFAAN3OTDFcb7h5tiFG+YXCO2By+riZw== + "@types/node@^12.12.29": version "12.19.4" resolved "https://registry.yarnpkg.com/@types/node/-/node-12.19.4.tgz#cdfbb62e26c7435ed9aab9c941393cc3598e9b46" @@ -184,6 +189,11 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-13.13.30.tgz#1ed6e01e4ca576d5aec9cc802cc3bcf94c274192" integrity sha512-HmqFpNzp3TSELxU/bUuRK+xzarVOAsR00hzcvM0TXrMlt/+wcSLa5q6YhTb6/cA6wqDCZLDcfd8fSL95x5h7AA== +"@types/object-hash@^1.3.0": + version "1.3.4" + resolved "https://registry.yarnpkg.com/@types/object-hash/-/object-hash-1.3.4.tgz#079ba142be65833293673254831b5e3e847fe58b" + integrity sha512-xFdpkAkikBgqBdG9vIlsqffDV8GpvnPEzs0IUtr1v3BEB97ijsFQ4RXVbUZwjFThhB4MDSTUfvmxUD5PGx0wXA== + "@types/parse-json@^4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0" @@ -194,6 +204,11 @@ resolved "https://registry.yarnpkg.com/@types/parsimmon/-/parsimmon-1.10.4.tgz#7639e16015440d9baf622f83c12dae47787226b7" integrity sha512-M56NfQHfaWuaj6daSgCVs7jh8fXLI3LmxjRoQxmOvYesgIkI+9HPsDLO0vd7wX7cwA0D0ZWFEJdp0VPwLdS+bQ== +"@types/prettier@^1.19.0": + version "1.19.1" + resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-1.19.1.tgz#33509849f8e679e4add158959fdb086440e9553f" + integrity sha512-5qOlnZscTn4xxM5MeGXAMOsIOIKIbh9e85zJWfBRVPlRMEVawzoPhINYbRGkBZCI8LxvBe7tJCdWiarA99OZfQ== + "@typescript-eslint/eslint-plugin@^4.7.0": version "4.7.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.7.0.tgz#85c9bbda00c0cb604d3c241f7bc7fb171a2d3479" @@ -1156,6 +1171,11 @@ dashdash@^1.12.0: dependencies: assert-plus "^1.0.0" +dataloader@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/dataloader/-/dataloader-1.4.0.tgz#bca11d867f5d3f1b9ed9f737bd15970c65dff5c8" + integrity sha512-68s5jYdlvasItOJnCuI2Q9s4q98g0pCyL3HrcKJu8KNugUl8ahgmZYg38ysLTgQjjXX3H8CJLkAvWrclWfcalw== + date.js@^0.3.1: version "0.3.3" resolved "https://registry.yarnpkg.com/date.js/-/date.js-0.3.3.tgz#ef1e92332f507a638795dbb985e951882e50bbda" @@ -3154,6 +3174,11 @@ object-copy@^0.1.0: define-property "^0.2.5" kind-of "^3.0.3" +object-hash@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-1.3.1.tgz#fde452098a951cb145f039bb7d455449ddc126df" + integrity sha512-OSuu/pU4ENM9kmREg0BdNrUDIl1heYa4mBZacJc+vVWz4GtAwu7jO8s4AIt2aGRUTqxykpWzI3Oqnsm13tTMDA== + object-inspect@^1.8.0: version "1.8.0" resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.8.0.tgz#df807e5ecf53a609cc6bfe93eac3cc7be5b3a9d0" @@ -3423,7 +3448,7 @@ prelude-ls@^1.2.1: resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== -prettier@^2.3.1: +prettier@^2.0.2, prettier@^2.3.1: version "2.5.1" resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.5.1.tgz#fff75fa9d519c54cf0fce328c1017d94546bc56a" integrity sha512-vBZcPRUR5MZJwoyi3ZoyQlc1rXeEck8KgeC9AwwOn+exuxLxq5toTRDTSaVrXHxelDMHy9zlicw8u66yxoSUFg== @@ -3467,6 +3492,25 @@ protobufjs@^6.10.1: "@types/node" "^13.7.0" long "^4.0.0" +protobufjs@^6.8.8: + version "6.11.2" + resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-6.11.2.tgz#de39fabd4ed32beaa08e9bb1e30d08544c1edf8b" + integrity sha512-4BQJoPooKJl2G9j3XftkIXjoC9C0Av2NOrWmbLWT1vH32GcSUHjM0Arra6UfTsVyfMAuFzaLucXn1sadxJydAw== + dependencies: + "@protobufjs/aspromise" "^1.1.2" + "@protobufjs/base64" "^1.1.2" + "@protobufjs/codegen" "^2.0.4" + "@protobufjs/eventemitter" "^1.1.0" + "@protobufjs/fetch" "^1.1.0" + "@protobufjs/float" "^1.0.2" + "@protobufjs/inquire" "^1.1.0" + "@protobufjs/path" "^1.1.2" + "@protobufjs/pool" "^1.1.0" + "@protobufjs/utf8" "^1.1.0" + "@types/long" "^4.0.1" + "@types/node" ">=13.7.0" + long "^4.0.0" + psl@^1.1.28: version "1.8.0" resolved "https://registry.yarnpkg.com/psl/-/psl-1.8.0.tgz#9326f8bcfb013adcc005fdff056acce020e51c24" @@ -4245,6 +4289,35 @@ tough-cookie@~2.5.0: psl "^1.1.28" punycode "^2.1.1" +ts-poet@^4.5.0: + version "4.6.1" + resolved "https://registry.yarnpkg.com/ts-poet/-/ts-poet-4.6.1.tgz#015dc823d726655af9f095c900f84ed7c60e2dd3" + integrity sha512-DXJ+mBJIDp+jiaUgB4N5I/sczHHDU2FWacdbDNVAVS4Mh4hb7ckpvUWVW7m7/nAOcjR0r4Wt+7AoO7FeJKExfA== + dependencies: + "@types/prettier" "^1.19.0" + lodash "^4.17.15" + prettier "^2.0.2" + +ts-proto-descriptors@^1.2.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/ts-proto-descriptors/-/ts-proto-descriptors-1.3.1.tgz#760ebaaa19475b03662f7b358ffea45b9c5348f5" + integrity sha512-Cybb3fqceMwA6JzHdC32dIo8eVGVmXrM6TWhdk1XQVVHT/6OQqk0ioyX1dIdu3rCIBhRmWUhUE4HsyK+olmgMw== + dependencies: + long "^4.0.0" + protobufjs "^6.8.8" + +ts-proto@^1.96.0: + version "1.96.0" + resolved "https://registry.yarnpkg.com/ts-proto/-/ts-proto-1.96.0.tgz#63768d7da533b337aee84db065dd66773bd4cac9" + integrity sha512-fKwaGzi8EOCU9xwmcXK917jj1WhFdLbFkPRawQ+5CAZM9eSXr/mpkz/yEctXCiuei364z6jAB2Odb64KCDFTPQ== + dependencies: + "@types/object-hash" "^1.3.0" + dataloader "^1.4.0" + object-hash "^1.3.1" + protobufjs "^6.8.8" + ts-poet "^4.5.0" + ts-proto-descriptors "^1.2.1" + tsconfig-paths@^3.9.0: version "3.9.0" resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.9.0.tgz#098547a6c4448807e8fcb8eae081064ee9a3c90b"