Skip to content

Commit 5c4b830

Browse files
authored
feat(api): add SERIALIZE_TO_IPC_FN const and implement it for dpi types, add more constructors (#11191)
1 parent cbc095e commit 5c4b830

File tree

15 files changed

+462
-205
lines changed

15 files changed

+462
-205
lines changed

.changes/api-dpi-toIPC.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
"@tauri-apps/api": "minor:feat"
3+
"tauri": "minor:feat"
4+
---
5+
6+
Improved support for `dpi` module types to allow these types to be used without manual conversions with `invoke`:
7+
8+
- Added `SERIALIZE_TO_IPC_FN` const in `core` module which can be used to implement custom IPC serialization for types passed to `invoke`.
9+
- Added `Size` and `Position` classes in `dpi` module.
10+
- Implementd `SERIALIZE_TO_IPC_FN` method on `PhysicalSize`, `PhysicalPosition`, `LogicalSize` and `LogicalPosition` to convert it into a valid IPC-compatible value that can be deserialized correctly on the Rust side into its equivalent struct.

.changes/tauri-toIPC copy.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"tauri": "minor:feat"
3+
---
4+
5+
Detect if `SERIALIZE_TO_IPC_FN`, const from the JS `core` module, is implemented on objects when serializing over IPC and use it.

crates/tauri/scripts/bundle.global.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/tauri/scripts/ipc.js

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,51 @@
8888
return
8989
}
9090

