Criar APK grátis

API da ponte JavaScript

Login e passkeys

Um seletor de contas Google de verdade, nativo e não um redirecionamento web, e passkeys por meio do Credential Manager do Android - navigator.credentials funciona como no Chrome, ou use AppMint.passkey com as opções JSON do seu servidor.

AppMint.passkey helper#

window.AppMint.passkey — create(options), get(options), isAvailable(), origin(), assetLinks()

Exemplo

Passkeys (entre com impressão digital, rosto ou bloqueio de tela - sem senha) por meio do gerenciador de passkeys do Android, usando as opções JSON que seu servidor de passkeys gera.

Retorna: create(optionsJSON) → Promise do RegistrationResponseJSON; get(optionsJSON) → Promise do AuthenticationResponseJSON (envie qualquer um ao seu servidor para verificar). Ambos são rejeitados com um DOMException (err.name, e err.reason = um código curto). isAvailable() → booleano, origin() → string, assetLinks() → array - todos na hora. Requer: Android 9+ com Google Play Services, um servidor de passkeys e as duas etapas de configuração abaixo. Sem chave de compilação.

Entrar:

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();
}

Crie um (depois que o usuário entrou de outra 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);
  }
}

Configure uma vez - execute isto no app instalado DO Google Play (o Play assina com a própria chave):

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 origins

Observações: err.name / err.reason: NotAllowedError (cancelled, no_credential, interrupted), InvalidStateError (já existe), SecurityError com not_linked (o assetlinks.json não lista o app) ou rp_mismatch (o rpId não é o domínio desta página), NotSupportedError / unavailable (Android < 9 ou sem Play Services), TypeError / invalid_options (uma página incluída no app não tem domínio, então defina rp.id / rpId como o seu domínio). window.AppMint.passkey existe apenas no app; em um navegador use navigator.credentials (o mesmo código funciona no app também).

WebToApkAuth.signInWithGoogle helper#

window.WebToApkAuth.signInWithGoogle(webClientId)

Exemplo

Faz o login do usuário com a conta Google dele por meio do seletor de contas do próprio Android e entrega a você um token de ID do Google. O Google bloqueia sua página de login dentro de WebViews de apps ("disallowed_useragent"); este caminho nativo não é bloqueado.

Retorna: uma Promise de {ok:true, idToken, email, displayName, profilePictureUri}. Ela é REJEITADA com um Error cujo message é o código (cancelled, google_play_services_unavailable, missing_web_client_id, failed, …). Requer: seu ID do cliente Web do OAuth (termina em .apps.googleusercontent.com) e as impressões digitais SHA-1 e SHA-256 do app registradas no seu projeto Google / Firebase (do arquivo _signkey_info.txt da sua compilação, e da chave de assinatura do app do Play se você publica no Play). Sem chave de compilação.

Com o 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);
  }
});

Com o seu próprio servidor - envie o token de ID e verifique-o lá (nunca confie só na 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 })
  });
}

Com o Supabase Auth (entregue o token de ID para 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);
  }
}

Observações: Passe o ID do cliente Web. Sem argumento, o helper usa window.firebaseConfig._googleWebClientId se sua página o definir, depois o cliente Web do google-services.json que você enviou para este app (somente quando esse arquivo é do seu próprio projeto Firebase com login do Google ativado - nunca uma compilação do AppMint Push); sem nenhum desses, ele é rejeitado com missing_web_client_id. Uma página que não é a do próprio app (um site externo aberto dentro do app) é recusada com foreign_origin. Use o ID do cliente WEB, não o de um cliente Android. "No credentials available" geralmente significa que o SHA-1 não está registrado. Este helper existe apenas no app - em um navegador, use seu login web normal. Chame-o a partir de um toque em botão.

WebToApkAuth.signOutGoogle helper#

window.WebToApkAuth.signOutGoogle()

Exemplo

Limpa a escolha de login do Google salva no Android, de modo que o próximo signInWithGoogle mostre o seletor de contas novamente em vez de reutilizar silenciosamente a última conta.

Retorna: uma Promise de {ok:true}; é rejeitada com um Error cujo message diz o que deu errado. Requer: nada.

Chame junto com o seu próprio logout:

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();
});

Um botão "Trocar de conta": limpe a escolha salva e depois entre novamente para que o usuário escolha outra conta:

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();
  }
}

Faça primeiro o logout do seu próprio servidor e depois limpe a escolha do Google sem deixar uma falha bloquear o logout:

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');
}

Observações: Isso não encerra a sessão do seu próprio app - faça o logout do usuário no seu backend por conta própria. Isso apenas redefine qual conta Google o celular oferece na próxima vez.

navigator.credentials padrão web#

navigator.credentials.create / get({ publicKey }) — passkeys

Exemplo

Chamadas padrão de passkey WebAuthn. Um WebView normal do Android não consegue fazê-las; no app elas vão para o gerenciador de passkeys do Android, então código e bibliotecas escritos para o Chrome (SimpleWebAuthn, Hanko, Corbado, Auth0) funcionam sem alterações.

Retorna: uma Promise de um PublicKeyCredential (com .toJSON()), exatamente como no Chrome; é rejeitada com um DOMException. Requer: Android 9+ com Google Play Services, um servidor de passkeys, o assetlinks.json do seu domínio listando o app e seu servidor aceitando a origem do app (veja AppMint.passkey). Sem chave de compilação.

Entre com a API padrão (opções vindas do seu servidor, campos binários como ArrayBuffers):

async function signIn() {
  if (!window.PublicKeyCredential) { alert('Passkeys are not supported here.'); return; }
  var ok = await PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable();
  if (!ok) { alert('This phone cannot use passkeys.'); return; }

  try {
    var json = await (await fetch('https://your-domain.com/passkey/login-options')).json();
    var publicKey = PublicKeyCredential.parseRequestOptionsFromJSON(json);  // provided in the app too
    var cred = await navigator.credentials.get({ publicKey: publicKey });
    await fetch('https://your-domain.com/passkey/login-verify', {
      method: 'POST', headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(cred.toJSON())
    });
  } catch (err) {
    if (err.name !== 'NotAllowedError') alert('Passkey problem: ' + err.message);
  }
}

Registre uma passkey:

async function register() {
  if (!window.PublicKeyCredential) return;
  try {
    var json = await (await fetch('https://your-domain.com/passkey/register-options')).json();
    var cred = await navigator.credentials.create({
      publicKey: PublicKeyCredential.parseCreationOptionsFromJSON(json)
    });
    await fetch('https://your-domain.com/passkey/register-verify', {
      method: 'POST', headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(cred.toJSON())
    });
  } catch (err) {
    if (err.name === 'InvalidStateError') alert('You already have a passkey here.');
    else if (err.name !== 'NotAllowedError') alert('Passkey problem: ' + err.message);
  }
}

Decida quais botões de login mostrar com base no que o celular oferece (o preenchimento automático de passkey está desativado no app, então mantenha um botão):

async function setupSignInButtons() {
  if (!window.PublicKeyCredential) return;
  var caps = typeof PublicKeyCredential.getClientCapabilities === 'function'
    ? await PublicKeyCredential.getClientCapabilities() : {};
  var platform = caps.passkeyPlatformAuthenticator ||
    await PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable();
  document.getElementById('passkey-button').hidden = !platform;
  if (caps.conditionalGet) startPasskeyAutofill();   // false in the app; true in some browsers
}

Observações: O preenchimento automático de passkey em um campo de texto (mediation: 'conditional') não está disponível no app - isConditionalMediationAvailable() responde false e a chamada é rejeitada com NotSupportedError; inicie o login a partir de um botão. signal (AbortController) funciona. Uma página incluída no app não tem domínio próprio: defina rp.id / rpId como o seu domínio. Chamadas sem publicKey (senhas, OTP) vão para o WebView como antes.

OTPCredential padrão web#

navigator.credentials.get({ otp: { transport: ['sms'] } }) — WebOTP

Exemplo

A API padrão WebOTP: o código de login recebido por SMS se preenche sozinho. O Android mostra a tela do Google "Permitir que este app leia esta mensagem?" - sem permissão de SMS.

Retorna: uma Promise de um OTPCredential {type:'otp', code}. É rejeitada com AbortError quando o usuário toca em Negar ou o sinal é abortado, InvalidStateError após cinco minutos sem SMS, NotSupportedError sem o Google Play Services. Requer: nada para ativar. O SMS deve TERMINAR com uma linha que nomeie o host desta 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));
}

O SMS que seu servidor envia (a última linha é o vínculo):

Your Acme code is 482913.

@acme.example #482913

Depois de "Reenviar código", inicie uma nova solicitação (ela substitui a que ainda está esperando, que é rejeitada com 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.');
  }
}

