Javascript Libraries (CS)

The JavaScript libraries are designed to work with browser-based applications and facilitate communication with a SafeKey device. They're constructed using TypeScript and bundled with the parcel tool.


This is a pre-release. All is subject to change. Some of the files and settings are redundant/obsolete. Communication over FIDO2 was not thoroughly tested. The demo application should not be run with the production devices - it can destroy data stored there.


Setting up on Linux OS

To use the device on the Udev-enabled Linux distributions, please install the Udev rules provided within the firmware repository. Otherwise access to the device might be forbidden. Debian-based distributions, including Ubuntu, need this. Fedora does not.

Device preparation

  1. To log in to the CS session, a PIN has to be set first. It can be done using the CS_PIN_SET() function, and can be done only once per device's life-cycle. This is the only required operation to make device working with the Custom Storage. It can be done as well using FIDO2 actions (e.g. via the Windows 10 tool).

  2. Further PIN changes are done through CS_PIN_CHANGE() function, or proper FIDO2 command.

  3. Device can be reset to uninitialized state, with user data removed, by executing the CS_FACTORY_RESET() - then the CS_PIN_SET() is required to be called once again.

  4. If the CS_UNLOCK_GENERATE() was called ever on the device, it starts operating in the PROTECTED mode. This means, that CS data are no longer removed on the FIDO2 reset operation, and login is impossible until the CS_UNLOCK() function is called with the previously generated passphrase (which are 16 bytes of random, device's HWRNG-sourced data). Please keep in mind, that device can never leave the PROTECTED mode, once activated (with the current implementation).

Please refer to manual about the details of the commands' internals.


Here is a brief guide for running the development setup.

Required tools installed: npm.


To install all required npm packages (while being in the main directory):

npm install


To build the JavaScript sources, and run the local server to host them with the parcel tool:

npm start

Error handling

On each error encountered, whether this is a browser, or device sourced, the library throws an Exception with a proper type, as in table below:

Cause is available indirectly by the Exception message as well.

It is expected that both exception types are taken care by the caller of the library. Both have error code properties, which defines the cause. The browser's exception codes are described in detail here: Error codes.

The CommandExecutionError/ERR_USER_NOT_PRESENT is handled by the library, by repeating the request asking user confirmation for 20 times, with 1 second delay in-between. This active polling is required, because device does not wait for the user to touch, and returns immediately without waiting for the user feedback. In the future device's firmware releases this might be changed to have the active loop on the device, if would prove to be more useful for the users.

The device's error codes are described in the technical manual for the SafeKey.

Common errors

These are the common errors returned by the library:

  1. CommandExecutionError/ERR_USER_NOT_PRESENT - this error shows up, when the user will not confirm the request for given command by pressing the touch button.

  2. CommandExecutionError/INVALID_PIN - PIN is not set or invalid. The demo needs device to have PIN set up already. The demo actions should make sure the device is initialized.

  3. DOMException: "The request is not allowed by the user agent or the platform in the current context, possibly because the user denied permission." or DOMException/NotAllowedError (message from the Firefox, might be different on Chrome) - the browser main window was not the active one (on the foreground) during the communication, hence the requests were never sent to the device due to security policy. The same error is as well shown, when users press the cancel in the pop-up. Tabbing away to another windows also causes this exception, as it makes the browser denying the requests to the device.

Handling exceptions

Common code for handling errors is as follows:

import {CS_LOGIN} from "./js/safekey";
import {CommandExecutionError} from "./js/exceptions";

async function run_demo() {
    const PIN_current = '123456';
    try {
        await CS_LOGIN(PIN_current);
    catch (error) {
        if (error instanceof CommandExecutionError && === "ERR_USER_NOT_PRESENT"){
            // The request reached the device, but was not executed due to some conditions being not fulfilled
            console.log('User has not confirmed the action');
        } else if (error instanceof CommandExecutionError && === "INVALID_PIN"){
            // Wrong PIN was supplied by the user
            console.log('Wrong PIN provided');
        } else if (error instanceof CommandExecutionError){
            // Some other execution error occurred, eg. ERR_NOT_ALLOWED or CTAP2_ERR_INVALID_CBOR_TYPE
            console.log(`Other execution error: ${}`);
        } else if(error instanceof DOMException) {
            // General error. Browser has not sent the request to device at all.
            // Potential causes include: user cancelled request, the browser window was no longer on foreground,
            // the device is locked (by using up all PIN attempts) and cannot be used anymore, and others.
            console.log('Browser rejected the request');
        } else {
            // Some unknown exception has occurred - throwing it to the caller
            console.error('Unknown error');
            throw error;


Please refer to the API docs for all the error types to handle.

Source files description

  • ./packaged.ts - NPM package configuration for the SafeKey JS API distribution;

  • ./js/safekey.ts - SafeKey JS API, meant to be used for the development;

  • ./js/cbor.d.ts - CBOR Typescript definitions;

  • ./js/ext_storage.ts - low-level internals: sending and receiving;

  • ./js/ctaphid.ts - low-level internals: transport via the CTAP/Webauthn;

  • ./js/exceptions.ts - device errors exception definition;

  • ./js/helpers.ts - helper functions placeholder;

  • ./js/constants.ts - constants: names and identifiers for errors and commands;

  • ./js/test.ts - demo application tests implementation;

  • ./index.ts - demo application running script.

Configuration used

Following is the configuration used during the development: - Fedora 29 - VS Code 1.39.2 - npm version 6.9.0 - Firefox 69.0.1 (64-bit) - Chromium Version 77.0.3865.90 (Developer Build) Fedora Project (64-bit)

Additionally following were tested (on Fedora 29): - Firefox Nightly 71.0a1 (2019-10-17) (64-bit) - Google Chrome Version 79.0.3941.4 (Official Build) dev (64-bit)

Repository contains configured debugging for both Firefox and Chromium for the VS Code - see .vscode/launch.json path.

Demo application and common pitfalls

Please make sure the device is connected, and press the touch button of the device whenever the site will ask to do so.

Do not use production devices! All data on device should be consider lost or altered, including FIDO U2F / FIDO2 credentials.

Device can be initialized with a PIN only once per life-cycle - later PIN can be only changed, or the factory reset has to be called to get device back to 'uninitialized' state. For changing the PIN, please provide input in format: CURRPIN;NEWPIN, that is current and new PIN should be divided by semicolon. Please do not use ';' character in your PIN, for the sake of the demo application usefulness (it is allowed otherwise). The PIN minimal length is 4 bytes, and maximum is 63. UTF-8 characters are allowed by FIDO2 specification (device accepts binary data).

Demo actions expect to have the PIN set to 01234567890123456. If the wrong PIN was used more than 3 times in the given power cycle, the device will stop accepting the requests, which might result in the DOMException-NotAllowed error. Please reinsert the device in such case. Device has a total of 8 invalid PIN attempts allowed, after which only a factory reset (either CS or FIDO2 action) could reinstate its working state.

Constantly pressing the touch button to accept all requests will not work. Each confirmation has to be done separately, with the finger releasing the touch button, and pressing again on each request. This is by design to not mass-confirm malicious requests.

Browser has to be constantly in the foreground during the action calls, or the requests to the device will be canceled by the browser, and the DomException error will be thrown.

Please note, that write/read tests (via the Run demo button) will fail on Chrome, since it sends requests to CS over FIDO2. Changing the PIN, and other non-data related commands will work though.

ERR_NOT_ALLOWED error on LOGIN action means, that the device is locked, either temporary for the current power-cycle, or permanently after using all PIN attempts, until FIDO2 factory reset will be called.

Using this library in another project

The packaged.ts file is the entrypoint for this library. If you add new functionality that should be exposed in the library to users, please make sure to include the new functionality in the packaged.ts file.

Last updated