Compare commits

..

25 Commits

Author SHA1 Message Date
FabianLars 15fc071773 add corenfc framework 2025-08-11 22:21:03 +02:00
FabianLars 73c6047b78 fix(nfc): use xcode project instead of swift package 2025-08-11 22:08:58 +02:00
Tony a4b71a1992 refactor(opener): rename to FailedToConvertPathToItemIdList (#2906) 2025-08-10 18:52:54 +08:00
Tony 4eb36b0ff5 fix(fs): writeFile with ReadableStream throws (#2907) 2025-08-10 18:44:33 +08:00
Petr b8056f484c feat(opener): reveal multiple items in dir (#2897)
* feature: reveal multiple items in dir

* feature: reveal multiple items in dir

* feature: reveal multiple items in dir

* feature: reveal multiple items in dir

* feature: reveal multiple items in dir

* feature: reveal multiple items in dir

* feature: reveal multiple items in dir

* Support multiple roots on Windows

* feature: reveal multiple items in dir

* feature: reveal multiple items in dir

* feature: reveal multiple items in dir

* feature: reveal multiple items in dir

---------

Co-authored-by: Tony <legendmastertony@gmail.com>
2025-08-09 09:06:56 +08:00
Tony 5ac8fbb1fa feat(store): load override defaults (#2857)
* feat(store): load override defaults

* Update docs

* Update example

* Allow setting defaults from js

* Tweak resolve

* Merge remote-tracking branch 'upstream/v2' into store-load-override-defaults

* Merge branch 'v2' of https://github.com/tauri-apps/plugins-workspace into store-load-override-defaults

* Merge branch 'v2' into store-load-override-defaults

* Rename to ignore defaults

* Merge remote-tracking branch 'upstream/v2' into store-load-override-defaults
2025-08-05 18:45:09 +08:00
renovate[bot] e0323ec752 chore(deps): update dependency typescript-eslint to v8.39.0 (#2894)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-05 10:08:29 +08:00
Fabian-Lars cc98e6a892 docs: Remove mirror install instructions (#2893) 2025-08-04 12:30:18 +02:00
renovate[bot] af22ae0a97 chore(deps): update rust crate notify-debouncer-full to 0.6 (v2) (#2889)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Fabian-Lars <github@fabianlars.de>
2025-08-03 22:10:31 +02:00
Fabian-Lars 2f24c7a70c chore(deps): Update rand to 0.9 (#2367) 2025-08-03 21:22:23 +02:00
Ray fe23a5e013 fix(nfc): Ensure that Session is dropped when an error causes it to become invalid (#2885)
Co-authored-by: Fabian-Lars <github@fabianlars.de>
2025-08-01 20:49:49 +02:00
renovate[bot] ff6d23ede1 chore(deps): update dependency typescript to v5.9.2 (#2886)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-01 11:04:56 +08:00
renovate[bot] 449dd117a4 chore(deps): update dependency rollup to v4.46.2 (#2882)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-30 13:52:44 +08:00
github-actions[bot] 9b43f48856 publish new versions (#2880)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-07-28 17:14:41 +02:00
Fabian-Lars af08c66faa fix(dialog): remove use of ACTION_PICK (#2871) 2025-07-28 16:43:35 +02:00
renovate[bot] 7974acae22 chore(deps): update dependency rollup to v4.46.1 (#2878)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-28 15:19:17 +08:00
renovate[bot] a985359e69 chore(deps): update dependency rollup to v4.46.0 (#2876)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-27 20:39:56 +08:00
renovate[bot] 97bebcf6e8 chore(deps): update eslint monorepo to v9.32.0 (#2873)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-26 19:50:08 +08:00
github-actions[bot] 27ddcd0abe publish new versions (#2869)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-07-22 23:32:16 +02:00
Fabian-Lars d7fb5623d6 docs(deep-link): update platform support wording
ref https://github.com/tauri-apps/tauri/issues/13877
2025-07-22 23:27:41 +02:00
yobson1 d4f8299b12 fix(deep-link): handler not set as default on linux (#2844)
Co-authored-by: Fabian-Lars <github@fabianlars.de>
2025-07-22 11:54:12 +02:00
Fabian-Lars 341919ed57 docs(shell): Remove left over tauri.conf.json > scope mentino 2025-07-22 09:58:00 +02:00
renovate[bot] 124f2191aa chore(deps): update dependency @tauri-apps/cli to v2.7.1 (#2867)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-22 14:50:50 +08:00
renovate[bot] 0970b94949 chore(deps): update dependency typescript-eslint to v8.38.0 (#2864)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-22 14:40:05 +08:00
renovate[bot] 7340242d4e chore(deps): update tauri monorepo to v2.7.0 (#2863)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-21 11:35:10 +02:00
90 changed files with 1810 additions and 906 deletions
+6
View File
@@ -0,0 +1,6 @@
---
fs: patch
fs-js: patch
---
Fixed calling `writeFile` with `data: ReadableStream` throws `Invalid argument`
+6
View File
@@ -0,0 +1,6 @@
---
nfc: patch
nfc-js: patch
---
On iOS, the reader session will now get closed properly on errors, preventing dangling invalid sessions that could prevent subsequent write attempts.
+6
View File
@@ -0,0 +1,6 @@
---
"opener": 'minor:enhance'
"opener-js": 'minor:enhance'
---
Allow reveal multiple items in the file explorer.
+6
View File
@@ -0,0 +1,6 @@
---
store: minor
store-js: minor
---
Allow setting defaults from the JavaScript API
+6
View File
@@ -0,0 +1,6 @@
---
store: minor
store-js: minor
---
Add an new option `overrideDefaults` for creating/loading and reloading the store that overrides the store with the on-disk state, ignoring defaults
Generated
+19 -20
View File
@@ -207,7 +207,7 @@ checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487"
[[package]]
name = "api"
version = "2.0.31"
version = "2.0.32"
dependencies = [
"log",
"serde",
@@ -1910,11 +1910,11 @@ dependencies = [
[[package]]
name = "file-id"
version = "0.2.2"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6bc904b9bbefcadbd8e3a9fb0d464a9b979de6324c03b3c663e8994f46a5be36"
checksum = "e1fc6a637b6dc58414714eddd9170ff187ecb0933d4c7024d1abbd23a3cc26e9"
dependencies = [
"windows-sys 0.52.0",
"windows-sys 0.60.2",
]
[[package]]
@@ -3243,9 +3243,9 @@ dependencies = [
[[package]]
name = "kqueue"
version = "1.0.8"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7447f1ca1b7b563588a205fe93dea8df60fd981423a768bc1c0ded35ed147d0c"
checksum = "eac30106d7dce88daf4a3fcb4879ea939476d5074a9b7ddd0fb97fa4bed5596a"
dependencies = [
"kqueue-sys",
"libc",
@@ -3720,12 +3720,11 @@ dependencies = [
[[package]]
name = "notify"
version = "8.0.0"
version = "8.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2fee8403b3d66ac7b26aee6e40a897d85dc5ce26f44da36b8b73e987cc52e943"
checksum = "4d3d07927151ff8575b7087f245456e549fea62edf0ec4e565a5ee50c8402bc3"
dependencies = [
"bitflags 2.9.0",
"filetime",
"fsevent-sys",
"inotify",
"kqueue",
@@ -3734,14 +3733,14 @@ dependencies = [
"mio",
"notify-types",
"walkdir",
"windows-sys 0.59.0",
"windows-sys 0.60.2",
]
[[package]]
name = "notify-debouncer-full"
version = "0.5.0"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2d88b1a7538054351c8258338df7c931a590513fb3745e8c15eb9ff4199b8d1"
checksum = "375bd3a138be7bfeff3480e4a623df4cbfb55b79df617c055cd810ba466fa078"
dependencies = [
"file-id",
"log",
@@ -6536,7 +6535,7 @@ dependencies = [
[[package]]
name = "tauri-plugin-deep-link"
version = "2.4.0"
version = "2.4.1"
dependencies = [
"dunce",
"rust-ini",
@@ -6554,7 +6553,7 @@ dependencies = [
[[package]]
name = "tauri-plugin-dialog"
version = "2.3.1"
version = "2.3.2"
dependencies = [
"log",
"raw-window-handle",
@@ -6708,7 +6707,7 @@ dependencies = [
"log",
"maplit",
"notify-rust",
"rand 0.8.5",
"rand 0.9.0",
"serde",
"serde_json",
"serde_repr",
@@ -6813,7 +6812,7 @@ dependencies = [
[[package]]
name = "tauri-plugin-single-instance"
version = "2.3.1"
version = "2.3.2"
dependencies = [
"semver",
"serde",
@@ -6865,9 +6864,9 @@ dependencies = [
"iota-crypto",
"iota_stronghold",
"log",
"rand 0.8.5",
"rand_chacha 0.3.1",
"rand_core 0.6.4",
"rand 0.9.0",
"rand_chacha 0.9.0",
"rand_core 0.9.3",
"rust-argon2 2.1.0",
"rusty-fork",
"serde",
@@ -6933,7 +6932,7 @@ dependencies = [
"futures-util",
"http",
"log",
"rand 0.8.5",
"rand 0.9.0",
"serde",
"serde_json",
"tauri",
+6
View File
@@ -1,5 +1,11 @@
# Changelog
## \[2.0.28]
### Dependencies
- Upgraded to `dialog-js@2.3.2`
## \[2.0.27]
### Dependencies
+4 -4
View File
@@ -1,7 +1,7 @@
{
"name": "api",
"private": true,
"version": "2.0.27",
"version": "2.0.28",
"type": "module",
"scripts": {
"dev": "vite --clearScreen false",
@@ -10,12 +10,12 @@
"tauri": "tauri"
},
"dependencies": {
"@tauri-apps/api": "2.6.0",
"@tauri-apps/api": "2.7.0",
"@tauri-apps/plugin-barcode-scanner": "^2.4.0",
"@tauri-apps/plugin-biometric": "^2.3.0",
"@tauri-apps/plugin-cli": "^2.4.0",
"@tauri-apps/plugin-clipboard-manager": "^2.3.0",
"@tauri-apps/plugin-dialog": "^2.3.1",
"@tauri-apps/plugin-dialog": "^2.3.2",
"@tauri-apps/plugin-fs": "^2.4.1",
"@tauri-apps/plugin-geolocation": "^2.2.0",
"@tauri-apps/plugin-global-shortcut": "^2.3.0",
@@ -36,7 +36,7 @@
"@iconify-json/codicon": "^1.2.12",
"@iconify-json/ph": "^1.2.2",
"@sveltejs/vite-plugin-svelte": "^6.0.0",
"@tauri-apps/cli": "2.6.2",
"@tauri-apps/cli": "2.7.1",
"@unocss/extractor-svelte": "^66.3.3",
"svelte": "^5.20.4",
"unocss": "^66.3.3",
+6
View File
@@ -1,5 +1,11 @@
# Changelog
## \[2.0.32]
### Dependencies
- Upgraded to `dialog@2.3.2`
## \[2.0.31]
### Dependencies
+2 -2
View File
@@ -1,7 +1,7 @@
[package]
name = "api"
publish = false
version = "2.0.31"
version = "2.0.32"
description = "An example Tauri Application showcasing the api"
edition = "2021"
rust-version = { workspace = true }
@@ -25,7 +25,7 @@ tauri-plugin-fs = { path = "../../../plugins/fs", version = "2.4.1", features =
"watch",
] }
tauri-plugin-clipboard-manager = { path = "../../../plugins/clipboard-manager", version = "2.3.0" }
tauri-plugin-dialog = { path = "../../../plugins/dialog", version = "2.3.1" }
tauri-plugin-dialog = { path = "../../../plugins/dialog", version = "2.3.2" }
tauri-plugin-http = { path = "../../../plugins/http", features = [
"multipart",
"cookies",
+90 -94
View File
@@ -1,194 +1,190 @@
<script>
import * as fs from "@tauri-apps/plugin-fs";
import { convertFileSrc } from "@tauri-apps/api/core";
import { arrayBufferToBase64 } from "../lib/utils";
import { onDestroy } from "svelte";
import * as fs from '@tauri-apps/plugin-fs'
import { convertFileSrc } from '@tauri-apps/api/core'
import { arrayBufferToBase64 } from '../lib/utils'
import { onDestroy } from 'svelte'
export let onMessage;
export let insecureRenderHtml;
const { onMessage, insecureRenderHtml } = $props()
let path = "";
let img;
let path = $state('')
let img
/** @type {fs.FileHandle} */
let file;
let renameTo;
let watchPath = "";
let watchDebounceDelay = "0";
let watchRecursive = false;
let unwatchFn;
let unwatchPath = "";
let file = $state()
let renameTo = $state()
let watchPath = $state('')
let watchDebounceDelay = $state(0)
let watchRecursive = $state(false)
/** @type {fs.BaseDirectory | undefined} */
let baseDir = $state()
let unwatchFn
let unwatchPath = ''
function getDir() {
const dirSelect = document.getElementById("dir");
return dirSelect.value ? parseInt(dir.value) : null;
}
const DirOptions = Object.keys(fs.BaseDirectory)
.filter((key) => isNaN(parseInt(key)))
.map((dir) => [dir, fs.BaseDirectory[dir]]);
const dirOptions = Object.keys(fs.BaseDirectory).filter((key) =>
isNaN(parseInt(key))
)
function open() {
fs.open(path, {
baseDir: getDir(),
baseDir,
read: true,
write: true,
create: true,
create: true
})
.then((f) => {
file = f;
onMessage(`Opened ${path}`);
file = f
onMessage(`Opened ${path}`)
})
.catch(onMessage);
.catch(onMessage)
}
function mkdir() {
fs.mkdir(path, { baseDir: getDir() })
fs.mkdir(path, { baseDir })
.then(() => {
onMessage(`Created dir ${path}`);
onMessage(`Created dir ${path}`)
})
.catch(onMessage);
.catch(onMessage)
}
function remove() {
fs.remove(path, { baseDir: getDir() })
fs.remove(path, { baseDir })
.then(() => {
onMessage(`Removed ${path}`);
onMessage(`Removed ${path}`)
})
.catch(onMessage);
.catch(onMessage)
}
function rename() {
fs.rename(path, renameTo, {
oldPathBaseDir: getDir(),
newPathBaseDir: getDir(),
oldPathBaseDir,
newPathBaseDir
})
.then(() => {
onMessage(`Renamed ${path} to ${renameTo}`);
onMessage(`Renamed ${path} to ${renameTo}`)
})
.catch(onMessage);
.catch(onMessage)
}
function truncate() {
file
.truncate(0)
.then(() => {
onMessage(`Truncated file`);
onMessage(`Truncated file`)
})
.catch(onMessage);
.catch(onMessage)
}
function stat() {
file
.stat()
.then((stat) => {
onMessage(`File stat ${JSON.stringify(stat)}`);
onMessage(`File stat ${JSON.stringify(stat)}`)
})
.catch(onMessage);
.catch(onMessage)
}
function read() {
const opts = {
baseDir: getDir(),
};
baseDir
}
fs.stat(path, opts)
.then((stat) => {
const isFile = stat.isFile;
const isFile = stat.isFile
const promise = isFile
? fs.readFile(path, opts)
: fs.readDir(path, opts);
: fs.readDir(path, opts)
promise
.then(function (response) {
if (isFile) {
if (path.includes(".png") || path.includes(".jpg")) {
if (path.includes('.png') || path.includes('.jpg')) {
arrayBufferToBase64(
new Uint8Array(response),
function (base64) {
const src = "data:image/png;base64," + base64;
insecureRenderHtml('<img src="' + src + '"></img>');
const src = 'data:image/png;base64,' + base64
insecureRenderHtml('<img src="' + src + '"></img>')
}
);
)
} else {
const value = String.fromCharCode.apply(null, response);
const value = String.fromCharCode.apply(null, response)
insecureRenderHtml(
'<textarea id="file-response"></textarea><button id="file-save">Save</button>'
);
)
setTimeout(() => {
const fileInput = document.getElementById("file-response");
fileInput.value = value;
const fileInput = document.getElementById('file-response')
fileInput.value = value
document
.getElementById("file-save")
.addEventListener("click", function () {
.getElementById('file-save')
.addEventListener('click', function () {
fs.writeTextFile(path, fileInput.value, {
baseDir: getDir(),
}).catch(onMessage);
});
});
baseDir
}).catch(onMessage)
})
})
}
} else {
onMessage(response);
onMessage(response)
}
})
.catch(onMessage);
.catch(onMessage)
})
.catch(onMessage);
.catch(onMessage)
}
function setSrc() {
img.src = convertFileSrc(path);
img.src = convertFileSrc(path)
}
function watch() {
unwatch();
unwatch()
if (watchPath) {
onMessage(`Watching ${watchPath} for changes`);
onMessage(`Watching ${watchPath} for changes`)
let options = {
recursive: watchRecursive,
delayMs: parseInt(watchDebounceDelay),
};
delayMs: watchDebounceDelay
}
if (options.delayMs === 0) {
fs.watchImmediate(watchPath, onMessage, options)
.then((fn) => {
unwatchFn = fn;
unwatchPath = watchPath;
unwatchFn = fn
unwatchPath = watchPath
})
.catch(onMessage);
.catch(onMessage)
} else {
fs.watch(watchPath, onMessage, options)
.then((fn) => {
unwatchFn = fn;
unwatchPath = watchPath;
unwatchFn = fn
unwatchPath = watchPath
})
.catch(onMessage);
.catch(onMessage)
}
}
}
function unwatch() {
if (unwatchFn) {
onMessage(`Stopped watching ${unwatchPath} for changes`);
unwatchFn();
onMessage(`Stopped watching ${unwatchPath} for changes`)
unwatchFn()
}
unwatchFn = undefined;
unwatchPath = undefined;
unwatchFn = undefined
unwatchPath = undefined
}
onDestroy(() => {
if (file) {
file.close();
file.close()
}
if (unwatchFn) {
unwatchFn();
unwatchFn()
}
})
</script>
<div class="flex flex-col">
<div class="flex gap-1">
<select class="input" id="dir">
<option value="">None</option>
{#each DirOptions as dir}
<option value={dir[1]}>{dir[0]}</option>
<select class="input" bind:value={baseDir}>
<option value={undefined} selected>None</option>
{#each dirOptions as dir}
<option value={fs.BaseDirectory[dir]}>{dir}</option>
{/each}
</select>
<input
@@ -199,20 +195,20 @@
</div>
<br />
<div>
<button class="btn" on:click={open}>Open</button>
<button class="btn" on:click={read}>Read</button>
<button class="btn" on:click={mkdir}>Mkdir</button>
<button class="btn" on:click={remove}>Remove</button>
<button class="btn" onclick={open}>Open</button>
<button class="btn" onclick={read}>Read</button>
<button class="btn" onclick={mkdir}>Mkdir</button>
<button class="btn" onclick={remove}>Remove</button>
<div class="flex flex-row">
<button class="btn" on:click={rename}>Rename</button>
<button class="btn" onclick={rename}>Rename</button>
<input class="input" bind:value={renameTo} placeholder="To" />
</div>
<button class="btn" type="button" on:click={setSrc}>Use as img src</button>
<button class="btn" type="button" onclick={setSrc}>Use as img src</button>
</div>
{#if file}
<div>
<button class="btn" on:click={truncate}>Truncate</button>
<button class="btn" on:click={stat}>Stat</button>
<button class="btn" onclick={truncate}>Truncate</button>
<button class="btn" onclick={stat}>Stat</button>
</div>
{/if}
@@ -241,8 +237,8 @@
</div>
<br />
<div>
<button class="btn" on:click={watch}>Watch</button>
<button class="btn" on:click={unwatch}>Unwatch</button>
<button class="btn" onclick={watch}>Watch</button>
<button class="btn" onclick={unwatch}>Unwatch</button>
</div>
</div>
+49 -32
View File
@@ -1,71 +1,85 @@
<script>
import { LazyStore } from "@tauri-apps/plugin-store";
import { onMount } from "svelte";
import { appDataDir, resolve } from '@tauri-apps/api/path'
import { LazyStore } from '@tauri-apps/plugin-store'
import { onMount } from 'svelte'
export let onMessage;
let { onMessage } = $props()
let key;
let value;
let key = $state()
let value = $state()
let store = new LazyStore("cache.json");
let cache = {};
const storeName = 'cache.json'
let store = new LazyStore(storeName)
let path = $state('')
let cache = $state({})
async function refreshEntries() {
try {
const values = await store.entries();
cache = {};
const values = await store.entries()
cache = {}
for (const [key, value] of values) {
cache[key] = value;
cache[key] = value
}
} catch (error) {
onMessage(error);
onMessage(error)
}
}
onMount(async () => {
await refreshEntries();
});
path = await resolve(await appDataDir(), storeName)
await refreshEntries()
})
async function write(key, value) {
try {
if (value) {
await store.set(key, value);
await store.set(key, value)
} else {
await store.delete(key);
await store.delete(key)
}
const v = await store.get(key);
const v = await store.get(key)
if (v === undefined) {
delete cache[key];
cache = cache;
delete cache[key]
cache = cache
} else {
cache[key] = v;
cache[key] = v
}
} catch (error) {
onMessage(error);
onMessage(error)
}
}
async function reset() {
try {
await store.reset();
await store.reset()
} catch (error) {
onMessage(error);
onMessage(error)
}
await refreshEntries();
await refreshEntries()
}
async function reload() {
try {
await store.reload({ overrideDefaults: true })
} catch (error) {
onMessage(error)
}
await refreshEntries()
}
async function close() {
try {
await store.close();
onMessage("Store is now closed, any new operations will error out");
await store.close()
onMessage('Store is now closed, any new operations will error out')
} catch (error) {
onMessage(error);
onMessage(error)
}
}
function reopen() {
store = new LazyStore("cache.json");
onMessage("We made a new `LazyStore` instance, operations will now work");
store = new LazyStore(storeName)
onMessage('We made a new `LazyStore` instance, operations will now work')
}
</script>
@@ -82,14 +96,17 @@
</div>
<div>
<button class="btn" on:click={() => write(key, value)}>Write</button>
<button class="btn" on:click={() => reset()}>Reset</button>
<button class="btn" on:click={() => close()}>Close</button>
<button class="btn" on:click={() => reopen()}>Re-open</button>
<button class="btn" onclick={() => write(key, value)}>Write</button>
<button class="btn" onclick={() => reset()}>Reset</button>
<button class="btn" onclick={() => reload()}>Reload</button>
<button class="btn" onclick={() => close()}>Close</button>
<button class="btn" onclick={() => reopen()}>Re-open</button>
</div>
<div>Store at <code>{path}</code> on disk</div>
</div>
<div>
<h2>Store Values</h2>
{#each Object.entries(cache) as [k, v]}
<div>{k} = {v}</div>
{/each}
+5 -5
View File
@@ -11,19 +11,19 @@
"example:api:dev": "pnpm run --filter \"api\" tauri dev"
},
"devDependencies": {
"@eslint/js": "9.31.0",
"@eslint/js": "9.32.0",
"@rollup/plugin-node-resolve": "16.0.1",
"@rollup/plugin-terser": "0.4.4",
"@rollup/plugin-typescript": "12.1.4",
"covector": "^0.12.4",
"eslint": "9.31.0",
"eslint": "9.32.0",
"eslint-config-prettier": "10.1.8",
"eslint-plugin-security": "3.0.1",
"prettier": "3.6.2",
"rollup": "4.45.1",
"rollup": "4.46.2",
"tslib": "2.8.1",
"typescript": "5.8.3",
"typescript-eslint": "8.37.0"
"typescript": "5.9.2",
"typescript-eslint": "8.39.0"
},
"pnpm": {
"overrides": {
-9
View File
@@ -33,21 +33,12 @@ tauri-plugin-autostart = { git = "https://github.com/tauri-apps/plugins-workspac
You can install the JavaScript Guest bindings using your preferred JavaScript package manager:
> Note: Since most JavaScript package managers are unable to install packages from git monorepos we provide read-only mirrors of each plugin. This makes installation option 2 more ergonomic to use.
```sh
pnpm add @tauri-apps/plugin-autostart
# or
npm add @tauri-apps/plugin-autostart
# or
yarn add @tauri-apps/plugin-autostart
# alternatively with Git:
pnpm add https://github.com/tauri-apps/tauri-plugin-autostart#v2
# or
npm add https://github.com/tauri-apps/tauri-plugin-autostart#v2
# or
yarn add https://github.com/tauri-apps/tauri-plugin-autostart#v2
```
## Usage
-9
View File
@@ -33,21 +33,12 @@ tauri-plugin-barcode-scanner = { git = "https://github.com/tauri-apps/plugins-wo
You can install the JavaScript Guest bindings using your preferred JavaScript package manager:
> Note: Since most JavaScript package managers are unable to install packages from git monorepos we provide read-only mirrors of each plugin. This makes installation option 2 more ergonomic to use.
```sh
pnpm add @tauri-apps/plugin-barcode-scanner
# or
npm add @tauri-apps/plugin-barcode-scanner
# or
yarn add @tauri-apps/plugin-barcode-scanner
# alternatively with Git:
pnpm add https://github.com/tauri-apps/tauri-plugin-barcode-scanner#v2
# or
npm add https://github.com/tauri-apps/tauri-plugin-barcode-scanner#v2
# or
yarn add https://github.com/tauri-apps/tauri-plugin-barcode-scanner#v2
```
## Usage
-9
View File
@@ -33,8 +33,6 @@ tauri-plugin-biometric = { git = "https://github.com/tauri-apps/plugins-workspac
You can install the JavaScript Guest bindings using your preferred JavaScript package manager:
> Note: Since most JavaScript package managers are unable to install packages from git monorepos we provide read-only mirrors of each plugin. This makes installation option 2 more ergonomic to use.
<!-- Add the branch for installations using git! -->
```sh
@@ -43,13 +41,6 @@ pnpm add @tauri-apps/plugin-biometric
npm add @tauri-apps/plugin-biometric
# or
yarn add @tauri-apps/plugin-biometric
# alternatively with Git:
pnpm add https://github.com/tauri-apps/tauri-plugin-biometric#v2
# or
npm add https://github.com/tauri-apps/tauri-plugin-biometric#v2
# or
yarn add https://github.com/tauri-apps/tauri-plugin-biometric#v2
```
## Usage
-9
View File
@@ -34,21 +34,12 @@ tauri-plugin-cli = { git = "https://github.com/tauri-apps/plugins-workspace", br
You can install the JavaScript Guest bindings using your preferred JavaScript package manager:
> Note: Since most JavaScript package managers are unable to install packages from git monorepos we provide read-only mirrors of each plugin. This makes installation option 2 more ergonomic to use.
```sh
pnpm add @tauri-apps/plugin-cli
# or
npm add @tauri-apps/plugin-cli
# or
yarn add @tauri-apps/plugin-cli
# alternatively with Git:
pnpm add https://github.com/tauri-apps/tauri-plugin-cli#v2
# or
npm add https://github.com/tauri-apps/tauri-plugin-cli#v2
# or
yarn add https://github.com/tauri-apps/tauri-plugin-cli#v2
```
## Usage
-9
View File
@@ -33,21 +33,12 @@ tauri-plugin-clipboard-manager = { git = "https://github.com/tauri-apps/plugins-
You can install the JavaScript Guest bindings using your preferred JavaScript package manager:
> Note: Since most JavaScript package managers are unable to install packages from git monorepos we provide read-only mirrors of each plugin. This makes installation option 2 more ergonomic to use.
```sh
pnpm add @tauri-apps/plugin-clipboard-manager
# or
npm add @tauri-apps/plugin-clipboard-manager
# or
yarn add @tauri-apps/plugin-clipboard-manager
# alternatively with Git:
pnpm add https://github.com/tauri-apps/tauri-plugin-clipboard-manager#v2
# or
npm add https://github.com/tauri-apps/tauri-plugin-clipboard-manager#v2
# or
yarn add https://github.com/tauri-apps/tauri-plugin-clipboard-manager#v2
```
## Usage
+5
View File
@@ -1,5 +1,10 @@
# Changelog
## \[2.4.1]
- [`d4f8299b`](https://github.com/tauri-apps/plugins-workspace/commit/d4f8299b12f107718c70692840a63768d65baaf9) ([#2844](https://github.com/tauri-apps/plugins-workspace/pull/2844) by [@yobson1](https://github.com/tauri-apps/plugins-workspace/../../yobson1)) Fix deep link protocol handler not set as default on linux
Fix duplicate protocols added to MimeType section in .desktop files on linux
## \[2.4.0]
- [`f209b2f2`](https://github.com/tauri-apps/plugins-workspace/commit/f209b2f23cb29133c97ad5961fb46ef794dbe063) ([#2804](https://github.com/tauri-apps/plugins-workspace/pull/2804) by [@renovate](https://github.com/tauri-apps/plugins-workspace/../../renovate)) Updated tauri to 2.6
+4 -4
View File
@@ -1,6 +1,6 @@
[package]
name = "tauri-plugin-deep-link"
version = "2.4.0"
version = "2.4.1"
description = "Set your Tauri application as the default handler for an URL"
authors = { workspace = true }
license = { workspace = true }
@@ -17,9 +17,9 @@ targets = ["x86_64-linux-android"]
[package.metadata.platforms.support]
windows = { level = "full", notes = "" }
linux = { level = "full", notes = "" }
macos = { level = "partial", notes = "Runtime deep link registration is not supported" }
android = { level = "partial", notes = "Runtime deep link registration is not supported" }
ios = { level = "partial", notes = "Runtime deep link registration is not supported" }
macos = { level = "partial", notes = "Deep links must be registered in config. Dynamic registration at runtime is not supported." }
android = { level = "partial", notes = "Deep links must be registered in config. Dynamic registration at runtime is not supported." }
ios = { level = "partial", notes = "Deep links must be registered in config. Dynamic registration at runtime is not supported." }
[build-dependencies]
serde = { workspace = true }
-9
View File
@@ -33,21 +33,12 @@ tauri-plugin-deep-link = { git = "https://github.com/tauri-apps/plugins-workspac
You can install the JavaScript Guest bindings using your preferred JavaScript package manager:
> Note: Since most JavaScript package managers are unable to install packages from git monorepos we provide read-only mirrors of each plugin. This makes installation option 2 more ergonomic to use.
```sh
pnpm add @tauri-apps/plugin-deep-link
# or
npm add @tauri-apps/plugin-deep-link
# or
yarn add @tauri-apps/plugin-deep-link
# alternatively with Git:
pnpm add https://github.com/tauri-apps/tauri-plugin-deep-link#v2
# or
npm add https://github.com/tauri-apps/tauri-plugin-deep-link#v2
# or
yarn add https://github.com/tauri-apps/tauri-plugin-deep-link#v2
```
## Setting up
@@ -1,5 +1,11 @@
# Changelog
## \[2.2.4]
### Dependencies
- Upgraded to `deep-link-js@2.4.1`
## \[2.2.3]
### Dependencies
+4 -4
View File
@@ -1,7 +1,7 @@
{
"name": "deep-link-example",
"private": true,
"version": "2.2.3",
"version": "2.2.4",
"type": "module",
"scripts": {
"dev": "vite",
@@ -10,11 +10,11 @@
"tauri": "tauri"
},
"dependencies": {
"@tauri-apps/api": "2.6.0",
"@tauri-apps/plugin-deep-link": "2.4.0"
"@tauri-apps/api": "2.7.0",
"@tauri-apps/plugin-deep-link": "2.4.1"
},
"devDependencies": {
"@tauri-apps/cli": "2.6.2",
"@tauri-apps/cli": "2.7.1",
"typescript": "^5.7.3",
"vite": "^7.0.4"
}
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@tauri-apps/plugin-deep-link",
"version": "2.4.0",
"version": "2.4.1",
"description": "Set your Tauri application as the default handler for an URL",
"license": "MIT OR Apache-2.0",
"authors": [
+9 -7
View File
@@ -303,12 +303,14 @@ mod imp {
if let Ok(mut desktop_file) = ini::Ini::load_from_file(&target_file) {
if let Some(section) = desktop_file.section_mut(Some("Desktop Entry")) {
let old_mimes = section.remove("MimeType");
section.append(
"MimeType",
format!("{mime_type};{}", old_mimes.unwrap_or_default()),
);
desktop_file.write_to_file(&target_file)?;
// it's ok to remove it - we only write to the file if it's missing
// and in that case we include old_mimes
let old_mimes = section.remove("MimeType").unwrap_or_default();
if !old_mimes.split(';').any(|mime| mime == mime_type) {
section.append("MimeType", format!("{mime_type};{old_mimes}"));
desktop_file.write_to_file(&target_file)?;
}
}
} else {
let mut file = File::create(target_file)?;
@@ -333,7 +335,7 @@ mod imp {
.status()?;
Command::new("xdg-mime")
.args(["default", &file_name, _protocol.as_ref()])
.args(["default", &file_name, mime_type.as_str()])
.status()?;
Ok(())
+4
View File
@@ -1,5 +1,9 @@
# Changelog
## \[2.3.2]
- [`af08c66f`](https://github.com/tauri-apps/plugins-workspace/commit/af08c66faafe0dffc4b0a80aef030cd3f0f89a9c) ([#2871](https://github.com/tauri-apps/plugins-workspace/pull/2871) by [@FabianLars](https://github.com/tauri-apps/plugins-workspace/../../FabianLars)) Fixed an issue that caused the file picker not to open on Android when extension filters were set.
## \[2.3.1]
### Dependencies
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "tauri-plugin-dialog"
version = "2.3.1"
version = "2.3.2"
description = "Native system dialogs for opening and saving files along with message dialogs on your Tauri application."
edition = { workspace = true }
authors = { workspace = true }
-9
View File
@@ -33,21 +33,12 @@ tauri-plugin-dialog = { git = "https://github.com/tauri-apps/plugins-workspace",
You can install the JavaScript Guest bindings using your preferred JavaScript package manager:
> Note: Since most JavaScript package managers are unable to install packages from git monorepos we provide read-only mirrors of each plugin. This makes installation option 2 more ergonomic to use.
```sh
pnpm add @tauri-apps/plugin-dialog
# or
npm add @tauri-apps/plugin-dialog
# or
yarn add @tauri-apps/plugin-dialog
# alternatively with Git:
pnpm add https://github.com/tauri-apps/tauri-plugin-dialog#v2
# or
npm add https://github.com/tauri-apps/tauri-plugin-dialog#v2
# or
yarn add https://github.com/tauri-apps/tauri-plugin-dialog#v2
```
## Usage
@@ -56,20 +56,18 @@ class DialogPlugin(private val activity: Activity): Plugin(activity) {
try {
val args = invoke.parseArgs(FilePickerOptions::class.java)
val parsedTypes = parseFiltersOption(args.filters)
val intent = if (parsedTypes.isNotEmpty()) {
val intent = Intent(Intent.ACTION_PICK)
setIntentMimeTypes(intent, parsedTypes)
intent
} else {
val intent = Intent(Intent.ACTION_GET_CONTENT)
intent.addCategory(Intent.CATEGORY_OPENABLE)
intent.type = "*/*"
intent
// TODO: ACTION_OPEN_DOCUMENT ??
val intent = Intent(Intent.ACTION_GET_CONTENT)
intent.addCategory(Intent.CATEGORY_OPENABLE)
intent.type = "*/*"
if (parsedTypes.isNotEmpty()) {
intent.putExtra(Intent.EXTRA_MIME_TYPES, parsedTypes)
}
intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, args.multiple ?: false)
startActivityForResult(invoke, intent, "filePickerResult")
} catch (ex: Exception) {
val message = ex.message ?: "Failed to pick file"
@@ -115,7 +113,7 @@ class DialogPlugin(private val activity: Activity): Plugin(activity) {
callResult.put("files", JSArray.from(uris.toTypedArray()))
return callResult
}
private fun parseFiltersOption(filters: Array<Filter>): Array<String> {
val mimeTypes = mutableListOf<String>()
for (filter in filters) {
@@ -132,38 +130,10 @@ class DialogPlugin(private val activity: Activity): Plugin(activity) {
return mimeTypes.toTypedArray()
}
private fun setIntentMimeTypes(intent: Intent, mimeTypes: Array<String>) {
if (mimeTypes.isNotEmpty()) {
var uniqueMimeKind = true
var mimeKind: String? = null
for (mime in mimeTypes) {
val kind = mime.split("/")[0]
if (mimeKind == null) {
mimeKind = kind
} else if (mimeKind != kind) {
uniqueMimeKind = false
}
}
if (uniqueMimeKind) {
if (mimeTypes.size > 1) {
intent.putExtra(Intent.EXTRA_MIME_TYPES, mimeTypes)
intent.type = Intent.normalizeMimeType("$mimeKind/*")
} else {
intent.type = mimeTypes[0]
}
} else {
intent.type = "*/*"
}
} else {
intent.type = "*/*"
}
}
@Command
fun showMessageDialog(invoke: Invoke) {
val args = invoke.parseArgs(MessageOptions::class.java)
if (activity.isFinishing) {
invoke.reject("App is finishing")
return
@@ -179,7 +149,7 @@ class DialogPlugin(private val activity: Activity): Plugin(activity) {
Handler(Looper.getMainLooper())
.post {
val builder = AlertDialog.Builder(activity)
if (args.title != null) {
builder.setTitle(args.title)
}
@@ -213,10 +183,14 @@ class DialogPlugin(private val activity: Activity): Plugin(activity) {
val parsedTypes = parseFiltersOption(args.filters)
val intent = Intent(Intent.ACTION_CREATE_DOCUMENT)
setIntentMimeTypes(intent, parsedTypes)
intent.addCategory(Intent.CATEGORY_OPENABLE)
intent.putExtra(Intent.EXTRA_TITLE, args.fileName ?: "")
intent.type = "*/*"
if (parsedTypes.isNotEmpty()) {
intent.putExtra(Intent.EXTRA_MIME_TYPES, parsedTypes)
}
startActivityForResult(invoke, intent, "saveFileDialogResult")
} catch (ex: Exception) {
val message = ex.message ?: "Failed to pick save file"
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@tauri-apps/plugin-dialog",
"version": "2.3.1",
"version": "2.3.2",
"license": "MIT OR Apache-2.0",
"authors": [
"Tauri Programme within The Commons Conservancy"
+1 -1
View File
@@ -41,7 +41,7 @@ notify = { version = "8", optional = true, features = [
"serde",
"serialization-compat-6",
] }
notify-debouncer-full = { version = "0.5", optional = true }
notify-debouncer-full = { version = "0.6", optional = true }
dunce = { workspace = true }
percent-encoding = "2"
-9
View File
@@ -33,21 +33,12 @@ tauri-plugin-fs = { git = "https://github.com/tauri-apps/plugins-workspace", bra
You can install the JavaScript Guest bindings using your preferred JavaScript package manager:
> Note: Since most JavaScript package managers are unable to install packages from git monorepos we provide read-only mirrors of each plugin. This makes installation option 2 more ergonomic to use.
```sh
pnpm add @tauri-apps/plugin-fs
# or
npm add @tauri-apps/plugin-fs
# or
yarn add @tauri-apps/plugin-fs
# alternatively with Git:
pnpm add https://github.com/tauri-apps/tauri-plugin-fs#v2
# or
npm add https://github.com/tauri-apps/tauri-plugin-fs#v2
# or
yarn add https://github.com/tauri-apps/tauri-plugin-fs#v2
```
## Usage
File diff suppressed because one or more lines are too long
+6 -1
View File
@@ -1074,7 +1074,12 @@ async function writeFile(
}
if (data instanceof ReadableStream) {
const file = await open(path, { create: true, ...options })
const file = await open(path, {
read: false,
create: true,
write: true,
...options
})
const reader = data.getReader()
try {
-9
View File
@@ -33,8 +33,6 @@ tauri-plugin-geolocation = { git = "https://github.com/tauri-apps/plugins-worksp
You can install the JavaScript Guest bindings using your preferred JavaScript package manager:
> Note: Since most JavaScript package managers are unable to install packages from git monorepos we provide read-only mirrors of each plugin. This makes installation option 2 more ergonomic to use.
<!-- Add the branch for installations using git! -->
```sh
@@ -43,13 +41,6 @@ pnpm add @tauri-apps/plugin-geolocation
npm add @tauri-apps/plugin-geolocation
# or
yarn add @tauri-apps/plugin-geolocation
# alternatively with Git:
pnpm add https://github.com/tauri-apps/tauri-plugin-geolocation#v2
# or
npm add https://github.com/tauri-apps/tauri-plugin-geolocation#v2
# or
yarn add https://github.com/tauri-apps/tauri-plugin-geolocation#v2
```
## Setting up
-9
View File
@@ -34,21 +34,12 @@ tauri-plugin-global-shortcut = { git = "https://github.com/tauri-apps/plugins-wo
You can install the JavaScript Guest bindings using your preferred JavaScript package manager:
> Note: Since most JavaScript package managers are unable to install packages from git monorepos we provide read-only mirrors of each plugin. This makes installation option 2 more ergonomic to use.
```sh
pnpm add @tauri-apps/plugin-global-shortcut
# or
npm add @tauri-apps/plugin-global-shortcut
# or
yarn add @tauri-apps/plugin-global-shortcut
# alternatively with Git:
pnpm add https://github.com/tauri-apps/tauri-plugin-global-shortcut#v2
# or
npm add https://github.com/tauri-apps/tauri-plugin-global-shortcut#v2
# or
yarn add https://github.com/tauri-apps/tauri-plugin-global-shortcut#v2
```
## Usage
-9
View File
@@ -35,8 +35,6 @@ tauri-plugin-haptics = { git = "https://github.com/tauri-apps/plugins-workspace"
You can install the JavaScript Guest bindings using your preferred JavaScript package manager:
> Note: Since most JavaScript package managers are unable to install packages from git monorepos we provide read-only mirrors of each plugin. This makes installation option 2 more ergonomic to use.
<!-- Add the branch for installations using git! -->
```sh
@@ -45,13 +43,6 @@ pnpm add @tauri-apps/plugin-haptics
npm add @tauri-apps/plugin-haptics
# or
yarn add @tauri-apps/plugin-haptics
# alternatively with Git:
pnpm add https://github.com/tauri-apps/tauri-plugin-haptics#v2
# or
npm add https://github.com/tauri-apps/tauri-plugin-haptics#v2
# or
yarn add https://github.com/tauri-apps/tauri-plugin-haptics#v2
```
## Usage
-9
View File
@@ -33,21 +33,12 @@ tauri-plugin-http = { git = "https://github.com/tauri-apps/plugins-workspace", b
You can install the JavaScript Guest bindings using your preferred JavaScript package manager:
> Note: Since most JavaScript package managers are unable to install packages from git monorepos we provide read-only mirrors of each plugin. This makes installation option 2 more ergonomic to use.
```sh
pnpm add @tauri-apps/plugin-http
# or
npm add @tauri-apps/plugin-http
# or
yarn add @tauri-apps/plugin-http
# alternatively with Git:
pnpm add https://github.com/tauri-apps/tauri-plugin-http#v2
# or
npm add https://github.com/tauri-apps/tauri-plugin-http#v2
# or
yarn add https://github.com/tauri-apps/tauri-plugin-http#v2
```
## Usage
+1 -1
View File
@@ -30,7 +30,7 @@ fn set_cookies(
fn cookies(cookie_store: &CookieStore, url: &url::Url) -> Option<HeaderValue> {
let s = cookie_store
.get_request_values(url)
.map(|(name, value)| format!("{}={}", name, value))
.map(|(name, value)| format!("{name}={value}"))
.collect::<Vec<_>>()
.join("; ");
+1 -1
View File
@@ -52,7 +52,7 @@ impl<'de> Deserialize<'de> for Entry {
};
Ok(Entry {
url: parse_url_pattern(&url).map_err(|e| {
serde::de::Error::custom(format!("`{}` is not a valid URL pattern: {e}", url))
serde::de::Error::custom(format!("`{url}` is not a valid URL pattern: {e}"))
})?,
})
})
-9
View File
@@ -35,21 +35,12 @@ If you want the single instance mechanism to only trigger for semver compatible
Then you can install the JavaScript Guest bindings using your preferred JavaScript package manager:
> Note: Since most JavaScript package managers are unable to install packages from git monorepos we provide read-only mirrors of each plugin. This makes installation option 2 more ergonomic to use.
```sh
pnpm add @tauri-apps/plugin-log
# or
npm add @tauri-apps/plugin-log
# or
yarn add @tauri-apps/plugin-log
# alternatively with Git:
pnpm add https://github.com/tauri-apps/tauri-plugin-log#v2
# or
npm add https://github.com/tauri-apps/tauri-plugin-log#v2
# or
yarn add https://github.com/tauri-apps/tauri-plugin-log#v2
```
## Usage
-9
View File
@@ -33,8 +33,6 @@ tauri-plugin-nfc = { git = "https://github.com/tauri-apps/plugins-workspace", br
You can install the JavaScript Guest bindings using your preferred JavaScript package manager:
> Note: Since most JavaScript package managers are unable to install packages from git monorepos we provide read-only mirrors of each plugin. This makes installation option 2 more ergonomic to use.
<!-- Add the branch for installations using git! -->
```sh
@@ -43,13 +41,6 @@ pnpm add @tauri-apps/plugin-nfc
npm add @tauri-apps/plugin-nfc
# or
yarn add @tauri-apps/plugin-nfc
# alternatively with Git:
pnpm add https://github.com/tauri-apps/tauri-plugin-nfc#v2
# or
npm add https://github.com/tauri-apps/tauri-plugin-nfc#v2
# or
yarn add https://github.com/tauri-apps/tauri-plugin-nfc#v2
```
## Usage
@@ -0,0 +1,331 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 60;
objects = {
/* Begin PBXBuildFile section */
8E12E6982E4A879F0019CC26 /* CoreNFC.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8E12E6962E4A862B0019CC26 /* CoreNFC.framework */; settings = {ATTRIBUTES = (Weak, ); }; };
A0E2115A2BF552D2003BCF4D /* ExamplePlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = A0E211592BF552D2003BCF4D /* ExamplePlugin.swift */; };
A0E211622BF55305003BCF4D /* Tauri in Frameworks */ = {isa = PBXBuildFile; productRef = A0E211612BF55305003BCF4D /* Tauri */; };
/* End PBXBuildFile section */
/* Begin PBXCopyFilesBuildPhase section */
A0E211542BF552D2003BCF4D /* CopyFiles */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "include/$(PRODUCT_NAME)";
dstSubfolderSpec = 16;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
8E12E6962E4A862B0019CC26 /* CoreNFC.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreNFC.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX15.5.sdk/System/iOSSupport/System/Library/Frameworks/CoreNFC.framework; sourceTree = DEVELOPER_DIR; };
A0E211562BF552D2003BCF4D /* libtauri-plugin-nfc.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libtauri-plugin-nfc.a"; sourceTree = BUILT_PRODUCTS_DIR; };
A0E211592BF552D2003BCF4D /* ExamplePlugin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExamplePlugin.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
A0E211532BF552D2003BCF4D /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
8E12E6982E4A879F0019CC26 /* CoreNFC.framework in Frameworks */,
A0E211622BF55305003BCF4D /* Tauri in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
8E12E6952E4A862B0019CC26 /* Frameworks */ = {
isa = PBXGroup;
children = (
8E12E6962E4A862B0019CC26 /* CoreNFC.framework */,
);
name = Frameworks;
sourceTree = "<group>";
};
A0E2114D2BF552D2003BCF4D = {
isa = PBXGroup;
children = (
A0E211582BF552D2003BCF4D /* tauri-plugin-nfc */,
8E12E6952E4A862B0019CC26 /* Frameworks */,
A0E211572BF552D2003BCF4D /* Products */,
);
sourceTree = "<group>";
};
A0E211572BF552D2003BCF4D /* Products */ = {
isa = PBXGroup;
children = (
A0E211562BF552D2003BCF4D /* libtauri-plugin-nfc.a */,
);
name = Products;
sourceTree = "<group>";
};
A0E211582BF552D2003BCF4D /* tauri-plugin-nfc */ = {
isa = PBXGroup;
children = (
A0E211592BF552D2003BCF4D /* ExamplePlugin.swift */,
);
path = "tauri-plugin-nfc";
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
A0E211552BF552D2003BCF4D /* tauri-plugin-nfc */ = {
isa = PBXNativeTarget;
buildConfigurationList = A0E2115D2BF552D2003BCF4D /* Build configuration list for PBXNativeTarget "tauri-plugin-nfc" */;
buildPhases = (
A0E211522BF552D2003BCF4D /* Sources */,
A0E211532BF552D2003BCF4D /* Frameworks */,
A0E211542BF552D2003BCF4D /* CopyFiles */,
);
buildRules = (
);
dependencies = (
);
name = "tauri-plugin-nfc";
packageProductDependencies = (
A0E211612BF55305003BCF4D /* Tauri */,
);
productName = "tauri-plugin-nfc";
productReference = A0E211562BF552D2003BCF4D /* libtauri-plugin-nfc.a */;
productType = "com.apple.product-type.library.static";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
A0E2114E2BF552D2003BCF4D /* Project object */ = {
isa = PBXProject;
attributes = {
BuildIndependentTargetsInParallel = 1;
LastSwiftUpdateCheck = 1540;
LastUpgradeCheck = 1540;
TargetAttributes = {
A0E211552BF552D2003BCF4D = {
CreatedOnToolsVersion = 15.4;
};
};
};
buildConfigurationList = A0E211512BF552D2003BCF4D /* Build configuration list for PBXProject "tauri-plugin-nfc" */;
compatibilityVersion = "Xcode 14.0";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = A0E2114D2BF552D2003BCF4D;
packageReferences = (
A0E211602BF55305003BCF4D /* XCLocalSwiftPackageReference "../.tauri/tauri-api" */,
);
productRefGroup = A0E211572BF552D2003BCF4D /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
A0E211552BF552D2003BCF4D /* tauri-plugin-nfc */,
);
};
/* End PBXProject section */
/* Begin PBXSourcesBuildPhase section */
A0E211522BF552D2003BCF4D /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
A0E2115A2BF552D2003BCF4D /* ExamplePlugin.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin XCBuildConfiguration section */
A0E2115B2BF552D2003BCF4D /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
};
name = Debug;
};
A0E2115C2BF552D2003BCF4D /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
SDKROOT = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
VALIDATE_PRODUCT = YES;
};
name = Release;
};
A0E2115E2BF552D2003BCF4D /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
A0E2115F2BF552D2003BCF4D /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
A0E211512BF552D2003BCF4D /* Build configuration list for PBXProject "tauri-plugin-nfc" */ = {
isa = XCConfigurationList;
buildConfigurations = (
A0E2115B2BF552D2003BCF4D /* Debug */,
A0E2115C2BF552D2003BCF4D /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
A0E2115D2BF552D2003BCF4D /* Build configuration list for PBXNativeTarget "tauri-plugin-nfc" */ = {
isa = XCConfigurationList;
buildConfigurations = (
A0E2115E2BF552D2003BCF4D /* Debug */,
A0E2115F2BF552D2003BCF4D /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
/* Begin XCLocalSwiftPackageReference section */
A0E211602BF55305003BCF4D /* XCLocalSwiftPackageReference "../.tauri/tauri-api" */ = {
isa = XCLocalSwiftPackageReference;
relativePath = "../.tauri/tauri-api";
};
/* End XCLocalSwiftPackageReference section */
/* Begin XCSwiftPackageProductDependency section */
A0E211612BF55305003BCF4D /* Tauri */ = {
isa = XCSwiftPackageProductDependency;
productName = Tauri;
};
/* End XCSwiftPackageProductDependency section */
};
rootObject = A0E2114E2BF552D2003BCF4D /* Project object */;
}
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>
@@ -0,0 +1,15 @@
{
"originHash" : "cd669d6eb9e52a836461d0d6168afc46839b3c2631131b83d6c1996da63315ec",
"pins" : [
{
"identity" : "swift-rs",
"kind" : "remoteSourceControl",
"location" : "https://github.com/Brendonovich/swift-rs",
"state" : {
"revision" : "f64a4514de07f450ec5b6aa297624cd3479d9579",
"version" : "1.0.7"
}
}
],
"version" : 3
}
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>SchemeUserState</key>
<dict>
<key>tauri-plugin-nfc.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>0</integer>
</dict>
</dict>
</dict>
</plist>
@@ -0,0 +1,523 @@
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
// https://developer.apple.com/documentation/corenfc/building_an_nfc_tag-reader_app
import CoreNFC
import SwiftRs
import Tauri
import UIKit
import WebKit
enum ScanKind: Decodable {
case ndef, tag
}
struct ScanOptions: Decodable {
let kind: ScanKind
var keepSessionAlive: Bool?
var message: String?
var successMessage: String?
}
struct NDEFRecord: Decodable {
var format: UInt8?
var kind: [UInt8]?
var identifier: [UInt8]?
var payload: [UInt8]?
}
struct WriteOptions: Decodable {
var kind: ScanKind?
let records: [NDEFRecord]
var message: String?
var successMessage: String?
var successfulReadMessage: String?
}
enum TagProcessMode {
case write(message: NFCNDEFMessage)
case read
}
class Session {
let nfcSession: NFCReaderSession?
let invoke: Invoke
var keepAlive: Bool
let tagProcessMode: TagProcessMode
var tagStatus: NFCNDEFStatus?
var tag: NFCNDEFTag?
let successfulReadMessage: String?
let successfulWriteAlertMessage: String?
init(
nfcSession: NFCReaderSession?,
invoke: Invoke,
keepAlive: Bool,
tagProcessMode: TagProcessMode,
successfulReadMessage: String?,
successfulWriteAlertMessage: String?
) {
self.nfcSession = nfcSession
self.invoke = invoke
self.keepAlive = keepAlive
self.tagProcessMode = tagProcessMode
self.successfulReadMessage = successfulReadMessage
self.successfulWriteAlertMessage = successfulWriteAlertMessage
}
}
class NfcStatus {
let available: Bool
let errorReason: String?
init(available: Bool, errorReason: String?) {
self.available = available
self.errorReason = errorReason
}
}
class NfcPlugin: Plugin, NFCTagReaderSessionDelegate, NFCNDEFReaderSessionDelegate {
var session: Session?
var status: NfcStatus!
public override func load(webview: WKWebView) {
var available = false
var errorReason: String?
let entry = Bundle.main.infoDictionary?["NFCReaderUsageDescription"] as? String
if entry == nil || entry?.count == 0 {
errorReason = "missing NFCReaderUsageDescription configuration on the Info.plist file"
} else if !NFCNDEFReaderSession.readingAvailable {
errorReason =
"NFC tag reading unavailable, make sure the Near-Field Communication capability on Xcode is enabled and the device supports NFC tag reading"
} else {
available = true
}
if let error = errorReason {
Logger.error("\(error)")
}
self.status = NfcStatus(available: available, errorReason: errorReason)
}
func tagReaderSessionDidBecomeActive(
_ session: NFCTagReaderSession
) {
Logger.info("tagReaderSessionDidBecomeActive")
}
func tagReaderSession(_ session: NFCTagReaderSession, didDetect tags: [NFCTag]) {
let tag = tags.first!
session.connect(
to: tag,
completionHandler: { [self] (error) in
if let error = error {
self.closeSession(session, error: "cannot connect to tag: \(error)")
} else {
let ndefTag: NFCNDEFTag
switch tag {
case let .feliCa(tag):
ndefTag = tag as NFCNDEFTag
break
case let .miFare(tag):
ndefTag = tag as NFCNDEFTag
break
case let .iso15693(tag):
ndefTag = tag as NFCNDEFTag
break
case let .iso7816(tag):
ndefTag = tag as NFCNDEFTag
break
default:
return
}
self.processTag(
session: session, tag: ndefTag, metadata: tagMetadata(tag),
mode: self.session!.tagProcessMode)
}
}
)
}
func tagReaderSession(_ session: NFCTagReaderSession, didInvalidateWithError error: Error) {
Logger.error("Tag reader session error \(error)")
self.session?.invoke.reject("session invalidated with error: \(error)")
self.session = nil
}
func readerSession(_ session: NFCNDEFReaderSession, didDetectNDEFs messages: [NFCNDEFMessage]) {
let message = messages.first!
// TODO: do we really need this hook?
self.session?.invoke.resolve(["records": ndefMessageRecords(message)])
}
func readerSession(_ session: NFCNDEFReaderSession, didDetect tags: [NFCNDEFTag]) {
let tag = tags.first!
session.connect(
to: tag,
completionHandler: { [self] (error) in
if let error = error {
self.closeSession(session, error: "cannot connect to tag: \(error)")
} else {
var metadata: JsonObject = [:]
if tag.isKind(of: NFCFeliCaTag.self) {
metadata["kind"] = ["FeliCa"]
metadata["id"] = nil
} else if let t = tag as? NFCMiFareTag {
metadata["kind"] = ["MiFare"]
metadata["id"] = byteArrayFromData(t.identifier)
} else if let t = tag as? NFCISO15693Tag {
metadata["kind"] = ["ISO15693"]
metadata["id"] = byteArrayFromData(t.identifier)
} else if let t = tag as? NFCISO7816Tag {
metadata["kind"] = ["ISO7816Compatible"]
metadata["id"] = byteArrayFromData(t.identifier)
}
self.processTag(
session: session, tag: tag, metadata: metadata,
mode: self.session!.tagProcessMode)
}
}
)
}
func readerSession(_ session: NFCNDEFReaderSession, didInvalidateWithError error: Error) {
if (error as NSError).code
== NFCReaderError.Code.readerSessionInvalidationErrorFirstNDEFTagRead.rawValue
{
// not an error because we're using invalidateAfterFirstRead: true
Logger.debug("readerSessionInvalidationErrorFirstNDEFTagRead")
} else {
Logger.error("NDEF reader session error \(error)")
self.session?.invoke.reject("session invalidated with error: \(error)")
self.session = nil
}
}
private func tagMetadata(_ tag: NFCTag) -> JsonObject {
var metadata: JsonObject = [:]
switch tag {
case .feliCa:
metadata["kind"] = ["FeliCa"]
metadata["id"] = []
break
case let .miFare(tag):
metadata["kind"] = ["MiFare"]
metadata["id"] = byteArrayFromData(tag.identifier)
break
case let .iso15693(tag):
metadata["kind"] = ["ISO15693"]
metadata["id"] = byteArrayFromData(tag.identifier)
break
case let .iso7816(tag):
metadata["kind"] = ["ISO7816Compatible"]
metadata["id"] = byteArrayFromData(tag.identifier)
break
default:
metadata["kind"] = ["Unknown"]
metadata["id"] = []
break
}
return metadata
}
private func closeSession(_ session: NFCReaderSession) {
session.invalidate()
self.session = nil
}
private func closeSession(_ session: NFCReaderSession, error: String) {
session.invalidate(errorMessage: error)
self.session = nil
}
private func processTag<T: NFCNDEFTag>(
session: NFCReaderSession, tag: T, metadata: JsonObject, mode: TagProcessMode
) {
tag.queryNDEFStatus(completionHandler: {
[self] (status, capacity, error) in
if let error = error {
self.closeSession(session, error: "cannot connect to tag: \(error)")
} else {
switch mode {
case .write(let message):
self.writeNDEFTag(
session: session, status: status, tag: tag, message: message,
alertMessage: self.session?.successfulWriteAlertMessage)
break
case .read:
if self.session?.keepAlive == true {
self.session!.tagStatus = status
self.session!.tag = tag
}
self.readNDEFTag(
session: session, status: status, tag: tag, metadata: metadata,
alertMessage: self.session?.successfulReadMessage)
break
}
}
})
}
private func writeNDEFTag<T: NFCNDEFTag>(
session: NFCReaderSession, status: NFCNDEFStatus, tag: T, message: NFCNDEFMessage,
alertMessage: String?
) {
switch status {
case .notSupported:
self.closeSession(session, error: "Tag is not an NDEF-formatted tag")
break
case .readOnly:
self.closeSession(session, error: "Read only tag")
break
case .readWrite:
if let currentSession = self.session {
tag.writeNDEF(
message,
completionHandler: { (error) in
if let error = error {
self.closeSession(session, error: "cannot write to tag: \(error)")
} else {
if let message = alertMessage {
session.alertMessage = message
}
currentSession.invoke.resolve()
self.closeSession(session)
}
})
}
break
default:
return
}
}
private func readNDEFTag<T: NFCNDEFTag>(
session: NFCReaderSession, status: NFCNDEFStatus, tag: T, metadata m: JsonObject,
alertMessage: String?
) {
var metadata: JsonObject = [:]
metadata.merge(m) { (_, new) in new }
switch status {
case .notSupported:
self.resolveInvoke(message: nil, metadata: metadata)
self.closeSession(session)
return
case .readOnly:
metadata["readOnly"] = true
break
case .readWrite:
metadata["readOnly"] = false
break
default:
break
}
tag.readNDEF(completionHandler: {
[self] (message, error) in
if let error = error {
let code = (error as NSError).code
if code != 403 {
self.closeSession(session, error: "Failed to read: \(error)")
return
}
}
if let message = alertMessage {
session.alertMessage = message
}
self.resolveInvoke(message: message, metadata: metadata)
if self.session?.keepAlive != true {
self.closeSession(session)
}
})
}
private func resolveInvoke(message: NFCNDEFMessage?, metadata: JsonObject) {
var data: JsonObject = [:]
data.merge(metadata) { (_, new) in new }
if let message = message {
data["records"] = ndefMessageRecords(message)
} else {
data["records"] = []
}
self.session?.invoke.resolve(data)
}
private func ndefMessageRecords(_ message: NFCNDEFMessage) -> [JsonObject] {
var records: [JsonObject] = []
for record in message.records {
var recordJson: JsonObject = [:]
recordJson["tnf"] = record.typeNameFormat.rawValue
recordJson["kind"] = byteArrayFromData(record.type)
recordJson["id"] = byteArrayFromData(record.identifier)
recordJson["payload"] = byteArrayFromData(record.payload)
records.append(recordJson)
}
return records
}
private func byteArrayFromData(_ data: Data) -> [UInt8] {
var arr: [UInt8] = []
for b in data {
arr.append(b)
}
return arr
}
private func dataFromByteArray(_ array: [UInt8]) -> Data {
var data = Data(capacity: array.count)
data.append(contentsOf: array)
return data
}
@objc func isAvailable(_ invoke: Invoke) {
invoke.resolve([
"available": self.status.available
])
}
@objc public func write(_ invoke: Invoke) throws {
if !self.status.available {
invoke.reject("NFC reading unavailable: \(self.status.errorReason ?? "")")
return
}
let args = try invoke.parseArgs(WriteOptions.self)
var ndefPayloads = [NFCNDEFPayload]()
for record in args.records {
ndefPayloads.append(
NFCNDEFPayload(
format: NFCTypeNameFormat(rawValue: record.format ?? 0) ?? .unknown,
type: dataFromByteArray(record.kind ?? []),
identifier: dataFromByteArray(record.identifier ?? []),
payload: dataFromByteArray(record.payload ?? [])
)
)
}
if let session = self.session {
if let nfcSession = session.nfcSession, let tagStatus = session.tagStatus,
let tag = session.tag
{
session.keepAlive = false
self.writeNDEFTag(
session: nfcSession, status: tagStatus, tag: tag,
message: NFCNDEFMessage(records: ndefPayloads),
alertMessage: args.successMessage
)
} else {
invoke.reject(
"connected tag not found, please wait for it to be available and then call write()")
}
} else {
self.startScanSession(
invoke: invoke,
kind: args.kind ?? .ndef,
keepAlive: true,
invalidateAfterFirstRead: false,
tagProcessMode: .write(
message: NFCNDEFMessage(records: ndefPayloads)
),
alertMessage: args.message,
successfulReadMessage: args.successfulReadMessage,
successfulWriteAlertMessage: args.successMessage
)
}
}
@objc public func scan(_ invoke: Invoke) throws {
if !self.status.available {
invoke.reject("NFC reading unavailable: \(self.status.errorReason ?? "")")
return
}
let args = try invoke.parseArgs(ScanOptions.self)
self.startScanSession(
invoke: invoke,
kind: args.kind,
keepAlive: args.keepSessionAlive ?? false,
invalidateAfterFirstRead: true,
tagProcessMode: .read,
alertMessage: args.message,
successfulReadMessage: args.successMessage,
successfulWriteAlertMessage: nil
)
}
private func startScanSession(
invoke: Invoke,
kind: ScanKind,
keepAlive: Bool,
invalidateAfterFirstRead: Bool,
tagProcessMode: TagProcessMode,
alertMessage: String?,
successfulReadMessage: String?,
successfulWriteAlertMessage: String?
) {
let nfcSession: NFCReaderSession?
switch kind {
case .tag:
nfcSession = NFCTagReaderSession(
pollingOption: [.iso14443, .iso15693],
delegate: self,
queue: DispatchQueue.main
)
break
case .ndef:
nfcSession = NFCNDEFReaderSession(
delegate: self,
queue: DispatchQueue.main,
invalidateAfterFirstRead: invalidateAfterFirstRead
)
break
}
if let message = alertMessage {
nfcSession?.alertMessage = message
}
nfcSession?.begin()
self.session = Session(
nfcSession: nfcSession,
invoke: invoke,
keepAlive: keepAlive,
tagProcessMode: tagProcessMode,
successfulReadMessage: successfulReadMessage,
successfulWriteAlertMessage: successfulWriteAlertMessage
)
}
}
@_cdecl("init_plugin_nfc")
func initPlugin() -> Plugin {
return NfcPlugin()
}
@@ -149,6 +149,7 @@ class NfcPlugin: Plugin, NFCTagReaderSessionDelegate, NFCNDEFReaderSessionDelega
func tagReaderSession(_ session: NFCTagReaderSession, didInvalidateWithError error: Error) {
Logger.error("Tag reader session error \(error)")
self.session?.invoke.reject("session invalidated with error: \(error)")
self.session = nil
}
func readerSession(_ session: NFCNDEFReaderSession, didDetectNDEFs messages: [NFCNDEFMessage]) {
@@ -200,6 +201,7 @@ class NfcPlugin: Plugin, NFCTagReaderSessionDelegate, NFCNDEFReaderSessionDelega
} else {
Logger.error("NDEF reader session error \(error)")
self.session?.invoke.reject("session invalidated with error: \(error)")
self.session = nil
}
}
+1 -1
View File
@@ -30,7 +30,7 @@ serde_json = { workspace = true }
tauri = { workspace = true }
log = { workspace = true }
thiserror = { workspace = true }
rand = "0.8"
rand = "0.9"
time = { version = "0.3", features = ["serde", "parsing", "formatting"] }
url = { version = "2", features = ["serde"] }
serde_repr = "0.1"
-9
View File
@@ -33,21 +33,12 @@ tauri-plugin-notification = { git = "https://github.com/tauri-apps/plugins-works
You can install the JavaScript Guest bindings using your preferred JavaScript package manager:
> Note: Since most JavaScript package managers are unable to install packages from git monorepos we provide read-only mirrors of each plugin. This makes installation option 2 more ergonomic to use.
```sh
pnpm add @tauri-apps/plugin-notification
# or
npm add @tauri-apps/plugin-notification
# or
yarn add @tauri-apps/plugin-notification
# alternatively with Git:
pnpm add https://github.com/tauri-apps/tauri-plugin-notification#v2
# or
npm add https://github.com/tauri-apps/tauri-plugin-notification#v2
# or
yarn add https://github.com/tauri-apps/tauri-plugin-notification#v2
```
## Usage
-2
View File
@@ -35,8 +35,6 @@ tauri = { workspace = true }
thiserror = { workspace = true }
open = { version = "5", features = ["shellexecute-on-windows"] }
glob = { workspace = true }
[target."cfg(windows)".dependencies]
dunce = { workspace = true }
[target."cfg(windows)".dependencies.windows]
+7 -9
View File
@@ -33,8 +33,6 @@ tauri-plugin-opener = { git = "https://github.com/tauri-apps/plugins-workspace",
You can install the JavaScript Guest bindings using your preferred JavaScript package manager:
> Note: Since most JavaScript package managers are unable to install packages from git monorepos we provide read-only mirrors of each plugin. This makes installation option 2 more ergonomic to use.
<!-- Add the branch for installations using git! -->
```sh
@@ -43,13 +41,6 @@ pnpm add @tauri-apps/plugin-opener
npm add @tauri-apps/plugin-opener
# or
yarn add @tauri-apps/plugin-opener
# alternatively with Git:
pnpm add https://github.com/tauri-apps/tauri-plugin-opener#v2
# or
npm add https://github.com/tauri-apps/tauri-plugin-opener#v2
# or
yarn add https://github.com/tauri-apps/tauri-plugin-opener#v2
```
## Usage
@@ -84,6 +75,10 @@ await openPath('/path/to/file', 'firefox')
// Reveal a path with the system's default explorer
await revealItemInDir('/path/to/file')
// Reveal multiple paths with the system's default explorer
// Note: will be renamed to `revealItemsInDir` in the next major version
await revealItemInDir(['/path/to/file', '/path/to/another/file'])
```
### Usage from Rust
@@ -111,6 +106,9 @@ fn main() {
// Reveal a path with the system's default explorer
opener.reveal_item_in_dir("/path/to/file")?;
// Reveal multiple paths with the system's default explorer
opener.reveal_items_in_dir(["/path/to/file"])?;
Ok(())
})
.run(tauri::generate_context!())
+1 -1
View File
@@ -1 +1 @@
if("__TAURI__"in window){var __TAURI_PLUGIN_OPENER__=function(n){"use strict";async function e(n,e={},_){return window.__TAURI_INTERNALS__.invoke(n,e,_)}return"function"==typeof SuppressedError&&SuppressedError,n.openPath=async function(n,_){await e("plugin:opener|open_path",{path:n,with:_})},n.openUrl=async function(n,_){await e("plugin:opener|open_url",{url:n,with:_})},n.revealItemInDir=async function(n){return e("plugin:opener|reveal_item_in_dir",{path:n})},n}({});Object.defineProperty(window.__TAURI__,"opener",{value:__TAURI_PLUGIN_OPENER__})}
if("__TAURI__"in window){var __TAURI_PLUGIN_OPENER__=function(n){"use strict";async function e(n,e={},r){return window.__TAURI_INTERNALS__.invoke(n,e,r)}return"function"==typeof SuppressedError&&SuppressedError,n.openPath=async function(n,r){await e("plugin:opener|open_path",{path:n,with:r})},n.openUrl=async function(n,r){await e("plugin:opener|open_url",{url:n,with:r})},n.revealItemInDir=async function(n){return e("plugin:opener|reveal_item_in_dir",{paths:"string"==typeof n?[n]:n})},n}({});Object.defineProperty(window.__TAURI__,"opener",{value:__TAURI_PLUGIN_OPENER__})}
+4 -2
View File
@@ -86,12 +86,14 @@ export async function openPath(path: string, openWith?: string): Promise<void> {
* ```typescript
* import { revealItemInDir } from '@tauri-apps/plugin-opener';
* await revealItemInDir('/path/to/file');
* await revealItemInDir([ '/path/to/file', '/path/to/another/file' ]);
* ```
*
* @param path The path to reveal.
*
* @since 2.0.0
*/
export async function revealItemInDir(path: string) {
return invoke('plugin:opener|reveal_item_in_dir', { path })
export async function revealItemInDir(path: string | string[]): Promise<void> {
const paths = typeof path === 'string' ? [path] : path
return invoke('plugin:opener|reveal_item_in_dir', { paths })
}
+3 -2
View File
@@ -69,7 +69,8 @@ pub async fn open_path<R: Runtime>(
}
}
/// TODO: in the next major version, rename to `reveal_items_in_dir`
#[tauri::command]
pub async fn reveal_item_in_dir(path: PathBuf) -> crate::Result<()> {
crate::reveal_item_in_dir(path)
pub async fn reveal_item_in_dir(paths: Vec<PathBuf>) -> crate::Result<()> {
crate::reveal_items_in_dir(&paths)
}
+3
View File
@@ -31,6 +31,9 @@ pub enum Error {
Win32Error(#[from] windows::core::Error),
#[error("Path doesn't have a parent: {0}")]
NoParent(PathBuf),
#[cfg(windows)]
#[error("Failed to convert path '{0}' to ITEMIDLIST")]
FailedToConvertPathToItemIdList(PathBuf),
#[error("Failed to convert path to file:// url")]
FailedToConvertPathToFileUrl,
#[error(transparent)]
+11 -3
View File
@@ -25,7 +25,7 @@ pub use error::Error;
type Result<T> = std::result::Result<T, Error>;
pub use open::{open_path, open_url};
pub use reveal_item_in_dir::reveal_item_in_dir;
pub use reveal_item_in_dir::{reveal_item_in_dir, reveal_items_in_dir};
pub struct Opener<R: Runtime> {
// we use `fn() -> R` to silence the unused generic error
@@ -146,7 +146,15 @@ impl<R: Runtime> Opener<R> {
}
pub fn reveal_item_in_dir<P: AsRef<Path>>(&self, p: P) -> Result<()> {
crate::reveal_item_in_dir::reveal_item_in_dir(p)
reveal_item_in_dir(p)
}
pub fn reveal_items_in_dir<I, P>(&self, paths: I) -> Result<()>
where
I: IntoIterator<Item = P>,
P: AsRef<Path>,
{
reveal_items_in_dir(paths)
}
}
@@ -213,7 +221,7 @@ impl Builder {
.invoke_handler(tauri::generate_handler![
commands::open_url,
commands::open_path,
commands::reveal_item_in_dir
commands::reveal_item_in_dir,
]);
if self.open_js_links_on_click {
+147 -44
View File
@@ -10,7 +10,7 @@ use std::path::Path;
///
/// - **Android / iOS:** Unsupported.
pub fn reveal_item_in_dir<P: AsRef<Path>>(path: P) -> crate::Result<()> {
let path = path.as_ref().canonicalize()?;
let path = dunce::canonicalize(path.as_ref())?;
#[cfg(any(
windows,
@@ -21,7 +21,47 @@ pub fn reveal_item_in_dir<P: AsRef<Path>>(path: P) -> crate::Result<()> {
target_os = "netbsd",
target_os = "openbsd"
))]
return imp::reveal_item_in_dir(&path);
return imp::reveal_items_in_dir(&[path]);
#[cfg(not(any(
windows,
target_os = "macos",
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
)))]
Err(crate::Error::UnsupportedPlatform)
}
/// Reveal the paths the system's default explorer.
///
/// ## Platform-specific:
///
/// - **Android / iOS:** Unsupported.
pub fn reveal_items_in_dir<I, P>(paths: I) -> crate::Result<()>
where
I: IntoIterator<Item = P>,
P: AsRef<Path>,
{
let mut canonicalized = vec![];
for path in paths {
let path = dunce::canonicalize(path.as_ref())?;
canonicalized.push(path);
}
#[cfg(any(
windows,
target_os = "macos",
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
))]
return imp::reveal_items_in_dir(&canonicalized);
#[cfg(not(any(
windows,
@@ -37,8 +77,10 @@ pub fn reveal_item_in_dir<P: AsRef<Path>>(path: P) -> crate::Result<()> {
#[cfg(windows)]
mod imp {
use super::*;
use std::collections::HashMap;
use std::path::{Path, PathBuf};
use windows::Win32::UI::Shell::Common::ITEMIDLIST;
use windows::{
core::{w, HSTRING, PCWSTR},
Win32::{
@@ -54,57 +96,98 @@ mod imp {
},
};
pub fn reveal_item_in_dir(path: &Path) -> crate::Result<()> {
let file = dunce::simplified(path);
pub fn reveal_items_in_dir(paths: &[PathBuf]) -> crate::Result<()> {
if paths.is_empty() {
return Ok(());
}
let mut grouped_paths: HashMap<&Path, Vec<&Path>> = HashMap::new();
for path in paths {
let parent = path
.parent()
.ok_or_else(|| crate::Error::NoParent(path.to_path_buf()))?;
grouped_paths.entry(parent).or_default().push(path);
}
let _ = unsafe { CoInitialize(None) };
let dir = file
.parent()
.ok_or_else(|| crate::Error::NoParent(file.to_path_buf()))?;
let dir = HSTRING::from(dir);
let dir_item = unsafe { ILCreateFromPathW(&dir) };
let file_h = HSTRING::from(file);
let file_item = unsafe { ILCreateFromPathW(&file_h) };
unsafe {
if let Err(e) = SHOpenFolderAndSelectItems(dir_item, Some(&[file_item]), 0) {
for (parent, to_reveals) in grouped_paths {
let parent_item_id_list = OwnedItemIdList::new(parent)?;
let to_reveals_item_id_list = to_reveals
.iter()
.map(|to_reveal| OwnedItemIdList::new(*to_reveal))
.collect::<crate::Result<Vec<_>>>()?;
if let Err(e) = unsafe {
SHOpenFolderAndSelectItems(
parent_item_id_list.item,
Some(
&to_reveals_item_id_list
.iter()
.map(|item| item.item)
.collect::<Vec<_>>(),
),
0,
)
} {
// from https://github.com/electron/electron/blob/10d967028af2e72382d16b7e2025d243b9e204ae/shell/common/platform_util_win.cc#L302
// On some systems, the above call mysteriously fails with "file not
// found" even though the file is there. In these cases, ShellExecute()
// seems to work as a fallback (although it won't select the file).
//
// Note: we only handle the first file here if multiple of are present
if e.code().0 == ERROR_FILE_NOT_FOUND.0 as i32 {
let is_dir = file.is_dir();
let first_path = to_reveals[0];
let is_dir = first_path.is_dir();
let mut info = SHELLEXECUTEINFOW {
cbSize: std::mem::size_of::<SHELLEXECUTEINFOW>() as _,
nShow: SW_SHOWNORMAL.0,
lpFile: PCWSTR(dir.as_ptr()),
lpFile: PCWSTR(parent_item_id_list.hstring.as_ptr()),
lpClass: if is_dir { w!("folder") } else { PCWSTR::null() },
lpVerb: if is_dir {
w!("explore")
} else {
PCWSTR::null()
},
..std::mem::zeroed()
..Default::default()
};
ShellExecuteExW(&mut info).inspect_err(|_| {
ILFree(Some(dir_item));
ILFree(Some(file_item));
})?;
unsafe { ShellExecuteExW(&mut info) }?;
}
}
}
unsafe {
ILFree(Some(dir_item));
ILFree(Some(file_item));
}
Ok(())
}
struct OwnedItemIdList {
hstring: HSTRING,
item: *const ITEMIDLIST,
}
impl OwnedItemIdList {
fn new(path: &Path) -> crate::Result<Self> {
let path_hstring = HSTRING::from(path);
let item_id_list = unsafe { ILCreateFromPathW(&path_hstring) };
if item_id_list.is_null() {
Err(crate::Error::FailedToConvertPathToItemIdList(
path.to_owned(),
))
} else {
Ok(Self {
hstring: path_hstring,
item: item_id_list,
})
}
}
}
impl Drop for OwnedItemIdList {
fn drop(&mut self) {
if !self.item.is_null() {
unsafe { ILFree(Some(self.item)) };
}
}
}
}
#[cfg(any(
@@ -115,24 +198,36 @@ mod imp {
target_os = "openbsd"
))]
mod imp {
use std::collections::HashMap;
use super::*;
use std::collections::HashMap;
use std::path::PathBuf;
pub fn reveal_item_in_dir(path: &Path) -> crate::Result<()> {
pub fn reveal_items_in_dir(paths: &[PathBuf]) -> crate::Result<()> {
let connection = zbus::blocking::Connection::session()?;
reveal_with_filemanager1(path, &connection)
.or_else(|_| reveal_with_open_uri_portal(path, &connection))
reveal_with_filemanager1(paths, &connection).or_else(|e| {
// Fallback to opening the directory of the first item if revealing multiple items fails.
if let Some(first_path) = paths.first() {
reveal_with_open_uri_portal(first_path, &connection)
} else {
Err(e)
}
})
}
fn reveal_with_filemanager1(
path: &Path,
paths: &[PathBuf],
connection: &zbus::blocking::Connection,
) -> crate::Result<()> {
let uri = url::Url::from_file_path(path)
.map_err(|_| crate::Error::FailedToConvertPathToFileUrl)?;
let uris: Result<Vec<_>, _> = paths
.iter()
.map(|path| {
url::Url::from_file_path(path)
.map_err(|_| crate::Error::FailedToConvertPathToFileUrl)
})
.collect();
let uris = uris?;
let uri_strs: Vec<&str> = uris.iter().map(|uri| uri.as_str()).collect();
#[zbus::proxy(
interface = "org.freedesktop.FileManager1",
@@ -145,7 +240,7 @@ mod imp {
let proxy = FileManager1ProxyBlocking::new(connection)?;
proxy.ShowItems(vec![uri.as_str()], "")
proxy.ShowItems(uri_strs, "")
}
fn reveal_with_open_uri_portal(
@@ -177,14 +272,22 @@ mod imp {
#[cfg(target_os = "macos")]
mod imp {
use super::*;
use objc2_app_kit::NSWorkspace;
use objc2_foundation::{NSArray, NSString, NSURL};
pub fn reveal_item_in_dir(path: &Path) -> crate::Result<()> {
use std::path::PathBuf;
pub fn reveal_items_in_dir(paths: &[PathBuf]) -> crate::Result<()> {
unsafe {
let path = path.to_string_lossy();
let path = NSString::from_str(&path);
let urls = vec![NSURL::fileURLWithPath(&path)];
let mut urls = Vec::new();
for path in paths {
let path = path.to_string_lossy();
let path = NSString::from_str(&path);
let url = NSURL::fileURLWithPath(&path);
urls.push(url);
}
let urls = NSArray::from_retained_slice(&urls);
let workspace = NSWorkspace::new();
-9
View File
@@ -33,21 +33,12 @@ tauri-plugin-os = { git = "https://github.com/tauri-apps/plugins-workspace", bra
You can install the JavaScript Guest bindings using your preferred JavaScript package manager:
> Note: Since most JavaScript package managers are unable to install packages from git monorepos we provide read-only mirrors of each plugin. This makes installation option 2 more ergonomic to use.
```sh
pnpm add @tauri-apps/plugin-os
# or
npm add @tauri-apps/plugin-os
# or
yarn add @tauri-apps/plugin-os
# alternatively with Git:
pnpm add https://github.com/tauri-apps/tauri-plugin-os#v2
# or
npm add https://github.com/tauri-apps/tauri-plugin-os#v2
# or
yarn add https://github.com/tauri-apps/tauri-plugin-os#v2
```
## Usage
-9
View File
@@ -35,21 +35,12 @@ tauri-plugin-positioner = { git = "https://github.com/tauri-apps/plugins-workspa
You can install the JavaScript Guest bindings using your preferred JavaScript package manager:
> Note: Since most JavaScript package managers are unable to install packages from git monorepos we provide read-only mirrors of each plugin. This makes installation option 2 more ergonomic to use.
```sh
pnpm add @tauri-apps/plugin-positioner
# or
npm add @tauri-apps/plugin-positioner
# or
yarn add @tauri-apps/plugin-positioner
# alternatively with Git:
pnpm add https://github.com/tauri-apps/tauri-plugin-positioner#v2
# or
npm add https://github.com/tauri-apps/tauri-plugin-positioner#v2
# or
yarn add https://github.com/tauri-apps/tauri-plugin-positioner#v2
```
## Usage
-9
View File
@@ -33,21 +33,12 @@ tauri-plugin-process = { git = "https://github.com/tauri-apps/plugins-workspace"
You can install the JavaScript Guest bindings using your preferred JavaScript package manager:
> Note: Since most JavaScript package managers are unable to install packages from git monorepos we provide read-only mirrors of each plugin. This makes installation option 2 more ergonomic to use.
```sh
pnpm add @tauri-apps/plugin-process
# or
npm add @tauri-apps/plugin-process
# or
yarn add @tauri-apps/plugin-process
# alternatively with Git:
pnpm add https://github.com/tauri-apps/tauri-plugin-process#v2
# or
npm add https://github.com/tauri-apps/tauri-plugin-process#v2
# or
yarn add https://github.com/tauri-apps/tauri-plugin-process#v2
```
## Usage
-9
View File
@@ -33,21 +33,12 @@ tauri-plugin-shell = { git = "https://github.com/tauri-apps/plugins-workspace",
You can install the JavaScript Guest bindings using your preferred JavaScript package manager:
> Note: Since most JavaScript package managers are unable to install packages from git monorepos we provide read-only mirrors of each plugin. This makes installation option 2 more ergonomic to use.
```sh
pnpm add @tauri-apps/plugin-shell
# or
npm add @tauri-apps/plugin-shell
# or
yarn add @tauri-apps/plugin-shell
# alternatively with Git:
pnpm add https://github.com/tauri-apps/tauri-plugin-shell#v2
# or
npm add https://github.com/tauri-apps/tauri-plugin-shell#v2
# or
yarn add https://github.com/tauri-apps/tauri-plugin-shell#v2
```
## Usage
+3 -3
View File
@@ -388,7 +388,7 @@ class Command<O extends IOPayload> extends EventEmitter<CommandEvents> {
* Creates a new `Command` instance.
*
* @param program The program name to execute.
* It must be configured on `tauri.conf.json > plugins > shell > scope`.
* It must be configured in your project's capabilities.
* @param args Program arguments.
* @param options Spawn options.
*/
@@ -425,7 +425,7 @@ class Command<O extends IOPayload> extends EventEmitter<CommandEvents> {
* ```
*
* @param program The program to execute.
* It must be configured on `tauri.conf.json > plugins > shell > scope`.
* It must be configured in your project's capabilities.
*/
static create<O extends IOPayload>(
program: string,
@@ -457,7 +457,7 @@ class Command<O extends IOPayload> extends EventEmitter<CommandEvents> {
* ```
*
* @param program The program to execute.
* It must be configured on `tauri.conf.json > plugins > shell > scope`.
* It must be configured in your project's capabilities.
*/
static sidecar<O extends IOPayload>(
program: string,
+6
View File
@@ -1,5 +1,11 @@
# Changelog
## \[2.3.2]
### Dependencies
- Upgraded to `deep-link@2.4.1`
## \[2.3.1]
- [`6f345870`](https://github.com/tauri-apps/plugins-workspace/commit/6f345870df4e7b187deb869df03b79858e03b4fe) ([#2860](https://github.com/tauri-apps/plugins-workspace/pull/2860) by [@MorpheusXAUT](https://github.com/tauri-apps/plugins-workspace/../../MorpheusXAUT)) Fix D-Bus name replacement logic on Linux to prevent multiple instances from acquiring the same well-known name.\
+2 -2
View File
@@ -1,6 +1,6 @@
[package]
name = "tauri-plugin-single-instance"
version = "2.3.1"
version = "2.3.2"
description = "Ensure a single instance of your tauri app is running."
authors = { workspace = true }
license = { workspace = true }
@@ -26,7 +26,7 @@ serde_json = { workspace = true }
tauri = { workspace = true }
tracing = { workspace = true }
thiserror = { workspace = true }
tauri-plugin-deep-link = { path = "../deep-link", version = "2.4.0", optional = true }
tauri-plugin-deep-link = { path = "../deep-link", version = "2.4.1", optional = true }
semver = { version = "1", optional = true }
[target."cfg(target_os = \"windows\")".dependencies.windows-sys]
@@ -9,6 +9,6 @@
"author": "",
"license": "MIT",
"devDependencies": {
"@tauri-apps/cli": "2.6.2"
"@tauri-apps/cli": "2.7.1"
}
}
-9
View File
@@ -35,21 +35,12 @@ branch = "v2"
You can install the JavaScript Guest bindings using your preferred JavaScript package manager:
> Note: Since most JavaScript package managers are unable to install packages from git monorepos we provide read-only mirrors of each plugin. This makes installation option 2 more ergonomic to use.
```sh
pnpm add @tauri-apps/plugin-sql
# or
npm add @tauri-apps/plugin-sql
# or
yarn add @tauri-apps/plugin-sql
# alternatively with Git:
pnpm add https://github.com/tauri-apps/tauri-plugin-sql#v2
# or
npm add https://github.com/tauri-apps/tauri-plugin-sql#v2
# or
yarn add https://github.com/tauri-apps/tauri-plugin-sql#v2
```
## Usage
-9
View File
@@ -33,21 +33,12 @@ tauri-plugin-store = { git = "https://github.com/tauri-apps/plugins-workspace",
You can install the JavaScript Guest bindings using your preferred JavaScript package manager:
> Note: Since most JavaScript package managers are unable to install packages from git monorepos we provide read-only mirrors of each plugin. This makes installation option 2 more ergonomic to use.
```sh
pnpm add @tauri-apps/plugin-store
# or
npm add @tauri-apps/plugin-store
# or
yarn add @tauri-apps/plugin-store
# alternatively with Git:
pnpm add https://github.com/tauri-apps/tauri-plugin-store#v2
# or
npm add https://github.com/tauri-apps/tauri-plugin-store#v2
# or
yarn add https://github.com/tauri-apps/tauri-plugin-store#v2
```
## Usage
+1 -1
View File
@@ -1 +1 @@
if("__TAURI__"in window){var __TAURI_PLUGIN_STORE__=function(t){"use strict";var e,a;function r(t,e=!1){return window.__TAURI_INTERNALS__.transformCallback(t,e)}async function s(t,e={},a){return window.__TAURI_INTERNALS__.invoke(t,e,a)}"function"==typeof SuppressedError&&SuppressedError;class i{get rid(){return function(t,e,a,r){if("function"==typeof e?t!==e||!r:!e.has(t))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===a?r:"a"===a?r.call(t):r?r.value:e.get(t)}(this,e,"f")}constructor(t){e.set(this,void 0),function(t,e,a){if("function"==typeof e||!e.has(t))throw new TypeError("Cannot write private member to an object whose class did not declare it");e.set(t,a)}(this,e,t)}async close(){return s("plugin:resources|close",{rid:this.rid})}}async function n(t,e,a){const i={kind:"Any"};return s("plugin:event|listen",{event:t,target:i,handler:r(e)}).then((e=>async()=>async function(t,e){window.__TAURI_EVENT_PLUGIN_INTERNALS__.unregisterListener(t,e),await s("plugin:event|unlisten",{event:t,eventId:e})}(t,e)))}async function o(t,e){return await u.load(t,e)}e=new WeakMap,function(t){t.WINDOW_RESIZED="tauri://resize",t.WINDOW_MOVED="tauri://move",t.WINDOW_CLOSE_REQUESTED="tauri://close-requested",t.WINDOW_DESTROYED="tauri://destroyed",t.WINDOW_FOCUS="tauri://focus",t.WINDOW_BLUR="tauri://blur",t.WINDOW_SCALE_FACTOR_CHANGED="tauri://scale-change",t.WINDOW_THEME_CHANGED="tauri://theme-changed",t.WINDOW_CREATED="tauri://window-created",t.WEBVIEW_CREATED="tauri://webview-created",t.DRAG_ENTER="tauri://drag-enter",t.DRAG_OVER="tauri://drag-over",t.DRAG_DROP="tauri://drag-drop",t.DRAG_LEAVE="tauri://drag-leave"}(a||(a={}));class u extends i{constructor(t){super(t)}static async load(t,e){const a=await s("plugin:store|load",{path:t,...e});return new u(a)}static async get(t){return await s("plugin:store|get_store",{path:t}).then((t=>t?new u(t):null))}async set(t,e){await s("plugin:store|set",{rid:this.rid,key:t,value:e})}async get(t){const[e,a]=await s("plugin:store|get",{rid:this.rid,key:t});return a?e:void 0}async has(t){return await s("plugin:store|has",{rid:this.rid,key:t})}async delete(t){return await s("plugin:store|delete",{rid:this.rid,key:t})}async clear(){await s("plugin:store|clear",{rid:this.rid})}async reset(){await s("plugin:store|reset",{rid:this.rid})}async keys(){return await s("plugin:store|keys",{rid:this.rid})}async values(){return await s("plugin:store|values",{rid:this.rid})}async entries(){return await s("plugin:store|entries",{rid:this.rid})}async length(){return await s("plugin:store|length",{rid:this.rid})}async reload(){await s("plugin:store|reload",{rid:this.rid})}async save(){await s("plugin:store|save",{rid:this.rid})}async onKeyChange(t,e){return await n("store://change",(a=>{a.payload.resourceId===this.rid&&a.payload.key===t&&e(a.payload.exists?a.payload.value:void 0)}))}async onChange(t){return await n("store://change",(e=>{e.payload.resourceId===this.rid&&t(e.payload.key,e.payload.exists?e.payload.value:void 0)}))}}return t.LazyStore=class{get store(){return this._store||(this._store=o(this.path,this.options)),this._store}constructor(t,e){this.path=t,this.options=e}async init(){await this.store}async set(t,e){return(await this.store).set(t,e)}async get(t){return(await this.store).get(t)}async has(t){return(await this.store).has(t)}async delete(t){return(await this.store).delete(t)}async clear(){await(await this.store).clear()}async reset(){await(await this.store).reset()}async keys(){return(await this.store).keys()}async values(){return(await this.store).values()}async entries(){return(await this.store).entries()}async length(){return(await this.store).length()}async reload(){await(await this.store).reload()}async save(){await(await this.store).save()}async onKeyChange(t,e){return(await this.store).onKeyChange(t,e)}async onChange(t){return(await this.store).onChange(t)}async close(){this._store&&await(await this._store).close()}},t.Store=u,t.getStore=async function(t){return await u.get(t)},t.load=o,t}({});Object.defineProperty(window.__TAURI__,"store",{value:__TAURI_PLUGIN_STORE__})}
if("__TAURI__"in window){var __TAURI_PLUGIN_STORE__=function(t){"use strict";var e,a;function r(t,e=!1){return window.__TAURI_INTERNALS__.transformCallback(t,e)}async function s(t,e={},a){return window.__TAURI_INTERNALS__.invoke(t,e,a)}"function"==typeof SuppressedError&&SuppressedError;class i{get rid(){return function(t,e,a,r){if("function"==typeof e?t!==e||!r:!e.has(t))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===a?r:"a"===a?r.call(t):r?r.value:e.get(t)}(this,e,"f")}constructor(t){e.set(this,void 0),function(t,e,a){if("function"==typeof e||!e.has(t))throw new TypeError("Cannot write private member to an object whose class did not declare it");e.set(t,a)}(this,e,t)}async close(){return s("plugin:resources|close",{rid:this.rid})}}async function n(t,e,a){const i={kind:"Any"};return s("plugin:event|listen",{event:t,target:i,handler:r(e)}).then((e=>async()=>async function(t,e){window.__TAURI_EVENT_PLUGIN_INTERNALS__.unregisterListener(t,e),await s("plugin:event|unlisten",{event:t,eventId:e})}(t,e)))}async function o(t,e){return await u.load(t,e)}e=new WeakMap,function(t){t.WINDOW_RESIZED="tauri://resize",t.WINDOW_MOVED="tauri://move",t.WINDOW_CLOSE_REQUESTED="tauri://close-requested",t.WINDOW_DESTROYED="tauri://destroyed",t.WINDOW_FOCUS="tauri://focus",t.WINDOW_BLUR="tauri://blur",t.WINDOW_SCALE_FACTOR_CHANGED="tauri://scale-change",t.WINDOW_THEME_CHANGED="tauri://theme-changed",t.WINDOW_CREATED="tauri://window-created",t.WEBVIEW_CREATED="tauri://webview-created",t.DRAG_ENTER="tauri://drag-enter",t.DRAG_OVER="tauri://drag-over",t.DRAG_DROP="tauri://drag-drop",t.DRAG_LEAVE="tauri://drag-leave"}(a||(a={}));class u extends i{constructor(t){super(t)}static async load(t,e){const a=await s("plugin:store|load",{path:t,options:e});return new u(a)}static async get(t){return await s("plugin:store|get_store",{path:t}).then((t=>t?new u(t):null))}async set(t,e){await s("plugin:store|set",{rid:this.rid,key:t,value:e})}async get(t){const[e,a]=await s("plugin:store|get",{rid:this.rid,key:t});return a?e:void 0}async has(t){return await s("plugin:store|has",{rid:this.rid,key:t})}async delete(t){return await s("plugin:store|delete",{rid:this.rid,key:t})}async clear(){await s("plugin:store|clear",{rid:this.rid})}async reset(){await s("plugin:store|reset",{rid:this.rid})}async keys(){return await s("plugin:store|keys",{rid:this.rid})}async values(){return await s("plugin:store|values",{rid:this.rid})}async entries(){return await s("plugin:store|entries",{rid:this.rid})}async length(){return await s("plugin:store|length",{rid:this.rid})}async reload(t){await s("plugin:store|reload",{rid:this.rid,...t})}async save(){await s("plugin:store|save",{rid:this.rid})}async onKeyChange(t,e){return await n("store://change",(a=>{a.payload.resourceId===this.rid&&a.payload.key===t&&e(a.payload.exists?a.payload.value:void 0)}))}async onChange(t){return await n("store://change",(e=>{e.payload.resourceId===this.rid&&t(e.payload.key,e.payload.exists?e.payload.value:void 0)}))}}return t.LazyStore=class{get store(){return this._store||(this._store=o(this.path,this.options)),this._store}constructor(t,e){this.path=t,this.options=e}async init(){await this.store}async set(t,e){return(await this.store).set(t,e)}async get(t){return(await this.store).get(t)}async has(t){return(await this.store).has(t)}async delete(t){return(await this.store).delete(t)}async clear(){await(await this.store).clear()}async reset(){await(await this.store).reset()}async keys(){return(await this.store).keys()}async values(){return(await this.store).values()}async entries(){return(await this.store).entries()}async length(){return(await this.store).length()}async reload(t){await(await this.store).reload(t)}async save(){await(await this.store).save()}async onKeyChange(t,e){return(await this.store).onKeyChange(t,e)}async onChange(t){return(await this.store).onChange(t)}async close(){this._store&&await(await this._store).close()}},t.Store=u,t.getStore=async function(t){return await u.get(t)},t.load=o,t}({});Object.defineProperty(window.__TAURI__,"store",{value:__TAURI_PLUGIN_STORE__})}
@@ -8,7 +8,7 @@
"tauri": "tauri"
},
"devDependencies": {
"@tauri-apps/cli": "2.6.2",
"@tauri-apps/cli": "2.7.1",
"typescript": "^5.7.3",
"vite": "^7.0.4"
}
+30 -7
View File
@@ -18,6 +18,10 @@ interface ChangePayload<T> {
* Options to create a store
*/
export type StoreOptions = {
/**
* Default value of the store
*/
defaults: { [key: string]: unknown }
/**
* Auto save on modification with debounce duration in milliseconds, it's 100ms by default, pass in `false` to disable it
*/
@@ -34,6 +38,10 @@ export type StoreOptions = {
* Force create a new store with default values even if it already exists.
*/
createNew?: boolean
/**
* When creating the store, override the store with the on-disk state if it exists, ignoring defaults
*/
overrideDefaults?: boolean
}
/**
@@ -145,8 +153,8 @@ export class LazyStore implements IStore {
return (await this.store).length()
}
async reload(): Promise<void> {
await (await this.store).reload()
async reload(options?: ReloadOptions): Promise<void> {
await (await this.store).reload(options)
}
async save(): Promise<void> {
@@ -196,7 +204,7 @@ export class Store extends Resource implements IStore {
static async load(path: string, options?: StoreOptions): Promise<Store> {
const rid = await invoke<number>('plugin:store|load', {
path,
...options
options
})
return new Store(rid)
}
@@ -280,8 +288,8 @@ export class Store extends Resource implements IStore {
return await invoke('plugin:store|length', { rid: this.rid })
}
async reload(): Promise<void> {
await invoke('plugin:store|reload', { rid: this.rid })
async reload(options?: ReloadOptions): Promise<void> {
await invoke('plugin:store|reload', { rid: this.rid, ...options })
}
async save(): Promise<void> {
@@ -396,10 +404,15 @@ interface IStore {
*
* This method is useful if the on-disk state was edited by the user and you want to synchronize the changes.
*
* Note: This method does not emit change events.
* Note:
* - This method loads the data and merges it with the current store,
* this behavior will be changed to resetting to default first and then merging with the on-disk state in v3,
* to fully match the store with the on-disk state, set {@linkcode ReloadOptions.ignoreDefaults} to `true`
* - This method does not emit change events.
*
* @returns
*/
reload(): Promise<void>
reload(options?: ReloadOptions): Promise<void>
/**
* Saves the store to disk at the store's `path`.
@@ -437,3 +450,13 @@ interface IStore {
*/
close(): Promise<void>
}
/**
* Options to {@linkcode IStore.reload} a {@linkcode IStore}
*/
export type ReloadOptions = {
/**
* To fully match the store with the on-disk state, ignoring defaults
*/
ignoreDefaults?: boolean
}
+43 -23
View File
@@ -53,17 +53,36 @@ enum AutoSave {
Bool(bool),
}
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
struct LoadStoreOptions {
defaults: Option<HashMap<String, JsonValue>>,
auto_save: Option<AutoSave>,
serialize_fn_name: Option<String>,
deserialize_fn_name: Option<String>,
#[serde(default)]
create_new: bool,
#[serde(default)]
override_defaults: bool,
}
fn builder<R: Runtime>(
app: AppHandle<R>,
store_state: State<'_, StoreState>,
path: PathBuf,
auto_save: Option<AutoSave>,
serialize_fn_name: Option<String>,
deserialize_fn_name: Option<String>,
create_new: bool,
options: Option<LoadStoreOptions>,
) -> Result<StoreBuilder<R>> {
let mut builder = app.store_builder(path);
if let Some(auto_save) = auto_save {
let Some(options) = options else {
return Ok(builder);
};
if let Some(defaults) = options.defaults {
builder = builder.defaults(defaults);
}
if let Some(auto_save) = options.auto_save {
match auto_save {
AutoSave::DebounceDuration(duration) => {
builder = builder.auto_save(Duration::from_millis(duration));
@@ -75,7 +94,7 @@ fn builder<R: Runtime>(
}
}
if let Some(serialize_fn_name) = serialize_fn_name {
if let Some(serialize_fn_name) = options.serialize_fn_name {
let serialize_fn = store_state
.serialize_fns
.get(&serialize_fn_name)
@@ -83,7 +102,7 @@ fn builder<R: Runtime>(
builder = builder.serialize(*serialize_fn);
}
if let Some(deserialize_fn_name) = deserialize_fn_name {
if let Some(deserialize_fn_name) = options.deserialize_fn_name {
let deserialize_fn = store_state
.deserialize_fns
.get(&deserialize_fn_name)
@@ -91,10 +110,14 @@ fn builder<R: Runtime>(
builder = builder.deserialize(*deserialize_fn);
}
if create_new {
if options.create_new {
builder = builder.create_new();
}
if options.override_defaults {
builder = builder.override_defaults();
}
Ok(builder)
}
@@ -103,20 +126,9 @@ async fn load<R: Runtime>(
app: AppHandle<R>,
store_state: State<'_, StoreState>,
path: PathBuf,
auto_save: Option<AutoSave>,
serialize_fn_name: Option<String>,
deserialize_fn_name: Option<String>,
create_new: Option<bool>,
options: Option<LoadStoreOptions>,
) -> Result<ResourceId> {
let builder = builder(
app,
store_state,
path,
auto_save,
serialize_fn_name,
deserialize_fn_name,
create_new.unwrap_or_default(),
)?;
let builder = builder(app, store_state, path, options)?;
let (_, rid) = builder.build_inner()?;
Ok(rid)
}
@@ -209,9 +221,17 @@ async fn length<R: Runtime>(app: AppHandle<R>, rid: ResourceId) -> Result<usize>
}
#[tauri::command]
async fn reload<R: Runtime>(app: AppHandle<R>, rid: ResourceId) -> Result<()> {
async fn reload<R: Runtime>(
app: AppHandle<R>,
rid: ResourceId,
ignore_defaults: Option<bool>,
) -> Result<()> {
let store = app.resources_table().get::<Store<R>>(rid)?;
store.reload()
if ignore_defaults.unwrap_or_default() {
store.reload_ignore_defaults()
} else {
store.reload()
}
}
#[tauri::command]
+36 -1
View File
@@ -39,6 +39,7 @@ pub struct StoreBuilder<R: Runtime> {
deserialize_fn: DeserializeFn,
auto_save: Option<Duration>,
create_new: bool,
override_defaults: bool,
}
impl<R: Runtime> StoreBuilder<R> {
@@ -66,6 +67,7 @@ impl<R: Runtime> StoreBuilder<R> {
deserialize_fn,
auto_save: Some(Duration::from_millis(100)),
create_new: false,
override_defaults: false,
}
}
@@ -178,6 +180,12 @@ impl<R: Runtime> StoreBuilder<R> {
self
}
/// Override the store values when creating the store, ignoring defaults.
pub fn override_defaults(mut self) -> Self {
self.override_defaults = true;
self
}
pub(crate) fn build_inner(mut self) -> crate::Result<(Arc<Store<R>>, ResourceId)> {
let stores = self.app.state::<StoreState>().stores.clone();
let mut stores = stores.lock().unwrap();
@@ -205,7 +213,11 @@ impl<R: Runtime> StoreBuilder<R> {
);
if !self.create_new {
let _ = store_inner.load();
if self.override_defaults {
let _ = store_inner.load_ignore_defaults();
} else {
let _ = store_inner.load();
}
}
let store = Store {
@@ -284,6 +296,8 @@ impl<R: Runtime> StoreInner<R> {
}
/// Update the store from the on-disk state
///
/// Note: This method loads the data and merges it with the current store
pub fn load(&mut self) -> crate::Result<()> {
let bytes = fs::read(&self.path)?;
@@ -293,6 +307,13 @@ impl<R: Runtime> StoreInner<R> {
Ok(())
}
/// Load the store from the on-disk state, ignoring defaults
pub fn load_ignore_defaults(&mut self) -> crate::Result<()> {
let bytes = fs::read(&self.path)?;
self.cache = (self.deserialize_fn)(&bytes).map_err(crate::Error::Deserialize)?;
Ok(())
}
/// Inserts a key-value pair into the store.
pub fn set(&mut self, key: impl Into<String>, value: impl Into<JsonValue>) {
let key = key.into();
@@ -499,10 +520,24 @@ impl<R: Runtime> Store<R> {
}
/// Update the store from the on-disk state
///
/// Note:
/// - This method loads the data and merges it with the current store,
/// this behavior will be changed to resetting to default first and then merging with the on-disk state in v3,
/// to fully match the store with the on-disk state,
/// use [`reload_override_defaults`](Self::reload_override_defaults) instead
/// - This method does not emit change events
pub fn reload(&self) -> crate::Result<()> {
self.store.lock().unwrap().load()
}
/// Load the store from the on-disk state, ignoring defaults
///
/// Note: This method does not emit change events
pub fn reload_ignore_defaults(&self) -> crate::Result<()> {
self.store.lock().unwrap().load_ignore_defaults()
}
/// Saves the store to disk at the store's `path`.
pub fn save(&self) -> crate::Result<()> {
if let Some(sender) = self.auto_save_debounce_sender.lock().unwrap().take() {
+3 -3
View File
@@ -34,11 +34,11 @@ iota-crypto = "0.23"
hex = "0.4"
zeroize = { version = "1", features = ["zeroize_derive"] }
rust-argon2 = { version = "2", optional = true }
rand_chacha = { version = "0.3.1", optional = true }
rand_core = { version = "0.6.4", features = ["getrandom"], optional = true }
rand_chacha = { version = "0.9", optional = true }
rand_core = { version = "0.9", features = ["os_rng"], optional = true }
[dev-dependencies]
rand = "0.8"
rand = "0.9"
rusty-fork = "0.3"
[features]
-7
View File
@@ -41,13 +41,6 @@ pnpm add @tauri-apps/plugin-stronghold
npm add @tauri-apps/plugin-stronghold
# or
yarn add @tauri-apps/plugin-stronghold
# alternatively with Git:
pnpm add https://github.com/tauri-apps/tauri-plugin-stronghold#v2
# or
npm add https://github.com/tauri-apps/tauri-plugin-stronghold#v2
# or
yarn add https://github.com/tauri-apps/tauri-plugin-stronghold#v2
```
## Usage
+1 -1
View File
@@ -32,7 +32,7 @@ fn create_or_get_salt(salt: &mut [u8], salt_path: &Path) {
salt.clone_from_slice(&tmp);
} else {
// Generate new salt
let mut gen = ChaCha20Rng::from_entropy();
let mut gen = ChaCha20Rng::from_os_rng();
gen.fill_bytes(salt);
std::fs::write(salt_path, salt).expect("Failed to write salt for Stronghold")
}
-9
View File
@@ -34,21 +34,12 @@ tauri-plugin-updater = { git = "https://github.com/tauri-apps/plugins-workspace"
You can install the JavaScript Guest bindings using your preferred JavaScript package manager:
> Note: Since most JavaScript package managers are unable to install packages from git monorepos we provide read-only mirrors of each plugin. This makes installation option 2 more ergonomic to use.
```sh
pnpm add @tauri-apps/plugin-updater
# or
npm add @tauri-apps/plugin-updater
# or
yarn add @tauri-apps/plugin-updater
# alternatively with Git:
pnpm add https://github.com/tauri-apps/tauri-plugin-updater#v2
# or
npm add https://github.com/tauri-apps/tauri-plugin-updater#v2
# or
yarn add https://github.com/tauri-apps/tauri-plugin-updater#v2
```
## Usage
-9
View File
@@ -34,21 +34,12 @@ tauri-plugin-upload = { git = "https://github.com/tauri-apps/plugins-workspace",
You can install the JavaScript Guest bindings using your preferred JavaScript package manager:
> Note: Since most JavaScript package managers are unable to install packages from git monorepos we provide read-only mirrors of each plugin. This makes installation option 2 more ergonomic to use.
```sh
pnpm add @tauri-apps/plugin-upload
# or
npm add @tauri-apps/plugin-upload
# or
yarn add @tauri-apps/plugin-upload
# alternatively with Git:
pnpm add https://github.com/tauri-apps/tauri-plugin-upload#v2
# or
npm add https://github.com/tauri-apps/tauri-plugin-upload#v2
# or
yarn add https://github.com/tauri-apps/tauri-plugin-upload#v2
```
## Usage
+1 -1
View File
@@ -31,7 +31,7 @@ tauri = { workspace = true }
log = { workspace = true }
thiserror = { workspace = true }
http = "1"
rand = "0.8"
rand = "0.9"
futures-util = "0.3"
tokio = { version = "1", features = ["net", "sync"] }
tokio-tungstenite = { version = "0.27" }
-9
View File
@@ -33,21 +33,12 @@ tauri-plugin-websocket = { git = "https://github.com/tauri-apps/plugins-workspac
You can install the JavaScript Guest bindings using your preferred JavaScript package manager:
> Note: Since most JavaScript package managers are unable to install packages from git monorepos we provide read-only mirrors of each plugin. This makes installation option 2 more ergonomic to use.
```sh
pnpm add @tauri-apps/plugin-websocket
# or
npm add @tauri-apps/plugin-websocket
# or
yarn add @tauri-apps/plugin-websocket
# alternatively with Git:
pnpm add https://github.com/tauri-apps/tauri-plugin-websocket#v2
# or
npm add https://github.com/tauri-apps/tauri-plugin-websocket#v2
# or
yarn add https://github.com/tauri-apps/tauri-plugin-websocket#v2
```
## Usage
@@ -9,7 +9,7 @@
"preview": "vite preview"
},
"devDependencies": {
"@tauri-apps/cli": "2.6.2",
"@tauri-apps/cli": "2.7.1",
"typescript": "^5.7.3",
"vite": "^7.0.4"
},
-9
View File
@@ -33,21 +33,12 @@ tauri-plugin-window-state = { git = "https://github.com/tauri-apps/plugins-works
You can install the JavaScript Guest bindings using your preferred JavaScript package manager:
> Note: Since most JavaScript package managers are unable to install packages from git monorepos we provide read-only mirrors of each plugin. This makes installation option 2 more ergonomic to use.
```sh
pnpm add @tauri-apps/plugin-window-state
# or
npm add @tauri-apps/plugin-window-state
# or
yarn add @tauri-apps/plugin-window-state
# alternatively with Git:
pnpm add https://github.com/tauri-apps/tauri-plugin-window-state#v2
# or
npm add https://github.com/tauri-apps/tauri-plugin-window-state#v2
# or
yarn add https://github.com/tauri-apps/tauri-plugin-window-state#v2
```
## Usage
+330 -330
View File
File diff suppressed because it is too large Load Diff
-11
View File
@@ -33,23 +33,12 @@ tauri-plugin-PLUGIN_NAME = { git = "https://github.com/tauri-apps/plugins-worksp
You can install the JavaScript Guest bindings using your preferred JavaScript package manager:
> Note: Since most JavaScript package managers are unable to install packages from git monorepos we provide read-only mirrors of each plugin. This makes installation option 2 more ergonomic to use.
<!-- Add the branch for installations using git! -->
```sh
pnpm add @tauri-apps/plugin-PLUGIN_NAME
# or
npm add @tauri-apps/plugin-PLUGIN_NAME
# or
yarn add @tauri-apps/plugin-PLUGIN_NAME
# alternatively with Git:
pnpm add https://github.com/tauri-apps/tauri-plugin-PLUGIN_NAME#v2
# or
npm add https://github.com/tauri-apps/tauri-plugin-PLUGIN_NAME#v2
# or
yarn add https://github.com/tauri-apps/tauri-plugin-PLUGIN_NAME#v2
```
## Usage