Compare commits

..

7 Commits

Author SHA1 Message Date
damencho
a24ddf205c feat(visitors): Adds enabled flag in the metadata. 2025-07-09 15:51:21 +03:00
damencho
7a97d15e89 feat(conference): Clears any error from previous attempts.
When you see the error, you may click join on pre-join again, which may succeeded, so clear previous errors.
2025-07-09 14:14:49 +03:00
damencho
1acb99d763 fix(av-moderation): Fixes auto starting av moderation, notify everyone. 2025-07-08 21:18:44 +03:00
damencho
adbe990867 fix(visitors): A join case with live rooms. 2025-07-08 19:10:28 +03:00
Saúl Ibarra Corretgé
a4367567ab fix(amplitude) adjust to new SDK API changes
Ref: https://amplitude.com/docs/sdks/analytics/browser/migrate-from-javascript-sdk-to-browser-sdk-2-0
2025-07-08 17:40:46 +02:00
damencho
7f56cbc4ce fix(av-moderation): Fixes auto starting av moderation.
There are some startMuted policies we set when starting it.
2025-07-08 16:18:57 +03:00
damencho
d636d084c8 fix(visitors): Fixes empty array case and wrong json.
When there is empty array cjson produces array: {} while prosody's json impl checks is it array and produces the correct value (array: []). Prosody impl is a little bit slower, but this is not a hot path and those are not huge json strings.
2025-07-08 13:31:05 +03:00
11 changed files with 128 additions and 88 deletions

View File

