npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@samlab-corp/sfm-node

v0.7.0

Published

SFM Protocol V3.6.0 based fingerprint recognition library for Node.js

Readme

@samlab-corp/sfm-node

Node.js library for Suprema SFM (Suprema Fingerprint Module) devices based on SFM Protocol Manual V3.6.0.

Supports fingerprint enrollment, verification (1:1), identification (1:N), template management, firmware upgrade, and continuous scanning via Serial (RS232), RS422/485, or TCP/IP.

Installation

npm install @samlab-corp/sfm-node
# or
yarn add @samlab-corp/sfm-node

Requirements

  • Node.js 18+
  • A Suprema SFM fingerprint module connected via serial port or TCP/IP

Quick Start

import { SfmCommands } from "@samlab-corp/sfm-node";

// Create and connect in one step
const sfm = await SfmCommands.create({
  portPath: "/dev/ttyUSB0",
  baudRate: 115200,
});

// Enroll fingerprint (userId: 1)
await sfm.fingerprint.enroll(1);

// 1:1 Verification
await sfm.fingerprint.verify(1);

// 1:N Identification
await sfm.fingerprint.identify();

await sfm.disconnect();

Constructor Options

const sfm = new SfmCommands({
  portPath: "/dev/ttyUSB0", // Serial port path (used by create() / connect())
  baudRate: 115200, // Serial baud rate (default: 115200)
  debug: false, // Enable debug logging
  commandTimeout: 20000, // Finger/matching-wait command timeout in ms (ES/ESA/VS/VH/IS/DS/SI/SIX/ST, IT/II/IIX, ET/ETX/EI/EIX)
  queryTimeout: 3000, // Query/config command timeout in ms (SR/SW/SS/CA/AR/AW)
  processingTimeout: 10000, // Other command timeout in ms (delete/save/template read/reset ...)
  takeOverExistingPort: false, // Take over a port already opened by another instance in this process (default: fail)
  writeTimeout: 2000, // Per-chunk write (write + drain) timeout in ms
  readTimeout: 3000, // Data read timeout in ms
  secureMode: false, // AES256 encryption mode
  encryptionKey: Buffer, // AES256 encryption key (32 bytes)
  iv: Buffer, // AES256 IV (16 bytes)
  secureCode: Buffer, // Secure code
  encryptionMode: "AES256_CBC", // Encryption mode ('AES256_CBC' | 'AES256_ECB')
  networkMode: false, // RS422/485 network mode
  terminalId: 0, // Network terminal ID
  hexAsciiMode: false, // Hex-ASCII serial bridge mode
  autoReconnect: {
    // Auto-reconnect options
    enabled: true,
    interval: 3000,
    maxRetries: 10,
    rescanAfter: 3, // Re-discover the port by USB VID/PID every N consecutive failures (0 = off)
    usbIds: [{ vendorId: "16d1", productId: "a4a7" }], // VID/PID used for re-discovery (default)
  },
});

Concurrency and timeouts

  • All commands go through a single internal queue: only one command (or multi-packet flow such as enroll, readTemplate, data.send) talks to the device at a time. Calling several methods concurrently is safe; they run in call order.
  • Each command has its own response timeout by kind (queryTimeout / processingTimeout / commandTimeout). A timeout rejects with SfmTimeoutError (code: "SFM_COMMAND_TIMEOUT", or "SFM_WRITE_TIMEOUT" when the device stops accepting data) and the queue moves on. Commands that may wait for the device's Matching Timeout (1:N identify, enroll with CHECK_FINGER) use commandTimeout. If commandTimeout is set above its default (20000), processing commands wait at least that long too (compatible with callers that raised commandTimeout in 0.6.0).
  • When a finger-wait command times out, the library sends CA (cancel) so the module does not stay BUSY (relevant when the TIMEOUT parameter is 0x30 = infinite).
  • Before the first packet of each command, stale frames left in the receive buffer are discarded. Unsolicited IS frames (Free Scan results) are never discarded; they are delivered as unsolicitedPacket events (and to freeScan handlers). This also applies while reading follow-up responses (enrollScan1/2, verify, writeTemplate, writeTemplateMulti, receiveResponse(timeoutMs, expectedCommand)): unless the expected command is IS, an IS frame is routed to the event instead of being returned as the response.
  • fingerprint.cancel() and disconnect() bypass the queue on purpose.
  • Use sfm.exclusive(fn) to run several calls as one uninterrupted unit.