91+
// `postMessage` uses `structuredClone` to serialize the data before sending it
92+
// unlike `JSON.stringify`, we can't extend a type with a method similar to `toJSON`
93+
// so that `structuredClone` would use, so until https://github.com/whatwg/html/issues/7428
94+
// we manually call `toIPC`
95+
function serializeIpcPayload(data) {
96+
// if this value changes, make sure to update it in:
97+
// 1. process-ipc-message-fn.js
98+
// 2. core.ts
99+
const SERIALIZE_TO_IPC_FN = '__TAURI_TO_IPC_KEY__'
100+
101+
if (
102+
typeof data === 'object' &&
103+
data !== null &&
104+
'constructor' in data &&
105+
data.constructor === Array
106+
) {
107+
return data.map((v) => serializeIpcPayload(v))
108+
}
109+
110+
if (
111+
typeof data === 'object' &&
112+
data !== null &&
113+
SERIALIZE_TO_IPC_FN in data
114+
) {
115+
return data[SERIALIZE_TO_IPC_FN]()
116+
}
117+
118+
if (
119+
typeof data === 'object' &&
120+
data !== null &&
121+
'constructor' in data &&
122+
data.constructor === Object
123+
) {
124+
const acc = {}
125+
Object.entries(data).forEach(([k, v]) => {
126+
acc[k] = serializeIpcPayload(v)
127+
})
128+
return acc
129+
}
130+
131+
return data
132+
}
133+
134+
data.payload = serializeIpcPayload(data.payload)
135+
91136
isolation.frame.contentWindow.postMessage(
92137
data,
93138
'*' /* todo: set this to the secure origin */

crates/tauri/scripts/process-ipc-message-fn.js

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,21 +16,26 @@
1616
}
1717
} else {
1818
const data = JSON.stringify(message, (_k, val) => {
19+
// if this value changes, make sure to update it in:
20+
// 1. ipc.js
21+
// 2. core.ts
22+
const SERIALIZE_TO_IPC_FN = '__TAURI_TO_IPC_KEY__'
23+
1924
if (val instanceof Map) {
20-
let o = {}
21-
val.forEach((v, k) => (o[k] = v))
22-
return o
25+
return Object.fromEntries(val.entries())
2326
} else if (val instanceof Uint8Array) {
2427
return Array.from(val)
2528
} else if (val instanceof ArrayBuffer) {
2629
return Array.from(new Uint8Array(val))
27-
} else if (
30+
} else if (typeof val === "object" && val !== null && SERIALIZE_TO_IPC_FN in val) {
31+
return val[SERIALIZE_TO_IPC_FN]()
32+
} else if (
2833
val instanceof Object &&
2934
'__TAURI_CHANNEL_MARKER__' in val &&
3035
typeof val.id === 'number'
3136
) {
3237
return `__CHANNEL__:${val.id}`
33-
} else {
38+
} else {
3439
return val
3540
}
3641
})

crates/tauri/src/window/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2052,7 +2052,7 @@ tauri::Builder::default()
20522052
docsrs,
20532053
doc(cfg(any(target_os = "macos", target_os = "linux", windows)))
20542054
)]
2055-
#[derive(serde::Deserialize)]
2055+
#[derive(serde::Deserialize, Debug)]
20562056
pub struct ProgressBarState {
20572057
/// The progress bar status.
20582058
pub status: Option<ProgressBarStatus>,

packages/api/eslint.config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import prettierConfig from 'eslint-config-prettier'
88
import securityPlugin from 'eslint-plugin-security'
99
import tseslint from 'typescript-eslint'
1010

11-
/** @type {import('eslint').Linter.FlatConfig[]} */
11+
/** @type {import('eslint').Linter.Config} */
1212
export default [
1313
eslint.configs.recommended,
1414
prettierConfig,

packages/api/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,8 @@
4040
"npm-pack": "pnpm build && cd ./dist && npm pack",
4141
"npm-publish": "pnpm build && cd ./dist && pnpm publish --access public --loglevel silly --no-git-checks",
4242
"ts:check": "tsc --noEmit",
43-
"eslint:check": "eslint src/**.ts",
44-
"eslint:fix": "eslint src/**.ts --fix"
43+
"eslint:check": "eslint src/**/*.ts",
44+
"eslint:fix": "eslint src/**/*.ts --fix"
4545
},
4646
"devDependencies": {
4747
"@eslint/js": "^9.4.0",

packages/api/src/core.ts

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,56 @@
99
* @module
1010
*/
1111

12+
/**
13+
* A key to be used to implement a special function
14+
* on your types that define how your type should be serialized
15+
* when passing across the IPC.
16+
* @example
17+
* Given a type in Rust that looks like this
18+
* ```rs
19+
* #[derive(serde::Serialize, serde::Deserialize)
20+
* enum UserId {
21+
* String(String),
22+
* Number(u32),
23+
* }
24+
* ```
25+
* `UserId::String("id")` would be serialized into `{ String: "id" }`
26+
* and so we need to pass the same structure back to Rust
27+
* ```ts
28+
* import { SERIALIZE_TO_IPC_FN } from "@tauri-apps/api/core"
29+
*
30+
* class UserIdString {
31+
* id
32+
* constructor(id) {
33+
* this.id = id
34+
* }
35+
*
36+
* [SERIALIZE_TO_IPC_FN]() {
37+
* return { String: this.id }
38+
* }
39+
* }
40+
*
41+
* class UserIdNumber {
42+
* id
43+
* constructor(id) {
44+
* this.id = id
45+
* }
46+
*
47+
* [SERIALIZE_TO_IPC_FN]() {
48+
* return { Number: this.id }
49+
* }
50+
* }
51+
*
52+
*
53+
* type UserId = UserIdString | UserIdNumber
54+
* ```
55+
*
56+
*/
57+
// if this value changes, make sure to update it in:
58+
// 1. ipc.js
59+
// 2. process-ipc-message-fn.js
60+
export const SERIALIZE_TO_IPC_FN = '__TAURI_TO_IPC_KEY__'
61+
1262
/**
1363
* Transforms a callback function to a string identifier that can be passed to the backend.
1464
* The backend uses the identifier to `eval()` the callback.
@@ -80,9 +130,14 @@ class Channel<T = unknown> {
80130
return this.#onmessage
81131
}
82132

83-
toJSON(): string {
133+
[SERIALIZE_TO_IPC_FN]() {
84134
return `__CHANNEL__:${this.id}`
85135
}
136+
137+
toJSON(): string {
138+
// eslint-disable-next-line security/detect-object-injection
139+
return this[SERIALIZE_TO_IPC_FN]()
140+
}
86141
}
87142

88143
class PluginListener {

0 commit comments

Comments
 (0)