Skip to content

Commit 08b0cd7

Browse files
committed
feat: add count impressions functionality
1 parent 51ae53c commit 08b0cd7

16 files changed

+150
-5
lines changed

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,7 @@
147147
"version": "0.44.1"
148148
},
149149
"dependencies": {
150-
"@sentry/react-native": "^6.3.0"
150+
"@sentry/react-native": "^6.3.0",
151+
"react-native-uuid": "^2.0.3"
151152
}
152153
}

sharedExample/src/ContentpassUsage.tsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,13 +87,23 @@ export default function ContentpassUsage() {
8787
spConsentManager.current?.loadMessage();
8888
};
8989

90+
const countImpression = async () => {
91+
try {
92+
await contentpassSdk.countImpression();
93+
} catch (err) {
94+
// eslint-disable-next-line no-console
95+
console.error('Failed to count impression', err);
96+
}
97+
};
98+
9099
return (
91100
<>
92101
<View style={styles.buttonsContainer}>
93102
<Button
94103
title={'Clear sourcepoint data'}
95104
onPress={clearSourcepointData}
96105
/>
106+
<Button title={'Count impression'} onPress={countImpression} />
97107
<Button title={'Logout'} onPress={contentpassSdk.logout} />
98108
</View>
99109
<View style={styles.logsView}>

sharedExample/src/contentpassConfig.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,12 @@ export const contentpassConfig: ContentpassConfig = {
44
// Testing app
55
propertyId: 'cc3fc4ad-cbe5-4d09-bf85-a49796603b19',
66
issuer: 'https://my.contentpass.dev',
7+
apiUrl: 'https://cp.cmp-sourcepoint.contenttimes.dev',
78
// Staging app
89
// propertyId: '78da2fd3-8b25-4642-b7b7-4a0193d00f89',
910
// issuer: 'https://my.contentpass.io',
11+
// apiUrl: 'cp.cmp-sourcepoint.contenttimes.io',
1012

13+
samplingRate: 1,
1114
redirectUrl: 'de.contentpass.demo://oauth',
1215
};

src/Contentpass.test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,15 @@ import * as AppAuthModule from 'react-native-app-auth';
55
import * as OidcAuthStateStorageModule from './OidcAuthStateStorage';
66
import type { ContentpassState } from './types/ContentpassState';
77
import OidcAuthStateStorage from './OidcAuthStateStorage';
8-
import * as FetchContentpassTokenModule from './utils/fetchContentpassToken';
8+
import * as FetchContentpassTokenModule from './contentpassTokenUtils/fetchContentpassToken';
99
import { SCOPES } from './consts/oidcConsts';
1010
import * as SentryIntegrationModule from './sentryIntegration';
1111

1212
const config: ContentpassConfig = {
1313
propertyId: 'propertyId-1',
1414
redirectUrl: 'de.test.net://oauth',
1515
issuer: 'https://issuer.net',
16+
apiUrl: 'https://cp.property.com',
1617
};
1718

1819
const NOW = new Date('2024-12-02T11:53:56.272Z').getTime();

src/Contentpass.ts

Lines changed: 74 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import uuid from 'react-native-uuid';
12
import OidcAuthStateStorage, {
23
type OidcAuthState,
34
} from './OidcAuthStateStorage';
@@ -12,16 +13,31 @@ import {
1213
} from 'react-native-app-auth';
1314
import { REFRESH_TOKEN_RETRIES, SCOPES } from './consts/oidcConsts';
1415
import { RefreshTokenStrategy } from './types/RefreshTokenStrategy';
15-
import fetchContentpassToken from './utils/fetchContentpassToken';
16-
import validateSubscription from './utils/validateSubscription';
16+
import fetchContentpassToken from './contentpassTokenUtils/fetchContentpassToken';
17+
import validateSubscription from './contentpassTokenUtils/validateSubscription';
1718
import type { ContentpassConfig } from './types/ContentpassConfig';
1819
import { reportError, setSentryExtraAttribute } from './sentryIntegration';
20+
import sendStats from './countImpressionUtils/sendStats';
21+
import sendPageViewEvent from './countImpressionUtils/sendPageViewEvent';
22+
23+
const DEFAULT_FREE_IMPRESSIONS_SAMPLING_RATE = 0.01;
1924

2025
export type ContentpassObserver = (state: ContentpassState) => void;
2126

22-
export default class Contentpass {
27+
interface ContentpassInterface {
28+
authenticate: () => Promise<void>;
29+
registerObserver: (observer: ContentpassObserver) => void;
30+
unregisterObserver: (observer: ContentpassObserver) => void;
31+
logout: () => Promise<void>;
32+
recoverFromError: () => Promise<void>;
33+
countImpression: () => Promise<void>;
34+
}
35+
36+
export default class Contentpass implements ContentpassInterface {
2337
private authStateStorage: OidcAuthStateStorage;
2438
private readonly config: ContentpassConfig;
39+
private readonly samplingRate: number;
40+
private instanceId: string;
2541

2642
private contentpassState: ContentpassState = {
2743
state: ContentpassStateType.INITIALISING,
@@ -31,6 +47,15 @@ export default class Contentpass {
3147
private refreshTimer: NodeJS.Timeout | null = null;
3248

3349
constructor(config: ContentpassConfig) {
50+
if (
51+
config.samplingRate &&
52+
(config.samplingRate < 0 || config.samplingRate > 1)
53+
) {
54+
throw new Error('Sampling rate must be between 0 and 1');
55+
}
56+
this.samplingRate =
57+
config.samplingRate || DEFAULT_FREE_IMPRESSIONS_SAMPLING_RATE;
58+
this.instanceId = uuid.v4();
3459
this.authStateStorage = new OidcAuthStateStorage(config.propertyId);
3560
this.config = config;
3661
setSentryExtraAttribute('propertyId', config.propertyId);
@@ -96,6 +121,52 @@ export default class Contentpass {
96121
await this.initialiseAuthState();
97122
};
98123

124+
public countImpression = async () => {
125+
if (this.oidcAuthState?.accessToken) {
126+
await this.countPaidImpression();
127+
}
128+
129+
// always count free impression even if user is authenticated
130+
await this.countFreeImpression();
131+
};
132+
133+
private countPaidImpression = async () => {
134+
const impressionId = uuid.v4();
135+
136+
try {
137+
await sendPageViewEvent(this.config.apiUrl, {
138+
propertyId: this.config.propertyId,
139+
impressionId,
140+
accessToken: this.oidcAuthState!.accessToken,
141+
});
142+
} catch (err: any) {
143+
reportError(err, { msg: 'Failed to count impression paid impression' });
144+
throw err;
145+
}
146+
};
147+
148+
private countFreeImpression = async () => {
149+
const generatedSample = Math.random();
150+
const publicId = this.config.propertyId.slice(0, 8);
151+
152+
if (generatedSample >= this.samplingRate) {
153+
return;
154+
}
155+
156+
try {
157+
await sendStats(this.config.apiUrl, {
158+
ea: 'load',
159+
ec: 'tcf-sampled',
160+
cpabid: this.instanceId,
161+
cppid: publicId,
162+
cpsr: this.samplingRate,
163+
});
164+
} catch (err: any) {
165+
reportError(err, { msg: 'Failed to count impression paid impression' });
166+
throw err;
167+
}
168+
};
169+
99170
private initialiseAuthState = async () => {
100171
const authState = await this.authStateStorage.getOidcAuthState();
101172
if (authState) {
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.

0 commit comments

Comments
 (0)