Skip to content

Commit a16796a

Browse files
fix(api): use array for channel queueing (#12069)
1 parent 231e9a5 commit a16796a

File tree

9 files changed

+59
-33
lines changed

9 files changed

+59
-33
lines changed

.changes/api-channel-miss-events.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@tauri-apps/api": "patch:bug"
3+
---
4+
5+
Fix `Channel` never calls `onmessage` in some cases

crates/tauri/scripts/bundle.global.js

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

examples/api/src-tauri/build.rs

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ fn main() {
1717
"log_operation",
1818
"perform_request",
1919
"echo",
20+
"spam",
2021
])),
2122
)
2223
.expect("failed to run tauri-build");

examples/api/src-tauri/capabilities/run-app.json

+1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
},
1717
"allow-perform-request",
1818
"allow-echo",
19+
"allow-spam",
1920
"app-menu:default",
2021
"sample:allow-ping-scoped",
2122
"sample:global-scope",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Automatically generated - DO NOT EDIT!
2+
3+
[[permission]]
4+
identifier = "allow-spam"
5+
description = "Enables the spam command without any pre-configured scope."
6+
commands.allow = ["spam"]
7+
8+
[[permission]]
9+
identifier = "deny-spam"
10+
description = "Denies the spam command without any pre-configured scope."
11+
commands.deny = ["spam"]

examples/api/src-tauri/src/cmd.rs

+14-3
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@
33
// SPDX-License-Identifier: MIT
44

55
use serde::{Deserialize, Serialize};
6-
use tauri::{command, ipc::CommandScope};
6+
use tauri::{
7+
command,
8+
ipc::{Channel, CommandScope},
9+
};
710

811
#[derive(Debug, Deserialize)]
912
#[allow(unused)]
@@ -28,7 +31,7 @@ pub fn log_operation(
2831
} else if !command_scope.allows().iter().any(|s| s.event == event) {
2932
Err("not allowed")
3033
} else {
31-
log::info!("{} {:?}", event, payload);
34+
log::info!("{event} {payload:?}");
3235
Ok(())
3336
}
3437
}
@@ -40,7 +43,7 @@ pub struct ApiResponse {
4043

4144
#[command]
4245
pub fn perform_request(endpoint: String, body: RequestBody) -> ApiResponse {
43-
println!("{} {:?}", endpoint, body);
46+
println!("{endpoint} {body:?}");
4447
ApiResponse {
4548
message: "message response".into(),
4649
}
@@ -50,3 +53,11 @@ pub fn perform_request(endpoint: String, body: RequestBody) -> ApiResponse {
5053
pub fn echo(request: tauri::ipc::Request<'_>) -> tauri::ipc::Response {
5154
tauri::ipc::Response::new(request.body().clone())
5255
}
56+
57+
#[command]
58+
pub fn spam(channel: Channel<i32>) -> tauri::Result<()> {
59+
for i in 1..=1_000 {
60+
channel.send(i)?;
61+
}
62+
Ok(())
63+
}

examples/api/src-tauri/src/lib.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,8 @@ pub fn run_app<R: Runtime, F: FnOnce(&App<R>) + Send + 'static>(
145145
.invoke_handler(tauri::generate_handler![
146146
cmd::log_operation,
147147
cmd::perform_request,
148-
cmd::echo
148+
cmd::echo,
149+
cmd::spam,
149150
])
150151
.build(tauri::tauri_build_context!())
151152
.expect("error while building tauri application");

examples/api/src/views/Communication.svelte

+8-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<script>
22
import { getCurrentWebviewWindow } from '@tauri-apps/api/webviewWindow'
3-
import { invoke } from '@tauri-apps/api/core'
3+
import { Channel, invoke } from '@tauri-apps/api/core'
44
import { onMount, onDestroy } from 'svelte'
55
66
export let onMessage
@@ -46,6 +46,12 @@
4646
invoke('echo', [1, 2, 3]).then(onMessage).catch(onMessage)
4747
}
4848
49+
function spam() {
50+
const channel = new Channel()
51+
channel.onmessage = onMessage
52+
invoke('spam', { channel })
53+
}
54+
4955
function emitEvent() {
5056
webviewWindow.emit('js-event', 'this is the payload string')
5157
}
@@ -60,4 +66,5 @@
6066
Send event to Rust
6167
</button>
6268
<button class="btn" id="request" on:click={echo}> Echo </button>
69+
<button class="btn" id="request" on:click={spam}> Spam </button>
6370
</div>

packages/api/src/core.ts

+16-27
Original file line numberDiff line numberDiff line change
@@ -81,42 +81,31 @@ class Channel<T = unknown> {
8181
#onmessage: (response: T) => void = () => {
8282
// no-op
8383
}
84+
// the id is used as a mechanism to preserve message order
8485
#nextMessageId = 0
85-
#pendingMessages: Record<string, T> = {}
86+
#pendingMessages: T[] = []
8687

8788
constructor() {
8889
this.id = transformCallback(
8990
({ message, id }: { message: T; id: number }) => {
90-
// the id is used as a mechanism to preserve message order
91-
if (id === this.#nextMessageId) {
92-
this.#nextMessageId = id + 1
91+
// Process the message if we're at the right order
92+
if (id == this.#nextMessageId) {
9393
this.#onmessage(message)
94+
this.#nextMessageId += 1
9495

9596
// process pending messages
96-
const pendingMessageIds = Object.keys(this.#pendingMessages)
97-
if (pendingMessageIds.length > 0) {
98-
let nextId = id + 1
99-
for (const pendingId of pendingMessageIds.sort()) {
100-
// if we have the next message, process it
101-
if (parseInt(pendingId) === nextId) {
102-
// eslint-disable-next-line security/detect-object-injection
103-
const message = this.#pendingMessages[pendingId]
104-
// eslint-disable-next-line security/detect-object-injection
105-
delete this.#pendingMessages[pendingId]
106-
107-
this.#onmessage(message)
108-
109-
// move the id counter to the next message to check
110-
nextId += 1
111-
} else {
112-
// we do not have the next message, let's wait
113-
break
114-
}
115-
}
116-
this.#nextMessageId = nextId
97+
while (this.#nextMessageId in this.#pendingMessages) {
98+
const message = this.#pendingMessages[this.#nextMessageId]
99+
this.#onmessage(message)
100+
// eslint-disable-next-line @typescript-eslint/no-array-delete
101+
delete this.#pendingMessages[this.#nextMessageId]
102+
this.#nextMessageId += 1
117103
}
118-
} else {
119-
this.#pendingMessages[id.toString()] = message
104+
}
105+
// Queue the message if we're not
106+
else {
107+
// eslint-disable-next-line security/detect-object-injection
108+
this.#pendingMessages[id] = message
120109
}
121110
}
122111
)

0 commit comments

Comments
 (0)