Skip to content

Commit

Permalink
fix: page view events lost due to cached session storage (#41)
Browse files Browse the repository at this point in the history
  • Loading branch information
zhu-xiaowei authored Feb 22, 2024
1 parent 2483314 commit f07f26c
Show file tree
Hide file tree
Showing 13 changed files with 106 additions and 41 deletions.
13 changes: 13 additions & 0 deletions src/browser/BrowserInfo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,4 +78,17 @@ export class BrowserInfo {
if (!BrowserInfo.isBrowser()) return '';
return window.document.title ?? '';
}

static isFromReload() {
if (performance && performance.getEntriesByType) {
const performanceEntries = performance.getEntriesByType('navigation');
if (performanceEntries && performanceEntries.length > 0) {
const type = (performanceEntries[0] as any)['type'];
return type === 'reload';
}
} else {
logger.warn('unsupported web environment for performance');
}
return false;
}
}
15 changes: 6 additions & 9 deletions src/tracker/PageViewTracker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,13 @@
* and limitations under the License.
*/

import { Logger } from '@aws-amplify/core';
import { BaseTracker } from './BaseTracker';
import { BrowserInfo } from '../browser';
import { ClickstreamContext, ClickstreamProvider, Event } from '../provider';
import { PageType } from '../types';
import { MethodEmbed } from '../util/MethodEmbed';
import { StorageUtil } from '../util/StorageUtil';

const logger = new Logger('PageViewTracker');