API Reference

Connection Management

| Method | Description | | ------------------------------------------ | --------------------------------------------- | | SfmCommands.create(options) | Create instance and connect in one step | | SfmCommands.listPorts() | List available serial ports | | sfm.connect(portPath?) | Connect to device (uses portPath from options if omitted) | | sfm.disconnect() | Disconnect from device | | sfm.setSecureMode(enabled, options?) | Toggle AES256 encryption mode | | sfm.setNetworkMode(enabled, terminalId?) | Toggle RS422/485 network mode | | sfm.setOnDisconnect(callback) | Register disconnect callback | | sfm.setAutoReconnect(options) | Configure auto-reconnect | | sfm.setAutoReconnectCallbacks(callbacks) | Register reconnect status callbacks | | sfm.isReconnecting | Whether reconnection is in progress (boolean) | | sfm.on(event, listener) / sfm.off(...) | Subscribe to connectionState / unsolicitedPacket events | | sfm.exclusive(fn, options?) | Run several calls as one unit in the command queue | | sfm.pendingCommandCount | Number of running + queued commands |

Events

sfm.on("connectionState", (e) => {
  // e.state: "connected" | "disconnected" | "reconnecting" | "reconnected"
  //        | "reconnectFailed" | "portChanged" | "superseded" | "closed"
  // e.path, e.error?, e.attempt?, e.maxRetries?, e.previousPath? (portChanged)
});

sfm.on("unsolicitedPacket", (packet, raw) => {
  // Packets the host did not request (e.g. Free Scan IS results)
});
  • disconnected fires on every unexpected disconnect, before auto-reconnect starts.
  • reconnectFailed fires when auto-reconnect gives up (onFailed / setOnDisconnect callbacks are still called).
  • Opening a port that another instance in the same process already has open fails by default and the existing instance keeps working (same as 0.6.0). With takeOverExistingPort: true the new instance takes the port over: the old one receives disconnected then superseded, its setOnDisconnect callback is called, and its reconnect loop stops.
  • connectionState listeners, setOnDisconnect and auto-reconnect callbacks run outside the command queue context, so they can safely send commands (they queue as new transactions).
  • disconnect() always stops the reconnect loop, even while the port is closed.

sfm.fingerprint — Enrollment / Verification / Identification

| Method | Description | Return Type | | ----------------------------------------------------------- | ------------------------------------------------------------------------- | ---------------------------------- | | enroll(userId, flag?, options?) | Enroll fingerprint (auto-adapts to device ENROLL_MODE: 1-scan or 2-scan) | EnrollResult | | enrollScan1(userId, flag?, options?) | First scan enrollment | EnrollResult | | enrollScan2(options?) | Second scan enrollment (for SCAN2 modes) | EnrollResult | | verify(userId, options?) | 1:1 Verification — match against a specific user | VerifyResult | | identify(options?) | 1:N Identification — match against all enrolled fingerprints | IdentifyResult | | deleteTemplate(userId) | Delete a specific user's fingerprint template | CommandResult | | deleteAllTemplates() | Delete all fingerprint templates | CommandResult | | listUserIds(options?) | List enrolled user IDs | ListUserIdsResult | | readTemplate(userId, options?) | Read fingerprint template data from device (RTX protocol) | { success, templateData, error } | | writeTemplate(userId, templateData, flag?, options?) | Write fingerprint template to device (ET protocol, 384B chunks) | { success, userId, error } | | writeTemplateMulti(userId, templateData, flag?, options?) | Write fingerprint template to device (ETX extended protocol, multi-chunk) | { success, userId, error } | | captureImage(options?) | Capture fingerprint image (SI command) | CaptureImageResult | | cancel() | Cancel in-progress command (CA: 0x60). Sends packet only, no response wait | CommandResult |

