Skip to content

Commit

Permalink
feat(api): add SERIALIZE_TO_IPC_FN const and implement it for dpi t…
Browse files Browse the repository at this point in the history
…ypes, add more constructors (#11191)
  • Loading branch information
amrbashir authored Nov 5, 2024
1 parent cbc095e commit 5c4b830
Show file tree
Hide file tree
Showing 15 changed files with 462 additions and 205 deletions.
10 changes: 10 additions & 0 deletions .changes/api-dpi-toIPC.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
"@tauri-apps/api": "minor:feat"
"tauri": "minor:feat"
---

Improved support for `dpi` module types to allow these types to be used without manual conversions with `invoke`:

- Added `SERIALIZE_TO_IPC_FN` const in `core` module which can be used to implement custom IPC serialization for types passed to `invoke`.
- Added `Size` and `Position` classes in `dpi` module.
- 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.
5 changes: 5 additions & 0 deletions .changes/tauri-toIPC copy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"tauri": "minor:feat"
---

Detect if `SERIALIZE_TO_IPC_FN`, const from the JS `core` module, is implemented on objects when serializing over IPC and use it.
2 changes: 1 addition & 1 deletion crates/tauri/scripts/bundle.global.js

Large diffs are not rendered by default.

45 changes: 45 additions & 0 deletions crates/tauri/scripts/ipc.js
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,51 @@
return
}

// `postMessage` uses `structuredClone` to serialize the data before sending it
// unlike `JSON.stringify`, we can't extend a type with a method similar to `toJSON`
// so that `structuredClone` would use, so until https://github.com/whatwg/html/issues/7428
// we manually call `toIPC`
function serializeIpcPayload(data) {
// if this value changes, make sure to update it in:
// 1. process-ipc-message-fn.js
// 2. core.ts
const SERIALIZE_TO_IPC_FN = '__TAURI_TO_IPC_KEY__'

if (
typeof data === 'object' &&
data !== null &&
'constructor' in data &&
data.constructor === Array
) {
return data.map((v) => serializeIpcPayload(v))
}

if (
typeof data === 'object' &&
data !== null &&
SERIALIZE_TO_IPC_FN in data
) {
return data[SERIALIZE_TO_IPC_FN]()
}

if (
typeof data === 'object' &&
data !== null &&
'constructor' in data &&
data.constructor === Object
) {
const acc = {}
Object.entries(data).forEach(([k, v]) => {
acc[k] = serializeIpcPayload(v)
})
return acc
}

return data
}

data.payload = serializeIpcPayload(data.payload)

isolation.frame.contentWindow.postMessage(
data,
'*' /* todo: set this to the secure origin */
Expand Down
15 changes: 10 additions & 5 deletions crates/tauri/scripts/process-ipc-message-fn.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,21 +16,26 @@
}
} else {
const data = JSON.stringify(message, (_k, val) => {
// if this value changes, make sure to update it in:
// 1. ipc.js
// 2. core.ts
const SERIALIZE_TO_IPC_FN = '__TAURI_TO_IPC_KEY__'

if (val instanceof Map) {
let o = {}
val.forEach((v, k) => (o[k] = v))
return o
return Object.fromEntries(val.entries())
} else if (val instanceof Uint8Array) {
return Array.from(val)
} else if (val instanceof ArrayBuffer) {
return Array.from(new Uint8Array(val))
} else if (
} else if (typeof val === "object" && val !== null && SERIALIZE_TO_IPC_FN in val) {
return val[SERIALIZE_TO_IPC_FN]()
} else if (
val instanceof Object &&
'__TAURI_CHANNEL_MARKER__' in val &&
typeof val.id === 'number'
) {
return `__CHANNEL__:${val.id}`
} else {
} else {
return val
}
})
Expand Down
2 changes: 1 addition & 1 deletion crates/tauri/src/window/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2052,7 +2052,7 @@ tauri::Builder::default()
docsrs,
doc(cfg(any(target_os = "macos", target_os = "linux", windows)))
)]
#[derive(serde::Deserialize)]
#[derive(serde::Deserialize, Debug)]
pub struct ProgressBarState {
/// The progress bar status.
pub status: Option<ProgressBarStatus>,
Expand Down
2 changes: 1 addition & 1 deletion packages/api/eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import prettierConfig from 'eslint-config-prettier'
import securityPlugin from 'eslint-plugin-security'
import tseslint from 'typescript-eslint'

/** @type {import('eslint').Linter.FlatConfig[]} */
/** @type {import('eslint').Linter.Config} */
export default [
eslint.configs.recommended,
prettierConfig,
Expand Down
4 changes: 2 additions & 2 deletions packages/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@
"npm-pack": "pnpm build && cd ./dist && npm pack",
"npm-publish": "pnpm build && cd ./dist && pnpm publish --access public --loglevel silly --no-git-checks",
"ts:check": "tsc --noEmit",
"eslint:check": "eslint src/**.ts",
"eslint:fix": "eslint src/**.ts --fix"
"eslint:check": "eslint src/**/*.ts",
"eslint:fix": "eslint src/**/*.ts --fix"
},
"devDependencies": {
"@eslint/js": "^9.4.0",
Expand Down
57 changes: 56 additions & 1 deletion packages/api/src/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,56 @@
* @module
*/

/**
* A key to be used to implement a special function
* on your types that define how your type should be serialized
* when passing across the IPC.
* @example
* Given a type in Rust that looks like this
* ```rs
* #[derive(serde::Serialize, serde::Deserialize)
* enum UserId {
* String(String),
* Number(u32),
* }
* ```
* `UserId::String("id")` would be serialized into `{ String: "id" }`
* and so we need to pass the same structure back to Rust
* ```ts
* import { SERIALIZE_TO_IPC_FN } from "@tauri-apps/api/core"
*
* class UserIdString {
* id
* constructor(id) {
* this.id = id
* }
*
* [SERIALIZE_TO_IPC_FN]() {
* return { String: this.id }
* }
* }
*
* class UserIdNumber {
* id
* constructor(id) {
* this.id = id
* }
*
* [SERIALIZE_TO_IPC_FN]() {
* return { Number: this.id }
* }
* }
*
*
* type UserId = UserIdString | UserIdNumber
* ```
*
*/
// if this value changes, make sure to update it in:
// 1. ipc.js
// 2. process-ipc-message-fn.js
export const SERIALIZE_TO_IPC_FN = '__TAURI_TO_IPC_KEY__'

/**
* Transforms a callback function to a string identifier that can be passed to the backend.
* The backend uses the identifier to `eval()` the callback.
Expand Down Expand Up @@ -80,9 +130,14 @@ class Channel<T = unknown> {
return this.#onmessage
}

toJSON(): string {
[SERIALIZE_TO_IPC_FN]() {
return `__CHANNEL__:${this.id}`
}

toJSON(): string {
// eslint-disable-next-line security/detect-object-injection
return this[SERIALIZE_TO_IPC_FN]()
}
}

class PluginListener {
Expand Down
Loading

0 comments on commit 5c4b830

Please sign in to comment.