# NFC - JavaScript bridge API

> Reading a tag through window.AndroidNFC, and the write side behind the NDEFReader polyfill.

- **Applies to:** AppMint
- **Source:** extracted from the app runtime and its per-method example files; this page is generated from them.
- **HTML:** https://freewebtoapk.com/docs/api/nfc

### `NDEFReader`

```js
new NDEFReader() — Web NFC
```

**Example**

Standard Web NFC: `new NDEFReader()` reads, writes and locks NFC tags in the installed app, with the same code Chrome on Android accepts.

**Returns:** Promises and `reading` / `readingerror` events, as in the web standard. Records are `NDEFRecord`s: `recordType` (`'text'`, `'url'`, `'absolute-url'`, `'mime'`, `'smart-poster'`, `'empty'`, `'unknown'` or an external type such as `'example.com:thing'`), `mediaType` (mime records), `id`, `encoding` and `lang` (text records) and `data` as a `DataView`. **Needs:** turn on **Web NFC Support** in Step 4 (Access) when you build. Without it `NDEFReader` does not exist. No runtime permission prompt; `navigator.permissions.query({name:'nfc'})` answers `granted`.

**Read tags.** Decode `data` with `TextDecoder`, exactly as in Chrome:

```js
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
});
```

**Write text or a link.** The Promise resolves after the user taps a tag:

```js
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
  }
}
```

**Lock a tag for ever.** `makeReadOnly()` cannot be undone - ask first:

```js
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); }
}
```

**Notes:** Record types for `write`: `text` (string, or bytes with `encoding`), `url` and `absolute-url` (string), `mime` (bytes + `mediaType`), `smart-poster` (`{records: [...]}`), an external type `'domain:thing'` (bytes or `{records: [...]}`), `unknown` (bytes) and `empty`. A plain string writes a text record; plain bytes write an `application/octet-stream` mime record. Only readers that called `scan()` get `reading` events; `AbortController.abort()` on the `signal` stops that reader (there is no `stop()` in the standard). `readingerror` fires for a tag that cannot hold NFC data. The phone waits for ONE tag operation at a time: a new `write()` or `makeReadOnly()` while one waits replaces it, and the replaced call rejects with `AbortError` (Chrome would queue it). A `signal` on `write()` / `makeReadOnly()` cancels the wait. Errors: `NotSupportedError` (no NFC, or the tag cannot hold NDEF, or it is too small), `NotReadableError` (NFC switched off), `NotAllowedError` (read-only tag, or `overwrite: false` on a tag with records), `NetworkError` (the tag moved away), `TypeError` (a record the standard does not allow). `NDEFReader` exists in the main page, not inside iframes.

### `__nfcCancelWrite`

```js
window.WebToApk.__nfcCancelWrite()
```

