diff --git a/src/routes/docs/Sidebar.svelte b/src/routes/docs/Sidebar.svelte index fbee7c5cad..f0abe5776b 100644 --- a/src/routes/docs/Sidebar.svelte +++ b/src/routes/docs/Sidebar.svelte @@ -107,7 +107,8 @@ { label: 'Realtime', href: '/docs/apis/realtime', - icon: 'icon-clock' + icon: 'icon-clock', + isParent: true }, { label: 'REST', diff --git a/src/routes/docs/apis/+layout.svelte b/src/routes/docs/apis/graphql/+layout.svelte similarity index 75% rename from src/routes/docs/apis/+layout.svelte rename to src/routes/docs/apis/graphql/+layout.svelte index 2416c16742..93e22c8ceb 100644 --- a/src/routes/docs/apis/+layout.svelte +++ b/src/routes/docs/apis/graphql/+layout.svelte @@ -1,6 +1,6 @@ diff --git a/src/routes/docs/apis/realtime/+layout.svelte b/src/routes/docs/apis/realtime/+layout.svelte new file mode 100644 index 0000000000..80ebef8218 --- /dev/null +++ b/src/routes/docs/apis/realtime/+layout.svelte @@ -0,0 +1,64 @@ + + + + + + diff --git a/src/routes/docs/apis/realtime/+page.markdoc b/src/routes/docs/apis/realtime/+page.markdoc index 748e9fa820..3132a0c990 100644 --- a/src/routes/docs/apis/realtime/+page.markdoc +++ b/src/routes/docs/apis/realtime/+page.markdoc @@ -4,7 +4,7 @@ title: Realtime description: Want to build dynamic and interactive applications with real-time data updates? Appwrite Realtime API makes it possible, get started with our intro guide. --- -Appwrite supports multiple protocols for accessing the server, including [REST](/docs/apis/rest), [GraphQL](/docs/apis/graphql), and [Realtime](/docs/apis/realtime). The Appwrite Realtime allows you to listen to any Appwrite events in realtime using the `Realtime` service. +Appwrite supports multiple protocols for accessing the server, including [REST](/docs/apis/rest), [GraphQL](/docs/apis/graphql), and Realtime. The Appwrite Realtime allows you to listen to any Appwrite events in realtime using the `Realtime` service. Instead of requesting new data via HTTP, the subscription will receive new data every time it changes, any connected client receives that update within milliseconds via a WebSocket connection. @@ -90,744 +90,14 @@ val subscription = realtime.subscribe(Channel.files()) { {% /multicode %} -To subscribe to updates from different Appwrite resources, you need to specify one or more [channels](/docs/apis/realtime#channels). The channels offer a wide and powerful selection that will allow you to listen to all possible resources. This allows you to receive updates not only from the database, but from _all_ the services that Appwrite offers. +To subscribe to updates from different Appwrite resources, you need to specify one or more [channels](/docs/apis/realtime/channels). The channels offer a wide and powerful selection that will allow you to listen to all possible resources. This allows you to receive updates not only from the database, but from _all_ the services that Appwrite offers. If you subscribe to a channel, you will receive callbacks for a variety of events related to the channel. The events column in the callback can be used to filter and respond to specific events in a channel. [View a list of all available events](/docs/advanced/platform/events). -# Channel helpers {% #channel-helpers %} - -Instead of manually writing channel strings, you can use the `Channel` helper class to build type-safe channel subscriptions. The helper provides a fluent API that makes it easier to construct channel strings and reduces errors. - -{% multicode %} -```client-web -import { Client, Realtime, Channel } from "appwrite"; - -const client = new Client() - .setEndpoint('https://.cloud.appwrite.io/v1') - .setProject(''); - -const realtime = new Realtime(client); - -// Subscribe to account channel -const subscription = await realtime.subscribe(Channel.account(), response => { - console.log(response); -}); - -// Subscribe to a specific row -const rowSubscription = await realtime.subscribe( - Channel.tablesdb('').table('').row(''), - response => { - console.log(response); - } -); -``` - -```client-flutter -import 'package:appwrite/appwrite.dart'; - -final client = Client() - .setEndpoint('https://.cloud.appwrite.io/v1') - .setProject(''); - -final realtime = Realtime(client); - -// Subscribe to account channel -final subscription = realtime.subscribe([Channel.account()]); - -// Subscribe to a specific row -final docSubscription = realtime.subscribe([ - Channel.tablesdb('').table('').row('') -]); -``` - -```client-apple -import Appwrite - -let client = Client() - .setEndpoint("https://.cloud.appwrite.io/v1") - .setProject("") - -let realtime = Realtime(client) - -// Subscribe to account channel -let subscription = realtime.subscribe(channels: [Channel.account()]) { response in - print(String(describing: response)) -} - -// Subscribe to a specific row -let docSubscription = realtime.subscribe( - channels: [Channel.tablesdb("").table("").row("")] -) { response in - print(String(describing: response)) -} -``` - -```client-android-kotlin -import io.appwrite.Client -import io.appwrite.services.Realtime -import io.appwrite.extensions.Channel - -val client = Client(context) - .setEndpoint("https://.cloud.appwrite.io/v1") - .setProject("") - -val realtime = Realtime(client) - -// Subscribe to account channel -val subscription = realtime.subscribe(Channel.account()) { - print(it.toString()) -} - -// Subscribe to a specific row -val docSubscription = realtime.subscribe( - Channel.tablesdb("").table("").row("") -) { - print(it.toString()) -} -``` - -{% /multicode %} - -The `Channel` helper supports all available channels and allows you to: -- Build channels with a fluent, chainable API -- Optionally specify resource IDs (omit IDs to subscribe to all resources) -- Add event filters like `.create()`, `.update()`, or `.delete()` - {% info title="Permissions" %} All subscriptions are secured by the [permissions system](/docs/advanced/platform/permissions) offered by Appwrite, meaning a user will only receive updates to resources they have permission to access. Using `Role.any()` on read permissions will allow any client to receive updates. {% /info %} - -# Authentication {% #authentication %} - -Realtime authenticates using an existing user session. If you authenticate **after** creating a subscription, the subscription will not receive updates for the newly authenticated user. You will need to re-create the subscription to work with the new user. - -More information and examples of authenticating users can be found in the dedicated [authentication docs](/docs/products/auth). - -# Examples {% #examples %} -The examples below will show you how you can use Realtime in various ways. - -## Subscribe to a Channel {% #subscribe-to-a-channel %} -In this example we are subscribing to all updates related to our account by using the `account` channel. This will be triggered by any update related to the authenticated user, like updating the user's name or e-mail address. - -{% multicode %} -```client-web -import { Client, Realtime, Channel } from "appwrite"; - -const client = new Client() - .setEndpoint('https://.cloud.appwrite.io/v1') - .setProject(''); - -const realtime = new Realtime(client); - -const subscription = await realtime.subscribe(Channel.account(), response => { - // Callback will be executed on all account events. - console.log(response); -}); -``` - -```client-flutter -import 'package:appwrite/appwrite.dart'; - -final client = Client() - .setEndpoint('https://.cloud.appwrite.io/v1') - .setProject(''); - -final realtime = Realtime(client); - -final subscription = realtime.subscribe([Channel.account()]); - -subscription.stream.listen((response) { - // Callback will be executed on all account events. - print(response); -}) -``` - -```client-apple -import Appwrite -import AppwriteModels - -let client = Client() - .setEndpoint("https://.cloud.appwrite.io/v1") - .setProject("") - -let realtime = Realtime(client) - -let subscription = realtime.subscribe(channels: [Channel.account()]) { response in - // Callback will be executed on all account events. - print(String(describing: response)) -} -``` - -```client-android-kotlin -import io.appwrite.Client -import io.appwrite.services.Realtime -import io.appwrite.extensions.Channel - -val client = Client(context) - .setEndpoint("https://.cloud.appwrite.io/v1") - .setProject("") - -val realtime = Realtime(client) - -val subscription = realtime.subscribe(Channel.account()) { - // Callback will be executed on all account events. - print(it.payload.toString()) -} -``` - -{% /multicode %} - -## Subscribe to Multiple Channels {% #subscribe-to-multiple-channel %} - -You can also listen to multiple channels at once by passing an array of channels. This will trigger the callback for any events for all channels passed. - -In this example we are listening to a specific row and all files by subscribing to `Channel.tablesdb("").table("").row("")` and `Channel.files()` channels. - -{% multicode %} -```client-web -import { Client, Realtime, Channel } from "appwrite"; - -const client = new Client() - .setEndpoint('https://.cloud.appwrite.io/v1') - .setProject(''); - -const realtime = new Realtime(client); - -const subscription = await realtime.subscribe([ - Channel.tablesdb('').table('').row(''), - Channel.files() -], response => { - // Callback will be executed on changes for the row and all files. - console.log(response); -}); -``` - -```client-flutter -import 'package:appwrite/appwrite.dart'; - -final client = Client() - .setEndpoint('https://.cloud.appwrite.io/v1') - .setProject(''); - -final realtime = Realtime(client); - -final subscription = realtime.subscribe([ - Channel.tablesdb('').table('').row(''), - Channel.files() -]); - -subscription.stream.listen((response) { - // Callback will be executed on changes for the row and all files. - print(response); -}) -``` - -```client-apple -import Appwrite -import AppwriteModels - -let client = Client() - .setEndpoint("https://.cloud.appwrite.io/v1") - .setProject("") - -let realtime = Realtime(client) - -realtime.subscribe(channels: [ - Channel.tablesdb("").table("").row(""), - Channel.files() -]) { response in - // Callback will be executed on changes for the row and all files. - print(String(describing: response)) -} -``` - -```client-android-kotlin -import io.appwrite.Client -import io.appwrite.services.Realtime -import io.appwrite.extensions.Channel - -val client = Client(context) - .setEndpoint("https://.cloud.appwrite.io/v1") - .setProject("") -val realtime = Realtime(client) - -realtime.subscribe( - Channel.tablesdb("").table("").row(""), - Channel.files() -) { - // Callback will be executed on changes for the row and all files. - print(it.toString()) -} -``` - -{% /multicode %} - -## Unsubscribe {% #unsubscribe %} - -If you no longer want to receive updates from a subscription, you can unsubscribe so that your callbacks are no longer called. Leaving old subscriptions alive and resubscribing can result in duplicate subscriptions and cause race conditions. - -{% multicode %} -```client-web -import { Client, Realtime, Channel } from "appwrite"; - -const client = new Client() - .setEndpoint('https://.cloud.appwrite.io/v1') - .setProject(''); - -const realtime = new Realtime(client); - -const subscription = await realtime.subscribe(Channel.files(), response => { - // Callback will be executed on changes for all files. - console.log(response); -}); - -// Closes the subscription. -await subscription.close(); -``` - -```client-flutter -import 'package:appwrite/appwrite.dart'; - -final client = Client() - .setEndpoint('https://.cloud.appwrite.io/v1') - .setProject(''); - -final realtime = Realtime(client); - -final subscription = realtime.subscribe([Channel.files()]); - -subscription.stream.listen((response) { - // Callback will be executed on changes for all files. - print(response); -}) - -// Closes the subscription. -subscription.close(); -``` - -```client-apple -import Appwrite - -let client = Client() -let realtime = Realtime(client) - -let subscription = realtime.subscribe(channels: [Channel.files()]) { response in - // Callback will be executed on changes for all files. - print(response.toString()) -} - -// Closes the subscription. -subscription.close() -``` - -```client-android-kotlin -import io.appwrite.Client -import io.appwrite.services.Realtime -import io.appwrite.extensions.Channel - -val client = Client(context) - .setEndpoint("https://.cloud.appwrite.io/v1") - .setProject("") - -val realtime = Realtime(client) - -val subscription = realtime.subscribe(Channel.files()) { - // Callback will be executed on changes for all files. - print(it.toString()) -} - -// Closes the subscription. -subscription.close() -``` - -{% /multicode %} - -# Queries {% #queries %} - -You can filter realtime events by passing queries as a third parameter when subscribing. Events are filtered server-side based on your queries, so your callback only receives updates that match your conditions. This allows you to use familiar SDK queries like `Query.equal` to automatically filter events instead of filtering manually in your callback. - -{% multicode %} -```client-web -import { Client, Realtime, Channel, Query } from "appwrite"; - -const client = new Client() - .setEndpoint('https://.cloud.appwrite.io/v1') - .setProject(''); - -const realtime = new Realtime(client); - -// Subscribe to all updates -const allVotes = await realtime.subscribe( - Channel.tablesdb('').table('').row(), - response => { - console.log(response.payload); - } -); - -// Subscribe to updates where person equals 'person1' -const person1Votes = await realtime.subscribe( - Channel.tablesdb('').table('').row(), - response => { - console.log(response.payload); - }, - [Query.equal('person', ['person1'])] -); - -// Subscribe to updates where person is not 'person1' -const otherVotes = await realtime.subscribe( - Channel.tablesdb('').table('').row(), - response => { - console.log(response.payload); - }, - [Query.notEqual('person', 'person1')] -); -``` - -```client-flutter -import 'package:appwrite/appwrite.dart'; - -final client = Client() - .setEndpoint('https://.cloud.appwrite.io/v1') - .setProject(''); - -final realtime = Realtime(client); - -// Subscribe to all updates -final allVotes = realtime.subscribe( - [Channel.tablesdb('').table('').row()] -); - -allVotes.stream.listen((response) { - print(response.payload); -}); - -// Subscribe to updates where person equals 'person1' -final person1Votes = realtime.subscribe( - [Channel.tablesdb('').table('').row()], - queries: [Query.equal('person', ['person1'])] -); - -person1Votes.stream.listen((response) { - print(response.payload); -}); - -// Subscribe to updates where person is not 'person1' -final otherVotes = realtime.subscribe( - [Channel.tablesdb('').table('').row()], - queries: [Query.notEqual('person', 'person1')] -); - -otherVotes.stream.listen((response) { - print(response.payload); -}); -``` - -```client-apple -import Appwrite -import AppwriteModels - -let client = Client() - .setEndpoint("https://.cloud.appwrite.io/v1") - .setProject("") - -let realtime = Realtime(client) - -// Subscribe to all updates -let allVotes = realtime.subscribe( - channels: [Channel.tablesdb("").table("").row()] -) { response in - print(String(describing: response.payload)) -} - -// Subscribe to updates where person equals 'person1' -let person1Votes = realtime.subscribe( - channels: [Channel.tablesdb("").table("").row()], - callback: { response in - print(String(describing: response.payload)) - }, - queries: [Query.equal("person", value: ["person1"])] -) - -// Subscribe to updates where person is not 'person1' -let otherVotes = realtime.subscribe( - channels: [Channel.tablesdb("").table("").row()], - callback: { response in - print(String(describing: response.payload)) - }, - queries: [Query.notEqual("person", value: "person1")] -) -``` - -```client-android-kotlin -import io.appwrite.Client -import io.appwrite.Query -import io.appwrite.services.Realtime -import io.appwrite.extensions.Channel - -val client = Client(context) - .setEndpoint("https://.cloud.appwrite.io/v1") - .setProject("") - -val realtime = Realtime(client) - -// Subscribe to all updates -val allVotes = realtime.subscribe( - Channel.tablesdb("").table("").row() -) { - print(it.payload.toString()) -} - -// Subscribe to updates where person equals 'person1' -val person1Votes = realtime.subscribe( - Channel.tablesdb("").table("").row(), - payloadType = Any::class.java, - queries = setOf(Query.equal("person", listOf("person1"))) -) { - print(it.payload.toString()) -} - -// Subscribe to updates where person is not 'person1' -val otherVotes = realtime.subscribe( - Channel.tablesdb("").table("").row(), - payloadType = Any::class.java, - queries = setOf(Query.notEqual("person", "person1")) -) { - print(it.payload.toString()) -} -``` - -{% /multicode %} - -## Supported queries {% #supported-queries %} - -The following query methods are supported for realtime filtering: - -{% table %} -* Category -* Queries ---- -* Comparison -* `Query.equal()`, `Query.notEqual()`, `Query.greaterThan()`, `Query.greaterThanEqual()`, `Query.lessThan()`, `Query.lessThanEqual()` ---- -* Null checks -* `Query.isNull()`, `Query.isNotNull()` ---- -* Logical -* `Query.and()`, `Query.or()` -{% /table %} - -# Payload {% #payload %} - -The payload from the subscription will contain following properties: - -{% table %} -* Name -* Type -* Description ---- -* events -* string[] -* The [Appwrite events](/docs/advanced/platform/events) that triggered this update. ---- -* channels -* string[] -* An array of [channels](/docs/apis/realtime#channels) that can receive this message. ---- -* timestamp -* string -* The [ISO 8601 timestamp](https://en.wikipedia.org/wiki/ISO_8601) in UTC timezone from the server ---- -* payload -* object -* Payload contains the data equal to the response model. -{% /table %} - -If you subscribe to the `rows` channel and a row the user is allowed to read is updated, you will receive an object containing information about the event and the updated row. - -The response will look like this: - -```json -{ - "events": [ - "databases.default.tables.sample.rows.63c98b9baea0938e1206.update", - "databases.*.tables.*.rows.*.update", - "databases.default.tables.*.rows.63c98b9baea0938e1206.update", - "databases.*.tables.*.rows.63c98b9baea0938e1206.update", - "databases.*.tables.sample.rows.63c98b9baea0938e1206.update", - "databases.default.tables.sample.rows.*.update", - "databases.*.tables.sample.rows.*.update", - "databases.default.tables.*.rows.*.update", - "databases.default.tables.sample.rows.63c98b9baea0938e1206", - "databases.*.tables.*.rows.*", - "databases.default.tables.*.rows.63c98b9baea0938e1206", - "databases.*.tables.*.rows.63c98b9baea0938e1206", - "databases.*.tables.sample.rows.63c98b9baea0938e1206", - "databases.default.tables.sample.rows.*", - "databases.*.tables.sample.rows.*", - "databases.default.tables.*.rows.*", - "databases.default.tables.sample", - "databases.*.tables.*", - "databases.default.tables.*", - "databases.*.tables.sample", - "databases.default", - "databases.*" - ], - "channels": [ - "rows", - "databases.default.tables.sample.rows", - "databases.default.tables.sample.rows.63c98b9baea0938e1206" - ], - "timestamp": "2023-01-19 18:30:04.051", - "payload": { - "ip": "127.0.0.1", - "stringArray": [ - "sss" - ], - "email": "joe@example.com", - "stringRequired": "req", - "float": 3.3, - "boolean": false, - "integer": 3, - "enum": "apple", - "stringDefault": "default", - "datetime": "2023-01-19T10:27:09.428+00:00", - "url": "https://appwrite.io", - "$id": "63c98b9baea0938e1206", - "$createdAt": "2023-01-19T18:27:39.715+00:00", - "$updatedAt": "2023-01-19T18:30:04.040+00:00", - "$permissions": [], - "$tableId": "sample", - "$databaseId": "default" - } -} -``` - -# Channels {% #channels %} - -A list of all channels available you can subscribe to. When using `Channel` helpers, leaving an ID blank will subscribe using `*`. - -{% table %} -* Channel -* Channel Helper -* Description ---- -* `account` -* `Channel.account()` -* All account related events (session create, name update...) ---- -* `tablesdb..tables..rows` -* `Channel.tablesdb('').table('').row()` -* Any create/update/delete events to any row in a table ---- -* `rows` -* `Channel.rows()` -* Any create/update/delete events to any row ---- -* `tablesdb..tables..rows.` -* `Channel.tablesdb('').table('').row('')` -* Any update/delete events to a given row ---- -* `files` -* `Channel.files()` -* Any create/update/delete events to any file ---- -* `buckets..files.` -* `Channel.bucket('').file('')` -* Any update/delete events to a given file of the given bucket ---- -* `buckets..files` -* `Channel.bucket('').file()` -* Any update/delete events to any file of the given bucket ---- -* `teams.*` -* `Channel.teams()` -* Any create/update/delete events to any team ---- -* `teams.` -* `Channel.team('')` -* Any update/delete events to a given team ---- -* `memberships` -* `Channel.memberships()` -* Any create/update/delete events to any membership ---- -* `memberships.` -* `Channel.membership('')` -* Any update/delete events to a given membership ---- -* `executions` -* `Channel.executions()` -* Any update to executions ---- -* `executions.` -* `Channel.execution(ID)` -* Any update to a given execution ---- -* `functions.` -* `Channel.function('')` -* Any execution event to a given function - -{% /table %} - -You can also filter events by appending event methods to the channel helpers: -- `.create()` - Listen only to create events -- `.update()` - Listen only to update events -- `.delete()` - Listen only to delete events - -For example, `Channel.tablesdb('').table('').row('').update()` will only trigger on row updates. - -# Custom endpoint {% #custom-endpoint %} - -The SDK will guess the endpoint of the Realtime API when setting the endpoint of your Appwrite instance. If you are running Appwrite with a custom proxy and changed the route of the Realtime API, you can call the `setEndpointRealtime` method on the Client SDK and set your new endpoint value. - -By default the endpoint is `wss://.cloud.appwrite.io/v1/realtime`. - -{% multicode %} -```client-web -import { Client } from "appwrite"; -const client = new Client(); - -client.setEndpointRealtime('wss://.cloud.appwrite.io/v1/realtime'); -``` - -```client-flutter -import 'package:appwrite/appwrite.dart'; - -final client = Client(); -client.setEndpointRealtime('wss://.cloud.appwrite.io/v1/realtime'); -``` - -```client-apple -import Appwrite - -let client = Client() -client.setEndpointRealtime("wss://.cloud.appwrite.io/v1/realtime") -``` - -```client-android-kotlin -import io.appwrite.Client - -val client = Client(context) -client.setEndpointRealtime("wss://.cloud.appwrite.io/v1/realtime") -``` - -{% /multicode %} - -# Limitations {% #limitations %} - -While the Realtime API offers robust capabilities, there are currently some limitations to be aware of in its implementation. - -## Subscription changes {% #subscription-changes %} - -The SDK creates a single WebSocket connection for all subscribed channels. -Each time a channel is added or unsubscribed, the SDK currently creates a completely new connection and terminates the old one. -Therefore, subscriptions to channels should always be done in conjunction with state management so as not to be unnecessarily -built up several times by multiple components' life cycles. - -## Server SDKs {% #server-sdks %} - -We currently are not offering access to realtime with Server SDKs and an API key. diff --git a/src/routes/docs/apis/realtime/authentication/+page.markdoc b/src/routes/docs/apis/realtime/authentication/+page.markdoc new file mode 100644 index 0000000000..a60aff58d9 --- /dev/null +++ b/src/routes/docs/apis/realtime/authentication/+page.markdoc @@ -0,0 +1,23 @@ +--- +layout: article +title: Authentication +description: Learn how authentication works with Appwrite Realtime subscriptions and how to handle session-based access. +--- + +Realtime authenticates using an existing user session. If you authenticate **after** creating a subscription, the subscription will not receive updates for the newly authenticated user. You will need to re-create the subscription to work with the new user. + +More information and examples of authenticating users can be found in the dedicated [authentication docs](/docs/products/auth). + +{% info title="Permissions" %} +All subscriptions are secured by the [permissions system](/docs/advanced/platform/permissions) offered by Appwrite, meaning a user will only receive updates to resources they have permission to access. + +Using `Role.any()` on read permissions will allow any client to receive updates. +{% /info %} + +# Session lifecycle {% #session-lifecycle %} + +When working with Realtime subscriptions and authentication, keep the following in mind: + +1. **Create session first** - Always authenticate the user before creating subscriptions that require access to protected resources. +2. **Re-subscribe on session change** - If a user logs out and a new user logs in, you must close existing subscriptions and create new ones for the new session. +3. **Handle session expiry** - If a session expires, subscriptions tied to that session will stop receiving updates. Listen for session-related errors and re-authenticate when needed. diff --git a/src/routes/docs/apis/realtime/channels/+page.markdoc b/src/routes/docs/apis/realtime/channels/+page.markdoc new file mode 100644 index 0000000000..2903f2e0ce --- /dev/null +++ b/src/routes/docs/apis/realtime/channels/+page.markdoc @@ -0,0 +1,182 @@ +--- +layout: article +title: Channels +description: Explore the available Realtime channels and learn how to use Channel helpers for type-safe subscriptions in Appwrite. +--- + +Channels define which Appwrite resources you want to subscribe to. When subscribing to a channel, you will receive callbacks for events related to that channel's resources. The Appwrite SDKs provide a `Channel` helper class to build type-safe channel subscriptions using a fluent API. + +# Channel helpers {% #channel-helpers %} + +Instead of manually writing channel strings, you can use the `Channel` helper class to build type-safe channel subscriptions. The helper provides a fluent API that makes it easier to construct channel strings and reduces errors. + +{% multicode %} +```client-web +import { Client, Realtime, Channel } from "appwrite"; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +const realtime = new Realtime(client); + +// Subscribe to account channel +const subscription = await realtime.subscribe(Channel.account(), response => { + console.log(response); +}); + +// Subscribe to a specific row +const rowSubscription = await realtime.subscribe( + Channel.tablesdb('').table('').row(''), + response => { + console.log(response); + } +); +``` + +```client-flutter +import 'package:appwrite/appwrite.dart'; + +final client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +final realtime = Realtime(client); + +// Subscribe to account channel +final subscription = realtime.subscribe([Channel.account()]); + +// Subscribe to a specific row +final docSubscription = realtime.subscribe([ + Channel.tablesdb('').table('').row('') +]); +``` + +```client-apple +import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + +let realtime = Realtime(client) + +// Subscribe to account channel +let subscription = realtime.subscribe(channels: [Channel.account()]) { response in + print(String(describing: response)) +} + +// Subscribe to a specific row +let docSubscription = realtime.subscribe( + channels: [Channel.tablesdb("").table("").row("")] +) { response in + print(String(describing: response)) +} +``` + +```client-android-kotlin +import io.appwrite.Client +import io.appwrite.services.Realtime +import io.appwrite.extensions.Channel + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + +val realtime = Realtime(client) + +// Subscribe to account channel +val subscription = realtime.subscribe(Channel.account()) { + print(it.toString()) +} + +// Subscribe to a specific row +val docSubscription = realtime.subscribe( + Channel.tablesdb("").table("").row("") +) { + print(it.toString()) +} +``` + +{% /multicode %} + +The `Channel` helper supports all available channels and allows you to: +- Build channels with a fluent, chainable API +- Optionally specify resource IDs (omit IDs to subscribe to all resources) +- Add event filters like `.create()`, `.update()`, or `.delete()` + +# Available channels {% #available-channels %} + +A list of all channels available you can subscribe to. When using `Channel` helpers, leaving an ID blank will subscribe using `*`. + +{% table %} +* Channel +* Channel Helper +* Description +--- +* `account` +* `Channel.account()` +* All account related events (session create, name update...) +--- +* `tablesdb..tables..rows` +* `Channel.tablesdb('').table('').row()` +* Any create/update/delete events to any row in a table +--- +* `rows` +* `Channel.rows()` +* Any create/update/delete events to any row +--- +* `tablesdb..tables..rows.` +* `Channel.tablesdb('').table('').row('')` +* Any update/delete events to a given row +--- +* `files` +* `Channel.files()` +* Any create/update/delete events to any file +--- +* `buckets..files.` +* `Channel.bucket('').file('')` +* Any update/delete events to a given file of the given bucket +--- +* `buckets..files` +* `Channel.bucket('').file()` +* Any update/delete events to any file of the given bucket +--- +* `teams.*` +* `Channel.teams()` +* Any create/update/delete events to any team +--- +* `teams.` +* `Channel.team('')` +* Any update/delete events to a given team +--- +* `memberships` +* `Channel.memberships()` +* Any create/update/delete events to any membership +--- +* `memberships.` +* `Channel.membership('')` +* Any update/delete events to a given membership +--- +* `executions` +* `Channel.executions()` +* Any update to executions +--- +* `executions.` +* `Channel.execution(ID)` +* Any update to a given execution +--- +* `functions.` +* `Channel.function('')` +* Any execution event to a given function + +{% /table %} + +# Event filters {% #event-filters %} + +You can also filter events by appending event methods to the channel helpers: +- `.create()` - Listen only to create events +- `.update()` - Listen only to update events +- `.delete()` - Listen only to delete events + +For example, `Channel.tablesdb('').table('').row('').update()` will only trigger on row updates. diff --git a/src/routes/docs/apis/realtime/custom-endpoint/+page.markdoc b/src/routes/docs/apis/realtime/custom-endpoint/+page.markdoc new file mode 100644 index 0000000000..676eaeef88 --- /dev/null +++ b/src/routes/docs/apis/realtime/custom-endpoint/+page.markdoc @@ -0,0 +1,40 @@ +--- +layout: article +title: Custom endpoint +description: Learn how to configure a custom WebSocket endpoint for the Appwrite Realtime API when using a custom proxy. +--- + +The SDK will guess the endpoint of the Realtime API when setting the endpoint of your Appwrite instance. If you are running Appwrite with a custom proxy and changed the route of the Realtime API, you can call the `setEndpointRealtime` method on the Client SDK and set your new endpoint value. + +By default the endpoint is `wss://.cloud.appwrite.io/v1/realtime`. + +{% multicode %} +```client-web +import { Client } from "appwrite"; +const client = new Client(); + +client.setEndpointRealtime('wss://.cloud.appwrite.io/v1/realtime'); +``` + +```client-flutter +import 'package:appwrite/appwrite.dart'; + +final client = Client(); +client.setEndpointRealtime('wss://.cloud.appwrite.io/v1/realtime'); +``` + +```client-apple +import Appwrite + +let client = Client() +client.setEndpointRealtime("wss://.cloud.appwrite.io/v1/realtime") +``` + +```client-android-kotlin +import io.appwrite.Client + +val client = Client(context) +client.setEndpointRealtime("wss://.cloud.appwrite.io/v1/realtime") +``` + +{% /multicode %} diff --git a/src/routes/docs/apis/realtime/limitations/+page.markdoc b/src/routes/docs/apis/realtime/limitations/+page.markdoc new file mode 100644 index 0000000000..817da25fda --- /dev/null +++ b/src/routes/docs/apis/realtime/limitations/+page.markdoc @@ -0,0 +1,18 @@ +--- +layout: article +title: Limitations +description: Learn about the current limitations of the Appwrite Realtime API and how to work around them. +--- + +While the Realtime API offers robust capabilities, there are currently some limitations to be aware of in its implementation. + +# Subscription changes {% #subscription-changes %} + +The SDK creates a single WebSocket connection for all subscribed channels. +Each time a channel is added or unsubscribed, the SDK currently creates a completely new connection and terminates the old one. +Therefore, subscriptions to channels should always be done in conjunction with state management so as not to be unnecessarily +built up several times by multiple components' life cycles. + +# Server SDKs {% #server-sdks %} + +We currently are not offering access to realtime with Server SDKs and an API key. diff --git a/src/routes/docs/apis/realtime/payload/+page.markdoc b/src/routes/docs/apis/realtime/payload/+page.markdoc new file mode 100644 index 0000000000..88f8c45a5f --- /dev/null +++ b/src/routes/docs/apis/realtime/payload/+page.markdoc @@ -0,0 +1,95 @@ +--- +layout: article +title: Payload +description: Understand the structure of Appwrite Realtime subscription payloads and learn how to work with the response data. +--- + +When you receive an update from a Realtime subscription, the payload contains information about the event and the affected resource. Understanding this structure helps you handle updates effectively in your application. + +# Response structure {% #response-structure %} + +The payload from the subscription will contain following properties: + +{% table %} +* Name +* Type +* Description +--- +* events +* string[] +* The [Appwrite events](/docs/advanced/platform/events) that triggered this update. +--- +* channels +* string[] +* An array of [channels](/docs/apis/realtime/channels) that can receive this message. +--- +* timestamp +* string +* The [ISO 8601 timestamp](https://en.wikipedia.org/wiki/ISO_8601) in UTC timezone from the server +--- +* payload +* object +* Payload contains the data equal to the response model. +{% /table %} + +# Example {% #example %} + +If you subscribe to the `rows` channel and a row the user is allowed to read is updated, you will receive an object containing information about the event and the updated row. + +The response will look like this: + +```json +{ + "events": [ + "tablesdb.default.tables.sample.rows.63c98b9baea0938e1206.update", + "tablesdb.*.tables.*.rows.*.update", + "tablesdb.default.tables.*.rows.63c98b9baea0938e1206.update", + "tablesdb.*.tables.*.rows.63c98b9baea0938e1206.update", + "tablesdb.*.tables.sample.rows.63c98b9baea0938e1206.update", + "tablesdb.default.tables.sample.rows.*.update", + "tablesdb.*.tables.sample.rows.*.update", + "tablesdb.default.tables.*.rows.*.update", + "tablesdb.default.tables.sample.rows.63c98b9baea0938e1206", + "tablesdb.*.tables.*.rows.*", + "tablesdb.default.tables.*.rows.63c98b9baea0938e1206", + "tablesdb.*.tables.*.rows.63c98b9baea0938e1206", + "tablesdb.*.tables.sample.rows.63c98b9baea0938e1206", + "tablesdb.default.tables.sample.rows.*", + "tablesdb.*.tables.sample.rows.*", + "tablesdb.default.tables.*.rows.*", + "tablesdb.default.tables.sample", + "tablesdb.*.tables.*", + "tablesdb.default.tables.*", + "tablesdb.*.tables.sample", + "tablesdb.default", + "tablesdb.*" + ], + "channels": [ + "rows", + "tablesdb.default.tables.sample.rows", + "tablesdb.default.tables.sample.rows.63c98b9baea0938e1206" + ], + "timestamp": "2023-01-19 18:30:04.051", + "payload": { + "ip": "127.0.0.1", + "stringArray": [ + "sss" + ], + "email": "joe@example.com", + "stringRequired": "req", + "float": 3.3, + "boolean": false, + "integer": 3, + "enum": "apple", + "stringDefault": "default", + "datetime": "2023-01-19T10:27:09.428+00:00", + "url": "https://appwrite.io", + "$id": "63c98b9baea0938e1206", + "$createdAt": "2023-01-19T18:27:39.715+00:00", + "$updatedAt": "2023-01-19T18:30:04.040+00:00", + "$permissions": [], + "$tableId": "sample", + "$databaseId": "default" + } +} +``` diff --git a/src/routes/docs/apis/realtime/queries/+page.markdoc b/src/routes/docs/apis/realtime/queries/+page.markdoc new file mode 100644 index 0000000000..ab97bc912c --- /dev/null +++ b/src/routes/docs/apis/realtime/queries/+page.markdoc @@ -0,0 +1,177 @@ +--- +layout: article +title: Queries +description: Filter realtime events using queries. Use familiar SDK query methods to receive only the updates that match your conditions. +--- + +You can filter realtime events by passing queries as a third parameter when subscribing. Events are filtered server-side based on your queries, so your callback only receives updates that match your conditions. This allows you to use familiar SDK queries like `Query.equal` to automatically filter events instead of filtering manually in your callback. + +{% multicode %} +```client-web +import { Client, Realtime, Channel, Query } from "appwrite"; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +const realtime = new Realtime(client); + +// Subscribe to all updates +const allVotes = await realtime.subscribe( + Channel.tablesdb('').table('').row(), + response => { + console.log(response.payload); + } +); + +// Subscribe to updates where person equals 'person1' +const person1Votes = await realtime.subscribe( + Channel.tablesdb('').table('').row(), + response => { + console.log(response.payload); + }, + [Query.equal('person', ['person1'])] +); + +// Subscribe to updates where person is not 'person1' +const otherVotes = await realtime.subscribe( + Channel.tablesdb('').table('').row(), + response => { + console.log(response.payload); + }, + [Query.notEqual('person', 'person1')] +); +``` + +```client-flutter +import 'package:appwrite/appwrite.dart'; + +final client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +final realtime = Realtime(client); + +// Subscribe to all updates +final allVotes = realtime.subscribe( + [Channel.tablesdb('').table('').row()] +); + +allVotes.stream.listen((response) { + print(response.payload); +}); + +// Subscribe to updates where person equals 'person1' +final person1Votes = realtime.subscribe( + [Channel.tablesdb('').table('').row()], + queries: [Query.equal('person', ['person1'])] +); + +person1Votes.stream.listen((response) { + print(response.payload); +}); + +// Subscribe to updates where person is not 'person1' +final otherVotes = realtime.subscribe( + [Channel.tablesdb('').table('').row()], + queries: [Query.notEqual('person', 'person1')] +); + +otherVotes.stream.listen((response) { + print(response.payload); +}); +``` + +```client-apple +import Appwrite +import AppwriteModels + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + +let realtime = Realtime(client) + +// Subscribe to all updates +let allVotes = realtime.subscribe( + channels: [Channel.tablesdb("").table("").row()] +) { response in + print(String(describing: response.payload)) +} + +// Subscribe to updates where person equals 'person1' +let person1Votes = realtime.subscribe( + channels: [Channel.tablesdb("").table("").row()], + callback: { response in + print(String(describing: response.payload)) + }, + queries: [Query.equal("person", value: ["person1"])] +) + +// Subscribe to updates where person is not 'person1' +let otherVotes = realtime.subscribe( + channels: [Channel.tablesdb("").table("").row()], + callback: { response in + print(String(describing: response.payload)) + }, + queries: [Query.notEqual("person", value: "person1")] +) +``` + +```client-android-kotlin +import io.appwrite.Client +import io.appwrite.Query +import io.appwrite.services.Realtime +import io.appwrite.extensions.Channel + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + +val realtime = Realtime(client) + +// Subscribe to all updates +val allVotes = realtime.subscribe( + Channel.tablesdb("").table("").row() +) { + print(it.payload.toString()) +} + +// Subscribe to updates where person equals 'person1' +val person1Votes = realtime.subscribe( + Channel.tablesdb("").table("").row(), + payloadType = Any::class.java, + queries = setOf(Query.equal("person", listOf("person1"))) +) { + print(it.payload.toString()) +} + +// Subscribe to updates where person is not 'person1' +val otherVotes = realtime.subscribe( + Channel.tablesdb("").table("").row(), + payloadType = Any::class.java, + queries = setOf(Query.notEqual("person", "person1")) +) { + print(it.payload.toString()) +} +``` + +{% /multicode %} + +# Supported queries {% #supported-queries %} + +The following query methods are supported for realtime filtering: + +{% table %} +* Category +* Queries +--- +* Comparison +* `Query.equal()`, `Query.notEqual()`, `Query.greaterThan()`, `Query.greaterThanEqual()`, `Query.lessThan()`, `Query.lessThanEqual()` +--- +* Null checks +* `Query.isNull()`, `Query.isNotNull()` +--- +* Logical +* `Query.and()`, `Query.or()` +{% /table %} diff --git a/src/routes/docs/apis/realtime/subscribe/+page.markdoc b/src/routes/docs/apis/realtime/subscribe/+page.markdoc new file mode 100644 index 0000000000..aa9f29c0f0 --- /dev/null +++ b/src/routes/docs/apis/realtime/subscribe/+page.markdoc @@ -0,0 +1,244 @@ +--- +layout: article +title: Subscribe +description: Learn how to subscribe to realtime events from Appwrite services. Subscribe to single or multiple channels and manage your subscriptions. +--- + +The Appwrite Realtime API lets you subscribe to events from any Appwrite service through [channels](/docs/apis/realtime/channels). You can subscribe to a single channel, multiple channels at once, and unsubscribe when you no longer need updates. + +# Subscribe to a channel {% #subscribe-to-a-channel %} + +In this example we are subscribing to all updates related to our account by using the `account` channel. This will be triggered by any update related to the authenticated user, like updating the user's name or e-mail address. + +{% multicode %} +```client-web +import { Client, Realtime, Channel } from "appwrite"; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +const realtime = new Realtime(client); + +const subscription = await realtime.subscribe(Channel.account(), response => { + // Callback will be executed on all account events. + console.log(response); +}); +``` + +```client-flutter +import 'package:appwrite/appwrite.dart'; + +final client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +final realtime = Realtime(client); + +final subscription = realtime.subscribe([Channel.account()]); + +subscription.stream.listen((response) { + // Callback will be executed on all account events. + print(response); +}) +``` + +```client-apple +import Appwrite +import AppwriteModels + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + +let realtime = Realtime(client) + +let subscription = realtime.subscribe(channels: [Channel.account()]) { response in + // Callback will be executed on all account events. + print(String(describing: response)) +} +``` + +```client-android-kotlin +import io.appwrite.Client +import io.appwrite.services.Realtime +import io.appwrite.extensions.Channel + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + +val realtime = Realtime(client) + +val subscription = realtime.subscribe(Channel.account()) { + // Callback will be executed on all account events. + print(it.payload.toString()) +} +``` + +{% /multicode %} + +# Subscribe to multiple channels {% #subscribe-to-multiple-channels %} + +You can also listen to multiple channels at once by passing an array of channels. This will trigger the callback for any events for all channels passed. + +In this example we are listening to a specific row and all files by subscribing to `Channel.tablesdb("").table("").row("")` and `Channel.files()` channels. + +{% multicode %} +```client-web +import { Client, Realtime, Channel } from "appwrite"; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +const realtime = new Realtime(client); + +const subscription = await realtime.subscribe([ + Channel.tablesdb('').table('').row(''), + Channel.files() +], response => { + // Callback will be executed on changes for the row and all files. + console.log(response); +}); +``` + +```client-flutter +import 'package:appwrite/appwrite.dart'; + +final client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +final realtime = Realtime(client); + +final subscription = realtime.subscribe([ + Channel.tablesdb('').table('').row(''), + Channel.files() +]); + +subscription.stream.listen((response) { + // Callback will be executed on changes for the row and all files. + print(response); +}) +``` + +```client-apple +import Appwrite +import AppwriteModels + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + +let realtime = Realtime(client) + +realtime.subscribe(channels: [ + Channel.tablesdb("").table("").row(""), + Channel.files() +]) { response in + // Callback will be executed on changes for the row and all files. + print(String(describing: response)) +} +``` + +```client-android-kotlin +import io.appwrite.Client +import io.appwrite.services.Realtime +import io.appwrite.extensions.Channel + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") +val realtime = Realtime(client) + +realtime.subscribe( + Channel.tablesdb("").table("").row(""), + Channel.files() +) { + // Callback will be executed on changes for the row and all files. + print(it.toString()) +} +``` + +{% /multicode %} + +# Unsubscribe {% #unsubscribe %} + +If you no longer want to receive updates from a subscription, you can unsubscribe so that your callbacks are no longer called. Leaving old subscriptions alive and resubscribing can result in duplicate subscriptions and cause race conditions. + +{% multicode %} +```client-web +import { Client, Realtime, Channel } from "appwrite"; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +const realtime = new Realtime(client); + +const subscription = await realtime.subscribe(Channel.files(), response => { + // Callback will be executed on changes for all files. + console.log(response); +}); + +// Closes the subscription. +await subscription.close(); +``` + +```client-flutter +import 'package:appwrite/appwrite.dart'; + +final client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +final realtime = Realtime(client); + +final subscription = realtime.subscribe([Channel.files()]); + +subscription.stream.listen((response) { + // Callback will be executed on changes for all files. + print(response); +}) + +// Closes the subscription. +subscription.close(); +``` + +```client-apple +import Appwrite + +let client = Client() +let realtime = Realtime(client) + +let subscription = realtime.subscribe(channels: [Channel.files()]) { response in + // Callback will be executed on changes for all files. + print(response.toString()) +} + +// Closes the subscription. +subscription.close() +``` + +```client-android-kotlin +import io.appwrite.Client +import io.appwrite.services.Realtime +import io.appwrite.extensions.Channel + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + +val realtime = Realtime(client) + +val subscription = realtime.subscribe(Channel.files()) { + // Callback will be executed on changes for all files. + print(it.toString()) +} + +// Closes the subscription. +subscription.close() +``` + +{% /multicode %} diff --git a/src/routes/docs/apis/rest/+layout.svelte b/src/routes/docs/apis/rest/+layout.svelte new file mode 100644 index 0000000000..93e22c8ceb --- /dev/null +++ b/src/routes/docs/apis/rest/+layout.svelte @@ -0,0 +1,9 @@ + + + + + +