Перейти к содержимому

Общие права за столом

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

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

  • Игрок подключил свой лист — остальные участники группы получают доступ к нему на чтение. Мастер группы читает любой подключённый лист.
  • Права не хранятся списком: доступ вычисляется из членства. Человек вышел из группы — доступ пропал сам, вычищать нечего.
  • Членство создаёт сам игрок, своим кликом, в своей сессии. Молча подключить чужой лист нельзя — ни вам, ни мастеру.
  • Группа без активности со временем уходит в мягкий архив и возвращается сама, как только владелец переподключается по сохранённому коду.

Разовый шаг, после которого всё остальное — код. Для регистрации требуется два момента:

  1. Домен вашего приложения — тот, с которого будут открываться страницы с iframe. Именно рабочий домен: localhost в список не добавляется.
  2. Желаемый слаг — короткое имя вашего проекта латиницей в kebab-case (a-z, цифры, дефис), например roll-30.

Что вы получите:

  1. Ваш домен попадёт в CSP frame-ancestors — без этого браузер просто не покажет наши страницы у вас на сайте.
  2. Слаг попадёт в реестр клиентов. Вы будете использовать его для работы с группами. По нему, например, мастер сможет увидеть все свои группы, созданные в контексте вашего приложения.

Куда писать: @shakusky в Telegram, shakusky.lss в Discord или в сообщения сообщества VK.

Все страницы групп открываются с параметром ?client=<ваш слаг>. Без него страница честно скажет, что клиент не представился, и ничего не создаст.

Событие Направление Payload
lss:group-selected страница группы → хост { groupId, code, name }
lss:group-code хост → лист игрока { code: string | null }
lss:group-leave хост → лист игрока { code: string }
lss:group-status лист игрока → хост { connected: boolean }

Все четыре помечены как экспериментальные: они типизированы и проведены сквозь лист, но их API на стороне хоста может измениться до стабилизации. Подписка — через source.onEvent.

Встройте страницу управления группой куда-нибудь в свой интерфейс мастера. Страница живёт у нас, требует того же sandbox, что и лист, и сама попросит войти в аккаунт Long Story Short:

import { createBridgeSheetSource, SHEET_IFRAME_SANDBOX } from '@longstoryshort/vtt-sdk';
const iframe = document.createElement('iframe');
iframe.src = 'https://longstoryshort.app/iframe/group/?client=my-table';
iframe.setAttribute('sandbox', SHEET_IFRAME_SANDBOX);
gmPanel.appendChild(iframe);
const groupPage = createBridgeSheetSource({
iframe,
allowedOrigins: ['https://longstoryshort.app'],
});
groupPage.onEvent((event) => {
if (event.type === 'lss:group-selected') {
const { groupId, code, name } = event.payload;
myVTT.room.saveLssGroup({ groupId, code, name }); // ← ваше хранилище
}
});

Событие приходит один раз на группу — повторно на перерисовках оно не шлётся, так что обработчик может писать в своё хранилище без защиты от дублей.

На всех последующих загрузках открывайте ту же страницу с сохранённым кодом:

https://longstoryshort.app/iframe/group/?client=my-table&code=<сохранённый код>

Это проверка статуса, а не выбор группы: страница ответит только тому аккаунту, который эту группу создал, и снова пришлёт lss:group-selected. Заодно она снимает архив — вернувшийся владелец однозначно означает «группа живая», отдельной кнопки для этого не нужно.

Если код мёртв (группу удалили) или мастер вошёл не под тем аккаунтом, страница скажет об этом и предложит создать новую группу. Особой обработки на вашей стороне не требуется.

Код группы доставляется в лист конкретного игрока — в тот самый iframe, который вы уже встроили для него по обычному сценарию:

const sheet = createBridgeSheetSource({ iframe: playerSheetIframe, allowedOrigins: [...] });
const delivered = sheet.send({
type: 'lss:group-code',
payload: { code: myVTT.room.lssGroup.code },
});
if (!delivered) {
myVTT.notification.show('Откройте панель с листом, чтобы присоединиться к группе', 'warning');
}

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

{ code: null } снимает ранее сделанное предложение — например, если стол потерял свою группу. Это не отключение: персонаж, который уже вступил, остаётся в группе. Убрать его — следующий шаг.

Игрок ушёл из-за стола или мастер его выгнал — стол отправляет в тот же лист:

sheet.send({ type: 'lss:group-leave', payload: { code: myVTT.room.lssGroup.code } });

Персонаж выходит из группы, и доступ к листу у остальных участников пропадает сам. Подтверждения у игрока не спрашивают: в отличие от вступления, здесь доступ только отбирается. В ответ придёт lss:group-status с connected: false.

Две границы, о которых стоит знать:

  • Код обязателен, и он должен быть тем самым. Лист выполняет отключение только для группы, которую предложил этот же стол. Так стол может отменить собственное приглашение — и ничего больше: до чужих групп, в которых состоит персонаж, он не дотянется.
  • Работает, пока лист открыт у вас. Отключение выполняет сессия самого игрока, поэтому убрать того, кто закрыл лист, стол не может — это остаётся задачей владельца группы.

Если нужен список «подключён / не подключён», вы рисуете его сами, по одному событию на игрока:

sheet.onEvent((event) => {
if (event.type === 'lss:group-status') {
myVTT.roster.setConnected(playerId, event.payload.connected);
}
});

lss:group-status — событие состояния: подписавшись позже, вы сразу получите последнее известное значение, а не будете ждать следующего изменения.

Отдельная страница на случай, если мастер потерял сохранённый код или вы хотите дать явный выбор:

https://longstoryshort.app/iframe/groups/?client=my-table

Она показывает только группы этого приложения и только те, которыми мастер владеет; выбор шлёт то же lss:group-selected.

  • Код — пароль на вход, а не ключ управления. Зная код, посторонний может попробовать войти в группу, но не управлять ею: ?code= на странице группы отвечает только её владельцу.
  • Группы разных приложений не смешиваются: список фильтруется по вашему слагу.
  • Ваш стол не хранит прав. Всё, что у вас появляется, — groupId, code и то, что вы сами решили нарисовать по lss:group-status.