Этап 5: история — экспорт и импорт .bare, устройства, место, пагинация
Экспорт: ключ архива из секрета аккаунта через HKDF, заголовок ровно 65 байт (magic, версия, соль, 32 сырых байта отпечатка владельца, iv) и он же целиком AAD шифротекста. Ника владельца в файле нет. Импорт сверяет отпечаток до расшифровки, сливает идемпотентно по id, а записи peers берёт только для ников, которых в локальном TOFU ещё нет: архивом доверие к ключу не перебить. Настройки: устройства с датой и пометкой «это устройство», «занято N МБ», кнопка «экспортировать» в подтверждении выхода и в подтверждении входа под другим ником — долг этапа 1 и обещание ADR-029 закрыты. Лента: страницы по 50 с подгрузкой вверх без прыжка прокрутки; новая страница вставляется, а не пересобирает ленту. ADR-050: импорт не перезаписывает лежащую запись — у своей есть состояние отправки, которого в архиве нет. ADR-054: архив — недоверенный ввод. Ревью собрало архивы с ts вне диапазона Date, мусорным lastId, ником с bidi-переопределением и roomId с обходом пути: каждый из них навсегда ломал ленту или счётчик. Теперь форма ника, roomId, id, ts и автора проверяется, а lastId из файла не читается вовсе. ADR-051, 052, 053: тексты и кнопки, устройства и место, страницы ленты без виртуализации. Приёмка: формат сверен побайтно на модулях, скачанных с боевого сервера, — смещения заголовка, отпечаток сырыми байтами, новые соль и iv на каждый экспорт, подмена любого байта заголовка ломает расшифровку, чужой секрет не открывает, семь видов битых файлов отвергнуты. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015DbCjVfTFq4ZFG8juD45YJ
This commit is contained in:
@@ -62,6 +62,8 @@ const ctx = {
|
||||
},
|
||||
errorText,
|
||||
ensureConfig,
|
||||
devices,
|
||||
removeDevice,
|
||||
signUp,
|
||||
signIn,
|
||||
changePassword,
|
||||
@@ -226,6 +228,34 @@ async function storedNick() {
|
||||
}
|
||||
}
|
||||
|
||||
// --- устройства ---------------------------------------------------------
|
||||
|
||||
// devices — устройства аккаунта для настроек (docs/protocol.md,
|
||||
// «Устройства»). Своё сервер помечает по сессии; заодно сверяем
|
||||
// с устройством этой вкладки: сессия привязывается к устройству
|
||||
// в POST /api/devices, и до него current не проставлен (ADR-017).
|
||||
async function devices() {
|
||||
const list = await api.devices();
|
||||
const mine = sync.deviceId();
|
||||
if (!Array.isArray(list)) {
|
||||
return [];
|
||||
}
|
||||
return list
|
||||
.filter((item) => item !== null && typeof item === "object" && typeof item.id === "string")
|
||||
.map((item) => ({
|
||||
id: item.id,
|
||||
createdAt: item.createdAt,
|
||||
current: item.current === true || (mine !== null && item.id === mine),
|
||||
}));
|
||||
}
|
||||
|
||||
// removeDevice — «удалить» в настройках: очередь, подписка и сессии
|
||||
// устройства уходят вместе с ним. Своё устройство сюда не приходит —
|
||||
// его отцепляет «выйти» (ADR-052).
|
||||
function removeDevice(id) {
|
||||
return api.removeDevice(id);
|
||||
}
|
||||
|
||||
// --- аккаунт -----------------------------------------------------------
|
||||
|
||||
// derive — вывод ключей по числу итераций, пришедшему от сервера. Границы
|
||||
|
||||
Reference in New Issue
Block a user