/* * SPDX-FileCopyrightText: 2020, microG Project Team * SPDX-License-Identifier: Apache-2.0 */ package org.microg.gms.firebase.auth import android.annotation.SuppressLint import android.app.Activity import android.content.Context import android.content.Intent import android.content.Intent.* import android.os.Bundle import android.os.ResultReceiver import android.util.Log import android.webkit.JavascriptInterface import android.webkit.WebSettings import android.webkit.WebView import androidx.appcompat.app.AppCompatActivity import org.microg.gms.firebase.auth.core.R import org.microg.gms.profile.Build import org.microg.gms.profile.ProfileManager import kotlin.coroutines.resume import kotlin.coroutines.resumeWithException import kotlin.coroutines.suspendCoroutine private const val TAG = "GmsFirebaseAuthCaptcha" class ReCaptchaActivity : AppCompatActivity() { private val receiver: ResultReceiver? get() = intent.getParcelableExtra(EXTRA_RESULT_RECEIVER) private val hostname: String get() = intent.getStringExtra(EXTRA_HOSTNAME) ?: "localhost:5000" private var finished = false @SuppressLint("SetJavaScriptEnabled", "AddJavascriptInterface") override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) openWebsite() } private fun openWebsite() { val apiKey = intent.getStringExtra(EXTRA_API_KEY) ?: return finishResult(Activity.RESULT_CANCELED) setContentView(R.layout.activity_recaptcha) val view = findViewById(R.id.web) val settings = view.settings settings.javaScriptEnabled = true settings.useWideViewPort = false settings.setSupportZoom(false) settings.displayZoomControls = false settings.cacheMode = WebSettings.LOAD_NO_CACHE ProfileManager.ensureInitialized(this) settings.userAgentString = Build.generateWebViewUserAgentString(settings.userAgentString) view.addJavascriptInterface(object : Any() { @JavascriptInterface fun onReCaptchaToken(token: String) { Log.d(TAG, "onReCaptchaToken: $token") finishResult(Activity.RESULT_OK, token) } }, "MyCallback") val captcha = assets.open("recaptcha.html").bufferedReader().readText().replace("%apikey%", apiKey) view.loadDataWithBaseURL("https://$hostname/", captcha, null, null, "https://$hostname/") } fun finishResult(resultCode: Int, token: String? = null) { finished = true setResult(resultCode, token?.let { Intent().apply { putExtra(EXTRA_TOKEN, it) } }) receiver?.send(resultCode, token?.let { Bundle().apply { putString(EXTRA_TOKEN, it) } }) finish() } override fun onDestroy() { super.onDestroy() if (!finished) receiver?.send(Activity.RESULT_CANCELED, null) } companion object { const val EXTRA_TOKEN = "token" const val EXTRA_API_KEY = "api_key" const val EXTRA_HOSTNAME = "hostname" const val EXTRA_RESULT_RECEIVER = "receiver" fun isSupported(context: Context): Boolean = true suspend fun awaitToken(context: Context, apiKey: String, hostname: String? = null) = suspendCoroutine { continuation -> val intent = Intent(context, ReCaptchaActivity::class.java) val resultReceiver = object : ResultReceiver(null) { override fun onReceiveResult(resultCode: Int, resultData: Bundle?) { try { if (resultCode == Activity.RESULT_OK) { continuation.resume(resultData?.getString(EXTRA_TOKEN)!!) } } catch (e: Exception) { continuation.resumeWithException(e) } } } intent.putExtra(EXTRA_API_KEY, apiKey) intent.putExtra(EXTRA_RESULT_RECEIVER, resultReceiver) intent.putExtra(EXTRA_HOSTNAME, hostname) intent.addFlags(FLAG_ACTIVITY_NEW_TASK) intent.addFlags(FLAG_ACTIVITY_REORDER_TO_FRONT) intent.addFlags(FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) context.startActivity(intent) } } }