docs: update docs
This commit is contained in:
@@ -0,0 +1,60 @@
|
||||
# twitch-panel-bio
|
||||
|
||||
Twitch Panel Extension: одна панель (318×496), внутри — несколько документов.
|
||||
Контент — Markdown из git-репозитория: push → CI собирает → зрители видят новое
|
||||
через ≤5 минут. Никаких бэкендов и кнопок «обновить».
|
||||
|
||||
## Структура
|
||||
|
||||
```
|
||||
viewer.html / config.html входы (single-file билд, один файл каждый)
|
||||
src/viewer/ вьювер: пейджер документов, тема Twitch
|
||||
src/config/ панель управления (Creator Dashboard): порядок/скрытие
|
||||
src/shared/DocRenderer.svelte рекурсивный рендерер JSON-AST (без {@html})
|
||||
builder/ валидация MD по белому списку → JSON-AST, sharp-картинки
|
||||
content/docs/*.md документы (front-matter: title обязателен, order/hidden/header)
|
||||
.github/workflows/publish.yml CI: push в main → ветка `published` (index.json + docs + img)
|
||||
config.json ← впиши сюда repo: "user/repo"
|
||||
```
|
||||
|
||||
## Команды
|
||||
|
||||
| Команда | Что делает |
|
||||
|---|---|
|
||||
| `npm run dev` | dev-сервер (HTTPS :8080): `/viewer.html` и `/config.html` |
|
||||
| `npm run build` | два single-file билда → `dist/pkg/` (viewer.html + config.html) — это zip-билд |
|
||||
| `npm run build:content` | контент: `content/` → `out/` (index.json, docs/*.json, img/*.webp) |
|
||||
|
||||
Локально билдер требует заполненного `config.json`; в CI SHA коммита подставляется автоматически.
|
||||
|
||||
## Контент
|
||||
|
||||
- Документы: `content/docs/<id>.md`. Front-matter: `title` (обязателен), `order` (число),
|
||||
`hidden` (bool), `header` (путь к заголовочной картинке от `content/assets/`).
|
||||
- Картинки в тексте: `` — путь относительно .md; билдер жмёт в WebP
|
||||
(макс. ширина 640) и переписывает на иммутабельный jsDelivr-URL. Битые пути = ошибка сборки.
|
||||
- Заголовочные картинки режутся в 636×340 (панель 318×170 @2x, object-fit: cover).
|
||||
- Белый список: заголовки, абзацы, **жирный**/*курсив*/~~зачёркнутый~~, списки (включая
|
||||
GFM-чекбоксы), ссылки (только http/https), картинки, цитаты, `---`. Всё прочее
|
||||
(HTML, код-блоки, таблицы) — ошибка сборки с файлом:строкой.
|
||||
|
||||
## Доставка
|
||||
|
||||
CI (`push` в `main`) собирает `out/` и коммитит в ветку `published` (peaceiris/actions-gh-pages,
|
||||
история сохраняется). Вьювер читает `raw.githubusercontent.com/<repo>/published/index.json`
|
||||
(кэш 5 мин), документы — по абсолютным jsDelivr-URL с SHA коммита (иммутабельные).
|
||||
Конфиг-сегмент Twitch хранит только per-channel исключения: `{v:1, hidden:[], order:[]}`.
|
||||
|
||||
## Чеклист Twitch-консоли (dev.twitch.tv → Console)
|
||||
|
||||
1. Create Extension → тип **Panel**, заполнить имя/описание.
|
||||
2. **Asset Hosting**: Base URI `https://localhost:8080/` (для Local Test);
|
||||
Panel Viewer Path = `/viewer.html`; Panel Config Path = `/config.html`.
|
||||
3. **CSP**: connect-src — `raw.githubusercontent.com`, `cdn.jsdelivr.net`;
|
||||
img-src — `cdn.jsdelivr.net` (добавить raw, если картинки будут там).
|
||||
4. Загрузить zip из `dist/pkg/` (Files → Upload Version).
|
||||
5. Local Test → Activate на своём канале (ревью не нужно). Публичный релиз —
|
||||
Hosted Test → Review (walkthrough + change log).
|
||||
|
||||
Pop-out отключить нельзя — это кнопка UI Twitch. Скрипт Helper (`twitch-ext.min.js`)
|
||||
обязателен в обоих HTML — уже подключён.
|
||||
+327
@@ -0,0 +1,327 @@
|
||||
# Исследование: Twitch Panel Extension «мультидокументная панель»
|
||||
|
||||
> Задача: расширение размером в одну Twitch-панель (в блоке «О канале»), внутри которого
|
||||
> листается несколько документов на основе Markdown (+ заголовочные картинки).
|
||||
> Управление контентом — через удобную админ-панель; желательно обновление через git.
|
||||
> Фронтенд — Svelte 5.
|
||||
|
||||
---
|
||||
|
||||
## 1. Вердикт
|
||||
|
||||
**Полностью реализуемо, без бэкенда, бесплатно.** Платформа Twitch Extensions жива
|
||||
(2025–2026, Developer Rig устарел, но сами Extensions и ревью работают).
|
||||
Ключевой трюк: код приложения (зашит в билд расширения) почти не меняется, а контент
|
||||
(MD + картинки) тянется в рантайме из git-репозитория через CORS-дружелюбный CDN
|
||||
(jsDelivr / raw.githubusercontent.com — оба отдают `Access-Control-Allow-Origin: *`,
|
||||
проверено curl-ом). Обновление контента = `git push`, без пересборки и без ревью Twitch.
|
||||
|
||||
---
|
||||
|
||||
## 2. Ключевые факты о панельных расширениях (проверено по докам)
|
||||
|
||||
| Факт | Значение |
|
||||
|---|---|
|
||||
| Тип расширения | **Panel** — «коробка» в блоке «О канале», под плеером |
|
||||
| Размер | **318 × 496 px**, жёстко (чтобы не было скролла iframe); скроллим внутренний контент |
|
||||
| Активность | Панель видна **даже когда канал офлайн** — идеально для био/справки |
|
||||
| Pop-out | Зритель может открыть расширение в отдельном окне побольше |
|
||||
| Хостинг кода | zip-загрузка на CDN Twitch (`*.ext-twitch.tv`) ИЛИ свой HTTPS-хостинг (Base URI) |
|
||||
| Config Service | 3 сегмента (developer / broadcaster / global), **5 КБ на сегмент**, публично читаемо |
|
||||
| Helper-библиотека | `<script src="https://extension-files.twitch.tv/helper/v1/twitch-ext.min.js">` — обязательна |
|
||||
| CSP | Настраивается в консоли: домены для `connect-src` и `img-src` надо внести в allowlist |
|
||||
| Sandbox | нет `alert/confirm/prompt`, нет вложенных iframe, нет top-navigation, storage API ограничен |
|
||||
| Мобилки | Панели показываются в мобильном приложении, если включить mobile-ready; бюджет первой загрузки **1 МБ / 3 сек** |
|
||||
| Ревью | Нужно только для публичного релиза. **На свой канал ставится без ревью** (Local Test → Activate) |
|
||||
| Жизненный цикл | Local Test → Hosted Test → Review → Released (Released не редактируется, только новая версия) |
|
||||
|
||||
Важно: в Config Service можно хранить **только метаданные** (список документов, порядок,
|
||||
URL'ы), не сами документы — 5 КБ мало, и сегмент публичен (никаких секретов).
|
||||
Контент живёт в git-репозитории.
|
||||
|
||||
---
|
||||
|
||||
## 3. Архитектура (рекомендуемый вариант, без бэкенда)
|
||||
|
||||
```
|
||||
GitHub-репозиторий (public) Twitch
|
||||
├─ docs/
|
||||
│ ├─ index.json ← манифест контента ┌─────────────────────────────┐
|
||||
│ ├─ rules.md ← сами документы │ Extension (Panel 318×496) │
|
||||
│ ├─ faq.md │ Svelte 5 + Vite │
|
||||
│ └─ assets/ │ ┌───────────────────────┐ │
|
||||
│ ├─ rules-header.png │ │ табы/пейджер доков │ │
|
||||
│ └─ ... │ │ MD → HTML (sanitized) │ │
|
||||
└─ (git push = обновление контента) │ │ картинки из CDN git'а │ │
|
||||
│ └───────────────────────┘ │
|
||||
Config Service (5 КБ) │ Config view (в дашборде): │
|
||||
broadcaster segment ← JSON: список доков, │ CRUD документов, порядок, │
|
||||
порядок, заголовки, URL md/картинок │ сохранение в конфиг-сегмент│
|
||||
└─────────────────────────────┘
|
||||
```
|
||||
|
||||
**Поток данных во вьювер-вью:**
|
||||
1. При старте Helper отдаёт `configuration.broadcaster` (JSON со списком документов).
|
||||
2. Приложение рендерит табы/карусель; по выбору таба лениво фетчит MD с jsDelivr
|
||||
(`https://cdn.jsdelivr.net/gh/<user>/<repo>@<branch|sha>/docs/faq.md`).
|
||||
3. Markdown → HTML (`marked`/`markdown-it`) → `DOMPurify.sanitize` → `{@html}` в Svelte.
|
||||
4. Относительные пути картинок переписываются на абсолютные URL CDN до рендера.
|
||||
5. Кэширование: jsDelivr держит кэш ветки ~12ч; для мгновенных апдейтов — либо
|
||||
`@sha`-пиннинг (SHA коммита кладётся в index.json/конфиг), либо cache-buster query.
|
||||
|
||||
**Конфиг-вью (панель управления):** отдельная страница того же Svelte-приложения
|
||||
(в консоли на неё указывает Panel Config Path; режим можно бранчить по query `?mode=config`,
|
||||
которую Helper/Twitch сам подставляет). UI: список документов, добавить/удалить/скрыть,
|
||||
переупорядочить (drag&drop), заголовок, ссылка на .md, URL заголовочной картинки,
|
||||
кнопка «Сохранить» → `Twitch.ext.configuration.set('broadcaster', '1', JSON.stringify(...))`.
|
||||
|
||||
**Картинки:** заголовочные и inline-картинки кладутся в `docs/assets/` в том же репо
|
||||
(«загрузка картинки» = файл в репо; при желании конфиг-вью может коммитить их сам через
|
||||
GitHub REST API — см. открытый вопрос про PAT).
|
||||
|
||||
### Альтернативы
|
||||
- **С EBS (свой мини-бэкенд, CF Worker/Node):** синк репо по webhook, прокси приватного
|
||||
репо, серверное хранение PAT, аплоад картинок из UI коммитом в репо. Плюсы: приватность,
|
||||
секреты, мгновенная инвалидация. Минусы: инфраструктура, которую мы хотели избежать.
|
||||
- **Без git вообще (Twitch-only):** нежизнеспособно — Config Service 5 КБ, файловый хост
|
||||
Twitch меняется только вместе с билдом версии. Именно поэтому внешний CDN — правильный путь.
|
||||
|
||||
---
|
||||
|
||||
## 4. Git-обновления: что именно работает
|
||||
|
||||
- `raw.githubusercontent.com` — `ACAO: *`, кэш 5 мин.
|
||||
- `cdn.jsdelivr.net/gh/...` — `ACAO: *`, кэш браузера 7 дней / CDN 12 ч; поддерживает
|
||||
пиннинг по ветке, тегу и SHA; есть минификация для `gh`.
|
||||
- GitHub Pages — тоже `ACAO: *` (можно собирать статический сайт/бандлы из репо).
|
||||
|
||||
Схема: ветка `main` = черновик, тег/отдельная ветка `published` = то, что показывает панель;
|
||||
в конфиг-сегменте храним ссылку с `@published` + SHA. Обновить контент = push + (при
|
||||
SHA-пиннинге) одно нажатие в конфиг-вью. Никакого пересборочного пайплайна не нужно,
|
||||
если MD рендерится на клиенте; при желании можно рендерить на CI (GitHub Actions →
|
||||
JSON-бандл в `gh-pages`), но это опция.
|
||||
|
||||
---
|
||||
|
||||
## 5. Плюсы и минусы
|
||||
|
||||
### Плюсы
|
||||
- Нативное место на канале, виден офлайн — ровно то, что нужно для био/справки.
|
||||
- Одна панель слота — сколько угодно документов (табы), лимит только UX.
|
||||
- Хостинг кода на CDN Twitch бесплатно; контент — на CDN GitHub бесплатно. Ноль затрат, ноль серверов.
|
||||
- Обновление контента через `git push` — без пересборки расширения и без ревью (ревью нужно только коду при публичном релизе).
|
||||
- Config Service бесплатно решает per-channel настройки (у каждого стримера свой список документов).
|
||||
- Svelte 5 здесь уместен: маленький бандл (бюджет мобильной первой загрузки 1 МБ), рунеты ($state/$derived) закрывают весь стейт, Vite мульти-HTML сборка из коробки.
|
||||
- Версионирование контента бесплатно из коробки — это git.
|
||||
|
||||
### Минусы / ограничения
|
||||
- 318×496 px — тесно для длинных документов; обязателен внутренний скролл и мобильный дизайн; pop-out спасает частично.
|
||||
- CSP-allowlist: каждый внешний домен (jsDelivr, GitHub) надо руками вносить в консоли на версию — забыл = контент молча не грузится.
|
||||
- Config Service публичен и мал (5 КБ) — секреты (PAT) там хранить нельзя.
|
||||
- Нет серверного аплоада картинок в CDN Twitch в рантайме — картинки только через репо/внешний хост.
|
||||
- Песочница: нет alert/confirm, нет вложенных iframe, storage API ограничен (localStorage под вопросом — проверить рано).
|
||||
- Platform risk: Twitch вправе менять правила (Rig уже убили); расширение не переносится на другие платформы.
|
||||
- Мобильная поддержка = отдельная mobile-ready версия вьюера + жёсткий бюджет 1 МБ/3 c.
|
||||
- Для публичного релиза — ревью с гайдом/скриншотами; Released-версию нельзя править, только новая версия.
|
||||
|
||||
---
|
||||
|
||||
## 6. Библиотеки
|
||||
|
||||
| Назначение | Пакет | Комментарий |
|
||||
|---|---|---|
|
||||
| Фреймворк | `svelte@5`, `vite`, `@sveltejs/vite-plugin-svelte` | два entry: viewer.html / config.html |
|
||||
| Markdown | `marked` | крошечный, GFM из коробки; либо `markdown-it` если нужны плагины |
|
||||
| Санитайзер | `dompurify` | **обязателен** — контент внешний |
|
||||
| Заголовки-анкоры | `marked-gfm-heading-id` или `github-slugger` | для оглавления в панели |
|
||||
| Подсветка кода | `highlight.js` (опц.) | shiki тяжёлый для 1 МБ бюджета |
|
||||
| Формулы | `katex` (опц.) | если нужны |
|
||||
| Git API из UI | `octokit` (опц.) | коммит картинок из конфиг-вью |
|
||||
| Local dev | `mkcert` + Twitch CLI / community DevRig | Rig от Twitch устарел; HTTPS обязателен даже локально |
|
||||
| Типы Helper | своя `.d.ts` для `window.Twitch.ext` | npm-пакета с офиц. типами нет надёжного |
|
||||
|
||||
CSS-фреймворк не нужен — свои стили на 318px проще.
|
||||
|
||||
---
|
||||
|
||||
## 7. Что надо сделать (по шагам)
|
||||
|
||||
1. **Консоль** dev.twitch.tv → Extensions → создать (тип Panel), получить client id/secret.
|
||||
2. **Скелет**: Vite + Svelte 5, два entry (`viewer.html`, `config.html`), скрипт Helper
|
||||
в обоих, `mkcert` для localhost:8080, в консоли Base URI + пути вьюера/конфига.
|
||||
3. **Вьювер**: чтение `configuration.broadcaster` (fallback — дефолтный demo-документ),
|
||||
табы/пейджер, ленивый фетч MD, marked+DOMPurify, рерайт путей картинок, скролл.
|
||||
4. **Конфиг-вью**: CRUD списка документов + порядок + заголовочные картинки, сохранение
|
||||
сегмента через `configuration.set`. (В конфиг-вью JWT имеет роль broadcaster.)
|
||||
5. **Репозиторий контента**: `docs/*.md`, `docs/assets/`, `index.json`; CSP-домены в консоли.
|
||||
6. **Тест на своём канале**: Local Test → «Activate» в Extension Manager — ревью не нужно.
|
||||
Потом Hosted Test (загрузка zip) — проверить кэши CDN Twitch.
|
||||
7. **(Опц.) Публичный релиз**: walkthrough guide, change log, скриншоты, EULA/privacy →
|
||||
Review → Released или Invite Only (allowlist стримеров).
|
||||
|
||||
Оценка трудоёмкости MVP (вьювер + конфиг + репо + тест на своём канале): 2–4 вечера.
|
||||
|
||||
---
|
||||
|
||||
## 8. Открытые вопросы (решить в процессе)
|
||||
|
||||
1. **localStorage в sandbox** — работает ли (кэш доков между сессиями)? Проверить первым делом.
|
||||
2. **Точный лимит zip-билда** в консоли (официальной цифры в доках нет, форум даёт ориентиры ~несколько МБ) — некритично, билд маленький.
|
||||
3. **PAT для коммита картинок из конфиг-вью**: в Config Service хранить нельзя (публично).
|
||||
Варианты: вводить PAT сессией (in-memory), хранить в EBS, или отказаться от аплоада из UI —
|
||||
картинки просто коммитятся в репо руками.
|
||||
4. **Приватный контент?** Если да — только через EBS-прокси (или приватный репо + Worker).
|
||||
5. **Мобильная версия**: делать ли mobile-ready сразу; как табы ведут себя в мобильной панели.
|
||||
6. **Инвалидация кэша**: ветка@main (до 12ч задержка jsDelivr) vs SHA-пиннинг (мгновенно, но
|
||||
нужно обновлять ссылку) — выбрать схему; компромисс: index.json на @main, он дешёвый.
|
||||
7. **Pop-out у панелей** — заявлен в доках дизайна; проверить фактическое поведение и размеры.
|
||||
8. **Формат index.json** и схема конфиг-сегмента (поля: id, title, headerImage, src, sha?, hidden, order) — зафиксировать до начала UI.
|
||||
|
||||
---
|
||||
|
||||
## 9. Источники
|
||||
|
||||
- Extensions overview / hosting / CSP: https://dev.twitch.tv/docs/extensions/
|
||||
- Building (Helper, Config Service 5 КБ, PubSub): https://dev.twitch.tv/docs/extensions/building/
|
||||
- Designing (панель 318×496, pop-out): https://dev.twitch.tv/docs/extensions/designing/
|
||||
- Life cycle (Local Test, ревью, Released): https://dev.twitch.tv/docs/extensions/life-cycle/
|
||||
- Guidelines & Policies (мобайл 1 МБ): https://dev.twitch.tv/docs/extensions/guidelines-and-policies/
|
||||
- Mobile-видимость панелей: https://help.twitch.tv/s/article/how-to-use-extensions
|
||||
- Статус платформы / deprecation Rig: https://dev.twitch.tv/docs/product-lifecycle/ и https://barrycarlyon.co.uk/wordpress/2023/04/04/the-twitch-extensions-developer-rig-is-dead/
|
||||
- CORS jsDelivr/GitHub: проверено curl-ом (ACAO: * на raw.githubusercontent.com, cdn.jsdelivr.net, pages)
|
||||
- Pop-out как элемент UI Twitch (отключить нельзя): RFC 0008 https://discuss.dev.twitch.com/t/rfc-0008-pop-out-support-for-extensions/16920
|
||||
- vite-plugin-singlefile (один вход на билд, multi-entry не поддерживается): https://github.com/richardtallent/vite-plugin-singlefile
|
||||
|
||||
---
|
||||
|
||||
## 10. Итерация 2 — принятые решения
|
||||
|
||||
Уточнения от автора: подсветка кода и формулы не нужны; localStorage не нужен;
|
||||
приватность не нужна; интерактив только — кнопки листания и ссылки; попапы/pop-out не нужны;
|
||||
нужен билдер контента (кроп картинок, валидация MD, чистка лишнего); главный приоритет —
|
||||
надёжная загрузка контента.
|
||||
|
||||
### 10.1 Билд фронтенда → два самодостаточных HTML
|
||||
|
||||
- Vite/Svelte по умолчанию НЕ дробит на мелкие чанки: код-сплиттинг возникает только из
|
||||
`import()` и нескольких entry с общими модулями. Правило: **не использовать `import()` вообще**
|
||||
(ленивость не нужна — контент лениво грузится по сети).
|
||||
- Схема: `viewer.html` и `config.html`, каждый собирается отдельным билдом через
|
||||
`vite-plugin-singlefile` → один HTML без внешних js/css (плагин официально не умеет
|
||||
multi-entry, поэтому два конфига: `vite build -c vite.viewer.config.ts` + `vite build -c vite.config.config.ts`).
|
||||
В консоли: Panel Viewer Path = `/viewer.html`, Panel Config Path = `/config.html`.
|
||||
- Размеры: вьювер без marked/DOMPurify (см. 10.2) ≈ 15–25 КБ, конфиг-вью ≈ 40–60 КБ —
|
||||
на порядок ниже мобильного бюджета 1 МБ и лимитов zip.
|
||||
- Альтернатива (проще): обычный multi-entry Vite без плагина → 2 html + 3–4 hashed-ассета.
|
||||
Тоже корректно; single-file выигрывает надёжностью (меньше файлов, меньше шансов ошибиться в путях).
|
||||
- CSP в консоли: `connect-src`/`img-src` = cdn.jsdelivr.net + raw.githubusercontent.com.
|
||||
Шрифтов и GA нет — системный стек.
|
||||
|
||||
### 10.2 Пре-рендер MD в билдере + чистка на стороне зрителя
|
||||
|
||||
Рендер Markdown происходит **на этапе сборки (в CI)**, но решение «убрать санитайзер
|
||||
из рантайма» пересмотрено (итерация 3): всё, что приходит с jsDelivr, считается
|
||||
**недоверенными данными** (репо/CDN могут быть подменены), а доверенный корень — это
|
||||
код вьювера (CDN Twitch) и конфиг-сегмент (Twitch). Чистка результата выполняется
|
||||
у зрителя, до вставки в DOM — два варианта в 10.7.
|
||||
|
||||
Пайплайн: `git push → GitHub Actions → builder → ветка published → jsDelivr`.
|
||||
|
||||
### 10.3 Билдер контента (Node-скрипт, `builder/`)
|
||||
|
||||
Стек: `unified` + `remark-parse` + `remark-gfm` (AST + валидация по белому списку),
|
||||
`gray-matter` (front-matter: title/order/hidden), `sharp` (картинки). Сериализация:
|
||||
основной вариант — компактный JSON-AST (своя функция ~50 строк поверх mdast, см. 10.7);
|
||||
запасной — `remark-rehype` + `rehype-external-links` + `rehype-stringify` → HTML
|
||||
(тогда во вьювере DOMPurify).
|
||||
|
||||
- **Валидация MD по белому списку узлов AST**: heading, paragraph, emphasis/strong,
|
||||
list/listItem, link, image, blockquote, hr, break, text. Всё прочее (html, code, table,
|
||||
footnote…) = ошибка сборки с файлом и позицией. Белый список — один конфиг-объект.
|
||||
- **Картинки (sharp)**: заголовочные — кроп 636×340 (318×170 @2x, fit:cover), webp;
|
||||
inline — max width 640, webp q≈80; метаданные стирать; битые пути = ошибка билда;
|
||||
лимит на размер исходника (напр. 10 МБ) и на суммарный вес документа.
|
||||
- **Чистка лишнего (кандидаты, расширяемо)**: сырой HTML в MD; запрещённые конструкции;
|
||||
битые относительные ссылки/картинки; нормализация уровней заголовков (в панели не выше h4);
|
||||
трим пустых строк/пробелов; слишком большие документы → warning с размером.
|
||||
- **Выход**: `published/index.json`, `published/docs/*.json` (или `*.html` в варианте A),
|
||||
`published/img/*`; для каждого файла — SHA-256 в index.json (см. 10.7). Относительные
|
||||
пути картинок работают на jsDelivr как есть (всё под одним `@sha`-префиксом) — рерайт не нужен.
|
||||
|
||||
### 10.4 Схема доставки (надёжность важнее автомагии)
|
||||
|
||||
- **Индекс читается вьювером напрямую из CDN**: `https://raw.githubusercontent.com/<u>/<r>/published/index.json`
|
||||
(TTL raw — 5 минут, файл крошечный). Каждый entry содержит абсолютные jsDelivr-URL,
|
||||
**запиннованные на SHA коммита** (`.../gh/<u>/<r>@<sha>/published/...`) — контент иммутабелен,
|
||||
нет расхождения TTL: индекс ссылается только на документы, которые уже существуют.
|
||||
- **Обновление контента полностью автоматическое**: push → CI собрал published →
|
||||
через ≤5 минут зрители видят новое. Никаких кнопок и заходов в панель.
|
||||
- **broadcaster-сегмент — только per-channel исключения** (опционально):
|
||||
`{v:1, hidden:[id], order:[id]}`; пустой сегмент = порядок из index.json.
|
||||
- Fallback: index недоступен → developer-сегмент → вшитый дефолтный документ.
|
||||
- localStorage не используется.
|
||||
|
||||
### 10.5 Pop-out и интерактив
|
||||
|
||||
- Pop-out отключить нельзя — это кнопка UI Twitch (RFC 0008), не наша. Приложение просто
|
||||
остаётся нейтральным к размеру окна.
|
||||
- Интерактив внутри: только кнопки «‹ ›» и ссылки. Ссылки `target=_blank` разрешены
|
||||
песочницей (allow-popups), top-navigation заблокирован самим Twitch — «увести» зрителя
|
||||
со страницы нельзя.
|
||||
|
||||
### 10.6 Итоговый стек (после упрощений)
|
||||
|
||||
- Приложение: `svelte@5`, `vite`, `@sveltejs/vite-plugin-svelte`, `vite-plugin-singlefile`. Всё.
|
||||
- Билдер: `unified`/`remark-parse`/`remark-gfm`, `gray-matter`, `sharp` (+rehype-стек в варианте A).
|
||||
- Убраны: marked, highlight.js, katex. DOMPurify — по варианту 10.7 (A: в рантайме ~8 КБ gzip; B: не нужен вовсе).
|
||||
|
||||
### 10.7 Санитизация на стороне зрителя (итерация 3)
|
||||
|
||||
Модель доверия: доверенный корень = билд расширения (CDN Twitch) + конфиг-сегмент
|
||||
(хранит Twitch, правит только аккаунт стримера). Всё, что приходит с jsDelivr (даже
|
||||
запиннованное на SHA) — недоверенные данные: компрометация репо/CDN/GitHub-аккаунта не
|
||||
должна приводить к исполнению чего-либо у зрителя. Важно: билдер живёт в том же репо и
|
||||
тоже подменяем — поэтому граница доверия обязана находиться в коде, который раздаёт Twitch.
|
||||
|
||||
**Вариант B — рекомендуемый: JSON-AST вместо HTML, никакого `{@html}`**
|
||||
|
||||
- билдер сериализует проверенный mdast в компактное мини-дерево:
|
||||
`["h2","Заголовок"]`, `["p","текст ",["strong","жирный"]]`, `["ul",[["li","пункт"]]]`,
|
||||
`["a","https://…","текст"]`, `["img","https://…","alt"]`, `["blockquote",[…]]`, `["hr"]`;
|
||||
- вьювер — один рекурсивный Svelte-компонент (~80–120 строк): строка = текст
|
||||
(авто-эскейп Svelte), неизвестный узел = пропустить с warning в консоль,
|
||||
URL проверяется по схеме (img: только https на cdn.jsdelivr.net / raw.githubusercontent.com;
|
||||
a: только http/https), атрибуты кроме href/src/alt/title не мапятся вообще;
|
||||
- санитайзер не нужен: в DOM физически попадают только узлы, которые отрисовал наш код.
|
||||
Zero зависимостей — это и есть «попроще»: нечего чистить.
|
||||
|
||||
**Вариант A — запасной: HTML в выдаче + DOMPurify во вьювере**
|
||||
|
||||
~8–9 КБ gzip, одна строка вызова. Tight-конфиг: `ALLOWED_TAGS` по белому списку,
|
||||
`ALLOWED_ATTR = ['href','src','alt','title']`, `ADD_ATTR: ['target']`. Стандарт индустрии,
|
||||
mXSS закрыты, поддерживается активнее всех.
|
||||
|
||||
**Дополнительный слой — SHA-256 пиннинг (напрямую закрывает «подменят файлы»)**
|
||||
|
||||
- билдер считает SHA-256 каждого файла выдачи → index.json → конфиг-сегмент;
|
||||
- вьювер: `const buf = await (await fetch(url)).arrayBuffer();`
|
||||
`const digest = await crypto.subtle.digest('SHA-256', buf);` → сравнить со значением из
|
||||
сегмента; не совпало → экран «контент изменился, обновите список в панели управления».
|
||||
~10 строк, ноль зависимостей, работает во всех браузерах (secure context).
|
||||
- согласуется с @sha-пиннингом: старый контент остаётся доступен по старому SHA, поэтому
|
||||
push без нажатия «Обновить список» ничего не ломает — зритель видит прежнюю версию.
|
||||
- картинки через `<img>` так не проверить; для них достаточно host-allowlist в рендерере;
|
||||
максимум паранойи — тот же fetch+hash+objectURL.
|
||||
|
||||
Остаточный риск (честно): компрометация Twitch-аккаунта = компрометация доверенного корня
|
||||
(атакующий правит сегмент и хеши). Это граница любой схемы без EBS.
|
||||
|
||||
### 10.8 Итерация 4 — финальные решения
|
||||
|
||||
- **SHA-256 слой отклонён**: лишний обязательный шаг («обновила файл — зайди в панель и
|
||||
нажми кнопку») перевешивает пользу. Подмена ловится схемой B + host-allowlist.
|
||||
- **Принят вариант B**: JSON-AST, рекурсивный рендерер, никакого `{@html}` и санитайзера.
|
||||
- **Доставка упрощена** (см. 10.4): index.json вьювер берёт прямо с raw@published —
|
||||
контентные пуши доезжают до зрителей сами за ≤5 минут; сегмент — только исключения.
|
||||
- Реализован скелет: два single-file билда (viewer/config), вьювер с пейджером,
|
||||
конфиг-панель (скрыть/порядок), билдер (валидация + JSON-AST + sharp), sample-контент, CI.
|
||||
Reference in New Issue
Block a user