Skip to content

Commit 0ab5f40

Browse files
authored
feat(core): add IPC channel (#6813)
1 parent b072daa commit 0ab5f40

File tree

18 files changed

+216
-184
lines changed

18 files changed

+216
-184
lines changed

.changes/channel-api.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"api": patch
3+
"tauri": patch
4+
---
5+
6+
Add channel API for sending data across the IPC.
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
2+
// SPDX-License-Identifier: Apache-2.0
3+
// SPDX-License-Identifier: MIT
4+
5+
package app.tauri.plugin
6+
7+
class Channel(val id: Long, private val handler: (data: JSObject) -> Unit) {
8+
fun send(data: JSObject) {
9+
handler(data)
10+
}
11+
}

core/tauri/mobile/android/src/main/java/app/tauri/plugin/Invoke.kt

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,23 @@ package app.tauri.plugin
66

77
import app.tauri.Logger
88

9+
const val CHANNEL_PREFIX = "__CHANNEL__:"
10+
911
class Invoke(
1012
val id: Long,
1113
val command: String,
12-
private val sendResponse: (success: PluginResult?, error: PluginResult?) -> Unit,
14+
val callback: Long,
15+
val error: Long,
16+
private val sendResponse: (callback: Long, data: PluginResult?) -> Unit,
1317
val data: JSObject) {
1418

1519
fun resolve(data: JSObject?) {
1620
val result = PluginResult(data)
17-
sendResponse(result, null)
21+
sendResponse(callback, result)
1822
}
1923

2024
fun resolve() {
21-
sendResponse(null, null)
25+
sendResponse(callback, null)
2226
}
2327

2428
fun reject(msg: String?, code: String?, ex: Exception?, data: JSObject?) {
@@ -35,7 +39,7 @@ class Invoke(
3539
} catch (jsonEx: Exception) {
3640
Logger.error(Logger.tags("Plugin"), jsonEx.message!!, jsonEx)
3741
}
38-
sendResponse(null, errorResult)
42+
sendResponse(error, errorResult)
3943
}
4044

4145
fun reject(msg: String?, ex: Exception?, data: JSObject?) {
@@ -197,4 +201,10 @@ class Invoke(
197201
fun hasOption(name: String): Boolean {
198202
return data.has(name)
199203
}
204+
205+
fun getChannel(name: String): Channel? {
206+
val channelDef = getString(name, "")
207+
val callback = channelDef.substring(CHANNEL_PREFIX.length).toLongOrNull() ?: return null
208+
return Channel(callback) { res -> sendResponse(callback, PluginResult(res)) }
209+
}
200210
}

core/tauri/mobile/android/src/main/java/app/tauri/plugin/Plugin.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ import app.tauri.Logger
1515
import app.tauri.PermissionHelper
1616
import app.tauri.PermissionState
1717
import app.tauri.annotation.ActivityCallback
18-
import app.tauri.annotation.PermissionCallback
1918
import app.tauri.annotation.Command
19+
import app.tauri.annotation.PermissionCallback
2020
import app.tauri.annotation.TauriPlugin
2121
import org.json.JSONException
2222
import java.util.*
@@ -126,7 +126,7 @@ abstract class Plugin(private val activity: Activity) {
126126

127127
// If call was made without any custom permissions, request all from plugin annotation
128128
val aliasSet: MutableSet<String> = HashSet()
129-
if (providedPermsList == null || providedPermsList.isEmpty()) {
129+
if (providedPermsList.isNullOrEmpty()) {
130130
for (perm in annotation.permissions) {
131131
// If a permission is defined with no permission strings, separate it for auto-granting.
132132
// Otherwise, the alias is added to the list to be requested.
@@ -153,7 +153,7 @@ abstract class Plugin(private val activity: Activity) {
153153
permAliases = aliasSet.toTypedArray()
154154
}
155155
}
156-
if (permAliases != null && permAliases.isNotEmpty()) {
156+
if (!permAliases.isNullOrEmpty()) {
157157
// request permissions using provided aliases or all defined on the plugin
158158
requestPermissionForAliases(permAliases, invoke, "checkPermissions")
159159
} else if (autoGrantPerms.isNotEmpty()) {

core/tauri/mobile/android/src/main/java/app/tauri/plugin/PluginManager.kt

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -87,11 +87,7 @@ class PluginManager(val activity: AppCompatActivity) {
8787

8888
@JniMethod
8989
fun postIpcMessage(webView: WebView, pluginId: String, command: String, data: JSObject, callback: Long, error: Long) {
90-
val invoke = Invoke(callback, command, { successResult, errorResult ->
91-
val (fn, result) = if (errorResult == null) Pair(callback, successResult) else Pair(
92-
error,
93-
errorResult
94-
)
90+
val invoke = Invoke(callback, command, callback, error, { fn, result ->
9591
webView.evaluateJavascript("window['_$fn']($result)", null)
9692
}, data)
9793

@@ -100,8 +96,17 @@ class PluginManager(val activity: AppCompatActivity) {
10096

10197
@JniMethod
10298
fun runCommand(id: Int, pluginId: String, command: String, data: JSObject) {
103-
val invoke = Invoke(id.toLong(), command, { successResult, errorResult ->
104-
handlePluginResponse(id, successResult?.toString(), errorResult?.toString())
99+
val successId = 0L
100+
val errorId = 1L
101+
val invoke = Invoke(id.toLong(), command, successId, errorId, { fn, result ->
102+
var success: PluginResult? = null
103+
var error: PluginResult? = null
104+
if (fn == successId) {
105+
success = result
106+
} else {
107+
error = result
108+
}
109+
handlePluginResponse(id, success?.toString(), error?.toString())
105110
}, data)
106111

107112
dispatchPluginMessage(invoke, pluginId)
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
2+
// SPDX-License-Identifier: Apache-2.0
3+
// SPDX-License-Identifier: MIT
4+
5+
public class Channel {
6+
var callback: UInt64
7+
var handler: (JsonValue) -> Void
8+
9+
public init(callback: UInt64, handler: @escaping (JsonValue) -> Void) {
10+
self.callback = callback
11+
self.handler = handler
12+
}
13+
14+
public func send(_ data: JsonObject) {
15+
handler(.dictionary(data))
16+
}
17+
}

core/tauri/mobile/ios-api/Sources/Tauri/Invoke.swift

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
import Foundation
66
import UIKit
77

8+
let CHANNEL_PREFIX = "__CHANNEL__:"
9+
810
@objc public class Invoke: NSObject, JSValueContainer, BridgedJSValueContainer {
911
public var dictionaryRepresentation: NSDictionary {
1012
return data as NSDictionary
@@ -15,25 +17,29 @@ import UIKit
1517
}()
1618

1719
public var command: String
20+
var callback: UInt64
21+
var error: UInt64
1822
public var data: JSObject
19-
var sendResponse: (JsonValue?, JsonValue?) -> Void
23+
var sendResponse: (UInt64, JsonValue?) -> Void
2024

21-
public init(command: String, sendResponse: @escaping (JsonValue?, JsonValue?) -> Void, data: JSObject?) {
25+
public init(command: String, callback: UInt64, error: UInt64, sendResponse: @escaping (UInt64, JsonValue?) -> Void, data: JSObject?) {
2226
self.command = command
27+
self.callback = callback
28+
self.error = error
2329
self.data = data ?? [:]
2430
self.sendResponse = sendResponse
2531
}
2632

2733
public func resolve() {
28-
sendResponse(nil, nil)
34+
sendResponse(callback, nil)
2935
}
3036

3137
public func resolve(_ data: JsonObject) {
3238
resolve(.dictionary(data))
3339
}
3440

3541
public func resolve(_ data: JsonValue) {
36-
sendResponse(data, nil)
42+
sendResponse(callback, data)
3743
}
3844

3945
public func reject(_ message: String, _ code: String? = nil, _ error: Error? = nil, _ data: JsonValue? = nil) {
@@ -46,22 +52,37 @@ import UIKit
4652
}
4753
}
4854
}
49-
sendResponse(nil, .dictionary(payload as! JsonObject))
55+
sendResponse(self.error, .dictionary(payload as! JsonObject))
5056
}
5157

5258
public func unimplemented() {
5359
unimplemented("not implemented")
5460
}
5561

5662
public func unimplemented(_ message: String) {
57-
sendResponse(nil, .dictionary(["message": message]))
63+
sendResponse(error, .dictionary(["message": message]))
5864
}
5965

6066
public func unavailable() {
6167
unavailable("not available")
6268
}
6369

6470
public func unavailable(_ message: String) {
65-
sendResponse(nil, .dictionary(["message": message]))
71+
sendResponse(error, .dictionary(["message": message]))
6672
}
73+
74+
public func getChannel(_ key: String) -> Channel? {
75+
let channelDef = getString(key, "")
76+
if channelDef.starts(with: CHANNEL_PREFIX) {
77+
let index = channelDef.index(channelDef.startIndex, offsetBy: CHANNEL_PREFIX.count)
78+
guard let callback = UInt64(channelDef[index...]) else {
79+
return nil
80+
}
81+
return Channel(callback: callback, handler: { (res: JsonValue) -> Void in
82+
self.sendResponse(callback, res)
83+
})
84+
} else {
85+
return nil
86+
}
87+
}
6788
}

core/tauri/mobile/ios-api/Sources/Tauri/Tauri.swift

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -109,9 +109,8 @@ func onWebviewCreated(webview: WKWebView, viewController: UIViewController) {
109109
}
110110

111111
@_cdecl("post_ipc_message")
112-
func postIpcMessage(webview: WKWebView, name: SRString, command: SRString, data: NSDictionary, callback: UInt, error: UInt) {
113-
let invoke = Invoke(command: command.toString(), sendResponse: { (successResult: JsonValue?, errorResult: JsonValue?) -> Void in
114-
let (fn, payload) = errorResult == nil ? (callback, successResult) : (error, errorResult)
112+
func postIpcMessage(webview: WKWebView, name: SRString, command: SRString, data: NSDictionary, callback: UInt64, error: UInt64) {
113+
let invoke = Invoke(command: command.toString(), callback: callback, error: error, sendResponse: { (fn: UInt64, payload: JsonValue?) -> Void in
115114
var payloadJson: String
116115
do {
117116
try payloadJson = payload == nil ? "null" : payload!.jsonRepresentation() ?? "`Failed to serialize payload`"
@@ -131,8 +130,10 @@ func runCommand(
131130
data: NSDictionary,
132131
callback: @escaping @convention(c) (Int, Bool, UnsafePointer<CChar>?) -> Void
133132
) {
134-
let invoke = Invoke(command: command.toString(), sendResponse: { (successResult: JsonValue?, errorResult: JsonValue?) -> Void in
135-
let (success, payload) = errorResult == nil ? (true, successResult) : (false, errorResult)
133+
let callbackId: UInt64 = 0
134+
let errorId: UInt64 = 1
135+
let invoke = Invoke(command: command.toString(), callback: callbackId, error: errorId, sendResponse: { (fn: UInt64, payload: JsonValue?) -> Void in
136+
let success = fn == callbackId
136137
var payloadJson: String = ""
137138
do {
138139
try payloadJson = payload == nil ? "null" : payload!.jsonRepresentation() ?? "`Failed to serialize payload`"

core/tauri/scripts/bundle.global.js

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

core/tauri/scripts/stringify-ipc-message-fn.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
let o = {};
99
val.forEach((v, k) => o[k] = v);
1010
return o;
11+
} else if (val instanceof Object && '__TAURI_CHANNEL_MARKER__' in val && typeof val.id === 'number') {
12+
return `__CHANNEL__:${val.id}`
1113
} else {
1214
return val;
1315
}

0 commit comments

Comments
 (0)