Compare commits

..

2 Commits

Author SHA1 Message Date
damencho
11a6e94541 debug: longer waits 2. 2025-08-19 16:59:38 -05:00
damencho
e1e262fb68 debug: longer wait. 2025-08-19 16:31:03 -05:00
36 changed files with 167 additions and 549 deletions

View File

@@ -25,15 +25,9 @@ import android.content.IntentFilter;
import android.content.res.Configuration;
import android.net.Uri;
import android.os.Bundle;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import com.facebook.react.modules.core.PermissionListener;
@@ -93,28 +87,6 @@ public class JitsiMeetActivity extends AppCompatActivity
launch(context, options);
}
public static void addTopBottomInsets(@NonNull Window w, @NonNull View v) {
if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.VANILLA_ICE_CREAM) return;
View decorView = w.getDecorView();
decorView.post(() -> {
WindowInsetsCompat insets = ViewCompat.getRootWindowInsets(decorView);
if (insets != null) {
ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) v.getLayoutParams();
params.topMargin = insets.getInsets(WindowInsetsCompat.Type.systemBars()).top;
params.bottomMargin = insets.getInsets(WindowInsetsCompat.Type.systemBars()).bottom;
v.setLayoutParams(params);
decorView.setOnApplyWindowInsetsListener((view, windowInsets) -> {
view.setBackgroundColor(JitsiMeetView.BACKGROUND_COLOR);
return windowInsets;
});
}
});
}
// Overrides
//
@@ -135,7 +107,6 @@ public class JitsiMeetActivity extends AppCompatActivity
JitsiMeetActivityDelegate.onHostResume(this);
setContentView(R.layout.activity_jitsi_meet);
addTopBottomInsets(getWindow(),findViewById(android.R.id.content));
this.jitsiView = findViewById(R.id.jitsiView);
registerForBroadcastMessages();

View File

