Skip to content

Commit c9aa43a

Browse files
author
purnimagupta
committed
feat(UserInteractionResource): Storage strategy
- Use localforage to store UserInteractionResource in browser - Use gzip to compress data within Worker Define Storage settings to determine location & frequency of storage strategy: - resourceLimit: Invoke storage strategy when resource reaches a certain number - apiUrl: Endpoint to send compressed resources array - dataKey: Key to be used in browser store
1 parent 2eabc05 commit c9aa43a

File tree

9 files changed

+2815
-1949
lines changed

9 files changed

+2815
-1949
lines changed

.vscode/settings.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
{
2+
}

package-lock.json

Lines changed: 2650 additions & 1924 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,15 @@
1212
"@types/react-dom": "^17.0.0",
1313
"@types/styled-components": "^5.1.8",
1414
"antd": "^4.12.3",
15+
"localforage": "^1.9.0",
16+
"pako": "^2.0.3",
1517
"react": "^17.0.1",
1618
"react-dom": "^17.0.1",
1719
"react-router": "^5.2.0",
18-
"react-scripts": "4.0.2",
20+
"react-scripts": "4.0.3",
1921
"styled-components": "^5.2.1",
2022
"typescript": "^4.1.2",
21-
"web-vitals": "^1.0.1"
23+
"workerize-loader": "^1.3.0"
2224
},
2325
"scripts": {
2426
"start": "react-scripts start",
@@ -53,6 +55,7 @@
5355
"@storybook/addon-links": "^6.1.18",
5456
"@storybook/node-logger": "^6.1.18",
5557
"@storybook/preset-create-react-app": "^3.1.6",
56-
"@storybook/react": "^6.1.18"
58+
"@storybook/react": "^6.1.18",
59+
"@types/pako": "^1.0.1"
5760
}
5861
}

src/components/templates/LandingPageOne/index.tsx

Lines changed: 24 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,37 @@
11
import { Layout, Menu } from 'antd';
2+
import styled from 'styled-components';
23

34
import NavBar, { NavbarProps } from '../../widgets/NavBar';
45
import Card from '../../widgets/Card';
56

6-
import styled from 'styled-components';
7-
import
8-
UserInteractionResource,
9-
{
10-
UserInteraction,
11-
}
12-
from '../../../library/user-analytics/lib/resources/userInteractionResource';
7+
import UserInteractionResource, { UserInteraction } from '../../../library/user-analytics/lib/resources/userInteractionResource';
138
import { DataContext } from '../../../library/user-analytics/react/contexts/dataContext';
149

15-
// import Header from '../../elements/Header';
1610
import { ButtonWithTracking } from '../../elements/Button';
1711
import { InputWithTracking } from '../../elements/Input';
1812
import { MenuItemWithTracking } from '../../elements/Menu/MenuItem';
1913

20-
const { Header, Content, Footer } = Layout;
14+
import { StorageClient } from '../../../library/user-analytics/lib/data-processing/storage';
15+
const { Content } = Layout;
2116

2217

2318
export interface LandingPageProps extends NavbarProps {
2419
items: string[];
25-
2620
}
2721

22+
const storage = StorageClient.init({
23+
resourceLimit: 3,
24+
apiUrl: 'http://localhost:3000/events',
25+
});
26+
2827
const data = {
2928
context: "Landing Page",
3029
app: {
3130
version: "1",
3231
},
3332
} as UserInteraction.DataContext;
3433

