أنشئ APK مجانًا

واجهة JavaScript bridge API

المهام الخلفية

عمل يستمر بعد إغلاق التطبيق: حفظ تلقائي متين، ومزامنة دورية.

workCancel الجسر (bridge)#

window.WebToApk.workCancel(id: String)

لا حاجة إلى إذن؛ تصل النتائج كـ `appmint:work` / window.onAppMintWork.

موصوف ضمن مجموعته، المهام الخلفية (WorkManager): حفظ تلقائي متين + مزامنة أصلية دورية، وليس وحده.

مثال

يلغي مهمة خلفية (لمرة واحدة أو متكررة) بمعرّفها ويحذف طلبها المحفوظ.

يعيد: لا شيء مباشرة. يجيب حدث appmint:work (وكذلك window.onAppMintWork) بـ { id, status: 'cancelled' }. يتطلب: لا شيء.

function stopSync() {
  if (!(window.WebToApk && window.WebToApk.workCancel)) return;
  WebToApk.workCancel('sync');
}

window.addEventListener('appmint:work', function (e) {
  if (e.detail.id === 'sync' && e.detail.status === 'cancelled') {
    document.getElementById('sync-status').textContent = 'Background sync is off';
  }
});

تخلَّ عن مسودة: ألغِ حفظها التلقائي الذي لم يُرسل بعد، ثم امسح المحرر عندما يؤكد Android:

function discardDraft() {
  if (!(window.WebToApk && window.WebToApk.workCancel)) { clearEditor(); return; }
  function onWork(e) {
    if (e.detail.id !== 'autosave' || e.detail.status !== 'cancelled') return;
    window.removeEventListener('appmint:work', onWork);
    clearEditor();
  }
  window.addEventListener('appmint:work', onWork);
  WebToApk.workCancel('autosave');
}

عند تسجيل الخروج، ألغِ كل مهمة لهذا التطبيق، باستخدام المعرّفات التي يعرضها workList:

function cancelAllJobs() {
  if (!(window.WebToApk && window.WebToApk.workList && window.WebToApk.workCancel)) return;
  const requestId = 'signout-' + Date.now();
  function onWork(e) {
    if (String(e.detail.requestId) !== requestId) return;
    window.removeEventListener('appmint:work', onWork);
    (e.detail.jobs || []).forEach(function (job) {
      WebToApk.workCancel(job.id);                      // each answers { id, status: 'cancelled' }
    });
  }
  window.addEventListener('appmint:work', onWork);
  WebToApk.workList(requestId);
}

ملاحظات: يجيب بـ cancelled حتى لو لم توجد مهمة بذلك المعرّف. الحفظ التلقائي الذي لم يُرسل بعد يُسقَط، فلا تلغِ إلا إذا كنت لا تريد تسليمه فعلًا.

workEnqueueOnline الجسر (bridge)#

window.WebToApk.workEnqueueOnline(id: String, url: String, method: String, headersJson: String, payload: String)

لا حاجة إلى إذن؛ تصل النتائج كـ `appmint:work` / window.onAppMintWork.

موصوف ضمن مجموعته، المهام الخلفية (WorkManager): حفظ تلقائي متين + مزامنة أصلية دورية، وليس وحده.

مثال

يسلّم طلب HTTP واحدًا إلى Android ليرسله عندما يكون الهاتف متصلًا - حتى لو أغلق المستخدم التطبيق أو أعاد تشغيل الهاتف. استخدمه لحفظ تلقائي موثوق.

يعيد: لا شيء مباشرة. تصل الإجابة كحدث appmint:work (وكذلك window.onAppMintWork(detail) إن عرّفته): { id, status: 'enqueued' }، أو { id, status: 'failed', reason } بالسبب invalid_arguments (معرّف فارغ أو عنوان لا يبدأ بـ http) أو enqueue_failed. يتطلب: لا شيء.

حفظ تلقائي متين: المعرّف نفسه يحل محل حفظ لم يُرسل بعد، فتفوز أحدث حالة.

function autoSave(state, token) {
  if (!(window.WebToApk && window.WebToApk.workEnqueueOnline)) {
    showMessage('Offline auto-save works in the installed app only.');
    return;
  }
  WebToApk.workEnqueueOnline(
    'autosave',                                        // your job id
    'https://api.example.com/save',                    // http(s) URL
    'POST',                                            // POST | PUT | GET ...
    JSON.stringify({ Authorization: 'Bearer ' + token }), // headers as a JSON string
    JSON.stringify(state)                              // body as a string ('' for none)
  );
}

window.addEventListener('appmint:work', function (e) {
  const d = e.detail;
  if (d.id !== 'autosave') return;
  if (d.status === 'enqueued') showSavedBadge('Will sync when online');
  if (d.status === 'failed') showMessage('Could not queue the save: ' + d.reason);
});

