mirror of
https://gitcode.com/GitHub_Trending/ji/jitsi-meet.git
synced 2026-05-23 08:27:51 +00:00
Compare commits
38 Commits
8919
...
damencho-p
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
54161a828c | ||
|
|
9013881f76 | ||
|
|
b6e7e0a19e | ||
|
|
ae42e42534 | ||
|
|
21e2504cf9 | ||
|
|
7a9ba79783 | ||
|
|
1f5a3b5b0f | ||
|
|
fe2aff4f3c | ||
|
|
d847f6f96b | ||
|
|
45ce467dcd | ||
|
|
2b81fa6bd3 | ||
|
|
6f6100ceb2 | ||
|
|
62cd1c29d7 | ||
|
|
64869e8970 | ||
|
|
29464e6886 | ||
|
|
5ed92f2bc5 | ||
|
|
048d12de24 | ||
|
|
40c240c7ca | ||
|
|
289c1907e7 | ||
|
|
35adea48ae | ||
|
|
d72114d5bc | ||
|
|
2f6b6ca837 | ||
|
|
615bbdc39b | ||
|
|
ef97778158 | ||
|
|
2885f39355 | ||
|
|
ae256b23b8 | ||
|
|
412aa83268 | ||
|
|
f4c61e4760 | ||
|
|
f313fb81d0 | ||
|
|
975af80e27 | ||
|
|
0a30a51bab | ||
|
|
54e28e223c | ||
|
|
a4def96763 | ||
|
|
dad4fb9e06 | ||
|
|
3772b9a5ae | ||
|
|
89b9c75242 | ||
|
|
b24b60b735 | ||
|
|
486a1f6511 |
12
.github/dependabot.yml
vendored
Normal file
12
.github/dependabot.yml
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
version: 2
|
||||
updates:
|
||||
# Monitor GitHub Actions versions
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
labels:
|
||||
- "dependencies"
|
||||
- "github-actions"
|
||||
commit-message:
|
||||
prefix: "chore(ci)"
|
||||
2
.github/workflows/ci-lua.yml
vendored
2
.github/workflows/ci-lua.yml
vendored
@@ -7,7 +7,7 @@ jobs:
|
||||
name: Luacheck
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Install luarocks
|
||||
run: sudo apt-get --install-recommends -y install luarocks
|
||||
|
||||
33
.github/workflows/ci.yml
vendored
33
.github/workflows/ci.yml
vendored
@@ -7,8 +7,8 @@ jobs:
|
||||
name: Lint
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'npm'
|
||||
@@ -42,8 +42,8 @@ jobs:
|
||||
matrix:
|
||||
os: [macos-latest, ubuntu-latest]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'npm'
|
||||
@@ -59,8 +59,8 @@ jobs:
|
||||
name: Build mobile bundle (Android)
|
||||
runs-on: macos-15
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'npm'
|
||||
@@ -74,8 +74,8 @@ jobs:
|
||||
name: Build mobile bundle (iOS)
|
||||
runs-on: macos-15
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'npm'
|
||||
@@ -103,10 +103,10 @@ jobs:
|
||||
android-sdk-build:
|
||||
name: Build mobile SDK (Android)
|
||||
runs-on: ubuntu-latest
|
||||
container: reactnativecommunity/react-native-android:v18.0
|
||||
container: reactnativecommunity/react-native-android:v15.0
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'npm'
|
||||
@@ -119,12 +119,15 @@ jobs:
|
||||
cd android
|
||||
./gradlew :sdk:clean
|
||||
./gradlew :sdk:assembleRelease
|
||||
- run: |
|
||||
git config --global --add safe.directory /__w/jitsi-meet/jitsi-meet
|
||||
git clean -dfx
|
||||
ios-sdk-build:
|
||||
name: Build mobile SDK (iOS)
|
||||
runs-on: macos-15
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'npm'
|
||||
@@ -173,8 +176,8 @@ jobs:
|
||||
name: Test Debian packages build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'npm'
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
# <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.
|
||||
|
||||
|
||||
@@ -907,6 +907,8 @@ var config = {
|
||||
// alwaysVisible: false,
|
||||
// // Indicates whether the toolbar should still autohide when chat is open
|
||||
// autoHideWhileChatIsOpen: false,
|
||||
// // Default background color for the main toolbar. Accepts any valid CSS color.
|
||||
// // backgroundColor: '#ffffff',
|
||||
// },
|
||||
|
||||
// Overrides the buttons displayed in the main toolbar. Depending on the screen size the number of displayed
|
||||
|
||||
7
debian/jitsi-meet-prosody.postinst
vendored
7
debian/jitsi-meet-prosody.postinst
vendored
@@ -124,10 +124,17 @@ case "$1" in
|
||||
ln -s $PROSODY_HOST_CONFIG /etc/prosody/conf.d/$JVB_HOSTNAME.cfg.lua
|
||||
fi
|
||||
PROSODY_CREATE_JICOFO_USER="true"
|
||||
fi
|
||||
|
||||
if ! grep -q "VirtualHost \"$JVB_HOSTNAME\"" $PROSODY_CONFIG_OLD; then
|
||||
# on some distributions main prosody config doesn't include configs
|
||||
# from conf.d folder enable it as this where we put our config by default
|
||||
# also when upgrading to new prosody version from prosody repo we need to add it again
|
||||
if ! grep -q "Include \"conf\.d\/\*\.cfg.lua\"" $PROSODY_CONFIG_OLD; then
|
||||
echo -e "\nInclude \"conf.d/*.cfg.lua\"" >> $PROSODY_CONFIG_OLD
|
||||
|
||||
# trigger a restart
|
||||
PROSODY_CONFIG_PRESENT="false"
|
||||
fi
|
||||
fi
|
||||
|
||||
|
||||
@@ -1484,6 +1484,7 @@
|
||||
"connectionInfo": "Verbindungsinformationen",
|
||||
"demote": "Zu Gästen verschieben",
|
||||
"domute": "Stummschalten",
|
||||
"domuteDesktopOfOthers": "Bildschirm freigeben für alle beenden",
|
||||
"domuteOthers": "Alle anderen stummschalten",
|
||||
"domuteVideo": "Kamera ausschalten",
|
||||
"domuteVideoOfOthers": "Alle anderen Kameras auschalten",
|
||||
|
||||
@@ -114,6 +114,9 @@
|
||||
"error": "Kļūda: Jūsu ziņa netika nosūtīta. Cēlonis: {{error}}",
|
||||
"everyone": "Visi",
|
||||
"fieldPlaceHolder": "Rakstiet ziņu šeit",
|
||||
"fileAccessibleTitle": "{{user}} augšuplādēja failu",
|
||||
"fileAccessibleTitleMe": "es augšuplādēju failu",
|
||||
"fileDeleted": "Fails tika dzēsts",
|
||||
"guestsChatIndicator": "(viesis)",
|
||||
"lobbyChatMessageTo": "Vestibila tērzēšanas ziņa adresātam {{recipient}}",
|
||||
"message": "Ziņa",
|
||||
@@ -123,12 +126,20 @@
|
||||
"messagebox": "Rakstiet ziņu",
|
||||
"newMessages": "Jaunas ziņas",
|
||||
"nickname": {
|
||||
"popover": "Izvēlieties vārdu",
|
||||
"title": "Ierakstiet vārdu, lai izmantotu tērzēšanā",
|
||||
"titleWithCC": "Ievadiet segvārdu, lai izmantotu tērzēšanā un slēptos subtitros",
|
||||
"titleWithPolls": "Ierakstiet segvārdu, lai izmantotu tērzēšanā un aptaujās",
|
||||
"titleWithPollsAndCC": "Ievadiet segvārdu, lai izmantotu tērzēšanā, aptaujās un slēptos subtitros",
|
||||
"titleWithPollsAndCCAndFileSharing": "Ievadiet segvārdu, lai izmantotu tērzēšanā, aptaujās, slēptos subtitros un failos"
|
||||
"featureChat": "tērzētava",
|
||||
"featureClosedCaptions": "slēgtie subtitri",
|
||||
"featureFileSharing": "failu kopīgošana",
|
||||
"featurePolls": "aptaujas",
|
||||
"popover": "Izvēlieties segvārdu",
|
||||
"title": "Ierakstiet segvārdu, lai izmantotu tērzēšanu",
|
||||
"titleWith1Features": "Ievadiet segvārdu, lai izmantotu {{feature1}}",
|
||||
"titleWith2Features": "Ievadiet segvārdu, lai izmantotu {{feature1}} un {{feature2}}",
|
||||
"titleWith3Features": "Ievadiet segvārdu, lai izmantotu {{feature1}}, {{feature2}} un {{feature3}}",
|
||||
"titleWith4Features": "Ievadiet segvārdu, lai izmantotu {{feature1}}, {{feature2}}, {{feature3}} un {{feature4}}",
|
||||
"titleWithCC": "Ievadiet segvārdu, lai izmantotu tērzēšanu un slēgtos subtitrus",
|
||||
"titleWithPolls": "Ierakstiet segvārdu, lai izmantotu tērzēšanu un aptaujas",
|
||||
"titleWithPollsAndCC": "Ievadiet segvārdu, lai izmantotu tērzēšanu, aptaujas un slēgtos subtitrus",
|
||||
"titleWithPollsAndCCAndFileSharing": "Ievadiet segvārdu, lai izmantotu tērzēšanu, aptaujas, slēgtos subtitrus un failus"
|
||||
},
|
||||
"noMessagesMessage": "Sapulcē pagaidām nav nevienas ziņas. Uzsāciet saraksti!",
|
||||
"privateNotice": "Privāta ziņa adresātam {{recipient}}",
|
||||
@@ -137,12 +148,12 @@
|
||||
"systemDisplayName": "Sistēma",
|
||||
"tabs": {
|
||||
"chat": "Tērzēšana",
|
||||
"closedCaptions": "Slēptie subtitri",
|
||||
"closedCaptions": "Slēgtie subtitri",
|
||||
"fileSharing": "Faili",
|
||||
"polls": "Aptaujas"
|
||||
},
|
||||
"title": "Tērzēšana",
|
||||
"titleWithCC": "Tērzēšana un Slēptie subtitri",
|
||||
"titleWithCC": "Tērzēšana un Slēgtie subtitri",
|
||||
"titleWithFeatures": "Tērzēšana un",
|
||||
"titleWithFileSharing": "Faili",
|
||||
"titleWithPolls": "Tērzēšana un Aptaujas",
|
||||
@@ -156,8 +167,8 @@
|
||||
"installExtensionText": "Uzstādīt spraudni Google kalendāra un Office 365 integrācijai"
|
||||
},
|
||||
"closedCaptionsTab": {
|
||||
"emptyState": "Slēpto subtitru saturs būs pieejams, tiklīdz moderators uzsāks to.",
|
||||
"startClosedCaptionsButton": "Uzsākt slēptos subtitrus"
|
||||
"emptyState": "Slēgto subtitru saturs būs pieejams, tiklīdz moderators uzsāks to.",
|
||||
"startClosedCaptionsButton": "Uzsākt slēgtos subtitrus"
|
||||
},
|
||||
"connectingOverlay": {
|
||||
"joiningRoom": "Notiek pieslēgšanās jūsu sapulcei…"
|
||||
@@ -280,7 +291,6 @@
|
||||
"Submit": "Iesniegt",
|
||||
"Understand": "Saprotu",
|
||||
"UnderstandAndUnmute": "Es saprotu, lūdzu, ieslēdziet skaņu.",
|
||||
"WaitForHostMsg": "Sapulce vēl nav sākusies, jo vēl nav ieradies neviens moderators. Lūdzu, autorizējieties, lai kļūtu par moderatoru. Pretējā gadījumā, lūdzu, uzgaidiet.",
|
||||
"WaitForHostNoAuthMsg": "Sapulce vēl nav sākusies, jo vēl nav ieradies neviens moderators. Lūdzu, uzgaidiet.",
|
||||
"WaitingForHostButton": "Gaidīt rīkotāju",
|
||||
"WaitingForHostTitle": "Gaida rīkotāju…",
|
||||
@@ -417,7 +427,7 @@
|
||||
"muteParticipantsVideoDialog": "Vai tiešām vēlaties izslēgt šī dalībnieka kameru? Jūs nevarēsiet to ieslēgt atpakaļ, taču dalībnieks pats to varēs izdarīt jebkurā laikā.",
|
||||
"muteParticipantsVideoDialogModerationOn": "Vai tiešām vēlaties izslēgt šī dalībnieka kameru? Ne Jūs, ne dalībnieks nevarēsiet to ieslēgt atpakaļ.",
|
||||
"muteParticipantsVideoTitle": "Vai izslēgt šī dalībnieka video?",
|
||||
"noDropboxToken": "Nav derīga Dropbox tokena",
|
||||
"noDropboxToken": "Nav derīgas Dropbox pilnvaras",
|
||||
"password": "Parole",
|
||||
"passwordLabel": "Dalībnieks ir aizslēdzis sapulci. Lūdzu, ievadiet $t(lockRoomPassword), lai pievienotos.",
|
||||
"passwordNotSupported": "Sapulces slēgšana ar $t(lockRoomPassword) netiek atbalstīta.",
|
||||
@@ -501,7 +511,7 @@
|
||||
"stopStreamingWarning": "Tiešām vēlaties beigt tiešraidi?",
|
||||
"streamKey": "Tiešraides atslēga",
|
||||
"thankYou": "Paldies, ka izmantojāt {{appName}}!",
|
||||
"token": "tokens",
|
||||
"token": "pilnvara",
|
||||
"tokenAuthFailed": "Atvainojiet, jums nav atļauts pievienoties šim zvanam.",
|
||||
"tokenAuthFailedReason": {
|
||||
"audInvalid": "Nederīga `aud` vērtība. Tai vajadzētu būt `jitsi`.",
|
||||
@@ -517,12 +527,13 @@
|
||||
"nbfFuture": "`nbf` vērtība ir nākotnē.",
|
||||
"nbfInvalid": "Nederīga `nbf` vērtība.",
|
||||
"payloadNotFound": "Trūkst satura.",
|
||||
"tokenExpired": "Token ir beidzies."
|
||||
"tokenExpired": "Pilnvara ir beigusies."
|
||||
},
|
||||
"tokenAuthFailedTitle": "Autentifikācijas kļūda",
|
||||
"tokenAuthFailedWithReasons": "Atvainojiet, jums nav atļauts pievienoties šim zvanam. Iespējamie iemesli: {{reason}}",
|
||||
"tokenAuthUnsupported": "Token URL netiek atbalstīts.",
|
||||
"tokenAuthUnsupported": "Pilnvaras URL nav atbalstīts.",
|
||||
"transcribing": "Notiek atšifrējuma izveide",
|
||||
"unauthenticatedAccessDisabled": "Šim zvanam nepieciešama autentifikācija. Lūdzu, piesakieties, lai turpinātu.",
|
||||
"unlockRoom": "Noņemt $t(lockRoomPassword)",
|
||||
"user": "Lietotājs",
|
||||
"userIdentifier": "Lietotājvārds",
|
||||
@@ -570,10 +581,12 @@
|
||||
"downloadStarted": "Sākta faila lejuplāde",
|
||||
"dragAndDrop": "Velciet un palaidiet failus šeit, vai jebkurā ekrāna vietā",
|
||||
"fileAlreadyUploaded": "Fails jau ir augšuplādēts šajā sanāksmē.",
|
||||
"fileRemovedByOther": "Jūsu fails '{{ fileName }}' tika noņemts",
|
||||
"fileTooLargeDescription": "Lūdzu, pārliecinieties, vai faila lielums nepārsniedz {{ maxFileSize }}.",
|
||||
"fileTooLargeTitle": "Izvēlētais fails ir pārāk liels",
|
||||
"fileUploadProgress": "Faila augšuplādes gaita",
|
||||
"fileUploadedSuccessfully": "Fails veiksmīgi augšuplādēts",
|
||||
"newFileNotification": "{{ participantName }} kopīgoja '{{ fileName }}'",
|
||||
"removeFile": "Noņemt",
|
||||
"removeFileSuccess": "Fails veiksmīgi noņemts",
|
||||
"uploadFailedDescription": "Lūdzu, mēģiniet vēlreiz.",
|
||||
@@ -748,7 +761,8 @@
|
||||
"notificationTitle": "Vestibils",
|
||||
"passwordJoinButton": "Pievienoties",
|
||||
"title": "Vestibils",
|
||||
"toggleLabel": "Iespējot vestibilu"
|
||||
"toggleLabel": "Iespējot vestibilu",
|
||||
"waitForModerator": "Konference vēl nav sākusies, jo vēl nav ieradušies moderatori. Ja vēlaties kļūt par moderatoru, lūdzu, piesakieties. Pretējā gadījumā, lūdzu, uzgaidiet."
|
||||
},
|
||||
"localRecording": {
|
||||
"clientState": {
|
||||
@@ -775,7 +789,7 @@
|
||||
"participant": "Dalībnieks",
|
||||
"participantStats": "Dalībnieku statistika",
|
||||
"selectTabTitle": "🎥 Lūdzu, atveriet šo cilni ierakstīšanai",
|
||||
"sessionToken": "Sessijas tokens",
|
||||
"sessionToken": "Sesijas Pilnvara",
|
||||
"start": "Sākt ierakstu",
|
||||
"stop": "Beigt ierakstu",
|
||||
"stopping": "Ierakstīšanas pārtraukšana",
|
||||
@@ -865,6 +879,7 @@
|
||||
"oldElectronClientDescription1": "Izskatās, ka jūs izmantojat vecu Jitsi Meet klienta versiju, kurai ir zināmas drošības ievainojamības. Lūdzu, atjauniniet uz ",
|
||||
"oldElectronClientDescription2": "jaunākā versija",
|
||||
"oldElectronClientDescription3": "tagad!",
|
||||
"openChat": "Atvērt tērzētavu",
|
||||
"participantWantsToJoin": "Vēlas pievienoties sapulcei",
|
||||
"participantsWantToJoin": "Vēlas pievienoties sapulcei",
|
||||
"passwordRemovedRemotely": "Kāds dalībnieks noņēma $t(lockRoomPasswordUppercase).",
|
||||
@@ -963,6 +978,9 @@
|
||||
"by": "Pēc {{ name }} iniciatīvas",
|
||||
"closeButton": "Slēgt aptauju",
|
||||
"create": {
|
||||
"accessibilityLabel": {
|
||||
"send": "Nosūtīt aptauju"
|
||||
},
|
||||
"addOption": "Pievienot opciju",
|
||||
"answerPlaceholder": "Opcija {{index}}",
|
||||
"cancel": "Atcelt",
|
||||
@@ -971,8 +989,7 @@
|
||||
"pollQuestion": "Aptaujas Jautājums",
|
||||
"questionPlaceholder": "Uzdod jautājumu",
|
||||
"removeOption": "Noņemt opciju",
|
||||
"save": "Saglabāt",
|
||||
"send": "Nosūtīt"
|
||||
"save": "Saglabāt"
|
||||
},
|
||||
"errors": {
|
||||
"notUniqueOption": "Iespējām jābūt unikālām"
|
||||
@@ -1091,7 +1108,7 @@
|
||||
}
|
||||
},
|
||||
"recording": {
|
||||
"authDropboxText": "Augšupielādēt uz Dropbox",
|
||||
"authDropboxText": "Augšuplādēt uz Dropbox",
|
||||
"availableSpace": "Pieejama vieta: {{spaceLeft}} MB (apmēram {{duration}} ieraksta minūtes)",
|
||||
"beta": "BETA",
|
||||
"busy": "Cenšamies nodrošināt ierakstam vairāk resursu. Lūdzu, pēc dažām minūtēm pamēģiniet vēlreiz.",
|
||||
@@ -1145,7 +1162,7 @@
|
||||
"title": "Ieraksts",
|
||||
"unavailable": "Hmm! {{serviceName}} pašlaik nav pieejams. Mēs strādājam pie problēmas risināšanas. Lūdzu, pamēģiniet vēlreiz vēlāk.",
|
||||
"unavailableTitle": "Ieraksts nav iespējams",
|
||||
"uploadToCloud": "Augšupielādēt mākonī"
|
||||
"uploadToCloud": "Augšuplādēt mākonī"
|
||||
},
|
||||
"screenshareDisplayName": "{{name}} ekrāns",
|
||||
"sectionList": {
|
||||
@@ -1300,7 +1317,7 @@
|
||||
"closeChat": "Aizvērt tērzēšanu",
|
||||
"closeMoreActions": "Aizvērt vairāk darbību izvēlni",
|
||||
"closeParticipantsPane": "Aizvērt dalībnieku paneli",
|
||||
"closedCaptions": "Slēptie subtitri",
|
||||
"closedCaptions": "Slēgtie subtitri",
|
||||
"collapse": "Sakļaut",
|
||||
"document": "Kopīgotais dokuments (iesl./izsl.)",
|
||||
"documentClose": "Aizvērt kopīgoto dokumentu",
|
||||
@@ -1378,7 +1395,21 @@
|
||||
"videomuteGUMPending": "Kameras pievienošana",
|
||||
"videounmute": "Ieslēgt kameru"
|
||||
},
|
||||
"addPeople": "Pievienot cilvēkus savai sesijai/zvanam",
|
||||
"addPeople": "Pievienot cilvēkus savam zvanam",
|
||||
"advancedAudioSettings": {
|
||||
"aec": {
|
||||
"label": "Akustiskās atbalss slāpēšana"
|
||||
},
|
||||
"agc": {
|
||||
"label": "Automātiska pastiprinājuma kontrole"
|
||||
},
|
||||
"ns": {
|
||||
"label": "Trokšņu slāpēšana"
|
||||
},
|
||||
"stereo": {
|
||||
"label": "Stereo"
|
||||
}
|
||||
},
|
||||
"audioOnlyOff": "Atspējot kanāla/trafika taupības režīmu",
|
||||
"audioOnlyOn": "Iespējot kanāla/trafika taupības režīmu",
|
||||
"audioRoute": "Izvēlēties audioierīci",
|
||||
@@ -1391,7 +1422,7 @@
|
||||
"closeChat": "Aizvērt tērzētavu",
|
||||
"closeParticipantsPane": "Aizvērt dalībnieku paneli",
|
||||
"closeReactionsMenu": "Aizvērt reakciju izvēlni",
|
||||
"closedCaptions": "Slēptie subtitri",
|
||||
"closedCaptions": "Slēgtie subtitri",
|
||||
"disableNoiseSuppression": "Atspējot trokšņu slāpēšanu",
|
||||
"disableReactionSounds": "Šai sapulcei varat atspējot reakcijas skaņas",
|
||||
"documentClose": "Aizvērt kopīgoto dokumentu",
|
||||
@@ -1406,6 +1437,7 @@
|
||||
"exitFullScreen": "Pilnekrāna režīms",
|
||||
"exitTileView": "Tuvplāna režīms",
|
||||
"feedback": "Atstāts atsauksmi",
|
||||
"fileSharing": "Failu kopīgošana",
|
||||
"giphy": "GIPHY izvēlne (rādīt/nerādīt)",
|
||||
"hangup": "Iziet no sapulces",
|
||||
"help": "Palīdzība",
|
||||
@@ -1441,17 +1473,19 @@
|
||||
"openReactionsMenu": "Atvērt reakciju izvēlni",
|
||||
"participants": "Dalībnieki",
|
||||
"pip": "Iesl. attēls attēlā (PIP) režīmu",
|
||||
"polls": "Aptaujas",
|
||||
"privateMessage": "Nosūtīt privātu ziņu",
|
||||
"profile": "Rediģēt profilu",
|
||||
"raiseHand": "Pacelt roku",
|
||||
"raiseYourHand": "Pacelt roku",
|
||||
"reactionBoo": "Nosūtīt būū reakciju",
|
||||
"reactionClap": "Nosūtīt aplausu reakciju",
|
||||
"reactionHeart": "Nosūtīt sirds reakciju",
|
||||
"reactionLaugh": "Nosūtīt smieklu reakciju",
|
||||
"reactionLike": "Nosūtīt īkšķi augšup reakciju",
|
||||
"reactionSilence": "Nosūtīt klusuma reakciju",
|
||||
"reactionSurprised": "Nosūtīt pārsteigts reakciju",
|
||||
"reactionBoo": "Sūtīt būū reakciju",
|
||||
"reactionClap": "Sūtīt aplausu reakciju",
|
||||
"reactionHeart": "Sūtīt sirds reakciju",
|
||||
"reactionLaugh": "Sūtīt smieklu reakciju",
|
||||
"reactionLike": "Sūtīt īkšķis augšup reakciju",
|
||||
"reactionLove": "Sūtīt mīlestības reakciju",
|
||||
"reactionSilence": "Sūtīt klusuma reakciju",
|
||||
"reactionSurprised": "Sūtīt pārsteiguma reakciju",
|
||||
"reactions": "Reakcijas",
|
||||
"security": "Drošības iespējas",
|
||||
"selectBackground": "Izvēlēties fonu",
|
||||
@@ -1484,7 +1518,7 @@
|
||||
"failed": "Atšifrējuma izveide neizdevās",
|
||||
"labelTooltip": "Šajā sapulcē notiek atšifrējuma izveide.",
|
||||
"labelTooltipExtra": "Turklāt vēlāk būs pieejams atšifrējums.",
|
||||
"openClosedCaptions": "Atvērt slēptos subtitrus",
|
||||
"openClosedCaptions": "Atvērt slēgtos subtitrus",
|
||||
"original": "Oriģināls",
|
||||
"sourceLanguageDesc": "Pašlaik sapulces valoda ir iestatīta uz <b>{{sourceLanguage}}</b>. <br/> Varat to mainīt no ",
|
||||
"sourceLanguageHere": "šeit",
|
||||
@@ -1582,7 +1616,7 @@
|
||||
"removeBackground": "Noņemt fonu",
|
||||
"slightBlur": "Viegli izplūdis",
|
||||
"title": "Virtuālie foni",
|
||||
"uploadedImage": "Augšupielādēts attēls {{index}}",
|
||||
"uploadedImage": "Augšuplādēts attēls {{index}}",
|
||||
"webAssemblyWarning": "WebAssembly netiek atbalstīts",
|
||||
"webAssemblyWarningDescription": "WebAssemb ir atspējots vai šī pārlūkprogramma to neatbalsta"
|
||||
},
|
||||
@@ -1601,6 +1635,8 @@
|
||||
"noMainParticipantsTitle": "Šī sapulce vēl nav sākusies.",
|
||||
"noVisitorLobby": "Jūs nevarat pievienoties, kamēr sapulcei ir iespējots vestibils.",
|
||||
"notAllowedPromotion": "Dalībniekam vispirms ir jāatļauj jūsu pieprasījums.",
|
||||
"requestToJoin": "Roka Pacelta",
|
||||
"requestToJoinDescription": "Jūsu pieprasījums tika nosūtīts moderatoriem. Uzgaidiet!",
|
||||
"title": "Jūs esat sapulces apmeklētājs"
|
||||
},
|
||||
"waitingMessage": "Jūs pievienosities sapulcei, tiklīdz tā sāksies!"
|
||||
|
||||
@@ -340,6 +340,7 @@ function initCommands() {
|
||||
|
||||
APP.store.dispatch(setAssumedBandwidthBps(value));
|
||||
},
|
||||
|
||||
'set-blurred-background': blurType => {
|
||||
const tracks = APP.store.getState()['features/base/tracks'];
|
||||
const videoTrack = getLocalVideoTrack(tracks)?.jitsiTrack;
|
||||
|
||||
@@ -158,11 +158,10 @@ const VideoLayout = {
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const state = APP.store.getState();
|
||||
const currentContainer = largeVideo.getCurrentContainer();
|
||||
const currentContainerType = largeVideo.getCurrentContainerType();
|
||||
const isOnLarge = this.isCurrentlyOnLarge(id);
|
||||
const state = APP.store.getState();
|
||||
const participant = getParticipantById(state, id);
|
||||
const videoTrack = getVideoTrackByParticipant(state, participant);
|
||||
const videoStream = videoTrack?.jitsiTrack;
|
||||
|
||||
1367
package-lock.json
generated
1367
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -28,7 +28,7 @@
|
||||
"@jitsi/js-utils": "2.6.7",
|
||||
"@jitsi/logger": "2.1.1",
|
||||
"@jitsi/rnnoise-wasm": "0.2.1",
|
||||
"@matrix-org/olm": "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.3.tgz",
|
||||
"@matrix-org/olm": "3.2.15",
|
||||
"@microsoft/microsoft-graph-client": "3.0.1",
|
||||
"@mui/material": "5.12.1",
|
||||
"@react-native-async-storage/async-storage": "1.23.1",
|
||||
@@ -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/v2109.0.0+cb9d000c/lib-jitsi-meet.tgz",
|
||||
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v2115.0.0+cc2f34c2/lib-jitsi-meet.tgz",
|
||||
"lodash-es": "4.17.21",
|
||||
"null-loader": "4.0.1",
|
||||
"optional-require": "1.0.3",
|
||||
@@ -136,7 +136,7 @@
|
||||
"@babel/preset-env": "7.25.9",
|
||||
"@babel/preset-react": "7.25.9",
|
||||
"@jitsi/eslint-config": "6.0.4",
|
||||
"@react-native-community/cli": "17.0.1",
|
||||
"@react-native-community/cli": "15.0.1",
|
||||
"@react-native-community/cli-platform-android": "15.0.1",
|
||||
"@react-native-community/cli-platform-ios": "15.0.1",
|
||||
"@react-native/babel-preset": "0.77.2",
|
||||
|
||||
@@ -618,6 +618,10 @@ export interface IConfig {
|
||||
toolbarConfig?: {
|
||||
alwaysVisible?: boolean;
|
||||
autoHideWhileChatIsOpen?: boolean;
|
||||
/**
|
||||
* Background color for the main toolbar. Accepts any valid CSS color.
|
||||
*/
|
||||
backgroundColor?: string;
|
||||
initialTimeout?: number;
|
||||
timeout?: number;
|
||||
};
|
||||
|
||||
@@ -3,13 +3,14 @@ import { jitsiLocalStorage } from '@jitsi/js-utils/jitsi-local-storage';
|
||||
// eslint-disable-next-line lines-around-comment
|
||||
// @ts-ignore
|
||||
import { safeJsonParse } from '@jitsi/js-utils/json';
|
||||
import { pick } from 'lodash-es';
|
||||
|
||||
import { browser } from '../lib-jitsi-meet';
|
||||
import { isEmbedded } from '../util/embedUtils';
|
||||
import { parseURLParams } from '../util/parseURLParams';
|
||||
|
||||
import logger from './logger';
|
||||
|
||||
import WHITELIST from './whitelist';
|
||||
|
||||
/**
|
||||
* Handles changes of the fake local storage.
|
||||
@@ -61,7 +62,7 @@ function setupJitsiLocalStorage() {
|
||||
|
||||
if (shouldUseHostPageLocalStorage(urlParams)) {
|
||||
try {
|
||||
const localStorageContent = safeJsonParse(urlParams['appData.localStorageContent']);
|
||||
let localStorageContent = safeJsonParse(urlParams['appData.localStorageContent']);
|
||||
|
||||
// We need to disable the local storage before setting the data in case the browser local storage doesn't
|
||||
// throw exception (in some cases when this happens the local storage may be cleared for every session.
|
||||
@@ -71,6 +72,10 @@ function setupJitsiLocalStorage() {
|
||||
jitsiLocalStorage.setLocalStorageDisabled(true);
|
||||
|
||||
if (typeof localStorageContent === 'object') {
|
||||
if (!isEmbedded()) {
|
||||
localStorageContent = pick(localStorageContent, WHITELIST);
|
||||
}
|
||||
|
||||
Object.keys(localStorageContent).forEach(key => {
|
||||
jitsiLocalStorage.setItem(key, localStorageContent[key]);
|
||||
});
|
||||
|
||||
11
react/features/base/jitsi-local-storage/whitelist.ts
Normal file
11
react/features/base/jitsi-local-storage/whitelist.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
/**
|
||||
* Keys of localStorage that are used by jibri.
|
||||
*/
|
||||
export default [
|
||||
'callStatsUserName',
|
||||
'displayname',
|
||||
'email',
|
||||
'xmpp_username_override',
|
||||
'xmpp_password_override',
|
||||
'xmpp_conference_password_override'
|
||||
];
|
||||
@@ -1,5 +1,7 @@
|
||||
import { IReduxState, IStore } from '../../app/types';
|
||||
import { isTrackStreamingStatusActive } from '../../connection-indicator/functions';
|
||||
import { handleToggleVideoMuted } from '../../toolbox/actions.any';
|
||||
import { muteLocal } from '../../video-menu/actions.any';
|
||||
import { MEDIA_TYPE, VIDEO_TYPE } from '../media/constants';
|
||||
import { getParticipantById, isScreenShareParticipant } from '../participants/functions';
|
||||
import {
|
||||
@@ -78,3 +80,43 @@ export function isRemoteVideoReceived({ getState }: IStore, id: string): boolean
|
||||
|
||||
return Boolean(videoTrack && !videoTrack.muted && isTrackStreamingStatusActive(videoTrack));
|
||||
}
|
||||
|
||||
/**
|
||||
* Mutes the local audio. Same as clicking the audio mute button.
|
||||
*
|
||||
* @param {IStore} store - The redux store.
|
||||
* @returns {Promise} Resolves when the action is complete.
|
||||
*/
|
||||
export function audioMute({ dispatch }: IStore) {
|
||||
return dispatch(muteLocal(true, MEDIA_TYPE.AUDIO));
|
||||
}
|
||||
|
||||
/**
|
||||
* Unmutes the local audio. Same as clicking the audio unmute button.
|
||||
*
|
||||
* @param {IStore} store - The redux store.
|
||||
* @returns {Promise} Resolves when the action is complete.
|
||||
*/
|
||||
export function audioUnmute({ dispatch }: IStore) {
|
||||
return dispatch(muteLocal(false, MEDIA_TYPE.AUDIO));
|
||||
}
|
||||
|
||||
/**
|
||||
* Mutes the local video. Same as clicking the video mute button.
|
||||
*
|
||||
* @param {IStore} store - The redux store.
|
||||
* @returns {Promise} Resolves when the action is complete.
|
||||
*/
|
||||
export function videoMute({ dispatch }: IStore) {
|
||||
return dispatch(handleToggleVideoMuted(true, true, true));
|
||||
}
|
||||
|
||||
/**
|
||||
* Unmutes the local video. Same as clicking the video unmute button.
|
||||
*
|
||||
* @param {IStore} store - The redux store.
|
||||
* @returns {Promise} Resolves when the action is complete.
|
||||
*/
|
||||
export function videoUnmute({ dispatch }: IStore) {
|
||||
return dispatch(handleToggleVideoMuted(false, true, true));
|
||||
}
|
||||
|
||||
@@ -8,11 +8,15 @@ import { getJitsiMeetGlobalNS } from '../util/helpers';
|
||||
|
||||
import { setConnectionState } from './actions';
|
||||
import {
|
||||
audioMute,
|
||||
audioUnmute,
|
||||
getLocalCameraEncoding,
|
||||
getRemoteVideoType,
|
||||
isLargeVideoReceived,
|
||||
isRemoteVideoReceived,
|
||||
isTestModeEnabled
|
||||
isTestModeEnabled,
|
||||
videoMute,
|
||||
videoUnmute
|
||||
} from './functions';
|
||||
import logger from './logger';
|
||||
|
||||
@@ -85,10 +89,14 @@ function _bindTortureHelpers(store: IStore) {
|
||||
|
||||
// All torture helper methods go in here
|
||||
getJitsiMeetGlobalNS().testing = {
|
||||
audioMute: audioMute.bind(null, store),
|
||||
audioUnmute: audioUnmute.bind(null, store),
|
||||
getRemoteVideoType: getRemoteVideoType.bind(null, store),
|
||||
isLargeVideoReceived: isLargeVideoReceived.bind(null, store),
|
||||
getLocalCameraEncoding: getLocalCameraEncoding.bind(null, store),
|
||||
isRemoteVideoReceived: isRemoteVideoReceived.bind(null, store)
|
||||
isRemoteVideoReceived: isRemoteVideoReceived.bind(null, store),
|
||||
videoMute: videoMute.bind(null, store),
|
||||
videoUnmute: videoUnmute.bind(null, store),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import { setScreenshareMuted } from '../media/actions';
|
||||
|
||||
import { addLocalTrack, replaceLocalTrack } from './actions.any';
|
||||
import { getLocalDesktopTrack, getTrackState } from './functions.native';
|
||||
import logger from './logger';
|
||||
|
||||
|
||||
export * from './actions.any';
|
||||
@@ -63,6 +64,6 @@ async function _startScreenSharing(dispatch: IStore['dispatch'], state: IReduxSt
|
||||
}, NOTIFICATION_TIMEOUT_TYPE.LONG));
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.log('ERROR creating screen-sharing stream ', error);
|
||||
logger.error('Error creating screen-sharing stream', error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -298,8 +298,6 @@ const Chat = ({
|
||||
|
||||
// Disable text selection during resize
|
||||
document.body.style.userSelect = 'none';
|
||||
|
||||
console.log('Chat resize: Mouse down', { clientX: e.clientX, initialWidth: _width });
|
||||
}, [ _width, dispatch ]);
|
||||
|
||||
/**
|
||||
@@ -315,8 +313,6 @@ const Chat = ({
|
||||
// Restore cursor and text selection
|
||||
document.body.style.cursor = '';
|
||||
document.body.style.userSelect = '';
|
||||
|
||||
console.log('Chat resize: Mouse up');
|
||||
}
|
||||
}, [ isMouseDown, dispatch ]);
|
||||
|
||||
@@ -327,7 +323,6 @@ const Chat = ({
|
||||
* @returns {void}
|
||||
*/
|
||||
const onChatResize = useCallback(throttle((e: MouseEvent) => {
|
||||
// console.log('Chat resize: Mouse move', { clientX: e.clientX, isMouseDown, mousePosition, _width });
|
||||
if (isMouseDown && mousePosition !== null && dragChatWidth !== null) {
|
||||
// For chat panel resizing on the left edge:
|
||||
// - Dragging left (decreasing X coordinate) should make the panel wider
|
||||
|
||||
@@ -12,6 +12,7 @@ import Button from '../../../base/ui/components/web/Button';
|
||||
import { BUTTON_TYPES } from '../../../base/ui/constants.any';
|
||||
import { copyText } from '../../../base/util/copyText.web';
|
||||
import { handleLobbyChatInitialized, openChat } from '../../actions.web';
|
||||
import logger from '../../logger';
|
||||
|
||||
export interface IProps {
|
||||
className?: string;
|
||||
@@ -125,11 +126,11 @@ const MessageMenu = ({ message, participantId, isFromVisitor, isLobbyMessage, en
|
||||
setShowCopiedMessage(false);
|
||||
}, 2000);
|
||||
} else {
|
||||
console.error('Failed to copy text');
|
||||
logger.error('Failed to copy text');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error copying text:', error);
|
||||
.catch((error: Error) => {
|
||||
logger.error('Error copying text', error);
|
||||
});
|
||||
handleClose();
|
||||
}, [ message ]);
|
||||
|
||||
3
react/features/chat/logger.ts
Normal file
3
react/features/chat/logger.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import { getLogger } from '../base/logging/functions';
|
||||
|
||||
export default getLogger('app:chat');
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
getVirtualScreenshareParticipantByOwnerId
|
||||
} from '../base/participants/functions';
|
||||
import { toState } from '../base/redux/functions';
|
||||
import { isStageFilmstripAvailable } from '../filmstrip/functions';
|
||||
import { getAutoPinSetting } from '../video-layout/functions';
|
||||
|
||||
import {
|
||||
@@ -18,7 +19,6 @@ import {
|
||||
SET_LARGE_VIDEO_DIMENSIONS,
|
||||
UPDATE_KNOWN_LARGE_VIDEO_RESOLUTION
|
||||
} from './actionTypes';
|
||||
import { shouldHideLargeVideo } from './functions';
|
||||
|
||||
/**
|
||||
* Action to select the participant to be displayed in LargeVideo based on the
|
||||
@@ -34,8 +34,12 @@ export function selectParticipantInLargeVideo(participant?: string) {
|
||||
return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
|
||||
const state = getState();
|
||||
|
||||
// Skip large video updates when the large video container is hidden.
|
||||
if (shouldHideLargeVideo(state)) {
|
||||
if (isStageFilmstripAvailable(state, 2)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Keep Etherpad open.
|
||||
if (state['features/etherpad'].editing) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import { IReduxState } from '../app/types';
|
||||
import { getParticipantById } from '../base/participants/functions';
|
||||
import { isStageFilmstripAvailable } from '../filmstrip/functions';
|
||||
import { shouldDisplayTileView } from '../video-layout/functions.any';
|
||||
|
||||
/**
|
||||
* Selector for the participant currently displaying on the large video.
|
||||
@@ -14,17 +12,3 @@ export function getLargeVideoParticipant(state: IReduxState) {
|
||||
|
||||
return getParticipantById(state, participantId ?? '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the large video container should be hidden.
|
||||
* Large video is hidden in tile view, stage filmstrip mode (with multiple participants),
|
||||
* or when editing etherpad.
|
||||
*
|
||||
* @param {IReduxState} state - The Redux state.
|
||||
* @returns {boolean} True if large video should be hidden, false otherwise.
|
||||
*/
|
||||
export function shouldHideLargeVideo(state: IReduxState): boolean {
|
||||
return shouldDisplayTileView(state)
|
||||
|| isStageFilmstripAvailable(state, 2)
|
||||
|| Boolean(state['features/etherpad']?.editing);
|
||||
}
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
import StateListenerRegistry from '../base/redux/StateListenerRegistry';
|
||||
|
||||
import { SELECT_LARGE_VIDEO_PARTICIPANT } from './actionTypes';
|
||||
import { selectParticipantInLargeVideo } from './actions.any';
|
||||
import { shouldHideLargeVideo } from './functions';
|
||||
|
||||
/**
|
||||
* Updates the large video when transitioning from a hidden state to visible state.
|
||||
* This ensures the large video is properly updated when exiting tile view, stage filmstrip,
|
||||
* whiteboard, or etherpad editing modes.
|
||||
*/
|
||||
StateListenerRegistry.register(
|
||||
/* selector */ state => shouldHideLargeVideo(state),
|
||||
/* listener */ (isHidden, { dispatch }) => {
|
||||
// When transitioning from hidden to visible state, select participant (because currently it is undefined).
|
||||
// Otherwise set it to undefined because we don't show the large video.
|
||||
if (!isHidden) {
|
||||
dispatch(selectParticipantInLargeVideo());
|
||||
} else {
|
||||
dispatch({
|
||||
type: SELECT_LARGE_VIDEO_PARTICIPANT,
|
||||
participantId: undefined
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
@@ -1 +0,0 @@
|
||||
import './subscriber.any';
|
||||
|
||||
@@ -4,7 +4,6 @@ import StateListenerRegistry from '../base/redux/StateListenerRegistry';
|
||||
import { getVideoTrackByParticipant } from '../base/tracks/functions.web';
|
||||
|
||||
import { getLargeVideoParticipant } from './functions';
|
||||
import './subscriber.any';
|
||||
|
||||
/**
|
||||
* Updates the on stage participant video.
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
RESET_UNREAD_POLLS_COUNT,
|
||||
SAVE_POLL
|
||||
} from './actionTypes';
|
||||
import logger from './logger';
|
||||
import { IIncomingAnswerData, IPollData } from './types';
|
||||
|
||||
const INITIAL_STATE = {
|
||||
@@ -87,7 +88,7 @@ ReducerRegistry.register<IPollsState>(STORE_NAME, (state = INITIAL_STATE, action
|
||||
|
||||
// if the poll doesn't exist
|
||||
if (!(pollId in state.polls)) {
|
||||
console.warn('requested poll does not exist: pollId ', pollId);
|
||||
logger.warn('Requested poll does not exist', { pollId });
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
@@ -213,13 +213,16 @@ const LocalRecordingManager: ILocalRecordingManager = {
|
||||
});
|
||||
|
||||
const gdmVideoTrack = gdmStream.getVideoTracks()[0];
|
||||
const isBrowser = gdmVideoTrack.getSettings().displaySurface === 'browser';
|
||||
const matchesHandle = (supportsCaptureHandle // @ts-ignore
|
||||
&& gdmVideoTrack.getCaptureHandle()?.handle === `JitsiMeet-${tabId}`);
|
||||
|
||||
if (!isBrowser || !matchesHandle) {
|
||||
gdmStream.getTracks().forEach((track: MediaStreamTrack) => track.stop());
|
||||
throw new Error('WrongSurfaceSelected');
|
||||
if (supportsCaptureHandle) {
|
||||
const isBrowser = gdmVideoTrack.getSettings().displaySurface === 'browser';
|
||||
const matchesHandle = (supportsCaptureHandle // @ts-ignore
|
||||
&& gdmVideoTrack.getCaptureHandle()?.handle === `JitsiMeet-${tabId}`);
|
||||
|
||||
if (!isBrowser || !matchesHandle) {
|
||||
gdmStream.getTracks().forEach((track: MediaStreamTrack) => track.stop());
|
||||
throw new Error('WrongSurfaceSelected');
|
||||
}
|
||||
}
|
||||
|
||||
this.initializeAudioMixer();
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { AnyAction } from 'redux';
|
||||
|
||||
import { IStore } from '../app/types';
|
||||
import { ENDPOINT_MESSAGE_RECEIVED } from '../base/conference/actionTypes';
|
||||
import { ENDPOINT_MESSAGE_RECEIVED, NON_PARTICIPANT_MESSAGE_RECEIVED } from '../base/conference/actionTypes';
|
||||
import { MEET_FEATURES } from '../base/jwt/constants';
|
||||
import { isJwtFeatureEnabled } from '../base/jwt/functions';
|
||||
import JitsiMeetJS from '../base/lib-jitsi-meet';
|
||||
@@ -76,6 +76,7 @@ const STABLE_TRANSCRIPTION_FACTOR = 0.85;
|
||||
MiddlewareRegistry.register(store => next => action => {
|
||||
switch (action.type) {
|
||||
case ENDPOINT_MESSAGE_RECEIVED:
|
||||
case NON_PARTICIPANT_MESSAGE_RECEIVED:
|
||||
return _endpointMessageReceived(store, next, action);
|
||||
|
||||
case TOGGLE_REQUESTING_SUBTITLES: {
|
||||
|
||||
@@ -60,6 +60,7 @@ function Toolbox(props: IProps) {
|
||||
|
||||
const { clientWidth } = useSelector((state: IReduxState) => state['features/base/responsive-ui']);
|
||||
const { customToolbarButtons } = useSelector((state: IReduxState) => state['features/base/config']);
|
||||
const toolbarBackgroundColor = useSelector((state: IReduxState) => state['features/base/config'].toolbarConfig?.backgroundColor);
|
||||
const {
|
||||
mainToolbarButtonsThresholds,
|
||||
toolbarButtons
|
||||
@@ -79,6 +80,11 @@ function Toolbox(props: IProps) {
|
||||
const { buttonStylesBorderless, hangupButtonStyles } = _styles;
|
||||
const style = { ...styles.toolbox };
|
||||
|
||||
// Allow overriding the toolbox background color from config (configOverwrite/overwriteConfig).
|
||||
if (toolbarBackgroundColor) {
|
||||
style.backgroundColor = toolbarBackgroundColor as any;
|
||||
}
|
||||
|
||||
// We have only hangup and raisehand button in _iAmVisitor mode
|
||||
if (_iAmVisitor) {
|
||||
style.justifyContent = 'center';
|
||||
|
||||
@@ -37,6 +37,11 @@ import Separator from './Separator';
|
||||
*/
|
||||
interface IProps {
|
||||
|
||||
/**
|
||||
* Optional toolbar background color passed as a prop.
|
||||
*/
|
||||
toolbarBackgroundColor?: string;
|
||||
|
||||
/**
|
||||
* Explicitly passed array with the buttons which this Toolbox should display.
|
||||
*/
|
||||
@@ -65,7 +70,8 @@ const useStyles = makeStyles()(() => {
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
export default function Toolbox({
|
||||
toolbarButtons
|
||||
toolbarButtons,
|
||||
toolbarBackgroundColor: toolbarBackgroundColorProp
|
||||
}: IProps) {
|
||||
const { classes, cx } = useStyles();
|
||||
const { t } = useTranslation();
|
||||
@@ -92,7 +98,10 @@ export default function Toolbox({
|
||||
const localParticipant = useSelector(getLocalParticipant);
|
||||
const transcribing = useSelector(isTranscribing);
|
||||
const _isCCTabEnabled = useSelector(isCCTabEnabled);
|
||||
|
||||
// Read toolbar background color from config (if provided) or from props.
|
||||
const toolbarBackgroundColorFromConfig = useSelector((state: IReduxState) =>
|
||||
state['features/base/config'].toolbarConfig?.backgroundColor);
|
||||
const toolbarBackgroundColor = toolbarBackgroundColorProp || toolbarBackgroundColorFromConfig;
|
||||
// Do not convert to selector, it returns new array and will cause re-rendering of toolbox on every action.
|
||||
const jwtDisabledButtons = getJwtDisabledButtons(transcribing, _isCCTabEnabled, localParticipant?.features);
|
||||
|
||||
@@ -242,7 +251,8 @@ export default function Toolbox({
|
||||
return (
|
||||
<div
|
||||
className = { cx(rootClassNames, shiftUp && 'shift-up') }
|
||||
id = 'new-toolbox'>
|
||||
id = 'new-toolbox'
|
||||
style = { toolbarBackgroundColor ? { backgroundColor: toolbarBackgroundColor } : undefined }>
|
||||
<div className = { containerClassName }>
|
||||
<div
|
||||
className = 'toolbox-content-wrapper'
|
||||
|
||||
@@ -153,7 +153,7 @@ end
|
||||
module:hook('message/full', on_message); -- private messages
|
||||
module:hook('message/bare', on_message); -- room messages
|
||||
|
||||
module:hook('muc-room-destroyed', room_destroyed, -1);
|
||||
module:hook('muc-room-destroyed', room_destroyed, 1); -- prosody handles it at 0
|
||||
module:hook("muc-occupant-left", function(event)
|
||||
local occupant, room = event.occupant, event.room;
|
||||
local session = event.origin;
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
-- muc_room_locking = false
|
||||
-- muc_room_default_public_jids = true
|
||||
--
|
||||
|
||||
module:depends('room_destroy');
|
||||
|
||||
-- we use async to detect Prosody 0.10 and earlier
|
||||
@@ -650,7 +649,7 @@ function process_main_muc_loaded(main_muc, host_module)
|
||||
module:log("info", "Hook to muc events on %s", main_muc_component_config);
|
||||
host_module:hook('muc-occupant-joined', on_occupant_joined);
|
||||
host_module:hook('muc-occupant-left', on_occupant_left);
|
||||
host_module:hook('muc-room-destroyed', on_main_room_destroyed);
|
||||
host_module:hook('muc-room-destroyed', on_main_room_destroyed, 1); -- prosody handles it at 0
|
||||
end
|
||||
|
||||
-- process or waits to process the main muc component
|
||||
|
||||
@@ -26,7 +26,8 @@ end, -100); -- make sure we are last in the chain
|
||||
module:hook('muc-occupant-left', function (event)
|
||||
local occupant, room = event.occupant, event.room;
|
||||
|
||||
if is_admin(occupant.bare_jid) or is_jibri(occupant.jid) or is_transcriber(occupant.jid) then
|
||||
if is_admin(occupant.bare_jid) or is_jibri(occupant.jid) or is_transcriber(occupant.jid)
|
||||
or room._data.breakout_rooms_active then
|
||||
return;
|
||||
end
|
||||
|
||||
@@ -54,5 +55,4 @@ module:hook('muc-room-destroyed', function (event)
|
||||
room.empty_destroy_timer:stop();
|
||||
room.empty_destroy_timer = nil;
|
||||
end
|
||||
end);
|
||||
|
||||
end, 1); -- prosody handles it at 0
|
||||
|
||||
@@ -512,7 +512,7 @@ process_host_module(main_muc_component_config, function(host_module, host)
|
||||
if room._data.lobbyroom then
|
||||
destroy_lobby_room(room, nil);
|
||||
end
|
||||
end);
|
||||
end, 1); -- prosody handles it at 0
|
||||
host_module:hook('muc-disco#info', function (event)
|
||||
local room = event.room;
|
||||
if (room._data.lobbyroom and room:get_members_only()) then
|
||||
|
||||
@@ -118,9 +118,12 @@ module:hook("muc-occupant-pre-join", function (event)
|
||||
join_rate_per_conference,
|
||||
room.join_rate_presence_queue,
|
||||
function(ev)
|
||||
-- we mark what we pass here so we can skip it on the next muc-occupant-pre-join event
|
||||
ev.stanza.delayed_join_skip = true;
|
||||
room:handle_normal_presence(ev.origin, ev.stanza);
|
||||
-- if the connection was closed while waiting in the queue, ignore
|
||||
if ev.origin.conn then
|
||||
-- we mark what we pass here so we can skip it on the next muc-occupant-pre-join event
|
||||
ev.stanza.delayed_join_skip = true;
|
||||
room:handle_normal_presence(ev.origin, ev.stanza);
|
||||
end
|
||||
end,
|
||||
function() -- empty callback
|
||||
room.join_rate_queue_timer = false;
|
||||
@@ -164,7 +167,7 @@ module:hook('muc-room-destroyed',function(event)
|
||||
if event.room.leave_rate_presence_queue then
|
||||
event.room.leave_rate_presence_queue.empty = true;
|
||||
end
|
||||
end);
|
||||
end, 1); -- prosody handles it at 0
|
||||
|
||||
module:hook('muc-occupant-pre-leave', function (event)
|
||||
local occupant, room, stanza = event.occupant, event.room, event.stanza;
|
||||
|
||||
@@ -12,6 +12,7 @@ local muc = module:depends("muc");
|
||||
|
||||
local NS_NICK = 'http://jabber.org/protocol/nick';
|
||||
local get_room_by_name_and_subdomain = util.get_room_by_name_and_subdomain;
|
||||
local get_room_from_jid = util.get_room_from_jid;
|
||||
local is_healthcheck_room = util.is_healthcheck_room;
|
||||
local room_jid_match_rewrite = util.room_jid_match_rewrite;
|
||||
local internal_room_jid_match_rewrite = util.internal_room_jid_match_rewrite;
|
||||
@@ -138,6 +139,7 @@ end
|
||||
end
|
||||
|
||||
local room;
|
||||
local occupant;
|
||||
if session.type == 's2sin' then
|
||||
if not json_message.attr.roomJid then
|
||||
module:log('warn', 'No room jid found in %s', stanza);
|
||||
@@ -145,7 +147,32 @@ end
|
||||
end
|
||||
room = get_room_from_jid(room_jid_match_rewrite(json_message.attr.roomJid));
|
||||
else
|
||||
room = get_room_by_name_and_subdomain(session.jitsi_web_query_room, session.jitsi_web_query_prefix);
|
||||
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;
|
||||
|
||||
occupant = main_room:get_occupant_by_real_jid(occupant_jid);
|
||||
|
||||
if main_room._data.breakout_rooms_active then
|
||||
-- let's find is this participant in the main room or in some breakout room
|
||||
if not occupant then
|
||||
-- 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;
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
room = main_room;
|
||||
end
|
||||
|
||||
if not occupant then
|
||||
module:log('error', 'Occupant sending poll msg %s was not found in room %s', occupant_jid, room.jid)
|
||||
return;
|
||||
end
|
||||
end
|
||||
|
||||
if not room then
|
||||
@@ -177,11 +204,6 @@ end
|
||||
local occupant_details;
|
||||
if session.type ~= 's2sin' then
|
||||
local occupant_jid = stanza.attr.from;
|
||||
occupant = room:get_occupant_by_real_jid(occupant_jid);
|
||||
if not occupant then
|
||||
module:log("error", "Occupant sending msg %s was not found in room %s", occupant_jid, room.jid)
|
||||
return;
|
||||
end
|
||||
occupant_details = get_occupant_details(occupant)
|
||||
if not occupant_details then
|
||||
module:log("error", "Cannot retrieve poll creator or voter id and name for %s from %s",
|
||||
|
||||
@@ -331,7 +331,7 @@ function process_main_muc_loaded(main_muc, host_module)
|
||||
host_module:hook("muc-room-created", room_created, -1);
|
||||
host_module:hook("muc-occupant-joined", occupant_joined, -1);
|
||||
host_module:hook("muc-occupant-pre-leave", occupant_leaving, -1);
|
||||
host_module:hook("muc-room-destroyed", room_destroyed, -1);
|
||||
host_module:hook("muc-room-destroyed", room_destroyed, 1); -- prosody handles it at 0
|
||||
end
|
||||
|
||||
function process_breakout_muc_loaded(breakout_muc, host_module)
|
||||
@@ -340,7 +340,7 @@ function process_breakout_muc_loaded(breakout_muc, host_module)
|
||||
host_module:hook("muc-room-created", breakout_room_created, -1);
|
||||
host_module:hook("muc-occupant-joined", occupant_joined, -1);
|
||||
host_module:hook("muc-occupant-pre-leave", occupant_leaving, -1);
|
||||
host_module:hook("muc-room-destroyed", room_destroyed, -1);
|
||||
host_module:hook("muc-room-destroyed", room_destroyed, 1); -- prosody handles it at 0
|
||||
end
|
||||
|
||||
-- process or waits to process the conference muc component
|
||||
|
||||
@@ -329,7 +329,7 @@ process_host_module(main_muc_component_config, function(host_module, host)
|
||||
|
||||
visitors_nodes[room.jid] = nil;
|
||||
end
|
||||
end);
|
||||
end, 1); -- prosody handles it at 0
|
||||
|
||||
-- detects new participants joining main room and sending them to the visitor nodes
|
||||
host_module:hook('muc-occupant-joined', function (event)
|
||||
|
||||
@@ -582,7 +582,7 @@ process_host_module(muc_domain_prefix..'.'..muc_domain_base, function(host_modul
|
||||
host_module:hook('muc-room-destroyed', function (event)
|
||||
visitors_promotion_map[event.room.jid] = nil;
|
||||
visitors_promotion_requests[event.room.jid] = nil;
|
||||
end);
|
||||
end, 1); -- prosody handles it at 0
|
||||
|
||||
host_module:hook('muc-occupant-joined', function (event)
|
||||
local room, occupant = event.room, event.occupant;
|
||||
@@ -754,7 +754,7 @@ function handle_occupant_leaving_breakout(event)
|
||||
local main_room, occupant, stanza = event.main_room, event.occupant, event.stanza;
|
||||
local presence_status = stanza:get_child_text('status');
|
||||
|
||||
if presence_status ~= 'switch_room' or not visitors_promotion_map[main_room.jid] then
|
||||
if presence_status ~= 'switch_room' or not main_room or not visitors_promotion_map[main_room.jid] then
|
||||
return;
|
||||
end
|
||||
|
||||
|
||||
134
resources/prosody-plugins/token/jwk.lib.lua
Normal file
134
resources/prosody-plugins/token/jwk.lib.lua
Normal file
@@ -0,0 +1,134 @@
|
||||
local basexx = require "basexx";
|
||||
|
||||
local M = {}
|
||||
|
||||
-- Helper function to encode bytes to base64
|
||||
function base64_encode(bytes)
|
||||
return basexx.to_base64(bytes)
|
||||
end
|
||||
|
||||
-- Pure Lua ASN.1 DER encoder (no external dependencies)
|
||||
local ASN1 = {}
|
||||
|
||||
-- Encode ASN.1 length field
|
||||
function ASN1.encode_length(len)
|
||||
if len < 128 then
|
||||
return string.char(len)
|
||||
elseif len < 256 then
|
||||
return string.char(0x81, len)
|
||||
elseif len < 65536 then
|
||||
return string.char(0x82, math.floor(len / 256), len % 256)
|
||||
else
|
||||
local b1 = math.floor(len / 65536)
|
||||
local b2 = math.floor((len % 65536) / 256)
|
||||
local b3 = len % 256
|
||||
return string.char(0x83, b1, b2, b3)
|
||||
end
|
||||
end
|
||||
|
||||
-- Encode ASN.1 INTEGER
|
||||
function ASN1.encode_integer(bytes)
|
||||
-- ASN.1 INTEGER tag is 0x02
|
||||
-- If the high bit is set, prepend 0x00 to indicate positive number
|
||||
if bytes:byte(1) >= 0x80 then
|
||||
bytes = string.char(0x00) .. bytes
|
||||
end
|
||||
return string.char(0x02) .. ASN1.encode_length(#bytes) .. bytes
|
||||
end
|
||||
|
||||
-- Encode ASN.1 SEQUENCE
|
||||
function ASN1.encode_sequence(content)
|
||||
-- ASN.1 SEQUENCE tag is 0x30
|
||||
return string.char(0x30) .. ASN1.encode_length(#content) .. content
|
||||
end
|
||||
|
||||
-- Encode ASN.1 BIT STRING
|
||||
function ASN1.encode_bit_string(content)
|
||||
-- ASN.1 BIT STRING tag is 0x03
|
||||
-- First byte indicates number of unused bits (0x00 for byte-aligned)
|
||||
return string.char(0x03) .. ASN1.encode_length(#content + 1) .. string.char(0x00) .. content
|
||||
end
|
||||
|
||||
-- Encode ASN.1 OBJECT IDENTIFIER
|
||||
function ASN1.encode_oid(oid_bytes)
|
||||
-- ASN.1 OID tag is 0x06
|
||||
return string.char(0x06) .. ASN1.encode_length(#oid_bytes) .. oid_bytes
|
||||
end
|
||||
|
||||
-- Encode ASN.1 NULL
|
||||
function ASN1.encode_null()
|
||||
-- ASN.1 NULL tag is 0x05, length 0
|
||||
return string.char(0x05, 0x00)
|
||||
end
|
||||
|
||||
-- Convert DER to PEM format
|
||||
function ASN1.der_to_pem(der, label)
|
||||
label = label or "PUBLIC KEY"
|
||||
local base64 = base64_encode(der)
|
||||
|
||||
-- Break into 64-character lines
|
||||
local lines = {}
|
||||
for i = 1, #base64, 64 do
|
||||
table.insert(lines, base64:sub(i, i + 63))
|
||||
end
|
||||
|
||||
return "-----BEGIN " .. label .. "-----\n" ..
|
||||
table.concat(lines, "\n") .. "\n" ..
|
||||
"-----END " .. label .. "-----\n"
|
||||
end
|
||||
|
||||
-- Helper function to decode base64url
|
||||
function base64url_decode(str)
|
||||
-- Convert base64url to base64
|
||||
str = str:gsub('-', '+'):gsub('_', '/')
|
||||
-- Add padding if needed
|
||||
local padding = #str % 4
|
||||
if padding > 0 then
|
||||
str = str .. string.rep('=', 4 - padding)
|
||||
end
|
||||
return basexx.from_base64(str)
|
||||
end
|
||||
|
||||
-- Helper function to convert JWK to PEM format
|
||||
function M.jwk_to_pem(jwk)
|
||||
-- Decode the modulus (n) and exponent (e) from base64url
|
||||
local n_bytes = base64url_decode(jwk.n)
|
||||
local e_bytes = base64url_decode(jwk.e)
|
||||
|
||||
-- Build RSA public key structure
|
||||
-- RSAPublicKey ::= SEQUENCE {
|
||||
-- modulus INTEGER, -- n
|
||||
-- publicExponent INTEGER -- e
|
||||
-- }
|
||||
local modulus_asn1 = ASN1.encode_integer(n_bytes)
|
||||
local exponent_asn1 = ASN1.encode_integer(e_bytes)
|
||||
local rsa_pubkey = ASN1.encode_sequence(modulus_asn1 .. exponent_asn1)
|
||||
|
||||
-- Build SubjectPublicKeyInfo structure
|
||||
-- SubjectPublicKeyInfo ::= SEQUENCE {
|
||||
-- algorithm AlgorithmIdentifier,
|
||||
-- subjectPublicKey BIT STRING
|
||||
-- }
|
||||
|
||||
-- RSA OID: 1.2.840.113549.1.1.1 (rsaEncryption)
|
||||
-- Encoded as: 06 09 2A 86 48 86 F7 0D 01 01 01
|
||||
local rsa_oid = string.char(0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01)
|
||||
local rsa_oid_encoded = ASN1.encode_oid(rsa_oid)
|
||||
|
||||
-- AlgorithmIdentifier ::= SEQUENCE {
|
||||
-- algorithm OBJECT IDENTIFIER,
|
||||
-- parameters NULL
|
||||
-- }
|
||||
local algorithm_id = ASN1.encode_sequence(rsa_oid_encoded .. ASN1.encode_null())
|
||||
|
||||
-- Wrap the RSA public key in a BIT STRING
|
||||
local subject_public_key = ASN1.encode_bit_string(rsa_pubkey)
|
||||
|
||||
-- Final SubjectPublicKeyInfo
|
||||
local spki = ASN1.encode_sequence(algorithm_id .. subject_public_key)
|
||||
|
||||
-- Convert to PEM format
|
||||
return ASN1.der_to_pem(spki, "PUBLIC KEY")
|
||||
end
|
||||
|
||||
return M
|
||||
@@ -5,6 +5,7 @@ local basexx = require "basexx";
|
||||
local have_async, async = pcall(require, "util.async");
|
||||
local hex = require "util.hex";
|
||||
local jwt = module:require "luajwtjitsi";
|
||||
local jwk_to_pem = module:require "token/jwk".jwk_to_pem;
|
||||
local jid = require "util.jid";
|
||||
local json_safe = require "cjson.safe";
|
||||
local path = require "util.paths";
|
||||
@@ -111,14 +112,14 @@ function Util.new(module)
|
||||
return nil;
|
||||
end
|
||||
|
||||
if self.appSecret == nil and self.asapKeyServer == nil then
|
||||
module:log("error", "'app_secret' or 'asap_key_server' must be specified");
|
||||
if self.appSecret == nil and self.asapKeyServer == nil and self.cacheKeysUrl == nil then
|
||||
module:log("error", "'app_secret', 'asap_key_server or 'cacheKeysUrl' must be specified");
|
||||
return nil;
|
||||
end
|
||||
|
||||
-- Set defaults for signature algorithm
|
||||
if self.signatureAlgorithm == nil then
|
||||
if self.asapKeyServer ~= nil then
|
||||
if self.asapKeyServer ~= nil or self.cacheKeysUrl then
|
||||
self.signatureAlgorithm = "RS256"
|
||||
elseif self.appSecret ~= nil then
|
||||
self.signatureAlgorithm = "HS256"
|
||||
@@ -133,7 +134,7 @@ function Util.new(module)
|
||||
|
||||
self.requireRoomClaim = module:get_option_boolean('asap_require_room_claim', true);
|
||||
|
||||
if self.asapKeyServer and not have_async then
|
||||
if (self.asapKeyServer or self.cacheKeysUrl) and not have_async then
|
||||
module:log("error", "requires a version of Prosody with util.async");
|
||||
return nil;
|
||||
end
|
||||
@@ -148,7 +149,18 @@ function Util.new(module)
|
||||
local keys_to_delete = table_shallow_copy(self.cachedKeys);
|
||||
-- Let's convert any certificate to public key
|
||||
for k, v in pairs(cjson_safe.decode(content)) do
|
||||
if starts_with(v, '-----BEGIN CERTIFICATE-----') then
|
||||
-- JWKS format
|
||||
if k == "keys" and type(v) == "table" then
|
||||
for _, key in ipairs(v) do
|
||||
if key.kid then
|
||||
self.cachedKeys[key.kid] = jwk_to_pem(key);
|
||||
|
||||
-- do not clean this key if it already exists
|
||||
keys_to_delete[key.kid] = nil;
|
||||
end
|
||||
end
|
||||
-- direct PEM mapping (Firebase)
|
||||
elseif starts_with(v, '-----BEGIN CERTIFICATE-----') then
|
||||
self.cachedKeys[k] = ssl.loadcertificate(v):pubkey();
|
||||
-- do not clean this key if it already exists
|
||||
keys_to_delete[k] = nil;
|
||||
@@ -263,7 +275,7 @@ function Util:process_and_verify_token(session)
|
||||
-- We're using an public key stored in the session
|
||||
-- module:log("debug","Public key was found on the session");
|
||||
key = session.public_key;
|
||||
elseif self.asapKeyServer and session.auth_token ~= nil then
|
||||
elseif (self.asapKeyServer or self.cacheKeysUrl) and session.auth_token ~= nil then
|
||||
-- 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
|
||||
|
||||
@@ -12,12 +12,21 @@ const defaultExpectations = {
|
||||
* null -> if the config is enabled, assert the UI elements are displayed and the feature works.
|
||||
*/
|
||||
enabled: null,
|
||||
minPinLength: 8
|
||||
},
|
||||
iframe: {
|
||||
// Whether the iframe integration is enabled (the inverse of `disableIframeAPI` from config.js)
|
||||
enabled: true
|
||||
},
|
||||
jaas: {
|
||||
liveStreamingEnabled: true,
|
||||
recordingEnabled: true,
|
||||
transcriptionEnabled: true,
|
||||
/**
|
||||
* Whether the jaas account is configured with the account-level setting to allow unauthenticated users to join.
|
||||
*/
|
||||
unauthenticatedJoins: false
|
||||
unauthenticatedJoins: false,
|
||||
visitors: true
|
||||
},
|
||||
moderation: {
|
||||
// Everyone is a moderator.
|
||||
|
||||
@@ -124,20 +124,6 @@ export default class Filmstrip extends BasePageObject {
|
||||
return await elem.isExisting() ? await elem.getAttribute('src') : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the endpoint is dominant speaker and false otherwise.
|
||||
* Uses the dominant-speaker class on the video thumbnail in order to check.
|
||||
*
|
||||
* @param {string} endpointId - The endpoint id of the participant we want to check.
|
||||
* @returns {boolean} - True if the endpoint is dominant speaker and false otherwise.
|
||||
*/
|
||||
async isDominantSpeaker(endpointId: string) {
|
||||
const elem = this.participant.driver.$(
|
||||
`//span[@id='participant_${endpointId}' and contains(@class,'dominant-speaker')]`);
|
||||
|
||||
return await elem.isExisting();
|
||||
}
|
||||
|
||||
/**
|
||||
* Grants moderator rights to a participant.
|
||||
* @param participant
|
||||
|
||||
@@ -58,7 +58,12 @@ export default class Toolbar extends BasePageObject {
|
||||
async clickAudioMuteButton(): Promise<void> {
|
||||
await this.participant.log('Clicking on: Audio Mute Button');
|
||||
|
||||
return this.audioMuteBtn.click();
|
||||
await this.audioMuteBtn.waitForExist({
|
||||
timeout: 2000, timeoutMsg: 'Audio mute button not found'
|
||||
});
|
||||
|
||||
// not directly clicking the button to avoid issues of UI notifications preventing it
|
||||
return this.participant.execute(() => JitsiMeetJS.app.testing.audioMute());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -69,7 +74,12 @@ export default class Toolbar extends BasePageObject {
|
||||
async clickAudioUnmuteButton(): Promise<void> {
|
||||
await this.participant.log('Clicking on: Audio Unmute Button');
|
||||
|
||||
return this.audioUnMuteBtn.click();
|
||||
await this.audioUnMuteBtn.waitForExist({
|
||||
timeout: 2000, timeoutMsg: 'Audio unmute button not found'
|
||||
});
|
||||
|
||||
// not directly clicking the button to avoid issues of UI notifications preventing it
|
||||
return this.participant.execute(() => JitsiMeetJS.app.testing.audioUnmute());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -94,7 +104,12 @@ export default class Toolbar extends BasePageObject {
|
||||
async clickVideoMuteButton(): Promise<void> {
|
||||
await this.participant.log('Clicking on: Video Mute Button');
|
||||
|
||||
return this.videoMuteBtn.click();
|
||||
await this.videoMuteBtn.waitForExist({
|
||||
timeout: 2000, timeoutMsg: 'Video mute button not found'
|
||||
});
|
||||
|
||||
// not directly clicking the button to avoid issues of UI notifications preventing it
|
||||
return this.participant.execute(() => JitsiMeetJS.app.testing.videoMute());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -105,7 +120,12 @@ export default class Toolbar extends BasePageObject {
|
||||
async clickVideoUnmuteButton(): Promise<void> {
|
||||
await this.participant.log('Clicking on: Video Unmute Button');
|
||||
|
||||
return this.videoUnMuteBtn.click();
|
||||
await this.videoUnMuteBtn.waitForExist({
|
||||
timeout: 2000, timeoutMsg: 'Video unmute button not found'
|
||||
});
|
||||
|
||||
// not directly clicking the button to avoid issues of UI notifications preventing it
|
||||
return this.participant.execute(() => JitsiMeetJS.app.testing.videoUnmute());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -5,6 +5,8 @@ import { setTestProperties } from '../../helpers/TestProperties';
|
||||
import { config as testsConfig } from '../../helpers/TestsConfig';
|
||||
import { joinMuc } from '../../helpers/joinMuc';
|
||||
|
||||
import { checkIframeApi } from './util';
|
||||
|
||||
setTestProperties(__filename, {
|
||||
usesBrowsers: [ 'p1', 'p2' ]
|
||||
});
|
||||
@@ -14,13 +16,10 @@ describe('Chat', () => {
|
||||
|
||||
it('setup', async () => {
|
||||
p1 = await joinMuc({ name: 'p1', iFrameApi: true, token: testsConfig.jwt.preconfiguredToken });
|
||||
p2 = await joinMuc({ name: 'p2', iFrameApi: true });
|
||||
|
||||
if (await p1.execute(() => config.disableIframeAPI)) {
|
||||
ctx.skipSuiteTests = 'The environment has the iFrame API disabled.';
|
||||
|
||||
if (!await checkIframeApi(p1)) {
|
||||
return;
|
||||
}
|
||||
p2 = await joinMuc({ name: 'p2', iFrameApi: true });
|
||||
|
||||
await p1.switchToMainFrame();
|
||||
await p2.switchToMainFrame();
|
||||
|
||||
136
tests/specs/iframe/kick.spec.ts
Normal file
136
tests/specs/iframe/kick.spec.ts
Normal file
@@ -0,0 +1,136 @@
|
||||
import { isEqual } from 'lodash-es';
|
||||
|
||||
import { setTestProperties } from '../../helpers/TestProperties';
|
||||
import { ensureTwoParticipants } from '../../helpers/participants';
|
||||
|
||||
import { checkIframeApi } from './util';
|
||||
|
||||
setTestProperties(__filename, {
|
||||
usesBrowsers: [ 'p1', 'p2' ]
|
||||
});
|
||||
|
||||
describe('Kick participants', () => {
|
||||
it('joining the meeting', async () => {
|
||||
await ensureTwoParticipants({}, { name: 'p1', iFrameApi: true });
|
||||
|
||||
const { p1, p2 } = ctx;
|
||||
|
||||
if (!await checkIframeApi(p1)) {
|
||||
return;
|
||||
}
|
||||
|
||||
await Promise.all([
|
||||
p1.switchToMainFrame(),
|
||||
p2.switchToMainFrame()
|
||||
]);
|
||||
|
||||
expect(await p1.getIframeAPI().getEventResult('isModerator')).toBe(true);
|
||||
|
||||
expect(await p1.getIframeAPI().getEventResult('videoConferenceJoined')).toBeDefined();
|
||||
expect(await p2.getIframeAPI().getEventResult('videoConferenceJoined')).toBeDefined();
|
||||
});
|
||||
|
||||
it('kick participant', async () => {
|
||||
await ctx.p2.getIframeAPI().clearEventResults('videoConferenceLeft');
|
||||
await ctx.p2.getIframeAPI().addEventListener('videoConferenceLeft');
|
||||
await ctx.p2.switchToMainFrame();
|
||||
await ctx.p2.getIframeAPI().executeCommand('hangup');
|
||||
await ctx.p2.driver.waitUntil(() =>
|
||||
ctx.p2.getIframeAPI().getEventResult('videoConferenceLeft'), {
|
||||
timeout: 4000,
|
||||
timeoutMsg: 'videoConferenceLeft not received'
|
||||
});
|
||||
|
||||
await ensureTwoParticipants({}, { name: 'p1', iFrameApi: true });
|
||||
|
||||
const { p1, p2, roomName } = ctx;
|
||||
|
||||
const p1EpId = await p1.getEndpointId();
|
||||
const p2EpId = await p2.getEndpointId();
|
||||
|
||||
const p1DisplayName = await p1.getLocalDisplayName();
|
||||
const p2DisplayName = await p2.getLocalDisplayName();
|
||||
|
||||
await p1.switchToMainFrame();
|
||||
await p2.switchToMainFrame();
|
||||
|
||||
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);
|
||||
|
||||
const eventP1 = await p1.driver.waitUntil(() => p1.getIframeAPI().getEventResult('participantKickedOut'), {
|
||||
timeout: 2000,
|
||||
timeoutMsg: 'participantKickedOut event not received on p1 side'
|
||||
});
|
||||
const eventP2 = await p2.driver.waitUntil(() => p2.getIframeAPI().getEventResult('participantKickedOut'), {
|
||||
timeout: 2000,
|
||||
timeoutMsg: 'participantKickedOut event not received on p2 side'
|
||||
});
|
||||
|
||||
expect(eventP1).toBeDefined();
|
||||
expect(eventP2).toBeDefined();
|
||||
|
||||
expect(isEqual(eventP1, {
|
||||
kicked: {
|
||||
id: p2EpId,
|
||||
local: false,
|
||||
name: p2DisplayName
|
||||
},
|
||||
kicker: {
|
||||
id: p1EpId,
|
||||
local: true,
|
||||
name: p1DisplayName
|
||||
}
|
||||
})).toBe(true);
|
||||
|
||||
expect(isEqual(eventP2, {
|
||||
kicked: {
|
||||
id: 'local',
|
||||
local: true,
|
||||
name: p2DisplayName
|
||||
},
|
||||
kicker: {
|
||||
id: p1EpId,
|
||||
name: p1DisplayName
|
||||
}
|
||||
})).toBe(true);
|
||||
|
||||
const eventConferenceLeftP2 = await p2.driver.waitUntil(() =>
|
||||
p2.getIframeAPI().getEventResult('videoConferenceLeft'), {
|
||||
timeout: 4000,
|
||||
timeoutMsg: 'videoConferenceLeft not received'
|
||||
});
|
||||
|
||||
expect(eventConferenceLeftP2).toBeDefined();
|
||||
expect(eventConferenceLeftP2.roomName).toBe(roomName);
|
||||
});
|
||||
|
||||
it('join after kick', async () => {
|
||||
const { p1 } = ctx;
|
||||
|
||||
await p1.getIframeAPI().addEventListener('participantJoined');
|
||||
|
||||
// join again
|
||||
await ensureTwoParticipants({}, { name: 'p1', iFrameApi: true });
|
||||
const { p2 } = ctx;
|
||||
|
||||
await p1.switchToMainFrame();
|
||||
|
||||
const event = await p1.driver.waitUntil(() => p1.getIframeAPI().getEventResult('participantJoined'), {
|
||||
timeout: 2000,
|
||||
timeoutMsg: 'participantJoined not received'
|
||||
});
|
||||
|
||||
const p2DisplayName = await p2.getLocalDisplayName();
|
||||
|
||||
expect(event).toBeDefined();
|
||||
expect(event.id).toBe(await p2.getEndpointId());
|
||||
expect(event.displayName).toBe(p2DisplayName);
|
||||
expect(event.formattedDisplayName).toBe(p2DisplayName);
|
||||
|
||||
});
|
||||
});
|
||||
@@ -1,9 +1,9 @@
|
||||
import { isEqual } from 'lodash-es';
|
||||
|
||||
import { P1, P2 } from '../../helpers/Participant';
|
||||
import { setTestProperties } from '../../helpers/TestProperties';
|
||||
import { ensureTwoParticipants, parseJid } from '../../helpers/participants';
|
||||
|
||||
import { checkIframeApi } from './util';
|
||||
|
||||
setTestProperties(__filename, {
|
||||
usesBrowsers: [ 'p1', 'p2' ]
|
||||
});
|
||||
@@ -14,9 +14,7 @@ describe('Participants presence', () => {
|
||||
|
||||
const { p1, p2 } = ctx;
|
||||
|
||||
if (await p1.execute(() => config.disableIframeAPI)) {
|
||||
ctx.skipSuiteTests = 'The environment has the iFrame API disabled.';
|
||||
|
||||
if (!await checkIframeApi(p1)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -71,111 +69,6 @@ describe('Participants presence', () => {
|
||||
expect((await p1.getIframeAPI().getEventResult('participantsPaneToggled'))?.open).toBe(false);
|
||||
});
|
||||
|
||||
it('kick participant', async () => {
|
||||
await ctx.p2.getIframeAPI().clearEventResults('videoConferenceLeft');
|
||||
await ctx.p2.getIframeAPI().addEventListener('videoConferenceLeft');
|
||||
await ctx.p2.switchToMainFrame();
|
||||
await ctx.p2.getIframeAPI().executeCommand('hangup');
|
||||
await ctx.p2.driver.waitUntil(() =>
|
||||
ctx.p2.getIframeAPI().getEventResult('videoConferenceLeft'), {
|
||||
timeout: 4000,
|
||||
timeoutMsg: 'videoConferenceLeft not received'
|
||||
});
|
||||
|
||||
await ensureTwoParticipants({}, { name: 'p1', iFrameApi: true });
|
||||
|
||||
const { p1, p2, roomName } = ctx;
|
||||
|
||||
const p1EpId = await p1.getEndpointId();
|
||||
const p2EpId = await p2.getEndpointId();
|
||||
|
||||
const p1DisplayName = await p1.getLocalDisplayName();
|
||||
const p2DisplayName = await p2.getLocalDisplayName();
|
||||
|
||||
await p1.switchToMainFrame();
|
||||
await p2.switchToMainFrame();
|
||||
|
||||
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);
|
||||
|
||||
const eventP1 = await p1.driver.waitUntil(() => p1.getIframeAPI().getEventResult('participantKickedOut'), {
|
||||
timeout: 2000,
|
||||
timeoutMsg: 'participantKickedOut event not received on p1 side'
|
||||
});
|
||||
const eventP2 = await p2.driver.waitUntil(() => p2.getIframeAPI().getEventResult('participantKickedOut'), {
|
||||
timeout: 2000,
|
||||
timeoutMsg: 'participantKickedOut event not received on p2 side'
|
||||
});
|
||||
|
||||
expect(eventP1).toBeDefined();
|
||||
expect(eventP2).toBeDefined();
|
||||
|
||||
expect(isEqual(eventP1, {
|
||||
kicked: {
|
||||
id: p2EpId,
|
||||
local: false,
|
||||
name: p2DisplayName
|
||||
},
|
||||
kicker: {
|
||||
id: p1EpId,
|
||||
local: true,
|
||||
name: p1DisplayName
|
||||
}
|
||||
})).toBe(true);
|
||||
|
||||
expect(isEqual(eventP2, {
|
||||
kicked: {
|
||||
id: 'local',
|
||||
local: true,
|
||||
name: p2DisplayName
|
||||
},
|
||||
kicker: {
|
||||
id: p1EpId,
|
||||
name: p1DisplayName
|
||||
}
|
||||
})).toBe(true);
|
||||
|
||||
const eventConferenceLeftP2 = await p2.driver.waitUntil(() =>
|
||||
p2.getIframeAPI().getEventResult('videoConferenceLeft'), {
|
||||
timeout: 4000,
|
||||
timeoutMsg: 'videoConferenceLeft not received'
|
||||
});
|
||||
|
||||
expect(eventConferenceLeftP2).toBeDefined();
|
||||
expect(eventConferenceLeftP2.roomName).toBe(roomName);
|
||||
});
|
||||
|
||||
it('join after kick', async () => {
|
||||
const { p1 } = ctx;
|
||||
|
||||
await p1.getIframeAPI().addEventListener('participantJoined');
|
||||
await p1.getIframeAPI().addEventListener('participantMenuButtonClick');
|
||||
|
||||
// join again
|
||||
await ensureTwoParticipants({}, { name: 'p1', iFrameApi: true });
|
||||
const { p2 } = ctx;
|
||||
|
||||
await p1.switchToMainFrame();
|
||||
|
||||
const event = await p1.driver.waitUntil(() => p1.getIframeAPI().getEventResult('participantJoined'), {
|
||||
timeout: 2000,
|
||||
timeoutMsg: 'participantJoined not received'
|
||||
});
|
||||
|
||||
const p2DisplayName = await p2.getLocalDisplayName();
|
||||
|
||||
expect(event).toBeDefined();
|
||||
expect(event.id).toBe(await p2.getEndpointId());
|
||||
expect(event.displayName).toBe(p2DisplayName);
|
||||
expect(event.formattedDisplayName).toBe(p2DisplayName);
|
||||
|
||||
});
|
||||
|
||||
it('overwrite names', async () => {
|
||||
const { p1, p2 } = ctx;
|
||||
|
||||
|
||||
15
tests/specs/iframe/util.ts
Normal file
15
tests/specs/iframe/util.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { expect } from '@wdio/globals';
|
||||
|
||||
import type { Participant } from '../../helpers/Participant';
|
||||
import { expectations } from '../../helpers/expectations';
|
||||
|
||||
export async function checkIframeApi(p: Participant) {
|
||||
const iframeEnabled = !await p.execute(() => config.disableIframeAPI);
|
||||
|
||||
expect(iframeEnabled).toBe(expectations.iframe.enabled);
|
||||
if (!iframeEnabled) {
|
||||
ctx.skipSuiteTests = 'The iFrame API is disabled';
|
||||
}
|
||||
|
||||
return iframeEnabled;
|
||||
}
|
||||
@@ -2,6 +2,7 @@ import { Participant } from '../../helpers/Participant';
|
||||
import { setTestProperties } from '../../helpers/TestProperties';
|
||||
import { config as testsConfig } from '../../helpers/TestsConfig';
|
||||
import WebhookProxy from '../../helpers/WebhookProxy';
|
||||
import { expectations } from '../../helpers/expectations';
|
||||
import { joinJaasMuc, generateJaasToken as t } from '../../helpers/jaas';
|
||||
|
||||
setTestProperties(__filename, {
|
||||
@@ -12,16 +13,13 @@ setTestProperties(__filename, {
|
||||
/**
|
||||
* Tests the recording and live-streaming functionality of JaaS (including relevant webhooks) exercising the iFrame API
|
||||
* commands and functions.
|
||||
* TODO: read flags from config.
|
||||
* TODO: also assert "this meeting is being recorder" notificaitons are show/played?
|
||||
* TODO: also assert "this meeting is being recorded" notifications are show/played?
|
||||
*/
|
||||
describe('Recording and live-streaming', () => {
|
||||
const tenant = testsConfig.jaas.tenant;
|
||||
const customerId = tenant?.replace('vpaas-magic-cookie-', '');
|
||||
// TODO: read from config
|
||||
let recordingDisabled: boolean;
|
||||
// TODO: read from config
|
||||
let liveStreamingDisabled: boolean;
|
||||
let recordingEnabled: boolean;
|
||||
let liveStreamingEnabled: boolean;
|
||||
let p: Participant;
|
||||
let webhooksProxy: WebhookProxy;
|
||||
|
||||
@@ -29,18 +27,17 @@ describe('Recording and live-streaming', () => {
|
||||
webhooksProxy = ctx.webhooksProxy;
|
||||
p = await joinJaasMuc({ iFrameApi: true, token: t({ moderator: true }) }, { roomName: ctx.roomName });
|
||||
|
||||
// TODO: what should we do in this case? Add a config for this?
|
||||
if (await p.execute(() => config.disableIframeAPI)) {
|
||||
ctx.skipSuiteTests = 'The environment has the iFrame API disabled.';
|
||||
recordingEnabled = Boolean(await p.execute(() => config.recordingService?.enabled));
|
||||
expect(recordingEnabled).toBe(expectations.jaas.recordingEnabled);
|
||||
|
||||
return;
|
||||
liveStreamingEnabled = Boolean(await p.execute(() => config.liveStreaming?.enabled));
|
||||
expect(liveStreamingEnabled).toBe(expectations.jaas.liveStreamingEnabled);
|
||||
|
||||
if (liveStreamingEnabled && !process.env.YTUBE_TEST_STREAM_KEY) {
|
||||
liveStreamingEnabled = false;
|
||||
console.log('Skipping live-streaming tests because YTUBE_TEST_STREAM_KEY is not set.');
|
||||
}
|
||||
|
||||
// TODO: only read if config says so
|
||||
recordingDisabled = Boolean(!await p.execute(() => config.recordingService?.enabled));
|
||||
liveStreamingDisabled = Boolean(!await p.execute(() => config.liveStreaming?.enabled))
|
||||
|| !process.env.YTUBE_TEST_STREAM_KEY;
|
||||
|
||||
await p.switchToMainFrame();
|
||||
});
|
||||
|
||||
@@ -138,7 +135,7 @@ describe('Recording and live-streaming', () => {
|
||||
}
|
||||
|
||||
it('start/stop recording using the iFrame command', async () => {
|
||||
if (recordingDisabled) {
|
||||
if (!recordingEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -150,7 +147,7 @@ describe('Recording and live-streaming', () => {
|
||||
});
|
||||
|
||||
it('start/stop recording using the iFrame function', async () => {
|
||||
if (recordingDisabled) {
|
||||
if (!recordingEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -162,7 +159,7 @@ describe('Recording and live-streaming', () => {
|
||||
});
|
||||
|
||||
it('start/stop live-streaming using the iFrame command', async () => {
|
||||
if (liveStreamingDisabled) {
|
||||
if (!liveStreamingEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import { expect } from '@wdio/globals';
|
||||
import type { Participant } from '../../helpers/Participant';
|
||||
import { setTestProperties } from '../../helpers/TestProperties';
|
||||
import type WebhookProxy from '../../helpers/WebhookProxy';
|
||||
import { expectations } from '../../helpers/expectations';
|
||||
import { joinJaasMuc, generateJaasToken as t } from '../../helpers/jaas';
|
||||
|
||||
setTestProperties(__filename, {
|
||||
@@ -25,12 +26,9 @@ describe('Transcription', () => {
|
||||
token: t({ room, moderator: true }),
|
||||
iFrameApi: true });
|
||||
|
||||
if (await p1.execute(() => config.disableIframeAPI || !config.transcription?.enabled)) {
|
||||
// skip the test if iframeAPI or transcriptions are disabled
|
||||
ctx.skipSuiteTests = 'The environment has the iFrame API or transcriptions disabled.';
|
||||
const transcriptionEnabled = await p1.execute(() => config.transcription?.enabled);
|
||||
|
||||
return;
|
||||
}
|
||||
expect(transcriptionEnabled).toBe(expectations.jaas.transcriptionEnabled);
|
||||
|
||||
p2 = await joinJaasMuc({
|
||||
name: 'p2',
|
||||
@@ -57,10 +55,16 @@ describe('Transcription', () => {
|
||||
|
||||
await checkReceivingChunks(p1, p2, webhooksProxy);
|
||||
|
||||
await p1.getIframeAPI().clearEventResults('transcribingStatusChanged');
|
||||
await p1.getIframeAPI().addEventListener('transcribingStatusChanged');
|
||||
|
||||
await p1.getIframeAPI().executeCommand('toggleSubtitles');
|
||||
|
||||
// give it some time to process
|
||||
await p1.driver.pause(5000);
|
||||
await p1.driver.waitUntil(() => p1.getIframeAPI()
|
||||
.getEventResult('transcribingStatusChanged'), {
|
||||
timeout: 15000,
|
||||
timeoutMsg: 'transcribingStatusChanged event not received by p1'
|
||||
});
|
||||
});
|
||||
|
||||
it('set subtitles on and off', async () => {
|
||||
@@ -72,18 +76,23 @@ describe('Transcription', () => {
|
||||
|
||||
await checkReceivingChunks(p1, p2, webhooksProxy);
|
||||
|
||||
await p1.getIframeAPI().clearEventResults('transcribingStatusChanged');
|
||||
|
||||
await p1.getIframeAPI().executeCommand('setSubtitles', false);
|
||||
|
||||
// give it some time to process
|
||||
await p1.driver.pause(5000);
|
||||
await p1.driver.waitUntil(() => p1.getIframeAPI()
|
||||
.getEventResult('transcribingStatusChanged'), {
|
||||
timeout: 15000,
|
||||
timeoutMsg: 'transcribingStatusChanged event not received by p1'
|
||||
});
|
||||
});
|
||||
|
||||
it('start/stop transcriptions via recording', async () => {
|
||||
// we need to clear results or the last one will be used, from the previous time subtitles were on
|
||||
await p1.getIframeAPI().clearEventResults('transcribingStatusChanged');
|
||||
await p1.getIframeAPI().clearEventResults('transcriptionChunkReceived');
|
||||
await p2.getIframeAPI().clearEventResults('transcriptionChunkReceived');
|
||||
|
||||
await p1.getIframeAPI().addEventListener('transcribingStatusChanged');
|
||||
await p2.getIframeAPI().addEventListener('transcribingStatusChanged');
|
||||
|
||||
await p1.getIframeAPI().executeCommand('startRecording', { transcription: true });
|
||||
|
||||
@@ -2,6 +2,7 @@ import { expect } from '@wdio/globals';
|
||||
|
||||
import { Participant } from '../../../helpers/Participant';
|
||||
import { setTestProperties } from '../../../helpers/TestProperties';
|
||||
import { expectations } from '../../../helpers/expectations';
|
||||
import { joinJaasMuc, generateJaasToken as t } from '../../../helpers/jaas';
|
||||
|
||||
setTestProperties(__filename, {
|
||||
@@ -24,12 +25,13 @@ describe('Visitors live', () => {
|
||||
token: t({ room: ctx.roomName, displayName: 'Mo de Rator', moderator: true })
|
||||
});
|
||||
|
||||
// TODO: Remove this in favor of configurable test expectations
|
||||
await moderator.driver.waitUntil(() => moderator.execute(() => APP.conference._room.isVisitorsSupported()), {
|
||||
timeout: 2000
|
||||
}).catch(e => {
|
||||
ctx.skipSuiteTests = `Because isVisitorsSupported() returned an error: ${e}.`;
|
||||
});
|
||||
if (expectations.jaas.visitors) {
|
||||
await moderator.driver.waitUntil(() => moderator.execute(() => APP.conference._room.isVisitorsSupported()), {
|
||||
timeout: 2000
|
||||
}).catch(e => {
|
||||
throw new Error(`isVisitorsSupported() returned an error: ${e}.`);
|
||||
});
|
||||
}
|
||||
|
||||
visitor = await joinJaasMuc({
|
||||
name: 'p2',
|
||||
|
||||
@@ -66,10 +66,10 @@ async function testActiveSpeaker(
|
||||
const otherParticipant1Driver = otherParticipant1.driver;
|
||||
|
||||
await otherParticipant1Driver.waitUntil(
|
||||
async () => await otherParticipant1.getFilmstrip().isDominantSpeaker(speakerEndpoint),
|
||||
async () => await otherParticipant1.getLargeVideo().getResource() === speakerEndpoint,
|
||||
{
|
||||
timeout: 30_000, // 30 seconds
|
||||
timeoutMsg: `${activeSpeaker.name} is not selected as active speaker.`
|
||||
timeoutMsg: 'Active speaker not displayed on large video.'
|
||||
});
|
||||
|
||||
// just a debug print to go in logs
|
||||
|
||||
@@ -49,7 +49,6 @@ describe('Codec selection', () => {
|
||||
it('asymmetric codecs with AV1', async () => {
|
||||
await ensureThreeParticipants({
|
||||
configOverwrite: {
|
||||
disableTileView: true,
|
||||
videoQuality: {
|
||||
codecPreferenceOrder: [ 'AV1', 'VP9', 'VP8' ]
|
||||
}
|
||||
@@ -99,7 +98,6 @@ describe('Codec selection', () => {
|
||||
|
||||
await ensureThreeParticipants({
|
||||
configOverwrite: {
|
||||
disableTileView: true,
|
||||
videoQuality: {
|
||||
codecPreferenceOrder: [ 'VP8' ]
|
||||
}
|
||||
|
||||
@@ -152,7 +152,7 @@ describe('Desktop sharing', () => {
|
||||
await checkForScreensharingTile(p1, p3);
|
||||
await checkForScreensharingTile(p2, p3);
|
||||
|
||||
// Add another particpant to verify multiple screenshares are visible without gaps in filmstrip.
|
||||
// Add another participant to verify multiple screenshares are visible without gaps in filmstrip.
|
||||
await ensureFourParticipants({
|
||||
configOverwrite: {
|
||||
filmstrip: {
|
||||
|
||||
@@ -92,7 +92,10 @@ describe('Dial-in', () => {
|
||||
throw new Error('no pin');
|
||||
}
|
||||
|
||||
expect(dialInPin.length >= 8).toBe(true);
|
||||
if (!dialInPin.match(/^[0-9]+$/)) {
|
||||
throw new Error(`The dial-in PIN contains non-digit characters: ${dialInPin}`);
|
||||
}
|
||||
expect(dialInPin.length).toBeGreaterThanOrEqual(expectations.dialIn.minPinLength);
|
||||
});
|
||||
|
||||
it('skip the rest if a dial-in URL is not configured', async () => {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import AllureReporter from '@wdio/allure-reporter';
|
||||
import { multiremotebrowser } from '@wdio/globals';
|
||||
import { Buffer } from 'buffer';
|
||||
import fs from 'fs';
|
||||
import { glob } from 'glob';
|
||||
import path from 'node:path';
|
||||
import process from 'node:process';
|
||||
@@ -381,24 +382,25 @@ export const config: WebdriverIO.MultiremoteConfig = {
|
||||
}));
|
||||
|
||||
const allProcessing: Promise<any>[] = [];
|
||||
const attachments: { content: string | Buffer; filename: string; type: string; }[] = [];
|
||||
|
||||
multiremotebrowser.instances.forEach((instance: string) => {
|
||||
const bInstance = multiremotebrowser.getInstance(instance);
|
||||
|
||||
allProcessing.push(bInstance.takeScreenshot().then(shot => {
|
||||
AllureReporter.addAttachment(
|
||||
`Screenshot-${instance}`,
|
||||
Buffer.from(shot, 'base64'),
|
||||
'image/png');
|
||||
attachments.push({
|
||||
filename: `${instance}-screenshot`,
|
||||
content: Buffer.from(shot, 'base64'),
|
||||
type: 'image/png' });
|
||||
}));
|
||||
|
||||
// @ts-ignore
|
||||
allProcessing.push(bInstance.execute(() => typeof APP !== 'undefined' && APP.connection?.getLogs())
|
||||
.then(logs =>
|
||||
logs && AllureReporter.addAttachment(
|
||||
`debug-logs-${instance}`,
|
||||
JSON.stringify(logs, null, ' '),
|
||||
'text/plain'))
|
||||
logs && attachments.push({
|
||||
filename: `${instance}-debug-logs`,
|
||||
content: JSON.stringify(logs, null, ' '),
|
||||
type: 'text/plain' }))
|
||||
.catch(e => console.error('Failed grabbing debug logs', e)));
|
||||
|
||||
allProcessing.push(
|
||||
@@ -407,15 +409,29 @@ export const config: WebdriverIO.MultiremoteConfig = {
|
||||
saveLogs(bInstance, res);
|
||||
}
|
||||
|
||||
AllureReporter.addAttachment(`console-logs-${instance}`, getLogs(bInstance) || '', 'text/plain');
|
||||
attachments.push({
|
||||
filename: `${instance}-console-logs`,
|
||||
content: getLogs(bInstance) || '',
|
||||
type: 'text/plain' });
|
||||
}));
|
||||
|
||||
allProcessing.push(bInstance.getPageSource().then(source => {
|
||||
AllureReporter.addAttachment(`html-source-${instance}`, pretty(source), 'text/plain');
|
||||
attachments.push({
|
||||
filename: `${instance}-html-source`,
|
||||
content: pretty(source),
|
||||
type: 'text/plain' });
|
||||
}));
|
||||
});
|
||||
|
||||
await Promise.allSettled(allProcessing);
|
||||
attachments.sort(
|
||||
(a, b) => {
|
||||
return a.filename < b.filename ? -1 : 1;
|
||||
}).forEach(
|
||||
a => {
|
||||
AllureReporter.addAttachment(a.filename, a.content, a.type);
|
||||
}
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -460,6 +476,15 @@ export const config: WebdriverIO.MultiremoteConfig = {
|
||||
}
|
||||
|
||||
console.log('Allure report successfully generated');
|
||||
|
||||
// An ugly hack to sort by test order by default in the allure report.
|
||||
const content = fs.readFileSync(`${TEST_RESULTS_DIR}/allure-report/index.html`, 'utf8');
|
||||
const modifiedContent = content.replace('<body>',
|
||||
'<body><script>localStorage.setItem("ALLURE_REPORT_SETTINGS_SUITES", \'{"treeSorting":{"sorter":"sorter.order","ascending":true}}\')</script>'
|
||||
);
|
||||
|
||||
fs.writeFileSync(`${TEST_RESULTS_DIR}/allure-report/index.html`, modifiedContent);
|
||||
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -250,7 +250,7 @@ function getDevServerConfig() {
|
||||
warnings: false
|
||||
}
|
||||
},
|
||||
host: '::',
|
||||
host: 'localhost',
|
||||
hot: true,
|
||||
proxy: [
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user