diff --git a/deeployer.libsonnet b/deeployer.libsonnet index a2e8970a..75a4de8c 100644 --- a/deeployer.libsonnet +++ b/deeployer.libsonnet @@ -75,6 +75,9 @@ "UPLOADER_URL": "//uploader-"+url, "ADMIN_URL": "//"+url, "JITSI_URL": env.JITSI_URL, + #POSTHOG + "POSTHOG_API_KEY": if namespace == "master" then env.POSTHOG_API_KEY else "", + "POSTHOG_URL": if namespace == "master" then env.POSTHOG_URL else "", "SECRET_JITSI_KEY": env.SECRET_JITSI_KEY, "TURN_SERVER": "turn:coturn.workadventu.re:443,turns:coturn.workadventu.re:443", "JITSI_PRIVATE_MODE": if env.SECRET_JITSI_KEY != '' then "true" else "false", diff --git a/docs/maps/opening-a-website.md b/docs/maps/opening-a-website.md index 682306b4..2ca54281 100644 --- a/docs/maps/opening-a-website.md +++ b/docs/maps/opening-a-website.md @@ -12,7 +12,7 @@ In order to create a zone that opens websites: * You must create a specific layer. * In layer properties, you MUST add a "`openWebsite`" property (of type "`string`"). The value of the property is the URL of the website to open (the URL must start with "https://") -* You may also use "`openWebsiteWidth`" property (of type "`number`" between 0 and 100) to control the width of the iframe. +* You may also use "`openWebsiteWidth`" property (of type "`int`" or "`float`" between 0 and 100) to control the width of the iframe. * You may also use "`openTab`" property (of type "`string`") to open in a new tab instead. {.alert.alert-warning} diff --git a/front/Dockerfile b/front/Dockerfile index ef724e0f..6fef9dc8 100644 --- a/front/Dockerfile +++ b/front/Dockerfile @@ -1,13 +1,13 @@ -FROM thecodingmachine/workadventure-back-base:latest as builder -WORKDIR /var/www/messages -COPY --chown=docker:docker messages . +FROM node:14.15.4-buster-slim@sha256:cbae886186467bbfd274b82a234a1cdfbbd31201c2a6ee63a6893eefcf3c6e76 as builder +WORKDIR /usr/src +COPY messages . RUN yarn install && yarn proto # we are rebuilding on each deploy to cope with the PUSHER_URL environment URL FROM thecodingmachine/nodejs:14-apache COPY --chown=docker:docker front . -COPY --from=builder --chown=docker:docker /var/www/messages/generated /var/www/html/src/Messages/generated +COPY --from=builder --chown=docker:docker /usr/src/generated /var/www/html/src/Messages/generated # Removing the iframe.html file from the final image as this adds a XSS attack. # iframe.html is only in dev mode to circumvent a limitation diff --git a/front/package.json b/front/package.json index d60b6fa6..38c0570f 100644 --- a/front/package.json +++ b/front/package.json @@ -49,6 +49,7 @@ "phaser": "^3.54.0", "phaser-animated-tiles": "workadventure/phaser-animated-tiles#da68bbededd605925621dd4f03bd27e69284b254", "phaser3-rex-plugins": "^1.1.42", + "posthog-js": "^1.13.12", "queue-typescript": "^1.0.1", "quill": "1.3.6", "quill-delta-to-html": "^0.12.0", diff --git a/front/src/Administration/AnalyticsClient.ts b/front/src/Administration/AnalyticsClient.ts new file mode 100644 index 00000000..f3cde793 --- /dev/null +++ b/front/src/Administration/AnalyticsClient.ts @@ -0,0 +1,61 @@ +import {POSTHOG_API_KEY, POSTHOG_URL} from "../Enum/EnvironmentVariable"; + +class AnalyticsClient { + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + private posthogPromise: Promise; + + constructor() { + if (POSTHOG_API_KEY && POSTHOG_URL) { + this.posthogPromise = import('posthog-js').then(({default: posthog}) => { + posthog.init(POSTHOG_API_KEY, { api_host: POSTHOG_URL, disable_cookie: true }); + return posthog; + }); + } else { + this.posthogPromise = Promise.reject(); + } + } + + identifyUser(uuid: string) { + this.posthogPromise.then(posthog => { + posthog.identify(uuid, { uuid, wa: true }); + }).catch(); + } + + loggedWithSso() { + this.posthogPromise.then(posthog => { + posthog.capture('wa-logged-sso'); + }).catch(); + } + + loggedWithToken() { + this.posthogPromise.then(posthog => { + posthog.capture('wa-logged-token'); + }).catch(); + } + + enteredRoom(roomId: string) { + this.posthogPromise.then(posthog => { + posthog.capture('$pageView', {roomId}); + }).catch(); + } + + openedMenu() { + this.posthogPromise.then(posthog => { + posthog.capture('wa-opened-menu'); + }).catch(); + } + + launchEmote(emote: string) { + this.posthogPromise.then(posthog => { + posthog.capture('wa-emote-launch', {emote}); + }).catch(); + } + + enteredJitsi(roomName: string, roomId: string) { + this.posthogPromise.then(posthog => { + posthog.capture('wa-entered-jitsi', {roomName, roomId}); + }).catch(); + } +} +export const analyticsClient = new AnalyticsClient(); diff --git a/front/src/Api/IframeListener.ts b/front/src/Api/IframeListener.ts index 140d2f34..5a9aca85 100644 --- a/front/src/Api/IframeListener.ts +++ b/front/src/Api/IframeListener.ts @@ -46,30 +46,18 @@ type AnswererCallback = ( * Also allows to send messages to those iframes. */ class IframeListener { - private readonly _readyStream: Subject = new Subject(); - public readonly readyStream = this._readyStream.asObservable(); - - private readonly _chatStream: Subject = new Subject(); - public readonly chatStream = this._chatStream.asObservable(); - private readonly _openPopupStream: Subject = new Subject(); public readonly openPopupStream = this._openPopupStream.asObservable(); private readonly _openTabStream: Subject = new Subject(); public readonly openTabStream = this._openTabStream.asObservable(); - private readonly _goToPageStream: Subject = new Subject(); - public readonly goToPageStream = this._goToPageStream.asObservable(); - private readonly _loadPageStream: Subject = new Subject(); public readonly loadPageStream = this._loadPageStream.asObservable(); private readonly _openCoWebSiteStream: Subject = new Subject(); public readonly openCoWebSiteStream = this._openCoWebSiteStream.asObservable(); - private readonly _closeCoWebSiteStream: Subject = new Subject(); - public readonly closeCoWebSiteStream = this._closeCoWebSiteStream.asObservable(); - private readonly _disablePlayerControlStream: Subject = new Subject(); public readonly disablePlayerControlStream = this._disablePlayerControlStream.asObservable(); @@ -219,7 +207,7 @@ class IframeListener { } else if (payload.type === "setProperty" && isSetPropertyEvent(payload.data)) { this._setPropertyStream.next(payload.data); } else if (payload.type === "chat" && isChatEvent(payload.data)) { - this._chatStream.next(payload.data); + scriptUtils.sendAnonymousChat(payload.data); } else if (payload.type === "openPopup" && isOpenPopupEvent(payload.data)) { this._openPopupStream.next(payload.data); } else if (payload.type === "closePopup" && isClosePopupEvent(payload.data)) { diff --git a/front/src/Api/ScriptUtils.ts b/front/src/Api/ScriptUtils.ts index 0dbe40fe..ad6dcc0f 100644 --- a/front/src/Api/ScriptUtils.ts +++ b/front/src/Api/ScriptUtils.ts @@ -1,4 +1,7 @@ import { coWebsiteManager } from "../WebRtc/CoWebsiteManager"; +import { playersStore } from "../Stores/PlayersStore"; +import { chatMessagesStore } from "../Stores/ChatStore"; +import type { ChatEvent } from "./Events/ChatEvent"; class ScriptUtils { public openTab(url: string) { @@ -16,6 +19,11 @@ class ScriptUtils { public closeCoWebSite() { coWebsiteManager.closeCoWebsite(); } + + public sendAnonymousChat(chatEvent: ChatEvent) { + const userId = playersStore.addFacticePlayer(chatEvent.author); + chatMessagesStore.addExternalMessage(userId, chatEvent.message); + } } export const scriptUtils = new ScriptUtils(); diff --git a/front/src/Components/AudioManager/AudioManager.svelte b/front/src/Components/AudioManager/AudioManager.svelte index a448d56c..a7f96f61 100644 --- a/front/src/Components/AudioManager/AudioManager.svelte +++ b/front/src/Components/AudioManager/AudioManager.svelte @@ -5,9 +5,9 @@ audioManagerFileStore, audioManagerVolumeStore, } from "../../Stores/AudioManagerStore"; - import {get} from "svelte/store"; + import { get } from "svelte/store"; import type { Unsubscriber } from "svelte/store"; - import {onDestroy, onMount} from "svelte"; + import { onDestroy, onMount } from "svelte"; let HTMLAudioPlayer: HTMLAudioElement; let audioPlayerVolumeIcon: HTMLElement; @@ -21,9 +21,9 @@ onMount(() => { volume = localUserStore.getAudioPlayerVolume(); audioManagerVolumeStore.setMuted(localUserStore.getAudioPlayerMuted()); - setVolume(); + changeVolume(); - unsubscriberFileStore = audioManagerFileStore.subscribe(() =>{ + unsubscriberFileStore = audioManagerFileStore.subscribe(() => { HTMLAudioPlayer.pause(); HTMLAudioPlayer.loop = get(audioManagerVolumeStore).loop; HTMLAudioPlayer.volume = get(audioManagerVolumeStore).volume; @@ -53,13 +53,7 @@ } }) - function onMute() { - audioManagerVolumeStore.setMuted(!get(audioManagerVolumeStore).muted); - localUserStore.setAudioPlayerMuted(get(audioManagerVolumeStore).muted); - setVolume(); - } - - function setVolume() { + function changeVolume() { if (get(audioManagerVolumeStore).muted) { audioPlayerVolumeIcon.classList.add('muted'); audioPlayerVol.value = "0"; @@ -76,8 +70,22 @@ audioPlayerVolumeIcon.classList.remove('mid'); } } - audioManagerVolumeStore.setVolume(volume) - localUserStore.setAudioPlayerVolume(get(audioManagerVolumeStore).volume); + } + + function onMute() { + const muted = !get(audioManagerVolumeStore).muted; + audioManagerVolumeStore.setMuted(muted); + localUserStore.setAudioPlayerMuted(muted); + changeVolume(); + } + + function setVolume() { + volume = parseFloat(audioPlayerVol.value); + audioManagerVolumeStore.setVolume(volume); + localUserStore.setAudioPlayerVolume(volume); + audioManagerVolumeStore.setMuted(false); + localUserStore.setAudioPlayerMuted(false); + changeVolume(); } function setDecrease() { @@ -108,8 +116,7 @@ - +