أنشئ APK مجانًا

واجهة JavaScript bridge API

التشغيل في الخلفية

خدمة في المقدمة تبدؤها الصفحة وتوقفها، وموقع يستمر بالوصول والتطبيق في الخلفية، وسياجات جغرافية تعمل والتطبيق مغلق، وكودك الخاص يعمل دوريًا بعد إعادة التشغيل. يحتاج كل منها إلى مفتاحه في البناء وإعلانًا في Play Console.

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

AppMint.background.register(name, options) / unregister / list / runNow / done / isBackgroundRun / currentRun / onRun

مثال

يشغّل كود صفحتك أنت بشكل دوري والتطبيق مغلق - لتحديث البيانات أو فحص سعر أو إرسال إشعار. يحمّل كل تشغيل صفحتك بشكل غير مرئي (النطاق نفسه، وlocalStorage/IndexedDB نفسهما، بلا شاشة ولا إعلانات) مع ?appmint_background=1&appmint_task=<name>&appmint_run=<runId>، ويطلق appmint:background { name, runId } بمجرد تحميلها، وينتظر done(runId, { ok }). يُقطع التشغيل بعد نحو 9 دقائق.

يعيد: كل دالة تعيد Promise. register ← { ok: true, name, intervalMinutes } أو { ok: false, reason } (not_enabled، invalid_name، interval_below_15_minutes، invalid_interval، schedule_failed). unregister ← true / false. list ← [{ name, intervalMinutes, requiresNetwork, requiresCharging, registeredAt, lastRunAt, lastResult }] (lastResult: ok، retry، failed، timeout، unsupported_mode، page_load_failed، …). runNow ← { ok } (تشغيل واحد بمجرد أن تسمح الشروط - للاختبار). done ← true عند إنهاء التشغيل. isBackgroundRun() وcurrentRun() متزامنتان. يتطلب: حدّد تشغيل كود الصفحة أثناء الإغلاق (خطوة الوصول ← الخلفية). تطبيقات الموقع ووضع عدم الاتصال/ZIP؛ أما تطبيقات المستندات والوسائط فلا تملك كود صفحة لتشغيله.

صفحة واحدة، دوران:

function startApp() {
  const bg = window.AppMint && AppMint.background;
  if (bg && bg.isBackgroundRun()) return;          // a background run: no UI work
  renderUi();
  if (bg) bg.register('prices', { intervalMinutes: 60, requiresNetwork: true }).then(function (r) {
    if (!r.ok) console.warn('background task not scheduled:', r.reason);
  });
}

// Runs only in the background copy of the page.
window.addEventListener('appmint:background', async function (e) {
  const run = e.detail;                              // { name: 'prices', runId: '…' }
  try {
    const price = await fetch('https://api.example.com/price').then(function (r) { return r.json(); });
    const last = Number(localStorage.getItem('lastPrice') || 0);
    if (price.value < last) {
      WebToApk.notify(JSON.stringify({ title: 'Price dropped', body: 'Now ' + price.value, tag: 'price' }));
    }
    localStorage.setItem('lastPrice', String(price.value));
    AppMint.background.done(run.runId, { ok: true });
  } catch (err) {
    AppMint.background.done(run.runId, { ok: false, retry: true });   // retried with backoff
  }
});
startApp();

الصفحات المركَّبة متأخرًا (React، مسار كسول) تستخدم onRun، الذي يُطلق أيضًا إذا بدأ التشغيل بالفعل، أو دالة الصفحة:

if (window.AppMint && AppMint.background) {
  AppMint.background.onRun(function (run) { syncInbox().then(function () { AppMint.background.done(run.runId, { ok: true }); }); });
}
window.onAppMintBackground = function (run) { console.log('background run', run.name, run.runId, AppMint.background.currentRun()); };

إدارة:

AppMint.background.list().then(function (tasks) { console.table(tasks); });
AppMint.background.runNow('prices');
AppMint.background.unregister('prices');

ملاحظات: يقرر Android اللحظة الدقيقة؛ و15 دقيقة هي أقصر فترة، وقد يؤخر توفير البطارية التشغيلات. مع البدء بعد إعادة تشغيل الهاتف تعمل كل مهمة أيضًا مرة واحدة بعد إعادة التشغيل. يحتوي WebToApk في التشغيل على notify وshowNotification ومخزن المواقع المؤقت والسياجات الجغرافية وworkEnqueueOnline وisBackgroundServiceRunning ودوال المهام هذه فقط؛ وكل ما عداه غير موجود (اكتشف الدعم).

addGeofence الجسر (bridge)#

window.WebToApk.addGeofence(json: String): String

يضيف سياجًا جغرافيًا {id, lat, lng, radiusM, events, dwellMs?, notify?} ← JSON {ok, status:'adding'} أو {ok:false, reason}؛ والنتيجة هي appmint:geofence-added.

مثال

دائرة تطلق enter وexit و/أو dwell والتطبيق مغلق، مع إمكانية نشر إشعار. تبقى السياجات الجغرافية بعد إعادة تشغيل الهاتف (تُضاف مجددًا تلقائيًا).

يعيد: addGeofence ← سلسلة JSON { ok: true, status: 'adding', id } أو { ok: false, reason } - not_enabled، invalid_id، invalid_position، invalid_radius (من 1 م إلى 100 كم)، invalid_events، location_permission_required، background_permission_required، foreign_origin. تصل إجابة خدمات Play كـ appmint:geofence-added (window.onAppMintGeofenceAdded) = { id, ok, reason? } (location_off، too_many_geofences - 100 لكل تطبيق، play_services_unavailable). ويصل الانتقال كـ appmint:geofence (window.onAppMintGeofence) = { id, event: 'enter' | 'exit' | 'dwell', time, lat, lng, queued? } - queued: true عندما حدث أثناء إغلاق التطبيق ويُسلَّم عند البدء التالي. removeGeofence ← true إذا كان موجودًا؛ getGeofences ← مصفوفة JSON بما أضفته. يتطلب: حدّد السياجات الجغرافية في البناء، و"السماح طوال الوقت" (requestBackgroundLocation()).

function watchOffice() {
  const b = window.WebToApk;
  if (!(b && b.addGeofence)) return;
  const r = JSON.parse(b.addGeofence(JSON.stringify({
    id: 'office', lat: 51.5033, lng: -0.1196, radiusM: 150,
    events: ['enter', 'exit', 'dwell'], dwellMs: 5 * 60000,
    notify: {
      enter: { title: 'At the office', body: 'Tap to check in' },
      exit: { title: 'Left the office', body: 'Tap to check out' }
    }
  })));
  if (!r.ok && r.reason === 'background_permission_required') b.requestBackgroundLocation();
}

window.addEventListener('appmint:geofence-added', function (e) {
  if (!e.detail.ok) showMessage('Geofence ' + e.detail.id + ' not added: ' + e.detail.reason);
});
window.addEventListener('appmint:geofence', function (e) {
  logVisit(e.detail.id, e.detail.event, e.detail.time);
});

console.log(JSON.parse(WebToApk.getGeofences()).map(function (g) { return g.id; }));
WebToApk.removeGeofence('office');

اكتشف الدعم وأبلغ عن كل رفض:

function addCircle(id, lat, lng, radiusM) {
  const b = window.WebToApk;
  if (!(b && b.addGeofence)) { showMessage('Place alerts need the app'); return false; }
  const r = JSON.parse(b.addGeofence(JSON.stringify({ id: id, lat: lat, lng: lng, radiusM: radiusM, events: ['enter'] })));
  if (r.ok) return true;
  if (r.reason === 'not_enabled') showMessage('Geofences are not switched on in this build');
  else if (r.reason === 'background_permission_required') b.requestBackgroundLocation();
  else if (r.reason === 'invalid_radius') showMessage('Pick a radius between 1 m and 100 km');
  else showMessage('Could not add the place: ' + r.reason);
  return false;
}

اعرض المسجَّل وأزل واحدًا:

const mine = JSON.parse(WebToApk.getGeofences());          // [] when none
mine.forEach(function (g) { addRow(g.id); });
document.getElementById('forget').onclick = function () {
  const existed = WebToApk.removeGeofence('office');       // true if it was there
  if (!existed) showMessage('That place was not registered');
};

backgroundDone الجسر (bridge)#

window.WebToApk.backgroundDone(runId: String, resultJson: String): Boolean

AppMint.background.done(runId, result): ينهي تشغيلًا في الخلفية؛ وهي دائمًا false داخل التطبيق نفسه.

مثال

موثَّق مع WebToApk.backgroundRegister - والمثال هناك يعرض هذا أيضًا.

الدوال الخام وراء AppMint.background - استخدم AppMint.background.register(...) (يعيد وعودًا ويعالج حدث التشغيل). مدرجة هنا ليكون لكل دالة في الجسر مثال.

يعيد: backgroundRegister ← JSON { ok: true, name, intervalMinutes } أو { ok: false, reason } (not_enabled، invalid_name - حروف وأرقام و_ و-، حتى 64 - interval_below_15_minutes، invalid_interval، schedule_failed). backgroundUnregister ← true إذا كان موجودًا. backgroundList ← مصفوفة JSON. backgroundRunNow ← JSON { ok, name } أو { ok: false, reason: 'not_registered' }. backgroundDone(runId, resultJson) ← true في تشغيل خلفية، دائمًا false في التطبيق. isBackgroundRun() ← false في التطبيق، true في تشغيل خلفية. يتطلب: حدّد تشغيل كود الصفحة أثناء الإغلاق.

const b = window.WebToApk;
if (b && b.backgroundRegister) {
  const r = JSON.parse(b.backgroundRegister('refresh', JSON.stringify({ intervalMinutes: 60, requiresNetwork: true })));
  if (!r.ok) console.warn('not scheduled:', r.reason);
  console.log(JSON.parse(b.backgroundList()));
  console.log(b.isBackgroundRun());               // false here: this is the app on screen
  // b.backgroundRunNow('refresh');  b.backgroundUnregister('refresh');
  // In a run: b.backgroundDone(runId, JSON.stringify({ ok: true }))
}

