Crea APK gratis

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 existed

backgroundList 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 existed

backgroundRegister 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 existed

backgroundRunNow 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 existed

backgroundUnregister 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 existed

clearBufferedLocations 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 existed

isBackgroundServiceRunning 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.

Generato dal runtime dell'app e dai suoi file di esempio a ogni build della documentazione. Leggilo in Markdown · Tutte le famiglie.

Verificato rispetto al bridge distribuito il 22/10/2026.