Compare commits

...

41 Commits

Author SHA1 Message Date
Thomas Basler e8127fc4ae Fix handling of remote maps 2021-01-20 21:00:59 +01:00
Thomas Basler 84d9e47c73 Undo previous changes regaring index.html template 2021-01-20 20:38:58 +01:00
Thomas Basler cfaf7aaf60 Merge remote-tracking branch 'upstream/develop' into develop 2021-01-20 19:41:05 +01:00
David Négrier 84f85effe5
Merge pull request #628 from thecodingmachine/template-analytics
Template google analytics data (2)
2021-01-20 18:03:28 +01:00
David Négrier f87f3889df Running templater in CI phase 2021-01-20 18:00:11 +01:00
David Négrier 9e469a4a8f Removing Google Analytics tracking from test environments 2021-01-20 17:30:26 +01:00
David Négrier 15b3e87bd1 Renaming TRACKING_NUMBER to GA_TRACKING_ID 2021-01-20 17:30:11 +01:00
David Négrier fd89d54ed9 Adding GA tracking in test envs 2021-01-20 16:57:30 +01:00
David Négrier aa9a21ad4c
Merge pull request #581 from Informatic/fix/firefox-webcam
front: fix webrtc webcam feed on Firefox
2021-01-20 16:27:45 +01:00
David Négrier b03abee481 Fixing admin links 2021-01-20 10:34:34 +01:00
grégoire parant cb7cf7a141
Merge pull request #625 from thecodingmachine/phaser3.54
FEATURE: updated phaser to version 3.52
2021-01-19 18:51:03 +01:00
kharhamel 49531bb8fa FEATURE: updated phaser to version 3.52 2021-01-19 18:31:58 +01:00
Jean-Philippe Evrard e2f400472f Template google analytics data
Without this patch, the index.html contains google analytics at
all times, including for people self-hosting it.

This is a problem for privacy reasons, and only people wanting
to have analytics on their instances should be able to enable
them.

This fixes it by making sure the index.html page is templated
to sideload content from ANALYTICS_CODE_PATH (which itself is
also templated, for convenience).
2021-01-19 12:17:05 +01:00
Gregoire Parant c95419f0e7 Prepar release 1.1.0
Automatically redirect on floor0 of TCM map
2021-01-18 22:52:25 +01:00
David Négrier d891a288b5 Switching to new admin url 2021-01-18 21:02:25 +01:00
David Négrier 1f9d975505 Fixing CI 2021-01-18 19:52:44 +01:00
David Négrier db1e700488
Merge pull request #618 from thecodingmachine/featureBan
Removing website container entirely from project
2021-01-18 19:49:21 +01:00
David Négrier 675c043cb5 Removing website container entirely from project (as it is specific to the WorkAdventure company and not to the WorkAdventure self-installed project) 2021-01-18 19:39:22 +01:00
David Négrier c466ba8ca5
Merge pull request #601 from thecodingmachine/featureBan
Create ban feature by admin console
2021-01-18 19:31:51 +01:00
David Négrier 4df200c6c0 Switching ban by IP to be world specific 2021-01-18 16:41:36 +01:00
Gregoire Parant 5954ded195 Update link for private TCM map 2021-01-18 16:25:44 +01:00
David Négrier 0d4808231a Linting code 2021-01-18 15:43:27 +01:00
David Négrier 65d2c3dfb0 Disabling explicit any check on error handling 2021-01-18 15:36:02 +01:00
David Négrier e0fcb38c4f Fixing CI pipeline 2021-01-18 15:34:22 +01:00
David Négrier 8fcc1534d8 Fixing let/const in Admin 2021-01-18 15:31:19 +01:00
David Négrier 217b04dafa Adding pusher CI 2021-01-18 15:31:07 +01:00
David Négrier d1222e4440 Refactoring admin messages 2021-01-18 15:07:40 +01:00
grégoire parant ddb7e2a12b
Merge pull request #614 from thecodingmachine/fixFocusCamScene
Fix focus cam on select cam scene
2021-01-18 10:52:28 +01:00
Gregoire Parant 679dbd4215 Fix focus cam on select cam scene 2021-01-18 10:40:13 +01:00
David Négrier b1d4af005e Adding missing use statement 2021-01-17 22:44:22 +01:00
David Négrier 4234b38910 Better error message in case of 404 2021-01-17 22:40:54 +01:00
David Négrier 48c3f951bf Correctly forwarding HTTP error status code from admin to front 2021-01-17 20:42:45 +01:00
David Négrier a5aa9b6cf9 /map now returns the correct error status code 2021-01-17 20:42:45 +01:00
David Négrier adca51f6de Replacing FourOFourScene with more generic ErrorScene 2021-01-17 20:42:45 +01:00
Gregoire Parant 0bbaef0cb5 Fix send message and ban message 2021-01-17 03:07:46 +01:00
Gregoire Parant 74d0594246 Fix feedback @moufmou 2021-01-16 20:14:21 +01:00
Gregoire Parant a7398fa851 Fix ci 2021-01-15 05:12:23 +01:00
Gregoire Parant b1f8522c05 Create ban feature by admin console 2021-01-15 03:19:58 +01:00
Gregoire Parant 8c9e9522db Revert "Update ban feature"
This reverts commit d2ee52ce43.
2021-01-15 03:15:14 +01:00
Gregoire Parant d2ee52ce43 Update ban feature 2021-01-15 03:14:15 +01:00
Piotr Dobrowolski 7f8cc76eb7 front: fix webrtc webcam feed on Firefox
Seems like Firefox doesn't support specifing both exact and ideal
constraints...
2021-01-10 17:05:48 +01:00
183 changed files with 532 additions and 9824 deletions

View File

@ -102,29 +102,6 @@ jobs:
tags: ${{ env.GITHUB_REF_SLUG }}
add_git_labels: true
build-website:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
# Create a slugified value of the branch
- uses: rlespinasse/github-slug-action@3.1.0
- name: "Build and push back image"
uses: docker/build-push-action@v1
with:
dockerfile: website/Dockerfile
path: website/
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
repository: thecodingmachine/workadventure-website
tags: ${{ env.GITHUB_REF_SLUG }}
add_git_labels: true
build-maps:
runs-on: ubuntu-latest
@ -156,7 +133,6 @@ jobs:
- build-pusher
- build-maps
- build-uploader
- build-website
runs-on: ubuntu-latest
steps:

View File

@ -39,6 +39,10 @@ jobs:
run: yarn run proto && yarn run copy-to-front
working-directory: "messages"
- name: "Create index.html"
run: ./templater.sh
working-directory: "front"
- name: "Build"
run: yarn run build
env:
@ -53,6 +57,49 @@ jobs:
run: yarn test
working-directory: "front"
continuous-integration-pusher:
name: "Continuous Integration Pusher"
runs-on: "ubuntu-latest"
steps:
- name: "Checkout"
uses: "actions/checkout@v2.0.0"
- name: "Setup NodeJS"
uses: actions/setup-node@v1
with:
node-version: '12.x'
- name: Install Protoc
uses: arduino/setup-protoc@v1
with:
version: '3.x'
- name: "Install dependencies"
run: yarn install
working-directory: "pusher"
- name: "Install messages dependencies"
run: yarn install
working-directory: "messages"
- name: "Build proto messages"
run: yarn run proto && yarn run copy-to-pusher
working-directory: "messages"
- name: "Build"
run: yarn run tsc
working-directory: "pusher"
- name: "Lint"
run: yarn run lint
working-directory: "pusher"
- name: "Jasmine"
run: yarn test
working-directory: "pusher"
continuous-integration-back:
name: "Continuous Integration Back"

View File

