Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 97 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,22 +1,114 @@
# react-native-contentpass

Contentpass React Native SDK
Contentpass React Native SDK enables easy integration of Contentpass functionality into your React Native applications.

## Installation
Install the package using npm or Yarn:

```sh
npm install react-native-contentpass
```

or

```sh
yarn add react-native-contentpass
```

### Peer Dependencies
The following peer dependencies must also be installed:
- [react](https://github.com/facebook/react) (Required for React Native projects.)
- [react-native](https://github.com/facebook/react-native) (Core React Native framework)
- [react-native-app-auth](https://github.com/FormidableLabs/react-native-app-auth) (Used for OAuth 2.0 authentication)
- [react-native-encrypted-storage](https://github.com/emeraldsanto/react-native-encrypted-storage) (Ensures secure storage of authentication tokens)

Some dependencies require additional setup in the native code. Refer to their official guides:
- [react-native-app-auth setup](https://commerce.nearform.com/open-source/react-native-app-auth/docs#setup)
- [react-native-encrypted-storage setup](https://github.com/emeraldsanto/react-native-encrypted-storage?tab=readme-ov-file#installation)

### Expo support
If you are using Expo, you need to run the following command to enable modifications to the `ios` and `android` directories:

```sh
npx expo prebuild
```

## Usage

### Initialization
Wrap your app's root component with ContentpassSdkProvider. The provider requires a configuration object (contentpassConfig) with the following properties:
- `propertyId` - Your unique property ID
- `issuer` - The OAuth 2.0 server URL (e.g. `https://my.contentpass.net`)
- `redirectUrl` - the redirect URL of your app to which the OAuth2 server will redirect after the authentication


```jsx
import React from 'react';
import { ContentpassSdkProvider } from 'react-native-contentpass';

const contentpassConfig = {
propertyId: 'your-property-id',
issuer: 'https://my.contentpass.net',
redirectUrl: 'com.yourapp://oauthredirect',
};

const App = () => {
return (
<ContentpassSdkProvider contentpassConfig={contentpassConfig}>
<YourApp />
</ContentpassSdkProvider>
);
};

export default App;
```

### SDK Methods
The SDK exposes the following methods through the `useContentpassSdk` hook:

### authenticate
Initiates the OAuth 2.0 authentication process via a modal interface. It validates the user’s active Contentpass subscriptions
upon successful authentication.

### registerObserver
Registers a callback function to listen for changes in the user’s authentication and subscription status. The observer function
receives a state object describing the current status (see the exported [ContentpassState](./src/types/ContentpassState.ts) type).

### unregisterObserver
Unregisters a previously registered observer. The observer will no longer receive updates.

### logout
Logs the user out by clearing all stored authentication tokens.

### recoverFromError
During background token refresh, an error state may occur due to poor or no internet connection. This is indicated by the
`state` switching to `ERROR`. The state object includes a reference to the original exception that was thrown. As the SDK
does not monitor the device's connection state, you must notify the SDK when the network connection has been reestablished
or improved. The SDK will then refresh and revalidate the user's authentication tokens.

```jsx
import React, { useEffect } from 'react';
import { useContentpassSdk } from 'react-native-contentpass';

const YourApp = () => {
const { authenticate, registerObserver, unregisterObserver, logout, recoverFromError } = useContentpassSdk();

useEffect(() => {
const observer = (state) => {
console.log('Contentpass state changed:', state);
};

```js
import { multiply } from 'react-native-contentpass';
registerObserver(observer);

// ...
return () => {
unregisterObserver(observer);
};
}, []);

const result = await multiply(3, 7);
return (
<button onClick={authenticate}>Authenticate</button>
);
};
```


Expand Down
18 changes: 12 additions & 6 deletions sharedExample/src/ContentpassUsage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,15 @@ import {
import setupSourcepoint from './setupSourcepoint';

const styles = StyleSheet.create({
sourcepointDataContainer: {
scrollViewLogsContainer: {
padding: 10,
height: 400,
height: 200,
flexGrow: 0,
},
resultsText: {
marginTop: 20,
fontWeight: 'bold',
},
buttonsContainer: {
display: 'flex',
gap: 4,
Expand Down Expand Up @@ -84,10 +88,12 @@ export default function ContentpassUsage() {
<Button title={'Logout'} onPress={contentpassSdk.logout} />
</View>
<View style={styles.logsView}>
<Text>Authenticate result:</Text>
<Text>{JSON.stringify(authResult, null, 2)}</Text>
<Text>Sourcepoint user data:</Text>
<ScrollView style={styles.sourcepointDataContainer}>
<Text style={styles.resultsText}>Authenticate result:</Text>
<ScrollView style={styles.scrollViewLogsContainer}>
<Text>{JSON.stringify(authResult, null, 2)}</Text>
</ScrollView>
<Text style={styles.resultsText}>Sourcepoint user data:</Text>
<ScrollView style={styles.scrollViewLogsContainer}>
<Text>{JSON.stringify(sourcepointUserData, null, 2)}</Text>
</ScrollView>
</View>
Expand Down
12 changes: 7 additions & 5 deletions src/Contentpass.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ describe('Contentpass', () => {
expect(contentpassStates).toHaveLength(2);
expect(contentpassStates[1]).toEqual({
state: 'ERROR',
error: 'Authorize error',
error,
});
});

Expand All @@ -213,7 +213,8 @@ describe('Contentpass', () => {
contentpass.registerObserver((state) => {
contentpassStates.push(state);
});
fetchContentpassTokenSpy.mockRejectedValue(new Error('Fetch error'));
const error = new Error('Fetch error');
fetchContentpassTokenSpy.mockRejectedValue(error);

await contentpass.authenticate();

Expand All @@ -223,7 +224,7 @@ describe('Contentpass', () => {
expect(contentpassStates).toHaveLength(2);
expect(contentpassStates[1]).toEqual({
state: 'ERROR',
error: 'Fetch error',
error,
});
});

Expand Down Expand Up @@ -410,13 +411,14 @@ describe('Contentpass', () => {
contentpassStates.push(state);
});

authorizeSpy.mockRejectedValue(new Error('Authorize error'));
const error = new Error('Authorize error');
authorizeSpy.mockRejectedValue(error);

await contentpass.authenticate();

expect(contentpassStates[1]).toEqual({
state: 'ERROR',
error: 'Authorize error',
error,
});

await contentpass.recoverFromError();
Expand Down
4 changes: 2 additions & 2 deletions src/Contentpass.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ export default class Contentpass {

this.changeContentpassState({
state: ContentpassStateType.ERROR,
error: 'message' in err ? err.message : 'Unknown error',
error: err,
});
return;
}
Expand Down Expand Up @@ -133,7 +133,7 @@ export default class Contentpass {
} catch (err: any) {
this.changeContentpassState({
state: ContentpassStateType.ERROR,
error: err.message || 'Unknown error',
error: err,
});
}
};
Expand Down
3 changes: 2 additions & 1 deletion src/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export type {
ContentpassState,
ContentpassStateType,
ErrorState,
AuthenticatedState,
InitialisingState,
Expand All @@ -9,7 +10,7 @@ export type {
export type { ContentpassConfig } from './types/ContentpassConfig';

export {
default as Contentpass,
type default as Contentpass,
type ContentpassObserver,
} from './Contentpass';

Expand Down
2 changes: 1 addition & 1 deletion src/types/ContentpassState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export type AuthenticatedState = {
export type ErrorState = {
state: ContentpassStateType.ERROR;
hasValidSubscription?: never;
error: string;
error: unknown;
};

export type ContentpassState =
Expand Down