Giter Site home page Giter Site logo

hcaptcha-android-sdk's Introduction

Android SDK for hCaptcha

CI Release Minimal Android OS

This SDK provides a wrapper for hCaptcha. It is a drop-in replacement for the SafetyNet reCAPTCHA API. You will need to configure a site key and a secret key from your hCaptcha account in order to use it.

Installation

// Register JitPack Repository inside the root build.gradle file
repositories {
    maven { url 'https://jitpack.io' } 
}
// Add hCaptcha sdk dependency inside the app's build.gradle file
dependencies {
    implementation 'com.github.hcaptcha:hcaptcha-android-sdk:x.y.z'
}

Note: replace x.y.z with one from Release (e.g. 1.0.0).

Requirements

Platform Requirements
Android OS ✅ >= 4.1 (Android API 16)
Wear OS ✖️

Example App

The current repository comes with an example Android application demonstrating 3 different hCaptcha usage patterns.

See the code example below along with the possible customization to enable human verification in your Android application.

invisible hcaptcha example

Usage

There are multiple ways to run a hCaptcha human verification. See the below snippet for the overall flow.

import com.hcaptcha.sdk.*;
import com.hcaptcha.sdk.tasks.*;

// =================================================
// 1. Initialize a client using the current activity
final HCaptcha hCaptcha = HCaptcha.getClient(this);

// =================================================
// 2. Add the desired listeners
hCaptcha
  .addOnSuccessListener(new OnSuccessListener<HCaptchaTokenResponse>() {
    @Override
    public void onSuccess(HCaptchaTokenResponse response) {
        // Successul verification. The resulting token must be passed to your backend to be validated.
        String userResponseToken = response.getTokenResult();
        Log.d("hCaptcha", "hCaptcha success. Token: " + userResponseToken");
    }
  })
  .addOnFailureListener(new OnFailureListener() {
    @Override
    public void onFailure(HCaptchaException e) {
        // Error handling here: trigger another verification, display a toast, etc.
        Log.d("hCaptcha", "hCaptcha failed: " + e.getMessage() + "(" + e.getStatusCode() + ")");
    }
  })
  .addOnOpenListener(new OnOpenListener() {
    @Override
    public void onOpen() {
        // Usefull for analytics purposes
        Log.d("hCaptcha", "hCaptcha is now visible.");
    }
  });

// =================================================
// 3. Trigger the verification process which may or may not require user input. 
//    It depends on the sitekey difficulty setting and the hCaptcha client configuration.
// 3.1 Optionaly, setup the client to pre-warm the assets.
//     It helps speeding up the actual verification process by having the assets locally already.
hCaptcha.setup(/* args */);
// 3.2 Invoke the actual verification process
//     If "setup(/* args */)" was used, this should be called with empty args.
hCaptcha.verifyWithHCaptcha(/* args */).

// The "args" for setup and verifyWithHCaptcha can be the following:
// 1. The sitekey string.
final String SITE_KEY = "10000000-ffff-ffff-ffff-000000000001";
hCaptcha.setup(SITE_KEY).verifyWithHCaptcha()
// 2. An "HCaptchaConfig" object which allows customization 
//    of the look and feel, the language and more. 
//    See section "Config Params" below.
final HCaptchaConfig hCaptchaConfig = HCaptchaConfig.builder()
        .siteKey("10000000-ffff-ffff-ffff-000000000001")
        .size(HCaptchaSize.NORMAL)
        .theme(HCaptchaTheme.LIGHT)
        .build();
hCaptcha.setup(hCaptchaConfig).verifyWithHCaptcha()
// 3. No params. Sitekey must be configured via `AndroidManifest.xml`.
hCaptcha.setup().verifyWithHCaptcha()
// Set sitekey in AndroidManifest.xml (required only for option 3)
<?xml version="1.0" encoding="utf-8"?>
<application ...>
   <meta-data android:name="com.hcaptcha.sdk.site-key"
              android:value="YOUR_API_SITE_KEY" />
</application>
</manifest>

To remove a specific listener you may use HCaptcha.removeOn[Success|Failure|Open]Listener(listener).

To remove all listeners you may use HCaptcha.removeAllListener().

Note ⚠️: For any sitekey that can show visual challenges, HCaptcha.getClient(Activity) must be called with FragmentActivity instance.

Activity is allowed only when hideDialog=true and sitekey setting is Passive (Enterprise feature).

...
OnSuccessListener<HCaptchaTokenResponse> firstListener = new OnSuccessListener<HCaptchaTokenResponse>() {
    @Override
    public void onSuccess(HCaptchaTokenResponse response) {
        ...
    }
};
hCaptcha.addOnSuccessListener(firstListener).verifyWithHCaptcha();
...
OnSuccessListener<HCaptchaTokenResponse> secondListener = new OnSuccessListener<HCaptchaTokenResponse>() {
    @Override
    public void onSuccess(HCaptchaTokenResponse response) {
        ...
    }
};
hCaptcha.removeOnSuccessListener(firstListener)
    .addOnSuccessListener(secondListener)
    .verifyWithHCaptcha();

For Jetpack Compose

import com.hcaptcha.sdk.HCaptcha
import com.hcaptcha.sdk.HCaptchaException
import com.hcaptcha.sdk.HCaptchaTokenResponse

class HCaptchaActivity : AppCompatActivity() {

    private val hCaptcha = HCaptcha.getClient(this)
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        hCaptcha.setup(BuildConfig.SITE_KEY).verifyWithHCaptcha()
        hCaptcha.addOnSuccessListener { response: HCaptchaTokenResponse ->
            val userResponseToken = response.tokenResult
            val intent = Intent()
            intent.putExtra("captcha", userResponseToken)
            setResult(RESULT_OK, intent)
            finish()
        }.addOnFailureListener { e: HCaptchaException ->
            // Error handling here: trigger another verification, display a toast, etc.
            Log.d("hCaptcha", "hCaptcha failed: " + e.getMessage() + "(" + e.getStatusCode() + ")")
            setResult(RESULT_CANCELED)
            finish()
        }.addOnOpenListener { 
            // Usefull for analytics purposes
            Log.d("hCaptcha", "hCaptcha is now visible.")
        }
    }
}