ضع كل إرسال نموذج في قائمة الانتظار كمهمة مستقلة (معرّف فريد لكل إرسال، لئلا يحل الثاني محل الأول) وانتظر جواب Android:

function queueFeedback(form) {
  return new Promise(function (resolve, reject) {
    if (!(window.WebToApk && window.WebToApk.workEnqueueOnline)) {
      reject(new Error('not_in_app'));
      return;
    }
    const id = 'feedback-' + Date.now();
    function onWork(e) {
      if (e.detail.id !== id) return;                  // another job's answer
      window.removeEventListener('appmint:work', onWork);
      if (e.detail.status === 'enqueued') resolve(id);
      else reject(new Error(e.detail.reason));         // invalid_arguments | enqueue_failed
    }
    window.addEventListener('appmint:work', onWork);
    WebToApk.workEnqueueOnline(id, 'https://api.example.com/feedback', 'POST', '{}',
      JSON.stringify({ rating: form.rating.value, text: form.text.value }));
  });
}

احفظ ملاحظة كنص عادي بـ PUT؛ تحديد Content-Type بنفسك يمنع إضافة افتراضي JSON:

function saveNote(noteId, text) {
  if (!(window.WebToApk && window.WebToApk.workEnqueueOnline)) return;
  WebToApk.workEnqueueOnline(
    'note-' + noteId,                                  // same note, same id: only the newest text is sent
    'https://api.example.com/notes/' + encodeURIComponent(noteId),
    'PUT',
    JSON.stringify({ 'Content-Type': 'text/plain; charset=utf-8' }),
    text
  );
}

document.getElementById('note').addEventListener('change', function (e) {
  saveNote(e.target.dataset.noteId, e.target.value);
});

ملاحظات: enqueued تعني أن Android قبل المهمة، لا أن الخادم تلقاها؛ ولا يوجد حدث عند تسليمها. قواعد التسليم: رد 2xx ينهي المهمة؛ ورد 4xx يوقفها (بلا إعادة محاولة)؛ وخطأ الشبكة أو 5xx يعيد المحاولة بتأخيرات متزايدة، حتى 8 محاولات. يُضاف Content-Type: application/json ما لم تحدد واحدًا. وGET لا يرسل جسمًا. لا يعمل JavaScript الصفحة في الخلفية - فقط هذا الطلب الأصلي.

workList الجسر (bridge)#

window.WebToApk.workList(requestId: String)

لا حاجة إلى إذن؛ تصل النتائج كـ `appmint:work` / window.onAppMintWork.

موصوف ضمن مجموعته، المهام الخلفية (WorkManager): حفظ تلقائي متين + مزامنة أصلية دورية، وليس وحده.

مثال

يسرد المهام الخلفية التي وضعها هذا التطبيق في قائمة الانتظار أو جدولها.

يعيد: لا شيء مباشرة. يجيب حدث appmint:work (وكذلك window.onAppMintWork) بـ { requestId, jobs: [{ id, periodic, url, createdAt, pendingDelivery }] }. pendingDelivery هو true ما دامت مهمة لمرة واحدة لم تُسلَّم بعد (ودائمًا للمهمة الدورية). يتطلب: لا شيء.

غلاف Promise صغير يطابق الإجابة بـ requestId:

function listJobs() {
  return new Promise(function (resolve) {
    if (!(window.WebToApk && window.WebToApk.workList)) { resolve([]); return; }
    const requestId = 'jobs-' + Date.now();
    function onWork(e) {
      if (String(e.detail.requestId) !== requestId) return;   // not our answer
      window.removeEventListener('appmint:work', onWork);
      resolve(e.detail.jobs || []);
    }
    window.addEventListener('appmint:work', onWork);
    WebToApk.workList(requestId);
  });
}

listJobs().then(function (jobs) {
  const unsent = jobs.filter(function (j) { return !j.periodic && j.pendingDelivery; });
  document.getElementById('unsent').textContent =
    unsent.length ? unsent.length + ' change(s) waiting for network' : 'All changes saved';
});

اعرض كل مهمة في شاشة إعدادات، مع وقت وضعها في قائمة الانتظار:

window.addEventListener('appmint:work', function (e) {
  if (e.detail.requestId !== 'settings-jobs') return;
  const list = document.getElementById('jobs');
  list.innerHTML = '';
  (e.detail.jobs || []).forEach(function (job) {
    const li = document.createElement('li');
    li.textContent = job.id + (job.periodic ? ' (repeats)' : job.pendingDelivery ? ' (waiting)' : '') +
      ' - since ' + new Date(job.createdAt).toLocaleString();
    list.appendChild(li);
  });
});