اكتشف الدعم وميّز تشغيل التطبيق من تشغيل الخلفية:

const b = window.WebToApk;
if (!(b && b.backgroundRegister)) {
  console.log('background tasks are only in the app');
} else if (b.isBackgroundRun()) {
  // the invisible copy: do the work, no UI
} else {
  const r = JSON.parse(b.backgroundRegister('sync', JSON.stringify({ intervalMinutes: 5 })));
  if (!r.ok) console.warn(r.reason);                // 'interval_below_15_minutes'
}

اختبر مهمة ونظّف:

const run = JSON.parse(WebToApk.backgroundRunNow('sync'));
if (!run.ok && run.reason === 'not_registered') WebToApk.backgroundRegister('sync', JSON.stringify({ intervalMinutes: 30 }));
JSON.parse(WebToApk.backgroundList()).forEach(function (t) { console.log(t.name, t.intervalMinutes, t.lastResult); });
WebToApk.backgroundUnregister('old-task');           // true if it existed

backgroundList الجسر (bridge)#

window.WebToApk.backgroundList(): String

AppMint.background.list(): مصفوفة JSON {name, intervalMinutes, requiresNetwork, requiresCharging, registeredAt, lastRunAt, lastResult}.

مثال

موثَّق مع WebToApk.backgroundRegister - والمثال هناك يعرض هذا أيضًا.

الدوال الخام وراء AppMint.background - استخدم AppMint.background.register(...) (يعيد وعودًا ويعالج حدث التشغيل). مدرجة هنا ليكون لكل دالة في الجسر مثال.

يعيد: backgroundRegister ← JSON { ok: true, name, intervalMinutes } أو { ok: false, reason } (not_enabled، invalid_name - حروف وأرقام و_ و-، حتى 64 - interval_below_15_minutes، invalid_interval، schedule_failed). backgroundUnregister ← true إذا كان موجودًا. backgroundList ← مصفوفة JSON. backgroundRunNow ← JSON { ok, name } أو { ok: false, reason: 'not_registered' }. backgroundDone(runId, resultJson) ← true في تشغيل خلفية، دائمًا false في التطبيق. isBackgroundRun() ← false في التطبيق، true في تشغيل خلفية. يتطلب: حدّد تشغيل كود الصفحة أثناء الإغلاق.

const b = window.WebToApk;
if (b && b.backgroundRegister) {
  const r = JSON.parse(b.backgroundRegister('refresh', JSON.stringify({ intervalMinutes: 60, requiresNetwork: true })));
  if (!r.ok) console.warn('not scheduled:', r.reason);
  console.log(JSON.parse(b.backgroundList()));
  console.log(b.isBackgroundRun());               // false here: this is the app on screen
  // b.backgroundRunNow('refresh');  b.backgroundUnregister('refresh');
  // In a run: b.backgroundDone(runId, JSON.stringify({ ok: true }))
}

اكتشف الدعم وميّز تشغيل التطبيق من تشغيل الخلفية:

const b = window.WebToApk;
if (!(b && b.backgroundRegister)) {
  console.log('background tasks are only in the app');
} else if (b.isBackgroundRun()) {
  // the invisible copy: do the work, no UI
} else {
  const r = JSON.parse(b.backgroundRegister('sync', JSON.stringify({ intervalMinutes: 5 })));
  if (!r.ok) console.warn(r.reason);                // 'interval_below_15_minutes'
}

اختبر مهمة ونظّف:

const run = JSON.parse(WebToApk.backgroundRunNow('sync'));
if (!run.ok && run.reason === 'not_registered') WebToApk.backgroundRegister('sync', JSON.stringify({ intervalMinutes: 30 }));
JSON.parse(WebToApk.backgroundList()).forEach(function (t) { console.log(t.name, t.intervalMinutes, t.lastResult); });
WebToApk.backgroundUnregister('old-task');           // true if it existed

backgroundRegister الجسر (bridge)#

window.WebToApk.backgroundRegister(name: String, optionsJson: String): String

AppMint.background.register(name, {intervalMinutes >= 15, requiresNetwork, requiresCharging}) ← JSON {ok, name, intervalMinutes} أو {ok:false, reason}.

مثال

الدوال الخام وراء AppMint.background - استخدم AppMint.background.register(...) (يعيد وعودًا ويعالج حدث التشغيل). مدرجة هنا ليكون لكل دالة في الجسر مثال.

يعيد: backgroundRegister ← JSON { ok: true, name, intervalMinutes } أو { ok: false, reason } (not_enabled، invalid_name - حروف وأرقام و_ و-، حتى 64 - interval_below_15_minutes، invalid_interval، schedule_failed). backgroundUnregister ← true إذا كان موجودًا. backgroundList ← مصفوفة JSON. backgroundRunNow ← JSON { ok, name } أو { ok: false, reason: 'not_registered' }. backgroundDone(runId, resultJson) ← true في تشغيل خلفية، دائمًا false في التطبيق. isBackgroundRun() ← false في التطبيق، true في تشغيل خلفية. يتطلب: حدّد تشغيل كود الصفحة أثناء الإغلاق.

const b = window.WebToApk;
if (b && b.backgroundRegister) {
  const r = JSON.parse(b.backgroundRegister('refresh', JSON.stringify({ intervalMinutes: 60, requiresNetwork: true })));
  if (!r.ok) console.warn('not scheduled:', r.reason);
  console.log(JSON.parse(b.backgroundList()));
  console.log(b.isBackgroundRun());               // false here: this is the app on screen
  // b.backgroundRunNow('refresh');  b.backgroundUnregister('refresh');
  // In a run: b.backgroundDone(runId, JSON.stringify({ ok: true }))
}

اكتشف الدعم وميّز تشغيل التطبيق من تشغيل الخلفية:

const b = window.WebToApk;
if (!(b && b.backgroundRegister)) {
  console.log('background tasks are only in the app');
} else if (b.isBackgroundRun()) {
  // the invisible copy: do the work, no UI
} else {
  const r = JSON.parse(b.backgroundRegister('sync', JSON.stringify({ intervalMinutes: 5 })));
  if (!r.ok) console.warn(r.reason);                // 'interval_below_15_minutes'
}

اختبر مهمة ونظّف:

const run = JSON.parse(WebToApk.backgroundRunNow('sync'));
if (!run.ok && run.reason === 'not_registered') WebToApk.backgroundRegister('sync', JSON.stringify({ intervalMinutes: 30 }));
JSON.parse(WebToApk.backgroundList()).forEach(function (t) { console.log(t.name, t.intervalMinutes, t.lastResult); });
WebToApk.backgroundUnregister('old-task');           // true if it existed

backgroundRunNow الجسر (bridge)#

window.WebToApk.backgroundRunNow(name: String): String

AppMint.background.runNow(name): تشغيل واحد في الخلفية بمجرد أن تسمح شروطه.

مثال

موثَّق مع WebToApk.backgroundRegister - والمثال هناك يعرض هذا أيضًا.

الدوال الخام وراء AppMint.background - استخدم AppMint.background.register(...) (يعيد وعودًا ويعالج حدث التشغيل). مدرجة هنا ليكون لكل دالة في الجسر مثال.

يعيد: backgroundRegister ← JSON { ok: true, name, intervalMinutes } أو { ok: false, reason } (not_enabled، invalid_name - حروف وأرقام و_ و-، حتى 64 - interval_below_15_minutes، invalid_interval، schedule_failed). backgroundUnregister ← true إذا كان موجودًا. backgroundList ← مصفوفة JSON. backgroundRunNow ← JSON { ok, name } أو { ok: false, reason: 'not_registered' }. backgroundDone(runId, resultJson) ← true في تشغيل خلفية، دائمًا false في التطبيق. isBackgroundRun() ← false في التطبيق، true في تشغيل خلفية. يتطلب: حدّد تشغيل كود الصفحة أثناء الإغلاق.

const b = window.WebToApk;
if (b && b.backgroundRegister) {
  const r = JSON.parse(b.backgroundRegister('refresh', JSON.stringify({ intervalMinutes: 60, requiresNetwork: true })));
  if (!r.ok) console.warn('not scheduled:', r.reason);
  console.log(JSON.parse(b.backgroundList()));
  console.log(b.isBackgroundRun());               // false here: this is the app on screen
  // b.backgroundRunNow('refresh');  b.backgroundUnregister('refresh');
  // In a run: b.backgroundDone(runId, JSON.stringify({ ok: true }))
}

اكتشف الدعم وميّز تشغيل التطبيق من تشغيل الخلفية:

const b = window.WebToApk;
if (!(b && b.backgroundRegister)) {
  console.log('background tasks are only in the app');
} else if (b.isBackgroundRun()) {
  // the invisible copy: do the work, no UI
} else {
  const r = JSON.parse(b.backgroundRegister('sync', JSON.stringify({ intervalMinutes: 5 })));
  if (!r.ok) console.warn(r.reason);                // 'interval_below_15_minutes'
}

اختبر مهمة ونظّف:

const run = JSON.parse(WebToApk.backgroundRunNow('sync'));
if (!run.ok && run.reason === 'not_registered') WebToApk.backgroundRegister('sync', JSON.stringify({ intervalMinutes: 30 }));
JSON.parse(WebToApk.backgroundList()).forEach(function (t) { console.log(t.name, t.intervalMinutes, t.lastResult); });
WebToApk.backgroundUnregister('old-task');           // true if it existed

backgroundUnregister الجسر (bridge)#

window.WebToApk.backgroundUnregister(name: String): Boolean

AppMint.background.unregister(name)؛ وfalse عندما لا توجد مهمة بهذا الاسم.

مثال

موثَّق مع WebToApk.backgroundRegister - والمثال هناك يعرض هذا أيضًا.

