Skip to content

Commit

Permalink
feat: optimize page view after session start
Browse files Browse the repository at this point in the history
  • Loading branch information
xiaoweii committed Feb 28, 2024
1 parent 86cd73b commit c296c16
Show file tree
Hide file tree
Showing 16 changed files with 165 additions and 29 deletions.
4 changes: 1 addition & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,7 @@
"format": "npx prettier --check 'src/**/*.{js,ts}'",
"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"
"clean": "rimraf lib-esm lib dist"
},
"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
26 changes: 22 additions & 4 deletions src/tracker/PageViewTracker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,17 +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.isFirstTime = false;
}
} else {
this.trackPageViewForSPA();
}
}

Expand All @@ -45,6 +48,8 @@ export class PageViewTracker extends BaseTracker {
window.addEventListener('popstate', this.onPageChange);
if (!BrowserInfo.isFromReload()) {
this.onPageChange();
} else {
this.isFirstTime = false;
}
}

Expand All @@ -55,18 +60,27 @@ 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);
this.trackSearchEvents();

StorageUtil.savePreviousPageUrl(currentPageUrl);
StorageUtil.savePreviousPageTitle(currentPageTitle);
if (this.isFirstTime) {
this.isFirstTime = false;
}
}
}
}
Expand Down Expand Up @@ -128,6 +142,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
Loading

0 comments on commit c296c16

Please sign in to comment.