diff --git a/CHANGELOG b/CHANGELOG index 8605e8e32..e3300fd05 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,7 @@ +Version 6.1.0 (2025-01-16) +-------------------------- +Add new WebView interface (#700) + Version 6.0.6 (2024-09-12) -------------------------- Set negative battery levels to null in mobile context (#698) diff --git a/VERSION b/VERSION index b7ff1516c..dfda3e0b4 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -6.0.6 +6.1.0 diff --git a/build.gradle b/build.gradle index 2257cab4b..d10c11b11 100644 --- a/build.gradle +++ b/build.gradle @@ -22,7 +22,7 @@ plugins { subprojects { group = 'com.snowplowanalytics' - version = '6.0.6' + version = '6.1.0' repositories { google() maven { diff --git a/gradle.properties b/gradle.properties index 7e43e82c1..6bedc6c4b 100644 --- a/gradle.properties +++ b/gradle.properties @@ -31,7 +31,7 @@ systemProp.org.gradle.internal.http.socketTimeout=120000 SONATYPE_STAGING_PROFILE=comsnowplowanalytics GROUP=com.snowplowanalytics POM_ARTIFACT_ID=snowplow-android-tracker -VERSION_NAME=6.0.6 +VERSION_NAME=6.1.0 POM_NAME=snowplow-android-tracker POM_PACKAGING=aar diff --git a/snowplow-tracker/src/androidTest/java/com/snowplowanalytics/snowplow/tracker/TrackerWebViewInterfaceV2Test.kt b/snowplow-tracker/src/androidTest/java/com/snowplowanalytics/snowplow/tracker/TrackerWebViewInterfaceV2Test.kt new file mode 100644 index 000000000..617004c10 --- /dev/null +++ b/snowplow-tracker/src/androidTest/java/com/snowplowanalytics/snowplow/tracker/TrackerWebViewInterfaceV2Test.kt @@ -0,0 +1,223 @@ +/* + * Copyright (c) 2015-present Snowplow Analytics Ltd. All rights reserved. + * + * This program is licensed to you under the Apache License Version 2.0, + * and you may not use this file except in compliance with the Apache License Version 2.0. + * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the Apache License Version 2.0 is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. + */ + +package com.snowplowanalytics.snowplow.tracker + +import android.content.Context +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry +import com.snowplowanalytics.core.constants.Parameters +import com.snowplowanalytics.core.constants.TrackerConstants +import com.snowplowanalytics.core.emitter.Executor +import com.snowplowanalytics.core.tracker.TrackerWebViewInterfaceV2 +import com.snowplowanalytics.snowplow.Snowplow.createTracker +import com.snowplowanalytics.snowplow.Snowplow.removeAllTrackers +import com.snowplowanalytics.snowplow.configuration.NetworkConfiguration +import com.snowplowanalytics.snowplow.configuration.TrackerConfiguration +import com.snowplowanalytics.snowplow.controller.TrackerController +import com.snowplowanalytics.snowplow.network.HttpMethod +import com.snowplowanalytics.snowplow.util.EventSink +import org.json.JSONException +import org.json.JSONObject +import org.junit.After +import org.junit.Assert.* +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class TrackerWebViewInterfaceV2Test { + private var webInterface: TrackerWebViewInterfaceV2? = null + + @Before + fun setUp() { + webInterface = TrackerWebViewInterfaceV2() + } + + @After + fun tearDown() { + removeAllTrackers() + Executor.shutdown() + } + + @Test + @Throws(JSONException::class, InterruptedException::class) + fun tracksEventWithAllOptions() { + val networkConnection = MockNetworkConnection(HttpMethod.GET, 200) + createTracker( + context, + "ns${Math.random()}", + NetworkConfiguration(networkConnection), + TrackerConfiguration("appId").base64encoding(false) + ) + + val data = "{\"schema\":\"iglu:etc\",\"data\":{\"key\":\"val\"}}" + val atomic = "{\"eventName\":\"pv\",\"trackerVersion\":\"webview\"," + + "\"useragent\":\"Chrome\",\"pageUrl\":\"http://snowplow.com\"," + + "\"pageTitle\":\"Snowplow\",\"referrer\":\"http://google.com\"," + + "\"pingXOffsetMin\":10,\"pingXOffsetMax\":20,\"pingYOffsetMin\":30," + + "\"pingYOffsetMax\":40,\"category\":\"cat\",\"action\":\"act\"," + + "\"property\":\"prop\",\"label\":\"lbl\",\"value\":10.0}" + + webInterface!!.trackWebViewEvent( + selfDescribingEventData = data, + atomicProperties = atomic + ) + + waitForEvents(networkConnection) + assertEquals(1, networkConnection.countRequests()) + + val request = networkConnection.allRequests[0] + val payload = request.payload.map + + assertEquals("pv", payload[Parameters.EVENT]) + assertEquals("webview", payload[Parameters.TRACKER_VERSION]) + assertEquals("Chrome", payload[Parameters.USERAGENT]) + assertEquals("http://snowplow.com", payload[Parameters.PAGE_URL]) + assertEquals("Snowplow", payload[Parameters.PAGE_TITLE]) + assertEquals("http://google.com", payload[Parameters.PAGE_REFR]) + assertEquals("10", payload[Parameters.PING_XOFFSET_MIN]) + assertEquals("20", payload[Parameters.PING_XOFFSET_MAX]) + assertEquals("30", payload[Parameters.PING_YOFFSET_MIN]) + assertEquals("40", payload[Parameters.PING_YOFFSET_MAX]) + assertEquals("cat", payload[Parameters.SE_CATEGORY]) + assertEquals("act", payload[Parameters.SE_ACTION]) + assertEquals("prop", payload[Parameters.SE_PROPERTY]) + assertEquals("lbl", payload[Parameters.SE_LABEL]) + assertEquals("10.0", payload[Parameters.SE_VALUE]) + + assertTrue(payload.containsKey(Parameters.UNSTRUCTURED)) + val selfDescJson = JSONObject(payload[Parameters.UNSTRUCTURED] as String) + assertEquals(TrackerConstants.SCHEMA_UNSTRUCT_EVENT, selfDescJson.getString("schema")) + assertEquals(data, selfDescJson.getString("data")) + } + + @Test + @Throws(JSONException::class, InterruptedException::class) + fun addsDefaultPropertiesIfNotProvided() { + val networkConnection = MockNetworkConnection(HttpMethod.GET, 200) + createTracker( + context, + "ns${Math.random()}", + NetworkConfiguration(networkConnection), + TrackerConfiguration("appId").base64encoding(false) + ) + + webInterface!!.trackWebViewEvent(atomicProperties = "{}") + + waitForEvents(networkConnection) + assertEquals(1, networkConnection.countRequests()) + + val request = networkConnection.allRequests[0] + val payload = request.payload.map + + assertEquals("ue", payload[Parameters.EVENT]) + + val trackerVersion = payload[Parameters.TRACKER_VERSION] as String? + assertTrue(trackerVersion?.startsWith("andr") ?: false) + } + + @Test + @Throws(JSONException::class, InterruptedException::class) + fun tracksEventWithCorrectTracker() { + val eventSink1 = EventSink() + val eventSink2 = EventSink() + + createTracker("ns1", eventSink1) + createTracker("ns2", eventSink2) + Thread.sleep(200) + + // track an event using the second tracker + webInterface!!.trackWebViewEvent( + atomicProperties = "{}", + trackers = arrayOf("ns2") + ) + Thread.sleep(200) + + assertEquals(0, eventSink1.trackedEvents.size) + assertEquals(1, eventSink2.trackedEvents.size) + + // tracks using default tracker if not specified + webInterface!!.trackWebViewEvent(atomicProperties = "{}") + Thread.sleep(200) + + assertEquals(1, eventSink1.trackedEvents.size) + assertEquals(1, eventSink2.trackedEvents.size) + } + + @Test + @Throws(JSONException::class, InterruptedException::class) + fun tracksEventWithEntity() { + val namespace = "ns" + Math.random().toString() + val eventSink = EventSink() + createTracker(namespace, eventSink) + + webInterface!!.trackWebViewEvent( + atomicProperties = "{}", + entities = "[{\"schema\":\"iglu:com.example/etc\",\"data\":{\"key\":\"val\"}}]", + trackers = arrayOf(namespace) + ) + Thread.sleep(200) + val events = eventSink.trackedEvents + assertEquals(1, events.size) + + val relevantEntities = events[0].entities.filter { it.map["schema"] == "iglu:com.example/etc" } + assertEquals(1, relevantEntities.size) + + val entityData = relevantEntities[0].map["data"] as HashMap<*, *>? + assertEquals("val", entityData?.get("key")) + } + + @Test + @Throws(JSONException::class, InterruptedException::class) + fun addsEventNameAndSchemaForInspection() { + val namespace = "ns" + Math.random().toString() + val eventSink = EventSink() + createTracker(namespace, eventSink) + + webInterface!!.trackWebViewEvent( + atomicProperties = "{\"eventName\":\"se\"}", + selfDescribingEventData = "{\"schema\":\"iglu:etc\",\"data\":{\"key\":\"val\"}}", + trackers = arrayOf(namespace) + ) + + Thread.sleep(200) + val events = eventSink.trackedEvents + + assertEquals(1, events.size) + assertEquals("se", events[0].name) + assertEquals("iglu:etc", events[0].schema) + } + + // --- PRIVATE + private val context: Context + get() = InstrumentationRegistry.getInstrumentation().targetContext + + private fun createTracker(namespace: String, eventSink: EventSink): TrackerController { + val networkConfig = NetworkConfiguration(MockNetworkConnection(HttpMethod.POST, 200)) + return createTracker( + context, + namespace = namespace, + network = networkConfig, + configurations = arrayOf(eventSink) + ) + } + + private fun waitForEvents(networkConnection: MockNetworkConnection) { + var i = 0 + while (i < 10 && networkConnection.countRequests() == 0) { + Thread.sleep(1000) + i++ + } + } +} diff --git a/snowplow-tracker/src/main/java/com/snowplowanalytics/core/constants/Parameters.kt b/snowplow-tracker/src/main/java/com/snowplowanalytics/core/constants/Parameters.kt index f29d85ae5..0a5ac0d7e 100755 --- a/snowplow-tracker/src/main/java/com/snowplowanalytics/core/constants/Parameters.kt +++ b/snowplow-tracker/src/main/java/com/snowplowanalytics/core/constants/Parameters.kt @@ -259,4 +259,11 @@ object Parameters { const val DIAGNOSTIC_ERROR_STACK = "stackTrace" const val DIAGNOSTIC_ERROR_CLASS_NAME = "className" const val DIAGNOSTIC_ERROR_EXCEPTION_NAME = "exceptionName" + + // Page Pings (for WebView tracking) + const val PING_XOFFSET_MIN = "pp_mix" + const val PING_XOFFSET_MAX = "pp_max" + const val PING_YOFFSET_MIN = "pp_miy" + const val PING_YOFFSET_MAX = "pp_may" + const val WEBVIEW_EVENT_DATA = "selfDescribingEventData" } diff --git a/snowplow-tracker/src/main/java/com/snowplowanalytics/core/event/WebViewReader.kt b/snowplow-tracker/src/main/java/com/snowplowanalytics/core/event/WebViewReader.kt new file mode 100644 index 000000000..b48ac9e98 --- /dev/null +++ b/snowplow-tracker/src/main/java/com/snowplowanalytics/core/event/WebViewReader.kt @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2015-present Snowplow Analytics Ltd. All rights reserved. + * + * This program is licensed to you under the Apache License Version 2.0, + * and you may not use this file except in compliance with the Apache License Version 2.0. + * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the Apache License Version 2.0 is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. + */ +package com.snowplowanalytics.core.event + +import com.snowplowanalytics.core.constants.Parameters +import com.snowplowanalytics.snowplow.event.AbstractEvent +import com.snowplowanalytics.snowplow.payload.SelfDescribingJson + +/** + * Allows the tracking of JavaScript events from WebViews. + */ +class WebViewReader( + val selfDescribingEventData: SelfDescribingJson? = null, + val eventName: String? = null, + val trackerVersion: String? = null, + val useragent: String? = null, + val pageUrl: String? = null, + val pageTitle: String? = null, + val referrer: String? = null, + val category: String? = null, + val action: String? = null, + val label: String? = null, + val property: String? = null, + val value: Double? = null, + val pingXOffsetMin: Int? = null, + val pingXOffsetMax: Int? = null, + val pingYOffsetMin: Int? = null, + val pingYOffsetMax: Int? = null +) : AbstractEvent() { + + // Public methods + override val dataPayload: Map + get() { + val payload = HashMap() + if (selfDescribingEventData != null) payload[Parameters.WEBVIEW_EVENT_DATA] = selfDescribingEventData + if (eventName != null) payload[Parameters.EVENT] = eventName + if (trackerVersion != null) payload[Parameters.TRACKER_VERSION] = trackerVersion + if (useragent != null) payload[Parameters.USERAGENT] = useragent + if (pageUrl != null) payload[Parameters.PAGE_URL] = pageUrl + if (pageTitle != null) payload[Parameters.PAGE_TITLE] = pageTitle + if (referrer != null) payload[Parameters.PAGE_REFR] = referrer + if (category != null) payload[Parameters.SE_CATEGORY] = category + if (action != null) payload[Parameters.SE_ACTION] = action + if (label != null) payload[Parameters.SE_LABEL] = label + if (property != null) payload[Parameters.SE_PROPERTY] = property + if (value != null) payload[Parameters.SE_VALUE] = value + if (pingXOffsetMin != null) payload[Parameters.PING_XOFFSET_MIN] = pingXOffsetMin + if (pingXOffsetMax != null) payload[Parameters.PING_XOFFSET_MAX] = pingXOffsetMax + if (pingYOffsetMin != null) payload[Parameters.PING_YOFFSET_MIN] = pingYOffsetMin + if (pingYOffsetMax != null) payload[Parameters.PING_YOFFSET_MAX] = pingYOffsetMax + return payload + } +} diff --git a/snowplow-tracker/src/main/java/com/snowplowanalytics/core/tracker/TrackerEvent.kt b/snowplow-tracker/src/main/java/com/snowplowanalytics/core/tracker/TrackerEvent.kt index 3967b9ab9..ef168a3e8 100644 --- a/snowplow-tracker/src/main/java/com/snowplowanalytics/core/tracker/TrackerEvent.kt +++ b/snowplow-tracker/src/main/java/com/snowplowanalytics/core/tracker/TrackerEvent.kt @@ -14,6 +14,7 @@ package com.snowplowanalytics.core.tracker import com.snowplowanalytics.core.constants.Parameters import com.snowplowanalytics.core.constants.TrackerConstants +import com.snowplowanalytics.core.event.WebViewReader import com.snowplowanalytics.core.statemachine.StateMachineEvent import com.snowplowanalytics.core.statemachine.TrackerState import com.snowplowanalytics.core.statemachine.TrackerStateSnapshot @@ -39,6 +40,7 @@ class TrackerEvent @JvmOverloads constructor(event: Event, state: TrackerStateSn var trueTimestamp: Long? var isPrimitive = false var isService: Boolean + var isWebView = false init { entities = event.entities.toMutableList() @@ -56,12 +58,20 @@ class TrackerEvent @JvmOverloads constructor(event: Event, state: TrackerStateSn } isService = event is TrackerError - if (event is AbstractPrimitive) { - name = event.name - isPrimitive = true - } else { - schema = (event as? AbstractSelfDescribing)?.schema - isPrimitive = false + when (event) { + is WebViewReader -> { + name = payload[Parameters.EVENT]?.toString() + schema = getWebViewSchema() + isWebView = true + } + is AbstractPrimitive -> { + name = event.name + isPrimitive = true + } + else -> { + schema = (event as? AbstractSelfDescribing)?.schema + isPrimitive = false + } } } @@ -100,16 +110,19 @@ class TrackerEvent @JvmOverloads constructor(event: Event, state: TrackerStateSn } fun wrapPropertiesToPayload(toPayload: Payload, base64Encoded: Boolean) { - if (isPrimitive) { - toPayload.addMap(payload) - } else { - wrapSelfDescribingToPayload(toPayload, base64Encoded) + when { + isWebView -> wrapWebViewToPayload(toPayload, base64Encoded) + isPrimitive -> toPayload.addMap(payload) + else -> wrapSelfDescribingEventToPayload(toPayload, base64Encoded) } } + + private fun getWebViewSchema(): String? { + val selfDescribingData = payload[Parameters.WEBVIEW_EVENT_DATA] as SelfDescribingJson? + return selfDescribingData?.map?.get(Parameters.SCHEMA)?.toString() + } - private fun wrapSelfDescribingToPayload(toPayload: Payload, base64Encoded: Boolean) { - val schema = schema ?: return - val data = SelfDescribingJson(schema, payload) + private fun addSelfDescribingDataToPayload(toPayload: Payload, base64Encoded: Boolean, data: SelfDescribingJson) { val unstructuredEventPayload = HashMap() unstructuredEventPayload[Parameters.SCHEMA] = TrackerConstants.SCHEMA_UNSTRUCT_EVENT unstructuredEventPayload[Parameters.DATA] = data.map @@ -120,4 +133,17 @@ class TrackerEvent @JvmOverloads constructor(event: Event, state: TrackerStateSn Parameters.UNSTRUCTURED ) } + + private fun wrapWebViewToPayload(toPayload: Payload, base64Encoded: Boolean) { + val selfDescribingData = payload[Parameters.WEBVIEW_EVENT_DATA] as SelfDescribingJson? + if (selfDescribingData != null) { + addSelfDescribingDataToPayload(toPayload, base64Encoded, selfDescribingData) + } + toPayload.addMap(payload.filterNot { it.key == Parameters.WEBVIEW_EVENT_DATA }) + } + + private fun wrapSelfDescribingEventToPayload(toPayload: Payload, base64Encoded: Boolean) { + val schema = schema ?: return + addSelfDescribingDataToPayload(toPayload, base64Encoded, SelfDescribingJson(schema, payload)) + } } diff --git a/snowplow-tracker/src/main/java/com/snowplowanalytics/core/tracker/TrackerWebViewInterfaceV2.kt b/snowplow-tracker/src/main/java/com/snowplowanalytics/core/tracker/TrackerWebViewInterfaceV2.kt new file mode 100644 index 000000000..d21463fe9 --- /dev/null +++ b/snowplow-tracker/src/main/java/com/snowplowanalytics/core/tracker/TrackerWebViewInterfaceV2.kt @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2015-present Snowplow Analytics Ltd. All rights reserved. + * + * This program is licensed to you under the Apache License Version 2.0, + * and you may not use this file except in compliance with the Apache License Version 2.0. + * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the Apache License Version 2.0 is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. + */ +package com.snowplowanalytics.core.tracker + +import android.webkit.JavascriptInterface +import com.snowplowanalytics.core.event.WebViewReader +import com.snowplowanalytics.core.tracker.Logger.e +import com.snowplowanalytics.core.utils.JsonUtils.jsonToMap +import com.snowplowanalytics.snowplow.Snowplow.defaultTracker +import com.snowplowanalytics.snowplow.Snowplow.getTracker +import com.snowplowanalytics.snowplow.event.* +import com.snowplowanalytics.snowplow.payload.SelfDescribingJson +import org.json.JSONArray +import org.json.JSONException +import org.json.JSONObject +import java.util.* + +/** + * JavaScript interface used to provide an API for tracking events from WebViews. + * This V2 interface works with the WebView tracker v0.3.0+. + */ +class TrackerWebViewInterfaceV2 { + @JavascriptInterface + @Throws(JSONException::class) + fun trackWebViewEvent( + atomicProperties: String, + selfDescribingEventData: String? = null, + entities: String? = null, + trackers: Array? = null + ) { + val atomic = JSONObject(atomicProperties) + + val event = WebViewReader( + selfDescribingEventData = parseSelfDescribingEventData(selfDescribingEventData), + eventName = getProperty(atomic, "eventName")?.toString(), + trackerVersion = getProperty(atomic, "trackerVersion")?.toString(), + useragent = getProperty(atomic, "useragent")?.toString(), + pageUrl = getProperty(atomic, "pageUrl")?.toString(), + pageTitle = getProperty(atomic, "pageTitle")?.toString(), + referrer = getProperty(atomic, "referrer")?.toString(), + category = getProperty(atomic, "category")?.toString(), + action = getProperty(atomic, "action")?.toString(), + label = getProperty(atomic, "label")?.toString(), + property = getProperty(atomic, "property")?.toString(), + value = getProperty(atomic, "value")?.toString()?.toDoubleOrNull(), + pingXOffsetMin = getProperty(atomic, "pingXOffsetMin")?.toString()?.toIntOrNull(), + pingXOffsetMax = getProperty(atomic, "pingXOffsetMax")?.toString()?.toIntOrNull(), + pingYOffsetMin = getProperty(atomic, "pingYOffsetMin")?.toString()?.toIntOrNull(), + pingYOffsetMax = getProperty(atomic, "pingYOffsetMax")?.toString()?.toIntOrNull(), + ) + trackEvent(event, entities, trackers) + } + + private fun getProperty(atomicProperties: JSONObject, property: String) = try { + if (atomicProperties.has(property)) { + atomicProperties.get(property) + } else { + null + } + } catch (e: JSONException) { + null + } + + @Throws(JSONException::class) + private fun trackEvent(event: AbstractEvent, contextEntities: String?, trackers: Array?) { + if (contextEntities != null) { + val entities = parseEntities(contextEntities) + if (entities.isNotEmpty()) { + event.entities(entities) + } + } + if (trackers.isNullOrEmpty()) { + val tracker = defaultTracker + if (tracker != null) { + tracker.track(event) + } else { + e(TAG, "Tracker not initialized.") + } + } else { + for (namespace in trackers) { + val tracker = getTracker(namespace) + if (tracker != null) { + tracker.track(event) + } else { + e(TAG, String.format("Tracker with namespace %s not found.", namespace)) + } + } + } + } + + private fun createSelfDescribingJson(map: Map): SelfDescribingJson? { + val schema = map["schema"] as? String? + val data = map["data"] + return if (schema != null && data != null) { + SelfDescribingJson(schema, data) + } else { + null + } + } + + @Throws(JSONException::class) + private fun parseEntities(serialisedEntities: String): List { + val entities: MutableList = ArrayList() + val entitiesJson = JSONArray(serialisedEntities) + for (i in 0 until entitiesJson.length()) { + val itemJson = entitiesJson.getJSONObject(i) + val item = jsonToMap(itemJson) + val selfDescribingJson = createSelfDescribingJson(item) + if (selfDescribingJson != null) { + entities.add(selfDescribingJson) + } + } + return entities + } + + @Throws(JSONException::class) + private fun parseSelfDescribingEventData(serialisedEvent: String?): SelfDescribingJson? { + return serialisedEvent?.let { + val eventJson = JSONObject(it) + createSelfDescribingJson(jsonToMap(eventJson)) + } + } + + companion object { + const val TAG = "SnowplowWebInterfaceV2" + } +} diff --git a/snowplow-tracker/src/main/java/com/snowplowanalytics/snowplow/Snowplow.kt b/snowplow-tracker/src/main/java/com/snowplowanalytics/snowplow/Snowplow.kt index 95cc4af2c..6c3b7afe4 100644 --- a/snowplow-tracker/src/main/java/com/snowplowanalytics/snowplow/Snowplow.kt +++ b/snowplow-tracker/src/main/java/com/snowplowanalytics/snowplow/Snowplow.kt @@ -23,6 +23,7 @@ import com.snowplowanalytics.snowplow.configuration.ConfigurationState import com.snowplowanalytics.core.remoteconfiguration.RemoteConfigurationBundle import com.snowplowanalytics.core.tracker.ServiceProvider import com.snowplowanalytics.core.tracker.TrackerWebViewInterface +import com.snowplowanalytics.core.tracker.TrackerWebViewInterfaceV2 import com.snowplowanalytics.snowplow.configuration.* import com.snowplowanalytics.snowplow.controller.TrackerController @@ -356,6 +357,7 @@ object Snowplow { @JvmStatic fun subscribeToWebViewEvents(webView: WebView) { webView.addJavascriptInterface(TrackerWebViewInterface(), TrackerWebViewInterface.TAG) + webView.addJavascriptInterface(TrackerWebViewInterfaceV2(), TrackerWebViewInterfaceV2.TAG) } // Private methods