Observações: o host deve ser o host da própria página - para um app baseado em site é o seu domínio (o SMS que o Chrome já lê); para um app offline/criado por IA é appassets.androidplatform.net. Uma mensagem para outro host é ignorada e o app continua esperando. Somente na página principal (códigos vinculados a uma origem incorporada, @top #code @iframe, não são compatíveis). Só uma solicitação espera por vez; uma nova cancela a anterior. Evento por trás: appmint:otp.

__otpCancel ponte#

window.WebToApk.__otpCancel(requestId: String)

Abandona uma solicitação WebOTP pendente (AbortSignal, ou uma solicitação mais nova a substituiu).

Exemplo

Transporte interno por trás do cancelamento de uma solicitação WebOTP (AbortSignal). As páginas usam a API padrão; nunca chame isso diretamente - os métodos __ podem mudar sem aviso.

Retorna: nada. Requer: nada para ativar.

Use a 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 AbortError

Pare de esperar assim que o usuário começar a digitar o código por conta própria:

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); });

Cancele a espera quando o usuário voltar para alterar o número de telefone:

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();
});

Observações: Implementado no shell do app (WebApiPolyfills.kt). O mesmo código de página funciona sem alterações no Chrome.

__otpStart ponte#

window.WebToApk.__otpStart(requestId: String)

WebOTP: espera (até cinco minutos) por um SMS por meio da tela SMS User Consent do Google - sem permissão de SMS. A resposta é `appmint:otp {requestId, message|error}`.

Exemplo

Transporte interno por trás de navigator.credentials.get({ otp: { transport: ['sms'] } }). As páginas usam a API padrão; nunca chame isso diretamente - os métodos __ podem mudar sem aviso.

Retorna: nada; a resposta chega como o evento appmint:otp {requestId, message} ou {requestId, error} (cancelled, timeout, unavailable). Requer: nada para ativar.

Use a API pública:

const ac = new AbortController();
const cred = await navigator.credentials.get({ otp: { transport: ['sms'] }, signal: ac.signal });
codeInput.value = cred.code;

Pare de esperar após um minuto com um sinal de timeout:

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.');
  }
}

Diga ao usuário o que fazer em seguida a partir do nome do erro:

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
  }
}

Observações: Implementado no shell do app (WebApiPolyfills.kt). O mesmo código de página funciona sem alterações no Chrome.

passkeyIsAvailable ponte#

window.WebToApk.passkeyIsAvailable(): Boolean

True no Android 9+ com Google Play Services - os celulares que podem guardar uma passkey.

Exemplo

Diz se este celular pode usar passkeys. As páginas normalmente usam a API pública AppMint.passkey.isAvailable() (ou PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable()), que envolve esta chamada.

Retorna: um booleano, na hora - true no Android 9+ com Google Play Services e um WebView novo o bastante para o canal de passkey. Requer: nada.

A API pública:

var canPasskey = !!(window.AppMint && window.AppMint.passkey && window.AppMint.passkey.isAvailable());
document.getElementById('passkey-button').hidden = !canPasskey;

A chamada bruta, se você precisar dela:

var canPasskey = !!window.WebToApk &&
  typeof window.WebToApk.passkeyIsAvailable === 'function' &&
  window.WebToApk.passkeyIsAvailable() === true;

Ofereça "Adicionar uma passkey" uma vez após um login com senha, apenas em celulares que possam guardar uma (a verificação padrão, o mesmo código do 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;
}

Observações: Quando isto for false, AppMint.passkey.create/get são rejeitados com NotSupportedError. Oculte o botão de passkey e ofereça seu outro método de login.

passkeyOrigin ponte#

window.WebToApk.passkeyOrigin(): String

A origem que um servidor de passkey vê deste app: `android:apk-key-hash:<SHA-256 em base64url>`.

Exemplo

Informa a origem que seu servidor de passkeys vê a partir deste app: android:apk-key-hash:<base64url SHA-256 of the signing certificate>. As páginas normalmente usam a API pública AppMint.passkey.origin(), que envolve esta chamada.

Retorna: uma string, na hora ("" se o certificado não puder ser lido). Requer: nada.

Execute isto uma vez no app instalado A PARTIR do Google Play e adicione o valor às origens permitidas do seu servidor:

if (window.AppMint && window.AppMint.passkey) {
  console.log(window.AppMint.passkey.origin());   // android:apk-key-hash:Lir5oIjf552K...
}

A chamada bruta retorna a mesma string:

if (window.WebToApk && typeof window.WebToApk.passkeyOrigin === 'function') {
  console.log(window.WebToApk.passkeyOrigin());
}

