واجهة 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 الذي يعرضه الهاتف في المرة القادمة.
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.
passkeyAssetLinks الجسر (bridge)#
window.WebToApk.passkeyAssetLinks(): String
مصفوفة assetlinks.json (كنص) التي تخوّل هذا التطبيق المثبّت لنطاقك.
مثال
يبني محتوى assetlinks.json الذي يخبر Android بأن نطاقك يثق بهذا التطبيق (اسم الحزمة + SHA-256 للشهادة التي وُقّع بها التطبيق المثبّت). تستخدم الصفحات عادةً الواجهة العامة AppMint.passkey.assetLinks() التي تغلّف هذا الاستدعاء وتُرجع مصفوفة مُحلَّلة.
تُرجع: مصفوفة JSON كنص، فورًا ("[]" إن تعذّرت قراءة الشهادة). تحتاج إلى: لا شيء.
شغّله مرة واحدة في التطبيق المثبّت من Google Play، ثم احفظ المخرجات باسم https://your-domain.com/.well-known/assetlinks.json:
if (window.AppMint && window.AppMint.passkey) {
console.log(JSON.stringify(window.AppMint.passkey.assetLinks(), null, 2));
}يُرجع الاستدعاء الخام النص منسّقًا مسبقًا:
if (window.WebToApk && typeof window.WebToApk.passkeyAssetLinks === 'function') {
var text = window.WebToApk.passkeyAssetLinks();
console.log(text);
}اقرأ اسم الحزمة وبصمة SHA-256 من البيان، لوحدة تحكم تطلبهما واحدًا تلو الآخر:
if (window.AppMint && window.AppMint.passkey) {
var statements = window.AppMint.passkey.assetLinks();
if (statements.length === 0) {
console.log('The signing certificate could not be read.');
} else {
var target = statements[0].target;
console.log('Package: ' + target.package_name);
console.log('SHA-256: ' + target.sha256_cert_fingerprints[0]); // AB:CD:... upper-case, colon-separated
}
}ملاحظات: يجب أن يكون النطاق هو rpID الخاص بخادمك بالضبط (www. نطاق مختلف)، ويُقدَّم عبر https دون أي إعادة توجيه. إذا كان الملف خاطئًا، تفشل استدعاءات مفاتيح المرور مع SecurityError (السبب not_linked).
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 أو من خادمك - افعل ذلك بنفسك.