diff --git a/.drone.yml b/.drone.yml index f1b7944..172457a 100644 --- a/.drone.yml +++ b/.drone.yml @@ -18,6 +18,7 @@ trigger: event: include: - tag + - push volumes: - name: drone-shared diff --git a/backend/README.md b/backend/README.md index 3b0a281..3286061 100644 --- a/backend/README.md +++ b/backend/README.md @@ -3,7 +3,8 @@ ## Development `stack build` -`stack stack run` + +`stack run` ## Debug @@ -12,3 +13,44 @@ Using websocat `websocat ws://127.0.0.1:9160` `curl --data 'body data' localhost:8081` + +``` +curl -X POST localhost:8081/roomdata \ + -H "Content-Type: application/json" \ + -d '[ + { + "roomName": "ConferenceRoom1", + "participants": [ + { + "jid": "participant1@example.com", + "email": "participant1@example.com", + "displayName": "Alice", + "avatarURL": "https://example.com/avatars/alice.png" + }, + { + "jid": "participant2@example.com", + "email": "participant2@example.com", + "displayName": "Bob", + "avatarURL": "https://example.com/avatars/bob.png" + } + ] + }, + { + "roomName": "ConferenceRoom2", + "participants": [ + { + "jid": "participant3@example.com", + "email": "participant3@example.com", + "displayName": "Charlie", + "avatarURL": "https://example.com/avatars/charlie.png" + }, + { + "jid": "participant4@example.com", + "email": "participant4@example.com", + "displayName": "Dana", + "avatarURL": "https://example.com/avatars/dana.png" + } + ] + } + ]' +``` diff --git a/backend/app/Main.hs b/backend/app/Main.hs index 7dce4e5..aee6c9a 100644 --- a/backend/app/Main.hs +++ b/backend/app/Main.hs @@ -1,7 +1,27 @@ module Main (main) where import ClassyPrelude +import GHC.IO.Encoding (setLocaleEncoding) +import GHC.IO.Encoding.UTF8 (utf8) import Lib (runBothServers) +import Options.Applicative +import Types.Config (ServerOptions, serverOptionsParser) main :: IO () -main = runBothServers +main = do + setLocaleEncoding utf8 + hSetBuffering stdout LineBuffering + hSetBuffering stderr LineBuffering + + opts <- execParser serverOptions + + runBothServers opts + +serverOptions :: ParserInfo ServerOptions +serverOptions = + info + (serverOptionsParser <**> helper) + ( fullDesc + <> progDesc "Run the server with specified options" + <> header "Haskell Server - A configurable server application" + ) diff --git a/backend/converted.nix b/backend/converted.nix index f8b3710..9432f5c 100644 --- a/backend/converted.nix +++ b/backend/converted.nix @@ -1,18 +1,20 @@ -{ mkDerivation -, aeson -, base -, bytestring -, classy-prelude -, http-types -, lib -, lifted-base -, mtl -, text -, uuid -, wai -, wai-extra -, warp -, websockets +{ + mkDerivation, + aeson, + base, + bytestring, + classy-prelude, + http-types, + lib, + lifted-base, + mtl, + text, + uuid, + wai, + wai-extra, + warp, + websockets, + optparse-applicative, }: mkDerivation { pname = "jitsi-rooms"; @@ -34,6 +36,7 @@ mkDerivation { wai-extra warp websockets + optparse-applicative ]; executableHaskellDepends = [ aeson @@ -49,6 +52,7 @@ mkDerivation { wai-extra warp websockets + optparse-applicative ]; homepage = "https://github.com/githubuser/jitsi-rooms#readme"; license = lib.licenses.bsd3; diff --git a/backend/default.nix b/backend/default.nix index d65d501..87e6b37 100644 --- a/backend/default.nix +++ b/backend/default.nix @@ -1,43 +1,4 @@ -let - config = { - packageOverrides = pkgs: rec { - haskellPackages = pkgs.haskellPackages.override { - overrides = haskellPackagesNew: haskellPackagesOld: rec { - jitsi-rooms = - haskellPackagesNew.callPackage ./converted.nix { }; - }; - }; - }; - }; - pkgs = import { inherit config; }; +{ pkgs ? import { } }: -in -pkgs.dockerTools.buildImage { - name = "jitsi-rooms"; - tag = "latest"; - copyToRoot = pkgs.buildEnv { - name = "image-root"; - paths = [ - # pkgs.bash - # pkgs.coreutils - ]; - pathsToLink = [ "/bin" ]; - }; - config = { - Cmd = [ "${pkgs.haskellPackages.jitsi-rooms}/bin/jitsi-rooms-exe" ]; - ExposedPorts = { - "9160/tcp" = { }; - "8081/tcp" = { }; - }; - Healthcheck = { - "Test" = [ - "CMD-SHELL" - "${pkgs.curl} -f http://0.0.0.0:8081" - ]; - "Interval" = 30000000000; - "Timeout" = 10000000000; - "Retries" = 3; - }; - }; -} +pkgs.haskellPackages.callPackage ./converted.nix { } diff --git a/backend/docker.nix b/backend/docker.nix new file mode 100644 index 0000000..4a8ecbc --- /dev/null +++ b/backend/docker.nix @@ -0,0 +1,37 @@ +let + config = { + packageOverrides = pkgs: rec { + haskellPackages = pkgs.haskellPackages.override { + overrides = haskellPackagesNew: haskellPackagesOld: rec { + jitsi-rooms = + haskellPackagesNew.callPackage ./converted.nix { }; + }; + }; + }; + }; + pkgs = import { inherit config; }; + + +in +pkgs.dockerTools.buildImage { + name = "jitsi-rooms"; + tag = "latest"; + copyToRoot = pkgs.buildEnv { + name = "image-root"; + paths = [ + # pkgs.bash + # pkgs.coreutils + ]; + pathsToLink = [ "/bin" ]; + }; + config = { + Cmd = [ "${pkgs.haskellPackages.jitsi-rooms}/bin/jitsi-rooms-exe" ]; + ExposedPorts = { + "9160/tcp" = { }; + "8081/tcp" = { }; + }; + Env = [ + "LANG=en_US.UTF-8" + ]; + }; +} diff --git a/backend/jitsi-rooms.cabal b/backend/jitsi-rooms.cabal index 416656e..2f4d9fc 100644 --- a/backend/jitsi-rooms.cabal +++ b/backend/jitsi-rooms.cabal @@ -1,6 +1,6 @@ cabal-version: 1.12 --- This file has been generated from package.yaml by hpack version 0.35.2. +-- This file has been generated from package.yaml by hpack version 0.37.0. -- -- see: https://github.com/sol/hpack @@ -29,12 +29,14 @@ library Lib RoomDataHandler State.ConnectedClientsState + State.GenericTVarState State.RoomDataState + State.RoomsState Types.AppTypes + Types.Config Types.ConnectionState Types.Participant Types.RoomData - Types.RoomsState Types.User Types.UsersData Types.WebEnv @@ -61,6 +63,8 @@ library , http-types , lifted-base , mtl + , optparse-applicative + , process , text , time , uuid @@ -88,6 +92,8 @@ executable jitsi-rooms-exe , jitsi-rooms , lifted-base , mtl + , optparse-applicative + , process , text , time , uuid diff --git a/backend/nix/sources.json b/backend/nix/sources.json new file mode 100644 index 0000000..6ce8996 --- /dev/null +++ b/backend/nix/sources.json @@ -0,0 +1,14 @@ +{ + "nixpkgs": { + "branch": "nixpkgs-unstable", + "description": "Nix Packages collection", + "homepage": "https://github.com/NixOS/nixpkgs", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "abfd31179174133ab8131139d650297bf4da63b7", + "sha256": "1jmkz6l7sj876wzyn5niyfaxshbmw9fp3g8r41k1wbjvmm5xrnsn", + "type": "tarball", + "url": "https://github.com/NixOS/nixpkgs/archive/abfd31179174133ab8131139d650297bf4da63b7.tar.gz", + "url_template": "https://github.com///archive/.tar.gz" + } +} diff --git a/backend/nix/sources.nix b/backend/nix/sources.nix new file mode 100644 index 0000000..9a01c8a --- /dev/null +++ b/backend/nix/sources.nix @@ -0,0 +1,194 @@ +# This file has been generated by Niv. + +let + + # + # The fetchers. fetch_ fetches specs of type . + # + + fetch_file = pkgs: name: spec: + let + name' = sanitizeName name + "-src"; + in + if spec.builtin or true then + builtins_fetchurl { inherit (spec) url sha256; name = name'; } + else + pkgs.fetchurl { inherit (spec) url sha256; name = name'; }; + + fetch_tarball = pkgs: name: spec: + let + name' = sanitizeName name + "-src"; + in + if spec.builtin or true then + builtins_fetchTarball { name = name'; inherit (spec) url sha256; } + else + pkgs.fetchzip { name = name'; inherit (spec) url sha256; }; + + fetch_git = name: spec: + let + ref = + if spec ? ref then spec.ref else + if spec ? branch then "refs/heads/${spec.branch}" else + if spec ? tag then "refs/tags/${spec.tag}" else + abort "In git source '${name}': Please specify `ref`, `tag` or `branch`!"; + submodules = if spec ? submodules then spec.submodules else false; + submoduleArg = + let + nixSupportsSubmodules = builtins.compareVersions builtins.nixVersion "2.4" >= 0; + emptyArgWithWarning = + if submodules == true + then + builtins.trace + ( + "The niv input \"${name}\" uses submodules " + + "but your nix's (${builtins.nixVersion}) builtins.fetchGit " + + "does not support them" + ) + {} + else {}; + in + if nixSupportsSubmodules + then { inherit submodules; } + else emptyArgWithWarning; + in + builtins.fetchGit + ({ url = spec.repo; inherit (spec) rev; inherit ref; } // submoduleArg); + + fetch_local = spec: spec.path; + + fetch_builtin-tarball = name: throw + ''[${name}] The niv type "builtin-tarball" is deprecated. You should instead use `builtin = true`. + $ niv modify ${name} -a type=tarball -a builtin=true''; + + fetch_builtin-url = name: throw + ''[${name}] The niv type "builtin-url" will soon be deprecated. You should instead use `builtin = true`. + $ niv modify ${name} -a type=file -a builtin=true''; + + # + # Various helpers + # + + # https://github.com/NixOS/nixpkgs/pull/83241/files#diff-c6f540a4f3bfa4b0e8b6bafd4cd54e8bR695 + sanitizeName = name: + ( + concatMapStrings (s: if builtins.isList s then "-" else s) + ( + builtins.split "[^[:alnum:]+._?=-]+" + ((x: builtins.elemAt (builtins.match "\\.*(.*)" x) 0) name) + ) + ); + + # The set of packages used when specs are fetched using non-builtins. + mkPkgs = sources: system: + let + sourcesNixpkgs = + import (builtins_fetchTarball { inherit (sources.nixpkgs) url sha256; }) { inherit system; }; + hasNixpkgsPath = builtins.any (x: x.prefix == "nixpkgs") builtins.nixPath; + hasThisAsNixpkgsPath = == ./.; + in + if builtins.hasAttr "nixpkgs" sources + then sourcesNixpkgs + else if hasNixpkgsPath && ! hasThisAsNixpkgsPath then + import {} + else + abort + '' + Please specify either (through -I or NIX_PATH=nixpkgs=...) or + add a package called "nixpkgs" to your sources.json. + ''; + + # The actual fetching function. + fetch = pkgs: name: spec: + + if ! builtins.hasAttr "type" spec then + abort "ERROR: niv spec ${name} does not have a 'type' attribute" + else if spec.type == "file" then fetch_file pkgs name spec + else if spec.type == "tarball" then fetch_tarball pkgs name spec + else if spec.type == "git" then fetch_git name spec + else if spec.type == "local" then fetch_local spec + else if spec.type == "builtin-tarball" then fetch_builtin-tarball name + else if spec.type == "builtin-url" then fetch_builtin-url name + else + abort "ERROR: niv spec ${name} has unknown type ${builtins.toJSON spec.type}"; + + # If the environment variable NIV_OVERRIDE_${name} is set, then use + # the path directly as opposed to the fetched source. + replace = name: drv: + let + saneName = stringAsChars (c: if isNull (builtins.match "[a-zA-Z0-9]" c) then "_" else c) name; + ersatz = builtins.getEnv "NIV_OVERRIDE_${saneName}"; + in + if ersatz == "" then drv else + # this turns the string into an actual Nix path (for both absolute and + # relative paths) + if builtins.substring 0 1 ersatz == "/" then /. + ersatz else /. + builtins.getEnv "PWD" + "/${ersatz}"; + + # Ports of functions for older nix versions + + # a Nix version of mapAttrs if the built-in doesn't exist + mapAttrs = builtins.mapAttrs or ( + f: set: with builtins; + listToAttrs (map (attr: { name = attr; value = f attr set.${attr}; }) (attrNames set)) + ); + + # https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/lists.nix#L295 + range = first: last: if first > last then [] else builtins.genList (n: first + n) (last - first + 1); + + # https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/strings.nix#L257 + stringToCharacters = s: map (p: builtins.substring p 1 s) (range 0 (builtins.stringLength s - 1)); + + # https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/strings.nix#L269 + stringAsChars = f: s: concatStrings (map f (stringToCharacters s)); + concatMapStrings = f: list: concatStrings (map f list); + concatStrings = builtins.concatStringsSep ""; + + # https://github.com/NixOS/nixpkgs/blob/8a9f58a375c401b96da862d969f66429def1d118/lib/attrsets.nix#L331 + optionalAttrs = cond: as: if cond then as else {}; + + # fetchTarball version that is compatible between all the versions of Nix + builtins_fetchTarball = { url, name ? null, sha256 }@attrs: + let + inherit (builtins) lessThan nixVersion fetchTarball; + in + if lessThan nixVersion "1.12" then + fetchTarball ({ inherit url; } // (optionalAttrs (!isNull name) { inherit name; })) + else + fetchTarball attrs; + + # fetchurl version that is compatible between all the versions of Nix + builtins_fetchurl = { url, name ? null, sha256 }@attrs: + let + inherit (builtins) lessThan nixVersion fetchurl; + in + if lessThan nixVersion "1.12" then + fetchurl ({ inherit url; } // (optionalAttrs (!isNull name) { inherit name; })) + else + fetchurl attrs; + + # Create the final "sources" from the config + mkSources = config: + mapAttrs ( + name: spec: + if builtins.hasAttr "outPath" spec + then abort + "The values in sources.json should not have an 'outPath' attribute" + else + spec // { outPath = replace name (fetch config.pkgs name spec); } + ) config.sources; + + # The "config" used by the fetchers + mkConfig = + { sourcesFile ? if builtins.pathExists ./sources.json then ./sources.json else null + , sources ? if isNull sourcesFile then {} else builtins.fromJSON (builtins.readFile sourcesFile) + , system ? builtins.currentSystem + , pkgs ? mkPkgs sources system + }: rec { + # The sources, i.e. the attribute set of spec name to spec + inherit sources; + + # The "pkgs" (evaluated nixpkgs) to use for e.g. non-builtin fetchers + inherit pkgs; + }; + +in +mkSources (mkConfig {}) // { __functor = _: settings: mkSources (mkConfig settings); } diff --git a/backend/nix/stack-integration.nix b/backend/nix/stack-integration.nix new file mode 100644 index 0000000..68376c6 --- /dev/null +++ b/backend/nix/stack-integration.nix @@ -0,0 +1,16 @@ +let + sources = import ./sources.nix; + pkgs = import sources.nixpkgs { }; +in + +# See https://docs.haskellstack.org/en/stable/nix_integration/#using-a-custom-shellnix-file +{ ghc }: + +pkgs.haskell.lib.buildStackProject { + inherit ghc; + name = "haskell-stack-nix"; + # System dependencies needed at compilation time + buildInputs = [ + pkgs.zlib + ]; +} diff --git a/backend/package.yaml b/backend/package.yaml index dc41a92..a2df9ff 100644 --- a/backend/package.yaml +++ b/backend/package.yaml @@ -34,6 +34,8 @@ dependencies: - mtl - time - wai-extra + - process + - optparse-applicative ghc-options: - -Wall diff --git a/backend/shell.nix b/backend/shell.nix new file mode 100644 index 0000000..a24dad2 --- /dev/null +++ b/backend/shell.nix @@ -0,0 +1,12 @@ +{ pkgs ? import { } }: + +pkgs.mkShell { + buildInputs = with pkgs; [ + haskell-language-server + stack + stylish-haskell + + libnotify + ]; + +} diff --git a/backend/src/BroadcastUserData.hs b/backend/src/BroadcastUserData.hs index a53f81b..4aa652d 100644 --- a/backend/src/BroadcastUserData.hs +++ b/backend/src/BroadcastUserData.hs @@ -8,9 +8,9 @@ where import ClassyPrelude import Data.Aeson (encode) import Network.WebSockets qualified as WS -import State.ConnectedClientsState (MonadConnectedClientsRead (getConnctedClients)) +import State.ConnectedClientsState (ConnectedClients, MonadConnectedClientsRead (getConnctedClients)) import State.RoomDataState (MonadRoomDataStateRead (getRoomDataState)) -import Types.ConnectionState (Client (..), ConnectedClients) +import Types.ConnectionState (Client (..)) import Types.User (User, clientToUser) import Types.UsersData (UsersData (..)) @@ -46,5 +46,5 @@ broadCastToClientsGeneric message = do broadcast :: Text -> ConnectedClients -> IO () broadcast message clients = do - putStrLn message + putStrLn $ "Broadcasting: " ++ message forM_ clients $ \client -> WS.sendTextData (conn client) message diff --git a/backend/src/Lib.hs b/backend/src/Lib.hs index b468739..0a01244 100644 --- a/backend/src/Lib.hs +++ b/backend/src/Lib.hs @@ -6,14 +6,15 @@ module Lib where import ClassyPrelude +import State.ConnectedClientsState (initConnectionsState) +import State.RoomsState (initRoomsState) import Types.AppTypes -import Types.ConnectionState (initConnectionsState) -import Types.RoomsState (initRoomsState) +import Types.Config (ServerOptions) import WebServer (runWebServer) import WebSocket.Server (runWebSocketServer) -runBothServers :: IO () -runBothServers = do +runBothServers :: ServerOptions -> IO () +runBothServers serverOptions = do connectedClientsState <- initConnectionsState roomsState <- initRoomsState @@ -21,7 +22,8 @@ runBothServers = do Env { connectedClientsState = connectedClientsState, profile = Dev, - roomsState = roomsState + roomsState = roomsState, + config = serverOptions } _ <- concurrently (unApp runWebSocketServer env) (unApp runWebServer env) diff --git a/backend/src/RoomDataHandler.hs b/backend/src/RoomDataHandler.hs index 273846d..5741201 100644 --- a/backend/src/RoomDataHandler.hs +++ b/backend/src/RoomDataHandler.hs @@ -10,23 +10,26 @@ import ClassyPrelude import Control.Monad.Except (MonadError, throwError) import Data.Aeson (eitherDecodeStrict) import Data.Aeson.Types (FromJSON) +import GHC.IO.Exception (ExitCode (ExitSuccess)) import Network.HTTP.Types (status200, status500) import Network.Wai ( ResponseReceived, consumeRequestBodyStrict, responseLBS, ) -import State.ConnectedClientsState (MonadConnectedClientsRead) import State.RoomDataState ( MonadRoomDataStateModify (setRoomDataState), MonadRoomDataStateRead, ) -import Types.AppTypes (HasConnectedClientState) -import Types.RoomsState - ( HasRoomsState, +import State.RoomsState + ( roomStateDiffInOpenRooms, roomStateDiffers, - updateRoomState, ) +import System.Process +import Text.Printf (printf) +import Types.AppTypes (HasConfig (getConfig)) +import Types.Config (ServerOptions (..)) +import Types.RoomData (RoomData, prettyPrintOpenedRoom) import Types.WebEnv ( HasWebEnv (getRequest), getRespond, @@ -39,15 +42,19 @@ roomDataHandler :: MonadError ResponseReceived m, MonadRoomDataStateRead m, MonadRoomDataStateModify m, - MonadBroadcast m + MonadBroadcast m, + HasConfig env ) => m ResponseReceived roomDataHandler = do newRoomData <- parseBodyOrBadRequest liftIO $ putStrLn "Got triggered from prosody" - whenM (roomStateDiffers newRoomData) $ do - setRoomDataState newRoomData - broadcastUserData + (openedRooms, closedRooms) <- setRoomDataState newRoomData + + mapM_ notifyRoomOpend openedRooms + mapM_ notifyRoomClosed closedRooms + + broadcastUserData success parseBodyOrBadRequest :: @@ -59,6 +66,7 @@ parseBodyOrBadRequest :: ) => m a parseBodyOrBadRequest = do + liftIO $ putStrLn "Parsing body" body <- getRequestBody case eitherDecodeStrict body of Left errorMessage -> do @@ -107,3 +115,22 @@ success = do status200 [("Content-Type", "text/plain")] "" + +notifyRoomOpend :: (MonadIO m, HasConfig a0, MonadReader a0 m) => RoomData -> m () +notifyRoomOpend room = do + config' <- getConfig <$> ask + let ServerOptions {notifyExecutable = notifyExecutable'} = config' + let (name, user) = prettyPrintOpenedRoom room + liftIO $ printf "Room %s opened by %s\n" name user + let command = printf "%s open '%s' '%s'" notifyExecutable' name user + exitCode <- liftIO $ system command + when (exitCode /= ExitSuccess) $ liftIO $ printf "Failed to notify room %s opened by %s running command %s\n" name user command + +notifyRoomClosed :: (MonadIO m, HasConfig a0, MonadReader a0 m) => RoomData -> m () +notifyRoomClosed room = do + config' <- getConfig <$> ask + let ServerOptions {notifyExecutable = notifyExecutable'} = config' + let (name, _) = prettyPrintOpenedRoom room + liftIO $ printf "Room %s closed\n" name + exitCode <- liftIO $ system $ printf "%s closed '%s'" notifyExecutable' name + when (exitCode /= ExitSuccess) $ liftIO $ printf "Failed to notify room %s closed\n" name diff --git a/backend/src/State/ConnectedClientsState.hs b/backend/src/State/ConnectedClientsState.hs index 550e953..1d09bb5 100644 --- a/backend/src/State/ConnectedClientsState.hs +++ b/backend/src/State/ConnectedClientsState.hs @@ -1,6 +1,10 @@ module State.ConnectedClientsState ( MonadConnectedClientsModify (..), MonadConnectedClientsRead (..), + ConnectedClients, + ConnectedClientsState, + HasConnectedClientState (..), + initConnectionsState, addWSClientGeneric, updateWSClientGeneric, removeWSClientGeneric, @@ -10,14 +14,24 @@ where import ClassyPrelude import Data.UUID -import Types.AppTypes import Types.ConnectionState +type ConnectedClientsState = TVar ConnectedClients + +initConnectionsState :: IO ConnectedClientsState +initConnectionsState = newTVarIO newConnectedClients + +newConnectedClients :: ConnectedClients +newConnectedClients = [] + class Monad m => MonadConnectedClientsModify m where addWSClient :: Client -> m () removeWSClient :: UUID -> m () updateWSClient :: UUID -> (Client -> Client) -> m () +class HasConnectedClientState a where + getConnectedClientState :: a -> ConnectedClientsState + addWSClientGeneric :: ( HasConnectedClientState env, MonadReader env m, diff --git a/backend/src/State/GenericTVarState.hs b/backend/src/State/GenericTVarState.hs new file mode 100644 index 0000000..0793468 --- /dev/null +++ b/backend/src/State/GenericTVarState.hs @@ -0,0 +1,17 @@ +module State.GenericTVarState (GenericTVarState, updateGenericTVarState, updateGenericTVarStateWithQuery, getGenericTVarState) where + +import ClassyPrelude + +type GenericTVarState a = TVar a + +updateGenericTVarState :: (MonadIO m) => GenericTVarState a -> a -> m () +updateGenericTVarState tv a = atomically $ writeTVar tv a + +updateGenericTVarStateWithQuery :: (MonadIO m) => GenericTVarState a -> (a -> a -> b) -> a -> m b +updateGenericTVarStateWithQuery tv f a = atomically $ do + b <- readTVar tv + writeTVar tv a + return $ f b a + +getGenericTVarState :: (MonadIO m) => GenericTVarState a -> m a +getGenericTVarState = readTVarIO diff --git a/backend/src/State/RoomDataState.hs b/backend/src/State/RoomDataState.hs index 35140df..9ef5a09 100644 --- a/backend/src/State/RoomDataState.hs +++ b/backend/src/State/RoomDataState.hs @@ -7,8 +7,8 @@ where import ClassyPrelude import Types.RoomData -class Monad m => MonadRoomDataStateModify m where - setRoomDataState :: RoomsData -> m () +class (Monad m) => MonadRoomDataStateModify m where + setRoomDataState :: RoomsData -> m RoomsStateDiff -class Monad m => MonadRoomDataStateRead m where +class (Monad m) => MonadRoomDataStateRead m where getRoomDataState :: m RoomsData diff --git a/backend/src/State/RoomsState.hs b/backend/src/State/RoomsState.hs new file mode 100644 index 0000000..62a9edc --- /dev/null +++ b/backend/src/State/RoomsState.hs @@ -0,0 +1,88 @@ +module State.RoomsState + ( RoomsState, + initRoomsState, + HasRoomsState (..), + roomStateDiffers, + RoomsStateDiff, + roomStateDiffInOpenRooms, + updateRoomState, + getRoomState, + ) +where + +import ClassyPrelude +import State.GenericTVarState +import State.RoomDataState (MonadRoomDataStateRead (getRoomDataState)) +import Types.RoomData (RoomsData, RoomsStateDiff, roomNotEmpty, sameName) + +type RoomsState = GenericTVarState RoomsData + +initRoomsState :: IO RoomsState +initRoomsState = newTVarIO [] + +class HasRoomsState a where + getRoomsState :: a -> RoomsState + +updateRoomState :: + ( HasRoomsState env, + MonadIO m, + MonadReader env m, + MonadRoomDataStateRead m + ) => + RoomsData -> + m RoomsStateDiff +updateRoomState newData = do + state <- getRoomsState <$> ask + current <- getRoomDataState + if not $ eqIgnoreOrdering newData current + then + ( do + liftIO $ putStrLn "Upating room state" + diff <- updateGenericTVarStateWithQuery state roomStateDiff newData + liftIO $ putStrLn "Done Upating room state" + return diff + ) + else return ([], []) + +getRoomState :: + ( HasRoomsState env, + MonadIO m, + MonadReader env m + ) => + m RoomsData +getRoomState = do + state <- getRoomsState <$> ask + getGenericTVarState state + +roomStateDiffers :: + ( MonadRoomDataStateRead m + ) => + RoomsData -> + m Bool +roomStateDiffers newData = do + not . eqIgnoreOrdering newData <$> getRoomDataState + +roomStateDiffInOpenRooms :: + ( MonadRoomDataStateRead m, + MonadIO m + ) => + RoomsData -> + m (RoomsData, RoomsData) +roomStateDiffInOpenRooms newData = do + current <- getRoomDataState + + liftIO $ putStrLn $ pack $ "Current rooms: " ++ show current + liftIO $ putStrLn $ pack $ "New rooms: " ++ show newData + let newRooms = filter roomNotEmpty $ filter (\newRoom -> isNothing $ find (sameName newRoom) (filter roomNotEmpty current)) newData + let oldRooms = filter (\oldRoom -> isNothing $ find (sameName oldRoom) newData) current + + return (newRooms, oldRooms) + +roomStateDiff :: RoomsData -> RoomsData -> RoomsStateDiff +roomStateDiff newData current = (newRooms, oldRooms) + where + newRooms = filter roomNotEmpty $ filter (\newRoom -> isNothing $ find (sameName newRoom) (filter roomNotEmpty current)) newData + oldRooms = filter (\oldRoom -> isNothing $ find (sameName oldRoom) newData) current + +eqIgnoreOrdering :: (Eq a) => [a] -> [a] -> Bool +eqIgnoreOrdering a b = length a == length b && all (`elem` b) a diff --git a/backend/src/Types/AppTypes.hs b/backend/src/Types/AppTypes.hs index 49338c5..112f480 100644 --- a/backend/src/Types/AppTypes.hs +++ b/backend/src/Types/AppTypes.hs @@ -1,31 +1,36 @@ {-# LANGUAGE DerivingVia #-} -module Types.AppTypes (Env (..), App (..), getConnectedClientState, HasConnectedClientState, AppProfile (Prod, Dev)) where +module Types.AppTypes (Env (..), App (..), HasConfig (getConfig), getConnectedClientState, AppProfile (Prod, Dev)) where import ClassyPrelude -import Types.ConnectionState (ConnectedClientsState) -import Types.RoomsState +import State.ConnectedClientsState +import State.RoomsState ( HasRoomsState (getRoomsState), RoomsState, ) +import Types.Config (ServerOptions) data AppProfile = Prod | Dev data Env = Env { connectedClientsState :: ConnectedClientsState, roomsState :: RoomsState, - profile :: AppProfile + profile :: AppProfile, + config :: ServerOptions } -class HasConnectedClientState a where - getConnectedClientState :: a -> ConnectedClientsState - instance HasConnectedClientState Env where getConnectedClientState = connectedClientsState instance HasRoomsState Env where getRoomsState = roomsState +class HasConfig a where + getConfig :: a -> ServerOptions + +instance HasConfig Env where + getConfig = config + newtype App env a = App {unApp :: env -> IO a} deriving ( Functor, diff --git a/backend/src/Types/Config.hs b/backend/src/Types/Config.hs new file mode 100644 index 0000000..7211e51 --- /dev/null +++ b/backend/src/Types/Config.hs @@ -0,0 +1,61 @@ +module Types.Config + ( ServerOptions (..), + serverOptionsParser, + ) +where + +import ClassyPrelude + ( Applicative ((<*>)), + Int, + Semigroup ((<>)), + Show, + String, + (<$>), + ) +import Options.Applicative (Parser, auto, help, long, metavar, option, short, showDefault, strOption, value) + +data ServerOptions = ServerOptions + { port :: Int, -- Main server port + websocketPort :: Int, -- WebSocket server port + listenAddress :: String, -- Address to bind the server + notifyExecutable :: String -- Path to the notify executable + } + deriving (Show) + +serverOptionsParser :: Parser ServerOptions +serverOptionsParser = + ServerOptions + <$> option + auto + ( long "port" + <> short 'p' + <> metavar "PORT" + <> help "Port number for the main server (default: 8081)" + <> value 8081 + <> showDefault + ) + <*> option + auto + ( long "websocketPort" + <> short 'w' + <> metavar "WS_PORT" + <> help "Port number for the WebSocket server (default: 9160)" + <> value 9160 + <> showDefault + ) + <*> strOption + ( long "listenAddress" + <> short 'l' + <> metavar "ADDRESS" + <> help "IP address or hostname to bind the server (default: 127.0.0.1)" + <> value "127.0.0.1" + <> showDefault + ) + <*> strOption + ( long "notifyExecutable" + <> short 'n' + <> metavar "EXECUTABLE" + <> help "Path to the notify executable (default: /usr/bin/notify)" + <> value "/usr/bin/notify" + <> showDefault + ) diff --git a/backend/src/Types/ConnectionState.hs b/backend/src/Types/ConnectionState.hs index 6f160a4..799ca76 100644 --- a/backend/src/Types/ConnectionState.hs +++ b/backend/src/Types/ConnectionState.hs @@ -1,8 +1,6 @@ module Types.ConnectionState ( Client (..), - ConnectedClientsState, ConnectedClients, - initConnectionsState, ) where @@ -17,12 +15,4 @@ data Client = Client joinedRoom :: Bool } -type ConnectedClientsState = TVar ConnectedClients - type ConnectedClients = [Client] - -initConnectionsState :: IO ConnectedClientsState -initConnectionsState = newTVarIO newConnectedClients - -newConnectedClients :: ConnectedClients -newConnectedClients = [] diff --git a/backend/src/Types/Participant.hs b/backend/src/Types/Participant.hs index 70dbbce..960ec48 100644 --- a/backend/src/Types/Participant.hs +++ b/backend/src/Types/Participant.hs @@ -1,6 +1,6 @@ {-# LANGUAGE DeriveGeneric #-} -module Types.Participant (Participant) where +module Types.Participant (Participant (Participant, displayName)) where import ClassyPrelude import Data.Aeson (FromJSON, ToJSON) diff --git a/backend/src/Types/RoomData.hs b/backend/src/Types/RoomData.hs index 6186ae1..2354257 100644 --- a/backend/src/Types/RoomData.hs +++ b/backend/src/Types/RoomData.hs @@ -1,10 +1,10 @@ {-# LANGUAGE DeriveGeneric #-} -module Types.RoomData (RoomData, RoomsData) where +module Types.RoomData (RoomData, RoomsStateDiff, RoomsData, sameName, roomNotEmpty, prettyPrintOpenedRoom) where import ClassyPrelude import Data.Aeson (FromJSON, ToJSON) -import Types.Participant (Participant) +import Types.Participant (Participant (Participant, displayName)) data RoomData = RoomData { roomName :: RoomName, @@ -12,10 +12,22 @@ data RoomData = RoomData } deriving (Generic, Show, Eq) +sameName :: RoomData -> RoomData -> Bool +sameName RoomData {roomName = name1} RoomData {roomName = name2} = name1 == name2 + +roomNotEmpty :: RoomData -> Bool +roomNotEmpty RoomData {participants = participants} = not $ null participants + +prettyPrintOpenedRoom :: RoomData -> (Text, Text) +prettyPrintOpenedRoom RoomData {roomName = roomName, participants = participants} = + (roomName, fromMaybe "" (headMay (map (\Participant {displayName = displayName} -> displayName) participants))) + type RoomName = Text type RoomsData = [RoomData] +type RoomsStateDiff = (RoomsData, RoomsData) + instance ToJSON RoomData instance FromJSON RoomData diff --git a/backend/src/Types/RoomsState.hs b/backend/src/Types/RoomsState.hs deleted file mode 100644 index 145bbab..0000000 --- a/backend/src/Types/RoomsState.hs +++ /dev/null @@ -1,55 +0,0 @@ -module Types.RoomsState - ( RoomsState, - initRoomsState, - HasRoomsState (..), - roomStateDiffers, - updateRoomState, - getRoomState, - ) -where - -import ClassyPrelude -import State.RoomDataState (MonadRoomDataStateRead (getRoomDataState)) -import Types.RoomData (RoomsData) - -type RoomsState = TVar RoomsData - -initRoomsState :: IO RoomsState -initRoomsState = newTVarIO [] - -class HasRoomsState a where - getRoomsState :: a -> RoomsState - -updateRoomState :: - ( HasRoomsState env, - MonadIO m, - MonadReader env m - ) => - RoomsData -> - m () -updateRoomState newData = do - state <- getRoomsState <$> ask - liftIO $ putStrLn "Upating room state" - atomically $ writeTVar state newData - liftIO $ putStrLn "Done Upating room state" - -getRoomState :: - ( HasRoomsState env, - MonadIO m, - MonadReader env m - ) => - m RoomsData -getRoomState = do - state <- getRoomsState <$> ask - readTVarIO state - -roomStateDiffers :: - ( MonadRoomDataStateRead m - ) => - RoomsData -> - m Bool -roomStateDiffers newData = do - not . eqIgnoreOrdering newData <$> getRoomDataState - -eqIgnoreOrdering :: (Eq a) => [a] -> [a] -> Bool -eqIgnoreOrdering a b = length a == length b && all (`elem` b) a diff --git a/backend/src/Types/WebEnv.hs b/backend/src/Types/WebEnv.hs index eff4d4d..f27d348 100644 --- a/backend/src/Types/WebEnv.hs +++ b/backend/src/Types/WebEnv.hs @@ -6,11 +6,12 @@ where import ClassyPrelude import Network.Wai (Request, Response, ResponseReceived) +import State.ConnectedClientsState (HasConnectedClientState (getConnectedClientState)) +import State.RoomsState (HasRoomsState (getRoomsState)) import Types.AppTypes ( Env (..), - HasConnectedClientState (getConnectedClientState), + HasConfig (getConfig), ) -import Types.RoomsState (HasRoomsState (getRoomsState)) class HasWebEnv a where getRequest :: a -> Request @@ -31,3 +32,6 @@ instance HasRoomsState WebEnv where instance HasWebEnv WebEnv where getRequest = request getRespond = respond + +instance HasConfig WebEnv where + getConfig = getConfig . appEnv diff --git a/backend/src/Types/WebSocketMessages/WebSocketMessages.hs b/backend/src/Types/WebSocketMessages/WebSocketMessages.hs index 641bc5d..66afebc 100644 --- a/backend/src/Types/WebSocketMessages/WebSocketMessages.hs +++ b/backend/src/Types/WebSocketMessages/WebSocketMessages.hs @@ -1,5 +1,6 @@ {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE DuplicateRecordFields #-} +{-# LANGUAGE GADTs #-} module Types.WebSocketMessages.WebSocketMessages ( WebSocketMessage (..), @@ -29,23 +30,22 @@ data WebSocketMessage = ClientInfoMessage SetClientInfo | JoinRoomMessage JoinRo instance FromJSON WebSocketMessage where parseJSON = genericParseJSON defaultOptions {sumEncoding = UntaggedValue} -data SetClientInfo = SetClientInfo - { displayName :: Text - } +data SetClientInfo where + SetClientInfo :: {displayName :: Text} -> SetClientInfo deriving (Generic, Show) instance FromJSON SetClientInfo -data JoinRoom = JoinRoom - { roomName :: Text - } +data JoinRoom where + JoinRoom :: {roomName :: Text} -> JoinRoom deriving (Generic, Show) instance FromJSON JoinRoom -data AllChatMessageIncoming = AllChatMessageIncoming - { content :: Text - } +data AllChatMessageIncoming where + AllChatMessageIncoming :: + {content :: Text} -> + AllChatMessageIncoming deriving (Generic, Show) instance FromJSON AllChatMessageIncoming diff --git a/backend/src/WebServer.hs b/backend/src/WebServer.hs index 8b572f9..5981ee7 100644 --- a/backend/src/WebServer.hs +++ b/backend/src/WebServer.hs @@ -15,7 +15,7 @@ import Control.Monad.Except import Network.HTTP.Types import Network.Wai import Network.Wai.Handler.Warp (run) -import Network.Wai.Middleware.RequestLogger (logStdoutDev) +import Network.Wai.Middleware.RequestLogger (logStdout) import RoomDataHandler (roomDataHandler) import State.ConnectedClientsState ( MonadConnectedClientsRead (..), @@ -25,13 +25,13 @@ import State.RoomDataState ( MonadRoomDataStateModify (..), MonadRoomDataStateRead (getRoomDataState), ) -import Types.AppTypes (Env (..)) -import Types.RoomsState - ( HasRoomsState (getRoomsState), - getRoomState, - roomStateDiffers, +import State.RoomsState + ( getRoomState, updateRoomState, ) +import Text.Printf (printf) +import Types.AppTypes (Env (..), HasConfig (getConfig)) +import Types.Config (ServerOptions (..)) import Types.WebEnv newtype ExceptTApp e a = E {unExceptTApp :: IO (Either e a)} @@ -84,7 +84,8 @@ app :: MonadError ResponseReceived m, MonadRoomDataStateModify m, MonadRoomDataStateRead m, - MonadBroadcast m + MonadBroadcast m, + HasConfig env ) => m ResponseReceived app = requestPathHandler @@ -96,7 +97,8 @@ requestPathHandler :: MonadError ResponseReceived m, MonadRoomDataStateModify m, MonadRoomDataStateRead m, - MonadBroadcast m + MonadBroadcast m, + HasConfig env ) => m ResponseReceived requestPathHandler = do @@ -122,27 +124,11 @@ notFound = do "404 - Not Found" throwError response --- notFound :: --- ( MonadIO m, --- HasWebEnv env, --- MonadReader env m, --- MonadError ResponseReceived m --- ) => --- m ResponseReceived --- notFound = do --- respond' <- getRespond <$> ask --- response <- --- liftIO $ --- respond' $ --- responseLBS --- status200 --- [("Content-Type", "text/plain")] --- "200 - Success" --- response - runWebApp :: ( MonadIO m, - MonadReader Env m + MonadReader Env m, + HasConfig Env, + HasConfig WebEnv ) => m Application runWebApp = do @@ -161,10 +147,14 @@ runWebApp = do runWebServer :: ( MonadIO m, - MonadReader Env m + MonadReader Env m, + HasConfig Env, + HasConfig WebEnv ) => m () runWebServer = do - putStrLn "http://localhost:8081/" - runWebApp >>= liftIO . (run 8081 . logStdoutDev) + config' <- getConfig <$> ask + let ServerOptions {port = webPort, listenAddress = address} = config' + putStrLn $ pack $ printf "Webserver up and running at http://%s:%d/" address webPort + runWebApp >>= liftIO . (run webPort . logStdout) return () diff --git a/backend/src/WebSocket/Server.hs b/backend/src/WebSocket/Server.hs index 666a8e0..14c6e66 100644 --- a/backend/src/WebSocket/Server.hs +++ b/backend/src/WebSocket/Server.hs @@ -1,4 +1,5 @@ {-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE MonoLocalBinds #-} module WebSocket.Server ( runWebSocketServer, @@ -9,22 +10,27 @@ where import ClassyPrelude import Data.UUID.V4 (nextRandom) import Network.WebSockets qualified as WS -import Types.AppTypes +import Text.Printf +import Types.AppTypes (Env, HasConfig (getConfig)) +import Types.Config (ServerOptions (..)) import WebSocket.WSApp (WSApp (..), WSEnv (..), wsApp) runWebSocketServer :: ( MonadIO m, - MonadReader Env m + Types.AppTypes.HasConfig Types.AppTypes.Env, + MonadReader Types.AppTypes.Env m ) => m () runWebSocketServer = do - putStrLn "Websocket up at 0.0.0.0:9160" + config' <- getConfig <$> ask + let ServerOptions {websocketPort = wsPort, listenAddress = address} = config' + putStrLn $ pack $ printf "WebSocket server up and running at ws://%s:%d/" address wsPort wsApp' <- runWSApp - liftIO $ WS.runServer "0.0.0.0" 9160 wsApp' + liftIO $ WS.runServer address wsPort wsApp' runWSApp :: ( MonadIO m, - MonadReader Env m + MonadReader Types.AppTypes.Env m ) => m WS.ServerApp runWSApp = do diff --git a/backend/src/WebSocket/WSApp.hs b/backend/src/WebSocket/WSApp.hs index 6d613ad..85a0786 100644 --- a/backend/src/WebSocket/WSApp.hs +++ b/backend/src/WebSocket/WSApp.hs @@ -33,11 +33,11 @@ wsApp = do broadcastUserData withCleanUp $ forever $ do handleWSAction - broadcastUserData handleWSAction :: ( MonadWebSocketSession m, MonadConnectedClientsModify m, + MonadRoomDataStateRead m, MonadBroadcast m, MonadAllChat m ) => @@ -47,8 +47,10 @@ handleWSAction = do case msg of JoinRoomMessage _ -> do joinRoom + broadcastUserData ClientInfoMessage clientInfo -> do updateClientName clientInfo + broadcastUserData AllChatMessageIncomingMessage incomingMessage -> do broadCastAllChatMessage incomingMessage diff --git a/backend/src/WebSocket/WSReaderTApp.hs b/backend/src/WebSocket/WSReaderTApp.hs index 3ea6ef3..d997065 100644 --- a/backend/src/WebSocket/WSReaderTApp.hs +++ b/backend/src/WebSocket/WSReaderTApp.hs @@ -17,7 +17,8 @@ import ClassyPrelude import Data.UUID import Network.WebSockets qualified as WS import State.ConnectedClientsState - ( MonadConnectedClientsModify (..), + ( HasConnectedClientState, + MonadConnectedClientsModify (..), MonadConnectedClientsRead (..), addWSClientGeneric, getConnctedClientsGeneric, @@ -25,8 +26,8 @@ import State.ConnectedClientsState updateWSClientGeneric, ) import State.RoomDataState +import State.RoomsState (HasRoomsState (..), getRoomState) import Types.AppTypes -import Types.RoomsState (HasRoomsState (..), getRoomState) data WSEnv = WSEnv { appEnv :: Env, diff --git a/backend/stack.yaml b/backend/stack.yaml index 1c01a4b..e142431 100644 --- a/backend/stack.yaml +++ b/backend/stack.yaml @@ -14,9 +14,10 @@ # Use the latest resolver that uses the same ghc version # as build for nixos # this way we can use prebuild binaries for hls -#resolver: nightly-2022-11-12 -#resolver: ghc-9.2.4 -resolver: lts-20.26 +# go to +# https://www.stackage.org/ +# and select the lts that is matching you hls version +resolver: lts-22.43 # # The location of a snapshot can be provided as a file or url. Stack assumes # a snapshot provided as a file might change, whereas a url resource does not. @@ -73,5 +74,3 @@ packages: # compiler-check: newer-minor ghc-options: "$everything": -haddock - - diff --git a/backend/stack.yaml.lock b/backend/stack.yaml.lock index ea5a850..f9829eb 100644 --- a/backend/stack.yaml.lock +++ b/backend/stack.yaml.lock @@ -6,7 +6,7 @@ packages: [] snapshots: - completed: - sha256: 5a59b2a405b3aba3c00188453be172b85893cab8ebc352b1ef58b0eae5d248a2 - size: 650475 - url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/20/26.yaml - original: lts-20.26 + sha256: 08bd13ce621b41a8f5e51456b38d5b46d7783ce114a50ab604d6bbab0d002146 + size: 720271 + url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/22/43.yaml + original: lts-22.43 diff --git a/frontend/default.nix b/frontend/default.nix new file mode 100644 index 0000000..e5c4960 --- /dev/null +++ b/frontend/default.nix @@ -0,0 +1,34 @@ +{ + pkgs ? import { }, +}: + +pkgs.buildNpmPackage { + pname = "jitsi-rooms-backendd"; + version = "1.0.0"; + + # Source files (usually the current directory) + src = pkgs.lib.cleanSource ./.; + + # Optionally, you can provide a package-lock.json or yarn.lock file + # This ensures dependencies are installed reproducibly. + packageLock = ./package-lock.json; # Use this for npm + + # Node.js version (optional, defaults to pkgs.nodejs) + nodejs = pkgs.nodejs_24; + npmDepsHash = "sha256-n9SpPPRvu92RwNPDKZ3f1Splbux2IVGhSazJ4DM2IrA="; + + # Add any additional arguments for the build process + buildInputs = [ ]; + + # Specify the build phase, if needed + buildPhase = '' + echo "Building the app..." + npm run build + ''; + + # Specify the install phase (what to copy to the output) + installPhase = '' + mkdir -p $out + cp -r dist/* $out/ + ''; +} diff --git a/frontend/index.html b/frontend/index.html index db6a78f..d6b1c17 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -2,7 +2,7 @@ - + FF Jitsi Rooms diff --git a/frontend/package-lock.json b/frontend/package-lock.json new file mode 100644 index 0000000..bb7a441 --- /dev/null +++ b/frontend/package-lock.json @@ -0,0 +1,1632 @@ +{ + "name": "jitsi-roomsv2", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "jitsi-roomsv2", + "version": "0.0.0", + "dependencies": { + "@fortawesome/fontawesome-svg-core": "^6.4.0", + "@fortawesome/free-regular-svg-icons": "^6.4.0", + "@fortawesome/react-fontawesome": "^0.2.0", + "@jitsi/react-sdk": "^1.3.0", + "jotai": "^2.0.3", + "just-curry-it": "^5.3.0", + "react": "^18.2.0", + "react-dom": "^18.2.0" + }, + "devDependencies": { + "@types/react": "^18.0.26", + "@types/react-dom": "^18.0.9", + "@vitejs/plugin-react": "^3.0.0", + "typescript": "^4.9.3", + "vite": "^4.0.0" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", + "integrity": "sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jridgewell/gen-mapping": "^0.1.0", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", + "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/highlight": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.20.10", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.20.10.tgz", + "integrity": "sha512-sEnuDPpOJR/fcafHMjpcpGN5M2jbUGUHwmuWKM/YdPzeEDJg8bgmbcWQFUfE32MQjti1koACvoPVsDe8Uq+idg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.20.12", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.20.12.tgz", + "integrity": "sha512-XsMfHovsUYHFMdrIHkZphTN/2Hzzi78R08NuHfDBehym2VsPDL6Zn/JAD/JQdnRvbSsbQc4mVaU1m6JgtTEElg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.1.0", + "@babel/code-frame": "^7.18.6", + "@babel/generator": "^7.20.7", + "@babel/helper-compilation-targets": "^7.20.7", + "@babel/helper-module-transforms": "^7.20.11", + "@babel/helpers": "^7.20.7", + "@babel/parser": "^7.20.7", + "@babel/template": "^7.20.7", + "@babel/traverse": "^7.20.12", + "@babel/types": "^7.20.7", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.2", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.20.7.tgz", + "integrity": "sha512-7wqMOJq8doJMZmP4ApXTzLxSr7+oO2jroJURrVEp6XShrQUObV8Tq/D0NCcoYg2uHqUrjzO0zwBjoYzelxK+sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.20.7", + "@jridgewell/gen-mapping": "^0.3.2", + "jsesc": "^2.5.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/generator/node_modules/@jridgewell/gen-mapping": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", + "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.20.7.tgz", + "integrity": "sha512-4tGORmfQcrc+bvrjb5y3dG9Mx1IOZjsHqQVUz7XCNHO+iTmqxWnVg3KRygjGmpRLJGdQSKuvFinbIb0CnZwHAQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.20.5", + "@babel/helper-validator-option": "^7.18.6", + "browserslist": "^4.21.3", + "lru-cache": "^5.1.1", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-environment-visitor": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz", + "integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-function-name": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.19.0.tgz", + "integrity": "sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.18.10", + "@babel/types": "^7.19.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-hoist-variables": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", + "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz", + "integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.20.11", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.20.11.tgz", + "integrity": "sha512-uRy78kN4psmji1s2QtbtcCSaj/LILFDp0f/ymhpQH5QY3nljUZCaNWz9X1dEj/8MBdBEFECs7yRhKn8i7NjZgg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-module-imports": "^7.18.6", + "@babel/helper-simple-access": "^7.20.2", + "@babel/helper-split-export-declaration": "^7.18.6", + "@babel/helper-validator-identifier": "^7.19.1", + "@babel/template": "^7.20.7", + "@babel/traverse": "^7.20.10", + "@babel/types": "^7.20.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.20.2.tgz", + "integrity": "sha512-8RvlJG2mj4huQ4pZ+rU9lqKi9ZKiRmuvGuM2HlWmkmgOhbs6zEAw6IEiJ5cQqGbDzGZOhwuOQNtZMi/ENLjZoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-simple-access": { + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.20.2.tgz", + "integrity": "sha512-+0woI/WPq59IrqDYbVGfshjT5Dmk/nnbdpcF8SnMhhXObpTq2KNBdLFRFrkVdbDOyUmHBCxzm5FHV1rACIkIbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.20.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-split-export-declaration": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", + "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.19.4", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz", + "integrity": "sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", + "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz", + "integrity": "sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.20.7.tgz", + "integrity": "sha512-PBPjs5BppzsGaxHQCDKnZ6Gd9s6xl8bBCluz3vEInLGRJmnZan4F6BYCeqtyXqkk4W5IlPmjK4JlOuZkpJ3xZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.20.7", + "@babel/traverse": "^7.20.7", + "@babel/types": "^7.20.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", + "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.18.6", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.7.tgz", + "integrity": "sha512-T3Z9oHybU+0vZlY9CiDSJQTD5ZapcW18ZctFMi0MOAl/4BjFF4ul7NVSARLdbGO5vDqy9eQiGTV0LtKfvCYvcg==", + "dev": true, + "license": "MIT", + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.18.6.tgz", + "integrity": "sha512-A0LQGx4+4Jv7u/tWzoJF7alZwnBDQd6cGLh9P+Ttk4dpiL+J5p7NSNv/9tlEFFJDq3kjxOavWmbm6t0Gk+A3Ig==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.19.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.19.6.tgz", + "integrity": "sha512-RpAi004QyMNisst/pvSanoRdJ4q+jMCWyk9zdw/CyLB9j8RXEahodR6l2GyttDRyEVWZtbN+TpLiHJ3t34LbsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.19.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.20.7.tgz", + "integrity": "sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.18.6", + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.20.12", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.20.12.tgz", + "integrity": "sha512-MsIbFN0u+raeja38qboyF8TIT7K0BFzz/Yd/77ta4MsUsmP2RAnidIlwq7d5HFQrH/OZJecGV6B71C4zAgpoSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.18.6", + "@babel/generator": "^7.20.7", + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-function-name": "^7.19.0", + "@babel/helper-hoist-variables": "^7.18.6", + "@babel/helper-split-export-declaration": "^7.18.6", + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "debug": "^4.1.0", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.20.7.tgz", + "integrity": "sha512-69OnhBxSSgK0OzTJai4kyPDiKTIe3j+ctaHdIGVbRahTLAT7L3R9oeXHC2aVSuGYt3cVnoAMDmOCgJ2yaiLMvg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.19.4", + "@babel/helper-validator-identifier": "^7.19.1", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.16.15", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.16.15.tgz", + "integrity": "sha512-JsJtmadyWcR+DEtHLixM7bAQsfi1s0Xotv9kVOoXbCLyhKPOHvMEyh3kJBuTbCPSE4c2jQkQVmarwc9Mg9k3bA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.16.15", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.16.15.tgz", + "integrity": "sha512-OdbkUv7468dSsgoFtHIwTaYAuI5lDEv/v+dlfGBUbVa2xSDIIuSOHXawynw5N9+5lygo/JdXa5/sgGjiEU18gQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.16.15", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.16.15.tgz", + "integrity": "sha512-dPUOBiNNWAm+/bxoA75o7R7qqqfcEzXaYlb5uJk2xGHmUMNKSAnDCtRYLgx9/wfE4sXyn8H948OrDyUAHhPOuA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.16.15", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.16.15.tgz", + "integrity": "sha512-AksarYV85Hxgwh5/zb6qGl4sYWxIXPQGBAZ+jUro1ZpINy3EWumK+/4DPOKUBPnsrOIvnNXy7Rq4mTeCsMQDNA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.16.15", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.16.15.tgz", + "integrity": "sha512-qqrKJxoohceZGGP+sZ5yXkzW9ZiyFZJ1gWSEfuYdOWzBSL18Uy3w7s/IvnDYHo++/cxwqM0ch3HQVReSZy7/4Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.16.15", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.16.15.tgz", + "integrity": "sha512-LBWaep6RvJm5KnsKkocdVEzuwnGMjz54fcRVZ9d3R7FSEWOtPBxMhuxeA1n98JVbCLMkTPFmKN6xSnfhnM9WXQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.16.15", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.16.15.tgz", + "integrity": "sha512-LE8mKC6JPR04kPLRP9A6k7ZmG0k2aWF4ru79Sde6UeWCo7yDby5f48uJNFQ2pZqzUUkLrHL8xNdIHerJeZjHXg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.16.15", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.16.15.tgz", + "integrity": "sha512-+1sGlqtMJTOnJUXwLUGnDhPaGRKqxT0UONtYacS+EjdDOrSgpQ/1gUXlnze45Z/BogwYaswQM19Gu1YD1T19/w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.16.15", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.16.15.tgz", + "integrity": "sha512-mRYpuQGbzY+XLczy3Sk7fMJ3DRKLGDIuvLKkkUkyecDGQMmil6K/xVKP9IpKO7JtNH477qAiMjjX7jfKae8t4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.16.15", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.16.15.tgz", + "integrity": "sha512-puXVFvY4m8EB6/fzu3LdgjiNnEZ3gZMSR7NmKoQe51l3hyQalvTjab3Dt7aX4qGf+8Pj7dsCOBNzNzkSlr/4Aw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.16.15", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.16.15.tgz", + "integrity": "sha512-ATMGb3eg8T6ZTGZFldlGeFEcevBiVq6SBHvRAO04HMfUjZWneZ/U+JJb3YzlNZxuscJ4Tmzq+JrYxlk7ro4dRg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.16.15", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.16.15.tgz", + "integrity": "sha512-3SEA4L82OnoSATW+Ve8rPgLaKjC8WMt8fnx7De9kvi/NcVbkj8W+J7qnu/tK2P9pUPQP7Au/0sjPEqZtFeyKQQ==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.16.15", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.16.15.tgz", + "integrity": "sha512-8PgbeX+N6vmqeySzyxO0NyDOltCEW13OS5jUHTvCHmCgf4kNXZtAWJ+zEfJxjRGYhVezQ1FdIm7WfN1R27uOyg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.16.15", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.16.15.tgz", + "integrity": "sha512-U+coqH+89vbPVoU30no1Fllrn6gvEeO5tfEArBhjYZ+dQ3Gv7ciQXYf5nrT1QdlIFwEjH4Is1U1iiaGWW+tGpQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.16.15", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.16.15.tgz", + "integrity": "sha512-M0nKLFMdyFGBoitxG42kq6Xap0CPeDC6gfF9lg7ZejzGF6kqYUGT+pQGl2QCQoxJBeat/LzTma1hG8C3dq2ocg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.16.15", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.16.15.tgz", + "integrity": "sha512-t7/fOXBUKfigvhJLGKZ9TPHHgqNgpIpYaAbcXQk1X+fPeUG7x0tpAbXJ2wST9F/gJ02+CLETPMnhG7Tra2wqsQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.16.15", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.16.15.tgz", + "integrity": "sha512-0k0Nxi6DOJmTnLtKD/0rlyqOPpcqONXY53vpkoAsue8CfyhNPWtwzba1ICFNCfCY1dqL3Ho/xEzujJhmdXq1rg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.16.15", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.16.15.tgz", + "integrity": "sha512-3SkckazfIbdSjsGpuIYT3d6n2Hx0tck3MS1yVsbahhWiLvdy4QozTpvlbjqO3GmvtvhxY4qdyhFOO2wiZKeTAQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.16.15", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.16.15.tgz", + "integrity": "sha512-8PNvBC+O8X5EnyIGqE8St2bOjjrXMR17NOLenIrzolvwWnJXvwPo0tE/ahOeiAJmTOS/eAcN8b4LAZcn17Uj7w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.16.15", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.16.15.tgz", + "integrity": "sha512-YPaSgm/mm7kNcATB53OxVGVfn6rDNbImTn330ZlF3hKej1e9ktCaljGjn2vH08z2dlHEf3kdt57tNjE6zs8SzA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.16.15", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.16.15.tgz", + "integrity": "sha512-0movUXbSNrTeNf5ZXT0avklEvlJD0hNGZsrrXHfsp9z4tK5xC+apCqmUEZeE9mqrb84Z8XbgGr/MS9LqafTP2A==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.16.15", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.16.15.tgz", + "integrity": "sha512-27h5GCcbfomVAqAnMJWvR1LqEY0dFqIq4vTe5nY3becnZNu0SX8F0+gTk3JPvgWQHzaGc6VkPzlOiMkdSUunUA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@fortawesome/fontawesome-common-types": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.4.0.tgz", + "integrity": "sha512-HNii132xfomg5QVZw0HwXXpN22s7VBHQBv9CeOu9tfJnhsWQNd2lmTNi8CSrnw5B+5YOmzu1UoPAyxaXsJ6RgQ==", + "hasInstallScript": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/@fortawesome/fontawesome-svg-core": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.4.0.tgz", + "integrity": "sha512-Bertv8xOiVELz5raB2FlXDPKt+m94MQ3JgDfsVbrqNpLU9+UE2E18GKjLKw+d3XbeYPqg1pzyQKGsrzbw+pPaw==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "@fortawesome/fontawesome-common-types": "6.4.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@fortawesome/free-regular-svg-icons": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-6.4.0.tgz", + "integrity": "sha512-ZfycI7D0KWPZtf7wtMFnQxs8qjBXArRzczABuMQqecA/nXohquJ5J/RCR77PmY5qGWkxAZDxpnUFVXKwtY/jPw==", + "hasInstallScript": true, + "license": "(CC-BY-4.0 AND MIT)", + "dependencies": { + "@fortawesome/fontawesome-common-types": "6.4.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@fortawesome/react-fontawesome": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@fortawesome/react-fontawesome/-/react-fontawesome-0.2.0.tgz", + "integrity": "sha512-uHg75Rb/XORTtVt7OS9WoK8uM276Ufi7gCzshVWkUJbHhh3svsUUeqXerrM96Wm7fRiDzfKRwSoahhMIkGAYHw==", + "license": "MIT", + "dependencies": { + "prop-types": "^15.8.1" + }, + "peerDependencies": { + "@fortawesome/fontawesome-svg-core": "~1 || ~6", + "react": ">=16.3" + } + }, + "node_modules/@jitsi/react-sdk": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@jitsi/react-sdk/-/react-sdk-1.3.0.tgz", + "integrity": "sha512-f+xtk/j0iXIMJscJeGt3OXnocFeG1pX6OMjv7H/9AaTnCUNsDc/jfDSPme+h0RmdymzJdkSwyHjmkhDK9aehzw==", + "license": "Apache-2.0", + "peerDependencies": { + "react": "16 || 17 || 18", + "react-dom": "16 || 17 || 18" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", + "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/set-array": "^1.0.0", + "@jridgewell/sourcemap-codec": "^1.4.10" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.17", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz", + "integrity": "sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "3.1.0", + "@jridgewell/sourcemap-codec": "1.4.14" + } + }, + "node_modules/@types/prop-types": { + "version": "15.7.5", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", + "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "18.0.26", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.26.tgz", + "integrity": "sha512-hCR3PJQsAIXyxhTNSiDFY//LhnMZWpNNr5etoCqx/iUfGc5gXWtQR2Phl908jVR6uPXacojQWTg4qRpkxTuGug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/prop-types": "*", + "@types/scheduler": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.0.10", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.0.10.tgz", + "integrity": "sha512-E42GW/JA4Qv15wQdqJq8DL4JhNpB3prJgjgapN3qJT9K2zO5IIAQh4VXvCEDupoqAwnz0cY4RlXeC/ajX5SFHg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/react": "*" + } + }, + "node_modules/@types/scheduler": { + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", + "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vitejs/plugin-react": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-3.0.1.tgz", + "integrity": "sha512-mx+QvYwIbbpOIJw+hypjnW1lAbKDHtWK5ibkF/V1/oMBu8HU/chb+SnqJDAsLq1+7rGqjktCEomMTM5KShzUKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.20.7", + "@babel/plugin-transform-react-jsx-self": "^7.18.6", + "@babel/plugin-transform-react-jsx-source": "^7.19.6", + "magic-string": "^0.27.0", + "react-refresh": "^0.14.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.0.0" + } + }, + "node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/browserslist": { + "version": "4.21.4", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.4.tgz", + "integrity": "sha512-CBHJJdDmgjl3daYjN5Cp5kbTf1mUhZoS+beLklHIvkOWscs83YAhLlF3Wsh/lciQYAcbBJgTOD44VtG31ZM4Hw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + } + ], + "license": "MIT", + "dependencies": { + "caniuse-lite": "^1.0.30001400", + "electron-to-chromium": "^1.4.251", + "node-releases": "^2.0.6", + "update-browserslist-db": "^1.0.9" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001442", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001442.tgz", + "integrity": "sha512-239m03Pqy0hwxYPYR5JwOIxRJfLTWtle9FV8zosfV5pHg+/51uD4nxcUlM8+mWWGfwKtt8lJNHnD3cWw9VZ6ow==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true, + "license": "MIT" + }, + "node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "dev": true, + "license": "MIT" + }, + "node_modules/csstype": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.1.tgz", + "integrity": "sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw==", + "dev": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/electron-to-chromium": { + "version": "1.4.284", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.284.tgz", + "integrity": "sha512-M8WEXFuKXMYMVr45fo8mq0wUrrJHheiKZf6BArTKk9ZBYCKJEOU5H8cdWgDT+qCVZf7Na4lVUaZsA+h6uA9+PA==", + "dev": true, + "license": "ISC" + }, + "node_modules/esbuild": { + "version": "0.16.15", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.16.15.tgz", + "integrity": "sha512-v+3ozjy9wyj8cOElzx3//Lsb4TCxPfZxRmdsfm0YaEkvZu7y6rKH7Zi1UpDx4JI7dSQui+U1Qxhfij9KBbHfrA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/android-arm": "0.16.15", + "@esbuild/android-arm64": "0.16.15", + "@esbuild/android-x64": "0.16.15", + "@esbuild/darwin-arm64": "0.16.15", + "@esbuild/darwin-x64": "0.16.15", + "@esbuild/freebsd-arm64": "0.16.15", + "@esbuild/freebsd-x64": "0.16.15", + "@esbuild/linux-arm": "0.16.15", + "@esbuild/linux-arm64": "0.16.15", + "@esbuild/linux-ia32": "0.16.15", + "@esbuild/linux-loong64": "0.16.15", + "@esbuild/linux-mips64el": "0.16.15", + "@esbuild/linux-ppc64": "0.16.15", + "@esbuild/linux-riscv64": "0.16.15", + "@esbuild/linux-s390x": "0.16.15", + "@esbuild/linux-x64": "0.16.15", + "@esbuild/netbsd-x64": "0.16.15", + "@esbuild/openbsd-x64": "0.16.15", + "@esbuild/sunos-x64": "0.16.15", + "@esbuild/win32-arm64": "0.16.15", + "@esbuild/win32-ia32": "0.16.15", + "@esbuild/win32-x64": "0.16.15" + } + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true, + "license": "MIT" + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/is-core-module": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", + "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/jotai": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/jotai/-/jotai-2.0.3.tgz", + "integrity": "sha512-MMjhSPAL3RoeZD9WbObufRT2quThEAEknHHridf2ma8Ml7ZVQmUiHk0ssdbR3F0h3kcwhYqSGJ59OjhPge7RRg==", + "license": "MIT", + "engines": { + "node": ">=12.20.0" + }, + "peerDependencies": { + "react": ">=17.0.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + } + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/just-curry-it": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/just-curry-it/-/just-curry-it-5.3.0.tgz", + "integrity": "sha512-silMIRiFjUWlfaDhkgSzpuAyQ6EX/o09Eu8ZBfmFwQMbax7+LQzeIU2CBrICT6Ne4l86ITCGvUCBpCubWYy0Yw==", + "license": "MIT" + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/magic-string": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.27.0.tgz", + "integrity": "sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.13" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", + "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==", + "dev": true, + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-releases": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.8.tgz", + "integrity": "sha512-dFSmB8fFHEH/s81Xi+Y/15DQY6VHW81nXRj86EMSL3lmuTmK1e+aT4wrFCkTbm+gSwkw4KpX+rT/pMM2c1mF+A==", + "dev": true, + "license": "MIT" + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/postcss": { + "version": "8.4.21", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.21.tgz", + "integrity": "sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.4", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/react": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", + "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", + "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.0" + }, + "peerDependencies": { + "react": "^18.2.0" + } + }, + "node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" + }, + "node_modules/react-refresh": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.0.tgz", + "integrity": "sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", + "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.9.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/rollup": { + "version": "3.9.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.9.1.tgz", + "integrity": "sha512-GswCYHXftN8ZKGVgQhTFUJB/NBXxrRGgO2NCy6E8s1rwEJ4Q9/VttNqcYfEvx4dTo4j58YqdC3OVztPzlKSX8w==", + "dev": true, + "license": "MIT", + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=14.18.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/scheduler": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", + "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/typescript": { + "version": "4.9.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.4.tgz", + "integrity": "sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz", + "integrity": "sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + }, + "bin": { + "browserslist-lint": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/vite": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/vite/-/vite-4.0.4.tgz", + "integrity": "sha512-xevPU7M8FU0i/80DMR+YhgrzR5KS2ORy1B4xcX/cXLsvnUWvfHuqMmVU6N0YiJ4JWGRJJsLCgjEzKjG9/GKoSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.16.3", + "postcss": "^8.4.20", + "resolve": "^1.22.1", + "rollup": "^3.7.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + }, + "peerDependencies": { + "@types/node": ">= 14", + "less": "*", + "sass": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + } + } +} diff --git a/frontend/public/favicon.ico b/frontend/public/favicon.ico new file mode 100755 index 0000000..425b15a Binary files /dev/null and b/frontend/public/favicon.ico differ diff --git a/frontend/public/vite.svg b/frontend/public/vite.svg deleted file mode 100644 index e7b8dfb..0000000 --- a/frontend/public/vite.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/frontend/result b/frontend/result new file mode 120000 index 0000000..3053c75 --- /dev/null +++ b/frontend/result @@ -0,0 +1 @@ +/nix/store/x3fqdrf05dcg52s7x6dkzql551g0bfxw-my-node-app-1.0.0 \ No newline at end of file diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 8fde18d..7206931 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -20,6 +20,7 @@ function App() { conferenceData={conferenceData} setConferenceData={setConferenceData} userInfo={userInfo} + usersData={roomData} /> ) diff --git a/frontend/src/background/constants.ts b/frontend/src/background/constants.ts index 2da5e5d..46f6bd8 100644 --- a/frontend/src/background/constants.ts +++ b/frontend/src/background/constants.ts @@ -1,9 +1,9 @@ const ISPROD = window.location.protocol == 'https:' -const JITSI_DOMAIN = 'thisisnotajitsi.filefighter.de' +const JITSI_DOMAIN = 'meet.filefighter.de' const USE_REMOTE_BACKEND = true const getWebsocketUrl = () => { if (ISPROD) return 'wss://' + window.location.host + '/ws' - if (USE_REMOTE_BACKEND) return 'wss://' + 'discord.filefighter.de/ws' + if (USE_REMOTE_BACKEND) return 'wss://' + 'treffen.filefighter.de/ws' return 'ws://' + 'localhost:9160/ws' } const WEBSOCKET_URL = getWebsocketUrl() diff --git a/frontend/src/components/meeting/Meeting.css b/frontend/src/components/meeting/Meeting.css new file mode 100644 index 0000000..a63af6c --- /dev/null +++ b/frontend/src/components/meeting/Meeting.css @@ -0,0 +1,7 @@ +.meeting-quickjoin { + text-align: center +} + +.meeting { + flex-grow: 3; +} diff --git a/frontend/src/components/meeting/Meeting.tsx b/frontend/src/components/meeting/Meeting.tsx index ccc2b1f..eb0e3b3 100644 --- a/frontend/src/components/meeting/Meeting.tsx +++ b/frontend/src/components/meeting/Meeting.tsx @@ -1,19 +1,27 @@ import { ConferenceData } from '../../background/jitsi/eventListeners' +import { UsersData } from '../../background/types/roomData' import useMeetingStarted from '../../hooks/useMeetingStarted' import { useRoomName } from '../../hooks/useRoomName' import JitsiEntrypoint from '../jitsi/JitsiEntrypoint' import { UserInfo } from '../jitsi/types' import MeetingNameInput from './MeetingNameInput' +import './Meeting.css' +import React from 'react' interface Props { conferenceData: ConferenceData | undefined setConferenceData: (newData: ConferenceData) => void userInfo: UserInfo + usersData: UsersData } -function Meeting({ conferenceData, setConferenceData, userInfo }: Props) { +function Meeting(props: Props) { + const { conferenceData, setConferenceData, userInfo, usersData } = props + console.log("[Rooms] meeting usersData", props) const [meetingStarted] = useMeetingStarted() const { roomName } = useRoomName() + const [_, setMeetingStarted] = useMeetingStarted() + const { updateAndSubmitRoomName: updateAndSubmitRoomName } = useRoomName() if (meetingStarted) { return ( @@ -26,7 +34,32 @@ function Meeting({ conferenceData, setConferenceData, userInfo }: Props) { ) } - return + return ( +
+ +
+ {usersData?.roomsData.map((roomData) => { + return ( + +

+ { + updateAndSubmitRoomName(roomData.roomName) + setMeetingStarted(true) + }} + > + {decodeURI(roomData.roomName)} + +

+ {roomData.participants.map((participant) => ( +
{participant.displayName}
+ ))} +
+ ) + })} +
+
) } export default Meeting diff --git a/frontend/src/components/meeting/MeetingNameInput.tsx b/frontend/src/components/meeting/MeetingNameInput.tsx index 0da3d15..3c498be 100644 --- a/frontend/src/components/meeting/MeetingNameInput.tsx +++ b/frontend/src/components/meeting/MeetingNameInput.tsx @@ -8,7 +8,7 @@ function MeetingNameInput(props: { roomName: string; currentUser: string }) { const [_, setMeetingStarted] = useMeetingStarted() const onInput: React.ChangeEventHandler = (event) => { - updateRoomName(event.target.value) + updateRoomName(encodeURI(event.target.value)) event.preventDefault() } @@ -23,7 +23,7 @@ function MeetingNameInput(props: { roomName: string; currentUser: string }) {

Greetings {props.currentUser}

- +
diff --git a/frontend/src/components/sidebar/Sidebar.css b/frontend/src/components/sidebar/Sidebar.css index 40a20cb..d74aba8 100644 --- a/frontend/src/components/sidebar/Sidebar.css +++ b/frontend/src/components/sidebar/Sidebar.css @@ -1,4 +1,5 @@ .sidebar { + resize: horizontal; display: flex; flex-direction: column; white-space: nowrap; diff --git a/frontend/src/components/sidebar/Sidebar.tsx b/frontend/src/components/sidebar/Sidebar.tsx index e67f381..b5440b5 100644 --- a/frontend/src/components/sidebar/Sidebar.tsx +++ b/frontend/src/components/sidebar/Sidebar.tsx @@ -5,6 +5,7 @@ import { UsersData } from '../../background/types/roomData' import useMeetingStarted from '../../hooks/useMeetingStarted' import { useRoomName } from '../../hooks/useRoomName' import Chat from '../chat/Chat' +import React from 'react' interface Props { usersData: UsersData @@ -22,7 +23,7 @@ function Sidebar(props: Props) {
{props.usersData.roomsData.map((roomData) => { return ( - <> +

- {roomData.roomName} + {decodeURI(roomData.roomName)}

{roomData.participants.map((participant) => (
{participant.displayName}
))} - +
) })}
-

No room

+

No room

{props.usersData.usersWithOutRoom.map((user) => (
{user.name}
))} diff --git a/frontend/src/components/sidebar/useSidebarVisibility.ts b/frontend/src/components/sidebar/useSidebarVisibility.ts index 2cbdbff..8a1589f 100644 --- a/frontend/src/components/sidebar/useSidebarVisibility.ts +++ b/frontend/src/components/sidebar/useSidebarVisibility.ts @@ -20,7 +20,7 @@ function useSidebarVisibility() { return { sidebarVisibility, toggleSidebarVisibility, sidebarToggleText } } const getSidebarToggleText = (sidebarVisibility: sidebarVisibilityOptions) => { - if (sidebarVisibility === 'full') return '<-' + if (sidebarVisibility === 'full') return '<--' if (sidebarVisibility === 'small') return '<-' if (sidebarVisibility === 'hidden') return '->' } diff --git a/frontend/src/hooks/useBackendData.ts b/frontend/src/hooks/useBackendData.ts index 43be19c..a69ac5e 100644 --- a/frontend/src/hooks/useBackendData.ts +++ b/frontend/src/hooks/useBackendData.ts @@ -7,20 +7,23 @@ import useWebSocketConnection from './useWebSocketConnection' function useBackendData(userInfo: UserInfo) { console.log('[Rooms] useBackendData') - const { onMessage, sendMessage, disconnect } = useWebSocketConnection(userInfo) + const { onMessage, cleanUpOnMessage, sendMessage, disconnect } = useWebSocketConnection(userInfo) const { roomData, setRoomData } = useRoomData() const { addChatMessage } = useAllChat() useEffect(() => { - onMessage((messageString) => { + console.log('[Rooms] add onMessage Listener') + const messageCallback = (messageString: string) => { console.log('[Rooms] message from ws', messageString) const messageObject = JSON.parse(messageString) !!messageObject.roomsData && setRoomData(messageObject) !!messageObject.content && addChatMessage(messageObject) return disconnect - }) - }, [onMessage, setRoomData, disconnect]) + } + onMessage(messageCallback) + return () => cleanUpOnMessage(messageCallback) + }, []) return { roomData, sendMessage } } diff --git a/frontend/src/hooks/useRoomData.ts b/frontend/src/hooks/useRoomData.ts index f8a66f4..ccef5b3 100644 --- a/frontend/src/hooks/useRoomData.ts +++ b/frontend/src/hooks/useRoomData.ts @@ -2,7 +2,15 @@ import { useState } from 'react' import { UsersData } from '../background/types/roomData' function useRoomData() { - const [roomData, setRoomData] = useState() + const [roomData, setRoomDataInternal] = useState() + + const setRoomData = (usersData: UsersData) => { + usersData.roomsData = usersData.roomsData.map((roomData) => { + roomData.roomName = decodeURI(roomData.roomName) + return roomData + }) + setRoomDataInternal(usersData) + } return { roomData, setRoomData } } diff --git a/frontend/src/hooks/useRoomName.ts b/frontend/src/hooks/useRoomName.ts index 459b201..14e3c1f 100644 --- a/frontend/src/hooks/useRoomName.ts +++ b/frontend/src/hooks/useRoomName.ts @@ -1,5 +1,5 @@ import { atom, useAtom } from 'jotai' -import { useCallback, useState } from 'react' +import { useCallback } from 'react' const roomNameAtom = atom(getRoomNameFromUrl()) @@ -46,7 +46,7 @@ function setRoomNameInUrl(roomName: string) { function setRoomNameInTitle(roomName: string) { if (!!roomName) { - document.title = roomName + document.title = decodeURI(roomName) } } diff --git a/frontend/src/hooks/useWebSocketConnection.ts b/frontend/src/hooks/useWebSocketConnection.ts index 77aa0b1..33ba89c 100644 --- a/frontend/src/hooks/useWebSocketConnection.ts +++ b/frontend/src/hooks/useWebSocketConnection.ts @@ -1,19 +1,19 @@ -import { useCallback, useState } from 'react' +import { atom, useAtom } from 'jotai' +import { useCallback, useEffect } from 'react' import { WEBSOCKET_URL } from '../background/constants' import { UserInfo } from '../components/jitsi/types' -const createWebSocketConnection = (userInfo: UserInfo) => { - const webSocket = new WebSocket(WEBSOCKET_URL) - console.log('[Rooms] createWebSocketConnection') - webSocket.addEventListener('open', (_: Event) => webSocket.send(JSON.stringify(userInfo))) - return webSocket -} +const webSocket = new WebSocket(WEBSOCKET_URL) +const webSocketConnectionAtom = atom(webSocket) function useWebSocketConnection(userInfo: UserInfo) { console.log('[Rooms] useWebSocketConnection') - const [webSocketConnection] = useState(() => createWebSocketConnection(userInfo)) + const [webSocketConnection] = useAtom(webSocketConnectionAtom) + useEffect(() => { + sendMessageNowOrLater(webSocketConnection, JSON.stringify(userInfo)) + }, [webSocketConnection, userInfo]); const sendMessage = useCallback( (message: string) => { @@ -37,8 +37,19 @@ function useWebSocketConnection(userInfo: UserInfo) { ) const disconnect = useCallback(webSocketConnection.close, [webSocketConnection]) + const cleanUpOnMessage = useCallback((callbackToRemove: any) => { + console.log('[Rooms] cleanUpOnMessage') + webSocketConnection.removeEventListener('message', callbackToRemove) + }, [webSocketConnection]) - return { onMessage, sendMessage, disconnect } + return { onMessage, cleanUpOnMessage, sendMessage, disconnect } +} + +const sendMessageNowOrLater = (webSocket: WebSocket, message: string) => { + if (webSocket.readyState !== WebSocket.OPEN) { + return webSocket.addEventListener('open', (_: Event) => webSocket.send(message)) + } + webSocket.send(message) } export default useWebSocketConnection diff --git a/frontend/src/index.css b/frontend/src/index.css index a51a5c0..7b35201 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -51,7 +51,9 @@ button { background-color: #1a1a1a; cursor: pointer; transition: border-color 0.25s; + color: white; } + button:hover { border-color: #646cff; } @@ -60,19 +62,6 @@ button:focus-visible { outline: 4px auto -webkit-focus-ring-color; } -@media (prefers-color-scheme: light) { - :root { - color: #213547; - background-color: #ffffff; - } - a:hover { - color: #747bff; - } - button { - background-color: #f9f9f9; - } -} - #root { height: 100%; width: 100%; @@ -92,4 +81,5 @@ input { background-color: #2b2a33; border-color: #646cff; border-radius: 8px; + color: white; } diff --git a/frontend/yarn.lock b/frontend/yarn.lock index d389464..3153213 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -22,7 +22,7 @@ resolved "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.20.10.tgz" integrity sha512-sEnuDPpOJR/fcafHMjpcpGN5M2jbUGUHwmuWKM/YdPzeEDJg8bgmbcWQFUfE32MQjti1koACvoPVsDe8Uq+idg== -"@babel/core@^7.20.7": +"@babel/core@^7.0.0", "@babel/core@^7.0.0-0", "@babel/core@^7.20.7": version "7.20.12" resolved "https://registry.npmjs.org/@babel/core/-/core-7.20.12.tgz" integrity sha512-XsMfHovsUYHFMdrIHkZphTN/2Hzzi78R08NuHfDBehym2VsPDL6Zn/JAD/JQdnRvbSsbQc4mVaU1m6JgtTEElg== @@ -209,122 +209,17 @@ "@babel/helper-validator-identifier" "^7.19.1" to-fast-properties "^2.0.0" -"@esbuild/android-arm64@0.16.15": - version "0.16.15" - resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.16.15.tgz#d58b9efe279b553b377395318d21e360058b3622" - integrity sha512-OdbkUv7468dSsgoFtHIwTaYAuI5lDEv/v+dlfGBUbVa2xSDIIuSOHXawynw5N9+5lygo/JdXa5/sgGjiEU18gQ== - -"@esbuild/android-arm@0.16.15": - version "0.16.15" - resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.16.15.tgz#6a8ad3016fd9c89bb419bd21605fba242c051809" - integrity sha512-JsJtmadyWcR+DEtHLixM7bAQsfi1s0Xotv9kVOoXbCLyhKPOHvMEyh3kJBuTbCPSE4c2jQkQVmarwc9Mg9k3bA== - -"@esbuild/android-x64@0.16.15": - version "0.16.15" - resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.16.15.tgz#94b0589b6bec5eaf7e8ea2fe6368427899676f21" - integrity sha512-dPUOBiNNWAm+/bxoA75o7R7qqqfcEzXaYlb5uJk2xGHmUMNKSAnDCtRYLgx9/wfE4sXyn8H948OrDyUAHhPOuA== - -"@esbuild/darwin-arm64@0.16.15": - version "0.16.15" - resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.16.15.tgz#525e5603a82837a1e7c8265d3b14433aa869e9b6" - integrity sha512-AksarYV85Hxgwh5/zb6qGl4sYWxIXPQGBAZ+jUro1ZpINy3EWumK+/4DPOKUBPnsrOIvnNXy7Rq4mTeCsMQDNA== - -"@esbuild/darwin-x64@0.16.15": - version "0.16.15" - resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.16.15.tgz#38ec324a3653ade5acc5c190a7a27185caa6223e" - integrity sha512-qqrKJxoohceZGGP+sZ5yXkzW9ZiyFZJ1gWSEfuYdOWzBSL18Uy3w7s/IvnDYHo++/cxwqM0ch3HQVReSZy7/4Q== - -"@esbuild/freebsd-arm64@0.16.15": - version "0.16.15" - resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.16.15.tgz#05c19bf6e4e56387f6a56bd6933839e889146726" - integrity sha512-LBWaep6RvJm5KnsKkocdVEzuwnGMjz54fcRVZ9d3R7FSEWOtPBxMhuxeA1n98JVbCLMkTPFmKN6xSnfhnM9WXQ== - -"@esbuild/freebsd-x64@0.16.15": - version "0.16.15" - resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.16.15.tgz#68855666ecf1616e2a927154148d4409cd8bc55b" - integrity sha512-LE8mKC6JPR04kPLRP9A6k7ZmG0k2aWF4ru79Sde6UeWCo7yDby5f48uJNFQ2pZqzUUkLrHL8xNdIHerJeZjHXg== - -"@esbuild/linux-arm64@0.16.15": - version "0.16.15" - resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.16.15.tgz#93e8630d19f3a25d31c6467e2b136438391a3ca9" - integrity sha512-mRYpuQGbzY+XLczy3Sk7fMJ3DRKLGDIuvLKkkUkyecDGQMmil6K/xVKP9IpKO7JtNH477qAiMjjX7jfKae8t4g== - -"@esbuild/linux-arm@0.16.15": - version "0.16.15" - resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.16.15.tgz#9343c9d0e18d15ca5b4e293154e4beae2598b5db" - integrity sha512-+1sGlqtMJTOnJUXwLUGnDhPaGRKqxT0UONtYacS+EjdDOrSgpQ/1gUXlnze45Z/BogwYaswQM19Gu1YD1T19/w== - -"@esbuild/linux-ia32@0.16.15": - version "0.16.15" - resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.16.15.tgz#2c63615bb87cb2d080f3dc7dd0e3174b9b977233" - integrity sha512-puXVFvY4m8EB6/fzu3LdgjiNnEZ3gZMSR7NmKoQe51l3hyQalvTjab3Dt7aX4qGf+8Pj7dsCOBNzNzkSlr/4Aw== - -"@esbuild/linux-loong64@0.16.15": - version "0.16.15" - resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.16.15.tgz#85a245672709ce60895baf93b1ad3fb4b3cdab4f" - integrity sha512-ATMGb3eg8T6ZTGZFldlGeFEcevBiVq6SBHvRAO04HMfUjZWneZ/U+JJb3YzlNZxuscJ4Tmzq+JrYxlk7ro4dRg== - -"@esbuild/linux-mips64el@0.16.15": - version "0.16.15" - resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.16.15.tgz#41e325ccd6432f952c674d763d9e5acc25a00267" - integrity sha512-3SEA4L82OnoSATW+Ve8rPgLaKjC8WMt8fnx7De9kvi/NcVbkj8W+J7qnu/tK2P9pUPQP7Au/0sjPEqZtFeyKQQ== - -"@esbuild/linux-ppc64@0.16.15": - version "0.16.15" - resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.16.15.tgz#51c7ed8fec6f9860716cdb1bb86835bdb9206108" - integrity sha512-8PgbeX+N6vmqeySzyxO0NyDOltCEW13OS5jUHTvCHmCgf4kNXZtAWJ+zEfJxjRGYhVezQ1FdIm7WfN1R27uOyg== - -"@esbuild/linux-riscv64@0.16.15": - version "0.16.15" - resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.16.15.tgz#44cb5ad5318d72f52378fa666b2010551c67f333" - integrity sha512-U+coqH+89vbPVoU30no1Fllrn6gvEeO5tfEArBhjYZ+dQ3Gv7ciQXYf5nrT1QdlIFwEjH4Is1U1iiaGWW+tGpQ== - -"@esbuild/linux-s390x@0.16.15": - version "0.16.15" - resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.16.15.tgz#69572a26c2120ddd446d8207d30ae8f94a801a72" - integrity sha512-M0nKLFMdyFGBoitxG42kq6Xap0CPeDC6gfF9lg7ZejzGF6kqYUGT+pQGl2QCQoxJBeat/LzTma1hG8C3dq2ocg== - "@esbuild/linux-x64@0.16.15": version "0.16.15" resolved "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.16.15.tgz" integrity sha512-t7/fOXBUKfigvhJLGKZ9TPHHgqNgpIpYaAbcXQk1X+fPeUG7x0tpAbXJ2wST9F/gJ02+CLETPMnhG7Tra2wqsQ== -"@esbuild/netbsd-x64@0.16.15": - version "0.16.15" - resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.16.15.tgz#cba5674608c197bee9d25451ae458ab76a770a45" - integrity sha512-0k0Nxi6DOJmTnLtKD/0rlyqOPpcqONXY53vpkoAsue8CfyhNPWtwzba1ICFNCfCY1dqL3Ho/xEzujJhmdXq1rg== - -"@esbuild/openbsd-x64@0.16.15": - version "0.16.15" - resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.16.15.tgz#4ea4394d5b9c67bac6dcd1e527b47c64990d7d92" - integrity sha512-3SkckazfIbdSjsGpuIYT3d6n2Hx0tck3MS1yVsbahhWiLvdy4QozTpvlbjqO3GmvtvhxY4qdyhFOO2wiZKeTAQ== - -"@esbuild/sunos-x64@0.16.15": - version "0.16.15" - resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.16.15.tgz#91174f7058dfc6cfafdf2251330f6767506db7a7" - integrity sha512-8PNvBC+O8X5EnyIGqE8St2bOjjrXMR17NOLenIrzolvwWnJXvwPo0tE/ahOeiAJmTOS/eAcN8b4LAZcn17Uj7w== - -"@esbuild/win32-arm64@0.16.15": - version "0.16.15" - resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.16.15.tgz#3fa7189ec92d1de87563ab9e73b3e0a4adbfd203" - integrity sha512-YPaSgm/mm7kNcATB53OxVGVfn6rDNbImTn330ZlF3hKej1e9ktCaljGjn2vH08z2dlHEf3kdt57tNjE6zs8SzA== - -"@esbuild/win32-ia32@0.16.15": - version "0.16.15" - resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.16.15.tgz#a114e4878e74fa6b5453cb407be4f2a28b72809d" - integrity sha512-0movUXbSNrTeNf5ZXT0avklEvlJD0hNGZsrrXHfsp9z4tK5xC+apCqmUEZeE9mqrb84Z8XbgGr/MS9LqafTP2A== - -"@esbuild/win32-x64@0.16.15": - version "0.16.15" - resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.16.15.tgz#e28277cdbc1c9cde2b982c814d05f44d4b1f0580" - integrity sha512-27h5GCcbfomVAqAnMJWvR1LqEY0dFqIq4vTe5nY3becnZNu0SX8F0+gTk3JPvgWQHzaGc6VkPzlOiMkdSUunUA== - "@fortawesome/fontawesome-common-types@6.4.0": version "6.4.0" resolved "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.4.0.tgz" integrity sha512-HNii132xfomg5QVZw0HwXXpN22s7VBHQBv9CeOu9tfJnhsWQNd2lmTNi8CSrnw5B+5YOmzu1UoPAyxaXsJ6RgQ== -"@fortawesome/fontawesome-svg-core@^6.4.0": +"@fortawesome/fontawesome-svg-core@^6.4.0", "@fortawesome/fontawesome-svg-core@~1 || ~6": version "6.4.0" resolved "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.4.0.tgz" integrity sha512-Bertv8xOiVELz5raB2FlXDPKt+m94MQ3JgDfsVbrqNpLU9+UE2E18GKjLKw+d3XbeYPqg1pzyQKGsrzbw+pPaw== @@ -377,7 +272,7 @@ resolved "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz" integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw== -"@jridgewell/sourcemap-codec@1.4.14", "@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.13": +"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.13", "@jridgewell/sourcemap-codec@1.4.14": version "1.4.14" resolved "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz" integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw== @@ -434,7 +329,7 @@ ansi-styles@^3.2.1: dependencies: color-convert "^1.9.0" -browserslist@^4.21.3: +browserslist@^4.21.3, "browserslist@>= 4.21.0": version "4.21.4" resolved "https://registry.npmjs.org/browserslist/-/browserslist-4.21.4.tgz" integrity sha512-CBHJJdDmgjl3daYjN5Cp5kbTf1mUhZoS+beLklHIvkOWscs83YAhLlF3Wsh/lciQYAcbBJgTOD44VtG31ZM4Hw== @@ -530,11 +425,6 @@ escape-string-regexp@^1.0.5: resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz" integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== -fsevents@~2.3.2: - 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.npmjs.org/function-bind/-/function-bind-1.1.1.tgz" @@ -663,7 +553,7 @@ prop-types@^15.8.1: object-assign "^4.1.1" react-is "^16.13.1" -react-dom@^18.2.0: +react-dom@^18.2.0, "react-dom@16 || 17 || 18": version "18.2.0" resolved "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz" integrity sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g== @@ -681,7 +571,7 @@ react-refresh@^0.14.0: resolved "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.0.tgz" integrity sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ== -react@^18.2.0: +react@^18.2.0, react@>=16.3, react@>=17.0.0, "react@16 || 17 || 18": version "18.2.0" resolved "https://registry.npmjs.org/react/-/react-18.2.0.tgz" integrity sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ== diff --git a/nixos.nix b/nixos.nix new file mode 100644 index 0000000..a9751b5 --- /dev/null +++ b/nixos.nix @@ -0,0 +1,68 @@ +{ + lib, + pkgs, + config, + ... +}: +with lib; +let + cfg = config.services.jitsi-rooms; + backendPort = 8081; + wsPort = 9160; + address = "127.0.0.1"; + backend = (pkgs.callPackage ./backend/default.nix { }); + frontend = (pkgs.callPackage ./frontend/default.nix { }); + prodsodyPackage = (pkgs.callPackage ./prodsody/default.nix { }); + notifyScript = "/run/current-system/sw/bin/send-signal-jitsi-notify"; +in +{ + options.services.jitsi-rooms = { + enable = mkEnableOption "jitsi-rooms service"; + nginxHostname = mkOption { + type = types.str; + default = "treffen.filefighter.de"; + }; + }; + + config = mkIf cfg.enable { + systemd.services.jitsi-rooms = { + description = "jitsi-rooms"; + + wantedBy = [ "multi-user.target" ]; + after = [ "network.target" ]; + + serviceConfig = { + ExecStart = "${backend}/bin/jitsi-rooms-exe -n ${notifyScript}"; + DynamicUser = true; + User = "jitsi-rooms-backend"; + }; + }; + + users.users.jitsi-rooms-backend = { + isSystemUser = true; + group = "jitsi-rooms-backend"; + extraGroups = [ "dbus" ]; + }; + users.groups.jitsi-rooms-backend = { }; + + services.nginx.virtualHosts.${cfg.nginxHostname} = { + forceSSL = true; + enableACME = true; + + locations = { + "/" = { + root = frontend; + tryFiles = "$uri $uri/ /index.html"; + }; + "/ws" = { + proxyPass = "http://${address}:${toString wsPort}"; + }; + }; + }; + services.prosody = { + extraPluginPaths = [ "${prodsodyPackage}/share" ]; + extraModules = [ "jitsi_rooms" ]; + }; + }; + +} diff --git a/prodsody/Readme.md b/prodsody/Readme.md index 65ebf87..299f8dc 100644 --- a/prodsody/Readme.md +++ b/prodsody/Readme.md @@ -10,7 +10,7 @@ ### Get started - run `./run_dev.sh` -- open [https://localhost:8443/](https://localhost:8443/) +- open [https://localhost:8443/](https://localhost:8443/) (accept the risk about self-signed certs) - make changes to mod_jitsi_rooms.lua and save the file - prosody will be restarted - join a room in jitsi @@ -19,3 +19,8 @@ ## Deploying - all required changes to the [jitsi meet docker setup](https://github.com/jitsi/docker-jitsi-meet) are marked with a "custom" comment + +## ? + +Maybe we could just use https://github.com/jitsi-contrib/prosody-plugins/tree/dd1cb9098f5fbd281f2b62c1e2cf30e220ff14b0/event_sync +=> This does not include the whole participants list diff --git a/prodsody/default.nix b/prodsody/default.nix new file mode 100644 index 0000000..1f15f09 --- /dev/null +++ b/prodsody/default.nix @@ -0,0 +1,23 @@ +{ + pkgs ? import { }, +}: + +pkgs.stdenv.mkDerivation { + pname = "jitsi-room-prosody"; + version = "1.0.0"; + + src = pkgs.lib.cleanSource ./.; + dontBuild = true; + + installPhase = '' + runHook preInstall + mkdir -p $out/share + mv *.lua $out/share/ + runHook postInstall + ''; + + meta = { + name = "Jitsi Rooms Prosody Plugin"; + description = "Prosody configuration for Jitsi Rooms"; + }; +} diff --git a/prodsody/docker-compose.yml b/prodsody/docker-compose.yml index 08feefc..0c4a04b 100644 --- a/prodsody/docker-compose.yml +++ b/prodsody/docker-compose.yml @@ -347,7 +347,7 @@ services: meet.jitsi: jitsi-rooms: - image: jitsi-rooms:67y5d9y2zbi7wkqm2jpcjj1k2614qwx2 + image: jitsi-rooms restart: ${RESTART_POLICY:-unless-stopped} ports: - '9160:9160' diff --git a/prodsody/fix-permissions.sh b/prodsody/fix-permissions.sh old mode 100644 new mode 100755 diff --git a/prodsody/jitsi-meet-cfg/prosody/prosody-plugins-custom/mod_jitsi_rooms.lua b/prodsody/jitsi-meet-cfg/prosody/prosody-plugins-custom/mod_jitsi_rooms.lua index dde9499..cfe138b 100644 --- a/prodsody/jitsi-meet-cfg/prosody/prosody-plugins-custom/mod_jitsi_rooms.lua +++ b/prodsody/jitsi-meet-cfg/prosody/prosody-plugins-custom/mod_jitsi_rooms.lua @@ -4,6 +4,7 @@ local json = require "util.json"; local array = require "util.array"; local iterators = require "util.iterators"; local jid = require "util.jid"; +local http = require "util.http"; local async_handler_wrapper = module:require "util".async_handler_wrapper; local get_room_from_jid = module:require "util".get_room_from_jid; @@ -12,9 +13,9 @@ local domain_name = "meet.jitsi" function get_participants_for_room(room_name) - local room_address = jid.join(room_name, muc_domain_prefix .. "." .. domain_name); - local room = get_room_from_jid(room_address); + local decoded_room_address = http.urldecode(room_address); + local room = get_room_from_jid(decoded_room_address); local occupants_json = array(); if room then local occupants = room._occupants; @@ -47,7 +48,6 @@ function get_participants_for_room(room_name) end function get_all_rooms_with_participants() - local sessions = prosody.full_sessions; local someTable = (it.join(it.keys(sessions))); @@ -74,7 +74,7 @@ end -- @return GET response, containing a json with participants details function handle_get_sessions(event) handle_room_event() - return { status_code = 200; body = get_all_rooms_with_participants() }; + return { status_code = 200, body = get_all_rooms_with_participants() }; end function module.load() @@ -86,9 +86,9 @@ function module.load() module:log("info", "Hello! You can reach me at: %s", module:http_url()); module:provides("http", { route = { - ["GET"] = function(event) return async_handler_wrapper(event, handle_get_sessions) end; + ["GET"] = function(event) return async_handler_wrapper(event, handle_get_sessions) end, - }; + }, }); end @@ -125,7 +125,6 @@ end --- Checks if event is triggered by healthchecks or focus user. function is_system_event(event) - if event == nil or event.room == nil or event.room.jid == nil then return true; end @@ -147,12 +146,11 @@ function handle_room_event(event) return; end - async_http_request("http://192.168.2.116:8081/roomdata", + async_http_request("http://jitsi-rooms:8081/roomdata", { method = "POST", body = get_all_rooms_with_participants() }) - end --Helper function to wait till a component is loaded before running the given callback diff --git a/prodsody/mod_jitsi_rooms.lua b/prodsody/mod_jitsi_rooms.lua index dde9499..470f7f7 100644 --- a/prodsody/mod_jitsi_rooms.lua +++ b/prodsody/mod_jitsi_rooms.lua @@ -1,95 +1,94 @@ ---@diagnostic disable: deprecated -local it = require "util.iterators"; -local json = require "util.json"; -local array = require "util.array"; -local iterators = require "util.iterators"; -local jid = require "util.jid"; - -local async_handler_wrapper = module:require "util".async_handler_wrapper; -local get_room_from_jid = module:require "util".get_room_from_jid; -local muc_domain_prefix = module:get_option_string("muc_mapper_domain_prefix", "conference"); -local domain_name = "meet.jitsi" +local it = require("util.iterators") +local json = require("util.json") +local array = require("util.array") +local iterators = require("util.iterators") +local jid = require("util.jid") +local http = require("util.http") +local async_handler_wrapper = module:require("util").async_handler_wrapper +local get_room_from_jid = module:require("util").get_room_from_jid +local muc_domain_prefix = module:get_option_string("muc_mapper_domain_prefix", "conference") +local domain_name = "meet.filefighter.de" function get_participants_for_room(room_name) - - local room_address = jid.join(room_name, muc_domain_prefix .. "." .. domain_name); - local room = get_room_from_jid(room_address); - local occupants_json = array(); - if room then - local occupants = room._occupants; - if occupants then - participant_count = iterators.count(room:each_occupant()); - for _, occupant in room:each_occupant() do - -- filter focus as we keep it as hidden participant - if string.sub(occupant.nick, -string.len("/focus")) ~= "/focus" then - for _, pr in occupant:each_session() do - local nick = pr:get_child_text("nick", "http://jabber.org/protocol/nick") or ""; - local email = pr:get_child_text("email") or ""; - local avatarURL = pr:get_child_text("avatarURL") or "" -- does not work :( - occupants_json:push({ - jid = tostring(occupant.nick), - email = tostring(email), - displayName = tostring(nick), - avatarURL = tostring(avatarURL) - }); - end - end - end - end - log("debug", - "there are %s occupants in room", tostring(participant_count)); - return occupants_json - else - log("debug", "no such room exists"); - end - return occupants_json + local room_address = jid.join(room_name, muc_domain_prefix .. "." .. domain_name) + local decoded_room_address = http.urldecode(room_address) + local room = get_room_from_jid(decoded_room_address) + local occupants_json = array() + if room then + local occupants = room._occupants + if occupants then + participant_count = iterators.count(room:each_occupant()) + for _, occupant in room:each_occupant() do + -- filter focus as we keep it as hidden participant + if string.sub(occupant.nick, -string.len("/focus")) ~= "/focus" then + for _, pr in occupant:each_session() do + local nick = pr:get_child_text("nick", "http://jabber.org/protocol/nick") or "" + local email = pr:get_child_text("email") or "" + local avatarURL = pr:get_child_text("avatarURL") or "" -- does not work :( + occupants_json:push({ + jid = tostring(occupant.nick), + email = tostring(email), + displayName = tostring(nick), + avatarURL = tostring(avatarURL), + }) + end + end + end + end + log("debug", "there are %s occupants in room", tostring(participant_count)) + return occupants_json + else + log("debug", "no such room exists") + end + return occupants_json end function get_all_rooms_with_participants() + local sessions = prosody.full_sessions - local sessions = prosody.full_sessions; + local someTable = (it.join(it.keys(sessions))) + local actual_rooms = someTable[1][2] - local someTable = (it.join(it.keys(sessions))); - local actual_rooms = someTable[1][2] + local roomNames = {} + for _, v in pairs(actual_rooms) do + local roomName = v["jitsi_web_query_room"] + if roomName ~= nil then + roomNames[roomName] = true + end + end - local roomNames = {} - for _, v in pairs(actual_rooms) do - local roomName = v["jitsi_web_query_room"] - if (roomName ~= nil) then - roomNames[roomName] = true; - end - end - - local rooms_with_participants = array(); - for room_name, _ in pairs(roomNames) do - local room = { roomName = room_name, participants = get_participants_for_room(room_name) } - rooms_with_participants:push(room) - end - return json.encode(rooms_with_participants) + local rooms_with_participants = array() + for room_name, _ in pairs(roomNames) do + local room = { roomName = room_name, participants = get_participants_for_room(room_name) } + rooms_with_participants:push(room) + end + return json.encode(rooms_with_participants) end --- Handles request for retrieving the room participants details -- @param event the http event, holds the request query -- @return GET response, containing a json with participants details function handle_get_sessions(event) - handle_room_event() - return { status_code = 200; body = get_all_rooms_with_participants() }; + handle_room_event() + return { status_code = 200, body = get_all_rooms_with_participants() } end function module.load() - -- Ensure that mod_http is loaded: - -- - module:depends("http"); + -- Ensure that mod_http is loaded: + -- + module:depends("http") - -- Now publish our HTTP 'app': - module:log("info", "Hello! You can reach me at: %s", module:http_url()); - module:provides("http", { - route = { - ["GET"] = function(event) return async_handler_wrapper(event, handle_get_sessions) end; - - }; - }); + -- Now publish our HTTP 'app': + module:log("info", "Hello! You can reach me at: %s", module:http_url()) + module:provides("http", { + route = { + ["GET"] = function(event) + return async_handler_wrapper(event, handle_get_sessions) + end, + }, + }) end -------- event stuff @@ -103,106 +102,104 @@ end -- -- -local http = require "net.http"; -local is_healthcheck_room = module:require "util".is_healthcheck_room; +local http = require("net.http") +local is_healthcheck_room = module:require("util").is_healthcheck_room --- Start non-blocking HTTP call -- @param url URL to call -- @param options options table as expected by net.http where we provide optional headers, body or method. local function async_http_request(url, options) - local completed = false; - local timed_out = false; + local completed = false + local timed_out = false - local function cb_(response_body, response_code) - if not timed_out then -- request completed before timeout - completed = true; + local function cb_(response_body, response_code) + if not timed_out then -- request completed before timeout + completed = true - module:log("debug", "%s %s returned code %s", options.method, url, response_code); - end - end + module:log("debug", "%s %s returned code %s", options.method, url, response_code) + end + end - http.request(url, options, cb_); + http.request(url, options, cb_) end --- Checks if event is triggered by healthchecks or focus user. function is_system_event(event) + if event == nil or event.room == nil or event.room.jid == nil then + return true + end - if event == nil or event.room == nil or event.room.jid == nil then - return true; - end + if is_healthcheck_room(event.room.jid) then + return true + end - if is_healthcheck_room(event.room.jid) then - return true; - end + if event.occupant and jid.node(event.occupant.jid) == "focus" then + return true + end - if event.occupant and jid.node(event.occupant.jid) == "focus" then - return true; - end - - return false; + return false end function handle_room_event(event) - module:log('info', "hallo irgendwas ist passiert"); - if is_system_event(event) then - return; - end - - async_http_request("http://192.168.2.116:8081/roomdata", - { - method = "POST", - body = get_all_rooms_with_participants() - }) + module:log("info", "hallo irgendwas ist passiert") + if is_system_event(event) then + return + end + async_http_request("http://localhost:8081/roomdata", { + method = "POST", + body = get_all_rooms_with_participants(), + }) end --Helper function to wait till a component is loaded before running the given callback function run_when_component_loaded(component_host_name, callback) - local function trigger_callback() - module:log('info', 'Component loaded %s', component_host_name); - callback(module:context(component_host_name), component_host_name); - end + local function trigger_callback() + module:log("info", "Component loaded %s", component_host_name) + callback(module:context(component_host_name), component_host_name) + end - if prosody.hosts[component_host_name] == nil then - module:log('debug', 'Host %s not yet loaded. Will trigger when it is loaded.', component_host_name); - prosody.events.add_handler('host-activated', function(host) - if host == component_host_name then - trigger_callback(); - end - end); - else - trigger_callback(); - end + if prosody.hosts[component_host_name] == nil then + module:log("debug", "Host %s not yet loaded. Will trigger when it is loaded.", component_host_name) + prosody.events.add_handler("host-activated", function(host) + if host == component_host_name then + trigger_callback() + end + end) + else + trigger_callback() + end end -- Helper function to wait till a component's muc module is loaded before running the given callback function run_when_muc_module_loaded(component_host_module, component_host_name, callback) - local function trigger_callback() - module:log('info', 'MUC module loaded for %s', component_host_name); - callback(prosody.hosts[component_host_name].modules.muc, component_host_module); - end + local function trigger_callback() + module:log("info", "MUC module loaded for %s", component_host_name) + callback(prosody.hosts[component_host_name].modules.muc, component_host_module) + end - if prosody.hosts[component_host_name].modules.muc == nil then - module:log('debug', 'MUC module for %s not yet loaded. Will trigger when it is loaded.', component_host_name); - prosody.hosts[component_host_name].events.add_handler('module-loaded', function(event) - if (event.module == 'muc') then - trigger_callback(); - end - end); - else - trigger_callback() - end + if prosody.hosts[component_host_name].modules.muc == nil then + module:log("debug", "MUC module for %s not yet loaded. Will trigger when it is loaded.", component_host_name) + prosody.hosts[component_host_name].events.add_handler("module-loaded", function(event) + if event.module == "muc" then + trigger_callback() + end + end) + else + trigger_callback() + end end local main_muc_component_host = muc_domain_prefix .. "." .. domain_name +-- local main_muc_component_host = module:get_option_string("muc_component") -- Handle events on main muc module run_when_component_loaded(main_muc_component_host, function(host_module, host_name) - run_when_muc_module_loaded(host_module, host_name, function(main_muc, main_module) - main_muc_service = main_muc; -- so it can be accessed from breakout muc event handlers + run_when_muc_module_loaded(host_module, host_name, function(main_muc, main_module) + main_muc_service = main_muc -- so it can be accessed from breakout muc event handlers - -- the following must run after speakerstats (priority -1) - main_module:hook("muc-room-created", handle_room_event, -3); -- must run after handle_main_room_created - main_module:hook("muc-occupant-joined", handle_room_event, -2); - main_module:hook("muc-occupant-left", handle_room_event, -2); - main_module:hook("muc-room-destroyed", handle_room_event, -2); - end); -end); + -- the following must run after speakerstats (priority -1) + main_module:hook("muc-room-created", handle_room_event, -3) -- must run after handle_main_room_created + main_module:hook("muc-occupant-joined", handle_room_event, -2) + main_module:hook("muc-occupant-left", handle_room_event, -2) -- see also https://issues.prosody.im/1743 + main_module:hook("muc-room-destroyed", handle_room_event, -2) + end) +end) diff --git a/prodsody/result b/prodsody/result new file mode 120000 index 0000000..437c8d3 --- /dev/null +++ b/prodsody/result @@ -0,0 +1 @@ +/nix/store/88ak9a0jwp38h95kb3hpd3965pmbhnjx-jitsi-room-prosody-1.0.0 \ No newline at end of file