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

This commit is contained in:
Anton Bracke 2022-03-03 14:43:33 +01:00
commit 4535a8ae96
No known key found for this signature in database
GPG Key ID: B1222603899C6B25
36 changed files with 271 additions and 147 deletions

View File

@ -28,7 +28,7 @@ You can use [GitHub issue tracker](https://github.com/thecodingmachine/workadven
- File bug reports - File bug reports
- Ask for feature requests - Ask for feature requests
If you have more general questions, a good place to ask is [our Discord server](https://discord.gg/YGtngdh9gt). If you have more general questions, a good place to ask is [our Discord server](https://discord.gg/G6Xh9ZM9aR).
Finally, you can come and talk to the WorkAdventure core team... on WorkAdventure, of course! [Our offices are here](https://play.staging.workadventu.re/@/tcm/workadventure/wa-village). Finally, you can come and talk to the WorkAdventure core team... on WorkAdventure, of course! [Our offices are here](https://play.staging.workadventu.re/@/tcm/workadventure/wa-village).
@ -41,7 +41,7 @@ Please ask first before embarking on any significant pull request (e.g. implemen
otherwise you risk spending a lot of time working on something that the project's developers might not want to merge otherwise you risk spending a lot of time working on something that the project's developers might not want to merge
into the project. into the project.
You can ask us on [Discord](https://discord.gg/YGtngdh9gt) or in the [GitHub issues](https://github.com/thecodingmachine/workadventure/issues). You can ask us on [Discord](https://discord.gg/G6Xh9ZM9aR) or in the [GitHub issues](https://github.com/thecodingmachine/workadventure/issues).
### Linting your code ### Linting your code

View File

@ -1,4 +1,4 @@
![](https://github.com/thecodingmachine/workadventure/workflows/Continuous%20Integration/badge.svg) [![Discord](https://img.shields.io/discord/821338762134290432?label=Discord)](https://discord.gg/YGtngdh9gt) ![](https://github.com/thecodingmachine/workadventure/workflows/Continuous%20Integration/badge.svg) [![Discord](https://img.shields.io/discord/821338762134290432?label=Discord)](https://discord.gg/G6Xh9ZM9aR)
![WorkAdventure logo](README-LOGO.svg) ![WorkAdventure logo](README-LOGO.svg)
![WorkAdventure office image](README-MAP.png) ![WorkAdventure office image](README-MAP.png)

View File

@ -1,8 +1,10 @@
# protobuf build # protobuf build
FROM node:14.18.2-buster-slim@sha256:20bedf0c09de887379e59a41c04284974f5fb529cf0e13aab613473ce298da3d as builder FROM node:14.18.2-buster-slim@sha256:20bedf0c09de887379e59a41c04284974f5fb529cf0e13aab613473ce298da3d as builder
WORKDIR /usr/src WORKDIR /usr/src
COPY messages/yarn.lock messages/package.json ./
RUN yarn install
COPY messages . COPY messages .
RUN yarn install && yarn proto RUN yarn proto
# typescript build # typescript build
FROM node:14.18.2-buster-slim@sha256:20bedf0c09de887379e59a41c04284974f5fb529cf0e13aab613473ce298da3d as builder2 FROM node:14.18.2-buster-slim@sha256:20bedf0c09de887379e59a41c04284974f5fb529cf0e13aab613473ce298da3d as builder2
@ -18,9 +20,9 @@ RUN yarn run tsc
FROM node:14.18.2-buster-slim@sha256:20bedf0c09de887379e59a41c04284974f5fb529cf0e13aab613473ce298da3d FROM node:14.18.2-buster-slim@sha256:20bedf0c09de887379e59a41c04284974f5fb529cf0e13aab613473ce298da3d
WORKDIR /usr/src WORKDIR /usr/src
COPY back/yarn.lock back/package.json ./ COPY back/yarn.lock back/package.json ./
COPY --from=builder2 /usr/src/dist /usr/src/dist
ENV NODE_ENV=production ENV NODE_ENV=production
RUN yarn install --production RUN yarn install --production
COPY --from=builder2 /usr/src/dist /usr/src/dist
USER node USER node
CMD ["yarn", "run", "runprod"] CMD ["yarn", "run", "runprod"]

View File

@ -9,10 +9,7 @@ services:
build: build:
context: ./ context: ./
dockerfile: front/Dockerfile dockerfile: front/Dockerfile
environment: command: /bin/sh -c "/templater.sh && envsubst < /usr/share/nginx/html/env-config.template.js > /usr/share/nginx/html/env-config.js && exec nginx -g 'daemon off;'"
STARTUP_COMMAND_1: 'envsubst < dist/env-config.template.js > dist/env-config.js'
STARTUP_COMMAND_2: ''
command: apache2-foreground
volumes: [] volumes: []
labels: labels:
- "traefik.enable=true" - "traefik.enable=true"
@ -29,9 +26,6 @@ services:
build: build:
context: ./ context: ./
dockerfile: pusher/Dockerfile dockerfile: pusher/Dockerfile
environment:
STARTUP_COMMAND_1: ''
STARTUP_COMMAND_2: ''
command: yarn run runprod command: yarn run runprod
volumes: [] volumes: []
@ -40,8 +34,5 @@ services:
build: build:
context: ./ context: ./
dockerfile: back/Dockerfile dockerfile: back/Dockerfile
environment:
STARTUP_COMMAND_1: ''
STARTUP_COMMAND_2: ''
command: yarn run runprod command: yarn run runprod
volumes: [] volumes: []

View File

@ -76,6 +76,9 @@ services:
OPID_CLIENT_ISSUER: $OPID_CLIENT_ISSUER OPID_CLIENT_ISSUER: $OPID_CLIENT_ISSUER
OPID_CLIENT_REDIRECT_URL: $OPID_CLIENT_REDIRECT_URL OPID_CLIENT_REDIRECT_URL: $OPID_CLIENT_REDIRECT_URL
OPID_PROFILE_SCREEN_PROVIDER: $OPID_PROFILE_SCREEN_PROVIDER OPID_PROFILE_SCREEN_PROVIDER: $OPID_PROFILE_SCREEN_PROVIDER
OPID_SCOPE: $OPID_SCOPE
OPID_USERNAME_CLAIM: $OPID_USERNAME_CLAIM
OPID_LOCALE_CLAIM: $OPID_LOCALE_CLAIM
DISABLE_ANONYMOUS: $DISABLE_ANONYMOUS DISABLE_ANONYMOUS: $DISABLE_ANONYMOUS
volumes: volumes:
- ./pusher:/usr/src/app - ./pusher:/usr/src/app

View File

@ -85,6 +85,9 @@ services:
OPID_CLIENT_ISSUER: $OPID_CLIENT_ISSUER OPID_CLIENT_ISSUER: $OPID_CLIENT_ISSUER
OPID_CLIENT_REDIRECT_URL: $OPID_CLIENT_REDIRECT_URL OPID_CLIENT_REDIRECT_URL: $OPID_CLIENT_REDIRECT_URL
OPID_PROFILE_SCREEN_PROVIDER: $OPID_PROFILE_SCREEN_PROVIDER OPID_PROFILE_SCREEN_PROVIDER: $OPID_PROFILE_SCREEN_PROVIDER
OPID_SCOPE: $OPID_SCOPE
OPID_USERNAME_CLAIM: $OPID_USERNAME_CLAIM
OPID_LOCALE_CLAIM: $OPID_LOCALE_CLAIM
DISABLE_ANONYMOUS: $DISABLE_ANONYMOUS DISABLE_ANONYMOUS: $DISABLE_ANONYMOUS
volumes: volumes:
- ./pusher:/usr/src/app - ./pusher:/usr/src/app

View File

@ -9,9 +9,11 @@ On your map, you can define special zones (meeting rooms) that will trigger the
In order to create Jitsi meet zones: In order to create Jitsi meet zones:
* You must create a specific layer. * You must create a specific layer.
* In layer properties, you MUST add a "`jitsiRoom`" 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 "slugified" and prepended with the name of the instance of the map (so that different instances of the map have different rooms) * In layer properties, you MUST add a "`jitsiRoom`" 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 "slugified" and prepended with the name of the instance of the map (so that different instances of the map have different rooms)
* You may also use "jitsiWidth" property (of type "number" between 0 and 100) to control the width of the iframe containing the meeting room. * You may also use "jitsiWidth" property (of type "number" between 0 and 100) to control the width of the iframe containing the meeting room.
You can have this layer (i.e. your meeting area) to be selectable as the precise location for your meeting using the [Google Calendar integration for Work Adventure](/integrations/google-calendar). To do so, you must set the `meetingRoomLabel` property. You can provide any name that you would like your meeting room to have (as a string).
## Triggering of the "Jitsi meet" action ## Triggering of the "Jitsi meet" action

View File

@ -1,31 +1,28 @@
# protobuf build
FROM node:14.18.2-buster-slim@sha256:20bedf0c09de887379e59a41c04284974f5fb529cf0e13aab613473ce298da3d as builder FROM node:14.18.2-buster-slim@sha256:20bedf0c09de887379e59a41c04284974f5fb529cf0e13aab613473ce298da3d as builder
WORKDIR /usr/src
WORKDIR /usr/src/messages COPY messages/yarn.lock messages/package.json ./
RUN yarn install
COPY messages . COPY messages .
RUN yarn install && yarn ts-proto RUN yarn ts-proto
WORKDIR /usr/src/front # typescript build
FROM node:14.18.2-buster-slim@sha256:20bedf0c09de887379e59a41c04284974f5fb529cf0e13aab613473ce298da3d as builder2
WORKDIR /usr/src
COPY front/yarn.lock front/package.json ./
RUN yarn install
COPY front . COPY front .
COPY --from=builder /usr/src/ts-proto-generated/protos src/Messages/ts-proto-generated
# move messages to front
RUN cp -r ../messages/ts-proto-generated/protos/* src/Messages/ts-proto-generated
RUN sed -i 's/import { Observable } from "rxjs";/import type { Observable } from "rxjs";/g' src/Messages/ts-proto-generated/messages.ts RUN sed -i 's/import { Observable } from "rxjs";/import type { Observable } from "rxjs";/g' src/Messages/ts-proto-generated/messages.ts
RUN cp -r ../messages/JsonMessages/* src/Messages/JsonMessages COPY --from=builder /usr/src/JsonMessages src/Messages/JsonMessages
RUN yarn run typesafe-i18n && yarn run build-iframe-api && yarn build
RUN yarn install && yarn run typesafe-i18n && yarn run build-iframe-api && yarn build # final production image
FROM nginx:1.21.6-alpine
FROM thecodingmachine/nodejs:14-apache COPY front/nginx.conf /etc/nginx/conf.d/default.conf
COPY front/templater.sh /
COPY --from=builder2 /usr/src/dist /usr/share/nginx/html
COPY --from=builder --chown=docker:docker /usr/src/front/dist dist EXPOSE 80
COPY front/templater.sh . CMD ["/bin/sh", "-c", "/templater.sh && envsubst < /usr/share/nginx/html/env-config.template.js > /usr/share/nginx/html/env-config.js && exec nginx -g 'daemon off;'"]
USER root
RUN DEBIAN_FRONTEND=noninteractive apt-get update \
&& apt-get install -y \
gettext-base \
&& rm -rf /var/lib/apt/lists/*
USER docker
ENV STARTUP_COMMAND_0="./templater.sh"
ENV STARTUP_COMMAND_1="envsubst < dist/env-config.template.js > dist/env-config.js"
ENV APACHE_DOCUMENT_ROOT=dist/

51
front/nginx.conf Normal file
View File

@ -0,0 +1,51 @@
server {
listen 80;
listen [::]:80;
server_name localhost;
access_log off;
gzip on;
gzip_comp_level 6;
gzip_min_length 1000;
gzip_proxied any;
gzip_disable "msie6";
gzip_types
application/atom+xml
application/geo+json
application/javascript
application/x-javascript
application/json
application/ld+json
application/manifest+json
application/rdf+xml
application/rss+xml
application/xhtml+xml
application/xml
font/eot
font/otf
font/ttf
image/svg+xml
text/css
text/javascript
text/plain
text/xml;
# serve static assets (that have a cache busting hash) with an efficient cache policy
location /assets {
root /usr/share/nginx/html;
expires 1y;
add_header Cache-Control "public";
}
location / {
root /usr/share/nginx/html;
index index.html;
rewrite ^/register/ /index.html break;
rewrite ^/login /index.html break;
rewrite ^/jwt /index.html break;
}
location ~ ^/[@_]/ {
try_files $uri $uri/ /index.html;
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

View File

@ -1,49 +0,0 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="452.388px" height="452.388px" viewBox="0 0 452.388 452.388" style="enable-background:new 0 0 452.388 452.388;"
xml:space="preserve">
<g>
<g id="Layer_8_38_">
<path d="M441.677,43.643H10.687C4.785,43.643,0,48.427,0,54.329v297.425c0,5.898,4.785,10.676,10.687,10.676h162.069v25.631
c0,0.38,0.074,0.722,0.112,1.089h-23.257c-5.407,0-9.796,4.389-9.796,9.795c0,5.408,4.389,9.801,9.796,9.801h158.506
c5.406,0,9.795-4.389,9.795-9.801c0-5.406-4.389-9.795-9.795-9.795h-23.256c0.032-0.355,0.115-0.709,0.115-1.089V362.43H441.7
c5.898,0,10.688-4.782,10.688-10.676V54.329C452.37,48.427,447.589,43.643,441.677,43.643z M422.089,305.133
c0,5.903-4.784,10.687-10.683,10.687H40.96c-5.898,0-10.684-4.783-10.684-10.687V79.615c0-5.898,4.786-10.684,10.684-10.684
h370.446c5.898,0,10.683,4.785,10.683,10.684V305.133z M303.942,290.648H154.025c0-29.872,17.472-55.661,42.753-67.706
c-15.987-10.501-26.546-28.571-26.546-49.13c0-32.449,26.306-58.755,58.755-58.755c32.448,0,58.753,26.307,58.753,58.755
c0,20.553-10.562,38.629-26.545,49.13C286.475,234.987,303.942,260.781,303.942,290.648z"/>
</g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -21,7 +21,7 @@
onMount(() => { onMount(() => {
icon.src = isJitsi icon.src = isJitsi
? "/resources/logos/meet.svg" ? "/resources/logos/jitsi.png"
: `${ICON_URL}/icon?url=${coWebsite.getUrl().hostname}&size=64..96..256&fallback_icon_color=14304c`; : `${ICON_URL}/icon?url=${coWebsite.getUrl().hostname}&size=64..96..256&fallback_icon_color=14304c`;
icon.alt = coWebsite.getUrl().hostname; icon.alt = coWebsite.getUrl().hostname;
icon.onload = () => { icon.onload = () => {
@ -188,10 +188,16 @@
/> />
</rect> </rect>
</svg> </svg>
<!-- TODO use trigger message property -->
<div class="cowebsite-hover" class:hide={!isJitsi} style="width: max-content;">
<p>Open / Close Jitsi meeting!</p>
</div>
</div> </div>
<style lang="scss"> <style lang="scss">
.cowebsite-thumbnail { .cowebsite-thumbnail {
cursor: url("../../../style/images/cursor_pointer.png"), pointer;
position: relative; position: relative;
padding: 0; padding: 0;
background-color: rgba(#000000, 0.6); background-color: rgba(#000000, 0.6);
@ -236,6 +242,11 @@
height: 40px; height: 40px;
} }
.cowebsite-hover {
top: -4px;
left: 55px;
}
animation: shake 0.35s ease-in-out; animation: shake 0.35s ease-in-out;
} }
@ -315,10 +326,33 @@
} }
&.jitsi { &.jitsi {
filter: invert(100%);
-webkit-filter: invert(100%);
padding: 7px; padding: 7px;
} }
} }
&:hover {
.cowebsite-hover {
display: block;
width: max-content !important;
}
}
.cowebsite-hover {
display: none;
position: absolute;
background-color: rgba(0, 0, 0, 0.6);
top: -40px;
left: -4px;
width: 0 !important;
min-height: 20px;
transition: all 0.2s ease;
overflow: hidden;
color: white;
padding: 4px;
border-radius: 4px;
p {
margin-bottom: 0;
}
}
} }
</style> </style>

View File

@ -24,6 +24,7 @@
left: 2%; left: 2%;
overflow-x: auto; overflow-x: auto;
overflow-y: hidden; overflow-y: hidden;
overflow: visible;
&.vertical { &.vertical {
height: auto !important; height: auto !important;
@ -31,8 +32,6 @@
bottom: auto !important; bottom: auto !important;
left: auto !important; left: auto !important;
position: relative; position: relative;
overflow-x: hidden;
overflow-y: auto;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
padding-top: 4px; padding-top: 4px;

View File

@ -61,15 +61,15 @@
on:dragstart|preventDefault={noDrag} on:dragstart|preventDefault={noDrag}
on:click|preventDefault={showMenu} on:click|preventDefault={showMenu}
/> />
<img
src={logoTalk}
alt={$LL.menu.icon.open.chat()}
class="nes-pointer"
draggable="false"
on:dragstart|preventDefault={noDrag}
on:click|preventDefault={showChat}
/>
{/if} {/if}
<img
src={logoTalk}
alt={$LL.menu.icon.open.chat()}
class="nes-pointer"
draggable="false"
on:dragstart|preventDefault={noDrag}
on:click|preventDefault={showChat}
/>
</main> </main>
<style lang="scss"> <style lang="scss">

View File

@ -4,13 +4,15 @@
import type { Streamable } from "../../Stores/StreamableCollectionStore"; import type { Streamable } from "../../Stores/StreamableCollectionStore";
import type { ScreenSharingPeer } from "../../WebRtc/ScreenSharingPeer"; import type { ScreenSharingPeer } from "../../WebRtc/ScreenSharingPeer";
import { getColorByString, srcObject } from "./utils"; import { getColorByString, srcObject, getTextColorByBackgroundColor } from "./utils";
export let clickable = false; export let clickable = false;
export let peer: ScreenSharingPeer; export let peer: ScreenSharingPeer;
let streamStore = peer.streamStore; let streamStore = peer.streamStore;
let name = peer.userName; let name = peer.userName;
let backGroundColor = getColorByString(peer.userName);
let textColor = getTextColorByBackgroundColor(backGroundColor);
let statusStore = peer.statusStore; let statusStore = peer.statusStore;
let embedScreen: EmbedScreen; let embedScreen: EmbedScreen;
@ -32,7 +34,7 @@
{/if} {/if}
{#if $streamStore !== null} {#if $streamStore !== null}
<i class="container"> <i class="container">
<span style="background-color: {getColorByString(name)};">{name}</span> <span style="background-color: {backGroundColor}; color: {textColor};">{name}</span>
</i> </i>
<!-- svelte-ignore a11y-media-has-caption --> <!-- svelte-ignore a11y-media-has-caption -->
<video <video

View File

@ -5,7 +5,7 @@
import reportImg from "./images/report.svg"; import reportImg from "./images/report.svg";
import blockSignImg from "./images/blockSign.svg"; import blockSignImg from "./images/blockSign.svg";
import { showReportScreenStore } from "../../Stores/ShowReportScreenStore"; import { showReportScreenStore } from "../../Stores/ShowReportScreenStore";
import { getColorByString, srcObject } from "./utils"; import { getColorByString, getTextColorByBackgroundColor, srcObject } from "./utils";
import { highlightedEmbedScreen } from "../../Stores/EmbedScreensStore"; import { highlightedEmbedScreen } from "../../Stores/EmbedScreensStore";
import type { EmbedScreen } from "../../Stores/EmbedScreensStore"; import type { EmbedScreen } from "../../Stores/EmbedScreensStore";
import type { Streamable } from "../../Stores/StreamableCollectionStore"; import type { Streamable } from "../../Stores/StreamableCollectionStore";
@ -20,6 +20,8 @@
let streamStore = peer.streamStore; let streamStore = peer.streamStore;
let volumeStore = peer.volumeStore; let volumeStore = peer.volumeStore;
let name = peer.userName; let name = peer.userName;
let backGroundColor = getColorByString(peer.userName);
let textColor = getTextColorByBackgroundColor(backGroundColor);
let statusStore = peer.statusStore; let statusStore = peer.statusStore;
let constraintStore = peer.constraintsStore; let constraintStore = peer.constraintsStore;
@ -65,7 +67,7 @@
{/if} {/if}
<!-- {#if !$constraintStore || $constraintStore.video === false} --> <!-- {#if !$constraintStore || $constraintStore.video === false} -->
<i class="container"> <i class="container">
<span style="background-color: {getColorByString(name)};">{name}</span> <span style="background-color: {backGroundColor}; color: {textColor};">{name}</span>
</i> </i>
<div class="woka-icon {($constraintStore && $constraintStore.video !== false) || minimized ? '' : 'no-video'}"> <div class="woka-icon {($constraintStore && $constraintStore.video !== false) || minimized ? '' : 'no-video'}">
<Woka userId={peer.userId} placeholderSrc={""} /> <Woka userId={peer.userId} placeholderSrc={""} />

View File

@ -18,6 +18,24 @@ export function getColorByString(str: string): string | null {
return color; return color;
} }
/**
* @param color: string
* @return string
*/
export function getTextColorByBackgroundColor(color: string | null): string {
if (!color) {
return "white";
}
const rgb = color.slice(1);
const brightness = Math.round(
(parseInt(rgb[0] + rgb[1], 16) * 299 +
parseInt(rgb[2] + rgb[3], 16) * 587 +
parseInt(rgb[4] + rgb[5], 16) * 114) /
1000
);
return brightness > 125 ? "black" : "white";
}
export function srcObject(node: HTMLVideoElement, stream: MediaStream | null) { export function srcObject(node: HTMLVideoElement, stream: MediaStream | null) {
node.srcObject = stream; node.srcObject = stream;
return { return {

View File

@ -4,36 +4,33 @@
import { ADMIN_URL } from "../../Enum/EnvironmentVariable"; import { ADMIN_URL } from "../../Enum/EnvironmentVariable";
import LL from "../../i18n/i18n-svelte"; import LL from "../../i18n/i18n-svelte";
const upgradeLink = ADMIN_URL + "/pricing";
const registerLink = ADMIN_URL + "/second-step-register"; const registerLink = ADMIN_URL + "/second-step-register";
</script> </script>
<main class="warningMain" transition:fly={{ y: -200, duration: 500 }}> <main class="warningMain" transition:fly={{ y: -200, duration: 500 }}>
{#if $userIsAdminStore} {#if $userIsAdminStore}
<h2>{$LL.warning.title()}</h2> <h2>{$LL.warning.title()}</h2>
<p> <p>{@html $LL.warning.content()}</p>
{$LL.warning.content({ upgradeLink })}
</p>
{:else if $limitMapStore} {:else if $limitMapStore}
<p> <p>
This map is available for 2 days. You can register your domain <a href={registerLink}>here</a>! This map is available for 2 days. You can register your domain <a href={registerLink}>here</a>!
</p> </p>
{:else} {:else}
<h2>{$LL.warning.title()}</h2> <h2>{$LL.warning.title()}</h2>
<p>{$LL.warning.limit()}</p> <p>{@html $LL.warning.content()}</p>
{/if} {/if}
</main> </main>
<style lang="scss"> <style lang="scss">
main.warningMain { main.warningMain {
pointer-events: auto; pointer-events: auto;
width: 80%; width: 100%;
background-color: #f9e81e; background-color: #f9e81e;
color: #14304c; color: #14304c;
text-align: center; text-align: center;
position: absolute; position: absolute;
top: 4%; top: 0;
left: 0; left: 0;
right: 0; right: 0;
margin-left: auto; margin-left: auto;

View File

@ -16,6 +16,10 @@ import { isRegisterData } from "../Messages/JsonMessages/RegisterData";
import { isAdminApiData } from "../Messages/JsonMessages/AdminApiData"; import { isAdminApiData } from "../Messages/JsonMessages/AdminApiData";
import { limitMapStore } from "../Stores/GameStore"; import { limitMapStore } from "../Stores/GameStore";
import { showLimitRoomModalStore } from "../Stores/ModalStore"; import { showLimitRoomModalStore } from "../Stores/ModalStore";
import { gameManager } from "../Phaser/Game/GameManager";
import { locales } from "../i18n/i18n-util";
import type { Locales } from "../i18n/i18n-types";
import { setCurrentLocale } from "../i18n/locales";
class ConnectionManager { class ConnectionManager {
private localUser!: LocalUser; private localUser!: LocalUser;
@ -342,9 +346,12 @@ class ConnectionManager {
throw new Error("No Auth code provided"); throw new Error("No Auth code provided");
} }
} }
const { authToken, userUuid, textures, email } = await Axios.get(`${PUSHER_URL}/login-callback`, { const { authToken, userUuid, textures, email, username, locale } = await Axios.get(
params: { code, nonce, token, playUri: this.currentRoom?.key }, `${PUSHER_URL}/login-callback`,
}).then((res) => { {
params: { code, nonce, token, playUri: this.currentRoom?.key },
}
).then((res) => {
return res.data; return res.data;
}); });
localUserStore.setAuthToken(authToken); localUserStore.setAuthToken(authToken);
@ -352,6 +359,27 @@ class ConnectionManager {
localUserStore.saveUser(this.localUser); localUserStore.saveUser(this.localUser);
this.authToken = authToken; this.authToken = authToken;
if (username) {
gameManager.setPlayerName(username);
}
if (locale) {
try {
if (locales.indexOf(locale) == -1) {
locales.forEach((l) => {
if (l.startsWith(locale.split("-")[0])) {
setCurrentLocale(l);
return;
}
});
} else {
setCurrentLocale(locale as Locales);
}
} catch (err) {
console.warn("Could not set locale", err);
}
}
//user connected, set connected store for menu at true //user connected, set connected store for menu at true
userIsConnected.set(true); userIsConnected.set(true);
} }

View File

@ -97,7 +97,7 @@ export abstract class Character extends Container implements OutlineableInterfac
fontFamily: '"Press Start 2P"', fontFamily: '"Press Start 2P"',
fontSize: "8px", fontSize: "8px",
strokeThickness: 2, strokeThickness: 2,
stroke: "gray", stroke: "#14304C",
metrics: { metrics: {
ascent: 20, ascent: 20,
descent: 10, descent: 10,

View File

@ -13,7 +13,7 @@ export class ActivatablesManager {
private canSelectByDistance: boolean = true; private canSelectByDistance: boolean = true;
private readonly outlineColor = 0xffff00; private readonly outlineColor = 0xf9e81e;
private readonly directionalActivationPositionShift = 50; private readonly directionalActivationPositionShift = 50;
constructor(currentPlayer: Player) { constructor(currentPlayer: Player) {

View File

@ -1220,6 +1220,8 @@ ${escapedMessage}
openCoWebsite.closable ?? true openCoWebsite.closable ?? true
); );
coWebsiteManager.addCoWebsiteToStore(coWebsite, openCoWebsite.position);
if (openCoWebsite.lazy === undefined || !openCoWebsite.lazy) { if (openCoWebsite.lazy === undefined || !openCoWebsite.lazy) {
await coWebsiteManager.loadCoWebsite(coWebsite); await coWebsiteManager.loadCoWebsite(coWebsite);
} }
@ -1596,6 +1598,8 @@ ${escapedMessage}
} }
}) })
.catch((reason) => console.warn(reason)); .catch((reason) => console.warn(reason));
urlManager.clearHashParameter();
} catch (err) { } catch (err) {
console.warn(`Cannot proceed with moveTo command:\n\t-> ${err}`); console.warn(`Cannot proceed with moveTo command:\n\t-> ${err}`);
} }
@ -1715,7 +1719,7 @@ ${escapedMessage}
} }
}); });
this.CurrentPlayer.on(Phaser.Input.Events.POINTER_OVER, (pointer: Phaser.Input.Pointer) => { this.CurrentPlayer.on(Phaser.Input.Events.POINTER_OVER, (pointer: Phaser.Input.Pointer) => {
this.CurrentPlayer.pointerOverOutline(0x00ffff); this.CurrentPlayer.pointerOverOutline(0x365dff);
}); });
this.CurrentPlayer.on(Phaser.Input.Events.POINTER_OUT, (pointer: Phaser.Input.Pointer) => { this.CurrentPlayer.on(Phaser.Input.Events.POINTER_OUT, (pointer: Phaser.Input.Pointer) => {
this.CurrentPlayer.pointerOutOutline(); this.CurrentPlayer.pointerOutOutline();

View File

@ -56,7 +56,7 @@ export class ActionableItem implements ActivatableInterface {
this.getOutlinePlugin()?.add(this.sprite, { this.getOutlinePlugin()?.add(this.sprite, {
thickness: 2, thickness: 2,
outlineColor: 0xffff00, outlineColor: 0xf9e81e,
}); });
} }

View File

@ -58,6 +58,10 @@ class UrlManager {
return this.getHashParameters()[name]; return this.getHashParameters()[name];
} }
public clearHashParameter(): void {
window.location.hash = "";
}
private getHashParameters(): Record<string, string> { private getHashParameters(): Record<string, string> {
return window.location.hash return window.location.hash
.substring(1) .substring(1)

View File

@ -1,9 +1,11 @@
import type { Translation } from "../i18n-types"; import type { Translation } from "../i18n-types";
import { ADMIN_URL } from "../../Enum/EnvironmentVariable";
const upgradeLink = ADMIN_URL + "/pricing";
const warning: NonNullable<Translation["warning"]> = { const warning: NonNullable<Translation["warning"]> = {
title: "Warnung!", title: "Warnung!",
content: content: `Diese Welt erreicht bald die maximale Kapazität. Du kannst die Kapazität <a href="${upgradeLink}" target="_blank">hier</a> erhöhen`,
'Diese Welt erreicht bald die maximale Kapazität. Du kannst die Kapazität <a href={upgradeLink} target="_blank">hier</a> erhöhen',
limit: "Diese Welt erreicht bald die maximale Kapazität!", limit: "Diese Welt erreicht bald die maximale Kapazität!",
accessDenied: { accessDenied: {
camera: "Zugriff auf die Kamera verweigert. Hier klicken um deine Browser Berechtigungen zu prüfen.", camera: "Zugriff auf die Kamera verweigert. Hier klicken um deine Browser Berechtigungen zu prüfen.",

View File

@ -1,9 +1,11 @@
import type { BaseTranslation } from "../i18n-types"; import type { BaseTranslation } from "../i18n-types";
import { ADMIN_URL } from "../../Enum/EnvironmentVariable";
const upgradeLink = ADMIN_URL + "/pricing";
const warning: BaseTranslation = { const warning: BaseTranslation = {
title: "Warning!", title: "Warning!",
content: content: `This world is close to its limit!. You can upgrade its capacity <a href="${upgradeLink}" target="_blank">here</a>`,
'This world is close to its limit!. You can upgrade its capacity <a href={upgradeLink} target="_blank">here</a>',
limit: "This world is close to its limit!", limit: "This world is close to its limit!",
accessDenied: { accessDenied: {
camera: "Camera access denied. Click here and check your browser permissions.", camera: "Camera access denied. Click here and check your browser permissions.",

View File

@ -1,9 +1,11 @@
import type { Translation } from "../i18n-types"; import type { Translation } from "../i18n-types";
import { ADMIN_URL } from "../../Enum/EnvironmentVariable";
const upgradeLink = ADMIN_URL + "/pricing";
const warning: NonNullable<Translation["warning"]> = { const warning: NonNullable<Translation["warning"]> = {
title: "Attention!", title: "Attention!",
content: content: `Ce monde est proche de sa limite ! Vous pouvez améliorer sa capacité <a href="${upgradeLink}" target="_blank">içi</a>`,
'Ce monde est proche de sa limite ! Vous pouvez améliorer sa capacité <a href={upgradeLink} target="_blank">içi</a>',
limit: "Ce monde est proche de ses limites!", limit: "Ce monde est proche de ses limites!",
accessDenied: { accessDenied: {
camera: "Accès à la caméra refusé. Cliquez ici et vérifiez les autorisations de votre navigateur.", camera: "Accès à la caméra refusé. Cliquez ici et vérifiez les autorisations de votre navigateur.",

View File

@ -1,7 +1,7 @@
#!/usr/bin/env bash #!/usr/bin/env sh
set -x set -x
set -o nounset errexit set -o nounset errexit
index_file=dist/index.html index_file=/usr/share/nginx/html/index.html
tmp_trackcodefile=/tmp/trackcode tmp_trackcodefile=/tmp/trackcode
# To inject tracking code, you have two choices: # To inject tracking code, you have two choices:
@ -10,12 +10,12 @@ tmp_trackcodefile=/tmp/trackcode
# The ANALYTICS_CODE_PATH is the location of a file inside the container # The ANALYTICS_CODE_PATH is the location of a file inside the container
ANALYTICS_CODE_PATH="${ANALYTICS_CODE_PATH:-dist/ga.html.tmpl}" ANALYTICS_CODE_PATH="${ANALYTICS_CODE_PATH:-dist/ga.html.tmpl}"
if [[ "${INSERT_ANALYTICS:-NO}" == "NO" ]]; then if [ "${INSERT_ANALYTICS:-NO}" = "NO" ]; then
echo "" > "${tmp_trackcodefile}" echo "" > "${tmp_trackcodefile}"
fi fi
# Automatically insert analytics if GA_TRACKING_ID is set # Automatically insert analytics if GA_TRACKING_ID is set
if [[ "${GA_TRACKING_ID:-}" != "" || "${INSERT_ANALYTICS:-NO}" != "NO" ]]; then if [ "${GA_TRACKING_ID:-}" != "" ] || [ "${INSERT_ANALYTICS:-NO}" != "NO" ]; then
echo "Templating code from ${ANALYTICS_CODE_PATH}" echo "Templating code from ${ANALYTICS_CODE_PATH}"
sed "s#<!-- TRACKING NUMBER -->#${GA_TRACKING_ID:-}#g" "${ANALYTICS_CODE_PATH}" > "$tmp_trackcodefile" sed "s#<!-- TRACKING NUMBER -->#${GA_TRACKING_ID:-}#g" "${ANALYTICS_CODE_PATH}" > "$tmp_trackcodefile"
fi fi

View File

@ -475,12 +475,7 @@ ansi-escapes@^4.3.0:
dependencies: dependencies:
type-fest "^0.21.3" type-fest "^0.21.3"
ansi-regex@^5.0.0: ansi-regex@^5.0.0, ansi-regex@^5.0.1:
version "5.0.0"
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.0.tgz#388539f55179bf39339c81af30a654d69f87cb75"
integrity sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==
ansi-regex@^5.0.1:
version "5.0.1" version "5.0.1"
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304"
integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==

View File

@ -1,8 +1,10 @@
# protobuf build # protobuf build
FROM node:14.18.2-buster-slim@sha256:20bedf0c09de887379e59a41c04284974f5fb529cf0e13aab613473ce298da3d as builder FROM node:14.18.2-buster-slim@sha256:20bedf0c09de887379e59a41c04284974f5fb529cf0e13aab613473ce298da3d as builder
WORKDIR /usr/src WORKDIR /usr/src
COPY messages/yarn.lock messages/package.json ./
RUN yarn install
COPY messages . COPY messages .
RUN yarn install && yarn proto RUN yarn proto
# typescript build # typescript build
FROM node:14.18.2-buster-slim@sha256:20bedf0c09de887379e59a41c04284974f5fb529cf0e13aab613473ce298da3d as builder2 FROM node:14.18.2-buster-slim@sha256:20bedf0c09de887379e59a41c04284974f5fb529cf0e13aab613473ce298da3d as builder2
@ -19,9 +21,9 @@ RUN yarn run tsc
FROM node:14.18.2-buster-slim@sha256:20bedf0c09de887379e59a41c04284974f5fb529cf0e13aab613473ce298da3d FROM node:14.18.2-buster-slim@sha256:20bedf0c09de887379e59a41c04284974f5fb529cf0e13aab613473ce298da3d
WORKDIR /usr/src WORKDIR /usr/src
COPY pusher/yarn.lock pusher/package.json ./ COPY pusher/yarn.lock pusher/package.json ./
COPY --from=builder2 /usr/src/dist /usr/src/dist
ENV NODE_ENV=production ENV NODE_ENV=production
RUN yarn install --production RUN yarn install --production
COPY --from=builder2 /usr/src/dist /usr/src/dist
USER node USER node
CMD ["yarn", "run", "runprod"] CMD ["yarn", "run", "runprod"]

View File

@ -84,11 +84,13 @@ export class AdminController extends BaseController {
if (ADMIN_API_TOKEN === "") { if (ADMIN_API_TOKEN === "") {
res.writeStatus("401 Unauthorized").end("No token configured!"); res.writeStatus("401 Unauthorized").end("No token configured!");
res.end();
return; return;
} }
if (token !== ADMIN_API_TOKEN) { if (token !== ADMIN_API_TOKEN) {
console.error("Admin access refused for token: " + token); console.error("Admin access refused for token: " + token);
res.writeStatus("401 Unauthorized").end("Incorrect token"); res.writeStatus("401 Unauthorized").end("Incorrect token");
res.end();
return; return;
} }

View File

@ -93,7 +93,15 @@ export class AuthenticateController extends BaseController {
res.writeStatus("200"); res.writeStatus("200");
this.addCorsHeaders(res); this.addCorsHeaders(res);
res.writeHeader("Content-Type", "application/json"); res.writeHeader("Content-Type", "application/json");
return res.end(JSON.stringify({ ...resCheckTokenAuth, ...resUserData, authToken: token })); return res.end(
JSON.stringify({
...resCheckTokenAuth,
...resUserData,
authToken: token,
username: authTokenData?.username,
locale: authTokenData?.locale,
})
);
} catch (err) { } catch (err) {
console.info("User was not connected", err); console.info("User was not connected", err);
} }
@ -115,7 +123,12 @@ export class AuthenticateController extends BaseController {
if (!email) { if (!email) {
throw new Error("No email in the response"); throw new Error("No email in the response");
} }
const authToken = jwtTokenManager.createAuthToken(email, userInfo?.access_token); const authToken = jwtTokenManager.createAuthToken(
email,
userInfo?.access_token,
userInfo?.username,
userInfo?.locale
);
//Get user data from Admin Back Office //Get user data from Admin Back Office
//This is very important to create User Local in LocalStorage in WorkAdventure //This is very important to create User Local in LocalStorage in WorkAdventure
@ -124,7 +137,9 @@ export class AuthenticateController extends BaseController {
res.writeStatus("200"); res.writeStatus("200");
this.addCorsHeaders(res); this.addCorsHeaders(res);
res.writeHeader("Content-Type", "application/json"); res.writeHeader("Content-Type", "application/json");
return res.end(JSON.stringify({ ...data, authToken })); return res.end(
JSON.stringify({ ...data, authToken, username: userInfo?.username, locale: userInfo?.locale })
);
} catch (e) { } catch (e) {
console.error("openIDCallback => ERROR", e); console.error("openIDCallback => ERROR", e);
return this.errorToResponse(e, res); return this.errorToResponse(e, res);

View File

@ -18,6 +18,9 @@ export const OPID_CLIENT_SECRET = process.env.OPID_CLIENT_SECRET || "";
export const OPID_CLIENT_ISSUER = process.env.OPID_CLIENT_ISSUER || ""; export const OPID_CLIENT_ISSUER = process.env.OPID_CLIENT_ISSUER || "";
export const OPID_CLIENT_REDIRECT_URL = process.env.OPID_CLIENT_REDIRECT_URL || FRONT_URL + "/jwt"; export const OPID_CLIENT_REDIRECT_URL = process.env.OPID_CLIENT_REDIRECT_URL || FRONT_URL + "/jwt";
export const OPID_PROFILE_SCREEN_PROVIDER = process.env.OPID_PROFILE_SCREEN_PROVIDER || ADMIN_URL + "/profile"; export const OPID_PROFILE_SCREEN_PROVIDER = process.env.OPID_PROFILE_SCREEN_PROVIDER || ADMIN_URL + "/profile";
export const OPID_SCOPE = process.env.OPID_SCOPE || "openid email";
export const OPID_USERNAME_CLAIM = process.env.OPID_USERNAME_CLAIM || "username";
export const OPID_LOCALE_CLAIM = process.env.OPID_LOCALE_CLAIM || "locale";
export const DISABLE_ANONYMOUS: boolean = process.env.DISABLE_ANONYMOUS === "true"; export const DISABLE_ANONYMOUS: boolean = process.env.DISABLE_ANONYMOUS === "true";
export { export {

View File

@ -5,6 +5,8 @@ import { InvalidTokenError } from "../Controller/InvalidTokenError";
export interface AuthTokenData { export interface AuthTokenData {
identifier: string; //will be a email if logged in or an uuid if anonymous identifier: string; //will be a email if logged in or an uuid if anonymous
accessToken?: string; accessToken?: string;
username?: string;
locale?: string;
} }
export interface AdminSocketTokenData { export interface AdminSocketTokenData {
authorizedRoomIds: string[]; //the list of rooms the client is authorized to read from. authorizedRoomIds: string[]; //the list of rooms the client is authorized to read from.
@ -16,8 +18,8 @@ class JWTTokenManager {
return Jwt.verify(token, ADMIN_SOCKETS_TOKEN) as AdminSocketTokenData; return Jwt.verify(token, ADMIN_SOCKETS_TOKEN) as AdminSocketTokenData;
} }
public createAuthToken(identifier: string, accessToken?: string) { public createAuthToken(identifier: string, accessToken?: string, username?: string, locale?: string) {
return Jwt.sign({ identifier, accessToken }, SECRET_KEY, { expiresIn: "30d" }); return Jwt.sign({ identifier, accessToken, username, locale }, SECRET_KEY, { expiresIn: "30d" });
} }
public verifyJWTToken(token: string, ignoreExpiration: boolean = false): AuthTokenData { public verifyJWTToken(token: string, ignoreExpiration: boolean = false): AuthTokenData {

View File

@ -4,6 +4,9 @@ import {
OPID_CLIENT_SECRET, OPID_CLIENT_SECRET,
OPID_CLIENT_ISSUER, OPID_CLIENT_ISSUER,
OPID_CLIENT_REDIRECT_URL, OPID_CLIENT_REDIRECT_URL,
OPID_USERNAME_CLAIM,
OPID_LOCALE_CLAIM,
OPID_SCOPE,
} from "../Enum/EnvironmentVariable"; } from "../Enum/EnvironmentVariable";
class OpenIDClient { class OpenIDClient {
@ -25,8 +28,11 @@ class OpenIDClient {
public authorizationUrl(state: string, nonce: string, playUri?: string, redirect?: string) { public authorizationUrl(state: string, nonce: string, playUri?: string, redirect?: string) {
return this.initClient().then((client) => { return this.initClient().then((client) => {
if (!OPID_SCOPE.includes("email") || !OPID_SCOPE.includes("openid")) {
throw new Error("Invalid scope, 'email' and 'openid' are required in OPID_SCOPE.");
}
return client.authorizationUrl({ return client.authorizationUrl({
scope: "openid email", scope: OPID_SCOPE,
prompt: "login", prompt: "login",
state: state, state: state,
nonce: nonce, nonce: nonce,
@ -36,7 +42,10 @@ class OpenIDClient {
}); });
} }
public getUserInfo(code: string, nonce: string): Promise<{ email: string; sub: string; access_token: string }> { public getUserInfo(
code: string,
nonce: string
): Promise<{ email: string; sub: string; access_token: string; username: string; locale: string }> {
return this.initClient().then((client) => { return this.initClient().then((client) => {
return client.callback(OPID_CLIENT_REDIRECT_URL, { code }, { nonce }).then((tokenSet) => { return client.callback(OPID_CLIENT_REDIRECT_URL, { code }, { nonce }).then((tokenSet) => {
return client.userinfo(tokenSet).then((res) => { return client.userinfo(tokenSet).then((res) => {
@ -45,6 +54,8 @@ class OpenIDClient {
email: res.email as string, email: res.email as string,
sub: res.sub, sub: res.sub,
access_token: tokenSet.access_token as string, access_token: tokenSet.access_token as string,
username: res[OPID_USERNAME_CLAIM] as string,
locale: res[OPID_LOCALE_CLAIM] as string,
}; };
}); });
}); });

View File

@ -11,9 +11,9 @@ RUN yarn run tsc
FROM node:14.15.4-buster-slim@sha256:cbae886186467bbfd274b82a234a1cdfbbd31201c2a6ee63a6893eefcf3c6e76 FROM node:14.15.4-buster-slim@sha256:cbae886186467bbfd274b82a234a1cdfbbd31201c2a6ee63a6893eefcf3c6e76
WORKDIR /usr/src WORKDIR /usr/src
COPY uploader/yarn.lock uploader/package.json ./ COPY uploader/yarn.lock uploader/package.json ./
COPY --from=builder2 /usr/src/dist /usr/src/dist
ENV NODE_ENV=production ENV NODE_ENV=production
RUN yarn install --production RUN yarn install --production
COPY --from=builder2 /usr/src/dist /usr/src/dist
USER node USER node
CMD ["yarn", "run", "runprod"] CMD ["yarn", "run", "runprod"]