34+
3535
function LandingPage(props: LandingPageProps) {
3636

3737
function handleClick(e: React.MouseEvent<HTMLElement, MouseEvent>) {
@@ -51,14 +51,17 @@ function LandingPage(props: LandingPageProps) {
5151
// tracking logic goes here
5252
console.log("logEvent");
5353
console.log(interactionResource);
54+
5455
/*
5556
do whatever you want with the resource,
5657
like save it to IndexedDB, compress it, save it via API, etc
5758
*/
59+
60+
storage.handle(event, interactionResource);
5861
}
5962

6063
const headerStyle = {
61-
textAlign: "center" as "center"
64+
textAlign: "center" as "center"
6265
};
6366

6467
const { mode, theme, items, style, logo } = props;
@@ -68,8 +71,8 @@ function LandingPage(props: LandingPageProps) {
6871
trackers={[
6972
{
7073
action: "onClick",
71-
track: logEvent,
72-
74+
track: storage.handle,
75+
7376
}
7477
]}
7578
origin="NavBar Header"
@@ -81,11 +84,11 @@ function LandingPage(props: LandingPageProps) {
8184
</MenuItemWithTracking>
8285
)
8386
});
84-
87+
8588
return (
8689
<DataContext.Provider value={data}>
87-
<Layout className="layout">
88-
<NavBar
90+
<Layout className="layout">
91+
<NavBar
8992
mode={mode}
9093
theme={theme}
9194
style={style}
@@ -97,19 +100,19 @@ function LandingPage(props: LandingPageProps) {
97100
<div style={headerStyle}>
98101
<h1>Let us help solve your critical website development challenges.</h1>
99102
</div>
100-
</StyledContent>
103+
</StyledContent>
101104
<NewsLetter
102105
title="Newsletter"
103106
actions={[
104107
<ButtonWithTracking
105108
type="primary"
106109
label="Subscribe"
107110
onClick={verifyEmail}
108-
111+
109112
trackers={[{
110113
action: "onClick",
111114
track: logEvent,
112-
115+
113116
// pass optional custom data
114117
data: {
115118
color: "blue",
@@ -126,7 +129,7 @@ function LandingPage(props: LandingPageProps) {
126129
}}
127130
/>,
128131
]}>
129-
132+
130133
<InputWithTracking
131134
type="text"
132135
placeholder="Type your email"
@@ -152,7 +155,7 @@ function LandingPage(props: LandingPageProps) {
152155
// optional props
153156
origin="Email Input"
154157
/>
155-
</NewsLetter>
158+
</NewsLetter>
156159
</Layout>
157160
</DataContext.Provider>
158161
)

src/components/templates/LoginForm/index.tsx

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { DataContext } from '../../../library/user-analytics/react/contexts/data
99
import Button, { ButtonWithTracking } from '../../elements/Button';
1010
import Input, { InputWithTracking } from '../../elements/Input';
1111
import Card from '../../widgets/Card';
12-
12+
import { useEffect } from 'react';
1313
export interface LoginFormProps {
1414

1515
}
@@ -22,6 +22,13 @@ const data = {
2222
} as UserInteraction.DataContext;
2323

2424
function LoginForm(props: LoginFormProps) {
25+
// useEffect(() => {
26+
// if(typeof(window.Worker) !== 'undefined') {
27+
28+
// newWorker = new WebWorker(worker)
29+
30+
// }
31+
// },[])
2532

2633
function verifyUsernameAndPassword(e: React.MouseEvent<HTMLElement, MouseEvent>) {
2734
// app logic goes here
@@ -39,6 +46,8 @@ function LoginForm(props: LoginFormProps) {
3946
do whatever you want with the resource,
4047
like save it to IndexedDB, compress it, save it via API, etc
4148
*/
49+
//console.log("worker is", worker)
50+
4251
}
4352

4453
return (
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import pako from "pako";
2+
3+
export function gzip(data: any) : Uint8Array {
4+
return pako.gzip(JSON.stringify(data));
5+
}
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
// localforage
2+
import localforage from 'localforage';
3+
4+
import UserInteractionResource from '../resources/userInteractionResource';
5+
import { init } from './web-worker';
6+
7+
const worker = init();
8+
9+
interface StorageSettings {
10+
resourceLimit: number;
11+
apiUrl: string;
12+
dataKey?: string;
13+
}
14+
15+
const STORAGE_SETTINGS_DEFAULTS : Required<StorageSettings> = {
16+
resourceLimit: 0,
17+
apiUrl: "",
18+
dataKey: "events",
19+
}
20+
21+
export const StorageClient = (function () {
22+
let storageSettings : Required<StorageSettings> = STORAGE_SETTINGS_DEFAULTS;
23+
24+
return {
25+
init: function (options: StorageSettings) {
26+
if (storageSettings.resourceLimit === 0) {
27+
storageSettings = {
28+
...options,
29+
dataKey: options.dataKey ? options.dataKey : storageSettings.dataKey
30+
};
31+
}
32+
33+
return this;
34+
},
35+
36+
getConfig: function() {
37+
return storageSettings;
38+
},
39+
40+
handle: async function(
41+
event: React.MouseEvent<HTMLElement, MouseEvent>, data: UserInteractionResource
42+
) {
43+
let eventsData = await retrieveData(storageSettings.dataKey) as any[];
44+
45+
if(eventsData) {
46+
if(eventsData.length < storageSettings.resourceLimit) {
47+
saveData(storageSettings.dataKey, [data, ...eventsData]);
48+
} else {
49+
if(window.navigator.onLine) {
50+
const compressedData = await worker.gzip(eventsData) as Uint8Array;
51+
52+
syncData(storageSettings.apiUrl, compressedData)
53+
.then((res) => {
54+
clearData();
55+
saveData(storageSettings.dataKey, [data]);
56+
})
57+
.catch(err => {
58+
saveData(storageSettings.dataKey, [data, ...eventsData])
59+
})
60+
}
61+
else {
62+
saveData(storageSettings.dataKey, [data, ...eventsData]);
63+
}
64+
}
65+
} else {
66+
saveData(storageSettings.dataKey, [data]);
67+
}
68+
69+
}
70+
};
71+
})();
72+
73+
74+
function retrieveData(key: string) {
75+
return localforage.getItem(key)
76+
}
77+
78+
function saveData(key: string, data: UserInteractionResource[]) : Promise<void> {
79+
return localforage.setItem(key, data)
80+
.then((result) => {
81+
// console.log(result)
82+
})
83+
.catch((err) => {
84+
console.error("Error saving data", err)
85+
});
86+
}
87+
88+
function clearData() {
89+
localforage.clear()
90+
.then((data) => {
91+
console.log("data has been deleted successfully", data)
92+
})
93+
}
94+
95+
export async function syncData(url: string, data: Uint8Array) {
96+
const response = await fetch(url, {
97+
method: 'POST',
98+
//mode: 'cors',
99+
cache: 'no-cache',
100+
headers: {
101+
'Content-Type': 'application/json',
102+
'Content-Encoding': "gzip",
103+
104+
},
105+
redirect: 'follow',
106+
referrerPolicy: 'no-referrer',
107+
body: data
108+
});
109+
return response.json();
110+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// @ts-ignore
2+
import worker from 'workerize-loader!../data-processing/compression'; // eslint-disable-line import/no-webpack-loader-syntax
3+
4+
export function init() {
5+
const instance = worker();
6+
return instance;
7+
}

tsconfig.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
"compilerOptions": {
33
"target": "es5",
44
"lib": [
5+
"webworker",
56
"dom",
67
"dom.iterable",
78
"esnext"

0 commit comments

Comments
 (0)