feat(reservations) start lobby and set password from reservation (#12215)
* feat(reservations) support enabling lobby and password based on reservations data * Add warning about unhandled use case * feat(lobby) Support automated activation of lobby
This commit is contained in:
parent
90b17046f6
commit
2e6f14f872
|
@ -30,6 +30,8 @@ local jid_bare = require 'util.jid'.bare;
|
|||
local json = require 'util.json';
|
||||
local filters = require 'util.filters';
|
||||
local st = require 'util.stanza';
|
||||
local muc_util = module:require "muc/util";
|
||||
local valid_affiliations = muc_util.valid_affiliations;
|
||||
local MUC_NS = 'http://jabber.org/protocol/muc';
|
||||
local DISCO_INFO_NS = 'http://jabber.org/protocol/disco#info';
|
||||
local DISPLAY_NAME_REQUIRED_FEATURE = 'http://jitsi.org/protocol/lobbyrooms#displayname_required';
|
||||
|
@ -436,8 +438,30 @@ end);
|
|||
|
||||
function handle_create_lobby(event)
|
||||
local room = event.room;
|
||||
|
||||
-- since this is called by backend rather than triggered by UI, we need to handle a few additional things:
|
||||
-- 1. Make sure existing participants are already members or they will get kicked out when set_members_only(true)
|
||||
-- 2. Trigger a 104 (config change) status message so UI state is properly updated for existing users
|
||||
|
||||
-- make sure all existing occupants are members
|
||||
for _, occupant in room:each_occupant() do
|
||||
local affiliation = room:get_affiliation(occupant.bare_jid);
|
||||
if valid_affiliations[affiliation or "none"] < valid_affiliations.member then
|
||||
room:set_affiliation(true, occupant.bare_jid, 'member');
|
||||
end
|
||||
end
|
||||
-- Now it is safe to set the room to members only
|
||||
room:set_members_only(true);
|
||||
attach_lobby_room(room)
|
||||
|
||||
-- Trigger a presence with 104 so existing participants retrieves new muc#roomconfig
|
||||
room:broadcast_message(
|
||||
st.message({ type='groupchat', from=room.jid })
|
||||
:tag('x', { xmlns='http://jabber.org/protocol/muc#user' })
|
||||
:tag('status', { code='104' })
|
||||
);
|
||||
|
||||
-- Attach the lobby room.
|
||||
attach_lobby_room(room);
|
||||
end
|
||||
|
||||
function handle_destroy_lobby(event)
|
||||
|
|
|
@ -0,0 +1,174 @@
|
|||
-- This module allows lobby room to be created even when the main room is empty.
|
||||
-- Without this module, the empty main room will get deleted after grace period
|
||||
-- which triggers lobby room deletion even if there are still people in the lobby.
|
||||
--
|
||||
-- This module should be added to the main virtual host domain.
|
||||
-- It assumes you have properly configured the muc_lobby_rooms module and lobby muc component.
|
||||
--
|
||||
-- To trigger creation of lobby room:
|
||||
-- prosody.events.fire_event("create-persistent-lobby-room", { room = room; });
|
||||
--
|
||||
|
||||
local util = module:require "util";
|
||||
local is_healthcheck_room = util.is_healthcheck_room;
|
||||
local main_muc_component_host = module:get_option_string('main_muc');
|
||||
local lobby_muc_component_host = module:get_option_string('lobby_muc');
|
||||
|
||||
|
||||
if main_muc_component_host == nil then
|
||||
module:log('error', 'main_muc not configured. Cannot proceed.');
|
||||
return;
|
||||
end
|
||||
|
||||
if lobby_muc_component_host == nil then
|
||||
module:log('error', 'lobby not enabled missing lobby_muc config');
|
||||
return;
|
||||
end
|
||||
|
||||
|
||||
-- Helper function to wait till a component is loaded before running the given callback
|
||||
local function run_when_component_loaded(component_host_name, callback)
|
||||
local function trigger_callback()
|
||||
module:log('info', 'Component loaded %s', component_host_name);
|
||||
callback(module:context(component_host_name), component_host_name);
|
||||
end
|
||||
|
||||
if prosody.hosts[component_host_name] == nil then
|
||||
module:log('debug', 'Host %s not yet loaded. Will trigger when it is loaded.', component_host_name);
|
||||
prosody.events.add_handler('host-activated', function (host)
|
||||
if host == component_host_name then
|
||||
trigger_callback();
|
||||
end
|
||||
end);
|
||||
else
|
||||
trigger_callback();
|
||||
end
|
||||
end
|
||||
|
||||
-- Helper function to wait till a component's muc module is loaded before running the given callback
|
||||
local function run_when_muc_module_loaded(component_host_module, component_host_name, callback)
|
||||
local function trigger_callback()
|
||||
module:log('info', 'MUC module loaded for %s', component_host_name);
|
||||
callback(prosody.hosts[component_host_name].modules.muc, component_host_module);
|
||||
end
|
||||
|
||||
if prosody.hosts[component_host_name].modules.muc == nil then
|
||||
module:log('debug', 'MUC module for %s not yet loaded. Will trigger when it is loaded.', component_host_name);
|
||||
prosody.hosts[component_host_name].events.add_handler('module-loaded', function(event)
|
||||
if (event.module == 'muc') then
|
||||
trigger_callback();
|
||||
end
|
||||
end);
|
||||
else
|
||||
trigger_callback()
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local lobby_muc_service;
|
||||
local main_muc_service;
|
||||
local main_muc_module;
|
||||
|
||||
|
||||
-- Helper methods to track rooms that have persistent lobby
|
||||
local function set_persistent_lobby(room)
|
||||
room._data.persist_lobby = true;
|
||||
end
|
||||
|
||||
local function has_persistent_lobby(room)
|
||||
if room._data.persist_lobby == true then
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- Helper method to trigger main room destroy if room is persistent (no auto-delete) and destroy not yet triggered
|
||||
local function trigger_room_destroy(room)
|
||||
if room.get_persistent(room) and room._data.room_destroy_triggered == nil then
|
||||
room._data.room_destroy_triggered = true;
|
||||
main_muc_module:fire_event("muc-room-destroyed", { room = room; });
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- For rooms with persistent lobby, we need to trigger deletion ourselves when both the main room
|
||||
-- and the lobby room are empty. This will be checked each time an occupant leaves the main room
|
||||
-- of if someone drops off the lobby.
|
||||
|
||||
|
||||
-- Handle events on main muc module
|
||||
run_when_component_loaded(main_muc_component_host, function(host_module, host_name)
|
||||
run_when_muc_module_loaded(host_module, host_name, function (main_muc, main_module)
|
||||
main_muc_service = main_muc; -- so it can be accessed from lobby muc event handlers
|
||||
main_muc_module = main_module;
|
||||
|
||||
main_module:hook("muc-occupant-left", function(event)
|
||||
-- Check if room should be destroyed when someone leaves the main room
|
||||
|
||||
local main_room = event.room;
|
||||
if is_healthcheck_room(main_room.jid) or not has_persistent_lobby(main_room) then
|
||||
return;
|
||||
end
|
||||
|
||||
local lobby_room_jid = main_room._data.lobbyroom;
|
||||
|
||||
-- If occupant leaving results in main room being empty, we trigger room destroy if
|
||||
-- a) lobby exists and is not empty
|
||||
-- b) lobby does not exist (possible for lobby to be disabled manually by moderator in meeting)
|
||||
--
|
||||
-- (main room destroy also triggers lobby room destroy in muc_lobby_rooms)
|
||||
if not main_room:has_occupant() then
|
||||
if lobby_room_jid == nil then -- lobby disabled
|
||||
trigger_room_destroy(main_room);
|
||||
else -- lobby exists
|
||||
local lobby_room = lobby_muc_service.get_room_from_jid(lobby_room_jid);
|
||||
if lobby_room and not lobby_room:has_occupant() then
|
||||
trigger_room_destroy(main_room);
|
||||
end
|
||||
end
|
||||
end
|
||||
end);
|
||||
|
||||
end);
|
||||
end);
|
||||
|
||||
|
||||
-- Handle events on lobby muc module
|
||||
run_when_component_loaded(lobby_muc_component_host, function(host_module, host_name)
|
||||
run_when_muc_module_loaded(host_module, host_name, function (lobby_muc, lobby_module)
|
||||
lobby_muc_service = lobby_muc; -- so it can be accessed from main muc event handlers
|
||||
|
||||
lobby_module:hook("muc-occupant-left", function(event)
|
||||
-- Check if room should be destroyed when someone leaves the lobby
|
||||
|
||||
local lobby_room = event.room;
|
||||
local main_room = lobby_room.main_room;
|
||||
|
||||
if is_healthcheck_room(main_room.jid) or not has_persistent_lobby(main_room) then
|
||||
return;
|
||||
end
|
||||
|
||||
-- If both lobby room and main room are empty, we destroy main room.
|
||||
-- (main room destroy also triggers lobby room destroy in muc_lobby_rooms)
|
||||
if not lobby_room:has_occupant() and main_room and not main_room:has_occupant() then
|
||||
trigger_room_destroy(main_room);
|
||||
end
|
||||
|
||||
end);
|
||||
end);
|
||||
end);
|
||||
|
||||
|
||||
function handle_create_persistent_lobby(event)
|
||||
local room = event.room;
|
||||
prosody.events.fire_event("create-lobby-room", { room = room; });
|
||||
|
||||
set_persistent_lobby(room);
|
||||
room:set_persistent(true);
|
||||
end
|
||||
|
||||
|
||||
module:hook_global('create-persistent-lobby-room', handle_create_persistent_lobby);
|
||||
|
|
@ -33,8 +33,15 @@
|
|||
-- returns true if API call should be retried. By default, retries are done for 5XX
|
||||
-- responses. Timeouts are never retried, and HTTP call failures are always retried.
|
||||
-- * set "reservations_enable_max_occupants" to true to enable integration with
|
||||
-- mod_muc_max_occupants. Setting thia will allow optional "max_occupants"
|
||||
-- mod_muc_max_occupants. Setting thia will allow optional "max_occupants" (integer)
|
||||
-- payload from API to influence max occupants allowed for a given room.
|
||||
-- * set "reservations_enable_lobby_support" to true to enable integration
|
||||
-- with "muc_lobby_rooms". Setting this will allow optional "lobby" (boolean)
|
||||
-- fields in API payload. If set to true, Lobby will be enabled for the room.
|
||||
-- "persistent_lobby" module must also be enabled for this to work.
|
||||
-- * set "reservations_enable_password_support" to allow optional "password" (string)
|
||||
-- field in API payload. If set and not empty, then room password will be set
|
||||
-- to the given string.
|
||||
--
|
||||
--
|
||||
-- Example config:
|
||||
|
@ -56,7 +63,10 @@
|
|||
-- return code >= 500 or code == 408
|
||||
-- end
|
||||
--
|
||||
|
||||
-- reservations_enable_max_occupants = true -- support "max_occupants" field
|
||||
-- reservations_enable_lobby_support = true -- support "lobby" field
|
||||
-- reservations_enable_password_support = true -- support "password" field
|
||||
--
|
||||
|
||||
local jid = require 'util.jid';
|
||||
local http = require "net.http";
|
||||
|
@ -75,6 +85,8 @@ local api_timeout = module:get_option("reservations_api_timeout", 20);
|
|||
local api_retry_count = tonumber(module:get_option("reservations_api_retry_count", 3));
|
||||
local api_retry_delay = tonumber(module:get_option("reservations_api_retry_delay", 3));
|
||||
local max_occupants_enabled = module:get_option("reservations_enable_max_occupants", false);
|
||||
local lobby_support_enabled = module:get_option("reservations_enable_lobby_support", false);
|
||||
local password_support_enabled = module:get_option("reservations_enable_password_support", false);
|
||||
|
||||
|
||||
-- Option for user to control HTTP response codes that will result in a retry.
|
||||
|
@ -248,7 +260,7 @@ function RoomReservation:enqueue_or_route_event(event)
|
|||
end
|
||||
|
||||
--- Updates status and initiates event routing. Called internally when API call complete.
|
||||
function RoomReservation:set_status_success(start_time, duration, mail_owner, conflict_id, max_occupants)
|
||||
function RoomReservation:set_status_success(start_time, duration, mail_owner, conflict_id, data)
|
||||
module:log("info", "Reservation created successfully for %s", self.room_jid);
|
||||
self.meta = {
|
||||
status = STATUS.SUCCESS;
|
||||
|
@ -259,8 +271,14 @@ function RoomReservation:set_status_success(start_time, duration, mail_owner, co
|
|||
error_text = nil;
|
||||
error_code = nil;
|
||||
}
|
||||
if max_occupants_enabled and max_occupants then
|
||||
self.meta.max_occupants = max_occupants
|
||||
if max_occupants_enabled and data.max_occupants then
|
||||
self.meta.max_occupants = data.max_occupants
|
||||
end
|
||||
if lobby_support_enabled and data.lobby then
|
||||
self.meta.lobby = data.lobby
|
||||
end
|
||||
if password_support_enabled and data.password then
|
||||
self.meta.password = data.password
|
||||
end
|
||||
self:route_pending_events()
|
||||
end
|
||||
|
@ -400,7 +418,7 @@ function RoomReservation:parse_conference_response(response_body)
|
|||
end
|
||||
data.duration = duration;
|
||||
|
||||
-- if optional max_occupants field set, cast to number
|
||||
-- if optional "max_occupants" field set, cast to number
|
||||
if data.max_occupants ~= nil then
|
||||
local max_occupants = tonumber(data.max_occupants)
|
||||
if max_occupants == nil or max_occupants < 1 then
|
||||
|
@ -411,6 +429,24 @@ function RoomReservation:parse_conference_response(response_body)
|
|||
data.max_occupants = max_occupants
|
||||
end
|
||||
|
||||
-- if optional "lobby" field set, accept boolean true or "true"
|
||||
if data.lobby ~= nil then
|
||||
if (type(data.lobby) == "boolean" and data.lobby) or data.lobby == "true" then
|
||||
data.lobby = true
|
||||
else
|
||||
data.lobby = false
|
||||
end
|
||||
end
|
||||
|
||||
-- if optional "password" field set, it has to be string
|
||||
if data.password ~= nil then
|
||||
if type(data.password) ~= "string" then
|
||||
-- N.B. invalid "password" rejected even if reservations_enable_password_support=false
|
||||
module:log("error", "Invalid type for password - string expected");
|
||||
return;
|
||||
end
|
||||
end
|
||||
|
||||
local start_time = datetime.parse(data.start_time); -- N.B. we lose milliseconds portion of the date
|
||||
if start_time == nil then
|
||||
module:log("error", "Missing or invalid start_time - %s", data.start_time);
|
||||
|
@ -458,7 +494,7 @@ function RoomReservation:handler_conference_data_returned_from_api(response_body
|
|||
module:log("error", "API returned success code but invalid payload");
|
||||
self:set_status_failed(500, 'Invalid response from reservation server');
|
||||
else
|
||||
self:set_status_success(data.start_time, data.duration, data.mail_owner, data.id, data.max_occupants)
|
||||
self:set_status_success(data.start_time, data.duration, data.mail_owner, data.id, data)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -591,30 +627,58 @@ local function room_destroyed(event)
|
|||
end
|
||||
end
|
||||
|
||||
--- If max_occupants_enabled, update room max_occupants if returned by API
|
||||
|
||||
local function room_created(event)
|
||||
local res;
|
||||
local room = event.room
|
||||
|
||||
if max_occupants_enabled and not is_healthcheck_room(room.jid) then
|
||||
res = reservations[room.jid]
|
||||
if is_healthcheck_room(room.jid) then
|
||||
return;
|
||||
end
|
||||
|
||||
if res and res.meta.max_occupants ~= nil then
|
||||
module:log("info", "Setting max_occupants %d for room %s", res.meta.max_occupants, room.jid);
|
||||
room._data.max_occupants = res.meta.max_occupants
|
||||
end
|
||||
local res = reservations[room.jid]
|
||||
|
||||
if res and max_occupants_enabled and res.meta.max_occupants ~= nil then
|
||||
module:log("info", "Setting max_occupants %d for room %s", res.meta.max_occupants, room.jid);
|
||||
room._data.max_occupants = res.meta.max_occupants
|
||||
end
|
||||
|
||||
if res and password_support_enabled and res.meta.password ~= nil then
|
||||
module:log("info", "Setting password for room %s", room.jid);
|
||||
room:set_password(res.meta.password);
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local function room_pre_create(event)
|
||||
local room = event.room
|
||||
|
||||
if is_healthcheck_room(room.jid) then
|
||||
return;
|
||||
end
|
||||
|
||||
local res = reservations[room.jid]
|
||||
|
||||
if res and lobby_support_enabled and res.meta.lobby then
|
||||
module:log("info", "Enabling lobby for room %s", room.jid);
|
||||
prosody.events.fire_event("create-persistent-lobby-room", { room = room; });
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function process_host(host)
|
||||
if host == muc_component_host then -- the conference muc component
|
||||
module:log("info", "Hook to muc-room-destroyed on %s", host);
|
||||
module:context(host):hook("muc-room-destroyed", room_destroyed, -1);
|
||||
|
||||
if max_occupants_enabled then
|
||||
module:log("info", "Hook to muc-room-created on %s (mod_muc_max_occupants integration enabled)", host);
|
||||
if max_occupants_enabled or password_support_enabled then
|
||||
module:log("info", "Hook to muc-room-created on %s (max_occupants or password integration enabled)", host);
|
||||
module:context(host):hook("muc-room-created", room_created);
|
||||
end
|
||||
|
||||
if lobby_support_enabled then
|
||||
module:log("info", "Hook to muc-room-pre-create on %s (lobby integration enabled)", host);
|
||||
module:context(host):hook("muc-room-pre-create", room_pre_create);
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
Loading…
Reference in New Issue