feat(ble): breadcrumb persistente sobrevive crash WebView v1.10.18
Some checks are pending
Build Android (APK + AAB) / build-android (push) Waiting to run
Some checks are pending
Build Android (APK + AAB) / build-android (push) Waiting to run
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:<name> - 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) <noreply@anthropic.com>
This commit is contained in:
parent
70b123735e
commit
0c0b2d2825
6 changed files with 51 additions and 13 deletions
|
|
@ -3749,6 +3749,16 @@ async function updateStorageInfo(){
|
||||||
(async()=>{
|
(async()=>{
|
||||||
// Wrapper try/catch pra capturar crash no boot (Capacitor WebView pode fechar silenciosamente)
|
// Wrapper try/catch pra capturar crash no boot (Capacitor WebView pode fechar silenciosamente)
|
||||||
try{
|
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();
|
await openDB();
|
||||||
try{loadState()}catch(e){console.error('[boot] loadState',e);try{localStorage.removeItem(STORAGE_KEY)}catch{}/* corrupt state — reset */}
|
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
|
// Migration defensiva: limpa entries inválidas em state.btDevices
|
||||||
|
|
@ -5885,6 +5895,12 @@ function setBleDiag(msg,type){
|
||||||
console.log('[ble]',msg);
|
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(){
|
async function pairBluetoothDevice(){
|
||||||
const backend=bleBackend();
|
const backend=bleBackend();
|
||||||
setBleDiag('Backend: '+(backend||'NENHUM'),backend?'info':'err');
|
setBleDiag('Backend: '+(backend||'NENHUM'),backend?'info':'err');
|
||||||
|
|
@ -5892,8 +5908,10 @@ async function pairBluetoothDevice(){
|
||||||
try{
|
try{
|
||||||
let deviceId,deviceName;
|
let deviceId,deviceName;
|
||||||
if(backend==='capacitor'){
|
if(backend==='capacitor'){
|
||||||
|
bleCrumb('ensureBleNativeReady');
|
||||||
setBleDiag('Inicializando plugin nativo...');
|
setBleDiag('Inicializando plugin nativo...');
|
||||||
await ensureBleNativeReady();
|
await ensureBleNativeReady();
|
||||||
|
bleCrumb('requestDevice');
|
||||||
setBleDiag('Plugin OK · abrindo picker...');
|
setBleDiag('Plugin OK · abrindo picker...');
|
||||||
const ble=window.Capacitor.Plugins.BluetoothLe;
|
const ble=window.Capacitor.Plugins.BluetoothLe;
|
||||||
const result=await ble.requestDevice({
|
const result=await ble.requestDevice({
|
||||||
|
|
@ -5901,9 +5919,10 @@ async function pairBluetoothDevice(){
|
||||||
optionalServices:[BLE_BATTERY_SERVICE,BLE_DEVICE_INFO],
|
optionalServices:[BLE_BATTERY_SERVICE,BLE_DEVICE_INFO],
|
||||||
allowDuplicates:false,
|
allowDuplicates:false,
|
||||||
});
|
});
|
||||||
if(!result?.deviceId){setBleDiag('Picker cancelado','warn');return}
|
if(!result?.deviceId){bleCrumbClear();setBleDiag('Picker cancelado','warn');return}
|
||||||
deviceId=result.deviceId;
|
deviceId=result.deviceId;
|
||||||
deviceName=result.name||'Dispositivo BLE';
|
deviceName=result.name||'Dispositivo BLE';
|
||||||
|
bleCrumb('selected:'+deviceName);
|
||||||
setBleDiag('Selecionado: '+deviceName+' ('+deviceId+')','ok');
|
setBleDiag('Selecionado: '+deviceName+' ('+deviceId+')','ok');
|
||||||
}else{
|
}else{
|
||||||
setBleDiag('Abrindo picker do navegador...');
|
setBleDiag('Abrindo picker do navegador...');
|
||||||
|
|
@ -5963,6 +5982,7 @@ async function connectAndRead(deviceId,deviceName){
|
||||||
if(backend==='capacitor'){
|
if(backend==='capacitor'){
|
||||||
const ble=window.Capacitor.Plugins.BluetoothLe;
|
const ble=window.Capacitor.Plugins.BluetoothLe;
|
||||||
try{
|
try{
|
||||||
|
bleCrumb('ble.connect');
|
||||||
await ble.connect({deviceId,timeout:30000});
|
await ble.connect({deviceId,timeout:30000});
|
||||||
setBleDiag('GATT conectado','ok');
|
setBleDiag('GATT conectado','ok');
|
||||||
}catch(e){
|
}catch(e){
|
||||||
|
|
@ -5972,13 +5992,12 @@ async function connectAndRead(deviceId,deviceName){
|
||||||
const conn=_bleConnections.get(deviceId)||{};
|
const conn=_bleConnections.get(deviceId)||{};
|
||||||
conn.backend='capacitor';conn.deviceId=deviceId;conn.connected=true;
|
conn.backend='capacitor';conn.deviceId=deviceId;conn.connected=true;
|
||||||
_bleConnections.set(deviceId,conn);
|
_bleConnections.set(deviceId,conn);
|
||||||
// Discover all services pra diagnóstico + auto-detect protocols
|
|
||||||
try{
|
try{
|
||||||
|
bleCrumb('ble.getServices');
|
||||||
const r=await ble.getServices({deviceId});
|
const r=await ble.getServices({deviceId});
|
||||||
const svcs=(r.services||r||[]).map(s=>s.uuid||s).slice(0,8);
|
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');
|
setBleDiag('Serviços encontrados: '+svcs.length+' ('+svcs.map(u=>u.slice(4,8)).join(', ')+')','info');
|
||||||
info.services=svcs;
|
info.services=svcs;
|
||||||
// Auto-detect: service ff00 = JBD/LLT Power BMS
|
|
||||||
const hasJbd=svcs.some(u=>u.toLowerCase().startsWith('0000ff00'));
|
const hasJbd=svcs.some(u=>u.toLowerCase().startsWith('0000ff00'));
|
||||||
if(hasJbd){
|
if(hasJbd){
|
||||||
setBleDiag('🔋 JBD BMS protocol detectado!','ok');
|
setBleDiag('🔋 JBD BMS protocol detectado!','ok');
|
||||||
|
|
@ -6461,7 +6480,7 @@ async function removeBluetoothDevice(id){
|
||||||
renderBluetoothCard();
|
renderBluetoothCard();
|
||||||
}
|
}
|
||||||
|
|
||||||
const APP_VERSION='1.10.17';
|
const APP_VERSION='1.10.18';
|
||||||
function renderBluetoothCard(){
|
function renderBluetoothCard(){
|
||||||
const el=document.getElementById('bt-list');
|
const el=document.getElementById('bt-list');
|
||||||
const supportEl=document.getElementById('bt-support');
|
const supportEl=document.getElementById('bt-support');
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,8 @@ android {
|
||||||
applicationId "br.com.pontualtech.shivao"
|
applicationId "br.com.pontualtech.shivao"
|
||||||
minSdkVersion rootProject.ext.minSdkVersion
|
minSdkVersion rootProject.ext.minSdkVersion
|
||||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||||
versionCode 30
|
versionCode 31
|
||||||
versionName "1.10.17"
|
versionName "1.10.18"
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
aaptOptions {
|
aaptOptions {
|
||||||
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.
|
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "shivao-mobile",
|
"name": "shivao-mobile",
|
||||||
"version": "1.10.17",
|
"version": "1.10.18",
|
||||||
"description": "Shivao app nativo (Capacitor wrapper Android/iOS)",
|
"description": "Shivao app nativo (Capacitor wrapper Android/iOS)",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|
|
||||||
|
|
@ -3749,6 +3749,16 @@ async function updateStorageInfo(){
|
||||||
(async()=>{
|
(async()=>{
|
||||||
// Wrapper try/catch pra capturar crash no boot (Capacitor WebView pode fechar silenciosamente)
|
// Wrapper try/catch pra capturar crash no boot (Capacitor WebView pode fechar silenciosamente)
|
||||||
try{
|
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();
|
await openDB();
|
||||||
try{loadState()}catch(e){console.error('[boot] loadState',e);try{localStorage.removeItem(STORAGE_KEY)}catch{}/* corrupt state — reset */}
|
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
|
// Migration defensiva: limpa entries inválidas em state.btDevices
|
||||||
|
|
@ -5885,6 +5895,12 @@ function setBleDiag(msg,type){
|
||||||
console.log('[ble]',msg);
|
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(){
|
async function pairBluetoothDevice(){
|
||||||
const backend=bleBackend();
|
const backend=bleBackend();
|
||||||
setBleDiag('Backend: '+(backend||'NENHUM'),backend?'info':'err');
|
setBleDiag('Backend: '+(backend||'NENHUM'),backend?'info':'err');
|
||||||
|
|
@ -5892,8 +5908,10 @@ async function pairBluetoothDevice(){
|
||||||
try{
|
try{
|
||||||
let deviceId,deviceName;
|
let deviceId,deviceName;
|
||||||
if(backend==='capacitor'){
|
if(backend==='capacitor'){
|
||||||
|
bleCrumb('ensureBleNativeReady');
|
||||||
setBleDiag('Inicializando plugin nativo...');
|
setBleDiag('Inicializando plugin nativo...');
|
||||||
await ensureBleNativeReady();
|
await ensureBleNativeReady();
|
||||||
|
bleCrumb('requestDevice');
|
||||||
setBleDiag('Plugin OK · abrindo picker...');
|
setBleDiag('Plugin OK · abrindo picker...');
|
||||||
const ble=window.Capacitor.Plugins.BluetoothLe;
|
const ble=window.Capacitor.Plugins.BluetoothLe;
|
||||||
const result=await ble.requestDevice({
|
const result=await ble.requestDevice({
|
||||||
|
|
@ -5901,9 +5919,10 @@ async function pairBluetoothDevice(){
|
||||||
optionalServices:[BLE_BATTERY_SERVICE,BLE_DEVICE_INFO],
|
optionalServices:[BLE_BATTERY_SERVICE,BLE_DEVICE_INFO],
|
||||||
allowDuplicates:false,
|
allowDuplicates:false,
|
||||||
});
|
});
|
||||||
if(!result?.deviceId){setBleDiag('Picker cancelado','warn');return}
|
if(!result?.deviceId){bleCrumbClear();setBleDiag('Picker cancelado','warn');return}
|
||||||
deviceId=result.deviceId;
|
deviceId=result.deviceId;
|
||||||
deviceName=result.name||'Dispositivo BLE';
|
deviceName=result.name||'Dispositivo BLE';
|
||||||
|
bleCrumb('selected:'+deviceName);
|
||||||
setBleDiag('Selecionado: '+deviceName+' ('+deviceId+')','ok');
|
setBleDiag('Selecionado: '+deviceName+' ('+deviceId+')','ok');
|
||||||
}else{
|
}else{
|
||||||
setBleDiag('Abrindo picker do navegador...');
|
setBleDiag('Abrindo picker do navegador...');
|
||||||
|
|
@ -5963,6 +5982,7 @@ async function connectAndRead(deviceId,deviceName){
|
||||||
if(backend==='capacitor'){
|
if(backend==='capacitor'){
|
||||||
const ble=window.Capacitor.Plugins.BluetoothLe;
|
const ble=window.Capacitor.Plugins.BluetoothLe;
|
||||||
try{
|
try{
|
||||||
|
bleCrumb('ble.connect');
|
||||||
await ble.connect({deviceId,timeout:30000});
|
await ble.connect({deviceId,timeout:30000});
|
||||||
setBleDiag('GATT conectado','ok');
|
setBleDiag('GATT conectado','ok');
|
||||||
}catch(e){
|
}catch(e){
|
||||||
|
|
@ -5972,13 +5992,12 @@ async function connectAndRead(deviceId,deviceName){
|
||||||
const conn=_bleConnections.get(deviceId)||{};
|
const conn=_bleConnections.get(deviceId)||{};
|
||||||
conn.backend='capacitor';conn.deviceId=deviceId;conn.connected=true;
|
conn.backend='capacitor';conn.deviceId=deviceId;conn.connected=true;
|
||||||
_bleConnections.set(deviceId,conn);
|
_bleConnections.set(deviceId,conn);
|
||||||
// Discover all services pra diagnóstico + auto-detect protocols
|
|
||||||
try{
|
try{
|
||||||
|
bleCrumb('ble.getServices');
|
||||||
const r=await ble.getServices({deviceId});
|
const r=await ble.getServices({deviceId});
|
||||||
const svcs=(r.services||r||[]).map(s=>s.uuid||s).slice(0,8);
|
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');
|
setBleDiag('Serviços encontrados: '+svcs.length+' ('+svcs.map(u=>u.slice(4,8)).join(', ')+')','info');
|
||||||
info.services=svcs;
|
info.services=svcs;
|
||||||
// Auto-detect: service ff00 = JBD/LLT Power BMS
|
|
||||||
const hasJbd=svcs.some(u=>u.toLowerCase().startsWith('0000ff00'));
|
const hasJbd=svcs.some(u=>u.toLowerCase().startsWith('0000ff00'));
|
||||||
if(hasJbd){
|
if(hasJbd){
|
||||||
setBleDiag('🔋 JBD BMS protocol detectado!','ok');
|
setBleDiag('🔋 JBD BMS protocol detectado!','ok');
|
||||||
|
|
@ -6461,7 +6480,7 @@ async function removeBluetoothDevice(id){
|
||||||
renderBluetoothCard();
|
renderBluetoothCard();
|
||||||
}
|
}
|
||||||
|
|
||||||
const APP_VERSION='1.10.17';
|
const APP_VERSION='1.10.18';
|
||||||
function renderBluetoothCard(){
|
function renderBluetoothCard(){
|
||||||
const el=document.getElementById('bt-list');
|
const el=document.getElementById('bt-list');
|
||||||
const supportEl=document.getElementById('bt-support');
|
const supportEl=document.getElementById('bt-support');
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
// Shivao Service Worker — offline real
|
// Shivao Service Worker — offline real
|
||||||
// Estratégia: shell precachado, tiles cache-first, windy network-first, /api passa direto.
|
// 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.
|
// 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 SHELL_CACHE = `shivao-shell-${VERSION}`;
|
||||||
const TILES_CACHE = 'shivao-tiles-v1'; // separado pra não invalidar tiles em update do shell
|
const TILES_CACHE = 'shivao-tiles-v1'; // separado pra não invalidar tiles em update do shell
|
||||||
const WINDY_CACHE = `shivao-windy-${VERSION}`;
|
const WINDY_CACHE = `shivao-windy-${VERSION}`;
|
||||||
|
|
|
||||||
|
|
@ -413,7 +413,7 @@ app.get('/.well-known/assetlinks.json', (req, res) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
// Atalho: /apk redireciona pra última APK release no Forgejo
|
// 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));
|
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)
|
// Página A4 imprimível com QR Code + instruções (cola no barco/marina)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue