ساخت APK رایگان

API پل جاوااسکریپت

ورود و passkeyها

یک انتخاب‌گر واقعی حساب Google، بومی و نه ریدایرکت وب، و passkeyها از طریق Credential Manager اندروید - navigator.credentials مثل Chrome کار می‌کند، یا از AppMint.passkey با گزینه‌های JSON سرور خود استفاده کنید.

AppMint.passkey کمک‌کننده#

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

نمونه

Passkeyها (ورود با اثر انگشت، چهره یا قفل صفحه - بدون رمز عبور) از طریق مدیر passkey اندروید، با استفاده از گزینه‌های JSON که سرور passkey شما می‌سازد.

برمی‌گرداند: create(optionsJSON) → Promise از RegistrationResponseJSON؛ get(optionsJSON) → Promise از AuthenticationResponseJSON (هر کدام را برای تأیید به سرور خود بفرستید). هر دو با یک DOMException رد می‌شوند (err.name، و err.reason = یک کد کوتاه). isAvailable() → boolean، origin() → رشته، assetLinks() → آرایه - همه بلافاصله. نیاز دارد: Android 9+ با Google Play services، یک سرور 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 services)، TypeError / invalid_options (صفحه‌ای که داخل اپلیکیشن بسته‌بندی شده دامنه ندارد، پس rp.id / rpId را روی دامنه خودتان بگذارید). window.AppMint.passkey فقط در اپلیکیشن وجود دارد؛ در مرورگر از navigator.credentials استفاده کنید (همان کد در اپلیکیشن هم کار می‌کند).

WebToApkAuth.signInWithGoogle کمک‌کننده#

window.WebToApkAuth.signInWithGoogle(webClientId)

نمونه

کاربر را با حساب Google او از طریق انتخاب‌گر حساب خود اندروید وارد می‌کند و یک Google ID token به شما می‌دهد. Google صفحه ورود خود را داخل WebViewهای اپلیکیشن مسدود می‌کند («disallowed_useragent»)؛ این مسیر بومی مسدود نمی‌شود.

برمی‌گرداند: یک Promise از {ok:true, idToken, email, displayName, profilePictureUri}. با یک Error رد می‌شود که message آن کد است (cancelled، google_play_services_unavailable، missing_web_client_id، failed، …). نیاز دارد: Web client ID مربوط به OAuth شما (به .apps.googleusercontent.com ختم می‌شود)، و ثبت اثرانگشت‌های SHA-1 و SHA-256 اپلیکیشن در پروژه Google / Firebase شما (از فایل _signkey_info.txt ساخت شما، و کلید App signing مربوط به Play اگر در Play منتشر می‌کنید). بدون کلید ساخت.

با Firebase Authentication (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 token را بفرستید و آنجا تأیید کنید (هرگز فقط در صفحه به آن اعتماد نکنید):

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 token را به 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);
  }
}

نکات: Web client ID را بدهید. بدون آرگومان، کمک‌کننده از window.firebaseConfig._googleWebClientId استفاده می‌کند اگر صفحه شما آن را تنظیم کرده باشد، سپس Web client موجود در google-services.json بارگذاری‌شده برای این اپلیکیشن (فقط وقتی آن فایل پروژه Firebase خودتان با ورود Google فعال باشد - هرگز یک ساخت AppMint Push)؛ اگر هیچ‌کدام نبود با missing_web_client_id رد می‌شود. صفحه‌ای که متعلق به خود اپلیکیشن نیست (یک سایت خارجی باز در اپلیکیشن) با foreign_origin رد می‌شود. از شناسه WEB client استفاده کنید، نه شناسه Android client. «No credentials available» معمولاً یعنی SHA-1 ثبت نشده است. این کمک‌کننده فقط در اپلیکیشن وجود دارد - در مرورگر از ورود وب عادی خود استفاده کنید. آن را از یک ضربه روی دکمه فراخوانی کنید.

WebToApkAuth.signOutGoogle کمک‌کننده#

window.WebToApkAuth.signOutGoogle()

نمونه

انتخاب ذخیره‌شده ورود Google اندروید را پاک می‌کند، پس signInWithGoogle بعدی دوباره انتخاب‌گر حساب را نشان می‌دهد، به‌جای اینکه بی‌صدا از آخرین حساب دوباره استفاده کند.

برمی‌گرداند: یک Promise از {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 معمولی اندروید نمی‌تواند آن‌ها را انجام دهد؛ در اپلیکیشن به مدیر passkey اندروید می‌روند، پس کد و کتابخانه‌های نوشته‌شده برای Chrome (SimpleWebAuthn، Hanko، Corbado، Auth0) بدون تغییر کار می‌کنند.

برمی‌گرداند: یک Promise از یک PublicKeyCredential (با .toJSON())، دقیقاً مثل Chrome؛ با یک DOMException رد می‌شود. نیاز دارد: Android 9+ با Google Play services، یک سرور 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

نمونه

WebOTP API استاندارد: کد ورود از یک SMS خودش پر می‌شود. اندروید پیام «Allow this app to read this message?» از Google را نشان می‌دهد - بدون مجوز SMS.

برمی‌گرداند: یک Promise از یک OTPCredential {type:'otp', code}. وقتی کاربر Deny بزند یا signal لغو شود با AbortError رد می‌شود، پس از پنج دقیقه بدون SMS با InvalidStateError، بدون Google Play services با NotSupportedError. نیاز دارد: چیزی برای روشن کردن. 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 گوگل می‌ماند - بدون مجوز 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

روی Android 9+ با Google Play services درست است - گوشی‌هایی که می‌توانند passkey نگه دارند.

نمونه

می‌گوید این گوشی می‌تواند از passkey استفاده کند یا نه. صفحه‌ها معمولاً از API عمومی AppMint.passkey.isAvailable() (یا PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable()) استفاده می‌کنند که این فراخوانی را می‌پوشاند.

برمی‌گرداند: یک boolean، بلافاصله - true روی Android 9+ با Google Play services و یک 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

مبدأیی که سرور 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());
}

مبدأ را در یک صفحهٔ تنظیمات مخفی نمایش دهید تا بتوانید آن را بدون دیباگر از نسخهٔ نصب‌شده از 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)

===== Native Google Sign-In via Credential Manager ===== Bypasses Google's Android WebView OAuth block (disallowed_useragent error) by running the sign-in flow in native Android and bridging just the resulting Google ID token back to JS, where the Firebase Web SDK consumes it via GoogleAuthProvider.credential(idToken) + signInWithCredential(...). JS contract: window.WebToApk.signInWithGoogle(serverClientId, callbackId) serverClientId "" = the Web client (type 3) in the creator's own google-services.json for this package; with none, error "missing_web_client_id". A page that is not the app's own gets error "foreign_origin". -> on completion, window.__webToApkAuth[callbackId](resultJsonString) is called resultJsonString = '{"ok":true,"idToken":"...","email":"...","displayName":"..."}' or '{"ok":false,"error":"<message>"}'

نمونه

ورود بومی Google را شروع می‌کند (انتخاب‌گر حساب اندروید) و با یک Google ID token پاسخ می‌دهد. صفحه‌ها معمولاً از کمک‌کننده Promise به نام WebToApkAuth.signInWithGoogle(webClientId) استفاده می‌کنند که این فراخوانی را می‌پوشاند.

برمی‌گرداند: چیزی. پاسخ بعداً می‌رسد: پوسته window.__webToApkAuth[callbackId](json) را با '{"ok":true,"idToken":"...","email":"...","displayName":"...","profilePictureUri":"..."}' یا '{"ok":false,"error":"<code>","detail":"..."}' فراخوانی می‌کند، سپس آن ورودی را برمی‌دارد. نیاز دارد: Web client 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;
  }
}

فراخوانی خام (ترتیب آرگومان‌ها: web client ID، سپس شناسه 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);
}

فراخوانی خام با client ID خالی، که از Web client موجود در 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 بارگذاری‌شده شما هم Web client وجود ندارد)، foreign_origin (صفحه روی صفحه‌نمایش متعلق به خود اپلیکیشن نیست)، failed، unexpected_credential_type، parse_failed: .... آرگومان اول خالی از Web client موجود در google-services.json بارگذاری‌شده خودتان استفاده می‌کند، اگر داشته باشد. ID token را روی سرور خود تأیید کنید (یا به Firebase بدهید با GoogleAuthProvider.credential(idToken)).

signOutGoogle پل#

window.WebToApk.signOutGoogle(callbackId: String)

وضعیت ذخیره‌شده Credential Manager را پاک می‌کند تا فراخوانی بعدی signInWithGoogle دوباره انتخاب‌گر حساب را نشان دهد (به‌جای اینکه بی‌صدا آخرین حساب را دوباره انتخاب کند). اپلیکیشن‌های ساخته‌شده باید این را هنگام خروج آغازشده توسط کاربر فراخوانی کنند، علاوه بر firebase.auth().signOut().

نمونه

وضعیت ذخیره‌شده ورود Google اندروید را پاک می‌کند، پس ورود بعدی دوباره انتخاب‌گر حساب را نشان می‌دهد. صفحه‌ها معمولاً از کمک‌کننده Promise به نام 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);
}

فراخوانی خام پیچیده‌شده در یک Promise، وقتی کاربر حساب خود را حذف می‌کند:

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 یا سرور شما خارج نمی‌کند - آن را خودتان انجام دهید.