Enrollment Flags

Flags used with enroll, writeTemplate, etc.:

| Flag | Value | Description | | ----------------------- | ------ | ------------------------------- | | SFM_FLAG.NONE | 0x00 | No flag (default) | | SFM_FLAG.CHECK_ID | 0x70 | Check for duplicate ID | | SFM_FLAG.ADD_NEW | 0x71 | Add fingerprint to existing ID | | SFM_FLAG.AUTO_ID | 0x79 | Auto-assign ID | | SFM_FLAG.CHECK_FINGER | 0x84 | Check for duplicate fingerprint |

Examples

// Basic enrollment
const result = await sfm.fingerprint.enroll(1);

// Enroll with duplicate fingerprint check
const result = await sfm.fingerprint.enroll(1, SFM_FLAG.CHECK_FINGER);

// With log callback
const result = await sfm.fingerprint.verify(1, {
  onLog: (msg) => console.log(msg),
});

// Template backup / restore
const { templateData } = await sfm.fingerprint.readTemplate(1);
await sfm.fingerprint.writeTemplate(2, templateData, SFM_FLAG.ADD_NEW);

// List enrolled users
const list = await sfm.fingerprint.listUserIds();
// { success: true, userIds: [1, 2, 3], count: 3, error: null }

// Capture fingerprint image
const image = await sfm.fingerprint.captureImage();
// { success: true, imageData: Buffer, width: 256, height: 288, error: null }

// Cancel in-progress scan (e.g. user navigated away)
const enrollPromise = sfm.fingerprint.enrollScan1(userId);
await sfm.fingerprint.cancel();
const result = await enrollPromise;
// { success: false, cancelled: true, step: 1, error: '스캔 1 실패: 0x81' }

Return Types

interface EnrollResult {
  success: boolean;
  cancelled?: boolean; // true if cancelled by CA command (0x81)
  step: number; // Current scan step (1 or 2)
  userId?: number;
  error?: string;
}

interface VerifyResult {
  success: boolean;
  userId?: number;
  error: string | null;
}

interface IdentifyResult {
  success: boolean;
  userId: number;
  error: string | null;
}

interface ListUserIdsResult {
  success: boolean;
  userIds: number[];
  count: number; // Number of enrolled templates
  error: string | null;
}

interface CaptureImageResult {
  success: boolean;
  imageData: Buffer | null;
  width: number;
  height: number;
  error: string | null;
}

sfm.system — System Configuration / Diagnostics

| Method | Description | Return Type | | ------------------------------- | ------------------------------------------------------------- | ------------------------------------------------------- | | getSystemStatus() | Get module and sensor status | SystemStatusResult | | getDeviceInfo() | Get firmware version, build number, serial number | { firmwareVersion, buildNumber, serialNumber, error } | | getParam(key) | Read system parameter | ParamResult | | setParam(key, value) | Write system parameter (RAM only) | CommandResult | | writeConfig(key, value) | Write system parameter + save to flash (setParam + saveParam) | CommandResult | | saveParam() | Save current parameters to flash | CommandResult | | reset() | Reset module | CommandResult | | selfTest(command?, dataSize?) | Communication self-test (data round-trip verification) | SelfTestResult |

Examples

// Device info
const info = await sfm.system.getDeviceInfo();
// { firmwareVersion: '3.10', buildNumber: '19.7.9.32', serialNumber: 12345, error: null }

// System status
const status = await sfm.system.getSystemStatus();
// { success: true, moduleStatusLabel: 'ALIVE', sensorStatusLabel: 'ALIVE', ... }

// Read parameter
const param = await sfm.system.getParam("SECURITY_LEVEL");
// { value: 0x33, error: null }

// Write parameter (string alias or numeric value)
await sfm.system.setParam("SECURITY_LEVEL", "HIGH");
await sfm.system.saveParam();

