Criar APK grátis

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.

Gerado a partir do runtime do app e de seus arquivos de exemplo a cada build da documentação. Leia em Markdown · Todas as famílias.

Conferido com a ponte atual em 22/10/2026.