diff --git a/api-docs/docs/react-native-tracker/markdown/react-native-tracker.getalltrackers.md b/api-docs/docs/react-native-tracker/markdown/react-native-tracker.getalltrackers.md
new file mode 100644
index 000000000..dd12938e2
--- /dev/null
+++ b/api-docs/docs/react-native-tracker/markdown/react-native-tracker.getalltrackers.md
@@ -0,0 +1,19 @@
+
+
+[Home](./index.md) > [@snowplow/react-native-tracker](./react-native-tracker.md) > [getAllTrackers](./react-native-tracker.getalltrackers.md)
+
+## getAllTrackers() function
+
+Retrieves all initialized trackers
+
+Signature:
+
+```typescript
+export declare function getAllTrackers(): ReactNativeTracker[];
+```
+Returns:
+
+[ReactNativeTracker](./react-native-tracker.reactnativetracker.md)\[\]
+
+All initialized trackers
+
diff --git a/api-docs/docs/react-native-tracker/markdown/react-native-tracker.getwebviewcallback.md b/api-docs/docs/react-native-tracker/markdown/react-native-tracker.getwebviewcallback.md
new file mode 100644
index 000000000..e5313e453
--- /dev/null
+++ b/api-docs/docs/react-native-tracker/markdown/react-native-tracker.getwebviewcallback.md
@@ -0,0 +1,25 @@
+
+
+[Home](./index.md) > [@snowplow/react-native-tracker](./react-native-tracker.md) > [getWebViewCallback](./react-native-tracker.getwebviewcallback.md)
+
+## getWebViewCallback() function
+
+Enables tracking events from apps rendered in react-native-webview components. The apps need to use the Snowplow WebView tracker to track the events.
+
+To subscribe for the events, set the `onMessage` attribute: <WebView onMessage={getWebViewCallback()} ... />
+
+Signature:
+
+```typescript
+export declare function getWebViewCallback(): (message: {
+ nativeEvent: {
+ data: string;
+ };
+}) => void;
+```
+Returns:
+
+(message: { nativeEvent: { data: string; }; }) => void
+
+Callback to subscribe for events from Web views tracked using the Snowplow WebView tracker.
+
diff --git a/api-docs/docs/react-native-tracker/markdown/react-native-tracker.md b/api-docs/docs/react-native-tracker/markdown/react-native-tracker.md
index 4f68312df..c7d351cd6 100644
--- a/api-docs/docs/react-native-tracker/markdown/react-native-tracker.md
+++ b/api-docs/docs/react-native-tracker/markdown/react-native-tracker.md
@@ -15,7 +15,9 @@
| Function | Description |
| --- | --- |
+| [getAllTrackers()](./react-native-tracker.getalltrackers.md) | Retrieves all initialized trackers |
| [getTracker(trackerNamespace)](./react-native-tracker.gettracker.md) | Retrieves an initialized tracker given its namespace |
+| [getWebViewCallback()](./react-native-tracker.getwebviewcallback.md) | Enables tracking events from apps rendered in react-native-webview components. The apps need to use the Snowplow WebView tracker to track the events.To subscribe for the events, set the onMessage
attribute: <WebView onMessage={getWebViewCallback()} ... /> |
| [newTracker(configuration)](./react-native-tracker.newtracker.md) | Creates a new tracker instance with the given configuration |
| [removeAllTrackers()](./react-native-tracker.removealltrackers.md) | Removes all initialized trackers |
| [removeTracker(trackerNamespace)](./react-native-tracker.removetracker.md) | Removes a tracker given its namespace |
@@ -46,7 +48,6 @@
| [RuleSet](./react-native-tracker.ruleset.md) | A ruleset has accept or reject properties that contain rules for matching Iglu schema URIs |
| [SessionConfiguration](./react-native-tracker.sessionconfiguration.md) | Configuration for session tracking |
| [SessionState](./react-native-tracker.sessionstate.md) | Current session state that is tracked in events. |
-| [StructuredEvent](./react-native-tracker.structuredevent.md) | A Structured Event A classic style of event tracking, allows for easier movement between analytics systems. A loosely typed event, creating a Self Describing event is preferred, but useful for interoperability. |
| [SubjectConfiguration](./react-native-tracker.subjectconfiguration.md) | Configuration of subject properties tracked with events |
| [TrackerConfiguration](./react-native-tracker.trackerconfiguration.md) | The configuration object for initialising the tracker |
| [TrackerCore](./react-native-tracker.trackercore.md) | Export interface containing all Core functions |
@@ -84,7 +85,8 @@
| [ScreenSize](./react-native-tracker.screensize.md) | Screen size in pixels |
| [ScreenViewProps](./react-native-tracker.screenviewprops.md) | ScreenView event properties schema: iglu:com.snowplowanalytics.mobile/screen\_view/jsonschema/1-0-0 |
| [ScrollChangedProps](./react-native-tracker.scrollchangedprops.md) | Event tracked when a scroll view's scroll position changes. If screen engagement tracking is enabled, the scroll changed events will be aggregated into a screen_summary
entity.Schema: iglu:com.snowplowanalytics.mobile/scroll_changed/jsonschema/1-0-0
|
-| [SelfDescribingJson](./react-native-tracker.selfdescribingjson.md) | Export interface for any Self-Describing JSON such as context or Self Describing events |
+| [SelfDescribing](./react-native-tracker.selfdescribing.md) | Interface for any self-describing JSON such as context entities or self-describing events |
+| [StructuredProps](./react-native-tracker.structuredprops.md) | A Structured Event A classic style of event tracking, allows for easier movement between analytics systems. A loosely typed event, creating a Self Describing event is preferred, but useful for interoperability. |
| [Timestamp](./react-native-tracker.timestamp.md) | Algebraic datatype representing possible timestamp type choice |
| [TimingProps](./react-native-tracker.timingprops.md) | Timing event properties |
| [Trigger](./react-native-tracker.trigger.md) | Trigger for MessageNotification event |
diff --git a/api-docs/docs/react-native-tracker/markdown/react-native-tracker.reactnativetracker.md b/api-docs/docs/react-native-tracker/markdown/react-native-tracker.reactnativetracker.md
index 5785f9ad5..a25a41512 100644
--- a/api-docs/docs/react-native-tracker/markdown/react-native-tracker.reactnativetracker.md
+++ b/api-docs/docs/react-native-tracker/markdown/react-native-tracker.reactnativetracker.md
@@ -11,11 +11,11 @@ The ReactNativeTracker type
```typescript
export declare type ReactNativeTracker = {
namespace: string;
- readonly trackSelfDescribingEvent: = Record>(argmap: SelfDescribingJson, contexts?: EventContext[]) => void;
+ readonly trackSelfDescribingEvent: = Record>(argmap: SelfDescribing, contexts?: EventContext[]) => void;
readonly trackScreenViewEvent: (argmap: ScreenViewProps, contexts?: EventContext[]) => void;
readonly trackScrollChangedEvent: (argmap: ScrollChangedProps, contexts?: EventContext[]) => void;
readonly trackListItemViewEvent: (argmap: ListItemViewProps, contexts?: EventContext[]) => void;
- readonly trackStructuredEvent: (argmap: StructuredEvent, contexts?: EventContext[]) => void;
+ readonly trackStructuredEvent: (argmap: StructuredProps, contexts?: EventContext[]) => void;
readonly trackPageViewEvent: (argmap: PageViewEvent, contexts?: EventContext[]) => void;
readonly trackTimingEvent: (argmap: TimingProps, contexts?: EventContext[]) => void;
readonly trackDeepLinkReceivedEvent: (argmap: DeepLinkReceivedProps, contexts?: EventContext[]) => void;
@@ -47,5 +47,5 @@ export declare type ReactNativeTracker = {
readonly refreshPlatformContext: () => Promise;
};
```
-References: [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)
+References: [SelfDescribing](./react-native-tracker.selfdescribing.md), [EventContext](./react-native-tracker.eventcontext.md), [ScreenViewProps](./react-native-tracker.screenviewprops.md), [ScrollChangedProps](./react-native-tracker.scrollchangedprops.md), [ListItemViewProps](./react-native-tracker.listitemviewprops.md), [StructuredProps](./react-native-tracker.structuredprops.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)
diff --git a/api-docs/docs/react-native-tracker/markdown/react-native-tracker.selfdescribing.md b/api-docs/docs/react-native-tracker/markdown/react-native-tracker.selfdescribing.md
new file mode 100644
index 000000000..1efbed825
--- /dev/null
+++ b/api-docs/docs/react-native-tracker/markdown/react-native-tracker.selfdescribing.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [@snowplow/react-native-tracker](./react-native-tracker.md) > [SelfDescribing](./react-native-tracker.selfdescribing.md)
+
+## SelfDescribing type
+
+Interface for any self-describing JSON such as context entities or self-describing events
+
+Signature:
+
+```typescript
+export declare type SelfDescribing> = SelfDescribingJson;
+```
diff --git a/api-docs/docs/react-native-tracker/markdown/react-native-tracker.selfdescribingjson.md b/api-docs/docs/react-native-tracker/markdown/react-native-tracker.selfdescribingjson.md
deleted file mode 100644
index 0a32034ac..000000000
--- a/api-docs/docs/react-native-tracker/markdown/react-native-tracker.selfdescribingjson.md
+++ /dev/null
@@ -1,16 +0,0 @@
-
-
-[Home](./index.md) > [@snowplow/react-native-tracker](./react-native-tracker.md) > [SelfDescribingJson](./react-native-tracker.selfdescribingjson.md)
-
-## SelfDescribingJson type
-
-Export interface for any Self-Describing JSON such as context or Self Describing events
-
-Signature:
-
-```typescript
-type SelfDescribingJson> = {
- schema: string;
- data: T extends any[] ? never : T extends {} ? T : never;
-};
-```
diff --git a/api-docs/docs/react-native-tracker/markdown/react-native-tracker.structuredevent.action.md b/api-docs/docs/react-native-tracker/markdown/react-native-tracker.structuredevent.action.md
deleted file mode 100644
index a1e9ec061..000000000
--- a/api-docs/docs/react-native-tracker/markdown/react-native-tracker.structuredevent.action.md
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-[Home](./index.md) > [@snowplow/react-native-tracker](./react-native-tracker.md) > [StructuredEvent](./react-native-tracker.structuredevent.md) > [action](./react-native-tracker.structuredevent.action.md)
-
-## StructuredEvent.action property
-
-Signature:
-
-```typescript
-action: string;
-```
diff --git a/api-docs/docs/react-native-tracker/markdown/react-native-tracker.structuredevent.category.md b/api-docs/docs/react-native-tracker/markdown/react-native-tracker.structuredevent.category.md
deleted file mode 100644
index 0e1a2cbb6..000000000
--- a/api-docs/docs/react-native-tracker/markdown/react-native-tracker.structuredevent.category.md
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-[Home](./index.md) > [@snowplow/react-native-tracker](./react-native-tracker.md) > [StructuredEvent](./react-native-tracker.structuredevent.md) > [category](./react-native-tracker.structuredevent.category.md)
-
-## StructuredEvent.category property
-
-Signature:
-
-```typescript
-category: string;
-```
diff --git a/api-docs/docs/react-native-tracker/markdown/react-native-tracker.structuredevent.label.md b/api-docs/docs/react-native-tracker/markdown/react-native-tracker.structuredevent.label.md
deleted file mode 100644
index 2d8731a8d..000000000
--- a/api-docs/docs/react-native-tracker/markdown/react-native-tracker.structuredevent.label.md
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-[Home](./index.md) > [@snowplow/react-native-tracker](./react-native-tracker.md) > [StructuredEvent](./react-native-tracker.structuredevent.md) > [label](./react-native-tracker.structuredevent.label.md)
-
-## StructuredEvent.label property
-
-Signature:
-
-```typescript
-label?: string;
-```
diff --git a/api-docs/docs/react-native-tracker/markdown/react-native-tracker.structuredevent.md b/api-docs/docs/react-native-tracker/markdown/react-native-tracker.structuredevent.md
deleted file mode 100644
index 0ce7bdde8..000000000
--- a/api-docs/docs/react-native-tracker/markdown/react-native-tracker.structuredevent.md
+++ /dev/null
@@ -1,24 +0,0 @@
-
-
-[Home](./index.md) > [@snowplow/react-native-tracker](./react-native-tracker.md) > [StructuredEvent](./react-native-tracker.structuredevent.md)
-
-## StructuredEvent interface
-
-A Structured Event A classic style of event tracking, allows for easier movement between analytics systems. A loosely typed event, creating a Self Describing event is preferred, but useful for interoperability.
-
-Signature:
-
-```typescript
-interface StructuredEvent
-```
-
-## Properties
-
-| Property | Type | Description |
-| --- | --- | --- |
-| [action](./react-native-tracker.structuredevent.action.md) | string | |
-| [category](./react-native-tracker.structuredevent.category.md) | string | |
-| [label?](./react-native-tracker.structuredevent.label.md) | string | (Optional) |
-| [property?](./react-native-tracker.structuredevent.property.md) | string | (Optional) |
-| [value?](./react-native-tracker.structuredevent.value.md) | number | (Optional) |
-
diff --git a/api-docs/docs/react-native-tracker/markdown/react-native-tracker.structuredevent.property.md b/api-docs/docs/react-native-tracker/markdown/react-native-tracker.structuredevent.property.md
deleted file mode 100644
index 1015c9a8e..000000000
--- a/api-docs/docs/react-native-tracker/markdown/react-native-tracker.structuredevent.property.md
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-[Home](./index.md) > [@snowplow/react-native-tracker](./react-native-tracker.md) > [StructuredEvent](./react-native-tracker.structuredevent.md) > [property](./react-native-tracker.structuredevent.property.md)
-
-## StructuredEvent.property property
-
-Signature:
-
-```typescript
-property?: string;
-```
diff --git a/api-docs/docs/react-native-tracker/markdown/react-native-tracker.structuredevent.value.md b/api-docs/docs/react-native-tracker/markdown/react-native-tracker.structuredevent.value.md
deleted file mode 100644
index e7673c4ca..000000000
--- a/api-docs/docs/react-native-tracker/markdown/react-native-tracker.structuredevent.value.md
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-[Home](./index.md) > [@snowplow/react-native-tracker](./react-native-tracker.md) > [StructuredEvent](./react-native-tracker.structuredevent.md) > [value](./react-native-tracker.structuredevent.value.md)
-
-## StructuredEvent.value property
-
-Signature:
-
-```typescript
-value?: number;
-```
diff --git a/api-docs/docs/react-native-tracker/markdown/react-native-tracker.structuredprops.md b/api-docs/docs/react-native-tracker/markdown/react-native-tracker.structuredprops.md
new file mode 100644
index 000000000..2c4fda4c3
--- /dev/null
+++ b/api-docs/docs/react-native-tracker/markdown/react-native-tracker.structuredprops.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [@snowplow/react-native-tracker](./react-native-tracker.md) > [StructuredProps](./react-native-tracker.structuredprops.md)
+
+## StructuredProps type
+
+A Structured Event A classic style of event tracking, allows for easier movement between analytics systems. A loosely typed event, creating a Self Describing event is preferred, but useful for interoperability.
+
+Signature:
+
+```typescript
+export declare type StructuredProps = StructuredEvent;
+```
diff --git a/api-docs/docs/react-native-tracker/react-native-tracker.api.md b/api-docs/docs/react-native-tracker/react-native-tracker.api.md
index f2f785ae1..82c214e95 100644
--- a/api-docs/docs/react-native-tracker/react-native-tracker.api.md
+++ b/api-docs/docs/react-native-tracker/react-native-tracker.api.md
@@ -16,6 +16,8 @@ export type ConditionalContextProvider = FilterProvider | RuleSetProvider;
// @public
export type ContextFilter = (args?: ContextEvent) => boolean;
+// Warning: (ae-forgotten-export) The symbol "SelfDescribingJson" needs to be exported by the entry point index.d.ts
+//
// @public
export type ContextGenerator = (args?: ContextEvent) => SelfDescribingJson | SelfDescribingJson[] | undefined;
@@ -189,9 +191,19 @@ export interface FormFocusOrChangeEvent {
value: string | null;
}
+// @public
+export function getAllTrackers(): ReactNativeTracker[];
+
// @public
export function getTracker(trackerNamespace: string): ReactNativeTracker | undefined;
+// @public
+export function getWebViewCallback(): (message: {
+ nativeEvent: {
+ data: string;
+ };
+}) => void;
+
// @public
export type JsonProcessor = (payloadBuilder: PayloadBuilder, jsonForProcessing: EventJson, contextEntitiesForProcessing: SelfDescribingJson[]) => void;
@@ -346,11 +358,11 @@ export interface PlatformContextRetriever {
// @public
export type ReactNativeTracker = {
namespace: string;
- readonly trackSelfDescribingEvent: = Record>(argmap: SelfDescribingJson, contexts?: EventContext[]) => void;
+ readonly trackSelfDescribingEvent: = Record>(argmap: SelfDescribing, contexts?: EventContext[]) => void;
readonly trackScreenViewEvent: (argmap: ScreenViewProps, contexts?: EventContext[]) => void;
readonly trackScrollChangedEvent: (argmap: ScrollChangedProps, contexts?: EventContext[]) => void;
readonly trackListItemViewEvent: (argmap: ListItemViewProps, contexts?: EventContext[]) => void;
- readonly trackStructuredEvent: (argmap: StructuredEvent, contexts?: EventContext[]) => void;
+ readonly trackStructuredEvent: (argmap: StructuredProps, contexts?: EventContext[]) => void;
readonly trackPageViewEvent: (argmap: PageViewEvent, contexts?: EventContext[]) => void;
readonly trackTimingEvent: (argmap: TimingProps, contexts?: EventContext[]) => void;
readonly trackDeepLinkReceivedEvent: (argmap: DeepLinkReceivedProps, contexts?: EventContext[]) => void;
@@ -435,10 +447,7 @@ export type ScrollChangedProps = {
};
// @public
-export type SelfDescribingJson> = {
- schema: string;
- data: T extends any[] ? never : T extends {} ? T : never;
-};
+export type SelfDescribing> = SelfDescribingJson;
// @public
export interface SessionConfiguration {
@@ -458,19 +467,10 @@ export interface SessionState {
userId: string;
}
+// Warning: (ae-forgotten-export) The symbol "StructuredEvent" needs to be exported by the entry point index.d.ts
+//
// @public
-export interface StructuredEvent {
- // (undocumented)
- action: string;
- // (undocumented)
- category: string;
- // (undocumented)
- label?: string;
- // (undocumented)
- property?: string;
- // (undocumented)
- value?: number;
-}
+export type StructuredProps = StructuredEvent;
// @public
export interface SubjectConfiguration {
diff --git a/trackers/react-native-tracker/src/events.ts b/trackers/react-native-tracker/src/events.ts
index 1195b83f2..02e1ef523 100644
--- a/trackers/react-native-tracker/src/events.ts
+++ b/trackers/react-native-tracker/src/events.ts
@@ -4,10 +4,9 @@ import {
buildStructEvent,
PageViewEvent,
SelfDescribingJson,
- StructuredEvent,
TrackerCore,
} from '@snowplow/tracker-core';
-import { EventContext, MessageNotificationProps, TimingProps } from './types';
+import { EventContext, MessageNotificationProps, StructuredProps, TimingProps } from './types';
export function newTrackEventFunctions(core: TrackerCore) {
const trackSelfDescribingEvent = = Record>(
@@ -17,7 +16,7 @@ export function newTrackEventFunctions(core: TrackerCore) {
core.track(buildSelfDescribingEvent({ event: argmap }), contexts);
};
- const trackStructuredEvent = (argmap: StructuredEvent, contexts?: EventContext[]) => {
+ const trackStructuredEvent = (argmap: StructuredProps, contexts?: EventContext[]) => {
return core.track(buildStructEvent(argmap), contexts)?.eid;
};
diff --git a/trackers/react-native-tracker/src/index.ts b/trackers/react-native-tracker/src/index.ts
index 6c29689cb..a4cdb5590 100644
--- a/trackers/react-native-tracker/src/index.ts
+++ b/trackers/react-native-tracker/src/index.ts
@@ -2,4 +2,5 @@
import 'react-native-get-random-values';
export * from './types';
-export * from './tracker';
+export { newTracker, getTracker, getAllTrackers, removeTracker, removeAllTrackers } from './tracker';
+export * from './web_view_interface';
diff --git a/trackers/react-native-tracker/src/tracker.ts b/trackers/react-native-tracker/src/tracker.ts
index ef6608f92..6eafe7ae9 100644
--- a/trackers/react-native-tracker/src/tracker.ts
+++ b/trackers/react-native-tracker/src/tracker.ts
@@ -143,6 +143,31 @@ export function getTracker(trackerNamespace: string): ReactNativeTracker | undef
return initializedTrackers[trackerNamespace]?.tracker;
}
+/**
+ * Retrieves all initialized trackers
+ * @returns All initialized trackers
+ */
+export function getAllTrackers(): ReactNativeTracker[] {
+ return Object.values(initializedTrackers).map(({ tracker }) => tracker);
+}
+
+/**
+ * Internal function to retrieve the tracker core given its namespace
+ * @param trackerNamespace - Tracker namespace
+ * @returns Tracker core if exists
+ */
+export function getTrackerCore(trackerNamespace: string): TrackerCore | undefined {
+ return initializedTrackers[trackerNamespace]?.core;
+}
+
+/**
+ * Internal function to retrieve all initialized tracker cores
+ * @returns All initialized tracker cores
+ */
+export function getAllTrackerCores(): TrackerCore[] {
+ return Object.values(initializedTrackers).map(({ core }) => core);
+}
+
/**
* Removes a tracker given its namespace
*
diff --git a/trackers/react-native-tracker/src/types.ts b/trackers/react-native-tracker/src/types.ts
index 6441399e5..e5e72b5f2 100755
--- a/trackers/react-native-tracker/src/types.ts
+++ b/trackers/react-native-tracker/src/types.ts
@@ -584,6 +584,20 @@ export interface SessionState {
firstEventTimestamp?: string;
}
+/**
+ * A Structured Event
+ * A classic style of event tracking, allows for easier movement between analytics
+ * systems. A loosely typed event, creating a Self Describing event is preferred, but
+ * useful for interoperability.
+ */
+export type StructuredProps = StructuredEvent;
+
+/**
+ * Interface for any self-describing JSON such as context entities or self-describing events
+ * @typeParam T - The type of the data object within a SelfDescribingJson
+ */
+export type SelfDescribing> = SelfDescribingJson;
+
/**
* The ReactNativeTracker type
*/
@@ -600,7 +614,7 @@ export type ReactNativeTracker = {
* @typeParam TData - The type of the data object within the SelfDescribing object
*/
readonly trackSelfDescribingEvent: = Record>(
- argmap: SelfDescribingJson,
+ argmap: SelfDescribing,
contexts?: EventContext[]
) => void;
@@ -634,7 +648,7 @@ export type ReactNativeTracker = {
* @param argmap - The structured event properties
* @param contexts - The array of event contexts
*/
- readonly trackStructuredEvent: (argmap: StructuredEvent, contexts?: EventContext[]) => void;
+ readonly trackStructuredEvent: (argmap: StructuredProps, contexts?: EventContext[]) => void;
/**
* Tracks a page-view event
@@ -857,9 +871,7 @@ export type ReactNativeTracker = {
export {
version,
PageViewEvent,
- StructuredEvent,
FormFocusOrChangeEvent,
- SelfDescribingJson,
Timestamp,
PayloadBuilder,
Payload,
diff --git a/trackers/react-native-tracker/src/web_view_interface.ts b/trackers/react-native-tracker/src/web_view_interface.ts
new file mode 100644
index 000000000..ec69dec29
--- /dev/null
+++ b/trackers/react-native-tracker/src/web_view_interface.ts
@@ -0,0 +1,215 @@
+'use strict';
+
+import { buildSelfDescribingEvent, payloadBuilder } from '@snowplow/tracker-core';
+import { getAllTrackerCores, getAllTrackers, getTracker, getTrackerCore } from './tracker';
+import type {
+ ScreenViewProps,
+ SelfDescribing,
+ StructuredProps,
+ ReactNativeTracker,
+ TrackerCore,
+ PayloadBuilder,
+ Payload,
+} from './types';
+
+/**
+ * Internal event type for events with payload properties tracked using the WebView tracker.
+ */
+interface WebViewEvent {
+ selfDescribingEventData?: SelfDescribing;
+ eventName?: string;
+ trackerVersion?: string;
+ useragent?: string;
+ pageUrl?: string;
+ pageTitle?: string;
+ referrer?: string;
+ category?: string;
+ action?: string;
+ label?: string;
+ property?: string;
+ value?: number;
+ pingXOffsetMin?: number;
+ pingXOffsetMax?: number;
+ pingYOffsetMin?: number;
+ pingYOffsetMax?: number;
+}
+
+/**
+ * Internal event type for page views tracked using the WebView tracker.
+ */
+interface WebViewPageViewEvent {
+ title?: string | null;
+ url?: string;
+ referrer?: string;
+}
+
+/**
+ * Internal type exchanged in messages received from the WebView tracker in Web views through the web view callback.
+ */
+type WebViewMessage = {
+ command: 'trackSelfDescribingEvent' | 'trackStructEvent' | 'trackPageView' | 'trackScreenView' | 'trackWebViewEvent';
+ event: StructuredProps | SelfDescribing | ScreenViewProps | WebViewPageViewEvent | WebViewEvent;
+ context?: Array | null;
+ trackers?: Array;
+};
+
+function forEachTracker(trackers: Array | undefined, iterator: (tracker: ReactNativeTracker) => void): void {
+ if (trackers && trackers.length > 0) {
+ trackers
+ .map(getTracker)
+ .filter((t) => t !== undefined)
+ .map((t) => t!)
+ .forEach(iterator);
+ } else {
+ getAllTrackers().forEach(iterator);
+ }
+}
+
+function forEachTrackerCore(trackers: Array | undefined, iterator: (tracker: TrackerCore) => void): void {
+ if (trackers && trackers.length > 0) {
+ trackers
+ .map(getTrackerCore)
+ .filter((t) => t !== undefined)
+ .map((t) => t!)
+ .forEach(iterator);
+ } else {
+ getAllTrackerCores().forEach(iterator);
+ }
+}
+
+/**
+ * Wrapper around the PayloadBuilder that disables overriding property values.
+ * This is to prevent the tracker overriding values like tracker version set in the WebView.
+ */
+function webViewPayloadBuilder(pb: PayloadBuilder): PayloadBuilder {
+ const addedKeys = new Set();
+
+ const add = (key: string, value: unknown): void => {
+ if (!addedKeys.has(key)) {
+ addedKeys.add(key);
+ pb.add(key, value);
+ }
+ };
+
+ const addDict = (dict: Payload): void => {
+ for (const key in dict) {
+ if (Object.prototype.hasOwnProperty.call(dict, key)) {
+ add(key, dict[key]);
+ }
+ }
+ };
+
+ return {
+ ...pb,
+ add,
+ addDict,
+ };
+}
+
+/**
+ * Enables tracking events from apps rendered in react-native-webview components.
+ * The apps need to use the Snowplow WebView tracker to track the events.
+ *
+ * To subscribe for the events, set the `onMessage` attribute:
+ *
+ *
+ * @returns Callback to subscribe for events from Web views tracked using the Snowplow WebView tracker.
+ */
+export function getWebViewCallback() {
+ return (message: { nativeEvent: { data: string } }): void => {
+ const data = JSON.parse(message.nativeEvent.data) as WebViewMessage;
+ switch (data.command) {
+ case 'trackSelfDescribingEvent':
+ forEachTracker(data.trackers, (tracker) => {
+ tracker.trackSelfDescribingEvent(data.event as SelfDescribing, data.context || []);
+ });
+ break;
+
+ case 'trackStructEvent':
+ forEachTracker(data.trackers, (tracker) => {
+ tracker.trackStructuredEvent(data.event as StructuredProps, data.context || []);
+ });
+ break;
+
+ case 'trackPageView':
+ forEachTracker(data.trackers, (tracker) => {
+ const event = data.event as WebViewPageViewEvent;
+ tracker.trackPageViewEvent({
+ pageTitle: event.title ?? '',
+ pageUrl: event.url ?? '',
+ referrer: event.referrer,
+ });
+ });
+ break;
+
+ case 'trackScreenView':
+ forEachTracker(data.trackers, (tracker) => {
+ tracker.trackScreenViewEvent(data.event as ScreenViewProps, data.context || []);
+ });
+ break;
+
+ case 'trackWebViewEvent':
+ forEachTrackerCore(data.trackers, (tracker) => {
+ const event = data.event as WebViewEvent;
+ let pb: PayloadBuilder;
+ if (event.selfDescribingEventData) {
+ pb = buildSelfDescribingEvent({ event: event.selfDescribingEventData });
+ } else {
+ pb = payloadBuilder();
+ }
+ pb = webViewPayloadBuilder(pb);
+
+ if (event.eventName !== undefined) {
+ pb.add('e', event.eventName);
+ }
+ if (event.action !== undefined) {
+ pb.add('se_ac', event.action);
+ }
+ if (event.category !== undefined) {
+ pb.add('se_ca', event.category);
+ }
+ if (event.label !== undefined) {
+ pb.add('se_la', event.label);
+ }
+ if (event.property !== undefined) {
+ pb.add('se_pr', event.property);
+ }
+ if (event.value !== undefined) {
+ pb.add('se_va', event.value.toString());
+ }
+ if (event.pageUrl !== undefined) {
+ pb.add('url', event.pageUrl);
+ }
+ if (event.pageTitle !== undefined) {
+ pb.add('page', event.pageTitle);
+ }
+ if (event.referrer !== undefined) {
+ pb.add('refr', event.referrer);
+ }
+ if (event.pingXOffsetMin !== undefined) {
+ pb.add('pp_mix', event.pingXOffsetMin.toString());
+ }
+ if (event.pingXOffsetMax !== undefined) {
+ pb.add('pp_max', event.pingXOffsetMax.toString());
+ }
+ if (event.pingYOffsetMin !== undefined) {
+ pb.add('pp_miy', event.pingYOffsetMin.toString());
+ }
+ if (event.pingYOffsetMax !== undefined) {
+ pb.add('pp_may', event.pingYOffsetMax.toString());
+ }
+ if (event.trackerVersion !== undefined) {
+ pb.add('tv', event.trackerVersion);
+ }
+ if (event.useragent !== undefined) {
+ pb.add('ua', event.useragent);
+ }
+ tracker.track(pb, data.context || []);
+ });
+ break;
+
+ default:
+ console.warn(`Unknown command from WebView: ${data.command}`);
+ }
+ };
+}
diff --git a/trackers/react-native-tracker/test/web_view_interface.test.ts b/trackers/react-native-tracker/test/web_view_interface.test.ts
new file mode 100644
index 000000000..da33775af
--- /dev/null
+++ b/trackers/react-native-tracker/test/web_view_interface.test.ts
@@ -0,0 +1,386 @@
+import { getWebViewCallback, newTracker, removeTracker } from '../src';
+
+function createMockFetch(status: number, requests: Request[]) {
+ return async (input: Request) => {
+ requests.push(input);
+ let response = new Response(null, { status });
+ return response;
+ };
+}
+
+describe('WebView interface', () => {
+ let requests: Request[];
+ let mockFetch: ReturnType;
+
+ beforeEach(async () => {
+ requests = [];
+ mockFetch = createMockFetch(200, requests);
+ });
+
+ afterEach(() => {
+ removeTracker('test');
+ });
+
+ it('tracks a page view event', async () => {
+ const tracker = await newTracker({
+ namespace: 'test',
+ appId: 'my-app',
+ endpoint: 'http://localhost:9090',
+ customFetch: mockFetch,
+ });
+
+ const webViewInterface = getWebViewCallback();
+ webViewInterface({
+ nativeEvent: {
+ data: JSON.stringify({
+ command: 'trackPageView',
+ event: {
+ title: 'Home',
+ url: 'http://localhost:9090',
+ referrer: 'http://refr.com',
+ },
+ }),
+ },
+ });
+
+ await tracker.flush();
+ expect(requests.length).toBe(1);
+
+ const [request] = requests;
+ const payload = await request?.json();
+ expect(payload.data.length).toBe(1);
+
+ const [event] = payload.data;
+ expect(event.e).toBe('pv');
+ expect(event.url).toBe('http://localhost:9090');
+ expect(event.refr).toBe('http://refr.com');
+ expect(event.page).toBe('Home');
+ });
+
+ it('tracks a self-describing event', async () => {
+ const tracker = await newTracker({
+ namespace: 'test',
+ appId: 'my-app',
+ endpoint: 'http://localhost:9090',
+ customFetch: mockFetch,
+ encodeBase64: false,
+ });
+
+ const webViewInterface = getWebViewCallback();
+ webViewInterface({
+ nativeEvent: {
+ data: JSON.stringify({
+ command: 'trackSelfDescribingEvent',
+ event: {
+ schema: 'iglu:com.snowplowanalytics.snowplow/event/jsonschema/1-0-0',
+ data: {
+ key: 'value',
+ },
+ },
+ }),
+ },
+ });
+
+ await tracker.flush();
+ expect(requests.length).toBe(1);
+
+ const [request] = requests;
+ const payload = await request?.json();
+ expect(payload.data.length).toBe(1);
+
+ const [event] = payload.data;
+ const { e, ue_pr } = event;
+ expect(e).toBe('ue');
+ expect(ue_pr).toBeDefined();
+ const { data } = JSON.parse(ue_pr);
+ expect(data.schema).toBe('iglu:com.snowplowanalytics.snowplow/event/jsonschema/1-0-0');
+ expect(data.data.key).toBe('value');
+ });
+
+ it('tracks a structured event', async () => {
+ const tracker = await newTracker({
+ namespace: 'test',
+ appId: 'my-app',
+ endpoint: 'http://localhost:9090',
+ customFetch: mockFetch,
+ });
+
+ const webViewInterface = getWebViewCallback();
+ webViewInterface({
+ nativeEvent: {
+ data: JSON.stringify({
+ command: 'trackStructEvent',
+ event: {
+ category: 'category',
+ action: 'action',
+ label: 'label',
+ property: 'property',
+ value: 1,
+ },
+ }),
+ },
+ });
+
+ await tracker.flush();
+ expect(requests.length).toBe(1);
+
+ const [request] = requests;
+ const payload = await request?.json();
+ expect(payload.data.length).toBe(1);
+
+ const [event] = payload.data;
+ const { e, se_ca, se_ac, se_la, se_pr, se_va } = event;
+ expect(e).toBe('se');
+ expect(se_ca).toBe('category');
+ expect(se_ac).toBe('action');
+ expect(se_la).toBe('label');
+ expect(se_pr).toBe('property');
+ expect(se_va).toBe('1');
+ });
+
+ it('tracks a screen view event', async () => {
+ const tracker = await newTracker({
+ namespace: 'test',
+ appId: 'my-app',
+ endpoint: 'http://localhost:9090',
+ customFetch: mockFetch,
+ encodeBase64: false,
+ });
+
+ const webViewInterface = getWebViewCallback();
+ webViewInterface({
+ nativeEvent: {
+ data: JSON.stringify({
+ command: 'trackScreenView',
+ event: {
+ name: 'Home',
+ id: 'home',
+ },
+ }),
+ },
+ });
+
+ await tracker.flush();
+ expect(requests.length).toBe(1);
+
+ const [request] = requests;
+ const payload = await request?.json();
+ expect(payload.data.length).toBe(1);
+
+ const [event] = payload.data;
+ expect(event.e).toBe('ue');
+ expect(event.ue_pr).toBeDefined();
+ const { data } = JSON.parse(event.ue_pr);
+ expect(data.schema).toBe('iglu:com.snowplowanalytics.mobile/screen_view/jsonschema/1-0-0');
+ expect(data.data.name).toBe('Home');
+ expect(data.data.id).toBe('home');
+ });
+
+ describe('WebView event tracking', () => {
+
+ it('tracks a page view event', async () => {
+ const tracker = await newTracker({
+ namespace: 'test',
+ appId: 'my-app',
+ endpoint: 'http://localhost:9090',
+ customFetch: mockFetch,
+ });
+
+ const webViewInterface = getWebViewCallback();
+ webViewInterface({
+ nativeEvent: {
+ data: JSON.stringify({
+ command: 'trackWebViewEvent',
+ event: {
+ eventName: 'pv',
+ pageTitle: 'Home',
+ pageUrl: 'http://localhost:9090',
+ referrer: 'http://refr.com',
+ },
+ }),
+ },
+ });
+
+ await tracker.flush();
+ expect(requests.length).toBe(1);
+
+ const [request] = requests;
+ const payload = await request?.json();
+ expect(payload.data.length).toBe(1);
+
+ const [event] = payload.data;
+ expect(event.e).toBe('pv');
+ expect(event.url).toBe('http://localhost:9090');
+ expect(event.refr).toBe('http://refr.com');
+ expect(event.page).toBe('Home');
+ });
+
+ it('tracks a self-describing event', async () => {
+ const tracker = await newTracker({
+ namespace: 'test',
+ appId: 'my-app',
+ endpoint: 'http://localhost:9090',
+ customFetch: mockFetch,
+ encodeBase64: false,
+ });
+
+ const webViewInterface = getWebViewCallback();
+ webViewInterface({
+ nativeEvent: {
+ data: JSON.stringify({
+ command: 'trackWebViewEvent',
+ event: {
+ selfDescribingEventData: {
+ schema: 'iglu:com.snowplowanalytics.snowplow/event/jsonschema/1-0-0',
+ data: {
+ key: 'value',
+ },
+ },
+ },
+ }),
+ },
+ });
+
+ await tracker.flush();
+ expect(requests.length).toBe(1);
+
+ const [request] = requests;
+ const payload = await request?.json();
+ expect(payload.data.length).toBe(1);
+
+ const [event] = payload.data;
+ const { e, ue_pr } = event;
+ expect(e).toBe('ue');
+ expect(ue_pr).toBeDefined();
+ const { data } = JSON.parse(ue_pr);
+ expect(data.schema).toBe('iglu:com.snowplowanalytics.snowplow/event/jsonschema/1-0-0');
+ expect(data.data.key).toBe('value');
+ });
+
+ it('tracks a structured event', async () => {
+ const tracker = await newTracker({
+ namespace: 'test',
+ appId: 'my-app',
+ endpoint: 'http://localhost:9090',
+ customFetch: mockFetch,
+ });
+
+ const webViewInterface = getWebViewCallback();
+ webViewInterface({
+ nativeEvent: {
+ data: JSON.stringify({
+ command: 'trackWebViewEvent',
+ event: {
+ eventName: 'se',
+ category: 'category',
+ action: 'action',
+ label: 'label',
+ property: 'property',
+ value: 1,
+ },
+ }),
+ },
+ });
+
+ await tracker.flush();
+ expect(requests.length).toBe(1);
+
+ const [request] = requests;
+ const payload = await request?.json();
+ expect(payload.data.length).toBe(1);
+
+ const [event] = payload.data;
+ const { e, se_ca, se_ac, se_la, se_pr, se_va } = event;
+ expect(e).toBe('se');
+ expect(se_ca).toBe('category');
+ expect(se_ac).toBe('action');
+ expect(se_la).toBe('label');
+ expect(se_pr).toBe('property');
+ expect(se_va).toBe('1');
+ });
+
+ it('tracks a page ping event', async () => {
+ const tracker = await newTracker({
+ namespace: 'test',
+ appId: 'my-app',
+ endpoint: 'http://localhost:9090',
+ customFetch: mockFetch,
+ });
+
+ const webViewInterface = getWebViewCallback();
+ webViewInterface({
+ nativeEvent: {
+ data: JSON.stringify({
+ command: 'trackWebViewEvent',
+ event: {
+ eventName: 'pp',
+ pageTitle: 'Home',
+ pageUrl: 'http://localhost:9090',
+ referrer: 'http://refr.com',
+ pingXOffsetMin: 1,
+ pingXOffsetMax: 2,
+ pingYOffsetMin: 3,
+ pingYOffsetMax: 4,
+ },
+ }),
+ },
+ });
+
+ await tracker.flush();
+ expect(requests.length).toBe(1);
+
+ const [request] = requests;
+ const payload = await request?.json();
+ expect(payload.data.length).toBe(1);
+
+ const [event] = payload.data;
+ expect(event.e).toBe('pp');
+ expect(event.url).toBe('http://localhost:9090');
+ expect(event.refr).toBe('http://refr.com');
+ expect(event.page).toBe('Home');
+ expect(event.pp_mix).toBe('1');
+ expect(event.pp_max).toBe('2');
+ expect(event.pp_miy).toBe('3');
+ expect(event.pp_may).toBe('4');
+ });
+ });
+
+ it('tracks tracker version and useragent', async () => {
+ const tracker = await newTracker({
+ namespace: 'test',
+ appId: 'my-app',
+ endpoint: 'http://localhost:9090',
+ customFetch: mockFetch,
+ });
+
+ const webViewInterface = getWebViewCallback();
+ webViewInterface({
+ nativeEvent: {
+ data: JSON.stringify({
+ command: 'trackWebViewEvent',
+ event: {
+ eventName: 'pv',
+ pageTitle: 'Home',
+ pageUrl: 'http://localhost:9090',
+ trackerVersion: 'wv-1.0.0',
+ useragent: 'Mozilla/5.0',
+ },
+ }),
+ },
+ });
+
+ await tracker.flush();
+ expect(requests.length).toBe(1);
+
+ const [request] = requests;
+ const payload = await request?.json();
+ expect(payload.data.length).toBe(1);
+
+ const [event] = payload.data;
+ expect(event.e).toBe('pv');
+ expect(event.url).toBe('http://localhost:9090');
+ expect(event.page).toBe('Home');
+ expect(event.tv).toBe('wv-1.0.0');
+ expect(event.ua).toBe('Mozilla/5.0');
+ });
+});