Compare commits

...

213 Commits

Author SHA1 Message Date
Thomas Basler 15ad163da3 Bump Phaser version 2021-03-26 21:36:20 +01:00
Thomas Basler fa71e2b296 Merge remote-tracking branch 'upstream/develop' into develop 2021-03-26 21:21:33 +01:00
Kharhamel 4b8a2aca34
Merge pull request #849 from thecodingmachine/unloadingFixes
improved the back to better handle ghost connexions
2021-03-26 15:32:09 +01:00
kharhamel 3b6fe8da8d improved the back to better handle ghost connexions 2021-03-26 15:14:10 +01:00
Kharhamel 77c40985c4
Merge pull request #847 from thecodingmachine/unloadingFixes
improved the unloading process to not create a useless socket
2021-03-26 14:50:02 +01:00
David Négrier d269596702
Fixing Discord invite link 2021-03-26 14:33:55 +01:00
kharhamel 886daebfea improved the unloading process to not create a useless socket 2021-03-26 14:19:37 +01:00
David Négrier 30e12702eb
Merge pull request #838 from thecodingmachine/fixChromeResize
FIX: resizing on wide screen on chrome now use the correct coordinates
2021-03-25 18:52:15 +01:00
kharhamel 99e6743c01 FIX: resizing on wide screen on chrome now use the correct coordinates 2021-03-25 14:35:51 +01:00
Kharhamel dc1a5da175
Merge pull request #837 from thecodingmachine/fixCameraHelpPopup
FIX: help camera settings popup now only show up once
2021-03-24 16:05:21 +01:00
Kharhamel 6f038001de
Merge pull request #836 from thecodingmachine/fixFullScreen
FIX: fixed a bug with fullscreen mode
2021-03-24 16:00:25 +01:00
kharhamel 0b352fc5dc FIX: help camera settings popup now only show up once 2021-03-24 15:59:18 +01:00
kharhamel 43921e3fcc FIX: fixed a bug with fullscreen mode 2021-03-24 15:51:18 +01:00
Kharhamel 1d69af0664
Merge pull request #835 from thecodingmachine/warningMessage
FIX: fixed a error in the validation of receiveGlobalMessagePrompt in pusher/AdminController
2021-03-24 14:37:50 +01:00
kharhamel f90046459e FIX: fixed a error in the validation of receiveGlobalMessagePrompt in pusher/AdminController 2021-03-24 14:35:20 +01:00
Kharhamel ec2fa9501c
Merge pull request #813 from thecodingmachine/warningMessage
Warning message
2021-03-24 14:27:13 +01:00
kharhamel 8d6c1a50bf FEATURE: warning message when world is near full capacity 2021-03-23 17:44:47 +01:00
Kharhamel 7765775df5
Merge pull request #825 from workadventure-xce/audio_player_improvements
Audio player improvements
2021-03-23 14:07:20 +01:00
Kharhamel 210daf46e3
Merge pull request #832 from thecodingmachine/master
Hotfix from master
2021-03-23 11:11:17 +01:00
David Négrier 979e43bb05
Merge pull request #830 from thecodingmachine/discordbadge
Adding Discord badge to the README
2021-03-23 09:41:40 +01:00
David Négrier 23c33b2971 Adding Discord badge to the README
This should be merged only when the Discord widget mode is enabled on the server (otherwise the number of current users is not displayed)
2021-03-23 09:41:18 +01:00
PizZaKatZe 821f91e996 Finer steps for audio volume 2021-03-22 20:10:27 +01:00
PizZaKatZe 599fdd6ceb Fix group icons and audio attenuation 2021-03-22 20:09:11 +01:00
TabascoEye e6accd711d Store muted setting of audio player in local storage
This commit was originally authored by @TabascoEye, then rebased and
improved by @pizkaz:

* refactors local user storage, adding audio player settings
* stores "muted" state of audio player in local store
2021-03-22 20:09:11 +01:00
PizZaKatZe 5bd6f49846 Fix (now legacy) 'playAudioLoop' property 2021-03-22 20:09:11 +01:00
PizZaKatZe fdbcd98a9a Implement audio volume and loop properties 2021-03-22 20:09:11 +01:00
PizZaKatZe 132c6c9ad6 Fix volume auto-reduction in conversations 2021-03-22 20:09:09 +01:00
PizZaKatZe 5a7e67f5df Reflect volume change in audio control 2021-03-22 20:06:55 +01:00
PizZaKatZe 20d3236e73 Fix audio control box appearance 2021-03-22 20:06:55 +01:00
David Négrier f9a2097bc9
Merge pull request #828 from thecodingmachine/develop
Deploy 2021-03-22
2021-03-22 16:42:28 +01:00
David Négrier 4966789b3b
Merge pull request #663 from shug0/patch-1
Correct the local app url in README
2021-03-22 16:33:03 +01:00
David Négrier ce0ede7613
Merge pull request #805 from thecodingmachine/dependabot/npm_and_yarn/front/elliptic-6.5.4
Bump elliptic from 6.5.3 to 6.5.4 in /front
2021-03-22 16:31:59 +01:00
David Négrier 6bb757e8b5
Merge pull request #824 from workadventure-xce/fix/front-page-template
Fix/front page template
2021-03-22 16:30:53 +01:00
Kharhamel 04bd60ca26
Merge pull request #827 from workadventure-xce/fix/jitsi_integration
Fix Jitsi integration
2021-03-22 16:27:32 +01:00
David Négrier 9da0acb0d7
Merge pull request #814 from Wikinaut/patch-1
update /etc/hosts readme
2021-03-22 16:12:48 +01:00
David Négrier 96492e8594
Merge pull request #826 from workadventure-xce/fix/scrollbars
Remove scroll bars
2021-03-22 15:06:23 +01:00
PizZaKatZe 318c63ab90 Fix Jitsi integration
Jitsi iframes are rendered directly in the cowebsite div next to the
cowebsite > main element. This patch fixes the situation.
2021-03-22 11:36:14 +01:00
PizZaKatZe 13fcda7bcf Remove scroll bars 2021-03-21 12:55:14 +01:00
PizZaKatZe c0bf5755ed front: Fix page template 2021-03-21 11:28:02 +01:00
PizZaKatZe 97ec721c2a front: Fix page template indentation 2021-03-21 11:28:02 +01:00
Kharhamel 519018f6c5
Merge pull request #764 from githubIsNotOpenSourceLOL/patch-1
Use email address from .env
2021-03-19 17:30:12 +01:00
David Négrier 4660b450c5
Merge pull request #700 from thecodingmachine/audioCameraPermission
Input need camera and microphone permission
2021-03-19 17:09:48 +01:00
GRL e50c8bc354 Suppression of *{...} in every HTML files because the style.ccs file already declare it.
Suppression unused code
2021-03-19 16:21:35 +01:00
GRL 0eb16bce6f Suppression width in Html file 2021-03-19 15:58:02 +01:00
GRL 1cfb22f695 Creation of the HelpCameraSettingsScene
Update of GameManager and index to include the new scene
Change title of helpCameraSettings
Suppression function reset
2021-03-19 15:40:07 +01:00
GRL 0b00055eda Use includes instead of indexOf 2021-03-19 14:40:18 +01:00
GRL c654f722e3 Add Firefox image
Check permissions in the game
Button Continue to continue without allowing camera and microphone
2021-03-19 12:04:37 +01:00
Gregoire Parant d68406d5e1 Create input need page 2021-03-19 12:02:42 +01:00
Kharhamel 1f85e548ce
Merge pull request #816 from thecodingmachine/iframeResize
FEATURE: complete control over the iframe size
2021-03-18 16:16:54 +01:00
kharhamel 32fdfaab35 edited CoWebsiteManager to manage vertical resizing 2021-03-18 16:11:22 +01:00
githubIsNotOpenSourceLOL ec691a75fe
Update .env.template 2021-03-18 13:26:02 +01:00
kharhamel 0701e607fa added sass loader to the project and put the cowebsite style in its own sass file 2021-03-18 12:37:05 +01:00
kharhamel f9c8b4131c added fullscreen 2021-03-17 18:57:00 +01:00
kharhamel 272be3eba0 improve the visual of the cowebsite 2021-03-17 18:35:19 +01:00
kharhamel 3f6c61633a FEATURE: complete control over the iframe size 2021-03-17 18:34:12 +01:00
Kharhamel 19856c0ee9
Merge pull request #783 from Moini/patch-1
Improve visibility of close button for embedded web pages
2021-03-17 14:35:11 +01:00
David Négrier 4a50729db3
Merge pull request #817 from thecodingmachine/customjitsiurl
Adding the ability to specify a custom Jitsi URL directly in the map
2021-03-16 21:33:35 +01:00
David Négrier ffb5823b2a Adding the ability to specify a custom Jitsi URL directly in the map
This feature allows to add a "jitsiUrl" property in the map.
As a result, you can use for a given Jitsi room a custom instance of Jitsi.
Using "jitsiUrl" will only work with public Jitsi instances (authentication is not supported when "jitsiUrl" property is provided)

