fix: initiate OAuth using Custom Tabs on Android, closes #361 (#788)

This commit is contained in:
Huang Xin
2025-04-01 23:04:17 +08:00
committed by GitHub
parent 7ccf3d0632
commit 6131180a31
18 changed files with 161 additions and 46 deletions
@@ -19,6 +19,12 @@
android:label="@string/main_activity_title"
android:name=".MainActivity"
android:exported="true">
<intent-filter android:label="oauth">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="readest" android:host="auth-callback" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
@@ -2,10 +2,14 @@ package com.bilingify.readest
import android.os.Build
import android.os.Bundle
import android.content.Intent
import android.net.Uri
import android.view.View
import android.view.WindowInsets
import android.view.WindowInsetsController
import androidx.activity.enableEdgeToEdge
import app.tauri.plugin.JSObject
import com.readest.native_bridge.NativeBridgePlugin
class MainActivity : TauriActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
@@ -15,6 +19,20 @@ class MainActivity : TauriActivity() {
hideSystemUI()
}
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
val uri = intent.data ?: return
if (uri.scheme == "readest" && uri.host == "auth-callback") {
val result = JSObject().apply {
put("redirectUrl", uri.toString())
}
NativeBridgePlugin.pendingInvoke?.resolve(result)
NativeBridgePlugin.pendingInvoke = null
}
}
private fun hideSystemUI() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
window.decorView.windowInsetsController?.let { controller ->
@@ -33,9 +33,9 @@ android {
}
dependencies {
implementation("androidx.core:core-ktx:1.9.0")
implementation("androidx.appcompat:appcompat:1.6.0")
implementation("androidx.browser:browser:1.8.0")
implementation("com.google.android.material:material:1.7.0")
testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.1.5")
@@ -3,7 +3,4 @@ package com.readest.native_bridge
import android.util.Log
class NativeBridge {
fun auth_with_safari(value: String): String {
return ""
}
}
@@ -1,7 +1,10 @@
package com.readest.native_bridge
import android.app.Activity
import android.content.Intent
import android.net.Uri
import android.util.Log
import androidx.browser.customtabs.CustomTabsIntent
import app.tauri.annotation.Command
import app.tauri.annotation.InvokeArg
import app.tauri.annotation.TauriPlugin
@@ -11,7 +14,7 @@ import app.tauri.plugin.Invoke
import java.io.*
@InvokeArg
class SafariAuthRequestArgs {
class AuthRequestArgs {
var authUrl: String? = null
}
@@ -24,14 +27,25 @@ class CopyURIRequestArgs {
@TauriPlugin
class NativeBridgePlugin(private val activity: Activity): Plugin(activity) {
private val implementation = NativeBridge()
private var redirectScheme = "readest"
private var redirectHost = "auth-callback"
companion object {
var pendingInvoke: Invoke? = null
}
@Command
fun auth_with_safari(invoke: Invoke) {
val args = invoke.parseArgs(SafariAuthRequestArgs::class.java)
fun auth_with_custom_tab(invoke: Invoke) {
val args = invoke.parseArgs(AuthRequestArgs::class.java)
val uri = Uri.parse(args.authUrl)
val ret = JSObject()
ret.put("redirectUrl", implementation.auth_with_safari(args.authUrl ?: ""))
invoke.resolve(ret)
val customTabsIntent = CustomTabsIntent.Builder().build()
customTabsIntent.intent.flags = Intent.FLAG_ACTIVITY_NO_HISTORY
Log.d("NativeBridgePlugin", "Launching OAuth URL: ${args.authUrl}")
customTabsIntent.launchUrl(activity, uri)
pendingInvoke = invoke
}
@Command
@@ -1,4 +1,8 @@
const COMMANDS: &[&str] = &["auth_with_safari", "copy_uri_to_path"];
const COMMANDS: &[&str] = &[
"auth_with_safari",
"auth_with_custom_tab",
"copy_uri_to_path",
];
fn main() {
tauri_plugin::Builder::new(COMMANDS)
@@ -0,0 +1,13 @@
# Automatically generated - DO NOT EDIT!
"$schema" = "../../schemas/schema.json"
[[permission]]
identifier = "allow-auth-with-custom-tab"
description = "Enables the auth_with_custom_tab command without any pre-configured scope."
commands.allow = ["auth_with_custom_tab"]
[[permission]]
identifier = "deny-auth-with-custom-tab"
description = "Denies the auth_with_custom_tab command without any pre-configured scope."
commands.deny = ["auth_with_custom_tab"]
@@ -3,6 +3,7 @@
Default permissions for the plugin
- `allow-auth-with-safari`
- `allow-auth-with-custom-tab`
- `allow-copy-uri-to-path`
## Permission Table
@@ -14,6 +15,32 @@ Default permissions for the plugin
</tr>
<tr>
<td>
`native-bridge:allow-auth-with-custom-tab`
</td>
<td>
Enables the auth_with_custom_tab command without any pre-configured scope.
</td>
</tr>
<tr>
<td>
`native-bridge:deny-auth-with-custom-tab`
</td>
<td>
Denies the auth_with_custom_tab command without any pre-configured scope.
</td>
</tr>
<tr>
<td>
@@ -1,3 +1,3 @@
[default]
description = "Default permissions for the plugin"
permissions = ["allow-auth-with-safari", "allow-copy-uri-to-path"]
permissions = ["allow-auth-with-safari", "allow-auth-with-custom-tab", "allow-copy-uri-to-path"]
@@ -294,6 +294,16 @@
"PermissionKind": {
"type": "string",
"oneOf": [
{
"description": "Enables the auth_with_custom_tab command without any pre-configured scope.",
"type": "string",
"const": "allow-auth-with-custom-tab"
},
{
"description": "Denies the auth_with_custom_tab command without any pre-configured scope.",
"type": "string",
"const": "deny-auth-with-custom-tab"
},
{
"description": "Enables the auth_with_safari command without any pre-configured scope.",
"type": "string",
@@ -7,11 +7,19 @@ use crate::Result;
#[command]
pub(crate) async fn auth_with_safari<R: Runtime>(
app: AppHandle<R>,
payload: SafariAuthRequest,
) -> Result<SafariAuthResponse> {
payload: AuthRequest,
) -> Result<AuthResponse> {
app.native_bridge().auth_with_safari(payload)
}
#[command]
pub(crate) async fn auth_with_custom_tab<R: Runtime>(
app: AppHandle<R>,
payload: AuthRequest,
) -> Result<AuthResponse> {
app.native_bridge().auth_with_custom_tab(payload)
}
#[command]
pub(crate) async fn copy_uri_to_path<R: Runtime>(
app: AppHandle<R>,
@@ -14,10 +14,11 @@ pub fn init<R: Runtime, C: DeserializeOwned>(
pub struct NativeBridge<R: Runtime>(AppHandle<R>);
impl<R: Runtime> NativeBridge<R> {
pub fn auth_with_safari(
&self,
_payload: SafariAuthRequest,
) -> crate::Result<SafariAuthResponse> {
pub fn auth_with_safari(&self, _payload: AuthRequest) -> crate::Result<AuthResponse> {
Err(crate::Error::UnsupportedPlatformError)
}
pub fn auth_with_custom_tab(&self, _payload: AuthRequest) -> crate::Result<AuthResponse> {
Err(crate::Error::UnsupportedPlatformError)
}
@@ -38,6 +38,7 @@ pub fn init<R: Runtime>() -> TauriPlugin<R> {
Builder::new("native-bridge")
.invoke_handler(tauri::generate_handler![
commands::auth_with_safari,
commands::auth_with_custom_tab,
commands::copy_uri_to_path,
])
.setup(|app, api| {
@@ -25,16 +25,21 @@ pub fn init<R: Runtime, C: DeserializeOwned>(
pub struct NativeBridge<R: Runtime>(PluginHandle<R>);
impl<R: Runtime> NativeBridge<R> {
pub fn auth_with_safari(
&self,
payload: SafariAuthRequest,
) -> crate::Result<SafariAuthResponse> {
pub fn auth_with_safari(&self, payload: AuthRequest) -> crate::Result<AuthResponse> {
self.0
.run_mobile_plugin("auth_with_safari", payload)
.map_err(Into::into)
}
}
impl<R: Runtime> NativeBridge<R> {
pub fn auth_with_custom_tab(&self, payload: AuthRequest) -> crate::Result<AuthResponse> {
self.0
.run_mobile_plugin("auth_with_custom_tab", payload)
.map_err(Into::into)
}
}
impl<R: Runtime> NativeBridge<R> {
pub fn copy_uri_to_path(&self, payload: CopyURIRequest) -> crate::Result<CopyURIResponse> {
self.0
@@ -2,13 +2,13 @@ use serde::{Deserialize, Serialize};
#[derive(Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct SafariAuthRequest {
pub struct AuthRequest {
pub auth_url: String,
}
#[derive(Debug, Clone, Default, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct SafariAuthResponse {
pub struct AuthResponse {
pub redirect_url: String,
}