API del puente de JavaScript
NFC
Lectura de una etiqueta a través de window.AndroidNFC, y el lado de escritura detrás del polyfill NDEFReader.
NDEFReader estándar web#
new NDEFReader() — Web NFC
Ejemplo
Web NFC estándar: new NDEFReader() lee, escribe y bloquea etiquetas NFC en la app instalada, con el mismo código que acepta Chrome en Android.
Devuelve: Promises y eventos reading / readingerror, como en el estándar web. Los registros son NDEFRecords: recordType ('text', 'url', 'absolute-url', 'mime', 'smart-poster', 'empty', 'unknown' o un tipo externo como 'example.com:thing'), mediaType (registros mime), id, encoding y lang (registros de texto) y data como un DataView. Requiere: activa Web NFC Support en el Paso 4 (Acceso) al compilar. Sin él NDEFReader no existe. Sin aviso de permiso en tiempo de ejecución; navigator.permissions.query({name:'nfc'}) responde granted.
Lee etiquetas. Decodifica data con TextDecoder, exactamente como en 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
});Escribe texto o un enlace. La Promise se resuelve después de que el usuario toca una etiqueta:
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
}
}Bloquea una etiqueta para siempre. makeReadOnly() no se puede deshacer: pregunta 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); }
}Notas: Tipos de registro para write: text (cadena, o bytes con encoding), url y absolute-url (cadena), mime (bytes + mediaType), smart-poster ({records: [...]}), un tipo externo 'domain:thing' (bytes o {records: [...]}), unknown (bytes) y empty. Una cadena simple escribe un registro de texto; los bytes simples escriben un registro mime application/octet-stream. Solo los lectores que llamaron a scan() reciben eventos reading; AbortController.abort() en la signal detiene ese lector (no existe stop() en el estándar). readingerror se dispara para una etiqueta que no puede contener datos NFC. El teléfono espera UNA operación de etiqueta a la vez: un nuevo write() o makeReadOnly() mientras uno espera lo reemplaza, y la llamada reemplazada se rechaza con AbortError (Chrome lo pondría en cola). Una signal en write() / makeReadOnly() cancela la espera. Errores: NotSupportedError (sin NFC, o la etiqueta no puede contener NDEF, o es demasiado pequeña), NotReadableError (NFC desactivado), NotAllowedError (etiqueta de solo lectura, o overwrite: false en una etiqueta con registros), NetworkError (la etiqueta se alejó), TypeError (un registro que el estándar no permite). NDEFReader existe en la página principal, no dentro de iframes.
__nfcCancelWrite puente#
window.WebToApk.__nfcCancelWrite()
Cancela la escritura/bloqueo en espera (el polyfill lo llama cuando el `signal` de la operación se aborta).
Ejemplo
Descarta una escritura o bloqueo NFC en cola para que el próximo toque de etiqueta se lea con normalidad. Las páginas cancelan mediante el estándar web, un AbortController signal, y el polyfill NDEFReader lo llama por ellas.
Devuelve: nada. Requiere: Web NFC Support activado en el Paso 4 (Acceso) al compilar.
La forma estándar, que la app respeta: el write() en espera se rechaza con 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();
});Se rinde solo cuando no llega ninguna etiqueta a tiempo; un temporizador que aborta la signal cancela de la misma manera:
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);
}
}Cancela un bloqueo (o escritura) en espera cuando el usuario sale de la pantalla, para que un toque posterior no cambie una etiqueta:
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; });
});Notas: No llames a WebToApk.__nfcCancelWrite() tú mismo: llamado directamente descarta la espera nativa pero no puede resolver la Promise de la página. La signal hace ambas cosas. Cancelar también descarta un elemento en cola makeReadOnly().
__nfcMakeReadOnly puente#
window.WebToApk.__nfcMakeReadOnly(): String
Pone en cola un bloqueo permanente (`NDEFReader.makeReadOnly()`) para el próximo toque de etiqueta; devuelve su id de operación, "" cuando NFC está desactivado en esta app.
Ejemplo
Transporte interno detrás de NDEFReader.makeReadOnly(). Las páginas usan new NDEFReader().makeReadOnly(); nunca lo llames directamente.
Devuelve: (a través de makeReadOnly) una Promise que se resuelve después de que el usuario toca una etiqueta y esta queda bloqueada, o se rechaza con una DOMException. Requiere: Web NFC Support activado en el Paso 4 (Acceso) al compilar.
El bloqueo es permanente. Escribe primero la etiqueta, luego bloquéala, y pregunta al usuario antes de ambas cosas:
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);
}
});Bloquea una etiqueta que ya está escrita, y se rinde si no llega ninguna etiqueta en 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);
}
}Lee primero una etiqueta y ofrece bloquearla solo cuando contiene el enlace 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); }
};
}Notas: Cada paso necesita su propio toque. La llamada devuelve un id de operación y el resultado llega a través de window.__appmintNfcDone. Rechazos: NotSupportedError "Tag does not support NDEF", NotAllowedError "This tag cannot be made read-only" (muchas etiquetas no se pueden bloquear), NetworkError cuando la etiqueta se aleja. Iniciar un write() mientras espera un bloqueo reemplaza el bloqueo, que entonces se rechaza con AbortError. Un signal pasado a makeReadOnly({ signal }) cancela la espera.
__nfcState puente#
window.WebToApk.__nfcState(): String
"on", "off" (NFC desactivado en los ajustes del teléfono) o "none" (sin NFC, o no habilitado en esta app): lo que `NDEFReader.scan()` comprueba primero.
Ejemplo
Comprobación interna detrás de NDEFReader.scan(): si esta app puede usar NFC ahora mismo. Las páginas llaman a scan() y leen su rechazo.
Devuelve: 'on', 'off' (NFC está desactivado en los ajustes del teléfono) o 'none' (el teléfono no tiene NFC, o la app se compiló sin Web NFC Support). Requiere: Web NFC Support activado en el Paso 4 (Acceso) al compilar.
La forma estándar: scan() se rechaza con la misma respuesta:
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);
}
}Comprueba una vez cuando se abre la pantalla, para elegir entre el panel de NFC, una sugerencia de "activar NFC" u ocultar NFC por completo. Abortar de inmediato el escaneo del sondeo no deja nada en ejecución:
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
});Lee etiquetas solo mientras una pantalla esté abierta, y distingue una etiqueta vacía o ilegible de que NFC esté desactivado:
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; }
}Notas: Es infraestructura interna del polyfill; usa scan(). window.AndroidNFC.startScan() responde la misma pregunta como booleano y muestra el mensaje "Please enable NFC" cuando NFC está desactivado.
__nfcWrite puente#
window.WebToApk.__nfcWrite(messageJson: String): String
Pone en cola [messageJson] (`{records:[…], overwrite}`, creado por `NDEFReader.write()`) para el próximo toque de etiqueta y devuelve su id de operación; "" cuando NFC no está habilitado en esta app.
Ejemplo
Transporte interno detrás de NDEFReader.write(message). Las páginas usan new NDEFReader().write(...); nunca lo llames directamente.
Devuelve: (a través de write) una Promise que se resuelve después de que el usuario toca una etiqueta y esta se escribe, o se rechaza con una DOMException. Requiere: Web NFC Support activado en el Paso 4 (Acceso) al 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);
}
});Escribe solo en una etiqueta en blanco, para que una etiqueta que ya tiene datos nunca se reemplace por error:
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);
}
}Escribe un smart poster, un enlace junto con su título, como un solo 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);
}
});Notas: El polyfill revisa los registros, los convierte en {records:[{recordType, id, data(base64), encoding, lang, mediaType, records?}], overwrite} y pasa aquí la cadena JSON. La llamada devuelve un id de operación; el resultado vuelve a la página a través de window.__appmintNfcDone(id, errorName, message), de modo que una respuesta de una escritura reemplazada nunca resuelve una más nueva. Las etiquetas sin formato se formatean primero. Rechazos: NotAllowedError ("Tag is read-only", o una etiqueta con registros cuando overwrite es false), NotSupportedError ("Tag does not support NDEF", "Tag too small (N bytes available)"), NetworkError (la etiqueta se alejó), SyntaxError (un registro que el formato de la etiqueta no puede contener). La app también muestra sus propios mensajes breves "Ready to write" y "NFC tag written".
startScan puente#
window.AndroidNFC.startScan(): Boolean
Sondeo de disponibilidad heredado, presente solo cuando el creador habilitó NFC. El trabajo real con NFC pasa por el polyfill estándar NDEFReader (window.WebToApk.__nfc*), que es lo que debe usar una página; esto solo informa si el adaptador está activado.
Se describe en su grupo, Web NFC, y no por separado.
Ejemplo
Te dice si el NFC del teléfono está activado. Está en window.AndroidNFC, no en window.WebToApk, y no lee una etiqueta: la lectura se hace con NDEFReader.
Devuelve: true (de forma síncrona) cuando el teléfono tiene NFC y está activado; false cuando el teléfono no tiene NFC, o cuando NFC está desactivado (entonces la app también muestra un breve mensaje "Please enable NFC"). Requiere: Web NFC Support activado en el Paso 4 (Acceso) al compilar; sin él window.AndroidNFC no existe.
Comprueba antes de mostrar un botón 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.');
});Comprueba justo antes de escribir: con NFC desactivado la escritura esperaría un toque que nunca llega, mientras que startScan() responde al instante y muestra el mensaje "Please enable NFC" de la 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);
}
});Vuelve a comprobar cuando el usuario regrese y toque "I've turned it on":
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.');
}
});Notas: window.AndroidNFC existe desde el primer script de la página. Después de que el usuario activa NFC en los ajustes y vuelve, la lectura de etiquetas empieza sola.