Skip to main content
Everything here is plain TypeScript over Uint8Array, built on a single transceive primitive. There is no native module and no React Native import — a lint rule enforces that — so these layers are unit tested at 100% branch coverage rather than on a device, and they work in Node and on the web too. That is the point of the split: the native side does one thing, which is move bytes to and from a tag, and everything that can be reasoned about in software lives here where it can be tested properly.

The transport

Every function takes a transport: a function from bytes to bytes. A narrowed tag gives you one directly.
A transport is a plain function, which is what makes all of this testable without hardware: pass async (bytes) => cannedResponse and you have a fake card. It is also why these helpers work against a reader on a laptop, or against captured bytes, with no changes.
For DESFire, JavaCard applets, transit cards, EMV-adjacent work — anything where you select an application by AID and exchange APDUs.

Building commands

selectByName and friends return a CommandApdu; they do not send one. That separation is what lets you log, cache or assert on a command before it goes out.Or write one out. le counts bytes, so 256 is 256 — the encoding quirk where 0x00 means 256 is handled for you:

Sending them

sendApdu handles 61xx and 6Cxx transparently, so the status word you get back is the card’s actual verdict rather than a protocol detail, and data is everything the card had to say rather than the first frame of it.
boolean
default:"true"
Follows a 61xx with GET RESPONSE, concatenating the parts.
boolean
default:"true"
Repeats the command with the corrected length after a 6Cxx.
number
default:"32"
How many follow-up exchanges to allow. A card that answers 61xx forever would otherwise loop until the session times out with nothing to show for it.
For command data larger than one APDU, sendApduChained splits it and sets the chaining bit for you:

Reading the answer

Also available: encodeCommandApdu, decodeResponseApdu, and the constants SW_SUCCESS, CLA_CHAINING, SHORT_MAX_LC, SHORT_MAX_LE, EXTENDED_MAX_LC and EXTENDED_MAX_LE.
On iOS the AID you select must be declared in Info.plist or the tag never arrives at all — no error, no event. See ISO 7816 setup.

Namespaced access

Each protocol is also exported as a namespace, for when a bare name would be ambiguous in your own code:

Without a device

None of this needs a phone. A transport is a function, so a test is a function:
That is how these layers reach 100% branch coverage, and it is available to you for your own card logic too.

Next

Card emulation

The other side of ISO 7816: answering a terminal.

Error reference

transceiveFailed, transceiveTooLong, authenticationFailed.