> ## Documentation Index
> Fetch the complete documentation index at: https://omniflow-vpn.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Подключение устройства

> Что такое ссылка на подписку, как она попадает на телефон или ноутбук, какие клиентские приложения документирует Omniflow, как перечисляются и удаляются подключённые устройства и как сменить утёкшую ссылку

Всё на этой странице — о переносе одной строки. Подписка провижинится в
Remnawave, Remnawave выпускает для неё ссылку доступа, и устройство считается
подключённым, когда клиентское приложение держит эту ссылку. Ничего другого в
подключении интересного нет: нет ни отдельных учётных данных на устройство, ни
аккаунта, который нужно вводить в приложении, ни файла конфигурации, который надо
править.

Эта единственная строка одновременно является предъявительскими учётными данными.
Подключиться может любой, кто ею владеет, и поэтому Omniflow показывает её только
на том экране, чья цель — её передать, никогда не хранит её, никогда не пишет в
логи и никогда не помещает в выгрузку персональных данных. Вся конструкция экрана
подключения — QR-код, рисуемый в браузере, усечённое отображение, запрос по
требованию — следует из этого одного факта.

Обе клиентские поверхности отрисовывают это из одного источника. И бот Telegram, и
веб-панель читают таблицу клиентских приложений из `internal/commerce`, потому что
клиенту, которому в чате велят поставить одно приложение, а в браузере — другое,
вручили два продукта.

## Ссылка на подписку

**Что это.** URL, выпущенный и принадлежащий Remnawave, для одной подписки.
Клиентское приложение запрашивает его, получает конфигурацию, которую даёт эта
подписка, и подключается.

**Почему Omniflow не хранит копию.** Ссылка — это учётные данные, а самый дешёвый
способ гарантировать, что учётные данные никогда не попадут в резервную копию,
строку лога или отчёт о сбое, — никогда их не держать. Omniflow читает ссылку из
Remnawave в тот момент, когда клиент её запрашивает, и передаёт прямо на экран.
Последствия видны по всему продукту:

* `GET /v1/account/overview` — payload дашборда — намеренно не несёт URL подписки.
  Ответ дашборда, всегда несущий его, поместил бы ссылку в каждый кэш, лог и запись
  истории браузера, которые касаются страницы.
* Каждый ответ `/v1/account` отправляется с `Cache-Control: no-store`,
  `Referrer-Policy: no-referrer` и `Content-Security-Policy` со значением
  `default-src 'none'`. Комментарий у этого промежуточного обработчика называет
  причину: клиентский ответ может содержать ссылку на подписку.
