Compare commits

...

4 Commits

Author SHA1 Message Date
Jaya Allamsetty
1c517f8fa9 chore(deps) Update LJM to release-8384 branch. 2025-02-21 12:52:48 -05:00
damencho
55782c7a0e feat(prosody): Introduces events for json messages and transcripts.
Optimizes json parsing of incoming messages. Now we do it in centralized place and firing an event.
2025-02-10 08:09:22 -06:00
damencho
62d0d2b3a2 fix: Fixes is_jibri check. 2025-02-03 15:48:37 -06:00
damencho
c2b3d9f4d1 fix: Adds nil check in some util methods. 2025-01-31 17:57:05 -06:00
6 changed files with 150 additions and 81 deletions

9
package-lock.json generated
View File

@@ -62,7 +62,7 @@
"js-md5": "0.6.1",
"js-sha512": "0.8.0",
"jwt-decode": "2.2.0",
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1907.0.0+0d3304b7/lib-jitsi-meet.tgz",
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet#release-8384",
"lodash-es": "4.17.21",
"moment": "2.29.4",
"moment-duration-format": "2.2.2",
@@ -16959,8 +16959,7 @@
},
"node_modules/lib-jitsi-meet": {
"version": "0.0.0",
"resolved": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1907.0.0+0d3304b7/lib-jitsi-meet.tgz",
"integrity": "sha512-qll80OJZol+xlDt3n58SqGPYQPer72C28XE9lSEtfOx6XdFdhLYXSzaNpPY4OP2tFjJqNX+Y2ZQkSkARGSqVrg==",
"resolved": "git+ssh://git@github.com/jitsi/lib-jitsi-meet.git#4df8a186c7424eec16e8f7d3f01d09667a2ab430",
"hasInstallScript": true,
"license": "Apache-2.0",
"dependencies": {
@@ -37746,8 +37745,8 @@
}
},
"lib-jitsi-meet": {
"version": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1907.0.0+0d3304b7/lib-jitsi-meet.tgz",
"integrity": "sha512-qll80OJZol+xlDt3n58SqGPYQPer72C28XE9lSEtfOx6XdFdhLYXSzaNpPY4OP2tFjJqNX+Y2ZQkSkARGSqVrg==",
"version": "git+ssh://git@github.com/jitsi/lib-jitsi-meet.git#4df8a186c7424eec16e8f7d3f01d09667a2ab430",
"from": "lib-jitsi-meet@https://github.com/jitsi/lib-jitsi-meet#release-8384",
"requires": {
"@jitsi/js-utils": "2.2.1",
"@jitsi/logger": "2.0.2",

View File

@@ -68,7 +68,7 @@
"js-md5": "0.6.1",
"js-sha512": "0.8.0",
"jwt-decode": "2.2.0",
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1907.0.0+0d3304b7/lib-jitsi-meet.tgz",
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet#release-8384",
"lodash-es": "4.17.21",
"moment": "2.29.4",
"moment-duration-format": "2.2.2",

View File

@@ -1,7 +1,10 @@
local jid = require 'util.jid';
local json = require 'cjson.safe';
local queue = require "util.queue";
local uuid_gen = require "util.uuid".generate;
local main_util = module:require "util";
local ends_with = main_util.ends_with;
local get_room_from_jid = main_util.get_room_from_jid;
local is_healthcheck_room = main_util.is_healthcheck_room;
local internal_room_jid_match_rewrite = main_util.internal_room_jid_match_rewrite;
local presence_check_status = main_util.presence_check_status;
@@ -13,9 +16,15 @@ end
local QUEUE_MAX_SIZE = 500;
-- Module that generates a unique meetingId, attaches it to the room
-- and adds it to all disco info form data (when room is queried or in the
-- initial room owner config)
-- Common module for all logic that can be loaded under the conference muc component.
--
-- This module:
-- a) Generates a unique meetingId, attaches it to the room and adds it to all disco info form data
-- (when room is queried or in the initial room owner config).
-- b) Updates user region (obtain it from the incoming http headers) in the occupant's presence on pre-join.
-- c) Avoids any participant joining the room in the interval between creating the room and jicofo entering the room.
-- d) Removes any nick that maybe set to messages being sent to the room.
-- e) Fires event for received endpoint messages (optimization to decode them once).
-- Hook to assign meetingId for new rooms
module:hook("muc-room-created", function(event)
@@ -149,3 +158,82 @@ module:hook('jicofo-unlock-room', handle_jicofo_unlock);
module:hook("muc-occupant-groupchat", function(event)
event.stanza:remove_children('nick', 'http://jabber.org/protocol/nick');
end, 45); -- prosody check is prio 50, we want to run after it
module:hook('message/bare', function(event)
local stanza = event.stanza;
if stanza.attr.type ~= 'groupchat' then
return nil;
end
-- we are interested in all messages without a body
local body = stanza:get_child('body')
if body then
return;
end
local room = get_room_from_jid(stanza.attr.to);
if not room then
module:log('warn', 'No room found found for %s', stanza.attr.to);
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 = stanza:get_child_text('json-message', 'http://jitsi.org/jitmeet');
if not json_message then
return;
end
-- TODO: add optimization by moving type and certain fields like is_interim as attribute on 'json-message'
-- using string find is roughly 70x faster than json decode for checking the value
if string.find(json_message, '"is_interim":true', 1, true) then
return;
end
local msg_obj, error = json.decode(json_message);
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 msg_obj.transcript ~= nil then
local transcription = msg_obj;
-- in case of the string matching optimization above failed
if transcription.is_interim then
return;
end
-- TODO what if we have multiple alternative transcriptions not just 1
local text_message = transcription.transcript[1].text;
--do not send empty messages
if text_message == '' then
return;
end
local user_id = transcription.participant.id;
local who = room:get_occupant_by_nick(jid.bare(room.jid)..'/'..user_id);
transcription.jid = who and who.jid;
transcription.session_id = room._data.meetingId;
local tenant, conference_name, id = extract_subdomain(jid.node(room.jid));
transcription.fqn = tenant..'/'..conference_name;
transcription.customer_id = id;
return module:fire_event('jitsi-transcript-received', {
room = room, occupant = occupant, transcription = transcription, stanza = stanza });
end
return module:fire_event('jitsi-endpoint-message-received', {
room = room, occupant = occupant, message = msg_obj,
origin = event.origin,
stanza = stanza, raw_message = json_message });
end);

View File

@@ -14,31 +14,6 @@ local is_healthcheck_room = util.is_healthcheck_room;
local POLLS_LIMIT = 128;
local POLL_PAYLOAD_LIMIT = 1024;
-- Checks if the given stanza contains a JSON message,
-- and that the message type pertains to the polls feature.
-- If yes, returns the parsed message. Otherwise, returns nil.
local function get_poll_message(stanza)
if stanza.attr.type ~= "groupchat" then
return nil;
end
local json_data = stanza:get_child_text("json-message", "http://jitsi.org/jitmeet");
if json_data == nil then
return nil;
end
if string.len(json_data) >= POLL_PAYLOAD_LIMIT then
module:log('error', 'Poll payload too large, discarding. Sender: %s to:%s', stanza.attr.from, stanza.attr.to);
return nil;
end
local data, error = json.decode(json_data);
if not data or (data.type ~= "new-poll" and data.type ~= "answer-poll") then
if error then
module:log('error', 'Error decoding data error:%s', error);
end
return nil;
end
return data;
end
-- Logs a warning and returns true if a room does not
-- have poll data associated with it.
local function check_polls(room)
@@ -87,21 +62,22 @@ end);
-- 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/bare", function(event)
local data = get_poll_message(event.stanza);
if data == nil then return end
module:hook('jitsi-endpoint-message-received', function(event)
local data, error, occupant, room, origin, stanza
= event.message, event.error, event.occupant, event.room, event.origin, event.stanza;
local room = muc.get_room_from_jid(event.stanza.attr.to);
if not data or (data.type ~= "new-poll" and data.type ~= "answer-poll") then
return;
end
if string.len(event.raw_message) >= POLL_PAYLOAD_LIMIT then
module:log('error', 'Poll payload too large, discarding. Sender: %s to:%s', stanza.attr.from, stanza.attr.to);
return nil;
end
if data.type == "new-poll" then
if check_polls(room) then return end
local occupant_jid = event.stanza.attr.from;
local occupant = room:get_occupant_by_real_jid(occupant_jid);
if not occupant then
module:log("error", "Occupant %s was not found in room %s", occupant_jid, room.jid)
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)
@@ -115,7 +91,7 @@ module:hook("message/bare", function(event)
if room.polls.by_id[data.pollId] ~= nil then
module:log("error", "Poll already exists: %s", data.pollId);
event.origin.send(st.error_reply(event.stanza, 'cancel', 'not-allowed', 'Poll already exists'));
origin.send(st.error_reply(stanza, 'cancel', 'not-allowed', 'Poll already exists'));
return true;
end
@@ -150,16 +126,9 @@ module:hook("message/bare", function(event)
}
}
module:fire_event("poll-created", pollData);
elseif data.type == "answer-poll" then
if check_polls(room) then return end
local occupant_jid = event.stanza.attr.from;
local occupant = room:get_occupant_by_real_jid(occupant_jid);
if not occupant then
module:log("error", "Occupant %s does not exists for room %s", occupant_jid, room.jid)
return
end
local poll = room.polls.by_id[data.pollId];
if poll == nil then
module:log("warn", "answering inexistent poll");

