Skip to content

Commit

Permalink
Add app install, foreground and background event and application enti…
Browse files Browse the repository at this point in the history
…ty tracking (#1396)
  • Loading branch information
matus-tomlein authored Dec 12, 2024
1 parent d6a87a0 commit 15c050c
Show file tree
Hide file tree
Showing 20 changed files with 648 additions and 27 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->

[Home](./index.md) &gt; [@snowplow/react-native-tracker](./react-native-tracker.md) &gt; [AppLifecycleConfiguration](./react-native-tracker.applifecycleconfiguration.md) &gt; [appBuild](./react-native-tracker.applifecycleconfiguration.appbuild.md)

## AppLifecycleConfiguration.appBuild property

Build name of the application e.g s9f2k2d or 1.1.0 beta

Entity schema: `iglu:com.snowplowanalytics.mobile/application/jsonschema/1-0-0`

<b>Signature:</b>

```typescript
appBuild?: string;
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->

[Home](./index.md) &gt; [@snowplow/react-native-tracker](./react-native-tracker.md) &gt; [AppLifecycleConfiguration](./react-native-tracker.applifecycleconfiguration.md) &gt; [appVersion](./react-native-tracker.applifecycleconfiguration.appversion.md)

## AppLifecycleConfiguration.appVersion property

Version number of the application e.g 1.1.0 (semver or git commit hash).

Entity schema if `appBuild` property is set: `iglu:com.snowplowanalytics.mobile/application/jsonschema/1-0-0` Entity schema if `appBuild` property is not set: `iglu:com.snowplowanalytics.snowplow/application/jsonschema/1-0-0`

<b>Signature:</b>

```typescript
appVersion?: string;
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->

[Home](./index.md) &gt; [@snowplow/react-native-tracker](./react-native-tracker.md) &gt; [AppLifecycleConfiguration](./react-native-tracker.applifecycleconfiguration.md) &gt; [installAutotracking](./react-native-tracker.applifecycleconfiguration.installautotracking.md)

## AppLifecycleConfiguration.installAutotracking property

Whether to automatically track app install event on first run.

Schema: `iglu:com.snowplowanalytics.mobile/application_install/jsonschema/1-0-0`

<b>Signature:</b>

```typescript
installAutotracking?: boolean;
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->

[Home](./index.md) &gt; [@snowplow/react-native-tracker](./react-native-tracker.md) &gt; [AppLifecycleConfiguration](./react-native-tracker.applifecycleconfiguration.md) &gt; [lifecycleAutotracking](./react-native-tracker.applifecycleconfiguration.lifecycleautotracking.md)

## AppLifecycleConfiguration.lifecycleAutotracking property

Whether to automatically track app lifecycle events (app foreground and background events). Also adds a lifecycle context entity to all events.

Foreground event schema: `iglu:com.snowplowanalytics.snowplow/application_foreground/jsonschema/1-0-0` Background event schema: `iglu:com.snowplowanalytics.snowplow/application_background/jsonschema/1-0-0` Context entity schema: `iglu:com.snowplowanalytics.mobile/application_lifecycle/jsonschema/1-0-0`

<b>Signature:</b>

```typescript
lifecycleAutotracking?: boolean;
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->

[Home](./index.md) &gt; [@snowplow/react-native-tracker](./react-native-tracker.md) &gt; [AppLifecycleConfiguration](./react-native-tracker.applifecycleconfiguration.md)

## AppLifecycleConfiguration interface

Configuration for app lifecycle tracking

<b>Signature:</b>

```typescript
export interface AppLifecycleConfiguration
```

## Properties

| Property | Type | Description |
| --- | --- | --- |
| [appBuild?](./react-native-tracker.applifecycleconfiguration.appbuild.md) | string | <i>(Optional)</i> Build name of the application e.g s9f2k2d or 1.1.0 beta<!-- -->Entity schema: <code>iglu:com.snowplowanalytics.mobile/application/jsonschema/1-0-0</code> |
| [appVersion?](./react-native-tracker.applifecycleconfiguration.appversion.md) | string | <i>(Optional)</i> Version number of the application e.g 1.1.0 (semver or git commit hash).<!-- -->Entity schema if <code>appBuild</code> property is set: <code>iglu:com.snowplowanalytics.mobile/application/jsonschema/1-0-0</code> Entity schema if <code>appBuild</code> property is not set: <code>iglu:com.snowplowanalytics.snowplow/application/jsonschema/1-0-0</code> |
| [installAutotracking?](./react-native-tracker.applifecycleconfiguration.installautotracking.md) | boolean | <i>(Optional)</i> Whether to automatically track app install event on first run.<!-- -->Schema: <code>iglu:com.snowplowanalytics.mobile/application_install/jsonschema/1-0-0</code> |
| [lifecycleAutotracking?](./react-native-tracker.applifecycleconfiguration.lifecycleautotracking.md) | boolean | <i>(Optional)</i> Whether to automatically track app lifecycle events (app foreground and background events). Also adds a lifecycle context entity to all events.<!-- -->Foreground event schema: <code>iglu:com.snowplowanalytics.snowplow/application_foreground/jsonschema/1-0-0</code> Background event schema: <code>iglu:com.snowplowanalytics.snowplow/application_background/jsonschema/1-0-0</code> Context entity schema: <code>iglu:com.snowplowanalytics.mobile/application_lifecycle/jsonschema/1-0-0</code> |

Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@

| Interface | Description |
| --- | --- |
| [AppLifecycleConfiguration](./react-native-tracker.applifecycleconfiguration.md) | Configuration for app lifecycle tracking |
| [CoreConfiguration](./react-native-tracker.coreconfiguration.md) | The configuration object for the tracker core library |
| [CorePlugin](./react-native-tracker.coreplugin.md) | Interface which defines Core Plugins |
| [CorePluginConfiguration](./react-native-tracker.corepluginconfiguration.md) | The configuration of the plugin to add |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@ Creates a new tracker instance with the given configuration
<b>Signature:</b>

```typescript
export declare function newTracker(configuration: TrackerConfiguration & EmitterConfiguration & SessionConfiguration & SubjectConfiguration & EventStoreConfiguration & ScreenTrackingConfiguration & PlatformContextConfiguration & DeepLinkConfiguration): Promise<ReactNativeTracker>;
export declare function newTracker(configuration: TrackerConfiguration & EmitterConfiguration & SessionConfiguration & SubjectConfiguration & EventStoreConfiguration & ScreenTrackingConfiguration & PlatformContextConfiguration & DeepLinkConfiguration & AppLifecycleConfiguration): Promise<ReactNativeTracker>;
```

## Parameters

| Parameter | Type | Description |
| --- | --- | --- |
| configuration | [TrackerConfiguration](./react-native-tracker.trackerconfiguration.md) &amp; EmitterConfiguration &amp; [SessionConfiguration](./react-native-tracker.sessionconfiguration.md) &amp; [SubjectConfiguration](./react-native-tracker.subjectconfiguration.md) &amp; [EventStoreConfiguration](./react-native-tracker.eventstoreconfiguration.md) &amp; ScreenTrackingConfiguration &amp; [PlatformContextConfiguration](./react-native-tracker.platformcontextconfiguration.md) &amp; [DeepLinkConfiguration](./react-native-tracker.deeplinkconfiguration.md) | Configuration for the tracker |
| configuration | [TrackerConfiguration](./react-native-tracker.trackerconfiguration.md) &amp; EmitterConfiguration &amp; [SessionConfiguration](./react-native-tracker.sessionconfiguration.md) &amp; [SubjectConfiguration](./react-native-tracker.subjectconfiguration.md) &amp; [EventStoreConfiguration](./react-native-tracker.eventstoreconfiguration.md) &amp; ScreenTrackingConfiguration &amp; [PlatformContextConfiguration](./react-native-tracker.platformcontextconfiguration.md) &amp; [DeepLinkConfiguration](./react-native-tracker.deeplinkconfiguration.md) &amp; [AppLifecycleConfiguration](./react-native-tracker.applifecycleconfiguration.md) | Configuration for the tracker |

<b>Returns:</b>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ export declare type ReactNativeTracker = {
readonly getSessionId: () => Promise<string | undefined>;
readonly getSessionIndex: () => Promise<number | undefined>;
readonly getSessionState: () => Promise<SessionState | undefined>;
readonly getIsInBackground: () => boolean | undefined;
readonly getBackgroundIndex: () => number | undefined;
readonly getForegroundIndex: () => number | undefined;
readonly enablePlatformContext: () => Promise<void>;
readonly disablePlatformContext: () => void;
readonly refreshPlatformContext: () => Promise<void>;
Expand Down
13 changes: 12 additions & 1 deletion api-docs/docs/react-native-tracker/react-native-tracker.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,14 @@ import { BrowserPlugin } from '@snowplow/browser-tracker-core';
import { BrowserPluginConfiguration } from '@snowplow/browser-tracker-core';
import { ScreenTrackingConfiguration } from '@snowplow/browser-plugin-screen-tracking';

// @public
export interface AppLifecycleConfiguration {
appBuild?: string;
appVersion?: string;
installAutotracking?: boolean;
lifecycleAutotracking?: boolean;
}

// @public
export type ConditionalContextProvider = FilterProvider | RuleSetProvider;

Expand Down Expand Up @@ -260,7 +268,7 @@ export type MessageNotificationProps = {
};

// @public
export function newTracker(configuration: TrackerConfiguration & EmitterConfiguration & SessionConfiguration & SubjectConfiguration & EventStoreConfiguration & ScreenTrackingConfiguration & PlatformContextConfiguration & DeepLinkConfiguration): Promise<ReactNativeTracker>;
export function newTracker(configuration: TrackerConfiguration & EmitterConfiguration & SessionConfiguration & SubjectConfiguration & EventStoreConfiguration & ScreenTrackingConfiguration & PlatformContextConfiguration & DeepLinkConfiguration & AppLifecycleConfiguration): Promise<ReactNativeTracker>;

// @public
export interface PageViewEvent {
Expand Down Expand Up @@ -377,6 +385,9 @@ export type ReactNativeTracker = {
readonly getSessionId: () => Promise<string | undefined>;
readonly getSessionIndex: () => Promise<number | undefined>;
readonly getSessionState: () => Promise<SessionState | undefined>;
readonly getIsInBackground: () => boolean | undefined;
readonly getBackgroundIndex: () => number | undefined;
readonly getForegroundIndex: () => number | undefined;
readonly enablePlatformContext: () => Promise<void>;
readonly disablePlatformContext: () => void;
readonly refreshPlatformContext: () => Promise<void>;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@snowplow/react-native-tracker",
"comment": "Add app install, foreground and background event and application entity tracking (#1396)",
"type": "none"
}
],
"packageName": "@snowplow/react-native-tracker"
}
4 changes: 4 additions & 0 deletions trackers/react-native-tracker/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,14 @@ export const FOREGROUND_EVENT_SCHEMA = 'iglu:com.snowplowanalytics.snowplow/appl
export const BACKGROUND_EVENT_SCHEMA = 'iglu:com.snowplowanalytics.snowplow/application_background/jsonschema/1-0-0';
export const DEEP_LINK_RECEIVED_EVENT_SCHEMA = 'iglu:com.snowplowanalytics.mobile/deep_link_received/jsonschema/1-0-0';
export const SCREEN_VIEW_EVENT_SCHEMA = 'iglu:com.snowplowanalytics.mobile/screen_view/jsonschema/1-0-0';
export const APPLICATION_INSTALL_EVENT_SCHEMA = 'iglu:com.snowplowanalytics.mobile/application_install/jsonschema/1-0-0';

export const CLIENT_SESSION_ENTITY_SCHEMA ='iglu:com.snowplowanalytics.snowplow/client_session/jsonschema/1-0-2'
export const MOBILE_CONTEXT_SCHEMA = 'iglu:com.snowplowanalytics.snowplow/mobile_context/jsonschema/1-0-3';
export const DEEP_LINK_ENTITY_SCHEMA = 'iglu:com.snowplowanalytics.mobile/deep_link/jsonschema/1-0-0';
export const LIFECYCLE_CONTEXT_SCHEMA = 'iglu:com.snowplowanalytics.mobile/application_lifecycle/jsonschema/1-0-0';
export const MOBILE_APPLICATION_CONTEXT_SCHEMA = 'iglu:com.snowplowanalytics.mobile/application/jsonschema/1-0-0';
export const APPLICATION_CONTEXT_SCHEMA = 'iglu:com.snowplowanalytics.snowplow/application/jsonschema/1-0-0';

export const PAGE_URL_PROPERTY = 'url';
export const PAGE_REFERRER_PROPERTY = 'refr';
44 changes: 44 additions & 0 deletions trackers/react-native-tracker/src/plugins/app_context/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { CorePluginConfiguration, SelfDescribingJson } from '@snowplow/tracker-core';
import { AppLifecycleConfiguration } from '../../types';
import { APPLICATION_CONTEXT_SCHEMA, MOBILE_APPLICATION_CONTEXT_SCHEMA } from '../../constants';

/**
* Tracks the application context entity with information about the app version.
* If appBuild is provided, a mobile application context is tracked, otherwise the Web equivalent is tracked.
*
* Entity schema if `appBuild` property is set: `iglu:com.snowplowanalytics.mobile/application/jsonschema/1-0-0`
* Entity schema if `appBuild` property is not set: `iglu:com.snowplowanalytics.snowplow/application/jsonschema/1-0-0`
*/
export function newAppContextPlugin({ appVersion, appBuild }: AppLifecycleConfiguration): CorePluginConfiguration {
const contexts = () => {
let entities: SelfDescribingJson[] = [];

if (appVersion) {
// Add application context to all events
if (appBuild) {
entities.push({
schema: MOBILE_APPLICATION_CONTEXT_SCHEMA,
data: {
version: appVersion,
build: appBuild,
},
});
} else {
entities.push({
schema: APPLICATION_CONTEXT_SCHEMA,
data: {
version: appVersion,
},
});
}
}

return entities;
};

return {
plugin: {
contexts,
},
};
}
37 changes: 37 additions & 0 deletions trackers/react-native-tracker/src/plugins/app_install/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { buildSelfDescribingEvent, CorePluginConfiguration, TrackerCore } from '@snowplow/tracker-core';
import { AppLifecycleConfiguration, TrackerConfiguration } from '../../types';
import { APPLICATION_INSTALL_EVENT_SCHEMA } from '../../constants';
import AsyncStorage from '@react-native-async-storage/async-storage';

/**
* Tracks an application install event on the first run of the app.
* Stores the install event in AsyncStorage to prevent tracking on subsequent runs.
*
* Event schema: `iglu:com.snowplowanalytics.mobile/application_install/jsonschema/1-0-0`
*/
export function newAppInstallPlugin(
{ namespace, installAutotracking = false }: TrackerConfiguration & AppLifecycleConfiguration,
core: TrackerCore
): CorePluginConfiguration {
if (installAutotracking) {
// Track install event on first run
const key = `snowplow_${namespace}_install`;
setTimeout(async () => {
const installEvent = await AsyncStorage.getItem(key);
if (!installEvent) {
core.track(
buildSelfDescribingEvent({
event: {
schema: APPLICATION_INSTALL_EVENT_SCHEMA,
data: {},
},
})
);
await AsyncStorage.setItem(key, new Date().toISOString());
}
}, 0);
}
return {
plugin: {},
};
}
97 changes: 97 additions & 0 deletions trackers/react-native-tracker/src/plugins/app_lifecycle/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import {
buildSelfDescribingEvent,
CorePluginConfiguration,
SelfDescribingJson,
TrackerCore,
} from '@snowplow/tracker-core';
import { AppLifecycleConfiguration, EventContext } from '../../types';
import { BACKGROUND_EVENT_SCHEMA, FOREGROUND_EVENT_SCHEMA, LIFECYCLE_CONTEXT_SCHEMA } from '../../constants';
import { AppState } from 'react-native';

export interface AppLifecyclePlugin extends CorePluginConfiguration {
getIsInBackground: () => boolean | undefined;
getBackgroundIndex: () => number | undefined;
getForegroundIndex: () => number | undefined;
}

/**
* Tracks foreground and background events automatically when the app state changes.
* Also adds a lifecycle context to all events with information about the app visibility.
*/
export async function newAppLifecyclePlugin(
{ lifecycleAutotracking = true }: AppLifecycleConfiguration,
core: TrackerCore
): Promise<AppLifecyclePlugin> {
let isInForeground = AppState.currentState !== 'background';
let foregroundIndex = isInForeground ? 1 : 0;
let backgroundIndex = isInForeground ? 0 : 1;
let subscription: ReturnType<typeof AppState.addEventListener> | undefined;

if (lifecycleAutotracking) {
// Subscribe to app state changes and track foreground/background events
subscription = AppState.addEventListener('change', async (nextAppState) => {
if (nextAppState === 'active' && !isInForeground) {
trackForegroundEvent();
}
if (nextAppState === 'background' && isInForeground) {
trackBackgroundEvent();
}
});
}

const contexts = () => {
let entities: SelfDescribingJson[] = [];

if (lifecycleAutotracking) {
// Add lifecycle context to all events
entities.push({
schema: LIFECYCLE_CONTEXT_SCHEMA,
data: {
isVisible: isInForeground,
index: isInForeground ? foregroundIndex : backgroundIndex,
},
});
}

return entities;
};

const deactivatePlugin = () => {
if (subscription) {
subscription.remove();
subscription = undefined;
}
};

const trackForegroundEvent = (contexts?: EventContext[]) => {
if (!isInForeground) {
isInForeground = true;
foregroundIndex += 1;
}
core.track(
buildSelfDescribingEvent({ event: { schema: FOREGROUND_EVENT_SCHEMA, data: { foregroundIndex } } }),
contexts
);
};

const trackBackgroundEvent = (contexts?: EventContext[]) => {
if (isInForeground) {
isInForeground = false;
backgroundIndex += 1;
}
core.track(
buildSelfDescribingEvent({ event: { schema: BACKGROUND_EVENT_SCHEMA, data: { backgroundIndex } } }),
contexts
);
};

return {
getIsInBackground: () => (lifecycleAutotracking ? !isInForeground : undefined),
getBackgroundIndex: () => (lifecycleAutotracking ? backgroundIndex : undefined),
getForegroundIndex: () => (lifecycleAutotracking ? foregroundIndex : undefined),
plugin: {
contexts,
deactivatePlugin,
},
};
}
Loading

0 comments on commit 15c050c

Please sign in to comment.