docs(realtime): record the design decisions behind the realtime layer
Eleven resolved decision tickets plus two research notes, produced with /wayfinder before any code was written. They live next to the code they explain rather than in the issue tracker: they are reasoning documents, not open work, and in a tracker they would drift from the code with no diff and no review. The value is in the rejected alternatives. Why a counter under a row lock and not BIGSERIAL, why an event log and not a column on messages, why resync is a 200 with a discriminated body and not a 4xx, why the fan-out trait was deleted again, why voice is peer-to-peer with a hard six-person cap and no speculative SFU seam, and why Linux needs a native media stack while the other three platforms do not. Two entries record where the spec was wrong and got corrected: created_at was never a safe sort key, and an unread count cannot be derived without fetching because seq counts events rather than messages. Open work is tracked as issues, not here.
This commit is contained in:
742
docs/realtime/research/01-webrtc-tauri-mobile.md
Normal file
742
docs/realtime/research/01-webrtc-tauri-mobile.md
Normal file
@@ -0,0 +1,742 @@
|
||||
# Recherche-Ergebnis: WebRTC im Tauri-WebView (Mobile + Desktop)
|
||||
|
||||
Ticket: [`../issues/01-research-webrtc-tauri-mobile.md`](../issues/01-research-webrtc-tauri-mobile.md)
|
||||
Map: [`../map.md`](../map.md)
|
||||
Stand: 14.08.2026
|
||||
Methodik: Primärquellen (Apple/WebKit, AOSP, Chromium, Microsoft Learn, W3C/IETF, wry-/Tauri-Quellcode
|
||||
auf den tatsächlich aufgelösten Versions-Tags). Blogposts nur als Hinweisgeber, jeweils mit Datum markiert.
|
||||
|
||||
---
|
||||
|
||||
## 1. Kernantwort
|
||||
|
||||
| Plattform | Antwort | Aufwand | Blockierender Punkt |
|
||||
|---|---|---|---|
|
||||
| **iOS** (WKWebView) | **Ja** | 1 Datei | `NSMicrophoneUsageDescription` in `src-tauri/Info.ios.plist` |
|
||||
| **Android** (System WebView) | **Ja** | 2 Manifest-Zeilen | `RECORD_AUDIO` **und** `MODIFY_AUDIO_SETTINGS` |
|
||||
| **Windows** (WebView2) | **Ja** | keiner | — (funktioniert out of the box, mit WebView2-eigenem Prompt) |
|
||||
| **Linux** (WebKitGTK 4.1) | **Nein** | hoch / teils unlösbar | Drei unabhängige Gates, siehe §5 |
|
||||
|
||||
**Fazit für die Map:** Die im Council-Verdikt als Risiko 2 geführte „Mobile-WebRTC-Unbekannte" ist
|
||||
**aufgelöst — positiv**. Android und iOS tragen WebRTC im WebView. Das Voice-Signaling-Design ist für
|
||||
die Mobile-Hälfte der Zielplattformen nicht wertlos. Ticket 04 (*Voice-Topologie*) ist damit entblockt.
|
||||
|
||||
**Die neue schlechte Nachricht liegt woanders:** Nicht Mobile ist der Problemfall, sondern **Linux**.
|
||||
Dort ist WebRTC in WebKitGTK weder einkompiliert noch zur Laufzeit aktivierbar noch berechtigt.
|
||||
Das ist keine Kleinigkeit für ein Projekt, dessen Zielgruppe Selbsthoster sind.
|
||||
|
||||
---
|
||||
|
||||
## 2. Ausgangslage im Repo (Versionsanker)
|
||||
|
||||
Diese Recherche gilt gegen die Versionen, die `rustification-client` heute tatsächlich auflöst:
|
||||
|
||||
| Komponente | Version | Herkunft |
|
||||
|---|---|---|
|
||||
| `@tauri-apps/cli` | **2.11.4** | `package-lock.json` |
|
||||
| `tauri` (Rust) | `"2"` → aktuell **2.11.5** (01.07.2026) | `src-tauri/Cargo.toml`, crates.io |
|
||||
| `tauri-runtime-wry` | **2.11.4** (30.06.2026) | crates.io |
|
||||
| → `wry` | Anforderung `^0.55.0` → **0.55.1** (04.05.2026) | crates.io Dependency-Auflösung |
|
||||
|
||||
Das ist der wichtigste Kontext des ganzen Dokuments: **wry 0.56.0 (30.07.2026) hat die Permission-Lage
|
||||
grundlegend geändert**, aber `tauri-runtime-wry 2.11.4` fordert `^0.55.0` und kann 0.56 nicht ziehen
|
||||
(SemVer bei `0.x`: `>=0.55.0, <0.56.0`). Alles unten Beschriebene ist gegen **wry 0.55.1** verifiziert.
|
||||
|
||||
Weitere relevante Repo-Fakten:
|
||||
- `src-tauri/gen/android` und `src-tauri/gen/apple` existieren **noch nicht** — Mobile ist nie
|
||||
initialisiert worden. Es gibt also noch nichts zu migrieren.
|
||||
- `app.security.csp` ist `null`. Tauri v2 hat **keine Default-CSP**
|
||||
([v2.tauri.app/security/csp](https://v2.tauri.app/security/csp/): *„The CSP protection is only enabled
|
||||
if set on the Tauri configuration file."*). Für WebRTC ist das irrelevant (siehe §7.5), aber es ist gut
|
||||
zu wissen, dass hier nichts im Weg steht.
|
||||
- Es existiert **kein** WebRTC- oder WebSocket-Code im Client. Grüne Wiese.
|
||||
|
||||
---
|
||||
|
||||
## 3. iOS — **Ja**
|
||||
|
||||
### 3.1 Versionsuntergrenze
|
||||
|
||||
`getUserMedia` ist in `WKWebView` (nicht nur Safari) seit **iOS 14.3** verfügbar.
|
||||
Primärquelle: WebKit-Blog, *MediaRecorder API*, Youenn Fablet, **23.11.2020**
|
||||
([webkit.org/blog/11353](https://webkit.org/blog/11353/mediarecorder-api/)), wörtlich:
|
||||
|
||||
> „Speaking of Safari in latest iOS 14.3 beta and local capture, `navigator.mediaDevices.getUserMedia`
|
||||
> can now be exposed to WKWebView applications. `navigator.mediaDevices.getUserMedia` is automatically
|
||||
> exposed **if the embedding application is able to natively capture either audio or video**."
|
||||
|
||||
Der Satzteil „able to natively capture" ist die eigentliche Bedingung: Er bedeutet **die
|
||||
Usage-Description-Keys sind in der Info.plist vorhanden**. Kein Entitlement, keine private API.
|
||||
|
||||
Tauris eigener Default `bundle.iOS.minimumSystemVersion` ist `"14.0"` — für WebRTC muss man das
|
||||
faktisch auf **14.3** oder höher lesen. Praktisch relevanter Floor ist ohnehin **iOS 15.0**, weil
|
||||
erst dort der `WKUIDelegate`-Callback existiert, den wry registriert (siehe 3.3).
|
||||
|
||||
### 3.2 Secure Context — passt, ohne Tricks
|
||||
|
||||
`getUserMedia` verlangt einen Secure Context. Tauri liefert das Frontend auf iOS (wie macOS und Linux)
|
||||
unter dem Custom-Scheme **`tauri://localhost`** aus (`crates/tauri/src/manager/mod.rs`,
|
||||
`tauri_protocol_url`). Ein Custom-Scheme allein ist *nicht* vertrauenswürdig — aber WebKit prüft den
|
||||
**Host unabhängig vom Schema** (`Source/WebCore/page/SecurityOrigin.cpp`):
|
||||
|
||||
```cpp
|
||||
if (LegacySchemeRegistry::shouldTreatURLSchemeAsSecure(protocol)) return true;
|
||||
if (SecurityOrigin::isLocalHostOrLoopbackIPAddress(host)) return true;
|
||||
```
|
||||
|
||||
Host ist `localhost` ⇒ potentially trustworthy ⇒ Secure Context. Der aus Blogposts bekannte
|
||||
`_registerURLSchemeAsSecure`-Hack (private API) ist **nicht nötig**.
|
||||
|
||||
### 3.3 Was wry bereits erledigt — verifiziert im Quellcode
|
||||
|
||||
wry registriert einen `WKUIDelegate` und darin den Media-Capture-Callback.
|
||||
Quelle: `src/wkwebview/class/wry_web_view_ui_delegate.rs` am Tag **`wry-v0.55.1`**:
|
||||
|
||||
```rust
|
||||
#[unsafe(method(webView:requestMediaCapturePermissionForOrigin:initiatedByFrame:type:decisionHandler:))]
|
||||
fn request_media_capture_permission(
|
||||
&self, _webview: &WryWebView, _origin: &WKSecurityOrigin, _frame: &WKFrameInfo,
|
||||
_capture_type: WKMediaCaptureType,
|
||||
decision_handler: &Block<dyn Fn(WKPermissionDecision)>,
|
||||
) {
|
||||
(*decision_handler).call((WKPermissionDecision::Grant,));
|
||||
}
|
||||
```
|
||||
|
||||
Drei Beobachtungen, alle wichtig:
|
||||
|
||||
1. **Kein `#[cfg(target_os = "macos")]`.** Die Methode wird auf iOS mitkompiliert. (In `dev`/0.56 steht
|
||||
dort `#[cfg(not(macos_12_unavailable))]` — ein Flag, das der `build.rs` nur unter
|
||||
`target_os = "macos"` setzt; auch dort bleibt iOS also drin.)
|
||||
2. **In 0.55.1 wird bedingungslos `Grant` zurückgegeben.** Es gibt in dieser Version gar keinen
|
||||
`permission_handler` — `WryWebViewUIDelegateIvars` hat das Feld schlicht nicht. Der WebKit-Prompt
|
||||
entfällt damit; es bleibt genau **ein** Prompt, nämlich der des Betriebssystems (TCC).
|
||||
3. Apple dokumentiert den Default ohne Delegate-Implementierung als `WKPermissionDecision.prompt` —
|
||||
also **nicht** Deny ([Apple Developer](https://developer.apple.com/documentation/webkit/wkuidelegate/webview(_:requestmediacapturepermissionfor:initiatedbyframe:type:decisionhandler:)),
|
||||
verfügbar ab iOS 15.0 / macOS 12.0). wry macht daraus ein Grant. Sicherheitsrelevant, siehe §7.6.
|
||||
|
||||
`allowsInlineMediaPlayback` setzt wry auf iOS hart auf `true` — relevant, sobald der Remote-Stream in
|
||||
ein `<audio>`/`<video>`-Element gehängt wird.
|
||||
|
||||
### 3.4 Der eine nötige Konfigurationsschritt
|
||||
|
||||
`src-tauri/Info.ios.plist` (oder `src-tauri/Info.plist`, siehe unten) anlegen:
|
||||
|
||||
```xml
|
||||
<key>NSMicrophoneUsageDescription</key>
|
||||
<string>Rustification braucht Mikrofonzugriff für Sprachanrufe.</string>
|
||||
```
|
||||
|
||||
Fehlt der Key, gilt die App als nicht „able to natively capture" ⇒ `navigator.mediaDevices` ist
|
||||
**gar nicht erst definiert** (das klassische `navigator.mediaDevices is undefined`), und ein nativer
|
||||
Capture-Versuch bringt iOS zum harten Absturz.
|
||||
|
||||
Tauri merged die Datei mit den generierten Werten. Merge-Reihenfolge auf iOS (später gewinnt), laut
|
||||
offenem Issue [tauri#13068](https://github.com/tauri-apps/tauri/issues/13068) (24.03.2025, *Unify/Document
|
||||
handling of Info.plist files*): XcodeGen-`project.yml` → `Info.plist` aus `TAURI_DIR` →
|
||||
`Info.ios.plist` aus `TAURI_DIR` → generierte Version-Strings. Zusätzlich existiert der Config-Key
|
||||
`bundle.iOS.infoPlist` („Path to an Info.plist file for merging with defaults").
|
||||
|
||||
**`Info.ios.plist` ist der robuste Weg**, weil er `gen/apple` wegwerfbar hält.
|
||||
|
||||
Belastbares Signal, dass genau dieser Pfad vorgesehen ist: Tauris eigenes API-Beispiel
|
||||
[`examples/api/src-tauri/Info.plist`](https://github.com/tauri-apps/tauri/blob/dev/examples/api/src-tauri/Info.plist)
|
||||
enthält wörtlich `NSMicrophoneUsageDescription` = *„Request microphone access for WebRTC"*.
|
||||
|
||||
### 3.5 Bekanntes offenes Issue
|
||||
|
||||
[tauri#12068 — *iOS Microphone Access*](https://github.com/tauri-apps/tauri/issues/12068), geöffnet
|
||||
**28.12.2024**, **noch offen**, Label `needs triage`, keine Maintainer-Antwort. Symptom: „it failed to
|
||||
access the mic nor enumerate media devices. The mic permission is shown randomly once."
|
||||
|
||||
Der Report enthält weder `tauri info` noch die iOS-Version noch die Info.plist. **Wahrscheinlichste
|
||||
Erklärung ist der fehlende `NSMicrophoneUsageDescription`-Key** — das Symptombild passt exakt. Das ist
|
||||
aber eine Hypothese, kein Befund. Siehe Experiment E1 in §8.
|
||||
|
||||
---
|
||||
|
||||
## 4. Android — **Ja**
|
||||
|
||||
### 4.1 Versionsuntergrenze
|
||||
|
||||
WebRTC ist im Android System WebView seit **Android 5.0 Lollipop / API 21 / WebView 37** vorhanden.
|
||||
Primärquelle: [developer.android.com/about/versions/android-5.0](https://developer.android.com/about/versions/android-5.0):
|
||||
|
||||
> „Android 5.0 updates the `WebView` implementation to **Chromium M37** … This release brings support
|
||||
> for the following open standards: WebAudio, WebGL, **WebRTC**."
|
||||
> „This release introduces the `PermissionRequest` class … **Your app must have the appropriate Android
|
||||
> permissions for these resources in order to grant the permissions to the WebView.**"
|
||||
|
||||
Tauris Default `bundle.android.minSdkVersion` ist **24** — deutlich darüber. wrys Runtime-Permission-Pfad
|
||||
ist auf `SDK_INT >= M` (API 23) gegated. Praktisch irrelevant: Das System WebView ist seit Lollipop über
|
||||
den Play Store aktualisierbar, jedes reale Gerät fährt ein aktuelles Chromium.
|
||||
|
||||
### 4.2 Der WebView-Gate: `onPermissionRequest`
|
||||
|
||||
Ohne Override **verweigert** der `WebChromeClient`. AOSP,
|
||||
[`core/java/android/webkit/WebChromeClient.java`](https://android.googlesource.com/platform/frameworks/base/+/refs/heads/main/core/java/android/webkit/WebChromeClient.java):
|
||||
|
||||
```java
|
||||
/** ... If this method isn't overridden, the permission is denied. */
|
||||
public void onPermissionRequest(PermissionRequest request) {
|
||||
request.deny();
|
||||
}
|
||||
```
|
||||
|
||||
**wry überschreibt es.** Verifiziert in `src/android/kotlin/RustWebChromeClient.kt` am Tag
|
||||
**`wry-v0.55.1`**:
|
||||
|
||||
```kotlin
|
||||
override fun onPermissionRequest(request: PermissionRequest) {
|
||||
val isRequestPermissionRequired = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
|
||||
val permissionList: MutableList<String> = ArrayList()
|
||||
if (listOf(*request.resources).contains("android.webkit.resource.VIDEO_CAPTURE")) {
|
||||
permissionList.add(Manifest.permission.CAMERA)
|
||||
}
|
||||
if (listOf(*request.resources).contains("android.webkit.resource.AUDIO_CAPTURE")) {
|
||||
permissionList.add(Manifest.permission.MODIFY_AUDIO_SETTINGS)
|
||||
permissionList.add(Manifest.permission.RECORD_AUDIO)
|
||||
}
|
||||
if (permissionList.isNotEmpty() && isRequestPermissionRequired) {
|
||||
... permissionLauncher.launch(permissions) // OS-Prompt
|
||||
// Callback: alle granted -> request.grant(request.resources), sonst request.deny()
|
||||
} else {
|
||||
request.grant(request.resources)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Die App muss also **nichts** abfangen. wry löst den Runtime-Prompt selbst aus und reicht das Ergebnis
|
||||
an den WebView durch. Der Callback kollabiert das Ergebnis-Map mit „alle müssen `true` sein".
|
||||
|
||||
Diese Methode existiert bei wry seit **0.21.0 (05.09.2022, PR #685)** — sie ist in *jedem* Tauri-v2-Release
|
||||
vorhanden.
|
||||
|
||||
### 4.3 Die zwei nötigen Manifest-Zeilen
|
||||
|
||||
Tauris generiertes Manifest enthält **nur `INTERNET`**. Verifiziert im Template
|
||||
[`crates/tauri-cli/templates/mobile/android/app/src/main/AndroidManifest.xml`](https://github.com/tauri-apps/tauri/blob/dev/crates/tauri-cli/templates/mobile/android/app/src/main/AndroidManifest.xml):
|
||||
|
||||
```xml
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
```
|
||||
|
||||
In `src-tauri/gen/android/app/src/main/AndroidManifest.xml` ergänzen:
|
||||
|
||||
```xml
|
||||
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
||||
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
|
||||
<uses-feature android:name="android.hardware.microphone" android:required="false" />
|
||||
```
|
||||
|
||||
**`MODIFY_AUDIO_SETTINGS` ist nicht optional**, obwohl Chromium selbst es nicht braucht. Begründung aus
|
||||
dem Code oben: wry fordert beide Permissions in *einem* `RequestMultiplePermissions`-Aufruf an und
|
||||
verlangt, dass *alle* zurückgemeldeten Werte `true` sind. Ein nicht im Manifest deklariertes Recht
|
||||
liefert dort `false` — also `request.deny()` und `NotAllowedError`, **obwohl der Nutzer den Mikrofon-Dialog
|
||||
mit „Zulassen" bestätigt hat**. `MODIFY_AUDIO_SETTINGS` hat Protection-Level `normal`, erzeugt also
|
||||
keinen zusätzlichen Dialog; es muss nur deklariert sein.
|
||||
|
||||
Das ist die wahrscheinlichste Ursache von
|
||||
[tauri#10846](https://github.com/tauri-apps/tauri/issues/10846) (31.08.2024, *Audio Permission Not
|
||||
Working in Tauri Android App Despite Manifest Declaration*, `needs triage`) — dort wurde ausschließlich
|
||||
`RECORD_AUDIO` deklariert. Deckt sich mit der Community-Diagnose in
|
||||
[Discussion #12732](https://github.com/orgs/tauri-apps/discussions/12732) (Feb–Apr 2025, getestet gegen
|
||||
Tauri 2.10.3): *„Critically, MODIFY_AUDIO_SETTINGS to prevent NotReadableError."*
|
||||
**Status: durch Code-Lesung sehr gut gestützt, nicht empirisch verifiziert** — siehe Experiment E2.
|
||||
|
||||
Es gibt **keinen** `tauri.conf.json`-Key für Android-Manifest-Permissions und **kein** offizielles
|
||||
Tauri-Plugin für generische Runtime-Permissions (die offizielle Plugin-Liste auf
|
||||
[v2.tauri.app/plugin](https://v2.tauri.app/plugin/) führt 30 Plugins, keines davon Mikrofon oder
|
||||
Permissions). Der dokumentierte Weg ist Hand-Editieren des generierten Manifests.
|
||||
|
||||
### 4.4 `gen/android` gehört ins Git
|
||||
|
||||
Das Template-`.gitignore` schließt ausschließlich Build-Artefakte aus (`build`, `.gradle`,
|
||||
`local.properties`, `/.tauri`, `/tauri.settings.gradle`, `app/src/main/**/generated`,
|
||||
`app/src/main/jniLibs/**/*.so`, `app/src/main/assets/tauri.conf.json`). `AndroidManifest.xml` und
|
||||
`MainActivity.kt` sind getrackte Quelldateien ohne `THIS IS AN AUTOGENERATED FILE`-Header.
|
||||
|
||||
Maintainer FabianLars, [Discussion #8323](https://github.com/orgs/tauri-apps/discussions/8323) (01.12.2023):
|
||||
|
||||
> „Generally yes. it has .gitignore folders inside that ignores the build artifacts etc. The gen folder
|
||||
> also contains the normal android studio / xcode projects which you can modify if you want which would
|
||||
> require you to commit it."
|
||||
|
||||
`tauri android build`/`dev` überschreibt die Datei nicht; **`tauri android init` rendert die Templates
|
||||
neu** und ist damit als destruktiv zu behandeln. Siehe Experiment E3.
|
||||
|
||||
### 4.5 Secure Context
|
||||
|
||||
Tauri liefert auf Android und Windows unter **`http://tauri.localhost`** aus.
|
||||
Verifiziert in `crates/tauri-utils/src/config.rs`:
|
||||
|
||||
```rust
|
||||
/// Sets whether the custom protocols should use `https://<scheme>.localhost` instead of the default
|
||||
/// `http://<scheme>.localhost` on Windows and Android. Defaults to `false`.
|
||||
pub use_https_scheme: bool,
|
||||
```
|
||||
und im `Default`-Impl: `use_https_scheme: false`.
|
||||
|
||||
Chromium behandelt `*.localhost` auch über plain http als vertrauenswürdig.
|
||||
[`net/base/url_util.cc`](https://chromium.googlesource.com/chromium/src/+/refs/heads/main/net/base/url_util.cc):
|
||||
|
||||
```cpp
|
||||
bool IsNormalizedLocalhostTLD(std::string_view host) {
|
||||
return base::EndsWith(host, ".localhost", base::CompareCase::INSENSITIVE_ASCII);
|
||||
}
|
||||
```
|
||||
|
||||
Deckt sich mit W3C [Secure Contexts](https://w3c.github.io/webappsec-secure-contexts/), Schritt 5:
|
||||
*„origin's host ends with `.localhost` or `.localhost.`"*. Das System WebView ist Chromium und linkt
|
||||
denselben `//services/network`-Code.
|
||||
|
||||
**Gegenstimme, die ich für falsch halte:** [wry#1709](https://github.com/tauri-apps/wry/issues/1709) /
|
||||
[#1710](https://github.com/tauri-apps/wry/issues/1710) (01.04.2026) behaupten, Android WebView halte nur
|
||||
exakt `http://localhost`, `https://` und `file://` für sicher. Beide Issues stammen von einem
|
||||
Nicht-Maintainer, #1709 hat null Antworten, und die Behauptung wird ohne Chromium-Quelle aufgestellt —
|
||||
im direkten Widerspruch zum Code oben. Die dort angeführten Belege (SharedWorker, Service Worker) haben
|
||||
andere, bekannte Ursachen. Trotzdem: Experiment E4 kostet zehn Sekunden.
|
||||
|
||||
**Risikoloser Hedge:** `useHttpsScheme: true` unter `app.windows[]`. Nachteile laut Tauri-Doc-Kommentar:
|
||||
kein Mixed Content mehr (`http://`-Fetches brechen), und **die IndexedDB-/Cookie-/localStorage-Partition
|
||||
wandert**. Also vor dem ersten Release entscheiden, nicht danach.
|
||||
|
||||
---
|
||||
|
||||
## 5. Linux (WebKitGTK 4.1) — **Nein**
|
||||
|
||||
Tauri v2 verlangt auf Linux `webkit2gtk-4.1` (`libwebkit2gtk-4.1-dev` / `webkit2gtk-4.1` /
|
||||
`webkit2gtk4.1-devel`, [v2.tauri.app/start/prerequisites](https://v2.tauri.app/start/prerequisites/)).
|
||||
Dort scheitert WebRTC an **drei unabhängigen Gates**, die alle passiert werden müssen.
|
||||
|
||||
### Gate 1 — Compile-Time: `ENABLE_WEB_RTC` ist standardmäßig AUS
|
||||
|
||||
[`Source/cmake/OptionsGTK.cmake`](https://github.com/WebKit/WebKit/blob/main/Source/cmake/OptionsGTK.cmake):
|
||||
|
||||
```cmake
|
||||
WEBKIT_OPTION_DEFAULT_PORT_VALUE(ENABLE_MEDIA_STREAM PRIVATE ON)
|
||||
WEBKIT_OPTION_DEFAULT_PORT_VALUE(ENABLE_WEB_RTC PRIVATE ${ENABLE_EXPERIMENTAL_FEATURES})
|
||||
```
|
||||
|
||||
`Source/cmake/WebKitFeatures.cmake`:
|
||||
|
||||
```cmake
|
||||
option(ENABLE_EXPERIMENTAL_FEATURES "Enable experimental features" OFF)
|
||||
```
|
||||
|
||||
⇒ `RTCPeerConnection` ist im JS-Global schlicht **nicht vorhanden**, sofern der Packager nicht explizit
|
||||
`-DENABLE_WEB_RTC=ON` setzt. `ENABLE_MEDIA_STREAM` ist zwar an (also `getUserMedia` grundsätzlich da),
|
||||
aber ohne `ENABLE_WEB_RTC` nützt das nichts.
|
||||
|
||||
Verifizierter Distro-Stand:
|
||||
- **Debian/Ubuntu** `webkit2gtk` — `debian/rules` enthält weder `ENABLE_WEB_RTC` noch
|
||||
`ENABLE_EXPERIMENTAL_FEATURES`. ⇒ **AUS.** ([sources.debian.org](https://sources.debian.org/src/webkit2gtk/latest/debian/rules/))
|
||||
- **Alpine** `community/webkit2gtk-4.1` APKBUILD — dito. ⇒ **AUS.**
|
||||
- **Arch / Fedora** — nicht verifiziert; beide Dist-Git-Hosts sind hinter einer Anubis-Bot-Schranke
|
||||
(Experiment E5). Schwaches Indiz gegen WebRTC bei Arch: `libnice` taucht in den Dependencies nicht auf.
|
||||
|
||||
Korroborierend: [tauri#13143](https://github.com/tauri-apps/tauri/issues/13143) *„The current browser
|
||||
does not support WebRTC"* auf Ubuntu 22.04 mit WebKit2GTK 2.48.0 — geschlossen als *not planned*, Label
|
||||
`status: upstream` (05.04.2025).
|
||||
|
||||
Historie: Der GstWebRTC-Backend kam mit WebKitGTK **2.37.1** („Add initial implementation of WebRTC using
|
||||
GstWebRTC if GStreamer 1.20 is available"). Es gibt **keinen** NEWS-Eintrag, der ein
|
||||
Default-On dokumentiert.
|
||||
|
||||
### Gate 2 — Runtime-Setting: `enable-webrtc` ist standardmäßig FALSE
|
||||
|
||||
[`WebKitSettings:enable-webrtc`](https://webkitgtk.org/reference/webkit2gtk/stable/property.Settings.enable-webrtc.html):
|
||||
|
||||
> „Enable WebRTC support for loaded pages. Enabling this setting implies that
|
||||
> `WebKitSettings:enable-media-stream` will be enabled as well."
|
||||
> **Default value: FALSE. Available since: 2.38.**
|
||||
|
||||
wry setzt es **nie** — weder in 0.55.1 noch in `dev`. In `set_webview_settings`
|
||||
(`src/webkitgtk/mod.rs`) stehen `set_enable_webgl`, `set_enable_webaudio`, `set_enable_page_cache` u. a.,
|
||||
aber kein `set_enable_webrtc` und kein `set_enable_media_stream`. Tauri exponiert die WebKitSettings
|
||||
auch nicht ([tauri#8989](https://github.com/tauri-apps/tauri/issues/8989), *[feat] expose control over
|
||||
webkit settings*, offen).
|
||||
|
||||
### Gate 3 — Permission: Default ist DENY
|
||||
|
||||
`WebKitWebView`s Default-Class-Handler für `permission-request`
|
||||
(`Source/WebKit/UIProcess/API/glib/WebKitWebView.cpp`) verweigert alles außer Pointer-Lock. Die Doku ist
|
||||
explizit ([`WebKitUserMediaPermissionRequest`](https://webkitgtk.org/reference/webkit2gtk/stable/class.UserMediaPermissionRequest.html)):
|
||||
|
||||
> „When a `WebKitUserMediaPermissionRequest` is not handled by the user, **it is denied by default**."
|
||||
|
||||
wry **0.55.1** verbindet `connect_permission_request` **überhaupt nicht** — im ganzen
|
||||
`src/webkitgtk/mod.rs` kommt der String nicht vor. Das erklärt
|
||||
[tauri#12547](https://github.com/tauri-apps/tauri/issues/12547) („access denied, no dialog",
|
||||
geschlossen *not planned*, 28.01.2025).
|
||||
|
||||
wry **0.56+** verbindet ihn — aber nur, wenn die App einen Handler übergibt:
|
||||
|
||||
```rust
|
||||
// Permission handler
|
||||
if let Some(permission_handler) = attributes.permission_handler.take() {
|
||||
webview.connect_permission_request(move |_webview, request| {
|
||||
if let Some(media_request) = request.downcast_ref::<UserMediaPermissionRequest>() { ... }
|
||||
```
|
||||
|
||||
Und docs.rs zu `PermissionResponse::Default` ist unmissverständlich: *„Windows / macOS / Android:
|
||||
continue the platform or browser permission flow. **Linux: The default behavior is `Deny`.**"*
|
||||
|
||||
### Der Weg, der theoretisch bliebe
|
||||
|
||||
`WebviewWindow::with_webview` gibt auf Linux Zugriff auf die rohe `webkit2gtk::WebView`
|
||||
([docs.rs/tauri](https://docs.rs/tauri/latest/tauri/webview/struct.WebviewWindow.html)). Damit ließen sich
|
||||
Gate 2 (`settings.set_enable_webrtc(true)`, Binding-Feature `v2_38`) und Gate 3
|
||||
(`connect_permission_request`) aus dem App-Code heraus schließen — ohne wry oder Tauri zu patchen.
|
||||
|
||||
**Gate 1 bleibt trotzdem.** Ist WebRTC nicht einkompiliert, hilft kein Setting. Und Gate 1 hängt am
|
||||
Distro-Paket des Nutzers, nicht an unserem Code.
|
||||
|
||||
### Konsequenz für die Map
|
||||
|
||||
Die harte Deployment-Regel („eine Binary + Postgres") betrifft den *Server*. Für den *Client* auf Linux
|
||||
gibt es keine analoge Zusage — aber die Zielgruppe „Selbsthoster" ist stark linux-lastig, und Linux ist
|
||||
in der Map als Zielplattform gelistet. Realistische Optionen, alle unattraktiv:
|
||||
|
||||
1. **Voice auf Linux als nicht unterstützt deklarieren.** Chat läuft voll, Voice nicht. Ehrlich, billig,
|
||||
schmerzhaft.
|
||||
2. **Nativer Pfad nur auf Linux** (§6). Bricht die Architektur-Einheitlichkeit — Voice läge auf drei
|
||||
Plattformen im WebView und auf einer in Rust.
|
||||
3. **Eigenes WebKitGTK bündeln.** Widerspricht der Simplizitätsdoktrin des Projekts frontal.
|
||||
4. **Warten/upstream drücken:** wry ≥ 0.56 + Tauri ≥ 2.12 (PR
|
||||
[#14865](https://github.com/tauri-apps/tauri/pull/14865), gemergt **03.08.2026**, Milestone 2.12,
|
||||
noch nicht released) lösen Gate 3 sauber. Gate 2 bräuchte zusätzlich `with_webview`-Code oder
|
||||
tauri#8989. **Gate 1 löst niemand außer den Distros.**
|
||||
|
||||
Diese Entscheidung gehört als Ergebnis in Ticket 04 (*Voice-Topologie*), nicht hierher.
|
||||
|
||||
---
|
||||
|
||||
## 6. Windows (WebView2) — **Ja, ohne Konfiguration**
|
||||
|
||||
- **Engine:** Chromium. Microsoft dokumentiert `navigator.mediaDevices.getUserMedia()` explizit als in
|
||||
WebView2 verfügbar, mit Secure-Context als einziger Einschränkung
|
||||
([Working with local content](https://learn.microsoft.com/en-us/microsoft-edge/webview2/concepts/working-with-local-content)).
|
||||
- **Permission-Default ist PROMPT, nicht Deny.** `COREWEBVIEW2_PERMISSION_STATE_DEFAULT` —
|
||||
*„Specifies that the default browser behavior is used, which normally prompt users for decision."*
|
||||
`get_State` — *„The default value is `COREWEBVIEW2_PERMISSION_STATE_DEFAULT`."*
|
||||
Permission-Kind: `COREWEBVIEW2_PERMISSION_KIND_MICROPHONE` — *„Indicates permission to capture audio."*
|
||||
API vorhanden seit WebView2 **0.9.430**; jede GA-Runtime genügt.
|
||||
- **wry 0.55.1** registriert zwar `add_PermissionRequested`, setzt aber **nur** für
|
||||
`COREWEBVIEW2_PERMISSION_KIND_CLIPBOARD_READ` einen State (verifiziert in `src/webview2/mod.rs`).
|
||||
Alles andere bleibt auf `STATE_DEFAULT` ⇒ **WebView2 zeigt seinen eigenen Prompt.**
|
||||
- **Secure Context:** `http://tauri.localhost` — siehe §4.5. Kein
|
||||
`--unsafely-treat-insecure-origin-as-secure` nötig.
|
||||
|
||||
**Eine Falle:** Die Entscheidung wird im WebView2-Profil **persistiert**. Wer einmal „Blockieren" klickt,
|
||||
bekommt nie wieder einen Prompt, bis `%LOCALAPPDATA%\<bundleId>\EBWebView\Default\Preferences` gelöscht
|
||||
wird ([tauri#5042](https://github.com/tauri-apps/tauri/issues/5042),
|
||||
[#8979](https://github.com/tauri-apps/tauri/issues/8979)). Das muss die UI abfangen: `getUserMedia`, das
|
||||
ohne sichtbaren Dialog sofort mit `NotAllowedError` zurückkommt, ist ein anderer Zustand als „Nutzer hat
|
||||
gerade abgelehnt", und der Nutzer braucht eine Anleitung statt eines Timeouts.
|
||||
|
||||
Für MSIX/UWP-Pakete zusätzlich: `microphone`-Device-Capability im AppxManifest.
|
||||
|
||||
---
|
||||
|
||||
## 7. Nativer Fallback und Sicherheits-Eigenheiten
|
||||
|
||||
### 7.1 `webrtc-rs` — reiner Protokoll-Stack, **keine** Audio-Erfassung
|
||||
|
||||
`webrtc` **0.20.2** (11.08.2026), 5,6 Mio. Downloads,
|
||||
[github.com/webrtc-rs/webrtc](https://github.com/webrtc-rs/webrtc). README-Selbstbeschreibung:
|
||||
*„Sans-I/O protocol core with complete WebRTC stack (95%+ W3C API compliance)"* — ICE, DTLS, SRTP, SCTP,
|
||||
RTP, DataChannels, Simulcast.
|
||||
|
||||
**Nicht enthalten:** Geräte-Capture, Codecs, Jitter-Buffer/NetEQ, Echo-Cancellation. Der Maintainer selbst
|
||||
(webrtc-rs#94) beschreibt das Ersetzen von Google-libwebrtc als „requires porting NetEQ, audio 3A
|
||||
(AEC, AGC, NS), audio/video codec" — also ausdrücklich nicht getan.
|
||||
|
||||
Der vollständige Eigenbau bräuchte zusätzlich: `cpal` 0.18.1 (Capture), `opus`/`audiopus` (Encoder),
|
||||
RTP-Packetisierung, einen selbstgeschriebenen Jitter-Buffer mit PLC — und für AEC/AGC/NS ausgerechnet
|
||||
`webrtc-audio-processing`, das seinerseits ein C++-Binding an Googles APM ist. Man entkommt libwebrtc
|
||||
also nicht, man baut nur den unangenehmen Teil selbst.
|
||||
|
||||
**Mobile:** keine dokumentierte Plattform-Matrix. Android wird real benutzt, mit bekannten Kanten bei
|
||||
Netzwerk-Interfaces und Handover (webrtc-rs #874, #272, #78). iOS: #645 *„build a static lib for
|
||||
ios/macos"* wurde als **not planned** geschlossen.
|
||||
|
||||
### 7.2 `livekit/rust-sdks` (`libwebrtc`-Crate) — die realistische native Option
|
||||
|
||||
`libwebrtc` **0.3.45** (10.08.2026), `livekit` 0.8.3 —
|
||||
[github.com/livekit/rust-sdks](https://github.com/livekit/rust-sdks), *„LiveKit safe bindings to
|
||||
libwebrtc"*. README listet **Windows, macOS, Linux, iOS, Android**, mit Mobile-Beispiel. Liefert echtes
|
||||
libwebrtc: NetEQ, AEC/AGC/NS, Opus, Geräte-Capture, HW-Encoding.
|
||||
|
||||
Preis: ein großer vorkompilierter nativer Blob pro Plattform/Architektur plus LiveKits Build-Toolchain.
|
||||
Das steht in offener Spannung zur Simplizitäts-Doktrin des Projekts.
|
||||
|
||||
### 7.3 Der eigentliche Aufwand liegt in der Rust↔WebView-Naht
|
||||
|
||||
Geht Audio nativ, ist der WebView **nicht mehr der Medien-Endpunkt**: Mikrofon-Capture in Rust,
|
||||
Remote-Wiedergabe in Rust (kein `<audio>`), Signaling/State über Tauri-IPC, und für die UI (Pegelanzeige,
|
||||
Speaking-Indikator) nur heruntergerechnete Metering-Werte über IPC — niemals rohes PCM. Zurück-Injizieren
|
||||
von PCM in den WebView via WebAudio/`MediaStreamTrackGenerator` geht in Chromium, aber **nicht** in
|
||||
WebKitGTK — auf Linux müsste der native Pfad die Wiedergabe also komplett besitzen.
|
||||
|
||||
Grobe Größenordnung: `livekit/libwebrtc` ≈ Tage bis zum funktionierenden Call mit brauchbarer AEC;
|
||||
`webrtc-rs` + cpal + opus + APM ≈ Wochen, und man besitzt die Echo-Cancellation-Qualität auf Dauer selbst.
|
||||
|
||||
**Empfehlung:** Der native Pfad ist als *Fallback für Linux* diskutabel, nicht als Ersatz für den
|
||||
WebView-Pfad. Für Mobile ist er unnötig — dort funktioniert der WebView.
|
||||
|
||||
### 7.4 Bedrohungsmodell: der Signaling-Server kann DTLS-Fingerprints tauschen
|
||||
|
||||
Das ist der wichtigste Sicherheitsbefund für ein Projekt, dessen Betreiber als Angreifer gilt.
|
||||
|
||||
**RFC 8827** *(WebRTC Security Architecture, Januar 2021)* benennt den Angriff wörtlich:
|
||||
|
||||
> „any on-path attacker can replace the DTLS-SRTP fingerprints in the handshake and thus substitute its
|
||||
> own identity for either endpoint"
|
||||
|
||||
Der Server relayed die SDP. Er kann `a=fingerprint:` austauschen und zwei separate DTLS-Sitzungen
|
||||
terminieren — vollständiges MITM auf dem Audio, ohne dass irgendein WebRTC-Mechanismus anschlägt.
|
||||
|
||||
**Der spezifizierte Gegenmechanismus ist tot.** W3C *Identity for WebRTC 1.0* steht seit **27.09.2018**
|
||||
unverändert als Candidate Recommendation. MDN-Browser-Compat für `setIdentityProvider` / `peerIdentity` /
|
||||
`getIdentityAssertion`: Chrome `false`, Safari `false`, **WebView Android `false`**, nur Firefox 40.
|
||||
⇒ In *keiner* der vier Ziel-Engines verfügbar.
|
||||
|
||||
**Was stattdessen zu tun ist** — und was ins Signaling-Protokoll (Ticket 09) gehört:
|
||||
|
||||
1. Fingerprint deterministisch vorab bestimmen: `RTCPeerConnection.generateCertificate()` +
|
||||
`RTCCertificate.getFingerprints()`, statt ihn erst aus `localDescription.sdp` zu lesen.
|
||||
2. Den Fingerprint **durch den E2EE-Kanal der App** übertragen, signiert mit dem Langzeit-Identity-Key —
|
||||
*nicht* im Klartext-SDP, das der Server relayed.
|
||||
3. Beim Empfang gegen den Fingerprint im relayed SDP vergleichen. Mismatch ⇒ Call abbrechen und als
|
||||
Sicherheitsereignis anzeigen, nicht still weiterlaufen lassen.
|
||||
4. **Key Continuity** (RFC 8827): Fingerprints pro Gerät über Calls hinweg cachen und bei Änderung warnen.
|
||||
5. Optional eine **SAS** (Emoji/Wortliste aus dem Hash beider Fingerprints, à la ZRTP/Signal) für den
|
||||
paranoiden Pfad — die verteidigt zusätzlich gegen einen kompromittierten Identity-Key.
|
||||
|
||||
Genau das macht Matrix in MSC3401/Element Call: Call-SDP läuft über `m.room.encrypted` To-Device-Messages,
|
||||
der Homeserver sieht den Fingerprint nie im Klartext. **Hier ist die eine Stelle, an der das
|
||||
Voice-Signaling-Protokoll heute schon eine E2EE-Naht braucht** — auch wenn das volle E2EE-Design ein
|
||||
Folge-Effort ist. Das gehört als Randbedingung in Ticket 09.
|
||||
|
||||
### 7.5 IP-Leaks, TURN-Metadaten, CSP
|
||||
|
||||
- **`iceTransportPolicy: "relay"`** ([W3C WebRTC 1.0](https://www.w3.org/TR/webrtc/)): *„The ICE Agent
|
||||
uses only media relay candidates"*; Spec-Note: *„can be used to prevent the remote endpoint from
|
||||
learning the user's IP addresses"*. Versteckt die IP **vor dem Peer**, nicht vor dem TURN-Server.
|
||||
Da der Server die IP ohnehin aus der WebSocket-Verbindung kennt, ist `relay` nahezu gratis — es reduziert
|
||||
strikt die Exposition gegenüber dem *Peer*. Relevant für Ticket 04.
|
||||
- **mDNS-ICE-Kandidaten** (`.local`): IETF-Draft `draft-ietf-mmusic-mdns-ice-candidates` ist **abgelaufen**
|
||||
(-03, Juni 2022, nie RFC) — trotzdem überall ausgeliefert: Chromium ab M74 (2019), WebKit ab
|
||||
[bug 209050](https://bugs.webkit.org/show_bug.cgi?id=209050) (RESOLVED FIXED, März 2020). WebKit ist
|
||||
strenger als Chromium: Host-Kandidaten werden bis zur Capture-Erlaubnis komplett unterdrückt
|
||||
([webkit.org/blog/7763](https://webkit.org/blog/7763/a-closer-look-into-webrtc/), 03.07.2017).
|
||||
Ob die `.local`-Namen im Android-WebView auch *auflösbar* sind (Multicast braucht dort einen
|
||||
`MulticastLock`), ist ungeprüft — Experiment E6.
|
||||
- **Was ein feindlicher TURN-Betreiber unter `relay` lernt:** nicht den Klartext (DTLS-SRTP bleibt
|
||||
Ende-zu-Ende), aber beide öffentlichen IPs, exakt wer-mit-wem, Start/Ende/Dauer, und über Paketgrößen
|
||||
eine **Voice-Activity-Detection**. Opus läuft per Default variabel-bitratig; das leckt Sprech-/Pausen-Muster.
|
||||
**Gegenmaßnahme: `cbr=1` (und ggf. `usedtx=0`) in der Opus-`fmtp`-Zeile**, gegen Bandbreite eingetauscht.
|
||||
Gehört als Notiz zu Ticket 04/09, auch wenn Codecs formal out of scope sind — es ist eine SDP-Frage.
|
||||
- **CSP kann WebRTC nicht einschränken.** Der `webrtc`-Direktive existiert in
|
||||
[CSP Level 3](https://www.w3.org/TR/CSP3/) (Working Draft, **13.08.2026**, §6.2.1: *„The `webrtc`
|
||||
directive controls whether a global object may initiate WebRTC connections"*), ist aber **nirgends
|
||||
implementiert**: Firefox Bugzilla 1783489 NEW/unassigned, Chromium 40188662 noch im „evaluate"-Stadium,
|
||||
MDN-Doku-Issue als *not planned* geschlossen. `connect-src` greift bei `RTCPeerConnection` nicht — genau
|
||||
deshalb brauchte es überhaupt eine eigene Direktive. Ein harter Kill-Switch geht nur JS-seitig
|
||||
(Konstruktor vor dem App-Code entfernen).
|
||||
|
||||
### 7.6 WebView-spezifische Eigenheit: wry vergibt Mikrofonrechte ohne App-Beteiligung
|
||||
|
||||
Aus §3.3 und §4.2 folgt eine Eigenschaft, die man kennen sollte:
|
||||
|
||||
- **iOS/macOS (wry 0.55.1):** `WKPermissionDecision::Grant`, **bedingungslos**, für jeden Origin und
|
||||
jeden Frame. Es gibt in dieser Version keinen Hook, um das zu verhindern.
|
||||
- **Android (wry 0.55.1/0.56):** `RESOURCE_AUDIO_CAPTURE` steht in der Menge der bekannten, automatisch
|
||||
weitergereichten Ressourcen.
|
||||
|
||||
Solange das Frontend ausschließlich gebündelter, eigener Code auf `tauri://localhost` bzw.
|
||||
`http://tauri.localhost` ist, ist das Risiko gering — der feindliche Server liefert kein Skript aus.
|
||||
Es wird sofort relevant, sobald irgendwann **remote Inhalte, ein `<iframe>`, eine Vorschau-Einbettung
|
||||
oder ein `<webview>` für fremde Inhalte** dazukommt: Dann bekäme dieser Origin Mikrofonzugriff, ohne dass
|
||||
die App gefragt würde. Für eine datenschutzorientierte App ist das eine Regel, die aufgeschrieben gehört:
|
||||
**keine fremden Origins im selben WebView.**
|
||||
|
||||
Ab Tauri 2.12 (wry ≥ 0.56, PR #14865) lässt sich das über `on_permission_request` sauber kontrollieren.
|
||||
Bis dahin ist die Architektur-Regel der einzige Schutz.
|
||||
|
||||
### 7.7 Insertable Streams / SFrame — falls je ein SFU kommt
|
||||
|
||||
[W3C WebRTC Encoded Transform](https://www.w3.org/TR/webrtc-encoded-transform/) (Working Draft, 25.06.2026),
|
||||
inkl. SFrame nach RFC 9605. Support (MDN BCD):
|
||||
|
||||
| Engine | `RTCRtpScriptTransform` |
|
||||
|---|---|
|
||||
| Safari / WKWebView (iOS) | **15.4** |
|
||||
| Chrome / Android WebView / WebView2 | **141** (~Okt. 2025) |
|
||||
| Firefox | 117 |
|
||||
| Chromium-Legacy `createEncodedStreams()` | 86 (nicht standardisiert, WebKit hat es nie) |
|
||||
|
||||
Wichtige Einschränkung für das Bedrohungsmodell: Der SFU behält auch mit E2EE Zugriff auf
|
||||
RTP-Metadaten — *„synchronization sources, payload types, timestamps, and audio levels"*. Die
|
||||
`audio-level`-Header-Extension ist ein direktes Voice-Activity-Orakel; sie müsste beim SFU-Einsatz
|
||||
wegverhandelt werden.
|
||||
|
||||
Für die im Ticket 04 zur Debatte stehende **Mesh-Topologie ist das alles irrelevant** — dort ist
|
||||
DTLS-SRTP schon Ende-zu-Ende. Es wird erst mit einem SFU nötig.
|
||||
|
||||
### 7.8 `enumerateDevices()`
|
||||
|
||||
Spec-konform sind `label`-Felder vor der Erlaubnis leer; `deviceId`/`groupId` sind aber pro Origin stabile
|
||||
Hashes, und **Anzahl und Art der Geräte lecken vor der Erlaubnis**. In einer Tauri-App ist das
|
||||
konstruktionsbedingt kein Angriffsvektor (das Frontend ist eigener Code). Die daraus folgende Regel ist
|
||||
eine andere: **Geräte-Inventar niemals an den Server senden** — nicht in Telemetrie, nicht in Logs, nicht
|
||||
in Crash-Reports. Und SDP ist selbst ein starker Fingerabdruck (Codec-Liste, Header-Extensions, `a=msid`,
|
||||
in Chromium `a=ssrc cname`); wo möglich reduzieren.
|
||||
|
||||
---
|
||||
|
||||
## 8. Offen — und welches Experiment es entscheidet
|
||||
|
||||
Nichts davon blockiert die Spec-Arbeit; alles davon sollte vor der *Implementierung* geklärt sein.
|
||||
|
||||
**E1 — iOS: funktioniert `getUserMedia` real auf einem Gerät? (höchste Priorität)**
|
||||
Ist die einzige Frage, die noch ein offenes Upstream-Issue hat (tauri#12068).
|
||||
Minimale Tauri-v2-App zweimal bauen — einmal **mit**, einmal **ohne** `NSMicrophoneUsageDescription` —
|
||||
auf einem **physischen** iPhone (iOS ≥ 15). In beiden Fällen loggen: `typeof navigator.mediaDevices`,
|
||||
`window.isSecureContext`, `location.origin`, `await navigator.mediaDevices.enumerateDevices()`, dann
|
||||
`getUserMedia({audio:true})` aus einem Click-Handler. Erwartung: `isSecureContext === true`,
|
||||
`origin === "tauri://localhost"`, Erfolg nur in der Variante mit Key. Zusätzlich prüfen, ob der Key nach
|
||||
dem Merge tatsächlich in `gen/apple/*/Info.plist` steht (tauri#13068 warnt vor stillem Überschreiben).
|
||||
|
||||
**E2 — Android: ist `MODIFY_AUDIO_SETTINGS` wirklich zwingend?**
|
||||
Dieselbe App dreimal: nur `RECORD_AUDIO` / nur `MODIFY_AUDIO_SETTINGS` / beide. Erwartung nach
|
||||
Code-Lesung: nur die dritte Variante liefert einen Stream; die erste bringt `NotAllowedError` *nachdem*
|
||||
der Nutzer im Dialog zugestimmt hat. Entscheidet zugleich tauri#10846.
|
||||
|
||||
**E3 — Überlebt das hand-editierte `AndroidManifest.xml`?**
|
||||
Auf einem Scratch-Checkout `tauri android init`, Manifest editieren, `tauri android build`, diffen;
|
||||
dann `tauri android init` erneut und wieder diffen. Erwartung: Build lässt es in Ruhe, `init` rendert neu.
|
||||
|
||||
**E4 — Ist `http://tauri.localhost` im Android-WebView ein Secure Context?**
|
||||
Über `chrome://inspect` in der laufenden App auswerten: `window.isSecureContext` und
|
||||
`!!window.crypto.subtle`. Beide `true` ⇒ wry#1709 ist widerlegt und `useHttpsScheme` bleibt aus.
|
||||
|
||||
**E5 — Haben Arch und Fedora WebKitGTK mit `-DENABLE_WEB_RTC=ON` gebaut?**
|
||||
Für Debian/Ubuntu und Alpine ist die Antwort belegt (nein). Für Arch/Fedora blockte die Bot-Schranke.
|
||||
Distro-unabhängiger und definitiver Test auf einer beliebigen Maschine:
|
||||
`nm -D --defined-only /usr/lib/*/libwebkit2gtk-4.1.so.0 | grep -i rtcpeerconnection`
|
||||
— oder in einem beliebigen WebKitGTK-View `typeof RTCPeerConnection === 'function'`.
|
||||
|
||||
**E6 — Lösen sich mDNS-`.local`-ICE-Kandidaten im Android-WebView und in WKWebView auf?**
|
||||
Zwei echte Geräte im selben LAN, `pc.onicecandidate` loggen, prüfen ob `typ host`-Kandidaten auf
|
||||
`.local` enden **und** ob die Verbindung trotzdem zustande kommt.
|
||||
|
||||
**E7 — Wann kommt Tauri 2.12?**
|
||||
Trägt PR #14865 (`on_permission_request`), gemergt 03.08.2026. crates.io `tauri` steht auf 2.11.5.
|
||||
Relevant für Linux Gate 3 und für §7.6.
|
||||
|
||||
---
|
||||
|
||||
## 9. Konfigurationsschritte, kompakt
|
||||
|
||||
```
|
||||
src-tauri/Info.ios.plist [NEU]
|
||||
NSMicrophoneUsageDescription = "…"
|
||||
|
||||
src-tauri/gen/android/app/src/main/AndroidManifest.xml [EDITIEREN, committen]
|
||||
+ android.permission.RECORD_AUDIO
|
||||
+ android.permission.MODIFY_AUDIO_SETTINGS
|
||||
+ uses-feature android.hardware.microphone (required=false)
|
||||
|
||||
src-tauri/tauri.conf.json [UNVERÄNDERT]
|
||||
csp: null — passt, CSP betrifft WebRTC ohnehin nicht
|
||||
useHttpsScheme — nicht setzen, außer E4 fällt negativ aus (dann VOR dem ersten Release)
|
||||
bundle.iOS.minimumSystemVersion: mindestens "14.3", praktisch "15.0"
|
||||
|
||||
Windows [NICHTS ZU TUN]
|
||||
Linux [NICHT LÖSBAR — siehe §5, Entscheidung in Ticket 04]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 10. Quellen
|
||||
|
||||
**Apple / WebKit**
|
||||
- WebKit-Blog, *MediaRecorder API*, 23.11.2020 — https://webkit.org/blog/11353/mediarecorder-api/
|
||||
- WebKit-Blog, *A Closer Look Into WebRTC*, 03.07.2017 — https://webkit.org/blog/7763/a-closer-look-into-webrtc/
|
||||
- Apple, `webView(_:requestMediaCapturePermissionFor:…)`, iOS 15.0+ — https://developer.apple.com/documentation/webkit/wkuidelegate/webview(_:requestmediacapturepermissionfor:initiatedbyframe:type:decisionhandler:)
|
||||
- Apple, `NSMicrophoneUsageDescription` — https://developer.apple.com/documentation/BundleResources/Information-Property-List/NSMicrophoneUsageDescription
|
||||
- WebKit-Quellcode, `Source/WebCore/page/SecurityOrigin.cpp` — https://github.com/WebKit/WebKit/blob/main/Source/WebCore/page/SecurityOrigin.cpp
|
||||
- WebKit-Bug 209050, mDNS-Kandidaten in Safari, RESOLVED FIXED (März 2020) — https://bugs.webkit.org/show_bug.cgi?id=209050
|
||||
|
||||
**Android / Chromium**
|
||||
- Android 5.0 Release Notes (WebView → Chromium M37, WebRTC, `PermissionRequest`) — https://developer.android.com/about/versions/android-5.0
|
||||
- AOSP `WebChromeClient.java` (Default = `request.deny()`) — https://android.googlesource.com/platform/frameworks/base/+/refs/heads/main/core/java/android/webkit/WebChromeClient.java
|
||||
- AOSP `PermissionRequest.java` — https://android.googlesource.com/platform/frameworks/base/+/refs/heads/main/core/java/android/webkit/PermissionRequest.java
|
||||
- AOSP `core/res/AndroidManifest.xml` (Protection Levels) — https://android.googlesource.com/platform/frameworks/base/+/refs/heads/main/core/res/AndroidManifest.xml
|
||||
- Chromium `net/base/url_util.cc` (`IsNormalizedLocalhostTLD`) — https://chromium.googlesource.com/chromium/src/+/refs/heads/main/net/base/url_util.cc
|
||||
- Chromium `services/network/public/cpp/is_potentially_trustworthy.cc` — https://chromium.googlesource.com/chromium/src/+/refs/heads/main/services/network/public/cpp/is_potentially_trustworthy.cc
|
||||
|
||||
**Microsoft / WebView2**
|
||||
- `ICoreWebView2PermissionRequestedEventArgs` (Default = `STATE_DEFAULT`; ab 0.9.430) — https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2permissionrequestedeventargs
|
||||
- WebView2 IDL (`COREWEBVIEW2_PERMISSION_KIND_MICROPHONE`, `…_STATE_DEFAULT`) — https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl
|
||||
- WebView2 *Working with local content* (getUserMedia / Secure Context) — https://learn.microsoft.com/en-us/microsoft-edge/webview2/concepts/working-with-local-content
|
||||
|
||||
**WebKitGTK**
|
||||
- `Source/cmake/OptionsGTK.cmake` (`ENABLE_WEB_RTC` = `${ENABLE_EXPERIMENTAL_FEATURES}`) — https://github.com/WebKit/WebKit/blob/main/Source/cmake/OptionsGTK.cmake
|
||||
- `Source/cmake/WebKitFeatures.cmake` (`ENABLE_EXPERIMENTAL_FEATURES` = OFF) — https://github.com/WebKit/WebKit/blob/main/Source/cmake/WebKitFeatures.cmake
|
||||
- `WebKitSettings:enable-webrtc` (Default FALSE, seit 2.38) — https://webkitgtk.org/reference/webkit2gtk/stable/property.Settings.enable-webrtc.html
|
||||
- `WebKitSettings:enable-media-stream` (Default FALSE laut Doku, Port-Default ON, seit 2.4) — https://webkitgtk.org/reference/webkit2gtk/stable/property.Settings.enable-media-stream.html
|
||||
- `WebKitUserMediaPermissionRequest` („denied by default") — https://webkitgtk.org/reference/webkit2gtk/stable/class.UserMediaPermissionRequest.html
|
||||
- `WebKitWebView::permission-request` — https://webkitgtk.org/reference/webkit2gtk/stable/signal.WebView.permission-request.html
|
||||
- WebKitGTK 2.52 Highlights (librice, Netzwerk-Sandbox), 18.03.2026 — https://webkitgtk.org/2026/03/18/webkitgtk-2.52-highlights.html
|
||||
- Debian `webkit2gtk` `debian/rules` — https://sources.debian.org/src/webkit2gtk/latest/debian/rules/
|
||||
- Alpine `community/webkit2gtk-4.1` APKBUILD — https://github.com/alpinelinux/aports/blob/master/community/webkit2gtk-4.1/APKBUILD
|
||||
|
||||
**Tauri / wry** (jeweils Tag `wry-v0.55.1`, sofern nicht anders vermerkt)
|
||||
- `src/wkwebview/class/wry_web_view_ui_delegate.rs` — https://github.com/tauri-apps/wry/blob/wry-v0.55.1/src/wkwebview/class/wry_web_view_ui_delegate.rs
|
||||
- `src/android/kotlin/RustWebChromeClient.kt` — https://github.com/tauri-apps/wry/blob/wry-v0.55.1/src/android/kotlin/RustWebChromeClient.kt
|
||||
- `src/webview2/mod.rs` (nur Clipboard-Read) — https://github.com/tauri-apps/wry/blob/wry-v0.55.1/src/webview2/mod.rs
|
||||
- `src/webkitgtk/mod.rs` (kein `connect_permission_request`) — https://github.com/tauri-apps/wry/blob/wry-v0.55.1/src/webkitgtk/mod.rs
|
||||
- wry CHANGELOG (0.56.0, 30.07.2026: Permission-API; 0.56.1, 13.08.2026) — https://github.com/tauri-apps/wry/blob/dev/CHANGELOG.md
|
||||
- wry PR #1654, *expanded permission handling API* — https://github.com/tauri-apps/wry/pull/1654
|
||||
- `crates/tauri-utils/src/config.rs` (`use_https_scheme`, Default `false`) — https://github.com/tauri-apps/tauri/blob/dev/crates/tauri-utils/src/config.rs
|
||||
- `crates/tauri/src/manager/mod.rs` (`tauri_protocol_url`) — https://github.com/tauri-apps/tauri/blob/dev/crates/tauri/src/manager/mod.rs
|
||||
- AndroidManifest-Template (nur `INTERNET`) — https://github.com/tauri-apps/tauri/blob/dev/crates/tauri-cli/templates/mobile/android/app/src/main/AndroidManifest.xml
|
||||
- `examples/api/src-tauri/Info.plist` (WebRTC-Usage-Strings) — https://github.com/tauri-apps/tauri/blob/dev/examples/api/src-tauri/Info.plist
|
||||
- Tauri PR #14865, `on_permission_request`, gemergt 03.08.2026, Milestone 2.12 — https://github.com/tauri-apps/tauri/pull/14865
|
||||
- `WebviewWindow::with_webview` — https://docs.rs/tauri/latest/tauri/webview/struct.WebviewWindow.html
|
||||
- Tauri v2 Prerequisites (webkit2gtk-4.1) — https://v2.tauri.app/start/prerequisites/
|
||||
- Tauri v2 CSP („only enabled if set") — https://v2.tauri.app/security/csp/
|
||||
- Tauri v2 Capabilities / Permissions (ACL ≠ OS-Rechte) — https://v2.tauri.app/security/capabilities/ · https://v2.tauri.app/security/permissions/
|
||||
|
||||
**Tauri-Issues und -Discussions** (Hinweisgeber, mit Datum und Status)
|
||||
- tauri#12068 *iOS Microphone Access* — offen, 28.12.2024 — https://github.com/tauri-apps/tauri/issues/12068
|
||||
- tauri#10846 *Audio Permission Not Working on Android* — 31.08.2024 — https://github.com/tauri-apps/tauri/issues/10846
|
||||
- tauri#12547 *Linux: access denied, no dialog* — not planned, 28.01.2025 — https://github.com/tauri-apps/tauri/issues/12547
|
||||
- tauri#13143 *„browser does not support WebRTC"* (Ubuntu 22.04) — not planned/upstream, 05.04.2025 — https://github.com/tauri-apps/tauri/issues/13143
|
||||
- tauri#13068 *Unify/Document handling of Info.plist files* — offen, 24.03.2025 — https://github.com/tauri-apps/tauri/issues/13068
|
||||
- tauri#8989 *[feat] expose control over webkit settings* — offen — https://github.com/tauri-apps/tauri/issues/8989
|
||||
- tauri#5042 / #8979 — WebView2 persistiert „Block" — https://github.com/tauri-apps/tauri/issues/5042
|
||||
- wry#1195 *getUserMedia/getDisplayMedia prompt on macOS* — offen, 20.03.2024 — https://github.com/tauri-apps/wry/issues/1195
|
||||
- wry#1709 / #1710 — Secure-Context-Behauptung, **unbelegt**, 01.04.2026 — https://github.com/tauri-apps/wry/issues/1709
|
||||
- Discussion #8426 *Functional WebRTC in WebkitGTK on Linux!* — 28.01.2024 — https://github.com/tauri-apps/tauri/discussions/8426
|
||||
- Discussion #12732 *Camera permissions on mobile (Android)* — Feb–Apr 2025 — https://github.com/orgs/tauri-apps/discussions/12732
|
||||
- Discussion #8323 (FabianLars zu `gen/` im Git) — 01.12.2023 — https://github.com/orgs/tauri-apps/discussions/8323
|
||||
|
||||
**Standards**
|
||||
- RFC 8827, *WebRTC Security Architecture*, Januar 2021 — https://www.rfc-editor.org/rfc/rfc8827.html
|
||||
- W3C WebRTC 1.0 (`RTCIceTransportPolicy`) — https://www.w3.org/TR/webrtc/
|
||||
- W3C Identity for WebRTC 1.0, CR 27.09.2018 (praktisch tot) — https://www.w3.org/TR/webrtc-identity/
|
||||
- W3C WebRTC Encoded Transform, WD 25.06.2026 — https://www.w3.org/TR/webrtc-encoded-transform/
|
||||
- W3C Secure Contexts — https://w3c.github.io/webappsec-secure-contexts/
|
||||
- W3C CSP Level 3, WD 13.08.2026 (`webrtc`-Direktive, unimplementiert) — https://www.w3.org/TR/CSP3/
|
||||
- IETF `draft-ietf-mmusic-mdns-ice-candidates` — abgelaufen, -03 (Juni 2022) — https://datatracker.ietf.org/doc/draft-ietf-mmusic-mdns-ice-candidates/
|
||||
|
||||
**Rust-Ökosystem**
|
||||
- `webrtc` 0.20.2 (11.08.2026) — https://github.com/webrtc-rs/webrtc · https://crates.io/crates/webrtc
|
||||
- `libwebrtc` 0.3.45 (10.08.2026) / `livekit` 0.8.3 — https://github.com/livekit/rust-sdks · https://crates.io/crates/libwebrtc
|
||||
- `cpal` 0.18.1 (07.06.2026) — https://crates.io/crates/cpal
|
||||
- `webrtc-audio-processing` 2.1.0 (13.05.2026) — https://crates.io/crates/webrtc-audio-processing
|
||||
1492
docs/realtime/research/03-ws-auth-reconnect.md
Normal file
1492
docs/realtime/research/03-ws-auth-reconnect.md
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user