// Or use writeConfig to write + save in one call
await sfm.system.writeConfig("ENROLL_MODE", "SCAN2_MERGE");

// Self-test
const test = await sfm.system.selfTest();
// { success: true, sent: 65536, received: 65536, elapsed: 1200, error: null }

Return Types

interface SystemStatusResult {
  success: boolean;
  moduleStatus?: number; // 0x30=ALIVE, 0x34=BUSY
  moduleStatusLabel?: string;
  sensorStatus?: number; // 0x30=ALIVE, 0x31=ERROR
  sensorStatusLabel?: string;
  error: string | null;
}

interface ParamResult {
  value: number;
  error: string | null;
}

interface CommandResult {
  success: boolean;
  error: string | null;
}

interface SelfTestResult {
  success: boolean;
  sent: number;
  received: number;
  elapsed: number; // ms
  error: string | null;
}

sfm.freeScan — Continuous Fingerprint Scanning Mode

The device automatically scans fingerprints and delivers identification results via callback.

| Method | Description | Return Type | | -------------------------- | ---------------------------------------------------------- | ---------------------- | | start() | Start FreeScan (auto-configures AUTO_RESPONSE + FREE_SCAN) | { success, message } | | stop() | Stop FreeScan | { success, message } | | isRunning | Whether FreeScan is active (boolean) | — | | isDeviceFreeScanActive() | Check if device FREE_SCAN parameter is ON | boolean | | onDataHandler(handler) | Register external data handler (FreeScanDataHandler) | — | | offDataHandler() | Remove external data handler | — | | onErrorHandler(handler) | Register external error handler (FreeScanErrorHandler) | — | | offErrorHandler() | Remove external error handler | — | | onStatusHandler(handler) | Register handler for other IS results (FreeScanStatusHandler) | — | | offStatusHandler() | Remove status handler | — |

  • stop() checks the device write results and retries transient failures (BUSY 0x80, TIME_OUT 0x6c, TRY_AGAIN 0x6b, or no response) up to 3 times, 200 ms apart. If FREE_SCAN OFF still fails it reads the device value: if the device reports OFF it continues, otherwise it throws and keeps listening (the device may still be scanning). If AUTO_RESPONSE OFF fails it stops listening and throws. Wrap stop() in try/catch — before 0.7.0 it never threw.
  • Other commands can be sent while Free Scan is running (the queue orders them and IS results that arrive meanwhile are still delivered). While the module is processing a finger it may answer BUSY to commands other than SR/SS, so finger commands such as enroll should still be wrapped with stop() / start().

Examples

// Register data handler
sfm.freeScan.onDataHandler((result) => {
  if (result.success) {
    console.log(`Identified user: ${result.userId}`);
  } else {
    console.log("Unregistered fingerprint");
  }
});

// Register error handler (optional)
sfm.freeScan.onErrorHandler((error) => {
  console.error("FreeScan error:", error);
});

// Start FreeScan
await sfm.freeScan.start();

// Check status
console.log(sfm.freeScan.isRunning); // true

// Stop FreeScan
await sfm.freeScan.stop();

// Remove handlers when no longer needed
sfm.freeScan.offDataHandler();
sfm.freeScan.offErrorHandler();

Callback Types

interface FreeScanResult {
  success: boolean;
  userId?: number; // User ID on successful identification
  flag?: number; // Error flag on failure
}

type FreeScanDataHandler = (result: FreeScanResult) => void;
type FreeScanErrorHandler = (error?: Error) => void;

// IS results other than SUCCESS / NOT_FOUND
// (SCAN_SUCCESS interim, TIMEOUT_MATCH, FAKE_DETECTED, REJECTED_ID, ...)
interface FreeScanStatus {
  flag: number;
  userId: number;
  message: string;
}
type FreeScanStatusHandler = (status: FreeScanStatus) => void;

sfm.firmware — Firmware Upgrade

