Merge pull request #1581 from thecodingmachine/develop

Deploy 2021-12-24
This commit is contained in:
David Négrier 2021-11-24 13:36:11 +01:00 committed by GitHub
commit ced08e69c9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
282 changed files with 15021 additions and 9304 deletions

View file

@ -5,7 +5,7 @@ JITSI_PRIVATE_MODE=false
JITSI_ISS=
SECRET_JITSI_KEY=
ADMIN_API_TOKEN=123
START_ROOM_URL=/_/global/maps.workadventure.localhost/Floor0/floor0.json
START_ROOM_URL=/_/global/maps.workadventure.localhost/starter/map.json
# If your Turn server is configured to use the Turn REST API, you should put the shared auth secret here.
# If you are using Coturn, this is the value of the "static-auth-secret" parameter in your coturn config file.
# Keep empty if you are sharing hard coded / clear text credentials.
@ -22,3 +22,10 @@ MAX_USERNAME_LENGTH=8
OPID_CLIENT_ID=
OPID_CLIENT_SECRET=
OPID_CLIENT_ISSUER=
OPID_CLIENT_REDIRECT_URL=
OPID_LOGIN_SCREEN_PROVIDER=http://pusher.workadventure.localhost/login-screen
OPID_PROFILE_SCREEN_PROVIDER=
DISABLE_ANONYMOUS=
# If you want to have a contact page in your menu, you MUST set CONTACT_URL to the URL of the page that you want
CONTACT_URL=

View file

@ -190,6 +190,8 @@ jobs:
SECRET_JITSI_KEY: ${{ secrets.SECRET_JITSI_KEY }}
TURN_STATIC_AUTH_SECRET: ${{ secrets.TURN_STATIC_AUTH_SECRET }}
DEPLOY_REF: ${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }}
POSTHOG_API_KEY: ${{ secrets.POSTHOG_API_KEY }}
POSTHOG_URL: ${{ secrets.POSTHOG_URL }}
with:
namespace: workadventure-${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }}

51
.github/workflows/end_to_end_tests.yml vendored Normal file
View file

@ -0,0 +1,51 @@
# https://help.github.com/en/categories/automating-your-workflow-with-github-actions
name: "End to end tests"
on:
push:
branches:
- master
- develop
pull_request:
jobs:
end-to-end-tests:
name: "End-to-end testcafe tests"
runs-on: "ubuntu-latest"
steps:
- name: "Checkout"
uses: "actions/checkout@v2.0.0"
- name: "Setup .env file"
run: cp .env.template .env
- name: "Edit ownership of file for test cases"
run: sudo chown 1000:1000 -R .
- name: "Start environment"
run: docker-compose up -d
- name: "Wait for environment to build (and downloading testcafe image)"
run: (docker-compose -f docker-compose.testcafe.yml pull &) && docker-compose logs -f --tail=0 front | grep -m 1 "Compiled successfully"
- name: "Run tests"
run: docker-compose -f docker-compose.testcafe.yml up --exit-code-from testcafe
- name: Upload failed tests
if: ${{ failure() }}
uses: actions/upload-artifact@v2
with:
name: my-artifact
path: './tests/screenshots/'
- name: Display state
if: ${{ failure() }}
run: docker-compose ps
- name: Display logs
if: ${{ failure() }}
run: docker-compose logs

View file

