From 2bf0b1922fbc5c207520ac67197632fe1c6c6e8d Mon Sep 17 00:00:00 2001 From: damencho Date: Thu, 25 Sep 2025 18:06:21 -0500 Subject: [PATCH] feat(visitors): Adds support for visitors voting in polls. --- react/features/conference/functions.any.ts | 4 +- react/features/polls/functions.ts | 5 + resources/extra-large-conference/README.md | 12 +- .../prosody.cfg.lua.visitor.template | 5 + resources/prosody-plugins/mod_fmuc.lua | 37 ++ .../prosody-plugins/mod_polls_component.lua | 485 ++++++++++++------ resources/prosody-plugins/mod_visitors.lua | 57 ++ .../mod_visitors_component.lua | 3 + 8 files changed, 450 insertions(+), 158 deletions(-) diff --git a/react/features/conference/functions.any.ts b/react/features/conference/functions.any.ts index a918bdcba0..31b6ee2673 100644 --- a/react/features/conference/functions.any.ts +++ b/react/features/conference/functions.any.ts @@ -1,7 +1,5 @@ import { IStateful } from '../base/app/types'; import { toState } from '../base/redux/functions'; -import { iAmVisitor } from '../visitors/functions'; - /** * Tells whether or not the notifications should be displayed within @@ -36,5 +34,5 @@ export function arePollsDisabled(stateful: IStateful) { return true; } - return state['features/base/config']?.disablePolls || iAmVisitor(state); + return state['features/base/config']?.disablePolls; } diff --git a/react/features/polls/functions.ts b/react/features/polls/functions.ts index 327d236545..182ebfd062 100644 --- a/react/features/polls/functions.ts +++ b/react/features/polls/functions.ts @@ -1,6 +1,7 @@ import { IReduxState } from '../app/types'; import { MEET_FEATURES } from '../base/jwt/constants'; import { isJwtFeatureEnabled } from '../base/jwt/functions'; +import { iAmVisitor } from '../visitors/functions'; import { IAnswerData } from './types'; @@ -72,6 +73,10 @@ export function hasIdenticalAnswers(currentAnswers: Array): boolean * @returns {boolean} - Returns true if the participant is not allowed to create polls. */ export function isCreatePollDisabled(state: IReduxState) { + if (iAmVisitor(state)) { + return true; + } + const { pollCreationRequiresPermission } = state['features/dynamic-branding']; if (!pollCreationRequiresPermission) { diff --git a/resources/extra-large-conference/README.md b/resources/extra-large-conference/README.md index 4f1a9442d8..9b77efa5b6 100644 --- a/resources/extra-large-conference/README.md +++ b/resources/extra-large-conference/README.md @@ -55,25 +55,35 @@ Setting up configuration for the main prosody is a manual process: s2sout_override = { ["conference.v1.meet.jitsi"] = "tcp://127.0.0.1:52691"; ["v1.meet.jitsi"] = "tcp://127.0.0.1:52691"; -- needed for v1.meet.jitsi->visitors.jitmeet.example.com + ["polls.v1.meet.jitsi"] = "tcp://127.0.0.1:52691"; ["conference.v2.meet.jitsi"] = "tcp://127.0.0.1:52692"; ["v2.meet.jitsi"] = "tcp://127.0.0.1:52692"; + ["polls.v2.meet.jitsi"] = "tcp://127.0.0.1:52692"; ["conference.v3.meet.jitsi"] = "tcp://127.0.0.1:52693"; ["v3.meet.jitsi"] = "tcp://127.0.0.1:52693"; + ["polls.v3.meet.jitsi"] = "tcp://127.0.0.1:52693"; ["conference.v4.meet.jitsi"] = "tcp://127.0.0.1:52694"; ["v4.meet.jitsi"] = "tcp://127.0.0.1:52694"; + ["polls.v4.meet.jitsi"] = "tcp://127.0.0.1:52694"; ["conference.v5.meet.jitsi"] = "tcp://127.0.0.1:52695"; ["v5.meet.jitsi"] = "tcp://127.0.0.1:52695"; + ["polls.v5.meet.jitsi"] = "tcp://127.0.0.1:52695"; ["conference.v6.meet.jitsi"] = "tcp://127.0.0.1:52696"; ["v6.meet.jitsi"] = "tcp://127.0.0.1:52696"; + ["polls.v6.meet.jitsi"] = "tcp://127.0.0.1:52696"; ["conference.v7.meet.jitsi"] = "tcp://127.0.0.1:52697"; ["v7.meet.jitsi"] = "tcp://127.0.0.1:52697"; + ["polls.v7.meet.jitsi"] = "tcp://127.0.0.1:52697"; ["conference.v8.meet.jitsi"] = "tcp://127.0.0.1:52698"; ["v8.meet.jitsi"] = "tcp://127.0.0.1:52698"; + ["polls.v8.meet.jitsi"] = "tcp://127.0.0.1:52698"; } -- allowed list of server-2-server connections s2s_whitelist = { "conference.v1.meet.jitsi", "conference.v2.meet.jitsi", "conference.v3.meet.jitsi", "conference.v4.meet.jitsi", - "conference.v5.meet.jitsi", "conference.v6.meet.jitsi", "conference.v7.meet.jitsi", "conference.v8.meet.jitsi" + "conference.v5.meet.jitsi", "conference.v6.meet.jitsi", "conference.v7.meet.jitsi", "conference.v8.meet.jitsi", + 'polls.v1.meet.jitsi', 'polls.v2.meet.jitsi', 'polls.v3.meet.jitsi', 'polls.v4.meet.jitsi', + 'polls.v5.meet.jitsi', 'polls.v6.meet.jitsi', 'polls.v7.meet.jitsi', 'polls.v8.meet.jitsi' }; ``` diff --git a/resources/extra-large-conference/prosody.cfg.lua.visitor.template b/resources/extra-large-conference/prosody.cfg.lua.visitor.template index b65168cd69..3d52837ab8 100644 --- a/resources/extra-large-conference/prosody.cfg.lua.visitor.template +++ b/resources/extra-large-conference/prosody.cfg.lua.visitor.template @@ -38,12 +38,14 @@ s2s_whitelist = { 'conference.jitmeet.example.com', -- needed for visitors to send messages to main room 'visitors.jitmeet.example.com'; -- needed for sending promotion request to visitors.jitmeet.example.com component 'jitmeet.example.com'; -- unavailable presences back to main room + 'polls.jitmeet.example.com'; -- polls component }; s2sout_override = { ["conference.jitmeet.example.com"] = "tcp://127.0.0.1:5269"; -- needed for visitors to send messages to main room ["jitmeet.example.com"] = "tcp://127.0.0.1:5269"; -- needed for the main room when connecting in to send main participants ["visitors.jitmeet.example.com"] = "tcp://127.0.0.1:5269"; -- needed for sending promotion request to visitors.jitmeet.example.com component + ['polls.jitmeet.example.com'] = "tcp://127.0.0.1:5269"; -- polls component } external_service_secret = '__turnSecret__'; @@ -106,6 +108,7 @@ VirtualHost 'vX.meet.jitsi' 'smacks'; 'jiconop'; 'conference_duration'; + 'features_identity'; } main_muc = 'conference.vX.meet.jitsi'; @@ -138,3 +141,5 @@ Component 'conference.vX.meet.jitsi' 'muc' }; muc_room_locking = false muc_room_default_public_jids = true + +Component 'polls.vX.meet.jitsi' 'polls_component' diff --git a/resources/prosody-plugins/mod_fmuc.lua b/resources/prosody-plugins/mod_fmuc.lua index 7eaa8b99e7..248ac78a82 100644 --- a/resources/prosody-plugins/mod_fmuc.lua +++ b/resources/prosody-plugins/mod_fmuc.lua @@ -749,6 +749,13 @@ local function iq_from_main_handler(event) end end + local pollsEl = node:get_child('polls'); + if pollsEl then + local polls = json.decode(pollsEl:get_text()); + -- let's find is there a new poll + module:fire_event('jitsi-polls-update', { room = room; polls = polls; }); + end + if fire_jicofo_unlock then -- everything is connected allow participants to join module:fire_event('jicofo-unlock-room', { room = room; fmuc_fired = true; }); @@ -814,6 +821,36 @@ function route_s2s_stanza(event) end end +module:hook('answer-poll', function(answerData) + local room = answerData.room; + local room_jid = room_jid_match_rewrite(jid.join(jid.node(room.jid), muc_domain_prefix..'.'..main_domain)); + + -- now send it to the main prosody + local data = { + answers = answerData.data.answers; + command = 'answer-poll'; + pollId = answerData.pollId; + roomJid = room_jid, + senderId = answerData.voterId; + senderName = answerData.voterName; + type = 'polls'; + }; + + local data_str, error = json.encode(data); + if not data_str then + module:log('error', 'Error encoding data room:%s error:%s', room.jid, error); + end + + local stanza = st.message({ + from = module.host, + to = 'polls.'..main_domain + }) + :tag("json-message", { xmlns = "http://jitsi.org/jitmeet"; roomJid = room_jid; }) + :text(data_str) + :up(); + room:route_stanza(stanza); +end); + -- routing to sessions in mod_s2s is -1 and -10, we want to hook before that to make sure to is correct -- or if we want to filter that stanza module:hook("route/remote", route_s2s_stanza, 10); diff --git a/resources/prosody-plugins/mod_polls_component.lua b/resources/prosody-plugins/mod_polls_component.lua index 3d9af49bf6..95d302faa3 100644 --- a/resources/prosody-plugins/mod_polls_component.lua +++ b/resources/prosody-plugins/mod_polls_component.lua @@ -2,7 +2,9 @@ -- by keeping track of the state of polls in each room, and sending -- that state to new participants when they join. +local it = require 'util.iterators'; local json = require 'cjson.safe'; +local array = require 'util.array'; local st = require("util.stanza"); local jid = require "util.jid"; local util = module:require("util"); @@ -12,6 +14,8 @@ local NS_NICK = 'http://jabber.org/protocol/nick'; local get_room_by_name_and_subdomain = util.get_room_by_name_and_subdomain; local is_healthcheck_room = util.is_healthcheck_room; local room_jid_match_rewrite = util.room_jid_match_rewrite; +local internal_room_jid_match_rewrite = util.internal_room_jid_match_rewrite; +local table_compare = util.table_compare; local POLLS_LIMIT = 128; local POLL_PAYLOAD_LIMIT = 1024; @@ -23,6 +27,11 @@ if not main_virtual_host then end local muc_domain_prefix = module:get_option_string('muc_mapper_domain_prefix', 'conference'); +-- this is the main virtual host of the main prosody that this vnode serves +local main_domain = module:get_option_string('main_domain'); +-- only the visitor prosody has main_domain setting +local is_visitor_prosody = main_domain ~= nil; + -- Logs a warning and returns true if a room does not -- have poll data associated with it. local function check_polls(room) @@ -103,191 +112,283 @@ end local function send_polls_message_to_all(room, data_str) for _, room_occupant in room:each_occupant() do - send_polls_message(room, data_str, room_occupant.jid); + -- in case of visitor node send only to visitors + if not is_visitor_prosody or room_occupant.role == 'visitor' then + send_polls_message(room, data_str, room_occupant.jid); + end end end --- Keeps track of the current state of the polls in each room, --- by listening to "new-poll" and "answer-poll" messages, --- and updating the room poll data accordingly. --- This mirrors the client-side poll update logic. -module:hook('message/host', function(event) - local session, stanza = event.origin, event.stanza; + -- Keeps track of the current state of the polls in each room, + -- by listening to "new-poll" and "answer-poll" messages, + -- and updating the room poll data accordingly. + -- This mirrors the client-side poll update logic. + module:hook('message/host', function(event) + local session, stanza = event.origin, event.stanza; - -- we are interested in all messages without a body that are not groupchat - if stanza.attr.type == 'groupchat' or stanza:get_child('body') then - return; - end - - local json_message = stanza:get_child('json-message', 'http://jitsi.org/jitmeet') - or stanza:get_child('json-message'); - if not json_message then - return; - end - - local room = get_room_by_name_and_subdomain(session.jitsi_web_query_room, session.jitsi_web_query_prefix); - if not room then - module:log('warn', 'No room found found for %s %s', session.jitsi_web_query_room, session.jitsi_web_query_prefix); - return; - end - - local occupant_jid = stanza.attr.from; - local occupant = room:get_occupant_by_real_jid(occupant_jid); - if not occupant then - module:log("error", "Occupant sending msg %s was not found in room %s", occupant_jid, room.jid) - return; - end - - local json_message_text = json_message:get_text(); - if string.len(json_message_text) >= POLL_PAYLOAD_LIMIT then - module:log('error', 'Poll payload too large, discarding. Sender: %s to:%s', stanza.attr.from, stanza.attr.to); - return true; - end - - local data, error = json.decode(json_message_text); - if error then - module:log('error', 'Error decoding data error:%s Sender: %s to:%s', error, stanza.attr.from, stanza.attr.to); - return true; - end - - if not data or (data.command ~= "new-poll" and data.command ~= "answer-poll") then - return; - end - - if not validate_polls(data) then - module:log('error', 'Invalid poll data. Sender: %s (%s)', stanza.attr.from, json_message_text); - return true; - end - - if data.command == "new-poll" then - if check_polls(room) then return end - - local poll_creator = get_occupant_details(occupant) - if not poll_creator then - module:log("error", "Cannot retrieve poll creator id and name for %s from %s", occupant.jid, room.jid) - return + -- we are interested in all messages without a body that are not groupchat + if stanza.attr.type == 'groupchat' or stanza:get_child('body') then + return; end - if room.polls.count >= POLLS_LIMIT then - module:log("error", "Too many polls created in %s", room.jid) + local json_message = stanza:get_child('json-message', 'http://jitsi.org/jitmeet') + or stanza:get_child('json-message'); + if not json_message then + return; + end + + local room; + if session.type == 's2sin' then + if not json_message.attr.roomJid then + module:log('warn', 'No room jid found in %s', stanza); + return; + end + room = get_room_from_jid(room_jid_match_rewrite(json_message.attr.roomJid)); + else + room = get_room_by_name_and_subdomain(session.jitsi_web_query_room, session.jitsi_web_query_prefix); + end + + if not room then + module:log('warn', 'No room found found for %s %s', session.jitsi_web_query_room, session.jitsi_web_query_prefix); + return; + end + + local json_message_text = json_message:get_text(); + if string.len(json_message_text) >= POLL_PAYLOAD_LIMIT then + module:log('error', 'Poll payload too large, discarding. Sender: %s to:%s', stanza.attr.from, stanza.attr.to); return true; end - if room.polls.by_id[data.pollId] ~= nil then - module:log("error", "Poll already exists: %s", data.pollId); - origin.send(st.error_reply(stanza, 'cancel', 'not-allowed', 'Poll already exists')); + local data, error = json.decode(json_message_text); + if error then + module:log('error', 'Error decoding data error:%s Sender: %s to:%s', error, stanza.attr.from, stanza.attr.to); return true; end - if room.jitsiMetadata and room.jitsiMetadata.permissions - and room.jitsiMetadata.permissions.pollCreationRestricted - and not is_feature_allowed('create-polls', origin.jitsi_meet_context_features) then - origin.send(st.error_reply(stanza, 'cancel', 'not-allowed', 'Creation of polls not allowed for user')); + if not data or (data.command ~= "new-poll" and data.command ~= "answer-poll") then + return; + end + + if not validate_polls(data) then + module:log('error', 'Invalid poll data. Sender: %s (%s)', stanza.attr.from, json_message_text); + return true; + end + + local occupant_details; + if session.type ~= 's2sin' then + local occupant_jid = stanza.attr.from; + occupant = room:get_occupant_by_real_jid(occupant_jid); + if not occupant then + module:log("error", "Occupant sending msg %s was not found in room %s", occupant_jid, room.jid) + return; + end + occupant_details = get_occupant_details(occupant) + if not occupant_details then + module:log("error", "Cannot retrieve poll creator or voter id and name for %s from %s", + occupant.jid, room.jid) + return + end + else + -- this is a message from a visitor prosody, we will trust it + occupant_details = { occupant_id = data.senderId; occupant_name = data.senderName; }; + end + + if data.command == "new-poll" then + if is_visitor_prosody then + module:log("error", "Poll cannot be created on visitor node."); + session.send(st.error_reply(stanza, 'cancel', 'not-allowed', 'Poll cannot be created by visitor node')); return true; - end + end - local answers = {} - local compact_answers = {} - for i, a in ipairs(data.answers) do - table.insert(answers, { name = a.name }); - table.insert(compact_answers, { key = i, name = a.name}); - end + if check_polls(room) then return end - local poll = { - pollId = data.pollId, - senderId = poll_creator.occupant_id, - senderName = poll_creator.occupant_name, - question = data.question, - answers = answers - }; + local poll_creator = occupant_details; - room.polls.by_id[data.pollId] = poll - table.insert(room.polls.order, poll) - room.polls.count = room.polls.count + 1; + if room.polls.count >= POLLS_LIMIT then + module:log("error", "Too many polls created in %s", room.jid) + return true; + end - local pollData = { - event = event, - room = room, - poll = { + if room.polls.by_id[data.pollId] ~= nil then + module:log("error", "Poll already exists: %s", data.pollId); + session.send(st.error_reply(stanza, 'cancel', 'not-allowed', 'Poll already exists')); + return true; + end + + if room.jitsiMetadata and room.jitsiMetadata.permissions + and room.jitsiMetadata.permissions.pollCreationRestricted + and not is_feature_allowed('create-polls', session.jitsi_meet_context_features) then + session.send(st.error_reply(stanza, 'cancel', 'not-allowed', 'Creation of polls not allowed for user')); + return true; + end + + local answers = {} + local compact_answers = {} + for i, a in ipairs(data.answers) do + table.insert(answers, { name = a.name }); + table.insert(compact_answers, { key = i, name = a.name}); + end + + local poll = { pollId = data.pollId, senderId = poll_creator.occupant_id, senderName = poll_creator.occupant_name, question = data.question, - answers = compact_answers + answers = answers + }; + + room.polls.by_id[data.pollId] = poll + table.insert(room.polls.order, poll) + room.polls.count = room.polls.count + 1; + + local pollData = { + event = event, + room = room, + poll = { + pollId = data.pollId, + senderId = poll_creator.occupant_id, + senderName = poll_creator.occupant_name, + question = data.question, + answers = compact_answers + } } - } - module:context(jid.host(room.jid)):fire_event('poll-created', pollData); - - -- now send message to all participants - data.senderId = poll_creator.occupant_id; - data.type = 'polls'; - 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 - send_polls_message_to_all(room, json_msg_str); - elseif data.command == "answer-poll" then - if check_polls(room) then return end - - local poll = room.polls.by_id[data.pollId]; - if poll == nil then - module:log("warn", "answering inexistent poll"); - return; - end - - local voter = get_occupant_details(occupant) - if not voter then - module:log("error", "Cannot retrieve voter id and name for %s from %s", occupant.jid, room.jid) - return - end - - local answers = {}; - for vote_option_idx, vote_flag in ipairs(data.answers) do - local answer = poll.answers[vote_option_idx] - - table.insert(answers, { - key = vote_option_idx, - value = vote_flag, - name = answer.name, - }); - - if vote_flag then - local voters = answer.voters; - if not voters then - answer.voters = {}; - voters = answer.voters; - end - - table.insert(voters, { - id = voter.occupant_id; - name = vote_flag and voter.occupant_name or nil; - }); + -- now send message to all participants + data.senderId = poll_creator.occupant_id; + data.type = 'polls'; + 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 - end + send_polls_message_to_all(room, json_msg_str); - local answerData = { - event = event, - room = room, - pollId = poll.pollId, - voterName = voter.occupant_name, - voterId = voter.occupant_id, - answers = answers - } - module:context(jid.host(room.jid)):fire_event("answer-poll", answerData); + module:context(jid.host(room.jid)):fire_event('poll-created', pollData); + elseif data.command == "answer-poll" then + if check_polls(room) then return end - data.senderId = voter.occupant_id; - data.type = 'polls'; - 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); + local poll = room.polls.by_id[data.pollId]; + if poll == nil then + module:log("warn", "answering inexistent poll %s", data.pollId); + return; + end + + local voter = occupant_details; + + local answers = {}; + for vote_option_idx, vote_flag in ipairs(data.answers) do + local answer = poll.answers[vote_option_idx] + + table.insert(answers, { + key = vote_option_idx, + value = vote_flag, + name = answer.name, + }); + + if vote_flag then + local voters = answer.voters; + if not voters then + answer.voters = {}; + voters = answer.voters; + end + + table.insert(voters, { + id = voter.occupant_id; + name = vote_flag and voter.occupant_name or nil; + }); + end + end + + local answerData = { + data = data, + event = event, + room = room, + pollId = poll.pollId, + voterName = voter.occupant_name, + voterId = voter.occupant_id, + answers = answers, + } + + data.senderId = voter.occupant_id; + data.senderName = voter.occupant_name; + data.type = 'polls'; + 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); + return; + end + send_polls_message_to_all(room, json_msg_str); + + module:context(jid.host(room.jid)):fire_event('answer-poll', answerData); end - send_polls_message_to_all(room, json_msg_str); - end return true; end); +-- Find in which poll in newPolls we have updated answers +-- @returns poll, senderId, array of boolean values for the answers of this sender +function find_updated_poll(oldPolls, newPolls) + for _, v in pairs(newPolls) do + local existing_poll = oldPolls[v.pollId]; + local senderId; + + for idx, newAnswer in ipairs(v.answers) do + -- let's examine now the voters + -- Create lookup tables using id as key for efficient searching + local oldLookup = {} + local newLookup = {} + + -- Build lookup table for old array + if existing_poll.answers[idx].voters then + for _, element in ipairs(existing_poll.answers[idx].voters) do + oldLookup[element.id] = element + end + end + + -- Build lookup table for new array + if newAnswer.voters then + for _, element in ipairs(newAnswer.voters) do + newLookup[element.id] = element + end + end + + -- Find removed elements (in old but not in new) + if existing_poll.answers[idx].voters then + for _, element in ipairs(existing_poll.answers[idx].voters) do + if not newLookup[element.id] then + senderId = element.id; + end + end + end + + -- Find added elements (in new but not in old) + if newAnswer.voters then + for _, element in ipairs(newAnswer.voters) do + if not oldLookup[element.id] then + senderId = element.id; + end + end + end + end + + if senderId ~= nil then + -- an array of true/false values for this sender + local senderAnswers = {}; + for idx, newAnswer in ipairs(v.answers) do + senderAnswers[idx] = false; + if newAnswer.voters then + for _, element in ipairs(newAnswer.voters) do + if element.id == senderId then + senderAnswers[idx] = true; + end + end + end + end + + return v, senderId, senderAnswers; + end + end +end + local setup_muc_component = function(host_module, host) -- Sets up poll data in new rooms. host_module:hook("muc-room-created", function(event) @@ -329,6 +430,82 @@ local setup_muc_component = function(host_module, host) end send_polls_message(room, json_msg_str, event.occupant.jid); end); + + -- Handles poll updates coming for a visitor node, the event contain polls structure + -- like 'old-polls' one + host_module:hook('jitsi-polls-update', function(event) + local polls_command, room = event.polls, event.room; + -- this is the initial state coming from the main prosody when only jicofo is in the room + if room.polls.count == 0 and it.count(room:each_occupant()) == 1 then + for i, v in ipairs(polls_command.polls) do + room.polls.by_id[v.pollId] = v; + table.insert(room.polls.order, v); + room.polls.count = room.polls.count + 1; + end + + return; + end + + -- at this point we need to find which is the new poll + local new_poll; + for _, v in pairs(polls_command.polls) do + if not room.polls.by_id[v.pollId] then + new_poll = v; + break; + end + end + + if not new_poll then + -- this is an update of the voters in some of the existing polls + local updatedPoll, senderId, answers = find_updated_poll(room.polls.by_id, polls_command.polls); + + if not updatedPoll then + module:log('warn', 'no new or updated poll found in update for room %s', room.jid); + return; + end + + local data = { + answers = answers, + command = 'answer-poll', + pollId = updatedPoll.pollId, + senderId = senderId, + roomJid = internal_room_jid_match_rewrite(room.jid), + type = 'polls' + }; + + -- we need to update the history + room.polls.by_id[updatedPoll.pollId].answers = updatedPoll.answers; + + 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 + send_polls_message_to_all(room, json_msg_str); + + return; + end + + room.polls.by_id[new_poll.pollId] = new_poll; + table.insert(room.polls.order, new_poll); + room.polls.count = room.polls.count + 1; + + local data = { + answers = new_poll.answers, + command = 'new-poll', + pollId = new_poll.pollId, + question = new_poll.question, + senderId = new_poll.senderId, + roomJid = internal_room_jid_match_rewrite(room.jid), + type = 'polls' + }; + + 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 + + send_polls_message_to_all(room, json_msg_str); + end); end process_host_module(muc_domain_prefix..'.'..main_virtual_host, setup_muc_component); diff --git a/resources/prosody-plugins/mod_visitors.lua b/resources/prosody-plugins/mod_visitors.lua index b22e3bbbbf..4d37207f4a 100644 --- a/resources/prosody-plugins/mod_visitors.lua +++ b/resources/prosody-plugins/mod_visitors.lua @@ -94,6 +94,31 @@ local function send_visitors_iq(conference_service, room, type) 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(); @@ -472,6 +497,38 @@ process_host_module(main_muc_component_config, function(host_module, host) 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) diff --git a/resources/prosody-plugins/mod_visitors_component.lua b/resources/prosody-plugins/mod_visitors_component.lua index 9d810bd3ff..1e49884c15 100644 --- a/resources/prosody-plugins/mod_visitors_component.lua +++ b/resources/prosody-plugins/mod_visitors_component.lua @@ -213,6 +213,9 @@ end local function disconnect_vnode_received(room, vnode) module:context(muc_domain_base):fire_event('jitsi-disconnect-vnode', { room = room; vnode = vnode; }); + if not room._connected_vnodes then + return; + end room._connected_vnodes:set(vnode..'.meet.jitsi', nil); if room._connected_vnodes:count() == 0 then