# Лицензия

> Настройка авторизации экспорта Exchange, запрос оставшейся квоты и обработка ошибок лицензии.

- Human documentation: [https://docs.univer.ai/ru-RU/server/import-export/license](https://docs.univer.ai/ru-RU/server/import-export/license)

- Agent Markdown: [https://docs.univer.ai/ru-RU/server/import-export/license.md](https://docs.univer.ai/ru-RU/server/import-export/license.md)

- Requested language: `ru-RU`

- Content language: `ru-RU`

- Documentation version: `1.4.0`

- Source: [import-export/license.ru-RU.mdx](https://github.com/dream-num/documentation/blob/dev/content/server/import-export/license.ru-RU.mdx)

---

Exchange читает лицензию в серверном процессе, где работает `@univerjs-pro/exchange-node`. Конвертация описана в разделе [Импорт и экспорт файлов Office](https://docs.univer.ai/ru-RU/server/import-export/node.md).

## Настройка авторизации

Передайте лицензию через переменные окружения `UNIVER_LICENSE` и `UNIVER_LICENSE_KEY`. Вызывать `configureExchange` необязательно: SDK один раз читает окружение при первом экспорте или запросе квоты и затем использует эту конфигурацию. Без учётных данных в окружении действует Free. Переданные, но недействительные учётные данные возвращают ошибку и не могут использоваться для экспорта.

Для явной настройки `configureExchange({ license, licenseKey })` возвращает `Promise<void>` и полностью игнорирует окружение. Дождитесь его завершения, прежде чем запускать экспорты с новыми учётными данными.

```ts
import { configureExchange, getExportQuota } from '@univerjs-pro/exchange-node'

await configureExchange({
  license: '<license content>',
  licenseKey: '<licenseKey content>',
})

const quota = await getExportQuota()
```

Используйте полное содержимое `license` и `licenseKey`, полученное от поставщика лицензии, а не пути к файлам. Задавайте оба значения вместе через систему управления секретами вашего приложения.

| Входные данные | Поведение |
| --- | --- |
| `configureExchange` не вызывается | При первом использовании читаются обе переменные окружения; если ни одна не задана, используется Free. |
| `configureExchange({ license, licenseKey })` | Используются только явные учётные данные; окружение игнорируется. |
| `configureExchange({})` | Явно выбирается Free; окружение игнорируется. |
| Выбранный источник передаёт только одно значение | Возвращается `INVALID_ARGUMENT`; второе значение никогда не берётся из другого источника. |

Прочитанная полная конфигурация окружения остаётся неизменной, даже если последующая операция завершится ошибкой. Чтобы заменить учётные данные по умолчанию во время работы, явно вызовите `configureExchange` и дождитесь его завершения. При ошибке настройки продолжает действовать прежняя конфигурация; обработайте ошибку, прежде чем запускать новые экспорты.

ESM и CommonJS используют общую конфигурацию в одном контексте JavaScript. Каждый Worker из `worker_threads` должен настраиваться сам или инициализироваться из своего окружения. Экспорт использует конфигурацию на момент вызова публичного метода экспорта; последующая смена учётных данных не влияет на уже запущенные экспорты.

### Авторизация одной операции

Передайте полную пару `license` и `licenseKey` в параметрах API экспорта или `getExportQuota`, чтобы авторизовать только эту операцию, не меняя конфигурацию по умолчанию. Например, запросите квоту и выполните экспорт с одними и теми же учётными данными:

```ts
import { UniverInstanceType } from '@univerjs/core'
import { ExchangeFormat, exportToBuffer, getExportQuota } from '@univerjs-pro/exchange-node'

const credentials = { license: '<license content>', licenseKey: '<licenseKey content>' }
const quota = await getExportQuota(credentials)
const output = await exportToBuffer(workbook, {
  type: UniverInstanceType.UNIVER_SHEET,
  format: ExchangeFormat.XLSX,
  ...credentials,
})
```

Это относится и к экспорту в файл, экспорту Snapshot и необязательному экспорту в PDF. Если оба поля опущены, используется конфигурация по умолчанию; если передано только одно, возвращается `INVALID_ARGUMENT`. Явная пара никогда не дополняется из конфигурации по умолчанию или окружения, а пустые строки не считаются пропуском. Недействительные учётные данные возвращают `LICENSE_INVALID` без перехода на коммерческую лицензию по умолчанию.

### Необязательная поддержка PDF

Установите пакет расширения PDF и импортируйте его точку входа до настройки авторизации:

```sh
pnpm add @univerjs-pro/exchange-node @univerjs-pro/exchange-pdf-node-binding
```

```ts
import '@univerjs-pro/exchange-node/pdf'
import { configureExchange, getExportQuota } from '@univerjs-pro/exchange-node'

await configureExchange({
  license: '<license content>',
  licenseKey: '<licenseKey content>',
})

const quota = await getExportQuota()
```

Эта точка входа регистрирует поддержку PDF; нативные компоненты загружаются при использовании. Для PDF применяются те же API `importBuffer`, `importFile`, `exportToBuffer`, `exportToFile` и соответствующие API Snapshot с `UniverInstanceType.UNIVER_PDF` и `ExchangeFormat.PDF`. Приложению, работающему только с PDF, не нужно загружать нативный компонент Office. Настройка через окружение действует и для PDF.

## Запрос оставшейся квоты

`getExportQuota({ license, licenseKey })` возвращает `Promise<ExportQuota>` с квотой, общей для Office и PDF в рамках авторизации. Без учётных данных запрашивается конфигурация по умолчанию. Метод не принимает тип Unit и не проверяет право на экспорт конкретного типа; эта проверка выполняется при экспорте.

Запросы не расходуют и не резервируют квоту и не проверяют версию SDK, дату активации или право на тип экспорта. При нулевом остатке запрос всё равно успешно возвращает снимок. Другие запросы могут израсходовать квоту позже, поэтому обрабатывайте и ошибки самого экспорта.

До начала авторизации запрос с ограниченной квотой возвращает первый месяц авторизации с `periodStart` в будущем. Успешный запрос или состояние `valid` не дают права на экспорт. Версия, активация, право на тип, доступная квота и параллельность проверяются при экспорте.

| Поле | Тип | Значение |
| --- | --- | --- |
| `licenseStatus` | `'not_configured' \| 'expired' \| 'valid'` | Подписка на экспорт не настроена, истекла или не истекла. `valid` не означает, что проверки допуска к экспорту пройдены. Недействительные учётные данные возвращают ошибку. |
| `expiresAt` | `string \| null` | Окончание подписки на экспорт, метка времени ISO в UTC; `null`, если не настроена или срок не ограничен. |
| `observedAt` | `string` | Метка времени ISO в UTC для этого снимка. |
| `limit` | `number \| null` | Текущий месячный лимит экспортов; `null` без ограничений. |
| `used` | `number \| null` | Успешные экспорты за текущий месяц; `null` без ограничений. |
| `reserved` | `number \| null` | Выполняющиеся экспорты, занимающие квоту текущего месяца; `null` без ограничений. |
| `remaining` | `number \| null` | Доступные экспорты: `max(0, limit - used - reserved)`; `null` без ограничений. |
| `periodStart` | `string \| null` | Начало (включительно) запрошенного месячного периода квоты, ISO в UTC; до активации первый период начинается в будущем. `null` без ограничений. |
| `periodEnd` | `string \| null` | Конец (не включительно) запрошенного месячного периода квоты, ISO в UTC; `null` без ограничений. |

`periodEnd` отмечает начало следующего месячного периода квоты; `expiresAt` — окончание подписки на экспорт. Действующая подписка всегда использует коммерческую квоту. Её исчерпание никогда не переключает на Free, а новый период коммерческой квоты начинается в следующем месяце. Только истечение подписки переключает на оставшуюся квоту Free, без её сброса или повторной выдачи.

Пример снимка с ограниченной квотой:

```json
{
  "licenseStatus": "valid",
  "expiresAt": "2027-10-19T23:59:59Z",
  "observedAt": "2026-10-21T09:00:00Z",
  "limit": 500,
  "used": 20,
  "reserved": 1,
  "remaining": 479,
  "periodStart": "2026-10-19T00:00:00Z",
  "periodEnd": "2026-11-19T00:00:00Z"
}
```

## Расход квоты экспорта

Free допускает 10 успешных экспортов за месяц авторизации. Квота экспорта, параллельность и диапазон версий коммерческой лицензии зависят от приобретённой лицензии. Ни Free, ни коммерческие лицензии не ограничивают размер файла или число ячеек. Текущий период и оставшуюся квоту узнавайте из ответа на запрос. Неиспользованная месячная квота не переносится.

Импорт не требует авторизации экспорта и не расходует квоту экспорта. Экспорты Office и PDF с одной коммерческой лицензией в рамках одного развёртывания используют общую квоту. Форматы конвертации Exchange описаны в разделе [Импорт и экспорт файлов Office](https://docs.univer.ai/ru-RU/server/import-export/node.md).

Каждая успешная конвертация расходует один экспорт. Возврат Buffer считается успехом. Последующая ошибка записи, загрузки или скачивания файла не возвращает этот экспорт; новая успешная конвертация расходует ещё один. Когда в текущем периоде квота закончилась, новые экспорты отклоняются. Дождитесь следующего периода авторизации или используйте действующую лицензию с большей квотой; исчерпанная действующая коммерческая лицензия не использует квоту Free.

## Обработка ошибок

Определяйте ошибки по структурированному `code`, а не разбирая текст `message`. Интерфейс может сопоставлять коды ошибок с локализованными сообщениями. Сохраняйте диагностическую информацию для технической поддержки.

```ts
import { UniverInstanceType } from '@univerjs/core'
import { ExchangeError, ExchangeFormat, exportToBuffer } from '@univerjs-pro/exchange-node'

try {
  const output = await exportToBuffer(workbook, {
    type: UniverInstanceType.UNIVER_SHEET,
    format: ExchangeFormat.XLSX,
  })
} catch (error) {
  if (error instanceof ExchangeError) {
    // Покажите сообщение для error.code, например LICENSE_EXPORT_QUOTA_EXCEEDED, когда квота исчерпана
    console.error(error.code)
  } else {
    throw error
  }
}
```

В таблице различаются запросы квоты (`getExportQuota`) и фактические экспорты. Оба API возвращают Promise; обрабатывайте ошибки с помощью `await` и `try/catch`.

| Код | Операция | Значение и действия |
| --- | --- | --- |
| `LICENSE_EXPORT_QUOTA_EXCEEDED` | Только экспорт | Недостаточно квоты экспорта в текущем периоде. Запросите квоту или обратитесь в отдел продаж, чтобы изменить лицензию. |
| `LICENSE_CONCURRENCY_TIMEOUT` | Только экспорт | Истекло время ожидания свободного места для параллельного экспорта. Уменьшите число одновременных экспортов и отправьте снова. |
| `LICENSE_UNIT_TYPE_REJECTED` | Только экспорт | Этот тип экспорта не лицензирован или право на него истекло. Используйте действующую лицензию, покрывающую этот тип. |
| `LICENSE_INVALID` | Запрос, экспорт | Лицензия недействительна. Проверьте полноту учётных данных или запросите обновление у поставщика лицензии. |
| `LICENSE_POLICY_INVALID` | Запрос, экспорт | Конфигурация авторизации не соответствует требованиям SDK. Запросите обновление у поставщика лицензии. |
| `LICENSE_NOT_YET_EFFECTIVE` | Только экспорт | Авторизация ещё не началась. Используйте её после даты вступления в силу. |
| `LICENSE_VERSION_NOT_ALLOWED` | Только экспорт | Текущая версия SDK вне лицензированного диапазона. Используйте подходящую версию или обновите лицензию. |
| `UNSUPPORTED_FORMAT` | Только экспорт | Формат не поддерживается или не соответствует типу Unit. Проверьте параметры экспорта. |
| `INVALID_FILE` | Только экспорт | Входные данные конвертации недействительны. Проверьте их содержимое. |
| `INCOMPLETE_SNAPSHOT` | Только экспорт | Данные экспорта неполные. Передайте полные данные или полный Snapshot. |
| `CONVERSION_FAILED` | Только экспорт | Конвертация не удалась. Сохраните ошибку и проверьте входные данные. |
| `IO_ERROR` | Только экспорт | Ошибка файлового ввода-вывода. Ошибка записи после успешной конвертации уже израсходовала один экспорт. |
| `INVALID_ARGUMENT` | Запрос, экспорт | Неполные аргументы или учётные данные. Проверьте конфигурацию. |
| `NATIVE_LOAD_FAILED` | Запрос, экспорт | Не удалось загрузить нативный компонент SDK. Проверьте совместимость пакета и среды выполнения. |
| `LICENSE_USAGE_STATE_INVALID` | Запрос, экспорт | Невозможно проверить состояние использования лицензии. Сохраните ошибку и обратитесь в техническую поддержку. |
| `LICENSE_USAGE_STORAGE_ERROR` | Запрос, экспорт | Не удалось обработать использование лицензии. Сохраните ошибку, обратитесь в техническую поддержку и избегайте автоматической повторной отправки. |

Приложения могут формировать напоминания об окончании срока по `licenseStatus` и `expiresAt`; SDK не возвращает отдельный список уведомлений.

После ошибки обработки использования лицензии не делайте выводов об учёте только по ошибке. Сохраните полную ошибку и обратитесь в техническую поддержку.
