"Fossies" - the Fresh Open Source Software Archive 
Member "jitsi-meet-7329/resources/prosody-plugins/mod_reservations.lua" (9 Jun 2023, 27933 Bytes) of package /linux/misc/jitsi-meet-7329.tar.gz:
As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Lua source code syntax highlighting (style:
standard) with prefixed line numbers and
code folding option.
Alternatively you can here
view or
download the uninterpreted source code file.
1 --- This is a port of Jicofo's Reservation System as a prosody module
2 -- ref: https://github.com/jitsi/jicofo/blob/master/doc/reservation.md
3 --
4 -- We try to retain the same behaviour and interfaces where possible, but there
5 -- is some difference:
6 -- * In the event that the DELETE call fails, Jicofo's reservation
7 -- system retains reservation data and allows re-creation of room if requested by
8 -- the same creator without making further call to the API; this module does not
9 -- offer this behaviour. Re-creation of a closed room will behave like a new meeting
10 -- and trigger a new API call to validate the reservation.
11 -- * Jicofo's reservation system expect int-based conflict_id. We take any sensible string.
12 --
13 -- In broad strokes, this module works by intercepting Conference IQs sent to focus component
14 -- and buffers it until reservation is confirmed (by calling the provided API endpoint).
15 -- The IQ events are routed on to focus component if reservation is valid, or error
16 -- response is sent back to the origin if reservation is denied. Events are routed as usual
17 -- if the room already exists.
18 --
19 --
20 -- Installation:
21 -- =============
22 --
23 -- Under domain config,
24 -- 1. add "reservations" to modules_enabled.
25 -- 2. Specify URL base for your API endpoint using "reservations_api_prefix" (required)
26 -- 3. Optional config:
27 -- * set "reservations_api_timeout" to change API call timeouts (defaults to 20 seconds)
28 -- * set "reservations_api_headers" to specify custom HTTP headers included in
29 -- all API calls e.g. to provide auth tokens.
30 -- * set "reservations_api_retry_count" to the number of times API call failures are retried (defaults to 3)
31 -- * set "reservations_api_retry_delay" seconds to wait between retries (defaults to 3s)
32 -- * set "reservations_api_should_retry_for_code" to a function that takes an HTTP response code and
33 -- returns true if API call should be retried. By default, retries are done for 5XX
34 -- responses. Timeouts are never retried, and HTTP call failures are always retried.
35 -- * set "reservations_enable_max_occupants" to true to enable integration with
36 -- mod_muc_max_occupants. Setting thia will allow optional "max_occupants" (integer)
37 -- payload from API to influence max occupants allowed for a given room.
38 -- * set "reservations_enable_lobby_support" to true to enable integration
39 -- with "muc_lobby_rooms". Setting this will allow optional "lobby" (boolean)
40 -- fields in API payload. If set to true, Lobby will be enabled for the room.
41 -- "persistent_lobby" module must also be enabled for this to work.
42 -- * set "reservations_enable_password_support" to allow optional "password" (string)
43 -- field in API payload. If set and not empty, then room password will be set
44 -- to the given string.
45 -- * By default, reservation checks are skipped for breakout rooms. You can subject
46 -- breakout rooms to the same checks by setting "reservations_skip_breakout_rooms" to false.
47 --
48 --
49 -- Example config:
50 --
51 -- VirtualHost "jitmeet.example.com"
52 -- modules_enabled = {
53 -- "reservations";
54 -- }
55 -- reservations_api_prefix = "http://reservation.example.com"
56 --
57 -- --- The following are all optional
58 -- reservations_api_headers = {
59 -- ["Authorization"] = "Bearer TOKEN-237958623045";
60 -- }
61 -- reservations_api_timeout = 10 -- timeout if API does not respond within 10s
62 -- reservations_api_retry_count = 5 -- retry up to 5 times
63 -- reservations_api_retry_delay = 1 -- wait 1s between retries
64 -- reservations_api_should_retry_for_code = function (code)
65 -- return code >= 500 or code == 408
66 -- end
67 --
68 -- reservations_enable_max_occupants = true -- support "max_occupants" field
69 -- reservations_enable_lobby_support = true -- support "lobby" field
70 -- reservations_enable_password_support = true -- support "password" field
71 --
72
73 local jid = require 'util.jid';
74 local http = require "net.http";
75 local json = require "util.json";
76 local st = require "util.stanza";
77 local timer = require 'util.timer';
78 local datetime = require 'util.datetime';
79
80 local get_room_from_jid = module:require "util".get_room_from_jid;
81 local is_healthcheck_room = module:require "util".is_healthcheck_room;
82 local room_jid_match_rewrite = module:require "util".room_jid_match_rewrite;
83
84 local api_prefix = module:get_option("reservations_api_prefix");
85 local api_headers = module:get_option("reservations_api_headers");
86 local api_timeout = module:get_option("reservations_api_timeout", 20);
87 local api_retry_count = tonumber(module:get_option("reservations_api_retry_count", 3));
88 local api_retry_delay = tonumber(module:get_option("reservations_api_retry_delay", 3));
89 local max_occupants_enabled = module:get_option("reservations_enable_max_occupants", false);
90 local lobby_support_enabled = module:get_option("reservations_enable_lobby_support", false);
91 local password_support_enabled = module:get_option("reservations_enable_password_support", false);
92 local skip_breakout_room = module:get_option("reservations_skip_breakout_rooms", true);
93
94
95 -- Option for user to control HTTP response codes that will result in a retry.
96 -- Defaults to returning true on any 5XX code or 0
97 local api_should_retry_for_code = module:get_option("reservations_api_should_retry_for_code", function (code)
98 return code >= 500;
99 end)
100
101
102 local muc_component_host = module:get_option_string("main_muc");
103 local breakout_muc_component_host = module:get_option_string('breakout_rooms_muc', 'breakout.'..module.host);
104
105
106 -- How often to check and evict expired reservation data
107 local expiry_check_period = 60;
108
109
110 -- Cannot proceed if "reservations_api_prefix" not configured
111 if not api_prefix then
112 module:log("error", "reservations_api_prefix not specified. Disabling %s", module:get_name());
113 return;
114 end
115
116
117 -- get/infer focus component hostname so we can intercept IQ bound for it
118 local focus_component_host = module:get_option_string("focus_component");
119 if not focus_component_host then
120 local muc_domain_base = module:get_option_string("muc_mapper_domain_base");
121 if not muc_domain_base then
122 module:log("error", "Could not infer focus domain. Disabling %s", module:get_name());
123 return;
124 end
125 focus_component_host = 'focus.'..muc_domain_base;
126 end
127
128 -- common HTTP headers added to all API calls
129 local http_headers = {
130 ["User-Agent"] = "Prosody ("..prosody.version.."; "..prosody.platform..")";
131 };
132 if api_headers then -- extra headers from config
133 for key, value in pairs(api_headers) do
134 http_headers[key] = value;
135 end
136 end
137
138
139 --- Utils
140
141 --- Converts int timestamp to datetime string compatible with Java SimpleDateFormat
142 -- @param t timestamps in seconds. Supports int (as returned by os.time()) or higher
143 -- precision (as returned by socket.gettime())
144 -- @return formatted datetime string (yyyy-MM-dd'T'HH:mm:ss.SSSX)
145 local function to_java_date_string(t)
146 local t_secs, mantissa = math.modf(t);
147 local ms_str = (mantissa == 0) and '.000' or tostring(mantissa):sub(2,5);
148 local date_str = os.date("!%Y-%m-%dT%H:%M:%S", t_secs);
149 return date_str..ms_str..'Z';
150 end
151
152
153 --- Start non-blocking HTTP call
154 -- @param url URL to call
155 -- @param options options table as expected by net.http where we provide optional headers, body or method.
156 -- @param callback if provided, called with callback(response_body, response_code) when call complete.
157 -- @param timeout_callback if provided, called without args when request times out.
158 -- @param retries how many times to retry on failure; 0 means no retries.
159 local function async_http_request(url, options, callback, timeout_callback, retries)
160 local completed = false;
161 local timed_out = false;
162 local retries = retries or api_retry_count;
163
164 local function cb_(response_body, response_code)
165 if not timed_out then -- request completed before timeout
166 completed = true;
167 if (response_code == 0 or api_should_retry_for_code(response_code)) and retries > 0 then
168 module:log("warn", "API Response code %d. Will retry after %ds", response_code, api_retry_delay);
169 timer.add_task(api_retry_delay, function()
170 async_http_request(url, options, callback, timeout_callback, retries - 1)
171 end)
172 return;
173 end
174
175 if callback then
176 callback(response_body, response_code)
177 end
178 end
179 end
180
181 local request = http.request(url, options, cb_);
182
183 timer.add_task(api_timeout, function ()
184 timed_out = true;
185
186 if not completed then
187 http.destroy_request(request);
188 if timeout_callback then
189 timeout_callback()
190 end
191 end
192 end);
193
194 end
195
196 --- Returns current timestamp
197 local function now()
198 -- Don't really need higher precision of socket.gettime(). Besides, we loose
199 -- milliseconds precision when converting back to timestamp from date string
200 -- when we use datetime.parse(t), so let's be consistent.
201 return os.time();
202 end
203
204 --- Start RoomReservation implementation
205
206 -- Status enums used in RoomReservation:meta.status
207 local STATUS = {
208 PENDING = 0;
209 SUCCESS = 1;
210 FAILED = -1;
211 }
212
213 local RoomReservation = {};
214 RoomReservation.__index = RoomReservation;
215
216 function newRoomReservation(room_jid, creator_jid)
217 return setmetatable({
218 room_jid = room_jid;
219
220 -- Reservation metadata. store as table so we can set and read atomically.
221 -- N.B. This should always be updated using self.set_status_*
222 meta = {
223 status = STATUS.PENDING;
224 mail_owner = jid.bare(creator_jid);
225 conflict_id = nil;
226 start_time = now(); -- timestamp, in seconds
227 expires_at = nil; -- timestamp, in seconds
228 error_text = nil;
229 error_code = nil;
230 };
231
232 -- Array of pending events that we need to route once API call is complete
233 pending_events = {};
234
235 -- Set true when API call trigger has been triggered (by enqueue of first event)
236 api_call_triggered = false;
237 }, RoomReservation);
238 end
239
240
241 --- Extracts room name from room jid
242 function RoomReservation:get_room_name()
243 return jid.node(self.room_jid);
244 end
245
246 --- Checks if reservation data is expires and should be evicted from store
247 function RoomReservation:is_expired()
248 return self.meta.expires_at ~= nil and now() > self.meta.expires_at;
249 end
250
251 --- Main entry point for handing and routing events.
252 function RoomReservation:enqueue_or_route_event(event)
253 if self.meta.status == STATUS.PENDING then
254 table.insert(self.pending_events, event)
255 if self.api_call_triggered ~= true then
256 self:call_api_create_conference();
257 end
258 else
259 -- API call already complete. Immediately route without enqueueing.
260 -- This could happen if request comes in between the time reservation approved
261 -- and when Jicofo actually creates the room.
262 module:log("debug", "Reservation details already stored. Skipping queue for %s", self.room_jid);
263 self:route_event(event);
264 end
265 end
266
267 --- Updates status and initiates event routing. Called internally when API call complete.
268 function RoomReservation:set_status_success(start_time, duration, mail_owner, conflict_id, data)
269 module:log("info", "Reservation created successfully for %s", self.room_jid);
270 self.meta = {
271 status = STATUS.SUCCESS;
272 mail_owner = mail_owner or self.meta.mail_owner;
273 conflict_id = conflict_id;
274 start_time = start_time;
275 expires_at = start_time + duration;
276 error_text = nil;
277 error_code = nil;
278 }
279 if max_occupants_enabled and data.max_occupants then
280 self.meta.max_occupants = data.max_occupants
281 end
282 if lobby_support_enabled and data.lobby then
283 self.meta.lobby = data.lobby
284 end
285 if password_support_enabled and data.password then
286 self.meta.password = data.password
287 end
288 self:route_pending_events()
289 end
290
291 --- Updates status and initiates error response to pending events. Called internally when API call complete.
292 function RoomReservation:set_status_failed(error_code, error_text)
293 module:log("info", "Reservation creation failed for %s - (%s) %s", self.room_jid, error_code, error_text);
294 self.meta = {
295 status = STATUS.FAILED;
296 mail_owner = self.meta.mail_owner;
297 conflict_id = nil;
298 start_time = self.meta.start_time;
299 -- Retain reservation rejection for a short while so we have time to report failure to
300 -- existing clients and not trigger a re-query too soon.
301 -- N.B. Expiry could take longer since eviction happens periodically.
302 expires_at = now() + 30;
303 error_text = error_text;
304 error_code = error_code;
305 }
306 self:route_pending_events()
307 end
308
309 --- Triggers routing of all enqueued events
310 function RoomReservation:route_pending_events()
311 if self.meta.status == STATUS.PENDING then -- should never be called while PENDING. check just in case.
312 return;
313 end
314
315 module:log("debug", "Routing all pending events for %s", self.room_jid);
316 local event;
317
318 while #self.pending_events ~= 0 do
319 event = table.remove(self.pending_events);
320 self:route_event(event)
321 end
322 end
323
324 --- Event routing implementation
325 function RoomReservation:route_event(event)
326 -- this should only be called after API call complete and status no longer PENDING
327 assert(self.meta.status ~= STATUS.PENDING, "Attempting to route event while API call still PENDING")
328
329 local meta = self.meta;
330 local origin, stanza = event.origin, event.stanza;
331
332 if meta.status == STATUS.FAILED then
333 module:log("debug", "Route: Sending reservation error to %s", stanza.attr.from);
334 self:reply_with_error(event, meta.error_code, meta.error_text);
335 else
336 if meta.status == STATUS.SUCCESS then
337 if self:is_expired() then
338 module:log("debug", "Route: Sending reservation expiry to %s", stanza.attr.from);
339 self:reply_with_error(event, 419, "Reservation expired");
340 else
341 module:log("debug", "Route: Forwarding on event from %s", stanza.attr.from);
342 prosody.core_post_stanza(origin, stanza, false); -- route iq to intended target (focus)
343 end
344 else
345 -- this should never happen unless dev made a mistake. Block by default just in case.
346 module:log("error", "Reservation for %s has invalid state %s. Rejecting request.", self.room_jid, meta.status);
347 self:reply_with_error(event, 500, "Failed to determine reservation state");
348 end
349 end
350 end
351
352 --- Generates reservation-error stanza and sends to event origin.
353 function RoomReservation:reply_with_error(event, error_code, error_text)
354 local stanza = event.stanza;
355 local id = stanza.attr.id;
356 local to = stanza.attr.from;
357 local from = stanza.attr.to;
358
359 event.origin.send(
360 st.iq({ type="error", to=to, from=from, id=id })
361 :tag("error", { type="cancel" })
362 :tag("service-unavailable", { xmlns="urn:ietf:params:xml:ns:xmpp-stanzas" }):up()
363 :tag("text", { xmlns="urn:ietf:params:xml:ns:xmpp-stanzas" }):text(error_text):up()
364 :tag("reservation-error", { xmlns="http://jitsi.org/protocol/focus", ["error-code"]=tostring(error_code) })
365 );
366 end
367
368 --- Initiates non-blocking API call to validate reservation
369 function RoomReservation:call_api_create_conference()
370 self.api_call_triggered = true;
371
372 local url = api_prefix..'/conference';
373 local request_data = {
374 name = self:get_room_name();
375 start_time = to_java_date_string(self.meta.start_time);
376 mail_owner = self.meta.mail_owner;
377 }
378
379 local http_options = {
380 body = http.formencode(request_data); -- because Jicofo reservation encodes as form data instead JSON
381 method = 'POST';
382 headers = http_headers;
383 }
384
385 module:log("debug", "Sending POST /conference for %s", self.room_jid);
386 async_http_request(url, http_options, function (response_body, response_code)
387 self:on_api_create_conference_complete(response_body, response_code);
388 end, function ()
389 self:on_api_call_timeout();
390 end);
391 end
392
393 --- Parses and validates HTTP response body for conference payload
394 -- Ref: https://github.com/jitsi/jicofo/blob/master/doc/reservation.md
395 -- @return nil if invalid, or table with payload parsed from JSON response
396 function RoomReservation:parse_conference_response(response_body)
397 local data = json.decode(response_body);
398
399 if data == nil then -- invalid JSON payload
400 module:log("error", "Invalid JSON response from API - %s", response_body);
401 return;
402 end
403
404 if data.name == nil or data.name:lower() ~= self:get_room_name() then
405 module:log("error", "Missing or mismatching room name - %s", data.name);
406 return;
407 end
408
409 if data.id == nil then
410 module:log("error", "Missing id");
411 return;
412 end
413
414 if data.mail_owner == nil then
415 module:log("error", "Missing mail_owner");
416 return;
417 end
418
419 local duration = tonumber(data.duration);
420 if duration == nil then
421 module:log("error", "Missing or invalid duration - %s", data.duration);
422 return;
423 end
424 data.duration = duration;
425
426 -- if optional "max_occupants" field set, cast to number
427 if data.max_occupants ~= nil then
428 local max_occupants = tonumber(data.max_occupants)
429 if max_occupants == nil or max_occupants < 1 then
430 -- N.B. invalid max_occupants rejected even if max_occupants_enabled=false
431 module:log("error", "Invalid value for max_occupants - %s", data.max_occupants);
432 return;
433 end
434 data.max_occupants = max_occupants
435 end
436
437 -- if optional "lobby" field set, accept boolean true or "true"
438 if data.lobby ~= nil then
439 if (type(data.lobby) == "boolean" and data.lobby) or data.lobby == "true" then
440 data.lobby = true
441 else
442 data.lobby = false
443 end
444 end
445
446 -- if optional "password" field set, it has to be string
447 if data.password ~= nil then
448 if type(data.password) ~= "string" then
449 -- N.B. invalid "password" rejected even if reservations_enable_password_support=false
450 module:log("error", "Invalid type for password - string expected");
451 return;
452 end
453 end
454
455 local start_time = datetime.parse(data.start_time); -- N.B. we lose milliseconds portion of the date
456 if start_time == nil then
457 module:log("error", "Missing or invalid start_time - %s", data.start_time);
458 return;
459 end
460 data.start_time = start_time;
461
462 return data;
463 end
464
465 --- Parses and validates HTTP error response body for API call.
466 -- Expect JSON with a "message" field.
467 -- @return message string, or generic error message if invalid payload.
468 function RoomReservation:parse_error_message_from_response(response_body)
469 local data = json.decode(response_body);
470 if data ~= nil and data.message ~= nil then
471 module:log("debug", "Invalid error response body. Will use generic error message.");
472 return data.message;
473 else
474 return "Rejected by reservation server";
475 end
476 end
477
478 --- callback on API timeout
479 function RoomReservation:on_api_call_timeout()
480 self:set_status_failed(500, 'Reservation lookup timed out');
481 end
482
483 --- callback on API response
484 function RoomReservation:on_api_create_conference_complete(response_body, response_code)
485 if response_code == 200 or response_code == 201 then
486 self:handler_conference_data_returned_from_api(response_body);
487 elseif response_code == 409 then
488 self:handle_conference_already_exist(response_body);
489 elseif response_code == nil then -- warrants a retry, but this should be done automatically by the http call method.
490 self:set_status_failed(500, 'Could not contact reservation server');
491 else
492 self:set_status_failed(response_code, self:parse_error_message_from_response(response_body));
493 end
494 end
495
496 function RoomReservation:handler_conference_data_returned_from_api(response_body)
497 local data = self:parse_conference_response(response_body);
498 if not data then -- invalid response from API
499 module:log("error", "API returned success code but invalid payload");
500 self:set_status_failed(500, 'Invalid response from reservation server');
501 else
502 self:set_status_success(data.start_time, data.duration, data.mail_owner, data.id, data)
503 end
504 end
505
506 function RoomReservation:handle_conference_already_exist(response_body)
507 local data = json.decode(response_body);
508 if data == nil or data.conflict_id == nil then
509 -- yes, in the case of 409, API expected to return "id" as "conflict_id".
510 self:set_status_failed(409, 'Invalid response from reservation server');
511 else
512 local url = api_prefix..'/conference/'..data.conflict_id;
513 local http_options = {
514 method = 'GET';
515 headers = http_headers;
516 }
517
518 async_http_request(url, http_options, function(response_body, response_code)
519 if response_code == 200 then
520 self:handler_conference_data_returned_from_api(response_body);
521 else
522 self:set_status_failed(response_code, self:parse_error_message_from_response(response_body));
523 end
524 end, function ()
525 self:on_api_call_timeout();
526 end);
527 end
528 end
529
530 --- End RoomReservation
531
532 --- Store reservations lookups that are still pending or with room still active
533 local reservations = {}
534
535 local function get_or_create_reservations(room_jid, creator_jid)
536 if reservations[room_jid] == nil then
537 module:log("debug", "Creating new reservation data for %s", room_jid);
538 reservations[room_jid] = newRoomReservation(room_jid, creator_jid);
539 end
540
541 return reservations[room_jid];
542 end
543
544 local function evict_expired_reservations()
545 local expired = {}
546
547 -- first, gather jids of expired rooms. So we don't remove from table while iterating.
548 for room_jid, res in pairs(reservations) do
549 if res:is_expired() then
550 table.insert(expired, room_jid);
551 end
552 end
553
554 local room;
555 for _, room_jid in ipairs(expired) do
556 room = get_room_from_jid(room_jid);
557 if room then
558 -- Close room if still active (reservation duration exceeded)
559 module:log("info", "Room exceeded reservation duration. Terminating %s", room_jid);
560 room:destroy(nil, "Scheduled conference duration exceeded.");
561 -- Rely on room_destroyed to calls DELETE /conference and drops reservation[room_jid]
562 else
563 module:log("error", "Reservation references expired room that is no longer active. Dropping %s", room_jid);
564 -- This should not happen unless evict_expired_reservations somehow gets triggered
565 -- between the time room is destroyed and room_destroyed callback is called. (Possible?)
566 -- But just in case, we drop the reservation to avoid repeating this path on every pass.
567 reservations[room_jid] = nil;
568 end
569 end
570 end
571
572 timer.add_task(expiry_check_period, function()
573 evict_expired_reservations();
574 return expiry_check_period;
575 end)
576
577
578 --- Intercept conference IQ to Jicofo handle reservation checks before allowing normal event flow
579 module:log("info", "Hook to global pre-iq/host");
580 module:hook("pre-iq/host", function(event)
581 local stanza = event.stanza;
582
583 if stanza.name ~= "iq" or stanza.attr.to ~= focus_component_host or stanza.attr.type ~= 'set' then
584 return; -- not IQ for jicofo. Ignore this event.
585 end
586
587 local conference = stanza:get_child('conference', 'http://jitsi.org/protocol/focus');
588 if conference == nil then
589 return; -- not Conference IQ. Ignore.
590 end
591
592 local room_jid = room_jid_match_rewrite(conference.attr.room);
593
594 if get_room_from_jid(room_jid) ~= nil then
595 module:log("debug", "Skip reservation check for existing room %s", room_jid);
596 return; -- room already exists. Continue with normal flow
597 end
598
599 if skip_breakout_room then
600 local _, host = jid.split(room_jid);
601 if host == breakout_muc_component_host then
602 module:log("debug", "Skip reservation check for breakout room %s", room_jid);
603 return;
604 end
605 end
606
607 local res = get_or_create_reservations(room_jid, stanza.attr.from);
608 res:enqueue_or_route_event(event); -- hand over to reservation obj to route event
609 return true;
610
611 end);
612
613
614 --- Forget reservation details once room destroyed so query is repeated if room re-created
615 local function room_destroyed(event)
616 local res;
617 local room = event.room
618
619 if not is_healthcheck_room(room.jid) then
620 res = reservations[room.jid]
621
622 -- drop reservation data for this room
623 reservations[room.jid] = nil
624
625 if res then -- just in case event triggered more than once?
626 module:log("info", "Dropped reservation data for destroyed room %s", room.jid);
627
628 local conflict_id = res.meta.conflict_id
629 if conflict_id then
630 local url = api_prefix..'/conference/'..conflict_id;
631 local http_options = {
632 method = 'DELETE';
633 headers = http_headers;
634 }
635
636 module:log("debug", "Sending DELETE /conference/%s", conflict_id);
637 async_http_request(url, http_options);
638 end
639 end
640 end
641 end
642
643
644 local function room_created(event)
645 local room = event.room
646
647 if is_healthcheck_room(room.jid) then
648 return;
649 end
650
651 local res = reservations[room.jid]
652
653 if res and max_occupants_enabled and res.meta.max_occupants ~= nil then
654 module:log("info", "Setting max_occupants %d for room %s", res.meta.max_occupants, room.jid);
655 room._data.max_occupants = res.meta.max_occupants
656 end
657
658 if res and password_support_enabled and res.meta.password ~= nil then
659 module:log("info", "Setting password for room %s", room.jid);
660 room:set_password(res.meta.password);
661 end
662 end
663
664
665 local function room_pre_create(event)
666 local room = event.room
667
668 if is_healthcheck_room(room.jid) then
669 return;
670 end
671
672 local res = reservations[room.jid]
673
674 if res and lobby_support_enabled and res.meta.lobby then
675 module:log("info", "Enabling lobby for room %s", room.jid);
676 prosody.events.fire_event("create-persistent-lobby-room", { room = room; });
677 end
678 end
679
680
681 function process_host(host)
682 if host == muc_component_host then -- the conference muc component
683 module:log("info", "Hook to muc-room-destroyed on %s", host);
684 module:context(host):hook("muc-room-destroyed", room_destroyed, -1);
685
686 if max_occupants_enabled or password_support_enabled then
687 module:log("info", "Hook to muc-room-created on %s (max_occupants or password integration enabled)", host);
688 module:context(host):hook("muc-room-created", room_created);
689 end
690
691 if lobby_support_enabled then
692 module:log("info", "Hook to muc-room-pre-create on %s (lobby integration enabled)", host);
693 module:context(host):hook("muc-room-pre-create", room_pre_create);
694 end
695 end
696 end
697
698 if prosody.hosts[muc_component_host] == nil then
699 module:log("info", "No muc component found, will listen for it: %s", muc_component_host)
700 prosody.events.add_handler("host-activated", process_host);
701 else
702 process_host(muc_component_host);
703 end