الدوال الخام وراء AppMint.background - استخدم AppMint.background.register(...) (يعيد وعودًا ويعالج حدث التشغيل). مدرجة هنا ليكون لكل دالة في الجسر مثال.

يعيد: backgroundRegister ← JSON { ok: true, name, intervalMinutes } أو { ok: false, reason } (not_enabled، invalid_name - حروف وأرقام و_ و-، حتى 64 - interval_below_15_minutes، invalid_interval، schedule_failed). backgroundUnregister ← true إذا كان موجودًا. backgroundList ← مصفوفة JSON. backgroundRunNow ← JSON { ok, name } أو { ok: false, reason: 'not_registered' }. backgroundDone(runId, resultJson) ← true في تشغيل خلفية، دائمًا false في التطبيق. isBackgroundRun() ← false في التطبيق، true في تشغيل خلفية. يتطلب: حدّد تشغيل كود الصفحة أثناء الإغلاق.

const b = window.WebToApk;
if (b && b.backgroundRegister) {
  const r = JSON.parse(b.backgroundRegister('refresh', JSON.stringify({ intervalMinutes: 60, requiresNetwork: true })));
  if (!r.ok) console.warn('not scheduled:', r.reason);
  console.log(JSON.parse(b.backgroundList()));
  console.log(b.isBackgroundRun());               // false here: this is the app on screen
  // b.backgroundRunNow('refresh');  b.backgroundUnregister('refresh');
  // In a run: b.backgroundDone(runId, JSON.stringify({ ok: true }))
}

اكتشف الدعم وميّز تشغيل التطبيق من تشغيل الخلفية:

const b = window.WebToApk;
if (!(b && b.backgroundRegister)) {
  console.log('background tasks are only in the app');
} else if (b.isBackgroundRun()) {
  // the invisible copy: do the work, no UI
} else {
  const r = JSON.parse(b.backgroundRegister('sync', JSON.stringify({ intervalMinutes: 5 })));
  if (!r.ok) console.warn(r.reason);                // 'interval_below_15_minutes'
}

اختبر مهمة ونظّف:

const run = JSON.parse(WebToApk.backgroundRunNow('sync'));
if (!run.ok && run.reason === 'not_registered') WebToApk.backgroundRegister('sync', JSON.stringify({ intervalMinutes: 30 }));
JSON.parse(WebToApk.backgroundList()).forEach(function (t) { console.log(t.name, t.intervalMinutes, t.lastResult); });
WebToApk.backgroundUnregister('old-task');           // true if it existed

clearBufferedLocations الجسر (bridge)#

window.WebToApk.clearBufferedLocations(): Boolean

يفرغ المخزن المؤقت الذي يقرأه getBufferedLocations (استدعه بعد حفظ النقاط).

مثال

موثَّق مع WebToApk.getBufferedLocations - والمثال هناك يعرض هذا أيضًا.

المواقع المسجَّلة أثناء غياب صفحتك (التطبيق في الخلفية أو مغلق)، الأقدم أولًا، حتى 10,000.

يعيد: getBufferedLocations() ← مصفوفة سلاسل JSON من { type: 'fix', lat, lng, accuracy, time, …, background: true }؛ '[]' عندما يكون الموقع في الخلفية متوقفًا. clearBufferedLocations() ← true عند إفراغه. يتطلب: الموقع في الخلفية في البناء.

function syncTrack() {
  if (!(window.WebToApk && WebToApk.getBufferedLocations)) return;
  const points = JSON.parse(WebToApk.getBufferedLocations());
  if (!points.length) return;
  points.forEach(function (p) { drawPoint(p.lat, p.lng); });
  saveTrack(points).then(function () { WebToApk.clearBufferedLocations(); });
}
document.addEventListener('visibilitychange', function () { if (!document.hidden) syncTrack(); });

ابدأ التتبع، ثم عالج الرفض. يجيب استدعاء البدء بسلسلة JSON.

function startTracking() {
  if (!(window.WebToApk && WebToApk.startLocationUpdates)) return;
  const r = JSON.parse(WebToApk.startLocationUpdates(JSON.stringify({
    intervalMs: 15000, accuracy: 'balanced', background: true
  })));
  if (!r.ok) showMessage('Tracking not started: ' + r.reason);   // not_enabled, location_permission_required, ...
}

التحديثات المباشرة أثناء فتح الصفحة تصل كـ appmint:location؛ ويحتفظ المخزن المؤقت بالباقي.

window.addEventListener('appmint:location', function (e) {
  drawPoint(e.detail.lat, e.detail.lng);
});
document.getElementById('stop').addEventListener('click', function () {
  if (window.WebToApk && WebToApk.stopLocationUpdates) WebToApk.stopLocationUpdates();
  if (WebToApk.getBufferedLocations) {
    const left = JSON.parse(WebToApk.getBufferedLocations());
    console.log(left.length + ' fixes still buffered');
  }
});

clearGeofenceEvents الجسر (bridge)#

window.WebToApk.clearGeofenceEvents(): Boolean

يفرغ سجل الانتقالات الذي يقرأه getGeofenceEvents.

مثال

موثَّق مع WebToApk.getGeofenceEvents - والمثال هناك يعرض هذا أيضًا.

آخر 100 انتقال للسياج الجغرافي، الأقدم أولًا - بما فيها التي حدثت أثناء إغلاق التطبيق. اقرأه عند البدء إذا كانت صفحتك تُركَّب متأخرة وقد تفوتها أحداث appmint:geofence المصطفّة.

يعيد: getGeofenceEvents() ← مصفوفة سلاسل JSON من { id, event, time, lat, lng }؛ clearGeofenceEvents() ← true. يتطلب: السياجات الجغرافية في البناء.

if (window.WebToApk && WebToApk.getGeofenceEvents) {
  JSON.parse(WebToApk.getGeofenceEvents()).forEach(function (ev) {
    logVisit(ev.id, ev.event, ev.time);
  });
  WebToApk.clearGeofenceEvents();
}

أضف سياجًا جغرافيًا، ثم راقب الانتقالات أثناء فتح الصفحة.

if (window.WebToApk && WebToApk.addGeofence) {
  const r = JSON.parse(WebToApk.addGeofence(JSON.stringify({
    id: 'shop-1', lat: 12.9716, lng: 77.5946, radiusM: 150, events: ['enter', 'exit']
  })));
  if (!r.ok) showMessage('Geofence refused: ' + r.reason);   // not_enabled, invalid_radius, ...
}
window.addEventListener('appmint:geofence', function (e) {
  logVisit(e.detail.id, e.detail.event, e.detail.time);
});

اعرض المسجَّل وأزل واحدًا عندما يغادر المستخدم ميزة.

function dropGeofence(id) {
  if (!(window.WebToApk && WebToApk.removeGeofence)) return false;
  const known = JSON.parse(WebToApk.getGeofences()).length;
  const removed = WebToApk.removeGeofence(id);
  console.log(removed ? 'removed' : 'not found', 'of', known, 'registered');
  return removed;
}

getBufferedLocations الجسر (bridge)#

window.WebToApk.getBufferedLocations(): String

مصفوفة JSON للمواقع المسجَّلة أثناء غياب الصفحة، الأقدم أولًا (حتى 10,000).

مثال

المواقع المسجَّلة أثناء غياب صفحتك (التطبيق في الخلفية أو مغلق)، الأقدم أولًا، حتى 10,000.

يعيد: getBufferedLocations() ← مصفوفة سلاسل JSON من { type: 'fix', lat, lng, accuracy, time, …, background: true }؛ '[]' عندما يكون الموقع في الخلفية متوقفًا. clearBufferedLocations() ← true عند إفراغه. يتطلب: الموقع في الخلفية في البناء.

function syncTrack() {
  if (!(window.WebToApk && WebToApk.getBufferedLocations)) return;
  const points = JSON.parse(WebToApk.getBufferedLocations());
  if (!points.length) return;
  points.forEach(function (p) { drawPoint(p.lat, p.lng); });
  saveTrack(points).then(function () { WebToApk.clearBufferedLocations(); });
}
document.addEventListener('visibilitychange', function () { if (!document.hidden) syncTrack(); });

ابدأ التتبع، ثم عالج الرفض. يجيب استدعاء البدء بسلسلة JSON.

function startTracking() {
  if (!(window.WebToApk && WebToApk.startLocationUpdates)) return;
  const r = JSON.parse(WebToApk.startLocationUpdates(JSON.stringify({
    intervalMs: 15000, accuracy: 'balanced', background: true
  })));
  if (!r.ok) showMessage('Tracking not started: ' + r.reason);   // not_enabled, location_permission_required, ...
}

التحديثات المباشرة أثناء فتح الصفحة تصل كـ appmint:location؛ ويحتفظ المخزن المؤقت بالباقي.

window.addEventListener('appmint:location', function (e) {
  drawPoint(e.detail.lat, e.detail.lng);
});
document.getElementById('stop').addEventListener('click', function () {
  if (window.WebToApk && WebToApk.stopLocationUpdates) WebToApk.stopLocationUpdates();
  if (WebToApk.getBufferedLocations) {
    const left = JSON.parse(WebToApk.getBufferedLocations());
    console.log(left.length + ' fixes still buffered');
  }
});

getGeofenceEvents الجسر (bridge)#

window.WebToApk.getGeofenceEvents(): String

مصفوفة JSON لآخر 100 انتقال للسياج الجغرافي {id, event, time, lat, lng}، الأقدم أولًا.

مثال

آخر 100 انتقال للسياج الجغرافي، الأقدم أولًا - بما فيها التي حدثت أثناء إغلاق التطبيق. اقرأه عند البدء إذا كانت صفحتك تُركَّب متأخرة وقد تفوتها أحداث appmint:geofence المصطفّة.

يعيد: getGeofenceEvents() ← مصفوفة سلاسل JSON من { id, event, time, lat, lng }؛ clearGeofenceEvents() ← true. يتطلب: السياجات الجغرافية في البناء.

