Crear APK gratis

API del puente de JavaScript

Notificaciones en el dispositivo

Publicar y programar sin servidor, el permiso POST_NOTIFICATIONS, alarmas exactas y sonidos personalizados.

AppMint.enableNotifications ayudante#

AppMint.enableNotifications()

Ejemplo

"Turn on notifications" en una sola llamada, para usuarios que rechazaron u omitieron el aviso de notificaciones de Android: el diálogo de permiso mientras Android todavía pueda mostrarlo, y si no los ajustes de notificaciones de esta app, y el nuevo estado cuando el usuario vuelve.

Devuelve: AppMint.enableNotifications(): una Promise que siempre se resuelve {state, changed, openedSettings}; state es 'granted' | 'prompt' | 'blocked' | 'unavailable'. AppMint.getNotificationState(): una Promise de ese estado. AppMint.openNotificationSettings(): una Promise de true cuando se abrió la pantalla de ajustes. Requiere: llama a enableNotifications() desde un botón que el usuario tocó; las notificaciones necesitan Notifications (o Push / Reminders) en la compilación.

// A banner that only shows while notifications are off, and turns them on in one tap.
async function setupNotifBanner() {
  if (!(window.AppMint && AppMint.enableNotifications)) return;   // a browser: no banner
  var banner = document.getElementById('notif-banner');
  var state = await AppMint.getNotificationState();
  banner.hidden = !(state === 'prompt' || state === 'blocked');
  document.getElementById('notif-turn-on').onclick = async function () {
    var r = await AppMint.enableNotifications();
    banner.hidden = r.state === 'granted';
    if (r.state === 'blocked') showToast('Switch on "Allow notifications" to get updates.');
  };
}
setupNotifBanner();

Un simple enlace "Notification settings" en tu propia página de ajustes:

document.getElementById('notif-settings').onclick = async function () {
  var opened = window.AppMint && AppMint.openNotificationSettings && await AppMint.openNotificationSettings();
  if (!opened) showToast('Open Settings > Apps > this app > Notifications.');
};

Notas: un botón de push o de mensaje dentro de la app cuyo enlace es appmint://notifications/enable ejecuta el mismo flujo sin ningún código de página (la página escucha appmint:notifications). La audiencia push de la app ("Notifications on / off") se actualiza en cuanto el usuario las activa.

AppMint.getNotificationState ayudante#

window.AppMint.getNotificationState

Ejemplo

Documentado junto con AppMint.enableNotifications: el ejemplo de allí también muestra este.

"Turn on notifications" en una sola llamada, para usuarios que rechazaron u omitieron el aviso de notificaciones de Android: el diálogo de permiso mientras Android todavía pueda mostrarlo, y si no los ajustes de notificaciones de esta app, y el nuevo estado cuando el usuario vuelve.

Devuelve: AppMint.enableNotifications(): una Promise que siempre se resuelve {state, changed, openedSettings}; state es 'granted' | 'prompt' | 'blocked' | 'unavailable'. AppMint.getNotificationState(): una Promise de ese estado. AppMint.openNotificationSettings(): una Promise de true cuando se abrió la pantalla de ajustes. Requiere: llama a enableNotifications() desde un botón que el usuario tocó; las notificaciones necesitan Notifications (o Push / Reminders) en la compilación.

// A banner that only shows while notifications are off, and turns them on in one tap.
async function setupNotifBanner() {
  if (!(window.AppMint && AppMint.enableNotifications)) return;   // a browser: no banner
  var banner = document.getElementById('notif-banner');
  var state = await AppMint.getNotificationState();
  banner.hidden = !(state === 'prompt' || state === 'blocked');
  document.getElementById('notif-turn-on').onclick = async function () {
    var r = await AppMint.enableNotifications();
    banner.hidden = r.state === 'granted';
    if (r.state === 'blocked') showToast('Switch on "Allow notifications" to get updates.');
  };
}
setupNotifBanner();

Un simple enlace "Notification settings" en tu propia página de ajustes:

document.getElementById('notif-settings').onclick = async function () {
  var opened = window.AppMint && AppMint.openNotificationSettings && await AppMint.openNotificationSettings();
  if (!opened) showToast('Open Settings > Apps > this app > Notifications.');
};

Notas: un botón de push o de mensaje dentro de la app cuyo enlace es appmint://notifications/enable ejecuta el mismo flujo sin ningún código de página (la página escucha appmint:notifications). La audiencia push de la app ("Notifications on / off") se actualiza en cuanto el usuario las activa.

AppMint.openNotificationSettings ayudante#

window.AppMint.openNotificationSettings

Ejemplo

Documentado junto con AppMint.enableNotifications: el ejemplo de allí también muestra este.

"Turn on notifications" en una sola llamada, para usuarios que rechazaron u omitieron el aviso de notificaciones de Android: el diálogo de permiso mientras Android todavía pueda mostrarlo, y si no los ajustes de notificaciones de esta app, y el nuevo estado cuando el usuario vuelve.

Devuelve: AppMint.enableNotifications(): una Promise que siempre se resuelve {state, changed, openedSettings}; state es 'granted' | 'prompt' | 'blocked' | 'unavailable'. AppMint.getNotificationState(): una Promise de ese estado. AppMint.openNotificationSettings(): una Promise de true cuando se abrió la pantalla de ajustes. Requiere: llama a enableNotifications() desde un botón que el usuario tocó; las notificaciones necesitan Notifications (o Push / Reminders) en la compilación.

// A banner that only shows while notifications are off, and turns them on in one tap.
async function setupNotifBanner() {
  if (!(window.AppMint && AppMint.enableNotifications)) return;   // a browser: no banner
  var banner = document.getElementById('notif-banner');
  var state = await AppMint.getNotificationState();
  banner.hidden = !(state === 'prompt' || state === 'blocked');
  document.getElementById('notif-turn-on').onclick = async function () {
    var r = await AppMint.enableNotifications();
    banner.hidden = r.state === 'granted';
    if (r.state === 'blocked') showToast('Switch on "Allow notifications" to get updates.');
  };
}
setupNotifBanner();

Un simple enlace "Notification settings" en tu propia página de ajustes:

document.getElementById('notif-settings').onclick = async function () {
  var opened = window.AppMint && AppMint.openNotificationSettings && await AppMint.openNotificationSettings();
  if (!opened) showToast('Open Settings > Apps > this app > Notifications.');
};

Notas: un botón de push o de mensaje dentro de la app cuyo enlace es appmint://notifications/enable ejecuta el mismo flujo sin ningún código de página (la página escucha appmint:notifications). La audiencia push de la app ("Notifications on / off") se actualiza en cuanto el usuario las activa.

navigator.setAppBadge estándar web#

navigator.setAppBadge(count) / clearAppBadge()

Ejemplo

La API Badging estándar: un número en el icono del launcher de la app, como un contador de no leídos.

Devuelve: una Promise que se resuelve cuando se establece la insignia; TypeError para un contador negativo o no numérico. Requiere: nada que activar. Existe solo en teléfonos cuyo launcher muestra contadores (Samsung, Huawei, Xiaomi, OPPO, Sony, …); en launchers estilo Pixel 'setAppBadge' in navigator es false.

function updateBadge(unread) {
  if (!('setAppBadge' in navigator)) return;      // this launcher cannot show a count
  if (unread > 0) navigator.setAppBadge(unread);
  else navigator.clearAppBadge();
}

updateBadge(messages.filter((m) => !m.read).length);

Borra la insignia siempre que el usuario esté mirando la app, y maneja una actualización rechazada:

function setUnread(count) {
  if (!('setAppBadge' in navigator)) return;
  navigator.setAppBadge(count).catch(function (err) {
    console.warn('badge not updated:', err.name);   // NotAllowedError or TypeError
  });
}

document.addEventListener('visibilitychange', function () {
  if (!document.hidden && 'clearAppBadge' in navigator) {
    navigator.clearAppBadge().catch(function () {});
  }
});

Muestra una simple bandera de "algo nuevo" cuando una sincronización encuentra cambios pero no tiene un conteo exacto:

async function afterSync(result) {
  if (!('setAppBadge' in navigator)) return;
  try {
    if (result.newCount > 0) await navigator.setAppBadge(result.newCount);
    else if (result.hasChanges) await navigator.setAppBadge();   // flag badge, shown as 1
    else await navigator.clearAppBadge();
  } catch (e) {
    console.warn('badge update failed', e);
  }
}

Notas: setAppBadge() sin número es la insignia "flag" del estándar, que se muestra como 1 en un launcher con contador. El contador sobrevive a un reinicio de la app. El mismo almacén respalda badge.set() en @/lib/appmintNative, así que ambos siempre coinciden.

Notification estándar web#

new Notification(title, options) / Notification.requestPermission()

Ejemplo

La API web estándar de notificaciones funciona dentro de la app: new Notification() muestra una notificación real de Android, close() la elimina y Notification.requestPermission() muestra el diálogo de permiso de Android y espera la respuesta del usuario.

Devuelve: Notification.permission es 'granted', 'denied' o 'default', leído en vivo del teléfono (un cambio en Ajustes se muestra sin recargar). requestPermission() devuelve una Promise de 'granted' o 'denied' (la forma con callback también funciona). Una nueva notificación dispara show al publicarse, o error cuando falta el permiso. Requiere: activa Notifications en el paso de Acceso (Paso 4) al compilar.

async function notifyWhenSaved() {
  if (!('Notification' in window)) return;
  let perm = Notification.permission;
  if (perm === 'default') perm = await Notification.requestPermission();
  if (perm !== 'granted') {
    showMessage('Turn on notifications for this app in Settings to get alerts.');
    return;
  }
  const n = new Notification('Saved', {
    body: 'Your note was saved',
    icon: 'https://example.com/avatar.png',   // shown as the large icon
    image: 'https://example.com/note.jpg',    // big picture
    tag: 'note'                               // same tag replaces the old one
  });
  n.onshow = function () { console.log('posted'); };
  n.onerror = function () { console.log('not posted'); };
  setTimeout(function () { n.close(); }, 60000);   // removes it from the shade
}

