Skip to content

Commit

Permalink
feat: Add time on site (#975)
Browse files Browse the repository at this point in the history
  • Loading branch information
rmi22186 committed Feb 13, 2025
1 parent ceb74cf commit 3645120
Show file tree
Hide file tree
Showing 17 changed files with 2,305 additions and 19,617 deletions.
21,266 changes: 1,661 additions & 19,605 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@
"@babel/preset-env": "^7.6.0",
"@babel/preset-typescript": "^7.6.0",
"@mparticle/data-planning-models": "^0.1.0",
"@mparticle/event-models": "^1.1.8",
"@mparticle/event-models": "^1.1.9",
"@rollup/plugin-babel": "6.0.3",
"@rollup/plugin-commonjs": "25.0.4",
"@rollup/plugin-json": "^5.0.2",
Expand Down
113 changes: 113 additions & 0 deletions src/foregroundTimeTracker.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import { isNumber } from './utils';
import { LocalStorageVault } from './vault';

export default class ForegroundTimeTracker {
private isTrackerActive: boolean = false;
private localStorageName: string = '';
private timerVault: LocalStorageVault<number>;
public startTime: number = 0;
public totalTime: number = 0;

constructor(timerKey: string) {
this.localStorageName = `mp-time-${timerKey}`;
this.timerVault = new LocalStorageVault<number>(this.localStorageName);
this.loadTimeFromStorage();
this.addHandlers();
if (document.hidden === false) {
this.startTracking();
}
}

private addHandlers(): void {
// when user switches tabs or minimizes the window
document.addEventListener('visibilitychange', () =>
this.handleVisibilityChange()
);
// when user switches to another application
window.addEventListener('blur', () => this.handleWindowBlur());
// when window gains focus
window.addEventListener('focus', () => this.handleWindowFocus());
// this ensures that timers between tabs are in sync
window.addEventListener('storage', event => this.syncAcrossTabs(event));
// when user closes tab, refreshes, or navigates to another page via link
window.addEventListener('beforeunload', () =>
this.updateTimeInPersistence()
);
}

private handleVisibilityChange(): void {
if (document.hidden) {
this.stopTracking();
} else {
this.startTracking();
}
}

private handleWindowBlur(): void {
if (this.isTrackerActive) {
this.stopTracking();
}
}

private handleWindowFocus(): void {
if (!this.isTrackerActive) {
this.startTracking();
}
}

private syncAcrossTabs(event: StorageEvent): void {
if (event.key === this.localStorageName && event.newValue !== null) {
const newTime = parseFloat(event.newValue) || 0;
this.totalTime = newTime;
}
}

public updateTimeInPersistence(): void {
if (this.isTrackerActive) {
this.timerVault.store(Math.round(this.totalTime));
}
}

private loadTimeFromStorage(): void {
const storedTime = this.timerVault.retrieve();
if (isNumber(storedTime) && storedTime !== null) {
this.totalTime = storedTime;
}
}


private startTracking(): void {
if (!document.hidden) {
this.startTime = Math.floor(performance.now());
this.isTrackerActive = true;
}
}

private stopTracking(): void {
if (this.isTrackerActive) {
this.setTotalTime();
this.updateTimeInPersistence();
this.isTrackerActive = false;
}
}

private setTotalTime(): void {
if (this.isTrackerActive) {
const now = Math.floor(performance.now());
this.totalTime += now - this.startTime;
this.startTime = now;

}
}

public getTimeInForeground(): number {
this.setTotalTime();
this.updateTimeInPersistence();
return this.totalTime;
}

public resetTimer(): void {
this.totalTime = 0;
this.updateTimeInPersistence();
}
}
3 changes: 3 additions & 0 deletions src/mockBatchCreator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ export default class _BatchValidator {
},
_resetForTests: mockFunction,
_APIClient: null,
_timeOnSiteTimer: {
getTimeInForeground: mockFunction
},
MPSideloadedKit: null,
_Consent: null,
_Events: null,
Expand Down
2 changes: 2 additions & 0 deletions src/mp-instance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ import { IEvents } from './events.interfaces';
import { IECommerce } from './ecommerce.interfaces';
import { INativeSdkHelpers } from './nativeSdkHelpers.interfaces';
import { IPersistence } from './persistence.interfaces';
import ForegroundTimer from './foregroundTimeTracker';

export interface IErrorLogMessage {
message?: string;
Expand Down Expand Up @@ -84,6 +85,7 @@ export interface IMParticleWebSDKInstance extends MParticleWebSDK {
_Store: IStore;
_instanceName: string;
_preInit: IPreInit;
_timeOnSiteTimer: ForegroundTimer;
}

const { Messages, HTTPCodes, FeatureFlags } = Constants;
Expand Down
2 changes: 2 additions & 0 deletions src/sdkRuntimeModels.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,9 @@ export interface SDKEvent {
DataPlan?: SDKDataPlan;
LaunchReferral?: string;
ExpandedEventCount: number;
ActiveTimeOnSite: number;
}

export interface SDKGeoLocation {
lat: number | string;
lng: number | string;
Expand Down
1 change: 1 addition & 0 deletions src/sdkToEventsApiConverter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -658,6 +658,7 @@ export function convertBaseEventData(
custom_attributes: sdkEvent.EventAttributes,
location: convertSDKLocation(sdkEvent.Location),
source_message_id: sdkEvent.SourceMessageId,
active_time_on_site_ms: sdkEvent.ActiveTimeOnSite
};

return commonEventData;
Expand Down
1 change: 1 addition & 0 deletions src/serverModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,7 @@ export default function ServerModel(
event.data,
event.name
),
ActiveTimeOnSite: mpInstance._timeOnSiteTimer?.getTimeInForeground(),
SourceMessageId:
event.sourceMessageId ||
mpInstance._Helpers.generateUniqueId(),
Expand Down
9 changes: 8 additions & 1 deletion src/sessionManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,17 +132,20 @@ export default function SessionManager(
});

mpInstance._Store.nullifySession();
mpInstance._timeOnSiteTimer?.resetTimer();
return;
}

if (!mpInstance._Helpers.canLog()) {
// At this moment, an AbandonedEndSession is defined when on of three things occurs:
// At this moment, an AbandonedEndSession is defined when one of three things occurs:
// - the SDK's store is not enabled because mParticle.setOptOut was called
// - the devToken is undefined
// - webviewBridgeEnabled is set to false
mpInstance.Logger.verbose(
Messages.InformationMessages.AbandonEndSession
);
mpInstance._timeOnSiteTimer?.resetTimer();

return;
}

Expand All @@ -155,6 +158,8 @@ export default function SessionManager(
mpInstance.Logger.verbose(
Messages.InformationMessages.NoSessionToEnd
);
mpInstance._timeOnSiteTimer?.resetTimer();

return;
}

Expand All @@ -180,6 +185,8 @@ export default function SessionManager(
mpInstance._Store.nullifySession();
}
}

