API del puente de JavaScript
Teléfono, SMS y registro de llamadas
Realizar una llamada, enviar o redactar un mensaje y leer el historial. Cada uno de estos está sujeto a permisos y Play pregunta por ellos.
composeSms puente#
window.WebToApk.composeSms(phoneNumber: String, message: String): Boolean
Abre la app de SMS del usuario con el mensaje ya escrito. No necesita permiso y Play no puede rechazarlo: es la opción adecuada por defecto para cualquier cosa no automatizada. Devuelve false cuando el teléfono no tiene app de SMS (o no se pudo abrir).
Ejemplo
Abre la app de SMS del usuario con el número y el texto ya escritos. El usuario pulsa enviar.
Devuelve: true cuando se abrió la app de SMS, false cuando el teléfono no tiene app de SMS o no se pudo abrir (síncrono). Requiere: nada: ningún interruptor de compilación, ningún permiso.
Un botón «Escríbenos».
document.getElementById('textUs').addEventListener('click', function () {
if (window.WebToApk && window.WebToApk.composeSms) {
var ok = WebToApk.composeSms('+911234567890', 'Hello, I need help with my order');
if (!ok) showMessage('Could not open the SMS app.');
} else {
// In a normal browser the standard sms: link does the same job.
location.href = 'sms:+911234567890?body=' + encodeURIComponent('Hello, I need help with my order');
}
});Un botón «Recordar» en cada fila de cita, que rellena un recordatorio con el número de ese cliente:
document.getElementById('appointments').addEventListener('click', function (e) {
var row = e.target.closest('[data-phone]');
if (!row || !(window.WebToApk && typeof window.WebToApk.composeSms === 'function')) return;
var when = new Date(Number(row.getAttribute('data-time')));
var text = 'Hi ' + row.getAttribute('data-name') + ', a reminder of your appointment on ' +
when.toLocaleDateString() + ' at ' +
when.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }) + '.';
if (!WebToApk.composeSms(row.getAttribute('data-phone'), text)) {
showMessage('This phone has no SMS app.');
}
});Un formulario «Enviar por SMS»: cuando el teléfono no tiene app de SMS (false), copia el texto para que el usuario pueda pegarlo en otro lugar:
document.getElementById('smsForm').addEventListener('submit', function (e) {
e.preventDefault();
var number = document.getElementById('to').value.trim();
var text = document.getElementById('message').value;
if (!number || !text) return;
var opened = window.WebToApk && typeof window.WebToApk.composeSms === 'function' &&
WebToApk.composeSms(number, text);
if (!opened && navigator.clipboard) {
navigator.clipboard.writeText(text).then(function () {
showMessage('No SMS app here. The message was copied instead.');
});
}
});Notas: No se envía nada hasta que el usuario pulsa enviar en su propia app de SMS, así que este método en sí no necesita permiso de SMS. Úsalo para cada mensaje que el usuario escribe o aprueba; usa sendSms solo cuando la app realmente deba enviar por sí misma. En las apps creadas con IA y las importaciones de Google AI Studio, la compilación analiza tu código; una llamada a composeSms( no marca SMS (leer y enviar), solo lo hacen sendSms y listSms, así que un botón «Escríbenos» no añade ningún permiso restringido de Play.
listCallLog puente#
window.WebToApk.listCallLog(requestId: String, limit: Int, offset: Int, sinceMillis: String)
WebToApk.listCallLog(id, limit, offset, sinceMillis) Entregado como `appmint:calllog` / window.onAppMintCallLog.
Descrito por su grupo, Registro de llamadas, en lugar de por separado.
Ejemplo
Lee el historial de llamadas del teléfono, de la más reciente a la más antigua.
Devuelve: nada por ahora. La respuesta es el evento appmint:calllog (y window.onAppMintCallLog) con detail = { requestId, calls: [{ number, name, type, date, duration }] } o { requestId, error }. date son milisegundos desde 1970; duration son segundos. Requiere: activa Registro de llamadas (lectura) en el Paso 4 (Acceso) al compilar; se pide permiso al usuario en la primera llamada.
Muestra las últimas 50 llamadas.
function loadCalls() {
if (!(window.WebToApk && window.WebToApk.listCallLog)) return;
var id = 'calls-' + Date.now();
window.addEventListener('appmint:calllog', function handler(e) {
if (!e.detail || e.detail.requestId !== id) return;
window.removeEventListener('appmint:calllog', handler);
if (e.detail.error) { showMessage('Call log: ' + e.detail.error); return; }
e.detail.calls.forEach(function (c) {
// c.type: incoming | outgoing | missed | voicemail | rejected | blocked | other
addRow((c.name || c.number) + ' - ' + c.type + ' - ' +
new Date(c.date).toLocaleString() + ' - ' + c.duration + 's');
});
});
WebToApk.listCallLog(id, 50, 0, '0'); // limit, offset, since (string)
}Solo las llamadas de las últimas 24 horas, usando el hook de la página. sinceMillis es una CADENA porque el número es demasiado grande para el int del puente.
window.onAppMintCallLog = function (detail) {
if (detail.requestId !== 'today') return;
if (detail.error) return;
document.getElementById('count').textContent = detail.calls.length + ' calls today';
};
if (window.WebToApk && window.WebToApk.listCallLog) {
WebToApk.listCallLog('today', 500, 0, String(Date.now() - 24 * 60 * 60 * 1000));
}Una pantalla de historial de llamadas con un botón «Cargar más», que trae 25 llamadas cada vez con offset:
var PAGE = 25, loaded = 0;
function loadMoreCalls() {
if (!(window.WebToApk && typeof window.WebToApk.listCallLog === 'function')) return;
var id = 'page-' + loaded;
window.addEventListener('appmint:calllog', function handler(e) {
if (!e.detail || e.detail.requestId !== id) return;
window.removeEventListener('appmint:calllog', handler);
if (e.detail.error) { showMessage('Call log: ' + e.detail.error); return; }
e.detail.calls.forEach(function (c) { addRow(c.name || c.number, c.type, c.date); });
loaded += e.detail.calls.length;
document.getElementById('more').hidden = e.detail.calls.length < PAGE; // last page
});
WebToApk.listCallLog(id, PAGE, loaded, '0'); // skip the calls already shown
}
document.getElementById('more').addEventListener('click', loadMoreCalls);
loadMoreCalls();Notas: limit se limita a 1-2000. Errores: not_enabled, permission_denied, foreign_origin. El registro de llamadas es un permiso restringido de Google Play: debes presentar una Declaración de permisos aprobada en Play Console, o la app será rechazada.
listSms puente#
window.WebToApk.listSms(requestId: String, box: String, limit: Int, offset: Int)
WebToApk.composeSms(number, message) <- SIN permiso, abre la app de SMS WebToApk.sendSms(id, number, message, wantDelivery) WebToApk.listSms(id, "inbox"|"sent"|"draft", limit, offset) Resultados del envío: `appmint:sms` / window.onAppMintSms {requestId, status:"sent"|"delivered"|"failed", ok, reason?} Mensajes entrantes: `appmint:sms-received` / window.onAppMintSmsReceived.
Descrito por su grupo, SMS, en lugar de por separado.
Ejemplo
Lee mensajes SMS de la bandeja de entrada, enviados o borradores del teléfono, de los más recientes a los más antiguos. Este archivo también muestra cómo recibir mensajes nuevos a medida que llegan.
Devuelve: nada por ahora. La respuesta es el evento appmint:sms (y window.onAppMintSms) con detail = { requestId, messages: [{ id, address, body, date, read, box }] }. En caso de fallo: { requestId, status: 'failed', ok: false, reason }. Requiere: activa SMS (leer y enviar) en el Paso 4 (Acceso) al compilar; se pide permiso al usuario en la primera llamada.
Lee los últimos 30 mensajes de la bandeja de entrada.
function loadInbox() {
if (!(window.WebToApk && window.WebToApk.listSms)) return;
var id = 'inbox-' + Date.now();
window.addEventListener('appmint:sms', function handler(e) {
if (!e.detail || e.detail.requestId !== id) return;
window.removeEventListener('appmint:sms', handler);
if (e.detail.status === 'failed') { showMessage('Cannot read SMS: ' + e.detail.reason); return; }
e.detail.messages.forEach(function (m) {
addRow(m.address + ': ' + m.body + (m.read ? '' : ' (new)') +
' - ' + new Date(m.date).toLocaleString());
});
});
WebToApk.listSms(id, 'inbox', 30, 0); // box: 'inbox' | 'sent' | 'draft'
}Mensajes nuevos a medida que llegan (appmint:sms-received, también window.onAppMintSmsReceived). Los mensajes que llegan mientras la app está cerrada se guardan (los últimos 50) y se entregan a tu página cuando la app vuelve a abrirse.
window.addEventListener('appmint:sms-received', function (e) {
// e.detail = { address: '+91...', body: 'Your OTP is 4821', date: 1737000000000 }
var code = (e.detail.body.match(/\b\d{4,6}\b/) || [])[0];
if (code) document.getElementById('otp').value = code;
});
// or, as a page hook:
window.onAppMintSmsReceived = function (sms) {
console.log('SMS from ' + sms.address);
};Un control de gastos que analiza los últimos 200 mensajes de la bandeja de entrada en busca de cargos bancarios y suma los importes:
function scanSpending() {
if (!(window.WebToApk && typeof window.WebToApk.listSms === 'function')) return;
var id = 'spend-' + Date.now();
window.addEventListener('appmint:sms', function handler(e) {
if (!e.detail || e.detail.requestId !== id) return;
window.removeEventListener('appmint:sms', handler);
if (e.detail.status === 'failed') { showMessage('Cannot read SMS: ' + e.detail.reason); return; }
var total = 0;
e.detail.messages.forEach(function (m) {
if (!/debited/i.test(m.body)) return;
var amount = m.body.match(/(?:Rs\.?|INR)\s*([\d,]+(?:\.\d{1,2})?)/i);
if (amount) total += parseFloat(amount[1].replace(/,/g, ''));
});
document.getElementById('spent').textContent = 'Spent: Rs ' + total.toFixed(2);
});
WebToApk.listSms(id, 'inbox', 200, 0);
}Notas: limit se limita a 1-500. Cualquier nombre de buzón desconocido lee la bandeja de entrada. Valores de reason: not_enabled, permission_denied, foreign_origin. Los mensajes recibidos siguen llegando también a la app de SMS normal del usuario. El SMS es un permiso restringido de Google Play: debes presentar una Declaración de permisos aprobada en Play Console (normalmente solo las apps de SMS predeterminadas la obtienen), o la app será rechazada.
makePhoneCall puente#
window.WebToApk.makePhoneCall(phoneNumber: String)
Llama a [phoneNumber]. - Permiso de Teléfono ACTIVADO en la compilación: realiza la llamada directamente. La primera vez, Android pide al usuario el permiso de Teléfono; si lo rechaza, se abre el marcador con el número escrito para que aún pueda llamar él mismo. - Permiso de Teléfono DESACTIVADO: abre el marcador con el número escrito (el marcador no necesita permiso) y el usuario toca Llamar. - Una página que no es la propia de la app (un sitio externo abierto en la app) siempre recibe el marcador, nunca una llamada directa. Un enlace `tel:` simple en tu HTML también abre el marcador y a menudo basta.
Ejemplo
Inicia una llamada telefónica a un número, o abre el marcador con el número escrito.
Devuelve: nada. Requiere: nada para abrir el marcador. Activa Teléfono en el Paso 4 (Acceso) al compilar si la llamada debe empezar directamente.
Un botón «Llamar a la tienda».
document.getElementById('callShop').addEventListener('click', function () {
if (window.WebToApk && window.WebToApk.makePhoneCall) {
WebToApk.makePhoneCall('+911234567890');
}
});No se necesita ningún interruptor: un simple enlace tel: abre el marcador con el número, en la app y en un navegador. A menudo es todo lo que necesitas.
<a href="tel:+911234567890">Call the shop</a>Un solo controlador para todo un directorio de personal, donde cada fila lleva su número en un atributo data-phone:
document.getElementById('staffList').addEventListener('click', function (e) {
var btn = e.target.closest('[data-phone]');
if (!btn) return;
var number = btn.getAttribute('data-phone').replace(/[^\d+]/g, ''); // keep digits and +
if (!number) return;
if (window.WebToApk && typeof window.WebToApk.makePhoneCall === 'function') {
WebToApk.makePhoneCall(number);
} else {
location.href = 'tel:' + number; // a normal browser
}
});Notas: Con Teléfono desactivado, el marcador se abre con el número y el usuario pulsa llamar. Con Teléfono activado, la llamada empieza directamente; la primera vez, Android pide al usuario el permiso de llamadas telefónicas, y si lo rechaza, se abre el marcador para que aún pueda hacer la llamada. Una página que no es la propia de la app (un sitio externo abierto dentro de la app) siempre recibe el marcador. Pasa el número como una cadena simple. No se informa de nada: no puedes saber si se realizó la llamada. El interruptor Teléfono añade el permiso CALL_PHONE a tu ficha de Play; decláralo en el formulario de Seguridad de los datos y actívalo solo si lo necesitas.
sendSms puente#
window.WebToApk.sendSms(
WebToApk.composeSms(number, message) <- SIN permiso, abre la app de SMS WebToApk.sendSms(id, number, message, wantDelivery) WebToApk.listSms(id, "inbox"|"sent"|"draft", limit, offset) Resultados del envío: `appmint:sms` / window.onAppMintSms {requestId, status:"sent"|"delivered"|"failed", ok, reason?} Mensajes entrantes: `appmint:sms-received` / window.onAppMintSmsReceived.
Descrito por su grupo, SMS, en lugar de por separado.
Ejemplo
Envía un SMS directamente desde la app, sin abrir la app de SMS, e informa de lo que realmente ocurrió.
Devuelve: nada por ahora. La respuesta es el evento appmint:sms (y window.onAppMintSms) con detail = { requestId, status: 'sent' | 'delivered' | 'failed', ok, reason? }. Con un informe de entrega recibes DOS respuestas si tiene éxito: sent, luego delivered. Requiere: activa SMS (leer y enviar) en el Paso 4 (Acceso) al compilar; se pide permiso al usuario en la primera llamada.
Envía un código y sigue su estado. No quites el listener tras la primera respuesta cuando pediste un informe de entrega.
function sendCode(phone, code) {
if (!(window.WebToApk && window.WebToApk.sendSms)) {
showMessage('Sending SMS works only inside the app.');
return;
}
var id = 'sms-' + Date.now();
function handler(e) {
if (!e.detail || e.detail.requestId !== id) return;
var d = e.detail;
if (d.status === 'sent' && d.reason === 'no_delivery_report') {
showStatus('Sent (the network gave no delivery report)');
window.removeEventListener('appmint:sms', handler);
} else if (d.status === 'sent') {
showStatus('Sent');
} else if (d.status === 'delivered') {
showStatus('Delivered');
window.removeEventListener('appmint:sms', handler);
} else { // 'failed'
showStatus('Failed: ' + d.reason);
window.removeEventListener('appmint:sms', handler);
}
}
window.addEventListener('appmint:sms', handler);
WebToApk.sendSms(id, phone, 'Your code is ' + code, true); // true = delivery report
}Sin informe de entrega. Pasa false como último valor: recibes exactamente una respuesta, sent o failed.
window.onAppMintSms = function (d) {
if (d.requestId === 'alert-1') showStatus(d.ok ? 'Alert sent' : 'Failed: ' + d.reason);
};
if (window.WebToApk && window.WebToApk.sendSms) {
WebToApk.sendSms('alert-1', '+911234567890', 'Gate opened at ' + new Date().toLocaleTimeString(), false);
}El mismo aviso a una lista corta de números, uno tras otro, recogiendo cada resultado (y deteniéndose cuando el SMS está desactivado o rechazado, porque cualquier otro número fallaría de la misma manera):
function sendToAll(numbers, text, done) {
if (!(window.WebToApk && typeof window.WebToApk.sendSms === 'function')) { done(null); return; }
var results = [], i = 0;
function next() {
if (i >= numbers.length) { done(results); return; }
var number = numbers[i++];
var id = 'notice-' + Date.now() + '-' + i;
window.addEventListener('appmint:sms', function handler(e) {
if (!e.detail || e.detail.requestId !== id) return;
window.removeEventListener('appmint:sms', handler);
results.push({ number: number, ok: e.detail.ok, reason: e.detail.reason });
var r = e.detail.reason;
if (r === 'not_enabled' || r === 'permission_denied' || r === 'foreign_origin') done(results);
else next();
});
WebToApk.sendSms(id, number, text, false); // one answer per number
}
next();
}
sendToAll(['+911234567890', '+919876543210'], 'Practice moved to 6 pm today.', function (results) {
if (!results) return showMessage('Sending SMS works only inside the app.');
showStatus(results.filter(function (r) { return r.ok; }).length + ' of ' + results.length + ' sent');
});Notas: El texto largo se divide en partes y se envía como un solo mensaje. Si no llega ningún informe de entrega en 60 segundos recibes status: 'sent' con reason: 'no_delivery_report'; el mensaje se envió de todos modos. Valores de reason: not_enabled, permission_denied, foreign_origin, invalid_arguments (número o texto vacío), no_service, radio_off, generic_failure, null_pdu, not_delivered y otros. Usa un requestId de tipo cadena. Cada SMS puede costarle dinero al usuario. El SMS es un permiso restringido de Google Play: debes presentar una Declaración de permisos aprobada en Play Console (normalmente solo las apps de SMS predeterminadas la obtienen), así que prefiere composeSms cuando el usuario pueda pulsar enviar.