Card Interaction
Implementation, constraints, and selection logic for interacting with the United Network card using a web browser or the mobile application.
Interaction Methods
Communication between the client device and the United Network card is achieved via two primary protocols: FIDO (WebAuthn) and WebNFC. Neither method is universally supported across all platforms, necessitating a hybrid approach.
1. FIDO (WebAuthn over NFC)
The FIDO protocol utilizes the browser's Credential Management API to send commands to the card. The card acts as a security key.
- iOS + Safari: Fully supported. This is the primary method for Apple mobile devices.
- Android + Chrome: Partially supported. The Android FIDO implementation does not support commands with a length greater than 256 bytes. This limitation restricts functionality to basic operations like single secp256k1 signatures (non-batch).
- United Network Application: The native application uses the FIDO protocol for card communication on both Android and iOS platforms.
2. WebNFC
WebNFC is a browser API that provides direct access to NFC tags for reading and writing NDEF records.
- iOS + Safari: Not supported by Apple.
- Android + Chrome: Fully supported (Browser version 89 and above). This method allows for larger data transfers, overcoming the 256-byte limit of FIDO on Android.
Selection Logic
The application automatically determines the best interaction method based on the environment:
| Platform + Browser | Selected Method |
|---|---|
| iOS + Safari | FIDO |
| Android + Chrome | WebNFC |
| Mobile App (WebView) | FIDO (Proxied via App) |
| Incompatible Environments | Prompt user to install the United Network App |
The priority is set in src/api/sub-utils.ts:
- Android devices default to
nfc. - iOS devices and the WebView app default to
fido.
Functions
buildFidoRequest(args)
Location: src/api/fido.ts
Constructs the options object for the WebAuthn authentication ceremony.
- Parameters:
hostname: The RP ID (Relying Party Identifier).data: The raw command data (Uint8Array) to be sent to the card.timeout: Optional session timeout.
- Returns: A
PublicKeyCredentialRequestOptionsobject where the command data is passed as theidinallowCredentials.
buildNfcTagRequest(payload)
Location: src/api/nfc-tag.ts
Prepares the payload for WebNFC transmission.
- Parameters:
payload: The command data (Uint8Array).
- Behavior: If the payload exceeds 255 bytes, it wraps it with a custom opcode (
0xF0) and a length header to handle long commands.
executeNfcSequence(ndefMessage, expectedLength)
Location: src/components/NFCHandlerWindow.vue
Orchestrates a complete command-response cycle over WebNFC.
- Write Command: Sends the NDEF record containing the card command.
- Write Dummy: Sends a padding record to trigger the card's processing and response.
- Read Response: Scans for the response record returned by the card.
Implementation Details
FIDO Implementation
The FIDO flow uses the @simplewebauthn/browser library to interface with the browser's WebAuthn API.
// Example from NFCHandlerWindow.vue
const fidoRequest = buildFidoRequest({
hostname: getHost(),
data: requestData,
});
const response = await startAuthentication(fidoRequest, false);
const signature = await base64url_decode(response.response.signature);WebNFC Implementation
The WebNFC flow uses the NDEFReader class. Because WebNFC is connectionless, the application must manage the sequence of writes and reads manually.
// Example from NFCHandlerWindow.vue
const ndef = new window.NDEFReader();
await ndef.scan({ signal: abortController.signal });
ndef.onreading = async () => {
// Write command record
// Write dummy record
// Read response record
};Usage Example
Generic Card Command Execution
import { buildFidoRequest } from '@/api/fido';
import { startAuthentication } from '@simplewebauthn/browser';
/**
* Example of how the application chooses and executes a card command
*/
async function sendCommandToCard(commandData: Uint8Array) {
const priority = store.state.settings.cardReaderPriority;
if (priority === 'fido') {
const options = buildFidoRequest({ hostname: "united.network", data: commandData });
const assertion = await startAuthentication(options);
return assertion.response.signature;
} else if (priority === 'nfc') {
// WebNFC logic using NDEFReader
// ...
}
}