@@ -19,6 +19,12 @@
|
|||||||
android:label="@string/main_activity_title"
|
android:label="@string/main_activity_title"
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
android:exported="true">
|
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>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
|
|||||||
+18
@@ -2,10 +2,14 @@ package com.bilingify.readest
|
|||||||
|
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.content.Intent
|
||||||
|
import android.net.Uri
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.WindowInsets
|
import android.view.WindowInsets
|
||||||
import android.view.WindowInsetsController
|
import android.view.WindowInsetsController
|
||||||
import androidx.activity.enableEdgeToEdge
|
import androidx.activity.enableEdgeToEdge
|
||||||
|
import app.tauri.plugin.JSObject
|
||||||
|
import com.readest.native_bridge.NativeBridgePlugin
|
||||||
|
|
||||||
class MainActivity : TauriActivity() {
|
class MainActivity : TauriActivity() {
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
@@ -15,6 +19,20 @@ class MainActivity : TauriActivity() {
|
|||||||
hideSystemUI()
|
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() {
|
private fun hideSystemUI() {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||||
window.decorView.windowInsetsController?.let { controller ->
|
window.decorView.windowInsetsController?.let { controller ->
|
||||||
|
|||||||
+1
-1
@@ -33,9 +33,9 @@ android {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
|
||||||
implementation("androidx.core:core-ktx:1.9.0")
|
implementation("androidx.core:core-ktx:1.9.0")
|
||||||
implementation("androidx.appcompat:appcompat:1.6.0")
|
implementation("androidx.appcompat:appcompat:1.6.0")
|
||||||
|
implementation("androidx.browser:browser:1.8.0")
|
||||||
implementation("com.google.android.material:material:1.7.0")
|
implementation("com.google.android.material:material:1.7.0")
|
||||||
testImplementation("junit:junit:4.13.2")
|
testImplementation("junit:junit:4.13.2")
|
||||||
androidTestImplementation("androidx.test.ext:junit:1.1.5")
|
androidTestImplementation("androidx.test.ext:junit:1.1.5")
|
||||||
|
|||||||
-3
@@ -3,7 +3,4 @@ package com.readest.native_bridge
|
|||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
|
||||||
class NativeBridge {
|
class NativeBridge {
|
||||||
fun auth_with_safari(value: String): String {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
+20
-6
@@ -1,7 +1,10 @@
|
|||||||
package com.readest.native_bridge
|
package com.readest.native_bridge
|
||||||
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
|
import android.content.Intent
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
import android.util.Log
|
||||||
|
import androidx.browser.customtabs.CustomTabsIntent
|
||||||
import app.tauri.annotation.Command
|
import app.tauri.annotation.Command
|
||||||
import app.tauri.annotation.InvokeArg
|
import app.tauri.annotation.InvokeArg
|
||||||
import app.tauri.annotation.TauriPlugin
|
import app.tauri.annotation.TauriPlugin
|
||||||
@@ -11,7 +14,7 @@ import app.tauri.plugin.Invoke
|
|||||||
import java.io.*
|
import java.io.*
|
||||||
|
|
||||||
@InvokeArg
|
@InvokeArg
|
||||||
class SafariAuthRequestArgs {
|
class AuthRequestArgs {
|
||||||
var authUrl: String? = null
|
var authUrl: String? = null
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -24,14 +27,25 @@ class CopyURIRequestArgs {
|
|||||||
@TauriPlugin
|
@TauriPlugin
|
||||||
class NativeBridgePlugin(private val activity: Activity): Plugin(activity) {
|
class NativeBridgePlugin(private val activity: Activity): Plugin(activity) {
|
||||||
private val implementation = NativeBridge()
|
private val implementation = NativeBridge()
|
||||||
|
private var redirectScheme = "readest"
|
||||||
|
private var redirectHost = "auth-callback"
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
var pendingInvoke: Invoke? = null
|
||||||
|
}
|
||||||
|
|
||||||
@Command
|
@Command
|
||||||
fun auth_with_safari(invoke: Invoke) {
|
fun auth_with_custom_tab(invoke: Invoke) {
|
||||||
val args = invoke.parseArgs(SafariAuthRequestArgs::class.java)
|
val args = invoke.parseArgs(AuthRequestArgs::class.java)
|
||||||
|
val uri = Uri.parse(args.authUrl)
|
||||||
|
|
||||||
val ret = JSObject()
|
val customTabsIntent = CustomTabsIntent.Builder().build()
|
||||||
ret.put("redirectUrl", implementation.auth_with_safari(args.authUrl ?: ""))
|
customTabsIntent.intent.flags = Intent.FLAG_ACTIVITY_NO_HISTORY
|
||||||
invoke.resolve(ret)
|
|
||||||
|
Log.d("NativeBridgePlugin", "Launching OAuth URL: ${args.authUrl}")
|
||||||
|
customTabsIntent.launchUrl(activity, uri)
|
||||||
|
|
||||||
|
pendingInvoke = invoke
|
||||||
}
|
}
|
||||||
|
|
||||||
@Command
|
@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() {
|
fn main() {
|
||||||
tauri_plugin::Builder::new(COMMANDS)
|
tauri_plugin::Builder::new(COMMANDS)
|
||||||
|
|||||||
+13
@@ -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"]
|
||||||
+27
@@ -3,6 +3,7 @@
|
|||||||
Default permissions for the plugin
|
Default permissions for the plugin
|
||||||
|
|
||||||
- `allow-auth-with-safari`
|
- `allow-auth-with-safari`
|
||||||
|
- `allow-auth-with-custom-tab`
|
||||||
- `allow-copy-uri-to-path`
|
- `allow-copy-uri-to-path`
|
||||||
|
|
||||||
## Permission Table
|
## Permission Table
|
||||||
@@ -14,6 +15,32 @@ Default permissions for the plugin
|
|||||||
</tr>
|
</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>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -1,3 +1,3 @@
|
|||||||
[default]
|
[default]
|
||||||
description = "Default permissions for the plugin"
|
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"]
|
||||||
|
|||||||
+10
@@ -294,6 +294,16 @@
|
|||||||
"PermissionKind": {
|
"PermissionKind": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"oneOf": [
|
"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.",
|
"description": "Enables the auth_with_safari command without any pre-configured scope.",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
|||||||
@@ -7,11 +7,19 @@ use crate::Result;
|
|||||||
#[command]
|
#[command]
|
||||||
pub(crate) async fn auth_with_safari<R: Runtime>(
|
pub(crate) async fn auth_with_safari<R: Runtime>(
|
||||||
app: AppHandle<R>,
|
app: AppHandle<R>,
|
||||||
payload: SafariAuthRequest,
|
payload: AuthRequest,
|
||||||
) -> Result<SafariAuthResponse> {
|
) -> Result<AuthResponse> {
|
||||||
app.native_bridge().auth_with_safari(payload)
|
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]
|
#[command]
|
||||||
pub(crate) async fn copy_uri_to_path<R: Runtime>(
|
pub(crate) async fn copy_uri_to_path<R: Runtime>(
|
||||||
app: AppHandle<R>,
|
app: AppHandle<R>,
|
||||||
|
|||||||
@@ -14,10 +14,11 @@ pub fn init<R: Runtime, C: DeserializeOwned>(
|
|||||||
pub struct NativeBridge<R: Runtime>(AppHandle<R>);
|
pub struct NativeBridge<R: Runtime>(AppHandle<R>);
|
||||||
|
|
||||||
impl<R: Runtime> NativeBridge<R> {
|
impl<R: Runtime> NativeBridge<R> {
|
||||||
pub fn auth_with_safari(
|
pub fn auth_with_safari(&self, _payload: AuthRequest) -> crate::Result<AuthResponse> {
|
||||||
&self,
|
Err(crate::Error::UnsupportedPlatformError)
|
||||||
_payload: SafariAuthRequest,
|
}
|
||||||
) -> crate::Result<SafariAuthResponse> {
|
|
||||||
|
pub fn auth_with_custom_tab(&self, _payload: AuthRequest) -> crate::Result<AuthResponse> {
|
||||||
Err(crate::Error::UnsupportedPlatformError)
|
Err(crate::Error::UnsupportedPlatformError)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ pub fn init<R: Runtime>() -> TauriPlugin<R> {
|
|||||||
Builder::new("native-bridge")
|
Builder::new("native-bridge")
|
||||||
.invoke_handler(tauri::generate_handler![
|
.invoke_handler(tauri::generate_handler![
|
||||||
commands::auth_with_safari,
|
commands::auth_with_safari,
|
||||||
|
commands::auth_with_custom_tab,
|
||||||
commands::copy_uri_to_path,
|
commands::copy_uri_to_path,
|
||||||
])
|
])
|
||||||
.setup(|app, api| {
|
.setup(|app, api| {
|
||||||
|
|||||||
@@ -25,16 +25,21 @@ pub fn init<R: Runtime, C: DeserializeOwned>(
|
|||||||
pub struct NativeBridge<R: Runtime>(PluginHandle<R>);
|
pub struct NativeBridge<R: Runtime>(PluginHandle<R>);
|
||||||
|
|
||||||
impl<R: Runtime> NativeBridge<R> {
|
impl<R: Runtime> NativeBridge<R> {
|
||||||
pub fn auth_with_safari(
|
pub fn auth_with_safari(&self, payload: AuthRequest) -> crate::Result<AuthResponse> {
|
||||||
&self,
|
|
||||||
payload: SafariAuthRequest,
|
|
||||||
) -> crate::Result<SafariAuthResponse> {
|
|
||||||
self.0
|
self.0
|
||||||
.run_mobile_plugin("auth_with_safari", payload)
|
.run_mobile_plugin("auth_with_safari", payload)
|
||||||
.map_err(Into::into)
|
.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> {
|
impl<R: Runtime> NativeBridge<R> {
|
||||||
pub fn copy_uri_to_path(&self, payload: CopyURIRequest) -> crate::Result<CopyURIResponse> {
|
pub fn copy_uri_to_path(&self, payload: CopyURIRequest) -> crate::Result<CopyURIResponse> {
|
||||||
self.0
|
self.0
|
||||||
|
|||||||
@@ -2,13 +2,13 @@ use serde::{Deserialize, Serialize};
|
|||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize)]
|
#[derive(Debug, Deserialize, Serialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct SafariAuthRequest {
|
pub struct AuthRequest {
|
||||||
pub auth_url: String,
|
pub auth_url: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default, Deserialize, Serialize)]
|
#[derive(Debug, Clone, Default, Deserialize, Serialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct SafariAuthResponse {
|
pub struct AuthResponse {
|
||||||
pub redirect_url: String,
|
pub redirect_url: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ import { start, cancel, onUrl, onInvalidUrl } from '@fabianlars/tauri-plugin-oau
|
|||||||
import { openUrl } from '@tauri-apps/plugin-opener';
|
import { openUrl } from '@tauri-apps/plugin-opener';
|
||||||
import { handleAuthCallback } from '@/helpers/auth';
|
import { handleAuthCallback } from '@/helpers/auth';
|
||||||
import { getAppleIdAuth, Scope } from './utils/appleIdAuth';
|
import { getAppleIdAuth, Scope } from './utils/appleIdAuth';
|
||||||
import { authWithSafari } from './utils/safariAuth';
|
import { authWithCustomTab, authWithSafari } from './utils/nativeAuth';
|
||||||
import { READEST_WEB_BASE_URL } from '@/services/constants';
|
import { READEST_WEB_BASE_URL } from '@/services/constants';
|
||||||
import WindowButtons from '@/components/WindowButtons';
|
import WindowButtons from '@/components/WindowButtons';
|
||||||
|
|
||||||
@@ -42,7 +42,7 @@ interface ProviderLoginProp {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const WEB_AUTH_CALLBACK = `${READEST_WEB_BASE_URL}/auth/callback`;
|
const WEB_AUTH_CALLBACK = `${READEST_WEB_BASE_URL}/auth/callback`;
|
||||||
const DEEPLINK_CALLBACK = 'readest://auth/callback';
|
const DEEPLINK_CALLBACK = 'readest://auth-callback';
|
||||||
|
|
||||||
const ProviderLogin: React.FC<ProviderLoginProp> = ({ provider, handleSignIn, Icon, label }) => {
|
const ProviderLogin: React.FC<ProviderLoginProp> = ({ provider, handleSignIn, Icon, label }) => {
|
||||||
return (
|
return (
|
||||||
@@ -75,10 +75,8 @@ export default function AuthPage() {
|
|||||||
|
|
||||||
const getTauriRedirectTo = (isOAuth: boolean) => {
|
const getTauriRedirectTo = (isOAuth: boolean) => {
|
||||||
if (process.env.NODE_ENV === 'production' || appService?.isMobile) {
|
if (process.env.NODE_ENV === 'production' || appService?.isMobile) {
|
||||||
if (appService?.isIOSApp) {
|
if (appService?.isMobile) {
|
||||||
return isOAuth ? DEEPLINK_CALLBACK : WEB_AUTH_CALLBACK;
|
return isOAuth ? DEEPLINK_CALLBACK : WEB_AUTH_CALLBACK;
|
||||||
} else if (appService?.isAndroidApp) {
|
|
||||||
return WEB_AUTH_CALLBACK;
|
|
||||||
}
|
}
|
||||||
return DEEPLINK_CALLBACK;
|
return DEEPLINK_CALLBACK;
|
||||||
}
|
}
|
||||||
@@ -135,6 +133,11 @@ export default function AuthPage() {
|
|||||||
if (res) {
|
if (res) {
|
||||||
handleOAuthUrl(res.redirectUrl);
|
handleOAuthUrl(res.redirectUrl);
|
||||||
}
|
}
|
||||||
|
} else if (appService?.isAndroidApp) {
|
||||||
|
const res = await authWithCustomTab({ authUrl: data.url });
|
||||||
|
if (res) {
|
||||||
|
handleOAuthUrl(res.redirectUrl);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
await openUrl(data.url);
|
await openUrl(data.url);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import { invoke } from '@tauri-apps/api/core';
|
||||||
|
|
||||||
|
export interface AuthRequest {
|
||||||
|
authUrl: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AuthResponse {
|
||||||
|
redirectUrl: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function authWithSafari(request: AuthRequest): Promise<AuthResponse> {
|
||||||
|
const result = await invoke<AuthResponse>('plugin:native-bridge|auth_with_safari', {
|
||||||
|
payload: request,
|
||||||
|
});
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function authWithCustomTab(request: AuthRequest): Promise<AuthResponse> {
|
||||||
|
const result = await invoke<AuthResponse>('plugin:native-bridge|auth_with_custom_tab', {
|
||||||
|
payload: request,
|
||||||
|
});
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
import { invoke } from '@tauri-apps/api/core';
|
|
||||||
|
|
||||||
export interface SafariAuthRequest {
|
|
||||||
authUrl: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SafariAuthResponse {
|
|
||||||
redirectUrl: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function authWithSafari(request: SafariAuthRequest): Promise<SafariAuthResponse> {
|
|
||||||
const result = await invoke<SafariAuthResponse>('plugin:native-bridge|auth_with_safari', {
|
|
||||||
payload: request,
|
|
||||||
});
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user