Cancels the waiting write/lock (the polyfill calls it when the operation's `signal` aborts).

**Example**

Drops a queued NFC write or lock so the next tag tap is read normally. Pages cancel through the web standard - an `AbortController` `signal` - and the `NDEFReader` polyfill calls this for them.

**Returns:** nothing. **Needs:** **Web NFC Support** turned on in Step 4 (Access) when you build.

The standard way, which the app honours - the waiting `write()` rejects with `AbortError`:

```js
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();
});
```

**Notes:** Do not call `WebToApk.__nfcCancelWrite()` yourself: called directly it drops the native wait but cannot settle the page's Promise. The `signal` does both. Cancelling also drops a queued `makeReadOnly()`.

### `__nfcMakeReadOnly`

```js
window.WebToApk.__nfcMakeReadOnly(): String
```

Queues a permanent lock (`NDEFReader.makeReadOnly()`) for the next tag tap; returns its op id, "" when NFC is off in this app.

**Example**

Internal transport behind `NDEFReader.makeReadOnly()`. Pages use `new NDEFReader().makeReadOnly()`; never call this directly.

**Returns:** (through `makeReadOnly`) a Promise that resolves after the user taps a tag and it is locked, or rejects with a `DOMException`. **Needs:** **Web NFC Support** turned on in Step 4 (Access) when you build.

Locking is permanent. Write the tag first, then lock it, and ask the user before both:

```js
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);
  }
});
```

**Notes:** Each step needs its own tap. The call returns an operation id and the result arrives through `window.__appmintNfcDone`. Rejections: `NotSupportedError` "Tag does not support NDEF", `NotAllowedError` "This tag cannot be made read-only" (many tags cannot be locked), `NetworkError` when the tag moves away. Starting a `write()` while a lock waits replaces the lock, which then rejects with `AbortError`. A `signal` passed to `makeReadOnly({ signal })` cancels the wait.

### `__nfcState`

```js
window.WebToApk.__nfcState(): String
```

"on", "off" (NFC switched off in the phone's settings) or "none" (no NFC, or not enabled in this app) - what `NDEFReader.scan()` checks first.

**Example**

Internal check behind `NDEFReader.scan()`: whether this app can use NFC right now. Pages call `scan()` and read its rejection instead.

**Returns:** `'on'`, `'off'` (NFC is switched off in the phone's settings) or `'none'` (the phone has no NFC, or the app was built without Web NFC Support). **Needs:** **Web NFC Support** turned on in Step 4 (Access) when you build.

The standard way - `scan()` rejects with the same answer:

```js
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);
  }
}
```

**Notes:** Plumbing for the polyfill; use `scan()`. `window.AndroidNFC.startScan()` answers the same question as a boolean and shows a "Please enable NFC" message when NFC is off.

### `__nfcWrite`

```js
window.WebToApk.__nfcWrite(messageJson: String): String
```

Queues [messageJson] (`{records:[…], overwrite}`, built by `NDEFReader.write()`) for the next tag tap and returns its op id; "" when NFC is not enabled in this app.

**Example**

Internal transport behind `NDEFReader.write(message)`. Pages use `new NDEFReader().write(...)`; never call this directly.

**Returns:** (through `write`) a Promise that resolves after the user taps a tag and it is written, or rejects with a `DOMException`. **Needs:** **Web NFC Support** turned on in Step 4 (Access) when you build.

```js
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);
  }
});
```

**Notes:** The polyfill checks the records, turns them into `{records:[{recordType, id, data(base64), encoding, lang, mediaType, records?}], overwrite}` and passes the JSON string here. The call returns an operation id; the result comes back to the page through `window.__appmintNfcDone(id, errorName, message)`, so an answer for a replaced write never settles a newer one. Unformatted tags are formatted first. Rejections: `NotAllowedError` ("Tag is read-only", or a tag with records when `overwrite` is false), `NotSupportedError` ("Tag does not support NDEF", "Tag too small (N bytes available)"), `NetworkError` (the tag moved away), `SyntaxError` (a record the tag format cannot hold). The app also shows its own short "Ready to write" and "NFC tag written" messages.

### `startScan`

```js
window.AndroidNFC.startScan(): Boolean
```

Legacy availability probe, present only when the creator enabled NFC. Real NFC work goes through the standard NDEFReader polyfill (window.WebToApk.__nfc*), which is what a page should use; this only reports whether the adapter is on.

_Described by its group, Web NFC, rather than on its own._

**Example**

Tells you whether the phone's NFC is switched on. It lives on `window.AndroidNFC`, not on `window.WebToApk`, and it does not read a tag - reading is done with `NDEFReader`.

**Returns:** `true` (synchronously) when the phone has NFC and it is on; `false` when the phone has no NFC, or when NFC is off (then the app also shows a short "Please enable NFC" message). **Needs:** **Web NFC Support** turned on in Step 4 (Access) when you build; without it `window.AndroidNFC` does not exist.

Check before showing an NFC button:

```js
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.');
});
```

**Notes:** `window.AndroidNFC` exists from the first script on the page. After the user turns NFC on in settings and comes back, tag reading starts by itself.

