Skip to content

About

Plug-and-play realtime + chat libraries for Flutter & Dart on Supabase (live queries with optimistic merge, presence, broadcast, offline outbox).

Resources

Stars

4 stars

Watchers

0 watching

Forks

Latest commit

Β 

History

22 Commits

Folders and files

Repository files navigation

supabase_realtime_kit

supabase_realtime_kit supabase_chat supabase_chat_widgets supabase_chat_seal supabase_chat_e2ee

Realtime, done once β€” cleanly. A monorepo of plug-and-play realtime libraries for Flutter & Dart on top of Supabase.

Build a live, optimistic, self-healing chat β€” or live cursors, dashboards, and collaborative docs β€” without re-stitching postgres_changes, broadcast, and presence by hand every single time. The core is headless and pure Dart; chat is just its flagship consumer; and end-to-end encryption is one opt-in package away.

final chat = SupabaseChat(supabaseClient);
final room = chat.room(roomId)..join();

ChatView(room: room); // πŸ‘ˆ a complete, live chat screen in Flutter

That's a real-time message list with optimistic send, typing indicators, presence, reactions, replies, media, read receipts, reconnect backfill, and an offline outbox β€” from three lines.

Demo

Two users (Alice & Bob) chatting end-to-end encrypted side by side β€” note the bottom panel showing the ciphertext the Supabase server actually stores, while each client renders the decrypted text.

Encrypted two-user chat demo

Prefer full quality? Watch the MP4.

The packages

Package pub.dev What it is Layer
supabase_realtime_kit pub Pure-Dart core. Live queries with optimistic merge, presence, broadcast, reconnect reconciliation, and a pluggable offline outbox. Foundation
supabase_chat pub Chat domain on the core: rooms, messages, typing, presence, reactions, replies, edits, media, read receipts. Domain
supabase_chat_widgets pub Optional Flutter widgets: a drop-in ChatView + building blocks (MessageBubble, MessageComposer, TypingIndicator). UI
supabase_chat_e2ee pub Opt-in E2EE β€” Signal Protocol (forward secrecy). The server stores only ciphertext. GPL-3.0 (open-source apps). Security
supabase_chat_seal pub Opt-in E2EE β€” ECDH + AES-GCM sealed box. Same verified-E2EE API, MIT licensed β†’ safe for closed-source apps (no forward secrecy). Security
example/ β€” A runnable Flutter chat app wiring it all together. β€”
e2e/ β€” Real-instance end-to-end tests against a live Supabase project. β€”

The dependency arrow only ever points down: ui, e2ee, and seal build on chat, which builds on the core. The core knows nothing about chat β€” live cursors and dashboards reuse the exact same primitives.

Two E2EE options, by license. supabase_chat_e2ee uses the Signal Protocol (forward secrecy) but is GPL-3.0 β€” distributing it forces your app open-source. supabase_chat_seal is MIT (ECDH + AES-GCM, no forward secrecy) and is safe for closed-source apps. Same verified-E2EE API (safetyNumber / markVerified / strict mode); pick by your licensing.

Why this exists

Raw Supabase realtime is powerful but low-level. Every app re-implements the same plumbing and gets the edge cases wrong:

  • πŸ” Optimistic UI β€” show a row instantly, then reconcile with the server echo without duplicates.
  • πŸ“‘ Reconnect backfill β€” catch the changes you missed while offline when the socket comes back.
  • πŸ“¦ Offline outbox β€” queue writes that fail offline and retry them on reconnect.
  • πŸ‘€ Presence + typing β€” ephemeral signals over the wire, no DB writes.

This kit does all of that once, behind a small, predictable API.

Quick start

1. Backend β€” apply the SQL migrations to your Supabase project (tables, RLS policies, realtime publication):

# 0001 = chat schema Β· 0002 = E2EE key directory (only if you use supabase_chat_e2ee)
supabase db push   # or paste supabase/migrations/*.sql into the SQL editor

2. Run the example app:

dart pub global activate melos
melos bootstrap            # or: flutter pub get  (native pub workspace)
cd example
flutter run \
  --dart-define=SUPABASE_URL=https://YOUR.supabase.co \
  --dart-define=SUPABASE_ANON_KEY=YOUR_ANON_KEY

Show me code

1 Β· The headless core β€” a live, optimistic list

Not just for chat. Any table β†’ a self-healing stream.

import 'package:supabase_realtime_kit/supabase_realtime_kit.dart';

final kit = RealtimeKit(supabaseClient);

// Initial REST load + live postgres_changes tail + optimistic merge, one stream.
final messages = kit.liveQuery<Map<String, dynamic>>(
  table: 'messages',
  fromJson: (row) => row,
  idOf: (row) => row['id'] as String,
  compare: (a, b) =>
      (a['created_at'] as String).compareTo(b['created_at'] as String),
);

messages.stream.listen((rows) => render(rows)); // re-renders on every change
await messages.start();

// Optimistic insert: shows immediately, reconciles with the echo,
// and is buffered in the outbox + retried if you're offline.
await kit.insert(
  table: 'messages',
  payload: {'body': 'hello realtime'},
  outboxId: 'client-generated-id',
);

2 Β· The chat domain β€” rooms, messages, presence

import 'package:supabase_chat/supabase_chat.dart';

final chat = SupabaseChat(supabaseClient); // user must be signed in

// 1:1 or group rooms, created/looked up idempotently.
final created = await chat.directRoom('the-other-user-id');
final room = switch (created) {
  Ok(:final value) => value,
  Err(:final error) => throw error,
};

