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

@uzum-tech/e-imzo

v1.0.0

Published

Типизированный клиент E-IMZO: подключение по WebSocket, список ключей, подпись PKCS#7

Readme

@uzum-tech/e-imzo

Типизированный клиент E-IMZO для браузера: список ключей (PFX и FTJC), загрузка ключа и подпись PKCS#7 — опционально открепленная и с меткой времени.

Пакет не зависит от фреймворка: ноль импортов Vue/React и ноль рантайм-зависимостей.

Установка

pnpm add @uzum-tech/e-imzo

Быстрый старт

Клиент создаётся один раз на приложение и переиспользуется — так проверка версии и установка API-ключей не повторяются на каждый вызов:

import { EImzo } from '@uzum-tech/e-imzo';

export const eImzoClient = new EImzo({
  wsUrl: 'wss://127.0.0.1:64443/service/cryptapi',
  apiKeys: [
    {
      domain: 'localhost',
      key: 'API-KEY'
    }
  ]
});

Подпись одним вызовом — клиент сам возьмёт список ключей, загрузит выбранный и создаст PKCS#7:

const result = await eImzoClient.sign(data);

result.pkcs7;
result.key.TIN;

Конфигурация

| Поле | Обязательное | По умолчанию | Описание | | --- | --- | --- | --- | | wsUrl | да | — | Адрес локального сервиса E-IMZO | | apiKeys | да | — | Пары «домен → ключ» | | minVersion | нет | 336 | Минимальная версия E-IMZO (major * 100 + minor) | | minVersionExtended | нет | 412 | Версия, начиная с которой ключи с FTJC-токенов не запрашиваются отдельно | | timeoutMs | нет | 15000 | Таймаут одного запроса к E-IMZO |

API-ключ выдаётся под конкретный домен: без ключа для домена приложения работа не начнётся — будет api-key-rejected.

timeoutMs не действует на шаги, где E-IMZO открывает нативное окно ввода пароля или ПИН-кода: отсчитывать сетевой таймаут, пока пользователь печатает, бессмысленно. Такой запрос ждёт столько, сколько нужно, но не зависает навсегда — при отмене ввода или закрытии E-IMZO он завершится ошибкой.

Форматы apiKeys

Массив пар:

apiKeys: [
  {
    domain: 'localhost',
    key: 'API-KEY-1'
  }
]

Плоский список — тот же формат, что у официального e-imzo-client.js, так что готовый список ключей переносится копипастом:

apiKeys: [
  'localhost', 'API-KEY-1',
  'cabinet.example.uz', 'API-KEY-2'
]

Строка домен=ключ с парами через запятую — чтобы весь список лежал в одной переменной окружения:

VITE_E_IMZO_API_KEYS="localhost=API-KEY-1,cabinet.example.uz=API-KEY-2"
apiKeys: import.meta.env.VITE_E_IMZO_API_KEYS

Пробелы вокруг домена, ключа и запятых игнорируются, хвостовая запятая допустима. Пустой список, строка без =, плоский список нечётной длины, пустой домен или пустой ключ — это invalid-config при первом обращении к E-IMZO.

Подпись

Подписать можно строку, Uint8Array или ArrayBuffer. Строка кодируется как UTF-8, поэтому кириллица подписывается корректно. А вот файл — PDF, docx, картинку — через строку прогонять нельзя: любой байт больше 0x7F превратится в UTF-8 пару, и подпись ляжет на данные, которых в файле нет. Файлы передавайте байтами:

const bytes = new Uint8Array(await file.arrayBuffer());

await eImzoClient.sign(bytes);

Если строка уже в base64, скажите об этом — она уйдёт в E-IMZO как есть:

await eImzoClient.sign(base64String, {
  base64Encoded: true
});

Для байтов флаг игнорируется: кодировать их всё равно нужно клиенту.

Открепленная подпись и метка времени:

await eImzoClient.sign(data, {
  detached: true,
  timestamper: (signatureHex) => requestTimestampToken(signatureHex)
});

Выбор ключа

По умолчанию берётся первый доступный ключ. Чтобы выбрать свой — передайте selectKey, например по ПИНФЛ владельца:

await eImzoClient.sign(data, {
  selectKey: (keys) => keys.find((key) => key.PINFL === pinfl)
});

selectKey может быть асинхронным — этого достаточно, чтобы показать пользователю свой диалог выбора прямо внутри sign:

