API da ponte JavaScript
NFC
Leitura de uma tag por window.AndroidNFC e o lado de gravação por trás do polyfill NDEFReader.
NDEFReader padrão web#
new NDEFReader() — Web NFC
Exemplo
Web NFC padrão: new NDEFReader() lê, grava e bloqueia tags NFC no app instalado, com o mesmo código que o Chrome no Android aceita.
Retorna: Promises e eventos reading / readingerror, como no padrão da web. Os registros são NDEFRecords: recordType ('text', 'url', 'absolute-url', 'mime', 'smart-poster', 'empty', 'unknown' ou um tipo externo como 'example.com:thing'), mediaType (registros mime), id, encoding e lang (registros de texto) e data como um DataView. Requer: ative Suporte a Web NFC na Etapa 4 (Acesso) ao compilar. Sem isso, NDEFReader não existe. Sem pedido de permissão em tempo de execução; navigator.permissions.query({name:'nfc'}) responde granted.
Leia tags. Decodifique data com TextDecoder, exatamente como no Chrome:
function describe(record) {
if (record.recordType === 'text') return new TextDecoder(record.encoding).decode(record.data);
if (record.recordType === 'url' || record.recordType === 'absolute-url') return new TextDecoder().decode(record.data);
if (record.recordType === 'mime') return record.mediaType + ' (' + record.data.byteLength + ' bytes)';
return record.recordType;
}
var scanStop = null;
document.getElementById('read').addEventListener('click', async function () {
if (!('NDEFReader' in window)) { showStatus('NFC is not available in this app.'); return; }
var reader = new NDEFReader();
scanStop = new AbortController();
reader.onreading = function (e) {
showStatus('Tag ' + e.serialNumber + ': ' + e.message.records.map(describe).join(' | '));
};
reader.onreadingerror = function () { showStatus('That tag holds no NFC data this phone can read.'); };
try {
await reader.scan({ signal: scanStop.signal });
showStatus('Hold a tag to the back of the phone.');
} catch (err) {
showStatus('NFC error: ' + err.name + ' ' + err.message); // NotReadableError = NFC switched off
}
});
document.getElementById('stop').addEventListener('click', function () {
if (scanStop) scanStop.abort(); // the standard way to stop scanning
});Grave texto ou um link. A Promise é resolvida depois que o usuário toca em uma tag:
async function writeTag(kind, value) {
if (!('NDEFReader' in window)) { showStatus('NFC is not available in this app.'); return; }
var reader = new NDEFReader();
showStatus('Tap a tag to write it...');
try {
if (kind === 'text') await reader.write(value); // same as { records: [{ recordType: 'text', data: value }] }
else await reader.write({ records: [{ recordType: 'url', data: value }] }, { overwrite: false });
showStatus('Tag written.');
} catch (err) {
showStatus('Write failed: ' + err.name + ' ' + err.message); // NotAllowedError: read-only, or overwrite:false on a used tag
}
}Bloqueie uma tag para sempre. makeReadOnly() não pode ser desfeito — pergunte antes:
async function lockTag() {
if (!('NDEFReader' in window)) return;
if (!confirm('Lock the next tag you tap? It can never be changed again.')) return;
try { await new NDEFReader().makeReadOnly(); showStatus('Tag locked.'); }
catch (err) { showStatus('Lock failed: ' + err.message); }
}Observações: Tipos de registro para write: text (string, ou bytes com encoding), url e absolute-url (string), mime (bytes + mediaType), smart-poster ({records: [...]}), um tipo externo 'domain:thing' (bytes ou {records: [...]}), unknown (bytes) e empty. Uma string simples grava um registro de texto; bytes simples gravam um registro mime application/octet-stream. Apenas leitores que chamaram scan() recebem eventos reading; AbortController.abort() no signal interrompe esse leitor (não existe stop() no padrão). readingerror é disparado para uma tag que não comporta dados NFC. O celular aguarda UMA operação de tag por vez: um novo write() ou makeReadOnly() enquanto um aguarda o substitui, e a chamada substituída é rejeitada com AbortError (o Chrome a colocaria na fila). Um signal em write() / makeReadOnly() cancela a espera. Erros: NotSupportedError (sem NFC, ou a tag não comporta NDEF, ou é pequena demais), NotReadableError (NFC desligado), NotAllowedError (tag somente leitura, ou overwrite: false em uma tag com registros), NetworkError (a tag foi afastada), TypeError (um registro que o padrão não permite). NDEFReader existe na página principal, não dentro de iframes.
__nfcCancelWrite ponte#
window.WebToApk.__nfcCancelWrite()
Cancela a gravação/bloqueio em espera (o polyfill chama isso quando o `signal` da operação é abortado).
Exemplo
Descarta uma gravação ou bloqueio de NFC em fila, para que o próximo toque em uma tag seja lido normalmente. As páginas cancelam pelo padrão da web — um AbortController signal — e o polyfill NDEFReader chama isto por elas.
Retorna: nada. Requer: Suporte a Web NFC ativado na Etapa 4 (Acesso) ao compilar.
A forma padrão, que o app respeita — o write() em espera é rejeitado com AbortError:
var controller = null;
async function writeWithCancel(text) {
if (!('NDEFReader' in window)) { showStatus('NFC is not available in this app.'); return; }
controller = new AbortController();
showStatus('Tap a tag to write it... (or press Cancel)');
try {
await new NDEFReader().write(text, { signal: controller.signal });
showStatus('Tag written.');
} catch (err) {
showStatus(err.name === 'AbortError' ? 'Cancelled.' : 'Write failed: ' + err.message);
}
}
document.getElementById('cancel').addEventListener('click', function () {
if (controller) controller.abort();
});Desista sozinho quando nenhuma tag chegar a tempo; um temporizador que aborta o signal cancela do mesmo jeito:
async function writeWithTimeout(url, ms) {
if (!('NDEFReader' in window)) { showStatus('NFC is not available in this app.'); return; }
var controller = new AbortController();
var timer = setTimeout(function () { controller.abort(); }, ms);
showStatus('Tap a tag within ' + Math.round(ms / 1000) + ' seconds...');
try {
await new NDEFReader().write({ records: [{ recordType: 'url', data: url }] }, { signal: controller.signal });
showStatus('Tag written.');
} catch (err) {
showStatus(err.name === 'AbortError' ? 'Timed out. Tap Write to try again.' : 'Write failed: ' + err.message);
} finally {
clearTimeout(timer);
}
}Cancele um bloqueio (ou gravação) em espera quando o usuário sair da tela, para que um toque posterior não altere uma tag:
var pendingTagOp = null;
window.addEventListener('hashchange', function () {
if (pendingTagOp) { pendingTagOp.abort(); pendingTagOp = null; }
});
document.getElementById('lock').addEventListener('click', function () {
if (!('NDEFReader' in window)) { showStatus('NFC is not available in this app.'); return; }
if (pendingTagOp) pendingTagOp.abort();
var controller = pendingTagOp = new AbortController();
showStatus('Tap the tag to lock it...');
new NDEFReader().makeReadOnly({ signal: controller.signal })
.then(function () { showStatus('Tag locked.'); })
.catch(function (err) { if (err.name !== 'AbortError') showStatus('Lock failed: ' + err.message); })
.finally(function () { if (pendingTagOp === controller) pendingTagOp = null; });
});Observações: Não chame WebToApk.__nfcCancelWrite() você mesmo: chamado diretamente, ele descarta a espera nativa mas não consegue concluir a Promise da página. O signal faz as duas coisas. Cancelar também descarta uma fila makeReadOnly().
__nfcMakeReadOnly ponte#
window.WebToApk.__nfcMakeReadOnly(): String
Coloca na fila um bloqueio permanente (`NDEFReader.makeReadOnly()`) para o próximo toque em uma tag; retorna o id da operação, "" quando o NFC está desativado neste app.
Exemplo
Transporte interno por trás de NDEFReader.makeReadOnly(). As páginas usam new NDEFReader().makeReadOnly(); nunca chame isto diretamente.
Retorna: (por makeReadOnly) uma Promise que é resolvida depois que o usuário toca em uma tag e ela é bloqueada, ou é rejeitada com um DOMException. Requer: Suporte a Web NFC ativado na Etapa 4 (Acesso) ao compilar.
O bloqueio é permanente. Grave a tag primeiro, depois bloqueie, e pergunte ao usuário antes de ambos:
document.getElementById('publish-tag').addEventListener('click', async function () {
if (!('NDEFReader' in window)) { showStatus('NFC is not available in this app.'); return; }
if (!confirm('Write and lock this tag? A locked tag can never be changed.')) return;
var reader = new NDEFReader();
try {
showStatus('Tap the tag to write it...');
await reader.write({ records: [{ recordType: 'url', data: 'https://example.com/item/42' }] });
showStatus('Now tap the same tag again to lock it...');
await reader.makeReadOnly();
showStatus('Tag written and locked.');
} catch (err) {
showStatus('Failed: ' + err.message);
}
});Bloqueie uma tag que já foi gravada e desista se nenhuma tag chegar em 20 segundos:
async function lockTag() {
if (!('NDEFReader' in window)) { showStatus('NFC is not available in this app.'); return; }
if (!confirm('Lock this tag for good? It can never be changed again.')) return;
var controller = new AbortController();
var timer = setTimeout(function () { controller.abort(); }, 20000);
showStatus('Tap the tag to lock it...');
try {
await new NDEFReader().makeReadOnly({ signal: controller.signal });
showStatus('Tag locked.');
} catch (err) {
if (err.name === 'AbortError') showStatus('No tag was tapped. Try again when ready.');
else if (err.name === 'NotAllowedError') showStatus('This kind of tag cannot be locked.');
else showStatus('Lock failed: ' + err.message);
} finally {
clearTimeout(timer);
}
}Leia uma tag primeiro e só ofereça bloqueá-la quando ela contiver o link esperado:
async function checkThenLock(expectedUrl) {
if (!('NDEFReader' in window)) { showStatus('NFC is not available in this app.'); return; }
var reader = new NDEFReader();
var scanning = new AbortController();
var lockBtn = document.getElementById('lock-btn');
reader.onreading = function (e) {
var rec = e.message.records.find(function (r) { return r.recordType === 'url'; });
var url = rec ? new TextDecoder().decode(rec.data) : null;
showStatus(url ? 'Tag points to ' + url : 'This tag holds no link.');
lockBtn.disabled = url !== expectedUrl;
};
await reader.scan({ signal: scanning.signal });
showStatus('Hold the tag to the phone to check it.');
lockBtn.onclick = async function () {
if (!confirm('Lock this tag? It can never be changed again.')) return;
scanning.abort();
showStatus('Tap the tag again to lock it...');
try { await reader.makeReadOnly(); showStatus('Tag locked.'); }
catch (err) { showStatus('Lock failed: ' + err.message); }
};
}Observações: Cada etapa precisa do próprio toque. A chamada retorna um id de operação e o resultado chega por window.__appmintNfcDone. Rejeições: NotSupportedError "Tag does not support NDEF", NotAllowedError "This tag cannot be made read-only" (muitas tags não podem ser bloqueadas), NetworkError quando a tag é afastada. Iniciar um write() enquanto um bloqueio aguarda substitui o bloqueio, que então é rejeitado com AbortError. Um signal passado para makeReadOnly({ signal }) cancela a espera.
__nfcState ponte#
window.WebToApk.__nfcState(): String
"on", "off" (NFC desligado nas configurações do celular) ou "none" (sem NFC, ou não ativado neste app) — o que `NDEFReader.scan()` verifica primeiro.
Exemplo
Verificação interna por trás de NDEFReader.scan(): se este app pode usar NFC agora. As páginas chamam scan() e leem a rejeição em vez disso.
Retorna: 'on', 'off' (o NFC está desligado nas configurações do celular) ou 'none' (o celular não tem NFC, ou o app foi compilado sem Suporte a Web NFC). Requer: Suporte a Web NFC ativado na Etapa 4 (Acesso) ao compilar.
A forma padrão — scan() é rejeitado com a mesma resposta:
async function startReading() {
if (!('NDEFReader' in window)) { showStatus('NFC is not available in this app.'); return; }
try {
await new NDEFReader().scan();
showStatus('Hold a tag to the back of the phone.');
} catch (err) {
if (err.name === 'NotReadableError') showStatus('Turn on NFC in the phone settings, then try again.');
else if (err.name === 'NotSupportedError') showStatus('This phone has no NFC.');
else showStatus('NFC error: ' + err.message);
}
}Verifique uma vez quando a tela abrir, para escolher entre o painel de NFC, uma dica "ative o NFC" e ocultar o NFC por completo. Abortar o scan da verificação logo em seguida não deixa nada em execução:
async function nfcStatus() {
if (!('NDEFReader' in window)) return 'none';
var probe = new AbortController();
try {
await new NDEFReader().scan({ signal: probe.signal });
return 'on';
} catch (err) {
return err.name === 'NotReadableError' ? 'off' : 'none';
} finally {
probe.abort();
}
}
async function refreshNfcUi() {
var state = await nfcStatus();
document.getElementById('nfc-panel').hidden = state !== 'on';
document.getElementById('nfc-off-hint').hidden = state !== 'off';
}
refreshNfcUi();
document.addEventListener('visibilitychange', function () {
if (document.visibilityState === 'visible') refreshNfcUi(); // back from the phone settings
});Leia tags apenas enquanto uma tela estiver aberta e diferencie uma tag vazia ou ilegível de um NFC desligado:
var tagScreen = null;
async function openTagScreen() {
if (!('NDEFReader' in window)) { showStatus('NFC is not available in this app.'); return; }
tagScreen = new AbortController();
var reader = new NDEFReader();
reader.onreading = function (e) {
var n = e.message.records.length;
showStatus(n ? 'Tag ' + e.serialNumber + ' holds ' + n + ' record(s).' : 'This tag is empty.');
};
reader.onreadingerror = function () { showStatus('This tag cannot be read. Try another one.'); };
try {
await reader.scan({ signal: tagScreen.signal });
showStatus('Hold a tag to the back of the phone.');
} catch (err) {
showStatus(err.name === 'NotReadableError' ? 'NFC is off. Turn it on in the phone settings.' : 'NFC error: ' + err.message);
}
}
function closeTagScreen() {
if (tagScreen) { tagScreen.abort(); tagScreen = null; }
}Observações: Estrutura interna do polyfill; use scan(). window.AndroidNFC.startScan() responde à mesma pergunta como booleano e mostra a mensagem "Ative o NFC" quando o NFC está desligado.
__nfcWrite ponte#
window.WebToApk.__nfcWrite(messageJson: String): String
Coloca [messageJson] (`{records:[…], overwrite}`, criado por `NDEFReader.write()`) na fila para o próximo toque em uma tag e retorna seu id de operação; "" quando o NFC não está ativado neste app.
Exemplo
Transporte interno por trás de NDEFReader.write(message). As páginas usam new NDEFReader().write(...); nunca chame isto diretamente.
Retorna: (por write) uma Promise que é resolvida depois que o usuário toca em uma tag e ela é gravada, ou é rejeitada com um DOMException. Requer: Suporte a Web NFC ativado na Etapa 4 (Acesso) ao compilar.
document.getElementById('write-table-tag').addEventListener('click', async function () {
if (!('NDEFReader' in window)) { showStatus('NFC is not available in this app.'); return; }
showStatus('Tap a tag to write it...');
try {
await new NDEFReader().write({
records: [
{ recordType: 'url', data: 'https://example.com/menu' },
{ recordType: 'text', data: 'Table 12', lang: 'en' },
{ recordType: 'mime', mediaType: 'application/json',
data: new TextEncoder().encode(JSON.stringify({ table: 12 })) }
]
});
showStatus('Tag written.');
} catch (err) {
showStatus('Write failed: ' + err.message);
}
});Grave apenas em uma tag em branco, para que uma tag que já contém dados nunca seja substituída por engano:
async function writeToBlankTag(url) {
if (!('NDEFReader' in window)) { showStatus('NFC is not available in this app.'); return; }
showStatus('Tap a blank tag...');
try {
await new NDEFReader().write(
{ records: [{ recordType: 'url', data: url }] },
{ overwrite: false }
);
showStatus('Tag written.');
} catch (err) {
if (err.name === 'NotAllowedError') showStatus('That tag already holds data or is locked. Use a blank tag.');
else if (err.name === 'NetworkError') showStatus('Hold the tag still until it is written.');
else showStatus('Write failed: ' + err.message);
}
}Grave um smart poster, um link junto com seu título, como um único registro:
document.getElementById('write-poster').addEventListener('click', async function () {
if (!('NDEFReader' in window)) { showStatus('NFC is not available in this app.'); return; }
showStatus('Tap a tag to write the poster...');
try {
await new NDEFReader().write({
records: [{
recordType: 'smart-poster',
data: { records: [
{ recordType: 'url', data: 'https://example.com/events/spring-fair' },
{ recordType: 'text', data: 'Spring Fair tickets', lang: 'en' }
] }
}]
});
showStatus('Poster tag written.');
} catch (err) {
showStatus(err.name === 'NotSupportedError' ? 'This tag is too small or cannot hold NFC data.' : 'Write failed: ' + err.message);
}
});Observações: O polyfill verifica os registros, transforma-os em {records:[{recordType, id, data(base64), encoding, lang, mediaType, records?}], overwrite} e passa a string JSON para cá. A chamada retorna um id de operação; o resultado volta para a página por window.__appmintNfcDone(id, errorName, message), então uma resposta de uma gravação substituída nunca conclui uma mais nova. Tags não formatadas são formatadas antes. Rejeições: NotAllowedError ("Tag is read-only", ou uma tag com registros quando overwrite é false), NotSupportedError ("Tag does not support NDEF", "Tag too small (N bytes available)"), NetworkError (a tag foi afastada), SyntaxError (um registro que o formato da tag não comporta). O app também mostra as próprias mensagens curtas "Pronto para gravar" e "Tag NFC gravada".
startScan ponte#
window.AndroidNFC.startScan(): Boolean
Verificação antiga de disponibilidade, presente apenas quando o criador ativou o NFC. O trabalho real com NFC passa pelo polyfill padrão NDEFReader (window.WebToApk.__nfc*), que é o que uma página deve usar; isto só informa se o adaptador está ligado.
Descrito pelo seu grupo, Web NFC, e não individualmente.
Exemplo
Informa se o NFC do celular está ligado. Ele fica em window.AndroidNFC, não em window.WebToApk, e não lê uma tag — a leitura é feita com NDEFReader.
Retorna: true (de forma síncrona) quando o celular tem NFC e ele está ligado; false quando o celular não tem NFC, ou quando o NFC está desligado (então o app também mostra uma breve mensagem "Ative o NFC"). Requer: Suporte a Web NFC ativado na Etapa 4 (Acesso) ao compilar; sem isso, window.AndroidNFC não existe.
Verifique antes de mostrar um botão de NFC:
function nfcReady() {
if (!window.AndroidNFC || typeof window.AndroidNFC.startScan !== 'function') return false;
return window.AndroidNFC.startScan();
}
document.getElementById('scan-tag').addEventListener('click', async function () {
if (!nfcReady()) { showStatus('Turn on NFC in the phone settings, then try again.'); return; }
var reader = new NDEFReader();
reader.addEventListener('reading', function (e) { showStatus('Tag ' + e.serialNumber); });
await reader.scan();
showStatus('Hold a tag to the back of the phone.');
});Verifique logo antes de gravar: com o NFC desligado, a gravação ficaria esperando um toque que nunca vem, enquanto startScan() responde na hora e mostra a mensagem "Ative o NFC" do app:
document.getElementById('write-pass').addEventListener('click', async function () {
if (!window.AndroidNFC || typeof window.AndroidNFC.startScan !== 'function') {
showStatus('NFC is not available in this app.');
return;
}
if (!window.AndroidNFC.startScan()) { showStatus('NFC is off, or this phone has no NFC.'); return; }
showStatus('Tap a tag to write it...');
try {
await new NDEFReader().write('Visitor pass #1042');
showStatus('Tag written.');
} catch (err) {
showStatus('Write failed: ' + err.message);
}
});Verifique de novo quando o usuário voltar e tocar em "Já ativei":
var retry = document.getElementById('nfc-retry');
retry.addEventListener('click', function () {
if (!window.AndroidNFC || typeof window.AndroidNFC.startScan !== 'function') {
showStatus('NFC is not available in this app.');
return;
}
if (window.AndroidNFC.startScan()) {
retry.hidden = true;
document.getElementById('nfc-panel').hidden = false;
startReading(); // your own NDEFReader.scan() code
} else {
showStatus('NFC is still off, or this phone has no NFC.');
}
});Observações: window.AndroidNFC existe desde o primeiro script da página. Depois que o usuário liga o NFC nas configurações e volta, a leitura de tags começa sozinha.