From 0c0b2d28253b9fb13a3cb8a48a4e06acc878476a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?PontualTech=20/=20Karl=C3=A3o?= Date: Wed, 29 Apr 2026 16:01:23 -0300 Subject: [PATCH] feat(ble): breadcrumb persistente sobrevive crash WebView v1.10.18 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Karlão reportou: APK fecha 'em seguida' ao mandar parear. v1.10.17 removeu wake-up do path Capacitor mas crash ainda persiste — agora é em ble.requestDevice ou ble.connect/getServices. Sem alert popup = crash nativo lado Java do plugin BLE. Try/catch JS não captura. Solução: breadcrumb em localStorage ANTES de cada chamada nativa. bleCrumb(step) grava 'shivao_ble_last_step' no disco antes de: - ensureBleNativeReady - requestDevice - selected: - ble.connect - ble.getServices Se app crashar, próxima abertura lê o breadcrumb e mostra alert '⚠ Crash detectado · Última ação: ble.connect @ 2026...' — daí descobrimos exatamente onde o plugin Java explode. Co-Authored-By: Claude Opus 4.7 (1M context) --- app/diario-bordo.html | 27 +++++++++++++++++++++++---- mobile/android/app/build.gradle | 4 ++-- mobile/package.json | 2 +- server/public/index.html | 27 +++++++++++++++++++++++---- server/public/sw.js | 2 +- server/src/index.js | 2 +- 6 files changed, 51 insertions(+), 13 deletions(-) diff --git a/app/diario-bordo.html b/app/diario-bordo.html index 7b9ede4..3dacbfc 100644 --- a/app/diario-bordo.html +++ b/app/diario-bordo.html @@ -3749,6 +3749,16 @@ async function updateStorageInfo(){ (async()=>{ // Wrapper try/catch pra capturar crash no boot (Capacitor WebView pode fechar silenciosamente) try{ + // Detecta crash BLE da sessão anterior via breadcrumb + try{ + const lastStep=localStorage.getItem('shivao_ble_last_step'); + if(lastStep){ + setTimeout(()=>{ + try{alert('⚠ Crash detectado na sessão anterior!\nÚltima ação BLE: '+lastStep+'\n\nMe envie esta mensagem.')}catch{} + },2000); + localStorage.removeItem('shivao_ble_last_step'); + } + }catch{} await openDB(); try{loadState()}catch(e){console.error('[boot] loadState',e);try{localStorage.removeItem(STORAGE_KEY)}catch{}/* corrupt state — reset */} // Migration defensiva: limpa entries inválidas em state.btDevices @@ -5885,6 +5895,12 @@ function setBleDiag(msg,type){ console.log('[ble]',msg); } +// Breadcrumb persistente: grava ANTES de cada chamada nativa pra sobreviver crash do WebView +function bleCrumb(step){ + try{localStorage.setItem('shivao_ble_last_step',step+' @ '+new Date().toISOString())}catch{} +} +function bleCrumbClear(){try{localStorage.removeItem('shivao_ble_last_step')}catch{}} + async function pairBluetoothDevice(){ const backend=bleBackend(); setBleDiag('Backend: '+(backend||'NENHUM'),backend?'info':'err'); @@ -5892,8 +5908,10 @@ async function pairBluetoothDevice(){ try{ let deviceId,deviceName; if(backend==='capacitor'){ + bleCrumb('ensureBleNativeReady'); setBleDiag('Inicializando plugin nativo...'); await ensureBleNativeReady(); + bleCrumb('requestDevice'); setBleDiag('Plugin OK · abrindo picker...'); const ble=window.Capacitor.Plugins.BluetoothLe; const result=await ble.requestDevice({ @@ -5901,9 +5919,10 @@ async function pairBluetoothDevice(){ optionalServices:[BLE_BATTERY_SERVICE,BLE_DEVICE_INFO], allowDuplicates:false, }); - if(!result?.deviceId){setBleDiag('Picker cancelado','warn');return} + if(!result?.deviceId){bleCrumbClear();setBleDiag('Picker cancelado','warn');return} deviceId=result.deviceId; deviceName=result.name||'Dispositivo BLE'; + bleCrumb('selected:'+deviceName); setBleDiag('Selecionado: '+deviceName+' ('+deviceId+')','ok'); }else{ setBleDiag('Abrindo picker do navegador...'); @@ -5963,6 +5982,7 @@ async function connectAndRead(deviceId,deviceName){ if(backend==='capacitor'){ const ble=window.Capacitor.Plugins.BluetoothLe; try{ + bleCrumb('ble.connect'); await ble.connect({deviceId,timeout:30000}); setBleDiag('GATT conectado','ok'); }catch(e){ @@ -5972,13 +5992,12 @@ async function connectAndRead(deviceId,deviceName){ const conn=_bleConnections.get(deviceId)||{}; conn.backend='capacitor';conn.deviceId=deviceId;conn.connected=true; _bleConnections.set(deviceId,conn); - // Discover all services pra diagnóstico + auto-detect protocols try{ + bleCrumb('ble.getServices'); const r=await ble.getServices({deviceId}); const svcs=(r.services||r||[]).map(s=>s.uuid||s).slice(0,8); setBleDiag('Serviços encontrados: '+svcs.length+' ('+svcs.map(u=>u.slice(4,8)).join(', ')+')','info'); info.services=svcs; - // Auto-detect: service ff00 = JBD/LLT Power BMS const hasJbd=svcs.some(u=>u.toLowerCase().startsWith('0000ff00')); if(hasJbd){ setBleDiag('🔋 JBD BMS protocol detectado!','ok'); @@ -6461,7 +6480,7 @@ async function removeBluetoothDevice(id){ renderBluetoothCard(); } -const APP_VERSION='1.10.17'; +const APP_VERSION='1.10.18'; function renderBluetoothCard(){ const el=document.getElementById('bt-list'); const supportEl=document.getElementById('bt-support'); diff --git a/mobile/android/app/build.gradle b/mobile/android/app/build.gradle index b42bb9e..fcafd1d 100644 --- a/mobile/android/app/build.gradle +++ b/mobile/android/app/build.gradle @@ -7,8 +7,8 @@ android { applicationId "br.com.pontualtech.shivao" minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 30 - versionName "1.10.17" + versionCode 31 + versionName "1.10.18" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" aaptOptions { // Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps. diff --git a/mobile/package.json b/mobile/package.json index 8beb1e8..647b9d7 100644 --- a/mobile/package.json +++ b/mobile/package.json @@ -1,6 +1,6 @@ { "name": "shivao-mobile", - "version": "1.10.17", + "version": "1.10.18", "description": "Shivao app nativo (Capacitor wrapper Android/iOS)", "main": "index.js", "type": "module", diff --git a/server/public/index.html b/server/public/index.html index 7b9ede4..3dacbfc 100644 --- a/server/public/index.html +++ b/server/public/index.html @@ -3749,6 +3749,16 @@ async function updateStorageInfo(){ (async()=>{ // Wrapper try/catch pra capturar crash no boot (Capacitor WebView pode fechar silenciosamente) try{ + // Detecta crash BLE da sessão anterior via breadcrumb + try{ + const lastStep=localStorage.getItem('shivao_ble_last_step'); + if(lastStep){ + setTimeout(()=>{ + try{alert('⚠ Crash detectado na sessão anterior!\nÚltima ação BLE: '+lastStep+'\n\nMe envie esta mensagem.')}catch{} + },2000); + localStorage.removeItem('shivao_ble_last_step'); + } + }catch{} await openDB(); try{loadState()}catch(e){console.error('[boot] loadState',e);try{localStorage.removeItem(STORAGE_KEY)}catch{}/* corrupt state — reset */} // Migration defensiva: limpa entries inválidas em state.btDevices @@ -5885,6 +5895,12 @@ function setBleDiag(msg,type){ console.log('[ble]',msg); } +// Breadcrumb persistente: grava ANTES de cada chamada nativa pra sobreviver crash do WebView +function bleCrumb(step){ + try{localStorage.setItem('shivao_ble_last_step',step+' @ '+new Date().toISOString())}catch{} +} +function bleCrumbClear(){try{localStorage.removeItem('shivao_ble_last_step')}catch{}} + async function pairBluetoothDevice(){ const backend=bleBackend(); setBleDiag('Backend: '+(backend||'NENHUM'),backend?'info':'err'); @@ -5892,8 +5908,10 @@ async function pairBluetoothDevice(){ try{ let deviceId,deviceName; if(backend==='capacitor'){ + bleCrumb('ensureBleNativeReady'); setBleDiag('Inicializando plugin nativo...'); await ensureBleNativeReady(); + bleCrumb('requestDevice'); setBleDiag('Plugin OK · abrindo picker...'); const ble=window.Capacitor.Plugins.BluetoothLe; const result=await ble.requestDevice({ @@ -5901,9 +5919,10 @@ async function pairBluetoothDevice(){ optionalServices:[BLE_BATTERY_SERVICE,BLE_DEVICE_INFO], allowDuplicates:false, }); - if(!result?.deviceId){setBleDiag('Picker cancelado','warn');return} + if(!result?.deviceId){bleCrumbClear();setBleDiag('Picker cancelado','warn');return} deviceId=result.deviceId; deviceName=result.name||'Dispositivo BLE'; + bleCrumb('selected:'+deviceName); setBleDiag('Selecionado: '+deviceName+' ('+deviceId+')','ok'); }else{ setBleDiag('Abrindo picker do navegador...'); @@ -5963,6 +5982,7 @@ async function connectAndRead(deviceId,deviceName){ if(backend==='capacitor'){ const ble=window.Capacitor.Plugins.BluetoothLe; try{ + bleCrumb('ble.connect'); await ble.connect({deviceId,timeout:30000}); setBleDiag('GATT conectado','ok'); }catch(e){ @@ -5972,13 +5992,12 @@ async function connectAndRead(deviceId,deviceName){ const conn=_bleConnections.get(deviceId)||{}; conn.backend='capacitor';conn.deviceId=deviceId;conn.connected=true; _bleConnections.set(deviceId,conn); - // Discover all services pra diagnóstico + auto-detect protocols try{ + bleCrumb('ble.getServices'); const r=await ble.getServices({deviceId}); const svcs=(r.services||r||[]).map(s=>s.uuid||s).slice(0,8); setBleDiag('Serviços encontrados: '+svcs.length+' ('+svcs.map(u=>u.slice(4,8)).join(', ')+')','info'); info.services=svcs; - // Auto-detect: service ff00 = JBD/LLT Power BMS const hasJbd=svcs.some(u=>u.toLowerCase().startsWith('0000ff00')); if(hasJbd){ setBleDiag('🔋 JBD BMS protocol detectado!','ok'); @@ -6461,7 +6480,7 @@ async function removeBluetoothDevice(id){ renderBluetoothCard(); } -const APP_VERSION='1.10.17'; +const APP_VERSION='1.10.18'; function renderBluetoothCard(){ const el=document.getElementById('bt-list'); const supportEl=document.getElementById('bt-support'); diff --git a/server/public/sw.js b/server/public/sw.js index f661c19..8d8919d 100644 --- a/server/public/sw.js +++ b/server/public/sw.js @@ -1,7 +1,7 @@ // Shivao Service Worker — offline real // Estratégia: shell precachado, tiles cache-first, windy network-first, /api passa direto. // Versão usada nos cache names — bumpa essa string pra invalidar caches antigos em deploys. -const VERSION = 'shivao-v1.10.17'; +const VERSION = 'shivao-v1.10.18'; const SHELL_CACHE = `shivao-shell-${VERSION}`; const TILES_CACHE = 'shivao-tiles-v1'; // separado pra não invalidar tiles em update do shell const WINDY_CACHE = `shivao-windy-${VERSION}`; diff --git a/server/src/index.js b/server/src/index.js index 9aa5c01..fff6380 100644 --- a/server/src/index.js +++ b/server/src/index.js @@ -413,7 +413,7 @@ app.get('/.well-known/assetlinks.json', (req, res) => { }); // Atalho: /apk redireciona pra última APK release no Forgejo -const LATEST_APK_URL = 'https://git.pontualtech.work/karlao/shivao-projeto/releases/download/v1.10.17/Shivao-v1.10.17.apk'; +const LATEST_APK_URL = 'https://git.pontualtech.work/karlao/shivao-projeto/releases/download/v1.10.18/Shivao-v1.10.18.apk'; app.get('/apk', (req, res) => res.redirect(302, LATEST_APK_URL)); // Página A4 imprimível com QR Code + instruções (cola no barco/marina)