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
21 changes: 21 additions & 0 deletions examples/consentmanager/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# @contentpass/examples-consentmanager

## 0.0.3

### Patch Changes

- Improve OneTrust integration and handling of first layer links

- Updated dependencies []:
- @contentpass/react-native-contentpass-ui@0.6.0
- @contentpass/react-native-contentpass-cmp-consentmanager@0.1.1
- @contentpass/react-native-contentpass@0.7.1

## 0.0.2

### Patch Changes

- Updated dependencies []:
- @contentpass/react-native-contentpass-cmp-consentmanager@0.1.0
- @contentpass/react-native-contentpass-ui@0.5.0
- @contentpass/react-native-contentpass@0.7.0
2 changes: 1 addition & 1 deletion examples/consentmanager/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@contentpass/examples-consentmanager",
"version": "0.0.2",
"version": "0.0.3",
"main": "index.ts",
"scripts": {
"start": "expo start",
Expand Down
9 changes: 9 additions & 0 deletions examples/onetrust/src/Content.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,19 @@ export default function Content({ cmpAdapter }: { cmpAdapter: CmpAdapter }) {
});
}, [sdk]);

async function openOneTrustSettings() {
try {
await cmpAdapter.showSecondLayer('purpose');
} catch (error) {
console.error('Failed to open OneTrust settings', error);
}
}

