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
7 changes: 7 additions & 0 deletions .changeset/ui-onetrust-layer-cleanup.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@contentpass/react-native-contentpass": patch
"@contentpass/react-native-contentpass-ui": patch
"@contentpass/react-native-contentpass-cmp-onetrust": patch
---

Clean up debug logs, fix consent layer theme and SDK version string. Add README documentation for all packages.
153 changes: 15 additions & 138 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,150 +1,27 @@
# @contentpass/react-native-contentpass
# react-native-contentpass

Contentpass React Native SDK enables easy integration of Contentpass functionality into your React Native applications.
Monorepo for the Contentpass React Native SDK and related packages.

## Installation
Install the package using npm or Yarn:
## Packages

```sh
npm install @contentpass/react-native-contentpass
```
| Package | Description |
|---------|-------------|
| [`@contentpass/react-native-contentpass`](./packages/react-native-contentpass) | Core SDK — authentication, subscription validation, and impression tracking. |
| [`@contentpass/react-native-contentpass-ui`](./packages/react-native-contentpass-ui) | Pre-built UI components for the Contentpass consent layer. |
| [`@contentpass/react-native-contentpass-cmp-onetrust`](./packages/react-native-contentpass-cmp-onetrust) | OneTrust CMP adapter for the Contentpass SDK. |

or
## Examples

```sh
yarn add @contentpass/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 (ask Contentpass team for details)
- `planId` - The ID of the plan you want to check the user's subscription status against (ask Contentpass team for details)
- `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
- `samplingRate` - Optional: The rate at which the SDK will send impression events for unauthenticated users. Default is 0.05 (5%)
- `logLevel` - Optional: The log level for the SDK. By default logger is disabled. Possible values are 'info', 'warn', 'error' and 'debug'


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

const contentpassConfig = {
propertyId: 'property-id',
planId: 'plan-id',
issuer: 'https://my.contentpass.net',
redirectUrl: 'com.yourapp://oauthredirect',
samplingRate: 0.1,
logLevel: 'info'
};

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.

### countImpression
Tracks and increments the impression count for the current user. This method should be invoked whenever a user views a
piece of content. It applies to all users, whether authenticated or unauthenticated.

### 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](./packages/react-native-contentpass/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 '@contentpass/react-native-contentpass';
import { Button, View } from 'react-native';

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

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

registerObserver(observer);

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

return (
<View>
<Button onPress={authenticate} title={'Authenticate'} />
<Button onPress={countImpression} title={'Count Impression'} />
</View>
);
};
```

## Integration with Sourcepoint SDK

See the [Sourcepoint SDK documentation](packages/react-native-contentpass/docs/SOURCEPOINT_SDK_INTEGRATION.md) for information on integrating the Contentpass SDK with the Sourcepoint SDK.
| Example | Description |
|---------|-------------|
| [`examples/onetrust`](./examples/onetrust) | Integration using OneTrust as the CMP. |
| [`examples/sourcepoint`](./examples/sourcepoint) | Integration using Sourcepoint as the CMP (bare React Native). |
| [`examples/sourcepoint-expo`](./examples/sourcepoint-expo) | Integration using Sourcepoint as the CMP (Expo). |

## Contributing

See the [contributing guide](packages/react-native-contentpass/docs/CONTRIBUTING.md) to learn how to contribute to the repository and the development workflow.

See the [contributing guide](./packages/react-native-contentpass/docs/CONTRIBUTING.md).

## License

MIT

---

Made with [create-react-native-library](https://github.com/callstack/react-native-builder-bob)
113 changes: 113 additions & 0 deletions packages/react-native-contentpass-cmp-onetrust/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
# @contentpass/react-native-contentpass-cmp-onetrust

A [OneTrust](https://www.onetrust.com/) CMP adapter for `@contentpass/react-native-contentpass`. Bridges the OneTrust SDK (`react-native-onetrust-cmp`) to the Contentpass `CmpAdapter` interface, so the Contentpass consent layer can manage consent through OneTrust.

## Installation

```bash
npm install @contentpass/react-native-contentpass-cmp-onetrust
# or
yarn add @contentpass/react-native-contentpass-cmp-onetrust
```

### Peer dependencies

- `@contentpass/react-native-contentpass`
- `react-native-onetrust-cmp` — the OneTrust React Native SDK must be installed and configured in your project

## Usage

First, initialize the OneTrust SDK, then create the adapter using `createOnetrustCmpAdapter`:

```tsx
import OTPublishersNativeSDK from 'react-native-onetrust-cmp';
import { createOnetrustCmpAdapter } from '@contentpass/react-native-contentpass-cmp-onetrust';
import type { CmpAdapter } from '@contentpass/react-native-contentpass';