@@ -1131,10 +1131,6 @@ var config = {
// The Amplitude APP Key:
// amplitudeAPPKey: '<APP_KEY>',
// Enables Amplitude UTM tracking:
// Default value is false.
// amplitudeIncludeUTM: false,
// Obfuscates room name sent to analytics (amplitude, rtcstats)
// Default value is false.
// obfuscateRoomName: false,

View File

@@ -84,7 +84,6 @@ export async function createHandlers({ getState }: IStore) {
} = config;
const {
amplitudeAPPKey,
amplitudeIncludeUTM,
blackListedEvents,
scriptURLs,
matomoEndpoint,
@@ -94,7 +93,6 @@ export async function createHandlers({ getState }: IStore) {
const { group, user } = state['features/base/jwt'];
const handlerConstructorOptions = {
amplitudeAPPKey,
amplitudeIncludeUTM,
blackListedEvents,
envType: deploymentInfo?.envType || 'dev',
matomoEndpoint,

View File

@@ -11,7 +11,6 @@ export interface IEvent {
interface IOptions {
amplitudeAPPKey?: string;
amplitudeIncludeUTM?: boolean;
blackListedEvents?: string[];
envType?: string;
group?: string;

View File

@@ -4,7 +4,7 @@ import logger from '../logger';
import AbstractHandler, { IEvent } from './AbstractHandler';
import { fixDeviceID } from './amplitude/fixDeviceID';
import amplitude from './amplitude/lib';
import amplitude, { initAmplitude } from './amplitude/lib';
/**
* Analytics handler for Amplitude.
@@ -15,8 +15,7 @@ export default class AmplitudeHandler extends AbstractHandler {
* Creates new instance of the Amplitude analytics handler.
*
* @param {Object} options - The amplitude options.
* @param {string} options.amplitudeAPPKey - The Amplitude app key required by the Amplitude API.
* @param {boolean} options.amplitudeIncludeUTM - Whether to include UTM parameters
* @param {string} options.amplitudeAPPKey - The Amplitude app key required by the Amplitude API
* in the Amplitude events.
*/
constructor(options: any) {
@@ -24,47 +23,26 @@ export default class AmplitudeHandler extends AbstractHandler {
const {
amplitudeAPPKey,
amplitudeIncludeUTM: includeUtm = true,
user
} = options;
this._enabled = true;
const onError = (e: Error) => {
logger.error('Error initializing Amplitude', e);
this._enabled = false;
};
// Forces sending all events on exit (flushing) via sendBeacon
const onExitPage = () => {
amplitude.flush();
};
if (navigator.product === 'ReactNative') {
amplitude.init(amplitudeAPPKey);
fixDeviceID(amplitude);
} else {
const amplitudeOptions: any = {
includeReferrer: true,
includeUtm,
saveParamsReferrerOncePerSession: false,
onError,
onExitPage
};
amplitude.init(amplitudeAPPKey, undefined, amplitudeOptions);
fixDeviceID(amplitude);
}
if (user) {
amplitude.setUserId(user);
}
initAmplitude(amplitudeAPPKey, user)
.then(() => {
logger.info('Amplitude initialized');
fixDeviceID(amplitude);
})
.catch(e => {
logger.error('Error initializing Amplitude', e);
this._enabled = false;
});
}
/**
* Sets the Amplitude user properties.
*
* @param {Object} userProps - The user portperties.
* @param {Object} userProps - The user properties.
* @returns {void}
*/
setUserProperties(userProps: any) {
@@ -95,7 +73,7 @@ export default class AmplitudeHandler extends AbstractHandler {
const eventName = this._extractName(event) ?? '';
amplitude.logEvent(eventName, event);
amplitude.track(eventName, event);
}
/**

View File

@@ -1,3 +1,15 @@
import { createInstance } from '@amplitude/analytics-react-native';
import amplitude from '@amplitude/analytics-react-native';
export default createInstance();
export default amplitude;
/**
* Initializes the Amplitude instance.
*
* @param {string} amplitudeAPPKey - The Amplitude app key.
* @param {string | undefined} user - The user ID.
* @returns {Promise} The initialized Amplitude instance.
*/
export function initAmplitude(
amplitudeAPPKey: string, user: string | undefined): Promise<unknown> {
return amplitude.init(amplitudeAPPKey, user, {}).promise;
}

View File

@@ -1,3 +1,38 @@
import { createInstance } from '@amplitude/analytics-browser';
export default createInstance();
const amplitude = createInstance();
export default amplitude;
/**
* Initializes the Amplitude instance.
*
* @param {string} amplitudeAPPKey - The Amplitude app key.
* @param {string | undefined} user - The user ID.
* @returns {Promise} The initialized Amplitude instance.
*/
export function initAmplitude(
amplitudeAPPKey: string, user: string | undefined): Promise<unknown> {
// Forces sending all events on exit (flushing) via sendBeacon.
window.addEventListener('pagehide', () => {
// Set https transport to use sendBeacon API.
amplitude.setTransport('beacon');
// Send all pending events to server.
amplitude.flush();
});
const options = {
autocapture: {
attribution: true,
pageViews: true,
sessions: false,
fileDownloads: false,
formInteractions: false,
elementInteractions: false
},
defaultTracking: false
};
return amplitude.init(amplitudeAPPKey, user, options).promise;
}

View File

@@ -16,7 +16,7 @@ import { IStore } from '../../app/types';
import { removeLobbyChatParticipant } from '../../chat/actions.any';
import { openDisplayNamePrompt } from '../../display-name/actions';
import { isVpaasMeeting } from '../../jaas/functions';
import { showErrorNotification, showNotification } from '../../notifications/actions';
import { clearNotifications, showErrorNotification, showNotification } from '../../notifications/actions';
import { NOTIFICATION_TIMEOUT_TYPE } from '../../notifications/constants';
import { INotificationProps } from '../../notifications/types';
import { hasDisplayName } from '../../prejoin/utils';
@@ -25,7 +25,7 @@ import LocalRecordingManager from '../../recording/components/Recording/LocalRec
import { AudioMixerEffect } from '../../stream-effects/audio-mixer/AudioMixerEffect';
import { iAmVisitor } from '../../visitors/functions';
import { overwriteConfig } from '../config/actions';
import { CONNECTION_ESTABLISHED, CONNECTION_FAILED } from '../connection/actionTypes';
import { CONNECTION_ESTABLISHED, CONNECTION_FAILED, CONNECTION_WILL_CONNECT } from '../connection/actionTypes';
import { connectionDisconnected, disconnect } from '../connection/actions';
import { validateJwt } from '../jwt/functions';
import { JitsiConferenceErrors, JitsiConferenceEvents, JitsiConnectionErrors } from '../lib-jitsi-meet';
@@ -99,6 +99,11 @@ MiddlewareRegistry.register(store => next => action => {
case CONNECTION_FAILED:
return _connectionFailed(store, next, action);
case CONNECTION_WILL_CONNECT:
// we are starting a new join process, let's clear the error notifications if any from any previous attempt
store.dispatch(clearNotifications());
break;
case CONFERENCE_SUBJECT_CHANGED:
return _conferenceSubjectChanged(store, next, action);

View File

@@ -172,7 +172,6 @@ export interface IConfig {
_screenshotHistoryRegionUrl?: number;
analytics?: {
amplitudeAPPKey?: string;
amplitudeIncludeUTM?: boolean;
blackListedEvents?: string[];
disabled?: boolean;
matomoEndpoint?: string;

View File

@@ -5,6 +5,7 @@ local internal_room_jid_match_rewrite = util.internal_room_jid_match_rewrite;
local room_jid_match_rewrite = util.room_jid_match_rewrite;
local process_host_module = util.process_host_module;
local table_shallow_copy = util.table_shallow_copy;
local is_admin = util.is_admin;
local array = require "util.array";
local json = require 'cjson.safe';
local st = require 'util.stanza';
@@ -151,6 +152,29 @@ function notify_jid_approved(jid, from, room, mediaType)
send_json_message(jid, json_message);
end
function start_av_moderation(room, mediaType, occupant)
if not room.av_moderation then
room.av_moderation = {};
room.av_moderation_actors = {};
end
room.av_moderation[mediaType] = array{};
-- We want to set startMuted policy in metadata, in case of new participants are joining to respect
-- it, that will be enforced by jicofo
local startMutedMetadata = room.jitsiMetadata.startMuted or {};
-- We want to keep the previous value of startMuted for this mediaType if av moderation is disabled
-- to be able to restore
local av_moderation_startMuted_restore = room.av_moderation_startMuted_restore or {};
av_moderation_startMuted_restore = startMutedMetadata[mediaType];
room.av_moderation_startMuted_restore = av_moderation_startMuted_restore;
startMutedMetadata[mediaType] = true;
room.jitsiMetadata.startMuted = startMutedMetadata;
room.av_moderation_actors[mediaType] = occupant.nick;
end
-- receives messages from clients to the component sending A/V moderation enable/disable commands or adding
-- jids to the whitelist
function on_message(event)
@@ -208,26 +232,7 @@ function on_message(event)
module:log('warn', 'Concurrent moderator enable/disable request or something is out of sync');
return true;
else
if not room.av_moderation then
room.av_moderation = {};
room.av_moderation_actors = {};
end
room.av_moderation[mediaType] = array{};
-- We want to set startMuted policy in metadata, in case of new participants are joining to respect
-- it, that will be enforced by jicofo
local startMutedMetadata = room.jitsiMetadata.startMuted or {};
-- We want to keep the previous value of startMuted for this mediaType if av moderation is disabled
-- to be able to restore
local av_moderation_startMuted_restore = room.av_moderation_startMuted_restore or {};
av_moderation_startMuted_restore = startMutedMetadata[mediaType];
room.av_moderation_startMuted_restore = av_moderation_startMuted_restore;
startMutedMetadata[mediaType] = true;
room.jitsiMetadata.startMuted = startMutedMetadata;
room.av_moderation_actors[mediaType] = occupant.nick;
start_av_moderation(room, mediaType, occupant);
end
else
enabled = false;
@@ -317,23 +322,27 @@ end
function occupant_joined(event)
local room, occupant = event.room, event.occupant;
if is_healthcheck_room(room.jid) then
if is_healthcheck_room(room.jid) or is_admin(occupant.bare_jid) then
return;
end
-- when first moderator joins if av_can_unmute from password preset is set to false, we enable av moderation for both
-- audio and video, and set the first moderator as the actor that enabled it
if (occupant.role == 'moderator') and room._data.av_can_unmute ~= nil and not room._data.av_first_moderator_joined then
if room._data.av_can_unmute ~= nil
and not room._data.av_first_moderator_joined
-- occupant.role is not reflecting the actual role after set_affiliation is used in same occupant_joined event
and room:get_role(occupant.nick) == 'moderator' then
if not room._data.av_can_unmute then
room.av_moderation = {
audio = array{},
video = array{}
};
room.av_moderation_actors = {
audio = occupant.nick,
video = occupant.nick
};
for _,mediaType in pairs({'audio', 'video'}) do
start_av_moderation(room, mediaType, occupant);
notify_occupants_enable(nil, true, room, occupant.nick, mediaType);
end
room._data.av_first_moderator_joined = true;
return;
end
end

View File

@@ -3,9 +3,10 @@
-- Component "metadata.jitmeet.example.com" "room_metadata_component"
-- muc_component = "conference.jitmeet.example.com"
-- breakout_rooms_component = "breakout.jitmeet.example.com"
local array = require 'util.array';
local filters = require 'util.filters';
local jid_node = require 'util.jid'.node;
local json = require 'cjson.safe';
local json = require 'util.json';
local st = require 'util.stanza';
local jid = require 'util.jid';
@@ -79,19 +80,19 @@ function send_metadata(occupant, room, json_msg)
-- we want to send the main meeting participants only to jicofo
if is_admin(occupant.bare_jid) then
local participants;
local moderators = {};
local moderators = array();
if room._data.participants then
participants = {};
table_add(participants, room._data.participants);
participants = array();
participants:append(room._data.participants);
end
if room._data.moderator_id then
table.insert(moderators, room._data.moderator_id);
moderators:push(room._data.moderator_id);
end
if room._data.moderators then
table_add(moderators, room._data.moderators);
moderators:append(room._data.moderators);
end
metadata_to_send = table_shallow_copy(metadata_to_send);

View File

@@ -558,13 +558,14 @@ process_host_module(muc_domain_prefix..'.'..muc_domain_base, function(host_modul
:tag('no-main-participants', { xmlns = 'jitsi:visitors' }));
return true;
end
elseif is_live == false and room._data.participants then
-- This is non jaas room which is not live and has a list of participants
-- allowed to participate in the main room, but this participant is not one of them
elseif room._data.participants then
-- This is non jaas room which has a list of participants allowed to participate in the main room
-- but this occupant is not one of them and the room is either not live or has no participants joined
session.log('warn',
'Deny user join in the main not live meeting, not in the list of main participants');
session.send(st.error_reply(
stanza, 'cancel', 'not-allowed', 'Tried to join the main (not live) room')
stanza, 'cancel', 'not-allowed',
'Tried to join the main (not live or without main participants) room')
:tag('not-live-room', { xmlns = 'jitsi:visitors' }));
return true;
end
@@ -713,6 +714,13 @@ process_host_module(muc_domain_prefix..'.'..muc_domain_base, function(host_modul
host_module:context(host):hook("muc-config-form", function(event)
table.insert(event.form, visitorsEnabledField);
end);
if not room.jitsiMetadata then
room.jitsiMetadata = {};
end
room.jitsiMetadata.visitorsEnabled = true;
module:context(muc_domain_prefix..'.'..muc_domain_base)
:fire_event('room-metadata-changed', { room = room; });
end
end);