To fetch token data from activity in @Composable Class:

val intent = Intent(context, HCaptchaActivity::class.java)
                val launcher =
                    rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
                        val data: Intent? = result.data
                        when (result.resultCode) {
                            Activity.RESULT_OK -> {
                                data?.let {
                                    captcha.value = data.extras?.getString("captcha")?: ""
                                }                        
                            }
                            Activity.RESULT_CANCELED -> {
                                Log.d("hCaptcha", "hCaptcha failed")
                            }
                        }
                    }
                SideEffect {
                    launcher.launch(intent)
                }

Memory usage (fragment lifecycle)

We cache the fragment instance inside the SDK to speed up the next HCaptcha.verifyWithHCaptcha calls.

Once you are done with verification, you can call HCaptcha.reset() to release all allocated resources including the strong reference to com.hcaptcha.sdk.HCaptchaDialogFragment.

Note: If you do not call .reset() you will likely see a warning from tools like LeakCanary.

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
    2. the config option resetOnTimeout(true) is used which will automatically trigger a new verification when the current token expired. This will result in a new success or error callback.
    3. the config option HCaptchaConfig.retryPredicate is used to automatically trigger a new verification when some error occurs. If HCaptchaConfig.retryPredicate returns true, this will result in a new success or error callback.
    4. onFailure with TOKEN_TIMEOUT will be called once the token is expired. To prevent this you can call HCaptchaTokenResponse.markUsed once the token is utilized. Also, you can change expiration timeout with HCaptchaConfigBuilder.tokenExpiration(timeout) (default 2 min.)

Config Params

The following list contains configuration properties to allows customization of the hCaptcha verification flow.

Name Values/Type Required Default Description
siteKey String Yes - This is your sitekey, this allows you to load challenges. If you need a sitekey, please visit hCaptcha, and sign up to get your sitekey.
size Enum No INVISIBLE This specifies the "size" of the checkbox component. By default, the checkbox is invisible and the challenge is shown automatically.
orientation Enum No PORTRAIT This specifies the "orientation" of the challenge.
theme Enum No LIGHT hCaptcha supports light, dark, and contrast themes.
locale String (ISO 639-1 code) No AUTO You can enforce a specific language or let hCaptcha auto-detect the local language based on user's device.
resetOnTimeout Boolean No False (DEPRECATED, use retryPredicate) Automatically reload to fetch new challenge if user does not submit challenge. (Matches iOS SDK behavior.)
retryPredicate Lambda* No - Automatically trigger a new verification when some error occurs.
jsSrc String (URL) No https://js.hcaptcha.com/1/api.js See Enterprise docs.
sentry Boolean No True See Enterprise docs.
rqdata String No - See Enterprise docs.
apiEndpoint String (URL) No - (DEPRECATED, use jsSrc) See Enterprise docs.
endpoint String (URL) No - See Enterprise docs.
reportapi String (URL) No - See Enterprise docs.
assethost String (URL) No - See Enterprise docs.
imghost String (URL) No - See Enterprise docs.
customTheme Stringified JSON No - See Enterprise docs.
host String (URL) No - See Enterprise docs.
loading Boolean No True Show or hide the loading dialog.
hideDialog Boolean No False To be used in combination with a passive sitekey when no user interaction is required. See Enterprise docs.
tokenExpiration long No 120 hCaptcha token expiration timeout (seconds).
diagnosticLog Boolean No False Emit detailed console logs for debugging
disableHardwareAcceleration Boolean No True Disable WebView hardware acceleration

