Skip to content

Commit

Permalink
#28 add addOnOpenListener for open-callback events (#30)
Browse files Browse the repository at this point in the history
* #28 add addOnOpenListener for open-callback events

* Improve documentation for addOnOpenListener API

Co-authored-by: e271828- <[email protected]>

* #28 put back iternal OnLoadedListener

* #28 fix PR suggestions

 - wrong function name for onLoaded
 - logging statement for onLoaded stub function

* #28 remove debug logging from JS and don't drop listeners after open event is happens

* Improve open-callback docs

Co-authored-by: e271828- <[email protected]>

* Bump version 2.2.0

Co-authored-by: e271828- <[email protected]>
  • Loading branch information
CAMOBAP and e271828- authored May 4, 2022
1 parent 5ac6b08 commit 76af5d2
Show file tree
Hide file tree
Showing 13 changed files with 142 additions and 15 deletions.
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,19 @@ HCaptcha hCaptcha = HCaptcha.getClient(this).setup()

If `verifyWithHCaptcha` is called with different arguments than `setup` the SDK will handle this by re-configuring hCaptcha. Note that this will reduce some of the performance benefit of using `setup`.

The SDK also provides a listener to track hCaptcha open events, e.g. for analytics:

```java
HCaptcha.getClient(this).verifyWithHCaptcha(YOUR_API_SITE_KEY)
...
.addOnOpenListener(new OnOpenListener() {
@Override
public void onOpen() {
Log.d("MainActivity", "hCaptcha has been displayed");
}
});
```

##### Config params


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@
import android.widget.CheckBox;
import android.widget.RadioGroup;
import android.widget.TextView;
import android.widget.Toast;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
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;


Expand Down Expand Up @@ -112,7 +114,14 @@ public void onFailure(HCaptchaException e) {
Log.d(TAG, "hCaptcha failed: " + e.getMessage() + "(" + e.getStatusCode() + ")");
setErrorTextView(e.getMessage());
}
})
.addOnOpenListener(new OnOpenListener() {
@Override
public void onOpen() {
Toast.makeText(MainActivity.this, "hCaptcha shown", Toast.LENGTH_SHORT).show();
}
});

}

}
4 changes: 2 additions & 2 deletions sdk/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,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 11
versionCode 12

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

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

Expand Down
3 changes: 3 additions & 0 deletions sdk/src/androidTest/assets/hcaptcha-form.html
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@
} catch (e) {
BridgeObject.onError(29);
}
setTimeout(function() {
BridgeObject.onOpen();
}, 200);
}

function onPass() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@
@RunWith(AndroidJUnit4.class)
public class HCaptchaDialogFragmentTest {
public class HCaptchaDialogTestAdapter extends HCaptchaDialogListener {
@Override
void onOpen() {
}

@Override
void onSuccess(HCaptchaTokenResponse hCaptchaTokenResponse) {
}
Expand Down Expand Up @@ -138,4 +142,29 @@ void onFailure(HCaptchaException hCaptchaException) {

assertTrue(latch.await(1000, TimeUnit.MILLISECONDS)); // wait for callback
}

@Test
public void onOpenCallbackWorks() throws Exception {
CountDownLatch latch = new CountDownLatch(1);
final HCaptchaDialogListener listener = new HCaptchaDialogTestAdapter() {
@Override
void onOpen() {
latch.countDown();
}
};

final FragmentScenario<HCaptchaDialogFragment> scenario = launchCaptchaFragment(listener);
onView(withId(R.id.webView)).perform(waitToBeDisplayed(1000));

onWebView(withId(R.id.webView)).forceJavascriptEnabled();

onWebView().withElement(findElement(Locator.ID, "input-text"))
.perform(clearElement())
.perform(DriverAtoms.webKeys("test-token"));

onWebView().withElement(findElement(Locator.ID, "on-pass"))
.perform(webClick());

assertTrue(latch.await(1000, TimeUnit.MILLISECONDS)); // wait for callback
}
}
11 changes: 6 additions & 5 deletions sdk/src/main/assets/hcaptcha-form.html
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,10 @@
return console.log("error: code ".concat(errCode));
},
onLoaded: function onLoaded() {
return console.log('cb: challenge or checkbox is visible');
return console.log('cb: api is loaded');
},
onOpen: function onOpen() {
return console.log('cb: challenge is visible');
}
};
var bridgeConfig = JSON.parse(BridgeObject.getConfig());
Expand All @@ -70,8 +73,6 @@

if (renderConfig.size === 'invisible') {
hcaptcha.execute(hCaptchaID);
} else {
BridgeObject.onLoaded();
}
}

