mirror of
https://github.com/supabase/agent-skills.git
synced 2026-03-27 10:09:26 +08:00
Merge pull request #21 from supabase/feature/realtime-agent-reference
feat: realtime agent reference
This commit is contained in:
@@ -62,6 +62,17 @@ Reference files are named `{prefix}-{topic}.md` (e.g., `query-missing-indexes.md
|
||||
- `references/db-security-functions.md`
|
||||
- `references/db-security-service-role.md`
|
||||
|
||||
**Realtime** (`realtime-`):
|
||||
- `references/realtime-broadcast-basics.md`
|
||||
- `references/realtime-broadcast-database.md`
|
||||
- `references/realtime-patterns-cleanup.md`
|
||||
- `references/realtime-patterns-debugging.md`
|
||||
- `references/realtime-patterns-errors.md`
|
||||
- `references/realtime-postgres-changes.md`
|
||||
- `references/realtime-presence-tracking.md`
|
||||
- `references/realtime-setup-auth.md`
|
||||
- `references/realtime-setup-channels.md`
|
||||
|
||||
---
|
||||
|
||||
*18 reference files across 11 categories*
|
||||
*27 reference files across 11 categories*
|
||||
@@ -4,7 +4,7 @@ description: Guides and best practices for working with Supabase. Covers getting
|
||||
license: MIT
|
||||
metadata:
|
||||
author: supabase
|
||||
version: "1.0.0"
|
||||
version: '1.0.0'
|
||||
organization: Supabase
|
||||
date: January 2026
|
||||
abstract: Comprehensive Supabase development guide for building applications with Supabase services. Contains guides covering Auth, Database, Storage, Edge Functions, Realtime, client libraries, CLI, and tooling. Each reference includes setup instructions, code examples, common mistakes, and integration patterns.
|
||||
@@ -33,13 +33,23 @@ Reference the appropriate resource file based on the user's needs:
|
||||
|
||||
### Database
|
||||
|
||||
| Area | Resource | When to Use |
|
||||
| ------------------ | -------------------------------- | ---------------------------------------------- |
|
||||
| RLS Security | `references/db-rls-*.md` | Row Level Security policies, common mistakes |
|
||||
| Connection Pooling | `references/db-conn-pooling.md` | Transaction vs Session mode, port 6543 vs 5432 |
|
||||
| Schema Design | `references/db-schema-*.md` | auth.users FKs, timestamps, JSONB, extensions |
|
||||
| Migrations | `references/db-migrations-*.md` | CLI workflows, idempotent patterns, db diff |
|
||||
| Performance | `references/db-perf-*.md` | Indexes (BRIN, GIN), query optimization |
|
||||
| Security | `references/db-security-*.md` | Service role key, security_definer functions |
|
||||
| Area | Resource | When to Use |
|
||||
| ------------------ | ------------------------------- | ---------------------------------------------- |
|
||||
| RLS Security | `references/db-rls-*.md` | Row Level Security policies, common mistakes |
|
||||
| Connection Pooling | `references/db-conn-pooling.md` | Transaction vs Session mode, port 6543 vs 5432 |
|
||||
| Schema Design | `references/db-schema-*.md` | auth.users FKs, timestamps, JSONB, extensions |
|
||||
| Migrations | `references/db-migrations-*.md` | CLI workflows, idempotent patterns, db diff |
|
||||
| Performance | `references/db-perf-*.md` | Indexes (BRIN, GIN), query optimization |
|
||||
| Security | `references/db-security-*.md` | Service role key, security_definer functions |
|
||||
|
||||
### Realtime
|
||||
|
||||
| Area | Resource | When to Use |
|
||||
| ---------------- | ------------------------------------ | ----------------------------------------------- |
|
||||
| Channel Setup | `references/realtime-setup-*.md` | Creating channels, naming conventions, auth |
|
||||
| Broadcast | `references/realtime-broadcast-*.md` | Client messaging, database-triggered broadcasts |
|
||||
| Presence | `references/realtime-presence-*.md` | User online status, shared state tracking |
|
||||
| Postgres Changes | `references/realtime-postgres-*.md` | Database change listeners (prefer Broadcast) |
|
||||
| Patterns | `references/realtime-patterns-*.md` | Cleanup, error handling, React integration |
|
||||
|
||||
**CLI Usage:** Always use `npx supabase` instead of `supabase` for version consistency across team members.
|
||||
|
||||
94
skills/supabase/references/realtime-broadcast-basics.md
Normal file
94
skills/supabase/references/realtime-broadcast-basics.md
Normal file
@@ -0,0 +1,94 @@
|
||||
---
|
||||
title: Send and Receive Broadcast Messages
|
||||
impact: HIGH
|
||||
impactDescription: Core pattern for real-time client-to-client messaging
|
||||
tags: realtime, broadcast, send, receive, subscribe
|
||||
---
|
||||
|
||||
## Send and Receive Broadcast Messages
|
||||
|
||||
Broadcast enables low-latency pub/sub messaging between clients. Prefer Broadcast over Postgres Changes for applications that require more concurrent connections.
|
||||
|
||||
## Subscribe to Broadcast Events
|
||||
|
||||
```javascript
|
||||
const channel = supabase.channel('room:123:messages', {
|
||||
config: { private: true },
|
||||
})
|
||||
|
||||
channel
|
||||
.on('broadcast', { event: 'message_created' }, (payload) => {
|
||||
console.log('New message:', payload.payload)
|
||||
})
|
||||
.on('broadcast', { event: '*' }, (payload) => {
|
||||
// Listen to all events on this channel
|
||||
})
|
||||
.subscribe((status) => {
|
||||
if (status === 'SUBSCRIBED') {
|
||||
console.log('Connected!')
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
## Send Messages
|
||||
|
||||
**After subscribing (WebSocket - lower latency):**
|
||||
|
||||
```javascript
|
||||
channel.send({
|
||||
type: 'broadcast',
|
||||
event: 'message_created',
|
||||
payload: { text: 'Hello!', user_id: '123' },
|
||||
})
|
||||
```
|
||||
|
||||
**Before subscribing or one-off (HTTP):**
|
||||
|
||||
```javascript
|
||||
await channel.httpSend('message_created', { text: 'Hello!' })
|
||||
```
|
||||
|
||||
## Receive Own Messages
|
||||
|
||||
By default, senders don't receive their own broadcasts.
|
||||
|
||||
**Incorrect:**
|
||||
|
||||
```javascript
|
||||
// Won't receive own messages
|
||||
const channel = supabase.channel('room:123')
|
||||
```
|
||||
|
||||
**Correct:**
|
||||
|
||||
```javascript
|
||||
// Enable self-receive when needed (e.g., optimistic UI confirmation)
|
||||
const channel = supabase.channel('room:123', {
|
||||
config: {
|
||||
broadcast: { self: true },
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
## Get Server Acknowledgment
|
||||
|
||||
```javascript
|
||||
const channel = supabase.channel('room:123', {
|
||||
config: {
|
||||
broadcast: { ack: true },
|
||||
},
|
||||
})
|
||||
|
||||
// Returns 'ok' when server confirms receipt
|
||||
const status = await channel.send({
|
||||
type: 'broadcast',
|
||||
event: 'message_created',
|
||||
payload: { text: 'Hello!' },
|
||||
})
|
||||
```
|
||||
|
||||
## Related
|
||||
|
||||
- [broadcast-database.md](broadcast-database.md)
|
||||
- [patterns-cleanup.md](patterns-cleanup.md)
|
||||
- [Docs](https://supabase.com/docs/guides/realtime/broadcast)
|
||||
95
skills/supabase/references/realtime-broadcast-database.md
Normal file
95
skills/supabase/references/realtime-broadcast-database.md
Normal file
@@ -0,0 +1,95 @@
|
||||
---
|
||||
title: Broadcast from Database Triggers
|
||||
impact: CRITICAL
|
||||
impactDescription: Scalable pattern for notifying clients of database changes
|
||||
tags: realtime, broadcast, database, triggers, realtime.send, realtime.broadcast_changes
|
||||
---
|
||||
|
||||
## Broadcast from Database Triggers
|
||||
|
||||
Use database triggers with `realtime.broadcast_changes()` instead of `postgres_changes` for better scalability. This avoids per-subscriber RLS checks.
|
||||
|
||||
## realtime.broadcast_changes()
|
||||
|
||||
Broadcasts database changes in a standard format.
|
||||
|
||||
```sql
|
||||
create or replace function room_messages_broadcast()
|
||||
returns trigger
|
||||
security definer
|
||||
language plpgsql
|
||||
as $$
|
||||
begin
|
||||
perform realtime.broadcast_changes(
|
||||
'room:' || coalesce(new.room_id, old.room_id)::text, -- topic
|
||||
tg_op, -- event (INSERT/UPDATE/DELETE)
|
||||
tg_op, -- operation
|
||||
tg_table_name, -- table
|
||||
tg_table_schema, -- schema
|
||||
new, -- new record
|
||||
old -- old record
|
||||
);
|
||||
return coalesce(new, old);
|
||||
end;
|
||||
$$;
|
||||
|
||||
create trigger messages_broadcast_trigger
|
||||
after insert or update or delete on messages
|
||||
for each row execute function room_messages_broadcast();
|
||||
```
|
||||
|
||||
**Client subscription:**
|
||||
|
||||
```javascript
|
||||
const channel = supabase
|
||||
.channel('room:123', { config: { private: true } })
|
||||
.on('broadcast', { event: 'INSERT' }, (payload) => console.log('Insert:', payload))
|
||||
.on('broadcast', { event: 'UPDATE' }, (payload) => console.log('Update:', payload))
|
||||
.on('broadcast', { event: 'DELETE' }, (payload) => console.log('Delete:', payload))
|
||||
.subscribe()
|
||||
```
|
||||
|
||||
## realtime.send()
|
||||
|
||||
Sends custom payloads without table binding.
|
||||
|
||||
```sql
|
||||
select realtime.send(
|
||||
jsonb_build_object('message', 'Custom notification'), -- payload
|
||||
'notification_sent', -- event
|
||||
'user:456:notifications', -- topic
|
||||
true -- private (true = requires auth)
|
||||
);
|
||||
```
|
||||
|
||||
## Public vs Private Mismatch
|
||||
|
||||
**Incorrect:**
|
||||
|
||||
```sql
|
||||
-- Database sends to public channel
|
||||
select realtime.send('{}', 'event', 'topic', false); -- private = false
|
||||
```
|
||||
|
||||
```javascript
|
||||
// Client expects private channel - won't receive message
|
||||
const channel = supabase.channel('topic', { config: { private: true } })
|
||||
```
|
||||
|
||||
**Correct:**
|
||||
|
||||
```sql
|
||||
-- Database sends to private channel
|
||||
select realtime.send('{}', 'event', 'topic', true); -- private = true
|
||||
```
|
||||
|
||||
```javascript
|
||||
// Client matches
|
||||
const channel = supabase.channel('topic', { config: { private: true } })
|
||||
```
|
||||
|
||||
## Related
|
||||
|
||||
- [setup-auth.md](setup-auth.md)
|
||||
- [broadcast-basics.md](broadcast-basics.md)
|
||||
- [Docs](https://supabase.com/docs/guides/realtime/broadcast)
|
||||
92
skills/supabase/references/realtime-patterns-cleanup.md
Normal file
92
skills/supabase/references/realtime-patterns-cleanup.md
Normal file
@@ -0,0 +1,92 @@
|
||||
---
|
||||
title: Clean Up Channels to Prevent Memory Leaks
|
||||
impact: CRITICAL
|
||||
impactDescription: Prevents memory leaks and connection quota exhaustion
|
||||
tags: realtime, cleanup, react, lifecycle, removeChannel
|
||||
---
|
||||
|
||||
## Clean Up Channels to Prevent Memory Leaks
|
||||
|
||||
Always remove channels when components unmount or subscriptions are no longer needed.
|
||||
|
||||
## React Pattern
|
||||
|
||||
**Incorrect:**
|
||||
|
||||
```javascript
|
||||
function ChatRoom({ roomId }) {
|
||||
useEffect(() => {
|
||||
const channel = supabase.channel(`room:${roomId}`)
|
||||
channel.on('broadcast', { event: 'message' }, handleMessage).subscribe()
|
||||
// Missing cleanup - channel persists after unmount
|
||||
}, [roomId])
|
||||
}
|
||||
```
|
||||
|
||||
**Correct:**
|
||||
|
||||
```javascript
|
||||
function ChatRoom({ roomId }) {
|
||||
const channelRef = useRef(null)
|
||||
|
||||
useEffect(() => {
|
||||
// Prevent duplicate subscriptions
|
||||
if (channelRef.current?.state === 'subscribed') return
|
||||
|
||||
const channel = supabase.channel(`room:${roomId}:messages`, {
|
||||
config: { private: true },
|
||||
})
|
||||
channelRef.current = channel
|
||||
|
||||
channel
|
||||
.on('broadcast', { event: 'message_created' }, handleMessage)
|
||||
.subscribe()
|
||||
|
||||
return () => {
|
||||
if (channelRef.current) {
|
||||
supabase.removeChannel(channelRef.current)
|
||||
channelRef.current = null
|
||||
}
|
||||
}
|
||||
}, [roomId])
|
||||
}
|
||||
```
|
||||
|
||||
## Channel Lifecycle Methods
|
||||
|
||||
```javascript
|
||||
// Remove specific channel
|
||||
supabase.removeChannel(channel)
|
||||
|
||||
// Remove all channels (e.g., on logout)
|
||||
supabase.removeAllChannels()
|
||||
|
||||
// Get active channels
|
||||
const channels = supabase.getChannels()
|
||||
```
|
||||
|
||||
## Check Channel State Before Subscribing
|
||||
|
||||
```javascript
|
||||
// Prevent duplicate subscriptions
|
||||
if (channel.state === 'subscribed') {
|
||||
return
|
||||
}
|
||||
channel.subscribe()
|
||||
```
|
||||
|
||||
## Connection Quotas
|
||||
|
||||
| Plan | Max Connections | Channels per Connection |
|
||||
|------|-----------------|------------------------|
|
||||
| Free | 200 | 100 |
|
||||
| Pro | 500 | 100 |
|
||||
| Team | 10,000 | 100 |
|
||||
|
||||
Leaked channels count against quotas even when inactive.
|
||||
For Pay as you go customers you can edit these limits on [Realtime Settings](https://supabase.com/dashboard/project/_/realtime/settings)
|
||||
## Related
|
||||
|
||||
- [patterns-errors.md](patterns-errors.md)
|
||||
- [setup-channels.md](setup-channels.md)
|
||||
- [Docs](https://supabase.com/docs/guides/realtime/quotas)
|
||||
78
skills/supabase/references/realtime-patterns-debugging.md
Normal file
78
skills/supabase/references/realtime-patterns-debugging.md
Normal file
@@ -0,0 +1,78 @@
|
||||
---
|
||||
title: Debug Realtime Connections
|
||||
impact: MEDIUM
|
||||
impactDescription: Enables visibility into connection and message flow issues
|
||||
tags: realtime, debugging, logging, troubleshooting
|
||||
---
|
||||
|
||||
## Debug Realtime Connections
|
||||
|
||||
Use logging to diagnose connection issues, message flow, and performance problems.
|
||||
|
||||
## Client-Side Logging
|
||||
|
||||
**Incorrect:**
|
||||
|
||||
```javascript
|
||||
// No logging - no visibility into issues
|
||||
const supabase = createClient(url, key)
|
||||
```
|
||||
|
||||
**Correct:**
|
||||
|
||||
Enable client-side logging with a custom logger function:
|
||||
|
||||
```javascript
|
||||
const supabase = createClient(url, key, {
|
||||
realtime: {
|
||||
logger: (kind, msg, data) => {
|
||||
console.log(`[${kind}] ${msg}`, data)
|
||||
},
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
Log message types:
|
||||
- `push` - Messages sent to server
|
||||
- `receive` - Messages received from server
|
||||
- `transport` - Connection events (connect, disconnect, heartbeat)
|
||||
- `error` - Error events
|
||||
- `worker` - Web Worker events
|
||||
|
||||
## Server-Side Log Level
|
||||
|
||||
Configure Realtime server log verbosity via client params:
|
||||
|
||||
```javascript
|
||||
const supabase = createClient(url, key, {
|
||||
realtime: {
|
||||
params: {
|
||||
log_level: 'info', // 'debug' | 'info' | 'warn' | 'error'
|
||||
},
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
This affects the verbosity of logs from the Realtime server, not client-side logs.
|
||||
|
||||
## Filtering Logs for Debugging
|
||||
|
||||
Filter logs to focus on specific events:
|
||||
|
||||
```javascript
|
||||
const supabase = createClient(url, key, {
|
||||
realtime: {
|
||||
logger: (kind, msg, data) => {
|
||||
// Only log push/receive for subscription debugging
|
||||
if (kind === 'push' || kind === 'receive') {
|
||||
console.log(`[${kind}] ${msg}`, data)
|
||||
}
|
||||
},
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
## Related
|
||||
|
||||
- [patterns-errors.md](patterns-errors.md)
|
||||
- [Docs](https://supabase.com/docs/guides/troubleshooting/realtime-debugging-with-logger)
|
||||
97
skills/supabase/references/realtime-patterns-errors.md
Normal file
97
skills/supabase/references/realtime-patterns-errors.md
Normal file
@@ -0,0 +1,97 @@
|
||||
---
|
||||
title: Handle Realtime Errors and Connection Issues
|
||||
impact: HIGH
|
||||
impactDescription: Enables graceful handling of connection failures
|
||||
tags: realtime, errors, subscribe, status, reconnection
|
||||
---
|
||||
|
||||
## Handle Realtime Errors and Connection Issues
|
||||
|
||||
Handle subscription status and errors to provide reliable user experiences.
|
||||
|
||||
## Subscription Status Handling
|
||||
|
||||
**Incorrect:**
|
||||
|
||||
```javascript
|
||||
// Ignoring subscription status - no visibility into connection issues
|
||||
channel.subscribe()
|
||||
```
|
||||
|
||||
**Correct:**
|
||||
|
||||
```javascript
|
||||
channel.subscribe((status, err) => {
|
||||
switch (status) {
|
||||
case 'SUBSCRIBED':
|
||||
console.log('Connected!')
|
||||
break
|
||||
case 'CHANNEL_ERROR':
|
||||
console.error('Channel error:', err)
|
||||
// Client retries automatically
|
||||
break
|
||||
case 'TIMED_OUT':
|
||||
console.error('Connection timed out')
|
||||
break
|
||||
case 'CLOSED':
|
||||
console.log('Channel closed')
|
||||
break
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
## Common Error Codes
|
||||
|
||||
| Error | Cause | Solution |
|
||||
|-------|-------|----------|
|
||||
| `too_many_connections` | Connection limit exceeded | Clean up unused channels, upgrade plan |
|
||||
| `too_many_joins` | Channel join rate exceeded | Reduce join frequency |
|
||||
| `ConnectionRateLimitReached` | Max connections reached | Upgrade plan |
|
||||
| `DatabaseLackOfConnections` | No available DB connections | Increase compute size |
|
||||
| `TenantNotFound` | Invalid project reference | Verify project URL |
|
||||
|
||||
## Automatic Reconnection
|
||||
|
||||
Supabase handles reconnection automatically with exponential backoff. No manual re-subscribe is needed.
|
||||
|
||||
## Client-Side Logging
|
||||
|
||||
Enable client-side logging to debug connection issues:
|
||||
|
||||
```javascript
|
||||
const supabase = createClient(url, key, {
|
||||
realtime: {
|
||||
logger: (kind, msg, data) => {
|
||||
console.log(`[${kind}] ${msg}`, data)
|
||||
},
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
Log message types include `push`, `receive`, `transport`, `error`, and `worker`.
|
||||
|
||||
## Silent Disconnections in Background
|
||||
|
||||
WebSocket connections can disconnect when apps are backgrounded (mobile, inactive tabs). Supabase reconnects automatically. Re-track presence after reconnection if needed:
|
||||
|
||||
```javascript
|
||||
channel.subscribe((status) => {
|
||||
if (status === 'SUBSCRIBED') {
|
||||
// Re-track presence after reconnection
|
||||
channel.track({ user_id: userId, online_at: new Date().toISOString() })
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
## Authorization Errors
|
||||
|
||||
Private channel authorization fails when:
|
||||
- User not authenticated
|
||||
- Missing RLS policies on `realtime.messages`
|
||||
- Token expired
|
||||
|
||||
## Related
|
||||
|
||||
- [patterns-cleanup.md](patterns-cleanup.md)
|
||||
- [setup-auth.md](setup-auth.md)
|
||||
- [Docs](https://supabase.com/docs/guides/realtime/troubleshooting)
|
||||
99
skills/supabase/references/realtime-postgres-changes.md
Normal file
99
skills/supabase/references/realtime-postgres-changes.md
Normal file
@@ -0,0 +1,99 @@
|
||||
---
|
||||
title: Listen to Database Changes with Postgres Changes
|
||||
impact: MEDIUM
|
||||
impactDescription: Simple database change listeners with scaling limitations
|
||||
tags: realtime, postgres_changes, database, subscribe, publication
|
||||
---
|
||||
|
||||
## Listen to Database Changes with Postgres Changes
|
||||
|
||||
Postgres Changes streams database changes via logical replication. Note: **Broadcast is recommended for applications that demand higher scalability**.
|
||||
|
||||
## When to Use Postgres Changes
|
||||
|
||||
- Quick prototyping and development
|
||||
- Low user counts (< 100 concurrent subscribers per table)
|
||||
- When simplicity is more important than scale
|
||||
|
||||
## Basic Setup
|
||||
|
||||
**1. Add table to publication:**
|
||||
|
||||
```sql
|
||||
alter publication supabase_realtime add table messages;
|
||||
```
|
||||
|
||||
**2. Subscribe to changes:**
|
||||
|
||||
```javascript
|
||||
const channel = supabase
|
||||
.channel('db-changes')
|
||||
.on(
|
||||
'postgres_changes',
|
||||
{
|
||||
event: 'INSERT', // 'INSERT' | 'UPDATE' | 'DELETE' | '*'
|
||||
schema: 'public',
|
||||
table: 'messages',
|
||||
},
|
||||
(payload) => console.log('New row:', payload.new)
|
||||
)
|
||||
.subscribe()
|
||||
```
|
||||
|
||||
## Filter Syntax
|
||||
|
||||
```javascript
|
||||
.on('postgres_changes', {
|
||||
event: '*',
|
||||
schema: 'public',
|
||||
table: 'messages',
|
||||
filter: 'room_id=eq.123', // Only changes where room_id = 123
|
||||
}, callback)
|
||||
```
|
||||
|
||||
| Filter | Example |
|
||||
|--------|---------|
|
||||
| `eq` | `id=eq.1` |
|
||||
| `neq` | `status=neq.deleted` |
|
||||
| `lt`, `lte` | `age=lt.65` |
|
||||
| `gt`, `gte` | `quantity=gt.10` |
|
||||
| `in` | `name=in.(red,blue,yellow)` (max 100 values) |
|
||||
|
||||
## Receive Old Records on UPDATE/DELETE
|
||||
|
||||
By default, only `new` records are sent.
|
||||
|
||||
**Incorrect:**
|
||||
|
||||
```sql
|
||||
-- Only new record available in payload
|
||||
alter publication supabase_realtime add table messages;
|
||||
```
|
||||
|
||||
**Correct:**
|
||||
|
||||
```sql
|
||||
-- Enable old record in payload
|
||||
alter table messages replica identity full;
|
||||
alter publication supabase_realtime add table messages;
|
||||
```
|
||||
|
||||
## Scaling Limitation
|
||||
|
||||
Each change triggers RLS checks for every subscriber:
|
||||
|
||||
```text
|
||||
100 subscribers = 100 database reads per change
|
||||
```
|
||||
|
||||
For high-traffic tables, migrate to [broadcast-database.md](broadcast-database.md).
|
||||
|
||||
## DELETE Events Not Filterable
|
||||
|
||||
Filters don't work on DELETE events due to how Postgres logical replication works.
|
||||
|
||||
## Related
|
||||
|
||||
- [broadcast-database.md](broadcast-database.md)
|
||||
- [patterns-cleanup.md](patterns-cleanup.md)
|
||||
- [Docs](https://supabase.com/docs/guides/realtime/postgres-changes)
|
||||
87
skills/supabase/references/realtime-presence-tracking.md
Normal file
87
skills/supabase/references/realtime-presence-tracking.md
Normal file
@@ -0,0 +1,87 @@
|
||||
---
|
||||
title: Track User Presence and Online Status
|
||||
impact: MEDIUM
|
||||
impactDescription: Enables features like online indicators and typing status
|
||||
tags: realtime, presence, track, online, state
|
||||
---
|
||||
|
||||
## Track User Presence and Online Status
|
||||
|
||||
Presence synchronizes shared state between users. Use sparingly due to computational overhead.
|
||||
|
||||
## Track Presence
|
||||
|
||||
```javascript
|
||||
const channel = supabase.channel('room:123', {
|
||||
config: { private: true },
|
||||
})
|
||||
|
||||
channel
|
||||
.on('presence', { event: 'sync' }, () => {
|
||||
const state = channel.presenceState()
|
||||
console.log('Online users:', Object.keys(state))
|
||||
})
|
||||
.on('presence', { event: 'join' }, ({ key, newPresences }) => {
|
||||
console.log('User joined:', key, newPresences)
|
||||
})
|
||||
.on('presence', { event: 'leave' }, ({ key, leftPresences }) => {
|
||||
console.log('User left:', key, leftPresences)
|
||||
})
|
||||
.subscribe(async (status) => {
|
||||
if (status === 'SUBSCRIBED') {
|
||||
await channel.track({
|
||||
user_id: 'user-123',
|
||||
online_at: new Date().toISOString(),
|
||||
})
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
## Get Current State
|
||||
|
||||
```javascript
|
||||
const state = channel.presenceState()
|
||||
// Returns: { "key1": [{ user_id: "123" }], "key2": [{ user_id: "456" }] }
|
||||
```
|
||||
|
||||
## Stop Tracking
|
||||
|
||||
```javascript
|
||||
await channel.untrack()
|
||||
```
|
||||
|
||||
## Custom Presence Key
|
||||
|
||||
By default, presence uses a UUIDv1 key. Override for user-specific tracking.
|
||||
|
||||
**Incorrect:**
|
||||
|
||||
```javascript
|
||||
// Each browser tab gets separate presence entry
|
||||
const channel = supabase.channel('room:123')
|
||||
```
|
||||
|
||||
**Correct:**
|
||||
|
||||
```javascript
|
||||
// Same user shows once across tabs
|
||||
const channel = supabase.channel('room:123', {
|
||||
config: {
|
||||
presence: { key: `user:${userId}` },
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
## Quotas
|
||||
|
||||
| Plan | Presence Messages/Second |
|
||||
|------|-------------------------|
|
||||
| Free | 20 |
|
||||
| Pro | 50 |
|
||||
| Team/Enterprise | 1,000 |
|
||||
For Pay as you go customers you can edit these limits on [Realtime Settings](https://supabase.com/dashboard/project/_/realtime/settings)
|
||||
## Related
|
||||
|
||||
- [setup-channels.md](setup-channels.md)
|
||||
- [patterns-cleanup.md](patterns-cleanup.md)
|
||||
- [Docs](https://supabase.com/docs/guides/realtime/presence)
|
||||
82
skills/supabase/references/realtime-setup-auth.md
Normal file
82
skills/supabase/references/realtime-setup-auth.md
Normal file
@@ -0,0 +1,82 @@
|
||||
---
|
||||
title: Configure Private Channels with Authentication
|
||||
impact: CRITICAL
|
||||
impactDescription: Prevents unauthorized access to real-time messages
|
||||
tags: realtime, auth, private, rls, security, setAuth
|
||||
---
|
||||
|
||||
## Configure Private Channels with Authentication
|
||||
|
||||
Always use private channels in production. Public channels allow any client to subscribe.
|
||||
|
||||
## Enable Private Channels
|
||||
|
||||
**Incorrect:**
|
||||
|
||||
```javascript
|
||||
// Public channel - anyone can subscribe
|
||||
const channel = supabase.channel('room:123:messages')
|
||||
```
|
||||
|
||||
**Correct:**
|
||||
|
||||
```javascript
|
||||
// Private channel requires authentication
|
||||
const channel = supabase.channel('room:123:messages', {
|
||||
config: { private: true },
|
||||
})
|
||||
```
|
||||
|
||||
## RLS Policies on realtime.messages
|
||||
|
||||
Private channels require RLS policies on the `realtime.messages` table.
|
||||
|
||||
**Read access (subscribe to channel):**
|
||||
|
||||
```sql
|
||||
create policy "authenticated_users_can_receive"
|
||||
on realtime.messages for select
|
||||
to authenticated
|
||||
using (true);
|
||||
```
|
||||
|
||||
**Write access (send to channel):**
|
||||
|
||||
```sql
|
||||
create policy "authenticated_users_can_send"
|
||||
on realtime.messages for insert
|
||||
to authenticated
|
||||
with check (true);
|
||||
```
|
||||
|
||||
**Topic-specific access:**
|
||||
|
||||
```sql
|
||||
-- Only room members can receive messages
|
||||
create policy "room_members_can_read"
|
||||
on realtime.messages for select
|
||||
to authenticated
|
||||
using (
|
||||
extension in ('broadcast', 'presence')
|
||||
and exists (
|
||||
select 1 from room_members
|
||||
where user_id = (select auth.uid())
|
||||
and room_id = split_part(realtime.topic(), ':', 2)::uuid
|
||||
)
|
||||
);
|
||||
```
|
||||
|
||||
## Index RLS Policy Columns
|
||||
|
||||
Missing indexes slow channel joins significantly.
|
||||
|
||||
```sql
|
||||
create index idx_room_members_user_room
|
||||
on room_members(user_id, room_id);
|
||||
```
|
||||
|
||||
## Related
|
||||
|
||||
- [setup-channels.md](setup-channels.md)
|
||||
- [broadcast-database.md](broadcast-database.md)
|
||||
- [Docs](https://supabase.com/docs/guides/realtime/authorization)
|
||||
70
skills/supabase/references/realtime-setup-channels.md
Normal file
70
skills/supabase/references/realtime-setup-channels.md
Normal file
@@ -0,0 +1,70 @@
|
||||
---
|
||||
title: Create and Configure Realtime Channels
|
||||
impact: HIGH
|
||||
impactDescription: Proper channel setup enables reliable real-time communication
|
||||
tags: realtime, channels, configuration, topics, naming
|
||||
---
|
||||
|
||||
## Create and Configure Realtime Channels
|
||||
|
||||
Channels are rooms where clients communicate. Use consistent naming and appropriate configuration.
|
||||
|
||||
## Topic Naming Convention
|
||||
|
||||
Use `scope:entity:id` format for predictable, filterable topics.
|
||||
|
||||
**Incorrect:**
|
||||
|
||||
```javascript
|
||||
// Generic names make filtering impossible
|
||||
const channel = supabase.channel('messages')
|
||||
const channel = supabase.channel('room1')
|
||||
```
|
||||
|
||||
**Correct:**
|
||||
|
||||
```javascript
|
||||
// Structured naming enables topic-based RLS policies
|
||||
const channel = supabase.channel('room:123:messages')
|
||||
const channel = supabase.channel('user:456:notifications')
|
||||
const channel = supabase.channel('game:789:moves')
|
||||
```
|
||||
|
||||
## Channel Configuration Options
|
||||
|
||||
```javascript
|
||||
const channel = supabase.channel('room:123:messages', {
|
||||
config: {
|
||||
private: true, // Require authentication (recommended)
|
||||
broadcast: {
|
||||
self: true, // Receive own messages
|
||||
ack: true, // Get server acknowledgment
|
||||
},
|
||||
presence: {
|
||||
key: 'user-session-id', // Custom presence key (default: UUIDv1)
|
||||
},
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
## Event Naming
|
||||
|
||||
Use snake_case for event names.
|
||||
|
||||
**Incorrect:**
|
||||
|
||||
```javascript
|
||||
channel.send({ type: 'broadcast', event: 'newMessage', payload: {} })
|
||||
```
|
||||
|
||||
**Correct:**
|
||||
|
||||
```javascript
|
||||
channel.send({ type: 'broadcast', event: 'message_created', payload: {} })
|
||||
channel.send({ type: 'broadcast', event: 'user_joined', payload: {} })
|
||||
```
|
||||
|
||||
## Related
|
||||
|
||||
- [setup-auth.md](setup-auth.md)
|
||||
- [Docs](https://supabase.com/docs/guides/realtime/concepts)
|
||||
Reference in New Issue
Block a user