Skip to content

Commit 1d7d91e

Browse files
Merge pull request #6 from SourcePointUSA/DIA-4063_authenticated_consent
DIA-4063 implement authenticated consent
2 parents f5f69d8 + 6d4c2c2 commit 1d7d91e

16 files changed

+135
-32
lines changed

README.md

+7
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,13 @@ export default function App() {
158158
)
159159
```
160160
161+
## Implementing authenticated consent
162+
In a nutshell, you provide an identifier for the current user (username, user id, uuid or any unique string) and we'll take care of associating the consent profile to that identifier.
163+
164+
In order to use the authenticated consent all you need to do is replace `.loadMessage()` with `.loadMessage({ authId: "JohnDoe"}))`.
165+
166+
If our APIs have a consent profile associated with that token `"JohnDoe"` the SDK will bring the consent profile from the server, overwriting whatever was stored in the device. If none is found, the session will be treated as a new user.
167+
161168
## Complete App examples
162169
163170
Complete app examples for iOS and Android can be found in the [`/example`](/example/) folder of the SDK.

android/src/main/java/com/sourcepoint/reactnativecmp/RNSourcepointCmpModule.kt

+11-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package com.sourcepoint.reactnativecmp
22

33
import android.view.View
4-
import com.facebook.react.bridge.Arguments.createArray
54
import com.facebook.react.bridge.Arguments.createMap
65
import com.facebook.react.bridge.ReactApplicationContext
76
import com.facebook.react.bridge.ReactMethod
@@ -24,6 +23,10 @@ import com.sourcepoint.cmplibrary.util.userConsents
2423
import com.sourcepoint.reactnativecmp.consents.RNSPUserData
2524
import org.json.JSONObject
2625

26+
data class SPLoadMessageParams(val authId: String?) {
27+
constructor(fromReadableMap: ReadableMap?) : this(authId = fromReadableMap?.getString("authId"))
28+
}
29+
2730
class RNSourcepointCmpModule internal constructor(context: ReactApplicationContext) :
2831
RNSourcepointCmpSpec(context) , SpClient {
2932
enum class SDKEvent {
@@ -68,8 +71,13 @@ class RNSourcepointCmpModule internal constructor(context: ReactApplicationConte
6871
}
6972

7073
@ReactMethod
71-
override fun loadMessage() {
72-
runOnMainThread { spConsentLib?.loadMessage(View.generateViewId()) }
74+
override fun loadMessage(params: ReadableMap?) {
75+
val parsedParams = SPLoadMessageParams(fromReadableMap = params)
76+
77+
runOnMainThread { spConsentLib?.loadMessage(
78+
authId = parsedParams.authId,
79+
cmpViewId = View.generateViewId()
80+
) }
7381
}
7482

7583
@ReactMethod

android/src/oldarch/RNSourcepointCmpSpec.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ abstract class RNSourcepointCmpSpec internal constructor(context: ReactApplicati
99
ReactContextBaseJavaModule(context) {
1010

1111
abstract fun build(accountId: Int, propertyId: Int, propertyName: String, campaigns: ReadableMap)
12-
abstract fun loadMessage()
12+
abstract fun loadMessage(params: ReadableMap?)
1313
abstract fun clearLocalData()
1414
abstract fun getUserData(promise: Promise)
1515
abstract fun loadGDPRPrivacyManager(pmId: String)

e2e/full.test.ts

+22-5
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
import { web, element, by, expect, waitFor, device } from 'detox';
2+
import { v4 as uuid } from 'uuid';
3+
4+
import type { LaunchArgs } from '../example/src/LaunchArgs';
25

36
export const sleep = async (milliseconds: number) =>
47
new Promise((resolve) => setTimeout(resolve, milliseconds));
@@ -84,18 +87,15 @@ const assertUUIDsDontChangeAfterReloadingMessages = async () => {
8487
await expect(app.usnatUUIDLabel).toHaveText(usnatUUIDBeforeReloading);
8588
};
8689

87-
const launchApp = async (launchArgs = {}) =>
90+
const launchApp = async (launchArgs: LaunchArgs = {}) =>
8891
device.launchApp({
8992
newInstance: true,
9093
launchArgs: { clearData: true, ...launchArgs },
9194
});
9295

93-
beforeEach(async () => {
94-
await launchApp();
95-
});
96-
9796
describe('SourcepointSDK', () => {
9897
it('Accepting All, works', async () => {
98+
await launchApp();
9999
await app.acceptAll(); // GDPR
100100
await app.acceptAll(); // USNAT
101101
await app.forSDKToBeFinished();
@@ -105,11 +105,28 @@ describe('SourcepointSDK', () => {
105105
});
106106

107107
it('Rejecting All, works', async () => {
108+
await launchApp();
108109
await app.rejectAll(); // GDPR
109110
await app.rejectAll(); // USNAT
110111
await app.forSDKToBeFinished();
111112
await assertUUIDsDontChangeAfterReloadingMessages();
112113
await expect(app.gdprConsentStatusLabel).toHaveText('rejectedAll');
113114
await expect(app.usnatConsentStatusLabel).toHaveText('rejectedAll');
114115
});
116+
117+
describe('authenticated consent', () => {
118+
describe('when authId is new', () => {
119+
it('calling loadMessages shows a message', async () => {
120+
await launchApp({ authId: `rn-e2e-test-${uuid()}` });
121+
await app.forSDKToBePresenting();
122+
});
123+
});
124+
125+
describe('when authId has consented before', () => {
126+
it('calling loadMessages does not show a message', async () => {
127+
await launchApp({ authId: 'rn-automated-test-accept-all' });
128+
await app.forSDKToBeFinished();
129+
});
130+
});
131+
});
115132
});

example/ios/Podfile.lock

+2-2
Original file line numberDiff line numberDiff line change
@@ -1169,7 +1169,7 @@ PODS:
11691169
- React-perflogger (= 0.74.1)
11701170
- React-utils (= 0.74.1)
11711171
- SocketRocket (0.7.0)
1172-
- sourcepoint-react-native-cmp (0.0.2):
1172+
- sourcepoint-react-native-cmp (0.2.0):
11731173
- ConsentViewController (= 7.6.7)
11741174
- DoubleConversion
11751175
- glog
@@ -1430,7 +1430,7 @@ SPEC CHECKSUMS:
14301430
React-utils: 3285151c9d1e3a28a9586571fc81d521678c196d
14311431
ReactCommon: f42444e384d82ab89184aed5d6f3142748b54768
14321432
SocketRocket: abac6f5de4d4d62d24e11868d7a2f427e0ef940d
1433-
sourcepoint-react-native-cmp: 4ab4090e7167aec98fba9ee3ad2f88612b17c469
1433+
sourcepoint-react-native-cmp: ec9bb56628c019641bddac75ca37b5b071a5bef2
14341434
Yoga: b9a182ab00cf25926e7f79657d08c5d23c2d03b0
14351435

14361436
PODFILE CHECKSUM: 2a400e13ba47ff4014982e7ee6044d16cb97b3a2

example/src/App.tsx

+33-6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
import React, { useState, useEffect, useCallback, useRef } from 'react';
2-
import { View, Text, SafeAreaView, Button, StyleSheet } from 'react-native';
2+
import {
3+
View,
4+
Text,
5+
SafeAreaView,
6+
Button,
7+
StyleSheet,
8+
TextInput,
9+
} from 'react-native';
310
import { LaunchArguments } from 'react-native-launch-arguments';
411

512
import {
@@ -38,6 +45,7 @@ const config = {
3845
export default function App() {
3946
const [userData, setUserData] = useState<SPUserData>({});
4047
const [sdkStatus, setSDKStatus] = useState<SDKStatus>(SDKStatus.NotStarted);
48+
const [authId, setAuthId] = useState<string | undefined>(launchArgs.authId);
4149
const consentManager = useRef<SPConsentManager | null>();
4250

4351
useEffect(() => {
@@ -75,7 +83,7 @@ export default function App() {
7583

7684
consentManager.current?.getUserData().then(setUserData);
7785

78-
consentManager.current?.loadMessage();
86+
consentManager.current?.loadMessage({ authId });
7987

8088
setSDKStatus(SDKStatus.Networking);
8189

@@ -85,9 +93,9 @@ export default function App() {
8593
}, []);
8694

8795
const onLoadMessagePress = useCallback(() => {
88-
consentManager.current?.loadMessage();
96+
consentManager.current?.loadMessage({ authId });
8997
setSDKStatus(SDKStatus.Networking);
90-
}, []);
98+
}, [authId]);
9199

92100
const onGDPRPMPress = useCallback(() => {
93101
setSDKStatus(SDKStatus.Networking);
@@ -111,8 +119,18 @@ export default function App() {
111119
<SafeAreaView style={styles.container}>
112120
<View>
113121
<Text style={styles.title}>Sourcepoint CMP</Text>
122+
<TextInput
123+
value={authId}
124+
placeholder="(optional) authId"
125+
onChangeText={setAuthId}
126+
style={styles.authIdInput}
127+
autoCapitalize="none"
128+
autoCorrect={false}
129+
autoComplete="off"
130+
clearButtonMode="always"
131+
/>
114132
<Button
115-
title="Load Messages"
133+
title={authId ? `Load Messages (${authId})` : 'Load Messages'}
116134
onPress={onLoadMessagePress}
117135
disabled={disable}
118136
/>
@@ -131,7 +149,7 @@ export default function App() {
131149
{sdkStatus}
132150
</Text>
133151
</View>
134-
<UserDataView data={userData} />
152+
<UserDataView data={userData} authId={authId} />
135153
</SafeAreaView>
136154
);
137155
}
@@ -146,4 +164,13 @@ const styles = StyleSheet.create({
146164
textAlign: 'center',
147165
color: '#999',
148166
},
167+
authIdInput: {
168+
marginVertical: 12,
169+
marginHorizontal: 'auto',
170+
width: '70%',
171+
padding: 8,
172+
fontSize: 18,
173+
textAlign: 'center',
174+
borderWidth: 1,
175+
},
149176
});

example/src/LaunchArgs.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
import type { SPCampaigns } from '@sourcepoint/react-native-cmp';
22

33
export type LaunchArgs = {
4-
config: {
4+
config?: {
55
accountId?: number;
66
propertyId?: number;
77
propertyName?: string;
88
gdprPMId?: string;
99
usnatPMId?: string;
1010
campaigns?: SPCampaigns;
1111
};
12+
authId?: string;
1213
clearData?: boolean;
1314
};

example/src/TestableText.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import * as React from 'react';
22
import { Text, StyleSheet } from 'react-native';
33

44
export type TestableTextProps = {
5-
testID: string | undefined;
5+
testID?: string;
66
} & React.PropsWithChildren;
77

88
export const TestableText = ({ testID, children }: TestableTextProps) => (

example/src/UserDataView.tsx

+5-2
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@ import { ScrollView, View, Text, StyleSheet } from 'react-native';
44
import { TestableText } from './TestableText';
55
import type { SPUserData } from '@sourcepoint/react-native-cmp';
66

7-
export default ({ data }: UserDataViewProps) => (
7+
export default ({ data, authId }: UserDataViewProps) => (
88
<View style={styles.container}>
9-
<Text style={styles.header}>Local User Data</Text>
9+
<Text style={styles.header}>
10+
{authId ? `User Data (${authId})` : `User Data`}
11+
</Text>
1012
<TestableText testID="gdpr.uuid">{data?.gdpr?.consents?.uuid}</TestableText>
1113
<TestableText testID="gdpr.consentStatus">
1214
{data?.gdpr?.consents?.statuses?.consentedAll
@@ -31,6 +33,7 @@ export default ({ data }: UserDataViewProps) => (
3133

3234
type UserDataViewProps = {
3335
data: SPUserData;
36+
authId?: string;
3437
};
3538

3639
const styles = StyleSheet.create({

ios/RCTConvert+Types.swift

+12
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,14 @@ import Foundation
99
import React
1010
import ConsentViewController
1111

12+
@objcMembers class SPLoadMessageParams: NSObject {
13+
let authId: String?
14+
15+
init(authId: String?) {
16+
self.authId = authId
17+
}
18+
}
19+
1220
extension RCTConvert {
1321
@objc static func SPCampaignEnv(_ envString: String?) -> ConsentViewController.SPCampaignEnv {
1422
switch envString {
@@ -34,4 +42,8 @@ extension RCTConvert {
3442
environment: SPCampaignEnv(json["environment"] as? String)
3543
)
3644
}
45+
46+
@objc static func SPLoadMessageParams(_ json: NSDictionary) -> SPLoadMessageParams {
47+
sourcepoint_react_native_cmp.SPLoadMessageParams(authId: json["authId"] as? String)
48+
}
3749
}

ios/RNSourcepointCmp.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
RCT_EXTERN_METHOD(build:(int)accountId propertyId:(int)propertyId propertyName:(NSString *)propertyName campaigns:(SPCampaigns*)campaigns)
1313

14-
RCT_EXTERN_METHOD(loadMessage)
14+
RCT_EXTERN_METHOD(loadMessage: (SPLoadMessageParams *)params)
1515
RCT_EXTERN_METHOD(clearLocalData)
1616
RCT_EXTERN_METHOD(loadGDPRPrivacyManager:(NSString *)pmId)
1717
RCT_EXTERN_METHOD(loadUSNatPrivacyManager:(NSString *)pmId)

ios/RNSourcepointCmp.swift

+3-2
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,9 @@ import React
4040
RNSourcepointCmp.shared?.consentManager = manager
4141
}
4242

43-
func loadMessage() {
44-
consentManager?.loadMessage(forAuthId: nil, pubData: nil)
43+
func loadMessage(_ params: SPLoadMessageParams) {
44+
print("calling loadMessage with: ", params.authId as Any)
45+
consentManager?.loadMessage(forAuthId: params.authId, pubData: nil)
4546
}
4647

4748
// TODO: fix an issue with `SPConsentManager.clearAllData` returning in-memory data

package.json

+9-4
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@
7070
"@release-it/conventional-changelog": "^5.0.0",
7171
"@types/jest": "^29.5.5",
7272
"@types/react": "^18.2.44",
73+
"@types/uuid": "^9.0.8",
7374
"commitlint": "^17.0.2",
7475
"del-cli": "^5.1.0",
7576
"detox": "^20.20.3",
@@ -84,7 +85,8 @@
8485
"react-native-builder-bob": "^0.23.2",
8586
"release-it": "^15.0.0",
8687
"turbo": "^1.13.3",
87-
"typescript": "^5.2.2"
88+
"typescript": "^5.2.2",
89+
"uuid": "^9.0.1"
8890
},
8991
"resolutions": {
9092
"@types/react": "^18.2.44"
@@ -143,14 +145,17 @@
143145
"trailingComma": "es5",
144146
"useTabs": false
145147
}
146-
]
148+
],
149+
"react-hooks/exhaustive-deps": "warn"
147150
}
148151
},
149152
"eslintIgnore": [
150153
"node_modules/",
151154
"lib/",
152-
"example/android/app/build",
153-
"example/ios/Pods"
155+
"android/build/",
156+
"example/android/app/build/",
157+
"example/ios/Pods/",
158+
"example/ios/build/"
154159
],
155160
"prettier": {
156161
"quoteProps": "consistent",

src/index.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { NativeModules, Platform, NativeEventEmitter } from 'react-native';
2-
import type { Spec, SPCampaigns, SPUserData } from './types';
2+
import type { Spec, SPCampaigns, SPUserData, LoadMessageParams } from './types';
33

44
const LINKING_ERROR =
55
`The package '@sourcepoint/react-native-cmp' doesn't seem to be linked. Make sure: \n\n` +
@@ -45,8 +45,8 @@ export class SPConsentManager implements Spec {
4545
return RNSourcepointCmp.getUserData();
4646
}
4747

48-
loadMessage() {
49-
RNSourcepointCmp.loadMessage();
48+
loadMessage(params?: LoadMessageParams) {
49+
RNSourcepointCmp.loadMessage(params);
5050
}
5151

5252
clearLocalData() {

src/types.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,10 @@ export type SPUserData = {
8686
usnat?: CampaignConsent<USNatConsent>;
8787
};
8888

89+
export type LoadMessageParams = {
90+
authId?: string;
91+
};
92+
8993
export interface Spec extends TurboModule {
9094
build(
9195
accountId: number,
@@ -94,7 +98,7 @@ export interface Spec extends TurboModule {
9498
campaigns: SPCampaigns
9599
): void;
96100
getUserData(): Promise<SPUserData>;
97-
loadMessage(): void;
101+
loadMessage(params?: LoadMessageParams): void;
98102
clearLocalData(): void;
99103
loadGDPRPrivacyManager(pmId: string): void;
100104
loadUSNatPrivacyManager(pmId: string): void;

0 commit comments

Comments
 (0)