Files
Stefan-Sanger 2252b08139 fix(fcm): pass full JID to fcm_notify and use st.clone in MUC handler
fcm_notify splits userName internally via jid_split, so callers were
double-splitting: passing jid_split(to) fed the bare node as userName
and shifted every subsequent argument by one (host→title, resource→body,
…), corrupting all notification payloads. Pass the full JID instead.

In mod_vnc_muc_fcm, replace event.stanza:clone() with st.clone() and
add the util.stanza import for robustness.

Part-of: <http://gitlab.vnc.biz/uxf/vnctalk-prosody/-/merge_requests/11>
2026-07-22 16:33:17 +02:00

176 lines
6.3 KiB
Lua

-- mod_vnc_muc_fcm.lua
--
-- FCM push notifications for MUC / group chat.
--
-- Shared logic (caching, FCM HTTP, token building, stale-token cleanup)
-- lives in vnc_fcm_common.lua. This module retains the MUC broadcast
-- handler and event hooks that are specific to the conference component.
--
-- The affiliate-notification loop is deferred via timer.add_task(0, …) so
-- that FCM work (cache lookups, HTTP request setup) does not block the
-- muc-broadcast-message event path.
local storagemanager = require "core.storagemanager";
local jid_bare = require "util.jid".bare;
local jid_split = require "util.jid".split;
local timer = require "util.timer";
local st = require "util.stanza";
local common = require "vnc_fcm_common";
local trim = common.utils.trim;
local starts_with = common.utils.starts_with;
local storage_host = module:get_option_string("storage_host");
module:log("info","founs storage_host: %s", storage_host);
local fcmAPIKey = module:get_option_string("fcm_api_key");
local fcmAPIURL = module:get_option_string("fcm_api_url");
local moduleIsActive = fcmAPIURL and fcmAPIKey;
local inactive_devices = {};
local ThisDomain = module.host;
local stores = {
private = module:open_store("private"),
vcard = module:open_store("vcard"),
fcmtoken = module:open_store("fcmtoken"),
fcmtoken_map = module:open_store("fcmtoken", "map"),
};
timer.add_task(3, function ()
stores.private = storagemanager.open(storage_host, "private");
stores.vcard = storagemanager.open(storage_host, "vcard");
stores.fcmtoken = storagemanager.open(storage_host, "fcmtoken");
stores.fcmtoken_map = storagemanager.open(storage_host, "fcmtoken", "map");
if not stores.private or not stores.vcard then
module:log("info", "private/vcard_store %s/%s not found? - will try again", stores.private, stores.vcard);
return 3;
else
module:log("info", "private/vcard_store are now %s/%s", stores.private, stores.vcard);
end
end);
local F = common.new({
mod = module,
stores = stores,
domain = storage_host,
is_muc = true,
ios_always_push = true,
global_mute_early_return = true,
override_topic_on_hidden = false,
allow_etype_or_readtarget = false,
domain_check_enabled = true,
fcm_api_key = fcmAPIKey,
fcm_api_url = fcmAPIURL,
module_is_active = moduleIsActive,
inactive_devices = inactive_devices,
});
local fcm_notify = F.fcm_notify;
local getDisplayName_cached = F.getDisplayName_cached;
local invalidateNotifyCache = F.invalidateNotifyCache;
local invalidateVcardCache = F.invalidateVcardCache;
-- ─── MUC broadcast handler (deferred) ──────────────────────────────
--
-- handle_muc_message runs on the muc-broadcast-message event. The
-- notification work (cache lookups, HTTP request setup, affiliate loop)
-- is deferred to a zero-delay timer so it does not block message
-- delivery to room occupants. The stanza is cloned before deferral
-- because the original may be recycled after the event handler returns.
local function handle_muc_message(event)
local room = event.room
local stanza = st.clone(event.stanza)
timer.add_task(0, function()
local message_id = stanza.attr.id;
local type = stanza.attr.type or "normal";
local from = jid_bare(stanza.attr.from);
local to = jid_bare(stanza.attr.to) or from;
local vncTalkConferenceEtype = stanza:find("{xmpp:vnctalk}vncTalkConference/eventType#") or nil;
local content = trim(stanza:find("body#"));
local delayedDelivery = stanza:get_child("delay", "urn:xmpp:delay");
local vncTalkIncomingType = stanza:find("{xmpp:vnctalk}vncTalkConference/conferenceType#");
local vncTalkConferenceId = stanza:find("{xmpp:vnctalk}vncTalkConference/conferenceId#") or nil;
local jitsiURL = stanza:find("{xmpp:vnctalk}vncTalkConference/jitsiURL#") or nil;
local jitsiRoom = stanza:find("{xmpp:vnctalk}vncTalkConference/jitsiRoom#") or nil;
local vncTalkWhiteboard = stanza:find("{xmpp:vnctalk}whiteboard");
local isSentCarbonMessage = stanza:get_child("sent", "urn:xmpp:carbons:2");
local vnc_attachment_type = stanza:find("{xmpp:vnctalk}attachment/fileType#");
local vnctalk_broadcast = stanza:get_child("vncTalkBroadcast", "xmpp:vnctalk");
if vnctalk_broadcast then
if starts_with(to, "broadcast") then
return;
end
return;
end
local isMessageCorrection = stanza:find("{urn:xmpp:message-correct:0}replace@id") or nil;
if (isMessageCorrection) then
content = " ";
type = "CORRECTION";
end
local auxType = vncTalkIncomingType or (vncTalkWhiteboard and "whiteboard");
if (auxType) then
content = auxType;
type = auxType;
end;
local doNotify = content and (content ~= " ") and (content ~= "") and (delayedDelivery == nil) and (isSentCarbonMessage == nil) and (
(type == "chat") or (type == "groupchat") or (type == "audio") or (type == "video") or (type == "whiteboard") or (type == "screen") or (type == "CORRECTION")
);
if (doNotify) then
local affs = room._affiliations;
if not affs then return end
local sender = "";
local f2, h2, n2 = jid_split(stanza.attr.from);
if (affs[n2] ~= nil) then sender = n2; end
local roomTopic = room._data.subject or nil;
local senderDisplayName = getDisplayName_cached(sender);
local roomJid = to;
for aff_jid, aff in pairs(affs) do
if aff ~= "outcast" then
if (sender ~= aff_jid) then
module:log("info", "from is: %s", from);
fcm_notify(aff_jid, roomJid, content, type, senderDisplayName, roomTopic, vnc_attachment_type, message_id, vncTalkConferenceEtype, vncTalkConferenceId, sender, jitsiURL, jitsiRoom, isMessageCorrection);
end
end
end
end
end)
end
-- ─── event hooks ───────────────────────────────────────────────────
module:hook("muc-broadcast-message", handle_muc_message, 5)
module:hook("host-activated", function(host)
module:log("info", "got activated event for host %s", host);
end);
module:hook_global("vnc-fcm-invalidate-notify-cache", function(event)
if event and event.username then
invalidateNotifyCache(event.username);
end
end);
module:hook_global("vnc-fcm-invalidate-vcard-cache", function(event)
if event and event.username then
invalidateVcardCache(event.username);
end
end);
module:log("debug", "Module loaded")