API del bridge JavaScript
Esecuzione in background
Un servizio in primo piano che la pagina avvia e ferma, una posizione che continua ad arrivare con l'app in secondo piano, geofence che scattano con l'app chiusa e il tuo codice eseguito periodicamente dopo un riavvio. Ognuno richiede il suo interruttore nella build e una dichiarazione in Play Console.
AppMint.background helper#
AppMint.background.register(name, options) / unregister / list / runNow / done / isBackgroundRun / currentRun / onRun
Esempio
Esegue periodicamente il codice della TUA pagina con l'app chiusa - aggiorna dati, controlla un prezzo, invia una notifica. Ogni esecuzione carica la tua pagina in modo invisibile (stessa origine, stessi localStorage/IndexedDB, nessuno schermo, nessun annuncio) con ?appmint_background=1&appmint_task=<name>&appmint_run=<runId>, fa scattare appmint:background { name, runId } una volta caricata e aspetta done(runId, { ok }). Un'esecuzione viene interrotta dopo circa 9 minuti.
Restituisce: ogni metodo restituisce una Promise. register → { ok: true, name, intervalMinutes } oppure { 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 } (un'esecuzione non appena le condizioni lo permettono - per i test). done → true quando l'esecuzione è stata terminata. isBackgroundRun() e currentRun() sono sincroni. Richiede: seleziona Esegui il codice della pagina ad app chiusa (passaggio Accesso → Background). App sito web e offline/ZIP; le app documento e media non hanno codice di pagina da eseguire.
Una pagina, due ruoli:
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();Le pagine che si montano tardi (React, un percorso caricato in lazy) usano onRun, che scatta anche se l'esecuzione è già iniziata, oppure l'hook della pagina:
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()); };Gestisci:
AppMint.background.list().then(function (tasks) { console.table(tasks); });
AppMint.background.runNow('prices');
AppMint.background.unregister('prices');Note: Android decide il momento esatto; 15 minuti è l'intervallo più breve e il risparmio batteria può ritardare le esecuzioni. Con Avvio dopo il riavvio del telefono ogni attività viene eseguita anche una volta dopo un riavvio. Il WebToApk di un'esecuzione ha solo notify, showNotification, il buffer delle posizioni, i geofence, workEnqueueOnline, isBackgroundServiceRunning e questi metodi per le attività ; tutto il resto è assente (rileva la disponibilità ).
addGeofence bridge#
window.WebToApk.addGeofence(json: String): String
Aggiunge un geofence {id, lat, lng, radiusM, events, dwellMs?, notify?} → JSON {ok, status:'adding'} oppure {ok:false, reason}; il risultato è appmint:geofence-added.
Esempio
Un cerchio che fa scattare enter, exit e/o dwell con l'app chiusa, pubblicando facoltativamente una notifica. I geofence sopravvivono al riavvio del telefono (vengono aggiunti di nuovo automaticamente).
Restituisce: addGeofence → una stringa JSON { ok: true, status: 'adding', id } oppure { ok: false, reason } - not_enabled, invalid_id, invalid_position, invalid_radius (da 1 m a 100 km), invalid_events, location_permission_required, background_permission_required, foreign_origin. La risposta di Play services arriva come appmint:geofence-added (window.onAppMintGeofenceAdded) = { id, ok, reason? } (location_off, too_many_geofences - 100 per app, play_services_unavailable). Una transizione arriva come appmint:geofence (window.onAppMintGeofence) = { id, event: 'enter' | 'exit' | 'dwell', time, lat, lng, queued? } - queued: true quando è avvenuta con l'app chiusa e viene consegnata al prossimo avvio. removeGeofence → true se esisteva; getGeofences → un array JSON di ciò che hai aggiunto. Richiede: seleziona Geofence nella build e «Consenti sempre» (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');Rileva la disponibilità e segnala ogni rifiuto:
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;
}Elenca ciò che è registrato e rimuovine uno:
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): termina un'esecuzione in background; nell'app stessa è sempre false.
Esempio
Documentato insieme a WebToApk.backgroundRegister - l'esempio lì mostra anche questo.
I metodi diretti dietro AppMint.background - usa AppMint.background.register(...) (restituisce promise e gestisce l'evento di esecuzione). Elencati qui perché ogni metodo del bridge abbia un esempio.
Restituisce: backgroundRegister → JSON { ok: true, name, intervalMinutes } oppure { ok: false, reason } (not_enabled, invalid_name - lettere, cifre, _, -, fino a 64 - interval_below_15_minutes, invalid_interval, schedule_failed). backgroundUnregister → true se esisteva. backgroundList → array JSON. backgroundRunNow → JSON { ok, name } oppure { ok: false, reason: 'not_registered' }. backgroundDone(runId, resultJson) → true in un'esecuzione in background, sempre false nell'app. isBackgroundRun() → false nell'app, true in un'esecuzione in background. Richiede: seleziona Esegui il codice della pagina ad app chiusa.
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 }))
}Rileva la disponibilità e distingui l'esecuzione dell'app da quella in background:
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'
}Prova un'attività e fai pulizia:
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 bridge#
window.WebToApk.backgroundList(): String
AppMint.background.list(): array JSON {name, intervalMinutes, requiresNetwork, requiresCharging, registeredAt, lastRunAt, lastResult}.
Esempio
Documentato insieme a WebToApk.backgroundRegister - l'esempio lì mostra anche questo.
I metodi diretti dietro AppMint.background - usa AppMint.background.register(...) (restituisce promise e gestisce l'evento di esecuzione). Elencati qui perché ogni metodo del bridge abbia un esempio.
Restituisce: backgroundRegister → JSON { ok: true, name, intervalMinutes } oppure { ok: false, reason } (not_enabled, invalid_name - lettere, cifre, _, -, fino a 64 - interval_below_15_minutes, invalid_interval, schedule_failed). backgroundUnregister → true se esisteva. backgroundList → array JSON. backgroundRunNow → JSON { ok, name } oppure { ok: false, reason: 'not_registered' }. backgroundDone(runId, resultJson) → true in un'esecuzione in background, sempre false nell'app. isBackgroundRun() → false nell'app, true in un'esecuzione in background. Richiede: seleziona Esegui il codice della pagina ad app chiusa.
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 }))
}Rileva la disponibilità e distingui l'esecuzione dell'app da quella in background:
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'
}Prova un'attività e fai pulizia:
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 bridge#
window.WebToApk.backgroundRegister(name: String, optionsJson: String): String
AppMint.background.register(name, {intervalMinutes >= 15, requiresNetwork, requiresCharging}) → JSON {ok, name, intervalMinutes} oppure {ok:false, reason}.
Esempio
I metodi diretti dietro AppMint.background - usa AppMint.background.register(...) (restituisce promise e gestisce l'evento di esecuzione). Elencati qui perché ogni metodo del bridge abbia un esempio.
Restituisce: backgroundRegister → JSON { ok: true, name, intervalMinutes } oppure { ok: false, reason } (not_enabled, invalid_name - lettere, cifre, _, -, fino a 64 - interval_below_15_minutes, invalid_interval, schedule_failed). backgroundUnregister → true se esisteva. backgroundList → array JSON. backgroundRunNow → JSON { ok, name } oppure { ok: false, reason: 'not_registered' }. backgroundDone(runId, resultJson) → true in un'esecuzione in background, sempre false nell'app. isBackgroundRun() → false nell'app, true in un'esecuzione in background. Richiede: seleziona Esegui il codice della pagina ad app chiusa.
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 }))
}Rileva la disponibilità e distingui l'esecuzione dell'app da quella in background:
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'
}Prova un'attività e fai pulizia:
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 bridge#
window.WebToApk.backgroundRunNow(name: String): String
AppMint.background.runNow(name): un'esecuzione in background non appena le condizioni lo permettono.
Esempio
Documentato insieme a WebToApk.backgroundRegister - l'esempio lì mostra anche questo.
I metodi diretti dietro AppMint.background - usa AppMint.background.register(...) (restituisce promise e gestisce l'evento di esecuzione). Elencati qui perché ogni metodo del bridge abbia un esempio.
Restituisce: backgroundRegister → JSON { ok: true, name, intervalMinutes } oppure { ok: false, reason } (not_enabled, invalid_name - lettere, cifre, _, -, fino a 64 - interval_below_15_minutes, invalid_interval, schedule_failed). backgroundUnregister → true se esisteva. backgroundList → array JSON. backgroundRunNow → JSON { ok, name } oppure { ok: false, reason: 'not_registered' }. backgroundDone(runId, resultJson) → true in un'esecuzione in background, sempre false nell'app. isBackgroundRun() → false nell'app, true in un'esecuzione in background. Richiede: seleziona Esegui il codice della pagina ad app chiusa.
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 }))
}Rileva la disponibilità e distingui l'esecuzione dell'app da quella in background:
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'
}Prova un'attività e fai pulizia:
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 bridge#
window.WebToApk.backgroundUnregister(name: String): Boolean
AppMint.background.unregister(name); false quando nessuna attività aveva quel nome.
Esempio
Documentato insieme a WebToApk.backgroundRegister - l'esempio lì mostra anche questo.
I metodi diretti dietro AppMint.background - usa AppMint.background.register(...) (restituisce promise e gestisce l'evento di esecuzione). Elencati qui perché ogni metodo del bridge abbia un esempio.
Restituisce: backgroundRegister → JSON { ok: true, name, intervalMinutes } oppure { ok: false, reason } (not_enabled, invalid_name - lettere, cifre, _, -, fino a 64 - interval_below_15_minutes, invalid_interval, schedule_failed). backgroundUnregister → true se esisteva. backgroundList → array JSON. backgroundRunNow → JSON { ok, name } oppure { ok: false, reason: 'not_registered' }. backgroundDone(runId, resultJson) → true in un'esecuzione in background, sempre false nell'app. isBackgroundRun() → false nell'app, true in un'esecuzione in background. Richiede: seleziona Esegui il codice della pagina ad app chiusa.
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 }))
}Rileva la disponibilità e distingui l'esecuzione dell'app da quella in background:
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'
}Prova un'attività e fai pulizia:
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 bridge#
window.WebToApk.clearBufferedLocations(): Boolean
Svuota il buffer che legge getBufferedLocations (chiamalo dopo aver salvato i punti).
Esempio
Documentato insieme a WebToApk.getBufferedLocations - l'esempio lì mostra anche questo.
Le posizioni registrate mentre la tua pagina era assente (app in secondo piano o chiusa), dalla più vecchia, fino a 10.000.
Restituisce: getBufferedLocations() → un array JSON in stringa di { type: 'fix', lat, lng, accuracy, time, …, background: true }; '[]' quando la posizione in background è disattivata. clearBufferedLocations() → true quando svuotato. Richiede: Posizione in background nella build.
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(); });Avvia il tracciamento, poi gestisci un rifiuto. La chiamata di avvio risponde con una stringa 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, ...
}Le posizioni in tempo reale mentre la pagina è aperta arrivano come appmint:location; il buffer contiene il resto.
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
Svuota la cronologia delle transizioni che legge getGeofenceEvents.
Esempio
Documentato insieme a WebToApk.getGeofenceEvents - l'esempio lì mostra anche questo.
Le ultime 100 transizioni di geofence, dalla più vecchia - comprese quelle avvenute mentre l'app era chiusa. Leggile all'avvio se la tua pagina si monta tardi e potrebbe perdere gli eventi appmint:geofence in coda.
Restituisce: getGeofenceEvents() → un array JSON in stringa di { id, event, time, lat, lng }; clearGeofenceEvents() → true. Richiede: Geofence nella build.
if (window.WebToApk && WebToApk.getGeofenceEvents) {
JSON.parse(WebToApk.getGeofenceEvents()).forEach(function (ev) {
logVisit(ev.id, ev.event, ev.time);
});
WebToApk.clearGeofenceEvents();
}Aggiungi un geofence, poi osserva le transizioni mentre la pagina è aperta.
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);
});Elenca ciò che è registrato e rimuovine uno quando l'utente lascia una funzione.
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
Array JSON delle posizioni registrate mentre la pagina era assente, dalla più vecchia (fino a 10.000).
Esempio
Le posizioni registrate mentre la tua pagina era assente (app in secondo piano o chiusa), dalla più vecchia, fino a 10.000.
Restituisce: getBufferedLocations() → un array JSON in stringa di { type: 'fix', lat, lng, accuracy, time, …, background: true }; '[]' quando la posizione in background è disattivata. clearBufferedLocations() → true quando svuotato. Richiede: Posizione in background nella build.
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(); });Avvia il tracciamento, poi gestisci un rifiuto. La chiamata di avvio risponde con una stringa 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, ...
}Le posizioni in tempo reale mentre la pagina è aperta arrivano come appmint:location; il buffer contiene il resto.
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
Array JSON delle ultime 100 transizioni di geofence {id, event, time, lat, lng}, dalla più vecchia.
Esempio
Le ultime 100 transizioni di geofence, dalla più vecchia - comprese quelle avvenute mentre l'app era chiusa. Leggile all'avvio se la tua pagina si monta tardi e potrebbe perdere gli eventi appmint:geofence in coda.
Restituisce: getGeofenceEvents() → un array JSON in stringa di { id, event, time, lat, lng }; clearGeofenceEvents() → true. Richiede: Geofence nella build.
if (window.WebToApk && WebToApk.getGeofenceEvents) {
JSON.parse(WebToApk.getGeofenceEvents()).forEach(function (ev) {
logVisit(ev.id, ev.event, ev.time);
});
WebToApk.clearGeofenceEvents();
}Aggiungi un geofence, poi osserva le transizioni mentre la pagina è aperta.
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);
});Elenca ciò che è registrato e rimuovine uno quando l'utente lascia una funzione.
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
Array JSON dei geofence che questa app ha aggiunto (sopravvivono ai riavvii).
Esempio
Documentato insieme a WebToApk.addGeofence - l'esempio lì mostra anche questo.
Un cerchio che fa scattare enter, exit e/o dwell con l'app chiusa, pubblicando facoltativamente una notifica. I geofence sopravvivono al riavvio del telefono (vengono aggiunti di nuovo automaticamente).
Restituisce: addGeofence → una stringa JSON { ok: true, status: 'adding', id } oppure { ok: false, reason } - not_enabled, invalid_id, invalid_position, invalid_radius (da 1 m a 100 km), invalid_events, location_permission_required, background_permission_required, foreign_origin. La risposta di Play services arriva come appmint:geofence-added (window.onAppMintGeofenceAdded) = { id, ok, reason? } (location_off, too_many_geofences - 100 per app, play_services_unavailable). Una transizione arriva come appmint:geofence (window.onAppMintGeofence) = { id, event: 'enter' | 'exit' | 'dwell', time, lat, lng, queued? } - queued: true quando è avvenuta con l'app chiusa e viene consegnata al prossimo avvio. removeGeofence → true se esisteva; getGeofences → un array JSON di ciò che hai aggiunto. Richiede: seleziona Geofence nella build e «Consenti sempre» (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');Rileva la disponibilità e segnala ogni rifiuto:
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;
}Elenca ciò che è registrato e rimuovine uno:
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}.
Esempio
Documentato insieme a WebToApk.requestBackgroundLocation - l'esempio lì mostra anche questo.
Chiede la posizione, poi «Consenti sempre» (Android 10+ li chiede separatamente; su Android 11+ il secondo passaggio apre la pagina delle impostazioni di posizione dell'app). I geofence ne hanno bisogno; serve anche per riavviare il tracciamento della posizione dopo un riavvio del telefono.
Restituisce: niente direttamente; la risposta arriva come appmint:location-permission (e window.onAppMintLocationPermission(detail)) = { foreground: 'granted' | 'denied', background: 'granted' | 'denied', precise: boolean, error?: 'not_enabled' }. getLocationPermissionState() → lo stesso oggetto come stringa JSON, senza chiedere. Richiede: Posizione in background o Geofence nella build.
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.');
});Controlla prima lo stato e spiega cosa deve toccare l'utente:
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.');
}Richiedi l'autorizzazione, poi aggiungi un geofence una volta concessa:
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
Qui false: la pagina dell'app stessa non è mai un'esecuzione in background (un'esecuzione headless risponde true).
Esempio
Documentato insieme a WebToApk.backgroundRegister - l'esempio lì mostra anche questo.
I metodi diretti dietro AppMint.background - usa AppMint.background.register(...) (restituisce promise e gestisce l'evento di esecuzione). Elencati qui perché ogni metodo del bridge abbia un esempio.
Restituisce: backgroundRegister → JSON { ok: true, name, intervalMinutes } oppure { ok: false, reason } (not_enabled, invalid_name - lettere, cifre, _, -, fino a 64 - interval_below_15_minutes, invalid_interval, schedule_failed). backgroundUnregister → true se esisteva. backgroundList → array JSON. backgroundRunNow → JSON { ok, name } oppure { ok: false, reason: 'not_registered' }. backgroundDone(runId, resultJson) → true in un'esecuzione in background, sempre false nell'app. isBackgroundRun() → false nell'app, true in un'esecuzione in background. Richiede: seleziona Esegui il codice della pagina ad app chiusa.
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 }))
}Rileva la disponibilità e distingui l'esecuzione dell'app da quella in background:
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'
}Prova un'attività e fai pulizia:
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 bridge#
window.WebToApk.isBackgroundServiceRunning(): Boolean
Se il servizio in primo piano dell'app è in esecuzione (per il keep-running o per la posizione in background).
Esempio
Documentato insieme a WebToApk.startBackgroundService - l'esempio lì mostra anche questo.
Mantiene la tua app in esecuzione mentre l'utente è assente: un vero servizio in primo piano di Android con una notifica. Finché è attivo la pagina non viene messa in pausa, quindi il suo JavaScript, <audio> e la musica di sottofondo dell'app continuano (Android rallenta comunque i timer di una pagina che nessuno può vedere).
Restituisce: startBackgroundService → una stringa JSON { ok: true, status: 'starting', type, notificationShown } oppure { ok: false, reason } - motivo not_enabled, invalid_options, type_not_declared, invalid_progress, location_permission_required, start_not_allowed. updateBackgroundService → { ok: true } oppure { ok: false, reason: 'not_running' }. stopBackgroundService / isBackgroundServiceRunning → true / false. I cambi di stato arrivano come appmint:background-service (e window.onAppMintBackgroundService(detail)): { state: 'running' | 'stopped' | 'failed' | 'not_restarted', reason?, types?, restored?, queued? }. Richiede: seleziona Mantieni in esecuzione in background (passaggio Accesso → Background) e scegli il tipo di servizio. Su Android 13+ la notifica è visibile solo dopo che l'utente ha consentito le notifiche (altrimenti notificationShown: false - il servizio funziona comunque).
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 + ')');
});Gestisci le risposte che significano che la build o il telefono non possono farlo:
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);
}Combinalo con un ciclo di caricamento: resta in esecuzione solo finché la coda ha lavoro.
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();
}Note: il tipo deve essere uno di quelli dichiarati dalla build (quello scelto nella procedura guidata, più location quando la posizione in background è attiva). Chiudere l'app dai recenti ferma un servizio keep-running a meno che tu non passi keepAfterSwipe: true (la pagina stessa a quel punto non c'è più). Play Console chiede una dichiarazione del servizio in primo piano con un breve video per ogni tipo dichiarato.
isIgnoringBatteryOptimizations bridge#
window.WebToApk.isIgnoringBatteryOptimizations(): Boolean
Se l'utente ha esentato questa app dall'ottimizzazione della batteria (Doze app standby).
Esempio
Controlla e chiede l'esenzione dall'ottimizzazione della batteria, così Doze non rimanda il tuo lavoro in background.
Restituisce: isIgnoringBatteryOptimizations() → true / false. requestIgnoreBatteryOptimizations() → 'already_ignoring', 'dialog_opened' (la finestra Consenti/Nega del sistema - solo quando la build ha selezionato Chiedi direttamente di saltare l'ottimizzazione della batteria), 'settings_opened' (l'elenco dell'ottimizzazione della batteria; nessuna autorizzazione, nessuna dichiarazione Play) oppure 'unavailable'. Richiede: niente per l'elenco delle impostazioni. La finestra diretta dichiara REQUEST_IGNORE_BATTERY_OPTIMIZATIONS, che Play accetta solo quando è la funzione principale dell'app.
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".');
});Di' cosa è successo per ogni risposta.
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);
}Abbinalo al tracciamento in background, dato che Doze lo metterebbe in pausa.
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
Rimuove il geofence con questo id; false quando non ce n'era.
Esempio
Documentato insieme a WebToApk.addGeofence - l'esempio lì mostra anche questo.
Un cerchio che fa scattare enter, exit e/o dwell con l'app chiusa, pubblicando facoltativamente una notifica. I geofence sopravvivono al riavvio del telefono (vengono aggiunti di nuovo automaticamente).
Restituisce: addGeofence → una stringa JSON { ok: true, status: 'adding', id } oppure { ok: false, reason } - not_enabled, invalid_id, invalid_position, invalid_radius (da 1 m a 100 km), invalid_events, location_permission_required, background_permission_required, foreign_origin. La risposta di Play services arriva come appmint:geofence-added (window.onAppMintGeofenceAdded) = { id, ok, reason? } (location_off, too_many_geofences - 100 per app, play_services_unavailable). Una transizione arriva come appmint:geofence (window.onAppMintGeofence) = { id, event: 'enter' | 'exit' | 'dwell', time, lat, lng, queued? } - queued: true quando è avvenuta con l'app chiusa e viene consegnata al prossimo avvio. removeGeofence → true se esisteva; getGeofences → un array JSON di ciò che hai aggiunto. Richiede: seleziona Geofence nella build e «Consenti sempre» (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');Rileva la disponibilità e segnala ogni rifiuto:
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;
}Elenca ciò che è registrato e rimuovine uno:
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()
Chiede la posizione, poi «Consenti sempre»; il risultato arriva come appmint:location-permission.
Esempio
Chiede la posizione, poi «Consenti sempre» (Android 10+ li chiede separatamente; su Android 11+ il secondo passaggio apre la pagina delle impostazioni di posizione dell'app). I geofence ne hanno bisogno; serve anche per riavviare il tracciamento della posizione dopo un riavvio del telefono.
Restituisce: niente direttamente; la risposta arriva come appmint:location-permission (e window.onAppMintLocationPermission(detail)) = { foreground: 'granted' | 'denied', background: 'granted' | 'denied', precise: boolean, error?: 'not_enabled' }. getLocationPermissionState() → lo stesso oggetto come stringa JSON, senza chiedere. Richiede: Posizione in background o Geofence nella build.
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.');
});Controlla prima lo stato e spiega cosa deve toccare l'utente:
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.');
}Richiedi l'autorizzazione, poi aggiungi un geofence una volta concessa:
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
Chiede l'esenzione dalla batteria: 'already_ignoring' | 'dialog_opened' | 'settings_opened' | 'unavailable'.
Esempio
Documentato insieme a WebToApk.isIgnoringBatteryOptimizations - l'esempio lì mostra anche questo.
Controlla e chiede l'esenzione dall'ottimizzazione della batteria, così Doze non rimanda il tuo lavoro in background.
Restituisce: isIgnoringBatteryOptimizations() → true / false. requestIgnoreBatteryOptimizations() → 'already_ignoring', 'dialog_opened' (la finestra Consenti/Nega del sistema - solo quando la build ha selezionato Chiedi direttamente di saltare l'ottimizzazione della batteria), 'settings_opened' (l'elenco dell'ottimizzazione della batteria; nessuna autorizzazione, nessuna dichiarazione Play) oppure 'unavailable'. Richiede: niente per l'elenco delle impostazioni. La finestra diretta dichiara REQUEST_IGNORE_BATTERY_OPTIMIZATIONS, che Play accetta solo quando è la funzione principale dell'app.
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".');
});Di' cosa è successo per ogni risposta.
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);
}Abbinalo al tracciamento in background, dato che Doze lo metterebbe in pausa.
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
Avvia il servizio in primo piano dell'app: {title, text, type?, progress?, stopLabel?, keepAfterSwipe?} → JSON {ok, status, type, notificationShown} oppure {ok:false, reason}.
Esempio
Mantiene la tua app in esecuzione mentre l'utente è assente: un vero servizio in primo piano di Android con una notifica. Finché è attivo la pagina non viene messa in pausa, quindi il suo JavaScript, <audio> e la musica di sottofondo dell'app continuano (Android rallenta comunque i timer di una pagina che nessuno può vedere).
Restituisce: startBackgroundService → una stringa JSON { ok: true, status: 'starting', type, notificationShown } oppure { ok: false, reason } - motivo not_enabled, invalid_options, type_not_declared, invalid_progress, location_permission_required, start_not_allowed. updateBackgroundService → { ok: true } oppure { ok: false, reason: 'not_running' }. stopBackgroundService / isBackgroundServiceRunning → true / false. I cambi di stato arrivano come appmint:background-service (e window.onAppMintBackgroundService(detail)): { state: 'running' | 'stopped' | 'failed' | 'not_restarted', reason?, types?, restored?, queued? }. Richiede: seleziona Mantieni in esecuzione in background (passaggio Accesso → Background) e scegli il tipo di servizio. Su Android 13+ la notifica è visibile solo dopo che l'utente ha consentito le notifiche (altrimenti notificationShown: false - il servizio funziona comunque).
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 + ')');
});Gestisci le risposte che significano che la build o il telefono non possono farlo:
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);
}Combinalo con un ciclo di caricamento: resta in esecuzione solo finché la coda ha lavoro.
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();
}Note: il tipo deve essere uno di quelli dichiarati dalla build (quello scelto nella procedura guidata, più location quando la posizione in background è attiva). Chiudere l'app dai recenti ferma un servizio keep-running a meno che tu non passi keepAfterSwipe: true (la pagina stessa a quel punto non c'è più). Play Console chiede una dichiarazione del servizio in primo piano con un breve video per ogni tipo dichiarato.
startLocationUpdates bridge#
window.WebToApk.startLocationUpdates(optionsJson: String): String
Aggiornamenti di posizione {intervalMs, minDistanceM, accuracy, background, uploadUrl?, headers?} → JSON {ok, background, backgroundPermission, precise} oppure {ok:false, reason}; le posizioni arrivano come appmint:location.
Esempio
Aggiornamenti di posizione che continuano ad arrivare mentre l'app è in secondo piano (un tracker per la corsa, un'app di consegne). Con background: true (il valore predefinito) vengono eseguiti dentro il servizio in primo piano dell'app. Ogni posizione va alla pagina come appmint:location finché la pagina c'è, in un buffer (getBufferedLocations()) quando non c'è e - se fornisci un uploadUrl - al tuo server come POST JSON dal codice nativo, così il tracciamento funziona anche senza la pagina.
Restituisce: una stringa JSON { ok: true, background, backgroundPermission, precise } oppure { ok: false, reason } - not_enabled, invalid_options, invalid_interval (da 1 s a 24 h), invalid_distance, invalid_accuracy, invalid_upload_url, location_permission_required, start_not_allowed, foreign_origin. stopLocationUpdates() → true se qualcosa era in esecuzione. Posizioni: appmint:location / window.onAppMintLocation(detail) = { type: 'fix', lat, lng, accuracy, altitude?, speed?, bearing?, time, provider, background }, oppure { type: 'error', reason: 'play_services_unavailable' | 'location_unavailable' | 'location_permission_required' }. Richiede: seleziona Posizione in background (passaggio Accesso → Background) e prima l'autorizzazione di posizione dell'utente.
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(); }Segnala un'opzione errata o un'autorizzazione mancante con il 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 abbinamento naturale con il buffer: fermati a fine turno e carica ciò che è stato registrato mentre la pagina era assente.
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();
}
}Note: un servizio di posizione avviato dall'app continua a ricevere posizioni in background senza «Consenti sempre»; riavviarlo dopo un riavvio del telefono la richiede (requestBackgroundLocation()). Un POST fallito viene passato a WorkManager e inviato quando la rete torna. I telefoni senza Google Play services rispondono play_services_unavailable. Play Console richiede una dichiarazione per la posizione in background.
stopBackgroundService bridge#
window.WebToApk.stopBackgroundService(): Boolean
Ferma il servizio avviato da startBackgroundService (la posizione in background, se attiva, continua).
Esempio
Documentato insieme a WebToApk.startBackgroundService - l'esempio lì mostra anche questo.
Mantiene la tua app in esecuzione mentre l'utente è assente: un vero servizio in primo piano di Android con una notifica. Finché è attivo la pagina non viene messa in pausa, quindi il suo JavaScript, <audio> e la musica di sottofondo dell'app continuano (Android rallenta comunque i timer di una pagina che nessuno può vedere).
Restituisce: startBackgroundService → una stringa JSON { ok: true, status: 'starting', type, notificationShown } oppure { ok: false, reason } - motivo not_enabled, invalid_options, type_not_declared, invalid_progress, location_permission_required, start_not_allowed. updateBackgroundService → { ok: true } oppure { ok: false, reason: 'not_running' }. stopBackgroundService / isBackgroundServiceRunning → true / false. I cambi di stato arrivano come appmint:background-service (e window.onAppMintBackgroundService(detail)): { state: 'running' | 'stopped' | 'failed' | 'not_restarted', reason?, types?, restored?, queued? }. Richiede: seleziona Mantieni in esecuzione in background (passaggio Accesso → Background) e scegli il tipo di servizio. Su Android 13+ la notifica è visibile solo dopo che l'utente ha consentito le notifiche (altrimenti notificationShown: false - il servizio funziona comunque).
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 + ')');
});Gestisci le risposte che significano che la build o il telefono non possono farlo:
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);
}Combinalo con un ciclo di caricamento: resta in esecuzione solo finché la coda ha lavoro.
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();
}Note: il tipo deve essere uno di quelli dichiarati dalla build (quello scelto nella procedura guidata, più location quando la posizione in background è attiva). Chiudere l'app dai recenti ferma un servizio keep-running a meno che tu non passi keepAfterSwipe: true (la pagina stessa a quel punto non c'è più). Play Console chiede una dichiarazione del servizio in primo piano con un breve video per ogni tipo dichiarato.
stopLocationUpdates bridge#
window.WebToApk.stopLocationUpdates(): Boolean
Ferma gli aggiornamenti di posizione avviati da startLocationUpdates; false quando non ce n'erano in corso.
Esempio
Documentato insieme a WebToApk.startLocationUpdates - l'esempio lì mostra anche questo.
Aggiornamenti di posizione che continuano ad arrivare mentre l'app è in secondo piano (un tracker per la corsa, un'app di consegne). Con background: true (il valore predefinito) vengono eseguiti dentro il servizio in primo piano dell'app. Ogni posizione va alla pagina come appmint:location finché la pagina c'è, in un buffer (getBufferedLocations()) quando non c'è e - se fornisci un uploadUrl - al tuo server come POST JSON dal codice nativo, così il tracciamento funziona anche senza la pagina.
Restituisce: una stringa JSON { ok: true, background, backgroundPermission, precise } oppure { ok: false, reason } - not_enabled, invalid_options, invalid_interval (da 1 s a 24 h), invalid_distance, invalid_accuracy, invalid_upload_url, location_permission_required, start_not_allowed, foreign_origin. stopLocationUpdates() → true se qualcosa era in esecuzione. Posizioni: appmint:location / window.onAppMintLocation(detail) = { type: 'fix', lat, lng, accuracy, altitude?, speed?, bearing?, time, provider, background }, oppure { type: 'error', reason: 'play_services_unavailable' | 'location_unavailable' | 'location_permission_required' }. Richiede: seleziona Posizione in background (passaggio Accesso → Background) e prima l'autorizzazione di posizione dell'utente.
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(); }Segnala un'opzione errata o un'autorizzazione mancante con il 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 abbinamento naturale con il buffer: fermati a fine turno e carica ciò che è stato registrato mentre la pagina era assente.
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();
}
}Note: un servizio di posizione avviato dall'app continua a ricevere posizioni in background senza «Consenti sempre»; riavviarlo dopo un riavvio del telefono la richiede (requestBackgroundLocation()). Un POST fallito viene passato a WorkManager e inviato quando la rete torna. I telefoni senza Google Play services rispondono play_services_unavailable. Play Console richiede una dichiarazione per la posizione in background.
updateBackgroundService bridge#
window.WebToApk.updateBackgroundService(optionsJson: String): String
Modifica la notifica del servizio in esecuzione (stesse opzioni di startBackgroundService) → JSON {ok} oppure {ok:false, reason:'not_running'}.
Esempio
Documentato insieme a WebToApk.startBackgroundService - l'esempio lì mostra anche questo.
Mantiene la tua app in esecuzione mentre l'utente è assente: un vero servizio in primo piano di Android con una notifica. Finché è attivo la pagina non viene messa in pausa, quindi il suo JavaScript, <audio> e la musica di sottofondo dell'app continuano (Android rallenta comunque i timer di una pagina che nessuno può vedere).
Restituisce: startBackgroundService → una stringa JSON { ok: true, status: 'starting', type, notificationShown } oppure { ok: false, reason } - motivo not_enabled, invalid_options, type_not_declared, invalid_progress, location_permission_required, start_not_allowed. updateBackgroundService → { ok: true } oppure { ok: false, reason: 'not_running' }. stopBackgroundService / isBackgroundServiceRunning → true / false. I cambi di stato arrivano come appmint:background-service (e window.onAppMintBackgroundService(detail)): { state: 'running' | 'stopped' | 'failed' | 'not_restarted', reason?, types?, restored?, queued? }. Richiede: seleziona Mantieni in esecuzione in background (passaggio Accesso → Background) e scegli il tipo di servizio. Su Android 13+ la notifica è visibile solo dopo che l'utente ha consentito le notifiche (altrimenti notificationShown: false - il servizio funziona comunque).
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 + ')');
});Gestisci le risposte che significano che la build o il telefono non possono farlo:
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);
}Combinalo con un ciclo di caricamento: resta in esecuzione solo finché la coda ha lavoro.
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();
}Note: il tipo deve essere uno di quelli dichiarati dalla build (quello scelto nella procedura guidata, più location quando la posizione in background è attiva). Chiudere l'app dai recenti ferma un servizio keep-running a meno che tu non passi keepAfterSwipe: true (la pagina stessa a quel punto non c'è più). Play Console chiede una dichiarazione del servizio in primo piano con un breve video per ogni tipo dichiarato.