From 60e65cceba31fb11b1961b9a195a61feaf3586b5 Mon Sep 17 00:00:00 2001 From: Alex Babrykovich Date: Tue, 21 Mar 2023 14:37:17 +0100 Subject: [PATCH] feat(sdk): remove listeners (#100) * Add remove listeners API * Add removedListenerShouldNotBeCalled test * refactor(docs): remove listeners * feat: changelog --------- Co-authored-by: Sergiu Danalachi --- CHANGES.md | 4 ++ README.md | 25 ++++++++++ sdk/build.gradle | 4 +- .../java/com/hcaptcha/sdk/HCaptchaTest.java | 25 ++++++++++ .../java/com/hcaptcha/sdk/tasks/Task.java | 48 +++++++++++++++++++ .../java/com/hcaptcha/sdk/HCaptchaTest.java | 34 +++++++++++++ 6 files changed, 138 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 544b7858..da95d26a 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,9 @@ # Changelog +# 3.6.0 + +- Feat: new `HCaptcha.removeAllListener` and `HCaptcha.removeOn[Success|Failure|Open]Listener(listener)` to remove all or specific listener. + # 3.5.2 - Bugfix: java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState, on `verifyWithHCaptcha` diff --git a/README.md b/README.md index 0a15de7e..bf28cb0d 100644 --- a/README.md +++ b/README.md @@ -112,6 +112,31 @@ hCaptcha.setup().verifyWithHCaptcha() ``` +To remove a specific listener you may use `HCaptcha.removeOn[Success|Failure|Open]Listener(listener)`. + +To remove all listeners you may use `HCaptcha.removeAllListener()`. + +```java +... +OnSuccessListener firstListener = new OnSuccessListener() { + @Override + public void onSuccess(HCaptchaTokenResponse response) { + ... + } +}; +hCaptcha.addOnSuccessListener(firstListener).verifyWithHCaptcha(); +... +OnSuccessListener secondListener = new OnSuccessListener() { + @Override + public void onSuccess(HCaptchaTokenResponse response) { + ... + } +}; +hCaptcha.removeOnSuccessListener(firstListener) + .addOnSuccessListener(secondListener) + .verifyWithHCaptcha(); +``` + ### Good to know 1. The listeners (`onSuccess`, `onFailure`, `onOpen`) can be called multiple times in the following cases: 1. the same client is used to invoke multiple verifications diff --git a/sdk/build.gradle b/sdk/build.gradle index 0b1784dd..2d62ed91 100644 --- a/sdk/build.gradle +++ b/sdk/build.gradle @@ -22,11 +22,11 @@ android { // See https://developer.android.com/studio/publish/versioning // versionCode must be integer and be incremented by one for every new update // android system uses this to prevent downgrades - versionCode 29 + versionCode 30 // version number visible to the user // should follow semantic versioning (See https://semver.org) - versionName "3.5.2" + versionName "3.6.0" buildConfigField 'String', 'VERSION_NAME', "\"${defaultConfig.versionName}_${defaultConfig.versionCode}\"" diff --git a/sdk/src/androidTest/java/com/hcaptcha/sdk/HCaptchaTest.java b/sdk/src/androidTest/java/com/hcaptcha/sdk/HCaptchaTest.java index 426c363b..102dc9cd 100644 --- a/sdk/src/androidTest/java/com/hcaptcha/sdk/HCaptchaTest.java +++ b/sdk/src/androidTest/java/com/hcaptcha/sdk/HCaptchaTest.java @@ -8,6 +8,7 @@ import androidx.test.core.app.ActivityScenario; import androidx.test.ext.junit.rules.ActivityScenarioRule; +import com.hcaptcha.sdk.tasks.OnSuccessListener; import org.junit.Rule; import org.junit.Test; @@ -63,6 +64,30 @@ public void webViewSessionTimeoutSuppressed() throws Exception { waitHCaptchaWebViewToken(latch, AWAIT_CALLBACK_MS); } + @Test + public void removedListenerShouldNotBeCalled() throws Exception { + final CountDownLatch latch = new CountDownLatch(1); + + final OnSuccessListener listener1 = response -> { + fail("Listener1 should never be called"); + }; + + final OnSuccessListener listener2 = response -> { + response.markUsed(); + latch.countDown(); + }; + + final ActivityScenario scenario = rule.getScenario(); + scenario.onActivity(activity -> HCaptcha.getClient(activity, internalConfig) + .verifyWithHCaptcha(config) + .addOnSuccessListener(listener1) + .addOnFailureListener(exception -> fail("Session timeout should not be happened")) + .removeOnSuccessListener(listener1) + .addOnSuccessListener(listener2)); + + waitHCaptchaWebViewToken(latch, AWAIT_CALLBACK_MS); + } + @Test public void e2eWithDebugTokenFragmentDialog() throws Exception { final CountDownLatch latch = new CountDownLatch(1); diff --git a/sdk/src/main/java/com/hcaptcha/sdk/tasks/Task.java b/sdk/src/main/java/com/hcaptcha/sdk/tasks/Task.java index 89329c7d..5f371920 100644 --- a/sdk/src/main/java/com/hcaptcha/sdk/tasks/Task.java +++ b/sdk/src/main/java/com/hcaptcha/sdk/tasks/Task.java @@ -7,6 +7,7 @@ import com.hcaptcha.sdk.HCaptchaError; import com.hcaptcha.sdk.HCaptchaException; +import com.hcaptcha.sdk.HCaptchaLog; import java.util.ArrayList; import java.util.List; @@ -143,6 +144,18 @@ public Task addOnSuccessListener(@NonNull final OnSuccessListener removeOnSuccessListener(@NonNull final OnSuccessListener onSuccessListener) { + if (!onSuccessListeners.remove(onSuccessListener)) { + HCaptchaLog.d("removeOnSuccessListener: %1 not found and cannot be removed", onSuccessListener); + } + return this; + } + /** * Add a failure listener triggered when the task finishes with an exception * @@ -155,6 +168,18 @@ public Task addOnFailureListener(@NonNull final OnFailureListener onFai return this; } + /** + * Remove a failure listener + * @param onFailureListener to be removed + * @return current object + */ + public Task removeOnFailureListener(@NonNull final OnFailureListener onFailureListener) { + if (!onFailureListeners.remove(onFailureListener)) { + HCaptchaLog.d("removeOnFailureListener: %1 not found and cannot be removed", onFailureListener); + } + return this; + } + /** * Add a hCaptcha open listener triggered when the hCaptcha View is displayed * @@ -167,6 +192,29 @@ public Task addOnOpenListener(@NonNull final OnOpenListener onOpenListe return this; } + /** + * Remove a open listener + * @param onOpenListener to be removed + * @return current object + */ + public Task removeOnOpenListener(@NonNull final OnOpenListener onOpenListener) { + if (!onOpenListeners.remove(onOpenListener)) { + HCaptchaLog.d("removeOnOpenListener: %1 not found and cannot be removed", onOpenListener); + } + return this; + } + + /** + * Remove all listeners: success, failure and open listeners + * @return current object + */ + public Task removeAllListeners() { + onSuccessListeners.clear(); + onFailureListeners.clear(); + onOpenListeners.clear(); + return this; + } + private void tryCb() { boolean shouldReset = false; if (getResult() != null) { diff --git a/sdk/src/test/java/com/hcaptcha/sdk/HCaptchaTest.java b/sdk/src/test/java/com/hcaptcha/sdk/HCaptchaTest.java index c329eabc..59d297e9 100644 --- a/sdk/src/test/java/com/hcaptcha/sdk/HCaptchaTest.java +++ b/sdk/src/test/java/com/hcaptcha/sdk/HCaptchaTest.java @@ -18,6 +18,9 @@ import android.os.Bundle; import androidx.fragment.app.FragmentActivity; +import com.hcaptcha.sdk.tasks.OnFailureListener; +import com.hcaptcha.sdk.tasks.OnOpenListener; +import com.hcaptcha.sdk.tasks.OnSuccessListener; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -45,6 +48,15 @@ public class HCaptchaTest { @Mock HCaptchaDialogFragment fragment; + @Mock + OnSuccessListener onSuccessListener; + + @Mock + OnFailureListener onFailureListener; + + @Mock + OnOpenListener onOpenListener; + @Captor ArgumentCaptor hCaptchaConfigCaptor; @@ -219,4 +231,26 @@ public void test_verify_site_key_has_priority_over_setup_config() throws Excepti assertEquals(HCaptchaConfigTest.MOCK_SITE_KEY, hCaptchaConfigCaptor.getValue().getSiteKey()); } + + @Test + public void test_remove_listener() { + HCaptcha.getClient(fragmentActivity) + .addOnSuccessListener(onSuccessListener) + .removeOnSuccessListener(onSuccessListener); + } + + @Test + public void test_remove_non_existing_listener() { + HCaptcha.getClient(fragmentActivity) + .removeOnSuccessListener(onSuccessListener); + } + + @Test + public void test_clear_all_listener() { + HCaptcha.getClient(fragmentActivity) + .addOnSuccessListener(onSuccessListener) + .addOnFailureListener(onFailureListener) + .addOnOpenListener(onOpenListener) + .removeAllListeners(); + } }