The Jitsi external_api.js script is now lazily loaded.
2021-03-16 20:37:12 +01:00
Wikinaut d8c74fa838
update /etc/hosts readme 2021-03-15 15:37:51 +01:00
Kharhamel 18fa1e6153
Merge pull request #807 from thecodingmachine/improvePlayerAnims
FIX: player anims correctly stop on the idle frame
2021-03-11 16:34:20 +01:00
kharhamel fe8c75610d FIX: player anims correctly stop on the idle frame 2021-03-11 16:21:02 +01:00
Gregoire Parant 59c310d0a8 Update to optimize maintainability of the code 2021-03-11 12:17:37 +01:00
Gregoire Parant 2fcb8a76b1 Merge branch 'master' into develop
# Conflicts:
#	front/src/Connexion/RoomConnection.ts
#	front/src/Phaser/Menu/ReportMenu.ts
2021-03-11 02:22:05 +01:00
grégoire parant ce6976ec34
Merge pull request #806 from thecodingmachine/hot-fix-moderate-controls
Hot fix moderate controls
2021-03-11 02:13:15 +01:00
Gregoire Parant 2cf99df2da Hot Fix report 2021-03-11 02:03:06 +01:00
Gregoire Parant 46cb9333e6 HotFix message in back 2021-03-11 01:25:36 +01:00
dependabot[bot] d95c71281f
Bump elliptic from 6.5.3 to 6.5.4 in /front
Bumps [elliptic](https://github.com/indutny/elliptic) from 6.5.3 to 6.5.4.
- [Release notes](https://github.com/indutny/elliptic/releases)
- [Commits](https://github.com/indutny/elliptic/compare/v6.5.3...v6.5.4)

Signed-off-by: dependabot[bot] <support@github.com>
2021-03-10 13:26:46 +00:00
David Négrier a28fcbb9e6 Catching error when JWT token cannot be generated to avoid crashing Pusher 2021-03-10 09:41:54 +01:00
Kharhamel 21cc842c33
Merge pull request #793 from thecodingmachine/adminWorldsLimit
FEATURE: the connexion error an user can get when a world is full is now correctly treated.
2021-03-09 17:45:43 +01:00
kharhamel 8abc34c631 FEATURE: the connexion error an user can get when a world is full is now correctly traited. Also remove a redundant adminApi call 2021-03-09 16:34:19 +01:00
David Négrier f3c67a94ee Trying to enable back connection on admin from develop, knowing that the develop certificate is broken. Fixing priority. 2021-03-09 09:12:55 +01:00
David Négrier 1a4c7ad928 Trying to enable back connection on admin from develop, knowing that the develop certificate is broken 2021-03-08 10:58:57 +01:00
David Négrier 80df1ffb5f Revert "Adding back admin in develop branch CI"
This reverts commit c384a73edf.
2021-03-05 17:45:16 +01:00
David Négrier c384a73edf Adding back admin in develop branch CI 2021-03-05 16:52:54 +01:00
David Négrier 99ab99d893
Merge pull request #792 from thecodingmachine/gafix
Fixing GA tag not included in play domain
2021-03-05 10:52:16 +01:00
David Négrier 15405633d1 Fixing GA tag not included in play domain
The GA tag was not properly included because the index.html file that contained it was overwritten by Webpack's HtmlWebpackPlugin.
2021-03-05 10:00:11 +01:00
David Négrier bf8e8bf777
Merge pull request #689 from znerol-forks/feature/cowebsite-load-map-relative-url
Allow URLs relative to map base in iframe / openWebsite
2021-03-04 18:21:48 +01:00
Kharhamel 5833629469
Merge pull request #784 from thecodingmachine/betterGlobalMessage
FEATURE: better implementation of the admin global message
2021-03-04 18:10:52 +01:00
kharhamel ad7e16c33b FEATURE: better implementation of the admin global message 2021-03-04 16:18:06 +01:00
grégoire parant df610aa01b
Merge pull request #785 from thecodingmachine/updatePrivateAccess
Update private access
2021-03-02 14:35:39 +01:00
Gregoire Parant 915d945dd5 Fix ci 2021-03-01 22:32:50 +01:00
Gregoire Parant 30f4793342 Update private access 2021-03-01 22:21:52 +01:00
David Négrier e0540790e1 Translating button 2021-03-01 14:36:18 +01:00
Moini 95da470a89
Improve visibility of close button for embedded web pages
The button (a white x) wasn't visible on white background. Now it is. Also is a little bit larger and changes color on hover.
2021-03-01 14:08:56 +01:00
David Négrier 48c13119bd
Merge pull request #776 from s00500/fix_report_menu
Fix report userId in report menu
2021-02-27 23:16:23 +01:00
Lukas Bachschwell 2772d72eba
Fix report userId in report menu
Signed-off-by: Lukas Bachschwell <lukas@lbsfilm.at>
2021-02-24 21:32:16 +01:00
githubIsNotOpenSourceLOL 6e6b607226
Use email address from .env 2021-02-19 23:10:48 +01:00
David Négrier 6e9c715980
Merge pull request #739 from t10r/fix/multi-user-videochat-mode
fix multi user videochat mode
2021-02-19 14:54:38 +01:00
David Négrier 3eec41a1d0
Merge pull request #762 from thecodingmachine/develop
Deploy 2021-02-18
2021-02-18 20:10:55 +01:00
David Négrier 6f06b92395
Merge pull request #760 from thecodingmachine/turncredentials
Fixing screensharing Coturn credentials
2021-02-18 14:09:33 +01:00
David Négrier 13a20a0ef0 Fixing screensharing Coturn credentials
When screensharing is initiated by the user, we previously did not send the correct Coturn credentials.
We now send the last credentials received from the Video stream.
2021-02-18 11:52:30 +01:00
grégoire parant b5514e6e48
Merge pull request #759 from thecodingmachine/jitsiConfig
Fix last properties changed after trigger change position
2021-02-18 11:37:41 +01:00
Gregoire Parant 772c3174be Fix last properties changed after trigger change position 2021-02-18 11:32:37 +01:00
grégoire parant 3a1b2490ef
Merge pull request #758 from thecodingmachine/jitsiConfig
Jitsi config
2021-02-18 10:12:16 +01:00
Gregoire Parant 80449e9a96 Change json map test 2021-02-18 10:00:13 +01:00
Gregoire Parant 8a2954eab9 Fix new conf feature 2021-02-18 09:38:27 +01:00
grégoire parant 13078489c6
Merge pull request #757 from thecodingmachine/jitsiConfig
Merge Jitsi config
2021-02-17 20:34:31 +01:00
Gregoire Parant 5ddf909fe6 Remove unless export constante 2021-02-17 20:26:33 +01:00
grégoire parant c3286e61cf
Merge pull request #655 from thecodingmachine/exitTriggerAction
Message trigger action
2021-02-17 19:47:46 +01:00
Gregoire Parant fff0cbc3f6 Revert message exit 2021-02-17 19:40:00 +01:00
Gregoire Parant 9b7b3eb140 Update feedback @tabascoeye 2021-02-17 19:31:05 +01:00
Gregoire Parant ce423c9fec Remove exit trigger action 2021-02-17 19:29:59 +01:00
Gregoire Parant 83fc7d0cc0 Merge branch 'develop' into exitTriggerAction
# Conflicts:
#	front/src/Phaser/Game/GameScene.ts
2021-02-17 19:28:41 +01:00
Gregoire Parant baae1e8125 Update to restore previous config 2021-02-17 19:21:37 +01:00
Gregoire Parant e8b1b286a3 Merge Jitsi config 2021-02-17 18:49:55 +01:00
David Négrier 36a713ec91
Merge pull request #754 from thecodingmachine/develop
Deploy 2021-02-16
2021-02-16 20:28:39 +01:00
David Négrier ae1d00c828
Merge pull request #736 from MisterErwin/patch-1
Fix offsets of SpeechBubble.ts
2021-02-16 20:02:03 +01:00
David Négrier 43b48ce555
Merge pull request #737 from MisterErwin/patch-2
Do not change rooms if scene failed to load
2021-02-16 20:01:00 +01:00
David Négrier ddcb5ede59
Merge pull request #742 from znerol-forks/fix/huge-jitsi-url
Fix jitsi URLs growing indefinitely in length
2021-02-16 19:52:56 +01:00
David Négrier 9affa36608
Merge pull request #746 from thecodingmachine/turncredentials
[Feature] Connect to a Coturn server using REST API
2021-02-16 19:32:21 +01:00
Kharhamel f00d07289c
Merge pull request #753 from thecodingmachine/fixJitsiConfig
FIX: current audio and video status are overwriten by jitsi config
2021-02-16 19:02:52 +01:00
kharhamel e21868ed3b FIX: current audio and video status are overwriten by jitsi config 2021-02-16 18:25:23 +01:00
David Négrier 6b00355ea8 Fixing WebRtc user name / password when using TURN REST API 2021-02-16 18:13:30 +01:00
David Négrier 7fb923f823 Fixing deploy with new TURN_STATIC_AUTH_SECRET secret 2021-02-16 11:34:12 +01:00
David Négrier 12ef2820bf Refactoring parameters passed to ScreenSharingPeer 2021-02-16 11:08:03 +01:00
David Négrier cdb3cfdc81 [Feature] Connect to a Coturn server using REST API
This allows connecting to a TURN server with temporary passwords.
The passwords are expiring after 4 hours.
2021-02-16 11:06:05 +01:00
znerol 9670f92a08 Allow URLs relative to map base in iframe / openWebsite 2021-02-16 10:53:50 +01:00
David Négrier e07efbdf28
Merge pull request #698 from thecodingmachine/fix/deploy-cleanup
Adding docker-compose.prod.yaml to ease deployment
2021-02-16 10:35:18 +01:00
David Négrier f5610261c0 Fixing deploy URL in Github message 2021-02-16 10:28:00 +01:00
David Négrier b9c839a87d Adding link to the docker-compose prod file in README. 2021-02-16 10:24:51 +01:00
znerol 4fcdad1551 Fix jitsi URLs growing indefinitely in length 2021-02-15 10:36:56 +01:00
Tim Schlüter 05b829da70 fix multi user videochat mode 2021-02-13 21:19:45 +01:00
MisterErwin aff4c8d145
Do not change rooms if scene failed to load
Sometimes a following room fails to load (e.g., #582) and instead of fading to black (as no scene is active) when trying to enter that next room, the player will stay in the old room and an error is logged (as throwing one crashes the game).
2021-02-13 14:15:09 +01:00
MisterErwin 511cd99e22
Fix offsets of SpeechBubble.ts
Since some recent optimizations, they are being added on top of the player, which makes the offset redundant.
2021-02-13 14:04:28 +01:00
Kharhamel cfcad07487
Merge pull request #603 from thecodingmachine/localBlackList
FEATURE: implemented a client side blacklist
2021-02-12 19:23:56 +01:00
kharhamel 0c892e0243 FEATURE: implemented a client side blacklist 2021-02-12 19:15:24 +01:00
grégoire parant f917420ca7
Merge pull request #731 from thecodingmachine/develop
Deploy 2021-02-11 (3)
2021-02-11 21:56:08 +01:00
Gregoire Parant b92b7304b0 Fix to use clear All Keys 2021-02-11 21:48:24 +01:00
David Négrier 0e8be106e5
Merge pull request #729 from thecodingmachine/develop
Deploy 2021-02-11 (2)
2021-02-11 18:09:56 +01:00
David Négrier 64cd07cc39
Merge pull request #726 from thecodingmachine/fixHtmlAnchorElement
Update to use Anchor type
2021-02-11 18:08:52 +01:00
David Négrier bc35ade8df Improving urlify 2021-02-11 18:03:14 +01:00
David Négrier 3eb4d7bb1e
Merge pull request #728 from thecodingmachine/develop
Deploy 2021-02-11
2021-02-11 17:27:32 +01:00
David Négrier c8fc8a2834
Merge pull request #699 from thecodingmachine/pixelartmode
Switching Phaser to pixelart mode
2021-02-11 17:20:20 +01:00
David Négrier a4bddbf4fc
Merge pull request #720 from h42i/fix/metrics
Fix workadventure_nb_clients_per_room in pusher
2021-02-11 17:18:50 +01:00
David Négrier 42aee9f5c8
Merge pull request #722 from thecodingmachine/jitsislugify
Slugifies the Jitsi room name
2021-02-11 17:17:58 +01:00
David Négrier 35fdd3bf29
Merge pull request #717 from kalkspace/add-stun-server-env-variable-with-fallback
Add STUN_SERVER env variable with fallback
2021-02-11 17:17:36 +01:00
Gregoire Parant c5916e7b6d Fix ci 2021-02-11 15:06:12 +01:00
Gregoire Parant 0d104cb307 Update to use Anchor html 2021-02-11 14:49:32 +01:00
David Négrier 91fddd6893 Slugifies the Jitsi room name
This will avoid Jitsi breaking on unexpected characters and displaying a HTTP 404 error.
2021-02-10 13:51:17 +01:00
David Négrier f6e93da48a Fixing loading logo being displayed when nothing was loaded 2021-02-10 12:08:16 +01:00
David Négrier 8389fad877 Disabling video background by default on Jitsi
(because it consumes a lot of CPU)
2021-02-10 11:20:05 +01:00
David Négrier e7ddeedb3e Disabling Join/Leave notifications by default 2021-02-10 11:13:47 +01:00
David Négrier f8b5ca04e5 Adding a test map for Jitsi meet special properties 2021-02-10 11:09:22 +01:00
David Négrier a5bdf68246 Moving property reading inside startJitsi (startJitsi is called at 2 different places) 2021-02-10 11:08:57 +01:00
PizZaKatZe d7b0cb83de Fix workadventure_nb_clients_per_room in pusher 2021-02-10 00:03:42 +01:00
Mickael Chimansky 5cb9624b0b fixup! Jitsi config properties 2021-02-09 20:31:49 +01:00
David Négrier f98b107bb7
Merge pull request #716 from thecodingmachine/loaderfix
Fixing loading logo being displayed when nothing was loaded
2021-02-09 19:00:41 +01:00
Mickael Chimansky 9c4d0aa32f Jitsi config properties 2021-02-09 16:05:21 +01:00
Sebastian Wiesendahl b6807d274b Add STUN_SERVER env variable with fallback
default: "stun:stun.l.google.com:19302"

Make STUN_SERVER available for app config

Change also the STUN_SERVER for ScreenSharingPeer
2021-02-09 13:09:17 +01:00
David Négrier 969c9fd544 Fixing loading logo being displayed when nothing was loaded 2021-02-09 12:41:35 +01:00
David Négrier 10e4f515b3
Merge pull request #704 from thecodingmachine/develop
Deploy 2021-02-04
2021-02-04 16:16:27 +01:00
David Négrier adb535d1b6
Merge pull request #703 from thecodingmachine/socialNetworkLink
Social network link anonymous connexion
2021-02-04 16:10:25 +01:00
Gregoire Parant 03f1f5bf75 Update feedback @moufmouf 2021-02-04 16:05:05 +01:00
grégoire parant b0c49478cd
Merge pull request #702 from thecodingmachine/fixMenuDiscussion
Fix icon discussion
2021-02-03 23:14:47 +01:00
Gregoire Parant 59b4b168e9 Social network link anonymous connexion
In menu, add social newtork link when user is connected in anonymous mode
2021-02-03 23:14:31 +01:00
Gregoire Parant 4de9b8ba40 Fix icon discussion
- Fix size of discussion icon
- Update x and y when page is resize
- Fix blanc link in chat
2021-02-03 23:11:33 +01:00
grégoire parant c41c058fb0
Merge pull request #527 from oliverlorenz/feature/iframe-feature-policy
feat: adds property openWebsitePolicy property to set "allow" property in iframe
2021-02-03 17:38:35 +01:00
David Négrier 9c9f046b16
Merge pull request #668 from piitaya/fix_game_not_centered
Use canvas instead of div container for calculating the game size
2021-02-03 14:06:09 +01:00
David Négrier 246174c365 Removing useless file 2021-02-03 12:29:56 +01:00
David Négrier 4efa308473 Moving docker-compose prod file to contrib/docker 2021-02-03 12:29:41 +01:00
grégoire parant f2b9f6c92a
Merge pull request #686 from thecodingmachine/publicReportedUser
Permit puiblic report
2021-02-02 18:09:25 +01:00
David Négrier 8572959cb0
Merge pull request #690 from thecodingmachine/newLogo
Update Loading page
2021-02-02 17:26:48 +01:00
David Négrier 3f274df813 Redesigning loading bar 2021-02-02 17:17:00 +01:00
David Négrier 6665317f63
Merge pull request #696 from mstock/cowebsite-background-color
Improve display of websites not specifying background color
2021-02-02 12:07:34 +01:00
David Négrier e757d63924 Switching Phaser to pixelart mode
To be honest, I'm not sure what it does exactly (it is supposed to prevent antialiasing and it is rounding coordinates for rendering sprites, but I don't see a difference yet.
2021-02-02 12:04:50 +01:00
David Négrier cc53023586 Using locally built images 2021-02-02 11:34:29 +01:00
David Négrier 274f5eba4c Reworking docker-compose.prod.yaml to make it generic while still having a front container configurable 2021-02-02 11:19:24 +01:00
Manfred Stock 0afbb5412e Improve display of websites not specifying background color
The `index.html` of the front-end specifies `#000` as background color,
however, it seems like various websites found 'in the wild' don't
specify their own background color and thus default to `transparent`. As
a result of this, they tend to become mostly unreadable when displayed
using `openWebsite` since they often assume that they get displayed on a
white background. Setting white as background color on the container
element of the `<iframe>` works around this issue.
2021-02-01 22:48:10 +01:00
Gregoire Parant 788e22e8b0 Fix loading logo of WorkAdventure 2021-02-01 16:52:28 +01:00
David Négrier 686427f6fe Merge branch 'develop' of github.com:thecodingmachine/workadventure into fix/deploy-cleanup
# Conflicts:
#	front/Dockerfile
2021-02-01 14:00:07 +01:00
David Négrier cc1e3bbe0e
Merge pull request #692 from thecodingmachine/develop
Deploy 2021-02-01
2021-02-01 07:16:18 +01:00
Gregoire Parant f99db4856f Add WorkAdventure logo 2021-01-30 20:33:31 +01:00
Gregoire Parant ac2bc76239 change style report button 2021-01-30 14:08:11 +01:00
Gregoire Parant 7eb38fae83 Permit puiblic report
- Create report html
 - Add report flag
2021-01-29 21:09:10 +01:00
David Négrier 3a046cb450 Removing admin from develop branch because of issue with SSL certificate 2021-01-28 23:18:12 +01:00
David Négrier 2f01677849
Merge pull request #681 from thecodingmachine/downgradephaser3.24
Downgraded phaser version to 3.24.1
2021-01-28 23:08:47 +01:00
kharhamel f2c3d6f158 downgraded phaser version to 3.24.1 2021-01-28 23:03:06 +01:00
David Négrier 8d8d0cb1b7
Merge pull request #680 from thecodingmachine/revert-677-phaserDowngrade
Revert "downgraded phaser version to 3.22"
2021-01-28 22:45:45 +01:00
David Négrier ce56a5cba1
Revert "downgraded phaser version to 3.22" 2021-01-28 22:38:20 +01:00
David Négrier 755fc44dd9
Merge pull request #677 from thecodingmachine/phaserDowngrade
downgraded phaser version to 3.22
2021-01-28 22:31:46 +01:00
Paul Bottein a8b5e8599f use HTMLCanvasElement 2021-01-28 21:09:41 +01:00
Paul Bottein 608a9ad347 use type guard 2021-01-28 21:07:17 +01:00
Paul Bottein b52c546a8c Use canvas instead of div container for calculating the game size 2021-01-28 21:04:43 +01:00
kharhamel e7120317c1 downgraded phaser version to 3.22 2021-01-28 19:15:34 +01:00
David Négrier b7b2934106
Merge pull request #672 from Amirault/tam_use_type_guard_on_html_element
Use type guard for html element subtype
2021-01-28 17:13:02 +01:00
Tony Amirault 72799d4f0e added end of line 2021-01-28 15:31:09 +01:00
Tony Amirault 3a05ef6640 package.lock removed 2021-01-28 15:30:20 +01:00
Kharhamel 9849e70613
Merge pull request #670 from thecodingmachine/httpCypress
Removed cypress from the project
2021-01-28 15:03:12 +01:00
kharhamel ca231eee4a remove cypress from the project :( 2021-01-28 14:56:24 +01:00
Kharhamel a6a66c7789
Merge pull request #666 from thecodingmachine/disablepipeline
Removing pipeline related code
2021-01-28 14:53:33 +01:00
Tony Amirault cdbae5fa43 use type guard on html element 2021-01-27 18:33:40 +01:00
David Négrier 8eebed1829 Removing pipeline related code
This code is broken since Phaser 3.50 and we are facing weird issues with MacOS memory leaks and crashes.
Removing this code will remove one potential suspect.
2021-01-27 11:18:07 +01:00
Thomas Alberola 5cd982f114
core(Readme) : Fix App url after installation 2021-01-26 17:19:32 +01:00
grégoire parant 86f1099247
Merge pull request #648 from thecodingmachine/customCharacterLadyLoading
Fix custom character lazy loading
2021-01-26 15:27:46 +01:00
Gregoire Parant 45b355f780 Refactor lazy loading 2021-01-26 15:21:23 +01:00
Kharhamel 556d5a4695
Merge pull request #658 from thecodingmachine/lockUserBanned
Lock user banned
2021-01-26 14:06:35 +01:00
Gregoire Parant 1dd2858424 Fix feadback @Kharhamel 2021-01-26 14:04:42 +01:00
Gregoire Parant e2695c58af Verify existing texture 2021-01-26 09:16:39 +01:00
Gregoire Parant 803be36def Merge branch 'develop' into customCharacterLadyLoading 2021-01-26 09:02:17 +01:00
Gregoire Parant c6903cc4c5 Use Error scene and add banned message 2021-01-26 08:57:10 +01:00
David Négrier 6432d2459b
Merge pull request #659 from thecodingmachine/develop
Deploy 2020-01-25
2021-01-25 22:55:12 +01:00
David Négrier ab0fe63cb2 Adding start room url to develop env 2021-01-25 22:42:44 +01:00
Gregoire Parant 8de05c39c1 Lock user when he was banned 2021-01-25 14:10:16 +01:00
Gregoire Parant beb53df364 Error problem open new map 2021-01-25 13:18:57 +01:00
Gregoire Parant 5f88a1a0b9 Exit trigger action
- [x] Add trigger message custom
- [x] Add trigger for exit map
- [x] Update documentation
2021-01-25 12:21:40 +01:00
Gregoire Parant 232ac6d5d1 Error lazy loading
Update loader custom characters
2021-01-24 18:00:30 +01:00
Gregoire Parant 0133609770 clean 2021-01-24 16:06:52 +01:00
Gregoire Parant 75d8d46c62 clean 2021-01-24 16:02:10 +01:00
Gregoire Parant d93a8da828 Fix custom character lazy loading 2021-01-24 15:57:47 +01:00
David Négrier 2452c96bf1
Merge pull request #620 from thecodingmachine/develop
Deploy 2021-01-18
2021-01-19 21:49:45 +01:00
David Négrier 92246df8b6
Merge pull request #598 from thecodingmachine/develop
master
2021-01-14 18:01:18 +01:00
Piotr Dobrowolski 33b9bd773f *: Dockerfiles cleanup, docker-compose.prod.yaml
New docker-compose.prod.yaml should provide a production-ish deployment
of WorkAdventure
2021-01-12 22:32:37 +01:00
Oliver Lorenz 7c89cf0e47 feat: adds property openWebsitePolicy to set allow property in iframe 2020-12-29 23:17:16 +01:00
122 changed files with 3174 additions and 3133 deletions

2
.dockerignore Normal file
View File

@ -0,0 +1,2 @@
**/node_modules/**
**/Dockerfile

View File

@ -6,3 +6,10 @@ JITSI_ISS=
SECRET_JITSI_KEY=
ADMIN_API_TOKEN=123
START_ROOM_URL=/_/global/maps.workadventure.localhost/Floor0/floor0.json
# If your Turn server is configured to use the Turn REST API, you should put the shared auth secret here.
# If you are using Coturn, this is the value of the "static-auth-secret" parameter in your coturn config file.
# Keep empty if you are sharing hard coded / clear text credentials.
TURN_STATIC_AUTH_SECRET=
# The email address used by Let's encrypt to send renewal warnings (compulsory)
ACME_EMAIL=

View File

@ -150,6 +150,7 @@ jobs:
JITSI_ISS: ${{ secrets.JITSI_ISS }}
JITSI_URL: ${{ secrets.JITSI_URL }}
SECRET_JITSI_KEY: ${{ secrets.SECRET_JITSI_KEY }}
TURN_STATIC_AUTH_SECRET: ${{ secrets.TURN_STATIC_AUTH_SECRET }}
with:
namespace: workadventure-${{ env.GITHUB_REF_SLUG }}
@ -159,34 +160,5 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
msg: Environment deployed at https://${{ env.GITHUB_REF_SLUG }}.workadventure.test.thecodingmachine.com
msg: Environment deployed at https://play.${{ env.GITHUB_REF_SLUG }}.workadventure.test.thecodingmachine.com
check_for_duplicate_msg: true
- name: Run Cypress tests
uses: cypress-io/github-action@v2
if: ${{ env.GITHUB_REF_SLUG != 'master' }}
env:
CYPRESS_BASE_URL: https://play.${{ env.GITHUB_REF_SLUG }}.workadventure.test.thecodingmachine.com
with:
env: host=play.${{ env.GITHUB_REF_SLUG }}.workadventure.test.thecodingmachine.com,port=80
spec: cypress/integration/spec.js
wait-on: https://play.${{ env.GITHUB_REF_SLUG }}.workadventure.test.thecodingmachine.com
working-directory: e2e
- name: Run Cypress tests in prod
uses: cypress-io/github-action@v2
if: ${{ env.GITHUB_REF_SLUG == 'master' }}
env:
CYPRESS_BASE_URL: https://play.workadventu.re
with:
env: host=play.workadventu.re
spec: cypress/integration/spec.js
wait-on: https://workadventu.re
working-directory: e2e
- name: "Upload the screenshot on test failure"
uses: actions/upload-artifact@v1
if: failure()
with:
name: "screenshot"
path: "./e2e/cypress/screenshots/spec.js/WorkAdventureGame -- loads (failed).png"

View File

@ -1,4 +1,4 @@
![](https://github.com/thecodingmachine/workadventure/workflows/Continuous%20Integration/badge.svg)
![](https://github.com/thecodingmachine/workadventure/workflows/Continuous%20Integration/badge.svg) [![Discord](https://img.shields.io/discord/821338762134290432?label=Discord)](https://discord.gg/YGtngdh9gt)
![WorkAdventure landscape image](README-INTRO.jpg)
@ -6,8 +6,6 @@ Demo here : [https://workadventu.re/](https://workadventu.re/).
# Work Adventure
## Work in progress
Work Adventure is a web-based collaborative workspace for small to medium teams (2-100 people) presented in the form of a
16-bit video game.
@ -15,7 +13,7 @@ In Work Adventure, you can move around your office and talk to your colleagues (
triggered when you move next to a colleague).
## Getting started
## Setting up a development environment
Install Docker.
@ -27,13 +25,14 @@ docker-compose up
The environment will start.
You should now be able to browse to http://workadventure.localhost/ and see the application.
You should now be able to browse to http://play.workadventure.localhost/ and see the application.
You can view the dashboard at http://workadventure.localhost:8080/
Note: on some OSes, you will need to add this line to your `/etc/hosts` file:
**/etc/hosts**
```
workadventure.localhost 127.0.0.1
127.0.0.1 workadventure.localhost
```
### MacOS developers, your environment with Vagrant
@ -101,5 +100,7 @@ Vagrant destroy
* `Vagrant halt`: stop your VM Vagrant.
* `Vagrant destroy`: delete your VM Vagrant.
## Features developed
You have more details of features developed in back [README.md](./back/README.md).
## Setting up a production environment
The way you set up your production environment will highly depend on your servers.
We provide a production ready `docker-compose` file that you can use as a good starting point in the [contrib/docker](https://github.com/thecodingmachine/workadventure/tree/master/contrib/docker) directory.

View File

@ -1,16 +1,26 @@
FROM thecodingmachine/workadventure-back-base:latest as builder
WORKDIR /var/www/messages
COPY --chown=docker:docker messages .
# protobuf build
FROM node:14.15.4-buster-slim@sha256:cbae886186467bbfd274b82a234a1cdfbbd31201c2a6ee63a6893eefcf3c6e76 as builder
WORKDIR /usr/src
COPY messages .
RUN yarn install && yarn proto
FROM thecodingmachine/nodejs:12
COPY --chown=docker:docker back .
COPY --from=builder --chown=docker:docker /var/www/messages/generated /usr/src/app/src/Messages/generated
# typescript build
FROM node:14.15.4-buster-slim@sha256:cbae886186467bbfd274b82a234a1cdfbbd31201c2a6ee63a6893eefcf3c6e76 as builder2
WORKDIR /usr/src
COPY back/yarn.lock back/package.json ./
RUN yarn install
COPY back .
COPY --from=builder /usr/src/generated src/Messages/generated
ENV NODE_ENV=production
RUN yarn run tsc
CMD ["yarn", "run", "runprod"]
# final production image
FROM node:14.15.4-buster-slim@sha256:cbae886186467bbfd274b82a234a1cdfbbd31201c2a6ee63a6893eefcf3c6e76
WORKDIR /usr/src
COPY back/yarn.lock back/package.json ./
COPY --from=builder2 /usr/src/dist /usr/src/dist
ENV NODE_ENV=production
RUN yarn install --production
USER node
CMD ["yarn", "run", "runprod"]

View File

@ -1,61 +0,0 @@
# Back Features
## Login
To start your game, you must authenticate on the server back.
When you are authenticated, the back server return token and room starting.
```
POST => /login
Params :
email: email of user.
```
## Join a room
When a user is connected, the user can join a room.
So you must send emit `join-room` with information user:
```
Socket.io => 'join-room'
userId: user id of gamer
roomId: room id when user enter in game
position: {
x: position x on map
y: position y on map
}
```
All data users are stocked on socket client.
## Send position user
When user move on the map, you can share new position on back with event `user-position`.
The information sent:
```
Socket.io => 'user-position'
userId: user id of gamer
roomId: room id when user enter in game
position: {
x: position x on map
y: position y on map
}
```
All data users are updated on socket client.
## Receive positions of all users
The application sends position of all users in each room in every few 10 milliseconds.
The data will pushed on event `user-position`:
```
Socket.io => 'user-position'
[
{
userId: user id of gamer
roomId: room id when user enter in game
position: {
x: position x on map
y: position y on map
}
},
...
]
```
[<<< back](../README.md)

View File

@ -1,5 +1,4 @@
import {HttpRequest, HttpResponse} from "uWebSockets.js";
import {ADMIN_API_TOKEN} from "../Enum/EnvironmentVariable";
import {HttpResponse} from "uWebSockets.js";
export class BaseController {

View File

@ -4,7 +4,6 @@ import {HttpRequest, HttpResponse} from "uWebSockets.js";
import { parse } from 'query-string';
import {App} from "../Server/sifrr.server";
import {socketManager} from "../Services/SocketManager";
import {ServerWritableStream} from "grpc";
export class DebugController {
constructor(private App : App) {

View File

@ -1,10 +1,8 @@
const SECRET_KEY = process.env.SECRET_KEY || "THECODINGMACHINE_SECRET_KEY";
const MINIMUM_DISTANCE = process.env.MINIMUM_DISTANCE ? Number(process.env.MINIMUM_DISTANCE) : 64;
const GROUP_RADIUS = process.env.GROUP_RADIUS ? Number(process.env.GROUP_RADIUS) : 48;
const ALLOW_ARTILLERY = process.env.ALLOW_ARTILLERY ? process.env.ALLOW_ARTILLERY == 'true' : false;
const ADMIN_API_URL = process.env.ADMIN_API_URL || '';
const ADMIN_API_TOKEN = process.env.ADMIN_API_TOKEN || 'myapitoken';
const MAX_USERS_PER_ROOM = parseInt(process.env.MAX_USERS_PER_ROOM || '') || 600;
const CPU_OVERHEAT_THRESHOLD = Number(process.env.CPU_OVERHEAT_THRESHOLD) || 80;
const JITSI_URL : string|undefined = (process.env.JITSI_URL === '') ? undefined : process.env.JITSI_URL;
const JITSI_ISS = process.env.JITSI_ISS || '';
@ -12,15 +10,14 @@ const SECRET_JITSI_KEY = process.env.SECRET_JITSI_KEY || '';
const HTTP_PORT = parseInt(process.env.HTTP_PORT || '8080') || 8080;
const GRPC_PORT = parseInt(process.env.GRPC_PORT || '50051') || 50051;
export const SOCKET_IDLE_TIMER = parseInt(process.env.SOCKET_IDLE_TIMER as string) || 30; // maximum time (in second) without activity before a socket is closed
export const TURN_STATIC_AUTH_SECRET = process.env.TURN_STATIC_AUTH_SECRET || '';
export {
SECRET_KEY,
MINIMUM_DISTANCE,
ADMIN_API_URL,
ADMIN_API_TOKEN,
HTTP_PORT,
GRPC_PORT,
MAX_USERS_PER_ROOM,
GROUP_RADIUS,
ALLOW_ARTILLERY,
CPU_OVERHEAT_THRESHOLD,

View File

@ -7,7 +7,6 @@ import {PositionNotifier} from "./PositionNotifier";
import {Movable} from "_Model/Movable";
import {extractDataFromPrivateRoomId, extractRoomSlugPublicRoomId, isRoomAnonymous} from "./RoomIdentifier";
import {arrayIntersect} from "../Services/ArrayHelper";
import {MAX_USERS_PER_ROOM} from "../Enum/EnvironmentVariable";
import {JoinRoomMessage} from "../Messages/generated/messages_pb";
import {ProtobufUtils} from "../Model/Websocket/ProtobufUtils";
import {ZoneSocket} from "src/RoomManager";
@ -116,8 +115,6 @@ export class GameRoom {
this.nextUserId++;
this.users.set(user.id, user);
this.usersByUuid.set(user.uuid, user);
// Let's call update position to trigger the join / leave room
//this.updatePosition(socket, userPosition);
this.updateUserGroup(user);
// Notify admins
@ -149,10 +146,6 @@ export class GameRoom {
}
}
get isFull(): boolean {
return this.users.size >= MAX_USERS_PER_ROOM;
}
public isEmpty(): boolean {
return this.users.size === 0 && this.admins.size === 0;
}

View File

@ -3,6 +3,7 @@ import {
AdminGlobalMessage,
AdminMessage,
AdminPusherToBackMessage,
AdminRoomMessage,
BanMessage,
EmptyMessage,
ItemEventMessage,
@ -14,7 +15,7 @@ import {
ServerToClientMessage,
SilentMessage,
UserMovesMessage,
WebRtcSignalToServerMessage,
WebRtcSignalToServerMessage, WorldFullWarningToRoomMessage,
ZoneMessage
} from "./Messages/generated/messages_pb";
import {sendUnaryData, ServerDuplexStream, ServerUnaryCall, ServerWritableStream} from "grpc";
@ -42,8 +43,13 @@ const roomManager: IRoomManagerServer = {
if (room === null || user === null) {
if (message.hasJoinroommessage()) {
socketManager.handleJoinRoom(call, message.getJoinroommessage() as JoinRoomMessage).then(({room: gameRoom, user: myUser}) => {
room = gameRoom;
user = myUser;
if (call.writable) {
room = gameRoom;
user = myUser;
} else {
//Connexion may have been closed before the init was finished, so we have to manually disconnect the user.
socketManager.leaveRoom(gameRoom, myUser);
}
});
} else {
throw new Error('The first message sent MUST be of type JoinRoomMessage');
@ -51,12 +57,8 @@ const roomManager: IRoomManagerServer = {
} else {
if (message.hasJoinroommessage()) {
throw new Error('Cannot call JoinRoomMessage twice!');
/*} else if (message.hasViewportmessage()) {
socketManager.handleViewport(client, message.getViewportmessage() as ViewportMessage);*/
} else if (message.hasUsermovesmessage()) {
socketManager.handleUserMovesMessage(room, user, message.getUsermovesmessage() as UserMovesMessage);
/*} else if (message.hasSetplayerdetailsmessage()) {
socketManager.handleSetPlayerDetails(client, message.getSetplayerdetailsmessage() as SetPlayerDetailsMessage);*/
} else if (message.hasSilentmessage()) {
socketManager.handleSilentMessage(room, user, message.getSilentmessage() as SilentMessage);
} else if (message.hasItemeventmessage()) {
@ -67,8 +69,6 @@ const roomManager: IRoomManagerServer = {
socketManager.emitScreenSharing(room, user, message.getWebrtcscreensharingsignaltoservermessage() as WebRtcSignalToServerMessage);
} else if (message.hasPlayglobalmessage()) {
socketManager.emitPlayGlobalMessage(room, message.getPlayglobalmessage() as PlayGlobalMessage);
/*} else if (message.hasReportplayermessage()){
socketManager.handleReportMessage(client, message.getReportplayermessage() as ReportPlayerMessage);*/
} else if (message.hasQueryjitsijwtmessage()){
socketManager.handleQueryJitsiJwtMessage(user, message.getQueryjitsijwtmessage() as QueryJitsiJwtMessage);
}else if (message.hasSendusermessage()) {
@ -119,10 +119,7 @@ const roomManager: IRoomManagerServer = {
socketManager.removeZoneListener(call, zoneMessage.getRoomid(), zoneMessage.getX(), zoneMessage.getY());
call.end();
})
/*call.on('finish', () => {
debug('listenZone finish');
})*/
call.on('close', () => {
debug('listenZone connection closed');
socketManager.removeZoneListener(call, zoneMessage.getRoomid(), zoneMessage.getX(), zoneMessage.getY());
@ -150,26 +147,6 @@ const roomManager: IRoomManagerServer = {
} else {
throw new Error('The first message sent MUST be of type JoinRoomMessage');
}
} else {
/*if (message.hasJoinroommessage()) {
throw new Error('Cannot call JoinRoomMessage twice!');
} else if (message.hasUsermovesmessage()) {
socketManager.handleUserMovesMessage(room, user, message.getUsermovesmessage() as UserMovesMessage);
} else if (message.hasSilentmessage()) {
socketManager.handleSilentMessage(room, user, message.getSilentmessage() as SilentMessage);
} else if (message.hasItemeventmessage()) {
socketManager.handleItemEvent(room, user, message.getItemeventmessage() as ItemEventMessage);
} else if (message.hasWebrtcsignaltoservermessage()) {
socketManager.emitVideo(room, user, message.getWebrtcsignaltoservermessage() as WebRtcSignalToServerMessage);
} else if (message.hasWebrtcscreensharingsignaltoservermessage()) {
socketManager.emitScreenSharing(room, user, message.getWebrtcscreensharingsignaltoservermessage() as WebRtcSignalToServerMessage);
} else if (message.hasPlayglobalmessage()) {
socketManager.emitPlayGlobalMessage(room, message.getPlayglobalmessage() as PlayGlobalMessage);
} else if (message.hasQueryjitsijwtmessage()){
socketManager.handleQueryJitsiJwtMessage(user, message.getQueryjitsijwtmessage() as QueryJitsiJwtMessage);
} else {
throw new Error('Unhandled message type');
}*/
}
} catch (e) {
emitError(call, e);
@ -204,10 +181,18 @@ const roomManager: IRoomManagerServer = {
},
ban(call: ServerUnaryCall<BanMessage>, callback: sendUnaryData<EmptyMessage>): void {
// FIXME Work in progress
socketManager.banUser(call.request.getRoomid(), call.request.getRecipientuuid(), 'foo bar TODO change this');
socketManager.banUser(call.request.getRoomid(), call.request.getRecipientuuid(), call.request.getMessage());
callback(null, new EmptyMessage());
},
sendAdminMessageToRoom(call: ServerUnaryCall<AdminRoomMessage>, callback: sendUnaryData<EmptyMessage>): void {
socketManager.sendAdminRoomMessage(call.request.getRoomid(), call.request.getMessage());
callback(null, new EmptyMessage());
},
sendWorldFullWarningToRoom(call: ServerUnaryCall<WorldFullWarningToRoomMessage>, callback: sendUnaryData<EmptyMessage>): void {
socketManager.dispatchWorlFullWarning(call.request.getRoomid());
callback(null, new EmptyMessage());
},
};
export {roomManager};

View File

@ -1,6 +1,5 @@
import {ADMIN_API_TOKEN, ADMIN_API_URL} from "../Enum/EnvironmentVariable";
import Axios from "axios";
import {v4} from "uuid";
export interface AdminApiData {
organizationSlug: string
@ -21,13 +20,6 @@ export interface CharacterTexture {
rights: string
}
export interface FetchMemberDataByUuidResponse {
uuid: string;
tags: string[];
textures: CharacterTexture[];
messages: unknown[];
}
class AdminApi {
async fetchMapDetails(organizationSlug: string, worldSlug: string, roomSlug: string|undefined): Promise<AdminApiData> {
@ -52,64 +44,6 @@ class AdminApi {
)
return res.data;
}
async fetchMemberDataByUuid(uuid: string): Promise<FetchMemberDataByUuidResponse> {
if (!ADMIN_API_URL) {
return Promise.reject('No admin backoffice set!');
}
try {
const res = await Axios.get(ADMIN_API_URL+'/api/membership/'+uuid,
{ headers: {"Authorization" : `${ADMIN_API_TOKEN}`} }
)
return res.data;
} catch (e) {
if (e?.response?.status == 404) {
// If we get an HTTP 404, the token is invalid. Let's perform an anonymous login!
console.warn('Cannot find user with uuid "'+uuid+'". Performing an anonymous login instead.');
return {
uuid: v4(),
tags: [],
textures: [],
messages: [],
}
} else {
throw e;
}
}
}
async fetchMemberDataByToken(organizationMemberToken: string): Promise<AdminApiData> {
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.
const res = await Axios.get(ADMIN_API_URL+'/api/login-url/'+organizationMemberToken,
{ headers: {"Authorization" : `${ADMIN_API_TOKEN}`} }
)
return res.data;
}
async fetchCheckUserByToken(organizationMemberToken: string): Promise<AdminApiData> {
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.
const res = await Axios.get(ADMIN_API_URL+'/api/check-user/'+organizationMemberToken,
{ headers: {"Authorization" : `${ADMIN_API_TOKEN}`} }
)
return res.data;
}
reportPlayer(reportedUserUuid: string, reportedUserComment: string, reporterUserUuid: string) {
return Axios.post(`${ADMIN_API_URL}/api/report`, {
reportedUserUuid,
reportedUserComment,
reporterUserUuid,
},
{
headers: {"Authorization": `${ADMIN_API_TOKEN}`}
});
}
}
export const adminApi = new AdminApi();

View File

@ -1,5 +1,4 @@
import {GameRoom} from "../Model/GameRoom";
import {CharacterLayer} from "_Model/Websocket/CharacterLayer";
import {
ItemEventMessage,
ItemStateMessage,
@ -22,13 +21,24 @@ import {
Zone as ProtoZone,
BatchToPusherMessage,
SubToPusherMessage,
UserJoinedZoneMessage, GroupUpdateZoneMessage, GroupLeftZoneMessage, UserLeftZoneMessage, BanUserMessage
UserJoinedZoneMessage,
GroupUpdateZoneMessage,
GroupLeftZoneMessage,
WorldFullWarningMessage,
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 {GROUP_RADIUS, JITSI_ISS, MINIMUM_DISTANCE, SECRET_JITSI_KEY} from "../Enum/EnvironmentVariable";
import {
GROUP_RADIUS,
JITSI_ISS,
MINIMUM_DISTANCE,
SECRET_JITSI_KEY,
TURN_STATIC_AUTH_SECRET
} from "../Enum/EnvironmentVariable";
import {Movable} from "../Model/Movable";
import {PositionInterface} from "../Model/PositionInterface";
import {adminApi, CharacterTexture} from "./AdminApi";
@ -40,25 +50,16 @@ import {ZoneSocket} from "../RoomManager";
import {Zone} from "_Model/Zone";
import Debug from "debug";
import {Admin} from "_Model/Admin";
import crypto from "crypto";
const debug = Debug('sockermanager');
interface AdminSocketRoomsList {
[index: string]: number;
}
interface AdminSocketUsersList {
[index: string]: boolean;
}
export interface AdminSocketData {
rooms: AdminSocketRoomsList,
users: AdminSocketUsersList,
}
function emitZoneMessage(subMessage: SubToPusherMessage, socket: ZoneSocket): void {
// TODO: should we batch those every 100ms?
const batchMessage = new BatchToPusherMessage();
batchMessage.addPayload(subMessage);
socket.write(batchMessage);
}
@ -75,68 +76,20 @@ export class SocketManager {
});
}
/*getAdminSocketDataFor(roomId:string): AdminSocketData {
const data:AdminSocketData = {
rooms: {},
users: {},
}
const room = this.rooms.get(roomId);
if (room === undefined) {
return data;
}
const users = room.getUsers();
data.rooms[roomId] = users.size;
users.forEach(user => {
data.users[user.uuid] = true
})
return data;
}*/
public async handleJoinRoom(socket: UserSocket, joinRoomMessage: JoinRoomMessage): Promise<{ room: GameRoom; user: User }> {
/*const positionMessage = joinRoomMessage.getPositionmessage();
if (positionMessage === undefined) {
// TODO: send error message?
throw new Error('Empty pointMessage found in JoinRoomMessage');
}*/
//const position = ProtobufUtils.toPointInterface(positionMessage);
//const viewport = client.viewport;
//this.sockets.set(client.userId, client); //todo: should this be at the end of the function?
//join new previous room
const {room, user} = await this.joinRoom(socket, joinRoomMessage);
//const things = room.setViewport(client, viewport);
if (!socket.writable) {
console.warn('Socket was aborted');
return {
room,
user
};
}
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);
if (player === undefined) {
console.warn('Something went wrong. The World contains a user "'+thing.id+"' but this user does not exist in the sockets list!");
continue;
}
const userJoinedMessage = new UserJoinedMessage();
userJoinedMessage.setUserid(thing.id);
userJoinedMessage.setName(player.name);
userJoinedMessage.setCharacterlayersList(ProtobufUtils.toCharacterLayerMessages(player.characterLayers));
userJoinedMessage.setPosition(ProtobufUtils.toPositionMessage(player.position));
roomJoinedMessage.addUser(userJoinedMessage);
roomJoinedMessage.setTagList(joinRoomMessage.getTagList());
} else if (thing instanceof Group) {
const groupUpdateMessage = new GroupUpdateMessage();
groupUpdateMessage.setGroupid(thing.getId());
groupUpdateMessage.setPosition(ProtobufUtils.toPointMessage(thing.getPosition()));
roomJoinedMessage.addGroup(groupUpdateMessage);
} else {
console.error("Unexpected type for Movable returned by setViewport");
}
}*/
for (const [itemId, item] of room.getItemsState().entries()) {
const itemStateMessage = new ItemStateMessage();
@ -150,9 +103,6 @@ export class SocketManager {
const serverToClientMessage = new ServerToClientMessage();
serverToClientMessage.setRoomjoinedmessage(roomJoinedMessage);
//user.socket.write(serverToClientMessage);
console.log('SENDING MESSAGE roomJoinedMessage');
socket.write(serverToClientMessage);
return {
@ -160,13 +110,6 @@ export class SocketManager {
user
};
/*const serverToClientMessage = new ServerToClientMessage();
serverToClientMessage.setRoomjoinedmessage(roomJoinedMessage);
if (!client.disconnecting) {
client.send(serverToClientMessage.serializeBinary().buffer, true);
}*/
}
handleUserMovesMessage(room: GameRoom, user: User, userMovesMessage: UserMovesMessage) {
@ -275,6 +218,12 @@ export class SocketManager {
const webrtcSignalToClient = new WebRtcSignalToClientMessage();
webrtcSignalToClient.setUserid(user.id);
webrtcSignalToClient.setSignal(data.getSignal());
// TODO: only compute credentials if data.signal.type === "offer"
if (TURN_STATIC_AUTH_SECRET !== '') {
const {username, password} = this.getTURNCredentials(''+user.id, TURN_STATIC_AUTH_SECRET);
webrtcSignalToClient.setWebrtcusername(username);
webrtcSignalToClient.setWebrtcpassword(password);
}
const serverToClientMessage = new ServerToClientMessage();
serverToClientMessage.setWebrtcsignaltoclientmessage(webrtcSignalToClient);
@ -295,6 +244,12 @@ export class SocketManager {
const webrtcSignalToClient = new WebRtcSignalToClientMessage();
webrtcSignalToClient.setUserid(user.id);
webrtcSignalToClient.setSignal(data.getSignal());
// TODO: only compute credentials if data.signal.type === "offer"
if (TURN_STATIC_AUTH_SECRET !== '') {
const {username, password} = this.getTURNCredentials(''+user.id, TURN_STATIC_AUTH_SECRET);
webrtcSignalToClient.setWebrtcusername(username);
webrtcSignalToClient.setWebrtcpassword(password);
}
const serverToClientMessage = new ServerToClientMessage();
serverToClientMessage.setWebrtcscreensharingsignaltoclientmessage(webrtcSignalToClient);
@ -315,8 +270,6 @@ export class SocketManager {
debug('Room is empty. Deleting room "%s"', room.roomId);
}
} finally {
//delete Client.roomId;
//this.sockets.delete(Client.userId);
clientEventsEmitter.emitClientLeave(user.uuid, room.roomId);
console.log('A user left');
}
@ -351,20 +304,14 @@ export class SocketManager {
const roomId = joinRoomMessage.getRoomid();
const world = await socketManager.getOrCreateRoom(roomId);
// Dispatch groups position to newly connected user
/*world.getGroups().forEach((group: Group) => {
this.emitCreateUpdateGroupEvent(socket, group);
});*/
const room = await socketManager.getOrCreateRoom(roomId);
//join world
const user = world.join(socket, joinRoomMessage);
const user = room.join(socket, joinRoomMessage);
clientEventsEmitter.emitClientJoin(user.uuid, roomId);
//console.log(new Date().toISOString() + ' A user joined (', this.sockets.size, ' connected users)');
console.log(new Date().toISOString() + ' A user joined');
return {room: world, user};
return {room, user};
}
private onZoneEnter(thing: Movable, fromZone: Zone|null, listener: ZoneSocket) {
@ -472,10 +419,6 @@ export class SocketManager {
}
private joinWebRtcRoom(user: User, group: Group) {
/*const roomId: string = "webrtcroom"+group.getId();
if (user.socket.webRtcRoomId === roomId) {
return;
}*/
for (const otherUser of group.getUsers()) {
if (user === otherUser) {
@ -487,6 +430,11 @@ export class SocketManager {
webrtcStartMessage1.setUserid(otherUser.id);
webrtcStartMessage1.setName(otherUser.name);
webrtcStartMessage1.setInitiator(true);
if (TURN_STATIC_AUTH_SECRET !== '') {
const {username, password} = this.getTURNCredentials(''+otherUser.id, TURN_STATIC_AUTH_SECRET);
webrtcStartMessage1.setWebrtcusername(username);
webrtcStartMessage1.setWebrtcpassword(password);
}
const serverToClientMessage1 = new ServerToClientMessage();
serverToClientMessage1.setWebrtcstartmessage(webrtcStartMessage1);
@ -500,6 +448,11 @@ export class SocketManager {
webrtcStartMessage2.setUserid(user.id);
webrtcStartMessage2.setName(user.name);
webrtcStartMessage2.setInitiator(false);
if (TURN_STATIC_AUTH_SECRET !== '') {
const {username, password} = this.getTURNCredentials(''+user.id, TURN_STATIC_AUTH_SECRET);
webrtcStartMessage2.setWebrtcusername(username);
webrtcStartMessage2.setWebrtcpassword(password);
}
const serverToClientMessage2 = new ServerToClientMessage();
serverToClientMessage2.setWebrtcstartmessage(webrtcStartMessage2);
@ -512,6 +465,25 @@ export class SocketManager {
}
}
/**
* Computes a unique user/password for the TURN server, using a shared secret between the WorkAdventure API server
* and the Coturn server.
* The Coturn server should be initialized with parameters: `--use-auth-secret --static-auth-secret=MySecretKey`
*/
private getTURNCredentials(name: string, secret: string): {username: string, password: string} {
const unixTimeStamp = Math.floor(Date.now()/1000) + 4*3600; // this credential would be valid for the next 4 hours
const username = [unixTimeStamp, name].join(':');
const hmac = crypto.createHmac('sha1', secret);
hmac.setEncoding('base64');
hmac.write(username);
hmac.end();
const password = hmac.read();
return {
username: username,
password: password
};
}
//disconnect user
private disConnectedUser(user: User, group: Group) {
// Most of the time, sending a disconnect event to one of the players is enough (the player will close the connection
@ -644,33 +616,6 @@ export class SocketManager {
}, 10000);
}
/**
* Merges the characterLayers received from the front (as an array of string) with the custom textures from the back.
*/
static mergeCharacterLayersAndCustomTextures(characterLayers: string[], memberTextures: CharacterTexture[]): CharacterLayer[] {
const characterLayerObjs: CharacterLayer[] = [];
for (const characterLayer of characterLayers) {
if (characterLayer.startsWith('customCharacterTexture')) {
const customCharacterLayerId: number = +characterLayer.substr(22);
for (const memberTexture of memberTextures) {
if (memberTexture.id == customCharacterLayerId) {
characterLayerObjs.push({
name: characterLayer,
url: memberTexture.url
})
break;
}
}
} else {
characterLayerObjs.push({
name: characterLayer,
url: undefined
})
}
}
return characterLayerObjs;
}
public addZoneListener(call: ZoneSocket, roomId: string, x: number, y: number): void {
const room = this.rooms.get(roomId);
if (!room) {
@ -724,11 +669,6 @@ export class SocketManager {
public async handleJoinAdminRoom(admin: Admin, roomId: string): Promise<GameRoom> {
const room = await socketManager.getOrCreateRoom(roomId);
// Dispatch groups position to newly connected user
/*world.getGroups().forEach((group: Group) => {
this.emitCreateUpdateGroupEvent(socket, group);
});*/
room.adminJoin(admin);
return room;
@ -746,7 +686,7 @@ export class SocketManager {
public sendAdminMessage(roomId: string, recipientUuid: string, message: string): void {
const room = this.rooms.get(roomId);
if (!room) {
console.error("In sendAdminMessage, could not find room with id '" + roomId + "'. Maybe the room was closed a few milliseconds ago and there was a race condition?");
console.error("In sendAdminMessage, could not find room with id '" + roomId + "'. Maybe the room was closed a few milliseconds ago and there was a race condition?");
return;
}
@ -758,12 +698,12 @@ export class SocketManager {
const sendUserMessage = new SendUserMessage();
sendUserMessage.setMessage(message);
sendUserMessage.setType('ban');
sendUserMessage.setType('ban'); //todo: is the type correct?
const subToPusherMessage = new SubToPusherMessage();
subToPusherMessage.setSendusermessage(sendUserMessage);
const serverToClientMessage = new ServerToClientMessage();
serverToClientMessage.setSendusermessage(sendUserMessage);
recipient.socket.write(subToPusherMessage);
recipient.socket.write(serverToClientMessage);
}
public banUser(roomId: string, recipientUuid: string, message: string): void {
@ -782,18 +722,56 @@ export class SocketManager {
// Let's leave the room now.
room.leave(recipient);
const sendUserMessage = new SendUserMessage();
sendUserMessage.setMessage(message);
sendUserMessage.setType('banned');
const banUserMessage = new BanUserMessage();
banUserMessage.setMessage(message);
banUserMessage.setType('banned');
const subToPusherMessage = new SubToPusherMessage();
subToPusherMessage.setSendusermessage(sendUserMessage);
recipient.socket.write(subToPusherMessage);
const serverToClientMessage = new ServerToClientMessage();
serverToClientMessage.setBanusermessage(banUserMessage);
// Let's close the connection when the user is banned.
recipient.socket.write(serverToClientMessage);
recipient.socket.end();
}
sendAdminRoomMessage(roomId: string, message: string) {
const room = this.rooms.get(roomId);
if (!room) {
//todo: this should cause the http call to return a 500
console.error("In sendAdminRoomMessage, could not find room with id '" + roomId + "'. Maybe the room was closed a few milliseconds ago and there was a race condition?");
return;
}
room.getUsers().forEach((recipient) => {
const sendUserMessage = new SendUserMessage();
sendUserMessage.setMessage(message);
sendUserMessage.setType('message');
const clientMessage = new ServerToClientMessage();
clientMessage.setSendusermessage(sendUserMessage);
recipient.socket.write(clientMessage);
});
}
dispatchWorlFullWarning(roomId: string,): void {
const room = this.rooms.get(roomId);
if (!room) {
//todo: this should cause the http call to return a 500
console.error("In sendAdminRoomMessage, could not find room with id '" + roomId + "'. Maybe the room was closed a few milliseconds ago and there was a race condition?");
return;
}
room.getUsers().forEach((recipient) => {
const worldFullMessage = new WorldFullWarningMessage();
const clientMessage = new ServerToClientMessage();
clientMessage.setWorldfullwarningmessage(worldFullMessage);
recipient.socket.write(clientMessage);
});
}
}
export const socketManager = new SocketManager();

View File

@ -0,0 +1,20 @@
# The base domain
DOMAIN=workadventure.localhost
DEBUG_MODE=false
JITSI_URL=meet.jit.si
# If your Jitsi environment has authentication set up, you MUST set JITSI_PRIVATE_MODE to "true" and you MUST pass a SECRET_JITSI_KEY to generate the JWT secret
JITSI_PRIVATE_MODE=false
JITSI_ISS=
SECRET_JITSI_KEY=
# URL of the TURN server (needed to "punch a hole" through some networks for P2P connections)
TURN_SERVER=
TURN_USER=
TURN_PASSWORD=
# The URL used by default, in the form: "/_/global/map/url.json"
START_ROOM_URL=/_/global/maps.workadventu.re/Floor0/floor0.json
# The email address used by Let's encrypt to send renewal warnings (compulsory)
ACME_EMAIL=

View File

@ -0,0 +1,100 @@
version: "3.3"
services:
reverse-proxy:
image: traefik:v2.3
command:
- --log.level=WARN
#- --api.insecure=true
- --providers.docker
- --entryPoints.web.address=:80
- --entrypoints.web.http.redirections.entryPoint.to=websecure
- --entrypoints.web.http.redirections.entryPoint.scheme=https
- --entryPoints.websecure.address=:443
- --certificatesresolvers.myresolver.acme.email=${ACME_EMAIL}
- --certificatesresolvers.myresolver.acme.storage=/acme.json
# used during the challenge
- --certificatesresolvers.myresolver.acme.httpchallenge.entrypoint=web
ports:
- "80:80"
- "443:443"
# The Web UI (enabled by --api.insecure=true)
#- "8080:8080"
depends_on:
- pusher
- front
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- ./acme.json:/acme.json
restart: unless-stopped
front:
build:
context: ../..
dockerfile: front/Dockerfile
#image: thecodingmachine/workadventure-front:master
environment:
DEBUG_MODE: "$DEBUG_MODE"
JITSI_URL: $JITSI_URL
JITSI_PRIVATE_MODE: "$JITSI_PRIVATE_MODE"
API_URL: pusher.${DOMAIN}
TURN_SERVER: "${TURN_SERVER}"
TURN_USER: "${TURN_USER}"
TURN_PASSWORD: "${TURN_PASSWORD}"
START_ROOM_URL: "${START_ROOM_URL}"
labels:
- "traefik.http.routers.front.rule=Host(`play.${DOMAIN}`)"
- "traefik.http.routers.front.entryPoints=web,traefik"
- "traefik.http.services.front.loadbalancer.server.port=80"
- "traefik.http.routers.front-ssl.rule=Host(`play.${DOMAIN}`)"
- "traefik.http.routers.front-ssl.entryPoints=websecure"
- "traefik.http.routers.front-ssl.tls=true"
- "traefik.http.routers.front-ssl.service=front"
- "traefik.http.routers.front-ssl.tls.certresolver=myresolver"
restart: unless-stopped
pusher:
build:
context: ../..
dockerfile: pusher/Dockerfile
#image: thecodingmachine/workadventure-pusher:master
command: yarn run runprod
environment:
SECRET_JITSI_KEY: "$SECRET_JITSI_KEY"
SECRET_KEY: yourSecretKey
API_URL: back:50051
JITSI_URL: $JITSI_URL
JITSI_ISS: $JITSI_ISS
labels:
- "traefik.http.routers.pusher.rule=Host(`pusher.${DOMAIN}`)"
- "traefik.http.routers.pusher.entryPoints=web,traefik"
- "traefik.http.services.pusher.loadbalancer.server.port=8080"
- "traefik.http.routers.pusher-ssl.rule=Host(`pusher.${DOMAIN}`)"
- "traefik.http.routers.pusher-ssl.entryPoints=websecure"
- "traefik.http.routers.pusher-ssl.tls=true"
- "traefik.http.routers.pusher-ssl.service=pusher"
- "traefik.http.routers.pusher-ssl.tls.certresolver=myresolver"
restart: unless-stopped
back:
build:
context: ../..
dockerfile: back/Dockerfile
#image: thecodingmachine/workadventure-back:master
command: yarn run runprod
environment:
SECRET_JITSI_KEY: "$SECRET_JITSI_KEY"
ADMIN_API_TOKEN: "$ADMIN_API_TOKEN"
ADMIN_API_URL: "$ADMIN_API_URL"
JITSI_URL: $JITSI_URL
JITSI_ISS: $JITSI_ISS
labels:
- "traefik.http.routers.back.rule=Host(`api.${DOMAIN}`)"
- "traefik.http.routers.back.entryPoints=web"
- "traefik.http.services.back.loadbalancer.server.port=8080"
- "traefik.http.routers.back-ssl.rule=Host(`api.${DOMAIN}`)"
- "traefik.http.routers.back-ssl.entryPoints=websecure"
- "traefik.http.routers.back-ssl.tls=true"
- "traefik.http.routers.back-ssl.service=back"
- "traefik.http.routers.back-ssl.tls.certresolver=myresolver"
restart: unless-stopped

View File

@ -3,6 +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",
// develop branch does not use admin because of issue with SSL certificate of admin as of now.
local adminUrl = if namespace == "master" || namespace == "develop" || std.startsWith(namespace, "admin") then "https://"+url else null,
"$schema": "https://raw.githubusercontent.com/thecodingmachine/deeployer/master/deeployer.schema.json",
"version": "1.0",
@ -21,9 +22,13 @@
"JITSI_ISS": env.JITSI_ISS,
"JITSI_URL": env.JITSI_URL,
"SECRET_JITSI_KEY": env.SECRET_JITSI_KEY,
} + if adminUrl != null then {
"TURN_STATIC_AUTH_SECRET": env.TURN_STATIC_AUTH_SECRET,
} + (if adminUrl != null then {
"ADMIN_API_URL": adminUrl,
} else {}
} else {}) + if namespace != "master" then {
// Absolutely ugly WorkAround to circumvent broken certificates on the K8S test cluster. Don't do this in production kids!
"NODE_TLS_REJECT_UNAUTHORIZED": "0"
}
},
"back2": {
"image": "thecodingmachine/workadventure-back:"+tag,
@ -39,9 +44,13 @@
"JITSI_ISS": env.JITSI_ISS,
"JITSI_URL": env.JITSI_URL,
"SECRET_JITSI_KEY": env.SECRET_JITSI_KEY,
} + if adminUrl != null then {
"TURN_STATIC_AUTH_SECRET": env.TURN_STATIC_AUTH_SECRET,
} + (if adminUrl != null then {
"ADMIN_API_URL": adminUrl,
} else {}
} else {}) + if namespace != "master" then {
// Absolutely ugly WorkAround to circumvent broken certificates on the K8S test cluster. Don't do this in production kids!
"NODE_TLS_REJECT_UNAUTHORIZED": "0"
}
},
"pusher": {
"replicas": 2,
@ -58,9 +67,12 @@
"JITSI_URL": env.JITSI_URL,
"API_URL": "back1:50051,back2:50051",
"SECRET_JITSI_KEY": env.SECRET_JITSI_KEY,
} + if adminUrl != null then {
} + (if adminUrl != null then {
"ADMIN_API_URL": adminUrl,
} else {}
} else {}) + if namespace != "master" then {
// Absolutely ugly WorkAround to circumvent broken certificates on the K8S test cluster. Don't do this in production kids!
"NODE_TLS_REJECT_UNAUTHORIZED": "0"
}
},
"front": {
"image": "thecodingmachine/workadventure-front:"+tag,
@ -79,6 +91,7 @@
"TURN_USER": "workadventure",
"TURN_PASSWORD": "WorkAdventure123",
"JITSI_PRIVATE_MODE": if env.SECRET_JITSI_KEY != '' then "true" else "false",
"START_ROOM_URL": "/_/global/maps."+url+"/Floor0/floor0.json"
//"GA_TRACKING_ID": "UA-10196481-11"
}
},

View File

@ -1,20 +0,0 @@
version: '3'
services:
wait_app:
image: dadarek/wait-for-dependencies
depends_on:
- reverse-proxy
command: front:8080
cypress:
# the Docker image to use from https://github.com/cypress-io/cypress-docker-images
image: "cypress/included:3.8.3"
depends_on:
- reverse-proxy
environment:
# pass base url to test pointing at the web application
- CYPRESS_baseUrl=http://front:8080
working_dir: /e2e
volumes:
- ./e2e/:/e2e

View File

@ -31,9 +31,12 @@ services:
ADMIN_URL: workadventure.localhost
STARTUP_COMMAND_1: ./templater.sh
STARTUP_COMMAND_2: yarn install
TURN_SERVER: "turn:coturn.workadventu.re:443,turns:coturn.workadventu.re:443"
TURN_USER: workadventure
TURN_PASSWORD: WorkAdventure123
STUN_SERVER: "stun:stun.l.google.com:19302"
TURN_SERVER: "turn:coturn.workadventure.localhost:3478,turns:coturn.workadventure.localhost:5349"
# Use TURN_USER/TURN_PASSWORD if your Coturn server is secured via hard coded credentials.
# Advice: you should instead use Coturn REST API along the TURN_STATIC_AUTH_SECRET in the Back container
TURN_USER: ""
TURN_PASSWORD: ""
START_ROOM_URL: "$START_ROOM_URL"
command: yarn run start
volumes:
@ -50,10 +53,8 @@ services:
pusher:
image: thecodingmachine/nodejs:12
command: yarn dev
#command: yarn run prod
#command: yarn run profile
environment:
DEBUG: "*"
DEBUG: "socket:*"
STARTUP_COMMAND_1: yarn install
SECRET_JITSI_KEY: "$SECRET_JITSI_KEY"
SECRET_KEY: yourSecretKey
@ -108,6 +109,7 @@ services:
ADMIN_API_TOKEN: "$ADMIN_API_TOKEN"
JITSI_URL: $JITSI_URL
JITSI_ISS: $JITSI_ISS
TURN_STATIC_AUTH_SECRET: SomeStaticAuthSecret
volumes:
- ./back:/usr/src/app
labels:
@ -149,3 +151,28 @@ services:
- ./back:/usr/src/back
- ./front:/usr/src/front
- ./pusher:/usr/src/pusher
# coturn:
# image: coturn/coturn:4.5.2
# command:
# - turnserver
# #- -c=/etc/coturn/turnserver.conf
# - --log-file=stdout
# - --external-ip=$$(detect-external-ip)
# - --listening-port=3478
# - --min-port=10000
# - --max-port=10010
# - --tls-listening-port=5349
# - --listening-ip=0.0.0.0
# - --realm=coturn.workadventure.localhost
# - --server-name=coturn.workadventure.localhost
# - --lt-cred-mech
# # Enable Coturn "REST API" to validate temporary passwords.
# #- --use-auth-secret
# #- --static-auth-secret=SomeStaticAuthSecret
# #- --userdb=/var/lib/turn/turndb
# - --user=workadventure:WorkAdventure123
# # use real-valid certificate/privatekey files
# #- --cert=/root/letsencrypt/fullchain.pem
# #- --pkey=/root/letsencrypt/privkey.pem
# network_mode: host

3
e2e/.gitignore vendored
View File

@ -1,3 +0,0 @@
screenshots/
videos/
node_modules/

View File

@ -1,36 +0,0 @@
# Testing with cypress
This project use [cypress](https://www.cypress.io/) to do functional testing of the website.
Unfortunately we cannot integrate it with docker-compose for the moment, so you will need to install some packages locally on your pc.
## Getting Started
You will need to install theses dependancies on linux (don't know about mac):
```bash
sudo apt update
sudo apt install libgtk2.0-0 libgtk-3-0 libnotify-dev libgconf-2-4 libnss3 libxss1 libasound2 libxtst6 xauth xvfb
```
Cypress can be installed locally in the e2e directory
```bash
cd e2e
npm install
```
How to use:
```bash
npm run cy:run
npm run cy:open
```
The first command will run all tests in the terminal, while the second will open the interactive task runner which allow you to easily manage the test workflow
[More details here](https://docs.cypress.io/guides/getting-started/testing-your-app.html#Step-1-Start-your-server)
## How to test a game
Cypress cannot "see" and so cannot directly manipulate the canva created by Phaser.
This means we have to do workarounds such as exposing core objects in the window so that cypress can manipulate them or doing console that cypress can catch.

View File

@ -1,7 +0,0 @@
{
"baseUrl": "http://workadventure.localhost",
"video": false,
"defaultCommandTimeout": 20000,
"pluginsFile": false,
"supportFile": false
}

View File

@ -1,25 +0,0 @@
Cypress.on('window:before:load', (win) => {
// because this is called before any scripts
// have loaded - the ga function is undefined
// so we need to create it.
win.cypressAsserter = cy.stub().as('ca')
})
describe('WorkAdventureGame', () => {
beforeEach(() => {
cy.visit('/', {
onBeforeLoad (win) {
cy.spy(win.console, 'log').as('console.log')
},
})
});
it('loads', () => {
cy.get('@console.log').should('be.calledWith', 'Started the game')
cy.get('@console.log').should('be.calledWith', 'Preloading')
cy.get('@console.log').should('be.calledWith', 'Preloading done')
cy.get('@console.log').should('be.calledWith', 'startInit')
cy.get('@console.log').should('be.calledWith', 'startInit done')
});
});

1406
e2e/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,9 +0,0 @@
{
"dependencies": {
"cypress": "^3.8.3"
},
"scripts": {
"cy:run": "cypress run",
"cy:open": "cypress open"
}
}

3
front/dist/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
index.html
index.tmpl.html.tmp
style.*.css

View File

@ -29,7 +29,6 @@
<base href="/">
<link rel="stylesheet" href="/resources/style/style.css">
<title>Binary Kitchen World</title>
</head>
<body id="body" style="margin: 0; background-color: #000">
@ -72,98 +71,54 @@
</div>
</div>
<div id="cowebsite" class="cowebsite hidden">
<button class="close-btn" id="cowebsite-close">
<aside id="cowebsite-aside">
<img src="/static/images/menu.svg" alt="hold to resize"/>
</aside>
<main id="cowebsite-main">
</main>
<button class="top-right-btn" id="cowebsite-fullscreen">
<img id="cowebsite-fullscreen-open" src="resources/logos/monitor.svg"/>
<img id="cowebsite-fullscreen-close" style="display: none;" src="resources/logos/monitor-close.svg"/>
</button>
<button class="top-right-btn" id="cowebsite-close">
<img src="resources/logos/close.svg"/>
</button>
</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.025" 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">
reduce in conversations
<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"/>
<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>
<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">

View File

@ -1,11 +1,4 @@
<style>
*{
font-family: 'Open Sans', sans-serif;
cursor: auto;
}
* a, button, select{
cursor: pointer;
}
#gameMenu button {
background-color: black;
color: white;
@ -15,6 +8,14 @@
#gameMenu section {
margin: 10px;
}
section#socialLinks{
position: absolute;
margin-bottom: 0;
}
section#socialLinks img{
width: 32px;
cursor: url('/resources/logos/cursor_pointer.png'), pointer;
}
</style>
<div id="gameMenu" hidden>
@ -38,5 +39,9 @@
<section id="adminConsoleSection" hidden>
<button id="adminConsoleButton">Admin console</button>
</section>
<section id="socialLinks" hidden>
<a class="not-button" href="https://www.facebook.com/workadventurebytcm" target="_blank"><img class="not-button" src="/resources/objects/facebook-icon.png"/></a>
<a class="not-button" href="https://twitter.com/Workadventure_" target="_blank"><img class="not-button" src="/resources/objects/twitter-icon.png"/></a>
</section>
</main>
</div>

View File

@ -1,11 +1,4 @@
<style>
*{
font-family: 'Open Sans', sans-serif;
cursor: auto;
}
* a, button, select{
cursor: pointer;
}
#menuIcon button {
background-color: black;
color: white;

View File

@ -1,11 +1,4 @@
<style>
*{
font-family: 'Open Sans', sans-serif;
cursor: auto;
}
* a, button, select{
cursor: pointer;
}
#gameQuality {
background: #eceeee;
border: 1px solid #42464b;

View File

@ -0,0 +1,115 @@
<style>
#gameReport {
background: #eceeee;
border: 1px solid #42464b;
border-radius: 6px;
margin: 2px auto 0;
width: 298px;
}
#gameReport h1 {
background-image: linear-gradient(top, #f1f3f3, #d4dae0);
border-bottom: 1px solid #a6abaf;
border-radius: 6px 6px 0 0;
box-sizing: border-box;
color: #727678;
display: block;
height: 43px;
padding-top: 10px;
margin: 0;
text-align: center;
text-shadow: 0 -1px 0 rgba(0,0,0,0.2), 0 1px 0 #fff;
}
#gameReport h3 {
margin: 0;
}
#gameReport textarea {
font-size: 70%;
background: linear-gradient(top, #d6d7d7, #dee0e0);
border: 1px solid #a1a3a3;
border-radius: 4px;
box-shadow: 0 1px #fff;
box-sizing: border-box;
color: #696969;
height: 100px;
transition: box-shadow 0.3s;
width: 100%;
}
#gameReport section {
margin: 10px;
}
#gameReport section.action{
text-align: center;
margin: 0;
}
#gameReport button {
margin-top: 10px;
font-size: 60%;
background-color: #dc3545;
color: white;
border-radius: 7px;
padding: 3px 10px 3px 10px;
}
#gameReport button#gameReportFormCancel {
background-color: #c7c7c700;
color: #292929;
display: block;
float: right;
}
#gameReport section a{
text-align: center;
font-size: 12px;
margin: 0 6px;
color: black;
}
#gameReport section h6,
#gameReport section h5{
margin: 1px;
}
#gameReport section.text-center{
text-align: center;
}
#gameReport p{
font-size: 8px;
margin: 3px 0 0 0;
}
#gameReport form p{
margin: 0px 70px;
}
#gameReport section p.err{
color: red;
display: none;
}
#gameReport section p.info{
display: none;
}
</style>
<main id="gameReport" hidden>
<section>
<button id="gameReportFormCancel">X</button>
<h1>Moderate <span id="nameReported"></span></h1>
<p id="askActionP">What action do you want to take?</p>
</section>
<section>
<h3>Block: </h3>
<p>Block any communication from and to this user. This can be reverted.</p>
<section class="action">
<button id="toggleBlockButton">Block this user</button>
</section>
</section>
<section id="reportSection">
<h3>Report: </h3>
<p>Send a report message to the administrators of this room. They may later ban this user.</p>
<form>
<section>
<h6>Your message: </h6>
<textarea type="text" name="report" id="gameReportInput"></textarea>
<p class="err" id="gameReportErr"></p>
</section>
<section class="action">
<button type="submit" id="gameReportFormSubmit">Report this user</button>
</section>
</form>
</section>
</main>

View File

@ -1,11 +1,4 @@
<style>
*{
font-family: 'Open Sans', sans-serif;
cursor: auto;
}
* a, button, input{
cursor: pointer;
}
#gameShare {
background: #eceeee;
border: 1px solid #42464b;
@ -14,9 +7,6 @@
width: 298px;
height: 150px;
}
#gameShare .cautiousText {
font-size: 50%;
}
#gameShare h1 {
background-image: linear-gradient(top, #f1f3f3, #d4dae0);
border-bottom: 1px solid #a6abaf;

View File

@ -0,0 +1,103 @@
<style>
#helpCameraSettings {
background: #eceeee;
border: 1px solid #42464b;
border-radius: 6px;
margin: 10px auto 0;
width: 400px;
height: 370px;
}
#helpCameraSettings h1 {
background-image: linear-gradient(top, #f1f3f3, #d4dae0);
border-bottom: 1px solid #a6abaf;
border-radius: 6px 6px 0 0;
box-sizing: border-box;
color: #727678;
display: block;
height: 43px;
padding-top: 10px;
margin: 0;
text-align: center;
text-shadow: 0 -1px 0 rgba(0,0,0,0.2), 0 1px 0 #fff;
}
#helpCameraSettings input {
font-size: 70%;
background: linear-gradient(top, #d6d7d7, #dee0e0);
border: 1px solid #a1a3a3;
border-radius: 4px;
box-shadow: 0 1px #fff;
box-sizing: border-box;
color: #696969;
height: 30px;
transition: box-shadow 0.3s;
width: 100%;
}
#helpCameraSettings section {
margin: 10px;
}
#helpCameraSettings section.action{
text-align: center;
margin: 0;
}
#helpCameraSettings button {
margin-top: 10px;
background-color: black;
color: white;
border-radius: 7px;
padding-bottom: 4px;
}
#helpCameraSettings button#helpCameraSettingsFormCancel {
background-color: #c7c7c700;
color: #292929;
}
#helpCameraSettings section a{
text-align: center;
font-size: 12px;
margin: 0 6px;
color: black;
}
#helpCameraSettings section h6,
#helpCameraSettings section h5{
margin: 1px;
}
#helpCameraSettings section.text-center{
text-align: center;
}
#helpCameraSettings section p{
font-size: 8px;
margin: 0px 20px;
}
#helpCameraSettings section p.err{
color: #ff0000;
}
#helpCameraSettings section ul{
margin: 6px;
}
#helpCameraSettings section li{
text-align: left;
font-size: 8px;
}
#helpCameraSettings section img {
width: 200px;
margin-top: 10px;
}
</style>
<form id="helpCameraSettings" hidden>
<section class="text-center">
<h5>Camera/Microphone access needed</h5>
<p class="err" id="permissionError">Permission denied</p>
<p class="info">You must allow camera and microphone access in your browser.</p>
<ul>
<li>Please click on the lock or camera symbol on the side of the URL in the address bar. Here you can grant "always allow" access to your input devices.</li>
<li>Please ensure that you have a camera AND microphone plugged into your computer.</li>
</ul>
<p class="info">Once you've followed these steps, please refresh this page.</p>
<p>If you prefer to continue without allowing camera and microphone access, click on Continue</p>
<p id='browserHelpSetting'></p>
</section>
<section class="action">
<button type="submit" id="helpCameraSettingsFormRefresh">Refresh</button>
<button type="submit" id="helpCameraSettingsFormContinue">Continue</button>
</section>
</form>