Config Examples

  1. Ask the user to complete a challenge without requiring a previous checkbox tap.
final HCaptchaConfig config = HCaptchaConfig.builder()
        .siteKey(YOUR_API_SITE_KEY)
        .size(HCaptchaSize.INVISIBLE)
        .build();
  1. Set a specific language, use a dark theme and a compact checkbox.
final HCaptchaConfig config = HCaptchaConfig.builder()
                .siteKey("YOUR_API_SITE_KEY")
                .locale("ro")
                .size(HCaptchaSize.COMPACT)
                .theme(HCaptchaTheme.DARK)
                .build();

Error handling

In some scenarios in which the human verification process cannot be completed. You can add logic to gracefully handle the errors.

The following is a list of possible error codes:

Name Code Description
NETWORK_ERROR 7 There is no internet connection.
INVALID_DATA 8 Invalid data is not accepted by endpoints.
CHALLENGE_ERROR 9 JS client encountered an error on challenge setup.
INTERNAL_ERROR 10 JS client encountered an internal error.
SESSION_TIMEOUT 15 The challenge expired.
TOKEN_TIMEOUT 16 The token expired.
CHALLENGE_CLOSED 30 The challenge was closed by the user.
RATE_LIMITED 31 Spam detected.
INVALID_CUSTOM_THEME 32 Invalid custom theme.
INSECURE_HTTP_REQUEST_ERROR 33 Insecure resource requested.
ERROR 29 General failure.

Retry Failed Verification

You can indicate an automatic verification retry by setting the lambda config HCaptchaConfig.retryPredicate.

One must be careful to not introduce infinite retries and thus blocking the user from error recovering.

Example below will automatically retry in case of CHALLENGE_CLOSED error:

final HCaptchaConfig config = HCaptchaConfig.builder()
        .siteKey("YOUR_API_SITE_KEY")
        .retryPredicate((config, hCaptchaException) -> {
            return hCaptchaException.getHCaptchaError() == HCaptchaError.CHALLENGE_CLOSED;
        })
        .build();
...

Retry predicate serialization

Lambda may implicitly capture variables from its surrounding context. For a lambda to be serializable, all the captured variables must be serializable as well. Failing to meet this requirement can result in runtime errors when attempting to deserialize the lambda.

The retryPredicate is part of HCaptchaConfig that may get persist during application lifecycle. So pay attention to this aspect and make sure that retryPredicate is serializable to avoid android.os.BadParcelableException in run-time.

Debugging Tips

Useful error messages are often rendered on the hCaptcha checkbox. For example, if the sitekey within your config is invalid, you'll see a message there. To quickly debug your local instance using this tool, set .size(HCaptchaSize.NORMAL)

HCaptchaConfigBuilder.diagnosticLog(true) can help to get more detailed logs.

Verify the completed challenge

After retrieving a token, you should pass it to your backend in order to verify the validity of the token by doing a server side check using the hCaptcha secret linked to your sitekey.


FAQ

Can I get a token in a non-UI thread?

No: the SDK depends on WebView, which is a UI component and cannot be instantiated in a non-UI thread.

However, the SDK provides a completely silent (invisible to the end-user) mechanism with hideDialog=true config + "passive" site key (this is an Enterprise feature). But note that the token request still has to be called from the UI thread.

How can I prevent the hCaptcha verification from being canceled when the back button is pressed?

It is possible by specifying HCaptchaConfig.retryPredicate as shown in the following code snippet:

final HCaptchaConfig config = HCaptchaConfig.builder()
        .siteKey("YOUR_API_SITE_KEY")
        .retryPredicate((config, hCaptchaException) -> {
            return hCaptchaException.getHCaptchaError() == HCaptchaError.CHALLENGE_CLOSED;
        })
        .build();

HCaptcha constantly failing with IllegalStateException "Visual Challenge verification require FragmentActivity", how to fix it?

SDK expect to be initialized with FragmentActivity instance in regular scenario.

In case if you use passive siteKey make sure that you called hideDialog(true) on HCaptchaCconfig.builder()

For maintainers

If you plan to contribute to the repo, please see MAINTAINERS.md for detailed build, test, and release instructions.

hcaptcha-android-sdk's People

Contributors

camobap avatar dsergiu avatar e271828- avatar onurkaral avatar vladd-g avatar wontem avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

hcaptcha-android-sdk's Issues

HCaptcha dialog recieves click events when not showing

Currently the code allows for the dialog to receive click events even when it is not showing.
Our setup is as follows:

    .builder()
    .siteKey(siteKey)
    .diagnosticLog(false)
    .loading(false)
    .hideDialog(false)
    .build()

