feat: backend баннеров
This commit is contained in:
@@ -0,0 +1,154 @@
|
||||
# Баннеры (informer) в web-версии MAX
|
||||
|
||||
Баннеры вроде «Публичные каналы — для всех» внутри кода называются **informer**.
|
||||
Они **полностью приходят с сервера** по WebSocket запросом с опкодом `302`.
|
||||
В JS-бандлах текста баннеров нет — локально хранятся только счётчики показов.
|
||||
|
||||
Исходники: `dump/web.max.ru/_app/immutable/chunks/BWjA24QQ.js` (логика),
|
||||
`_app/immutable/nodes/9.Dqm5zlvE.js` (рендер тайла + аналитика).
|
||||
|
||||
---
|
||||
|
||||
## Как это работает
|
||||
|
||||
1. В общем sync-ответе сессии сервер выставляет бит `1` в bitmask-поле `updates`:
|
||||
```js
|
||||
e.updates && new Pu(e.updates).get(1) && await J.banners.resync()
|
||||
```
|
||||
2. Клиент шлёт запрос с опкодом **302**:
|
||||
```js
|
||||
Ah = Y(async ({bannersSync:e}, {send:t}) => await t(302, {bannersSync:e}))
|
||||
// resync(): let e = await Ah({bannersSync:0}); await this.upsert(e);
|
||||
```
|
||||
3. Сервер отвечает `{showTime, updateTime, banners:[…]}`.
|
||||
4. Какой баннер показать — считается на клиенте (`activeBanner`): фильтр `canShow`,
|
||||
затем выбор с максимальным `priority`.
|
||||
5. Весь механизм гейтится серверным конфигом `informer-enabled`.
|
||||
6. Состояние показов хранится в `localStorage['__oneme_informer']`:
|
||||
```js
|
||||
informerState = { lastShowedBannerId: null, banners: { /* id -> {showCounter, closedTime} */ } }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Пример ответа сервера
|
||||
|
||||
```json
|
||||
{
|
||||
"ver": 10,
|
||||
"cmd": 1,
|
||||
"seq": 14,
|
||||
"opcode": 302,
|
||||
"payload": {
|
||||
"showTime": 86400000,
|
||||
"updateTime": 1784123975588,
|
||||
"banners": [
|
||||
{
|
||||
"id": "public_channels_cid_test",
|
||||
"title": "Публичные каналы — для всех",
|
||||
"settings": 1,
|
||||
"description": "Привлекайте новую аудиторию",
|
||||
"priority": 16,
|
||||
"repeat": 1,
|
||||
"rerun": 5400000000000000,
|
||||
"animojiId": 1446,
|
||||
"url": "https://max.ru/max_news/AZ9kk1KhJAo",
|
||||
"type": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Конверт (обёртка WS-сообщения)
|
||||
|
||||
| Поле | Значение | На что влияет |
|
||||
|---|---|---|
|
||||
| `ver` | 10 | Версия протокола WebSocket. |
|
||||
| `cmd` | 1 | Тип пакета. `1` = ответ на запрос (response), не server-push. |
|
||||
| `seq` | 14 | Номер запроса. Клиент послал `302 {bannersSync:0}` под этим seq — сервер отвечает тем же seq для матчинга. |
|
||||
| `opcode` | 302 | Код операции = синхронизация баннеров/informer. |
|
||||
| `payload` | {…} | Полезная нагрузка, попадает в `banners.upsert(e)`. |
|
||||
|
||||
## payload — верхний уровень
|
||||
|
||||
**`showTime`: 86400000** (24 ч в мс)
|
||||
Сколько времени баннер считается «свежим» после первого показа.
|
||||
```js
|
||||
isShowTimeOver = ka.now - this.showAt > this.showTime
|
||||
```
|
||||
Прошло больше `showTime` с момента первого показа → `canShow` = false, баннер пропадает.
|
||||
|
||||
**`updateTime`: 1784123975588** (unix-мс)
|
||||
Версия набора баннеров. Новые данные применяются только если пришло свежее:
|
||||
```js
|
||||
e.banners && e.updateTime > this.informer.updateTime && (…apply…)
|
||||
```
|
||||
|
||||
## Объект баннера
|
||||
|
||||
**`id`: `"public_channels_cid_test"`**
|
||||
Уникальный ключ. По нему хранятся `showCounter` и `closedTime` в `localStorage['__oneme_informer']`
|
||||
и запоминается `lastShowedBannerId` (чтобы не показать один баннер подряд дважды).
|
||||
Суффикс `_test` = тестовый баннер.
|
||||
|
||||
**`title`: «Публичные каналы — для всех»** — верхняя (цветная) строка тайла.
|
||||
|
||||
**`description`: «Привлекайте новую аудиторию»** — вторая строка. Оба текста только с сервера.
|
||||
|
||||
**`settings`: 1** — битовая маска флагов отображения (`new Pu(settings)`). Биты:
|
||||
- `1` = TEXT_ANIMATION — анимация текста
|
||||
- `2` = HIDE_CLOSE_BUTTON — скрыть крестик
|
||||
- `4` = HIDE_ON_CLICK — закрывать по клику
|
||||
- `8` = ICON_THEME_COLOR — красить иконку в цвет темы
|
||||
|
||||
Здесь `1` → только анимация текста; крестик «×» есть, клик ведёт по ссылке.
|
||||
|
||||
**`priority`: 16**
|
||||
Приоритет при нескольких баннерах — показывается один с максимальным:
|
||||
```js
|
||||
e.reduce((a,b) => b.priority > a.priority ? b : a)
|
||||
```
|
||||
При одном баннере роли не играет.
|
||||
|
||||
**`repeat`: 1**
|
||||
Максимум показов:
|
||||
```js
|
||||
hasRemainingShows = showCounter===0 ? true : showCounter < repeat
|
||||
```
|
||||
`1` → показать ровно один раз.
|
||||
|
||||
**`rerun`: 5400000000000000**
|
||||
Кулдаун (мс) перед повторным показом после закрытия крестиком:
|
||||
```js
|
||||
isCloseCooldownOver = closedAt ? (rerun>0 ? Date.now()-closedAt > rerun : false) : true
|
||||
```
|
||||
Значение огромное (~171 000 лет) → «закрыл — больше не увидишь».
|
||||
|
||||
**`animojiId`: 1446**
|
||||
ID аниможи-иконки слева в тайле:
|
||||
```js
|
||||
J.assets.animojisCache.getLazy(bu.resolveId(this.$.animojiId))
|
||||
```
|
||||
|
||||
**`url`: `https://max.ru/max_news/AZ9kk1KhJAo`**
|
||||
Куда ведёт клик (вместе с `type:1`).
|
||||
|
||||
**`type`: 1** — тип баннера:
|
||||
- `0` TYPE_TEXT — просто текст
|
||||
- `1` TYPE_LINK — кликабельный, открывает `url` ← этот случай
|
||||
- `2` TYPE_UPDATE — баннер обновления приложения
|
||||
|
||||
---
|
||||
|
||||
## Итог по этому баннеру
|
||||
|
||||
Тестовый кликабельный баннер про публичные каналы: показывается один раз (`repeat:1`),
|
||||
с анимацией текста (`settings:1`), «живёт» сутки после показа (`showTime`), по клику
|
||||
открывает `max.ru/max_news/...`; если закрыть крестиком — не возвращается практически
|
||||
никогда (`rerun`). Приоритет 16 нужен только при конкуренции нескольких баннеров.
|
||||
|
||||
Убрать/подменить баннер локально можно, перехватив ответ на опкод `302`
|
||||
либо отключив серверный флаг `informer-enabled`.
|
||||
Reference in New Issue
Block a user