Compare commits
24 Commits
refactor/f
...
main
Author | SHA1 | Date |
---|---|---|
qvalentin | 32685964c2 | |
qvalentin | 4b3f7fb19d | |
qvalentin | 2159e273fa | |
qvalentin | 8c82cd81bd | |
qvalentin | 07b883636d | |
qvalentin | bc88f79fef | |
qvalentin | b8c7b4b954 | |
qvalentin | c77e006ce4 | |
qvalentin | 1f5e5c944c | |
qvalentin | 66d70cbfe4 | |
qvalentin | bf48a8d4cc | |
nf3lix | 5d70be142d | |
qvalentin | e21ca799d6 | |
qvalentin | 77a00e68e9 | |
qvalentin | a5d769e9b5 | |
qvalentin | cc1808c9ec | |
qvalentin | f3e6699ca5 | |
qvalentin | eedc21fb8e | |
qvalentin | 6e43e10d1f | |
qvalentin | 2ea2aa4abb | |
qvalentin | 69aa82837c | |
qvalentin | 1e318817a4 | |
qvalentin | 03985cede2 | |
qvalentin | d07a5f61d7 |
|
@ -18,6 +18,7 @@ trigger:
|
||||||
event:
|
event:
|
||||||
include:
|
include:
|
||||||
- tag
|
- tag
|
||||||
|
- push
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
- name: drone-shared
|
- name: drone-shared
|
||||||
|
|
|
@ -1,7 +1,11 @@
|
||||||
module Main (main) where
|
module Main (main) where
|
||||||
|
|
||||||
import ClassyPrelude
|
import ClassyPrelude
|
||||||
|
import GHC.IO.Encoding (setLocaleEncoding)
|
||||||
|
import GHC.IO.Encoding.UTF8 (utf8)
|
||||||
import Lib (runBothServers)
|
import Lib (runBothServers)
|
||||||
|
|
||||||
main :: IO ()
|
main :: IO ()
|
||||||
main = runBothServers
|
main = do
|
||||||
|
setLocaleEncoding utf8
|
||||||
|
runBothServers
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
, text
|
, text
|
||||||
, uuid
|
, uuid
|
||||||
, wai
|
, wai
|
||||||
|
, wai-extra
|
||||||
, warp
|
, warp
|
||||||
, websockets
|
, websockets
|
||||||
}:
|
}:
|
||||||
|
@ -30,6 +31,7 @@ mkDerivation {
|
||||||
text
|
text
|
||||||
uuid
|
uuid
|
||||||
wai
|
wai
|
||||||
|
wai-extra
|
||||||
warp
|
warp
|
||||||
websockets
|
websockets
|
||||||
];
|
];
|
||||||
|
@ -44,6 +46,7 @@ mkDerivation {
|
||||||
text
|
text
|
||||||
uuid
|
uuid
|
||||||
wai
|
wai
|
||||||
|
wai-extra
|
||||||
warp
|
warp
|
||||||
websockets
|
websockets
|
||||||
];
|
];
|
||||||
|
|
|
@ -30,5 +30,8 @@ pkgs.dockerTools.buildImage {
|
||||||
"9160/tcp" = { };
|
"9160/tcp" = { };
|
||||||
"8081/tcp" = { };
|
"8081/tcp" = { };
|
||||||
};
|
};
|
||||||
|
Env = [
|
||||||
|
"LANG=en_US.UTF-8"
|
||||||
|
];
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
cabal-version: 1.12
|
cabal-version: 1.12
|
||||||
|
|
||||||
-- This file has been generated from package.yaml by hpack version 0.35.0.
|
-- This file has been generated from package.yaml by hpack version 0.35.2.
|
||||||
--
|
--
|
||||||
-- see: https://github.com/sol/hpack
|
-- see: https://github.com/sol/hpack
|
||||||
|
|
||||||
|
@ -29,12 +29,13 @@ library
|
||||||
Lib
|
Lib
|
||||||
RoomDataHandler
|
RoomDataHandler
|
||||||
State.ConnectedClientsState
|
State.ConnectedClientsState
|
||||||
|
State.GenericTVarState
|
||||||
State.RoomDataState
|
State.RoomDataState
|
||||||
|
State.RoomsState
|
||||||
Types.AppTypes
|
Types.AppTypes
|
||||||
Types.ConnectionState
|
Types.ConnectionState
|
||||||
Types.Participant
|
Types.Participant
|
||||||
Types.RoomData
|
Types.RoomData
|
||||||
Types.RoomsState
|
|
||||||
Types.User
|
Types.User
|
||||||
Types.UsersData
|
Types.UsersData
|
||||||
Types.WebEnv
|
Types.WebEnv
|
||||||
|
@ -65,6 +66,7 @@ library
|
||||||
, time
|
, time
|
||||||
, uuid
|
, uuid
|
||||||
, wai
|
, wai
|
||||||
|
, wai-extra
|
||||||
, warp
|
, warp
|
||||||
, websockets
|
, websockets
|
||||||
default-language: Haskell2010
|
default-language: Haskell2010
|
||||||
|
@ -91,6 +93,7 @@ executable jitsi-rooms-exe
|
||||||
, time
|
, time
|
||||||
, uuid
|
, uuid
|
||||||
, wai
|
, wai
|
||||||
|
, wai-extra
|
||||||
, warp
|
, warp
|
||||||
, websockets
|
, websockets
|
||||||
default-language: Haskell2010
|
default-language: Haskell2010
|
||||||
|
|
|
@ -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/<owner>/<repo>/archive/<rev>.tar.gz"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,194 @@
|
||||||
|
# This file has been generated by Niv.
|
||||||
|
|
||||||
|
let
|
||||||
|
|
||||||
|
#
|
||||||
|
# The fetchers. fetch_<type> fetches specs of type <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 = <nixpkgs> == ./.;
|
||||||
|
in
|
||||||
|
if builtins.hasAttr "nixpkgs" sources
|
||||||
|
then sourcesNixpkgs
|
||||||
|
else if hasNixpkgsPath && ! hasThisAsNixpkgsPath then
|
||||||
|
import <nixpkgs> {}
|
||||||
|
else
|
||||||
|
abort
|
||||||
|
''
|
||||||
|
Please specify either <nixpkgs> (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); }
|
|
@ -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
|
||||||
|
];
|
||||||
|
}
|
|
@ -33,6 +33,7 @@ dependencies:
|
||||||
- lifted-base
|
- lifted-base
|
||||||
- mtl
|
- mtl
|
||||||
- time
|
- time
|
||||||
|
- wai-extra
|
||||||
|
|
||||||
ghc-options:
|
ghc-options:
|
||||||
- -Wall
|
- -Wall
|
||||||
|
|
|
@ -8,9 +8,9 @@ where
|
||||||
import ClassyPrelude
|
import ClassyPrelude
|
||||||
import Data.Aeson (encode)
|
import Data.Aeson (encode)
|
||||||
import Network.WebSockets qualified as WS
|
import Network.WebSockets qualified as WS
|
||||||
import State.ConnectedClientsState (MonadConnectedClientsRead (getConnctedClients))
|
import State.ConnectedClientsState (ConnectedClients, MonadConnectedClientsRead (getConnctedClients))
|
||||||
import State.RoomDataState (MonadRoomDataStateRead (getRoomDataState))
|
import State.RoomDataState (MonadRoomDataStateRead (getRoomDataState))
|
||||||
import Types.ConnectionState (Client (..), ConnectedClients)
|
import Types.ConnectionState (Client (..))
|
||||||
import Types.User (User, clientToUser)
|
import Types.User (User, clientToUser)
|
||||||
import Types.UsersData (UsersData (..))
|
import Types.UsersData (UsersData (..))
|
||||||
|
|
||||||
|
@ -41,8 +41,8 @@ broadCastToClientsGeneric ::
|
||||||
Text ->
|
Text ->
|
||||||
m ()
|
m ()
|
||||||
broadCastToClientsGeneric message = do
|
broadCastToClientsGeneric message = do
|
||||||
state <- getConnctedClients
|
connectedClients <- getConnctedClients
|
||||||
liftIO $ broadcast message state
|
liftIO (broadcast message connectedClients)
|
||||||
|
|
||||||
broadcast :: Text -> ConnectedClients -> IO ()
|
broadcast :: Text -> ConnectedClients -> IO ()
|
||||||
broadcast message clients = do
|
broadcast message clients = do
|
||||||
|
|
|
@ -6,9 +6,9 @@ module Lib
|
||||||
where
|
where
|
||||||
|
|
||||||
import ClassyPrelude
|
import ClassyPrelude
|
||||||
|
import State.ConnectedClientsState (initConnectionsState)
|
||||||
|
import State.RoomsState (initRoomsState)
|
||||||
import Types.AppTypes
|
import Types.AppTypes
|
||||||
import Types.ConnectionState (initConnectionsState)
|
|
||||||
import Types.RoomsState (initRoomsState)
|
|
||||||
import WebServer (runWebServer)
|
import WebServer (runWebServer)
|
||||||
import WebSocket.Server (runWebSocketServer)
|
import WebSocket.Server (runWebSocketServer)
|
||||||
|
|
||||||
|
|
|
@ -16,16 +16,12 @@ import Network.Wai
|
||||||
consumeRequestBodyStrict,
|
consumeRequestBodyStrict,
|
||||||
responseLBS,
|
responseLBS,
|
||||||
)
|
)
|
||||||
import State.ConnectedClientsState (MonadConnectedClientsRead)
|
|
||||||
import State.RoomDataState
|
import State.RoomDataState
|
||||||
( MonadRoomDataStateModify (setRoomDataState),
|
( MonadRoomDataStateModify (setRoomDataState),
|
||||||
MonadRoomDataStateRead,
|
MonadRoomDataStateRead,
|
||||||
)
|
)
|
||||||
import Types.AppTypes (HasConnectedClientState)
|
import State.RoomsState
|
||||||
import Types.RoomsState
|
( roomStateDiffers,
|
||||||
( HasRoomsState,
|
|
||||||
roomStateDiffers,
|
|
||||||
updateRoomState,
|
|
||||||
)
|
)
|
||||||
import Types.WebEnv
|
import Types.WebEnv
|
||||||
( HasWebEnv (getRequest),
|
( HasWebEnv (getRequest),
|
||||||
|
@ -44,7 +40,7 @@ roomDataHandler ::
|
||||||
m ResponseReceived
|
m ResponseReceived
|
||||||
roomDataHandler = do
|
roomDataHandler = do
|
||||||
newRoomData <- parseBodyOrBadRequest
|
newRoomData <- parseBodyOrBadRequest
|
||||||
liftIO $ putStrLn "Got triggered form prosody"
|
liftIO $ putStrLn "Got triggered from prosody"
|
||||||
whenM (roomStateDiffers newRoomData) $ do
|
whenM (roomStateDiffers newRoomData) $ do
|
||||||
setRoomDataState newRoomData
|
setRoomDataState newRoomData
|
||||||
broadcastUserData
|
broadcastUserData
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
module State.ConnectedClientsState
|
module State.ConnectedClientsState
|
||||||
( MonadConnectedClientsModify (..),
|
( MonadConnectedClientsModify (..),
|
||||||
MonadConnectedClientsRead (..),
|
MonadConnectedClientsRead (..),
|
||||||
|
ConnectedClients,
|
||||||
|
ConnectedClientsState,
|
||||||
|
HasConnectedClientState (..),
|
||||||
|
initConnectionsState,
|
||||||
addWSClientGeneric,
|
addWSClientGeneric,
|
||||||
updateWSClientGeneric,
|
updateWSClientGeneric,
|
||||||
removeWSClientGeneric,
|
removeWSClientGeneric,
|
||||||
|
@ -10,14 +14,24 @@ where
|
||||||
|
|
||||||
import ClassyPrelude
|
import ClassyPrelude
|
||||||
import Data.UUID
|
import Data.UUID
|
||||||
import Types.AppTypes
|
|
||||||
import Types.ConnectionState
|
import Types.ConnectionState
|
||||||
|
|
||||||
|
type ConnectedClientsState = TVar ConnectedClients
|
||||||
|
|
||||||
|
initConnectionsState :: IO ConnectedClientsState
|
||||||
|
initConnectionsState = newTVarIO newConnectedClients
|
||||||
|
|
||||||
|
newConnectedClients :: ConnectedClients
|
||||||
|
newConnectedClients = []
|
||||||
|
|
||||||
class Monad m => MonadConnectedClientsModify m where
|
class Monad m => MonadConnectedClientsModify m where
|
||||||
addWSClient :: Client -> m ()
|
addWSClient :: Client -> m ()
|
||||||
removeWSClient :: UUID -> m ()
|
removeWSClient :: UUID -> m ()
|
||||||
updateWSClient :: UUID -> (Client -> Client) -> m ()
|
updateWSClient :: UUID -> (Client -> Client) -> m ()
|
||||||
|
|
||||||
|
class HasConnectedClientState a where
|
||||||
|
getConnectedClientState :: a -> ConnectedClientsState
|
||||||
|
|
||||||
addWSClientGeneric ::
|
addWSClientGeneric ::
|
||||||
( HasConnectedClientState env,
|
( HasConnectedClientState env,
|
||||||
MonadReader env m,
|
MonadReader env m,
|
||||||
|
@ -72,9 +86,7 @@ modifyState ::
|
||||||
m ()
|
m ()
|
||||||
modifyState modifyFunc = do
|
modifyState modifyFunc = do
|
||||||
state <- getConnectedClientState <$> ask
|
state <- getConnectedClientState <$> ask
|
||||||
modifyMVar_ state $ \s ->
|
atomically $ modifyTVar state modifyFunc
|
||||||
let s' = modifyFunc s
|
|
||||||
in return s'
|
|
||||||
|
|
||||||
class Monad m => MonadConnectedClientsRead m where
|
class Monad m => MonadConnectedClientsRead m where
|
||||||
getConnctedClients :: m ConnectedClients
|
getConnctedClients :: m ConnectedClients
|
||||||
|
@ -86,4 +98,4 @@ getConnctedClientsGeneric ::
|
||||||
) =>
|
) =>
|
||||||
m ConnectedClients
|
m ConnectedClients
|
||||||
getConnctedClientsGeneric = do
|
getConnctedClientsGeneric = do
|
||||||
ask >>= readMVar . getConnectedClientState
|
ask >>= readTVarIO . getConnectedClientState
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
module State.GenericTVarState (GenericTVarState, updateGenericTVarState, getGenericTVarState) where
|
||||||
|
|
||||||
|
import ClassyPrelude
|
||||||
|
|
||||||
|
type GenericTVarState a = TVar a
|
||||||
|
|
||||||
|
updateGenericTVarState :: (MonadIO m) => GenericTVarState a -> a -> m ()
|
||||||
|
updateGenericTVarState tv a = atomically $ writeTVar tv a
|
||||||
|
|
||||||
|
getGenericTVarState :: (MonadIO m) => GenericTVarState a -> m a
|
||||||
|
getGenericTVarState = readTVarIO
|
|
@ -1,4 +1,4 @@
|
||||||
module Types.RoomsState
|
module State.RoomsState
|
||||||
( RoomsState,
|
( RoomsState,
|
||||||
initRoomsState,
|
initRoomsState,
|
||||||
HasRoomsState (..),
|
HasRoomsState (..),
|
||||||
|
@ -9,13 +9,14 @@ module Types.RoomsState
|
||||||
where
|
where
|
||||||
|
|
||||||
import ClassyPrelude
|
import ClassyPrelude
|
||||||
|
import State.GenericTVarState
|
||||||
import State.RoomDataState (MonadRoomDataStateRead (getRoomDataState))
|
import State.RoomDataState (MonadRoomDataStateRead (getRoomDataState))
|
||||||
import Types.RoomData (RoomsData)
|
import Types.RoomData (RoomsData)
|
||||||
|
|
||||||
type RoomsState = MVar RoomsData
|
type RoomsState = GenericTVarState RoomsData
|
||||||
|
|
||||||
initRoomsState :: IO RoomsState
|
initRoomsState :: IO RoomsState
|
||||||
initRoomsState = newMVar []
|
initRoomsState = newTVarIO []
|
||||||
|
|
||||||
class HasRoomsState a where
|
class HasRoomsState a where
|
||||||
getRoomsState :: a -> RoomsState
|
getRoomsState :: a -> RoomsState
|
||||||
|
@ -29,8 +30,9 @@ updateRoomState ::
|
||||||
m ()
|
m ()
|
||||||
updateRoomState newData = do
|
updateRoomState newData = do
|
||||||
state <- getRoomsState <$> ask
|
state <- getRoomsState <$> ask
|
||||||
_ <- swapMVar state newData
|
liftIO $ putStrLn "Upating room state"
|
||||||
return ()
|
updateGenericTVarState state newData
|
||||||
|
liftIO $ putStrLn "Done Upating room state"
|
||||||
|
|
||||||
getRoomState ::
|
getRoomState ::
|
||||||
( HasRoomsState env,
|
( HasRoomsState env,
|
||||||
|
@ -40,7 +42,7 @@ getRoomState ::
|
||||||
m RoomsData
|
m RoomsData
|
||||||
getRoomState = do
|
getRoomState = do
|
||||||
state <- getRoomsState <$> ask
|
state <- getRoomsState <$> ask
|
||||||
readMVar state
|
getGenericTVarState state
|
||||||
|
|
||||||
roomStateDiffers ::
|
roomStateDiffers ::
|
||||||
( MonadRoomDataStateRead m
|
( MonadRoomDataStateRead m
|
|
@ -1,10 +1,10 @@
|
||||||
{-# LANGUAGE DerivingVia #-}
|
{-# LANGUAGE DerivingVia #-}
|
||||||
|
|
||||||
module Types.AppTypes (Env (..), App (..), getConnectedClientState, HasConnectedClientState, AppProfile (Prod, Dev)) where
|
module Types.AppTypes (Env (..), App (..), getConnectedClientState, AppProfile (Prod, Dev)) where
|
||||||
|
|
||||||
import ClassyPrelude
|
import ClassyPrelude
|
||||||
import Types.ConnectionState (ConnectedClientsState)
|
import State.ConnectedClientsState
|
||||||
import Types.RoomsState
|
import State.RoomsState
|
||||||
( HasRoomsState (getRoomsState),
|
( HasRoomsState (getRoomsState),
|
||||||
RoomsState,
|
RoomsState,
|
||||||
)
|
)
|
||||||
|
@ -17,9 +17,6 @@ data Env = Env
|
||||||
profile :: AppProfile
|
profile :: AppProfile
|
||||||
}
|
}
|
||||||
|
|
||||||
class HasConnectedClientState a where
|
|
||||||
getConnectedClientState :: a -> ConnectedClientsState
|
|
||||||
|
|
||||||
instance HasConnectedClientState Env where
|
instance HasConnectedClientState Env where
|
||||||
getConnectedClientState = connectedClientsState
|
getConnectedClientState = connectedClientsState
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
module Types.ConnectionState
|
module Types.ConnectionState
|
||||||
( Client (..),
|
( Client (..),
|
||||||
ConnectedClientsState,
|
|
||||||
ConnectedClients,
|
ConnectedClients,
|
||||||
initConnectionsState,
|
|
||||||
)
|
)
|
||||||
where
|
where
|
||||||
|
|
||||||
|
@ -17,12 +15,4 @@ data Client = Client
|
||||||
joinedRoom :: Bool
|
joinedRoom :: Bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type ConnectedClientsState = MVar ConnectedClients
|
|
||||||
|
|
||||||
type ConnectedClients = [Client]
|
type ConnectedClients = [Client]
|
||||||
|
|
||||||
initConnectionsState :: IO ConnectedClientsState
|
|
||||||
initConnectionsState = newMVar newConnectedClients
|
|
||||||
|
|
||||||
newConnectedClients :: ConnectedClients
|
|
||||||
newConnectedClients = []
|
|
||||||
|
|
|
@ -6,11 +6,11 @@ where
|
||||||
|
|
||||||
import ClassyPrelude
|
import ClassyPrelude
|
||||||
import Network.Wai (Request, Response, ResponseReceived)
|
import Network.Wai (Request, Response, ResponseReceived)
|
||||||
|
import State.ConnectedClientsState (HasConnectedClientState (getConnectedClientState))
|
||||||
|
import State.RoomsState (HasRoomsState (getRoomsState))
|
||||||
import Types.AppTypes
|
import Types.AppTypes
|
||||||
( Env (..),
|
( Env (..),
|
||||||
HasConnectedClientState (getConnectedClientState),
|
|
||||||
)
|
)
|
||||||
import Types.RoomsState (HasRoomsState (getRoomsState))
|
|
||||||
|
|
||||||
class HasWebEnv a where
|
class HasWebEnv a where
|
||||||
getRequest :: a -> Request
|
getRequest :: a -> Request
|
||||||
|
|
|
@ -29,23 +29,22 @@ data WebSocketMessage = ClientInfoMessage SetClientInfo | JoinRoomMessage JoinRo
|
||||||
instance FromJSON WebSocketMessage where
|
instance FromJSON WebSocketMessage where
|
||||||
parseJSON = genericParseJSON defaultOptions {sumEncoding = UntaggedValue}
|
parseJSON = genericParseJSON defaultOptions {sumEncoding = UntaggedValue}
|
||||||
|
|
||||||
data SetClientInfo = SetClientInfo
|
data SetClientInfo where
|
||||||
{ displayName :: Text
|
SetClientInfo :: {displayName :: Text} -> SetClientInfo
|
||||||
}
|
|
||||||
deriving (Generic, Show)
|
deriving (Generic, Show)
|
||||||
|
|
||||||
instance FromJSON SetClientInfo
|
instance FromJSON SetClientInfo
|
||||||
|
|
||||||
data JoinRoom = JoinRoom
|
data JoinRoom where
|
||||||
{ roomName :: Text
|
JoinRoom :: {roomName :: Text} -> JoinRoom
|
||||||
}
|
|
||||||
deriving (Generic, Show)
|
deriving (Generic, Show)
|
||||||
|
|
||||||
instance FromJSON JoinRoom
|
instance FromJSON JoinRoom
|
||||||
|
|
||||||
data AllChatMessageIncoming = AllChatMessageIncoming
|
data AllChatMessageIncoming where
|
||||||
{ content :: Text
|
AllChatMessageIncoming ::
|
||||||
}
|
{content :: Text} ->
|
||||||
|
AllChatMessageIncoming
|
||||||
deriving (Generic, Show)
|
deriving (Generic, Show)
|
||||||
|
|
||||||
instance FromJSON AllChatMessageIncoming
|
instance FromJSON AllChatMessageIncoming
|
||||||
|
|
|
@ -15,6 +15,7 @@ import Control.Monad.Except
|
||||||
import Network.HTTP.Types
|
import Network.HTTP.Types
|
||||||
import Network.Wai
|
import Network.Wai
|
||||||
import Network.Wai.Handler.Warp (run)
|
import Network.Wai.Handler.Warp (run)
|
||||||
|
import Network.Wai.Middleware.RequestLogger (logStdout)
|
||||||
import RoomDataHandler (roomDataHandler)
|
import RoomDataHandler (roomDataHandler)
|
||||||
import State.ConnectedClientsState
|
import State.ConnectedClientsState
|
||||||
( MonadConnectedClientsRead (..),
|
( MonadConnectedClientsRead (..),
|
||||||
|
@ -24,13 +25,11 @@ import State.RoomDataState
|
||||||
( MonadRoomDataStateModify (..),
|
( MonadRoomDataStateModify (..),
|
||||||
MonadRoomDataStateRead (getRoomDataState),
|
MonadRoomDataStateRead (getRoomDataState),
|
||||||
)
|
)
|
||||||
import Types.AppTypes (Env (..))
|
import State.RoomsState
|
||||||
import Types.RoomsState
|
( getRoomState,
|
||||||
( HasRoomsState (getRoomsState),
|
|
||||||
getRoomState,
|
|
||||||
roomStateDiffers,
|
|
||||||
updateRoomState,
|
updateRoomState,
|
||||||
)
|
)
|
||||||
|
import Types.AppTypes (Env (..))
|
||||||
import Types.WebEnv
|
import Types.WebEnv
|
||||||
|
|
||||||
newtype ExceptTApp e a = E {unExceptTApp :: IO (Either e a)}
|
newtype ExceptTApp e a = E {unExceptTApp :: IO (Either e a)}
|
||||||
|
@ -146,6 +145,6 @@ runWebServer ::
|
||||||
) =>
|
) =>
|
||||||
m ()
|
m ()
|
||||||
runWebServer = do
|
runWebServer = do
|
||||||
putStrLn "http://localhost:8081/"
|
putStrLn "Webserver up and running at http://localhost:8081/"
|
||||||
runWebApp >>= liftIO . run 8081
|
runWebApp >>= liftIO . (run 8081 . logStdout)
|
||||||
return ()
|
return ()
|
||||||
|
|
|
@ -33,11 +33,11 @@ wsApp = do
|
||||||
broadcastUserData
|
broadcastUserData
|
||||||
withCleanUp $ forever $ do
|
withCleanUp $ forever $ do
|
||||||
handleWSAction
|
handleWSAction
|
||||||
broadcastUserData
|
|
||||||
|
|
||||||
handleWSAction ::
|
handleWSAction ::
|
||||||
( MonadWebSocketSession m,
|
( MonadWebSocketSession m,
|
||||||
MonadConnectedClientsModify m,
|
MonadConnectedClientsModify m,
|
||||||
|
MonadRoomDataStateRead m,
|
||||||
MonadBroadcast m,
|
MonadBroadcast m,
|
||||||
MonadAllChat m
|
MonadAllChat m
|
||||||
) =>
|
) =>
|
||||||
|
@ -47,8 +47,10 @@ handleWSAction = do
|
||||||
case msg of
|
case msg of
|
||||||
JoinRoomMessage _ -> do
|
JoinRoomMessage _ -> do
|
||||||
joinRoom
|
joinRoom
|
||||||
|
broadcastUserData
|
||||||
ClientInfoMessage clientInfo -> do
|
ClientInfoMessage clientInfo -> do
|
||||||
updateClientName clientInfo
|
updateClientName clientInfo
|
||||||
|
broadcastUserData
|
||||||
AllChatMessageIncomingMessage incomingMessage -> do
|
AllChatMessageIncomingMessage incomingMessage -> do
|
||||||
broadCastAllChatMessage incomingMessage
|
broadCastAllChatMessage incomingMessage
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,8 @@ import ClassyPrelude
|
||||||
import Data.UUID
|
import Data.UUID
|
||||||
import Network.WebSockets qualified as WS
|
import Network.WebSockets qualified as WS
|
||||||
import State.ConnectedClientsState
|
import State.ConnectedClientsState
|
||||||
( MonadConnectedClientsModify (..),
|
( HasConnectedClientState,
|
||||||
|
MonadConnectedClientsModify (..),
|
||||||
MonadConnectedClientsRead (..),
|
MonadConnectedClientsRead (..),
|
||||||
addWSClientGeneric,
|
addWSClientGeneric,
|
||||||
getConnctedClientsGeneric,
|
getConnctedClientsGeneric,
|
||||||
|
@ -25,8 +26,8 @@ import State.ConnectedClientsState
|
||||||
updateWSClientGeneric,
|
updateWSClientGeneric,
|
||||||
)
|
)
|
||||||
import State.RoomDataState
|
import State.RoomDataState
|
||||||
|
import State.RoomsState (HasRoomsState (..), getRoomState)
|
||||||
import Types.AppTypes
|
import Types.AppTypes
|
||||||
import Types.RoomsState (HasRoomsState (..), getRoomState)
|
|
||||||
|
|
||||||
data WSEnv = WSEnv
|
data WSEnv = WSEnv
|
||||||
{ appEnv :: Env,
|
{ appEnv :: Env,
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
# this way we can use prebuild binaries for hls
|
# this way we can use prebuild binaries for hls
|
||||||
#resolver: nightly-2022-11-12
|
#resolver: nightly-2022-11-12
|
||||||
#resolver: ghc-9.2.4
|
#resolver: ghc-9.2.4
|
||||||
resolver: lts-20.16
|
resolver: lts-20.26
|
||||||
#
|
#
|
||||||
# The location of a snapshot can be provided as a file or url. Stack assumes
|
# 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.
|
# a snapshot provided as a file might change, whereas a url resource does not.
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
packages: []
|
packages: []
|
||||||
snapshots:
|
snapshots:
|
||||||
- completed:
|
- completed:
|
||||||
sha256: dad15e2ec0c09280a5c2e07190fb18710fc54472f029f34f861f686540824d81
|
sha256: 5a59b2a405b3aba3c00188453be172b85893cab8ebc352b1ef58b0eae5d248a2
|
||||||
size: 649592
|
size: 650475
|
||||||
url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/20/16.yaml
|
url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/20/26.yaml
|
||||||
original: lts-20.16
|
original: lts-20.26
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
<link rel="icon" href="/favicon.ico" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>FF Jitsi Rooms</title>
|
<title>FF Jitsi Rooms</title>
|
||||||
</head>
|
</head>
|
||||||
|
|
Binary file not shown.
After Width: | Height: | Size: 548 B |
|
@ -1 +0,0 @@
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
|
Before Width: | Height: | Size: 1.5 KiB |
|
@ -20,6 +20,7 @@ function App() {
|
||||||
conferenceData={conferenceData}
|
conferenceData={conferenceData}
|
||||||
setConferenceData={setConferenceData}
|
setConferenceData={setConferenceData}
|
||||||
userInfo={userInfo}
|
userInfo={userInfo}
|
||||||
|
usersData={roomData}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
|
@ -27,7 +27,7 @@ function JitsiEntrypoint({ roomName, userInfo, conferenceData, setConferenceData
|
||||||
domain={JITSI_DOMAIN}
|
domain={JITSI_DOMAIN}
|
||||||
roomName={roomName}
|
roomName={roomName}
|
||||||
configOverwrite={{
|
configOverwrite={{
|
||||||
startWithAudioMuted: true,
|
startWithAudioMuted: false,
|
||||||
disableModeratorIndicator: true,
|
disableModeratorIndicator: true,
|
||||||
startScreenSharing: true,
|
startScreenSharing: true,
|
||||||
enableEmailInStats: false,
|
enableEmailInStats: false,
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
.meeting-quickjoin {
|
||||||
|
text-align: center
|
||||||
|
}
|
||||||
|
|
||||||
|
.meeting {
|
||||||
|
flex-grow: 3;
|
||||||
|
}
|
|
@ -1,19 +1,27 @@
|
||||||
import { ConferenceData } from '../../background/jitsi/eventListeners'
|
import { ConferenceData } from '../../background/jitsi/eventListeners'
|
||||||
|
import { UsersData } from '../../background/types/roomData'
|
||||||
import useMeetingStarted from '../../hooks/useMeetingStarted'
|
import useMeetingStarted from '../../hooks/useMeetingStarted'
|
||||||
import { useRoomName } from '../../hooks/useRoomName'
|
import { useRoomName } from '../../hooks/useRoomName'
|
||||||
import JitsiEntrypoint from '../jitsi/JitsiEntrypoint'
|
import JitsiEntrypoint from '../jitsi/JitsiEntrypoint'
|
||||||
import { UserInfo } from '../jitsi/types'
|
import { UserInfo } from '../jitsi/types'
|
||||||
import MeetingNameInput from './MeetingNameInput'
|
import MeetingNameInput from './MeetingNameInput'
|
||||||
|
import './Meeting.css'
|
||||||
|
import React from 'react'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
conferenceData: ConferenceData | undefined
|
conferenceData: ConferenceData | undefined
|
||||||
setConferenceData: (newData: ConferenceData) => void
|
setConferenceData: (newData: ConferenceData) => void
|
||||||
userInfo: UserInfo
|
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 [meetingStarted] = useMeetingStarted()
|
||||||
const { roomName } = useRoomName()
|
const { roomName } = useRoomName()
|
||||||
|
const [_, setMeetingStarted] = useMeetingStarted()
|
||||||
|
const { updateAndSubmitRoomName: updateAndSubmitRoomName } = useRoomName()
|
||||||
|
|
||||||
if (meetingStarted) {
|
if (meetingStarted) {
|
||||||
return (
|
return (
|
||||||
|
@ -26,7 +34,32 @@ function Meeting({ conferenceData, setConferenceData, userInfo }: Props) {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return <MeetingNameInput roomName={roomName} currentUser={userInfo.displayName} />
|
return (
|
||||||
|
<div className="meeting">
|
||||||
|
<MeetingNameInput roomName={roomName} currentUser={userInfo.displayName} />
|
||||||
|
<div className="meeting-quickjoin">
|
||||||
|
{usersData?.roomsData.map((roomData) => {
|
||||||
|
return (
|
||||||
|
<React.Fragment key={roomData.roomName}>
|
||||||
|
<h3>
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
onClick={() => {
|
||||||
|
updateAndSubmitRoomName(roomData.roomName)
|
||||||
|
setMeetingStarted(true)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{decodeURI(roomData.roomName)}
|
||||||
|
</a>
|
||||||
|
</h3>
|
||||||
|
{roomData.participants.map((participant) => (
|
||||||
|
<div key={participant.jid}> {participant.displayName} </div>
|
||||||
|
))}
|
||||||
|
</React.Fragment>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Meeting
|
export default Meeting
|
||||||
|
|
|
@ -8,7 +8,7 @@ function MeetingNameInput(props: { roomName: string; currentUser: string }) {
|
||||||
const [_, setMeetingStarted] = useMeetingStarted()
|
const [_, setMeetingStarted] = useMeetingStarted()
|
||||||
|
|
||||||
const onInput: React.ChangeEventHandler<HTMLInputElement> = (event) => {
|
const onInput: React.ChangeEventHandler<HTMLInputElement> = (event) => {
|
||||||
updateRoomName(event.target.value)
|
updateRoomName(encodeURI(event.target.value))
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,7 +23,7 @@ function MeetingNameInput(props: { roomName: string; currentUser: string }) {
|
||||||
<div className="meeting-name-input">
|
<div className="meeting-name-input">
|
||||||
<h1>Greetings {props.currentUser}</h1>
|
<h1>Greetings {props.currentUser}</h1>
|
||||||
<form onSubmit={onSubmit}>
|
<form onSubmit={onSubmit}>
|
||||||
<input placeholder="Roomname" type="text" value={roomName} onChange={onInput} />
|
<input placeholder="Roomname" type="text" value={decodeURI(roomName)} onChange={onInput} />
|
||||||
<button type="submit">Enter the adventure</button>
|
<button type="submit">Enter the adventure</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
.sidebar {
|
.sidebar {
|
||||||
|
resize: horizontal;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
|
|
@ -5,6 +5,7 @@ import { UsersData } from '../../background/types/roomData'
|
||||||
import useMeetingStarted from '../../hooks/useMeetingStarted'
|
import useMeetingStarted from '../../hooks/useMeetingStarted'
|
||||||
import { useRoomName } from '../../hooks/useRoomName'
|
import { useRoomName } from '../../hooks/useRoomName'
|
||||||
import Chat from '../chat/Chat'
|
import Chat from '../chat/Chat'
|
||||||
|
import React from 'react'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
usersData: UsersData
|
usersData: UsersData
|
||||||
|
@ -22,7 +23,7 @@ function Sidebar(props: Props) {
|
||||||
<div className="sidebar-body">
|
<div className="sidebar-body">
|
||||||
{props.usersData.roomsData.map((roomData) => {
|
{props.usersData.roomsData.map((roomData) => {
|
||||||
return (
|
return (
|
||||||
<>
|
<React.Fragment key={roomData.roomName}>
|
||||||
<h3>
|
<h3>
|
||||||
<a
|
<a
|
||||||
href="#"
|
href="#"
|
||||||
|
@ -31,18 +32,18 @@ function Sidebar(props: Props) {
|
||||||
setMeetingStarted(true)
|
setMeetingStarted(true)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{roomData.roomName}
|
{decodeURI(roomData.roomName)}
|
||||||
</a>
|
</a>
|
||||||
</h3>
|
</h3>
|
||||||
{roomData.participants.map((participant) => (
|
{roomData.participants.map((participant) => (
|
||||||
<div key={participant.jid}> {participant.displayName} </div>
|
<div key={participant.jid}> {participant.displayName} </div>
|
||||||
))}
|
))}
|
||||||
</>
|
</React.Fragment>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h3> No room</h3>
|
<h3>No room</h3>
|
||||||
{props.usersData.usersWithOutRoom.map((user) => (
|
{props.usersData.usersWithOutRoom.map((user) => (
|
||||||
<div key={user.uuid}>{user.name}</div>
|
<div key={user.uuid}>{user.name}</div>
|
||||||
))}
|
))}
|
||||||
|
|
|
@ -20,7 +20,7 @@ function useSidebarVisibility() {
|
||||||
return { sidebarVisibility, toggleSidebarVisibility, sidebarToggleText }
|
return { sidebarVisibility, toggleSidebarVisibility, sidebarToggleText }
|
||||||
}
|
}
|
||||||
const getSidebarToggleText = (sidebarVisibility: sidebarVisibilityOptions) => {
|
const getSidebarToggleText = (sidebarVisibility: sidebarVisibilityOptions) => {
|
||||||
if (sidebarVisibility === 'full') return '<-'
|
if (sidebarVisibility === 'full') return '<--'
|
||||||
if (sidebarVisibility === 'small') return '<-'
|
if (sidebarVisibility === 'small') return '<-'
|
||||||
if (sidebarVisibility === 'hidden') return '->'
|
if (sidebarVisibility === 'hidden') return '->'
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,20 +7,23 @@ import useWebSocketConnection from './useWebSocketConnection'
|
||||||
function useBackendData(userInfo: UserInfo) {
|
function useBackendData(userInfo: UserInfo) {
|
||||||
console.log('[Rooms] useBackendData')
|
console.log('[Rooms] useBackendData')
|
||||||
|
|
||||||
const { onMessage, sendMessage, disconnect } = useWebSocketConnection(userInfo)
|
const { onMessage, cleanUpOnMessage, sendMessage, disconnect } = useWebSocketConnection(userInfo)
|
||||||
const { roomData, setRoomData } = useRoomData()
|
const { roomData, setRoomData } = useRoomData()
|
||||||
const { addChatMessage } = useAllChat()
|
const { addChatMessage } = useAllChat()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
onMessage((messageString) => {
|
console.log('[Rooms] add onMessage Listener')
|
||||||
|
const messageCallback = (messageString: string) => {
|
||||||
console.log('[Rooms] message from ws', messageString)
|
console.log('[Rooms] message from ws', messageString)
|
||||||
const messageObject = JSON.parse(messageString)
|
const messageObject = JSON.parse(messageString)
|
||||||
|
|
||||||
!!messageObject.roomsData && setRoomData(messageObject)
|
!!messageObject.roomsData && setRoomData(messageObject)
|
||||||
!!messageObject.content && addChatMessage(messageObject)
|
!!messageObject.content && addChatMessage(messageObject)
|
||||||
return disconnect
|
return disconnect
|
||||||
})
|
}
|
||||||
}, [onMessage, setRoomData, disconnect])
|
onMessage(messageCallback)
|
||||||
|
return () => cleanUpOnMessage(messageCallback)
|
||||||
|
}, [])
|
||||||
|
|
||||||
return { roomData, sendMessage }
|
return { roomData, sendMessage }
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,15 @@ import { useState } from 'react'
|
||||||
import { UsersData } from '../background/types/roomData'
|
import { UsersData } from '../background/types/roomData'
|
||||||
|
|
||||||
function useRoomData() {
|
function useRoomData() {
|
||||||
const [roomData, setRoomData] = useState<UsersData>()
|
const [roomData, setRoomDataInternal] = useState<UsersData>()
|
||||||
|
|
||||||
|
const setRoomData = (usersData: UsersData) => {
|
||||||
|
usersData.roomsData = usersData.roomsData.map((roomData) => {
|
||||||
|
roomData.roomName = decodeURI(roomData.roomName)
|
||||||
|
return roomData
|
||||||
|
})
|
||||||
|
setRoomDataInternal(usersData)
|
||||||
|
}
|
||||||
|
|
||||||
return { roomData, setRoomData }
|
return { roomData, setRoomData }
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { atom, useAtom } from 'jotai'
|
import { atom, useAtom } from 'jotai'
|
||||||
import { useCallback, useState } from 'react'
|
import { useCallback } from 'react'
|
||||||
|
|
||||||
const roomNameAtom = atom(getRoomNameFromUrl())
|
const roomNameAtom = atom(getRoomNameFromUrl())
|
||||||
|
|
||||||
|
@ -46,7 +46,7 @@ function setRoomNameInUrl(roomName: string) {
|
||||||
|
|
||||||
function setRoomNameInTitle(roomName: string) {
|
function setRoomNameInTitle(roomName: string) {
|
||||||
if (!!roomName) {
|
if (!!roomName) {
|
||||||
document.title = roomName
|
document.title = decodeURI(roomName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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 { WEBSOCKET_URL } from '../background/constants'
|
||||||
import { UserInfo } from '../components/jitsi/types'
|
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) {
|
function useWebSocketConnection(userInfo: UserInfo) {
|
||||||
console.log('[Rooms] useWebSocketConnection')
|
console.log('[Rooms] useWebSocketConnection')
|
||||||
|
|
||||||
const [webSocketConnection] = useState<WebSocket>(() => createWebSocketConnection(userInfo))
|
const [webSocketConnection] = useAtom(webSocketConnectionAtom)
|
||||||
|
useEffect(() => {
|
||||||
|
sendMessageNowOrLater(webSocketConnection, JSON.stringify(userInfo))
|
||||||
|
}, [webSocketConnection, userInfo]);
|
||||||
|
|
||||||
const sendMessage = useCallback(
|
const sendMessage = useCallback(
|
||||||
(message: string) => {
|
(message: string) => {
|
||||||
|
@ -37,8 +37,19 @@ function useWebSocketConnection(userInfo: UserInfo) {
|
||||||
)
|
)
|
||||||
|
|
||||||
const disconnect = useCallback(webSocketConnection.close, [webSocketConnection])
|
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
|
export default useWebSocketConnection
|
||||||
|
|
|
@ -51,7 +51,9 @@ button {
|
||||||
background-color: #1a1a1a;
|
background-color: #1a1a1a;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: border-color 0.25s;
|
transition: border-color 0.25s;
|
||||||
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
button:hover {
|
button:hover {
|
||||||
border-color: #646cff;
|
border-color: #646cff;
|
||||||
}
|
}
|
||||||
|
@ -60,19 +62,6 @@ button:focus-visible {
|
||||||
outline: 4px auto -webkit-focus-ring-color;
|
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 {
|
#root {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
@ -92,4 +81,5 @@ input {
|
||||||
background-color: #2b2a33;
|
background-color: #2b2a33;
|
||||||
border-color: #646cff;
|
border-color: #646cff;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
### Get started
|
### Get started
|
||||||
|
|
||||||
- run `./run_dev.sh`
|
- 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
|
- make changes to mod_jitsi_rooms.lua and save the file
|
||||||
- prosody will be restarted
|
- prosody will be restarted
|
||||||
- join a room in jitsi
|
- join a room in jitsi
|
||||||
|
|
|
@ -347,7 +347,7 @@ services:
|
||||||
meet.jitsi:
|
meet.jitsi:
|
||||||
|
|
||||||
jitsi-rooms:
|
jitsi-rooms:
|
||||||
image: jitsi-rooms:67y5d9y2zbi7wkqm2jpcjj1k2614qwx2
|
image: jitsi-rooms
|
||||||
restart: ${RESTART_POLICY:-unless-stopped}
|
restart: ${RESTART_POLICY:-unless-stopped}
|
||||||
ports:
|
ports:
|
||||||
- '9160:9160'
|
- '9160:9160'
|
||||||
|
|
|
@ -4,6 +4,7 @@ local json = require "util.json";
|
||||||
local array = require "util.array";
|
local array = require "util.array";
|
||||||
local iterators = require "util.iterators";
|
local iterators = require "util.iterators";
|
||||||
local jid = require "util.jid";
|
local jid = require "util.jid";
|
||||||
|
local http = require "util.http";
|
||||||
|
|
||||||
local async_handler_wrapper = module:require "util".async_handler_wrapper;
|
local async_handler_wrapper = module:require "util".async_handler_wrapper;
|
||||||
local get_room_from_jid = module:require "util".get_room_from_jid;
|
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)
|
function get_participants_for_room(room_name)
|
||||||
|
|
||||||
local room_address = jid.join(room_name, muc_domain_prefix .. "." .. domain_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();
|
local occupants_json = array();
|
||||||
if room then
|
if room then
|
||||||
local occupants = room._occupants;
|
local occupants = room._occupants;
|
||||||
|
@ -47,7 +48,6 @@ function get_participants_for_room(room_name)
|
||||||
end
|
end
|
||||||
|
|
||||||
function get_all_rooms_with_participants()
|
function get_all_rooms_with_participants()
|
||||||
|
|
||||||
local sessions = prosody.full_sessions;
|
local sessions = prosody.full_sessions;
|
||||||
|
|
||||||
local someTable = (it.join(it.keys(sessions)));
|
local someTable = (it.join(it.keys(sessions)));
|
||||||
|
@ -74,7 +74,7 @@ end
|
||||||
-- @return GET response, containing a json with participants details
|
-- @return GET response, containing a json with participants details
|
||||||
function handle_get_sessions(event)
|
function handle_get_sessions(event)
|
||||||
handle_room_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
|
end
|
||||||
|
|
||||||
function module.load()
|
function module.load()
|
||||||
|
@ -86,9 +86,9 @@ function module.load()
|
||||||
module:log("info", "Hello! You can reach me at: %s", module:http_url());
|
module:log("info", "Hello! You can reach me at: %s", module:http_url());
|
||||||
module:provides("http", {
|
module:provides("http", {
|
||||||
route = {
|
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
|
end
|
||||||
|
|
||||||
|
@ -125,7 +125,6 @@ end
|
||||||
|
|
||||||
--- Checks if event is triggered by healthchecks or focus user.
|
--- Checks if event is triggered by healthchecks or focus user.
|
||||||
function is_system_event(event)
|
function is_system_event(event)
|
||||||
|
|
||||||
if event == nil or event.room == nil or event.room.jid == nil then
|
if event == nil or event.room == nil or event.room.jid == nil then
|
||||||
return true;
|
return true;
|
||||||
end
|
end
|
||||||
|
@ -147,12 +146,11 @@ function handle_room_event(event)
|
||||||
return;
|
return;
|
||||||
end
|
end
|
||||||
|
|
||||||
async_http_request("http://192.168.2.116:8081/roomdata",
|
async_http_request("http://jitsi-rooms:8081/roomdata",
|
||||||
{
|
{
|
||||||
method = "POST",
|
method = "POST",
|
||||||
body = get_all_rooms_with_participants()
|
body = get_all_rooms_with_participants()
|
||||||
})
|
})
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
--Helper function to wait till a component is loaded before running the given callback
|
--Helper function to wait till a component is loaded before running the given callback
|
||||||
|
|
|
@ -4,6 +4,7 @@ local json = require "util.json";
|
||||||
local array = require "util.array";
|
local array = require "util.array";
|
||||||
local iterators = require "util.iterators";
|
local iterators = require "util.iterators";
|
||||||
local jid = require "util.jid";
|
local jid = require "util.jid";
|
||||||
|
local http = require "util.http";
|
||||||
|
|
||||||
local async_handler_wrapper = module:require "util".async_handler_wrapper;
|
local async_handler_wrapper = module:require "util".async_handler_wrapper;
|
||||||
local get_room_from_jid = module:require "util".get_room_from_jid;
|
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)
|
function get_participants_for_room(room_name)
|
||||||
|
|
||||||
local room_address = jid.join(room_name, muc_domain_prefix .. "." .. domain_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();
|
local occupants_json = array();
|
||||||
if room then
|
if room then
|
||||||
local occupants = room._occupants;
|
local occupants = room._occupants;
|
||||||
|
@ -47,7 +48,6 @@ function get_participants_for_room(room_name)
|
||||||
end
|
end
|
||||||
|
|
||||||
function get_all_rooms_with_participants()
|
function get_all_rooms_with_participants()
|
||||||
|
|
||||||
local sessions = prosody.full_sessions;
|
local sessions = prosody.full_sessions;
|
||||||
|
|
||||||
local someTable = (it.join(it.keys(sessions)));
|
local someTable = (it.join(it.keys(sessions)));
|
||||||
|
@ -74,7 +74,7 @@ end
|
||||||
-- @return GET response, containing a json with participants details
|
-- @return GET response, containing a json with participants details
|
||||||
function handle_get_sessions(event)
|
function handle_get_sessions(event)
|
||||||
handle_room_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
|
end
|
||||||
|
|
||||||
function module.load()
|
function module.load()
|
||||||
|
@ -86,9 +86,9 @@ function module.load()
|
||||||
module:log("info", "Hello! You can reach me at: %s", module:http_url());
|
module:log("info", "Hello! You can reach me at: %s", module:http_url());
|
||||||
module:provides("http", {
|
module:provides("http", {
|
||||||
route = {
|
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
|
end
|
||||||
|
|
||||||
|
@ -125,7 +125,6 @@ end
|
||||||
|
|
||||||
--- Checks if event is triggered by healthchecks or focus user.
|
--- Checks if event is triggered by healthchecks or focus user.
|
||||||
function is_system_event(event)
|
function is_system_event(event)
|
||||||
|
|
||||||
if event == nil or event.room == nil or event.room.jid == nil then
|
if event == nil or event.room == nil or event.room.jid == nil then
|
||||||
return true;
|
return true;
|
||||||
end
|
end
|
||||||
|
@ -147,12 +146,11 @@ function handle_room_event(event)
|
||||||
return;
|
return;
|
||||||
end
|
end
|
||||||
|
|
||||||
async_http_request("http://192.168.2.116:8081/roomdata",
|
async_http_request("http://jitsi-rooms:8081/roomdata",
|
||||||
{
|
{
|
||||||
method = "POST",
|
method = "POST",
|
||||||
body = get_all_rooms_with_participants()
|
body = get_all_rooms_with_participants()
|
||||||
})
|
})
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
--Helper function to wait till a component is loaded before running the given callback
|
--Helper function to wait till a component is loaded before running the given callback
|
||||||
|
@ -202,7 +200,7 @@ run_when_component_loaded(main_muc_component_host, function(host_module, host_na
|
||||||
-- the following must run after speakerstats (priority -1)
|
-- 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-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-joined", handle_room_event, -2);
|
||||||
main_module:hook("muc-occupant-left", 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);
|
main_module:hook("muc-room-destroyed", handle_room_event, -2);
|
||||||
end);
|
end);
|
||||||
end);
|
end);
|
||||||
|
|
Loading…
Reference in New Issue