End call using XMPP Presence and IQ stanzas

Hello!

I’m working on an interactive installation based on Jitsi and i need to be able to gracefully end call for all participants, from the server end.

I’m connecting to prosody as jicofo / focus@auth.domain user who is admin and per XMPP docs XEP-0045: Multi-User Chat attempting to destroy my call/room call@conference.domain, which according to jicofo.log seems to somewhat succeed:

DEBUG SEND: <iq id="dc440fb6-822d-4621-bbb5-82057d62b1ef-5" type="set" to="call@conference.domain" from="focus@auth.domain/vb72kv_J"><query xmlns="http://jabber.org/protocol/muc#owner"><destroy /></query></iq>

DEBUG RECV: <iq to="focus@auth.domain/vb72kv_J" id="dc440fb6-822d-4621-bbb5-82057d62b1ef-5" from="call@conference.domain" type="result" />

I also receive this Presence update which seems to indicate that (my only in this case) used was removed

DEBUG RECV: <presence to="focus@auth.domain/vb72kv_J" type="unavailable" from="call@conference.domain/focus@auth.domain"><x xmlns="http://jabber.org/protocol/muc#user"><destroy /><item jid="focus@auth.domain/vb72kv_J" role="none" affiliation="owner" /><status code="110" /></x></presence>

However, each client who was in the call receives “Call terminated” message and their streams stop but the call itself doesn’t seem to actually end (e.g. if a client reconnects to that “terminated” call, the call duration indicates that the call kept going)

Can someone please shed some light on what is the right/correct way of doing this?

Many thanks,
Danja

Can you try the latest packages, there was some bug in jicofo which was not handling destroy which is now fixed in the latest versions in the unstable repo, maybe that will help.

And seeing the call duration continuing … means the room was not really destroyed. Which version of prosody are you using, we had reports for problems like that for prosody 0.10.

Hi Damian, thanks for the pointers.
Currently I’m (intentionally) running on rather dated Jitsi install:

jicofo/stable,now 1.0-567-1
jitsi-meet/stable,now 2.0.4548-1
jitsi-meet-prosody/stable,now 1.0.4074-1
jitsi-meet-web/stable,now 1.0.4074-1
jitsi-meet-web-config/stable,now 1.0.4074-1
jitsi-videobridge2/stable,now 2.1-197-g38256192-1

Prosody is however recent:

prosody/now 0.11.8-1~bionic1

I will try jitsi/unstable and report back.

Danja

Why is that?
Well you should be able to update.

You will need to do it in two months or so anyway. In Aug planB will be dropped from chrome, and outdated deployments will stop working.
We are almost done with the transition, but it will take two stable releases to enable it by default.

Prosody looks good. That is strange that you get a stanza for the room being destroyed and reloading shows that room is not destroyed …

I just created an entirely new Jitsi instance using packages from jitsi/unstable:

jicofo/unstable,now 1.0-755-1
jitsi-meet-prosody/unstable,now 1.0.5043-1
jitsi-meet-turnserver/unstable,now 1.0.5043-1
jitsi-meet-web-config/unstable,now 1.0.5043-1
jitsi-meet-web/unstable,now 1.0.5043-1
jitsi-meet/unstable,now 2.0.5947-1
jitsi-videobridge2/unstable,now 2.1-506-g77c79fd8-1
prosody/unknown,now 0.11.9-1~focal1

and yet I’m getting the same results (full log):

DEBUG    MUC presence from call@conference.domain/b0b1430d : OrderedDict([('lang', ''), ('affiliation', 'owner'), ('jid', 'rdaomoqzsbnfka0_@domain/ZvgK2bSP'), ('role', 'moderator'), ('nick', 'b0b1430d'), ('room', 'call@conference.domain'), ('show', ''), ('status', ''), ('alt_nick', <nick xmlns="http://jabber.org/protocol/nick" />)])
DEBUG    Event triggered: groupchat_presence
DEBUG    Event triggered: muc::call@conference.domain::presence
DEBUG    Event triggered: muc::call@conference.domain::got_online
DEBUG    Event triggered: presence
DEBUG    Event triggered: presence_available
DEBUG    MUC presence from call@conference.domain/focus : OrderedDict([('lang', ''), ('affiliation', 'owner'), ('jid', 'focus@auth.domain/focus'), ('role', 'moderator'), ('nick', 'focus'), ('room', 'call@conference.domain'), ('show', ''), ('status', ''), ('alt_nick', <nick xmlns="http://jabber.org/protocol/nick" />)])
DEBUG    Event triggered: groupchat_presence
DEBUG    Event triggered: muc::call@conference.domain::presence
DEBUG    Event triggered: muc::call@conference.domain::got_online
DEBUG    Event triggered: presence
DEBUG    Event triggered: presence_available
DEBUG    MUC presence from call@conference.domain/focus@auth.domain : OrderedDict([('lang', 'en'), ('affiliation', 'owner'), ('jid', 'focus@auth.domain/ZZfOBJmW'), ('role', 'moderator'), ('nick', 'focus@auth.domain'), ('room', 'call@conference.domain'), ('show', ''), ('status', ''), ('alt_nick', <nick xmlns="http://jabber.org/protocol/nick" />)])
DEBUG    Event triggered: groupchat_presence
DEBUG    Event triggered: muc::call@conference.domain::presence
DEBUG    Event triggered: muc::call@conference.domain::got_online
DEBUG    Event triggered: groupchat_subject
DEBUG    Event triggered: got_online
DEBUG    Event triggered: changed_status
DEBUG    Event triggered: changed_status
DEBUG    Event triggered: changed_status
DEBUG    SEND: <iq id="da37a0af-c506-4e58-88f3-fa5fddf9fcbd-4" type="get"><query xmlns="jabber:iq:roster" ver="" /></iq>
DEBUG    SEND: <presence type="unavailable" to="call@conference.domain/b0b1430d" from="rdaomoqzsbnfka0_@domain/ZvgK2bSP]@2124609133" xml:lang="en" />
DEBUG    SEND: <presence type="unavailable" to="call@conference.domain/b0b1430d" from="rdaomoqzsbnfka0_@domain/ZvgK2bSP]@2124609133" xml:lang="en" />
DEBUG    SEND: <presence type="unavailable" to="call@conference.domain/b0b1430d" from="rdaomoqzsbnfka0_@domain/ZvgK2bSP]@2124609133" xml:lang="en" />
DEBUG    RECV: <iq type="result" to="focus@auth.domain/ZZfOBJmW" id="da37a0af-c506-4e58-88f3-fa5fddf9fcbd-4"><query xmlns="jabber:iq:roster" ver="2"><item subscription="from" jid="focus.domain" /></query></iq>
DEBUG    RECV: <presence xml:lang="en" from="call@conference.domain/focus@auth.domain" to="focus@auth.domain/ZZfOBJmW" type="unavailable"><x xmlns="http://jabber.org/protocol/muc#user"><item affiliation="owner" role="none" jid="focus@auth.domain/ZZfOBJmW" /><status code="110" /></x></presence>
DEBUG    Event triggered: roster_update
DEBUG    got response: <iq type="result" to="focus@auth.domain/ZZfOBJmW" id="da37a0af-c506-4e58-88f3-fa5fddf9fcbd-4"><query xmlns="jabber:iq:roster" ver="2"><item subscription="from" jid="focus.domain" /></query></iq>
DEBUG    Event triggered: presence
DEBUG    Event triggered: presence_unavailable
DEBUG    I got kicked :( from call@conference.domain
DEBUG    MUC presence from call@conference.domain/focus@auth.domain : OrderedDict([('lang', 'en'), ('affiliation', 'owner'), ('jid', 'focus@auth.domain/ZZfOBJmW'), ('role', 'none'), ('nick', 'focus@auth.domain'), ('room', 'call@conference.domain'), ('show', ''), ('status', ''), ('alt_nick', <nick xmlns="http://jabber.org/protocol/nick" />)])
DEBUG    Event triggered: groupchat_presence
DEBUG    Event triggered: muc::call@conference.domain::presence
DEBUG    Event triggered: muc::call@conference.domain::got_offline
DEBUG    Event triggered: changed_status
DEBUG    SEND: <presence type="unavailable" to="call@conference.domain/b0b1430d" from="rdaomoqzsbnfka0_@domain/ZvgK2bSP]@2124609133" xml:lang="en" />

I have tried both, destroying a room and removing user (per XEP-0045: Multi-User Chat) as well as exiting room as a user (per XEP-0045: Multi-User Chat) both of which didn’t end the call but left it in a “zombie”…

What seems odd to me is the following line:

DEBUG I got kicked :( from call@conference.domain

which I presume is from jicofo / focus@conference being kicked instead of the user I was intending to kick.

My question is - since I’m originally connecting to the service as focus@conference JID and later sending and that includes “from=” field with a different user JID - does it work to use a different “from” JID than the JID of the currently user (focuss@conference)? Do I need to “impersonate” the correct JID in order to be able to send a stanza originating from that JID?

EDIT: To simplify my question above, it all probably just boils down to: can focus@conference JID remove other users from a room, including room’s owner (the first user who created the room) and the room itself?

Thanks in advance for any clues.

Where do you get these debugs from?

You cannot do that, in practice the from field is ignored due to security reasons, you cannot impersonate.

Are you destroying the room from a custom module?
Here is an example how you can destroy a room from a custom prosody module: jitsi-meet/mod_reservations.lua at fdbf526b6041a1e1a8b6d6b2dc163f6e5ccaf839 · jitsi/jitsi-meet · GitHub

These debugs are coming from a SleekXMPP Python script i’m running locally of the server, which talks to Prosody as focus@conference JID

Right, I kind of arrived to the same conclusion that once connected with focus@conference JID I can only issue command as that JID.

BUT, since focus@converence is the de-facto admin of any room is creates, I should be able to set other owner or participant users’ affiliation to ‘none’ (revoke membership, per XEP-0045: Multi-User Chat) which should effectively remove those users from the room, followed by jicofo killing the room since the room won’t have any active participants left in it – am I thinking right about this?

My intention is to be able to connect to Prosody externally and issue commands… I’m going to try this now, but otherwise I might have to implement a module shall this be the only way, thanks for your tip.

I’m now using a small prosody module, that listens for a muc message to the call@conference room and runs room:clear() and room:destroy(), like this:

mod_kill_room.lua

local async = require "util.async";
local timer = require "util.timer"; 
local delay = 3;
    
local async_destroy = async.runner(function (event)
        local wait, done = async.waiter();
    
        timer.add_task(delay, function ()
                done();
        end);
        wait();
    
        -- event.room:clear(); -- should not be used in order to trigger conference.failed event
        event.room:destroy();
        
        module:log("info", "room destroyed.");
end);

module:hook("muc-broadcast-message", function(event)
        if tostring(event.stanza[1][1]) == "die_all" then
                module:log("debug", "got killer broadcast")
                async_destroy:run(event);
        end
end,150)

When I send a muc message to the room i need to destroy, the module picks up and according to prosody logs the users and the room are removed. But the browser clients again remain in the half-zombie state with video/audio streams stopped but control UI and small preview of their own my camera feed still rolling.

jicofo.log:

Jun 03, 2021 5:13:33 AM org.jitsi.utils.logging2.LoggerImpl log
INFO: Member left:6ff49599
Jun 03, 2021 5:13:33 AM org.jitsi.utils.logging2.LoggerImpl log
INFO: Terminating 6ff49599, reason: gone, send session-terminate: false
Jun 03, 2021 5:13:33 AM org.jitsi.utils.logging2.LoggerImpl log
INFO: Removed participant 6ff49599 removed=true
Jun 03, 2021 5:13:33 AM org.jitsi.utils.logging2.LoggerImpl log
WARNING: Canceling ParticipantChannelAllocator[BridgeSession[id=32435_d87b68, bridge=Bridge[jid=jvbbrewery@internal.xxx/f78c082a-73b3-4d0c-8a7e-297c3a5f3e47, relayId=null, region=null, stress=0.02]]@1655994669, Participant[call@conference.xxx/6ff49599]@1731331476]@1245607086

Looking and the JS console, I don’t see the expected conference.failed event, in fact almost nothing in the JS console suggests that the call destroyed:

2021-06-03T12:19:38.825Z [conference.js] <a.<anonymous>>:  My role changed, new role: moderator
Logger.js:154 2021-06-03T12:19:39.789Z [features/video-quality] <si>:  Setting receiver video constraints to {"constraints":{"5f20ad62":{"maxHeight":2160}},"defaultConstraints":{"maxHeight":180},"lastN":-1,"onStageEndpoints":["5f20ad62"],"selectedEndpoints":[]}
Logger.js:154 2021-06-03T12:19:39.924Z [features/base/redux] <Object.persistState>:  redux state persisted. d2f55bfcbb5c906bbd14fae4c5872fb8 -> eb79ce6e867e50a39a47231de9f4735b
Logger.js:154 2021-06-03T12:21:05.386Z [modules/xmpp/moderator.js] <l.onMucMemberLeft>:  Someone left is it focus ? facetime@conference.xxx/focus
Logger.js:154 2021-06-03T12:21:05.387Z [modules/xmpp/moderator.js] <l.onMucMemberLeft>:  Focus has left the room - leaving conference
Logger.js:154 2021-06-03T12:21:05.397Z [features/overlay] <Object.componentDidMount>:  The conference will be reloaded after 15 seconds.
Logger.js:154 2021-06-03T12:21:05.414Z [conference.js] <te._onConferenceFailed>:  CONFERENCE FAILED: conference.focusLeft
o @ app.bundle.min.js?v=5043:182
_onConferenceFailed @ app.bundle.min.js?v=5043:200
a.emit @ lib-jitsi-meet.min.js?v=5043:1
(anonymous) @ lib-jitsi-meet.min.js?v=5043:10
a.emit @ lib-jitsi-meet.min.js?v=5043:1
l.onMucMemberLeft @ lib-jitsi-meet.min.js?v=5043:10
onParticipantLeft @ lib-jitsi-meet.min.js?v=5043:10
onPresenceUnavailable @ lib-jitsi-meet.min.js?v=5043:10
onPresenceUnavailable @ lib-jitsi-meet.min.js?v=5043:10
run @ lib-jitsi-meet.min.js?v=5043:1
(anonymous) @ lib-jitsi-meet.min.js?v=5043:1
forEachChild @ lib-jitsi-meet.min.js?v=5043:1
_dataRecv @ lib-jitsi-meet.min.js?v=5043:1
_onMessage @ lib-jitsi-meet.min.js?v=5043:1
Logger.js:154 2021-06-03T12:21:05.422Z [modules/e2eping/e2eping.js] <u.stop>:  Stopping e2eping
Logger.js:154 2021-06-03T12:21:05.423Z [modules/xmpp/ChatRoom.js] <b.doLeave>:  do leave facetime@conference.xxx/5f20ad62
Logger.js:154 2021-06-03T12:21:05.464Z [modules/xmpp/xmpp.js] <P.connectionHandler>:  (TIME) Strophe disconnecting:	 88218.25999999419
Logger.js:154 2021-06-03T12:21:05.464Z [modules/xmpp/strophe.util.js] <Object.r.Strophe.log>:  Strophe: Disconnect was called because: undefined
o @ lib-jitsi-meet.min.js?v=5043:10
r.Strophe.log @ lib-jitsi-meet.min.js?v=5043:17
warn @ lib-jitsi-meet.min.js?v=5043:1
disconnect @ lib-jitsi-meet.min.js?v=5043:1
_interceptDisconnect @ lib-jitsi-meet.min.js?v=5043:25
disconnect @ lib-jitsi-meet.min.js?v=5043:1
_cleanupXmppConnection @ lib-jitsi-meet.min.js?v=5043:1
disconnect @ lib-jitsi-meet.min.js?v=5043:1
c.disconnect @ lib-jitsi-meet.min.js?v=5043:10
(anonymous) @ app.bundle.min.js?v=5043:200
Promise.then (async)
_onConferenceFailed @ app.bundle.min.js?v=5043:200
a.emit @ lib-jitsi-meet.min.js?v=5043:1
(anonymous) @ lib-jitsi-meet.min.js?v=5043:10
a.emit @ lib-jitsi-meet.min.js?v=5043:1
l.onMucMemberLeft @ lib-jitsi-meet.min.js?v=5043:10
onParticipantLeft @ lib-jitsi-meet.min.js?v=5043:10
onPresenceUnavailable @ lib-jitsi-meet.min.js?v=5043:10
onPresenceUnavailable @ lib-jitsi-meet.min.js?v=5043:10
run @ lib-jitsi-meet.min.js?v=5043:1
(anonymous) @ lib-jitsi-meet.min.js?v=5043:1
forEachChild @ lib-jitsi-meet.min.js?v=5043:1
_dataRecv @ lib-jitsi-meet.min.js?v=5043:1
_onMessage @ lib-jitsi-meet.min.js?v=5043:1
Logger.js:154 2021-06-03T12:21:05.464Z [modules/xmpp/strophe.ping.js] <d.stopInterval>:  Ping interval cleared
Logger.js:154 2021-06-03T12:21:05.465Z [modules/xmpp/xmpp.js] <P.connectionHandler>:  (TIME) Strophe disconnected:	 88219.3800000241
Logger.js:154 2021-06-03T12:21:05.708Z [modules/UI/videolayout/LargeVideoManager.js] hover in local
Logger.js:154 2021-06-03T12:21:07.406Z [features/base/redux] <Object.persistState>:  redux state persisted. eb79ce6e867e50a39a47231de9f4735b -> 635b58e08badd00948ced2d9f37f92a7

Any hints are appreciated!

Is there a reason for the clear?
What happens if you skip that?

Oh wow, without room.clear() i’m right away getting the correct conference.destroyed event!

[conference.js] <te._onConferenceFailed>: CONFERENCE FAILED: conference.destroyed

I would have never assumed that, thanks for your masterful comment!
What’s the explanation for this? Is it related to how jicofo interprets room events? (i’m just curious)

Thanks again Damian

Nope, you are first removing participant including jicofo from the room and then destroying it, and that for the client is different thing. It is like jicofo hit an internal problem and exited the conference …

One more question - what’s the correct way of attaching a listener to ‘conferenceFailed’ error?

APP.conference.addListener(JitsiMeetJS.events.conference.CONFERENCE_FAILED, function (e) {});
or
APP.conference.addListener(JitsiMeetJS.errors.conference.CONFERENCE_DESTROYED, function (e) {});

don’t seem to the way…

Thank you!

APP.conference._room.

APP.conference is the internal conference object for jitsi-meet while JitsiConference is _room.

1 Like