Decia "Preparando la aplicacion" sin tilde y "puede tardar unos minutos", que
para un primer arranque de quince segundos suena a que algo va mal. Ahora dice
lo que hace y cuanto: descomprime el servidor, tarda un minuto, no hay que
hacer nada.
Al girar el movil aparecia ERR_CONNECTION_REFUSED y ahi se quedaba. La lista de
configChanges estaba incompleta —faltaba smallestScreenSize, que es justo lo que
cambia al rotar—, asi que Android destruia y recreaba la Activity: el WebView se
perdia y volvia a cargar la URL, a veces antes de que el backend escuchara.
Con la lista completa, rotar ya no recrea nada y ademas no se pierde lo que
estuvieras escribiendo.
Y se anade una red de seguridad que no habia: onReceivedError vuelve a esperar
al backend y recarga la ultima pagina que si cargo, en vez de dejar el error de
Chromium en pantalla. Cubre tres casos mas, todos comprobados en el emulador:
- Matar el proceso :node con la app abierta (lo que hace Android cuando necesita
memoria): antes se quedaba muerta, ahora vuelve sola en unos 19 segundos.
- Cambiar de identidad, que reinicia el backend a proposito.
- Arranques lentos en moviles con poca memoria.
Un guard evita que se acumulen hilos esperando al mismo puerto, porque
onReceivedError se dispara una vez por recurso que falle.
Probado ademas: boton atras, modo avion, trim-memory RUNNING_CRITICAL y
am kill con reapertura. Todo eso ya iba bien.
release.sh hace el ciclo entero y siempre igual, se lance a mano o desde un
runner: empaqueta el backend, compila, verifica y publica la release en gitea.
Las verificaciones no son decorativas, son las que han cazado fallos reales en
esta rama: una APK puede compilar, instalar y arrancar sin el backend dentro, o
haber perdido los permisos de camara y quedarse sin videollamadas. Se comprueba
que estan el backend y libnode.so, los dos permisos, y la firma.
El versionCode sale del numero de commits, que sube solo; el versionName, de
package.json, que es de donde lo saca tambien el backend, para que la version
que muestra la aplicacion no pueda discrepar de la de la APK. Los dos se pasan
por entorno a Gradle en vez de editarlos a mano.
La release se crea como borrador a proposito: publicar una APK firmada sin que
nadie la mire no deberia poder pasar por descuido.
El workflow de gitea no se dispara con cada commit (la APK son mas de 100 MB) y
necesita un runner con el SDK de Android; queda documentado en su cabecera.
Cuatro arreglos del envoltorio, todos vistos al probarlo en el emulador:
- onShowFileChooser: sin el, el navegador interno no abre nada al pulsar
"Choose File", asi que no se podia subir ninguna imagen. El APK oficial si
lo implementa; era una regresion frente a el.
- La marca de re-extraccion del backend era el versionName. Al reconstruir la
APK sin subir version, la app se quedaba con el backend anterior y ningun
cambio se aplicaba. Pasa a ser lastUpdateTime del paquete, que cambia en
cada instalacion. (openFd sobre el asset no vale: va comprimido dentro del
APK y no admite descriptor.)
- La espera del backend sondeaba con HEAD, que el backend rechaza con 400.
Ahora abre un socket TCP contra 127.0.0.1:3000, que es lo unico que hay que
saber, y espera hasta cinco minutos en vez de uno: descomprimir 231 MB y
levantar SSB tarda mas en un movil lento. Mientras, pinta una pantalla de
estado con los segundos transcurridos, en vez de dejar el WebView en blanco.
- targetUrl/onNewIntent para poder abrir una ruta concreta desde un intent.
network_security_config.xml tenia base-config detras de domain-config, que el
esquema no admite. Corregido el orden y restaurada la referencia en el
manifiesto, que se habia sustituido por usesCleartextTraffic=true al depurar:
eso abria texto en claro hacia cualquier host, no solo hacia loopback.
Una revision a fondo del arbol contra 0.9.5 saco varios fallos, unos heredados y
otros que introdujo la propia integracion. Verificados uno a uno antes de tocar.
Recursos referenciados que no existian (los introduje al adoptar vistas de 0.9.5
que los usan, mientras la rama los habia borrado):
- Los cuatro temas *-SNH.css. settings_view ofrece Dark, Clear, Matrix y Purple,
pero solo estaba OasisMobile: elegir cualquiera daba 404 y la app se quedaba sin
estilos. Ahora los cuatro responden 200.
- pdf.min.mjs, pdf.worker.min.mjs y pdf-viewer.js, referenciados desde seis vistas.
Regresiones revertidas a 0.9.5 verbatim en catorce ficheros. Las de fondo:
- larp_model perdio getGoverningPeriodId, que backend.js llama: el anuncio de
gobierno no salia nunca, con el TypeError tragado por un catch.
- courts_view y search_view tenian selected: false, que hyperscript SI serializa
como atributo y HTML interpreta como seleccionado. La forma de upstream, el
spread condicional, es la correcta.
- pm_model perdio includeDeleted, projects_model la validacion de deadline pasado,
jobs_model el desempate por timestamp, middleware el flag de debug HTTP.
forum_view pasa a 0.9.5 conservando el filtro hot. Esto arregla un fallo activo: la
vista de la rama declaraba un cuarto parametro topic y backend.js llama con cinco
argumentos, asi que al abrir una respuesta el %clave del hilo se colaba como tema y
el hilo salia VACIO. Upstream pasa los mismos cinco argumentos a una firma de tres y
JS los ignora, que es lo correcto.
chats_view pasa a 0.9.5 y se implementa replyTo de verdad. Estaba muerto de punta a
punta: backend.js no leia ctx.query.replyTo ni pasaba el cuarto argumento a
sendMessage, asi que ningun mensaje llegaba a tener replyTo y la cita no se
renderizaba jamas. Ahora la cadena esta completa —modelo, GET, POST y vista— con
indice msgById armado antes de mezclar con las encuestas, y sin gate de movil:
responder a un mensaje no es una funcion de movil.
aria-label: encontrada la causa. No es que hyperaxe lo ignore, es que hyperscript
solo asigna como propiedad y html-element solo serializa atributos de su tabla, en
la que aria-* no esta; data-* si tiene rama propia, por eso las tablas responsive
funcionaban. Se arregla con el escape hatch attrs:{}, sin tocar node_modules.
prepare.sh copia README.md y falla si no esta: backend.js lo lee con readFileSync
sin try/catch, de modo que sin el fichero el backend muere con ENOENT al arrancar.
Verificado con la app en marcha: 17 rutas OK, los cuatro recursos que faltaban
sirviendo 200, aria-label emitido en el HTML, e interfaz intacta (10 categorias,
50 modulos, 7 accesos rapidos, 0 etiquetas vacias).
main.js resuelve el directorio de datos con process.env.HOME || resolve(__dirname,
'..','..'). En Android HOME suele llegar como "/", que no es escribible, asi que el
servicio lo fija explicitamente con Os.setenv al almacenamiento privado de la app:
ahi cuelga ~/.ssb. Tambien fija TMPDIR.
prepare.sh deja de pedir un npm install: saca el node_modules del APK base, que
viene podado —137 MB frente a los 1,4 GB de una instalacion de escritorio— y
sustituye solo el codigo. Es seguro porque las 98 dependencias son identicas entre
0.9.1 y 0.9.5: ninguna nueva, ninguna quitada, ninguna con version distinta.
El zip se comprime; sin comprimir el backend son 145 MB y la APK se iria por encima
de 200.
APK generada y verificada: 78 MB, net.laenre.oasis, versionName 0.9.5, targetSdk 35,
con libnode.so y el backend de 32 MB dentro.
Hasta ahora la APK salia re-firmando la de epsylon, y su classes.dex no implementa
WebChromeClient.onPermissionRequest. Sin ese metodo el WebView deniega por defecto
todo getUserMedia, asi que las videollamadas de Karvan no podian funcionar por mucho
que se parchease el manifest: la lista de permisos es estatica y se fija al compilar.
El wrapper cierra las dos puertas. Declara CAMERA, RECORD_AUDIO y
MODIFY_AUDIO_SETTINGS, pero no se conceden al instalar: son permisos peligrosos y se
piden en tiempo de ejecucion la primera vez que se pulsa Llamar. Si nadie llama, no
se pide nada. uses-feature required=false evita excluir dispositivos sin camara.
Reutiliza las tres librerias nativas tal cual: libnode.so es nodejs-mobile v18.20.4
oficial y libnative-lib.so son 6,8 KB con un unico simbolo JNI. De ahi que el
namespace sea com.solarnethub.oasis —JNI resuelve por nombre— mientras el
applicationId es net.laenre.oasis, que permite instalarla junto a la oficial. No
hace falta NDK ni compilar Node.
De paso corrige cosas del APK actual: targetSdk 35, trafico en claro permitido solo
hacia loopback en vez de usesCleartextTraffic global, allowBackup a false para que
adb backup no saque el .ssb, y el backend en un servicio en primer plano en el
proceso :node, que ademas es lo que permitira reiniciarlo limpio para el cambio de
identidad. El APK actual declara FOREGROUND_SERVICE pero no registra ningun servicio,
de modo que el backend muere al pasar a segundo plano.
Los binarios no se versionan: scripts/prepare.sh extrae las librerias y empaqueta el
backend. La keystore se pasa por entorno.
Verificado: ./gradlew assembleDebug construye una APK de 52 MB con las tres librerias,
targetSdk 35 y los permisos declarados; su DEX contiene onPermissionRequest,
PermissionRequest, CAMERA y RECORD_AUDIO, donde el APK actual tiene cero de los cuatro.
Falta probarla en un dispositivo real.