API del puente de JavaScript
Inicio de sesión y passkeys
Un auténtico selector de cuentas de Google, nativo en lugar de una redirección web, y passkeys mediante Credential Manager de Android: navigator.credentials funciona como en Chrome, o usa AppMint.passkey con las opciones JSON de tu servidor.
AppMint.passkey ayudante#
window.AppMint.passkey — create(options), get(options), isAvailable(), origin(), assetLinks()
Ejemplo
Passkeys (inicia sesión con huella, rostro o bloqueo de pantalla, sin contraseña) mediante el administrador de passkeys de Android, usando las opciones JSON que genera tu servidor de passkeys.
Devuelve: create(optionsJSON) → Promise del RegistrationResponseJSON; get(optionsJSON) → Promise del AuthenticationResponseJSON (envía cualquiera a tu servidor para verificarlo). Ambos se rechazan con un DOMException (err.name y err.reason = un código corto). isAvailable() → booleano, origin() → cadena, assetLinks() → arreglo; todos de inmediato. Necesita: Android 9+ con Google Play Services, un servidor de passkeys y los dos pasos de configuración siguientes. Sin interruptor de compilación.
Iniciar sesión:
async function signInWithPasskey() {
if (!window.AppMint || !window.AppMint.passkey || !window.AppMint.passkey.isAvailable()) {
alert('Passkeys are not available on this phone.');
return;
}
var options = await (await fetch('https://your-domain.com/passkey/login-options')).json();
var answer;
try {
answer = await window.AppMint.passkey.get(options); // rpId must be your domain
} catch (err) {
if (err.name === 'NotAllowedError') return; // cancelled, or no passkey on this phone
alert('Passkey problem: ' + err.message);
return;
}
var res = await fetch('https://your-domain.com/passkey/login-verify', {
method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(answer)
});
if (res.ok) showAccount();
}Crea una (después de que el usuario haya iniciado sesión de otra forma):
async function addPasskey() {
var options = await (await fetch('https://your-domain.com/passkey/register-options')).json();
try {
var answer = await window.AppMint.passkey.create(options);
await fetch('https://your-domain.com/passkey/register-verify', {
method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(answer)
});
} catch (err) {
if (err.name === 'InvalidStateError') alert('You already have a passkey here.');
else if (err.name !== 'NotAllowedError') alert('Passkey problem: ' + err.message);
}
}Configúralo una sola vez: ejecuta esto en la app instalada DESDE Google Play (Play firma con su propia clave):
console.log(JSON.stringify(window.AppMint.passkey.assetLinks(), null, 2));
// save as https://your-domain.com/.well-known/assetlinks.json
console.log(window.AppMint.passkey.origin());
// android:apk-key-hash:... — add to your server's expected originsNotas: err.name / err.reason: NotAllowedError (cancelled, no_credential, interrupted), InvalidStateError (ya existe), SecurityError con not_linked (assetlinks.json no incluye la app) o rp_mismatch (el rpId no es el dominio de esta página), NotSupportedError / unavailable (Android < 9 o sin Play Services), TypeError / invalid_options (una página incluida en la app no tiene dominio, así que define rp.id / rpId con tu dominio). window.AppMint.passkey solo existe en la app; en un navegador usa navigator.credentials (el mismo código también funciona en la app).
WebToApkAuth.signInWithGoogle ayudante#
window.WebToApkAuth.signInWithGoogle(webClientId)
Ejemplo
Inicia la sesión del usuario con su cuenta de Google mediante el selector de cuentas propio de Android y te da un token de ID de Google. Google bloquea su página de inicio de sesión dentro de los WebView de las apps («disallowed_useragent»); esta vía nativa no está bloqueada.
Devuelve: una Promise de {ok:true, idToken, email, displayName, profilePictureUri}. SE RECHAZA con un Error cuyo message es el código (cancelled, google_play_services_unavailable, missing_web_client_id, failed, …). Necesita: tu ID de cliente web de OAuth (termina en .apps.googleusercontent.com) y las huellas SHA-1 y SHA-256 de la app registradas en tu proyecto de Google / Firebase (del archivo _signkey_info.txt de tu compilación y de la clave de firma de la app de Play si publicas en Play). Sin interruptor de compilación.
Con Firebase Authentication (SDK compat):
var WEB_CLIENT_ID = '1234567890-abc.apps.googleusercontent.com';
document.getElementById('google-button').addEventListener('click', async function () {
if (!window.WebToApkAuth) {
alert('Google sign-in works in the app.'); // or run your normal web sign-in here
return;
}
try {
var r = await window.WebToApkAuth.signInWithGoogle(WEB_CLIENT_ID);
var cred = firebase.auth.GoogleAuthProvider.credential(r.idToken);
await firebase.auth().signInWithCredential(cred);
showWelcome(r.displayName || r.email);
} catch (err) {
if (err.message === 'cancelled') return; // user closed the picker
if (err.message === 'google_play_services_unavailable') {
alert('No Google account or Google Play services on this phone.');
return;
}
alert('Google sign-in failed: ' + err.message);
}
});Con tu propio servidor: envía el token de ID y verifícalo allí (nunca confíes en él solo en la página):
async function signInWithMyServer() {
var r = await window.WebToApkAuth.signInWithGoogle(WEB_CLIENT_ID);
await fetch('https://your-domain.com/auth/google', {
method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ idToken: r.idToken })
});
}Con Supabase Auth (pasa el token de ID a signInWithIdToken):
async function signInWithSupabase() {
if (!window.WebToApkAuth) {
return supabase.auth.signInWithOAuth({ provider: 'google' }); // normal web sign-in in a browser
}
try {
var r = await window.WebToApkAuth.signInWithGoogle(WEB_CLIENT_ID);
var res = await supabase.auth.signInWithIdToken({ provider: 'google', token: r.idToken });
if (res.error) throw res.error;
showWelcome(res.data.user.email);
} catch (err) {
if (err.message !== 'cancelled') alert('Google sign-in failed: ' + err.message);
}
}Notas: Pasa el ID de cliente web. Sin argumento, el ayudante usa window.firebaseConfig._googleWebClientId si tu página lo define y luego el cliente web del google-services.json que subiste para esta app (solo cuando ese archivo es de tu propio proyecto de Firebase con el inicio de sesión con Google activado; nunca de una compilación de AppMint Push); si no hay ninguno de estos, se rechaza con missing_web_client_id. Una página que no es la propia de la app (un sitio externo abierto dentro de la app) se rechaza con foreign_origin. Usa el ID de cliente WEB, no un ID de cliente de Android. «No credentials available» suele significar que el SHA-1 no está registrado. Este ayudante solo existe en la app; en un navegador usa tu inicio de sesión web normal. Llámalo desde un toque en un botón.
WebToApkAuth.signOutGoogle ayudante#
window.WebToApkAuth.signOutGoogle()
Ejemplo
Borra la selección guardada de inicio de sesión con Google de Android, de modo que el siguiente signInWithGoogle vuelva a mostrar el selector de cuentas en lugar de reutilizar en silencio la última cuenta.
Devuelve: una Promise de {ok:true}; se rechaza con un Error cuyo message indica qué salió mal. Necesita: nada.
Llámalo junto con tu propio cierre de sesión:
document.getElementById('sign-out').addEventListener('click', async function () {
await firebase.auth().signOut(); // your normal sign-out (Firebase here)
if (window.WebToApkAuth) {
try {
await window.WebToApkAuth.signOutGoogle();
} catch (err) {
console.log('Could not clear the Google choice: ' + err.message);
}
}
showSignInScreen();
});Un botón «Cambiar de cuenta»: borra la selección guardada y luego vuelve a iniciar sesión para que el usuario elija otra cuenta:
async function switchGoogleAccount() {
if (!window.WebToApkAuth) return;
await firebase.auth().signOut();
try { await window.WebToApkAuth.signOutGoogle(); } catch (err) { /* nothing saved to clear */ }
try {
var r = await window.WebToApkAuth.signInWithGoogle('1234567890-abc.apps.googleusercontent.com');
await firebase.auth().signInWithCredential(firebase.auth.GoogleAuthProvider.credential(r.idToken));
} catch (err) {
if (err.message !== 'cancelled') alert('Google sign-in failed: ' + err.message);
showSignInScreen();
}
}Cierra primero la sesión en tu propio servidor y luego borra la selección de Google sin dejar que un fallo bloquee el cierre de sesión:
async function signOutEverywhere() {
await fetch('https://your-domain.com/auth/logout', { method: 'POST', credentials: 'include' });
localStorage.removeItem('profile');
if (window.WebToApkAuth) {
var r = await window.WebToApkAuth.signOutGoogle()
.catch(function (err) { return { ok: false, error: err.message }; });
if (!r.ok) console.log('Google choice not cleared: ' + r.error);
}
location.replace('/login');
}Notas: Esto no cierra la sesión propia de tu app: cierra tú mismo la sesión del usuario en tu backend. Solo restablece qué cuenta de Google ofrece el teléfono la próxima vez.
OTPCredential estándar web#
navigator.credentials.get({ otp: { transport: ['sms'] } }) — WebOTP
Ejemplo
La API estándar WebOTP: el código de inicio de sesión de un SMS se rellena solo. Android muestra la hoja de Google «¿Permitir que esta app lea este mensaje?»; sin permiso de SMS.
Devuelve: una Promise de un OTPCredential {type:'otp', code}. Se rechaza con AbortError cuando el usuario toca Denegar o la señal se cancela, con InvalidStateError tras cinco minutos sin SMS, con NotSupportedError sin Google Play Services. Necesita: nada que activar. El SMS debe TERMINAR con una línea que nombre el host de esta página: @<host> #<code>.
const input = document.querySelector('input[autocomplete="one-time-code"]');
if ('OTPCredential' in window) {
const ac = new AbortController();
document.querySelector('form').addEventListener('submit', () => ac.abort());
navigator.credentials.get({ otp: { transport: ['sms'] }, signal: ac.signal })
.then((otp) => { input.value = otp.code; input.form.requestSubmit(); })
.catch((e) => console.log('Type the code instead:', e.name));
}El SMS que envía tu servidor (la última línea es la vinculación):
Your Acme code is 482913.
@acme.example #482913Después de «Reenviar código», inicia una solicitud nueva (reemplaza la que sigue esperando, que se rechaza con AbortError):
async function resendAndWait(phone) {
await fetch('/api/send-code', {
method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ phone })
});
if (!('OTPCredential' in window)) return;
try {
const otp = await navigator.credentials.get({ otp: { transport: ['sms'] } });
await verifyCode(otp.code);
} catch (e) {
if (e.name === 'InvalidStateError') showMessage('No SMS yet. Tap Resend to try again.');
}
}Notas: el host debe ser el propio de la página: para una app basada en un sitio web es tu dominio (el SMS que Chrome ya lee); para una app sin conexión o creada con IA es appassets.androidplatform.net. Un mensaje para otro host se ignora y la app sigue esperando. Solo en la página superior (los códigos vinculados a un origen incrustado, @top #code @iframe, no son compatibles). Solo espera una solicitud a la vez; una más nueva cancela la anterior. Evento que hay detrás: appmint:otp.
__otpCancel puente#
window.WebToApk.__otpCancel(requestId: String)
Abandona una solicitud WebOTP pendiente (AbortSignal, o una solicitud más nueva la reemplazó).
Ejemplo
Transporte interno detrás de la cancelación de una solicitud WebOTP (AbortSignal). Las páginas usan la API estándar; nunca llames a esto directamente: los métodos __ pueden cambiar sin previo aviso.
Devuelve: nada. Necesita: nada que activar.
Usa la API pública:
const ac = new AbortController();
const cred = await navigator.credentials.get({ otp: { transport: ['sms'] }, signal: ac.signal });
codeInput.value = cred.code;
ac.abort(); // the pending get() rejects with AbortErrorDeja de esperar en cuanto el usuario empiece a escribir el código por sí mismo:
const input = document.querySelector('input[autocomplete="one-time-code"]');
const ac = new AbortController();
input.addEventListener('input', () => ac.abort(), { once: true });
navigator.credentials.get({ otp: { transport: ['sms'] }, signal: ac.signal })
.then((cred) => { input.value = cred.code; })
.catch((e) => { if (e.name !== 'AbortError') console.log('No code:', e.name); });Cancela la espera cuando el usuario vuelve atrás para cambiar su número de teléfono:
let otpAbort = null;
function waitForSms() {
otpAbort = new AbortController();
return navigator.credentials.get({ otp: { transport: ['sms'] }, signal: otpAbort.signal });
}
document.getElementById('change-number').addEventListener('click', () => {
if (otpAbort) { otpAbort.abort(); otpAbort = null; } // the pending get() rejects with AbortError
showPhoneStep();
});Notas: Implementado en el shell de la app (WebApiPolyfills.kt). El mismo código de página funciona sin cambios en Chrome.
__otpStart puente#
window.WebToApk.__otpStart(requestId: String)
WebOTP: espera (hasta cinco minutos) un SMS mediante la hoja SMS User Consent de Google, sin permiso de SMS. La respuesta es `appmint:otp {requestId, message|error}`.
Ejemplo
Transporte interno detrás de navigator.credentials.get({ otp: { transport: ['sms'] } }). Las páginas usan la API estándar; nunca llames a esto directamente: los métodos __ pueden cambiar sin previo aviso.
Devuelve: nada; la respuesta llega como el evento appmint:otp {requestId, message} o {requestId, error} (cancelled, timeout, unavailable). Necesita: nada que activar.
Usa la API pública:
const ac = new AbortController();
const cred = await navigator.credentials.get({ otp: { transport: ['sms'] }, signal: ac.signal });
codeInput.value = cred.code;Deja de esperar después de un minuto con una señal de tiempo de espera:
async function waitForCode(input) {
if (!('OTPCredential' in window)) return;
try {
const cred = await navigator.credentials.get({
otp: { transport: ['sms'] },
signal: AbortSignal.timeout(60000)
});
input.value = cred.code;
} catch (e) {
// TimeoutError after 60 s, AbortError when the user taps Deny
showMessage('Type the code from the SMS.');
}
}Dile al usuario qué hacer a continuación según el nombre del error:
async function readOtp() {
try {
const cred = await navigator.credentials.get({ otp: { transport: ['sms'] } });
return cred.code;
} catch (e) {
if (e.name === 'InvalidStateError') showResendButton(); // no SMS within five minutes
else if (e.name === 'NotSupportedError') showManualEntry(); // no Google Play services
return null; // AbortError: the user tapped Deny
}
}Notas: Implementado en el shell de la app (WebApiPolyfills.kt). El mismo código de página funciona sin cambios en Chrome.
passkeyAssetLinks puente#
window.WebToApk.passkeyAssetLinks(): String
El arreglo assetlinks.json (como texto) que autoriza esta app instalada para tu dominio.
Ejemplo
Genera el contenido de assetlinks.json que le dice a Android que tu dominio confía en esta app (nombre del paquete + el SHA-256 del certificado con el que está firmada la app instalada). Las páginas normalmente usan la API pública AppMint.passkey.assetLinks(), que envuelve esta llamada y devuelve un arreglo ya analizado.
Devuelve: un arreglo JSON como TEXTO, de inmediato ("[]" si no se puede leer el certificado). Necesita: nada.
Ejecútalo una vez en la app instalada DESDE Google Play y guarda el resultado como https://your-domain.com/.well-known/assetlinks.json:
if (window.AppMint && window.AppMint.passkey) {
console.log(JSON.stringify(window.AppMint.passkey.assetLinks(), null, 2));
}La llamada en bruto devuelve el texto ya formateado:
if (window.WebToApk && typeof window.WebToApk.passkeyAssetLinks === 'function') {
var text = window.WebToApk.passkeyAssetLinks();
console.log(text);
}Lee el nombre del paquete y la huella SHA-256 de la declaración, para una consola que los pide uno por uno:
if (window.AppMint && window.AppMint.passkey) {
var statements = window.AppMint.passkey.assetLinks();
if (statements.length === 0) {
console.log('The signing certificate could not be read.');
} else {
var target = statements[0].target;
console.log('Package: ' + target.package_name);
console.log('SHA-256: ' + target.sha256_cert_fingerprints[0]); // AB:CD:... upper-case, colon-separated
}
}Notas: El dominio debe ser exactamente el rpID de tu servidor (www. es un dominio distinto), servido por https y sin redirecciones. Si el archivo es incorrecto, las llamadas de passkey fallan con SecurityError (motivo not_linked).
passkeyIsAvailable puente#
window.WebToApk.passkeyIsAvailable(): Boolean
True en Android 9+ con Google Play Services: los teléfonos que pueden guardar una passkey.
Ejemplo
Te dice si este teléfono puede usar passkeys. Las páginas normalmente usan la API pública AppMint.passkey.isAvailable() (o PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable()), que envuelve esta llamada.
Devuelve: un booleano, de inmediato: true en Android 9+ con Google Play Services y un WebView lo bastante reciente para el canal de passkeys. Necesita: nada.
La API pública:
var canPasskey = !!(window.AppMint && window.AppMint.passkey && window.AppMint.passkey.isAvailable());
document.getElementById('passkey-button').hidden = !canPasskey;La llamada directa, si la necesitas:
var canPasskey = !!window.WebToApk &&
typeof window.WebToApk.passkeyIsAvailable === 'function' &&
window.WebToApk.passkeyIsAvailable() === true;Ofrece «Añadir una passkey» una vez después de un inicio de sesión con contraseña, solo en los teléfonos que pueden guardar una (la comprobación estándar, el mismo código que en Chrome):
async function offerPasskeyAfterLogin() {
var ok = !!window.PublicKeyCredential &&
await PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable();
if (!ok) return; // Android below 9 or no Google Play services
if (localStorage.getItem('passkey-offered')) return;
localStorage.setItem('passkey-offered', '1');
document.getElementById('add-passkey-banner').hidden = false;
}Notas: Cuando esto es false, AppMint.passkey.create/get se rechazan con NotSupportedError. Oculta el botón de passkey y ofrece tu otro método de inicio de sesión.
passkeyOrigin puente#
window.WebToApk.passkeyOrigin(): String
El origen que ve un servidor de passkeys desde esta app: `android:apk-key-hash:<base64url SHA-256>`.
Ejemplo
Da el origen que ve tu servidor de passkeys desde esta app: android:apk-key-hash:<base64url SHA-256 of the signing certificate>. Las páginas normalmente usan la API pública AppMint.passkey.origin(), que envuelve esta llamada.
Devuelve: una cadena, de inmediato ("" si no se puede leer el certificado). Necesita: nada.
Ejecuta esto una vez en la app instalada DESDE Google Play y añade el valor a los orígenes permitidos de tu servidor:
if (window.AppMint && window.AppMint.passkey) {
console.log(window.AppMint.passkey.origin()); // android:apk-key-hash:Lir5oIjf552K...
}La llamada directa devuelve la misma cadena:
if (window.WebToApk && typeof window.WebToApk.passkeyOrigin === 'function') {
console.log(window.WebToApk.passkeyOrigin());
}Muestra el origen en una pantalla de ajustes oculta, para poder leerlo desde la instalación de Play sin un depurador:
var out = document.getElementById('passkey-origin');
var origin = (window.AppMint && window.AppMint.passkey) ? window.AppMint.passkey.origin() : '';
if (!origin) {
out.textContent = 'Open this screen in the installed app.';
} else {
out.textContent = origin; // android:apk-key-hash:...
out.style.userSelect = 'all'; // one long-press selects the whole value
}Notas: Google Play vuelve a firmar tu app, así que un APK de prueba da un valor distinto al de la instalación de Play. Ejemplo con SimpleWebAuthn: expectedOrigin: ['https://your-domain.com', 'android:apk-key-hash:...'].
signInWithGoogle puente#
window.WebToApk.signInWithGoogle(serverClientId: String, callbackId: String)
===== Inicio de sesión nativo con Google mediante Credential Manager ===== Evita el bloqueo de OAuth de Google en el WebView de Android (error disallowed_useragent) ejecutando el flujo de inicio de sesión en Android nativo y pasando a JS solo el token de ID de Google resultante, donde el SDK web de Firebase lo consume mediante GoogleAuthProvider.credential(idToken) + signInWithCredential(...). Contrato de JS: window.WebToApk.signInWithGoogle(serverClientId, callbackId) serverClientId "" = el cliente web (tipo 3) del google-services.json propio del creador para este paquete; si no hay ninguno, error "missing_web_client_id". Una página que no es la propia de la app recibe el error "foreign_origin". -> al terminar, se llama a window.__webToApkAuth[callbackId](resultJsonString) resultJsonString = '{"ok":true,"idToken":"...","email":"...","displayName":"..."}' o '{"ok":false,"error":"<message>"}'
Ejemplo
Inicia el inicio de sesión nativo con Google (el selector de cuentas de Android) y responde con un token de ID de Google. Las páginas normalmente usan el ayudante con Promise WebToApkAuth.signInWithGoogle(webClientId), que envuelve esta llamada.
Devuelve: nada. La respuesta llega después: el shell llama a window.__webToApkAuth[callbackId](json) con '{"ok":true,"idToken":"...","email":"...","displayName":"...","profilePictureUri":"..."}' o '{"ok":false,"error":"<code>","detail":"..."}', y luego elimina esa entrada. Necesita: tu ID de cliente web de OAuth y el SHA-1 / SHA-256 de la app registrados en tu proyecto de Google o Firebase. Sin interruptor de compilación.
El ayudante (recomendado):
async function googleSignIn() {
if (!window.WebToApkAuth) return null;
try {
var r = await window.WebToApkAuth.signInWithGoogle('1234567890-abc.apps.googleusercontent.com');
return r.idToken;
} catch (err) {
if (err.message !== 'cancelled') alert('Google sign-in failed: ' + err.message);
return null;
}
}La llamada en bruto (orden de los argumentos: ID de cliente web y luego el id de tu callback):
function googleSignInRaw(done) {
if (!window.WebToApk || typeof window.WebToApk.signInWithGoogle !== 'function') return;
var id = 'g_' + Date.now();
window.__webToApkAuth = window.__webToApkAuth || {};
window.__webToApkAuth[id] = function (json) {
var r = JSON.parse(json);
if (r.ok) done(r.idToken, r.email);
else if (r.error !== 'cancelled') alert('Google sign-in failed: ' + r.error);
};
window.WebToApk.signInWithGoogle('1234567890-abc.apps.googleusercontent.com', id);
}La llamada en bruto con un ID de cliente vacío, que usa el cliente web del google-services.json que subiste:
function googleSignInFromUploadedFile() {
return new Promise(function (resolve, reject) {
if (!window.WebToApk || typeof window.WebToApk.signInWithGoogle !== 'function') {
return reject(new Error('not_in_app'));
}
var id = 'g_' + Date.now();
window.__webToApkAuth = window.__webToApkAuth || {};
window.__webToApkAuth[id] = function (json) {
var r = JSON.parse(json);
if (r.ok) resolve(r); else reject(new Error(r.error));
};
window.WebToApk.signInWithGoogle('', id);
});
}
googleSignInFromUploadedFile()
.then(function (r) { showProfile(r.displayName, r.profilePictureUri); })
.catch(function (e) {
if (e.message === 'missing_web_client_id') console.log('Pass your Web client ID: the uploaded file has none.');
});Notas: Códigos de error: cancelled (el usuario cerró el selector), google_play_services_unavailable (no hay cuenta de Google ni proveedor; también es el aspecto de un SHA-1 sin registrar), missing_web_client_id (no se pasó ninguno y no hay cliente web en el google-services.json que subiste), foreign_origin (la página en pantalla no es la propia de la app), failed, unexpected_credential_type, parse_failed: .... Un primer argumento vacío usa el cliente web de tu propio google-services.json subido, si lo tiene. Verifica el token de ID en tu servidor (o pásalo a Firebase con GoogleAuthProvider.credential(idToken)).
signOutGoogle puente#
window.WebToApk.signOutGoogle(callbackId: String)
Borra el estado guardado de Credential Manager para que la siguiente llamada a signInWithGoogle vuelva a mostrar el selector de cuentas (en lugar de volver a seleccionar en silencio la última cuenta). Las apps generadas deben llamar a esto al cerrar sesión por iniciativa del usuario, además de firebase.auth().signOut().
Ejemplo
Borra el estado guardado de inicio de sesión con Google de Android, de modo que el siguiente inicio de sesión vuelva a mostrar el selector de cuentas. Las páginas normalmente usan el ayudante con Promise WebToApkAuth.signOutGoogle(), que envuelve esta llamada.
Devuelve: nada. La respuesta llega después: el shell llama a window.__webToApkAuth[callbackId](json) con '{"ok":true}' o '{"ok":false,"error":"..."}'. Necesita: nada.
El ayudante (recomendado):
async function signOut() {
await myBackendSignOut(); // end your own session first
if (window.WebToApkAuth) {
try { await window.WebToApkAuth.signOutGoogle(); } catch (err) { /* nothing to undo */ }
}
}La llamada en bruto:
if (window.WebToApk && typeof window.WebToApk.signOutGoogle === 'function') {
var id = 'gout_' + Date.now();
window.__webToApkAuth = window.__webToApkAuth || {};
window.__webToApkAuth[id] = function (json) {
var r = JSON.parse(json);
if (!r.ok) console.log('Google sign-out failed: ' + r.error);
};
window.WebToApk.signOutGoogle(id);
}La llamada en bruto envuelta en una Promise, usada cuando el usuario elimina su cuenta:
function clearGoogleChoice() {
return new Promise(function (resolve) {
if (!window.WebToApk || typeof window.WebToApk.signOutGoogle !== 'function') return resolve(false);
var id = 'gout_' + Date.now();
window.__webToApkAuth = window.__webToApkAuth || {};
window.__webToApkAuth[id] = function (json) { resolve(JSON.parse(json).ok === true); };
window.WebToApk.signOutGoogle(id);
});
}
async function deleteAccount() {
await fetch('https://your-domain.com/account', { method: 'DELETE', credentials: 'include' });
await clearGoogleChoice(); // the next sign-in starts from the account picker
showSignInScreen();
}Notas: No cierra la sesión del usuario en Firebase ni en tu servidor: hazlo tú mismo.