await eImzoClient.sign(data, {
  selectKey: (keys) => showKeyPicker(keys)
});

Если ключ выбран раньше — передайте его через key, тогда список запрашиваться не будет. key имеет приоритет над selectKey:

const keys = await eImzoClient.listKeys();
const key = await showKeyPicker(keys);

await eImzoClient.sign(data, {
  key
});

Пачка документов

Пароль спрашивается на каждый sign. Чтобы подписать несколько документов одним вводом пароля, загрузите ключ один раз и вызывайте createPkcs7 напрямую:

const keyId = await eImzoClient.loadKey(key);

for (const document of documents) {
  await eImzoClient.createPkcs7(keyId, document);
}

Ключи

listKeys() возвращает PFX-сертификаты и, на старых версиях E-IMZO, ключи с FTJC-токенов. X.500-строка сертификата разбирается в поля CN, TIN, PINFL, O, T, серийный номер и даты. Ключи, у которых нет ни СТИР, ни ПИНФЛ, в список не попадают.

validFrom и validTo — обычные Date. Если даты в сертификате записаны не так, как ожидает парсер, ключ всё равно остаётся в списке, но его Date будет Invalid Date — скрывать такой ключ от пользователя библиотека не берётся. Прежде чем показывать или сравнивать срок действия, проверьте разбор:

const expired = !Number.isNaN(key.validTo.getTime())
  && key.validTo < new Date();

Методы

| Метод | Что делает | | --- | --- | | sign(data, options?) | Подпись целиком: список ключей, загрузка выбранного, PKCS#7 | | listKeys() | Список доступных ключей | | loadKey(key, options?) | Загружает ключ и возвращает его идентификатор; { verify: false } пропускает ввод пароля | | createPkcs7(keyId, data, options?) | PKCS#7 для уже загруженного ключа | | connect() | Проверяет версию и ставит API-ключи; вызывается автоматически, отдельно нужен, только чтобы проверить связь заранее | | getVersion() | Диагностический пинг: жив ли сервис и какой версии. Клиент к работе не готовит — подписывать после него ещё нельзя | | getConfig() | Конфиг с подставленными значениями по умолчанию | | disconnect() | Забыть состояние соединения: следующий вызов начнёт с проверки версии и API-ключей | | reload() | То же плюс перечитать конфиг |

Проверка версии и установка API-ключей выполняются один раз на инстанс клиента. Если E-IMZO перезапустили и он забыл выданные ключи, клиент заметит это сам и повторит подготовку на следующем вызове — disconnect() руками для этого не нужен.

Ошибки

Все отказы приходят как EImzoError с полями code и reason:

import { EImzoError, ErrorTypes } from '@uzum-tech/e-imzo';

try {
  await eImzoClient.sign(data);
} catch (error) {
  if (EImzoError.matches(error, ErrorTypes.Code.NOT_INSTALLED)) {
    // E-IMZO не запущен
  }
}

EImzoError.matches(error) без второго аргумента отвечает только на вопрос «это отказ E-IMZO?» — как error instanceof EImzoError, но без импорта типа в месте проверки.

| Код | Когда | | --- | --- | | invalid-config | apiKeys пуст или строка не разобралась в пары «домен → ключ» | | not-installed | E-IMZO не запущен, недоступен или закрыл соединение раньше ответа | | timeout | Сервис не ответил за timeoutMs | | invalid-response | Ответ не разобрался как JSON, оказался не объектом или версия в нём не читается числом | | unsupported-version | Версия не пришла или ниже minVersion | | api-key-rejected | Домен не совпал с выданным API-ключом | | key-not-found | Список ключей пуст или selectKey ничего не вернул | | key-load-failed | Неверный пароль ключа, ПИН токена или пустой идентификатор ключа в ответе | | sign-failed | E-IMZO не создал PKCS#7 | | timestamp-failed | E-IMZO не вернул данные для метки времени или не прикрепил токен |

Окружение

Пакет собран как ESM и рассчитан на браузер: нужны WebSocket, TextEncoder и btoa. На импорте модуль не трогает window, поэтому его можно импортировать и в SSR-сборке — там он просто откажет с not-installed при первом запросе.

Типы отдаются вместе с пакетом: ConfigTypes, KeyTypes, SignTypes и ErrorTypes.