Начало работы
Этот документ содержит инструкции по встраиванию PC WEB SDK в веб-приложение. Чтобы лучше понять, как работать с PC, рекомендуем также ознакомиться со следующими документами:
- Архитектура и функциональность — презентация
- Архитектура и функциональность
PC WEB SDK позволяет реализовать всю функциональность PC прямо в вашем веб-приложении.
Интеграция проекта
Библиотеку PC WEB SDK можно импортировать в ваш проект из репозитория npm SafeTech.
Необходимо добавить следующую строку:
@safetech:registry=https://nexus.paycontrol.org/repository/npm/
в файл .npmrc в корне проекта, а затем установить pcsdk.
npm i @safetech/pcsdk
После установки библиотеки PCSDK её можно импортировать в проект:
import PCSDK from ‘@safetech/pcsdk’
Ваше веб-приложение должно использовать один экземпляр класса PCSDK во время работы.
Инициализация SDK
Чтобы инициализировать библиотеку PC SDK, необходимо вызвать асинхронный метод PCSDK.init(callback?, errorCallback?, options?).
Метод принимает 3 необязательных параметра:
1. callback(status) — вызывается при каждом изменении состояния подключённых токенов. В функцию передаётся объект:
{ connected: Array<number>, disconnected: Array<number> }
* connected — массив идентификаторов подключённых токенов
* disconnected — массив идентификаторов токенов, которые были отключены
2. errorCallback(error) — вызывается, если во время периодического опроса состояния произошла ошибка (например, если плагин перестал отвечать).
3. options — объект с необязательным параметром конфигурации: { rutokenOnly?: boolean }. Параметр rutokenOnly определяет, какие типы токенов будут использоваться библиотекой:
* true (значение по умолчанию) — библиотека работает только с Rutoken.
* false — позволяет использовать ECDSA и другие поддерживаемые токены, а не только Rutoken.
Если параметр rutokenOnly = true (значение по умолчанию), то при ошибке инициализации Rutoken-плагина метод PCSDK.init() генерирует исключение (throw Error). В этом режиме библиотека ожидает, что плагин Rutoken должен быть доступен и корректно загружен, поэтому любая ошибка загрузки приводит к немедленному прерыванию инициализации.
Если же rutokenOnly = false, ошибка загрузки плагина не вызывает исключение — она будет возвращена в результате Promise в виде: plugin: { ok: false, error: PCError }
Метод PCSDK.init(callback, errorCallback, options) — возвращает Promise, результатом которого является объект:
javascript
{
db: { ok: boolean, error: PCError | null },
plugin: { ok: boolean, error: PCError | null }
}
Инициализацию PCSDK необходимо выполнить до вызова любых других методов библиотеки.
import PCSDK from ‘@safetech/pcsdk’
try {
const result = await PCSDK.init(callback, errorCallback);
if (!result.db.ok) {
// Требуется обработать ошибку инициализации хранилища пользователей
}
if (!result.plugin.ok) {
// Требуется обработать ошибку инициализации Рутокен плагина
}
} catch (error) {
console.error('Ошибка инициализации PCSDK:', error);
}
Структура объекта error
Объект plugin.error присутствует в ответе всегда и отражает состояние плагина.
* Если плагин инициализирован корректно — значение plugin.error будет равно null.
* Если при инициализации возникла ошибка — в plugin.error возвращается объект типа PCError, содержащий информацию о причине сбоя.
Пример успешной инициализации:
{
db: { ok: true, error: null },
plugin: { ok: true, error: null }
}
Пример с ошибкой плагина:
{
db: { ok: true, error: null },
plugin: {
ok: false,
error: {
code: 3,
errorName: "PC_ERROR_RUTOKEN_EXTENSION_NOT_INSTALLED",
link: "https://chromewebstore.google.com/detail/ohedcglhbbfdgaogjhcclacoccbagkjg",
message: "Расширение Рутокен не установлено",
name: "PCError"
}
}
}
Регистрация пользователей
Процесс регистрации пользователя включает следующие этапы:
- Получение персонализационных данных с сервера PC (через deeplink или deeplink + код активации). Подробнее см. в документе Архитектура и функциональность.
- Регистрация пользователя PC User на сервере PC. Во время этого процесса PCSDK генерирует необходимые наборы ключей.
- Сохранение ключей пользователя в хранилище токена для дальнейшего использования.
Шаг 1. Импортируйте PCUser из соответствующего источника
Прежде всего, вам необходимо получить список подключенных токенов и информацию о них, используя метод PCSDK.getTokensInfo().
Затем нужно создать объект PCUser (он же ключ пользователя) из строки JSON, полученной из deeplink или через API PC Server, указав deviceId, на котором пользователь будет храниться.
Метод PCUsersManager.importUser(source: string | PCUserJSON, deviceId: number) — возвращает Promise, результатом которого является объект PCUser.
import PCSDK, { PCUsersManager } from ‘@safetech/pcsdk’;
try {
/*
devices возвращает:
Array<{
label: string,
serial: string,
leftSpace: number,
deviceId: number,
}>
*/
const devices = await PCSDK.getTokensInfo();
const user = await PCUsersManager.importUser(source, deviceId);
console.log('Импорт завершён:', user);
} catch(error) {
console.error('Ошибка при импорте пользователя:', error);
}
Шаг 2. Проверка необходимости активации PCUser
При передаче данных персонализации вашим клиентам инфраструктура может использовать разные способы передачи данных — deeplink, deeplink + код активации или JSON.
Перед регистрацией необходимо:
1. Импортировать данные пользователя в объект PCUser;
2. Проверить, требуется ли активация (user.isActivated() || user.hasKeyPair());
3. При необходимости выполнить активацию с помощью кода активации.
Для активации используется метод PCUsersManager.activate(user: PCUser, activationCode: string)
import PCSDK, { PCUsersManager } from ‘@safetech/pcsdk’;
try {
const devices = await PCSDK.getTokensInfo();
if (!devices.length) {
console.log('Нет подключённых токенов');
return;
}
const { deviceId } = devices[0];
const user = await PCUsersManager.importUser(source, deviceId);
if (!user.isActivated()) {
// Для активации PCUser необходим код активации,
// полученный в соответствии с правилами вашей инфраструктуры
const activationCode = getActivationCode();
await PCUsersManager.activate(user, activationCode);
console.log('Пользователь успешно активирован');
} else {
console.log('Активация не требуется');
}
// Если ошибок нет — пользователь успешно импортирован и готов к регистрации
} catch(error) {
console.error('Ошибка при импорте или активации пользователя:', error);
}
Шаг 3. Регистрация ключа на сервере PC
Теперь можно зарегистрировать PCUser на сервере PC.
Генерируется пара ключей: открытый ключ отправляется на сервер, а закрытый — шифруется паролем и слоем шифрования.
Для регистрации используется метод PCUsersManager.register(user: PCUser, password: string).
import { PCUsersManager } from ‘@safetech/pcsdk’;
try {
// запрос пароля токена
const password = promptPassword();
await PCUsersManager.register(user, password)
} catch(error) {
console.error('При регистрации произошла ошибка', error);
}
// Если ошибок нет — пользователь успешно зарегистрирован
PCSDK нигде не сохраняет введённые пароли. Клиент должен самостоятельно их запомнить.
Шаг 4. Сохранение объекта PCUser в хранилище токена
На последнем этапе нужно сохранить объект PCUser в хранилище токена. Каждый пользователь сохраняется под уникальным именем (keyName), которое задаётся клиентом вашего приложения.
Ключи, используемые для подтверждения транзакций (HMAC-ключ и закрытый ключ для подписи ECDSA), дополнительно защищены паролем токена и шифрованием.
Если устройство поддерживает WebAuthn (например, Touch ID, Face ID или Windows Hello), возможно использование биометрической аутентификации вместо пароля.
Для активации биометрии необходимо выполнить две проверки, основанные на флагах пользователя:
* user.hasOnlineCredentials() — Возвращает true, если у пользователя активирован keyFlag, разрешающий использование online credentials. Если флаг отключён, SDK обязан работать только с локальным паролем, без запроса salt + online credentials с сервера, которые SDK использует для вычисления ключей. Хотя WebAuthn и online credentials — это разные механизмы, WebAuthn может быть включён только при наличии online credentials у данного пользователя.
* user.isWebAuthnAllowed() — Возвращает true, если пользователю разрешено использовать WebAuthn. Если биометрия отключена в настройках, метод вернёт false.
Метод PCUsersManager.store(user: PCUser, keyName: string, password: string, useBiometry: boolean) сохраняет объект пользователя в хранилище токена, включая активацию biometry-mode при useBiometry = true
Пример проверки WebAuthn:
let useBiometry = false;
try {
const isPlatformAuthenticatorAvailable =
typeof window.PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable === 'function'
? await window.PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable()
: false;
if (
user.type === 0 &&
isPlatformAuthenticatorAvailable &&
user.isWebAuthnAllowed() &&
user.hasOnlineCredentials()
) {
useBiometry = true;
} else {
console.warn('Биометрические данные недоступны');
}
} catch (error) {
console.warn('Произошла ошибка при использовании биометрии', e);
useBiometry = false;
}
return useBiometry
Пример процесса сохранения ключа:
// запрос уникального имени ключа от клиента или генерация в приложении
const keyName = promptKeyName();
// запрос пароля токена
const password = promptPassword();
// использовать ли биометрию (WebAuthn)
const useBiometry = await askUserToUseBiometry();
// сохранение ключа в хранилище
try {
await PCUsersManager.store(user, keyName, password, useBiometry);
} catch(error) {
console.error('Ошибка при сохранении пользователя:', error);
}
// Если ошибок нет — пользователь успешно сохранён
Общая схема регистрации ключа
import PCSDK, { PCUsersManager } from ‘@safetech/pcsdk’;
// 1. Импорт PCUser
let user = null;
try {
const devices = await PCSDK.getTokensInfo();
if (!devices.length) {
console.log('Нет подключённых токенов');
return;
}
const { deviceId } = devices[0];
user = await PCUsersManager.importUser(source, deviceId);
} catch(error) {
console.error('Ошибка при импорте пользователя:', error);
}
// 2. Проверка необходимости активации
try {
if (!user.isActivated()) {
const activationCode = getActivationCode();
await PCUsersManager.activate(user, activationCode);
} else {
// активация не требуется
}
} catch(error) {
console.error('Ошибка при активации пользователя:', error);
}
// 3. Регистрация пользователя
try {
const password = promptPassword();
await PCUsersManager.register(user, password)
} catch(error) {
console.error('При регистрации произошла ошибка', error);
}
// 4. Сохранение ключа
const keyName = promptKeyName();
const password = promptPassword();
const useBiometry = await askUserToUseBiometry();
try {
await PCUsersManager.store(user, keyName, password, useBiometry);
} catch(error) {
console.error('Ошибка при сохранении пользователя:', error);
}
Обработка транзакций
Общий сценарий обработки транзакции включает следующие этапы:
- Вызвать
PCUsersManager.listStorage()для получения списка пользователей из хранилища. - Отфильтровать пользователей, для которых нужно загрузить транзакции.
- Для каждого пользователя вызвать
PCTransactionsManager.getTransactionList(user: PCUser)и проверить наличие транзакций. - Для каждой транзакции вызвать
PCTransactionsManager.getTransaction(user: PCUser, transactionId: string)для получения данных.- При наличии вложений использовать
PCTransactionsManager.getTransactionBinaryData(user: PCUser, transaction: PCTransaction).
- При наличии вложений использовать
- Отобразить загруженные транзакции пользователю с кнопками подтверждения и отклонения.
- Обработать действия пользователя:
- запросить пароль.
- для подтверждения транзакции вызвать метод
PCTransactionsManager.sign(user: PCUser, transaction: PCTransaction, password: string). - для отклонения транзакции вызвать метод
PCTransactionsManager.decline(user: PCUser, transaction: PCTransaction, password: string).
Получение данных транзакции
Пример загрузки доступных транзакций:
import { PCUsersManager, PCTransactionsManager } from '@safetech/pcsdk';
try {
const users = await PCUsersManager.listStorage();
// фильтрация списка пользователей
for (let user of users) {
if (user.userId === TARGET_USER_ID || user.keyName === TARGET_KEY_NAME) {
// загрузка списка транзакций
const transactionsIdArr = await PCTransactionsManager.getTransactionList(user);
if (transactionsIdArr.length) {
for (let transactionId of transactionsIdArr) {
const transaction = await PCTransactionsManager.getTransaction(user, transactionId);
if (transaction.hasBinaryData()) {
await PCTransactionsManager.getTransactionBinaryData(user, transaction);
} else {
// нет вложений — можно сразу отображать
}
}
}
}
}
} catch (error) {
console.error('Произошла ошибка при получении транзакции', error);
}
Методы
PCTransactionsManager.getTransactionList()иPCTransactionsManager.getTransaction()лёгкие и создают небольшой сетевой трафик (несколько килобайт).
МетодPCTransactionsManager.getTransactionBinaryData()может использовать значительно больше трафика при загрузке больших вложений.
Отображение данных транзакции
Транзакцию нужно показать пользователю перед подтверждением или отклонением. Для удобного отображения можно использовать методы:
PCTransaction.getTransactionText()— возвращает текст транзакции (может бытьnull, если есть только вложение).PCTransaction.getSnippet()— возвращает краткое описание (может бытьnull).PCTransaction.getStoredBinaryData()— возвращаетUint8Arrayс бинарным вложением (например, PDF).PCTransaction.getTextRenderType()— возвращает тип текста:nullили'raw'для обычного текста,'markdown'для markdown-формата.PCTransaction.getSnippetRenderType()— аналогично предыдущему, но для краткого описания.
Подтверждение или отклонение транзакции
После загрузки и отображения данных транзакции её можно подтвердить методом sign() или отклонить методом decline() класса PCTransactionsManager.
Пример процесса подтверждения:
import { PCTransactionsManager } from ‘@safetech/pcsdk’;
const password = promptPassword();
if (confirmationButtonPressed) {
PCTransactionsManager.sign(user, transaction, password)
.then(/* уведомить пользователя об успешной подписи */)
.catch((error) => {/* обработка ошибки */})
} else {
PCTransactionsManager.decline(user, transaction, password)
.then(/* уведомить пользователя об успешном отклонении */)
.catch((error) => {/* обработка ошибки */})
}
Если транзакция содержит вложение, но оно не было загружено через
PCTransactionsManager.getTransactionBinaryData(), транзакция не будет обработана.
Работа с операциями
Операция (PCOperation) — это набор связанных транзакций (PCTransaction), которые могут быть обработаны одним запросом. Это позволяет уменьшить количество сетевых вызовов и ускорить выполнение.
Алгоритм обработки операций может включать следующие этапы:
- Вызовите
PCUsersManager.listStorage()и при необходимости выполните фильтрацию полученных пользователей. - Для каждого
PCUserвызовитеPCOperationsManager.getOperationsList(user: PCUser)и определите, есть ли операции, ожидающие обработки. - Если доступны операции, получите каждую из них с помощью
PCOperationsManager.getOperation(user: PCUser, operationId: string). ОбъектPCOperationсодержит данные обо всех входящих в неё транзакциях. Однако бинарные данные для каждой транзакции необходимо загружать отдельно. - Для каждой транзакции предоставляемой
PCOperation.getTransactions()проверьте, требуется ли загрузка бинарных данных, вызвавPCTransaction.hasBinaryData(), и при необходимости загрузите их черезPCTransactionsManager.getTransactionBinaryData(user: PCUser, transaction: PCTransaction). - Отобразите операцию желаемым образом. Подробнее о методах отображения транзакций см. раздел «Отображение данных транзакции». Помимо данных транзакций, также должно отображаться описание операции (
PCOperation.getDescription()). - Операции поддерживают частичную обработку: клиент может подтвердить одни транзакции сразу, а другие — отложить. Поэтому следующим шагом будет предоставление интерфейса, позволяющего клиенту выбирать, какие транзакции он хочет обработать сейчас, вызвав
PCTransaction.processOperation(user: PCUser, operation: PCOperation, password: string, transactionsToConfirm: Array<PCTransaction>, transactionsToDecline: Array<PCTransaction>). - Если часть транзакций не была обработана, клиент может вернуться к ним позднее.
Получение данных операции
В следующем фрагменте кода показано, как правильно извлекать данные PCOperation с PC сервера, чтобы их можно было корректно отображать и обрабатывать.
import { PCUsersManager, PCOperationsManager } from '@safetech/pcsdk';
// 1. Найдите пользователя (PCUser) в соответствии с логикой вашего приложения
const targetUser = PCUsersManager.getById(TARGET_USER_ID);
// 2. Получить список операций пользователя
try {
const operationIds = await PCOperationsManager.getOperationsList(targetUser);
if (!operationIds || operationIds.length === 0) {
// Список пуст
return;
}
// 3. Загрузить данные операции
const operationId = operationIds[0];
const pcOperation = await PCOperationsManager.getOperation(targetUser, operationId);
// Описание операции
const description = pcOperation.getDescription();
// Список транзакций
const transactions = pcOperation.getTransactions();
// 4. Загрузить бинарные данные транзакций (если есть)
for (const transaction of transactions) {
if (transaction.hasBinaryData()) {
try {
// Загружаем бинарные данные транзакции
await PCTransactionsManager.getTransactionBinaryData(targetUser, transaction);
// Теперь транзакция полностью загружена и готова к обработке
} catch (error) {
console.error("Ошибка загрузки бинарных данных транзакции:", err);
}
} else {
// У транзакции нет бинарных данных — можно сразу отображать/обрабатывать
}
}
} catch (error) {
console.error("Произошла ошибка при загрузке операций:", error);
}
Для обработки операции необходимо указать список транзакций для подтверждения и список транзакций для отклонения. Каждый из списков может быть пустым, но не оба одновременно. Также списки не должны пересекаться.
Обработка операции
Пример ниже демонстрирует, как может быть обработана PCOperation. Предполагается, что бинарные данные (attachments) уже загружены для всех транзакций, которые планируется обработать.
import {PCUsersManager, PCOperationsManager} from '@safetech/pcsdk';
const pcUser = PCUsersManager.getById(TARGET_USER_ID);
const operationId = TARGET_OPERATION_ID;
const pcOperation = await PCOperationsManager.getOperation(pcUser, operationId);
/*
* 1. Клиент должен передать список транзакций, которые нужно подтвердить или отклонить.
* Каждый элемент должен быть объектом из PCOperation.getTransactions().
*/
const transactionsToConfirm = getTransactionsSelectedForConfirmation();
const transactionsToDecline = getTransactionsSelectedForDeclination();
// 2. Запрашиваем пароль
const password = promptPassword();
// 3. Обработка операции
async function processOperation() {
try {
const {
confirmationResults,
declinationResults
} = await PCOperationsManager.processOperation(
pcUser,
pcOperation,
password,
transactionsToConfirm,
transactionsToDecline
);
const allResults = [...confirmationResults, ...declinationResults];
const hasErrors = allResults.some(r => r.result.errorCode !== 0);
if (!hasErrors) {
console.log("Все транзакции обработаны успешно.");
} else {
console.warn("Некоторые транзакции не были обработаны.");
}
} catch (error) {
console.error("Произошла ошибка при обработке операции:", error);
}
}
Функция PCOperationsManager.processOperation(user: PCUser, operation: PCOperation, password: string, transactionsToConfirm: Array<PCTransaction>, transactionsToDecline: Array<PCTransaction>) обрабатывает переданные транзакции и возвращает объект с двумя полями:
* confirmationResults — результаты обработки транзакций, отправленных на подтверждение
* declinationResults — результаты обработки транзакций, отправленных на отклонение
Каждый элемент в этих массивах содержит информацию о конкретной транзакции и итог обработки. Формат результата выглядит так:
const confirmationResults = {
transactionId: "string",
result: {
errorCode: 0,
errorMessage: "Success"
}
}
Где errorCode = 0 означает успешную обработку, а любые другие значения обозначают ошибку.
После обработки транзакций объект PCOperation не обновляется автоматически. Чтобы получить актуальное состояние, операции, необходимо заново запросить её с сервера с помощью PCOperationsManager.getOperation(targetUser, operationId).