@ -9,7 +9,7 @@ import {
PusherToBackMessage,
ServerToAdminClientMessage,
ServerToClientMessage,
SubMessage
SubMessage, UserJoinedRoomMessage, UserLeftRoomMessage
} from "../Messages/generated/messages_pb";
import {CharacterLayer} from "_Model/Websocket/CharacterLayer";
import {AdminSocket} from "../RoomManager";
@ -21,16 +21,26 @@ export class Admin {
) {
}
public sendUserJoin(uuid: string): void {
public sendUserJoin(uuid: string, name: string, ip: string): void {
const serverToAdminClientMessage = new ServerToAdminClientMessage();
serverToAdminClientMessage.setUseruuidjoinedroom(uuid);
const userJoinedRoomMessage = new UserJoinedRoomMessage();
userJoinedRoomMessage.setUuid(uuid);
userJoinedRoomMessage.setName(name);
userJoinedRoomMessage.setIpaddress(ip);
serverToAdminClientMessage.setUserjoinedroom(userJoinedRoomMessage);
this.socket.write(serverToAdminClientMessage);
}
public sendUserLeft(uuid: string): void {
public sendUserLeft(uuid: string/*, name: string, ip: string*/): void {
const serverToAdminClientMessage = new ServerToAdminClientMessage();
serverToAdminClientMessage.setUseruuidleftroom(uuid);
const userLeftRoomMessage = new UserLeftRoomMessage();
userLeftRoomMessage.setUuid(uuid);
serverToAdminClientMessage.setUserleftroom(userLeftRoomMessage);
this.socket.write(serverToAdminClientMessage);
}

View File

@ -102,7 +102,17 @@ export class GameRoom {
}
const position = ProtobufUtils.toPointInterface(positionMessage);
const user = new User(this.nextUserId, joinRoomMessage.getUseruuid(), position, false, this.positionNotifier, socket, joinRoomMessage.getTagList(), joinRoomMessage.getName(), ProtobufUtils.toCharacterLayerObjects(joinRoomMessage.getCharacterlayerList()));
const user = new User(this.nextUserId,
joinRoomMessage.getUseruuid(),
joinRoomMessage.getIpaddress(),
position,
false,
this.positionNotifier,
socket,
joinRoomMessage.getTagList(),
joinRoomMessage.getName(),
ProtobufUtils.toCharacterLayerObjects(joinRoomMessage.getCharacterlayerList())
);
this.nextUserId++;
this.users.set(user.id, user);
this.usersByUuid.set(user.uuid, user);
@ -112,7 +122,7 @@ export class GameRoom {
// Notify admins
for (const admin of this.admins) {
admin.sendUserJoin(user.uuid);
admin.sendUserJoin(user.uuid, user.name, user.IPAddress);
}
return user;
@ -135,7 +145,7 @@ export class GameRoom {
// Notify admins
for (const admin of this.admins) {
admin.sendUserLeft(user.uuid);
admin.sendUserLeft(user.uuid/*, user.name, user.IPAddress*/);
}
}
@ -318,7 +328,7 @@ export class GameRoom {
// Let's send all connected users
for (const user of this.users.values()) {
admin.sendUserJoin(user.uuid);
admin.sendUserJoin(user.uuid, user.name, user.IPAddress);
}
}

View File

@ -16,6 +16,7 @@ export class User implements Movable {
public constructor(
public id: number,
public readonly uuid: string,
public readonly IPAddress: string,
private position: PointInterface,
public silent: boolean,
private positionNotifier: PositionNotifier,

View File

@ -2,25 +2,22 @@ import {IRoomManagerServer} from "./Messages/generated/messages_grpc_pb";
import {
AdminGlobalMessage,
AdminMessage,
AdminPusherToBackMessage, BanMessage,
ClientToServerMessage, EmptyMessage,
AdminPusherToBackMessage,
BanMessage,
EmptyMessage,
ItemEventMessage,
JoinRoomMessage,
PlayGlobalMessage,
PusherToBackMessage,
QueryJitsiJwtMessage,
ReportPlayerMessage,
RoomJoinedMessage,
ServerToAdminClientMessage,
ServerToClientMessage,
SilentMessage,
UserMovesMessage,
ViewportMessage,
WebRtcSignalToServerMessage,
ZoneMessage
} from "./Messages/generated/messages_pb";
import grpc, {sendUnaryData, ServerDuplexStream, ServerUnaryCall, ServerWritableStream} from "grpc";
import {Empty} from "google-protobuf/google/protobuf/empty_pb";
import {sendUnaryData, ServerDuplexStream, ServerUnaryCall, ServerWritableStream} from "grpc";
import {socketManager} from "./Services/SocketManager";
import {emitError} from "./Services/MessageHelpers";
import {User, UserSocket} from "./Model/User";
@ -74,6 +71,16 @@ const roomManager: IRoomManagerServer = {
socketManager.handleReportMessage(client, message.getReportplayermessage() as ReportPlayerMessage);*/
} else if (message.hasQueryjitsijwtmessage()){
socketManager.handleQueryJitsiJwtMessage(user, message.getQueryjitsijwtmessage() as QueryJitsiJwtMessage);
}else if (message.hasSendusermessage()) {
const sendUserMessage = message.getSendusermessage();
if(sendUserMessage !== undefined) {
socketManager.handlerSendUserMessage(user, sendUserMessage);
}
}else if (message.hasBanusermessage()) {
const banUserMessage = message.getBanusermessage();
if(banUserMessage !== undefined) {
socketManager.handlerBanUserMessage(room, user, banUserMessage);
}
} else {
throw new Error('Unhandled message type');
}
@ -196,8 +203,8 @@ const roomManager: IRoomManagerServer = {
callback(null, new EmptyMessage());
},
ban(call: ServerUnaryCall<BanMessage>, callback: sendUnaryData<EmptyMessage>): void {
socketManager.banUser(call.request.getRoomid(), call.request.getRecipientuuid());
// FIXME Work in progress
socketManager.banUser(call.request.getRoomid(), call.request.getRecipientuuid(), 'foo bar TODO change this');
callback(null, new EmptyMessage());
},

View File

@ -1,24 +1,16 @@
import {GameRoom} from "../Model/GameRoom";
import {CharacterLayer} from "_Model/Websocket/CharacterLayer";
import {
GroupDeleteMessage,
GroupUpdateMessage,
ItemEventMessage,
ItemStateMessage,
PlayGlobalMessage,
PointMessage,
PositionMessage,
RoomJoinedMessage,
ServerToClientMessage,
SetPlayerDetailsMessage,
SilentMessage,
SubMessage,
ReportPlayerMessage,
UserJoinedMessage,
UserLeftMessage,
UserMovedMessage,
UserMovesMessage,
ViewportMessage,
WebRtcDisconnectMessage,
WebRtcSignalToClientMessage,
WebRtcSignalToServerMessage,
@ -28,24 +20,23 @@ import {
SendUserMessage,
JoinRoomMessage,
Zone as ProtoZone,
BatchMessage,
BatchToPusherMessage,
SubToPusherMessage,
UserJoinedZoneMessage, GroupUpdateZoneMessage, GroupLeftZoneMessage, UserLeftZoneMessage, AdminMessage, BanMessage
UserJoinedZoneMessage, GroupUpdateZoneMessage, GroupLeftZoneMessage, UserLeftZoneMessage, BanUserMessage
} from "../Messages/generated/messages_pb";
import {User, UserSocket} from "../Model/User";
import {ProtobufUtils} from "../Model/Websocket/ProtobufUtils";
import {Group} from "../Model/Group";
import {cpuTracker} from "./CpuTracker";
import {ADMIN_API_URL, GROUP_RADIUS, JITSI_ISS, MINIMUM_DISTANCE, SECRET_JITSI_KEY} from "../Enum/EnvironmentVariable";
import {GROUP_RADIUS, JITSI_ISS, MINIMUM_DISTANCE, SECRET_JITSI_KEY} from "../Enum/EnvironmentVariable";
import {Movable} from "../Model/Movable";
import {PositionInterface} from "../Model/PositionInterface";
import {adminApi, CharacterTexture, FetchMemberDataByUuidResponse} from "./AdminApi";
import {adminApi, CharacterTexture} from "./AdminApi";
import Jwt from "jsonwebtoken";
import {JITSI_URL} from "../Enum/EnvironmentVariable";
import {clientEventsEmitter} from "./ClientEventsEmitter";
import {gaugeManager} from "./GaugeManager";
import {AdminSocket, ZoneSocket} from "../RoomManager";
import {ZoneSocket} from "../RoomManager";
import {Zone} from "_Model/Zone";
import Debug from "debug";
import {Admin} from "_Model/Admin";
@ -119,7 +110,7 @@ export class SocketManager {
//const things = room.setViewport(client, viewport);
const roomJoinedMessage = new RoomJoinedMessage();
roomJoinedMessage.setTagList(joinRoomMessage.getTagList());
/*for (const thing of things) {
if (thing instanceof User) {
const player: ExSocketInterface|undefined = this.sockets.get(thing.id);
@ -626,6 +617,33 @@ export class SocketManager {
user.socket.write(serverToClientMessage);
}
public handlerSendUserMessage(user: User, sendUserMessageToSend: SendUserMessage){
const sendUserMessage = new SendUserMessage();
sendUserMessage.setMessage(sendUserMessageToSend.getMessage());
sendUserMessage.setType(sendUserMessageToSend.getType());
const serverToClientMessage = new ServerToClientMessage();
serverToClientMessage.setSendusermessage(sendUserMessage);
user.socket.write(serverToClientMessage);
}
public handlerBanUserMessage(room: GameRoom, user: User, banUserMessageToSend: BanUserMessage){
const banUserMessage = new BanUserMessage();
banUserMessage.setMessage(banUserMessageToSend.getMessage());
banUserMessage.setType(banUserMessageToSend.getType());
const serverToClientMessage = new ServerToClientMessage();
serverToClientMessage.setSendusermessage(banUserMessage);
user.socket.write(serverToClientMessage);
setTimeout(() => {
// Let's leave the room now.
room.leave(user);
// Let's close the connection when the user is banned.
user.socket.end();
}, 10000);
}
/**
* Merges the characterLayers received from the front (as an array of string) with the custom textures from the back.
*/
@ -748,7 +766,7 @@ export class SocketManager {
recipient.socket.write(subToPusherMessage);
}
public banUser(roomId: string, recipientUuid: string): void {
public banUser(roomId: string, recipientUuid: string, message: string): void {
const room = this.rooms.get(roomId);
if (!room) {
console.error("In banUser, could not find room with id '" + roomId + "'. Maybe the room was closed a few milliseconds ago and there was a race condition?");
@ -765,6 +783,7 @@ export class SocketManager {
room.leave(recipient);
const sendUserMessage = new SendUserMessage();
sendUserMessage.setMessage(message);
sendUserMessage.setType('banned');
const subToPusherMessage = new SubToPusherMessage();

View File

@ -26,6 +26,7 @@ function createJoinRoomMessage(uuid: string, x: number, y: number): JoinRoomMess
positionMessage.setMoving(false);
const joinRoomMessage = new JoinRoomMessage();
joinRoomMessage.setUseruuid('1');
joinRoomMessage.setIpaddress('10.0.0.2');
joinRoomMessage.setName('foo');
joinRoomMessage.setRoomid('_/global/test.json');
joinRoomMessage.setPositionmessage(positionMessage);

View File

@ -25,14 +25,14 @@ describe("PositionNotifier", () => {
leaveTriggered = true;
});
const user1 = new User(1, 'test', {
const user1 = new User(1, 'test', '10.0.0.2', {
x: 500,
y: 500,
moving: false,
direction: 'down'
}, false, positionNotifier, {} as UserSocket, [], 'foo', []);
const user2 = new User(2, 'test', {
const user2 = new User(2, 'test', '10.0.0.2', {
x: -9999,
y: -9999,
moving: false,
@ -100,14 +100,14 @@ describe("PositionNotifier", () => {
leaveTriggered = true;
});
const user1 = new User(1, 'test', {
const user1 = new User(1, 'test', '10.0.0.2', {
x: 500,
y: 500,
moving: false,
direction: 'down'
}, false, positionNotifier, {} as UserSocket, [], 'foo', []);
const user2 = new User(2, 'test', {
const user2 = new User(2, 'test', '10.0.0.2', {
x: 0,
y: 0,
moving: false,

View File

@ -3,7 +3,7 @@
local namespace = env.GITHUB_REF_SLUG,
local tag = namespace,
local url = if namespace == "master" then "workadventu.re" else namespace+".workadventure.test.thecodingmachine.com",
local adminUrl = if namespace == "master" || namespace == "develop" || std.startsWith(namespace, "admin") then "https://admin."+url else null,
local adminUrl = if namespace == "master" || namespace == "develop" || std.startsWith(namespace, "admin") then "https://"+url else null,
"$schema": "https://raw.githubusercontent.com/thecodingmachine/deeployer/master/deeployer.schema.json",
"version": "1.0",
"containers": {
@ -72,13 +72,14 @@
"env": {
"API_URL": "pusher."+url,
"UPLOADER_URL": "uploader."+url,
"ADMIN_URL": "admin."+url,
"ADMIN_URL": url,
"JITSI_URL": env.JITSI_URL,
"SECRET_JITSI_KEY": env.SECRET_JITSI_KEY,
"TURN_SERVER": "turn:coturn.workadventu.re:443,turns:coturn.workadventu.re:443",
"TURN_USER": "workadventure",
"TURN_PASSWORD": "WorkAdventure123",
"JITSI_PRIVATE_MODE": if env.SECRET_JITSI_KEY != '' then "true" else "false"
"JITSI_PRIVATE_MODE": if env.SECRET_JITSI_KEY != '' then "true" else "false",
//"GA_TRACKING_ID": "UA-10196481-11"
}
},
"uploader": {
@ -100,17 +101,6 @@
},
"ports": [80]
},
"website": {
"image": "thecodingmachine/workadventure-website:"+tag,
"host": {
"url": url,
"https": "enable"
},
"ports": [80],
"env": {
"GAME_URL": "https://play."+url
}
}
},
"config": {
"https": {

View File

@ -28,7 +28,7 @@ services:
NODE_ENV: development
API_URL: pusher.workadventure.localhost
UPLOADER_URL: uploader.workadventure.localhost
ADMIN_URL: admin.workadventure.localhost
ADMIN_URL: workadventure.localhost
STARTUP_COMMAND_1: yarn install
TURN_SERVER: "turn:coturn.workadventu.re:443,turns:coturn.workadventu.re:443"
TURN_USER: workadventure
@ -135,23 +135,6 @@ services:
- "traefik.http.routers.uploader-ssl.tls=true"
- "traefik.http.routers.uploader-ssl.service=uploader"
website:
image: thecodingmachine/nodejs:12-apache
environment:
STARTUP_COMMAND_1: npm install
STARTUP_COMMAND_2: npm run watch &
APACHE_DOCUMENT_ROOT: dist/
volumes:
- ./website:/var/www/html
labels:
- "traefik.http.routers.website.rule=Host(`workadventure.localhost`)"
- "traefik.http.routers.website.entryPoints=web"
- "traefik.http.services.website.loadbalancer.server.port=80"
- "traefik.http.routers.website-ssl.rule=Host(`workadventure.localhost`)"
- "traefik.http.routers.website-ssl.entryPoints=websecure"
- "traefik.http.routers.website-ssl.tls=true"
- "traefik.http.routers.website-ssl.service=website"
messages:
#image: thecodingmachine/nodejs:14
image: thecodingmachine/workadventure-back-base:latest

1
front/.gitignore vendored
View File

@ -8,3 +8,4 @@
*.sh
/dist/index.html
/dist/main.js*
!templater.sh

View File

@ -11,5 +11,6 @@ COPY --from=builder --chown=docker:docker /var/www/messages/generated /var/www/h
RUN yarn install
ENV NODE_ENV=production
ENV STARTUP_COMMAND_0="./templater.sh"
ENV STARTUP_COMMAND_1="yarn run build"
ENV APACHE_DOCUMENT_ROOT=dist/

9
front/dist/ga.html.tmpl vendored Normal file
View File

@ -0,0 +1,9 @@
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=<!-- TRACKING NUMBER -->"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', '<!-- TRACKING NUMBER -->');
</script>

View File

@ -6,6 +6,9 @@
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<!-- TRACK CODE -->
<!-- END TRACK CODE -->
<link rel="apple-touch-icon" sizes="57x57" href="static/images/favicons/apple-icon-57x57.png">
<link rel="apple-touch-icon" sizes="60x60" href="static/images/favicons/apple-icon-60x60.png">
<link rel="apple-touch-icon" sizes="72x72" href="static/images/favicons/apple-icon-72x72.png">
@ -173,4 +176,4 @@
</audio>
</body>
</html>
</html>

View File

@ -26,7 +26,7 @@
"axios": "^0.21.1",
"generic-type-guard": "^3.2.0",
"google-protobuf": "^3.13.0",
"phaser": "^3.22.0",
"phaser": "^3.52.0",
"queue-typescript": "^1.0.1",
"quill": "^1.3.7",
"simple-peer": "^9.6.2",

View File

@ -48,11 +48,10 @@ class ConnectionManager {
}
let roomId: string
if (connexionType === GameConnexionTypes.empty) {
let defaultMapUrl = window.location.host.replace('play.', 'maps.') + URL_ROOM_STARTED;
roomId = urlManager.editUrlForRoom(URL_ROOM_STARTED, null, null);
if (URL_ROOM_STARTED.startsWith('http://') || URL_ROOM_STARTED.startsWith('https://')) {
defaultMapUrl = URL_ROOM_STARTED.replace('http://', '').replace('https://', '');
roomId = '/_/global/' + URL_ROOM_STARTED.replace('http://', '').replace('https://', '');
}
roomId = urlManager.editUrlForRoom(defaultMapUrl, null, null);
} else {
roomId = window.location.pathname + window.location.hash;
}

View File

@ -25,7 +25,7 @@ export class Room {
this.id = this.id.substr(0, indexOfHash);
}
}
public static getIdFromIdentifier(identifier: string, baseUrl: string, currentInstance: string): {roomId: string, hash: string} {
let roomId = '';
let hash = '';
@ -72,8 +72,9 @@ export class Room {
console.log('Map ', this.id, ' resolves to URL ', data.mapUrl);
resolve(data.mapUrl);
return;
}).catch((reason) => {
reject(reason);
});
}
});
}

View File

@ -1,8 +1,8 @@
const DEBUG_MODE: boolean = process.env.DEBUG_MODE == "true";
const URL_ROOM_STARTED : string = process.env.URL_ROOM_STARTED || '/Floor0/floor0.json';
const URL_ROOM_STARTED : string = process.env.URL_ROOM_STARTED || 'tcm/workadventure/floor0';
const API_URL = (process.env.API_PROTOCOL || (typeof(window) !== 'undefined' ? window.location.protocol : 'http:')) + '//' + (process.env.API_URL || "pusher.workadventure.localhost");
const UPLOADER_URL = (process.env.API_PROTOCOL || (typeof(window) !== 'undefined' ? window.location.protocol : 'http:')) + '//' + (process.env.UPLOADER_URL || 'uploader.workadventure.localhost');
const ADMIN_URL = (process.env.API_PROTOCOL || (typeof(window) !== 'undefined' ? window.location.protocol : 'http:')) + '//' + (process.env.ADMIN_URL || "admin.workadventure.localhost");
const ADMIN_URL = (process.env.API_PROTOCOL || (typeof(window) !== 'undefined' ? window.location.protocol : 'http:')) + '//' + (process.env.ADMIN_URL || "workadventure.localhost");
const TURN_SERVER: string = process.env.TURN_SERVER || "turn:numb.viagenie.ca";
const TURN_USER: string = process.env.TURN_USER || 'g.parant@thecodingmachine.com';
const TURN_PASSWORD: string = process.env.TURN_PASSWORD || 'itcugcOHxle9Acqi$';

View File

@ -45,7 +45,6 @@ import FILE_LOAD_ERROR = Phaser.Loader.Events.FILE_LOAD_ERROR;
import {GameMap} from "./GameMap";
import {coWebsiteManager} from "../../WebRtc/CoWebsiteManager";
import {mediaManager} from "../../WebRtc/MediaManager";
import {FourOFourSceneName} from "../Reconnecting/FourOFourScene";
import {ItemFactoryInterface} from "../Items/ItemFactoryInterface";
import {ActionableItem} from "../Items/ActionableItem";
import {UserInputManager} from "../UserInput/UserInputManager";
@ -68,6 +67,7 @@ import {OpenChatIcon, openChatIconName} from "../Components/OpenChatIcon";
import {SelectCharacterScene, SelectCharacterSceneName} from "../Login/SelectCharacterScene";
import {TextureError} from "../../Exception/TextureError";
import {addLoader} from "../Components/Loader";
import {ErrorSceneName} from "../Reconnecting/ErrorScene";
export interface GameSceneInitInterface {
initPosition: PointInterface|null,
@ -186,8 +186,10 @@ export class GameScene extends ResizableScene implements CenterListener {
this.load.image(openChatIconName, 'resources/objects/talk.png');
this.load.on(FILE_LOAD_ERROR, (file: {src: string}) => {
this.scene.start(FourOFourSceneName, {
file: file.src
this.scene.start(ErrorSceneName, {
title: 'Network error',
subTitle: 'An error occurred while loading resource:',
message: file.src
});
});
this.load.scenePlugin('AnimatedTiles', 'resources/plugins/AnimatedTiles.js', 'animatedTiles', 'animatedTiles');
@ -377,7 +379,7 @@ export class GameScene extends ResizableScene implements CenterListener {
//notify game manager can to create currentUser in map
this.createCurrentPlayer();
this.removeAllRemotePlayers(); //cleanup the list of remote players in case the scene was rebooted
this.initCamera();
(this as any).animatedTiles.init(this.Map); // eslint-disable-line @typescript-eslint/no-explicit-any
@ -1045,7 +1047,7 @@ export class GameScene extends ResizableScene implements CenterListener {
event: addPlayerData
});
}
private doAddPlayer(addPlayerData : AddPlayerInterface): void {
//check if exist player, if exist, move position
if(this.MapPlayersByKey.has(addPlayerData.userId)){
@ -1198,7 +1200,7 @@ export class GameScene extends ResizableScene implements CenterListener {
// Let's put this in Game coordinates by applying the zoom level:
xCenter /= ZOOM_LEVEL * RESOLUTION;
yCenter /= ZOOM_LEVEL * RESOLUTION;
this.cameras.main.startFollow(this.CurrentPlayer, true, 1, 1, xCenter - this.game.renderer.width / 2, yCenter - this.game.renderer.height / 2);
}

View File

@ -253,6 +253,8 @@ export class EnableCameraScene extends Phaser.Scene {
this.pressReturnField.setVisible(!!(Math.floor(time / 500) % 2));
this.soundMeterSprite.setVolume(this.soundMeter.getVolume());
mediaManager.setLastUpdateScene();
}
private login(): void {

View File

@ -1,7 +1,7 @@
import {gameManager} from "../Game/GameManager";
import {Scene} from "phaser";
import {LoginSceneName} from "./LoginScene";
import {FourOFourSceneName} from "../Reconnecting/FourOFourScene";
import {ErrorScene} from "../Reconnecting/ErrorScene";
import {WAError} from "../Reconnecting/WAError";
export const EntrySceneName = "EntryScene";
@ -20,10 +20,11 @@ export class EntryScene extends Scene {
gameManager.init(this.scene).then((nextSceneName) => {
this.scene.start(nextSceneName);
}).catch((err) => {
console.error(err)
this.scene.start(FourOFourSceneName, {
url: window.location.pathname.toString()
});
if (err.response && err.response.status == 404) {
ErrorScene.showError(new WAError('Page Not Found', 'Could not find map', window.location.pathname), this.scene);
} else {
ErrorScene.showError(err, this.scene);
}
});
}
}

View File

@ -0,0 +1,120 @@
import {TextField} from "../Components/TextField";
import Image = Phaser.GameObjects.Image;
import Sprite = Phaser.GameObjects.Sprite;
import Text = Phaser.GameObjects.Text;
import ScenePlugin = Phaser.Scenes.ScenePlugin;
import {WAError} from "./WAError";
export const ErrorSceneName = "ErrorScene";
enum Textures {
icon = "icon",
mainFont = "main_font"
}
export class ErrorScene extends Phaser.Scene {
private titleField!: TextField;
private subTitleField!: TextField;
private messageField!: Text;
private logo!: Image;
private cat!: Sprite;
private title!: string;
private subTitle!: string;
private message!: string;
constructor() {
super({
key: ErrorSceneName
});
}
init({title, subTitle, message}: { title?: string, subTitle?: string, message?: string }) {
this.title = title ? title : '';
this.subTitle = subTitle ? subTitle : '';
this.message = message ? message : '';
}
preload() {
this.load.image(Textures.icon, "resources/logos/tcm_full.png");
// Note: arcade.png from the Phaser 3 examples at: https://github.com/photonstorm/phaser3-examples/tree/master/public/assets/fonts/bitmap
this.load.bitmapFont(Textures.mainFont, 'resources/fonts/arcade.png', 'resources/fonts/arcade.xml');
this.load.spritesheet(
'cat',
'resources/characters/pipoya/Cat 01-1.png',
{frameWidth: 32, frameHeight: 32}
);
}
create() {
this.logo = new Image(this, this.game.renderer.width - 30, this.game.renderer.height - 20, Textures.icon);
this.add.existing(this.logo);
this.titleField = new TextField(this, this.game.renderer.width / 2, this.game.renderer.height / 2, this.title);
this.subTitleField = new TextField(this, this.game.renderer.width / 2, this.game.renderer.height / 2 + 24, this.subTitle);
this.messageField = this.add.text(this.game.renderer.width / 2, this.game.renderer.height / 2 + 38, this.message, {
fontFamily: 'Georgia, "Goudy Bookletter 1911", Times, serif',
fontSize: '10px'
});
this.messageField.setOrigin(0.5, 0.5);
this.cat = this.physics.add.sprite(this.game.renderer.width / 2, this.game.renderer.height / 2 - 32, 'cat', 6);
this.cat.flipY = true;
}
/**
* Displays the error page, with an error message matching the "error" parameters passed in.
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
public static showError(error: any, scene: ScenePlugin): void {
console.error(error);
if (typeof error === 'string' || error instanceof String) {
scene.start(ErrorSceneName, {
title: 'An error occurred',
subTitle: error
});
} else if (error instanceof WAError) {
scene.start(ErrorSceneName, {
title: error.title,
subTitle: error.subTitle,
message: error.details
});
} else if (error.response) {
// Axios HTTP error
// client received an error response (5xx, 4xx)
scene.start(ErrorSceneName, {
title: 'HTTP ' + error.response.status + ' - ' + error.response.statusText,
subTitle: 'An error occurred while accessing URL:',
message: error.response.config.url
});
} else if (error.request) {
// Axios HTTP error
// client never received a response, or request never left
scene.start(ErrorSceneName, {
title: 'Network error',
subTitle: error.message
});
} else if (error instanceof Error) {
// Error
scene.start(ErrorSceneName, {
title: 'An error occurred',
subTitle: error.name,
message: error.message
});
} else {
throw error;
}
}
/**
* Displays the error page, with an error message matching the "error" parameters passed in.
*/
public static startErrorPage(title: string, subTitle: string, message: string, scene: ScenePlugin): void {
scene.start(ErrorSceneName, {
title,
subTitle,
message
});
}
}

View File

@ -1,68 +0,0 @@
import {TextField} from "../Components/TextField";
import Image = Phaser.GameObjects.Image;
import Sprite = Phaser.GameObjects.Sprite;
import Text = Phaser.GameObjects.Text;
export const FourOFourSceneName = "FourOFourScene";
enum Textures {
icon = "icon",
mainFont = "main_font"
}
export class FourOFourScene extends Phaser.Scene {
private mapNotFoundField!: TextField;
private couldNotFindField!: TextField;
private fileNameField!: Text;
private logo!: Image;
private cat!: Sprite;
private file: string|undefined;
private url: string|undefined;
constructor() {
super({
key: FourOFourSceneName
});
}
init({ file, url }: { file?: string, url?: string }) {
this.file = file;
this.url = url;
}
preload() {
this.load.image(Textures.icon, "resources/logos/tcm_full.png");
// Note: arcade.png from the Phaser 3 examples at: https://github.com/photonstorm/phaser3-examples/tree/master/public/assets/fonts/bitmap
this.load.bitmapFont(Textures.mainFont, 'resources/fonts/arcade.png', 'resources/fonts/arcade.xml');
this.load.spritesheet(
'cat',
'resources/characters/pipoya/Cat 01-1.png',
{frameWidth: 32, frameHeight: 32}
);
}
create() {
this.logo = new Image(this, this.game.renderer.width - 30, this.game.renderer.height - 20, Textures.icon);
this.add.existing(this.logo);
this.mapNotFoundField = new TextField(this, this.game.renderer.width / 2, this.game.renderer.height / 2, "404 - File not found");
let text: string = '';
if (this.file !== undefined) {
text = "Could not load map"
}
if (this.url !== undefined) {
text = "Invalid URL"
}
this.couldNotFindField = new TextField(this, this.game.renderer.width / 2, this.game.renderer.height / 2 + 24, text);
const url = this.file ? this.file : this.url;
if (url !== undefined) {
this.fileNameField = this.add.text(this.game.renderer.width / 2, this.game.renderer.height / 2 + 38, url, { fontFamily: 'Georgia, "Goudy Bookletter 1911", Times, serif', fontSize: '10px' });
this.fileNameField.setOrigin(0.5, 0.5);
}
this.cat = this.physics.add.sprite(this.game.renderer.width / 2, this.game.renderer.height / 2 - 32, 'cat', 6);
this.cat.flipY=true;
}
}

View File

@ -0,0 +1,26 @@
export class WAError extends Error {
private _title: string;
private _subTitle: string;
private _details: string;
constructor (title: string, subTitle: string, details: string) {
super(title+' - '+subTitle+' - '+details);
this._title = title;
this._subTitle = subTitle;
this._details = details;
// Set the prototype explicitly.
Object.setPrototypeOf (this, WAError.prototype);
}
get title(): string {
return this._title;
}
get subTitle(): string {
return this._subTitle;
}
get details(): string {
return this._details;
}
}

View File

@ -40,7 +40,7 @@ class UrlManager {
if (organizationSlug) {
newUrl = '/@/'+organizationSlug+'/'+worldSlug+'/'+roomSlug;
} else {
newUrl = '/_/global/'+roomSlug;
newUrl = '/@/'+roomSlug;
}
history.pushState({}, 'WorkAdventure', newUrl);
return newUrl;

View File

@ -13,7 +13,7 @@ if(localValueVideo){
let videoConstraint: boolean|MediaTrackConstraints = {
width: { min: 640, ideal: 1280, max: 1920 },
height: { min: 400, ideal: 720 },
frameRate: {ideal: valueVideo},
frameRate: { ideal: valueVideo },
facingMode: "user",
resizeMode: 'crop-and-scale',
aspectRatio: 1.777777778

View File

@ -6,7 +6,6 @@ import {LoginScene} from "./Phaser/Login/LoginScene";
import {ReconnectingScene} from "./Phaser/Reconnecting/ReconnectingScene";
import {SelectCharacterScene} from "./Phaser/Login/SelectCharacterScene";
import {EnableCameraScene} from "./Phaser/Login/EnableCameraScene";
import {FourOFourScene} from "./Phaser/Reconnecting/FourOFourScene";
import WebGLRenderer = Phaser.Renderer.WebGL.WebGLRenderer;
import {OutlinePipeline} from "./Phaser/Shaders/OutlinePipeline";
import {CustomizeScene} from "./Phaser/Login/CustomizeScene";
@ -15,6 +14,7 @@ import {EntryScene} from "./Phaser/Login/EntryScene";
import {coWebsiteManager} from "./WebRtc/CoWebsiteManager";
import {MenuScene} from "./Phaser/Menu/MenuScene";
import {localUserStore} from "./Connexion/LocalUserStore";
import {ErrorScene} from "./Phaser/Reconnecting/ErrorScene";
// Load Jitsi if the environment variable is set.
if (JITSI_URL) {
@ -59,7 +59,7 @@ const config: GameConfig = {
width: width / RESOLUTION,
height: height / RESOLUTION,
parent: "game",
scene: [EntryScene, LoginScene, SelectCharacterScene, EnableCameraScene, ReconnectingScene, FourOFourScene, CustomizeScene, MenuScene],
scene: [EntryScene, LoginScene, SelectCharacterScene, EnableCameraScene, ReconnectingScene, ErrorScene, CustomizeScene, MenuScene],
zoom: RESOLUTION,
fps: fps,
dom: {

26
front/templater.sh Executable file
View File

@ -0,0 +1,26 @@
#!/usr/bin/env bash
set -x
set -o nounset errexit
template_file_index=dist/index.html.tmpl
generated_file_index=dist/index.html
tmp_trackcodefile=/tmp/trackcode
# To inject tracking code, you have two choices:
# 1) Template using the provided google analytics
# 2) Insert with your own provided code, by overriding the ANALYTICS_CODE_PATH
# The ANALYTICS_CODE_PATH is the location of a file inside the container
ANALYTICS_CODE_PATH="${ANALYTICS_CODE_PATH:-dist/ga.html.tmpl}"
if [[ "${INSERT_ANALYTICS:-NO}" == "NO" ]]; then
echo "" > "${tmp_trackcodefile}"
fi
# Automatically insert analytics if GA_TRACKING_ID is set
if [[ "${GA_TRACKING_ID:-}" != "" || "${INSERT_ANALYTICS:-NO}" != "NO" ]]; then
echo "Templating code from ${ANALYTICS_CODE_PATH}"
sed "s#<!-- TRACKING NUMBER -->#${GA_TRACKING_ID:-}#g" "${ANALYTICS_CODE_PATH}" > "$tmp_trackcodefile"
fi
echo "Templating ${generated_file_index} from ${template_file_index}"
sed "/<!-- TRACK CODE -->/r ${tmp_trackcodefile}" "${template_file_index}" > "${generated_file_index}"
rm "${tmp_trackcodefile}"

View File

@ -39,7 +39,7 @@ module.exports = {
plugins: [
new HtmlWebpackPlugin(
{
template: './src/index.html'
template: './dist/index.html'
}
),
new webpack.ProvidePlugin({

View File

@ -3663,10 +3663,10 @@ pbkdf2@^3.0.3:
safe-buffer "^5.0.1"
sha.js "^2.4.8"
phaser@^3.22.0:
version "3.51.0"
resolved "https://registry.yarnpkg.com/phaser/-/phaser-3.51.0.tgz#b0c7ee2b21e795830d74f476dd30816a42b023bd"
integrity sha512-Z7XNToZWO60Zx/YetaoeGSeELy5ND45TPPfYB9HtQU2692ACXc/nioQaWp20NzTMgeBsgl6vYf3CI82y/DzSyg==
phaser@^3.52.0:
version "3.52.0"
resolved "https://registry.yarnpkg.com/phaser/-/phaser-3.52.0.tgz#834dd7a7717308c2576d2450d0806317cf3d1ea8"
integrity sha512-oH39AUXR+cletB3GIvLnVO2J7/M6xs0D0LamVdCrbCDO3jckQOVBcqR6SJ2wam+4D5iJTCXv8K+FmxFzcTUWuA==
dependencies:
eventemitter3 "^4.0.7"
exports-loader "^1.1.1"

View File

@ -278,7 +278,7 @@
{
"name":"exitUrl",
"type":"string",
"value":"..\/Floor1\/floor1.json"
"value":"\/@\/tcm\/workadventure\/floor1"
}],
"type":"tilelayer",
"visible":true,

View File

@ -85,7 +85,7 @@
{
"name":"exitSceneUrl",
"type":"string",
"value":"..\/Floor0\/floor0.json#down-the-stairs"
"value":"\/@\/tcm\/workadventure\/floor0#down-the-stairs"
}],
"type":"tilelayer",
"visible":true,
@ -103,7 +103,7 @@
{
"name":"exitSceneUrl",
"type":"string",
"value":"..\/Floor2\/floor2.json#down-the-stairs"
"value":"\/@\/tcm\/workadventure\/floor2#down-the-stairs"
}],
"type":"tilelayer",
"visible":true,
@ -121,7 +121,7 @@
{
"name":"exitSceneUrl",
"type":"string",
"value":"..\/Floor2\/floor2.json#down-the-stairs-secours"
"value":"\/@\/tcm\/workadventure\/floor2#down-the-stairs-secours"
}],
"type":"tilelayer",
"visible":true,

View File

@ -103,7 +103,7 @@
{
"name":"exitSceneUrl",
"type":"string",
"value":"..\/Floor1\/floor1.json#down-the-stairs"
"value":"\/@\/tcm\/workadventure\/floor1#down-the-stairs"
}],
"type":"tilelayer",
"visible":true,
@ -139,7 +139,7 @@
{
"name":"exitSceneUrl",
"type":"string",
"value":"..\/Floor1\/floor1.json#down-the-stairs-secours"
"value":"\/@\/tcm\/workadventure\/floor1#down-the-stairs-secours"
}],
"type":"tilelayer",
"visible":true,

View File

@ -37,7 +37,7 @@
{
"name":"exitSceneUrl",
"type":"string",
"value":"..\/Floor0\/floor0.json"
"value":"\/@\/tcm\/workadventure\/floor0"
}],
"type":"tilelayer",
"visible":true,

View File

@ -193,6 +193,11 @@ message SendUserMessage{
string message = 2;
}
message BanUserMessage{
string type = 1;
string message = 2;
}
message ServerToClientMessage {
oneof message {
BatchMessage batchMessage = 1;
@ -207,6 +212,7 @@ message ServerToClientMessage {
TeleportMessageMessage teleportMessageMessage = 10;
SendJitsiJwtMessage sendJitsiJwtMessage = 11;
SendUserMessage sendUserMessage = 12;
BanUserMessage banUserMessage = 13;
}
}
@ -220,6 +226,7 @@ message JoinRoomMessage {
string userUuid = 4;
string roomId = 5;
repeated string tag = 6;
string IPAddress = 7;
}
message UserJoinedZoneMessage {
@ -272,6 +279,8 @@ message PusherToBackMessage {
StopGlobalMessage stopGlobalMessage = 9;
ReportPlayerMessage reportPlayerMessage = 10;
QueryJitsiJwtMessage queryJitsiJwtMessage = 11;
SendUserMessage sendUserMessage = 12;
BanUserMessage banUserMessage = 13;
}
}
@ -288,6 +297,7 @@ message SubToPusherMessage {
UserLeftZoneMessage userLeftZoneMessage = 5;
ItemEventMessage itemEventMessage = 6;
SendUserMessage sendUserMessage = 7;
BanUserMessage banUserMessage = 8;
}
}
@ -306,10 +316,20 @@ message ServerToAdminClientMessage {
repeated SubToAdminPusherMessage payload = 2;
}*/
message UserJoinedRoomMessage {
string uuid = 1;
string ipAddress = 2;
string name = 3;
}
message UserLeftRoomMessage {
string uuid = 1;
}
message ServerToAdminClientMessage {
oneof message {
string userUuidJoinedRoom = 1;
string userUuidLeftRoom = 2;
UserJoinedRoomMessage userJoinedRoom = 1;
UserLeftRoomMessage userLeftRoom = 2;
}
}

View File

@ -10,8 +10,8 @@
"runprod": "node --max-old-space-size=4096 ./dist/server.js",
"profile": "tsc && node --prof ./dist/server.js",
"test": "ts-node node_modules/jasmine/bin/jasmine --config=jasmine.json",
"lint": "node_modules/.bin/eslint src/ . --ext .ts",
"fix": "node_modules/.bin/eslint --fix src/ . --ext .ts"
"lint": "DEBUG= node_modules/.bin/eslint src/ . --ext .ts",
"fix": "DEBUG= node_modules/.bin/eslint --fix src/ . --ext .ts"
},
"repository": {
"type": "git",

View File

@ -60,10 +60,7 @@ export class AuthenticateController extends BaseController {
}));
} catch (e) {
console.error("An error happened", e)
res.writeStatus(e.status || "500 Internal Server Error");
this.addCorsHeaders(res);
res.end('An error happened');
this.errorToResponse(e, res);
}

View File

@ -8,4 +8,21 @@ export class BaseController {
res.writeHeader('access-control-allow-methods', 'GET, POST, OPTIONS, PUT, PATCH, DELETE');
res.writeHeader('access-control-allow-origin', '*');
}
/**
* Turns any exception into a HTTP response (and logs the error)
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
protected errorToResponse(e: any, res: HttpResponse): void {
console.error("An error happened", e);
if (e.response) {
res.writeStatus(e.response.status+" "+e.response.statusText);
this.addCorsHeaders(res);
res.end("An error occurred: "+e.response.status+" "+e.response.statusText);
} else {
res.writeStatus("500 Internal Server Error")
this.addCorsHeaders(res);
res.end("An error occurred");
}
}
}

View File

@ -91,18 +91,11 @@ export class IoSocketController {
if(message.event === 'user-message') {
const messageToEmit = (message.message as { message: string, type: string, userUuid: string });
switch (message.message.type) {
case 'ban': {
socketManager.emitSendUserMessage(messageToEmit.userUuid, messageToEmit.message, roomId);
break;
}
case 'banned': {
socketManager.emitBan(messageToEmit.userUuid, messageToEmit.message, roomId);
break;
}
default: {
break;
}
if(messageToEmit.type === 'banned'){
socketManager.emitBan(messageToEmit.userUuid, messageToEmit.message, messageToEmit.type);
}
if(messageToEmit.type === 'ban') {
socketManager.emitSendUserMessage(messageToEmit.userUuid, messageToEmit.message, messageToEmit.type);
}
}
}catch (err) {
@ -148,6 +141,7 @@ export class IoSocketController {
const websocketKey = req.getHeader('sec-websocket-key');
const websocketProtocol = req.getHeader('sec-websocket-protocol');
const websocketExtensions = req.getHeader('sec-websocket-extensions');
const IPAddress = req.getHeader('x-forwarded-for');
const roomId = query.roomId;
if (typeof roomId !== 'string') {
@ -176,7 +170,7 @@ export class IoSocketController {
characterLayers = [ characterLayers ];
}
const userUuid = await jwtTokenManager.getUserUuidFromToken(token);
const userUuid = await jwtTokenManager.getUserUuidFromToken(token, IPAddress, roomId);
let memberTags: string[] = [];
let memberTextures: CharacterTexture[] = [];
@ -217,6 +211,7 @@ export class IoSocketController {
url,
token,
userUuid,
IPAddress,
roomId,
name,
characterLayers: characterLayerObjs,
@ -336,6 +331,7 @@ export class IoSocketController {
client.userId = this.nextUserId;
this.nextUserId++;
client.userUuid = ws.userUuid;
client.IPAddress = ws.IPAddress;
client.token = ws.token;
client.batchedMessages = new BatchMessage();
client.batchTimeout = null;

View File

@ -59,10 +59,7 @@ export class MapController extends BaseController{
this.addCorsHeaders(res);
res.end(JSON.stringify(mapDetails));
} catch (e) {
console.error(e.message || e);
res.writeStatus("500 Internal Server Error")
this.addCorsHeaders(res);
res.end("An error occurred");
this.errorToResponse(e, res);
}
})();

View File

@ -24,6 +24,7 @@ export interface ExSocketInterface extends WebSocket, Identificable {
roomId: string;
//userId: number; // A temporary (autoincremented) identifier for this user
userUuid: string; // A unique identifier for this user
IPAddress: string; // IP address
name: string;
characterLayers: CharacterLayer[];
position: PointInterface;

View File

@ -14,6 +14,11 @@ export interface AdminApiData {
textures: CharacterTexture[]
}
export interface AdminBannedData {
is_banned: boolean,
message: string
}
export interface CharacterTexture {
id: number,
level: number,
@ -110,6 +115,18 @@ class AdminApi {
headers: {"Authorization": `${ADMIN_API_TOKEN}`}
});
}
async verifyBanUser(organizationMemberToken: string, ipAddress: string, organization: string, world: string): Promise<AdminBannedData> {
if (!ADMIN_API_URL) {
return Promise.reject('No admin backoffice set!');
}
//todo: this call can fail if the corresponding world is not activated or if the token is invalid. Handle that case.
return Axios.get(ADMIN_API_URL + '/api/check-moderate-user/'+organization+'/'+world+'?ipAddress='+ipAddress+'&token='+organizationMemberToken,
{headers: {"Authorization": `${ADMIN_API_TOKEN}`}}
).then((data) => {
return data.data;
});
}
}
export const adminApi = new AdminApi();

View File

@ -2,7 +2,7 @@ import {ADMIN_API_URL, ALLOW_ARTILLERY, SECRET_KEY} from "../Enum/EnvironmentVar
import {uuid} from "uuidv4";
import Jwt from "jsonwebtoken";
import {TokenInterface} from "../Controller/AuthenticateController";
import {adminApi, AdminApiData} from "../Services/AdminApi";
import {adminApi, AdminBannedData} from "../Services/AdminApi";
class JWTTokenManager {
@ -10,7 +10,7 @@ class JWTTokenManager {
return Jwt.sign({userUuid: userUuid}, SECRET_KEY, {expiresIn: '200d'}); //todo: add a mechanic to refresh or recreate token
}
public async getUserUuidFromToken(token: unknown): Promise<string> {
public async getUserUuidFromToken(token: unknown, ipAddress?: string, room?: string): Promise<string> {
if (!token) {
throw new Error('An authentication error happened, a user tried to connect without a token.');
@ -50,14 +50,22 @@ class JWTTokenManager {
if (ADMIN_API_URL) {
//verify user in admin
adminApi.fetchCheckUserByToken(tokenInterface.userUuid).then(() => {
resolve(tokenInterface.userUuid);
}).catch((err) => {
//anonymous user
if(err.response && err.response.status && err.response.status === 404){
let promise = new Promise((resolve) => resolve());
if(ipAddress && room) {
promise = this.verifyBanUser(tokenInterface.userUuid, ipAddress, room);
}
promise.then(() => {
adminApi.fetchCheckUserByToken(tokenInterface.userUuid).then(() => {
resolve(tokenInterface.userUuid);
return;
}
}).catch((err) => {
//anonymous user
if (err.response && err.response.status && err.response.status === 404) {
resolve(tokenInterface.userUuid);
return;
}
reject(err);
});
}).catch((err) => {
reject(err);
});
} else {
@ -67,6 +75,27 @@ class JWTTokenManager {
});
}
private verifyBanUser(userUuid: string, ipAddress: string, room: string): Promise<AdminBannedData> {
const parts = room.split('/');
if (parts.length < 3 || parts[0] !== '@') {
return Promise.resolve({
is_banned: false,
message: ''
});
}
const organization = parts[1];
const world = parts[2];
return adminApi.verifyBanUser(userUuid, ipAddress, organization, world).then((data: AdminBannedData) => {
if (data && data.is_banned) {
throw new Error('User was banned');
}
return data;
}).catch((err) => {
throw err;
});
}
private isValidToken(token: object): token is TokenInterface {
return !(typeof((token as TokenInterface).userUuid) !== 'string');
}

View File

@ -2,11 +2,8 @@ import {PusherRoom} from "../Model/PusherRoom";
import {CharacterLayer, ExSocketInterface} from "../Model/Websocket/ExSocketInterface";
import {
GroupDeleteMessage,
GroupUpdateMessage,
ItemEventMessage,
ItemStateMessage,
PlayGlobalMessage,
PointMessage,
PositionMessage,
RoomJoinedMessage,
ServerToClientMessage,
@ -14,23 +11,19 @@ import {
SilentMessage,
SubMessage,
ReportPlayerMessage,
UserJoinedMessage,
UserLeftMessage,
UserMovedMessage,
UserMovesMessage,
ViewportMessage,
WebRtcDisconnectMessage,
WebRtcSignalToClientMessage,
WebRtcSignalToServerMessage,
WebRtcStartMessage,
QueryJitsiJwtMessage,
SendJitsiJwtMessage,
SendUserMessage,
JoinRoomMessage,
CharacterLayerMessage,
PusherToBackMessage,
AdminPusherToBackMessage,
ServerToAdminClientMessage, AdminMessage, BanMessage
ServerToAdminClientMessage,
SendUserMessage,
BanUserMessage, UserJoinedRoomMessage, UserLeftRoomMessage
} from "../Messages/generated/messages_pb";
import {PointInterface} from "../Model/Websocket/PointInterface";
import {ProtobufUtils} from "../Model/Websocket/ProtobufUtils";
@ -79,23 +72,33 @@ export class SocketManager implements ZoneEventListener {
}
async handleAdminRoom(client: ExAdminSocketInterface, roomId: string): Promise<void> {
console.log('Calling adminRoom')
const apiClient = await apiClientRepository.getClient(roomId);
const adminRoomStream = apiClient.adminRoom();
client.adminConnection = adminRoomStream;
adminRoomStream.on('data', (message: ServerToAdminClientMessage) => {
if (message.hasUseruuidjoinedroom()) {
const userUuid = message.getUseruuidjoinedroom();
if (message.hasUserjoinedroom()) {
const userJoinedRoomMessage = message.getUserjoinedroom() as UserJoinedRoomMessage;
if (!client.disconnecting) {
client.send('MemberJoin:'+userUuid+';'+roomId);
client.send(JSON.stringify({
type: 'MemberJoin',
data: {
uuid: userJoinedRoomMessage.getUuid(),
name: userJoinedRoomMessage.getName(),
ipAddress: userJoinedRoomMessage.getIpaddress(),
roomId: roomId,
}
}));
}
} else if (message.hasUseruuidleftroom()) {
const userUuid = message.getUseruuidleftroom();
} else if (message.hasUserleftroom()) {
const userLeftRoomMessage = message.getUserleftroom() as UserLeftRoomMessage;
if (!client.disconnecting) {
client.send('MemberLeave:'+userUuid+';'+roomId);
client.send(JSON.stringify({
type: 'MemberLeave',
data: {
uuid: userLeftRoomMessage.getUuid()
}
}));
}
} else {
throw new Error('Unexpected admin message');
@ -145,15 +148,16 @@ export class SocketManager implements ZoneEventListener {
}
async handleJoinRoom(client: ExSocketInterface): Promise<void> {
const position = client.position;
const viewport = client.viewport;
try {
const joinRoomMessage = new JoinRoomMessage();
joinRoomMessage.setUseruuid(client.userUuid);
joinRoomMessage.setIpaddress(client.IPAddress);
joinRoomMessage.setRoomid(client.roomId);
joinRoomMessage.setName(client.name);
joinRoomMessage.setPositionmessage(ProtobufUtils.toPositionMessage(client.position));
joinRoomMessage.setTagList(client.tags);
for (const characterLayer of client.characterLayers) {
const characterLayerMessage = new CharacterLayerMessage();
characterLayerMessage.setName(characterLayer.name);
@ -540,51 +544,54 @@ export class SocketManager implements ZoneEventListener {
client.send(serverToClientMessage.serializeBinary().buffer, true);
}
public async emitSendUserMessage(userUuid: string, message: string, roomId: string): Promise<void> {
public emitSendUserMessage(userUuid: string, message: string, type: string): void {
const client = this.searchClientByUuid(userUuid);
if(!client){
throw Error('client not found');
}
const backConnection = await apiClientRepository.getClient(roomId);
const adminMessage = new AdminMessage();
adminMessage.setRecipientuuid(userUuid);
const adminMessage = new SendUserMessage();
adminMessage.setMessage(message);
adminMessage.setRoomid(roomId);
adminMessage.setType(type);
const pusherToBackMessage = new PusherToBackMessage();
pusherToBackMessage.setSendusermessage(adminMessage);
client.backConnection.write(pusherToBackMessage);
/*const backConnection = await apiClientRepository.getClient(client.roomId);
const adminMessage = new AdminMessage();
adminMessage.setMessage(message);
adminMessage.setRoomid(client.roomId);
adminMessage.setRecipientuuid(client.userUuid);
backConnection.sendAdminMessage(adminMessage, (error) => {
if (error !== null) {
console.error('Error while sending admin message', error);
}
});
/*
const socket = this.searchClientByUuid(messageToSend.userUuid);
if(!socket){
throw 'socket was not found';
}
const sendUserMessage = new SendUserMessage();
sendUserMessage.setMessage(messageToSend.message);
sendUserMessage.setType(messageToSend.type);
const serverToClientMessage = new ServerToClientMessage();
serverToClientMessage.setSendusermessage(sendUserMessage);
if (!socket.disconnecting) {
socket.send(serverToClientMessage.serializeBinary().buffer, true);
}
return socket;*/
});*/
}
public async emitBan(userUuid: string, message: string, roomId: string): Promise<void> {
const backConnection = await apiClientRepository.getClient(roomId);
public emitBan(userUuid: string, message: string, type: string): void {
const client = this.searchClientByUuid(userUuid);
if(!client){
throw Error('client not found');
}
const banMessage = new BanMessage();
banMessage.setRecipientuuid(userUuid);
banMessage.setRoomid(roomId);
const banUserMessage = new BanUserMessage();
banUserMessage.setMessage(message);
banUserMessage.setType(type);
const pusherToBackMessage = new PusherToBackMessage();
pusherToBackMessage.setBanusermessage(banUserMessage);
client.backConnection.write(pusherToBackMessage);
backConnection.ban(banMessage, (error) => {
/*const backConnection = await apiClientRepository.getClient(client.roomId);
const adminMessage = new AdminMessage();
adminMessage.setMessage(message);
adminMessage.setRoomid(client.roomId);
adminMessage.setRecipientuuid(client.userUuid);
backConnection.sendAdminMessage(adminMessage, (error) => {
if (error !== null) {
console.error('Error while sending ban message', error);
console.error('Error while sending admin message', error);
}
});
});*/
}
/**

5
website/.gitignore vendored
View File

@ -1,5 +0,0 @@
/node_modules/
/dist/bundle.js
/dist/main.css
/dist/fonts
/dist/images

View File

@ -1,9 +0,0 @@
# we are rebuilding on each deploy to cope with the GAME_URL environment URL
FROM thecodingmachine/nodejs:12-apache
COPY --chown=docker:docker . .
RUN yarn install
ENV NODE_ENV=production
ENV STARTUP_COMMAND_1="yarn run build"
ENV APACHE_DOCUMENT_ROOT=dist/

View File

@ -1,45 +0,0 @@
Basic Webpack config for simple website.
Install all packages:
```
$ npm install
```
Run webpack
```
$ npm run build
```
Done! Open index.html in browser for a cat image.
----
### Notice about production mode and postcss.config.js
In *postcss.config.js* there is a check for **process.env.NODE_ENV** variable. The thing is even if you set Webpack mode to production it *won't* automatically change Node environment variable.
The simplest way to configure this is to install *cross-env* package:
```
$ npm install --save-dev cross-env
```
Then just add another npm script in *package.json* for production mode:
```javascript
"scripts": {
"build": "webpack --config webpack.config.js",
"build-production": "cross-env NODE_ENV=production webpack --config webpack.config.js"
}
```
Now when you run `npm run build-production` the *process.env.NODE_ENV* variable will be production and postcss.config.js check is going to work:
```javascript
if(process.env.NODE_ENV === 'production') {
module.exports = {
plugins: [
require('autoprefixer'),
require('cssnano')
]
}
}
```
[From Webpack documentation](https://webpack.js.org/guides/production/):
Technically, *NODE_ENV* is a system environment variable that Node.js exposes into running scripts. It is used by convention to determine dev-vs-prod behavior by server tools, build scripts, and client-side libraries. Contrary to expectations, *process.env.NODE_ENV* **is not set to "production"** within the build script webpack.config.js. Thus, conditionals like `process.env.NODE_ENV === 'production' ? '[name].[hash].bundle.js' : '[name].bundle.js'` within webpack configurations do not work as expected.

View File

@ -1,201 +0,0 @@
<!doctype html>
<html class="no-js" lang="">
<head>
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-10196481-11"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'UA-10196481-11');
</script>
<link rel="apple-touch-icon" sizes="57x57" href="static/images/favicons/apple-icon-57x57.png">
<link rel="apple-touch-icon" sizes="60x60" href="static/images/favicons/apple-icon-60x60.png">
<link rel="apple-touch-icon" sizes="72x72" href="static/images/favicons/apple-icon-72x72.png">
<link rel="apple-touch-icon" sizes="76x76" href="static/images/favicons/apple-icon-76x76.png">
<link rel="apple-touch-icon" sizes="114x114" href="static/images/favicons/apple-icon-114x114.png">
<link rel="apple-touch-icon" sizes="120x120" href="static/images/favicons/apple-icon-120x120.png">
<link rel="apple-touch-icon" sizes="144x144" href="static/images/favicons/apple-icon-144x144.png">
<link rel="apple-touch-icon" sizes="152x152" href="static/images/favicons/apple-icon-152x152.png">
<link rel="apple-touch-icon" sizes="180x180" href="static/images/favicons/apple-icon-180x180.png">
<link rel="icon" type="image/png" sizes="192x192" href="static/images/favicons/android-icon-192x192.png">
<link rel="icon" type="image/png" sizes="32x32" href="static/images/favicons/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="96x96" href="static/images/favicons/favicon-96x96.png">
<link rel="icon" type="image/png" sizes="16x16" href="static/images/favicons/favicon-16x16.png">
<link rel="manifest" href="static/images/favicons/manifest.json">
<meta name="msapplication-TileColor" content="#000000">
<meta name="msapplication-TileImage" content="static/images/favicons/ms-icon-144x144.png">
<meta name="theme-color" content="#000000">
<meta charset="utf-8">
<title>Choose map - WorkAdventu.re</title>
<link rel="stylesheet" href="main.css">
<script src="bundle.js"></script>
</head>
<body class="choose-map">
<div class="container">
<div class="row">
<div class="col">
<a href="/" class="d-block mt-3 pixel-text">
&lt;&lt; BACK TO HOMEPAGE
</a>
</div>
</div>
</div>
<div class="container-fluid container-lg section pt-5">
<h1 class="text-center pixel-title">CHOOSE YOUR MAP&nbsp;!</h1>
<div class="row">
<div class="col text-center">
<p>Pick a map that suits your mood! If you don't find what you need, you can always <a href="create-map.html">CREATE YOUR OWN MAP</a>.</p>
</div>
</div>
<div class="row no-gutters justify-content-center">
<div class="col-12 col-sm-6 col-md-4">
<div class="map-item" data-url="npeguin.github.io/skapa-map/map.json" id="map_1">
<img src="static/images/maps/creative.png">
<p>Need some inspiration? Enter our CREATIVE SPACE&nbsp;!</p>
</div>
</div>
<div class="col-12 col-sm-6 col-md-4">
<div class="map-item" data-url="npeguin.github.io/pub-map/map.json" id="map_2">
<img src="static/images/maps/pub.png">
<p>Too late for working ? Just GO TO THE PUB&nbsp;!</p>
</div>
</div>
<div class="col-12 col-sm-6 col-md-4">
<div class="map-item" data-url="npeguin.github.io/office-map/map.json" id="map_3">
<img src="static/images/maps/office.png">
<p>Want to try a SIMPLE OFFICE map&nbsp;?</p>
</div>
</div>
<div class="col-12 col-sm-6 col-md-4">
<div class="map-item" data-url="npeguin.github.io/classroom-map/map.json" id="map_4">
<img src="static/images/maps/school.png">
<p>Send your kids BACK TO SCHOOL... and rest a bit&nbsp;;)</p>
</div>
</div>
<div class="col-12 col-sm-6 col-md-4">
<div class="map-item" data-url="npeguin.github.io/tower-map/map.json" id="map_5">
<img src="static/images/maps/dungeon.png">
<p>Dungeons & Dragons Nostalgia&nbsp;?</p>
</div>
</div>
<div class="col-12 col-sm-6 col-md-4">
<div class="map-item" data-url="npeguin.github.io/fantasy-map/map.json" id="map_6">
<img src="static/images/maps/fantasy.png">
<p>Explore a fantasy world&nbsp;!</p>
</div>
</div>
<div class="col-12 col-sm-6 col-md-4">
<div class="map-item" data-url="maps.workadventu.re/Floor0/floor0.json" id="map_7">
<img src="static/images/maps/tcm.png">
<p>Need a bigger Office? Visit us&nbsp;!</p>
</div>
</div>
</div>
<div class="row">
<div class="col text-center">
<div class="map-item" data-url="npeguin.github.io/corridor-map/map.json" id="map_8">
<img src="static/images/maps/street.png">
<p>NO IDEA, or just want to ROAM THE STREETS&nbsp;? Enter the street map and choose your own path&nbsp;!</p>
</div>
</div>
</div>
<div class="row">
<div class="col text-center">
<p>Could not find what you need? No worries, you can always <a href="create-map.html">CREATE YOUR OWN MAP</a>.</p>
</div>
</div>
</div>
<div class="container-fluid container-lg section text-center" id="map-link-container" style="display: none;">
<h1 class="mb-3">YOUR MAP URL IS</h1>
<p id="wa-link" class="mb-5"></p>
<div class="row align-items-center justify-content-center">
<div class="col-sm-8 text-right mb-4 pb-sm-0">
<button class="copy-btn" onclick="copyToClipboard()">COPY MAP URL<small> TO CLIPBOARD</small></button>
</div>
<div class="col-sm-4 text-center text-sm-left">
<span id="new-url">to share it !</span>
<span id="url-copied"><img src="static/images/check.png">Link copied !</span>
</div>
</div>
<div class="row start-area justify-content-center mt-5">
<div class="col-12 col-sm-1 d-none d-sm-block">
<img src="static/images/female-character.gif">
</div>
<div class="col-12 col-sm-4 mb-3 mb-sm-0">
<button id="start-btn" onclick="play()">START <span>&gt;&gt;</span></button>
</div>
<div class="col-12 col-sm-1">
<img src="static/images/male-character.gif">
</div>
</div>
</div>
<script>
var rand = '';
var characters = 'abcdefghijklmnopqrstuvwxyz';
var charactersLength = characters.length;
for ( var i = 0; i < 9; i++ ) {
rand += characters.charAt(Math.floor(Math.random() * charactersLength));
}
var id = rand.slice(0,3) + '-' + rand.slice(3,6) + '-' + rand.slice(6);
var mapLink = document.getElementById('wa-link');
var mapLinkContainer = document.getElementById('map-link-container');
function setSelectedMap(element){
var items = document.querySelectorAll(".map-item");
[].forEach.call(items, function(el) {
el.classList.remove("active");
});
element.classList.add("active");
mapLink.innerText = window.location.protocol + "//play."+window.location.host + "/_/" + id + "/" + element.dataset.url;
mapLinkContainer.style.display = 'block';
document.getElementById('new-url').style.display = 'inline';
document.getElementById('url-copied').style.display = 'none';
mapLinkContainer.scrollIntoView({
block: "start",
inline: "nearest",
behavior: "smooth"
})
}
function copyToClipboard() {
var aux = document.createElement("input");
aux.setAttribute("value", mapLink.innerHTML);
document.body.appendChild(aux);
aux.select();
document.execCommand("copy");
document.body.removeChild(aux);
document.getElementById('new-url').style.display = 'none';
document.getElementById('url-copied').style.display = 'inline';
setTimeout(function(){
document.getElementById('new-url').style.display = 'inline';
document.getElementById('url-copied').style.display = 'none';
}, 2000);
}
function play(){
window.location.assign(mapLink.innerText);
}
(function() {
document.addEventListener('click', function (event) {
// If the clicked element doesn't have the right selector, bail
var mapItem = event.target.closest('.map-item');
if (mapItem === null) {
return;
}
// Don't follow the link
event.preventDefault();
setSelectedMap(mapItem);
}, false);
})();
</script>
</body>
</html>

View File

@ -1,240 +0,0 @@
<!doctype html>
<html class="no-js" lang="">
<head>
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-10196481-11"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'UA-10196481-11');
</script>
<link rel="apple-touch-icon" sizes="57x57" href="static/images/favicons/apple-icon-57x57.png">
<link rel="apple-touch-icon" sizes="60x60" href="static/images/favicons/apple-icon-60x60.png">
<link rel="apple-touch-icon" sizes="72x72" href="static/images/favicons/apple-icon-72x72.png">
<link rel="apple-touch-icon" sizes="76x76" href="static/images/favicons/apple-icon-76x76.png">
<link rel="apple-touch-icon" sizes="114x114" href="static/images/favicons/apple-icon-114x114.png">
<link rel="apple-touch-icon" sizes="120x120" href="static/images/favicons/apple-icon-120x120.png">
<link rel="apple-touch-icon" sizes="144x144" href="static/images/favicons/apple-icon-144x144.png">
<link rel="apple-touch-icon" sizes="152x152" href="static/images/favicons/apple-icon-152x152.png">
<link rel="apple-touch-icon" sizes="180x180" href="static/images/favicons/apple-icon-180x180.png">
<link rel="icon" type="image/png" sizes="192x192" href="static/images/favicons/android-icon-192x192.png">
<link rel="icon" type="image/png" sizes="32x32" href="static/images/favicons/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="96x96" href="static/images/favicons/favicon-96x96.png">
<link rel="icon" type="image/png" sizes="16x16" href="static/images/favicons/favicon-16x16.png">
<link rel="manifest" href="static/images/favicons/manifest.json">
<meta name="msapplication-TileColor" content="#000000">
<meta name="msapplication-TileImage" content="static/images/favicons/ms-icon-144x144.png">
<meta name="theme-color" content="#000000">
<meta charset="utf-8">
<title>Choose map - WorkAdventu.re</title>
<link rel="stylesheet" href="main.css">
<script src="bundle.js"></script>
</head>
<body class="create-map">
<div class="container">
<div class="row">
<div class="col">
<a href="/" class="d-block mt-3 pixel-text">
&lt;&lt; BACK TO HOMEPAGE
</a>
</div>
</div>
</div>
<div class="container-fluid container-lg section pt-5">
<h1 class="text-center pixel-title">CREATE YOUR MAP&nbsp;!</h1>
<div class="row">
<div class="col text-center">
<p>Learn how to create your own map! If you want to go the easy route, you can instead <a href="choose-map.html">PICK A PREBUILT MAP</a>.</p>
</div>
</div>
<div class="row">
<div class="col">
<h2 id="tools-you-will-need" class="pixel-title">Tools you will need</h2>
<p>In order to build your own map for WorkAdventure, you need:</p>
<ul>
<li>the <a href="https://www.mapeditor.org/">Tiled editor</a> software</li>
<li>&quot;tiles&quot; (i.e. images) to create your map (this starter kit provides a good default tileset for offices)</li>
<li>a web-server to serve your map (this starter kit proposes to use Github static pages as a web-server which is both free and performant)</li>
</ul>
<p>WorkAdventure comes with a "map starter kit" that we recommend using to start designing your map quickly.</p>
<h2 id="getting-started" class="pixel-title">Getting started</h2>
<p>Start by <a href="https://github.com/join">creating a GitHub account</a> if you don't already have one.</p>
<p>Then, go to the <a href="https://github.com/thecodingmachine/workadventure-map-starter-kit">Github map starter kit repository page</a>
and click the <strong>&quot;Use this template&quot;</strong> button.</p>
<p class="text-center"><img src="docs/use_this_template.png" alt="" style="width: 70%"></p>
<p>You will be prompted to enter a repository name for your map.</p>
<p class="text-center"><img src="docs/create_repo.png" alt="" style="width: 70%"></p>
<p>Be sure to keep the repository &quot;Public&quot;.</p>
<p>In your newly created repository, click on the <strong>Settings tab</strong> and scroll down to the <strong>GitHub Pages</strong> section.
Then select the <strong>gh-pages</strong> branch. It might already be selected, but please be sure to click on it nonetheless (otherwise
GitHub will not enable GitHub pages).</p>
<p class="text-center"><img src="docs/github_pages.png" alt="" style="width: 70%"></p>
<p>Wait a few minutes... Github will deploy a new website with the content of the repository.
The address of the website is visible in the &quot;GitHub Pages&quot; section.</p>
<p class="text-center"><img src="docs/website_address.png" alt="" style="width: 70%"></p>
<p>Click on the link. You should be redirected directly to WorkAdventure, on your map!</p>
<h2 id="customizing-your-map" class="pixel-title">Customizing your map</h2>
<p>Your map is now up and online, but this is still the demo map from the starter kit. You need to customize it.</p>
<h3 id="cloning-the-map" class="pixel-title">Cloning the map</h3>
<p>Start by cloning the map. If you are used to Git and GitHub, simply clone the map
to your computer using your preferred tool and <a href="#loading-the-map-in-tiled">jump to the next chapter</a>.</p>
<p>If you are new to Git, cloning the map means downloading the map to your computer.
To do this, you will need Git, or a Git compatible tool. Our advice is to use
<a href="https://desktop.github.com/">GitHub Desktop</a>. We recommend you take some time mastering
the notion of pull / commit / push as this will make uploading your maps really easier.</p>
<p>As an (easier) alternative, you can simply use the "Export" button to download the code of the map in a big Zip file.
When you want to upload your work again, you will simply drag'n'drop your files in the GitHub website.</p>
<h3 id="loading-the-map-in-tiled" class="pixel-title">Loading the map in Tiled</h3>
<p>The sample map is in the file <code>map.json</code>.
You can load this file in <a href="https://www.mapeditor.org/">Tiled</a>.</p>
<p>Now, it&#39;s up to you to edit the map and write your own map.</p>
<p>Some resources regarding Tiled:</p>
<ul>
<li><a href="https://doc.mapeditor.org/en/stable/manual/introduction/" target="_blank">Tiled documentation</a></li>
<li><a href="https://www.gamefromscratch.com/post/2015/10/14/Tiled-Map-Editor-Tutorial-Series.aspx" target="_blank">Tiled video tutorials</a></li>
</ul>
<h2 id="about-workadventu-re-maps" class="pixel-title">About WorkAdventure maps</h2>
<p>In order to design a map that will be readable by WorkAdventure, you will have to respect some constraints.</p>
<p>In particular, you will need to:</p>
<ul>
<li>set a start position for the players</li>
<li>configure the &quot;floor layer&quot; (so that WorkAdventure can correctly display characters above the floor, but under the ceiling)</li>
<li>eventually, you can place exits that link to other maps</li>
<li>the "Tile Layer Format" must be set to CSV. If you started from the map starter kit as explained above,
you have nothing to do. However, if you start from scratch, please be sure to select "CSV" for the tile layer format
when creating the map. You can change this setting later in the map properties.</li>
</ul>
<h3 id="workadventure-maps-rules" class="pixel-title">WorkAdventure Map Rules</h3>
<p>A few things to notice:</p>
<ul>
<li>your map can have as many layers as you want</li>
<li>your map MUST contain a layer named &quot;floorLayer&quot; of type &quot;objectgroup&quot; that represents the layer on which characters will be drawn.
Every layer above the &quot;floorLayer&quot; will be displayed on top of the characters.</li>
<li>the tilesets in your map MUST be embedded. You cannot refer to an external typeset in a TSX file. Click the &quot;embed tileset&quot; button in the tileset tab to embed tileset data.</li>
<li>your map MUST be exported in JSON format. You need to use a recent version of Tiled to get JSON format export (1.3+)</li>
<li>WorkAdventure doesn't support object layers and will ignore them</li>
<li>If you are starting from a blank map, your map MUST be orthogonal and tiles size should be 32x32. </li>
</ul>
<p class="text-center"><img src="docs/tiled_screenshot_1.png" alt="" style="width: 70%"></p>
<h3 id="building-walls" class="pixel-title">Building walls and "collidable" areas</h3>
<p>By default, the characters can traverse any tiles. If you want to prevent your characeter from
going through a tile (like a wall or a desktop), you must make this tile "collidable". You can do
this by settings the <code>collides</code> property on a given tile.</p>
<p>To make a tile "collidable", you should:</p>
<ol>
<li>select the relevant tileset and switch to "edit" mode:<br/>
<img src="docs/collides-1.png" alt="">
</li>
<li>right click on a tile of the tileset to select it:<br/>
<img src="docs/collides-2.png" alt="">
</li>
<li>on the left pane in the custom properties section, right click and select "Add properties":<br/>
<img src="docs/collides-3.png" alt=""><br/>
Please add a <code>collides</code> property. The type of the property must be <strong>bool</strong>.
</li>
<li>finally, check the checkbox for the <code>collides</code> property:<br/>
<img src="docs/collides-4.png" alt="">
</li>
</ol>
<p>Repeat for every tile that should be "collidable".</p>
<h3 id="defining-a-default-entry-point" class="pixel-title">Defining a default entry point</h3>
<p>In order to define a default start position, you MUST create a layer named &quot;start&quot; on your map.
This layer MUST contain at least one tile. The players will start on the tile of this layer.
If the layer contains many tiles selected, the players will start randomly on one of those tiles.</p>
<p class="text-center"><img src="docs/start_layer.png" alt="Start layer screenshot" style="width: 70%"></p>
<p>In the screenshot above, the start layer is made of the 2 white tiles. These tiles are
not visible to the end user because they are hidden below the "bottom" layer that displays the floor
of the map.</p>
<h3 id="defining-exits" class="pixel-title">Defining exits</h3>
<p>In order to place an exit on your scene that leads to another scene:</p>
<ul>
<li>You must create a specific layer. When a character reaches ANY tile of that layer, it will exit the scene.</li>
<li>In layer properties, you MUST add "exitUrl&quot; property. It represents the URL of the next scene. You can put relative or absolute URLs.</li>
<li>If you want to have multiple exits, you can create many layers with name &quot;exit&quot;. Each layer has a different key <code>exitUrl</code> and have tiles that represent exits to another scene.</li>
</ul>
<p>
<strong>Understanding map URLs in WorkAdventure:</strong><br/>
There are 2 kinds of URLs in WorkAdventure:
<ul>
<li>Public URLs are in the form https://play.workadventu.re/_/[instance]/[server]/[path to map]</li>
<li>Private URLs (used in paid accounts) are in the form https://play.workadventu.re/@/[organization]/[world]/[map]</li>
</ul>
Assuming your JSON map is hosted at "https://example.com/my/map.json", then you can browse your map at "https://play.workadventu.re/_/global/example.com/my/map.json".
Here, "global" is a name of an "instance" of your map. You can put anything instead of "global" here. People on the same instance of the map can see each others.
If 2 users use 2 different instances, they are on the same map, but in 2 parallel universes. They cannot see each other.
</p>
<p class="text-center"><img src="docs/exit_layer_map.png" alt="" style="width: 90%"></p>
<p>Note: in older releases of WorkAdventure, you could link to a map file directly using properties "exitSceneUrl&quot; and &quot;exitInstance&quot;. Those properties are now deprecated. Use "exitUrl" instead.</p>
<h3 id="defining-several-entry-points" class="pixel-title">Defining several entry points</h3>
<p>Often your map will have several exits, and therefore, several entry points. For instance, if there
is an exit by a door that leads to the garden map, when you come back from the garden you expect to
come back by the same door. Therefore, a map can have several entry points.
Those entry points are &quot;named&quot; (they have a name).</p>
<p>In order to create a named entry point:</p>
<ul>
<li>You must create a specific layer. When a character enters the map by this entry point, it will enter the map randomly on ANY tile of that layer.</li>
<li>In layer properties, you MUST add a boolean &quot;startLayer&quot; property. It should be set to true.</li>
<li>The name of the entry point is the name of the layer</li>
<li>To enter via this entry point, simply add a hash with the entry point name to the URL (&quot;#[<em>startLayerName</em>]&quot;). For instance: &quot;https://workadventu.re/_/global/mymap.com/path/map.json#my-entry-point&quot;.</li>
<li>You can of course use the &quot;#&quot; notation in an exit scene URL (so an exit scene URL will point to a given entry scene URL)</li>
</ul>
<h3 id="opening-a-website-when-walking-on-the-map" class="pixel-title">Opening a website when walking on the map</h3>
<p>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)</p>
<p>In order to create a zone that opens websites:</p>
<ul>
<li>You must create a specific layer.</li>
<li>In layer properties, you MUST add a &quot;openWebsite&quot; property (of type "string"). The value of the property is the URL of the website to open (the URL must start with "https://")</li>
</ul>
<h3 id="opening-a-jitsi-meet-when-walking-on-the-map" class="pixel-title">Opening a Jitsi meet when walking on the map</h3>
<p>On your map, you can define special zones (meeting rooms) that will trigger the opening of a Jitsi meet. When a player will pass over these zones, a Jitsi meet will open
(as an iframe on the right side of the screen)</p>
<p>In order to create Jitsi meet zones:</p>
<ul>
<li>You must create a specific layer.</li>
<li>In layer properties, you MUST add a &quot;jitsiRoom&quot; property (of type "string"). The value of the property is the name of the room in Jitsi. Note: the name of the room will be prepended with the name of the instance of the map (so that different instances of the map have different rooms)</li>
</ul>
<h3 id="making-a-silent-zone" class="pixel-title">Making a "silent" zone</h3>
<p>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.</p>
<p>In order to create a silent zone:</p>
<ul>
<li>You must create a specific layer.</li>
<li>In layer properties, you MUST add a boolean &quot;silent&quot; property. If the silent property is checked, the users are entering the silent zone when they walk on any tile of the layer.</li>
</ul>
<h3 id="pushing-the-map" class="pixel-title">Pushing the map</h3>
<p>When your changes are ready, you need to &quot;commit&quot; and &quot;push&quot; (i.e. "upload") the changes back to GitHub.
Just wait a few minutes, and your map will be propagated automatically to the GitHub pages web-server.</p>
<h3 id="need-some-help" class="pixel-title">Need some help?</h3>
<p>WorkAdventure is a young project and much needs to be said / written regarding map editing.</p>
<p>If you are facing any troubles, do not hesitate to open an "issue" in the
<a href="https://github.com/thecodingmachine/workadventure/issues" target="_blank">GitHub WorkAdventure account</a>.</p>
</div>
</div>
</div>
</body>
</html>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 290 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 144 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 129 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 158 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 401 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

View File

@ -1,482 +0,0 @@
<!doctype html>
<html class="no-js" lang="">
<head>
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-10196481-11"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'UA-10196481-11');
if (window.location.host.endsWith("localhost")){
window['ga-disable-UA-10196481-11'] = true;
}
</script>
<link rel="apple-touch-icon" sizes="57x57" href="static/images/favicons/apple-icon-57x57.png">
<link rel="apple-touch-icon" sizes="60x60" href="static/images/favicons/apple-icon-60x60.png">
<link rel="apple-touch-icon" sizes="72x72" href="static/images/favicons/apple-icon-72x72.png">
<link rel="apple-touch-icon" sizes="76x76" href="static/images/favicons/apple-icon-76x76.png">
<link rel="apple-touch-icon" sizes="114x114" href="static/images/favicons/apple-icon-114x114.png">
<link rel="apple-touch-icon" sizes="120x120" href="static/images/favicons/apple-icon-120x120.png">
<link rel="apple-touch-icon" sizes="144x144" href="static/images/favicons/apple-icon-144x144.png">
<link rel="apple-touch-icon" sizes="152x152" href="static/images/favicons/apple-icon-152x152.png">
<link rel="apple-touch-icon" sizes="180x180" href="static/images/favicons/apple-icon-180x180.png">
<link rel="icon" type="image/png" sizes="192x192" href="static/images/favicons/android-icon-192x192.png">
<link rel="icon" type="image/png" sizes="32x32" href="static/images/favicons/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="96x96" href="static/images/favicons/favicon-96x96.png">
<link rel="icon" type="image/png" sizes="16x16" href="static/images/favicons/favicon-16x16.png">
<link rel="manifest" href="static/images/favicons/manifest.json">
<meta name="msapplication-TileColor" content="#000000">
<meta name="msapplication-TileImage" content="static/images/favicons/ms-icon-144x144.png">
<meta name="theme-color" content="#000000">
<title>WorkAdventu.re</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<meta name="title" content="Workadventure" />
<meta name="description" content="You are impatient to discover this new world? Click on 'Work online' and meet new people or share this adventure with your colleagues and friends by clicking on 'Work in private'" />
<!-- Open Graph / Facebook -->
<meta property="og:url" content="https://workadventu.re/" />
<meta property="og:type" content="website" />
<meta property="og:title" content="Workadventure" />
<meta property="og:description" content="You are impatient to discover this new world? Click on 'Work online' and meet new people or share this adventure with your colleagues and friends by clicking on 'Work in private'" />
<meta property="og:image" content="https://workadventu.re/static/images/meta-tags-image.jpg" />
<meta property="og:image:type" content="image/jpeg" />
<meta property="og:image:alt" content="workadventure" />
<!-- Twitter -->
<meta name="twitter:site" content="@coding_machine">
<meta name="twitter:url" content="https://workadventu.re/" />
<meta name="twitter:title" content="Workadventure" />
<meta name="twitter:description" content="You are impatient to discover this new world? Click on 'Work online' and meet new people or share this adventure with your colleagues and friends by clicking on 'Work in private'" />
<meta name="twitter:image" content="https://workadventu.re/static/images/meta-tags-image.jpg" />
<link rel="stylesheet" href="main.css">
<script src="bundle.js"></script>
<script>
function startDemo() {
let playUrl = window.location.protocol + "//play."+window.location.host+'/_/global/gparant.github.io/tcm-client/Demo/demo-v1.json';
window.open(playUrl, '_blank');
}
function startGame() {
let playUrl = window.location.protocol + "//play."+window.location.host+'/_/global/npeguin.github.io/office-map/map.json';
window.open(playUrl, '_blank');
}
function shareFB() {
window.open('https://www.facebook.com/sharer/sharer.php?u=https://workadventu.re/', '_blank', 'width=500,height=500');
}
function shareLI() {
window.open('https://www.linkedin.com/shareArticle?mini=true&url=https://workadventu.re/&title=&summary=Your workplace but better!&source=TheCodingMachine', '_blank', 'width=500,height=500');
}
function shareTW() {
window.open('https://twitter.com/share?text=Your workplace but better!&url=https://workadventu.re/&hashtags=thecodingmachine', '_blank', 'width=500,height=500');
}
</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.3.1/gsap.min.js" integrity="sha256-MVs0yHYDQBhIRZrNeWB1YaNMrGbFwowIEPIl3um5MZE=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.3.1/ScrollTrigger.min.js" integrity="sha256-FEFPM9cOclVyq+lIim2xnU/dAgrALF+g4M8kYm2tbX0=" crossorigin="anonymous"></script>
</head>
<body>
<header>
<div class="container-lg section">
<div class="over-image">
<div class="row">
<div class="col-10 col-md-6">
<div class="logo">
<img src="static/images/logo.png">
</div>
</div>
<div class="col-2 col-md-6">
<div class="social-links">
<a href="https://www.facebook.com/workadventurebytcm">
<img class="social-image" src="static/images/facebook_bw.png" />
</a>
<a href="https://www.linkedin.com/company/workadventure-by-tcm">
<img class="social-image" src="static/images/linkedin_bw.png" />
</a>
<a href="https://twitter.com/Workadventure_">
<img class="social-image" src="static/images/twitter_bw.png" />
</a>
</div>
</div>
</div>
<div class="title title-main text-center">
<h1>Meet your teammates</h1>
<h3>
WorkAdventure preserves your social interaction while COVID is still out there.
</h3>
<h3>
Stay connected with your teamworkers, by creating your own online workspace to work remotely.
</h3>
<h3>
Stay connected with your clients by providing a dedicated digital place to organize meetings, workshops.
</h3>
<h3>
Engage with your future collaborators by organizing online events.
</h3>
</div>
<div class="row buttons-row justify-content-md-center pt-5">
<div class="col col-lg-3">
<a class="custom-link start" href="/choose-map.html" title="WORK IN PRIVATE">
PRIVATE MAPS
</a>
</div>
<div class="col col-lg-3">
<a class="custom-link play" target="_BLANK" onclick="startGame()" title="WORK ONLINE">
TRY IT NOW!
</a>
</div>
</div>
<!--<div class="row buttons-row justify-content-md-center pt-5"
style="min-height: 80px;">
<div class="col col-lg-3">
<a class="custom-link start"
href="/choose-map.html">
CREATE YOUR MAP
</a>
</div>
<div class="col col-lg-3">
<a class="custom-link play" onclick="startDemo()">
TUTORIAL
</a>
</div>
</div>
<div style="min-height: 100px" class="row buttons-row justify-content-md-center pt-5">
<div class="col col-lg-3">
<a class="custom-link contribute" onclick="startGame()">
DEMO
</a>
</div>
</div>-->
</div>
</div>
<div class="clouds clouds-2">
<div class="cloud"></div>
</div>
<div class="clouds">
<div class="cloud"></div>
</div>
</header>
<section class="story-wrapper">
<div class="clouds clouds-2">
<div class="cloud"></div>
</div>
<div class="clouds">
<div class="cloud"></div>
</div>
<div class="sky"></div>
<div class="mountains"></div>
<img src="static/images/story/story-map-bg.png" height="672" class="story-1" />
<img src="static/images/story/character-static.png" class="character" id="leymah" />
<div class="birds">
<img class="bird bird-1" src="static/images/story/bird.gif" />
<img class="bird bird-2" src="static/images/story/bird.gif" />
<img class="bird bird-3" src="static/images/story/bird.gif" />
<img class="bird bird-4" src="static/images/story/bird.gif" />
<img class="bird bird-5" src="static/images/story/bird.gif" />
<img class="bird bird-6" src="static/images/story/bird.gif" />
<img class="bird bird-7" src="static/images/story/bird.gif" />
<img class="bird bird-8" src="static/images/story/bird.gif" />
</div>
<div class="bubble bubble-1" style="width: 320px;height: 209px;">
<div>
<p>WorkAdventure is a video-conference application that lets people hold multiple parallel conversations in a virtual universe. </p>
<p>Walk in, out, speak to anyone just like in real-life!</p>
</div>
</div>
<div class="bubble bubble-thinking" style="width: 269px;height: 211px;">
<div class="demo-gif">
<img src="static/images/story/WA-Demo.gif" />
</div>
</div>
<div class="bubble bubble-3" style="height: 209px;">
<div>
<p>Remote work? Friend party? Use WorkAdventure as you prefer.</p>
<p>Click the button below to come and say hi!</p>
<p class="bubble-action"><span onclick="startDemo()">
<img src="static/images/playicon.png" />
DEMO!
</span></p>
</div>
</div>
<div class="bubble bubble-4 b-revert" style="height: 254px;">
<div>
<p>You can also create a private room with your friends or your team ! </p>
<p class="bubble-legend">To try, press button work in private</p>
<p class="bubble-action">
<a href="/choose-map.html">
<img src="static/images/playicon.png" />
CHOOSE YOU OWN MAP
</a>
<p>
Dont forget to activate your mic and camera, lets play
</p>
</div>
</div>
<!-- PRELOADING (hidden elements) -->
<img src="static/images/story/character-walk-right.gif" style="display:none;" />
</section>
<script>
gsap.to(".title-main", {
//y:-1000,
scale: 0,
opacity: 0,
ease: "none",
scrollTrigger: {
trigger: "header",
start: "top top", // the default values
// end: "bottom top",
scrub: true
},
});
gsap.to(".over-image .clouds", {
y: 100,
ease: "none",
scrollTrigger: {
trigger: "header",
start: "top top", // the default values
// end: "bottom top",
scrub: true
},
});
var maxImageTranslateValue = 3507 - window.innerWidth;
var bubbleNumber = document.getElementsByClassName("bubble").length;
var charWalkToRightImage = "static/images/story/character-walk-right.gif";
var charStaticImage = "static/images/story/character-static.png";
var storyScrollTrigger = gsap.timeline({
scrollTrigger: {
trigger: ".story-wrapper",
pin: true, // pin the trigger element while active
start: "top top", // when the top of the trigger hits the top of the viewport
//end: "+=500", // end after scrolling 500px beyond the start
scrub: 1, // smooth scrubbing, takes 1 second to "catch up" to the scrollbar
}
});
storyScrollTrigger.to(".birds", {
x: (-window.innerWidth -200)
});
storyScrollTrigger.from(".character", {
x: -maxImageTranslateValue * (1/bubbleNumber),
onStart: function() {
document.getElementById("leymah").src = charWalkToRightImage;
},
onComplete: function() {
document.getElementById("leymah").src = charStaticImage;
}
})
.to(".bubble-1, .bubble-thinking", {
opacity: 1,
y: 0
})
.to(".story-1", {
x: -maxImageTranslateValue * (2/bubbleNumber),
onStart: function() {
document.getElementById("leymah").src = charWalkToRightImage;
},
onComplete: function() {
document.getElementById("leymah").src = charStaticImage;
}
})
.to(".bubble-1, .bubble-thinking", {
opacity: 0
})
.to(".bubble-3", {
opacity: 1,
y: 0
})
.to(".story-1", {
x: -maxImageTranslateValue,
onStart: function() {
document.getElementById("leymah").src = "static/images/story/character-walk-right.gif";
},
onComplete: function() {
document.getElementById("leymah").src = "static/images/story/character-static.png";
}
})
.to(".bubble-3", {
opacity: 0
})
.to(".bubble-4", {
opacity: 1,
y: 0
})
.to(".character, .bubble-4", {
x: "53vw",
onStart: function() {
document.getElementById("leymah").src = "static/images/story/character-walk-right.gif";
},
})
.to(".bubble-4", {
opacity: 0
});
</script>
<div class="section bg-white how-to">
<div class="desktop-only text-center d-block d-md-none d-lg-none d-xl-none">
<img src="static/images/desktop.png" width="64" /><br />
Unfortunately, it's not mobile friendly yet. But we are happy to invite you to try it on your desktop. Enjoy!
</div>
<div class="container-fluid container-lg">
<div class="row justify-content-md-center">
<div class="col-12 col-md-12 text-center">
<h3>HOW IT WORKS</h3>
</div>
<div class="col-12 col-md-4 text-center my-3 my-md-0">
<div class="image-item">
<h2>CHOOSE YOUR WORKSPACE</h2>
<div class="step-image"><img src="static/images/maps/office.png"></div>
</div>
</div>
<div class="col-12 col-md-4 text-center my-3 my-md-0">
<div class="image-item">
<h2>SELECT YOUR WOKA</h2>
<div class="step-image"><img src="static/images/choose_character.png"></div>
</div>
</div>
<div class="col-12 col-md-4 text-center my-3 my-md-0">
<div class="image-item">
<h2>LET'S GO TO YOUR OFFICE</h2>
<div class="step-image"><img src="static/images/interact.png"></div>
</div>
</div>
</div>
<div class="row mt-5">
<div class="col-lg-9 text-right">
<p class="py-3 font-weight-bold" style="font-size: 1.25rem">
Want to try WorkAdventure with your team mates or friends?
</p>
</div>
<div class="col-lg-3">
<a class="custom-link relative" href="/choose-map.html" title="WORK IN PRIVATE">
GET STARTED!
</a>
</div>
</div>
<div class="social-links text-center pt-2 pb-4">
<span class="share-title">... and if you liked WorkAdventure, share your experience!</span>
<a onclick="shareFB()">
<img class="social-image" src="static/images/facebook.png" />
</a>
<a onclick="shareLI()">
<img class="social-image" src="static/images/linkedin.png" />
</a>
<a onclick="shareTW()">
<img class="social-image" src="static/images/twitter.png" />
</a>
</div>
</div>
</div>
<div class="section bg-white text-center pt-5">
<div class="container-fluid container-lg">
<div class="row justify-content-md-center">
<div class="col-12 col-md-12 text-center">
<h3>WORKADVENTURE'S USE CASES</h3>
</div>
<p>
Workadventure is an intuitive and fun solution to professional issues:
</p>
<ul class="text-left">
<li>
Work in a remote way within a team,
</li>
<li>
Create an event (even a big one),
</li>
<li>
Stay connected and increase social interactions...
</li>
</ul>
<p>
Feel free to contact us if you need a specific map, need a dedicated admin console or any
support (for instance a large number of connexions) : <a href="mailto:workadventure@thecodingmachine.com" target="_blank">workadventure@thecodingmachine.com</a>
</p>
</div>
</div>
</div>
<div class="section bg-white">
<div class="container-fluid container-lg">
<div class="col-12 credits">
<h2>CURRENT FEATURES</h2>
<!-- <h3>We have already thought of new features:</h3>-->
<p>Instant interaction with up to 4 people</p>
<p>Share screen with others</p>
<p>Organize a workshop and meeting with up to 60 people</p>
<p>Design your own digital space</p>
<p>... and infinite possibilities</p>
<h3>You need more? Do not hesitate to tell us!</h3>
<div class="row justify-content-md-center pt-5" style="margin-top: 65px;">
<div class="col col-lg-3">
<a class="custom-link contribute" target="_BLANK" href="https://docs.google.com/forms/d/e/1FAIpQLSdxvajEyqsn4X0ai0SoDAcdsa_JQPIfiP2Tp9PDzkfZA54v9Q/viewform" title="FEEDBACK">
FEEDBACK
</a>
</div>
</div>
</div>
</div>
</div>
<div class="bg-gray section used-by">
<div class="container-fluid container-lg">
<h2 class="text-center pb-4">THEY MIGHT APPROVE</h2>
<div class="row justify-content-md-center align-items-center">
<div class="col col-md-auto">
<img src="static/images/atari.png">
</div>
<div class="col col-md-auto">
<img src="static/images/super-nintendo.png">
</div>
<div class="col col-md-auto">
<img src="static/images/amstrad.png">
</div>
<div class="col col-md-auto">
<img src="static/images/sinclair-2.png">
</div>
</div>
</div>
</div>
<div class="container-fluid container-lg section quotes">
<h2 class="text-center">Why we love WorkAdventure</h2>
<div class="row justify-content-center">
<div class="col-12 col-md-9">
<div class="quote-item">
<p class="quote">&laquo;&nbsp;Right on time! I feel like less alone in my home office.&nbsp;&raquo;</p>
<p class="author">Julie</p>
</div>
<div class="quote-item">
<p class="quote">&laquo;&nbsp;I love running into the hallway and check out where are my teammates!&nbsp;&raquo;</p>
<p class="author">Sophie</p>
</div>
<div class="quote-item">
<p class="quote">&laquo;&nbsp;Wow! More intimate than a Meet conference.&nbsp;&raquo;</p>
<p class="author">Greg</p>
</div>
</div>
</div>
</div>
<div class="bg-white footer">
<div class="container-fluid container-lg">
<div class="row">
<div class="col-12 col-md-6 my-3 my-md-0">
<a href="https://www.thecodingmachine.com/" target="_blank"><img src="static/images/Logo TCM.png"></a>
</div>
<div class="col-12 col-md-6 my-3 my-md-0 floppy">
<img src="static/images/floppy.png" />
<div>Soon available on floppy !<br/><span>otherwise, available on <a href="https://github.com/thecodingmachine/workadventure" target="_BLANK">GitHub</a></span></div>
</div>
</div>
<div class="container-fluid container-lg">
<div class="row text-center">
<div style="width: 100%;color:#afafaf;margin-top: 25px;">TheCodingMachine - All Rights Reserved</div>
</div>
</div>
</div>
</div>
</body>
</html>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 158 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 231 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 135 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 639 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

View File

@ -1,2 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<browserconfig><msapplication><tile><square70x70logo src="/ms-icon-70x70.png"/><square150x150logo src="/ms-icon-150x150.png"/><square310x310logo src="/ms-icon-310x310.png"/><TileColor>#ffffff</TileColor></tile></msapplication></browserconfig>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -1,41 +0,0 @@
{
"name": "App",
"icons": [
{
"src": "\/android-icon-36x36.png",
"sizes": "36x36",
"type": "image\/png",
"density": "0.75"
},
{
"src": "\/android-icon-48x48.png",
"sizes": "48x48",
"type": "image\/png",
"density": "1.0"
},
{
"src": "\/android-icon-72x72.png",
"sizes": "72x72",
"type": "image\/png",
"density": "1.5"
},
{
"src": "\/android-icon-96x96.png",
"sizes": "96x96",
"type": "image\/png",
"density": "2.0"
},
{
"src": "\/android-icon-144x144.png",
"sizes": "144x144",
"type": "image\/png",
"density": "3.0"
},
{
"src": "\/android-icon-192x192.png",
"sizes": "192x192",
"type": "image\/png",
"density": "4.0"
}
]
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.9 KiB

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