npm package: @napopravku/eds-cryptopro
Description: Библиотека для работы с электронной подписью КриптоПро в браузере
Author: Napopravku
Latest version: 0.1.1

# @napopravku/eds-cryptopro

Библиотека для работы с электронной подписью КриптоПро в браузере.

## 📦 Установка

```bash
npm install @napopravku/eds-cryptopro
```

или

```bash
yarn add @napopravku/eds-cryptopro
```

## 🚀 Быстрый старт

Библиотека работает "из коробки" - дополнительная настройка не требуется!

### Простое использование

```typescript
import { CryptoProService } from '@napopravku/eds-cryptopro';

async function init() {
  try {
    // Плагин загрузится автоматически!
    await CryptoProService.initialize();
    console.log('Плагин КриптоПро успешно инициализирован');
    
    // Получаем список сертификатов
    const certificates = await CryptoProService.getCertificates();
    console.log('Найдено сертификатов:', certificates.length);
  } catch (error) {
    console.error('Ошибка:', error);
  }
}

init();
```

## 📖 Процесс работы библиотеки

### Инициализация

1. **Пользователь вызывает `initialize()`**
   - Библиотека проверяет, загружен ли уже плагин КриптоПро
   - Если нет, начинает автоматический поиск файла `cadesplugin_api.js`

2. **Автоматическая загрузка плагина**
   - Библиотека последовательно проверяет несколько стандартных путей:
     - `/node_modules/@napopravku/eds-cryptopro/lib/cadesplugin_api.js`
     - `/cadesplugin_api.js`
     - `/assets/cadesplugin_api.js`
     - `/js/cadesplugin_api.js`
     - `/lib/cadesplugin_api.js`
   - Как только файл найден, он загружается динамически

3. **Подключение к браузерному расширению**
   - Загруженный плагин ищет установленное браузерное расширение КриптоПро
   - Устанавливается связь между JavaScript и расширением
   - Библиотека готова к работе

### Получение сертификатов

4. **Вызов `getCertificates()`**
   - Открывается хранилище сертификатов операционной системы
   - Извлекаются все доступные сертификаты
   - Фильтруются только сертификаты с закрытым ключом (пригодные для подписи)
   - Формируется читаемое имя для каждого сертификата
   - Возвращается массив с информацией о сертификатах

### Создание подписи

5. **Подготовка данных для подписи**
   - Исходные данные (строка или объект) → конвертируются в строку
   - Строка кодируется в Base64 (для корректной передачи в плагин)

6. **Вызов `signData(данные, индекс_сертификата)`**
   - Выбирается нужный сертификат по индексу
   - Создается объект подписи CAdES-BES (стандарт электронной подписи)
   - Данные подписываются с использованием закрытого ключа сертификата
   - Возвращается подпись в формате Base64

7. **Типы подписи**
   - **Отделенная подпись** (по умолчанию): подпись отдельно, данные отдельно
   - **Присоединенная подпись**: подпись содержит в себе данные

### Проверка подписи

8. **Вызов `verifySignature(данные, подпись)`**
   - Исходные данные кодируются в Base64 (если еще не закодированы)
   - Подпись проверяется на валидность
   - Проверяется соответствие подписи данным
   - Проверяется срок действия сертификата
   - Проверяется цепочка доверия сертификата
   - Возвращается результат: `{ isValid: true }` или `{ isValid: false, error: "описание" }`

### Формат данных

**Для подписания строк:**
```typescript
const signature = await CryptoProService.signData('Текст документа', 0);
```

**Для подписания файлов (например, XML):**
```typescript
// 1. Читаем файл
const fileContent = await readFile('document.xml');

// 2. Конвертируем в Base64
const base64Content = btoa(fileContent);

// 3. Подписываем
const signature = await CryptoProService.signData(base64Content, 0);

// При проверке указываем, что данные уже в Base64:
const result = await CryptoProService.verifySignature(
  base64Content,
  signature,
  true  // isDataBase64 = true
);
```

### Очистка ресурсов

9. **Вызов `clearCertificates()`** (опционально)
   - Очищает кэш загруженных сертификатов
   - Используется при необходимости обновить список сертификатов

## 📖 Документация API

#### Методы

##### `initialize(): Promise<void>`

Инициализирует плагин КриптоПро. Должен быть вызван перед использованием других методов.

```typescript
await CryptoProService.initialize();
```

##### `getCertificates(): Promise<ICertificateInfo[]>`

Получает список сертификатов с закрытым ключом.

```typescript
const certificates = await CryptoProService.getCertificates();
console.log('Доступные сертификаты:', certificates);
```

**Возвращает:** массив объектов типа `ICertificateInfo`:

```typescript
interface ICertificateInfo {
  id: number;           // Индекс сертификата
  name: string;         // Имя сертификата
  validFrom: Date;      // Дата выдачи
  cert: any;            // Объект сертификата
}
```

##### `signData(dataToSign: string, certIndex: number, isDetached?: boolean): Promise<string>`

Создает отделенную подпись CAdES-BES.

**Параметры:**
- `dataToSign` - данные для подписи (строка)
- `certIndex` - индекс сертификата из списка (поле `id` из `ICertificateInfo`)
- `isDetached` - создать отделенную подпись (по умолчанию `true`)

**Возвращает:** подпись в формате base64

```typescript
const signature = await CryptoProService.signData('Данные для подписи', 0);
console.log('Подпись:', signature);
```

##### `verifySignature(originalData: string, signature: string, isDataBase64?: boolean): Promise<IVerifyResult>`

Проверяет отделенную подпись CAdES.

