fix(api): use array for channel queueing (#12069)

This commit is contained in:
Tony 2025-01-02 19:19:50 +08:00 committed by GitHub
parent 231e9a5ee1
commit a16796a555
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 59 additions and 33 deletions

View File

@ -0,0 +1,5 @@
---
"@tauri-apps/api": "patch:bug"
---
Fix `Channel` never calls `onmessage` in some cases

File diff suppressed because one or more lines are too long

View File

@ -17,6 +17,7 @@ fn main() {
"log_operation",
"perform_request",
"echo",
"spam",
])),
)
.expect("failed to run tauri-build");

View File

@ -16,6 +16,7 @@
},
"allow-perform-request",
"allow-echo",
"allow-spam",
"app-menu:default",
"sample:allow-ping-scoped",
"sample:global-scope",

View File

@ -0,0 +1,11 @@
# Automatically generated - DO NOT EDIT!
[[permission]]
identifier = "allow-spam"
description = "Enables the spam command without any pre-configured scope."
commands.allow = ["spam"]
[[permission]]
identifier = "deny-spam"
description = "Denies the spam command without any pre-configured scope."
commands.deny = ["spam"]

View File

@ -3,7 +3,10 @@
// SPDX-License-Identifier: MIT
use serde::{Deserialize, Serialize};
use tauri::{command, ipc::CommandScope};
use tauri::{
command,
ipc::{Channel, CommandScope},
};
#[derive(Debug, Deserialize)]
#[allow(unused)]
@ -28,7 +31,7 @@ pub fn log_operation(
} else if !command_scope.allows().iter().any(|s| s.event == event) {
Err("not allowed")
} else {
log::info!("{} {:?}", event, payload);
log::info!("{event} {payload:?}");
Ok(())
}
}
@ -40,7 +43,7 @@ pub struct ApiResponse {
#[command]
pub fn perform_request(endpoint: String, body: RequestBody) -> ApiResponse {
println!("{} {:?}", endpoint, body);
println!("{endpoint} {body:?}");
ApiResponse {
message: "message response".into(),
}
@ -50,3 +53,11 @@ pub fn perform_request(endpoint: String, body: RequestBody) -> ApiResponse {
pub fn echo(request: tauri::ipc::Request<'_>) -> tauri::ipc::Response {
tauri::ipc::Response::new(request.body().clone())
}
#[command]
pub fn spam(channel: Channel<i32>) -> tauri::Result<()> {
for i in 1..=1_000 {
channel.send(i)?;
}
Ok(())
}

View File

@ -145,7 +145,8 @@ pub fn run_app<R: Runtime, F: FnOnce(&App<R>) + Send + 'static>(
.invoke_handler(tauri::generate_handler![
cmd::log_operation,
cmd::perform_request,
cmd::echo
cmd::echo,
cmd::spam,
])
.build(tauri::tauri_build_context!())
.expect("error while building tauri application");

View File

@ -1,6 +1,6 @@
<script>
import { getCurrentWebviewWindow } from '@tauri-apps/api/webviewWindow'
import { invoke } from '@tauri-apps/api/core'
import { Channel, invoke } from '@tauri-apps/api/core'
import { onMount, onDestroy } from 'svelte'
export let onMessage
@ -46,6 +46,12 @@
invoke('echo', [1, 2, 3]).then(onMessage).catch(onMessage)
}
function spam() {
const channel = new Channel()
channel.onmessage = onMessage
invoke('spam', { channel })
}
function emitEvent() {
webviewWindow.emit('js-event', 'this is the payload string')
}
@ -60,4 +66,5 @@
Send event to Rust
</button>
<button class="btn" id="request" on:click={echo}> Echo </button>
<button class="btn" id="request" on:click={spam}> Spam </button>
</div>

View File

@ -81,42 +81,31 @@ class Channel<T = unknown> {
#onmessage: (response: T) => void = () => {
// no-op
}
// the id is used as a mechanism to preserve message order
#nextMessageId = 0
#pendingMessages: Record<string, T> = {}
#pendingMessages: T[] = []
constructor() {
this.id = transformCallback(
({ message, id }: { message: T; id: number }) => {
// the id is used as a mechanism to preserve message order
if (id === this.#nextMessageId) {
this.#nextMessageId = id + 1
// Process the message if we're at the right order
if (id == this.#nextMessageId) {
this.#onmessage(message)
this.#nextMessageId += 1
// process pending messages
const pendingMessageIds = Object.keys(this.#pendingMessages)
if (pendingMessageIds.length > 0) {
let nextId = id + 1
for (const pendingId of pendingMessageIds.sort()) {
// if we have the next message, process it
if (parseInt(pendingId) === nextId) {
// eslint-disable-next-line security/detect-object-injection
const message = this.#pendingMessages[pendingId]
// eslint-disable-next-line security/detect-object-injection
delete this.#pendingMessages[pendingId]
this.#onmessage(message)
// move the id counter to the next message to check
nextId += 1
} else {
// we do not have the next message, let's wait
break
}
}
this.#nextMessageId = nextId
while (this.#nextMessageId in this.#pendingMessages) {
const message = this.#pendingMessages[this.#nextMessageId]
this.#onmessage(message)
// eslint-disable-next-line @typescript-eslint/no-array-delete
delete this.#pendingMessages[this.#nextMessageId]
this.#nextMessageId += 1
}
} else {
this.#pendingMessages[id.toString()] = message
}
// Queue the message if we're not
else {
// eslint-disable-next-line security/detect-object-injection
this.#pendingMessages[id] = message
}
}
)