-
Notifications
You must be signed in to change notification settings - Fork 224
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add internal session plugin to the React Native tracker
- Loading branch information
1 parent
0a00be2
commit 19af78c
Showing
11 changed files
with
470 additions
and
30 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,5 @@ | ||
// DO NOT MODIFY THIS FILE MANUALLY BUT DO COMMIT IT. It is generated and used by Rush. | ||
{ | ||
"pnpmShrinkwrapHash": "f5c19c955ef1b13843dbacdb20bdd6461a9a5042", | ||
"pnpmShrinkwrapHash": "bf1ad132a0781c6f74cda6742bc190f7802256f1", | ||
"preferredVersionsHash": "bf21a9e8fbc5a3846fb05b4fa0859e0917b2202f" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
export const FOREGROUND_EVENT_SCHEMA = 'iglu:com.snowplowanalytics.snowplow/application_foreground/jsonschema/1-0-0'; | ||
export const BACKGROUND_EVENT_SCHEMA = 'iglu:com.snowplowanalytics.snowplow/application_background/jsonschema/1-0-0'; | ||
|
||
export const CLIENT_SESSION_ENTITY_SCHEMA ='iglu:com.snowplowanalytics.snowplow/client_session/jsonschema/1-0-2' |
117 changes: 117 additions & 0 deletions
117
trackers/react-native-tracker/src/plugins/session/index.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
import { CorePluginConfiguration, PayloadBuilder } from '@snowplow/tracker-core'; | ||
import { SessionConfiguration, SessionState, TrackerConfiguration } from '../../types'; | ||
import AsyncStorage from '@react-native-async-storage/async-storage'; | ||
import { v4 as uuidv4 } from 'uuid'; | ||
import { BACKGROUND_EVENT_SCHEMA, CLIENT_SESSION_ENTITY_SCHEMA, FOREGROUND_EVENT_SCHEMA } from '../../constants'; | ||
import { getUsefulSchema } from '../../utils'; | ||
|
||
interface StoredSessionState { | ||
userId: string; | ||
sessionId: string; | ||
sessionIndex: number; | ||
} | ||
|
||
interface SessionPlugin extends CorePluginConfiguration { | ||
getSessionUserId: () => Promise<string | undefined>; | ||
getSessionId: () => Promise<string | undefined>; | ||
getSessionIndex: () => Promise<number | undefined>; | ||
getSessionState: () => Promise<SessionState>; | ||
startNewSession: () => Promise<void>; | ||
} | ||
|
||
async function storeSessionState(namespace: string, state: StoredSessionState) { | ||
const { userId, sessionId, sessionIndex } = state; | ||
await AsyncStorage.setItem(`snowplow_${namespace}_session`, JSON.stringify({ userId, sessionId, sessionIndex })); | ||
} | ||
|
||
async function resumeStoredSession(namespace: string): Promise<SessionState> { | ||
const storedState = await AsyncStorage.getItem(`snowplow_${namespace}_session`); | ||
if (storedState) { | ||
const state = JSON.parse(storedState) as StoredSessionState; | ||
return { | ||
userId: state.userId, | ||
sessionId: uuidv4(), | ||
previousSessionId: state.sessionId, | ||
sessionIndex: state.sessionIndex + 1, | ||
storageMechanism: 'LOCAL_STORAGE', | ||
}; | ||
} else { | ||
return { | ||
userId: uuidv4(), | ||
sessionId: uuidv4(), | ||
sessionIndex: 1, | ||
storageMechanism: 'LOCAL_STORAGE', | ||
}; | ||
} | ||
} | ||
|
||
export async function newSessionPlugin({ | ||
namespace, | ||
foregroundSessionTimeout, | ||
backgroundSessionTimeout, | ||
}: TrackerConfiguration & SessionConfiguration): Promise<SessionPlugin> { | ||
let sessionState = await resumeStoredSession(namespace); | ||
await storeSessionState(namespace, sessionState); | ||
|
||
let inBackground = false; | ||
let lastUpdateTs = new Date().getTime(); | ||
|
||
const startNewSession = async () => { | ||
sessionState = { | ||
userId: sessionState.userId, | ||
storageMechanism: sessionState.storageMechanism, | ||
sessionId: uuidv4(), | ||
sessionIndex: sessionState.sessionIndex + 1, | ||
previousSessionId: sessionState.sessionId, | ||
}; | ||
}; | ||
|
||
const getTimeoutMs = () => { | ||
return ((inBackground ? backgroundSessionTimeout : foregroundSessionTimeout) ?? 30 * 60) * 1000; | ||
}; | ||
|
||
const beforeTrack = (payloadBuilder: PayloadBuilder) => { | ||
// check if session has timed out and start a new one if necessary | ||
const now = new Date(); | ||
const timeDiff = now.getTime() - lastUpdateTs; | ||
if (timeDiff > getTimeoutMs()) { | ||
startNewSession(); | ||
storeSessionState(namespace, sessionState); | ||
} | ||
lastUpdateTs = now.getTime(); | ||
|
||
// update event properties | ||
sessionState.eventIndex = (sessionState.eventIndex ?? 0) + 1; | ||
if (sessionState.eventIndex === 1) { | ||
sessionState.firstEventId = payloadBuilder.getPayload().eid as string; | ||
sessionState.firstEventTimestamp = now.toISOString(); | ||
} | ||
|
||
// update background state | ||
if (payloadBuilder.getPayload().e === 'ue') { | ||
const schema = getUsefulSchema(payloadBuilder); | ||
if (schema === FOREGROUND_EVENT_SCHEMA) { | ||
inBackground = false; | ||
} else if (schema === BACKGROUND_EVENT_SCHEMA) { | ||
inBackground = true; | ||
} | ||
} | ||
|
||
// add session context to the payload | ||
payloadBuilder.addContextEntity({ | ||
schema: CLIENT_SESSION_ENTITY_SCHEMA, | ||
data: { ...sessionState }, | ||
}); | ||
}; | ||
|
||
return { | ||
getSessionUserId: () => Promise.resolve(sessionState.userId), | ||
getSessionId: () => Promise.resolve(sessionState.sessionId), | ||
getSessionIndex: () => Promise.resolve(sessionState.sessionIndex), | ||
getSessionState: () => Promise.resolve(sessionState), | ||
startNewSession, | ||
plugin: { | ||
beforeTrack, | ||
}, | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
import { PayloadBuilder } from '@snowplow/tracker-core'; | ||
|
||
// Returns the "useful" schema, i.e. what would someone want to use to identify events. | ||
// For some events this is the 'e' property but for unstructured events, this is the | ||
// 'schema' from the 'ue_px' field. | ||
export function getUsefulSchema(sb: PayloadBuilder): string { | ||
let eventJson = sb.getJson(); | ||
for (const json of eventJson) { | ||
if (json.keyIfEncoded === 'ue_px' && typeof json.json['data'] === 'object') { | ||
const schema = (json.json['data'] as Record<string, unknown>)['schema']; | ||
if (typeof schema == 'string') { | ||
return schema; | ||
} | ||
} | ||
} | ||
return ''; | ||
} |
Oops, something went wrong.