API da ponte JavaScript
Telefone, SMS e histórico de chamadas
Fazer uma ligação, enviar ou preparar uma mensagem e ler o histórico. Todas essas ações exigem permissão e a Play pergunta sobre elas.
composeSms ponte#
window.WebToApk.composeSms(phoneNumber: String, message: String): Boolean
Abre o app de SMS do usuário com a mensagem preenchida. Não precisa de permissão e não pode ser rejeitado pela Play: é o padrão certo para tudo que não seja automatizado. Retorna false quando o celular não tem app de SMS (ou ele não pôde ser aberto).
Exemplo
Abre o app de SMS do usuário com o número e o texto já preenchidos. O usuário toca em enviar.
Retorna: true quando o app de SMS abriu, false quando o celular não tem app de SMS ou ele não pôde ser aberto (síncrono). Requer: nada: nenhuma chave de build, nenhuma permissão.
Um botão "Envie uma mensagem".
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');
}
});Um botão "Lembrar" em cada linha de compromisso, preenchendo um lembrete para o número daquele 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.');
}
});Um formulário "Enviar por SMS": quando o celular não tem app de SMS (false), copie o texto para o usuário poder colá-lo em outro 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.');
});
}
});Observações: Nada é enviado até o usuário tocar em enviar no próprio app de SMS, então este método em si não precisa de permissão de SMS. Use-o para toda mensagem que o usuário escreve ou aprova; use sendSms somente quando o app realmente precisar enviar sozinho. Em apps criados com IA e importações do Google AI Studio, o build analisa seu código; uma chamada composeSms( não marca SMS (ler e enviar): só sendSms e listSms marcam, então um botão "Envie uma mensagem" não adiciona nenhuma permissão restrita da Play.
listCallLog ponte#
window.WebToApk.listCallLog(requestId: String, limit: Int, offset: Int, sinceMillis: String)
WebToApk.listCallLog(id, limit, offset, sinceMillis) Entregue como `appmint:calllog` / window.onAppMintCallLog.
Descrito por seu grupo, Histórico de chamadas, e não separadamente.
Exemplo
Lê o histórico de chamadas do celular, da mais recente para a mais antiga.
Retorna: nada por enquanto. A resposta é o evento appmint:calllog (e window.onAppMintCallLog) com detail = { requestId, calls: [{ number, name, type, date, duration }] } ou { requestId, error }. date é em milissegundos desde 1970; duration é em segundos. Requer: ative Histórico de chamadas (leitura) na Etapa 4 (Acesso) ao compilar; o usuário é solicitado a dar permissão na primeira chamada.
Mostre as últimas 50 chamadas.
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)
}Somente chamadas das últimas 24 horas, usando o hook da página. sinceMillis é uma STRING porque o número é grande demais para o int da ponte.
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));
}Uma tela de histórico de chamadas com um botão "Carregar mais", buscando 25 chamadas por vez com 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();Observações: limit é limitado a 1-2000. Erros: not_enabled, permission_denied, foreign_origin. O histórico de chamadas é uma permissão restrita do Google Play: você precisa enviar uma Declaração de Permissões aprovada no Play Console, ou o app é rejeitado.
listSms ponte#
window.WebToApk.listSms(requestId: String, box: String, limit: Int, offset: Int)
WebToApk.composeSms(number, message) <- SEM permissão, abre o app de SMS WebToApk.sendSms(id, number, message, wantDelivery) WebToApk.listSms(id, "inbox"|"sent"|"draft", limit, offset) Resultados de envio: `appmint:sms` / window.onAppMintSms {requestId, status:"sent"|"delivered"|"failed", ok, reason?} Mensagens recebidas: `appmint:sms-received` / window.onAppMintSmsReceived.
Descrito por seu grupo, SMS, e não separadamente.
Exemplo
Lê mensagens SMS da caixa de entrada, da caixa de enviadas ou dos rascunhos do celular, da mais recente para a mais antiga. Este arquivo também mostra como receber novas mensagens à medida que chegam.
Retorna: nada por enquanto. A resposta é o evento appmint:sms (e window.onAppMintSms) com detail = { requestId, messages: [{ id, address, body, date, read, box }] }. Em caso de falha: { requestId, status: 'failed', ok: false, reason }. Requer: ative SMS (ler e enviar) na Etapa 4 (Acesso) ao compilar; o usuário é solicitado a dar permissão na primeira chamada.
Leia as últimas 30 mensagens da caixa 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'
}Novas mensagens à medida que chegam (appmint:sms-received, também window.onAppMintSmsReceived). As mensagens que chegam com o app fechado são guardadas (as últimas 50) e entregues à sua página quando o app abrir de novo.
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);
};Um controle de despesas que analisa as últimas 200 mensagens da caixa de entrada em busca de débitos bancários e soma os valores:
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);
}Observações: limit é limitado a 1-500. Qualquer nome de caixa desconhecido lê a caixa de entrada. Valores de reason: not_enabled, permission_denied, foreign_origin. As mensagens recebidas continuam chegando também ao app de SMS normal do usuário. SMS é uma permissão restrita do Google Play: você precisa enviar uma Declaração de Permissões aprovada no Play Console (geralmente só apps de SMS padrão se qualificam), ou o app é rejeitado.
makePhoneCall ponte#
window.WebToApk.makePhoneCall(phoneNumber: String)
Liga para [phoneNumber]. - Permissão de Telefone ATIVADA no build: faz a ligação diretamente. Na primeira vez, o Android pede ao usuário a permissão de Telefone; se ele recusar, o discador abre com o número preenchido para que ele ainda possa ligar por conta própria. - Permissão de Telefone DESATIVADA: abre o discador com o número preenchido (o discador não precisa de permissão) e o usuário toca em Ligar. - Uma página que não é do próprio app (um site externo aberto no app) sempre recebe o discador, nunca uma ligação direta. Um link `tel:` simples no seu HTML também abre o discador e muitas vezes basta.
Exemplo
Inicia uma ligação para um número ou abre o discador com o número preenchido.
Retorna: nada. Requer: nada para abrir o discador. Ative Telefone na Etapa 4 (Acesso) ao compilar se a ligação deve começar direto.
Um botão "Ligue para a loja".
document.getElementById('callShop').addEventListener('click', function () {
if (window.WebToApk && window.WebToApk.makePhoneCall) {
WebToApk.makePhoneCall('+911234567890');
}
});Sem precisar de chave: um link tel: simples abre o discador com o número, no app e em um navegador. Muitas vezes isso é tudo de que você precisa.
<a href="tel:+911234567890">Call the shop</a>Um único manipulador para um diretório inteiro de funcionários, em que cada linha carrega o número em um 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
}
});Observações: Com Telefone desativado, o discador abre com o número e o usuário toca em ligar. Com Telefone ativado, a ligação começa direto; na primeira vez, o Android pede ao usuário a permissão de chamada telefônica e, se ele recusar, o discador abre para que ele ainda possa ligar. Uma página que não é do próprio app (um site externo aberto dentro do app) sempre recebe o discador. Passe o número como uma string simples. Nada é informado de volta: você não sabe se a ligação foi feita. A chave Telefone adiciona a permissão CALL_PHONE à sua ficha na Play; declare-a no formulário de Segurança dos dados e só ative se precisar.
sendSms ponte#
window.WebToApk.sendSms(
WebToApk.composeSms(number, message) <- SEM permissão, abre o app de SMS WebToApk.sendSms(id, number, message, wantDelivery) WebToApk.listSms(id, "inbox"|"sent"|"draft", limit, offset) Resultados de envio: `appmint:sms` / window.onAppMintSms {requestId, status:"sent"|"delivered"|"failed", ok, reason?} Mensagens recebidas: `appmint:sms-received` / window.onAppMintSmsReceived.
Descrito por seu grupo, SMS, e não separadamente.
Exemplo
Envia um SMS diretamente do app, sem abrir o app de SMS, e informa o que realmente aconteceu.
Retorna: nada por enquanto. A resposta é o evento appmint:sms (e window.onAppMintSms) com detail = { requestId, status: 'sent' | 'delivered' | 'failed', ok, reason? }. Com relatório de entrega, você recebe DUAS respostas em caso de sucesso: sent e depois delivered. Requer: ative SMS (ler e enviar) na Etapa 4 (Acesso) ao compilar; o usuário é solicitado a dar permissão na primeira chamada.
Envie um código e acompanhe o status dele. Não remova o listener depois da primeira resposta quando você pediu um relatório 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
}Sem relatório de entrega. Passe false como último valor: você recebe exatamente uma resposta, sent ou 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);
}O mesmo aviso para uma pequena lista de números, um após o outro, coletando cada resultado (e parando quando o SMS está desativado ou foi recusado, porque todos os outros números falhariam do mesmo jeito):
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');
});Observações: Textos longos são divididos em partes e enviados como uma única mensagem. Se nenhum relatório de entrega chegar em 60 segundos, você recebe status: 'sent' com reason: 'no_delivery_report': a mensagem foi enviada mesmo assim. Valores de reason: not_enabled, permission_denied, foreign_origin, invalid_arguments (número ou texto vazio), no_service, radio_off, generic_failure, null_pdu, not_delivered e outros. Use uma string em requestId. Cada SMS pode custar dinheiro ao usuário. SMS é uma permissão restrita do Google Play: você precisa enviar uma Declaração de Permissões aprovada no Play Console (geralmente só apps de SMS padrão se qualificam), então prefira composeSms quando o usuário puder tocar em enviar.