Evaluación Final – Función de Control Parental para VLC-iOS
Cuando comencé este proyecto, el objetivo era claro: implementar un sistema de Control Parental en VLC para iOS que pudiera proteger funciones sensibles, como eliminar archivos multimedia, acceder a configuraciones y abrir transmisiones de red, requiriendo autenticación a través de un código de acceso o Face ID. El objetivo era proporcionar una experiencia más segura para las familias, particularmente para padres que comparten sus dispositivos con niños pequeños.
Pero desde el principio, me di cuenta de que esta tarea no sería simple. De hecho, resultó ser la experiencia técnica más desafiante que he tenido, mucho más compleja que cualquier cosa que haya enfrentado en la universidad, proyectos personales o trabajo freelance. Y no solo por el código. Este también fue mi primer proyecto colaborativo de código abierto, lo que significaba aprender a navegar por una base de código compartida, respetar los cambios de otros contribuyentes y mantener un historial de Git limpio y revisable, algo que nunca había hecho antes.
Primeros Pasos y Desafíos de Git
A principios de junio, comencé diseñando la estructura de una nueva clase, ParentalControlCoordinator, que manejaría todos los aspectos de la autenticación. Sin embargo, rápidamente tuve problemas al trabajar con Git. Después de ejecutar un git rebase master, accidentalmente introduje docenas de commits no relacionados en mi merge request. Me tomó más de una semana entender cómo limpiar el historial de commits correctamente usando git reset y git push --force, pero eventualmente pude recuperar la rama y dejarla lista para desarrollo nuevamente.
Primera Versión – Lógica Básica de Autenticación
Una vez que los problemas de Git fueron resueltos, comencé a experimentar con la lógica real de la funcionalidad. El primer método que creé fue:
func authenticate(completion: @escaping (Bool) -> Void)
Verificaba si el control parental estaba habilitado y, si es así, llamaba:
keychain.validateSecret(allowBiometricAuthentication: true, isCancellable: true)
Esto funcionaba en algunos escenarios pero reveló una gran falla arquitectónica: la funcionalidad de control parental estaba vinculada al Passcode Lock, una funcionalidad que solicita al usuario que ingrese un código de acceso al abrir la aplicación. Si el Passcode Lock estuviera deshabilitado, el control parental no funcionaría en absoluto.
Independencia Entre Funcionalidades
Para resolver esto, creé una entrada separada en el keychain dedicada al control parental. Luego refactoricé el ParentalControlCoordinator para eliminar todas las dependencias de UserDefaults y reutilicé el KeychainCoordinator solo para lógica relacionada con mostrar la pantalla de código de acceso. Esto permitió que el control parental y el bloqueo por código funcionaran completamente independientes, como deberían.
Protegiendo Acciones – Método por Método
Con la lógica de autenticación funcionando, pasé a proteger funcionalidades individuales. Para evitar duplicación de código, creé un método centralizado:
func authorizeIfParentalControlIsEnabled(action: @escaping @convention(block) () -> Void, fail: (@convention(block) () -> Void)? = nil)
Este método era entonces llamado antes de cada acción restringida. Aquí está cómo fue integrado en toda la aplicación:
- Renombrar un archivo: Dentro de
EditActions.rename(...) - Eliminar un archivo: En
EditActions.delete(...) - Abrir la pantalla de configuraciones: Via
TabBarCoordinatorusandotabBarController(_:shouldSelect:)yhandleAppDidBecomeActive() - Abrir una transmisión de red: En
_openURLStringAndDismiss() - Descargar archivos: También en
_openURLStringAndDismiss() - Acceder a archivos locales: En
RemoteNetworkDataSource, viatableView(_:didSelectRowAt:) - Agregar/eliminar archivos de playlists/grupos de medios: En
EditActions, envolviendo la lógica conauthorizeIfParentalControlIsEnabled
Lógica de Sesión y Timeout
Para evitar abrumar al usuario con solicitudes constantes de código de acceso, implementé una sesión de desbloqueo temporal. Después de una autenticación exitosa, un timestamp era almacenado usando:
private var lastAuthenticationDate: Date?
Luego verifiqué el tiempo transcurrido usando:
var isParentalControlUnlocked: Bool
Esto permitía a los usuarios realizar múltiples acciones protegidas dentro de una ventana de 30 segundos sin re-autenticar.
Autenticación Biométrica – El Último Obstáculo
El último y más frustrante desafío fue la integración del Face ID. Si el usuario abría la aplicación usando Face ID (via Passcode Lock), luego intentaba autenticar nuevamente para control parental, la pantalla del Face ID aparecería repetidamente y nunca se descartaría.
Aunque la autenticación fuera técnicamente exitosa, el prompt biométrico permanecería visible. Intenté de todo, desde refactorizar los métodos existentes hasta crear un nuevo showPasscodeController() dentro del ParentalControlCoordinator, y verificar LAContext, pero el problema persistía.
Estado Actual y Lo Que Queda
La funcionalidad está actualmente en un estado avanzado y testeable. Protege exitosamente todas las acciones principales y proporciona una buena experiencia de usuario gracias a la lógica de sesión de desbloqueo.
Sin embargo, la merge request aún no ha sido fusionada upstream debido al conflicto no resuelto con Face ID, que parece requerir investigación más profunda y colaboración.
Reflexiones
Este proyecto ha sido un viaje increíble. Tuve la libertad de diseñar la lógica y estructurar la arquitectura. Esa autonomía creativa es uno de los aspectos más gratificantes de trabajar en código abierto.
Al mismo tiempo, me llevó a mis límites. Tuve que aprender Git de la manera difícil, sumergirme en una base de código grande y arreglar bugs que ni siquiera sabía cómo describir al principio.
Aunque aún no ha sido fusionado, creo que este es el primer borrador sólido de una funcionalidad útil. Una que puede ser refinada y eventualmente agregada al VLC para iOS para mejorar la experiencia para familias alrededor del mundo.