Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add app install, foreground and background event and application entity tracking #1396

Merged
merged 3 commits into from
Dec 12, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading