diff --git a/README.md b/README.md index 76d719ef..88c07d62 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,8 @@ # Appwrite React Native SDK -![License](https://img.shields.io/github/license/appwrite/sdk-for-react-native.svg?style=flat-square) +![License](https://img.shields.io/badge/license-BSD--3--Clause-blue.svg?style=flat-square) ![Version](https://img.shields.io/badge/api%20version-2.5.0-blue.svg?style=flat-square) -[![Build Status](https://img.shields.io/travis/com/appwrite/sdk-generator?style=flat-square)](https://travis-ci.com/appwrite/sdk-generator) -[![Twitter Account](https://img.shields.io/twitter/follow/appwrite?color=00acee&label=twitter&style=flat-square)](https://twitter.com/appwrite) +[![X Account](https://img.shields.io/badge/follow-%40appwrite-000000?logo=x&style=flat-square)](https://x.com/appwrite) [![Discord](https://img.shields.io/discord/564160730845151244?label=discord&style=flat-square)](https://appwrite.io/discord) **This SDK targets Appwrite server version 2.5.x as shipped on Appwrite Cloud.** Self-hosted releases can lag behind Cloud — if you run an older self-hosted build, use a matching older SDK from [previous releases](https://github.com/appwrite/sdk-for-react-native/releases) when APIs differ. @@ -14,332 +13,271 @@ Appwrite is an open-source backend as a service server that abstracts and simpli ## Installation -To install - ```bash -npx expo install react-native-appwrite react-native-url-polyfill +npx expo install react-native-appwrite expo-file-system react-native-url-polyfill ``` -### Push +The SDK supports Expo SDK 52 and later. It reads files for uploads through `expo-file-system`, which your app provides: `npx expo install` picks the version that matches your Expo SDK. + +The SDK uses Expo modules. In a React Native project created without Expo, run `npx install-expo-modules@latest` first. + +## Getting started + +### Add your platform + +In the Appwrite Console, open your project and under **Add a platform** add an **Android app**, an **Apple app**, or both. -`Push` connects over a raw TCP socket, so it needs `react-native-tcp-socket` and a development -or standalone build (native modules do not run in Expo Go). +- **Apple:** enter your app's name and bundle identifier. In Expo projects it is `expo.ios.bundleIdentifier` in `app.json`; otherwise find it in the **General** tab of your app target in Xcode. +- **Android:** enter your app's name and package name. In Expo projects it is `expo.android.package` in `app.json`; otherwise it is the `applicationId` in your app-level `build.gradle`. -On Expo SDK 52 and older, enable package exports in `metro.config.js` so `mqtt` resolves to its -React Native build (Expo SDK 53 and later do this by default): +### Load the URL polyfill + +Import `react-native-url-polyfill` once in your app's entry file (for example `index.js` or your root `_layout.tsx`), before any SDK code runs: ```js -config.resolver.unstable_enablePackageExports = true; +import 'react-native-url-polyfill/auto'; ``` -#### Background delivery on Android +### Initialize the client -A subscription with `background: true` keeps delivering after the app is backgrounded, killed or -the device restarts, until it is unsubscribed or `push.close()` is called (do this on sign-out). -The SDK's native Android module (autolinked) saves the subscription, and a scheduled job and -alarm wake the app every 15 to 60 seconds to reconnect; the broker replays what was sent in -between (`retry: true`). While the app is not on screen, each message is posted as a -notification that opens the app. It reconnects with the credential saved at subscribe time, so use a session rather -than a short-lived JWT. - -On Android 13 and later, the first background subscription asks the user for the -`POST_NOTIFICATIONS` runtime permission. If they decline, the subscription still delivers to your -callback but posts no notification. Notifications show the title, body and image sent with -`createPush`, falling back to the subscription's `title` when only a body is sent. A push with no -title or body, such as a data-only push, posts no notification and reaches only your callback. -Messages that are not a JSON object show the subscription's `title` with the raw payload as the body. +Copy your endpoint and project ID from your project's **Settings** page, and pass the bundle identifier or package name you registered to `setPlatform`. ```js -const sub = await push.subscribe('news', (message) => console.log(message.data), { - background: true, - title: 'News', -}); +import { Client } from 'react-native-appwrite'; -// Optional: immediate delivery even after a kill and during Doze, with a quiet ongoing -// notification (call while the app is in the foreground). -await push.setForeground(true); +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setPlatform('com.example.myapp'); ``` -Notifications are posted while the app is backgrounded or closed. While it is on screen your -callback shows the message, so none is posted unless the subscription passes -`notifyInForeground: true`. +For a self-hosted Appwrite, use `https:///v1` as the endpoint. -#### Delivery while the app is closed +### Make your first request -Messages sent while the app is closed arrive at the next scheduled wake-up. While the device is -awake that is about every 15 seconds, or about every 60 seconds once exact alarms are allowed; -without exact alarms the wake-ups are inexact, so battery saver can defer them further. In Doze -(screen off and idle for a while) Android limits background alarms, exact ones included, to about -one every nine minutes, so a closed app can take several minutes to receive a message: allowing -exact alarms makes wake-ups punctual, it does not lift Doze. For immediate delivery, also in -Doze, use `push.setForeground(true)` (see above). +Create a service from the client and call any of its methods. Every method takes a single object of named parameters and returns a promise. See the [API reference](https://appwrite.io/docs/references) for every service and method. -The SDK uses exact alarms on its own whenever the app may schedule them. To allow it: +```js +import { Account, ID } from 'react-native-appwrite'; -1. Declare the permissions in your app's `AndroidManifest.xml`. Both are optional and subject to - Google Play policy: `SCHEDULE_EXACT_ALARM` needs a declaration in the Play Console, and - `USE_EXACT_ALARM` is reserved for alarm, clock and calendar apps (the SDK does not use it). +const account = new Account(client); - ```xml - - - ``` +const user = await account.create({ + userId: ID.unique(), + email: 'email@example.com', + password: 'password', + name: "Walter O'Brien", +}); -With Expo, list them under `android.permissions` in `app.json` instead. +console.log(user); +``` -2. On Android 13 and later the user has to allow exact alarms, under Settings > Apps > Special app - access > Alarms & reminders. Android 12 grants a declared `SCHEDULE_EXACT_ALARM` - automatically, and older versions need nothing. Check with `await push.backgroundStatus()`: when `bestEffort` is - true, explain why to the user, then from a user action open that screen with `push.requestExactAlarms()`, or - ask for the battery-optimisation exemption with `push.requestIgnoreBatteryOptimizations()`. Both return false when there is - nothing to ask, including when the permission is not declared. The SDK never opens these - screens on its own. +### Sign in with OAuth -If `backgroundRestricted` is true, the user restricted the app's background usage and messages can -wait until the app is opened. Exact alarms don't lift it, and neither does foreground mode: Android -stops a restricted app's foreground service once it leaves the screen. Ask the user to choose -Unrestricted in the app's battery settings (Settings > Apps > your app > Battery); granting the -battery optimisation exemption also lets the SDK's alarms and jobs run, and `bestEffort` then -reflects only the exact-alarm state. It is always false below Android 9. +Enable the provider in the Console under **Auth > Settings**. In Expo, set the app's URL scheme to `appwrite-callback-` in `app.json`: -If the user force-stops the app (Settings > Force stop, and on some devices swiping it away from -recents), Android cancels its alarms and jobs: nothing is delivered until the app is opened -again, and the broker then replays what was sent meanwhile. +```json +{ + "expo": { + "scheme": "appwrite-callback-" + } +} +``` -Set the notification icon with -`` in your -``; without it a generic icon is used. +Install the Expo packages the sign-in flow uses: -```js -const status = await push.backgroundStatus(); // null outside Android -if (status?.backgroundRestricted && !status.ignoringBatteryOptimizations) { - // Ask the user to choose Unrestricted in the app's battery settings. -} else if (status?.bestEffort) { - // Explain why, then from a button press: - await push.requestExactAlarms(); -} +```bash +npx expo install expo-auth-session expo-web-browser ``` -Saved background delivery follows the app's current session, also while the app is closed: each -background run re-reads the session cookie, so a rotated session of the same user replaces the -saved one, and signing out (no session) or signing in as someone else stops background delivery. -If the broker still refuses the credential, delivery stops and `onError` reports it the next time -the app registers one. Still call `push.close()` on sign-out. +Then create a login URL with `createOAuth2Token`, open it with `expo-web-browser`, and exchange the returned secret for a session: -#### Upgrading from an earlier release candidate +```js +import { makeRedirectUri } from 'expo-auth-session'; +import * as WebBrowser from 'expo-web-browser'; +import { OAuthProvider } from 'react-native-appwrite'; -The Expo config plugin is gone: the native module now declares everything background delivery -needs. Remove `"react-native-appwrite"` from the `plugins` list in `app.json`, or `expo config` -fails to load it. +const deepLink = new URL(makeRedirectUri({ preferLocalhost: true })); +const scheme = `${deepLink.protocol}//`; -#### Opening a tapped notification +const loginUrl = account.createOAuth2Token({ + provider: OAuthProvider.Github, + success: `${deepLink}`, + failure: `${deepLink}`, +}); -Read the `data` sent with `createPush` when the user taps a background notification: +const result = await WebBrowser.openAuthSessionAsync(`${loginUrl}`, scheme); -```js -// The tap that launched the app (reported once, so call it at startup). -const opened = await push.getInitialNotification(); -if (opened) { - openSale(opened.data.saleId); -} +if (result.type === 'success') { + const url = new URL(result.url); + const userId = url.searchParams.get('userId'); + const secret = url.searchParams.get('secret'); -// Taps while the app is running, including in the background. -const stop = push.onNotificationOpened(({ topic, data }) => openSale(data.saleId)); + if (userId && secret) { + await account.createSession({ userId, secret }); + } +} ``` -On Android the SDK's native module reports the taps. Elsewhere they come from `expo-notifications`. - -Foreground mode runs a `remoteMessaging` foreground service, which Google Play asks apps to -declare in the Play Console. Apps that never enable it can remove the service from their merged -manifest with `tools:node="remove"` on `io.appwrite.reactnative.services.PushService` and -`android.permission.FOREGROUND_SERVICE_REMOTE_MESSAGING`. +### Type safety with models +Methods that return rows, such as `listRows` and `getRow`, accept a type parameter for your own row shape: -## Getting Started +```ts +import { TablesDB, type Models } from 'react-native-appwrite'; -### Add your Platform +interface Book extends Models.Row { + name: string; + author: string; + releaseYear?: string; + genre?: string[]; + isCheckedOut: boolean; +} -If this is your first time using Appwrite, create an account and create your first project. +const tablesDB = new TablesDB(client); -Then, under **Add a platform**, add a **Android app** or a **Apple app**. You can skip optional steps. +const result = await tablesDB.listRows({ + databaseId: '', + tableId: '', +}); -#### iOS steps +for (const book of result.rows) { + console.log(`${book.name} by ${book.author}`); +} +``` -Add your app **name** and **Bundle ID**. You can find your **Bundle Identifier** in the **General** tab for your app's primary target in XCode. For Expo projects you can set or find it on **app.json** file at your project's root directory. +The [Appwrite CLI](https://github.com/appwrite/sdk-for-cli) can generate these interfaces from your database schema with `appwrite types`. See [type generation](https://appwrite.io/docs/products/databases/tablesdb/type-generation). -#### Android steps -Add your app's **name** and **package name**, Your package name is generally the **applicationId** in your app-level **build.gradle** file. For Expo projects you can set or find it on **app.json** file at your project's root directory. +### Error handling -## Setup +Failed requests throw an `AppwriteException` with `message`, `code`, `type` and `response` properties. -On `index.js` add import for `react-native-url-polyfill` +```js +import { AppwriteException } from 'react-native-appwrite'; -``` -import 'react-native-url-polyfill/auto' +try { + const user = await account.get(); + console.log(user); +} catch (error) { + if (error instanceof AppwriteException) { + console.error(error.code, error.type, error.message); + } +} ``` -> If you are building for iOS, don't forget to install pods -> `cd ios && pod install && cd ..` +The [response codes](https://appwrite.io/docs/apis/response-codes) page lists every error type. -### Init your SDK +## Push -Initialize your SDK with your Appwrite server API endpoint and project ID which can be found in your project settings page. +`Push` connects over a raw TCP socket, so it needs `react-native-tcp-socket` and a development or standalone build (native modules do not run in Expo Go): -```js -import { Client } from 'react-native-appwrite'; -// Init your React Native SDK -const client = new Client(); - -client - .setEndpoint('http://localhost/v1') // Your Appwrite Endpoint - .setProject('455x34dfkj') // Your project ID - .setPlatform('com.example.myappwriteapp') // Your application ID or bundle ID. -; +```bash +npx expo install react-native-tcp-socket ``` -### Make Your First Request +Background delivery outside Android also needs `react-native-background-actions` and `expo-notifications`. -Once your SDK object is set, access any of the Appwrite services and choose any request to send. Full documentation for any service method you would like to use can be found in your SDK documentation or in the [API References](https://appwrite.io/docs) section. +Create the service from your client once the user is signed in: ```js -const account = new Account(client); - -// Register User -account.create({ - userId: ID.unique(), - email: 'me@example.com', - password: 'password', - name: 'Jane Doe' -}) - .then(function (response) { - console.log(response); - }, function (error) { - console.log(error); - }); +import { Push } from 'react-native-appwrite'; +const push = new Push(client); ``` -### Full Example +On Expo SDK 52, enable package exports in `metro.config.js` so `mqtt` resolves to its React Native build (Expo SDK 53 and later do this by default): ```js -import { Client, Account, ID } from 'react-native-appwrite'; -// Init your React Native SDK -const client = new Client(); +config.resolver.unstable_enablePackageExports = true; +``` -client - .setEndpoint('http://localhost/v1') // Your Appwrite Endpoint - .setProject('455x34dfkj') - .setPlatform('com.example.myappwriteapp') // YOUR application ID -; +### Background delivery on Android -const account = new Account(client); +A subscription with `background: true` keeps delivering after the app is backgrounded, killed or the device restarts, until it is unsubscribed or `push.close()` is called (do this on sign-out). The SDK's native Android module (autolinked) saves the subscription, and a scheduled job and alarm wake the app every 15 to 60 seconds to reconnect; the broker replays what was sent in between (`retry: true`). While the app is not on screen, each message is posted as a notification that opens the app. It reconnects with the credential saved at subscribe time, so use a session rather than a short-lived JWT. -// Register User -account.create({ - userId: ID.unique(), - email: 'me@example.com', - password: 'password', - name: 'Jane Doe' -}) - .then(function (response) { - console.log(response); - }, function (error) { - console.log(error); - }); +On Android 13 and later, the first background subscription asks the user for the `POST_NOTIFICATIONS` runtime permission. If they decline, the subscription still delivers to your callback but posts no notification. Notifications show the title, body and image sent with `createPush`, falling back to the subscription's `title` when only a body is sent. A push with no title or body, such as a data-only push, posts no notification and reaches only your callback. Messages that are not a JSON object show the subscription's `title` with the raw payload as the body. + +```js +const sub = await push.subscribe('news', (message) => console.log(message.data), { + background: true, + title: 'News', +}); + +// Optional: immediate delivery even after a kill and during Doze, with a quiet ongoing +// notification (call while the app is in the foreground). +await push.setForeground(true); ``` -### Type Safety with Models +Notifications are posted while the app is backgrounded or closed. While it is on screen your callback shows the message, so none is posted unless the subscription passes `notifyInForeground: true`. -The Appwrite React Native SDK provides type safety when working with table rows through generic methods. Methods like `listRows`, `getRow`, and others accept a generic type parameter that allows you to specify your custom model type for full type safety. +### Delivery while the app is closed -**TypeScript:** -```typescript -interface Book extends Models.Row { - name: string; - author: string; - releaseYear?: string; - category?: string; - genre?: string[]; - isCheckedOut: boolean; -} +Messages sent while the app is closed arrive at the next scheduled wake-up. While the device is awake that is about every 15 seconds, or about every 60 seconds once exact alarms are allowed; without exact alarms the wake-ups are inexact, so battery saver can defer them further. In Doze (screen off and idle for a while) Android limits background alarms, exact ones included, to about one every nine minutes, so a closed app can take several minutes to receive a message: allowing exact alarms makes wake-ups punctual, it does not lift Doze. For immediate delivery, also in Doze, use `push.setForeground(true)` (see above). -const tablesDB = new TablesDB(client); +The SDK uses exact alarms on its own whenever the app may schedule them. To allow it: -try { - const result = await tablesDB.listRows({ - databaseId: 'your-database-id', - tableId: 'your-table-id' - }); - - result.rows.forEach(book => { - console.log(`Book: ${book.name} by ${book.author}`); // Now you have full type safety - }); -} catch (error) { - console.error('Appwrite error:', error); -} -``` +1. Declare the permissions in your app's `AndroidManifest.xml`. Both are optional and subject to Google Play policy: `SCHEDULE_EXACT_ALARM` needs a declaration in the Play Console, and `USE_EXACT_ALARM` is reserved for alarm, clock and calendar apps (the SDK does not use it). -**JavaScript (with JSDoc for type hints):** -```javascript -/** - * @typedef {Object} Book - * @property {string} name - * @property {string} author - * @property {string} [releaseYear] - * @property {string} [category] - * @property {string[]} [genre] - * @property {boolean} isCheckedOut - */ + ```xml + + + ``` -const tablesDB = new TablesDB(client); + With Expo, list them under `android.permissions` in `app.json` instead. -try { - /** @type {Models.RowList} */ - const result = await tablesDB.listRows({ - databaseId: 'your-database-id', - tableId: 'your-table-id' - }); - - result.rows.forEach(book => { - console.log(`Book: ${book.name} by ${book.author}`); // Type hints available in IDE - }); -} catch (error) { - console.error('Appwrite error:', error); +2. On Android 13 and later the user has to allow exact alarms, under Settings > Apps > Special app access > Alarms & reminders. Android 12 grants a declared `SCHEDULE_EXACT_ALARM` automatically, and older versions need nothing. Check with `await push.backgroundStatus()`: when `bestEffort` is true, explain why to the user, then from a user action open that screen with `push.requestExactAlarms()`, or ask for the battery-optimisation exemption with `push.requestIgnoreBatteryOptimizations()`. Both return false when there is nothing to ask, including when the permission is not declared. The SDK never opens these screens on its own. + +If `backgroundRestricted` is true, the user restricted the app's background usage and messages can wait until the app is opened. Exact alarms don't lift it, and neither does foreground mode: Android stops a restricted app's foreground service once it leaves the screen. Ask the user to choose Unrestricted in the app's battery settings (Settings > Apps > your app > Battery); granting the battery optimisation exemption also lets the SDK's alarms and jobs run, and `bestEffort` then reflects only the exact-alarm state. It is always false below Android 9. + +If the user force-stops the app (Settings > Force stop, and on some devices swiping it away from recents), Android cancels its alarms and jobs: nothing is delivered until the app is opened again, and the broker then replays what was sent meanwhile. + +Set the notification icon with `` in your ``; without it a generic icon is used. + +```js +const status = await push.backgroundStatus(); // null outside Android +if (status?.backgroundRestricted && !status.ignoringBatteryOptimizations) { + // Ask the user to choose Unrestricted in the app's battery settings. +} else if (status?.bestEffort) { + // Explain why, then from a button press: + await push.requestExactAlarms(); } ``` -**Tip**: You can use the `appwrite types` command to automatically generate TypeScript interfaces based on your Appwrite database schema. Learn more about [type generation](https://appwrite.io/docs/products/databases/type-generation). +Saved background delivery follows the app's current session, also while the app is closed: each background run re-reads the session cookie, so a rotated session of the same user replaces the saved one, and signing out (no session) or signing in as someone else stops background delivery. If the broker still refuses the credential, delivery stops and `onError` reports it the next time the app registers one. Still call `push.close()` on sign-out. -### Error Handling +### Upgrading from an earlier release candidate -The Appwrite React Native SDK raises an `AppwriteException` object with `message`, `code` and `response` properties. You can handle any errors by catching the exception and present the `message` to the user or handle it yourself based on the provided error information. Below is an example. +The Expo config plugin is gone: the native module now declares everything background delivery needs. Remove `"react-native-appwrite"` from the `plugins` list in `app.json`, or `expo config` fails to load it. -```javascript -try { - const user = await account.create({ - userId: ID.unique(), - email: "email@example.com", - password: "password", - name: "Walter O'Brien" -}); - console.log('User created:', user); -} catch (error) { - console.error('Appwrite error:', error.message); +### Opening a tapped notification + +Read the `data` sent with `createPush` when the user taps a background notification: + +```js +// The tap that launched the app (reported once, so call it at startup). +const opened = await push.getInitialNotification(); +if (opened) { + openSale(opened.data.saleId); } + +// Taps while the app is running, including in the background. +const stop = push.onNotificationOpened(({ topic, data }) => openSale(data.saleId)); ``` -### Learn more +On Android the SDK's native module reports the taps. Elsewhere they come from `expo-notifications`. + +Foreground mode runs a `remoteMessaging` foreground service, which Google Play asks apps to declare in the Play Console. Apps that never enable it can remove the service from their merged manifest with `tools:node="remove"` on `io.appwrite.reactnative.services.PushService` and `android.permission.FOREGROUND_SERVICE_REMOTE_MESSAGING`. -You can use the following resources to learn more and get help -- 🚀 [Getting Started Tutorial](https://appwrite.io/docs/quick-starts/react-native) -- 📜 [Appwrite Docs](https://appwrite.io/docs) -- 💬 [Discord Community](https://appwrite.io/discord) -- 🚂 [Appwrite React Native Playground](https://github.com/appwrite/playground-for-react-native) +## Learn more +- [React Native quick start](https://appwrite.io/docs/quick-starts/react-native) +- [Appwrite docs](https://appwrite.io/docs) +- [Discord community](https://appwrite.io/discord) +- [React Native playground](https://github.com/appwrite/playground-for-react-native) ## Contribution -This library is auto-generated by Appwrite custom [SDK Generator](https://github.com/appwrite/sdk-generator). To learn more about how you can help us improve this SDK, please check the [contribution guide](https://github.com/appwrite/sdk-generator/blob/master/CONTRIBUTING.md) before sending a pull-request. +This library is auto-generated by the Appwrite [SDK Generator](https://github.com/appwrite/sdk-generator). To learn more about how you can help us improve this SDK, please check the [contribution guide](https://github.com/appwrite/sdk-generator/blob/main/CONTRIBUTING.md) before sending a pull-request. ## License