From 1ad43444dc7dd8828426ea5d74b53687dc336aae Mon Sep 17 00:00:00 2001 From: Darshan Parmar Date: Wed, 9 Oct 2024 15:15:13 +0530 Subject: [PATCH 1/4] [MOB-9640] Keep AUT off until concent to track is granted --- react-example/.eslintrc | 12 ++---- react-example/src/indexWithoutJWT.tsx | 10 ++++- react-example/src/styles/index.css | 43 ++++++++++++++++--- react-example/src/views/AUTTesting.tsx | 27 +++++++++++- .../anonymousUserEventManager.ts | 27 +++++++++++- .../tests/anonymousUserEventManager.test.ts | 15 +++++-- .../tests/userMergeScenarios.test.ts | 12 +++++- .../tests/userUpdate.test.ts | 6 ++- .../validateCustomEventUserUpdateAPI.test.ts | 15 ++++++- src/authorization/authorization.ts | 36 +++++++++++++++- src/constants.ts | 1 + 11 files changed, 177 insertions(+), 27 deletions(-) diff --git a/react-example/.eslintrc b/react-example/.eslintrc index 1583a522..8193022d 100644 --- a/react-example/.eslintrc +++ b/react-example/.eslintrc @@ -1,13 +1,9 @@ { - "extends": [ - "../.eslintrc", - "plugin:react/recommended" - ], + "extends": ["../.eslintrc", "plugin:react/recommended"], "rules": { "@typescript-eslint/no-empty-interface": "off", "react/react-in-jsx-scope": "off", + "@typescript-eslint/no-explicit-any": "off" }, - "ignorePatterns": [ - "node_modules/" - ] -} \ No newline at end of file + "ignorePatterns": ["node_modules/"] +} diff --git a/react-example/src/indexWithoutJWT.tsx b/react-example/src/indexWithoutJWT.tsx index 36208eae..77ae22f2 100644 --- a/react-example/src/indexWithoutJWT.tsx +++ b/react-example/src/indexWithoutJWT.tsx @@ -55,9 +55,12 @@ const HomeLink = styled(Link)` } }; - const { setUserID, logout, setEmail } = + const { setUserID, logout, setEmail, startStopAnonymousUserTracking } = initializeWithConfig(initializeParams); + const handleConcent = (concent?: boolean) => + startStopAnonymousUserTracking(concent); + // eslint-disable-next-line react/no-deprecated ReactDOM.render( @@ -86,7 +89,10 @@ const HomeLink = styled(Link)` path="/embedded-msgs-impression-tracker" element={} /> - } /> + } + /> diff --git a/react-example/src/styles/index.css b/react-example/src/styles/index.css index 28642755..b3d49296 100644 --- a/react-example/src/styles/index.css +++ b/react-example/src/styles/index.css @@ -1,4 +1,5 @@ -html, body { +html, +body { margin: 0; padding: 0; } @@ -30,7 +31,7 @@ html, body { } #change-email-form input { - margin-top: .5em; + margin-top: 0.5em; flex-grow: 1; padding: 1em; } @@ -42,17 +43,17 @@ html, body { flex-flow: column; justify-content: center; } - + .input-wrapper { margin-right: 0; transform: translateY(0); } - + #change-email-form button { width: 100%; margin-top: 1em; } - + #change-email-form input { height: 50px; } @@ -62,4 +63,34 @@ footer { display: flex; justify-content: flex-end; align-items: flex-end; -} \ No newline at end of file +} + +#cookie-concent-container { + display: flex; + justify-content: center; + flex-direction: column; + + position: fixed; + bottom: 0; + right: 0; + + box-shadow: 0 0 5px 0 rgba(0, 0, 0, 0.2); + padding: 1em; + background: #fff; + margin: 1em; + max-width: 400px; + + h3 { + margin-top: 0; + margin-bottom: 0.5em; + } + + p { + margin-top: 0; + } + + div { + display: flex; + gap: 0.5em; + } +} diff --git a/react-example/src/views/AUTTesting.tsx b/react-example/src/views/AUTTesting.tsx index 53838c08..ca19f668 100644 --- a/react-example/src/views/AUTTesting.tsx +++ b/react-example/src/views/AUTTesting.tsx @@ -1,4 +1,5 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ +/* eslint-disable @typescript-eslint/no-unused-vars */ import { FC, FormEvent, useState } from 'react'; import { updateCart, @@ -19,9 +20,11 @@ import { Response } from './Components.styled'; -interface Props {} +interface Props { + setConcent?: (accept: boolean) => void; +} -export const AUTTesting: FC = () => { +export const AUTTesting: FC = ({ setConcent }) => { const [updateCartResponse, setUpdateCartResponse] = useState( 'Endpoint JSON goes here' ); @@ -200,6 +203,25 @@ export const AUTTesting: FC = () => { const inputAttr = { 'data-qa-track-input': true }; const responseAttr = { 'data-qa-track-response': true }; + const acceptCookie = () => setConcent(true); + + const declineCookie = () => setConcent(false); + + const renderCookieConcent = ( + + ); + return ( <>

Commerce Endpoints

@@ -274,6 +296,7 @@ export const AUTTesting: FC = () => { {trackResponse} + {renderCookieConcent} ); }; diff --git a/src/anonymousUserTracking/anonymousUserEventManager.ts b/src/anonymousUserTracking/anonymousUserEventManager.ts index fcec85d9..dbe48653 100644 --- a/src/anonymousUserTracking/anonymousUserEventManager.ts +++ b/src/anonymousUserTracking/anonymousUserEventManager.ts @@ -25,7 +25,8 @@ import { WEB_PLATFORM, KEY_PREFER_USERID, ENDPOINTS, - DEFAULT_EVENT_THRESHOLD_LIMIT + DEFAULT_EVENT_THRESHOLD_LIMIT, + SHARED_PREF_ANON_USAGE_TRACKED } from '../constants'; import { baseIterableRequest } from '../request'; import { IterableResponse } from '../types'; @@ -48,9 +49,21 @@ let anonUserIdSetter: AnonUserFunction | null = null; export function registerAnonUserIdSetter(setterFunction: AnonUserFunction) { anonUserIdSetter = setterFunction; } + +export function isAnonymousUsageTracked(): boolean { + const anonymousUsageTracked = localStorage.getItem( + SHARED_PREF_ANON_USAGE_TRACKED + ); + return anonymousUsageTracked === 'true'; +} + export class AnonymousUserEventManager { updateAnonSession() { try { + const anonymousUsageTracked = isAnonymousUsageTracked(); + + if (!anonymousUsageTracked) return; + const strAnonSessionInfo = localStorage.getItem( SHARED_PREFS_ANON_SESSIONS ); @@ -91,6 +104,10 @@ export class AnonymousUserEventManager { } getAnonCriteria() { + const anonymousUsageTracked = isAnonymousUsageTracked(); + + if (!anonymousUsageTracked) return; + baseIterableRequest({ method: 'GET', url: GET_CRITERIA_PATH, @@ -169,6 +186,10 @@ export class AnonymousUserEventManager { } private async createKnownUser(criteriaId: string) { + const anonymousUsageTracked = isAnonymousUsageTracked(); + + if (!anonymousUsageTracked) return; + const userData = localStorage.getItem(SHARED_PREFS_ANON_SESSIONS); const eventList = localStorage.getItem(SHARED_PREFS_EVENT_LIST_KEY); const events = eventList ? JSON.parse(eventList) : []; @@ -293,6 +314,10 @@ export class AnonymousUserEventManager { >, shouldOverWrite: boolean ) { + const anonymousUsageTracked = isAnonymousUsageTracked(); + + if (!anonymousUsageTracked) return; + const strTrackEventList = localStorage.getItem(SHARED_PREFS_EVENT_LIST_KEY); let previousDataArray = []; diff --git a/src/anonymousUserTracking/tests/anonymousUserEventManager.test.ts b/src/anonymousUserTracking/tests/anonymousUserEventManager.test.ts index dc55abf1..64826966 100644 --- a/src/anonymousUserTracking/tests/anonymousUserEventManager.test.ts +++ b/src/anonymousUserTracking/tests/anonymousUserEventManager.test.ts @@ -3,7 +3,8 @@ import { baseIterableRequest } from '../../request'; import { SHARED_PREFS_ANON_SESSIONS, SHARED_PREFS_EVENT_LIST_KEY, - SHARED_PREFS_CRITERIA + SHARED_PREFS_CRITERIA, + SHARED_PREF_ANON_USAGE_TRACKED } from '../../constants'; import { UpdateUserParams } from '../../users'; import { TrackPurchaseRequestParams } from '../../commerce'; @@ -52,9 +53,15 @@ describe('AnonymousUserEventManager', () => { } }; - localStorageMock.getItem.mockReturnValue( - JSON.stringify(initialAnonSessionInfo) - ); + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === SHARED_PREFS_ANON_SESSIONS) { + return JSON.stringify(initialAnonSessionInfo); + } + if (key === SHARED_PREF_ANON_USAGE_TRACKED) { + return 'true'; + } + return null; + }); anonUserEventManager.updateAnonSession(); diff --git a/src/anonymousUserTracking/tests/userMergeScenarios.test.ts b/src/anonymousUserTracking/tests/userMergeScenarios.test.ts index 2e040590..a5546eec 100644 --- a/src/anonymousUserTracking/tests/userMergeScenarios.test.ts +++ b/src/anonymousUserTracking/tests/userMergeScenarios.test.ts @@ -8,7 +8,8 @@ import { GET_CRITERIA_PATH, SHARED_PREFS_ANON_SESSIONS, ENDPOINT_MERGE_USER, - SHARED_PREF_ANON_USER_ID + SHARED_PREF_ANON_USER_ID, + SHARED_PREF_ANON_USAGE_TRACKED } from '../../constants'; import { track } from '../../events'; import { getInAppMessages } from '../../inapp'; @@ -88,6 +89,9 @@ describe('UserMergeScenariosTests', () => { if (key === SHARED_PREFS_ANON_SESSIONS) { return JSON.stringify(initialAnonSessionInfo); } + if (key === SHARED_PREF_ANON_USAGE_TRACKED) { + return 'true'; + } return null; }); jest.useFakeTimers(); @@ -270,6 +274,9 @@ describe('UserMergeScenariosTests', () => { if (key === SHARED_PREFS_ANON_SESSIONS) { return JSON.stringify(initialAnonSessionInfo); } + if (key === SHARED_PREF_ANON_USAGE_TRACKED) { + return 'true'; + } return null; }); const { setUserID, logout } = initializeWithConfig({ @@ -587,6 +594,9 @@ describe('UserMergeScenariosTests', () => { if (key === SHARED_PREFS_ANON_SESSIONS) { return JSON.stringify(initialAnonSessionInfo); } + if (key === SHARED_PREF_ANON_USAGE_TRACKED) { + return 'true'; + } return null; }); const { setEmail, logout } = initializeWithConfig({ diff --git a/src/anonymousUserTracking/tests/userUpdate.test.ts b/src/anonymousUserTracking/tests/userUpdate.test.ts index a899dc58..6d1f84c8 100644 --- a/src/anonymousUserTracking/tests/userUpdate.test.ts +++ b/src/anonymousUserTracking/tests/userUpdate.test.ts @@ -6,7 +6,8 @@ import { SHARED_PREFS_CRITERIA, GET_CRITERIA_PATH, ENDPOINT_TRACK_ANON_SESSION, - ENDPOINT_MERGE_USER + ENDPOINT_MERGE_USER, + SHARED_PREF_ANON_USAGE_TRACKED } from '../../constants'; import { updateUser } from '../../users'; import { initializeWithConfig } from '../../authorization'; @@ -95,6 +96,9 @@ describe('UserUpdate', () => { if (key === SHARED_PREFS_ANON_SESSIONS) { return JSON.stringify(initialAnonSessionInfo); } + if (key === SHARED_PREF_ANON_USAGE_TRACKED) { + return 'true'; + } return null; }); diff --git a/src/anonymousUserTracking/tests/validateCustomEventUserUpdateAPI.test.ts b/src/anonymousUserTracking/tests/validateCustomEventUserUpdateAPI.test.ts index 3d98ff6e..0acbceb4 100644 --- a/src/anonymousUserTracking/tests/validateCustomEventUserUpdateAPI.test.ts +++ b/src/anonymousUserTracking/tests/validateCustomEventUserUpdateAPI.test.ts @@ -7,7 +7,8 @@ import { ENDPOINT_MERGE_USER, ENDPOINT_TRACK_ANON_SESSION, GET_CRITERIA_PATH, - GETMESSAGES_PATH + GETMESSAGES_PATH, + SHARED_PREF_ANON_USAGE_TRACKED } from '../../constants'; import { track } from '../../events'; import { initializeWithConfig } from '../../authorization'; @@ -137,6 +138,9 @@ describe('validateCustomEventUserUpdateAPI', () => { if (key === SHARED_PREFS_ANON_SESSIONS) { return JSON.stringify(initialAnonSessionInfo); } + if (key === SHARED_PREF_ANON_USAGE_TRACKED) { + return 'true'; + } return null; }); @@ -221,6 +225,9 @@ describe('validateCustomEventUserUpdateAPI', () => { if (key === SHARED_PREFS_ANON_SESSIONS) { return JSON.stringify(initialAnonSessionInfo); } + if (key === SHARED_PREF_ANON_USAGE_TRACKED) { + return 'true'; + } return null; }); @@ -287,6 +294,9 @@ describe('validateCustomEventUserUpdateAPI', () => { if (key === SHARED_PREFS_ANON_SESSIONS) { return JSON.stringify(initialAnonSessionInfo); } + if (key === SHARED_PREF_ANON_USAGE_TRACKED) { + return 'true'; + } return null; }); @@ -358,6 +368,9 @@ describe('validateCustomEventUserUpdateAPI', () => { if (key === SHARED_PREFS_ANON_SESSIONS) { return JSON.stringify(initialAnonSessionInfo); } + if (key === SHARED_PREF_ANON_USAGE_TRACKED) { + return 'true'; + } return null; }); diff --git a/src/authorization/authorization.ts b/src/authorization/authorization.ts index 1e48d0eb..e5feaa59 100644 --- a/src/authorization/authorization.ts +++ b/src/authorization/authorization.ts @@ -7,7 +7,9 @@ import { STATIC_HEADERS, SHARED_PREF_ANON_USER_ID, ENDPOINTS, - RouteConfig + RouteConfig, + SHARED_PREF_ANON_USAGE_TRACKED, + SHARED_PREFS_CRITERIA } from 'src/constants'; import { cancelAxiosRequestAndMakeFetch, @@ -21,6 +23,7 @@ import { import { AnonymousUserMerge } from 'src/anonymousUserTracking/anonymousUserMerge'; import { AnonymousUserEventManager, + isAnonymousUsageTracked, registerAnonUserIdSetter } from 'src/anonymousUserTracking/anonymousUserEventManager'; import { Options, config } from 'src/utils/config'; @@ -74,9 +77,14 @@ export interface WithoutJWT { setEmail: (email: string) => Promise; setUserID: (userId: string) => Promise; logout: () => void; + startStopAnonymousUserTracking: (concent: boolean) => void; } export const setAnonUserId = async (userId: string) => { + const anonymousUsageTracked = isAnonymousUsageTracked(); + + if (!anonymousUsageTracked) return; + let token: null | string = null; if (generateJWTGlobal) { token = await generateJWTGlobal({ userID: userId }); @@ -523,6 +531,32 @@ export function initialize( /* stop adding JWT to requests */ baseAxiosRequest.interceptors.request.eject(userInterceptor); } + }, + startStopAnonymousUserTracking: (concent: boolean) => { + /* if concent is true, we want to clear anon user data and start tracking from point forward */ + if (concent) { + anonUserManager.removeAnonSessionCriteriaData(); + localStorage.removeItem(SHARED_PREFS_CRITERIA); + + localStorage.setItem(SHARED_PREF_ANON_USAGE_TRACKED, 'true'); + enableAnonymousTracking(); + } else { + /* if concent is false, we want to stop tracking and clear anon user data */ + const anonymousUsageTracked = isAnonymousUsageTracked(); + if (anonymousUsageTracked) { + anonUserManager.removeAnonSessionCriteriaData(); + + localStorage.removeItem(SHARED_PREFS_CRITERIA); + localStorage.removeItem(SHARED_PREF_ANON_USER_ID); + localStorage.removeItem(SHARED_PREF_ANON_USAGE_TRACKED); + + typeOfAuth = null; + authIdentifier = null; + /* clear fetched in-app messages */ + clearMessages(); + } + localStorage.setItem(SHARED_PREF_ANON_USAGE_TRACKED, 'false'); + } } }; } diff --git a/src/constants.ts b/src/constants.ts index 11dd7792..54a768e5 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -285,6 +285,7 @@ export const SHARED_PREFS_EVENT_LIST_KEY = 'itbl_event_list'; export const SHARED_PREFS_CRITERIA = 'criteria'; export const SHARED_PREFS_ANON_SESSIONS = 'itbl_anon_sessions'; export const SHARED_PREF_ANON_USER_ID = 'anon_userId'; +export const SHARED_PREF_ANON_USAGE_TRACKED = 'itbl_anonymous_usage_tracked'; export const KEY_EVENT_NAME = 'eventName'; export const KEY_CREATED_AT = 'createdAt'; From fd934f881514371e279a0d5eee0a387f46b325d6 Mon Sep 17 00:00:00 2001 From: Darshan Parmar Date: Tue, 15 Oct 2024 15:05:05 +0530 Subject: [PATCH 2/4] rename concent to consent --- react-example/src/indexWithoutJWT.tsx | 8 ++++---- react-example/src/styles/index.css | 2 +- react-example/src/views/AUTTesting.tsx | 14 +++++++------- src/authorization/authorization.ts | 10 +++++----- 4 files changed, 17 insertions(+), 17 deletions(-) diff --git a/react-example/src/indexWithoutJWT.tsx b/react-example/src/indexWithoutJWT.tsx index 77ae22f2..b5e5d8a6 100644 --- a/react-example/src/indexWithoutJWT.tsx +++ b/react-example/src/indexWithoutJWT.tsx @@ -55,11 +55,11 @@ const HomeLink = styled(Link)` } }; - const { setUserID, logout, setEmail, startStopAnonymousUserTracking } = + const { setUserID, logout, setEmail, toggleAnonUserTrackingConsent } = initializeWithConfig(initializeParams); - const handleConcent = (concent?: boolean) => - startStopAnonymousUserTracking(concent); + const handleConsent = (consent?: boolean) => + toggleAnonUserTrackingConsent(consent); // eslint-disable-next-line react/no-deprecated ReactDOM.render( @@ -91,7 +91,7 @@ const HomeLink = styled(Link)` /> } + element={} /> diff --git a/react-example/src/styles/index.css b/react-example/src/styles/index.css index b3d49296..e6033d05 100644 --- a/react-example/src/styles/index.css +++ b/react-example/src/styles/index.css @@ -65,7 +65,7 @@ footer { align-items: flex-end; } -#cookie-concent-container { +#cookie-consent-container { display: flex; justify-content: center; flex-direction: column; diff --git a/react-example/src/views/AUTTesting.tsx b/react-example/src/views/AUTTesting.tsx index ca19f668..e0a0ab2c 100644 --- a/react-example/src/views/AUTTesting.tsx +++ b/react-example/src/views/AUTTesting.tsx @@ -21,10 +21,10 @@ import { } from './Components.styled'; interface Props { - setConcent?: (accept: boolean) => void; + setConsent?: (accept: boolean) => void; } -export const AUTTesting: FC = ({ setConcent }) => { +export const AUTTesting: FC = ({ setConsent }) => { const [updateCartResponse, setUpdateCartResponse] = useState( 'Endpoint JSON goes here' ); @@ -203,12 +203,12 @@ export const AUTTesting: FC = ({ setConcent }) => { const inputAttr = { 'data-qa-track-input': true }; const responseAttr = { 'data-qa-track-response': true }; - const acceptCookie = () => setConcent(true); + const acceptCookie = () => setConsent(true); - const declineCookie = () => setConcent(false); + const declineCookie = () => setConsent(false); - const renderCookieConcent = ( -