أنشئ APK مجانًا

واجهة JavaScript bridge API

تسجيل الدخول ومفاتيح المرور

أداة اختيار حسابات Google حقيقية، أصلية وليست إعادة توجيه على الويب، ومفاتيح مرور عبر Credential Manager في Android - يعمل navigator.credentials كما في Chrome، أو استخدم AppMint.passkey مع خيارات JSON من خادمك.

AppMint.passkey دالة مساعدة (helper)#

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

مثال

مفاتيح المرور (تسجيل الدخول ببصمة الإصبع أو الوجه أو قفل الشاشة - دون كلمة مرور) عبر مدير مفاتيح المرور في Android، باستخدام خيارات JSON التي ينشئها خادم مفاتيح المرور لديك.

تُرجع: create(optionsJSON) ← Promise بقيمة RegistrationResponseJSON؛ get(optionsJSON) ← Promise بقيمة AuthenticationResponseJSON (أرسل أيًّا منهما إلى خادمك للتحقق). كلاهما يرفض بـ DOMException (err.name، وerr.reason = رمز قصير). isAvailable() ← قيمة منطقية، origin() ← نص، assetLinks() ← مصفوفة - جميعها فورًا. تحتاج إلى: Android 9+ مع خدمات Google Play، وخادم مفاتيح مرور، وخطوتَي الإعداد أدناه. لا يوجد مفتاح تفعيل في البناء.

تسجيل الدخول:

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 دالة مساعدة (helper)#

window.WebToApkAuth.signInWithGoogle(webClientId)

مثال

يسجّل دخول المستخدم بحساب Google عبر أداة اختيار الحسابات الخاصة بـ Android، ويمنحك رمز Google ID. تحظر Google صفحة تسجيل الدخول الخاصة بها داخل WebView التطبيقات («disallowed_useragent»)؛ وهذا المسار الأصلي غير محظور.

تُرجع: Promise بقيمة {ok:true, idToken, email, displayName, profilePictureUri}. وهي ترفض بـ Error يكون message فيه هو الرمز (cancelled أو google_play_services_unavailable أو missing_web_client_id أو failed …). تحتاج إلى: معرّف عميل Web الخاص بـ OAuth (ينتهي بـ .apps.googleusercontent.com)، وبصمتي SHA-1 وSHA-256 للتطبيق مسجّلتين في مشروع Google / Firebase (من ملف _signkey_info.txt الخاص ببنائك، ومفتاح App signing في 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);
  }
}

ملاحظات: مرّر معرّف عميل Web. دون وسيط تستخدم الدالة المساعدة window.firebaseConfig._googleWebClientId إن ضبطته صفحتك، ثم عميل Web في google-services.json الذي رفعته لهذا التطبيق (فقط عندما يكون هذا الملف من مشروع Firebase الخاص بك مع تفعيل تسجيل الدخول بـ Google - وليس من إنشاء AppMint Push أبدًا)؛ وإن لم يوجد أي منها ترفض بـ missing_web_client_id. الصفحة التي ليست صفحة التطبيق نفسه (موقع خارجي مفتوح داخل التطبيق) تُرفض بـ foreign_origin. استخدم معرّف عميل WEB وليس معرّف عميل Android. عادةً تعني «No credentials available» أن SHA-1 غير مسجّل. هذه الدالة المساعدة موجودة في التطبيق فقط - في المتصفح استخدم تسجيل الدخول المعتاد على الويب. استدعها من نقرة زر.

WebToApkAuth.signOutGoogle دالة مساعدة (helper)#

window.WebToApkAuth.signOutGoogle()

مثال

يمسح اختيار تسجيل الدخول المحفوظ بـ Google في Android، فيعرض 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

مثال

استدعاءات مفاتيح المرور القياسية WebAuthn. لا يستطيع WebView العادي في Android تنفيذها؛ وفي التطبيق تُوجَّه إلى مدير مفاتيح المرور في Android، فتعمل الأكواد والمكتبات المكتوبة لـ Chrome (SimpleWebAuthn وHanko وCorbado وAuth0) دون تغيير.

تُرجع: Promise بقيمة PublicKeyCredential (مع .toJSON())، تمامًا كما في Chrome؛ وترفض بـ DOMException. تحتاج إلى: Android 9+ مع خدمات Google Play، وخادم مفاتيح مرور، وملف assetlinks.json في نطاقك يدرج التطبيق، وخادمك يقبل أصل التطبيق (انظر AppMint.passkey). لا يوجد مفتاح تفعيل في البناء.

سجّل الدخول بالواجهة القياسية (الخيارات من خادمك، والحقول الثنائية كـ 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);
  }
}

سجّل مفتاح مرور:

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

قرّر أي أزرار تسجيل الدخول تعرض بناءً على ما يدعمه الهاتف (الملء التلقائي لمفتاح المرور معطّل في التطبيق، لذا أبقِ الزر):

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
}

ملاحظات: الملء التلقائي لمفتاح المرور في حقل نصي (mediation: 'conditional') غير متاح في التطبيق - تجيب isConditionalMediationAvailable() بـ false ويُرفض الاستدعاء بـ NotSupportedError؛ ابدأ تسجيل الدخول من زر. يعمل signal (AbortController). الصفحة المضمّنة في التطبيق ليس لها نطاق خاص: اضبط rp.id / rpId على نطاقك. الاستدعاءات دون publicKey (كلمات المرور، OTP) تذهب إلى WebView كما كان سابقًا.

OTPCredential معيار الويب#

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

مثال

واجهة WebOTP القياسية: يُملأ رمز الدخول من الرسالة النصية تلقائيًا. يعرض Android نافذة Google «هل تسمح لهذا التطبيق بقراءة هذه الرسالة؟» - دون إذن SMS.

تُرجع: Promise بقيمة OTPCredential {type:'otp', code}. ترفض بـ AbortError عندما ينقر المستخدم على «رفض» أو يُلغى الإشارة، وبـ InvalidStateError بعد خمس دقائق دون رسالة نصية، وبـ NotSupportedError دون خدمات Google Play. تحتاج إلى: لا شيء لتفعيله. يجب أن تنتهي الرسالة بسطر يسمّي مضيف هذه الصفحة: @<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));
}

الرسالة التي يرسلها خادمك (السطر الأخير هو الربط):

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