mpInstance._timeOnSiteTimer?.resetTimer();
};

this.setSessionTimer = function(): void {
Expand Down
2 changes: 2 additions & 0 deletions src/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import {
} from './persistence.interfaces';
import { CookieSyncDates, IPixelConfiguration } from './cookieSyncManager';
import { IMParticleWebSDKInstance } from './mp-instance';
import ForegroundTimer from './foregroundTimeTracker';

// This represents the runtime configuration of the SDK AFTER
// initialization has been complete and all settings and
Expand Down Expand Up @@ -680,6 +681,7 @@ export default function Store(

if (workspaceToken) {
this.SDKConfig.workspaceToken = workspaceToken;
mpInstance._timeOnSiteTimer = new ForegroundTimer(workspaceToken);
} else {
mpInstance.Logger.warning(
'You should have a workspaceToken on your config object for security purposes.'
Expand Down
4 changes: 4 additions & 0 deletions test/fixtures/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export const event0: SDKEvent = {
Debug: false,
DeviceId: 'test-device',
Timestamp: 0,
ActiveTimeOnSite: 10
};

export const event1: SDKEvent = {
Expand All @@ -42,6 +43,7 @@ export const event1: SDKEvent = {
Debug: false,
DeviceId: 'test-device',
Timestamp: 0,
ActiveTimeOnSite: 20
};

export const event2: SDKEvent = {
Expand All @@ -64,6 +66,7 @@ export const event2: SDKEvent = {
Debug: false,
DeviceId: 'test-device',
Timestamp: 0,
ActiveTimeOnSite: 30
};

export const event3: SDKEvent = {
Expand All @@ -86,4 +89,5 @@ export const event3: SDKEvent = {
Debug: false,
DeviceId: 'test-device',
Timestamp: 0,
ActiveTimeOnSite: 40
};
Loading

0 comments on commit 3645120

Please sign in to comment.