fix(chat): cross-tab lock реплея офлайн-очереди + идемпотентность мессенджера (conv_messages)
Клиентская часть гонки (вариант B): Web Locks API (navigator.locks) — кросс-вкладочный мьютекс iistwin-offline-sync-replay на replay-проход в useOfflineSync: первая вкладка под локом реплеит и удаляет записи, остальные видят пустую очередь. Fallback для сред без Web Locks — старое поведение (дубли гасит серверная идемпотентность). Race-тест: без locks → 2 POST, с locks → 1 POST для одной записи очереди. Мессенджер (conv_messages) — та же защита, что у task_messages: - conversation_messages.client_message_id + partial unique (миграция 0087); - POST /api/messenger/conversations/:id/messages: повтор с тем же clientMessageId → существующее сообщение без insert и без SSE/уведомлений, гонка insert'ов ловится по 23505; - useChatController шлёт crypto.randomUUID(), attachment-реплей мессенджера тоже использует id записи очереди.
This commit is contained in:
10
migrations/0087_conv_message_idempotency.sql
Normal file
10
migrations/0087_conv_message_idempotency.sql
Normal file
@@ -0,0 +1,10 @@
|
||||
-- Идемпотентность сообщений мессенджера (conversation_messages): клиентский ключ.
|
||||
-- Та же защита, что у task_messages (миграция 0086): multi-tab replay
|
||||
-- офлайн-очереди, ретраи после сетевых сбоев, двойные клики.
|
||||
-- Partial unique index: NULL-значения (старые сообщения, боты) не конфликтуют.
|
||||
|
||||
ALTER TABLE conversation_messages ADD COLUMN IF NOT EXISTS client_message_id varchar(64);
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS conv_messages_client_message_id_unique
|
||||
ON conversation_messages (client_message_id)
|
||||
WHERE client_message_id IS NOT NULL;
|
||||
Reference in New Issue
Block a user