mirror of
https://gitcode.com/GitHub_Trending/ji/jitsi-meet.git
synced 2025-12-30 11:22:31 +00:00
Make sure we execute before prosody cleans it up from the list of room. If we try to look it up after that we will not find it. If we also add at 0 we cannot guarantee the order of hook execution.
564 lines
22 KiB
Lua
564 lines
22 KiB
Lua
--- activate under main vhost
|
|
--- In /etc/hosts add:
|
|
--- vm1-ip-address visitors1.domain.com
|
|
--- vm1-ip-address conference.visitors1.domain.com
|
|
--- vm2-ip-address visitors2.domain.com
|
|
--- vm2-ip-address conference.visitors2.domain.com
|
|
--- Enable in global modules: 's2s_bidi' and 'certs_all'
|
|
--- Make sure 's2s' is not in modules_disabled
|
|
--- Open port 5269 on the provider side and on the firewall on the machine (iptables -I INPUT 4 -p tcp -m tcp --dport 5269 -j ACCEPT)
|
|
--- NOTE: Make sure all communication between prosodies is using the real jids ([foo]room1@muc.example.com)
|
|
local st = require 'util.stanza';
|
|
local jid = require 'util.jid';
|
|
local new_id = require 'util.id'.medium;
|
|
local util = module:require 'util';
|
|
local filter_identity_from_presence = util.filter_identity_from_presence;
|
|
local is_admin = util.is_admin;
|
|
local presence_check_status = util.presence_check_status;
|
|
local process_host_module = util.process_host_module;
|
|
local is_transcriber_jigasi = util.is_transcriber_jigasi;
|
|
local room_jid_match_rewrite = util.room_jid_match_rewrite;
|
|
local json = require 'cjson.safe';
|
|
|
|
-- Debug flag
|
|
local DEBUG = false;
|
|
|
|
local MUC_NS = 'http://jabber.org/protocol/muc';
|
|
|
|
-- required parameter for custom muc component prefix, defaults to 'conference'
|
|
local muc_domain_prefix = module:get_option_string('muc_mapper_domain_prefix', 'conference');
|
|
|
|
local main_muc_component_config = module:get_option_string('main_muc');
|
|
if main_muc_component_config == nil then
|
|
module:log('error', 'visitors rooms not enabled missing main_muc config');
|
|
return ;
|
|
end
|
|
|
|
-- A list of domains which to be ignored for visitors. For occupants using those domain we do not propagate them
|
|
-- to visitor nodes and we do not update them with presence changes
|
|
local ignore_list = module:get_option_set('visitors_ignore_list', {});
|
|
|
|
-- Advertise the component for discovery via disco#items
|
|
module:add_identity('component', 'visitors', 'visitors.'..module.host);
|
|
|
|
local sent_iq_cache = require 'util.cache'.new(200);
|
|
|
|
-- visitors_nodes = {
|
|
-- roomjid1 = {
|
|
-- nodes = {
|
|
-- ['conference.visitors1.jid'] = 2, // number of main participants, on 0 we clean it
|
|
-- ['conference.visitors2.jid'] = 3
|
|
-- }
|
|
-- },
|
|
-- roomjid2 = {}
|
|
--}
|
|
local visitors_nodes = {};
|
|
|
|
-- sends connect or update iq
|
|
-- @parameter type - Type of iq to send 'connect' or 'update'
|
|
local function send_visitors_iq(conference_service, room, type)
|
|
-- send iq informing the vnode that the connect is done and it will allow visitors to join
|
|
local iq_id = new_id();
|
|
sent_iq_cache:set(iq_id, socket.gettime());
|
|
local visitors_iq = st.iq({
|
|
type = 'set',
|
|
to = conference_service,
|
|
from = module.host,
|
|
id = iq_id })
|
|
:tag('visitors', { xmlns = 'jitsi:visitors',
|
|
room = jid.join(jid.node(room.jid), conference_service) })
|
|
:tag(type, { xmlns = 'jitsi:visitors',
|
|
password = type ~= 'disconnect' and room:get_password() or '',
|
|
lobby = room._data.lobbyroom and 'true' or 'false',
|
|
meetingId = room._data.meetingId,
|
|
createdTimestamp = room.created_timestamp and tostring(room.created_timestamp) or nil,
|
|
allowUnauthenticatedAccess = room._data.allowUnauthenticatedAccess ~= nil and tostring(room._data.allowUnauthenticatedAccess) or nil
|
|
});
|
|
|
|
if type == 'update' then
|
|
visitors_iq:tag('moderators', { xmlns = 'jitsi:visitors' });
|
|
|
|
for _, o in room:each_occupant() do
|
|
if not is_admin(o.bare_jid) and o.role == 'moderator' then
|
|
visitors_iq:tag('item', { epId = jid.resource(o.nick) }):up();
|
|
end
|
|
end
|
|
|
|
visitors_iq:up();
|
|
|
|
-- files that are shared in the room
|
|
if room.jitsi_shared_files then
|
|
visitors_iq:tag('files', { xmlns = 'jitsi:visitors' });
|
|
for k, v in pairs(room.jitsi_shared_files) do
|
|
visitors_iq:tag('file', {
|
|
id = k
|
|
}):text(json.encode(v)):up();
|
|
end
|
|
visitors_iq:up();
|
|
end
|
|
|
|
if room.polls and room.polls.count > 0 then
|
|
-- polls created in the room that we want to send to the visitor nodes
|
|
local data = {
|
|
command = "old-polls",
|
|
polls = {},
|
|
type = 'polls'
|
|
};
|
|
for i, poll in ipairs(room.polls.order) do
|
|
data.polls[i] = {
|
|
pollId = poll.pollId,
|
|
senderId = poll.senderId,
|
|
senderName = poll.senderName,
|
|
question = poll.question,
|
|
answers = poll.answers
|
|
};
|
|
end
|
|
|
|
local json_msg_str, error = json.encode(data);
|
|
if not json_msg_str then
|
|
module:log('error', 'Error encoding data room:%s error:%s', room.jid, error);
|
|
end
|
|
|
|
visitors_iq:tag('polls'):text(json_msg_str):up();
|
|
end
|
|
end
|
|
|
|
visitors_iq:up();
|
|
|
|
module:send(visitors_iq);
|
|
end
|
|
|
|
-- Filter out identity information (nick name, email, etc) from a presence stanza,
|
|
-- if the hideDisplayNameForGuests option for the room is set.
|
|
-- This is applied to presence of main room participants before it is sent out to vnodes.
|
|
local function filter_stanza_nick_if_needed(stanza, room)
|
|
if not stanza or stanza.name ~= 'presence' or stanza.attr.type == 'error' or stanza.attr.type == 'unavailable' then
|
|
return stanza;
|
|
end
|
|
|
|
-- if hideDisplayNameForGuests we want to drop any display name from the presence stanza
|
|
if room and room._data.hideDisplayNameForGuests then
|
|
return filter_identity_from_presence(stanza);
|
|
end
|
|
|
|
return stanza;
|
|
end
|
|
|
|
-- an event received from visitors component, which receives iqs from jicofo
|
|
local function connect_vnode(event)
|
|
local room, vnode = event.room, event.vnode;
|
|
local conference_service = muc_domain_prefix..'.'..vnode..'.meet.jitsi';
|
|
|
|
if visitors_nodes[room.jid] and
|
|
visitors_nodes[room.jid].nodes and
|
|
visitors_nodes[room.jid].nodes[conference_service] then
|
|
-- nothing to do
|
|
return;
|
|
end
|
|
|
|
if visitors_nodes[room.jid] == nil then
|
|
visitors_nodes[room.jid] = {};
|
|
end
|
|
if visitors_nodes[room.jid].nodes == nil then
|
|
visitors_nodes[room.jid].nodes = {};
|
|
end
|
|
|
|
local sent_main_participants = 0;
|
|
|
|
-- send update initially so we can report the moderators that will join
|
|
send_visitors_iq(conference_service, room, 'update');
|
|
|
|
-- let's send message history
|
|
local event = {
|
|
room = room;
|
|
to = conference_service;
|
|
next_stanza = function() end; -- muc-get-history should define this iterator
|
|
};
|
|
module:context(main_muc_component_config):fire_event("muc-get-history", event);
|
|
for msg in event.next_stanza, event do
|
|
-- the messages stored in history has been stored before domain_mapper and
|
|
-- contain the virtual jid for a from
|
|
msg.attr.from = room_jid_match_rewrite(msg.attr.from);
|
|
room:route_stanza(msg);
|
|
end
|
|
|
|
for _, o in room:each_occupant() do
|
|
if not is_admin(o.bare_jid) then
|
|
local fmuc_pr = filter_stanza_nick_if_needed(st.clone(o:get_presence()), room);
|
|
local user, _, res = jid.split(o.nick);
|
|
fmuc_pr.attr.to = jid.join(user, conference_service , res);
|
|
fmuc_pr.attr.from = o.jid;
|
|
-- add <x>
|
|
fmuc_pr:tag('x', { xmlns = MUC_NS });
|
|
|
|
-- if there is a password on the main room let's add the password for the vnode join
|
|
-- as we will set the password to the vnode room and we will need it
|
|
local pass = room:get_password();
|
|
if pass and pass ~= '' then
|
|
fmuc_pr:tag('password'):text(pass);
|
|
end
|
|
fmuc_pr:up();
|
|
|
|
module:send(fmuc_pr);
|
|
|
|
sent_main_participants = sent_main_participants + 1;
|
|
end
|
|
end
|
|
visitors_nodes[room.jid].nodes[conference_service] = sent_main_participants;
|
|
|
|
send_visitors_iq(conference_service, room, 'connect');
|
|
end
|
|
module:hook('jitsi-connect-vnode', connect_vnode);
|
|
|
|
-- listens for responses to the iq sent for connecting vnode
|
|
local function stanza_handler(event)
|
|
local origin, stanza = event.origin, event.stanza;
|
|
|
|
if stanza.name ~= 'iq' then
|
|
return;
|
|
end
|
|
|
|
-- we receive error from vnode for our disconnect message as the room was already destroyed (all visitors left)
|
|
if (stanza.attr.type == 'result' or stanza.attr.type == 'error') and sent_iq_cache:get(stanza.attr.id) then
|
|
sent_iq_cache:set(stanza.attr.id, nil);
|
|
return true;
|
|
end
|
|
end
|
|
module:hook('iq/host', stanza_handler, 10);
|
|
|
|
-- an event received from visitors component, which receives iqs from jicofo
|
|
local function disconnect_vnode(event)
|
|
local room, vnode = event.room, event.vnode;
|
|
|
|
if visitors_nodes[event.room.jid] == nil then
|
|
-- maybe the room was already destroyed and vnodes cleared
|
|
return;
|
|
end
|
|
|
|
local conference_service = muc_domain_prefix..'.'..vnode..'.meet.jitsi';
|
|
|
|
visitors_nodes[room.jid].nodes[conference_service] = nil;
|
|
|
|
send_visitors_iq(conference_service, room, 'disconnect');
|
|
end
|
|
module:hook('jitsi-disconnect-vnode', disconnect_vnode);
|
|
|
|
-- takes care when the visitor nodes destroys the room to count the leaving participants from there, and if its really destroyed
|
|
-- we clean up, so if we establish again the connection to the same visitor node to send the main participants
|
|
module:hook('presence/full', function(event)
|
|
local stanza = event.stanza;
|
|
local room_name, from_host = jid.split(stanza.attr.from);
|
|
if stanza.attr.type == 'unavailable' and from_host ~= main_muc_component_config then
|
|
local room_jid = jid.join(room_name, main_muc_component_config); -- converts from visitor to main room jid
|
|
|
|
local x = stanza:get_child('x', 'http://jabber.org/protocol/muc#user');
|
|
if not presence_check_status(x, '110') then
|
|
return;
|
|
end
|
|
|
|
if visitors_nodes[room_jid] and visitors_nodes[room_jid].nodes
|
|
and visitors_nodes[room_jid].nodes[from_host] then
|
|
visitors_nodes[room_jid].nodes[from_host] = visitors_nodes[room_jid].nodes[from_host] - 1;
|
|
-- we clean only on disconnect coming from jicofo
|
|
end
|
|
end
|
|
end, 900);
|
|
|
|
process_host_module(main_muc_component_config, function(host_module, host)
|
|
-- detects presence change in a main participant and propagate it to the used visitor nodes
|
|
host_module:hook('muc-occupant-pre-change', function (event)
|
|
local room, stanzaEv, occupant = event.room, event.stanza, event.dest_occupant;
|
|
local stanza = filter_stanza_nick_if_needed(stanzaEv, room);
|
|
|
|
-- filter focus and configured domains (used for jibri and transcribers)
|
|
if is_admin(stanza.attr.from) or visitors_nodes[room.jid] == nil
|
|
or (ignore_list:contains(jid.host(occupant.bare_jid)) and not is_transcriber_jigasi(stanza)) then
|
|
return;
|
|
end
|
|
|
|
local vnodes = visitors_nodes[room.jid].nodes;
|
|
local user, _, res = jid.split(occupant.nick);
|
|
-- a change in the presence of a main participant we need to update all active visitor nodes
|
|
for k in pairs(vnodes) do
|
|
local fmuc_pr = st.clone(stanza);
|
|
fmuc_pr.attr.to = jid.join(user, k, res);
|
|
fmuc_pr.attr.from = occupant.jid;
|
|
module:send(fmuc_pr);
|
|
end
|
|
end);
|
|
|
|
-- when a main participant leaves inform the visitor nodes
|
|
host_module:hook('muc-occupant-left', function (event)
|
|
local room, stanzaEv, occupant = event.room, event.stanza, event.occupant;
|
|
local stanza = filter_stanza_nick_if_needed(stanzaEv, room);
|
|
|
|
-- ignore configured domains (jibri and transcribers)
|
|
if is_admin(occupant.bare_jid) or visitors_nodes[room.jid] == nil or visitors_nodes[room.jid].nodes == nil
|
|
or (ignore_list:contains(jid.host(occupant.bare_jid)) and not is_transcriber_jigasi(stanza)) then
|
|
return;
|
|
end
|
|
|
|
--this is probably participant kick scenario, create an unavailable presence and send to vnodes.
|
|
if not stanza then
|
|
stanza = st.presence {from = occupant.nick; type = "unavailable";};
|
|
end
|
|
|
|
-- we want to update visitor node that a main participant left or kicked.
|
|
if stanza then
|
|
local vnodes = visitors_nodes[room.jid].nodes;
|
|
local user, _, res = jid.split(occupant.nick);
|
|
for k in pairs(vnodes) do
|
|
local fmuc_pr = st.clone(stanza);
|
|
fmuc_pr.attr.to = jid.join(user, k, res);
|
|
fmuc_pr.attr.from = occupant.jid;
|
|
module:send(fmuc_pr);
|
|
end
|
|
end
|
|
end);
|
|
|
|
-- cleanup cache
|
|
host_module:hook('muc-room-destroyed',function(event)
|
|
local room = event.room;
|
|
|
|
-- room is destroyed let's disconnect all vnodes
|
|
if visitors_nodes[room.jid] then
|
|
local vnodes = visitors_nodes[room.jid].nodes;
|
|
for conference_service in pairs(vnodes) do
|
|
send_visitors_iq(conference_service, room, 'disconnect');
|
|
end
|
|
|
|
visitors_nodes[room.jid] = nil;
|
|
end
|
|
end, 1); -- prosody handles it at 0
|
|
|
|
-- detects new participants joining main room and sending them to the visitor nodes
|
|
host_module:hook('muc-occupant-joined', function (event)
|
|
local room, stanzaEv, occupant = event.room, event.stanza, event.occupant;
|
|
local stanza = filter_stanza_nick_if_needed(stanzaEv, room);
|
|
|
|
-- filter focus, ignore configured domains (jibri and transcribers)
|
|
if is_admin(stanza.attr.from) or visitors_nodes[room.jid] == nil
|
|
or (ignore_list:contains(jid.host(occupant.bare_jid)) and not is_transcriber_jigasi(stanza)) then
|
|
return;
|
|
end
|
|
|
|
local vnodes = visitors_nodes[room.jid].nodes;
|
|
local user, _, res = jid.split(occupant.nick);
|
|
-- a main participant we need to update all active visitor nodes
|
|
for k in pairs(vnodes) do
|
|
if occupant.role == 'moderator' then
|
|
-- first send that the participant is a moderator
|
|
send_visitors_iq(k, room, 'update');
|
|
end
|
|
local fmuc_pr = st.clone(stanza);
|
|
fmuc_pr.attr.to = jid.join(user, k, res);
|
|
fmuc_pr.attr.from = occupant.jid;
|
|
module:send(fmuc_pr);
|
|
end
|
|
end);
|
|
-- forwards messages from main participants to vnodes
|
|
host_module:hook('muc-occupant-groupchat', function(event)
|
|
local room, stanzaEv, occupant = event.room, event.stanza, event.occupant;
|
|
local stanza = filter_stanza_nick_if_needed(stanzaEv, room);
|
|
|
|
-- filter sending messages from transcribers/jibris to visitors
|
|
if not visitors_nodes[room.jid] then
|
|
return;
|
|
end
|
|
|
|
local vnodes = visitors_nodes[room.jid].nodes;
|
|
local user = jid.node(occupant.nick);
|
|
-- a main participant we need to update all active visitor nodes
|
|
for k in pairs(vnodes) do
|
|
local fmuc_msg = st.clone(stanza);
|
|
fmuc_msg.attr.to = jid.join(user, k);
|
|
fmuc_msg.attr.from = occupant.jid;
|
|
module:send(fmuc_msg);
|
|
end
|
|
end);
|
|
-- receiving messages from visitor nodes and forward them to local main participants
|
|
-- and forward them to the rest of visitor nodes
|
|
host_module:hook('muc-occupant-groupchat', function(event)
|
|
local occupant, room, stanzaEv = event.occupant, event.room, event.stanza;
|
|
local stanza = filter_stanza_nick_if_needed(stanzaEv, room);
|
|
local to = stanza.attr.to;
|
|
local from = stanza.attr.from;
|
|
local from_vnode = jid.host(from);
|
|
|
|
if occupant or not (visitors_nodes[to]
|
|
and visitors_nodes[to].nodes
|
|
and visitors_nodes[to].nodes[from_vnode]) then
|
|
return;
|
|
end
|
|
|
|
if host_module:fire_event('jitsi-visitor-groupchat-pre-route', event) then
|
|
-- message filtered
|
|
return;
|
|
end
|
|
|
|
-- a message from visitor occupant of known visitor node
|
|
stanza.attr.from = to;
|
|
for _, o in room:each_occupant() do
|
|
-- send it to the nick to be able to route it to the room (ljm multiple rooms) from unknown occupant
|
|
room:route_to_occupant(o, stanza);
|
|
end
|
|
-- let's add the message to the history of the room
|
|
host_module:fire_event("muc-add-history", { room = room; stanza = stanza; from = from; visitor = true; });
|
|
|
|
-- now we need to send to rest of visitor nodes
|
|
local vnodes = visitors_nodes[room.jid].nodes;
|
|
for k in pairs(vnodes) do
|
|
if k ~= from_vnode then
|
|
local st_copy = st.clone(stanza);
|
|
st_copy.attr.to = jid.join(jid.node(room.jid), k);
|
|
module:send(st_copy);
|
|
end
|
|
end
|
|
|
|
return true;
|
|
end, 55); -- prosody check for unknown participant chat is prio 50, we want to override it
|
|
|
|
-- Handle private messages from visitor nodes to main participants
|
|
-- This routes forwarded private messages through the proper MUC system
|
|
host_module:hook('message/full', function(event)
|
|
local stanza = event.stanza;
|
|
|
|
-- Only handle chat messages (private messages)
|
|
if stanza.attr.type ~= 'chat' then
|
|
return; -- Let other handlers process non-chat messages
|
|
end
|
|
|
|
local to = stanza.attr.to;
|
|
|
|
-- Early return if this is not targeted at our MUC component
|
|
if jid.host(to) ~= main_muc_component_config then
|
|
return; -- Not for our MUC component, let other handlers process
|
|
end
|
|
|
|
local from = stanza.attr.from;
|
|
local from_host = jid.host(from);
|
|
local to_node = jid.node(to);
|
|
local to_resource = jid.resource(to);
|
|
|
|
-- Check if this is a private message from a known visitor node
|
|
local target_room_jid = jid.bare(to);
|
|
|
|
-- Early return if we don't have any visitor nodes for this room
|
|
if not (visitors_nodes[target_room_jid] and visitors_nodes[target_room_jid].nodes) then
|
|
return; -- No visitor nodes for this room, let default MUC handle it
|
|
end
|
|
|
|
-- Early return if the from_host is not a known visitor node
|
|
if not visitors_nodes[target_room_jid].nodes[from_host] then
|
|
-- This could be a main->visitor message, let it go through s2s
|
|
return; -- Not from a known visitor node, let default MUC handle it
|
|
end
|
|
|
|
-- At this point we know it's a visitor message, handle it
|
|
local room = prosody.hosts[main_muc_component_config].modules.muc.get_room_from_jid(target_room_jid);
|
|
if room then
|
|
-- Find the occupant
|
|
local occupant = room:get_occupant_by_nick(to);
|
|
if occupant then
|
|
-- Add addresses element (XEP-0033) to store original visitor JID for reply functionality
|
|
stanza:tag('addresses', { xmlns = 'http://jabber.org/protocol/address' })
|
|
:tag('address', { type = 'ofrom', jid = stanza.attr.from }):up()
|
|
:up();
|
|
|
|
-- Change from to be the main domain equivalent for proper client recognition
|
|
-- Use bare JID without resource
|
|
stanza.attr.from = jid.join(to_node, main_muc_component_config);
|
|
|
|
room:route_to_occupant(occupant, stanza);
|
|
|
|
return true;
|
|
else
|
|
module:log('warn', 'VISITOR PRIVATE MESSAGE: Occupant not found for %s', to);
|
|
end
|
|
else
|
|
module:log('warn', 'VISITOR PRIVATE MESSAGE: Room not found for %s', to);
|
|
end
|
|
|
|
return false;
|
|
end, 10); -- Normal priority since we're in the right place now
|
|
|
|
-- Main->visitor private messages work via s2s routing automatically
|
|
-- No special handling needed!
|
|
|
|
host_module:hook('muc-config-submitted/muc#roomconfig_roomsecret', function(event)
|
|
if event.status_codes['104'] then
|
|
local room = event.room;
|
|
|
|
if visitors_nodes[room.jid] then
|
|
-- we need to update all vnodes
|
|
local vnodes = visitors_nodes[room.jid].nodes;
|
|
for conference_service in pairs(vnodes) do
|
|
send_visitors_iq(conference_service, room, 'update');
|
|
end
|
|
end
|
|
end
|
|
end, -100); -- we want to run last in order to check is the status code 104
|
|
|
|
host_module:hook('muc-set-affiliation', function (event)
|
|
if event.actor and not is_admin(event.actor) and event.affiliation == 'owner' then
|
|
local room = event.room;
|
|
|
|
if not visitors_nodes[room.jid] then
|
|
return;
|
|
end
|
|
-- we need to update all vnodes
|
|
local vnodes = visitors_nodes[room.jid].nodes;
|
|
for conference_service in pairs(vnodes) do
|
|
send_visitors_iq(conference_service, room, 'update');
|
|
end
|
|
end
|
|
end, -2);
|
|
|
|
host_module:hook('poll-created', function (event)
|
|
local room = event.room;
|
|
|
|
if not visitors_nodes[room.jid] then
|
|
return;
|
|
end
|
|
|
|
-- we need to update all vnodes
|
|
local vnodes = visitors_nodes[room.jid].nodes;
|
|
for conference_service in pairs(vnodes) do
|
|
send_visitors_iq(conference_service, room, 'update');
|
|
end
|
|
end);
|
|
host_module:hook('answer-poll', function (event)
|
|
local room, stanza = event.room, event.event.stanza;
|
|
|
|
if not visitors_nodes[room.jid] then
|
|
return;
|
|
end
|
|
|
|
local from = stanza.attr.from;
|
|
|
|
-- we need to update all vnodes
|
|
local vnodes = visitors_nodes[room.jid].nodes;
|
|
for conference_service in pairs(vnodes) do
|
|
-- skip sending the answer to the node from where it originates
|
|
if conference_service ~= from then
|
|
send_visitors_iq(conference_service, room, 'update');
|
|
end
|
|
end
|
|
end);
|
|
end);
|
|
|
|
local function update_vnodes_for_room(event)
|
|
local room = event.room;
|
|
if visitors_nodes[room.jid] then
|
|
-- we need to update all vnodes
|
|
local vnodes = visitors_nodes[room.jid].nodes;
|
|
for conference_service in pairs(vnodes) do
|
|
send_visitors_iq(conference_service, room, 'update');
|
|
end
|
|
end
|
|
end
|
|
|
|
module:hook('jitsi-lobby-enabled', update_vnodes_for_room);
|
|
module:hook('jitsi-lobby-disabled', update_vnodes_for_room);
|
|
module:hook('jitsi-filesharing-updated', update_vnodes_for_room);
|