* Выгрузка персональных данных объявляет `subscription_links` среди своих
  постоянных изъятий, а не включает их. См.
  [Приватность и ваши данные](https://omniflow-vpn.mintlify.site/ru/customer/privacy.md).
* Собственный журнал безопасности клиента записывает смену ссылки по подписи
  подписки и ничего более: помещение ссылки в журнал свело бы на нет ту самую
  смену, которую он записывает.

**Откуда она берётся.** Ссылка есть только у провижиненной подписки. Между платежом
и выдачей доступа пользователя в Remnawave ещё нет, поэтому экран подключения
отвечает `409 not_provisioned` и показывает «Ещё настраивается» вместо пустого
поля. Что происходит в это время, описано в [Выдаче доступа](https://omniflow-vpn.mintlify.site/ru/commerce/fulfillment.md).

## Как перенести ссылку на устройство

### Из веб-панели

**1. Откройте подписку**

На `/account` нажмите **Подключить** на карточке подписки. Это ведёт на
`/account/subscriptions/{id}/connect`. Экран также принимает параметр запроса
`?platform=`, поэтому ссылка на него может заранее выбрать платформу.

**2. Выберите платформу**

Выбор предлагает iOS, Android, Windows, macOS и Linux в этом фиксированном
порядке. Неизвестная или пустая платформа приводит к первой из них, а не к
ошибке.

**3. Пройдите три шага**

Установите первое рекомендованное приложение для этой платформы; нажмите
кнопку, которая добавляет подписку; если ничего не открылось, скопируйте
ссылку и вставьте её в приложение. Шаги называют конкретное приложение, а не
«ваше VPN-приложение», потому что клиенту придётся найти его в магазине.

**4. Передайте ссылку**

Нажмите кнопку **Открыть в …** для установленного вами приложения, наведите
камеру второго устройства на QR-код или нажмите **Копировать** и вставьте
ссылку вручную. Все три способа несут одну и ту же строку.

### Из бота Telegram

На экране подписки есть кнопка **Подключить**. Она открывает тот же выбор
платформы, и каждая платформа показывает те же пронумерованные шаги, называющие то
же первое рекомендованное приложение.

Разница — в кнопках. Встроенные кнопки Telegram принимают только URL со схемами
`http`, `https` и `tg`, поэтому deep-ссылка приложения вроде `happ://add/…` вообще
не может быть кнопкой-ссылкой. Поэтому бот отрисовывает по одной кнопке
**копирования текста** на каждое приложение, плюс кнопку копирования самой ссылки,
плюс кнопку-ссылку «Открыть подписку» для `https`-URL подписки. Копирование —
документированный запасной вариант везде, в том числе в десктопном браузере, где
для схемы не зарегистрирован обработчик.

Каждый экран бота отправляется как защищённое содержимое, поэтому Telegram не
позволит переслать сообщение со ссылкой на подписку. Полный путь в Telegram описан
на странице [бота Telegram](https://omniflow-vpn.mintlify.site/ru/customer/telegram-bot.md).

## Клиентские приложения, которые документирует Omniflow

Каталог хранится в базе, а не вкомпилирован, и обе поверхности читают одни и те
же строки — поэтому бот и браузер не могут рекомендовать разные приложения.
Редактируется он по адресу
[`/admin/settings/connect`](https://omniflow-vpn.mintlify.site/ru/operations/connection-catalogue). Порядок — это
порядок рекомендации: первая запись — то приложение, которое называют шаги
настройки.

Новая установка засевается ровно тем, что прежние версии носили в бинарнике:

| Платформа | Приложения, по порядку | Схема импорта |
| - | - | - |
| iOS | Happ, v2RayTun, Streisand | `happ://add/`, `v2raytun://import/`, `streisand://import/` |
| Android | Happ, v2RayTun, Hiddify | `happ://add/`, `v2raytun://import/`, `hiddify://import/` |
| Windows | Hiddify, v2RayTun | `hiddify://import/`, `v2raytun://import/` |
| macOS | Happ, Streisand | `happ://add/`, `streisand://import/` |
| Linux | Hiddify | `hiddify://import/` |

Deep-ссылка — это схема с приписанным к ней URL подписки, и ничего больше:
`happ://add/https://panel.example.com/sub/…`. Никакого сервиса редиректов между
ними нет, поэтому приложение, поддерживающее свою схему, импортирует подписку так,
что ни одна часть Omniflow не наблюдает этого события.

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

> [!NOTE]
>
> Засеянный список намеренно короткий. Это приложения, которые установка
> документирует и поддерживает, а не все приложения, способные прочитать ссылку на
> подписку: клиент, использующий что-то другое, всё равно может вставить ссылку
> туда, но шаги на экране не будут описывать его приложение.

## Три способа, которыми путешествует ссылка

### Deep-ссылки

В браузере каждое приложение — это обычный якорь, а не переход роутера, потому что
deep-ссылка уводит со страницы в приложение. На устройстве с установленным
приложением нажатие открывает его с уже заполненной подпиской. На устройстве без
него браузер сообщает, что не может открыть адрес, — вот почему третий шаг говорит
клиенту скопировать ссылку и почему бот предлагает deep-ссылку как копируемый
текст.

### QR-код

QR генерируется в браузере. Кодировщик импортируется лениво, чтобы не попадать в
бандл всех остальных экранов, а результат отрисовывается из data URL в обычном
элементе `<img>`.

Стоит сказать, почему это не изображение, отрисованное сервером: URL изображения,
несущий ссылку на подписку, поместил бы эти учётные данные в строку запроса, лог
прокси и кэш браузера, а прогон через конвейер изображений Next.js отправил бы
несущий учётные данные payload на сервер для кэширования. Если кодировщик даёт
сбой, блок с QR не отрисовывается вовсе, а не показывается сломанным.

Наведите камеру другого устройства на код, и оно получит ту же строку, которую
несла бы deep-ссылка.

### Копирование вручную

Ссылка показывается усечённой в моноширинной строке с кнопкой **Копировать**. Она
усечена, потому что это учётные данные, и у клиента нет причин её читать — только
перенести. Кнопка около двух секунд показывает «Скопировано» и возвращается в
исходное состояние.

Браузер, отказывающий в доступе к буферу обмена, — реальный случай: так бывает на
небезопасных источниках и в некоторых встроенных браузерах, — поэтому неудавшееся
копирование поднимает всплывающее уведомление с просьбой выделить и скопировать
ссылку вручную, вместо того чтобы ничего не сделать и выглядеть сломанной кнопкой.

## Когда экран подключения не показывает ссылку

| Ситуация | Статус | Что видит клиент |
| - | - | - |
| У подписки ещё нет пользователя в Remnawave | `409 not_provisioned` | «Ещё настраивается — Ваша подписка готовится. Этот экран заработает, как только она будет готова». |
| Remnawave недоступен | `503 upstream_unavailable` | «Временно недоступно — Живые данные о трафике и устройствах сейчас прочитать нельзя. На вашу подписку это не влияет». |
| Подписка принадлежит не этому клиенту | `404 not_found` | Тот же ответ, что и для несуществующей подписки, поэтому идентификатор нельзя прощупать. |

В боте подписка, у которой ещё нет ссылки, показывает вместо кнопок строку «Ссылка
на подписку появится, как только доступ станет активен».

**API.** `GET /v1/account/subscriptions/{subscriptionID}/connection?platform=`
возвращает `{subscriptionUrl, platform, platforms[], clients[{name, deepLink}]}`.

## Подключённые устройства

**Что это.** Устройства, сейчас привязанные к пользователю Remnawave одной
подписки, со счётчиком относительно лимита устройств тарифа, элементом управления
удалением для каждого устройства и элементом удаления всех.

**Зачем это нужно.** Лимит устройств выполним, только если клиент может сам
освободить слот. Без списка устройств достижение лимита означает открытие обращения
в поддержку, чтобы отключить телефон, заменённый год назад.

**Как туда попасть.** Устройств нет на панели вкладок: замена телефона — действие
раз в год, а пять вкладок упорядочены по частоте потребности. Откройте
`/account/profile` и перейдите в **Устройства** либо попадите на
`/account/devices` напрямую. Устройства принадлежат подписке, а не аккаунту,
поэтому, когда у клиента их больше одной, экран показывает над списком кнопки для
переключения между ними.

Каждая строка показывает иконку, выбранную по платформе, о которой сообщило
приложение (ноутбук для mac, Windows или Linux, планшет для планшета, иначе
телефон), модель устройства, о которой приложение сообщило само, — или «Неизвестное
устройство», если оно не сообщило ничего, — и дату. Под списком: «Одна ссылка
работает на всех устройствах. Отключение освобождает слот сразу».

### Что намеренно не показывается

Ни на этом экране, ни в стоящем за ним ответе нет ни аппаратного идентификатора, ни
IP-адреса. Это решение, а не упущение:

* **HWID никогда не покидает сервер.** Он идентифицирует чью-то физическую машину.
  Устройство называется клиенту **дескриптором** — `base64url` первых 16 байт
  `SHA-256("omniflow.device." + hwid)`, 128 бит, — который не раскрывает ничего об
  устройстве, за которое он стоит.
* **Дескриптор разрешается повторным хэшированием, а не сохранённым
  сопоставлением.** Когда приходит запрос на удаление, сервер запрашивает текущий
  список устройств, хэширует каждый HWID, который держит, и сопоставляет. Поэтому
  вызывающий никогда не передаёт идентификатор, над которым выполняется действие.
  Индекс в массиве не подошёл бы: список может измениться между чтением и
  удалением, поэтому к моменту использования индекс может указывать на другое
  устройство.
* **Последняя активность — это дата, а не момент.** Достаточно, чтобы клиент узнал
  свой ноутбук, и недостаточно, чтобы построить журнал перемещений.
* **IP-адрес не возвращается вообще**, в том числе для домашней сети или
  соединения, которым пользуется кто-то ещё. Выгрузка персональных данных объявляет
  `device_and_network_identifiers` постоянным изъятием по той же причине.

Дескриптор, которому ничего не соответствует, отвечает `404 not_found` — ровно так
же выглядит и устройство, удалённое из другой вкладки мгновение назад.

### Удаление устройств

**1. Удалите одно устройство**

Нажмите **Отключить** на его строке. Диалог подтверждения объясняет, что
устройство перестанет подключаться, пока подписка не будет добавлена снова. При
успехе всплывающее уведомление подтверждает это, и список перезагружается. Слот
освобождается сразу.

**2. Удалите все устройства**

Когда в списке больше одного устройства, появляется кнопка во всю ширину
**Отключить все устройства** с собственным диалогом. Это один вызов Remnawave, а
не цикл, потому что поустройственное отключение оставило бы частично отключённую
подписку, если бы третий вызов провалился.

Удаление всех устройств требует входа, выполненного в последние пятнадцать минут.
Устаревшей сессии отказывают с `401 reauthentication_required`, и панель
показывает «Войдите снова, чтобы подтвердить это действие», а не сырой отказ:
средство исправления доступно самому клиенту. Удаление одного устройства такого
требования не имеет: оно стоит одному устройству переподключения, а не всей
настройки.

| Действие | Маршрут | Условие |
| - | - | - |
| Список устройств | `GET /v1/account/subscriptions/{subscriptionID}/devices` | сессия |
| Удалить одно | `DELETE /v1/account/subscriptions/{subscriptionID}/devices/{handle}` | сессия |
| Удалить все | `DELETE /v1/account/subscriptions/{subscriptionID}/devices?confirm=true` | сессия + недавний вход |

Удаление всех без `?confirm=true` отклоняется с `422 confirmation_required`. При
нуле подключённых устройств оно возвращает `{"removed": 0}`, вовсе не обращаясь к
Remnawave.

Оба вида удаления пишутся в собственный журнал безопасности клиента —
`device_removed` с подписью подписки и сообщённым именем устройства,
`devices_removed_all` с подписью и числом удалённых. Ни то, ни другое не записывает
аппаратный идентификатор, а сам журнал доступен клиенту для чтения на
`/account/security`.

> [!NOTE]
>
> Оператор также может перечислить и удалить устройства из профиля клиента в
> админ-панели под правами `subscriptions.read` и `subscriptions.write`. Этот путь
> пишет событие аудита с собственной ссылкой на устройство, но никогда с HWID. См.
> [Операции администратора](https://omniflow-vpn.mintlify.site/ru/operations/admin-operations.md).

## Смена ссылки на подписку

**Что это.** Отзыв текущей ссылки доступа и чтение новой обратно из Remnawave.

**Зачем это нужно.** Это единственное средство исправления, когда ссылку передали
или она утекла. Это же и самое разрушительное, что клиент может сделать со своей
работающей настройкой: каждое устройство перестаёт подключаться, пока не будет
импортирована новая ссылка. Обе половины этой фразы объясняют, зачем этот элемент
управления существует и почему он защищён именно так.

> [!WARNING]
>
> Смена ссылки разом ломает подключение каждого устройства. Трафик и дата окончания
> не затрагиваются, но каждое устройство должно импортировать новую ссылку, прежде
> чем снова подключится.

**Как этим пользоваться.** Откройте `/account/subscriptions/{id}` и прокрутите до
раздела **Ссылка доступа**. Карточка с предупреждением точно указывает, что
сломается. Нажмите **Выпустить новую ссылку**; диалог требует, чтобы клиент ввёл
фразу `NEW LINK` и вдобавок нажал подтверждение. При успехе всплывающее уведомление
подтверждает это, а подсказка отсылает обратно к экрану подключения, чтобы добавить
новую ссылку на каждое устройство.

В Telegram то же действие находится на экране подписки за клавиатурой
подтверждения/отмены, и его выполнение возвращает экран подключения с уже свежей
ссылкой.

**API.** `POST /v1/account/subscriptions/{subscriptionID}/rotate-link`, тело
`{"confirm": true}`, за требованием недавнего входа. Отвечает
`{"subscriptionUrl": "…"}`.

| Отказ | Статус | Значение |
| - | - | - |
| `confirmation_required` | 422 | `confirm` не был отправлен. Запрос, ушедший не туда, или двойная отправка не должны иметь возможности запустить смену ссылки. |
| `reauthentication_required` | 401 | Сессия старше пятнадцати минут. Панель предупреждает до нажатия кнопки, а не только после. |
| `not_provisioned` | 409 | Пользователя Remnawave, у которого можно отозвать ссылку, ещё нет. |
| `upstream_unavailable` | 503 | Remnawave недоступен. Ничего не сменено. |

Новая ссылка **читается обратно** из Remnawave, а не предполагается: её выпускает
Remnawave, и показать клиенту старую после смены было бы хуже, чем не показать
никакой. В журнал безопасности клиента пишется запись `subscription_key_rotated`,
называющая только подпись подписки.

## Смежные страницы

> **[Веб-панель клиента](https://omniflow-vpn.mintlify.site/ru/customer/web-panel.md)**
>
> Остальная часть браузерного продукта: дашборд, оформление заказа, кошелёк,
> поддержка.

> **[Бот Telegram](https://omniflow-vpn.mintlify.site/ru/customer/telegram-bot.md)**
>
> Тот же путь подключения в чате, с кнопками копирования текста.

> **[Подписки](https://omniflow-vpn.mintlify.site/ru/commerce/subscriptions.md)**
>
> Права на услуги, лимиты устройств, окончание срока и что означает фаза.

> **[Интеграция с Remnawave](https://omniflow-vpn.mintlify.site/ru/integrations/remnawave.md)**
>
> Какая панель владеет ссылкой, устройствами и счётчиками трафика.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.