Add a dedicated section describing the licensing conflict between the AGPL‑3.0‑licensed web client and App Store DRM/usage rules. Explain why this is a non‑technical blocker, outline possible distribution approaches (server‑loaded client, OTA updates, PWA, sideload), and recommend confirming the chosen path before implementing any iOS wrapper code.
360 lines
30 KiB
Markdown
360 lines
30 KiB
Markdown
# Мобильное приложение gitmost — исследование и план
|
|
|
|
> Статус: исследовательский + проектный документ.
|
|
> Контекст: gitmost — форк Docmost, чистое веб-приложение. Отдельного
|
|
> мобильного (нативного/устанавливаемого) приложения **нет**.
|
|
> Цель: определить путь к мобильным приложениям — **iOS обязательно, Android
|
|
> как пойдёт** — с заделом на оффлайн в будущем (оффлайн сейчас не требуется).
|
|
|
|
Документ фиксирует, что уже есть в коде, почему путь к мобилке предопределён
|
|
устройством продукта, сравнивает варианты и описывает рекомендуемый план с
|
|
привязкой к файлам.
|
|
|
|
---
|
|
|
|
## 1. TL;DR
|
|
|
|
1. **Нативного приложения нет.** В проекте отсутствуют Capacitor, React Native,
|
|
Cordova и т.п. Мобильного клиента ещё не начинали.
|
|
2. **Адаптивная веб-версия — есть, и довольно проработанная.** Веб-клиент
|
|
открывается с телефона как mobile-friendly сайт: сворачиваемый сайдбар-drawer,
|
|
отдельные мобильные компоненты (история, поиск, хлебные крошки), responsive-
|
|
примитивы Mantine, mobile-tuned `viewport`. Это готовый фундамент UI.
|
|
3. **Ядро продукта — веб-редактор — нативно не воспроизвести.** TipTap 3
|
|
(ProseMirror) + совместное редактирование на Yjs/Hocuspocus плотно сшиты с
|
|
React. Production-порта Yjs под Swift/Kotlin нет. Любой реалистичный путь
|
|
оставляет редактор в **WebView**.
|
|
4. **API уже готов к нативному клиенту.** Сервер принимает JWT не только из
|
|
cookie, но и из заголовка `Authorization: Bearer`. Есть точка входа для
|
|
вебсокета совместного редактирования (`POST /auth/collab-token`).
|
|
5. **Рекомендуемый путь — Capacitor:** обернуть существующий React-SPA в
|
|
нативную оболочку (iOS + Android из одного кода), добавить нативные плагины
|
|
(push, биометрия, share, файлы). Эволюция в гибрид (нативная навигация +
|
|
WebView-редактор) делается потом инкрементально, без переписывания.
|
|
6. **Оффлайн-будущее уже заложено** (Yjs + `y-indexeddb`). Детальный план —
|
|
в [offline-sync-plan.md](offline-sync-plan.md); мобильное приложение этот
|
|
план переиспользует, а не дублирует.
|
|
7. **Главный блокер — не технический, а лицензионный.** AGPL форка несовместима
|
|
с условиями App Store, если зашивать веб-клиент в бинарник: DRM/usage-rules
|
|
Apple = «дополнительные ограничения», запрещённые AGPLv3 §10. Развязки —
|
|
грузить клиент с сервера (не из `.ipa`), PWA или sideload. Детали и матрица —
|
|
в §9; закрывать **до** кода обёртки.
|
|
|
|
---
|
|
|
|
## 2. Текущее состояние (как есть)
|
|
|
|
### 2.1. Стек
|
|
|
|
| Слой | Технологии |
|
|
|---|---|
|
|
| Бэкенд | NestJS 11 + Fastify, Kysely/Postgres, Redis/BullMQ. API в стиле RPC-POST (соглашение Docmost). Аутентификация — JWT. |
|
|
| Фронт | React 18 + Vite + Mantine + TanStack Query + i18next. Обычный SPA. |
|
|
| Ядро (редактор) | TipTap 3 (ProseMirror) + совместное редактирование на Yjs через Hocuspocus — см. [page-editor.tsx](../apps/client/src/features/editor/page-editor.tsx). |
|
|
| Оффлайн-фундамент | `yjs` + `y-indexeddb` уже в зависимостях клиента (локальная CRDT-копия тела документа). |
|
|
|
|
### 2.2. Мобильного приложения нет
|
|
|
|
В `package.json` и `apps/*/package.json` нет `capacitor`, `react-native`,
|
|
`cordova`, `expo`. Нативной оболочки в репозитории не заведено.
|
|
|
|
### 2.3. Адаптивная веб-версия — есть
|
|
|
|
| Что | Где |
|
|
|---|---|
|
|
| Адаптивная оболочка Mantine `AppShell` с `breakpoint: "sm"`, раздельные состояния `collapsed.mobile` / `collapsed.desktop` | [global-app-shell.tsx](../apps/client/src/components/layouts/global/global-app-shell.tsx) (L85–99) |
|
|
| Отдельный мобильный сайдбар-drawer (`mobileSidebarAtom` отделён от `desktopSidebarAtom`), авто-закрытие при навигации по дереву | [sidebar-atom.ts](../apps/client/src/components/layouts/global/hooks/atoms/sidebar-atom.ts), [space-tree-row.tsx](../apps/client/src/features/page/tree/components/space-tree-row.tsx) (L147–148) |
|
|
| Мобильная модалка истории + свой CSS | [history-modal.tsx](../apps/client/src/features/page-history/components/history-modal.tsx) (L17–19), `history-modal-mobile.tsx` |
|
|
| Мобильный контрол поиска | [search-control.tsx](../apps/client/src/features/search/components/search-control.tsx) (L38–42) |
|
|
| Мобильный рендер хлебных крошек через `useMediaQuery` | [breadcrumb.tsx](../apps/client/src/features/page/components/breadcrumbs/breadcrumb.tsx) (L41) |
|
|
| Responsive-примитивы `hiddenFrom`/`visibleFrom` (~16 мест), медиа-запросы в CSS-модулях | по всему `apps/client/src` |
|
|
| Mobile-tuned viewport (`width=device-width, user-scalable=no`) | [index.html](../apps/client/index.html) (L8) |
|
|
|
|
> Важно: адаптив проверялся в мобильном **браузере**, а не в WebView нативной
|
|
> оболочки. Перед сборкой приложения нужно прогнать UI как PWA/в WebView и
|
|
> отловить отличия (жесты, экранная клавиатура/IME в редакторе, safe-area).
|
|
|
|
### 2.4. Готовность API к нативному клиенту
|
|
|
|
- **Bearer-токен уже поддержан.** JWT извлекается из cookie **или** из заголовка
|
|
`Authorization`: см. [jwt.strategy.ts](../apps/server/src/core/auth/strategies/jwt.strategy.ts) (L27–29).
|
|
Серверная сторона нативной авторизации менять не нужно.
|
|
- **Токен сейчас не возвращается в теле логина.** [`login`](../apps/server/src/core/auth/auth.controller.ts)
|
|
(L55–105) кладёт JWT только в `httpOnly`-cookie ([`setAuthCookie`](../apps/server/src/core/auth/auth.controller.ts) L222–230).
|
|
- **Точка входа вебсокета коллаборации:** [`POST /auth/collab-token`](../apps/server/src/core/auth/auth.controller.ts) (L187–193).
|
|
- **CORS открыт без конфигурации:** [`app.enableCors()`](../apps/server/src/main.ts) (L144).
|
|
- **OpenAPI/Swagger отсутствует** (`@nestjs/swagger` не подключён) — авто-генерации
|
|
типизированного клиента сейчас нет.
|
|
|
|
---
|
|
|
|
## 3. Почему путь к мобилке предопределён
|
|
|
|
Три факта диктуют решение независимо от моды:
|
|
|
|
1. **Редактор практически невозможно переписать нативно.** ProseMirror + весь
|
|
набор TipTap-расширений + Yjs-CRDT — это не «поле ввода». Нативного
|
|
production-порта Yjs под Swift/Kotlin нет (есть Rust `yrs` с биндингами, но
|
|
это отдельный тяжёлый проект). Переписывание ядра нативно = годы и вечное
|
|
расхождение с веб-версией. **Вывод: редактор остаётся в WebView.**
|
|
2. **API уже умеет нативного клиента** (Bearer, collab-token).
|
|
3. **Оффлайн-фундамент уже заложен** на веб-уровне (Yjs + `y-indexeddb`),
|
|
и он работает внутри WebView.
|
|
|
|
---
|
|
|
|
## 4. Три возможных пути
|
|
|
|
| Путь | Суть | Плюсы | Минусы | Вердикт |
|
|
|---|---|---|---|---|
|
|
| **A. Полностью нативно** (Swift/Kotlin) | Переписать всё, включая редактор и CRDT-синк | Максимально нативный UX | Воспроизвести ProseMirror + расширения + Yjs; несоразмерные трудозатраты; вечное отставание от веба | ❌ Не наш случай |
|
|
| **B. WebView-обёртка SPA (Capacitor)** | Обернуть существующий React-клиент в нативную оболочку, native-возможности — плагинами | Реюз ~100% кода (редактор, коллаборация, оффлайн); один кодовый бэйз → iOS+Android; быстро | Менее «нативно»; риск отказа App Store за «просто сайт» (4.2) — лечится нативной ценностью | ✅ Рекомендуется |
|
|
| **C. Гибрид: нативная оболочка + WebView-редактор** | Навигация/списки/поиск/логин — нативно (React Native/Swift), экран редактирования — web в WebView | Лучший UX; путь Notion/Linear | Заметно больше работы; нужен мост JS↔native | ⚖️ Цель эволюции из B |
|
|
|
|
---
|
|
|
|
## 5. Рекомендуемый путь
|
|
|
|
**B (Capacitor) как первый релиз, с заложенной эволюцией в C.**
|
|
|
|
Почему:
|
|
- Capacitor создан под сценарий «есть веб-приложение → хочу его в App Store с
|
|
нативными возможностями». Переиспользуется весь React-клиент и, главное,
|
|
редактор — то, что нативно не сделать.
|
|
- Один кодовый бэйз закрывает «iOS обязательно» и «Android как пойдёт»
|
|
одновременно, без второй команды.
|
|
- Адаптивная вёрстка уже есть (см. §2.3) — переверстывать под телефон с нуля
|
|
не нужно; работа смещается в нативную обвязку.
|
|
- Оффлайн-будущее подготовлено (Yjs + `y-indexeddb`); см.
|
|
[offline-sync-plan.md](offline-sync-plan.md).
|
|
- Когда упрётесь в UX отдельных экранов — их по одному выносят в нативную
|
|
оболочку, оставив редактор в WebView. То есть B → C делается инкрементально.
|
|
|
|
Почему **не** чистый React Native сразу: редактор всё равно придётся держать в
|
|
WebView (ядро web-only), но при этом теряется прямой реюз остального React-кода
|
|
и появляется мост как обязательная сложность с первого дня — для iOS-first
|
|
старта это лишний оверхед.
|
|
|
|
> Альтернатива: если критичен максимально нативный UX с первого релиза и есть
|
|
> ресурс — сразу путь C на React Native (Expo) с WebView только под редактор.
|
|
> Это сознательный размен «больше работы сейчас» за «более нативное ощущение».
|
|
|
|
⚠️ **Лицензионная оговорка к iOS.** Обычный Capacitor зашивает веб-билд
|
|
`apps/client` в `.ipa` — для публикации в App Store это **нарушает AGPL**
|
|
(см. §9). Выбор Capacitor для **Android** остаётся в силе, но на **iOS**
|
|
веб-клиент нельзя бандлить в бинарник: либо грузить его с сервера
|
|
(`server.url`), либо PWA. То есть рекомендация «B (Capacitor)» применима к
|
|
Android как есть, а к iOS — только в конфигурации без зашитого AGPL.
|
|
|
|
---
|
|
|
|
## 6. Что доработать на бэкенде
|
|
|
|
Немного, но конкретно:
|
|
|
|
1. **Выдача токена в теле ответа для нативного хранения.** Сейчас логин кладёт
|
|
JWT только в `httpOnly`-cookie и не возвращает его в body. На мобиле
|
|
`httpOnly`-cookie между разными origin (`capacitor://localhost` ↔ API) — боль
|
|
с SameSite/CORS. Чище: мобильный логин-флоу, возвращающий JWT в ответе, чтобы
|
|
хранить его в Keychain/Keystore и слать как `Authorization: Bearer`. Сервер
|
|
уже принимает Bearer — менять надо только **выдачу**.
|
|
Файлы: [auth.controller.ts](../apps/server/src/core/auth/auth.controller.ts).
|
|
2. **CORS.** Сейчас [`app.enableCors()`](../apps/server/src/main.ts) (L144) без
|
|
конфигурации. Под мобильные origin'ы и для безопасности задать явный whitelist.
|
|
3. **Push-уведомления.** Модуль `notification` уже есть — добавить регистрацию
|
|
device-token и интеграцию **APNs** (iOS) / **FCM** (Android).
|
|
4. **Опционально — OpenAPI/Swagger.** Сейчас спецификации нет; добавить
|
|
`@nestjs/swagger` дёшево и сильно ускорит мобильную разработку
|
|
(типизированный клиент).
|
|
|
|
---
|
|
|
|
## 7. Android-специфика
|
|
|
|
На пути Capacitor Android едет почти бесплатно (`npx cap add android` из того же
|
|
веб-билда), но есть нюансы:
|
|
|
|
- **Движок в плюс.** Android System WebView (Chromium) обновляется через Play
|
|
Store независимо от ОС и обычно свежее iOS WKWebView. Более рискованный движок
|
|
по совместимости — это iOS, а не Android.
|
|
- **Фрагментация.** Дешёвые/старые устройства с малой памятью и устаревшим
|
|
WebView; стек тяжёлый (ProseMirror + Yjs + mermaid + katex + excalidraw) —
|
|
тестировать на бюджетных аппаратах.
|
|
- **Обвязка под Android:** аппаратная/жестовая кнопка «Назад» (навигация внутри
|
|
приложения, а не выход), **FCM** для push, Android App Links (вместо iOS
|
|
Universal Links), подписание и Play Console.
|
|
- **Главный риск именно для Android — ввод текста в ProseMirror на Gboard/IME.**
|
|
Историческая боль `contenteditable` на Android (прыжки курсора, дубли символов
|
|
при композиции). Стало лучше, но **проверять в первую очередь и рано**.
|
|
- **Магазин.** Google Play лояльнее к webview-обёрткам, чем App Store; риск
|
|
«отклонят как просто сайт» для Play практически неактуален.
|
|
|
|
---
|
|
|
|
## 8. iOS-специфика
|
|
|
|
- **WKWebView** на движке WebKit жёстко привязан к версии ОС — это более
|
|
рискованный по совместимости движок (тестировать прежде всего его).
|
|
- **App Store guideline 4.2 (minimum functionality).** Чистая webview-обёртка
|
|
рискует отклонением «это просто сайт». Лечится реальной нативной ценностью:
|
|
push, share-extension, биометрический разблок, оффлайн-кэш — всё это Capacitor
|
|
даёт плагинами.
|
|
- **safe-area** под «чёлку»/системные панели, поведение экранной клавиатуры в
|
|
редакторе.
|
|
|
|
---
|
|
|
|
## 9. Лицензионный блокер: AGPL ↔ App Store (iOS)
|
|
|
|
> Это не инженерная, а **лицензионная** задача — закрывать её надо **до** кода
|
|
> обёртки, иначе можно сделать приложение, которое некуда легально опубликовать.
|
|
> Ниже — инженерно-лицензионный разбор, **не** юридическая консультация; финально
|
|
> подтверждать у того, кто разбирается в лицензиях.
|
|
|
|
### 9.1. Суть конфликта
|
|
|
|
gitmost — форк Docmost под **AGPL-3.0** (константа форка: «100% open, AGPL-only»).
|
|
Две вещи несовместимы:
|
|
|
|
- **AGPLv3 §10** (последний абзац) запрещает накладывать на получателя кода
|
|
**любые дополнительные ограничения** сверх самой лицензии.
|
|
- **Стандартный EULA App Store** ровно их и накладывает: **FairPlay/DRM**,
|
|
привязка установки к Apple ID с лимитом устройств (**usage rules**), запрет
|
|
свободного перераспространения бинарника.
|
|
|
|
Приняв условия Apple, чтобы попасть в App Store, вы нарушаете AGPL кода, который
|
|
раздаёте.
|
|
|
|
### 9.2. Почему это бьёт именно по форку
|
|
|
|
Запрет «дополнительных ограничений» связывает **лицензиатов, но не самого
|
|
правообладателя**: владелец 100% копирайта может опубликовать свой код в App Store.
|
|
Но в gitmost бóльшая часть копирайта принадлежит **upstream-Docmost** и
|
|
контрибьюторам — вы выступаете дистрибьютором *чужого* AGPL-кода и не можете
|
|
единолично добавить App-Store-исключение.
|
|
|
|
Прецеденты: **VLC** (удалён из App Store в 2011 по жалобе на конфликт GPL с
|
|
условиями стора; вернулся только после перелицензирования и согласия
|
|
правообладателей), **GNU Go** — снят по той же причине. Это не теоретический риск.
|
|
|
|
### 9.3. Ключевой принцип развязки: лицензия смотрит на `.ipa`, а не на устройство
|
|
|
|
Определяющее — **что раздаёт сам Apple** (`.ipa` под FairPlay) и **кто раздаёт
|
|
AGPL-байты**, а не то, окажутся ли они в итоге на устройстве:
|
|
|
|
- AGPL **внутри `.ipa`** → получен под ограничениями Apple → **нарушение**.
|
|
- AGPL **скачан с вашего сервера** → получен от вас под AGPL (исходники открыты,
|
|
§13 выполнен) → ограничения Apple на него **не** накладываются, даже если бандл
|
|
кэшируется в песочнице приложения.
|
|
|
|
Следствие: **офлайн на iOS легально достижим** — если кэшированный бандл пришёл с
|
|
вашего сервера, а не из `.ipa`. Ограничение тут не лицензионное, а в **ревью
|
|
Apple** (см. §9.5).
|
|
|
|
### 9.4. Варианты «грузить веб-клиент с сервера»
|
|
|
|
**A. WebView навигируется на хостед-клиент (`server.url`).** Capacitor умеет
|
|
`server: { url: 'https://app.example.com' }` — оболочка грузит WebView с удалённого
|
|
URL, мост и нативные плагины по-прежнему инжектятся. В `.ipa` — ноль AGPL.
|
|
|
|
- Плюс: лицензионно самый чистый; **origin = ваш домен**, поэтому cookie/CORS
|
|
работают как в браузере (боль `capacitor://localhost` ↔ API из §6 исчезает —
|
|
токен в body/Keychain может и не понадобиться).
|
|
- Минус: холодный старт требует сети; сервер лёг → приложение кирпич; офлайна по
|
|
умолчанию нет.
|
|
|
|
**B. OTA: пустой шелл скачивает и кэширует бандл.** Шелл при первом запуске тянет
|
|
JS-бандл с вашего сервера и кэширует как веб-ассеты (механизм Cordova/CodePush).
|
|
Open-source self-host-вариант — `@capgo/capacitor-updater` (важно для AGPL-проекта:
|
|
без привязки к проприетарному Appflow).
|
|
|
|
- Плюс: **даёт офлайн** — кэш AGPL легален, т.к. распространён вами, а не Apple.
|
|
- Минус: упирается в политику Apple по hot-update (§9.5).
|
|
|
|
**Не-обходы (мифы):** «никто не засудит» — это нарушение, а не обход; «LGPL-нуть
|
|
обёртку» — не помогает (проблема в AGPL-веб-клиенте, а не в обёртке); «mere
|
|
aggregation» — не катит: зашитый бандл это комбинированное распространяемое
|
|
произведение, а не простая агрегация.
|
|
|
|
### 9.5. Гейты Apple
|
|
|
|
| # | Guideline | Суть | Влияние |
|
|
|---|---|---|---|
|
|
| 1 | **2.5.2** (исполняемый код) | Скачивать/исполнять **нативный** код нельзя, **но** есть исключение для скриптов, исполняемых встроенным WebKit/JavascriptCore, если они не меняют назначение приложения | Загрузка веб-клиента в `WKWebView` под исключение попадает: вариант A — чистый, B — терпимый, но с границами |
|
|
| 2 | **4.2** (minimum functionality) | Чистый WebView-«просто сайт» рискует отклонением | Лечится нативной ценностью в оболочке (push/APNs, биометрия, share, файлы — ваш нативный код, не AGPL) |
|
|
| 3 | конфликт двух гейтов | «Лицензионно чистый» вариант (пустой шелл качает всё) — самый рискованный для ревью; «безопасный для ревью» (зашить веб-билд в `.ipa`) — лицензионное нарушение | **Совместить (офлайн) + (чистая AGPL) + (низкий риск ревью) в одной конфигурации нельзя — выбираете любые два** |
|
|
|
|
Безопасность: раз исполняете удалённый код — только HTTPS, желательно cert-pinning
|
|
(подмена сервера = произвольный JS в WebView пользователя).
|
|
|
|
### 9.6. Итоговая матрица распространения iOS
|
|
|
|
| Конфигурация | AGPL-чистота | Офлайн | Риск ревью Apple |
|
|
|---|---|---|---|
|
|
| A. `server.url` на хостед-клиент | ✅ чистая | ❌ нет | средний (4.2, лечится плагинами) |
|
|
| B. OTA пустой шелл + кэш бандла | ✅ чистая | ✅ есть | выше (2.5.2 + 4.2) |
|
|
| Зашить веб-билд в `.ipa` (обычный Capacitor) | ❌ нарушение | ✅ | низкий |
|
|
| **PWA** | ✅ чистая | ✅ | App Store не нужен |
|
|
| Sideload / EU DMA-маркетплейсы (iOS 17.4+) | ✅ чистая | ✅ | вне App Store; **только ЕС** |
|
|
|
|
**Вывод:** для iOS **PWA** — самое дешёвое решение, закрывающее всё сразу. Если
|
|
присутствие именно в App Store критично — **вариант A** (`server.url` + нативные
|
|
плагины под 4.2) легальный и реалистичный ценой «онлайн для холодного старта».
|
|
Офлайн в App Store (вариант B) технически и лицензионно возможен, но это
|
|
максимальный риск на ревью — закладывать только если офлайн на iOS обязателен.
|
|
Совместить «App Store + зашитый офлайн AGPL» легально нельзя, пока копирайт не ваш.
|
|
|
|
---
|
|
|
|
## 10. Оффлайн в будущем
|
|
|
|
Оффлайн сейчас не требуется, но позиция хорошая:
|
|
|
|
- Тело документа уже редактируется через Yjs (CRDT) + `y-indexeddb` — локальная
|
|
копия и автослияние правок работают, в том числе в WebView.
|
|
- «Полностью онлайн» — это всё вокруг тела (навигация, заголовки, комментарии,
|
|
CRUD, вложения, авторизация). Их оффлайн-синхронизация описана отдельным
|
|
планом с этапами M0…M4 — см. [offline-sync-plan.md](offline-sync-plan.md).
|
|
- Мобильное приложение **переиспользует** этот план, а не строит оффлайн заново.
|
|
Нюанс Android: System WebView под нехваткой места может чистить хранилище →
|
|
для оффлайна, возможно, понадобится дублировать критичные данные в нативное
|
|
хранилище, чтобы локальные копии не вычищались.
|
|
|
|
---
|
|
|
|
## 11. Открытые вопросы (зафиксировать до старта)
|
|
|
|
- **Q1.** Путь: Capacitor (B) с эволюцией в гибрид, или сразу React Native (C)?
|
|
Рекомендация — B.
|
|
- **Q2.** Мобильная авторизация: отдельный логин-флоу с токеном в body + Keychain/
|
|
Keystore + Bearer (рекомендуется) или попытка работать через cookie в WebView?
|
|
- **Q3.** Push: APNs + FCM сразу или iOS-first?
|
|
- **Q4.** Подключать ли OpenAPI/Swagger для генерации мобильного клиента?
|
|
- **Q5.** Когда включать оффлайн (M0…M4 из offline-sync-plan.md) относительно
|
|
первого мобильного релиза?
|
|
- **Q6.** iOS-дистрибуция при AGPL (§9): App Store через `server.url`
|
|
(онлайн-клиент, без зашитого AGPL), PWA или sideload/EU-маркетплейсы? Этот
|
|
лицензионный путь нужно подтвердить **до** кода обёртки. Рекомендация — PWA для
|
|
iOS, Capacitor для Android.
|
|
|
|
---
|
|
|
|
## 12. Чеклист первого шага (бутстрап Capacitor, iOS-first)
|
|
|
|
- [ ] **Закрыть лицензионный путь iOS (§9) ДО кода обёртки:** выбрать
|
|
`server.url` / PWA / sideload и подтвердить у разбирающегося в лицензиях.
|
|
- [ ] **Не бандлить AGPL-веб-клиент в iOS `.ipa`** (DRM/usage-rules App Store ⟂
|
|
AGPLv3 §10) — на iOS грузить клиент с сервера или идти через PWA.
|
|
- [ ] Прогнать существующий адаптивный UI как PWA/в WebView, отловить отличия
|
|
(жесты, IME в редакторе, safe-area).
|
|
- [ ] Добавить Capacitor в монорепо, нацелить на веб-билд `apps/client`
|
|
(Android — зашитый билд; iOS — `server.url`/PWA без зашитого AGPL, см. §9).
|
|
- [ ] `npx cap add ios` (Android — `npx cap add android`, когда будет готова обвязка).
|
|
- [ ] Бэкенд: мобильный логин-флоу с токеном в body; хранить токен в Keychain/
|
|
Keystore; слать `Authorization: Bearer`.
|
|
- [ ] Бэкенд: явный CORS-whitelist под мобильные origin'ы.
|
|
- [ ] Native-плагины под App Store 4.2: push, биометрия, share, файлы.
|
|
- [ ] Push: APNs (iOS); FCM добавить вместе с Android.
|
|
- [ ] Проверить вебсокет коллаборации из WebView (`/auth/collab-token` + Hocuspocus).
|
|
- [ ] (Опционально) Подключить `@nestjs/swagger`.
|