diff --git a/.github/workflows/end_to_end_tests.yml b/.github/workflows/end_to_end_tests.yml index e369fe2f..aa2d5764 100644 --- a/.github/workflows/end_to_end_tests.yml +++ b/.github/workflows/end_to_end_tests.yml @@ -21,6 +21,7 @@ jobs: working-directory: tests - name: Install Playwright run: npx playwright install --with-deps + working-directory: tests - name: 'Setup .env file' run: cp .env.template .env - name: Install messages dependencies diff --git a/back/.eslintrc.json b/back/.eslintrc.json index ce78dd63..27927fea 100644 --- a/back/.eslintrc.json +++ b/back/.eslintrc.json @@ -26,6 +26,9 @@ "rules": { "no-unused-vars": "off", "@typescript-eslint/no-explicit-any": "error", + "@typescript-eslint/no-unused-vars": [ + "error" + ], "no-throw-literal": "error" } -} +} \ No newline at end of file diff --git a/back/package.json b/back/package.json index 4e4c9fc1..1216efcf 100644 --- a/back/package.json +++ b/back/package.json @@ -45,7 +45,6 @@ "busboy": "^0.3.1", "circular-json": "^0.5.9", "debug": "^4.3.1", - "generic-type-guard": "^3.2.0", "google-protobuf": "^3.13.0", "grpc": "^1.24.4", "ipaddr.js": "^2.0.1", @@ -56,7 +55,7 @@ "redis": "^3.1.2", "uWebSockets.js": "uNetworking/uWebSockets.js#v18.5.0", "uuidv4": "^6.0.7", - "zod": "^3.12.0" + "zod": "^3.14.3" }, "devDependencies": { "@types/busboy": "^0.2.3", diff --git a/back/src/Controller/DebugController.ts b/back/src/Controller/DebugController.ts index f571d6b2..9a1b4e01 100644 --- a/back/src/Controller/DebugController.ts +++ b/back/src/Controller/DebugController.ts @@ -48,7 +48,7 @@ export class DebugController { return obj; } else if (value instanceof Set) { const obj: Array = []; - for (const [setKey, setValue] of value.entries()) { + for (const setValue of value.values()) { obj.push(setValue); } return obj; diff --git a/back/src/Controller/PrometheusController.ts b/back/src/Controller/PrometheusController.ts index 7fff3981..3779f7b4 100644 --- a/back/src/Controller/PrometheusController.ts +++ b/back/src/Controller/PrometheusController.ts @@ -1,5 +1,5 @@ import { App } from "../Server/sifrr.server"; -import { HttpRequest, HttpResponse } from "uWebSockets.js"; +import { HttpResponse } from "uWebSockets.js"; import { register, collectDefaultMetrics } from "prom-client"; export class PrometheusController { @@ -11,7 +11,7 @@ export class PrometheusController { this.App.get("/metrics", this.metrics.bind(this)); } - private metrics(res: HttpResponse, req: HttpRequest): void { + private metrics(res: HttpResponse): void { res.writeHeader("Content-Type", register.contentType); res.end(register.metrics()); } diff --git a/back/src/Model/GameRoom.ts b/back/src/Model/GameRoom.ts index 1693844b..5b1e13bf 100644 --- a/back/src/Model/GameRoom.ts +++ b/back/src/Model/GameRoom.ts @@ -1,7 +1,7 @@ import { PointInterface } from "./Websocket/PointInterface"; import { Group } from "./Group"; import { User, UserSocket } from "./User"; -import { PositionInterface } from "_Model/PositionInterface"; +import { PositionInterface } from "../Model/PositionInterface"; import { EmoteCallback, EntersCallback, @@ -9,23 +9,20 @@ import { LockGroupCallback, MovesCallback, PlayerDetailsUpdatedCallback, -} from "_Model/Zone"; +} from "../Model/Zone"; import { PositionNotifier } from "./PositionNotifier"; -import { Movable } from "_Model/Movable"; +import { Movable } from "../Model/Movable"; import { - BatchToPusherMessage, BatchToPusherRoomMessage, EmoteEventMessage, - ErrorMessage, JoinRoomMessage, SetPlayerDetailsMessage, SubToPusherRoomMessage, - VariableMessage, VariableWithTagMessage, ServerToClientMessage, } from "../Messages/generated/messages_pb"; import { ProtobufUtils } from "../Model/Websocket/ProtobufUtils"; -import { RoomSocket, ZoneSocket } from "src/RoomManager"; +import { RoomSocket, ZoneSocket } from "../RoomManager"; import { Admin } from "../Model/Admin"; import { adminApi } from "../Services/AdminApi"; import { isMapDetailsData, MapDetailsData } from "../Messages/JsonMessages/MapDetailsData"; @@ -36,7 +33,6 @@ import { ADMIN_API_URL } from "../Enum/EnvironmentVariable"; import { LocalUrlError } from "../Services/LocalUrlError"; import { emitErrorOnRoomSocket } from "../Services/MessageHelpers"; import { VariableError } from "../Services/VariableError"; -import { isRoomRedirect } from "../Messages/JsonMessages/RoomRedirect"; export type ConnectCallback = (user: User, group: Group) => void; export type DisconnectCallback = (user: User, group: Group) => void; @@ -399,7 +395,7 @@ export class GameRoom { private searchClosestAvailableUserOrGroup(user: User): User | Group | null { let minimumDistanceFound: number = Math.max(this.minDistance, this.groupRadius); let matchingItem: User | Group | null = null; - this.users.forEach((currentUser, userId) => { + this.users.forEach((currentUser) => { // Let's only check users that are not part of a group if (typeof currentUser.group !== "undefined") { return; @@ -585,12 +581,15 @@ export class GameRoom { }; } - const result = await adminApi.fetchMapDetails(roomUrl); - if (isRoomRedirect(result)) { - console.error("Unexpected room redirect received while querying map details", result); - throw new Error("Unexpected room redirect received while querying map details"); + const result = isMapDetailsData.safeParse(await adminApi.fetchMapDetails(roomUrl)); + + if (result.success) { + return result.data; } - return result; + + console.error(result.error.issues); + console.error("Unexpected room redirect received while querying map details", result); + throw new Error("Unexpected room redirect received while querying map details"); } private mapPromise: Promise | undefined; diff --git a/back/src/Model/Group.ts b/back/src/Model/Group.ts index a960e7b3..2ed6c695 100644 --- a/back/src/Model/Group.ts +++ b/back/src/Model/Group.ts @@ -1,8 +1,8 @@ import { ConnectCallback, DisconnectCallback, GameRoom } from "./GameRoom"; import { User } from "./User"; -import { PositionInterface } from "_Model/PositionInterface"; -import { Movable } from "_Model/Movable"; -import { PositionNotifier } from "_Model/PositionNotifier"; +import { PositionInterface } from "../Model/PositionInterface"; +import { Movable } from "../Model/Movable"; +import { PositionNotifier } from "../Model/PositionNotifier"; import { MAX_PER_GROUP } from "../Enum/EnvironmentVariable"; import type { Zone } from "../Model/Zone"; diff --git a/back/src/Model/Movable.ts b/back/src/Model/Movable.ts index ca586b7c..19bc8f92 100644 --- a/back/src/Model/Movable.ts +++ b/back/src/Model/Movable.ts @@ -1,4 +1,4 @@ -import { PositionInterface } from "_Model/PositionInterface"; +import { PositionInterface } from "../Model/PositionInterface"; /** * A physical object that can be placed into a Zone diff --git a/back/src/Model/PositionNotifier.ts b/back/src/Model/PositionNotifier.ts index cde357ac..9dd1c544 100644 --- a/back/src/Model/PositionNotifier.ts +++ b/back/src/Model/PositionNotifier.ts @@ -17,8 +17,8 @@ import { PlayerDetailsUpdatedCallback, Zone, } from "./Zone"; -import { Movable } from "_Model/Movable"; -import { PositionInterface } from "_Model/PositionInterface"; +import { Movable } from "../Model/Movable"; +import { PositionInterface } from "../Model/PositionInterface"; import { ZoneSocket } from "../RoomManager"; import { User } from "../Model/User"; import { EmoteEventMessage, SetPlayerDetailsMessage } from "../Messages/generated/messages_pb"; diff --git a/back/src/Model/User.ts b/back/src/Model/User.ts index 2f0dad54..7a6a53f1 100644 --- a/back/src/Model/User.ts +++ b/back/src/Model/User.ts @@ -1,8 +1,8 @@ import { Group } from "./Group"; import { PointInterface } from "./Websocket/PointInterface"; -import { Zone } from "_Model/Zone"; -import { Movable } from "_Model/Movable"; -import { PositionNotifier } from "_Model/PositionNotifier"; +import { Zone } from "../Model/Zone"; +import { Movable } from "../Model/Movable"; +import { PositionNotifier } from "../Model/PositionNotifier"; import { ServerDuplexStream } from "grpc"; import { BatchMessage, @@ -14,7 +14,7 @@ import { SetPlayerDetailsMessage, SubMessage, } from "../Messages/generated/messages_pb"; -import { CharacterLayer } from "_Model/Websocket/CharacterLayer"; +import { CharacterLayer } from "../Model/Websocket/CharacterLayer"; import { BoolValue, UInt32Value } from "google-protobuf/google/protobuf/wrappers_pb"; export type UserSocket = ServerDuplexStream; diff --git a/back/src/Model/Websocket/ItemEventMessage.ts b/back/src/Model/Websocket/ItemEventMessage.ts index 1bb7f615..fd28293e 100644 --- a/back/src/Model/Websocket/ItemEventMessage.ts +++ b/back/src/Model/Websocket/ItemEventMessage.ts @@ -1,11 +1,10 @@ -import * as tg from "generic-type-guard"; +import { z } from "zod"; -export const isItemEventMessageInterface = new tg.IsInterface() - .withProperties({ - itemId: tg.isNumber, - event: tg.isString, - state: tg.isUnknown, - parameters: tg.isUnknown, - }) - .get(); -export type ItemEventMessageInterface = tg.GuardedType; +export const isItemEventMessageInterface = z.object({ + itemId: z.number(), + event: z.string(), + state: z.unknown(), + parameters: z.unknown(), +}); + +export type ItemEventMessageInterface = z.infer; diff --git a/back/src/Model/Websocket/PointInterface.ts b/back/src/Model/Websocket/PointInterface.ts index d7c7826e..2275e5f8 100644 --- a/back/src/Model/Websocket/PointInterface.ts +++ b/back/src/Model/Websocket/PointInterface.ts @@ -1,18 +1,10 @@ -import * as tg from "generic-type-guard"; +import { z } from "zod"; -/*export interface PointInterface { - readonly x: number; - readonly y: number; - readonly direction: string; - readonly moving: boolean; -}*/ +export const isPointInterface = z.object({ + x: z.number(), + y: z.number(), + direction: z.string(), + moving: z.boolean(), +}); -export const isPointInterface = new tg.IsInterface() - .withProperties({ - x: tg.isNumber, - y: tg.isNumber, - direction: tg.isString, - moving: tg.isBoolean, - }) - .get(); -export type PointInterface = tg.GuardedType; +export type PointInterface = z.infer; diff --git a/back/src/Model/Websocket/ProtobufUtils.ts b/back/src/Model/Websocket/ProtobufUtils.ts index 68817a4f..1b4945c0 100644 --- a/back/src/Model/Websocket/ProtobufUtils.ts +++ b/back/src/Model/Websocket/ProtobufUtils.ts @@ -5,10 +5,10 @@ import { PointMessage, PositionMessage, } from "../../Messages/generated/messages_pb"; -import { CharacterLayer } from "_Model/Websocket/CharacterLayer"; +import { CharacterLayer } from "../../Model/Websocket/CharacterLayer"; import Direction = PositionMessage.Direction; -import { ItemEventMessageInterface } from "_Model/Websocket/ItemEventMessage"; -import { PositionInterface } from "_Model/PositionInterface"; +import { ItemEventMessageInterface } from "../../Model/Websocket/ItemEventMessage"; +import { PositionInterface } from "../../Model/PositionInterface"; export class ProtobufUtils { public static toPositionMessage(point: PointInterface): PositionMessage { diff --git a/back/src/Model/Zone.ts b/back/src/Model/Zone.ts index 2d0cefd5..b333316a 100644 --- a/back/src/Model/Zone.ts +++ b/back/src/Model/Zone.ts @@ -1,5 +1,5 @@ import { User } from "./User"; -import { PositionInterface } from "_Model/PositionInterface"; +import { PositionInterface } from "../Model/PositionInterface"; import { Movable } from "./Movable"; import { Group } from "./Group"; import { ZoneSocket } from "../RoomManager"; @@ -71,6 +71,7 @@ export class Zone { /** * Notify listeners of this zone that this user entered */ + // eslint-disable-next-line @typescript-eslint/no-unused-vars private notifyEnter(thing: Movable, oldZone: Zone | null, position: PositionInterface) { for (const listener of this.listeners) { this.onEnters(thing, oldZone, listener); diff --git a/back/src/RoomManager.ts b/back/src/RoomManager.ts index ab886f50..c07d7e76 100644 --- a/back/src/RoomManager.ts +++ b/back/src/RoomManager.ts @@ -15,7 +15,6 @@ import { EmptyMessage, ItemEventMessage, JoinRoomMessage, - PlayGlobalMessage, PusherToBackMessage, QueryJitsiJwtMessage, RefreshRoomPromptMessage, diff --git a/back/src/Services/AdminApi.ts b/back/src/Services/AdminApi.ts index 148877af..cfbab6c3 100644 --- a/back/src/Services/AdminApi.ts +++ b/back/src/Services/AdminApi.ts @@ -18,12 +18,21 @@ class AdminApi { params, }); - if (!isMapDetailsData(res.data) && !isRoomRedirect(res.data)) { - console.error("Unexpected answer from the /api/map admin endpoint.", res.data); - throw new Error("Unexpected answer from the /api/map admin endpoint."); + const mapDetailData = isMapDetailsData.safeParse(res.data); + const roomRedirect = isRoomRedirect.safeParse(res.data); + + if (mapDetailData.success) { + return mapDetailData.data; } - return res.data; + if (roomRedirect.success) { + return roomRedirect.data; + } + + console.error(mapDetailData.error.issues); + console.error(roomRedirect.error.issues); + console.error("Unexpected answer from the /api/map admin endpoint.", res.data); + throw new Error("Unexpected answer from the /api/map admin endpoint."); } } diff --git a/back/src/Services/MessageHelpers.ts b/back/src/Services/MessageHelpers.ts index 34edc473..10e4d514 100644 --- a/back/src/Services/MessageHelpers.ts +++ b/back/src/Services/MessageHelpers.ts @@ -1,5 +1,4 @@ import { - BatchMessage, BatchToPusherMessage, BatchToPusherRoomMessage, ErrorMessage, @@ -7,7 +6,7 @@ import { SubToPusherMessage, SubToPusherRoomMessage, } from "../Messages/generated/messages_pb"; -import { UserSocket } from "_Model/User"; +import { UserSocket } from "../Model/User"; import { RoomSocket, ZoneSocket } from "../RoomManager"; function getMessageFromError(error: unknown): string { diff --git a/back/src/Services/Repository/VoidVariablesRepository.ts b/back/src/Services/Repository/VoidVariablesRepository.ts index 0a2664e8..f69a9976 100644 --- a/back/src/Services/Repository/VoidVariablesRepository.ts +++ b/back/src/Services/Repository/VoidVariablesRepository.ts @@ -4,10 +4,12 @@ import { VariablesRepositoryInterface } from "./VariablesRepositoryInterface"; * Mock class in charge of NOT saving/loading variables from the data store */ export class VoidVariablesRepository implements VariablesRepositoryInterface { + // eslint-disable-next-line @typescript-eslint/no-unused-vars loadVariables(roomUrl: string): Promise<{ [key: string]: string }> { return Promise.resolve({}); } + // eslint-disable-next-line @typescript-eslint/no-unused-vars saveVariable(roomUrl: string, key: string, value: string): Promise { return Promise.resolve(0); } diff --git a/back/src/Services/SocketManager.ts b/back/src/Services/SocketManager.ts index 6698ed43..c186658f 100644 --- a/back/src/Services/SocketManager.ts +++ b/back/src/Services/SocketManager.ts @@ -2,7 +2,6 @@ import { GameRoom } from "../Model/GameRoom"; import { ItemEventMessage, ItemStateMessage, - PlayGlobalMessage, PointMessage, RoomJoinedMessage, ServerToClientMessage, @@ -35,12 +34,10 @@ import { FollowAbortMessage, VariableMessage, BatchToPusherRoomMessage, - SubToPusherRoomMessage, SetPlayerDetailsMessage, PlayerDetailsUpdatedMessage, GroupUsersUpdateMessage, LockGroupPromptMessage, - RoomMessage, } from "../Messages/generated/messages_pb"; import { User, UserSocket } from "../Model/User"; import { ProtobufUtils } from "../Model/Websocket/ProtobufUtils"; @@ -60,9 +57,9 @@ import { JITSI_URL } from "../Enum/EnvironmentVariable"; import { clientEventsEmitter } from "./ClientEventsEmitter"; import { gaugeManager } from "./GaugeManager"; import { RoomSocket, ZoneSocket } from "../RoomManager"; -import { Zone } from "_Model/Zone"; +import { Zone } from "../Model/Zone"; import Debug from "debug"; -import { Admin } from "_Model/Admin"; +import { Admin } from "../Model/Admin"; import crypto from "crypto"; const debug = Debug("sockermanager"); diff --git a/back/src/Services/VariablesManager.ts b/back/src/Services/VariablesManager.ts index f7e65a66..32ae0493 100644 --- a/back/src/Services/VariablesManager.ts +++ b/back/src/Services/VariablesManager.ts @@ -2,7 +2,7 @@ * Handles variables shared between the scripting API and the server. */ import { ITiledMap, ITiledMapLayer, ITiledMapObject } from "@workadventure/tiled-map-type-guard/dist"; -import { User } from "_Model/User"; +import { User } from "../Model/User"; import { variablesRepository } from "./Repository/VariablesRepository"; import { redisClient } from "./RedisClient"; import { VariableError } from "./VariableError"; diff --git a/back/tests/GameRoomTest.ts b/back/tests/GameRoomTest.ts index fb9f09fb..03238a3d 100644 --- a/back/tests/GameRoomTest.ts +++ b/back/tests/GameRoomTest.ts @@ -1,11 +1,12 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ import "jasmine"; import { ConnectCallback, DisconnectCallback, GameRoom } from "../src/Model/GameRoom"; import { Point } from "../src/Model/Websocket/MessageUserPosition"; import { Group } from "../src/Model/Group"; -import { User, UserSocket } from "_Model/User"; +import { User, UserSocket } from "../src/Model/User"; import { JoinRoomMessage, PositionMessage } from "../src/Messages/generated/messages_pb"; import Direction = PositionMessage.Direction; -import { EmoteCallback } from "_Model/Zone"; +import { EmoteCallback } from "../src/Model/Zone"; function createMockUser(userId: number): User { return { diff --git a/back/tests/MapFetcherTest.ts b/back/tests/MapFetcherTest.ts index 1e7ca447..41298d61 100644 --- a/back/tests/MapFetcherTest.ts +++ b/back/tests/MapFetcherTest.ts @@ -1,4 +1,3 @@ -import { arrayIntersect } from "../src/Services/ArrayHelper"; import { mapFetcher } from "../src/Services/MapFetcher"; describe("MapFetcher", () => { diff --git a/back/tests/PositionNotifierTest.ts b/back/tests/PositionNotifierTest.ts index 7673ed3c..a5af48f8 100644 --- a/back/tests/PositionNotifierTest.ts +++ b/back/tests/PositionNotifierTest.ts @@ -1,9 +1,10 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ import "jasmine"; import { PositionNotifier } from "../src/Model/PositionNotifier"; import { User, UserSocket } from "../src/Model/User"; -import { Zone } from "_Model/Zone"; -import { Movable } from "_Model/Movable"; -import { PositionInterface } from "_Model/PositionInterface"; +import { Zone } from "../src/Model/Zone"; +import { Movable } from "../src/Model/Movable"; +import { PositionInterface } from "../src/Model/PositionInterface"; import { ZoneSocket } from "../src/RoomManager"; describe("PositionNotifier", () => { diff --git a/back/tsconfig.json b/back/tsconfig.json index e149d304..906ef01c 100644 --- a/back/tsconfig.json +++ b/back/tsconfig.json @@ -3,18 +3,18 @@ "experimentalDecorators": true, /* Basic Options */ // "incremental": true, /* Enable incremental compilation */ - "target": "ES2019", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */ + "target": "ES2019", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */ "downlevelIteration": true, - "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */ + "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */ // "lib": [], /* Specify library files to be included in the compilation. */ - "allowJs": true, /* Allow javascript files to be compiled. */ + "allowJs": true, /* Allow javascript files to be compiled. */ // "checkJs": true, /* Report errors in .js files. */ // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ // "declaration": true, /* Generates corresponding '.d.ts' file. */ // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ - "sourceMap": true, /* Generates corresponding '.map' file. */ + "sourceMap": true, /* Generates corresponding '.map' file. */ // "outFile": "./", /* Concatenate and emit output to single file. */ - "outDir": "./dist", /* Redirect output structure to the directory. */ + "outDir": "./dist", /* Redirect output structure to the directory. */ // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ // "composite": true, /* Enable project compilation */ // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ @@ -23,50 +23,50 @@ // "importHelpers": true, /* Import emit helpers from 'tslib'. */ // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ - /* Strict Type-Checking Options */ - "strict": true, /* Enable all strict type-checking options. */ + "strict": true, /* Enable all strict type-checking options. */ // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ // "strictNullChecks": true, /* Enable strict null checks. */ // "strictFunctionTypes": true, /* Enable strict checking of function types. */ // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ - "noImplicitThis": false, /* Raise error on 'this' expressions with an implied 'any' type. */ // Disabled because of sifrr server that is monkey patching HttpResponse + "noImplicitThis": false, /* Raise error on 'this' expressions with an implied 'any' type. */ // Disabled because of sifrr server that is monkey patching HttpResponse // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ - /* Additional Checks */ // "noUnusedLocals": true, /* Report errors on unused locals. */ // "noUnusedParameters": true, /* Report errors on unused parameters. */ - "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ - "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ - + "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ + "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ /* Module Resolution Options */ - "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ - "baseUrl": ".", /* Base directory to resolve non-absolute module names. */ - "paths": { - "_Controller/*": ["src/Controller/*"], - "_Model/*": ["src/Model/*"], - "_Enum/*": ["src/Enum/*"] - }, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ + "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ + // "baseUrl": ".", /* Base directory to resolve non-absolute module names. */ + // "paths": { + // "_Controller/*": [ + // "src/Controller/*" + // ], + // "_Model/*": [ + // "src/Model/*" + // ], + // "_Enum/*": [ + // "src/Enum/*" + // ] + // }, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ // "typeRoots": [], /* List of folders to include type definitions from. */ // "types": [], /* Type declaration files to be included in compilation. */ // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ - "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ + "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ - /* Source Map Options */ // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ - /* Experimental Options */ // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ - /* Advanced Options */ - "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ + "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ } -} +} \ No newline at end of file diff --git a/back/yarn.lock b/back/yarn.lock index 849b44a5..d23fc29b 100644 --- a/back/yarn.lock +++ b/back/yarn.lock @@ -981,7 +981,7 @@ gauge@^3.0.0: strip-ansi "^6.0.1" wide-align "^1.1.2" -generic-type-guard@^3.2.0, generic-type-guard@^3.4.1: +generic-type-guard@^3.4.1: version "3.5.0" resolved "https://registry.yarnpkg.com/generic-type-guard/-/generic-type-guard-3.5.0.tgz#39de9f8fceee65d79e7540959f0e7b23210c07b6" integrity sha512-OpgXv/sbRobhFboaSyN/Tsh97Sxt5pcfLLxCiYZgYIIWFFp+kn2EzAXiaQZKEVRlq1rOE/zh8cYhJXEwplbJiQ== @@ -2259,7 +2259,7 @@ yn@3.1.1: resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q== -zod@^3.12.0: - version "3.14.2" - resolved "https://registry.yarnpkg.com/zod/-/zod-3.14.2.tgz#0b4ed79085c471adce0e7f2c0a4fbb5ddc516ba2" - integrity sha512-iF+wrtzz7fQfkmn60PG6XFxaWBhYYKzp2i+nv24WbLUWb2JjymdkHlzBwP0erpc78WotwP5g9AAu7Sk8GWVVNw== +zod@^3.14.3: + version "3.14.3" + resolved "https://registry.yarnpkg.com/zod/-/zod-3.14.3.tgz#60e86341c05883c281fe96a0e79acea48a09f123" + integrity sha512-OzwRCSXB1+/8F6w6HkYHdbuWysYWnAF4fkRgKDcSFc54CE+Sv0rHXKfeNUReGCrHukm1LNpi6AYeXotznhYJbQ== diff --git a/desktop/electron/tsconfig.json b/desktop/electron/tsconfig.json index d356ed64..49c7d058 100644 --- a/desktop/electron/tsconfig.json +++ b/desktop/electron/tsconfig.json @@ -12,10 +12,10 @@ "noImplicitReturns": true, "noFallthroughCasesInSwitch": true, "moduleResolution": "node", - "baseUrl": ".", - "paths": {}, + //"baseUrl": ".", + //"paths": {}, "esModuleInterop": true, "forceConsistentCasingInFileNames": true, "skipLibCheck": true } -} +} \ No newline at end of file diff --git a/desktop/local-app/tsconfig.json b/desktop/local-app/tsconfig.json index 29d24a5f..ca38c31a 100644 --- a/desktop/local-app/tsconfig.json +++ b/desktop/local-app/tsconfig.json @@ -5,7 +5,7 @@ "useDefineForClassFields": true, "module": "esnext", "resolveJsonModule": true, - "baseUrl": ".", + //"baseUrl": ".", /** * Typecheck JS in `.svelte` and `.js` files by default. * Disable checkJs if you'd like to use dynamic types in JS. @@ -15,10 +15,23 @@ "allowJs": true, "checkJs": true, "paths": { - "~/*": ["./src/*"], - "@wa-preload-local-app": ["../electron/src/preload-local-app/types.ts"], + "~/*": [ + "./src/*" + ], + "@wa-preload-local-app": [ + "../electron/src/preload-local-app/types.ts" + ], } }, - "include": ["src/**/*.d.ts", "src/**/*.ts", "src/**/*.js", "src/**/*.svelte"], - "references": [{ "path": "./tsconfig.node.json" }] -} + "include": [ + "src/**/*.d.ts", + "src/**/*.ts", + "src/**/*.js", + "src/**/*.svelte" + ], + "references": [ + { + "path": "./tsconfig.node.json" + } + ] +} \ No newline at end of file diff --git a/docs/dev/contributing-to-scripting-api.md b/docs/dev/contributing-to-scripting-api.md index 8d716010..e440e837 100644 --- a/docs/dev/contributing-to-scripting-api.md +++ b/docs/dev/contributing-to-scripting-api.md @@ -9,7 +9,7 @@ The [scripting API](https://workadventu.re/map-building/scripting.md) allows map The philosophy behind WorkAdventure is to build a platform that is as open as possible. Part of this strategy is to offer map developers the ability to turn a WorkAdventures map into something unexpected, using the API. For instance, -you could use it to develop games (we have seen a PacMan and a mine-sweeper on WorkAdventure!) +you could use it to develop games (we have seen a PacMan and a mine-sweeper on WorkAdventure!) We started working on the WorkAdventure scripting API with this in mind, but at some point, maybe you will find that a feature is missing in the API. This article is here to explain to you how to add this feature. @@ -35,7 +35,7 @@ directly access Phaser objects (Phaser is the game engine used in WorkAdventure) can contribute a map, we cannot allow anyone to run any code in the scope of the WorkAdventure server (that would be a huge XSS security flaw). -Instead, the only way the script can interact with WorkAdventure is by sending messages using the +Instead, the only way the script can interact with WorkAdventure is by sending messages using the [postMessage API](https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage). ![](images/scripting_2.svg) @@ -103,14 +103,14 @@ All the other files dedicated to the iframe API are located in the `src/Api/ifra ## Utility functions to exchange messages -In the example above, we already saw you can easily send a message from the iframe to WorkAdventure using the +In the example above, we already saw you can easily send a message from the iframe to WorkAdventure using the [`sendToWorkadventure`](http://github.com/thecodingmachine/workadventure/blob/ab075ef6f4974766a3e2de12a230ac4df0954b58/front/src/Api/iframe/IframeApiContribution.ts#L11-L13) utility function. Of course, messaging can go the other way around and WorkAdventure can also send messages to the iframes. We use the [`IFrameListener.postMessage`](http://github.com/thecodingmachine/workadventure/blob/ab075ef6f4974766a3e2de12a230ac4df0954b58/front/src/Api/IframeListener.ts#L455-L459) function for this. Finally, there is a last type of utility function (a quite powerful one). It is quite common to need to call a function -from the iframe in WorkAdventure, and to expect a response. For those use cases, the iframe API comes with a +from the iframe in WorkAdventure, and to expect a response. For those use cases, the iframe API comes with a [`queryWorkadventure`](http://github.com/thecodingmachine/workadventure/blob/ab075ef6f4974766a3e2de12a230ac4df0954b58/front/src/Api/iframe/IframeApiContribution.ts#L30-L49) utility function. ## Types @@ -122,7 +122,7 @@ Indeed, Typescript interfaces only exist at compilation time but cannot be enfor is an entry point to WorkAdventure, and as with any entry point, data must be checked (otherwise, a hacker could send specially crafted JSON packages to try to hack WA). -In WorkAdventure, we use the [generic-type-guard](https://github.com/mscharley/generic-type-guard) package. This package +In WorkAdventure, we use the [zod](https://github.com/colinhacks/zod) package. This package allows us to create interfaces AND custom type guards in one go. Let's go back at our example. Let's have a look at the JSON message sent when we want to send a chat message from the API: @@ -140,21 +140,20 @@ sendToWorkadventure({ The "data" part of the message is defined in `front/src/Api/Events/ChatEvent.ts`: ```typescript -import * as tg from "generic-type-guard"; +import { z } from "zod"; + +export const isChatEvent = z.object({ + message: z.string(), + author: z.string(), +}); -export const isChatEvent = new tg.IsInterface() - .withProperties({ - message: tg.isString, - author: tg.isString, - }) - .get(); /** * A message sent from the iFrame to the game to add a message in the chat. */ -export type ChatEvent = tg.GuardedType; +export type ChatEvent = z.infer; ``` -Using the generic-type-guard library, we start by writing a type guard function (`isChatEvent`). +Using the zod library, we start by writing a type guard function (`isChatEvent`). From this type guard, the library can automatically generate the `ChatEvent` type that we can refer in our code. The advantage of this technique is that, **at runtime**, WorkAdventure can verify that the JSON message received @@ -212,7 +211,7 @@ export interface IframeResponseEvent { If you want to add a new "query" (if you are using the `queryWorkadventure` utility function), you will need to define the type of the query and the type of the response. -The signature of `queryWorkadventure` is: +The signature of `queryWorkadventure` is: ```typescript function queryWorkadventure( @@ -250,12 +249,12 @@ Here is a sample: ```typescript iframeListener.registerAnswerer("openCoWebsite", (openCoWebsiteEvent, source) => { // ... - + return /*...*/; }); ``` -The `registerAnswerer` callback is passed the event, and should return a response (or a promise to the response) in the expected format +The `registerAnswerer` callback is passed the event, and should return a response (or a promise to the response) in the expected format (the one you defined in the `answer` key of `iframeQueryMapTypeGuards`). Important: diff --git a/front/.eslintrc.js b/front/.eslintrc.js index fa57ebf4..d36f5c51 100644 --- a/front/.eslintrc.js +++ b/front/.eslintrc.js @@ -27,8 +27,8 @@ module.exports = { ], "overrides": [ { - "files": ["*.svelte"], - "processor": "svelte3/svelte3" + "files": ["*.svelte"], + "processor": "svelte3/svelte3" } ], "rules": { @@ -36,6 +36,7 @@ module.exports = { "eol-last": ["error", "always"], "@typescript-eslint/no-explicit-any": "error", "no-throw-literal": "error", + "@typescript-eslint/no-unused-vars": ["error"], // TODO: remove those ignored rules and write a stronger code! "@typescript-eslint/no-unsafe-call": "off", "@typescript-eslint/restrict-plus-operands": "off", diff --git a/front/package.json b/front/package.json index eb24773b..d6222324 100644 --- a/front/package.json +++ b/front/package.json @@ -44,7 +44,7 @@ "cross-env": "^7.0.3", "deep-copy-ts": "^0.5.0", "easystarjs": "^0.4.4", - "generic-type-guard": "^3.4.2", + "fast-deep-equal": "^3.1.3", "google-protobuf": "^3.13.0", "phaser": "3.55.1", "phaser-animated-tiles": "workadventure/phaser-animated-tiles#da68bbededd605925621dd4f03bd27e69284b254", @@ -62,7 +62,7 @@ "ts-proto": "^1.96.0", "typesafe-i18n": "^2.59.0", "uuidv4": "^6.2.10", - "zod": "^3.11.6" + "zod": "^3.14.3" }, "scripts": { "start": "run-p templater serve watch-iframe-api svelte-check-watch typesafe-i18n-watch", diff --git a/front/packages/iframe-api-typings/package.json b/front/packages/iframe-api-typings/package.json index fa35851c..d6874921 100644 --- a/front/packages/iframe-api-typings/package.json +++ b/front/packages/iframe-api-typings/package.json @@ -9,5 +9,8 @@ "license": "MIT", "publishConfig": { "access": "public" + }, + "dependencies": { + "rxjs": "^6.6.3" } } diff --git a/front/src/Api/Events/ActionsMenuActionClickedEvent.ts b/front/src/Api/Events/ActionsMenuActionClickedEvent.ts index 4ff5485a..3f24484b 100644 --- a/front/src/Api/Events/ActionsMenuActionClickedEvent.ts +++ b/front/src/Api/Events/ActionsMenuActionClickedEvent.ts @@ -1,12 +1,10 @@ -import * as tg from "generic-type-guard"; +import { z } from "zod"; -export const isActionsMenuActionClickedEvent = new tg.IsInterface() - .withProperties({ - id: tg.isNumber, - actionName: tg.isString, - }) - .get(); +export const isActionsMenuActionClickedEvent = z.object({ + id: z.number(), + actionName: z.string(), +}); -export type ActionsMenuActionClickedEvent = tg.GuardedType; +export type ActionsMenuActionClickedEvent = z.infer; export type ActionsMenuActionClickedEventCallback = (event: ActionsMenuActionClickedEvent) => void; diff --git a/front/src/Api/Events/AddActionsMenuKeyToRemotePlayerEvent.ts b/front/src/Api/Events/AddActionsMenuKeyToRemotePlayerEvent.ts index 6741d730..6f54d324 100644 --- a/front/src/Api/Events/AddActionsMenuKeyToRemotePlayerEvent.ts +++ b/front/src/Api/Events/AddActionsMenuKeyToRemotePlayerEvent.ts @@ -1,12 +1,10 @@ -import * as tg from "generic-type-guard"; +import { z } from "zod"; -export const isAddActionsMenuKeyToRemotePlayerEvent = new tg.IsInterface() - .withProperties({ - id: tg.isNumber, - actionKey: tg.isString, - }) - .get(); +export const isAddActionsMenuKeyToRemotePlayerEvent = z.object({ + id: z.number(), + actionKey: z.string(), +}); -export type AddActionsMenuKeyToRemotePlayerEvent = tg.GuardedType; +export type AddActionsMenuKeyToRemotePlayerEvent = z.infer; export type AddActionsMenuKeyToRemotePlayerEventCallback = (event: AddActionsMenuKeyToRemotePlayerEvent) => void; diff --git a/front/src/Api/Events/ButtonClickedEvent.ts b/front/src/Api/Events/ButtonClickedEvent.ts index 26a8aceb..be1f9028 100644 --- a/front/src/Api/Events/ButtonClickedEvent.ts +++ b/front/src/Api/Events/ButtonClickedEvent.ts @@ -1,12 +1,11 @@ -import * as tg from "generic-type-guard"; +import { z } from "zod"; + +export const isButtonClickedEvent = z.object({ + popupId: z.number(), + buttonId: z.number(), +}); -export const isButtonClickedEvent = new tg.IsInterface() - .withProperties({ - popupId: tg.isNumber, - buttonId: tg.isNumber, - }) - .get(); /** * A message sent from the game to the iFrame when a user enters or leaves a zone marked with the "zone" property. */ -export type ButtonClickedEvent = tg.GuardedType; +export type ButtonClickedEvent = z.infer; diff --git a/front/src/Api/Events/CameraFollowPlayerEvent.ts b/front/src/Api/Events/CameraFollowPlayerEvent.ts index cf34e7fc..a218ba79 100644 --- a/front/src/Api/Events/CameraFollowPlayerEvent.ts +++ b/front/src/Api/Events/CameraFollowPlayerEvent.ts @@ -1,11 +1,10 @@ -import * as tg from "generic-type-guard"; +import { z } from "zod"; + +export const isCameraFollowPlayerEvent = z.object({ + smooth: z.boolean(), +}); -export const isCameraFollowPlayerEvent = new tg.IsInterface() - .withProperties({ - smooth: tg.isBoolean, - }) - .get(); /** * A message sent from the iFrame to the game to make the camera follow player. */ -export type CameraFollowPlayerEvent = tg.GuardedType; +export type CameraFollowPlayerEvent = z.infer; diff --git a/front/src/Api/Events/CameraSetEvent.ts b/front/src/Api/Events/CameraSetEvent.ts index a3da7c62..aa6dab0b 100644 --- a/front/src/Api/Events/CameraSetEvent.ts +++ b/front/src/Api/Events/CameraSetEvent.ts @@ -1,16 +1,15 @@ -import * as tg from "generic-type-guard"; +import { z } from "zod"; + +export const isCameraSetEvent = z.object({ + x: z.number(), + y: z.number(), + width: z.optional(z.number()), + height: z.optional(z.number()), + lock: z.boolean(), + smooth: z.boolean(), +}); -export const isCameraSetEvent = new tg.IsInterface() - .withProperties({ - x: tg.isNumber, - y: tg.isNumber, - width: tg.isOptional(tg.isNumber), - height: tg.isOptional(tg.isNumber), - lock: tg.isBoolean, - smooth: tg.isBoolean, - }) - .get(); /** * A message sent from the iFrame to the game to change the camera position. */ -export type CameraSetEvent = tg.GuardedType; +export type CameraSetEvent = z.infer; diff --git a/front/src/Api/Events/ChangeLayerEvent.ts b/front/src/Api/Events/ChangeLayerEvent.ts index 77ff8ede..952b0ec4 100644 --- a/front/src/Api/Events/ChangeLayerEvent.ts +++ b/front/src/Api/Events/ChangeLayerEvent.ts @@ -1,11 +1,10 @@ -import * as tg from "generic-type-guard"; +import { z } from "zod"; + +export const isChangeLayerEvent = z.object({ + name: z.string(), +}); -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; +export type ChangeLayerEvent = z.infer; diff --git a/front/src/Api/Events/ChangeZoneEvent.ts b/front/src/Api/Events/ChangeZoneEvent.ts index e7ca3668..c5ed1a4c 100644 --- a/front/src/Api/Events/ChangeZoneEvent.ts +++ b/front/src/Api/Events/ChangeZoneEvent.ts @@ -1,11 +1,10 @@ -import * as tg from "generic-type-guard"; +import { z } from "zod"; + +export const isChangeZoneEvent = z.object({ + name: z.string(), +}); -export const isChangeZoneEvent = new tg.IsInterface() - .withProperties({ - name: tg.isString, - }) - .get(); /** * A message sent from the game to the iFrame when a user enters or leaves a zone. */ -export type ChangeZoneEvent = tg.GuardedType; +export type ChangeZoneEvent = z.infer; diff --git a/front/src/Api/Events/ChatEvent.ts b/front/src/Api/Events/ChatEvent.ts index 984859e8..0fadb950 100644 --- a/front/src/Api/Events/ChatEvent.ts +++ b/front/src/Api/Events/ChatEvent.ts @@ -1,12 +1,11 @@ -import * as tg from "generic-type-guard"; +import { z } from "zod"; + +export const isChatEvent = z.object({ + message: z.string(), + author: z.string(), +}); -export const isChatEvent = new tg.IsInterface() - .withProperties({ - message: tg.isString, - author: tg.isString, - }) - .get(); /** * A message sent from the iFrame to the game to add a message in the chat. */ -export type ChatEvent = tg.GuardedType; +export type ChatEvent = z.infer; diff --git a/front/src/Api/Events/CloseCoWebsiteEvent.ts b/front/src/Api/Events/CloseCoWebsiteEvent.ts index 4dc1e51d..0a0123e6 100644 --- a/front/src/Api/Events/CloseCoWebsiteEvent.ts +++ b/front/src/Api/Events/CloseCoWebsiteEvent.ts @@ -1,12 +1,10 @@ -import * as tg from "generic-type-guard"; +import { z } from "zod"; -export const isCloseCoWebsite = new tg.IsInterface() - .withProperties({ - id: tg.isOptional(tg.isString), - }) - .get(); +export const isCloseCoWebsite = z.object({ + id: z.optional(z.string()), +}); /** * A message sent from the iFrame to the game to add a message in the chat. */ -export type CloseCoWebsiteEvent = tg.GuardedType; +export type CloseCoWebsiteEvent = z.infer; diff --git a/front/src/Api/Events/ClosePopupEvent.ts b/front/src/Api/Events/ClosePopupEvent.ts index f604a404..d9916560 100644 --- a/front/src/Api/Events/ClosePopupEvent.ts +++ b/front/src/Api/Events/ClosePopupEvent.ts @@ -1,12 +1,10 @@ -import * as tg from "generic-type-guard"; +import { z } from "zod"; -export const isClosePopupEvent = new tg.IsInterface() - .withProperties({ - popupId: tg.isNumber, - }) - .get(); +export const isClosePopupEvent = z.object({ + popupId: z.number(), +}); /** * A message sent from the iFrame to the game to add a message in the chat. */ -export type ClosePopupEvent = tg.GuardedType; +export type ClosePopupEvent = z.infer; diff --git a/front/src/Api/Events/ColorEvent.ts b/front/src/Api/Events/ColorEvent.ts index c8e6d349..ae0a4f46 100644 --- a/front/src/Api/Events/ColorEvent.ts +++ b/front/src/Api/Events/ColorEvent.ts @@ -1,13 +1,12 @@ -import * as tg from "generic-type-guard"; +import { z } from "zod"; + +export const isColorEvent = z.object({ + red: z.number(), + green: z.number(), + blue: z.number(), +}); -export const isColorEvent = new tg.IsInterface() - .withProperties({ - red: tg.isNumber, - green: tg.isNumber, - blue: tg.isNumber, - }) - .get(); /** * A message sent from the iFrame to the game to dynamically set the outline of the player. */ -export type ColorEvent = tg.GuardedType; +export type ColorEvent = z.infer; diff --git a/front/src/Api/Events/EmbeddedWebsiteEvent.ts b/front/src/Api/Events/EmbeddedWebsiteEvent.ts index 57c24853..81c35568 100644 --- a/front/src/Api/Events/EmbeddedWebsiteEvent.ts +++ b/front/src/Api/Events/EmbeddedWebsiteEvent.ts @@ -1,52 +1,43 @@ -import * as tg from "generic-type-guard"; +import { z } from "zod"; -export const isRectangle = new tg.IsInterface() - .withProperties({ - x: tg.isNumber, - y: tg.isNumber, - width: tg.isNumber, - height: tg.isNumber, - }) - .get(); +export const isRectangle = z.object({ + x: z.number(), + y: z.number(), + width: z.number(), + height: z.number(), +}); -export const isEmbeddedWebsiteEvent = new tg.IsInterface() - .withProperties({ - name: tg.isString, - }) - .withOptionalProperties({ - url: tg.isString, - visible: tg.isBoolean, - allowApi: tg.isBoolean, - allow: tg.isString, - x: tg.isNumber, - y: tg.isNumber, - width: tg.isNumber, - height: tg.isNumber, - origin: tg.isSingletonStringUnion("player", "map"), - scale: tg.isNumber, - }) - .get(); +// TODO: make a variation that is all optional (except for the name) +export type Rectangle = z.infer; -export const isCreateEmbeddedWebsiteEvent = new tg.IsInterface() - .withProperties({ - name: tg.isString, - url: tg.isString, - position: isRectangle, - }) - .withOptionalProperties({ - visible: tg.isBoolean, - allowApi: tg.isBoolean, - allow: tg.isString, - origin: tg.isSingletonStringUnion("player", "map"), - scale: tg.isNumber, - }) - .get(); +export const isEmbeddedWebsiteEvent = z.object({ + name: z.string(), + url: z.optional(z.string()), + visible: z.optional(z.boolean()), + allowApi: z.optional(z.boolean()), + allow: z.optional(z.string()), + x: z.optional(z.number()), + y: z.optional(z.number()), + width: z.optional(z.number()), + height: z.optional(z.number()), + origin: z.optional(z.enum(["player", "map"])), + scale: z.optional(z.number()), +}); /** * A message sent from the iFrame to the game to modify an embedded website */ -export type ModifyEmbeddedWebsiteEvent = tg.GuardedType; +export type ModifyEmbeddedWebsiteEvent = z.infer; -export type CreateEmbeddedWebsiteEvent = tg.GuardedType; -// TODO: make a variation that is all optional (except for the name) -export type Rectangle = tg.GuardedType; +export const isCreateEmbeddedWebsiteEvent = z.object({ + name: z.string(), + url: z.string(), + position: isRectangle, + visible: z.optional(z.boolean()), + allowApi: z.optional(z.boolean()), + allow: z.optional(z.string()), + origin: z.optional(z.enum(["player", "map"])), + scale: z.optional(z.number()), +}); + +export type CreateEmbeddedWebsiteEvent = z.infer; diff --git a/front/src/Api/Events/EnterLeaveEvent.ts b/front/src/Api/Events/EnterLeaveEvent.ts index ca68136e..13733a1f 100644 --- a/front/src/Api/Events/EnterLeaveEvent.ts +++ b/front/src/Api/Events/EnterLeaveEvent.ts @@ -1,11 +1,10 @@ -import * as tg from "generic-type-guard"; +import { z } from "zod"; + +export const isEnterLeaveEvent = z.object({ + name: z.string(), +}); -export const isEnterLeaveEvent = new tg.IsInterface() - .withProperties({ - name: tg.isString, - }) - .get(); /** * A message sent from the game to the iFrame when a user enters or leaves a zone marked with the "zone" property. */ -export type EnterLeaveEvent = tg.GuardedType; +export type EnterLeaveEvent = z.infer; diff --git a/front/src/Api/Events/GameStateEvent.ts b/front/src/Api/Events/GameStateEvent.ts index 80c07e5a..e576c192 100644 --- a/front/src/Api/Events/GameStateEvent.ts +++ b/front/src/Api/Events/GameStateEvent.ts @@ -1,20 +1,19 @@ -import * as tg from "generic-type-guard"; +import { z } from "zod"; + +export const isGameStateEvent = z.object({ + roomId: z.string(), + mapUrl: z.string(), + nickname: z.string(), + language: z.optional(z.string()), + uuid: z.optional(z.string()), + startLayerName: z.optional(z.string()), + tags: z.array(z.string()), + variables: z.unknown(), // Todo : Typing + playerVariables: z.unknown(), // Todo : Typing + userRoomToken: z.optional(z.string()), +}); -export const isGameStateEvent = new tg.IsInterface() - .withProperties({ - roomId: tg.isString, - mapUrl: tg.isString, - nickname: tg.isString, - language: tg.isUnion(tg.isString, tg.isUndefined), - uuid: tg.isUnion(tg.isString, tg.isUndefined), - startLayerName: tg.isUnion(tg.isString, tg.isNull), - tags: tg.isArray(tg.isString), - variables: tg.isObject, - playerVariables: tg.isObject, - userRoomToken: tg.isUnion(tg.isString, tg.isUndefined), - }) - .get(); /** * A message sent from the game to the iFrame when the gameState is received by the script */ -export type GameStateEvent = tg.GuardedType; +export type GameStateEvent = z.infer; diff --git a/front/src/Api/Events/GoToPageEvent.ts b/front/src/Api/Events/GoToPageEvent.ts index d8d6467d..87ff2f7c 100644 --- a/front/src/Api/Events/GoToPageEvent.ts +++ b/front/src/Api/Events/GoToPageEvent.ts @@ -1,12 +1,10 @@ -import * as tg from "generic-type-guard"; +import { z } from "zod"; -export const isGoToPageEvent = new tg.IsInterface() - .withProperties({ - url: tg.isString, - }) - .get(); +export const isGoToPageEvent = z.object({ + url: z.string(), +}); /** * A message sent from the iFrame to the game to add a message in the chat. */ -export type GoToPageEvent = tg.GuardedType; +export type GoToPageEvent = z.infer; diff --git a/front/src/Api/Events/HasPlayerMovedEvent.ts b/front/src/Api/Events/HasPlayerMovedEvent.ts index a3f1aa21..aee1d671 100644 --- a/front/src/Api/Events/HasPlayerMovedEvent.ts +++ b/front/src/Api/Events/HasPlayerMovedEvent.ts @@ -1,19 +1,17 @@ -import * as tg from "generic-type-guard"; +import { z } from "zod"; -export const isHasPlayerMovedEvent = new tg.IsInterface() - .withProperties({ - direction: tg.isElementOf("right", "left", "up", "down"), - moving: tg.isBoolean, - x: tg.isNumber, - y: tg.isNumber, - oldX: tg.isOptional(tg.isNumber), - oldY: tg.isOptional(tg.isNumber), - }) - .get(); +export const isHasPlayerMovedEvent = z.object({ + direction: z.enum(["right", "left", "up", "down"]), + moving: z.boolean(), + x: z.number(), + y: z.number(), + oldX: z.optional(z.number()), + oldY: z.optional(z.number()), +}); /** * A message sent from the game to the iFrame to notify a movement from the current player. */ -export type HasPlayerMovedEvent = tg.GuardedType; +export type HasPlayerMovedEvent = z.infer; export type HasPlayerMovedEventCallback = (event: HasPlayerMovedEvent) => void; diff --git a/front/src/Api/Events/IframeEvent.ts b/front/src/Api/Events/IframeEvent.ts index 9daccbec..6a8aa823 100644 --- a/front/src/Api/Events/IframeEvent.ts +++ b/front/src/Api/Events/IframeEvent.ts @@ -1,46 +1,44 @@ -import * as tg from "generic-type-guard"; +import { z } from "zod"; import type { ButtonClickedEvent } from "./ButtonClickedEvent"; -import type { ChatEvent } from "./ChatEvent"; -import type { ClosePopupEvent } from "./ClosePopupEvent"; +import { isChatEvent } from "./ChatEvent"; +import { isClosePopupEvent } from "./ClosePopupEvent"; import type { EnterLeaveEvent } from "./EnterLeaveEvent"; -import type { GoToPageEvent } from "./GoToPageEvent"; -import type { LoadPageEvent } from "./LoadPageEvent"; +import { isGoToPageEvent } from "./GoToPageEvent"; +import { isLoadPageEvent } from "./LoadPageEvent"; import { isCoWebsite, isOpenCoWebsiteEvent } from "./OpenCoWebsiteEvent"; -import type { OpenPopupEvent } from "./OpenPopupEvent"; -import type { OpenTabEvent } from "./OpenTabEvent"; +import { isOpenPopupEvent } from "./OpenPopupEvent"; +import { isOpenTabEvent } from "./OpenTabEvent"; import type { UserInputChatEvent } from "./UserInputChatEvent"; -import type { LayerEvent } from "./LayerEvent"; -import type { SetPropertyEvent } from "./setPropertyEvent"; -import type { LoadSoundEvent } from "./LoadSoundEvent"; -import type { PlaySoundEvent } from "./PlaySoundEvent"; -import type { StopSoundEvent } from "./StopSoundEvent"; +import { isLayerEvent } from "./LayerEvent"; +import { isSetPropertyEvent } from "./setPropertyEvent"; +import { isLoadSoundEvent } from "./LoadSoundEvent"; +import { isPlaySoundEvent } from "./PlaySoundEvent"; +import { isStopSoundEvent } from "./StopSoundEvent"; import type { MenuItemClickedEvent } from "./ui/MenuItemClickedEvent"; import type { HasPlayerMovedEvent } from "./HasPlayerMovedEvent"; -import type { SetTilesEvent } from "./SetTilesEvent"; +import { isSetTilesEvent } from "./SetTilesEvent"; import type { SetVariableEvent } from "./SetVariableEvent"; import { isGameStateEvent } from "./GameStateEvent"; import { isMapDataEvent } from "./MapDataEvent"; import { isSetVariableEvent } from "./SetVariableEvent"; -import type { EmbeddedWebsite } from "../iframe/Room/EmbeddedWebsite"; -import { isCreateEmbeddedWebsiteEvent } from "./EmbeddedWebsiteEvent"; -import type { LoadTilesetEvent } from "./LoadTilesetEvent"; +import { isCreateEmbeddedWebsiteEvent, isEmbeddedWebsiteEvent } from "./EmbeddedWebsiteEvent"; import { isLoadTilesetEvent } from "./LoadTilesetEvent"; import type { MessageReferenceEvent } from "./ui/TriggerActionMessageEvent"; import { isMessageReferenceEvent, isTriggerActionMessageEvent } from "./ui/TriggerActionMessageEvent"; -import type { MenuRegisterEvent, UnregisterMenuEvent } from "./ui/MenuRegisterEvent"; +import { isMenuRegisterEvent, isUnregisterMenuEvent } from "./ui/MenuRegisterEvent"; import type { ChangeLayerEvent } from "./ChangeLayerEvent"; import { isPlayerPosition } from "./PlayerPosition"; import type { WasCameraUpdatedEvent } from "./WasCameraUpdatedEvent"; import type { ChangeZoneEvent } from "./ChangeZoneEvent"; -import type { CameraSetEvent } from "./CameraSetEvent"; -import type { CameraFollowPlayerEvent } from "./CameraFollowPlayerEvent"; +import { isCameraSetEvent } from "./CameraSetEvent"; +import { isCameraFollowPlayerEvent } from "./CameraFollowPlayerEvent"; import { isColorEvent } from "./ColorEvent"; import { isMovePlayerToEventConfig } from "./MovePlayerToEvent"; import { isMovePlayerToEventAnswer } from "./MovePlayerToEventAnswer"; import type { RemotePlayerClickedEvent } from "./RemotePlayerClickedEvent"; -import type { AddActionsMenuKeyToRemotePlayerEvent } from "./AddActionsMenuKeyToRemotePlayerEvent"; +import { isAddActionsMenuKeyToRemotePlayerEvent } from "./AddActionsMenuKeyToRemotePlayerEvent"; import type { ActionsMenuActionClickedEvent } from "./ActionsMenuActionClickedEvent"; -import type { RemoveActionsMenuKeyFromRemotePlayerEvent } from "./RemoveActionsMenuKeyFromRemotePlayerEvent"; +import { isRemoveActionsMenuKeyFromRemotePlayerEvent } from "./RemoveActionsMenuKeyFromRemotePlayerEvent"; export interface TypedMessageEvent extends MessageEvent { data: T; @@ -49,45 +47,114 @@ export interface TypedMessageEvent extends MessageEvent { /** * List event types sent from an iFrame to WorkAdventure */ -export type IframeEventMap = { - addActionsMenuKeyToRemotePlayer: AddActionsMenuKeyToRemotePlayerEvent; - removeActionsMenuKeyFromRemotePlayer: RemoveActionsMenuKeyFromRemotePlayerEvent; - loadPage: LoadPageEvent; - chat: ChatEvent; - cameraFollowPlayer: CameraFollowPlayerEvent; - cameraSet: CameraSetEvent; - openPopup: OpenPopupEvent; - closePopup: ClosePopupEvent; - openTab: OpenTabEvent; - goToPage: GoToPageEvent; - disablePlayerControls: null; - restorePlayerControls: null; - displayBubble: null; - removeBubble: null; - onPlayerMove: undefined; - onOpenActionMenu: undefined; - onCameraUpdate: undefined; - showLayer: LayerEvent; - hideLayer: LayerEvent; - setProperty: SetPropertyEvent; - loadSound: LoadSoundEvent; - playSound: PlaySoundEvent; - stopSound: StopSoundEvent; - getState: undefined; - loadTileset: LoadTilesetEvent; - registerMenu: MenuRegisterEvent; - unregisterMenu: UnregisterMenuEvent; - setTiles: SetTilesEvent; - modifyEmbeddedWebsite: Partial; // Note: name should be compulsory in fact -}; -export interface IframeEvent { - type: T; - data: IframeEventMap[T]; -} +export const isIframeEventWrapper = z.union([ + z.object({ + type: z.literal("addActionsMenuKeyToRemotePlayer"), + data: isAddActionsMenuKeyToRemotePlayerEvent, + }), + z.object({ + type: z.literal("removeActionsMenuKeyFromRemotePlayer"), + data: isRemoveActionsMenuKeyFromRemotePlayerEvent, + }), + z.object({ + type: z.literal("loadPage"), + data: isLoadPageEvent, + }), + z.object({ + type: z.literal("chat"), + data: isChatEvent, + }), + z.object({ + type: z.literal("cameraFollowPlayer"), + data: isCameraFollowPlayerEvent, + }), + z.object({ + type: z.literal("cameraSet"), + data: isCameraSetEvent, + }), + z.object({ + type: z.literal("openPopup"), + data: isOpenPopupEvent, + }), + z.object({ + type: z.literal("closePopup"), + data: isClosePopupEvent, + }), + z.object({ + type: z.literal("openTab"), + data: isOpenTabEvent, + }), + z.object({ + type: z.literal("goToPage"), + data: isGoToPageEvent, + }), + z.object({ + type: z.literal("disablePlayerControls"), + data: z.undefined(), + }), + z.object({ + type: z.literal("restorePlayerControls"), + data: z.undefined(), + }), + z.object({ + type: z.literal("displayBubble"), + data: z.undefined(), + }), + z.object({ + type: z.literal("removeBubble"), + data: z.undefined(), + }), + z.object({ + type: z.literal("onPlayerMove"), + data: z.undefined(), + }), + z.object({ + type: z.literal("onCameraUpdate"), + data: z.undefined(), + }), + z.object({ + type: z.literal("showLayer"), + data: isLayerEvent, + }), + z.object({ + type: z.literal("hideLayer"), + data: isLayerEvent, + }), + z.object({ + type: z.literal("setProperty"), + data: isSetPropertyEvent, + }), + z.object({ + type: z.literal("loadSound"), + data: isLoadSoundEvent, + }), + z.object({ + type: z.literal("playSound"), + data: isPlaySoundEvent, + }), + z.object({ + type: z.literal("stopSound"), + data: isStopSoundEvent, + }), + z.object({ + type: z.literal("registerMenu"), + data: isMenuRegisterEvent, + }), + z.object({ + type: z.literal("unregisterMenu"), + data: isUnregisterMenuEvent, + }), + z.object({ + type: z.literal("setTiles"), + data: isSetTilesEvent, + }), + z.object({ + type: z.literal("modifyEmbeddedWebsite"), + data: isEmbeddedWebsiteEvent, + }), +]); -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export const isIframeEventWrapper = (event: any): event is IframeEvent => - typeof event.type === "string"; +export type IframeEvent = z.infer; export interface IframeResponseEventMap { userInputChat: UserInputChatEvent; @@ -116,73 +183,78 @@ export const isIframeResponseEventWrapper = (event: { type?: string; }): event is IframeResponseEvent => typeof event.type === "string"; +export const isLookingLikeIframeEventWrapper = z.object({ + type: z.string(), + data: z.unknown().optional(), +}); + /** * List event types sent from an iFrame to WorkAdventure that expect a unique answer from WorkAdventure along the type for the answer from WorkAdventure to the iFrame. * Types are defined using Type guards that will actually bused to enforce and check types. */ export const iframeQueryMapTypeGuards = { getState: { - query: tg.isUndefined, + query: z.undefined(), answer: isGameStateEvent, }, getMapData: { - query: tg.isUndefined, + query: z.undefined(), answer: isMapDataEvent, }, setVariable: { query: isSetVariableEvent, - answer: tg.isUndefined, + answer: z.undefined(), }, loadTileset: { query: isLoadTilesetEvent, - answer: tg.isNumber, + answer: z.number(), }, openCoWebsite: { query: isOpenCoWebsiteEvent, answer: isCoWebsite, }, getCoWebsites: { - query: tg.isUndefined, - answer: tg.isArray(isCoWebsite), + query: z.undefined(), + answer: z.array(isCoWebsite), }, closeCoWebsite: { - query: tg.isString, - answer: tg.isUndefined, + query: z.string(), + answer: z.undefined(), }, closeCoWebsites: { - query: tg.isUndefined, - answer: tg.isUndefined, + query: z.undefined(), + answer: z.undefined(), }, triggerActionMessage: { query: isTriggerActionMessageEvent, - answer: tg.isUndefined, + answer: z.undefined(), }, removeActionMessage: { query: isMessageReferenceEvent, - answer: tg.isUndefined, + answer: z.undefined(), }, getEmbeddedWebsite: { - query: tg.isString, + query: z.string(), answer: isCreateEmbeddedWebsiteEvent, }, deleteEmbeddedWebsite: { - query: tg.isString, - answer: tg.isUndefined, + query: z.string(), + answer: z.undefined(), }, createEmbeddedWebsite: { query: isCreateEmbeddedWebsiteEvent, - answer: tg.isUndefined, + answer: z.undefined(), }, setPlayerOutline: { query: isColorEvent, - answer: tg.isUndefined, + answer: z.undefined(), }, removePlayerOutline: { - query: tg.isUndefined, - answer: tg.isUndefined, + query: z.undefined(), + answer: z.undefined(), }, getPlayerPosition: { - query: tg.isUndefined, + query: z.undefined(), answer: isPlayerPosition, }, movePlayerTo: { @@ -191,14 +263,13 @@ export const iframeQueryMapTypeGuards = { }, }; -type GuardedType = T extends (x: unknown) => x is infer T ? T : never; type IframeQueryMapTypeGuardsType = typeof iframeQueryMapTypeGuards; type UnknownToVoid = undefined extends T ? void : T; export type IframeQueryMap = { [key in keyof IframeQueryMapTypeGuardsType]: { - query: GuardedType; - answer: UnknownToVoid>; + query: z.infer; + answer: UnknownToVoid>; }; }; @@ -226,11 +297,18 @@ export const isIframeQuery = (event: any): event is IframeQuery; +export type LayerEvent = z.infer; diff --git a/front/src/Api/Events/LoadPageEvent.ts b/front/src/Api/Events/LoadPageEvent.ts index 63600a28..c5fe11b3 100644 --- a/front/src/Api/Events/LoadPageEvent.ts +++ b/front/src/Api/Events/LoadPageEvent.ts @@ -1,12 +1,10 @@ -import * as tg from "generic-type-guard"; +import { z } from "zod"; -export const isLoadPageEvent = new tg.IsInterface() - .withProperties({ - url: tg.isString, - }) - .get(); +export const isLoadPageEvent = z.object({ + url: z.string(), +}); /** * A message sent from the iFrame to the game to add a message in the chat. */ -export type LoadPageEvent = tg.GuardedType; +export type LoadPageEvent = z.infer; diff --git a/front/src/Api/Events/LoadSoundEvent.ts b/front/src/Api/Events/LoadSoundEvent.ts index f48f202f..fa338fac 100644 --- a/front/src/Api/Events/LoadSoundEvent.ts +++ b/front/src/Api/Events/LoadSoundEvent.ts @@ -1,12 +1,10 @@ -import * as tg from "generic-type-guard"; +import { z } from "zod"; -export const isLoadSoundEvent = new tg.IsInterface() - .withProperties({ - url: tg.isString, - }) - .get(); +export const isLoadSoundEvent = z.object({ + url: z.string(), +}); /** * A message sent from the iFrame to the game to add a message in the chat. */ -export type LoadSoundEvent = tg.GuardedType; +export type LoadSoundEvent = z.infer; diff --git a/front/src/Api/Events/LoadTilesetEvent.ts b/front/src/Api/Events/LoadTilesetEvent.ts index ecaf93be..239ffcc2 100644 --- a/front/src/Api/Events/LoadTilesetEvent.ts +++ b/front/src/Api/Events/LoadTilesetEvent.ts @@ -1,12 +1,10 @@ -import * as tg from "generic-type-guard"; +import { z } from "zod"; -export const isLoadTilesetEvent = new tg.IsInterface() - .withProperties({ - url: tg.isString, - }) - .get(); +export const isLoadTilesetEvent = z.object({ + url: z.string(), +}); /** * A message sent from the iFrame to the game to add a message in the chat. */ -export type LoadTilesetEvent = tg.GuardedType; +export type LoadTilesetEvent = z.infer; diff --git a/front/src/Api/Events/MapDataEvent.ts b/front/src/Api/Events/MapDataEvent.ts index f63164ed..d5fb62d4 100644 --- a/front/src/Api/Events/MapDataEvent.ts +++ b/front/src/Api/Events/MapDataEvent.ts @@ -1,12 +1,10 @@ -import * as tg from "generic-type-guard"; +import { z } from "zod"; -export const isMapDataEvent = new tg.IsInterface() - .withProperties({ - data: tg.isObject, - }) - .get(); +export const isMapDataEvent = z.object({ + data: z.unknown(), // Todo : Typing +}); /** * A message sent from the game to the iFrame when the data of the layers change after the iFrame send a message to the game that it want to listen to the data of the layers */ -export type MapDataEvent = tg.GuardedType; +export type MapDataEvent = z.infer; diff --git a/front/src/Api/Events/MovePlayerToEvent.ts b/front/src/Api/Events/MovePlayerToEvent.ts index 462e2f43..543270a8 100644 --- a/front/src/Api/Events/MovePlayerToEvent.ts +++ b/front/src/Api/Events/MovePlayerToEvent.ts @@ -1,11 +1,9 @@ -import * as tg from "generic-type-guard"; +import { z } from "zod"; -export const isMovePlayerToEventConfig = new tg.IsInterface() - .withProperties({ - x: tg.isNumber, - y: tg.isNumber, - speed: tg.isOptional(tg.isNumber), - }) - .get(); +export const isMovePlayerToEventConfig = z.object({ + x: z.number(), + y: z.number(), + speed: z.optional(z.number()), +}); -export type MovePlayerToEvent = tg.GuardedType; +export type MovePlayerToEvent = z.infer; diff --git a/front/src/Api/Events/MovePlayerToEventAnswer.ts b/front/src/Api/Events/MovePlayerToEventAnswer.ts index 67d2f9ae..c6cdc0de 100644 --- a/front/src/Api/Events/MovePlayerToEventAnswer.ts +++ b/front/src/Api/Events/MovePlayerToEventAnswer.ts @@ -1,11 +1,9 @@ -import * as tg from "generic-type-guard"; +import { z } from "zod"; -export const isMovePlayerToEventAnswer = new tg.IsInterface() - .withProperties({ - x: tg.isNumber, - y: tg.isNumber, - cancelled: tg.isBoolean, - }) - .get(); +export const isMovePlayerToEventAnswer = z.object({ + x: z.number(), + y: z.number(), + cancelled: z.boolean(), +}); -export type MovePlayerToEventAnswer = tg.GuardedType; +export type ActionsMenuActionClickedEvent = z.infer; diff --git a/front/src/Api/Events/OpenCoWebsiteEvent.ts b/front/src/Api/Events/OpenCoWebsiteEvent.ts index b991d3f7..91a87e41 100644 --- a/front/src/Api/Events/OpenCoWebsiteEvent.ts +++ b/front/src/Api/Events/OpenCoWebsiteEvent.ts @@ -1,24 +1,20 @@ -import * as tg from "generic-type-guard"; +import { z } from "zod"; -export const isOpenCoWebsiteEvent = new tg.IsInterface() - .withProperties({ - url: tg.isString, - allowApi: tg.isOptional(tg.isBoolean), - allowPolicy: tg.isOptional(tg.isString), - widthPercent: tg.isOptional(tg.isNumber), - position: tg.isOptional(tg.isNumber), - closable: tg.isOptional(tg.isBoolean), - lazy: tg.isOptional(tg.isBoolean), - }) - .get(); +export const isOpenCoWebsiteEvent = z.object({ + url: z.string(), + allowApi: z.optional(z.boolean()), + allowPolicy: z.optional(z.string()), + widthPercent: z.optional(z.number()), + position: z.optional(z.number()), + closable: z.optional(z.boolean()), + lazy: z.optional(z.boolean()), +}); -export const isCoWebsite = new tg.IsInterface() - .withProperties({ - id: tg.isString, - }) - .get(); +export const isCoWebsite = z.object({ + id: z.string(), +}); /** * A message sent from the iFrame to the game to add a message in the chat. */ -export type OpenCoWebsiteEvent = tg.GuardedType; +export type OpenCoWebsiteEvent = z.infer; diff --git a/front/src/Api/Events/OpenPopupEvent.ts b/front/src/Api/Events/OpenPopupEvent.ts index c1070bbe..3e157780 100644 --- a/front/src/Api/Events/OpenPopupEvent.ts +++ b/front/src/Api/Events/OpenPopupEvent.ts @@ -1,22 +1,18 @@ -import * as tg from "generic-type-guard"; +import { z } from "zod"; -const isButtonDescriptor = new tg.IsInterface() - .withProperties({ - label: tg.isString, - className: tg.isOptional(tg.isString), - }) - .get(); +export const isButtonDescriptor = z.object({ + label: z.string(), + className: z.optional(z.string()), +}); -export const isOpenPopupEvent = new tg.IsInterface() - .withProperties({ - popupId: tg.isNumber, - targetObject: tg.isString, - message: tg.isString, - buttons: tg.isArray(isButtonDescriptor), - }) - .get(); +export const isOpenPopupEvent = z.object({ + popupId: z.number(), + targetObject: z.string(), + message: z.string(), + buttons: z.array(isButtonDescriptor), +}); /** * A message sent from the iFrame to the game to add a message in the chat. */ -export type OpenPopupEvent = tg.GuardedType; +export type OpenPopupEvent = z.infer; diff --git a/front/src/Api/Events/OpenTabEvent.ts b/front/src/Api/Events/OpenTabEvent.ts index 6fe6ec21..d72d8f23 100644 --- a/front/src/Api/Events/OpenTabEvent.ts +++ b/front/src/Api/Events/OpenTabEvent.ts @@ -1,12 +1,10 @@ -import * as tg from "generic-type-guard"; +import { z } from "zod"; -export const isOpenTabEvent = new tg.IsInterface() - .withProperties({ - url: tg.isString, - }) - .get(); +export const isOpenTabEvent = z.object({ + url: z.string(), +}); /** * A message sent from the iFrame to the game to add a message in the chat. */ -export type OpenTabEvent = tg.GuardedType; +export type OpenTabEvent = z.infer; diff --git a/front/src/Api/Events/PlaySoundEvent.ts b/front/src/Api/Events/PlaySoundEvent.ts index 6fe56746..34cf0ad6 100644 --- a/front/src/Api/Events/PlaySoundEvent.ts +++ b/front/src/Api/Events/PlaySoundEvent.ts @@ -1,25 +1,21 @@ -import * as tg from "generic-type-guard"; +import { z } from "zod"; -const isSoundConfig = new tg.IsInterface() - .withProperties({ - volume: tg.isOptional(tg.isNumber), - loop: tg.isOptional(tg.isBoolean), - mute: tg.isOptional(tg.isBoolean), - rate: tg.isOptional(tg.isNumber), - detune: tg.isOptional(tg.isNumber), - seek: tg.isOptional(tg.isNumber), - delay: tg.isOptional(tg.isNumber), - }) - .get(); +export const isSoundConfig = z.object({ + volume: z.optional(z.number()), + loop: z.optional(z.boolean()), + mute: z.optional(z.boolean()), + rate: z.optional(z.number()), + detune: z.optional(z.number()), + seek: z.optional(z.number()), + delay: z.optional(z.number()), +}); -export const isPlaySoundEvent = new tg.IsInterface() - .withProperties({ - url: tg.isString, - config: tg.isOptional(isSoundConfig), - }) - .get(); +export const isPlaySoundEvent = z.object({ + url: z.string(), + config: z.optional(isSoundConfig), +}); /** * A message sent from the iFrame to the game to add a message in the chat. */ -export type PlaySoundEvent = tg.GuardedType; +export type PlaySoundEvent = z.infer; diff --git a/front/src/Api/Events/PlayerPosition.ts b/front/src/Api/Events/PlayerPosition.ts index 54fac6fe..b5a0c9dd 100644 --- a/front/src/Api/Events/PlayerPosition.ts +++ b/front/src/Api/Events/PlayerPosition.ts @@ -1,10 +1,8 @@ -import * as tg from "generic-type-guard"; +import { z } from "zod"; -export const isPlayerPosition = new tg.IsInterface() - .withProperties({ - x: tg.isNumber, - y: tg.isNumber, - }) - .get(); +export const isPlayerPosition = z.object({ + x: z.number(), + y: z.number(), +}); -export type PlayerPosition = tg.GuardedType; +export type PlayerPosition = z.infer; diff --git a/front/src/Api/Events/RemotePlayerClickedEvent.ts b/front/src/Api/Events/RemotePlayerClickedEvent.ts index bf217adc..a50acf87 100644 --- a/front/src/Api/Events/RemotePlayerClickedEvent.ts +++ b/front/src/Api/Events/RemotePlayerClickedEvent.ts @@ -1,15 +1,13 @@ -import * as tg from "generic-type-guard"; +import { z } from "zod"; // TODO: Change for player Clicked, add all neccessary data -export const isRemotePlayerClickedEvent = new tg.IsInterface() - .withProperties({ - id: tg.isNumber, - }) - .get(); +export const isRemotePlayerClickedEvent = z.object({ + id: z.number(), +}); /** * A message sent from the game to the iFrame when RemotePlayer is clicked. */ -export type RemotePlayerClickedEvent = tg.GuardedType; +export type RemotePlayerClickedEvent = z.infer; export type RemotePlayerClickedEventCallback = (event: RemotePlayerClickedEvent) => void; diff --git a/front/src/Api/Events/RemoveActionsMenuKeyFromRemotePlayerEvent.ts b/front/src/Api/Events/RemoveActionsMenuKeyFromRemotePlayerEvent.ts index 745a07df..c8857b5d 100644 --- a/front/src/Api/Events/RemoveActionsMenuKeyFromRemotePlayerEvent.ts +++ b/front/src/Api/Events/RemoveActionsMenuKeyFromRemotePlayerEvent.ts @@ -1,15 +1,11 @@ -import * as tg from "generic-type-guard"; +import { z } from "zod"; -export const isRemoveActionsMenuKeyFromRemotePlayerEvent = new tg.IsInterface() - .withProperties({ - id: tg.isNumber, - actionKey: tg.isString, - }) - .get(); +export const isRemoveActionsMenuKeyFromRemotePlayerEvent = z.object({ + id: z.number(), + actionKey: z.string(), +}); -export type RemoveActionsMenuKeyFromRemotePlayerEvent = tg.GuardedType< - typeof isRemoveActionsMenuKeyFromRemotePlayerEvent ->; +export type RemoveActionsMenuKeyFromRemotePlayerEvent = z.infer; export type RemoveActionsMenuKeyFromRemotePlayerEventCallback = ( event: RemoveActionsMenuKeyFromRemotePlayerEvent diff --git a/front/src/Api/Events/SetTilesEvent.ts b/front/src/Api/Events/SetTilesEvent.ts index 371f0884..847eef4c 100644 --- a/front/src/Api/Events/SetTilesEvent.ts +++ b/front/src/Api/Events/SetTilesEvent.ts @@ -1,16 +1,15 @@ -import * as tg from "generic-type-guard"; +import { z } from "zod"; -export const isSetTilesEvent = tg.isArray( - new tg.IsInterface() - .withProperties({ - x: tg.isNumber, - y: tg.isNumber, - tile: tg.isUnion(tg.isUnion(tg.isNumber, tg.isString), tg.isNull), - layer: tg.isString, - }) - .get() +export const isSetTilesEvent = z.array( + z.object({ + x: z.number(), + y: z.number(), + tile: z.union([z.number(), z.string(), z.null()]), + layer: z.string(), + }) ); + /** * A message sent from the iFrame to the game to set one or many tiles. */ -export type SetTilesEvent = tg.GuardedType; +export type SetTilesEvent = z.infer; diff --git a/front/src/Api/Events/SetVariableEvent.ts b/front/src/Api/Events/SetVariableEvent.ts index 80ac6f6e..3b869afb 100644 --- a/front/src/Api/Events/SetVariableEvent.ts +++ b/front/src/Api/Events/SetVariableEvent.ts @@ -1,20 +1,17 @@ -import * as tg from "generic-type-guard"; +import { z } from "zod"; + +export const isSetVariableEvent = z.object({ + key: z.string(), + value: z.unknown(), + target: z.enum(["global", "player"]), +}); + +export const isSetVariableIframeEvent = z.object({ + type: z.enum(["setVariable"]), + data: isSetVariableEvent, +}); -export const isSetVariableEvent = new tg.IsInterface() - .withProperties({ - key: tg.isString, - value: tg.isUnknown, - target: tg.isSingletonStringUnion("global", "player"), - }) - .get(); /** * A message sent from the iFrame to the game to change the value of the property of the layer */ -export type SetVariableEvent = tg.GuardedType; - -export const isSetVariableIframeEvent = new tg.IsInterface() - .withProperties({ - type: tg.isSingletonString("setVariable"), - data: isSetVariableEvent, - }) - .get(); +export type SetVariableEvent = z.infer; diff --git a/front/src/Api/Events/StopSoundEvent.ts b/front/src/Api/Events/StopSoundEvent.ts index cdfe43ca..44d1cd0d 100644 --- a/front/src/Api/Events/StopSoundEvent.ts +++ b/front/src/Api/Events/StopSoundEvent.ts @@ -1,12 +1,10 @@ -import * as tg from "generic-type-guard"; +import { z } from "zod"; -export const isStopSoundEvent = new tg.IsInterface() - .withProperties({ - url: tg.isString, - }) - .get(); +export const isStopSoundEvent = z.object({ + url: z.string(), +}); /** * A message sent from the iFrame to the game to add a message in the chat. */ -export type StopSoundEvent = tg.GuardedType; +export type StopSoundEvent = z.infer; diff --git a/front/src/Api/Events/UserInputChatEvent.ts b/front/src/Api/Events/UserInputChatEvent.ts index 9de41327..940063ad 100644 --- a/front/src/Api/Events/UserInputChatEvent.ts +++ b/front/src/Api/Events/UserInputChatEvent.ts @@ -1,11 +1,10 @@ -import * as tg from "generic-type-guard"; +import { z } from "zod"; + +export const isUserInputChatEvent = z.object({ + message: z.string(), +}); -export const isUserInputChatEvent = new tg.IsInterface() - .withProperties({ - message: tg.isString, - }) - .get(); /** * A message sent from the game to the iFrame when a user types a message in the chat. */ -export type UserInputChatEvent = tg.GuardedType; +export type UserInputChatEvent = z.infer; diff --git a/front/src/Api/Events/WasCameraUpdatedEvent.ts b/front/src/Api/Events/WasCameraUpdatedEvent.ts index 34e39a84..8a686695 100644 --- a/front/src/Api/Events/WasCameraUpdatedEvent.ts +++ b/front/src/Api/Events/WasCameraUpdatedEvent.ts @@ -1,19 +1,16 @@ -import * as tg from "generic-type-guard"; +import { z } from "zod"; -export const isWasCameraUpdatedEvent = new tg.IsInterface() - .withProperties({ - x: tg.isNumber, - y: tg.isNumber, - width: tg.isNumber, - height: tg.isNumber, - zoom: tg.isNumber, - }) - .get(); +export const isWasCameraUpdatedEvent = z.object({ + x: z.number(), + y: z.number(), + width: z.number(), + height: z.number(), + zoom: z.number(), +}); /** * A message sent from the game to the iFrame to notify a movement from the camera. */ - -export type WasCameraUpdatedEvent = tg.GuardedType; +export type WasCameraUpdatedEvent = z.infer; export type WasCameraUpdatedEventCallback = (event: WasCameraUpdatedEvent) => void; diff --git a/front/src/Api/Events/setPropertyEvent.ts b/front/src/Api/Events/setPropertyEvent.ts index 7335f781..1a1f7e38 100644 --- a/front/src/Api/Events/setPropertyEvent.ts +++ b/front/src/Api/Events/setPropertyEvent.ts @@ -1,13 +1,12 @@ -import * as tg from "generic-type-guard"; +import { z } from "zod"; + +export const isSetPropertyEvent = z.object({ + layerName: z.string(), + propertyName: z.string(), + propertyValue: z.optional(z.union([z.string(), z.number(), z.boolean()])), +}); -export const isSetPropertyEvent = new tg.IsInterface() - .withProperties({ - layerName: tg.isString, - propertyName: tg.isString, - propertyValue: tg.isUnion(tg.isString, tg.isUnion(tg.isNumber, tg.isUnion(tg.isBoolean, tg.isUndefined))), - }) - .get(); /** * A message sent from the iFrame to the game to change the value of the property of the layer */ -export type SetPropertyEvent = tg.GuardedType; +export type SetPropertyEvent = z.infer; diff --git a/front/src/Api/Events/ui/MenuItemClickedEvent.ts b/front/src/Api/Events/ui/MenuItemClickedEvent.ts index a8c8d0ed..eb2e21f3 100644 --- a/front/src/Api/Events/ui/MenuItemClickedEvent.ts +++ b/front/src/Api/Events/ui/MenuItemClickedEvent.ts @@ -1,11 +1,10 @@ -import * as tg from "generic-type-guard"; +import { z } from "zod"; + +export const isMenuItemClickedEvent = z.object({ + menuItem: z.string(), +}); -export const isMenuItemClickedEvent = new tg.IsInterface() - .withProperties({ - menuItem: tg.isString, - }) - .get(); /** * A message sent from the game to the iFrame when a menu item is clicked. */ -export type MenuItemClickedEvent = tg.GuardedType; +export type MenuItemClickedEvent = z.infer; diff --git a/front/src/Api/Events/ui/MenuRegisterEvent.ts b/front/src/Api/Events/ui/MenuRegisterEvent.ts index f620745f..201a52d4 100644 --- a/front/src/Api/Events/ui/MenuRegisterEvent.ts +++ b/front/src/Api/Events/ui/MenuRegisterEvent.ts @@ -1,31 +1,25 @@ -import * as tg from "generic-type-guard"; +import { z } from "zod"; /** * A message sent from a script to the game to remove a custom menu from the menu */ -export const isUnregisterMenuEvent = new tg.IsInterface() - .withProperties({ - name: tg.isString, - }) - .get(); +export const isUnregisterMenuEvent = z.object({ + name: z.string(), +}); -export type UnregisterMenuEvent = tg.GuardedType; +export type UnregisterMenuEvent = z.infer; -export const isMenuRegisterOptions = new tg.IsInterface() - .withProperties({ - allowApi: tg.isBoolean, - }) - .get(); +export const isMenuRegisterOptions = z.object({ + allowApi: z.boolean(), +}); /** * A message sent from a script to the game to add a custom menu from the menu */ -export const isMenuRegisterEvent = new tg.IsInterface() - .withProperties({ - name: tg.isString, - iframe: tg.isUnion(tg.isString, tg.isUndefined), - options: isMenuRegisterOptions, - }) - .get(); +export const isMenuRegisterEvent = z.object({ + name: z.string(), + iframe: z.optional(z.string()), + options: isMenuRegisterOptions, +}); -export type MenuRegisterEvent = tg.GuardedType; +export type MenuRegisterEvent = z.infer; diff --git a/front/src/Api/Events/ui/TriggerActionMessageEvent.ts b/front/src/Api/Events/ui/TriggerActionMessageEvent.ts index 48f1cae6..e34d72e8 100644 --- a/front/src/Api/Events/ui/TriggerActionMessageEvent.ts +++ b/front/src/Api/Events/ui/TriggerActionMessageEvent.ts @@ -1,26 +1,22 @@ -import * as tg from "generic-type-guard"; +import { z } from "zod"; export const triggerActionMessage = "triggerActionMessage"; export const removeActionMessage = "removeActionMessage"; -export const isActionMessageType = tg.isSingletonStringUnion("message", "warning"); +export const isActionMessageType = z.enum(["message", "warning"]); -export type ActionMessageType = tg.GuardedType; +export type ActionMessageType = z.infer; -export const isTriggerActionMessageEvent = new tg.IsInterface() - .withProperties({ - message: tg.isString, - uuid: tg.isString, - type: isActionMessageType, - }) - .get(); +export const isTriggerActionMessageEvent = z.object({ + message: z.string(), + uuid: z.string(), + type: isActionMessageType, +}); -export type TriggerActionMessageEvent = tg.GuardedType; +export type TriggerActionMessageEvent = z.infer; -export const isMessageReferenceEvent = new tg.IsInterface() - .withProperties({ - uuid: tg.isString, - }) - .get(); +export const isMessageReferenceEvent = z.object({ + uuid: z.string(), +}); -export type MessageReferenceEvent = tg.GuardedType; +export type MessageReferenceEvent = z.infer; diff --git a/front/src/Api/Events/ui/TriggerMessageEventHandler.ts b/front/src/Api/Events/ui/TriggerMessageEventHandler.ts index f7da0ad2..bd517031 100644 --- a/front/src/Api/Events/ui/TriggerMessageEventHandler.ts +++ b/front/src/Api/Events/ui/TriggerMessageEventHandler.ts @@ -5,20 +5,16 @@ import { triggerActionMessage, } from "./TriggerActionMessageEvent"; -import * as tg from "generic-type-guard"; +import { z } from "zod"; -const isTriggerMessageEventObject = new tg.IsInterface() - .withProperties({ - type: tg.isSingletonString(triggerActionMessage), - data: isTriggerActionMessageEvent, - }) - .get(); +const isTriggerMessageEventObject = z.object({ + type: z.enum([triggerActionMessage]), + data: isTriggerActionMessageEvent, +}); -const isTriggerMessageRemoveEventObject = new tg.IsInterface() - .withProperties({ - type: tg.isSingletonString(removeActionMessage), - data: isMessageReferenceEvent, - }) - .get(); +const isTriggerMessageRemoveEventObject = z.object({ + type: z.enum([removeActionMessage]), + data: isMessageReferenceEvent, +}); -export const isTriggerMessageHandlerEvent = tg.isUnion(isTriggerMessageEventObject, isTriggerMessageRemoveEventObject); +export const isTriggerMessageHandlerEvent = z.union([isTriggerMessageEventObject, isTriggerMessageRemoveEventObject]); diff --git a/front/src/Api/IframeListener.ts b/front/src/Api/IframeListener.ts index e3609b9f..56da2fe6 100644 --- a/front/src/Api/IframeListener.ts +++ b/front/src/Api/IframeListener.ts @@ -1,13 +1,11 @@ import { Subject } from "rxjs"; -import { isChatEvent } from "./Events/ChatEvent"; import { HtmlUtils } from "../WebRtc/HtmlUtils"; import type { EnterLeaveEvent } from "./Events/EnterLeaveEvent"; -import { isOpenPopupEvent, OpenPopupEvent } from "./Events/OpenPopupEvent"; -import { isOpenTabEvent, OpenTabEvent } from "./Events/OpenTabEvent"; +import { OpenPopupEvent } from "./Events/OpenPopupEvent"; +import { OpenTabEvent } from "./Events/OpenTabEvent"; import type { ButtonClickedEvent } from "./Events/ButtonClickedEvent"; -import { ClosePopupEvent, isClosePopupEvent } from "./Events/ClosePopupEvent"; +import { ClosePopupEvent } from "./Events/ClosePopupEvent"; import { scriptUtils } from "./ScriptUtils"; -import { isGoToPageEvent } from "./Events/GoToPageEvent"; import { IframeErrorAnswerEvent, IframeQueryMap, @@ -15,35 +13,28 @@ import { IframeResponseEventMap, isIframeEventWrapper, isIframeQueryWrapper, + isLookingLikeIframeEventWrapper, } from "./Events/IframeEvent"; import type { UserInputChatEvent } from "./Events/UserInputChatEvent"; -import { isPlaySoundEvent, PlaySoundEvent } from "./Events/PlaySoundEvent"; -import { isStopSoundEvent, StopSoundEvent } from "./Events/StopSoundEvent"; -import { isLoadSoundEvent, LoadSoundEvent } from "./Events/LoadSoundEvent"; -import { isSetPropertyEvent, SetPropertyEvent } from "./Events/setPropertyEvent"; -import { isLayerEvent, LayerEvent } from "./Events/LayerEvent"; +import { PlaySoundEvent } from "./Events/PlaySoundEvent"; +import { StopSoundEvent } from "./Events/StopSoundEvent"; +import { LoadSoundEvent } from "./Events/LoadSoundEvent"; +import { SetPropertyEvent } from "./Events/setPropertyEvent"; +import { LayerEvent } from "./Events/LayerEvent"; import type { HasPlayerMovedEvent } from "./Events/HasPlayerMovedEvent"; -import { isLoadPageEvent } from "./Events/LoadPageEvent"; -import { isMenuRegisterEvent, isUnregisterMenuEvent } from "./Events/ui/MenuRegisterEvent"; -import { SetTilesEvent, isSetTilesEvent } from "./Events/SetTilesEvent"; +import { SetTilesEvent } from "./Events/SetTilesEvent"; import type { SetVariableEvent } from "./Events/SetVariableEvent"; -import { ModifyEmbeddedWebsiteEvent, isEmbeddedWebsiteEvent } from "./Events/EmbeddedWebsiteEvent"; +import { ModifyEmbeddedWebsiteEvent } 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"; -import { CameraSetEvent, isCameraSetEvent } from "./Events/CameraSetEvent"; -import { CameraFollowPlayerEvent, isCameraFollowPlayerEvent } from "./Events/CameraFollowPlayerEvent"; +import { CameraSetEvent } from "./Events/CameraSetEvent"; +import { CameraFollowPlayerEvent } from "./Events/CameraFollowPlayerEvent"; import type { RemotePlayerClickedEvent } from "./Events/RemotePlayerClickedEvent"; -import { - AddActionsMenuKeyToRemotePlayerEvent, - isAddActionsMenuKeyToRemotePlayerEvent, -} from "./Events/AddActionsMenuKeyToRemotePlayerEvent"; +import { AddActionsMenuKeyToRemotePlayerEvent } from "./Events/AddActionsMenuKeyToRemotePlayerEvent"; import type { ActionsMenuActionClickedEvent } from "./Events/ActionsMenuActionClickedEvent"; -import { - isRemoveActionsMenuKeyFromRemotePlayerEvent, - RemoveActionsMenuKeyFromRemotePlayerEvent, -} from "./Events/RemoveActionsMenuKeyFromRemotePlayerEvent"; +import { RemoveActionsMenuKeyFromRemotePlayerEvent } from "./Events/RemoveActionsMenuKeyFromRemotePlayerEvent"; type AnswererCallback = ( query: IframeQueryMap[T]["query"], @@ -150,8 +141,10 @@ class IframeListener { const payload = message.data; + const lookingLikeEvent = isLookingLikeIframeEventWrapper.safeParse(payload); + if (foundSrc === undefined || iframe === undefined) { - if (isIframeEventWrapper(payload)) { + if (lookingLikeEvent.success) { console.warn( "It seems an iFrame is trying to communicate with WorkAdventure but was not explicitly granted the permission to do so. " + "If you are looking to use the WorkAdventure Scripting API inside an iFrame, you should allow the " + @@ -221,63 +214,70 @@ class IframeListener { } catch (reason) { errorHandler(reason); } - } else if (isIframeEventWrapper(payload)) { - if (payload.type === "showLayer" && isLayerEvent(payload.data)) { - this._showLayerStream.next(payload.data); - } else if (payload.type === "hideLayer" && isLayerEvent(payload.data)) { - this._hideLayerStream.next(payload.data); - } else if (payload.type === "setProperty" && isSetPropertyEvent(payload.data)) { - this._setPropertyStream.next(payload.data); - } else if (payload.type === "cameraSet" && isCameraSetEvent(payload.data)) { - this._cameraSetStream.next(payload.data); - } else if (payload.type === "cameraFollowPlayer" && isCameraFollowPlayerEvent(payload.data)) { - this._cameraFollowPlayerStream.next(payload.data); - } else if (payload.type === "chat" && isChatEvent(payload.data)) { - scriptUtils.sendAnonymousChat(payload.data); - } else if (payload.type === "openPopup" && isOpenPopupEvent(payload.data)) { - this._openPopupStream.next(payload.data); - } else if (payload.type === "closePopup" && isClosePopupEvent(payload.data)) { - this._closePopupStream.next(payload.data); - } else if (payload.type === "openTab" && isOpenTabEvent(payload.data)) { - scriptUtils.openTab(payload.data.url); - } else if (payload.type === "goToPage" && isGoToPageEvent(payload.data)) { - scriptUtils.goToPage(payload.data.url); - } else if (payload.type === "loadPage" && isLoadPageEvent(payload.data)) { - this._loadPageStream.next(payload.data.url); - } else if (payload.type === "playSound" && isPlaySoundEvent(payload.data)) { - this._playSoundStream.next(payload.data); - } else if (payload.type === "stopSound" && isStopSoundEvent(payload.data)) { - this._stopSoundStream.next(payload.data); - } else if (payload.type === "loadSound" && isLoadSoundEvent(payload.data)) { - this._loadSoundStream.next(payload.data); - } else if (payload.type === "disablePlayerControls") { + } else if (lookingLikeEvent.success) { + const iframeEventGuarded = isIframeEventWrapper.safeParse(lookingLikeEvent.data); + + if (!iframeEventGuarded.success) { + console.error( + `Invalid event "${lookingLikeEvent.data.type}" received from Iframe: `, + lookingLikeEvent.data, + iframeEventGuarded.error.issues + ); + return; + } + + const iframeEvent = iframeEventGuarded.data; + + if (iframeEvent.type === "showLayer") { + this._showLayerStream.next(iframeEvent.data); + } else if (iframeEvent.type === "hideLayer") { + this._hideLayerStream.next(iframeEvent.data); + } else if (iframeEvent.type === "setProperty") { + this._setPropertyStream.next(iframeEvent.data); + } else if (iframeEvent.type === "cameraSet") { + this._cameraSetStream.next(iframeEvent.data); + } else if (iframeEvent.type === "cameraFollowPlayer") { + this._cameraFollowPlayerStream.next(iframeEvent.data); + } else if (iframeEvent.type === "chat") { + scriptUtils.sendAnonymousChat(iframeEvent.data); + } else if (iframeEvent.type === "openPopup") { + this._openPopupStream.next(iframeEvent.data); + } else if (iframeEvent.type === "closePopup") { + this._closePopupStream.next(iframeEvent.data); + } else if (iframeEvent.type === "openTab") { + scriptUtils.openTab(iframeEvent.data.url); + } else if (iframeEvent.type === "goToPage") { + scriptUtils.goToPage(iframeEvent.data.url); + } else if (iframeEvent.type === "loadPage") { + this._loadPageStream.next(iframeEvent.data.url); + } else if (iframeEvent.type === "playSound") { + this._playSoundStream.next(iframeEvent.data); + } else if (iframeEvent.type === "stopSound") { + this._stopSoundStream.next(iframeEvent.data); + } else if (iframeEvent.type === "loadSound") { + this._loadSoundStream.next(iframeEvent.data); + } else if (iframeEvent.type === "disablePlayerControls") { this._disablePlayerControlStream.next(); - } else if (payload.type === "restorePlayerControls") { + } else if (iframeEvent.type === "restorePlayerControls") { this._enablePlayerControlStream.next(); - } else if (payload.type === "displayBubble") { + } else if (iframeEvent.type === "displayBubble") { this._displayBubbleStream.next(); - } else if (payload.type === "removeBubble") { + } else if (iframeEvent.type === "removeBubble") { this._removeBubbleStream.next(); - } else if (payload.type == "onPlayerMove") { + } else if (iframeEvent.type == "onPlayerMove") { this.sendPlayerMove = true; - } else if ( - payload.type == "addActionsMenuKeyToRemotePlayer" && - isAddActionsMenuKeyToRemotePlayerEvent(payload.data) - ) { - this._addActionsMenuKeyToRemotePlayerStream.next(payload.data); - } else if ( - payload.type == "removeActionsMenuKeyFromRemotePlayer" && - isRemoveActionsMenuKeyFromRemotePlayerEvent(payload.data) - ) { - this._removeActionsMenuKeyFromRemotePlayerEvent.next(payload.data); - } else if (payload.type == "onCameraUpdate") { + } else if (iframeEvent.type == "addActionsMenuKeyToRemotePlayer") { + this._addActionsMenuKeyToRemotePlayerStream.next(iframeEvent.data); + } else if (iframeEvent.type == "removeActionsMenuKeyFromRemotePlayer") { + this._removeActionsMenuKeyFromRemotePlayerEvent.next(iframeEvent.data); + } else if (iframeEvent.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)) { - this._modifyEmbeddedWebsiteStream.next(payload.data); - } else if (payload.type == "registerMenu" && isMenuRegisterEvent(payload.data)) { - const dataName = payload.data.name; + } else if (iframeEvent.type == "setTiles") { + this._setTilesStream.next(iframeEvent.data); + } else if (iframeEvent.type == "modifyEmbeddedWebsite") { + this._modifyEmbeddedWebsiteStream.next(iframeEvent.data); + } else if (iframeEvent.type == "registerMenu") { + const dataName = iframeEvent.data.name; this.iframeCloseCallbacks.get(iframe)?.push(() => { handleMenuUnregisterEvent(dataName); }); @@ -285,13 +285,17 @@ class IframeListener { foundSrc = this.getBaseUrl(foundSrc, message.source); handleMenuRegistrationEvent( - payload.data.name, - payload.data.iframe, + iframeEvent.data.name, + iframeEvent.data.iframe, foundSrc, - payload.data.options + iframeEvent.data.options ); - } else if (payload.type == "unregisterMenu" && isUnregisterMenuEvent(payload.data)) { - handleMenuUnregisterEvent(payload.data.name); + } else if (iframeEvent.type == "unregisterMenu") { + handleMenuUnregisterEvent(iframeEvent.data.name); + } else { + // Keep the line below. It will throw an error if we forget to handle one of the possible values. + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const _exhaustiveCheck: never = iframeEvent; } } }, @@ -315,7 +319,7 @@ class IframeListener { } registerScript(scriptUrl: string, enableModuleMode: boolean = true): Promise { - return new Promise((resolve, reject) => { + return new Promise((resolve) => { console.info("Loading map related script at ", scriptUrl); const iframe = document.createElement("iframe"); diff --git a/front/src/Api/iframe/IframeApiContribution.ts b/front/src/Api/iframe/IframeApiContribution.ts index 96548d5e..4e0ce71a 100644 --- a/front/src/Api/iframe/IframeApiContribution.ts +++ b/front/src/Api/iframe/IframeApiContribution.ts @@ -1,14 +1,8 @@ -import type * as tg from "generic-type-guard"; -import type { - IframeEvent, - IframeEventMap, - IframeQuery, - IframeQueryMap, - IframeResponseEventMap, -} from "../Events/IframeEvent"; +import { z } from "zod"; +import type { IframeEvent, IframeQuery, IframeQueryMap, IframeResponseEventMap } from "../Events/IframeEvent"; import type { IframeQueryWrapper } from "../Events/IframeEvent"; -export function sendToWorkadventure(content: IframeEvent) { +export function sendToWorkadventure(content: IframeEvent) { window.parent.postMessage(content, "*"); } @@ -48,12 +42,10 @@ export function queryWorkadventure( }); } -type GuardedType> = Guard extends tg.TypeGuard ? T : never; - export interface IframeCallback< Key extends keyof IframeResponseEventMap, T = IframeResponseEventMap[Key], - Guard = tg.TypeGuard + Guard = z.ZodType > { typeChecker: Guard; callback: (payloadData: T) => void; diff --git a/front/src/Api/iframe/Room/EmbeddedWebsite.ts b/front/src/Api/iframe/Room/EmbeddedWebsite.ts index d9c2d986..3f583194 100644 --- a/front/src/Api/iframe/Room/EmbeddedWebsite.ts +++ b/front/src/Api/iframe/Room/EmbeddedWebsite.ts @@ -1,9 +1,5 @@ import { sendToWorkadventure } from "../IframeApiContribution"; -import type { - CreateEmbeddedWebsiteEvent, - ModifyEmbeddedWebsiteEvent, - Rectangle, -} from "../../Events/EmbeddedWebsiteEvent"; +import type { CreateEmbeddedWebsiteEvent, Rectangle } from "../../Events/EmbeddedWebsiteEvent"; export class EmbeddedWebsite { public readonly name: string; diff --git a/front/src/Api/iframe/camera.ts b/front/src/Api/iframe/camera.ts index 38199e0d..c2d856f9 100644 --- a/front/src/Api/iframe/camera.ts +++ b/front/src/Api/iframe/camera.ts @@ -41,7 +41,7 @@ export class WorkAdventureCameraCommands extends IframeApiContribution { sendToWorkadventure({ type: "onCameraUpdate", - data: null, + data: undefined, }); return moveStream; } diff --git a/front/src/Api/iframe/controls.ts b/front/src/Api/iframe/controls.ts index a5f4c458..9fb53640 100644 --- a/front/src/Api/iframe/controls.ts +++ b/front/src/Api/iframe/controls.ts @@ -4,11 +4,11 @@ export class WorkadventureControlsCommands extends IframeApiContribution { diff --git a/front/src/Api/iframe/state.ts b/front/src/Api/iframe/state.ts index 278b208e..c6664926 100644 --- a/front/src/Api/iframe/state.ts +++ b/front/src/Api/iframe/state.ts @@ -1,13 +1,9 @@ import { Observable, Subject } from "rxjs"; -import { EnterLeaveEvent, isEnterLeaveEvent } from "../Events/EnterLeaveEvent"; - -import { IframeApiContribution, queryWorkadventure, sendToWorkadventure } from "./IframeApiContribution"; +import { IframeApiContribution, queryWorkadventure } from "./IframeApiContribution"; import { apiCallback } from "./registeredCallbacks"; import { isSetVariableEvent, SetVariableEvent } from "../Events/SetVariableEvent"; -import type { ITiledMap } from "../../Phaser/Map/ITiledMap"; - export class WorkadventureStateCommands extends IframeApiContribution { private setVariableResolvers = new Subject(); private variables = new Map(); @@ -17,7 +13,7 @@ export class WorkadventureStateCommands extends IframeApiContribution { - const oldValue = this.variables.get(event.key); + // 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)) { @@ -92,6 +88,7 @@ export function createState(target: "global" | "player"): WorkadventureStateComm } return target.loadVariable(p.toString()); }, + // eslint-disable-next-line @typescript-eslint/no-unused-vars 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. diff --git a/front/src/Api/iframe/ui.ts b/front/src/Api/iframe/ui.ts index 9b109654..15f0756c 100644 --- a/front/src/Api/iframe/ui.ts +++ b/front/src/Api/iframe/ui.ts @@ -34,14 +34,6 @@ interface MenuDescriptor { export type MenuOptions = RequireOnlyOne; -interface ZonedPopupOptions { - zone: string; - objectLayerName?: string; - popupText: string; - delay?: number; - popupOptions: Array; -} - export interface ActionMessageOptions { message: string; type?: "message" | "warning"; @@ -277,11 +269,11 @@ export class WorkAdventureUiCommands extends IframeApiContribution { return res; }); -const interceptorId = rax.attach(axiosWithRetry); +rax.attach(axiosWithRetry); diff --git a/front/src/Connexion/ConnectionManager.ts b/front/src/Connexion/ConnectionManager.ts index c0c9597c..3723c099 100644 --- a/front/src/Connexion/ConnectionManager.ts +++ b/front/src/Connexion/ConnectionManager.ts @@ -4,7 +4,7 @@ import { RoomConnection } from "./RoomConnection"; import type { OnConnectInterface, PositionInterface, ViewportInterface } from "./ConnexionModels"; import { GameConnexionTypes, urlManager } from "../Url/UrlManager"; import { localUserStore } from "./LocalUserStore"; -import { CharacterTexture, LocalUser } from "./LocalUser"; +import { LocalUser } from "./LocalUser"; import { Room } from "./Room"; import { _ServiceWorker } from "../Network/ServiceWorker"; import { loginSceneVisibleIframeStore } from "../Stores/LoginSceneStore"; @@ -13,7 +13,6 @@ import { analyticsClient } from "../Administration/AnalyticsClient"; import { axiosWithRetry } from "./AxiosUtils"; import axios from "axios"; import { isRegisterData } from "../Messages/JsonMessages/RegisterData"; -import { isAdminApiData } from "../Messages/JsonMessages/AdminApiData"; import { limitMapStore } from "../Stores/GameStore"; import { showLimitRoomModalStore } from "../Stores/ModalStore"; import { gameManager } from "../Phaser/Game/GameManager"; @@ -73,9 +72,7 @@ class ConnectionManager { //Logout user in pusher and hydra const token = localUserStore.getAuthToken(); - const { authToken } = await Axios.get(`${PUSHER_URL}/logout-callback`, { params: { token } }).then( - (res) => res.data - ); + await Axios.get(`${PUSHER_URL}/logout-callback`, { params: { token } }).then((res) => res.data); localUserStore.setAuthToken(null); //Go on login page can permit to clear token and start authentication process @@ -139,13 +136,19 @@ class ConnectionManager { //@deprecated else if (this.connexionType === GameConnexionTypes.register) { const organizationMemberToken = urlManager.getOrganizationToken(); - const data = await Axios.post(`${PUSHER_URL}/register`, { organizationMemberToken }).then( + const result = await Axios.post(`${PUSHER_URL}/register`, { organizationMemberToken }).then( (res) => res.data ); - if (!isRegisterData(data)) { - console.error("Invalid data received from /register route. Data: ", data); + + const registerDataChecking = isRegisterData.safeParse(result); + + if (!registerDataChecking.success) { + console.error("Invalid data received from /register route. Data: ", result); throw new Error("Invalid data received from /register route."); } + + const data = registerDataChecking.data; + this.localUser = new LocalUser(data.userUuid, data.email); this.authToken = data.authToken; localUserStore.saveUser(this.localUser); @@ -306,9 +309,9 @@ class ConnectionManager { connection.roomJoinedMessageStream.subscribe((connect: OnConnectInterface) => { resolve(connect); }); - }).catch((err) => { + }).catch(() => { // Let's retry in 4-6 seconds - return new Promise((resolve, reject) => { + return new Promise((resolve) => { this.reconnectingTimeout = setTimeout(() => { //todo: allow a way to break recursion? //todo: find a way to avoid recursive function. Otherwise, the call stack will grow indefinitely. diff --git a/front/src/Connexion/ConnexionModels.ts b/front/src/Connexion/ConnexionModels.ts index 0e32c9ed..1231373f 100644 --- a/front/src/Connexion/ConnexionModels.ts +++ b/front/src/Connexion/ConnexionModels.ts @@ -5,7 +5,7 @@ import type { BodyResourceDescriptionInterface } from "../Phaser/Entity/PlayerTe export interface PointInterface { x: number; y: number; - direction: string; // TODO: modify this to the enum from ts-proto + direction: "up" | "down" | "left" | "right"; // TODO: modify this to the enum from ts-proto moving: boolean; } diff --git a/front/src/Connexion/Room.ts b/front/src/Connexion/Room.ts index bf95e48d..a76e3f31 100644 --- a/front/src/Connexion/Room.ts +++ b/front/src/Connexion/Room.ts @@ -1,7 +1,4 @@ -import * as rax from "retry-axios"; -import Axios from "axios"; import { CONTACT_URL, PUSHER_URL, DISABLE_ANONYMOUS, OPID_LOGIN_SCREEN_PROVIDER } from "../Enum/EnvironmentVariable"; -import type { CharacterTexture } from "./LocalUser"; import { localUserStore } from "./LocalUserStore"; import axios from "axios"; import { axiosWithRetry } from "./AxiosUtils"; @@ -112,11 +109,14 @@ export class Room { data.authenticationMandatory = Boolean(data.authenticationMandatory); } - if (isRoomRedirect(data)) { + const roomRedirectChecking = isRoomRedirect.safeParse(data); + const mapDetailsDataChecking = isMapDetailsData.safeParse(data); + + if (roomRedirectChecking.success) { return { redirectUrl: data.redirectUrl, }; - } else if (isMapDetailsData(data)) { + } else if (mapDetailsDataChecking.success) { console.log("Map ", this.id, " resolves to URL ", data.mapUrl); this._mapUrl = data.mapUrl; this._group = data.group; @@ -132,6 +132,9 @@ export class Room { this._loginSceneLogo = data.loginSceneLogo ?? undefined; return new MapDetail(data.mapUrl); } else { + console.log(data); + console.error("roomRedirectChecking", roomRedirectChecking.error.issues); + console.error("mapDetailsDataChecking", mapDetailsDataChecking.error.issues); throw new Error("Data received by the /map endpoint of the Pusher is not in a valid format."); } } catch (e) { diff --git a/front/src/Connexion/RoomConnection.ts b/front/src/Connexion/RoomConnection.ts index 04b07cf3..5ec1e395 100644 --- a/front/src/Connexion/RoomConnection.ts +++ b/front/src/Connexion/RoomConnection.ts @@ -196,7 +196,7 @@ export class RoomConnection implements RoomConnection { let interval: ReturnType | undefined = undefined; - this.socket.onopen = (ev) => { + this.socket.onopen = () => { //we manually ping every 20s to not be logged out by the server, even when the game is in background. const pingMessage = PingMessageTsProto.encode({}).finish(); interval = setInterval(() => this.socket.send(pingMessage), manualPingDelay); @@ -303,6 +303,7 @@ export class RoomConnection implements RoomConnection { } default: { // Security check: if we forget a "case", the line below will catch the error at compile-time. + // eslint-disable-next-line @typescript-eslint/no-unused-vars const tmp: never = subMessage; } } @@ -490,6 +491,7 @@ export class RoomConnection implements RoomConnection { } default: { // Security check: if we forget a "case", the line below will catch the error at compile-time. + // eslint-disable-next-line @typescript-eslint/no-unused-vars const tmp: never = message; } } diff --git a/front/src/Network/ProtobufClientUtils.ts b/front/src/Network/ProtobufClientUtils.ts index beec3d9f..09d85d49 100644 --- a/front/src/Network/ProtobufClientUtils.ts +++ b/front/src/Network/ProtobufClientUtils.ts @@ -4,7 +4,7 @@ import type { PointInterface } from "../Connexion/ConnexionModels"; export class ProtobufClientUtils { public static toPointInterface(position: PositionMessage): PointInterface { - let direction: string; + let direction: "up" | "down" | "left" | "right"; switch (position.direction) { case PositionMessage_Direction.UP: direction = "up"; diff --git a/front/src/Phaser/Components/Loader.ts b/front/src/Phaser/Components/Loader.ts index 1048f512..9e8751d7 100644 --- a/front/src/Phaser/Components/Loader.ts +++ b/front/src/Phaser/Components/Loader.ts @@ -1,9 +1,7 @@ -import ImageFrameConfig = Phaser.Types.Loader.FileTypes.ImageFrameConfig; import { DirtyScene } from "../Game/DirtyScene"; import { gameManager } from "../Game/GameManager"; import { SuperLoaderPlugin } from "../Services/SuperLoaderPlugin"; import CancelablePromise from "cancelable-promise"; -import Image = Phaser.GameObjects.Image; import Texture = Phaser.Textures.Texture; const TextName: string = "Loading..."; @@ -35,8 +33,6 @@ export class Loader { const logoResource = gameManager.currentStartedRoom.loadingLogo ?? "static/images/logo.png"; this.logoNameIndex = "logoLoading" + logoResource; - const loadingBarWidth: number = Math.floor(this.scene.game.renderer.width / 3); - //add loading if logo image until logo image is ready this.loadingText = this.scene.add.text( this.scene.game.renderer.width / 2, diff --git a/front/src/Phaser/Components/TextUtils.ts b/front/src/Phaser/Components/TextUtils.ts index b8f1bd69..f9ec4847 100644 --- a/front/src/Phaser/Components/TextUtils.ts +++ b/front/src/Phaser/Components/TextUtils.ts @@ -1,6 +1,5 @@ import type { ITiledMapObject } from "../Map/ITiledMap"; import type { GameScene } from "../Game/GameScene"; -import { type } from "os"; import { GameMapProperties } from "../Game/GameMapProperties"; export class TextUtils { diff --git a/front/src/Phaser/Entity/Character.ts b/front/src/Phaser/Entity/Character.ts index a4066cf2..681efd29 100644 --- a/front/src/Phaser/Entity/Character.ts +++ b/front/src/Phaser/Entity/Character.ts @@ -1,9 +1,4 @@ -import { - AnimationData, - getPlayerAnimations, - PlayerAnimationDirections, - PlayerAnimationTypes, -} from "../Player/Animation"; +import { getPlayerAnimations, PlayerAnimationDirections, PlayerAnimationTypes } from "../Player/Animation"; import { SpeechBubble } from "./SpeechBubble"; import Text = Phaser.GameObjects.Text; import Container = Phaser.GameObjects.Container; diff --git a/front/src/Phaser/Entity/PlayerTexturesLoadingManager.ts b/front/src/Phaser/Entity/PlayerTexturesLoadingManager.ts index 550b5545..18e904c6 100644 --- a/front/src/Phaser/Entity/PlayerTexturesLoadingManager.ts +++ b/front/src/Phaser/Entity/PlayerTexturesLoadingManager.ts @@ -1,6 +1,5 @@ import LoaderPlugin = Phaser.Loader.LoaderPlugin; -import type { CharacterTexture } from "../../Connexion/LocalUser"; -import { BodyResourceDescriptionInterface, mapLayerToLevel, PlayerTextures, PlayerTexturesKey } from "./PlayerTextures"; +import { BodyResourceDescriptionInterface, PlayerTextures, PlayerTexturesKey } from "./PlayerTextures"; import CancelablePromise from "cancelable-promise"; import { SuperLoaderPlugin } from "../Services/SuperLoaderPlugin"; import Texture = Phaser.Textures.Texture; diff --git a/front/src/Phaser/Game/CameraManager.ts b/front/src/Phaser/Game/CameraManager.ts index 3c4f6035..0049890a 100644 --- a/front/src/Phaser/Game/CameraManager.ts +++ b/front/src/Phaser/Game/CameraManager.ts @@ -97,6 +97,7 @@ export class CameraManager extends Phaser.Events.EventEmitter { }); return; } + // eslint-disable-next-line @typescript-eslint/no-unused-vars this.camera.pan(setTo.x, setTo.y, duration, Easing.SineEaseOut, true, (camera, progress, x, y) => { if (this.cameraMode === CameraMode.Positioned) { this.waScaleManager.zoomModifier = currentZoomModifier + progress * zoomModifierChange; @@ -138,6 +139,7 @@ export class CameraManager extends Phaser.Events.EventEmitter { this.emit(CameraManagerEvent.CameraUpdate, this.getCameraUpdateEventData()); return; } + // eslint-disable-next-line @typescript-eslint/no-unused-vars this.camera.pan(focusOn.x, focusOn.y, duration, Easing.SineEaseOut, true, (camera, progress, x, y) => { this.waScaleManager.zoomModifier = currentZoomModifier + progress * zoomModifierChange; if (progress === 1) { diff --git a/front/src/Phaser/Game/EmbeddedWebsiteManager.ts b/front/src/Phaser/Game/EmbeddedWebsiteManager.ts index 387940c7..41864a92 100644 --- a/front/src/Phaser/Game/EmbeddedWebsiteManager.ts +++ b/front/src/Phaser/Game/EmbeddedWebsiteManager.ts @@ -223,7 +223,7 @@ height,*/ } close(): void { - for (const [key, website] of this.embeddedWebsites) { + for (const website of this.embeddedWebsites.values()) { if (website.allowApi) { iframeListener.unregisterIframe(website.iframe); } diff --git a/front/src/Phaser/Game/Game.ts b/front/src/Phaser/Game/Game.ts index 783f2348..6b9352d8 100644 --- a/front/src/Phaser/Game/Game.ts +++ b/front/src/Phaser/Game/Game.ts @@ -1,6 +1,4 @@ import { SKIP_RENDER_OPTIMIZATIONS } from "../../Enum/EnvironmentVariable"; -import { coWebsiteManager } from "../../WebRtc/CoWebsiteManager"; -import { waScaleManager } from "../Services/WaScaleManager"; import { ResizableScene } from "../Login/ResizableScene"; const Events = Phaser.Core.Events; diff --git a/front/src/Phaser/Game/GameMapPropertiesListener.ts b/front/src/Phaser/Game/GameMapPropertiesListener.ts index 103cc4bc..1dffd434 100644 --- a/front/src/Phaser/Game/GameMapPropertiesListener.ts +++ b/front/src/Phaser/Game/GameMapPropertiesListener.ts @@ -112,7 +112,7 @@ export class GameMapPropertiesListener { } }); - this.gameMap.onPropertyChange(GameMapProperties.EXIT_SCENE_URL, (newValue, oldValue) => { + this.gameMap.onPropertyChange(GameMapProperties.EXIT_SCENE_URL, (newValue) => { if (newValue) { this.scene .onMapExit( @@ -130,7 +130,7 @@ export class GameMapPropertiesListener { } }); - this.gameMap.onPropertyChange(GameMapProperties.EXIT_URL, (newValue, oldValue) => { + this.gameMap.onPropertyChange(GameMapProperties.EXIT_URL, (newValue) => { if (newValue) { this.scene .onMapExit(Room.getRoomPathFromExitUrl(newValue as string, window.location.toString())) @@ -142,7 +142,7 @@ export class GameMapPropertiesListener { } }); - this.gameMap.onPropertyChange(GameMapProperties.SILENT, (newValue, oldValue) => { + this.gameMap.onPropertyChange(GameMapProperties.SILENT, (newValue) => { if (newValue === undefined || newValue === false || newValue === "") { this.scene.connection?.setSilent(false); this.scene.CurrentPlayer.noSilent(); @@ -162,7 +162,7 @@ export class GameMapPropertiesListener { }); // TODO: This legacy property should be removed at some point - this.gameMap.onPropertyChange(GameMapProperties.PLAY_AUDIO_LOOP, (newValue, oldValue) => { + this.gameMap.onPropertyChange(GameMapProperties.PLAY_AUDIO_LOOP, (newValue) => { newValue === undefined ? audioManagerFileStore.unloadAudio() : audioManagerFileStore.playAudio(newValue, this.scene.getMapDirUrl(), undefined, true); diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index c6336e8b..ab8f572c 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -97,7 +97,6 @@ import { startLayerNamesStore } from "../../Stores/StartLayerNamesStore"; import { JitsiCoWebsite } from "../../WebRtc/CoWebsite/JitsiCoWebsite"; import { SimpleCoWebsite } from "../../WebRtc/CoWebsite/SimpleCoWebsite"; import type { CoWebsite } from "../../WebRtc/CoWebsite/CoWesbite"; -import type { VideoPeer } from "../../WebRtc/VideoPeer"; import CancelablePromise from "cancelable-promise"; import { Deferred } from "ts-deferred"; import { SuperLoaderPlugin } from "../Services/SuperLoaderPlugin"; @@ -178,6 +177,7 @@ export class GameScene extends DirtyScene { private localVolumeStoreUnsubscriber: Unsubscriber | undefined; private followUsersColorStoreUnsubscribe!: Unsubscriber; + private currentPlayerGroupIdStoreUnsubscribe!: Unsubscriber; private privacyShutdownStoreUnsubscribe!: Unsubscriber; private userIsJitsiDominantSpeakerStoreUnsubscriber!: Unsubscriber; private jitsiParticipantsCountStoreUnsubscriber!: Unsubscriber; @@ -190,7 +190,7 @@ export class GameScene extends DirtyScene { currentTick!: number; lastSentTick!: number; // The last tick at which a position was sent. lastMoveEventSent: HasPlayerMovedEvent = { - direction: "", + direction: "down", moving: false, x: -1000, y: -1000, @@ -222,8 +222,8 @@ export class GameScene extends DirtyScene { private loader: Loader; private lastCameraEvent: WasCameraUpdatedEvent | undefined; private firstCameraUpdateSent: boolean = false; - private showVoiceIndicatorChangeMessageSent: boolean = false; private currentPlayerGroupId?: number; + private showVoiceIndicatorChangeMessageSent: boolean = false; private jitsiDominantSpeaker: boolean = false; private jitsiParticipantsCount: number = 0; public readonly superLoad: SuperLoaderPlugin; @@ -843,6 +843,10 @@ export class GameScene extends DirtyScene { this.currentPlayerGroupId = message.groupId; }); + this.connection.groupUsersUpdateMessageStream.subscribe((message) => { + this.currentPlayerGroupId = message.groupId; + }); + /** * Triggered when we receive the JWT token to connect to Jitsi */ @@ -1046,6 +1050,7 @@ ${escapedMessage} }, 100); id = 0; + // eslint-disable-next-line @typescript-eslint/no-unused-vars for (const button of openPopupEvent.buttons) { const button = HtmlUtils.getElementByIdOrFail( `popup-${openPopupEvent.popupId}-${id}` @@ -1304,7 +1309,7 @@ ${escapedMessage} await this.connectionAnswerPromiseDeferred.promise; return { mapUrl: this.MapUrlFile, - startLayerName: this.startPositionCalculator.startLayerName, + startLayerName: this.startPositionCalculator.startLayerName ?? undefined, uuid: localUserStore.getLocalUser()?.uuid, nickname: this.playerName, language: get(locale), @@ -1413,6 +1418,7 @@ ${escapedMessage} break; } default: { + // eslint-disable-next-line @typescript-eslint/no-unused-vars const _exhaustiveCheck: never = event.target; } } @@ -1432,7 +1438,7 @@ ${escapedMessage} this.connection?.emitPlayerOutlineColor(color); }); - iframeListener.registerAnswerer("removePlayerOutline", (message) => { + iframeListener.registerAnswerer("removePlayerOutline", () => { this.CurrentPlayer.removeApiOutlineColor(); this.connection?.emitPlayerOutlineColor(null); }); @@ -1715,6 +1721,7 @@ ${escapedMessage} private createCollisionWithPlayer() { //add collision layer for (const phaserLayer of this.gameMap.phaserLayers) { + // eslint-disable-next-line @typescript-eslint/no-unused-vars this.physics.add.collider(this.CurrentPlayer, phaserLayer, (object1: GameObject, object2: GameObject) => { //this.CurrentPlayer.say("Collision with layer : "+ (object2 as Tile).layer.name) }); @@ -1765,9 +1772,11 @@ ${escapedMessage} emoteMenuStore.openEmoteMenu(); } }); + // eslint-disable-next-line @typescript-eslint/no-unused-vars this.CurrentPlayer.on(Phaser.Input.Events.POINTER_OVER, (pointer: Phaser.Input.Pointer) => { this.CurrentPlayer.pointerOverOutline(0x365dff); }); + // eslint-disable-next-line @typescript-eslint/no-unused-vars this.CurrentPlayer.on(Phaser.Input.Events.POINTER_OUT, (pointer: Phaser.Input.Pointer) => { this.CurrentPlayer.pointerOutOutline(); }); @@ -1869,6 +1878,7 @@ ${escapedMessage} break; } default: { + // eslint-disable-next-line @typescript-eslint/no-unused-vars const tmp: never = event; } } @@ -2030,8 +2040,6 @@ ${escapedMessage} this.currentTick, { ...message.position, - oldX: undefined, - oldY: undefined, }, this.currentTick + POSITION_DELAY ); diff --git a/front/src/Phaser/Items/Computer/computer.ts b/front/src/Phaser/Items/Computer/computer.ts index c198d7e1..32738730 100644 --- a/front/src/Phaser/Items/Computer/computer.ts +++ b/front/src/Phaser/Items/Computer/computer.ts @@ -4,14 +4,13 @@ import type { ITiledMapObject } from "../../Map/ITiledMap"; import type { ItemFactoryInterface } from "../ItemFactoryInterface"; import type { GameScene } from "../../Game/GameScene"; import { ActionableItem } from "../ActionableItem"; -import * as tg from "generic-type-guard"; +import { z } from "zod"; -const isComputerState = new tg.IsInterface() - .withProperties({ - status: tg.isString, - }) - .get(); -type ComputerState = tg.GuardedType; +export const isComputerState = z.object({ + status: z.string(), +}); + +export type ComputerState = z.infer; let state: ComputerState = { status: "off", @@ -55,10 +54,14 @@ export default { }, factory: (scene: GameScene, object: ITiledMapObject, initState: unknown): ActionableItem => { if (initState !== undefined) { - if (!isComputerState(initState)) { - throw new Error("Invalid state received for computer object"); + try { + state = isComputerState.parse(initState); + } catch (err) { + if (err instanceof z.ZodError) { + console.error(err.issues); + } + throw new Error(`Invalid state received for computer object`); } - state = initState; } const computer = new Sprite(scene, object.x, object.y, "computer"); diff --git a/front/src/Phaser/Login/CustomizeScene.ts b/front/src/Phaser/Login/CustomizeScene.ts index 48b909e1..e03863c0 100644 --- a/front/src/Phaser/Login/CustomizeScene.ts +++ b/front/src/Phaser/Login/CustomizeScene.ts @@ -112,6 +112,7 @@ export class CustomizeScene extends AbstractCharacterScene { this.onResize(); } + // eslint-disable-next-line @typescript-eslint/no-unused-vars public update(time: number, dt: number): void { this.customWokaPreviewer.update(); } diff --git a/front/src/Phaser/Login/EmptyScene.ts b/front/src/Phaser/Login/EmptyScene.ts index 4511a160..9d0b0c6d 100644 --- a/front/src/Phaser/Login/EmptyScene.ts +++ b/front/src/Phaser/Login/EmptyScene.ts @@ -13,5 +13,6 @@ export class EmptyScene extends Scene { create() {} + // eslint-disable-next-line @typescript-eslint/no-unused-vars update(time: number, delta: number): void {} } diff --git a/front/src/Phaser/Login/EnableCameraScene.ts b/front/src/Phaser/Login/EnableCameraScene.ts index 0f29f441..6f576b88 100644 --- a/front/src/Phaser/Login/EnableCameraScene.ts +++ b/front/src/Phaser/Login/EnableCameraScene.ts @@ -1,7 +1,6 @@ import { gameManager } from "../Game/GameManager"; import { ResizableScene } from "./ResizableScene"; import { enableCameraSceneVisibilityStore } from "../../Stores/MediaStore"; -import { localUserStore } from "../../Connexion/LocalUserStore"; import { analyticsClient } from "../../Administration/AnalyticsClient"; export const EnableCameraSceneName = "EnableCameraScene"; @@ -25,6 +24,7 @@ export class EnableCameraScene extends ResizableScene { public onResize(): void {} + // eslint-disable-next-line @typescript-eslint/no-unused-vars update(time: number, delta: number): void {} public login(): void { diff --git a/front/src/Phaser/Login/LoginScene.ts b/front/src/Phaser/Login/LoginScene.ts index 14cee6a1..17bd2f04 100644 --- a/front/src/Phaser/Login/LoginScene.ts +++ b/front/src/Phaser/Login/LoginScene.ts @@ -49,6 +49,7 @@ export class LoginScene extends ResizableScene { loginSceneVisibleStore.set(false); } + // eslint-disable-next-line @typescript-eslint/no-unused-vars update(time: number, delta: number): void {} public onResize(): void {} diff --git a/front/src/Phaser/Reconnecting/ReconnectingScene.ts b/front/src/Phaser/Reconnecting/ReconnectingScene.ts index 9c103bf0..2ba6730f 100644 --- a/front/src/Phaser/Reconnecting/ReconnectingScene.ts +++ b/front/src/Phaser/Reconnecting/ReconnectingScene.ts @@ -1,6 +1,5 @@ import { TextField } from "../Components/TextField"; import Image = Phaser.GameObjects.Image; -import Sprite = Phaser.GameObjects.Sprite; import LL from "../../i18n/i18n-svelte"; import { get } from "svelte/store"; diff --git a/front/src/Phaser/Services/WaScaleManager.ts b/front/src/Phaser/Services/WaScaleManager.ts index 27e8f2ba..c024c048 100644 --- a/front/src/Phaser/Services/WaScaleManager.ts +++ b/front/src/Phaser/Services/WaScaleManager.ts @@ -86,7 +86,7 @@ export class WaScaleManager { const { width: gameWidth, height: gameHeight } = coWebsiteManager.getGameSize(); const devicePixelRatio = window.devicePixelRatio ?? 1; - const { game: gameSize, real: realSize } = this.hdpiManager.getOptimalGameSize({ + const { real: realSize } = this.hdpiManager.getOptimalGameSize({ width: gameWidth * devicePixelRatio, height: gameHeight * devicePixelRatio, }); diff --git a/front/src/Phaser/UserInput/GameSceneUserInputHandler.ts b/front/src/Phaser/UserInput/GameSceneUserInputHandler.ts index fc9e83cf..09f2258d 100644 --- a/front/src/Phaser/UserInput/GameSceneUserInputHandler.ts +++ b/front/src/Phaser/UserInput/GameSceneUserInputHandler.ts @@ -16,6 +16,7 @@ export class GameSceneUserInputHandler implements UserInputHandlerInterface { gameObjects: Phaser.GameObjects.GameObject[], deltaX: number, deltaY: number, + // eslint-disable-next-line @typescript-eslint/no-unused-vars deltaZ: number ): void { this.gameScene.zoomByFactor(1 - (deltaY / 53) * 0.1); @@ -43,13 +44,14 @@ export class GameSceneUserInputHandler implements UserInputHandlerInterface { .then((path) => { // Remove first step as it is for the tile we are currently standing on path.shift(); - this.gameScene.CurrentPlayer.setPathToFollow(path).catch((reason) => {}); + this.gameScene.CurrentPlayer.setPathToFollow(path).catch(() => {}); }) .catch((reason) => { console.warn(reason); }); } + // eslint-disable-next-line @typescript-eslint/no-unused-vars public handlePointerDownEvent(pointer: Phaser.Input.Pointer, gameObjects: Phaser.GameObjects.GameObject[]): void {} public handleSpaceKeyUpEvent(event: Event): Event { diff --git a/front/src/Stores/AudioManagerStore.ts b/front/src/Stores/AudioManagerStore.ts index 16be88ec..1cfa79b9 100644 --- a/front/src/Stores/AudioManagerStore.ts +++ b/front/src/Stores/AudioManagerStore.ts @@ -91,7 +91,7 @@ function createAudioManagerFileStore() { }); }, unloadAudio: () => { - update((file: string) => { + update(() => { audioManagerVolumeStore.setLoop(false); return ""; }); diff --git a/front/src/Stores/CoWebsiteStore.ts b/front/src/Stores/CoWebsiteStore.ts index c4ed2f65..998e4edf 100644 --- a/front/src/Stores/CoWebsiteStore.ts +++ b/front/src/Stores/CoWebsiteStore.ts @@ -9,7 +9,7 @@ function createCoWebsiteStore() { return { subscribe, add: (coWebsite: CoWebsite, position?: number) => { - coWebsite.getStateSubscriber().subscribe((value) => { + coWebsite.getStateSubscriber().subscribe(() => { update((currentArray) => currentArray); }); @@ -18,7 +18,6 @@ function createCoWebsiteStore() { if (position === 0) { return [coWebsite, ...currentArray]; } else if (currentArray.length > position) { - const test = [...currentArray.splice(position, 0, coWebsite)]; return [...currentArray.splice(position, 0, coWebsite)]; } diff --git a/front/src/Stores/CustomCharacterStore.ts b/front/src/Stores/CustomCharacterStore.ts index 0ade6ea8..d6eda12c 100644 --- a/front/src/Stores/CustomCharacterStore.ts +++ b/front/src/Stores/CustomCharacterStore.ts @@ -1,3 +1,3 @@ -import { derived, writable, Writable } from "svelte/store"; +import { writable } from "svelte/store"; export const activeRowStore = writable(0); diff --git a/front/src/Stores/EmbedScreensStore.ts b/front/src/Stores/EmbedScreensStore.ts index 172ec45b..326640f2 100644 --- a/front/src/Stores/EmbedScreensStore.ts +++ b/front/src/Stores/EmbedScreensStore.ts @@ -45,7 +45,4 @@ function createHighlightedEmbedScreenStore() { export const highlightedEmbedScreen = createHighlightedEmbedScreenStore(); export const embedScreenLayout = writable(LayoutMode.Presentation); -export const hasEmbedScreen = derived( - [streamableCollectionStore], - ($values) => get(streamableCollectionStore).size + get(coWebsites).length > 0 -); +export const hasEmbedScreen = derived([], () => get(streamableCollectionStore).size + get(coWebsites).length > 0); diff --git a/front/src/Stores/ErrorStore.ts b/front/src/Stores/ErrorStore.ts index 3b6d7842..a5001f45 100644 --- a/front/src/Stores/ErrorStore.ts +++ b/front/src/Stores/ErrorStore.ts @@ -10,7 +10,7 @@ interface ErrorMessage { * A store that contains a list of error messages to be displayed. */ function createErrorStore() { - const { subscribe, set, update } = writable([]); + const { subscribe, update } = writable([]); return { subscribe, diff --git a/front/src/Stores/LayoutManagerStore.ts b/front/src/Stores/LayoutManagerStore.ts index e0f8d955..b6f428aa 100644 --- a/front/src/Stores/LayoutManagerStore.ts +++ b/front/src/Stores/LayoutManagerStore.ts @@ -1,5 +1,4 @@ import { derived, writable } from "svelte/store"; -import type { ActivatablesManager } from "../Phaser/Game/ActivatablesManager"; import type { UserInputManager } from "../Phaser/UserInput/UserInputManager"; export interface LayoutManagerAction { diff --git a/front/src/Stores/MediaStore.ts b/front/src/Stores/MediaStore.ts index 557cbcd8..534fa53b 100644 --- a/front/src/Stores/MediaStore.ts +++ b/front/src/Stores/MediaStore.ts @@ -11,13 +11,13 @@ import { peerStore } from "./PeerStore"; import { privacyShutdownStore } from "./PrivacyShutdownStore"; import { MediaStreamConstraintsError } from "./Errors/MediaStreamConstraintsError"; import { SoundMeter } from "../Phaser/Components/SoundMeter"; -import { visibilityStore } from "./VisibilityStore"; +import deepEqual from "fast-deep-equal"; /** * A store that contains the camera state requested by the user (on or off). */ function createRequestedCameraState() { - const { subscribe, set, update } = writable(true); + const { subscribe, set } = writable(true); return { subscribe, @@ -30,7 +30,7 @@ function createRequestedCameraState() { * A store that contains the microphone state requested by the user (on or off). */ function createRequestedMicrophoneState() { - const { subscribe, set, update } = writable(true); + const { subscribe, set } = writable(true); return { subscribe, @@ -43,7 +43,7 @@ function createRequestedMicrophoneState() { * A store that contains whether the EnableCameraScene is shown or not. */ function createEnableCameraSceneVisibilityStore() { - const { subscribe, set, update } = writable(false); + const { subscribe, set } = writable(false); return { subscribe, @@ -147,7 +147,7 @@ export const cameraEnergySavingStore = derived( * A store that contains video constraints. */ function createVideoConstraintStore() { - const { subscribe, set, update } = writable({ + const { subscribe, update } = writable({ width: { min: 640, ideal: 1280, max: 1920 }, height: { min: 400, ideal: 720 }, frameRate: { ideal: localUserStore.getVideoQualityValue() }, @@ -190,7 +190,7 @@ export const videoConstraintStore = createVideoConstraintStore(); * A store that contains video constraints. */ function createAudioConstraintStore() { - const { subscribe, set, update } = writable({ + const { subscribe, update } = writable({ //TODO: make these values configurable in the game settings menu and store them in localstorage autoGainControl: false, echoCancellation: true, @@ -242,7 +242,6 @@ export const mediaStreamConstraintsStore = derived( privacyShutdownStore, cameraEnergySavingStore, isSilentStore, - visibilityStore, ], ( [ @@ -255,7 +254,6 @@ export const mediaStreamConstraintsStore = derived( $privacyShutdownStore, $cameraEnergySavingStore, $isSilentStore, - $visibilityStore, ], set ) => { @@ -317,10 +315,10 @@ export const mediaStreamConstraintsStore = derived( currentAudioConstraint = false; } - // Let's make the changes only if the new value is different from the old one. + // Let's make the changes only if the new value is different from the old one.tile if ( - previousComputedVideoConstraint != currentVideoConstraint || - previousComputedAudioConstraint != currentAudioConstraint + !deepEqual(previousComputedVideoConstraint, currentVideoConstraint) || + !deepEqual(previousComputedAudioConstraint, currentAudioConstraint) ) { previousComputedVideoConstraint = currentVideoConstraint; previousComputedAudioConstraint = currentAudioConstraint; diff --git a/front/src/Stores/PlayersStore.ts b/front/src/Stores/PlayersStore.ts index 9dc78780..70c59b92 100644 --- a/front/src/Stores/PlayersStore.ts +++ b/front/src/Stores/PlayersStore.ts @@ -2,8 +2,6 @@ import { writable } from "svelte/store"; import type { PlayerInterface } from "../Phaser/Game/PlayerInterface"; import type { RoomConnection } from "../Connexion/RoomConnection"; import { getRandomColor } from "../WebRtc/ColorGenerator"; -import { localUserStore } from "../Connexion/LocalUserStore"; -import room from "../Api/iframe/room"; let idCount = 0; diff --git a/front/src/Stores/PrivacyShutdownStore.ts b/front/src/Stores/PrivacyShutdownStore.ts index 6ef31fe7..36855dda 100644 --- a/front/src/Stores/PrivacyShutdownStore.ts +++ b/front/src/Stores/PrivacyShutdownStore.ts @@ -8,7 +8,7 @@ import { visibilityStore } from "./VisibilityStore"; function createPrivacyShutdownStore() { let privacyEnabled = false; - const { subscribe, set, update } = writable(privacyEnabled); + const { subscribe, set } = writable(privacyEnabled); visibilityStore.subscribe((isVisible) => { if (!isVisible && get(peerStore).size === 0) { diff --git a/front/src/Stores/ScreenSharingStore.ts b/front/src/Stores/ScreenSharingStore.ts index 96dcb062..08d9f1a3 100644 --- a/front/src/Stores/ScreenSharingStore.ts +++ b/front/src/Stores/ScreenSharingStore.ts @@ -9,7 +9,7 @@ declare const navigator: any; // eslint-disable-line @typescript-eslint/no-expli * A store that contains the camera state requested by the user (on or off). */ function createRequestedScreenSharingState() { - const { subscribe, set, update } = writable(false); + const { subscribe, set } = writable(false); return { subscribe, diff --git a/front/src/Stores/SelectCharacterStore.ts b/front/src/Stores/SelectCharacterStore.ts index 0c84a031..23f9a546 100644 --- a/front/src/Stores/SelectCharacterStore.ts +++ b/front/src/Stores/SelectCharacterStore.ts @@ -1,3 +1,3 @@ -import { derived, writable, Writable } from "svelte/store"; +import { writable } from "svelte/store"; export const selectCharacterSceneVisibleStore = writable(false); diff --git a/front/src/Stores/SelectCompanionStore.ts b/front/src/Stores/SelectCompanionStore.ts index e66f5de3..48ce3e7d 100644 --- a/front/src/Stores/SelectCompanionStore.ts +++ b/front/src/Stores/SelectCompanionStore.ts @@ -1,3 +1,3 @@ -import { derived, writable, Writable } from "svelte/store"; +import { writable } from "svelte/store"; export const selectCompanionSceneVisibleStore = writable(false); diff --git a/front/src/Stores/SoundPlayingStore.ts b/front/src/Stores/SoundPlayingStore.ts index 36fd5d77..eec6f65f 100644 --- a/front/src/Stores/SoundPlayingStore.ts +++ b/front/src/Stores/SoundPlayingStore.ts @@ -4,7 +4,7 @@ import { writable } from "svelte/store"; * A store that contains the URL of the sound currently playing */ function createSoundPlayingStore() { - const { subscribe, set, update } = writable(null); + const { subscribe, set } = writable(null); return { subscribe, diff --git a/front/src/Stores/StartLayerNamesStore.ts b/front/src/Stores/StartLayerNamesStore.ts index a6397872..f769e087 100644 --- a/front/src/Stores/StartLayerNamesStore.ts +++ b/front/src/Stores/StartLayerNamesStore.ts @@ -1,4 +1,4 @@ -import { Readable, writable } from "svelte/store"; +import { writable } from "svelte/store"; /** * A store that contains the map starting layers names diff --git a/front/src/Utils/PathfindingManager.ts b/front/src/Utils/PathfindingManager.ts index 1f88342f..04a1143f 100644 --- a/front/src/Utils/PathfindingManager.ts +++ b/front/src/Utils/PathfindingManager.ts @@ -100,7 +100,7 @@ export class PathfindingManager { start: { x: number; y: number }, end: { x: number; y: number } ): Promise<{ x: number; y: number }[]> { - return new Promise((resolve, reject) => { + return new Promise((resolve) => { this.easyStar.findPath(start.x, start.y, end.x, end.y, (path) => { if (path === null) { resolve([]); diff --git a/front/src/WebRtc/CoWebsite/CoWesbite.ts b/front/src/WebRtc/CoWebsite/CoWesbite.ts index 50ce3b9f..6364f599 100644 --- a/front/src/WebRtc/CoWebsite/CoWesbite.ts +++ b/front/src/WebRtc/CoWebsite/CoWesbite.ts @@ -1,5 +1,5 @@ import type CancelablePromise from "cancelable-promise"; -import type { Readable, Writable } from "svelte/store"; +import type { Readable } from "svelte/store"; export type CoWebsiteState = "asleep" | "loading" | "ready"; diff --git a/front/src/WebRtc/CoWebsiteManager.ts b/front/src/WebRtc/CoWebsiteManager.ts index 5cf3846d..7bc53c23 100644 --- a/front/src/WebRtc/CoWebsiteManager.ts +++ b/front/src/WebRtc/CoWebsiteManager.ts @@ -52,7 +52,7 @@ class CoWebsiteManager { trails: number[] | undefined; }; - private resizeObserver = new ResizeObserver((entries) => { + private resizeObserver = new ResizeObserver(() => { this.resizeAllIframes(); }); @@ -223,7 +223,7 @@ class CoWebsiteManager { this.fire(); }; - this.cowebsiteAsideHolderDom.addEventListener("mousedown", (event) => { + this.cowebsiteAsideHolderDom.addEventListener("mousedown", () => { if (this.isFullScreen) return; const coWebsite = this.getMainCoWebsite(); @@ -240,7 +240,7 @@ class CoWebsiteManager { document.addEventListener("mousemove", movecallback); }); - document.addEventListener("mouseup", (event) => { + document.addEventListener("mouseup", () => { if (!this.resizing || this.isFullScreen) return; document.removeEventListener("mousemove", movecallback); const coWebsite = this.getMainCoWebsite(); @@ -277,7 +277,7 @@ class CoWebsiteManager { document.addEventListener("touchmove", movecallback); }); - document.addEventListener("touchend", (event) => { + document.addEventListener("touchend", () => { if (!this.resizing || this.isFullScreen) return; this.previousTouchMoveCoordinates = null; document.removeEventListener("touchmove", movecallback); @@ -298,7 +298,7 @@ class CoWebsiteManager { } private transitionListeners() { - this.cowebsiteDom.addEventListener("transitionend", (event) => { + this.cowebsiteDom.addEventListener("transitionend", () => { if (this.cowebsiteDom.classList.contains("loading")) { this.fire(); } @@ -552,7 +552,7 @@ class CoWebsiteManager { } coWebsite.unload().catch((err) => { - console.error("Cannot unload cowebsite on remove from stack"); + console.error("Cannot unload cowebsite on remove from stack", err); }); } diff --git a/front/src/WebRtc/ColorGenerator.ts b/front/src/WebRtc/ColorGenerator.ts index 971715a6..77038aa7 100644 --- a/front/src/WebRtc/ColorGenerator.ts +++ b/front/src/WebRtc/ColorGenerator.ts @@ -14,6 +14,7 @@ export function getColorRgbFromHue(hue: number): { r: number; g: number; b: numb return hsv_to_rgb(hue, 0.5, 0.95); } +// eslint-disable-next-line @typescript-eslint/no-unused-vars function stringToDouble(string: string): number { let num = 1; for (const char of string.split("")) { diff --git a/front/src/i18n/formatters.ts b/front/src/i18n/formatters.ts index ff2bddef..00695fd6 100644 --- a/front/src/i18n/formatters.ts +++ b/front/src/i18n/formatters.ts @@ -2,7 +2,7 @@ import type { AsyncFormattersInitializer } from "typesafe-i18n"; import type { Locales, Formatters } from "./i18n-types"; // eslint-disable-next-line @typescript-eslint/require-await -export const initFormatters: AsyncFormattersInitializer = async (locale: Locales) => { +export const initFormatters: AsyncFormattersInitializer = async () => { const formatters: Formatters = { // add your formatter functions here }; diff --git a/front/src/i18n/locales.ts b/front/src/i18n/locales.ts index b38db71c..bff2f8e6 100644 --- a/front/src/i18n/locales.ts +++ b/front/src/i18n/locales.ts @@ -1,7 +1,7 @@ import { detectLocale, navigatorDetector, initLocalStorageDetector } from "typesafe-i18n/detectors"; import { FALLBACK_LOCALE } from "../Enum/EnvironmentVariable"; import { initI18n, setLocale } from "./i18n-svelte"; -import type { Locales, Translation } from "./i18n-types"; +import type { Locales } from "./i18n-types"; import { baseLocale, getTranslationForLocale, locales } from "./i18n-util"; const fallbackLocale = FALLBACK_LOCALE || baseLocale; diff --git a/front/src/i18n/zh-CN/audio.ts b/front/src/i18n/zh-CN/audio.ts new file mode 100644 index 00000000..7f8a8915 --- /dev/null +++ b/front/src/i18n/zh-CN/audio.ts @@ -0,0 +1,11 @@ +import type { Translation } from "../i18n-types"; + +const audio: NonNullable = { + manager: { + reduce: "说话时降低音乐音量", + allow: "播放声音", + }, + message: "音频消息", +}; + +export default audio; diff --git a/front/src/i18n/zh-CN/camera.ts b/front/src/i18n/zh-CN/camera.ts new file mode 100644 index 00000000..fbe186dc --- /dev/null +++ b/front/src/i18n/zh-CN/camera.ts @@ -0,0 +1,25 @@ +import type { Translation } from "../i18n-types"; + +const camera: NonNullable = { + enable: { + title: "开启你的摄像头和麦克风", + start: "出发!", + }, + help: { + title: "需要摄像头/麦克风权限", + permissionDenied: "拒绝访问", + content: "你必须在浏览器设置里允许摄像头和麦克风访问权限。", + firefoxContent: '如果你不希望Firefox反复要求授权,请选中"记住此决定"。', + refresh: "刷新", + continue: "不使用摄像头继续游戏", + screen: { + firefox: "/resources/help-setting-camera-permission/en-US-firefox.png", + chrome: "/resources/help-setting-camera-permission/en-US-firefox.png", + }, + }, + my: { + silentZone: "安静区", + }, +}; + +export default camera; diff --git a/front/src/i18n/zh-CN/chat.ts b/front/src/i18n/zh-CN/chat.ts new file mode 100644 index 00000000..5bca4570 --- /dev/null +++ b/front/src/i18n/zh-CN/chat.ts @@ -0,0 +1,12 @@ +import type { Translation } from "../i18n-types"; + +const chat: NonNullable = { + intro: "聊天历史:", + enter: "输入消息...", + menu: { + visitCard: "Visit card", + addFriend: "添加朋友", + }, +}; + +export default chat; diff --git a/front/src/i18n/zh-CN/companion.ts b/front/src/i18n/zh-CN/companion.ts new file mode 100644 index 00000000..aacb69f4 --- /dev/null +++ b/front/src/i18n/zh-CN/companion.ts @@ -0,0 +1,11 @@ +import type { Translation } from "../i18n-types"; + +const companion: NonNullable = { + select: { + title: "选择你的伙伴", + any: "没有伙伴", + continue: "继续", + }, +}; + +export default companion; diff --git a/front/src/i18n/zh-CN/emoji.ts b/front/src/i18n/zh-CN/emoji.ts new file mode 100644 index 00000000..43c69a81 --- /dev/null +++ b/front/src/i18n/zh-CN/emoji.ts @@ -0,0 +1,21 @@ +import type { Translation } from "../i18n-types"; + +const emoji: NonNullable = { + search: "搜索 emojis...", + categories: { + recents: "最近的 Emojis", + smileys: "表情", + people: "人物", + animals: "动物和自然", + food: "视频和饮料", + activities: "活动", + travel: "旅行和地点", + objects: "物品", + symbols: "符号", + flags: "旗帜", + custom: "自定义", + }, + notFound: "未找到emoji", +}; + +export default emoji; diff --git a/front/src/i18n/zh-CN/error.ts b/front/src/i18n/zh-CN/error.ts new file mode 100644 index 00000000..d5dc2453 --- /dev/null +++ b/front/src/i18n/zh-CN/error.ts @@ -0,0 +1,20 @@ +import type { Translation } from "../i18n-types"; + +const error: NonNullable = { + accessLink: { + title: "访问链接错误", + subTitle: "找不到地图。请检查你的访问链接。", + details: "如果你想了解更多信息,你可以联系管理员或联系我们: hello@workadventu.re", + }, + connectionRejected: { + title: "连接被拒绝", + subTitle: "你无法加入该世界。请稍后重试 {error}.", + details: "如果你想了解更多信息,你可以联系管理员或联系我们: hello@workadventu.re", + }, + connectionRetry: { + unableConnect: "无法链接到 WorkAdventure. 请检查互联网连接。", + }, + error: "错误", +}; + +export default error; diff --git a/front/src/i18n/zh-CN/follow.ts b/front/src/i18n/zh-CN/follow.ts new file mode 100644 index 00000000..f82f9934 --- /dev/null +++ b/front/src/i18n/zh-CN/follow.ts @@ -0,0 +1,27 @@ +import type { Translation } from "../i18n-types"; + +const follow: NonNullable = { + interactStatus: { + following: "跟随 {leader}", + waitingFollowers: "等待跟随者确认", + followed: { + one: "{follower} 正在跟随你", + two: "{firstFollower} 和 {secondFollower} 正在跟随你", + many: "{followers} 和 {lastFollower} 正在跟随你", + }, + }, + interactMenu: { + title: { + interact: "交互", + follow: "要跟随 {leader} 吗?", + }, + stop: { + leader: "要停止领路吗?", + follower: "要停止跟随 {leader} 吗?", + }, + yes: "是", + no: "否", + }, +}; + +export default follow; diff --git a/front/src/i18n/zh-CN/index.ts b/front/src/i18n/zh-CN/index.ts new file mode 100644 index 00000000..23e2a24c --- /dev/null +++ b/front/src/i18n/zh-CN/index.ts @@ -0,0 +1,36 @@ +import en_US from "../en-US"; +import type { Translation } from "../i18n-types"; +import audio from "./audio"; +import camera from "./camera"; +import chat from "./chat"; +import companion from "./companion"; +import woka from "./woka"; +import error from "./error"; +import follow from "./follow"; +import login from "./login"; +import menu from "./menu"; +import report from "./report"; +import warning from "./warning"; +import emoji from "./emoji"; +import trigger from "./trigger"; + +const zh_CN: Translation = { + ...(en_US as Translation), + language: "中文", + country: "中国", + audio, + camera, + chat, + companion, + woka, + error, + follow, + login, + menu, + report, + warning, + emoji, + trigger, +}; + +export default zh_CN; diff --git a/front/src/i18n/zh-CN/login.ts b/front/src/i18n/zh-CN/login.ts new file mode 100644 index 00000000..e5f5f99e --- /dev/null +++ b/front/src/i18n/zh-CN/login.ts @@ -0,0 +1,14 @@ +import type { Translation } from "../i18n-types"; + +const login: NonNullable = { + input: { + name: { + placeholder: "输入你的名字", + empty: "名字为空", + }, + }, + terms: '点击继续,意味着你同意我们的使用协议, 隐私政策Cookie策略.', + continue: "继续", +}; + +export default login; diff --git a/front/src/i18n/zh-CN/menu.ts b/front/src/i18n/zh-CN/menu.ts new file mode 100644 index 00000000..be1d63d5 --- /dev/null +++ b/front/src/i18n/zh-CN/menu.ts @@ -0,0 +1,132 @@ +import type { Translation } from "../i18n-types"; + +const menu: NonNullable = { + title: "菜单", + icon: { + open: { + menu: "打开菜单", + invite: "显示邀请", + register: "注册", + chat: "打开聊天", + }, + }, + visitCard: { + close: "关闭", + }, + profile: { + edit: { + name: "编辑名字", + woka: "编辑 WOKA", + companion: "编辑伙伴", + camera: "摄像头设置", + }, + login: "登录", + logout: "登出", + }, + settings: { + gameQuality: { + title: "游戏质量", + short: { + high: "高 (120 fps)", + medium: "中 (60 fps)", + small: "低 (40 fps)", + minimum: "最低 (20 fps)", + }, + long: { + high: "高视频质量 (120 fps)", + medium: "中视频质量 (60 fps, 推荐)", + small: "低视频质量 (40 fps)", + minimum: "最低视频质量 (20 fps)", + }, + }, + videoQuality: { + title: "视频质量", + short: { + high: "高 (30 fps)", + medium: "中 (20 fps)", + small: "低 (10 fps)", + minimum: "最低 (5 fps)", + }, + long: { + high: "高视频质量 (120 fps)", + medium: "中视频质量 (60 fps, 推荐)", + small: "低视频质量 (40 fps)", + minimum: "最低视频质量 (20 fps)", + }, + }, + language: { + title: "语言", + }, + privacySettings: { + title: "离开模式设置", + explanation: + '当WorkAdventure标签页在后台时, 会切换到"离开模式"。在该模式中,你可以选择自动禁用摄像头 和/或 麦克风 直到标签页显示。', + cameraToggle: "摄像头", + microphoneToggle: "麦克风", + }, + save: { + warning: "(保存这些设置会重新加载游戏)", + button: "保存", + }, + fullscreen: "全屏", + notifications: "通知", + cowebsiteTrigger: "在打开网页和Jitsi Meet会议前总是询问", + ignoreFollowRequest: "忽略跟随其他用户的请求", + }, + invite: { + description: "分享该房间的链接!", + copy: "复制", + share: "分享", + walk_automatically_to_position: "自动走到我的位置", + }, + globalMessage: { + text: "文本", + audio: "音频", + warning: "广播到世界的所有房间", + enter: "输入你的消息...", + send: "发送", + }, + globalAudio: { + uploadInfo: "上传文件", + error: "未选择文件。发送前必须上传一个文件。", + }, + contact: { + gettingStarted: { + title: "开始", + description: + "WorkAdventure使你能够创建一个在线空间,与他们自然地交流。这都从创建你自己的空间开始。从我们的团队预制的大量选项中选择一个地图。", + }, + createMap: { + title: "创建地图", + description: "你也可以跟随文档中的步骤创建你自己的地图。", + }, + }, + about: { + mapInfo: "地图信息", + mapLink: "地图链接", + copyrights: { + map: { + title: "地图版权", + empty: "地图创建者未申明地图版权。", + }, + tileset: { + title: "tilesets版权", + empty: "地图创建者未申明tilesets版权。这不意味着这些tilesets没有版权。", + }, + audio: { + title: "音频文件版权", + empty: "地图创建者未申明音频文件版权。这不意味着这些音频文件没有版权。", + }, + }, + }, + sub: { + profile: "资料", + settings: "设置", + invite: "邀请", + credit: "Credit", + globalMessages: "全局消息", + contact: "联系", + }, +}; + +export default menu; diff --git a/front/src/i18n/zh-CN/report.ts b/front/src/i18n/zh-CN/report.ts new file mode 100644 index 00000000..85ebc4cd --- /dev/null +++ b/front/src/i18n/zh-CN/report.ts @@ -0,0 +1,25 @@ +import type { Translation } from "../i18n-types"; + +const report: NonNullable = { + block: { + title: "屏蔽", + content: "屏蔽任何来自 {userName} 的通信。该操作是可逆的。", + unblock: "解除屏蔽该用户", + block: "屏蔽该用户", + }, + title: "举报", + content: "发送举报信息给这个房间的管理员,他们后续可能禁用该用户。", + message: { + title: "举报信息: ", + empty: "举报信息不能为空.", + }, + submit: "举报该用户", + moderate: { + title: "Moderate {userName}", + block: "屏蔽", + report: "举报", + noSelect: "错误:未选择行为。", + }, +}; + +export default report; diff --git a/front/src/i18n/zh-CN/trigger.ts b/front/src/i18n/zh-CN/trigger.ts new file mode 100644 index 00000000..2be48e6a --- /dev/null +++ b/front/src/i18n/zh-CN/trigger.ts @@ -0,0 +1,9 @@ +import type { Translation } from "../i18n-types"; + +const trigger: NonNullable = { + cowebsite: "按空格键或点击这里打开网页", + jitsiRoom: "按空格键或点击这里进入Jitsi Meet会议", + newTab: "按空格键或点击这里在新标签打开网页", +}; + +export default trigger; diff --git a/front/src/i18n/zh-CN/warning.ts b/front/src/i18n/zh-CN/warning.ts new file mode 100644 index 00000000..628fa671 --- /dev/null +++ b/front/src/i18n/zh-CN/warning.ts @@ -0,0 +1,18 @@ +import type { Translation } from "../i18n-types"; +import { ADMIN_URL } from "../../Enum/EnvironmentVariable"; + +const upgradeLink = ADMIN_URL + "/pricing"; + +const warning: NonNullable = { + title: "警告!", + content: `该世界已接近容量限制!你可以 点击这里 升级它的容量`, + limit: "该世界已接近容量限制!", + accessDenied: { + camera: "摄像头访问权限被拒绝。点击这里检查你的浏览器权限。", + screenSharing: "屏幕共享权限被拒绝。点击这里检查你的浏览器权限。", + }, + importantMessage: "重要消息", + connectionLost: "连接丢失。重新连接中...", +}; + +export default warning; diff --git a/front/src/i18n/zh-CN/woka.ts b/front/src/i18n/zh-CN/woka.ts new file mode 100644 index 00000000..cbdfd34c --- /dev/null +++ b/front/src/i18n/zh-CN/woka.ts @@ -0,0 +1,23 @@ +import type { Translation } from "../i18n-types"; + +const woka: NonNullable = { + customWoka: { + title: "自定义你的WOKA", + navigation: { + return: "返回", + back: "上一个", + finish: "完成", + next: "下一个", + }, + }, + selectWoka: { + title: "选择你的WOKA", + continue: "继续", + customize: "自定义你的 WOKA", + }, + menu: { + businessCard: "Business Card", + }, +}; + +export default woka; diff --git a/front/src/iframe_api.ts b/front/src/iframe_api.ts index f5e20032..83ea065e 100644 --- a/front/src/iframe_api.ts +++ b/front/src/iframe_api.ts @@ -227,7 +227,7 @@ window.addEventListener( const payloadData = payload.data; const callback = registeredCallbacks[payload.type] as IframeCallback | undefined; - if (callback?.typeChecker(payloadData)) { + if (callback?.typeChecker.safeParse(payloadData).success) { callback?.callback(payloadData); } } diff --git a/front/src/index.ts b/front/src/index.ts index 8310263f..d45829b5 100644 --- a/front/src/index.ts +++ b/front/src/index.ts @@ -24,7 +24,6 @@ import { Game } from "./Phaser/Game/Game"; import App from "./Components/App.svelte"; import { HtmlUtils } from "./WebRtc/HtmlUtils"; import WebGLRenderer = Phaser.Renderer.WebGL.WebGLRenderer; -import { analyticsClient } from "./Administration/AnalyticsClient"; import { isMediaBreakpointUp } from "./Utils/BreakpointsUtils"; const { width, height } = coWebsiteManager.getGameSize(); @@ -149,7 +148,7 @@ HtmlUtils.querySelectorOrFail("#game canvas").addEventListener("contextmenu", fu e.preventDefault(); }); -window.addEventListener("resize", function (event) { +window.addEventListener("resize", function () { coWebsiteManager.resetStyleMain(); waScaleManager.applyNewSize(); diff --git a/front/tests/Phaser/Game/HtmlUtilsTest.ts b/front/tests/Phaser/Game/HtmlUtilsTest.ts index a878fdc0..c23dad34 100644 --- a/front/tests/Phaser/Game/HtmlUtilsTest.ts +++ b/front/tests/Phaser/Game/HtmlUtilsTest.ts @@ -1,5 +1,4 @@ import "jasmine"; -import {HtmlUtils} from "../../../src/WebRtc/HtmlUtils"; describe("urlify()", () => { // FIXME: we need to add PhantomJS to have a good mock for "document". diff --git a/front/tests/Phaser/Map/LayersIteratorTest.ts b/front/tests/Phaser/Map/LayersIteratorTest.ts index a3951a7a..670799de 100644 --- a/front/tests/Phaser/Map/LayersIteratorTest.ts +++ b/front/tests/Phaser/Map/LayersIteratorTest.ts @@ -1,5 +1,4 @@ import "jasmine"; -import { Room } from "../../../src/Connexion/Room"; import { flattenGroupLayersMap } from "../../../src/Phaser/Map/LayersFlattener"; import type { ITiledMapLayer } from "../../../src/Phaser/Map/ITiledMap"; diff --git a/front/tests/Stores/Utils/MapStoreTest.ts b/front/tests/Stores/Utils/MapStoreTest.ts index dddc83ee..75b2bf42 100644 --- a/front/tests/Stores/Utils/MapStoreTest.ts +++ b/front/tests/Stores/Utils/MapStoreTest.ts @@ -1,6 +1,6 @@ import "jasmine"; import { MapStore } from "../../../src/Stores/Utils/MapStore"; -import type { Readable, Writable } from "svelte/store"; +import type { Writable } from "svelte/store"; import { get, writable } from "svelte/store"; describe("Main store", () => { diff --git a/front/yarn.lock b/front/yarn.lock index af55855d..72bca77a 100644 --- a/front/yarn.lock +++ b/front/yarn.lock @@ -1365,11 +1365,6 @@ fuzzysort@^1.1.4: resolved "https://registry.yarnpkg.com/fuzzysort/-/fuzzysort-1.1.4.tgz#a0510206ed44532cbb52cf797bf5a3cb12acd4ba" integrity sha512-JzK/lHjVZ6joAg3OnCjylwYXYVjRiwTY6Yb25LvfpJHK8bjisfnZJ5bY8aVWwTwCXgxPNgLAtmHL+Hs5q1ddLQ== -generic-type-guard@^3.4.2: - version "3.4.2" - resolved "https://registry.yarnpkg.com/generic-type-guard/-/generic-type-guard-3.4.2.tgz#fbbafde675dc30f12e701e67d2e0a451d17ccf07" - integrity sha512-k7HLCaToIwCx28Ck0H6SXwjxgV2GLoJuwq8CjgST5CeeFD0dEwE95jRinpv9ubsTPPXOCNdUufUAgJVbiRpMJg== - get-browser-rtc@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/get-browser-rtc/-/get-browser-rtc-1.1.0.tgz#d1494e299b00f33fc8e9d6d3343ba4ba99711a2c" @@ -3183,7 +3178,7 @@ yn@3.1.1: resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q== -zod@^3.11.6: - version "3.11.6" - resolved "https://registry.yarnpkg.com/zod/-/zod-3.11.6.tgz#e43a5e0c213ae2e02aefe7cb2b1a6fa3d7f1f483" - integrity sha512-daZ80A81I3/9lIydI44motWe6n59kRBfNzTuS2bfzVh1nAXi667TOTWWtatxyG+fwgNUiagSj/CWZwRRbevJIg== +zod@^3.14.3: + version "3.14.3" + resolved "https://registry.yarnpkg.com/zod/-/zod-3.14.3.tgz#60e86341c05883c281fe96a0e79acea48a09f123" + integrity sha512-OzwRCSXB1+/8F6w6HkYHdbuWysYWnAF4fkRgKDcSFc54CE+Sv0rHXKfeNUReGCrHukm1LNpi6AYeXotznhYJbQ== diff --git a/messages/JsonMessages/AdminApiData.ts b/messages/JsonMessages/AdminApiData.ts index 236c6355..5c994a11 100644 --- a/messages/JsonMessages/AdminApiData.ts +++ b/messages/JsonMessages/AdminApiData.ts @@ -1,19 +1,16 @@ -import * as tg from "generic-type-guard"; +import { z } from "zod"; /* * WARNING! The original file is in /messages/JsonMessages. * All other files are automatically copied from this file on container startup / build */ -export const isAdminApiData = new tg.IsInterface() - .withProperties({ - userUuid: tg.isString, - email: tg.isNullable(tg.isString), - roomUrl: tg.isString, - mapUrlStart: tg.isString, - }) - .withOptionalProperties({ - messages: tg.isArray(tg.isUnknown), - }) - .get(); -export type AdminApiData = tg.GuardedType; +export const isAdminApiData = z.object({ + userUuid: z.string(), + email: z.nullable(z.string()), + roomUrl: z.string(), + mapUrlStart: z.string(), + messages: z.optional(z.array(z.unknown())), +}); + +export type AdminApiData = z.infer; diff --git a/messages/JsonMessages/MapDetailsData.ts b/messages/JsonMessages/MapDetailsData.ts index 2dbf88ea..5ee5432c 100644 --- a/messages/JsonMessages/MapDetailsData.ts +++ b/messages/JsonMessages/MapDetailsData.ts @@ -1,32 +1,28 @@ -import * as tg from "generic-type-guard"; -import { isNumber } from "generic-type-guard"; +import { z } from "zod"; /* * WARNING! The original file is in /messages/JsonMessages. * All other files are automatically copied from this file on container startup / build */ -export const isMapDetailsData = new tg.IsInterface() - .withProperties({ - mapUrl: tg.isString, - policy_type: isNumber, //isNumericEnum(GameRoomPolicyTypes), - tags: tg.isArray(tg.isString), - authenticationMandatory: tg.isUnion(tg.isNullable(tg.isBoolean), tg.isUndefined), - roomSlug: tg.isNullable(tg.isString), // deprecated - contactPage: tg.isNullable(tg.isString), - group: tg.isNullable(tg.isString), - }) - .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, - // The URL of the logo image on the loading screen - loadingLogo: tg.isNullable(tg.isString), - // The URL of the logo image on "LoginScene" - loginSceneLogo: tg.isNullable(tg.isString), - }) - .get(); +export const isMapDetailsData = z.object({ + mapUrl: z.string(), + policy_type: z.number(), + tags: z.array(z.string()), + authenticationMandatory: z.optional(z.nullable(z.boolean())), + roomSlug: z.nullable(z.string()), // deprecated + contactPage: z.nullable(z.string()), + group: z.nullable(z.string()), -export type MapDetailsData = tg.GuardedType; + iframeAuthentication: z.optional(z.nullable(z.string())), + // The date (in ISO 8601 format) at which the room will expire + expireOn: z.optional(z.string()), + // Whether the "report" feature is enabled or not on this room + canReport: z.optional(z.boolean()), + // The URL of the logo image on the loading screen + loadingLogo: z.optional(z.nullable(z.string())), + // The URL of the logo image on "LoginScene" + loginSceneLogo: z.optional(z.nullable(z.string())), +}); + +export type MapDetailsData = z.infer; diff --git a/messages/JsonMessages/PlayerTextures.ts b/messages/JsonMessages/PlayerTextures.ts index 8c7407f9..f5d218d3 100644 --- a/messages/JsonMessages/PlayerTextures.ts +++ b/messages/JsonMessages/PlayerTextures.ts @@ -1,6 +1,10 @@ -import * as tg from "generic-type-guard"; import { z } from "zod"; +/* + * WARNING! The original file is in /messages/JsonMessages. + * All other files are automatically copied from this file on container startup / build + */ + //The list of all the player textures, both the default models and the partial textures used for customization const wokaTexture = z.object({ @@ -33,16 +37,12 @@ export type WokaList = z.infer; export const wokaPartNames = ["woka", "body", "eyes", "hair", "clothes", "hat", "accessory"]; -export const isWokaDetail = new tg.IsInterface() - .withProperties({ - id: tg.isString, - }) - .withOptionalProperties({ - url: tg.isString, - layer: tg.isString, - }) - .get(); +export const isWokaDetail = z.object({ + id: z.string(), + url: z.optional(z.string()), + layer: z.optional(z.string()), +}); -export type WokaDetail = tg.GuardedType; +export type WokaDetail = z.infer; export type WokaDetailsResult = WokaDetail[]; diff --git a/messages/JsonMessages/RegisterData.ts b/messages/JsonMessages/RegisterData.ts index de1b2ca7..39add182 100644 --- a/messages/JsonMessages/RegisterData.ts +++ b/messages/JsonMessages/RegisterData.ts @@ -1,22 +1,18 @@ -import * as tg from "generic-type-guard"; -//import { isCharacterTexture } from "./CharacterTexture"; +import { z } from "zod"; /* * WARNING! The original file is in /messages/JsonMessages. * All other files are automatically copied from this file on container startup / build */ -export const isRegisterData = new tg.IsInterface() - .withProperties({ - roomUrl: tg.isString, - email: tg.isNullable(tg.isString), - organizationMemberToken: tg.isNullable(tg.isString), - mapUrlStart: tg.isString, - userUuid: tg.isString, - authToken: tg.isString, - }) - .withOptionalProperties({ - messages: tg.isArray(tg.isUnknown), - }) - .get(); -export type RegisterData = tg.GuardedType; +export const isRegisterData = z.object({ + roomUrl: z.string(), + email: z.nullable(z.string()), + organizationMemberToken: z.nullable(z.string()), + mapUrlStart: z.string(), + userUuid: z.string(), + authToken: z.string(), + messages: z.optional(z.array(z.unknown())), +}); + +export type RegisterData = z.infer; diff --git a/messages/JsonMessages/RoomRedirect.ts b/messages/JsonMessages/RoomRedirect.ts index 1eb09937..50e72e15 100644 --- a/messages/JsonMessages/RoomRedirect.ts +++ b/messages/JsonMessages/RoomRedirect.ts @@ -1,13 +1,12 @@ -import * as tg from "generic-type-guard"; +import { z } from "zod"; /* * WARNING! The original file is in /messages/JsonMessages. * All other files are automatically copied from this file on container startup / build */ -export const isRoomRedirect = new tg.IsInterface() - .withProperties({ - redirectUrl: tg.isString, - }) - .get(); -export type RoomRedirect = tg.GuardedType; +export const isRoomRedirect = z.object({ + redirectUrl: z.string(), +}); + +export type RoomRedirect = z.infer; diff --git a/messages/package.json b/messages/package.json index 4cef28dd..1dba4f5a 100644 --- a/messages/package.json +++ b/messages/package.json @@ -18,11 +18,10 @@ "pretty-check": "yarn prettier --check 'JsonMessages/**/*.ts'" }, "dependencies": { - "generic-type-guard": "^3.5.0", "google-protobuf": "^3.13.0", "grpc": "^1.24.4", "ts-proto": "^1.96.0", - "zod": "^3.12.0" + "zod": "^3.14.3" }, "devDependencies": { "@types/google-protobuf": "^3.7.4", diff --git a/messages/yarn.lock b/messages/yarn.lock index 85d974d1..131c433b 100644 --- a/messages/yarn.lock +++ b/messages/yarn.lock @@ -1901,11 +1901,6 @@ gauge@~2.7.3: strip-ansi "^3.0.1" wide-align "^1.1.0" -generic-type-guard@^3.5.0: - version "3.5.0" - resolved "https://registry.yarnpkg.com/generic-type-guard/-/generic-type-guard-3.5.0.tgz#39de9f8fceee65d79e7540959f0e7b23210c07b6" - integrity sha512-OpgXv/sbRobhFboaSyN/Tsh97Sxt5pcfLLxCiYZgYIIWFFp+kn2EzAXiaQZKEVRlq1rOE/zh8cYhJXEwplbJiQ== - get-caller-file@^2.0.1: version "2.0.5" resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" @@ -4646,7 +4641,7 @@ year@^0.2.1: resolved "https://registry.yarnpkg.com/year/-/year-0.2.1.tgz#4083ae520a318b23ec86037f3000cb892bdf9bb0" integrity sha1-QIOuUgoxiyPshgN/MADLiSvfm7A= -zod@^3.12.0: - version "3.14.2" - resolved "https://registry.yarnpkg.com/zod/-/zod-3.14.2.tgz#0b4ed79085c471adce0e7f2c0a4fbb5ddc516ba2" - integrity sha512-iF+wrtzz7fQfkmn60PG6XFxaWBhYYKzp2i+nv24WbLUWb2JjymdkHlzBwP0erpc78WotwP5g9AAu7Sk8GWVVNw== +zod@^3.14.3: + version "3.14.3" + resolved "https://registry.yarnpkg.com/zod/-/zod-3.14.3.tgz#60e86341c05883c281fe96a0e79acea48a09f123" + integrity sha512-OzwRCSXB1+/8F6w6HkYHdbuWysYWnAF4fkRgKDcSFc54CE+Sv0rHXKfeNUReGCrHukm1LNpi6AYeXotznhYJbQ== diff --git a/pusher/.eslintrc.json b/pusher/.eslintrc.json index ce78dd63..27927fea 100644 --- a/pusher/.eslintrc.json +++ b/pusher/.eslintrc.json @@ -26,6 +26,9 @@ "rules": { "no-unused-vars": "off", "@typescript-eslint/no-explicit-any": "error", + "@typescript-eslint/no-unused-vars": [ + "error" + ], "no-throw-literal": "error" } -} +} \ No newline at end of file diff --git a/pusher/package.json b/pusher/package.json index 4de55915..22f6960e 100644 --- a/pusher/package.json +++ b/pusher/package.json @@ -43,7 +43,6 @@ "axios": "^0.21.2", "circular-json": "^0.5.9", "debug": "^4.3.1", - "generic-type-guard": "^3.2.0", "google-protobuf": "^3.13.0", "grpc": "^1.24.4", "hyper-express": "^5.8.1", @@ -54,7 +53,7 @@ "qs": "^6.10.3", "query-string": "^6.13.3", "uuidv4": "^6.0.7", - "zod": "^3.12.0" + "zod": "^3.14.3" }, "devDependencies": { "@types/circular-json": "^0.4.0", diff --git a/pusher/src/App.ts b/pusher/src/App.ts index f63bca1e..c816abfb 100644 --- a/pusher/src/App.ts +++ b/pusher/src/App.ts @@ -1,6 +1,6 @@ // lib/app.ts -import { IoSocketController } from "./Controller/IoSocketController"; //TODO fix import by "_Controller/..." -import { AuthenticateController } from "./Controller/AuthenticateController"; //TODO fix import by "_Controller/..." +import { IoSocketController } from "./Controller/IoSocketController"; +import { AuthenticateController } from "./Controller/AuthenticateController"; import { MapController } from "./Controller/MapController"; import { PrometheusController } from "./Controller/PrometheusController"; import { DebugController } from "./Controller/DebugController"; diff --git a/pusher/src/Controller/AuthenticateController.ts b/pusher/src/Controller/AuthenticateController.ts index e1b8c84d..88e9d6ff 100644 --- a/pusher/src/Controller/AuthenticateController.ts +++ b/pusher/src/Controller/AuthenticateController.ts @@ -1,11 +1,12 @@ import { v4 } from "uuid"; import { BaseHttpController } from "./BaseHttpController"; -import { adminApi, FetchMemberDataByUuidResponse } from "../Services/AdminApi"; +import { adminApi } from "../Services/AdminApi"; import { AuthTokenData, jwtTokenManager } from "../Services/JWTTokenManager"; import { parse } from "query-string"; import { openIDClient } from "../Services/OpenIDClient"; import { DISABLE_ANONYMOUS } from "../Enum/EnvironmentVariable"; import { RegisterData } from "../Messages/JsonMessages/RegisterData"; +import { adminService } from "../Services/AdminService"; export interface TokenInterface { userUuid: string; @@ -166,10 +167,11 @@ export class AuthenticateController extends BaseHttpController { //Get user data from Admin Back Office //This is very important to create User Local in LocalStorage in WorkAdventure - const resUserData = await this.getUserByUserIdentifier( + const resUserData = await adminService.fetchMemberDataByUuid( authTokenData.identifier, playUri as string, - IPAddress + IPAddress, + [] ); if (authTokenData.accessToken == undefined) { @@ -221,7 +223,7 @@ export class AuthenticateController extends BaseHttpController { //Get user data from Admin Back Office //This is very important to create User Local in LocalStorage in WorkAdventure - const data = await this.getUserByUserIdentifier(email, playUri as string, IPAddress); + const data = await adminService.fetchMemberDataByUuid(email, playUri as string, IPAddress, []); return res.json({ ...data, authToken, username: userInfo?.username, locale: userInfo?.locale }); } catch (e) { @@ -432,34 +434,4 @@ export class AuthenticateController extends BaseHttpController { } }); } - - /** - * - * @param email - * @param playUri - * @param IPAddress - * @return FetchMemberDataByUuidResponse|object - * @private - */ - private async getUserByUserIdentifier( - email: string, - playUri: string, - IPAddress: string - ): Promise { - let data: FetchMemberDataByUuidResponse = { - email: email, - userUuid: email, - tags: [], - messages: [], - visitCardUrl: null, - textures: [], - userRoomToken: undefined, - }; - try { - data = await adminApi.fetchMemberDataByUuid(email, playUri, IPAddress, []); - } catch (err) { - console.error("openIDCallback => fetchMemberDataByUuid", err); - } - return data; - } } diff --git a/pusher/src/Controller/DebugController.ts b/pusher/src/Controller/DebugController.ts index c8bc6430..223200e1 100644 --- a/pusher/src/Controller/DebugController.ts +++ b/pusher/src/Controller/DebugController.ts @@ -28,7 +28,7 @@ export class DebugController extends BaseHttpController { return obj; } else if (value instanceof Set) { const obj: Array = []; - for (const [setKey, setValue] of value.entries()) { + for (const setValue of value.values()) { obj.push(setValue); } return obj; diff --git a/pusher/src/Controller/IoSocketController.ts b/pusher/src/Controller/IoSocketController.ts index bf44d92f..9bae7595 100644 --- a/pusher/src/Controller/IoSocketController.ts +++ b/pusher/src/Controller/IoSocketController.ts @@ -1,4 +1,4 @@ -import { ExSocketInterface } from "../Model/Websocket/ExSocketInterface"; //TODO fix import by "_Model/.." +import { ExSocketInterface } from "../Model/Websocket/ExSocketInterface"; import { GameRoomPolicyTypes } from "../Model/PusherRoom"; import { PointInterface } from "../Model/Websocket/PointInterface"; import { @@ -27,18 +27,19 @@ import { UserMovesMessage } from "../Messages/generated/messages_pb"; import { parse } from "query-string"; import { AdminSocketTokenData, jwtTokenManager, tokenInvalidException } from "../Services/JWTTokenManager"; import { adminApi, FetchMemberDataByUuidResponse } from "../Services/AdminApi"; -import { SocketManager, socketManager } from "../Services/SocketManager"; +import { socketManager } from "../Services/SocketManager"; import { emitInBatch } from "../Services/IoSocketHelpers"; import { ADMIN_API_URL, ADMIN_SOCKETS_TOKEN, DISABLE_ANONYMOUS, SOCKET_IDLE_TIMER } from "../Enum/EnvironmentVariable"; -import { Zone } from "_Model/Zone"; -import { ExAdminSocketInterface } from "_Model/Websocket/ExAdminSocketInterface"; -import { isAdminMessageInterface } from "../Model/Websocket/Admin/AdminMessages"; +import { Zone } from "../Model/Zone"; +import { ExAdminSocketInterface } from "../Model/Websocket/ExAdminSocketInterface"; +import { AdminMessageInterface, isAdminMessageInterface } from "../Model/Websocket/Admin/AdminMessages"; import Axios from "axios"; import { InvalidTokenError } from "../Controller/InvalidTokenError"; import HyperExpress from "hyper-express"; import { localWokaService } from "../Services/LocalWokaService"; import { WebSocket } from "uWebSockets.js"; import { WokaDetail } from "../Messages/JsonMessages/PlayerTextures"; +import { z } from "zod"; /** * The object passed between the "open" and the "upgrade" methods when opening a websocket @@ -96,11 +97,18 @@ export class IoSocketController { console.log("Admin socket connect to client on " + Buffer.from(ws.getRemoteAddressAsText()).toString()); ws.disconnecting = false; }, - message: (ws, arrayBuffer, isBinary): void => { + message: (ws, arrayBuffer): void => { try { - const message = JSON.parse(new TextDecoder("utf-8").decode(new Uint8Array(arrayBuffer))); + const message: AdminMessageInterface = JSON.parse( + new TextDecoder("utf-8").decode(new Uint8Array(arrayBuffer)) + ); - if (!isAdminMessageInterface(message)) { + try { + isAdminMessageInterface.parse(message); + } catch (err) { + if (err instanceof z.ZodError) { + console.error(err.issues); + } console.error("Invalid message received.", message); ws.send( JSON.stringify({ @@ -186,14 +194,12 @@ export class IoSocketController { .catch((error) => console.error(error)); } } - } else { - const tmp: never = message.event; } } catch (err) { console.error(err); } }, - close: (ws, code, message) => { + close: (ws) => { const Client = ws as ExAdminSocketInterface; try { Client.disconnecting = true; @@ -224,7 +230,6 @@ export class IoSocketController { upgradeAborted.aborted = true; }); - const url = req.getUrl(); const query = parse(req.getQuery()); const websocketKey = req.getHeader("sec-websocket-key"); const websocketProtocol = req.getHeader("sec-websocket-protocol"); @@ -522,7 +527,7 @@ export class IoSocketController { }); } }, - message: (ws, arrayBuffer, isBinary): void => { + message: (ws, arrayBuffer): void => { const client = ws as ExSocketInterface; const message = ClientToServerMessage.deserializeBinary(new Uint8Array(arrayBuffer)); @@ -590,7 +595,7 @@ export class IoSocketController { drain: (ws) => { console.log("WebSocket backpressure: " + ws.getBufferedAmount()); }, - close: (ws, code, message) => { + close: (ws) => { const Client = ws as ExSocketInterface; try { Client.disconnecting = true; diff --git a/pusher/src/Controller/MapController.ts b/pusher/src/Controller/MapController.ts index bbab821d..19131a24 100644 --- a/pusher/src/Controller/MapController.ts +++ b/pusher/src/Controller/MapController.ts @@ -160,9 +160,11 @@ export class MapController extends BaseHttpController { } } } - const mapDetails = await adminApi.fetchMapDetails(query.playUri as string, userId); + const mapDetails = isMapDetailsData.parse( + await adminApi.fetchMapDetails(query.playUri as string, userId) + ); - if (isMapDetailsData(mapDetails) && DISABLE_ANONYMOUS) { + if (DISABLE_ANONYMOUS) { mapDetails.authenticationMandatory = true; } diff --git a/pusher/src/Enum/PlayerTextures.ts b/pusher/src/Enum/PlayerTextures.ts deleted file mode 100644 index 8c7407f9..00000000 --- a/pusher/src/Enum/PlayerTextures.ts +++ /dev/null @@ -1,48 +0,0 @@ -import * as tg from "generic-type-guard"; -import { z } from "zod"; - -//The list of all the player textures, both the default models and the partial textures used for customization - -const wokaTexture = z.object({ - id: z.string(), - name: z.string(), - url: z.string(), - tags: z.array(z.string()).optional(), - tintable: z.boolean().optional(), -}); - -export type WokaTexture = z.infer; - -const wokaTextureCollection = z.object({ - name: z.string(), - textures: z.array(wokaTexture), -}); - -export type WokaTextureCollection = z.infer; - -const wokaPartType = z.object({ - collections: z.array(wokaTextureCollection), - required: z.boolean().optional(), -}); - -export type WokaPartType = z.infer; - -export const wokaList = z.record(wokaPartType); - -export type WokaList = z.infer; - -export const wokaPartNames = ["woka", "body", "eyes", "hair", "clothes", "hat", "accessory"]; - -export const isWokaDetail = new tg.IsInterface() - .withProperties({ - id: tg.isString, - }) - .withOptionalProperties({ - url: tg.isString, - layer: tg.isString, - }) - .get(); - -export type WokaDetail = tg.GuardedType; - -export type WokaDetailsResult = WokaDetail[]; diff --git a/pusher/src/Model/Movable.ts b/pusher/src/Model/Movable.ts index ca586b7c..19bc8f92 100644 --- a/pusher/src/Model/Movable.ts +++ b/pusher/src/Model/Movable.ts @@ -1,4 +1,4 @@ -import { PositionInterface } from "_Model/PositionInterface"; +import { PositionInterface } from "../Model/PositionInterface"; /** * A physical object that can be placed into a Zone diff --git a/pusher/src/Model/PositionDispatcher.ts b/pusher/src/Model/PositionDispatcher.ts index aae5c572..daf5e7dc 100644 --- a/pusher/src/Model/PositionDispatcher.ts +++ b/pusher/src/Model/PositionDispatcher.ts @@ -9,8 +9,8 @@ * number of players around the current player. */ import { Zone, ZoneEventListener } from "./Zone"; -import { ViewportInterface } from "_Model/Websocket/ViewportMessage"; -import { ExSocketInterface } from "_Model/Websocket/ExSocketInterface"; +import { ViewportInterface } from "../Model/Websocket/ViewportMessage"; +import { ExSocketInterface } from "../Model/Websocket/ExSocketInterface"; //import Debug from "debug"; //const debug = Debug('positiondispatcher'); diff --git a/pusher/src/Model/PusherRoom.ts b/pusher/src/Model/PusherRoom.ts index 4f616eca..eeda267c 100644 --- a/pusher/src/Model/PusherRoom.ts +++ b/pusher/src/Model/PusherRoom.ts @@ -1,28 +1,18 @@ -import { ExSocketInterface } from "_Model/Websocket/ExSocketInterface"; +import { ExSocketInterface } from "../Model/Websocket/ExSocketInterface"; import { PositionDispatcher } from "./PositionDispatcher"; -import { ViewportInterface } from "_Model/Websocket/ViewportMessage"; +import { ViewportInterface } from "../Model/Websocket/ViewportMessage"; import { arrayIntersect } from "../Services/ArrayHelper"; -import { GroupDescriptor, UserDescriptor, ZoneEventListener } from "_Model/Zone"; +import { ZoneEventListener } from "../Model/Zone"; import { apiClientRepository } from "../Services/ApiClientRepository"; import { - BatchToPusherMessage, BatchToPusherRoomMessage, - EmoteEventMessage, ErrorMessage, - GroupLeftZoneMessage, - GroupUpdateZoneMessage, RoomMessage, SubMessage, - UserJoinedZoneMessage, - UserLeftZoneMessage, - UserMovedMessage, - VariableMessage, VariableWithTagMessage, - ZoneMessage, } from "../Messages/generated/messages_pb"; import Debug from "debug"; import { ClientReadableStream } from "grpc"; -import { ExAdminSocketInterface } from "_Model/Websocket/ExAdminSocketInterface"; const debug = Debug("room"); @@ -121,7 +111,7 @@ export class PusherRoom { } }); - this.backConnection.on("error", (e) => { + this.backConnection.on("error", (err) => { if (!this.isClosing) { debug("Error on back connection"); this.close(); @@ -129,6 +119,7 @@ export class PusherRoom { for (const listener of this.listeners) { listener.disconnecting = true; listener.end(1011, "Connection error between pusher and back server"); + console.error("Connection error between pusher and back server", err); } } }); diff --git a/pusher/src/Model/Websocket/Admin/AdminMessages.ts b/pusher/src/Model/Websocket/Admin/AdminMessages.ts index 1d64899a..1dc18ae5 100644 --- a/pusher/src/Model/Websocket/Admin/AdminMessages.ts +++ b/pusher/src/Model/Websocket/Admin/AdminMessages.ts @@ -1,30 +1,24 @@ -import * as tg from "generic-type-guard"; +import { z } from "zod"; -export const isBanBannedAdminMessageInterface = new tg.IsInterface() - .withProperties({ - type: tg.isSingletonStringUnion("ban", "banned"), - message: tg.isString, - userUuid: tg.isString, - }) - .get(); +export const isBanBannedAdminMessageInterface = z.object({ + type: z.enum(["ban", "banned"]), + message: z.string(), + userUuid: z.string(), +}); -export const isUserMessageAdminMessageInterface = new tg.IsInterface() - .withProperties({ - event: tg.isSingletonString("user-message"), - message: isBanBannedAdminMessageInterface, - world: tg.isString, - jwt: tg.isString, - }) - .get(); +export const isUserMessageAdminMessageInterface = z.object({ + event: z.enum(["user-message"]), + message: isBanBannedAdminMessageInterface, + world: z.string(), + jwt: z.string(), +}); -export const isListenRoomsMessageInterface = new tg.IsInterface() - .withProperties({ - event: tg.isSingletonString("listen"), - roomIds: tg.isArray(tg.isString), - jwt: tg.isString, - }) - .get(); +export const isListenRoomsMessageInterface = z.object({ + event: z.enum(["listen"]), + roomIds: z.array(z.string()), + jwt: z.string(), +}); -export const isAdminMessageInterface = tg.isUnion(isUserMessageAdminMessageInterface, isListenRoomsMessageInterface); +export const isAdminMessageInterface = z.union([isUserMessageAdminMessageInterface, isListenRoomsMessageInterface]); -export type AdminMessageInterface = tg.GuardedType; +export type AdminMessageInterface = z.infer; diff --git a/pusher/src/Model/Websocket/ExAdminSocketInterface.ts b/pusher/src/Model/Websocket/ExAdminSocketInterface.ts index 663953ef..72e24c87 100644 --- a/pusher/src/Model/Websocket/ExAdminSocketInterface.ts +++ b/pusher/src/Model/Websocket/ExAdminSocketInterface.ts @@ -1,17 +1,6 @@ -import { PointInterface } from "./PointInterface"; -import { Identificable } from "./Identificable"; -import { ViewportInterface } from "_Model/Websocket/ViewportMessage"; -import { - AdminPusherToBackMessage, - BatchMessage, - PusherToBackMessage, - ServerToAdminClientMessage, - ServerToClientMessage, - SubMessage, -} from "../../Messages/generated/messages_pb"; +import { AdminPusherToBackMessage, ServerToAdminClientMessage } from "../../Messages/generated/messages_pb"; import { compressors } from "hyper-express"; import { ClientDuplexStream } from "grpc"; -import { Zone } from "_Model/Zone"; export type AdminConnection = ClientDuplexStream; diff --git a/pusher/src/Model/Websocket/ExSocketInterface.ts b/pusher/src/Model/Websocket/ExSocketInterface.ts index 7b902d8f..53547fdf 100644 --- a/pusher/src/Model/Websocket/ExSocketInterface.ts +++ b/pusher/src/Model/Websocket/ExSocketInterface.ts @@ -1,6 +1,6 @@ import { PointInterface } from "./PointInterface"; import { Identificable } from "./Identificable"; -import { ViewportInterface } from "_Model/Websocket/ViewportMessage"; +import { ViewportInterface } from "../../Model/Websocket/ViewportMessage"; import { BatchMessage, CompanionMessage, @@ -9,7 +9,7 @@ import { SubMessage, } from "../../Messages/generated/messages_pb"; import { ClientDuplexStream } from "grpc"; -import { Zone } from "_Model/Zone"; +import { Zone } from "../../Model/Zone"; import { compressors } from "hyper-express"; import { WokaDetail } from "../../Messages/JsonMessages/PlayerTextures"; diff --git a/pusher/src/Model/Websocket/ItemEventMessage.ts b/pusher/src/Model/Websocket/ItemEventMessage.ts index 1bb7f615..fd28293e 100644 --- a/pusher/src/Model/Websocket/ItemEventMessage.ts +++ b/pusher/src/Model/Websocket/ItemEventMessage.ts @@ -1,11 +1,10 @@ -import * as tg from "generic-type-guard"; +import { z } from "zod"; -export const isItemEventMessageInterface = new tg.IsInterface() - .withProperties({ - itemId: tg.isNumber, - event: tg.isString, - state: tg.isUnknown, - parameters: tg.isUnknown, - }) - .get(); -export type ItemEventMessageInterface = tg.GuardedType; +export const isItemEventMessageInterface = z.object({ + itemId: z.number(), + event: z.string(), + state: z.unknown(), + parameters: z.unknown(), +}); + +export type ItemEventMessageInterface = z.infer; diff --git a/pusher/src/Model/Websocket/PointInterface.ts b/pusher/src/Model/Websocket/PointInterface.ts index d7c7826e..2275e5f8 100644 --- a/pusher/src/Model/Websocket/PointInterface.ts +++ b/pusher/src/Model/Websocket/PointInterface.ts @@ -1,18 +1,10 @@ -import * as tg from "generic-type-guard"; +import { z } from "zod"; -/*export interface PointInterface { - readonly x: number; - readonly y: number; - readonly direction: string; - readonly moving: boolean; -}*/ +export const isPointInterface = z.object({ + x: z.number(), + y: z.number(), + direction: z.string(), + moving: z.boolean(), +}); -export const isPointInterface = new tg.IsInterface() - .withProperties({ - x: tg.isNumber, - y: tg.isNumber, - direction: tg.isString, - moving: tg.isBoolean, - }) - .get(); -export type PointInterface = tg.GuardedType; +export type PointInterface = z.infer; diff --git a/pusher/src/Model/Websocket/ProtobufUtils.ts b/pusher/src/Model/Websocket/ProtobufUtils.ts index 09be4729..a0a19aa1 100644 --- a/pusher/src/Model/Websocket/ProtobufUtils.ts +++ b/pusher/src/Model/Websocket/ProtobufUtils.ts @@ -5,10 +5,9 @@ import { PointMessage, PositionMessage, } from "../../Messages/generated/messages_pb"; -import { ExSocketInterface } from "_Model/Websocket/ExSocketInterface"; import Direction = PositionMessage.Direction; -import { ItemEventMessageInterface } from "_Model/Websocket/ItemEventMessage"; -import { PositionInterface } from "_Model/PositionInterface"; +import { ItemEventMessageInterface } from "../../Model/Websocket/ItemEventMessage"; +import { PositionInterface } from "../../Model/PositionInterface"; import { WokaDetail } from "../../Messages/JsonMessages/PlayerTextures"; export class ProtobufUtils { diff --git a/pusher/src/Model/Websocket/ViewportMessage.ts b/pusher/src/Model/Websocket/ViewportMessage.ts index ea71ad68..86609790 100644 --- a/pusher/src/Model/Websocket/ViewportMessage.ts +++ b/pusher/src/Model/Websocket/ViewportMessage.ts @@ -1,11 +1,10 @@ -import * as tg from "generic-type-guard"; +import { z } from "zod"; -export const isViewport = new tg.IsInterface() - .withProperties({ - left: tg.isNumber, - top: tg.isNumber, - right: tg.isNumber, - bottom: tg.isNumber, - }) - .get(); -export type ViewportInterface = tg.GuardedType; +export const isViewport = z.object({ + left: z.number(), + top: z.number(), + right: z.number(), + bottom: z.number(), +}); + +export type ViewportInterface = z.infer; diff --git a/pusher/src/Model/Zone.ts b/pusher/src/Model/Zone.ts index ca4646a4..ab82bf1b 100644 --- a/pusher/src/Model/Zone.ts +++ b/pusher/src/Model/Zone.ts @@ -20,7 +20,7 @@ import { SetPlayerDetailsMessage, } from "../Messages/generated/messages_pb"; import { ClientReadableStream } from "grpc"; -import { PositionDispatcher } from "_Model/PositionDispatcher"; +import { PositionDispatcher } from "../Model/PositionDispatcher"; import Debug from "debug"; import { BoolValue, UInt32Value } from "google-protobuf/google/protobuf/wrappers_pb"; @@ -427,7 +427,7 @@ export class Zone { } } - for (const [groupId, group] of this.groups.entries()) { + for (const group of this.groups.values()) { this.socketListener.onGroupEnters(group, listener); } @@ -436,13 +436,13 @@ export class Zone { } public stopListening(listener: ExSocketInterface): void { - for (const [userId, user] of this.users.entries()) { + for (const userId of this.users.keys()) { if (userId !== listener.userId) { this.socketListener.onUserLeaves(userId, listener); } } - for (const [groupId, group] of this.groups.entries()) { + for (const groupId of this.groups.keys()) { this.socketListener.onGroupLeaves(groupId, listener); } diff --git a/pusher/src/Services/AdminApi.ts b/pusher/src/Services/AdminApi.ts index 8ccc4e4b..0cde82bd 100644 --- a/pusher/src/Services/AdminApi.ts +++ b/pusher/src/Services/AdminApi.ts @@ -1,36 +1,32 @@ -import { ADMIN_API_TOKEN, ADMIN_API_URL, ADMIN_URL, OPID_PROFILE_SCREEN_PROVIDER } from "../Enum/EnvironmentVariable"; +import { ADMIN_API_TOKEN, ADMIN_API_URL, OPID_PROFILE_SCREEN_PROVIDER } from "../Enum/EnvironmentVariable"; import Axios, { AxiosResponse } from "axios"; import { isMapDetailsData, MapDetailsData } from "../Messages/JsonMessages/MapDetailsData"; import { isRoomRedirect, RoomRedirect } from "../Messages/JsonMessages/RoomRedirect"; import { AdminApiData, isAdminApiData } from "../Messages/JsonMessages/AdminApiData"; -import * as tg from "generic-type-guard"; -import { isNumber } from "generic-type-guard"; +import { z } from "zod"; import { isWokaDetail } from "../Messages/JsonMessages/PlayerTextures"; import qs from "qs"; +import { AdminInterface } from "./AdminInterface"; export interface AdminBannedData { is_banned: boolean; message: string; } -const isFetchMemberDataByUuidResponse = new tg.IsInterface() - .withProperties({ - email: tg.isString, - userUuid: tg.isString, - tags: tg.isArray(tg.isString), - visitCardUrl: tg.isNullable(tg.isString), - textures: tg.isArray(isWokaDetail), - messages: tg.isArray(tg.isUnknown), - }) - .withOptionalProperties({ - anonymous: tg.isBoolean, - userRoomToken: tg.isString, - }) - .get(); +export const isFetchMemberDataByUuidResponse = z.object({ + email: z.string(), + userUuid: z.string(), + tags: z.array(z.string()), + visitCardUrl: z.nullable(z.string()), + textures: z.array(isWokaDetail), + messages: z.array(z.unknown()), + anonymous: z.optional(z.boolean()), + userRoomToken: z.optional(z.string()), +}); -export type FetchMemberDataByUuidResponse = tg.GuardedType; +export type FetchMemberDataByUuidResponse = z.infer; -class AdminApi { +class AdminApi implements AdminInterface { private locale: string = "en"; setLocale(locale: string) { //console.info('PUSHER LOCALE SET TO :', locale); @@ -55,17 +51,27 @@ class AdminApi { headers: { Authorization: `${ADMIN_API_TOKEN}`, "Accept-Language": this.locale }, params, }); - if (!isMapDetailsData(res.data) && !isRoomRedirect(res.data)) { - throw new Error( - "Invalid answer received from the admin for the /api/map endpoint. Received: " + - JSON.stringify(res.data) - ); + + const mapDetailData = isMapDetailsData.safeParse(res.data); + const roomRedirect = isRoomRedirect.safeParse(res.data); + + if (mapDetailData.success) { + return mapDetailData.data; } - return res.data; + + if (roomRedirect.success) { + return roomRedirect.data; + } + + console.error(mapDetailData.error.issues); + console.error(roomRedirect.error.issues); + throw new Error( + "Invalid answer received from the admin for the /api/map endpoint. Received: " + JSON.stringify(res.data) + ); } async fetchMemberDataByUuid( - userIdentifier: string | null, + userIdentifier: string, playUri: string, ipAddress: string, characterLayers: string[] @@ -85,13 +91,18 @@ class AdminApi { return qs.stringify(p, { arrayFormat: "brackets" }); }, }); - if (!isFetchMemberDataByUuidResponse(res.data)) { - throw new Error( - "Invalid answer received from the admin for the /api/room/access endpoint. Received: " + - JSON.stringify(res.data) - ); + + const fetchMemberDataByUuidResponse = isFetchMemberDataByUuidResponse.safeParse(res.data); + + if (fetchMemberDataByUuidResponse.success) { + return fetchMemberDataByUuidResponse.data; } - return res.data; + + console.error(fetchMemberDataByUuidResponse.error.issues); + throw new Error( + "Invalid answer received from the admin for the /api/room/access endpoint. Received: " + + JSON.stringify(res.data) + ); } async fetchMemberDataByToken(organizationMemberToken: string, playUri: string | null): Promise { @@ -103,11 +114,16 @@ class AdminApi { params: { playUri }, headers: { Authorization: `${ADMIN_API_TOKEN}`, "Accept-Language": this.locale }, }); - if (!isAdminApiData(res.data)) { - console.error("Message received from /api/login-url is not in the expected format. Message: ", res.data); - throw new Error("Message received from /api/login-url is not in the expected format."); + + const adminApiData = isAdminApiData.safeParse(res.data); + + if (adminApiData.success) { + return adminApiData.data; } - return res.data; + + console.error(adminApiData.error.issues); + console.error("Message received from /api/login-url is not in the expected format. Message: ", res.data); + throw new Error("Message received from /api/login-url is not in the expected format."); } reportPlayer( diff --git a/pusher/src/Services/AdminInterface.ts b/pusher/src/Services/AdminInterface.ts new file mode 100644 index 00000000..66121316 --- /dev/null +++ b/pusher/src/Services/AdminInterface.ts @@ -0,0 +1,10 @@ +import { FetchMemberDataByUuidResponse } from "./AdminApi"; + +export interface AdminInterface { + fetchMemberDataByUuid( + userIdentifier: string, + playUri: string, + ipAddress: string, + characterLayers: string[] + ): Promise; +} diff --git a/pusher/src/Services/AdminService.ts b/pusher/src/Services/AdminService.ts new file mode 100644 index 00000000..8d21a105 --- /dev/null +++ b/pusher/src/Services/AdminService.ts @@ -0,0 +1,5 @@ +import { ADMIN_API_URL } from "../Enum/EnvironmentVariable"; +import { adminApi } from "./AdminApi"; +import { localAdmin } from "./LocalAdmin"; + +export const adminService = ADMIN_API_URL ? adminApi : localAdmin; diff --git a/pusher/src/Services/IoSocketHelpers.ts b/pusher/src/Services/IoSocketHelpers.ts index 2da7c430..ac67bf93 100644 --- a/pusher/src/Services/IoSocketHelpers.ts +++ b/pusher/src/Services/IoSocketHelpers.ts @@ -1,4 +1,4 @@ -import { ExSocketInterface } from "_Model/Websocket/ExSocketInterface"; +import { ExSocketInterface } from "../Model/Websocket/ExSocketInterface"; import { BatchMessage, ErrorMessage, ServerToClientMessage, SubMessage } from "../Messages/generated/messages_pb"; import { WebSocket } from "uWebSockets.js"; diff --git a/pusher/src/Services/LocalAdmin.ts b/pusher/src/Services/LocalAdmin.ts new file mode 100644 index 00000000..4e2c41a0 --- /dev/null +++ b/pusher/src/Services/LocalAdmin.ts @@ -0,0 +1,29 @@ +import { FetchMemberDataByUuidResponse } from "./AdminApi"; +import { AdminInterface } from "./AdminInterface"; + +/** + * A local class mocking a real admin if no admin is configured. + */ +class LocalAdmin implements AdminInterface { + fetchMemberDataByUuid( + userIdentifier: string, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + playUri: string, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + ipAddress: string, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + characterLayers: string[] + ): Promise { + return Promise.resolve({ + email: userIdentifier, + userUuid: userIdentifier, + tags: [], + messages: [], + visitCardUrl: null, + textures: [], + userRoomToken: undefined, + }); + } +} + +export const localAdmin = new LocalAdmin(); diff --git a/pusher/src/Services/LocalWokaService.ts b/pusher/src/Services/LocalWokaService.ts index e2eaac1f..2834f63c 100644 --- a/pusher/src/Services/LocalWokaService.ts +++ b/pusher/src/Services/LocalWokaService.ts @@ -1,10 +1,11 @@ -import { WokaDetail, WokaDetailsResult, WokaList, wokaPartNames } from "../Messages/JsonMessages/PlayerTextures"; +import { WokaDetail, WokaList, wokaPartNames } from "../Messages/JsonMessages/PlayerTextures"; import { WokaServiceInterface } from "./WokaServiceInterface"; class LocalWokaService implements WokaServiceInterface { /** * Returns the list of all available Wokas & Woka Parts for the current user. */ + // eslint-disable-next-line @typescript-eslint/no-unused-vars async getWokaList(roomId: string, token: string): Promise { const wokaData: WokaList = await require("../../data/woka.json"); if (!wokaData) { @@ -21,7 +22,7 @@ class LocalWokaService implements WokaServiceInterface { * * If one of the textures cannot be found, undefined is returned (and the user should be redirected to Woka choice page!) */ - async fetchWokaDetails(textureIds: string[]): Promise { + async fetchWokaDetails(textureIds: string[]): Promise { const wokaData: WokaList = await require("../../data/woka.json"); const textures = new Map< string, diff --git a/pusher/src/Services/SocketManager.ts b/pusher/src/Services/SocketManager.ts index c644fc49..735c5095 100644 --- a/pusher/src/Services/SocketManager.ts +++ b/pusher/src/Services/SocketManager.ts @@ -50,13 +50,11 @@ import Jwt from "jsonwebtoken"; import { clientEventsEmitter } from "./ClientEventsEmitter"; import { gaugeManager } from "./GaugeManager"; import { apiClientRepository } from "./ApiClientRepository"; -import { GroupDescriptor, UserDescriptor, ZoneEventListener } from "_Model/Zone"; +import { GroupDescriptor, UserDescriptor, ZoneEventListener } from "../Model/Zone"; import Debug from "debug"; -import { ExAdminSocketInterface } from "_Model/Websocket/ExAdminSocketInterface"; -import { WebSocket } from "uWebSockets.js"; -import { isRoomRedirect } from "../Messages/JsonMessages/RoomRedirect"; -//import { CharacterTexture } from "../Messages/JsonMessages/CharacterTexture"; +import { ExAdminSocketInterface } from "../Model/Websocket/ExAdminSocketInterface"; import { compressors } from "hyper-express"; +import { isMapDetailsData } from "../Messages/JsonMessages/MapDetailsData"; const debug = Debug("socket"); @@ -124,7 +122,13 @@ export class SocketManager implements ZoneEventListener { } }) .on("end", () => { - console.warn("Admin connection lost to back server"); + console.warn( + "Admin connection lost to back server '" + + apiClient.getChannel().getTarget() + + "' for room '" + + roomId + + "'" + ); // Let's close the front connection if the back connection is closed. This way, we can retry connecting from the start. if (!client.disconnecting) { this.closeWebsocketConnection(client, 1011, "Admin Connection lost to back server"); @@ -132,7 +136,14 @@ export class SocketManager implements ZoneEventListener { console.log("A user left"); }) .on("error", (err: Error) => { - console.error("Error in connection to back server:", err); + console.error( + "Error in connection to back server '" + + apiClient.getChannel().getTarget() + + "' for room '" + + roomId + + "':", + err + ); if (!client.disconnecting) { this.closeWebsocketConnection(client, 1011, "Error while connecting to back server"); } @@ -189,7 +200,7 @@ export class SocketManager implements ZoneEventListener { joinRoomMessage.addCharacterlayer(characterLayerMessage); } - console.log("Calling joinRoom"); + console.log("Calling joinRoom '" + client.roomId + "'"); const apiClient = await apiClientRepository.getClient(client.roomId); const streamToPusher = apiClient.joinRoom(); clientEventsEmitter.emitClientJoin(client.userUuid, client.roomId); @@ -217,7 +228,13 @@ export class SocketManager implements ZoneEventListener { } }) .on("end", () => { - console.warn("Connection lost to back server"); + console.warn( + "Connection lost to back server '" + + apiClient.getChannel().getTarget() + + "' for room '" + + client.roomId + + "'" + ); // Let's close the front connection if the back connection is closed. This way, we can retry connecting from the start. if (!client.disconnecting) { this.closeWebsocketConnection(client, 1011, "Connection lost to back server"); @@ -225,7 +242,14 @@ export class SocketManager implements ZoneEventListener { console.log("A user left"); }) .on("error", (err: Error) => { - console.error("Error in connection to back server:", err); + console.error( + "Error in connection to back server '" + + apiClient.getChannel().getTarget() + + "' for room '" + + client.roomId + + "':", + err + ); if (!client.disconnecting) { this.closeWebsocketConnection(client, 1011, "Error while connecting to back server"); } @@ -447,13 +471,14 @@ export class SocketManager implements ZoneEventListener { public async updateRoomWithAdminData(room: PusherRoom): Promise { const data = await adminApi.fetchMapDetails(room.roomUrl); + const mapDetailsData = isMapDetailsData.safeParse(data); - if (isRoomRedirect(data)) { + if (mapDetailsData.success) { + room.tags = mapDetailsData.data.tags; + room.policyType = Number(mapDetailsData.data.policy_type); + } else { // TODO: if the updated room data is actually a redirect, we need to take everybody on the map // and redirect everybody to the new location (so we need to close the connection for everybody) - } else { - room.tags = data.tags; - room.policyType = Number(data.policy_type); } } @@ -715,7 +740,7 @@ export class SocketManager implements ZoneEventListener { for (const roomUrl of tabUrlRooms) { const apiRoom = await apiClientRepository.getClient(roomUrl); roomMessage.setRoomid(roomUrl); - apiRoom.sendAdminMessageToRoom(roomMessage, (response) => { + apiRoom.sendAdminMessageToRoom(roomMessage, () => { return; }); } diff --git a/pusher/tsconfig.json b/pusher/tsconfig.json index e149d304..906ef01c 100644 --- a/pusher/tsconfig.json +++ b/pusher/tsconfig.json @@ -3,18 +3,18 @@ "experimentalDecorators": true, /* Basic Options */ // "incremental": true, /* Enable incremental compilation */ - "target": "ES2019", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */ + "target": "ES2019", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */ "downlevelIteration": true, - "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */ + "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */ // "lib": [], /* Specify library files to be included in the compilation. */ - "allowJs": true, /* Allow javascript files to be compiled. */ + "allowJs": true, /* Allow javascript files to be compiled. */ // "checkJs": true, /* Report errors in .js files. */ // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ // "declaration": true, /* Generates corresponding '.d.ts' file. */ // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ - "sourceMap": true, /* Generates corresponding '.map' file. */ + "sourceMap": true, /* Generates corresponding '.map' file. */ // "outFile": "./", /* Concatenate and emit output to single file. */ - "outDir": "./dist", /* Redirect output structure to the directory. */ + "outDir": "./dist", /* Redirect output structure to the directory. */ // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ // "composite": true, /* Enable project compilation */ // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ @@ -23,50 +23,50 @@ // "importHelpers": true, /* Import emit helpers from 'tslib'. */ // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ - /* Strict Type-Checking Options */ - "strict": true, /* Enable all strict type-checking options. */ + "strict": true, /* Enable all strict type-checking options. */ // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ // "strictNullChecks": true, /* Enable strict null checks. */ // "strictFunctionTypes": true, /* Enable strict checking of function types. */ // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ - "noImplicitThis": false, /* Raise error on 'this' expressions with an implied 'any' type. */ // Disabled because of sifrr server that is monkey patching HttpResponse + "noImplicitThis": false, /* Raise error on 'this' expressions with an implied 'any' type. */ // Disabled because of sifrr server that is monkey patching HttpResponse // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ - /* Additional Checks */ // "noUnusedLocals": true, /* Report errors on unused locals. */ // "noUnusedParameters": true, /* Report errors on unused parameters. */ - "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ - "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ - + "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ + "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ /* Module Resolution Options */ - "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ - "baseUrl": ".", /* Base directory to resolve non-absolute module names. */ - "paths": { - "_Controller/*": ["src/Controller/*"], - "_Model/*": ["src/Model/*"], - "_Enum/*": ["src/Enum/*"] - }, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ + "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ + // "baseUrl": ".", /* Base directory to resolve non-absolute module names. */ + // "paths": { + // "_Controller/*": [ + // "src/Controller/*" + // ], + // "_Model/*": [ + // "src/Model/*" + // ], + // "_Enum/*": [ + // "src/Enum/*" + // ] + // }, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ // "typeRoots": [], /* List of folders to include type definitions from. */ // "types": [], /* Type declaration files to be included in compilation. */ // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ - "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ + "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ - /* Source Map Options */ // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ - /* Experimental Options */ // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ - /* Advanced Options */ - "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ + "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ } -} +} \ No newline at end of file diff --git a/pusher/yarn.lock b/pusher/yarn.lock index 1d65440d..f698837e 100644 --- a/pusher/yarn.lock +++ b/pusher/yarn.lock @@ -1146,11 +1146,6 @@ gauge@^3.0.0: strip-ansi "^6.0.1" wide-align "^1.1.2" -generic-type-guard@^3.2.0: - version "3.5.0" - resolved "https://registry.yarnpkg.com/generic-type-guard/-/generic-type-guard-3.5.0.tgz#39de9f8fceee65d79e7540959f0e7b23210c07b6" - integrity sha512-OpgXv/sbRobhFboaSyN/Tsh97Sxt5pcfLLxCiYZgYIIWFFp+kn2EzAXiaQZKEVRlq1rOE/zh8cYhJXEwplbJiQ== - get-intrinsic@^1.0.2: version "1.1.1" resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.1.tgz#15f59f376f855c446963948f0d24cd3637b4abc6" @@ -2835,7 +2830,7 @@ z-schema@^4.2.3: optionalDependencies: commander "^2.7.1" -zod@^3.12.0: - version "3.12.0" - resolved "https://registry.yarnpkg.com/zod/-/zod-3.12.0.tgz#84ba9f6bdb7835e2483982d5f52cfffcb6a00346" - integrity sha512-w+mmntgEL4hDDL5NLFdN6Fq2DSzxfmlSoJqiYE1/CApO8EkOCxvJvRYEVf8Vr/lRs3i6gqoiyFM6KRcWqqdBzQ== +zod@^3.14.3: + version "3.14.3" + resolved "https://registry.yarnpkg.com/zod/-/zod-3.14.3.tgz#60e86341c05883c281fe96a0e79acea48a09f123" + integrity sha512-OzwRCSXB1+/8F6w6HkYHdbuWysYWnAF4fkRgKDcSFc54CE+Sv0rHXKfeNUReGCrHukm1LNpi6AYeXotznhYJbQ== diff --git a/tests/package-lock.json b/tests/package-lock.json index 80220130..287f6dd9 100644 --- a/tests/package-lock.json +++ b/tests/package-lock.json @@ -5,7 +5,7 @@ "packages": { "": { "devDependencies": { - "@playwright/test": "^1.20.0", + "@playwright/test": "~1.21.0", "@types/dockerode": "^3.3.0", "axios": "^0.24.0", "dockerode": "^3.3.1", @@ -854,9 +854,9 @@ } }, "node_modules/@playwright/test": { - "version": "1.20.0", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.20.0.tgz", - "integrity": "sha512-UpI5HTcgNLckR0kqXqwNvbcIXtRaDxk+hnO0OBwPSjfbBjRfRgAJ2ClA/b30C5E3UW5dJa17zhsy2qrk66l5cg==", + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.21.0.tgz", + "integrity": "sha512-jvgN3ZeAG6rw85z4u9Rc4uyj6qIaYlq2xrOtS7J2+CDYhzKOttab9ix9ELcvBOCHuQ6wgTfxfJYdh6DRZmQ9hg==", "dev": true, "dependencies": { "@babel/code-frame": "7.16.7", @@ -882,13 +882,13 @@ "debug": "4.3.3", "expect": "27.2.5", "jest-matcher-utils": "27.2.5", - "json5": "2.2.0", + "json5": "2.2.1", "mime": "3.0.0", "minimatch": "3.0.4", "ms": "2.1.3", "open": "8.4.0", "pirates": "4.0.4", - "playwright-core": "1.20.0", + "playwright-core": "1.21.0", "rimraf": "3.0.2", "source-map-support": "0.4.18", "stack-utils": "2.0.5", @@ -1007,9 +1007,9 @@ "dev": true }, "node_modules/@types/yauzl": { - "version": "2.9.2", - "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.9.2.tgz", - "integrity": "sha512-8uALY5LTvSuHgloDVUvWP3pIauILm+8/0pDMokuDYIoNsOkSwd5AiHBTSEJjKTDcZr5z8UpgOWZkxBF4iJftoA==", + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-Cn6WYCm0tXv8p6k+A8PvbDG763EDpBoTzHdA+Q/MF6H3sapGjCm9NzoaJncJS9tUKSuCoDs9XHxYYsQDgxR6kw==", "dev": true, "optional": true, "dependencies": { @@ -2071,13 +2071,10 @@ } }, "node_modules/json5": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz", - "integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", + "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", "dev": true, - "dependencies": { - "minimist": "^1.2.5" - }, "bin": { "json5": "lib/cli.js" }, @@ -2279,9 +2276,9 @@ } }, "node_modules/playwright-core": { - "version": "1.20.0", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.20.0.tgz", - "integrity": "sha512-d25IRcdooS278Cijlp8J8A5fLQZ+/aY3dKRJvgX5yjXA69N0huIUdnh3xXSgn+LsQ9DCNmB7Ngof3eY630jgdA==", + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.21.0.tgz", + "integrity": "sha512-yDGVs9qaaW6WiefgR7wH1CGt9D6D/X4U3jNpIzH0FjjrrWLCOYQo78Tu3SkW8X+/kWlBpj49iWf3QNSxhYc12Q==", "dev": true, "dependencies": { "colors": "1.4.0", @@ -3340,9 +3337,9 @@ } }, "@playwright/test": { - "version": "1.20.0", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.20.0.tgz", - "integrity": "sha512-UpI5HTcgNLckR0kqXqwNvbcIXtRaDxk+hnO0OBwPSjfbBjRfRgAJ2ClA/b30C5E3UW5dJa17zhsy2qrk66l5cg==", + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.21.0.tgz", + "integrity": "sha512-jvgN3ZeAG6rw85z4u9Rc4uyj6qIaYlq2xrOtS7J2+CDYhzKOttab9ix9ELcvBOCHuQ6wgTfxfJYdh6DRZmQ9hg==", "dev": true, "requires": { "@babel/code-frame": "7.16.7", @@ -3368,13 +3365,13 @@ "debug": "4.3.3", "expect": "27.2.5", "jest-matcher-utils": "27.2.5", - "json5": "2.2.0", + "json5": "2.2.1", "mime": "3.0.0", "minimatch": "3.0.4", "ms": "2.1.3", "open": "8.4.0", "pirates": "4.0.4", - "playwright-core": "1.20.0", + "playwright-core": "1.21.0", "rimraf": "3.0.2", "source-map-support": "0.4.18", "stack-utils": "2.0.5", @@ -3489,9 +3486,9 @@ "dev": true }, "@types/yauzl": { - "version": "2.9.2", - "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.9.2.tgz", - "integrity": "sha512-8uALY5LTvSuHgloDVUvWP3pIauILm+8/0pDMokuDYIoNsOkSwd5AiHBTSEJjKTDcZr5z8UpgOWZkxBF4iJftoA==", + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-Cn6WYCm0tXv8p6k+A8PvbDG763EDpBoTzHdA+Q/MF6H3sapGjCm9NzoaJncJS9tUKSuCoDs9XHxYYsQDgxR6kw==", "dev": true, "optional": true, "requires": { @@ -4269,13 +4266,10 @@ "dev": true }, "json5": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz", - "integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==", - "dev": true, - "requires": { - "minimist": "^1.2.5" - } + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", + "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", + "dev": true }, "micromatch": { "version": "4.0.4", @@ -4425,9 +4419,9 @@ } }, "playwright-core": { - "version": "1.20.0", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.20.0.tgz", - "integrity": "sha512-d25IRcdooS278Cijlp8J8A5fLQZ+/aY3dKRJvgX5yjXA69N0huIUdnh3xXSgn+LsQ9DCNmB7Ngof3eY630jgdA==", + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.21.0.tgz", + "integrity": "sha512-yDGVs9qaaW6WiefgR7wH1CGt9D6D/X4U3jNpIzH0FjjrrWLCOYQo78Tu3SkW8X+/kWlBpj49iWf3QNSxhYc12Q==", "dev": true, "requires": { "colors": "1.4.0", diff --git a/tests/package.json b/tests/package.json index 1699c1fa..25e8e101 100644 --- a/tests/package.json +++ b/tests/package.json @@ -1,6 +1,6 @@ { "devDependencies": { - "@playwright/test": "^1.20.0", + "@playwright/test": "~1.21.0", "@types/dockerode": "^3.3.0", "axios": "^0.24.0", "dockerode": "^3.3.1", diff --git a/uploader/tsconfig.json b/uploader/tsconfig.json index 6972715f..5f6d4c8e 100644 --- a/uploader/tsconfig.json +++ b/uploader/tsconfig.json @@ -3,18 +3,18 @@ "experimentalDecorators": true, /* Basic Options */ // "incremental": true, /* Enable incremental compilation */ - "target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */ + "target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */ "downlevelIteration": true, - "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */ + "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */ // "lib": [], /* Specify library files to be included in the compilation. */ - "allowJs": true, /* Allow javascript files to be compiled. */ + "allowJs": true, /* Allow javascript files to be compiled. */ // "checkJs": true, /* Report errors in .js files. */ // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ // "declaration": true, /* Generates corresponding '.d.ts' file. */ // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ - "sourceMap": true, /* Generates corresponding '.map' file. */ + "sourceMap": true, /* Generates corresponding '.map' file. */ // "outFile": "./", /* Concatenate and emit output to single file. */ - "outDir": "./dist", /* Redirect output structure to the directory. */ + "outDir": "./dist", /* Redirect output structure to the directory. */ // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ // "composite": true, /* Enable project compilation */ // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ @@ -23,50 +23,50 @@ // "importHelpers": true, /* Import emit helpers from 'tslib'. */ // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ - /* Strict Type-Checking Options */ - "strict": true, /* Enable all strict type-checking options. */ + "strict": true, /* Enable all strict type-checking options. */ // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ // "strictNullChecks": true, /* Enable strict null checks. */ // "strictFunctionTypes": true, /* Enable strict checking of function types. */ // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ - "noImplicitThis": false, /* Raise error on 'this' expressions with an implied 'any' type. */ // Disabled because of sifrr server that is monkey patching HttpResponse + "noImplicitThis": false, /* Raise error on 'this' expressions with an implied 'any' type. */ // Disabled because of sifrr server that is monkey patching HttpResponse // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ - /* Additional Checks */ // "noUnusedLocals": true, /* Report errors on unused locals. */ // "noUnusedParameters": true, /* Report errors on unused parameters. */ - "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ - "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ - + "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ + "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ /* Module Resolution Options */ - "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ - "baseUrl": ".", /* Base directory to resolve non-absolute module names. */ - "paths": { - "_Controller/*": ["src/Controller/*"], - "_Model/*": ["src/Model/*"], - "_Enum/*": ["src/Enum/*"] - }, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ + "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ + //"baseUrl": ".", /* Base directory to resolve non-absolute module names. */ + // "paths": { + // "_Controller/*": [ + // "src/Controller/*" + // ], + // "_Model/*": [ + // "src/Model/*" + // ], + // "_Enum/*": [ + // "src/Enum/*" + // ] + // }, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ // "typeRoots": [], /* List of folders to include type definitions from. */ // "types": [], /* Type declaration files to be included in compilation. */ // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ - "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ + "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ - /* Source Map Options */ // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ - /* Experimental Options */ // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ - /* Advanced Options */ - "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ + "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ } -} +} \ No newline at end of file