Skip to content

Commit 4457e30

Browse files
committed
feat(User Interaction): Send analytics at end of session
- Web sites often want to send analytics or diagnostics to the server when the user has finished with the page. The most reliable way to do this is to send the data on the visibilitychange event. - Reorganize library directory structure
1 parent 62375a8 commit 4457e30

File tree

7 files changed

+56
-47
lines changed

7 files changed

+56
-47
lines changed

src/examples/components/templates/LandingPageOne/index.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,20 +8,23 @@ import Card from '../../widgets/Card';
88
import UserInteractionResource, { UserInteraction } from 'library/resources/userInteractionResource';
99
import { DataContext } from 'library/react/contexts/dataContext';
1010
import { workerInstance } from 'library/data-processing/web-worker';
11+
import { initializeBeacon } from 'library/browser/storage';
1112

1213
import { ButtonWithTracking } from '../../elements/Button';
1314
import { InputWithTracking } from '../../elements/Input';
1415
import { MenuItemWithTracking } from '../../elements/Menu/MenuItem';
1516

1617
const worker = workerInstance.getInstance();
1718

18-
worker.init({
19+
worker.start({
1920
resourceLimit: 5,
2021
ttl: 7000,
2122
apiUrl: 'http://localhost:3000/events',
2223
dataKey: "events"
2324
});
2425

26+
initializeBeacon("events", 'http://localhost:3000/events');
27+
2528
const { Content } = Layout;
2629

2730
export interface LandingPageProps extends NavbarProps {

src/examples/components/templates/LoginForm/index.tsx

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

1515
}
@@ -22,13 +22,6 @@ 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-
// },[])
3225

3326
function verifyUsernameAndPassword(e: React.MouseEvent<HTMLElement, MouseEvent>) {
3427
// app logic goes here
@@ -46,7 +39,6 @@ function LoginForm(props: LoginFormProps) {
4639
do whatever you want with the resource,
4740
like save it to IndexedDB, compress it, save it via API, etc
4841
*/
49-
//console.log("worker is", worker)
5042

5143
}
5244

@@ -125,4 +117,4 @@ function LoginForm(props: LoginFormProps) {
125117
)
126118
}
127119

128-
export default LoginForm;
120+
export default LoginForm;

src/library/browser/storage.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import localforage from 'localforage';
2+
import UserInteractionResource from '../resources/userInteractionResource';
3+
4+
export function retrieveData(key: string) {
5+
return localforage.getItem(key)
6+
}
7+
8+
export function saveData(key: string, data: UserInteractionResource[]) : Promise<void> {
9+
return localforage.setItem(key, data)
10+
.then((result) => {
11+
// console.log(result)
12+
})
13+
.catch((err) => {
14+
console.error("Error saving data", err)
15+
});
16+
}
17+
18+
export function clearData() {
19+
localforage.clear()
20+
}
21+
22+
export async function initializeBeacon(dataKey: string, apiUrl: string) {
23+
document.addEventListener('visibilitychange', async function () {
24+
if (document.visibilityState === 'hidden') {
25+
const eventsData = await retrieveData(dataKey) as any[];
26+
if (eventsData) {
27+
var blob = new Blob([JSON.stringify(eventsData)], { type: "text/plain" });
28+
const returnValue = navigator.sendBeacon(apiUrl, blob);
29+
30+
if (returnValue) {
31+
clearData();
32+
}
33+
}
34+
}
35+
});
36+
}

src/library/data-processing/storage.ts renamed to src/library/browser/user-interaction.ts

Lines changed: 10 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import localforage from 'localforage';
2-
31
import UserInteractionResource from '../resources/userInteractionResource';
42

5-
import { gzip } from './compression';
3+
import { gzip } from '../data-processing/compression';
4+
5+
import { retrieveData, saveData, clearData } from '../browser/storage';
66

77
declare let self: WorkerGlobalScope;
88

@@ -29,15 +29,19 @@ const STORAGE_SETTINGS_DEFAULTS : Required<StorageSettings> = {
2929
let storageSettings : Required<StorageSettings> = STORAGE_SETTINGS_DEFAULTS;
3030
let isAppLoaded : boolean = true;
3131

32-
export async function init(options: StorageSettings) {
32+
export async function start(options: StorageSettings) {
33+
init(options);
34+
35+
reAttemptSync(storageSettings.apiUrl, storageSettings.ttl, storageSettings.dataKey);
36+
}
37+
38+
export function init(options: StorageSettings) {
3339
if (storageSettings.resourceLimit === 0) {
3440
storageSettings = {
3541
...options,
3642
dataKey: options.dataKey ? options.dataKey : storageSettings.dataKey,
3743
};
3844
}
39-
40-
reAttemptSync(storageSettings.apiUrl, storageSettings.ttl, storageSettings.dataKey);
4145
}
4246

4347
export function getConfig() {
@@ -106,24 +110,6 @@ function reAttemptSync(url: string, ttl: number, key: string) {
106110

107111
}
108112

109-
export function retrieveData(key: string) {
110-
return localforage.getItem(key)
111-
}
112-
113-
export function saveData(key: string, data: UserInteractionResource[]) : Promise<void> {
114-
return localforage.setItem(key, data)
115-
.then((result) => {
116-
// console.log(result)
117-
})
118-
.catch((err) => {
119-
console.error("Error saving data", err)
120-
});
121-
}
122-
123-
export function clearData() {
124-
localforage.clear()
125-
}
126-
127113
export async function syncData(url: string, data: Uint8Array) {
128114
const response = await fetch(url, {
129115
method: 'POST',

src/library/interaction-tracking/browser.ts renamed to src/library/browser/utils.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ export interface OSDetails{
77
* Returns the Operating System information
88
*
99
*/
10-
export function getUserOS() : OSDetails {
10+
export function getUserOS() : OSDetails {
1111
const userAgent = window.navigator.userAgent;
1212
const platform = window.navigator.platform;
1313
const macosPlatforms = ["Macintosh", "MacIntel", "MacPPC", "Mac68K"];
@@ -32,4 +32,4 @@ export function getUserOS() : OSDetails {
3232
name,
3333
version: ""
3434
};
35-
}
35+
}
Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// @ts-ignore
2-
import worker from 'workerize-loader!./storage.ts'; // eslint-disable-line import/no-webpack-loader-syntax
2+
import worker from 'workerize-loader!../browser/user-interaction.ts'; // eslint-disable-line import/no-webpack-loader-syntax
33

44
export const workerInstance = (function () {
55
let instance: any;
@@ -16,11 +16,3 @@ export const workerInstance = (function () {
1616
}
1717

1818
})();
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);

src/library/resources/userInteractionResource.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import BaseResource from './baseResource';
22

3-
import { getUserOS } from '../interaction-tracking/browser';
3+
import { getUserOS } from '../browser/utils';
44

55
export type Object<T> = {
66
[P in keyof T]: T[P]

0 commit comments

Comments
 (0)