diff --git a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts index 07a43a57d9e..1a08ecc0efe 100644 --- a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts +++ b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts @@ -808,10 +808,6 @@ export const realtime: NavMenuConstant = { name: 'Concepts', url: '/guides/realtime/concepts', }, - { - name: 'Quickstart', - url: '/guides/realtime/quickstart', - }, { name: 'Features', url: undefined, diff --git a/apps/docs/pages/guides/api/quickstart.mdx b/apps/docs/pages/guides/api/quickstart.mdx index a9c73ab8c44..8e7baf747a5 100644 --- a/apps/docs/pages/guides/api/quickstart.mdx +++ b/apps/docs/pages/guides/api/quickstart.mdx @@ -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). diff --git a/apps/docs/pages/guides/realtime/broadcast.mdx b/apps/docs/pages/guides/realtime/broadcast.mdx index 3a2120c1494..90055d2c2ae 100644 --- a/apps/docs/pages/guides/realtime/broadcast.mdx +++ b/apps/docs/pages/guides/realtime/broadcast.mdx @@ -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: + + + + + + + Install the Supabase JavaScript client. + + + + + + ```bash + npm install @supabase/supabase-js + ``` + + + + + + + + + + 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. + + + + + + ```js + import { + createClient + } from '@supabase/supabase-js' + + const clientA = createClient( + 'https://.supabase.co', + '' + ) + ``` + + + + + + + + + + A channel's topic can be anything except for `'realtime'`. + + + + + + ```js + const channelA = clientA.channel('room-1') + ``` + + + + + + + + + + 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. + + + + + + ```js + channelA + .on( + 'broadcast', + { event: 'test' }, + (payload) => console.log(payload) + ) + .subscribe() + ``` + + + + + + + + + + This client will be used to send a message. + + + + + + ```js + const clientB = createClient( + 'https://.supabase.co', + '' + ) + ``` + + + + + + + + + + This channel's topic must match `channelA`'s. + + + + + + ```js + const channelB = clientB.channel('room-1') + ``` + + + + + + + + + + Subscribe to channel and send a message. + + The payload's `event` must match channelA's `event` in the `on` handler. + + + + + + ```js + channelB.subscribe((status) => { + if (status === 'SUBSCRIBED') { + channelB.send({ + type: 'broadcast', + event: 'test', + payload: { + message: 'hello, world' + }, + }) + } + }) + ``` + + + + + + + + + + `clientA` receives the message `clientB` sent. + + + + + + + +## 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://.supabase.co', '', { + 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 }) => export default Page diff --git a/apps/docs/pages/guides/realtime/concepts.mdx b/apps/docs/pages/guides/realtime/concepts.mdx index 6db5b9f5f9e..af4dee031bd 100644 --- a/apps/docs/pages/guides/realtime/concepts.mdx +++ b/apps/docs/pages/guides/realtime/concepts.mdx @@ -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://.supabase.co', '') -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. diff --git a/apps/docs/pages/guides/realtime/postgres-changes.mdx b/apps/docs/pages/guides/realtime/postgres-changes.mdx index 523855cd8ca..57b1d355830 100644 --- a/apps/docs/pages/guides/realtime/postgres-changes.mdx +++ b/apps/docs/pages/guides/realtime/postgres-changes.mdx @@ -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. - - 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. - + -## Replication Setup + + -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; + --- re-create the supabase_realtime publication with no tables -create publication supabase_realtime; + -commit; + + --- 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 + + -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; -``` + + - + -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). + - + -## Schema Changes + -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') + -const supabase = createClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY) + -/* - 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: + -```js -// Supabase client setup + -const channel = supabase - .channel('table-db-changes') - .on( - 'postgres_changes', - { - event: 'INSERT', - schema: 'public', - table: 'messages', - }, - (payload) => console.log(payload) - ) - .subscribe() -``` + + + -## 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. + + + + + + + + + + Install the Supabase JavaScript client. + + + + + + ```bash + npm install @supabase/supabase-js + ``` + + + + + + + + + + This client will be used to listen to Postgres changes. + + + + + + ```js + import { + createClient + } from '@supabase/supabase-js' + + const client = createClient( + 'https://.supabase.co', + '' + ) + ``` + + + + + + + + + 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'. + + + + + + ```js + const channelA = client + .channel('schema-db-changes') + .on( + 'postgres_changes', + { + event: '*', + schema: 'public', + }, + (payload) => console.log(payload) + ) + .subscribe() + ``` + + + + + + Listen to just inserts in the `todos` table by setting the `table` property to 'todos' and event name to `INSERT`. + + + + + + ```js + const channelB = client + .channel('table-db-changes') + .on( + 'postgres_changes', + { + event: 'INSERT', + schema: 'public', + table: 'todos', + }, + (payload) => console.log(payload) + ) + .subscribe() + ``` + + + + + + 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. + + + + + + ```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() + ``` + + + + + + Check out the [full list of available filters](/docs/guides/realtime/postgres-changes#available-filters). + + + + + + + + + Now we can add some data to our table which will trigger `channelA`, `channelB`, and `channelC` event handlers. + + + + + + ```sql + insert into todos (task) + values + ('Change!'); + ``` + + + + + + + +## 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. -## 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; +``` + + + +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). + + + +## 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; +``` + + + +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. + + + +## 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. 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 }) => export default Page diff --git a/apps/docs/pages/guides/realtime/presence.mdx b/apps/docs/pages/guides/realtime/presence.mdx index 940fafc0288..311e6aec849 100644 --- a/apps/docs/pages/guides/realtime/presence.mdx +++ b/apps/docs/pages/guides/realtime/presence.mdx @@ -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. + + + + + + + + Install the Supabase JavaScript client. + + + + + + ```bash + npm install @supabase/supabase-js + ``` + + + + + + + + + + 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. + + + + + + ```js + import { + createClient + } from '@supabase/supabase-js' + + const clientA = createClient( + 'https://.supabase.co', + '' + ) + ``` + + + + + + + + + + A channel's topic can be anything except for `'realtime'`. + + + + + + ```js + const channelA = clientA.channel('room-1') + ``` + + + + + + + + + + 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. + + + + + + ```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) + } + }) + ``` + + + + + + + + + + This client will add to and remove from shared state so other clients can be notified of changes to Presence state. + + + + + + ```js + const clientB = createClient( + 'https://.supabase.co', + '' + ) + ``` + + + + + + + + + + This channel's topic must match `channelA`'s. + + + + + + ```js + const channelB = clientB.channel('room-1') + ``` + + + + + + + + + + Subscribe to channel and add to state. + + This will trigger `clientA`'s `sync` and `join` event handlers. + + + + + + ```js + channelB.subscribe(async (status) => { + if (status === 'SUBSCRIBED') { + const presenceTrackStatus = await channelA.track({ + user: 'user-2', + online_at: new Date().toISOString(), + }) + console.log(presenceTrackStatus) + } + }) + ``` + + + + + + + + + + This will trigger `clientA`'s `sync` and `leave` event handlers. + + + + + + ```js + const untrackPresence = async () => { + const presenceUntrackStatus = await channelB.untrack() + console.log(presenceUntrackStatus) + } + + untrackPresence() + ``` + + + + + + + +## 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://.supabase.co', '', { + 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 }) => export default Page diff --git a/apps/docs/pages/guides/realtime/quickstart.mdx b/apps/docs/pages/guides/realtime/quickstart.mdx deleted file mode 100644 index de92b99f588..00000000000 --- a/apps/docs/pages/guides/realtime/quickstart.mdx +++ /dev/null @@ -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). - -
- -
- -## 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) - } -}) -``` - - - -JavaScript client has a default rate limit of 1 Realtime event every 100 milliseconds that's configured by `eventsPerSecond`. - - - -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 - } - }) -``` - - - -`type` must be `broadcast` and the `event` must match for clients subscribed to the channel. - - - -## 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 }) => - -export default Page diff --git a/apps/docs/pages/guides/realtime/quotas.mdx b/apps/docs/pages/guides/realtime/quotas.mdx index a27fc95755c..8f9ec1e6121 100644 --- a/apps/docs/pages/guides/realtime/quotas.mdx +++ b/apps/docs/pages/guides/realtime/quotas.mdx @@ -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 diff --git a/apps/docs/pages/guides/storage/quickstart.mdx b/apps/docs/pages/guides/storage/quickstart.mdx index 15421921b23..090dabb2690 100644 --- a/apps/docs/pages/guides/storage/quickstart.mdx +++ b/apps/docs/pages/guides/storage/quickstart.mdx @@ -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). diff --git a/apps/www/lib/redirects.js b/apps/www/lib/redirects.js index e41861d6bcf..e32c48b1759 100644 --- a/apps/www/lib/redirects.js +++ b/apps/www/lib/redirects.js @@ -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', + }, ]