Skip to content

Commit 9037a6b

Browse files
author
purnimagupta
committed
feat(UserInteractionResource): Storage strategy
- Use storage ES6 module as a singleton - Use workerize loader to use storage functions inside a web-worker Storage Strategy: - Add support for a `tll` param, to reattempt data compression + server-sync after certain time interval - Add Re-attempt logic
1 parent c9aa43a commit 9037a6b

File tree

4 files changed

+127
-74
lines changed

4 files changed

+127
-74
lines changed

src/components/templates/LandingPageOne/index.tsx

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { useEffect, useRef } from 'react';
12
import { Layout, Menu } from 'antd';
23
import styled from 'styled-components';
34

@@ -10,31 +11,33 @@ import { DataContext } from '../../../library/user-analytics/react/contexts/data
1011
import { ButtonWithTracking } from '../../elements/Button';
1112
import { InputWithTracking } from '../../elements/Input';
1213
import { MenuItemWithTracking } from '../../elements/Menu/MenuItem';
14+
import { workerInstance } from '../../../library/user-analytics/lib/data-processing/web-worker';
1315

14-
import { StorageClient } from '../../../library/user-analytics/lib/data-processing/storage';
15-
const { Content } = Layout;
16+
const worker = workerInstance.getInstance();
17+
18+
worker.init({
19+
resourceLimit: 5,
20+
ttl: 7000,
21+
apiUrl: 'http://localhost:3000/events',
22+
dataKey: "events"
23+
});
1624

25+
const { Content } = Layout;
1726

1827
export interface LandingPageProps extends NavbarProps {
1928
items: string[];
2029
}
2130

22-
const storage = StorageClient.init({
23-
resourceLimit: 3,
24-
apiUrl: 'http://localhost:3000/events',
25-
});
26-
2731
const data = {
2832
context: "Landing Page",
2933
app: {
3034
version: "1",
3135
},
3236
} as UserInteraction.DataContext;
3337

34-
3538
function LandingPage(props: LandingPageProps) {
3639

37-
function handleClick(e: React.MouseEvent<HTMLElement, MouseEvent>) {
40+
function handleNavbarClick(e: React.MouseEvent<HTMLElement, MouseEvent>) {
3841
// app logic goes here
3942
console.log("navbar link has been clicked with this Item", e);
4043
}
@@ -56,8 +59,8 @@ function LandingPage(props: LandingPageProps) {
5659
do whatever you want with the resource,
5760
like save it to IndexedDB, compress it, save it via API, etc
5861
*/
59-
60-
storage.handle(event, interactionResource);
62+
worker.handle(interactionResource);
63+
6164
}
6265

6366
const headerStyle = {
@@ -71,14 +74,13 @@ function LandingPage(props: LandingPageProps) {
7174
trackers={[
7275
{
7376
action: "onClick",
74-
track: storage.handle,
75-
77+
track: logEvent,
7678
}
7779
]}
7880
origin="NavBar Header"
7981
key={k}
8082
name={item}
81-
onClick={handleClick}
83+
onClick={handleNavbarClick}
8284
>
8385
{item}
8486
</MenuItemWithTracking>

src/library/user-analytics/lib/data-processing/compression.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@ import pako from "pako";
22

33
export function gzip(data: any) : Uint8Array {
44
return pako.gzip(JSON.stringify(data));
5-
}
5+
}
Lines changed: 86 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,81 +1,116 @@
1-
// localforage
21
import localforage from 'localforage';
32

43
import UserInteractionResource from '../resources/userInteractionResource';
5-
import { init } from './web-worker';
64

7-
const worker = init();
5+
import { gzip } from './compression';
86

9-
interface StorageSettings {
7+
declare let self: WorkerGlobalScope;
8+
9+
(function (self) {
10+
console.log("WorkerGlobalScope: ", WorkerGlobalScope)
11+
console.log("self: ", self);
12+
})(self);
13+
14+
export interface StorageSettings {
1015
resourceLimit: number;
16+
ttl: number;
1117
apiUrl: string;
1218
dataKey?: string;
1319
}
1420

1521
const STORAGE_SETTINGS_DEFAULTS : Required<StorageSettings> = {
1622
resourceLimit: 0,
23+
ttl: 5000,
1724
apiUrl: "",
1825
dataKey: "events",
26+
1927
}
2028

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-
}
29+
let storageSettings : Required<StorageSettings> = STORAGE_SETTINGS_DEFAULTS;
30+
let isAppLoaded : boolean = true;
31+
32+
export async function init(options: StorageSettings) {
33+
if (storageSettings.resourceLimit === 0) {
34+
storageSettings = {
35+
...options,
36+
dataKey: options.dataKey ? options.dataKey : storageSettings.dataKey,
37+
};
38+
}
3239

33-
return this;
34-
},
40+
reAttemptSync(storageSettings.apiUrl, storageSettings.ttl, storageSettings.dataKey);
41+
}
3542

36-
getConfig: function() {
37-
return storageSettings;
38-
},
43+
export function getConfig() {
44+
return storageSettings;
45+
}
46+
47+
export async function handle(data: UserInteractionResource) {
48+
let eventsData = await retrieveData(storageSettings.dataKey) as any[];
49+
50+
if(eventsData) {
51+
if(eventsData.length < storageSettings.resourceLimit) {
52+
saveData(storageSettings.dataKey, [data, ...eventsData]);
53+
} else {
54+
if(self.navigator.onLine) {
55+
const compressedData = gzip(eventsData) as Uint8Array;
56+
57+
syncData(storageSettings.apiUrl, compressedData)
58+
.then((res) => {
59+
clearData();
60+
saveData(storageSettings.dataKey, [data]);
61+
})
62+
.catch(err => {
63+
saveData(storageSettings.dataKey, [data, ...eventsData])
64+
})
65+
}
66+
else {
67+
saveData(storageSettings.dataKey, [data, ...eventsData]);
68+
}
69+
}
70+
} else {
71+
saveData(storageSettings.dataKey, [data]);
72+
}
73+
74+
}
3975

40-
handle: async function(
41-
event: React.MouseEvent<HTMLElement, MouseEvent>, data: UserInteractionResource
42-
) {
43-
let eventsData = await retrieveData(storageSettings.dataKey) as any[];
76+
export function onAppClose() {
77+
isAppLoaded = false;
78+
reAttemptSync(storageSettings.apiUrl, storageSettings.ttl, storageSettings.dataKey);
79+
}
4480

81+
function reAttemptSync(url: string, ttl: number, key: string) {
82+
83+
async function compressAndSend(){
84+
if(self.navigator.onLine) {
85+
const eventsData = await retrieveData(key) as any[];
4586
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-
}
87+
const compressedData = gzip(eventsData) as Uint8Array;
88+
89+
syncData(url, compressedData)
90+
.then((res) => {
91+
clearData();
92+
})
93+
.catch(err => {
94+
console.log("error is", err)
95+
});
6596
} else {
66-
saveData(storageSettings.dataKey, [data]);
97+
if (!isAppLoaded) {
98+
// @ts-ignore
99+
self.close(); // kill web worker
100+
}
67101
}
68-
69102
}
70-
};
71-
})();
103+
}
104+
105+
const interval = setInterval(compressAndSend, ttl);
72106

