Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Agrim/CRO-731/ analytics script in deriv app #17180

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
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 @@ -16,7 +16,7 @@ import QuestionnaireModal from '../QuestionnaireModal';
import ResidenceForm from '../SetResidenceModal/set-residence-form.jsx';
import validateSignupFields from './validate-signup-fields.jsx';
import 'Sass/app/modules/account-signup.scss';
import { trackEventWithCache } from 'Utils/Analytics/analytics.ts';
import cacheTrackEvents from 'Utils/Analytics/analytics.ts';

const AccountSignup = ({
enableApp,
Expand Down Expand Up @@ -56,20 +56,36 @@ const AccountSignup = ({

// didMount lifecycle hook
React.useEffect(() => {
trackEventWithCache({
name: 'ce_virtual_signup_form',
properties: {
action: 'signup_confirmed',
form_name: is_mobile ? 'virtual_signup_web_mobile_default' : 'virtual_signup_web_desktop_default',
// eslint-disable-next-line no-console
cacheTrackEvents.pageLoadEvent([
{
page: 'onboarding',
event: {
name: 'ce_virtual_signup_form',
properties: {
action: 'signup_confirmed',
form_name: is_mobile
? 'virtual_signup_web_mobile_default'
: 'virtual_signup_web_desktop_default',
},
},
},
});
trackEventWithCache({
name: 'ce_virtual_signup_form',
properties: {
action: 'country_selection_screen_opened',
form_name: is_mobile ? 'virtual_signup_web_mobile_default' : 'virtual_signup_web_desktop_default',
]);

cacheTrackEvents.pageLoadEvent([
{
page: 'onboarding',
event: {
name: 'ce_virtual_signup_form',
properties: {
action: 'country_selection_screen_opened',
form_name: is_mobile
? 'virtual_signup_web_mobile_default'
: 'virtual_signup_web_desktop_default',
},
},
},
});
]);

WS.wait('website_status', 'residence_list').then(() => {
if (clients_country && residence_list) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { Button, PasswordInput, PasswordMeter, Text } from '@deriv/components';
import { getErrorMessages, redirectToSignUp } from '@deriv/shared';
import { observer, useStore } from '@deriv/stores';
import { Localize, localize } from '@deriv/translations';
import { trackEventWithCache } from 'Utils/Analytics/analytics.ts';
import cacheTrackEvents from 'Utils/Analytics/analytics.ts';
import SignupSeparatorContainer from '../AccountSignupModal/signup-separator-container.jsx';

import 'Sass/app/modules/account-signup.scss';
Expand All @@ -29,13 +29,20 @@ const PasswordSelectionModal = observer(
const { is_mobile } = ui;

React.useEffect(() => {
trackEventWithCache({
name: 'ce_virtual_signup_form',
properties: {
action: 'password_screen_opened',
form_name: is_mobile ? 'virtual_signup_web_mobile_default' : 'virtual_signup_web_desktop_default',
cacheTrackEvents.pageLoadEvent([
{
page: 'onboarding',
event: {
name: 'ce_virtual_signup_form',
properties: {
action: 'password_screen_opened',
form_name: is_mobile
? 'virtual_signup_web_mobile_default'
: 'virtual_signup_web_desktop_default',
},
},
},
});
]);

// Analytics.trackEvent('ce_virtual_signup_form', {
// action: 'password_screen_opened',
Expand Down
220 changes: 178 additions & 42 deletions packages/core/src/Utils/Analytics/analytics.ts
Original file line number Diff line number Diff line change
@@ -1,48 +1,184 @@
interface AnalyticsEvent {
name: string;
properties: {
[key: string]: string;
};
import { Analytics } from '@deriv-com/analytics';

interface Payload {
type: string;
anonymousId: string;
}

const handleCachedEvents = () => {
let eventQueue: AnalyticsEvent[] = [];
const storedEvents = localStorage.getItem('pending_events');
try {
if (storedEvents) {
eventQueue = JSON.parse(storedEvents) as AnalyticsEvent[];
if (eventQueue.length > 0) {
eventQueue.forEach(event => {
window.rudderanalytics.track(event.name, event.properties);
type ResponseData = {
url: string;
method: string;
status: number;
headers: string;
data: string;
payload: Payload;
};
type Event = {
name: string;
properties: Record<string, string>;
cache?: boolean;
};
type Item = {
event: Event;
cache?: boolean;
callback?: (e: Event) => Event;
};
const cacheTrackEvents = {
interval: null as NodeJS.Timeout | null,
responses: [] as ResponseData[],
isTrackingResponses: false,
hash: (inputString: string, desiredLength = 32): string => {
const fnv1aHash = (string: string): number => {
let hash = 0x811c9dc5;
for (let i = 0; i < string.length; i++) {
// eslint-disable-next-line no-bitwise
hash ^= string.charCodeAt(i);
// eslint-disable-next-line no-bitwise
hash = (hash * 0x01000193) >>> 0;
}
return hash;
};
const base64Encode = (string: string): string => btoa(string);
const hash = fnv1aHash(inputString).toString(16);
let combined = base64Encode(hash);
while (combined.length < desiredLength) {
combined += base64Encode(fnv1aHash(combined).toString(16));
}
return combined.substring(0, desiredLength);
},
getCookies: (name: string): string | null => {
const value = `; ${document.cookie}`;
const parts = value.split(`; ${name}=`);
if (parts.length === 2) {
const part = parts.pop();
const cookieValue = part ? decodeURIComponent(part.split(';').shift()!) : null;
try {
return cookieValue ? JSON.parse(cookieValue) : null;
} catch (e) {
return cookieValue;
}
}
return null;
},
trackPageUnload: () => {
window.addEventListener('beforeunload', event => {
if (!cacheTrackEvents.isPageViewSent()) {
cacheTrackEvents.push('cached_analytics_page_views', {
name: window.location.href,
properties: {
url: window.location.href,
},
});

eventQueue = [];
localStorage.removeItem('pending_events');
}
});
},
isReady: (): boolean => {
if (typeof Analytics === 'undefined' || Analytics === null) {
return false;
}
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
}
};

const setEvent = (event: AnalyticsEvent): void => {
const storedEvents = localStorage.getItem('pending_events');
let eventQueue: AnalyticsEvent[] = [];
if (storedEvents) {
eventQueue = JSON.parse(storedEvents) as AnalyticsEvent[];
}
eventQueue.push(event);
localStorage.setItem('pending_events', JSON.stringify(eventQueue));
};

const trackEventWithCache = (event: AnalyticsEvent): void => {
if (window.rudderanalytics) {
handleCachedEvents();
window.rudderanalytics.track(event.name, event.properties);
} else {
setEvent(event);
}
const instances = Analytics?.getInstances();
return !!instances?.tracking;
},
parseCookies: (cookieName: string): Event[] | null => {
const cookies = document.cookie.split('; ').reduce((acc: Record<string, string>, cookie) => {
const [key, value] = cookie.split('=');
acc[decodeURIComponent(key)] = decodeURIComponent(value);
return acc;
}, {});
return JSON.parse(cookies[cookieName] || 'null');
},
isPageViewSent: (): boolean =>
!!cacheTrackEvents.responses.find(e => e.payload?.type === 'page' && e.payload?.anonymousId),
set: (event: Event) => {
cacheTrackEvents.push('cached_analytics_events', event);
},
push: (cookieName: string, data: Event) => {
let storedCookies: Event[] = [];
const cacheCookie = cacheTrackEvents.parseCookies(cookieName);
if (cacheCookie) storedCookies = cacheCookie;
storedCookies.push(data);
document.cookie = `${cookieName}=${JSON.stringify(storedCookies)}; path=/; Domain=.deriv.com`;
},
processEvent: (event: Event): Event => {
const clientInfo = cacheTrackEvents.getCookies('client_information');
if (clientInfo) {
const { email = null } = JSON.parse(clientInfo);
if (email) {
event.properties.email_hash = cacheTrackEvents.hash(email);
}
}
if (event?.properties?.email) {
const email = event.properties.email;
delete event.properties.email;
event.properties.email_hash = cacheTrackEvents.hash(email);
}
return event;
},
track: (originalEvent: Event, cache: boolean) => {
const event: any = cacheTrackEvents.processEvent(originalEvent);
if (cacheTrackEvents.isReady() && !cache) {
Analytics?.trackEvent(event.name, event.properties);
} else {
cacheTrackEvents.set(event);
}
},
pageView: () => {
if (!cacheTrackEvents.isTrackingResponses) {
cacheTrackEvents.trackPageUnload();
}
let pageViewInterval: NodeJS.Timeout | null = null;
pageViewInterval = setInterval(() => {
if (Analytics !== undefined && typeof Analytics?.pageView === 'function' && cacheTrackEvents.isReady()) {
Analytics?.pageView(window.location.href);
}
if (cacheTrackEvents.isPageViewSent()) {
clearInterval(pageViewInterval!);
}
}, 1000);
},
loadEvent: (items: Item[]) => {
items.forEach(({ event }) => {
const { name, properties } = event;
cacheTrackEvents.track(
{
name,
properties,
},
false
);
});
return cacheTrackEvents;
},
pageLoadEvent: (
items: Array<{ pages?: string[]; excludedPages?: string[]; event: Event; callback?: () => Event }>
) => {
const pathname = window.location.pathname.slice(1);
if (!Array.isArray(items)) {
return cacheTrackEvents;
}
items.forEach(({ pages = [], excludedPages = [], event, callback = null }) => {
let dispatch = false;
if (pages.length) {
if (pages.includes(pathname)) {
dispatch = true;
}
} else if (excludedPages.length) {
if (!excludedPages.includes(pathname)) {
dispatch = true;
}
} else {
dispatch = true;
}
if (dispatch) {
const eventData = callback ? callback() : event;
cacheTrackEvents.loadEvent([
{
event: eventData,
},
]);
}
});
return cacheTrackEvents;
},
};

export { trackEventWithCache, handleCachedEvents };
export default cacheTrackEvents;
Loading