Close room properly (prosody custom module)

Hi @plokta,
You’re code worked like a charm (for web and mobile)! Thank you very much for your effort and sharing :slightly_smiling_face: This will keep our students from messing around when the teacher leaves the room!

Just wondering, is there any option / extra code for timing clearing the room (in case the owner accidentally refreshes the page, or retruns in approx. 30 sec)?

Again, thanks for sharing!
Grtz Serel

I think this would be a good idea. I don’t have a test deployment currently to try this myself but I assume it should be possible to adjust the module and do something like the below

Before actually closing the room

  • sleep for a predefined number of seconds
  • iterate over all occupants and check whether there is a moderator which is not the focus user
    • if no moderator is in the room anymore, proceed to the clear() and destroy()
    • if a moderator is still present, exit without closing

Things to consider:

  • how does the delay in the event propagation (caused by the sleep statement) affect other parts of prosody/jitsi.
  • is a limit necessary to prevent having multiple hooks in the sleep state (e.g., long sleep intervall and moderator with a flaky connection or a not-so-funny moderator who keeps disconnecting and re-entering the room)

I have been experimenting with the sleep function but the problem is that lua has no direct support for an async function and with the sleep going into the stack it keeps the other operations from continuing. I even created a module with an event handler:
local events = require “”;
local myevents =;
local socket = require(“socket”);
local function on_TimeUp(data)
module:log(“info”, “TimesUp”, data);
– Pause for a bit
local socket = require(“socket”);
myevents.fire_event(“timesup”, data);
myevents.add_handler(“times_up”, on_TimeUp);

