a04ecf7a19
- Voice messages: MediaRecorder → encrypted attachment, custom waveform player via OfflineAudioContext, 60s limit + live mic-level meter - Offline message queue: localStorage outbox, exponential backoff retries, optimistic pending bubble with retry/discard - Delivery indicator: message_deliveries table + RLS (reciprocal receipts), ✓ / ✓✓ / ✓✓-blue tick states, group-aware (all members must ack) - Per-participant volume slider in calls via right-click tile menu, persisted to localStorage, applied to attached audio elements - Group call scaling: grid up to 12 tiles with pagination, active-speaker auto-promotion in fullscreen - Push notifications scaffolding: service worker, VAPID subscription registration, notify-push edge function skeleton - Backup recovery code: 24-char base32 code (~120 bits entropy) as alternative decrypt path, restore UI with mode toggle - Admin panel: conversations list, audit log (admin_audit_log table + admin_log_action RPC), audit entry on user flag toggle - Search v2: sender filter, attachment-only toggle, date range - Reactions pop animation (scale 0.4→1.15→1 on count change) - Message list windowing (150 default, expand via IntersectionObserver) - Stub cleanup: removed dead ScreenshareStub from CallParticipantTile Fixes: - Focus-triggered flicker: dropped window.focus listeners in three spots, throttled visibilitychange/online wake-refreshes to 30s, keep existing data visible during background re-syncs (no more spinner on every click) - Voice attachment audio element collapsed to 0px on peer side — now forces 280px min-width on bubble Migrations (push required): 20260421000001_message_deliveries.sql 20260421000002_admin_audit_log.sql Server TODO: VAPID keys + notify-push edge function deploy
55 lines
2.2 KiB
SQL
55 lines
2.2 KiB
SQL
-- ============================================================================
|
|
-- Delivery receipts — "✓✓" tick in WhatsApp/Telegram parlance: the recipient's
|
|
-- device has fetched the message from the server, regardless of whether they
|
|
-- have actually opened the conversation yet. `message_reads` already covers
|
|
-- the "read" step; this table fills the gap between "sent" and "read".
|
|
--
|
|
-- A row exists iff the recipient user has acknowledged receiving the message
|
|
-- on at least one device. Writes are triggered client-side after successful
|
|
-- message fetch/decrypt. Select policy mirrors read receipts (opt-out via
|
|
-- profiles.show_read_receipts on BOTH ends).
|
|
-- ============================================================================
|
|
create table public.message_deliveries (
|
|
message_id uuid not null references public.messages(id) on delete cascade,
|
|
user_id uuid not null references auth.users(id) on delete cascade,
|
|
delivered_at timestamptz not null default now(),
|
|
primary key (message_id, user_id)
|
|
);
|
|
|
|
alter table public.message_deliveries enable row level security;
|
|
|
|
create policy deliveries_select_reciprocal on public.message_deliveries
|
|
for select to authenticated
|
|
using (
|
|
user_id = auth.uid()
|
|
or (
|
|
coalesce(
|
|
(select show_read_receipts from public.profiles where profiles.user_id = auth.uid()),
|
|
true
|
|
)
|
|
and coalesce(
|
|
(select show_read_receipts from public.profiles where profiles.user_id = message_deliveries.user_id),
|
|
true
|
|
)
|
|
and exists (
|
|
select 1 from public.messages m
|
|
where m.id = message_id and public.is_conversation_member(m.conversation_id)
|
|
)
|
|
)
|
|
);
|
|
|
|
create policy deliveries_insert_own on public.message_deliveries
|
|
for insert to authenticated
|
|
with check (
|
|
user_id = auth.uid()
|
|
and exists (
|
|
select 1 from public.messages m
|
|
where m.id = message_id and public.is_conversation_member(m.conversation_id)
|
|
)
|
|
);
|
|
|
|
create index message_deliveries_message_id_idx on public.message_deliveries (message_id);
|
|
create index message_deliveries_user_id_idx on public.message_deliveries (user_id);
|
|
|
|
alter publication supabase_realtime add table public.message_deliveries;
|