document.getElementById('save').addEventListener('click', notifyWhenSaved);

Forma con callback (código antiguo):

Notification.requestPermission(function (result) {
  if (result === 'granted') new Notification('Welcome!', { body: 'Alerts are on', silent: true });
});

El estilo service worker (código PWA) también publica a través de la app, pero solo donde el WebView no tiene navigator.serviceWorker (por ejemplo páginas file:// o http://):

if ('serviceWorker' in navigator && Notification.permission === 'granted') {
  navigator.serviceWorker.ready.then(function (reg) {
    return reg.showNotification('Backup done', { body: '12 files', tag: 'backup' });
  });
}

Un toque dispara click en la notificación: la app pasa al primer plano y la página lo recibe. Si la app estaba cerrada (el toque la inició), el objeto ya no existe, así que la ventana recibe en su lugar un evento notificationclick, con la forma de uno de un service worker - event.notification.tag / title / body / data:

const n = new Notification('Order shipped', { body: '#7', tag: 'order-7', data: { orderId: 7 } });
n.onclick = () => openOrder(7);                         // the app was running
window.addEventListener('notificationclick', (e) => {   // the tap started the app
  if (e.notification.data && e.notification.data.orderId) openOrder(e.notification.data.orderId);
});

Notas: opciones usadas: body, icon, image, tag, silent, data (JSON, menos de 16 KB). El toque llega a la página a través de las pantallas de bienvenida y de PIN (evento detrás: appmint:notification-click). En Android 12 y anteriores no hay permiso en tiempo de ejecución, así que es 'granted' a menos que el usuario haya desactivado las notificaciones. Web Push (pushManager.subscribe) no es compatible; usa AppMint Push. Para botones, progreso o una notificación fija usa WebToApk.notify(...). El mismo código funciona en un navegador normal.

__badgeSupported puente#

window.WebToApk.__badgeSupported(): Boolean

Si este launcher puede mostrar un contador en la insignia (navigator.setAppBadge existe solo entonces).

Ejemplo

Transporte interno detrás de la presencia de navigator.setAppBadge. Las páginas usan la API estándar; nunca lo llames directamente: los métodos __ pueden cambiar sin previo aviso.

Devuelve: true cuando el launcher de este teléfono muestra contadores de insignias. Requiere: nada que activar.

Usa la API pública:

if ('setAppBadge' in navigator) navigator.setAppBadge(unreadCount);

Muestra el ajuste "badge on app icon" solo en teléfonos cuyo launcher puede mostrar un contador:

function renderBadgeSetting() {
  const row = document.getElementById('badge-setting');
  // setAppBadge exists only when this launcher shows counts
  row.hidden = !('setAppBadge' in navigator);
}
renderBadgeSetting();

Borra el contador cuando el usuario abre la bandeja de entrada, en cualquier launcher que lo tenga:

function openInbox() {
  showInbox();
  if ('clearAppBadge' in navigator) {
    navigator.clearAppBadge().catch(function (e) {
      console.warn('badge not cleared', e.name);   // NotAllowedError
    });
  }
}

Notas: La API Badging se instala solo donde el launcher puede mostrar un contador (Samsung, Huawei, Xiaomi, OPPO, Sony, …); en launchers estilo Pixel no existe, como en un navegador sin badging.

__closeNotification puente#

window.WebToApk.__closeNotification(tag: String): Boolean

Notification.close(): elimina una notificación que la página publicó, por su etiqueta.

Ejemplo

Infraestructura interna detrás de Notification.prototype.close(): las páginas usan new Notification(...) y llaman a close() sobre ella. Elimina una notificación del panel por su etiqueta.

Devuelve: true cuando se hizo la solicitud de eliminación (también cuando nada tenía esa etiqueta); false en un error interno. Requiere: nada.

Usa la API pública:

let uploadNote = null;

function showUploading() {
  if (!('Notification' in window) || Notification.permission !== 'granted') return;
  uploadNote = new Notification('Uploading…', { body: 'Keep the app open', tag: 'upload', silent: true });
  uploadNote.onclose = function () { uploadNote = null; };
}

function uploadFinished() {
  if (uploadNote) uploadNote.close();   // removes it from the notification shade
}

Abre el chat correcto cuando se toca la notificación, y luego elimínala del panel:

function notifyNewMessage(msg) {
  if (!('Notification' in window) || Notification.permission !== 'granted') return;
  const n = new Notification(msg.from, {
    body: msg.text,
    tag: 'chat-' + msg.threadId,          // one notification per chat
    data: { threadId: msg.threadId }
  });
  n.onclick = function () {
    openThread(n.data.threadId);
    n.close();
  };
}

Muestra una breve confirmación que se elimina sola después de cinco segundos:

function confirmCopied() {
  if (!('Notification' in window) || Notification.permission !== 'granted') return;
  const n = new Notification('Link copied', { body: 'Paste it anywhere', tag: 'copied', silent: true });
  setTimeout(function () { n.close(); }, 5000);
}

Notas: no llames directamente a WebToApk.__closeNotification; los métodos __ pueden cambiar sin previo aviso. close() dispara el evento close de la notificación.

__setAppBadge puente#

window.WebToApk.__setAppBadge(contents: Long): Boolean

navigator.setAppBadge(n): n >= 0 es un conteo (0 lo borra), -1 la insignia de bandera del estándar.

Ejemplo

Transporte interno detrás de navigator.setAppBadge(count) / clearAppBadge(). Las páginas usan la API estándar; nunca lo llames directamente: los métodos __ pueden cambiar sin previo aviso.

Devuelve: true cuando se aplicó el conteo. Requiere: nada que activar.

Usa la API pública:

if ('setAppBadge' in navigator) navigator.setAppBadge(unreadCount);

Mantén el contador del icono sincronizado con el contador de no leídos, y bórralo cuando llegue a cero:

async function syncBadge(unread) {
  if (!('setAppBadge' in navigator)) return;
  try {
    if (unread > 0) await navigator.setAppBadge(unread);
    else await navigator.clearAppBadge();
  } catch (e) {
    console.warn('badge not applied', e.name);   // NotAllowedError when the launcher refused it
  }
}
syncBadge(messages.filter(function (m) { return !m.read; }).length);

Pon una insignia de bandera (sin número) en el icono mientras haya trabajo sin sincronizar:

function markPendingSync(hasPending) {
  if (!('setAppBadge' in navigator)) return;
  const p = hasPending
    ? navigator.setAppBadge()      // no argument = flag badge; a count launcher shows 1
    : navigator.clearAppBadge();
  p.catch(function () {});
}

Notas: -1 es la insignia de bandera del estándar (sin número), que un launcher con contador muestra como 1.

cancelAllNotifications puente#

window.WebToApk.cancelAllNotifications(): Boolean

Lectura y solicitud del permiso POST_NOTIFICATIONS, y gestión de las notificaciones que la página ya programó. Publicar y programar se documentan en sus propios métodos.

Se describe en su grupo, Permiso de notificaciones, cancelación y listado, y no por separado.

Ejemplo

Cancela todos los recordatorios que esta app ha programado.

Devuelve: true cuando termina; false solo en un error interno. Requiere: nada adicional.

document.getElementById('reset-reminders').addEventListener('click', function () {
  if (!(window.WebToApk && window.WebToApk.cancelAllNotifications)) return;
  if (!confirm('Remove all your reminders?')) return;
  const ok = WebToApk.cancelAllNotifications();
  showMessage(ok ? 'All reminders removed' : 'Could not remove reminders');
});

Al cerrar sesión, detén los recordatorios del usuario anterior y borra el contador del icono:

async function signOut() {
  if (window.WebToApk && window.WebToApk.cancelAllNotifications) {
    WebToApk.cancelAllNotifications();
  }
  if ('clearAppBadge' in navigator) navigator.clearAppBadge().catch(function () {});
  await auth.signOut();
  showLoginScreen();
}

Reconstruye todo el horario diario a partir de las horas guardadas (cancela todo y luego añade cada una):

function applyMedicineTimes(times) {        // e.g. ['08:00', '20:00']
  const b = window.WebToApk;
  if (!(b && b.cancelAllNotifications)) return;
  b.cancelAllNotifications();
  times.forEach(function (hhmm, i) {
    const parts = hhmm.split(':');
    const at = new Date();
    at.setHours(Number(parts[0]), Number(parts[1]), 0, 0);
    if (at.getTime() <= Date.now()) at.setDate(at.getDate() + 1);   // next occurrence
    b.scheduleNotification('med-' + i, 'Medicine', 'Time for your ' + hhmm + ' dose',
      String(at.getTime()), 'daily');
  });
}

Notas: borra la lista programada, así que nada se activa después y nada se vuelve a configurar tras un reinicio. Las notificaciones que ya se muestran en la bandeja se quedan ahí.

cancelNotification puente#

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

Lectura y solicitud del permiso POST_NOTIFICATIONS, y gestión de las notificaciones que la página ya programó. Publicar y programar se documentan en sus propios métodos.

Se describe en su grupo, Permiso de notificaciones, cancelación y listado, y no por separado.

Ejemplo

Cancela un recordatorio programado por su id, para que no se active de nuevo.

Devuelve: true (también cuando ningún recordatorio tenía ese id); false solo en un error interno. Requiere: nada adicional (los recordatorios necesitan ⏰ Reminders al compilar).

function stopWaterReminder() {
  if (!(window.WebToApk && window.WebToApk.cancelNotification)) return;
  WebToApk.cancelNotification('water');   // the id you gave scheduleNotification / scheduleNotificationEx

  // Check it is gone:
  const left = JSON.parse(WebToApk.getScheduledNotifications() || '[]');
  const stillThere = left.some(function (r) { return r.id === 'water'; });
  console.log('water reminder still scheduled?', stillThere);   // false
}

Un interruptor de recordatorio en los ajustes que programa o cancela con el mismo id:

document.getElementById('water-toggle').addEventListener('change', function (e) {
  const b = window.WebToApk;
  if (!(b && b.cancelNotification && b.scheduleNotification)) return;
  if (e.target.checked) {
    b.scheduleNotification('water', 'Drink water', 'Time for a glass of water',
      String(Date.now() + 60 * 60 * 1000), 'hourly');
  } else {
    b.cancelNotification('water');
  }
});

Cuando se elimina una tarea, cancela todos los recordatorios que le pertenecen:

function cancelTaskReminders(taskId) {
  const b = window.WebToApk;
  if (!(b && b.cancelNotification)) return;
  let list = [];
  try { list = JSON.parse(b.getScheduledNotifications() || '[]'); } catch (e) { list = []; }
  list
    .filter(function (r) { return r.id.indexOf('task-' + taskId + '-') === 0; })
    .forEach(function (r) { b.cancelNotification(r.id); });
}

Notas: esto cancela un recordatorio que aún no se ha activado (único o repetitivo). No elimina una notificación que ya se muestra en la bandeja, y no afecta a las notificaciones publicadas con notify() / showNotification().

canOpenAppAtTime puente#

window.WebToApk.canOpenAppAtTime(): String

Si una notificación programada puede poner la APP MISMA en pantalla (`openApp`). "granted": se mostrará una alerta a pantalla completa, sobre la pantalla de bloqueo. "denied": Android 14+ lo negó; llama a requestOpenAppPermission(). No hay una tercera respuesta en la que la app se inicie sola en segundo plano: Android bloquea los inicios de actividad en segundo plano desde Android 10, y un intent de pantalla completa es todo lo que queda.

Ejemplo

Te dice si un recordatorio con openApp: true puede poner la app misma en pantalla (una alerta a pantalla completa, también sobre la pantalla de bloqueo).

Devuelve: una cadena, de forma síncrona: 'granted' o 'denied'. Por debajo de Android 14 siempre es 'granted'. Requiere: activa ⏰ Reminders en el paso de Acceso (Paso 4) al compilar.

function prepareAlarm() {
  const b = window.WebToApk;
  if (!(b && b.canOpenAppAtTime)) return;
  if (b.canOpenAppAtTime() === 'denied') {
    showMessage('To open the app at alarm time, allow "full-screen notifications" for this app.');
    b.requestOpenAppPermission();
    return;
  }
  const t = new Date();
  t.setDate(t.getDate() + 1);
  t.setHours(7, 0, 0, 0);                    // tomorrow 07:00
  b.scheduleNotificationEx('wake-up', JSON.stringify({
    at: t.getTime(), repeat: 'daily',
    title: 'Wake up', body: 'Good morning!',
    openApp: true
  }));
}

window.addEventListener('appmint:reminder', function (e) {
  // e.detail.overLockScreen is true when the app was shown as a full-screen alert
  if (e.detail.overLockScreen) showBigAlarmScreen(e.detail.id);
});

Una pantalla de ajustes que muestra ambos interruptores de alarma con su estado actual:

function renderAlarmStatus() {
  const b = window.WebToApk;
  if (!(b && b.canOpenAppAtTime && b.getAlarmPrecision)) return;
  const openApp = b.canOpenAppAtTime();       // 'granted' | 'denied'
  const precision = b.getAlarmPrecision();    // 'exact' | 'inexact' | 'unavailable'
  document.getElementById('open-app-state').textContent =
    openApp === 'granted' ? 'Opens over the lock screen' : 'Notification only';
  document.getElementById('open-app-fix').hidden = openApp === 'granted';
  document.getElementById('timing-state').textContent =
    precision === 'exact' ? 'On the minute' : 'May be late';
}

renderAlarmStatus();
document.addEventListener('visibilitychange', function () {
  if (!document.hidden) renderAlarmStatus();  // back from Settings
});

Avisa cuando las alarmas guardadas se configuraron para abrir la app pero Android no lo permite en este momento:

function checkSavedAlarms() {
  const b = window.WebToApk;
  if (!(b && b.canOpenAppAtTime && b.getScheduledNotifications)) return;
  if (b.canOpenAppAtTime() === 'granted') return;
  // [{ id, title, body, trigger, repeat, options }] - options is the JSON string you scheduled
  const items = JSON.parse(b.getScheduledNotifications());
  const affected = items.filter(function (r) {
    try { return JSON.parse(r.options || '{}').openApp === true; } catch (e) { return false; }
  });
  if (affected.length) {
    showMessage(affected.length + ' alarm(s) will only show a notification. Tap "Allow" to let them open the app.');
  }
}

Notas: cuando es 'denied', un recordatorio openApp sigue publicando una notificación emergente, pero la app no se abre sola. Android no permite ninguna otra forma de que una app se inicie a sí misma en segundo plano.

clearNotificationSounds puente#

window.WebToApk.clearNotificationSounds(): Int

Elimina los archivos de sonido copiados y devuelve cuántos se eliminaron. Un archivo se vuelve a copiar cuando una notificación (o un recordatorio programado, cuando se activa) vuelve a usar ese sonido, incluso en un canal que ya existe, que sigue apuntando al mismo nombre de archivo (NotificationBridge.restoreBundleSound).

Ejemplo

Elimina las copias de tus sonidos de notificación personalizados que la app hizo a partir de tu paquete.

Devuelve: un número, de forma síncrona: cuántos archivos se eliminaron. Requiere: nada.

Borra y luego vuelve a poner los sonidos que sigues usando:

document.getElementById('reset-sounds').addEventListener('click', function () {
  const b = window.WebToApk;
  if (!(b && b.clearNotificationSounds)) return;
  const removed = b.clearNotificationSounds();

  // Sounds still in use must be copied again, or they play the default sound:
  ['/audio/alarm.mp3', '/audio/chime.mp3'].forEach(function (path) {
    if (!b.prepareNotificationSound(path)) console.warn('missing sound', path);
  });
  showMessage(removed + ' sound file(s) refreshed');
});

Un botón en la pantalla de almacenamiento que libera los archivos de sonido copiados e indica cuántos se eliminaron:

document.getElementById('free-space').addEventListener('click', function () {
  const b = window.WebToApk;
  if (!(b && b.clearNotificationSounds)) return;
  const removed = b.clearNotificationSounds();
  showMessage(removed === 0 ? 'No sound files to remove.' : 'Removed ' + removed + ' sound file(s).');
});

Al cerrar sesión, cancela todos los recordatorios y elimina las copias de sonidos que pertenecían a este usuario:

function signOutAndReset() {
  const b = window.WebToApk;
  if (b && b.cancelAllNotifications) b.cancelAllNotifications();
  if (b && b.clearNotificationSounds) b.clearNotificationSounds();
  localStorage.removeItem('alarmSound');
  goToLoginScreen();
}

Notas: rara vez necesitas esto; las copias son pequeñas. Un sonido se vuelve a copiar la próxima vez que algo lo use: notify(), un recordatorio cuando se activa, scheduleNotificationEx, o prepareNotificationSound(path), incluso en un canal de notificaciones que ya existe, así que el sonido personalizado sigue sonando.

enableNotifications puente#

window.WebToApk.enableNotifications(callbackId: String)

Devuelve a un usuario a las notificaciones: el diálogo del sistema mientras aún se pueda mostrar, o si no los ajustes de notificaciones de la app. Resuelve `window.__appmintReplies[callbackId]` con {state, changed, openedSettings} (AppMint.enableNotifications() es el envoltorio de promesa).

Ejemplo

Devuelve a un usuario que rechazó u omitió las notificaciones: muestra el diálogo de permiso de Android mientras todavía se pueda mostrar, y si no abre los ajustes de notificaciones de esta app y responde cuando el usuario vuelve.

Devuelve: nada directamente. La respuesta {state, changed, openedSettings} se entrega a window.__appmintReplies[callbackId]; state es 'granted' | 'prompt' | 'blocked' | 'unavailable', changed es true cuando difiere de antes de la llamada, openedSettings es true cuando se usó la pantalla de ajustes. Si ya está 'granted' (o 'unavailable') responde de inmediato. Requiere: llámalo desde un botón que el usuario tocó; las notificaciones necesitan Notifications (o Push / Reminders) en la compilación.

Prefiere el envoltorio de promesa AppMint.enableNotifications(). La llamada directa, para código que gestiona sus propias respuestas:

function enableNotificationsRaw() {
  return new Promise(function (resolve) {
    if (!(window.WebToApk && WebToApk.enableNotifications)) return resolve({ state: 'unavailable', changed: false, openedSettings: false });
    var replies = window.__appmintReplies = window.__appmintReplies || {};
    var id = 'my_notif_' + Date.now();
    replies[id] = resolve;
    WebToApk.enableNotifications(id);
  });
}

document.getElementById('turn-on-btn').onclick = async function () {
  var r = await enableNotificationsRaw();
  if (r.state === 'granted') document.getElementById('notif-banner').hidden = true;
};

Cada flujo terminado también se anuncia, incluido uno iniciado por un botón de push (appmint://notifications/enable):

window.addEventListener('appmint:notifications', function (e) {
  // e.detail = { state, changed, openedSettings }
  if (e.detail.state === 'granted') document.getElementById('notif-banner').hidden = true;
});

Notas: un botón de push o de mensaje dentro de la app cuyo enlace es appmint://notifications/enable ejecuta este mismo flujo; appmint://notifications/settings abre la pantalla de ajustes. La audiencia push de la app ve el nuevo estado de inmediato.

getAlarmPrecision puente#

window.WebToApk.getAlarmPrecision(): String

"exact": los recordatorios se activan en el minuto. "inexact": el usuario (o el fabricante) desactivó las alarmas exactas para esta app, así que Doze puede agruparlas con minutos de diferencia. Pide a [requestExactAlarms] que lo arregle. "unavailable": Android 12+ y no se pudo acceder al servicio de alarmas del sistema, así que no se puede leer la precisión (un recordatorio programado ahora puede no activarse tampoco). Una página que deba ser puntual debe comprobar esto en lugar de suponerlo. @return `"exact"`, `"inexact"` o `"unavailable"`.

Ejemplo

Te dice si los recordatorios se activarán en el minuto exacto, o pueden llegar tarde porque el usuario (o el fabricante del teléfono) desactivó las alarmas exactas para esta app.

Devuelve: una cadena, de forma síncrona: 'exact', 'inexact' (el ahorro de batería puede agruparla, a menudo con minutos de diferencia), o 'unavailable' (el teléfono no tiene servicio de alarmas). Requiere: activa ⏰ Reminders en el paso de Acceso (Paso 4) al compilar.

function checkReminderTiming() {
  if (!(window.WebToApk && window.WebToApk.getAlarmPrecision)) return;
  const precision = WebToApk.getAlarmPrecision();
  const warn = document.getElementById('timing-warning');
  warn.hidden = precision === 'exact';
  if (precision === 'inexact') {
    warn.textContent = 'Reminders may be a few minutes late. Tap to allow exact alarms.';
    warn.onclick = function () { WebToApk.requestExactAlarms(); };
  }
}
checkReminderTiming();
// Re-check when the user returns from Settings:
document.addEventListener('visibilitychange', function () {
  if (!document.hidden) checkReminderTiming();
});

Comprueba antes de configurar una alarma de despertador, e informa al usuario si puede llegar tarde:

function setWakeUp(atMs) {
  const b = window.WebToApk;
  if (!(b && b.getAlarmPrecision && b.scheduleNotificationEx)) return;
  const precision = b.getAlarmPrecision();
  if (precision === 'unavailable') { showMessage('Alarms cannot be set on this phone right now.'); return; }
  b.scheduleNotificationEx('wake', JSON.stringify({ at: atMs, title: 'Wake up', body: 'Good morning', openApp: true }));
  if (precision === 'inexact') {
    showMessage('Alarm set, but it may ring a few minutes late. Allow exact alarms for on-time alarms.');
  }
}

Muestra la puntualidad de los recordatorios como una línea en una pantalla de ajustes:

function renderTimingRow() {
  if (!(window.WebToApk && window.WebToApk.getAlarmPrecision)) return;
  const labels = {
    exact: 'Reminders arrive on time',
    inexact: 'Reminders may be a few minutes late',
    unavailable: 'Reminders are not available on this phone'
  };
  document.getElementById('timing-row').textContent = labels[WebToApk.getAlarmPrecision()];
}
renderTimingRow();

Notas: Android 11 y anteriores siempre responden 'exact'. Un recordatorio 'inexact' sigue activándose, solo que no en el minuto exacto.

getAlarmSupport puente#

window.WebToApk.getAlarmSupport(): String

Si `scheduleNotificationEx(id, {alarm: true, ...})` se puede programar ahora: "ready" | "not_enabled" (Alarm clock está desactivado en esta compilación) | "needs_exact_alarm" (llama a requestExactAlarms) | "needs_notification_permission".

Ejemplo

Te dice si se puede configurar una alarma (scheduleNotificationEx(id, { alarm: true, ... })) en este teléfono ahora mismo y, si no, qué tiene que permitir primero el usuario.

Devuelve: una cadena, de forma síncrona: 'ready', 'not_enabled' (la app se compiló sin 🔔 Alarm clock), 'needs_exact_alarm' (Android 12+: las alarmas exactas están desactivadas para esta app; llama a requestExactAlarms()), o 'needs_notification_permission' (Android 13+: sin permiso de notificaciones, así que no habría botón Dismiss; llama a requestNotificationPermission()). Requiere: activa 🔔 Alarm clock en el paso de Acceso (Paso 4) al compilar.

function prepareAlarms() {
  const b = window.WebToApk;
  if (!(b && b.getAlarmSupport)) { showMessage('Alarms work in the installed app only.'); return false; }
  switch (b.getAlarmSupport()) {
    case 'ready': return true;
    case 'needs_exact_alarm':
      showMessage('Allow "Alarms & reminders" for this app, then come back.');
      b.requestExactAlarms();
      return false;
    case 'needs_notification_permission':
      b.requestNotificationPermission();
      return false;
    default:
      showMessage('This app was built without alarm clocks.');
      return false;
  }
}

Vuelve a comprobar cuando el usuario regrese de Ajustes:

document.addEventListener('visibilitychange', function () {
  if (document.hidden || !(window.WebToApk && window.WebToApk.getAlarmSupport)) return;
  document.getElementById('alarm-setup').hidden = WebToApk.getAlarmSupport() === 'ready';
});

Crea una alarma solo cuando el teléfono esté listo:

function setAlarmIn(hours) {
  const b = window.WebToApk;
  if (!(b && b.getAlarmSupport)) return false;
  if (b.getAlarmSupport() !== 'ready') { prepareAlarms(); return false; }   // asks for what is missing
  return b.scheduleNotificationEx('wake-up', JSON.stringify({
    at: Date.now() + hours * 3600 * 1000,
    alarm: true,
    title: 'Wake up',
    body: 'Good morning'
  }));
}

Notas: scheduleNotificationEx con alarm: true devuelve false siempre que esto no sea 'ready': nunca se guarda una alarma que no podría sonar.

getNotificationPermission puente#

window.WebToApk.getNotificationPermission(): String

El permiso de notificación ACTUAL, leído en vivo del sistema operativo. El polyfill solía fijar `Notification.permission = 'default'` en cada carga de página, así que el patrón de página más común if (Notification.permission === 'granted') new Notification(...) nunca se ejecutaba, ni siquiera para un usuario que ya lo había concedido, ni en Android 12 y anteriores, donde las notificaciones no necesitan ningún permiso en tiempo de ejecución.

Ejemplo

Lee, ahora mismo, si esta app puede publicar notificaciones.

Devuelve: una cadena, de forma síncrona: 'granted'; 'denied' (el usuario las rechazó definitivamente, o desactivó las notificaciones de esta app en Ajustes: una publicación no se mostraría); o 'default' (aún no decidido, la app todavía puede preguntar). Requiere: nada para llamarlo; publicar requiere Notifications activado en el paso de Acceso (Paso 4) al compilar.

La API web estándar lee el mismo valor en vivo:

if ('Notification' in window && Notification.permission === 'granted') {
  document.getElementById('alerts-on').hidden = false;
}

La llamada directa, por ejemplo después de que el usuario vuelve de Ajustes:

function refreshAlertSetting() {
  if (!(window.WebToApk && window.WebToApk.getNotificationPermission)) return;
  const perm = WebToApk.getNotificationPermission();   // 'granted' | 'denied' | 'default'
  document.getElementById('enable-alerts').hidden = perm !== 'default';   // can still ask
  document.getElementById('open-settings-hint').hidden = perm !== 'denied';
}
document.addEventListener('visibilitychange', function () {
  if (!document.hidden) refreshAlertSetting();
});
refreshAlertSetting();

Decide antes de programar si pedir permiso, explicar o seguir adelante:

async function ensureAlerts() {
  const b = window.WebToApk;
  if (!(b && b.getNotificationPermission)) return false;
  const perm = b.getNotificationPermission();
  if (perm === 'granted') return true;
  if (perm === 'denied') {
    showMessage('Notifications are off for this app. Turn them on in Settings.');
    return false;
  }
  // 'default': the app can still ask
  return (await Notification.requestPermission()) === 'granted';
}

Notas: da la misma respuesta que navigator.permissions.query({ name: 'notifications' }). Para preguntar al usuario, usa Notification.requestPermission().

getNotificationState puente#

window.WebToApk.getNotificationState(): String

'granted' | 'prompt' (Android aún puede preguntar) | 'blocked' (rechazado definitivamente o desactivado en Ajustes: solo Ajustes lo arregla) | 'unavailable' (Android 13+ y esta compilación no declara el permiso de notificaciones). Ver NotificationOptIn.

Ejemplo

Lee, ahora mismo, si las notificaciones de esta app están activadas y, si no, si la app aún puede pedir permiso o solo Ajustes puede volver a activarlas.

Devuelve: una cadena, de forma síncrona: 'granted' (las notificaciones llegan al teléfono); 'prompt' (Android aún puede mostrar su diálogo de permiso); 'blocked' (el usuario las rechazó definitivamente, o desactivó las notificaciones de esta app en los Ajustes del sistema: solo Ajustes puede arreglarlo); o 'unavailable' (Android 13+ y la app se compiló sin Notifications / Reminders / Push, así que nada puede activarlas). Requiere: nada para llamarlo.

La llamada directa, para decidir si mostrar un banner de "Turn on notifications":

function refreshNotifBanner() {
  if (!(window.WebToApk && WebToApk.getNotificationState)) return;
  var state = WebToApk.getNotificationState();   // 'granted' | 'prompt' | 'blocked' | 'unavailable'
  var banner = document.getElementById('notif-banner');
  banner.hidden = !(state === 'prompt' || state === 'blocked');
  banner.querySelector('.hint').textContent = state === 'blocked'
    ? 'Notifications are off. Tap to open Settings and switch them on.'
    : 'Get order updates. Tap to turn on notifications.';
}
document.addEventListener('visibilitychange', function () {
  if (!document.hidden) refreshNotifBanner();
});
refreshNotifBanner();

Notas: getNotificationPermission() conserva sus propias palabras ('granted' / 'default' / 'denied') para páginas antiguas. Para devolver al usuario, llama a AppMint.enableNotifications() (elige por ti el diálogo o Ajustes).

getScheduledNotifications puente#

window.WebToApk.getScheduledNotifications(): String

Lectura y solicitud del permiso POST_NOTIFICATIONS, y gestión de las notificaciones que la página ya programó. Publicar y programar se documentan en sus propios métodos.

Se describe en su grupo, Permiso de notificaciones, cancelación y listado, y no por separado.

Ejemplo

Enumera los recordatorios que siguen programados.

Devuelve: una cadena JSON, de forma síncrona. Analízala. Cada elemento: { id, title, body, trigger, repeat, options? } - trigger es la próxima hora de activación en epoch ms, repeat es none|minutely|hourly|daily|weekly, y options (solo para scheduleNotificationEx) son las opciones como cadena JSON. Los campos en vivo indican cómo se entregará realmente, leídos en vivo: precision ("exact"; "inexact" = "Alarms & reminders" está desactivado para la app, así que Android lo entrega dentro de una ventana de 10 minutos mientras el teléfono está despierto, y puede retenerlo más tiempo mientras duerme; "alarm_clock" para alarm: true), windowMs + latestAt (la ventana que se dio a Android: 0 / trigger cuando es exacta, 600000 / trigger + 600000 cuando es inexacta) y notificationsEnabled (false = se activa pero Android no muestra nada, porque las notificaciones de la app están desactivadas). '[]' cuando no hay ninguno. Requiere: nada adicional.

function renderReminders() {
  if (!(window.WebToApk && window.WebToApk.getScheduledNotifications)) return;
  let list = [];
  try { list = JSON.parse(WebToApk.getScheduledNotifications() || '[]'); } catch (e) { list = []; }

  const ul = document.getElementById('reminders');
  ul.innerHTML = '';
  list.sort(function (a, b) { return a.trigger - b.trigger; }).forEach(function (r) {
    const li = document.createElement('li');
    li.textContent = r.title + ' — ' + new Date(r.trigger).toLocaleString() +
      (r.repeat !== 'none' ? ' (' + r.repeat + ')' : '');
    ul.appendChild(li);
  });
}

Muestra si un recordatorio está activado y cuándo se activa a continuación:

function describeReminder(id) {
  if (!(window.WebToApk && window.WebToApk.getScheduledNotifications)) return 'Not available';
  let list = [];
  try { list = JSON.parse(WebToApk.getScheduledNotifications() || '[]'); } catch (e) { list = []; }
  const r = list.find(function (x) { return x.id === id; });
  return r ? 'Next: ' + new Date(r.trigger).toLocaleString() : 'Off';
}
document.getElementById('water-status').textContent = describeReminder('water');

Lee las opciones adicionales de los recordatorios creados con scheduleNotificationEx:

function listAlarmStyleReminders() {
  if (!(window.WebToApk && window.WebToApk.getScheduledNotifications)) return [];
  let list = [];
  try { list = JSON.parse(WebToApk.getScheduledNotifications() || '[]'); } catch (e) { list = []; }
  return list.filter(function (r) {
    if (!r.options) return false;                  // made with scheduleNotification
    let o = {};
    try { o = JSON.parse(r.options); } catch (e) { return false; }
    return o.openApp === true;                     // puts the app on screen when it fires
  }).map(function (r) { return r.id; });
}

Notas: un recordatorio único sale de la lista cuando se dispara. Uno repetitivo se queda, con trigger movido a la siguiente hora. Un recordatorio cuya hora pasó mientras el teléfono estaba apagado o la app estaba detenida a la fuerza se muestra una vez, con retraso, la próxima vez que se inicie la app o el teléfono; un alarm: true tardío se informa como missed en lugar de sonar. Un recordatorio diario, semanal, mensual o anual mantiene su hora local cuando el teléfono cambia de zona horaria.

Avisa cuando los recordatorios no aparecerán a tiempo:

function reminderHealth() {
  if (!(window.WebToApk && window.WebToApk.getScheduledNotifications)) return '';
  let list = [];
  try { list = JSON.parse(WebToApk.getScheduledNotifications() || '[]'); } catch (e) { list = []; }
  if (!list.length) return '';
  if (list.some(function (r) { return r.notificationsEnabled === false; })) return 'Notifications are off for this app: reminders will not appear.';
  if (list.some(function (r) { return r.precision === 'inexact'; })) return 'Reminders may arrive late. Turn on Alarms & reminders.';
  return '';
}

isAlarmRinging puente#

window.WebToApk.isAlarmRinging(): Boolean

True mientras una alarma está sonando.

Ejemplo

Te dice si una de las alarmas de la app está sonando ahora mismo, para que la página pueda mostrar su propia pantalla de detener / posponer.

Devuelve: true mientras suena una alarma, si no false (de forma síncrona). Requiere: 🔔 Alarm clock en el paso de Acceso (Paso 4) al compilar.

function showRingScreenIfNeeded() {
  const b = window.WebToApk;
  const ringing = !!(b && b.isAlarmRinging && b.isAlarmRinging());
  document.getElementById('ring-screen').hidden = !ringing;
}
showRingScreenIfNeeded();
document.addEventListener('visibilitychange', showRingScreenIfNeeded);

Detén la alarma que suena desde tu propia pantalla con las llamadas correspondientes.

document.getElementById('stop').addEventListener('click', function () {
  const b = window.WebToApk;
  if (b && b.isAlarmRinging && b.isAlarmRinging()) b.stopAlarm('');   // '' = whichever is ringing
});
document.getElementById('snooze').addEventListener('click', function () {
  const b = window.WebToApk;
  if (b && b.snoozeAlarm && !b.snoozeAlarm('', 10)) showMessage('Nothing is ringing');
});

Reacciona a que la app se abra por una alarma que suena.

window.addEventListener('appmint:reminder', function (e) {
  if (e.detail.action === 'open' && e.detail.ringing) {
    document.getElementById('ring-screen').hidden = false;
  }
});

Notas: cuando la alarma que suena abre la app, appmint:reminder también llega con { action: 'open', ringing: true }.

notify puente#

window.WebToApk.notify(optionsJson: String): Boolean

Notificación enriquecida. Consulta NotificationBridge.show para ver la lista completa de opciones: channel (urgent|default|quiet|ongoing), image, largeIcon, bigText, ongoing, actions, progress, sound, silent, group, color. La descarga y decodificación de imágenes ocurren fuera del hilo principal, así que una imagen remota nunca puede bloquear la página.

Ejemplo

Publica una notificación ahora mismo, con cualquier combinación de opciones: imagen, texto largo, botones, barra de progreso, fija (ongoing), canal, sonido, color y etiqueta.

Devuelve: true cuando las opciones eran JSON válido y la notificación se entregó al sistema (se construye en un hilo en segundo plano, así que una URL de imagen incorrecta no bloquea la página); false para JSON no válido. Requiere: activa Notifications en el paso de Acceso (Paso 4) al compilar, y el permiso del usuario en Android 13+ (pídelo con Notification.requestPermission()).

Notificación simple. tag es un ID: publicar de nuevo con la misma etiqueta reemplaza la anterior.

async function notifyOrderReady() {
  if (!(window.WebToApk && window.WebToApk.notify)) return;
  if (window.Notification && Notification.permission !== 'granted') {
    const p = await Notification.requestPermission();
    if (p !== 'granted') { showMessage('Notifications are off for this app.'); return; }
  }
  WebToApk.notify(JSON.stringify({
    title: 'Order ready',
    body: 'Your order is ready to collect',
    channel: 'default',            // urgent | default | quiet | ongoing
    tag: 'order-42'
  }));
}

Imagen, icono grande y texto largo. image gana sobre bigText cuando se dan ambos.

WebToApk.notify(JSON.stringify({
  title: 'New photo',
  body: 'Tap to see it',
  image: 'https://example.com/photo.jpg',     // expanded big picture
  largeIcon: 'https://example.com/avatar.png', // round icon on the right
  color: '#00C6FF',
  tag: 'photo-7'
}));

WebToApk.notify(JSON.stringify({
  title: 'Weekly summary',
  body: '3 tasks done, 2 left',
  bigText: 'Done: pay rent, call bank, water plants. Left: tax form, dentist.',
  channel: 'quiet',                            // no sound, low priority
  group: 'summaries'
}));

Botones (como máximo 3). El toque vuelve como el evento appmint:notification-action.

WebToApk.notify(JSON.stringify({
  title: 'Ali sent you a message',
  body: 'Are we still on for 6pm?',
  channel: 'urgent',                           // heads-up banner
  tag: 'chat-ali',
  actions: [
    { id: 'open', label: 'Open chat' },
    { id: 'done', label: 'Mark read' }
  ]
}));

window.addEventListener('appmint:notification-action', function (e) {
  // e.detail = { actionId: 'done', tag: 'chat-ali' }
  if (e.detail.actionId === 'open') openChat(e.detail.tag);
  if (e.detail.actionId === 'done') markRead(e.detail.tag);
});

Progreso fijo (ongoing). Vuelve a publicar con la misma etiqueta para actualizarlo. Cuando termina el trabajo, publica el estado final con ongoing: false para que el usuario pueda deslizarlo y quitarlo.

function showUpload(percent) {
  WebToApk.notify(JSON.stringify({
    title: 'Uploading', body: percent + '%',
    ongoing: true,                             // cannot be swiped away
    progress: { current: percent, max: 100 },  // or { indeterminate: true }
    silent: true,
    tag: 'upload'
  }));
}
showUpload(40);
// finished:
WebToApk.notify(JSON.stringify({ title: 'Upload complete', body: '3 photos saved', tag: 'upload' }));

Otras opciones: sound (un archivo de tu paquete como '/audio/ding.mp3', una URL data:audio/..., o 'silent'), smallIcon (el nombre de un drawable), when (epoch ms mostrado como la hora), openApp: true (alerta a pantalla completa; ver scheduleNotificationEx).

Notas: los botones no tienen cuadro de respuesta en línea, y el toque de un botón llega a la página solo mientras la app se está ejecutando (no abre la app); el evento también llama a window.onAppMintNotificationAction(detail) si lo defines. image y largeIcon aceptan una URL https:// o data:, o una imagen empaquetada con tu app nombrada como la nombra tu página ('img/logo.png', '/img/logo.png', o su dirección completa https://appassets.androidplatform.net/assets/…). Una imagen incorrecta se omite, la notificación se muestra igualmente. ongoing: true siempre usa el canal silencioso "Ongoing". notify() no tiene un compañero público de "eliminar": cancelNotification solo cancela recordatorios programados. Una notificación que quieras eliminar más tarde desde la página debe publicarse con new Notification(...) y eliminarse con su close(); una tarjeta de progreso fija con Capacitor.AppwrightLiveProgress (end). Android fija el sonido de un canal cuando se crea el canal, así que un nuevo valor de sound crea un nuevo canal.

openNotificationSettings puente#

window.WebToApk.openNotificationSettings(): Boolean

Abre los ajustes de notificaciones de esta app; true cuando se pudo abrir la pantalla.

Ejemplo

Abre la pantalla de ajustes de notificaciones propia de esta app, donde el usuario puede activar o desactivar las notificaciones (y cada canal).

Devuelve: true cuando se abrió la pantalla, de forma síncrona; false cuando no se puede (Android 13+ y la compilación no tiene notificaciones). En Android 8+ es la página de notificaciones de la app; en Android más antiguo, la pantalla de información de la app. Requiere: nada que activar.

document.getElementById('notif-settings-link').onclick = function () {
  if (!(window.WebToApk && WebToApk.openNotificationSettings && WebToApk.openNotificationSettings())) {
    showMessage('Open Settings > Apps > this app > Notifications.');
  }
};

Notas: para devolver a un usuario en un toque, usa mejor AppMint.enableNotifications(): muestra el diálogo de permiso cuando Android aún lo permite y abre esta pantalla solo cuando no.

prepareNotificationSound puente#

window.WebToApk.prepareNotificationSound(spec: String): Boolean

Copia un archivo de audio incluido en la app a su lugar con antelación e informa si se puede usar como sonido de notificación. Opcional: `notify()` y `scheduleNotificationEx()` lo hacen por sí mismos, pero permite que una pantalla de ajustes verifique un sonido en el momento en que el usuario lo elige.

Ejemplo

Comprueba que un archivo de audio de tu app se puede usar como sonido de notificación, y lo copia a su lugar ahora. Opcional: notify() y scheduleNotificationEx() lo hacen por sí mismos; esto permite que una pantalla de ajustes verifique un sonido en el momento en que el usuario lo elige.

Devuelve: true si el sonido se puede usar; false si el archivo no existe o no es un tipo de audio compatible. Requiere: nada.

function chooseAlarmSound(path) {             // e.g. '/audio/chime.mp3'
  if (!(window.WebToApk && window.WebToApk.prepareNotificationSound)) return;
  if (!WebToApk.prepareNotificationSound(path)) {
    showMessage('This sound file is missing from the app: ' + path);
    return;
  }
  localStorage.setItem('alarmSound', path);
  // Use it:
  WebToApk.notify(JSON.stringify({ title: 'Sound test', body: 'This is your alarm sound', sound: path, tag: 'sound-test' }));
}

Llena un selector de sonidos solo con los sonidos que realmente se pueden usar:

const SOUNDS = [
  { label: 'Chime', path: '/audio/chime.mp3' },
  { label: 'Bell',  path: '/audio/bell.ogg' },
  { label: 'Beep',  path: '/audio/beep.wav' }
];

function renderSoundPicker(select) {
  const b = window.WebToApk;
  if (!(b && b.prepareNotificationSound)) return;
  select.innerHTML = '';
  SOUNDS.forEach(function (s) {
    if (!b.prepareNotificationSound(s.path)) return;   // missing or unsupported file
    const opt = document.createElement('option');
    opt.value = s.path; opt.textContent = s.label;
    select.appendChild(opt);
  });
}

Deja que el usuario elija su propio archivo de audio y úsalo para un recordatorio diario, pasado como una URL data::

document.getElementById('own-sound').addEventListener('change', function (e) {
  const file = e.target.files[0];
  const b = window.WebToApk;
  if (!file || !(b && b.prepareNotificationSound)) return;
  const reader = new FileReader();
  reader.onload = function () {
    const dataUrl = reader.result;                 // 'data:audio/mpeg;base64,...'
    if (!b.prepareNotificationSound(dataUrl)) {
      showMessage('That file cannot be used as a notification sound.');
      return;
    }
    b.scheduleNotificationEx('daily-walk', JSON.stringify({
      at: nextEvening(), repeat: 'daily',
      title: 'Walk time', body: 'Time for your evening walk', sound: dataUrl
    }));
  };
  reader.readAsDataURL(file);
});

Valores aceptados: una ruta en tu paquete web ('/audio/chime.mp3', 'audio/chime.mp3') o una URL data:audio/...;base64,... creada por tu página. Tipos: mp3, wav, ogg, oga, m4a, aac, flac, opus, mid, midi.

Notas: un nombre simple sin carpeta ni extensión (un sonido res/raw) no se comprueba aquí y devuelve false, aunque notify({sound: 'name'}) pueda usarlo. Android guarda un sonido por canal, así que cambiar el sonido crea un nuevo canal y restablece los ajustes del usuario para ese canal.

requestExactAlarms puente#

window.WebToApk.requestExactAlarms(): Boolean

Abre la pantalla del sistema donde se conceden las alarmas exactas. Android 12+.

Ejemplo

Abre la pantalla del sistema de Android 12+ donde el usuario permite que esta app use alarmas exactas, para que los recordatorios se activen en el minuto.

Devuelve: true cuando se abrió la pantalla de ajustes (y en Android 11 y anteriores, donde no se necesita nada); false si no se pudo abrir la pantalla. No te dice la elección del usuario. Requiere: activa ⏰ Reminders en el paso de Acceso (Paso 4) al compilar.

document.getElementById('allow-exact').addEventListener('click', function () {
  if (!(window.WebToApk && window.WebToApk.requestExactAlarms)) return;
  if (WebToApk.getAlarmPrecision() === 'exact') return;   // already fine
  WebToApk.requestExactAlarms();
});

// The user comes back from Settings: read the real answer.
document.addEventListener('visibilitychange', function () {
  if (document.hidden || !window.WebToApk) return;
  const exact = WebToApk.getAlarmPrecision() === 'exact';
  document.getElementById('allow-exact').hidden = exact;
});

Antes de guardar un recordatorio crítico en tiempo, ofrece la pantalla de ajustes solo cuando la puntualidad realmente esté desactivada:

function saveMedicineReminder(at) {
  const b = window.WebToApk;
  if (!(b && b.scheduleNotificationEx && b.getAlarmPrecision)) return;
  b.scheduleNotificationEx('medicine', JSON.stringify({
    at: at, repeat: 'daily', title: 'Medicine', body: 'Time for your tablet'
  }));
  const precision = b.getAlarmPrecision();   // 'exact' | 'inexact' | 'unavailable'
  if (precision === 'inexact') {
    showBanner('Reminders may arrive a few minutes late.', 'Fix timing', function () {
      b.requestExactAlarms();                // runs from the banner button tap
    });
  } else if (precision === 'unavailable') {
    showBanner('The phone\'s alarm service is not answering; this reminder may not fire.');
  }
}

Cuando no se puede abrir la pantalla de ajustes (false), dile al usuario dónde encontrar el interruptor:

document.getElementById('fix-timing').addEventListener('click', function () {
  const b = window.WebToApk;
  if (!(b && b.requestExactAlarms)) return;
  const opened = b.requestExactAlarms();
  if (!opened) {
    showMessage('Open Settings > Apps > this app > "Alarms & reminders" and turn it on.');
  }
  // When it did open, read the answer on return with getAlarmPrecision().
});

Notas: llámalo desde el toque de un botón y explica primero el motivo: sale de tu app. Los recordatorios ya programados siguen funcionando en ambos casos; solo cambia su puntualidad.

requestNotificationPermission puente#

window.WebToApk.requestNotificationPermission(): String

Lectura y solicitud del permiso POST_NOTIFICATIONS, y gestión de las notificaciones que la página ya programó. Publicar y programar se documentan en sus propios métodos.

Se describe en su grupo, Permiso de notificaciones, cancelación y listado, y no por separado.

Ejemplo

Muestra el diálogo "Allow notifications?" de Android 13+. Las páginas normalmente usan Notification.requestPermission(), que llama a esto y espera la respuesta del usuario.

Devuelve: una cadena, de forma síncrona: 'granted' si ya está permitido (o en Android 12 y anteriores), si no 'default' mientras se abre el diálogo. No puede devolver la respuesta del usuario. Requiere: activa Notifications en el paso de Acceso (Paso 4) al compilar.

Usa la API de Promise: se resuelve con la respuesta real ('granted' o 'denied'):

document.getElementById('enable-alerts').addEventListener('click', async function () {
  if (!('Notification' in window)) return;
  const perm = await Notification.requestPermission();
  if (perm === 'granted') {
    showMessage('Alerts are on');
  } else {
    showMessage('Alerts are off. You can turn them on in Settings.');
  }
});

La llamada directa, solo para abrir el diálogo sin esperar:

if (window.WebToApk && window.WebToApk.requestNotificationPermission) {
  const now = WebToApk.requestNotificationPermission();  // 'granted' or 'default'
  if (now === 'granted') enableAlertFeatures();
  // otherwise re-check later with WebToApk.getNotificationPermission()
  // ('granted' | 'denied' | 'default')
}

El código antiguo que pasa un callback a Notification.requestPermission obtiene la misma respuesta final:

document.getElementById('turn-on-reminders').addEventListener('click', function () {
  if (!('Notification' in window)) return;
  if (Notification.permission !== 'default') { updateReminderUi(Notification.permission); return; }
  Notification.requestPermission(function (perm) {
    updateReminderUi(perm);   // 'granted' or 'denied', once the user answers
  });
});

Notas: pide el permiso desde el toque de un botón con una razón clara. Después de que el usuario lo rechaza dos veces, Android deja de mostrar el diálogo y la respuesta es 'denied' al instante; el usuario debe entonces permitirlo en los Ajustes de la app.

requestOpenAppPermission puente#

window.WebToApk.requestOpenAppPermission(): Boolean

Abre la pantalla del sistema donde se concede la alerta a pantalla completa. Android 14+.

Ejemplo

Abre la pantalla del sistema de Android 14+ donde el usuario permite alertas a pantalla completa para esta app, para que un recordatorio openApp: true pueda poner la app en pantalla.

Devuelve: true cuando se abrió la pantalla de ajustes (y por debajo de Android 14, donde no se necesita nada); false si no se pudo abrir. No te dice la elección del usuario. Requiere: activa ⏰ Reminders en el paso de Acceso (Paso 4) al compilar.

document.getElementById('allow-open-app').addEventListener('click', function () {
  const b = window.WebToApk;
  if (!(b && b.requestOpenAppPermission)) return;
  if (b.canOpenAppAtTime() === 'granted') return;
  b.requestOpenAppPermission();
});

// Read the real answer when the user comes back:
document.addEventListener('visibilitychange', function () {
  if (document.hidden || !window.WebToApk) return;
  const ok = WebToApk.canOpenAppAtTime() === 'granted';
  document.getElementById('allow-open-app').hidden = ok;
});

Recuerda la alarma que el usuario estaba configurando, envíalo a Ajustes y termínala cuando vuelva:

let pendingAlarm = null;

function setWakeAlarm(at) {
  const b = window.WebToApk;
  if (!(b && b.requestOpenAppPermission)) return;
  if (b.canOpenAppAtTime() === 'denied') {
    pendingAlarm = at;
    b.requestOpenAppPermission();
    return;
  }
  b.scheduleNotificationEx('wake', JSON.stringify({ at: at, title: 'Alarm', body: 'Wake up', openApp: true }));
}

document.addEventListener('visibilitychange', function () {
  if (document.hidden || pendingAlarm === null || !window.WebToApk) return;
  if (WebToApk.canOpenAppAtTime() === 'granted') setWakeAlarm(pendingAlarm);
  else showMessage('Without that setting the alarm cannot open the app by itself.');
  pendingAlarm = null;
});

Cuando no se puede abrir la pantalla de ajustes (false), indica dónde está el interruptor:

document.getElementById('allow-fullscreen').addEventListener('click', function () {
  const b = window.WebToApk;
  if (!(b && b.requestOpenAppPermission)) return;
  if (!b.requestOpenAppPermission()) {
    showMessage('Open Settings > Apps > this app > Notifications and allow full-screen notifications.');
  }
});

Notas: llámalo desde el toque de un botón después de explicar el motivo: sale de tu app.

scheduleNotification puente#

window.WebToApk.scheduleNotification(id: String, title: String, body: String, triggerAtMillis: String, repeat: String): Boolean

Lectura y solicitud del permiso POST_NOTIFICATIONS, y gestión de las notificaciones que la página ya programó. Publicar y programar se documentan en sus propios métodos.

Se describe en su grupo, Permiso de notificaciones, cancelación y listado, y no por separado.

Ejemplo

Programa un recordatorio simple (título y texto) para una hora futura. Se activa incluso con la app cerrada y se vuelve a configurar después de reiniciar el teléfono.

Devuelve: true cuando se programó; false cuando id está vacío o la hora no es un número. Requiere: activa ⏰ Reminders en el paso de Acceso (Paso 4) al compilar (también activa Notifications), y el permiso de notificaciones del usuario en Android 13+.

function remindToDrinkWater() {
  if (!(window.WebToApk && window.WebToApk.scheduleNotification)) {
    showMessage('Reminders work in the installed app only.');
    return;
  }
  const ok = WebToApk.scheduleNotification(
    'water',                                  // your own id; same id replaces the old reminder
    'Drink water',                            // title
    'Time for a glass of water',              // body
    String(Date.now() + 60 * 60 * 1000),      // epoch milliseconds, AS A STRING
    'daily'                                   // none | minutely | hourly | daily | weekly
  );
  if (!ok) showMessage('Could not schedule the reminder');
}

Detecta cuándo un recordatorio abrió la app (el usuario lo tocó):

window.addEventListener('appmint:reminder', function (e) {
  // e.detail = { id: 'water', overLockScreen: false }
  if (e.detail.id === 'water') openWaterLog();
});

Un recordatorio único a las 8:00 del día en que vence una tarea:

function remindTask(task) {
  if (!(window.WebToApk && window.WebToApk.scheduleNotification)) return false;
  const at = new Date(task.dueDate);
  at.setHours(8, 0, 0, 0);
  if (at.getTime() <= Date.now()) return false;     // that morning has passed
  return WebToApk.scheduleNotification(
    'task-' + task.id,
    'Due today',
    task.title,
    String(at.getTime()),                           // epoch ms as a string
    'none'
  );
}

Notas: el recordatorio usa el estilo "urgent" (emergente) y su etiqueta es el id. Un recordatorio repetitivo que se perdió mientras el teléfono estaba apagado se activa en el siguiente paso. Para una imagen, botones, tu propio sonido o abrir la app sobre la pantalla de bloqueo, usa scheduleNotificationEx. Si las alarmas exactas están desactivadas para la app, la hora puede desviarse unos minutos; comprueba getAlarmPrecision().

scheduleNotificationEx puente#

window.WebToApk.scheduleNotificationEx(id: String, optionsJson: String): Boolean

La forma completa de [scheduleNotification]: todo lo que acepta `notify()`, activado a una hora futura con la app cerrada. WebToApk.scheduleNotificationEx('dose-1', JSON.stringify({ at: Date.now() + 3600000, // epoch millis (alias: triggerAt) repeat: 'daily', // none|minutely|hourly|daily|weekly|weekdays|monthly|yearly // days: [1, 3, 5], // with 'weekdays': ISO Mon=1..Sun=7 (default Mon-Fri) // alarm: true, // ring like an alarm clock (getAlarmSupport()) title: 'Time for your dose', body: 'Vitamin D - 1 tablet', sound: '/audio/chime.mp3', // a file in YOUR bundle; 'silent' for none openApp: true // open the app itself, over the lock screen })); Un sonido incluido en la app se copia a su lugar durante esta llamada, así que un nombre de archivo incorrecto se informa ahora (false) en lugar de convertirse en el sonido por defecto días después.

Ejemplo

Programa un recordatorio con todas las opciones que tiene notify() (imagen, botones, sonido, canal, abrir la app), para una hora futura. Se activa cuando la app está cerrada y sobrevive a un reinicio.

Devuelve: true cuando se programó. false cuando no se da hora (at, triggerAt, triggerAtMillis o inSeconds), el id está vacío, el JSON es incorrecto, o sound nombra un archivo que no está en tu app. Requiere: activa ⏰ Reminders en el paso de Acceso (Paso 4) al compilar, y el permiso de notificaciones del usuario en Android 13+.

Un recordatorio diario con tu propio sonido que abre la app (sobre la pantalla de bloqueo):

function scheduleStandup(tomorrowAt9am) {
  const b = window.WebToApk;
  if (!(b && b.scheduleNotificationEx)) { showMessage('Reminders work in the installed app only.'); return; }

  // Both are user settings: check, and ask if needed.
  if (b.getAlarmPrecision() === 'inexact') b.requestExactAlarms();
  if (b.canOpenAppAtTime() === 'denied') b.requestOpenAppPermission();

  const ok = b.scheduleNotificationEx('standup', JSON.stringify({
    at: tomorrowAt9am,          // epoch ms (or triggerAt, or inSeconds: 3600)
    repeat: 'daily',            // none | minutely | hourly | daily | weekly | weekdays | monthly | yearly
    title: 'Stand-up',
    body: 'Daily sync starts now',
    sound: '/audio/alarm.mp3',  // a file in YOUR app; 'silent' for none
    openApp: true               // full-screen alert that puts the app on screen
  }));
  if (!ok) showMessage('Check the sound file path — nothing was scheduled.');
}

window.addEventListener('appmint:reminder', function (e) {
  // e.detail = { id: 'standup', overLockScreen: true, action: 'open', ringing: false }
  openMeetingScreen(e.detail.id);
});

Solo días de gimnasio: lunes, miércoles y viernes a las 18:00 (days son ISO: lunes = 1 … domingo = 7; sin days, 'weekdays' significa lunes a viernes):

WebToApk.scheduleNotificationEx('gym', JSON.stringify({
  at: nextAt(18, 0),          // epoch ms of the first time; moved to the next listed day if needed
  repeat: 'weekdays',
  days: [1, 3, 5],
  title: 'Gym time',
  body: 'Leg day'
}));

// Rent on the 31st: 'monthly' rings on the last day of shorter months (28/29/30) and on the 31st again when there is one.
WebToApk.scheduleNotificationEx('rent', JSON.stringify({ at: rentDueAt, repeat: 'monthly', title: 'Rent is due' }));
// A birthday: same date every year (29 February rings on the 28th in other years).
WebToApk.scheduleNotificationEx('bday-ana', JSON.stringify({ at: anaBirthdayAt9am, repeat: 'yearly', title: 'Ana\'s birthday' }));

Una alarma de verdad (alarm: true): suena con el volumen de ALARMA del teléfono, incluso en modo silencio, y a través de No molestar cuando el usuario permite alarmas, y sigue sonando hasta Dismiss / Snooze o ringSeconds. Muestra el icono de alarma en la barra de estado y aparece en pantalla sobre la pantalla de bloqueo:

function setWakeAlarm(atMs) {
  const b = window.WebToApk;
  if (!(b && b.getAlarmSupport)) { showMessage('Alarms work in the installed app only.'); return; }
  const support = b.getAlarmSupport();   // 'ready' | 'not_enabled' | 'needs_exact_alarm' | 'needs_notification_permission'
  if (support === 'needs_exact_alarm') { b.requestExactAlarms(); return; }
  if (support !== 'ready') { showMessage('Alarms are not available: ' + support); return; }

  const ok = b.scheduleNotificationEx('wake', JSON.stringify({
    at: atMs,
    repeat: 'weekdays',          // daily/weekly/weekdays/monthly/yearly keep 07:00 across clock changes
    title: 'Wake up',
    body: 'Good morning',
    alarm: true,
    sound: '',                   // '' = the phone's alarm tone; or '/audio/rooster.mp3'; 'silent' = vibrate only
    vibrate: true,               // needs Vibration on in the build
    ringSeconds: 300,            // stop and count as missed after 5 minutes (default 600, max 3600)
    snoozeMinutes: 9,            // 0 hides the Snooze button (default 10)
    snoozeLabel: 'Snooze', dismissLabel: 'Stop'
  }));
  if (!ok) showMessage('The alarm was not set.');
}

window.addEventListener('appmint:reminder', function (e) {
  const d = e.detail;
  // Opened by the ringing alarm:  { id: 'wake', action: 'open', ringing: true, overLockScreen: true }
  // How a ring ended (also delivered later if the app was closed):
  //   { id: 'wake', action: 'dismiss' | 'snooze' | 'missed' | 'error', alarm: true,
  //     source: 'notification' | 'page' | 'timeout' | 'replaced' | 'system',
  //     snoozeMinutes?, snoozedUntil?, error?, soundError? }
  if (d.action === 'open' && d.ringing) showRingScreen(d.id);   // your Stop / Snooze buttons call stopAlarm / snoozeAlarm
  if (d.action === 'missed') logMissedAlarm(d.id);
});

Un recordatorio con botones, en 30 minutos:

WebToApk.scheduleNotificationEx('dose-1', JSON.stringify({
  inSeconds: 30 * 60,
  title: 'Time for your dose',
  body: 'Vitamin D — 1 tablet',
  actions: [{ id: 'taken', label: 'Taken' }, { id: 'snooze', label: 'Snooze' }]
}));

window.addEventListener('appmint:notification-action', function (e) {
  // e.detail = { actionId: 'taken', tag: 'dose-1' }
  if (e.detail.actionId === 'snooze') {
    WebToApk.scheduleNotificationEx(e.detail.tag, JSON.stringify({ inSeconds: 600, title: 'Time for your dose', body: 'Snoozed reminder' }));
  }
});

Un recordatorio único con una imagen cuando empieza una oferta, en el canal normal:

function remindSale(sale) {                        // sale.startsAt = epoch ms
  if (!(window.WebToApk && window.WebToApk.scheduleNotificationEx)) return false;
  return WebToApk.scheduleNotificationEx('sale-' + sale.id, JSON.stringify({
    at: sale.startsAt,
    title: 'The sale starts now',
    body: sale.name,
    image: 'https://example.com/banners/' + sale.id + '.jpg',   // expanded picture
    channel: 'default'                             // not the heads-up style reminders use by default
  }));
}

Notas: el mismo id reemplaza un recordatorio anterior. Valores por defecto: tag = el id, canal = urgent, bigText = el cuerpo. El toque de un botón llega a la página solo mientras la app se está ejecutando. El archivo de sonido se copia cuando programas, así que una ruta incorrecta falla ahora y no días después; funcionan mp3, wav, ogg, m4a, aac, flac y opus. openApp es la única forma de que una app aparezca sola en pantalla a una hora fijada; en Android 14+ el usuario debe permitirlo (canOpenAppAtTime). repeat: 'weekdays' con days fuera de 1-7 devuelve false. alarm: true necesita 🔔 Alarm clock en el paso de Acceso (Paso 4) y devuelve false a menos que getAlarmSupport() sea 'ready'; solo suena una alarma a la vez (una nueva termina la anterior como missed). Si el archivo de sonido propio de una alarma ha desaparecido cuando suena (una Remote Update lo eliminó), suena con el tono de alarma del teléfono y el evento lleva soundError: true.

showNotification puente#

window.WebToApk.showNotification(title: String, body: String, icon: String, tag: String): Boolean

La forma ORIGINAL de cuatro argumentos, conservada textualmente como adaptador para que sigan funcionando todas las apps ya publicadas y todos los fragmentos de las guías. Ahora se reenvía al constructor completo, lo que significa que el argumento `icon`, descartado en silencio por la implementación antigua, por fin hace algo.

Ejemplo

La llamada de notificación original de cuatro argumentos. Muestra una notificación con título, texto, un icono grande opcional y una etiqueta. El código nuevo debería usar notify(optionsJson) o new Notification(...), que hacen más.

Devuelve: true cuando la notificación se entregó al sistema. Requiere: activa Notifications en el paso de Acceso (Paso 4) al compilar, y el permiso del usuario en Android 13+.

La API web estándar (llama a este método por ti):

async function tellUser() {
  if (!('Notification' in window)) return;
  const p = Notification.permission === 'granted' ? 'granted' : await Notification.requestPermission();
  if (p === 'granted') new Notification('Saved', { body: 'Your note was saved', tag: 'note' });
}

La llamada directa. Argumentos: título, cuerpo, icono, etiqueta. Pasa '' si no hay icono ni etiqueta.

if (window.WebToApk && window.WebToApk.showNotification) {
  WebToApk.showNotification(
    'Saved',                                  // title
    'Your note was saved',                    // body
    'https://example.com/avatar.png',         // large icon (https://, data:, or 'img/x.png' in your app), or ''
    'note'                                    // tag: same tag replaces the old notification
  );
}

Actualiza una notificación de estado en el mismo lugar reutilizando su etiqueta:

function showSyncStatus(done, total) {
  const b = window.WebToApk;
  if (!(b && b.showNotification)) return;
  if (b.getNotificationPermission() !== 'granted') return;
  b.showNotification(
    'Syncing photos',
    done + ' of ' + total + ' uploaded',
    '',          // no large icon
    'sync'       // same tag every time: the text updates, no new notification
  );
}

Notas: se reenvía a notify({title, body, largeIcon: icon, tag}) en el canal por defecto. Sin etiqueta, cada llamada añade una nueva notificación. Para imágenes, botones, progreso o una notificación fija usa notify.

snoozeAlarm puente#

window.WebToApk.snoozeAlarm(id: String, minutes: Int): Boolean

Detiene la alarma que suena [id] ("" = la que esté sonando) y la hace sonar de nuevo en [minutes] (1-1440; 0 = el snoozeMinutes propio de la alarma). False cuando no está sonando.

Ejemplo

Detiene una alarma que suena y la hace sonar de nuevo pasados unos minutos, como su botón Snooze. Solo se mueve este timbre: una alarma diaria sigue sonando a su hora habitual mañana.

Devuelve: true cuando la alarma estaba sonando y queda pospuesta; false cuando no estaba sonando. id '' = la que esté sonando; minutes 1-1440, o 0 para el snoozeMinutes propio de la alarma (por defecto 10). Requiere: 🔔 Alarm clock en el paso de Acceso (Paso 4) al compilar.

document.getElementById('snooze').onclick = function () {
  if (!(window.WebToApk && window.WebToApk.snoozeAlarm)) return;
  if (WebToApk.snoozeAlarm('', 5)) showMessage('Ringing again in 5 minutes');
  hideRingScreen();
};

window.addEventListener('appmint:reminder', function (e) {
  // { id: 'wake', action: 'snooze', alarm: true, source: 'page', snoozeMinutes: 5, snoozedUntil: 1767340800000 }
  if (e.detail.action === 'snooze') updateNextAlarmLabel(e.detail.snoozedUntil);
});

Programa una alarma y luego conecta un botón para descartarla:

const b = window.WebToApk;
if (b && b.snoozeAlarm && b.getAlarmSupport() === 'ready') {
  b.scheduleNotificationEx('wake', JSON.stringify({
    at: Date.now() + 8 * 3600000, repeat: 'daily', alarm: true,
    title: 'Wake up', body: 'Good morning'
  }));
}
document.getElementById('dismiss').onclick = () => {
  if (WebToApk.isAlarmRinging()) WebToApk.stopAlarm('');
};

Pospón según la duración propia de la alarma, y maneja el caso de "nada está sonando":

document.getElementById('snooze').onclick = () => {
  if (!(window.WebToApk && WebToApk.snoozeAlarm)) return;
  const ok = WebToApk.snoozeAlarm('', 0);   // 0 = the alarm's own snoozeMinutes (default 10)
  if (!ok) showMessage('No alarm is ringing right now.');
};

Notas: el timbre pospuesto aparece en getScheduledNotifications() como <id>#snooze; cancelNotification(id) también lo elimina.

stopAlarm puente#

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

Detiene la alarma que suena [id] ("" = la que esté sonando), como su botón Dismiss. Una alarma repetitiva sigue programada para su próxima hora. False cuando no está sonando.

Ejemplo

Detiene una alarma que está sonando, exactamente igual que su botón Dismiss. Una alarma repetitiva sigue programada para su próxima hora; usa cancelNotification(id) para eliminarla.

Devuelve: true cuando esa alarma estaba sonando y ahora está detenida; false cuando no estaba sonando. Pasa '' para detener la alarma que esté sonando. Requiere: 🔔 Alarm clock en el paso de Acceso (Paso 4) al compilar.

window.addEventListener('appmint:reminder', function (e) {
  const d = e.detail;                 // { id, action: 'open', ringing: true, overLockScreen }
  if (d.action === 'open' && d.ringing) showRingScreen(d.id);
});

document.getElementById('stop').onclick = function () {
  if (window.WebToApk && window.WebToApk.stopAlarm) WebToApk.stopAlarm(currentAlarmId);
  hideRingScreen();
};

Ofrece el botón de detener solo donde exista el puente, y lee la respuesta:

const stopBtn = document.getElementById('stop');
if (window.WebToApk && typeof WebToApk.stopAlarm === 'function') {
  stopBtn.onclick = function () {
    const wasRinging = WebToApk.stopAlarm('');      // '' = whichever alarm is ringing
    if (!wasRinging) showMessage('Nothing was ringing.');
  };
} else {
  stopBtn.hidden = true;                             // plain browser: no alarm clock
}

Combínalo con isAlarmRinging() para que la página pueda mostrar la pantalla de la alarma de un timbre que empezó mientras estaba ausente:

document.addEventListener('visibilitychange', function () {
  if (document.hidden || !window.WebToApk) return;
  if (WebToApk.isAlarmRinging && WebToApk.isAlarmRinging()) showRingScreen();
  else hideRingScreen();
});

Notas: la página entonces también recibe appmint:reminder con { id, action: 'dismiss', alarm: true, source: 'page' }.

Generado a partir del runtime de la app y sus archivos de ejemplo en cada compilación de la documentación. Léelo como Markdown · Todas las familias.

Comprobado con el puente publicado el 2026-10-22.