ملاحظات: يجب أن يكون المضيف هو مضيف الصفحة نفسها - وهو في التطبيق المبني على موقع نطاقك (الرسالة نفسها التي يقرؤها Chrome)؛ أما في التطبيق دون اتصال/المبني بالذكاء الاصطناعي فهو appassets.androidplatform.net. تُتجاهل الرسالة الخاصة بمضيف آخر ويستمر التطبيق في الانتظار. الصفحة العليا فقط (الرموز المرتبطة بأصل مضمَّن، @top #code @iframe، غير مدعومة). يُنتظر طلب واحد فقط في كل مرة؛ والأحدث يُلغي الأقدم. الحدث الذي يقف خلفه: appmint:otp.

__otpCancel الجسر (bridge)#

window.WebToApk.__otpCancel(requestId: String)

يتخلى عن طلب WebOTP معلّق (AbortSignal، أو حلّ محله طلب أحدث).

مثال

ناقل داخلي يقف خلف إلغاء طلب WebOTP (AbortSignal). تستخدم الصفحات الواجهة القياسية؛ لا تستدعِ هذا مباشرةً أبدًا - فقد تتغير دوال __ دون إشعار.

تُرجع: لا شيء. تحتاج إلى: لا شيء لتفعيله.

استخدم الواجهة العامة:

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 الجسر (bridge)#

window.WebToApk.__otpStart(requestId: String)

WebOTP: ينتظر (حتى خمس دقائق) رسالة نصية واحدة عبر نافذة SMS User Consent من Google - دون إذن SMS. الإجابة هي `appmint:otp {requestId, message|error}`.

مثال

ناقل داخلي يقف خلف navigator.credentials.get({ otp: { transport: ['sms'] } }). تستخدم الصفحات الواجهة القياسية؛ لا تستدعِ هذا مباشرةً أبدًا - فقد تتغير دوال __ دون إشعار.

تُرجع: لا شيء؛ تصل الإجابة كحدث appmint:otp بالقيمة {requestId, message} أو {requestId, error} (cancelled أو timeout أو unavailable). تحتاج إلى: لا شيء لتفعيله.

استخدم الواجهة العامة:

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 الجسر (bridge)#

window.WebToApk.passkeyIsAvailable(): Boolean

تكون true على Android 9+ مع خدمات Google Play - أي الهواتف القادرة على حمل مفتاح مرور.

مثال

يخبرك ما إذا كان هذا الهاتف يستطيع استخدام مفاتيح المرور. تستخدم الصفحات عادةً الواجهة العامة AppMint.passkey.isAvailable() (أو PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable()) التي تغلّف هذا الاستدعاء.

تُرجع: قيمة منطقية، فورًا - true على Android 9+ مع خدمات Google Play وWebView حديثة بما يكفي لقناة مفاتيح المرور. تحتاج إلى: لا شيء.

الواجهة العامة:

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;

اعرض «إضافة مفتاح مرور» مرة واحدة بعد تسجيل الدخول بكلمة المرور، وفقط على الهواتف القادرة على حمله (التحقق القياسي، والكود نفسه في 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. أخفِ زر مفتاح المرور وقدّم طريقة تسجيل الدخول الأخرى.

passkeyOrigin الجسر (bridge)#

window.WebToApk.passkeyOrigin(): String

المصدر الذي يراه خادم مفاتيح المرور من هذا التطبيق: `android:apk-key-hash:<base64url SHA-256>`.

مثال

يعطي الأصل (origin) الذي يراه خادم مفاتيح المرور لديك من هذا التطبيق: android:apk-key-hash:<base64url SHA-256 of the signing certificate>. تستخدم الصفحات عادةً الواجهة العامة 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 الجسر (bridge)#

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

===== تسجيل الدخول الأصلي بـ Google عبر Credential Manager ===== يتجاوز حظر Google لـ OAuth في WebView على Android (الخطأ disallowed_useragent) بتشغيل مسار تسجيل الدخول في Android الأصلي وتمرير رمز Google ID الناتج فقط إلى JS، حيث يستهلكه Firebase Web SDK عبر GoogleAuthProvider.credential(idToken) + signInWithCredential(...). عقد JS: window.WebToApk.signInWithGoogle(serverClientId, callbackId) serverClientId "" = عميل Web (type 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) ويجيب برمز Google ID. تستخدم الصفحات عادةً دالة Promise المساعدة WebToApkAuth.signInWithGoogle(webClientId) التي تغلّف هذا الاستدعاء.

تُرجع: لا شيء. تصل الإجابة لاحقًا: يستدعي الغلاف window.__webToApkAuth[callbackId](json) بقيمة '{"ok":true,"idToken":"...","email":"...","displayName":"...","profilePictureUri":"..."}' أو '{"ok":false,"error":"<code>","detail":"..."}'، ثم يزيل ذلك الإدخال. تحتاج إلى: معرّف عميل Web الخاص بـ 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;
  }
}

الاستدعاء الخام (ترتيب المعاملات: معرّف عميل الويب، ثم معرّف دالة الاستدعاء الراجع):

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

الاستدعاء الخام مع معرّف عميل فارغ، وهو يستخدم عميل Web من 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 (لم يُمرَّر أي معرّف ولا يوجد عميل Web في google-services.json الذي رفعته)، foreign_origin (الصفحة المعروضة ليست صفحة التطبيق نفسه)، failed، unexpected_credential_type، parse_failed: .... يستخدم الوسيط الأول الفارغ عميل Web من google-services.json الذي رفعته إن كان يحتويه. تحقق من رمز ID على خادمك (أو سلّمه إلى Firebase بـ GoogleAuthProvider.credential(idToken)).

signOutGoogle الجسر (bridge)#

window.WebToApk.signOutGoogle(callbackId: String)

يمسح حالة Credential Manager المحفوظة ليعرض استدعاء signInWithGoogle التالي أداة اختيار الحساب مجددًا (بدلًا من إعادة اختيار الحساب الأخير بصمت). ينبغي للتطبيقات المُنشأة استدعاء هذا عند تسجيل الخروج بمبادرة المستخدم، إضافةً إلى firebase.auth().signOut().

مثال

يمسح حالة تسجيل الدخول المحفوظة بـ Google في Android، فيعرض تسجيل الدخول التالي أداة اختيار الحساب مجددًا. تستخدم الصفحات عادةً دالة 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 أو من خادمك - افعل ذلك بنفسك.

يتم إنشاؤه من بيئة تشغيل التطبيق وملفات الأمثلة الخاصة به عند كل بناء للمستندات. اقرأه بصيغة Markdown · جميع العائلات.

تمت المراجعة مقابل الجسر المُرفق بتاريخ 2026-10-22.