# MicrosoftLoginDialog

## Обзор компонента

`Minecraft_launcher` — десктопный лаунчер Minecraft на Qt Quick. Он поддерживает несколько
способов входа: офлайн-профиль, сервер Ely.by и учётную запись Microsoft. Последний путь требует
показать пользователю настоящую страницу входа Microsoft и дождаться, пока браузер уйдёт на
`redirect_uri` с кодом авторизации в адресе — ровно так же поступает официальный лаунчер.

`MicrosoftLoginDialog` — окно с этой страницей. Внутри него живёт `WebEngineView`; диалог следит
за сменой адреса, отдаёт перехваченный код бэкенду и закрывается. Собственной логики разбора
адреса у него нет — она в C++, чтобы правила совпадения совпадали с теми, по которым сервис сам
строит `redirect_uri`.

## Место в проекте и зависимости

Импортирует `QtQuick`, `QtQuick.Controls 2.15` и `QtWebEngine`. В начале файла объявлена
`pragma ComponentBehavior: Bound`.

Работает с C++-типом `LauncherBackend` (`launcherbackend.h`, `QML_ELEMENT`), который передаётся
снаружи в свойство `backend`. Вызывает у него `inspectMicrosoftRedirect()`,
`finishMicrosoftLogin()` и `cancelMicrosoftLogin()`.

**Особенность сборки.** Это единственный QML-файл проекта, который попадает в модуль условно.
`CMakeLists.txt` ищет `Qt6WebEngineQuick` через `find_package(... QUIET)`; модуль объявлен
необязательным сознательно — он ставится отдельной галочкой в установщике Qt и тянет за собой
WebChannel с Positioning, которых в типовой установке нет. Если модуль найден, файл добавляется в
`QML_FILES` и определяется макрос `LAUNCHER_HAS_WEBENGINE`; если нет — лаунчер собирается и
работает как прежде, только без входа через Microsoft. В QML это различие видно через свойство
`backend.microsoftAvailable`, и интерфейс не должен предлагать этот путь, когда оно ложно.

Инстанцируется динамически из [Main](Main.md) — главное окно создаёт диалог по требованию, потому
что при сборке без WebEngine самого типа в модуле не существует.

## Иерархия и роль

Корневой тип — `Dialog` из Qt Quick Controls: модальный, 560×680 px, по центру родителя, с нулевым
внутренним отступом и `closePolicy: Popup.NoAutoClose` — окно нельзя закрыть щелчком мимо или
клавишей Escape, выход только через кнопку отмены или успешный вход.

Оформление задано вручную: тёмный фон со скруглением и акцентной рамкой, заголовок с
разделительной линией, подвал с кнопкой «Отмена».

Содержимое — `WebEngineView` во всю площадь с отступом 12 px и индикатор занятости по центру,
видимый на время загрузки страницы. Рядом объявлен `WebEngineProfilePrototype` без `storageName`:
профиль без имени хранилища означает профиль без диска, поэтому куки живут только пока работает
лаунчер и в общий браузер не попадают. За выбор аккаунта в пределах сессии отвечает параметр
`prompt=select_account` в адресе входа, который формирует бэкенд.

## Свойства

| Свойство | Тип | По умолчанию | Обязательное | Описание |
|----------|-----|--------------|--------------|----------|
| `backend` | `var` | — | **Да** | Экземпляр `LauncherBackend`. Разбирает перехваченный адрес и завершает или отменяет вход. |
| `codeTaken` | `bool` | `false` | Нет | Код авторизации уже отдан бэкенду. Защита от повторной обработки: `WebEngineView` успевает сообщить об изменении адреса несколько раз, и без этого признака код ушёл бы дважды. Сбрасывается в `openAt()` и принудительно выставляется при отмене. |

## Сигналы

#### failed(string message)

Вход не завершён: адрес совпал с `redirect_uri`, но кода в нём нет — например, пользователь
отказался выдать разрешение, или Microsoft вернула ошибку. В параметре приходит текст ошибки от
бэкенда, а если его нет — сообщение по умолчанию о незавершённом входе.

Окно ничего не знает про тосты главного окна, поэтому о неудаче сообщает сигналом. Обработчик в
[Main](Main.md) показывает это сообщение пользователю. К моменту испускания сигнала диалог уже
закрыт, а вход у бэкенда отменён — обработчику остаётся только уведомить.

Отмена по кнопке сигнала не испускает: пользователь и так знает, что закрыл окно.

## Методы

#### openAt(string url) : void

Открывает диалог на переданном адресе страницы входа. Сбрасывает `codeTaken`, загружает адрес в
`WebEngineView` и показывает окно. Адрес формирует бэкенд — в нём уже присутствуют `redirect_uri`,
идентификатор клиента и `prompt=select_account`.

#### handleUrl(url) : void

Обработчик смены адреса в `WebEngineView`; вызывать снаружи не нужно. Ничего не делает, если код
уже перехвачен. Иначе передаёт адрес в `backend.inspectMicrosoftRedirect()` и смотрит на поле
`matched` ответа: если адрес не является `redirect_uri`, обработка на этом заканчивается — это
обычная навигация по страницам входа.

При совпадении выставляет `codeTaken`, закрывает окно и дальше расходится по двум путям: непустое
поле `code` уходит в `backend.finishMicrosoftLogin()`, иначе вход отменяется через
`backend.cancelMicrosoftLogin()` и испускается сигнал `failed()` с текстом из поля `error`.

## Взаимодействие с другими компонентами

**Со стороны главного окна.** [Main](Main.md) создаёт диалог динамически (функция
`openMicrosoftLogin()`), задаёт `backend`, вызывает `openAt()` с адресом от бэкенда и
подписывается на `failed()`, чтобы показать тост с ошибкой. Показывать ли кнопку входа через
Microsoft вообще, главное окно решает по `backend.microsoftAvailable`.

**Со стороны бэкенда.** Диалог только доставляет код: `inspectMicrosoftRedirect()` — разбор
адреса, `finishMicrosoftLogin()` — продолжение обмена кода на токены, `cancelMicrosoftLogin()` —
сброс начатой сессии входа. Результат входа диалогу не возвращается: об успехе главное окно
узнаёт от бэкенда по его собственным сигналам, а диалог к этому моменту уже закрыт.

**Кнопка отмены.** Выставляет `codeTaken`, закрывает окно и отменяет вход у бэкенда. Признак
ставится до закрытия, чтобы последний сигнал об изменении адреса при закрытии не был обработан.

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

```qml
MicrosoftLoginDialog {
    id: msLogin
    parent: Overlay.overlay
    backend: launcherBackend
    onFailed: (message) => showToast(message, "#cc6666", 4000)
}

// открывать только в сборке с Qt WebEngine
Component.onCompleted: if (launcherBackend.microsoftAvailable) msLogin.openAt(loginUrl)
```

---

При создании этого документа использовался ИИ.
