--- 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 presence_check_status = util.presence_check_status; local process_host_module = util.process_host_module; local um_is_admin = require 'core.usermanager'.is_admin; local function is_admin(jid) return um_is_admin(jid, module.host); end 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 connect_done = 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, moderatorId = room._data.moderator_id, -- can be used from external modules to set single moderator for meetings createdTimestamp = room.created_timestamp and tostring(room.created_timestamp) or nil }):up(); module:send(connect_done); 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; for _, o in room:each_occupant() do if not is_admin(o.bare_jid) then local fmuc_pr = st.clone(o:get_presence()); local user, _, res = jid.split(o.nick); fmuc_pr.attr.to = jid.join(user, conference_service , res); fmuc_pr.attr.from = o.jid; -- add 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, stanza, occupant = event.room, event.stanza, event.dest_occupant; -- 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)) 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, stanza, occupant = event.room, event.stanza, event.occupant; -- 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)) 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); -- detects new participants joining main room and sending them to the visitor nodes host_module:hook('muc-occupant-joined', function (event) local room, stanza, occupant = event.room, event.stanza, event.occupant; -- 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)) 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 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, stanza, occupant = event.room, event.stanza, event.occupant; -- filter sending messages from transcribers/jibris to visitors if not visitors_nodes[room.jid] or ignore_list:contains(jid.host(occupant.bare_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, stanza = event.occupant, event.room, event.stanza; 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 -- 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 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 end); module:hook('jitsi-lobby-enabled', function(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-disabled', function(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);