// 1. Start the OneTrust SDK
await OTPublishersNativeSDK.startSDK(
'cdn.cookielaw.org', // CDN location
'YOUR_APP_ID', // OneTrust app ID
'en', // language code
{}, // params
false // auto-show banner
);

// 2. Create the CMP adapter
const cmpAdapter: CmpAdapter = await createOnetrustCmpAdapter(OTPublishersNativeSDK);
```

The returned `cmpAdapter` can then be passed to `ContentpassConsentGate` from `@contentpass/react-native-contentpass-ui`, or used directly via the `CmpAdapter` interface.

### Full example

```tsx
import { useEffect, useState } from 'react';
import { View, Text } from 'react-native';
import OTPublishersNativeSDK from 'react-native-onetrust-cmp';
import { ContentpassSdkProvider } from '@contentpass/react-native-contentpass';
import { ContentpassConsentGate } from '@contentpass/react-native-contentpass-ui';
import { createOnetrustCmpAdapter } from '@contentpass/react-native-contentpass-cmp-onetrust';
import type { CmpAdapter } from '@contentpass/react-native-contentpass';

export default function App() {
const [cmpAdapter, setCmpAdapter] = useState<CmpAdapter | null>(null);

useEffect(() => {
OTPublishersNativeSDK.startSDK('cdn.cookielaw.org', 'YOUR_APP_ID', 'en', {}, false)
.then(() => createOnetrustCmpAdapter(OTPublishersNativeSDK))
.then((adapter) => setCmpAdapter(adapter))
.catch((error) => console.error('Failed to initialize CMP', error));
}, []);

if (!cmpAdapter) {
return <Text>Loading...</Text>;
}

return (
<ContentpassSdkProvider contentpassConfig={contentpassConfig}>
<ContentpassConsentGate
cmpAdapter={cmpAdapter}
contentpassConfig={contentpassConfig}
>
<View>
<Text>Your app content</Text>
</View>
</ContentpassConsentGate>
</ContentpassSdkProvider>
);
}
```

For a complete working example, see the [`examples/onetrust`](../../examples/onetrust) directory.

## API

### `createOnetrustCmpAdapter(sdk)`

Factory function that creates a `CmpAdapter` from an initialized OneTrust SDK instance.

| Parameter | Type | Description |
|-----------|------|-------------|
| `sdk` | `OTPublishersNativeSDK` | An initialized OneTrust SDK instance (after `startSDK` has resolved). |

Returns `Promise<CmpAdapter>`.

The adapter fetches banner and preference center data from the OneTrust SDK during creation, and automatically extracts TCF purpose IDs and the vendor count.

### `CmpAdapter` methods provided

| Method | Description |
|--------|-------------|
| `acceptAll()` | Saves "accept all" consent via OneTrust. |
| `denyAll()` | Saves "reject all" consent via OneTrust. |
| `hasFullConsent()` | Checks whether all consent categories are granted. |
| `onConsentStatusChange(callback)` | Registers a listener that fires whenever full-consent status changes. Returns an unsubscribe function. |
| `showSecondLayer(view)` | Opens the OneTrust preference center (`'purpose'`) or vendor list (`'vendor'`) UI. The returned promise resolves when the user dismisses it. |
| `getRequiredPurposes()` | Returns the list of TCF purpose identifiers extracted from OneTrust. |
| `getNumberOfVendors()` | Returns the vendor count parsed from the OneTrust banner data. |
| `waitForInit()` | Resolves immediately (OneTrust initialization is handled before adapter creation). |

## License

MIT
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,9 @@ export async function createOnetrustCmpAdapter(
): Promise<OnetrustCmpAdapter> {
try {
const bannerData = await sdk.getBannerData();
console.log('Banner data', bannerData);

const preferenceCenterData: PreferenceCenterData =
await sdk.getPreferenceCenterData();
console.log('Preference center data', preferenceCenterData);

return new OnetrustCmpAdapter(sdk, bannerData, preferenceCenterData);
} catch (error: any) {
Expand Down
65 changes: 61 additions & 4 deletions packages/react-native-contentpass-ui/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# @contentpass/react-native-contentpass-ui

React Native UI components for Contentpass layer rendering.
Pre-built UI components for integrating the Contentpass consent layer into React Native apps. Renders the Contentpass first-layer consent dialog in a WebView and manages its visibility based on the user's authentication and consent state.

## Installation

Expand All @@ -10,18 +10,75 @@ npm install @contentpass/react-native-contentpass-ui
yarn add @contentpass/react-native-contentpass-ui
```

