diff --git a/README.md b/README.md index 6032e22..e21e592 100644 --- a/README.md +++ b/README.md @@ -60,7 +60,8 @@ from [Frontegg Portal Domain](https://portal.frontegg.com/development/settings/d - Navigate to [Login Method Settings](https://portal.frontegg.com/development/authentication/hosted) - Toggle Hosted login method -- Add `{{ANDROID_PACKAGE_NAME}}://{{FRONTEGG_BASE_URL}}/ios/oauth/callback` +- Add `{{ANDROID_PACKAGE_NAME}}://{{FRONTEGG_BASE_URL}}/android/oauth/callback` **(without assetlinks)** +- Add `https://{{FRONTEGG_BASE_URL}}/{{ANDROID_PACKAGE_NAME}}/android/oauth/callback` **(required for assetlinks)** - Replace `ANDROID_PACKAGE_NAME` with your application identifier - Replace `FRONTEGG_BASE_URL` with your Frontegg base url @@ -319,9 +320,11 @@ Follow [Config Android AssetLinks](#config-android-assetlinks) to add your Andro The first domain will be placed automatically in the `AndroidManifest.xml` file. For each additional region, you will need to add an `intent-filter`. +Replace `${FRONTEGG_DOMAIN_2}` with the second domain from the previous step. NOTE: if you are using `Custom Chrome Tab` you have to use `android:name` `com.frontegg.android.HostedAuthActivity` instead of `com.frontegg.android.EmbeddedAuthActivity` + ```xml @@ -334,13 +337,15 @@ NOTE: if you are using `Custom Chrome Tab` you have to use `android:name` `com.f - - - - - - - + + + + + @@ -352,8 +357,11 @@ NOTE: if you are using `Custom Chrome Tab` you have to use `android:name` `com.f - - + + + diff --git a/android/build.gradle b/android/build.gradle index 4066b22..bcde5ee 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -7,7 +7,7 @@ plugins { group 'com.frontegg.android' -version '1.2.2' +version '1.2.3' android { diff --git a/android/src/main/AndroidManifest.xml b/android/src/main/AndroidManifest.xml index 9058bbe..f47a762 100644 --- a/android/src/main/AndroidManifest.xml +++ b/android/src/main/AndroidManifest.xml @@ -1,5 +1,6 @@ - + @@ -16,12 +17,22 @@ - - - - - - + + + + + + @@ -65,13 +76,17 @@ + + - diff --git a/android/src/main/java/com/frontegg/android/AuthenticationActivity.kt b/android/src/main/java/com/frontegg/android/AuthenticationActivity.kt index 35a21de..7049522 100644 --- a/android/src/main/java/com/frontegg/android/AuthenticationActivity.kt +++ b/android/src/main/java/com/frontegg/android/AuthenticationActivity.kt @@ -59,7 +59,7 @@ class AuthenticationActivity : Activity() { if (code != null) { Log.d(TAG, "Got intent with oauth callback") FronteggAuth.instance.isLoading.value = true - FronteggAuth.instance.handleHostedLoginCallback(code) + FronteggAuth.instance.handleHostedLoginCallback(code, null, this) setResult(RESULT_OK) finish() return diff --git a/android/src/main/java/com/frontegg/android/FronteggApp.kt b/android/src/main/java/com/frontegg/android/FronteggApp.kt index 47ffe01..0bc34fc 100644 --- a/android/src/main/java/com/frontegg/android/FronteggApp.kt +++ b/android/src/main/java/com/frontegg/android/FronteggApp.kt @@ -7,10 +7,8 @@ import android.content.pm.PackageManager.MATCH_ALL import android.util.Log import com.frontegg.android.exceptions.FronteggException import com.frontegg.android.exceptions.FronteggException.Companion.FRONTEGG_APP_MUST_BE_INITIALIZED -import com.frontegg.android.exceptions.FronteggException.Companion.FRONTEGG_DOMAIN_MUST_NOT_START_WITH_HTTPS import com.frontegg.android.regions.RegionConfig import com.frontegg.android.services.* -import java.lang.RuntimeException class FronteggApp private constructor( val context: Context, @@ -19,8 +17,9 @@ class FronteggApp private constructor( val isEmbeddedMode: Boolean = true, val regions: List = listOf(), val selectedRegion: RegionConfig? = null, - val handleLoginWithSocialLogin: Boolean = true, - val handleLoginWithSSO: Boolean = false + var handleLoginWithSocialLogin: Boolean = true, + var handleLoginWithSSO: Boolean = false, + val useAssetsLinks: Boolean = false, ) { val credentialManager: CredentialManager = CredentialManager(context) @@ -45,19 +44,30 @@ class FronteggApp private constructor( public fun init( fronteggDomain: String, clientId: String, - context: Context + context: Context, + useAssetsLinks: Boolean = false ) { val baseUrl: String = if (fronteggDomain.startsWith("https")) { - throw FronteggException(FRONTEGG_DOMAIN_MUST_NOT_START_WITH_HTTPS) + fronteggDomain } else { "https://$fronteggDomain" } val isEmbeddedMode = isActivityEnabled(context, EmbeddedAuthActivity::class.java.name) - instance = FronteggApp(context, baseUrl, clientId, isEmbeddedMode) + instance = FronteggApp( + context, + baseUrl, + clientId, + isEmbeddedMode, + useAssetsLinks = useAssetsLinks + ) } - public fun initWithRegions(regions: List, context: Context): FronteggApp { + public fun initWithRegions( + regions: List, + context: Context, + useAssetsLinks: Boolean = false + ): FronteggApp { val isEmbeddedMode = isActivityEnabled(context, EmbeddedAuthActivity::class.java.name) val selectedRegion = CredentialManager(context).getSelectedRegion() @@ -71,7 +81,8 @@ class FronteggApp private constructor( regionConfig.clientId, isEmbeddedMode, regions, - regionConfig + regionConfig, + useAssetsLinks = useAssetsLinks ) instance = newInstance return newInstance diff --git a/android/src/main/java/com/frontegg/android/FronteggAuth.kt b/android/src/main/java/com/frontegg/android/FronteggAuth.kt index 3f4bf40..806e527 100644 --- a/android/src/main/java/com/frontegg/android/FronteggAuth.kt +++ b/android/src/main/java/com/frontegg/android/FronteggAuth.kt @@ -173,7 +173,7 @@ class FronteggAuth( this.initializing.value = false } - fun handleHostedLoginCallback(code: String, webView: WebView? = null): Boolean { + fun handleHostedLoginCallback(code: String, webView: WebView? = null, activity: Activity? = null): Boolean { val codeVerifier = credentialManager.getCodeVerifier() val redirectUrl = Constants.oauthCallbackUrl(baseUrl) @@ -194,6 +194,8 @@ class FronteggAuth( Handler(Looper.getMainLooper()).post { webView.loadUrl(url.first) } + }else if (activity != null){ + login(activity) } } diff --git a/android/src/main/java/com/frontegg/android/embedded/FronteggWebClient.kt b/android/src/main/java/com/frontegg/android/embedded/FronteggWebClient.kt index 1203db8..466f413 100644 --- a/android/src/main/java/com/frontegg/android/embedded/FronteggWebClient.kt +++ b/android/src/main/java/com/frontegg/android/embedded/FronteggWebClient.kt @@ -7,8 +7,14 @@ import android.net.Uri import android.net.UrlQuerySanitizer import android.os.Handler import android.os.Looper +import android.text.Html +import android.util.Base64 import android.util.Log -import android.webkit.* +import android.webkit.WebResourceError +import android.webkit.WebResourceRequest +import android.webkit.WebResourceResponse +import android.webkit.WebView +import android.webkit.WebViewClient import com.frontegg.android.FronteggApp import com.frontegg.android.FronteggAuth import com.frontegg.android.utils.AuthorizeUrlGenerator @@ -16,7 +22,7 @@ import com.frontegg.android.utils.Constants import com.frontegg.android.utils.Constants.Companion.loginRoutes import com.frontegg.android.utils.Constants.Companion.socialLoginRedirectUrl import com.frontegg.android.utils.Constants.Companion.successLoginRoutes -import com.google.gson.Gson +import com.frontegg.android.utils.generateErrorPage import com.google.gson.JsonParser import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.Dispatchers @@ -54,10 +60,13 @@ class FronteggWebClient(val context: Context) : WebViewClient() { FronteggAuth.instance.isLoading.value = true } - + if (url?.startsWith("data:text/html,") == true) { + FronteggAuth.instance.isLoading.value = false + return + } val fronteggApp = FronteggApp.getInstance() - val nativeModuleFunctions = JSONObject() + val nativeModuleFunctions = JSONObject() nativeModuleFunctions.put("loginWithSocialLogin", fronteggApp.handleLoginWithSocialLogin) nativeModuleFunctions.put("loginWithSSO", fronteggApp.handleLoginWithSSO) val jsObject = nativeModuleFunctions.toString() @@ -70,9 +79,60 @@ class FronteggWebClient(val context: Context) : WebViewClient() { request: WebResourceRequest?, error: WebResourceError? ) { + + try { + val errorMessage = "Check your internet connection and try again." + val htmlError = Html.escapeHtml(errorMessage) + val errorPage = generateErrorPage( + htmlError, + error = error?.description?.toString(), + url = request?.url?.toString() + ) + val encodedHtml = Base64.encodeToString(errorPage.toByteArray(), Base64.NO_PADDING) + Handler(Looper.getMainLooper()).post { + view?.loadData(encodedHtml, "text/html", "base64") + } + } catch (e: Exception) { + // ignore error + } + Log.e(TAG, "onReceivedError: ${error?.description}") super.onReceivedError(view, request, error) } + private fun checkIfFronteggError(view: WebView?, url: String?, status: Int? = null) { + if (view == null || url == null) { + return + } + view.evaluateJavascript("document.body.innerText") { result -> + try { + var text = result + if (text == null) { + return@evaluateJavascript + } + var json = JsonParser.parseString(text) + while (!json.isJsonObject) { + text = json.asString + json = JsonParser.parseString(text) + } + val error = json.asJsonObject.get("errors").asJsonArray.map { + it.asString + }.joinToString("\n") + + Log.e(TAG, "Frontegg ERROR: $error") + + val htmlError = Html.escapeHtml(error) + val errorPage = generateErrorPage(htmlError, status = status) + val encodedHtml = Base64.encodeToString(errorPage.toByteArray(), Base64.NO_PADDING) + Handler(Looper.getMainLooper()).post { + view.loadData(encodedHtml, "text/html", "base64") + } + + } catch (e: Exception) { + // ignore error + } + } + } + override fun onReceivedHttpError( view: WebView?, request: WebResourceRequest?, @@ -83,6 +143,7 @@ class FronteggWebClient(val context: Context) : WebViewClient() { } else { Log.d(TAG, "onReceivedHttpError: HTTP api call, ${request?.url?.path}") } + checkIfFronteggError(view, request?.url.toString(), errorResponse?.statusCode) super.onReceivedHttpError(view, request, errorResponse) } @@ -217,7 +278,10 @@ class FronteggWebClient(val context: Context) : WebViewClient() { } @OptIn(DelicateCoroutinesApi::class) - private fun setSocialLoginRedirectUri(@Suppress("UNUSED_PARAMETER") webView: WebView, uri: Uri): Boolean { + private fun setSocialLoginRedirectUri( + @Suppress("UNUSED_PARAMETER") webView: WebView, + uri: Uri + ): Boolean { Log.d(TAG, "setSocialLoginRedirectUri setting redirect uri for social login") if (uri.getQueryParameter("redirectUri") != null) { diff --git a/android/src/main/java/com/frontegg/android/utils/Constants.kt b/android/src/main/java/com/frontegg/android/utils/Constants.kt index aed0416..15aeddf 100644 --- a/android/src/main/java/com/frontegg/android/utils/Constants.kt +++ b/android/src/main/java/com/frontegg/android/utils/Constants.kt @@ -18,7 +18,6 @@ class ApiConstants { class Constants { companion object { - val successLoginRoutes = listOf( "/oauth/account/social/success", ) @@ -29,9 +28,14 @@ class Constants { fun oauthCallbackUrl(baseUrl: String): String { val host = baseUrl.substring("https://".length) - val packageName = FronteggApp.getInstance().packageName - - return "${packageName}://${host}/android/oauth/callback" + val app = FronteggApp.getInstance(); + val packageName = app.packageName + val useAssetsLinks = app.useAssetsLinks + return if (useAssetsLinks) { + "https://${host}/${packageName}/android/oauth/callback" + } else { + "${packageName}://${host}/android/oauth/callback" + } } fun socialLoginRedirectUrl(baseUrl: String): String { diff --git a/android/src/main/java/com/frontegg/android/utils/ErrorPages.kt b/android/src/main/java/com/frontegg/android/utils/ErrorPages.kt new file mode 100644 index 0000000..07239ba --- /dev/null +++ b/android/src/main/java/com/frontegg/android/utils/ErrorPages.kt @@ -0,0 +1,53 @@ +package com.frontegg.android.utils + + +fun generateErrorPage( + message: String, + status: Int? = null, + error: String? = null, + url: String? = null +): String { + return """ + + + Fatal Error + + + + +
$message
+ ${if (status != null) "
Status Code: $status
" else ""} + ${if (error != null) "
$error
" else ""} + ${ + if (url != null) "" else "" + } + + + """ +} diff --git a/embedded/src/main/AndroidManifest.xml b/embedded/src/main/AndroidManifest.xml index 0d16b33..a2b133f 100644 --- a/embedded/src/main/AndroidManifest.xml +++ b/embedded/src/main/AndroidManifest.xml @@ -24,6 +24,8 @@ + +
\ No newline at end of file diff --git a/embedded/src/main/java/com/frontegg/demo/App.kt b/embedded/src/main/java/com/frontegg/demo/App.kt index bc312ba..ae93685 100644 --- a/embedded/src/main/java/com/frontegg/demo/App.kt +++ b/embedded/src/main/java/com/frontegg/demo/App.kt @@ -15,7 +15,8 @@ class App : Application() { FronteggApp.init( BuildConfig.FRONTEGG_DOMAIN, BuildConfig.FRONTEGG_CLIENT_ID, - this + this, + useAssetsLinks = true, ) } } \ No newline at end of file diff --git a/multi-region/src/main/AndroidManifest.xml b/multi-region/src/main/AndroidManifest.xml index 1e15bda..12ac938 100644 --- a/multi-region/src/main/AndroidManifest.xml +++ b/multi-region/src/main/AndroidManifest.xml @@ -15,8 +15,8 @@ tools:targetApi="31"> + android:exported="false" + android:label="Select Region" /> - - - - - - + + + + + + +