| Method | Description | | ---------------------------------------------------- | ---------------------------------------- | | upgrade(filePath, options?) | Firmware upgrade via serial handshake | | upgradeViaProtocol(filePath, chunkSize?, options?) | Firmware upgrade via SFM packet protocol | | loadFirmware(filePath) | Load firmware file and build packet |

Examples

// Serial handshake method (typical)
await sfm.firmware.upgrade("/path/to/firmware.bin", {
  onProgress: (percent, sent, total) => console.log(`${percent}%`),
  onStatus: (msg) => console.log(msg),
});

// Protocol method
await sfm.firmware.upgradeViaProtocol("/path/to/firmware.bin");

Options Type

interface FirmwareOptions {
  onProgress?: (percent: number, sent: number, total: number) => void;
  onStatus?: (message: string) => void;
}

sfm.data — Multi-Packet Data Transfer

Low-level multi-packet data transfer. Handles checksum verification and AES256 encryption automatically.

| Method | Description | | ------------------------------------------- | ------------------------- | | send(command, data, chunkSize?, options?) | Send multi-packet data | | recv(command, totalSize, options?) | Receive multi-packet data |

await sfm.data.send(command, buffer, 0x4000, {
  onProgress: (packetIndex, totalPackets) => { ... },
});

const data = await sfm.data.recv(command, totalSize);

System Parameter Key Reference

Keys and available values for sfm.system.getParam(key), sfm.system.setParam(key, value), and sfm.system.writeConfig(key, value).

Timeout / Timing

| Key | Description | Values | | ------------------ | -------------------------------------- | ------------------------------------------------------------------------------- | | TIMEOUT | Fingerprint input wait time | 'INFINITE', '1S', '2S', '3S', '4S', '5S', '10S', '15S', '20S' | | MATCHING_TIMEOUT | Matching timeout | 'INFINITE', '1S', '3S', '5S', '10S', '20S' | | RESPONSE_DELAY | Response delay | 'NONE', '20MS', '40MS', '60MS', '80MS', '100MS', '200MS' | | FREE_SCAN_DELAY | FreeScan consecutive recognition delay | 'NONE', '1S', '2S', '3S', '5S', '10S' |

Enrollment / Authentication

| Key | Description | Values | | --------------------- | -------------------------------------- | ----------------------------------------------------------------------------------------------------- | | ENROLL_MODE | Enrollment mode | 'SCAN1' (single scan), 'SCAN2_MERGE' (2 scans merged), 'SCAN2_DUAL' (2 scans stored separately) | | SECURITY_LEVEL | Security level (FAR) | 'LOW', 'NORMAL', 'HIGH', 'HIGHEST', 'AUTO_NORMAL', 'AUTO_SECURE', 'AUTO_MORE_SECURE' | | ENROLL_DISPLACEMENT | Finger displacement during enrollment | 'NONE', '1MM', '3MM', '5MM', '10MM' | | PROVISIONAL_ENROLL | Provisional enrollment mode | 'PERMANENT', 'TEMPORARY' | | PASS_WHEN_EMPTY | Authentication result when DB is empty | 'FAIL', 'PASS' |

Sensor / Image

| Key | Description | Values | | -------------------- | --------------------------- | ---------------------------------------------------------------- | | SENSOR_TYPE | Sensor type (read-only) | 'FC', 'OPTICAL', 'TC', 'OC', 'OL', 'FOH02', 'TS4' | | SENSITIVITY | Sensor sensitivity | 'LV0' ~ 'LV7' | | IMAGE_FORMAT | Image transfer format | 'RAW_GRAY', 'BINARY', 'GRAY_4BIT', 'WSQ' | | IMAGE_QUALITY | Image quality threshold | 'WEAK', 'MODERATE', 'STRONG', 'STRONGEST' | | ROTATE_IMAGE | Sensor image rotation | 'NORMAL', 'FLIP' | | ROTATION | Matching rotation tolerance | 'DEG15', 'DEG30', 'DEG45', 'DEG60', 'DEG75', 'DEG90' | | LIGHTING_CONDITION | Lighting environment | 'OUTDOOR', 'INDOOR' |

Communication

