Skip to content

Commit

Permalink
Add deep link tracking to the React Native tracker (#1398)
Browse files Browse the repository at this point in the history
  • Loading branch information
matus-tomlein authored Dec 10, 2024
1 parent 45212f0 commit d6a87a0
Show file tree
Hide file tree
Showing 12 changed files with 262 additions and 13 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!-- 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; [DeepLinkConfiguration](./react-native-tracker.deeplinkconfiguration.md) &gt; [deepLinkContext](./react-native-tracker.deeplinkconfiguration.deeplinkcontext.md)

## DeepLinkConfiguration.deepLinkContext property

Whether to track the deep link context entity with information from the deep link received event on the first screen view event.

<b>Signature:</b>

```typescript
deepLinkContext?: boolean;
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<!-- 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; [DeepLinkConfiguration](./react-native-tracker.deeplinkconfiguration.md)

## DeepLinkConfiguration interface

Configuration for deep link tracking

<b>Signature:</b>

```typescript
export interface DeepLinkConfiguration
```

## Properties

| Property | Type | Description |
| --- | --- | --- |
| [deepLinkContext?](./react-native-tracker.deeplinkconfiguration.deeplinkcontext.md) | boolean | <i>(Optional)</i> Whether to track the deep link context entity with information from the deep link received event on the first screen view event. |

Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
| [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 |
| [DeepLinkConfiguration](./react-native-tracker.deeplinkconfiguration.md) | Configuration for deep link tracking |
| [DeviceTimestamp](./react-native-tracker.devicetimestamp.md) | A representation of a Device Timestamp (dtm) |
| [Emitter](./react-native-tracker.emitter.md) | Emitter is responsible for sending events to the collector. It manages the event queue and sends events in batches depending on configuration. |
| [EmitterConfiguration](./react-native-tracker.emitterconfiguration.md) | |
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): Promise<ReactNativeTracker>;
export declare function newTracker(configuration: TrackerConfiguration & EmitterConfiguration & SessionConfiguration & SubjectConfiguration & EventStoreConfiguration & ScreenTrackingConfiguration & PlatformContextConfiguration & DeepLinkConfiguration): 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) | 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) | Configuration for the tracker |

<b>Returns:</b>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export declare type ReactNativeTracker = {
readonly trackStructuredEvent: (argmap: StructuredEvent, contexts?: EventContext[]) => void;
readonly trackPageViewEvent: (argmap: PageViewEvent, contexts?: EventContext[]) => void;
readonly trackTimingEvent: (argmap: TimingProps, contexts?: EventContext[]) => void;
readonly trackDeepLinkReceivedEvent: (argmap: DeepLinkReceivedProps, contexts?: EventContext[]) => void;
readonly trackMessageNotificationEvent: (argmap: MessageNotificationProps, contexts?: EventContext[]) => void;
addGlobalContexts(contexts: Array<ConditionalContextProvider | ContextPrimitive> | Record<string, ConditionalContextProvider | ContextPrimitive>): void;
clearGlobalContexts(): void;
Expand Down Expand Up @@ -46,5 +47,5 @@ export declare type ReactNativeTracker = {
readonly refreshPlatformContext: () => Promise<void>;
};
```
<b>References:</b> [EventContext](./react-native-tracker.eventcontext.md)<!-- -->, [ScreenViewProps](./react-native-tracker.screenviewprops.md)<!-- -->, [ScrollChangedProps](./react-native-tracker.scrollchangedprops.md)<!-- -->, [ListItemViewProps](./react-native-tracker.listitemviewprops.md)<!-- -->, [TimingProps](./react-native-tracker.timingprops.md)<!-- -->, [MessageNotificationProps](./react-native-tracker.messagenotificationprops.md)<!-- -->, [ScreenSize](./react-native-tracker.screensize.md)<!-- -->, [SubjectConfiguration](./react-native-tracker.subjectconfiguration.md)<!-- -->, [SessionState](./react-native-tracker.sessionstate.md)
<b>References:</b> [EventContext](./react-native-tracker.eventcontext.md)<!-- -->, [ScreenViewProps](./react-native-tracker.screenviewprops.md)<!-- -->, [ScrollChangedProps](./react-native-tracker.scrollchangedprops.md)<!-- -->, [ListItemViewProps](./react-native-tracker.listitemviewprops.md)<!-- -->, [TimingProps](./react-native-tracker.timingprops.md)<!-- -->, [DeepLinkReceivedProps](./react-native-tracker.deeplinkreceivedprops.md)<!-- -->, [MessageNotificationProps](./react-native-tracker.messagenotificationprops.md)<!-- -->, [ScreenSize](./react-native-tracker.screensize.md)<!-- -->, [SubjectConfiguration](./react-native-tracker.subjectconfiguration.md)<!-- -->, [SessionState](./react-native-tracker.sessionstate.md)

Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,11 @@ export interface CorePluginConfiguration {
plugin: CorePlugin;
}

// @public
export interface DeepLinkConfiguration {
deepLinkContext?: boolean;
}

// @public
export type DeepLinkReceivedProps = {
url: string;
Expand Down Expand Up @@ -255,7 +260,7 @@ export type MessageNotificationProps = {
};

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

// @public
export interface PageViewEvent {
Expand Down Expand Up @@ -348,6 +353,7 @@ export type ReactNativeTracker = {
readonly trackStructuredEvent: (argmap: StructuredEvent, contexts?: EventContext[]) => void;
readonly trackPageViewEvent: (argmap: PageViewEvent, contexts?: EventContext[]) => void;
readonly trackTimingEvent: (argmap: TimingProps, contexts?: EventContext[]) => void;
readonly trackDeepLinkReceivedEvent: (argmap: DeepLinkReceivedProps, contexts?: EventContext[]) => void;
readonly trackMessageNotificationEvent: (argmap: MessageNotificationProps, contexts?: EventContext[]) => void;
addGlobalContexts(contexts: Array<ConditionalContextProvider | ContextPrimitive> | Record<string, ConditionalContextProvider | ContextPrimitive>): void;
clearGlobalContexts(): 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 deep link tracking to the React Native tracker",
"type": "none"
}
],
"packageName": "@snowplow/react-native-tracker"
}
6 changes: 6 additions & 0 deletions trackers/react-native-tracker/src/constants.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
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 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 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 PAGE_URL_PROPERTY = 'url';
export const PAGE_REFERRER_PROPERTY = 'refr';
68 changes: 68 additions & 0 deletions trackers/react-native-tracker/src/plugins/deep_links/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { buildSelfDescribingEvent, CorePluginConfiguration, PayloadBuilder, TrackerCore } from '@snowplow/tracker-core';
import { DeepLinkConfiguration, DeepLinkReceivedProps, EventContext } from '../../types';
import { DEEP_LINK_ENTITY_SCHEMA, DEEP_LINK_RECEIVED_EVENT_SCHEMA, PAGE_REFERRER_PROPERTY, PAGE_URL_PROPERTY, SCREEN_VIEW_EVENT_SCHEMA } from '../../constants';
import { getUsefulSchema } from '../../utils';

interface DeepLinksPlugin extends CorePluginConfiguration {
trackDeepLinkReceivedEvent: (argmap: DeepLinkReceivedProps, contexts?: EventContext[]) => void;
}

export function newDeepLinksPlugin(
{ deepLinkContext = true }: DeepLinkConfiguration,
core: TrackerCore
): DeepLinksPlugin {
let lastDeepLink: DeepLinkReceivedProps | undefined;

const beforeTrack = (payloadBuilder: PayloadBuilder) => {
const schema = getUsefulSchema(payloadBuilder);

if (schema == SCREEN_VIEW_EVENT_SCHEMA && lastDeepLink) {
const { url, referrer } = lastDeepLink;
if (url) {
payloadBuilder.add(PAGE_URL_PROPERTY, url);
}
if (referrer) {
payloadBuilder.add(PAGE_REFERRER_PROPERTY, referrer);
}

if (deepLinkContext) {
payloadBuilder.addContextEntity({
schema: DEEP_LINK_ENTITY_SCHEMA,
data: lastDeepLink,
});
}

// Clear the last deep link since we only add it to the first screen view event
lastDeepLink = undefined;
}
};

const trackDeepLinkReceivedEvent = (argmap: DeepLinkReceivedProps, contexts?: EventContext[]) => {
lastDeepLink = argmap;

const payload = buildSelfDescribingEvent({
event: {
schema: DEEP_LINK_RECEIVED_EVENT_SCHEMA,
data: argmap,
},
});

// Add atomic event properties
const { url, referrer } = argmap;
if (url) {
payload.add(PAGE_URL_PROPERTY, url);
}
if (referrer) {
payload.add(PAGE_REFERRER_PROPERTY, referrer);
}

core.track(payload, contexts);
};

return {
trackDeepLinkReceivedEvent,
plugin: {
beforeTrack,
},
};
}
9 changes: 8 additions & 1 deletion trackers/react-native-tracker/src/tracker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
} from '@snowplow/browser-plugin-screen-tracking';

