mirror of
https://github.com/supabase/agent-skills.git
synced 2026-03-27 10:09:26 +08:00
feat: realtime agent references
This commit is contained in:
@@ -24,10 +24,15 @@ supabase/
|
||||
| Priority | Category | Impact | Prefix |
|
||||
|----------|----------|--------|--------|
|
||||
| 1 | Row Level Security | CRITICAL | `rls-` |
|
||||
| 1 | Channel Setup | HIGH | `setup-` |
|
||||
| 2 | Connection Pooling | CRITICAL | `conn-` |
|
||||
| 2 | Broadcast Messaging | CRITICAL | `broadcast-` |
|
||||
| 3 | Schema Design | HIGH | `schema-` |
|
||||
| 3 | Presence Tracking | MEDIUM | `presence-` |
|
||||
| 4 | Migrations | HIGH | `migrations-` |
|
||||
| 4 | Postgres Changes | MEDIUM | `postgres-` |
|
||||
| 5 | Performance | CRITICAL | `perf-` |
|
||||
| 5 | Implementation Patterns | CRITICAL | `patterns-` |
|
||||
| 6 | Security | CRITICAL | `security-` |
|
||||
|
||||
Reference files are named `{prefix}-{topic}.md` (e.g., `query-missing-indexes.md`).
|
||||
@@ -64,6 +69,24 @@ Reference files are named `{prefix}-{topic}.md` (e.g., `query-missing-indexes.md
|
||||
- `references/db/security-functions.md`
|
||||
- `references/db/security-service-role.md`
|
||||
|
||||
**Broadcast Messaging** (`broadcast-`):
|
||||
- `references/realtime/broadcast-basics.md`
|
||||
- `references/realtime/broadcast-database.md`
|
||||
|
||||
**Implementation Patterns** (`patterns-`):
|
||||
- `references/realtime/patterns-cleanup.md`
|
||||
- `references/realtime/patterns-errors.md`
|
||||
|
||||
**Postgres Changes** (`postgres-`):
|
||||
- `references/realtime/postgres-changes.md`
|
||||
|
||||
**Presence Tracking** (`presence-`):
|
||||
- `references/realtime/presence-tracking.md`
|
||||
|
||||
**Channel Setup** (`setup-`):
|
||||
- `references/realtime/setup-auth.md`
|
||||
- `references/realtime/setup-channels.md`
|
||||
|
||||
---
|
||||
|
||||
*18 reference files across 6 categories*
|
||||
*26 reference files across 11 categories*
|
||||
@@ -71,9 +71,14 @@ Reference the appropriate resource file based on the user's needs:
|
||||
|
||||
### Realtime
|
||||
|
||||
| Area | Resource | When to Use |
|
||||
| -------- | ------------------------ | -------------------------------------------- |
|
||||
| Realtime | `references/realtime.md` | Real-time subscriptions, presence, broadcast |
|
||||
| Area | Resource | When to Use |
|
||||
| ---------------- | ----------------------------------- | --------------------------------------------------- |
|
||||
| Realtime | `references/realtime.md` | Real-time subscriptions, presence, broadcast |
|
||||
| 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 |
|
||||
|
||||
### Client Libraries & CLI
|
||||
|
||||
|
||||
31
skills/supabase/references/realtime/_sections.md
Normal file
31
skills/supabase/references/realtime/_sections.md
Normal file
@@ -0,0 +1,31 @@
|
||||
# Section Definitions
|
||||
|
||||
Reference files are grouped by prefix. Claude loads specific files based on user
|
||||
queries.
|
||||
|
||||
---
|
||||
|
||||
## 1. Channel Setup (setup)
|
||||
|
||||
**Impact:** HIGH
|
||||
**Description:** Channel creation, naming conventions, configuration options, and authentication setup for private channels.
|
||||
|
||||
## 2. Broadcast Messaging (broadcast)
|
||||
|
||||
**Impact:** CRITICAL
|
||||
**Description:** Sending and receiving real-time messages between clients, database-triggered broadcasts using `realtime.broadcast_changes()` and `realtime.send()`.
|
||||
|
||||
## 3. Presence Tracking (presence)
|
||||
|
||||
**Impact:** MEDIUM
|
||||
**Description:** Tracking user online status, shared state synchronization, and presence lifecycle management.
|
||||
|
||||
## 4. Postgres Changes (postgres)
|
||||
|
||||
**Impact:** MEDIUM
|
||||
**Description:** Database change listeners via logical replication. Note: Broadcast is recommended for new applications due to better scalability.
|
||||
|
||||
## 5. Implementation Patterns (patterns)
|
||||
|
||||
**Impact:** CRITICAL
|
||||
**Description:** Channel cleanup, React integration patterns, error handling, and connection management best practices.
|
||||
98
skills/supabase/references/realtime/broadcast-basics.md
Normal file
98
skills/supabase/references/realtime/broadcast-basics.md
Normal file
@@ -0,0 +1,98 @@
|
||||
---
|
||||
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 production applications.
|
||||
|
||||
## 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
|
||||
const { error } = await channel.send({
|
||||
type: 'broadcast',
|
||||
event: 'message_created',
|
||||
payload: { 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)
|
||||
96
skills/supabase/references/realtime/broadcast-database.md
Normal file
96
skills/supabase/references/realtime/broadcast-database.md
Normal file
@@ -0,0 +1,96 @@
|
||||
---
|
||||
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
|
||||
await supabase.realtime.setAuth()
|
||||
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)
|
||||
94
skills/supabase/references/realtime/patterns-cleanup.md
Normal file
94
skills/supabase/references/realtime/patterns-cleanup.md
Normal file
@@ -0,0 +1,94 @@
|
||||
---
|
||||
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
|
||||
|
||||
supabase.realtime.setAuth()
|
||||
|
||||
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.
|
||||
|
||||
## Related
|
||||
|
||||
- [patterns-errors.md](patterns-errors.md)
|
||||
- [setup-channels.md](setup-channels.md)
|
||||
- [Docs](https://supabase.com/docs/guides/realtime/quotas)
|
||||
98
skills/supabase/references/realtime/patterns-errors.md
Normal file
98
skills/supabase/references/realtime/patterns-errors.md
Normal file
@@ -0,0 +1,98 @@
|
||||
---
|
||||
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 with exponential backoff. Configure timing:
|
||||
|
||||
```javascript
|
||||
const supabase = createClient(url, key, {
|
||||
realtime: {
|
||||
params: {
|
||||
log_level: 'info', // 'debug' | 'info' | 'warn' | 'error'
|
||||
},
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
## Silent Disconnections in Background
|
||||
|
||||
WebSocket connections can disconnect when apps are backgrounded (mobile, inactive tabs).
|
||||
|
||||
**Solution:** Monitor connection state and re-subscribe when needed:
|
||||
|
||||
```javascript
|
||||
channel.subscribe((status) => {
|
||||
if (status === 'SUBSCRIBED') {
|
||||
// Re-track presence if needed
|
||||
channel.track({ user_id: userId, online_at: new Date().toISOString() })
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
## Authorization Errors
|
||||
|
||||
Private channel authorization fails when:
|
||||
- User not authenticated (`setAuth()` not called)
|
||||
- Missing RLS policies on `realtime.messages`
|
||||
- Token expired (refresh before expiry)
|
||||
|
||||
```javascript
|
||||
// Refresh auth before token expires
|
||||
await supabase.realtime.setAuth('fresh-jwt-token')
|
||||
```
|
||||
|
||||
## 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 new applications** due to better 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 |
|
||||
|
||||
## Related
|
||||
|
||||
- [setup-channels.md](setup-channels.md)
|
||||
- [patterns-cleanup.md](patterns-cleanup.md)
|
||||
- [Docs](https://supabase.com/docs/guides/realtime/presence)
|
||||
83
skills/supabase/references/realtime/setup-auth.md
Normal file
83
skills/supabase/references/realtime/setup-auth.md
Normal file
@@ -0,0 +1,83 @@
|
||||
---
|
||||
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
|
||||
await supabase.realtime.setAuth() // Must call before subscribing
|
||||
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