if (window.WebToApk && WebToApk.getGeofenceEvents) {
  JSON.parse(WebToApk.getGeofenceEvents()).forEach(function (ev) {
    logVisit(ev.id, ev.event, ev.time);
  });
  WebToApk.clearGeofenceEvents();
}

أضف سياجًا جغرافيًا، ثم راقب الانتقالات أثناء فتح الصفحة.

if (window.WebToApk && WebToApk.addGeofence) {
  const r = JSON.parse(WebToApk.addGeofence(JSON.stringify({
    id: 'shop-1', lat: 12.9716, lng: 77.5946, radiusM: 150, events: ['enter', 'exit']
  })));
  if (!r.ok) showMessage('Geofence refused: ' + r.reason);   // not_enabled, invalid_radius, ...
}
window.addEventListener('appmint:geofence', function (e) {
  logVisit(e.detail.id, e.detail.event, e.detail.time);
});

اعرض المسجَّل وأزل واحدًا عندما يغادر المستخدم ميزة.

function dropGeofence(id) {
  if (!(window.WebToApk && WebToApk.removeGeofence)) return false;
  const known = JSON.parse(WebToApk.getGeofences()).length;
  const removed = WebToApk.removeGeofence(id);
  console.log(removed ? 'removed' : 'not found', 'of', known, 'registered');
  return removed;
}

getGeofences الجسر (bridge)#

window.WebToApk.getGeofences(): String

مصفوفة JSON للسياجات الجغرافية التي أضافها هذا التطبيق (تبقى بعد إعادة التشغيل).

مثال

موثَّق مع WebToApk.addGeofence - والمثال هناك يعرض هذا أيضًا.

دائرة تطلق enter وexit و/أو dwell والتطبيق مغلق، مع إمكانية نشر إشعار. تبقى السياجات الجغرافية بعد إعادة تشغيل الهاتف (تُضاف مجددًا تلقائيًا).

يعيد: addGeofence ← سلسلة JSON { ok: true, status: 'adding', id } أو { ok: false, reason } - not_enabled، invalid_id، invalid_position، invalid_radius (من 1 م إلى 100 كم)، invalid_events، location_permission_required، background_permission_required، foreign_origin. تصل إجابة خدمات Play كـ appmint:geofence-added (window.onAppMintGeofenceAdded) = { id, ok, reason? } (location_off، too_many_geofences - 100 لكل تطبيق، play_services_unavailable). ويصل الانتقال كـ appmint:geofence (window.onAppMintGeofence) = { id, event: 'enter' | 'exit' | 'dwell', time, lat, lng, queued? } - queued: true عندما حدث أثناء إغلاق التطبيق ويُسلَّم عند البدء التالي. removeGeofence ← true إذا كان موجودًا؛ getGeofences ← مصفوفة JSON بما أضفته. يتطلب: حدّد السياجات الجغرافية في البناء، و"السماح طوال الوقت" (requestBackgroundLocation()).

function watchOffice() {
  const b = window.WebToApk;
  if (!(b && b.addGeofence)) return;
  const r = JSON.parse(b.addGeofence(JSON.stringify({
    id: 'office', lat: 51.5033, lng: -0.1196, radiusM: 150,
    events: ['enter', 'exit', 'dwell'], dwellMs: 5 * 60000,
    notify: {
      enter: { title: 'At the office', body: 'Tap to check in' },
      exit: { title: 'Left the office', body: 'Tap to check out' }
    }
  })));
  if (!r.ok && r.reason === 'background_permission_required') b.requestBackgroundLocation();
}

window.addEventListener('appmint:geofence-added', function (e) {
  if (!e.detail.ok) showMessage('Geofence ' + e.detail.id + ' not added: ' + e.detail.reason);
});
window.addEventListener('appmint:geofence', function (e) {
  logVisit(e.detail.id, e.detail.event, e.detail.time);
});

console.log(JSON.parse(WebToApk.getGeofences()).map(function (g) { return g.id; }));
WebToApk.removeGeofence('office');

اكتشف الدعم وأبلغ عن كل رفض:

function addCircle(id, lat, lng, radiusM) {
  const b = window.WebToApk;
  if (!(b && b.addGeofence)) { showMessage('Place alerts need the app'); return false; }
  const r = JSON.parse(b.addGeofence(JSON.stringify({ id: id, lat: lat, lng: lng, radiusM: radiusM, events: ['enter'] })));
  if (r.ok) return true;
  if (r.reason === 'not_enabled') showMessage('Geofences are not switched on in this build');
  else if (r.reason === 'background_permission_required') b.requestBackgroundLocation();
  else if (r.reason === 'invalid_radius') showMessage('Pick a radius between 1 m and 100 km');
  else showMessage('Could not add the place: ' + r.reason);
  return false;
}

اعرض المسجَّل وأزل واحدًا:

const mine = JSON.parse(WebToApk.getGeofences());          // [] when none
mine.forEach(function (g) { addRow(g.id); });
document.getElementById('forget').onclick = function () {
  const existed = WebToApk.removeGeofence('office');       // true if it was there
  if (!existed) showMessage('That place was not registered');
};

getLocationPermissionState الجسر (bridge)#

window.WebToApk.getLocationPermissionState(): String

JSON {foreground:'granted'|'denied', background:'granted'|'denied', precise: boolean}.

مثال

موثَّق مع WebToApk.requestBackgroundLocation - والمثال هناك يعرض هذا أيضًا.

يطلب الموقع، ثم "السماح طوال الوقت" (يطلبهما Android 10+ منفصلين؛ وفي Android 11+ تفتح الخطوة الثانية صفحة إعدادات موقع التطبيق). تحتاجه السياجات الجغرافية؛ وكذلك إعادة تشغيل تتبع الموقع بعد إعادة تشغيل الهاتف.

يعيد: لا شيء مباشرة؛ وتصل الإجابة كـ appmint:location-permission (وwindow.onAppMintLocationPermission(detail)) = { foreground: 'granted' | 'denied', background: 'granted' | 'denied', precise: boolean, error?: 'not_enabled' }. getLocationPermissionState() ← الكائن نفسه كسلسلة JSON، دون طرح سؤال. يتطلب: الموقع في الخلفية أو السياجات الجغرافية في البناء.

function askAlways() {
  const b = window.WebToApk;
  if (!(b && b.requestBackgroundLocation)) return;
  const now = JSON.parse(b.getLocationPermissionState());
  if (now.background === 'granted') { enableGeofences(); return; }
  // Explain first: Play requires an in-app disclosure before this request.
  if (!confirm('Allow location "all the time" so we can remind you when you arrive?')) return;
  b.requestBackgroundLocation();
}

window.addEventListener('appmint:location-permission', function (e) {
  if (e.detail.background === 'granted') enableGeofences();
  else showMessage('Choose "Allow all the time" in the location settings to use arrival reminders.');
});

تحقق من الحالة أولًا واشرح ما على المستخدم النقر عليه:

const b = window.WebToApk;
if (b && b.getLocationPermissionState) {
  const s = JSON.parse(b.getLocationPermissionState());
  if (s.foreground !== 'granted') showMessage('Location is off for this app.');
  else if (s.background !== 'granted') showMessage('Tap "Allow all the time" to enable arrival reminders.');
  else if (!s.precise) showMessage('Precise location is off; arrival alerts may be late.');
}

اطلب الإذن، ثم أضف سياجًا جغرافيًا بعد منحه:

window.addEventListener('appmint:location-permission', function (e) {
  if (e.detail.error === 'not_enabled') return showMessage('This build has no background location.');
  if (e.detail.background !== 'granted') return;
  const r = JSON.parse(WebToApk.addGeofence(JSON.stringify({
    id: 'home', lat: 51.5033, lng: -0.1196, radiusM: 120, events: ['enter']
  })));
  if (!r.ok) showMessage('Could not add the place: ' + r.reason);
}, { once: true });
WebToApk.requestBackgroundLocation();

isBackgroundRun الجسر (bridge)#

window.WebToApk.isBackgroundRun(): Boolean

false هنا: صفحة التطبيق نفسه ليست أبدًا تشغيلًا في الخلفية (التشغيل بلا واجهة يجيب true).

مثال

موثَّق مع WebToApk.backgroundRegister - والمثال هناك يعرض هذا أيضًا.

الدوال الخام وراء AppMint.background - استخدم AppMint.background.register(...) (يعيد وعودًا ويعالج حدث التشغيل). مدرجة هنا ليكون لكل دالة في الجسر مثال.

يعيد: backgroundRegister ← JSON { ok: true, name, intervalMinutes } أو { ok: false, reason } (not_enabled، invalid_name - حروف وأرقام و_ و-، حتى 64 - interval_below_15_minutes، invalid_interval، schedule_failed). backgroundUnregister ← true إذا كان موجودًا. backgroundList ← مصفوفة JSON. backgroundRunNow ← JSON { ok, name } أو { ok: false, reason: 'not_registered' }. backgroundDone(runId, resultJson) ← true في تشغيل خلفية، دائمًا false في التطبيق. isBackgroundRun() ← false في التطبيق، true في تشغيل خلفية. يتطلب: حدّد تشغيل كود الصفحة أثناء الإغلاق.

const b = window.WebToApk;
if (b && b.backgroundRegister) {
  const r = JSON.parse(b.backgroundRegister('refresh', JSON.stringify({ intervalMinutes: 60, requiresNetwork: true })));
  if (!r.ok) console.warn('not scheduled:', r.reason);
  console.log(JSON.parse(b.backgroundList()));
  console.log(b.isBackgroundRun());               // false here: this is the app on screen
  // b.backgroundRunNow('refresh');  b.backgroundUnregister('refresh');
  // In a run: b.backgroundDone(runId, JSON.stringify({ ok: true }))
}

اكتشف الدعم وميّز تشغيل التطبيق من تشغيل الخلفية:

