mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 17:35:10 +03:00
Merge pull request #15104 from supabase/docs/realtime-quickstarts
docs: add steps to Realtime quickstarts
This commit is contained in:
11 files changed
+730
-586
No files matched your search
@@ -808,10 +808,6 @@ export const realtime: NavMenuConstant = {
|
||||
name: 'Concepts',
|
||||
url: '/guides/realtime/concepts',
|
||||
},
|
||||
{
|
||||
name: 'Quickstart',
|
||||
url: '/guides/realtime/quickstart',
|
||||
},
|
||||
{
|
||||
name: 'Features',
|
||||
url: undefined,
|
||||
|
||||
@@ -141,7 +141,7 @@ You can query the route in your browser, by appending the `anon` key as a query
|
||||
|
||||
### Client libraries
|
||||
|
||||
We provide a numerous [Client Libraries](https://github.com/supabase/supabase#client-libraries).
|
||||
We provide a number of [Client Libraries](https://github.com/supabase/supabase#client-libraries).
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
|
||||
@@ -19,7 +19,6 @@ All clients can connect to a channel and take advantage of the built-in features
|
||||
|
||||
## See Also
|
||||
|
||||
- [Quickstart](/docs/guides/realtime/quickstart)
|
||||
- [Realtime: Multiplayer Edition](https://supabase.com/blog/supabase-realtime-multiplayer-general-availability) blog post
|
||||
|
||||
export const Page = ({ children }) => <Layout meta={meta} children={children} />
|
||||
|
||||
@@ -1,66 +1,202 @@
|
||||
import Layout from '~/layouts/DefaultGuideLayout'
|
||||
import StepHikeCompact from '~/components/StepHikeCompact'
|
||||
|
||||
export const meta = {
|
||||
id: 'broadcast',
|
||||
title: 'Broadcast',
|
||||
description: "Getting started with Realtime's Broadcast feature",
|
||||
subtitle: "Get up and running with Realtime's Broadcast feature",
|
||||
breadcrumb: 'Realtime Broadcast Quickstart',
|
||||
}
|
||||
|
||||
Broadcast follows the [publish-subscribe pattern](https://en.wikipedia.org/wiki/Publish%E2%80%93subscribe_pattern) where a client publishes messages to a channel with a unique identifier. For example, a user could send a message to a channel with id `room-1`.
|
||||
Realtime Broadcast follows the [publish-subscribe pattern](https://en.wikipedia.org/wiki/Publish%E2%80%93subscribe_pattern) where a client publishes messages to a channel based on a unique topic. For example, a user could send a message to a channel with topic `room-1`.
|
||||
|
||||
Other clients can elect to receive the message in real-time by subscribing to the channel with id `room-1`. If these clients are online and subscribed then they will receive the message.
|
||||
Other clients can receive the message in real-time by subscribing to the channel with topic `room-1`. These clients can continue to receive messages as long as they continue to be online and subscribed to the same channel topic.
|
||||
|
||||
Broadcast works by connecting your client to the nearest Realtime server, which will communicate with other servers to relay messages to other clients.
|
||||
An example use-case is sharing a user's cursor position with other clients in an online tool or game.
|
||||
|
||||
A common use-case is sharing a user's cursor position with other clients in an online game.
|
||||
## Quick start
|
||||
|
||||
## Listen to Messages
|
||||
Let's explore how to implement Realtime Broadcast so you can integrate it into your use case.
|
||||
|
||||
You can get started with Broadcast by creating a client and listening to a channel's messages:
|
||||
<StepHikeCompact>
|
||||
|
||||
<StepHikeCompact.Step step={1}>
|
||||
|
||||
<StepHikeCompact.Details title="Install the client">
|
||||
|
||||
Install the Supabase JavaScript client.
|
||||
|
||||
</StepHikeCompact.Details>
|
||||
|
||||
<StepHikeCompact.Code>
|
||||
|
||||
```bash
|
||||
npm install @supabase/supabase-js
|
||||
```
|
||||
|
||||
</StepHikeCompact.Code>
|
||||
|
||||
</StepHikeCompact.Step>
|
||||
|
||||
<StepHikeCompact.Step step={2}>
|
||||
|
||||
<StepHikeCompact.Details title="Create the first client">
|
||||
|
||||
This client will be used to listen for messages.
|
||||
|
||||
Go to your Supabase project's [API Settings](https://supabase.com/dashboard/project/_/settings/api) and grab the `URL` and `anon` public API key.
|
||||
|
||||
</StepHikeCompact.Details>
|
||||
|
||||
<StepHikeCompact.Code>
|
||||
|
||||
```js
|
||||
import {
|
||||
createClient
|
||||
} from '@supabase/supabase-js'
|
||||
|
||||
const clientA = createClient(
|
||||
'https://<project>.supabase.co',
|
||||
'<your-anon-key>'
|
||||
)
|
||||
```
|
||||
|
||||
</StepHikeCompact.Code>
|
||||
|
||||
</StepHikeCompact.Step>
|
||||
|
||||
<StepHikeCompact.Step step={3}>
|
||||
|
||||
<StepHikeCompact.Details title="Create a channel">
|
||||
|
||||
A channel's topic can be anything except for `'realtime'`.
|
||||
|
||||
</StepHikeCompact.Details>
|
||||
|
||||
<StepHikeCompact.Code>
|
||||
|
||||
```js
|
||||
const channelA = clientA.channel('room-1')
|
||||
```
|
||||
|
||||
</StepHikeCompact.Code>
|
||||
|
||||
</StepHikeCompact.Step>
|
||||
|
||||
<StepHikeCompact.Step step={4}>
|
||||
|
||||
<StepHikeCompact.Details title="Listen for messages">
|
||||
|
||||
Specify the Broadcast event you want the `on` handler to listen for. This event name can be anything you want. We'll send a broadcast message with this event name later on.
|
||||
|
||||
</StepHikeCompact.Details>
|
||||
|
||||
<StepHikeCompact.Code>
|
||||
|
||||
```js
|
||||
channelA
|
||||
.on(
|
||||
'broadcast',
|
||||
{ event: 'test' },
|
||||
(payload) => console.log(payload)
|
||||
)
|
||||
.subscribe()
|
||||
```
|
||||
|
||||
</StepHikeCompact.Code>
|
||||
|
||||
</StepHikeCompact.Step>
|
||||
|
||||
<StepHikeCompact.Step step={5}>
|
||||
|
||||
<StepHikeCompact.Details title="Create the second client">
|
||||
|
||||
This client will be used to send a message.
|
||||
|
||||
</StepHikeCompact.Details>
|
||||
|
||||
<StepHikeCompact.Code>
|
||||
|
||||
```js
|
||||
const clientB = createClient(
|
||||
'https://<project>.supabase.co',
|
||||
'<your-anon-key>'
|
||||
)
|
||||
```
|
||||
|
||||
</StepHikeCompact.Code>
|
||||
|
||||
</StepHikeCompact.Step>
|
||||
|
||||
<StepHikeCompact.Step step={6}>
|
||||
|
||||
<StepHikeCompact.Details title="Create another channel">
|
||||
|
||||
This channel's topic must match `channelA`'s.
|
||||
|
||||
</StepHikeCompact.Details>
|
||||
|
||||
<StepHikeCompact.Code>
|
||||
|
||||
```js
|
||||
const channelB = clientB.channel('room-1')
|
||||
```
|
||||
|
||||
</StepHikeCompact.Code>
|
||||
|
||||
</StepHikeCompact.Step>
|
||||
|
||||
<StepHikeCompact.Step step={7}>
|
||||
|
||||
<StepHikeCompact.Details title="Send message">
|
||||
|
||||
Subscribe to channel and send a message.
|
||||
|
||||
The payload's `event` must match channelA's `event` in the `on` handler.
|
||||
|
||||
</StepHikeCompact.Details>
|
||||
|
||||
<StepHikeCompact.Code>
|
||||
|
||||
```js
|
||||
channelB.subscribe((status) => {
|
||||
if (status === 'SUBSCRIBED') {
|
||||
channelB.send({
|
||||
type: 'broadcast',
|
||||
event: 'test',
|
||||
payload: {
|
||||
message: 'hello, world'
|
||||
},
|
||||
})
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
</StepHikeCompact.Code>
|
||||
|
||||
</StepHikeCompact.Step>
|
||||
|
||||
<StepHikeCompact.Step step={8}>
|
||||
|
||||
<StepHikeCompact.Details title="First client receives message">
|
||||
|
||||
`clientA` receives the message `clientB` sent.
|
||||
|
||||
</StepHikeCompact.Details>
|
||||
|
||||
</StepHikeCompact.Step>
|
||||
|
||||
</StepHikeCompact>
|
||||
|
||||
## Broadcast options
|
||||
|
||||
There are additional Broadcast functionality that you can enable when creating a channel.
|
||||
|
||||
### Self-send messages
|
||||
|
||||
You can have a client broadcast a message and then receive the same message by setting Broadcast's `self` config to `true`. Without this, broadcast messages are only sent to other clients.
|
||||
|
||||
```js
|
||||
const { createClient } = require('@supabase/supabase-js')
|
||||
|
||||
const supabase = createClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY)
|
||||
|
||||
const channel = supabase.channel('test')
|
||||
|
||||
channel.on('broadcast', { event: 'supa' }, (payload) => console.log(payload)).subscribe()
|
||||
```
|
||||
|
||||
## Send Messages
|
||||
|
||||
You can create another client and send messages to other clients:
|
||||
|
||||
```js
|
||||
const { createClient } = require('@supabase/supabase-js')
|
||||
|
||||
const supabase = createClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY)
|
||||
|
||||
const channel = supabase.channel('test')
|
||||
|
||||
channel.subscribe((status) => {
|
||||
if (status === 'SUBSCRIBED') {
|
||||
channel.send({
|
||||
type: 'broadcast',
|
||||
event: 'supa',
|
||||
payload: { org: 'supabase' },
|
||||
})
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
In order for clients to successfully send and receive mesages to one another, they must both specify the same `event`.
|
||||
|
||||
We recommend that the client has successfully subscribed to the channel prior to sending messages.
|
||||
|
||||
### Self-Send Messages
|
||||
|
||||
You can also choose for a client to receive messages that it sent:
|
||||
|
||||
```js
|
||||
// Supabase client setup
|
||||
const channel = supabase.channel('test', {
|
||||
const channelC = clientC.channel('room-2', {
|
||||
config: {
|
||||
broadcast: {
|
||||
self: true,
|
||||
@@ -68,69 +204,72 @@ const channel = supabase.channel('test', {
|
||||
},
|
||||
})
|
||||
|
||||
channel
|
||||
.on('broadcast', { event: 'supa' }, (payload) => console.log(payload))
|
||||
.subscribe((status) => {
|
||||
if (status === 'SUBSCRIBED') {
|
||||
channel.send({
|
||||
type: 'broadcast',
|
||||
event: 'supa',
|
||||
payload: { org: 'supabase' },
|
||||
})
|
||||
}
|
||||
})
|
||||
channelC.on('broadcast', { event: 'test-my-messages' }, (payload) => console.log(payload))
|
||||
|
||||
channelC.subscribe((status) => {
|
||||
if (status === 'SUBSCRIBED') {
|
||||
channelC.send({
|
||||
type: 'broadcast',
|
||||
event: 'test-my-messages',
|
||||
payload: { message: 'talking to myself' },
|
||||
})
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### Acknowledge Messages
|
||||
### Acknowledge messages
|
||||
|
||||
You can ensure that Realtime's servers received your message by:
|
||||
You can confirm that Realtime received your message by setting Broadcast's `ack` config to `true`.
|
||||
|
||||
```js
|
||||
// Supabase client setup
|
||||
|
||||
const channel = supabase.channel('receipt', {
|
||||
const channelD = clientD.channel('room-3', {
|
||||
config: {
|
||||
broadcast: { ack: true },
|
||||
broadcast: {
|
||||
ack: true,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
channel.subscribe(async (status) => {
|
||||
channelD.subscribe(async (status) => {
|
||||
if (status === 'SUBSCRIBED') {
|
||||
const resp = await channel.send({
|
||||
const resp = await channelD.send({
|
||||
type: 'broadcast',
|
||||
event: 'latency',
|
||||
event: 'acknowledge',
|
||||
payload: {},
|
||||
})
|
||||
console.log(resp)
|
||||
|
||||
console.log('resp', resp)
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
If `ack` is not set to `true`, Realtime servers will not acknowledge that it received the sent message and `send` promise resolves immediately.
|
||||
Use this to guarantee that the server has received the message before resolving `channelD.send`'s promise. If the `ack` config is not set to `true` when creating the channel, the promise returned by `channelD.send` will resolve immediately.
|
||||
|
||||
## Client-Side Rate Limit
|
||||
## Client-side rate limit
|
||||
|
||||
There is a default client-side rate limit that enables you to send 10 messages per second, or one message every 100 milliseconds. You can customize this when creating the client:
|
||||
By default the client will rate limit itself at 10 messages per second (1 message every 100 milliseconds). You can customize this when creating the client:
|
||||
|
||||
```js
|
||||
const { createClient } = require('@supabase/supabase-js')
|
||||
import { createClient } from '@supabase/supabase-js'
|
||||
|
||||
const supabase = createClient(
|
||||
process.env.SUPABASE_URL,
|
||||
process.env.SUPABASE_KEY,
|
||||
{
|
||||
realtime: {
|
||||
params: {
|
||||
eventsPerSecond: 20
|
||||
}
|
||||
}
|
||||
}
|
||||
const clientE = createClient('https://<project>.supabase.co', '<your-anon-key>', {
|
||||
realtime: {
|
||||
params: {
|
||||
eventsPerSecond: 20,
|
||||
},
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
By setting `eventsPerSecond` to 20, you can send one message every 50 milliseconds on a per client basis.
|
||||
|
||||
Learn more by visiting the [Quotas](/docs/guides/realtime/quotas) section.
|
||||
|
||||
## More Realtime Quickstarts
|
||||
|
||||
- [Presence Quickstart](/docs/guides/realtime/presence)
|
||||
- [Postgres Changes Quickstart](/docs/guides/realtime/postgres-changes)
|
||||
|
||||
export const Page = ({ children }) => <Layout meta={meta} children={children} />
|
||||
|
||||
export default Page
|
||||
@@ -18,11 +18,11 @@ Supabase Realtime lets you to build real-time applications with collaborative/mu
|
||||
When you initialize your Supabase Realtime client, you define a `topic` that uniquely references a channel. Everyone connected to the same Channel `topic` receives the same messages.
|
||||
|
||||
```js
|
||||
const { createClient } = require('@supabase/supabase-js')
|
||||
import { createClient } from '@supabase/supabase-js'
|
||||
|
||||
const supabase = createClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY)
|
||||
const client = createClient('https://<project>.supabase.co', '<your-anon-key>')
|
||||
|
||||
const channel = supabase.channel('my-topic') // set your topic here
|
||||
const channel = client.channel('my-topic') // set your topic here
|
||||
```
|
||||
|
||||
Clients can bi-directionally send and receive messages over a Channel. The Realtime backend can also push messages to all clients connected to the same Channel.
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import Layout from '~/layouts/DefaultGuideLayout'
|
||||
import StepHikeCompact from '~/components/StepHikeCompact'
|
||||
|
||||
export const meta = {
|
||||
id: 'postgres-changes',
|
||||
title: 'Postgres Changes',
|
||||
description: "Getting started with Realtime's Postgres Changes feature",
|
||||
subtitle: "Get up and running with Realtime's Postgres Changes feature",
|
||||
breadcrumb: 'Realtime Postgres Changes Quickstart',
|
||||
}
|
||||
|
||||
Realtime's Postgres Changes feature listens for database changes and sends them to clients. Clients are required to subscribe with a JWT dictating which changes they are allowed to receive based on the database's [Row Level Security](/docs/guides/auth/row-level-security).
|
||||
@@ -12,108 +13,249 @@ Anyone with access to a valid JWT signed with the project's JWT secret is able t
|
||||
|
||||
Clients can choose to receive `INSERT`, `UPDATE`, `DELETE`, or `*` (all) changes for all changes in a schema, a table in a schema, or a column's value in a table. Your clients should only listen to tables in the `public` schema and you must first enable the tables you want your clients to listen to.
|
||||
|
||||
Postgres Changes works out of the box for tables in the `public` schema. You can listen to tables in your private schemas by granting table `SELECT` permissions to the database role found in your access token. You can run a query similar to the following:
|
||||
## Quick start
|
||||
|
||||
```sql
|
||||
grant
|
||||
select
|
||||
on "private_schema"."table" to authenticated;
|
||||
```
|
||||
Let's explore how to implement Realtime Postgres Changes so you can integrate it into your use case.
|
||||
|
||||
<Admonition type="caution">
|
||||
We strongly encourage you to enable RLS and create policies for tables in private schemas.
|
||||
Otherwise, any role you grant access to will have unfettered read access to the table.
|
||||
</Admonition>
|
||||
<StepHikeCompact>
|
||||
|
||||
## Replication Setup
|
||||
<StepHikeCompact.Step step={1}>
|
||||
<StepHikeCompact.Details title="Set up a Supabase project with a 'todos' table">
|
||||
|
||||
You can do this in the [Replication](https://supabase.com/dashboard/project/_/database/replication) section in the Dashboard or with the [SQL editor](https://supabase.com/dashboard/project/_/sql):
|
||||
[Create a new project](https://app.supabase.com) in the Supabase Dashboard.
|
||||
|
||||
```sql
|
||||
begin;
|
||||
After your project is ready, create a table in your Supabase database. You can do this with either the Table interface or the [SQL Editor](https://app.supabase.com/project/_/sql).
|
||||
|
||||
-- remove the supabase_realtime publication
|
||||
drop
|
||||
publication if exists supabase_realtime;
|
||||
</StepHikeCompact.Details>
|
||||
|
||||
-- re-create the supabase_realtime publication with no tables
|
||||
create publication supabase_realtime;
|
||||
<StepHikeCompact.Code>
|
||||
|
||||
commit;
|
||||
<Tabs
|
||||
scrollable
|
||||
size="small"
|
||||
type="underlined"
|
||||
defaultActiveId="sql"
|
||||
>
|
||||
<TabPanel id="sql" label="SQL">
|
||||
|
||||
-- add a table to the publication
|
||||
alter
|
||||
publication supabase_realtime add table messages;
|
||||
```
|
||||
```sql
|
||||
-- Create a table called "todos"
|
||||
-- with a column to store tasks.
|
||||
create table todos (
|
||||
id serial primary key,
|
||||
task text
|
||||
);
|
||||
```
|
||||
|
||||
### Full `old` Record
|
||||
</TabPanel>
|
||||
<TabPanel id="dashboard" label="Dashboard">
|
||||
|
||||
By default, only `new` record changes are sent but if you want to receive the `old` record (previous values) whenever you `UPDATE` or `DELETE` a record,
|
||||
you can set the `replica identity` of your table to `full`:
|
||||
<video width="99%" muted playsInline controls={true}>
|
||||
<source
|
||||
src="https://xguihxuzqibwxjnimxev.supabase.co/storage/v1/object/public/videos/docs/api/api-create-table-sm.mp4"
|
||||
type="video/mp4"
|
||||
/>
|
||||
</video>
|
||||
|
||||
```sql
|
||||
alter table
|
||||
messages replica identity full;
|
||||
```
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
<Admonition type="caution">
|
||||
</StepHikeCompact.Code>
|
||||
|
||||
RLS policies are not applied to `DELETE` statements. When RLS is enabled and `replica identity` is set to `full` on a table, the `old` record contains only the primary key(s).
|
||||
</StepHikeCompact.Step>
|
||||
|
||||
</Admonition>
|
||||
<StepHikeCompact.Step step={2}>
|
||||
|
||||
## Schema Changes
|
||||
<StepHikeCompact.Details title="Allow anonymous access">
|
||||
|
||||
To listen to all changes in the `public` schema:
|
||||
In this example we'll turn on [Row Level Security](/docs/guides/auth/row-level-security) for this table and allow anonymous access. In production, be sure to secure your application with the appropriate permissions.
|
||||
|
||||
```js
|
||||
const { createClient } = require('@supabase/supabase-js')
|
||||
</StepHikeCompact.Details>
|
||||
|
||||
const supabase = createClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY)
|
||||
<StepHikeCompact.Code>
|
||||
|
||||
/*
|
||||
Channel name can be any string.
|
||||
Event name can can be one of:
|
||||
- INSERT
|
||||
- UPDATE
|
||||
- DELETE
|
||||
- *
|
||||
*/
|
||||
const channel = supabase
|
||||
.channel('schema-db-changes')
|
||||
.on(
|
||||
'postgres_changes',
|
||||
{
|
||||
event: '*',
|
||||
schema: 'public',
|
||||
},
|
||||
(payload) => console.log(payload)
|
||||
)
|
||||
.subscribe()
|
||||
```
|
||||
```sql
|
||||
-- Turn on security
|
||||
alter table "todos"
|
||||
enable row level security;
|
||||
|
||||
## Table Changes
|
||||
-- Allow anonymous access
|
||||
create policy "Allow anonymous access"
|
||||
on todos
|
||||
for select
|
||||
to anon
|
||||
using (true);
|
||||
```
|
||||
|
||||
To listen to changes on a table in the `public` schema:
|
||||
</StepHikeCompact.Code>
|
||||
|
||||
```js
|
||||
// Supabase client setup
|
||||
</StepHikeCompact.Step>
|
||||
|
||||
const channel = supabase
|
||||
.channel('table-db-changes')
|
||||
.on(
|
||||
'postgres_changes',
|
||||
{
|
||||
event: 'INSERT',
|
||||
schema: 'public',
|
||||
table: 'messages',
|
||||
},
|
||||
(payload) => console.log(payload)
|
||||
)
|
||||
.subscribe()
|
||||
```
|
||||
<StepHikeCompact.Step step={3}>
|
||||
|
||||
<StepHikeCompact.Details title="Enable Postgres replication">
|
||||
|
||||
## Filter Changes
|
||||
Go to your project's [Replication settings](https://supabase.com/dashboard/project/_/database/replication), and under `supabase_realtime`, toggle on the tables you want to listen to.
|
||||
|
||||
</StepHikeCompact.Details>
|
||||
|
||||
</StepHikeCompact.Step>
|
||||
|
||||
<StepHikeCompact.Step step={4}>
|
||||
|
||||
<StepHikeCompact.Details title="Install the client">
|
||||
|
||||
Install the Supabase JavaScript client.
|
||||
|
||||
</StepHikeCompact.Details>
|
||||
|
||||
<StepHikeCompact.Code>
|
||||
|
||||
```bash
|
||||
npm install @supabase/supabase-js
|
||||
```
|
||||
|
||||
</StepHikeCompact.Code>
|
||||
|
||||
</StepHikeCompact.Step>
|
||||
|
||||
<StepHikeCompact.Step step={5}>
|
||||
|
||||
<StepHikeCompact.Details title="Create the client">
|
||||
|
||||
This client will be used to listen to Postgres changes.
|
||||
|
||||
</StepHikeCompact.Details>
|
||||
|
||||
<StepHikeCompact.Code>
|
||||
|
||||
```js
|
||||
import {
|
||||
createClient
|
||||
} from '@supabase/supabase-js'
|
||||
|
||||
const client = createClient(
|
||||
'https://<project>.supabase.co',
|
||||
'<your-anon-key>'
|
||||
)
|
||||
```
|
||||
|
||||
</StepHikeCompact.Code>
|
||||
|
||||
</StepHikeCompact.Step>
|
||||
|
||||
<StepHikeCompact.Step step={6}>
|
||||
<StepHikeCompact.Details title="Listen to changes by schema">
|
||||
|
||||
Listen to changes on all tables in the `public` schema by setting the `schema` property to 'public' and event name to `*`. The event name can be one of:
|
||||
- `INSERT`
|
||||
- `UPDATE`
|
||||
- `DELETE`
|
||||
- `*`
|
||||
|
||||
The channel name can be any string except 'realtime'.
|
||||
|
||||
</StepHikeCompact.Details>
|
||||
|
||||
<StepHikeCompact.Code>
|
||||
|
||||
```js
|
||||
const channelA = client
|
||||
.channel('schema-db-changes')
|
||||
.on(
|
||||
'postgres_changes',
|
||||
{
|
||||
event: '*',
|
||||
schema: 'public',
|
||||
},
|
||||
(payload) => console.log(payload)
|
||||
)
|
||||
.subscribe()
|
||||
```
|
||||
|
||||
</StepHikeCompact.Code>
|
||||
|
||||
<StepHikeCompact.Details title="Listen to changes by table">
|
||||
|
||||
Listen to just inserts in the `todos` table by setting the `table` property to 'todos' and event name to `INSERT`.
|
||||
|
||||
</StepHikeCompact.Details>
|
||||
|
||||
<StepHikeCompact.Code>
|
||||
|
||||
```js
|
||||
const channelB = client
|
||||
.channel('table-db-changes')
|
||||
.on(
|
||||
'postgres_changes',
|
||||
{
|
||||
event: 'INSERT',
|
||||
schema: 'public',
|
||||
table: 'todos',
|
||||
},
|
||||
(payload) => console.log(payload)
|
||||
)
|
||||
.subscribe()
|
||||
```
|
||||
|
||||
</StepHikeCompact.Code>
|
||||
|
||||
<StepHikeCompact.Details title="Listen to changes by filter">
|
||||
|
||||
Listen to changes in the `todos` table when a column's value equals a specified value. In this example, we only listen to inserts on `todos` where the row `id` is 1.
|
||||
|
||||
</StepHikeCompact.Details>
|
||||
|
||||
<StepHikeCompact.Code>
|
||||
|
||||
```js
|
||||
const channelC = client
|
||||
.channel('table-filter-changes')
|
||||
.on(
|
||||
'postgres_changes',
|
||||
{
|
||||
event: 'INSERT',
|
||||
schema: 'public',
|
||||
table: 'todos',
|
||||
filter: 'id=eq.1',
|
||||
},
|
||||
(payload) => console.log(payload)
|
||||
)
|
||||
.subscribe()
|
||||
```
|
||||
|
||||
</StepHikeCompact.Code>
|
||||
|
||||
<StepHikeCompact.Details title="More filters">
|
||||
|
||||
Check out the [full list of available filters](/docs/guides/realtime/postgres-changes#available-filters).
|
||||
|
||||
</StepHikeCompact.Details>
|
||||
|
||||
</StepHikeCompact.Step>
|
||||
|
||||
<StepHikeCompact.Step step={7}>
|
||||
<StepHikeCompact.Details title="Insert dummy data">
|
||||
|
||||
Now we can add some data to our table which will trigger `channelA`, `channelB`, and `channelC` event handlers.
|
||||
|
||||
</StepHikeCompact.Details>
|
||||
|
||||
<StepHikeCompact.Code>
|
||||
|
||||
```sql
|
||||
insert into todos (task)
|
||||
values
|
||||
('Change!');
|
||||
```
|
||||
|
||||
</StepHikeCompact.Code>
|
||||
|
||||
</StepHikeCompact.Step>
|
||||
|
||||
</StepHikeCompact>
|
||||
|
||||
## Available filters
|
||||
|
||||
Realtime offers filters so you can specify the data your client receives at a more granular level.
|
||||
|
||||
@@ -122,8 +264,6 @@ Realtime offers filters so you can specify the data your client receives at a mo
|
||||
To listen to changes when a column's value in a table equals a client-specified value:
|
||||
|
||||
```js
|
||||
// Supabase client setup
|
||||
|
||||
const channel = supabase
|
||||
.channel('changes')
|
||||
.on(
|
||||
@@ -146,8 +286,6 @@ const channel = supabase
|
||||
To listen to changes when a column's value in a table does not equal a client-specified value:
|
||||
|
||||
```js
|
||||
// Supabase client setup
|
||||
|
||||
const channel = supabase
|
||||
.channel('changes')
|
||||
.on(
|
||||
@@ -170,8 +308,6 @@ const channel = supabase
|
||||
To listen to changes when a column's value in a table is less than a client-specified value:
|
||||
|
||||
```js
|
||||
// Supabase client setup
|
||||
|
||||
const channel = supabase
|
||||
.channel('changes')
|
||||
.on(
|
||||
@@ -196,8 +332,6 @@ const channel = supabase
|
||||
To listen to changes when a column's value in a table is less than or equal to a client-specified value:
|
||||
|
||||
```js
|
||||
// Supabase client setup
|
||||
|
||||
const channel = supabase
|
||||
.channel('changes')
|
||||
.on(
|
||||
@@ -222,8 +356,6 @@ const channel = supabase
|
||||
To listen to changes when a column's value in a table is greater than a client-specified value:
|
||||
|
||||
```js
|
||||
// Supabase client setup
|
||||
|
||||
const channel = supabase
|
||||
.channel('changes')
|
||||
.on(
|
||||
@@ -249,8 +381,6 @@ const channel = supabase
|
||||
To listen to changes when a column's value in a table is greater than or equal to a client-specified value:
|
||||
|
||||
```js
|
||||
// Supabase client setup
|
||||
|
||||
const channel = supabase
|
||||
.channel('changes')
|
||||
.on(
|
||||
@@ -276,8 +406,6 @@ const channel = supabase
|
||||
To listen to changes when a column's value in a table equals any client-specified values:
|
||||
|
||||
```js
|
||||
// Supabase client setup
|
||||
|
||||
const channel = supabase
|
||||
.channel('changes')
|
||||
.on(
|
||||
@@ -297,13 +425,11 @@ const channel = supabase
|
||||
This filter uses Postgres' `= ANY`. Realtime allows a maximum of 100 values for this filter.
|
||||
</Admonition>
|
||||
|
||||
## Combination Changes
|
||||
## Combination changes
|
||||
|
||||
To listen to different events and schema/tables/filters combinations with the same channel:
|
||||
|
||||
```js
|
||||
// Supabase client setup
|
||||
|
||||
const channel = supabase
|
||||
.channel('db-changes')
|
||||
.on(
|
||||
@@ -328,11 +454,40 @@ const channel = supabase
|
||||
.subscribe()
|
||||
```
|
||||
|
||||
## Custom Tokens
|
||||
## Full `old` record
|
||||
|
||||
By default, only `new` record changes are sent but if you want to receive the `old` record (previous values) whenever you `UPDATE` or `DELETE` a record, you can set the `replica identity` of your table to `full`:
|
||||
|
||||
```sql
|
||||
alter table
|
||||
messages replica identity full;
|
||||
```
|
||||
|
||||
<Admonition type="caution">
|
||||
|
||||
RLS policies are not applied to `DELETE` statements. When RLS is enabled and `replica identity` is set to `full` on a table, the `old` record contains only the primary key(s).
|
||||
|
||||
</Admonition>
|
||||
|
||||
## Private schemas
|
||||
|
||||
Postgres Changes works out of the box for tables in the `public` schema. You can listen to tables in your private schemas by granting table `SELECT` permissions to the database role found in your access token. You can run a query similar to the following:
|
||||
|
||||
```sql
|
||||
grant select on "non_private_schema"."some_table" to authenticated;
|
||||
```
|
||||
|
||||
<Admonition type="caution">
|
||||
|
||||
We strongly encourage you to enable RLS and create policies for tables in private schemas. Otherwise, any role you grant access to will have unfettered read access to the table.
|
||||
|
||||
</Admonition>
|
||||
|
||||
## Custom tokens
|
||||
|
||||
You may choose to sign your own tokens to customize claims that can be checked in your RLS policies.
|
||||
|
||||
Your project JWT secret is found with your [Project API keys](https://supabase.com/dashboard/project/_/settings/api) in your dashboard.
|
||||
Your project JWT secret is found with your [Project API keys](https://app.supabase.com/project/_/settings/api) in your dashboard.
|
||||
|
||||
<Admonition type="caution">
|
||||
Do not expose the `service_role` token on the client because the role is authorized to bypass
|
||||
@@ -364,7 +519,7 @@ const channel = supabase
|
||||
.subscribe()
|
||||
```
|
||||
|
||||
### Refreshed Tokens
|
||||
### Refreshed tokens
|
||||
|
||||
You will need to refresh tokens on your own, but once generated, you can pass them to Realtime.
|
||||
|
||||
@@ -376,6 +531,11 @@ For example, if you're using the `supabase-js` `v2` client then you can pass you
|
||||
supabase.realtime.setAuth('fresh-token')
|
||||
```
|
||||
|
||||
## More Realtime Quickstarts
|
||||
|
||||
- [Broadcast Quickstart](/docs/guides/realtime/broadcast)
|
||||
- [Presence Quickstart](/docs/guides/realtime/presence)
|
||||
|
||||
export const Page = ({ children }) => <Layout meta={meta} children={children} />
|
||||
|
||||
export default Page
|
||||
@@ -1,11 +1,14 @@
|
||||
import Layout from '~/layouts/DefaultGuideLayout'
|
||||
import StepHikeCompact from '~/components/StepHikeCompact'
|
||||
|
||||
export const meta = {
|
||||
id: 'presence',
|
||||
title: 'Presence',
|
||||
description: "Getting started with Realtime's Presence feature",
|
||||
subtitle: "Get up and running with Realtime's Presence feature",
|
||||
breadcrumb: 'Realtime Presence Quickstart',
|
||||
}
|
||||
|
||||
Presence can be used to share state between clients. Each client maintains their own piece of state within the shared state.
|
||||
|
||||
Presence utilizes an in-memory conflict-free replicated data type (CRDT) to track and synchronize shared state in an eventually consistent manner. It computes the difference between existing state and new state changes and sends the necessary updates to clients via Broadcast.
|
||||
|
||||
When a new client subscribes to a channel, it will immediately receive the channel's latest state in a single message instead of waiting for all other clients to send their individual states.
|
||||
@@ -14,152 +17,259 @@ Clients are free to come-and-go as they please, and as long as they are all subs
|
||||
|
||||
The neat thing about Presence is that if a client is suddenly disconnected (for example, they go offline), their state will be automatically removed from the shared state. If you've ever tried to build an “I'm online” feature which handles unexpected disconnects, you'll appreciate how useful this is.
|
||||
|
||||
## Presence State
|
||||
## Quick start
|
||||
|
||||
You can get started by listening to `sync` event messages notifying the client that a channel's state has been synchronized on the server. You can get the state by calling the channel's `presenceState` helper:
|
||||
Let's explore how to implement Realtime Presence so you can integrate it into your use case.
|
||||
|
||||
<StepHikeCompact>
|
||||
|
||||
<StepHikeCompact.Step step={1}>
|
||||
|
||||
<StepHikeCompact.Details title="Install the client">
|
||||
|
||||
Install the Supabase JavaScript client.
|
||||
|
||||
</StepHikeCompact.Details>
|
||||
|
||||
<StepHikeCompact.Code>
|
||||
|
||||
```bash
|
||||
npm install @supabase/supabase-js
|
||||
```
|
||||
|
||||
</StepHikeCompact.Code>
|
||||
|
||||
</StepHikeCompact.Step>
|
||||
|
||||
<StepHikeCompact.Step step={2}>
|
||||
|
||||
<StepHikeCompact.Details title="Create the first client">
|
||||
|
||||
This client will be used to track Presence state as new clients join and leave the channel.
|
||||
|
||||
Go to your Supabase project's [API Settings](https://supabase.com/dashboard/project/_/settings/api) and grab the `URL` and `anon` public API key.
|
||||
|
||||
</StepHikeCompact.Details>
|
||||
|
||||
<StepHikeCompact.Code>
|
||||
|
||||
```js
|
||||
import {
|
||||
createClient
|
||||
} from '@supabase/supabase-js'
|
||||
|
||||
const clientA = createClient(
|
||||
'https://<project>.supabase.co',
|
||||
'<your-anon-key>'
|
||||
)
|
||||
```
|
||||
|
||||
</StepHikeCompact.Code>
|
||||
|
||||
</StepHikeCompact.Step>
|
||||
|
||||
<StepHikeCompact.Step step={3}>
|
||||
|
||||
<StepHikeCompact.Details title="Create a channel">
|
||||
|
||||
A channel's topic can be anything except for `'realtime'`.
|
||||
|
||||
</StepHikeCompact.Details>
|
||||
|
||||
<StepHikeCompact.Code>
|
||||
|
||||
```js
|
||||
const channelA = clientA.channel('room-1')
|
||||
```
|
||||
|
||||
</StepHikeCompact.Code>
|
||||
|
||||
</StepHikeCompact.Step>
|
||||
|
||||
<StepHikeCompact.Step step={4}>
|
||||
|
||||
<StepHikeCompact.Details title="Sync and track state">
|
||||
|
||||
Listen to the `sync`, `join`, and `leave` events triggered whenever any client joins or leaves the channel or changes their slice of state.
|
||||
|
||||
To begin tracking state, `clientA` calls `channelA.track()`, passing in the desired state to share. Once `clientA` successfully tracks its state, it will automatically trigger its own `sync` and `join` event handlers.
|
||||
|
||||
</StepHikeCompact.Details>
|
||||
|
||||
<StepHikeCompact.Code>
|
||||
|
||||
```js
|
||||
channelA
|
||||
.on(
|
||||
'presence',
|
||||
{ event: 'sync' },
|
||||
() => {
|
||||
const newState = channelA.presenceState()
|
||||
console.log('sync', newState)
|
||||
}
|
||||
)
|
||||
.on(
|
||||
'presence',
|
||||
{ event: 'join' },
|
||||
({ key, newPresences }) => {
|
||||
console.log('join', key, newPresences)
|
||||
}
|
||||
)
|
||||
.on(
|
||||
'presence',
|
||||
{ event: 'leave' },
|
||||
({ key, leftPresences }) => {
|
||||
console.log('leave', key, leftPresences)
|
||||
}
|
||||
)
|
||||
.subscribe(async (status) => {
|
||||
if (status === 'SUBSCRIBED') {
|
||||
const presenceTrackStatus = await channelA.track({
|
||||
user: 'user-1',
|
||||
online_at: new Date().toISOString(),
|
||||
})
|
||||
console.log(presenceTrackStatus)
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
</StepHikeCompact.Code>
|
||||
|
||||
</StepHikeCompact.Step>
|
||||
|
||||
<StepHikeCompact.Step step={5}>
|
||||
|
||||
<StepHikeCompact.Details title="Create the second client">
|
||||
|
||||
This client will add to and remove from shared state so other clients can be notified of changes to Presence state.
|
||||
|
||||
</StepHikeCompact.Details>
|
||||
|
||||
<StepHikeCompact.Code>
|
||||
|
||||
```js
|
||||
const clientB = createClient(
|
||||
'https://<project>.supabase.co',
|
||||
'<your-anon-key>'
|
||||
)
|
||||
```
|
||||
|
||||
</StepHikeCompact.Code>
|
||||
|
||||
</StepHikeCompact.Step>
|
||||
|
||||
<StepHikeCompact.Step step={6}>
|
||||
|
||||
<StepHikeCompact.Details title="Create another channel">
|
||||
|
||||
This channel's topic must match `channelA`'s.
|
||||
|
||||
</StepHikeCompact.Details>
|
||||
|
||||
<StepHikeCompact.Code>
|
||||
|
||||
```js
|
||||
const channelB = clientB.channel('room-1')
|
||||
```
|
||||
|
||||
</StepHikeCompact.Code>
|
||||
|
||||
</StepHikeCompact.Step>
|
||||
|
||||
<StepHikeCompact.Step step={7}>
|
||||
|
||||
<StepHikeCompact.Details title="Add to state">
|
||||
|
||||
Subscribe to channel and add to state.
|
||||
|
||||
This will trigger `clientA`'s `sync` and `join` event handlers.
|
||||
|
||||
</StepHikeCompact.Details>
|
||||
|
||||
<StepHikeCompact.Code>
|
||||
|
||||
```js
|
||||
channelB.subscribe(async (status) => {
|
||||
if (status === 'SUBSCRIBED') {
|
||||
const presenceTrackStatus = await channelA.track({
|
||||
user: 'user-2',
|
||||
online_at: new Date().toISOString(),
|
||||
})
|
||||
console.log(presenceTrackStatus)
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
</StepHikeCompact.Code>
|
||||
|
||||
</StepHikeCompact.Step>
|
||||
|
||||
<StepHikeCompact.Step step={8}>
|
||||
|
||||
<StepHikeCompact.Details title="Remove from state">
|
||||
|
||||
This will trigger `clientA`'s `sync` and `leave` event handlers.
|
||||
|
||||
</StepHikeCompact.Details>
|
||||
|
||||
<StepHikeCompact.Code>
|
||||
|
||||
```js
|
||||
const untrackPresence = async () => {
|
||||
const presenceUntrackStatus = await channelB.untrack()
|
||||
console.log(presenceUntrackStatus)
|
||||
}
|
||||
|
||||
untrackPresence()
|
||||
```
|
||||
|
||||
</StepHikeCompact.Code>
|
||||
|
||||
</StepHikeCompact.Step>
|
||||
|
||||
</StepHikeCompact>
|
||||
|
||||
## Presence Key
|
||||
|
||||
By default, Presence will generate a unique `UUIDv1` key on the server to track a client channel's state. If you prefer, you can provide a custom key when creating the channel. This key should be unique among clients.
|
||||
|
||||
```js
|
||||
const { createClient } = require('@supabase/supabase-js')
|
||||
import { createClient } from '@supabase/supabase-js'
|
||||
|
||||
const supabase = createClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY)
|
||||
|
||||
const channel = supabase.channel('test')
|
||||
|
||||
channel
|
||||
.on('presence', { event: 'sync' }, () => {
|
||||
const state = channel.presenceState()
|
||||
console.log(state)
|
||||
})
|
||||
.subscribe()
|
||||
```
|
||||
|
||||
Whenever there's Presence activity on the `'test'` channel, this `sync` event will be broadcast to all clients subscribed to the channel.
|
||||
|
||||
## Listen to Joins
|
||||
|
||||
You can create a client and listen to new state joining the channel's Presence:
|
||||
|
||||
```js
|
||||
// Supabase client setup
|
||||
|
||||
const channel = supabase.channel('test')
|
||||
|
||||
channel
|
||||
.on('presence', { event: 'join' }, ({ key, newPresences }) => {
|
||||
console.log(key, newPresences)
|
||||
})
|
||||
.subscribe()
|
||||
```
|
||||
|
||||
## Track Presence
|
||||
|
||||
On another client, subscribe to the channel and insert state to be tracked by Presence:
|
||||
|
||||
```js
|
||||
// Supabase client setup
|
||||
|
||||
const channel = supabase.channel('test')
|
||||
|
||||
channel.subscribe(async (status) => {
|
||||
if (status === 'SUBSCRIBED') {
|
||||
const presenceTrackStatus = await channel.track({
|
||||
user: 'user-1',
|
||||
online_at: new Date().toISOString(),
|
||||
})
|
||||
console.log(presenceTrackStatus)
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### Presence Key
|
||||
|
||||
By default, Presence will generate an `UUIDv1` key on the server to uniquely track a client channel's state but you may pass Presence a custom key when creating the channel.
|
||||
|
||||
```js
|
||||
// Supabase client setup
|
||||
|
||||
const channel = supabase.channel('test', {
|
||||
const channelC = supabase.channel('test', {
|
||||
config: {
|
||||
presence: {
|
||||
key: 'userId-1',
|
||||
key: 'userId-123',
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
channel.subscribe(async (status) => {
|
||||
if (status === 'SUBSCRIBED') {
|
||||
const presenceTrackStatus = await channel.track({
|
||||
user: 'user-1',
|
||||
online_at: new Date().toISOString(),
|
||||
})
|
||||
console.log(presenceTrackStatus)
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
## Listen to Leaves
|
||||
|
||||
You can create a client and listen to a client channel's state leaving:
|
||||
|
||||
```js
|
||||
// Supabase client setup
|
||||
|
||||
const channel = supabase.channel('test')
|
||||
|
||||
channel
|
||||
.on('presence', { event: 'leave' }, ({ key, leftPresences }) => {
|
||||
console.log(key, leftPresences)
|
||||
})
|
||||
.subscribe()
|
||||
```
|
||||
|
||||
## Untrack Presence
|
||||
|
||||
On another client, subscribe to the channel, and remove tracked state from Presence:
|
||||
|
||||
```js
|
||||
// Supabase client setup
|
||||
|
||||
const channel = supabase.channel('test')
|
||||
|
||||
channel.subscribe(async (status) => {
|
||||
if (status === 'SUBSCRIBED') {
|
||||
const presenceTrackStatus = await channel.track({
|
||||
user: 'user-1',
|
||||
online_at: new Date().toISOString(),
|
||||
})
|
||||
|
||||
if (presenceTrackStatus === 'ok') {
|
||||
const presenceUntrackStatus = await channel.untrack()
|
||||
console.log(presenceUntrackStatus)
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
## Client-Side Rate Limit
|
||||
|
||||
There is a default client-side rate limit that enables you to send 10 messages per second, or one message every 100 milliseconds. You can customize this when creating the client:
|
||||
By default the client will rate limit itself at 10 messages per second (1 message every 100 milliseconds). You can customize this when creating the client:
|
||||
|
||||
```js
|
||||
const { createClient } = require('@supabase/supabase-js')
|
||||
import { createClient } from '@supabase/supabase-js'
|
||||
|
||||
const supabase = createClient(
|
||||
process.env.SUPABASE_URL,
|
||||
process.env.SUPABASE_KEY,
|
||||
{
|
||||
realtime: {
|
||||
params: {
|
||||
eventsPerSecond: 5
|
||||
}
|
||||
}
|
||||
}
|
||||
const supabase = createClient('https://<project>.supabase.co', '<your-anon-key>', {
|
||||
realtime: {
|
||||
params: {
|
||||
eventsPerSecond: 5,
|
||||
},
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
By setting `eventsPerSecond` to 5, you can send one message every 200 milliseconds on a per client basis.
|
||||
|
||||
Learn more by visiting the [Quotas](/docs/guides/realtime/quotas) section.
|
||||
|
||||
## More Realtime Quickstarts
|
||||
|
||||
- [Broadcast Quickstart](/docs/guides/realtime/broadcast)
|
||||
- [Postgres Changes Quickstart](/docs/guides/realtime/postgres-changes)
|
||||
|
||||
export const Page = ({ children }) => <Layout meta={meta} children={children} />
|
||||
|
||||
export default Page
|
||||
@@ -1,265 +0,0 @@
|
||||
import Layout from '~/layouts/DefaultGuideLayout'
|
||||
|
||||
export const meta = {
|
||||
id: 'quickstart',
|
||||
title: 'Realtime Quickstart',
|
||||
description: "Getting started with Realtime's Features",
|
||||
sidebar_label: 'Quickstart',
|
||||
video: 'https://www.youtube.com/v/BelYEMJ2N00',
|
||||
}
|
||||
|
||||
Learn how to build [multiplayer.dev](https://multiplayer.dev), a collaborative app that demonstrates Broadcast, Presence, and Postgres Changes using [Realtime](/docs/guides/realtime).
|
||||
|
||||
<div className="video-container">
|
||||
<iframe
|
||||
src="https://www.youtube-nocookie.com/embed/BelYEMJ2N00"
|
||||
frameBorder="1"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
||||
allowFullScreen
|
||||
></iframe>
|
||||
</div>
|
||||
|
||||
## Install `supabase-js` Client
|
||||
|
||||
```bash
|
||||
npm install @supabase/supabase-js
|
||||
```
|
||||
|
||||
## Cursor Positions
|
||||
|
||||
[Broadcast](/docs/guides/realtime/broadcast) allows a client to send messages and multiple clients to receive the messages. The broadcasted messages are ephemeral. They are not persisted to the database and are directly relayed through the Realtime servers. This is ideal for sending information like cursor positions where minimal latency is important, but persisting them is not.
|
||||
|
||||
In [multiplayer.dev](https://multiplayer.dev), client's cursor positions are sent to other clients in the room. However, cursor positions will be randomly generated for this example.
|
||||
|
||||
You need to get the public `anon` access token from your project's [API settings](https://supabase.com/dashboard/project/_/settings/api). Then you can set up the Supabase client and start sending a client's cursor positions to other clients in channel `room1`:
|
||||
|
||||
```js
|
||||
const { createClient } = require('@supabase/supabase-js')
|
||||
|
||||
const supabase = createClient('https://your-project-ref.supabase.co', 'anon-key', {
|
||||
realtime: {
|
||||
params: {
|
||||
eventsPerSecond: 10,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
// Channel name can be any string.
|
||||
// Create channels with the same name for both the broadcasting and receiving clients.
|
||||
const channel = supabase.channel('room1')
|
||||
|
||||
// Subscribe registers your client with the server
|
||||
channel.subscribe((status) => {
|
||||
if (status === 'SUBSCRIBED') {
|
||||
// now you can start broadcasting cursor positions
|
||||
setInterval(() => {
|
||||
channel.send({
|
||||
type: 'broadcast',
|
||||
event: 'cursor-pos',
|
||||
payload: { x: Math.random(), y: Math.random() },
|
||||
})
|
||||
console.log(status)
|
||||
}, 100)
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
<Admonition type="info">
|
||||
|
||||
JavaScript client has a default rate limit of 1 Realtime event every 100 milliseconds that's configured by `eventsPerSecond`.
|
||||
|
||||
</Admonition>
|
||||
|
||||
Another client can subscribe to channel `room1` and receive cursor positions:
|
||||
|
||||
```js
|
||||
// Supabase client setup
|
||||
|
||||
// Listen to broadcast messages.
|
||||
supabase
|
||||
.channel('room1')
|
||||
.on('broadcast', { event: 'cursor-pos' }, (payload) => console.log(payload))
|
||||
.subscribe((status) => {
|
||||
if (status === 'SUBSCRIBED') {
|
||||
// your callback function will now be called with the messages broadcast by the other client
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
<Admonition type="info">
|
||||
|
||||
`type` must be `broadcast` and the `event` must match for clients subscribed to the channel.
|
||||
|
||||
</Admonition>
|
||||
|
||||
## Roundtrip Latency
|
||||
|
||||
You can also configure the channel so that the server must return an acknowledgement that it received the `broadcast` message. This is useful if you want to measure the roundtrip latency:
|
||||
|
||||
```js
|
||||
// Supabase client setup
|
||||
|
||||
const channel = supabase.channel('calc-latency', {
|
||||
config: {
|
||||
broadcast: { ack: true },
|
||||
},
|
||||
})
|
||||
|
||||
channel.subscribe(async (status) => {
|
||||
if (status === 'SUBSCRIBED') {
|
||||
const begin = performance.now()
|
||||
|
||||
await channel.send({
|
||||
type: 'broadcast',
|
||||
event: 'latency',
|
||||
payload: {},
|
||||
})
|
||||
|
||||
const end = performance.now()
|
||||
|
||||
console.log(`Latency is ${end - begin} milliseconds`)
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
## Track and Display Which Users Are Online
|
||||
|
||||
[Presence](/docs/guides/realtime/presence) stores and synchronize shared state across clients. The `sync` event is triggered whenever the shared state changes. The `join` event is triggered when new clients join the channel and `leave` event is triggered when clients leave.
|
||||
|
||||
Each client can use the channel's `track` method to store an object in shared state. Each client can only track one object, and if `track` is called again by the same client, then the new object overwrites the previously tracked object in the shared state. You can use one client to track and display users who are online:
|
||||
|
||||
```js
|
||||
// Supabase client setup
|
||||
|
||||
const channel = supabase.channel('online-users', {
|
||||
config: {
|
||||
presence: {
|
||||
key: 'user1',
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
channel.on('presence', { event: 'sync' }, () => {
|
||||
console.log('Online users: ', channel.presenceState())
|
||||
})
|
||||
|
||||
channel.on('presence', { event: 'join' }, ({ newPresences }) => {
|
||||
console.log('New users have joined: ', newPresences)
|
||||
})
|
||||
|
||||
channel.on('presence', { event: 'leave' }, ({ leftPresences }) => {
|
||||
console.log('Users have left: ', leftPresences)
|
||||
})
|
||||
|
||||
channel.subscribe(async (status) => {
|
||||
if (status === 'SUBSCRIBED') {
|
||||
const status = await channel.track({ online_at: new Date().toISOString() })
|
||||
console.log(status)
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
Then you can use another client to add another user to the channel's Presence state:
|
||||
|
||||
```js
|
||||
// Supabase client setup
|
||||
|
||||
const channel = supabase.channel('online-users', {
|
||||
config: {
|
||||
presence: {
|
||||
key: 'user2',
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
// Presence event handlers setup
|
||||
|
||||
channel.subscribe(async (status) => {
|
||||
if (status === 'SUBSCRIBED') {
|
||||
const status = await channel.track({ online_at: new Date().toISOString() })
|
||||
console.log(status)
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
If a channel is set up without a presence key, the server generates a random UUID. `type` must be `presence` and `event` must be either `sync`, `join`, or `leave`.
|
||||
|
||||
## Insert and Receive Persisted Messages
|
||||
|
||||
[Postgres Changes](/docs/guides/realtime#postgres-changes) enables your client to insert, update, or delete database records and send the changes to clients. Create a `messages` table to keep track of messages created by users in specific rooms:
|
||||
|
||||
```sql
|
||||
create table messages (
|
||||
id serial primary key,
|
||||
message text,
|
||||
user_id text,
|
||||
room_id text,
|
||||
created_at timestamptz default now()
|
||||
)
|
||||
|
||||
alter table messages enable row level security;
|
||||
|
||||
create policy "anon_ins_policy"
|
||||
ON messages
|
||||
for insert
|
||||
to anon
|
||||
with check (true);
|
||||
|
||||
create policy "anon_sel_policy"
|
||||
ON messages
|
||||
for select
|
||||
to anon
|
||||
using (true);
|
||||
```
|
||||
|
||||
If it doesn't already exist, create a `supabase_realtime` publication and add `messages` table to the publication:
|
||||
|
||||
```sql
|
||||
begin;
|
||||
-- remove the supabase_realtime publication
|
||||
drop publication if exists supabase_realtime;
|
||||
|
||||
-- re-create the supabase_realtime publication with no tables and only for insert
|
||||
create publication supabase_realtime with (publish = 'insert');
|
||||
commit;
|
||||
|
||||
-- add a table to the publication
|
||||
alter publication supabase_realtime add table messages;
|
||||
```
|
||||
|
||||
You can then have a client listen for changes on the `messages` table for a specific room and send and receive persisted messages:
|
||||
|
||||
```js
|
||||
// Supabase client setup
|
||||
|
||||
const channel = supabase.channel('db-messages')
|
||||
|
||||
const roomId = 'room1'
|
||||
const userId = 'user1'
|
||||
|
||||
channel.on(
|
||||
'postgres_changes',
|
||||
{
|
||||
event: 'INSERT',
|
||||
schema: 'public',
|
||||
table: 'messages',
|
||||
filter: `room_id=eq.${roomId}`,
|
||||
},
|
||||
(payload) => console.log(payload)
|
||||
)
|
||||
|
||||
channel.subscribe(async (status) => {
|
||||
if (status === 'SUBSCRIBED') {
|
||||
const res = await supabase.from('messages').insert({
|
||||
room_id: roomId,
|
||||
user_id: userId,
|
||||
message: 'Welcome to Realtime!',
|
||||
})
|
||||
console.log(res)
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
export const Page = ({ children }) => <Layout meta={meta} children={children} />
|
||||
|
||||
export default Page
|
||||
@@ -32,7 +32,7 @@ Beyond the Free and Pro plan you can customize your quotas by [contacting suppor
|
||||
|
||||
Some basic WebSocket message rate limiting is implemented client-side.
|
||||
|
||||
For example, the [multiplayer.dev demo](/docs/guides/realtime/quickstart#cursor-positions) instantiates the Supabase client with an `eventsPerSecond` parameter.
|
||||
For example, the [multiplayer.dev](https://multiplayer.dev) instantiates the Supabase client with an `eventsPerSecond` parameter.
|
||||
|
||||
## Quota Errors
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ export const meta = {
|
||||
sidebar_label: 'Quickstart',
|
||||
}
|
||||
|
||||
This guide shows the basic functionality of Supabase Storage. Find a full [example application on GitHub](https://github.com/supabase/supabase/tree/master/examples/user-management/nextjs-ts-user-management) or deploy it with [Vercel for a preview](https://vercel.com/new/git/external?repository-url=https%3A%2F%2Fgithub.com%2Fsupabase%2Fsupabase%2Ftree%2Fmaster%2Fexamples%2Fuser-management%2Fnextjs-ts-user-management&project-name=supabase-user-management&repository-name=supabase-user-management&demo-title=Supabase%20User%20Management&demo-description=An%20example%20web%20app%20using%20Supabase%20and%20Next.js&demo-url=https%3A%2F%2Fsupabase-nextjs-ts-user-management.vercel.app&demo-image=https%3A%2F%2Fi.imgur.com%2FZ3HkQqe.png&integration-ids=oac_jUduyjQgOyzev1fjrW83NYOv&external-id=nextjs-user-management).
|
||||
This guide shows the basic functionality of Supabase Storage. Find a full [example application on GitHub](https://github.com/supabase/supabase/tree/master/examples/user-management/nextjs-user-management) or deploy it with [Vercel for a preview](https://vercel.com/new/git/external?repository-url=https%3A%2F%2Fgithub.com%2Fsupabase%2Fsupabase%2Ftree%2Fmaster%2Fexamples%2Fuser-management%2Fnextjs-ts-user-management&project-name=supabase-user-management&repository-name=supabase-user-management&demo-title=Supabase%20User%20Management&demo-description=An%20example%20web%20app%20using%20Supabase%20and%20Next.js&demo-url=https%3A%2F%2Fsupabase-nextjs-ts-user-management.vercel.app&demo-image=https%3A%2F%2Fi.imgur.com%2FZ3HkQqe.png&integration-ids=oac_jUduyjQgOyzev1fjrW83NYOv&external-id=nextjs-user-management).
|
||||
|
||||
<Admonition type="note">
|
||||
|
||||
|
||||
@@ -2041,4 +2041,9 @@ module.exports = [
|
||||
source: '/docs/guides/realtime/extensions/postgres-changes',
|
||||
destination: '/docs/guides/realtime/postgres-changes',
|
||||
},
|
||||
{
|
||||
permanent: true,
|
||||
source: '/docs/guides/realtime/quickstart',
|
||||
destination: '/docs/guides/realtime',
|
||||
},
|
||||
]
|
||||
Reference in new issue
Block a user