Merge remote-tracking branch 'upstream/develop' into develop

This commit is contained in:
Thomas Basler 2021-01-20 19:41:05 +01:00
commit cfaf7aaf60
182 changed files with 701 additions and 9819 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>

173
front/dist/index.html.tmpl vendored Normal file
View File

@ -0,0 +1,173 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
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">
<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">
<base href="/">
<link rel="stylesheet" href="/resources/style/style.css">
<title>WorkAdventure</title>
</head>
<body id="body" style="margin: 0; background-color: #000">
<div class="main-container" id="main-container">
<!-- Create the editor container -->
<div id="game" class="game">
<div id="game-overlay" class="game-overlay">
<div id="main-section" class="main-section">
</div>
<aside id="sidebar" class="sidebar">
</aside>
<div id="chat-mode" class="chat-mode three-col" style="display: none;">
</div>
<div id="activeCam" class="activeCam">
<div id="div-myCamVideo" class="video-container">
<video id="myCamVideo" autoplay muted></video>
</div>
</div>
<div class="btn-cam-action">
<div id="btn-micro" class="btn-micro">
<img id="microphone" src="resources/logos/microphone.svg">
<img id="microphone-close" src="resources/logos/microphone-close.svg">
</div>
<div id="btn-video" class="btn-video">
<img id="cinema" src="resources/logos/cinema.svg">
<img id="cinema-close" src="resources/logos/cinema-close.svg">
</div>
<div id="btn-monitor" class="btn-monitor">
<img id="monitor" src="resources/logos/monitor.svg">
<img id="monitor-close" src="resources/logos/monitor-close.svg">
</div>
</div>
</div>
</div>
<div id="cowebsite" class="cowebsite hidden">
<button class="close-btn" id="cowebsite-close">
<img src="resources/logos/close.svg"/>
</button>
</div>
<div class="audio-playing">
<img src="/resources/logos/megaphone.svg"/>
</div>
</div>
<div id="audioplayerctrl" class="hidden">
<div class="audioplayer">
<button type="button" id="audioplayer_mute" class="fa fa-volump-up">
<svg
width="1em"
height="1em"
viewBox="0 0 16 16"
class="bi bi-volume-up"
fill="white"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
d="M6.717 3.55A.5.5 0 0 1 7 4v8a.5.5 0 0 1-.812.39L3.825 10.5H1.5A.5.5 0 0 1 1 10V6a.5.5 0 0 1 .5-.5h2.325l2.363-1.89a.5.5 0 0 1 .529-.06zM6 5.04L4.312 6.39A.5.5 0 0 1 4 6.5H2v3h2a.5.5 0 0 1 .312.11L6 10.96V5.04z"
/>
<g id="audioplayer_volume_icon_playing">
<path
d="M11.536 14.01A8.473 8.473 0 0 0 14.026 8a8.473 8.473 0 0 0-2.49-6.01l-.708.707A7.476 7.476 0 0 1 13.025 8c0 2.071-.84 3.946-2.197 5.303l.708.707z"
/>
<path
d="M10.121 12.596A6.48 6.48 0 0 0 12.025 8a6.48 6.48 0 0 0-1.904-4.596l-.707.707A5.483 5.483 0 0 1 11.025 8a5.483 5.483 0 0 1-1.61 3.89l.706.706z"
/>
<path
d="M8.707 11.182A4.486 4.486 0 0 0 10.025 8a4.486 4.486 0 0 0-1.318-3.182L8 5.525A3.489 3.489 0 0 1 9.025 8 3.49 3.49 0 0 1 8 10.475l.707.707z"
/>
</g>
</svg>
</button>
<div class="audioplayer">
<input
type="range"
id="audioplayer_volume"
min="0"
max="1"
step="0.05"
value="1"
/>
</div>
</div>
<div class="audioplayer">
<label
id="label-audioplayer_decrease_while_talking"
for="audiooplayer_decrease_while_talking"
title="decrease background volume by 50% when entering conversations"
>
autoreduce
<input type="checkbox" id="audioplayer_decrease_while_talking" checked />
</label>
<div id="audioplayer" style="visibility: hidden"></div>
</div>
</div>
<div class="audio-playing">
<img src="/resources/logos/megaphone.svg" />
</div>
</div>
<!--
<div id="webRtc" class="webrtc">
<div id="activeCam" class="activeCam">
<div id="div-myCamVideo" class="video-container">
<video id="myCamVideo" autoplay muted></video>
</div>
</div>
<div class="btn-cam-action">
<div class="btn-micro">
<img id="microphone" src="resources/logos/microphone.svg">
<img id="microphone-close" src="resources/logos/microphone-close.svg">
</div>
<div class="btn-video">
<img id="cinema" src="resources/logos/cinema.svg">
<img id="cinema-close" src="resources/logos/cinema-close.svg">
</div>
<div class="btn-monitor">
<img id="monitor" src="resources/logos/monitor.svg">
<img id="monitor-close" src="resources/logos/monitor-close.svg">
</div>
</div>
</div>
-->
<div id="activeScreenSharing" class="active-screen-sharing active">
</div>
<div id="webRtcSetup" class="webrtcsetup">
<img id="webRtcSetupNoVideo" class="background-img" src="resources/logos/cinema-close.svg">
<video id="myCamVideoSetup" autoplay muted></video>
</div>
<audio id="audio-webrtc-in">
<source src="/resources/objects/webrtc-in.mp3" type="audio/mp3">
</audio>
<audio id="audio-webrtc-out">
<source src="/resources/objects/webrtc-out.mp3" type="audio/mp3">
</audio>
<audio id="report-message">
<source src="/resources/objects/report-message.mp3" type="audio/mp3">
</audio>
</body>
</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

@ -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');

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

@ -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">

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

@ -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,6 +50,11 @@ class JWTTokenManager {
if (ADMIN_API_URL) {
//verify user in admin
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);
}).catch((err) => {
@ -60,6 +65,9 @@ class JWTTokenManager {
}
reject(err);
});
}).catch((err) => {
reject(err);
});
} else {
resolve(tokenInterface.userUuid);
}
@ -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 emitBan(userUuid: string, message: string, type: string): void {
const client = this.searchClientByUuid(userUuid);
if(!client){
throw Error('client not found');
}
public async emitBan(userUuid: string, message: string, roomId: string): Promise<void> {
const backConnection = await apiClientRepository.getClient(roomId);
const banUserMessage = new BanUserMessage();
banUserMessage.setMessage(message);
banUserMessage.setType(type);
const pusherToBackMessage = new PusherToBackMessage();
pusherToBackMessage.setBanusermessage(banUserMessage);
client.backConnection.write(pusherToBackMessage);
const banMessage = new BanMessage();
banMessage.setRecipientuuid(userUuid);
banMessage.setRoomid(roomId);
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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

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