I am firing the initial call from within:
module:hook(“muc-occupant-left”, function(event)
myevents.fire_event(“timesup”, data); – IT WILL NOT GO TOT HE NEXT LINE TILL ALL OF THIS CODE EXECUTES

THE FUNCTION below will get the job done but there is no ‘timer’ library it looks like for me to access
function DoThis(ms, message, i)
module:log(“info”, message, i);
i = i+1;
if i < 100 then
simulatedDelCallback(ms, message, i);

function simulatedDelCallback(ms, message, i)
require(‘timer’).setTimeout(ms,function() DoThis(ms, message, i);end)

The initial call can be for simulatedDelCallback(ms, message, i); can be done from
module:hook(“muc-occupant-left”, function(event)

So I have almost given up on a server side call and I am looking a client side js function to end call if conditions are met. Ideally the server should do the job but I am kind of stuck.

I didn’t check in detail but the prosody devs have created an util.async to work around such limitations, see
Maybe this can help to delay the room destruction?

So here is a prosody module that will destroy the conference room after 30 seconds if a moderator leaves the room and no other moderator is present (or rejoins before the 30 seconds are up). Prerequisite is of course that authentication is setup and working.

local async = require "util.async";
local timer = require "util.timer"; 

local delay = module:get_option_number("close_room_delay", 30);
local focus_jid = module:get_option_string("focus_user_jid", "");

module:log("info","module loaded");

local async_destroy = async.runner(function (event)
	local leaver = event.occupant.bare_jid;
	local room =;
    local wait, done = async.waiter();

    timer.add_task(delay, function ()
    wait(); -- Wait here until done() is called
    local mods = room:each_affiliation("owner");
    for mod in mods do
        module:log("debug", "owner found: %s", tostring(mod));
 	    if mod ~= leaver and mod ~= focus_jid then
            -- there is still a moderator in this room, dont kick participants
            module:log("info", "still a moderator present: %s", mod);
    module:log("info", "room destroyed.");

module:hook("muc-occupant-left", function(event)
    local barejid = event.occupant.bare_jid;
    local role =;
    module:log("debug", "occupant with role %s left: %s", role, barejid);
    if role == "owner" and barejid ~= "" then

It would probably be a good idea to make a config option out of the delay interval and the focus user’s JID. You’ll need to adjust the focus user to match your configuration, check for the admin user in your prosody config.
EDIT: I updated the code above to make the delay interval and the focus user configurable. To make use of this, add to your prosody config the options:

close_room_delay = 30;  -- seconds until conference is destroyed after last moderator left
focus_user_jid = "focus@meet.jitsi";  -- JID of your focus/jicofo user. Should be same as in 'admins' array 

Does anybody have a good idea how to add the UI redirect posted above by @hkhait if Jitsi is not used in an iFrame?


Hey @serelsnauw ,
I also used this code snippet from @plokta plotka and saved it in /usr/share/jitsi-meet/prosody-plugins/mod_quit.lua

module:hook("muc-occupant-left", function(event)
    local barejid = event.occupant.bare_jid;
    local role =;
    if role == "owner" and barejid ~= "" then
        module:log("info", "kick");;
        --Destroy Room
        module:log("info", "kill");;

I enabled it in /etc/prosody/conf.d/“domain”.cfg.lua in the conference component:

Component "conference.domain" "muc"
    storage = "memory"
    modules_enabled = {
        -- "token_verification";
    admins = { "focus@auth.domain" }
    muc_room_locking = false
    muc_room_default_public_jids = true

In the prosody logs:
“kill” and “kick”
are shown. But the room is not destroyed when the moderator leaves…

Does anyone have an idea?


Make sure to adjust the code by replacing "" with "focus@auth.domain" as in your admins = { } configuration. Or use the updated code from this post and add the config options accordingly.

Can you be more specific about what happens and what you expected to happen? Did you test for p2p conferences only or also with multiple participants? Are there any errors in you prosody error log?

Furthermore, note that the participants will not be redirected to another page unless you implement that in the frontend. This module just kills the prosody conference, so participants will lose connection to each other but still remain on the same website.

Hey thanks for the quick response!

I already did, i just copied the snippet you posted earlier.

Furthermore, note that the participants will not be redirected to another page unless you implement that in the frontend. This module just kills the prosody conference, so participants will lose connection to each other but still remain on the same website.

I just tested with this p2p. I have tested it know with multiple participants and the room is destroyed. I thought the others in the conference would be redirected.
So it works! Thanks again.

Is there a way to redirect the participants when the room is destroyed via prosody!

I trided to setup normal jitsi setup on ununtu but i had problems with token several times, so this time i installed docker jitsi. How can i install “Close room module” on docker system?

And second question, is it possible to show countdown popup to users like “Moderator left room will be closed in XXX seconds”, when moderator left room?

In your docker-jitsi-meet configuration folder (defaults to ~/.jitsi-meet-config/) under the prosody subfolder create a new folder prosody-plugins-custom and in this folder create a new file called mod_close_rooms.lua. Paste the code posted above into this file and save.

In the prosody config conf.d/meet.your-domain.cfg look for the MUC component of your server, probably something like Component "" "muc" and add "close_rooms"; to the modules_enabled array.

Component "" "muc"
    storage = "memory"
    modules_enabled = {

Also add the lines

close_room_delay = 30;  -- seconds until conference is destroyed after last moderator left
focus_user_jid = "";  -- JID of your focus/jicofo user. Should be same as in 'admins' array 

to the prosody config, right below of the modules_enabled array (actually, it should not matter, where in the file these options are placed).

To restart prosody run sudo docker-compose restart prosody from your docker-jitsi-meet folder

This would require additional and probably non-trivial changes in the frontend. Of course, as the code is open source anything is possible if you find someone who implements it :wink:

EDIT: Just noted that I mixed up the configuration folder with the docker-jitsi-meet folder in the first sentence.


Thank you plokta for your answer, but i have also same error as fellow12312

it is closing connection, but not closing conferance room gui/not returning to lobby
only closing p2p connection

I just used the util.timer that you referenced instead of both async and timer to get the job done. It looks like the timer is executing in an async mode, ie:
Function …

timer.add_task(60, function () log(“debug”, “Hello1!”) end);
timer.add_task(30, function () log(“debug”, “Hello2!”) end);
Hello2 will execute first at 30 secs and then Hello1 at 60 secs but both will occur after the calling function has exited so no operations are blocked. Thus I did not see the need for util.async if I am using timer. Please let me know if I am mistaken. And thank you for your code it was very helpful.

1 Like

Great finding! I have to admit that I did not even try to only use timer.add(). After skimming through the prosody documentation I presumed using the async functions was required.

Have you been able to catch any client side events about the destroyed conference, i.e. CONFERENCE_FAILED? I’ve tried using addConferenceListener() as posted by @hkhait above but I never seem to receive any event at all…

I did finish my client side implementation and it works cleanly. Much better that the room.clear() and room.destroy(). I did not need to use the listener. Before I show you how I implemented let me explain what my goal was.

Goal: I wanted to limit my duration of call to a max time and also limit the amount of time we have less than the required number of occupants for a given time. The easiest way to limit was to disconnect every one individually client side.

Implementation: (I will list for someone who is getting mixed up with which code where)

  • In /usr/share/jitsi-meet/ create a file myname_conf.js
  • In /usr/share/jitsi-meet/index.html add this line … -#include virtual="/myname_conf_q.js" … and also add the myname_conf.js to the var critical files (not sure if this last one needs to be done).
  • In myname_conf.js add code to use a timer
    var timer;
    var ms = 60000; // this is one minute
    var occCount = 0;
    var mins = 0;
    … add what ever vars you need
    function startCounting(){…
    min = min +1;
    occCount = APP.conference.membersCount; // this is working

    // Do whatever you want with the time and members count
    // To redirect to home page from here is simple
    setTimeout(startCounting, ms); // the function calls itself before exiting
    // you should be able to APP.conference.addConferenceListener in this file - i have not tired
    timer = setTimeout(startCounting, ms);
  • You do not need to restart anything when you do this just go straight to testing after you save the js file.

Hope this helps.

1 Like

Please see my post above this should help you redirect to home page.

Can someone please tell me why I keep getting this error on my prososdy.log when I try to clear room. Is it because the occupants are all owners and moderators?
timer error Traceback[timer]: /usr/share/jitsi-meet/prosody-plugins/mod_quit.lua:63: attempt to index local ‘stanza’ (a nil value)
/usr/share/jitsi-meet/prosody-plugins/mod_quit.lua:63: in function ‘?’
/usr/lib/prosody/util/events.lua:79: in function </usr/lib/prosody/util/events.lua:75>
(…tail calls…)
/usr/lib/prosody/modules/muc/muc.lib.lua:882: in function ‘clear’
/usr/share/jitsi-meet/prosody-plugins/mod_quit.lua:27: in function ‘delayedCounter’
/usr/share/jitsi-meet/prosody-plugins/mod_quit.lua:25: in function </usr/share/jitsi-meet/prosody-plugins/mod_quit.lua:25>
[C]: in function ‘xpcall’
/usr/lib/prosody/util/timer.lua:39: in function ‘callback’
/usr/lib/prosody/net/server_select.lua:876: in function ‘?’
/usr/lib/prosody/net/server_select.lua:907: in function </usr/lib/prosody/net/server_select.lua:899>
[C]: in function ‘xpcall’
/usr/bin/prosody:80: in function ‘loop’
/usr/bin/prosody:90: in main chunk
[C]: in ?

I tried to add clientside javascript to do the redirection.
I created the file: /usr/share/jitsi-meet/quit_conf.js with the following code:

In index.html I added the script
<script><!--#include virtual="/quit_conf.js" --></script><!-- add quit conference script -->

And I also added it to the var critical files.

Via console.log… I confirmed that the module is loaded.
But I get an error

script loaded

TypeError: H is undefined[conference.js:2512:8](webpack:///conference.js)

addConferenceListener conference.js:2512


Do you have any idea why the “APP.conference.addConferenceListener…”-part is not working?
I destroy the room when the moderator leaves as descriped in Close room properly (prosody custom module) and Close room properly (prosody custom module)


Hi @plokta,
Please let me know is this work on with out enable token base authentication.

You need to wait for APP and APP.conference and some other things to be ready. You cannot add the event listeners as soon as the page loads.

Like this:

if (typeof APP !== "undefined" && 
    typeof APP.conference !== "undefined" &&
   // okay to attach listeners now
  APP.conference.addConferenceListener("conference.failed",function (e)
        case "conference.destroyed":


This solution isn’t linked to authentication. the focus_user_jid user is always added the the conferences behind the scenes even if you are not using the secure domain setup. It’s usually "" If your domain is then your focus user is