Skip to content

Commit

Permalink
Send analytics to FPTI (#834)
Browse files Browse the repository at this point in the history
  • Loading branch information
scannillo authored Nov 30, 2023
1 parent dd53d9d commit b353681
Show file tree
Hide file tree
Showing 15 changed files with 314 additions and 507 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -36,27 +36,9 @@ public void setUp() {
}

@Test(timeout = 10000)
public void sendsCorrectlyFormattedAnalyticsRequestToSandbox() throws Exception {
public void sendsCorrectlyFormattedAnalyticsRequestToWorkManager() throws Exception {
Authorization authorization = Authorization.fromString(Fixtures.TOKENIZATION_KEY);
Configuration configuration = Configuration.fromJson(Fixtures.CONFIGURATION_WITH_SANDBOX_ANALYTICS);

AnalyticsClient sut = new AnalyticsClient(context);
UUID workSpecId = sut.sendEvent(configuration, "event.started", "sessionId", "custom", 123, authorization);

WorkInfo workInfoBeforeDelay = WorkManager.getInstance(context).getWorkInfoById(workSpecId).get();
assertEquals(workInfoBeforeDelay.getState(), WorkInfo.State.ENQUEUED);

TestDriver testDriver = WorkManagerTestInitHelper.getTestDriver(context);
testDriver.setInitialDelayMet(workSpecId);

WorkInfo workInfoAfterDelay = WorkManager.getInstance(context).getWorkInfoById(workSpecId).get();
assertEquals(workInfoAfterDelay.getState(), WorkInfo.State.SUCCEEDED);
}

@Test(timeout = 10000)
public void sendsCorrectlyFormattedAnalyticsRequestToProd() throws Exception {
Authorization authorization = Authorization.fromString(Fixtures.PROD_TOKENIZATION_KEY);
Configuration configuration = Configuration.fromJson(Fixtures.CONFIGURATION_WITH_PROD_ANALYTICS);
Configuration configuration = Configuration.fromJson(Fixtures.CONFIGURATION_WITH_ENVIRONMENT);

AnalyticsClient sut = new AnalyticsClient(context);
UUID workSpecId = sut.sendEvent(configuration, "event.started", "sessionId", "custom", 123, authorization);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ internal class AnalyticsClient @VisibleForTesting constructor(
private val workManager: WorkManager,
private val deviceInspector: DeviceInspector
) {
private var lastKnownAnalyticsUrl: String? = null

constructor(context: Context) : this(
BraintreeHttpClient(),
Expand Down Expand Up @@ -50,7 +49,6 @@ internal class AnalyticsClient @VisibleForTesting constructor(
timestamp: Long,
authorization: Authorization
): UUID {
lastKnownAnalyticsUrl = configuration.analyticsUrl
scheduleAnalyticsWrite("android.$eventName", timestamp, authorization)
return scheduleAnalyticsUpload(configuration, authorization, sessionId, integration)
}
Expand Down Expand Up @@ -124,14 +122,15 @@ internal class AnalyticsClient @VisibleForTesting constructor(
val analyticsEventDao = analyticsDatabase.analyticsEventDao()
val events = analyticsEventDao.getAllEvents()
if (events.isNotEmpty()) {
val metadata = deviceInspector.getDeviceMetadata(context, sessionId, integration)
val metadata = deviceInspector.getDeviceMetadata(context, configuration, sessionId, integration)
val analyticsRequest = serializeEvents(authorization, events, metadata)
configuration?.analyticsUrl?.let { analyticsUrl ->
httpClient.post(
analyticsUrl, analyticsRequest.toString(), configuration, authorization
)
analyticsEventDao.deleteEvents(events)
}
httpClient.post(
FPTI_ANALYTICS_URL,
analyticsRequest.toString(),
configuration,
authorization
)
analyticsEventDao.deleteEvents(events)
}
ListenableWorker.Result.success()
} catch (e: Exception) {
Expand All @@ -141,14 +140,19 @@ internal class AnalyticsClient @VisibleForTesting constructor(
}

fun reportCrash(
context: Context?, sessionId: String?, integration: String?, authorization: Authorization?
context: Context?,
configuration: Configuration?,
sessionId: String?,
integration: String?,
authorization: Authorization?
) {
reportCrash(context, sessionId, integration, System.currentTimeMillis(), authorization)
reportCrash(context, configuration, sessionId, integration, System.currentTimeMillis(), authorization)
}

@VisibleForTesting
fun reportCrash(
context: Context?,
configuration: Configuration?,
sessionId: String?,
integration: String?,
timestamp: Long,
Expand All @@ -157,57 +161,66 @@ internal class AnalyticsClient @VisibleForTesting constructor(
if (authorization == null) {
return
}
val metadata = deviceInspector.getDeviceMetadata(context, sessionId, integration)
val metadata = deviceInspector.getDeviceMetadata(context, configuration, sessionId, integration)
val event = AnalyticsEvent("android.crash", timestamp)
val events = listOf(event)
try {
val analyticsRequest = serializeEvents(authorization, events, metadata)
lastKnownAnalyticsUrl?.let { analyticsUrl ->
httpClient.post(
analyticsUrl,
analyticsRequest.toString(),
null,
authorization,
HttpNoResponse()
)
}
httpClient.post(
FPTI_ANALYTICS_URL,
analyticsRequest.toString(),
null,
authorization,
HttpNoResponse()
)
} catch (e: JSONException) { /* ignored */
}
}

@Throws(JSONException::class)
private fun serializeEvents(
authorization: Authorization?, events: List<AnalyticsEvent>, metadata: DeviceMetadata
authorization: Authorization?,
events: List<AnalyticsEvent>, metadata: DeviceMetadata
): JSONObject {
val requestObject = JSONObject()
val batchParamsJSON = metadata.toJSON()
authorization?.let {
if (it is ClientToken) {
requestObject.put(AUTHORIZATION_FINGERPRINT_KEY, it.bearer)
batchParamsJSON.put(AUTHORIZATION_FINGERPRINT_KEY, it.bearer)
} else {
requestObject.put(TOKENIZATION_KEY, it.bearer)
batchParamsJSON.put(TOKENIZATION_KEY, it.bearer)
}
}

requestObject.put(META_KEY, metadata.toJSON())
val eventObjects = JSONArray()
var eventObject: JSONObject
val eventsContainerJSON = JSONObject()
eventsContainerJSON.put(BATCH_PARAMS_KEY, batchParamsJSON)

val eventParamsJSON = JSONArray()
for (analyticsEvent in events) {
eventObject = JSONObject()
.put(KIND_KEY, analyticsEvent.name)
val singleEventJSON = JSONObject()
.put(EVENT_NAME_KEY, analyticsEvent.name)
.put(TIMESTAMP_KEY, analyticsEvent.timestamp)
eventObjects.put(eventObject)
.put(TENANT_NAME_KEY, "Braintree")
eventParamsJSON.put(singleEventJSON)
}
requestObject.put(ANALYTICS_KEY, eventObjects)
return requestObject
eventsContainerJSON.put(EVENT_PARAMS_KEY, eventParamsJSON)

// Single-element "events" array required by FPTI formatting
val eventsArray = JSONArray(arrayOf(eventsContainerJSON))
return JSONObject().put(EVENTS_CONTAINER_KEY, eventsArray)
}

companion object {
private const val ANALYTICS_KEY = "analytics"
private const val KIND_KEY = "kind"
private const val TIMESTAMP_KEY = "timestamp"
private const val META_KEY = "_meta"
private const val TOKENIZATION_KEY = "tokenization_key"
private const val FPTI_ANALYTICS_URL = "https://api-m.paypal.com/v1/tracking/batch/events"

private const val EVENTS_CONTAINER_KEY = "events"
private const val BATCH_PARAMS_KEY = "batch_params"
private const val AUTHORIZATION_FINGERPRINT_KEY = "authorization_fingerprint"
private const val TOKENIZATION_KEY = "tokenization_key"
private const val EVENT_PARAMS_KEY = "event_params"
private const val EVENT_NAME_KEY = "event_name"
private const val TIMESTAMP_KEY = "t"
private const val TENANT_NAME_KEY = "tenant_name"

private const val INVALID_TIMESTAMP: Long = -1
const val WORK_NAME_ANALYTICS_UPLOAD = "uploadAnalytics"
const val WORK_NAME_ANALYTICS_WRITE = "writeAnalyticsToDb"
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -225,9 +225,9 @@ open class BraintreeClient @VisibleForTesting internal constructor(
configuration: Configuration?,
authorization: Authorization
) {
if (isAnalyticsEnabled(configuration)) {
configuration?.let {
analyticsClient.sendEvent(
configuration!!,
it,
eventName,
sessionId,
integrationType,
Expand Down Expand Up @@ -429,7 +429,15 @@ open class BraintreeClient @VisibleForTesting internal constructor(
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
fun reportCrash() = authorizationLoader.authorizationFromCache?.let { authorization ->
analyticsClient.reportCrash(applicationContext, sessionId, integrationType, authorization)
getConfiguration { configuration, _ ->
analyticsClient.reportCrash(
applicationContext,
configuration,
sessionId,
integrationType,
authorization
)
}
}

/**
Expand Down Expand Up @@ -463,15 +471,4 @@ open class BraintreeClient @VisibleForTesting internal constructor(
open fun launchesBrowserSwitchAsNewTask(launchesBrowserSwitchAsNewTask: Boolean) {
this.launchesBrowserSwitchAsNewTask = launchesBrowserSwitchAsNewTask
}

companion object {

/**
* @suppress
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
fun isAnalyticsEnabled(configuration: Configuration?): Boolean {
return configuration != null && configuration.isAnalyticsEnabled
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ open class Configuration internal constructor(configurationString: String?) {
* @suppress
*/
companion object {
private const val ANALYTICS_KEY = "analytics"
private const val ASSETS_URL_KEY = "assetsUrl"
private const val BRAINTREE_API_KEY = "braintreeApi"
private const val CARDINAL_AUTHENTICATION_JWT = "cardinalAuthenticationJWT"
Expand Down Expand Up @@ -94,12 +93,6 @@ open class Configuration internal constructor(configurationString: String?) {

// region Internal Properties

/**
* @return [String] url of the Braintree analytics service.
* @suppress
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) val analyticsUrl: String?

/**
* @return The Access Token for Braintree API.
* @suppress
Expand Down Expand Up @@ -149,12 +142,6 @@ open class Configuration internal constructor(configurationString: String?) {
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) val graphQLUrl: String

/**
* @return `true` if analytics are enabled, `false` otherwise.
* @suppress
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) val isAnalyticsEnabled: Boolean

/**
* @return a boolean indicating whether Braintree API is enabled for this merchant.
* @suppress
Expand Down Expand Up @@ -287,7 +274,6 @@ open class Configuration internal constructor(configurationString: String?) {
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) val visaCheckoutSupportedNetworks: List<String>

private val analyticsConfiguration: AnalyticsConfiguration
private val braintreeApiConfiguration: BraintreeApiConfiguration
private val cardConfiguration: CardConfiguration
private val challenges: MutableSet<String>
Expand Down Expand Up @@ -319,7 +305,6 @@ open class Configuration internal constructor(configurationString: String?) {
}
}

analyticsConfiguration = AnalyticsConfiguration(json.optJSONObject(ANALYTICS_KEY))
braintreeApiConfiguration = BraintreeApiConfiguration(json.optJSONObject(BRAINTREE_API_KEY))
cardConfiguration = CardConfiguration(json.optJSONObject(CARD_KEY))
cardinalAuthenticationJwt = Json.optString(json, CARDINAL_AUTHENTICATION_JWT, null)
Expand All @@ -346,7 +331,6 @@ open class Configuration internal constructor(configurationString: String?) {
payPalPrivacyUrl = payPalConfiguration.privacyUrl
payPalUserAgreementUrl = payPalConfiguration.userAgreementUrl

analyticsUrl = analyticsConfiguration.url
braintreeApiAccessToken = braintreeApiConfiguration.accessToken
braintreeApiUrl = braintreeApiConfiguration.url
googlePayAuthorizationFingerprint = googlePayConfiguration.googleAuthorizationFingerprint
Expand All @@ -355,7 +339,6 @@ open class Configuration internal constructor(configurationString: String?) {
googlePayPayPalClientId = googlePayConfiguration.paypalClientId
googlePaySupportedNetworks = googlePayConfiguration.supportedNetworks
graphQLUrl = graphQLConfiguration.url
isAnalyticsEnabled = analyticsConfiguration.isEnabled
isBraintreeApiEnabled = braintreeApiConfiguration.isEnabled
isFraudDataCollectionEnabled = cardConfiguration.isFraudDataCollectionEnabled
isGraphQLEnabled = graphQLConfiguration.isEnabled
Expand Down
Loading

0 comments on commit b353681

Please sign in to comment.