Skip to content

Commit 57612ab

Browse files
feat: add TSend generic to Channel (#10139)
* add TSend to Channel * add changeset * fix tray Channel * Update .changes/ipc-channel-generic.md --------- Co-authored-by: Lucas Fernandes Nogueira <lucas@tauri.app>
1 parent 15e1259 commit 57612ab

6 files changed

Lines changed: 37 additions & 16 deletions

File tree

.changes/ipc-channel-generic.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"tauri": major:breaking
3+
---
4+
5+
Add `TSend` generic to `ipc::Channel` for typesafe `send` calls and type inspection in `tauri-specta`

core/tauri/src/ipc/channel.rs

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,19 @@ pub struct ChannelDataIpcQueue(pub(crate) Arc<Mutex<HashMap<u32, InvokeBody>>>);
3737

3838
/// An IPC channel.
3939
#[derive(Clone)]
40-
pub struct Channel {
40+
pub struct Channel<TSend = InvokeBody> {
4141
id: u32,
4242
on_message: Arc<dyn Fn(InvokeBody) -> crate::Result<()> + Send + Sync>,
43+
phantom: std::marker::PhantomData<TSend>,
4344
}
4445

46+
#[cfg(feature = "specta")]
47+
const _: () = {
48+
#[derive(specta::Type)]
49+
#[specta(remote = Channel, rename = "TAURI_CHANNEL")]
50+
struct Channel<TSend>(std::marker::PhantomData<TSend>);
51+
};
52+
4553
impl Serialize for Channel {
4654
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
4755
where
@@ -88,7 +96,7 @@ impl FromStr for JavaScriptChannelId {
8896

8997
impl JavaScriptChannelId {
9098
/// Gets a [`Channel`] for this channel ID on the given [`Webview`].
91-
pub fn channel_on<R: Runtime>(&self, webview: Webview<R>) -> Channel {
99+
pub fn channel_on<R: Runtime, TSend>(&self, webview: Webview<R>) -> Channel<TSend> {
92100
let callback_id = self.0;
93101
let counter = AtomicUsize::new(0);
94102

@@ -128,7 +136,7 @@ impl<'de> Deserialize<'de> for JavaScriptChannelId {
128136
}
129137
}
130138

131-
impl Channel {
139+
impl<TSend> Channel<TSend> {
132140
/// Creates a new channel with the given message handler.
133141
pub fn new<F: Fn(InvokeBody) -> crate::Result<()> + Send + Sync + 'static>(
134142
on_message: F,
@@ -144,10 +152,15 @@ impl Channel {
144152
let channel = Self {
145153
id,
146154
on_message: Arc::new(on_message),
155+
phantom: Default::default(),
147156
};
148157

149158
#[cfg(mobile)]
150-
crate::plugin::mobile::register_channel(channel.clone());
159+
crate::plugin::mobile::register_channel(Channel {
160+
id,
161+
on_message: channel.on_message.clone(),
162+
phantom: Default::default(),
163+
});
151164

152165
channel
153166
}
@@ -178,13 +191,16 @@ impl Channel {
178191
}
179192

180193
/// Sends the given data through the channel.
181-
pub fn send<T: IpcResponse>(&self, data: T) -> crate::Result<()> {
194+
pub fn send(&self, data: TSend) -> crate::Result<()>
195+
where
196+
TSend: IpcResponse,
197+
{
182198
let body = data.body()?;
183199
(self.on_message)(body)
184200
}
185201
}
186202

187-
impl<'de, R: Runtime> CommandArg<'de, R> for Channel {
203+
impl<'de, R: Runtime, TSend: Clone> CommandArg<'de, R> for Channel<TSend> {
188204
/// Grabs the [`Webview`] from the [`CommandItem`] and returns the associated [`Channel`].
189205
fn from_command(command: CommandItem<'de, R>) -> Result<Self, InvokeError> {
190206
let name = command.name;
@@ -196,8 +212,8 @@ impl<'de, R: Runtime> CommandArg<'de, R> for Channel {
196212
.map(|id| id.channel_on(webview))
197213
.map_err(|_| {
198214
InvokeError::from_anyhow(anyhow::anyhow!(
199-
"invalid channel value `{value}`, expected a string in the `{IPC_PAYLOAD_PREFIX}ID` format"
200-
))
215+
"invalid channel value `{value}`, expected a string in the `{IPC_PAYLOAD_PREFIX}ID` format"
216+
))
201217
})
202218
}
203219
}

core/tauri/src/menu/plugin.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -350,7 +350,7 @@ fn new<R: Runtime>(
350350
kind: ItemKind,
351351
options: Option<NewOptions>,
352352
channels: State<'_, MenuChannels>,
353-
handler: Channel,
353+
handler: Channel<MenuId>,
354354
) -> crate::Result<(ResourceId, MenuId)> {
355355
let options = options.unwrap_or_default();
356356
let mut resources_table = app.resources_table();
@@ -866,7 +866,7 @@ fn set_icon<R: Runtime>(
866866
}
867867
}
868868

869-
struct MenuChannels(Mutex<HashMap<MenuId, Channel>>);
869+
struct MenuChannels(Mutex<HashMap<MenuId, Channel<MenuId>>>);
870870

871871
pub(crate) fn init<R: Runtime>() -> TauriPlugin<R> {
872872
Builder::new("menu")
@@ -877,7 +877,7 @@ pub(crate) fn init<R: Runtime>() -> TauriPlugin<R> {
877877
.on_event(|app, e| {
878878
if let RunEvent::MenuEvent(e) = e {
879879
if let Some(channel) = app.state::<MenuChannels>().0.lock().unwrap().get(&e.id) {
880-
let _ = channel.send(&e.id);
880+
let _ = channel.send(e.id.clone());
881881
}
882882
}
883883
})

core/tauri/src/plugin/mobile.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ type PendingPluginCallHandler = Box<dyn FnOnce(PluginResponse) + Send + 'static>
3030
static PENDING_PLUGIN_CALLS_ID: AtomicI32 = AtomicI32::new(0);
3131
static PENDING_PLUGIN_CALLS: OnceLock<Mutex<HashMap<i32, PendingPluginCallHandler>>> =
3232
OnceLock::new();
33-
static CHANNELS: OnceLock<Mutex<HashMap<u32, Channel>>> = OnceLock::new();
33+
static CHANNELS: OnceLock<Mutex<HashMap<u32, Channel<serde_json::Value>>>> = OnceLock::new();
3434

3535
/// Possible errors when invoking a plugin.
3636
#[derive(Debug, thiserror::Error)]
@@ -53,7 +53,7 @@ pub enum PluginInvokeError {
5353
CannotSerializePayload(serde_json::Error),
5454
}
5555

56-
pub(crate) fn register_channel(channel: Channel) {
56+
pub(crate) fn register_channel(channel: Channel<serde_json::Value>) {
5757
CHANNELS
5858
.get_or_init(Default::default)
5959
.lock()

core/tauri/src/tray/plugin.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ use crate::{
1717
AppHandle, Manager, Runtime, Webview,
1818
};
1919

20-
use super::TrayIcon;
20+
use super::{TrayIcon, TrayIconEvent};
2121

2222
#[derive(Deserialize)]
2323
#[serde(rename_all = "camelCase")]
@@ -36,7 +36,7 @@ struct TrayIconOptions {
3636
fn new<R: Runtime>(
3737
webview: Webview<R>,
3838
options: TrayIconOptions,
39-
handler: Channel,
39+
handler: Channel<TrayIconEvent>,
4040
) -> crate::Result<(ResourceId, String)> {
4141
let mut builder = if let Some(id) = options.id {
4242
TrayIconBuilder::<R>::with_id(id)

core/tauri/src/webview/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1268,7 +1268,7 @@ fn main() {
12681268
for v in map.values() {
12691269
if let serde_json::Value::String(s) = v {
12701270
let _ = crate::ipc::JavaScriptChannelId::from_str(s)
1271-
.map(|id| id.channel_on(webview.clone()));
1271+
.map(|id| id.channel_on::<R, ()>(webview.clone()));
12721272
}
12731273
}
12741274
}

0 commit comments

Comments
 (0)