Создать APK бесплатно

JavaScript bridge API

Вход и passkey

Настоящее окно выбора аккаунта Google, нативное, а не веб-перенаправление, и passkey через Credential Manager в Android — navigator.credentials работает, как в Chrome, или используйте AppMint.passkey с JSON-параметрами вашего сервера.

AppMint.passkey помощник#

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

Пример

Passkey (вход по отпечатку, лицу или блокировке экрана — без пароля) через менеджер passkey в Android, с использованием JSON-параметров, которые создаёт ваш сервер passkey.

Возвращает: create(optionsJSON) → промис с RegistrationResponseJSON; get(optionsJSON) → промис с AuthenticationResponseJSON (отправьте любой из них на сервер для проверки). Оба отклоняются с DOMException (err.name, а err.reason — короткий код). isAvailable() → логическое значение, origin() → строка, assetLinks() → массив — всё сразу. Требуется: Android 9+ с сервисами Google Play, сервер passkey и два шага настройки ниже. Переключатель сборки не нужен.

Вход:

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

Создайте его (после того как пользователь вошёл другим способом):

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

Настройте один раз — выполните это в приложении, установленном ИЗ Google Play (Play подписывает своим ключом):

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

Примечания: err.name / err.reason: NotAllowedError (cancelled, no_credential, interrupted), InvalidStateError (уже существует), SecurityError с not_linked (assetlinks.json не содержит приложения) или rp_mismatch (rpId — не домен этой страницы), NotSupportedError / unavailable (Android ниже 9 или нет сервисов Play), TypeError / invalid_options (у страницы, встроенной в приложение, нет домена, поэтому укажите в rp.id / rpId ваш домен). window.AppMint.passkey существует только в приложении; в браузере используйте navigator.credentials (тот же код работает и в приложении).

WebToApkAuth.signInWithGoogle помощник#

window.WebToApkAuth.signInWithGoogle(webClientId)

Пример

Входит пользователя через аккаунт Google с помощью собственного окна выбора аккаунта Android и даёт вам токен ID Google. Google блокирует свою страницу входа внутри WebView приложений («disallowed_useragent»); этот нативный путь не блокируется.

Возвращает: промис с {ok:true, idToken, email, displayName, profilePictureUri}. ОТКЛОНЯЕТСЯ с Error, у которого message — это код (cancelled, google_play_services_unavailable, missing_web_client_id, failed, …). Требуется: ваш ID веб-клиента OAuth (заканчивается на .apps.googleusercontent.com) и отпечатки SHA-1 и SHA-256 приложения, зарегистрированные в вашем проекте Google / Firebase (из файла _signkey_info.txt вашей сборки и ключа подписи приложения Play, если вы публикуетесь в Play). Переключатель сборки не нужен.

С Firebase Authentication (compat SDK):

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

С вашим собственным сервером — отправьте токен ID и проверьте его там (никогда не доверяйте ему только на странице):

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

С Supabase Auth (передайте токен ID в 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);
  }
}

Примечания: Передайте ID веб-клиента. Без аргумента помощник использует window.firebaseConfig._googleWebClientId, если ваша страница его задаёт, затем веб-клиент из загруженного вами для этого приложения google-services.json (только если этот файл из вашего собственного проекта Firebase с включённым входом через Google — никогда не из сборки AppMint Push); если ничего из этого нет, он завершается ошибкой missing_web_client_id. Страница, не принадлежащая самому приложению (внешний сайт, открытый внутри приложения), отклоняется с foreign_origin. Используйте ID ВЕБ-клиента, а не ID клиента Android. «No credentials available» обычно означает, что SHA-1 не зарегистрирован. Этот помощник существует только в приложении — в браузере используйте обычный веб-вход. Вызывайте его по нажатию кнопки.

WebToApkAuth.signOutGoogle помощник#

window.WebToApkAuth.signOutGoogle()

Пример

Очищает сохранённый в Android выбор аккаунта Google, поэтому при следующем signInWithGoogle снова покажется окно выбора аккаунта, а не будет молча использован прежний.

Возвращает: промис с {ok:true}; отклоняется с Error, у которого message говорит, что пошло не так. Требуется: ничего.

Вызывайте вместе с вашим собственным выходом:

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

Кнопка «Сменить аккаунт»: очистите сохранённый выбор, затем снова выполните вход, чтобы пользователь выбрал другой аккаунт:

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

Сначала выйдите из собственного сервера, затем очистите выбор Google, не давая сбою помешать выходу:

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

Примечания: Это не завершает собственный сеанс вашего приложения — завершите сеанс пользователя в своей серверной части сами. Здесь лишь сбрасывается, какой аккаунт Google телефон предложит в следующий раз.

navigator.credentials веб-стандарт#

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

Пример

Стандартные вызовы passkey WebAuthn. Обычный WebView Android не умеет их выполнять; в приложении они идут в менеджер passkey Android, поэтому код и библиотеки, написанные для Chrome (SimpleWebAuthn, Hanko, Corbado, Auth0), работают без изменений.