Expand Down Expand Up @@ -132,7 +133,7 @@
}
},
'open-callback': function openCallback() {
return BridgeObject.onLoaded();
return BridgeObject.onOpen();
}
};
}
Expand All @@ -141,7 +142,7 @@
try {
var renderConfig = getRenderConfig();
hCaptchaID = hcaptcha.render('hcaptcha-container', renderConfig);

BridgeObject.onLoaded();
execute(bridgeConfig, renderConfig);
} catch (e) {
console.error(e);
Expand Down
5 changes: 5 additions & 0 deletions sdk/src/main/java/com/hcaptcha/sdk/HCaptcha.java
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,11 @@ public HCaptcha setup(@NonNull final String siteKey) {
public HCaptcha setup(@NonNull final HCaptchaConfig hCaptchaConfig) {
this.hCaptchaConfig = hCaptchaConfig;
this.hCaptchaDialogFragment = HCaptchaDialogFragment.newInstance(hCaptchaConfig, new HCaptchaDialogListener() {
@Override
void onOpen() {
captchaOpened();
}

@Override
void onSuccess(final HCaptchaTokenResponse hCaptchaTokenResponse) {
setResult(hCaptchaTokenResponse);
Expand Down
14 changes: 13 additions & 1 deletion sdk/src/main/java/com/hcaptcha/sdk/HCaptchaDialogFragment.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import androidx.fragment.app.DialogFragment;
import com.hcaptcha.sdk.tasks.OnFailureListener;
import com.hcaptcha.sdk.tasks.OnLoadedListener;
import com.hcaptcha.sdk.tasks.OnOpenListener;
import com.hcaptcha.sdk.tasks.OnSuccessListener;


Expand All @@ -26,6 +27,7 @@
*/
public class HCaptchaDialogFragment extends DialogFragment implements
OnLoadedListener,
OnOpenListener,
OnSuccessListener<HCaptchaTokenResponse>,
OnFailureListener {

Expand Down Expand Up @@ -95,7 +97,7 @@ public void onCreate(Bundle savedInstanceState) {
}
final HCaptchaConfig hCaptchaConfig = (HCaptchaConfig) getArguments().getSerializable(KEY_CONFIG);
this.resetOnTimeout = hCaptchaConfig.getResetOnTimeout();
this.hCaptchaJsInterface = new HCaptchaJSInterface(hCaptchaConfig, this, this, this);
this.hCaptchaJsInterface = new HCaptchaJSInterface(hCaptchaConfig, this, this, this, this);
this.hCaptchaDebugInfo = new HCaptchaDebugInfo(getContext());
this.showLoader = hCaptchaConfig.getLoading();
setStyle(STYLE_NO_FRAME, R.style.HCaptchaDialogTheme);
Expand Down Expand Up @@ -192,6 +194,16 @@ public void onAnimationEnd(Animator animation) {
});
}

@Override
public void onOpen() {
handler.post(new Runnable() {
@Override
public void run() {
hCaptchaDialogListener.onOpen();
}
});
}

@Override
public void onFailure(@NonNull final HCaptchaException hCaptchaException) {
final boolean silentRetry = this.resetOnTimeout && hCaptchaException.getHCaptchaError() == HCaptchaError.SESSION_TIMEOUT;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ abstract class HCaptchaDialogListener implements Parcelable {

abstract void onFailure(HCaptchaException hCaptchaException);

abstract void onOpen();

@Override
public int describeContents() {
return 0;
Expand Down
7 changes: 7 additions & 0 deletions sdk/src/main/java/com/hcaptcha/sdk/HCaptchaJSInterface.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import com.fasterxml.jackson.databind.ObjectMapper;
import com.hcaptcha.sdk.tasks.OnFailureListener;
import com.hcaptcha.sdk.tasks.OnLoadedListener;
import com.hcaptcha.sdk.tasks.OnOpenListener;
import com.hcaptcha.sdk.tasks.OnSuccessListener;
import lombok.AllArgsConstructor;
import lombok.Data;
Expand All @@ -25,6 +26,8 @@ class HCaptchaJSInterface implements Serializable {

private final OnLoadedListener onLoadedListener;

private final OnOpenListener onOpenListener;

private final OnSuccessListener<HCaptchaTokenResponse> onSuccessListener;

private final OnFailureListener onFailureListener;
Expand All @@ -51,4 +54,8 @@ public void onLoaded() {
this.onLoadedListener.onLoaded();
}

@JavascriptInterface
public void onOpen() {
this.onOpenListener.onOpen();
}
}
12 changes: 12 additions & 0 deletions sdk/src/main/java/com/hcaptcha/sdk/tasks/OnOpenListener.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.hcaptcha.sdk.tasks;

/**
* A hCaptcha open listener class
*/
public interface OnOpenListener {

/**
* Called when the hCaptcha challenge is displayed on the html page
*/
void onOpen();
}
24 changes: 24 additions & 0 deletions sdk/src/main/java/com/hcaptcha/sdk/tasks/Task.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ public abstract class Task<TResult> {

private final List<OnFailureListener> onFailureListeners;

private final List<OnOpenListener> onOpenListeners;

/**
* Creates a new Task object
*/
Expand All @@ -36,6 +38,7 @@ protected Task() {
this.successful = false;
this.onSuccessListeners = new ArrayList<>();
this.onFailureListeners = new ArrayList<>();
this.onOpenListeners = new ArrayList<>();
}

/**
Expand Down Expand Up @@ -92,6 +95,15 @@ protected void setException(@NonNull HCaptchaException hCaptchaException) {
tryCb();
}

/**
* Internal callback which called once 'open-callback' fired in js SDK
*/
protected void captchaOpened() {
for (OnOpenListener listener : onOpenListeners) {
listener.onOpen();
}
}

/**
* Add a success listener triggered when the task finishes successfully
*
Expand All @@ -116,6 +128,18 @@ public Task<TResult> addOnFailureListener(@NonNull final OnFailureListener onFai
return this;
}

/**
* Add a hCaptcha open listener triggered when the hCaptcha View is displayed
*
* @param onOpenListener the open listener to be triggered
* @return current object
*/
public Task<TResult> addOnOpenListener(@NonNull final OnOpenListener onOpenListener) {
this.onOpenListeners.add(onOpenListener);
tryCb();
return this;
}

private void tryCb() {
if (getResult() != null) {
final Iterator<OnSuccessListener<TResult>> iterator = onSuccessListeners.iterator();
Expand Down
24 changes: 17 additions & 7 deletions sdk/src/test/java/com/hcaptcha/sdk/HCaptchaJSInterfaceTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.fasterxml.jackson.core.JsonProcessingException;
import com.hcaptcha.sdk.tasks.OnFailureListener;
import com.hcaptcha.sdk.tasks.OnLoadedListener;
import com.hcaptcha.sdk.tasks.OnOpenListener;
import com.hcaptcha.sdk.tasks.OnSuccessListener;

import org.json.JSONException;
Expand All @@ -27,6 +28,9 @@ public class HCaptchaJSInterfaceTest {
@Spy
OnLoadedListener onLoadedListener;

@Spy
OnOpenListener onOpenListener;

@Spy
OnSuccessListener<HCaptchaTokenResponse> onSuccessListener;

Expand Down Expand Up @@ -66,7 +70,7 @@ public void full_config_serialization() throws JsonProcessingException, JSONExce
.host(host)
.resetOnTimeout(true)
.build();
final HCaptchaJSInterface HCaptchaJsInterface = new HCaptchaJSInterface(config, null, null, null);
final HCaptchaJSInterface HCaptchaJsInterface = new HCaptchaJSInterface(config, null, null, null, null);

JSONObject expected = new JSONObject();
expected.put("siteKey", siteKey);
Expand Down Expand Up @@ -101,7 +105,7 @@ public void subset_config_serialization() throws JsonProcessingException, JSONEx
.theme(HCaptchaTheme.DARK)
.rqdata(rqdata)
.build();
final HCaptchaJSInterface HCaptchaJsInterface = new HCaptchaJSInterface(config, null, null, null);
final HCaptchaJSInterface HCaptchaJsInterface = new HCaptchaJSInterface(config, null, null, null, null);

JSONObject expected = new JSONObject();
expected.put("siteKey", siteKey);
Expand All @@ -124,16 +128,23 @@ public void subset_config_serialization() throws JsonProcessingException, JSONEx
}

@Test
public void calls_on_challenge_visible_cb() {
final HCaptchaJSInterface hCaptchaJSInterface = new HCaptchaJSInterface(null, onLoadedListener, null, null);
public void calls_on_challenge_ready() {
final HCaptchaJSInterface hCaptchaJSInterface = new HCaptchaJSInterface(null, onLoadedListener, null, null, null);
hCaptchaJSInterface.onLoaded();
verify(onLoadedListener, times(1)).onLoaded();
}

@Test
public void calls_on_challenge_visible_cb() {
final HCaptchaJSInterface hCaptchaJSInterface = new HCaptchaJSInterface(null, null, onOpenListener, null, null);
hCaptchaJSInterface.onOpen();
verify(onOpenListener, times(1)).onOpen();
}

@Test
public void on_pass_forwards_token_to_listeners() {
final String token = "mock-token";
final HCaptchaJSInterface hCaptchaJSInterface = new HCaptchaJSInterface(null, null, onSuccessListener, null);
final HCaptchaJSInterface hCaptchaJSInterface = new HCaptchaJSInterface(null, null, null, onSuccessListener, null);
hCaptchaJSInterface.onPass(token);
verify(onSuccessListener, times(1)).onSuccess(tokenCaptor.capture());
assertEquals(token, tokenCaptor.getValue().getTokenResult());
Expand All @@ -142,11 +153,10 @@ public void on_pass_forwards_token_to_listeners() {
@Test
public void on_error_forwards_error_to_listeners() {
final HCaptchaError error = HCaptchaError.CHALLENGE_CLOSED;
final HCaptchaJSInterface hCaptchaJSInterface = new HCaptchaJSInterface(null, null, null, onFailureListener);
final HCaptchaJSInterface hCaptchaJSInterface = new HCaptchaJSInterface(null, null, null, null, onFailureListener);
hCaptchaJSInterface.onError(error.getErrorId());
verify(onFailureListener, times(1)).onFailure(exceptionCaptor.capture());
assertEquals(error.getMessage(), exceptionCaptor.getValue().getMessage());
assertNotNull(exceptionCaptor.getValue());
}

}

0 comments on commit 76af5d2

Please sign in to comment.