Mostre a origem em uma tela de configurações oculta, para poder lê-la na instalação do Play sem um 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
}

Observações: O Google Play reassina seu app, então um APK de teste dá um valor diferente do da instalação pelo Play. Exemplo com SimpleWebAuthn: expectedOrigin: ['https://your-domain.com', 'android:apk-key-hash:...'].

signInWithGoogle ponte#

window.WebToApk.signInWithGoogle(serverClientId: String, callbackId: String)

===== Login nativo do Google via Credential Manager ===== Contorna o bloqueio de OAuth do Google no WebView do Android (erro disallowed_useragent) executando o fluxo de login no Android nativo e passando de volta ao JS apenas o token de ID do Google resultante, onde o SDK Web do Firebase o consome via GoogleAuthProvider.credential(idToken) + signInWithCredential(...). Contrato JS: window.WebToApk.signInWithGoogle(serverClientId, callbackId) serverClientId "" = o cliente Web (tipo 3) no google-services.json do próprio criador para este pacote; sem ele, erro "missing_web_client_id". Uma página que não é a do próprio app recebe o erro "foreign_origin". -> ao concluir, window.__webToApkAuth[callbackId](resultJsonString) é chamado resultJsonString = '{"ok":true,"idToken":"...","email":"...","displayName":"..."}' ou '{"ok":false,"error":"<message>"}'

Exemplo

Inicia o login nativo do Google (o seletor de contas do Android) e responde com um token de ID do Google. As páginas normalmente usam o helper com Promise WebToApkAuth.signInWithGoogle(webClientId), que envolve esta chamada.

Retorna: nada. A resposta chega depois: o shell chama window.__webToApkAuth[callbackId](json) com '{"ok":true,"idToken":"...","email":"...","displayName":"...","profilePictureUri":"..."}' ou '{"ok":false,"error":"<code>","detail":"..."}' e depois remove essa entrada. Requer: o ID do cliente Web do OAuth e o SHA-1 / SHA-256 do app registrados no seu projeto Google ou Firebase. Sem chave de compilação.

O helper (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;
  }
}

A chamada bruta (ordem dos argumentos: ID do cliente web e depois o ID do seu 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);
}

A chamada bruta com um ID de cliente vazio, que usa o cliente Web do google-services.json que você enviou:

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.');
  });

Observações: Códigos de erro: cancelled (o usuário fechou o seletor), google_play_services_unavailable (sem conta ou provedor Google - também é como aparece um registro de SHA-1 ausente), missing_web_client_id (nenhum informado, e nenhum cliente Web no google-services.json que você enviou), foreign_origin (a página exibida não é a do próprio app), failed, unexpected_credential_type, parse_failed: .... Um primeiro argumento vazio usa o cliente Web do seu próprio google-services.json enviado, quando ele tem um. Verifique o token de ID no seu servidor (ou entregue-o ao Firebase com GoogleAuthProvider.credential(idToken)).

signOutGoogle ponte#

window.WebToApk.signOutGoogle(callbackId: String)

Limpa o estado salvo do Credential Manager para que a próxima chamada de signInWithGoogle mostre o seletor de contas novamente (em vez de selecionar de novo a última conta silenciosamente). Apps gerados devem chamar isto no logout iniciado pelo usuário, além de firebase.auth().signOut().

Exemplo

Limpa o estado salvo de login do Google no Android, de modo que o próximo login mostre o seletor de contas novamente. As páginas normalmente usam o helper com Promise WebToApkAuth.signOutGoogle(), que envolve esta chamada.

Retorna: nada. A resposta chega depois: o shell chama window.__webToApkAuth[callbackId](json) com '{"ok":true}' ou '{"ok":false,"error":"..."}'. Requer: nada.

O helper (recomendado):

async function signOut() {
  await myBackendSignOut();                          // end your own session first
  if (window.WebToApkAuth) {
    try { await window.WebToApkAuth.signOutGoogle(); } catch (err) { /* nothing to undo */ }
  }
}

A chamada bruta:

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);
}

A chamada bruta envolvida em uma Promise, usada quando o usuário exclui a conta:

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();
}

Observações: Isso não faz o logout do usuário do Firebase nem do seu servidor - faça isso por conta própria.

Gerado a partir do runtime do app e de seus arquivos de exemplo a cada build da documentação. Leia em Markdown · Todas as famílias.

Conferido com a ponte atual em 22/10/2026.