Возвращает: промис с PublicKeyCredential (с .toJSON()), точно как в Chrome; отклоняется с DOMException. Требуется: Android 9+ с сервисами Google Play, сервер passkey, assetlinks.json вашего домена с этим приложением и ваш сервер, принимающий источник приложения (см. AppMint.passkey). Переключатель сборки не нужен.

Войдите через стандартный API (параметры с вашего сервера, двоичные поля как ArrayBuffer):

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

Зарегистрируйте 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);
  }
}

Решите, какие кнопки входа показывать, по тому, что поддерживает телефон (автозаполнение passkey в приложении отключено, поэтому оставьте кнопку):

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
}

Примечания: Автозаполнение passkey в текстовом поле (mediation: 'conditional') в приложении недоступно — isConditionalMediationAvailable() отвечает false, а вызов завершается ошибкой NotSupportedError; запускайте вход по нажатию кнопки. signal (AbortController) работает. У страницы, встроенной в приложение, нет собственного домена: укажите в rp.id / rpId ваш домен. Вызовы без publicKey (пароли, OTP) идут в WebView, как раньше.

OTPCredential веб-стандарт#

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

Пример

Стандартный API WebOTP: код входа из SMS подставляется сам. Android показывает окно Google «Разрешить этому приложению прочитать это сообщение?» — разрешение на SMS не нужно.

Возвращает: промис с OTPCredential {type:'otp', code}. Отклоняется с AbortError, когда пользователь нажимает «Запретить» или сигнал прерывает запрос, с InvalidStateError после пяти минут без SMS, с NotSupportedError без сервисов Google Play. Требуется: ничего включать не нужно. SMS должно ЗАКАНЧИВАТЬСЯ строкой с хостом этой страницы: @<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));
}

SMS, которое отправляет ваш сервер (последняя строка — привязка):

Your Acme code is 482913.

@acme.example #482913

После «Отправить код ещё раз» начните новый запрос (он заменяет тот, что ещё ждёт, и тот завершается с 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.');
  }
}

Примечания: хост должен совпадать с хостом самой страницы — для приложения на основе сайта это ваш домен (то SMS, которое уже читает Chrome); для офлайн-приложения или приложения, созданного ИИ, это appassets.androidplatform.net. Сообщение для другого хоста игнорируется, и приложение продолжает ждать. Только верхняя страница (коды, привязанные к вложенному источнику, @top #code @iframe, не поддерживаются). Одновременно ждёт только один запрос; новый прерывает старый. Событие, стоящее за этим: appmint:otp.

__otpCancel мост#

window.WebToApk.__otpCancel(requestId: String)

Прекращает ожидающий запрос WebOTP (AbortSignal или его заменил более новый запрос).

Пример

Внутренний транспорт для прерывания запроса WebOTP (AbortSignal). Страницы используют стандартный API; никогда не вызывайте это напрямую — методы __ могут измениться без предупреждения.

Возвращает: ничего. Требуется: ничего включать не нужно.

Используйте публичный API:

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

Перестаньте ждать, как только пользователь начал вводить код сам:

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

Отмените ожидание, когда пользователь возвращается, чтобы изменить номер телефона:

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

Примечания: Реализовано в оболочке приложения (WebApiPolyfills.kt). Тот же код страницы без изменений работает в Chrome.

__otpStart мост#

window.WebToApk.__otpStart(requestId: String)

WebOTP: ждёт (до пяти минут) одно SMS через окно SMS User Consent от Google — разрешение на SMS не нужно. Ответ приходит как `appmint:otp {requestId, message|error}`.

Пример

Внутренний транспорт для navigator.credentials.get({ otp: { transport: ['sms'] } }). Страницы используют стандартный API; никогда не вызывайте это напрямую — методы __ могут измениться без предупреждения.

Возвращает: ничего; ответ приходит как событие appmint:otp {requestId, message} или {requestId, error} (cancelled, timeout, unavailable). Требуется: ничего включать не нужно.

Используйте публичный API:

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

Перестаньте ждать через минуту с помощью сигнала таймаута:

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

Подскажите пользователю, что делать дальше, по названию ошибки:

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

Примечания: Реализовано в оболочке приложения (WebApiPolyfills.kt). Тот же код страницы без изменений работает в Chrome.

passkeyIsAvailable мост#

window.WebToApk.passkeyIsAvailable(): Boolean

True на Android 9+ с сервисами Google Play — на телефонах, которые могут хранить passkey.

Пример

Сообщает, может ли этот телефон использовать passkey. Страницы обычно используют публичный API AppMint.passkey.isAvailable() (или PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable()), который оборачивает этот вызов.

Возвращает: логическое значение, сразу — true на Android 9+ с сервисами Google Play и WebView, достаточно новым для канала passkey. Требуется: ничего.

Публичный API:

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

Прямой вызов, если он вам нужен:

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

Предложите «Добавить passkey» один раз после входа по паролю, только на телефонах, которые могут его хранить (стандартная проверка, тот же код, что и в 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;
}

Примечания: Когда значение false, AppMint.passkey.create/get завершаются ошибкой NotSupportedError. Скройте кнопку passkey и предложите другой способ входа.

passkeyOrigin мост#

window.WebToApk.passkeyOrigin(): String

Origin, который сервер passkey видит от этого приложения: `android:apk-key-hash:<base64url SHA-256>`.

Пример

Даёт источник, который ваш сервер passkey видит от этого приложения: android:apk-key-hash:<base64url SHA-256 of the signing certificate>. Страницы обычно используют публичный API AppMint.passkey.origin(), который оборачивает этот вызов.

Возвращает: строку, сразу ("", если сертификат не удаётся прочитать). Требуется: ничего.

Выполните один раз в приложении, установленном ИЗ Google Play, и добавьте значение в разрешённые источники вашего сервера:

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

Прямой вызов возвращает ту же строку:

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

Покажите origin на скрытом экране настроек, чтобы прочитать его в установке из Play без отладчика:

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
}