export class PageViewTracker extends BaseTracker {
provider: ClickstreamProvider;
context: ClickstreamContext;
Expand All @@ -36,22 +33,22 @@ export class PageViewTracker extends BaseTracker {
if (this.context.configuration.pageType === PageType.SPA) {
this.trackPageViewForSPA();
} else {
this.onPageChange();
if (!BrowserInfo.isFromReload()) {
this.onPageChange();
}
}
}

trackPageViewForSPA() {
MethodEmbed.add(history, 'pushState', this.onPageChange);
MethodEmbed.add(history, 'replaceState', this.onPageChange);
window.addEventListener('popstate', this.onPageChange);
this.onPageChange();
if (!BrowserInfo.isFromReload()) {
this.onPageChange();
}
}

onPageChange() {
if (!window.sessionStorage) {
logger.warn('unsupported web environment for sessionStorage');
return;
}
if (this.context.configuration.isTrackPageViewEvents) {
const previousPageUrl = StorageUtil.getPreviousPageUrl();
const previousPageTitle = StorageUtil.getPreviousPageTitle();
Expand Down
10 changes: 3 additions & 7 deletions src/tracker/SessionTracker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import { BaseTracker } from './BaseTracker';
import { Session } from './Session';
import { BrowserInfo } from '../browser';
import { Event } from '../provider';
import { PageType } from '../types';
import { StorageUtil } from '../util/StorageUtil';

const logger = new Logger('SessionTracker');
Expand Down Expand Up @@ -53,6 +52,7 @@ 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 @@ -71,8 +71,8 @@ export class SessionTracker extends BaseTracker {
pageViewTracker.setIsEntrances();
this.provider.record({ name: Event.PresetEvent.SESSION_START });
}
if (isFirstTime && this.isMultiPageApp() && this.isFromCurrentHost())
return;
if (isFirstTime && this.isFromCurrentHost()) return;
if (isFirstTime && BrowserInfo.isFromReload()) return;
this.provider.record({
name: Event.PresetEvent.APP_START,
attributes: {
Expand All @@ -85,10 +85,6 @@ export class SessionTracker extends BaseTracker {
return window.location.host === this.context.browserInfo.latestReferrerHost;
}

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

onPageHide() {
logger.debug('page hide');
this.storeSession();
Expand Down
1 change: 1 addition & 0 deletions src/types/Analytics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ export interface Item {
category3?: string;
category4?: string;
category5?: string;

[key: string]: string | number | boolean | null;
}

Expand Down
13 changes: 9 additions & 4 deletions src/util/StorageUtil.ts
Original file line number Diff line number Diff line change
Expand Up @@ -255,20 +255,25 @@ export class StorageUtil {
localStorage.setItem(StorageUtil.isFirstOpenKey, '0');
}

static clearPageInfo() {
localStorage.setItem(StorageUtil.previousPageUrlKey, '');
localStorage.setItem(StorageUtil.previousPageTitleKey, '');
}

static getPreviousPageUrl(): string {
return sessionStorage.getItem(StorageUtil.previousPageUrlKey) ?? '';
return localStorage.getItem(StorageUtil.previousPageUrlKey) ?? '';
}

static savePreviousPageUrl(url: string) {
sessionStorage.setItem(StorageUtil.previousPageUrlKey, url);
localStorage.setItem(StorageUtil.previousPageUrlKey, url);
}

static getPreviousPageTitle(): string {
return sessionStorage.getItem(StorageUtil.previousPageTitleKey) ?? '';
return localStorage.getItem(StorageUtil.previousPageTitleKey) ?? '';
}

static savePreviousPageTitle(title: string) {
sessionStorage.setItem(StorageUtil.previousPageTitleKey, title);
localStorage.setItem(StorageUtil.previousPageTitleKey, title);
}

static getPreviousPageStartTime(): number {
Expand Down
18 changes: 18 additions & 0 deletions test/browser/BrowserInfo.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
* OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions
* and limitations under the License.
*/
import { setPerformanceEntries } from './BrowserUtil';
import { MockObserver } from './MockObserver';
import { BrowserInfo } from '../../src/browser';

describe('BrowserInfo test', () => {
Expand Down Expand Up @@ -93,4 +95,20 @@ describe('BrowserInfo test', () => {
const isFirefox = BrowserInfo.isFirefox();
expect(isFirefox).toBeFalsy();
});

test('test unsupported web environment for performance', () => {
expect(BrowserInfo.isFromReload()).toBeFalsy();
});

test('test web page from reload', () => {
(global as any).PerformanceObserver = MockObserver;
setPerformanceEntries();
expect(BrowserInfo.isFromReload()).toBeFalsy();
});

test('test web page not from reload', () => {
(global as any).PerformanceObserver = MockObserver;
setPerformanceEntries(true, true);
expect(BrowserInfo.isFromReload()).toBeTruthy();
});
});
18 changes: 15 additions & 3 deletions test/browser/BrowserUtil.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,18 @@ export function setUpBrowserPerformance() {
setPerformanceEntries(false);
}

export function setPerformanceEntries(isLoaded = true) {
export function setPerformanceEntries(isLoaded = true, isReload = false) {
Object.defineProperty(window, 'performance', {
writable: true,
value: {
getEntriesByType: jest
.fn()
.mockImplementation(
isLoaded ? getEntriesByType : getEntriesByTypeUnload
isLoaded
? isReload
? getEntriesByTypeForReload
: getEntriesByType
: getEntriesByTypeUnload
),
},
});
Expand Down Expand Up @@ -78,14 +82,22 @@ function getEntriesByType(): PerformanceEntryList {
domComplete: 3440.4000000059605,
loadEventStart: 3444.2000000178814,
loadEventEnd: 3444.4000000059605,
type: 'reload',
type: 'navigate',
redirectCount: 0,
activationStart: 0,
criticalCHRestart: 0,
},
]);
}

function getEntriesByTypeForReload(): PerformanceEntryList {
return <PerformanceEntry[]>(<unknown>[
{
type: 'reload',
},
]);
}

function getEntriesByTypeUnload(): PerformanceEntryList {
return <PerformanceEntry[]>(<unknown>[
{
Expand Down
1 change: 0 additions & 1 deletion test/tracker/ClickTracker.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ describe('ClickTracker test', () => {
let recordMethodMock: any;

beforeEach(() => {
sessionStorage.clear();
localStorage.clear();
provider = new ClickstreamProvider();
Object.assign(provider.configuration, {
Expand Down
1 change: 0 additions & 1 deletion test/tracker/PageLoadTracker.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ describe('PageLoadTracker test', () => {
let recordMethodMock: any;

beforeEach(() => {
sessionStorage.clear();
localStorage.clear();
provider = new ClickstreamProvider();
Object.assign(provider.configuration, {
Expand Down
19 changes: 4 additions & 15 deletions test/tracker/PageViewTracker.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +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";

global.TextEncoder = TextEncoder;
global.TextDecoder = TextDecoder;
Expand All @@ -45,7 +47,6 @@ describe('PageViewTracker test', () => {

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

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

afterEach(() => {
Expand Down Expand Up @@ -149,20 +152,6 @@ describe('PageViewTracker test', () => {
expect(userEngagementMock).not.toBeCalled();
});

test('test environment is not supported for sessionStorage', () => {
const sessionStorage = window.sessionStorage;
Object.defineProperty(window, 'sessionStorage', {
writable: true,
value: undefined,
});
pageViewTracker.setUp();
expect(recordEventMethodMock).not.toBeCalled();
Object.defineProperty(window, 'sessionStorage', {
writable: true,
value: sessionStorage,
});
});

test('test two page view in SPA mode', async () => {
(context.configuration as any).pageType = PageType.SPA;
pageViewTracker.setUp();
Expand Down
1 change: 0 additions & 1 deletion test/tracker/ScrollTracker.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ describe('ScrollTracker test', () => {

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

Object.assign(provider.configuration, {
Expand Down
29 changes: 29 additions & 0 deletions test/tracker/SessionTracker.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ import {
} from '../../src/provider';
import { PageViewTracker, SessionTracker } from '../../src/tracker';
import { StorageUtil } from '../../src/util/StorageUtil';
import { setPerformanceEntries } from '../browser/BrowserUtil';
import { MockObserver } from '../browser/MockObserver';

describe('SessionTracker test', () => {
let provider: ClickstreamProvider;
Expand Down Expand Up @@ -57,6 +59,8 @@ describe('SessionTracker test', () => {
provider.sessionTracker = sessionTracker;
provider.eventRecorder = eventRecorder;
provider.pageViewTracker = pageViewTracker;
(global as any).PerformanceObserver = MockObserver;
setPerformanceEntries();
});

afterEach(() => {
Expand Down Expand Up @@ -116,6 +120,31 @@ describe('SessionTracker test', () => {
});
});

test('test spa mode not record app start when come from the same host name', () => {
Object.assign(provider.configuration, {
pageType: PageType.SPA,
});
context.browserInfo.latestReferrerHost = 'localhost';
sessionTracker.setUp();
expect(recordMethodMock).not.toBeCalledWith({
name: Event.PresetEvent.APP_START,
attributes: {
[Event.ReservedAttribute.IS_FIRST_TIME]: true,
},
});
});

test('test not record app start when browser is from reload', () => {
setPerformanceEntries(true, true);
sessionTracker.setUp();
expect(recordMethodMock).not.toBeCalledWith({
name: Event.PresetEvent.APP_START,
attributes: {
[Event.ReservedAttribute.IS_FIRST_TIME]: true,
},
});
});

test('test setUp for unsupported env', () => {
const addEventListenerMock = jest.spyOn(window, 'addEventListener');
const addEventListener = (global as any).document.addEventListener;
Expand Down
8 changes: 8 additions & 0 deletions test/util/StorageUtil.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,14 @@ describe('StorageUtil test', () => {
});
});

test('test clear page information', () => {
StorageUtil.savePreviousPageTitle('pageA');
StorageUtil.savePreviousPageUrl('https://example.com/pageA');
StorageUtil.clearPageInfo();
expect(StorageUtil.getPreviousPageTitle()).toBe('');
expect(StorageUtil.getPreviousPageUrl()).toBe('');
});

async function saveEvent() {
const event = await getTestEvent();
return StorageUtil.saveEvent(event);
Expand Down

0 comments on commit f07f26c

Please sign in to comment.