View File

@ -0,0 +1,18 @@
<style>
#warningMain {
border-radius: 5px;
height: 100px;
width: 300px;
background-color: red;
text-align: center;
}
#warningMain h2 {
padding: 5px;
}
</style>
<main id="warningMain">
<h2>Warning!</h2>
<p>This world is close to its limit!</p>
</main>

View File

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" id="svg2985" version="1.1" inkscape:version="0.48.4 r9939" width="485.33627" height="485.33627" sodipodi:docname="600px-France_road_sign_B1j.svg[1].png">
<metadata id="metadata2991">
<rdf:RDF>
<cc:Work rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
<dc:title/>
</cc:Work>
</rdf:RDF>
</metadata>
<defs id="defs2989"/>
<sodipodi:namedview pagecolor="#ffffff" bordercolor="#666666" borderopacity="1" objecttolerance="10" gridtolerance="10" guidetolerance="10" inkscape:pageopacity="0" inkscape:pageshadow="2" inkscape:window-width="1272" inkscape:window-height="745" id="namedview2987" showgrid="false" inkscape:snap-global="true" inkscape:snap-grids="true" inkscape:snap-bbox="true" inkscape:bbox-paths="true" inkscape:bbox-nodes="true" inkscape:snap-bbox-edge-midpoints="true" inkscape:snap-bbox-midpoints="true" inkscape:object-paths="true" inkscape:snap-intersection-paths="true" inkscape:object-nodes="true" inkscape:snap-smooth-nodes="true" inkscape:snap-midpoints="true" inkscape:snap-object-midpoints="true" inkscape:snap-center="false" fit-margin-top="0" fit-margin-left="0" fit-margin-right="0" fit-margin-bottom="0" inkscape:zoom="0.59970176" inkscape:cx="390.56499" inkscape:cy="244.34365" inkscape:window-x="86" inkscape:window-y="-8" inkscape:window-maximized="1" inkscape:current-layer="layer1">
<inkscape:grid type="xygrid" id="grid2995" empspacing="5" visible="true" enabled="true" snapvisiblegridlinesonly="true" originx="-57.33186px" originy="-57.33186px"/>
</sodipodi:namedview>
<g inkscape:groupmode="layer" id="layer1" inkscape:label="1" style="display:inline" transform="translate(-57.33186,-57.33186)">
<path sodipodi:type="arc" style="color:#000000;fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:2.5;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" id="path2997" sodipodi:cx="300" sodipodi:cy="300" sodipodi:rx="240" sodipodi:ry="240" d="M 540,300 C 540,432.54834 432.54834,540 300,540 167.45166,540 60,432.54834 60,300 60,167.45166 167.45166,60 300,60 432.54834,60 540,167.45166 540,300 z" transform="matrix(1.0058783,0,0,1.0058783,-1.76349,-1.76349)"/>
<path sodipodi:type="arc" style="color:#000000;fill:#ff0000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2.5;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" id="path4005" sodipodi:cx="304.75" sodipodi:cy="214.75" sodipodi:rx="44.75" sodipodi:ry="44.75" d="m 349.5,214.75 c 0,24.71474 -20.03526,44.75 -44.75,44.75 -24.71474,0 -44.75,-20.03526 -44.75,-44.75 0,-24.71474 20.03526,-44.75 44.75,-44.75 24.71474,0 44.75,20.03526 44.75,44.75 z" transform="matrix(5.1364411,0,0,5.1364411,-1265.3304,-803.05073)"/>
<rect style="color:#000000;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2.5;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" id="rect4001" width="345" height="80.599998" x="127.5" y="259.70001"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 KiB

BIN
front/dist/resources/logos/cancel.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

BIN
front/dist/resources/logos/logo.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

@ -0,0 +1 @@
<svg id="Calque_1" data-name="Calque 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 56.48 56.48"><defs><style>.cls-1{fill:#e76e54;}.cls-2{fill:#fff;}</style></defs><path class="cls-1" d="M39.94,512H16.54L0,495.46v-23.4l16.54-16.54h23.4l16.54,16.54v23.4Z" transform="translate(0 -455.52)"/><path class="cls-2" d="M33.54,485.52H23l-1.77-21.18H35.3Z" transform="translate(0 -455.52)"/><path class="cls-2" d="M23,492.58H33.54v10.59H23Z" transform="translate(0 -455.52)"/></svg>

After

Width:  |  Height:  |  Size: 477 B

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 477 B

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.8 KiB

After

Width:  |  Height:  |  Size: 516 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

@ -0,0 +1,170 @@
/* A potentially shared website could appear in an iframe in the cowebsite space. */
#cowebsite {
position: fixed;
transition: transform 0.5s;
background-color: white;
&.loading {
background-color: gray;
}
main {
iframe {
width: 100%;
height: 100%;
}
}
aside {
background: gray;
align-items: center;
display: flex;
img {
margin: 3px;
pointer-events: none;
height: 20px;
}
}
.top-right-btn{
position: absolute;
background: none;
border: none;
cursor: url('/resources/logos/cursor_pointer.png'), pointer;
img {
height: 20px;
background-color: rgba(0,0.0,0,0.3);
padding: 5px;
border-radius: 3px;
}
img:hover {
background-color: rgba(0,0,0,0.4);
}
}
}
@media (min-aspect-ratio: 1/1) {
#cowebsite {
right: 0;
top: 0;
width: 50%;
height: 100vh;
display: flex;
&.loading {
transform: translateX(90%);
}
&.hidden {
transform: translateX(100%);
}
main {
width: 100%;
}
aside {
width: 30px;
cursor: ew-resize;
img {
cursor: ew-resize;
transform: rotate(90deg);
}
}
.top-right-btn{
top: 10px;
right: -100px;
animation: right .2s ease;
img {
right: 15px;
}
}
#cowebsite-close {
right: -140px;
}
#cowebsite-fullscreen {
right: -100px;
}
}
#cowebsite:hover {
#cowebsite-close{
right: 10px;
}
#cowebsite-fullscreen{
right: 45px;
}
}
}
@media (max-aspect-ratio: 1/1) {
#cowebsite {
left: 0;
bottom: 0;
width: 100%;
height: 50%;
display: flex;
flex-direction: column;
&.loading {
transform: translateY(90%);
}
&.hidden {
transform: translateY(100%);
}
main {
height: 100%;
}
aside {
height: 30px;
cursor: ns-resize;
flex-direction: column;
img {
cursor: ns-resize;
}
}
.top-right-btn{
top: 10px;
right: -100px;
animation: right .2s ease;
img {
right: 15px;
}
}
#cowebsite-close {
right: -140px;
}
#cowebsite-fullscreen {
right: -100px;
}
}
#cowebsite:hover {
#cowebsite-close{
right: 10px;
}
#cowebsite-fullscreen{
right: 45px;
}
}
}

2
front/dist/resources/style/index.scss vendored Normal file
View File

@ -0,0 +1,2 @@
@import "cowebsite.scss";
@import "style.css";

View File