import {
DeepLinkConfiguration,
EventContext,
EventStoreConfiguration,
ListItemViewProps,
Expand All @@ -25,6 +26,7 @@ import {
TrackerConfiguration,
} from './types';
import { newSessionPlugin } from './plugins/session';
import { newDeepLinksPlugin } from './plugins/deep_links';
import { newPlugins } from './plugins';
import { newPlatformContextPlugin } from './plugins/platform_context';

Expand All @@ -42,7 +44,8 @@ export async function newTracker(
SubjectConfiguration &
EventStoreConfiguration &
ScreenTrackingConfiguration &
PlatformContextConfiguration
PlatformContextConfiguration &
DeepLinkConfiguration
): Promise<ReactNativeTracker> {
const { namespace, appId, encodeBase64 = false } = configuration;
if (configuration.eventStore === undefined) {
Expand All @@ -68,6 +71,9 @@ export async function newTracker(
const sessionPlugin = await newSessionPlugin(configuration);
addPlugin(sessionPlugin);

const deepLinksPlugin = await newDeepLinksPlugin(configuration, core);
addPlugin(deepLinksPlugin);

const subject = newSubject(core, configuration);
addPlugin(subject.subjectPlugin);

Expand Down Expand Up @@ -121,6 +127,7 @@ export async function newTracker(
},
[namespace]
),
trackDeepLinkReceivedEvent: deepLinksPlugin.trackDeepLinkReceivedEvent,
};
initializedTrackers[namespace] = { tracker, core };

Expand Down
26 changes: 18 additions & 8 deletions trackers/react-native-tracker/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,17 @@ export interface PlatformContextConfiguration {
platformContextRetriever?: PlatformContextRetriever;
}

/**
* Configuration for deep link tracking
*/
export interface DeepLinkConfiguration {
/**
* Whether to track the deep link context entity with information from the deep link received event on the first screen view event.
* @defaultValue true
*/
deepLinkContext?: boolean;
}

/**
* Configuration of subject properties tracked with events
*/
Expand Down Expand Up @@ -641,14 +652,13 @@ export type ReactNativeTracker = {
*/
readonly trackTimingEvent: (argmap: TimingProps, contexts?: EventContext[]) => void;

// TODO:
// /**
// * Tracks a deep link received event
// *
// * @param argmap - The deep link received event properties
// * @param contexts - The array of event contexts
// */
// readonly trackDeepLinkReceivedEvent: (argmap: DeepLinkReceivedProps, contexts?: EventContext[]) => void;
/**
* Tracks a deep link received event
*
* @param argmap - The deep link received event properties
* @param contexts - The array of event contexts
*/
readonly trackDeepLinkReceivedEvent: (argmap: DeepLinkReceivedProps, contexts?: EventContext[]) => void;

/**
* Tracks a message notification event
Expand Down
107 changes: 107 additions & 0 deletions trackers/react-native-tracker/test/plugins/deep_links.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import {
DEEP_LINK_ENTITY_SCHEMA,
DEEP_LINK_RECEIVED_EVENT_SCHEMA,
SCREEN_VIEW_EVENT_SCHEMA,
} from '../../src/constants';
import { newDeepLinksPlugin } from '../../src/plugins/deep_links';
import { buildSelfDescribingEvent, Payload, trackerCore } from '@snowplow/tracker-core';

describe('Deep Link plugin', () => {
it('adds the url and refr properties on the deep link event', () => {
const payloads: Payload[] = [];
const tracker = trackerCore({
callback: (pb) => payloads.push(pb.build()),
base64: false,
});
const deepLinksPlugin = newDeepLinksPlugin({}, tracker);
tracker.addPlugin(deepLinksPlugin);

deepLinksPlugin.trackDeepLinkReceivedEvent({
url: 'http://url.com',
referrer: 'http://referrer.com',
});

expect(payloads.length).toBe(1);
const [{ url, refr, ue_pr }] = payloads as any;
expect(url).toBe('http://url.com');
expect(refr).toBe('http://referrer.com');

const { data } = JSON.parse(ue_pr);
expect(data.schema).toBe(DEEP_LINK_RECEIVED_EVENT_SCHEMA);
expect(data.data.url).toBe('http://url.com');
expect(data.data.referrer).toBe('http://referrer.com');
});

it('adds the deep link context to the first screen view event', () => {
const payloads: Payload[] = [];
const tracker = trackerCore({
callback: (pb) => payloads.push(pb.build()),
base64: false,
});
const deepLinksPlugin = newDeepLinksPlugin({}, tracker);
tracker.addPlugin(deepLinksPlugin);

deepLinksPlugin.trackDeepLinkReceivedEvent({
url: 'http://url.com',
referrer: 'http://referrer.com',
});
tracker.track(
buildSelfDescribingEvent({
event: {
schema: SCREEN_VIEW_EVENT_SCHEMA,
data: {},
},
})
);
tracker.track(
buildSelfDescribingEvent({
event: {
schema: SCREEN_VIEW_EVENT_SCHEMA,
data: {},
},
})
);

expect(payloads.length).toBe(3);
const [, { url, refr, co }, { co: co2 }] = payloads as any;
expect(url).toBe('http://url.com');
expect(refr).toBe('http://referrer.com');
expect(co).not.toBeUndefined();
const entities = JSON.parse(co).data;
const deepLinkEntity = entities.find((entity: any) => entity.schema === DEEP_LINK_ENTITY_SCHEMA);
expect(deepLinkEntity).not.toBeUndefined();
expect(deepLinkEntity.data.url).toBe('http://url.com');
expect(deepLinkEntity.data.referrer).toBe('http://referrer.com');

expect(co2 ?? '').not.toContain(DEEP_LINK_ENTITY_SCHEMA);
});

it('does not add the deep link entity if disabled', () => {
const payloads: Payload[] = [];
const tracker = trackerCore({
callback: (pb) => payloads.push(pb.build()),
base64: false,
});
const deepLinksPlugin = newDeepLinksPlugin({ deepLinkContext: false }, tracker);
tracker.addPlugin(deepLinksPlugin);

deepLinksPlugin.trackDeepLinkReceivedEvent({
url: 'http://url.com',
referrer: 'http://referrer.com',
});
tracker.track(
buildSelfDescribingEvent({
event: {
schema: SCREEN_VIEW_EVENT_SCHEMA,
data: {},
},
})
);

expect(payloads.length).toBe(2);
const [, { url, refr, co }] = payloads as any;
expect(url).toBe('http://url.com');
expect(refr).toBe('http://referrer.com');
expect(co ?? '').not.toContain(DEEP_LINK_ENTITY_SCHEMA);
});
});

0 comments on commit d6a87a0

Please sign in to comment.