واجهة JavaScript bridge API
الهاتف والرسائل النصية وسجل المكالمات
إجراء مكالمة، وإرسال رسالة أو إعداد مسودة، وقراءة السجل. كل هذه مقيّدة بأذونات ويسأل عنها Play.
composeSms الجسر (bridge)#
window.WebToApk.composeSms(phoneNumber: String, message: String): Boolean
يفتح تطبيق الرسائل لدى المستخدم والرسالة معبأة مسبقًا. لا يحتاج إذنًا ولا يمكن أن يرفضه Play، وهو الافتراضي الصحيح لأي شيء غير آلي. يُرجع false عندما لا يملك الهاتف تطبيق رسائل (أو تعذّر فتحه).
مثال
يفتح تطبيق الرسائل لدى المستخدم والرقم والنص معبأين مسبقًا. يضغط المستخدم إرسال.
يُرجع: true عند فتح تطبيق الرسائل، وfalse عندما لا يملك الهاتف تطبيق رسائل أو تعذّر فتحه (متزامن). يتطلب: لا شيء، فلا خيار إنشاء ولا إذن.
زر "راسلنا".
document.getElementById('textUs').addEventListener('click', function () {
if (window.WebToApk && window.WebToApk.composeSms) {
var ok = WebToApk.composeSms('+911234567890', 'Hello, I need help with my order');
if (!ok) showMessage('Could not open the SMS app.');
} else {
// In a normal browser the standard sms: link does the same job.
location.href = 'sms:+911234567890?body=' + encodeURIComponent('Hello, I need help with my order');
}
});زر "ذكّر" على كل صف موعد، يعبّئ مسبقًا تذكيرًا لرقم ذلك العميل:
document.getElementById('appointments').addEventListener('click', function (e) {
var row = e.target.closest('[data-phone]');
if (!row || !(window.WebToApk && typeof window.WebToApk.composeSms === 'function')) return;
var when = new Date(Number(row.getAttribute('data-time')));
var text = 'Hi ' + row.getAttribute('data-name') + ', a reminder of your appointment on ' +
when.toLocaleDateString() + ' at ' +
when.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }) + '.';
if (!WebToApk.composeSms(row.getAttribute('data-phone'), text)) {
showMessage('This phone has no SMS app.');
}
});نموذج "إرسال برسالة نصية": عندما لا يملك الهاتف تطبيق رسائل (false)، انسخ النص ليلصقه المستخدم في مكان آخر:
document.getElementById('smsForm').addEventListener('submit', function (e) {
e.preventDefault();
var number = document.getElementById('to').value.trim();
var text = document.getElementById('message').value;
if (!number || !text) return;
var opened = window.WebToApk && typeof window.WebToApk.composeSms === 'function' &&
WebToApk.composeSms(number, text);
if (!opened && navigator.clipboard) {
navigator.clipboard.writeText(text).then(function () {
showMessage('No SMS app here. The message was copied instead.');
});
}
});ملاحظات: لا يُرسل شيء حتى ينقر المستخدم إرسال في تطبيق الرسائل الخاص به، لذا لا تحتاج هذه الطريقة نفسها إلى إذن رسائل. استخدمها لكل رسالة يكتبها المستخدم أو يوافق عليها؛ واستخدم sendSms فقط عندما يجب على التطبيق الإرسال بنفسه. في التطبيقات المبنية بالذكاء الاصطناعي واستيرادات Google AI Studio يفحص الإنشاء شيفرتك؛ والاستدعاء composeSms( لا يحدد الرسائل النصية (قراءة وإرسال)، فقط sendSms وlistSms يفعلان، لذا فزر "راسلنا" لا يضيف إذنًا مقيَّدًا في Play.
listCallLog الجسر (bridge)#
window.WebToApk.listCallLog(requestId: String, limit: Int, offset: Int, sinceMillis: String)
WebToApk.listCallLog(id, limit, offset, sinceMillis) تُسلَّم كـ `appmint:calllog` / window.onAppMintCallLog.
موصوف ضمن مجموعته، سجل المكالمات، وليس على حدة.
مثال
يقرأ سجل مكالمات الهاتف، الأحدث أولًا.
يُرجع: لا شيء الآن. الإجابة هي الحدث appmint:calllog (وwindow.onAppMintCallLog) مع detail = { requestId, calls: [{ number, name, type, date, duration }] } أو { requestId, error }. date بالميلي ثانية منذ 1970؛ وduration بالثواني. يتطلب: فعّل سجل المكالمات (قراءة) في الخطوة 4 (الوصول) عند الإنشاء؛ ويُسأل المستخدم عن الإذن عند أول استدعاء.
اعرض آخر 50 مكالمة.
function loadCalls() {
if (!(window.WebToApk && window.WebToApk.listCallLog)) return;
var id = 'calls-' + Date.now();
window.addEventListener('appmint:calllog', function handler(e) {
if (!e.detail || e.detail.requestId !== id) return;
window.removeEventListener('appmint:calllog', handler);
if (e.detail.error) { showMessage('Call log: ' + e.detail.error); return; }
e.detail.calls.forEach(function (c) {
// c.type: incoming | outgoing | missed | voicemail | rejected | blocked | other
addRow((c.name || c.number) + ' - ' + c.type + ' - ' +
new Date(c.date).toLocaleString() + ' - ' + c.duration + 's');
});
});
WebToApk.listCallLog(id, 50, 0, '0'); // limit, offset, since (string)
}مكالمات آخر 24 ساعة فقط، باستخدام خطّاف الصفحة. sinceMillis نص لأن الرقم أكبر من int الجسر.
window.onAppMintCallLog = function (detail) {
if (detail.requestId !== 'today') return;
if (detail.error) return;
document.getElementById('count').textContent = detail.calls.length + ' calls today';
};
if (window.WebToApk && window.WebToApk.listCallLog) {
WebToApk.listCallLog('today', 500, 0, String(Date.now() - 24 * 60 * 60 * 1000));
}شاشة سجل مكالمات بزر "تحميل المزيد"، تجلب 25 مكالمة في كل مرة بـ offset:
var PAGE = 25, loaded = 0;
function loadMoreCalls() {
if (!(window.WebToApk && typeof window.WebToApk.listCallLog === 'function')) return;
var id = 'page-' + loaded;
window.addEventListener('appmint:calllog', function handler(e) {
if (!e.detail || e.detail.requestId !== id) return;
window.removeEventListener('appmint:calllog', handler);
if (e.detail.error) { showMessage('Call log: ' + e.detail.error); return; }
e.detail.calls.forEach(function (c) { addRow(c.name || c.number, c.type, c.date); });
loaded += e.detail.calls.length;
document.getElementById('more').hidden = e.detail.calls.length < PAGE; // last page
});
WebToApk.listCallLog(id, PAGE, loaded, '0'); // skip the calls already shown
}
document.getElementById('more').addEventListener('click', loadMoreCalls);
loadMoreCalls();ملاحظات: limit محصور بين 1 و2000. الأخطاء: not_enabled وpermission_denied وforeign_origin. سجل المكالمات إذن مقيَّد في Google Play: عليك تقديم إقرار أذونات معتمد في Play Console وإلا رُفض التطبيق.
listSms الجسر (bridge)#
window.WebToApk.listSms(requestId: String, box: String, limit: Int, offset: Int)
WebToApk.composeSms(number, message) <- بلا إذن، يفتح تطبيق الرسائل WebToApk.sendSms(id, number, message, wantDelivery) WebToApk.listSms(id, "inbox"|"sent"|"draft", limit, offset) نتائج الإرسال: `appmint:sms` / window.onAppMintSms {requestId, status:"sent"|"delivered"|"failed", ok, reason?} الرسائل الواردة: `appmint:sms-received` / window.onAppMintSmsReceived.
موصوف ضمن مجموعته، الرسائل النصية، وليس على حدة.
مثال
يقرأ رسائل SMS من صندوق الوارد أو الصادر أو المسودات في الهاتف، الأحدث أولًا. يوضح هذا الملف أيضًا كيف تستقبل الرسائل الجديدة فور وصولها.
يُرجع: لا شيء الآن. الإجابة هي الحدث appmint:sms (وwindow.onAppMintSms) مع detail = { requestId, messages: [{ id, address, body, date, read, box }] }. عند الفشل: { requestId, status: 'failed', ok: false, reason }. يتطلب: فعّل الرسائل النصية (قراءة وإرسال) في الخطوة 4 (الوصول) عند الإنشاء؛ ويُسأل المستخدم عن الإذن عند أول استدعاء.
اقرأ أحدث 30 رسالة في صندوق الوارد.
function loadInbox() {
if (!(window.WebToApk && window.WebToApk.listSms)) return;
var id = 'inbox-' + Date.now();
window.addEventListener('appmint:sms', function handler(e) {
if (!e.detail || e.detail.requestId !== id) return;
window.removeEventListener('appmint:sms', handler);
if (e.detail.status === 'failed') { showMessage('Cannot read SMS: ' + e.detail.reason); return; }
e.detail.messages.forEach(function (m) {
addRow(m.address + ': ' + m.body + (m.read ? '' : ' (new)') +
' - ' + new Date(m.date).toLocaleString());
});
});
WebToApk.listSms(id, 'inbox', 30, 0); // box: 'inbox' | 'sent' | 'draft'
}الرسائل الجديدة فور وصولها (appmint:sms-received، وأيضًا window.onAppMintSmsReceived). الرسائل التي تصل والتطبيق مغلق تُحفظ (آخر 50) وتُسلَّم لصفحتك عند فتح التطبيق مجددًا.
window.addEventListener('appmint:sms-received', function (e) {
// e.detail = { address: '+91...', body: 'Your OTP is 4821', date: 1737000000000 }
var code = (e.detail.body.match(/\b\d{4,6}\b/) || [])[0];
if (code) document.getElementById('otp').value = code;
});
// or, as a page hook:
window.onAppMintSmsReceived = function (sms) {
console.log('SMS from ' + sms.address);
};متتبع مصاريف يفحص آخر 200 رسالة في صندوق الوارد بحثًا عن خصومات مصرفية ويجمع المبالغ:
function scanSpending() {
if (!(window.WebToApk && typeof window.WebToApk.listSms === 'function')) return;
var id = 'spend-' + Date.now();
window.addEventListener('appmint:sms', function handler(e) {
if (!e.detail || e.detail.requestId !== id) return;
window.removeEventListener('appmint:sms', handler);
if (e.detail.status === 'failed') { showMessage('Cannot read SMS: ' + e.detail.reason); return; }
var total = 0;
e.detail.messages.forEach(function (m) {
if (!/debited/i.test(m.body)) return;
var amount = m.body.match(/(?:Rs\.?|INR)\s*([\d,]+(?:\.\d{1,2})?)/i);
if (amount) total += parseFloat(amount[1].replace(/,/g, ''));
});
document.getElementById('spent').textContent = 'Spent: Rs ' + total.toFixed(2);
});
WebToApk.listSms(id, 'inbox', 200, 0);
}ملاحظات: limit محصور بين 1 و500. أي اسم صندوق غير معروف يقرأ صندوق الوارد. قيم reason: not_enabled وpermission_denied وforeign_origin. الرسائل المستلمة تصل أيضًا إلى تطبيق الرسائل المعتاد لدى المستخدم. الرسائل النصية إذن مقيَّد في Google Play: عليك تقديم إقرار أذونات معتمد في Play Console (غالبًا لا يؤهَّل إلا تطبيقات الرسائل الافتراضية) وإلا رُفض التطبيق.
makePhoneCall الجسر (bridge)#
window.WebToApk.makePhoneCall(phoneNumber: String)
يتصل بـ [phoneNumber]. - تفعيل إذن الهاتف في الإنشاء: يُجري المكالمة مباشرة. في المرة الأولى يطلب Android من المستخدم إذن الهاتف؛ وإذا رفض، يُفتح الطالب (dialer) والرقم مكتوب فيه ليتمكن من إجرائها بنفسه. - تعطيل إذن الهاتف: يفتح الطالب والرقم مكتوب فيه (الطالب لا يحتاج إذنًا) وينقر المستخدم اتصال. - الصفحة التي ليست من صفحات التطبيق نفسه (موقع خارجي مفتوح في التطبيق) تحصل دائمًا على الطالب، ولا تُجري مكالمة مباشرة. رابط `tel:` عادي في HTML يفتح الطالب أيضًا وكثيرًا ما يكفي.
مثال
يبدأ مكالمة هاتفية إلى رقم، أو يفتح الطالب والرقم مكتوب فيه.
يُرجع: لا شيء. يتطلب: لا شيء لفتح الطالب. فعّل الهاتف في الخطوة 4 (الوصول) عند الإنشاء إذا أردت أن تبدأ المكالمة مباشرة.
زر "اتصل بالمتجر".
document.getElementById('callShop').addEventListener('click', function () {
if (window.WebToApk && window.WebToApk.makePhoneCall) {
WebToApk.makePhoneCall('+911234567890');
}
});لا حاجة لخيار: رابط tel: عادي يفتح الطالب بالرقم، في التطبيق وفي المتصفح. وكثيرًا ما يكون هذا كل ما تحتاجه.
<a href="tel:+911234567890">Call the shop</a>معالج واحد لدليل موظفين كامل، حيث يحمل كل صف رقمه في سمة data-phone:
document.getElementById('staffList').addEventListener('click', function (e) {
var btn = e.target.closest('[data-phone]');
if (!btn) return;
var number = btn.getAttribute('data-phone').replace(/[^\d+]/g, ''); // keep digits and +
if (!number) return;
if (window.WebToApk && typeof window.WebToApk.makePhoneCall === 'function') {
WebToApk.makePhoneCall(number);
} else {
location.href = 'tel:' + number; // a normal browser
}
});ملاحظات: مع إيقاف الهاتف، يُفتح الطالب بالرقم ويضغط المستخدم اتصال. ومع تفعيل الهاتف تبدأ المكالمة مباشرة؛ وفي المرة الأولى يسأل Android المستخدم عن إذن إجراء المكالمات، وإن رفض يُفتح الطالب بدلًا من ذلك ليتمكن من إجراء المكالمة بنفسه. الصفحة التي ليست من التطبيق نفسه (موقع خارجي مفتوح داخل التطبيق) تحصل دائمًا على الطالب. مرّر الرقم كنص عادي. لا يُبلَّغ بشيء، فلا يمكنك معرفة ما إذا أُجريت المكالمة. خيار الهاتف يضيف إذن CALL_PHONE إلى صفحتك في Play؛ أعلن عنه في نموذج أمان البيانات ولا تفعّله إلا إذا احتجته.
sendSms الجسر (bridge)#
window.WebToApk.sendSms(
WebToApk.composeSms(number, message) <- بلا إذن، يفتح تطبيق الرسائل WebToApk.sendSms(id, number, message, wantDelivery) WebToApk.listSms(id, "inbox"|"sent"|"draft", limit, offset) نتائج الإرسال: `appmint:sms` / window.onAppMintSms {requestId, status:"sent"|"delivered"|"failed", ok, reason?} الرسائل الواردة: `appmint:sms-received` / window.onAppMintSmsReceived.
موصوف ضمن مجموعته، الرسائل النصية، وليس على حدة.
مثال
يرسل رسالة نصية مباشرة من التطبيق دون فتح تطبيق الرسائل، ويبلّغ بما حدث فعلًا.
يُرجع: لا شيء الآن. الإجابة هي الحدث appmint:sms (وwindow.onAppMintSms) مع detail = { requestId, status: 'sent' | 'delivered' | 'failed', ok, reason? }. مع تقرير التسليم تحصل على إجابتين عند النجاح: sent، ثم delivered. يتطلب: فعّل الرسائل النصية (قراءة وإرسال) في الخطوة 4 (الوصول) عند الإنشاء؛ ويُسأل المستخدم عن الإذن عند أول استدعاء.
أرسل رمزًا وتابع حالته. لا تزل المستمع بعد أول إجابة عندما طلبت تقرير تسليم.
function sendCode(phone, code) {
if (!(window.WebToApk && window.WebToApk.sendSms)) {
showMessage('Sending SMS works only inside the app.');
return;
}
var id = 'sms-' + Date.now();
function handler(e) {
if (!e.detail || e.detail.requestId !== id) return;
var d = e.detail;
if (d.status === 'sent' && d.reason === 'no_delivery_report') {
showStatus('Sent (the network gave no delivery report)');
window.removeEventListener('appmint:sms', handler);
} else if (d.status === 'sent') {
showStatus('Sent');
} else if (d.status === 'delivered') {
showStatus('Delivered');
window.removeEventListener('appmint:sms', handler);
} else { // 'failed'
showStatus('Failed: ' + d.reason);
window.removeEventListener('appmint:sms', handler);
}
}
window.addEventListener('appmint:sms', handler);
WebToApk.sendSms(id, phone, 'Your code is ' + code, true); // true = delivery report
}بلا تقرير تسليم. مرّر false كآخر قيمة: تحصل على إجابة واحدة بالضبط، sent أو failed.
window.onAppMintSms = function (d) {
if (d.requestId === 'alert-1') showStatus(d.ok ? 'Alert sent' : 'Failed: ' + d.reason);
};
if (window.WebToApk && window.WebToApk.sendSms) {
WebToApk.sendSms('alert-1', '+911234567890', 'Gate opened at ' + new Date().toLocaleTimeString(), false);
}الإشعار نفسه إلى قائمة قصيرة من الأرقام، واحدًا بعد الآخر، مع جمع كل نتيجة (والتوقف عندما تكون الرسائل النصية معطّلة أو مرفوضة، لأن كل رقم آخر سيفشل بالطريقة نفسها):
function sendToAll(numbers, text, done) {
if (!(window.WebToApk && typeof window.WebToApk.sendSms === 'function')) { done(null); return; }
var results = [], i = 0;
function next() {
if (i >= numbers.length) { done(results); return; }
var number = numbers[i++];
var id = 'notice-' + Date.now() + '-' + i;
window.addEventListener('appmint:sms', function handler(e) {
if (!e.detail || e.detail.requestId !== id) return;
window.removeEventListener('appmint:sms', handler);
results.push({ number: number, ok: e.detail.ok, reason: e.detail.reason });
var r = e.detail.reason;
if (r === 'not_enabled' || r === 'permission_denied' || r === 'foreign_origin') done(results);
else next();
});
WebToApk.sendSms(id, number, text, false); // one answer per number
}
next();
}
sendToAll(['+911234567890', '+919876543210'], 'Practice moved to 6 pm today.', function (results) {
if (!results) return showMessage('Sending SMS works only inside the app.');
showStatus(results.filter(function (r) { return r.ok; }).length + ' of ' + results.length + ' sent');
});ملاحظات: النص الطويل يُقسَّم إلى أجزاء ويُرسل كرسالة واحدة. إذا لم يصل تقرير تسليم خلال 60 ثانية تحصل على status: 'sent' مع reason: 'no_delivery_report'، فالرسالة أُرسلت مع ذلك. قيم reason: not_enabled وpermission_denied وforeign_origin وinvalid_arguments (رقم أو نص فارغ) وno_service وradio_off وgeneric_failure وnull_pdu وnot_delivered وغيرها. استخدم requestId نصيًا. قد تكلّف كل رسالة المستخدم مالًا. الرسائل النصية إذن مقيَّد في Google Play: عليك تقديم إقرار أذونات معتمد في Play Console (غالبًا لا يؤهَّل إلا تطبيقات الرسائل الافتراضية)، لذا فضّل composeSms عندما يستطيع المستخدم الضغط على إرسال.