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
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ export default function ContentpassConsentGate({
<ContentpassLayer
eventHandler={layerEvents}
baseUrl={contentpassConfig.apiUrl}
instanceId={sdk.instanceId}
planId={contentpassConfig.planId}
propertyId={contentpassConfig.propertyId}
purposesList={purposesList}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
import { ActivityIndicator, StyleSheet, Text, View } from 'react-native';
import {
ActivityIndicator,
Modal,
Pressable,
StyleSheet,
Text,
View,
} from 'react-native';
import { WebView, type WebViewMessageEvent } from 'react-native-webview';
import type { ContentpassLayerEvents } from './ContentpassLayerEvents';
import buildFirstLayerUrl from './buildFirstLayerUrl';
import { useMemo, useState } from 'react';
import { useCallback, useMemo, useState } from 'react';

const MESSAGE_PROTOCOL = 'contentpass-first-layer';

Expand Down Expand Up @@ -52,18 +59,44 @@ const styles = StyleSheet.create({
alignItems: 'center',
justifyContent: 'center',
},
popupContainer: {
flex: 1,
backgroundColor: '#fff',
},
popupHeader: {
flexDirection: 'row',
justifyContent: 'flex-end',
paddingHorizontal: 12,
paddingVertical: 8,
borderBottomWidth: StyleSheet.hairlineWidth,
borderBottomColor: '#ccc',
},
popupClose: {
paddingHorizontal: 12,
paddingVertical: 6,
},
popupCloseText: {
fontSize: 16,
fontWeight: '600',
color: '#007AFF',
},
popupWebview: {
flex: 1,
},
});

export default function ContentpassLayer({
baseUrl,
eventHandler,
instanceId,
planId,
propertyId,
purposesList,
vendorCount,
}: {
baseUrl: string;
eventHandler: ContentpassLayerEvents;
instanceId: string;
planId: string;
propertyId: string;
purposesList: string[];
Expand All @@ -80,6 +113,13 @@ export default function ContentpassLayer({
}, [baseUrl, planId, propertyId, purposesList, vendorCount]);

const [ready, setReady] = useState(false);
const [popupUrl, setPopupUrl] = useState<string | null>(null);

const closePopup = useCallback(() => setPopupUrl(null), []);

function buildFaqUrl(): string {
return `${baseUrl}/auth/login?instanceId=${encodeURIComponent(instanceId)}&propertyId=${encodeURIComponent(propertyId)}&planId=${encodeURIComponent(planId)}&route=faq`;
}

function handleMessage(event: WebViewMessageEvent) {
let msg: any;
Expand Down Expand Up @@ -118,6 +158,19 @@ export default function ContentpassLayer({
msg.payload?.options?.page as 'login' | 'signup'
);
break;
case 'faq':
setPopupUrl(buildFaqUrl());
break;
case 'url':
if (msg.payload?.options?.url) {
setPopupUrl(msg.payload?.options?.url);
} else {
console.warn(
'WebView message with unknown URL',
msg.payload?.options?.url
);
}
break;
default:
console.warn(
'WebView message with unknown page',
Expand Down Expand Up @@ -167,8 +220,16 @@ export default function ContentpassLayer({
handleMessage(event);
}}
onShouldStartLoadWithRequest={(request) => {
console.debug('WebView request', request.url);
return true;
// Prevent accidental redirects to external URLs
const firstLayerHostname = new URL(firstLayerUrl).hostname;
const requestedHostname = new URL(request.url).hostname;
const allowed = requestedHostname === firstLayerHostname;
console.debug('WebView request', request.url, {
allowed,
firstLayerHostname,
requestedHostname,
});
return allowed;
}}
onLoadStart={() => {
console.debug('WebView load start');
Expand Down Expand Up @@ -198,6 +259,34 @@ export default function ContentpassLayer({
<ActivityIndicator size="large" />
</View>
)}
<Modal
visible={popupUrl !== null}
animationType="slide"
presentationStyle="pageSheet"
onRequestClose={closePopup}
>
<View style={styles.popupContainer}>
<View style={styles.popupHeader}>
<Pressable onPress={closePopup} style={styles.popupClose}>
<Text style={styles.popupCloseText}>Close</Text>
</Pressable>
</View>
{popupUrl && (
<WebView
source={{ uri: popupUrl }}
style={styles.popupWebview}
javaScriptEnabled
domStorageEnabled
onShouldStartLoadWithRequest={(request) => {
console.debug('WebView popup request', request.url);
// Allow any request to load in the popup, otherwise
// we would block redirects to external URLs.
return true;
}}
/>
)}
</View>
</Modal>
</View>
);
}
14 changes: 9 additions & 5 deletions packages/react-native-contentpass/src/Contentpass.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,11 @@ export default class Contentpass implements ContentpassInterface {
private authStateStorage: OidcAuthStateStorage;
private readonly config: ContentpassConfig;
private readonly samplingRate: number;
private instanceId: string;
private _instanceId: string;

get instanceId(): string {
return this._instanceId;
}

private contentpassState: ContentpassState = {
state: ContentpassStateType.INITIALISING,
Expand All @@ -59,7 +63,7 @@ export default class Contentpass implements ContentpassInterface {
}

this.config = config;
this.instanceId = uuid.v4();
this._instanceId = uuid.v4();
logger.debug('Contentpass initialised with config', config);
if (
config.samplingRate &&
Expand Down Expand Up @@ -130,7 +134,7 @@ export default class Contentpass implements ContentpassInterface {
ea: eventAction,
ec: eventCategory,
el: eventLabel,
cpabid: this.instanceId,
cpabid: this._instanceId,
cppid: publicId,
cpsr: activeSamplingRate,
};
Expand Down Expand Up @@ -180,7 +184,7 @@ export default class Contentpass implements ContentpassInterface {
public countImpression = async () => {
// Generate a new impression id to be used per impression
// This mimics the behaviour of the web SDK when in SPA mode
this.instanceId = uuid.v4();
this._instanceId = uuid.v4();
await Promise.all([
this.countPaidImpressionWhenUserHasValidSub(),
this.countSampledImpression(),
Expand All @@ -197,7 +201,7 @@ export default class Contentpass implements ContentpassInterface {
try {
await sendPageViewEvent(this.config.apiUrl, {
propertyId: this.config.propertyId,
impressionId: this.instanceId,
impressionId: this._instanceId,
accessToken: this.oidcAuthState!.accessToken,
});
} catch (err: any) {
Expand Down
Loading