const b = window.WebToApk;
if (!(b && b.backgroundRegister)) {
  console.log('background tasks are only in the app');
} else if (b.isBackgroundRun()) {
  // the invisible copy: do the work, no UI
} else {
  const r = JSON.parse(b.backgroundRegister('sync', JSON.stringify({ intervalMinutes: 5 })));
  if (!r.ok) console.warn(r.reason);                // 'interval_below_15_minutes'
}

اختبر مهمة ونظّف:

const run = JSON.parse(WebToApk.backgroundRunNow('sync'));
if (!run.ok && run.reason === 'not_registered') WebToApk.backgroundRegister('sync', JSON.stringify({ intervalMinutes: 30 }));
JSON.parse(WebToApk.backgroundList()).forEach(function (t) { console.log(t.name, t.intervalMinutes, t.lastResult); });
WebToApk.backgroundUnregister('old-task');           // true if it existed

isBackgroundServiceRunning الجسر (bridge)#

window.WebToApk.isBackgroundServiceRunning(): Boolean

ما إذا كانت خدمة التطبيق في المقدمة تعمل (للاستمرار في العمل أو للموقع في الخلفية).

مثال

موثَّق مع WebToApk.startBackgroundService - والمثال هناك يعرض هذا أيضًا.

يبقي تطبيقك يعمل أثناء غياب المستخدم: خدمة Android حقيقية في المقدمة مع إشعار. أثناء عملها لا تتوقف الصفحة، فيستمر JavaScript الخاص بها و<audio> وموسيقى التطبيق في الخلفية (ما زال Android يبطئ مؤقتات الصفحة التي لا يراها أحد).

يعيد: startBackgroundService ← سلسلة JSON { ok: true, status: 'starting', type, notificationShown } أو { ok: false, reason } - السبب not_enabled، invalid_options، type_not_declared، invalid_progress، location_permission_required، start_not_allowed. updateBackgroundService ← { ok: true } أو { ok: false, reason: 'not_running' }. stopBackgroundService / isBackgroundServiceRunning ← true / false. تصل تغييرات الحالة كـ appmint:background-service (وwindow.onAppMintBackgroundService(detail)): { state: 'running' | 'stopped' | 'failed' | 'not_restarted', reason?, types?, restored?, queued? }. يتطلب: حدّد الاستمرار في العمل في الخلفية (خطوة الوصول ← الخلفية) واختر نوع الخدمة. في Android 13+ لا يظهر الإشعار إلا بعد سماح المستخدم بالإشعارات (notificationShown: false وإلا - وتظل الخدمة تعمل).

function startWorkout() {
  const b = window.WebToApk;
  if (!(b && b.startBackgroundService)) return;             // browser
  const r = JSON.parse(b.startBackgroundService(JSON.stringify({
    title: 'Workout in progress',
    text: '00:00 elapsed',
    stopLabel: 'Stop',                  // a Stop button on the notification
    progress: { value: 0, max: 100 }
  })));
  if (!r.ok) showMessage('Could not keep running: ' + r.reason);
}

function tick(seconds) {
  if (window.WebToApk && WebToApk.isBackgroundServiceRunning()) {
    WebToApk.updateBackgroundService(JSON.stringify({
      title: 'Workout in progress', text: seconds + ' s elapsed', stopLabel: 'Stop',
      progress: { value: Math.min(100, seconds / 18), max: 100 }
    }));
  }
}

function finishWorkout() {
  if (window.WebToApk && WebToApk.stopBackgroundService) WebToApk.stopBackgroundService();
}

window.addEventListener('appmint:background-service', function (e) {
  // reason: 'user' (Stop button), 'page', 'timeout' (Android 15 dataSync 6-hour limit),
  // 'task_removed' (app swiped away); after a phone restart, state 'not_restarted' with
  // 'android_forbids_type_at_boot' | 'background_permission_required' | 'start_not_allowed'
  if (e.detail.state === 'stopped') showMessage('Stopped (' + e.detail.reason + ')');
});

عالج الإجابات التي تعني أن البناء أو الهاتف لا يستطيع القيام بذلك:

function startKeepAlive() {
  const b = window.WebToApk;
  if (!(b && typeof b.startBackgroundService === 'function')) return;
  const r = JSON.parse(b.startBackgroundService(JSON.stringify({ title: 'Syncing', text: 'Uploading photos' })));
  if (r.ok) return;
  if (r.reason === 'not_enabled') showMessage('Keep running in background is not on in this build.');
  else if (r.reason === 'type_not_declared') showMessage('This build declares no service type.');
  else showMessage('Could not start: ' + r.reason);
}

اجمعه مع حلقة رفع: استمر في العمل فقط ما دام في الطابور عمل.

async function uploadAll(files) {
  const b = window.WebToApk;
  const keep = b && b.startBackgroundService &&
    JSON.parse(b.startBackgroundService(JSON.stringify({ title: 'Uploading', text: '0 of ' + files.length }))).ok;
  for (let i = 0; i < files.length; i++) {
    await upload(files[i]);
    if (keep) b.updateBackgroundService(JSON.stringify({ title: 'Uploading', text: (i + 1) + ' of ' + files.length }));
  }
  if (keep) b.stopBackgroundService();
}

ملاحظات: يجب أن يكون النوع واحدًا أعلنه البناء (الذي اختير في المعالج، بالإضافة إلى location عند تشغيل الموقع في الخلفية). سحب التطبيق بعيدًا يوقف خدمة الاستمرار ما لم تمرّر keepAfterSwipe: true (الصفحة نفسها تكون قد زالت حينها). تطلب Play Console إعلانًا لخدمة المقدمة مع فيديو قصير لكل نوع معلن.

isIgnoringBatteryOptimizations الجسر (bridge)#

window.WebToApk.isIgnoringBatteryOptimizations(): Boolean

ما إذا كان المستخدم قد استثنى هذا التطبيق من تحسين البطارية (وضع الانتظار Doze).

مثال

يفحص ويطلب استثناء تحسين البطارية، حتى لا يؤجّل Doze عملك في الخلفية.

يعيد: isIgnoringBatteryOptimizations() ← true / false. requestIgnoreBatteryOptimizations() ← 'already_ignoring'، 'dialog_opened' (مربع حوار النظام سماح/رفض - فقط عندما حدّد البناء اطلب تخطي تحسين البطارية مباشرة)، 'settings_opened' (قائمة تحسين البطارية؛ بلا إذن ولا إعلان في Play) أو 'unavailable'. يتطلب: لا شيء لقائمة الإعدادات. يعلن مربع الحوار المباشر REQUEST_IGNORE_BATTERY_OPTIMIZATIONS، وتقبله Play فقط عندما يكون الوظيفة الأساسية للتطبيق.

document.getElementById('reliable').addEventListener('click', function () {
  const b = window.WebToApk;
  if (!(b && b.isIgnoringBatteryOptimizations)) return;
  if (b.isIgnoringBatteryOptimizations()) { showMessage('Already unrestricted'); return; }
  const how = b.requestIgnoreBatteryOptimizations();
  if (how === 'settings_opened') showMessage('Find this app in the list and choose "Not optimised".');
});

اذكر ما حدث لكل إجابة.

function askBatteryExemption() {
  const b = window.WebToApk;
  if (!(b && b.requestIgnoreBatteryOptimizations)) { showMessage('Only available in the app'); return; }
  const how = b.requestIgnoreBatteryOptimizations();
  const text = {
    already_ignoring: 'Already unrestricted',
    dialog_opened: 'Please tap Allow',
    settings_opened: 'Choose "Not optimised" for this app',
    unavailable: 'This phone has no such setting'
  };
  showMessage(text[how] || how);
}

اقرنه بالتتبع في الخلفية، لأن Doze قد يوقفه.

if (window.WebToApk && WebToApk.startLocationUpdates) {
  const r = JSON.parse(WebToApk.startLocationUpdates(JSON.stringify({ intervalMs: 30000 })));
  if (r.ok && !WebToApk.isIgnoringBatteryOptimizations()) {
    showMessage('For steady tracking, turn off battery optimisation for this app.');
  }
}

removeGeofence الجسر (bridge)#

window.WebToApk.removeGeofence(id: String): Boolean

يزيل السياج الجغرافي بهذا المعرّف؛ وfalse عندما لم يكن هناك أي سياج.

مثال

موثَّق مع WebToApk.addGeofence - والمثال هناك يعرض هذا أيضًا.

دائرة تطلق enter وexit و/أو dwell والتطبيق مغلق، مع إمكانية نشر إشعار. تبقى السياجات الجغرافية بعد إعادة تشغيل الهاتف (تُضاف مجددًا تلقائيًا).

يعيد: addGeofence ← سلسلة JSON { ok: true, status: 'adding', id } أو { ok: false, reason } - not_enabled، invalid_id، invalid_position، invalid_radius (من 1 م إلى 100 كم)، invalid_events، location_permission_required، background_permission_required، foreign_origin. تصل إجابة خدمات Play كـ appmint:geofence-added (window.onAppMintGeofenceAdded) = { id, ok, reason? } (location_off، too_many_geofences - 100 لكل تطبيق، play_services_unavailable). ويصل الانتقال كـ appmint:geofence (window.onAppMintGeofence) = { id, event: 'enter' | 'exit' | 'dwell', time, lat, lng, queued? } - queued: true عندما حدث أثناء إغلاق التطبيق ويُسلَّم عند البدء التالي. removeGeofence ← true إذا كان موجودًا؛ getGeofences ← مصفوفة JSON بما أضفته. يتطلب: حدّد السياجات الجغرافية في البناء، و"السماح طوال الوقت" (requestBackgroundLocation()).