@ -39,6 +39,7 @@ body .message-info.warning{
position: relative;
transition: all 0.2s ease;
background-color: #00000099;
cursor: url('/resources/logos/cursor_pointer.png'), pointer;
}
.video-container i{
position: absolute;
@ -53,25 +54,71 @@ body .message-info.warning{
font-size: 28px;
color: white;
}
.video-container img.active{
display: block;
}
.video-container img{
position: absolute;
display: none;
width: 15px;
height: 15px;
background: #d93025;
border-radius: 48px;
width: 25px;
height: 25px;
left: 5px;
bottom: 5px;
padding: 10px;
z-index: 2;
}
.video-container img.block-logo {
left: 30%;
bottom: 15%;
width: 150px;
height: 150px;
}
.video-container img.report{
.video-container button.report{
display: block;
cursor: url('/resources/logos/cursor_pointer.png'), pointer;
background: none;
background-color: rgba(0, 0, 0, 0);
border: none;
background-color: black;
border-radius: 15px;
position: absolute;
width: 0px;
height: 35px;
right: 5px;
left: auto;
bottom: 5px;
padding: 0px;
overflow: hidden;
z-index: 2;
transition: all .5s ease;
}
.video-container:hover button.report{
width: 35px;
padding: 10px;
}
.video-container button.report:hover {
width: 150px;
}
.video-container button.report img{
position: absolute;
display: block;
bottom: 5px;
left: 5px;
margin: 0;
padding: 0;
cursor: url('/resources/logos/cursor_pointer.png'), pointer;
}
.video-container button.report span{
position: absolute;
bottom: 8px;
left: 36px;
color: white;
font-size: 16px;
cursor: url('/resources/logos/cursor_pointer.png'), pointer;
}
.video-container img.active {
display: block !important;
}
.video-container video{
@ -171,10 +218,6 @@ video#myCamVideo{
right: 44px;
opacity: 1;
}
/*.btn-call{
transition: all .1s;
left: 0px;
}*/
.btn-cam-action div img{
height: 22px;
width: 30px;
@ -302,35 +345,7 @@ body {
max-height: 25%;
}
#cowebsite {
right: 0;
top: 0;
width: 50%;
height: 100vh;
}
#cowebsite.loading {
transform: translateX(90%);
}
#cowebsite.hidden {
transform: translateX(100%);
}
#cowebsite .close-btn{
position: absolute;
top: 10px;
right: -100px;
background: none;
border: none;
cursor: pointer;
animation: right .2s ease;
}
#cowebsite .close-btn img{
height: 15px;
right: 15px;
}
#cowebsite:hover .close-btn{
right: 10px;
}
}
@media (max-aspect-ratio: 1/1) {
.game-overlay {
@ -349,19 +364,6 @@ body {
.sidebar > div:hover {
max-width: 25%;
}
#cowebsite {
left: 0;
bottom: 0;
width: 100%;
height: 50%;
}
#cowebsite.loading {
transform: translateY(90%);
}
#cowebsite.hidden {
transform: translateY(100%);
}
}
#game {
@ -369,20 +371,6 @@ body {
position: relative; /* Position relative is needed for the game-overlay. */
}
/* A potentially shared website could appear in an iframe in the cowebsite space. */
#cowebsite {
position: fixed;
transition: transform 0.5s;
}
#cowebsite.loading {
background-color: gray;
}
#cowebsite > iframe {
width: 100%;
height: 100%;
}
.audioplayer:first-child {
display: grid;
grid: 2rem / 4rem 10rem;
@ -395,10 +383,14 @@ body {
justify-content: center;
}
.audioplayer > div {
padding-right: 1.2rem;
}
#audioplayerctrl {
position: fixed;
top: 0;
right: 50%;
right: calc(50% - 120px);
padding: 0.3rem 0.5rem;
color: white;
transition: transform 0.5s;
@ -588,10 +580,9 @@ input[type=range]:focus::-ms-fill-upper {
}
.chat-mode {
display: flex;
display: grid;
width: 100%;
flex-wrap: wrap;
align-items: flex-start;
padding: 1%;
@ -607,24 +598,20 @@ input[type=range]:focus::-ms-fill-upper {
.chat-mode > div:hover {
margin: 0%;
}
.chat-mode.one-col > div {
flex-basis: 98%;
.chat-mode.one-col {
grid-template-columns: repeat(1, 1fr);
}
.chat-mode.two-col > div {
flex-basis: 48%;
.chat-mode.two-col {
grid-template-columns: repeat(2, 1fr);
}
.chat-mode.three-col > div {
flex-basis: 31.333333%;
.chat-mode.three-col {
grid-template-columns: repeat(3, 1fr);
}
.chat-mode.four-col > div {
flex-basis: 23%;
}
.chat-mode > div:last-child {
flex-grow: 5;
.chat-mode.four-col {
grid-template-columns: repeat(4, 1fr);
}
/*CONSOLE*/
@ -1099,7 +1086,7 @@ div.modal-report-user{
white-space: pre-wrap;
word-wrap: break-word;
}
.discussion .messages .message p.a{
.discussion .messages .message p a{
color: white;
}

View File

@ -9,9 +9,13 @@
"@types/quill": "^1.3.7",
"@typescript-eslint/eslint-plugin": "^2.26.0",
"@typescript-eslint/parser": "^2.26.0",
"css-loader": "^5.1.3",
"eslint": "^6.8.0",
"html-webpack-plugin": "^4.3.0",
"jasmine": "^3.5.0",
"mini-css-extract-plugin": "^1.3.9",
"sass": "^1.32.8",
"sass-loader": "10.1.1",
"ts-loader": "^6.2.2",
"ts-node": "^8.10.2",
"typescript": "^3.8.3",
@ -26,9 +30,10 @@
"axios": "^0.21.1",
"generic-type-guard": "^3.2.0",
"google-protobuf": "^3.13.0",
"phaser": "^3.52.0",
"phaser": "^3.53.1",
"queue-typescript": "^1.0.1",
"quill": "^1.3.7",
"rxjs": "^6.6.3",
"simple-peer": "^9.6.2",
"socket.io-client": "^2.3.0",
"webpack-require-http": "^0.4.3"

View File

@ -3,6 +3,7 @@ import {UserInputManager} from "../Phaser/UserInput/UserInputManager";
import {RoomConnection} from "../Connexion/RoomConnection";
import {PlayGlobalMessageInterface} from "../Connexion/ConnexionModels";
import {ADMIN_URL} from "../Enum/EnvironmentVariable";
import {AdminMessageEventTypes} from "../Connexion/AdminMessagesService";
export const CLASS_CONSOLE_MESSAGE = 'main-console';
export const INPUT_CONSOLE_MESSAGE = 'input-send-text';
@ -10,13 +11,16 @@ export const UPLOAD_CONSOLE_MESSAGE = 'input-upload-music';
export const INPUT_TYPE_CONSOLE = 'input-type';
export const VIDEO_QUALITY_SELECT = 'select-video-quality';
export const AUDIO_TYPE = 'audio';
export const MESSAGE_TYPE = 'message';
export const AUDIO_TYPE = AdminMessageEventTypes.audio;
export const MESSAGE_TYPE = AdminMessageEventTypes.admin;
interface EventTargetFiles extends EventTarget {
files: Array<File>;
}
/**
* @deprecated
*/
export class ConsoleGlobalMessageManager {
private readonly divMainConsole: HTMLDivElement;
@ -47,7 +51,7 @@ export class ConsoleGlobalMessageManager {
//this.buttonSettingsMainConsole = document.createElement('img');
//this.buttonAdminMainConsole = document.createElement('img');
this.userInputManager = userInputManager;
// TBA: Disable because of exception: this.initialise();
// TBA: Disable because of exception: this.initialise();
}
@ -140,7 +144,7 @@ export class ConsoleGlobalMessageManager {
const div = document.createElement('div');
div.id = INPUT_CONSOLE_MESSAGE
const buttonSend = document.createElement('button');
buttonSend.innerText = 'Envoyer';
buttonSend.innerText = 'Send';
buttonSend.classList.add('btn');
buttonSend.addEventListener('click', (event: MouseEvent) => {
this.sendMessage();
@ -242,7 +246,7 @@ export class ConsoleGlobalMessageManager {
div.appendChild(input);
const buttonSend = document.createElement('button');
buttonSend.innerText = 'Envoyer';
buttonSend.innerText = 'Send';
buttonSend.classList.add('btn');
buttonSend.addEventListener('click', (event: MouseEvent) => {
this.sendMessage();
@ -332,7 +336,7 @@ export class ConsoleGlobalMessageManager {
}
active(){
this.userInputManager.clearAllInputKeyboard();
this.userInputManager.clearAllKeys();
this.divMainConsole.style.top = '0';
this.activeConsole = true;
}
@ -372,23 +376,6 @@ export class ConsoleGlobalMessageManager {
this.buttonSendMainConsole.classList.remove('active');
}
/*activeSettingConsole(){
this.activeSetting = true;
if(this.activeMessage){
this.disabledSettingConsole();
}
this.active();
this.divSettingConsole.classList.add('active');
//this.buttonSettingsMainConsole.classList.add('active');
}
disabledSettingConsole(){
this.activeSetting = false;
this.disabled();
this.divSettingConsole.classList.remove('active');
//this.buttonSettingsMainConsole.classList.remove('active');
}*/
private getSectionId(id: string) : string {
return `section-${id}`;
}

View File

@ -77,8 +77,10 @@ export class TypeMessageExt implements TypeMessageInterface{
}
}
}
export class Ban extends TypeMessageExt {
}
export class Message extends TypeMessageExt {}
export class Ban extends TypeMessageExt {}
export class Banned extends TypeMessageExt {
showMessage(message: string){

View File

@ -1,28 +1,29 @@
import {RoomConnection} from "../Connexion/RoomConnection";
import * as TypeMessages from "./TypeMessage";
import {Banned} from "./TypeMessage";
import {adminMessagesService} from "../Connexion/AdminMessagesService";
export interface TypeMessageInterface {
showMessage(message: string): void;
}
export class UserMessageManager {
class UserMessageManager {
typeMessages: Map<string, TypeMessageInterface> = new Map<string, TypeMessageInterface>();
receiveBannedMessageListener!: Function;
constructor(private Connection: RoomConnection) {
constructor() {
const valueTypeMessageTab = Object.values(TypeMessages);
Object.keys(TypeMessages).forEach((value: string, index: number) => {
const typeMessageInstance: TypeMessageInterface = (new valueTypeMessageTab[index]() as TypeMessageInterface);
this.typeMessages.set(value.toLowerCase(), typeMessageInstance);
});
this.initialise();
}
initialise() {
//receive signal to show message
this.Connection.receiveUserMessage((type: string, message: string) => {
this.showMessage(type, message);
});
adminMessagesService.messageStream.subscribe((event) => {
const typeMessage = this.showMessage(event.type, event.text);
if(typeMessage instanceof Banned) {
this.receiveBannedMessageListener();
}
})
}
showMessage(type: string, message: string) {
@ -32,5 +33,11 @@ export class UserMessageManager {
return;
}
classTypeMessage.showMessage(message);
return classTypeMessage;
}
}
setReceiveBanListener(callback: Function){
this.receiveBannedMessageListener = callback;
}
}
export const userMessageManager = new UserMessageManager()

View File

@ -0,0 +1,34 @@
import {Subject} from "rxjs";
import {BanUserMessage, SendUserMessage} from "../Messages/generated/messages_pb";
export enum AdminMessageEventTypes {
admin = 'message',
audio = 'audio',
ban = 'ban',
}
interface AdminMessageEvent {
type: AdminMessageEventTypes,
text: string;
//todo add optional properties for other event types
}
//this class is designed to easily allow communication between the RoomConnection objects (that receive the message)
//and the various objects that may render the message on screen
class AdminMessagesService {
private _messageStream: Subject<AdminMessageEvent> = new Subject();
public messageStream = this._messageStream.asObservable();
constructor() {
this.messageStream.subscribe((event) => console.log('message', event))
}
onSendusermessage(message: SendUserMessage|BanUserMessage) {
this._messageStream.next({
type: message.getType() as unknown as AdminMessageEventTypes,
text: message.getMessage(),
})
}
}
export const adminMessagesService = new AdminMessagesService();

View File

@ -7,10 +7,24 @@ import {localUserStore} from "./LocalUserStore";
import {LocalUser} from "./LocalUser";
import {Room} from "./Room";
class ConnectionManager {
private localUser!:LocalUser;
private connexionType?: GameConnexionTypes
private reconnectingTimeout: NodeJS.Timeout|null = null;
private _unloading:boolean = false;
get unloading () {
return this._unloading;
}
constructor() {
window.addEventListener('beforeunload', () => {
this._unloading = true;
if (this.reconnectingTimeout) clearTimeout(this.reconnectingTimeout)
})
}
/**
* Tries to login to the node server and return the starting map url to be loaded
*/
@ -94,7 +108,7 @@ class ConnectionManager {
}).catch((err) => {
// Let's retry in 4-6 seconds
return new Promise<OnConnectInterface>((resolve, reject) => {
setTimeout(() => {
this.reconnectingTimeout = setTimeout(() => {
//todo: allow a way to break recursion?
//todo: find a way to avoid recursive function. Otherwise, the call stack will grow indefinitely.
this.connectToRoomSocket(roomId, name, characterLayers, position, viewport).then((connection) => resolve(connection));

View File

@ -1,4 +1,4 @@
import {PlayerAnimationNames} from "../Phaser/Player/Animation";
import {PlayerAnimationDirections} from "../Phaser/Player/Animation";
import {UserSimplePeerInterface} from "../WebRtc/SimplePeer";
import {SignalData} from "simple-peer";
import {RoomConnection} from "./RoomConnection";
@ -42,14 +42,6 @@ export interface PointInterface {
moving: boolean;
}
export class Point implements PointInterface{
constructor(public x : number, public y : number, public direction : string = PlayerAnimationNames.WalkDown, public moving : boolean = false) {
if(x === null || y === null){
throw Error("position x and y cannot be null");
}
}
}
export interface MessageUserPositionInterface {
userId: number;
name: string;
@ -80,23 +72,15 @@ export interface GroupCreatedUpdatedMessageInterface {
groupSize: number
}
export interface WebRtcStartMessageInterface {
roomId: string,
clients: UserSimplePeerInterface[]
}
export interface WebRtcDisconnectMessageInterface {
userId: number
}
export interface WebRtcSignalSentMessageInterface {
receiverId: number,
signal: SignalData
}
export interface WebRtcSignalReceivedMessageInterface {
userId: number,
signal: SignalData
signal: SignalData,
webRtcUser: string | undefined,
webRtcPassword: string | undefined
}
export interface StartMapInterface {
@ -111,11 +95,6 @@ export interface ViewportInterface {
bottom: number,
}
export interface BatchedMessageInterface {
event: string,
payload: unknown
}
export interface ItemEventMessageInterface {
itemId: number,
event: string,

View File

@ -1,12 +1,16 @@
import {LocalUser} from "./LocalUser";
const characterLayersKey = 'characterLayers';
const gameQualityKey = 'gameQuality';
const videoQualityKey = 'videoQuality';
const playerNameKey = 'playerName';
const selectedPlayerKey = 'selectedPlayer';
const customCursorPositionKey = 'customCursorPosition';
const characterLayersKey = 'characterLayers';
const gameQualityKey = 'gameQuality';
const videoQualityKey = 'videoQuality';
const audioPlayerVolumeKey = 'audioVolume';
const audioPlayerMuteKey = 'audioMute';
const helpCameraSettingsShown = 'helpCameraSettingsShown';
//todo: add localstorage fallback
class LocalUserStore {
saveUser(localUser: LocalUser) {
localStorage.setItem('localUser', JSON.stringify(localUser));
}
@ -14,48 +18,69 @@ class LocalUserStore {
const data = localStorage.getItem('localUser');
return data ? JSON.parse(data) : null;
}
setName(name:string): void {
window.localStorage.setItem('playerName', name);
localStorage.setItem(playerNameKey, name);
}
getName(): string {
return window.localStorage.getItem('playerName') ?? '';
return localStorage.getItem(playerNameKey) || '';
}
setPlayerCharacterIndex(playerCharacterIndex: number): void {
window.localStorage.setItem('selectedPlayer', ''+playerCharacterIndex);
localStorage.setItem(selectedPlayerKey, ''+playerCharacterIndex);
}
getPlayerCharacterIndex(): number {
return parseInt(window.localStorage.getItem('selectedPlayer') || '');
return parseInt(localStorage.getItem(selectedPlayerKey) || '');
}
setCustomCursorPosition(activeRow:number, selectedLayers: number[]): void {
window.localStorage.setItem('customCursorPosition', JSON.stringify({activeRow, selectedLayers}));
localStorage.setItem(customCursorPositionKey, JSON.stringify({activeRow, selectedLayers}));
}
getCustomCursorPosition(): {activeRow:number, selectedLayers:number[]}|null {
return JSON.parse(window.localStorage.getItem('customCursorPosition') || "null");
return JSON.parse(localStorage.getItem(customCursorPositionKey) || "null");
}
setCharacterLayers(layers: string[]): void {
window.localStorage.setItem(characterLayersKey, JSON.stringify(layers));
localStorage.setItem(characterLayersKey, JSON.stringify(layers));
}
getCharacterLayers(): string[]|null {
return JSON.parse(window.localStorage.getItem(characterLayersKey) || "null");
}
getGameQualityValue(): number {
return parseInt(window.localStorage.getItem(gameQualityKey) || '') || 60;
return JSON.parse(localStorage.getItem(characterLayersKey) || "null");
}
setGameQualityValue(value: number): void {
localStorage.setItem(gameQualityKey, '' + value);
}
getVideoQualityValue(): number {
return parseInt(window.localStorage.getItem(videoQualityKey) || '') || 20;
getGameQualityValue(): number {
return parseInt(localStorage.getItem(gameQualityKey) || '60');
}
setVideoQualityValue(value: number): void {
localStorage.setItem(videoQualityKey, '' + value);
}
getVideoQualityValue(): number {
return parseInt(localStorage.getItem(videoQualityKey) || '20');
}
setAudioPlayerVolume(value: number): void {
localStorage.setItem(audioPlayerVolumeKey, '' + value);
}
getAudioPlayerVolume(): number {
return parseFloat(localStorage.getItem(audioPlayerVolumeKey) || '1');
}
setAudioPlayerMuted(value: boolean): void {
localStorage.setItem(audioPlayerMuteKey, value.toString());
}
getAudioPlayerMuted(): boolean {
return localStorage.getItem(audioPlayerMuteKey) === 'true';
}
setHelpCameraSettingsShown(): void {
localStorage.setItem(helpCameraSettingsShown, '1');
}
getHelpCameraSettingsShown(): boolean {
return localStorage.getItem(helpCameraSettingsShown) === '1';
}
}
export const localUserStore = new LocalUserStore();
export const localUserStore = new LocalUserStore();

View File

@ -27,7 +27,7 @@ import {
SendJitsiJwtMessage,
CharacterLayerMessage,
PingMessage,
SendUserMessage
SendUserMessage, BanUserMessage
} from "../Messages/generated/messages_pb"
import {UserSimplePeerInterface} from "../WebRtc/SimplePeer";
@ -42,6 +42,10 @@ import {
WebRtcSignalReceivedMessageInterface,
} from "./ConnexionModels";
import {BodyResourceDescriptionInterface} from "../Phaser/Entity/PlayerTextures";
import {adminMessagesService} from "./AdminMessagesService";
import {worldFullMessageStream} from "./WorldFullMessageStream";
import {worldFullWarningStream} from "./WorldFullWarningStream";
import {connectionManager} from "./ConnectionManager";
const manualPingDelay = 20000;
@ -100,7 +104,7 @@ export class RoomConnection implements RoomConnection {
}
// If we are not connected yet (if a JoinRoomMessage was not sent), we need to retry.
if (this.userId === null) {
if (this.userId === null && !this.closed) {
this.dispatch(EventMessage.CONNECTING_ERROR, event);
}
});
@ -140,8 +144,6 @@ export class RoomConnection implements RoomConnection {
} else if (message.hasRoomjoinedmessage()) {
const roomJoinedMessage = message.getRoomjoinedmessage() as RoomJoinedMessage;
//const users: Array<MessageUserJoined> = roomJoinedMessage.getUserList().map(this.toMessageUserJoined.bind(this));
//const groups: Array<GroupCreatedUpdatedMessageInterface> = roomJoinedMessage.getGroupList().map(this.toGroupCreatedUpdatedMessage.bind(this));
const items: { [itemId: number] : unknown } = {};
for (const item of roomJoinedMessage.getItemList()) {
items[item.getItemid()] = JSON.parse(item.getStatejson());
@ -150,24 +152,15 @@ export class RoomConnection implements RoomConnection {
this.userId = roomJoinedMessage.getCurrentuserid();
this.tags = roomJoinedMessage.getTagList();
//console.log('Dispatching CONNECT')
this.dispatch(EventMessage.CONNECT, {
connection: this,
room: {
//users,
//groups,
items
} as RoomJoinedMessageInterface
});
/*console.log('Dispatching START_ROOM')
this.dispatch(EventMessage.START_ROOM, {
//users,
//groups,
items
});*/
} else if (message.hasErrormessage()) {
console.error(EventMessage.MESSAGE_ERROR, message.getErrormessage()?.getMessage());
} else if (message.hasWorldfullmessage()) {
worldFullMessageStream.onMessage();
this.closed = true;
} else if (message.hasWebrtcsignaltoclientmessage()) {
this.dispatch(EventMessage.WEBRTC_SIGNAL, message.getWebrtcsignaltoclientmessage());
} else if (message.hasWebrtcscreensharingsignaltoclientmessage()) {
@ -185,7 +178,11 @@ export class RoomConnection implements RoomConnection {
} else if (message.hasSendjitsijwtmessage()) {
this.dispatch(EventMessage.START_JITSI_ROOM, message.getSendjitsijwtmessage());
} else if (message.hasSendusermessage()) {
this.dispatch(EventMessage.USER_MESSAGE, message.getSendusermessage());
adminMessagesService.onSendusermessage(message.getSendusermessage() as SendUserMessage);
} else if (message.hasBanusermessage()) {
adminMessagesService.onSendusermessage(message.getSendusermessage() as BanUserMessage);
} else if (message.hasWorldfullwarningmessage()) {
worldFullWarningStream.onMessage();
} else {
throw new Error('Unknown message received');
}
@ -384,10 +381,7 @@ export class RoomConnection implements RoomConnection {
public onConnectError(callback: (error: Event) => void): void {
this.socket.addEventListener('error', callback)
}
/*public onConnect(callback: (e: Event) => void): void {
this.socket.addEventListener('open', callback)
}*/
public onConnect(callback: (roomConnection: OnConnectInterface) => void): void {
//this.socket.addEventListener('open', callback)
this.onMessage(EventMessage.CONNECT, callback);
@ -427,7 +421,9 @@ export class RoomConnection implements RoomConnection {
callback({
userId: message.getUserid(),
name: message.getName(),
initiator: message.getInitiator()
initiator: message.getInitiator(),
webRtcUser: message.getWebrtcusername() ?? undefined,
webRtcPassword: message.getWebrtcpassword() ?? undefined,
});
});
}
@ -436,7 +432,9 @@ export class RoomConnection implements RoomConnection {
this.onMessage(EventMessage.WEBRTC_SIGNAL, (message: WebRtcSignalToClientMessage) => {
callback({
userId: message.getUserid(),
signal: JSON.parse(message.getSignal())
signal: JSON.parse(message.getSignal()),
webRtcUser: message.getWebrtcusername() ?? undefined,
webRtcPassword: message.getWebrtcpassword() ?? undefined,
});
});
}
@ -445,14 +443,16 @@ export class RoomConnection implements RoomConnection {
this.onMessage(EventMessage.WEBRTC_SCREEN_SHARING_SIGNAL, (message: WebRtcSignalToClientMessage) => {
callback({
userId: message.getUserid(),
signal: JSON.parse(message.getSignal())
signal: JSON.parse(message.getSignal()),
webRtcUser: message.getWebrtcusername() ?? undefined,
webRtcPassword: message.getWebrtcpassword() ?? undefined,
});
});
}
public onServerDisconnected(callback: (event: CloseEvent) => void): void {
public onServerDisconnected(callback: () => void): void {
this.socket.addEventListener('close', (event) => {
if (this.closed === true) {
if (this.closed === true || connectionManager.unloading) {
return;
}
console.log('Socket closed with code '+event.code+". Reason: "+event.reason);
@ -460,11 +460,12 @@ export class RoomConnection implements RoomConnection {
// Normal closure case
return;
}
callback(event);
callback();
});
}
public getUserId(): number|null {
public getUserId(): number {
if (this.userId === null) throw 'UserId cannot be null!'
return this.userId;
}
@ -532,12 +533,6 @@ export class RoomConnection implements RoomConnection {
});
}
public receiveUserMessage(callback: (type: string, message: string) => void) {
return this.onMessage(EventMessage.USER_MESSAGE, (message: SendUserMessage) => {
callback(message.getType(), message.getMessage());
});
}
public emitGlobalMessage(message: PlayGlobalMessageInterface){
const playGlobalMessage = new PlayGlobalMessage();
playGlobalMessage.setId(message.id);
@ -583,7 +578,7 @@ export class RoomConnection implements RoomConnection {
public hasTag(tag: string): boolean {
return this.tags.includes(tag);
}
public isAdmin(): boolean {
return this.hasTag('admin');
}

View File

@ -0,0 +1,14 @@
import {Subject} from "rxjs";
class WorldFullMessageStream {
private _stream:Subject<void> = new Subject();
public stream = this._stream.asObservable();
onMessage() {
this._stream.next();
}
}
export const worldFullMessageStream = new WorldFullMessageStream();

View File

@ -0,0 +1,14 @@
import {Subject} from "rxjs";
class WorldFullWarningStream {
private _stream:Subject<void> = new Subject();
public stream = this._stream.asObservable();
onMessage() {
this._stream.next();
}
}
export const worldFullWarningStream = new WorldFullWarningStream();

View File

@ -1,36 +0,0 @@
declare let window:WindowWithCypressAsserter;
interface WindowWithCypressAsserter extends Window {
cypressAsserter: CypressAsserter;
}
//this class is used to communicate with cypress, our e2e testing client
//Since cypress cannot manipulate canvas, we notified it with console logs
class CypressAsserter {
constructor() {
window.cypressAsserter = this
}
gameStarted() {
console.log('Started the game')
}
preloadStarted() {
console.log('Preloading')
}
preloadFinished() {
console.log('Preloading done')
}
initStarted() {
console.log('startInit')
}
initFinished() {
console.log('startInit done')
}
}
export const cypressAsserter = new CypressAsserter()

View File

@ -3,9 +3,10 @@ const START_ROOM_URL : string = process.env.START_ROOM_URL || '/_/global/maps.wo
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 || "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$';
const STUN_SERVER: string = process.env.STUN_SERVER || "stun:stun.l.google.com:19302";
const TURN_SERVER: string = process.env.TURN_SERVER || "";
const TURN_USER: string = process.env.TURN_USER || '';
const TURN_PASSWORD: string = process.env.TURN_PASSWORD || '';
const JITSI_URL : string|undefined = (process.env.JITSI_URL === '') ? undefined : process.env.JITSI_URL;
const JITSI_PRIVATE_MODE : boolean = process.env.JITSI_PRIVATE_MODE == "true";
const RESOLUTION = 2;
@ -23,6 +24,7 @@ export {
ZOOM_LEVEL,
POSITION_DELAY,
MAX_EXTRAPOLATION_TIME,
STUN_SERVER,
TURN_SERVER,
TURN_USER,
TURN_PASSWORD,

View File

@ -1,14 +1,54 @@
import ImageFrameConfig = Phaser.Types.Loader.FileTypes.ImageFrameConfig;
export const addLoader = (scene:Phaser.Scene): void => {
const loadingText = scene.add.text(scene.game.renderer.width / 2, 200, 'Loading');
const LogoNameIndex: string = 'logoLoading';
const TextName: string = 'Loading...';
const LogoResource: string = 'resources/logos/logo.png';
const LogoFrame: ImageFrameConfig = {frameWidth: 307, frameHeight: 59};
export const addLoader = (scene: Phaser.Scene): void => {
// If there is nothing to load, do not display the loader.
if (scene.load.list.entries.length === 0) {
return;
}
let loadingText: Phaser.GameObjects.Text|null = null;
const loadingBarWidth: number = Math.floor(scene.game.renderer.width / 3);
const loadingBarHeight: number = 16;
const padding: number = 5;
const promiseLoadLogoTexture = new Promise<Phaser.GameObjects.Image>((res) => {
if(scene.load.textureManager.exists(LogoNameIndex)){
return res(scene.add.image(scene.game.renderer.width / 2, scene.game.renderer.height / 2 - 150, LogoNameIndex));
}else{
//add loading if logo image is not ready
loadingText = scene.add.text(scene.game.renderer.width / 2, scene.game.renderer.height / 2 - 50, TextName);
}
scene.load.spritesheet(LogoNameIndex, LogoResource, LogoFrame);
scene.load.once(`filecomplete-spritesheet-${LogoNameIndex}`, () => {
if(loadingText){
loadingText.destroy();
}
return res(scene.add.image(scene.game.renderer.width / 2, scene.game.renderer.height / 2 - 150, LogoNameIndex));
});
});
const progressContainer = scene.add.graphics();
const progress = scene.add.graphics();
progressContainer.fillStyle(0x444444, 0.8);
progressContainer.fillRect((scene.game.renderer.width - loadingBarWidth) / 2 - padding, scene.game.renderer.height / 2 + 50 - padding, loadingBarWidth + padding * 2, loadingBarHeight + padding * 2);
scene.load.on('progress', (value: number) => {
progress.clear();
progress.fillStyle(0xffffff, 1);
progress.fillRect(0, 270, 800 * value, 60);
progress.fillStyle(0xBBBBBB, 1);
progress.fillRect((scene.game.renderer.width - loadingBarWidth) / 2, scene.game.renderer.height / 2 + 50, loadingBarWidth * value, loadingBarHeight);
});
scene.load.on('complete', () => {
loadingText.destroy();
if(loadingText){
loadingText.destroy();
}
promiseLoadLogoTexture.then((resLoadingImage: Phaser.GameObjects.Image) => {
resLoadingImage.destroy();
});
progress.destroy();
progressContainer.destroy();
});
}
}

View File

@ -3,14 +3,12 @@ import {discussionManager} from "../../WebRtc/DiscussionManager";
export const openChatIconName = 'openChatIcon';
export class OpenChatIcon extends Phaser.GameObjects.Image {
constructor(scene: Phaser.Scene, x: number, y: number) {
super(scene, x, y, openChatIconName);
super(scene, x, y, openChatIconName, 3);
scene.add.existing(this);
this.setScrollFactor(0, 0);
this.setOrigin(0, 1);
this.displayWidth = 30;
this.displayHeight = 30;
this.setInteractive();
this.setVisible(false)
this.setVisible(false);
this.setDepth(99999);
this.on("pointerup", () => discussionManager.showDiscussionPart());

View File

@ -0,0 +1,14 @@
export const warningContainerKey = 'warningContainer';
export const warningContainerHtml = 'resources/html/warningContainer.html';
export class WarningContainer extends Phaser.GameObjects.DOMElement {
constructor(scene: Phaser.Scene) {
super(scene, 100, 0);
this.setOrigin(0, 0);
this.createFromCache(warningContainerKey);
this.scene.add.existing(this);
}
}

View File

@ -1,4 +1,4 @@
import {PlayerAnimationNames} from "../Player/Animation";
import {PlayerAnimationDirections, PlayerAnimationTypes} from "../Player/Animation";
import {SpeechBubble} from "./SpeechBubble";
import BitmapText = Phaser.GameObjects.BitmapText;
import Container = Phaser.GameObjects.Container;
@ -10,8 +10,7 @@ interface AnimationData {
frameRate: number;
repeat: number;
frameModel: string; //todo use an enum
frameStart: number;
frameEnd: number;
frames : number[]
}
export abstract class Character extends Container {
@ -19,7 +18,7 @@ export abstract class Character extends Container {
private readonly playerName: BitmapText;
public PlayerValue: string;
public sprites: Map<string, Sprite>;
private lastDirection: string = PlayerAnimationNames.WalkDown;
private lastDirection: PlayerAnimationDirections = PlayerAnimationDirections.Down;
//private teleportation: Sprite;
private invisible: boolean;
@ -28,7 +27,7 @@ export abstract class Character extends Container {
y: number,
texturesPromise: Promise<string[]>,
name: string,
direction: string,
direction: PlayerAnimationDirections,
moving: boolean,
frame?: string | number
) {
@ -81,7 +80,7 @@ export abstract class Character extends Container {
this.getPlayerAnimations(texture).forEach(d => {
this.scene.anims.create({
key: d.key,
frames: this.scene.anims.generateFrameNumbers(d.frameModel, {start: d.frameStart, end: d.frameEnd}),
frames: this.scene.anims.generateFrameNumbers(d.frameModel, {frames: d.frames}),
frameRate: d.frameRate,
repeat: d.repeat
});
@ -96,37 +95,57 @@ export abstract class Character extends Container {
private getPlayerAnimations(name: string): AnimationData[] {
return [{
key: `${name}-${PlayerAnimationNames.WalkDown}`,
key: `${name}-${PlayerAnimationDirections.Down}-${PlayerAnimationTypes.Walk}`,
frameModel: name,
frameStart: 0,
frameEnd: 2,
frames: [0, 1, 2, 1],
frameRate: 10,
repeat: -1
}, {
key: `${name}-${PlayerAnimationNames.WalkLeft}`,
key: `${name}-${PlayerAnimationDirections.Left}-${PlayerAnimationTypes.Walk}`,
frameModel: name,
frameStart: 3,
frameEnd: 5,
frames: [3, 4, 5, 4],
frameRate: 10,
repeat: -1
}, {
key: `${name}-${PlayerAnimationNames.WalkRight}`,
key: `${name}-${PlayerAnimationDirections.Right}-${PlayerAnimationTypes.Walk}`,
frameModel: name,
frameStart: 6,
frameEnd: 8,
frames: [6, 7, 8, 7],
frameRate: 10,
repeat: -1
}, {
key: `${name}-${PlayerAnimationNames.WalkUp}`,
key: `${name}-${PlayerAnimationDirections.Up}-${PlayerAnimationTypes.Walk}`,
frameModel: name,
frameStart: 9,
frameEnd: 11,
frames: [9, 10, 11, 10],
frameRate: 10,
repeat: -1
},{
key: `${name}-${PlayerAnimationDirections.Down}-${PlayerAnimationTypes.Idle}`,
frameModel: name,
frames: [1],
frameRate: 10,
repeat: 1
}, {
key: `${name}-${PlayerAnimationDirections.Left}-${PlayerAnimationTypes.Idle}`,
frameModel: name,
frames: [4],
frameRate: 10,
repeat: 1
}, {
key: `${name}-${PlayerAnimationDirections.Right}-${PlayerAnimationTypes.Idle}`,
frameModel: name,
frames: [7],
frameRate: 10,
repeat: 1
}, {
key: `${name}-${PlayerAnimationDirections.Up}-${PlayerAnimationTypes.Idle}`,
frameModel: name,
frames: [10],
frameRate: 10,
repeat: 1
}];
}
protected playAnimation(direction : string, moving: boolean): void {
protected playAnimation(direction : PlayerAnimationDirections, moving: boolean): void {
if (this.invisible) return;
for (const [texture, sprite] of this.sprites.entries()) {
if (!sprite.anims) {
@ -134,10 +153,9 @@ export abstract class Character extends Container {
return;
}
if (moving && (!sprite.anims.currentAnim || sprite.anims.currentAnim.key !== direction)) {
sprite.play(texture+'-'+direction, true);
sprite.play(texture+'-'+direction+'-'+PlayerAnimationTypes.Walk, true);
} else if (!moving) {
sprite.anims.play(texture + '-' + direction, true);
sprite.anims.stop();
sprite.anims.play(texture + '-' + direction + '-'+PlayerAnimationTypes.Idle, true);
}
}
}
@ -157,17 +175,17 @@ export abstract class Character extends Container {
// up or down animations are prioritized over left and right
if (body.velocity.y < 0) { //moving up
this.lastDirection = PlayerAnimationNames.WalkUp;
this.playAnimation(PlayerAnimationNames.WalkUp, true);
this.lastDirection = PlayerAnimationDirections.Up;
this.playAnimation(PlayerAnimationDirections.Up, true);
} else if (body.velocity.y > 0) { //moving down
this.lastDirection = PlayerAnimationNames.WalkDown;
this.playAnimation(PlayerAnimationNames.WalkDown, true);
this.lastDirection = PlayerAnimationDirections.Down;
this.playAnimation(PlayerAnimationDirections.Down, true);
} else if (body.velocity.x > 0) { //moving right
this.lastDirection = PlayerAnimationNames.WalkRight;
this.playAnimation(PlayerAnimationNames.WalkRight, true);
this.lastDirection = PlayerAnimationDirections.Right;
this.playAnimation(PlayerAnimationDirections.Right, true);
} else if (body.velocity.x < 0) { //moving left
this.lastDirection = PlayerAnimationNames.WalkLeft;
this.playAnimation(PlayerAnimationNames.WalkLeft, true);
this.lastDirection = PlayerAnimationDirections.Left;
this.playAnimation(PlayerAnimationDirections.Left, true);
}
this.setDepth(this.y);

View File

@ -6,7 +6,8 @@ export interface BodyResourceDescriptionListInterface {
export interface BodyResourceDescriptionInterface {
name: string,
img: string
img: string,
level?: number
}
export const PLAYER_RESOURCES: BodyResourceDescriptionListInterface = {

View File

@ -23,21 +23,26 @@ export const loadAllDefaultModels = (load: LoaderPlugin): BodyResourceDescriptio
});
return returnArray;
}
export const loadCustomTexture = (load: LoaderPlugin, texture: CharacterTexture) => {
export const loadCustomTexture = (loaderPlugin: LoaderPlugin, texture: CharacterTexture) : Promise<BodyResourceDescriptionInterface> => {
const name = 'customCharacterTexture'+texture.id;
load.spritesheet(name,texture.url,{frameWidth: 32, frameHeight: 32});
return name;
const playerResourceDescriptor: BodyResourceDescriptionInterface = {name, img: texture.url, level: texture.level}
return createLoadingPromise(loaderPlugin, playerResourceDescriptor);
}
export const lazyLoadPlayerCharacterTextures = (loadPlugin: LoaderPlugin, texturePlugin: TextureManager, texturekeys:Array<string|BodyResourceDescriptionInterface>): Promise<string[]> => {
const promisesList:Promise<void>[] = [];
export const lazyLoadPlayerCharacterTextures = (loadPlugin: LoaderPlugin, texturekeys:Array<string|BodyResourceDescriptionInterface>): Promise<string[]> => {
const promisesList:Promise<unknown>[] = [];
texturekeys.forEach((textureKey: string|BodyResourceDescriptionInterface) => {
const playerResourceDescriptor = getRessourceDescriptor(textureKey);
if(!texturePlugin.exists(playerResourceDescriptor.name)) {
console.log('Loading '+playerResourceDescriptor.name)
promisesList.push(createLoadingPromise(loadPlugin, playerResourceDescriptor));
try {
//TODO refactor
const playerResourceDescriptor = getRessourceDescriptor(textureKey);
if (playerResourceDescriptor && !loadPlugin.textureManager.exists(playerResourceDescriptor.name)) {
promisesList.push(createLoadingPromise(loadPlugin, playerResourceDescriptor));
}
}catch (err){
console.error(err);
}
})
});
let returnPromise:Promise<Array<string|BodyResourceDescriptionInterface>>;
if (promisesList.length > 0) {
loadPlugin.start();
@ -66,8 +71,14 @@ export const getRessourceDescriptor = (textureKey: string|BodyResourceDescriptio
}
const createLoadingPromise = (loadPlugin: LoaderPlugin, playerResourceDescriptor: BodyResourceDescriptionInterface) => {
return new Promise<void>((res, rej) => {
loadPlugin.spritesheet(playerResourceDescriptor.name, playerResourceDescriptor.img, {frameWidth: 32, frameHeight: 32});
loadPlugin.once('filecomplete-spritesheet-'+playerResourceDescriptor.name, () => res());
return new Promise<BodyResourceDescriptionInterface>((res) => {
if (loadPlugin.textureManager.exists(playerResourceDescriptor.name)) {
return res(playerResourceDescriptor);
}
loadPlugin.spritesheet(playerResourceDescriptor.name, playerResourceDescriptor.img, {
frameWidth: 32,
frameHeight: 32
});
loadPlugin.once('filecomplete-spritesheet-' + playerResourceDescriptor.name, () => res(playerResourceDescriptor));
});
}

View File

@ -1,7 +1,7 @@
import {GameScene} from "../Game/GameScene";
import {PointInterface} from "../../Connexion/ConnexionModels";
import {Character} from "../Entity/Character";
import {Sprite} from "./Sprite";
import {PlayerAnimationDirections} from "../Player/Animation";
/**
* Class representing the sprite of a remote player (a player that plays on another computer)
@ -16,7 +16,7 @@ export class RemotePlayer extends Character {
y: number,
name: string,
texturesPromise: Promise<string[]>,
direction: string,
direction: PlayerAnimationDirections,
moving: boolean
) {
super(Scene, x, y, texturesPromise, name, direction, moving, 1);
@ -26,7 +26,7 @@ export class RemotePlayer extends Character {
}
updatePosition(position: PointInterface): void {
this.playAnimation(position.direction, position.moving);
this.playAnimation(position.direction as PlayerAnimationDirections, position.moving);
this.setX(position.x);
this.setY(position.y);

View File

@ -14,7 +14,7 @@ export class SpeechBubble {
const bubbleWidth = bubblePadding * 2 + text.length * 10;
const arrowHeight = bubbleHeight / 4;
this.bubble = scene.add.graphics({ x: player.x + 16, y: player.y - 80 });
this.bubble = scene.add.graphics({ x: 16, y: -80 });
player.add(this.bubble);
// Bubble shadow

View File

@ -2,6 +2,7 @@ import {GameScene} from "./GameScene";
import {connectionManager} from "../../Connexion/ConnectionManager";
import {Room} from "../../Connexion/Room";
import {MenuScene, MenuSceneName} from "../Menu/MenuScene";
import {HelpCameraSettingsScene, HelpCameraSettingsSceneName} from "../Menu/HelpCameraSettingsScene";
import {LoginSceneName} from "../Login/LoginScene";
import {SelectCharacterSceneName} from "../Login/SelectCharacterScene";
import {EnableCameraSceneName} from "../Login/EnableCameraScene";
@ -78,6 +79,10 @@ export class GameManager {
console.log('starting '+ (this.currentGameSceneName || this.startRoom.id))
scenePlugin.start(this.currentGameSceneName || this.startRoom.id);
scenePlugin.launch(MenuSceneName);
if (!localUserStore.getHelpCameraSettingsShown()) {
scenePlugin.launch(HelpCameraSettingsSceneName);//700
}
}
public gameSceneIsCreated(scene: GameScene) {

View File

@ -29,6 +29,7 @@ export class GameMap {
const newProps = this.getProperties(key);
const oldProps = this.lastProperties;
this.lastProperties = newProps;
// Let's compare the 2 maps:
// First new properties vs oldProperties
@ -45,8 +46,10 @@ export class GameMap {
this.trigger(oldPropName, oldPropValue, undefined, newProps);
}
}
}
this.lastProperties = newProps;
public getCurrentProperties(): Map<string, string|boolean|number> {
return this.lastProperties;
}
private getProperties(key: number): Map<string, string|boolean|number> {

View File

@ -3,45 +3,36 @@ import {
GroupCreatedUpdatedMessageInterface,
MessageUserJoined,
MessageUserMovedInterface,
MessageUserPositionInterface, OnConnectInterface,
MessageUserPositionInterface,
OnConnectInterface,
PointInterface,
PositionInterface,
RoomJoinedMessageInterface
} from "../../Connexion/ConnexionModels";
import {CurrentGamerInterface, hasMovedEventName, Player} from "../Player/Player";
import {
DEBUG_MODE,
JITSI_PRIVATE_MODE,
POSITION_DELAY,
RESOLUTION,
ZOOM_LEVEL
} from "../../Enum/EnvironmentVariable";
import {
ITiledMap,
ITiledMapLayer,
ITiledMapLayerProperty, ITiledMapObject,
ITiledTileSet
} from "../Map/ITiledMap";
import {DEBUG_MODE, JITSI_PRIVATE_MODE, POSITION_DELAY, RESOLUTION, ZOOM_LEVEL} from "../../Enum/EnvironmentVariable";
import {ITiledMap, ITiledMapLayer, ITiledMapLayerProperty, ITiledMapObject, ITiledTileSet} from "../Map/ITiledMap";
import {AddPlayerInterface} from "./AddPlayerInterface";
import {PlayerAnimationNames} from "../Player/Animation";
import {PlayerAnimationDirections} from "../Player/Animation";
import {PlayerMovement} from "./PlayerMovement";
import {PlayersPositionInterpolator} from "./PlayersPositionInterpolator";
import {RemotePlayer} from "../Entity/RemotePlayer";
import {Queue} from 'queue-typescript';
import {SimplePeer, UserSimplePeerInterface} from "../../WebRtc/SimplePeer";
import {ReconnectingSceneName} from "../Reconnecting/ReconnectingScene";
import {lazyLoadPlayerCharacterTextures} from "../Entity/PlayerTexturesLoadingManager";
import {lazyLoadPlayerCharacterTextures, loadCustomTexture} from "../Entity/PlayerTexturesLoadingManager";
import {
CenterListener,
JITSI_MESSAGE_PROPERTIES,
layoutManager,
LayoutMode,
ON_ACTION_TRIGGER_BUTTON, TRIGGER_JITSI_PROPERTIES, TRIGGER_WEBSITE_PROPERTIES
ON_ACTION_TRIGGER_BUTTON,
TRIGGER_JITSI_PROPERTIES,
TRIGGER_WEBSITE_PROPERTIES,
WEBSITE_MESSAGE_PROPERTIES,
AUDIO_VOLUME_PROPERTY,
AUDIO_LOOP_PROPERTY
} from "../../WebRtc/LayoutManager";
import Texture = Phaser.Textures.Texture;
import Sprite = Phaser.GameObjects.Sprite;
import CanvasTexture = Phaser.Textures.CanvasTexture;
import GameObject = Phaser.GameObjects.GameObject;
import FILE_LOAD_ERROR = Phaser.Loader.Events.FILE_LOAD_ERROR;
import {GameMap} from "./GameMap";
import {coWebsiteManager} from "../../WebRtc/CoWebsiteManager";
import {mediaManager} from "../../WebRtc/MediaManager";
@ -53,7 +44,7 @@ import {ProtobufClientUtils} from "../../Network/ProtobufClientUtils";
import {connectionManager} from "../../Connexion/ConnectionManager";
import {RoomConnection} from "../../Connexion/RoomConnection";
import {GlobalMessageManager} from "../../Administration/GlobalMessageManager";
import {UserMessageManager} from "../../Administration/UserMessageManager";
import {userMessageManager} from "../../Administration/UserMessageManager";
import {ConsoleGlobalMessageManager} from "../../Administration/ConsoleGlobalMessageManager";
import {ResizableScene} from "../Login/ResizableScene";
import {Room} from "../../Connexion/Room";
@ -68,6 +59,14 @@ import {SelectCharacterScene, SelectCharacterSceneName} from "../Login/SelectCha
import {TextureError} from "../../Exception/TextureError";
import {addLoader} from "../Components/Loader";
import {ErrorSceneName} from "../Reconnecting/ErrorScene";
import {localUserStore} from "../../Connexion/LocalUserStore";
import Texture = Phaser.Textures.Texture;
import Sprite = Phaser.GameObjects.Sprite;
import CanvasTexture = Phaser.Textures.CanvasTexture;
import GameObject = Phaser.GameObjects.GameObject;
import FILE_LOAD_ERROR = Phaser.Loader.Events.FILE_LOAD_ERROR;
import {Subscription} from "rxjs";
import {worldFullMessageStream} from "../../Connexion/WorldFullMessageStream";
export interface GameSceneInitInterface {
initPosition: PointInterface|null,
@ -126,7 +125,6 @@ export class GameScene extends ResizableScene implements CenterListener {
public connection!: RoomConnection;
private simplePeer!: SimplePeer;
private GlobalMessageManager!: GlobalMessageManager;
private UserMessageManager!: UserMessageManager;
public ConsoleGlobalMessageManager!: ConsoleGlobalMessageManager;
private connectionAnswerPromise: Promise<RoomJoinedMessageInterface>;
private connectionAnswerPromiseResolve!: (value?: RoomJoinedMessageInterface | PromiseLike<RoomJoinedMessageInterface>) => void;
@ -153,12 +151,13 @@ export class GameScene extends ResizableScene implements CenterListener {
private actionableItems: Map<number, ActionableItem> = new Map<number, ActionableItem>();
// The item that can be selected by pressing the space key.
private outlinedItem: ActionableItem|null = null;
private userInputManager!: UserInputManager;
private isReconnecting: boolean = false;
public userInputManager!: UserInputManager;
private isReconnecting: boolean|undefined = undefined;
private startLayerName!: string | null;
private openChatIcon!: OpenChatIcon;
private playerName!: string;
private characterLayers!: string[];
private messageSubscription: Subscription|null = null;
constructor(private room: Room, MapUrlFile: string, customKey?: string|undefined) {
super({
@ -182,7 +181,13 @@ export class GameScene extends ResizableScene implements CenterListener {
//hook preload scene
preload(): void {
addLoader(this);
const localUser = localUserStore.getLocalUser();
const textures = localUser?.textures;
if (textures) {
for (const texture of textures) {
loadCustomTexture(this.load, texture);
}
}
this.load.image(openChatIconName, 'resources/objects/talk.png');
this.load.on(FILE_LOAD_ERROR, (file: {src: string}) => {
@ -207,6 +212,8 @@ export class GameScene extends ResizableScene implements CenterListener {
this.load.spritesheet('layout_modes', 'resources/objects/layout_modes.png', {frameWidth: 32, frameHeight: 32});
this.load.bitmapFont('main_font', 'resources/fonts/arcade.png', 'resources/fonts/arcade.xml');
addLoader(this);
}
// FIXME: we need to put a "unknown" instead of a "any" and validate the structure of the JSON we are receiving.
@ -283,25 +290,6 @@ export class GameScene extends ResizableScene implements CenterListener {
}
});
});
// import(/* webpackIgnore: true */ scriptUrl).then(result => {
//
// result.default.preload(this.load);
//
// this.load.start(); // Let's manually start the loader because the import might be over AFTER the loading ends.
// this.load.on('complete', () => {
// // FIXME: the factory might fail because the resources might not be loaded yet...
// // We would need to add a loader ended event in addition to the createPromise
// this.createPromise.then(() => {
// result.default.create(this);
//
// for (let object of objectsOfType) {
// // TODO: we should pass here a factory to create sprites (maybe?)
// let objectSprite = result.default.factory(this, object);
// }
// });
// });
// });
}
}
@ -320,6 +308,8 @@ export class GameScene extends ResizableScene implements CenterListener {
gameManager.gameSceneIsCreated(this);
urlManager.pushRoomIdToUrl(this.room);
this.startLayerName = urlManager.getStartLayerNameFromUrl();
this.messageSubscription = worldFullMessageStream.stream.subscribe((message) => this.showWorldFullError())
const playerName = gameManager.getPlayerName();
if (!playerName) {
@ -393,13 +383,13 @@ export class GameScene extends ResizableScene implements CenterListener {
this.scene.launch(ReconnectingSceneName);
}, 0);
} else if (this.connection === undefined) {
// Let's wait 0.5 seconds before printing the "connecting" screen to avoid blinking
// Let's wait 1 second before printing the "connecting" screen to avoid blinking
setTimeout(() => {
if (this.connection === undefined) {
this.scene.sleep();
this.scene.launch(ReconnectingSceneName);
}
}, 500);
}, 1000);
}
copyrightInfo.initCopyrightInfo(mapDirUrl);
@ -474,16 +464,12 @@ export class GameScene extends ResizableScene implements CenterListener {
});
this.connection.onGroupUpdatedOrCreated((groupPositionMessage: GroupCreatedUpdatedMessageInterface) => {
audioManager.decreaseVolume();
this.shareGroupPosition(groupPositionMessage);
this.openChatIcon.setVisible(true);
})
this.connection.onGroupDeleted((groupId: number) => {
audioManager.restoreVolume();
try {
this.deleteGroup(groupId);
this.openChatIcon.setVisible(false);
} catch (e) {
console.error(e);
}
@ -491,9 +477,7 @@ export class GameScene extends ResizableScene implements CenterListener {
this.connection.onServerDisconnected(() => {
console.log('Player disconnected from server. Reloading scene.');
this.simplePeer.closeAllConnections();
this.simplePeer.unregister();
this.cleanupClosingScene();
const gameSceneKey = 'somekey' + Math.round(Math.random() * 10000);
const game: Phaser.Scene = new GameScene(this.room, this.MapUrlFile, gameSceneKey);
@ -529,18 +513,22 @@ export class GameScene extends ResizableScene implements CenterListener {
// When connection is performed, let's connect SimplePeer
this.simplePeer = new SimplePeer(this.connection, !this.room.isPublic, this.playerName);
this.GlobalMessageManager = new GlobalMessageManager(this.connection);
this.UserMessageManager = new UserMessageManager(this.connection);
userMessageManager.setReceiveBanListener(this.bannedUser.bind(this));
const self = this;
this.simplePeer.registerPeerConnectionListener({
onConnect(user: UserSimplePeerInterface) {
self.presentationModeSprite.setVisible(true);
self.chatModeSprite.setVisible(true);
self.openChatIcon.setVisible(true);
audioManager.decreaseVolume();
},
onDisconnect(userId: number) {
if (self.simplePeer.getNbConnections() === 0) {
self.presentationModeSprite.setVisible(false);
self.chatModeSprite.setVisible(false);
self.openChatIcon.setVisible(false);
audioManager.restoreVolume();
}
}
})
@ -563,11 +551,10 @@ export class GameScene extends ResizableScene implements CenterListener {
//init user position and play trigger to check layers properties
this.gameMap.setPosition(this.CurrentPlayer.x, this.CurrentPlayer.y);
return this.connection;
});
}
//todo: into dedicated classes
private initCirclesCanvas(): void {
// Let's generate the circle for the group delimiter
@ -601,28 +588,14 @@ export class GameScene extends ResizableScene implements CenterListener {
contextRed.stroke();
this.circleRedTexture.refresh();
}
private playAudio(url: string|number|boolean|undefined, loop=false): void {
if (url === undefined) {
audioManager.unloadAudio();
} else {
const audioPath = url as string;
let realAudioPath = '';
if (audioPath.indexOf('://') > 0) {
// remote file or stream
realAudioPath = audioPath;
} else {
// local file, include it relative to map directory
const mapDirUrl = this.MapUrlFile.substr(0, this.MapUrlFile.lastIndexOf('/'));
realAudioPath = mapDirUrl + '/' + url;
}
audioManager.loadAudio(realAudioPath);
if (loop) {
audioManager.loop();
}
private safeParseJSONstring(jsonString: string|undefined, propertyName: string) {
try {
return jsonString ? JSON.parse(jsonString) : {};
} catch(e) {
console.warn('Invalid JSON found in property "' + propertyName + '" of the map:' + jsonString, e);
return {}
}
}
@ -639,13 +612,17 @@ export class GameScene extends ResizableScene implements CenterListener {
coWebsiteManager.closeCoWebsite();
}else{
const openWebsiteFunction = () => {
coWebsiteManager.loadCoWebsite(newValue as string);
coWebsiteManager.loadCoWebsite(newValue as string, this.MapUrlFile, allProps.get('openWebsitePolicy') as string | undefined);
layoutManager.removeActionButton('openWebsite', this.userInputManager);
};
const openWebsiteTriggerValue = allProps.get(TRIGGER_WEBSITE_PROPERTIES);
if(openWebsiteTriggerValue && openWebsiteTriggerValue === ON_ACTION_TRIGGER_BUTTON) {
layoutManager.addActionButton('openWebsite', 'Click on SPACE to open the web site', () => {
let message = allProps.get(WEBSITE_MESSAGE_PROPERTIES);
if(message === undefined){
message = 'Press on SPACE to open the web site';
}
layoutManager.addActionButton('openWebsite', message.toString(), () => {
openWebsiteFunction();
}, this.userInputManager);
}else{
@ -659,26 +636,32 @@ export class GameScene extends ResizableScene implements CenterListener {
this.stopJitsi();
}else{
const openJitsiRoomFunction = () => {
if (JITSI_PRIVATE_MODE) {
const roomName = jitsiFactory.getRoomName(newValue.toString(), this.instance);
const jitsiUrl = allProps.get("jitsiUrl") as string|undefined;
if (JITSI_PRIVATE_MODE && !jitsiUrl) {
const adminTag = allProps.get("jitsiRoomAdminTag") as string|undefined;
this.connection.emitQueryJitsiJwtMessage(this.instance.replace('/', '-') + "-" + newValue, adminTag);
this.connection.emitQueryJitsiJwtMessage(roomName, adminTag);
} else {
this.startJitsi(newValue as string);
this.startJitsi(roomName, undefined);
}
layoutManager.removeActionButton('jitsiRoom', this.userInputManager);
}
const jitsiTriggerValue = allProps.get(TRIGGER_JITSI_PROPERTIES);
if(jitsiTriggerValue && jitsiTriggerValue === ON_ACTION_TRIGGER_BUTTON) {
layoutManager.addActionButton('jitsiRoom', 'Click on SPACE to enter in jitsi meet room', () => {
let message = allProps.get(JITSI_MESSAGE_PROPERTIES);
if (message === undefined) {
message = 'Press on SPACE to enter in jitsi meet room';
}
layoutManager.addActionButton('jitsiRoom', message.toString(), () => {
openJitsiRoomFunction();
}, this.userInputManager);
}else{
openJitsiRoomFunction();
}
}
})
});
this.gameMap.onPropertyChange('silent', (newValue, oldValue) => {
if (newValue === undefined || newValue === false || newValue === '') {
this.connection.setSilent(false);
@ -686,14 +669,19 @@ export class GameScene extends ResizableScene implements CenterListener {
this.connection.setSilent(true);
}
});
this.gameMap.onPropertyChange('playAudio', (newValue, oldValue) => {
this.playAudio(newValue);
this.gameMap.onPropertyChange('playAudio', (newValue, oldValue, allProps) => {
const volume = allProps.get(AUDIO_VOLUME_PROPERTY) as number|undefined;
const loop = allProps.get(AUDIO_LOOP_PROPERTY) as boolean|undefined;
newValue === undefined ? audioManager.unloadAudio() : audioManager.playAudio(newValue, this.getMapDirUrl(), volume, loop);
});
// TODO: This legacy property should be removed at some point
this.gameMap.onPropertyChange('playAudioLoop', (newValue, oldValue) => {
this.playAudio(newValue, true);
newValue === undefined ? audioManager.unloadAudio() : audioManager.playAudio(newValue, this.getMapDirUrl(), undefined, true);
});
}
private getMapDirUrl(): string {
return this.MapUrlFile.substr(0, this.MapUrlFile.lastIndexOf('/'));
}
private onMapExit(exitKey: string) {
@ -701,6 +689,10 @@ export class GameScene extends ResizableScene implements CenterListener {
if (!roomId) throw new Error('Could not find the room from its exit key: '+exitKey);
urlManager.pushStartLayerNameToUrl(hash);
if (roomId !== this.scene.key) {
if (this.scene.get(roomId) === null) {
console.error("next room not loaded", exitKey);
return;
}
this.cleanupClosingScene();
this.scene.stop();
this.scene.remove(this.scene.key);
@ -717,14 +709,12 @@ export class GameScene extends ResizableScene implements CenterListener {
// stop playing audio, close any open website, stop any open Jitsi
coWebsiteManager.closeCoWebsite();
this.stopJitsi();
this.playAudio(undefined);
audioManager.unloadAudio();
// We are completely destroying the current scene to avoid using a half-backed instance when coming back to the same map.
if(this.connection) {
this.connection.closeConnection();
}
if(this.simplePeer) {
this.simplePeer.unregister();
}
this.connection?.closeConnection();
this.simplePeer.closeAllConnections();
this.simplePeer?.unregister();
this.messageSubscription?.unsubscribe();
}
private removeAllRemotePlayers(): void {
@ -878,7 +868,7 @@ export class GameScene extends ResizableScene implements CenterListener {
createCurrentPlayer(){
//TODO create animation moving between exit and start
const texturesPromise = lazyLoadPlayerCharacterTextures(this.load, this.textures, this.characterLayers);
const texturesPromise = lazyLoadPlayerCharacterTextures(this.load, this.characterLayers);
try {
this.CurrentPlayer = new Player(
this,
@ -886,7 +876,7 @@ export class GameScene extends ResizableScene implements CenterListener {
this.startY,
this.playerName,
texturesPromise,
PlayerAnimationNames.WalkDown,
PlayerAnimationDirections.Down,
false,
this.userInputManager
);
@ -935,16 +925,16 @@ export class GameScene extends ResizableScene implements CenterListener {
let x = event.x;
let y = event.y;
switch (event.direction) {
case PlayerAnimationNames.WalkUp:
case PlayerAnimationDirections.Up:
y -= 32;
break;
case PlayerAnimationNames.WalkDown:
case PlayerAnimationDirections.Down:
y += 32;
break;
case PlayerAnimationNames.WalkLeft:
case PlayerAnimationDirections.Left:
x -= 32;
break;
case PlayerAnimationNames.WalkRight:
case PlayerAnimationDirections.Right:
x += 32;
break;
default:
@ -1015,13 +1005,12 @@ export class GameScene extends ResizableScene implements CenterListener {
break;
}
}
// Let's move all users
const updatedPlayersPositions = this.playersPositionInterpolator.getUpdatedPositions(time);
updatedPlayersPositions.forEach((moveEvent: HasMovedEvent, userId: number) => {
const player : RemotePlayer | undefined = this.MapPlayersByKey.get(userId);
const player: RemotePlayer | undefined = this.MapPlayersByKey.get(userId);
if (player === undefined) {
throw new Error('Cannot find player with ID "' + userId +'"');
throw new Error('Cannot find player with ID "' + userId + '"');
}
player.updatePosition(moveEvent);
});
@ -1072,7 +1061,7 @@ export class GameScene extends ResizableScene implements CenterListener {
return;
}
const texturesPromise = lazyLoadPlayerCharacterTextures(this.load, this.textures, addPlayerData.characterLayers);
const texturesPromise = lazyLoadPlayerCharacterTextures(this.load, addPlayerData.characterLayers);
const player = new RemotePlayer(
addPlayerData.userId,
this,
@ -1080,7 +1069,7 @@ export class GameScene extends ResizableScene implements CenterListener {
addPlayerData.position.y,
addPlayerData.name,
texturesPromise,
addPlayerData.position.direction,
addPlayerData.position.direction as PlayerAnimationDirections,
addPlayerData.position.moving
);
this.MapPlayers.add(player);
@ -1197,6 +1186,7 @@ export class GameScene extends ResizableScene implements CenterListener {
private reposition(): void {
this.presentationModeSprite.setY(this.game.renderer.height - 2);
this.chatModeSprite.setY(this.game.renderer.height - 2);
this.openChatIcon.setY(this.game.renderer.height - 2);
// Recompute camera offset if needed
this.updateCameraOffset();
@ -1223,7 +1213,12 @@ export class GameScene extends ResizableScene implements CenterListener {
}
public startJitsi(roomName: string, jwt?: string): void {
jitsiFactory.start(roomName, this.playerName, jwt);
const allProps = this.gameMap.getCurrentProperties();
const jitsiConfig = this.safeParseJSONstring(allProps.get("jitsiConfig") as string|undefined, 'jitsiConfig');
const jitsiInterfaceConfig = this.safeParseJSONstring(allProps.get("jitsiInterfaceConfig") as string|undefined, 'jitsiInterfaceConfig');
const jitsiUrl = allProps.get("jitsiUrl") as string|undefined;
jitsiFactory.start(roomName, this.playerName, jwt, jitsiConfig, jitsiInterfaceConfig, jitsiUrl);
this.connection.setSilent(true);
mediaManager.hideGameOverlay();
@ -1234,12 +1229,33 @@ export class GameScene extends ResizableScene implements CenterListener {
}
public stopJitsi(): void {
this.connection.setSilent(false);
this.connection?.setSilent(false);
jitsiFactory.stop();
mediaManager.showGameOverlay();
mediaManager.removeTriggerCloseJitsiFrameButton('close-jisi');
}
//todo: put this into an 'orchestrator' scene (EntryScene?)
private bannedUser(){
this.cleanupClosingScene();
this.userInputManager.clearAllKeys();
this.scene.start(ErrorSceneName, {
title: 'Banned',
subTitle: 'You were banned from WorkAdventure',
message: 'If you want more information, you may contact us at: workadventure@thecodingmachine.com'
});
}
//todo: put this into an 'orchestrator' scene (EntryScene?)
private showWorldFullError(): void {
this.cleanupClosingScene();
this.scene.stop(ReconnectingSceneName);
this.userInputManager.clearAllKeys();
this.scene.start(ErrorSceneName, {
title: 'Connection rejected',
subTitle: 'The world you are trying to join is full. Try again later.',
message: 'If you want more information, you may contact us at: workadventure@thecodingmachine.com'
});
}
}

View File

@ -43,8 +43,9 @@ export class ActionableItem {
}
this.isSelectable = true;
if (this.sprite.pipeline) {
this.sprite.setPipeline(OutlinePipeline.KEY);
this.sprite.pipeline.set2f('uTextureSize', this.sprite.texture.getSourceImage().width, this.sprite.texture.getSourceImage().height);
// Commented out to try to fix MacOS issue
/*this.sprite.setPipeline(OutlinePipeline.KEY);
this.sprite.pipeline.set2f('uTextureSize', this.sprite.texture.getSourceImage().width, this.sprite.texture.getSourceImage().height);*/
}
}
@ -56,7 +57,8 @@ export class ActionableItem {
return;
}
this.isSelectable = false;
this.sprite.resetPipeline();
// Commented out to try to fix MacOS issue
//this.sprite.resetPipeline();
}
/**

View File

@ -0,0 +1,41 @@
import {ResizableScene} from "./ResizableScene";
import {localUserStore} from "../../Connexion/LocalUserStore";
import {BodyResourceDescriptionInterface} from "../Entity/PlayerTextures";
import {loadCustomTexture} from "../Entity/PlayerTexturesLoadingManager";
import {CharacterTexture} from "../../Connexion/LocalUser";
export abstract class AbstractCharacterScene extends ResizableScene {
loadCustomSceneSelectCharacters() : Promise<BodyResourceDescriptionInterface[]> {
const textures = this.getTextures();
const promises : Promise<BodyResourceDescriptionInterface>[] = [];
if (textures) {
for (const texture of textures) {
if (texture.level === -1) {
continue;
}
promises.push(loadCustomTexture(this.load, texture));
}
}
return Promise.all(promises)
}
loadSelectSceneCharacters() : Promise<BodyResourceDescriptionInterface[]> {
const textures = this.getTextures();
const promises: Promise<BodyResourceDescriptionInterface>[] = [];
if (textures) {
for (const texture of textures) {
if (texture.level !== -1) {
continue;
}
promises.push(loadCustomTexture(this.load, texture));
}
}
return Promise.all(promises)
}
private getTextures() : CharacterTexture[]|undefined{
const localUser = localUserStore.getLocalUser();
return localUser?.textures;
}
}

View File

@ -10,6 +10,7 @@ import {ResizableScene} from "./ResizableScene";
import {localUserStore} from "../../Connexion/LocalUserStore";
import {addLoader} from "../Components/Loader";
import {BodyResourceDescriptionInterface} from "../Entity/PlayerTextures";
import {AbstractCharacterScene} from "./AbstractCharacterScene";
export const CustomizeSceneName = "CustomizeScene";
@ -20,7 +21,7 @@ enum CustomizeTextures{
arrowUp = "arrow_up",
}
export class CustomizeScene extends ResizableScene {
export class CustomizeScene extends AbstractCharacterScene {
private textField!: TextField;
private enterField!: TextField;
@ -48,29 +49,21 @@ export class CustomizeScene extends ResizableScene {
preload() {
addLoader(this);
this.layers = loadAllLayers(this.load);
this.loadCustomSceneSelectCharacters().then((bodyResourceDescriptions) => {
bodyResourceDescriptions.forEach((bodyResourceDescription) => {
if(!bodyResourceDescription.level){
throw 'Texture level is null';
}
this.layers[bodyResourceDescription.level].unshift(bodyResourceDescription);
});
});
this.load.image(CustomizeTextures.arrowRight, "resources/objects/arrow_right.png");
this.load.image(CustomizeTextures.icon, "resources/logos/tcm_full.png");
this.load.image(CustomizeTextures.arrowUp, "resources/objects/arrow_up.png");
this.load.bitmapFont(CustomizeTextures.mainFont, 'resources/fonts/arcade.png', 'resources/fonts/arcade.xml');
this.layers = loadAllLayers(this.load);
const localUser = localUserStore.getLocalUser();
const textures = localUser?.textures;
if (textures) {
for (const texture of textures) {
if(texture.level === -1){
continue;
}
loadCustomTexture(this.load, texture);
const name = 'customCharacterTexture'+texture.id;
this.layers[texture.level].unshift({
name,
img: texture.url
});
}
}
}
create() {

View File

@ -1,8 +1,6 @@
import {gameManager} from "../Game/GameManager";
import {TextField} from "../Components/TextField";
import Image = Phaser.GameObjects.Image;
import {GameSceneInitInterface} from "../Game/GameScene";
import {StartMapInterface} from "../../Connexion/ConnexionModels";
import {mediaManager} from "../../WebRtc/MediaManager";
import {RESOLUTION} from "../../Enum/EnvironmentVariable";
import {SoundMeter} from "../Components/SoundMeter";
@ -18,6 +16,7 @@ enum LoginTextures {
arrowUp = "arrow_up"
}
export class EnableCameraScene extends Phaser.Scene {
private textField!: TextField;
private pressReturnField!: TextField;
@ -62,26 +61,22 @@ export class EnableCameraScene extends Phaser.Scene {
this.microphoneNameField = new TextField(this, this.game.renderer.width / 2, this.game.renderer.height - 40, '');
this.arrowRight = new Image(this, 0, 0, LoginTextures.arrowRight);
this.arrowRight.setOrigin(0.5, 0.5);
this.arrowRight.setVisible(false);
this.arrowRight.setInteractive().on('pointerdown', this.nextCam.bind(this));
this.add.existing(this.arrowRight);
this.arrowLeft = new Image(this, 0, 0, LoginTextures.arrowRight);
this.arrowLeft.setOrigin(0.5, 0.5);
this.arrowLeft.setVisible(false);
this.arrowLeft.flipX = true;
this.arrowLeft.setInteractive().on('pointerdown', this.previousCam.bind(this));
this.add.existing(this.arrowLeft);
this.arrowUp = new Image(this, 0, 0, LoginTextures.arrowUp);
this.arrowUp.setOrigin(0.5, 0.5);
this.arrowUp.setVisible(false);
this.arrowUp.setInteractive().on('pointerdown', this.previousMic.bind(this));
this.add.existing(this.arrowUp);
this.arrowDown = new Image(this, 0, 0, LoginTextures.arrowUp);
this.arrowDown.setOrigin(0.5, 0.5);
this.arrowDown.setVisible(false);
this.arrowDown.flipY = true;
this.arrowDown.setInteractive().on('pointerdown', this.nextMic.bind(this));
@ -165,8 +160,6 @@ export class EnableCameraScene extends Phaser.Scene {
private updateWebCamName(): void {
if (this.camerasList.length > 1) {
const div = HtmlUtils.getElementByIdOrFail<HTMLVideoElement>('myCamVideoSetup');
let label = this.camerasList[this.cameraSelected].label;
// remove text in parenthesis
label = label.replace(/\([^()]*\)/g, '').trim();
@ -174,17 +167,8 @@ export class EnableCameraScene extends Phaser.Scene {
label = label.normalize("NFD").replace(/[\u0300-\u036f]/g, "");
this.cameraNameField.text = label;
if (this.cameraSelected < this.camerasList.length - 1) {
this.arrowRight.setVisible(true);
} else {
this.arrowRight.setVisible(false);
}
if (this.cameraSelected > 0) {
this.arrowLeft.setVisible(true);
} else {
this.arrowLeft.setVisible(false);
}
this.arrowRight.setVisible(this.cameraSelected < this.camerasList.length - 1);
this.arrowLeft.setVisible(this.cameraSelected > 0);
}
if (this.microphonesList.length > 1) {
let label = this.microphonesList[this.microphoneSelected].label;
@ -195,17 +179,8 @@ export class EnableCameraScene extends Phaser.Scene {
this.microphoneNameField.text = label;
if (this.microphoneSelected < this.microphonesList.length - 1) {
this.arrowDown.setVisible(true);
} else {
this.arrowDown.setVisible(false);
}
if (this.microphoneSelected > 0) {
this.arrowUp.setVisible(true);
} else {
this.arrowUp.setVisible(false);
}
this.arrowDown.setVisible(this.microphoneSelected < this.microphonesList.length - 1);
this.arrowUp.setVisible(this.microphoneSelected > 0);
}
this.reposition();

View File

@ -2,7 +2,6 @@ import {gameManager} from "../Game/GameManager";
import {TextField} from "../Components/TextField";
import {TextInput} from "../Components/TextInput";
import Image = Phaser.GameObjects.Image;
import {cypressAsserter} from "../../Cypress/CypressAsserter";
import {SelectCharacterSceneName} from "./SelectCharacterScene";
import {ResizableScene} from "./ResizableScene";
@ -29,16 +28,13 @@ export class LoginScene extends ResizableScene {
}
preload() {
cypressAsserter.preloadStarted();
//this.load.image(LoginTextures.playButton, "resources/objects/play_button.png");
this.load.image(LoginTextures.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(LoginTextures.mainFont, 'resources/fonts/arcade.png', 'resources/fonts/arcade.xml');
cypressAsserter.preloadFinished();
}
create() {
cypressAsserter.initStarted();
this.textField = new TextField(this, this.game.renderer.width / 2, 50, 'Enter your name:');
this.nameInput = new TextInput(this, this.game.renderer.width / 2, 70, 8, this.name,(text: string) => {
@ -59,8 +55,6 @@ export class LoginScene extends ResizableScene {
}
this.login(this.name);
});
cypressAsserter.initFinished();
}
update(time: number, delta: number): void {

View File

@ -9,6 +9,7 @@ import {localUserStore} from "../../Connexion/LocalUserStore";
import {loadAllDefaultModels, loadCustomTexture} from "../Entity/PlayerTexturesLoadingManager";
import {addLoader} from "../Components/Loader";
import {BodyResourceDescriptionInterface} from "../Entity/PlayerTextures";
import {AbstractCharacterScene} from "./AbstractCharacterScene";
//todo: put this constants in a dedicated file
@ -21,7 +22,7 @@ enum LoginTextures {
customizeButtonSelected = "customize_button_selected"
}
export class SelectCharacterScene extends ResizableScene {
export class SelectCharacterScene extends AbstractCharacterScene {
private readonly nbCharactersPerRow = 6;
private textField!: TextField;
private pressReturnField!: TextField;
@ -44,6 +45,13 @@ export class SelectCharacterScene extends ResizableScene {
preload() {
addLoader(this);
this.loadSelectSceneCharacters().then((bodyResourceDescriptions) => {
bodyResourceDescriptions.forEach((bodyResourceDescription) => {
this.playerModels.push(bodyResourceDescription);
});
})
this.load.image(LoginTextures.playButton, "resources/objects/play_button.png");
this.load.image(LoginTextures.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
@ -52,17 +60,7 @@ export class SelectCharacterScene extends ResizableScene {
this.load.image(LoginTextures.customizeButton, 'resources/objects/customize.png');
this.load.image(LoginTextures.customizeButtonSelected, 'resources/objects/customize_selected.png');
const localUser = localUserStore.getLocalUser();
const textures = localUser?.textures;
if (textures) {
for (const texture of textures) {
if(texture.level !== -1){
continue;
}
const name = loadCustomTexture(this.load, texture);
this.playerModels.push({name: name, img: texture.url});
}
}
addLoader(this);
}
create() {
@ -127,7 +125,7 @@ export class SelectCharacterScene extends ResizableScene {
/*create user*/
this.createCurrentPlayer();
const playerNumber = localUserStore.getPlayerCharacterIndex();
if (playerNumber && playerNumber !== -1) {
this.selectedRectangleXPos = playerNumber % this.nbCharactersPerRow;

View File

@ -0,0 +1,95 @@
import {mediaManager} from "../../WebRtc/MediaManager";
import {HtmlUtils} from "../../WebRtc/HtmlUtils";
import {localUserStore} from "../../Connexion/LocalUserStore";
export const HelpCameraSettingsSceneName = 'HelpCameraSettingsScene';
const helpCameraSettings = 'helpCameraSettings';
/**
* The scene that show how to permit Camera and Microphone access if there are not already allowed
*/
export class HelpCameraSettingsScene extends Phaser.Scene {
private helpCameraSettingsElement!: Phaser.GameObjects.DOMElement;
private helpCameraSettingsOpened: boolean = false;
constructor() {
super({key: HelpCameraSettingsSceneName});
}
preload() {
this.load.html(helpCameraSettings, 'resources/html/helpCameraSettings.html');
}
create(){
localUserStore.setHelpCameraSettingsShown();
this.createHelpCameraSettings();
}
private createHelpCameraSettings() : void {
const middleX = (window.innerWidth / 3) - (370*0.85);
this.helpCameraSettingsElement = this.add.dom(middleX, -800, undefined, {overflow: 'scroll'}).createFromCache(helpCameraSettings);
this.revealMenusAfterInit(this.helpCameraSettingsElement, helpCameraSettings);
this.helpCameraSettingsElement.addListener('click');
this.helpCameraSettingsElement.on('click', (event:MouseEvent) => {
event.preventDefault();
if((event?.target as HTMLInputElement).id === 'helpCameraSettingsFormRefresh') {
window.location.reload();
}else if((event?.target as HTMLInputElement).id === 'helpCameraSettingsFormContinue') {
this.closeHelpCameraSettingsOpened();
}
});
if(!mediaManager.constraintsMedia.audio || !mediaManager.constraintsMedia.video){
this.openHelpCameraSettingsOpened();
}
}
private openHelpCameraSettingsOpened(): void{
HtmlUtils.getElementByIdOrFail<HTMLDivElement>('webRtcSetup').style.display = 'none';
this.helpCameraSettingsOpened = true;
let middleY = (window.innerHeight / 3) - (495);
if(middleY < 0){
middleY = 0;
}
let middleX = (window.innerWidth / 3) - (370*0.85);
if(middleX < 0){
middleX = 0;
}
if(window.navigator.userAgent.includes('Firefox')){
HtmlUtils.getElementByIdOrFail<HTMLParagraphElement>('browserHelpSetting').innerHTML ='<img src="/resources/objects/help-setting-camera-permission-firefox.png"/>';
}else if(window.navigator.userAgent.includes('Chrome')){
HtmlUtils.getElementByIdOrFail<HTMLParagraphElement>('browserHelpSetting').innerHTML ='<img src="/resources/objects/help-setting-camera-permission-chrome.png"/>';
}
this.tweens.add({
targets: this.helpCameraSettingsElement,
y: middleY,
x: middleX,
duration: 1000,
ease: 'Power3',
overflow: 'scroll'
});
}
private closeHelpCameraSettingsOpened(): void{
const helpCameraSettingsInfo = this.helpCameraSettingsElement.getChildByID('helpCameraSettings') as HTMLParagraphElement;
helpCameraSettingsInfo.innerText = '';
helpCameraSettingsInfo.style.display = 'none';
this.helpCameraSettingsOpened = false;
this.tweens.add({
targets: this.helpCameraSettingsElement,
y: -400,
duration: 1000,
ease: 'Power3',
overflow: 'scroll'
});
}
private revealMenusAfterInit(menuElement: Phaser.GameObjects.DOMElement, rootDomId: string) {
//Dom elements will appear inside the viewer screen when creating before being moved out of it, which create a flicker effect.
//To prevent this, we put a 'hidden' attribute on the root element, we remove it only after the init is done.
setTimeout(() => {
(menuElement.getChildByID(rootDomId) as HTMLElement).hidden = false;
}, 250);
}
}

View File

@ -3,7 +3,11 @@ import {SelectCharacterScene, SelectCharacterSceneName} from "../Login/SelectCha
import {gameManager} from "../Game/GameManager";
import {localUserStore} from "../../Connexion/LocalUserStore";
import {mediaManager} from "../../WebRtc/MediaManager";
import {coWebsiteManager} from "../../WebRtc/CoWebsiteManager";
import {gameReportKey, gameReportRessource, ReportMenu} from "./ReportMenu";
import {connectionManager} from "../../Connexion/ConnectionManager";
import {GameConnexionTypes} from "../../Url/UrlManager";
import {WarningContainer, warningContainerHtml, warningContainerKey} from "../Components/WarningContainer";
import {worldFullWarningStream} from "../../Connexion/WorldFullWarningStream";
export const MenuSceneName = 'MenuScene';
const gameMenuKey = 'gameMenu';
@ -21,12 +25,15 @@ export class MenuScene extends Phaser.Scene {
private menuElement!: Phaser.GameObjects.DOMElement;
private gameQualityMenuElement!: Phaser.GameObjects.DOMElement;
private gameShareElement!: Phaser.GameObjects.DOMElement;
private gameReportElement!: ReportMenu;
private sideMenuOpened = false;
private settingsMenuOpened = false;
private gameShareOpened = false;
private gameQualityValue: number;
private videoQualityValue: number;
private menuButton!: Phaser.GameObjects.DOMElement;
private warningContainer: WarningContainer | null = null;
private warningContainerTimeout: NodeJS.Timeout | null = null;
constructor() {
super({key: MenuSceneName});
@ -40,20 +47,22 @@ export class MenuScene extends Phaser.Scene {
this.load.html(gameMenuIconKey, 'resources/html/gameMenuIcon.html');
this.load.html(gameSettingsMenuKey, 'resources/html/gameQualityMenu.html');
this.load.html(gameShare, 'resources/html/gameShare.html');
this.load.html(gameReportKey, gameReportRessource);
this.load.html(warningContainerKey, warningContainerHtml);
}
create() {
this.menuElement = this.add.dom(closedSideMenuX, 30).createFromCache(gameMenuKey);
this.menuElement.setOrigin(0);
this.revealMenusAfterInit(this.menuElement, 'gameMenu');
MenuScene.revealMenusAfterInit(this.menuElement, 'gameMenu');
const middleX = (window.innerWidth / 3) - 298;
this.gameQualityMenuElement = this.add.dom(middleX, -400).createFromCache(gameSettingsMenuKey);
this.revealMenusAfterInit(this.gameQualityMenuElement, 'gameQuality');
MenuScene.revealMenusAfterInit(this.gameQualityMenuElement, 'gameQuality');
this.gameShareElement = this.add.dom(middleX, -400).createFromCache(gameShare);
this.revealMenusAfterInit(this.gameShareElement, gameShare);
MenuScene.revealMenusAfterInit(this.gameShareElement, gameShare);
this.gameShareElement.addListener('click');
this.gameShareElement.on('click', (event:MouseEvent) => {
event.preventDefault();
@ -64,6 +73,12 @@ export class MenuScene extends Phaser.Scene {
}
});
this.gameReportElement = new ReportMenu(this, connectionManager.getConnexionType === GameConnexionTypes.anonymous);
mediaManager.setShowReportModalCallBacks((userId, userName) => {
this.closeAll();
this.gameReportElement.open(parseInt(userId), userName);
});
this.input.keyboard.on('keyup-TAB', () => {
this.sideMenuOpened ? this.closeSideMenu() : this.openSideMenu();
});
@ -75,9 +90,12 @@ export class MenuScene extends Phaser.Scene {
this.menuElement.addListener('click');
this.menuElement.on('click', this.onMenuClick.bind(this));
worldFullWarningStream.stream.subscribe(() => this.showWorldCapacityWarning());
}
private revealMenusAfterInit(menuElement: Phaser.GameObjects.DOMElement, rootDomId: string) {
//todo put this method in a parent menuElement class
static revealMenusAfterInit(menuElement: Phaser.GameObjects.DOMElement, rootDomId: string) {
//Dom elements will appear inside the viewer screen when creating before being moved out of it, which create a flicker effect.
//To prevent this, we put a 'hidden' attribute on the root element, we remove it only after the init is done.
setTimeout(() => {
@ -98,6 +116,11 @@ export class MenuScene extends Phaser.Scene {
const adminSection = this.menuElement.getChildByID('adminConsoleSection') as HTMLElement;
adminSection.hidden = false;
}
//TODO bind with future metadata of card
//if (connectionManager.getConnexionType === GameConnexionTypes.anonymous){
const adminSection = this.menuElement.getChildByID('socialLinks') as HTMLElement;
adminSection.hidden = false;
//}
this.tweens.add({
targets: this.menuElement,
x: openedSideMenuX,
@ -105,6 +128,21 @@ export class MenuScene extends Phaser.Scene {
ease: 'Power3'
});
}
private showWorldCapacityWarning() {
if (!this.warningContainer) {
this.warningContainer = new WarningContainer(this);
}
if (this.warningContainerTimeout) {
clearTimeout(this.warningContainerTimeout);
}
this.warningContainerTimeout = setTimeout(() => {
this.warningContainer?.destroy();
this.warningContainer = null
this.warningContainerTimeout = null
}, 120000);
}
private closeSideMenu(): void {
if (!this.sideMenuOpened) return;
@ -222,6 +260,9 @@ export class MenuScene extends Phaser.Scene {
}
private onMenuClick(event:MouseEvent) {
if((event?.target as HTMLInputElement).classList.contains('not-button')){
return;
}
event.preventDefault();
switch ((event?.target as HTMLInputElement).id) {
@ -280,5 +321,6 @@ export class MenuScene extends Phaser.Scene {
private closeAll(){
this.closeGameQualityMenu();
this.closeGameShare();
this.gameReportElement.close();
}
}

View File

@ -0,0 +1,118 @@
import {MenuScene} from "./MenuScene";
import {gameManager} from "../Game/GameManager";
import {blackListManager} from "../../WebRtc/BlackListManager";
export const gameReportKey = 'gameReport';
export const gameReportRessource = 'resources/html/gameReport.html';
export class ReportMenu extends Phaser.GameObjects.DOMElement {
private opened: boolean = false;
private userId!: number;
private userName!: string|undefined;
private anonymous: boolean;
constructor(scene: Phaser.Scene, anonymous: boolean) {
super(scene, -2000, -2000);
this.anonymous = anonymous;
this.createFromCache(gameReportKey);
if (this.anonymous) {
const divToHide = this.getChildByID('reportSection') as HTMLElement;
divToHide.hidden = true;
const textToHide = this.getChildByID('askActionP') as HTMLElement;
textToHide.hidden = true;
}
scene.add.existing(this);
MenuScene.revealMenusAfterInit(this, gameReportKey);
this.addListener('click');
this.on('click', (event:MouseEvent) => {
event.preventDefault();
if ((event?.target as HTMLInputElement).id === 'gameReportFormSubmit') {
this.submitReport();
} else if((event?.target as HTMLInputElement).id === 'gameReportFormCancel') {
this.close();
} else if((event?.target as HTMLInputElement).id === 'toggleBlockButton') {
this.toggleBlock();
}
});
}
public open(userId: number, userName: string|undefined): void {
if (this.opened) {
this.close();
return;
}
this.userId = userId;
this.userName = userName;
const mainEl = this.getChildByID('gameReport') as HTMLElement;
this.x = this.getCenteredX(mainEl);
this.y = this.getHiddenY(mainEl);
const gameTitleReport = this.getChildByID('nameReported') as HTMLElement;
gameTitleReport.innerText = userName || '';
const blockButton = this.getChildByID('toggleBlockButton') as HTMLElement;
blockButton.innerText = blackListManager.isBlackListed(this.userId) ? 'Unblock this user' : 'Block this user';
this.opened = true;
gameManager.getCurrentGameScene(this.scene).userInputManager.clearAllKeys();
this.scene.tweens.add({
targets: this,
y: this.getCenteredY(mainEl),
duration: 1000,
ease: 'Power3'
});
}
public close(): void {
this.opened = false;
gameManager.getCurrentGameScene(this.scene).userInputManager.initKeyBoardEvent();
const mainEl = this.getChildByID('gameReport') as HTMLElement;
this.scene.tweens.add({
targets: this,
y: this.getHiddenY(mainEl),
duration: 1000,
ease: 'Power3'
});
}
//todo: into a parent class?
private getCenteredX(mainEl: HTMLElement): number {
return window.innerWidth / 4 - mainEl.clientWidth / 2;
}
private getHiddenY(mainEl: HTMLElement): number {
return - mainEl.clientHeight - 50;
}
private getCenteredY(mainEl: HTMLElement): number {
return window.innerHeight / 4 - mainEl.clientHeight / 2;
}
private toggleBlock(): void {
!blackListManager.isBlackListed(this.userId) ? blackListManager.blackList(this.userId) : blackListManager.cancelBlackList(this.userId);
this.close();
}
private submitReport(): void{
const gamePError = this.getChildByID('gameReportErr') as HTMLParagraphElement;
gamePError.innerText = '';
gamePError.style.display = 'none';
const gameTextArea = this.getChildByID('gameReportInput') as HTMLInputElement;
if(!gameTextArea || !gameTextArea.value){
gamePError.innerText = 'Report message cannot to be empty.';
gamePError.style.display = 'block';
return;
}
gameManager.getCurrentGameScene(this.scene).connection.emitReportPlayerMessage(
this.userId,
gameTextArea.value
);
this.close();
}
}

View File

@ -1,9 +1,13 @@
export enum PlayerAnimationNames {
WalkDown = 'down',
WalkLeft = 'left',
WalkUp = 'up',
WalkRight = 'right',
export enum PlayerAnimationDirections {
Down = 'down',
Left = 'left',
Up = 'up',
Right = 'right',
}
export enum PlayerAnimationTypes {
Walk = 'walk',
Idle = 'idle',
}

View File

@ -1,4 +1,4 @@
import {PlayerAnimationNames} from "./Animation";
import {PlayerAnimationDirections} from "./Animation";
import {GameScene} from "../Game/GameScene";
import {UserInputEvent, UserInputManager} from "../UserInput/UserInputManager";
import {Character} from "../Entity/Character";
@ -11,7 +11,7 @@ export interface CurrentGamerInterface extends Character{
}
export class Player extends Character implements CurrentGamerInterface {
private previousDirection: string = PlayerAnimationNames.WalkDown;
private previousDirection: string = PlayerAnimationDirections.Down;
private wasMoving: boolean = false;
constructor(
@ -20,7 +20,7 @@ export class Player extends Character implements CurrentGamerInterface {
y: number,
name: string,
texturesPromise: Promise<string[]>,
direction: string,
direction: PlayerAnimationDirections,
moving: boolean,
private userInputManager: UserInputManager
) {
@ -43,20 +43,20 @@ export class Player extends Character implements CurrentGamerInterface {
let y = 0;
if (activeEvents.get(UserInputEvent.MoveUp)) {
y = - moveAmount;
direction = PlayerAnimationNames.WalkUp;
direction = PlayerAnimationDirections.Up;
moving = true;
} else if (activeEvents.get(UserInputEvent.MoveDown)) {
y = moveAmount;
direction = PlayerAnimationNames.WalkDown;
direction = PlayerAnimationDirections.Down;
moving = true;
}
if (activeEvents.get(UserInputEvent.MoveLeft)) {
x = -moveAmount;
direction = PlayerAnimationNames.WalkLeft;
direction = PlayerAnimationDirections.Left;
moving = true;
} else if (activeEvents.get(UserInputEvent.MoveRight)) {
x = moveAmount;
direction = PlayerAnimationNames.WalkRight;
direction = PlayerAnimationDirections.Right;
moving = true;
}
if (x !== 0 || y !== 0) {

View File

@ -59,7 +59,11 @@ export class UserInputManager {
];
}
clearAllInputKeyboard(){
clearAllListeners(){
this.Scene.input.keyboard.removeAllListeners();
}
clearAllKeys(){
this.Scene.input.keyboard.removeAllKeys();
}

View File

@ -1,5 +1,6 @@
import {HtmlUtils} from "./HtmlUtils";
import {isUndefined} from "generic-type-guard";
import {localUserStore} from "../Connexion/LocalUserStore";
enum audioStates {
closed = 0,
@ -9,6 +10,8 @@ enum audioStates {
const audioPlayerDivId = "audioplayer";
const audioPlayerCtrlId = "audioplayerctrl";
const audioPlayerVolId = "audioplayer_volume";
const audioPlayerMuteId = "audioplayer_volume_icon_playing";
const animationTime = 500;
class AudioManager {
@ -17,6 +20,8 @@ class AudioManager {
private audioPlayerDiv: HTMLDivElement;
private audioPlayerCtrl: HTMLDivElement;
private audioPlayerElem: HTMLAudioElement | undefined;
private audioPlayerVol: HTMLInputElement;
private audioPlayerMute: HTMLInputElement;
private volume = 1;
private muted = false;
@ -26,16 +31,35 @@ class AudioManager {
constructor() {
this.audioPlayerDiv = HtmlUtils.getElementByIdOrFail<HTMLDivElement>(audioPlayerDivId);
this.audioPlayerCtrl = HtmlUtils.getElementByIdOrFail<HTMLDivElement>(audioPlayerCtrlId);
this.audioPlayerVol = HtmlUtils.getElementByIdOrFail<HTMLInputElement>(audioPlayerVolId);
this.audioPlayerMute = HtmlUtils.getElementByIdOrFail<HTMLInputElement>(audioPlayerMuteId);
const storedVolume = localStorage.getItem('volume')
if (storedVolume === null) {
this.setVolume(1);
this.volume = localUserStore.getAudioPlayerVolume();
this.audioPlayerVol.value = '' + this.volume;
this.muted = localUserStore.getAudioPlayerMuted();
if (this.muted) {
this.audioPlayerMute.classList.add('muted');
}
}
public playAudio(url: string|number|boolean, mapDirUrl: string, volume: number|undefined, loop=false): void {
const audioPath = url as string;
let realAudioPath = '';
if (audioPath.indexOf('://') > 0) {
// remote file or stream
realAudioPath = audioPath;
} else {
this.volume = parseFloat(storedVolume);
HtmlUtils.getElementByIdOrFail<HTMLInputElement>('audioplayer_volume').value = storedVolume;
// local file, include it relative to map directory
realAudioPath = mapDirUrl + '/' + url;
}
HtmlUtils.getElementByIdOrFail<HTMLInputElement>('audioplayer_volume').value = '' + this.volume;
this.loadAudio(realAudioPath, volume);
if (loop) {
this.loop();
}
}
private close(): void {
@ -56,26 +80,29 @@ class AudioManager {
}
private changeVolume(talking = false): void {
if (!isUndefined(this.audioPlayerElem)) {
this.audioPlayerElem.volume = this.naturalVolume(talking && this.decreaseWhileTalking);
this.audioPlayerElem.muted = this.muted;
if (isUndefined(this.audioPlayerElem)) {
return;
}
}
private naturalVolume(makeSofter: boolean = false): number {
const volume = this.volume
const retVol = makeSofter && !this.volumeReduced ? Math.pow(volume * 0.5, 3) : volume
this.volumeReduced = makeSofter
return retVol;
const reduceVolume = talking && this.decreaseWhileTalking;
if (reduceVolume && !this.volumeReduced) {
this.volume *= 0.5;
} else if (!reduceVolume && this.volumeReduced) {
this.volume *= 2.0;
}
this.volumeReduced = reduceVolume;
this.audioPlayerElem.volume = this.volume;
this.audioPlayerVol.value = '' + this.volume;
this.audioPlayerElem.muted = this.muted;
}
private setVolume(volume: number): void {
this.volume = volume;
localStorage.setItem('volume', '' + volume);
localUserStore.setAudioPlayerVolume(volume);
}
public loadAudio(url: string): void {
private loadAudio(url: string, volume: number|undefined): void {
this.load();
/* Solution 1, remove whole audio player */
@ -93,23 +120,24 @@ class AudioManager {
this.audioPlayerElem.append(srcElem);
this.audioPlayerDiv.append(this.audioPlayerElem);
this.volume = volume ? Math.min(volume, this.volume) : this.volume;
this.changeVolume();
this.audioPlayerElem.play();
const muteElem = HtmlUtils.getElementByIdOrFail<HTMLInputElement>('audioplayer_mute');
muteElem.onclick = (ev: Event)=> {
muteElem.onclick = (ev: Event) => {
this.muted = !this.muted;
this.changeVolume();
localUserStore.setAudioPlayerMuted(this.muted);
if (this.muted) {
HtmlUtils.getElementByIdOrFail<HTMLInputElement>('audioplayer_volume_icon_playing').classList.add('muted');
this.audioPlayerMute.classList.add('muted');
} else {
HtmlUtils.getElementByIdOrFail<HTMLInputElement>('audioplayer_volume_icon_playing').classList.remove('muted');
this.audioPlayerMute.classList.remove('muted');
}
}
const volumeElem = HtmlUtils.getElementByIdOrFail<HTMLInputElement>('audioplayer_volume');
volumeElem.oninput = (ev: Event)=> {
this.audioPlayerVol.oninput = (ev: Event)=> {
this.setVolume(parseFloat((<HTMLInputElement>ev.currentTarget).value));
this.changeVolume();
@ -125,7 +153,7 @@ class AudioManager {
this.open();
}
public loop(): void {
private loop(): void {
if (this.audioPlayerElem !== undefined) {
this.audioPlayerElem.loop = true;
}

View File

@ -0,0 +1,24 @@
import {Subject} from 'rxjs';
class BlackListManager {
private list: number[] = [];
public onBlockStream: Subject<number> = new Subject();
public onUnBlockStream: Subject<number> = new Subject();
isBlackListed(userId: number): boolean {
return this.list.find((data) => data === userId) !== undefined;
}
blackList(userId: number): void {
if (this.isBlackListed(userId)) return;
this.list.push(userId);
this.onBlockStream.next(userId);
}
cancelBlackList(userId: number): void {
this.list.splice(this.list.findIndex(data => data === userId), 1);
this.onUnBlockStream.next(userId);
}
}
export const blackListManager = new BlackListManager();

View File

@ -1,6 +1,5 @@
import {HtmlUtils} from "./HtmlUtils";
export type CoWebsiteStateChangedCallback = () => void;
import {Subject} from "rxjs";
enum iframeStates {
closed = 1,
@ -8,29 +7,102 @@ enum iframeStates {
opened,
}
const cowebsiteDivId = "cowebsite"; // the id of the parent div of the iframe.
const cowebsiteDivId = 'cowebsite'; // the id of the whole container.
const cowebsiteMainDomId = 'cowebsite-main'; // the id of the parent div of the iframe.
const cowebsiteAsideDomId = 'cowebsite-aside'; // the id of the parent div of the iframe.
const cowebsiteCloseButtonId = 'cowebsite-close';
const cowebsiteFullScreenButtonId = 'cowebsite-fullscreen';
const cowebsiteOpenFullScreenImageId = 'cowebsite-fullscreen-open';
const cowebsiteCloseFullScreenImageId = 'cowebsite-fullscreen-close';
const animationTime = 500; //time used by the css transitions, in ms.
class CoWebsiteManager {
private opened: iframeStates = iframeStates.closed;
private observers = new Array<CoWebsiteStateChangedCallback>();
private _onResize: Subject<void> = new Subject();
public onResize = this._onResize.asObservable();
/**
* Quickly going in and out of an iframe trigger can create conflicts between the iframe states.
* So we use this promise to queue up every cowebsite state transition
*/
private currentOperationPromise: Promise<void> = Promise.resolve();
private cowebsiteDiv: HTMLDivElement;
private resizing: boolean = false;
private cowebsiteMainDom: HTMLDivElement;
private cowebsiteAsideDom: HTMLDivElement;
get width(): number {
return this.cowebsiteDiv.clientWidth;
}
set width(width: number) {
this.cowebsiteDiv.style.width = width+'px';
}
get height(): number {
return this.cowebsiteDiv.clientHeight;
}
set height(height: number) {
this.cowebsiteDiv.style.height = height+'px';
}
get verticalMode(): boolean {
return window.innerWidth < window.innerHeight;
}
get isFullScreen(): boolean {
return this.verticalMode ? this.height === window.innerHeight : this.width === window.innerWidth;
}
constructor() {
this.cowebsiteDiv = HtmlUtils.getElementByIdOrFail<HTMLDivElement>(cowebsiteDivId);
this.cowebsiteMainDom = HtmlUtils.getElementByIdOrFail<HTMLDivElement>(cowebsiteMainDomId);
this.cowebsiteAsideDom = HtmlUtils.getElementByIdOrFail<HTMLDivElement>(cowebsiteAsideDomId);
this.initResizeListeners();
HtmlUtils.getElementByIdOrFail(cowebsiteCloseButtonId).addEventListener('click', () => {
this.closeCoWebsite();
});
HtmlUtils.getElementByIdOrFail(cowebsiteFullScreenButtonId).addEventListener('click', () => {
this.fullscreen();
});
}
private initResizeListeners() {
const movecallback = (event:MouseEvent) => {
this.verticalMode ? this.height -= event.movementY / this.getDevicePixelRatio() : this.width -= event.movementX / this.getDevicePixelRatio();
this.fire();
}
this.cowebsiteAsideDom.addEventListener('mousedown', (event) => {
this.resizing = true;
this.getIframeDom().style.display = 'none';
document.addEventListener('mousemove', movecallback);
});
document.addEventListener('mouseup', (event) => {
if (!this.resizing) return;
document.removeEventListener('mousemove', movecallback);
this.getIframeDom().style.display = 'block';
this.resizing = false;
});
}
private getDevicePixelRatio(): number {
//on chrome engines, movementX and movementY return global screens coordinates while other browser return pixels
//so on chrome-based browser we need to adjust using 'devicePixelRatio'
return window.navigator.userAgent.includes('Firefox') ? 1 : window.devicePixelRatio;
}
private close(): void {
this.cowebsiteDiv.classList.remove('loaded'); //edit the css class to trigger the transition
this.cowebsiteDiv.classList.add('hidden');
this.opened = iframeStates.closed;
this.resetStyle();
}
private load(): void {
this.cowebsiteDiv.classList.remove('hidden'); //edit the css class to trigger the transition
@ -40,26 +112,34 @@ class CoWebsiteManager {
private open(): void {
this.cowebsiteDiv.classList.remove('loading', 'hidden'); //edit the css class to trigger the transition
this.opened = iframeStates.opened;
this.resetStyle();
}
public loadCoWebsite(url: string): void {
public resetStyle() {
this.cowebsiteDiv.style.width = '';
this.cowebsiteDiv.style.height = '';
}
private getIframeDom(): HTMLIFrameElement {
const iframe = HtmlUtils.getElementByIdOrFail<HTMLDivElement>(cowebsiteDivId).querySelector('iframe');
if (!iframe) throw new Error('Could not find iframe!');
return iframe;
}
public loadCoWebsite(url: string, base: string, allowPolicy?: string): void {
this.load();
this.cowebsiteDiv.innerHTML = `<button class="close-btn" id="cowebsite-close">
<img src="resources/logos/close.svg">
</button>`;
setTimeout(() => {
HtmlUtils.getElementByIdOrFail('cowebsite-close').addEventListener('click', () => {
this.closeCoWebsite();
});
}, 100);
this.cowebsiteMainDom.innerHTML = ``;
const iframe = document.createElement('iframe');
iframe.id = 'cowebsite-iframe';
iframe.src = url;
iframe.src = (new URL(url, base)).toString();
if (allowPolicy) {
iframe.allow = allowPolicy;
}
const onloadPromise = new Promise((resolve) => {
iframe.onload = () => resolve();
});
this.cowebsiteDiv.appendChild(iframe);
this.cowebsiteMainDom.appendChild(iframe);
const onTimeoutPromise = new Promise((resolve) => {
setTimeout(() => resolve(), 2000);
});
@ -76,7 +156,8 @@ class CoWebsiteManager {
*/
public insertCoWebsite(callback: (cowebsite: HTMLDivElement) => Promise<void>): void {
this.load();
this.currentOperationPromise = this.currentOperationPromise.then(() => callback(this.cowebsiteDiv)).then(() => {
this.cowebsiteMainDom.innerHTML = ``;
this.currentOperationPromise = this.currentOperationPromise.then(() => callback(this.cowebsiteMainDom)).then(() => {
this.open();
setTimeout(() => {
this.fire();
@ -90,9 +171,7 @@ class CoWebsiteManager {
this.close();
this.fire();
setTimeout(() => {
this.cowebsiteDiv.innerHTML = `<button class="close-btn" id="cowebsite-close">
<img src="resources/logos/close.svg">
</button>`;
this.cowebsiteMainDom.innerHTML = ``;
resolve();
}, animationTime)
}));
@ -106,29 +185,37 @@ class CoWebsiteManager {
height: window.innerHeight
}
}
if (window.innerWidth >= window.innerHeight) {
if (!this.verticalMode) {
return {
width: window.innerWidth / 2,
width: window.innerWidth - this.width,
height: window.innerHeight
}
} else {
return {
width: window.innerWidth,
height: window.innerHeight / 2
height: window.innerHeight - this.height,
}
}
}
//todo: is it still useful to allow any kind of observers?
public onStateChange(observer: CoWebsiteStateChangedCallback) {
this.observers.push(observer);
}
private fire(): void {
for (const callback of this.observers) {
callback();
this._onResize.next();
}
private fullscreen(): void {
if (this.isFullScreen) {
this.resetStyle();
this.fire();
//we don't trigger a resize of the phaser game since it won't be visible anyway.
HtmlUtils.getElementByIdOrFail(cowebsiteOpenFullScreenImageId).style.display = 'inline';
HtmlUtils.getElementByIdOrFail(cowebsiteCloseFullScreenImageId).style.display = 'none';
} else {
this.verticalMode ? this.height = window.innerHeight : this.width = window.innerWidth;
//we don't trigger a resize of the phaser game since it won't be visible anyway.
HtmlUtils.getElementByIdOrFail(cowebsiteOpenFullScreenImageId).style.display = 'none';
HtmlUtils.getElementByIdOrFail(cowebsiteCloseFullScreenImageId).style.display = 'inline';
}
}
}
export const coWebsiteManager = new CoWebsiteManager();
export const coWebsiteManager = new CoWebsiteManager();

View File

@ -1,5 +1,5 @@
import {HtmlUtils} from "./HtmlUtils";
import {mediaManager, ReportCallback} from "./MediaManager";
import {mediaManager, ReportCallback, ShowReportCallBack} from "./MediaManager";
import {UserInputManager} from "../Phaser/UserInput/UserInputManager";
import {connectionManager} from "../Connexion/ConnectionManager";
import {GameConnexionTypes} from "../Url/UrlManager";
@ -61,7 +61,7 @@ export class DiscussionManager {
const inputMessage: HTMLInputElement = document.createElement('input');
inputMessage.onfocus = () => {
if(this.userInputManager) {
this.userInputManager.clearAllInputKeyboard();
this.userInputManager.clearAllKeys();
}
}
inputMessage.onblur = () => {
@ -99,7 +99,7 @@ export class DiscussionManager {
name: string|undefined,
img?: string|undefined,
isMe: boolean = false,
reportCallback?: ReportCallback
showReportCallBack?: ShowReportCallBack
) {
const divParticipant: HTMLDivElement = document.createElement('div');
divParticipant.classList.add('participant');
@ -128,8 +128,8 @@ export class DiscussionManager {
reportBanUserAction.classList.add('report-btn')
reportBanUserAction.innerText = 'Report';
reportBanUserAction.addEventListener('click', () => {
if(reportCallback) {
mediaManager.showReportModal(`${userId}`, name ?? '', reportCallback);
if(showReportCallBack) {
showReportCallBack(`${userId}`, name);
}else{
console.info('report feature is not activated!');
}

View File

@ -1,27 +1,45 @@
export class HtmlUtils {
public static getElementByIdOrFail<T extends HTMLElement>(id: string): T {
const elem = document.getElementById(id);
if (elem === null) {
throw new Error("Cannot find HTML element with id '"+id+"'");
if (HtmlUtils.isHtmlElement<T>(elem)) {
return elem;
}
// FIXME: does not check the type of the returned type
return elem as T;
throw new Error("Cannot find HTML element with id '"+id+"'");
}
public static querySelectorOrFail<T extends HTMLElement>(selector: string): T {
const elem = document.querySelector<T>(selector);
if (HtmlUtils.isHtmlElement<T>(elem)) {
return elem;
}
throw new Error("Cannot find HTML element with selector '"+selector+"'");
}
public static removeElementByIdOrFail<T extends HTMLElement>(id: string): T {
const elem = document.getElementById(id);
if (elem === null) {
throw new Error("Cannot find HTML element with id '"+id+"'");
if (HtmlUtils.isHtmlElement<T>(elem)) {
elem.remove();
return elem;
}
// FIXME: does not check the type of the returned type
elem.remove();
return elem as T;
throw new Error("Cannot find HTML element with id '"+id+"'");
}
private static escapeHtml(html: string): string {
const text = document.createTextNode(html);
const p = document.createElement('p');
p.appendChild(text);
return p.innerHTML;
}
public static urlify(text: string): string {
const urlRegex = /(https?:\/\/[^\s]+)/g;
text = HtmlUtils.escapeHtml(text);
return text.replace(urlRegex, (url: string) => {
return '<a href="' + url + '" target="_blank" style=":visited {color: white}">' + url + '</a>';
})
});
}
private static isHtmlElement<T extends HTMLElement>(elem: HTMLElement | null): elem is T {
return elem !== null;
}
}

View File

@ -3,11 +3,41 @@ import {mediaManager} from "./MediaManager";
import {coWebsiteManager} from "./CoWebsiteManager";
declare const window:any; // eslint-disable-line @typescript-eslint/no-explicit-any
const interfaceConfig = {
interface jitsiConfigInterface {
startWithAudioMuted: boolean
startWithVideoMuted: boolean
prejoinPageEnabled: boolean
}
const getDefaultConfig = () : jitsiConfigInterface => {
return {
startWithAudioMuted: !mediaManager.constraintsMedia.audio,
startWithVideoMuted: mediaManager.constraintsMedia.video === false,
prejoinPageEnabled: false
}
}
const mergeConfig = (config?: object) => {
const currentDefaultConfig = getDefaultConfig();
if(!config){
return currentDefaultConfig;
}
return {
...currentDefaultConfig,
...config,
startWithAudioMuted: (config as jitsiConfigInterface).startWithAudioMuted ? true : currentDefaultConfig.startWithAudioMuted,
startWithVideoMuted: (config as jitsiConfigInterface).startWithVideoMuted ? true : currentDefaultConfig.startWithVideoMuted,
prejoinPageEnabled: (config as jitsiConfigInterface).prejoinPageEnabled ? true : currentDefaultConfig.prejoinPageEnabled
}
}
const defaultInterfaceConfig = {
SHOW_CHROME_EXTENSION_BANNER: false,
MOBILE_APP_PROMO: false,
HIDE_INVITE_MORE_HEADER: true,
DISABLE_JOIN_LEAVE_NOTIFICATIONS: true,
DISABLE_VIDEO_BACKGROUND: true,
// Note: hiding brand does not seem to work, we probably need to put this on the server side.
SHOW_BRAND_WATERMARK: false,
@ -25,31 +55,64 @@ const interfaceConfig = {
],
};
const slugify = (...args: (string | number)[]): string => {
const value = args.join(' ')
return value
.normalize('NFD') // split an accented letter in the base letter and the accent
.replace(/[\u0300-\u036f]/g, '') // remove all previously split accents
.toLowerCase()
.trim()
.replace(/[^a-z0-9 ]/g, '') // remove all chars not letters, numbers and spaces (to be replaced)
.replace(/\s+/g, '-') // separator
}
class JitsiFactory {
private jitsiApi: any; // eslint-disable-line @typescript-eslint/no-explicit-any
private audioCallback = this.onAudioChange.bind(this);
private videoCallback = this.onVideoChange.bind(this);
public start(roomName: string, playerName:string, jwt?: string): void {
coWebsiteManager.insertCoWebsite((cowebsiteDiv => {
const domain = JITSI_URL;
private previousConfigMeet? : jitsiConfigInterface;
private jitsiScriptLoaded: boolean = false;
/**
* Slugifies the room name and prepends the room name with the instance
*/
public getRoomName(roomName: string, instance: string): string {
return slugify(instance.replace('/', '-') + "-" + roomName);
}
public start(roomName: string, playerName:string, jwt?: string, config?: object, interfaceConfig?: object, jitsiUrl?: string): void {
//save previous config
this.previousConfigMeet = getDefaultConfig();
coWebsiteManager.insertCoWebsite((async cowebsiteDiv => {
// Jitsi meet external API maintains some data in local storage
// which is sent via the appData URL parameter when joining a
// conference. Problem is that this data grows indefinitely. Thus
// after some time the URLs get so huge that loading the iframe
// becomes slow and eventually breaks completely. Thus lets just
// clear jitsi local storage before starting a new conference.
window.localStorage.removeItem("jitsiLocalStorage");
const domain = jitsiUrl || JITSI_URL;
if (domain === undefined) {
throw new Error('Missing JITSI_URL environment variable or jitsiUrl parameter in the map.')
}
await this.loadJitsiScript(domain);
const options: any = { // eslint-disable-line @typescript-eslint/no-explicit-any
roomName: roomName,
jwt: jwt,
width: "100%",
height: "100%",
parentNode: cowebsiteDiv,
configOverwrite: {
startWithAudioMuted: !mediaManager.constraintsMedia.audio,
startWithVideoMuted: mediaManager.constraintsMedia.video === false,
prejoinPageEnabled: false
},
interfaceConfigOverwrite: interfaceConfig,
configOverwrite: mergeConfig(config),
interfaceConfigOverwrite: {...defaultInterfaceConfig, ...interfaceConfig}
};
if (!options.jwt) {
delete options.jwt;
}
return new Promise((resolve, reject) => {
options.onload = () => resolve(); //we want for the iframe to be loaded before triggering animations.
setTimeout(() => resolve(), 2000); //failsafe in case the iframe is deleted before loading or too long to load
@ -70,6 +133,19 @@ class JitsiFactory {
this.jitsiApi.removeListener('audioMuteStatusChanged', this.audioCallback);
this.jitsiApi.removeListener('videoMuteStatusChanged', this.videoCallback);
this.jitsiApi?.dispose();
//restore previous config
if(this.previousConfigMeet?.startWithAudioMuted){
mediaManager.disableMicrophone();
}else{
mediaManager.enableMicrophone();
}
if(this.previousConfigMeet?.startWithVideoMuted){
mediaManager.disableCamera();
}else{
mediaManager.enableCamera();
}
}
private onAudioChange({muted}: {muted: boolean}): void {
@ -88,6 +164,31 @@ class JitsiFactory {
}
}
private async loadJitsiScript(domain: string): Promise<void> {
return new Promise<void>((resolve, reject) => {
if (this.jitsiScriptLoaded) {
resolve();
return;
}
this.jitsiScriptLoaded = true;
// Load Jitsi if the environment variable is set.
const jitsiScript = document.createElement('script');
jitsiScript.src = 'https://' + domain + '/external_api.js';
jitsiScript.onload = () => {
resolve();
}
jitsiScript.onerror = () => {
reject();
}
document.head.appendChild(jitsiScript);
})
}
}
export const jitsiFactory = new JitsiFactory();
export const jitsiFactory = new JitsiFactory();

View File

@ -24,9 +24,16 @@ export interface CenterListener {
}
export const ON_ACTION_TRIGGER_BUTTON = 'onaction';
export const TRIGGER_WEBSITE_PROPERTIES = 'openWebsiteTrigger';
export const TRIGGER_JITSI_PROPERTIES = 'jitsiTrigger';
export const WEBSITE_MESSAGE_PROPERTIES = 'openWebsiteTriggerMessage';
export const JITSI_MESSAGE_PROPERTIES = 'jitsiTriggerMessage';
export const AUDIO_VOLUME_PROPERTY = 'audioVolume';
export const AUDIO_LOOP_PROPERTY = 'audioLoop';
/**
* This class is in charge of the video-conference layout.
* It receives positioning requests for videos and does its best to place them on the screen depending on the active layout mode.
@ -188,7 +195,7 @@ class LayoutManager {
} else {
HtmlUtils.getElementByIdOrFail<HTMLDivElement>('sidebar').style.display = 'none';
HtmlUtils.getElementByIdOrFail<HTMLDivElement>('main-section').style.display = 'none';
HtmlUtils.getElementByIdOrFail<HTMLDivElement>('chat-mode').style.display = 'flex';
HtmlUtils.getElementByIdOrFail<HTMLDivElement>('chat-mode').style.display = 'grid';
}
for (const div of this.importantDivs.values()) {
@ -212,7 +219,7 @@ class LayoutManager {
* Tries to find the biggest available box of remaining space (this is a space where we can center the character)
*/
public findBiggestAvailableArray(): {xStart: number, yStart: number, xEnd: number, yEnd: number} {
const game = HtmlUtils.getElementByIdOrFail<HTMLDivElement>('game');
const game = HtmlUtils.querySelectorOrFail<HTMLCanvasElement>('#game canvas');
if (this.mode === LayoutMode.VideoChat) {
const children = document.querySelectorAll<HTMLDivElement>('div.chat-mode > div');
const htmlChildren = Array.from(children.values());

View File

@ -3,6 +3,7 @@ import {HtmlUtils} from "./HtmlUtils";
import {discussionManager, SendMessageCallback} from "./DiscussionManager";
import {UserInputManager} from "../Phaser/UserInput/UserInputManager";
import {VIDEO_QUALITY_SELECT} from "../Administration/ConsoleGlobalMessageManager";
import {UserSimplePeerInterface} from "./SimplePeer";
declare const navigator:any; // eslint-disable-line @typescript-eslint/no-explicit-any
const localValueVideo = localStorage.getItem(VIDEO_QUALITY_SELECT);
@ -23,9 +24,9 @@ export type UpdatedLocalStreamCallback = (media: MediaStream|null) => void;
export type StartScreenSharingCallback = (media: MediaStream) => void;
export type StopScreenSharingCallback = (media: MediaStream) => void;
export type ReportCallback = (message: string) => void;
export type ShowReportCallBack = (userId: string, userName: string|undefined) => void;
// TODO: Split MediaManager in 2 classes: MediaManagerUI (in charge of HTML) and MediaManager (singleton in charge of the camera only)
// TODO: verify that microphone event listeners are not triggered plenty of time NOW (since MediaManager is created many times!!!!)
export class MediaManager {
localStream: MediaStream|null = null;
localScreenCapture: MediaStream|null = null;
@ -46,6 +47,7 @@ export class MediaManager {
updatedLocalStreamCallBacks : Set<UpdatedLocalStreamCallback> = new Set<UpdatedLocalStreamCallback>();
startScreenSharingCallBacks : Set<StartScreenSharingCallback> = new Set<StartScreenSharingCallback>();
stopScreenSharingCallBacks : Set<StopScreenSharingCallback> = new Set<StopScreenSharingCallback>();
showReportModalCallBacks : Set<ShowReportCallBack> = new Set<ShowReportCallBack>();
private microphoneBtn: HTMLDivElement;
private cinemaBtn: HTMLDivElement;
private monitorBtn: HTMLDivElement;
@ -469,8 +471,9 @@ export class MediaManager {
return this.getCamera();
}
addActiveVideo(userId: string, reportCallBack: ReportCallback|undefined, userName: string = ""){
addActiveVideo(user: UserSimplePeerInterface, userName: string = ""){
this.webrtcInAudio.play();
const userId = ''+user.userId
userName = userName.toUpperCase();
const color = this.getColorByString(userName);
@ -480,33 +483,39 @@ export class MediaManager {
<div class="connecting-spinner"></div>
<div class="rtc-error" style="display: none"></div>
<i id="name-${userId}" style="background-color: ${color};">${userName}</i>
<img id="microphone-${userId}" src="resources/logos/microphone-close.svg">
` +
((reportCallBack!==undefined)?`<img id="report-${userId}" class="report active" src="resources/logos/report.svg">`:'')
+
`<video id="${userId}" autoplay></video>
<img id="microphone-${userId}" title="mute" src="resources/logos/microphone-close.svg">
<button id="report-${userId}" class="report">
<img title="report this user" src="resources/logos/report.svg">
<span>Report/Block</span>
</button>
<video id="${userId}" autoplay></video>
<img src="resources/logos/blockSign.svg" id="blocking-${userId}" class="block-logo">
</div>
`;
layoutManager.add(DivImportance.Normal, userId, html);
if (reportCallBack) {
const reportBtn = HtmlUtils.getElementByIdOrFail<HTMLDivElement>(`report-${userId}`);
reportBtn.addEventListener('click', (e: MouseEvent) => {
e.preventDefault();
this.showReportModal(userId, userName, reportCallBack);
});
}
this.remoteVideo.set(userId, HtmlUtils.getElementByIdOrFail<HTMLVideoElement>(userId));
//permit to create participant in discussion part
this.addNewParticipant(userId, userName, undefined, reportCallBack);
const showReportUser = () => {
for(const callBack of this.showReportModalCallBacks){
callBack(userId, userName);
}
};
this.addNewParticipant(userId, userName, undefined, showReportUser);
const reportBanUserActionEl: HTMLImageElement = HtmlUtils.getElementByIdOrFail<HTMLImageElement>(`report-${userId}`);
reportBanUserActionEl.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
showReportUser();
});
}
addScreenSharingActiveVideo(userId: string, divImportance: DivImportance = DivImportance.Important){
userId = `screen-sharing-${userId}`;
userId = this.getScreenSharingId(userId);
const html = `
<div id="div-${userId}" class="video-container">
<video id="${userId}" autoplay></video>
@ -517,7 +526,11 @@ export class MediaManager {
this.remoteVideo.set(userId, HtmlUtils.getElementByIdOrFail<HTMLVideoElement>(userId));
}
private getScreenSharingId(userId: string): string {
return `screen-sharing-${userId}`;
}
disabledMicrophoneByUserId(userId: number){
const element = document.getElementById(`microphone-${userId}`);
if(!element){
@ -556,6 +569,10 @@ export class MediaManager {
}
}
toggleBlockLogo(userId: number, show: boolean): void {
const blockLogoElement = HtmlUtils.getElementByIdOrFail<HTMLImageElement>('blocking-'+userId);
show ? blockLogoElement.classList.add('active') : blockLogoElement.classList.remove('active');
}
addStreamRemoteVideo(userId: string, stream : MediaStream): void {
const remoteVideo = this.remoteVideo.get(userId);
if (remoteVideo === undefined) {
@ -565,12 +582,12 @@ export class MediaManager {
}
addStreamRemoteScreenSharing(userId: string, stream : MediaStream){
// In the case of screen sharing (going both ways), we may need to create the HTML element if it does not exist yet
const remoteVideo = this.remoteVideo.get(`screen-sharing-${userId}`);
const remoteVideo = this.remoteVideo.get(this.getScreenSharingId(userId));
if (remoteVideo === undefined) {
this.addScreenSharingActiveVideo(userId);
}
this.addStreamRemoteVideo(`screen-sharing-${userId}`, stream);
this.addStreamRemoteVideo(this.getScreenSharingId(userId), stream);
}
removeActiveVideo(userId: string){
@ -581,7 +598,7 @@ export class MediaManager {
this.removeParticipant(userId);
}
removeActiveScreenSharingVideo(userId: string) {
this.removeActiveVideo(`screen-sharing-${userId}`)
this.removeActiveVideo(this.getScreenSharingId(userId))
}
playWebrtcOutSound(): void {
@ -617,7 +634,7 @@ export class MediaManager {
errorDiv.style.display = 'block';
}
isErrorScreenSharing(userId: string): void {
this.isError(`screen-sharing-${userId}`);
this.isError(this.getScreenSharingId(userId));
}
@ -645,65 +662,8 @@ export class MediaManager {
return color;
}
public showReportModal(userId: string, userName: string, reportCallBack: ReportCallback){
//create report text area
const mainContainer = HtmlUtils.getElementByIdOrFail<HTMLDivElement>('main-container');
const divReport = document.createElement('div');
divReport.classList.add('modal-report-user');
const inputHidden = document.createElement('input');
inputHidden.id = 'input-report-user';
inputHidden.type = 'hidden';
inputHidden.value = userId;
divReport.appendChild(inputHidden);
const titleMessage = document.createElement('p');
titleMessage.id = 'title-report-user';
titleMessage.innerText = 'Open a report';
divReport.appendChild(titleMessage);
const bodyMessage = document.createElement('p');
bodyMessage.id = 'body-report-user';
bodyMessage.innerText = `You are about to open a report regarding an offensive conduct from user ${userName.toUpperCase()}. Please explain to us how you think ${userName.toUpperCase()} breached the code of conduct.`;
divReport.appendChild(bodyMessage);
const imgReportUser = document.createElement('img');
imgReportUser.id = 'img-report-user';
imgReportUser.src = 'resources/logos/report.svg';
divReport.appendChild(imgReportUser);
const textareaUser = document.createElement('textarea');
textareaUser.id = 'textarea-report-user';
textareaUser.placeholder = 'Write ...';
divReport.appendChild(textareaUser);
const buttonReport = document.createElement('button');
buttonReport.id = 'button-save-report-user';
buttonReport.innerText = 'Report';
buttonReport.addEventListener('click', () => {
if(!textareaUser.value){
textareaUser.style.border = '1px solid red'
return;
}
reportCallBack(textareaUser.value);
divReport.remove();
});
divReport.appendChild(buttonReport);
const buttonCancel = document.createElement('img');
buttonCancel.id = 'cancel-report-user';
buttonCancel.src = 'resources/logos/close.svg';
buttonCancel.addEventListener('click', () => {
divReport.remove();
});
divReport.appendChild(buttonCancel);
mainContainer.appendChild(divReport);
}
public addNewParticipant(userId: number|string, name: string|undefined, img?: string, reportCallBack?: ReportCallback){
discussionManager.addParticipant(userId, name, img, false, reportCallBack);
public addNewParticipant(userId: number|string, name: string|undefined, img?: string, showReportUserCallBack?: ShowReportCallBack){
discussionManager.addParticipant(userId, name, img, false, showReportUserCallBack);
}
public removeParticipant(userId: number|string){
@ -769,6 +729,10 @@ export class MediaManager {
this.checkActiveUser();
}, this.focused ? 10000 : 1000);
}
public setShowReportModalCallBacks(callback: ShowReportCallBack){
this.showReportModalCallBacks.add(callback);
}
}
export const mediaManager = new MediaManager();

View File

@ -1,8 +1,9 @@
import * as SimplePeerNamespace from "simple-peer";
import {mediaManager} from "./MediaManager";
import {TURN_SERVER, TURN_USER, TURN_PASSWORD} from "../Enum/EnvironmentVariable";
import {STUN_SERVER, TURN_SERVER, TURN_USER, TURN_PASSWORD} from "../Enum/EnvironmentVariable";
import {RoomConnection} from "../Connexion/RoomConnection";
import {MESSAGE_TYPE_CONSTRAINT} from "./VideoPeer";
import {UserSimplePeerInterface} from "./SimplePeer";
const Peer: SimplePeerNamespace.SimplePeer = require('simple-peer');
@ -16,25 +17,28 @@ export class ScreenSharingPeer extends Peer {
private isReceivingStream:boolean = false;
public toClose: boolean = false;
public _connected: boolean = false;
private userId: number;
constructor(private userId: number, initiator: boolean, private connection: RoomConnection) {
constructor(user: UserSimplePeerInterface, initiator: boolean, private connection: RoomConnection) {
super({
initiator: initiator ? initiator : false,
reconnectTimer: 10000,
config: {
iceServers: [
{
urls: 'stun:stun.l.google.com:19302'
urls: STUN_SERVER.split(',')
},
{
TURN_SERVER !== '' ? {
urls: TURN_SERVER.split(','),
username: TURN_USER,
credential: TURN_PASSWORD
},
]
username: user.webRtcUser || TURN_USER,
credential: user.webRtcPassword || TURN_PASSWORD
} : undefined,
].filter((value) => value !== undefined)
}
});
this.userId = user.userId;
//start listen signal for the peer connection
this.on('signal', (data: unknown) => {
this.sendWebrtcScreenSharingSignal(data);

View File

@ -9,13 +9,18 @@ import {
UpdatedLocalStreamCallback
} from "./MediaManager";
import {ScreenSharingPeer} from "./ScreenSharingPeer";
import {MESSAGE_TYPE_CONSTRAINT, MESSAGE_TYPE_MESSAGE, VideoPeer} from "./VideoPeer";
import {MESSAGE_TYPE_BLOCKED, MESSAGE_TYPE_CONSTRAINT, MESSAGE_TYPE_MESSAGE, VideoPeer} from "./VideoPeer";
import {RoomConnection} from "../Connexion/RoomConnection";
import {connectionManager} from "../Connexion/ConnectionManager";
import {GameConnexionTypes} from "../Url/UrlManager";
import {blackListManager} from "./BlackListManager";
export interface UserSimplePeerInterface{
userId: number;
name?: string;
initiator?: boolean;
webRtcUser?: string|undefined;
webRtcPassword?: string|undefined;
}
export interface PeerConnectionListener {
@ -36,6 +41,9 @@ export class SimplePeer {
private readonly sendLocalScreenSharingStreamCallback: StartScreenSharingCallback;
private readonly stopLocalScreenSharingStreamCallback: StopScreenSharingCallback;
private readonly peerConnectionListeners: Array<PeerConnectionListener> = new Array<PeerConnectionListener>();
private readonly userId: number;
private lastWebrtcUserName: string|undefined;
private lastWebrtcPassword: string|undefined;
constructor(private Connection: RoomConnection, private enableReporting: boolean, private myName: string) {
// We need to go through this weird bound function pointer in order to be able to "free" this reference later.
@ -46,6 +54,7 @@ export class SimplePeer {
mediaManager.onUpdateLocalStream(this.sendLocalVideoStreamCallback);
mediaManager.onStartScreenSharing(this.sendLocalScreenSharingStreamCallback);
mediaManager.onStopScreenSharing(this.stopLocalScreenSharingStreamCallback);
this.userId = Connection.getUserId();
this.initialise();
}
@ -54,7 +63,7 @@ export class SimplePeer {
}
public getNbConnections(): number {
return this.PeerConnectionArray.size;
return this.Users.length;
}
/**
@ -89,15 +98,14 @@ export class SimplePeer {
});
}
private receiveWebrtcStart(user: UserSimplePeerInterface) {
//this.WebRtcRoomId = data.roomId;
private receiveWebrtcStart(user: UserSimplePeerInterface): void {
this.Users.push(user);
// Note: the clients array contain the list of all clients (even the ones we are already connected to in case a user joints a group)
// So we can receive a request we already had before. (which will abort at the first line of createPeerConnection)
// This would be symmetrical to the way we handle disconnection.
//start connection
console.log('receiveWebrtcStart. Initiator: ', user.initiator)
//console.log('receiveWebrtcStart. Initiator: ', user.initiator)
if(!user.initiator){
return;
}
@ -134,17 +142,16 @@ export class SimplePeer {
mediaManager.removeActiveVideo("" + user.userId);
const reportCallback = this.enableReporting ? (comment: string) => {
this.reportUser(user.userId, comment);
} : undefined;
mediaManager.addActiveVideo(user, name);
mediaManager.addActiveVideo("" + user.userId, reportCallback, name);
this.lastWebrtcUserName = user.webRtcUser;
this.lastWebrtcPassword = user.webRtcPassword;
const peer = new VideoPeer(user.userId, user.initiator ? user.initiator : false, this.Connection);
const peer = new VideoPeer(user, user.initiator ? user.initiator : false, this.Connection);
//permit to send message
mediaManager.addSendMessageCallback(user.userId,(message: string) => {
peer.write(new Buffer(JSON.stringify({type: MESSAGE_TYPE_MESSAGE, name: this.myName.toUpperCase(), message: message})));
peer.write(new Buffer(JSON.stringify({type: MESSAGE_TYPE_MESSAGE, name: this.myName.toUpperCase(), userId: this.userId, message: message})));
});
peer.toClose = false;
@ -189,7 +196,13 @@ export class SimplePeer {
mediaManager.addScreenSharingActiveVideo("" + user.userId);
}
const peer = new ScreenSharingPeer(user.userId, user.initiator ? user.initiator : false, this.Connection);
// Enrich the user with last known credentials (if they are not set in the user object, which happens when a user triggers the screen sharing)
if (user.webRtcUser === undefined) {
user.webRtcUser = this.lastWebrtcUserName;
user.webRtcPassword = this.lastWebrtcPassword;
}
const peer = new ScreenSharingPeer(user, user.initiator ? user.initiator : false, this.Connection);
this.PeerScreenSharingConnectionArray.set(user.userId, peer);
for (const peerConnectionListener of this.peerConnectionListeners) {
@ -217,9 +230,6 @@ export class SimplePeer {
this.closeScreenSharingConnection(userId);
for (const peerConnectionListener of this.peerConnectionListeners) {
peerConnectionListener.onDisconnect(userId);
}
const userIndex = this.Users.findIndex(user => user.userId === userId);
if(userIndex < 0){
throw 'Couln\'t delete user';
@ -237,6 +247,10 @@ export class SimplePeer {
this.PeerScreenSharingConnectionArray.delete(userId);
}
}
for (const peerConnectionListener of this.peerConnectionListeners) {
peerConnectionListener.onDisconnect(userId);
}
}
/**
@ -300,6 +314,7 @@ export class SimplePeer {
}
private receiveWebrtcScreenSharingSignal(data: WebRtcSignalReceivedMessageInterface) {
if (blackListManager.isBlackListed(data.userId)) return;
console.log("receiveWebrtcScreenSharingSignal", data);
try {
//if offer type, create peer connection
@ -391,14 +406,8 @@ export class SimplePeer {
}
}
/**
* Triggered locally when clicking on the report button
*/
public reportUser(userId: number, message: string) {
this.Connection.emitReportPlayerMessage(userId, message)
}
private sendLocalScreenSharingStreamToUser(userId: number): void {
if (blackListManager.isBlackListed(userId)) return;
// If a connection already exists with user (because it is already sharing a screen with us... let's use this connection)
if (this.PeerScreenSharingConnectionArray.has(userId)) {
this.pushScreenSharingToRemoteUser(userId);

View File

@ -1,65 +1,57 @@
import * as SimplePeerNamespace from "simple-peer";
import {mediaManager} from "./MediaManager";
import {TURN_PASSWORD, TURN_SERVER, TURN_USER} from "../Enum/EnvironmentVariable";
import {STUN_SERVER, TURN_PASSWORD, TURN_SERVER, TURN_USER} from "../Enum/EnvironmentVariable";
import {RoomConnection} from "../Connexion/RoomConnection";
import {blackListManager} from "./BlackListManager";
import {Subscription} from "rxjs";
import {UserSimplePeerInterface} from "./SimplePeer";
const Peer: SimplePeerNamespace.SimplePeer = require('simple-peer');
export const MESSAGE_TYPE_CONSTRAINT = 'constraint';
export const MESSAGE_TYPE_MESSAGE = 'message';
export const MESSAGE_TYPE_BLOCKED = 'blocked';
export const MESSAGE_TYPE_UNBLOCKED = 'unblocked';
/**
* A peer connection used to transmit video / audio signals between 2 peers.
*/
export class VideoPeer extends Peer {
public toClose: boolean = false;
public _connected: boolean = false;
private remoteStream!: MediaStream;
private blocked: boolean = false;
private userId: number;
private userName: string;
private onBlockSubscribe: Subscription;
private onUnBlockSubscribe: Subscription;
constructor(public userId: number, initiator: boolean, private connection: RoomConnection) {
constructor(public user: UserSimplePeerInterface, initiator: boolean, private connection: RoomConnection) {
super({
initiator: initiator ? initiator : false,
reconnectTimer: 10000,
config: {
iceServers: [
{
urls: 'stun:stun.l.google.com:19302'
urls: STUN_SERVER.split(',')
},
{
TURN_SERVER !== '' ? {
urls: TURN_SERVER.split(','),
username: TURN_USER,
credential: TURN_PASSWORD
},
]
username: user.webRtcUser || TURN_USER,
credential: user.webRtcPassword || TURN_PASSWORD
} : undefined,
].filter((value) => value !== undefined)
}
});
console.log('PEER SETUP ', {
initiator: initiator ? initiator : false,
reconnectTimer: 10000,
config: {
iceServers: [
{
urls: 'stun:stun.l.google.com:19302'
},
{
urls: TURN_SERVER.split(','),
username: TURN_USER,
credential: TURN_PASSWORD
},
]
}
});
this.userId = user.userId;
this.userName = user.name || '';
//start listen signal for the peer connection
this.on('signal', (data: unknown) => {
this.sendWebrtcSignal(data);
});
this.on('stream', (stream: MediaStream) => {
this.stream(stream);
});
/*peer.on('track', (track: MediaStreamTrack, stream: MediaStream) => {
});*/
this.on('stream', (stream: MediaStream) => this.stream(stream));
this.on('close', () => {
this._connected = false;
@ -70,7 +62,7 @@ export class VideoPeer extends Peer {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
this.on('error', (err: any) => {
console.error(`error => ${this.userId} => ${err.code}`, err);
mediaManager.isError("" + userId);
mediaManager.isError("" + this.userId);
});
this.on('connect', () => {
@ -81,8 +73,6 @@ export class VideoPeer extends Peer {
this.on('data', (chunk: Buffer) => {
const message = JSON.parse(chunk.toString('utf8'));
console.log("data", message);
if(message.type === MESSAGE_TYPE_CONSTRAINT) {
if (message.audio) {
mediaManager.enabledMicrophoneByUserId(this.userId);
@ -95,8 +85,19 @@ export class VideoPeer extends Peer {
} else {
mediaManager.disabledVideoByUserId(this.userId);
}
} else if(message.type === 'message') {
mediaManager.addNewMessage(message.name, message.message);
} else if(message.type === MESSAGE_TYPE_MESSAGE) {
if (!blackListManager.isBlackListed(message.userId)) {
mediaManager.addNewMessage(message.name, message.message);
}
} else if(message.type === MESSAGE_TYPE_BLOCKED) {
//FIXME when A blacklists B, the output stream from A is muted in B's js client. This is insecure since B can manipulate the code to unmute A stream.
// Find a way to block A's output stream in A's js client
//However, the output stream stream B is correctly blocked in A client
this.blocked = true;
this.toggleRemoteStream(false);
} else if(message.type === MESSAGE_TYPE_UNBLOCKED) {
this.blocked = false;
this.toggleRemoteStream(true);
}
});
@ -105,6 +106,31 @@ export class VideoPeer extends Peer {
});
this.pushVideoToRemoteUser();
this.onBlockSubscribe = blackListManager.onBlockStream.subscribe((userId) => {
if (userId === this.userId) {
this.toggleRemoteStream(false);
this.sendBlockMessage(true);
}
});
this.onUnBlockSubscribe = blackListManager.onUnBlockStream.subscribe((userId) => {
if (userId === this.userId) {
this.toggleRemoteStream(true);
this.sendBlockMessage(false);
}
});
if (blackListManager.isBlackListed(this.userId)) {
this.sendBlockMessage(true)
}
}
private sendBlockMessage(blocking: boolean) {
this.write(new Buffer(JSON.stringify({type: blocking ? MESSAGE_TYPE_BLOCKED : MESSAGE_TYPE_UNBLOCKED, name: this.userName.toUpperCase(), userId: this.userId, message: ''})));
}
private toggleRemoteStream(enable: boolean) {
this.remoteStream.getTracks().forEach(track => track.enabled = enable);
mediaManager.toggleBlockLogo(this.userId, !enable);
}
private sendWebrtcSignal(data: unknown) {
@ -120,13 +146,13 @@ export class VideoPeer extends Peer {
*/
private stream(stream: MediaStream) {
try {
this.remoteStream = stream;
if (blackListManager.isBlackListed(this.userId) || this.blocked) {
this.toggleRemoteStream(false);
}
mediaManager.addStreamRemoteVideo("" + this.userId, stream);
}catch (err){
console.error(err);
//Force add streem video
/*setTimeout(() => {
this.stream(stream);
}, 500);*/ //todo: find a way to prevent infinite regression.
}
}
@ -139,6 +165,8 @@ export class VideoPeer extends Peer {
if(!this.toClose){
return;
}
this.onBlockSubscribe.unsubscribe();
this.onUnBlockSubscribe.unsubscribe();
mediaManager.removeActiveVideo("" + this.userId);
// FIXME: I don't understand why "Closing connection with" message is displayed TWICE before "Nb users in peerConnectionArray"
// I do understand the method closeConnection is called twice, but I don't understand how they manage to run in parallel.

View File

@ -1,28 +1,21 @@
import 'phaser';
import GameConfig = Phaser.Types.Core.GameConfig;
import "../dist/resources/style/index.scss";
import {DEBUG_MODE, JITSI_URL, RESOLUTION} from "./Enum/EnvironmentVariable";
import {cypressAsserter} from "./Cypress/CypressAsserter";
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 WebGLRenderer = Phaser.Renderer.WebGL.WebGLRenderer;
import {OutlinePipeline} from "./Phaser/Shaders/OutlinePipeline";
import {CustomizeScene} from "./Phaser/Login/CustomizeScene";
import {ResizableScene} from "./Phaser/Login/ResizableScene";
import {EntryScene} from "./Phaser/Login/EntryScene";
import {coWebsiteManager} from "./WebRtc/CoWebsiteManager";
import {MenuScene} from "./Phaser/Menu/MenuScene";
import {HelpCameraSettingsScene} from "./Phaser/Menu/HelpCameraSettingsScene";
import {localUserStore} from "./Connexion/LocalUserStore";
import {ErrorScene} from "./Phaser/Reconnecting/ErrorScene";
// Load Jitsi if the environment variable is set.
if (JITSI_URL) {
const jitsiScript = document.createElement('script');
jitsiScript.src = 'https://' + JITSI_URL + '/external_api.js';
document.head.appendChild(jitsiScript);
}
const {width, height} = coWebsiteManager.getGameSize();
const valueGameQuality = localUserStore.getGameQualityValue();
@ -79,12 +72,17 @@ const config: GameConfig = {
width: width / RESOLUTION,
height: height / RESOLUTION,
parent: "game",
scene: [EntryScene, LoginScene, SelectCharacterScene, EnableCameraScene, ReconnectingScene, ErrorScene, CustomizeScene, MenuScene],
scene: [EntryScene, LoginScene, SelectCharacterScene, EnableCameraScene, ReconnectingScene, ErrorScene, CustomizeScene, MenuScene, HelpCameraSettingsScene],
zoom: RESOLUTION,
fps: fps,
dom: {
createContainer: true
},
render: {
pixelArt: true,
roundPixels: true,
antialias: false
},
physics: {
default: "arcade",
arcade: {
@ -93,19 +91,19 @@ const config: GameConfig = {
},
callbacks: {
postBoot: game => {
const renderer = game.renderer;
// Commented out to try to fix MacOS bug
/*const renderer = game.renderer;
if (renderer instanceof WebGLRenderer) {
renderer.pipelines.add(OutlinePipeline.KEY, new OutlinePipeline(game));
}
}*/
}
}
};
cypressAsserter.gameStarted();
const game = new Phaser.Game(config);
window.addEventListener('resize', function (event) {
coWebsiteManager.resetStyle();
const {width, height} = coWebsiteManager.getGameSize();
game.scale.resize(width / RESOLUTION, height / RESOLUTION);
@ -117,7 +115,7 @@ window.addEventListener('resize', function (event) {
}
});
coWebsiteManager.onStateChange(() => {
coWebsiteManager.onResize.subscribe(() => {
const {width, height} = coWebsiteManager.getGameSize();
game.scale.resize(width / RESOLUTION, height / RESOLUTION);
});

View File

@ -1,8 +1,8 @@
#!/usr/bin/env bash
set -x
set -o nounset errexit
template_file_index=dist/index.html.tmpl
generated_file_index=dist/index.html
template_file_index=dist/index.tmpl.html
generated_file_index=dist/index.tmpl.html.tmp
tmp_trackcodefile=/tmp/trackcode
# To inject tracking code, you have two choices:

View File

@ -2,13 +2,19 @@ import "jasmine";
import {HtmlUtils} from "../../../src/WebRtc/HtmlUtils";
describe("urlify()", () => {
it("should transform an url into a link", () => {
const text = HtmlUtils.urlify('https://google.com');
expect(text).toEqual('<a href="https://google.com" target="_blank" style=":visited {color: white}">https://google.com</a>');
// FIXME: we need to add PhantomJS to have a good mock for "document".
/*it("should transform an url into a link", () => {
const text = HtmlUtils.urlify('foo https://google.com bar');
expect(text).toEqual('foo <a href="https://google.com" target="_blank" style=":visited {color: white}">https://google.com</a> bar');
});
it("should not transform a normal text into a link", () => {
const text = HtmlUtils.urlify('hello');
expect(text).toEqual('hello');
});
});
it("should escape HTML", () => {
const text = HtmlUtils.urlify('<h1>boo</h1>');
expect(text).toEqual('&lt;h1&gt;boo&lt;/h1&gt;');
});*/
});

View File

@ -1,6 +1,7 @@
const path = require('path');
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
entry: './src/index.ts',
@ -23,6 +24,10 @@ module.exports = {
use: 'ts-loader',
exclude: /node_modules/,
},
{
test: /\.scss$/,
use: [MiniCssExtractPlugin.loader, 'css-loader?url=false', 'sass-loader'],
},
],
},
resolve: {
@ -37,15 +42,37 @@ module.exports = {
require('webpack-require-http')
],
plugins: [
new MiniCssExtractPlugin({filename: 'style.[contenthash].css'}),
new HtmlWebpackPlugin(
{
template: './dist/index.html'
template: './dist/index.tmpl.html.tmp',
minify: {
collapseWhitespace: true,
keepClosingSlash: true,
removeComments: false,
removeRedundantAttributes: true,
removeScriptTypeAttributes: true,
removeStyleLinkTypeAttributes: true,
useShortDoctype: true
}
}
),
new webpack.ProvidePlugin({
Phaser: 'phaser'
}),
new webpack.EnvironmentPlugin(['API_URL', 'UPLOADER_URL', 'ADMIN_URL', 'DEBUG_MODE', 'TURN_SERVER', 'TURN_USER', 'TURN_PASSWORD', 'JITSI_URL', 'JITSI_PRIVATE_MODE', 'START_ROOM_URL'])
new webpack.EnvironmentPlugin([
'API_URL',
'UPLOADER_URL',
'ADMIN_URL',
'DEBUG_MODE',
'STUN_SERVER',
'TURN_SERVER',
'TURN_USER',
'TURN_PASSWORD',
'JITSI_URL',
'JITSI_PRIVATE_MODE',
'START_ROOM_URL'
])
],
};

View File

@ -640,10 +640,10 @@ bluebird@^3.5.5:
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f"
integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==
bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.4.0:
version "4.11.9"
resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.9.tgz#26d556829458f9d1e81fc48952493d0ba3507828"
integrity sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==
bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.11.9:
version "4.12.0"
resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.0.tgz#775b3f278efbb9718eec7361f483fb36fbbfea88"
integrity sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==
bn.js@^5.1.1:
version "5.1.3"
@ -714,7 +714,7 @@ braces@^3.0.1, braces@~3.0.2:
dependencies:
fill-range "^7.0.1"
brorand@^1.0.1:
brorand@^1.0.1, brorand@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f"
integrity sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=
@ -878,6 +878,11 @@ camelcase@^5.0.0:
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320"
integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==
camelcase@^6.2.0:
version "6.2.0"
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.2.0.tgz#924af881c9d525ac9d87f40d964e5cea982a1809"
integrity sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==
chalk@^2.0.0, chalk@^2.1.0, chalk@^2.3.0, chalk@^2.4.2:
version "2.4.2"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
@ -905,6 +910,21 @@ charenc@0.0.2:
resolved "https://registry.yarnpkg.com/charenc/-/charenc-0.0.2.tgz#c0a1d2f3a7092e03774bfa83f14c0fc5790a8667"
integrity sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc=
"chokidar@>=2.0.0 <4.0.0":
version "3.5.1"
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.1.tgz#ee9ce7bbebd2b79f49f304799d5468e31e14e68a"
integrity sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==
dependencies:
anymatch "~3.1.1"
braces "~3.0.2"
glob-parent "~5.1.0"
is-binary-path "~2.1.0"
is-glob "~4.0.1"
normalize-path "~3.0.0"
readdirp "~3.5.0"
optionalDependencies:
fsevents "~2.3.1"
chokidar@^2.1.8:
version "2.1.8"
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.8.tgz#804b3a7b6a99358c3c5c61e71d8728f041cff917"
@ -1034,6 +1054,11 @@ color-name@~1.1.4:
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
colorette@^1.2.2:
version "1.2.2"
resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.2.2.tgz#cbcc79d5e99caea2dbf10eb3a26fd8b3e6acfa94"
integrity sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w==
commander@^2.20.0:
version "2.20.3"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
@ -1222,6 +1247,24 @@ crypto-browserify@^3.11.0:
randombytes "^2.0.0"
randomfill "^1.0.3"
css-loader@^5.1.3:
version "5.1.3"
resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-5.1.3.tgz#87f6fc96816b20debe3cf682f85c7e56a963d0d1"
integrity sha512-CoPZvyh8sLiGARK3gqczpfdedbM74klGWurF2CsNZ2lhNaXdLIUks+3Mfax3WBeRuHoglU+m7KG/+7gY6G4aag==
dependencies:
camelcase "^6.2.0"
cssesc "^3.0.0"
icss-utils "^5.1.0"
loader-utils "^2.0.0"
postcss "^8.2.8"
postcss-modules-extract-imports "^3.0.0"
postcss-modules-local-by-default "^4.0.0"
postcss-modules-scope "^3.0.0"
postcss-modules-values "^4.0.0"
postcss-value-parser "^4.1.0"
schema-utils "^3.0.0"
semver "^7.3.4"
css-select@^1.1.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/css-select/-/css-select-1.2.0.tgz#2b3a110539c5355f1cd8d314623e870b121ec858"
@ -1237,6 +1280,11 @@ css-what@2.1:
resolved "https://registry.yarnpkg.com/css-what/-/css-what-2.1.3.tgz#a6d7604573365fe74686c3f311c56513d88285f2"
integrity sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg==
cssesc@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee"
integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==
cyclist@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-1.0.1.tgz#596e9698fd0c80e12038c2b82d6eb1b35b6224d9"
@ -1493,17 +1541,17 @@ ee-first@1.1.1:
integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=
elliptic@^6.5.3:
version "6.5.3"
resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.3.tgz#cb59eb2efdaf73a0bd78ccd7015a62ad6e0f93d6"
integrity sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw==
version "6.5.4"
resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.4.tgz#da37cebd31e79a1367e941b592ed1fbebd58abbb"
integrity sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==
dependencies:
bn.js "^4.4.0"
brorand "^1.0.1"
bn.js "^4.11.9"
brorand "^1.1.0"
hash.js "^1.0.0"
hmac-drbg "^1.0.0"
inherits "^2.0.1"
minimalistic-assert "^1.0.0"
minimalistic-crypto-utils "^1.0.0"
hmac-drbg "^1.0.1"
inherits "^2.0.4"
minimalistic-assert "^1.0.1"
minimalistic-crypto-utils "^1.0.1"
emoji-regex@^7.0.1:
version "7.0.3"
@ -1829,15 +1877,6 @@ expand-tilde@^2.0.0, expand-tilde@^2.0.2:
dependencies:
homedir-polyfill "^1.0.1"
exports-loader@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/exports-loader/-/exports-loader-1.1.1.tgz#88c9a6877ee6a5519d7c41a016bdd99148421e69"
integrity sha512-CmyhIR2sJ3KOfVsHjsR0Yvo+0lhRhRMAevCbB8dhTVLHsZPs0lCQTvRmR9YNvBXDBxUuhmCE2f54KqEjZUaFrg==
dependencies:
loader-utils "^2.0.0"
schema-utils "^3.0.0"
source-map "^0.6.1"
express@^4.17.1:
version "4.17.1"
resolved "https://registry.yarnpkg.com/express/-/express-4.17.1.tgz#4491fc38605cf51f8629d39c2b5d026f98a4c134"
@ -2128,6 +2167,11 @@ fsevents@~2.1.2:
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.1.3.tgz#fb738703ae8d2f9fe900c33836ddebee8b97f23e"
integrity sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==
fsevents@~2.3.1:
version "2.3.2"
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a"
integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==
function-bind@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
@ -2353,7 +2397,7 @@ he@^1.2.0:
resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==
hmac-drbg@^1.0.0:
hmac-drbg@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1"
integrity sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=
@ -2497,6 +2541,11 @@ iconv-lite@0.4.24, iconv-lite@^0.4.24:
dependencies:
safer-buffer ">= 2.1.2 < 3"
icss-utils@^5.0.0, icss-utils@^5.1.0:
version "5.1.0"
resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-5.1.0.tgz#c6be6858abd013d768e98366ae47e25d5887b1ae"
integrity sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==
ieee754@^1.1.4:
version "1.1.13"
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.13.tgz#ec168558e95aa181fd87d37f55c32bbcb6708b84"
@ -2528,21 +2577,16 @@ import-local@^2.0.0:
pkg-dir "^3.0.0"
resolve-cwd "^2.0.0"
imports-loader@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/imports-loader/-/imports-loader-1.2.0.tgz#b06823d0bb42e6f5ff89bc893829000eda46693f"
integrity sha512-zPvangKEgrrPeqeUqH0Uhc59YqK07JqZBi9a9cQ3v/EKUIqrbJHY4CvUrDus2lgQa5AmPyXuGrWP8JJTqzE5RQ==
dependencies:
loader-utils "^2.0.0"
schema-utils "^3.0.0"
source-map "^0.6.1"
strip-comments "^2.0.1"
imurmurhash@^0.1.4:
version "0.1.4"
resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea"
integrity sha1-khi5srkoojixPcT7a21XbyMUU+o=
indexes-of@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/indexes-of/-/indexes-of-1.0.1.tgz#f30f716c8e2bd346c7b67d3df3915566a7c05607"
integrity sha1-8w9xbI4r00bHtn0985FVZqfAVgc=
indexof@0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/indexof/-/indexof-0.0.1.tgz#82dc336d232b9062179d05ab3293a66059fd435d"
@ -2942,6 +2986,11 @@ kind-of@^6.0.0, kind-of@^6.0.2:
resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd"
integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==
klona@^2.0.4:
version "2.0.4"
resolved "https://registry.yarnpkg.com/klona/-/klona-2.0.4.tgz#7bb1e3affb0cb8624547ef7e8f6708ea2e39dfc0"
integrity sha512-ZRbnvdg/NxqzC7L9Uyqzf4psi1OM4Cuc+sJAkQPjO6XkQIJTNbfK2Rsmbw8fx1p2mkZdp2FZYo2+LwXYY/uwIA==
levn@^0.3.0, levn@~0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee"
@ -3010,6 +3059,13 @@ lru-cache@^5.1.1:
dependencies:
yallist "^3.0.2"
lru-cache@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94"
integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==
dependencies:
yallist "^4.0.0"
make-dir@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-2.1.0.tgz#5f0310e18b8be898cc07009295a30ae41e91e6f5"
@ -3156,12 +3212,21 @@ mimic-fn@^2.1.0:
resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b"
integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==
mini-css-extract-plugin@^1.3.9:
version "1.3.9"
resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-1.3.9.tgz#47a32132b0fd97a119acd530e8421e8f6ab16d5e"
integrity sha512-Ac4s+xhVbqlyhXS5J/Vh/QXUz3ycXlCqoCPpg0vdfhsIBH9eg/It/9L1r1XhSCH737M1lqcWnMuWL13zcygn5A==
dependencies:
loader-utils "^2.0.0"
schema-utils "^3.0.0"
webpack-sources "^1.1.0"
minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7"
integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==
minimalistic-crypto-utils@^1.0.0, minimalistic-crypto-utils@^1.0.1:
minimalistic-crypto-utils@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a"
integrity sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=
@ -3259,6 +3324,11 @@ nan@^2.12.1:
resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.1.tgz#d7be34dfa3105b91494c3147089315eff8874b01"
integrity sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw==
nanoid@^3.1.20:
version "3.1.22"
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.22.tgz#b35f8fb7d151990a8aebd5aa5015c03cf726f844"
integrity sha512-/2ZUaJX2ANuLtTvqTlgqBQNJoQO398KyJgZloL0PZkC0dpysjncRUPsFe3DUPzz/y3h+u7C46np8RMuvF3jsSQ==
nanomatch@^1.2.9:
version "1.2.13"
resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119"
@ -3286,7 +3356,7 @@ negotiator@0.6.2:
resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb"
integrity sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==
neo-async@^2.5.0, neo-async@^2.6.1:
neo-async@^2.5.0, neo-async@^2.6.1, neo-async@^2.6.2:
version "2.6.2"
resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f"
integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==
@ -3663,14 +3733,12 @@ pbkdf2@^3.0.3:
safe-buffer "^5.0.1"
sha.js "^2.4.8"
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==
phaser@^3.53.1:
version "3.54.0"
resolved "https://registry.yarnpkg.com/phaser/-/phaser-3.54.0.tgz#46b191e46059aab2a9a57f78525c60b595767eee"
integrity sha512-/1XVI6J2siS0OGwJez7vLbRjars1zb//EvJdYMVyd3wNTUf5DHrvYUj1f6TsEISr4vjnbrNtS66QIuPbGU8x6A==
dependencies:
eventemitter3 "^4.0.7"
exports-loader "^1.1.1"
imports-loader "^1.2.0"
path "^0.12.7"
picomatch@^2.0.4, picomatch@^2.0.5, picomatch@^2.2.1:
@ -3721,6 +3789,58 @@ posix-character-classes@^0.1.0:
resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab"
integrity sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=
postcss-modules-extract-imports@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz#cda1f047c0ae80c97dbe28c3e76a43b88025741d"
integrity sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==
postcss-modules-local-by-default@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.0.tgz#ebbb54fae1598eecfdf691a02b3ff3b390a5a51c"
integrity sha512-sT7ihtmGSF9yhm6ggikHdV0hlziDTX7oFoXtuVWeDd3hHObNkcHRo9V3yg7vCAY7cONyxJC/XXCmmiHHcvX7bQ==
dependencies:
icss-utils "^5.0.0"
postcss-selector-parser "^6.0.2"
postcss-value-parser "^4.1.0"
postcss-modules-scope@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/postcss-modules-scope/-/postcss-modules-scope-3.0.0.tgz#9ef3151456d3bbfa120ca44898dfca6f2fa01f06"
integrity sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg==
dependencies:
postcss-selector-parser "^6.0.4"
postcss-modules-values@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz#d7c5e7e68c3bb3c9b27cbf48ca0bb3ffb4602c9c"
integrity sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==
dependencies:
icss-utils "^5.0.0"
postcss-selector-parser@^6.0.2, postcss-selector-parser@^6.0.4:
version "6.0.4"
resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.4.tgz#56075a1380a04604c38b063ea7767a129af5c2b3"
integrity sha512-gjMeXBempyInaBqpp8gODmwZ52WaYsVOsfr4L4lDQ7n3ncD6mEyySiDtgzCT+NYC0mmeOLvtsF8iaEf0YT6dBw==
dependencies:
cssesc "^3.0.0"
indexes-of "^1.0.1"
uniq "^1.0.1"
util-deprecate "^1.0.2"
postcss-value-parser@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz#443f6a20ced6481a2bda4fa8532a6e55d789a2cb"
integrity sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ==
postcss@^8.2.8:
version "8.2.8"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.2.8.tgz#0b90f9382efda424c4f0f69a2ead6f6830d08ece"
integrity sha512-1F0Xb2T21xET7oQV9eKuctbM9S7BC0fetoHCc4H13z0PT6haiRLP4T0ZY4XWh7iLP0usgqykT6p9B2RtOf4FPw==
dependencies:
colorette "^1.2.2"
nanoid "^3.1.20"
source-map "^0.6.1"
prelude-ls@~1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54"
@ -3940,6 +4060,13 @@ readdirp@~3.4.0:
dependencies:
picomatch "^2.2.1"
readdirp@~3.5.0:
version "3.5.0"
resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.5.0.tgz#9ba74c019b15d365278d2e91bb8c48d7b4d42c9e"
integrity sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==
dependencies:
picomatch "^2.2.1"
regex-not@^1.0.0, regex-not@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/regex-not/-/regex-not-1.0.2.tgz#1f4ece27e00b0b65e0247a6810e6a85d83a5752c"
@ -4094,7 +4221,7 @@ run-queue@^1.0.0, run-queue@^1.0.3:
dependencies:
aproba "^1.1.1"
rxjs@^6.6.0:
rxjs@^6.6.0, rxjs@^6.6.3:
version "6.6.3"
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.3.tgz#8ca84635c4daa900c0d3967a6ee7ac60271ee552"
integrity sha512-trsQc+xYYXZ3urjOiJOuCOa5N3jAZ3eiSpQB5hIT8zGlL2QfnHLJ2r7GMkBGuIausdJN1OneaI6gQlsqNHHmZQ==
@ -4123,6 +4250,24 @@ safe-regex@^1.1.0:
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
sass-loader@10.1.1:
version "10.1.1"
resolved "https://registry.yarnpkg.com/sass-loader/-/sass-loader-10.1.1.tgz#4ddd5a3d7638e7949065dd6e9c7c04037f7e663d"
integrity sha512-W6gVDXAd5hR/WHsPicvZdjAWHBcEJ44UahgxcIE196fW2ong0ZHMPO1kZuI5q0VlvMQZh32gpv69PLWQm70qrw==
dependencies:
klona "^2.0.4"
loader-utils "^2.0.0"
neo-async "^2.6.2"
schema-utils "^3.0.0"
semver "^7.3.2"
sass@^1.32.8:
version "1.32.8"
resolved "https://registry.yarnpkg.com/sass/-/sass-1.32.8.tgz#f16a9abd8dc530add8834e506878a2808c037bdc"
integrity sha512-Sl6mIeGpzjIUZqvKnKETfMf0iDAswD9TNlv13A7aAF3XZlRPMq4VvJWBC2N2DXbp94MQVdNSFG6LfF/iOXrPHQ==
dependencies:
chokidar ">=2.0.0 <4.0.0"
schema-utils@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-1.0.0.tgz#0b79a93204d7b600d4b2850d1f66c2a34951c770"
@ -4168,6 +4313,13 @@ semver@^7.3.2:
resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.2.tgz#604962b052b81ed0786aae84389ffba70ffd3938"
integrity sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==
semver@^7.3.4:
version "7.3.4"
resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.4.tgz#27aaa7d2e4ca76452f98d3add093a72c943edc97"
integrity sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==
dependencies:
lru-cache "^6.0.0"
send@0.17.1:
version "0.17.1"
resolved "https://registry.yarnpkg.com/send/-/send-0.17.1.tgz#c1d8b059f7900f7466dd4938bdc44e11ddb376c8"
@ -4569,11 +4721,6 @@ strip-ansi@^6.0.0:
dependencies:
ansi-regex "^5.0.0"
strip-comments@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/strip-comments/-/strip-comments-2.0.1.tgz#4ad11c3fbcac177a67a40ac224ca339ca1c1ba9b"
integrity sha512-ZprKx+bBLXv067WTCALv8SSz5l2+XhpYCsVtSqlMnkAXMWDq+/ekVbl1ghqP9rUHTzv6sm/DwCOiYutU/yp1fw==
strip-eof@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf"
@ -4812,6 +4959,11 @@ union-value@^1.0.0:
is-extendable "^0.1.1"
set-value "^2.0.1"
uniq@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/uniq/-/uniq-1.0.1.tgz#b31c5ae8254844a3a8281541ce2b04b865a734ff"
integrity sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8=
unique-filename@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/unique-filename/-/unique-filename-1.1.1.tgz#1d69769369ada0583103a1e6ae87681b56573230"
@ -4877,7 +5029,7 @@ use@^3.1.0:
resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f"
integrity sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==
util-deprecate@^1.0.1, util-deprecate@~1.0.1:
util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=
@ -5056,7 +5208,7 @@ webpack-require-http@^0.4.3:
md5 "^2.0.0"
url "^0.11.0"
webpack-sources@^1.4.0, webpack-sources@^1.4.1:
webpack-sources@^1.1.0, webpack-sources@^1.4.0, webpack-sources@^1.4.1:
version "1.4.3"
resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.4.3.tgz#eedd8ec0b928fbf1cbfe994e22d2d890f330a933"
integrity sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==
@ -5193,6 +5345,11 @@ yallist@^3.0.2:
resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd"
integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==
yallist@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"
integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==
yargs-parser@^13.1.2:
version "13.1.2"
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.1.2.tgz#130f09702ebaeef2650d54ce6e3e5706f7a4fb38"

4
maps/.dockerignore Normal file
View File

@ -0,0 +1,4 @@
/node_modules/
/dist/bundle.js
/yarn-error.log
/Dockerfile

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