@ -1,11 +1,50 @@
## Version develop
### Updates
- Added multi Co-Website management
### Bugfix
- Moving a discussion over a user will now add this user to the discussion
- Being in a silent zone new forces mediaConstraints to false (#1508)
- Fixes for the emote menu (#1501)
- Fixing chat message attributed to wrong user (#1507 #1528)
## Version 1.5.0
### Updates
- Added support for login with OpenID Connect
- New scripting library available to extend WorkAdventure: see [Scripting API Extra](https://github.com/workadventure/scripting-api-extra/)
- New menu design!
- New `openTab` property (#1419)
- Possible integration with Posthog (#1458)
### Bugfix
- Fixing layers flattened several times (#1427 @Lurkars)
- Fixing CSS of video elements
- Chat now scrolls to bottom when opened (#1450)
- Fixing silent zone not respected when exiting from Jitsi (#1456)
- Fixing "yarn install" failing because of missing rights on some Docker installs (#1457)
- Fixing audio not shut down when exiting a room (#1459)
### Misc
- Finished migrating "Build your map" documentation into the "/docs" directory of this repository (#1417 #1385)
- Refactoring documentation (dedicated page for variables) (#1414)
- Front container code is now completely linted (#1413)
## Version 1.4.15
### Updates
- New scripting API features :
- Use `WA.ui.registerMenuCommand(commandDescriptor: string, options: MenuOptions): Menu` to add a custom menu or an iframe to the menu.
- New `jitsiWidth` parameter to set the width of Jitsi
- New `jitsiWidth` parameter to set the width of Jitsi and Cowebsite (#1398 @tabascoeye)
- Refactored the way videos are displayed to better cope for vertical videos (on mobile)
- Fixing reconnection issues after 5 minutes of an inactive tab on Google Chrome
- Changes performed in `WA.room.setPropertyLayer` now have a real-time impact (#1395)
### Bugfixes
- Fixing streams in bubbles sometimes improperly muted when there are more than 2 people in the bubble (#1400 #1402)
- Properly displaying carriage returns in popups (#1388)
- `WA.state` now answers correctly to "in" keyword (#1393)
- Variables can now be nested in group layers (#1406)
## Version 1.4.14

Binary file not shown.

Before

Width:  |  Height:  |  Size: 386 KiB

1
README-LOGO.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 16 KiB

BIN
README-MAP.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

View file

@ -1,17 +1,18 @@
![](https://github.com/thecodingmachine/workadventure/workflows/Continuous%20Integration/badge.svg) [![Discord](https://img.shields.io/discord/821338762134290432?label=Discord)](https://discord.gg/YGtngdh9gt)
![WorkAdventure landscape image](README-INTRO.jpg)
![WorkAdventure logo](README-LOGO.svg)
![WorkAdventure office image](README-MAP.png)
Demo here : [https://workadventu.re/](https://workadventu.re/).
Live demo [here](https://play.workadventu.re/@/tcm/workadventure/wa-village).
# Work Adventure
# WorkAdventure
Work Adventure is a web-based collaborative workspace for small to medium teams (2-100 people) presented in the form of a
WorkAdventure is a web-based collaborative workspace presented in the form of a
16-bit video game.
In Work Adventure, you can move around your office and talk to your colleagues (using a video-chat feature that is
triggered when you move next to a colleague).
In WorkAdventure you can move around your office and talk to your colleagues (using a video-chat system, triggered when you approach someone).
See more features for your virtual office: https://workadventu.re/virtual-office
## Setting up a development environment
@ -20,6 +21,7 @@ Install Docker.
Run:
```
cp .env.template .env
docker-compose up -d
```
@ -36,7 +38,7 @@ Note: on some OSes, you will need to add this line to your `/etc/hosts` file:
```
Note: If on the first run you get a page with "network error". Try to ``docker-compose stop`` , then ``docker-compose start``.
Note 2: If you are still getting "network error". Make sure you are authorizing the self-signed certificate by entering https://pusher.workadventure.testing and accepting them.
Note 2: If you are still getting "network error". Make sure you are authorizing the self-signed certificate by entering https://pusher.workadventure.localhost and accepting them.
### MacOS developers, your environment with Vagrant

View file

@ -40,8 +40,8 @@
},
"homepage": "https://github.com/thecodingmachine/workadventure#readme",
"dependencies": {
"@workadventure/tiled-map-type-guard": "^1.0.2",
"axios": "^0.21.1",
"@workadventure/tiled-map-type-guard": "^1.0.3",
"axios": "^0.21.2",
"busboy": "^0.3.1",
"circular-json": "^0.5.9",
"debug": "^4.3.1",

View file

@ -14,6 +14,7 @@ export const MAX_PER_GROUP = parseInt(process.env.MAX_PER_GROUP || "4");
export const REDIS_HOST = process.env.REDIS_HOST || undefined;
export const REDIS_PORT = parseInt(process.env.REDIS_PORT || "6379") || 6379;
export const REDIS_PASSWORD = process.env.REDIS_PASSWORD || undefined;
export const STORE_VARIABLES_FOR_LOCAL_MAPS = process.env.STORE_VARIABLES_FOR_LOCAL_MAPS === "true";
export {
MINIMUM_DISTANCE,

View file

@ -26,6 +26,7 @@ import { VariablesManager } from "../Services/VariablesManager";
import { ADMIN_API_URL } from "../Enum/EnvironmentVariable";
import { LocalUrlError } from "../Services/LocalUrlError";
import { emitErrorOnRoomSocket } from "../Services/MessageHelpers";
import { VariableError } from "../Services/VariableError";
export type ConnectCallback = (user: User, group: Group) => void;
export type DisconnectCallback = (user: User, group: Group) => void;
@ -181,6 +182,7 @@ export class GameRoom {
private updateUserGroup(user: User): void {
user.group?.updatePosition();
user.group?.searchForNearbyUsers();
if (user.silent) {
return;
@ -206,6 +208,7 @@ export class GameRoom {
const group: Group = new Group(
this.roomUrl,
[user, closestUser],
this.groupRadius,
this.connectCallback,
this.disconnectCallback,
this.positionNotifier
@ -334,30 +337,62 @@ export class GameRoom {
// First, let's check if "user" is allowed to modify the variable.
const variableManager = await this.getVariableManager();
const readableBy = variableManager.setVariable(name, value, user);
try {
const readableBy = variableManager.setVariable(name, value, user);
// If the variable was not changed, let's not dispatch anything.
if (readableBy === false) {
return;
}
// If the variable was not changed, let's not dispatch anything.
if (readableBy === false) {
return;
}
// TODO: should we batch those every 100ms?
const variableMessage = new VariableWithTagMessage();
variableMessage.setName(name);
variableMessage.setValue(value);
if (readableBy) {
variableMessage.setReadableby(readableBy);
}
// TODO: should we batch those every 100ms?
const variableMessage = new VariableWithTagMessage();
variableMessage.setName(name);
variableMessage.setValue(value);
if (readableBy) {
variableMessage.setReadableby(readableBy);
}
const subMessage = new SubToPusherRoomMessage();
subMessage.setVariablemessage(variableMessage);
const subMessage = new SubToPusherRoomMessage();
subMessage.setVariablemessage(variableMessage);
const batchMessage = new BatchToPusherRoomMessage();
batchMessage.addPayload(subMessage);
const batchMessage = new BatchToPusherRoomMessage();
batchMessage.addPayload(subMessage);
// Dispatch the message on the room listeners
for (const socket of this.roomListeners) {
socket.write(batchMessage);
// Dispatch the message on the room listeners
for (const socket of this.roomListeners) {
socket.write(batchMessage);
}
} catch (e) {
if (e instanceof VariableError) {
// Ok, we have an error setting a variable. Either the user is trying to hack the map... or the map
// is not up to date. So let's try to reload the map from scratch.
if (this.variableManagerLastLoad === undefined) {
throw e;
}
const lastLoaded = new Date().getTime() - this.variableManagerLastLoad.getTime();
if (lastLoaded < 10000) {
console.log(
'An error occurred while setting the "' +
name +
"\" variable. But we tried to reload the map less than 10 seconds ago, so let's fail."
);
// Do not try to reload if we tried to reload less than 10 seconds ago.
throw e;
}
// Reset the variable manager
this.variableManagerPromise = undefined;
this.mapPromise = undefined;
console.log(
'An error occurred while setting the "' + name + "\" variable. Let's reload the map and try again"
);
// Try to set the variable again!
await this.setVariable(name, value, user);
} else {
throw e;
}
}
}
@ -447,9 +482,11 @@ export class GameRoom {
}
private variableManagerPromise: Promise<VariablesManager> | undefined;
private variableManagerLastLoad: Date | undefined;
private getVariableManager(): Promise<VariablesManager> {
if (!this.variableManagerPromise) {
this.variableManagerLastLoad = new Date();
this.variableManagerPromise = this.getMap()
.then((map) => {
const variablesManager = new VariablesManager(this.roomUrl, map);

View file

@ -1,10 +1,10 @@
import { ConnectCallback, DisconnectCallback } from "./GameRoom";
import { ConnectCallback, DisconnectCallback, GameRoom } from "./GameRoom";
import { User } from "./User";
import { PositionInterface } from "_Model/PositionInterface";
import { Movable } from "_Model/Movable";
import { PositionNotifier } from "_Model/PositionNotifier";
import { gaugeManager } from "../Services/GaugeManager";
import { MAX_PER_GROUP } from "../Enum/EnvironmentVariable";
import type { Zone } from "../Model/Zone";
export class Group implements Movable {
private static nextId: number = 1;
@ -13,13 +13,14 @@ export class Group implements Movable {
private users: Set<User>;
private x!: number;
private y!: number;
private hasEditedGauge: boolean = false;
private wasDestroyed: boolean = false;
private roomId: string;
private currentZone: Zone | null = null;
constructor(
roomId: string,
users: User[],
private groupRadius: number,
private connectCallback: ConnectCallback,
private disconnectCallback: DisconnectCallback,
private positionNotifier: PositionNotifier
@ -28,13 +29,6 @@ export class Group implements Movable {
this.users = new Set<User>();
this.id = Group.nextId;
Group.nextId++;
//we only send a event for prometheus metrics if the group lives more than 5 seconds
setTimeout(() => {
if (!this.wasDestroyed) {
this.hasEditedGauge = true;
gaugeManager.incNbGroupsPerRoomGauge(roomId);
}
}, 5000);
users.forEach((user: User) => {
this.join(user);
@ -85,9 +79,22 @@ export class Group implements Movable {
this.y = y;
if (oldX === undefined) {
this.positionNotifier.enter(this);
this.currentZone = this.positionNotifier.enter(this);
} else {
this.positionNotifier.updatePosition(this, { x, y }, { x: oldX, y: oldY });
this.currentZone = this.positionNotifier.updatePosition(this, { x, y }, { x: oldX, y: oldY });
}
}
searchForNearbyUsers(): void {
if (!this.currentZone) return;
for (const user of this.positionNotifier.getAllUsersInSquareAroundZone(this.currentZone)) {
if (user.group || this.isFull()) return; //we ignore users that are already in a group.
const distance = GameRoom.computeDistanceBetweenPositions(user.getPosition(), this.getPosition());
if (distance < this.groupRadius) {
this.join(user);
this.updatePosition();
}
}
}
@ -126,7 +133,6 @@ export class Group implements Movable {
* Usually used when there is only one user left.
*/
destroy(): void {
if (this.hasEditedGauge) gaugeManager.decNbGroupsPerRoomGauge(this.roomId);
for (const user of this.users) {
this.leave(user);
}

View file

@ -12,7 +12,7 @@ import { EmoteCallback, EntersCallback, LeavesCallback, MovesCallback, Zone } fr
import { Movable } from "_Model/Movable";
import { PositionInterface } from "_Model/PositionInterface";
import { ZoneSocket } from "../RoomManager";
import { User } from "_Model/User";
import { User } from "../Model/User";
import { EmoteEventMessage } from "../Messages/generated/messages_pb";
interface ZoneDescriptor {
@ -20,6 +20,17 @@ interface ZoneDescriptor {
j: number;
}
export function* getNearbyDescriptorsMatrix(middleZoneDescriptor: ZoneDescriptor): Generator<ZoneDescriptor> {
for (let n = 0; n < 9; n++) {
const i = middleZoneDescriptor.i + ((n % 3) - 1);
const j = middleZoneDescriptor.j + (Math.floor(n / 3) - 1);
if (i >= 0 && j >= 0) {
yield { i, j };
}
}
}
export class PositionNotifier {
// TODO: we need a way to clean the zones if no one is in the zone and no one listening (to free memory!)
@ -41,14 +52,15 @@ export class PositionNotifier {
};
}
public enter(thing: Movable): void {
public enter(thing: Movable): Zone {
const position = thing.getPosition();
const zoneDesc = this.getZoneDescriptorFromCoordinates(position.x, position.y);
const zone = this.getZone(zoneDesc.i, zoneDesc.j);
zone.enter(thing, null, position);
return zone;
}
public updatePosition(thing: Movable, newPosition: PositionInterface, oldPosition: PositionInterface): void {
public updatePosition(thing: Movable, newPosition: PositionInterface, oldPosition: PositionInterface): Zone {
// Did we change zone?
const oldZoneDesc = this.getZoneDescriptorFromCoordinates(oldPosition.x, oldPosition.y);
const newZoneDesc = this.getZoneDescriptorFromCoordinates(newPosition.x, newPosition.y);
@ -62,9 +74,11 @@ export class PositionNotifier {
// Enter new zone
newZone.enter(thing, oldZone, newPosition);
return newZone;
} else {
const zone = this.getZone(oldZoneDesc.i, oldZoneDesc.j);
zone.move(thing, newPosition);
return zone;
}
}
@ -106,4 +120,16 @@ export class PositionNotifier {
const zone = this.getZone(zoneDesc.i, zoneDesc.j);
zone.emitEmoteEvent(emoteEventMessage);
}
public *getAllUsersInSquareAroundZone(zone: Zone): Generator<User> {
const zoneDescriptor = this.getZoneDescriptorFromCoordinates(zone.x, zone.y);
for (const d of getNearbyDescriptorsMatrix(zoneDescriptor)) {
const zone = this.getZone(d.i, d.j);
for (const thing of zone.getThings()) {
if (thing instanceof User) {
yield thing;
}
}
}
}
}

View file

@ -52,15 +52,6 @@ class GaugeManager {
this.nbClientsGauge.dec();
this.nbClientsPerRoomGauge.dec({ room: roomId });
}
incNbGroupsPerRoomGauge(roomId: string): void {
this.nbGroupsPerRoomCounter.inc({ room: roomId });
this.nbGroupsPerRoomGauge.inc({ room: roomId });
}
decNbGroupsPerRoomGauge(roomId: string): void {
this.nbGroupsPerRoomGauge.dec({ room: roomId });
}
}
export const gaugeManager = new GaugeManager();

View file

@ -5,12 +5,13 @@ import { promisify } from "util";
import { LocalUrlError } from "./LocalUrlError";
import { ITiledMap } from "@workadventure/tiled-map-type-guard";
import { isTiledMap } from "@workadventure/tiled-map-type-guard/dist";
import { STORE_VARIABLES_FOR_LOCAL_MAPS } from "../Enum/EnvironmentVariable";
class MapFetcher {
async fetchMap(mapUrl: string): Promise<ITiledMap> {
// Before trying to make the query, let's verify the map is actually on the open internet (and not a local test map)
if (await this.isLocalUrl(mapUrl)) {
if ((await this.isLocalUrl(mapUrl)) && !STORE_VARIABLES_FOR_LOCAL_MAPS) {
throw new LocalUrlError('URL for map "' + mapUrl + '" targets a local map');
}

View file

@ -0,0 +1,9 @@
/**
* Errors related to variable handling.
*/
export class VariableError extends Error {
constructor(message: string) {
super(message);
Object.setPrototypeOf(this, VariableError.prototype);
}
}

View file

@ -10,6 +10,7 @@ import {
import { User } from "_Model/User";
import { variablesRepository } from "./Repository/VariablesRepository";
import { redisClient } from "./RedisClient";
import { VariableError } from "./VariableError";
interface Variable {
defaultValue?: string;
@ -174,11 +175,13 @@ export class VariablesManager {
if (this.variableObjects) {
variableObject = this.variableObjects.get(name);
if (variableObject === undefined) {
throw new Error('Trying to set a variable "' + name + '" that is not defined as an object in the map.');
throw new VariableError(
'Trying to set a variable "' + name + '" that is not defined as an object in the map.'
);
}
if (variableObject.writableBy && !user.tags.includes(variableObject.writableBy)) {
throw new Error(
throw new VariableError(
'Trying to set a variable "' +
name +
'". User "' +

View file

@ -0,0 +1,67 @@
import "jasmine";
import { getNearbyDescriptorsMatrix } from "../src/Model/PositionNotifier";
describe("getNearbyDescriptorsMatrix", () => {
it("should create a matrix of coordinates in a square around the parameter", () => {
const matrix = [];
for (const d of getNearbyDescriptorsMatrix({ i: 1, j: 1 })) {
matrix.push(d);
}
expect(matrix).toEqual([
{ i: 0, j: 0 },
{ i: 1, j: 0 },
{ i: 2, j: 0 },
{ i: 0, j: 1 },
{ i: 1, j: 1 },
{ i: 2, j: 1 },
{ i: 0, j: 2 },
{ i: 1, j: 2 },
{ i: 2, j: 2 },
]);
});
it("should create a matrix of coordinates in a square around the parameter bis", () => {
const matrix = [];
for (const d of getNearbyDescriptorsMatrix({ i: 8, j: 3 })) {
matrix.push(d);
}
expect(matrix).toEqual([
{ i: 7, j: 2 },
{ i: 8, j: 2 },
{ i: 9, j: 2 },
{ i: 7, j: 3 },
{ i: 8, j: 3 },
{ i: 9, j: 3 },
{ i: 7, j: 4 },
{ i: 8, j: 4 },
{ i: 9, j: 4 },
]);
});
it("should not create a matrix with negative coordinates", () => {
const matrix = [];
for (const d of getNearbyDescriptorsMatrix({ i: 0, j: 0 })) {
matrix.push(d);
}
expect(matrix).toEqual([
{ i: 0, j: 0 },
{ i: 1, j: 0 },
{ i: 0, j: 1 },
{ i: 1, j: 1 },
]);
});
/*it("should not create a matrix with coordinates bigger than its dimmensions", () => {
const matrix = getNearbyDescriptorsMatrix({i: 4, j: 4}, 5, 5);
expect(matrix).toEqual([
{i: 3,j: 3},
{i: 4,j: 3},
{i: 3,j: 4},
{i: 4,j: 4},
])
});*/
});

View file

@ -194,10 +194,10 @@
semver "^7.3.2"
tsutils "^3.17.1"
"@workadventure/tiled-map-type-guard@^1.0.2":
version "1.0.2"
resolved "https://registry.yarnpkg.com/@workadventure/tiled-map-type-guard/-/tiled-map-type-guard-1.0.2.tgz#4171550f6cd71be19791faef48360d65d698bcb0"
integrity sha512-RCtygGV5y9cb7QoyGMINBE9arM5pyXjkxvXgA5uXEv4GDbXKorhFim/rHgwbVR+eFnVF3rDgWbRnk3DIaHt+lQ==
"@workadventure/tiled-map-type-guard@^1.0.3":
version "1.0.3"
resolved "https://registry.yarnpkg.com/@workadventure/tiled-map-type-guard/-/tiled-map-type-guard-1.0.3.tgz#62c2061cacbe1360b84162af0b7e4639ed8bfa7e"
integrity sha512-pUMxBBZHYAFkpnGWZAVAE8+M+Wn9UtzqZhXvBBBbB1gEakHIka7ahdTGfh0DgRaWrVszVXOP3tf49Dhdmn9pDg==
dependencies:
generic-type-guard "^3.4.1"
@ -381,12 +381,12 @@ atob@^2.1.2:
resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9"
integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==
axios@^0.21.1:
version "0.21.1"
resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.1.tgz#22563481962f4d6bde9a76d516ef0e5d3c09b2b8"
integrity sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==
axios@^0.21.2:
version "0.21.2"
resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.2.tgz#21297d5084b2aeeb422f5d38e7be4fbb82239017"
integrity sha512-87otirqUw3e8CzHTMO+/9kh/FSgXt/eVDvipijwDtEuwbkySWZ9SBm6VEubmJ/kLKEoLQV/POhxXFb66bfekfg==
dependencies:
follow-redirects "^1.10.0"
follow-redirects "^1.14.0"
balanced-match@^1.0.0:
version "1.0.0"
@ -1142,10 +1142,10 @@ flatted@^2.0.0:
resolved "https://registry.yarnpkg.com/flatted/-/flatted-2.0.2.tgz#4575b21e2bcee7434aa9be662f4b7b5f9c2b5138"
integrity sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==
follow-redirects@^1.10.0:
version "1.13.0"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.13.0.tgz#b42e8d93a2a7eea5ed88633676d6597bc8e384db"
integrity sha512-aq6gF1BEKje4a9i9+5jimNFIpq4Q1WiwBToeRK5NvZBd/TRsmW8BsJfOEGkr76TbOyPVD3OVDN910EcUNtRYEA==
follow-redirects@^1.14.0:
version "1.14.4"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.4.tgz#838fdf48a8bbdd79e52ee51fb1c94e3ed98b9379"
integrity sha512-zwGkiSXC1MUJG/qmeIFH2HBJx9u0V46QGUe3YR1fXG8bXQxq7fLj0RjLZQ5nubr9qNJUZrH+xUcwXEoXNpfS+g==
for-in@^1.0.2:
version "1.0.2"

View file

@ -38,6 +38,7 @@ services:
JITSI_URL: $JITSI_URL
JITSI_PRIVATE_MODE: "$JITSI_PRIVATE_MODE"
PUSHER_URL: //pusher.${DOMAIN}
ICON_URL: //icon.${DOMAIN}
TURN_SERVER: "${TURN_SERVER}"
TURN_USER: "${TURN_USER}"
TURN_PASSWORD: "${TURN_PASSWORD}"
@ -98,3 +99,15 @@ services:
- "traefik.http.routers.back-ssl.service=back"
- "traefik.http.routers.back-ssl.tls.certresolver=myresolver"
restart: unless-stopped
icon:
image: matthiasluedtke/iconserver:v3.13.0
labels:
- "traefik.http.routers.icon.rule=Host(`icon.${DOMAIN}`)"
- "traefik.http.routers.icon.entryPoints=web,traefik"
- "traefik.http.services.icon.loadbalancer.server.port=8080"
- "traefik.http.routers.icon-ssl.rule=Host(`icon.${DOMAIN}`)"
- "traefik.http.routers.icon-ssl.entryPoints=websecure"
- "traefik.http.routers.icon-ssl.tls=true"
- "traefik.http.routers.icon-ssl.service=icon"
- "traefik.http.routers.icon-ssl.tls.certresolver=myresolver"

View file

@ -4,7 +4,7 @@
local tag = namespace,
local url = namespace+".test.workadventu.re",
// develop branch does not use admin because of issue with SSL certificate of admin as of now.
local adminUrl = if namespace == "master" || namespace == "develop" || std.startsWith(namespace, "admin") then "https://"+url else null,
local adminUrl = if std.startsWith(namespace, "admin") then "https://"+url else null,
"$schema": "https://raw.githubusercontent.com/thecodingmachine/deeployer/master/deeployer.schema.json",
"version": "1.0",
"containers": {
@ -17,7 +17,6 @@
"ports": [8080, 50051],
"env": {
"SECRET_KEY": "tempSecretKeyNeedsToChange",
"ADMIN_API_TOKEN": env.ADMIN_API_TOKEN,
"JITSI_ISS": env.JITSI_ISS,
"JITSI_URL": env.JITSI_URL,
"SECRET_JITSI_KEY": env.SECRET_JITSI_KEY,
@ -25,6 +24,7 @@
"REDIS_HOST": "redis",
} + (if adminUrl != null then {
"ADMIN_API_URL": adminUrl,
"ADMIN_API_TOKEN": env.ADMIN_API_TOKEN,
} else {})
},
"back2": {
@ -36,7 +36,6 @@
"ports": [8080, 50051],
"env": {
"SECRET_KEY": "tempSecretKeyNeedsToChange",
"ADMIN_API_TOKEN": env.ADMIN_API_TOKEN,
"JITSI_ISS": env.JITSI_ISS,
"JITSI_URL": env.JITSI_URL,
"SECRET_JITSI_KEY": env.SECRET_JITSI_KEY,
@ -44,6 +43,7 @@
"REDIS_HOST": "redis",
} + (if adminUrl != null then {
"ADMIN_API_URL": adminUrl,
"ADMIN_API_TOKEN": env.ADMIN_API_TOKEN,
} else {})
},
"pusher": {
@ -55,13 +55,14 @@
"ports": [8080],
"env": {
"SECRET_KEY": "tempSecretKeyNeedsToChange",
"ADMIN_API_TOKEN": env.ADMIN_API_TOKEN,
"JITSI_ISS": env.JITSI_ISS,
"JITSI_URL": env.JITSI_URL,
"API_URL": "back1:50051,back2:50051",
"SECRET_JITSI_KEY": env.SECRET_JITSI_KEY,
} + (if adminUrl != null then {
"ADMIN_API_URL": adminUrl,
"ADMIN_API_TOKEN": env.ADMIN_API_TOKEN,
"ADMIN_SOCKETS_TOKEN": env.ADMIN_SOCKETS_TOKEN,
} else {})
},
"front": {
@ -75,11 +76,13 @@
"UPLOADER_URL": "//uploader-"+url,
"ADMIN_URL": "//"+url,
"JITSI_URL": env.JITSI_URL,
#POSTHOG
"POSTHOG_API_KEY": if namespace == "master" then env.POSTHOG_API_KEY else "",
"POSTHOG_URL": if namespace == "master" then env.POSTHOG_URL else "",
"SECRET_JITSI_KEY": env.SECRET_JITSI_KEY,
"TURN_SERVER": "turn:coturn.workadventu.re:443,turns:coturn.workadventu.re:443",
"JITSI_PRIVATE_MODE": if env.SECRET_JITSI_KEY != '' then "true" else "false",
"START_ROOM_URL": "/_/global/maps-"+url+"/Floor0/floor0.json"
//"GA_TRACKING_ID": "UA-10196481-11"
"START_ROOM_URL": "/_/global/maps-"+url+"/starter/map.json"
}
},
"uploader": {

View file

@ -30,6 +30,7 @@ services:
UPLOADER_URL: /uploader
ADMIN_URL: /admin
MAPS_URL: /maps
ICON_URL: /icon
STARTUP_COMMAND_1: ./templater.sh
STARTUP_COMMAND_2: yarn install
TURN_SERVER: "turn:localhost:3478,turns:localhost:5349"
@ -40,6 +41,7 @@ services:
TURN_USER: ""
TURN_PASSWORD: ""
START_ROOM_URL: "$START_ROOM_URL"
DISABLE_ANONYMOUS: "$DISABLE_ANONYMOUS"
command: yarn run start
volumes:
- ./front:/usr/src/app
@ -60,6 +62,8 @@ services:
environment:
DEBUG: "*"
STARTUP_COMMAND_1: yarn install
# wait for files generated by "messages" container to exists
STARTUP_COMMAND_2: while [ ! -f /usr/src/app/src/Messages/generated/messages_pb.js ]; do sleep 1; done
SECRET_JITSI_KEY: "$SECRET_JITSI_KEY"
SECRET_KEY: yourSecretKey
ADMIN_API_TOKEN: "$ADMIN_API_TOKEN"
@ -70,6 +74,9 @@ services:
OPID_CLIENT_ID: $OPID_CLIENT_ID
OPID_CLIENT_SECRET: $OPID_CLIENT_SECRET
OPID_CLIENT_ISSUER: $OPID_CLIENT_ISSUER
OPID_CLIENT_REDIRECT_URL: $OPID_CLIENT_REDIRECT_URL
OPID_PROFILE_SCREEN_PROVIDER: $OPID_PROFILE_SCREEN_PROVIDER
DISABLE_ANONYMOUS: $DISABLE_ANONYMOUS
volumes:
- ./pusher:/usr/src/app
labels:
@ -117,6 +124,8 @@ services:
environment:
DEBUG: "*"
STARTUP_COMMAND_1: yarn install
# wait for files generated by "messages" container to exists
STARTUP_COMMAND_2: while [ ! -f /usr/src/app/src/Messages/generated/messages_pb.js ]; do sleep 1; done
SECRET_KEY: yourSecretKey
SECRET_JITSI_KEY: "$SECRET_JITSI_KEY"
ALLOW_ARTILLERY: "true"
@ -177,6 +186,20 @@ services:
redis:
image: redis:6
icon:
image: matthiasluedtke/iconserver:v3.13.0
labels:
- "traefik.http.middlewares.strip-icon-prefix.stripprefix.prefixes=/icon"
- "traefik.http.routers.icon.rule=PathPrefix(`/icon`)"
- "traefik.http.routers.icon.middlewares=strip-icon-prefix@docker"
- "traefik.http.routers.icon.entryPoints=web"
- "traefik.http.services.icon.loadbalancer.server.port=8080"
- "traefik.http.routers.icon-ssl.rule=PathPrefix(`/icon`)"
- "traefik.http.routers.icon-ssl.middlewares=strip-icon-prefix@docker"
- "traefik.http.routers.icon-ssl.entryPoints=websecure"
- "traefik.http.routers.icon-ssl.tls=true"
- "traefik.http.routers.icon-ssl.service=icon"
# coturn:
# image: coturn/coturn:4.5.2
# command:

View file

@ -0,0 +1,12 @@
version: "3"
services:
testcafe:
image: testcafe/testcafe:1.17.1
working_dir: /tests
environment:
BROWSER: "chromium --use-fake-device-for-media-stream"
volumes:
- ./tests:/tests
- ./maps:/maps
# security_opt:
# - seccomp:unconfined

View file

@ -17,6 +17,12 @@ services:
- front
volumes:
- /var/run/docker.sock:/var/run/docker.sock
networks:
default:
aliases:
- 'play.workadventure.localhost'
- 'pusher.workadventure.localhost'
- 'maps.workadventure.localhost'
front:
image: thecodingmachine/nodejs:14
@ -29,6 +35,7 @@ services:
PUSHER_URL: //pusher.workadventure.localhost
UPLOADER_URL: //uploader.workadventure.localhost
ADMIN_URL: //workadventure.localhost
ICON_URL: //icon.workadventure.localhost
STARTUP_COMMAND_1: ./templater.sh
STARTUP_COMMAND_2: yarn install
STUN_SERVER: "stun:stun.l.google.com:19302"
@ -42,6 +49,8 @@ services:
START_ROOM_URL: "$START_ROOM_URL"
MAX_PER_GROUP: "$MAX_PER_GROUP"
MAX_USERNAME_LENGTH: "$MAX_USERNAME_LENGTH"
DISABLE_ANONYMOUS: "$DISABLE_ANONYMOUS"
OPID_LOGIN_SCREEN_PROVIDER: "$OPID_LOGIN_SCREEN_PROVIDER"
command: yarn run start
volumes:
- ./front:/usr/src/app
@ -60,6 +69,8 @@ services:
environment:
DEBUG: "socket:*"
STARTUP_COMMAND_1: yarn install
# wait for files generated by "messages" container to exists
STARTUP_COMMAND_2: while [ ! -f /usr/src/app/src/Messages/generated/messages_pb.js ]; do sleep 1; done
SECRET_JITSI_KEY: "$SECRET_JITSI_KEY"
SECRET_KEY: yourSecretKey
ADMIN_API_TOKEN: "$ADMIN_API_TOKEN"
@ -70,6 +81,9 @@ services:
OPID_CLIENT_ID: $OPID_CLIENT_ID
OPID_CLIENT_SECRET: $OPID_CLIENT_SECRET
OPID_CLIENT_ISSUER: $OPID_CLIENT_ISSUER
OPID_CLIENT_REDIRECT_URL: $OPID_CLIENT_REDIRECT_URL
OPID_PROFILE_SCREEN_PROVIDER: $OPID_PROFILE_SCREEN_PROVIDER
DISABLE_ANONYMOUS: $DISABLE_ANONYMOUS
volumes:
- ./pusher:/usr/src/app
labels:
@ -111,6 +125,8 @@ services:
environment:
DEBUG: "*"
STARTUP_COMMAND_1: yarn install
# wait for files generated by "messages" container to exists
STARTUP_COMMAND_2: while [ ! -f /usr/src/app/src/Messages/generated/messages_pb.js ]; do sleep 1; done
SECRET_KEY: yourSecretKey
SECRET_JITSI_KEY: "$SECRET_JITSI_KEY"
ALLOW_ARTILLERY: "true"
@ -121,6 +137,7 @@ services:
MAX_PER_GROUP: "MAX_PER_GROUP"
REDIS_HOST: redis
NODE_ENV: development
STORE_VARIABLES_FOR_LOCAL_MAPS: "true"
volumes:
- ./back:/usr/src/app
labels:
@ -177,6 +194,17 @@ services:
- "traefik.http.routers.redisinsight-ssl.tls=true"
- "traefik.http.routers.redisinsight-ssl.service=redisinsight"
icon:
image: matthiasluedtke/iconserver:v3.13.0
labels:
- "traefik.http.routers.icon.rule=Host(`icon.workadventure.localhost`)"
- "traefik.http.routers.icon.entryPoints=web"
- "traefik.http.services.icon.loadbalancer.server.port=8080"
- "traefik.http.routers.icon-ssl.rule=Host(`icon.workadventure.localhost`)"
- "traefik.http.routers.icon-ssl.entryPoints=websecure"
- "traefik.http.routers.icon-ssl.tls=true"
- "traefik.http.routers.icon-ssl.service=icon"
# coturn:
# image: coturn/coturn:4.5.2
# command:

View file

@ -15,7 +15,7 @@ When controls are disabled, the user cannot move anymore using keyboard input. T
Example:
```javascript
WA.room.onEnterZone('myZone', () => {
WA.room.onEnterLayer('myZone').subscribe(() => {
WA.controls.disablePlayerControls();
WA.ui.openPopup("popupRectangle", 'This is an imporant message!', [{
label: "Got it!",
@ -25,5 +25,5 @@ WA.room.onEnterZone('myZone', () => {
popup.close();
}
}]);
});
})
```

View file

@ -3,19 +3,21 @@
The list of functions below is **deprecated**. You should not use those but. use the replacement functions.
- Method `WA.sendChatMessage` is deprecated. It has been renamed to `WA.chat.sendChatMessage`.
- Method `WA.disablePlayerControls` is deprecated. It has been renamed to `WA.controls.disablePlayerControls`.
- Method `WA.restorePlayerControls` is deprecated. It has been renamed to `WA.controls.restorePlayerControls`.
- Method `WA.sendChatMessage` is deprecated. It has been renamed to [`WA.chat.sendChatMessage`](api-chat.md#sending-a-message-in-the-chat).
- Method `WA.disablePlayerControls` is deprecated. It has been renamed to [`WA.controls.disablePlayerControls`](api-controls.md#disabling--restoring-controls).
- Method `WA.restorePlayerControls` is deprecated. It has been renamed to [`WA.controls.restorePlayerControls`](api-controls.md#disabling--restoring-controls).
- Method `WA.displayBubble` is deprecated. It has been renamed to `WA.ui.displayBubble`.
- Method `WA.removeBubble` is deprecated. It has been renamed to `WA.ui.removeBubble`.
- Method `WA.openTab` is deprecated. It has been renamed to `WA.nav.openTab`.
- Method `WA.loadSound` is deprecated. It has been renamed to `WA.sound.loadSound`.
- Method `WA.goToPage` is deprecated. It has been renamed to `WA.nav.goToPage`.
- Method `WA.goToRoom` is deprecated. It has been renamed to `WA.nav.goToRoom`.
- Method `WA.openCoWebSite` is deprecated. It has been renamed to `WA.nav.openCoWebSite`.
- Method `WA.closeCoWebSite` is deprecated. It has been renamed to `WA.nav.closeCoWebSite`.
- Method `WA.openPopup` is deprecated. It has been renamed to `WA.ui.openPopup`.
- Method `WA.onChatMessage` is deprecated. It has been renamed to `WA.chat.onChatMessage`.
- Method `WA.onEnterZone` is deprecated. It has been renamed to `WA.room.onEnterZone`.
- Method `WA.onLeaveZone` is deprecated. It has been renamed to `WA.room.onLeaveZone`.
- Method `WA.ui.registerMenuCommand` parameter `callback` is deprecated. Use `WA.ui.registerMenuCommand(commandDescriptor: string, options: MenuOptions)`.
- Method `WA.openTab` is deprecated. It has been renamed to [`WA.nav.openTab`](api-nav.md#opening-a-web-page-in-a-new-tab).
- Method `WA.loadSound` is deprecated. It has been renamed to [`WA.sound.loadSound`](api-sound.md#load-a-sound-from-an-url).
- Method `WA.goToPage` is deprecated. It has been renamed to [`WA.nav.goToPage`](api-nav.md#opening-a-web-page-in-the-current-tab).
- Method `WA.goToRoom` is deprecated. It has been renamed to [`WA.nav.goToRoom`](api-nav.md#going-to-a-different-map-from-the-script).
- Method `WA.openCoWebSite` is deprecated. It has been renamed to [`WA.nav.openCoWebSite`](api-nav.md#openingclosing-web-page-in-co-websites).
- Method `WA.closeCoWebSite` is deprecated. It has been remove and [replace by a function close](api-nav.md#openingclosing-web-page-in-co-websites).
- Method `WA.openPopup` is deprecated. It has been renamed to [`WA.ui.openPopup`](api-ui.md#opening-a-popup).
- Method `WA.onChatMessage` is deprecated. It has been renamed to [`WA.chat.onChatMessage`](api-chat.md#listening-to-messages-from-the-chat).
- Method `WA.onEnterZone` is deprecated. It has been renamed to [`WA.room.onEnterZone`](api-room.md#detecting-when-the-user-entersleaves-a-layer).
- Method `WA.onLeaveZone` is deprecated. It has been renamed to [`WA.room.onLeaveZone`](api-room.md#detecting-when-the-user-entersleaves-a-layer).
- Method `WA.ui.registerMenuCommand` parameter `callback` is deprecated. Use [`WA.ui.registerMenuCommand(commandDescriptor: string, options: MenuOptions)`](api-ui.md#add-custom-menu).
- Method `WA.room.onEnterZone` is deprecated. Use instead [`WA.room.onEnterLayer`](api-room.md#detecting-when-the-user-entersleaves-a-layer).
- Method `WA.room.onLeaveZone` is deprecated. Use instead [`WA.room.onLeaveLayer`](api-room.md#detecting-when-the-user-entersleaves-a-layer).

View file

@ -49,19 +49,34 @@ WA.nav.goToRoom('../otherMap/map.json');
WA.nav.goToRoom("/_/global/<path to global map>.json#start-layer-2")
```
### Opening/closing a web page in an iFrame
### Opening/closing web page in Co-Websites
```
WA.nav.openCoWebSite(url: string, allowApi: boolean = false, allowPolicy: string = ""): void
WA.nav.closeCoWebSite(): void
WA.nav.openCoWebSite(url: string, allowApi: boolean = false, allowPolicy: string = "", position: number = 0): Promise<CoWebsite>
```
Opens the webpage at "url" in an iFrame (on the right side of the screen) or close that iFrame. `allowApi` allows the webpage to use the "IFrame API" and execute script (it is equivalent to putting the `openWebsiteAllowApi` property in the map). `allowPolicy` grants additional access rights to the iFrame. The `allowPolicy` parameter is turned into an [`allow` feature policy in the iFrame](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe#attr-allow).
Opens the webpage at "url" in an iFrame (on the right side of the screen) or close that iFrame. `allowApi` allows the webpage to use the "IFrame API" and execute script (it is equivalent to putting the `openWebsiteAllowApi` property in the map). `allowPolicy` grants additional access rights to the iFrame. The `allowPolicy` parameter is turned into an [`allow` feature policy in the iFrame](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe#attr-allow), position in whitch slot the web page will be open.
You can have only 5 co-wbesites open simultaneously.
Example:
```javascript
WA.nav.openCoWebSite('https://www.wikipedia.org/');
const coWebsite = await WA.nav.openCoWebSite('https://www.wikipedia.org/');
const coWebsiteWorkAdventure = await WA.nav.openCoWebSite('https://workadventu.re/', true, "", 1);
// ...
WA.nav.closeCoWebSite();
coWebsite.close();
```
### Get all Co-Websites
```
WA.nav.getCoWebSites(): Promise<CoWebsite[]>
```
Get all opened co-websites with their ids and positions.
Example:
```javascript
const coWebsites = await WA.nav.getCowebSites();
```

View file

@ -68,7 +68,9 @@ The event has the following attributes :
* **moving (boolean):** **true** when the current player is moving, **false** otherwise.
* **direction (string):** **"right"** | **"left"** | **"down"** | **"top"** the direction where the current player is moving.
* **x (number):** coordinate X of the current player.
* **y (number):** coordinate Y of the current player.
* **y (number):** coordinate Y of the current player.
* **oldX (number):** old coordinate X of the current player.
* **oldY (number):** old coordinate Y of the current player.
**callback:** the function that will be called when the current player is moving. It contains the event.

View file

@ -17,35 +17,27 @@ The name of the layers of this map are :
* `bottom/build/carpet`
* `wall`
### Detecting when the user enters/leaves a zone
### Detecting when the user enters/leaves a layer
```
WA.room.onEnterZone(name: string, callback: () => void): void
WA.room.onLeaveZone(name: string, callback: () => void): void
WA.room.onEnterLayer(name: string): Subscription
WA.room.onLeaveLayer(name: string): Subscription
```
Listens to the position of the current user. The event is triggered when the user enters or leaves a given zone. The name of the zone is stored in the map, on a dedicated layer with the `zone` property.
Listens to the position of the current user. The event is triggered when the user enters or leaves a given layer.
<div>
<figure class="figure">
<img src="images/trigger_event.png" class="figure-img img-fluid rounded" alt="" />
<figcaption class="figure-caption">The `zone` property, applied on a layer</figcaption>
</figure>
</div>
* **name**: the name of the zone, as defined in the `zone` property.
* **callback**: the function that will be called when a user enters or leaves the zone.
* **name**: the name of the layer who as defined in Tiled.
Example:
```javascript
WA.room.onEnterZone('myZone', () => {
WA.room.onEnterLayer('myLayer').subscribe(() => {
WA.chat.sendChatMessage("Hello!", 'Mr Robot');
})
});
WA.room.onLeaveZone('myZone', () => {
WA.room.onLeaveLayer('myLayer').subscribe(() => {
WA.chat.sendChatMessage("Goodbye!", 'Mr Robot');
})
});
```
### Show / Hide a layer
@ -71,7 +63,7 @@ WA.room.setProperty(layerName : string, propertyName : string, propertyValue : s
Set the value of the `propertyName` property of the layer `layerName` at `propertyValue`. If the property doesn't exist, create the property `propertyName` and set the value of the property at `propertyValue`.
Note :
Note :
To unset a property from a layer, use `setProperty` with `propertyValue` set to `undefined`.
Example :
@ -131,7 +123,7 @@ console.log("Map generated with Tiled version ", map.tiledversion);
Check the [Tiled documentation to learn more about the format of the JSON map](https://doc.mapeditor.org/en/stable/reference/json-map-format/).
### Changing tiles
### Changing tiles
```
WA.room.setTiles(tiles: TileDescriptor[]): void
```
@ -144,7 +136,7 @@ If `tile` is a string, it's not the id of the tile but the value of the property
</div>
</div>
`TileDescriptor` has the following attributes :
`TileDescriptor` has the following attributes :
* **x (number) :** The coordinate x of the tile that you want to replace.
* **y (number) :** The coordinate y of the tile that you want to replace.
* **tile (number | string) :** The id of the tile that will be placed in the map.
@ -154,7 +146,7 @@ If `tile` is a string, it's not the id of the tile but the value of the property
Note: If you want to unset a tile, use `setTiles` with `tile` set to `null`.
Example :
Example :
```javascript
WA.room.setTiles([
{x: 6, y: 4, tile: 'blue', layer: 'setTiles'},
@ -246,7 +238,7 @@ const website = WA.room.website.create({
WA.room.website.delete(name: string): Promise<void>
```
Use `WA.room.website.delete` to completely remove an embedded website from your map.
Use `WA.room.website.delete` to completely remove an embedded website from your map.
### The EmbeddedWebsite class
@ -271,7 +263,7 @@ When you modify a property of an `EmbeddedWebsite` instance, the iframe is autom
{.alert.alert-warning}
The websites you add/edit/delete via the scripting API are only shown locally. If you want them
The websites you add/edit/delete via the scripting API are only shown locally. If you want them
to be displayed for every player, you can use [variables](api-start.md) to share a common state
between all users.

View file

@ -49,7 +49,7 @@ Example:
let helloWorldPopup;
// Open the popup when we enter a given zone
helloWorldPopup = WA.room.onEnterZone('myZone', () => {
helloWorldPopup = WA.room.onEnterLayer("myZone").subscribe(() => {
WA.ui.openPopup("popupRectangle", 'Hello world!', [{
label: "Close",
className: "primary",
@ -57,13 +57,13 @@ helloWorldPopup = WA.room.onEnterZone('myZone', () => {
// Close the popup when the "Close" button is pressed.
popup.close();
}
});
}]);
}]);
});
// Close the popup when we leave the zone.
WA.room.onLeaveZone('myZone', () => {
WA.room.onLeaveLayer("myZone").subscribe(() => {
helloWorldPopup.close();
});
})
```
### Add custom menu

View file

@ -1,7 +1,7 @@
{.section-title.accent.text-primary}
# Entries and exits
https://www.youtube.com/watch?v=MuhVgu8H7U0
[Building your map - Defined entries and exits](https://www.youtube.com/watch?v=MuhVgu8H7U0)
## Defining a default entry point

View file

@ -14,7 +14,7 @@ WorkAdventure comes with a "map starter kit" that we recommend using to start de
{.alert.alert-info}
If you are looking to host your maps on your own webserver, be sure to read the [Self-hosting your map](hosting.md) guide.
[](https://www.youtube.com/watch?v=lu1IZgBJJD4)
[Building your map - Create your map](https://www.youtube.com/watch?v=lu1IZgBJJD4)
## Getting started

View file

@ -1,7 +1,7 @@
{.section-title.accent.text-primary}
# Meeting rooms
https://www.youtube.com/watch?v=cN9VMWHN0eo
[Building your map - Meeting room](https://www.youtube.com/watch?v=cN9VMWHN0eo)
## Opening a Jitsi meet when walking on the map

View file

@ -5,64 +5,75 @@ $extraUtilsMenu = require __DIR__.'/../../scripting_api_extra_doc/menu_functions
return [
[
'title' => 'Getting started',
'url' => '/map-building',
'markdown' => 'maps.index'
'url' => '/map-building/',
'markdown' => 'maps.index',
'editUrl' => 'https://github.com/thecodingmachine/workadventure/edit/develop/docs/maps/index.md',
],
[
'title' => 'WorkAdventure maps',
'url' => '/map-building/wa-maps',
'markdown' => 'maps.wa-maps'
'url' => '/map-building/wa-maps.md',
'markdown' => 'maps.wa-maps',
'editUrl' => 'https://github.com/thecodingmachine/workadventure/edit/develop/docs/maps/wa-maps.md',
],
[
'title' => 'Entries and exits',
'url' => '/map-building/entry-exit.md',
'markdown' => 'maps.entry-exit'
'markdown' => 'maps.entry-exit',
'editUrl' => 'https://github.com/thecodingmachine/workadventure/edit/develop/docs/maps/entry-exit.md',
],
[
'title' => 'Opening a website',
'url' => '/map-building/opening-a-website.md',
'markdown' => 'maps.opening-a-website'
'markdown' => 'maps.opening-a-website',
'editUrl' => 'https://github.com/thecodingmachine/workadventure/edit/develop/docs/maps/opening-a-website.md',
],
[
'title' => 'Meeting rooms',
'url' => '/map-building/meeting-rooms.md',
'markdown' => 'maps.meeting-rooms'
'markdown' => 'maps.meeting-rooms',
'editUrl' => 'https://github.com/thecodingmachine/workadventure/edit/develop/docs/maps/meeting-rooms.md',
],
[
'title' => 'Special zones',
'url' => '/map-building/special-zones.md',
'markdown' => 'maps.special-zones'
'markdown' => 'maps.special-zones',
'editUrl' => 'https://github.com/thecodingmachine/workadventure/edit/develop/docs/maps/special-zones.md',
],
[
'title' => 'Animations',
'url' => '/map-building/animations.md',
'markdown' => 'maps.animations'
'markdown' => 'maps.animations',
'editUrl' => 'https://github.com/thecodingmachine/workadventure/edit/develop/docs/maps/animations.md',
],
[
'title' => 'Integrated websites',
'url' => '/map-building/website-in-map.md',
'markdown' => 'maps.website-in-map'
'markdown' => 'maps.website-in-map',
'editUrl' => 'https://github.com/thecodingmachine/workadventure/edit/develop/docs/maps/website-in-map.md',
],
[
'title' => 'Variables',
'url' => '/map-building/variables.md',
'markdown' => 'maps.variables'
'markdown' => 'maps.variables',
'editUrl' => 'https://github.com/thecodingmachine/workadventure/edit/develop/docs/maps/variables.md',
],
[
'title' => 'Self-hosting your map',
'url' => '/map-building/hosting.md',
'markdown' => 'maps.hosting'
'markdown' => 'maps.hosting',
'editUrl' => 'https://github.com/thecodingmachine/workadventure/edit/develop/docs/maps/hosting.md',
],
$extraMenu,
[
'title' => 'Scripting maps',
'url' => '/map-building/scripting',
'url' => '/map-building/scripting.md',
'markdown' => 'maps.scripting',
'children' => [
[
'title' => 'Using Typescript',
'url' => '/map-building/using-typescript.md',
'markdown' => 'maps.using-typescript'
'markdown' => 'maps.using-typescript',
'editUrl' => 'https://github.com/thecodingmachine/workadventure/edit/develop/docs/maps/using-typescript.md',
],
[
'title' => 'API Reference',
@ -74,51 +85,61 @@ return [
'title' => 'Initialization',
'url' => '/map-building/api-start.md',
'markdown' => 'maps.api-start',
'editUrl' => 'https://github.com/thecodingmachine/workadventure/edit/develop/docs/maps/api-start.md',
],
[
'title' => 'Navigation',
'url' => '/map-building/api-nav.md',
'markdown' => 'maps.api-nav',
'editUrl' => 'https://github.com/thecodingmachine/workadventure/edit/develop/docs/maps/api-nav.md',
],
[
'title' => 'Chat',
'url' => '/map-building/api-chat.md',
'markdown' => 'maps.api-chat',
'editUrl' => 'https://github.com/thecodingmachine/workadventure/edit/develop/docs/maps/api-chat.md',
],
[
'title' => 'Room',
'url' => '/map-building/api-room.md',
'markdown' => 'maps.api-room',
'editUrl' => 'https://github.com/thecodingmachine/workadventure/edit/develop/docs/maps/api-room.md',
],
[
'title' => 'State',
'url' => '/map-building/api-state.md',
'markdown' => 'maps.api-state',
'editUrl' => 'https://github.com/thecodingmachine/workadventure/edit/develop/docs/maps/api-state.md',
],
[
'title' => 'Player',
'url' => '/map-building/api-player.md',
'markdown' => 'maps.api-player',
'editUrl' => 'https://github.com/thecodingmachine/workadventure/edit/develop/docs/maps/api-player.md',
],
[
'title' => 'UI',
'url' => '/map-building/api-ui.md',
'markdown' => 'maps.api-ui',
'editUrl' => 'https://github.com/thecodingmachine/workadventure/edit/develop/docs/maps/api-ui.md',
],
[
'title' => 'Sound',
'url' => '/map-building/api-sound.md',
'markdown' => 'maps.api-sound',
'editUrl' => 'https://github.com/thecodingmachine/workadventure/edit/develop/docs/maps/api-sound.md',
],
[
'title' => 'Controls',
'url' => '/map-building/api-controls.md',
'markdown' => 'maps.api-controls',
'editUrl' => 'https://github.com/thecodingmachine/workadventure/edit/develop/docs/maps/api-controls.md',
],
[
'title' => 'Deprecated',
'url' => '/map-building/api-deprecated.md',
'markdown' => 'maps.api-deprecated',
'editUrl' => 'https://github.com/thecodingmachine/workadventure/edit/develop/docs/maps/api-deprecated.md',
],
]
],

View file

@ -1,22 +1,22 @@
{.section-title.accent.text-primary}
# Opening a website when walking on the map
https://www.youtube.com/watch?v=Me8cu5lLN3A
[Building your map - Opening a website](https://www.youtube.com/watch?v=Me8cu5lLN3A)
## The openWebsite property
On your map, you can define special zones. When a player will pass over these zones, a website will open (as an iframe
On your map, you can define special zones. When a player will pass over these zones, a website will open (as an iframe
on the right side of the screen)
In order to create a zone that opens websites:
* You must create a specific layer.
* In layer properties, you MUST add a "`openWebsite`" property (of type "`string`"). The value of the property is the URL of the website to open (the URL must start with "https://")
* You may also use "`openWebsiteWidth`" property (of type "`number`" between 0 and 100) to control the width of the iframe.
* You may also use "`openWebsiteWidth`" property (of type "`int`" or "`float`" between 0 and 100) to control the width of the iframe.
* You may also use "`openTab`" property (of type "`string`") to open in a new tab instead.
{.alert.alert-warning}
A website can explicitly forbid another website from loading it in an iFrame using
A website can explicitly forbid another website from loading it in an iFrame using
the [X-Frame-Options HTTP header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options).
## Integrating a Youtube video
@ -64,3 +64,13 @@ For instance, if you want an iFrame to be able to go in fullscreen, you will use
<img src="images/open_website_policy.jpg" class="figure-img img-fluid rounded" alt="" />
<figcaption class="figure-caption">The generated iFrame will have the allow attribute set to: <code>&lt;iframe allow="fullscreen"&gt;</code></figcaption>
</figure>
### Open a Jitsi with a co-website
Cowebsites allow you to have several sites open at the same time.
If you want to open a Jitsi and another page it's easy!
You have just to [add a Jitsi to the map](meeting-rooms.md) and [add a co-website](opening-a-website.md#the-openwebsite-property) on the same layer.
It's done!

View file

@ -3,7 +3,7 @@
## Making a "silent" zone
https://www.youtube.com/watch?v=z7XLo06o-ow
[Building your map - Special zones](https://www.youtube.com/watch?v=z7XLo06o-ow)
On your map, you can define special silent zones where nobody is allowed to talk. In these zones, users will not speak to each others, even if they are next to each others.

View file

@ -56,6 +56,8 @@ A few things to notice:
## Building walls and "collidable" areas
[Building your map - Collides](https://www.youtube.com/watch?v=qTK50ymhMIE)
By default, the characters can traverse any tiles. If you want to prevent your character from going through a tile (like a wall or a desktop), you must make this tile "collidable". You can do this by settings the `collides` property on a given tile.
To make a tile "collidable", you should:

View file

@ -1,13 +1,13 @@
FROM thecodingmachine/workadventure-back-base:latest as builder
WORKDIR /var/www/messages
COPY --chown=docker:docker messages .
FROM node:14.15.4-buster-slim@sha256:cbae886186467bbfd274b82a234a1cdfbbd31201c2a6ee63a6893eefcf3c6e76 as builder
WORKDIR /usr/src
COPY messages .
RUN yarn install && yarn proto
# we are rebuilding on each deploy to cope with the PUSHER_URL environment URL
FROM thecodingmachine/nodejs:14-apache
COPY --chown=docker:docker front .
COPY --from=builder --chown=docker:docker /var/www/messages/generated /var/www/html/src/Messages/generated
COPY --from=builder --chown=docker:docker /usr/src/generated /var/www/html/src/Messages/generated
# Removing the iframe.html file from the final image as this adds a XSS attack.
# iframe.html is only in dev mode to circumvent a limitation

View file

@ -23,4 +23,4 @@ RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule "^[_@]/" "/index.html" [L]
RewriteRule "^register/" "/index.html" [L]
RewriteRule "^login" "/index.html" [L]
RewriteRule "^jwt/" "/index.html" [L]
RewriteRule "^jwt" "/index.html" [L]

View file

@ -37,6 +37,54 @@
<div class="main-container" id="main-container">
<!-- Create the editor container -->
<div id="game" class="game">
<div id="cowebsite-container">
<div id="cowebsite-container-main">
<div id="cowebsite-slot-1">
<div class="actions">
<button type="button" class="nes-btn is-primary expand">></button>
<button type="button" class="nes-btn is-error close">&times;</button>
</div>
</div>
</div>
<div id="cowebsite-container-sub">
<div id="cowebsite-slot-2">
<div class="overlay">
<div class="actions">
<button type="button" title="Close" class="nes-btn is-error close">&times;</button>
</div>
<div class="actions-move">
<button type="button" title="Expand" class="nes-btn is-primary expand">></button>
<button type="button" title="Hightlight" class="nes-btn is-secondary hightlight">&Xi;</button>
</div>
</div>
</div>
<div id="cowebsite-slot-3">
<div class="overlay">
<div class="actions">
<button type="button" title="Close" class="nes-btn is-error close">&times;</button>
</div>
<div class="actions-move">
<button type="button" title="Expand" class="nes-btn is-primary expand">></button>
<button type="button" title="Hightlight" class="nes-btn is-secondary hightlight">&Xi;</button>
</div>
</div>
</div>
<div id="cowebsite-slot-4">
<div class="overlay">
<div class="actions">
<button type="button" title="Close" class="nes-btn is-error close">&times;</button>
</div>
<div class="actions-move">
<button type="button" title="Expand" class="nes-btn is-primary expand">></button>
<button type="button" title="Hightlight" class="nes-btn is-secondary hightlight">&Xi;</button>
</div>
</div>
</div>
</div>
</div>
<div id="svelte-overlay"></div>
<div id="game-overlay" class="game-overlay">
<div id="main-section" class="main-section">
@ -48,19 +96,24 @@
</div>
</div>
<div id="cowebsite" class="cowebsite hidden">
<aside id="cowebsite-aside">
<img src="/static/images/menu.svg" alt="hold to resize"/>
<aside id="cowebsite-aside" class="noselect">
<div id="cowebsite-aside-buttons">
<button class="top-right-btn nes-btn is-error" id="cowebsite-close" alt="close all co-websites">
&times;
</button>
<button class="top-right-btn nes-btn is-primary" id="cowebsite-fullscreen" alt="fullscreen mode">
<img id="cowebsite-fullscreen-close" style="display: none;" src="resources/logos/fullscreen-exit.svg"/>
<img id="cowebsite-fullscreen-open" src="resources/logos/fullscreen.svg"/>
</button>
</div>
<div id="cowebsite-aside-holder">
<img src="/static/images/menu.svg" alt="hold to resize"/>
</div>
<div id="cowebsite-sub-icons"></div>
</aside>
<main id="cowebsite-main">
</main>
<button class="top-right-btn" id="cowebsite-fullscreen" alt="fullscreen mode">
<img id="cowebsite-fullscreen-open" src="resources/logos/fullscreen.svg"/>
<img id="cowebsite-fullscreen-close" style="display: none;" src="resources/logos/fullscreen-exit.svg"/>
</button>
<button class="top-right-btn" id="cowebsite-close" alt="close the iframe">
<img src="resources/logos/close.svg"/>
</button>
<main id="cowebsite-slot-0"></main>
</div>
<div id="cowebsite-buffer"></div>
</div>
<div id="activeScreenSharing" class="active-screen-sharing active">

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 300 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 216 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 516 B

After

Width:  |  Height:  |  Size: 389 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 886 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 754 B

After

Width:  |  Height:  |  Size: 841 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 922 B

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 445 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 713 B

After

Width:  |  Height:  |  Size: 471 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 792 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 848 B

After

Width:  |  Height:  |  Size: 848 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

View file

@ -89,7 +89,7 @@
"sizes": "192x192",
"type": "image\/png",
"density": "4.0",
"purpose": "any maskable"
"purpose": "any"
},
{

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

View file

@ -40,15 +40,17 @@
},
"dependencies": {
"@fontsource/press-start-2p": "^4.3.0",
"@joeattardi/emoji-button": "^4.6.0",
"@types/simple-peer": "^9.11.1",
"@types/socket.io-client": "^1.4.32",
"axios": "^0.21.1",
"axios": "^0.21.2",
"cross-env": "^7.0.3",
"generic-type-guard": "^3.2.0",
"google-protobuf": "^3.13.0",
"phaser": "^3.54.0",
"phaser-animated-tiles": "workadventure/phaser-animated-tiles#da68bbededd605925621dd4f03bd27e69284b254",
"phaser3-rex-plugins": "^1.1.42",
"posthog-js": "^1.14.1",
"queue-typescript": "^1.0.1",
"quill": "1.3.6",
"quill-delta-to-html": "^0.12.0",

View file

@ -0,0 +1,91 @@
import { POSTHOG_API_KEY, POSTHOG_URL } from "../Enum/EnvironmentVariable";
// eslint-disable-next-line @typescript-eslint/no-explicit-any
declare let window: any;
class AnalyticsClient {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
private posthogPromise: Promise<any>|undefined;
constructor() {
if (POSTHOG_API_KEY && POSTHOG_URL) {
this.posthogPromise = import("posthog-js").then(({ default: posthog }) => {
posthog.init(POSTHOG_API_KEY, { api_host: POSTHOG_URL, disable_cookie: true });
//the posthog toolbar need a reference in window to be able to work
window.posthog = posthog;
return posthog;
});
}
}
identifyUser(uuid: string, email: string | null) {
this.posthogPromise
?.then((posthog) => {
posthog.identify(uuid, { uuid, email, wa: true });
});
}
loggedWithSso() {
this.posthogPromise
?.then((posthog) => {
posthog.capture("wa-logged-sso");
});
}
loggedWithToken() {
this.posthogPromise
?.then((posthog) => {
posthog.capture("wa-logged-token");
});
}
enteredRoom(roomId: string, roomGroup: string | null) {
this.posthogPromise
?.then((posthog) => {
posthog.capture("$pageView", { roomId, roomGroup });
posthog.capture("enteredRoom");
});
}
openedMenu() {
this.posthogPromise
?.then((posthog) => {
posthog.capture("wa-opened-menu");
});
}
launchEmote(emote: string) {
this.posthogPromise
?.then((posthog) => {
posthog.capture("wa-emote-launch", { emote });
});
}
enteredJitsi(roomName: string, roomId: string) {
this.posthogPromise
?.then((posthog) => {
posthog.capture("wa-entered-jitsi", { roomName, roomId });
});
}
validationName() {
this.posthogPromise
?.then((posthog) => {
posthog.capture("wa-name-validation");
});
}
validationWoka(scene: string) {
this.posthogPromise
?.then((posthog) => {
posthog.capture("wa-woka-validation", { scene });
});
}
validationVideo() {
this.posthogPromise
?.then((posthog) => {
posthog.capture("wa-video-validation");
});
}
}
export const analyticsClient = new AnalyticsClient();

View file

@ -0,0 +1,11 @@
import * as tg from "generic-type-guard";
export const isChangeLayerEvent = new tg.IsInterface()
.withProperties({
name: tg.isString,
})
.get();
/**
* A message sent from the game to the iFrame when a user enters or leaves a layer.
*/
export type ChangeLayerEvent = tg.GuardedType<typeof isChangeLayerEvent>;

View file

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

View file

@ -6,6 +6,8 @@ export const isHasPlayerMovedEvent = new tg.IsInterface()
moving: tg.isBoolean,
x: tg.isNumber,
y: tg.isNumber,
oldX: tg.isOptional(tg.isNumber),
oldY: tg.isOptional(tg.isNumber),
})
.get();

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