Skip to content

Commit

Permalink
feat(sdk): reset (#104)
Browse files Browse the repository at this point in the history
Co-authored-by: Sergiu Danalachi <[email protected]>
  • Loading branch information
CAMOBAP and DSergiu authored Apr 5, 2023
1 parent 1ace918 commit b58f3a7
Show file tree
Hide file tree
Showing 16 changed files with 142 additions and 45 deletions.
4 changes: 4 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Changelog

# 3.8.0

- Feat: new `HCaptcha.reset` to force stop verification and release all resources.

# 3.7.0

- Feat: new `HCaptchaConfig.orientation` to set either `portrait` or `landscape` challenge orientation.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,9 @@ public void onOpen() {
public void onSuccess(String s) {
// no implementation need for performance measurement
}

@Override
public void reset() {
// no implementation need for performance measurement
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,6 @@
import androidx.annotation.RequiresApi;
import androidx.appcompat.app.AppCompatActivity;
import com.hcaptcha.sdk.*;
import com.hcaptcha.sdk.tasks.OnFailureListener;
import com.hcaptcha.sdk.tasks.OnOpenListener;
import com.hcaptcha.sdk.tasks.OnSuccessListener;

import java.util.Arrays;

Expand Down Expand Up @@ -82,7 +79,10 @@ private void setErrorTextView(final String error) {
errorTextView.setText(error);
}

public void onClickClear(final View v) {
public void onClickReset(final View view) {
if (hCaptcha != null) {
hCaptcha.reset();
}
setTokenTextView("-");
hCaptcha = null;
}
Expand Down
5 changes: 3 additions & 2 deletions example-app/src/main/res/layout/activity_main.xml
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,8 @@
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/clear"
android:onClick="onClickClear" />
android:text="@string/reset"
android:onClick="onClickReset" />

<Button
android:layout_width="wrap_content"
Expand All @@ -70,6 +70,7 @@
android:layout_height="wrap_content"
android:text="@string/mark_used"
android:onClick="onMarkUsed" />

</LinearLayout>

<LinearLayout
Expand Down
2 changes: 1 addition & 1 deletion example-app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<resources>
<string name="app_name">Example hCaptcha App</string>
<string name="clear">Clear</string>
<string name="reset">Reset</string>
<string name="setup">Setup</string>
<string name="verify">Verify</string>
<string name="mark_used">Mark used</string>
Expand Down
4 changes: 2 additions & 2 deletions sdk/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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 31
versionCode 32

// version number visible to the user
// should follow semantic versioning (See https://semver.org)
versionName "3.7.0"
versionName "3.8.0"

buildConfigField 'String', 'VERSION_NAME', "\"${defaultConfig.versionName}_${defaultConfig.versionCode}\""

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -325,4 +325,15 @@ public void testVerifyOnStoppedFragmentNoException() throws InterruptedException
}
assertTrue(latch.await(AWAIT_CALLBACK_MS, TimeUnit.MILLISECONDS));
}

@Test(expected = IllegalArgumentException.class)
public void testReset() {
final FragmentScenario<HCaptchaDialogFragment> scenario = launchCaptchaFragment(
config, new HCaptchaStateTestAdapter());

scenario.onFragment(HCaptchaDialogFragment::reset);

// The fragment has been removed from the FragmentManager already.
scenario.onFragment(fragment -> assertTrue(fragment.isDetached()));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.test.core.app.ActivityScenario;
import androidx.test.ext.junit.rules.ActivityScenarioRule;
import androidx.test.ext.junit.runners.AndroidJUnit4;
Expand Down Expand Up @@ -162,4 +165,32 @@ void onFailure(HCaptchaException exception) {

assertTrue(failureLatch.await(AWAIT_CALLBACK_MS, TimeUnit.MILLISECONDS));
}

@Test
public void testReset() throws Exception {
final CountDownLatch latch = new CountDownLatch(1);

final ActivityScenario<TestActivity> scenario = rule.getScenario();
scenario.onActivity(activity -> {
final HCaptchaHeadlessWebView subject = new HCaptchaHeadlessWebView(
activity, config, internalConfig, new HCaptchaStateTestAdapter());

final ViewGroup rootView = (ViewGroup) activity.getWindow().getDecorView().getRootView();
final View webView = rootView.findViewById(R.id.webView);
webView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
@Override
public void onViewAttachedToWindow(@NonNull View view) {
// will not be fired because attached already
}

@Override
public void onViewDetachedFromWindow(@NonNull View view) {
latch.countDown();
}
});
subject.reset();
});

assertTrue(latch.await(AWAIT_CALLBACK_MS, TimeUnit.MILLISECONDS));
}
}
3 changes: 3 additions & 0 deletions sdk/src/main/html/hcaptcha.html
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,9 @@
hcaptcha.reset();
hcaptcha.execute(hCaptchaID);
}
function reset() {
hcaptcha.reset();
}
function getTheme(bridgeConfig) {
var theme = bridgeConfig.theme;
var customTheme = bridgeConfig.customTheme;
Expand Down
8 changes: 8 additions & 0 deletions sdk/src/main/java/com/hcaptcha/sdk/HCaptcha.java
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,14 @@ public HCaptcha verifyWithHCaptcha(@NonNull final HCaptchaConfig inputConfig) {
return startVerification();
}

@Override
public void reset() {
if (captchaVerifier != null) {
captchaVerifier.reset();
captchaVerifier = null;
}
}

private HCaptcha startVerification() {
HCaptchaLog.d("HCaptcha.startVerification");
handler.removeCallbacksAndMessages(null);
Expand Down
10 changes: 10 additions & 0 deletions sdk/src/main/java/com/hcaptcha/sdk/HCaptchaDialogFragment.java
Original file line number Diff line number Diff line change
Expand Up @@ -249,4 +249,14 @@ public void startVerification(@NonNull FragmentActivity fragmentActivity) {
}
}
}