### Peer dependencies

Make sure the following peer dependencies are installed in your project:

- `@contentpass/react-native-contentpass`
- `react`
- `react-native`
- `react-native-webview`

You also need a CMP adapter (e.g. `@contentpass/react-native-contentpass-cmp-onetrust`) that implements the `CmpAdapter` interface.

## Components

### `ContentpassConsentGate`

A gate component that wraps your app content. It automatically shows the Contentpass consent layer when the user has neither authenticated with Contentpass nor given full CMP consent. Once consent is granted or the user authenticates, the layer is dismissed and the children are displayed.

#### Props

| Prop | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| `children` | `ReactNode` | Yes | — | The app content to render when consent is given or the user is authenticated. |
| `cmpAdapter` | `CmpAdapter` | Yes | — | A CMP adapter instance (e.g. from `@contentpass/react-native-contentpass-cmp-onetrust`). |
| `contentpassConfig` | `ContentpassConfig` | Yes | — | The Contentpass SDK configuration object. |
| `hideAppWhenVisible` | `boolean` | No | `true` | When `true`, the app content is completely replaced by the consent layer. When `false`, the consent layer is rendered as an overlay on top of the app content. |
| `onVisibilityChange` | `(visible: boolean) => void` | No | — | Callback invoked when the consent layer visibility changes. |

## Usage

Wrap your app with `ContentpassSdkProvider` (from `@contentpass/react-native-contentpass`) and place `ContentpassConsentGate` around the content that should be gated:

```tsx
import { YourComponent } from '@contentpass/react-native-contentpass-ui';
import { ContentpassSdkProvider } from '@contentpass/react-native-contentpass';
import { ContentpassConsentGate } from '@contentpass/react-native-contentpass-ui';
import type { CmpAdapter } from '@contentpass/react-native-contentpass';

function App() {
// Initialize your CMP and create an adapter first
const cmpAdapter: CmpAdapter = /* ... */;
const contentpassConfig = /* your ContentpassConfig */;

// Use the component
return (
<ContentpassSdkProvider contentpassConfig={contentpassConfig}>
<ContentpassConsentGate
cmpAdapter={cmpAdapter}
contentpassConfig={contentpassConfig}
hideAppWhenVisible={false}
>
{/* Your app content */}
</ContentpassConsentGate>
</ContentpassSdkProvider>
);
}
```

For a full working example using OneTrust as the CMP, see the [`examples/onetrust`](../../examples/onetrust) directory.

## How it works

1. `ContentpassConsentGate` waits for both the CMP adapter and the Contentpass SDK to be ready.
2. It evaluates whether the user needs to see the consent layer: the layer is shown when the user is **neither** authenticated with Contentpass **nor** has given full consent via the CMP.
3. The consent layer itself is a hosted web page rendered in a WebView (`ContentpassLayer`). It communicates back to the native app via `postMessage` to trigger actions like "accept all", "show CMP details", or "login/signup with Contentpass".
4. When the user takes an action (e.g. accepts all cookies or logs in), the gate re-evaluates and hides the layer when appropriate.

## Requirements

- React Native >= 0.76.0
- @contentpass/react-native-contentpass (peer dependency)
- `@contentpass/react-native-contentpass` (peer dependency)
- `react-native-webview` (peer dependency)

## License

Expand Down
Loading