Примечания: Google Play заново подписывает ваше приложение, поэтому у тестового APK значение отличается от установки из Play. Пример SimpleWebAuthn: expectedOrigin: ['https://your-domain.com', 'android:apk-key-hash:...'].

signInWithGoogle мост#

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

===== Нативный вход через Google с помощью Credential Manager ===== Обходит блокировку Google для OAuth в WebView Android (ошибка disallowed_useragent): процесс входа выполняется в нативном Android, а в JS возвращается только полученный токен ID Google, который Web SDK Firebase использует через GoogleAuthProvider.credential(idToken) + signInWithCredential(...). Контракт JS: window.WebToApk.signInWithGoogle(serverClientId, callbackId) serverClientId "" = веб-клиент (тип 3) из собственного google-services.json создателя для этого пакета; если его нет, ошибка "missing_web_client_id". Страница, не принадлежащая самому приложению, получает ошибку "foreign_origin". -> по завершении вызывается window.__webToApkAuth[callbackId](resultJsonString) resultJsonString = '{"ok":true,"idToken":"...","email":"...","displayName":"..."}' или '{"ok":false,"error":"<message>"}'

Пример

Запускает нативный вход через Google (окно выбора аккаунта Android) и отвечает токеном ID Google. Страницы обычно используют помощник на промисах WebToApkAuth.signInWithGoogle(webClientId), который оборачивает этот вызов.

Возвращает: ничего. Ответ приходит позже: оболочка вызывает window.__webToApkAuth[callbackId](json) с '{"ok":true,"idToken":"...","email":"...","displayName":"...","profilePictureUri":"..."}' или '{"ok":false,"error":"<code>","detail":"..."}', затем удаляет эту запись. Требуется: ваш ID веб-клиента OAuth и SHA-1 / SHA-256 приложения, зарегистрированные в вашем проекте Google или Firebase. Переключатель сборки не нужен.

Помощник (рекомендуется):

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

Прямой вызов (порядок аргументов: ID веб-клиента, затем идентификатор вашей функции обратного вызова):

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

Прямой вызов с пустым ID клиента, при котором используется веб-клиент из загруженного вами google-services.json:

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

Примечания: Коды ошибок: cancelled (пользователь закрыл окно выбора), google_play_services_unavailable (нет аккаунта или поставщика Google — так же выглядит и отсутствие регистрации SHA-1), missing_web_client_id (ни один не передан, и в загруженном вами google-services.json нет веб-клиента), foreign_origin (страница на экране не принадлежит самому приложению), failed, unexpected_credential_type, parse_failed: .... Пустой первый аргумент берёт веб-клиент из загруженного вами google-services.json, если он там есть. Проверяйте токен ID на своём сервере (или передайте его в Firebase с помощью GoogleAuthProvider.credential(idToken)).

signOutGoogle мост#

window.WebToApk.signOutGoogle(callbackId: String)

Очищает сохранённое состояние Credential Manager, чтобы при следующем вызове signInWithGoogle снова показывалось окно выбора аккаунта (а не молча выбирался прежний). Созданные приложения должны вызывать это при выходе по инициативе пользователя, в дополнение к firebase.auth().signOut().

Пример

Очищает сохранённое в Android состояние входа через Google, поэтому при следующем входе снова покажется окно выбора аккаунта. Страницы обычно используют помощник на промисах WebToApkAuth.signOutGoogle(), который оборачивает этот вызов.

Возвращает: ничего. Ответ приходит позже: оболочка вызывает window.__webToApkAuth[callbackId](json) с '{"ok":true}' или '{"ok":false,"error":"..."}'. Требуется: ничего.

Помощник (рекомендуется):

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

Прямой вызов:

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

Прямой вызов, обёрнутый в промис, используется, когда пользователь удаляет аккаунт:

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

Примечания: Это не выводит пользователя из Firebase или вашего сервера — сделайте это сами.