function watchOffice() {
  const b = window.WebToApk;
  if (!(b && b.addGeofence)) return;
  const r = JSON.parse(b.addGeofence(JSON.stringify({
    id: 'office', lat: 51.5033, lng: -0.1196, radiusM: 150,
    events: ['enter', 'exit', 'dwell'], dwellMs: 5 * 60000,
    notify: {
      enter: { title: 'At the office', body: 'Tap to check in' },
      exit: { title: 'Left the office', body: 'Tap to check out' }
    }
  })));
  if (!r.ok && r.reason === 'background_permission_required') b.requestBackgroundLocation();
}

window.addEventListener('appmint:geofence-added', function (e) {
  if (!e.detail.ok) showMessage('Geofence ' + e.detail.id + ' not added: ' + e.detail.reason);
});
window.addEventListener('appmint:geofence', function (e) {
  logVisit(e.detail.id, e.detail.event, e.detail.time);
});

console.log(JSON.parse(WebToApk.getGeofences()).map(function (g) { return g.id; }));
WebToApk.removeGeofence('office');

اكتشف الدعم وأبلغ عن كل رفض:

function addCircle(id, lat, lng, radiusM) {
  const b = window.WebToApk;
  if (!(b && b.addGeofence)) { showMessage('Place alerts need the app'); return false; }
  const r = JSON.parse(b.addGeofence(JSON.stringify({ id: id, lat: lat, lng: lng, radiusM: radiusM, events: ['enter'] })));
  if (r.ok) return true;
  if (r.reason === 'not_enabled') showMessage('Geofences are not switched on in this build');
  else if (r.reason === 'background_permission_required') b.requestBackgroundLocation();
  else if (r.reason === 'invalid_radius') showMessage('Pick a radius between 1 m and 100 km');
  else showMessage('Could not add the place: ' + r.reason);
  return false;
}

اعرض المسجَّل وأزل واحدًا:

const mine = JSON.parse(WebToApk.getGeofences());          // [] when none
mine.forEach(function (g) { addRow(g.id); });
document.getElementById('forget').onclick = function () {
  const existed = WebToApk.removeGeofence('office');       // true if it was there
  if (!existed) showMessage('That place was not registered');
};

requestBackgroundLocation الجسر (bridge)#

window.WebToApk.requestBackgroundLocation()

يطلب الموقع، ثم "السماح طوال الوقت"؛ وتصل النتيجة كـ appmint:location-permission.

مثال

يطلب الموقع، ثم "السماح طوال الوقت" (يطلبهما Android 10+ منفصلين؛ وفي Android 11+ تفتح الخطوة الثانية صفحة إعدادات موقع التطبيق). تحتاجه السياجات الجغرافية؛ وكذلك إعادة تشغيل تتبع الموقع بعد إعادة تشغيل الهاتف.

يعيد: لا شيء مباشرة؛ وتصل الإجابة كـ appmint:location-permission (وwindow.onAppMintLocationPermission(detail)) = { foreground: 'granted' | 'denied', background: 'granted' | 'denied', precise: boolean, error?: 'not_enabled' }. getLocationPermissionState() ← الكائن نفسه كسلسلة JSON، دون طرح سؤال. يتطلب: الموقع في الخلفية أو السياجات الجغرافية في البناء.

function askAlways() {
  const b = window.WebToApk;
  if (!(b && b.requestBackgroundLocation)) return;
  const now = JSON.parse(b.getLocationPermissionState());
  if (now.background === 'granted') { enableGeofences(); return; }
  // Explain first: Play requires an in-app disclosure before this request.
  if (!confirm('Allow location "all the time" so we can remind you when you arrive?')) return;
  b.requestBackgroundLocation();
}

window.addEventListener('appmint:location-permission', function (e) {
  if (e.detail.background === 'granted') enableGeofences();
  else showMessage('Choose "Allow all the time" in the location settings to use arrival reminders.');
});

تحقق من الحالة أولًا واشرح ما على المستخدم النقر عليه:

const b = window.WebToApk;
if (b && b.getLocationPermissionState) {
  const s = JSON.parse(b.getLocationPermissionState());
  if (s.foreground !== 'granted') showMessage('Location is off for this app.');
  else if (s.background !== 'granted') showMessage('Tap "Allow all the time" to enable arrival reminders.');
  else if (!s.precise) showMessage('Precise location is off; arrival alerts may be late.');
}

اطلب الإذن، ثم أضف سياجًا جغرافيًا بعد منحه:

window.addEventListener('appmint:location-permission', function (e) {
  if (e.detail.error === 'not_enabled') return showMessage('This build has no background location.');
  if (e.detail.background !== 'granted') return;
  const r = JSON.parse(WebToApk.addGeofence(JSON.stringify({
    id: 'home', lat: 51.5033, lng: -0.1196, radiusM: 120, events: ['enter']
  })));
  if (!r.ok) showMessage('Could not add the place: ' + r.reason);
}, { once: true });
WebToApk.requestBackgroundLocation();

requestIgnoreBatteryOptimizations الجسر (bridge)#

window.WebToApk.requestIgnoreBatteryOptimizations(): String

يطلب استثناء البطارية: 'already_ignoring' | 'dialog_opened' | 'settings_opened' | 'unavailable'.

مثال

موثَّق مع WebToApk.isIgnoringBatteryOptimizations - والمثال هناك يعرض هذا أيضًا.

يفحص ويطلب استثناء تحسين البطارية، حتى لا يؤجّل Doze عملك في الخلفية.

يعيد: isIgnoringBatteryOptimizations() ← true / false. requestIgnoreBatteryOptimizations() ← 'already_ignoring'، 'dialog_opened' (مربع حوار النظام سماح/رفض - فقط عندما حدّد البناء اطلب تخطي تحسين البطارية مباشرة)، 'settings_opened' (قائمة تحسين البطارية؛ بلا إذن ولا إعلان في Play) أو 'unavailable'. يتطلب: لا شيء لقائمة الإعدادات. يعلن مربع الحوار المباشر REQUEST_IGNORE_BATTERY_OPTIMIZATIONS، وتقبله Play فقط عندما يكون الوظيفة الأساسية للتطبيق.

document.getElementById('reliable').addEventListener('click', function () {
  const b = window.WebToApk;
  if (!(b && b.isIgnoringBatteryOptimizations)) return;
  if (b.isIgnoringBatteryOptimizations()) { showMessage('Already unrestricted'); return; }
  const how = b.requestIgnoreBatteryOptimizations();
  if (how === 'settings_opened') showMessage('Find this app in the list and choose "Not optimised".');
});

اذكر ما حدث لكل إجابة.

function askBatteryExemption() {
  const b = window.WebToApk;
  if (!(b && b.requestIgnoreBatteryOptimizations)) { showMessage('Only available in the app'); return; }
  const how = b.requestIgnoreBatteryOptimizations();
  const text = {
    already_ignoring: 'Already unrestricted',
    dialog_opened: 'Please tap Allow',
    settings_opened: 'Choose "Not optimised" for this app',
    unavailable: 'This phone has no such setting'
  };
  showMessage(text[how] || how);
}

اقرنه بالتتبع في الخلفية، لأن Doze قد يوقفه.

if (window.WebToApk && WebToApk.startLocationUpdates) {
  const r = JSON.parse(WebToApk.startLocationUpdates(JSON.stringify({ intervalMs: 30000 })));
  if (r.ok && !WebToApk.isIgnoringBatteryOptimizations()) {
    showMessage('For steady tracking, turn off battery optimisation for this app.');
  }
}

startBackgroundService الجسر (bridge)#

window.WebToApk.startBackgroundService(optionsJson: String): String

يبدأ خدمة التطبيق في المقدمة: {title, text, type?, progress?, stopLabel?, keepAfterSwipe?} ← JSON {ok, status, type, notificationShown} أو {ok:false, reason}.

مثال

يبقي تطبيقك يعمل أثناء غياب المستخدم: خدمة Android حقيقية في المقدمة مع إشعار. أثناء عملها لا تتوقف الصفحة، فيستمر JavaScript الخاص بها و<audio> وموسيقى التطبيق في الخلفية (ما زال Android يبطئ مؤقتات الصفحة التي لا يراها أحد).

يعيد: startBackgroundService ← سلسلة JSON { ok: true, status: 'starting', type, notificationShown } أو { ok: false, reason } - السبب not_enabled، invalid_options، type_not_declared، invalid_progress، location_permission_required، start_not_allowed. updateBackgroundService ← { ok: true } أو { ok: false, reason: 'not_running' }. stopBackgroundService / isBackgroundServiceRunning ← true / false. تصل تغييرات الحالة كـ appmint:background-service (وwindow.onAppMintBackgroundService(detail)): { state: 'running' | 'stopped' | 'failed' | 'not_restarted', reason?, types?, restored?, queued? }. يتطلب: حدّد الاستمرار في العمل في الخلفية (خطوة الوصول ← الخلفية) واختر نوع الخدمة. في Android 13+ لا يظهر الإشعار إلا بعد سماح المستخدم بالإشعارات (notificationShown: false وإلا - وتظل الخدمة تعمل).

function startWorkout() {
  const b = window.WebToApk;
  if (!(b && b.startBackgroundService)) return;             // browser
  const r = JSON.parse(b.startBackgroundService(JSON.stringify({
    title: 'Workout in progress',
    text: '00:00 elapsed',
    stopLabel: 'Stop',                  // a Stop button on the notification
    progress: { value: 0, max: 100 }
  })));
  if (!r.ok) showMessage('Could not keep running: ' + r.reason);
}

function tick(seconds) {
  if (window.WebToApk && WebToApk.isBackgroundServiceRunning()) {
    WebToApk.updateBackgroundService(JSON.stringify({
      title: 'Workout in progress', text: seconds + ' s elapsed', stopLabel: 'Stop',
      progress: { value: Math.min(100, seconds / 18), max: 100 }
    }));
  }
}

function finishWorkout() {
  if (window.WebToApk && WebToApk.stopBackgroundService) WebToApk.stopBackgroundService();
}