@Override
public void reset() {
if (webViewHelper != null) {
webViewHelper.reset();
}
if (isAdded()) {
dismissAllowingStateLoss();
}
}
}
19 changes: 18 additions & 1 deletion sdk/src/main/java/com/hcaptcha/sdk/HCaptchaHeadlessWebView.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ final class HCaptchaHeadlessWebView implements IHCaptchaVerifier {

private boolean webViewLoaded;
private boolean shouldExecuteOnLoad;
private boolean shouldResetOnLoad;

HCaptchaHeadlessWebView(@NonNull final FragmentActivity activity,
@NonNull final HCaptchaConfig config,
Expand Down Expand Up @@ -70,7 +71,10 @@ public void onSuccess(final String token) {
@Override
public void onLoaded() {
webViewLoaded = true;
if (shouldExecuteOnLoad) {
if (shouldResetOnLoad) {
shouldResetOnLoad = false;
reset();
} else if (shouldExecuteOnLoad) {
shouldExecuteOnLoad = false;
webViewHelper.resetAndExecute();
}
Expand All @@ -80,4 +84,17 @@ public void onLoaded() {
public void onOpen() {
listener.onOpen();
}

@Override
public void reset() {
if (webViewLoaded) {
webViewHelper.reset();
final WebView webView = webViewHelper.getWebView();
if (webView.getParent() != null) {
((ViewGroup) webView.getParent()).removeView(webView);
}
} else {
shouldResetOnLoad = true;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -106,10 +106,14 @@ public void destroy() {
webView.destroy();
}

public void resetAndExecute() {
void resetAndExecute() {
webView.loadUrl("javascript:resetAndExecute();");
}

void reset() {
webView.loadUrl("javascript:reset();");
}

public boolean shouldRetry(HCaptchaException exception) {
return config.getRetryPredicate().shouldRetry(config, exception);
}
Expand Down
4 changes: 4 additions & 0 deletions sdk/src/main/java/com/hcaptcha/sdk/IHCaptcha.java
Original file line number Diff line number Diff line change
Expand Up @@ -84,4 +84,8 @@ public interface IHCaptcha {
*/
HCaptcha verifyWithHCaptcha(@NonNull HCaptchaConfig config);

/**
* Force stop verification and release resources.
*/
void reset();
}
4 changes: 4 additions & 0 deletions sdk/src/main/java/com/hcaptcha/sdk/IHCaptchaVerifier.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,8 @@ interface IHCaptchaVerifier extends
*/
void startVerification(@NonNull FragmentActivity activity);

/**
* Force stop verification and release resources.
*/
void reset();
}
63 changes: 29 additions & 34 deletions sdk/src/test/java/com/hcaptcha/sdk/HCaptchaTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,12 @@ public class HCaptchaTest {

MockedStatic<HCaptchaDialogFragment> dialogFragmentMock;

final HCaptchaConfig config = HCaptchaConfig.builder()
.siteKey(HCaptchaConfigTest.MOCK_SITE_KEY)
.size(HCaptchaSize.NORMAL)
.loading(true)
.build();

@Before
public void init() {
MockitoAnnotations.openMocks(this);
Expand Down Expand Up @@ -130,17 +136,17 @@ public void test_verify_with_hcaptcha_passes_site_key_as_config() {
any(HCaptchaStateListener.class)));
verify(fragment).startVerification(fragmentActivity);

final HCaptchaConfig config = hCaptchaConfigCaptor.getValue();
assertNotNull(config);
assertEquals(siteKey, config.getSiteKey());
final HCaptchaConfig capturedConfig = hCaptchaConfigCaptor.getValue();
assertNotNull(capturedConfig);
assertEquals(siteKey, capturedConfig.getSiteKey());

// Rest of params must be the defaults
final String locale = Locale.getDefault().getLanguage();
assertEquals(HCaptchaSize.INVISIBLE, config.getSize());
assertEquals(HCaptchaTheme.LIGHT, config.getTheme());
assertNull(config.getRqdata());
assertEquals(locale, config.getLocale());
assertEquals("https://js.hcaptcha.com/1/api.js", config.getApiEndpoint());
assertEquals(HCaptchaSize.INVISIBLE, capturedConfig.getSize());
assertEquals(HCaptchaTheme.LIGHT, capturedConfig.getTheme());
assertNull(capturedConfig.getRqdata());
assertEquals(locale, capturedConfig.getLocale());
assertEquals("https://js.hcaptcha.com/1/api.js", capturedConfig.getApiEndpoint());
}

@Test
Expand All @@ -156,18 +162,12 @@ public void test_verify_site_key_arg_has_priority_over_metadata() throws Excepti
any(HCaptchaInternalConfig.class),
any(HCaptchaStateListener.class)));

final HCaptchaConfig config = hCaptchaConfigCaptor.getValue();
assertEquals(HCaptchaConfigTest.MOCK_SITE_KEY, config.getSiteKey());
final HCaptchaConfig capturedConfig = hCaptchaConfigCaptor.getValue();
assertEquals(HCaptchaConfigTest.MOCK_SITE_KEY, capturedConfig.getSiteKey());
}

@Test
public void test_setup_config() throws Exception {
final HCaptchaConfig config = HCaptchaConfig.builder()
.siteKey(HCaptchaConfigTest.MOCK_SITE_KEY)
.size(HCaptchaSize.NORMAL)
.loading(true)
.build();

HCaptcha.getClient(fragmentActivity)
.setup(config)
.verifyWithHCaptcha();
Expand All @@ -184,20 +184,14 @@ public void test_setup_config() throws Exception {

@Test
public void test_verify_config_has_priority_over_setup_config() throws Exception {
final HCaptchaConfig setupConfig = HCaptchaConfig.builder()
.siteKey("SETUP-SITE-KEY")
final HCaptchaConfig verifyConfig = HCaptchaConfig.builder()
.siteKey(HCaptchaConfigTest.MOCK_SITE_KEY + "-on-verify")
.size(HCaptchaSize.INVISIBLE)
.loading(false)
.build();

final HCaptchaConfig verifyConfig = HCaptchaConfig.builder()
.siteKey(HCaptchaConfigTest.MOCK_SITE_KEY)
.size(HCaptchaSize.NORMAL)
.loading(true)
.build();

HCaptcha.getClient(fragmentActivity)
.setup(setupConfig)
.setup(config)
.verifyWithHCaptcha(verifyConfig);

verify(packageManager, never()).getApplicationInfo(any(String.class), anyInt());
Expand All @@ -212,15 +206,10 @@ public void test_verify_config_has_priority_over_setup_config() throws Exception

@Test
public void test_verify_site_key_has_priority_over_setup_config() throws Exception {
final HCaptchaConfig setupConfig = HCaptchaConfig.builder()
.siteKey("SETUP-SITE-KEY")
.size(HCaptchaSize.INVISIBLE)
.loading(false)
.build();

final String siteKey = HCaptchaConfigTest.MOCK_SITE_KEY + "-on-verify";
HCaptcha.getClient(fragmentActivity)
.setup(setupConfig)
.verifyWithHCaptcha(HCaptchaConfigTest.MOCK_SITE_KEY);
.setup(config)
.verifyWithHCaptcha(siteKey);

verify(packageManager, never()).getApplicationInfo(any(String.class), anyInt());
dialogFragmentMock.verify(times(2), () ->
Expand All @@ -229,7 +218,13 @@ public void test_verify_site_key_has_priority_over_setup_config() throws Excepti
any(HCaptchaInternalConfig.class),
any(HCaptchaStateListener.class)));

assertEquals(HCaptchaConfigTest.MOCK_SITE_KEY, hCaptchaConfigCaptor.getValue().getSiteKey());
assertEquals(siteKey, hCaptchaConfigCaptor.getValue().getSiteKey());
}

@Test
public void test_reset() {
HCaptcha.getClient(fragmentActivity).setup(config).reset();
verify(fragment).reset();
}

@Test
Expand Down

0 comments on commit b58f3a7

Please sign in to comment.