El build NUNCA firmó la app de macOS. Al no pasarle ninguna identidad, el bundler de Tauri se saltaba `codesign` entero y el .dmg salía con la firma ad hoc que deja el enlazador: Identifier=labstream_desktop-0a5819ba37119701 flags=0x20002(adhoc,linker-signed) Info.plist=not bound Sealed Resources=none Tres cosas mal, y ninguna avisaba en ningún log: · El Entitlements.plist del repo era DECORACIÓN. Los entitlements se escriben al firmar; sin firma, el micrófono, la cámara y el JIT nunca pidieron nada porque nunca estuvieron pedidos. · Ni el Info.plist ni los recursos iban dentro de la firma: se podían cambiar sin romperla. · El identificador era el del enlazador y no `co.labstream.os`, que es por el que macOS recuerda los permisos que le concedes a una app. AHORA. `APPLE_SIGNING_IDENTITY` se calcula en un paso propio y no en el `env:` del build, porque un secret que no existe llega como cadena VACÍA y Tauri lee `APPLE_CERTIFICATE=""` como «hay certificado» y revienta al importarlo. Solo se exporta lo que tiene valor: · con Developer ID → firma real, y notarizado si están las tres claves; · sin él → identidad "-", firma AD HOC. No engaña a Gatekeeper, pero sella los recursos, ata el Info.plist y APLICA LOS ENTITLEMENTS. En cuanto Jonathan añada los secrets, la siguiente etiqueta sale firmada de verdad sin tocar un solo archivo. ENSAYADO sobre una copia de la app instalada, con el mismo comando que va a correr el CI. Antes y después: flags=0x20002(adhoc,linker-signed) → flags=0x10002(adhoc,runtime) Identifier=labstream_desktop-0a58… → Identifier=co.labstream.os Info.plist=not bound → Info.plist entries=17 Sealed Resources=none → Sealed Resources version=2 rules=13 (sin entitlements) → allow-jit, audio-input, camera Y EL ENSAYO ENCONTRÓ UNA TRAMPA que `plutil -lint` no ve: el Entitlements.plist llevaba `--entitlements` dentro de un comentario XML, y dos guiones seguidos ahí son XML inválido. plutil lo aceptaba; AMFI, que es quien lo lee de verdad dentro de codesign, contestaba «Failed to parse entitlements» y la firma fallaba. Queda avisado en el propio archivo. De paso se cae `com.apple.security.network.client`: es exclusiva del App Sandbox y esta app no lo usa, así que no hacía nada. LA RED QUE FALTABA: un paso nuevo comprueba la firma del .app y TUMBA la publicación si los entitlements no llegaron. Que esto estuviera roto no lo dijo nada durante meses; se descubrió a mano persiguiendo otra cosa. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
77 líneas
4.0 KiB
TOML
77 líneas
4.0 KiB
TOML
[package]
|
|
name = "labstream-desktop"
|
|
version = "1.16.0"
|
|
description = "Cliente de escritorio de Labstream OS"
|
|
authors = ["Labstream SAS"]
|
|
edition = "2021"
|
|
rust-version = "1.77.2"
|
|
|
|
[lib]
|
|
name = "labstream_desktop_lib"
|
|
crate-type = ["staticlib", "cdylib", "rlib"]
|
|
|
|
[build-dependencies]
|
|
tauri-build = { version = "2", features = [] }
|
|
|
|
[dependencies]
|
|
# `unstable` habilita multiwebview: varios webviews (pestañas) dentro de UNA ventana.
|
|
# `devtools` deja el inspector disponible TAMBIÉN en release (por defecto solo va en debug:
|
|
# wry-0.55.1/src/lib.rs:834-837). Sin él, un fallo que solo aparece en la app instalada —como
|
|
# el de OnlyOffice en Windows— no se puede mirar por dentro y hay que deducirlo leyendo
|
|
# fuentes. Con él, clic derecho → Inspeccionar sobre el marco que falle y se acabó.
|
|
tauri = { version = "2", features = ["tray-icon", "unstable", "devtools"] }
|
|
tauri-plugin-opener = "2"
|
|
tauri-plugin-clipboard-manager = "2"
|
|
tauri-plugin-notification = "2"
|
|
tauri-plugin-autostart = "2"
|
|
tauri-plugin-updater = "2"
|
|
tauri-plugin-single-instance = "2"
|
|
serde = { version = "1", features = ["derive"] }
|
|
serde_json = "1"
|
|
# ── Diálogo «Guardar como» de las descargas (on_download en create_tab, lib.rs) ──
|
|
# rfd DIRECTO y no tauri-plugin-dialog: el manejador de descargas de wry exige la respuesta en
|
|
# el mismo hilo y de forma síncrona (WebView2 DownloadStarting / WKDownload
|
|
# decideDestination, ambos en el hilo principal). El `blocking_save_file()` del plugin monta
|
|
# el diálogo ASÍNCRONO (rfd::AsyncFileDialog) y espera en un canal; en macOS ese diálogo se
|
|
# presenta como hoja (beginSheetModalForWindow) y necesita que el run loop principal siga
|
|
# girando… que es justo el hilo que está bloqueado esperando el canal → cuelgue. El
|
|
# `rfd::FileDialog` síncrono corre un bucle modal anidado en el propio hilo
|
|
# (IFileDialog::Show / NSSavePanel.runModal), que es lo que hace falta aquí.
|
|
# Mismas features que usa el plugin oficial (common-controls-v6 = estilo moderno en Windows).
|
|
rfd = { version = "0.17", default-features = false, features = ["common-controls-v6"] }
|
|
# Para decodificar el último segmento de la URL cuando no hay nombre sugerido (macOS).
|
|
percent-encoding = "2"
|
|
# ── Rastreador de trabajo efectivo (src/tracker.rs) ──
|
|
# active-win-pos-rs: la ventana al frente (app + título) en Win/Mac.
|
|
# device_query: ¿hubo entrada? (huella de mouse/teclas presionadas; NUNCA qué se tecleó).
|
|
# ureq: subir los lotes a /api/tracker (bloqueante, corre en su propio hilo).
|
|
# gethostname: el nombre del equipo, que bautiza al device en el servidor.
|
|
active-win-pos-rs = "0.11"
|
|
ureq = { version = "2", features = ["json"] }
|
|
gethostname = "0.5"
|
|
|
|
# device_query solo en Windows/Linux: la huella de entrada (posición del mouse + cuántas teclas
|
|
# hay presionadas). En macOS NO se usa —leer el teclado global dispara el permiso de
|
|
# Accesibilidad y el diálogo «… quiere controlar este ordenador»— y se sustituye por
|
|
# CGEventSourceSecondsSinceLastEventType (idle_seconds_macos en tracker.rs), que no pide permiso.
|
|
[target.'cfg(not(target_os = "macos"))'.dependencies]
|
|
device_query = "4"
|
|
|
|
# ── Excepción del video (Windows) ──
|
|
# Para saber si «hay un video reproduciéndose» sin espiar el navegador: se mira el MEDIDOR de
|
|
# la salida de audio por defecto (WASAPI, IAudioMeterInformation). Si al frente hay un navegador
|
|
# y está saliendo sonido, es un video sonando → cuenta como navegador, no como inactividad.
|
|
# Solo Windows: en macOS no hay API pública sencilla de nivel de audio por app (el equipo de
|
|
# edición es Windows). El `windows` crate ya está en la caché por Tauri.
|
|
[target.'cfg(windows)'.dependencies]
|
|
windows = { version = "0.61", features = [
|
|
"Win32_Foundation",
|
|
"Win32_System_Com",
|
|
# IMMDevice::Activate exige AMBAS (su firma lleva un PROPVARIANT): sin ellas el método
|
|
# queda cfg'd out y «no existe». Verificado con cargo check --target windows-msvc.
|
|
"Win32_System_Com_StructuredStorage",
|
|
"Win32_System_Variant",
|
|
"Win32_Media_Audio",
|
|
"Win32_Media_Audio_Endpoints",
|
|
] }
|