if (window.WebToApk && window.WebToApk.workList) WebToApk.workList('settings-jobs');

عند فتح التطبيق، اضبط مفتاح "المزامنة في الخلفية" بحسب ما إذا كانت مهمة 'sync' المتكررة لا تزال مسجّلة:

window.addEventListener('appmint:work', function (e) {
  if (e.detail.requestId !== 'startup') return;
  const on = (e.detail.jobs || []).some(function (j) { return j.id === 'sync' && j.periodic; });
  document.getElementById('sync-toggle').checked = on;
});

document.addEventListener('DOMContentLoaded', function () {
  if (window.WebToApk && window.WebToApk.workList) WebToApk.workList('startup');
});

ملاحظات: تغادر المهمة لمرة واحدة القائمة عند تسليمها، أو رفضها برد 4xx، أو التخلي عنها بعد 8 محاولات. قد يعود requestId الرقمي كرقم، فقارن بـ String(...).

workSchedulePeriodic الجسر (bridge)#

window.WebToApk.workSchedulePeriodic(id: String, url: String, method: String, headersJson: String, payload: String, intervalMinutes: Int)

لا حاجة إلى إذن؛ تصل النتائج كـ `appmint:work` / window.onAppMintWork.

موصوف ضمن مجموعته، المهام الخلفية (WorkManager): حفظ تلقائي متين + مزامنة أصلية دورية، وليس وحده.

مثال

يجدول طلب HTTP متكررًا يرسله Android في الخلفية (عند الاتصال)، حتى والتطبيق مغلق. مناسب لمحفّز مزامنة أو نبضة.

يعيد: لا شيء مباشرة. تصل الإجابة كحدث appmint:work (وكذلك window.onAppMintWork(detail)): { id, status: 'scheduled', intervalMinutes }، أو { id, status: 'failed', reason } بالسبب invalid_arguments أو schedule_failed. يتطلب: لا شيء.

function startBackgroundSync(userId) {
  if (!(window.WebToApk && window.WebToApk.workSchedulePeriodic)) {
    showMessage('Background sync works in the installed app only.');
    return;
  }
  WebToApk.workSchedulePeriodic(
    'sync',                                   // job id (scheduling again updates it)
    'https://api.example.com/sync',
    'POST',
    JSON.stringify({ 'X-User': userId }),     // headers as a JSON string
    JSON.stringify({ userId: userId }),       // the same body is sent every time
    30                                        // minutes; values under 15 become 15
  );
}

// Optional page hook instead of addEventListener:
window.onAppMintWork = function (d) {
  if (d.id === 'sync' && d.status === 'scheduled') {
    console.log('sync every', d.intervalMinutes, 'minutes');
  }
  if (d.status === 'failed') console.warn('job', d.id, 'failed:', d.reason);
};

نبضة كل ساعة: طلب GET لا يرسل جسمًا، فمرّر سلسلة فارغة، و'{}' عندما لا تحتاج رؤوسًا:

function startHeartbeat(deviceId) {
  if (!(window.WebToApk && window.WebToApk.workSchedulePeriodic)) return;
  WebToApk.workSchedulePeriodic(
    'heartbeat',
    'https://api.example.com/ping?device=' + encodeURIComponent(deviceId),
    'GET',
    '{}',
    '',
    60
  );
}

دع المستخدم يختار مدى تكرار المزامنة، واعرض الفاصل الذي يستخدمه Android فعلًا (أي قيمة أقل من 15 تعود 15):

document.getElementById('sync-every').addEventListener('change', function (e) {
  if (!(window.WebToApk && window.WebToApk.workSchedulePeriodic)) return;
  const minutes = parseInt(e.target.value, 10);        // e.g. 5, 30, 120
  WebToApk.workSchedulePeriodic('sync', 'https://api.example.com/sync', 'POST', '{}',
    JSON.stringify({ reason: 'periodic' }), minutes);  // same id: updates the existing job
});

window.addEventListener('appmint:work', function (e) {
  const d = e.detail;
  if (d.id !== 'sync') return;
  if (d.status === 'scheduled') {
    document.getElementById('sync-note').textContent = 'Syncing about every ' + d.intervalMinutes + ' minutes';
  } else if (d.status === 'failed') {
    document.getElementById('sync-note').textContent = 'Could not schedule sync (' + d.reason + ')';
  }
});

ملاحظات: يقرر Android اللحظة الدقيقة؛ و15 دقيقة هي أقصر فاصل، وقد يؤخر توفير البطارية التشغيلات. يُثبَّت الجسم عند الجدولة؛ ولتغييره جدول مجددًا بالمعرّف نفسه. رد 4xx لا يلغي مهمة دورية؛ ويحاول التشغيل التالي مجددًا. أوقفه بـ workCancel('sync').

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

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