diff --git a/.github/actions/setup/action.yml b/.github/actions/setup/action.yml
index 84b6094..a21846e 100644
--- a/.github/actions/setup/action.yml
+++ b/.github/actions/setup/action.yml
@@ -20,19 +20,19 @@ runs:
env:
GITHUB_TOKEN: ${{ inputs.github_token }}
- - name: Setup JDK 11
+ - name: Setup JDK 17
uses: actions/setup-java@v3
if: ${{ inputs.gpg_signing_key == '' }}
with:
- java-version: 11
+ java-version: 17
settings-path: ${{ github.workspace }} # location for the settings.xml file
distribution: temurin
- - name: Setup JDK 11 with Credentials
+ - name: Setup JDK 17 with Credentials
if: ${{ inputs.gpg_signing_key != '' }}
uses: actions/setup-java@v3
with:
- java-version: 11
+ java-version: 17
settings-path: ${{ github.workspace }} # location for the settings.xml file
server-id: ossrh
server-username: NEXUS_USERNAME
diff --git a/.github/old/build.yml b/.github/old/build.yml
deleted file mode 100644
index 3c6cc1b..0000000
--- a/.github/old/build.yml
+++ /dev/null
@@ -1,99 +0,0 @@
-name: Frontegg Android SDK
-
-on:
- push:
- branches-ignore:
- - 'master'
- - 'release/next'
-env:
- CI: true
- LANG: en_US.UTF-8
- API_LEVEL: 29
-
-concurrency:
- group: ci-push-${{ github.ref }}
- cancel-in-progress: true
-
-jobs:
- build-and-test:
- name: Build And Test
- runs-on: macos-latest-xl
- steps:
- - name: Checkout
- uses: actions/checkout@v3
-# - name: Clone Mock Server
-# uses: actions/checkout@v3
-# with:
-# repository: frontegg/frontegg-mock-server
-# ssh-key: ${{ secrets.MOCK_SERVER_SSH_KEY }}
-# ref: "master"
-# path: mocker
-# - name: Install Mock Server
-# working-directory: mocker
-# run: yarn install
-# - name: Run Mock Server
-# working-directory: mocker
-# env:
-# ANDROID_ASSOCIATED_DOMAIN_GRADLE_PATH: "${{ github.workspace }}/app/build.gradle"
-# SERVER_HOSTNAME: "10.0.2.2"
-# NGROCK_AUTH_TOKEN: "${{ secrets.NGROCK_AUTH_TOKEN }}"
-# NGROCK_SUBDOMAIN: "frontegg-test"
-# run: |
-# echo "ANDROID_ASSOCIATED_DOMAIN_GRADLE_PATH: $ANDROID_ASSOCIATED_DOMAIN_GRADLE_PATH"
-# echo "SERVER_HOSTNAME: $SERVER_HOSTNAME"
-# echo "NGROCK_SUBDOMAIN: $NGROCK_SUBDOMAIN"
-# (yarn start:mobile-mock&)
-# sleep 40
-
- - name: Set git config
- run: |
- git config --global user.name 'github-actions'
- git config --global user.email 'github-actions@github.com'
-
- - name: Setup JDK 11
- uses: actions/setup-java@v3
- with:
- java-version: 11
- settings-path: ${{ github.workspace }} # location for the settings.xml file
- distribution: temurin
-
- - name: Gradle cache
- uses: gradle/gradle-build-action@v2
-
- - name: Build Libraries
- run: ./gradlew :app:build --no-daemon
-# - name: AVD cache
-# uses: actions/cache@v3
-# id: avd-cache
-# with:
-# path: |
-# ~/.android/avd/*
-# ~/.android/adb*
-# key: avd-${{env.API_LEVEL}}
-#
-# - name: Create AVD and generate snapshot for caching
-# if: steps.avd-cache.outputs.cache-hit != 'true'
-# uses: reactivecircus/android-emulator-runner@v2
-# with:
-# avd-name: "AndroidEmulator"
-# api-level: ${{env.API_LEVEL}}
-# force-avd-creation: false
-# emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
-# disable-animations: true
-# script: echo "Generated AVD snapshot for caching."
-
-# - name: Run tests
-# uses: reactivecircus/android-emulator-runner@v2
-# with:
-# api-level: ${{env.API_LEVEL}}
-# force-avd-creation: false
-# emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
-# disable-animations: true
-# script: ./run-tests.sh
-#
-# - name: Upload test results
-# if: always()
-# uses: actions/upload-artifact@v2
-# with:
-# name: test-results
-# path: app/build/reports/androidTests/connected/
diff --git a/.github/test-jks/debug.keystore b/.github/test-jks/debug.keystore
new file mode 100644
index 0000000..f7db5ba
Binary files /dev/null and b/.github/test-jks/debug.keystore differ
diff --git a/.github/test-jks/release.keystore b/.github/test-jks/release.keystore
new file mode 100644
index 0000000..eaa1ec2
Binary files /dev/null and b/.github/test-jks/release.keystore differ
diff --git a/.github/workflows/onPublishAlpha.yml b/.github/workflows/onPublishAlpha.yml
index 7da99b9..728b4d5 100644
--- a/.github/workflows/onPublishAlpha.yml
+++ b/.github/workflows/onPublishAlpha.yml
@@ -5,7 +5,7 @@ on:
env:
CI: true
LANG: en_US.UTF-8
- API_LEVEL: 29
+ API_LEVEL: 34
concurrency:
group: ci-publish-alpha-${{ github.ref }}
@@ -17,7 +17,7 @@ jobs:
runs-on: macos-latest-xl
steps:
- name: Checkout
- uses: actions/checkout@v3
+ uses: actions/checkout@v4.2.0
with:
fetch-depth: "0"
diff --git a/.github/workflows/onPullRequestMerged.yaml b/.github/workflows/onPullRequestMerged.yaml
index dad9e89..b1d5d17 100644
--- a/.github/workflows/onPullRequestMerged.yaml
+++ b/.github/workflows/onPullRequestMerged.yaml
@@ -7,7 +7,7 @@ on:
env:
CI: true
LANG: en_US.UTF-8
- API_LEVEL: 29
+ API_LEVEL: 34
jobs:
createReleasePullRequest:
@@ -16,7 +16,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
- uses: actions/checkout@v3
+ uses: actions/checkout@v4.2.0
- name: Setup
uses: ./.github/actions/setup
diff --git a/.github/workflows/onPush.yml b/.github/workflows/onPush.yml
index 0a3d9ff..1cab2ec 100644
--- a/.github/workflows/onPush.yml
+++ b/.github/workflows/onPush.yml
@@ -8,7 +8,7 @@ on:
env:
CI: true
LANG: en_US.UTF-8
- API_LEVEL: 29
+ API_LEVEL: 34
concurrency:
group: ci-push-${{ github.ref }}
@@ -20,7 +20,7 @@ jobs:
runs-on: macos-latest-xl
steps:
- name: Checkout
- uses: actions/checkout@v3
+ uses: actions/checkout@v4.2.0
- name: Setup
uses: ./.github/actions/setup
@@ -30,19 +30,3 @@ jobs:
- name: Build Libraries
shell: bash
run: ./gradlew :app:build --no-daemon
-
- - name: Set Alpha Version
- id: incremented-alpha-version
- uses: ./.github/actions/update-gradle-version
- with:
- type: alpha
-
- - name: prepare release
- env:
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- NEXUS_USERNAME: ${{ secrets.NEXUS_USERNAME }}
- NEXUS_PASSWORD: ${{ secrets.NEXUS_PASSWORD }}
- GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
- GPG_PRIVATE_KEY: ${{ secrets.GPG_SIGNING_KEY }}
- run: ./gradlew publish --no-daemon --no-parallel
-
diff --git a/.github/workflows/onReleaseMerged.yaml b/.github/workflows/onReleaseMerged.yaml
index 2fe2cc0..64ea941 100644
--- a/.github/workflows/onReleaseMerged.yaml
+++ b/.github/workflows/onReleaseMerged.yaml
@@ -16,7 +16,7 @@ jobs:
runs-on: macos-12
steps:
- name: Checkout
- uses: actions/checkout@v3
+ uses: actions/checkout@v4.2.0
- name: Setup
uses: ./.github/actions/setup
@@ -93,7 +93,7 @@ jobs:
- - uses: actions/github-script@0.8.0
+ - uses: actions/github-script@v7
with:
github-token: ${{secrets.GITHUB_TOKEN}}
script: |
diff --git a/.github/workflows/onTestWorkflow.yml b/.github/workflows/onTestWorkflow.yml
new file mode 100644
index 0000000..b306caa
--- /dev/null
+++ b/.github/workflows/onTestWorkflow.yml
@@ -0,0 +1,75 @@
+name: "(▶) E2E Test"
+on:
+ push:
+
+env:
+ CI: true
+ LANG: en_US.UTF-8
+ API_LEVEL: 34
+
+concurrency:
+ group: ci-e2e-test-${{ github.ref }}
+ cancel-in-progress: true
+
+jobs:
+ uploadApk:
+ name: 'Upload apk'
+ runs-on: macos-latest-xl
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4.2.0
+ with:
+ fetch-depth: "0"
+
+ - name: Setup
+ uses: ./.github/actions/setup
+ with:
+ github_token: ${{ secrets.GITHUB_TOKEN }}
+
+
+ - name: Config root certificate for testing
+ uses: actions/github-script@v7
+ with:
+ script: |
+ const fs = require('fs');
+ const path = require('path');
+ const securityConfig = `\n \n \n \n \n \n \n \n \n`
+ fs.mkdirSync(path.join(process.env.GITHUB_WORKSPACE, 'embedded/src/main/res/xml'), { recursive: true });
+ fs.writeFileSync(path.join(process.env.GITHUB_WORKSPACE, 'embedded/src/main/res/xml/network_security_config.xml'), securityConfig, 'utf8');
+
+ let manifest = fs.readFileSync(path.join(process.env.GITHUB_WORKSPACE, 'embedded/src/main/AndroidManifest.xml'), 'utf8');
+ manifest = manifest.replace(/ 0))')
+
+ echo "artifact urls: $apk_urls"
\ No newline at end of file
diff --git a/README.md b/README.md
index b97f395..70dbe12 100644
--- a/README.md
+++ b/README.md
@@ -486,13 +486,13 @@ class App : Application() {
listOf(
RegionConfig(
"eu",
- "auth.davidantoon.me",
+ "autheu.davidantoon.me",
"b6adfe4c-d695-4c04-b95f-3ec9fd0c6cca"
),
RegionConfig(
"us",
- "davidprod.frontegg.com",
- "d7d07347-2c57-4450-8418-0ec7ee6e096b"
+ "authus.frontegg.com",
+ "6903cab0-9809-4a2e-97dd-b8c0f966c813"
)
),
this
diff --git a/android/build.gradle b/android/build.gradle
index 714832c..3f8c896 100644
--- a/android/build.gradle
+++ b/android/build.gradle
@@ -35,7 +35,7 @@ android {
kotlinOptions {
jvmTarget = '1.8'
}
- buildToolsVersion '30.0.3'
+ buildToolsVersion '34.0.0'
publishing {
singleVariant("release") {
@@ -51,16 +51,20 @@ dependencies {
implementation 'io.reactivex.rxjava3:rxkotlin:3.0.1'
implementation 'com.squareup.okhttp3:okhttp:4.12.0'
implementation 'com.google.code.gson:gson:2.10.1'
- implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3"
- implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3"
- implementation "androidx.browser:browser:1.8.0"
- implementation("androidx.security:security-crypto:1.1.0-alpha06") {
+ implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3'
+ implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3'
+ implementation 'androidx.browser:browser:1.8.0'
+ implementation ('androidx.security:security-crypto:1.1.0-alpha06') {
exclude group: 'com.google.crypto.tink', module: 'tink-android'
}
- implementation "com.google.crypto.tink:tink-android:1.9.0"
+ implementation 'com.google.crypto.tink:tink-android:1.9.0'
implementation 'com.google.androidbrowserhelper:androidbrowserhelper:2.5.0'
- implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"
- implementation 'androidx.lifecycle:lifecycle-process:2.6.2'
+ implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
+ implementation 'androidx.webkit:webkit:1.12.1'
+ implementation 'androidx.lifecycle:lifecycle-process:2.8.6'
+ // optional - needed for credentials support from play services, for devices running Android 13 and below.
+ implementation "androidx.credentials:credentials-play-services-auth:1.3.0"
+ implementation 'androidx.credentials:credentials:1.3.0'
}
afterEvaluate {
diff --git a/android/consumer-rules.pro b/android/consumer-rules.pro
index 4689556..d4f8671 100644
--- a/android/consumer-rules.pro
+++ b/android/consumer-rules.pro
@@ -20,4 +20,12 @@
-keep class com.frontegg.android.models.** { *; }
# Retain Tink classes used for shared preferences encryption
--keep class com.google.crypto.tink.** { *; }
\ No newline at end of file
+-keep class com.google.crypto.tink.** { *; }
+
+-if class androidx.credentials.CredentialManager
+-keep class androidx.credentials.playservices.** {
+ *;
+}
+
+-keep public class android.net.http.SslError
+-keep public class android.webkit.WebViewClient
diff --git a/android/src/main/AndroidManifest.xml b/android/src/main/AndroidManifest.xml
index 1dd27e5..93df43f 100644
--- a/android/src/main/AndroidManifest.xml
+++ b/android/src/main/AndroidManifest.xml
@@ -4,6 +4,8 @@
+
+
diff --git a/android/src/main/java/com/frontegg/android/EmbeddedAuthActivity.kt b/android/src/main/java/com/frontegg/android/EmbeddedAuthActivity.kt
index 4dbebfc..d293b24 100644
--- a/android/src/main/java/com/frontegg/android/EmbeddedAuthActivity.kt
+++ b/android/src/main/java/com/frontegg/android/EmbeddedAuthActivity.kt
@@ -186,12 +186,17 @@ class EmbeddedAuthActivity : Activity() {
var onAuthFinishedCallback: (() -> Unit)? = null // Store callback
- fun authenticate(activity: Activity, loginHint: String? = null) {
+ fun authenticate(
+ activity: Activity,
+ loginHint: String? = null,
+ callback: (() -> Unit)? = null
+ ) {
val intent = Intent(activity, EmbeddedAuthActivity::class.java)
- val authorizeUri = AuthorizeUrlGenerator().generate(loginHint=loginHint)
+ val authorizeUri = AuthorizeUrlGenerator().generate(loginHint = loginHint)
intent.putExtra(AUTH_LAUNCHED, true)
intent.putExtra(AUTHORIZE_URI, authorizeUri.first)
+ onAuthFinishedCallback = callback
activity.startActivityForResult(intent, OAUTH_LOGIN_REQUEST)
}
diff --git a/android/src/main/java/com/frontegg/android/FronteggAuth.kt b/android/src/main/java/com/frontegg/android/FronteggAuth.kt
index db5004d..839e693 100644
--- a/android/src/main/java/com/frontegg/android/FronteggAuth.kt
+++ b/android/src/main/java/com/frontegg/android/FronteggAuth.kt
@@ -230,11 +230,11 @@ class FronteggAuth(
private fun cancelLastTimer() {
Log.d(TAG, "Cancel Last Timer")
- if(timerTask!= null){
+ if (timerTask != null) {
timerTask?.cancel()
timerTask = null
}
- if(refreshTokenJob!= null) {
+ if (refreshTokenJob != null) {
val context = FronteggApp.getInstance().context
val jobScheduler =
context.getSystemService(Context.JOB_SCHEDULER_SERVICE) as JobScheduler
@@ -245,15 +245,20 @@ class FronteggAuth(
fun scheduleTimer(offset: Long) {
FronteggApp.getInstance().lastJobStart = Instant.now().toEpochMilli()
- if(FronteggApp.getInstance().appInForeground){
+ if (FronteggApp.getInstance().appInForeground) {
Log.d(TAG, "[foreground] Start Timer task (${offset} ms)")
this.timerTask = Timer().schedule(offset) {
- Log.d(TAG, "[foreground] Job started, (${Instant.now().toEpochMilli()-FronteggApp.getInstance().lastJobStart} ms)")
+ Log.d(
+ TAG,
+ "[foreground] Job started, (${
+ Instant.now().toEpochMilli() - FronteggApp.getInstance().lastJobStart
+ } ms)"
+ )
refreshTokenIfNeeded()
}
- }else {
+ } else {
Log.d(TAG, "[background] Start Job Scheduler task (${offset} ms)")
val context = FronteggApp.getInstance().context
val jobScheduler =
@@ -347,15 +352,20 @@ class FronteggAuth(
}
- fun login(activity: Activity, loginHint: String? = null) {
+ fun login(activity: Activity, loginHint: String? = null, callback: (() -> Unit)? = null) {
if (FronteggApp.getInstance().isEmbeddedMode) {
- EmbeddedAuthActivity.authenticate(activity, loginHint)
+ EmbeddedAuthActivity.authenticate(activity, loginHint, callback)
} else {
AuthenticationActivity.authenticate(activity, loginHint)
}
}
- fun directLoginAction(activity: Activity, type: String, data: String, callback: (() -> Unit)? = null) {
+ fun directLoginAction(
+ activity: Activity,
+ type: String,
+ data: String,
+ callback: (() -> Unit)? = null
+ ) {
if (FronteggApp.getInstance().isEmbeddedMode) {
EmbeddedAuthActivity.directLoginAction(activity, type, data, callback)
} else {
diff --git a/android/src/main/java/com/frontegg/android/embedded/CredentialManagerHandler.kt b/android/src/main/java/com/frontegg/android/embedded/CredentialManagerHandler.kt
new file mode 100644
index 0000000..4e70af1
--- /dev/null
+++ b/android/src/main/java/com/frontegg/android/embedded/CredentialManagerHandler.kt
@@ -0,0 +1,69 @@
+package com.frontegg.android.embedded
+
+
+import android.app.Activity
+import android.util.Log
+import androidx.credentials.*
+import androidx.credentials.exceptions.*
+import org.json.JSONObject
+
+/**
+ * A class that encapsulates the credential manager object and provides simplified APIs for
+ * creating and retrieving public key credentials. For other types of credentials follow the
+ * documentation https://developer.android.com/training/sign-in/passkeys
+ */
+class CredentialManagerHandler(private val activity: Activity) {
+
+ private val mCredMan = CredentialManager.create(activity.applicationContext)
+ private val TAG = "CredentialManagerHandler"
+
+ /**
+ * Encapsulates the create passkey API for credential manager in a less error-prone manner.
+ *
+ * @param request a create public key credential request JSON required by [CreatePublicKeyCredentialRequest].
+ * @return [CreatePublicKeyCredentialResponse] containing the result of the credential creation.
+ */
+ suspend fun createPasskey(requestStr: String): CreatePublicKeyCredentialResponse {
+
+ val json = JSONObject(requestStr)
+ val authenticatorSelection = json.getJSONObject("authenticatorSelection");
+ authenticatorSelection.put("residentKey", "preferred")
+ authenticatorSelection.put("userVerification", "required")
+ authenticatorSelection.put("authenticatorAttachment", "platform")
+ authenticatorSelection.put("requireResidentKey", false)
+ json.put("authenticatorSelection", authenticatorSelection)
+ val request = json.toString()
+
+ val createRequest = CreatePublicKeyCredentialRequest(request, null, true)
+ try {
+ return mCredMan.createCredential(
+ activity,
+ createRequest
+ ) as CreatePublicKeyCredentialResponse
+ } catch (e: CreateCredentialException) {
+ // For error handling use guidance from https://developer.android.com/training/sign-in/passkeys
+ Log.i(
+ TAG,
+ "Error creating credential: ErrMessage: ${e.errorMessage}, ErrType: ${e.type}"
+ )
+ throw e
+ }
+ }
+
+ /**
+ * Encapsulates the get passkey API for credential manager in a less error-prone manner.
+ *
+ * @param request a get public key credential request JSON required by [GetCredentialRequest].
+ * @return [GetCredentialResponse] containing the result of the credential retrieval.
+ */
+ suspend fun getPasskey(request: String): GetCredentialResponse {
+ val getRequest = GetCredentialRequest(listOf(GetPublicKeyCredentialOption(request, null)))
+ try {
+ return mCredMan.getCredential(activity, getRequest)
+ } catch (e: GetCredentialException) {
+ // For error handling use guidance from https://developer.android.com/training/sign-in/passkeys
+ Log.i(TAG, "Error retrieving credential: ${e.message}")
+ throw e
+ }
+ }
+}
diff --git a/android/src/main/java/com/frontegg/android/embedded/FronteggNativeBridge.kt b/android/src/main/java/com/frontegg/android/embedded/FronteggNativeBridge.kt
index 96adcd7..2377a06 100644
--- a/android/src/main/java/com/frontegg/android/embedded/FronteggNativeBridge.kt
+++ b/android/src/main/java/com/frontegg/android/embedded/FronteggNativeBridge.kt
@@ -11,7 +11,6 @@ import androidx.browser.customtabs.CustomTabsIntent
import com.frontegg.android.EmbeddedAuthActivity
import com.frontegg.android.FronteggApp
import com.frontegg.android.utils.AuthorizeUrlGenerator
-import com.google.androidbrowserhelper.trusted.LauncherActivity
import org.json.JSONException
import org.json.JSONObject
@@ -30,11 +29,15 @@ class FronteggNativeBridge(val context: Context) {
companion object {
- fun directLoginWithContext(context: Context, directLogin: Map, preserveCodeVerifier:Boolean) {
+ fun directLoginWithContext(
+ context: Context,
+ directLogin: Map,
+ preserveCodeVerifier: Boolean
+ ) {
val generatedUrl = try {
val jsonData = JSONObject(directLogin).toString().toByteArray(Charsets.UTF_8)
- val jsonString = Base64.encodeToString(jsonData, Base64.NO_WRAP )
+ val jsonString = Base64.encodeToString(jsonData, Base64.NO_WRAP)
AuthorizeUrlGenerator().generate(null, jsonString, preserveCodeVerifier)
} catch (e: JSONException) {
AuthorizeUrlGenerator().generate(null, null, preserveCodeVerifier)
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 a4d25f5..f0e9810 100644
--- a/android/src/main/java/com/frontegg/android/embedded/FronteggWebClient.kt
+++ b/android/src/main/java/com/frontegg/android/embedded/FronteggWebClient.kt
@@ -33,7 +33,8 @@ import okhttp3.Request
import org.json.JSONObject
-class FronteggWebClient(val context: Context) : WebViewClient() {
+class FronteggWebClient(val context: Context, val passkeyWebListener: PasskeyWebListener) :
+ WebViewClient() {
companion object {
private val TAG = FronteggWebClient::class.java.simpleName
}
@@ -45,6 +46,9 @@ class FronteggWebClient(val context: Context) : WebViewClient() {
super.onPageStarted(view, url, favicon)
Log.d(TAG, "onPageStarted $url")
FronteggAuth.instance.isLoading.value = true
+
+ passkeyWebListener.onPageStarted();
+ view?.evaluateJavascript(PasskeyWebListener.INJECTED_VAL, null)
}
override fun onPageFinished(view: WebView?, url: String?) {
@@ -158,7 +162,6 @@ class FronteggWebClient(val context: Context) : WebViewClient() {
}
-
var json = JsonParser.parseString(text)
while (!json.isJsonObject) {
text = json.asString
diff --git a/android/src/main/java/com/frontegg/android/embedded/FronteggWebView.kt b/android/src/main/java/com/frontegg/android/embedded/FronteggWebView.kt
index 2fd933a..a0b61d1 100644
--- a/android/src/main/java/com/frontegg/android/embedded/FronteggWebView.kt
+++ b/android/src/main/java/com/frontegg/android/embedded/FronteggWebView.kt
@@ -1,15 +1,16 @@
package com.frontegg.android.embedded
import android.annotation.SuppressLint
+import android.app.Activity
import android.content.Context
import android.content.pm.PackageManager
import android.util.AttributeSet
import android.webkit.CookieManager
import android.webkit.WebView
+import androidx.webkit.WebViewCompat
+import androidx.webkit.WebViewFeature
import com.frontegg.android.FronteggApp
-import com.frontegg.android.utils.AuthorizeUrlGenerator
-import okhttp3.internal.userAgent
-import java.util.*
+import kotlinx.coroutines.MainScope
open class FronteggWebView : WebView {
@@ -60,14 +61,23 @@ open class FronteggWebView : WebView {
settings.userAgentString = userAgent
}
- webViewClient = FronteggWebClient(context)
+ val scope = MainScope()
+ val credentialManagerHandler = CredentialManagerHandler(context as Activity)
+ val passkeyWebListener = PasskeyWebListener(context, scope, credentialManagerHandler)
+
+ webViewClient = FronteggWebClient(context, passkeyWebListener)
CookieManager.getInstance().setAcceptThirdPartyCookies(this, true)
this.addJavascriptInterface(FronteggNativeBridge(context), "FronteggNativeBridge")
+ val rules = setOf("*")
+ if (WebViewFeature.isFeatureSupported(WebViewFeature.WEB_MESSAGE_LISTENER)) {
+ WebViewCompat.addWebMessageListener(
+ this, PasskeyWebListener.INTERFACE_NAME, rules, passkeyWebListener
+ )
+ }
}
-
override fun onDetachedFromWindow() {
super.onDetachedFromWindow()
clearWebView()
diff --git a/android/src/main/java/com/frontegg/android/embedded/PasskeyWebListener.kt b/android/src/main/java/com/frontegg/android/embedded/PasskeyWebListener.kt
new file mode 100644
index 0000000..4aec8be
--- /dev/null
+++ b/android/src/main/java/com/frontegg/android/embedded/PasskeyWebListener.kt
@@ -0,0 +1,276 @@
+package com.frontegg.android.embedded
+
+import android.app.Activity
+import android.net.Uri
+import android.util.Log
+import android.webkit.WebView
+import android.widget.Toast
+import androidx.annotation.UiThread
+import androidx.credentials.PublicKeyCredential
+import androidx.credentials.exceptions.CreateCredentialException
+import androidx.credentials.exceptions.GetCredentialException
+import androidx.credentials.exceptions.NoCredentialException
+import androidx.webkit.JavaScriptReplyProxy
+import androidx.webkit.WebMessageCompat
+import androidx.webkit.WebViewCompat
+import com.frontegg.android.FronteggAuth
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+import org.json.JSONArray
+import org.json.JSONObject
+
+
+/**
+This web listener looks for the 'postMessage()' call on the javascript web code, and when it
+receives it, it will handle it in the manner dictated in this local codebase. This allows for
+javascript on the web to interact with the local setup on device that contains more complex logic.
+
+The embedded javascript can be found in CredentialManagerWebView/javascript/encode.js.
+It can be modified depending on the use case. If you wish to minify, please use the following command
+to call the toptal minifier API.
+```
+cat encode.js | grep -v '^let __webauthn_interface__;$' | \
+curl -X POST --data-urlencode input@- \
+https://www.toptal.com/developers/javascript-minifier/api/raw | tr '"' "'" | pbcopy
+```
+pbpaste should output the proper minimized code. In linux, you may have to alias as follows:
+```
+alias pbcopy='xclip -selection clipboard'
+alias pbpaste='xclip -selection clipboard -o'
+```
+in your bashrc.
+ */
+class PasskeyWebListener(
+ private val activity: Activity,
+ private val coroutineScope: CoroutineScope,
+ private val credentialManagerHandler: CredentialManagerHandler
+) : WebViewCompat.WebMessageListener {
+
+ /** havePendingRequest is true if there is an outstanding WebAuthn request. There is only ever
+ one request outstanding at a time.*/
+ private var havePendingRequest = false
+
+ /** pendingRequestIsDoomed is true if the WebView has navigated since starting a request. The
+ fido module cannot be cancelled, but the response will never be delivered in this case.*/
+ private var pendingRequestIsDoomed = false
+
+ /** replyChannel is the port that the page is listening for a response on. It
+ is valid if `havePendingRequest` is true.*/
+ private var replyChannel: ReplyChannel? = null
+
+ /** Called by the page when it wants to do a WebAuthn `get` or 'post' request. */
+ @UiThread
+ override fun onPostMessage(
+ view: WebView,
+ message: WebMessageCompat,
+ sourceOrigin: Uri,
+ isMainFrame: Boolean,
+ replyProxy: JavaScriptReplyProxy,
+ ) {
+ Log.i(TAG, "In Post Message : $message source: $sourceOrigin");
+ val messageData = message.data ?: return
+ onRequest(messageData, sourceOrigin, isMainFrame, JavaScriptReplyChannel(replyProxy))
+ }
+
+ private fun onRequest(
+ msg: String,
+ sourceOrigin: Uri,
+ isMainFrame: Boolean,
+ reply: ReplyChannel,
+ ) {
+ msg.let {
+ val jsonObj = JSONObject(msg);
+ val type = jsonObj.getString(TYPE_KEY)
+ val message = jsonObj.getString(REQUEST_KEY)
+
+ if (havePendingRequest) {
+ postErrorMessage(reply, "Request already in progress", type)
+ return
+ }
+ replyChannel = reply
+ if (!isMainFrame) {
+ reportFailure("Requests from SubFrames are not supported", type)
+ return
+ }
+
+ val originScheme = sourceOrigin.scheme
+ if (originScheme == null || originScheme.lowercase() != "https") {
+ reportFailure("WebAuthn not permitted for current URL", type)
+ return
+ }
+
+ // Verify that origin belongs to your website,
+ // it's because the unknown origin may gain credential info.
+ if (isUnknownOrigin(sourceOrigin)) {
+ reportFailure("WebAuthn not permitted for current URL", type)
+ return
+ }
+
+ havePendingRequest = true
+ pendingRequestIsDoomed = false
+
+ // Let’s use a temporary “replyCurrent” variable to send the data back, while resetting
+ // the main “replyChannel” variable to null so it’s ready for the next request.
+ val replyCurrent = replyChannel
+ if (replyCurrent == null) {
+ Log.i(TAG, "reply channel was null, cannot continue")
+ return;
+ }
+
+ when (type) {
+ CREATE_UNIQUE_KEY ->
+ this.coroutineScope.launch {
+ handleCreateFlow(credentialManagerHandler, message, replyCurrent)
+ }
+
+ GET_UNIQUE_KEY -> this.coroutineScope.launch {
+ handleGetFlow(credentialManagerHandler, message, replyCurrent)
+ }
+
+ else -> Log.i(TAG, "Incorrect request json")
+ }
+ }
+ }
+
+ private fun isUnknownOrigin(sourceOrigin: Uri): Boolean {
+ val baseUrlHost = Uri.parse(FronteggAuth.instance.baseUrl).host
+ return sourceOrigin.host != baseUrlHost
+ }
+
+ // Handles the get flow in a less error-prone way
+ private suspend fun handleGetFlow(
+ credentialManagerHandler: CredentialManagerHandler,
+ message: String,
+ reply: ReplyChannel,
+ ) {
+ try {
+ havePendingRequest = false
+ pendingRequestIsDoomed = false
+ val r = credentialManagerHandler.getPasskey(message)
+ val successArray = ArrayList();
+ successArray.add("success");
+ successArray.add(
+ JSONObject(
+ (r.credential as PublicKeyCredential).authenticationResponseJson
+ )
+ )
+ successArray.add(GET_UNIQUE_KEY);
+ reply.send(JSONArray(successArray).toString())
+ replyChannel = null // setting initial replyChannel for next request given temp 'reply'
+ } catch (e: GetCredentialException) {
+ reportFailure("Error: ${e.errorMessage} w type: ${e.type} w obj: $e", GET_UNIQUE_KEY, e)
+ } catch (t: Throwable) {
+ reportFailure("Error: ${t.message}", GET_UNIQUE_KEY, Exception(t))
+ }
+ }
+
+ // handles the create flow in a less error prone way
+ private suspend fun handleCreateFlow(
+ credentialManagerHandler: CredentialManagerHandler,
+ message: String,
+ reply: ReplyChannel,
+ ) {
+ try {
+ havePendingRequest = false
+ pendingRequestIsDoomed = false
+ val response = credentialManagerHandler.createPasskey(message)
+ val successArray = ArrayList();
+ successArray.add("success");
+ successArray.add(JSONObject(response.registrationResponseJson));
+ successArray.add(CREATE_UNIQUE_KEY);
+ reply.send(JSONArray(successArray).toString())
+ replyChannel = null // setting initial replyChannel for next request given temp 'reply'
+ } catch (e: CreateCredentialException) {
+ reportFailure(
+ "Error: ${e.errorMessage} w type: ${e.type} w obj: $e",
+ CREATE_UNIQUE_KEY,
+ e
+ )
+ } catch (t: Throwable) {
+ reportFailure("Error: ${t.message}", CREATE_UNIQUE_KEY, t as Exception)
+ }
+ }
+
+ /** Invalidates any current request. */
+ fun onPageStarted() {
+ if (havePendingRequest) {
+ pendingRequestIsDoomed = true
+ }
+ }
+
+ /** Sends an error result to the page. */
+ private fun reportFailure(message: String, type: String, e: Exception? = null) {
+ havePendingRequest = false
+ pendingRequestIsDoomed = false
+ val reply: ReplyChannel = replyChannel!! // verifies non null by throwing NPE
+ replyChannel = null
+ postErrorMessage(reply, message, type, e)
+ }
+
+ private fun postErrorMessage(
+ reply: ReplyChannel,
+ errorMessage: String,
+ type: String,
+ e: Exception? = null
+ ) {
+ Log.i(TAG, "Sending error message back to the page via replyChannel $errorMessage");
+ val array: MutableList = ArrayList()
+ array.add("error")
+ array.add(errorMessage)
+ array.add(type)
+ reply.send(JSONArray(array).toString())
+
+ if (e is NoCredentialException) {
+ Toast.makeText(
+ this.activity.applicationContext,
+ "No passkeys found. Try another sign-in option.",
+ Toast.LENGTH_LONG
+ ).show()
+ } else if (e == null) {
+ Toast.makeText(this.activity.applicationContext, errorMessage, Toast.LENGTH_SHORT)
+ .show()
+ } else {
+ Toast.makeText(
+ this.activity.applicationContext,
+ "Unknown Error Occurred",
+ Toast.LENGTH_SHORT
+ ).show()
+
+ }
+ }
+
+ private class JavaScriptReplyChannel(private val reply: JavaScriptReplyProxy) :
+ ReplyChannel {
+ override fun send(message: String?) {
+ try {
+ reply.postMessage(message!!)
+ } catch (t: Throwable) {
+ Log.i(TAG, "Reply failure due to: " + t.message);
+ }
+ }
+ }
+
+ /** ReplyChannel is the interface over which replies to the embedded site are sent. This allows
+ for testing because AndroidX bans mocking its objects.*/
+ interface ReplyChannel {
+ fun send(message: String?)
+ }
+
+ companion object {
+ /** INTERFACE_NAME is the name of the MessagePort that must be injected into pages. */
+ const val INTERFACE_NAME = "__webauthn_interface__"
+
+ const val CREATE_UNIQUE_KEY = "create"
+ const val GET_UNIQUE_KEY = "get"
+ const val TYPE_KEY = "type"
+ const val REQUEST_KEY = "request"
+
+ /** INJECTED_VAL is the minified version of the JavaScript code described at this class
+ * heading. The non minified form is found at credmanweb/javascript/encode.js.*/
+ const val INJECTED_VAL = """
+ var __webauthn_interface__,__webauthn_hooks__;!function(e){console.log("In the hook."),__webauthn_interface__.addEventListener("message",function e(n){var r=JSON.parse(n.data),t=r[2];"get"===t?o(r):"create"===t?u(r):console.log("Incorrect response format for reply")});var n=null,r=null,t=null,a=null;function o(e){if(null!==n&&null!==t){if("success"!=e[0]){var r=t;n=null,t=null,r(new DOMException(e[1],"NotAllowedError"));return}var a=i(e[1]),o=n;n=null,t=null,o(a)}}function l(e){var n=e.length%4;return Uint8Array.from(atob(e.replace(/-/g,"+").replace(/_/g,"/").padEnd(e.length+(0===n?0:4-n),"=")),function(e){return e.charCodeAt(0)}).buffer}function s(e){return btoa(Array.from(new Uint8Array(e),function(e){return String.fromCharCode(e)}).join("")).replace(/\+/g,"-").replace(/\//g,"_").replace(/=+${'$'}/,"")}function u(e){if(null===r||null===a){console.log("Here: "+r+" and reject: "+a);return}if(console.log("Output back: "+e),"success"!=e[0]){var n=a;r=null,a=null,n(new DOMException(e[1],"NotAllowedError"));return}var t=i(e[1]),o=r;r=null,a=null,o(t)}function i(e){return console.log("Here is the response from credential manager: "+e),e.rawId=l(e.rawId),e.response.clientDataJSON=l(e.response.clientDataJSON),e.response.hasOwnProperty("attestationObject")&&(e.response.attestationObject=l(e.response.attestationObject)),e.response.hasOwnProperty("authenticatorData")&&(e.response.authenticatorData=l(e.response.authenticatorData)),e.response.hasOwnProperty("signature")&&(e.response.signature=l(e.response.signature)),e.response.hasOwnProperty("userHandle")&&(e.response.userHandle=l(e.response.userHandle)),e.getClientExtensionResults=function e(){return{}},e}e.create=function n(t){if(!("publicKey"in t))return e.originalCreateFunction(t);var o=new Promise(function(e,n){r=e,a=n}),l=t.publicKey;if(l.hasOwnProperty("challenge")){var u=s(l.challenge);l.challenge=u}if(l.hasOwnProperty("user")&&l.user.hasOwnProperty("id")){var i=s(l.user.id);l.user.id=i}var c=JSON.stringify({type:"create",request:l});return __webauthn_interface__.postMessage(c),o},e.get=function r(a){if(!("publicKey"in a))return e.originalGetFunction(a);var o=new Promise(function(e,r){n=e,t=r}),l=a.publicKey;if(l.hasOwnProperty("challenge")){var u=s(l.challenge);l.challenge=u}var i=JSON.stringify({type:"get",request:l});return __webauthn_interface__.postMessage(i),o},e.onReplyGet=o,e.CM_base64url_decode=l,e.CM_base64url_encode=s,e.onReplyCreate=u}(__webauthn_hooks__||(__webauthn_hooks__={})),__webauthn_hooks__.originalGetFunction=navigator.credentials.get,__webauthn_hooks__.originalCreateFunction=navigator.credentials.create,navigator.credentials.get=__webauthn_hooks__.get,navigator.credentials.create=__webauthn_hooks__.create,window.PublicKeyCredential=function(){},window.PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable=function(){return Promise.resolve(!1)};
+ """
+ const val TAG = "PasskeyWebListener"
+ }
+
+}
\ No newline at end of file
diff --git a/android/src/main/java/com/frontegg/android/exceptions/FronteggException.kt b/android/src/main/java/com/frontegg/android/exceptions/FronteggException.kt
index 1d05c20..23d874e 100644
--- a/android/src/main/java/com/frontegg/android/exceptions/FronteggException.kt
+++ b/android/src/main/java/com/frontegg/android/exceptions/FronteggException.kt
@@ -5,8 +5,11 @@ public open class FronteggException(message: String, cause: Throwable? = null) :
public companion object {
public const val UNKNOWN_ERROR: String = "frontegg.error.unknown"
- public const val FRONTEGG_APP_MUST_BE_INITIALIZED: String = "frontegg.error.app_must_be_initialized"
- public const val FRONTEGG_DOMAIN_MUST_NOT_START_WITH_HTTPS: String = "frontegg.error.domain_must_not_start_with_https"
- public const val KEY_NOT_FOUND_SHARED_PREFERENCES_ERROR: String = "frontegg.error.key_not_found_shared_preferences"
+ public const val FRONTEGG_APP_MUST_BE_INITIALIZED: String =
+ "frontegg.error.app_must_be_initialized"
+ public const val FRONTEGG_DOMAIN_MUST_NOT_START_WITH_HTTPS: String =
+ "frontegg.error.domain_must_not_start_with_https"
+ public const val KEY_NOT_FOUND_SHARED_PREFERENCES_ERROR: String =
+ "frontegg.error.key_not_found_shared_preferences"
}
}
\ No newline at end of file
diff --git a/android/src/main/java/com/frontegg/android/exceptions/KeyNotFoundException.kt b/android/src/main/java/com/frontegg/android/exceptions/KeyNotFoundException.kt
index 31853af..d68862a 100644
--- a/android/src/main/java/com/frontegg/android/exceptions/KeyNotFoundException.kt
+++ b/android/src/main/java/com/frontegg/android/exceptions/KeyNotFoundException.kt
@@ -3,4 +3,5 @@ package com.frontegg.android.exceptions
/**
* Exception that represents key not found when trying to get value from Shared Preference
*/
-public class KeyNotFoundException(cause: Throwable) : FronteggException(KEY_NOT_FOUND_SHARED_PREFERENCES_ERROR, cause)
+public class KeyNotFoundException(cause: Throwable) :
+ FronteggException(KEY_NOT_FOUND_SHARED_PREFERENCES_ERROR, cause)
diff --git a/android/src/main/java/com/frontegg/android/services/RefreshTokenService.kt b/android/src/main/java/com/frontegg/android/services/RefreshTokenService.kt
index 80f44fb..3ee9efa 100644
--- a/android/src/main/java/com/frontegg/android/services/RefreshTokenService.kt
+++ b/android/src/main/java/com/frontegg/android/services/RefreshTokenService.kt
@@ -16,7 +16,12 @@ class RefreshTokenService : JobService() {
}
override fun onStartJob(params: JobParameters?): Boolean {
- Log.d(TAG, "Job started, (${Instant.now().toEpochMilli() - FronteggApp.getInstance().lastJobStart} ms)")
+ Log.d(
+ TAG,
+ "Job started, (${
+ Instant.now().toEpochMilli() - FronteggApp.getInstance().lastJobStart
+ } ms)"
+ )
performBackgroundTask(params)
return true
}
diff --git a/android/src/main/java/com/frontegg/android/utils/ObservableValue.kt b/android/src/main/java/com/frontegg/android/utils/ObservableValue.kt
index 521edd6..ce6c668 100644
--- a/android/src/main/java/com/frontegg/android/utils/ObservableValue.kt
+++ b/android/src/main/java/com/frontegg/android/utils/ObservableValue.kt
@@ -1,7 +1,5 @@
package com.frontegg.android.utils
-import android.os.Handler
-import android.os.Looper
import io.reactivex.rxjava3.disposables.Disposable
import io.reactivex.rxjava3.functions.Consumer
import io.reactivex.rxjava3.subjects.PublishSubject
diff --git a/app/build.gradle b/app/build.gradle
index 5564db3..3ae4575 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -3,7 +3,7 @@ plugins {
id 'org.jetbrains.kotlin.android'
}
-def fronteggDomain = "auth.davidantoon.me"
+def fronteggDomain = "autheu.davidantoon.me"
def fronteggClientId = "b6adfe4c-d695-4c04-b95f-3ec9fd0c6cca"
def fronteggApplicationId = "16407b9a-5b6c-43de-9f58-6a1d1e0077f8"
@@ -14,7 +14,7 @@ android {
defaultConfig {
applicationId "com.frontegg.demo"
minSdk 26
- targetSdk 33
+ targetSdk 34
versionCode 1
versionName "1.0"
@@ -46,6 +46,7 @@ android {
}
buildFeatures {
viewBinding true
+ buildConfig true
}
buildToolsVersion buildToolsVersion
diff --git a/build.gradle b/build.gradle
index ba9ac0d..76a3d70 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,14 +1,14 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
- id 'com.android.application' version '7.4.2' apply false
- id 'com.android.library' version '7.4.2' apply false
+ id 'com.android.application' version '8.5.2' apply false
+ id 'com.android.library' version '8.5.2' apply false
id 'org.jetbrains.kotlin.android' version '1.9.10' apply false
}
ext {
kotlinVersion = "1.7.10"
- buildToolsVersion = "32.0.0"
+ buildToolsVersion = "35.0.0"
androidxAnnotationVersion = "1.5.0"
extTruthVersion = "1.6.0-alpha01"
coreVersion = "1.6.0-alpha01"
diff --git a/embedded/build.gradle b/embedded/build.gradle
index d2bcd2a..07019ee 100644
--- a/embedded/build.gradle
+++ b/embedded/build.gradle
@@ -3,9 +3,14 @@ plugins {
id 'org.jetbrains.kotlin.android'
}
-def fronteggDomain = "auth.davidantoon.me"
+
+def fronteggDomain = "autheu.davidantoon.me"
def fronteggClientId = "b6adfe4c-d695-4c04-b95f-3ec9fd0c6cca"
+//def fronteggDomain = "auth.davidantoon.me"
+//def fronteggClientId = "04ae2174-d8d9-4a90-8bab-2548e210a508"
+
+
android {
namespace 'com.frontegg.demo'
compileSdk 34
@@ -30,11 +35,29 @@ android {
}
+ signingConfigs {
+ release {
+ storeFile file(project.property("RELEASE_STORE_FILE"))
+ storePassword project.property("RELEASE_STORE_PASSWORD")
+ keyAlias project.property("RELEASE_KEY_ALIAS")
+ keyPassword project.property("RELEASE_KEY_PASSWORD")
+ }
+ debug {
+ storeFile file(project.property("DEBUG_STORE_FILE"))
+ storePassword project.property("DEBUG_STORE_PASSWORD")
+ keyAlias project.property("DEBUG_KEY_ALIAS")
+ keyPassword project.property("DEBUG_KEY_PASSWORD")
+ }
+ }
buildTypes {
release {
minifyEnabled true
shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ signingConfig signingConfigs.release
+ }
+ debug {
+ signingConfig signingConfigs.debug
}
}
compileOptions {
@@ -46,6 +69,7 @@ android {
}
buildFeatures {
viewBinding true
+ buildConfig true
}
buildToolsVersion buildToolsVersion
@@ -56,14 +80,14 @@ dependencies {
implementation 'androidx.core:core-ktx:1.10.0'
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'io.reactivex.rxjava3:rxkotlin:3.0.1'
- implementation 'com.google.android.material:material:1.8.0'
+ implementation 'com.google.android.material:material:1.12.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
- implementation 'androidx.navigation:navigation-fragment-ktx:2.5.3'
- implementation 'androidx.navigation:navigation-ui-ktx:2.5.3'
- implementation 'androidx.tracing:tracing:1.1.0'
+ implementation 'androidx.navigation:navigation-fragment-ktx:2.8.1'
+ implementation 'androidx.navigation:navigation-ui-ktx:2.8.1'
+ implementation 'androidx.tracing:tracing:1.2.0'
implementation project(path: ':android')
- implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.6.2'
- implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.2'
+ implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.8.6'
+ implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.6'
implementation 'de.hdodenhof:circleimageview:3.1.0'
implementation 'com.github.bumptech.glide:glide:4.12.0'
diff --git a/embedded/proguard-rules.pro b/embedded/proguard-rules.pro
index 481bb43..f1b4245 100644
--- a/embedded/proguard-rules.pro
+++ b/embedded/proguard-rules.pro
@@ -18,4 +18,4 @@
# If you keep the line number information, uncomment this to
# hide the original source file name.
-#-renamesourcefileattribute SourceFile
\ No newline at end of file
+#-renamesourcefileattribute SourceFile
diff --git a/embedded/src/main/java/com/frontegg/demo/AuthFragment.kt b/embedded/src/main/java/com/frontegg/demo/AuthFragment.kt
index 2c36c66..1ad1bfa 100644
--- a/embedded/src/main/java/com/frontegg/demo/AuthFragment.kt
+++ b/embedded/src/main/java/com/frontegg/demo/AuthFragment.kt
@@ -36,7 +36,9 @@ class AuthFragment : Fragment() {
super.onViewCreated(view, savedInstanceState)
binding.loginButton.setOnClickListener {
- FronteggAuth.instance.login(requireActivity())
+ FronteggAuth.instance.login(requireActivity()) {
+ Log.d("AuthFragment", "Login callback")
+ }
}
binding.googleLoginButton.setOnClickListener {
diff --git a/gradle.properties b/gradle.properties
index 8ea0209..e174f9e 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -21,4 +21,18 @@ kotlin.code.style=official
# resources declared in the library itself and none from the library's dependencies,
# thereby reducing the size of the R class for that library
android.nonTransitiveRClass=true
+android.suppressUnsupportedCompileSdk=35
+
+
+# Test release gradle settings
+RELEASE_STORE_FILE=../.github/test-jks/release.keystore
+RELEASE_STORE_PASSWORD=android
+RELEASE_KEY_ALIAS=androidreleasekey
+RELEASE_KEY_PASSWORD=android
+
+# Test debug gradle settings
+DEBUG_STORE_FILE=../.github/test-jks/debug.keystore
+DEBUG_STORE_PASSWORD=android
+DEBUG_KEY_ALIAS=androiddebugkey
+DEBUG_KEY_PASSWORD=android
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index df250ba..8556058 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
-#Sat Jul 08 01:29:18 IDT 2023
+#Tue Oct 01 17:33:30 IDT 2024
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
diff --git a/multi-region/build.gradle b/multi-region/build.gradle
index 367ec05..ae4c8c9 100644
--- a/multi-region/build.gradle
+++ b/multi-region/build.gradle
@@ -4,9 +4,11 @@ plugins {
}
-def fronteggDomain = "auth.davidantoon.me"
+def fronteggDomain = "autheu.davidantoon.me"
def fronteggClientId = "b6adfe4c-d695-4c04-b95f-3ec9fd0c6cca"
+def fronteggDomain2 = "authus.davidantoon.me"
+
android {
namespace 'com.frontegg.demo'
compileSdk 34
@@ -23,7 +25,8 @@ android {
manifestPlaceholders = [
"package_name" : applicationId,
"frontegg_domain" : fronteggDomain,
- "frontegg_client_id": fronteggClientId
+ "frontegg_client_id": fronteggClientId,
+ "frontegg_domain_2" : fronteggDomain2,
]
vectorDrawables {
useSupportLibrary true
diff --git a/multi-region/src/main/AndroidManifest.xml b/multi-region/src/main/AndroidManifest.xml
index 12ac938..4902a40 100644
--- a/multi-region/src/main/AndroidManifest.xml
+++ b/multi-region/src/main/AndroidManifest.xml
@@ -41,16 +41,16 @@
@@ -66,11 +66,19 @@
+
+
+
+
+
+
+
+
diff --git a/multi-region/src/main/java/com/frontegg/demo/App.kt b/multi-region/src/main/java/com/frontegg/demo/App.kt
index 8faff08..b0bf6e7 100644
--- a/multi-region/src/main/java/com/frontegg/demo/App.kt
+++ b/multi-region/src/main/java/com/frontegg/demo/App.kt
@@ -18,13 +18,13 @@ class App : Application() {
listOf(
RegionConfig(
"eu",
- "auth.davidantoon.me",
+ "autheu.davidantoon.me",
"b6adfe4c-d695-4c04-b95f-3ec9fd0c6cca"
),
RegionConfig(
"us",
- "davidprod.frontegg.com",
- "d7d07347-2c57-4450-8418-0ec7ee6e096b"
+ "authus.davidantoon.me",
+ "6903cab0-9809-4a2e-97dd-b8c0f966c813"
)
),
this
diff --git a/multi-region/src/main/res/layout/activity_region_selection.xml b/multi-region/src/main/res/layout/activity_region_selection.xml
index 47895b8..65571a2 100644
--- a/multi-region/src/main/res/layout/activity_region_selection.xml
+++ b/multi-region/src/main/res/layout/activity_region_selection.xml
@@ -26,7 +26,7 @@
@@ -51,7 +51,7 @@