feat(opener): add inAppBrowser option for iOS and Android (#2803)

This commit is contained in:
Lucas Fernandes Nogueira
2025-06-24 16:43:01 -03:00
committed by GitHub
parent 9799f0dbab
commit 2aec8ff4c4
8 changed files with 69 additions and 18 deletions
+6
View File
@@ -0,0 +1,6 @@
---
"opener": patch:feat
"opener-js": patch:feat
---
Add `inAppBrowser` option to open URLs in an in-app browser on Android and iOS.
@@ -83,6 +83,15 @@
}, },
"store:default", "store:default",
"opener:default", "opener:default",
{
"identifier": "opener:allow-open-url",
"allow": [
{
"url": "https://*",
"app": "inAppBrowser"
}
]
},
{ {
"identifier": "opener:allow-open-path", "identifier": "opener:allow-open-path",
"allow": [{ "path": "$APPDATA" }, { "path": "$APPDATA/**" }] "allow": [{ "path": "$APPDATA" }, { "path": "$APPDATA/**" }]
+4 -2
View File
@@ -1,10 +1,12 @@
<script> <script>
import * as opener from '@tauri-apps/plugin-opener' import * as opener from '@tauri-apps/plugin-opener'
import { platform } from '@tauri-apps/plugin-os'
export let onMessage export let onMessage
let url = '' let url = 'https://tauri.app'
let urlProgram = '' let urlProgram =
platform() === 'ios' || platform() === 'android' ? 'inAppBrowser' : ''
function openUrl() { function openUrl() {
opener.openUrl(url, urlProgram ? urlProgram : undefined).catch(onMessage) opener.openUrl(url, urlProgram ? urlProgram : undefined).catch(onMessage)
} }
+1
View File
@@ -35,5 +35,6 @@ android {
dependencies { dependencies {
implementation("androidx.core:core-ktx:1.9.0") implementation("androidx.core:core-ktx:1.9.0")
implementation("com.fasterxml.jackson.core:jackson-databind:2.15.3") implementation("com.fasterxml.jackson.core:jackson-databind:2.15.3")
implementation("androidx.browser:browser:1.8.0")
implementation(project(":tauri-android")) implementation(project(":tauri-android"))
} }
@@ -6,22 +6,36 @@ package app.tauri.opener
import android.app.Activity import android.app.Activity
import android.content.Intent import android.content.Intent
import android.net.Uri import androidx.browser.customtabs.CustomTabsIntent
import app.tauri.annotation.Command import app.tauri.annotation.Command
import app.tauri.annotation.TauriPlugin import app.tauri.annotation.TauriPlugin
import app.tauri.plugin.Invoke import app.tauri.plugin.Invoke
import app.tauri.plugin.Plugin import app.tauri.plugin.Plugin
import java.io.File import androidx.core.net.toUri
import app.tauri.annotation.InvokeArg
@InvokeArg
class OpenArgs {
lateinit var url: String
var with: String? = null
}
@TauriPlugin @TauriPlugin
class OpenerPlugin(private val activity: Activity) : Plugin(activity) { class OpenerPlugin(private val activity: Activity) : Plugin(activity) {
@Command @Command
fun open(invoke: Invoke) { fun open(invoke: Invoke) {
try { try {
val url = invoke.parseArgs(String::class.java) val args = invoke.parseArgs(OpenArgs::class.java)
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); if (args.with == "inAppBrowser") {
activity.applicationContext?.startActivity(intent) val builder = CustomTabsIntent.Builder()
val intent = builder.build()
intent.launchUrl(activity, args.url.toUri())
} else {
val intent = Intent(Intent.ACTION_VIEW, args.url.toUri())
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
activity.applicationContext?.startActivity(intent)
}
invoke.resolve() invoke.resolve()
} catch (ex: Exception) { } catch (ex: Exception) {
invoke.reject(ex.message) invoke.reject(ex.message)
+3 -1
View File
@@ -35,12 +35,14 @@ import { invoke } from '@tauri-apps/api/core'
* *
* @param url The URL to open. * @param url The URL to open.
* @param openWith The app to open the URL with. If not specified, defaults to the system default application for the specified url type. * @param openWith The app to open the URL with. If not specified, defaults to the system default application for the specified url type.
* On mobile, `openWith` can be provided as `inAppBrowser` to open the URL in an in-app browser. Otherwise, it will open the URL in the system default browser.
* *
* @since 2.0.0 * @since 2.0.0
*/ */
export async function openUrl( export async function openUrl(
url: string | URL, url: string | URL,
openWith?: string // eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents
openWith?: 'inAppBrowser' | string
): Promise<void> { ): Promise<void> {
await invoke('plugin:opener|open_url', { await invoke('plugin:opener|open_url', {
url, url,
+19 -5
View File
@@ -3,21 +3,35 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
import Foundation import Foundation
import SafariServices
import SwiftRs import SwiftRs
import Tauri import Tauri
import UIKit import UIKit
import WebKit import WebKit
struct OpenArgs: Decodable {
let url: String
let with: String?
}
class OpenerPlugin: Plugin { class OpenerPlugin: Plugin {
@objc public func open(_ invoke: Invoke) throws { @objc public func open(_ invoke: Invoke) throws {
do { do {
let urlString = try invoke.parseArgs(String.self) let args = try invoke.parseArgs(OpenArgs.self)
if let url = URL(string: urlString) { if let url = URL(string: args.url) {
if #available(iOS 10, *) { if args.with == "inAppBrowser" {
UIApplication.shared.open(url, options: [:]) DispatchQueue.main.async {
let safariVC = SFSafariViewController(url: url)
self.manager.viewController?.present(safariVC, animated: true)
}
} else { } else {
UIApplication.shared.openURL(url) if #available(iOS 10, *) {
UIApplication.shared.open(url, options: [:])
} else {
UIApplication.shared.openURL(url)
}
} }
} }
invoke.resolve() invoke.resolve()
} catch { } catch {
+7 -4
View File
@@ -55,7 +55,7 @@ impl<R: Runtime> Opener<R> {
/// ///
/// ## Platform-specific: /// ## Platform-specific:
/// ///
/// - **Android / iOS**: Always opens using default program. /// - **Android / iOS**: Always opens using default program, unless `with` is provided as "inAppBrowser".
#[cfg(desktop)] #[cfg(desktop)]
pub fn open_url(&self, url: impl Into<String>, with: Option<impl Into<String>>) -> Result<()> { pub fn open_url(&self, url: impl Into<String>, with: Option<impl Into<String>>) -> Result<()> {
crate::open::open(url.into(), with.map(Into::into)) crate::open::open(url.into(), with.map(Into::into))
@@ -78,11 +78,14 @@ impl<R: Runtime> Opener<R> {
/// ///
/// ## Platform-specific: /// ## Platform-specific:
/// ///
/// - **Android / iOS**: Always opens using default program. /// - **Android / iOS**: Always opens using default program, unless `with` is provided as "inAppBrowser".
#[cfg(mobile)] #[cfg(mobile)]
pub fn open_url(&self, url: impl Into<String>, _with: Option<impl Into<String>>) -> Result<()> { pub fn open_url(&self, url: impl Into<String>, with: Option<impl Into<String>>) -> Result<()> {
self.mobile_plugin_handle self.mobile_plugin_handle
.run_mobile_plugin("open", url.into()) .run_mobile_plugin(
"open",
serde_json::json!({ "url": url.into(), "with": with.map(Into::into) }),
)
.map_err(Into::into) .map_err(Into::into)
} }