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

feat: optimize page view after session start #44

Merged
merged 4 commits into from
Feb 29, 2024
Merged
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
3 changes: 1 addition & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,7 @@
"lint": "npx eslint src",
"test": "npm run prebuild && npx jest -w 1 --coverage",
"clean": "rimraf lib-esm lib dist",
"pack": "npm run build && npm pack",
"publish": "npm run pack && npm publish"
"pack": "npm run build && npm pack"
},
"repository": {
"type": "git",
Expand Down
3 changes: 2 additions & 1 deletion src/browser/BrowserInfo.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Logger } from '@aws-amplify/core';
import { StorageUtil } from '../util/StorageUtil';

/**
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
Expand Down Expand Up @@ -84,7 +85,7 @@ export class BrowserInfo {
const performanceEntries = performance.getEntriesByType('navigation');
if (performanceEntries && performanceEntries.length > 0) {
const type = (performanceEntries[0] as any)['type'];
return type === 'reload';
return type === 'reload' && StorageUtil.getPreviousPageUrl() !== '';
}
} else {
logger.warn('unsupported web environment for performance');
Expand Down
20 changes: 16 additions & 4 deletions src/tracker/PageViewTracker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,18 +25,20 @@ export class PageViewTracker extends BaseTracker {
searchKeywords = Event.Constants.KEYWORDS;
lastEngageTime = 0;
lastScreenStartTimestamp = 0;
isFirstTime = true;

init() {
const configuredSearchKeywords = this.provider.configuration.searchKeyWords;
Object.assign(this.searchKeywords, configuredSearchKeywords);
this.onPageChange = this.onPageChange.bind(this);
if (this.context.configuration.pageType === PageType.SPA) {
this.trackPageViewForSPA();
} else {
if (this.isMultiPageApp()) {
if (!BrowserInfo.isFromReload()) {
this.onPageChange();
}
} else {
this.trackPageViewForSPA();
}
this.isFirstTime = false;
}

trackPageViewForSPA() {
Expand All @@ -55,11 +57,17 @@ export class PageViewTracker extends BaseTracker {
const currentPageUrl = BrowserInfo.getCurrentPageUrl();
const currentPageTitle = BrowserInfo.getCurrentPageTitle();
if (
this.isFirstTime ||
this.isMultiPageApp() ||
previousPageUrl !== currentPageUrl ||
previousPageTitle !== currentPageTitle
) {
this.provider.scrollTracker?.enterNewPage();
if (previousPageUrl !== '') {
if (
!this.isMultiPageApp() &&
!this.isFirstTime &&
previousPageUrl !== ''
) {
this.recordUserEngagement();
}
this.trackPageView(previousPageUrl, previousPageTitle);
Expand Down Expand Up @@ -128,6 +136,10 @@ export class PageViewTracker extends BaseTracker {
return new Date().getTime() - this.lastScreenStartTimestamp;
}

isMultiPageApp() {
return this.context.configuration.pageType === PageType.multiPageApp;
}

trackSearchEvents() {
if (!this.context.configuration.isTrackSearchEvents) return;
const searchStr = window.location.search;
Expand Down
6 changes: 5 additions & 1 deletion src/tracker/SessionTracker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,6 @@ export class SessionTracker extends BaseTracker {

handleInit() {
this.session = Session.getCurrentSession(this.context);
StorageUtil.clearPageInfo();
if (StorageUtil.getIsFirstOpen()) {
this.provider.record({
name: Event.PresetEvent.FIRST_OPEN,
Expand All @@ -69,7 +68,11 @@ export class SessionTracker extends BaseTracker {
this.session = Session.getCurrentSession(this.context);
if (this.session.isNewSession()) {
pageViewTracker.setIsEntrances();
StorageUtil.clearPageInfo();
this.provider.record({ name: Event.PresetEvent.SESSION_START });
if (!isFirstTime) {
pageViewTracker.onPageChange();
}
}
if (!this.provider.configuration.isTrackAppStartEvents) return;
if (isFirstTime && this.isFromCurrentHost()) return;
Expand All @@ -89,6 +92,7 @@ export class SessionTracker extends BaseTracker {
onPageHide() {
logger.debug('page hide');
this.storeSession();
StorageUtil.checkClickstreamId();
const isImmediate = !(this.isWindowClosing && BrowserInfo.isFirefox());
this.recordUserEngagement(isImmediate);
this.recordAppEnd(isImmediate);
Expand Down
55 changes: 51 additions & 4 deletions src/util/StorageUtil.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,28 +39,39 @@ export class StorageUtil {
static readonly previousPageStartTimeKey =
this.prefix + 'previousPageStartTimeKey';
static readonly userIdMappingKey = this.prefix + 'userIdMappingKey';
private static deviceId = '';
private static userUniqueId = '';

static getDeviceId(): string {
let deviceId = localStorage.getItem(StorageUtil.deviceIdKey) ?? '';
if (deviceId === '') {
if (StorageUtil.deviceId !== '') {
return StorageUtil.deviceId;
}
let deviceId = localStorage.getItem(StorageUtil.deviceIdKey);
if (deviceId === null) {
deviceId = uuidV4();
localStorage.setItem(StorageUtil.deviceIdKey, deviceId);
}
StorageUtil.deviceId = deviceId;
return deviceId;
}

static setCurrentUserUniqueId(userUniqueId: string) {
StorageUtil.userUniqueId = userUniqueId;
localStorage.setItem(StorageUtil.userUniqueIdKey, userUniqueId);
}

static getCurrentUserUniqueId(): string {
let userUniqueId = localStorage.getItem(StorageUtil.userUniqueIdKey) ?? '';
if (userUniqueId === '') {
if (StorageUtil.userUniqueId !== '') {
return StorageUtil.userUniqueId;
}
let userUniqueId = localStorage.getItem(StorageUtil.userUniqueIdKey);
if (userUniqueId === null) {
userUniqueId = uuidV4();
StorageUtil.setCurrentUserUniqueId(userUniqueId);
localStorage.setItem(StorageUtil.userUniqueIdKey, userUniqueId);
StorageUtil.saveUserFirstTouchTimestamp();
}
StorageUtil.userUniqueId = userUniqueId;
return userUniqueId;
}

Expand Down Expand Up @@ -293,4 +304,40 @@ export class StorageUtil {
timestamp.toString()
);
}

static checkDeviceId() {
const currentDeviceId = localStorage.getItem(StorageUtil.deviceIdKey) ?? '';
if (StorageUtil.deviceId !== '' && currentDeviceId === '') {
localStorage.setItem(StorageUtil.deviceIdKey, StorageUtil.deviceId);
}
}

static checkUserUniqueId() {
const currentUserUniqueId =
localStorage.getItem(StorageUtil.userUniqueIdKey) ?? '';
if (StorageUtil.userUniqueId !== '' && currentUserUniqueId === '') {
localStorage.setItem(
StorageUtil.userUniqueIdKey,
StorageUtil.userUniqueId
);
}
}

static checkIsFirstOpen() {
if (StorageUtil.getIsFirstOpen()) {
StorageUtil.saveIsFirstOpenToFalse();
}
}

static checkClickstreamId() {
StorageUtil.checkDeviceId();
StorageUtil.checkUserUniqueId();
StorageUtil.checkIsFirstOpen();
}

static clearAll() {
localStorage.clear();
(StorageUtil as any).deviceid = '';
(StorageUtil as any).userUniqueId = '';
}
}
2 changes: 1 addition & 1 deletion test/ClickstreamAnalytics.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import { StorageUtil } from '../src/util/StorageUtil';

describe('ClickstreamAnalytics test', () => {
beforeEach(() => {
localStorage.clear();
StorageUtil.clearAll();
const mockSendRequestSuccess = jest.fn().mockResolvedValue(true);
jest
.spyOn(NetRequest, 'sendRequest')
Expand Down
3 changes: 3 additions & 0 deletions test/browser/BrowserInfo.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,15 @@
import { setPerformanceEntries } from './BrowserUtil';
import { MockObserver } from './MockObserver';
import { BrowserInfo } from '../../src/browser';
import { StorageUtil } from '../../src/util/StorageUtil';

describe('BrowserInfo test', () => {
afterEach(() => {
jest.clearAllMocks();
jest.resetAllMocks();
});
test('test create BrowserInfo', () => {
StorageUtil.clearAllEvents()
const referrer = 'https://example.com/collect';
Object.defineProperty(window.document, 'referrer', {
writable: true,
Expand Down Expand Up @@ -109,6 +111,7 @@ describe('BrowserInfo test', () => {
test('test web page not from reload', () => {
(global as any).PerformanceObserver = MockObserver;
setPerformanceEntries(true, true);
StorageUtil.savePreviousPageUrl("http://localhost:8080")
expect(BrowserInfo.isFromReload()).toBeTruthy();
});
});
3 changes: 2 additions & 1 deletion test/provider/BatchModeTimer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,13 @@
import { SendMode } from '../../src';
import { NetRequest } from '../../src/network/NetRequest';
import { ClickstreamProvider } from '../../src/provider';
import { StorageUtil } from '../../src/util/StorageUtil';
import { setUpBrowserPerformance } from '../browser/BrowserUtil';

describe('ClickstreamProvider timer test', () => {
let provider: ClickstreamProvider;
beforeEach(() => {
localStorage.clear();
StorageUtil.clearAll();
setUpBrowserPerformance();
provider = new ClickstreamProvider();
const mockSendRequest = jest.fn().mockResolvedValue(true);
Expand Down
2 changes: 1 addition & 1 deletion test/provider/ClickstreamProvider.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ describe('ClickstreamProvider test', () => {
let mockRecordProfileSet: any;

beforeEach(async () => {
localStorage.clear();
StorageUtil.clearAll();
setUpBrowserPerformance();
const mockSendRequest = jest.fn().mockResolvedValue(true);
jest.spyOn(NetRequest, 'sendRequest').mockImplementation(mockSendRequest);
Expand Down
2 changes: 1 addition & 1 deletion test/provider/EventRecorder.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ describe('EventRecorder test', () => {
let eventRecorder: EventRecorder;
let context: ClickstreamContext;
beforeEach(() => {
localStorage.clear();
StorageUtil.clearAll()
context = new ClickstreamContext(new BrowserInfo(), {
appId: 'testApp',
endpoint: 'https://localhost:8080/collect',
Expand Down
3 changes: 2 additions & 1 deletion test/tracker/ClickTracker.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
} from '../../src/provider';
import { Session, SessionTracker } from '../../src/tracker';
import { ClickTracker } from '../../src/tracker/ClickTracker';
import { StorageUtil } from "../../src/util/StorageUtil";

describe('ClickTracker test', () => {
let provider: ClickstreamProvider;
Expand All @@ -28,7 +29,7 @@ describe('ClickTracker test', () => {
let recordMethodMock: any;

beforeEach(() => {
localStorage.clear();
StorageUtil.clearAll()
provider = new ClickstreamProvider();
Object.assign(provider.configuration, {
appId: 'testAppId',
Expand Down
3 changes: 2 additions & 1 deletion test/tracker/PageLoadTracker.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { Session, SessionTracker } from '../../src/tracker';
import { PageLoadTracker } from '../../src/tracker/PageLoadTracker';
import { setPerformanceEntries } from '../browser/BrowserUtil';
import { MockObserver } from '../browser/MockObserver';
import { StorageUtil } from "../../src/util/StorageUtil";

describe('PageLoadTracker test', () => {
let provider: ClickstreamProvider;
Expand All @@ -29,7 +30,7 @@ describe('PageLoadTracker test', () => {
let recordMethodMock: any;

beforeEach(() => {
localStorage.clear();
StorageUtil.clearAll()
provider = new ClickstreamProvider();
Object.assign(provider.configuration, {
appId: 'testAppId',
Expand Down
51 changes: 47 additions & 4 deletions test/tracker/PageViewTracker.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ import {
import { PageViewTracker, Session, SessionTracker } from '../../src/tracker';
import { MethodEmbed } from '../../src/util/MethodEmbed';
import { StorageUtil } from '../../src/util/StorageUtil';
import { setPerformanceEntries } from "../browser/BrowserUtil";
import { MockObserver } from "../browser/MockObserver";
import { setPerformanceEntries } from '../browser/BrowserUtil';
import { MockObserver } from '../browser/MockObserver';

global.TextEncoder = TextEncoder;
global.TextDecoder = TextDecoder;
Expand All @@ -46,7 +46,7 @@ describe('PageViewTracker test', () => {
let dom: any;

beforeEach(() => {
localStorage.clear();
StorageUtil.clearAll();
provider = new ClickstreamProvider();

Object.assign(provider.configuration, {
Expand Down Expand Up @@ -76,7 +76,7 @@ describe('PageViewTracker test', () => {
value: 'index',
});
(global as any).PerformanceObserver = MockObserver;
setPerformanceEntries()
setPerformanceEntries();
});

afterEach(() => {
Expand All @@ -95,6 +95,33 @@ describe('PageViewTracker test', () => {
expect(pageAppearMock).toBeCalled();
});

test('test multiPageApp do not record page view when browser reload', () => {
const pageAppearMock = jest.spyOn(pageViewTracker, 'onPageChange');
pageViewTracker.setUp();
expect(pageAppearMock).toBeCalled();
(global as any).PerformanceObserver = MockObserver;
setPerformanceEntries(true, true);
pageViewTracker.setUp();
expect(pageAppearMock).toBeCalledTimes(1);
});

test('test isFirstTime to be false when browser reload in SPA mode', () => {
(global as any).PerformanceObserver = MockObserver;
StorageUtil.savePreviousPageUrl('https://example.com/pageA');
setPerformanceEntries(true, true);
pageViewTracker.setUp();
expect(pageViewTracker.isFirstTime).toBeFalsy();
});

test('test isFirstTime to be false when browser reload in multiPageApp mode', () => {
(context.configuration as any).pageType = PageType.multiPageApp;
StorageUtil.savePreviousPageUrl('https://example.com/pageA');
(global as any).PerformanceObserver = MockObserver;
setPerformanceEntries(true, true);
pageViewTracker.setUp();
expect(pageViewTracker.isFirstTime).toBeFalsy();
});

test('test environment is not supported', () => {
const addEventListener = (global as any).window.addEventListener;
(global as any).window.addEventListener = undefined;
Expand All @@ -120,6 +147,22 @@ describe('PageViewTracker test', () => {
expect(pageAppearMock).toBeCalledTimes(1);
});

test('reload browser will not record page view in SPA mode', async () => {
(context.configuration as any).pageType = PageType.SPA;
const pageAppearMock = jest.spyOn(pageViewTracker, 'onPageChange');
pageViewTracker.setUp();
expect(recordEventMethodMock).toBeCalledWith(
expect.objectContaining({
event_type: Event.PresetEvent.PAGE_VIEW,
})
);
expect(pageAppearMock).toBeCalledTimes(1);
(global as any).PerformanceObserver = MockObserver;
setPerformanceEntries(true, true);
pageViewTracker.setUp();
expect(pageAppearMock).toBeCalledTimes(1);
});

test('test two different page view with userEngagement', async () => {
pageViewTracker.setUp();
jest.spyOn(pageViewTracker, 'getLastEngageTime').mockReturnValue(1100);
Expand Down
3 changes: 2 additions & 1 deletion test/tracker/ScrollTracker.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
} from '../../src/provider';
import { Session, SessionTracker } from '../../src/tracker';
import { ScrollTracker } from '../../src/tracker/ScrollTracker';
import { StorageUtil } from "../../src/util/StorageUtil";

describe('ScrollTracker test', () => {
let provider: ClickstreamProvider;
Expand All @@ -27,7 +28,7 @@ describe('ScrollTracker test', () => {
let recordMethodMock: any;

beforeEach(() => {
localStorage.clear();
StorageUtil.clearAll()
provider = new ClickstreamProvider();

Object.assign(provider.configuration, {
Expand Down
Loading
Loading