mirror of
https://gitcode.com/GitHub_Trending/ji/jitsi-meet.git
synced 2026-05-17 00:47:46 +00:00
Compare commits
27 Commits
9119
...
saghul-pat
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6d05ac519a | ||
|
|
e9daf4395e | ||
|
|
2245e4e747 | ||
|
|
43132a7eba | ||
|
|
4f3dc195cf | ||
|
|
a80a732ec4 | ||
|
|
04757f103e | ||
|
|
768be97fa4 | ||
|
|
d67096ee8e | ||
|
|
783527ac53 | ||
|
|
fc289dd5ae | ||
|
|
e84877e91f | ||
|
|
b24e615ded | ||
|
|
5ad6332632 | ||
|
|
8006bd05c6 | ||
|
|
048791c858 | ||
|
|
ad82e557e0 | ||
|
|
15511f86be | ||
|
|
7699cfdac5 | ||
|
|
16fcf27fb7 | ||
|
|
94243c797c | ||
|
|
6564ba52a2 | ||
|
|
a77ac20db9 | ||
|
|
6907db1127 | ||
|
|
78931d4f0d | ||
|
|
b19d76fbdf | ||
|
|
0676ca5e62 |
7
.github/workflows/ci.yml
vendored
7
.github/workflows/ci.yml
vendored
@@ -135,6 +135,13 @@ jobs:
|
||||
node -v
|
||||
npm -v
|
||||
- run: npm install
|
||||
- name: Cache Gradle dependencies
|
||||
uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57
|
||||
with:
|
||||
path: /root/.gradle
|
||||
key: ${{ runner.os }}-gradle-${{ hashFiles('android/**/*.gradle*', 'android/gradle.properties', 'android/gradle/wrapper/gradle-wrapper.properties') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-gradle-
|
||||
- run: |
|
||||
cd android
|
||||
./gradlew :sdk:clean
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# <p align="center">Jitsi Meet</p>
|
||||
# <p align="center">Jitsi Meet</p>
|
||||
|
||||
Jitsi Meet is a set of Open Source projects which empower users to use and deploy
|
||||
video conferencing platforms with state-of-the-art video quality and features.
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<intent-filter android:autoVerify="true">
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
|
||||
@@ -883,6 +883,7 @@
|
||||
"or": "o",
|
||||
"premeeting": "Pre-reunión",
|
||||
"proceedAnyway": "Continuar de todos modos",
|
||||
"recordingWarning": "Otros participantes pueden estar grabando esta llamada",
|
||||
"screenSharingError": "Error al compartir pantalla:",
|
||||
"startWithPhone": "Iniciar con audio de llamada telefónica",
|
||||
"unsafeRoomConsent": "Comprendo los riesgos, quiero unirme a la reunión",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -469,6 +469,8 @@
|
||||
"screenSharingFailed": "Oeps! Er is iets misgegaan, de schermdeling kon niet worden gestart!",
|
||||
"screenSharingFailedTitle": "Schermdeling mislukt!",
|
||||
"screenSharingPermissionDeniedError": "Oeps! Er is iets misgegaan met uw toegangsrechten voor schermdeling. Herlaad en probeer opnieuw.",
|
||||
"screenshareStoppedDiskSpace": "Dit gebeurt als u de zwevende werkbalk van macOS hebt gebruikt om het delen van schermen te stoppen. Het kan ook te wijten zijn aan een lage schijfruimte.",
|
||||
"screenshareStoppedTitle": "Scherm delen gestopt via systeem",
|
||||
"searchInSalesforce": "Zoeken in Salesforce",
|
||||
"searchResults": "Zoekresultaten({{count}})",
|
||||
"searchResultsDetailsError": "Er ging iets mis bij het ophalen van eigenaargegevens.",
|
||||
|
||||
@@ -114,6 +114,9 @@
|
||||
"error": "Fel: ditt meddelande skickades inte. Orsak: {{error}}",
|
||||
"everyone": "Alla",
|
||||
"fieldPlaceHolder": "Skriv ditt meddelande här",
|
||||
"fileAccessibleTitle": "{{user}} laddade upp en fil",
|
||||
"fileAccessibleTitleMe": "jag laddade upp en fil",
|
||||
"fileDeleted": "En fil raderades",
|
||||
"guestsChatIndicator": "(gäst)",
|
||||
"lobbyChatMessageTo": "Skicka meddelande",
|
||||
"message": "Meddelande",
|
||||
@@ -123,8 +126,16 @@
|
||||
"messagebox": "Skriv ett meddelande",
|
||||
"newMessages": "Nytt meddelande",
|
||||
"nickname": {
|
||||
"featureChat": "chatt",
|
||||
"featureClosedCaptions": "textning",
|
||||
"featureFileSharing": "fildelning",
|
||||
"featurePolls": "omröstningar",
|
||||
"popover": "Välj ett namn",
|
||||
"title": "Skriv in ett namn för att börja använda chatten",
|
||||
"titleWith1Features": "Ange ett smeknamn för att använda {{feature1}}",
|
||||
"titleWith2Features": "Ange ett smeknamn för att använda {{feature1}} och {{feature2}}",
|
||||
"titleWith3Features": "Ange ett smeknamn för att använda {{feature1}}, {{feature2}} och {{feature3}}",
|
||||
"titleWith4Features": "Ange ett smeknamn för att använda {{feature1}}, {{feature2}}, {{feature3}} och {{feature4}}",
|
||||
"titleWithCC": "Skriv in ett namn för att börja använda chatten och för undertexter",
|
||||
"titleWithPolls": "Skriv in ett namn för att börja använda chatten och omröstningar",
|
||||
"titleWithPollsAndCC": "Skriv in ett namn för att börja använda chatten, omröstningar och undertexter",
|
||||
@@ -216,6 +227,9 @@
|
||||
"video_ssrc": "Video SSRC:",
|
||||
"yes": "Ja"
|
||||
},
|
||||
"customPanel": {
|
||||
"close": "Stäng"
|
||||
},
|
||||
"dateUtils": {
|
||||
"earlier": "Tidigare",
|
||||
"today": "Idag",
|
||||
@@ -230,10 +244,10 @@
|
||||
"downloadMobileApp": "Ladda ner mobilappen",
|
||||
"ifDoNotHaveApp": "Om du inte har appen än:",
|
||||
"ifHaveApp": "Om du redan har appen:",
|
||||
"joinInApp": "Delta i detta möte med din app",
|
||||
"joinInApp": "Delta i det här mötet med din app",
|
||||
"joinInAppNew": "Delta i appen",
|
||||
"joinInBrowser": "Delta på webben",
|
||||
"launchMeetingLabel": "Hur vill du delta i detta möte?",
|
||||
"launchMeetingLabel": "Hur vill du delta i det här mötet?",
|
||||
"launchWebButton": "Starta på webben",
|
||||
"noDesktopApp": "",
|
||||
"noMobileApp": "Har du inte appen?",
|
||||
@@ -280,7 +294,7 @@
|
||||
"Submit": "Skicka",
|
||||
"Understand": "Jag förstår, låt min mikrofon vara avstängd tillsvidare",
|
||||
"UnderstandAndUnmute": "Jag förstår, starta min mikrofon",
|
||||
"WaitForHostNoAuthMsg": "Konferensen har ännu inte startat eftersom ingen värd har anlänt ännu. Vänligen vänta.",
|
||||
"WaitForHostNoAuthMsg": "Konferensen har ännu inte startat eftersom ingen värd har anlänt ännu. Var god vänta.",
|
||||
"WaitingForHostButton": "Vänta på värd",
|
||||
"WaitingForHostTitle": "Väntar på värden…",
|
||||
"Yes": "Ja",
|
||||
@@ -330,7 +344,7 @@
|
||||
"contactSupport": "Kontakta kundtjänst",
|
||||
"copied": "Kopierad",
|
||||
"copy": "Kopiera",
|
||||
"demoteParticipantDialog": "Are you sure you want to move this participant to viewer? Är du säker på att du vill flytta denna deltagaren till tittare",
|
||||
"demoteParticipantDialog": "Är du säker på att du vill flytta den här deltagaren till tittare?",
|
||||
"demoteParticipantTitle": "Flytta till tittare",
|
||||
"dismiss": "Förkasta",
|
||||
"displayNameRequired": "Hej, vad heter du?",
|
||||
@@ -344,18 +358,18 @@
|
||||
"enterDisplayName": "Ange namn",
|
||||
"error": "Fel",
|
||||
"errorRoomCreationRestriction": "Du försökte gå med för snabbt, kom tillbaka om en stund.",
|
||||
"gracefulShutdown": "Vår tjänst är för tillfället nedstängd för underhåll. Vänligen försök senare.",
|
||||
"grantModeratorDialog": "Är du säker du vill göra denna deltagare till en moderator?",
|
||||
"gracefulShutdown": "Vår tjänst är för tillfället nedstängd för underhåll. Försök senare.",
|
||||
"grantModeratorDialog": "Är du säker på att du vill göra den här deltagaren till moderator?",
|
||||
"grantModeratorTitle": "Godkänn moderator",
|
||||
"hide": "Dölj",
|
||||
"hideShareAudioHelper": "Visa inte denna dialog igen ",
|
||||
"hideShareAudioHelper": "Visa inte den här dialogen igen ",
|
||||
"incorrectPassword": "Fel användarnamn eller lösenord",
|
||||
"incorrectRoomLockPassword": "Felaktigt lösenord",
|
||||
"internalError": "Ett fel uppstod. Fel: {{error}}",
|
||||
"internalErrorTitle": "Internt fel",
|
||||
"kickMessage": "Du kan kontakta {{participantDisplayName}} för mer information.",
|
||||
"kickParticipantButton": "Ta bort deltagaren från mötet",
|
||||
"kickParticipantDialog": "Vill du ta bort denna deltagaren från mötet?",
|
||||
"kickParticipantDialog": "Vill du ta bort den här deltagaren från mötet?",
|
||||
"kickParticipantTitle": "Tysta deltagaren?",
|
||||
"kickSystemTitle": "Du har blivit borttagen från mötet",
|
||||
"kickTitle": "{{participantDisplayName}} tog bort dig från mötet",
|
||||
@@ -369,6 +383,9 @@
|
||||
"lockRoom": "Lägg till möte $t(lockRoomPasswordUppercase)",
|
||||
"lockTitle": "Låsning misslyckades",
|
||||
"login": "Logga in",
|
||||
"loginFailed": "Inloggningen misslyckades.",
|
||||
"loginOnResume": "Din autentiseringssession har gått ut. Du måste logga in igen för att fortsätta mötet.",
|
||||
"loginPopupBlocked": "Inloggningsfönstret blockerades av din webbläsare.",
|
||||
"loginQuestion": "Är du säker på att du vill logga in och lämna mötet",
|
||||
"logoutQuestion": "Är du säker på att du vill logga ut och lämna konferensen?",
|
||||
"logoutTitle": "Logga ut",
|
||||
@@ -413,18 +430,18 @@
|
||||
"muteParticipantsVideoBody": "Du kommer inte att kunna aktivera kameran igen. Däremot kan deltagaren kunna aktivera sin egen kamera när som.",
|
||||
"muteParticipantsVideoBodyModerationOn": "Du och deltagarna kommer inte att kunna aktivera kameran igen.",
|
||||
"muteParticipantsVideoButton": "Inaktivera kamera",
|
||||
"muteParticipantsVideoDialog": "Är du säker du vill inaktivera denna deltagares kamera. Du kommer inte att kunna aktivera den igen. Däremot kan deltagaren kunna aktivera sin egen kamera när som.",
|
||||
"muteParticipantsVideoDialog": "Är du säker du vill inaktivera den här deltagarens kamera. Du kommer inte att kunna aktivera den igen. Däremot kan deltagaren kunna aktivera sin egen kamera när som.",
|
||||
"muteParticipantsVideoDialogModerationOn": "Är du säker på att du vill inaktivera den här deltagarens kamera? Du kommer inte att kunna aktivera kameran igen och inte de heller.",
|
||||
"muteParticipantsVideoTitle": "Inaktivera denna deltagares kamera?",
|
||||
"muteParticipantsVideoTitle": "Inaktivera den här deltagarens kamera?",
|
||||
"noDropboxToken": "Ingen giltig dropbox tecken",
|
||||
"password": "Lösenord",
|
||||
"passwordLabel": "Mötet har låsts av en deltagare. Ange $t(lockRoomPassword) för att gå med.",
|
||||
"passwordNotSupported": "Att sätta ett $t(lockRoomPassword) för mötesrummet stöds ej.",
|
||||
"passwordNotSupportedTitle": "$t(lockRoomPasswordUppercase) stöds inte",
|
||||
"passwordRequired": "$t(lockRoomPasswordUppercase) krävs",
|
||||
"permissionCameraRequiredError": "Tillåtelse krävs för att delta med kamera i denna möte. Var god skaffa detta i \"inställningar\".",
|
||||
"permissionCameraRequiredError": "Behörighet krävs för att delta med kamera i det här mötet. Ändra detta i \"Inställningar\".",
|
||||
"permissionErrorTitle": "Tillåtelse krävs",
|
||||
"permissionMicRequiredError": "Tillåtelse krävs för att delta med mikrofon i denna möte. Var god skaffa detta i \"inställningar\".",
|
||||
"permissionMicRequiredError": "Behörighet krävs för att delta med mikrofon i det här mötet. Ändra detta i \"Inställningar\".",
|
||||
"readMore": "Mer",
|
||||
"recentlyUsedObjects": "Dina senaste använda objekt",
|
||||
"recording": "Inspelning",
|
||||
@@ -452,12 +469,14 @@
|
||||
"screenSharingFailed": "Oops! Något gick fel, skärmdelning kunde ej startas.",
|
||||
"screenSharingFailedTitle": "Skärmdelning misslyckades!",
|
||||
"screenSharingPermissionDeniedError": "Något är fel med åtkomstinställningarna för skärmdelningen. Ladda om sidan och försök igen.",
|
||||
"screenshareStoppedDiskSpace": "Det här händer om du använde macOS flytande verktygsfält för att stoppa skärmdelningen. Det har inte stöd för att starta den igen.",
|
||||
"screenshareStoppedTitle": "Skärmdelningen stoppades via systemet",
|
||||
"searchInSalesforce": "Sök i Salesforce",
|
||||
"searchResults": "Sökresultat ({{count}})",
|
||||
"searchResultsDetailsError": "Något gick fel när ägardata hämtades.",
|
||||
"searchResultsError": "Något gick fel när data hämtades.",
|
||||
"searchResultsNotFound": "Inga sökresultat hittades.",
|
||||
"searchResultsTryAgain": "Try using alternative keywords.",
|
||||
"searchResultsTryAgain": "Försök med andra sökord.",
|
||||
"sendPrivateMessage": "Du har fått ett privat meddelande. Tänkte du svara på det privat, eller vill du skicka ditt meddelande till alla deltagare?",
|
||||
"sendPrivateMessageCancel": "Skicka till alla deltagare",
|
||||
"sendPrivateMessageOk": "Skicka privat",
|
||||
@@ -520,7 +539,7 @@
|
||||
"tokenAuthFailedWithReasons": "Förlåt, du har inte tillåtelse att gå med i det här samtalet. Troliga anledingar: {{reason}}",
|
||||
"tokenAuthUnsupported": "Token URL är inte tillåten",
|
||||
"transcribing": "Transkriberar",
|
||||
"unauthenticatedAccessDisabled": "Detta samtalet kräver identifiering. Logga in för att fortsätta.",
|
||||
"unauthenticatedAccessDisabled": "Det här samtalet kräver identifiering. Logga in för att fortsätta.",
|
||||
"unlockRoom": "Ta bort möte $t(lockRoomPassword)",
|
||||
"user": "Användare",
|
||||
"userIdentifier": "Användar-ID",
|
||||
@@ -543,7 +562,7 @@
|
||||
"title": "Delade dokument"
|
||||
},
|
||||
"e2ee": {
|
||||
"labelToolTip": "Ljud- och videokommunikation för detta samtal är krypterad från dator till dator"
|
||||
"labelToolTip": "Ljud- och videokommunikation för det här samtalet är krypterad från dator till dator"
|
||||
},
|
||||
"embedMeeting": {
|
||||
"title": "Bädda in möte"
|
||||
@@ -576,6 +595,7 @@
|
||||
"newFileNotification": "{{ participantName }} delade '{{ fileName }}'",
|
||||
"removeFile": "Ta bort",
|
||||
"removeFileSuccess": "Filen togs bort",
|
||||
"uploadDisabled": "Inte tillåtet att ladda upp filer. Be en moderator om behörighet för den åtgärden.",
|
||||
"uploadFailedDescription": "Snälla försök igen.",
|
||||
"uploadFailedTitle": "Överföring misslyckades",
|
||||
"uploadFile": "Dela fil"
|
||||
@@ -603,7 +623,7 @@
|
||||
"conferenceURL": "Länk:",
|
||||
"copyNumber": "Kopiera nummer",
|
||||
"country": "Land",
|
||||
"dialANumber": "Om du vill gå med i mötet ringer du något av dessa nummer och fyller sedan i PIN-koden.",
|
||||
"dialANumber": "Om du vill gå med i mötet ringer du något av de här numren och fyller sedan i PIN-koden.",
|
||||
"dialInConferenceID": "PIN-kod:",
|
||||
"dialInNotSupported": "Tyvärr stöds inte inringning just nu.",
|
||||
"dialInNumber": "Inringning:",
|
||||
@@ -635,14 +655,14 @@
|
||||
"sipAudioOnly": "SIP endast ljud address",
|
||||
"title": "Dela",
|
||||
"tooltip": "Dela länk och information om inringning för mötet",
|
||||
"upgradeOptions": "Vänligen kontrollera om uppgraderingsalternativen är på",
|
||||
"upgradeOptions": "Kontrollera om uppgraderingsalternativen är på",
|
||||
"whiteboardError": "Problem att ladda whiteboard, var god försök senare."
|
||||
},
|
||||
"inlineDialogFailure": {
|
||||
"msg": "Vi slirade lite.",
|
||||
"retry": "Försök igen",
|
||||
"support": "Support",
|
||||
"supportMsg": "Om detta fortsätter hända kontakta"
|
||||
"supportMsg": "Om det här fortsätter att hända, kontakta"
|
||||
},
|
||||
"inviteDialog": {
|
||||
"alertText": "Det gick inte att bjuda in alla deltagare.",
|
||||
@@ -709,13 +729,14 @@
|
||||
"streamIdHelp": "Vad är det här?",
|
||||
"title": "Direktsändning",
|
||||
"unavailableTitle": "Livesändning otillgänglig",
|
||||
"youTubeGoLiveWarning": "Kom ihåg att klicka på \"Gå live\" i YouTube Studio om autostart/autostopp är inaktiverade.",
|
||||
"youtubeTerms": "Tjänstevillkor för YouTube"
|
||||
},
|
||||
"lobby": {
|
||||
"backToKnockModeButton": "Tillbaka till väntrum",
|
||||
"chat": "Chatt",
|
||||
"dialogTitle": "Väntrum",
|
||||
"disableDialogContent": "Väntrumsläge är för närvarande aktiverat. Denna funktion säkerställer att oönskade deltagare inte kan gå med i ditt möte. Vill du inaktivera det?",
|
||||
"disableDialogContent": "Väntrumsläge är för närvarande aktiverat. Den här funktionen säkerställer att oönskade deltagare inte kan gå med i ditt möte. Vill du inaktivera det?",
|
||||
"disableDialogSubmit": "Inaktivera",
|
||||
"emailField": "Skriv in din mailadress",
|
||||
"enableDialogPasswordField": "Ange lösenord (valfritt)",
|
||||
@@ -809,7 +830,7 @@
|
||||
"desktopMutedRemotelyTitle": "Din skärmdelning har avslutats av {{participantDisplayName}}",
|
||||
"disabledIframe": "Inbäddning är endast avsedd för demonstrationsändamål, så det här samtalet kommer att kopplas ner om {{timeout}} minuter.",
|
||||
"disabledIframeSecondaryNative": "Inbäddning {{domain}} är endast avsedd för demonstrationsändamål, så det här samtalet kommer att kopplas ner om {{timeout}} minuter.",
|
||||
"disabledIframeSecondaryWeb": "Bädda in {{domain}} är bara till för demo, så detta samtal kommer att kopplas bort inom {{timeout}} minuter. Var god använd <a href='{{jaasDomain}}' rel='nooper noreferrer' target='_blank'>Jitsi som tjänst</a> för att bädda in i produktion.",
|
||||
"disabledIframeSecondaryWeb": "Bädda in {{domain}} är bara till för demo, så det här samtalet kommer att kopplas bort inom {{timeout}} minuter. Var god använd <a href='{{jaasDomain}}' rel='nooper noreferrer' target='_blank'>Jitsi som tjänst</a> för att bädda in i produktion.",
|
||||
"disconnected": "frånkopplad",
|
||||
"displayNotifications": "Visa aviseringar för",
|
||||
"dontRemindMe": "Påminn mig inte",
|
||||
@@ -817,7 +838,7 @@
|
||||
"focusFail": "{{component}} inte tillgänglig – försöker igen om {{ms}} sek",
|
||||
"gifsMenu": "GIPHY",
|
||||
"groupTitle": "Notifieringar",
|
||||
"hostAskedUnmute": "Värden vill att du ska stänga av ljudet",
|
||||
"hostAskedUnmute": "Värden vill att du ska starta din mikrofon",
|
||||
"invalidTenant": "Ogiltig tenant",
|
||||
"invalidTenantHyphenDescription": "Tenant du använder har ogiltiga tecken (startar eller slutar med '-').",
|
||||
"invalidTenantLengthDescription": "Tenant du använder är för lång",
|
||||
@@ -832,7 +853,7 @@
|
||||
"linkToSalesforce": "Länk till Salesforce",
|
||||
"linkToSalesforceDescription": "Du kan länka mötessammanfattningen till ett Salesforce-objekt.",
|
||||
"linkToSalesforceError": "Det gick inte att länka mötet till Salesforce",
|
||||
"linkToSalesforceKey": "Länka detta möte",
|
||||
"linkToSalesforceKey": "Länka det här mötet",
|
||||
"linkToSalesforceProgress": "Länkar möte till Salesforce…",
|
||||
"linkToSalesforceSuccess": "Mötet länkades till Salesforce",
|
||||
"localRecordingStarted": "{{name}} har påbörjat en lokal inspelning.",
|
||||
@@ -858,7 +879,7 @@
|
||||
"newDeviceAudioTitle": "Ny ljudenhet hittad",
|
||||
"newDeviceCameraTitle": "Ny kamera hittad",
|
||||
"nextToSpeak": "Du är näst i kö för att prata",
|
||||
"noiseSuppressionDesktopAudioDescription": "Brusreducering kan inte aktiveras när du delar skrivbordsljud, vänligen inaktivera det och försök igen.",
|
||||
"noiseSuppressionDesktopAudioDescription": "Brusreducering kan inte aktiveras när du delar skrivbordsljud, inaktivera det och försök igen.",
|
||||
"noiseSuppressionFailedTitle": "Det gick inte att starta brusreducering",
|
||||
"noiseSuppressionStereoDescription": "Brusreducering i stereoljud stöds för närvarande inte.",
|
||||
"oldElectronClientDescription1": "Den version av Jitsi meet som används är gammal och har säkerhetsluckor. Var god uppdatera till den senaste versionen.",
|
||||
@@ -886,7 +907,7 @@
|
||||
"suboptimalExperienceTitle": "Webbläsarvarning",
|
||||
"suggestRecordingAction": "Starta",
|
||||
"suggestRecordingDescription": "Vill du starta en inspelning?",
|
||||
"suggestRecordingTitle": "Spela in detta mötet",
|
||||
"suggestRecordingTitle": "Spela in det här mötett",
|
||||
"unmute": "Slå på mikrofonen",
|
||||
"unmuteScreen": "Starta skärmdelning",
|
||||
"unmuteVideo": "Starta kamera",
|
||||
@@ -981,7 +1002,7 @@
|
||||
},
|
||||
"notification": {
|
||||
"description": "Öppna fliken omröstningar för att rösta",
|
||||
"title": "En ny omröstning har blivit tillagd till detta möte"
|
||||
"title": "En ny omröstning har blivit tillagd till det här mötet"
|
||||
},
|
||||
"results": {
|
||||
"changeVote": "Ändra din röst",
|
||||
@@ -1014,7 +1035,7 @@
|
||||
"audioHighQuality": "Vi förväntar oss att ditt ljud har utmärkt kvalitet.",
|
||||
"audioLowNoVideo": "Vi förväntar oss att din ljudkvalitet är låg och ingen video.",
|
||||
"goodQuality": "Grymt bra! Din mediekvalitet kommer att bli bra.",
|
||||
"noMediaConnectivity": "Vi kunde inte hitta ett sätt att upprätta mediaanslutning för detta test. Detta orsakas vanligtvis av en brandvägg eller NAT.",
|
||||
"noMediaConnectivity": "Vi kunde inte upprätta mediaanslutning för det här testet. Det orsakas vanligtvis av en brandvägg eller NAT.",
|
||||
"noVideo": "Vi förväntar oss att din video kommer ha låg kvalitet eller inte fungera.",
|
||||
"testFailed": "Anslutningstestet stötte på oväntade problem, men det behöver inte påverka din upplevelse.",
|
||||
"undetectable": "Om du fortfarande inte kan ringa i webbläsaren rekommenderar vi att du ser till att dina högtalare, mikrofon och kamera är korrekt inställda, att du har beviljat din webbläsare rättigheter att använda din mikrofon och kamera och att din webbläsarversion är uppdaterad.",
|
||||
@@ -1028,7 +1049,7 @@
|
||||
"dialInMeeting": "Ring in till mötet",
|
||||
"dialInPin": "Ring in till mötet och ange PIN-kod:",
|
||||
"dialing": "Ringer",
|
||||
"doNotShow": "Visa inte denna ruta igen",
|
||||
"doNotShow": "Visa inte den här rutan igen",
|
||||
"errorDialOut": "Kunde inte ringa ut",
|
||||
"errorDialOutDisconnected": "Kunde inte ringa ut. Kopplar ner",
|
||||
"errorDialOutFailed": "Kunde inte ringa ut. Samtal misslyckades",
|
||||
@@ -1082,7 +1103,7 @@
|
||||
"raisedHandsLabel": "Antal uppräckta händer",
|
||||
"record": {
|
||||
"already": {
|
||||
"linked": "Mötet är redan länkat till detta Salesforce-objekt."
|
||||
"linked": "Mötet är redan länkat till det här Salesforce-objekt."
|
||||
},
|
||||
"type": {
|
||||
"account": "Konto",
|
||||
@@ -1121,7 +1142,7 @@
|
||||
"localRecordingVideoWarning": "För att spela in din video måste du ha den på när du startar inspelningen",
|
||||
"localRecordingWarning": "Se till att du väljer den aktuella fliken för att kunna använda rätt video och ljud.",
|
||||
"loggedIn": "Inloggad som {{userName}}",
|
||||
"noMicPermission": "Mikrofonspåret kunde inte skapas. Vänligen ge tillstånd att använda mikrofonen.",
|
||||
"noMicPermission": "Mikrofonspåret kunde inte skapas. Ge tillstånd att använda mikrofonen.",
|
||||
"noStreams": "Ingen ljud- eller videoström upptäcktes.",
|
||||
"off": "Inspelningen avslutades",
|
||||
"offBy": "{{name}} avslutade inspelningen",
|
||||
@@ -1246,7 +1267,7 @@
|
||||
"version": "Version"
|
||||
},
|
||||
"share": {
|
||||
"dialInfoText": "\n\n=====\n\nVill du istället ringa in via telefon?\n\n{{defaultDialInNumber}} Klicka på den här länken för att se telefonnumret för detta möte\n{{dialInfoPageUrl}}",
|
||||
"dialInfoText": "\n\n=====\n\nVill du istället ringa in via telefon?\n\n{{defaultDialInNumber}} Klicka på den här länken för att se telefonnumret för det här mötet\n{{dialInfoPageUrl}}",
|
||||
"mainText": "Klicka på länken för att delta i mötet:\n{{roomUrl}}"
|
||||
},
|
||||
"speaker": "Talare",
|
||||
@@ -1298,6 +1319,7 @@
|
||||
"chat": "Öppna eller stäng chattfönster",
|
||||
"clap": "Applådera",
|
||||
"closeChat": "Stäng chatten",
|
||||
"closeCustomPanel": "Stäng",
|
||||
"closeMoreActions": "Stäng menyn för fler åtgärder",
|
||||
"closeParticipantsPane": "Stäng deltagarfönstret",
|
||||
"closedCaptions": "Undertexter",
|
||||
@@ -1403,9 +1425,11 @@
|
||||
"chat": "Öppna / stäng chatten",
|
||||
"clap": "Klappa",
|
||||
"closeChat": "Stäng chatt",
|
||||
"closeCustomPanel": "Stäng",
|
||||
"closeParticipantsPane": "Stäng deltagarrutan",
|
||||
"closeReactionsMenu": "Stäng meny för reaktioner",
|
||||
"closedCaptions": "Undertexter",
|
||||
"copilot": "Copilot",
|
||||
"disableNoiseSuppression": "Inaktivera brusreducering",
|
||||
"disableReactionSounds": "Du kan inaktivera reaktionsljud för det här mötet",
|
||||
"documentClose": "Stäng delat dokument",
|
||||
@@ -1420,6 +1444,7 @@
|
||||
"exitFullScreen": "Stäng fullskärm",
|
||||
"exitTileView": "Stäng panelvy",
|
||||
"feedback": "Lämna återkoppling",
|
||||
"fileSharing": "Fildelning",
|
||||
"giphy": "Växla GIPHY-menyn",
|
||||
"hangup": "Lämna",
|
||||
"help": "Hjälp",
|
||||
@@ -1455,6 +1480,7 @@
|
||||
"openReactionsMenu": "Öppna meny för reaktioner",
|
||||
"participants": "Deltagare",
|
||||
"pip": "Öppna bild-i-bild-läge",
|
||||
"polls": "Omröstningar",
|
||||
"privateMessage": "Skicka privat meddelande",
|
||||
"profile": "Redigera din profil",
|
||||
"raiseHand": "Räck upp / ta ner din hand",
|
||||
@@ -1581,6 +1607,7 @@
|
||||
"addBackground": "Lägg till bakgrund",
|
||||
"apply": "Tillämpa",
|
||||
"backgroundEffectError": "Det gick inte att tillämpa bakgrundseffekt.",
|
||||
"backgroundLimitReached": "Gränsen för anpassade bakgrunder har nåtts",
|
||||
"blur": "Oskärpa",
|
||||
"deleteImage": "Ta bort bild",
|
||||
"desktopShare": "Dela skrivbord",
|
||||
@@ -1593,7 +1620,8 @@
|
||||
"image6": "Skog",
|
||||
"image7": "Soluppgång",
|
||||
"none": "Ingen",
|
||||
"pleaseWait": "Vänligen vänta…",
|
||||
"oldestBackgroundRemoved": "Den äldsta anpassade bakgrunden har tagits bort för att lägga till den nya.",
|
||||
"pleaseWait": "Var god vänta…",
|
||||
"removeBackground": "Ta bort bakgrunden",
|
||||
"slightBlur": "Lätt oskärpa",
|
||||
"title": "Virtuella bakgrunder",
|
||||
@@ -1665,7 +1693,7 @@
|
||||
"recentListEmpty": "Inga tidigare möten. Chatta med ditt team och hitta alla tidigare möten där.",
|
||||
"recentMeetings": "Dina senaste möten",
|
||||
"reducedUIText": "Välkommen till {{app}}!",
|
||||
"roomNameAllowedChars": "Mötesnamn kan inte innehålla dessa tecken: ?, &,:, ', \",%, #.",
|
||||
"roomNameAllowedChars": "Mötesnamn kan inte innehålla de här tecknen: ?, &,:, ', \",%, #.",
|
||||
"roomname": "Skriv in rumsnamn",
|
||||
"roomnameHint": "Ange namnet eller URL:en till mötesrummet du vill ansluta till. Du kan hitta på ett nytt namn, berätta då för de andra du tänker möta så de anger samma namn.",
|
||||
"sendFeedback": "Ge återkoppling",
|
||||
|
||||
@@ -469,6 +469,8 @@
|
||||
"screenSharingFailed": "Oops! Something went wrong, we weren't able to start screen sharing!",
|
||||
"screenSharingFailedTitle": "Screen sharing failed!",
|
||||
"screenSharingPermissionDeniedError": "Oops! Something went wrong with your screen sharing permissions. Please reload and try again.",
|
||||
"screenshareStoppedDiskSpace": "This happens if you used the macOS's floating toolbar to stop screen sharing. It may also be due to low disk space.",
|
||||
"screenshareStoppedTitle": "Screen sharing stopped via system",
|
||||
"searchInSalesforce": "Search in Salesforce",
|
||||
"searchResults": "Search results({{count}})",
|
||||
"searchResultsDetailsError": "Something went wrong while retrieving owner data.",
|
||||
|
||||
@@ -843,12 +843,8 @@ function initCommands() {
|
||||
|
||||
const activeSession = getActiveSession(state, mode);
|
||||
|
||||
if (activeSession && activeSession.id) {
|
||||
APP.store.dispatch(toggleScreenshotCaptureSummary(false));
|
||||
conference.stopRecording(activeSession.id);
|
||||
} else {
|
||||
logger.error('No recording or streaming session found');
|
||||
}
|
||||
APP.store.dispatch(toggleScreenshotCaptureSummary(false));
|
||||
conference.stopRecording(activeSession?.id);
|
||||
},
|
||||
'initiate-private-chat': participantId => {
|
||||
const state = APP.store.getState();
|
||||
|
||||
2752
package-lock.json
generated
2752
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
17
package.json
17
package.json
@@ -72,7 +72,7 @@
|
||||
"js-md5": "0.6.1",
|
||||
"js-sha512": "0.8.0",
|
||||
"jwt-decode": "2.2.0",
|
||||
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v2135.0.0+17e2281c/lib-jitsi-meet.tgz",
|
||||
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v2143.0.0+733e66c6/lib-jitsi-meet.tgz",
|
||||
"lodash-es": "4.17.23",
|
||||
"null-loader": "4.0.1",
|
||||
"optional-require": "1.0.3",
|
||||
@@ -164,12 +164,12 @@
|
||||
"@types/w3c-image-capture": "1.0.6",
|
||||
"@types/w3c-web-hid": "1.0.3",
|
||||
"@types/zxcvbn": "4.4.1",
|
||||
"@wdio/allure-reporter": "9.23.2",
|
||||
"@wdio/cli": "9.23.2",
|
||||
"@wdio/globals": "9.23.0",
|
||||
"@wdio/junit-reporter": "9.23.2",
|
||||
"@wdio/local-runner": "9.23.2",
|
||||
"@wdio/mocha-framework": "9.23.2",
|
||||
"@wdio/allure-reporter": "9.27.0",
|
||||
"@wdio/cli": "9.27.0",
|
||||
"@wdio/globals": "9.27.0",
|
||||
"@wdio/junit-reporter": "9.27.0",
|
||||
"@wdio/local-runner": "9.27.0",
|
||||
"@wdio/mocha-framework": "9.27.0",
|
||||
"babel-loader": "9.1.0",
|
||||
"babel-plugin-optional-require": "0.3.1",
|
||||
"circular-dependency-plugin": "5.2.0",
|
||||
@@ -184,6 +184,7 @@
|
||||
"eslint-plugin-typescript-sort-keys": "3.3.0",
|
||||
"jetifier": "1.6.4",
|
||||
"jsonwebtoken": "9.0.2",
|
||||
"junit-report-builder": "5.1.1",
|
||||
"patch-package": "6.4.7",
|
||||
"pretty": "2.0.0",
|
||||
"process": "0.11.10",
|
||||
@@ -193,7 +194,7 @@
|
||||
"ts-loader": "9.4.2",
|
||||
"typescript": "5.7.2",
|
||||
"unorm": "1.6.0",
|
||||
"webdriverio": "9.22.0",
|
||||
"webdriverio": "9.27.0",
|
||||
"webpack": "5.105.0",
|
||||
"webpack-bundle-analyzer": "4.4.2",
|
||||
"webpack-cli": "5.1.4",
|
||||
|
||||
@@ -1 +1,6 @@
|
||||
*.tgz
|
||||
tsconfig.json
|
||||
.npmrc
|
||||
.git
|
||||
.gitignore
|
||||
node_modules
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
"name": "@jitsi/react-native-sdk",
|
||||
"version": "0.0.0",
|
||||
"description": "React Native SDK for Jitsi Meet.",
|
||||
"main": "index.tsx",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"license": "Apache-2.0",
|
||||
"author": "",
|
||||
"homepage": "https://jitsi.org",
|
||||
@@ -92,9 +93,27 @@
|
||||
"@babel/plugin-proposal-optional-chaining": "0.0.0"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsc -p tsconfig.json",
|
||||
"postinstall": "node sdk_instructions.js",
|
||||
"prepare": "node prepare_sdk.js"
|
||||
"prepare": "node prepare_sdk.js && npm run build"
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
"android",
|
||||
"ios",
|
||||
"index.tsx",
|
||||
"jitsi-meet-rnsdk.podspec",
|
||||
"prepare_sdk.js",
|
||||
"sdk_instructions.js",
|
||||
"update_dependencies.js",
|
||||
"update_sdk_dependencies.js",
|
||||
"README.md",
|
||||
"images",
|
||||
"sounds",
|
||||
"lang",
|
||||
"modules",
|
||||
"react"
|
||||
],
|
||||
"bugs": {
|
||||
"url": "https://github.com/jitsi/jitsi-meet/issues"
|
||||
},
|
||||
|
||||
17
react-native-sdk/tsconfig.json
Normal file
17
react-native-sdk/tsconfig.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"extends": "../tsconfig.native.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./dist",
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"sourceMap": true,
|
||||
"emitDeclarationOnly": false
|
||||
},
|
||||
"include": [
|
||||
"index.tsx"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"dist"
|
||||
]
|
||||
}
|
||||
@@ -95,12 +95,6 @@ export function commonUserJoinedHandling(
|
||||
const isPromoted = conference?.getMetadataHandler().getMetadata()?.visitors?.promoted?.[id];
|
||||
const userIdentity = user.getIdentity()?.user;
|
||||
|
||||
// Map identity from JWT context to userContext for external API
|
||||
const userContext = userIdentity ? {
|
||||
id: userIdentity.id,
|
||||
name: userIdentity.name
|
||||
} : undefined;
|
||||
|
||||
// the identity and avatar come from jwt and never change in the presence
|
||||
dispatch(participantJoined({
|
||||
avatarURL: userIdentity?.avatar,
|
||||
@@ -113,7 +107,7 @@ export function commonUserJoinedHandling(
|
||||
isPromoted,
|
||||
isReplacing,
|
||||
sources: user.getSources(),
|
||||
userContext
|
||||
userContext: userIdentity
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -619,17 +619,12 @@ function _localParticipantJoined({ getState, dispatch }: IStore, next: Function,
|
||||
const settings = state['features/base/settings'];
|
||||
const jwtUser = state['features/base/jwt']?.user;
|
||||
|
||||
const userContext = jwtUser ? {
|
||||
id: jwtUser.id,
|
||||
name: jwtUser.name
|
||||
} : undefined;
|
||||
|
||||
dispatch(localParticipantJoined({
|
||||
avatarURL: settings.avatarURL,
|
||||
email: settings.email,
|
||||
name: settings.displayName,
|
||||
id: '',
|
||||
userContext
|
||||
userContext: jwtUser
|
||||
}));
|
||||
|
||||
return result;
|
||||
|
||||
@@ -45,6 +45,7 @@ export interface IParticipant {
|
||||
}
|
||||
|
||||
export interface IUserContext {
|
||||
[key: string]: any;
|
||||
id?: string;
|
||||
name?: string;
|
||||
}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { createTrackMutedEvent } from '../../analytics/AnalyticsEvents';
|
||||
import { sendAnalytics } from '../../analytics/functions';
|
||||
import { IStore } from '../../app/types';
|
||||
import { showErrorNotification, showNotification } from '../../notifications/actions';
|
||||
import { showErrorNotification, showNotification, showWarningNotification } from '../../notifications/actions';
|
||||
import { NOTIFICATION_TIMEOUT, NOTIFICATION_TIMEOUT_TYPE } from '../../notifications/constants';
|
||||
import { getCurrentConference } from '../conference/functions';
|
||||
import { IJitsiConference } from '../conference/reducer';
|
||||
import { isMacOS } from '../environment/environment';
|
||||
import { JitsiTrackErrors, JitsiTrackEvents } from '../lib-jitsi-meet';
|
||||
import { setAudioMuted, setScreenshareMuted, setVideoMuted } from '../media/actions';
|
||||
import {
|
||||
@@ -439,6 +440,12 @@ export function trackAdded(track: any) {
|
||||
track.on(JitsiTrackEvents.LOCAL_TRACK_STOPPED,
|
||||
() => {
|
||||
logger.debug(`Local track stopped: ${track}, removing it from the conference`);
|
||||
if (mediaType === MEDIA_TYPE.SCREENSHARE && isMacOS()) {
|
||||
dispatch(showWarningNotification({
|
||||
descriptionKey: 'dialog.screenshareStoppedDiskSpace',
|
||||
titleKey: 'dialog.screenshareStoppedTitle'
|
||||
}, NOTIFICATION_TIMEOUT_TYPE.LONG));
|
||||
}
|
||||
dispatch({
|
||||
type: TRACK_STOPPED,
|
||||
track: {
|
||||
|
||||
@@ -214,7 +214,7 @@ export const colorMap = {
|
||||
// Visitors
|
||||
visitorsCountBadge: 'warning02', // Visitors count badge background
|
||||
visitorsCountText: 'uiBackground', // Visitors count badge text
|
||||
visitorsCountIcon: 'icon04', // Visitors count icon
|
||||
visitorsCountIcon: 'iconSvgFill', // Visitors count icon
|
||||
visitorsQueueBackground: 'ui01', // Visitors queue panel background
|
||||
visitorsQueueText: 'text01', // Visitors queue text
|
||||
visitorsArrowBackground: 'ui02', // Visitors arrow container background
|
||||
@@ -355,7 +355,7 @@ export const colorMap = {
|
||||
dialInSecondaryText: 'text02', // Dial-in summary secondary text
|
||||
|
||||
// Reactions
|
||||
reactionsMenuBackground: 'ui01', // Reactions menu background
|
||||
reactionsMenuBackground: '#242528', // Reactions menu background
|
||||
reactionsMenuBorder: 'ui02', // Reactions menu border
|
||||
reactionsMenuButtonToggled: 'surface01', // Reactions menu button toggled state background
|
||||
reactionsMenuBoxShadow1: 'ui09', // Reactions menu box shadow primary
|
||||
@@ -411,7 +411,7 @@ export const colorMap = {
|
||||
fileSharingItemBorder: 'ui02', // File sharing item hover/border
|
||||
|
||||
// Gifs
|
||||
gifsBackground: 'ui01', // GIFs panel background
|
||||
gifsBackground: '#242528', // GIFs panel background
|
||||
gifsText: 'text01', // GIFs panel text
|
||||
|
||||
// Whiteboard
|
||||
|
||||
@@ -19,6 +19,7 @@ export interface IBreakoutRoomsState {
|
||||
rooms: IRooms;
|
||||
userContextCache: {
|
||||
[participantId: string]: {
|
||||
[key: string]: any;
|
||||
id?: string;
|
||||
name?: string;
|
||||
};
|
||||
|
||||
@@ -9,6 +9,7 @@ export interface IRoom {
|
||||
jid: string;
|
||||
role: string;
|
||||
userContext?: {
|
||||
[key: string]: any;
|
||||
id?: string;
|
||||
name?: string;
|
||||
};
|
||||
@@ -38,6 +39,7 @@ export interface IRoomInfoParticipant {
|
||||
jid: string;
|
||||
role: string;
|
||||
userContext?: {
|
||||
[key: string]: any;
|
||||
id?: string;
|
||||
name?: string;
|
||||
};
|
||||
|
||||
@@ -483,6 +483,7 @@ export default reactReduxConnect(_mapStateToProps)(translate(props => {
|
||||
|
||||
const { isOpen: isChatOpen } = useSelector((state: IReduxState) => state['features/chat']);
|
||||
const isFileUploadEnabled = useSelector(isFileUploadingEnabled);
|
||||
const isOnPrejoin = useSelector(isPrejoinPageVisible);
|
||||
|
||||
const handleDragEnter = useCallback((e: React.DragEvent) => {
|
||||
e.preventDefault();
|
||||
@@ -500,7 +501,7 @@ export default reactReduxConnect(_mapStateToProps)(translate(props => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
if (!isFileUploadEnabled) {
|
||||
if (!isFileUploadEnabled || isOnPrejoin) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -510,7 +511,7 @@ export default reactReduxConnect(_mapStateToProps)(translate(props => {
|
||||
}
|
||||
dispatch(setFocusedTab(ChatTabs.FILE_SHARING));
|
||||
}
|
||||
}, [ isChatOpen, isDragging, isFileUploadEnabled ]);
|
||||
}, [ isChatOpen, isDragging, isFileUploadEnabled, isOnPrejoin ]);
|
||||
|
||||
const handleDrop = useCallback((e: React.DragEvent) => {
|
||||
e.preventDefault();
|
||||
@@ -528,6 +529,7 @@ export default reactReduxConnect(_mapStateToProps)(translate(props => {
|
||||
|
||||
return (
|
||||
<div
|
||||
data-testid = 'conference-drag-zone'
|
||||
onDragEnter = { handleDragEnter }
|
||||
onDragLeave = { handleDragLeave }
|
||||
onDragOver = { handleDragOver }
|
||||
|
||||
@@ -235,6 +235,13 @@ class AudioDevicesSelection extends AbstractDialogTab<IProps, {}> {
|
||||
!== this.props.selectedAudioInputId) {
|
||||
this._createAudioInputTrack(this.props.selectedAudioInputId);
|
||||
}
|
||||
|
||||
if (!prevProps.hasAudioPermission && this.props.hasAudioPermission) {
|
||||
this._createAudioInputTrack(this.props.selectedAudioInputId)
|
||||
?.then(() => {
|
||||
this.props.dispatch(getAvailableDevices());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -194,6 +194,13 @@ class VideoDeviceSelection extends AbstractDialogTab<IProps, IState> {
|
||||
!== this.props.selectedVideoInputId) {
|
||||
this._createVideoInputTrack(this.props.selectedVideoInputId);
|
||||
}
|
||||
|
||||
if (!prevProps.hasVideoPermission && this.props.hasVideoPermission) {
|
||||
this._createVideoInputTrack(this.props.selectedVideoInputId)
|
||||
?.then(() => {
|
||||
this.props.dispatch(getAvailableDevices());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -32,6 +32,7 @@ const useStyles = makeStyles()(theme => {
|
||||
marginBottom: theme.spacing(2),
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
backgroundColor: theme.palette.gifsBackground,
|
||||
|
||||
'& div:focus': {
|
||||
border: '1px solid red !important',
|
||||
@@ -54,7 +55,7 @@ const useStyles = makeStyles()(theme => {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
color: '#fff',
|
||||
color: theme.palette.gifsText,
|
||||
marginTop: theme.spacing(1)
|
||||
},
|
||||
|
||||
@@ -62,7 +63,8 @@ const useStyles = makeStyles()(theme => {
|
||||
padding: theme.spacing(3),
|
||||
width: '100%',
|
||||
boxSizing: 'border-box',
|
||||
height: '100%'
|
||||
height: '100%',
|
||||
backgroundColor: theme.palette.gifsBackground
|
||||
},
|
||||
|
||||
overflowMenu: {
|
||||
|
||||
@@ -4,7 +4,6 @@ import BaseApp from '../../../../base/app/components/BaseApp';
|
||||
import { isMobileBrowser } from '../../../../base/environment/utils';
|
||||
import GlobalStyles from '../../../../base/ui/components/GlobalStyles.web';
|
||||
import JitsiThemeProvider from '../../../../base/ui/components/JitsiThemeProvider.web';
|
||||
import { parseURLParams } from '../../../../base/util/parseURLParams';
|
||||
import { DIAL_IN_INFO_PAGE_PATH_NAME } from '../../../constants';
|
||||
import NoRoomError from '../../dial-in-info-page/NoRoomError.web';
|
||||
|
||||
@@ -25,7 +24,8 @@ export default class DialInSummaryApp extends BaseApp<any> {
|
||||
await super.componentDidMount();
|
||||
|
||||
// @ts-ignore
|
||||
const { room } = parseURLParams(window.location, true, 'search');
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const room = params.get('room') || '';
|
||||
const { href } = window.location;
|
||||
const ix = href.indexOf(DIAL_IN_INFO_PAGE_PATH_NAME);
|
||||
const url = (ix > 0 ? href.substring(0, ix) : href) + room;
|
||||
@@ -36,7 +36,7 @@ export default class DialInSummaryApp extends BaseApp<any> {
|
||||
? <DialInSummary
|
||||
className = 'dial-in-page'
|
||||
clickableNumbers = { isMobileBrowser() }
|
||||
room = { decodeURIComponent(room) }
|
||||
room = { room }
|
||||
scrollable = { true }
|
||||
showTitle = { true }
|
||||
url = { url } />
|
||||
|
||||
@@ -17,6 +17,7 @@ import { parseURLParams } from '../base/util/parseURLParams';
|
||||
import {
|
||||
StatusCode,
|
||||
appendURLParam,
|
||||
getNormalizedRoomName,
|
||||
parseURIString
|
||||
} from '../base/util/uri';
|
||||
import { isVpaasMeeting } from '../jaas/functions';
|
||||
@@ -729,7 +730,7 @@ export function getDialInfoPageURL(state: IReduxState, roomName?: string) {
|
||||
const conferenceName = roomName ?? getRoomName(state);
|
||||
const { locationURL } = state['features/base/connection'];
|
||||
const { href = '' } = locationURL ?? {};
|
||||
const room = _decodeRoomURI(conferenceName ?? '');
|
||||
const room = getNormalizedRoomName(conferenceName) ?? '';
|
||||
|
||||
const url = didPageUrl || `${href.substring(0, href.lastIndexOf('/'))}/${DIAL_IN_INFO_PAGE_PATH_NAME}`;
|
||||
|
||||
|
||||
@@ -614,13 +614,7 @@ function _registerForNativeEvents(store: IStore) {
|
||||
|
||||
const activeSession = getActiveSession(state, mode);
|
||||
|
||||
if (!activeSession?.id) {
|
||||
logger.error('No recording or streaming session found');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
conference.stopRecording(activeSession.id);
|
||||
conference.stopRecording(activeSession?.id);
|
||||
});
|
||||
|
||||
eventEmitter.addListener(ExternalAPI.OVERWRITE_CONFIG, ({ config }: any) => {
|
||||
|
||||
@@ -57,9 +57,7 @@ export default class AbstractStopLiveStreamDialog extends Component<IProps> {
|
||||
|
||||
const { _session } = this.props;
|
||||
|
||||
if (_session) {
|
||||
this.props._conference?.stopRecording(_session.id);
|
||||
}
|
||||
this.props._conference?.stopRecording(_session?.id);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -102,8 +102,8 @@ export default class AbstractStopRecordingDialog<P extends IProps>
|
||||
if (localRecordingVideoStop) {
|
||||
dispatch(setVideoMuted(true));
|
||||
}
|
||||
} else if (_fileRecordingSession) {
|
||||
_conference?.stopRecording(_fileRecordingSession.id);
|
||||
} else {
|
||||
_conference?.stopRecording(_fileRecordingSession?.id);
|
||||
this._toggleScreenshotCapture();
|
||||
}
|
||||
|
||||
|
||||
@@ -87,30 +87,6 @@ paths:
|
||||
$ref: '#/components/schemas/AddDocumentResponse'
|
||||
example:
|
||||
fileId: e393a7e5-e790-4f43-836e-d27238201904
|
||||
delete:
|
||||
tags:
|
||||
- Document sharing history
|
||||
summary: Deletes documents for a given session, user and customer
|
||||
operationId: deleteDocumentsForSessionCustomerAndUser
|
||||
parameters:
|
||||
- name: sessionId
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
- name: user-id
|
||||
in: query
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
- name: customer-id
|
||||
in: query
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
'204':
|
||||
description: No Content
|
||||
/v1/documents/sessions/{sessionId}/files/{fileId}:
|
||||
get:
|
||||
tags:
|
||||
@@ -141,7 +117,7 @@ paths:
|
||||
tags:
|
||||
- Document sharing history
|
||||
summary: Delete a file by sessionId and fileId
|
||||
description: Delete a file by sessionId and fileId allowed by all moderators
|
||||
description: Delete a file by sessionId and fileId allowed by the "file-upload" feature
|
||||
operationId: deleteFile
|
||||
parameters:
|
||||
- name: sessionId
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
-- jwt is used to validate access
|
||||
-- Copyright (C) 2023-present 8x8, Inc.
|
||||
|
||||
local jid_split = require "util.jid".split;
|
||||
local jid = require "util.jid";
|
||||
local hashes = require "util.hashes";
|
||||
local random = require "util.random";
|
||||
local st = require("util.stanza");
|
||||
@@ -18,7 +18,6 @@ local muc_domain = module:get_option_string("muc_internal_domain_base", 'interna
|
||||
|
||||
local jigasi_brewery_room_jid = module:get_option_string("muc_jigasi_brewery_jid", 'jigasibrewery@' .. muc_domain);
|
||||
|
||||
local jigasi_bare_jid = module:get_option_string("muc_jigasi_jid", "jigasi@auth." .. muc_domain_base);
|
||||
local focus_jid = module:get_option_string("muc_jicofo_brewery_jid", jigasi_brewery_room_jid .. "/focus");
|
||||
|
||||
local main_muc_service;
|
||||
@@ -43,9 +42,9 @@ local function invite_jigasi(conference, phone_no)
|
||||
|
||||
--select least stressed Jigasi
|
||||
local least_stressed_value = math.huge;
|
||||
local least_stressed_jigasi_jid;
|
||||
local least_stressed_jigasi_occupant;
|
||||
for occupant_jid, occupant in jigasi_brewery_room:each_occupant() do
|
||||
local _, _, resource = jid_split(occupant_jid);
|
||||
local _, _, resource = jid.split(occupant_jid);
|
||||
if resource ~= 'focus' then
|
||||
local occ = occupant:get_presence();
|
||||
local stats_child = occ:get_child("stats", "http://jitsi.org/protocol/colibri")
|
||||
@@ -63,7 +62,7 @@ local function invite_jigasi(conference, phone_no)
|
||||
local stress_level = tonumber(stats_tag.attr.value);
|
||||
module:log("debug", "Stressed level %s %s ", stress_level, occupant_jid)
|
||||
if stress_level < least_stressed_value then
|
||||
least_stressed_jigasi_jid = occupant_jid
|
||||
least_stressed_jigasi_occupant = occupant;
|
||||
least_stressed_value = stress_level
|
||||
end
|
||||
end
|
||||
@@ -71,18 +70,15 @@ local function invite_jigasi(conference, phone_no)
|
||||
end
|
||||
end
|
||||
end
|
||||
module:log("debug", "Least stressed jigasi selected jid %s value %s", least_stressed_jigasi_jid, least_stressed_value)
|
||||
if not least_stressed_jigasi_jid then
|
||||
module:log("debug", "Least stressed jigasi selected jid %s value %s", least_stressed_jigasi_occupant.jid, least_stressed_value)
|
||||
if not least_stressed_jigasi_occupant then
|
||||
module:log("error", "Cannot invite jigasi from room %s", jigasi_brewery_room.jid)
|
||||
return 404, 'Jigasi not found'
|
||||
end
|
||||
|
||||
-- invite Jigasi to join the conference
|
||||
local _, _, jigasi_res = jid_split(least_stressed_jigasi_jid)
|
||||
local jigasi_full_jid = jigasi_bare_jid .. "/" .. jigasi_res;
|
||||
local stanza_id = hashes.sha256(random.bytes(8), true);
|
||||
|
||||
local invite_jigasi_stanza = st.iq({ xmlns = "jabber:client", type = "set", to = jigasi_full_jid, from = focus_jid, id = stanza_id })
|
||||
local invite_jigasi_stanza = st.iq({ xmlns = "jabber:client", type = "set", to = least_stressed_jigasi_occupant.jid, from = focus_jid, id = stanza_id })
|
||||
:tag("dial", { xmlns = "urn:xmpp:rayo:1", from = "fromnumber", to = phone_no })
|
||||
:tag("header", { xmlns = "urn:xmpp:rayo:1", name = "JvbRoomName", value = conference })
|
||||
|
||||
|
||||
@@ -141,6 +141,11 @@ end
|
||||
local main_room = get_room_by_name_and_subdomain(session.jitsi_web_query_room, session.jitsi_web_query_prefix);
|
||||
local occupant_jid = stanza.attr.from;
|
||||
|
||||
if not main_room then
|
||||
module:log('warn', 'No main room found for %s %s', session.jitsi_web_query_room, session.jitsi_web_query_prefix);
|
||||
return;
|
||||
end
|
||||
|
||||
occupant = main_room:get_occupant_by_real_jid(occupant_jid);
|
||||
|
||||
if main_room._data.breakout_rooms_active and not occupant then
|
||||
@@ -148,10 +153,13 @@ end
|
||||
-- not in main room, let's check breakout rooms
|
||||
for breakout_room_jid, subject in pairs(main_room._data.breakout_rooms or {}) do
|
||||
local breakout_room = get_room_from_jid(breakout_room_jid);
|
||||
occupant = breakout_room:get_occupant_by_real_jid(occupant_jid);
|
||||
if occupant then
|
||||
room = breakout_room;
|
||||
break;
|
||||
|
||||
if breakout_room then
|
||||
occupant = breakout_room:get_occupant_by_real_jid(occupant_jid);
|
||||
if occupant then
|
||||
room = breakout_room;
|
||||
break;
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
|
||||
@@ -279,7 +279,9 @@ function Util:process_and_verify_token(session)
|
||||
-- We're fetching an public key from an ASAP server
|
||||
local dotFirst = session.auth_token:find("%.");
|
||||
if not dotFirst then return false, "not-allowed", "Invalid token" end
|
||||
local header, err = json_safe.decode(basexx.from_url64(session.auth_token:sub(1,dotFirst-1)));
|
||||
local headerPartEncoded = basexx.from_url64(session.auth_token:sub(1,dotFirst-1));
|
||||
if not headerPartEncoded then return false, "not-allowed", "Invalid token" end
|
||||
local header, err = json_safe.decode(headerPartEncoded);
|
||||
if err then
|
||||
return false, "not-allowed", "bad token format";
|
||||
end
|
||||
|
||||
@@ -8,6 +8,7 @@ import { IConfig } from '../../react/features/base/config/configType';
|
||||
import { urlObjectToString } from '../../react/features/base/util/uri';
|
||||
import BreakoutRooms from '../pageobjects/BreakoutRooms';
|
||||
import ChatPanel from '../pageobjects/ChatPanel';
|
||||
import FileSharingPanel from '../pageobjects/FileSharingPanel';
|
||||
import Filmstrip from '../pageobjects/Filmstrip';
|
||||
import IframeAPI from '../pageobjects/IframeAPI';
|
||||
import InviteDialog from '../pageobjects/InviteDialog';
|
||||
@@ -534,6 +535,13 @@ export class Participant {
|
||||
return new ChatPanel(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the file sharing panel for this participant.
|
||||
*/
|
||||
getFileSharingPanel(): FileSharingPanel {
|
||||
return new FileSharingPanel(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the BreakoutRooms for this participant.
|
||||
*
|
||||
|
||||
@@ -22,6 +22,7 @@ const defaultExpectations = {
|
||||
enabled: true
|
||||
},
|
||||
jaas: {
|
||||
fileSharingEnabled: true,
|
||||
liveStreamingEnabled: true,
|
||||
recordingEnabled: true,
|
||||
transcriptionEnabled: true,
|
||||
|
||||
@@ -10,6 +10,11 @@ export type ITokenOptions = {
|
||||
* The duration for which the token is valid, e.g. "1h" for one hour.
|
||||
*/
|
||||
exp?: string;
|
||||
/**
|
||||
* Additional JWT features to include (merged with the defaults).
|
||||
* Keys are feature names (e.g. 'file-upload'), values are boolean or string.
|
||||
*/
|
||||
features?: Record<string, boolean | string>;
|
||||
/**
|
||||
* The key ID to use for the token.
|
||||
* If not provided, the kid configured with environment variables will be used (see env.example).
|
||||
@@ -72,7 +77,8 @@ export function generatePayload(options: ITokenOptions): any {
|
||||
'transcription': 'true',
|
||||
'recording': 'true',
|
||||
'sip-outbound-call': true,
|
||||
'livestreaming': true
|
||||
'livestreaming': true,
|
||||
...options.features
|
||||
},
|
||||
},
|
||||
'room': options.room || '*'
|
||||
|
||||
172
tests/pageobjects/FileSharingPanel.ts
Normal file
172
tests/pageobjects/FileSharingPanel.ts
Normal file
@@ -0,0 +1,172 @@
|
||||
import BasePageObject from './BasePageObject';
|
||||
|
||||
const FILE_SHARING_TAB_ID = 'file_sharing-tab';
|
||||
const FILE_SHARING_PANEL_ID = 'file_sharing-tab-panel';
|
||||
const UPLOAD_BUTTON_LABEL = 'Share file';
|
||||
const DOWNLOAD_BUTTON_LABEL = 'Download';
|
||||
const REMOVE_BUTTON_LABEL = 'Remove';
|
||||
|
||||
/**
|
||||
* Page object for the file sharing panel.
|
||||
*/
|
||||
export default class FileSharingPanel extends BasePageObject {
|
||||
/**
|
||||
* Opens the chat sidebar and navigates to the file sharing tab.
|
||||
*/
|
||||
async open(): Promise<void> {
|
||||
if (!await this.participant.driver.$('#sideToolbarContainer').isExisting()) {
|
||||
await this.participant.getToolbar().clickChatButton();
|
||||
}
|
||||
await this.participant.driver.$(`#${FILE_SHARING_TAB_ID}`).click();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the file sharing panel is the currently active tab.
|
||||
*/
|
||||
async isActive(): Promise<boolean> {
|
||||
return this.participant.execute(() => {
|
||||
// @ts-ignore
|
||||
const state = APP?.store?.getState?.();
|
||||
|
||||
return state?.['features/chat']?.focusedTab === 'file_sharing-tab';
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the upload button is enabled (not disabled).
|
||||
*/
|
||||
async isUploadButtonEnabled(): Promise<boolean> {
|
||||
const btn = this.participant.driver.$(`[aria-label="${UPLOAD_BUTTON_LABEL}"]`);
|
||||
|
||||
return !await btn.getAttribute('disabled');
|
||||
}
|
||||
|
||||
/**
|
||||
* Uploads a file via the hidden file input.
|
||||
*
|
||||
* @param {string} localFilePath - Local path to the file to upload.
|
||||
*/
|
||||
async uploadFile(localFilePath: string): Promise<void> {
|
||||
const remotePath = await this.participant.driver.uploadFile(localFilePath);
|
||||
const input = this.participant.driver.$(`#${FILE_SHARING_PANEL_ID} input[type="file"]`);
|
||||
|
||||
await input.addValue(remotePath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits until a file with the given name appears in the file list.
|
||||
*
|
||||
* @param {string} fileName - The file name to wait for.
|
||||
* @param {number} timeout - Timeout in milliseconds.
|
||||
*/
|
||||
async waitForFile(fileName: string, timeout = 15_000): Promise<void> {
|
||||
await this.participant.driver.$(`#${FILE_SHARING_PANEL_ID} [title="${fileName}"]`)
|
||||
.waitForExist({ timeout, timeoutMsg: `File "${fileName}" did not appear within ${timeout}ms` });
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits until a file with the given name disappears from the file list.
|
||||
*
|
||||
* @param {string} fileName - The file name to wait for.
|
||||
* @param {number} timeout - Timeout in milliseconds.
|
||||
*/
|
||||
async waitForFileGone(fileName: string, timeout = 15_000): Promise<void> {
|
||||
await this.participant.driver.$(`#${FILE_SHARING_PANEL_ID} [title="${fileName}"]`)
|
||||
.waitForExist({ timeout, reverse: true, timeoutMsg: `File "${fileName}" still present after ${timeout}ms` });
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether a file with the given name exists in the file list.
|
||||
*
|
||||
* @param {string} fileName - The file name to check.
|
||||
*/
|
||||
async hasFile(fileName: string): Promise<boolean> {
|
||||
return this.participant.driver.$(`#${FILE_SHARING_PANEL_ID} [title="${fileName}"]`).isExisting();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clicks the download button for the given file. Hovers over the file item first to make action buttons visible.
|
||||
*
|
||||
* @param {string} fileName - The file name to download.
|
||||
*/
|
||||
async downloadFile(fileName: string): Promise<void> {
|
||||
await this.hoverOverFileItem(fileName);
|
||||
await this.participant.driver
|
||||
.$(`button[aria-label="${DOWNLOAD_BUTTON_LABEL} ${fileName}"]`)
|
||||
.click();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clicks the remove button for the given file. Hovers over the file item first to make action buttons visible.
|
||||
*
|
||||
* @param {string} fileName - The file name to remove.
|
||||
*/
|
||||
async removeFile(fileName: string): Promise<void> {
|
||||
await this.hoverOverFileItem(fileName);
|
||||
await this.participant.driver
|
||||
.$(`button[aria-label="${REMOVE_BUTTON_LABEL} ${fileName}"]`)
|
||||
.click();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the remove button exists in the DOM for the given file.
|
||||
* The remove button is only rendered when the participant has the 'file-upload' JWT feature.
|
||||
*
|
||||
* @param {string} fileName - The file name.
|
||||
*/
|
||||
canRemoveFile(fileName: string) {
|
||||
return this.participant.driver
|
||||
.$(`button[aria-label="${REMOVE_BUTTON_LABEL} ${fileName}"]`)
|
||||
.isExisting();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the download button exists in the DOM for the given file.
|
||||
*
|
||||
* @param {string} fileName - The file name.
|
||||
*/
|
||||
canDownloadFile(fileName: string) {
|
||||
return this.participant.driver
|
||||
.$(`button[aria-label="${DOWNLOAD_BUTTON_LABEL} ${fileName}"]`)
|
||||
.isExisting();
|
||||
}
|
||||
|
||||
/**
|
||||
* Simulates dragging a file into the conference area and dispatches drag events.
|
||||
* A dragenter event sets the React isDragging state, then after a short delay a
|
||||
* dragover event triggers the file sharing tab to open.
|
||||
*/
|
||||
async simulateDragIntoConference(): Promise<void> {
|
||||
await this.participant.driver.executeAsync((done: () => void) => {
|
||||
const el = document.querySelector('[data-testid="conference-drag-zone"]') ?? document.body;
|
||||
|
||||
el.dispatchEvent(new DragEvent('dragenter', {
|
||||
bubbles: true,
|
||||
cancelable: true
|
||||
}));
|
||||
setTimeout(() => {
|
||||
el.dispatchEvent(new DragEvent('dragover', {
|
||||
bubbles: true,
|
||||
cancelable: true
|
||||
}));
|
||||
done();
|
||||
}, 200);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the chat sidebar is currently open.
|
||||
*/
|
||||
isChatOpen() {
|
||||
return this.participant.driver.$('#sideToolbarContainer').isExisting();
|
||||
}
|
||||
|
||||
/**
|
||||
* Hovers over the file item element to make its action buttons visible.
|
||||
*
|
||||
* @param {string} fileName - The file name whose item should be hovered.
|
||||
*/
|
||||
private async hoverOverFileItem(fileName: string): Promise<void> {
|
||||
await this.participant.driver.$(`#${FILE_SHARING_PANEL_ID} [title="${fileName}"]`).moveTo();
|
||||
}
|
||||
}
|
||||
1
tests/resources/test-upload.txt
Normal file
1
tests/resources/test-upload.txt
Normal file
@@ -0,0 +1 @@
|
||||
This is a test file for file sharing upload tests.
|
||||
169
tests/specs/jaas/fileSharing.spec.ts
Normal file
169
tests/specs/jaas/fileSharing.spec.ts
Normal file
@@ -0,0 +1,169 @@
|
||||
import { expect } from '@wdio/globals';
|
||||
|
||||
import type { Participant } from '../../helpers/Participant';
|
||||
import { setTestProperties } from '../../helpers/TestProperties';
|
||||
import { expectations } from '../../helpers/expectations';
|
||||
import { joinJaasMuc, generateJaasToken as t } from '../../helpers/jaas';
|
||||
|
||||
setTestProperties(__filename, {
|
||||
useJaas: true,
|
||||
usesBrowsers: [ 'p1', 'p2' ]
|
||||
});
|
||||
|
||||
const TEST_FILE_PATH = 'tests/resources/test-upload.txt';
|
||||
const TEST_FILE_NAME = 'test-upload.txt';
|
||||
|
||||
describe('File sharing', () => {
|
||||
let p1: Participant, p2: Participant;
|
||||
let fileSharingEnabled: boolean;
|
||||
|
||||
it('setup', async () => {
|
||||
const room = ctx.roomName;
|
||||
|
||||
p1 = await joinJaasMuc({
|
||||
name: 'p1',
|
||||
token: t({ room, features: { 'file-upload': 'true' } })
|
||||
});
|
||||
|
||||
fileSharingEnabled = await p1.execute(
|
||||
() => Boolean((window as any).config?.fileSharing?.enabled && (window as any).config?.fileSharing?.apiUrl)
|
||||
);
|
||||
|
||||
expect(fileSharingEnabled).toBe(expectations.jaas.fileSharingEnabled);
|
||||
});
|
||||
|
||||
it('upload button enabled with file-upload feature', async () => {
|
||||
if (!fileSharingEnabled) {
|
||||
return;
|
||||
}
|
||||
const panel = p1.getFileSharingPanel();
|
||||
|
||||
await panel.open();
|
||||
|
||||
expect(await panel.isUploadButtonEnabled()).toBe(true);
|
||||
});
|
||||
|
||||
it('upload button disabled without file-upload feature', async () => {
|
||||
if (!fileSharingEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
p2 = await joinJaasMuc({ name: 'p2', token: t({ room: ctx.roomName }) });
|
||||
|
||||
const panel = p2.getFileSharingPanel();
|
||||
|
||||
await panel.open();
|
||||
|
||||
expect(await panel.isUploadButtonEnabled()).toBe(false);
|
||||
});
|
||||
|
||||
it('user with file-upload can delete files uploaded by another participant', async () => {
|
||||
if (!fileSharingEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
// p1 (with file-upload) uploads a file
|
||||
const p1Panel = p1.getFileSharingPanel();
|
||||
|
||||
await p1Panel.open();
|
||||
await p1Panel.uploadFile(TEST_FILE_PATH);
|
||||
await p1Panel.waitForFile(TEST_FILE_NAME);
|
||||
|
||||
// p2 (joined earlier without file-upload) rejoins with file-upload to test deletion of p1's file
|
||||
p2 = await joinJaasMuc({
|
||||
name: 'p2',
|
||||
token: t({ room: ctx.roomName, features: { 'file-upload': 'true' } })
|
||||
});
|
||||
|
||||
const p2Panel = p2.getFileSharingPanel();
|
||||
|
||||
await p2Panel.open();
|
||||
await p2Panel.waitForFile(TEST_FILE_NAME);
|
||||
|
||||
// p2 should be able to see and use the remove button for p1's file
|
||||
expect(await p2Panel.canRemoveFile(TEST_FILE_NAME)).toBe(true);
|
||||
|
||||
await p2Panel.removeFile(TEST_FILE_NAME);
|
||||
|
||||
await p1Panel.waitForFileGone(TEST_FILE_NAME);
|
||||
});
|
||||
|
||||
it('user without file-upload can download but not delete files', async () => {
|
||||
if (!fileSharingEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
// p1 (with file-upload) uploads a fresh file
|
||||
const p1Panel = p1.getFileSharingPanel();
|
||||
|
||||
await p1Panel.open();
|
||||
await p1Panel.uploadFile(TEST_FILE_PATH);
|
||||
await p1Panel.waitForFile(TEST_FILE_NAME);
|
||||
|
||||
// p2 rejoins without file-upload
|
||||
p2 = await joinJaasMuc({ name: 'p2', token: t({ room: ctx.roomName }) });
|
||||
|
||||
const p2Panel = p2.getFileSharingPanel();
|
||||
|
||||
await p2Panel.open();
|
||||
await p2Panel.waitForFile(TEST_FILE_NAME);
|
||||
|
||||
// Download button should be present, remove button should not
|
||||
expect(await p2Panel.canDownloadFile(TEST_FILE_NAME)).toBe(true);
|
||||
expect(await p2Panel.canRemoveFile(TEST_FILE_NAME)).toBe(false);
|
||||
});
|
||||
|
||||
it('dragging into conference opens file sharing tab', async () => {
|
||||
if (!fileSharingEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
// p1 already has file-upload feature
|
||||
const panel = p1.getFileSharingPanel();
|
||||
|
||||
// Ensure chat is closed before starting
|
||||
if (await panel.isChatOpen()) {
|
||||
await p1.getToolbar().clickCloseChatButton();
|
||||
}
|
||||
|
||||
await panel.simulateDragIntoConference();
|
||||
|
||||
await p1.driver.waitUntil(
|
||||
() => panel.isChatOpen(),
|
||||
{ timeout: 3000, timeoutMsg: 'Chat did not open after drag' }
|
||||
);
|
||||
|
||||
expect(await panel.isActive()).toBe(true);
|
||||
});
|
||||
|
||||
it('dragging on pre-join screen does not open file sharing', async () => {
|
||||
if (!fileSharingEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Join with iFrame API, file-upload feature, and pre-join screen enabled — but do NOT click join
|
||||
p1 = await joinJaasMuc(
|
||||
{
|
||||
name: 'p1',
|
||||
iFrameApi: true,
|
||||
token: t({ room: ctx.roomName, features: { 'file-upload': 'true' } })
|
||||
},
|
||||
{
|
||||
configOverwrite: { prejoinConfig: { enabled: true } },
|
||||
skipPrejoinButtonClick: true,
|
||||
skipWaitToJoin: true
|
||||
}
|
||||
);
|
||||
|
||||
// Wait for pre-join screen to appear
|
||||
await p1.getPreJoinScreen().waitForLoading();
|
||||
|
||||
// Simulate drag while on pre-join screen
|
||||
const panel = p1.getFileSharingPanel();
|
||||
|
||||
await panel.simulateDragIntoConference();
|
||||
|
||||
// Chat/file sharing should NOT have opened
|
||||
expect(await panel.isChatOpen()).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -3,6 +3,8 @@ import { multiremotebrowser } from '@wdio/globals';
|
||||
import { Buffer } from 'buffer';
|
||||
import fs from 'fs';
|
||||
import { glob } from 'glob';
|
||||
import junitReportBuilder from 'junit-report-builder';
|
||||
import { randomUUID } from 'node:crypto';
|
||||
import path from 'node:path';
|
||||
import process from 'node:process';
|
||||
import pretty from 'pretty';
|
||||
@@ -146,7 +148,7 @@ export const config: WebdriverIO.MultiremoteConfig = {
|
||||
|
||||
// Default timeout in milliseconds for request
|
||||
// if browser driver or grid doesn't send response
|
||||
connectionRetryTimeout: 15_000,
|
||||
connectionRetryTimeout: 30_000,
|
||||
|
||||
// Default request retries count
|
||||
connectionRetryCount: 3,
|
||||
@@ -468,6 +470,60 @@ export const config: WebdriverIO.MultiremoteConfig = {
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets executed after a worker process has exited.
|
||||
* If the worker crashed (e.g. session DELETE timeout), the JUnit reporter never flushes,
|
||||
* leaving a zero-byte XML file. This hook detects that and writes a failure entry so the
|
||||
* report generator has something to show.
|
||||
*/
|
||||
onWorkerEnd(cid, exitCode, workerSpecs) {
|
||||
if (exitCode === 0) {
|
||||
return;
|
||||
}
|
||||
const xmlPath = path.join(TEST_RESULTS_DIR, `results-${cid}.xml`);
|
||||
|
||||
try {
|
||||
if (fs.statSync(xmlPath).size > 0) {
|
||||
return;
|
||||
}
|
||||
} catch {
|
||||
// file doesn't exist yet — fall through and create it
|
||||
}
|
||||
|
||||
const specName = workerSpecs?.[0] ? path.basename(workerSpecs[0], '.spec.ts') : 'unknown';
|
||||
const dirMatch = workerSpecs?.[0]?.match(/\/tests\/specs\/([^/]+)\//);
|
||||
const dir = dirMatch ? dirMatch[1] : 'unknown';
|
||||
const message = `Worker exited with code ${exitCode} before results were written. Test result is unknown - tests may have passed.`;
|
||||
|
||||
const b = junitReportBuilder.newBuilder();
|
||||
|
||||
b.testSuite().name(specName).testCase()
|
||||
.name('Test runner crashed')
|
||||
.className(specName)
|
||||
.error(message);
|
||||
b.writeTo(xmlPath);
|
||||
|
||||
const allureResult = {
|
||||
uuid: randomUUID(),
|
||||
name: 'Test runner crashed',
|
||||
status: 'broken',
|
||||
statusDetails: { message },
|
||||
stage: 'finished',
|
||||
steps: [],
|
||||
attachments: [],
|
||||
parameters: [],
|
||||
labels: [
|
||||
{ name: 'parentSuite', value: dir },
|
||||
{ name: 'suite', value: specName }
|
||||
],
|
||||
links: []
|
||||
};
|
||||
const allurePath = path.join(TEST_RESULTS_DIR, 'allure-results', `${allureResult.uuid}-result.json`);
|
||||
|
||||
fs.writeFileSync(allurePath, JSON.stringify(allureResult));
|
||||
console.log(`[onWorkerEnd] Wrote error XML and allure result for crashed worker ${cid} (spec: ${specName})`);
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets executed after all workers have shut down and the process is about to exit.
|
||||
* An error thrown in the `onComplete` hook will result in the test run failing.
|
||||
@@ -503,7 +559,6 @@ export const config: WebdriverIO.MultiremoteConfig = {
|
||||
}
|
||||
});
|
||||
|
||||
const reportError = new Error('Could not generate Allure report');
|
||||
const generation = allure([
|
||||
'generate', `${TEST_RESULTS_DIR}/allure-results`,
|
||||
'--clean', '--single-file',
|
||||
@@ -512,15 +567,15 @@ export const config: WebdriverIO.MultiremoteConfig = {
|
||||
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
const generationTimeout = setTimeout(
|
||||
() => reject(reportError),
|
||||
5000);
|
||||
() => reject(new Error('Could not generate Allure report: timed out after 60s')),
|
||||
60_000);
|
||||
|
||||
// @ts-ignore
|
||||
generation.on('exit', eCode => {
|
||||
clearTimeout(generationTimeout);
|
||||
|
||||
if (eCode !== 0) {
|
||||
return reject(reportError);
|
||||
return reject(new Error(`Could not generate Allure report: allure exited with code ${eCode}`));
|
||||
}
|
||||
|
||||
console.log('Allure report successfully generated');
|
||||
|
||||
@@ -7,5 +7,5 @@ import { merge } from 'lodash-es';
|
||||
import { config as defaultConfig } from './wdio.conf.ts';
|
||||
|
||||
export const config = merge(defaultConfig, {
|
||||
baseUrl: 'https://127.0.0.1:8080/torture'
|
||||
baseUrl: 'https://localhost:8080/torture'
|
||||
}, { clone: false });
|
||||
|
||||
@@ -257,6 +257,7 @@ function getDevServerConfig() {
|
||||
warnings: false
|
||||
}
|
||||
},
|
||||
allowedHosts: 'all',
|
||||
host: 'localhost',
|
||||
hot: true,
|
||||
proxy: [
|
||||
|
||||
Reference in New Issue
Block a user