Skip to content

Commit

Permalink
Merge pull request #6 from SourcePointUSA/DIA-4063_authenticated_consent
Browse files Browse the repository at this point in the history
DIA-4063 implement authenticated consent
  • Loading branch information
andresilveirah authored May 31, 2024
2 parents f5f69d8 + 6d4c2c2 commit 1d7d91e
Show file tree
Hide file tree
Showing 16 changed files with 135 additions and 32 deletions.
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,13 @@ export default function App() {
)
```
## Implementing authenticated consent
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.
In order to use the authenticated consent all you need to do is replace `.loadMessage()` with `.loadMessage({ authId: "JohnDoe"}))`.
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.
## Complete App examples
Complete app examples for iOS and Android can be found in the [`/example`](/example/) folder of the SDK.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package com.sourcepoint.reactnativecmp

import android.view.View
import com.facebook.react.bridge.Arguments.createArray
import com.facebook.react.bridge.Arguments.createMap
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.ReactMethod
Expand All @@ -24,6 +23,10 @@ import com.sourcepoint.cmplibrary.util.userConsents
import com.sourcepoint.reactnativecmp.consents.RNSPUserData
import org.json.JSONObject

data class SPLoadMessageParams(val authId: String?) {
constructor(fromReadableMap: ReadableMap?) : this(authId = fromReadableMap?.getString("authId"))
}

class RNSourcepointCmpModule internal constructor(context: ReactApplicationContext) :
RNSourcepointCmpSpec(context) , SpClient {
enum class SDKEvent {
Expand Down Expand Up @@ -68,8 +71,13 @@ class RNSourcepointCmpModule internal constructor(context: ReactApplicationConte
}

@ReactMethod
override fun loadMessage() {
runOnMainThread { spConsentLib?.loadMessage(View.generateViewId()) }
override fun loadMessage(params: ReadableMap?) {
val parsedParams = SPLoadMessageParams(fromReadableMap = params)

runOnMainThread { spConsentLib?.loadMessage(
authId = parsedParams.authId,
cmpViewId = View.generateViewId()
) }
}

@ReactMethod
Expand Down
2 changes: 1 addition & 1 deletion android/src/oldarch/RNSourcepointCmpSpec.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ abstract class RNSourcepointCmpSpec internal constructor(context: ReactApplicati
ReactContextBaseJavaModule(context) {

abstract fun build(accountId: Int, propertyId: Int, propertyName: String, campaigns: ReadableMap)
abstract fun loadMessage()
abstract fun loadMessage(params: ReadableMap?)
abstract fun clearLocalData()
abstract fun getUserData(promise: Promise)
abstract fun loadGDPRPrivacyManager(pmId: String)
Expand Down
27 changes: 22 additions & 5 deletions e2e/full.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import { web, element, by, expect, waitFor, device } from 'detox';
import { v4 as uuid } from 'uuid';

import type { LaunchArgs } from '../example/src/LaunchArgs';

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

const launchApp = async (launchArgs = {}) =>
const launchApp = async (launchArgs: LaunchArgs = {}) =>
device.launchApp({
newInstance: true,
launchArgs: { clearData: true, ...launchArgs },
});

beforeEach(async () => {
await launchApp();
});

describe('SourcepointSDK', () => {
it('Accepting All, works', async () => {
await launchApp();
await app.acceptAll(); // GDPR
await app.acceptAll(); // USNAT
await app.forSDKToBeFinished();
Expand All @@ -105,11 +105,28 @@ describe('SourcepointSDK', () => {
});

it('Rejecting All, works', async () => {
await launchApp();
await app.rejectAll(); // GDPR
await app.rejectAll(); // USNAT
await app.forSDKToBeFinished();
await assertUUIDsDontChangeAfterReloadingMessages();
await expect(app.gdprConsentStatusLabel).toHaveText('rejectedAll');
await expect(app.usnatConsentStatusLabel).toHaveText('rejectedAll');
});

describe('authenticated consent', () => {
describe('when authId is new', () => {
it('calling loadMessages shows a message', async () => {
await launchApp({ authId: `rn-e2e-test-${uuid()}` });
await app.forSDKToBePresenting();
});
});

describe('when authId has consented before', () => {
it('calling loadMessages does not show a message', async () => {
await launchApp({ authId: 'rn-automated-test-accept-all' });
await app.forSDKToBeFinished();
});
});
});
});
4 changes: 2 additions & 2 deletions example/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1169,7 +1169,7 @@ PODS:
- React-perflogger (= 0.74.1)
- React-utils (= 0.74.1)
- SocketRocket (0.7.0)
- sourcepoint-react-native-cmp (0.0.2):
- sourcepoint-react-native-cmp (0.2.0):
- ConsentViewController (= 7.6.7)
- DoubleConversion
- glog
Expand Down Expand Up @@ -1430,7 +1430,7 @@ SPEC CHECKSUMS:
React-utils: 3285151c9d1e3a28a9586571fc81d521678c196d
ReactCommon: f42444e384d82ab89184aed5d6f3142748b54768
SocketRocket: abac6f5de4d4d62d24e11868d7a2f427e0ef940d
sourcepoint-react-native-cmp: 4ab4090e7167aec98fba9ee3ad2f88612b17c469
sourcepoint-react-native-cmp: ec9bb56628c019641bddac75ca37b5b071a5bef2
Yoga: b9a182ab00cf25926e7f79657d08c5d23c2d03b0

PODFILE CHECKSUM: 2a400e13ba47ff4014982e7ee6044d16cb97b3a2
Expand Down
39 changes: 33 additions & 6 deletions example/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
import React, { useState, useEffect, useCallback, useRef } from 'react';
import { View, Text, SafeAreaView, Button, StyleSheet } from 'react-native';
import {
View,
Text,
SafeAreaView,
Button,
StyleSheet,
TextInput,
} from 'react-native';
import { LaunchArguments } from 'react-native-launch-arguments';

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

useEffect(() => {
Expand Down Expand Up @@ -75,7 +83,7 @@ export default function App() {

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

consentManager.current?.loadMessage();
consentManager.current?.loadMessage({ authId });

setSDKStatus(SDKStatus.Networking);

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

Check warning on line 93 in example/src/App.tsx

View workflow job for this annotation

GitHub Actions / lint

React Hook useEffect has a missing dependency: 'authId'. Either include it or remove the dependency array

const onLoadMessagePress = useCallback(() => {
consentManager.current?.loadMessage();
consentManager.current?.loadMessage({ authId });
setSDKStatus(SDKStatus.Networking);
}, []);
}, [authId]);

const onGDPRPMPress = useCallback(() => {
setSDKStatus(SDKStatus.Networking);
Expand All @@ -111,8 +119,18 @@ export default function App() {
<SafeAreaView style={styles.container}>
<View>
<Text style={styles.title}>Sourcepoint CMP</Text>
<TextInput
value={authId}
placeholder="(optional) authId"
onChangeText={setAuthId}
style={styles.authIdInput}
autoCapitalize="none"
autoCorrect={false}
autoComplete="off"
clearButtonMode="always"
/>
<Button
title="Load Messages"
title={authId ? `Load Messages (${authId})` : 'Load Messages'}
onPress={onLoadMessagePress}
disabled={disable}
/>
Expand All @@ -131,7 +149,7 @@ export default function App() {
{sdkStatus}
</Text>
</View>
<UserDataView data={userData} />
<UserDataView data={userData} authId={authId} />
</SafeAreaView>
);
}
Expand All @@ -146,4 +164,13 @@ const styles = StyleSheet.create({
textAlign: 'center',
color: '#999',
},
authIdInput: {
marginVertical: 12,
marginHorizontal: 'auto',
width: '70%',
padding: 8,
fontSize: 18,
textAlign: 'center',
borderWidth: 1,
},
});
3 changes: 2 additions & 1 deletion example/src/LaunchArgs.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import type { SPCampaigns } from '@sourcepoint/react-native-cmp';

export type LaunchArgs = {
config: {
config?: {
accountId?: number;
propertyId?: number;
propertyName?: string;
gdprPMId?: string;
usnatPMId?: string;
campaigns?: SPCampaigns;
};
authId?: string;
clearData?: boolean;
};
2 changes: 1 addition & 1 deletion example/src/TestableText.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import * as React from 'react';
import { Text, StyleSheet } from 'react-native';

export type TestableTextProps = {
testID: string | undefined;
testID?: string;
} & React.PropsWithChildren;

export const TestableText = ({ testID, children }: TestableTextProps) => (
Expand Down
7 changes: 5 additions & 2 deletions example/src/UserDataView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ import { ScrollView, View, Text, StyleSheet } from 'react-native';
import { TestableText } from './TestableText';
import type { SPUserData } from '@sourcepoint/react-native-cmp';

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

type UserDataViewProps = {
data: SPUserData;
authId?: string;
};

const styles = StyleSheet.create({
Expand Down
12 changes: 12 additions & 0 deletions ios/RCTConvert+Types.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,14 @@ import Foundation
import React
import ConsentViewController

@objcMembers class SPLoadMessageParams: NSObject {
let authId: String?

init(authId: String?) {
self.authId = authId
}
}

extension RCTConvert {
@objc static func SPCampaignEnv(_ envString: String?) -> ConsentViewController.SPCampaignEnv {
switch envString {
Expand All @@ -34,4 +42,8 @@ extension RCTConvert {
environment: SPCampaignEnv(json["environment"] as? String)
)
}

@objc static func SPLoadMessageParams(_ json: NSDictionary) -> SPLoadMessageParams {
sourcepoint_react_native_cmp.SPLoadMessageParams(authId: json["authId"] as? String)
}
}
2 changes: 1 addition & 1 deletion ios/RNSourcepointCmp.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

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

RCT_EXTERN_METHOD(loadMessage)
RCT_EXTERN_METHOD(loadMessage: (SPLoadMessageParams *)params)
RCT_EXTERN_METHOD(clearLocalData)
RCT_EXTERN_METHOD(loadGDPRPrivacyManager:(NSString *)pmId)
RCT_EXTERN_METHOD(loadUSNatPrivacyManager:(NSString *)pmId)
Expand Down
5 changes: 3 additions & 2 deletions ios/RNSourcepointCmp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,9 @@ import React
RNSourcepointCmp.shared?.consentManager = manager
}

func loadMessage() {
consentManager?.loadMessage(forAuthId: nil, pubData: nil)
func loadMessage(_ params: SPLoadMessageParams) {
print("calling loadMessage with: ", params.authId as Any)
consentManager?.loadMessage(forAuthId: params.authId, pubData: nil)
}

// TODO: fix an issue with `SPConsentManager.clearAllData` returning in-memory data
Expand Down
13 changes: 9 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@
"@release-it/conventional-changelog": "^5.0.0",
"@types/jest": "^29.5.5",
"@types/react": "^18.2.44",
"@types/uuid": "^9.0.8",
"commitlint": "^17.0.2",
"del-cli": "^5.1.0",
"detox": "^20.20.3",
Expand All @@ -84,7 +85,8 @@
"react-native-builder-bob": "^0.23.2",
"release-it": "^15.0.0",
"turbo": "^1.13.3",
"typescript": "^5.2.2"
"typescript": "^5.2.2",
"uuid": "^9.0.1"
},
"resolutions": {
"@types/react": "^18.2.44"
Expand Down Expand Up @@ -143,14 +145,17 @@
"trailingComma": "es5",
"useTabs": false
}
]
],
"react-hooks/exhaustive-deps": "warn"
}
},
"eslintIgnore": [
"node_modules/",
"lib/",
"example/android/app/build",
"example/ios/Pods"
"android/build/",
"example/android/app/build/",
"example/ios/Pods/",
"example/ios/build/"
],
"prettier": {
"quoteProps": "consistent",
Expand Down
6 changes: 3 additions & 3 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { NativeModules, Platform, NativeEventEmitter } from 'react-native';
import type { Spec, SPCampaigns, SPUserData } from './types';
import type { Spec, SPCampaigns, SPUserData, LoadMessageParams } from './types';

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

loadMessage() {
RNSourcepointCmp.loadMessage();
loadMessage(params?: LoadMessageParams) {
RNSourcepointCmp.loadMessage(params);
}

clearLocalData() {
Expand Down
6 changes: 5 additions & 1 deletion src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,10 @@ export type SPUserData = {
usnat?: CampaignConsent<USNatConsent>;
};

export type LoadMessageParams = {
authId?: string;
};

export interface Spec extends TurboModule {
build(
accountId: number,
Expand All @@ -94,7 +98,7 @@ export interface Spec extends TurboModule {
campaigns: SPCampaigns
): void;
getUserData(): Promise<SPUserData>;
loadMessage(): void;
loadMessage(params?: LoadMessageParams): void;
clearLocalData(): void;
loadGDPRPrivacyManager(pmId: string): void;
loadUSNatPrivacyManager(pmId: string): void;
Expand Down
Loading

0 comments on commit 1d7d91e

Please sign in to comment.