Even though the dialog is not showing, it still takes and listens to click events on the location. This currently gives an error in the callback chain, and more to the problem, it sits on top of our existing login button. If a user clicks on our apps login button while hcaptcha is verifying, hcaptcha will trigger addOnFailureListener because the invisible dialog sitting on top of our apps login button. Could this be fixed?

java.lang.IllegalStateException: Fragment already added

Problem

Multiple quick taps on "Show" button lead to:

2022-05-02 12:52:12.090 18570-18570/com.hcaptcha.example E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.hcaptcha.example, PID: 18570
    java.lang.IllegalStateException: Fragment already added: HCaptchaDialogFragment{992cc59} (62d1b750-e42c-425d-817a-530caa1a9f96 tag=HCaptchaDialogFragment)
        at androidx.fragment.app.FragmentStore.addFragment(FragmentStore.java:92)
        at androidx.fragment.app.FragmentManager.addFragment(FragmentManager.java:1422)
        at androidx.fragment.app.BackStackRecord.executeOps(BackStackRecord.java:387)
        at androidx.fragment.app.FragmentManager.executeOps(FragmentManager.java:1906)
        at androidx.fragment.app.FragmentManager.executeOpsTogether(FragmentManager.java:1814)
        at androidx.fragment.app.FragmentManager.removeRedundantOperationsAndExecute(FragmentManager.java:1764)
        at androidx.fragment.app.FragmentManager.execPendingActions(FragmentManager.java:1701)
        at androidx.fragment.app.FragmentManager$4.run(FragmentManager.java:488)
        at android.os.Handler.handleCallback(Handler.java:942)
        at android.os.Handler.dispatchMessage(Handler.java:99)
        at android.os.Looper.loopOnce(Looper.java:201)
        at android.os.Looper.loop(Looper.java:288)
        at android.app.ActivityThread.main(ActivityThread.java:7844)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:936)

Proposed solution

Make sense to add a simple guard with isAdded + UI tests

Issues with implementing 3.5.0 version

Hi, trying to implement com.github.hcaptcha:hcaptcha-android-sdk 3.5.0 version, but getting an error
Caused by: org.gradle.internal.resolve.ModuleVersionNotFoundException: Could not find com.github.hcaptcha:hcaptcha-android-sdk:3.5.0.

From releases I see its the newest one: https://github.com/hCaptcha/hcaptcha-android-sdk/releases/tag/3.5.0

