Merge branch 'develop' into codeAPI

This commit is contained in:
César Cardinale 2022-04-13 17:00:55 +02:00 committed by GitHub
commit d71b36d0fe
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
183 changed files with 1645 additions and 1330 deletions

View File

@ -21,6 +21,7 @@ jobs:
working-directory: tests working-directory: tests
- name: Install Playwright - name: Install Playwright
run: npx playwright install --with-deps run: npx playwright install --with-deps
working-directory: tests
- name: 'Setup .env file' - name: 'Setup .env file'
run: cp .env.template .env run: cp .env.template .env
- name: Install messages dependencies - name: Install messages dependencies

View File

@ -26,6 +26,9 @@
"rules": { "rules": {
"no-unused-vars": "off", "no-unused-vars": "off",
"@typescript-eslint/no-explicit-any": "error", "@typescript-eslint/no-explicit-any": "error",
"@typescript-eslint/no-unused-vars": [
"error"
],
"no-throw-literal": "error" "no-throw-literal": "error"
} }
} }

View File

@ -45,7 +45,6 @@
"busboy": "^0.3.1", "busboy": "^0.3.1",
"circular-json": "^0.5.9", "circular-json": "^0.5.9",
"debug": "^4.3.1", "debug": "^4.3.1",
"generic-type-guard": "^3.2.0",
"google-protobuf": "^3.13.0", "google-protobuf": "^3.13.0",
"grpc": "^1.24.4", "grpc": "^1.24.4",
"ipaddr.js": "^2.0.1", "ipaddr.js": "^2.0.1",
@ -56,7 +55,7 @@
"redis": "^3.1.2", "redis": "^3.1.2",
"uWebSockets.js": "uNetworking/uWebSockets.js#v18.5.0", "uWebSockets.js": "uNetworking/uWebSockets.js#v18.5.0",
"uuidv4": "^6.0.7", "uuidv4": "^6.0.7",
"zod": "^3.12.0" "zod": "^3.14.3"
}, },
"devDependencies": { "devDependencies": {
"@types/busboy": "^0.2.3", "@types/busboy": "^0.2.3",

View File

@ -48,7 +48,7 @@ export class DebugController {
return obj; return obj;
} else if (value instanceof Set) { } else if (value instanceof Set) {
const obj: Array<unknown> = []; const obj: Array<unknown> = [];
for (const [setKey, setValue] of value.entries()) { for (const setValue of value.values()) {
obj.push(setValue); obj.push(setValue);
} }
return obj; return obj;

View File

@ -1,5 +1,5 @@
import { App } from "../Server/sifrr.server"; import { App } from "../Server/sifrr.server";
import { HttpRequest, HttpResponse } from "uWebSockets.js"; import { HttpResponse } from "uWebSockets.js";
import { register, collectDefaultMetrics } from "prom-client"; import { register, collectDefaultMetrics } from "prom-client";
export class PrometheusController { export class PrometheusController {
@ -11,7 +11,7 @@ export class PrometheusController {
this.App.get("/metrics", this.metrics.bind(this)); 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.writeHeader("Content-Type", register.contentType);
res.end(register.metrics()); res.end(register.metrics());
} }

View File

@ -1,7 +1,7 @@
import { PointInterface } from "./Websocket/PointInterface"; import { PointInterface } from "./Websocket/PointInterface";
import { Group } from "./Group"; import { Group } from "./Group";
import { User, UserSocket } from "./User"; import { User, UserSocket } from "./User";
import { PositionInterface } from "_Model/PositionInterface"; import { PositionInterface } from "../Model/PositionInterface";
import { import {
EmoteCallback, EmoteCallback,
EntersCallback, EntersCallback,
@ -9,23 +9,20 @@ import {
LockGroupCallback, LockGroupCallback,
MovesCallback, MovesCallback,
PlayerDetailsUpdatedCallback, PlayerDetailsUpdatedCallback,
} from "_Model/Zone"; } from "../Model/Zone";
import { PositionNotifier } from "./PositionNotifier"; import { PositionNotifier } from "./PositionNotifier";
import { Movable } from "_Model/Movable"; import { Movable } from "../Model/Movable";
import { import {
BatchToPusherMessage,
BatchToPusherRoomMessage, BatchToPusherRoomMessage,
EmoteEventMessage, EmoteEventMessage,
ErrorMessage,
JoinRoomMessage, JoinRoomMessage,
SetPlayerDetailsMessage, SetPlayerDetailsMessage,
SubToPusherRoomMessage, SubToPusherRoomMessage,
VariableMessage,
VariableWithTagMessage, VariableWithTagMessage,
ServerToClientMessage, ServerToClientMessage,
} from "../Messages/generated/messages_pb"; } from "../Messages/generated/messages_pb";
import { ProtobufUtils } from "../Model/Websocket/ProtobufUtils"; import { ProtobufUtils } from "../Model/Websocket/ProtobufUtils";
import { RoomSocket, ZoneSocket } from "src/RoomManager"; import { RoomSocket, ZoneSocket } from "../RoomManager";
import { Admin } from "../Model/Admin"; import { Admin } from "../Model/Admin";
import { adminApi } from "../Services/AdminApi"; import { adminApi } from "../Services/AdminApi";
import { isMapDetailsData, MapDetailsData } from "../Messages/JsonMessages/MapDetailsData"; import { isMapDetailsData, MapDetailsData } from "../Messages/JsonMessages/MapDetailsData";
@ -36,7 +33,6 @@ import { ADMIN_API_URL } from "../Enum/EnvironmentVariable";
import { LocalUrlError } from "../Services/LocalUrlError"; import { LocalUrlError } from "../Services/LocalUrlError";
import { emitErrorOnRoomSocket } from "../Services/MessageHelpers"; import { emitErrorOnRoomSocket } from "../Services/MessageHelpers";
import { VariableError } from "../Services/VariableError"; import { VariableError } from "../Services/VariableError";
import { isRoomRedirect } from "../Messages/JsonMessages/RoomRedirect";
export type ConnectCallback = (user: User, group: Group) => void; export type ConnectCallback = (user: User, group: Group) => void;
export type DisconnectCallback = (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 { private searchClosestAvailableUserOrGroup(user: User): User | Group | null {
let minimumDistanceFound: number = Math.max(this.minDistance, this.groupRadius); let minimumDistanceFound: number = Math.max(this.minDistance, this.groupRadius);
let matchingItem: User | Group | null = null; 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 // Let's only check users that are not part of a group
if (typeof currentUser.group !== "undefined") { if (typeof currentUser.group !== "undefined") {
return; return;
@ -585,12 +581,15 @@ export class GameRoom {
}; };
} }
const result = await adminApi.fetchMapDetails(roomUrl); const result = isMapDetailsData.safeParse(await adminApi.fetchMapDetails(roomUrl));
if (isRoomRedirect(result)) {
console.error("Unexpected room redirect received while querying map details", result); if (result.success) {
throw new Error("Unexpected room redirect received while querying map details"); 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<ITiledMap> | undefined; private mapPromise: Promise<ITiledMap> | undefined;

View File

@ -1,8 +1,8 @@
import { ConnectCallback, DisconnectCallback, GameRoom } from "./GameRoom"; import { ConnectCallback, DisconnectCallback, GameRoom } from "./GameRoom";
import { User } from "./User"; import { User } from "./User";
import { PositionInterface } from "_Model/PositionInterface"; import { PositionInterface } from "../Model/PositionInterface";
import { Movable } from "_Model/Movable"; import { Movable } from "../Model/Movable";
import { PositionNotifier } from "_Model/PositionNotifier"; import { PositionNotifier } from "../Model/PositionNotifier";
import { MAX_PER_GROUP } from "../Enum/EnvironmentVariable"; import { MAX_PER_GROUP } from "../Enum/EnvironmentVariable";
import type { Zone } from "../Model/Zone"; import type { Zone } from "../Model/Zone";

View File

@ -1,4 +1,4 @@
import { PositionInterface } from "_Model/PositionInterface"; import { PositionInterface } from "../Model/PositionInterface";
/** /**
* A physical object that can be placed into a Zone * A physical object that can be placed into a Zone

View File

@ -17,8 +17,8 @@ import {
PlayerDetailsUpdatedCallback, PlayerDetailsUpdatedCallback,
Zone, Zone,
} from "./Zone"; } from "./Zone";
import { Movable } from "_Model/Movable"; import { Movable } from "../Model/Movable";
import { PositionInterface } from "_Model/PositionInterface"; import { PositionInterface } from "../Model/PositionInterface";
import { ZoneSocket } from "../RoomManager"; import { ZoneSocket } from "../RoomManager";
import { User } from "../Model/User"; import { User } from "../Model/User";
import { EmoteEventMessage, SetPlayerDetailsMessage } from "../Messages/generated/messages_pb"; import { EmoteEventMessage, SetPlayerDetailsMessage } from "../Messages/generated/messages_pb";

View File

@ -1,8 +1,8 @@
import { Group } from "./Group"; import { Group } from "./Group";
import { PointInterface } from "./Websocket/PointInterface"; import { PointInterface } from "./Websocket/PointInterface";
import { Zone } from "_Model/Zone"; import { Zone } from "../Model/Zone";
import { Movable } from "_Model/Movable"; import { Movable } from "../Model/Movable";
import { PositionNotifier } from "_Model/PositionNotifier"; import { PositionNotifier } from "../Model/PositionNotifier";
import { ServerDuplexStream } from "grpc"; import { ServerDuplexStream } from "grpc";
import { import {
BatchMessage, BatchMessage,
@ -14,7 +14,7 @@ import {
SetPlayerDetailsMessage, SetPlayerDetailsMessage,
SubMessage, SubMessage,
} from "../Messages/generated/messages_pb"; } 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"; import { BoolValue, UInt32Value } from "google-protobuf/google/protobuf/wrappers_pb";
export type UserSocket = ServerDuplexStream<PusherToBackMessage, ServerToClientMessage>; export type UserSocket = ServerDuplexStream<PusherToBackMessage, ServerToClientMessage>;

View File

@ -1,11 +1,10 @@
import * as tg from "generic-type-guard"; import { z } from "zod";
export const isItemEventMessageInterface = new tg.IsInterface() export const isItemEventMessageInterface = z.object({
.withProperties({ itemId: z.number(),
itemId: tg.isNumber, event: z.string(),
event: tg.isString, state: z.unknown(),
state: tg.isUnknown, parameters: z.unknown(),
parameters: tg.isUnknown, });
})
.get(); export type ItemEventMessageInterface = z.infer<typeof isItemEventMessageInterface>;
export type ItemEventMessageInterface = tg.GuardedType<typeof isItemEventMessageInterface>;

View File

@ -1,18 +1,10 @@
import * as tg from "generic-type-guard"; import { z } from "zod";
/*export interface PointInterface { export const isPointInterface = z.object({
readonly x: number; x: z.number(),
readonly y: number; y: z.number(),
readonly direction: string; direction: z.string(),
readonly moving: boolean; moving: z.boolean(),
}*/ });
export const isPointInterface = new tg.IsInterface() export type PointInterface = z.infer<typeof isPointInterface>;
.withProperties({
x: tg.isNumber,
y: tg.isNumber,
direction: tg.isString,
moving: tg.isBoolean,
})
.get();
export type PointInterface = tg.GuardedType<typeof isPointInterface>;

View File

@ -5,10 +5,10 @@ import {
PointMessage, PointMessage,
PositionMessage, PositionMessage,
} from "../../Messages/generated/messages_pb"; } from "../../Messages/generated/messages_pb";
import { CharacterLayer } from "_Model/Websocket/CharacterLayer"; import { CharacterLayer } from "../../Model/Websocket/CharacterLayer";
import Direction = PositionMessage.Direction; import Direction = PositionMessage.Direction;
import { ItemEventMessageInterface } from "_Model/Websocket/ItemEventMessage"; import { ItemEventMessageInterface } from "../../Model/Websocket/ItemEventMessage";
import { PositionInterface } from "_Model/PositionInterface"; import { PositionInterface } from "../../Model/PositionInterface";
export class ProtobufUtils { export class ProtobufUtils {
public static toPositionMessage(point: PointInterface): PositionMessage { public static toPositionMessage(point: PointInterface): PositionMessage {

View File

@ -1,5 +1,5 @@
import { User } from "./User"; import { User } from "./User";
import { PositionInterface } from "_Model/PositionInterface"; import { PositionInterface } from "../Model/PositionInterface";
import { Movable } from "./Movable"; import { Movable } from "./Movable";
import { Group } from "./Group"; import { Group } from "./Group";
import { ZoneSocket } from "../RoomManager"; import { ZoneSocket } from "../RoomManager";
@ -71,6 +71,7 @@ export class Zone {
/** /**
* Notify listeners of this zone that this user entered * 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) { private notifyEnter(thing: Movable, oldZone: Zone | null, position: PositionInterface) {
for (const listener of this.listeners) { for (const listener of this.listeners) {
this.onEnters(thing, oldZone, listener); this.onEnters(thing, oldZone, listener);

View File

@ -15,7 +15,6 @@ import {
EmptyMessage, EmptyMessage,
ItemEventMessage, ItemEventMessage,
JoinRoomMessage, JoinRoomMessage,
PlayGlobalMessage,
PusherToBackMessage, PusherToBackMessage,
QueryJitsiJwtMessage, QueryJitsiJwtMessage,
RefreshRoomPromptMessage, RefreshRoomPromptMessage,

View File

@ -18,12 +18,21 @@ class AdminApi {
params, params,
}); });
if (!isMapDetailsData(res.data) && !isRoomRedirect(res.data)) { const mapDetailData = isMapDetailsData.safeParse(res.data);
console.error("Unexpected answer from the /api/map admin endpoint.", res.data); const roomRedirect = isRoomRedirect.safeParse(res.data);
throw new Error("Unexpected answer from the /api/map admin endpoint.");
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.");
} }
} }

View File

@ -1,5 +1,4 @@
import { import {
BatchMessage,
BatchToPusherMessage, BatchToPusherMessage,
BatchToPusherRoomMessage, BatchToPusherRoomMessage,
ErrorMessage, ErrorMessage,
@ -7,7 +6,7 @@ import {
SubToPusherMessage, SubToPusherMessage,
SubToPusherRoomMessage, SubToPusherRoomMessage,
} from "../Messages/generated/messages_pb"; } from "../Messages/generated/messages_pb";
import { UserSocket } from "_Model/User"; import { UserSocket } from "../Model/User";
import { RoomSocket, ZoneSocket } from "../RoomManager"; import { RoomSocket, ZoneSocket } from "../RoomManager";
function getMessageFromError(error: unknown): string { function getMessageFromError(error: unknown): string {

View File

@ -4,10 +4,12 @@ import { VariablesRepositoryInterface } from "./VariablesRepositoryInterface";
* Mock class in charge of NOT saving/loading variables from the data store * Mock class in charge of NOT saving/loading variables from the data store
*/ */
export class VoidVariablesRepository implements VariablesRepositoryInterface { export class VoidVariablesRepository implements VariablesRepositoryInterface {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
loadVariables(roomUrl: string): Promise<{ [key: string]: string }> { loadVariables(roomUrl: string): Promise<{ [key: string]: string }> {
return Promise.resolve({}); return Promise.resolve({});
} }
// eslint-disable-next-line @typescript-eslint/no-unused-vars
saveVariable(roomUrl: string, key: string, value: string): Promise<number> { saveVariable(roomUrl: string, key: string, value: string): Promise<number> {
return Promise.resolve(0); return Promise.resolve(0);
} }

View File

@ -2,7 +2,6 @@ import { GameRoom } from "../Model/GameRoom";
import { import {
ItemEventMessage, ItemEventMessage,
ItemStateMessage, ItemStateMessage,
PlayGlobalMessage,
PointMessage, PointMessage,
RoomJoinedMessage, RoomJoinedMessage,
ServerToClientMessage, ServerToClientMessage,
@ -35,12 +34,10 @@ import {
FollowAbortMessage, FollowAbortMessage,
VariableMessage, VariableMessage,
BatchToPusherRoomMessage, BatchToPusherRoomMessage,
SubToPusherRoomMessage,
SetPlayerDetailsMessage, SetPlayerDetailsMessage,
PlayerDetailsUpdatedMessage, PlayerDetailsUpdatedMessage,
GroupUsersUpdateMessage, GroupUsersUpdateMessage,
LockGroupPromptMessage, LockGroupPromptMessage,
RoomMessage,
} from "../Messages/generated/messages_pb"; } from "../Messages/generated/messages_pb";
import { User, UserSocket } from "../Model/User"; import { User, UserSocket } from "../Model/User";
import { ProtobufUtils } from "../Model/Websocket/ProtobufUtils"; import { ProtobufUtils } from "../Model/Websocket/ProtobufUtils";
@ -60,9 +57,9 @@ import { JITSI_URL } from "../Enum/EnvironmentVariable";
import { clientEventsEmitter } from "./ClientEventsEmitter"; import { clientEventsEmitter } from "./ClientEventsEmitter";
import { gaugeManager } from "./GaugeManager"; import { gaugeManager } from "./GaugeManager";
import { RoomSocket, ZoneSocket } from "../RoomManager"; import { RoomSocket, ZoneSocket } from "../RoomManager";
import { Zone } from "_Model/Zone"; import { Zone } from "../Model/Zone";
import Debug from "debug"; import Debug from "debug";
import { Admin } from "_Model/Admin"; import { Admin } from "../Model/Admin";
import crypto from "crypto"; import crypto from "crypto";
const debug = Debug("sockermanager"); const debug = Debug("sockermanager");

View File

@ -2,7 +2,7 @@
* Handles variables shared between the scripting API and the server. * Handles variables shared between the scripting API and the server.
*/ */
import { ITiledMap, ITiledMapLayer, ITiledMapObject } from "@workadventure/tiled-map-type-guard/dist"; 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 { variablesRepository } from "./Repository/VariablesRepository";
import { redisClient } from "./RedisClient"; import { redisClient } from "./RedisClient";
import { VariableError } from "./VariableError"; import { VariableError } from "./VariableError";

View File

@ -1,11 +1,12 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import "jasmine"; import "jasmine";
import { ConnectCallback, DisconnectCallback, GameRoom } from "../src/Model/GameRoom"; import { ConnectCallback, DisconnectCallback, GameRoom } from "../src/Model/GameRoom";
import { Point } from "../src/Model/Websocket/MessageUserPosition"; import { Point } from "../src/Model/Websocket/MessageUserPosition";
import { Group } from "../src/Model/Group"; 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 { JoinRoomMessage, PositionMessage } from "../src/Messages/generated/messages_pb";
import Direction = PositionMessage.Direction; import Direction = PositionMessage.Direction;
import { EmoteCallback } from "_Model/Zone"; import { EmoteCallback } from "../src/Model/Zone";
function createMockUser(userId: number): User { function createMockUser(userId: number): User {
return { return {

View File

@ -1,4 +1,3 @@
import { arrayIntersect } from "../src/Services/ArrayHelper";
import { mapFetcher } from "../src/Services/MapFetcher"; import { mapFetcher } from "../src/Services/MapFetcher";
describe("MapFetcher", () => { describe("MapFetcher", () => {

View File

@ -1,9 +1,10 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import "jasmine"; import "jasmine";
import { PositionNotifier } from "../src/Model/PositionNotifier"; import { PositionNotifier } from "../src/Model/PositionNotifier";
import { User, UserSocket } from "../src/Model/User"; import { User, UserSocket } from "../src/Model/User";
import { Zone } from "_Model/Zone"; import { Zone } from "../src/Model/Zone";
import { Movable } from "_Model/Movable"; import { Movable } from "../src/Model/Movable";
import { PositionInterface } from "_Model/PositionInterface"; import { PositionInterface } from "../src/Model/PositionInterface";
import { ZoneSocket } from "../src/RoomManager"; import { ZoneSocket } from "../src/RoomManager";
describe("PositionNotifier", () => { describe("PositionNotifier", () => {

View File

@ -3,18 +3,18 @@
"experimentalDecorators": true, "experimentalDecorators": true,
/* Basic Options */ /* Basic Options */
// "incremental": true, /* Enable incremental compilation */ // "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, "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. */ // "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. */ // "checkJs": true, /* Report errors in .js files. */
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
// "declaration": true, /* Generates corresponding '.d.ts' file. */ // "declaration": true, /* Generates corresponding '.d.ts' file. */
// "declarationMap": true, /* Generates a sourcemap for each 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. */ // "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. */ // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
// "composite": true, /* Enable project compilation */ // "composite": true, /* Enable project compilation */
// "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
@ -23,50 +23,50 @@
// "importHelpers": true, /* Import emit helpers from 'tslib'. */ // "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'. */ // "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'). */ // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
/* Strict Type-Checking Options */ /* 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. */ // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
// "strictNullChecks": true, /* Enable strict null checks. */ // "strictNullChecks": true, /* Enable strict null checks. */
// "strictFunctionTypes": true, /* Enable strict checking of function types. */ // "strictFunctionTypes": true, /* Enable strict checking of function types. */
// "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
// "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ // "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. */ // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
/* Additional Checks */ /* Additional Checks */
// "noUnusedLocals": true, /* Report errors on unused locals. */ // "noUnusedLocals": true, /* Report errors on unused locals. */
// "noUnusedParameters": true, /* Report errors on unused parameters. */ // "noUnusedParameters": true, /* Report errors on unused parameters. */
"noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
"noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
/* Module Resolution Options */ /* Module Resolution Options */
"moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
"baseUrl": ".", /* Base directory to resolve non-absolute module names. */ // "baseUrl": ".", /* Base directory to resolve non-absolute module names. */
"paths": { // "paths": {
"_Controller/*": ["src/Controller/*"], // "_Controller/*": [
"_Model/*": ["src/Model/*"], // "src/Controller/*"
"_Enum/*": ["src/Enum/*"] // ],
}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ // "_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. */ // "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. */ // "typeRoots": [], /* List of folders to include type definitions from. */
// "types": [], /* Type declaration files to be included in compilation. */ // "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. */ // "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. */ // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
/* Source Map Options */ /* Source Map Options */
// "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ // "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. */ // "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. */ // "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. */ // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
/* Experimental Options */ /* Experimental Options */
// "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
/* Advanced Options */ /* Advanced Options */
"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
} }
} }

View File

@ -981,7 +981,7 @@ gauge@^3.0.0:
strip-ansi "^6.0.1" strip-ansi "^6.0.1"
wide-align "^1.1.2" 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" version "3.5.0"
resolved "https://registry.yarnpkg.com/generic-type-guard/-/generic-type-guard-3.5.0.tgz#39de9f8fceee65d79e7540959f0e7b23210c07b6" resolved "https://registry.yarnpkg.com/generic-type-guard/-/generic-type-guard-3.5.0.tgz#39de9f8fceee65d79e7540959f0e7b23210c07b6"
integrity sha512-OpgXv/sbRobhFboaSyN/Tsh97Sxt5pcfLLxCiYZgYIIWFFp+kn2EzAXiaQZKEVRlq1rOE/zh8cYhJXEwplbJiQ== 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" resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50"
integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q== integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==
zod@^3.12.0: zod@^3.14.3:
version "3.14.2" version "3.14.3"
resolved "https://registry.yarnpkg.com/zod/-/zod-3.14.2.tgz#0b4ed79085c471adce0e7f2c0a4fbb5ddc516ba2" resolved "https://registry.yarnpkg.com/zod/-/zod-3.14.3.tgz#60e86341c05883c281fe96a0e79acea48a09f123"
integrity sha512-iF+wrtzz7fQfkmn60PG6XFxaWBhYYKzp2i+nv24WbLUWb2JjymdkHlzBwP0erpc78WotwP5g9AAu7Sk8GWVVNw== integrity sha512-OzwRCSXB1+/8F6w6HkYHdbuWysYWnAF4fkRgKDcSFc54CE+Sv0rHXKfeNUReGCrHukm1LNpi6AYeXotznhYJbQ==

View File

@ -12,8 +12,8 @@
"noImplicitReturns": true, "noImplicitReturns": true,
"noFallthroughCasesInSwitch": true, "noFallthroughCasesInSwitch": true,
"moduleResolution": "node", "moduleResolution": "node",
"baseUrl": ".", //"baseUrl": ".",
"paths": {}, //"paths": {},
"esModuleInterop": true, "esModuleInterop": true,
"forceConsistentCasingInFileNames": true, "forceConsistentCasingInFileNames": true,
"skipLibCheck": true "skipLibCheck": true

View File

@ -5,7 +5,7 @@
"useDefineForClassFields": true, "useDefineForClassFields": true,
"module": "esnext", "module": "esnext",
"resolveJsonModule": true, "resolveJsonModule": true,
"baseUrl": ".", //"baseUrl": ".",
/** /**
* Typecheck JS in `.svelte` and `.js` files by default. * Typecheck JS in `.svelte` and `.js` files by default.
* Disable checkJs if you'd like to use dynamic types in JS. * Disable checkJs if you'd like to use dynamic types in JS.
@ -15,10 +15,23 @@
"allowJs": true, "allowJs": true,
"checkJs": true, "checkJs": true,
"paths": { "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"], "include": [
"references": [{ "path": "./tsconfig.node.json" }] "src/**/*.d.ts",
"src/**/*.ts",
"src/**/*.js",
"src/**/*.svelte"
],
"references": [
{
"path": "./tsconfig.node.json"
}
]
} }

View File

@ -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 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). 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. 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: 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`: The "data" part of the message is defined in `front/src/Api/Events/ChatEvent.ts`:
```typescript ```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. * A message sent from the iFrame to the game to add a message in the chat.
*/ */
export type ChatEvent = tg.GuardedType<typeof isChatEvent>; export type ChatEvent = z.infer<typeof isChatEvent>;
``` ```
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. 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 The advantage of this technique is that, **at runtime**, WorkAdventure can verify that the JSON message received

View File

@ -27,8 +27,8 @@ module.exports = {
], ],
"overrides": [ "overrides": [
{ {
"files": ["*.svelte"], "files": ["*.svelte"],
"processor": "svelte3/svelte3" "processor": "svelte3/svelte3"
} }
], ],
"rules": { "rules": {
@ -36,6 +36,7 @@ module.exports = {
"eol-last": ["error", "always"], "eol-last": ["error", "always"],
"@typescript-eslint/no-explicit-any": "error", "@typescript-eslint/no-explicit-any": "error",
"no-throw-literal": "error", "no-throw-literal": "error",
"@typescript-eslint/no-unused-vars": ["error"],
// TODO: remove those ignored rules and write a stronger code! // TODO: remove those ignored rules and write a stronger code!
"@typescript-eslint/no-unsafe-call": "off", "@typescript-eslint/no-unsafe-call": "off",
"@typescript-eslint/restrict-plus-operands": "off", "@typescript-eslint/restrict-plus-operands": "off",

View File

@ -44,7 +44,7 @@
"cross-env": "^7.0.3", "cross-env": "^7.0.3",
"deep-copy-ts": "^0.5.0", "deep-copy-ts": "^0.5.0",
"easystarjs": "^0.4.4", "easystarjs": "^0.4.4",
"generic-type-guard": "^3.4.2", "fast-deep-equal": "^3.1.3",
"google-protobuf": "^3.13.0", "google-protobuf": "^3.13.0",
"phaser": "3.55.1", "phaser": "3.55.1",
"phaser-animated-tiles": "workadventure/phaser-animated-tiles#da68bbededd605925621dd4f03bd27e69284b254", "phaser-animated-tiles": "workadventure/phaser-animated-tiles#da68bbededd605925621dd4f03bd27e69284b254",
@ -62,7 +62,7 @@
"ts-proto": "^1.96.0", "ts-proto": "^1.96.0",
"typesafe-i18n": "^2.59.0", "typesafe-i18n": "^2.59.0",
"uuidv4": "^6.2.10", "uuidv4": "^6.2.10",
"zod": "^3.11.6" "zod": "^3.14.3"
}, },
"scripts": { "scripts": {
"start": "run-p templater serve watch-iframe-api svelte-check-watch typesafe-i18n-watch", "start": "run-p templater serve watch-iframe-api svelte-check-watch typesafe-i18n-watch",

View File

@ -9,5 +9,8 @@
"license": "MIT", "license": "MIT",
"publishConfig": { "publishConfig": {
"access": "public" "access": "public"
},
"dependencies": {
"rxjs": "^6.6.3"
} }
} }

View File

@ -1,12 +1,10 @@
import * as tg from "generic-type-guard"; import { z } from "zod";
export const isActionsMenuActionClickedEvent = new tg.IsInterface() export const isActionsMenuActionClickedEvent = z.object({
.withProperties({ id: z.number(),
id: tg.isNumber, actionName: z.string(),
actionName: tg.isString, });
})
.get();
export type ActionsMenuActionClickedEvent = tg.GuardedType<typeof isActionsMenuActionClickedEvent>; export type ActionsMenuActionClickedEvent = z.infer<typeof isActionsMenuActionClickedEvent>;
export type ActionsMenuActionClickedEventCallback = (event: ActionsMenuActionClickedEvent) => void; export type ActionsMenuActionClickedEventCallback = (event: ActionsMenuActionClickedEvent) => void;

View File

@ -1,12 +1,10 @@
import * as tg from "generic-type-guard"; import { z } from "zod";
export const isAddActionsMenuKeyToRemotePlayerEvent = new tg.IsInterface() export const isAddActionsMenuKeyToRemotePlayerEvent = z.object({
.withProperties({ id: z.number(),
id: tg.isNumber, actionKey: z.string(),
actionKey: tg.isString, });
})
.get();
export type AddActionsMenuKeyToRemotePlayerEvent = tg.GuardedType<typeof isAddActionsMenuKeyToRemotePlayerEvent>; export type AddActionsMenuKeyToRemotePlayerEvent = z.infer<typeof isAddActionsMenuKeyToRemotePlayerEvent>;
export type AddActionsMenuKeyToRemotePlayerEventCallback = (event: AddActionsMenuKeyToRemotePlayerEvent) => void; export type AddActionsMenuKeyToRemotePlayerEventCallback = (event: AddActionsMenuKeyToRemotePlayerEvent) => void;

View File

@ -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. * 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<typeof isButtonClickedEvent>; export type ButtonClickedEvent = z.infer<typeof isButtonClickedEvent>;

View File

@ -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. * A message sent from the iFrame to the game to make the camera follow player.
*/ */
export type CameraFollowPlayerEvent = tg.GuardedType<typeof isCameraFollowPlayerEvent>; export type CameraFollowPlayerEvent = z.infer<typeof isCameraFollowPlayerEvent>;

View File

@ -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. * A message sent from the iFrame to the game to change the camera position.
*/ */
export type CameraSetEvent = tg.GuardedType<typeof isCameraSetEvent>; export type CameraSetEvent = z.infer<typeof isCameraSetEvent>;

View File

@ -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. * A message sent from the game to the iFrame when a user enters or leaves a layer.
*/ */
export type ChangeLayerEvent = tg.GuardedType<typeof isChangeLayerEvent>; export type ChangeLayerEvent = z.infer<typeof isChangeLayerEvent>;

View File

@ -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. * A message sent from the game to the iFrame when a user enters or leaves a zone.
*/ */
export type ChangeZoneEvent = tg.GuardedType<typeof isChangeZoneEvent>; export type ChangeZoneEvent = z.infer<typeof isChangeZoneEvent>;

View File

@ -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. * A message sent from the iFrame to the game to add a message in the chat.
*/ */
export type ChatEvent = tg.GuardedType<typeof isChatEvent>; export type ChatEvent = z.infer<typeof isChatEvent>;

View File

@ -1,12 +1,10 @@
import * as tg from "generic-type-guard"; import { z } from "zod";
export const isCloseCoWebsite = new tg.IsInterface() export const isCloseCoWebsite = z.object({
.withProperties({ id: z.optional(z.string()),
id: tg.isOptional(tg.isString), });
})
.get();
/** /**
* A message sent from the iFrame to the game to add a message in the chat. * A message sent from the iFrame to the game to add a message in the chat.
*/ */
export type CloseCoWebsiteEvent = tg.GuardedType<typeof isCloseCoWebsite>; export type CloseCoWebsiteEvent = z.infer<typeof isCloseCoWebsite>;

View File

@ -1,12 +1,10 @@
import * as tg from "generic-type-guard"; import { z } from "zod";
export const isClosePopupEvent = new tg.IsInterface() export const isClosePopupEvent = z.object({
.withProperties({ popupId: z.number(),
popupId: tg.isNumber, });
})
.get();
/** /**
* A message sent from the iFrame to the game to add a message in the chat. * A message sent from the iFrame to the game to add a message in the chat.
*/ */
export type ClosePopupEvent = tg.GuardedType<typeof isClosePopupEvent>; export type ClosePopupEvent = z.infer<typeof isClosePopupEvent>;

View File

@ -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. * A message sent from the iFrame to the game to dynamically set the outline of the player.
*/ */
export type ColorEvent = tg.GuardedType<typeof isColorEvent>; export type ColorEvent = z.infer<typeof isColorEvent>;

View File

@ -1,52 +1,43 @@
import * as tg from "generic-type-guard"; import { z } from "zod";
export const isRectangle = new tg.IsInterface() export const isRectangle = z.object({
.withProperties({ x: z.number(),
x: tg.isNumber, y: z.number(),
y: tg.isNumber, width: z.number(),
width: tg.isNumber, height: z.number(),
height: tg.isNumber, });
})
.get();
export const isEmbeddedWebsiteEvent = new tg.IsInterface() // TODO: make a variation that is all optional (except for the name)
.withProperties({ export type Rectangle = z.infer<typeof isRectangle>;
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();
export const isCreateEmbeddedWebsiteEvent = new tg.IsInterface() export const isEmbeddedWebsiteEvent = z.object({
.withProperties({ name: z.string(),
name: tg.isString, url: z.optional(z.string()),
url: tg.isString, visible: z.optional(z.boolean()),
position: isRectangle, allowApi: z.optional(z.boolean()),
}) allow: z.optional(z.string()),
.withOptionalProperties({ x: z.optional(z.number()),
visible: tg.isBoolean, y: z.optional(z.number()),
allowApi: tg.isBoolean, width: z.optional(z.number()),
allow: tg.isString, height: z.optional(z.number()),
origin: tg.isSingletonStringUnion("player", "map"), origin: z.optional(z.enum(["player", "map"])),
scale: tg.isNumber, scale: z.optional(z.number()),
}) });
.get();
/** /**
* A message sent from the iFrame to the game to modify an embedded website * A message sent from the iFrame to the game to modify an embedded website
*/ */
export type ModifyEmbeddedWebsiteEvent = tg.GuardedType<typeof isEmbeddedWebsiteEvent>; export type ModifyEmbeddedWebsiteEvent = z.infer<typeof isEmbeddedWebsiteEvent>;
export type CreateEmbeddedWebsiteEvent = tg.GuardedType<typeof isCreateEmbeddedWebsiteEvent>; export const isCreateEmbeddedWebsiteEvent = z.object({
// TODO: make a variation that is all optional (except for the name) name: z.string(),
export type Rectangle = tg.GuardedType<typeof isRectangle>; 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<typeof isCreateEmbeddedWebsiteEvent>;

View File

@ -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. * 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<typeof isEnterLeaveEvent>; export type EnterLeaveEvent = z.infer<typeof isEnterLeaveEvent>;

View File

@ -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 * A message sent from the game to the iFrame when the gameState is received by the script
*/ */
export type GameStateEvent = tg.GuardedType<typeof isGameStateEvent>; export type GameStateEvent = z.infer<typeof isGameStateEvent>;

View File

@ -1,12 +1,10 @@
import * as tg from "generic-type-guard"; import { z } from "zod";
export const isGoToPageEvent = new tg.IsInterface() export const isGoToPageEvent = z.object({
.withProperties({ url: z.string(),
url: tg.isString, });
})
.get();
/** /**
* A message sent from the iFrame to the game to add a message in the chat. * A message sent from the iFrame to the game to add a message in the chat.
*/ */
export type GoToPageEvent = tg.GuardedType<typeof isGoToPageEvent>; export type GoToPageEvent = z.infer<typeof isGoToPageEvent>;

View File

@ -1,19 +1,17 @@
import * as tg from "generic-type-guard"; import { z } from "zod";
export const isHasPlayerMovedEvent = new tg.IsInterface() export const isHasPlayerMovedEvent = z.object({
.withProperties({ direction: z.enum(["right", "left", "up", "down"]),
direction: tg.isElementOf("right", "left", "up", "down"), moving: z.boolean(),
moving: tg.isBoolean, x: z.number(),
x: tg.isNumber, y: z.number(),
y: tg.isNumber, oldX: z.optional(z.number()),
oldX: tg.isOptional(tg.isNumber), oldY: z.optional(z.number()),
oldY: tg.isOptional(tg.isNumber), });
})
.get();
/** /**
* A message sent from the game to the iFrame to notify a movement from the current player. * A message sent from the game to the iFrame to notify a movement from the current player.
*/ */
export type HasPlayerMovedEvent = tg.GuardedType<typeof isHasPlayerMovedEvent>; export type HasPlayerMovedEvent = z.infer<typeof isHasPlayerMovedEvent>;
export type HasPlayerMovedEventCallback = (event: HasPlayerMovedEvent) => void; export type HasPlayerMovedEventCallback = (event: HasPlayerMovedEvent) => void;

View File

@ -1,46 +1,44 @@
import * as tg from "generic-type-guard"; import { z } from "zod";
import type { ButtonClickedEvent } from "./ButtonClickedEvent"; import type { ButtonClickedEvent } from "./ButtonClickedEvent";
import type { ChatEvent } from "./ChatEvent"; import { isChatEvent } from "./ChatEvent";
import type { ClosePopupEvent } from "./ClosePopupEvent"; import { isClosePopupEvent } from "./ClosePopupEvent";
import type { EnterLeaveEvent } from "./EnterLeaveEvent"; import type { EnterLeaveEvent } from "./EnterLeaveEvent";
import type { GoToPageEvent } from "./GoToPageEvent"; import { isGoToPageEvent } from "./GoToPageEvent";
import type { LoadPageEvent } from "./LoadPageEvent"; import { isLoadPageEvent } from "./LoadPageEvent";
import { isCoWebsite, isOpenCoWebsiteEvent } from "./OpenCoWebsiteEvent"; import { isCoWebsite, isOpenCoWebsiteEvent } from "./OpenCoWebsiteEvent";
import type { OpenPopupEvent } from "./OpenPopupEvent"; import { isOpenPopupEvent } from "./OpenPopupEvent";
import type { OpenTabEvent } from "./OpenTabEvent"; import { isOpenTabEvent } from "./OpenTabEvent";
import type { UserInputChatEvent } from "./UserInputChatEvent"; import type { UserInputChatEvent } from "./UserInputChatEvent";
import type { LayerEvent } from "./LayerEvent"; import { isLayerEvent } from "./LayerEvent";
import type { SetPropertyEvent } from "./setPropertyEvent"; import { isSetPropertyEvent } from "./setPropertyEvent";
import type { LoadSoundEvent } from "./LoadSoundEvent"; import { isLoadSoundEvent } from "./LoadSoundEvent";
import type { PlaySoundEvent } from "./PlaySoundEvent"; import { isPlaySoundEvent } from "./PlaySoundEvent";
import type { StopSoundEvent } from "./StopSoundEvent"; import { isStopSoundEvent } from "./StopSoundEvent";
import type { MenuItemClickedEvent } from "./ui/MenuItemClickedEvent"; import type { MenuItemClickedEvent } from "./ui/MenuItemClickedEvent";
import type { HasPlayerMovedEvent } from "./HasPlayerMovedEvent"; import type { HasPlayerMovedEvent } from "./HasPlayerMovedEvent";
import type { SetTilesEvent } from "./SetTilesEvent"; import { isSetTilesEvent } from "./SetTilesEvent";
import type { SetVariableEvent } from "./SetVariableEvent"; import type { SetVariableEvent } from "./SetVariableEvent";
import { isGameStateEvent } from "./GameStateEvent"; import { isGameStateEvent } from "./GameStateEvent";
import { isMapDataEvent } from "./MapDataEvent"; import { isMapDataEvent } from "./MapDataEvent";
import { isSetVariableEvent } from "./SetVariableEvent"; import { isSetVariableEvent } from "./SetVariableEvent";
import type { EmbeddedWebsite } from "../iframe/Room/EmbeddedWebsite"; import { isCreateEmbeddedWebsiteEvent, isEmbeddedWebsiteEvent } from "./EmbeddedWebsiteEvent";
import { isCreateEmbeddedWebsiteEvent } from "./EmbeddedWebsiteEvent";
import type { LoadTilesetEvent } from "./LoadTilesetEvent";
import { isLoadTilesetEvent } from "./LoadTilesetEvent"; import { isLoadTilesetEvent } from "./LoadTilesetEvent";
import type { MessageReferenceEvent } from "./ui/TriggerActionMessageEvent"; import type { MessageReferenceEvent } from "./ui/TriggerActionMessageEvent";
import { isMessageReferenceEvent, isTriggerActionMessageEvent } 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 type { ChangeLayerEvent } from "./ChangeLayerEvent";
import { isPlayerPosition } from "./PlayerPosition"; import { isPlayerPosition } from "./PlayerPosition";
import type { WasCameraUpdatedEvent } from "./WasCameraUpdatedEvent"; import type { WasCameraUpdatedEvent } from "./WasCameraUpdatedEvent";
import type { ChangeZoneEvent } from "./ChangeZoneEvent"; import type { ChangeZoneEvent } from "./ChangeZoneEvent";
import type { CameraSetEvent } from "./CameraSetEvent"; import { isCameraSetEvent } from "./CameraSetEvent";
import type { CameraFollowPlayerEvent } from "./CameraFollowPlayerEvent"; import { isCameraFollowPlayerEvent } from "./CameraFollowPlayerEvent";
import { isColorEvent } from "./ColorEvent"; import { isColorEvent } from "./ColorEvent";
import { isMovePlayerToEventConfig } from "./MovePlayerToEvent"; import { isMovePlayerToEventConfig } from "./MovePlayerToEvent";
import { isMovePlayerToEventAnswer } from "./MovePlayerToEventAnswer"; import { isMovePlayerToEventAnswer } from "./MovePlayerToEventAnswer";
import type { RemotePlayerClickedEvent } from "./RemotePlayerClickedEvent"; import type { RemotePlayerClickedEvent } from "./RemotePlayerClickedEvent";
import type { AddActionsMenuKeyToRemotePlayerEvent } from "./AddActionsMenuKeyToRemotePlayerEvent"; import { isAddActionsMenuKeyToRemotePlayerEvent } from "./AddActionsMenuKeyToRemotePlayerEvent";
import type { ActionsMenuActionClickedEvent } from "./ActionsMenuActionClickedEvent"; import type { ActionsMenuActionClickedEvent } from "./ActionsMenuActionClickedEvent";
import type { RemoveActionsMenuKeyFromRemotePlayerEvent } from "./RemoveActionsMenuKeyFromRemotePlayerEvent"; import { isRemoveActionsMenuKeyFromRemotePlayerEvent } from "./RemoveActionsMenuKeyFromRemotePlayerEvent";
export interface TypedMessageEvent<T> extends MessageEvent { export interface TypedMessageEvent<T> extends MessageEvent {
data: T; data: T;
@ -49,45 +47,114 @@ export interface TypedMessageEvent<T> extends MessageEvent {
/** /**
* List event types sent from an iFrame to WorkAdventure * List event types sent from an iFrame to WorkAdventure
*/ */
export type IframeEventMap = { export const isIframeEventWrapper = z.union([
addActionsMenuKeyToRemotePlayer: AddActionsMenuKeyToRemotePlayerEvent; z.object({
removeActionsMenuKeyFromRemotePlayer: RemoveActionsMenuKeyFromRemotePlayerEvent; type: z.literal("addActionsMenuKeyToRemotePlayer"),
loadPage: LoadPageEvent; data: isAddActionsMenuKeyToRemotePlayerEvent,
chat: ChatEvent; }),
cameraFollowPlayer: CameraFollowPlayerEvent; z.object({
cameraSet: CameraSetEvent; type: z.literal("removeActionsMenuKeyFromRemotePlayer"),
openPopup: OpenPopupEvent; data: isRemoveActionsMenuKeyFromRemotePlayerEvent,
closePopup: ClosePopupEvent; }),
openTab: OpenTabEvent; z.object({
goToPage: GoToPageEvent; type: z.literal("loadPage"),
disablePlayerControls: null; data: isLoadPageEvent,
restorePlayerControls: null; }),
displayBubble: null; z.object({
removeBubble: null; type: z.literal("chat"),
onPlayerMove: undefined; data: isChatEvent,
onOpenActionMenu: undefined; }),
onCameraUpdate: undefined; z.object({
showLayer: LayerEvent; type: z.literal("cameraFollowPlayer"),
hideLayer: LayerEvent; data: isCameraFollowPlayerEvent,
setProperty: SetPropertyEvent; }),
loadSound: LoadSoundEvent; z.object({
playSound: PlaySoundEvent; type: z.literal("cameraSet"),
stopSound: StopSoundEvent; data: isCameraSetEvent,
getState: undefined; }),
loadTileset: LoadTilesetEvent; z.object({
registerMenu: MenuRegisterEvent; type: z.literal("openPopup"),
unregisterMenu: UnregisterMenuEvent; data: isOpenPopupEvent,
setTiles: SetTilesEvent; }),
modifyEmbeddedWebsite: Partial<EmbeddedWebsite>; // Note: name should be compulsory in fact z.object({
}; type: z.literal("closePopup"),
export interface IframeEvent<T extends keyof IframeEventMap> { data: isClosePopupEvent,
type: T; }),
data: IframeEventMap[T]; 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 type IframeEvent = z.infer<typeof isIframeEventWrapper>;
export const isIframeEventWrapper = (event: any): event is IframeEvent<keyof IframeEventMap> =>
typeof event.type === "string";
export interface IframeResponseEventMap { export interface IframeResponseEventMap {
userInputChat: UserInputChatEvent; userInputChat: UserInputChatEvent;
@ -116,73 +183,78 @@ export const isIframeResponseEventWrapper = (event: {
type?: string; type?: string;
}): event is IframeResponseEvent<keyof IframeResponseEventMap> => typeof event.type === "string"; }): event is IframeResponseEvent<keyof IframeResponseEventMap> => 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. * 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. * Types are defined using Type guards that will actually bused to enforce and check types.
*/ */
export const iframeQueryMapTypeGuards = { export const iframeQueryMapTypeGuards = {
getState: { getState: {
query: tg.isUndefined, query: z.undefined(),
answer: isGameStateEvent, answer: isGameStateEvent,
}, },
getMapData: { getMapData: {
query: tg.isUndefined, query: z.undefined(),
answer: isMapDataEvent, answer: isMapDataEvent,
}, },
setVariable: { setVariable: {
query: isSetVariableEvent, query: isSetVariableEvent,
answer: tg.isUndefined, answer: z.undefined(),
}, },
loadTileset: { loadTileset: {
query: isLoadTilesetEvent, query: isLoadTilesetEvent,
answer: tg.isNumber, answer: z.number(),
}, },
openCoWebsite: { openCoWebsite: {
query: isOpenCoWebsiteEvent, query: isOpenCoWebsiteEvent,
answer: isCoWebsite, answer: isCoWebsite,
}, },
getCoWebsites: { getCoWebsites: {
query: tg.isUndefined, query: z.undefined(),
answer: tg.isArray(isCoWebsite), answer: z.array(isCoWebsite),
}, },
closeCoWebsite: { closeCoWebsite: {
query: tg.isString, query: z.string(),
answer: tg.isUndefined, answer: z.undefined(),
}, },
closeCoWebsites: { closeCoWebsites: {
query: tg.isUndefined, query: z.undefined(),
answer: tg.isUndefined, answer: z.undefined(),
}, },
triggerActionMessage: { triggerActionMessage: {
query: isTriggerActionMessageEvent, query: isTriggerActionMessageEvent,
answer: tg.isUndefined, answer: z.undefined(),
}, },
removeActionMessage: { removeActionMessage: {
query: isMessageReferenceEvent, query: isMessageReferenceEvent,
answer: tg.isUndefined, answer: z.undefined(),
}, },
getEmbeddedWebsite: { getEmbeddedWebsite: {
query: tg.isString, query: z.string(),
answer: isCreateEmbeddedWebsiteEvent, answer: isCreateEmbeddedWebsiteEvent,
}, },
deleteEmbeddedWebsite: { deleteEmbeddedWebsite: {
query: tg.isString, query: z.string(),
answer: tg.isUndefined, answer: z.undefined(),
}, },
createEmbeddedWebsite: { createEmbeddedWebsite: {
query: isCreateEmbeddedWebsiteEvent, query: isCreateEmbeddedWebsiteEvent,
answer: tg.isUndefined, answer: z.undefined(),
}, },
setPlayerOutline: { setPlayerOutline: {
query: isColorEvent, query: isColorEvent,
answer: tg.isUndefined, answer: z.undefined(),
}, },
removePlayerOutline: { removePlayerOutline: {
query: tg.isUndefined, query: z.undefined(),
answer: tg.isUndefined, answer: z.undefined(),
}, },
getPlayerPosition: { getPlayerPosition: {
query: tg.isUndefined, query: z.undefined(),
answer: isPlayerPosition, answer: isPlayerPosition,
}, },
movePlayerTo: { movePlayerTo: {
@ -191,14 +263,13 @@ export const iframeQueryMapTypeGuards = {
}, },
}; };
type GuardedType<T> = T extends (x: unknown) => x is infer T ? T : never;
type IframeQueryMapTypeGuardsType = typeof iframeQueryMapTypeGuards; type IframeQueryMapTypeGuardsType = typeof iframeQueryMapTypeGuards;
type UnknownToVoid<T> = undefined extends T ? void : T; type UnknownToVoid<T> = undefined extends T ? void : T;
export type IframeQueryMap = { export type IframeQueryMap = {
[key in keyof IframeQueryMapTypeGuardsType]: { [key in keyof IframeQueryMapTypeGuardsType]: {
query: GuardedType<IframeQueryMapTypeGuardsType[key]["query"]>; query: z.infer<typeof iframeQueryMapTypeGuards[key]["query"]>;
answer: UnknownToVoid<GuardedType<IframeQueryMapTypeGuardsType[key]["answer"]>>; answer: UnknownToVoid<z.infer<typeof iframeQueryMapTypeGuards[key]["answer"]>>;
}; };
}; };
@ -226,11 +297,18 @@ export const isIframeQuery = (event: any): event is IframeQuery<keyof IframeQuer
return false; return false;
} }
const result = iframeQueryMapTypeGuards[type].query(event.data); try {
if (!result) { iframeQueryMapTypeGuards[type].query.parse(event.data);
} catch (err) {
if (err instanceof z.ZodError) {
console.error(err.issues);
}
console.warn('Received a query with type "' + type + '" but the payload is invalid.'); console.warn('Received a query with type "' + type + '" but the payload is invalid.');
return false;
} }
return result;
return true;
}; };
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any

View File

@ -1,11 +1,10 @@
import * as tg from "generic-type-guard"; import { z } from "zod";
export const isLayerEvent = z.object({
name: z.string(),
});
export const isLayerEvent = new tg.IsInterface()
.withProperties({
name: tg.isString,
})
.get();
/** /**
* A message sent from the iFrame to the game to show/hide a layer. * A message sent from the iFrame to the game to show/hide a layer.
*/ */
export type LayerEvent = tg.GuardedType<typeof isLayerEvent>; export type LayerEvent = z.infer<typeof isLayerEvent>;

View File

@ -1,12 +1,10 @@
import * as tg from "generic-type-guard"; import { z } from "zod";
export const isLoadPageEvent = new tg.IsInterface() export const isLoadPageEvent = z.object({
.withProperties({ url: z.string(),
url: tg.isString, });
})
.get();
/** /**
* A message sent from the iFrame to the game to add a message in the chat. * A message sent from the iFrame to the game to add a message in the chat.
*/ */
export type LoadPageEvent = tg.GuardedType<typeof isLoadPageEvent>; export type LoadPageEvent = z.infer<typeof isLoadPageEvent>;

View File

@ -1,12 +1,10 @@
import * as tg from "generic-type-guard"; import { z } from "zod";
export const isLoadSoundEvent = new tg.IsInterface() export const isLoadSoundEvent = z.object({
.withProperties({ url: z.string(),
url: tg.isString, });
})
.get();
/** /**
* A message sent from the iFrame to the game to add a message in the chat. * A message sent from the iFrame to the game to add a message in the chat.
*/ */
export type LoadSoundEvent = tg.GuardedType<typeof isLoadSoundEvent>; export type LoadSoundEvent = z.infer<typeof isLoadSoundEvent>;

View File

@ -1,12 +1,10 @@
import * as tg from "generic-type-guard"; import { z } from "zod";
export const isLoadTilesetEvent = new tg.IsInterface() export const isLoadTilesetEvent = z.object({
.withProperties({ url: z.string(),
url: tg.isString, });
})
.get();
/** /**
* A message sent from the iFrame to the game to add a message in the chat. * A message sent from the iFrame to the game to add a message in the chat.
*/ */
export type LoadTilesetEvent = tg.GuardedType<typeof isLoadTilesetEvent>; export type LoadTilesetEvent = z.infer<typeof isLoadTilesetEvent>;

View File

@ -1,12 +1,10 @@
import * as tg from "generic-type-guard"; import { z } from "zod";
export const isMapDataEvent = new tg.IsInterface() export const isMapDataEvent = z.object({
.withProperties({ data: z.unknown(), // Todo : Typing
data: tg.isObject, });
})
.get();
/** /**
* 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 * 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<typeof isMapDataEvent>; export type MapDataEvent = z.infer<typeof isMapDataEvent>;

View File

@ -1,11 +1,9 @@
import * as tg from "generic-type-guard"; import { z } from "zod";
export const isMovePlayerToEventConfig = new tg.IsInterface() export const isMovePlayerToEventConfig = z.object({
.withProperties({ x: z.number(),
x: tg.isNumber, y: z.number(),
y: tg.isNumber, speed: z.optional(z.number()),
speed: tg.isOptional(tg.isNumber), });
})
.get();
export type MovePlayerToEvent = tg.GuardedType<typeof isMovePlayerToEventConfig>; export type MovePlayerToEvent = z.infer<typeof isMovePlayerToEventConfig>;

View File

@ -1,11 +1,9 @@
import * as tg from "generic-type-guard"; import { z } from "zod";
export const isMovePlayerToEventAnswer = new tg.IsInterface() export const isMovePlayerToEventAnswer = z.object({
.withProperties({ x: z.number(),
x: tg.isNumber, y: z.number(),
y: tg.isNumber, cancelled: z.boolean(),
cancelled: tg.isBoolean, });
})
.get();
export type MovePlayerToEventAnswer = tg.GuardedType<typeof isMovePlayerToEventAnswer>; export type ActionsMenuActionClickedEvent = z.infer<typeof isMovePlayerToEventAnswer>;

View File

@ -1,24 +1,20 @@
import * as tg from "generic-type-guard"; import { z } from "zod";
export const isOpenCoWebsiteEvent = new tg.IsInterface() export const isOpenCoWebsiteEvent = z.object({
.withProperties({ url: z.string(),
url: tg.isString, allowApi: z.optional(z.boolean()),
allowApi: tg.isOptional(tg.isBoolean), allowPolicy: z.optional(z.string()),
allowPolicy: tg.isOptional(tg.isString), widthPercent: z.optional(z.number()),
widthPercent: tg.isOptional(tg.isNumber), position: z.optional(z.number()),
position: tg.isOptional(tg.isNumber), closable: z.optional(z.boolean()),
closable: tg.isOptional(tg.isBoolean), lazy: z.optional(z.boolean()),
lazy: tg.isOptional(tg.isBoolean), });
})
.get();
export const isCoWebsite = new tg.IsInterface() export const isCoWebsite = z.object({
.withProperties({ id: z.string(),
id: tg.isString, });
})
.get();
/** /**
* A message sent from the iFrame to the game to add a message in the chat. * A message sent from the iFrame to the game to add a message in the chat.
*/ */
export type OpenCoWebsiteEvent = tg.GuardedType<typeof isOpenCoWebsiteEvent>; export type OpenCoWebsiteEvent = z.infer<typeof isOpenCoWebsiteEvent>;

View File

@ -1,22 +1,18 @@
import * as tg from "generic-type-guard"; import { z } from "zod";
const isButtonDescriptor = new tg.IsInterface() export const isButtonDescriptor = z.object({
.withProperties({ label: z.string(),
label: tg.isString, className: z.optional(z.string()),
className: tg.isOptional(tg.isString), });
})
.get();
export const isOpenPopupEvent = new tg.IsInterface() export const isOpenPopupEvent = z.object({
.withProperties({ popupId: z.number(),
popupId: tg.isNumber, targetObject: z.string(),
targetObject: tg.isString, message: z.string(),
message: tg.isString, buttons: z.array(isButtonDescriptor),
buttons: tg.isArray(isButtonDescriptor), });
})
.get();
/** /**
* A message sent from the iFrame to the game to add a message in the chat. * A message sent from the iFrame to the game to add a message in the chat.
*/ */
export type OpenPopupEvent = tg.GuardedType<typeof isOpenPopupEvent>; export type OpenPopupEvent = z.infer<typeof isOpenPopupEvent>;

View File

@ -1,12 +1,10 @@
import * as tg from "generic-type-guard"; import { z } from "zod";
export const isOpenTabEvent = new tg.IsInterface() export const isOpenTabEvent = z.object({
.withProperties({ url: z.string(),
url: tg.isString, });
})
.get();
/** /**
* A message sent from the iFrame to the game to add a message in the chat. * A message sent from the iFrame to the game to add a message in the chat.
*/ */
export type OpenTabEvent = tg.GuardedType<typeof isOpenTabEvent>; export type OpenTabEvent = z.infer<typeof isOpenTabEvent>;

View File

@ -1,25 +1,21 @@
import * as tg from "generic-type-guard"; import { z } from "zod";
const isSoundConfig = new tg.IsInterface() export const isSoundConfig = z.object({
.withProperties({ volume: z.optional(z.number()),
volume: tg.isOptional(tg.isNumber), loop: z.optional(z.boolean()),
loop: tg.isOptional(tg.isBoolean), mute: z.optional(z.boolean()),
mute: tg.isOptional(tg.isBoolean), rate: z.optional(z.number()),
rate: tg.isOptional(tg.isNumber), detune: z.optional(z.number()),
detune: tg.isOptional(tg.isNumber), seek: z.optional(z.number()),
seek: tg.isOptional(tg.isNumber), delay: z.optional(z.number()),
delay: tg.isOptional(tg.isNumber), });
})
.get();
export const isPlaySoundEvent = new tg.IsInterface() export const isPlaySoundEvent = z.object({
.withProperties({ url: z.string(),
url: tg.isString, config: z.optional(isSoundConfig),
config: tg.isOptional(isSoundConfig), });
})
.get();
/** /**
* A message sent from the iFrame to the game to add a message in the chat. * A message sent from the iFrame to the game to add a message in the chat.
*/ */
export type PlaySoundEvent = tg.GuardedType<typeof isPlaySoundEvent>; export type PlaySoundEvent = z.infer<typeof isPlaySoundEvent>;

View File

@ -1,10 +1,8 @@
import * as tg from "generic-type-guard"; import { z } from "zod";
export const isPlayerPosition = new tg.IsInterface() export const isPlayerPosition = z.object({
.withProperties({ x: z.number(),
x: tg.isNumber, y: z.number(),
y: tg.isNumber, });
})
.get();
export type PlayerPosition = tg.GuardedType<typeof isPlayerPosition>; export type PlayerPosition = z.infer<typeof isPlayerPosition>;

View File

@ -1,15 +1,13 @@
import * as tg from "generic-type-guard"; import { z } from "zod";
// TODO: Change for player Clicked, add all neccessary data // TODO: Change for player Clicked, add all neccessary data
export const isRemotePlayerClickedEvent = new tg.IsInterface() export const isRemotePlayerClickedEvent = z.object({
.withProperties({ id: z.number(),
id: tg.isNumber, });
})
.get();
/** /**
* A message sent from the game to the iFrame when RemotePlayer is clicked. * A message sent from the game to the iFrame when RemotePlayer is clicked.
*/ */
export type RemotePlayerClickedEvent = tg.GuardedType<typeof isRemotePlayerClickedEvent>; export type RemotePlayerClickedEvent = z.infer<typeof isRemotePlayerClickedEvent>;
export type RemotePlayerClickedEventCallback = (event: RemotePlayerClickedEvent) => void; export type RemotePlayerClickedEventCallback = (event: RemotePlayerClickedEvent) => void;

View File

@ -1,15 +1,11 @@
import * as tg from "generic-type-guard"; import { z } from "zod";
export const isRemoveActionsMenuKeyFromRemotePlayerEvent = new tg.IsInterface() export const isRemoveActionsMenuKeyFromRemotePlayerEvent = z.object({
.withProperties({ id: z.number(),
id: tg.isNumber, actionKey: z.string(),
actionKey: tg.isString, });
})
.get();
export type RemoveActionsMenuKeyFromRemotePlayerEvent = tg.GuardedType< export type RemoveActionsMenuKeyFromRemotePlayerEvent = z.infer<typeof isRemoveActionsMenuKeyFromRemotePlayerEvent>;
typeof isRemoveActionsMenuKeyFromRemotePlayerEvent
>;
export type RemoveActionsMenuKeyFromRemotePlayerEventCallback = ( export type RemoveActionsMenuKeyFromRemotePlayerEventCallback = (
event: RemoveActionsMenuKeyFromRemotePlayerEvent event: RemoveActionsMenuKeyFromRemotePlayerEvent

View File

@ -1,16 +1,15 @@
import * as tg from "generic-type-guard"; import { z } from "zod";
export const isSetTilesEvent = tg.isArray( export const isSetTilesEvent = z.array(
new tg.IsInterface() z.object({
.withProperties({ x: z.number(),
x: tg.isNumber, y: z.number(),
y: tg.isNumber, tile: z.union([z.number(), z.string(), z.null()]),
tile: tg.isUnion(tg.isUnion(tg.isNumber, tg.isString), tg.isNull), layer: z.string(),
layer: tg.isString, })
})
.get()
); );
/** /**
* A message sent from the iFrame to the game to set one or many tiles. * A message sent from the iFrame to the game to set one or many tiles.
*/ */
export type SetTilesEvent = tg.GuardedType<typeof isSetTilesEvent>; export type SetTilesEvent = z.infer<typeof isSetTilesEvent>;

View File

@ -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 * A message sent from the iFrame to the game to change the value of the property of the layer
*/ */
export type SetVariableEvent = tg.GuardedType<typeof isSetVariableEvent>; export type SetVariableEvent = z.infer<typeof isSetVariableEvent>;
export const isSetVariableIframeEvent = new tg.IsInterface()
.withProperties({
type: tg.isSingletonString("setVariable"),
data: isSetVariableEvent,
})
.get();

View File

@ -1,12 +1,10 @@
import * as tg from "generic-type-guard"; import { z } from "zod";
export const isStopSoundEvent = new tg.IsInterface() export const isStopSoundEvent = z.object({
.withProperties({ url: z.string(),
url: tg.isString, });
})
.get();
/** /**
* A message sent from the iFrame to the game to add a message in the chat. * A message sent from the iFrame to the game to add a message in the chat.
*/ */
export type StopSoundEvent = tg.GuardedType<typeof isStopSoundEvent>; export type StopSoundEvent = z.infer<typeof isStopSoundEvent>;

View File

@ -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. * A message sent from the game to the iFrame when a user types a message in the chat.
*/ */
export type UserInputChatEvent = tg.GuardedType<typeof isUserInputChatEvent>; export type UserInputChatEvent = z.infer<typeof isUserInputChatEvent>;

View File

@ -1,19 +1,16 @@
import * as tg from "generic-type-guard"; import { z } from "zod";
export const isWasCameraUpdatedEvent = new tg.IsInterface() export const isWasCameraUpdatedEvent = z.object({
.withProperties({ x: z.number(),
x: tg.isNumber, y: z.number(),
y: tg.isNumber, width: z.number(),
width: tg.isNumber, height: z.number(),
height: tg.isNumber, zoom: z.number(),
zoom: tg.isNumber, });
})
.get();
/** /**
* A message sent from the game to the iFrame to notify a movement from the camera. * A message sent from the game to the iFrame to notify a movement from the camera.
*/ */
export type WasCameraUpdatedEvent = z.infer<typeof isWasCameraUpdatedEvent>;
export type WasCameraUpdatedEvent = tg.GuardedType<typeof isWasCameraUpdatedEvent>;
export type WasCameraUpdatedEventCallback = (event: WasCameraUpdatedEvent) => void; export type WasCameraUpdatedEventCallback = (event: WasCameraUpdatedEvent) => void;

View File

@ -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 * A message sent from the iFrame to the game to change the value of the property of the layer
*/ */
export type SetPropertyEvent = tg.GuardedType<typeof isSetPropertyEvent>; export type SetPropertyEvent = z.infer<typeof isSetPropertyEvent>;

View File

@ -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. * A message sent from the game to the iFrame when a menu item is clicked.
*/ */
export type MenuItemClickedEvent = tg.GuardedType<typeof isMenuItemClickedEvent>; export type MenuItemClickedEvent = z.infer<typeof isMenuItemClickedEvent>;

View File

@ -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 * A message sent from a script to the game to remove a custom menu from the menu
*/ */
export const isUnregisterMenuEvent = new tg.IsInterface() export const isUnregisterMenuEvent = z.object({
.withProperties({ name: z.string(),
name: tg.isString, });
})
.get();
export type UnregisterMenuEvent = tg.GuardedType<typeof isUnregisterMenuEvent>; export type UnregisterMenuEvent = z.infer<typeof isUnregisterMenuEvent>;
export const isMenuRegisterOptions = new tg.IsInterface() export const isMenuRegisterOptions = z.object({
.withProperties({ allowApi: z.boolean(),
allowApi: tg.isBoolean, });
})
.get();
/** /**
* A message sent from a script to the game to add a custom menu from the menu * A message sent from a script to the game to add a custom menu from the menu
*/ */
export const isMenuRegisterEvent = new tg.IsInterface() export const isMenuRegisterEvent = z.object({
.withProperties({ name: z.string(),
name: tg.isString, iframe: z.optional(z.string()),
iframe: tg.isUnion(tg.isString, tg.isUndefined), options: isMenuRegisterOptions,
options: isMenuRegisterOptions, });
})
.get();
export type MenuRegisterEvent = tg.GuardedType<typeof isMenuRegisterEvent>; export type MenuRegisterEvent = z.infer<typeof isMenuRegisterEvent>;

View File

@ -1,26 +1,22 @@
import * as tg from "generic-type-guard"; import { z } from "zod";
export const triggerActionMessage = "triggerActionMessage"; export const triggerActionMessage = "triggerActionMessage";
export const removeActionMessage = "removeActionMessage"; export const removeActionMessage = "removeActionMessage";
export const isActionMessageType = tg.isSingletonStringUnion("message", "warning"); export const isActionMessageType = z.enum(["message", "warning"]);
export type ActionMessageType = tg.GuardedType<typeof isActionMessageType>; export type ActionMessageType = z.infer<typeof isActionMessageType>;
export const isTriggerActionMessageEvent = new tg.IsInterface() export const isTriggerActionMessageEvent = z.object({
.withProperties({ message: z.string(),
message: tg.isString, uuid: z.string(),
uuid: tg.isString, type: isActionMessageType,
type: isActionMessageType, });
})
.get();
export type TriggerActionMessageEvent = tg.GuardedType<typeof isTriggerActionMessageEvent>; export type TriggerActionMessageEvent = z.infer<typeof isTriggerActionMessageEvent>;
export const isMessageReferenceEvent = new tg.IsInterface() export const isMessageReferenceEvent = z.object({
.withProperties({ uuid: z.string(),
uuid: tg.isString, });
})
.get();
export type MessageReferenceEvent = tg.GuardedType<typeof isMessageReferenceEvent>; export type MessageReferenceEvent = z.infer<typeof isMessageReferenceEvent>;

View File

@ -5,20 +5,16 @@ import {
triggerActionMessage, triggerActionMessage,
} from "./TriggerActionMessageEvent"; } from "./TriggerActionMessageEvent";
import * as tg from "generic-type-guard"; import { z } from "zod";
const isTriggerMessageEventObject = new tg.IsInterface() const isTriggerMessageEventObject = z.object({
.withProperties({ type: z.enum([triggerActionMessage]),
type: tg.isSingletonString(triggerActionMessage), data: isTriggerActionMessageEvent,
data: isTriggerActionMessageEvent, });
})
.get();
const isTriggerMessageRemoveEventObject = new tg.IsInterface() const isTriggerMessageRemoveEventObject = z.object({
.withProperties({ type: z.enum([removeActionMessage]),
type: tg.isSingletonString(removeActionMessage), data: isMessageReferenceEvent,
data: isMessageReferenceEvent, });
})
.get();
export const isTriggerMessageHandlerEvent = tg.isUnion(isTriggerMessageEventObject, isTriggerMessageRemoveEventObject); export const isTriggerMessageHandlerEvent = z.union([isTriggerMessageEventObject, isTriggerMessageRemoveEventObject]);

View File

@ -1,13 +1,11 @@
import { Subject } from "rxjs"; import { Subject } from "rxjs";
import { isChatEvent } from "./Events/ChatEvent";
import { HtmlUtils } from "../WebRtc/HtmlUtils"; import { HtmlUtils } from "../WebRtc/HtmlUtils";
import type { EnterLeaveEvent } from "./Events/EnterLeaveEvent"; import type { EnterLeaveEvent } from "./Events/EnterLeaveEvent";
import { isOpenPopupEvent, OpenPopupEvent } from "./Events/OpenPopupEvent"; import { OpenPopupEvent } from "./Events/OpenPopupEvent";
import { isOpenTabEvent, OpenTabEvent } from "./Events/OpenTabEvent"; import { OpenTabEvent } from "./Events/OpenTabEvent";
import type { ButtonClickedEvent } from "./Events/ButtonClickedEvent"; import type { ButtonClickedEvent } from "./Events/ButtonClickedEvent";
import { ClosePopupEvent, isClosePopupEvent } from "./Events/ClosePopupEvent"; import { ClosePopupEvent } from "./Events/ClosePopupEvent";
import { scriptUtils } from "./ScriptUtils"; import { scriptUtils } from "./ScriptUtils";
import { isGoToPageEvent } from "./Events/GoToPageEvent";
import { import {
IframeErrorAnswerEvent, IframeErrorAnswerEvent,
IframeQueryMap, IframeQueryMap,
@ -15,35 +13,28 @@ import {
IframeResponseEventMap, IframeResponseEventMap,
isIframeEventWrapper, isIframeEventWrapper,
isIframeQueryWrapper, isIframeQueryWrapper,
isLookingLikeIframeEventWrapper,
} from "./Events/IframeEvent"; } from "./Events/IframeEvent";
import type { UserInputChatEvent } from "./Events/UserInputChatEvent"; import type { UserInputChatEvent } from "./Events/UserInputChatEvent";
import { isPlaySoundEvent, PlaySoundEvent } from "./Events/PlaySoundEvent"; import { PlaySoundEvent } from "./Events/PlaySoundEvent";
import { isStopSoundEvent, StopSoundEvent } from "./Events/StopSoundEvent"; import { StopSoundEvent } from "./Events/StopSoundEvent";
import { isLoadSoundEvent, LoadSoundEvent } from "./Events/LoadSoundEvent"; import { LoadSoundEvent } from "./Events/LoadSoundEvent";
import { isSetPropertyEvent, SetPropertyEvent } from "./Events/setPropertyEvent"; import { SetPropertyEvent } from "./Events/setPropertyEvent";
import { isLayerEvent, LayerEvent } from "./Events/LayerEvent"; import { LayerEvent } from "./Events/LayerEvent";
import type { HasPlayerMovedEvent } from "./Events/HasPlayerMovedEvent"; import type { HasPlayerMovedEvent } from "./Events/HasPlayerMovedEvent";
import { isLoadPageEvent } from "./Events/LoadPageEvent"; import { SetTilesEvent } from "./Events/SetTilesEvent";
import { isMenuRegisterEvent, isUnregisterMenuEvent } from "./Events/ui/MenuRegisterEvent";
import { SetTilesEvent, isSetTilesEvent } from "./Events/SetTilesEvent";
import type { SetVariableEvent } from "./Events/SetVariableEvent"; import type { SetVariableEvent } from "./Events/SetVariableEvent";
import { ModifyEmbeddedWebsiteEvent, isEmbeddedWebsiteEvent } from "./Events/EmbeddedWebsiteEvent"; import { ModifyEmbeddedWebsiteEvent } from "./Events/EmbeddedWebsiteEvent";
import { handleMenuRegistrationEvent, handleMenuUnregisterEvent } from "../Stores/MenuStore"; import { handleMenuRegistrationEvent, handleMenuUnregisterEvent } from "../Stores/MenuStore";
import type { ChangeLayerEvent } from "./Events/ChangeLayerEvent"; import type { ChangeLayerEvent } from "./Events/ChangeLayerEvent";
import type { WasCameraUpdatedEvent } from "./Events/WasCameraUpdatedEvent"; import type { WasCameraUpdatedEvent } from "./Events/WasCameraUpdatedEvent";
import type { ChangeZoneEvent } from "./Events/ChangeZoneEvent"; import type { ChangeZoneEvent } from "./Events/ChangeZoneEvent";
import { CameraSetEvent, isCameraSetEvent } from "./Events/CameraSetEvent"; import { CameraSetEvent } from "./Events/CameraSetEvent";
import { CameraFollowPlayerEvent, isCameraFollowPlayerEvent } from "./Events/CameraFollowPlayerEvent"; import { CameraFollowPlayerEvent } from "./Events/CameraFollowPlayerEvent";
import type { RemotePlayerClickedEvent } from "./Events/RemotePlayerClickedEvent"; import type { RemotePlayerClickedEvent } from "./Events/RemotePlayerClickedEvent";
import { import { AddActionsMenuKeyToRemotePlayerEvent } from "./Events/AddActionsMenuKeyToRemotePlayerEvent";
AddActionsMenuKeyToRemotePlayerEvent,
isAddActionsMenuKeyToRemotePlayerEvent,
} from "./Events/AddActionsMenuKeyToRemotePlayerEvent";
import type { ActionsMenuActionClickedEvent } from "./Events/ActionsMenuActionClickedEvent"; import type { ActionsMenuActionClickedEvent } from "./Events/ActionsMenuActionClickedEvent";
import { import { RemoveActionsMenuKeyFromRemotePlayerEvent } from "./Events/RemoveActionsMenuKeyFromRemotePlayerEvent";
isRemoveActionsMenuKeyFromRemotePlayerEvent,
RemoveActionsMenuKeyFromRemotePlayerEvent,
} from "./Events/RemoveActionsMenuKeyFromRemotePlayerEvent";
type AnswererCallback<T extends keyof IframeQueryMap> = ( type AnswererCallback<T extends keyof IframeQueryMap> = (
query: IframeQueryMap[T]["query"], query: IframeQueryMap[T]["query"],
@ -150,8 +141,10 @@ class IframeListener {
const payload = message.data; const payload = message.data;
const lookingLikeEvent = isLookingLikeIframeEventWrapper.safeParse(payload);
if (foundSrc === undefined || iframe === undefined) { if (foundSrc === undefined || iframe === undefined) {
if (isIframeEventWrapper(payload)) { if (lookingLikeEvent.success) {
console.warn( console.warn(
"It seems an iFrame is trying to communicate with WorkAdventure but was not explicitly granted the permission to do so. " + "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 " + "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) { } catch (reason) {
errorHandler(reason); errorHandler(reason);
} }
} else if (isIframeEventWrapper(payload)) { } else if (lookingLikeEvent.success) {
if (payload.type === "showLayer" && isLayerEvent(payload.data)) { const iframeEventGuarded = isIframeEventWrapper.safeParse(lookingLikeEvent.data);
this._showLayerStream.next(payload.data);
} else if (payload.type === "hideLayer" && isLayerEvent(payload.data)) { if (!iframeEventGuarded.success) {
this._hideLayerStream.next(payload.data); console.error(
} else if (payload.type === "setProperty" && isSetPropertyEvent(payload.data)) { `Invalid event "${lookingLikeEvent.data.type}" received from Iframe: `,
this._setPropertyStream.next(payload.data); lookingLikeEvent.data,
} else if (payload.type === "cameraSet" && isCameraSetEvent(payload.data)) { iframeEventGuarded.error.issues
this._cameraSetStream.next(payload.data); );
} else if (payload.type === "cameraFollowPlayer" && isCameraFollowPlayerEvent(payload.data)) { return;
this._cameraFollowPlayerStream.next(payload.data); }
} else if (payload.type === "chat" && isChatEvent(payload.data)) {
scriptUtils.sendAnonymousChat(payload.data); const iframeEvent = iframeEventGuarded.data;
} else if (payload.type === "openPopup" && isOpenPopupEvent(payload.data)) {
this._openPopupStream.next(payload.data); if (iframeEvent.type === "showLayer") {
} else if (payload.type === "closePopup" && isClosePopupEvent(payload.data)) { this._showLayerStream.next(iframeEvent.data);
this._closePopupStream.next(payload.data); } else if (iframeEvent.type === "hideLayer") {
} else if (payload.type === "openTab" && isOpenTabEvent(payload.data)) { this._hideLayerStream.next(iframeEvent.data);
scriptUtils.openTab(payload.data.url); } else if (iframeEvent.type === "setProperty") {
} else if (payload.type === "goToPage" && isGoToPageEvent(payload.data)) { this._setPropertyStream.next(iframeEvent.data);
scriptUtils.goToPage(payload.data.url); } else if (iframeEvent.type === "cameraSet") {
} else if (payload.type === "loadPage" && isLoadPageEvent(payload.data)) { this._cameraSetStream.next(iframeEvent.data);
this._loadPageStream.next(payload.data.url); } else if (iframeEvent.type === "cameraFollowPlayer") {
} else if (payload.type === "playSound" && isPlaySoundEvent(payload.data)) { this._cameraFollowPlayerStream.next(iframeEvent.data);
this._playSoundStream.next(payload.data); } else if (iframeEvent.type === "chat") {
} else if (payload.type === "stopSound" && isStopSoundEvent(payload.data)) { scriptUtils.sendAnonymousChat(iframeEvent.data);
this._stopSoundStream.next(payload.data); } else if (iframeEvent.type === "openPopup") {
} else if (payload.type === "loadSound" && isLoadSoundEvent(payload.data)) { this._openPopupStream.next(iframeEvent.data);
this._loadSoundStream.next(payload.data); } else if (iframeEvent.type === "closePopup") {
} else if (payload.type === "disablePlayerControls") { 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(); this._disablePlayerControlStream.next();
} else if (payload.type === "restorePlayerControls") { } else if (iframeEvent.type === "restorePlayerControls") {
this._enablePlayerControlStream.next(); this._enablePlayerControlStream.next();
} else if (payload.type === "displayBubble") { } else if (iframeEvent.type === "displayBubble") {
this._displayBubbleStream.next(); this._displayBubbleStream.next();
} else if (payload.type === "removeBubble") { } else if (iframeEvent.type === "removeBubble") {
this._removeBubbleStream.next(); this._removeBubbleStream.next();
} else if (payload.type == "onPlayerMove") { } else if (iframeEvent.type == "onPlayerMove") {
this.sendPlayerMove = true; this.sendPlayerMove = true;
} else if ( } else if (iframeEvent.type == "addActionsMenuKeyToRemotePlayer") {
payload.type == "addActionsMenuKeyToRemotePlayer" && this._addActionsMenuKeyToRemotePlayerStream.next(iframeEvent.data);
isAddActionsMenuKeyToRemotePlayerEvent(payload.data) } else if (iframeEvent.type == "removeActionsMenuKeyFromRemotePlayer") {
) { this._removeActionsMenuKeyFromRemotePlayerEvent.next(iframeEvent.data);
this._addActionsMenuKeyToRemotePlayerStream.next(payload.data); } else if (iframeEvent.type == "onCameraUpdate") {
} else if (
payload.type == "removeActionsMenuKeyFromRemotePlayer" &&
isRemoveActionsMenuKeyFromRemotePlayerEvent(payload.data)
) {
this._removeActionsMenuKeyFromRemotePlayerEvent.next(payload.data);
} else if (payload.type == "onCameraUpdate") {
this._trackCameraUpdateStream.next(); this._trackCameraUpdateStream.next();
} else if (payload.type == "setTiles" && isSetTilesEvent(payload.data)) { } else if (iframeEvent.type == "setTiles") {
this._setTilesStream.next(payload.data); this._setTilesStream.next(iframeEvent.data);
} else if (payload.type == "modifyEmbeddedWebsite" && isEmbeddedWebsiteEvent(payload.data)) { } else if (iframeEvent.type == "modifyEmbeddedWebsite") {
this._modifyEmbeddedWebsiteStream.next(payload.data); this._modifyEmbeddedWebsiteStream.next(iframeEvent.data);
} else if (payload.type == "registerMenu" && isMenuRegisterEvent(payload.data)) { } else if (iframeEvent.type == "registerMenu") {
const dataName = payload.data.name; const dataName = iframeEvent.data.name;
this.iframeCloseCallbacks.get(iframe)?.push(() => { this.iframeCloseCallbacks.get(iframe)?.push(() => {
handleMenuUnregisterEvent(dataName); handleMenuUnregisterEvent(dataName);
}); });
@ -285,13 +285,17 @@ class IframeListener {
foundSrc = this.getBaseUrl(foundSrc, message.source); foundSrc = this.getBaseUrl(foundSrc, message.source);
handleMenuRegistrationEvent( handleMenuRegistrationEvent(
payload.data.name, iframeEvent.data.name,
payload.data.iframe, iframeEvent.data.iframe,
foundSrc, foundSrc,
payload.data.options iframeEvent.data.options
); );
} else if (payload.type == "unregisterMenu" && isUnregisterMenuEvent(payload.data)) { } else if (iframeEvent.type == "unregisterMenu") {
handleMenuUnregisterEvent(payload.data.name); 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<void> { registerScript(scriptUrl: string, enableModuleMode: boolean = true): Promise<void> {
return new Promise<void>((resolve, reject) => { return new Promise<void>((resolve) => {
console.info("Loading map related script at ", scriptUrl); console.info("Loading map related script at ", scriptUrl);
const iframe = document.createElement("iframe"); const iframe = document.createElement("iframe");

View File

@ -1,14 +1,8 @@
import type * as tg from "generic-type-guard"; import { z } from "zod";
import type { import type { IframeEvent, IframeQuery, IframeQueryMap, IframeResponseEventMap } from "../Events/IframeEvent";
IframeEvent,
IframeEventMap,
IframeQuery,
IframeQueryMap,
IframeResponseEventMap,
} from "../Events/IframeEvent";
import type { IframeQueryWrapper } from "../Events/IframeEvent"; import type { IframeQueryWrapper } from "../Events/IframeEvent";
export function sendToWorkadventure(content: IframeEvent<keyof IframeEventMap>) { export function sendToWorkadventure(content: IframeEvent) {
window.parent.postMessage(content, "*"); window.parent.postMessage(content, "*");
} }
@ -48,12 +42,10 @@ export function queryWorkadventure<T extends keyof IframeQueryMap>(
}); });
} }
type GuardedType<Guard extends tg.TypeGuard<unknown>> = Guard extends tg.TypeGuard<infer T> ? T : never;
export interface IframeCallback< export interface IframeCallback<
Key extends keyof IframeResponseEventMap, Key extends keyof IframeResponseEventMap,
T = IframeResponseEventMap[Key], T = IframeResponseEventMap[Key],
Guard = tg.TypeGuard<T> Guard = z.ZodType<T>
> { > {
typeChecker: Guard; typeChecker: Guard;
callback: (payloadData: T) => void; callback: (payloadData: T) => void;

View File

@ -1,9 +1,5 @@
import { sendToWorkadventure } from "../IframeApiContribution"; import { sendToWorkadventure } from "../IframeApiContribution";
import type { import type { CreateEmbeddedWebsiteEvent, Rectangle } from "../../Events/EmbeddedWebsiteEvent";
CreateEmbeddedWebsiteEvent,
ModifyEmbeddedWebsiteEvent,
Rectangle,
} from "../../Events/EmbeddedWebsiteEvent";
export class EmbeddedWebsite { export class EmbeddedWebsite {
public readonly name: string; public readonly name: string;

View File

@ -41,7 +41,7 @@ export class WorkAdventureCameraCommands extends IframeApiContribution<WorkAdven
onCameraUpdate(): Subject<WasCameraUpdatedEvent> { onCameraUpdate(): Subject<WasCameraUpdatedEvent> {
sendToWorkadventure({ sendToWorkadventure({
type: "onCameraUpdate", type: "onCameraUpdate",
data: null, data: undefined,
}); });
return moveStream; return moveStream;
} }

View File

@ -4,11 +4,11 @@ export class WorkadventureControlsCommands extends IframeApiContribution<Workadv
callbacks = []; callbacks = [];
disablePlayerControls(): void { disablePlayerControls(): void {
sendToWorkadventure({ type: "disablePlayerControls", data: null }); sendToWorkadventure({ type: "disablePlayerControls", data: undefined });
} }
restorePlayerControls(): void { restorePlayerControls(): void {
sendToWorkadventure({ type: "restorePlayerControls", data: null }); sendToWorkadventure({ type: "restorePlayerControls", data: undefined });
} }
} }

View File

@ -54,7 +54,7 @@ export class WorkadventurePlayerCommands extends IframeApiContribution<Workadven
moveStream.subscribe(callback); moveStream.subscribe(callback);
sendToWorkadventure({ sendToWorkadventure({
type: "onPlayerMove", type: "onPlayerMove",
data: null, data: undefined,
}); });
} }

View File

@ -1,7 +1,4 @@
import type { LoadSoundEvent } from "../Events/LoadSoundEvent"; import { IframeApiContribution } from "./IframeApiContribution";
import type { PlaySoundEvent } from "../Events/PlaySoundEvent";
import type { StopSoundEvent } from "../Events/StopSoundEvent";
import { IframeApiContribution, sendToWorkadventure } from "./IframeApiContribution";
import { Sound } from "./Sound/Sound"; import { Sound } from "./Sound/Sound";
export class WorkadventureSoundCommands extends IframeApiContribution<WorkadventureSoundCommands> { export class WorkadventureSoundCommands extends IframeApiContribution<WorkadventureSoundCommands> {

View File

@ -1,13 +1,9 @@
import { Observable, Subject } from "rxjs"; import { Observable, Subject } from "rxjs";
import { EnterLeaveEvent, isEnterLeaveEvent } from "../Events/EnterLeaveEvent"; import { IframeApiContribution, queryWorkadventure } from "./IframeApiContribution";
import { IframeApiContribution, queryWorkadventure, sendToWorkadventure } from "./IframeApiContribution";
import { apiCallback } from "./registeredCallbacks"; import { apiCallback } from "./registeredCallbacks";
import { isSetVariableEvent, SetVariableEvent } from "../Events/SetVariableEvent"; import { isSetVariableEvent, SetVariableEvent } from "../Events/SetVariableEvent";
import type { ITiledMap } from "../../Phaser/Map/ITiledMap";
export class WorkadventureStateCommands extends IframeApiContribution<WorkadventureStateCommands> { export class WorkadventureStateCommands extends IframeApiContribution<WorkadventureStateCommands> {
private setVariableResolvers = new Subject<SetVariableEvent>(); private setVariableResolvers = new Subject<SetVariableEvent>();
private variables = new Map<string, unknown>(); private variables = new Map<string, unknown>();
@ -17,7 +13,7 @@ export class WorkadventureStateCommands extends IframeApiContribution<Workadvent
super(); super();
this.setVariableResolvers.subscribe((event) => { this.setVariableResolvers.subscribe((event) => {
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. // 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 // No need to do this check since it is already performed in SharedVariablesManager
/*if (JSON.stringify(oldValue) === JSON.stringify(event.value)) { /*if (JSON.stringify(oldValue) === JSON.stringify(event.value)) {
@ -92,6 +88,7 @@ export function createState(target: "global" | "player"): WorkadventureStateComm
} }
return target.loadVariable(p.toString()); return target.loadVariable(p.toString());
}, },
// eslint-disable-next-line @typescript-eslint/no-unused-vars
set(target: WorkadventureStateCommands, p: PropertyKey, value: unknown, receiver: unknown): boolean { 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. // 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. // User must use WA.state.saveVariable to have error message.

View File

@ -34,14 +34,6 @@ interface MenuDescriptor {
export type MenuOptions = RequireOnlyOne<MenuDescriptor, "callback" | "iframe">; export type MenuOptions = RequireOnlyOne<MenuDescriptor, "callback" | "iframe">;
interface ZonedPopupOptions {
zone: string;
objectLayerName?: string;
popupText: string;
delay?: number;
popupOptions: Array<ButtonDescriptor>;
}
export interface ActionMessageOptions { export interface ActionMessageOptions {
message: string; message: string;
type?: "message" | "warning"; type?: "message" | "warning";
@ -277,11 +269,11 @@ export class WorkAdventureUiCommands extends IframeApiContribution<WorkAdventure
} }
public displayBubble(): void { public displayBubble(): void {
sendToWorkadventure({ type: "displayBubble", data: null }); sendToWorkadventure({ type: "displayBubble", data: undefined });
} }
public removeBubble(): void { public removeBubble(): void {
sendToWorkadventure({ type: "removeBubble", data: null }); sendToWorkadventure({ type: "removeBubble", data: undefined });
} }
public displayActionMessage(actionMessageOptions: ActionMessageOptions): ActionMessage { public displayActionMessage(actionMessageOptions: ActionMessageOptions): ActionMessage {

View File

@ -1,4 +1,4 @@
import { IframeApiContribution, queryWorkadventure, sendToWorkadventure } from "./IframeApiContribution"; import { IframeApiContribution, queryWorkadventure } from "./IframeApiContribution";
import { EmbeddedWebsite } from "./Room/EmbeddedWebsite"; import { EmbeddedWebsite } from "./Room/EmbeddedWebsite";
import type { CreateEmbeddedWebsiteEvent } from "../Events/EmbeddedWebsiteEvent"; import type { CreateEmbeddedWebsiteEvent } from "../Events/EmbeddedWebsiteEvent";

View File

@ -42,4 +42,4 @@ axiosWithRetry.interceptors.response.use((res) => {
return res; return res;
}); });
const interceptorId = rax.attach(axiosWithRetry); rax.attach(axiosWithRetry);

View File

@ -4,7 +4,7 @@ import { RoomConnection } from "./RoomConnection";
import type { OnConnectInterface, PositionInterface, ViewportInterface } from "./ConnexionModels"; import type { OnConnectInterface, PositionInterface, ViewportInterface } from "./ConnexionModels";
import { GameConnexionTypes, urlManager } from "../Url/UrlManager"; import { GameConnexionTypes, urlManager } from "../Url/UrlManager";
import { localUserStore } from "./LocalUserStore"; import { localUserStore } from "./LocalUserStore";
import { CharacterTexture, LocalUser } from "./LocalUser"; import { LocalUser } from "./LocalUser";
import { Room } from "./Room"; import { Room } from "./Room";
import { _ServiceWorker } from "../Network/ServiceWorker"; import { _ServiceWorker } from "../Network/ServiceWorker";
import { loginSceneVisibleIframeStore } from "../Stores/LoginSceneStore"; import { loginSceneVisibleIframeStore } from "../Stores/LoginSceneStore";
@ -13,7 +13,6 @@ import { analyticsClient } from "../Administration/AnalyticsClient";
import { axiosWithRetry } from "./AxiosUtils"; import { axiosWithRetry } from "./AxiosUtils";
import axios from "axios"; import axios from "axios";
import { isRegisterData } from "../Messages/JsonMessages/RegisterData"; import { isRegisterData } from "../Messages/JsonMessages/RegisterData";
import { isAdminApiData } from "../Messages/JsonMessages/AdminApiData";
import { limitMapStore } from "../Stores/GameStore"; import { limitMapStore } from "../Stores/GameStore";
import { showLimitRoomModalStore } from "../Stores/ModalStore"; import { showLimitRoomModalStore } from "../Stores/ModalStore";
import { gameManager } from "../Phaser/Game/GameManager"; import { gameManager } from "../Phaser/Game/GameManager";
@ -73,9 +72,7 @@ class ConnectionManager {
//Logout user in pusher and hydra //Logout user in pusher and hydra
const token = localUserStore.getAuthToken(); const token = localUserStore.getAuthToken();
const { authToken } = await Axios.get(`${PUSHER_URL}/logout-callback`, { params: { token } }).then( await Axios.get(`${PUSHER_URL}/logout-callback`, { params: { token } }).then((res) => res.data);
(res) => res.data
);
localUserStore.setAuthToken(null); localUserStore.setAuthToken(null);
//Go on login page can permit to clear token and start authentication process //Go on login page can permit to clear token and start authentication process
@ -139,13 +136,19 @@ class ConnectionManager {
//@deprecated //@deprecated
else if (this.connexionType === GameConnexionTypes.register) { else if (this.connexionType === GameConnexionTypes.register) {
const organizationMemberToken = urlManager.getOrganizationToken(); 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 (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."); throw new Error("Invalid data received from /register route.");
} }
const data = registerDataChecking.data;
this.localUser = new LocalUser(data.userUuid, data.email); this.localUser = new LocalUser(data.userUuid, data.email);
this.authToken = data.authToken; this.authToken = data.authToken;
localUserStore.saveUser(this.localUser); localUserStore.saveUser(this.localUser);
@ -306,9 +309,9 @@ class ConnectionManager {
connection.roomJoinedMessageStream.subscribe((connect: OnConnectInterface) => { connection.roomJoinedMessageStream.subscribe((connect: OnConnectInterface) => {
resolve(connect); resolve(connect);
}); });
}).catch((err) => { }).catch(() => {
// Let's retry in 4-6 seconds // Let's retry in 4-6 seconds
return new Promise<OnConnectInterface>((resolve, reject) => { return new Promise<OnConnectInterface>((resolve) => {
this.reconnectingTimeout = setTimeout(() => { this.reconnectingTimeout = setTimeout(() => {
//todo: allow a way to break recursion? //todo: allow a way to break recursion?
//todo: find a way to avoid recursive function. Otherwise, the call stack will grow indefinitely. //todo: find a way to avoid recursive function. Otherwise, the call stack will grow indefinitely.

View File

@ -5,7 +5,7 @@ import type { BodyResourceDescriptionInterface } from "../Phaser/Entity/PlayerTe
export interface PointInterface { export interface PointInterface {
x: number; x: number;
y: 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; moving: boolean;
} }

View File

@ -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 { CONTACT_URL, PUSHER_URL, DISABLE_ANONYMOUS, OPID_LOGIN_SCREEN_PROVIDER } from "../Enum/EnvironmentVariable";
import type { CharacterTexture } from "./LocalUser";
import { localUserStore } from "./LocalUserStore"; import { localUserStore } from "./LocalUserStore";
import axios from "axios"; import axios from "axios";
import { axiosWithRetry } from "./AxiosUtils"; import { axiosWithRetry } from "./AxiosUtils";
@ -112,11 +109,14 @@ export class Room {
data.authenticationMandatory = Boolean(data.authenticationMandatory); data.authenticationMandatory = Boolean(data.authenticationMandatory);
} }
if (isRoomRedirect(data)) { const roomRedirectChecking = isRoomRedirect.safeParse(data);
const mapDetailsDataChecking = isMapDetailsData.safeParse(data);
if (roomRedirectChecking.success) {
return { return {
redirectUrl: data.redirectUrl, redirectUrl: data.redirectUrl,
}; };
} else if (isMapDetailsData(data)) { } else if (mapDetailsDataChecking.success) {
console.log("Map ", this.id, " resolves to URL ", data.mapUrl); console.log("Map ", this.id, " resolves to URL ", data.mapUrl);
this._mapUrl = data.mapUrl; this._mapUrl = data.mapUrl;
this._group = data.group; this._group = data.group;
@ -132,6 +132,9 @@ export class Room {
this._loginSceneLogo = data.loginSceneLogo ?? undefined; this._loginSceneLogo = data.loginSceneLogo ?? undefined;
return new MapDetail(data.mapUrl); return new MapDetail(data.mapUrl);
} else { } 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."); throw new Error("Data received by the /map endpoint of the Pusher is not in a valid format.");
} }
} catch (e) { } catch (e) {

View File

@ -196,7 +196,7 @@ export class RoomConnection implements RoomConnection {
let interval: ReturnType<typeof setInterval> | undefined = undefined; let interval: ReturnType<typeof setInterval> | 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. //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(); const pingMessage = PingMessageTsProto.encode({}).finish();
interval = setInterval(() => this.socket.send(pingMessage), manualPingDelay); interval = setInterval(() => this.socket.send(pingMessage), manualPingDelay);
@ -303,6 +303,7 @@ export class RoomConnection implements RoomConnection {
} }
default: { default: {
// Security check: if we forget a "case", the line below will catch the error at compile-time. // 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; const tmp: never = subMessage;
} }
} }
@ -490,6 +491,7 @@ export class RoomConnection implements RoomConnection {
} }
default: { default: {
// Security check: if we forget a "case", the line below will catch the error at compile-time. // 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; const tmp: never = message;
} }
} }

View File

@ -4,7 +4,7 @@ import type { PointInterface } from "../Connexion/ConnexionModels";
export class ProtobufClientUtils { export class ProtobufClientUtils {
public static toPointInterface(position: PositionMessage): PointInterface { public static toPointInterface(position: PositionMessage): PointInterface {
let direction: string; let direction: "up" | "down" | "left" | "right";
switch (position.direction) { switch (position.direction) {
case PositionMessage_Direction.UP: case PositionMessage_Direction.UP:
direction = "up"; direction = "up";

View File

@ -1,9 +1,7 @@
import ImageFrameConfig = Phaser.Types.Loader.FileTypes.ImageFrameConfig;
import { DirtyScene } from "../Game/DirtyScene"; import { DirtyScene } from "../Game/DirtyScene";
import { gameManager } from "../Game/GameManager"; import { gameManager } from "../Game/GameManager";
import { SuperLoaderPlugin } from "../Services/SuperLoaderPlugin"; import { SuperLoaderPlugin } from "../Services/SuperLoaderPlugin";
import CancelablePromise from "cancelable-promise"; import CancelablePromise from "cancelable-promise";
import Image = Phaser.GameObjects.Image;
import Texture = Phaser.Textures.Texture; import Texture = Phaser.Textures.Texture;
const TextName: string = "Loading..."; const TextName: string = "Loading...";
@ -35,8 +33,6 @@ export class Loader {
const logoResource = gameManager.currentStartedRoom.loadingLogo ?? "static/images/logo.png"; const logoResource = gameManager.currentStartedRoom.loadingLogo ?? "static/images/logo.png";
this.logoNameIndex = "logoLoading" + logoResource; 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 //add loading if logo image until logo image is ready
this.loadingText = this.scene.add.text( this.loadingText = this.scene.add.text(
this.scene.game.renderer.width / 2, this.scene.game.renderer.width / 2,

View File

@ -1,6 +1,5 @@
import type { ITiledMapObject } from "../Map/ITiledMap"; import type { ITiledMapObject } from "../Map/ITiledMap";
import type { GameScene } from "../Game/GameScene"; import type { GameScene } from "../Game/GameScene";
import { type } from "os";
import { GameMapProperties } from "../Game/GameMapProperties"; import { GameMapProperties } from "../Game/GameMapProperties";
export class TextUtils { export class TextUtils {

View File

@ -1,9 +1,4 @@
import { import { getPlayerAnimations, PlayerAnimationDirections, PlayerAnimationTypes } from "../Player/Animation";
AnimationData,
getPlayerAnimations,
PlayerAnimationDirections,
PlayerAnimationTypes,
} from "../Player/Animation";
import { SpeechBubble } from "./SpeechBubble"; import { SpeechBubble } from "./SpeechBubble";
import Text = Phaser.GameObjects.Text; import Text = Phaser.GameObjects.Text;
import Container = Phaser.GameObjects.Container; import Container = Phaser.GameObjects.Container;

View File

@ -1,6 +1,5 @@
import LoaderPlugin = Phaser.Loader.LoaderPlugin; import LoaderPlugin = Phaser.Loader.LoaderPlugin;
import type { CharacterTexture } from "../../Connexion/LocalUser"; import { BodyResourceDescriptionInterface, PlayerTextures, PlayerTexturesKey } from "./PlayerTextures";
import { BodyResourceDescriptionInterface, mapLayerToLevel, PlayerTextures, PlayerTexturesKey } from "./PlayerTextures";
import CancelablePromise from "cancelable-promise"; import CancelablePromise from "cancelable-promise";
import { SuperLoaderPlugin } from "../Services/SuperLoaderPlugin"; import { SuperLoaderPlugin } from "../Services/SuperLoaderPlugin";
import Texture = Phaser.Textures.Texture; import Texture = Phaser.Textures.Texture;

View File

@ -97,6 +97,7 @@ export class CameraManager extends Phaser.Events.EventEmitter {
}); });
return; 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) => { this.camera.pan(setTo.x, setTo.y, duration, Easing.SineEaseOut, true, (camera, progress, x, y) => {
if (this.cameraMode === CameraMode.Positioned) { if (this.cameraMode === CameraMode.Positioned) {
this.waScaleManager.zoomModifier = currentZoomModifier + progress * zoomModifierChange; this.waScaleManager.zoomModifier = currentZoomModifier + progress * zoomModifierChange;
@ -138,6 +139,7 @@ export class CameraManager extends Phaser.Events.EventEmitter {
this.emit(CameraManagerEvent.CameraUpdate, this.getCameraUpdateEventData()); this.emit(CameraManagerEvent.CameraUpdate, this.getCameraUpdateEventData());
return; 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.camera.pan(focusOn.x, focusOn.y, duration, Easing.SineEaseOut, true, (camera, progress, x, y) => {
this.waScaleManager.zoomModifier = currentZoomModifier + progress * zoomModifierChange; this.waScaleManager.zoomModifier = currentZoomModifier + progress * zoomModifierChange;
if (progress === 1) { if (progress === 1) {

View File

@ -223,7 +223,7 @@ height,*/
} }
close(): void { close(): void {
for (const [key, website] of this.embeddedWebsites) { for (const website of this.embeddedWebsites.values()) {
if (website.allowApi) { if (website.allowApi) {
iframeListener.unregisterIframe(website.iframe); iframeListener.unregisterIframe(website.iframe);
} }

View File

@ -1,6 +1,4 @@
import { SKIP_RENDER_OPTIMIZATIONS } from "../../Enum/EnvironmentVariable"; import { SKIP_RENDER_OPTIMIZATIONS } from "../../Enum/EnvironmentVariable";
import { coWebsiteManager } from "../../WebRtc/CoWebsiteManager";
import { waScaleManager } from "../Services/WaScaleManager";
import { ResizableScene } from "../Login/ResizableScene"; import { ResizableScene } from "../Login/ResizableScene";
const Events = Phaser.Core.Events; const Events = Phaser.Core.Events;

View File

@ -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) { if (newValue) {
this.scene this.scene
.onMapExit( .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) { if (newValue) {
this.scene this.scene
.onMapExit(Room.getRoomPathFromExitUrl(newValue as string, window.location.toString())) .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 === "") { if (newValue === undefined || newValue === false || newValue === "") {
this.scene.connection?.setSilent(false); this.scene.connection?.setSilent(false);
this.scene.CurrentPlayer.noSilent(); this.scene.CurrentPlayer.noSilent();
@ -162,7 +162,7 @@ export class GameMapPropertiesListener {
}); });
// TODO: This legacy property should be removed at some point // 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 newValue === undefined
? audioManagerFileStore.unloadAudio() ? audioManagerFileStore.unloadAudio()
: audioManagerFileStore.playAudio(newValue, this.scene.getMapDirUrl(), undefined, true); : audioManagerFileStore.playAudio(newValue, this.scene.getMapDirUrl(), undefined, true);

View File

@ -97,7 +97,6 @@ import { startLayerNamesStore } from "../../Stores/StartLayerNamesStore";
import { JitsiCoWebsite } from "../../WebRtc/CoWebsite/JitsiCoWebsite"; import { JitsiCoWebsite } from "../../WebRtc/CoWebsite/JitsiCoWebsite";
import { SimpleCoWebsite } from "../../WebRtc/CoWebsite/SimpleCoWebsite"; import { SimpleCoWebsite } from "../../WebRtc/CoWebsite/SimpleCoWebsite";
import type { CoWebsite } from "../../WebRtc/CoWebsite/CoWesbite"; import type { CoWebsite } from "../../WebRtc/CoWebsite/CoWesbite";
import type { VideoPeer } from "../../WebRtc/VideoPeer";
import CancelablePromise from "cancelable-promise"; import CancelablePromise from "cancelable-promise";
import { Deferred } from "ts-deferred"; import { Deferred } from "ts-deferred";
import { SuperLoaderPlugin } from "../Services/SuperLoaderPlugin"; import { SuperLoaderPlugin } from "../Services/SuperLoaderPlugin";
@ -178,6 +177,7 @@ export class GameScene extends DirtyScene {
private localVolumeStoreUnsubscriber: Unsubscriber | undefined; private localVolumeStoreUnsubscriber: Unsubscriber | undefined;
private followUsersColorStoreUnsubscribe!: Unsubscriber; private followUsersColorStoreUnsubscribe!: Unsubscriber;
private currentPlayerGroupIdStoreUnsubscribe!: Unsubscriber;
private privacyShutdownStoreUnsubscribe!: Unsubscriber; private privacyShutdownStoreUnsubscribe!: Unsubscriber;
private userIsJitsiDominantSpeakerStoreUnsubscriber!: Unsubscriber; private userIsJitsiDominantSpeakerStoreUnsubscriber!: Unsubscriber;
private jitsiParticipantsCountStoreUnsubscriber!: Unsubscriber; private jitsiParticipantsCountStoreUnsubscriber!: Unsubscriber;
@ -190,7 +190,7 @@ export class GameScene extends DirtyScene {
currentTick!: number; currentTick!: number;
lastSentTick!: number; // The last tick at which a position was sent. lastSentTick!: number; // The last tick at which a position was sent.
lastMoveEventSent: HasPlayerMovedEvent = { lastMoveEventSent: HasPlayerMovedEvent = {
direction: "", direction: "down",
moving: false, moving: false,
x: -1000, x: -1000,
y: -1000, y: -1000,
@ -222,8 +222,8 @@ export class GameScene extends DirtyScene {
private loader: Loader; private loader: Loader;
private lastCameraEvent: WasCameraUpdatedEvent | undefined; private lastCameraEvent: WasCameraUpdatedEvent | undefined;
private firstCameraUpdateSent: boolean = false; private firstCameraUpdateSent: boolean = false;
private showVoiceIndicatorChangeMessageSent: boolean = false;
private currentPlayerGroupId?: number; private currentPlayerGroupId?: number;
private showVoiceIndicatorChangeMessageSent: boolean = false;
private jitsiDominantSpeaker: boolean = false; private jitsiDominantSpeaker: boolean = false;
private jitsiParticipantsCount: number = 0; private jitsiParticipantsCount: number = 0;
public readonly superLoad: SuperLoaderPlugin; public readonly superLoad: SuperLoaderPlugin;
@ -843,6 +843,10 @@ export class GameScene extends DirtyScene {
this.currentPlayerGroupId = message.groupId; this.currentPlayerGroupId = message.groupId;
}); });
this.connection.groupUsersUpdateMessageStream.subscribe((message) => {
this.currentPlayerGroupId = message.groupId;
});
/** /**
* Triggered when we receive the JWT token to connect to Jitsi * Triggered when we receive the JWT token to connect to Jitsi
*/ */
@ -1046,6 +1050,7 @@ ${escapedMessage}
}, 100); }, 100);
id = 0; id = 0;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
for (const button of openPopupEvent.buttons) { for (const button of openPopupEvent.buttons) {
const button = HtmlUtils.getElementByIdOrFail<HTMLButtonElement>( const button = HtmlUtils.getElementByIdOrFail<HTMLButtonElement>(
`popup-${openPopupEvent.popupId}-${id}` `popup-${openPopupEvent.popupId}-${id}`
@ -1304,7 +1309,7 @@ ${escapedMessage}
await this.connectionAnswerPromiseDeferred.promise; await this.connectionAnswerPromiseDeferred.promise;
return { return {
mapUrl: this.MapUrlFile, mapUrl: this.MapUrlFile,
startLayerName: this.startPositionCalculator.startLayerName, startLayerName: this.startPositionCalculator.startLayerName ?? undefined,
uuid: localUserStore.getLocalUser()?.uuid, uuid: localUserStore.getLocalUser()?.uuid,
nickname: this.playerName, nickname: this.playerName,
language: get(locale), language: get(locale),
@ -1413,6 +1418,7 @@ ${escapedMessage}
break; break;
} }
default: { default: {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const _exhaustiveCheck: never = event.target; const _exhaustiveCheck: never = event.target;
} }
} }
@ -1432,7 +1438,7 @@ ${escapedMessage}
this.connection?.emitPlayerOutlineColor(color); this.connection?.emitPlayerOutlineColor(color);
}); });
iframeListener.registerAnswerer("removePlayerOutline", (message) => { iframeListener.registerAnswerer("removePlayerOutline", () => {
this.CurrentPlayer.removeApiOutlineColor(); this.CurrentPlayer.removeApiOutlineColor();
this.connection?.emitPlayerOutlineColor(null); this.connection?.emitPlayerOutlineColor(null);
}); });
@ -1715,6 +1721,7 @@ ${escapedMessage}
private createCollisionWithPlayer() { private createCollisionWithPlayer() {
//add collision layer //add collision layer
for (const phaserLayer of this.gameMap.phaserLayers) { 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.physics.add.collider(this.CurrentPlayer, phaserLayer, (object1: GameObject, object2: GameObject) => {
//this.CurrentPlayer.say("Collision with layer : "+ (object2 as Tile).layer.name) //this.CurrentPlayer.say("Collision with layer : "+ (object2 as Tile).layer.name)
}); });
@ -1765,9 +1772,11 @@ ${escapedMessage}
emoteMenuStore.openEmoteMenu(); 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.on(Phaser.Input.Events.POINTER_OVER, (pointer: Phaser.Input.Pointer) => {
this.CurrentPlayer.pointerOverOutline(0x365dff); 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.on(Phaser.Input.Events.POINTER_OUT, (pointer: Phaser.Input.Pointer) => {
this.CurrentPlayer.pointerOutOutline(); this.CurrentPlayer.pointerOutOutline();
}); });
@ -1869,6 +1878,7 @@ ${escapedMessage}
break; break;
} }
default: { default: {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const tmp: never = event; const tmp: never = event;
} }
} }
@ -2030,8 +2040,6 @@ ${escapedMessage}
this.currentTick, this.currentTick,
{ {
...message.position, ...message.position,
oldX: undefined,
oldY: undefined,
}, },
this.currentTick + POSITION_DELAY this.currentTick + POSITION_DELAY
); );

View File

@ -4,14 +4,13 @@ import type { ITiledMapObject } from "../../Map/ITiledMap";
import type { ItemFactoryInterface } from "../ItemFactoryInterface"; import type { ItemFactoryInterface } from "../ItemFactoryInterface";
import type { GameScene } from "../../Game/GameScene"; import type { GameScene } from "../../Game/GameScene";
import { ActionableItem } from "../ActionableItem"; import { ActionableItem } from "../ActionableItem";
import * as tg from "generic-type-guard"; import { z } from "zod";
const isComputerState = new tg.IsInterface() export const isComputerState = z.object({
.withProperties({ status: z.string(),
status: tg.isString, });
})
.get(); export type ComputerState = z.infer<typeof isComputerState>;
type ComputerState = tg.GuardedType<typeof isComputerState>;
let state: ComputerState = { let state: ComputerState = {
status: "off", status: "off",
@ -55,10 +54,14 @@ export default {
}, },
factory: (scene: GameScene, object: ITiledMapObject, initState: unknown): ActionableItem => { factory: (scene: GameScene, object: ITiledMapObject, initState: unknown): ActionableItem => {
if (initState !== undefined) { if (initState !== undefined) {
if (!isComputerState(initState)) { try {
throw new Error("Invalid state received for computer object"); 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"); const computer = new Sprite(scene, object.x, object.y, "computer");

View File

@ -112,6 +112,7 @@ export class CustomizeScene extends AbstractCharacterScene {
this.onResize(); this.onResize();
} }
// eslint-disable-next-line @typescript-eslint/no-unused-vars
public update(time: number, dt: number): void { public update(time: number, dt: number): void {
this.customWokaPreviewer.update(); this.customWokaPreviewer.update();
} }

View File

@ -13,5 +13,6 @@ export class EmptyScene extends Scene {
create() {} create() {}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
update(time: number, delta: number): void {} update(time: number, delta: number): void {}
} }

Some files were not shown because too many files have changed in this diff Show More