A framework-agnostic JavaScript client for integrating with the NFC Agent.
Copy the client files to your project:
cp client/nfc-client.js your-project/
cp client/nfc-client.d.ts your-project/ # For TypeScript
Or include directly in HTML:
<script src="nfc-client.js"></script>
// Create client instance
const client = new NFCClient('http://localhost:9471', {
apiSecret: 'your-secret', // Optional
autoReconnect: true // Auto-reconnect on disconnect
});
// Listen for tag scans
client.on('tagData', (data) => {
console.log('Card UID:', data.uid);
console.log('Card Type:', data.type);
console.log('Text:', data.text);
});
// Listen for device status
client.on('deviceStatus', (status) => {
console.log('Device connected:', status.connected);
});
// Connect to server
await client.connect();
// Write to a card
await client.write({
records: [
{ type: 'text', content: 'Hello, NFC!' }
]
});
// Disconnect when done
await client.disconnect();
new NFCClient(serverUrl, options?)
| Parameter | Type | Description |
|---|---|---|
serverUrl |
string | Base URL of the NFC Agent server |
options.apiSecret |
string | Optional API secret for authentication |
options.autoReconnect |
boolean | Auto-reconnect on disconnect (default: true) |
connect()Connect to the WebSocket server. First connection wins.
await client.connect();
disconnect()Disconnect from the server. Releases session automatically.
await client.disconnect();
write(request)Write NDEF data to a card.
await client.write({
records: [
{ type: 'text', content: 'Hello!', language: 'en' },
{ type: 'uri', content: 'https://example.com' }
]
});
isConnected()Check if WebSocket is connected.
if (client.isConnected()) {
// ...
}
healthCheck()Perform REST API health check.
const health = await client.healthCheck();
| Event | Payload | Description |
|---|---|---|
tagData |
Tag data object | Tag was scanned |
deviceStatus |
Status object | Device status changed |
connected |
- | WebSocket connected |
disconnected |
- | WebSocket disconnected |
error |
Error object | Error occurred |
client.on('tagData', (data) => { /* ... */ });
client.on('deviceStatus', (status) => { /* ... */ });
client.on('connected', () => { /* ... */ });
client.on('disconnected', () => { /* ... */ });
client.on('error', (err) => { /* ... */ });
const client = new NFCClient('http://localhost:9471');
client.on('tagData', (data) => {
document.getElementById('uid').textContent = data.uid;
document.getElementById('text').textContent = data.text;
});
await client.connect();
const client = new NFCClient('http://localhost:9471');
await client.connect();
// Write single text record
await client.write({
records: [{ type: 'text', content: 'Hello, NFC!' }]
});
// Write multiple records
await client.write({
records: [
{ type: 'text', content: 'Welcome!' },
{ type: 'uri', content: 'https://example.com' }
]
});
const client = new NFCClient('http://localhost:9471');
await client.connect();
client.on('tagData', async (data) => {
if (!data.message) return;
// Extract existing records
const existingRecords = data.message.records.map(r => ({
type: r.type,
content: r.content,
language: r.language
}));
// Append new record
await client.write({
records: [
...existingRecords,
{ type: 'text', content: 'Appended record' }
]
});
});
const client = new NFCClient('http://localhost:9471');
client.on('error', (err) => {
console.error('NFC Error:', err);
});
client.on('disconnected', () => {
console.log('Disconnected - will auto-reconnect');
});
try {
await client.connect();
await client.write({
records: [{ type: 'text', content: 'Hello!' }]
});
} catch (err) {
console.error('Failed:', err);
}
TypeScript definitions are provided in nfc-client.d.ts. Import types:
import { NFCClient, TagData, DeviceStatus, WriteRequest } from './nfc-client';
const client = new NFCClient('http://localhost:9471');
client.on('tagData', (data: TagData) => {
console.log(data.uid);
});
See client/nfc-client.d.ts for full type definitions.
Use NFCDeviceClient to connect to the Device Server (port 9470) as an NFC device. This is a universal library that works in both Node.js and browser environments, allowing any NFC-capable device to act as a reader.
The library is NFC-source agnostic - integrate with any NFC library (WebNFC, React Native NFC Manager, etc.) by calling scanTag() when your NFC library detects a tag.
<script src="nfc-device-client.js"></script>
cp client/nfc-device-client.js your-project/
npm install ws # Or any WebSocket-compatible package
const NFCDeviceClient = require('./nfc-device-client');
const WebSocket = require('ws');
const client = new NFCDeviceClient('ws://localhost:9470', {
WebSocket: WebSocket, // Pass your WebSocket class
deviceName: 'Node.js Reader',
platform: 'node'
});
const client = new NFCDeviceClient('ws://localhost:9470', {
deviceName: 'My NFC Reader',
platform: 'web',
nfcType: 'webnfc' // Describe your NFC source
});
// Listen for registration
client.on('registered', ({ deviceID }) => {
console.log('Registered as device:', deviceID);
});
// Listen for write requests from server
client.on('writeRequest', ({ requestID, ndefMessage }) => {
console.log('Write request:', ndefMessage);
// Handle write with your NFC library, then respond
client.respondToWrite(requestID, true);
});
// Connect to server
await client.connect();
// When your NFC library detects a tag, call scanTag()
await client.scanTag({
uid: '04:AB:CD:EF:12:34:56',
type: 'MIFARE Classic 1K',
ndefMessage: { type: 'ndef', records: [...] }
});
new NFCDeviceClient(serverUrl, options?)
| Parameter | Type | Description |
|---|---|---|
serverUrl |
string | Device Server URL (e.g., ws://localhost:9470) |
options.WebSocket |
class | Custom WebSocket class (required in Node.js, optional in browser) |
options.deviceName |
string | Device name for registration (default: 'NFC Device') |
options.platform |
string | Platform identifier: 'web', 'ios', 'android', 'node' (default: 'unknown') |
options.appVersion |
string | App version (default: '1.0.0') |
options.canRead |
boolean | Device can read tags (default: true) |
options.canWrite |
boolean | Device can write tags (default: false) |
options.nfcType |
string | NFC library type: 'webnfc', 'react-native-nfc', 'custom' (default: 'custom') |
options.autoHeartbeat |
boolean | Send heartbeats automatically (default: true) |
options.heartbeatInterval |
number | Heartbeat interval in ms (default: 30000) |
options.autoReconnect |
boolean | Auto-reconnect on disconnect (default: true) |
options.reconnectDelay |
number | Delay before reconnecting in ms (default: 3000) |
connect()Connect to the Device Server and register as a device.
await client.connect();
disconnect()Disconnect from the server.
await client.disconnect();
scanTag(tagData)Send a tag scan event to the server. Call this when your NFC library detects a tag.
await client.scanTag({
uid: '04:AB:CD:EF:12:34:56',
technology: 'ISO14443A', // Optional, default: 'ISO14443A'
type: 'MIFARE Classic 1K', // Optional, default: 'Unknown'
atr: '', // Optional
scannedAt: new Date().toISOString(), // Optional, auto-set if not provided
ndefMessage: { // Optional
type: 'ndef',
records: [{ type: 'T', text: 'Hello', language: 'en' }]
},
rawData: null // Optional, base64 encoded
});
removeTag(uid)Notify server that a tag was removed from the reader.
await client.removeTag('04:AB:CD:EF:12:34:56');
respondToWrite(requestID, success, error?)Respond to a write request from the server.
client.respondToWrite(requestID, true);
// or on failure:
client.respondToWrite(requestID, false, 'Write failed: card removed');
getDeviceID()Get assigned device ID after registration.
const deviceID = client.getDeviceID();
getServerInfo()Get server info received during registration.
const serverInfo = client.getServerInfo();
// { version: '1.0.0', supportedNFC: ['ndef', 'mifare'] }
isConnected()Check if connected and registered.
if (client.isConnected()) {
// Ready to send/receive
}
| Event | Payload | Description |
|---|---|---|
registered |
{ deviceID, serverInfo } |
Successfully registered with server |
writeRequest |
{ requestID, deviceID, ndefMessage } |
Server requests a write operation |
connected |
{} |
WebSocket connected |
disconnected |
{} |
WebSocket disconnected |
error |
{ error, phase?, code? } |
Error occurred |
client.on('registered', ({ deviceID }) => { /* ... */ });
client.on('writeRequest', ({ requestID, ndefMessage }) => { /* ... */ });
client.on('connected', () => { /* ... */ });
client.on('disconnected', () => { /* ... */ });
client.on('error', ({ error, phase }) => { /* ... */ });
The NFCDeviceClient is NFC-source agnostic. Below are examples of integrating with popular NFC libraries.
WebNFC is available in Chrome on Android. Implement NFC scanning in your application code:
const client = new NFCDeviceClient('ws://localhost:9470', {
deviceName: 'Chrome NFC Reader',
platform: 'web',
nfcType: 'webnfc',
canWrite: true
});
let nfcReader = null;
let nfcAbortController = null;
// Check WebNFC support
function isWebNFCSupported() {
return 'NDEFReader' in window;
}
// Start WebNFC scanning
async function startNFCScanning() {
if (!isWebNFCSupported()) {
throw new Error('WebNFC not supported');
}
nfcAbortController = new AbortController();
nfcReader = new NDEFReader();
nfcReader.onreading = async (event) => {
const { serialNumber, message } = event;
// Convert NDEF message to protocol format
const records = [];
for (const record of message.records) {
const recordData = {
type: record.recordType,
mediaType: record.mediaType
};
if (record.recordType === 'text') {
const decoder = new TextDecoder(record.encoding || 'utf-8');
recordData.text = decoder.decode(record.data);
recordData.language = record.lang;
} else if (record.recordType === 'url') {
const decoder = new TextDecoder();
recordData.uri = decoder.decode(record.data);
}
records.push(recordData);
}
// Send to server
await client.scanTag({
uid: serialNumber.replace(/:/g, ''),
technology: 'NFC',
type: 'NDEF',
ndefMessage: { type: 'ndef', records }
});
};
nfcReader.onreadingerror = (error) => {
console.error('NFC reading error:', error);
};
await nfcReader.scan({ signal: nfcAbortController.signal });
}
// Stop scanning
function stopNFCScanning() {
if (nfcAbortController) {
nfcAbortController.abort();
nfcAbortController = null;
}
nfcReader = null;
}
// Handle write requests
client.on('writeRequest', async ({ requestID, ndefMessage }) => {
try {
const writer = new NDEFReader();
const records = ndefMessage.records.map(r => {
if (r.type === 'text' || r.type === 'T') {
return { recordType: 'text', data: r.text || r.content, lang: r.language || 'en' };
} else if (r.type === 'uri' || r.type === 'U') {
return { recordType: 'url', data: r.uri || r.content };
}
return r;
});
await writer.write({ records });
client.respondToWrite(requestID, true);
} catch (err) {
client.respondToWrite(requestID, false, err.message);
}
});
// Connect and start scanning
await client.connect();
if (isWebNFCSupported()) {
await startNFCScanning();
}
For React Native apps using react-native-nfc-manager:
import NfcManager, { NfcTech, Ndef } from 'react-native-nfc-manager';
import NFCDeviceClient from './nfc-device-client';
const client = new NFCDeviceClient('ws://your-server:9470', {
deviceName: 'React Native App',
platform: Platform.OS, // 'ios' or 'android'
nfcType: 'react-native-nfc',
canWrite: true
});
// Initialize NFC
async function initNFC() {
await NfcManager.start();
await client.connect();
}
// Read NFC tags
async function scanTag() {
try {
await NfcManager.requestTechnology(NfcTech.Ndef);
const tag = await NfcManager.getTag();
const ndefRecords = await NfcManager.ndefHandler.getNdefMessage();
// Convert to protocol format
const records = ndefRecords?.map(record => {
const decoded = Ndef.text.decodePayload(record.payload);
return {
type: record.tnf === Ndef.TNF_WELL_KNOWN ? 'T' : 'unknown',
text: decoded,
language: 'en'
};
}) || [];
// Send to server
await client.scanTag({
uid: tag.id,
technology: tag.techTypes?.[0] || 'NfcA',
type: tag.type || 'Unknown',
ndefMessage: { type: 'ndef', records }
});
} finally {
NfcManager.cancelTechnologyRequest();
}
}
// Handle write requests
client.on('writeRequest', async ({ requestID, ndefMessage }) => {
try {
await NfcManager.requestTechnology(NfcTech.Ndef);
const bytes = ndefMessage.records.map(r => {
if (r.type === 'text' || r.type === 'T') {
return Ndef.textRecord(r.text || r.content, r.language || 'en');
} else if (r.type === 'uri' || r.type === 'U') {
return Ndef.uriRecord(r.uri || r.content);
}
}).filter(Boolean);
await NfcManager.ndefHandler.writeNdefMessage(bytes);
client.respondToWrite(requestID, true);
} catch (err) {
client.respondToWrite(requestID, false, err.message);
} finally {
NfcManager.cancelTechnologyRequest();
}
});
For Node.js applications using external NFC readers (e.g., via serial port or USB):
const NFCDeviceClient = require('./nfc-device-client');
const WebSocket = require('ws');
const client = new NFCDeviceClient('ws://localhost:9470', {
WebSocket: WebSocket,
deviceName: 'Node.js NFC Reader',
platform: 'node',
nfcType: 'custom'
});
// Your NFC reader library
const nfcReader = require('your-nfc-library');
client.on('registered', ({ deviceID }) => {
console.log('Registered as:', deviceID);
});
client.on('error', ({ error }) => {
console.error('Client error:', error);
});
await client.connect();
// When your reader detects a tag
nfcReader.on('tag', async (tag) => {
await client.scanTag({
uid: tag.uid,
type: tag.type,
technology: 'ISO14443A',
ndefMessage: tag.ndef ? { type: 'ndef', records: tag.ndef.records } : null
});
});
nfcReader.on('removed', async (uid) => {
await client.removeTag(uid);
});
TypeScript definitions are provided in nfc-device-client.d.ts:
import { NFCDeviceClient, DeviceTagData, WriteRequestEvent } from './nfc-device-client';
const client = new NFCDeviceClient('ws://localhost:9470', {
deviceName: 'TypeScript Client',
nfcType: 'custom'
});
client.on('writeRequest', (event: WriteRequestEvent) => {
console.log(event.requestID);
});
const tagData: DeviceTagData = {
uid: '04:AB:CD:EF:12:34:56',
type: 'MIFARE Classic 1K'
};
await client.scanTag(tagData);
See client/nfc-device-client.d.ts for full type definitions.
The Device Server advertises itself via mDNS/Bonjour, allowing clients to discover the server on the local network without knowing the IP address.
Service Details:
_nfc-device._tcplocal.Using bonjour-service:
const { Bonjour } = require('bonjour-service');
const NFCDeviceClient = require('./nfc-device-client');
const WebSocket = require('ws');
const bonjour = new Bonjour();
// Find NFC Agent servers on the network
const browser = bonjour.find({ type: 'nfc-device' }, (service) => {
console.log('Found NFC Agent:', service.name);
console.log(' Host:', service.host);
console.log(' Port:', service.port);
console.log(' Addresses:', service.addresses);
// Connect to the discovered server
const serverUrl = `ws://${service.addresses[0]}:${service.port}`;
const client = new NFCDeviceClient(serverUrl, {
WebSocket: WebSocket,
deviceName: 'Auto-discovered Client',
platform: 'node'
});
client.on('registered', ({ deviceID }) => {
console.log('Connected to:', service.name, 'as', deviceID);
});
client.connect();
});
// Stop browsing after 10 seconds
setTimeout(() => {
browser.stop();
bonjour.destroy();
}, 10000);
Using react-native-zeroconf:
import Zeroconf from 'react-native-zeroconf';
import NFCDeviceClient from './nfc-device-client';
const zeroconf = new Zeroconf();
// Start scanning for NFC Agent servers
zeroconf.scan('nfc-device', 'tcp', 'local.');
zeroconf.on('resolved', (service) => {
console.log('Found NFC Agent:', service.name);
const serverUrl = `ws://${service.addresses[0]}:${service.port}`;
const client = new NFCDeviceClient(serverUrl, {
deviceName: 'React Native App',
platform: Platform.OS
});
client.on('registered', ({ deviceID }) => {
console.log('Connected as:', deviceID);
// Stop scanning once connected
zeroconf.stop();
});
client.connect();
});
zeroconf.on('error', (err) => {
console.error('Zeroconf error:', err);
});
// Stop scanning after 30 seconds if no server found
setTimeout(() => zeroconf.stop(), 30000);
For Electron apps, you can use Node.js mDNS libraries in the main process:
// main.js (main process)
const { Bonjour } = require('bonjour-service');
const { ipcMain } = require('electron');
const bonjour = new Bonjour();
ipcMain.handle('discover-nfc-servers', () => {
return new Promise((resolve) => {
const servers = [];
const browser = bonjour.find({ type: 'nfc-device' }, (service) => {
servers.push({
name: service.name,
host: service.host,
port: service.port,
addresses: service.addresses
});
});
setTimeout(() => {
browser.stop();
resolve(servers);
}, 5000);
});
});
// renderer.js (renderer process)
const servers = await window.electronAPI.discoverNFCServers();
if (servers.length > 0) {
const server = servers[0];
const client = new NFCDeviceClient(`ws://${server.addresses[0]}:${server.port}`, {
deviceName: 'Electron App',
platform: 'electron'
});
await client.connect();
}