room.messages.listen((messages) => render(messages));   // optimistic + live
room.typingUserIds.listen(showTyping);
room.presentUsers.listen(showOnline);
await room.join();

await room.send(text: 'hey πŸ‘‹');             // renders instantly, no dupes
await room.setTyping(typing: true);          // typing indicator (no DB write)
await room.react(someMessageId, 'πŸ‘');       // emoji reaction
await room.markRead();                       // read receipt + unread counts
await room.loadMore();                       // infinite-scroll history

3 Β· The UI β€” a screen in one widget

import 'package:supabase_chat_widgets/supabase_chat_widgets.dart';

Scaffold(
  appBar: AppBar(title: const Text('general')),
  body: ChatView(room: room), // joins on mount, leaves on dispose
);

Theming follows your app's ColorScheme β€” no hard-coded brand colors. Need a custom layout? Compose MessageBubble, MessageComposer, and TypingIndicator yourself.

4 Β· End-to-end encryption β€” the server sees only ciphertext πŸ”

Wrap a room and the plaintext never leaves the device. Built on the Signal Protocol (X3DH + Double Ratchet) with safety-number verification to defeat a malicious server.

import 'package:supabase_chat_e2ee/supabase_chat_e2ee.dart';

final identity = await E2eeIdentity.generate();      // once per install
final manager = E2eeManager(
  identity: identity,
  directory: SupabasePreKeyDirectory(supabaseClient),
  currentUserId: myUserId,
);
await manager.publishOwnKeys();

final secure = EncryptedChatRoom(room, manager, recipientUserIds: [peerId]);
await secure.join();

// Strict mode (default): verify the peer before the first send.
final number = await secure.safetyNumber();  // same 60 digits on both devices
await secure.markVerified();                  // after the user confirms it
await secure.send('hello, end-to-end πŸ”');     // server stores only ciphertext

See it run with zero backend:

dart run packages/supabase_chat_e2ee/example/e2ee_demo.dart
=== 3. Safety numbers (man-in-the-middle check) ===
  Alice computes: 05794 26793 94914 51015 06994 ...
  Bob computes:   05794 26793 94914 51015 06994 ...
  Identical on both sides? true  βœ…
=== 6. What the Supabase server actually stores ===
  messages.encrypted -> {"bob":{"t":3,"b":"MwgBEiEFLFeq7OT/ZMGJ..."}}
  The server sees only this blob β€” no plaintext, ever.
=== 9. Tamper check: a swapped identity key is rejected ===
  Rejected as expected: IdentityChangedException βœ…

Architecture at a glance

   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
   β”‚ supabase_chat_widgets β”‚ supabase_chat_e2ee β”‚ supabase_chat_seal  β”‚  opt-in
   β”‚   (Flutter UI)   β”‚  (Signal Β· GPL)    β”‚  (ECDH+GCM Β· MIT)   β”‚
   β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
            β”‚                   β”‚                    β”‚
        β”Œβ”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”
        β”‚                 supabase_chat                   β”‚  chat domain (Dart)
        β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                              β”‚
        β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
        β”‚             supabase_realtime_kit                β”‚  realtime core (Dart)
        β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                              β”‚
                       package:supabase

Develop

A native Dart pub workspace (Dart 3.10+) managed with Melos:

melos run format     # dart format
melos run analyze    # strict very_good_analysis lints
melos run test       # unit tests (incl. a backend-free E2EE round-trip)
melos run check      # all of the above (pre-commit gate)

The optional e2e/ package runs the chat flows against a real Supabase project; see its README for the env vars it expects.

Design principles

  • Thin over the SDK, no magic β€” predictable wrappers, not a framework.
  • Result-first β€” errors are returned (Ok / Err), not thrown across the API.
  • Pluggable, not bundled β€” bring your own outbox/key persistence; nothing platform-specific (no Keychain, no secure-storage) baked into the core.
  • RLS is the security boundary β€” the client trusts the policies shipped in the migrations.
  • Encryption is opt-in and honest β€” supabase_chat_e2ee documents exactly what it does and does not protect.

Licensing & compatibility

Four of the five packages are MIT, so the core, chat, UI, and the seal E2EE option are safe in any app β€” closed-source included. The one package to watch is supabase_chat_e2ee, which is GPL-3.0 because it links a copyleft dependency. The MIT UI package does not depend on it, so using the widgets can't pull GPL into your app:

Package License Notable deps Safe in a closed-source app?
supabase_realtime_kit MIT supabase (MIT) βœ… yes
supabase_chat MIT supabase (MIT) βœ… yes
supabase_chat_widgets MIT Flutter (BSD) β€” no crypto dep βœ… yes
supabase_chat_seal MIT cryptography (Apache-2.0), crypto (BSD-3) βœ… yes
supabase_chat_e2ee GPL-3.0 libsignal_protocol_dart (GPL-3.0) ❌ no β€” GPL forces your whole app open-source

The rule: if you want Signal-grade forward secrecy and your app is GPL/open-source, use supabase_chat_e2ee. For any closed-source or proprietary app, use supabase_chat_seal (same verified-E2EE API, MIT, no forward secrecy). Don't ship supabase_chat_e2ee unless your project can comply with GPL-3.0.

License

MIT Β© Ranbir Singh β€” for every package except supabase_chat_e2ee, which is GPL-3.0 (it links the GPL libsignal_protocol_dart; that license governs any app you distribute with it β€” see the table above).

About

Plug-and-play realtime + chat libraries for Flutter & Dart on Supabase (live queries with optimistic merge, presence, broadcast, offline outbox).

Resources

Stars

4 stars

Watchers

0 watching

Forks

Releases

Sponsor this project

Packages

Contributors

Languages