@@ -36,7 +36,7 @@ public class JitsiMeetView extends FrameLayout {
/**
* Background color. Should match the background color set in JS.
*/
public static final int BACKGROUND_COLOR = 0xFF040404;
private static final int BACKGROUND_COLOR = 0xFF040404;
/**
* React Native root view.

View File

@@ -109,10 +109,8 @@
}
},
"chat": {
"disabled": "O envio de mensagens de chat está desativado.",
"enter": "Entrar na sala",
"error": "Erro: a sua mensagem não foi enviada. Motivo: {{error}}",
"everyone": "Todos",
"fieldPlaceHolder": "Aa",
"lobbyChatMessageTo": "Mensagem de chat na sala de espera para {{recipient}}",
"message": "Mensagem",
@@ -124,10 +122,7 @@
"nickname": {
"popover": "Escolha um apelido",
"title": "Introduza um apelido para usar o chat",
"titleWithCC": "Insira um apelido para usar o chat e as legendas ocultas",
"titleWithPolls": "Digite um apelido para usar o chat e as sondagens",
"titleWithPollsAndCC": "Insira um apelido para utilizar o chat, as sondagens e as legendas ocultas",
"titleWithPollsAndCCAndFileSharing": "Insira um apelido para utilizar o chat, as sondagens, as legendas e os ficheiros"
"titleWithPolls": "Introduza um apelido para usar o chat e as sondagens"
},
"noMessagesMessage": "Ainda não há mensagens na reunião. Comece aqui uma conversa!",
"privateNotice": "Mensagem privada para {{recipient}}",
@@ -136,15 +131,10 @@
"systemDisplayName": "Sistema",
"tabs": {
"chat": "Chat",
"closedCaptions": "LO",
"fileSharing": "Ficheiros",
"polls": "Sondagens"
},
"title": "Chat",
"titleWithCC": "LO",
"titleWithFeatures": "Chat e",
"titleWithFileSharing": "Ficheiros",
"titleWithPolls": "Sondagens",
"titleWithPolls": "Chat e Sondagens",
"you": "você"
},
"chromeExtensionBanner": {
@@ -154,10 +144,6 @@
"dontShowAgain": "Não me mostre isto outra vez",
"installExtensionText": "Instalar a extensão para a integração Google Calendar e Office 365"
},
"closedCaptionsTab": {
"emptyState": "O conteúdo das legendas ocultas estará disponível assim que um moderador iniciar a sessão.",
"startClosedCaptionsButton": "Iniciar legendas ocultas"
},
"connectingOverlay": {
"joiningRoom": "A ligá-lo à reunião…"
},
@@ -277,8 +263,6 @@
"Remove": "Remover",
"Share": "Partilhar",
"Submit": "Submeter",
"Understand": "Entendo, mantenha-me em silêncio por enquanto.",
"UnderstandAndUnmute": "Entendo, por favor, desative o silêncio.",
"WaitForHostMsg": "A conferência ainda não começou porque ainda não chegaram moderadores. Se quiser ser um moderador, inicie a sessão. Caso contrário, aguarde.",
"WaitForHostNoAuthMsg": "A conferência ainda não começou porque ainda não chegaram os moderadores. Por favor, aguarde.",
"WaitingForHostButton": "Esperar pelo moderador",
@@ -301,12 +285,6 @@
"alreadySharedVideoTitle": "Só é permitido um vídeo partilhado de cada vez",
"applicationWindow": "Janela de aplicação",
"authenticationRequired": "Autenticação necessária",
"cameraCaptureDialog": {
"description": "Tire e envie uma foto usando a câmara do seu telemóvel",
"ok": "Ligar a câmara",
"reject": "Agora não",
"title": "Tire uma foto"
},
"cameraConstraintFailedError": "A sua câmara não satisfaz algumas das restrições exigidas.",
"cameraNotFoundError": "A câmara não foi encontrada.",
"cameraNotSendingData": "Não podemos aceder à sua câmara. Verifique se outra aplicação está a utilizar este dispositivo, seleccione outro dispositivo do menu de definições ou tente recarregar a aplicação.",
@@ -321,7 +299,6 @@
"conferenceReloadMsg": "Estamos a tentar resolver isto. Reconexão em {{seconds}} seg…",
"conferenceReloadTitle": "Infelizmente, algo correu mal.",
"confirm": "Confirme",
"confirmBack": "Voltar",
"confirmNo": "Não",
"confirmYes": "Sim",
"connectError": "Oops! Algo correu mal e não conseguimos estabelecer uma ligação com a conferência.",
@@ -330,8 +307,8 @@
"contactSupport": "Contacte o suporte",
"copied": "Copiado",
"copy": "Cópia",
"demoteParticipantDialog": "Tem a certeza de que deseja mover este participante para espectador?",
"demoteParticipantTitle": "Mover para espectador",
"demoteParticipantDialog": "Tem a certeza de que pretende passar este participante para visitante?",
"demoteParticipantTitle": "Passar a visitante",
"dismiss": "Dispensar",
"displayNameRequired": "Olá! Qual é o seu nome?",
"done": "Feito",
@@ -357,9 +334,7 @@
"kickParticipantButton": "Expulsar",
"kickParticipantDialog": "Tem a certeza que quer expulsar este participante?",
"kickParticipantTitle": "Expulsar este participante?",
"kickSystemTitle": "Ai! Foste expulso da reunião.",
"kickTitle": "Ai! {{participantDisplayName}} expulsou-o da reunião",
"learnMore": "saiba mais",
"linkMeeting": "Link da reunião",
"linkMeetingTitle": "Link da reunião à Força de Vendas",
"liveStreaming": "Transmissão em direto",
@@ -381,35 +356,23 @@
"micPermissionDeniedError": "Não concedeu autorização para utilizar o seu microfone. Ainda pode participar na conferência, mas outros não o ouvirão. Use o botão da câmara na barra de endereço para corrigir isto.",
"micTimeoutError": "Não foi possível iniciar a fonte de áudio. Tempo limite expirado!",
"micUnknownError": "Não pode usar microfone por uma razão desconhecida.",
"moderationAudioLabel": "Permitir aos não moderadores ligar o som",
"moderationDesktopLabel": "Permitir que não moderadores partilhem o seu ecrã",
"moderationVideoLabel": "Permitir que não moderadores iniciem os seus vídeos",
"moderationAudioLabel": "Permitir aos participantes ligar o som",
"moderationVideoLabel": "Permitir aos participantes ligar a câmara",
"muteEveryoneDialog": "Os participantes podem ligar o som a qualquer momento.",
"muteEveryoneDialogModerationOn": "Os participantes podem enviar um pedido para falar a qualquer momento.",
"muteEveryoneElseDialog": "Uma vez silenciados, não poderá reativá-los, mas eles podem ligar o microfone a qualquer momento.",
"muteEveryoneElseTitle": "Silenciar todos excepto {{whom}}?",
"muteEveryoneElsesDesktopDialog": "Depois que o compartilhamento for interrompido, não será possível reiniciá-lo, mas eles poderão fazê-lo a qualquer momento.",
"muteEveryoneElsesDesktopTitle": "SInterromper a partilha de ecrã de todos, exceto {{whom}}?",
"muteEveryoneElsesVideoDialog": "Quando a câmara for desligada, não poderá voltar a ligá-la, mas eles podem voltar a ligá-la em qualquer momento.",
"muteEveryoneElsesVideoTitle": "Parar o vídeo de todos excepto {{whom}}?",
"muteEveryoneSelf": "você mesmo",
"muteEveryoneStartMuted": "A partir de agora, toda a gente começa a ficar calada",
"muteEveryoneTitle": "Silenciar toda a gente?",
"muteEveryonesDesktopDialog": "Os participantes podem partilhar o seu ecrã a qualquer momento.",
"muteEveryonesDesktopDialogModerationOn": "Os participantes podem enviar um pedido para partilhar o seu ecrã a qualquer momento.",
"muteEveryonesDesktopTitle": "Interromper a partilha de ecrã de todos?",
"muteEveryonesVideoDialog": "Os participantes podem ligar a sua câmara a qualquer momento.",
"muteEveryonesVideoDialogModerationOn": "Os participantes podem enviar um pedido para ligar a sua câmara a qualquer momento.",
"muteEveryonesVideoDialogOk": "Desativar",
"muteEveryonesVideoTitle": "Desligar a câmara de todos?",
"muteParticipantBody": "Não poderá reativá-los, mas eles podem reativar-se a qualquer momento.",
"muteParticipantButton": "Silenciar",
"muteParticipantsDesktopBody": "Não poderá iniciar a partilha de ecrã deles, mas eles podem fazê-lo a qualquer momento.",
"muteParticipantsDesktopBodyModerationOn": "Não será possível iniciar a partilha de ecrã nem para si nem para eles.",
"muteParticipantsDesktopButton": "Parar a partilha de ecrã",
"muteParticipantsDesktopDialog": "Tem a certeza de que deseja desativar a partilha de ecrã deste participante? Não será possível reiniciá-la, mas ele poderá fazê-lo a qualquer momento.",
"muteParticipantsDesktopDialogModerationOn": "Tem a certeza de que deseja desativar a partilha de ecrã deste participante? Não será possível reativar o ecrã, nem para si nem para ele.",
"muteParticipantsDesktopTitle": "Desativar a partilha de ecrã deste participante?",
"muteParticipantsVideoBody": "Não poderá voltar a ligar a câmara, mas eles podem voltar a ligá-la a qualquer momento.",
"muteParticipantsVideoBodyModerationOn": "Não será capaz de voltar a ligar a câmara e eles também não.",
"muteParticipantsVideoButton": "Parar vídeo",
@@ -429,10 +392,6 @@
"recentlyUsedObjects": "Os seus objetos recentemente utilizados",
"recording": "A gravar",
"recordingDisabledBecauseOfActiveLiveStreamingTooltip": "Não possível enquanto a transmissão em direto estiver activa",
"recordingInProgressDescription": "Esta reunião está a ser gravada e analisada pela IA{{learnMore}}. O seu áudio e vídeo foram silenciados. Se optar por ativar o som, concorda em ser gravado.",
"recordingInProgressDescriptionFirstHalf": "Esta reunião está a ser gravada e analisada por IA.",
"recordingInProgressDescriptionSecondHalf": ". Your audio and video have been muted. If you choose to unmute, you consent to being recorded.",
"recordingInProgressTitle": "Gravação em andamento",
"rejoinNow": "Reingressar agora",
"remoteControlAllowedMessage": "{{user}} aceitou o seu pedido de controlo remoto!",
"remoteControlDeniedMessage": "{{user}} rejeitou o seu pedido de controlo remoto!",
@@ -480,10 +439,7 @@
"shareScreenWarningD2": "é necessário parar a partilha de áudio, iniciar a partilha de ecrã e verificar a opção \"partilhar áudio\".",
"shareScreenWarningH1": "Se quiser partilhar apenas o seu ecrã:",
"shareScreenWarningTitle": "Tem de parar a partilha de áudio antes de partilhar o seu ecrã",
"shareVideoConfirmPlay": "Está prestes a abrir um site externo. Deseja continuar?",
"shareVideoConfirmPlayTitle": "{{name}} partilhou um vídeo consigo.",
"shareVideoLinkError": "Oops, este vídeo não pode ser reproduzido.",
"shareVideoLinkStopped": "O vídeo de {{name}} foi interrompido.",
"shareVideoLinkError": "Por favor, forneça um link correcto do vídeo.",
"shareVideoTitle": "Partilhar vídeo",
"shareYourScreen": "Partilhe o seu ecrã",
"shareYourScreenDisabled": "Partilha de ecrã desactivada.",
@@ -562,21 +518,6 @@
"veryBad": "Muito má",
"veryGood": "Muito boa"
},
"fileSharing": {
"downloadFailedDescription": "Por favor, tente novamente.",
"downloadFailedTitle": "Falha no descarregar",
"downloadFile": "Descarregar",
"dragAndDrop": "Arraste e solte os ficheiros aqui ou em qualquer lugar do ecrã",
"fileAlreadyUploaded": "O ficheiro já foi carregado para esta reunião.",
"fileTooLargeDescription": "Certifique-se de que o ficheiro não exceda {{ maxFileSize }}.",
"fileTooLargeTitle": "O ficheiro selecionado é muito grande",
"fileUploadProgress": "Progresso do envio do ficheiro",
"fileUploadedSuccessfully": "Ficheiro carregado com sucesso",
"removeFile": "Remover",
"uploadFailedDescription": "Por favor, tente novamente.",
"uploadFailedTitle": "Falha ao carregar",
"uploadFile": "Partilhar ficheiro"
},
"filmstrip": {
"accessibilityLabel": {
"heading": "Miniaturas de vídeo"
@@ -697,7 +638,6 @@
"on": "Iniciada a transmissão em direto",
"onBy": "{{name}} iniciou a transmissão em direto",
"pending": "Início da transmissão em direto…",
"policyError": "Tentou iniciar uma transmissão ao vivo muito rapidamente. Por favor, tente novamente mais tarde!",
"serviceName": "Serviço de Transmissão em Direto",
"sessionAlreadyActive": "Esta sessão já está a ser gravada ou transmitida em direto.",
"signIn": "Iniciar sessão com o Google",
@@ -788,10 +728,7 @@
"me": "eu",
"notify": {
"OldElectronAPPTitle": "Vulnerabilidade de segurança!",
"allowAll": "Permitir tudo",
"allowAudio": "Permitir áudio",
"allowDesktop": "Permitir partilha de ecrã",
"allowVideo": "Permitir vídeo",
"allowAction": "Permitir",
"allowedUnmute": "Pode ligar o seu microfone, ligar a sua câmara ou partilhar o seu ecrã.",
"audioUnmuteBlockedDescription": "A operação de ligar o microfone foi temporariamente bloqueada devido aos limites do sistema.",
"audioUnmuteBlockedTitle": "Ligar microfone bloqueado!",
@@ -799,15 +736,12 @@
"connectedOneMember": "{{name}} entrou na reunião",
"connectedThreePlusMembers": "{{name}} e muitos outros entraram na reunião",
"connectedTwoMembers": "{{first}} e {{second}} entraram na reunião",
"connectionFailed": "Falha na ligação. Por favor, tente novamente mais tarde!",
"dataChannelClosed": "A qualidade do vídeo pode ser afetada",
"dataChannelClosedDescription": "O canal de ponte está em baixo e, por isso, a qualidade de vídeo pode estar limitada à sua definição mais baixa.",
"dataChannelClosedDescriptionWithAudio": "O canal de ponte está em baixo, pelo que podem ocorrer interrupções no áudio e no vídeo.",
"dataChannelClosedWithAudio": "A qualidade do áudio e do vídeo pode ser afetada",
"desktopMutedRemotelyTitle": "A partilha do seu ecrã foi interrompida por {{participantDisplayName}}",
"disabledIframe": "A incorporação destina-se apenas a fins de demonstração, pelo que esta chamada será desligada em {{timeout}} minutos.",
"disabledIframeSecondaryNative": "A incorporação de {{domain}} destina-se apenas a fins de demonstração, pelo que esta chamada será desligada em {{timeout}} minutos.",
"disabledIframeSecondaryWeb": "A incorporação de {{domain}} destina-se apenas a fins de demonstração, pelo que esta chamada será desligada em {{timeout}} minutos. Utilize <a href='{{jaasDomain}}' rel='noopener noreferrer' target='_blank'>Jitsi as a Service</a> para incorporação em produção!",
"disabledIframeSecondary": "A incorporação de {{domain}} destina-se apenas a fins de demonstração, pelo que esta chamada será desligada em {{timeout}} minutos. Por favor, use <a href='{{jaasDomain}}' rel='noopener noreferrer' target='_blank'>Jitsi as a Service</a> para incorporação em produção!",
"disconnected": "desconectado",
"displayNotifications": "Mostrar notificações para",
"dontRemindMe": "Não me lembre",
@@ -815,10 +749,7 @@
"focusFail": "{{component}} não disponĩvel - tente em {{ms}} seg.",
"gifsMenu": "GIPHY",
"groupTitle": "Notificações",
"hostAskedUnmute": "O moderador gostaria que participasse.",
"invalidTenant": "Tenant inválido",
"invalidTenantHyphenDescription": "O tenant que está a utilizar é inválido (começa ou termina com '-').",
"invalidTenantLengthDescription": "O tenant que está a utilizar é demasiado longo.",
"hostAskedUnmute": "O moderador gostaria que você falasse",
"invitedOneMember": "{{displayName}} foi convidado",
"invitedThreePlusMembers": "{{name}} e {{count}} outros foram convidados",
"invitedTwoMembers": "{{first}} e {{second}} foram convidados",
@@ -856,9 +787,9 @@
"newDeviceAudioTitle": "Novo dispositivo de áudio detetado",
"newDeviceCameraTitle": "Nova câmara detetada",
"nextToSpeak": "É o próximo na fila para falar",
"noiseSuppressionDesktopAudioDescription": "A supressão de ruído extra não pode ser ativada enquanto estiver a partilhar o áudio do ambiente de trabalho. Desative-a e tente novamente.",
"noiseSuppressionFailedTitle": "Falha ao iniciar a supressão de ruído extra",
"noiseSuppressionStereoDescription": "Atualmente, a supressão extra de ruído não é suportada com áudio estéreo.",
"noiseSuppressionDesktopAudioDescription": "A supressão de ruído não pode ser ativada enquanto se partilha o áudio do ambiente de trabalho, por favor desative-o e tente novamente.",
"noiseSuppressionFailedTitle": "Falha ao iniciar a supressão de ruído",
"noiseSuppressionStereoDescription": "A supressão do ruído de áudio estéreo não é atualmente suportada.",
"oldElectronClientDescription1": "Parece estar a utilizar uma versão antiga do cliente Jitsi Meet que tem vulnerabilidades de segurança conhecidas. Por favor, certifique-se de que actualiza a nossa ",
"oldElectronClientDescription2": "compilação mais recente",
"oldElectronClientDescription3": " agora!",
@@ -867,7 +798,7 @@
"passwordRemovedRemotely": "$t(lockRoomPasswordUppercase) removido por outro participante",
"passwordSetRemotely": "$t(lockRoomPasswordUppercase) definido por outro participante",
"raiseHandAction": "Levantar a mão",
"raisedHand": "Gostaria de participar.",
"raisedHand": "Gostaria de falar.",
"raisedHands": "{{participantName}} e mais {{raisedHands}} pessoas",
"reactionSounds": "Desactivar sons",
"reactionSoundsForAll": "Desativar sons para todos",
@@ -885,17 +816,15 @@
"suggestRecordingDescription": "Gostaria de iniciar uma gravação?",
"suggestRecordingTitle": "Gravar esta reunião",
"unmute": "Ligar microfone",
"unmuteScreen": "Iniciar partilha de ecrã",
"unmuteVideo": "Ligar câmara",
"videoMutedRemotelyDescription": "Pode sempre ligá-la novamente.",
"videoMutedRemotelyTitle": "A sua câmara foi desligada pelo {{participantDisplayName}}.",
"videoUnmuteBlockedDescription": "A operação de ligar a câmara e partilhar o ambiente de trabalho foi temporariamente bloqueada devido aos limites do sistema.",
"videoUnmuteBlockedTitle": "Está bloqueado ligar a câmara e partilhar o ambiente de trabalho!",
"viewLobby": "Ver sala de espera",
"viewParticipants": "Ver participantes",
"viewVisitors": "Visualizar espectadores",
"viewVisitors": "Ver visitantes",
"waitingParticipants": "{{waitingParticipants}} pessoas",
"waitingVisitors": "Espectadores em fila de espera: {{waitingVisitors}}",
"waitingVisitors": "Visitantes em fila de espera: {{waitingVisitors}}",
"waitingVisitorsTitle": "A reunião ainda não está em direto!",
"whiteboardLimitDescription": "Guarde o seu progresso, pois o limite de utilizadores será atingido em breve e o quadro branco será encerrado.",
"whiteboardLimitTitle": "Utilização do quadro branco"
@@ -904,17 +833,14 @@
"actions": {
"admit": "Aceitar",
"admitAll": "Aceitar todos",
"allow": "Permitir que os não moderadores:",
"allowDesktop": "Permitir partilha de ecrã",
"allow": "Permitir aos participantes:",
"allowVideo": "Permitir vídeo",
"askDesktop": "Pedir para partilhar o ecrã",
"askUnmute": "Pedir para ligar o som",
"audioModeration": "Ligar o microfone deles",
"blockEveryoneMicCamera": "Bloquear o microfone e a câmara de todos",
"breakoutRooms": "Salas simultâneas",
"desktopModeration": "Iniciar partilha de ecrã",
"goLive": "Aceder ao vivo",
"invite": "Convide alguém",
"invite": "Convidar alguém",
"lowerAllHands": "Baixar todas as mãos",
"lowerHand": "Baixar a mão",
"moreModerationActions": "Mais opções de moderação",
@@ -924,8 +850,6 @@
"muteAll": "Silenciar todos",
"muteEveryoneElse": "Silenciar todos os outros",
"reject": "Rejeitar",
"stopDesktop": "Parar a partilha de ecrã",
"stopEveryonesDesktop": "Interromper a partilha de ecrã de todos",
"stopEveryonesVideo": "Desligar a câmara de todos",
"stopVideo": "Desligar a câmara",
"unblockEveryoneMicCamera": "Desbloquear o microfone e a câmara de todos",
@@ -935,15 +859,12 @@
"headings": {
"lobby": "Sala de espera ({{count}})",
"participantsList": "Participantes da reunião ({{count}})",
"viewerRequests": "Pedidos dos espectadores {{count}}",
"visitorInQueue": " (à espera {{count}})",
"visitorRequests": " (pedidos {{count}})",
"visitors": "Espectadores ({{count}})",
"visitorsList": "Espectadores ({{count}})",
"visitors": "Visitantes ({{count}})",
"waitingLobby": "Aguardam na sala de espera ({{count}})"
},
"search": "Pesquisar participantes",
"searchDescription": "Comece a digitar para filtrar os participantes",
"title": "Participantes"
},
"passwordDigitsOnly": "Até {{number}} dígitos",
@@ -980,7 +901,7 @@
},
"results": {
"changeVote": "Mudar o voto",
"empty": "Ainda não há sondagens na reunião.",
"empty": "Ainda não há sondagens na reunião. Comece aqui uma sondagem!",
"hideDetailedResults": "Ocultar detalhes",
"showDetailedResults": "Mostrar detalhes",
"vote": "Voto"
@@ -998,11 +919,9 @@
"configuringDevices": "A configurar os dispositivos…",
"connectedWithAudioQ": "Está ligado com áudio?",
"connection": {
"failed": "Falha no teste de ligação!",
"good": "A sua ligação à Internet parece boa!",
"nonOptimal": "A sua ligação à Internet não é óptima",
"poor": "Tem uma ligação à Internet fraca",
"running": "A realizar teste de ligação..."
"poor": "Tem uma ligação à Internet"
},
"connectionDetails": {
"audioClipping": "Prevemos que o seu áudio tenha cortes.",
@@ -1011,7 +930,6 @@
"goodQuality": "Fantástico! A qualidade dos seus meios de comunicação vai ser óptima.",
"noMediaConnectivity": "Não foi possível encontrar uma forma de estabelecer a conectividade dos meios de comunicação para este teste. Isto é tipicamente causado por uma firewall ou NAT.",
"noVideo": "Prevemos que o seu vídeo seja terrível.",
"testFailed": "O teste de ligação encontrou problemas inesperados, mas isso pode não afetar a sua experiência.",
"undetectable": "Se mesmo assim não conseguir fazer chamadas no browser, recomendamos que se certifique de que os seus altifalantes, microfone e câmara estão devidamente configurados, que concedeu ao seu browser direitos de utilização do seu microfone e câmara, e que a versão do seu browser está actualizada. Se mesmo assim tiver problemas em telefonar, deverá contactar o criador da aplicação web.",
"veryPoorConnection": "Prevemos que a qualidade da sua chamada seja realmente terrível.",
"videoFreezing": "Prevemos que o seu vídeo congele, fique preto, e seja pixelizado.",
@@ -1040,7 +958,7 @@
"joinWithoutAudio": "Entrar sem áudio",
"keyboardShortcuts": "Ativar os atalhos de teclado",
"linkCopied": "Link copiado para a área de transferência",
"lookGood": "Os seus dispositivos estão a funcionar corretamente",
"lookGood": "Tudo está a funcionar corretamente",
"or": "ou",
"premeeting": "Pré-reunião",
"proceedAnyway": "Continuar na mesma",
@@ -1126,7 +1044,6 @@
"onBy": "{{name}} iniciou a gravação",
"onlyRecordSelf": "Gravar apenas as minhas transmissões áudio e vídeo",
"pending": "Preparando para gravar a reunião…",
"policyError": "Tentou iniciar uma gravação muito rapidamente. Por favor, tente novamente mais tarde!",
"recordAudioAndVideo": "Gravar áudio e vídeo",
"recordTranscription": "Gravar transcrições",
"saveLocalRecording": "Guardar ficheiro de gravação localmente (Beta)",
@@ -1170,13 +1087,11 @@
"signedIn": "Atualmente a aceder a eventos de calendário por {{email}}. Clique no botão Desconectar abaixo para parar de aceder a eventos de calendário.",
"title": "Calendário"
},
"chatWithPermissions": "O chat requer permissão",
"desktopShareFramerate": "Taxa de fotogramas para partilha do ambiente de trabalho",
"desktopShareHighFpsWarning": "Uma taxa de fotogramas mais elevada para a partilha do ambiente de trabalho pode afectar a sua largura de banda. É necessário reiniciar a partilha de ecrã para que as novas definições entrem em vigor.",
"desktopShareWarning": "É necessário reiniciar a partilha do ecrã para que as novas definições entrem em vigor.",
"devices": "Dispositivos",
"followMe": "Todos me seguem",
"followMeRecorder": "O gravador segue-me",
"framesPerSecond": "fotogramas-por-segundo",
"incomingMessage": "Receber uma mensagem",
"language": "Idioma",
@@ -1200,7 +1115,6 @@
"selectMic": "Microfone",
"selfView": "Autovisualização",
"shortcuts": "Atalhos",
"showSubtitlesOnStage": "Mostrar legendas",
"speakers": "Altifalantes",
"startAudioMuted": "Todos começam com microfone desligado",
"startReactionsMuted": "Todos começam com os sons de reação desativados",
@@ -1254,13 +1168,11 @@
"fearful": "Temeroso",
"happy": "Feliz",
"hours": "{{count}}h",
"labelTooltip": "Número de participantes: {{count}}",
"minutes": "{{count}}m",
"name": "Nome",
"neutral": "Neutro",
"sad": "Triste",
"search": "Pesquisar",
"searchDescription": "Comece a digitar para filtrar os participantes",
"searchHint": "Pesquisar participantes",
"seconds": "{{count}}s",
"speakerStats": "Estatísticas dos Participantes",
@@ -1297,7 +1209,6 @@
"closeChat": "Fechar chat",
"closeMoreActions": "Fechar menu de mais ações",
"closeParticipantsPane": "Fechar painel de participantes",
"closedCaptions": "Legendas ocultas",
"collapse": "Colapsar",
"document": "Mudar para documento partilhado",
"documentClose": "Fechar documento partilhado",
@@ -1327,7 +1238,6 @@
"lobbyButton": "Ativar/desativar sala de espera",
"localRecording": "Mudar os controlos locais de gravação",
"lockRoom": "Mudar palavra-chave de reunião",
"love": "Coração",
"lowerHand": "Baixar a mão",
"moreActions": "Mais ações",
"moreActionsMenu": "Menu de mais ações",
@@ -1338,14 +1248,13 @@
"muteEveryoneElsesVideo": "Parar o vídeo de todos os outros",
"muteEveryonesVideo": "Parar o vídeo de todos",
"muteGUMPending": "A ligar o seu microfone",
"noiseSuppression": "Supressão extra de ruído (BETA)",
"noiseSuppression": "Supressão de ruído",
"openChat": "Abrir chat",
"participants": "Abrir painel de participantes. {{participantsCount}} participantes",
"participants": "Abrir painel de participantes",
"pip": "Mudar para o modo Picture-in-Picture",
"privateMessage": "Enviar mensagem privada",
"profile": "Editar o seu perfil",
"raiseHand": "Levantar a mão",
"react": "Reações às mensagens",
"reactions": "Reações",
"reactionsMenu": "Menu de reações",
"recording": "Mudar gravação",
@@ -1388,15 +1297,14 @@
"closeChat": "Fechar chat",
"closeParticipantsPane": "Fechar painel de participantes",
"closeReactionsMenu": "Fechar menu de reações",
"closedCaptions": "Legendas ocultas",
"disableNoiseSuppression": "Desativar supressão de ruído extra (BETA)",
"disableNoiseSuppression": "Desativar a supressão de ruído",
"disableReactionSounds": "Pode desactivar os sons de reacção para esta reunião",
"documentClose": "Fechar documento partilhado",
"documentOpen": "Abrir documento partilhado",
"download": "Descarregar as nossas aplicações",
"e2ee": "Criptografia ponta a ponta",
"embedMeeting": "Incorporar reunião",
"enableNoiseSuppression": "Ativar supressão extra de ruído (BETA)",
"enableNoiseSuppression": "Ativar a supressão de ruído",
"endConference": "Terminar reunião para todos",
"enterFullScreen": "Ver em ecrã completo",
"enterTileView": "Ver em quadrícula",
@@ -1418,7 +1326,6 @@
"lobbyButtonEnable": "Ativar sala de espera",
"login": "Iniciar sessão",
"logout": "Terminar sessão",
"love": "Coração",
"lowerYourHand": "Baixar a mão",
"moreActions": "Mais ações",
"moreOptions": "Mais opções",
@@ -1431,7 +1338,7 @@
"noAudioSignalDialInDesc": "Também pode marcar usando:",
"noAudioSignalDialInLinkDesc": "Números de marcação",
"noAudioSignalTitle": "Não há nenhuma entrada vinda do seu microfone!",
"noiseSuppression": "Supressão extra de ruído (BETA)",
"noiseSuppression": "Supressão de ruído",
"noisyAudioInputDesc": "Parece que o seu microfone está a fazer barulho, por favor considere silenciar ou mudar de dispositivo.",
"noisyAudioInputTitle": "Seu microfone parece estar barulhento!",
"openChat": "Abrir chat",
@@ -1444,7 +1351,6 @@
"raiseYourHand": "Levantar a mão",
"reactionBoo": "Enviar reação de vaia",
"reactionClap": "Enviar reação de aplausos",
"reactionHeart": "Enviar reação com coração",
"reactionLaugh": "Enviar reação de risos",
"reactionLike": "Enviar reação de aprovado",
"reactionSilence": "Enviar reação de silêncio",
@@ -1478,19 +1384,15 @@
"transcribing": {
"ccButtonTooltip": "Iniciar/parar legendas",
"expandedLabel": "Transcrição ativada",
"failed": "Falha na transcrição",
"labelTooltip": "Esta reunião está a ser transcrita.",
"labelTooltipExtra": "Além disso, uma transcrição estará disponível posteriormente.",
"openClosedCaptions": "Abrir legendas ocultas",
"original": "Original",
"failedToStart": "Transcrição falhou ao iniciar",
"labelToolTip": "A reunião esta sendo transcrita",
"sourceLanguageDesc": "Atualmente a língua da reunião está definida para <b>{{sourceLanguage}}</b>. <br/> Pode alterá-la a partir ",
"sourceLanguageHere": "daqui",
"start": "Exibir legendas",
"stop": "Não exibir legendas",
"subtitles": "Legendas",
"subtitlesOff": "Desligado",
"tr": "TR",
"translateTo": "Traduzir para"
"tr": "TR"
},
"unpinParticipant": "{{participantName}} - Desafixar",
"userMedia": {
@@ -1522,7 +1424,7 @@
"ldTooltip": "Ver vídeo em baixa definição",
"lowDefinition": "Baixa definição (LD)",
"performanceSettings": "Definições de desempenho",
"recording": "Esta reunião está a ser gravada.",
"recording": "Gravação em curso",
"sd": "SD",
"sdTooltip": "Ver vídeo em definição padrão",
"standardDefinition": "Definição padrão",
@@ -1530,10 +1432,8 @@
},
"videothumbnail": {
"connectionInfo": "Informações sobre a ligação",
"demote": "Passar a espectador",
"demote": "Passar a visitante",
"domute": "Sem som",
"domuteDesktop": "Parar a partilha de ecrã",
"domuteDesktopOfOthers": "Interromper a partilha de ecrã para todos os outros",
"domuteOthers": "Silenciar todos os outros",
"domuteVideo": "Desativar a câmara",
"domuteVideoOfOthers": "Desactivar a câmara de todos os outros",
@@ -1584,21 +1484,21 @@
"webAssemblyWarningDescription": "WebAssembly desactivado ou não suportado por este navegador"
},
"visitors": {
"chatIndicator": "(espectador)",
"chatIndicator": "(visitante)",
"joinMeeting": {
"description": "Atualmente, é um espectador nesta conferência.",
"description": "Atualmente, é um observador nesta conferência.",
"raiseHand": "Levantar a mão",
"title": "Participar na reunião",
"wishToSpeak": "Se deseja intervir, levante a mão e aguarde a aprovação do moderador."
},
"labelTooltip": "Número de espectadores: {{count}}",
"labelTooltip": "Número de visitantes: {{count}}",
"notification": {
"demoteDescription": "Enviado aqui pelo {{actor}}, levante a mão para participar",
"noMainParticipantsDescription": "Um participante precisa de iniciar a reunião. Tente novamente daqui a pouco.",
"noMainParticipantsTitle": "Esta reunião ainda não começou.",
"noVisitorLobby": "Não é possível aderir enquanto houver uma sala de espera activada para a reunião.",
"notAllowedPromotion": "É necessário que um participante autorize primeiro o seu pedido.",
"title": "É um espectador na reunião"
"title": "É um visitante na reunião"
},
"waitingMessage": "Participará na reunião assim que esta estiver em direto!"
},

View File

@@ -566,7 +566,6 @@
"downloadFailedDescription": "Please try again.",
"downloadFailedTitle": "Download failed",
"downloadFile": "Download",
"downloadStarted": "File download started",
"dragAndDrop": "Drag and drop files here or anywhere on screen",
"fileAlreadyUploaded": "File has already been uploaded to this meeting.",
"fileTooLargeDescription": "Please make sure the file does not exceed {{ maxFileSize }}.",
@@ -574,7 +573,6 @@
"fileUploadProgress": "File upload progress",
"fileUploadedSuccessfully": "File uploaded successfully",
"removeFile": "Remove",
"removeFileSuccess": "File removed successfully",
"uploadFailedDescription": "Please try again.",
"uploadFailedTitle": "Upload failed",
"uploadFile": "Share file"

31
package-lock.json generated
View File

@@ -51,7 +51,6 @@
"clipboard-copy": "4.0.1",
"clsx": "1.1.1",
"dayjs": "1.11.13",
"dompurify": "3.2.6",
"dropbox": "10.7.0",
"focus-visible": "5.1.0",
"glob": "11.0.3",
@@ -7958,13 +7957,6 @@
"resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz",
"integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw=="
},
"node_modules/@types/trusted-types": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz",
"integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==",
"license": "MIT",
"optional": true
},
"node_modules/@types/unorm": {
"version": "1.3.28",
"resolved": "https://registry.npmjs.org/@types/unorm/-/unorm-1.3.28.tgz",
@@ -12445,15 +12437,6 @@
"url": "https://github.com/fb55/domhandler?sponsor=1"
}
},
"node_modules/dompurify": {
"version": "3.2.6",
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.6.tgz",
"integrity": "sha512-/2GogDQlohXPZe6D6NOgQvXLPSYBqIWMnZ8zzOhn09REE4eyAzb+Hed3jhoM9OkuaJ8P6ZGTTVWQKAi8ieIzfQ==",
"license": "(MPL-2.0 OR Apache-2.0)",
"optionalDependencies": {
"@types/trusted-types": "^2.0.7"
}
},
"node_modules/domutils": {
"version": "2.8.0",
"resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz",
@@ -32406,12 +32389,6 @@
"resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz",
"integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw=="
},
"@types/trusted-types": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz",
"integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==",
"optional": true
},
"@types/unorm": {
"version": "1.3.28",
"resolved": "https://registry.npmjs.org/@types/unorm/-/unorm-1.3.28.tgz",
@@ -35554,14 +35531,6 @@
"domelementtype": "^2.2.0"
}
},
"dompurify": {
"version": "3.2.6",
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.6.tgz",
"integrity": "sha512-/2GogDQlohXPZe6D6NOgQvXLPSYBqIWMnZ8zzOhn09REE4eyAzb+Hed3jhoM9OkuaJ8P6ZGTTVWQKAi8ieIzfQ==",
"requires": {
"@types/trusted-types": "^2.0.7"
}
},
"domutils": {
"version": "2.8.0",
"resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz",

View File

@@ -57,7 +57,6 @@
"clipboard-copy": "4.0.1",
"clsx": "1.1.1",
"dayjs": "1.11.13",
"dompurify": "3.2.6",
"dropbox": "10.7.0",
"focus-visible": "5.1.0",
"glob": "11.0.3",

View File

@@ -2,7 +2,6 @@ import { IReduxState } from '../app/types';
import { IStateful } from '../base/app/types';
import { toState } from '../base/redux/functions';
import { cleanSvg } from './functions';
import logger from './logger';
/**
@@ -83,7 +82,7 @@ export const fetchCustomIcons = async (customIcons: Record<string, string>) => {
if (response.ok) {
const svgXml = await response.text();
localCustomIcons[key] = cleanSvg(svgXml);
localCustomIcons[key] = svgXml;
} else {
logger.error(`Failed to fetch ${url}. Status: ${response.status}`);
}

View File

@@ -1,9 +0,0 @@
/**
* Sanitizes the given SVG by removing dangerous elements.
*
* @param {string} svg - The SVG string to clean.
* @returns {string} The sanitized SVG string.
*/
export function cleanSvg(svg: string): string {
return svg;
}

View File

@@ -1,22 +1,11 @@
import { Theme } from '@mui/material';
import { adaptV4Theme, createTheme } from '@mui/material/styles';
import DOMPurify from 'dompurify';
import { breakpoints, colorMap, font, shape, spacing, typography } from '../base/ui/Tokens';
import { createColorTokens } from '../base/ui/utils';
const DEFAULT_FONT_SIZE = 16;
/**
* Sanitizes the given SVG by removing dangerous elements.
*
* @param {string} svg - The SVG string to clean.
* @returns {string} The sanitized SVG string.
*/
export function cleanSvg(svg: string): string {
return DOMPurify.sanitize(svg);
}
/**
* Converts unitless fontSize and lineHeight values in a typography style object to rem units.
* Backward compatibility: This conversion supports custom themes that may still override

View File

@@ -23,13 +23,14 @@ const useStyles = makeStyles()(theme => {
return {
buttonContainer: {
alignItems: 'center',
bottom: 0,
display: 'flex',
justifyContent: 'end',
gap: theme.spacing(2),
position: 'absolute',
top: 0,
right: theme.spacing(3),
top: 0
bottom: 0,
left: 0
},
container: {
@@ -79,33 +80,17 @@ const useStyles = makeStyles()(theme => {
padding: theme.spacing(3),
position: 'relative',
'& .actionIconVisibility': {
opacity: 0,
transition: 'opacity 0.2s'
},
'& .timestampVisibility': {
opacity: 1
},
'&:hover': {
backgroundColor: theme.palette.ui03,
borderRadius: theme.shape.borderRadius,
'& .actionIconVisibility': {
opacity: 1
visibility: 'visible'
},
'& .timestampVisibility': {
opacity: 0
visibility: 'hidden'
}
},
'&.focused .actionIconVisibility': {
opacity: 1
},
'&.focused .timestampVisibility': {
opacity: 0
}
},
@@ -213,30 +198,9 @@ const useStyles = makeStyles()(theme => {
},
actionIcon: {
background: 'transparent',
border: 0,
cursor: 'pointer',
padding: theme.spacing(1),
visibility: 'hidden',
'&:focus': {
outline: `2px solid ${theme.palette.action01}`
}
},
iconButton: {
background: 'none',
border: 'none',
padding: 0,
marginLeft: '8px',
cursor: 'pointer',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
'&:focus-visible': {
outline: `2px solid ${theme.palette.action01}`,
borderRadius: '4px'
}
visibility: 'hidden'
}
};
});
@@ -244,7 +208,6 @@ const useStyles = makeStyles()(theme => {
const FileSharing = () => {
const { classes } = useStyles();
const [ isDragging, setIsDragging ] = useState(false);
const [ isFocused, setIsFocused ] = useState(false);
const fileInputRef = useRef<HTMLInputElement>(null);
const uploadButtonRef = useRef<HTMLButtonElement>(null);
const { t } = useTranslation();
@@ -301,7 +264,6 @@ const FileSharing = () => {
}
}, []);
/* eslint-disable react/jsx-no-bind */
return (
<div className = { classes.container }>
{
@@ -350,12 +312,8 @@ const FileSharing = () => {
{
sortedFiles.map(file => (
<li
className = { `${classes.fileItem} ${isFocused ? 'focused' : ''}` }
className = { classes.fileItem }
key = { file.fileId }
// Only remove focus when leaving the whole fileItem, not just moving between its buttons
onBlur = { e => !e.currentTarget.contains(e.relatedTarget as Node) && setIsFocused(false) }
onFocus = { () => setIsFocused(true) }
tabIndex = { -1 }
title = { file.fileName }>
{
(file.progress ?? 100) === 100 && (
@@ -388,30 +346,25 @@ const FileSharing = () => {
{ formatTimestamp(file.timestamp) }
</pre>
</div>
<div className = { `${classes.buttonContainer} actionIconVisibility` }>
<button
aria-label = { `${t('fileSharing.downloadFile')} ${file.fileName}` }
className = { `${classes.iconButton}` }
onClick = { () => dispatch(downloadFile(file.fileId)) }
type = 'button'>
<Icon
color = { BaseTheme.palette.icon01 }
size = { 24 }
src = { IconDownload } />
</button>
<div className = { classes.buttonContainer }>
<Icon
className = { `${classes.actionIcon} actionIconVisibility` }
color = { BaseTheme.palette.icon01 }
// eslint-disable-next-line react/jsx-no-bind
onClick = { () => dispatch(downloadFile(file.fileId)) }
size = { 24 }
src = { IconDownload } />
{
isUploadEnabled && (
<button
aria-label = { `${t('fileSharing.removeFile')} ${file.fileName}` }
className = { `${classes.iconButton}` }
<Icon
className = { `${classes.actionIcon} actionIconVisibility` }
color = { BaseTheme.palette.icon01 }
// eslint-disable-next-line react/jsx-no-bind
onClick = { () => dispatch(removeFile(file.fileId)) }
type = 'button'>
<Icon
color = { BaseTheme.palette.icon01 }
size = { 24 }
src = { IconTrash } />
</button>
size = { 24 }
src = { IconTrash } />
)
}
</div>

View File

@@ -6,7 +6,7 @@ import { JitsiConferenceEvents } from '../base/lib-jitsi-meet';
import { getLocalParticipant, getParticipantDisplayName } from '../base/participants/functions';
import MiddlewareRegistry from '../base/redux/MiddlewareRegistry';
import StateListenerRegistry from '../base/redux/StateListenerRegistry';
import { showErrorNotification, showNotification, showSuccessNotification } from '../notifications/actions';
import { showErrorNotification, showSuccessNotification } from '../notifications/actions';
import { NOTIFICATION_TIMEOUT_TYPE, NOTIFICATION_TYPE } from '../notifications/constants';
import { DOWNLOAD_FILE, REMOVE_FILE, UPLOAD_FILES, _FILE_LIST_RECEIVED, _FILE_REMOVED } from './actionTypes';
@@ -101,9 +101,6 @@ MiddlewareRegistry.register(store => next => action => {
if (!response.ok) {
throw new Error(`Failed to delete file: ${response.statusText}`);
}
store.dispatch(showSuccessNotification({
titleKey: 'fileSharing.removeFileSuccess'
}, NOTIFICATION_TIMEOUT_TYPE.SHORT));
})
.catch((error: any) => {
logger.warn('Could not delete file:', error);
@@ -133,10 +130,6 @@ MiddlewareRegistry.register(store => next => action => {
throw new Error('No presigned URL found in the response.');
}
store.dispatch(showNotification({
titleKey: 'fileSharing.downloadStarted'
}, NOTIFICATION_TIMEOUT_TYPE.SHORT));
return downloadFile(presignedUrl, fileName);
})
.catch((error: any) => {

View File

@@ -154,7 +154,7 @@ const Notification = ({
<>
<Text
numberOfLines = { 1 }
style = { styles.contentTextTitleDescription as TextStyle }>
style = { styles.contentTextTitle as TextStyle }>
{ titleText }
</Text>
{

View File

@@ -45,22 +45,15 @@ export default {
contentText: {
color: BaseTheme.palette.text04,
paddingLeft: BaseTheme.spacing[4],
paddingLeft: BaseTheme.spacing[5],
paddingTop: BaseTheme.spacing[1]
},
contentTextTitleDescription: {
color: BaseTheme.palette.text04,
fontWeight: 'bold',
paddingLeft: BaseTheme.spacing[4],
paddingTop: BaseTheme.spacing[2]
},
contentTextTitle: {
color: BaseTheme.palette.text04,
fontWeight: 'bold',
paddingLeft: BaseTheme.spacing[4],
paddingTop: BaseTheme.spacing[3]
paddingLeft: BaseTheme.spacing[5],
paddingTop: BaseTheme.spacing[2]
},
/**
@@ -106,7 +99,7 @@ export default {
btnContainer: {
display: 'flex',
flexDirection: 'row',
paddingLeft: BaseTheme.spacing[3],
paddingLeft: BaseTheme.spacing[4],
paddingTop: BaseTheme.spacing[1]
},

View File

@@ -9,9 +9,7 @@ import { translate } from '../../../../base/i18n/functions';
import { navigate }
from '../../../../mobile/navigation/components/conference/ConferenceNavigationContainerRef';
import { screen } from '../../../../mobile/navigation/routes';
import {
IProps, _mapStateToProps as abstractStartLiveStreamDialogMapStateToProps
} from '../../LiveStream/AbstractStartLiveStreamDialog';
import { IProps, _mapStateToProps as abstractMapStateToProps } from '../../LiveStream/AbstractStartLiveStreamDialog';
import AbstractRecordButton, {
IProps as AbstractProps,
_mapStateToProps as _abstractMapStateToProps
@@ -60,8 +58,8 @@ export function mapStateToProps(state: IReduxState) {
return {
...abstractProps,
...abstractStartLiveStreamDialogMapStateToProps(state),
visible: Boolean(enabled && iosEnabled && abstractProps.visible)
...abstractMapStateToProps(state),
visible: enabled && iosEnabled && abstractProps.visible
};
}

View File

@@ -7,7 +7,6 @@ import { JitsiRecordingConstants } from '../base/lib-jitsi-meet';
import { getSoundFileSrc } from '../base/media/functions';
import { getLocalParticipant, getRemoteParticipants } from '../base/participants/functions';
import { registerSound, unregisterSound } from '../base/sounds/actions';
import { isEmbedded } from '../base/util/embedUtils';
import { isSpotTV } from '../base/util/spot';
import { isInBreakoutRoom as isInBreakoutRoomF } from '../breakout-rooms/functions';
import { isEnabled as isDropboxEnabled } from '../dropbox/functions';
@@ -152,7 +151,7 @@ export function getSessionStatusToShow(state: IReduxState, mode: string): string
* @returns {boolean} - Whether local recording is supported or not.
*/
export function supportsLocalRecording() {
return LocalRecordingManager.isSupported() && !isEmbedded();
return LocalRecordingManager.isSupported();
}
/**

View File

@@ -352,8 +352,6 @@ function occupant_joined(event)
start_av_moderation(room, mediaType, occupant);
notify_occupants_enable(nil, true, room, occupant.nick, mediaType);
notify_whitelist_change(nil, true, room, mediaType);
end
room._data.av_first_moderator_joined = true;

View File

@@ -70,7 +70,7 @@ function process_set_affiliation(event)
return;
end
if (previous_affiliation == 'none' or previous_affiliation == 'member') and affiliation == 'owner' then
if previous_affiliation == 'none' and affiliation == 'owner' then
occupant_session.jitsi_meet_context_features = actor_session.jitsi_meet_context_features;
if actor_session.jitsi_meet_context_user then
occupant_session.granted_jitsi_meet_context_user_id = actor_session.jitsi_meet_context_user['id']

View File

@@ -62,7 +62,9 @@ function getMetadataJSON(room, metadata)
return res;
end
function broadcastMetadata(room, json_msg)
function broadcastMetadata(room)
local json_msg = getMetadataJSON(room);
if not json_msg then
return;
end
@@ -97,8 +99,6 @@ function send_metadata(occupant, room, json_msg)
metadata_to_send = table_shallow_copy(metadata_to_send);
metadata_to_send.participants = participants;
metadata_to_send.moderators = moderators;
module:log('info', 'Sending metadata to jicofo room=%s,meeting_id=%s', room.jid, room._data.meeting_id);
end
json_msg = getMetadataJSON(room, metadata_to_send);
@@ -193,8 +193,7 @@ function on_message(event)
if not table_equals(old_value, jsonData.data) then
room.jitsiMetadata[jsonData.key] = jsonData.data;
module:log('info', 'Мetadata key "%s" updated by %s in room:%s,meeting_id:%s', jsonData.key, from, room.jid, room._data.meeting_id);
broadcastMetadata(room, getMetadataJSON(room));
broadcastMetadata(room);
-- fire and event for the change
main_muc_module:fire_event('jitsi-metadata-updated', { room = room; actor = occupant; key = jsonData.key; });
@@ -219,11 +218,7 @@ function process_main_muc_loaded(main_muc, host_module)
-- The room metadata was updated internally (from another module).
host_module:hook("room-metadata-changed", function(event)
local room = event.room;
local json_msg = getMetadataJSON(room);
module:log('info', 'Metadata changed internally in room:%s,meeting_id:%s - broadcasting data:%s', room.jid, room._data.meeting_id, json_msg);
broadcastMetadata(room, json_msg);
broadcastMetadata(event.room);
end);
-- TODO: Once clients update to read/write metadata for startMuted policy we can drop this

View File

@@ -188,7 +188,7 @@ export class Participant {
* @param {IParticipantJoinOptions} options - Options for joining.
* @returns {Promise<void>}
*/
async joinConference(options: IParticipantJoinOptions): Promise<Participant> {
async joinConference(options: IParticipantJoinOptions): Promise<void> {
const config = {
room: options.roomName,
configOverwrite: {
@@ -254,8 +254,6 @@ export class Participant {
}
await this.postLoadProcess();
return this;
}
/**
@@ -468,7 +466,7 @@ export class Participant {
}
/**
* Waits until there are at least [number] participants that have at least one track.
* Waits for remote streams.
*
* @param {number} number - The number of remote streams to wait for.
* @returns {Promise<boolean>}
@@ -874,46 +872,26 @@ export class Participant {
}
}
/**
* Checks if video is currently received for the given remote endpoint ID (there is a track, it's not muted,
* and it's streaming status according to the connection-indicator is active).
*/
async isRemoteVideoReceived(endpointId: string): Promise<boolean> {
return this.execute(e => JitsiMeetJS.app.testing.isRemoteVideoReceived(e), endpointId);
}
/**
* Checks if the remove video is displayed for the given remote endpoint ID.
* @param endpointId
*/
async isRemoteVideoDisplayed(endpointId: string): Promise<boolean> {
return this.driver.$(
`//span[@id="participant_${endpointId}" and contains(@class, "display-video")]`).isExisting();
}
/**
* Check if remote video for a specific remote endpoint is both received and displayed.
* @param endpointId
*/
async isRemoteVideoReceivedAndDisplayed(endpointId: string): Promise<boolean> {
return await this.isRemoteVideoReceived(endpointId) && await this.isRemoteVideoDisplayed(endpointId);
}
/**
* Waits for remote video state - receiving and displayed.
* @param endpointId
* @param reverse if true, waits for the remote video to NOT be received AND NOT displayed.
* @param reverse
*/
async waitForRemoteVideo(endpointId: string, reverse = false) {
if (reverse) {
await this.driver.waitUntil(async () =>
!await this.isRemoteVideoReceived(endpointId) && !await this.isRemoteVideoDisplayed(endpointId), {
!await this.execute(epId => JitsiMeetJS.app.testing.isRemoteVideoReceived(`${epId}`),
endpointId) && !await this.driver.$(
`//span[@id="participant_${endpointId}" and contains(@class, "display-video")]`).isExisting(), {
timeout: 15_000,
timeoutMsg: `expected remote video for ${endpointId} to not be received 15s by ${this.name}`
});
} else {
await this.driver.waitUntil(async () =>
await this.isRemoteVideoReceivedAndDisplayed(endpointId), {
await this.execute(epId => JitsiMeetJS.app.testing.isRemoteVideoReceived(`${epId}`),
endpointId) && await this.driver.$(
`//span[@id="participant_${endpointId}" and contains(@class, "display-video")]`).isExisting(), {
timeout: 15_000,
timeoutMsg: `expected remote video for ${endpointId} to be received 15s by ${this.name}`
});

View File

@@ -107,7 +107,7 @@ export default class WebhookProxy {
* @param eventType
* @param timeout
*/
async waitForEvent(eventType: string, timeout = 120000): Promise<any> {
async waitForEvent(eventType: string, timeout = 4000): Promise<any> {
// we create the error here so we have a meaningful stack trace
const error = new Error(`Timeout waiting for event:${eventType}`);

View File

@@ -28,7 +28,6 @@ export async function ensureOneParticipant(options?: IJoinOptions): Promise<void
participantOps.token = generateToken({
...options?.tokenOptions,
displayName: participantOps.name,
moderator: true
});
}
}
@@ -149,10 +148,7 @@ export async function ensureTwoParticipants(options?: IJoinOptions): Promise<voi
});
}
await joinParticipant({
...participantOptions,
name: P2
}, options);
await joinParticipant({ name: P2 }, options);
if (options?.skipInMeetingChecks) {
return Promise.resolve();
@@ -169,16 +165,14 @@ export async function ensureTwoParticipants(options?: IJoinOptions): Promise<voi
}
/**
* Creates a new participant instance, or returns an existing one if it is already joined.
* Creates a participant instance or prepares one for re-joining.
* @param participantOptions - The participant options, with required name set.
* @param {boolean} options - Join options.
* @param reuse whether to reuse an existing participant instance if one is available.
* @returns {Promise<Participant>} - The participant instance.
*/
async function joinParticipant( // eslint-disable-line max-params
export async function joinParticipant( // eslint-disable-line max-params
participantOptions: IParticipantOptions,
options?: IJoinOptions
): Promise<Participant> {
options?: IJoinOptions): Promise<Participant> {
participantOptions.iFrameApi = ctx.testProperties.useIFrameApi;
@@ -201,6 +195,8 @@ async function joinParticipant( // eslint-disable-line max-params
// Change the page so we can reload same url if we need to, base.html is supposed to be empty or close to empty
await p.driver.url('/base.html');
// we want the participant instance re-recreated so we clear any kept state, like endpoint ID
}
const newParticipant = new Participant(participantOptions);
@@ -215,12 +211,13 @@ async function joinParticipant( // eslint-disable-line max-params
&& config.iframe.usesJaas && config.iframe.tenant) {
forceTenant = config.iframe.tenant;
}
return await newParticipant.joinConference({
await newParticipant.joinConference({
...options,
forceTenant,
roomName: options?.roomName || ctx.roomName,
});
return newParticipant;
}
/**

View File

@@ -26,11 +26,7 @@ export type IContext = {
**/
testProperties: ITestProperties;
times: any;
/**
* A WebhooksProxy instance generated by the framework and available for tests to use, if configured.
* Note that this is only configured for roomName, if a test wishes to use a different room name it can set up
* a WebhooksProxy instance itself.
*/
/** A WebhooksProxy instance generated by the framework and available for tests to use, if configured. */
webhooksProxy: WebhookProxy;
};

View File

@@ -41,10 +41,6 @@ export default class IframeAPI extends BasePageObject {
addEventListener(eventName: string) {
return this.participant.execute(
(event, prefix) => {
// we want to add it once as we use static .test[event] to store the last event
if (window.jitsiAPI.listenerCount(event) > 0) {
return;
}
console.log(`${new Date().toISOString()} ${prefix}iframeAPI - Adding listener for event: ${event}`);
window.jitsiAPI.addListener(event, evt => {
console.log(

View File

@@ -95,7 +95,6 @@
if (event.role === "moderator" && event.id === window.jitsiAPI.test.myEndpointId) {
window.jitsiAPI.test.isModerator = true;
}
window.jitsiAPI.test['participantRoleChanged'] = event;
});
window.jitsiAPI.addEventListener('audioAvailabilityChanged', function(event) {
log(`audioAvailabilityChanged: ${JSON.stringify(event)}`);

View File

@@ -1,7 +1,7 @@
import { Participant } from '../../helpers/Participant';
import type { Participant } from '../../helpers/Participant';
import { config } from '../../helpers/TestsConfig';
import { joinParticipant } from '../../helpers/participants';
import { IToken, ITokenOptions, generateToken } from '../../helpers/token';
import { IParticipantJoinOptions } from '../../helpers/types';
export function generateJaasToken(options: ITokenOptions): IToken {
if (!config.jaas.enabled) {
@@ -17,46 +17,27 @@ export function generateJaasToken(options: ITokenOptions): IToken {
}
/**
* Creates a new Participant and joins the MUC with the given options. The jaas-specific properties must be set as
* environment variables (see env.example and TestsConfig.ts). If no room name is specified, the default room name
* from the context is used.
* Creates a new Participant and joins the MUC with the given name. The jaas-specific properties must be set as
* environment variables (see env.example and TestsConfig.ts).
*
* @param instanceId This is the "name" passed to the Participant, I think it's used to match against one of the
* pre-configured browser instances in wdio? It must be one of 'p1', 'p2', 'p3', or 'p4'. TODO: figure out how this
* should be used.
* @param token the token to use, if any.
* @param joinOptions options to use when joining the MUC.
* @param roomName the name of the room to join, if any. If not provided, the ctx generated one will be used.
* @returns {Promise<Participant>} The Participant that has joined the MUC.
*/
export async function joinMuc(
instanceId: 'p1' | 'p2' | 'p3' | 'p4',
token?: IToken,
joinOptions?: Partial<IParticipantJoinOptions>): Promise<Participant> {
export async function joinMuc(instanceId: 'p1' | 'p2' | 'p3' | 'p4', token?: IToken, roomName?: string):
Promise<Participant> {
if (!config.jaas.enabled) {
throw new Error('JaaS is not configured.');
}
// @ts-ignore
const p = ctx[instanceId] as Participant;
if (p) {
// Load a blank page to make sure the page is reloaded (in case the new participant uses the same URL). Using
// 'about:blank' was causing problems in the past, if we notice any issues we can change to "base.html".
await p.driver.url('about:blank');
}
const newParticipant = new Participant({
return await joinParticipant({
name: instanceId,
token
});
// @ts-ignore
ctx[instanceId] = newParticipant;
return await newParticipant.joinConference({
...joinOptions,
}, {
forceTenant: config.jaas.tenant,
roomName: joinOptions?.roomName || ctx.roomName,
roomName
});
}

View File

@@ -146,7 +146,7 @@ describe('Chat', () => {
preAuthenticatedLink: string;
};
eventType: string;
} = await webhooksProxy.waitForEvent('CHAT_UPLOADED');
} = await webhooksProxy.waitForEvent('CHAT_UPLOADED', 120000);
expect('CHAT_UPLOADED').toBe(event.eventType);
expect(event.data.preAuthenticatedLink).toBeDefined();

View File

@@ -114,7 +114,7 @@ describe('Invite iframeAPI', () => {
sipAddress: string;
};
eventType: string;
} = await webhooksProxy.waitForEvent('SIP_CALL_OUT_STARTED');
} = await webhooksProxy.waitForEvent('SIP_CALL_OUT_STARTED', 120000);
expect('SIP_CALL_OUT_STARTED').toBe(sipCallOutStartedEvent.eventType);
expect(sipCallOutStartedEvent.data.sipAddress).toBe(`sip:${process.env.SIP_JIBRI_DIAL_OUT_URL}`);
@@ -201,7 +201,7 @@ async function checkDialEvents(participant: Participant, direction: string, star
participantJid: string;
};
eventType: string;
} = await webhooksProxy.waitForEvent(endedEventName);
} = await webhooksProxy.waitForEvent(endedEventName, 120000);
expect(endedEventName).toBe(dialInEndedEvent.eventType);
expect(dialInEndedEvent.customerId).toBe(customerId);

View File

@@ -100,6 +100,11 @@ describe('Participants presence', () => {
expect(event.data.filter(d => d.participantId === p1EpId
|| d.participantId === p2EpId).length).toBe(2);
}
// we will use it later
// TODO figure out why adding those just before grantModerator and we miss the events
await p1.getIframeAPI().addEventListener('participantRoleChanged');
await p2.getIframeAPI().addEventListener('participantRoleChanged');
});
it('participants info',
@@ -167,9 +172,6 @@ describe('Participants presence', () => {
const { p1, p2, webhooksProxy } = ctx;
const p2EpId = await p2.getEndpointId();
await p1.getIframeAPI().clearEventResults('participantRoleChanged');
await p2.getIframeAPI().clearEventResults('participantRoleChanged');
await p1.getIframeAPI().executeCommand('grantModerator', p2EpId);
await p2.driver.waitUntil(() => p2.getIframeAPI().getEventResult('isModerator'), {
@@ -177,25 +179,12 @@ describe('Participants presence', () => {
timeoutMsg: 'Moderator role not granted'
});
type RoleChangedEvent = {
id: string;
role: string;
};
const event1: RoleChangedEvent = await p1.driver.waitUntil(
() => p1.getIframeAPI().getEventResult('participantRoleChanged'), {
timeout: 3000,
timeoutMsg: 'Role was not update on p1 side'
});
const event1 = await p1.getIframeAPI().getEventResult('participantRoleChanged');
expect(event1?.id).toBe(p2EpId);
expect(event1?.role).toBe('moderator');
const event2: RoleChangedEvent = await p2.driver.waitUntil(
() => p2.getIframeAPI().getEventResult('participantRoleChanged'), {
timeout: 3000,
timeoutMsg: 'Role was not update on p2 side'
});
const event2 = await p2.getIframeAPI().getEventResult('participantRoleChanged');
expect(event2?.id).toBe(p2EpId);
expect(event2?.role).toBe('moderator');
@@ -257,8 +246,6 @@ describe('Participants presence', () => {
await p1.getIframeAPI().addEventListener('participantKickedOut');
await p2.getIframeAPI().addEventListener('participantKickedOut');
await p2.getIframeAPI().clearEventResults('videoConferenceLeft');
await p2.getIframeAPI().addEventListener('videoConferenceLeft');
await p1.getIframeAPI().executeCommand('kickParticipant', p2EpId);
@@ -395,7 +382,6 @@ describe('Participants presence', () => {
await p1.switchToAPI();
await p2.switchToAPI();
await p2.getIframeAPI().clearEventResults('videoConferenceLeft');
await p2.getIframeAPI().addEventListener('videoConferenceLeft');
await p2.getIframeAPI().addEventListener('readyToClose');
@@ -425,7 +411,6 @@ describe('Participants presence', () => {
await p1.switchToAPI();
await p1.getIframeAPI().clearEventResults('videoConferenceLeft');
await p1.getIframeAPI().addEventListener('videoConferenceLeft');
await p1.getIframeAPI().addEventListener('readyToClose');

View File

@@ -75,7 +75,7 @@ describe('Recording', () => {
const liveStreamEvent: {
customerId: string;
eventType: string;
} = await webhooksProxy.waitForEvent('LIVE_STREAM_STARTED');
} = await webhooksProxy.waitForEvent('LIVE_STREAM_STARTED', 15000);
expect('LIVE_STREAM_STARTED').toBe(liveStreamEvent.eventType);
expect(liveStreamEvent.customerId).toBe(customerId);
@@ -98,7 +98,7 @@ describe('Recording', () => {
const liveStreamEvent: {
customerId: string;
eventType: string;
} = await webhooksProxy.waitForEvent('LIVE_STREAM_ENDED');
} = await webhooksProxy.waitForEvent('LIVE_STREAM_ENDED', 120000);
expect('LIVE_STREAM_ENDED').toBe(liveStreamEvent.eventType);
expect(liveStreamEvent.customerId).toBe(customerId);
@@ -136,7 +136,7 @@ async function testRecordingStarted(command: boolean) {
const recordingEvent: {
customerId: string;
eventType: string;
} = await webhooksProxy.waitForEvent('RECORDING_STARTED');
} = await webhooksProxy.waitForEvent('RECORDING_STARTED', 15000);
expect('RECORDING_STARTED').toBe(recordingEvent.eventType);
expect(recordingEvent.customerId).toBe(customerId);
@@ -174,7 +174,7 @@ async function testRecordingStopped(command: boolean) {
const liveStreamEvent: {
customerId: string;
eventType: string;
} = await webhooksProxy.waitForEvent('RECORDING_ENDED');
} = await webhooksProxy.waitForEvent('RECORDING_ENDED', 20000);
expect('RECORDING_ENDED').toBe(liveStreamEvent.eventType);
expect(liveStreamEvent.customerId).toBe(customerId);
@@ -186,7 +186,7 @@ async function testRecordingStopped(command: boolean) {
participants: Array<string>;
};
eventType: string;
} = await webhooksProxy.waitForEvent('RECORDING_UPLOADED');
} = await webhooksProxy.waitForEvent('RECORDING_UPLOADED', 20000);
const jwtPayload = p1.getToken()?.payload;

View File

@@ -147,7 +147,7 @@ describe('Transcriptions', () => {
// sometimes events are not immediately received,
// let's wait for destroy event before waiting for those that depends on it
await webhooksProxy.waitForEvent('ROOM_DESTROYED');
await webhooksProxy.waitForEvent('ROOM_DESTROYED', 10000);
if (webhooksProxy) {
const event: {
@@ -155,7 +155,7 @@ describe('Transcriptions', () => {
preAuthenticatedLink: string;
};
eventType: string;
} = await webhooksProxy.waitForEvent('TRANSCRIPTION_UPLOADED');
} = await webhooksProxy.waitForEvent('TRANSCRIPTION_UPLOADED', 20000);
expect('TRANSCRIPTION_UPLOADED').toBe(event.eventType);
expect(event.data.preAuthenticatedLink).toBeDefined();
@@ -193,7 +193,7 @@ async function checkReceivingChunks(p1: Participant, p2: Participant, webhooksPr
stable: string;
};
eventType: string;
} = await webhooksProxy.waitForEvent('TRANSCRIPTION_CHUNK_RECEIVED');
} = await webhooksProxy.waitForEvent('TRANSCRIPTION_CHUNK_RECEIVED', 60000);
expect('TRANSCRIPTION_CHUNK_RECEIVED').toBe(event.eventType);

View File

@@ -13,6 +13,8 @@ describe('XMPP login and MUC join test', () => {
expect(await p.isInMuc()).toBe(true);
expect(await p.isModerator()).toBe(false);
await p.hangup();
});
it('with a valid token (specific room)', async () => {
@@ -21,6 +23,8 @@ describe('XMPP login and MUC join test', () => {
expect(await p.isInMuc()).toBe(true);
expect(await p.isModerator()).toBe(false);
await p.hangup();
});
it('with a token with bad signature', async () => {
@@ -37,6 +41,8 @@ describe('XMPP login and MUC join test', () => {
|| await p.getNotifications().getNotificationText(TOKEN_AUTH_FAILED_TITLE_TEST_ID);
expect(errorText).toContain('not allowed to join');
await p.hangup();
});
it('with an expired token', async () => {
@@ -48,6 +54,8 @@ describe('XMPP login and MUC join test', () => {
const errorText = await p.getNotifications().getNotificationText(TOKEN_AUTH_FAILED_TITLE_TEST_ID);
expect(errorText).toContain('Token is expired');
await p.hangup();
});
it('with a token using the wrong key ID', async () => {
@@ -59,6 +67,8 @@ describe('XMPP login and MUC join test', () => {
const errorText = await p.getNotifications().getNotificationText(TOKEN_AUTH_FAILED_TEST_ID);
expect(errorText).toContain('not allowed to join');
await p.hangup();
});
it('with a token for a different room', async () => {
@@ -70,6 +80,8 @@ describe('XMPP login and MUC join test', () => {
const errorText = await p.getNotifications().getNotificationText(TOKEN_AUTH_FAILED_TEST_ID);
expect(errorText).toContain('not allowed to join');
await p.hangup();
});
it('with a moderator token', async () => {
@@ -78,6 +90,8 @@ describe('XMPP login and MUC join test', () => {
expect(await p.isInMuc()).toBe(true);
expect(await p.isModerator()).toBe(true);
await p.hangup();
});
// This is dependent on jaas account configuration. All tests under jaas/ expect that "unauthenticated access" is
@@ -91,6 +105,8 @@ describe('XMPP login and MUC join test', () => {
const errorText = await p.getNotifications().getNotificationText(TOKEN_AUTH_FAILED_TEST_ID);
expect(errorText).toContain('not allowed to join');
await p.hangup();
});
// it('without sending a conference-request', async () => {

View File

@@ -4,8 +4,7 @@ import { joinMuc, generateJaasToken as t } from '../helpers/jaas';
setTestProperties(__filename, {
useJaas: true,
useWebhookProxy: true,
usesBrowsers: [ 'p1', 'p2' ]
useWebhookProxy: true
});
const passcode = '1234';
@@ -17,11 +16,22 @@ describe('Setting passcode through settings provisioning', () => {
visitorsEnabled: true
};
// We want to keep the room from getting destroyed, because the visitors queue has a timeout and causes
// problems. We could use different rooms instead, but the webhooksProxy is only configured for the default room.
await joinWithPassword('p1', t({ room: ctx.roomName }));
await joinWithPassword('p2', t({ room: ctx.roomName, moderator: true }));
await joinWithPassword('p2', t({ room: ctx.roomName, visitor: true }));
await joinWithPassword('p1', t({ room: ctx.roomName, moderator: true }));
await joinWithPassword('p1', t({ room: ctx.roomName, visitor: true }));
});
it('With an invalid passcode', async () => {
ctx.webhooksProxy.defaultMeetingSettings = {
passcode: 'passcode-must-be-digits-only'
};
const roomName = ctx.roomName + '-2';
const p = await joinMuc('p1', t({ room: roomName }), roomName);
// Setting the passcode should fail, resulting in the room being accessible without a password
await p.waitToJoinMUC();
expect(await p.isInMuc()).toBe(true);
expect(await p.getPasswordDialog().isOpen()).toBe(false);
});
});
@@ -31,7 +41,7 @@ describe('Setting passcode through settings provisioning', () => {
*/
async function joinWithPassword(instanceId: string, token: IToken) {
// @ts-ignore
const p = await joinMuc(instanceId, token, ctx.roomName);
const p = await joinMuc(instanceId, token);
await p.waitForMucJoinedOrError();
expect(await p.isInMuc()).toBe(false);
@@ -47,5 +57,7 @@ async function joinWithPassword(instanceId: string, token: IToken) {
expect(await p.isInMuc()).toBe(true);
expect(await p.getPasswordDialog().isOpen()).toBe(false);
await p.hangup();
}

View File

@@ -1,25 +0,0 @@
import { setTestProperties } from '../../helpers/TestProperties';
import { joinMuc, generateJaasToken as t } from '../helpers/jaas';
setTestProperties(__filename, {
useJaas: true,
useWebhookProxy: true
});
// This test is separate from passcode.spec.ts, because it needs to use a different room name, and webhooksProxy is only
// setup for the default room name.
describe('Setting passcode through settings provisioning', () => {
it('With an invalid passcode', async () => {
ctx.webhooksProxy.defaultMeetingSettings = {
passcode: 'passcode-must-be-digits-only'
};
const p = await joinMuc('p1', t({ room: ctx.roomName }), ctx.roomName);
// The settings provisioning contains an invalid passcode, the expected result is that the room is not
// configured to require a passcode.
await p.waitToJoinMUC();
expect(await p.isInMuc()).toBe(true);
expect(await p.getPasswordDialog().isOpen()).toBe(false);
});
});

View File

@@ -1,61 +0,0 @@
import { setTestProperties } from '../../../helpers/TestProperties';
import { joinMuc, generateJaasToken as t } from '../../helpers/jaas';
setTestProperties(__filename, {
useJaas: true,
useWebhookProxy: true,
usesBrowsers: [ 'p1', 'p2', 'p3', 'p4' ]
});
/**
* This is a case which fails if jitsi-videobridge doesn't properly forward PLIs from visitors.
*/
describe('Visitor receiving video from a single remote participant', () => {
it('joining the meeting', async () => {
ctx.webhooksProxy.defaultMeetingSettings = {
visitorsEnabled: true,
visitorsLive: true,
};
// Force a connection via JVB.
const configOverwrite = {
p2p: {
enabled: false
}
};
const sender = await joinMuc(
'p1',
t({ room: ctx.roomName, displayName: 'Sender', moderator: true }), {
configOverwrite
}
);
const senderEndpointId = await sender.getEndpointId();
const testVisitor = async function(instanceId: 'p1' | 'p2' | 'p3' | 'p4') {
const visitor = await joinMuc(
instanceId,
t({ room: ctx.roomName, displayName: 'Visitor', visitor: true }), {
configOverwrite
}
);
await visitor.waitForIceConnected();
const iceConnected = performance.now();
await visitor.driver.waitUntil(
() => visitor.isRemoteVideoReceivedAndDisplayed(senderEndpointId), {
timeout: 10_000,
timeoutMsg: `Visitor (${instanceId}) is not receiving video from the sender`
});
const duration = performance.now() - iceConnected;
console.log(`Video displayed after ${duration} ms after ICE connected (${instanceId})`);
};
await testVisitor('p2');
await testVisitor('p3');
await testVisitor('p4');
});
});

View File

@@ -45,6 +45,8 @@ describe('Visitors triggered by visitor tokens', () => {
expect(await v.isVisitor()).toBe(true);
console.log('Visitor joined');
await p.hangup();
// Joining with a participant token after visitors...:mindblown:
const v2 = await joinMuc(
'p2',

View File

@@ -211,13 +211,6 @@ export const config: WebdriverIO.MultiremoteConfig = {
} as IContext;
globalAny.ctx.testProperties = testProperties;
if (testProperties.useJaas && !testsConfig.jaas.enabled) {
console.warn(`JaaS is not configured, skipping ${testName}.`);
globalAny.ctx.skipSuiteTests = true;
return;
}
await Promise.all(multiremotebrowser.instances.map(async (instance: string) => {
const bInstance = multiremotebrowser.getInstance(instance);
@@ -240,7 +233,6 @@ export const config: WebdriverIO.MultiremoteConfig = {
}));
globalAny.ctx.roomName = generateRoomName(testName);
console.log(`Using room name: ${globalAny.ctx.roomName}`);
// If we are running the iFrameApi tests, we need to mark it as such and if needed to create the proxy
// and connect to it.
@@ -259,6 +251,11 @@ export const config: WebdriverIO.MultiremoteConfig = {
console.warn(`WebhookProxy is not available, skipping ${testName}`);
globalAny.ctx.skipSuiteTests = true;
}
if (testProperties.useJaas && !testsConfig.jaas.enabled) {
console.warn(`JaaS is not configured, skipping ${testName}.`);
globalAny.ctx.skipSuiteTests = true;
}
},
after() {