107+
}
73108

74-
function retrieveData(key: string) {
109+
export function retrieveData(key: string) {
75110
return localforage.getItem(key)
76111
}
77112

78-
function saveData(key: string, data: UserInteractionResource[]) : Promise<void> {
113+
export function saveData(key: string, data: UserInteractionResource[]) : Promise<void> {
79114
return localforage.setItem(key, data)
80115
.then((result) => {
81116
// console.log(result)
@@ -85,11 +120,8 @@ function saveData(key: string, data: UserInteractionResource[]) : Promise<void>
85120
});
86121
}
87122

88-
function clearData() {
123+
export function clearData() {
89124
localforage.clear()
90-
.then((data) => {
91-
console.log("data has been deleted successfully", data)
92-
})
93125
}
94126

95127
export async function syncData(url: string, data: Uint8Array) {
@@ -107,4 +139,4 @@ export async function syncData(url: string, data: Uint8Array) {
107139
body: data
108140
});
109141
return response.json();
110-
}
142+
}
Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,26 @@
11
// @ts-ignore
2-
import worker from 'workerize-loader!../data-processing/compression'; // eslint-disable-line import/no-webpack-loader-syntax
2+
import worker from 'workerize-loader!./storage.ts'; // eslint-disable-line import/no-webpack-loader-syntax
33

4-
export function init() {
5-
const instance = worker();
6-
return instance;
7-
}
4+
export const workerInstance = (function () {
5+
let instance: any;
6+
function getInstance() {
7+
if (instance) {
8+
return instance;
9+
}
10+
instance = worker()
11+
return instance;
12+
}
13+
14+
return {
15+
getInstance
16+
}
17+
18+
})();
19+
20+
window.addEventListener("beforeunload", function (e) {
21+
var confirmationMessage = "\o/";
22+
23+
(e || window.event).returnValue = confirmationMessage; // Gecko + IE
24+
return confirmationMessage; /* Safari, Chrome, and other
25+
* WebKit-derived browsers */
26+
}, false);

0 commit comments

Comments
 (0)