**Параметры:**
- `originalData` - исходные данные
- `signature` - подпись в формате base64
- `isDataBase64` - если `true`, то originalData уже в base64 (по умолчанию `false`)

**Возвращает:** объект типа `IVerifyResult`:

```typescript
interface IVerifyResult {
  isValid: boolean;     // Подпись валидна
  error?: string;       // Описание ошибки (если подпись невалидна)
}
```

```typescript
const result = await CryptoProService.verifySignature(
  'Исходные данные',
  signatureString
);

if (result.isValid) {
  console.log('Подпись валидна');
} else {
  console.error('Подпись невалидна:', result.error);
}
```

##### `clearCertificates(): void`

Очищает кэш сертификатов.

```typescript
CryptoProService.clearCertificates();
```

## 💡 Примеры использования

### Полный пример подписи документа

```typescript
import { CryptoProService, ICertificateInfo } from '@napopravku/eds-cryptopro';

async function signDocument() {
  try {
    // 1. Инициализация плагина
    await CryptoProService.initialize();
    
    // 2. Получение списка сертификатов
    const certificates = await CryptoProService.getCertificates();
    
    if (certificates.length === 0) {
      throw new Error('Сертификаты не найдены');
    }
    
    // 3. Выбор первого сертификата
    const selectedCert = certificates[0];
    console.log('Используется сертификат:', selectedCert.name);
    
    // 4. Данные для подписи
    const dataToSign = 'Важный документ для подписи';
    
    // 5. Создание подписи
    const signature = await CryptoProService.signData(
      dataToSign,
      selectedCert.id
    );
    
    console.log('Подпись успешно создана:', signature);
    
    // 6. Проверка подписи
    const verifyResult = await CryptoProService.verifySignature(
      dataToSign,
      signature
    );
    
    if (verifyResult.isValid) {
      console.log('Подпись валидна!');
    } else {
      console.error('Ошибка проверки:', verifyResult.error);
    }
    
  } catch (error) {
    console.error('Ошибка:', error);
  }
}

signDocument();
```

### Использование с Vue 3 (Composition API)

```vue
<template>
  <div>
    <div v-if="!isInitialized">
      Загрузка плагина КриптоПро...
    </div>
    
    <div v-else>
      <h2>Доступные сертификаты:</h2>
      
      <div v-if="error" class="error">
        {{ error }}
      </div>
      
      <ul v-if="certificates.length > 0">
        <li v-for="cert in certificates" :key="cert.id">
          {{ cert.name }}
          <button @click="handleSign(cert.id)">Подписать</button>
        </li>
      </ul>
      
      <p v-else>
        Сертификаты не найдены
      </p>
    </div>
  </div>
</template>

<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { CryptoProService, type ICertificateInfo } from '@napopravku/eds-cryptopro';

const certificates = ref<ICertificateInfo[]>([]);
const isInitialized = ref(false);
const error = ref<string | null>(null);

onMounted(async () => {
  try {
    // Инициализация плагина (загрузится автоматически)
    await CryptoProService.initialize();
    isInitialized.value = true;
    
    // Получение списка сертификатов
    const certs = await CryptoProService.getCertificates();
    certificates.value = certs;
  } catch (err) {
    console.error('Ошибка инициализации:', err);
    error.value = err instanceof Error ? err.message : 'Неизвестная ошибка';
  }
});

const handleSign = async (certId: number) => {
  try {
    const signature = await CryptoProService.signData(
      'Данные для подписи',
      certId
    );
    console.log('Подпись создана:', signature);
    alert('Подпись успешно создана!');
  } catch (err) {
    console.error('Ошибка подписи:', err);
    error.value = err instanceof Error ? err.message : 'Ошибка подписи';
  }
};
</script>

<style scoped>
.error {
  color: red;
  padding: 10px;
  background-color: #fee;
  border-radius: 4px;
  margin: 10px 0;
}
</style>
```

## 🔧 Требования

- **Браузер**: Chrome, Firefox, Safari, Edge (современные версии)
- **Плагин**: КриптоПро ЭЦП Browser plug-in (должен быть установлен в системе)
- **Node.js**: >= 14.0.0 (для сборки проекта)

## 📝 TypeScript

Библиотека полностью написана на TypeScript и включает типы из коробки. Никаких дополнительных установок `@types` не требуется.

## ⚠️ Важные примечания

1. **Плагин КриптоПро** должен быть установлен в системе пользователя (браузерное расширение)
2. Файл плагина загружается **автоматически** - никаких дополнительных действий не требуется
3. Библиотека автоматически ищет плагин в нескольких стандартных местах
4. Методы работают **асинхронно** - используйте `async/await` или промисы
5. Для работы с сертификатами требуется **закрытый ключ**
6. Библиотека работает **только в браузере**, не поддерживается в Node.js окружении

## Если плагин не загружается

В редких случаях может потребоваться вручную скопировать файл `cadesplugin_api.js`:

```bash
# Скопируйте в public директорию вашего проекта
cp node_modules/@napopravku/eds-cryptopro/lib/cadesplugin_api.js public/
```

### Как это работает?

При вызове `initialize()` библиотека **автоматически** попытается загрузить плагин КриптоПро из нескольких стандартных мест:
- `/node_modules/@napopravku/eds-cryptopro/lib/cadesplugin_api.js`
- `/cadesplugin_api.js`
- `/assets/cadesplugin_api.js`
- `/js/cadesplugin_api.js`
- `/lib/cadesplugin_api.js`

Если вы хотите указать собственный путь к плагину:

```typescript
// Укажите явный путь
await CryptoProService.initialize('/custom/path/cadesplugin_api.js'); 
```

## 📄 Лицензия

MIT © Napopravku