@aws-amplify/analytics
Version:
Analytics category of aws-amplify
156 lines (128 loc) • 4.2 kB
text/typescript
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
import { ConsoleLogger } from '@aws-amplify/core';
import { isBrowser } from '@aws-amplify/core/internals/utils';
import {
PageViewTrackingOptions,
TrackerEventRecorder,
TrackerInterface,
} from '../types/trackers';
const logger = new ConsoleLogger('PageViewTracker');
const DEFAULT_EVENT_NAME = 'pageView';
const DEFAULT_APP_TYPE = 'singlePage';
const DEFAULT_URL_PROVIDER = () => {
return window.location.origin + window.location.pathname;
};
const PREV_URL_STORAGE_KEY = 'aws-amplify-analytics-prevUrl';
export class PageViewTracker implements TrackerInterface {
private trackerActive: boolean;
private options: PageViewTrackingOptions;
private eventRecorder: TrackerEventRecorder;
// SPA tracking helpers
private spaTrackingActive: boolean;
private pushStateProxy?: any;
private replaceStateProxy?: any;
private originalPushState: any;
private originalReplaceState: any;
constructor(
eventRecorder: TrackerEventRecorder,
options?: PageViewTrackingOptions,
) {
this.options = {};
this.trackerActive = true;
this.eventRecorder = eventRecorder;
this.spaTrackingActive = false;
this.handleLocationChange = this.handleLocationChange.bind(this);
this.configure(eventRecorder, options);
}
public configure(
eventRecorder: TrackerEventRecorder,
options?: PageViewTrackingOptions,
) {
this.eventRecorder = eventRecorder;
// Clean up any existing listeners
this.cleanup();
// Apply defaults
this.options = {
appType: options?.appType ?? DEFAULT_APP_TYPE,
attributes: options?.attributes ?? undefined,
eventName: this.options?.eventName ?? DEFAULT_EVENT_NAME,
urlProvider: this.options?.urlProvider ?? DEFAULT_URL_PROVIDER,
};
// Configure SPA or MPA page view tracking
if (isBrowser()) {
if (this.options.appType === 'singlePage') {
this.setupSPATracking();
} else {
this.setupMPATracking();
}
this.trackerActive = true;
}
}
public cleanup() {
// No-op if document listener is not active
if (!this.trackerActive) {
return;
}
// Clean up SPA page view listeners
if (this.spaTrackingActive) {
window.history.pushState = this.originalPushState;
window.history.replaceState = this.originalReplaceState;
this.pushStateProxy?.revoke();
this.replaceStateProxy?.revoke();
window.removeEventListener('popstate', this.handleLocationChange);
this.spaTrackingActive = false;
}
}
private setupSPATracking() {
if (!this.spaTrackingActive) {
// Configure proxies on History APIs
this.pushStateProxy = Proxy.revocable(window.history.pushState, {
apply: (target, thisArg, args) => {
target.apply(thisArg, args as any);
this.handleLocationChange();
},
});
this.replaceStateProxy = Proxy.revocable(window.history.replaceState, {
apply: (target, thisArg, args) => {
target.apply(thisArg, args as any);
this.handleLocationChange();
},
});
this.originalPushState = window.history.pushState;
this.originalReplaceState = window.history.replaceState;
window.history.pushState = this.pushStateProxy.proxy;
window.history.replaceState = this.replaceStateProxy.proxy;
window.addEventListener('popstate', this.handleLocationChange);
sessionStorage.removeItem(PREV_URL_STORAGE_KEY);
this.spaTrackingActive = true;
}
}
private setupMPATracking() {
this.handleLocationChange();
}
private handleLocationChange() {
const currentUrl = this.options.urlProvider!();
const eventName = this.options.eventName || DEFAULT_EVENT_NAME;
if (this.urlHasChanged()) {
sessionStorage.setItem(PREV_URL_STORAGE_KEY, currentUrl);
// Assemble attribute list
const attributes = Object.assign(
{
url: currentUrl,
},
this.options.attributes,
);
logger.debug('Recording automatically tracked page view event', {
eventName,
attributes,
});
this.eventRecorder(eventName, attributes);
}
}
private urlHasChanged() {
const prevUrl = sessionStorage.getItem(PREV_URL_STORAGE_KEY);
const currUrl = this.options.urlProvider!();
return currUrl !== prevUrl;
}
}