| Key | Description | Values | | ------------------- | -------------------------- | ----------------------------------------------------------------------------------------- | | BAUDRATE | Baud rate | '9600', '19200', '38400', '57600', '115200', '230400', '460800', '921600' | | ENCRYPTION_MODE | Encryption mode | 'OFF', 'ON' | | ASCII_PACKET | Packet exchange format | 'HEX', 'ASCII' | | NETWORK_MODE | Network communication mode | 'SINGLE', 'NETWORK' | | SEND_SCAN_SUCCESS | Send SCAN_SUCCESS response | 'OFF', 'ON' |

Operation Mode

| Key | Description | Values | | --------------- | --------------------------- | ------------------------------------------------- | | FREE_SCAN | Idle-state auto scan | 'OFF', 'ON' | | AUTO_RESPONSE | GPIO/FreeScan auto response | 'OFF', 'HOST', 'AUX', 'BOTH' | | FAST_MODE | 1:N matching speed boost | 'OFF', 'LV1' ~ 'LV4', 'FASTEST', 'AUTO' | | WATCHDOG | Watchdog timer | 'OFF', 'ON' |

Template / Security

| Key | Description | Values | | -------------------------- | ---------------------------- | ----------------------------------------- | | TEMPLATE_TYPE | Template type | 'SUPREMA', 'ISO', 'ANSI' | | ENHANCED_PRIVACY | Enhanced privacy mode | 'OFF', 'ON' | | FAKE_FINGER_DETECTION | Fake finger detection | 'OFF', 'WEAK', 'NORMAL', 'STRONG' | | CHECK_LATENT_FINGERPRINT | Latent fingerprint detection | 'ENROLL_ONLY', 'NEVER', 'ALWAYS' |

Read-Only Parameters

| Key | Description | | ------------------ | ------------------------------------- | | TEMPLATE_SIZE | Template size | | MODULE_ID | Module ID | | FIRMWARE_VERSION | Firmware version | | SERIAL_NUMBER | Serial number | | BUILD_NUMBER | Build number | | ENROLLED_FINGER | Number of enrolled fingerprints | | AVAILABLE_FINGER | Number of available fingerprint slots | | BAUDRATE2 | Secondary baud rate | | VOLTAGE_WARNING | Voltage warning threshold | | POWER_OFF | Auto power-off timeout |


Error Codes

| Code | Value | Description | | ------------------------ | ------ | ------------------------------------ | | SFM_ERROR.SUCCESS | 0x61 | Success | | SFM_ERROR.SCAN_SUCCESS | 0x62 | Scan success (intermediate response) | | SFM_ERROR.SCAN_FAIL | 0x63 | Scan failed | | SFM_ERROR.NOT_FOUND | 0x69 | Data not found | | SFM_ERROR.NOT_MATCH | 0x6a | Mismatch | | SFM_ERROR.TRY_AGAIN | 0x6b | Retry needed (poor image quality) | | SFM_ERROR.TIME_OUT | 0x6c | Timeout | | SFM_ERROR.MEM_FULL | 0x6d | Memory full | | SFM_ERROR.EXIST_ID | 0x6e | ID already exists | | SFM_ERROR.CONTINUE | 0x74 | Continue data transfer | | SFM_ERROR.UNSUPPORTED | 0x75 | Unsupported command |

TypeScript

All types are exported:

import {
  SfmCommands,
  SFM_ERROR,
  SFM_FLAG,
  ENCRYPTION_MODE,
} from "@samlab-corp/sfm-node";

import type {
  SfmClientOptions,
  SfmParamKey,
  SfmParamAliasMap,
  EncryptionMode,
  SerialPortInfo,
  EnrollResult,
  VerifyResult,
  IdentifyResult,
  CommandResult,
  ListUserIdsResult,
  CaptureImageResult,
  SystemStatusResult,
  ParamResult,
  SelfTestResult,
  FreeScanResult,
  FreeScanDataHandler,
  FreeScanErrorHandler,
  FirmwareOptions,
} from "@samlab-corp/sfm-node";

License

MIT