diff --git a/src/browser/BrowserInfo.ts b/src/browser/BrowserInfo.ts index bb4404b..fbc0792 100644 --- a/src/browser/BrowserInfo.ts +++ b/src/browser/BrowserInfo.ts @@ -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; + } } diff --git a/src/tracker/PageViewTracker.ts b/src/tracker/PageViewTracker.ts index 64a5bc3..c506b72 100644 --- a/src/tracker/PageViewTracker.ts +++ b/src/tracker/PageViewTracker.ts @@ -11,7 +11,6 @@ * 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'; @@ -19,8 +18,6 @@ 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; @@ -36,7 +33,9 @@ export class PageViewTracker extends BaseTracker { if (this.context.configuration.pageType === PageType.SPA) { this.trackPageViewForSPA(); } else { - this.onPageChange(); + if (!BrowserInfo.isFromReload()) { + this.onPageChange(); + } } } @@ -44,14 +43,12 @@ export class PageViewTracker extends BaseTracker { 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(); diff --git a/src/tracker/SessionTracker.ts b/src/tracker/SessionTracker.ts index b678925..b54e05a 100644 --- a/src/tracker/SessionTracker.ts +++ b/src/tracker/SessionTracker.ts @@ -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'); @@ -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, @@ -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: { @@ -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(); diff --git a/src/types/Analytics.ts b/src/types/Analytics.ts index 4d5f904..c939ed6 100644 --- a/src/types/Analytics.ts +++ b/src/types/Analytics.ts @@ -72,6 +72,7 @@ export interface Item { category3?: string; category4?: string; category5?: string; + [key: string]: string | number | boolean | null; } diff --git a/src/util/StorageUtil.ts b/src/util/StorageUtil.ts index c525206..6c9134b 100644 --- a/src/util/StorageUtil.ts +++ b/src/util/StorageUtil.ts @@ -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 { diff --git a/test/browser/BrowserInfo.test.ts b/test/browser/BrowserInfo.test.ts index bb3ff28..dacb91b 100644 --- a/test/browser/BrowserInfo.test.ts +++ b/test/browser/BrowserInfo.test.ts @@ -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', () => { @@ -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(); + }); }); diff --git a/test/browser/BrowserUtil.ts b/test/browser/BrowserUtil.ts index 6001133..2ac0279 100644 --- a/test/browser/BrowserUtil.ts +++ b/test/browser/BrowserUtil.ts @@ -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 ), }, }); @@ -78,7 +82,7 @@ function getEntriesByType(): PerformanceEntryList { domComplete: 3440.4000000059605, loadEventStart: 3444.2000000178814, loadEventEnd: 3444.4000000059605, - type: 'reload', + type: 'navigate', redirectCount: 0, activationStart: 0, criticalCHRestart: 0, @@ -86,6 +90,14 @@ function getEntriesByType(): PerformanceEntryList { ]); } +function getEntriesByTypeForReload(): PerformanceEntryList { + return ([ + { + type: 'reload', + }, + ]); +} + function getEntriesByTypeUnload(): PerformanceEntryList { return ([ { diff --git a/test/tracker/ClickTracker.test.ts b/test/tracker/ClickTracker.test.ts index ac7afe3..f67c25f 100644 --- a/test/tracker/ClickTracker.test.ts +++ b/test/tracker/ClickTracker.test.ts @@ -28,7 +28,6 @@ describe('ClickTracker test', () => { let recordMethodMock: any; beforeEach(() => { - sessionStorage.clear(); localStorage.clear(); provider = new ClickstreamProvider(); Object.assign(provider.configuration, { diff --git a/test/tracker/PageLoadTracker.test.ts b/test/tracker/PageLoadTracker.test.ts index ad5d8b3..92212eb 100644 --- a/test/tracker/PageLoadTracker.test.ts +++ b/test/tracker/PageLoadTracker.test.ts @@ -29,7 +29,6 @@ describe('PageLoadTracker test', () => { let recordMethodMock: any; beforeEach(() => { - sessionStorage.clear(); localStorage.clear(); provider = new ClickstreamProvider(); Object.assign(provider.configuration, { diff --git a/test/tracker/PageViewTracker.test.ts b/test/tracker/PageViewTracker.test.ts index a44310b..8e4fe27 100644 --- a/test/tracker/PageViewTracker.test.ts +++ b/test/tracker/PageViewTracker.test.ts @@ -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; @@ -45,7 +47,6 @@ describe('PageViewTracker test', () => { beforeEach(() => { localStorage.clear(); - sessionStorage.clear(); provider = new ClickstreamProvider(); Object.assign(provider.configuration, { @@ -74,6 +75,8 @@ describe('PageViewTracker test', () => { writable: true, value: 'index', }); + (global as any).PerformanceObserver = MockObserver; + setPerformanceEntries() }); afterEach(() => { @@ -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(); diff --git a/test/tracker/ScrollTracker.test.ts b/test/tracker/ScrollTracker.test.ts index f830778..678d7e2 100644 --- a/test/tracker/ScrollTracker.test.ts +++ b/test/tracker/ScrollTracker.test.ts @@ -28,7 +28,6 @@ describe('ScrollTracker test', () => { beforeEach(() => { localStorage.clear(); - sessionStorage.clear(); provider = new ClickstreamProvider(); Object.assign(provider.configuration, { diff --git a/test/tracker/SessionTracker.test.ts b/test/tracker/SessionTracker.test.ts index a3b0769..eb3d133 100644 --- a/test/tracker/SessionTracker.test.ts +++ b/test/tracker/SessionTracker.test.ts @@ -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; @@ -57,6 +59,8 @@ describe('SessionTracker test', () => { provider.sessionTracker = sessionTracker; provider.eventRecorder = eventRecorder; provider.pageViewTracker = pageViewTracker; + (global as any).PerformanceObserver = MockObserver; + setPerformanceEntries(); }); afterEach(() => { @@ -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; diff --git a/test/util/StorageUtil.test.ts b/test/util/StorageUtil.test.ts index 88f9c05..c83cb6c 100644 --- a/test/util/StorageUtil.test.ts +++ b/test/util/StorageUtil.test.ts @@ -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);