diff --git a/README.md b/README.md
index f8e1922d..b87f68cb 100644
--- a/README.md
+++ b/README.md
@@ -23,14 +23,14 @@ And then add the artifact `incognia-api-client` **or** `incognia-api-client-shad
com.incognia
incognia-api-client
- 3.15.1
+ 3.16.0
```
```xml
com.incognia
incognia-api-client-shaded
- 3.15.1
+ 3.16.0
```
@@ -47,13 +47,13 @@ repositories {
And then add the dependency
```gradle
dependencies {
- implementation 'com.incognia:incognia-api-client:3.15.1'
+ implementation 'com.incognia:incognia-api-client:3.16.0'
}
```
OR
```gradle
dependencies {
- implementation 'com.incognia:incognia-api-client-shaded:3.15.1'
+ implementation 'com.incognia:incognia-api-client-shaded:3.16.0'
}
```
diff --git a/build.gradle b/build.gradle
index 2108fd2f..bced639a 100644
--- a/build.gradle
+++ b/build.gradle
@@ -8,7 +8,7 @@ plugins {
}
group = "com.incognia"
-version = "3.15.1"
+version = "3.16.0"
task createProjectVersionFile {
def projectVersionDir = "$projectDir/src/main/java/com/incognia/api"
diff --git a/src/main/java/com/incognia/api/clients/TokenAwareNetworkingClient.java b/src/main/java/com/incognia/api/clients/TokenAwareNetworkingClient.java
index 28f68b38..d8f915e1 100644
--- a/src/main/java/com/incognia/api/clients/TokenAwareNetworkingClient.java
+++ b/src/main/java/com/incognia/api/clients/TokenAwareNetworkingClient.java
@@ -3,11 +3,14 @@
import com.incognia.common.exceptions.IncogniaException;
import java.util.HashMap;
import java.util.Map;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
import okhttp3.OkHttpClient;
public class TokenAwareNetworkingClient {
private static final String USER_AGENT_HEADER = "User-Agent";
private static final String AUTHORIZATION_HEADER = "Authorization";
+ private static final String LATENCY_HEADER = "X-Incognia-Latency";
private static final String USER_AGENT_HEADER_CONTENT =
String.format(
"incognia-api-java/%s (%s %s %s) Java/%s",
@@ -19,6 +22,7 @@ public class TokenAwareNetworkingClient {
private final NetworkingClient networkingClient;
private final TokenProvider tokenProvider;
+ private final AtomicReference lastLatency = new AtomicReference<>();
public TokenAwareNetworkingClient(
OkHttpClient httpClient, String baseUrl, String clientId, String clientSecret) {
@@ -26,42 +30,40 @@ public TokenAwareNetworkingClient(
this.tokenProvider = new TokenProvider(clientId, clientSecret, networkingClient);
}
+ private Map buildHeaders() throws IncogniaException {
+ Map headers = new HashMap<>();
+ headers.put(USER_AGENT_HEADER, USER_AGENT_HEADER_CONTENT);
+ headers.put(AUTHORIZATION_HEADER, tokenProvider.buildAuthorizationHeader());
+ Long latency = lastLatency.get();
+ if (latency != null) {
+ headers.put(LATENCY_HEADER, Long.toString(latency));
+ }
+ return headers;
+ }
+
public U doPost(
String path, T body, Class responseType, Map queryParameters)
throws IncogniaException {
tokenProvider.getToken();
- Map headers =
- new HashMap() {
- {
- put(USER_AGENT_HEADER, USER_AGENT_HEADER_CONTENT);
- put(AUTHORIZATION_HEADER, tokenProvider.buildAuthorizationHeader());
- }
- };
- return networkingClient.doPost(path, body, responseType, headers, queryParameters);
+ long start = System.nanoTime();
+ U result = networkingClient.doPost(path, body, responseType, buildHeaders(), queryParameters);
+ lastLatency.set(TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start));
+ return result;
}
public U doPost(String path, T body, Class responseType) throws IncogniaException {
tokenProvider.getToken();
- Map headers =
- new HashMap() {
- {
- put(USER_AGENT_HEADER, USER_AGENT_HEADER_CONTENT);
- put(AUTHORIZATION_HEADER, tokenProvider.buildAuthorizationHeader());
- }
- };
- return networkingClient.doPost(path, body, responseType, headers);
+ long start = System.nanoTime();
+ U result = networkingClient.doPost(path, body, responseType, buildHeaders());
+ lastLatency.set(TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start));
+ return result;
}
public void doPost(String path, T body, Map queryParameters)
throws IncogniaException {
tokenProvider.getToken();
- Map headers =
- new HashMap() {
- {
- put(USER_AGENT_HEADER, USER_AGENT_HEADER_CONTENT);
- put(AUTHORIZATION_HEADER, tokenProvider.buildAuthorizationHeader());
- }
- };
- networkingClient.doPost(path, body, headers, queryParameters);
+ long start = System.nanoTime();
+ networkingClient.doPost(path, body, buildHeaders(), queryParameters);
+ lastLatency.set(TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start));
}
}
diff --git a/src/test/java/com/incognia/api/IncogniaAPITest.java b/src/test/java/com/incognia/api/IncogniaAPITest.java
index 2163bb16..76e62da3 100644
--- a/src/test/java/com/incognia/api/IncogniaAPITest.java
+++ b/src/test/java/com/incognia/api/IncogniaAPITest.java
@@ -61,7 +61,9 @@
import lombok.SneakyThrows;
import okhttp3.ConnectionPool;
import okhttp3.OkHttpClient;
+import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
+import okhttp3.mockwebserver.RecordedRequest;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
@@ -1106,6 +1108,99 @@ void testRegisterLogin_whenAccountIdIsNotValid() {
.hasMessage("'account id' cannot be empty");
}
+ private static final String TOKEN_RESPONSE =
+ "{\"access_token\": \"test-token\", \"expires_in\": 300, \"token_type\": \"Bearer\"}";
+ private static final String SIGNUP_RESPONSE =
+ "{\"id\": \"5e76a7ca-577c-4f47-a752-9e1e0cee9e49\","
+ + "\"request_id\": \"8afc84a7-f1d4-488d-bd69-36d9a37168b7\","
+ + "\"risk_assessment\": \"low_risk\"}";
+ private static final String TRANSACTION_RESPONSE =
+ "{\"id\": \"dfe1f2ff-8f0d-4ce8-aed1-af8435143044\"," + "\"risk_assessment\": \"low_risk\"}";
+
+ @Test
+ @SneakyThrows
+ void testLbmt_absentOnFirstCall() {
+ mockServer.enqueue(new MockResponse().setResponseCode(200).setBody(TOKEN_RESPONSE));
+ mockServer.enqueue(new MockResponse().setResponseCode(200).setBody(SIGNUP_RESPONSE));
+
+ client.registerSignup(RegisterSignupRequest.builder().installationId("test").build());
+
+ mockServer.takeRequest(); // token request
+ RecordedRequest signupRequest = mockServer.takeRequest();
+ assertThat(signupRequest.getHeader("X-Incognia-Latency")).isNull();
+ }
+
+ @Test
+ @SneakyThrows
+ void testLbmt_sentOnSecondSignupCall() {
+ mockServer.enqueue(new MockResponse().setResponseCode(200).setBody(TOKEN_RESPONSE));
+ mockServer.enqueue(new MockResponse().setResponseCode(200).setBody(SIGNUP_RESPONSE));
+ mockServer.enqueue(new MockResponse().setResponseCode(200).setBody(SIGNUP_RESPONSE));
+
+ RegisterSignupRequest request = RegisterSignupRequest.builder().installationId("test").build();
+ client.registerSignup(request);
+ client.registerSignup(request);
+
+ mockServer.takeRequest(); // token request
+ RecordedRequest firstRequest = mockServer.takeRequest();
+ RecordedRequest secondRequest = mockServer.takeRequest();
+
+ assertThat(firstRequest.getHeader("X-Incognia-Latency")).isNull();
+ String latencyHeader = secondRequest.getHeader("X-Incognia-Latency");
+ assertThat(latencyHeader).isNotNull();
+ assertThat(Long.parseLong(latencyHeader)).isGreaterThanOrEqualTo(0L);
+ }
+
+ @Test
+ @SneakyThrows
+ void testLbmt_sentOnFeedbackAfterTransaction() {
+ mockServer.enqueue(new MockResponse().setResponseCode(200).setBody(TOKEN_RESPONSE));
+ mockServer.enqueue(new MockResponse().setResponseCode(200).setBody(TRANSACTION_RESPONSE));
+ mockServer.enqueue(new MockResponse().setResponseCode(200));
+
+ client.registerPayment(
+ RegisterPaymentRequest.builder()
+ .accountId("account-id")
+ .addresses(Collections.emptyMap())
+ .build());
+ client.registerFeedback(
+ FeedbackEvent.ACCOUNT_TAKEOVER,
+ Instant.now(),
+ FeedbackIdentifiers.builder().accountId("account-id").build());
+
+ mockServer.takeRequest(); // token request
+ RecordedRequest transactionRequest = mockServer.takeRequest();
+ RecordedRequest feedbackRequest = mockServer.takeRequest();
+
+ assertThat(transactionRequest.getHeader("X-Incognia-Latency")).isNull();
+ String latencyHeader = feedbackRequest.getHeader("X-Incognia-Latency");
+ assertThat(latencyHeader).isNotNull();
+ assertThat(Long.parseLong(latencyHeader)).isGreaterThanOrEqualTo(0L);
+ }
+
+ @Test
+ @SneakyThrows
+ void testLbmt_sentOnSignupAfterFeedback() {
+ mockServer.enqueue(new MockResponse().setResponseCode(200).setBody(TOKEN_RESPONSE));
+ mockServer.enqueue(new MockResponse().setResponseCode(200));
+ mockServer.enqueue(new MockResponse().setResponseCode(200).setBody(SIGNUP_RESPONSE));
+
+ client.registerFeedback(
+ FeedbackEvent.ACCOUNT_TAKEOVER,
+ Instant.now(),
+ FeedbackIdentifiers.builder().accountId("account-id").build());
+ client.registerSignup(RegisterSignupRequest.builder().installationId("test").build());
+
+ mockServer.takeRequest(); // token request
+ RecordedRequest feedbackRequest = mockServer.takeRequest();
+ RecordedRequest signupRequest = mockServer.takeRequest();
+
+ assertThat(feedbackRequest.getHeader("X-Incognia-Latency")).isNull();
+ String latencyHeader = signupRequest.getHeader("X-Incognia-Latency");
+ assertThat(latencyHeader).isNotNull();
+ assertThat(Long.parseLong(latencyHeader)).isGreaterThanOrEqualTo(0L);
+ }
+
private void assertTransactionAssessment(TransactionAssessment transactionAssessment) {
assertThat(transactionAssessment)
.extracting("id", "riskAssessment", "deviceId")