mirror of
https://github.com/vector-im/element-call.git
synced 2026-08-26 21:09:21 +00:00
Compare commits
106 Commits
dbkr/roomn
...
dbkr/depth
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3016866a4b | ||
|
|
2cfa007d4c | ||
|
|
4613d43c3c | ||
|
|
a1aaacaad7 | ||
|
|
1d018eeead | ||
|
|
39ee20ab0c | ||
|
|
499ba0e059 | ||
|
|
6f425abd5f | ||
|
|
87fb3398d4 | ||
|
|
a57db61aed | ||
|
|
0423f2f4bb | ||
|
|
01c9454f92 | ||
|
|
31446e7997 | ||
|
|
02db81d846 | ||
|
|
5f72ec2416 | ||
|
|
4330b46086 | ||
|
|
c546042d18 | ||
|
|
7dac9caa6b | ||
|
|
15566770f9 | ||
|
|
f053ecfa98 | ||
|
|
39dd60598e | ||
|
|
e790c13112 | ||
|
|
946ab6ff1a | ||
|
|
e730926eab | ||
|
|
7195bf7672 | ||
|
|
8e2c1dd164 | ||
|
|
448865b0d5 | ||
|
|
c75b21e0cb | ||
|
|
8a22aa55a6 | ||
|
|
b6e9a0869a | ||
|
|
37e69f6023 | ||
|
|
d672190c6f | ||
|
|
ac6747bbf3 | ||
|
|
6c5e9226cf | ||
|
|
845b4e133b | ||
|
|
b0f63eb2ff | ||
|
|
0a12766171 | ||
|
|
444a37224b | ||
|
|
0297008339 | ||
|
|
6bc8ccf1ce | ||
|
|
48b038914f | ||
|
|
ef32b877ee | ||
|
|
058da0a60f | ||
|
|
f05cae71a8 | ||
|
|
51d31814ea | ||
|
|
f35dd28bd3 | ||
|
|
3cd0ca205b | ||
|
|
b21bdb1bb5 | ||
|
|
fd7541894b | ||
|
|
e5d7c638a6 | ||
|
|
811446c241 | ||
|
|
e9e37736b0 | ||
|
|
187a3c62f8 | ||
|
|
9bf0dc7dee | ||
|
|
9c878eab80 | ||
|
|
c62a9be2e9 | ||
|
|
1e84e438b7 | ||
|
|
1c41373ddd | ||
|
|
a5131be7c7 | ||
|
|
69bf3bd9a1 | ||
|
|
cbf27fc887 | ||
|
|
ed0059c898 | ||
|
|
fb4e0784fc | ||
|
|
850cae9dfa | ||
|
|
ec3783d1a6 | ||
|
|
4f43402299 | ||
|
|
12bd7f8bff | ||
|
|
96c6217a83 | ||
|
|
28315f237a | ||
|
|
9ca2573c0f | ||
|
|
5c377ba01d | ||
|
|
4842b2a89b | ||
|
|
5b5e62c851 | ||
|
|
54e6fd4c07 | ||
|
|
a115f0530f | ||
|
|
393eeabfa8 | ||
|
|
68963aa13b | ||
|
|
ec11689a49 | ||
|
|
d4b67015b8 | ||
|
|
7050eebe77 | ||
|
|
0128fece80 | ||
|
|
d891b23fc8 | ||
|
|
605a88220b | ||
|
|
67df28d66a | ||
|
|
ed532f6f38 | ||
|
|
6a78440260 | ||
|
|
3daba3a529 | ||
|
|
01ec38493a | ||
|
|
19bb78fbe3 | ||
|
|
703aa661ea | ||
|
|
0d7d400f75 | ||
|
|
fe9ea31149 | ||
|
|
1ebf1bd4e6 | ||
|
|
83fdb094d5 | ||
|
|
2df95c4fe9 | ||
|
|
aab27ae616 | ||
|
|
f33170f5f4 | ||
|
|
a268a804bb | ||
|
|
e604c7bcae | ||
|
|
53e3cbcf61 | ||
|
|
2753f04f0b | ||
|
|
754ade6e60 | ||
|
|
32a2bf3b32 | ||
|
|
728e37b23c | ||
|
|
24ffb6547c | ||
|
|
edd4ff077b |
@@ -33,6 +33,8 @@ module.exports = {
|
||||
rules: {
|
||||
// We're aiming to convert this code to strict mode
|
||||
"@typescript-eslint/no-non-null-assertion": "off",
|
||||
// We should use the js-sdk logger, never console directly.
|
||||
"no-console": ["error"],
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
27
.github/workflows/triage-incoming.yaml
vendored
27
.github/workflows/triage-incoming.yaml
vendored
@@ -1,27 +0,0 @@
|
||||
name: Move new issues into triage board
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [opened]
|
||||
|
||||
jobs:
|
||||
add-to-project:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: octokit/graphql-action@v2.x
|
||||
id: add_to_project
|
||||
with:
|
||||
headers: '{"GraphQL-Features": "projects_next_graphql"}'
|
||||
query: |
|
||||
mutation add_to_project($projectid:ID!,$contentid:ID!) {
|
||||
addProjectV2ItemById(input: {projectId: $projectid contentId: $contentid}) {
|
||||
item {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
projectid: ${{ env.PROJECT_ID }}
|
||||
contentid: ${{ github.event.issue.node_id }}
|
||||
env:
|
||||
PROJECT_ID: "PVT_kwDOAM0swc4AH1sa"
|
||||
GITHUB_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
||||
17
package.json
17
package.json
@@ -47,18 +47,18 @@
|
||||
"@sentry/react": "^6.13.3",
|
||||
"@sentry/tracing": "^6.13.3",
|
||||
"@use-gesture/react": "^10.2.11",
|
||||
"@vector-im/compound-design-tokens": "^0.0.5",
|
||||
"@vector-im/compound-design-tokens": "^0.0.6",
|
||||
"@vector-im/compound-web": "^0.4.0",
|
||||
"@vitejs/plugin-basic-ssl": "^1.0.1",
|
||||
"@vitejs/plugin-react": "^4.0.1",
|
||||
"classnames": "^2.3.1",
|
||||
"events": "^3.3.0",
|
||||
"i18next": "^21.10.0",
|
||||
"i18next-browser-languagedetector": "^6.1.8",
|
||||
"i18next-http-backend": "^1.4.4",
|
||||
"i18next": "^23.0.0",
|
||||
"i18next-browser-languagedetector": "^7.0.0",
|
||||
"i18next-http-backend": "^2.0.0",
|
||||
"livekit-client": "^1.12.3",
|
||||
"lodash": "^4.17.21",
|
||||
"matrix-js-sdk": "github:matrix-org/matrix-js-sdk#6836720e1e1c2cb01d49d6e5fcfc01afc14834ca",
|
||||
"matrix-js-sdk": "github:matrix-org/matrix-js-sdk#6385c9c0dab8fe67bd3a8992a4777f243fdd1b68",
|
||||
"matrix-widget-api": "^1.3.1",
|
||||
"mermaid": "^9.0.0",
|
||||
"normalize.css": "^8.0.1",
|
||||
@@ -68,7 +68,7 @@
|
||||
"re-resizable": "^6.9.0",
|
||||
"react": "18",
|
||||
"react-dom": "18",
|
||||
"react-i18next": "^11.18.6",
|
||||
"react-i18next": "^13.0.0",
|
||||
"react-json-view": "^1.21.3",
|
||||
"react-router-dom": "^5.2.0",
|
||||
"react-use-clipboard": "^1.0.7",
|
||||
@@ -85,13 +85,14 @@
|
||||
"@react-types/dialog": "^3.5.5",
|
||||
"@sentry/vite-plugin": "^2.0.0",
|
||||
"@storybook/react": "^6.5.0-alpha.5",
|
||||
"@testing-library/jest-dom": "^5.16.5",
|
||||
"@testing-library/jest-dom": "^6.0.0",
|
||||
"@testing-library/react": "^14.0.0",
|
||||
"@types/content-type": "^1.1.5",
|
||||
"@types/d3": "^7.4.0",
|
||||
"@types/dom-screen-wake-lock": "^1.0.1",
|
||||
"@types/dompurify": "^3.0.2",
|
||||
"@types/grecaptcha": "^3.0.4",
|
||||
"@types/jest": "^29.5.5",
|
||||
"@types/node": "^18.13.0",
|
||||
"@types/react-router-dom": "^5.3.3",
|
||||
"@types/request": "^2.48.8",
|
||||
@@ -109,7 +110,7 @@
|
||||
"eslint-plugin-matrix-org": "^0.4.0",
|
||||
"eslint-plugin-react": "^7.29.4",
|
||||
"eslint-plugin-react-hooks": "^4.5.0",
|
||||
"i18next-parser": "^6.6.0",
|
||||
"i18next-parser": "^8.0.0",
|
||||
"identity-obj-proxy": "^3.0.0",
|
||||
"jest": "^29.2.2",
|
||||
"jest-environment-jsdom": "^29.3.1",
|
||||
|
||||
@@ -119,5 +119,8 @@
|
||||
"Back to recents": "Zurück zu kürzlichen Anrufen",
|
||||
"Select app": "App auswählen",
|
||||
"Mute microphone": "Mikrofon deaktivieren",
|
||||
"Start new call": "Neuen Anruf beginnen"
|
||||
"Start new call": "Neuen Anruf beginnen",
|
||||
"Call not found": "Anruf nicht gefunden",
|
||||
"Calls are now end-to-end encrypted and need to be created from the home page. This helps make sure everyone's using the same encryption key.": "Anrufe sind nun Ende-zu-Ende-verschlüsselt und müssen auf der Startseite erstellt werden. Damit stellen wir sicher, dass alle denselben Schlüssel verwenden.",
|
||||
"Your web browser does not support media end-to-end encryption. Supported Browsers are Chrome, Safari, Firefox >=117": "Dein Webbrowser unterstützt keine Medien-Ende-zu-Ende-Verschlüsselung. Unterstützte Browser sind Chrome, Safari, Firefox >=117"
|
||||
}
|
||||
|
||||
@@ -21,6 +21,8 @@
|
||||
"By clicking \"Go\", you agree to our <2>End User Licensing Agreement (EULA)</2>": "By clicking \"Go\", you agree to our <2>End User Licensing Agreement (EULA)</2>",
|
||||
"By clicking \"Join call now\", you agree to our <2>End User Licensing Agreement (EULA)</2>": "By clicking \"Join call now\", you agree to our <2>End User Licensing Agreement (EULA)</2>",
|
||||
"By participating in this beta, you consent to the collection of anonymous data, which we use to improve the product. You can find more information about which data we track in our <2>Privacy Policy</2> and our <5>Cookie Policy</5>.": "By participating in this beta, you consent to the collection of anonymous data, which we use to improve the product. You can find more information about which data we track in our <2>Privacy Policy</2> and our <5>Cookie Policy</5>.",
|
||||
"Call not found": "Call not found",
|
||||
"Calls are now end-to-end encrypted and need to be created from the home page. This helps make sure everyone's using the same encryption key.": "Calls are now end-to-end encrypted and need to be created from the home page. This helps make sure everyone's using the same encryption key.",
|
||||
"Camera": "Camera",
|
||||
"Close": "Close",
|
||||
"Confirm password": "Confirm password",
|
||||
@@ -119,5 +121,6 @@
|
||||
"Yes, join call": "Yes, join call",
|
||||
"You": "You",
|
||||
"You were disconnected from the call": "You were disconnected from the call",
|
||||
"Your feedback": "Your feedback"
|
||||
"Your feedback": "Your feedback",
|
||||
"Your web browser does not support media end-to-end encryption. Supported Browsers are Chrome, Safari, Firefox >=117": "Your web browser does not support media end-to-end encryption. Supported Browsers are Chrome, Safari, Firefox >=117"
|
||||
}
|
||||
|
||||
@@ -77,7 +77,7 @@
|
||||
"Submit": "Saada",
|
||||
"If you are experiencing issues or simply would like to provide some feedback, please send us a short description below.": "Kui selle rakenduse kasutamisel tekib sul probleeme või lihtsalt soovid oma arvamust avaldada, siis palun täida alljärgnev lühike kirjeldus.",
|
||||
"Feedback": "Tagasiside",
|
||||
"{{count}} stars|one": "{{count}} tärn",
|
||||
"{{count}} stars|one": "{{count}} tärni",
|
||||
"{{count}} stars|other": "{{count}} tärni",
|
||||
"How did it go?": "Kuidas sujus?",
|
||||
"{{displayName}}, your call has ended.": "{{displayName}}, sinu kõne on lõppenud.",
|
||||
@@ -107,5 +107,20 @@
|
||||
"Sharing screen": "Ekraanivaade on jagamisel",
|
||||
"{{count, number}}|one": "{{count, number}}",
|
||||
"{{count, number}}|other": "{{count, number}}",
|
||||
"{{names, list(style: short;)}}": "{{names, list(style: short;)}}"
|
||||
"{{names, list(style: short;)}}": "{{names, list(style: short;)}}",
|
||||
"You": "Sina",
|
||||
"Continue in browser": "Jätka veebibrauseris",
|
||||
"Mute microphone": "Summuta mikrofon",
|
||||
"Name of call": "Kõne nimi",
|
||||
"Open in the app": "Ava rakenduses",
|
||||
"Ready to join?": "Oled valmis liituma?",
|
||||
"Select app": "Vali rakendus",
|
||||
"Start new call": "Algata uus kõne",
|
||||
"Back to recents": "Tagasi hiljutiste kõnede juurde",
|
||||
"Stop video": "Peata videovoog",
|
||||
"Start video": "Lülita videovoog sisse",
|
||||
"Unmute microphone": "Lülita mikrofon sisse",
|
||||
"Call not found": "Kõnet ei leidu",
|
||||
"Calls are now end-to-end encrypted and need to be created from the home page. This helps make sure everyone's using the same encryption key.": "Kõned on nüüd läbivalt krüptitud ning need pead looma kodulehelt. Sellega tagad, et kõik kasutavad samu krüptovõtmeid.",
|
||||
"Your web browser does not support media end-to-end encryption. Supported Browsers are Chrome, Safari, Firefox >=117": "Sinu veebibrauser ei toeta meedia läbivat krüptimist. Toetatud brauserid on Chrome, Chromium, Safari ja Firefox >=117"
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@
|
||||
"Sign in": "Connexion",
|
||||
"Sign out": "Déconnexion",
|
||||
"Spotlight": "Premier plan",
|
||||
"Submit feedback": "Envoyer des retours",
|
||||
"Submit feedback": "Envoyer un commentaire",
|
||||
"This call already exists, would you like to join?": "Cet appel existe déjà, voulez-vous le rejoindre ?",
|
||||
"Yes, join call": "Oui, rejoindre l’appel",
|
||||
"Waiting for other participants…": "En attente d’autres participants…",
|
||||
@@ -75,7 +75,7 @@
|
||||
"Thanks, we received your feedback!": "Merci, nous avons reçu vos commentaires !",
|
||||
"Submitting…": "Envoi…",
|
||||
"Submit": "Envoyer",
|
||||
"If you are experiencing issues or simply would like to provide some feedback, please send us a short description below.": "Si vous rencontrez des problèmes, ou vous voulez simplement faire un commentaire, veuillez nous envoyer une courte description ci-dessous.",
|
||||
"If you are experiencing issues or simply would like to provide some feedback, please send us a short description below.": "Si vous rencontrez des problèmes, ou vous voulez simplement faire un commentaire, faites-en une courte description ci-dessous.",
|
||||
"Feedback": "Commentaires",
|
||||
"{{count}} stars|other": "{{count}} favoris",
|
||||
"<0>We'd love to hear your feedback so we can improve your experience.</0>": "<0>Nous aimerions avoir vos commentaires afin que nous puissions améliorer votre expérience.</0>",
|
||||
@@ -108,5 +108,19 @@
|
||||
"Sharing screen": "L’écran est partagé",
|
||||
"{{count, number}}|one": "{{count, number}}",
|
||||
"Not encrypted": "Non chiffré",
|
||||
"You": "Vous"
|
||||
"You": "Vous",
|
||||
"Continue in browser": "Continuer dans le navigateur",
|
||||
"Mute microphone": "Couper le microphone",
|
||||
"Name of call": "Nom de l’appel",
|
||||
"Open in the app": "Ouvrir dans l’application",
|
||||
"Ready to join?": "Prêt à rejoindre ?",
|
||||
"Select app": "Choisissez l’application",
|
||||
"Start new call": "Démarrer un nouvel appel",
|
||||
"Back to recents": "Revenir à l’historique des appels",
|
||||
"Start video": "Démarrer la vidéo",
|
||||
"Stop video": "Arrêter la vidéo",
|
||||
"Unmute microphone": "Allumer le microphone",
|
||||
"Call not found": "Appel non trouvé",
|
||||
"Calls are now end-to-end encrypted and need to be created from the home page. This helps make sure everyone's using the same encryption key.": "Les appels sont maintenant chiffrés de bout-en-bout et doivent être créés depuis la page d’accueil. Cela permet d’être sûr que tout le monde utilise la même clé de chiffrement.",
|
||||
"Your web browser does not support media end-to-end encryption. Supported Browsers are Chrome, Safari, Firefox >=117": "Votre navigateur web ne prend pas en charge le chiffrement de bout-en-bout des médias. Les navigateurs pris en charge sont Chrome, Safari, Firefox >= 117"
|
||||
}
|
||||
|
||||
@@ -108,5 +108,19 @@
|
||||
"Sharing screen": "Berbagi layar",
|
||||
"{{count, number}}|one": "{{count, number}}",
|
||||
"{{names, list(style: short;)}}": "{{names, list(style: short;)}}",
|
||||
"You": "Anda"
|
||||
"You": "Anda",
|
||||
"Continue in browser": "Lanjutkan dalam peramban",
|
||||
"Mute microphone": "Matikan mikrofon",
|
||||
"Name of call": "Nama panggilan",
|
||||
"Open in the app": "Buka dalam aplikasi",
|
||||
"Ready to join?": "Siap untuk bergabung?",
|
||||
"Select app": "Pilih plikasi",
|
||||
"Start new call": "Mulai panggilan baru",
|
||||
"Start video": "Nyalakan video",
|
||||
"Stop video": "Matikan video",
|
||||
"Unmute microphone": "Nyalakan mikrofon",
|
||||
"Back to recents": "Kembali ke terkini",
|
||||
"Call not found": "Panggilan tidak ditemukan",
|
||||
"Calls are now end-to-end encrypted and need to be created from the home page. This helps make sure everyone's using the same encryption key.": "Panggilan sekarang terenkripsi secara ujung ke ujung dan harus dibuat dari laman beranda. Ini memastikan bahwa semuanya menggunakan kunci enkripsi yang sama.",
|
||||
"Your web browser does not support media end-to-end encryption. Supported Browsers are Chrome, Safari, Firefox >=117": "Peramban web Anda tidak mendukung enkripsi media ujung ke ujung. Peramban yang didukung adalah Chrome, Safari, dan Firefox >=117"
|
||||
}
|
||||
|
||||
@@ -119,5 +119,7 @@
|
||||
"Stop video": "Ferma video",
|
||||
"Unmute microphone": "Riaccendi il microfono",
|
||||
"Back to recents": "Torna ai recenti",
|
||||
"Start new call": "Inizia nuova chiamata"
|
||||
"Start new call": "Inizia nuova chiamata",
|
||||
"Call not found": "Chiamata non trovata",
|
||||
"Calls are now end-to-end encrypted and need to be created from the home page. This helps make sure everyone's using the same encryption key.": "Le chiamate ora sono cifrate end-to-end e devono essere create dalla pagina principale. Ciò assicura che chiunque usi la stessa chiave di crittografia."
|
||||
}
|
||||
|
||||
@@ -108,5 +108,19 @@
|
||||
"{{count, number}}|one": "{{count, number}}",
|
||||
"Share this call": "Zdieľať tento hovor",
|
||||
"{{names, list(style: short;)}}": "{{names, list(style: short;)}}",
|
||||
"You": "Vy"
|
||||
"You": "Vy",
|
||||
"Continue in browser": "Pokračovať v prehliadači",
|
||||
"Mute microphone": "Stlmiť mikrofón",
|
||||
"Name of call": "Názov hovoru",
|
||||
"Open in the app": "Otvoriť v aplikácii",
|
||||
"Ready to join?": "Ste pripravení sa pridať?",
|
||||
"Select app": "Vybrať aplikáciu",
|
||||
"Start new call": "Spustiť nový hovor",
|
||||
"Start video": "Spustiť video",
|
||||
"Stop video": "Zastaviť video",
|
||||
"Back to recents": "Späť k nedávnym",
|
||||
"Unmute microphone": "Zrušiť stlmenie mikrofónu",
|
||||
"Call not found": "Hovor nebol nájdený",
|
||||
"Calls are now end-to-end encrypted and need to be created from the home page. This helps make sure everyone's using the same encryption key.": "Hovory sú teraz end-to-end šifrované a je potrebné ich vytvoriť z domovskej stránky. To pomáha zabezpečiť, aby všetci používali rovnaký šifrovací kľúč.",
|
||||
"Your web browser does not support media end-to-end encryption. Supported Browsers are Chrome, Safari, Firefox >=117": "Váš webový prehliadač nepodporuje end-to-end šifrovanie médií. Podporované prehliadače sú Chrome, Safari, Firefox >=117"
|
||||
}
|
||||
|
||||
@@ -118,5 +118,9 @@
|
||||
"Unmute microphone": "Увімкнути мікрофон",
|
||||
"Continue in browser": "Продовжити у браузері",
|
||||
"Name of call": "Назва виклику",
|
||||
"Start new call": "Розпочати новий виклик"
|
||||
"Start new call": "Розпочати новий виклик",
|
||||
"Back to recents": "Повернутися до недавніх",
|
||||
"Call not found": "Виклик не знайдено",
|
||||
"Calls are now end-to-end encrypted and need to be created from the home page. This helps make sure everyone's using the same encryption key.": "Відтепер виклики захищено наскрізним шифруванням, і їх потрібно створювати з домашньої сторінки. Це допомагає переконатися, що всі користувачі використовують один і той самий ключ шифрування.",
|
||||
"Your web browser does not support media end-to-end encryption. Supported Browsers are Chrome, Safari, Firefox >=117": "Ваш браузер не підтримує наскрізне шифрування мультимедійних даних. Підтримувані браузери: Chrome, Safari, Firefox >=117"
|
||||
}
|
||||
|
||||
@@ -119,5 +119,8 @@
|
||||
"Start video": "開始影片",
|
||||
"Back to recents": "回到最近的通話",
|
||||
"Stop video": "停止影片",
|
||||
"Unmute microphone": "將麥克風取消靜音"
|
||||
"Unmute microphone": "將麥克風取消靜音",
|
||||
"Call not found": "找不到通話",
|
||||
"Calls are now end-to-end encrypted and need to be created from the home page. This helps make sure everyone's using the same encryption key.": "通話現在是端對端加密的,必須從首頁建立。這有助於確保每個人都使用相同的加密金鑰。",
|
||||
"Your web browser does not support media end-to-end encryption. Supported Browsers are Chrome, Safari, Firefox >=117": "您的網路瀏覽器不支援媒體端到端加密。支援的瀏覽器包含了 Chrome、Safari、Firefox >=117"
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ import { useLocation } from "react-router-dom";
|
||||
import classNames from "classnames";
|
||||
import { Trans, useTranslation } from "react-i18next";
|
||||
import * as Sentry from "@sentry/react";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
|
||||
import { Header, HeaderLogo, LeftNav, RightNav } from "./Header";
|
||||
import { LinkButton, Button } from "./button";
|
||||
@@ -57,7 +58,7 @@ export function ErrorView({ error }: ErrorViewProps) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
useEffect(() => {
|
||||
console.error(error);
|
||||
logger.error(error);
|
||||
Sentry.captureException(error);
|
||||
}, [error]);
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ import { useHistory, useLocation } from "react-router-dom";
|
||||
import { captureException } from "@sentry/react";
|
||||
import { sleep } from "matrix-js-sdk/src/utils";
|
||||
import { Trans, useTranslation } from "react-i18next";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
|
||||
import { FieldRow, InputField, ErrorMessage } from "../input/Input";
|
||||
import { Button } from "../button";
|
||||
@@ -97,7 +98,7 @@ export const RegisterPage: FC = () => {
|
||||
await newClient.joinRoom(roomId);
|
||||
} else {
|
||||
captureException(error);
|
||||
console.error(`Couldn't join room ${roomId}`, error);
|
||||
logger.error(`Couldn't join room ${roomId}`, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ limitations under the License.
|
||||
import { useEffect, useCallback, useRef, useState } from "react";
|
||||
import { randomString } from "matrix-js-sdk/src/randomstring";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
|
||||
import { translatedError } from "../TranslatedError";
|
||||
|
||||
@@ -74,7 +75,7 @@ export const useRecaptcha = (sitekey?: string) => {
|
||||
}
|
||||
|
||||
if (!window.grecaptcha) {
|
||||
console.log("Recaptcha not loaded");
|
||||
logger.log("Recaptcha not loaded");
|
||||
return Promise.reject(translatedError("Recaptcha not loaded", t));
|
||||
}
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ import { widget } from "../widget";
|
||||
export const getRoomSharedKeyLocalStorageKey = (roomId: string): string =>
|
||||
`room-shared-key-${roomId}`;
|
||||
|
||||
export const useInternalRoomSharedKey = (
|
||||
const useInternalRoomSharedKey = (
|
||||
roomId: string
|
||||
): [string | null, (value: string) => void] => {
|
||||
const key = useMemo(() => getRoomSharedKeyLocalStorageKey(roomId), [roomId]);
|
||||
@@ -35,36 +35,52 @@ export const useInternalRoomSharedKey = (
|
||||
return [e2eeEnabled ? roomSharedKey : null, setRoomSharedKey];
|
||||
};
|
||||
|
||||
export const useRoomSharedKey = (roomId: string): string | null => {
|
||||
return useInternalRoomSharedKey(roomId)[0];
|
||||
};
|
||||
|
||||
export const useManageRoomSharedKey = (roomId: string): string | null => {
|
||||
const { password } = useUrlParams();
|
||||
const useKeyFromUrl = (roomId: string): string | null => {
|
||||
const urlParams = useUrlParams();
|
||||
const [e2eeSharedKey, setE2EESharedKey] = useInternalRoomSharedKey(roomId);
|
||||
|
||||
useEffect(() => {
|
||||
if (!password) return;
|
||||
if (password === "") return;
|
||||
if (password === e2eeSharedKey) return;
|
||||
if (!urlParams.password) return;
|
||||
if (urlParams.password === "") return;
|
||||
if (urlParams.password === e2eeSharedKey) return;
|
||||
|
||||
setE2EESharedKey(password);
|
||||
}, [password, e2eeSharedKey, setE2EESharedKey]);
|
||||
setE2EESharedKey(urlParams.password);
|
||||
}, [urlParams, e2eeSharedKey, setE2EESharedKey]);
|
||||
|
||||
return urlParams.password ?? null;
|
||||
};
|
||||
|
||||
export const useRoomSharedKey = (roomId: string): string | null => {
|
||||
// make sure we've extracted the key from the URL first
|
||||
// (and we still need to take the value it returns because
|
||||
// the effect won't run in time for it to save to localstorage in
|
||||
// time for us to read it out again).
|
||||
const passwordFormUrl = useKeyFromUrl(roomId);
|
||||
|
||||
return useInternalRoomSharedKey(roomId)[0] ?? passwordFormUrl;
|
||||
};
|
||||
|
||||
export const useManageRoomSharedKey = (roomId: string): string | null => {
|
||||
const urlParams = useUrlParams();
|
||||
|
||||
const urlPassword = useKeyFromUrl(roomId);
|
||||
|
||||
const [e2eeSharedKey] = useInternalRoomSharedKey(roomId);
|
||||
|
||||
useEffect(() => {
|
||||
const hash = location.hash;
|
||||
|
||||
if (!hash.includes("?")) return;
|
||||
if (!hash.includes(PASSWORD_STRING)) return;
|
||||
if (password !== e2eeSharedKey) return;
|
||||
if (urlParams.password !== e2eeSharedKey) return;
|
||||
|
||||
const [hashStart, passwordStart] = hash.split(PASSWORD_STRING);
|
||||
const hashEnd = passwordStart.split("&")[1];
|
||||
const hashEnd = passwordStart.split("&").slice(1).join("&");
|
||||
|
||||
location.replace((hashStart ?? "") + (hashEnd ?? ""));
|
||||
}, [password, e2eeSharedKey]);
|
||||
}, [urlParams, e2eeSharedKey]);
|
||||
|
||||
return e2eeSharedKey;
|
||||
return e2eeSharedKey ?? urlPassword;
|
||||
};
|
||||
|
||||
export const useIsRoomE2EE = (roomId: string): boolean | null => {
|
||||
|
||||
@@ -17,11 +17,12 @@ limitations under the License.
|
||||
import { Link } from "react-router-dom";
|
||||
import { MatrixClient } from "matrix-js-sdk/src/client";
|
||||
import { RoomMember } from "matrix-js-sdk/src/models/room-member";
|
||||
import { Room } from "matrix-js-sdk/src/models/room";
|
||||
|
||||
import { CopyButton } from "../button";
|
||||
import { Avatar, Size } from "../Avatar";
|
||||
import styles from "./CallList.module.css";
|
||||
import { getRoomUrl } from "../matrix-utils";
|
||||
import { getAbsoluteRoomUrl, getRelativeRoomUrl } from "../matrix-utils";
|
||||
import { Body } from "../typography/Typography";
|
||||
import { GroupCallRoom } from "./useGroupCallRooms";
|
||||
import { useRoomSharedKey } from "../e2ee/sharedKeyManagement";
|
||||
@@ -40,7 +41,7 @@ export function CallList({ rooms, client }: CallListProps) {
|
||||
client={client}
|
||||
name={roomName}
|
||||
avatarUrl={avatarUrl}
|
||||
roomId={room.roomId}
|
||||
room={room}
|
||||
participants={participants}
|
||||
/>
|
||||
))}
|
||||
@@ -57,17 +58,22 @@ export function CallList({ rooms, client }: CallListProps) {
|
||||
interface CallTileProps {
|
||||
name: string;
|
||||
avatarUrl: string;
|
||||
roomId: string;
|
||||
room: Room;
|
||||
participants: RoomMember[];
|
||||
client: MatrixClient;
|
||||
}
|
||||
function CallTile({ name, avatarUrl, roomId }: CallTileProps) {
|
||||
const roomSharedKey = useRoomSharedKey(roomId);
|
||||
function CallTile({ name, avatarUrl, room }: CallTileProps) {
|
||||
const roomSharedKey = useRoomSharedKey(room.roomId);
|
||||
|
||||
return (
|
||||
<div className={styles.callTile}>
|
||||
<Link to={`/room/#?roomId=${roomId}`} className={styles.callTileLink}>
|
||||
<Avatar id={roomId} name={name} size={Size.LG} src={avatarUrl} />
|
||||
<Link
|
||||
// note we explicitly omit the password here as we don't want it on this link because
|
||||
// it's just for the user to navigate around and not for sharing
|
||||
to={getRelativeRoomUrl(room.roomId, room.name)}
|
||||
className={styles.callTileLink}
|
||||
>
|
||||
<Avatar id={room.roomId} name={name} size={Size.LG} src={avatarUrl} />
|
||||
<div className={styles.callInfo}>
|
||||
<Body overflowEllipsis fontWeight="semiBold">
|
||||
{name}
|
||||
@@ -78,7 +84,11 @@ function CallTile({ name, avatarUrl, roomId }: CallTileProps) {
|
||||
<CopyButton
|
||||
className={styles.copyButton}
|
||||
variant="icon"
|
||||
value={getRoomUrl(roomId, roomSharedKey ?? undefined)}
|
||||
value={getAbsoluteRoomUrl(
|
||||
room.roomId,
|
||||
room.name,
|
||||
roomSharedKey ?? undefined
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -20,9 +20,11 @@ import { MatrixClient } from "matrix-js-sdk/src/client";
|
||||
import { randomString } from "matrix-js-sdk/src/randomstring";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Heading } from "@vector-im/compound-web";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
|
||||
import {
|
||||
createRoom,
|
||||
getRelativeRoomUrl,
|
||||
roomAliasLocalpartFromRoomName,
|
||||
sanitiseRoomNameInput,
|
||||
} from "../matrix-utils";
|
||||
@@ -86,7 +88,7 @@ export function RegisteredView({ client }: Props) {
|
||||
);
|
||||
}
|
||||
|
||||
history.push(`/room/#?roomId=${roomId}`);
|
||||
history.push(getRelativeRoomUrl(roomId, roomName));
|
||||
}
|
||||
|
||||
submit().catch((error) => {
|
||||
@@ -96,7 +98,7 @@ export function RegisteredView({ client }: Props) {
|
||||
setError(undefined);
|
||||
setJoinExistingCallModalOpen(true);
|
||||
} else {
|
||||
console.error(error);
|
||||
logger.error(error);
|
||||
setLoading(false);
|
||||
setError(error);
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ import { useHistory } from "react-router-dom";
|
||||
import { randomString } from "matrix-js-sdk/src/randomstring";
|
||||
import { Trans, useTranslation } from "react-i18next";
|
||||
import { Heading } from "@vector-im/compound-web";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
|
||||
import { useClient } from "../ClientContext";
|
||||
import { Header, HeaderLogo, LeftNav, RightNav } from "../Header";
|
||||
@@ -27,6 +28,7 @@ import { FieldRow, InputField, ErrorMessage } from "../input/Input";
|
||||
import { Button } from "../button";
|
||||
import {
|
||||
createRoom,
|
||||
getRelativeRoomUrl,
|
||||
roomAliasLocalpartFromRoomName,
|
||||
sanitiseRoomNameInput,
|
||||
} from "../matrix-utils";
|
||||
@@ -125,11 +127,11 @@ export const UnauthenticatedView: FC = () => {
|
||||
}
|
||||
|
||||
setClient({ client, session });
|
||||
history.push(`/room/#?roomId=${roomId}`);
|
||||
history.push(getRelativeRoomUrl(roomId, roomName));
|
||||
}
|
||||
|
||||
submit().catch((error) => {
|
||||
console.error(error);
|
||||
logger.error(error);
|
||||
setLoading(false);
|
||||
setError(error);
|
||||
reset();
|
||||
|
||||
@@ -22,7 +22,7 @@ import { GroupCallEventHandlerEvent } from "matrix-js-sdk/src/webrtc/groupCallEv
|
||||
import { useState, useEffect } from "react";
|
||||
|
||||
export interface GroupCallRoom {
|
||||
roomAlias: string;
|
||||
roomAlias?: string;
|
||||
roomName: string;
|
||||
avatarUrl: string;
|
||||
room: Room;
|
||||
@@ -94,7 +94,7 @@ export function useGroupCallRooms(client: MatrixClient): GroupCallRoom[] {
|
||||
const groupCall = client.getGroupCallForRoom(room.roomId)!;
|
||||
|
||||
return {
|
||||
roomAlias: room.getCanonicalAlias(),
|
||||
roomAlias: room.getCanonicalAlias() ?? undefined,
|
||||
roomName: room.name,
|
||||
avatarUrl: room.getMxcAvatarUrl()!,
|
||||
room,
|
||||
@@ -103,7 +103,7 @@ export function useGroupCallRooms(client: MatrixClient): GroupCallRoom[] {
|
||||
};
|
||||
});
|
||||
|
||||
setRooms(items as GroupCallRoom[]);
|
||||
setRooms(items);
|
||||
}
|
||||
|
||||
updateRooms();
|
||||
|
||||
@@ -17,10 +17,8 @@ limitations under the License.
|
||||
import {
|
||||
AudioCaptureOptions,
|
||||
ConnectionState,
|
||||
LocalTrackPublication,
|
||||
Room,
|
||||
RoomEvent,
|
||||
Track,
|
||||
} from "livekit-client";
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
@@ -56,24 +54,22 @@ async function doConnect(
|
||||
audioOptions: AudioCaptureOptions
|
||||
): Promise<void> {
|
||||
await livekitRoom!.connect(sfuConfig!.url, sfuConfig!.jwt);
|
||||
const hasMicrophoneTrack = Array.from(
|
||||
livekitRoom?.localParticipant.audioTracks.values()
|
||||
).some((track: LocalTrackPublication) => {
|
||||
return track.source == Track.Source.Microphone;
|
||||
});
|
||||
// We create a track in case there isn't any.
|
||||
if (!hasMicrophoneTrack) {
|
||||
const audioTracks = await livekitRoom!.localParticipant.createTracks({
|
||||
audio: audioOptions,
|
||||
});
|
||||
if (audioTracks.length < 1) {
|
||||
logger.info("Tried to pre-create local audio track but got no tracks");
|
||||
return;
|
||||
}
|
||||
if (!audioEnabled) await audioTracks[0].mute();
|
||||
|
||||
await livekitRoom?.localParticipant.publishTrack(audioTracks[0]);
|
||||
// Always create an audio track manually.
|
||||
// livekit (by default) keeps the mic track open when you mute, but if you start muted,
|
||||
// doesn't publish it until you unmute. We want to publish it from the start so we're
|
||||
// always capturing audio: it helps keep bluetooth headsets in the right mode and
|
||||
// mobile browsers to know we're doing a call.
|
||||
const audioTracks = await livekitRoom!.localParticipant.createTracks({
|
||||
audio: audioOptions,
|
||||
});
|
||||
if (audioTracks.length < 1) {
|
||||
logger.info("Tried to pre-create local audio track but got no tracks");
|
||||
return;
|
||||
}
|
||||
if (!audioEnabled) await audioTracks[0].mute();
|
||||
|
||||
await livekitRoom?.localParticipant.publishTrack(audioTracks[0]);
|
||||
}
|
||||
|
||||
export function useECConnectionState(
|
||||
@@ -89,6 +85,7 @@ export function useECConnectionState(
|
||||
);
|
||||
|
||||
const [isSwitchingFocus, setSwitchingFocus] = useState(false);
|
||||
const [isInDoConnect, setIsInDoConnect] = useState(false);
|
||||
|
||||
const onConnStateChanged = useCallback((state: ConnectionState) => {
|
||||
if (state == ConnectionState.Connected) setSwitchingFocus(false);
|
||||
@@ -125,12 +122,17 @@ export function useECConnectionState(
|
||||
(async () => {
|
||||
setSwitchingFocus(true);
|
||||
await livekitRoom?.disconnect();
|
||||
await doConnect(
|
||||
livekitRoom!,
|
||||
sfuConfig!,
|
||||
initialAudioEnabled,
|
||||
initialAudioOptions
|
||||
);
|
||||
setIsInDoConnect(true);
|
||||
try {
|
||||
await doConnect(
|
||||
livekitRoom!,
|
||||
sfuConfig!,
|
||||
initialAudioEnabled,
|
||||
initialAudioOptions
|
||||
);
|
||||
} finally {
|
||||
setIsInDoConnect(false);
|
||||
}
|
||||
})();
|
||||
} else if (
|
||||
!sfuConfigValid(currentSFUConfig.current) &&
|
||||
@@ -142,16 +144,24 @@ export function useECConnectionState(
|
||||
// doesn't publish it until you unmute. We want to publish it from the start so we're
|
||||
// always capturing audio: it helps keep bluetooth headsets in the right mode and
|
||||
// mobile browsers to know we're doing a call.
|
||||
setIsInDoConnect(true);
|
||||
doConnect(
|
||||
livekitRoom!,
|
||||
sfuConfig!,
|
||||
initialAudioEnabled,
|
||||
initialAudioOptions
|
||||
);
|
||||
).finally(() => setIsInDoConnect(false));
|
||||
}
|
||||
|
||||
currentSFUConfig.current = Object.assign({}, sfuConfig);
|
||||
}, [sfuConfig, livekitRoom, initialAudioOptions, initialAudioEnabled]);
|
||||
|
||||
return isSwitchingFocus ? ECAddonConnectionState.ECSwitchingFocus : connState;
|
||||
// Because we create audio tracks by hand, there's more to connecting than
|
||||
// just what LiveKit does in room.connect, and we should continue to return
|
||||
// ConnectionState.Connecting for the entire duration of the doConnect promise
|
||||
return isSwitchingFocus
|
||||
? ECAddonConnectionState.ECSwitchingFocus
|
||||
: isInDoConnect
|
||||
? ConnectionState.Connecting
|
||||
: connState;
|
||||
}
|
||||
|
||||
@@ -20,10 +20,9 @@ import {
|
||||
ExternalE2EEKeyProvider,
|
||||
Room,
|
||||
RoomOptions,
|
||||
setLogLevel,
|
||||
} from "livekit-client";
|
||||
import { useLiveKitRoom } from "@livekit/components-react";
|
||||
import { useEffect, useMemo, useRef } from "react";
|
||||
import { useEffect, useMemo, useRef, useState } from "react";
|
||||
import E2EEWorker from "livekit-client/e2ee-worker?worker";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
|
||||
@@ -44,8 +43,6 @@ export type E2EEConfig = {
|
||||
sharedKey: string;
|
||||
};
|
||||
|
||||
setLogLevel("debug");
|
||||
|
||||
interface UseLivekitResult {
|
||||
livekitRoom?: Room;
|
||||
connState: ECConnectionState;
|
||||
@@ -98,6 +95,11 @@ export function useLiveKit(
|
||||
[e2eeOptions]
|
||||
);
|
||||
|
||||
// useECConnectionState creates and publishes an audio track by hand. To keep
|
||||
// this from racing with LiveKit's automatic creation of the audio track, we
|
||||
// block audio from being enabled until the connection is finished.
|
||||
const [blockAudio, setBlockAudio] = useState(true);
|
||||
|
||||
// We have to create the room manually here due to a bug inside
|
||||
// @livekit/components-react. JSON.stringify() is used in deps of a
|
||||
// useEffect() with an argument that references itself, if E2EE is enabled
|
||||
@@ -105,7 +107,7 @@ export function useLiveKit(
|
||||
const { room } = useLiveKitRoom({
|
||||
token: sfuConfig?.jwt,
|
||||
serverUrl: sfuConfig?.url,
|
||||
audio: initialMuteStates.current.audio.enabled,
|
||||
audio: initialMuteStates.current.audio.enabled && !blockAudio,
|
||||
video: initialMuteStates.current.video.enabled,
|
||||
room: roomWithoutProps,
|
||||
connect: false,
|
||||
@@ -120,6 +122,11 @@ export function useLiveKit(
|
||||
sfuConfig
|
||||
);
|
||||
|
||||
// Unblock audio once the connection is finished
|
||||
useEffect(() => {
|
||||
if (connectionState === ConnectionState.Connected) setBlockAudio(false);
|
||||
}, [connectionState, setBlockAudio]);
|
||||
|
||||
useEffect(() => {
|
||||
// Sync the requested mute states with LiveKit's mute states. We do it this
|
||||
// way around rather than using LiveKit as the source of truth, so that the
|
||||
|
||||
11
src/main.tsx
11
src/main.tsx
@@ -23,15 +23,22 @@ import "matrix-js-sdk/src/browser-index";
|
||||
import { StrictMode } from "react";
|
||||
import { createRoot } from "react-dom/client";
|
||||
import { createBrowserHistory } from "history";
|
||||
|
||||
import "./index.css";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
import {
|
||||
setLogExtension as setLKLogExtension,
|
||||
setLogLevel,
|
||||
} from "livekit-client";
|
||||
|
||||
import App from "./App";
|
||||
import { init as initRageshake } from "./settings/rageshake";
|
||||
import { Initializer } from "./initializer";
|
||||
|
||||
initRageshake();
|
||||
setLogLevel("debug");
|
||||
setLKLogExtension(global.mx_rage_logger.log);
|
||||
|
||||
console.info(`Element Call ${import.meta.env.VITE_APP_VERSION || "dev"}`);
|
||||
logger.info(`Element Call ${import.meta.env.VITE_APP_VERSION || "dev"}`);
|
||||
|
||||
const root = createRoot(document.getElementById("root")!);
|
||||
|
||||
|
||||
@@ -177,7 +177,7 @@ export async function initClient(
|
||||
try {
|
||||
await client.store.startup();
|
||||
} catch (error) {
|
||||
console.error(
|
||||
logger.error(
|
||||
"Error starting matrix client store. Falling back to memory store.",
|
||||
error
|
||||
);
|
||||
@@ -340,15 +340,35 @@ export async function createRoom(
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a URL to that will load Element Call with the given room
|
||||
* @param roomId of the room
|
||||
* @param password
|
||||
* @returns
|
||||
* Returns an absolute URL to that will load Element Call with the given room
|
||||
* @param roomId ID of the room
|
||||
* @param roomName Name of the room
|
||||
* @param password e2e key for the room
|
||||
*/
|
||||
export function getRoomUrl(roomId: string, password?: string): string {
|
||||
export function getAbsoluteRoomUrl(
|
||||
roomId: string,
|
||||
roomName?: string,
|
||||
password?: string
|
||||
): string {
|
||||
return `${window.location.protocol}//${
|
||||
window.location.host
|
||||
}/room/#?roomId=${roomId}${password ? "&" + PASSWORD_STRING + password : ""}`;
|
||||
}${getRelativeRoomUrl(roomId, roomName, password)}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a relative URL to that will load Element Call with the given room
|
||||
* @param roomId ID of the room
|
||||
* @param roomName Name of the room
|
||||
* @param password e2e key for the room
|
||||
*/
|
||||
export function getRelativeRoomUrl(
|
||||
roomId: string,
|
||||
roomName?: string,
|
||||
password?: string
|
||||
): string {
|
||||
return `/room/#${
|
||||
roomName ? "/" + roomAliasLocalpartFromRoomName(roomName) : ""
|
||||
}?roomId=${roomId}${password ? "&" + PASSWORD_STRING + password : ""}`;
|
||||
}
|
||||
|
||||
export function getAvatarUrl(
|
||||
|
||||
@@ -19,6 +19,7 @@ import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||
import { User, UserEvent } from "matrix-js-sdk/src/models/user";
|
||||
import { FileType } from "matrix-js-sdk/src/http-api";
|
||||
import { useState, useCallback, useEffect } from "react";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
|
||||
interface ProfileLoadState {
|
||||
success: boolean;
|
||||
@@ -127,7 +128,7 @@ export function useProfile(client: MatrixClient | undefined) {
|
||||
}));
|
||||
}
|
||||
} else {
|
||||
console.error("Client not initialized before calling saveProfile");
|
||||
logger.error("Client not initialized before calling saveProfile");
|
||||
}
|
||||
},
|
||||
[client]
|
||||
|
||||
@@ -18,10 +18,11 @@ import { FC, MouseEvent, useCallback, useMemo, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Button, Text } from "@vector-im/compound-web";
|
||||
import { ReactComponent as PopOutIcon } from "@vector-im/compound-design-tokens/icons/pop-out.svg";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
|
||||
import { Modal } from "../Modal";
|
||||
import { useRoomSharedKey } from "../e2ee/sharedKeyManagement";
|
||||
import { getRoomUrl } from "../matrix-utils";
|
||||
import { useIsRoomE2EE, useRoomSharedKey } from "../e2ee/sharedKeyManagement";
|
||||
import { getAbsoluteRoomUrl } from "../matrix-utils";
|
||||
import styles from "./AppSelectionModal.module.css";
|
||||
import { editFragmentQuery } from "../UrlParams";
|
||||
|
||||
@@ -43,12 +44,23 @@ export const AppSelectionModal: FC<Props> = ({ roomId }) => {
|
||||
);
|
||||
|
||||
const roomSharedKey = useRoomSharedKey(roomId ?? "");
|
||||
const roomIsEncrypted = useIsRoomE2EE(roomId ?? "");
|
||||
if (roomIsEncrypted && roomSharedKey === undefined) {
|
||||
logger.error(
|
||||
"Generating app redirect URL for encrypted room but don't have key available!"
|
||||
);
|
||||
}
|
||||
|
||||
const appUrl = useMemo(() => {
|
||||
// If the room ID is not known, fall back to the URL of the current page
|
||||
// Also, we don't really know the room name at this stage as we haven't
|
||||
// started a client and synced to get the room details. We could take the one
|
||||
// we got in our own URL and use that, but it's not a string that a human
|
||||
// ever sees so it's somewhat redundant. We just don't pass a name.
|
||||
const url = new URL(
|
||||
roomId === null
|
||||
? window.location.href
|
||||
: getRoomUrl(roomId, roomSharedKey ?? undefined)
|
||||
: getAbsoluteRoomUrl(roomId, undefined, roomSharedKey ?? undefined)
|
||||
);
|
||||
// Edit the URL to prevent the app selection prompt from appearing a second
|
||||
// time within the app, and to keep the user confined to the current room
|
||||
@@ -58,7 +70,7 @@ export const AppSelectionModal: FC<Props> = ({ roomId }) => {
|
||||
return params;
|
||||
});
|
||||
|
||||
const result = new URL("element://call");
|
||||
const result = new URL("io.element.call:/");
|
||||
result.searchParams.set("url", url.toString());
|
||||
return result.toString();
|
||||
}, [roomId, roomSharedKey]);
|
||||
|
||||
@@ -14,10 +14,13 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { ReactNode } from "react";
|
||||
import { ReactNode, useCallback } from "react";
|
||||
import { MatrixClient } from "matrix-js-sdk/src/client";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { MatrixRTCSession } from "matrix-js-sdk/src/matrixrtc/MatrixRTCSession";
|
||||
import { MatrixError } from "matrix-js-sdk";
|
||||
import { useHistory } from "react-router-dom";
|
||||
import { Heading, Link, Text } from "@vector-im/compound-web";
|
||||
|
||||
import { useLoadGroupCall } from "./useLoadGroupCall";
|
||||
import { ErrorView, FullScreenView } from "../FullScreenView";
|
||||
@@ -38,6 +41,15 @@ export function GroupCallLoader({
|
||||
const { t } = useTranslation();
|
||||
const groupCallState = useLoadGroupCall(client, roomIdOrAlias, viaServers);
|
||||
|
||||
const history = useHistory();
|
||||
const onHomeClick = useCallback(
|
||||
(ev: React.MouseEvent) => {
|
||||
ev.preventDefault();
|
||||
history.push("/");
|
||||
},
|
||||
[history]
|
||||
);
|
||||
|
||||
switch (groupCallState.kind) {
|
||||
case "loading":
|
||||
return (
|
||||
@@ -48,6 +60,24 @@ export function GroupCallLoader({
|
||||
case "loaded":
|
||||
return <>{children(groupCallState.rtcSession)}</>;
|
||||
case "failed":
|
||||
return <ErrorView error={groupCallState.error} />;
|
||||
if ((groupCallState.error as MatrixError).errcode === "M_NOT_FOUND") {
|
||||
return (
|
||||
<FullScreenView>
|
||||
<Heading>{t("Call not found")}</Heading>
|
||||
<Text>
|
||||
{t(
|
||||
"Calls are now end-to-end encrypted and need to be created from the home page. This helps make sure everyone's using the same encryption key."
|
||||
)}
|
||||
</Text>
|
||||
{/* XXX: A 'create it for me' button would be the obvious UX here. Two screens already have
|
||||
dupes of this flow, let's make a common component and put it here. */}
|
||||
<Link href="/" onClick={onHomeClick}>
|
||||
{t("Home")}
|
||||
</Link>
|
||||
</FullScreenView>
|
||||
);
|
||||
} else {
|
||||
return <ErrorView error={groupCallState.error} />;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,14 +17,16 @@ limitations under the License.
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import { useHistory } from "react-router-dom";
|
||||
import { MatrixClient } from "matrix-js-sdk/src/client";
|
||||
import { Room } from "livekit-client";
|
||||
import { Room, isE2EESupported } from "livekit-client";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
import { MatrixRTCSession } from "matrix-js-sdk/src/matrixrtc/MatrixRTCSession";
|
||||
import { JoinRule, RoomMember } from "matrix-js-sdk/src/matrix";
|
||||
import { Heading, Link, Text } from "@vector-im/compound-web";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import type { IWidgetApiRequest } from "matrix-widget-api";
|
||||
import { widget, ElementWidgetActions, JoinCallData } from "../widget";
|
||||
import { ErrorView } from "../FullScreenView";
|
||||
import { ErrorView, FullScreenView } from "../FullScreenView";
|
||||
import { LobbyView } from "./LobbyView";
|
||||
import { MatrixInfo } from "./VideoPreview";
|
||||
import { CallEndedView } from "./CallEndedView";
|
||||
@@ -284,6 +286,16 @@ export function GroupCallView({
|
||||
);
|
||||
const onShareClick = joinRule === JoinRule.Public ? onShareClickFn : null;
|
||||
|
||||
const onHomeClick = useCallback(
|
||||
(ev: React.MouseEvent) => {
|
||||
ev.preventDefault();
|
||||
history.push("/");
|
||||
},
|
||||
[history]
|
||||
);
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
if (e2eeEnabled && isRoomE2EE && !e2eeSharedKey) {
|
||||
return (
|
||||
<ErrorView
|
||||
@@ -294,15 +306,27 @@ export function GroupCallView({
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (!e2eeEnabled && isRoomE2EE) {
|
||||
} else if (!isE2EESupported() && isRoomE2EE) {
|
||||
return (
|
||||
<FullScreenView>
|
||||
<Heading>Incompatible Browser</Heading>
|
||||
<Text>
|
||||
{t(
|
||||
"Your web browser does not support media end-to-end encryption. Supported Browsers are Chrome, Safari, Firefox >=117"
|
||||
)}
|
||||
</Text>
|
||||
<Link href="/" onClick={onHomeClick}>
|
||||
{t("Home")}
|
||||
</Link>
|
||||
</FullScreenView>
|
||||
);
|
||||
} else if (!e2eeEnabled && isRoomE2EE) {
|
||||
return <ErrorView error={new Error("You need to enable E2EE to join.")} />;
|
||||
}
|
||||
|
||||
const shareModal = (
|
||||
<ShareModal
|
||||
roomId={rtcSession.room.roomId}
|
||||
room={rtcSession.room}
|
||||
open={shareModalOpen}
|
||||
onDismiss={onDismissShareModal}
|
||||
/>
|
||||
|
||||
@@ -344,19 +344,19 @@ export function InCallView({
|
||||
const buttons: JSX.Element[] = [];
|
||||
|
||||
buttons.push(
|
||||
<VideoButton
|
||||
key="2"
|
||||
muted={!muteStates.video.enabled}
|
||||
onPress={toggleCamera}
|
||||
disabled={muteStates.video.setEnabled === null}
|
||||
data-testid="incall_videomute"
|
||||
/>,
|
||||
<MicButton
|
||||
key="1"
|
||||
muted={!muteStates.audio.enabled}
|
||||
onPress={toggleMicrophone}
|
||||
disabled={muteStates.audio.setEnabled === null}
|
||||
data-testid="incall_mute"
|
||||
/>,
|
||||
<VideoButton
|
||||
key="2"
|
||||
muted={!muteStates.video.enabled}
|
||||
onPress={toggleCamera}
|
||||
disabled={muteStates.video.setEnabled === null}
|
||||
data-testid="incall_videomute"
|
||||
/>
|
||||
);
|
||||
|
||||
|
||||
@@ -24,8 +24,12 @@ limitations under the License.
|
||||
|
||||
.toggle input {
|
||||
appearance: none;
|
||||
/* Safari puts a margin on these, which is not removed via appearance: none */
|
||||
/*
|
||||
* Safari puts a margin on these, which is not removed via appearance: none
|
||||
* mobile safari also has them take up space in the DOM, so set width 0
|
||||
*/
|
||||
margin: 0;
|
||||
width: 0;
|
||||
outline: none !important;
|
||||
}
|
||||
|
||||
|
||||
@@ -129,16 +129,16 @@ export const LobbyView: FC<Props> = ({
|
||||
<div className={inCallStyles.footer}>
|
||||
{recentsButtonInFooter && recentsButton}
|
||||
<div className={inCallStyles.buttons}>
|
||||
<VideoButton
|
||||
muted={!muteStates.video.enabled}
|
||||
onPress={onVideoPress}
|
||||
disabled={muteStates.video.setEnabled === null}
|
||||
/>
|
||||
<MicButton
|
||||
muted={!muteStates.audio.enabled}
|
||||
onPress={onAudioPress}
|
||||
disabled={muteStates.audio.setEnabled === null}
|
||||
/>
|
||||
<VideoButton
|
||||
muted={!muteStates.video.enabled}
|
||||
onPress={onVideoPress}
|
||||
disabled={muteStates.video.setEnabled === null}
|
||||
/>
|
||||
<SettingsButton onPress={openSettings} />
|
||||
{!confineToRoom && <HangupButton onPress={onLeaveClick} />}
|
||||
</div>
|
||||
|
||||
@@ -17,6 +17,7 @@ limitations under the License.
|
||||
import { useCallback, useState } from "react";
|
||||
import { useLocation } from "react-router-dom";
|
||||
import { Trans, useTranslation } from "react-i18next";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
|
||||
import styles from "./RoomAuthView.module.css";
|
||||
import { Button } from "../button";
|
||||
@@ -46,7 +47,7 @@ export function RoomAuthView() {
|
||||
typeof dataForDisplayName === "string" ? dataForDisplayName : "";
|
||||
|
||||
registerPasswordlessUser(displayName).catch((error) => {
|
||||
console.error("Failed to register passwordless user", e);
|
||||
logger.error("Failed to register passwordless user", e);
|
||||
setLoading(false);
|
||||
setError(error);
|
||||
});
|
||||
|
||||
@@ -16,6 +16,7 @@ limitations under the License.
|
||||
|
||||
import { FC, useEffect, useState, useCallback, ReactNode } from "react";
|
||||
import { MatrixRTCSession } from "matrix-js-sdk/src/matrixrtc/MatrixRTCSession";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
|
||||
import { useClientLegacy } from "../ClientContext";
|
||||
import { ErrorView, LoadingView } from "../FullScreenView";
|
||||
@@ -37,7 +38,7 @@ export const RoomPage: FC = () => {
|
||||
|
||||
const roomIdOrAlias = roomId ?? roomAlias;
|
||||
if (!roomIdOrAlias) {
|
||||
console.error("No room specified");
|
||||
logger.error("No room specified");
|
||||
}
|
||||
|
||||
const [optInAnalytics, setOptInAnalytics] = useOptInAnalytics();
|
||||
|
||||
@@ -16,29 +16,34 @@ limitations under the License.
|
||||
|
||||
import { FC } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Room } from "matrix-js-sdk";
|
||||
|
||||
import { Modal } from "../Modal";
|
||||
import { CopyButton } from "../button";
|
||||
import { getRoomUrl } from "../matrix-utils";
|
||||
import { getAbsoluteRoomUrl } from "../matrix-utils";
|
||||
import styles from "./ShareModal.module.css";
|
||||
import { useRoomSharedKey } from "../e2ee/sharedKeyManagement";
|
||||
|
||||
interface Props {
|
||||
roomId: string;
|
||||
room: Room;
|
||||
open: boolean;
|
||||
onDismiss: () => void;
|
||||
}
|
||||
|
||||
export const ShareModal: FC<Props> = ({ roomId, open, onDismiss }) => {
|
||||
export const ShareModal: FC<Props> = ({ room, open, onDismiss }) => {
|
||||
const { t } = useTranslation();
|
||||
const roomSharedKey = useRoomSharedKey(roomId);
|
||||
const roomSharedKey = useRoomSharedKey(room.roomId);
|
||||
|
||||
return (
|
||||
<Modal title={t("Share this call")} open={open} onDismiss={onDismiss}>
|
||||
<p>{t("Copy and share this call link")}</p>
|
||||
<CopyButton
|
||||
className={styles.copyButton}
|
||||
value={getRoomUrl(roomId, roomSharedKey ?? undefined)}
|
||||
value={getAbsoluteRoomUrl(
|
||||
room.roomId,
|
||||
room.name,
|
||||
roomSharedKey ?? undefined
|
||||
)}
|
||||
data-testid="modal_inviteLink"
|
||||
/>
|
||||
</Modal>
|
||||
|
||||
@@ -24,6 +24,7 @@ import {
|
||||
Track,
|
||||
} from "livekit-client";
|
||||
import classNames from "classnames";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
|
||||
import { Avatar } from "../Avatar";
|
||||
import styles from "./VideoPreview.module.css";
|
||||
@@ -80,7 +81,7 @@ export const VideoPreview: FC<Props> = ({
|
||||
},
|
||||
},
|
||||
(error) => {
|
||||
console.error("Error while creating preview Tracks:", error);
|
||||
logger.error("Error while creating preview Tracks:", error);
|
||||
}
|
||||
);
|
||||
const videoTrack = useMemo(
|
||||
|
||||
@@ -20,14 +20,10 @@ import { ClientEvent, MatrixClient } from "matrix-js-sdk/src/client";
|
||||
import { SyncState } from "matrix-js-sdk/src/sync";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { MatrixRTCSession } from "matrix-js-sdk/src/matrixrtc/MatrixRTCSession";
|
||||
import { randomString } from "matrix-js-sdk/src/randomstring";
|
||||
|
||||
import type { Room } from "matrix-js-sdk/src/models/room";
|
||||
import type { GroupCall } from "matrix-js-sdk/src/webrtc/groupCall";
|
||||
import { setLocalStorageItem } from "../useLocalStorage";
|
||||
import { isLocalRoomId, createRoom, roomNameFromRoomId } from "../matrix-utils";
|
||||
import { useEnableE2EE } from "../settings/useSetting";
|
||||
import { getRoomSharedKeyLocalStorageKey } from "../e2ee/sharedKeyManagement";
|
||||
|
||||
export type GroupCallLoaded = {
|
||||
kind: "loaded";
|
||||
@@ -65,58 +61,42 @@ export const useLoadGroupCall = (
|
||||
|
||||
useEffect(() => {
|
||||
const fetchOrCreateRoom = async (): Promise<Room> => {
|
||||
try {
|
||||
let room: Room | null = null;
|
||||
if (roomIdOrAlias[0] === "#") {
|
||||
// We lowercase the localpart when we create the room, so we must lowercase
|
||||
// it here too (we just do the whole alias). We can't do the same to room IDs
|
||||
// though.
|
||||
const sanitisedIdOrAlias =
|
||||
roomIdOrAlias[0] === "#"
|
||||
? roomIdOrAlias.toLowerCase()
|
||||
: roomIdOrAlias;
|
||||
|
||||
const room = await client.joinRoom(sanitisedIdOrAlias, {
|
||||
viaServers,
|
||||
});
|
||||
logger.info(
|
||||
`Joined ${sanitisedIdOrAlias}, waiting room to be ready for group calls`
|
||||
// Also, we explicitly look up the room alias here. We previously just tried to
|
||||
// join anyway but the js-sdk recreates the room if you pass the alias for a
|
||||
// room you're already joined to (which it probably ought not to).
|
||||
const lookupResult = await client.getRoomIdForAlias(
|
||||
roomIdOrAlias.toLowerCase()
|
||||
);
|
||||
await client.waitUntilRoomReadyForGroupCalls(room.roomId);
|
||||
logger.info(`${sanitisedIdOrAlias}, is ready for group calls`);
|
||||
return room;
|
||||
} catch (error) {
|
||||
if (
|
||||
isLocalRoomId(roomIdOrAlias, client) &&
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
(error.errcode === "M_NOT_FOUND" ||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
(error.message &&
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
error.message.indexOf("Failed to fetch alias") !== -1))
|
||||
) {
|
||||
// The room doesn't exist, but we can create it
|
||||
const [, roomId] = await createRoom(
|
||||
client,
|
||||
roomNameFromRoomId(roomIdOrAlias),
|
||||
e2eeEnabled ?? false
|
||||
);
|
||||
|
||||
if (e2eeEnabled) {
|
||||
setLocalStorageItem(
|
||||
getRoomSharedKeyLocalStorageKey(roomId),
|
||||
randomString(32)
|
||||
);
|
||||
}
|
||||
|
||||
// likewise, wait for the room
|
||||
await client.waitUntilRoomReadyForGroupCalls(roomId);
|
||||
return client.getRoom(roomId)!;
|
||||
logger.info(`${roomIdOrAlias} resolved to ${lookupResult.room_id}`);
|
||||
room = client.getRoom(lookupResult.room_id);
|
||||
if (!room) {
|
||||
logger.info(`Room ${lookupResult.room_id} not found, joining.`);
|
||||
room = await client.joinRoom(lookupResult.room_id, {
|
||||
viaServers: lookupResult.servers,
|
||||
});
|
||||
} else {
|
||||
throw error;
|
||||
logger.info(
|
||||
`Already in room ${lookupResult.room_id}, not rejoining.`
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// room IDs we just try to join by their ID, which will not work in the
|
||||
// general case without providing some servers to join via. We could provide
|
||||
// our own server, but in practice that is implicit.
|
||||
room = await client.joinRoom(roomIdOrAlias);
|
||||
}
|
||||
|
||||
logger.info(
|
||||
`Joined ${roomIdOrAlias}, waiting room to be ready for group calls`
|
||||
);
|
||||
await client.waitUntilRoomReadyForGroupCalls(room.roomId);
|
||||
logger.info(`${roomIdOrAlias}, is ready for group calls`);
|
||||
return room;
|
||||
};
|
||||
|
||||
const fetchOrCreateGroupCall = async (): Promise<MatrixRTCSession> => {
|
||||
|
||||
@@ -50,15 +50,13 @@ const MAX_LOG_SIZE = 1024 * 1024 * 5; // 5 MB
|
||||
// we can batch the writes a little.
|
||||
const MAX_FLUSH_INTERVAL_MS = 2 * 1000;
|
||||
|
||||
// only descend this far into nested object trees
|
||||
const DEPTH_LIMIT = 3;
|
||||
|
||||
enum ConsoleLoggerEvent {
|
||||
Log = "log",
|
||||
}
|
||||
|
||||
type LogFunction = (
|
||||
...args: (Error | DOMException | object | string)[]
|
||||
) => void;
|
||||
type LogFunctionName = "log" | "info" | "warn" | "error";
|
||||
|
||||
// A class which monkey-patches the global console and stores log lines.
|
||||
|
||||
interface LogEntry {
|
||||
@@ -69,37 +67,11 @@ interface LogEntry {
|
||||
|
||||
class ConsoleLogger extends EventEmitter {
|
||||
private logs = "";
|
||||
private originalFunctions: { [key in LogFunctionName]?: LogFunction } = {};
|
||||
|
||||
public monkeyPatch(consoleObj: Console): void {
|
||||
// Monkey-patch console logging
|
||||
const consoleFunctionsToLevels = {
|
||||
log: "I",
|
||||
info: "I",
|
||||
warn: "W",
|
||||
error: "E",
|
||||
};
|
||||
|
||||
Object.entries(consoleFunctionsToLevels).forEach(([name, level]) => {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
const originalFn = consoleObj[name].bind(consoleObj);
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
this.originalFunctions[name] = originalFn;
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
consoleObj[name] = (...args) => {
|
||||
this.log(level, ...args);
|
||||
originalFn(...args);
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
public log(
|
||||
level: string,
|
||||
...args: (Error | DOMException | object | string)[]
|
||||
): void {
|
||||
public log = (
|
||||
level: LogLevel,
|
||||
...args: (Error | DOMException | object | string | undefined)[]
|
||||
): void => {
|
||||
// We don't know what locale the user may be running so use ISO strings
|
||||
const ts = new Date().toISOString();
|
||||
|
||||
@@ -129,7 +101,7 @@ class ConsoleLogger extends EventEmitter {
|
||||
this.logs += line;
|
||||
|
||||
this.emit(ConsoleLoggerEvent.Log);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the log lines to flush to disk and empties the internal log buffer
|
||||
@@ -510,7 +482,7 @@ declare global {
|
||||
*/
|
||||
export function init(): Promise<void> {
|
||||
global.mx_rage_logger = new ConsoleLogger();
|
||||
global.mx_rage_logger.monkeyPatch(window.console);
|
||||
setLogExtension(global.mx_rage_logger.log);
|
||||
|
||||
return tryInitStorage();
|
||||
}
|
||||
@@ -581,13 +553,61 @@ type StringifyReplacer = (
|
||||
// Injects `<$ cycle-trimmed $>` wherever it cuts a cyclical object relationship
|
||||
const getCircularReplacer = (): StringifyReplacer => {
|
||||
const seen = new WeakSet();
|
||||
return (key: string, value: unknown): unknown => {
|
||||
const depthMap = new WeakMap<object, number>();
|
||||
return function (this: unknown, key: string, value: unknown): unknown {
|
||||
if (typeof value === "object" && value !== null) {
|
||||
if (seen.has(value)) {
|
||||
return "<$ cycle-trimmed $>";
|
||||
}
|
||||
seen.add(value);
|
||||
|
||||
let depth = 0;
|
||||
if (this) {
|
||||
depth = depthMap.get(this) ?? 0;
|
||||
}
|
||||
depthMap.set(value, depth + 1);
|
||||
|
||||
if (depth > DEPTH_LIMIT) return "<$ object-pruned $>";
|
||||
}
|
||||
return value;
|
||||
};
|
||||
};
|
||||
|
||||
enum LogLevel {
|
||||
trace = 0,
|
||||
debug = 1,
|
||||
info = 2,
|
||||
warn = 3,
|
||||
error = 4,
|
||||
silent = 5,
|
||||
}
|
||||
|
||||
type LogExtensionFunc = (
|
||||
level: LogLevel,
|
||||
...rest: (Error | DOMException | object | string)[]
|
||||
) => void;
|
||||
type LogLevelString = keyof typeof LogLevel;
|
||||
|
||||
/**
|
||||
* This method borrowed from livekit (who also use loglevel and in turn essentially
|
||||
* took loglevel's example honouring log levels). Adds a loglevel logging extension
|
||||
* in the recommended way.
|
||||
*/
|
||||
export function setLogExtension(extension: LogExtensionFunc) {
|
||||
const originalFactory = logger.methodFactory;
|
||||
|
||||
logger.methodFactory = function (methodName, configLevel, loggerName) {
|
||||
const rawMethod = originalFactory(methodName, configLevel, loggerName);
|
||||
|
||||
const logLevel = LogLevel[methodName as LogLevelString];
|
||||
const needLog = logLevel >= configLevel && logLevel < LogLevel.silent;
|
||||
|
||||
return (...args) => {
|
||||
rawMethod.apply(this, args);
|
||||
if (needLog) {
|
||||
extension(logLevel, ...args);
|
||||
}
|
||||
};
|
||||
};
|
||||
logger.setLevel(logger.getLevel()); // Be sure to call setLevel method in order to apply plugin
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ import {
|
||||
import pako from "pako";
|
||||
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||
import { ClientEvent } from "matrix-js-sdk/src/client";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
|
||||
import { getLogsForReport } from "./rageshake";
|
||||
import { useClient } from "../ClientContext";
|
||||
@@ -296,7 +297,7 @@ export function useSubmitRageshake(): {
|
||||
setState({ sending: false, sent: true, error: undefined });
|
||||
} catch (error) {
|
||||
setState({ sending: false, sent: false, error: error as Error });
|
||||
console.error(error);
|
||||
logger.error(error);
|
||||
}
|
||||
},
|
||||
[client, inspectorState, sending]
|
||||
|
||||
@@ -38,6 +38,7 @@ import {
|
||||
} from "@react-spring/web";
|
||||
import useMeasure from "react-use-measure";
|
||||
import { ResizeObserver as JuggleResizeObserver } from "@juggle/resize-observer";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
|
||||
import styles from "./VideoGrid.module.css";
|
||||
import { Layout } from "../room/LayoutToggle";
|
||||
@@ -298,7 +299,7 @@ function getFreedomLayoutTilePositions(
|
||||
}
|
||||
|
||||
if (tileCount > 12) {
|
||||
console.warn("Over 12 tiles is not currently supported");
|
||||
logger.warn("Over 12 tiles is not currently supported");
|
||||
}
|
||||
|
||||
const { layoutDirection, itemGridRatio } = getGridLayout(
|
||||
|
||||
Reference in New Issue
Block a user