window.addEventListener('appmint:background-service', function (e) {
  // reason: 'user' (Stop button), 'page', 'timeout' (Android 15 dataSync 6-hour limit),
  // 'task_removed' (app swiped away); after a phone restart, state 'not_restarted' with
  // 'android_forbids_type_at_boot' | 'background_permission_required' | 'start_not_allowed'
  if (e.detail.state === 'stopped') showMessage('Stopped (' + e.detail.reason + ')');
});

عالج الإجابات التي تعني أن البناء أو الهاتف لا يستطيع القيام بذلك:

function startKeepAlive() {
  const b = window.WebToApk;
  if (!(b && typeof b.startBackgroundService === 'function')) return;
  const r = JSON.parse(b.startBackgroundService(JSON.stringify({ title: 'Syncing', text: 'Uploading photos' })));
  if (r.ok) return;
  if (r.reason === 'not_enabled') showMessage('Keep running in background is not on in this build.');
  else if (r.reason === 'type_not_declared') showMessage('This build declares no service type.');
  else showMessage('Could not start: ' + r.reason);
}

اجمعه مع حلقة رفع: استمر في العمل فقط ما دام في الطابور عمل.

async function uploadAll(files) {
  const b = window.WebToApk;
  const keep = b && b.startBackgroundService &&
    JSON.parse(b.startBackgroundService(JSON.stringify({ title: 'Uploading', text: '0 of ' + files.length }))).ok;
  for (let i = 0; i < files.length; i++) {
    await upload(files[i]);
    if (keep) b.updateBackgroundService(JSON.stringify({ title: 'Uploading', text: (i + 1) + ' of ' + files.length }));
  }
  if (keep) b.stopBackgroundService();
}

ملاحظات: يجب أن يكون النوع واحدًا أعلنه البناء (الذي اختير في المعالج، بالإضافة إلى location عند تشغيل الموقع في الخلفية). سحب التطبيق بعيدًا يوقف خدمة الاستمرار ما لم تمرّر keepAfterSwipe: true (الصفحة نفسها تكون قد زالت حينها). تطلب Play Console إعلانًا لخدمة المقدمة مع فيديو قصير لكل نوع معلن.

startLocationUpdates الجسر (bridge)#

window.WebToApk.startLocationUpdates(optionsJson: String): String

تحديثات الموقع {intervalMs, minDistanceM, accuracy, background, uploadUrl?, headers?} ← JSON {ok, background, backgroundPermission, precise} أو {ok:false, reason}؛ وتصل التحديثات كـ appmint:location.

مثال

تحديثات موقع تستمر في الوصول والتطبيق في الخلفية (متتبع جري، تطبيق توصيل). مع background: true (الافتراضي) تعمل داخل خدمة التطبيق في المقدمة. يذهب كل موقع إلى الصفحة كـ appmint:location ما دامت الصفحة موجودة، وإلى مخزن مؤقت (getBufferedLocations()) عندما لا تكون، وعندما تعطي uploadUrl - إلى خادمك كطلب JSON POST من الكود الأصلي، فيعمل التتبع والصفحة غائبة.

يعيد: سلسلة JSON { ok: true, background, backgroundPermission, precise } أو { ok: false, reason } - not_enabled، invalid_options، invalid_interval (من 1 ثانية إلى 24 ساعة)، invalid_distance، invalid_accuracy، invalid_upload_url، location_permission_required، start_not_allowed، foreign_origin. stopLocationUpdates() ← true إذا كان شيء يعمل. المواقع: appmint:location / window.onAppMintLocation(detail) = { type: 'fix', lat, lng, accuracy, altitude?, speed?, bearing?, time, provider, background }، أو { type: 'error', reason: 'play_services_unavailable' | 'location_unavailable' | 'location_permission_required' }. يتطلب: حدّد الموقع في الخلفية (خطوة الوصول ← الخلفية) وإذن الموقع من المستخدم أولًا.

function startTracking(token) {
  const b = window.WebToApk;
  if (!(b && b.startLocationUpdates)) return;
  navigator.geolocation.getCurrentPosition(function () {       // asks for location first
    const r = JSON.parse(b.startLocationUpdates(JSON.stringify({
      intervalMs: 10000, minDistanceM: 10, accuracy: 'high', background: true,
      uploadUrl: 'https://api.example.com/track',
      headers: { Authorization: 'Bearer ' + token }
    })));
    if (!r.ok) showMessage('Tracking not started: ' + r.reason);
  });
}

window.addEventListener('appmint:location', function (e) {
  if (e.detail.type === 'fix') drawPoint(e.detail.lat, e.detail.lng);
  else showMessage('Location stopped: ' + e.detail.reason);
});

function stopTracking() { if (window.WebToApk) WebToApk.stopLocationUpdates(); }

أبلغ عن خيار خاطئ أو إذن ناقص باستخدام reason:

function tryTrack() {
  const b = window.WebToApk;
  if (!(b && typeof b.startLocationUpdates === 'function')) {
    showMessage('Background tracking needs the installed app.');
    return;
  }
  const r = JSON.parse(b.startLocationUpdates(JSON.stringify({ intervalMs: 500 })));
  if (!r.ok && r.reason === 'invalid_interval') showMessage('Interval must be 1 second or more.');
  if (!r.ok && r.reason === 'location_permission_required') showMessage('Allow location first.');
}

ثنائي طبيعي مع المخزن المؤقت: توقف في نهاية المناوبة وارفع ما سُجّل أثناء غياب الصفحة.

async function endShift() {
  if (!window.WebToApk) return;
  WebToApk.stopLocationUpdates();
  const points = JSON.parse(WebToApk.getBufferedLocations());
  if (points.length) {
    await fetch('/api/shift/points', { method: 'POST', body: JSON.stringify(points) });
    WebToApk.clearBufferedLocations();
  }
}

ملاحظات: خدمة موقع بدأت من التطبيق تستمر في استلام المواقع في الخلفية دون "السماح طوال الوقت"؛ أما إعادة تشغيلها بعد إعادة تشغيل الهاتف فتحتاجه (requestBackgroundLocation()). طلب POST الذي يفشل يُسلَّم إلى WorkManager ويُرسل عند عودة الشبكة. الهواتف بدون خدمات Google Play تجيب بـ play_services_unavailable. تحتاج Play Console إلى إعلان موقع في الخلفية.

stopBackgroundService الجسر (bridge)#

window.WebToApk.stopBackgroundService(): Boolean

يوقف الخدمة التي بدأها startBackgroundService (يستمر تحديد الموقع في الخلفية، إن كان يعمل).

مثال

موثَّق مع WebToApk.startBackgroundService - والمثال هناك يعرض هذا أيضًا.

يبقي تطبيقك يعمل أثناء غياب المستخدم: خدمة Android حقيقية في المقدمة مع إشعار. أثناء عملها لا تتوقف الصفحة، فيستمر JavaScript الخاص بها و<audio> وموسيقى التطبيق في الخلفية (ما زال Android يبطئ مؤقتات الصفحة التي لا يراها أحد).

يعيد: startBackgroundService ← سلسلة JSON { ok: true, status: 'starting', type, notificationShown } أو { ok: false, reason } - السبب not_enabled، invalid_options، type_not_declared، invalid_progress، location_permission_required، start_not_allowed. updateBackgroundService ← { ok: true } أو { ok: false, reason: 'not_running' }. stopBackgroundService / isBackgroundServiceRunning ← true / false. تصل تغييرات الحالة كـ appmint:background-service (وwindow.onAppMintBackgroundService(detail)): { state: 'running' | 'stopped' | 'failed' | 'not_restarted', reason?, types?, restored?, queued? }. يتطلب: حدّد الاستمرار في العمل في الخلفية (خطوة الوصول ← الخلفية) واختر نوع الخدمة. في Android 13+ لا يظهر الإشعار إلا بعد سماح المستخدم بالإشعارات (notificationShown: false وإلا - وتظل الخدمة تعمل).

function startWorkout() {
  const b = window.WebToApk;
  if (!(b && b.startBackgroundService)) return;             // browser
  const r = JSON.parse(b.startBackgroundService(JSON.stringify({
    title: 'Workout in progress',
    text: '00:00 elapsed',
    stopLabel: 'Stop',                  // a Stop button on the notification
    progress: { value: 0, max: 100 }
  })));
  if (!r.ok) showMessage('Could not keep running: ' + r.reason);
}

function tick(seconds) {
  if (window.WebToApk && WebToApk.isBackgroundServiceRunning()) {
    WebToApk.updateBackgroundService(JSON.stringify({
      title: 'Workout in progress', text: seconds + ' s elapsed', stopLabel: 'Stop',
      progress: { value: Math.min(100, seconds / 18), max: 100 }
    }));
  }
}

function finishWorkout() {
  if (window.WebToApk && WebToApk.stopBackgroundService) WebToApk.stopBackgroundService();
}

window.addEventListener('appmint:background-service', function (e) {
  // reason: 'user' (Stop button), 'page', 'timeout' (Android 15 dataSync 6-hour limit),
  // 'task_removed' (app swiped away); after a phone restart, state 'not_restarted' with
  // 'android_forbids_type_at_boot' | 'background_permission_required' | 'start_not_allowed'
  if (e.detail.state === 'stopped') showMessage('Stopped (' + e.detail.reason + ')');
});

عالج الإجابات التي تعني أن البناء أو الهاتف لا يستطيع القيام بذلك:

function startKeepAlive() {
  const b = window.WebToApk;
  if (!(b && typeof b.startBackgroundService === 'function')) return;
  const r = JSON.parse(b.startBackgroundService(JSON.stringify({ title: 'Syncing', text: 'Uploading photos' })));
  if (r.ok) return;
  if (r.reason === 'not_enabled') showMessage('Keep running in background is not on in this build.');
  else if (r.reason === 'type_not_declared') showMessage('This build declares no service type.');
  else showMessage('Could not start: ' + r.reason);
}

اجمعه مع حلقة رفع: استمر في العمل فقط ما دام في الطابور عمل.