Yet, in Jitpack can't find it as well:
https://jitpack.io/com/github/hcaptcha/hcaptcha-android-sdk/3.5.0/
with 3.4.0 - no problems (https://jitpack.io/com/github/hcaptcha/hcaptcha-android-sdk/3.4.0/)

Unable to get client without fragment activity context

HCaptcha.getClient(context) casts context to FragmentActivity. If application is written with Jetpack Compose - fragment activity can not be provided.

How to get HCaptcha client when fragment activity context can not be provided?

Adjust content overlay color

Currently there is a white opacity set on the overlay area (clickable region) behind the challenge view, see screen shot. We want to either change that to being fully transparent or black set to a 10 - 15% opacity.

white_opacity

Memory leak detected by LeakCanary (Edit: needed to call .reset() to clear resources)


Hello,

I'm using the hCaptcha SDK in my app and I'm experiencing a memory leak issue. Upon finishing the captcha, a memory leak occurs.

I've attached my implementation code and the LeakCanary log below. Let me know if you need any more information and whether there are any workarounds or fixes available to address this issue.

Implementation code:
I'm using Jetpack Compose, with an activity extending FragmentActivity. Then, from inside a viewmodel, I have a function like this:

fun signInViaEmail(activity: FragmentActivity, onComplete: () -> Unit) {
      val hCaptcha = HCaptcha.getClient(activity)
      hCaptcha.addOnSuccessListener { response -> 
            // Check the response
            onComplete()
      }
}

This function is then in turn called from my Jetpack Compose UI where LocalContext.current is being casted to FragmentActivity.

LeakCanary log:

┬───
│ GC Root: System class
│
├─ android.app.ActivityThread class
│    Leaking: NO (MessageQueue↓ is not leaking and a class is never leaking)
│    ↓ static ActivityThread.sMainThreadHandler
├─ android.app.ActivityThread$H instance
│    Leaking: NO (MessageQueue↓ is not leaking)
│    ↓ Handler.mQueue
├─ android.os.MessageQueue instance
│    Leaking: NO (MessageQueue#mQuitting is false)
│    HandlerThread: "main"
│    ↓ MessageQueue[3]
│                  ~~~
├─ android.os.Message instance
│    Leaking: UNKNOWN
│    Retaining 63.0 kB in 1481 objects
│    Message.what = 0
│    Message.when = 396137 (74325 ms after heap dump)
│    Message.obj = null
│    Message.callback = instance @328107720 of com.hcaptcha.sdk.tasks.Task$1
│    Message.target = instance @325042816 of android.os.Handler
│    ↓ Message.callback
│              ~~~~~~~~
├─ com.hcaptcha.sdk.tasks.Task$1 instance
│    Leaking: UNKNOWN
│    Retaining 62.9 kB in 1480 objects
│    Anonymous class implementing java.lang.Runnable
│    ↓ Task$1.this$0
│             ~~~~~~
├─ com.hcaptcha.sdk.HCaptcha instance
│    Leaking: UNKNOWN
│    Retaining 62.9 kB in 1479 objects
│    activity instance of REDACTED.MainActivity with mDestroyed = false
│    ↓ HCaptcha.captchaVerifier
│               ~~~~~~~~~~~~~~~
╰→ com.hcaptcha.sdk.HCaptchaDialogFragment instance
​     Leaking: YES (ObjectWatcher was watching this because com.hcaptcha.sdk.HCaptchaDialogFragment received
​     Fragment#onDestroy() callback and Fragment#mFragmentManager is null)
​     Retaining 62.5 kB in 1466 objects
​     key = ae934461-4e99-4218-b07e-88cf2948fb5c
​     watchDurationMillis = 45527
​     retainedDurationMillis = 40526

METADATA

Build.VERSION.SDK_INT: 32
Build.MANUFACTURER: Google
LeakCanary version: 2.10
App process name: REDACTED
Class count: 36680
Instance count: 241264
Primitive array count: 161447
Object array count: 37254
Thread count: 130
Heap total bytes: 37370546
Bitmap count: 13
Bitmap total bytes: 1497189
Large bitmap count: 0
Large bitmap total bytes: 0
Db 1: open /data/user/0/REDACTED/databases/notification_db
Db 2: closed /data/user/0/REDACTED/databases/google_app_measurement_local.db
Db 3: open /data/user/0/REDACTED/databases/app_db
Db 4: open /data/user/0/REDACTED/databases/com.google.android.datatransport.events
Db 5: open /data/user/0/REDACTED/no_backup/androidx.work.workdb
Stats: LruCache[maxSize=3000,hits=126963,misses=237397,hitRate=34%]
RandomAccess[bytes=11735220,reads=237397,travel=107509458964,range=40473978,size=52966537]
Analysis duration: 21588 ms

Thank you in advance for your help!


I hope this helps! Let me know if you have any other questions.

HCaptchaConfig IOException writing serializable object

Hello for some time I'm getting crashes from some of users because of serialization problem. Currently I'm using 3.4.0 version, but it also happened few times on 3.3.6.

Some logs:
Fatal Exception: java.lang.RuntimeException
Parcelable encountered IOException writing serializable object (name = com.hcaptcha.sdk.HCaptchaConfig)

Fatal Exception: java.lang.RuntimeException: Parcelable encountered IOException writing serializable object (name = com.hcaptcha.sdk.HCaptchaConfig)
       at android.os.Parcel.writeSerializable(Parcel.java:1714)
       at android.os.Parcel.writeValue(Parcel.java:1662)
       at android.os.Parcel.writeArrayMapInternal(Parcel.java:875)
       at android.os.BaseBundle.writeToParcelInner(BaseBundle.java:1579)
       at android.os.Bundle.writeToParcel(Bundle.java:1233)
       at android.os.Parcel.writeBundle(Parcel.java:915)
       at androidx.fragment.app.FragmentState.writeToParcel(FragmentState.java:159)
       at android.os.Parcel.writeParcelable(Parcel.java:1683)
       at android.os.Parcel.writeValue(Parcel.java:1589)
       at android.os.Parcel.writeArrayMapInternal(Parcel.java:875)
       at android.os.BaseBundle.writeToParcelInner(BaseBundle.java:1579)
       at android.os.Bundle.writeToParcel(Bundle.java:1233)
       at android.os.Parcel.writeBundle(Parcel.java:915)
       at android.os.Parcel.writeValue(Parcel.java:1580)
       at android.os.Parcel.writeArrayMapInternal(Parcel.java:875)
       at android.os.BaseBundle.writeToParcelInner(BaseBundle.java:1579)
       at android.os.Bundle.writeToParcel(Bundle.java:1233)
       at android.os.Parcel.writeBundle(Parcel.java:915)
       at android.os.Parcel.writeValue(Parcel.java:1580)
       at android.os.Parcel.writeArrayMapInternal(Parcel.java:875)
       at android.os.BaseBundle.writeToParcelInner(BaseBundle.java:1579)
       at android.os.Bundle.writeToParcel(Bundle.java:1233)
       at android.os.Parcel.writeBundle(Parcel.java:915)
       at android.os.Parcel.writeValue(Parcel.java:1580)
       at android.os.Parcel.writeArrayMapInternal(Parcel.java:875)
       at android.os.BaseBundle.writeToParcelInner(BaseBundle.java:1579)
       at android.os.Bundle.writeToParcel(Bundle.java:1233)
       at android.app.IActivityManager$Stub$Proxy.activityStopped(IActivityManager.java:3895)
       at android.app.servertransaction.PendingTransactionActions$StopInfo.run(PendingTransactionActions.java:144)
       at android.os.Handler.handleCallback(Handler.java:873)
       at android.os.Handler.dispatchMessage(Handler.java:99)
       at android.os.Looper.loop(Looper.java:193)
       at android.app.ActivityThread.main(ActivityThread.java:6912)
       at java.lang.reflect.Method.invoke(Method.java)
       at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:590)
       at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)```

Crashes due to IllegalStateException

Here's the stacktrace

Fatal Exception: java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
       at androidx.fragment.app.FragmentManager.checkStateLoss(FragmentManager.java:1844)
       at androidx.fragment.app.FragmentManager.enqueueAction(FragmentManager.java:1884)
       at androidx.fragment.app.BackStackRecord.commitInternal(BackStackRecord.java:329)
       at androidx.fragment.app.BackStackRecord.commit(BackStackRecord.java:294)
       at androidx.fragment.app.DialogFragment.dismissInternal(DialogFragment.java:355)
       at androidx.fragment.app.DialogFragment.dismiss(DialogFragment.java:307)
       at com.hcaptcha.sdk.HCaptchaDialogFragment.onFailure(SourceFile:42)
       at com.hcaptcha.sdk.HCaptchaJSInterface$2.run(SourceFile:13)
       at android.os.Handler.handleCallback(Handler.java:938)
       at android.os.Handler.dispatchMessage(Handler.java:99)
       at android.os.Looper.loop(Looper.java:246)
       at android.app.ActivityThread.main(ActivityThread.java:8653)
       at java.lang.reflect.Method.invoke(Method.java)
       at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:602)
       at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1130)

Maybe the SDK should consider using dismissAllowingStateLoss instead of dismiss?

Code Linting

What

Extend pipeline to run ./gradlew lintRelease before the Build step.

Why

Obvious reasons.

How

More info in android docs.
It should report and stop the pipeline in case of:

  • unused dependencies
  • code smells?

Consider be less restrictive on Activity class

Problem

SDK requires FragmentActivity instance but it's not required for all cases (for example hidenDialog=true)

Check is we can provide less restrictive API in term of activity type

Release 3.1.0

All blocker patches are merged, so just:

  • bump version

Switch Sonar static analysis scans to CI

  • token secret added to repo
  • Update build.gradle file with the org.sonarqube plugin and its configuration:
plugins {
  id "org.sonarqube" version "3.4.0.2513"
}

sonarqube {
  properties {
    property "sonar.projectKey", "hCaptcha_hcaptcha-android-sdk"
    property "sonar.organization", "hcaptcha"
    property "sonar.host.url", "https://sonarcloud.io"
  }
}
  • update .github/workflows/build.yml

Base configuration to run a SonarCloud analysis on your master branch and Pull Requests below. We already have some GitHub Actions, so want to just add some of these new steps.

name: Build
on:
  push:
    branches:
      - master
  pull_request:
    types: [opened, synchronize, reopened]
jobs:
  build:
    name: Build
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
        with:
          fetch-depth: 0  # Shallow clones should be disabled for a better relevancy of analysis
      - name: Set up JDK 11
        uses: actions/setup-java@v1
        with:
          java-version: 11
      - name: Cache SonarCloud packages
        uses: actions/cache@v1
        with:
          path: ~/.sonar/cache
          key: ${{ runner.os }}-sonar
          restore-keys: ${{ runner.os }}-sonar
      - name: Cache Gradle packages
        uses: actions/cache@v1
        with:
          path: ~/.gradle/caches
          key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }}
          restore-keys: ${{ runner.os }}-gradle
      - name: Build and analyze
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}  # Needed to get PR information, if any
          SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
        run: ./gradlew build sonarqube --info
  • flip switch on sonarcloud config dash.

Support fully invisible flow

What

Add support for fully invisible hCaptcha verification flow.

How v1

  1. Add config option boolean hideDialog.
  2. In case of hideDialog == true:
    • the hCaptcha dialog should be completely invisible
    • the user should not be able to stop the verification process (e.g. by tapping Back button)

How v2

  1. Add config option hostView to specify the android view in which the WebView should be injected into. Thus allowing the client to specify a view he controls. By default the hostView should point to our HCaptchaDialogFragment instance.
  2. The js api internal error challenge-closed and the error thrown when closing the android view where hCaptcha is injected must be easily distinguished. The later one can be renamed to HCaptchaError.HOST_VIEW_CLOSED.
     @Override
     public void onCancel(@NonNull DialogInterface dialogInterface) {
         // User canceled the dialog through either `back` button or an outside touch
         super.onCancel(dialogInterface);
         this.onFailure(new HCaptchaException(HCaptchaError.CHALLENGE_CLOSED)); // shall be HCaptchaError.HOST_VIEW_CLOSED
     }

hCaptcha failed: No internet connection(7)

Hey there,
I implemented the SDK like so:

hCaptcha
                .addOnSuccessListener(new OnSuccessListener<HCaptchaTokenResponse>() {
                    @Override
                    public void onSuccess(HCaptchaTokenResponse response) {
                        // Successul verification. The resulting token must be passed to your backend to be validated.
                        String userResponseToken = response.getTokenResult();
                        Log.d("hCaptcha", "hCaptcha success. Token: " + userResponseToken);
                    }
                })
                .addOnFailureListener(new OnFailureListener() {
                    @Override
                    public void onFailure(HCaptchaException e) {
                        // Error handling here: trigger another verification, display a toast, etc.
                        Log.d("hCaptcha", "hCaptcha failed: " + e.getMessage() + "(" + e.getStatusCode() + ")");
                    }
                })
                .addOnOpenListener(new OnOpenListener() {
                    @Override
                    public void onOpen() {
                        // Usefull for analytics purposes
                        Log.d("hCaptcha", "hCaptcha is now visible.");
                    }
                });

        final HCaptchaConfig hCaptchaConfig = HCaptchaConfig.builder()
                .siteKey(sitekey)
                .size(HCaptchaSize.NORMAL)
                .theme(HCaptchaTheme.DARK)
                .build();
        hCaptcha.setup(hCaptchaConfig).verifyWithHCaptcha();

and while connected to WiFi it throws hCaptcha failed: No internet connection(7). Disabling WiFi and using mobile data opens a hCaptcha popup for a split second only to result in the same error. What is going wrong here?

Add option to hide loadingContainer

Goal: don't interrupt user flow with the spinner if not necessary.

On passive sitekeys this may not be desirable, since it will simply pop up and then disappear.

Workaround to do this manually:

https://github.com/hCaptcha/hcaptcha-android-sdk/blob/main/sdk/src/main/java/com/hcaptcha/sdk/HCaptchaDialogFragment.java

Delete any lines that include loadingContainer to prevent the interstitial from displaying. Line numbers for reference: 58, 99, and 158. May also need to remove LinearLayout from the layout/hcaptch_fragment.xml.

IllegalStateException: Can not perform this action after onSaveInstanceState

androidx.fragment.app.FragmentManager.y
FragmentManager.java, line 6
java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState

androidx.fragment.app.FragmentManager.y FragmentManager.java:6
androidx.fragment.app.c.l BackStackRecord.java:14
androidx.fragment.app.c.d BackStackRecord.java:1
androidx.fragment.app.n.dismissInternal DialogFragment.java:21
androidx.fragment.app.n.dismiss DialogFragment.java:1
com.hcaptcha.sdk.HCaptchaWebViewHelper.getConfig SourceFile:2
com.hcaptcha.sdk.HCaptchaJSInterface$b.run
android.os.Handler.handleCallback Handler.java:938
android.os.Handler.dispatchMessage Handler.java:99
android.os.Looper.loop Looper.java:246
android.app.ActivityThread.main ActivityThread.java:8633
java.lang.reflect.Method.invoke Method.java
com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run RuntimeInit.java:602
com.android.internal.os.ZygoteInit.main ZygoteInit.java:1130

Upgrade dependencies

  • com.fasterxml.jackson.core:jackson-databind:2.12.1 -> 2.13.2
  • androidx.test:* -> latest ones

Performance analysis of invisible path

Given the following (and most performant) scenario:

  • Pre-warm assets when activity in created via HCaptcha.getClient(activity).setup(hCaptchaConfig)
  • Perform verification when the user clicks a button via hCatpcha.verifyWithHCaptcha()

Setup visible captcha with the following configuration:

hideDialog(false)
loading(true)
siteKey(siteKey)
size(HCaptchaSize.NORMAL)

Setup invisible captcha with the following configuration:

hideDialog(true)
loading(false)
siteKey(siteKey)
size(HCaptchaSize.INVISIBLE)

When the app is configured for invisible then initialization performance drops compared to visible captcha, i.e. UI lag/freeze time increases significantly.

Error HCaptchaWebView not displayed

Initialize Hcaptch with different fragmentActivity, webview is abnormal when hcaptch is displayed

HCaptcha.getClient(fragmentActivity)?.setup(hCaptchaConfig())?.verifyWithHCaptcha()

The first call shows success, the second call fails

Error log

HCaptchaWebView not displayed because it is too large to fit into a software layer (or drawing cache), needs 10467840 bytes, only 10368000 available HCaptchaWebView not displayed because it is too large to fit into a software layer (or drawing cache), needs 10467840 bytes, only 10368000 available

I tried removing

HCaptchaWebViewHelper#setupWebView() //webView.setLayerType(View.LAYER_TYPE_SOFTWARE, null)

this problem can be solved

hCaptcha not showing challenge

When I tried to implement it keep buffering and never show the challenge

  • I have provided Internet permission
  • I have attempted all sizes
  • Using com.github.hcaptcha:hcaptcha-android-sdk:1.1.0
  • Using androidx.appcompat:appcompat:1.2.0

The code I'm using is

final String SITE_KEY = "site key here";
final HCaptchaConfig hconfig = HCaptchaConfig.builder()
        .siteKey(SITE_KEY)
        .apiEndpoint("https://js.hcaptcha.com/1/api.js")
        .size(HCaptchaSize.NORMAL)
        .loading(true)
        .build();


HCaptcha.getClient(MainActivity.this).verifyWithHCaptcha(hconfig) 
    .addOnSuccessListener(new OnSuccessListener<HCaptchaTokenResponse>() {
        @Override
        public void onSuccess(HCaptchaTokenResponse response) {
            String userResponseToken = response.getTokenResult();
            showMessage("Success! Token: " + userResponseToken );
        }
    })
    .addOnFailureListener(new OnFailureListener() {
        @Override
        public void onFailure(HCaptchaException e) {
            showMessage("Error: " + e.getMessage() );
        }
    });

Click screen during verify call trigger Challenge Closed Error

If you click screen during the verification call, you may trigger the Challenge Closed Error (before it is even known whether challenge is required) - the call then fails to return any other callbacks.

I would not expect any error to fire in this scenario.

This is with the builder.size(HCaptchaSize.INVISIBLE) (no checkbox) mode

And it seems to be possible to recreate whether or not you use builder.loading(true/false)

Samsung S21 Android 12 device
SDK version 3.3.3

Generate IHCaptchaHtmlProvider implementation at build time

Intro

A nice proposal was made by @DSergiu #61 (comment) . Because maintaining embedded HTML it's really error-prone and not convenient

Proposal

Put back hcaptcha-form.html (but keep it outside of assets) and generate IHCaptchaHtmlProvider at build time

TODO Check existing code-gen plugins for gradle to keep Androd Studio happy (and not report missing classes)

Implement clear API

  • trigger captcha reset via hcaptcha.reset() (check resetAndExecute)
  • remove webview
  • free any other resource
  • tests

Clear listeners

Hi! Not really the biggest issue, but, i'd like to be able to clear all listeners registered to the hCaptcha client whenever they are not needed anymore to optimize. Else they just lay around doing nothing when they might not be needed. There does not seem to be a way to this right now?

Immediate retry on challenge closed

We're attempting to auto trigger verification on challenge closed error. It seems this is causing a bit of a timing problem and the SDK thinks the dialog is still showing because I get a console log message:
"DialogFragment was already added"
But no actual onFailure callback, leaving us in an unhandled state.

I currently have a workaround to pause a bit before triggering the auto-retry verification, which seems to work.

regards

Samsung S21 Android 12 device
SDK version 3.3.3

Release 1.3.0

  • Fix connectedAndroidTest UI Tests, add to CI
  • Introduce CHANGES.md
  • Bump version
  • customTheme
  • ES5 linting

Suggestion: allow to pass siteKey

Intro

On ios-sdk we have an option to provide siteKey by an entry in Info.plist (more info in README https://github.com/hCaptcha/hcaptcha-ios-sdk#usage)

Proposed solution

In the Android world a similar technique is meta-data nodes from AndroidManifest.xml and many SDK use such approach, for example, Google Maps SDK https://developers.google.com/maps/documentation/android-sdk/config#step_3_add_your_api_key_to_the_project

Open question

  • This feature has already been implemented as part of #25, should we move it to separate PR

cc @e271828- @DSergiu

Add option to automatically reload on timeout, matching iOS behavior

Currently, we have a difference of behavior on the iOS vs Android SDKs.

On Android, after two minutes, the hCaptcha page times out and disappears. We can manually trigger the hCaptcha page again afterwards.

On iOS, after two minutes, the hCaptcha page times out and reloads itself. We don’t need to re-trigger the hCaptcha page to appear again.

Suggested fix: add a config option on Android that mimics the iOS behavior, in order to maintain backwards compat.

GitHub Actions Workflow doesn't work correctly for PR from forks

Problem

https://github.com/hCaptcha/hcaptcha-android-sdk/actions/runs/4703861808/jobs/8394211306

GitHub Actions Workflow doesn't work correctly for PR from forks, it fails on:

Sonar Job:

FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':sdk:sonarqube'.
> Project not found. Please check the 'sonar.projectKey' and 'sonar.organization' properties, the 'SONAR_TOKEN' environment variable, or contact the project administrator

Diffuse Job:

Error: Resource not accessible by integration
Error: See this action's readme for details about this error

Proposed solution

Check those links to find/implement a solution:

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.