diff --git a/app/diario-bordo.html b/app/diario-bordo.html
index f73dc49..c8dd2b1 100644
--- a/app/diario-bordo.html
+++ b/app/diario-bordo.html
@@ -3137,6 +3137,8 @@ async function updateStorageInfo(){
setTimeout(maybeAutoFetchWeather,3000);
// Welcome screen — só pra usuários sem login
setTimeout(maybeShowWelcome,300);
+ // Retoma polling do OAuth se app foi morto durante login Google
+ setTimeout(resumePollingIfPending,500);
})();
// Re-tenta init Google Sign-In quando o script async carrega
window.addEventListener('load',()=>setTimeout(()=>{if(document.getElementById('welcome-screen').style.display==='flex')initGoogleSignIn()},500));
@@ -3146,6 +3148,8 @@ document.addEventListener('visibilitychange',async()=>{
if(anchorWatch.active&&!anchorWatch.wakeLock)await requestAnchorWakeLock();
// Reconecta WS ao voltar ao foreground
if(cloudConfigured()&&(!_wsConn||_wsConn.readyState!==WebSocket.OPEN))rtConnect();
+ // Retoma polling Google se sessão pendente
+ resumePollingIfPending();
}
});
window.addEventListener('online',()=>{if(cloudConfigured())rtConnect()});
@@ -3921,6 +3925,24 @@ function welcomeGoogleClick(){
}
let _googleAuthPolling=null;
+const PENDING_SESSION_KEY='shivao_pending_google_session';
+
+function savePendingSession(session){
+ localStorage.setItem(PENDING_SESSION_KEY,JSON.stringify({session,startedAt:Date.now()}));
+}
+function getPendingSession(){
+ try{
+ const raw=localStorage.getItem(PENDING_SESSION_KEY);
+ if(!raw)return null;
+ const d=JSON.parse(raw);
+ if(Date.now()-d.startedAt>10*60*1000){localStorage.removeItem(PENDING_SESSION_KEY);return null}
+ return d.session;
+ }catch(e){return null}
+}
+function clearPendingSession(){
+ localStorage.removeItem(PENDING_SESSION_KEY);
+}
+
async function startGoogleRedirectFlow(){
toast('Abrindo Google...');
try{
@@ -3930,34 +3952,57 @@ async function startGoogleRedirectFlow(){
if(!r.ok)throw new Error('HTTP '+r.status);
const{url,session}=await r.json();
if(!url||!session)throw new Error('servidor sem URL/session');
+ // PERSISTE session no localStorage — sobrevive a app morrer/reabrir
+ savePendingSession(session);
// Abre URL no browser EXTERNO (em Capacitor isso usa Custom Tabs)
if(window.open){window.open(url,'_blank','noopener')}else{location.href=url}
- // Inicia polling
- if(_googleAuthPolling)clearInterval(_googleAuthPolling);
- let tries=0;
- _googleAuthPolling=setInterval(async()=>{
- tries++;
- if(tries>120){clearInterval(_googleAuthPolling);toast('Tempo esgotado. Tente de novo.');return}
- try{
- const pr=await fetch(cloudUrl('/api/auth/google/poll?session='+encodeURIComponent(session)));
- if(pr.status===204)return; // ainda esperando
- if(!pr.ok)return;
- const j=await pr.json();
- if(j.accessToken&&j.refreshToken){
- clearInterval(_googleAuthPolling);
- state.auth={accessToken:j.accessToken,refreshToken:j.refreshToken,user:j.user};
- saveState();
- toast('Bem-vindo, '+(j.user.name||j.user.email));
- welcomeFinish();
- if(typeof renderAuthBox==='function')renderAuthBox();
- }
- }catch(e){console.warn('[gsi-poll]',e.message)}
- },2000);
+ // Inicia polling (também resume após reabrir o app via resumePollingIfPending)
+ startSessionPolling(session);
}catch(e){
console.warn('[gsi-redirect]',e);
toast('Erro: '+e.message);
}
}
+
+function startSessionPolling(session){
+ if(_googleAuthPolling)clearInterval(_googleAuthPolling);
+ let tries=0;
+ console.log('[gsi-poll] starting for session',session);
+ // Faz uma chamada IMEDIATA primeiro (caso já esteja pronto)
+ const pollOnce=async()=>{
+ tries++;
+ if(tries>120){clearInterval(_googleAuthPolling);_googleAuthPolling=null;clearPendingSession();toast('Tempo esgotado. Tente de novo.');return}
+ try{
+ const pr=await fetch(cloudUrl('/api/auth/google/poll?session='+encodeURIComponent(session)));
+ if(pr.status===204)return; // ainda esperando
+ if(!pr.ok)return;
+ const j=await pr.json();
+ if(j.accessToken&&j.refreshToken){
+ clearInterval(_googleAuthPolling);_googleAuthPolling=null;
+ clearPendingSession();
+ state.auth={accessToken:j.accessToken,refreshToken:j.refreshToken,user:j.user};
+ saveState();
+ toast('Bem-vindo, '+(j.user.name||j.user.email));
+ welcomeFinish();
+ if(typeof renderAuthBox==='function')renderAuthBox();
+ }
+ }catch(e){console.warn('[gsi-poll]',e.message)}
+ };
+ pollOnce(); // imediato
+ _googleAuthPolling=setInterval(pollOnce,2000);
+}
+
+// Retoma polling se app foi morto e reaberto durante OAuth
+function resumePollingIfPending(){
+ const session=getPendingSession();
+ if(!session)return false;
+ if(_googleAuthPolling)return true; // já rodando
+ if(state.auth)return clearPendingSession(),false; // já logado
+ console.log('[gsi-poll] resuming session',session);
+ toast('Verificando login Google...');
+ startSessionPolling(session);
+ return true;
+}
async function onGoogleCredential(resp){
if(!resp?.credential){toast('Sem credential do Google');return}
state.cloud.url=DEFAULT_CLOUD_URL;
diff --git a/mobile/android/app/build.gradle b/mobile/android/app/build.gradle
index 64ac06c..4948964 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 8
- versionName "1.6.1"
+ versionCode 9
+ versionName "1.6.2"
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 b3033e6..3c5e443 100644
--- a/mobile/package.json
+++ b/mobile/package.json
@@ -1,6 +1,6 @@
{
"name": "shivao-mobile",
- "version": "1.6.1",
+ "version": "1.6.2",
"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 f73dc49..c8dd2b1 100644
--- a/server/public/index.html
+++ b/server/public/index.html
@@ -3137,6 +3137,8 @@ async function updateStorageInfo(){
setTimeout(maybeAutoFetchWeather,3000);
// Welcome screen — só pra usuários sem login
setTimeout(maybeShowWelcome,300);
+ // Retoma polling do OAuth se app foi morto durante login Google
+ setTimeout(resumePollingIfPending,500);
})();
// Re-tenta init Google Sign-In quando o script async carrega
window.addEventListener('load',()=>setTimeout(()=>{if(document.getElementById('welcome-screen').style.display==='flex')initGoogleSignIn()},500));
@@ -3146,6 +3148,8 @@ document.addEventListener('visibilitychange',async()=>{
if(anchorWatch.active&&!anchorWatch.wakeLock)await requestAnchorWakeLock();
// Reconecta WS ao voltar ao foreground
if(cloudConfigured()&&(!_wsConn||_wsConn.readyState!==WebSocket.OPEN))rtConnect();
+ // Retoma polling Google se sessão pendente
+ resumePollingIfPending();
}
});
window.addEventListener('online',()=>{if(cloudConfigured())rtConnect()});
@@ -3921,6 +3925,24 @@ function welcomeGoogleClick(){
}
let _googleAuthPolling=null;
+const PENDING_SESSION_KEY='shivao_pending_google_session';
+
+function savePendingSession(session){
+ localStorage.setItem(PENDING_SESSION_KEY,JSON.stringify({session,startedAt:Date.now()}));
+}
+function getPendingSession(){
+ try{
+ const raw=localStorage.getItem(PENDING_SESSION_KEY);
+ if(!raw)return null;
+ const d=JSON.parse(raw);
+ if(Date.now()-d.startedAt>10*60*1000){localStorage.removeItem(PENDING_SESSION_KEY);return null}
+ return d.session;
+ }catch(e){return null}
+}
+function clearPendingSession(){
+ localStorage.removeItem(PENDING_SESSION_KEY);
+}
+
async function startGoogleRedirectFlow(){
toast('Abrindo Google...');
try{
@@ -3930,34 +3952,57 @@ async function startGoogleRedirectFlow(){
if(!r.ok)throw new Error('HTTP '+r.status);
const{url,session}=await r.json();
if(!url||!session)throw new Error('servidor sem URL/session');
+ // PERSISTE session no localStorage — sobrevive a app morrer/reabrir
+ savePendingSession(session);
// Abre URL no browser EXTERNO (em Capacitor isso usa Custom Tabs)
if(window.open){window.open(url,'_blank','noopener')}else{location.href=url}
- // Inicia polling
- if(_googleAuthPolling)clearInterval(_googleAuthPolling);
- let tries=0;
- _googleAuthPolling=setInterval(async()=>{
- tries++;
- if(tries>120){clearInterval(_googleAuthPolling);toast('Tempo esgotado. Tente de novo.');return}
- try{
- const pr=await fetch(cloudUrl('/api/auth/google/poll?session='+encodeURIComponent(session)));
- if(pr.status===204)return; // ainda esperando
- if(!pr.ok)return;
- const j=await pr.json();
- if(j.accessToken&&j.refreshToken){
- clearInterval(_googleAuthPolling);
- state.auth={accessToken:j.accessToken,refreshToken:j.refreshToken,user:j.user};
- saveState();
- toast('Bem-vindo, '+(j.user.name||j.user.email));
- welcomeFinish();
- if(typeof renderAuthBox==='function')renderAuthBox();
- }
- }catch(e){console.warn('[gsi-poll]',e.message)}
- },2000);
+ // Inicia polling (também resume após reabrir o app via resumePollingIfPending)
+ startSessionPolling(session);
}catch(e){
console.warn('[gsi-redirect]',e);
toast('Erro: '+e.message);
}
}
+
+function startSessionPolling(session){
+ if(_googleAuthPolling)clearInterval(_googleAuthPolling);
+ let tries=0;
+ console.log('[gsi-poll] starting for session',session);
+ // Faz uma chamada IMEDIATA primeiro (caso já esteja pronto)
+ const pollOnce=async()=>{
+ tries++;
+ if(tries>120){clearInterval(_googleAuthPolling);_googleAuthPolling=null;clearPendingSession();toast('Tempo esgotado. Tente de novo.');return}
+ try{
+ const pr=await fetch(cloudUrl('/api/auth/google/poll?session='+encodeURIComponent(session)));
+ if(pr.status===204)return; // ainda esperando
+ if(!pr.ok)return;
+ const j=await pr.json();
+ if(j.accessToken&&j.refreshToken){
+ clearInterval(_googleAuthPolling);_googleAuthPolling=null;
+ clearPendingSession();
+ state.auth={accessToken:j.accessToken,refreshToken:j.refreshToken,user:j.user};
+ saveState();
+ toast('Bem-vindo, '+(j.user.name||j.user.email));
+ welcomeFinish();
+ if(typeof renderAuthBox==='function')renderAuthBox();
+ }
+ }catch(e){console.warn('[gsi-poll]',e.message)}
+ };
+ pollOnce(); // imediato
+ _googleAuthPolling=setInterval(pollOnce,2000);
+}
+
+// Retoma polling se app foi morto e reaberto durante OAuth
+function resumePollingIfPending(){
+ const session=getPendingSession();
+ if(!session)return false;
+ if(_googleAuthPolling)return true; // já rodando
+ if(state.auth)return clearPendingSession(),false; // já logado
+ console.log('[gsi-poll] resuming session',session);
+ toast('Verificando login Google...');
+ startSessionPolling(session);
+ return true;
+}
async function onGoogleCredential(resp){
if(!resp?.credential){toast('Sem credential do Google');return}
state.cloud.url=DEFAULT_CLOUD_URL;
diff --git a/server/src/index.js b/server/src/index.js
index d59e1fe..c7443a2 100644
--- a/server/src/index.js
+++ b/server/src/index.js
@@ -347,7 +347,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.6.1/Shivao-v1.6.1.apk';
+const LATEST_APK_URL = 'https://git.pontualtech.work/karlao/shivao-projeto/releases/download/v1.6.2/Shivao-v1.6.2.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)