async function uploadAll(files) {
  const b = window.WebToApk;
  const keep = b && b.startBackgroundService &&
    JSON.parse(b.startBackgroundService(JSON.stringify({ title: 'Uploading', text: '0 of ' + files.length }))).ok;
  for (let i = 0; i < files.length; i++) {
    await upload(files[i]);
    if (keep) b.updateBackgroundService(JSON.stringify({ title: 'Uploading', text: (i + 1) + ' of ' + files.length }));
  }
  if (keep) b.stopBackgroundService();
}

ملاحظات: يجب أن يكون النوع واحدًا أعلنه البناء (الذي اختير في المعالج، بالإضافة إلى location عند تشغيل الموقع في الخلفية). سحب التطبيق بعيدًا يوقف خدمة الاستمرار ما لم تمرّر keepAfterSwipe: true (الصفحة نفسها تكون قد زالت حينها). تطلب Play Console إعلانًا لخدمة المقدمة مع فيديو قصير لكل نوع معلن.

stopLocationUpdates الجسر (bridge)#

window.WebToApk.stopLocationUpdates(): Boolean

يوقف تحديثات الموقع التي بدأها startLocationUpdates؛ وfalse عندما لم يكن أي منها يعمل.

مثال

موثَّق مع WebToApk.startLocationUpdates - والمثال هناك يعرض هذا أيضًا.

تحديثات موقع تستمر في الوصول والتطبيق في الخلفية (متتبع جري، تطبيق توصيل). مع background: true (الافتراضي) تعمل داخل خدمة التطبيق في المقدمة. يذهب كل موقع إلى الصفحة كـ appmint:location ما دامت الصفحة موجودة، وإلى مخزن مؤقت (getBufferedLocations()) عندما لا تكون، وعندما تعطي uploadUrl - إلى خادمك كطلب JSON POST من الكود الأصلي، فيعمل التتبع والصفحة غائبة.

يعيد: سلسلة JSON { ok: true, background, backgroundPermission, precise } أو { ok: false, reason } - not_enabled، invalid_options، invalid_interval (من 1 ثانية إلى 24 ساعة)، invalid_distance، invalid_accuracy، invalid_upload_url، location_permission_required، start_not_allowed، foreign_origin. stopLocationUpdates() ← true إذا كان شيء يعمل. المواقع: appmint:location / window.onAppMintLocation(detail) = { type: 'fix', lat, lng, accuracy, altitude?, speed?, bearing?, time, provider, background }، أو { type: 'error', reason: 'play_services_unavailable' | 'location_unavailable' | 'location_permission_required' }. يتطلب: حدّد الموقع في الخلفية (خطوة الوصول ← الخلفية) وإذن الموقع من المستخدم أولًا.

function startTracking(token) {
  const b = window.WebToApk;
  if (!(b && b.startLocationUpdates)) return;
  navigator.geolocation.getCurrentPosition(function () {       // asks for location first
    const r = JSON.parse(b.startLocationUpdates(JSON.stringify({
      intervalMs: 10000, minDistanceM: 10, accuracy: 'high', background: true,
      uploadUrl: 'https://api.example.com/track',
      headers: { Authorization: 'Bearer ' + token }
    })));
    if (!r.ok) showMessage('Tracking not started: ' + r.reason);
  });
}

window.addEventListener('appmint:location', function (e) {
  if (e.detail.type === 'fix') drawPoint(e.detail.lat, e.detail.lng);
  else showMessage('Location stopped: ' + e.detail.reason);
});

function stopTracking() { if (window.WebToApk) WebToApk.stopLocationUpdates(); }

أبلغ عن خيار خاطئ أو إذن ناقص باستخدام reason:

function tryTrack() {
  const b = window.WebToApk;
  if (!(b && typeof b.startLocationUpdates === 'function')) {
    showMessage('Background tracking needs the installed app.');
    return;
  }
  const r = JSON.parse(b.startLocationUpdates(JSON.stringify({ intervalMs: 500 })));
  if (!r.ok && r.reason === 'invalid_interval') showMessage('Interval must be 1 second or more.');
  if (!r.ok && r.reason === 'location_permission_required') showMessage('Allow location first.');
}

ثنائي طبيعي مع المخزن المؤقت: توقف في نهاية المناوبة وارفع ما سُجّل أثناء غياب الصفحة.

async function endShift() {
  if (!window.WebToApk) return;
  WebToApk.stopLocationUpdates();
  const points = JSON.parse(WebToApk.getBufferedLocations());
  if (points.length) {
    await fetch('/api/shift/points', { method: 'POST', body: JSON.stringify(points) });
    WebToApk.clearBufferedLocations();
  }
}

ملاحظات: خدمة موقع بدأت من التطبيق تستمر في استلام المواقع في الخلفية دون "السماح طوال الوقت"؛ أما إعادة تشغيلها بعد إعادة تشغيل الهاتف فتحتاجه (requestBackgroundLocation()). طلب POST الذي يفشل يُسلَّم إلى WorkManager ويُرسل عند عودة الشبكة. الهواتف بدون خدمات Google Play تجيب بـ play_services_unavailable. تحتاج Play Console إلى إعلان موقع في الخلفية.

updateBackgroundService الجسر (bridge)#

window.WebToApk.updateBackgroundService(optionsJson: String): String

يغيّر إشعار الخدمة العاملة (الخيارات نفسها مثل startBackgroundService) ← JSON {ok} أو {ok:false, reason:'not_running'}.

مثال

موثَّق مع WebToApk.startBackgroundService - والمثال هناك يعرض هذا أيضًا.

يبقي تطبيقك يعمل أثناء غياب المستخدم: خدمة Android حقيقية في المقدمة مع إشعار. أثناء عملها لا تتوقف الصفحة، فيستمر JavaScript الخاص بها و<audio> وموسيقى التطبيق في الخلفية (ما زال Android يبطئ مؤقتات الصفحة التي لا يراها أحد).

يعيد: startBackgroundService ← سلسلة JSON { ok: true, status: 'starting', type, notificationShown } أو { ok: false, reason } - السبب not_enabled، invalid_options، type_not_declared، invalid_progress، location_permission_required، start_not_allowed. updateBackgroundService ← { ok: true } أو { ok: false, reason: 'not_running' }. stopBackgroundService / isBackgroundServiceRunning ← true / false. تصل تغييرات الحالة كـ appmint:background-service (وwindow.onAppMintBackgroundService(detail)): { state: 'running' | 'stopped' | 'failed' | 'not_restarted', reason?, types?, restored?, queued? }. يتطلب: حدّد الاستمرار في العمل في الخلفية (خطوة الوصول ← الخلفية) واختر نوع الخدمة. في Android 13+ لا يظهر الإشعار إلا بعد سماح المستخدم بالإشعارات (notificationShown: false وإلا - وتظل الخدمة تعمل).

function startWorkout() {
  const b = window.WebToApk;
  if (!(b && b.startBackgroundService)) return;             // browser
  const r = JSON.parse(b.startBackgroundService(JSON.stringify({
    title: 'Workout in progress',
    text: '00:00 elapsed',
    stopLabel: 'Stop',                  // a Stop button on the notification
    progress: { value: 0, max: 100 }
  })));
  if (!r.ok) showMessage('Could not keep running: ' + r.reason);
}

function tick(seconds) {
  if (window.WebToApk && WebToApk.isBackgroundServiceRunning()) {
    WebToApk.updateBackgroundService(JSON.stringify({
      title: 'Workout in progress', text: seconds + ' s elapsed', stopLabel: 'Stop',
      progress: { value: Math.min(100, seconds / 18), max: 100 }
    }));
  }
}

function finishWorkout() {
  if (window.WebToApk && WebToApk.stopBackgroundService) WebToApk.stopBackgroundService();
}

window.addEventListener('appmint:background-service', function (e) {
  // reason: 'user' (Stop button), 'page', 'timeout' (Android 15 dataSync 6-hour limit),
  // 'task_removed' (app swiped away); after a phone restart, state 'not_restarted' with
  // 'android_forbids_type_at_boot' | 'background_permission_required' | 'start_not_allowed'
  if (e.detail.state === 'stopped') showMessage('Stopped (' + e.detail.reason + ')');
});

عالج الإجابات التي تعني أن البناء أو الهاتف لا يستطيع القيام بذلك:

function startKeepAlive() {
  const b = window.WebToApk;
  if (!(b && typeof b.startBackgroundService === 'function')) return;
  const r = JSON.parse(b.startBackgroundService(JSON.stringify({ title: 'Syncing', text: 'Uploading photos' })));
  if (r.ok) return;
  if (r.reason === 'not_enabled') showMessage('Keep running in background is not on in this build.');
  else if (r.reason === 'type_not_declared') showMessage('This build declares no service type.');
  else showMessage('Could not start: ' + r.reason);
}

اجمعه مع حلقة رفع: استمر في العمل فقط ما دام في الطابور عمل.

async function uploadAll(files) {
  const b = window.WebToApk;
  const keep = b && b.startBackgroundService &&
    JSON.parse(b.startBackgroundService(JSON.stringify({ title: 'Uploading', text: '0 of ' + files.length }))).ok;
  for (let i = 0; i < files.length; i++) {
    await upload(files[i]);
    if (keep) b.updateBackgroundService(JSON.stringify({ title: 'Uploading', text: (i + 1) + ' of ' + files.length }));
  }
  if (keep) b.stopBackgroundService();
}

ملاحظات: يجب أن يكون النوع واحدًا أعلنه البناء (الذي اختير في المعالج، بالإضافة إلى location عند تشغيل الموقع في الخلفية). سحب التطبيق بعيدًا يوقف خدمة الاستمرار ما لم تمرّر keepAfterSwipe: true (الصفحة نفسها تكون قد زالت حينها). تطلب Play Console إعلانًا لخدمة المقدمة مع فيديو قصير لكل نوع معلن.

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

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