View File

@@ -535,17 +535,11 @@ process_host_module(muc_domain_prefix..'.'..muc_domain_base, function(host_modul
end
end
end);
host_module:hook("message/bare", function(event)
local stanza = event.stanza;
if stanza.attr.type ~= "groupchat" then
return;
end
local json_data = stanza:get_child_text("json-message", "http://jitsi.org/jitmeet");
if json_data == nil then
return;
end
local data, error = json.decode(json_data);
host_module:hook('jitsi-endpoint-message-received', function(event)
local data, error, occupant, room, stanza
= event.message, event.error, event.occupant, event.room, event.stanza;
if not data or data.type ~= 'visitors'
or (data.action ~= "promotion-response" and data.action ~= "demote-request") then
if error then
@@ -554,17 +548,9 @@ process_host_module(muc_domain_prefix..'.'..muc_domain_base, function(host_modul
return;
end
local room = get_room_from_jid(event.stanza.attr.to);
local occupant_jid = event.stanza.attr.from;
local occupant = room:get_occupant_by_real_jid(occupant_jid);
if not occupant then
module:log("error", "Occupant %s was not found in room %s", occupant_jid, room.jid)
return
end
if occupant.role ~= 'moderator' then
module:log('error', 'Occupant %s sending response message but not moderator in room %s',
occupant_jid, room.jid);
occupant.jid, room.jid);
return false;
end
@@ -590,7 +576,6 @@ process_host_module(muc_domain_prefix..'.'..muc_domain_base, function(host_modul
end
end
end
else
if data.id then
process_promotion_response(room, data.id, data.approved and 'true' or 'false');
@@ -604,6 +589,7 @@ process_host_module(muc_domain_prefix..'.'..muc_domain_base, function(host_modul
return true; -- halt processing, but return true that we handled it
end);
if visitors_queue_service then
host_module:hook('muc-room-created', function (event)
local room = event.room;

View File

@@ -5,6 +5,7 @@ local timer = require "util.timer";
local http = require "net.http";
local cache = require "util.cache";
local array = require "util.array";
local is_set = require 'util.set'.is_set;
local http_timeout = 30;
local have_async, async = pcall(require, "util.async");
@@ -32,6 +33,7 @@ local roomless_iqs = {};
local OUTBOUND_SIP_JIBRI_PREFIXES = { 'outbound-sip-jibri@', 'sipjibriouta@', 'sipjibrioutb@' };
local INBOUND_SIP_JIBRI_PREFIXES = { 'inbound-sip-jibri@', 'sipjibriina@', 'sipjibriina@' };
local RECORDER_PREFIXES = module:get_option_inherited_set('recorder_prefixes', { 'recorder@recorder.', 'jibria@recorder.', 'jibrib@recorder.' });
local TRANSCRIBER_PREFIXES = module:get_option_inherited_set('transcriber_prefixes', { 'transcriber@recorder.', 'transcribera@recorder.', 'transcriberb@recorder.' });
local split_subdomain_cache = cache.new(1000);
local extract_subdomain_cache = cache.new(1000);
@@ -267,17 +269,18 @@ function is_feature_allowed(ft, features, granted_features, is_moderator)
end
--- Extracts the subdomain and room name from internal jid node [foo]room1
-- @return subdomain(optional, if extracted or nil), the room name
-- @return subdomain(optional, if extracted or nil), the room name, the customer_id in case of vpaas
function extract_subdomain(room_node)
local ret = extract_subdomain_cache:get(room_node);
if ret then
return ret.subdomain, ret.room;
return ret.subdomain, ret.room, ret.customer_id;
end
local subdomain, room_name = room_node:match("^%[([^%]]+)%](.+)$");
local cache_value = {subdomain=subdomain, room=room_name};
local _, customer_id = subdomain and subdomain:match("^(vpaas%-magic%-cookie%-)(.*)$") or nil, nil;
local cache_value = { subdomain=subdomain, room=room_name, customer_id=customer_id };
extract_subdomain_cache:set(room_node, cache_value);
return subdomain, room_name;
return subdomain, room_name, customer_id;
end
function starts_with(str, start)
@@ -288,14 +291,25 @@ function starts_with(str, start)
end
function starts_with_one_of(str, prefixes)
if not str then
if not str or not prefixes then
return false;
end
for i=1,#prefixes do
if starts_with(str, prefixes[i]) then
return prefixes[i];
if is_set(prefixes) then
-- set is a table with keys and value of true
for k, _ in prefixes:items() do
if starts_with(str, k) then
return k;
end
end
else
for _, v in pairs(prefixes) do
if starts_with(str, v) then
return v;
end
end
end
return false
end
@@ -474,12 +488,21 @@ end
-- Returns the initiator extension if the stanza is coming from a sip jigasi
function is_sip_jigasi(stanza)
if not stanza then
return false;
end
return stanza:get_child('initiator', 'http://jitsi.org/protocol/jigasi');
end
-- This requires presence stanza being passed
function is_transcriber_jigasi(stanza)
if not stanza then
return false;
end
local features = stanza:get_child('features');
if not features then
if not features then
return false;
end
@@ -493,6 +516,9 @@ function is_transcriber_jigasi(stanza)
return false;
end
function is_transcriber(jid)
return starts_with_one_of(jid, TRANSCRIBER_PREFIXES);
end
function get_sip_jibri_email_prefix(email)
if not email then
@@ -608,6 +634,7 @@ return {
is_moderated = is_moderated;
is_sip_jibri_join = is_sip_jibri_join;
is_sip_jigasi = is_sip_jigasi;
is_transcriber = is_transcriber;
is_transcriber_jigasi = is_transcriber_jigasi;
is_vpaas = is_vpaas;
get_focus_occupant = get_focus_occupant;