API du pont JavaScript
Exécution en arrière-plan
Un service de premier plan que la page démarre et arrête, une localisation qui continue d'arriver application en arrière-plan, des géoclôtures qui se déclenchent application fermée, et votre propre code exécuté régulièrement après un redémarrage. Chacun nécessite son option dans la compilation et une déclaration dans la Play Console.
AppMint.background assistant#
AppMint.background.register(name, options) / unregister / list / runNow / done / isBackgroundRun / currentRun / onRun
Exemple
Exécute régulièrement le code PROPRE de votre page, application fermée : actualiser des données, vérifier un prix, envoyer une notification. Chaque exécution charge votre page de façon invisible (même origine, même localStorage/IndexedDB, sans écran ni publicité) avec ?appmint_background=1&appmint_task=<name>&appmint_run=<runId>, déclenche appmint:background { name, runId } une fois chargée, et attend done(runId, { ok }). Une exécution est coupée après environ 9 minutes.
Retourne : chaque méthode retourne une Promise. register → { ok: true, name, intervalMinutes } ou { 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 } (une exécution dès que les conditions le permettent, pour les tests). done → true quand l'exécution a été terminée. isBackgroundRun() et currentRun() sont synchrones. Nécessite : cochez Exécuter le code de la page application fermée (étape Accès → Arrière-plan). Applications de site web et hors ligne/ZIP ; les applications de document et de média n'ont pas de code de page à exécuter.
Une page, deux rôles :
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();Les pages montées tardivement (React, une route chargée à la demande) utilisent onRun, qui se déclenche aussi si l'exécution a déjà démarré, ou la fonction de page :
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()); };Gérer :
AppMint.background.list().then(function (tasks) { console.table(tasks); });
AppMint.background.runNow('prices');
AppMint.background.unregister('prices');Remarques : Android décide du moment exact ; 15 minutes est l'intervalle le plus court et l'économie de batterie peut retarder les exécutions. Avec Démarrer après le redémarrage du téléphone, chaque tâche s'exécute aussi une fois après un redémarrage. Le WebToApk d'une exécution n'a que notify, showNotification, le tampon de localisation, les géoclôtures, workEnqueueOnline, isBackgroundServiceRunning et ces méthodes de tâche ; tout le reste est absent (détectez la disponibilité).
addGeofence pont#
window.WebToApk.addGeofence(json: String): String
Ajoute une géoclôture {id, lat, lng, radiusM, events, dwellMs?, notify?} → JSON {ok, status:'adding'} ou {ok:false, reason} ; le résultat est appmint:geofence-added.
Exemple
Un cercle qui déclenche enter, exit et/ou dwell application fermée, en publiant éventuellement une notification. Les géoclôtures survivent à un redémarrage du téléphone (elles sont rajoutées automatiquement).
Retourne : addGeofence → une chaîne JSON { ok: true, status: 'adding', id } ou { ok: false, reason } - not_enabled, invalid_id, invalid_position, invalid_radius (1 m … 100 km), invalid_events, location_permission_required, background_permission_required, foreign_origin. La réponse des services Play arrive sous forme de appmint:geofence-added (window.onAppMintGeofenceAdded) = { id, ok, reason? } (location_off, too_many_geofences - 100 par application, play_services_unavailable). Une transition arrive sous forme de appmint:geofence (window.onAppMintGeofence) = { id, event: 'enter' | 'exit' | 'dwell', time, lat, lng, queued? } - queued: true quand elle s'est produite application fermée et est transmise au prochain démarrage. removeGeofence → true si elle existait ; getGeofences → un tableau JSON de ce que vous avez ajouté. Nécessite : cochez Géoclôtures dans la compilation, et « Toujours autoriser » (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');Détectez la disponibilité et signalez chaque refus :
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;
}Listez ce qui est enregistré et supprimez un élément :
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 pont#
window.WebToApk.backgroundDone(runId: String, resultJson: String): Boolean
AppMint.background.done(runId, result) : termine une exécution en arrière-plan ; toujours false dans l'application elle-même.
Exemple
Documenté avec WebToApk.backgroundRegister : l'exemple qui s'y trouve illustre aussi celui-ci.
Les méthodes brutes derrière AppMint.background : utilisez AppMint.background.register(...) (elle retourne des promesses et gère l'événement d'exécution). Listées ici pour que chaque méthode du pont ait un exemple.
Retourne : backgroundRegister → JSON { ok: true, name, intervalMinutes } ou { ok: false, reason } (not_enabled, invalid_name - lettres, chiffres, _, -, jusqu'à 64 - interval_below_15_minutes, invalid_interval, schedule_failed). backgroundUnregister → true si elle existait. backgroundList → tableau JSON. backgroundRunNow → JSON { ok, name } ou { ok: false, reason: 'not_registered' }. backgroundDone(runId, resultJson) → true dans une exécution en arrière-plan, toujours false dans l'application. isBackgroundRun() → false dans l'application, true dans une exécution en arrière-plan. Nécessite : cochez Exécuter le code de la page application fermée.
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 }))
}Détectez la disponibilité et distinguez l'exécution dans l'application de l'exécution en arrière-plan :
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'
}Testez une tâche et nettoyez :
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 existedbackgroundList pont#
window.WebToApk.backgroundList(): String
AppMint.background.list() : tableau JSON {name, intervalMinutes, requiresNetwork, requiresCharging, registeredAt, lastRunAt, lastResult}.
Exemple
Documenté avec WebToApk.backgroundRegister : l'exemple qui s'y trouve illustre aussi celui-ci.
Les méthodes brutes derrière AppMint.background : utilisez AppMint.background.register(...) (elle retourne des promesses et gère l'événement d'exécution). Listées ici pour que chaque méthode du pont ait un exemple.
Retourne : backgroundRegister → JSON { ok: true, name, intervalMinutes } ou { ok: false, reason } (not_enabled, invalid_name - lettres, chiffres, _, -, jusqu'à 64 - interval_below_15_minutes, invalid_interval, schedule_failed). backgroundUnregister → true si elle existait. backgroundList → tableau JSON. backgroundRunNow → JSON { ok, name } ou { ok: false, reason: 'not_registered' }. backgroundDone(runId, resultJson) → true dans une exécution en arrière-plan, toujours false dans l'application. isBackgroundRun() → false dans l'application, true dans une exécution en arrière-plan. Nécessite : cochez Exécuter le code de la page application fermée.
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 }))
}Détectez la disponibilité et distinguez l'exécution dans l'application de l'exécution en arrière-plan :
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'
}Testez une tâche et nettoyez :
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 existedbackgroundRegister pont#
window.WebToApk.backgroundRegister(name: String, optionsJson: String): String
AppMint.background.register(name, {intervalMinutes >= 15, requiresNetwork, requiresCharging}) → JSON {ok, name, intervalMinutes} ou {ok:false, reason}.
Exemple
Les méthodes brutes derrière AppMint.background : utilisez AppMint.background.register(...) (elle retourne des promesses et gère l'événement d'exécution). Listées ici pour que chaque méthode du pont ait un exemple.
Retourne : backgroundRegister → JSON { ok: true, name, intervalMinutes } ou { ok: false, reason } (not_enabled, invalid_name - lettres, chiffres, _, -, jusqu'à 64 - interval_below_15_minutes, invalid_interval, schedule_failed). backgroundUnregister → true si elle existait. backgroundList → tableau JSON. backgroundRunNow → JSON { ok, name } ou { ok: false, reason: 'not_registered' }. backgroundDone(runId, resultJson) → true dans une exécution en arrière-plan, toujours false dans l'application. isBackgroundRun() → false dans l'application, true dans une exécution en arrière-plan. Nécessite : cochez Exécuter le code de la page application fermée.
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 }))
}Détectez la disponibilité et distinguez l'exécution dans l'application de l'exécution en arrière-plan :
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'
}Testez une tâche et nettoyez :
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 existedbackgroundRunNow pont#
window.WebToApk.backgroundRunNow(name: String): String
AppMint.background.runNow(name) : une exécution en arrière-plan dès que ses conditions le permettent.
Exemple
Documenté avec WebToApk.backgroundRegister : l'exemple qui s'y trouve illustre aussi celui-ci.
Les méthodes brutes derrière AppMint.background : utilisez AppMint.background.register(...) (elle retourne des promesses et gère l'événement d'exécution). Listées ici pour que chaque méthode du pont ait un exemple.
Retourne : backgroundRegister → JSON { ok: true, name, intervalMinutes } ou { ok: false, reason } (not_enabled, invalid_name - lettres, chiffres, _, -, jusqu'à 64 - interval_below_15_minutes, invalid_interval, schedule_failed). backgroundUnregister → true si elle existait. backgroundList → tableau JSON. backgroundRunNow → JSON { ok, name } ou { ok: false, reason: 'not_registered' }. backgroundDone(runId, resultJson) → true dans une exécution en arrière-plan, toujours false dans l'application. isBackgroundRun() → false dans l'application, true dans une exécution en arrière-plan. Nécessite : cochez Exécuter le code de la page application fermée.
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 }))
}Détectez la disponibilité et distinguez l'exécution dans l'application de l'exécution en arrière-plan :
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'
}Testez une tâche et nettoyez :
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 existedbackgroundUnregister pont#
window.WebToApk.backgroundUnregister(name: String): Boolean
AppMint.background.unregister(name) ; false si aucune tâche n'avait ce nom.
Exemple
Documenté avec WebToApk.backgroundRegister : l'exemple qui s'y trouve illustre aussi celui-ci.
Les méthodes brutes derrière AppMint.background : utilisez AppMint.background.register(...) (elle retourne des promesses et gère l'événement d'exécution). Listées ici pour que chaque méthode du pont ait un exemple.
Retourne : backgroundRegister → JSON { ok: true, name, intervalMinutes } ou { ok: false, reason } (not_enabled, invalid_name - lettres, chiffres, _, -, jusqu'à 64 - interval_below_15_minutes, invalid_interval, schedule_failed). backgroundUnregister → true si elle existait. backgroundList → tableau JSON. backgroundRunNow → JSON { ok, name } ou { ok: false, reason: 'not_registered' }. backgroundDone(runId, resultJson) → true dans une exécution en arrière-plan, toujours false dans l'application. isBackgroundRun() → false dans l'application, true dans une exécution en arrière-plan. Nécessite : cochez Exécuter le code de la page application fermée.
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 }))
}Détectez la disponibilité et distinguez l'exécution dans l'application de l'exécution en arrière-plan :
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'
}Testez une tâche et nettoyez :
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 existedclearBufferedLocations pont#
window.WebToApk.clearBufferedLocations(): Boolean
Vide le tampon lu par getBufferedLocations (appelez-la après avoir enregistré les points).
Exemple
Documenté avec WebToApk.getBufferedLocations : l'exemple qui s'y trouve illustre aussi celui-ci.
Les positions enregistrées pendant l'absence de votre page (application en arrière-plan ou fermée), de la plus ancienne à la plus récente, jusqu'à 10 000.
Retourne : getBufferedLocations() → un tableau de chaînes JSON de { type: 'fix', lat, lng, accuracy, time, …, background: true } ; '[]' lorsque la localisation en arrière-plan est désactivée. clearBufferedLocations() → true une fois vidé. Nécessite : Localisation en arrière-plan dans la compilation.
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(); });Démarrez le suivi, puis gérez un refus. L'appel de démarrage répond par une chaîne 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, ...
}Les positions en direct tant que la page est ouverte arrivent sous forme de appmint:location ; le tampon contient le reste.
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 pont#
window.WebToApk.clearGeofenceEvents(): Boolean
Vide l'historique des transitions lu par getGeofenceEvents.
Exemple
Documenté avec WebToApk.getGeofenceEvents : l'exemple qui s'y trouve illustre aussi celui-ci.
Les 100 dernières transitions de géoclôture, de la plus ancienne à la plus récente, y compris celles survenues application fermée. Lisez-les au démarrage si votre page se monte tard et risque de manquer les événements appmint:geofence mis en file.
Retourne : getGeofenceEvents() → un tableau de chaînes JSON de { id, event, time, lat, lng } ; clearGeofenceEvents() → true. Nécessite : Géoclôtures dans la compilation.
if (window.WebToApk && WebToApk.getGeofenceEvents) {
JSON.parse(WebToApk.getGeofenceEvents()).forEach(function (ev) {
logVisit(ev.id, ev.event, ev.time);
});
WebToApk.clearGeofenceEvents();
}Ajoutez une géoclôture, puis surveillez les transitions tant que la page est ouverte.
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);
});Listez ce qui est enregistré et supprimez un élément quand l'utilisateur quitte une fonctionnalité.
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 pont#
window.WebToApk.getBufferedLocations(): String
Tableau JSON des positions enregistrées pendant l'absence de la page, de la plus ancienne à la plus récente (jusqu'à 10 000).
Exemple
Les positions enregistrées pendant l'absence de votre page (application en arrière-plan ou fermée), de la plus ancienne à la plus récente, jusqu'à 10 000.
Retourne : getBufferedLocations() → un tableau de chaînes JSON de { type: 'fix', lat, lng, accuracy, time, …, background: true } ; '[]' lorsque la localisation en arrière-plan est désactivée. clearBufferedLocations() → true une fois vidé. Nécessite : Localisation en arrière-plan dans la compilation.
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(); });Démarrez le suivi, puis gérez un refus. L'appel de démarrage répond par une chaîne 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, ...
}Les positions en direct tant que la page est ouverte arrivent sous forme de appmint:location ; le tampon contient le reste.
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 pont#
window.WebToApk.getGeofenceEvents(): String
Tableau JSON des 100 dernières transitions de géoclôture {id, event, time, lat, lng}, de la plus ancienne à la plus récente.
Exemple
Les 100 dernières transitions de géoclôture, de la plus ancienne à la plus récente, y compris celles survenues application fermée. Lisez-les au démarrage si votre page se monte tard et risque de manquer les événements appmint:geofence mis en file.
Retourne : getGeofenceEvents() → un tableau de chaînes JSON de { id, event, time, lat, lng } ; clearGeofenceEvents() → true. Nécessite : Géoclôtures dans la compilation.
if (window.WebToApk && WebToApk.getGeofenceEvents) {
JSON.parse(WebToApk.getGeofenceEvents()).forEach(function (ev) {
logVisit(ev.id, ev.event, ev.time);
});
WebToApk.clearGeofenceEvents();
}Ajoutez une géoclôture, puis surveillez les transitions tant que la page est ouverte.
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);
});Listez ce qui est enregistré et supprimez un élément quand l'utilisateur quitte une fonctionnalité.
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 pont#
window.WebToApk.getGeofences(): String
Tableau JSON des géoclôtures ajoutées par cette application (elles survivent aux redémarrages).
Exemple
Documenté avec WebToApk.addGeofence : l'exemple qui s'y trouve illustre aussi celui-ci.
Un cercle qui déclenche enter, exit et/ou dwell application fermée, en publiant éventuellement une notification. Les géoclôtures survivent à un redémarrage du téléphone (elles sont rajoutées automatiquement).
Retourne : addGeofence → une chaîne JSON { ok: true, status: 'adding', id } ou { ok: false, reason } - not_enabled, invalid_id, invalid_position, invalid_radius (1 m … 100 km), invalid_events, location_permission_required, background_permission_required, foreign_origin. La réponse des services Play arrive sous forme de appmint:geofence-added (window.onAppMintGeofenceAdded) = { id, ok, reason? } (location_off, too_many_geofences - 100 par application, play_services_unavailable). Une transition arrive sous forme de appmint:geofence (window.onAppMintGeofence) = { id, event: 'enter' | 'exit' | 'dwell', time, lat, lng, queued? } - queued: true quand elle s'est produite application fermée et est transmise au prochain démarrage. removeGeofence → true si elle existait ; getGeofences → un tableau JSON de ce que vous avez ajouté. Nécessite : cochez Géoclôtures dans la compilation, et « Toujours autoriser » (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');Détectez la disponibilité et signalez chaque refus :
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;
}Listez ce qui est enregistré et supprimez un élément :
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 pont#
window.WebToApk.getLocationPermissionState(): String
JSON {foreground:'granted'|'denied', background:'granted'|'denied', precise: boolean}.
Exemple
Documenté avec WebToApk.requestBackgroundLocation : l'exemple qui s'y trouve illustre aussi celui-ci.
Demande la localisation, puis « Toujours autoriser » (Android 10+ les demande séparément ; sur Android 11+ la seconde étape ouvre la page des paramètres de localisation de l'application). Les géoclôtures en ont besoin ; de même que la reprise du suivi de localisation après un redémarrage du téléphone.
Retourne : rien directement ; la réponse arrive sous forme de appmint:location-permission (et window.onAppMintLocationPermission(detail)) = { foreground: 'granted' | 'denied', background: 'granted' | 'denied', precise: boolean, error?: 'not_enabled' }. getLocationPermissionState() → le même objet sous forme de chaîne JSON, sans rien demander. Nécessite : Localisation en arrière-plan ou Géoclôtures dans la compilation.
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.');
});Vérifiez d'abord l'état et expliquez ce que l'utilisateur doit toucher :
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.');
}Demandez l'autorisation, puis ajoutez une géoclôture une fois accordée :
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 pont#
window.WebToApk.isBackgroundRun(): Boolean
False ici : la page propre à l'application n'est jamais une exécution en arrière-plan (une exécution sans interface répond true).
Exemple
Documenté avec WebToApk.backgroundRegister : l'exemple qui s'y trouve illustre aussi celui-ci.
Les méthodes brutes derrière AppMint.background : utilisez AppMint.background.register(...) (elle retourne des promesses et gère l'événement d'exécution). Listées ici pour que chaque méthode du pont ait un exemple.
Retourne : backgroundRegister → JSON { ok: true, name, intervalMinutes } ou { ok: false, reason } (not_enabled, invalid_name - lettres, chiffres, _, -, jusqu'à 64 - interval_below_15_minutes, invalid_interval, schedule_failed). backgroundUnregister → true si elle existait. backgroundList → tableau JSON. backgroundRunNow → JSON { ok, name } ou { ok: false, reason: 'not_registered' }. backgroundDone(runId, resultJson) → true dans une exécution en arrière-plan, toujours false dans l'application. isBackgroundRun() → false dans l'application, true dans une exécution en arrière-plan. Nécessite : cochez Exécuter le code de la page application fermée.
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 }))
}Détectez la disponibilité et distinguez l'exécution dans l'application de l'exécution en arrière-plan :
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'
}Testez une tâche et nettoyez :
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 existedisBackgroundServiceRunning pont#
window.WebToApk.isBackgroundServiceRunning(): Boolean
Indique si le service de premier plan de l'application tourne (pour le maintien en activité ou la localisation en arrière-plan).
Exemple
Documenté avec WebToApk.startBackgroundService : l'exemple qui s'y trouve illustre aussi celui-ci.
Garde votre application active pendant que l'utilisateur est absent : un vrai service de premier plan Android avec une notification. Tant qu'il tourne, la page n'est pas mise en pause : son JavaScript, <audio> et la musique de fond de l'application continuent (Android ralentit quand même les minuteurs d'une page que personne ne voit).
Retourne : startBackgroundService → une chaîne JSON { ok: true, status: 'starting', type, notificationShown } ou { ok: false, reason } - raison not_enabled, invalid_options, type_not_declared, invalid_progress, location_permission_required, start_not_allowed. updateBackgroundService → { ok: true } ou { ok: false, reason: 'not_running' }. stopBackgroundService / isBackgroundServiceRunning → true / false. Les changements d'état arrivent sous forme de appmint:background-service (et window.onAppMintBackgroundService(detail)) : { state: 'running' | 'stopped' | 'failed' | 'not_restarted', reason?, types?, restored?, queued? }. Nécessite : cochez Maintenir en activité en arrière-plan (étape Accès → Arrière-plan) et choisissez le type de service. Sur Android 13+, la notification n'est visible que si l'utilisateur a autorisé les notifications (notificationShown: false sinon ; le service tourne quand même).
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 + ')');
});Gérez les réponses qui signifient que la compilation ou le téléphone ne peut pas le faire :
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);
}Combinez-le avec une boucle d'envoi : ne restez actif que tant que la file contient du travail.
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();
}Remarques : le type doit être l'un de ceux déclarés par la compilation (celui choisi dans l'assistant, plus location lorsque la localisation en arrière-plan est activée). Fermer l'application d'un balayage arrête un service de maintien en activité, sauf si vous passez keepAfterSwipe: true (la page elle-même a alors disparu). La Play Console demande une déclaration de service de premier plan avec une courte vidéo pour chaque type déclaré.
isIgnoringBatteryOptimizations pont#
window.WebToApk.isIgnoringBatteryOptimizations(): Boolean
Indique si l'utilisateur a exempté cette application de l'optimisation de la batterie (mise en veille Doze).
Exemple
Vérifie et demande l'exemption d'optimisation de la batterie, pour que Doze ne reporte pas votre travail en arrière-plan.
Retourne : isIgnoringBatteryOptimizations() → true / false. requestIgnoreBatteryOptimizations() → 'already_ignoring', 'dialog_opened' (la boîte de dialogue Autoriser/Refuser du système, uniquement lorsque la compilation a coché Demander directement d'ignorer l'optimisation de la batterie), 'settings_opened' (la liste d'optimisation de la batterie ; sans autorisation ni déclaration Play) ou 'unavailable'. Nécessite : rien pour la liste des paramètres. La boîte de dialogue directe déclare REQUEST_IGNORE_BATTERY_OPTIMIZATIONS, que Play n'accepte que si c'est la fonction principale de l'application.
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".');
});Dites ce qui s'est passé pour chaque réponse.
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);
}Associez-le au suivi en arrière-plan, puisque Doze le mettrait en pause.
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 pont#
window.WebToApk.removeGeofence(id: String): Boolean
Supprime la géoclôture portant cet id ; false s'il n'y en avait pas.
Exemple
Documenté avec WebToApk.addGeofence : l'exemple qui s'y trouve illustre aussi celui-ci.
Un cercle qui déclenche enter, exit et/ou dwell application fermée, en publiant éventuellement une notification. Les géoclôtures survivent à un redémarrage du téléphone (elles sont rajoutées automatiquement).
Retourne : addGeofence → une chaîne JSON { ok: true, status: 'adding', id } ou { ok: false, reason } - not_enabled, invalid_id, invalid_position, invalid_radius (1 m … 100 km), invalid_events, location_permission_required, background_permission_required, foreign_origin. La réponse des services Play arrive sous forme de appmint:geofence-added (window.onAppMintGeofenceAdded) = { id, ok, reason? } (location_off, too_many_geofences - 100 par application, play_services_unavailable). Une transition arrive sous forme de appmint:geofence (window.onAppMintGeofence) = { id, event: 'enter' | 'exit' | 'dwell', time, lat, lng, queued? } - queued: true quand elle s'est produite application fermée et est transmise au prochain démarrage. removeGeofence → true si elle existait ; getGeofences → un tableau JSON de ce que vous avez ajouté. Nécessite : cochez Géoclôtures dans la compilation, et « Toujours autoriser » (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');Détectez la disponibilité et signalez chaque refus :
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;
}Listez ce qui est enregistré et supprimez un élément :
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 pont#
window.WebToApk.requestBackgroundLocation()
Demande la localisation, puis « Toujours autoriser » ; le résultat arrive sous forme de appmint:location-permission.
Exemple
Demande la localisation, puis « Toujours autoriser » (Android 10+ les demande séparément ; sur Android 11+ la seconde étape ouvre la page des paramètres de localisation de l'application). Les géoclôtures en ont besoin ; de même que la reprise du suivi de localisation après un redémarrage du téléphone.
Retourne : rien directement ; la réponse arrive sous forme de appmint:location-permission (et window.onAppMintLocationPermission(detail)) = { foreground: 'granted' | 'denied', background: 'granted' | 'denied', precise: boolean, error?: 'not_enabled' }. getLocationPermissionState() → le même objet sous forme de chaîne JSON, sans rien demander. Nécessite : Localisation en arrière-plan ou Géoclôtures dans la compilation.
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.');
});Vérifiez d'abord l'état et expliquez ce que l'utilisateur doit toucher :
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.');
}Demandez l'autorisation, puis ajoutez une géoclôture une fois accordée :
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 pont#
window.WebToApk.requestIgnoreBatteryOptimizations(): String
Demande l'exemption de la batterie : 'already_ignoring' | 'dialog_opened' | 'settings_opened' | 'unavailable'.
Exemple
Documenté avec WebToApk.isIgnoringBatteryOptimizations : l'exemple qui s'y trouve illustre aussi celui-ci.
Vérifie et demande l'exemption d'optimisation de la batterie, pour que Doze ne reporte pas votre travail en arrière-plan.
Retourne : isIgnoringBatteryOptimizations() → true / false. requestIgnoreBatteryOptimizations() → 'already_ignoring', 'dialog_opened' (la boîte de dialogue Autoriser/Refuser du système, uniquement lorsque la compilation a coché Demander directement d'ignorer l'optimisation de la batterie), 'settings_opened' (la liste d'optimisation de la batterie ; sans autorisation ni déclaration Play) ou 'unavailable'. Nécessite : rien pour la liste des paramètres. La boîte de dialogue directe déclare REQUEST_IGNORE_BATTERY_OPTIMIZATIONS, que Play n'accepte que si c'est la fonction principale de l'application.
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".');
});Dites ce qui s'est passé pour chaque réponse.
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);
}Associez-le au suivi en arrière-plan, puisque Doze le mettrait en pause.
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 pont#
window.WebToApk.startBackgroundService(optionsJson: String): String
Démarre le service de premier plan de l'application : {title, text, type?, progress?, stopLabel?, keepAfterSwipe?} → JSON {ok, status, type, notificationShown} ou {ok:false, reason}.
Exemple
Garde votre application active pendant que l'utilisateur est absent : un vrai service de premier plan Android avec une notification. Tant qu'il tourne, la page n'est pas mise en pause : son JavaScript, <audio> et la musique de fond de l'application continuent (Android ralentit quand même les minuteurs d'une page que personne ne voit).
Retourne : startBackgroundService → une chaîne JSON { ok: true, status: 'starting', type, notificationShown } ou { ok: false, reason } - raison not_enabled, invalid_options, type_not_declared, invalid_progress, location_permission_required, start_not_allowed. updateBackgroundService → { ok: true } ou { ok: false, reason: 'not_running' }. stopBackgroundService / isBackgroundServiceRunning → true / false. Les changements d'état arrivent sous forme de appmint:background-service (et window.onAppMintBackgroundService(detail)) : { state: 'running' | 'stopped' | 'failed' | 'not_restarted', reason?, types?, restored?, queued? }. Nécessite : cochez Maintenir en activité en arrière-plan (étape Accès → Arrière-plan) et choisissez le type de service. Sur Android 13+, la notification n'est visible que si l'utilisateur a autorisé les notifications (notificationShown: false sinon ; le service tourne quand même).
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 + ')');
});Gérez les réponses qui signifient que la compilation ou le téléphone ne peut pas le faire :
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);
}Combinez-le avec une boucle d'envoi : ne restez actif que tant que la file contient du travail.
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();
}Remarques : le type doit être l'un de ceux déclarés par la compilation (celui choisi dans l'assistant, plus location lorsque la localisation en arrière-plan est activée). Fermer l'application d'un balayage arrête un service de maintien en activité, sauf si vous passez keepAfterSwipe: true (la page elle-même a alors disparu). La Play Console demande une déclaration de service de premier plan avec une courte vidéo pour chaque type déclaré.
startLocationUpdates pont#
window.WebToApk.startLocationUpdates(optionsJson: String): String
Mises à jour de localisation {intervalMs, minDistanceM, accuracy, background, uploadUrl?, headers?} → JSON {ok, background, backgroundPermission, precise} ou {ok:false, reason} ; les positions arrivent sous forme de appmint:location.
Exemple
Des mises à jour de localisation qui continuent d'arriver pendant que l'application est en arrière-plan (suivi de course, application de livraison). Avec background: true (par défaut), elles s'exécutent dans le service de premier plan de l'application. Chaque position va à la page sous forme de appmint:location tant que la page est là, dans un tampon (getBufferedLocations()) quand elle n'y est pas, et, si vous fournissez une uploadUrl, à votre serveur sous forme de POST JSON depuis le code natif, donc le suivi fonctionne même sans la page.
Retourne : une chaîne JSON { ok: true, background, backgroundPermission, precise } ou { ok: false, reason } - not_enabled, invalid_options, invalid_interval (1 s … 24 h), invalid_distance, invalid_accuracy, invalid_upload_url, location_permission_required, start_not_allowed, foreign_origin. stopLocationUpdates() → true si quelque chose tournait. Positions : appmint:location / window.onAppMintLocation(detail) = { type: 'fix', lat, lng, accuracy, altitude?, speed?, bearing?, time, provider, background }, ou { type: 'error', reason: 'play_services_unavailable' | 'location_unavailable' | 'location_permission_required' }. Nécessite : cochez Localisation en arrière-plan (étape Accès → Arrière-plan) et l'autorisation de localisation de l'utilisateur d'abord.
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(); }Signalez une mauvaise option ou une autorisation manquante avec le 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.');
}Un duo naturel avec le tampon : arrêtez-vous en fin de service et envoyez ce qui a été enregistré pendant que la page était absente.
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();
}
}Remarques : un service de localisation démarré depuis l'application continue de recevoir des positions en arrière-plan sans « Toujours autoriser » ; le relancer après un redémarrage du téléphone en a besoin (requestBackgroundLocation()). Un POST qui échoue est confié à WorkManager et envoyé au retour du réseau. Les téléphones sans services Google Play répondent play_services_unavailable. La Play Console exige une déclaration de localisation en arrière-plan.
stopBackgroundService pont#
window.WebToApk.stopBackgroundService(): Boolean
Arrête le service démarré par startBackgroundService (la localisation en arrière-plan, si elle est active, continue).
Exemple
Documenté avec WebToApk.startBackgroundService : l'exemple qui s'y trouve illustre aussi celui-ci.
Garde votre application active pendant que l'utilisateur est absent : un vrai service de premier plan Android avec une notification. Tant qu'il tourne, la page n'est pas mise en pause : son JavaScript, <audio> et la musique de fond de l'application continuent (Android ralentit quand même les minuteurs d'une page que personne ne voit).
Retourne : startBackgroundService → une chaîne JSON { ok: true, status: 'starting', type, notificationShown } ou { ok: false, reason } - raison not_enabled, invalid_options, type_not_declared, invalid_progress, location_permission_required, start_not_allowed. updateBackgroundService → { ok: true } ou { ok: false, reason: 'not_running' }. stopBackgroundService / isBackgroundServiceRunning → true / false. Les changements d'état arrivent sous forme de appmint:background-service (et window.onAppMintBackgroundService(detail)) : { state: 'running' | 'stopped' | 'failed' | 'not_restarted', reason?, types?, restored?, queued? }. Nécessite : cochez Maintenir en activité en arrière-plan (étape Accès → Arrière-plan) et choisissez le type de service. Sur Android 13+, la notification n'est visible que si l'utilisateur a autorisé les notifications (notificationShown: false sinon ; le service tourne quand même).
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 + ')');
});Gérez les réponses qui signifient que la compilation ou le téléphone ne peut pas le faire :
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);
}Combinez-le avec une boucle d'envoi : ne restez actif que tant que la file contient du travail.
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();
}Remarques : le type doit être l'un de ceux déclarés par la compilation (celui choisi dans l'assistant, plus location lorsque la localisation en arrière-plan est activée). Fermer l'application d'un balayage arrête un service de maintien en activité, sauf si vous passez keepAfterSwipe: true (la page elle-même a alors disparu). La Play Console demande une déclaration de service de premier plan avec une courte vidéo pour chaque type déclaré.
stopLocationUpdates pont#
window.WebToApk.stopLocationUpdates(): Boolean
Arrête les mises à jour de localisation démarrées par startLocationUpdates ; false si aucune n'était en cours.
Exemple
Documenté avec WebToApk.startLocationUpdates : l'exemple qui s'y trouve illustre aussi celui-ci.
Des mises à jour de localisation qui continuent d'arriver pendant que l'application est en arrière-plan (suivi de course, application de livraison). Avec background: true (par défaut), elles s'exécutent dans le service de premier plan de l'application. Chaque position va à la page sous forme de appmint:location tant que la page est là, dans un tampon (getBufferedLocations()) quand elle n'y est pas, et, si vous fournissez une uploadUrl, à votre serveur sous forme de POST JSON depuis le code natif, donc le suivi fonctionne même sans la page.
Retourne : une chaîne JSON { ok: true, background, backgroundPermission, precise } ou { ok: false, reason } - not_enabled, invalid_options, invalid_interval (1 s … 24 h), invalid_distance, invalid_accuracy, invalid_upload_url, location_permission_required, start_not_allowed, foreign_origin. stopLocationUpdates() → true si quelque chose tournait. Positions : appmint:location / window.onAppMintLocation(detail) = { type: 'fix', lat, lng, accuracy, altitude?, speed?, bearing?, time, provider, background }, ou { type: 'error', reason: 'play_services_unavailable' | 'location_unavailable' | 'location_permission_required' }. Nécessite : cochez Localisation en arrière-plan (étape Accès → Arrière-plan) et l'autorisation de localisation de l'utilisateur d'abord.
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(); }Signalez une mauvaise option ou une autorisation manquante avec le 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.');
}Un duo naturel avec le tampon : arrêtez-vous en fin de service et envoyez ce qui a été enregistré pendant que la page était absente.
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();
}
}Remarques : un service de localisation démarré depuis l'application continue de recevoir des positions en arrière-plan sans « Toujours autoriser » ; le relancer après un redémarrage du téléphone en a besoin (requestBackgroundLocation()). Un POST qui échoue est confié à WorkManager et envoyé au retour du réseau. Les téléphones sans services Google Play répondent play_services_unavailable. La Play Console exige une déclaration de localisation en arrière-plan.
updateBackgroundService pont#
window.WebToApk.updateBackgroundService(optionsJson: String): String
Modifie la notification du service en cours (mêmes options que startBackgroundService) → JSON {ok} ou {ok:false, reason:'not_running'}.
Exemple
Documenté avec WebToApk.startBackgroundService : l'exemple qui s'y trouve illustre aussi celui-ci.
Garde votre application active pendant que l'utilisateur est absent : un vrai service de premier plan Android avec une notification. Tant qu'il tourne, la page n'est pas mise en pause : son JavaScript, <audio> et la musique de fond de l'application continuent (Android ralentit quand même les minuteurs d'une page que personne ne voit).
Retourne : startBackgroundService → une chaîne JSON { ok: true, status: 'starting', type, notificationShown } ou { ok: false, reason } - raison not_enabled, invalid_options, type_not_declared, invalid_progress, location_permission_required, start_not_allowed. updateBackgroundService → { ok: true } ou { ok: false, reason: 'not_running' }. stopBackgroundService / isBackgroundServiceRunning → true / false. Les changements d'état arrivent sous forme de appmint:background-service (et window.onAppMintBackgroundService(detail)) : { state: 'running' | 'stopped' | 'failed' | 'not_restarted', reason?, types?, restored?, queued? }. Nécessite : cochez Maintenir en activité en arrière-plan (étape Accès → Arrière-plan) et choisissez le type de service. Sur Android 13+, la notification n'est visible que si l'utilisateur a autorisé les notifications (notificationShown: false sinon ; le service tourne quand même).
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 + ')');
});Gérez les réponses qui signifient que la compilation ou le téléphone ne peut pas le faire :
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);
}Combinez-le avec une boucle d'envoi : ne restez actif que tant que la file contient du travail.
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();
}Remarques : le type doit être l'un de ceux déclarés par la compilation (celui choisi dans l'assistant, plus location lorsque la localisation en arrière-plan est activée). Fermer l'application d'un balayage arrête un service de maintien en activité, sauf si vous passez keepAfterSwipe: true (la page elle-même a alors disparu). La Play Console demande une déclaration de service de premier plan avec une courte vidéo pour chaque type déclaré.