return (
<View>
<Text>Hello World!</Text>
<Text>Has full consent: {hasFullConsent ? 'Yes' : 'No'}</Text>
<Button title="OneTrust Settings" onPress={openOneTrustSettings} />
{hasFullConsent && (
<Button
title="Deny All"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# @contentpass/react-native-contentpass-cmp-consentmanager

## 0.1.1

### Patch Changes

- Improve OneTrust integration and handling of first layer links

## 0.1.0

### Minor Changes
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@contentpass/react-native-contentpass-cmp-consentmanager",
"version": "0.1.0",
"version": "0.1.1",
"description": "Contentpass Consentmanager CMP adapter",
"source": "./src/index.ts",
"main": "./lib/commonjs/index.js",
Expand Down
6 changes: 6 additions & 0 deletions packages/react-native-contentpass-cmp-onetrust/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# @contentpass/react-native-contentpass-cmp-onetrust

## 0.3.1

### Patch Changes

- Improve OneTrust integration and handling of first layer links

## 0.3.0

### Minor Changes
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@contentpass/react-native-contentpass-cmp-onetrust",
"version": "0.3.0",
"version": "0.3.1",
"description": "Contentpass OneTrust CMP adapter",
"source": "./src/index.ts",
"main": "./lib/commonjs/index.js",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
import type OTPublishersNativeSDK from 'react-native-onetrust-cmp';
import { OTEventName } from 'react-native-onetrust-cmp';
import OnetrustCmpAdapter, {
createOnetrustCmpAdapter,
} from './OnetrustCmpAdapter';
import type { BannerData, PreferenceCenterData } from './types';

jest.mock('react-native-onetrust-cmp', () => ({
OTConsentInteraction: {
bannerAllowAll: 'bannerAllowAll',
bannerRejectAll: 'bannerRejectAll',
},
OTEventName: {
hidePreferenceCenter: 'hidePreferenceCenter',
preferenceCenterAcceptAll: 'preferenceCenterAcceptAll',
preferenceCenterRejectAll: 'preferenceCenterRejectAll',
preferenceCenterConfirmChoices: 'preferenceCenterConfirmChoices',
hideVendorList: 'hideVendorList',
vendorConfirmChoices: 'vendorConfirmChoices',
allSDKViewsDismissed: 'allSDKViewsDismissed',
},
}));

type EventHandler = (data?: any) => void;

function createBannerData(description = 'We use 42 vendors'): BannerData {
return {
otConsentString: '',
bannerUIData: {
summary: {
description: {
text: description,
textAlign: '',
textColor: '',
textColorDark: '',
fontSize: '',
},
dpdTitle: {} as any,
title: {} as any,
dpdDescription: {} as any,
},
buttons: {} as any,
general: {} as any,
logo: {} as any,
},
appConfig: {},
storageKeys: {},
};
}

function createPreferenceCenterData(): PreferenceCenterData {
return {
purposes: [
{
groupId: 'C0001',
isIabPurpose: true,
purposeId: '1',
},
{
groupId: 'C0002',
isIabPurpose: true,
purposeId: '2',
},
] as any,
pcUIData: {},
appConfig: {},
otConsentString: '',
storageKeys: {},
};
}

function createMockSdk(overrides: Partial<OTPublishersNativeSDK> = {}): {
sdk: OTPublishersNativeSDK;
eventHandlers: Map<OTEventName, EventHandler>;
} {
const eventHandlers = new Map<OTEventName, EventHandler>();
const sdk = {
fetchPreferencesCmpApiData: jest.fn().mockResolvedValue(undefined),
getBannerData: jest.fn().mockResolvedValue(createBannerData()),
getPreferenceCenterData: jest
.fn()
.mockResolvedValue(createPreferenceCenterData()),
saveConsent: jest.fn().mockResolvedValue(undefined),
getConsentStatusForCategory: jest.fn().mockResolvedValue(1),
addEventListener: jest.fn(
(eventName: OTEventName, handler: EventHandler) => {
eventHandlers.set(eventName, handler);
return { remove: jest.fn() };
}
),
showPreferenceCenterUI: jest.fn(),
showConsentPurposesUI: jest.fn(),
...overrides,
} as OTPublishersNativeSDK;

return { sdk, eventHandlers };
}

async function flushPromises(): Promise<void> {
await new Promise((resolve) => setTimeout(resolve, 0));
}

describe('createOnetrustCmpAdapter', () => {
it('should create an adapter from the SDK data', async () => {
const { sdk } = createMockSdk();
const adapter = await createOnetrustCmpAdapter(sdk);
expect(adapter).toBeInstanceOf(OnetrustCmpAdapter);
expect(sdk.fetchPreferencesCmpApiData).toHaveBeenCalled();
expect(sdk.getBannerData).toHaveBeenCalled();
expect(sdk.getPreferenceCenterData).toHaveBeenCalled();
});
});

describe('OnetrustCmpAdapter', () => {
it('should emit consent status when OneTrust confirms preference-center choices', async () => {
const { sdk, eventHandlers } = createMockSdk({
getConsentStatusForCategory: jest
.fn()
.mockResolvedValueOnce(1)
.mockResolvedValueOnce(1)
.mockResolvedValueOnce(1)
.mockResolvedValueOnce(0),
});
const adapter = await createOnetrustCmpAdapter(sdk);
const listener = jest.fn();

adapter.onConsentStatusChange(listener);
await flushPromises();
listener.mockClear();

eventHandlers.get(OTEventName.preferenceCenterConfirmChoices)?.();
await flushPromises();

expect(listener).toHaveBeenCalledWith(false);
});

it('should emit consent status when OneTrust rejects all in the preference center', async () => {
const { sdk, eventHandlers } = createMockSdk({
getConsentStatusForCategory: jest.fn().mockResolvedValue(0),
});
const adapter = await createOnetrustCmpAdapter(sdk);
const listener = jest.fn();

adapter.onConsentStatusChange(listener);
await flushPromises();
listener.mockClear();

eventHandlers.get(OTEventName.preferenceCenterRejectAll)?.();
await flushPromises();

expect(listener).toHaveBeenCalledWith(false);
});

it('should not emit consent status for close-only OneTrust events', async () => {
const { sdk, eventHandlers } = createMockSdk();
const adapter = await createOnetrustCmpAdapter(sdk);
const listener = jest.fn();

adapter.onConsentStatusChange(listener);
await flushPromises();
listener.mockClear();

eventHandlers.get(OTEventName.hidePreferenceCenter)?.();
await flushPromises();

expect(listener).not.toHaveBeenCalled();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@ import type { CmpAdapter } from '@contentpass/react-native-contentpass';
import type { BannerData, PreferenceCenterData } from './types';
import { getTcfPurposes } from './purposes';

const CONSENT_CHANGE_EVENTS = new Set<OTEventName>([
OTEventName.preferenceCenterAcceptAll,
OTEventName.preferenceCenterRejectAll,
OTEventName.preferenceCenterConfirmChoices,
OTEventName.vendorConfirmChoices,
]);

export async function createOnetrustCmpAdapter(
sdk: OTPublishersNativeSDK
): Promise<OnetrustCmpAdapter> {
Expand Down Expand Up @@ -148,6 +155,10 @@ export default class OnetrustCmpAdapter implements CmpAdapter {
}

private emitEvent(eventName: OTEventName, data?: any): void {
if (CONSENT_CHANGE_EVENTS.has(eventName)) {
this.emitCurrentConsentStatus();
}

this.eventListeners.forEach((listener) => {
try {
listener(eventName, data);
Expand All @@ -157,6 +168,17 @@ export default class OnetrustCmpAdapter implements CmpAdapter {
});
}

private emitCurrentConsentStatus(): void {
this.hasFullConsent()
.then((fullConsent) => this.emitConsentStatusChange(fullConsent))
.catch((error) => {
console.error(
'[OnetrustCmpAdapter::emitCurrentConsentStatus] failed',
error
);
});
}

private emitConsentStatusChange(fullConsent: boolean): void {
this.consentStatusChangeListeners.forEach((listener) =>
this.emitConsentStatusChangeEventSingle(fullConsent, listener)
Expand Down
6 changes: 6 additions & 0 deletions packages/react-native-contentpass-ui/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# @contentpass/react-native-contentpass-ui

## 0.6.0

### Minor Changes

- Improve OneTrust integration and handling of first layer links

## 0.5.0

### Minor Changes
Expand Down
2 changes: 1 addition & 1 deletion packages/react-native-contentpass-ui/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@contentpass/react-native-contentpass-ui",
"version": "0.5.0",
"version": "0.6.0",
"description": "Contentpass React Native UI Components",
"source": "./src/index.tsx",
"main": "./lib/commonjs/index.js",
Expand Down
Loading
Loading