Skip to content

Commit

Permalink
feat: added the possibility to pass the entire layout instead of header
Browse files Browse the repository at this point in the history
  • Loading branch information
DiegoBM committed May 31, 2021
1 parent 6173c7d commit 571ecdb
Show file tree
Hide file tree
Showing 6 changed files with 1,070 additions and 739 deletions.
87 changes: 49 additions & 38 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,15 @@ React Native component that implements the OAuth2 flow for the Appwrite BaaS (Ba
npm install react-native-appwrite-oauth
```

React native Appwrite OAuth depends on the libraries [`appwrite`](https://github.com/appwrite/sdk-for-web), [`react-native-webview`](https://github.com/react-native-webview/react-native-webview) and [`@react-native-cookies/cookies`](https://github.com/react-native-cookies/cookies). These dependencies are marked as peer dependencies, and need to be fullfilled before using the component. If you were **already using them** in your project, then you are good to go, otherwise please run:
React Native Appwrite OAuth depends on the libraries [`appwrite`](https://github.com/appwrite/sdk-for-web), [`react-native-webview`](https://github.com/react-native-webview/react-native-webview) and [`@react-native-cookies/cookies`](https://github.com/react-native-cookies/cookies). These dependencies are marked as peer dependencies, and need to be fullfilled before using the component. If you were **already using them** in your project, then you are good to go, otherwise please run:

```sh
npm install appwrite react-native-webview @react-native-cookies/cookies
```

## Basic Usage

In order to use the component, you will need to wrap your code with the `react-native-appwrite-oauth` component as shown below:
In order to use the component, you just need to place it anywhere in your screen, as shown below:

```js
import AppwriteOauth from 'react-native-appwrite-oauth';
Expand Down Expand Up @@ -48,55 +48,59 @@ const YourOAuthSigninComponent = () => {
return (
<View>
<AppwriteOauth
sdk={sdk}
authenticating={authenticating}
provider="facebook"
scopes={[]}
sdk={sdk}
onSuccess={handleSuccess}
onFailure={handleFailure}
>
{/* Implement your component here. Example below */}
<View>
<Button
title="Facebook Sign In"
onPress={() => {
// starts OAuth Sign in
setAuthenticating(true);
}}
/>
</View>
</AppwriteOauth>
/>
<Button
title="Facebook Sign In"
onPress={() => {
// Start OAuth Sign in
setAuthenticating(true);
}}
/>
</View>
);
};

// ...
```

## Using a Custom Header
## Using a Custom Layout

Even though the component already provides you with a button to cancel the OAuth sign in process, you can **optionally** implement your own custom header for the same purpose or any other:
By default, the component displays within the modal, a layout composed of a _Cancel_ button, and a WebView to perform the OAuth flow over the web. In this default layout, when you click the _Cancel_ button, the **onFailure** callback will be called with a _'User cancelled'_ message.

If for some reason you need more control on what gets displayed inside the modal component, you need to display more components besides the Button and the WebView, or simply render the WebView above a _"Back"_ button, you can pass your own layout as a React Component Type in the `modalLayout` prop.

As usual, please **_make sure_** to provide enough space through styles, for the content of the modal to be seen. In many cases, if you were not able to see the contents of the WebView might have been because the container View was not taking enough space in the screen.

```js
// ...

const CustomHeader = () => (
<View style={{ padding: 10 }}>
<Button title="Back" onPress={() => handleFailure('Cancelled')} />
const MyLayout = ({ WebViewComponent }) => (
<View>
<Button title="Cancel" onPress={() => handleFailure('User cancelled')} />
<WebViewComponent />
<Button title="Force Success" onPress={() => handleSuccess()} />
</View>
);

// ...

return (
<View>
<AppwriteOauth
authenticating={authenticating}
provider="facebook"
scopes={[]}
sdk={sdk}
authenticating={authenticating}
provider="github"
scopes={['user:email']}
onSuccess={handleSuccess}
onFailure={handleFailure}
header={CustomHeader}
>
{/* Implement your component here.*/}
</AppwriteOauth>
modalLayout={MyLayout}
/>
<Button title="Github Sign In" onPress={() => setAuthenticating(true)} />
</View>
);

Expand All @@ -107,21 +111,21 @@ return (

You can find a complete react native example that implements the entire OAuth2 flow (including navigation with [React Navigation](https://reactnavigation.org)), in the "**example**" folder. Feel free to use it as a reference.

In order run the example, after cloning this repository, you will need to install all the dependencies by running:
In order to run the example, after cloning this repository, you will need to install all the dependencies by running:

```sh
yarn
```

Then open the file `example/src/sdk.ts` and provide the values for `setEndpoint` and `setProject` pointing to your respective Appwrite API server and project.
Then open the `example/src/sdk.ts` file, and provide the values for `setEndpoint` and `setProject`, pointing to your respective Appwrite API server and project.

```js
const sdk = new Appwrite();
// Fill with your Appwrite API endpoint and Project ID!
sdk.setEndpoint('http://localhost/v1').setProject('123456789');
```

Finally run the code in your desired platform, using one of the following commands.
Finally run the code in your desired platform, using one of the following commands:

To run the example app on Android:

Expand All @@ -137,29 +141,36 @@ yarn example ios

## Props

- **`sdk`** _(Appwrite - **Mandatory**)_ - Appwrite SDK instance, which needs to be properly configured before using the component.

- **`authenticating`** _(Boolean - **Mandatory**)_ - Once you set this property to true, the modal will be displayed and the OAuth process will begin. Please bear in mind that the modal will keep being displayed until you set this property back to false. A normal flow will set this property back to false upon `success` or `failure`, but you are free to implement the flow that best suits your application.

- **`provider`** _(String - **Mandatory**)_ - OAuth provider string, as accepted by the Appwrite SDK. Please check the [Appwrite SDK documentation](https://appwrite.io/docs/client/account#accountCreateOAuth2Session) for a list of supported providers. Example: `'facebook'`

- **`scopes`** _(String Array - **Mandatory**)_ - A list of custom OAuth2 scopes. Check each provider internal docs for a list of supported scopes. Example: `['user:email']` // Github sign in scopes
- **`scopes`** _(String Array - **Optional**)_ - A list of custom OAuth2 scopes. Check each provider internal docs for a list of supported scopes. If no value is passed, an empty array will be sent as default. Example: `['user:email']` // Github sign in scopes

- **`cookieData`** _(String - **Optional**)_ - String representing additional data that you might want to set with your authorization cookie. It needs to follow the same [syntax of a Set-Cookie](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie) header sent by a server. Its default value, if no value is passed is `'path=/; HttpOnly'`. Please bear in mind that even if you don't include `HttpOnly`, the component will automatically include it. Example: `'Max-Age=2592000; Expires=Wed, 21 Oct 2015 07:28:00 GMT'`.

- **`sdk`** _(Appwrite - **Mandatory**)_ - Appwrite SDK instance, properly configured.
- **`loadingColor`** _(String - **Optional**)_ - Valid React Native color value, that will represent the color of the spinning wheel indicating the loading state inside the modal. Its default value is `'#206fce'`. Example: `'rgb(0, 0, 10)'`.

- **`cookieData`** _(String - **Optional**)_ - String representing additional data that you might want to set with your authorization cookie. It needs to follow the same [syntax of a Set-Cookie](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie) header sent by a server. It's default value, if no value is passed is `'path=/; HttpOnly'`. Please bear in mind that even if you don't include `HttpOnly`, the component will automatically include it. Example: `'Max-Age=2592000; Expires=Wed, 21 Oct 2015 07:28:00 GMT'`.
- **`modalLayout`** _(React.ComponentType - **Optional**)_ - Optional custom layout component, that specifies how the OAuth WebView (among any other components that you might wish to add), will be rendered inside the modal window. The component passed will receive four props:

- **`header`** _(React.ComponentType - **Optional**)_ - Optional React component that allows the user to implement a custom header on top of the WebView used for the OAuth flow, and typically used to cancel the OAuth Sign in. The default Header provided by the component displays a `Cancel` button, that once clicked will call the `onFailure` callback with a `'User cancelled'` message. This component will receive 2 props `onSuccess` and `onFailure`, equivalent to the ones described below.
- **`WebViewComponent`** _(WebView)_ Component, that represents the OAuth webview. Most of the characteristic **WebView** props that you pass to this component, will be passed directly to the internal WebView. In order to not interfere with the correct OAth flow, the only ones that will not be passed are `source`, `onError` and `onShouldStartLoadWithRequest`.
- **`setLoadingState`** _(Function)_ Controls the loading state inside the modal. If the value passed is true, it will display a spinning wheel overlaying the modal content, otherwise if the value passed is false, it will hide it. Signature: `(showLoading: boolean) => void`.
- **`onSuccess`** _(Function)_ Callback function that will be executed upon a successful OAuth sign in. Equivalent to the one described below. Signature: `() => void`.
- **`onFailure`** _(Function)_ Callback function that will be executed upon a failed OAuth sign in. Equivalent to the one described below. Signature: `(message: string, failureData: unknown) => void`.

- **`onSuccess`** _(Function - **Optional**)_ - Callback function that will be executed upon a successful OAuth sign in. It's signature is `() => void`.
- **`onSuccess`** _(Function - **Optional**)_ - Callback function that will be executed upon a successful OAuth sign in. Signature: `() => void`.

- **`onFailure`** _(Function - **Optional**)_ - Callback function that will be executed upon a failed OAuth sign in. It's signature is `(error: string) => void`.
- **`onFailure`** _(Function - **Optional**)_ - Callback function that will be executed upon a failed OAuth sign in. It will receive a message with a short description of the error. Additionally, it might receive extended error information in `failureData` if the triggering error provides that extra information. Signature: `(message: string, failureData: unknown) => void`.

## Remarks

- This component has been tested on Android only. If you experience issues using iOS devices and want to contribute with their fixes, I'll be happy to include them in.

- This component has been tested with `Facebook` and `Github` providers only, but it should work the same with all the other providers supported by Appwrite. Technically the same flow should apply to any other providers unless otherwise specified by the Appwrite team. If you experience any issues with any other provider, please check the logs on your Appwrite installation and see if it reveals important information.

- If you use the standard Appwrite development installation that includes an untrusted **SSL certificate**, the WebView will fail to navigate to your Appwrite server under https, and unfortunately many OAuth providers such as `Facebook` will require the requests to come from/to https domains. So far the only way that I have found to overcome this problem during development, is to disable the native Ssl handling. For `Android` this would entail modifying the file `node_modules\react-native-webview\android\src\main\java\com\reactnativecommunity\webview\RNCWebViewManager.java` by commenting the entire code of the `onReceivedSslError` method, and replacing it with `handler.proceed();`, as shown below (you will need to rebuild your application after this change). Since I can't test in `iOS` I don't know the steps to do the same there, but feel free to let me know and I will include them here. Also, if you know of a better way of dealing with this WebView issue, please let me know and I will include it here. Needless to say, this **will not** be necessary if your environment already has a trusted SSL certificate, or the OAuth provider that you need to use does not require requests coming from https endpoints.
- If you use the standard Appwrite development installation that includes an untrusted **SSL certificate**, the WebView will fail to navigate to your Appwrite server under https, and unfortunately many OAuth providers such as `Facebook` will require the requests to come from/to https domains (other providers like `Github` haven't made this a requirement yet). So far the only way that I have found to overcome this problem during development, is to disable the native Ssl handling. For `Android` this would entail modifying the file `node_modules\react-native-webview\android\src\main\java\com\reactnativecommunity\webview\RNCWebViewManager.java` by commenting the entire code of the `onReceivedSslError` method, and replacing it with `handler.proceed();`, as shown below (you will need to rebuild your application after this change). Since I can't test in `iOS` I don't know the steps to do the same there, but feel free to let me know and I will include them here. Also, if you know of a better way of dealing with this WebView issue, please let me know and I will include it here. Needless to say, this **will not** be necessary if your environment already has a trusted SSL certificate, or the OAuth provider that you need to use does not require requests coming from https endpoints.

```java
@Override
Expand Down
16 changes: 11 additions & 5 deletions example/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import 'react-native-gesture-handler';
import React, { useState } from 'react';
import React, { useState, useMemo } from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';

Expand All @@ -11,10 +11,16 @@ const Stack = createStackNavigator();

export default function App() {
const [auth, setAuth] = useState(false);
const authContext = {
signIn: () => setAuth(true),
signOut: () => setAuth(false),
};
const authContext = useMemo(
// This prevents context from changing unnecessarily when auth changes
// therefore components that use this context won't re-render when
// the sign in state changes (if they are not meant to, of course)
() => ({
signIn: () => setAuth(true),
signOut: () => setAuth(false),
}),
[setAuth]
);

return (
<NavigationContainer>
Expand Down
46 changes: 21 additions & 25 deletions example/src/LoginScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,38 +30,34 @@ const LoginScreen: React.FC = () => {
sdk={sdk}
onSuccess={handleSuccess}
onFailure={handleFailure}
>
<View>
<View style={styles.button}>
<Button
title="Facebook Sign In"
onPress={() => {
setProvider('facebook');
setScopes([]);
setOauthAuthenticating(true);
}}
/>
</View>
<View style={styles.button}>
<Button
title="Github Sign In"
onPress={() => {
setProvider('github');
setScopes(['user:email']);
setOauthAuthenticating(true);
}}
/>
</View>
</View>
</AppwriteOauth>
/>
<View style={styles.button}>
<Button
title="Facebook Sign In"
onPress={() => {
setProvider('facebook');
setScopes([]);
setOauthAuthenticating(true);
}}
/>
</View>
<View style={styles.button}>
<Button
title="Github Sign In"
onPress={() => {
setProvider('github');
setScopes(['user:email']);
setOauthAuthenticating(true);
}}
/>
</View>
</View>
);
};

const styles = StyleSheet.create({
screen: {
flex: 1,
alignItems: 'stretch',
},
button: {
marginHorizontal: 10,
Expand Down
Loading

0 comments on commit 571ecdb

Please sign in to comment.