Merge branch 'tauri-apps:v1' into no_record

pull/129/head
Ludea 2 years ago committed by GitHub
commit 1ef60fc262
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -0,0 +1,159 @@
{
"gitSiteUrl": "https://github.com/tauri-apps/plugins-workspace/",
"pkgManagers": {
"javascript": {
"version": true,
"getPublishedVersion": "node ../../.scripts/covector/package-latest-version.cjs npm ${ pkgFile.pkg.name } ${ pkgFile.pkg.version }",
"publish": ["pnpm build", "pnpm publish --access public --no-git-checks"]
},
"rust": {
"version": true,
"getPublishedVersion": "node ../../.scripts/covector/package-latest-version.cjs cargo ${ pkgFile.pkg.package.name } ${ pkgFile.pkg.package.version }",
"publish": [
{
"command": "cargo package --no-verify",
"dryRunCommand": true
},
{
"command": "echo '<details>\n<summary><em><h4>Cargo Publish</h4></em></summary>\n\n```'",
"dryRunCommand": true,
"pipe": true
},
{
"command": "cargo publish",
"dryRunCommand": "cargo publish --dry-run",
"pipe": true
},
{
"command": "echo '```\n\n</details>\n'",
"dryRunCommand": true,
"pipe": true
}
]
}
},
"packages": {
"authenticator": {
"path": "./plugins/authenticator",
"manager": "rust-disabled"
},
"authenticator-js": {
"path": "./plugins/authenticator",
"manager": "javascript-disabled"
},
"autostart": {
"path": "./plugins/autostart",
"manager": "rust-disabled"
},
"autostart-js": {
"path": "./plugins/autostart",
"manager": "javascript-disabled"
},
"fs-extra": {
"path": "./plugins/fs-extra",
"manager": "rust-disabled"
},
"fs-extra-js": {
"path": "./plugins/fs-extra",
"manager": "javascript-disabled"
},
"fs-watch": {
"path": "./plugins/fs-watch",
"manager": "rust-disabled"
},
"fs-watch-js": {
"path": "./plugins/fs-watch",
"manager": "javascript-disabled"
},
"localhost": {
"path": "./plugins/localhost",
"manager": "rust"
},
"log": {
"path": "./plugins/log",
"manager": "rust-disabled"
},
"log-js": {
"path": "./plugins/log",
"manager": "javascript-disabled"
},
"persisted-scope": {
"path": "./plugins/persisted-scope",
"manager": "rust"
},
"positioner": {
"path": "./plugins/positioner",
"manager": "rust"
},
"positioner-js": {
"path": "./plugins/positioner",
"manager": "javascript-disabled"
},
"single-instance": {
"path": "./plugins/single-instance",
"manager": "rust-disabled"
},
"sql": {
"path": "./plugins/sql",
"manager": "rust-disabled"
},
"sql-js": {
"path": "./plugins/sql",
"manager": "javascript-disabled"
},
"store": {
"path": "./plugins/store",
"manager": "rust-disabled"
},
"store-js": {
"path": "./plugins/store",
"manager": "javascript-disabled"
},
"stronghold": {
"path": "./plugins/stronghold",
"manager": "rust-disabled"
},
"stronghold-js": {
"path": "./plugins/stronghold",
"manager": "javascript-disabled"
},
"upload": {
"path": "./plugins/upload",
"manager": "rust-disabled"
},
"upload-js": {
"path": "./plugins/upload",
"manager": "javascript-disabled"
},
"websocket": {
"path": "./plugins/websocket",
"manager": "rust-disabled"
},
"websocket-js": {
"path": "./plugins/websocket",
"manager": "javascript-disabled"
},
"window-state": {
"path": "./plugins/window-state",
"manager": "rust"
},
"window-state-js": {
"path": "./plugins/window-state",
"manager": "javascript-disabled"
}
}
}

@ -0,0 +1,30 @@
# Changes
##### via https://github.com/jbolda/covector
As you create PRs and make changes that require a version bump, please add a new markdown file in this folder. You do not note the version _number_, but rather the type of bump that you expect: major, minor, or patch. The filename is not important, as long as it is a `.md`, but we recommend that it represents the overall change for organizational purposes.
When you select the version bump required, you do _not_ need to consider dependencies. Only note the package with the actual change, and any packages that depend on that package will be bumped automatically in the process.
Use the following format:
```md
---
"package-a": patch
"package-b": minor
---
Change summary goes here
```
Summaries do not have a specific character limit, but are text only. These summaries are used within the (future implementation of) changelogs. They will give context to the change and also point back to the original PR if more details and context are needed.
Changes will be designated as a `major`, `minor` or `patch` as further described in [semver](https://semver.org/).
Given a version number MAJOR.MINOR.PATCH, increment the:
- MAJOR version when you make incompatible API changes,
- MINOR version when you add functionality in a backwards compatible manner, and
- PATCH version when you make backwards compatible bug fixes.
Additional labels for pre-release and build metadata are available as extensions to the MAJOR.MINOR.PATCH format, but will be discussed prior to usage (as extra steps will be necessary in consideration of merging and publishing).

@ -1,3 +1,4 @@
target target
node_modules node_modules
dist dist
dist-js

@ -6,7 +6,8 @@
"extends": [ "extends": [
"prettier", "prettier",
"eslint:recommended", "eslint:recommended",
"plugin:@typescript-eslint/recommended" "plugin:@typescript-eslint/recommended",
"plugin:security/recommended"
], ],
"overrides": [], "overrides": [],
"parser": "@typescript-eslint/parser", "parser": "@typescript-eslint/parser",

@ -38,9 +38,10 @@ fi
if [[ -z "$COMMIT_MESSAGE" ]]; then if [[ -z "$COMMIT_MESSAGE" ]]; then
MONOREPO_COMMIT_MESSAGE=$(cd "${SOURCE_DIR:-.}" && git show -s --format=%B $GITHUB_SHA) MONOREPO_COMMIT_MESSAGE=$(cd "${SOURCE_DIR:-.}" && git show -s --format=%B $GITHUB_SHA)
COMMIT_MESSAGE=$( printf "%s\n\nCommitted via a GitHub action: https://github.com/%s/actions/runs/%s\n" "$MONOREPO_COMMIT_MESSAGE" "$GITHUB_REPOSITORY" "$GITHUB_RUN_ID" ) COMMIT_MESSAGE=$( printf "%s\n\nCommitted via a GitHub action: https://github.com/%s/actions/runs/%s" "$MONOREPO_COMMIT_MESSAGE" "$GITHUB_REPOSITORY" "$GITHUB_RUN_ID" )
fi fi
COMMIT_ORIGINAL_AUTHOR="${GITHUB_ACTOR} <${GITHUB_ACTOR}@users.noreply.github.com>" COMMIT_ACTOR="${GITHUB_ACTOR} <${GITHUB_ACTOR}@users.noreply.github.com>"
COMMIT_AUTHOR=$(cd "${SOURCE_DIR:-.}" &&git show -s --format="%an <%ae>" $GITHUB_SHA)
if [[ "$GITHUB_REF" =~ ^refs/heads/ ]]; then if [[ "$GITHUB_REF" =~ ^refs/heads/ ]]; then
BRANCH=${GITHUB_REF#refs/heads/} BRANCH=${GITHUB_REF#refs/heads/}
@ -59,6 +60,9 @@ fi
# : > "$BUILD_BASE/changes.diff" # : > "$BUILD_BASE/changes.diff"
# Collect tags of current commit
readarray -t COMMIT_TAGS < <(git tag --points-at HEAD)
EXIT=0 EXIT=0
while read -r PLUGIN_NAME; do while read -r PLUGIN_NAME; do
printf "\n\n\e[7m Mirror: %s \e[0m\n" "$PLUGIN_NAME" printf "\n\n\e[7m Mirror: %s \e[0m\n" "$PLUGIN_NAME"
@ -98,12 +102,24 @@ while read -r PLUGIN_NAME; do
if [[ -n "$FORCE_COMMIT" || -n "$(git status --porcelain)" ]]; then if [[ -n "$FORCE_COMMIT" || -n "$(git status --porcelain)" ]]; then
echo "Committing to $PLUGIN_NAME" echo "Committing to $PLUGIN_NAME"
if git commit $FORCE_COMMIT --author="${COMMIT_ORIGINAL_AUTHOR}" -m "${COMMIT_MESSAGE}" && GIT_CLI_COMMIT_MESSAGE=$( printf "%s \n\nCo-authored-by: %s" "$COMMIT_MESSAGE" "$COMMIT_ACTOR" )
if git commit $FORCE_COMMIT --author="${COMMIT_AUTHOR}" -m "${GIT_CLI_COMMIT_MESSAGE}" &&
{ [[ -z "$CI" ]] || git push origin "$BRANCH"; } # Only do the actual push from the GitHub Action { [[ -z "$CI" ]] || git push origin "$BRANCH"; } # Only do the actual push from the GitHub Action
then then
# echo "$BUILD_BASE/changes.diff" # echo "$BUILD_BASE/changes.diff"
# git show --pretty= --src-prefix="a/$PLUGIN_NAME/" --dst-prefix="b/$PLUGIN_NAME/" >> "$BUILD_BASE/changes.diff" # git show --pretty= --src-prefix="a/$PLUGIN_NAME/" --dst-prefix="b/$PLUGIN_NAME/" >> "$BUILD_BASE/changes.diff"
echo "https://github.com/tauri-apps/tauri-plugin-$PLUGIN_NAME/commit/$(git rev-parse HEAD)" echo "https://github.com/tauri-apps/tauri-plugin-$PLUGIN_NAME/commit/$(git rev-parse HEAD)"
# Add new tags
for FULL_TAG in "${COMMIT_TAGS[@]}"; do
if [[ "$FULL_TAG" =~ ^"$PLUGIN_NAME-js-v" ]]; then
TAG_NAME="${FULL_TAG#"$PLUGIN_NAME-js-"}"
echo "Creating tag $TAG_NAME"
git tag "${TAG_NAME}" -m "${GIT_CLI_COMMIT_MESSAGE}"
git push origin "${TAG_NAME}"
fi
done
echo "Completed $PLUGIN_NAME" echo "Completed $PLUGIN_NAME"
else else
echo "::error::Commit of ${PLUGIN_NAME} failed" echo "::error::Commit of ${PLUGIN_NAME} failed"
@ -114,4 +130,4 @@ while read -r PLUGIN_NAME; do
fi fi
done < "$BUILD_BASE/mirrors.txt" done < "$BUILD_BASE/mirrors.txt"
exit $EXIT exit $EXIT

@ -6,14 +6,16 @@ on:
- cron: "0 0 * * *" - cron: "0 0 * * *"
push: push:
branches: branches:
- dev - v1
- v2
paths: paths:
- ".github/workflows/audit-javascript.yml" - ".github/workflows/audit-javascript.yml"
- "**/pnpm-lock.yaml" - "**/pnpm-lock.yaml"
- "**/package.json" - "**/package.json"
pull_request: pull_request:
branches: branches:
- dev - v1
- v2
paths: paths:
- ".github/workflows/audit-javascript.yml" - ".github/workflows/audit-javascript.yml"
- "**/pnpm-lock.yaml" - "**/pnpm-lock.yaml"
@ -38,8 +40,9 @@ jobs:
- uses: actions/setup-node@v3 - uses: actions/setup-node@v3
with: with:
node-version: 18 node-version: 18
- uses: pnpm/action-setup@v2.2.4 - uses: pnpm/action-setup@v2
with: with:
version: 7.x.x
run_install: true run_install: true
- name: audit - name: audit
run: pnpm audit run: pnpm audit

@ -6,14 +6,16 @@ on:
- cron: "0 0 * * *" - cron: "0 0 * * *"
push: push:
branches: branches:
- dev - v1
- v2
paths: paths:
- ".github/workflows/audit-rust.yml" - ".github/workflows/audit-rust.yml"
- "**/Cargo.lock" - "**/Cargo.lock"
- "**/Cargo.toml" - "**/Cargo.toml"
pull_request: pull_request:
branches: branches:
- dev - v1
- v2
paths: paths:
- ".github/workflows/audit-rust.yml" - ".github/workflows/audit-rust.yml"
- "**/Cargo.lock" - "**/Cargo.lock"
@ -28,6 +30,6 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- uses: actions-rs/audit-check@v1 - uses: rustsec/audit-check@v1
with: with:
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}

@ -0,0 +1,16 @@
name: covector status
on: [pull_request]
jobs:
covector:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0 # required for use of git history
- name: covector status
uses: jbolda/covector/packages/action@covector-v0.8
id: covector
with:
command: "status"

@ -0,0 +1,64 @@
name: version or publish
on:
push:
branches:
- v1
jobs:
version-or-publish:
runs-on: ubuntu-latest
timeout-minutes: 65
outputs:
change: ${{ steps.covector.outputs.change }}
commandRan: ${{ steps.covector.outputs.commandRan }}
successfulPublish: ${{ steps.covector.outputs.successfulPublish }}
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0 # required for use of git history
- uses: actions/setup-node@v3
with:
node-version: "lts/*"
registry-url: "https://registry.npmjs.org"
- uses: pnpm/action-setup@v2
with:
version: 7.x.x
run_install: true
- name: install webkit2gtk and libudev for [authenticator]
run: |
sudo apt-get update
sudo apt-get install -y libwebkit2gtk-4.0-dev libwebkit2gtk-4.1-dev libudev-dev
- name: cargo login
run: cargo login ${{ secrets.ORG_CRATES_IO_TOKEN }}
- name: git config
run: |
git config --global user.name "${{ github.event.pusher.name }}"
git config --global user.email "${{ github.event.pusher.email }}"
- name: covector version or publish (publish when no change files present)
uses: jbolda/covector/packages/action@covector-v0.8
id: covector
env:
NODE_AUTH_TOKEN: ${{ secrets.ORG_NPM_TOKEN }}
with:
token: ${{ secrets.GITHUB_TOKEN }}
command: "version-or-publish"
createRelease: true
- name: Create Pull Request With Versions Bumped
id: cpr
uses: tauri-apps/create-pull-request@v3
if: steps.covector.outputs.commandRan == 'version'
with:
title: "Publish New Versions"
commit-message: "publish new versions"
labels: "version updates"
branch: "release"
body: ${{ steps.covector.outputs.change }}

@ -3,7 +3,8 @@ name: Lint JavaScript
on: on:
push: push:
branches: branches:
- dev - v1
- v2
paths: paths:
- ".github/workflows/lint-javascript.yml" - ".github/workflows/lint-javascript.yml"
- "plugins/*/guest-js/**" - "plugins/*/guest-js/**"
@ -13,7 +14,8 @@ on:
- "**/package.json" - "**/package.json"
pull_request: pull_request:
branches: branches:
- dev - v1
- v2
paths: paths:
- ".github/workflows/lint-javascript.yml" - ".github/workflows/lint-javascript.yml"
- "plugins/*/guest-js/**" - "plugins/*/guest-js/**"
@ -41,8 +43,9 @@ jobs:
- uses: actions/setup-node@v3 - uses: actions/setup-node@v3
with: with:
node-version: 18 node-version: 18
- uses: pnpm/action-setup@v2.2.4 - uses: pnpm/action-setup@v2
with: with:
version: 7.x.x
run_install: true run_install: true
- name: eslint - name: eslint
run: pnpm lint run: pnpm lint
@ -60,8 +63,9 @@ jobs:
- uses: actions/setup-node@v3 - uses: actions/setup-node@v3
with: with:
node-version: 18 node-version: 18
- uses: pnpm/action-setup@v2.2.4 - uses: pnpm/action-setup@v2
with: with:
version: 7.x.x
run_install: true run_install: true
- name: prettier check - name: prettier check
run: pnpm format-check run: pnpm format-check

@ -3,14 +3,16 @@ name: Lint Rust
on: on:
push: push:
branches: branches:
- dev - v1
- v2
paths: paths:
- ".github/workflows/lint-rust.yml" - ".github/workflows/lint-rust.yml"
- "plugins/*/src/**" - "plugins/*/src/**"
- "**/Cargo.toml" - "**/Cargo.toml"
pull_request: pull_request:
branches: branches:
- dev - v1
- v2
paths: paths:
- ".github/workflows/lint-rust.yml" - ".github/workflows/lint-rust.yml"
- "plugins/*/src/**" - "plugins/*/src/**"
@ -32,7 +34,7 @@ jobs:
- name: install webkit2gtk and libudev for [authenticator] - name: install webkit2gtk and libudev for [authenticator]
run: | run: |
sudo apt-get update sudo apt-get update
sudo apt-get install -y webkit2gtk-4.0 libudev-dev sudo apt-get install -y libwebkit2gtk-4.0-dev libwebkit2gtk-4.1-dev libudev-dev
- name: Install clippy with stable toolchain - name: Install clippy with stable toolchain
uses: dtolnay/rust-toolchain@stable uses: dtolnay/rust-toolchain@stable

@ -0,0 +1,55 @@
name: Check MSRV
on:
push:
branches:
- v1
- v2
paths:
- ".github/workflows/msrv-check.yml"
- "plugins/*/src/**"
- "**/Cargo.toml"
- "**/Cargo.lock"
pull_request:
branches:
- v1
- v2
paths:
- ".github/workflows/msrv-check.yml"
- "plugins/*/src/**"
- "**/Cargo.toml"
- "**/Cargo.lock"
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
msrv:
runs-on: ubuntu-latest
strategy:
fail-fast: false
steps:
- uses: actions/checkout@v3
- name: install webkit2gtk and libudev for [authenticator]
run: |
sudo apt-get update
sudo apt-get install -y libwebkit2gtk-4.0-dev libwebkit2gtk-4.1-dev libudev-dev
- uses: dtolnay/rust-toolchain@1.64.0
- uses: Swatinem/rust-cache@v2
- name: build
run: cargo build --workspace --exclude 'tauri-plugin-sql' --all-targets --all-features
- name: build sql:sqlite
run: cargo build --package 'tauri-plugin-sql' --all-targets --features sqlite
- name: build sql:mysql
run: cargo build --package 'tauri-plugin-sql' --all-targets --features mysql
- name: build sql:postgres
run: cargo build --package 'tauri-plugin-sql' --all-targets --features postgres

@ -4,7 +4,8 @@ on:
workflow_dispatch: workflow_dispatch:
push: push:
branches: branches:
- dev - v1
- v2
concurrency: concurrency:
group: ${{ github.workflow }}-${{ github.ref }} group: ${{ github.workflow }}-${{ github.ref }}
@ -15,6 +16,10 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- name: Fetch git tags
run: git fetch origin 'refs/tags/*:refs/tags/*'
- name: Cache pnpm modules - name: Cache pnpm modules
uses: actions/cache@v3 uses: actions/cache@v3
with: with:
@ -22,14 +27,19 @@ jobs:
key: ${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }} key: ${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: | restore-keys: |
${{ runner.os }}- ${{ runner.os }}-
- uses: actions/setup-node@v3 - uses: actions/setup-node@v3
with: with:
node-version: 18 node-version: 18
- uses: pnpm/action-setup@v2.2.4
- uses: pnpm/action-setup@v2
with: with:
version: 7.x.x
run_install: true run_install: true
- name: Build packages - name: Build packages
run: pnpm build run: pnpm build
- name: Sync - name: Sync
run: .github/sync-to-mirrors.sh run: .github/sync-to-mirrors.sh
env: env:

@ -0,0 +1,56 @@
#!/usr/bin/env node
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
/*
This script is solely intended to be run as part of the `covector publish` step to
check the latest version of a crate, considering the current minor version.
*/
const https = require("https");
const kind = process.argv[2];
const packageName = process.argv[3];
const packageVersion = process.argv[4];
const target = packageVersion.substring(0, packageVersion.lastIndexOf("."));
let url = null;
switch (kind) {
case "cargo":
url = `https://crates.io/api/v1/crates/${packageName}`;
break;
case "npm":
url = `https://registry.npmjs.org/${packageName}`;
break;
default:
throw new Error("unexpected kind " + kind);
}
const options = {
headers: {
"Content-Type": "application/json",
Accept: "application/json",
"User-Agent": "tauri (https://github.com/tauri-apps/tauri)",
},
};
https.get(url, options, (response) => {
let chunks = [];
response.on("data", function (chunk) {
chunks.push(chunk);
});
response.on("end", function () {
const data = JSON.parse(chunks.join(""));
if (kind === "cargo") {
const versions = data.versions.filter((v) => v.num.startsWith(target));
console.log(versions.length ? versions[0].num : "0.0.0");
} else if (kind === "npm") {
const versions = Object.keys(data.versions).filter((v) =>
v.startsWith(target)
);
console.log(versions[versions.length - 1] || "0.0.0");
}
});
});

461
Cargo.lock generated

File diff suppressed because it is too large Load Diff

@ -10,9 +10,12 @@
| [log](plugins/log) | Configurable logging. | ✅ | ✅ | ✅ | ✅ | ✅ | | [log](plugins/log) | Configurable logging. | ✅ | ✅ | ✅ | ✅ | ✅ |
| [persisted-scope](plugins/persisted-scope) | Persist runtime scope changes on the filesystem. | ✅ | ✅ | ✅ | ? | ? | | [persisted-scope](plugins/persisted-scope) | Persist runtime scope changes on the filesystem. | ✅ | ✅ | ✅ | ? | ? |
| [positioner](plugins/positioner) | Move windows to common locations. | ✅ | ✅ | ✅ | ? | ? | | [positioner](plugins/positioner) | Move windows to common locations. | ✅ | ✅ | ✅ | ? | ? |
| [single-instance](plugins/single-instance) | Ensure a single instance of your tauri app is running. | ✅ | ? | ✅ | ? | ? |
| [sql](plugins/sql) | Interface with SQL databases. | ✅ | ✅ | ✅ | ? | ? | | [sql](plugins/sql) | Interface with SQL databases. | ✅ | ✅ | ✅ | ? | ? |
| [store](plugins/store) | Persistent key value storage. | ✅ | ✅ | ✅ | ? | ? | | [store](plugins/store) | Persistent key value storage. | ✅ | ✅ | ✅ | ? | ? |
| [stronghold](plugins/stronghold) | Encrypted, secure database. | ✅ | ✅ | ✅ | ? | ? | | [stronghold](plugins/stronghold) | Encrypted, secure database. | ✅ | ✅ | ✅ | ? | ? |
| [upload](plugins/upload) | Tauri plugin for file uploads through HTTP. | ✅ | ✅ | ✅ | ? | ? | | [upload](plugins/upload) | Tauri plugin for file uploads through HTTP. | ✅ | ✅ | ✅ | ? | ? |
| [websocket](plugins/websocket) | | ✅ | ✅ | ✅ | ? | ? | | [websocket](plugins/websocket) | Open a WebSocket connection using a Rust client in JS. | ✅ | ✅ | ✅ | ? | ? |
| [window-state](plugins/window-state) | Persist window sizes and positions. | ✅ | ✅ | ✅ | ? | ? | | [window-state](plugins/window-state) | Persist window sizes and positions. | ✅ | ✅ | ✅ | ? | ? |
_This repo and all plugins require a Rust version of at least **1.64**_

@ -4,26 +4,29 @@
"license": "MIT or APACHE-2.0", "license": "MIT or APACHE-2.0",
"type": "module", "type": "module",
"scripts": { "scripts": {
"build": "pnpm run -r --parallel --filter !plugins-workspace build", "build": "pnpm run -r --parallel --filter !plugins-workspace --filter !\"./plugins/*/examples/**\" build",
"lint": "eslint .", "lint": "eslint .",
"format": "prettier --write .", "format": "prettier --write .",
"format-check": "prettier --check ." "format-check": "prettier --check ."
}, },
"devDependencies": { "devDependencies": {
"@rollup/plugin-node-resolve": "^15.0.1", "@rollup/plugin-node-resolve": "^15.0.2",
"@rollup/plugin-terser": "^0.4.0", "@rollup/plugin-terser": "^0.4.1",
"@rollup/plugin-typescript": "^11.0.0", "@rollup/plugin-typescript": "^11.1.0",
"@typescript-eslint/eslint-plugin": "^5.0.0", "@typescript-eslint/eslint-plugin": "^5.58.0",
"@typescript-eslint/parser": "^5.46.1", "@typescript-eslint/parser": "^5.58.0",
"eslint": "^8.0.1", "eslint": "^8.38.0",
"eslint-config-prettier": "^8.5.0", "eslint-config-prettier": "^8.8.0",
"eslint-config-standard-with-typescript": "^34.0.0", "eslint-config-standard-with-typescript": "^34.0.1",
"eslint-plugin-import": "^2.25.2", "eslint-plugin-import": "^2.27.5",
"eslint-plugin-n": "^15.0.0", "eslint-plugin-n": "^16.0.0",
"eslint-plugin-promise": "^6.0.0", "eslint-plugin-promise": "^6.1.1",
"prettier": "^2.8.1", "eslint-plugin-security": "^1.7.1",
"rollup": "^3.7.4", "prettier": "^2.8.7",
"typescript": "^4.9.4" "rollup": "^3.20.4",
"typescript": "^5.0.4"
}, },
"packageManager": "pnpm@7.18.1" "engines": {
"pnpm": ">=7.24.2"
}
} }

@ -1,6 +1,6 @@
[package] [package]
name = "tauri-plugin-authenticator" name = "tauri-plugin-authenticator"
version = "0.1.0" version = "0.0.0"
description = "Use hardware security-keys in your Tauri App." description = "Use hardware security-keys in your Tauri App."
authors.workspace = true authors.workspace = true
license.workspace = true license.workspace = true
@ -18,7 +18,7 @@ thiserror.workspace = true
authenticator = "0.3.1" authenticator = "0.3.1"
once_cell = "1" once_cell = "1"
sha2 = "0.10" sha2 = "0.10"
base64 = { version = "^0.13" } base64 = "0.21"
u2f = "0.2" u2f = "0.2"
chrono = "0.4" chrono = "0.4"

@ -4,6 +4,8 @@ Use hardware security-keys in your Tauri App.
## Install ## Install
_This plugin requires a Rust version of at least **1.64**_
There are three general methods of installation that we can recommend. There are three general methods of installation that we can recommend.
1. Use crates.io and npm (easiest, and requires you to trust that our publishing pipeline worked) 1. Use crates.io and npm (easiest, and requires you to trust that our publishing pipeline worked)
@ -18,7 +20,7 @@ Install the Core plugin by adding the following to your `Cargo.toml` file:
[dependencies] [dependencies]
tauri-plugin-authenticator = "0.1" tauri-plugin-authenticator = "0.1"
# or through git # or through git
tauri-plugin-authenticator = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "dev" } tauri-plugin-authenticator = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" }
``` ```
You can install the JavaScript Guest bindings using your preferred JavaScript package manager: You can install the JavaScript Guest bindings using your preferred JavaScript package manager:

@ -25,7 +25,7 @@
"LICENSE" "LICENSE"
], ],
"devDependencies": { "devDependencies": {
"tslib": "^2.4.1" "tslib": "^2.5.0"
}, },
"dependencies": { "dependencies": {
"@tauri-apps/api": "^1.2.0" "@tauri-apps/api": "^1.2.0"

@ -6,7 +6,7 @@ use authenticator::{
authenticatorservice::AuthenticatorService, statecallback::StateCallback, authenticatorservice::AuthenticatorService, statecallback::StateCallback,
AuthenticatorTransports, KeyHandle, RegisterFlags, SignFlags, StatusUpdate, AuthenticatorTransports, KeyHandle, RegisterFlags, SignFlags, StatusUpdate,
}; };
use base64::{decode_config, encode_config, URL_SAFE_NO_PAD}; use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine};
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use serde::Serialize; use serde::Serialize;
use sha2::{Digest, Sha256}; use sha2::{Digest, Sha256};
@ -75,9 +75,9 @@ pub fn register(application: String, timeout: u64, challenge: String) -> crate::
let (key_handle, public_key) = let (key_handle, public_key) =
_u2f_get_key_handle_and_public_key_from_register_response(&register_data).unwrap(); _u2f_get_key_handle_and_public_key_from_register_response(&register_data).unwrap();
let key_handle_base64 = encode_config(key_handle, URL_SAFE_NO_PAD); let key_handle_base64 = URL_SAFE_NO_PAD.encode(key_handle);
let public_key_base64 = encode_config(public_key, URL_SAFE_NO_PAD); let public_key_base64 = URL_SAFE_NO_PAD.encode(public_key);
let register_data_base64 = encode_config(&register_data, URL_SAFE_NO_PAD); let register_data_base64 = URL_SAFE_NO_PAD.encode(&register_data);
println!("Key Handle: {}", &key_handle_base64); println!("Key Handle: {}", &key_handle_base64);
println!("Public Key: {}", &public_key_base64); println!("Public Key: {}", &public_key_base64);
@ -108,7 +108,7 @@ pub fn sign(
challenge: String, challenge: String,
key_handle: String, key_handle: String,
) -> crate::Result<String> { ) -> crate::Result<String> {
let credential = match decode_config(key_handle, URL_SAFE_NO_PAD) { let credential = match URL_SAFE_NO_PAD.decode(key_handle) {
Ok(v) => v, Ok(v) => v,
Err(e) => { Err(e) => {
return Err(e.into()); return Err(e.into());
@ -152,19 +152,16 @@ pub fn sign(
let (_, handle_used, sign_data, device_info) = sign_result.unwrap(); let (_, handle_used, sign_data, device_info) = sign_result.unwrap();
let sig = encode_config(sign_data, URL_SAFE_NO_PAD); let sig = URL_SAFE_NO_PAD.encode(sign_data);
println!("Sign result: {sig}"); println!("Sign result: {sig}");
println!( println!("Key handle used: {}", URL_SAFE_NO_PAD.encode(&handle_used));
"Key handle used: {}",
encode_config(&handle_used, URL_SAFE_NO_PAD)
);
println!("Device info: {}", &device_info); println!("Device info: {}", &device_info);
println!("Done."); println!("Done.");
let res = serde_json::to_string(&Signature { let res = serde_json::to_string(&Signature {
sign_data: sig, sign_data: sig,
key_handle: encode_config(&handle_used, URL_SAFE_NO_PAD), key_handle: URL_SAFE_NO_PAD.encode(&handle_used),
})?; })?;
Ok(res) Ok(res)
} }

@ -2,7 +2,7 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
use base64::{decode_config, encode_config, URL_SAFE_NO_PAD}; use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine};
use chrono::prelude::*; use chrono::prelude::*;
use serde::Serialize; use serde::Serialize;
use std::convert::Into; use std::convert::Into;
@ -15,7 +15,7 @@ static VERSION: &str = "U2F_V2";
pub fn make_challenge(app_id: &str, challenge_bytes: Vec<u8>) -> Challenge { pub fn make_challenge(app_id: &str, challenge_bytes: Vec<u8>) -> Challenge {
let utc: DateTime<Utc> = Utc::now(); let utc: DateTime<Utc> = Utc::now();
Challenge { Challenge {
challenge: encode_config(challenge_bytes, URL_SAFE_NO_PAD), challenge: URL_SAFE_NO_PAD.encode(challenge_bytes),
timestamp: format!("{utc:?}"), timestamp: format!("{utc:?}"),
app_id: app_id.to_string(), app_id: app_id.to_string(),
} }
@ -35,10 +35,10 @@ pub fn verify_registration(
register_data: String, register_data: String,
client_data: String, client_data: String,
) -> crate::Result<String> { ) -> crate::Result<String> {
let challenge_bytes = decode_config(challenge, URL_SAFE_NO_PAD)?; let challenge_bytes = URL_SAFE_NO_PAD.decode(challenge)?;
let challenge = make_challenge(&app_id, challenge_bytes); let challenge = make_challenge(&app_id, challenge_bytes);
let client_data_bytes: Vec<u8> = client_data.as_bytes().into(); let client_data_bytes: Vec<u8> = client_data.as_bytes().into();
let client_data_base64 = encode_config(client_data_bytes, URL_SAFE_NO_PAD); let client_data_base64 = URL_SAFE_NO_PAD.encode(client_data_bytes);
let client = U2f::new(app_id); let client = U2f::new(app_id);
match client.register_response( match client.register_response(
challenge, challenge,
@ -50,8 +50,8 @@ pub fn verify_registration(
) { ) {
Ok(v) => { Ok(v) => {
let rv = RegistrationVerification { let rv = RegistrationVerification {
key_handle: encode_config(&v.key_handle, URL_SAFE_NO_PAD), key_handle: URL_SAFE_NO_PAD.encode(&v.key_handle),
pubkey: encode_config(&v.pub_key, URL_SAFE_NO_PAD), pubkey: URL_SAFE_NO_PAD.encode(&v.pub_key),
device_name: v.device_name, device_name: v.device_name,
}; };
Ok(serde_json::to_string(&rv)?) Ok(serde_json::to_string(&rv)?)
@ -74,12 +74,12 @@ pub fn verify_signature(
key_handle: String, key_handle: String,
pub_key: String, pub_key: String,
) -> crate::Result<u32> { ) -> crate::Result<u32> {
let challenge_bytes = decode_config(challenge, URL_SAFE_NO_PAD)?; let challenge_bytes = URL_SAFE_NO_PAD.decode(challenge)?;
let chal = make_challenge(&app_id, challenge_bytes); let chal = make_challenge(&app_id, challenge_bytes);
let client_data_bytes: Vec<u8> = client_data.as_bytes().into(); let client_data_bytes: Vec<u8> = client_data.as_bytes().into();
let client_data_base64 = encode_config(client_data_bytes, URL_SAFE_NO_PAD); let client_data_base64 = URL_SAFE_NO_PAD.encode(client_data_bytes);
let key_handle_bytes = decode_config(&key_handle, URL_SAFE_NO_PAD)?; let key_handle_bytes = URL_SAFE_NO_PAD.decode(&key_handle)?;
let pubkey_bytes = decode_config(pub_key, URL_SAFE_NO_PAD)?; let pubkey_bytes = URL_SAFE_NO_PAD.decode(pub_key)?;
let client = U2f::new(app_id); let client = U2f::new(app_id);
let mut _counter: u32 = 0; let mut _counter: u32 = 0;
match client.sign_response( match client.sign_response(

@ -1,6 +1,6 @@
[package] [package]
name = "tauri-plugin-autostart" name = "tauri-plugin-autostart"
version = "0.1.0" version = "0.0.0"
description = "Automatically launch your application at startup." description = "Automatically launch your application at startup."
authors.workspace = true authors.workspace = true
license.workspace = true license.workspace = true

@ -4,6 +4,8 @@ Automatically launch your application at startup. Supports Windows, Mac (via App
## Install ## Install
_This plugin requires a Rust version of at least **1.64**_
There are three general methods of installation that we can recommend. There are three general methods of installation that we can recommend.
1. Use crates.io and npm (easiest, and requires you to trust that our publishing pipeline worked) 1. Use crates.io and npm (easiest, and requires you to trust that our publishing pipeline worked)
@ -16,7 +18,7 @@ Install the Core plugin by adding the following to your `Cargo.toml` file:
```toml ```toml
[dependencies] [dependencies]
tauri-plugin-autostart = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "dev" } tauri-plugin-autostart = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" }
``` ```
You can install the JavaScript Guest bindings using your preferred JavaScript package manager: You can install the JavaScript Guest bindings using your preferred JavaScript package manager:

@ -24,7 +24,7 @@
"LICENSE" "LICENSE"
], ],
"devDependencies": { "devDependencies": {
"tslib": "^2.4.1" "tslib": "^2.5.0"
}, },
"dependencies": { "dependencies": {
"@tauri-apps/api": "^1.2.0" "@tauri-apps/api": "^1.2.0"

@ -3,6 +3,8 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
use auto_launch::{AutoLaunch, AutoLaunchBuilder}; use auto_launch::{AutoLaunch, AutoLaunchBuilder};
#[cfg(target_os = "macos")]
use log::info;
use serde::{ser::Serializer, Serialize}; use serde::{ser::Serializer, Serialize};
use tauri::{ use tauri::{
command, command,
@ -98,7 +100,6 @@ pub fn init<R: Runtime>(
.invoke_handler(tauri::generate_handler![enable, disable, is_enabled]) .invoke_handler(tauri::generate_handler![enable, disable, is_enabled])
.setup(move |app| { .setup(move |app| {
let mut builder = AutoLaunchBuilder::new(); let mut builder = AutoLaunchBuilder::new();
builder.set_app_name(&app.package_info().name); builder.set_app_name(&app.package_info().name);
if let Some(args) = args { if let Some(args) = args {
builder.set_args(&args); builder.set_args(&args);
@ -110,7 +111,22 @@ pub fn init<R: Runtime>(
#[cfg(windows)] #[cfg(windows)]
builder.set_app_path(&current_exe.display().to_string()); builder.set_app_path(&current_exe.display().to_string());
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
builder.set_app_path(&current_exe.canonicalize()?.display().to_string()); {
// on macOS, current_exe gives path to /Applications/Example.app/MacOS/Example
// but this results in seeing a Unix Executable in macOS login items
// It must be: /Applications/Example.app
// If it didn't find exactly a single occurance of .app, it will default to
// exe path to not break it.
let exe_path = current_exe.canonicalize()?.display().to_string();
let parts: Vec<&str> = exe_path.split(".app/").collect();
let app_path = if parts.len() == 2 {
format!("{}.app", parts.get(0).unwrap().to_string())
} else {
exe_path
};
info!("auto_start path {}", &app_path);
builder.set_app_path(&app_path);
}
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
if let Some(appimage) = app if let Some(appimage) = app
.env() .env()

@ -1,6 +1,6 @@
[package] [package]
name = "tauri-plugin-fs-extra" name = "tauri-plugin-fs-extra"
version = "0.1.0" version = "0.0.0"
description = "Additional file system methods not included in the core API." description = "Additional file system methods not included in the core API."
authors.workspace = true authors.workspace = true
license.workspace = true license.workspace = true

@ -4,6 +4,8 @@ Additional file system methods not included in the core API.
## Install ## Install
_This plugin requires a Rust version of at least **1.64**_
There are three general methods of installation that we can recommend. There are three general methods of installation that we can recommend.
1. Use crates.io and npm (easiest, and requires you to trust that our publishing pipeline worked) 1. Use crates.io and npm (easiest, and requires you to trust that our publishing pipeline worked)
@ -16,7 +18,7 @@ Install the Core plugin by adding the following to your `Cargo.toml` file:
```toml ```toml
[dependencies] [dependencies]
tauri-plugin-fs-extra = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "dev" } tauri-plugin-fs-extra = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" }
``` ```
You can install the JavaScript Guest bindings using your preferred JavaScript package manager: You can install the JavaScript Guest bindings using your preferred JavaScript package manager:

@ -25,7 +25,7 @@
"LICENSE" "LICENSE"
], ],
"devDependencies": { "devDependencies": {
"tslib": "^2.4.1" "tslib": "^2.5.0"
}, },
"dependencies": { "dependencies": {
"@tauri-apps/api": "^1.2.0" "@tauri-apps/api": "^1.2.0"

@ -1,6 +1,6 @@
[package] [package]
name = "tauri-plugin-fs-watch" name = "tauri-plugin-fs-watch"
version = "0.1.0" version = "0.0.0"
description = "Watch files and directories for changes." description = "Watch files and directories for changes."
authors.workspace = true authors.workspace = true
license.workspace = true license.workspace = true
@ -15,5 +15,5 @@ serde_json.workspace = true
tauri.workspace = true tauri.workspace = true
log.workspace = true log.workspace = true
thiserror.workspace = true thiserror.workspace = true
notify = { version = "5" , features = ["serde"] } notify = { version = "6" , features = ["serde"] }
notify-debouncer-mini = { version = "0.2.1" , features = ["serde"] } notify-debouncer-mini = { version = "0.3" , features = ["serde"] }

@ -4,6 +4,8 @@ Watch files and directories for changes using [notify](https://github.com/notify
## Install ## Install
_This plugin requires a Rust version of at least **1.64**_
There are three general methods of installation that we can recommend. There are three general methods of installation that we can recommend.
1. Use crates.io and npm (easiest, and requires you to trust that our publishing pipeline worked) 1. Use crates.io and npm (easiest, and requires you to trust that our publishing pipeline worked)
@ -16,7 +18,7 @@ Install the Core plugin by adding the following to your `Cargo.toml` file:
```toml ```toml
[dependencies] [dependencies]
tauri-plugin-fs-watch = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "dev" } tauri-plugin-fs-watch = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" }
``` ```
You can install the JavaScript Guest bindings using your preferred JavaScript package manager: You can install the JavaScript Guest bindings using your preferred JavaScript package manager:
@ -54,18 +56,18 @@ import { watch, watchImmediate } from "tauri-plugin-fs-watch-api";
// can also watch an array of paths // can also watch an array of paths
const stopWatching = await watch( const stopWatching = await watch(
"/path/to/something", "/path/to/something",
{ recursive: true },
(event) => { (event) => {
const { type, payload } = event; const { type, payload } = event;
} },
{ recursive: true }
); );
const stopRawWatcher = await watchImmediate( const stopRawWatcher = await watchImmediate(
["/path/a", "/path/b"], ["/path/a", "/path/b"],
{},
(event) => { (event) => {
const { path, operation, cookie } = event; const { path, operation, cookie } = event;
} },
{}
); );
``` ```

@ -44,8 +44,8 @@ async function unwatch(id: number): Promise<void> {
export async function watch( export async function watch(
paths: string | string[], paths: string | string[],
options: DebouncedWatchOptions, cb: (event: DebouncedEvent) => void,
cb: (event: DebouncedEvent) => void options: DebouncedWatchOptions = {}
): Promise<UnlistenFn> { ): Promise<UnlistenFn> {
const opts = { const opts = {
recursive: false, recursive: false,
@ -82,8 +82,8 @@ export async function watch(
export async function watchImmediate( export async function watchImmediate(
paths: string | string[], paths: string | string[],
options: WatchOptions, cb: (event: RawEvent) => void,
cb: (event: RawEvent) => void options: WatchOptions = {}
): Promise<UnlistenFn> { ): Promise<UnlistenFn> {
const opts = { const opts = {
recursive: false, recursive: false,

@ -25,7 +25,7 @@
"LICENSE" "LICENSE"
], ],
"devDependencies": { "devDependencies": {
"tslib": "^2.4.1" "tslib": "^2.5.0"
}, },
"dependencies": { "dependencies": {
"@tauri-apps/api": "^1.2.0" "@tauri-apps/api": "^1.2.0"

@ -6,6 +6,8 @@ Expose your apps assets through a localhost server instead of the default custom
## Install ## Install
_This plugin requires a Rust version of at least **1.64**_
There are three general methods of installation that we can recommend. There are three general methods of installation that we can recommend.
1. Use crates.io and npm (easiest, and requires you to trust that our publishing pipeline worked) 1. Use crates.io and npm (easiest, and requires you to trust that our publishing pipeline worked)
@ -18,7 +20,7 @@ Install the Core plugin by adding the following to your `Cargo.toml` file:
```toml ```toml
[dependencies] [dependencies]
tauri-plugin-localhost = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "dev" } tauri-plugin-localhost = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" }
portpicker = "0.1" # used in the example to pick a random free port portpicker = "0.1" # used in the example to pick a random free port
``` ```

@ -1,6 +1,6 @@
[package] [package]
name = "tauri-plugin-log" name = "tauri-plugin-log"
version = "0.1.0" version = "0.0.0"
description = "Configurable logging for your Tauri app." description = "Configurable logging for your Tauri app."
authors.workspace = true authors.workspace = true
license.workspace = true license.workspace = true
@ -17,7 +17,7 @@ serde_repr = "0.1"
byte-unit = "4.0" byte-unit = "4.0"
fern = "0.6" fern = "0.6"
log = { workspace = true, features = ["kv_unstable"] } log = { workspace = true, features = ["kv_unstable"] }
time = { version = "0.3", features = ["formatting"] } time = { version = "0.3", features = ["formatting", "local-offset"] }
[features] [features]
colored = ["fern/colored"] colored = ["fern/colored"]

@ -4,6 +4,8 @@ Configurable logging for your Tauri app.
## Install ## Install
_This plugin requires a Rust version of at least **1.64**_
There are three general methods of installation that we can recommend. There are three general methods of installation that we can recommend.
1. Use crates.io and npm (easiest, and requires you to trust that our publishing pipeline worked) 1. Use crates.io and npm (easiest, and requires you to trust that our publishing pipeline worked)
@ -16,7 +18,7 @@ Install the Core plugin by adding the following to your `Cargo.toml` file:
```toml ```toml
[dependencies] [dependencies]
tauri-plugin-log = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "dev" } tauri-plugin-log = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" }
``` ```
You can install the JavaScript Guest bindings using your preferred JavaScript package manager: You can install the JavaScript Guest bindings using your preferred JavaScript package manager:

@ -4,7 +4,8 @@ import { listen, UnlistenFn } from "@tauri-apps/api/event";
export type LogOptions = { export type LogOptions = {
file?: string; file?: string;
line?: number; line?: number;
} & Record<string, string | undefined>; keyValues?: Record<string, string | undefined>;
};
enum LogLevel { enum LogLevel {
/** /**
@ -50,12 +51,17 @@ async function log(
return name.length > 0 && location !== "[native code]"; return name.length > 0 && location !== "[native code]";
}); });
const { file, line, ...keyValues } = options ?? {}; const { file, line, keyValues } = options ?? {};
let location = filtered?.[0]?.filter((v) => v.length > 0).join("@");
if (location === "Error") {
location = "webview::unknown";
}
await invoke("plugin:log|log", { await invoke("plugin:log|log", {
level, level,
message, message,
location: filtered?.[0]?.filter((v) => v.length > 0).join("@"), location,
file, file,
line, line,
keyValues, keyValues,
@ -184,7 +190,8 @@ export async function attachConsole(): Promise<UnlistenFn> {
// Strip ANSI escape codes // Strip ANSI escape codes
const message = payload.message.replace( const message = payload.message.replace(
// eslint-disable-next-line no-control-regex // TODO: Investigate security/detect-unsafe-regex
// eslint-disable-next-line no-control-regex, security/detect-unsafe-regex
/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g, /[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g,
"" ""
); );

@ -25,7 +25,7 @@
"LICENSE" "LICENSE"
], ],
"devDependencies": { "devDependencies": {
"tslib": "^2.4.1" "tslib": "^2.5.0"
}, },
"dependencies": { "dependencies": {
"@tauri-apps/api": "^1.2.0" "@tauri-apps/api": "^1.2.0"

@ -21,9 +21,11 @@ use tauri::{
}; };
pub use fern; pub use fern;
use time::OffsetDateTime;
const DEFAULT_MAX_FILE_SIZE: u128 = 40000; const DEFAULT_MAX_FILE_SIZE: u128 = 40000;
const DEFAULT_ROTATION_STRATEGY: RotationStrategy = RotationStrategy::KeepOne; const DEFAULT_ROTATION_STRATEGY: RotationStrategy = RotationStrategy::KeepOne;
const DEFAULT_TIMEZONE_STRATEGY: TimezoneStrategy = TimezoneStrategy::UseUtc;
const DEFAULT_LOG_TARGETS: [LogTarget; 2] = [LogTarget::Stdout, LogTarget::LogDir]; const DEFAULT_LOG_TARGETS: [LogTarget; 2] = [LogTarget::Stdout, LogTarget::LogDir];
/// An enum representing the available verbosity levels of the logger. /// An enum representing the available verbosity levels of the logger.
@ -83,6 +85,23 @@ pub enum RotationStrategy {
KeepOne, KeepOne,
} }
#[derive(Debug, Clone)]
pub enum TimezoneStrategy {
UseUtc,
UseLocal,
}
impl TimezoneStrategy {
pub fn get_now(&self) -> OffsetDateTime {
match self {
TimezoneStrategy::UseUtc => OffsetDateTime::now_utc(),
TimezoneStrategy::UseLocal => {
OffsetDateTime::now_local().unwrap_or_else(|_| OffsetDateTime::now_utc())
} // Fallback to UTC since Rust cannot determine local timezone
}
}
}
#[derive(Debug, Serialize, Clone)] #[derive(Debug, Serialize, Clone)]
struct RecordPayload { struct RecordPayload {
message: String, message: String,
@ -127,8 +146,8 @@ fn log(
let location = location.unwrap_or("webview"); let location = location.unwrap_or("webview");
let mut builder = RecordBuilder::new(); let mut builder = RecordBuilder::new();
builder builder
.target(location)
.level(level.into()) .level(level.into())
.target(location)
.file(file) .file(file)
.line(line); .line(line);
@ -145,6 +164,7 @@ fn log(
pub struct Builder { pub struct Builder {
dispatch: fern::Dispatch, dispatch: fern::Dispatch,
rotation_strategy: RotationStrategy, rotation_strategy: RotationStrategy,
timezone_strategy: TimezoneStrategy,
max_file_size: u128, max_file_size: u128,
targets: Vec<LogTarget>, targets: Vec<LogTarget>,
} }
@ -157,15 +177,16 @@ impl Default for Builder {
let dispatch = fern::Dispatch::new().format(move |out, message, record| { let dispatch = fern::Dispatch::new().format(move |out, message, record| {
out.finish(format_args!( out.finish(format_args!(
"{}[{}][{}] {}", "{}[{}][{}] {}",
time::OffsetDateTime::now_utc().format(&format).unwrap(), DEFAULT_TIMEZONE_STRATEGY.get_now().format(&format).unwrap(),
record.target(),
record.level(), record.level(),
record.target(),
message message
)) ))
}); });
Self { Self {
dispatch, dispatch,
rotation_strategy: DEFAULT_ROTATION_STRATEGY, rotation_strategy: DEFAULT_ROTATION_STRATEGY,
timezone_strategy: DEFAULT_TIMEZONE_STRATEGY,
max_file_size: DEFAULT_MAX_FILE_SIZE, max_file_size: DEFAULT_MAX_FILE_SIZE,
targets: DEFAULT_LOG_TARGETS.into(), targets: DEFAULT_LOG_TARGETS.into(),
} }
@ -182,6 +203,24 @@ impl Builder {
self self
} }
pub fn timezone_strategy(mut self, timezone_strategy: TimezoneStrategy) -> Self {
self.timezone_strategy = timezone_strategy.clone();
let format =
time::format_description::parse("[[[year]-[month]-[day]][[[hour]:[minute]:[second]]")
.unwrap();
self.dispatch = fern::Dispatch::new().format(move |out, message, record| {
out.finish(format_args!(
"{}[{}][{}] {}",
timezone_strategy.get_now().format(&format).unwrap(),
record.level(),
record.target(),
message
))
});
self
}
pub fn max_file_size(mut self, max_file_size: u128) -> Self { pub fn max_file_size(mut self, max_file_size: u128) -> Self {
self.max_file_size = max_file_size; self.max_file_size = max_file_size;
self self
@ -228,12 +267,14 @@ impl Builder {
let format = let format =
time::format_description::parse("[[[year]-[month]-[day]][[[hour]:[minute]:[second]]") time::format_description::parse("[[[year]-[month]-[day]][[[hour]:[minute]:[second]]")
.unwrap(); .unwrap();
let timezone_strategy = self.timezone_strategy.clone();
self.format(move |out, message, record| { self.format(move |out, message, record| {
out.finish(format_args!( out.finish(format_args!(
"{}[{}][{}] {}", "{}[{}][{}] {}",
time::OffsetDateTime::now_utc().format(&format).unwrap(), timezone_strategy.get_now().format(&format).unwrap(),
record.target(),
colors.color(record.level()), colors.color(record.level()),
record.target(),
message message
)) ))
}) })
@ -259,6 +300,7 @@ impl Builder {
&path, &path,
app_name, app_name,
&self.rotation_strategy, &self.rotation_strategy,
&self.timezone_strategy,
self.max_file_size, self.max_file_size,
)?)? )?)?
.into() .into()
@ -273,6 +315,7 @@ impl Builder {
&path, &path,
app_name, app_name,
&self.rotation_strategy, &self.rotation_strategy,
&self.timezone_strategy,
self.max_file_size, self.max_file_size,
)?)? )?)?
.into() .into()
@ -306,6 +349,7 @@ fn get_log_file_path(
dir: &impl AsRef<Path>, dir: &impl AsRef<Path>,
app_name: &str, app_name: &str,
rotation_strategy: &RotationStrategy, rotation_strategy: &RotationStrategy,
timezone_strategy: &TimezoneStrategy,
max_file_size: u128, max_file_size: u128,
) -> plugin::Result<PathBuf> { ) -> plugin::Result<PathBuf> {
let path = dir.as_ref().join(format!("{app_name}.log")); let path = dir.as_ref().join(format!("{app_name}.log"));
@ -318,7 +362,8 @@ fn get_log_file_path(
let to = dir.as_ref().join(format!( let to = dir.as_ref().join(format!(
"{}_{}.log", "{}_{}.log",
app_name, app_name,
time::OffsetDateTime::now_utc() timezone_strategy
.get_now()
.format( .format(
&time::format_description::parse( &time::format_description::parse(
"[year]-[month]-[day]_[hour]-[minute]-[second]" "[year]-[month]-[day]_[hour]-[minute]-[second]"

@ -0,0 +1,7 @@
# Changelog
## \[0.1.1]
- The MSRV was raised to 1.64!
- The plugin now recursively unescapes saved patterns before allowing/forbidding them. This effectively prevents `.persisted-scope` files from blowing up, which caused Out-Of-Memory issues, while automatically fixing existing broken files seamlessly.
- [ebb2eb2](https://github.com/tauri-apps/plugins-workspace/commit/ebb2eb2fe2ebfbb70530d16a983d396aa5829aa1) fix(persisted-scope): Prevent out-of-memory issues, fixes [#274](https://github.com/tauri-apps/plugins-workspace/pull/274) ([#328](https://github.com/tauri-apps/plugins-workspace/pull/328)) on 2023-04-26

@ -1,6 +1,6 @@
[package] [package]
name = "tauri-plugin-persisted-scope" name = "tauri-plugin-persisted-scope"
version = "0.1.0" version = "0.1.1"
description = "Save filesystem and asset scopes and restore them when the app is reopened." description = "Save filesystem and asset scopes and restore them when the app is reopened."
authors.workspace = true authors.workspace = true
license.workspace = true license.workspace = true
@ -15,7 +15,8 @@ serde_json.workspace = true
tauri.workspace = true tauri.workspace = true
log.workspace = true log.workspace = true
thiserror.workspace = true thiserror.workspace = true
aho-corasick = "1.0"
bincode = "1" bincode = "1"
[features] [features]
protocol-asset = [ "tauri/protocol-asset" ] protocol-asset = [ "tauri/protocol-asset" ]

@ -4,6 +4,8 @@ Save filesystem and asset scopes and restore them when the app is reopened.
## Install ## Install
_This plugin requires a Rust version of at least **1.64**_
There are three general methods of installation that we can recommend. There are three general methods of installation that we can recommend.
1. Use crates.io and npm (easiest, and requires you to trust that our publishing pipeline worked) 1. Use crates.io and npm (easiest, and requires you to trust that our publishing pipeline worked)
@ -16,7 +18,7 @@ Install the Core plugin by adding the following to your `Cargo.toml` file:
```toml ```toml
[dependencies] [dependencies]
tauri-plugin-persisted-scope = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "dev" } tauri-plugin-persisted-scope = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" }
``` ```
## Usage ## Usage

@ -2,19 +2,34 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
use aho_corasick::AhoCorasick;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use tauri::{ use tauri::{
plugin::{Builder, TauriPlugin}, plugin::{Builder, TauriPlugin},
FsScopeEvent, Manager, Runtime, AppHandle, FsScopeEvent, Manager, Runtime,
}; };
use std::{ use std::{
fs::{create_dir_all, File}, fs::{create_dir_all, File},
io::Write, io::Write,
path::Path,
}; };
const SCOPE_STATE_FILENAME: &str = ".persisted-scope"; const SCOPE_STATE_FILENAME: &str = ".persisted-scope";
// Most of these patterns are just added to try to fix broken files in the wild.
// After a while we can hopefully reduce it to something like [r"[?]", r"[*]", r"\\?\\\?\"]
const PATTERNS: &[&str] = &[
r"[[]",
r"[]]",
r"[?]",
r"[*]",
r"\?\?",
r"\\?\\?\",
r"\\?\\\?\",
];
const REPLACE_WITH: &[&str] = &[r"[", r"]", r"?", r"*", r"\?", r"\\?\", r"\\?\"];
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]
enum Error { enum Error {
#[error(transparent)] #[error(transparent)]
@ -33,6 +48,41 @@ struct Scope {
forbidden_patterns: Vec<String>, forbidden_patterns: Vec<String>,
} }
fn fix_pattern(ac: &AhoCorasick, s: &str) -> String {
let s = ac.replace_all(s, REPLACE_WITH);
if ac.find(&s).is_some() {
return fix_pattern(ac, &s);
}
s
}
fn save_scopes<R: Runtime>(app: &AppHandle<R>, app_dir: &Path, scope_state_path: &Path) {
let fs_scope = app.fs_scope();
let scope = Scope {
allowed_paths: fs_scope
.allowed_patterns()
.into_iter()
.map(|p| p.to_string())
.collect(),
forbidden_patterns: fs_scope
.forbidden_patterns()
.into_iter()
.map(|p| p.to_string())
.collect(),
};
let _ = create_dir_all(app_dir)
.and_then(|_| File::create(scope_state_path))
.map_err(Error::Io)
.and_then(|mut f| {
f.write_all(&bincode::serialize(&scope).map_err(Error::from)?)
.map_err(Into::into)
});
}
pub fn init<R: Runtime>() -> TauriPlugin<R> { pub fn init<R: Runtime>() -> TauriPlugin<R> {
Builder::new("persisted-scope") Builder::new("persisted-scope")
.setup(|app| { .setup(|app| {
@ -49,49 +99,38 @@ pub fn init<R: Runtime>() -> TauriPlugin<R> {
#[cfg(feature = "protocol-asset")] #[cfg(feature = "protocol-asset")]
let _ = asset_protocol_scope.forbid_file(&scope_state_path); let _ = asset_protocol_scope.forbid_file(&scope_state_path);
// We're trying to fix broken .persisted-scope files seamlessly, so we'll be running this on the values read on the saved file.
// We will still save some semi-broken values because the scope events are quite spammy and we don't want to reduce runtime performance any further.
let ac = AhoCorasick::new(PATTERNS).unwrap(/* This should be impossible to fail since we're using a small static input */);
if scope_state_path.exists() { if scope_state_path.exists() {
let scope: Scope = tauri::api::file::read_binary(&scope_state_path) let scope: Scope = tauri::api::file::read_binary(&scope_state_path)
.map_err(Error::from) .map_err(Error::from)
.and_then(|scope| bincode::deserialize(&scope).map_err(Into::into)) .and_then(|scope| bincode::deserialize(&scope).map_err(Into::into))
.unwrap_or_default(); .unwrap_or_default();
for allowed in &scope.allowed_paths { for allowed in &scope.allowed_paths {
// allows the path as is let allowed = fix_pattern(&ac, allowed);
let _ = fs_scope.allow_file(allowed);
let _ = fs_scope.allow_file(&allowed);
#[cfg(feature = "protocol-asset")] #[cfg(feature = "protocol-asset")]
let _ = asset_protocol_scope.allow_file(allowed); let _ = asset_protocol_scope.allow_file(&allowed);
} }
for forbidden in &scope.forbidden_patterns { for forbidden in &scope.forbidden_patterns {
// forbid the path as is let forbidden = fix_pattern(&ac, forbidden);
let _ = fs_scope.forbid_file(forbidden);
let _ = fs_scope.forbid_file(&forbidden);
#[cfg(feature = "protocol-asset")] #[cfg(feature = "protocol-asset")]
let _ = asset_protocol_scope.forbid_file(forbidden); let _ = asset_protocol_scope.forbid_file(&forbidden);
} }
// Manually save the fixed scopes to disk once.
// This is needed to fix broken .peristed-scope files in case the app doesn't update the scope itself.
save_scopes(&app, &app_dir, &scope_state_path);
} }
fs_scope.listen(move |event| { fs_scope.listen(move |event| {
let fs_scope = app.fs_scope();
if let FsScopeEvent::PathAllowed(_) = event { if let FsScopeEvent::PathAllowed(_) = event {
let scope = Scope { save_scopes(&app, &app_dir, &scope_state_path);
allowed_paths: fs_scope
.allowed_patterns()
.into_iter()
.map(|p| p.to_string())
.collect(),
forbidden_patterns: fs_scope
.forbidden_patterns()
.into_iter()
.map(|p| p.to_string())
.collect(),
};
let scope_state_path = scope_state_path.clone();
let _ = create_dir_all(&app_dir)
.and_then(|_| File::create(scope_state_path))
.map_err(Error::Io)
.and_then(|mut f| {
f.write_all(&bincode::serialize(&scope).map_err(Error::from)?)
.map_err(Into::into)
});
} }
}); });
} }

@ -1,6 +1,6 @@
[package] [package]
name = "tauri-plugin-positioner" name = "tauri-plugin-positioner"
version = "0.2.7" version = "1.0.4"
description = "Position your windows at well-known locations." description = "Position your windows at well-known locations."
authors.workspace = true authors.workspace = true
license.workspace = true license.workspace = true

@ -6,6 +6,8 @@ This plugin is a port of [electron-positioner](https://github.com/jenslind/elect
## Install ## Install
_This plugin requires a Rust version of at least **1.64**_
There are three general methods of installation that we can recommend. There are three general methods of installation that we can recommend.
1. Use crates.io and npm (easiest, and requires you to trust that our publishing pipeline worked) 1. Use crates.io and npm (easiest, and requires you to trust that our publishing pipeline worked)
@ -20,7 +22,7 @@ Install the Core plugin by adding the following to your `Cargo.toml` file:
[dependencies] [dependencies]
tauri-plugin-positioner = "1.0" tauri-plugin-positioner = "1.0"
# or through git # or through git
tauri-plugin-positioner = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "dev" } tauri-plugin-positioner = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" }
``` ```
You can install the JavaScript Guest bindings using your preferred JavaScript package manager: You can install the JavaScript Guest bindings using your preferred JavaScript package manager:
@ -28,11 +30,11 @@ You can install the JavaScript Guest bindings using your preferred JavaScript pa
> 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. > 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 ```sh
pnpm add tauri-plugin-positioner pnpm add tauri-plugin-positioner-api
# or # or
npm add tauri-plugin-positioner npm add tauri-plugin-positioner-api
# or # or
yarn add tauri-plugin-positioner yarn add tauri-plugin-positioner-api
``` ```
Or through git: Or through git:

@ -1,6 +1,6 @@
{ {
"name": "tauri-plugin-positioner-api", "name": "tauri-plugin-positioner-api",
"version": "0.0.0", "version": "0.2.7",
"description": "Position your windows at well-known locations.", "description": "Position your windows at well-known locations.",
"license": "MIT or APACHE-2.0", "license": "MIT or APACHE-2.0",
"authors": [ "authors": [
@ -25,7 +25,7 @@
"LICENSE" "LICENSE"
], ],
"devDependencies": { "devDependencies": {
"tslib": "^2.4.1" "tslib": "^2.5.0"
}, },
"dependencies": { "dependencies": {
"@tauri-apps/api": "^1.2.0" "@tauri-apps/api": "^1.2.0"

@ -1,6 +1,6 @@
[package] [package]
name = "tauri-plugin-single-instance" name = "tauri-plugin-single-instance"
version = "0.1.0" version = "0.0.0"
description = "Ensure a single instance of your tauri app is running." description = "Ensure a single instance of your tauri app is running."
authors.workspace = true authors.workspace = true
license.workspace = true license.workspace = true
@ -18,7 +18,7 @@ log.workspace = true
thiserror.workspace = true thiserror.workspace = true
[target.'cfg(target_os = "windows")'.dependencies.windows-sys] [target.'cfg(target_os = "windows")'.dependencies.windows-sys]
version = "0.42" version = "0.48"
features = [ features = [
"Win32_System_Threading", "Win32_System_Threading",
"Win32_System_DataExchange", "Win32_System_DataExchange",

@ -1,9 +1,11 @@
![tauri-plugin-single-instance](banner.jpg) ![tauri-plugin-single-instance](banner.png)
Ensure a single instance of your tauri app is running. Ensure a single instance of your tauri app is running.
## Install ## Install
_This plugin requires a Rust version of at least **1.64**_
There are three general methods of installation that we can recommend. There are three general methods of installation that we can recommend.
1. Use crates.io and npm (easiest, and requires you to trust that our publishing pipeline worked) 1. Use crates.io and npm (easiest, and requires you to trust that our publishing pipeline worked)
@ -16,7 +18,7 @@ Install the Core plugin by adding the following to your `Cargo.toml` file:
```toml ```toml
[dependencies] [dependencies]
tauri-plugin-single-instance = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "dev" } tauri-plugin-single-instance = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" }
``` ```
## Usage ## Usage
@ -36,7 +38,7 @@ struct Payload {
fn main() { fn main() {
tauri::Builder::default() tauri::Builder::default()
.plugin(auri_plugin_single_instance::init(|app, argv, cwd| { .plugin(tauri_plugin_single_instance::init(|app, argv, cwd| {
println!("{}, {argv:?}, {cwd}", app.package_info().name); println!("{}, {argv:?}, {cwd}", app.package_info().name);
app.emit_all("single-instance", Payload { args: argv, cwd }).unwrap(); app.emit_all("single-instance", Payload { args: argv, cwd }).unwrap();

@ -8,7 +8,7 @@
}, },
"author": "", "author": "",
"license": "MIT", "license": "MIT",
"dependencies": { "devDependencies": {
"@tauri-apps/cli": "^1.0.0" "@tauri-apps/cli": "^1.2.3"
} }
} }

@ -1,6 +1,6 @@
[package] [package]
name = "tauri-plugin-sql" name = "tauri-plugin-sql"
version = "0.1.0" version = "0.0.0"
description = "Interface with SQL databases." description = "Interface with SQL databases."
authors.workspace = true authors.workspace = true
license.workspace = true license.workspace = true
@ -15,9 +15,10 @@ serde_json.workspace = true
tauri.workspace = true tauri.workspace = true
log.workspace = true log.workspace = true
thiserror.workspace = true thiserror.workspace = true
sqlx = { version = "0.6", features = ["runtime-tokio-rustls", "json"] } futures-core = "0.3"
sqlx = { version = "0.6", features = ["runtime-tokio-rustls", "json", "time"] }
time = "0.3"
tokio = { version = "1", features = ["sync"] } tokio = { version = "1", features = ["sync"] }
futures = "0.3"
[features] [features]
sqlite = ["sqlx/sqlite"] sqlite = ["sqlx/sqlite"]

@ -4,6 +4,8 @@ Interface with SQL databases through [sqlx](https://github.com/launchbadge/sqlx)
## Install ## Install
_This plugin requires a Rust version of at least **1.64**_
There are three general methods of installation that we can recommend. There are three general methods of installation that we can recommend.
1. Use crates.io and npm (easiest, and requires you to trust that our publishing pipeline worked) 1. Use crates.io and npm (easiest, and requires you to trust that our publishing pipeline worked)
@ -17,7 +19,7 @@ Install the Core plugin by adding the following to your `Cargo.toml` file:
```toml ```toml
[dependencies.tauri-plugin-sql] [dependencies.tauri-plugin-sql]
git = "https://github.com/tauri-apps/plugins-workspace" git = "https://github.com/tauri-apps/plugins-workspace"
branch = "dev" branch = "v1"
features = ["sqlite"] # or "postgres", or "mysql" features = ["sqlite"] # or "postgres", or "mysql"
``` ```

@ -25,7 +25,7 @@
"LICENSE" "LICENSE"
], ],
"devDependencies": { "devDependencies": {
"tslib": "^2.4.1" "tslib": "^2.5.0"
}, },
"dependencies": { "dependencies": {
"@tauri-apps/api": "^1.2.0" "@tauri-apps/api": "^1.2.0"

@ -0,0 +1,15 @@
#[cfg(feature = "mysql")]
mod mysql;
#[cfg(feature = "postgres")]
mod postgres;
#[cfg(feature = "sqlite")]
mod sqlite;
#[cfg(feature = "mysql")]
pub(crate) use mysql::to_json;
#[cfg(feature = "postgres")]
pub(crate) use postgres::to_json;
#[cfg(feature = "sqlite")]
pub(crate) use sqlite::to_json;

@ -0,0 +1,90 @@
use serde_json::Value as JsonValue;
use sqlx::{mysql::MySqlValueRef, TypeInfo, Value, ValueRef};
use time::{Date, OffsetDateTime, PrimitiveDateTime, Time};
use crate::Error;
pub(crate) fn to_json(v: MySqlValueRef) -> Result<JsonValue, Error> {
if v.is_null() {
return Ok(JsonValue::Null);
}
let res = match v.type_info().name() {
"CHAR" | "VARCHAR" | "TINYTEXT" | "TEXT" | "MEDIUMTEXT" | "LONGTEXT" | "ENUM" => {
if let Ok(v) = ValueRef::to_owned(&v).try_decode() {
JsonValue::String(v)
} else {
JsonValue::Null
}
}
"FLOAT" | "DOUBLE" => {
if let Ok(v) = ValueRef::to_owned(&v).try_decode::<f64>() {
JsonValue::from(v)
} else {
JsonValue::Null
}
}
"TINYINT" | "SMALLINT" | "INT" | "MEDIUMINT" | "BIGINT" => {
if let Ok(v) = ValueRef::to_owned(&v).try_decode::<i64>() {
JsonValue::Number(v.into())
} else {
JsonValue::Null
}
}
"TINYINT UNSIGNED" | "SMALLINT UNSIGNED" | "INT UNSIGNED" | "MEDIUMINT UNSIGNED"
| "BIGINT UNSIGNED" | "YEAR" => {
if let Ok(v) = ValueRef::to_owned(&v).try_decode::<u64>() {
JsonValue::Number(v.into())
} else {
JsonValue::Null
}
}
"BOOLEAN" => {
if let Ok(v) = ValueRef::to_owned(&v).try_decode() {
JsonValue::Bool(v)
} else {
JsonValue::Null
}
}
"DATE" => {
if let Ok(v) = ValueRef::to_owned(&v).try_decode::<Date>() {
JsonValue::String(v.to_string())
} else {
JsonValue::Null
}
}
"TIME" => {
if let Ok(v) = ValueRef::to_owned(&v).try_decode::<Time>() {
JsonValue::String(v.to_string())
} else {
JsonValue::Null
}
}
"DATETIME" => {
if let Ok(v) = ValueRef::to_owned(&v).try_decode::<PrimitiveDateTime>() {
JsonValue::String(v.to_string())
} else {
JsonValue::Null
}
}
"TIMESTAMP" => {
if let Ok(v) = ValueRef::to_owned(&v).try_decode::<OffsetDateTime>() {
JsonValue::String(v.to_string())
} else {
JsonValue::Null
}
}
"JSON" => ValueRef::to_owned(&v).try_decode().unwrap_or_default(),
"TINIYBLOB" | "MEDIUMBLOB" | "BLOB" | "LONGBLOB" => {
if let Ok(v) = ValueRef::to_owned(&v).try_decode::<Vec<u8>>() {
JsonValue::Array(v.into_iter().map(|n| JsonValue::Number(n.into())).collect())
} else {
JsonValue::Null
}
}
"NULL" => JsonValue::Null,
_ => return Err(Error::UnsupportedDatatype(v.type_info().name().to_string())),
};
Ok(res)
}

@ -0,0 +1,82 @@
use serde_json::Value as JsonValue;
use sqlx::{postgres::PgValueRef, TypeInfo, Value, ValueRef};
use time::{Date, OffsetDateTime, PrimitiveDateTime, Time};
use crate::Error;
pub(crate) fn to_json(v: PgValueRef) -> Result<JsonValue, Error> {
if v.is_null() {
return Ok(JsonValue::Null);
}
let res = match v.type_info().name() {
"CHAR" | "VARCHAR" | "TEXT" | "NAME" => {
if let Ok(v) = ValueRef::to_owned(&v).try_decode() {
JsonValue::String(v)
} else {
JsonValue::Null
}
}
"FLOAT4" | "FLOAT8" => {
if let Ok(v) = ValueRef::to_owned(&v).try_decode::<f64>() {
JsonValue::from(v)
} else {
JsonValue::Null
}
}
"INT2" | "INT4" | "INT8" => {
if let Ok(v) = ValueRef::to_owned(&v).try_decode::<i64>() {
JsonValue::Number(v.into())
} else {
JsonValue::Null
}
}
"BOOL" => {
if let Ok(v) = ValueRef::to_owned(&v).try_decode() {
JsonValue::Bool(v)
} else {
JsonValue::Null
}
}
"DATE" => {
if let Ok(v) = ValueRef::to_owned(&v).try_decode::<Date>() {
JsonValue::String(v.to_string())
} else {
JsonValue::Null
}
}
"TIME" => {
if let Ok(v) = ValueRef::to_owned(&v).try_decode::<Time>() {
JsonValue::String(v.to_string())
} else {
JsonValue::Null
}
}
"TIMESTAMP" => {
if let Ok(v) = ValueRef::to_owned(&v).try_decode::<PrimitiveDateTime>() {
JsonValue::String(v.to_string())
} else {
JsonValue::Null
}
}
"TIMESTAMPTZ" => {
if let Ok(v) = ValueRef::to_owned(&v).try_decode::<OffsetDateTime>() {
JsonValue::String(v.to_string())
} else {
JsonValue::Null
}
}
"JSON" | "JSONB" => ValueRef::to_owned(&v).try_decode().unwrap_or_default(),
"BYTEA" => {
if let Ok(v) = ValueRef::to_owned(&v).try_decode::<Vec<u8>>() {
JsonValue::Array(v.into_iter().map(|n| JsonValue::Number(n.into())).collect())
} else {
JsonValue::Null
}
}
"VOID" => JsonValue::Null,
_ => return Err(Error::UnsupportedDatatype(v.type_info().name().to_string())),
};
Ok(res)
}

@ -0,0 +1,74 @@
use serde_json::Value as JsonValue;
use sqlx::{sqlite::SqliteValueRef, TypeInfo, Value, ValueRef};
use time::{Date, PrimitiveDateTime, Time};
use crate::Error;
pub(crate) fn to_json(v: SqliteValueRef) -> Result<JsonValue, Error> {
if v.is_null() {
return Ok(JsonValue::Null);
}
let res = match v.type_info().name() {
"TEXT" => {
if let Ok(v) = v.to_owned().try_decode() {
JsonValue::String(v)
} else {
JsonValue::Null
}
}
"REAL" => {
if let Ok(v) = v.to_owned().try_decode::<f64>() {
JsonValue::from(v)
} else {
JsonValue::Null
}
}
"INTEGER" | "NUMERIC" => {
if let Ok(v) = v.to_owned().try_decode::<i64>() {
JsonValue::Number(v.into())
} else {
JsonValue::Null
}
}
"BOOLEAN" => {
if let Ok(v) = v.to_owned().try_decode() {
JsonValue::Bool(v)
} else {
JsonValue::Null
}
}
"DATE" => {
if let Ok(v) = v.to_owned().try_decode::<Date>() {
JsonValue::String(v.to_string())
} else {
JsonValue::Null
}
}
"TIME" => {
if let Ok(v) = v.to_owned().try_decode::<Time>() {
JsonValue::String(v.to_string())
} else {
JsonValue::Null
}
}
"DATETIME" => {
if let Ok(v) = v.to_owned().try_decode::<PrimitiveDateTime>() {
JsonValue::String(v.to_string())
} else {
JsonValue::Null
}
}
"BLOB" => {
if let Ok(v) = v.to_owned().try_decode::<Vec<u8>>() {
JsonValue::Array(v.into_iter().map(|n| JsonValue::Number(n.into())).collect())
} else {
JsonValue::Null
}
}
"NULL" => JsonValue::Null,
_ => return Err(Error::UnsupportedDatatype(v.type_info().name().to_string())),
};
Ok(res)
}

@ -14,15 +14,6 @@ compile_error!(
"Database driver not defined. Please set the feature flag for the driver of your choice." "Database driver not defined. Please set the feature flag for the driver of your choice."
); );
#[cfg(any( mod decode;
all(feature = "sqlite", not(any(feature = "mysql", feature = "postgres"))),
all(feature = "mysql", not(any(feature = "sqlite", feature = "postgres"))),
all(feature = "postgres", not(any(feature = "sqlite", feature = "mysql"))),
))]
mod plugin; mod plugin;
#[cfg(any(
all(feature = "sqlite", not(any(feature = "mysql", feature = "postgres"))),
all(feature = "mysql", not(any(feature = "sqlite", feature = "postgres"))),
all(feature = "postgres", not(any(feature = "sqlite", feature = "mysql"))),
))]
pub use plugin::*; pub use plugin::*;

@ -2,7 +2,7 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
use futures::future::BoxFuture; use futures_core::future::BoxFuture;
use serde::{ser::Serializer, Deserialize, Serialize}; use serde::{ser::Serializer, Deserialize, Serialize};
use serde_json::Value as JsonValue; use serde_json::Value as JsonValue;
use sqlx::{ use sqlx::{
@ -10,7 +10,7 @@ use sqlx::{
migrate::{ migrate::{
MigrateDatabase, Migration as SqlxMigration, MigrationSource, MigrationType, Migrator, MigrateDatabase, Migration as SqlxMigration, MigrationSource, MigrationType, Migrator,
}, },
Column, Pool, Row, TypeInfo, Column, Pool, Row,
}; };
use tauri::{ use tauri::{
command, command,
@ -44,6 +44,8 @@ pub enum Error {
Migration(#[from] sqlx::migrate::MigrateError), Migration(#[from] sqlx::migrate::MigrateError),
#[error("database {0} not loaded")] #[error("database {0} not loaded")]
DatabaseNotLoaded(String), DatabaseNotLoaded(String),
#[error("unsupported datatype: {0}")]
UnsupportedDatatype(String),
} }
impl Serialize for Error { impl Serialize for Error {
@ -208,7 +210,9 @@ async fn execute(
let db = instances.get_mut(&db).ok_or(Error::DatabaseNotLoaded(db))?; let db = instances.get_mut(&db).ok_or(Error::DatabaseNotLoaded(db))?;
let mut query = sqlx::query(&query); let mut query = sqlx::query(&query);
for value in values { for value in values {
if value.is_string() { if value.is_null() {
query = query.bind(None::<JsonValue>);
} else if value.is_string() {
query = query.bind(value.as_str().unwrap().to_owned()) query = query.bind(value.as_str().unwrap().to_owned())
} else { } else {
query = query.bind(value); query = query.bind(value);
@ -235,7 +239,9 @@ async fn select(
let db = instances.get_mut(&db).ok_or(Error::DatabaseNotLoaded(db))?; let db = instances.get_mut(&db).ok_or(Error::DatabaseNotLoaded(db))?;
let mut query = sqlx::query(&query); let mut query = sqlx::query(&query);
for value in values { for value in values {
if value.is_string() { if value.is_null() {
query = query.bind(None::<JsonValue>);
} else if value.is_string() {
query = query.bind(value.as_str().unwrap().to_owned()) query = query.bind(value.as_str().unwrap().to_owned())
} else { } else {
query = query.bind(value); query = query.bind(value);
@ -246,57 +252,16 @@ async fn select(
for row in rows { for row in rows {
let mut value = HashMap::default(); let mut value = HashMap::default();
for (i, column) in row.columns().iter().enumerate() { for (i, column) in row.columns().iter().enumerate() {
let info = column.type_info(); let v = row.try_get_raw(i)?;
let v = if info.is_null() {
JsonValue::Null let v = crate::decode::to_json(v)?;
} else {
match info.name() {
"VARCHAR" | "STRING" | "TEXT" | "DATETIME" | "JSON" => {
if let Ok(s) = row.try_get(i) {
JsonValue::String(s)
} else {
JsonValue::Null
}
}
"BOOL" | "BOOLEAN" => {
if let Ok(b) = row.try_get(i) {
JsonValue::Bool(b)
} else {
let x: String = row.get(i);
JsonValue::Bool(x.to_lowercase() == "true")
}
}
"INT" | "NUMBER" | "INTEGER" | "BIGINT" | "INT8" => {
if let Ok(n) = row.try_get::<i64, usize>(i) {
JsonValue::Number(n.into())
} else {
JsonValue::Null
}
}
"REAL" => {
if let Ok(n) = row.try_get::<f64, usize>(i) {
JsonValue::from(n)
} else {
JsonValue::Null
}
}
// "JSON" => JsonValue::Object(row.get(i)),
"BLOB" => {
if let Ok(n) = row.try_get::<Vec<u8>, usize>(i) {
JsonValue::Array(
n.into_iter().map(|n| JsonValue::Number(n.into())).collect(),
)
} else {
JsonValue::Null
}
}
_ => JsonValue::Null,
}
};
value.insert(column.name().to_string(), v); value.insert(column.name().to_string(), v);
} }
values.push(value); values.push(value);
} }
Ok(values) Ok(values)
} }

@ -1,6 +1,6 @@
[package] [package]
name = "tauri-plugin-store" name = "tauri-plugin-store"
version = "0.1.0" version = "0.0.0"
description = "Simple, persistent key-value store." description = "Simple, persistent key-value store."
authors.workspace = true authors.workspace = true
license.workspace = true license.workspace = true

@ -4,6 +4,8 @@ Simple, persistent key-value store.
## Install ## Install
_This plugin requires a Rust version of at least **1.64**_
There are three general methods of installation that we can recommend. There are three general methods of installation that we can recommend.
1. Use crates.io and npm (easiest, and requires you to trust that our publishing pipeline worked) 1. Use crates.io and npm (easiest, and requires you to trust that our publishing pipeline worked)
@ -16,7 +18,7 @@ Install the Core plugin by adding the following to your `Cargo.toml` file:
```toml ```toml
[dependencies] [dependencies]
tauri-plugin-store = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "dev" } tauri-plugin-store = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" }
``` ```
You can install the JavaScript Guest bindings using your preferred JavaScript package manager: You can install the JavaScript Guest bindings using your preferred JavaScript package manager:
@ -57,6 +59,48 @@ await store.set("some-key", { value: 5 });
const val = await store.get("some-key"); const val = await store.get("some-key");
assert(val, { value: 5 }); assert(val, { value: 5 });
await store.save(); // this manually saves the store, otherwise the store is only saved when your app is closed
```
### Persisting values
Values added to the store are not persisted between application loads unless:
1. The application is closed gracefully (plugin automatically saves)
2. The store is manually saved (using `store.save()`)
## Usage from Rust
You can also access Stores from Rust, you can create new stores:
```rust
use tauri_plugin_store::StoreBuilder;
use serde_json::json;
fn main() {
tauri::Builder::default()
.plugin(tauri_plugin_store::Builder::default().build())
.setup(|app| {
let mut store = StoreBuilder::new(app.handle(), "path/to/store.bin".parse()?).build();
store.insert("a".to_string(), json!("b")) // note that values must be serd_json::Value to be compatible with JS
})
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
```
As you may have noticed, the Store crated above isn't accessible to the frontend. To interoperate with stores created by JS use the exported `with_store` method:
```rust
use tauri::Wry;
use tauri_plugin_store::with_store;
let stores = app.state::<StoreCollection<Wry>>();
let path = PathBuf::from("path/to/the/storefile");
with_store(app_handle, stores, path, |store| store.insert("a".to_string(), json!("b")))
``` ```
## Contributing ## Contributing

@ -3,8 +3,7 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
import { invoke } from "@tauri-apps/api/tauri"; import { invoke } from "@tauri-apps/api/tauri";
import { UnlistenFn } from "@tauri-apps/api/event"; import { listen, UnlistenFn } from "@tauri-apps/api/event";
import { appWindow } from "@tauri-apps/api/window";
interface ChangePayload<T> { interface ChangePayload<T> {
path: string; path: string;
@ -180,14 +179,11 @@ export class Store {
key: string, key: string,
cb: (value: T | null) => void cb: (value: T | null) => void
): Promise<UnlistenFn> { ): Promise<UnlistenFn> {
return await appWindow.listen<ChangePayload<T>>( return await listen<ChangePayload<T>>("store://change", (event) => {
"store://change", if (event.payload.path === this.path && event.payload.key === key) {
(event) => { cb(event.payload.value);
if (event.payload.path === this.path && event.payload.key === key) {
cb(event.payload.value);
}
} }
); });
} }
/** /**
@ -198,13 +194,10 @@ export class Store {
async onChange<T>( async onChange<T>(
cb: (key: string, value: T | null) => void cb: (key: string, value: T | null) => void
): Promise<UnlistenFn> { ): Promise<UnlistenFn> {
return await appWindow.listen<ChangePayload<T>>( return await listen<ChangePayload<T>>("store://change", (event) => {
"store://change", if (event.payload.path === this.path) {
(event) => { cb(event.payload.key, event.payload.value);
if (event.payload.path === this.path) {
cb(event.payload.key, event.payload.value);
}
} }
); });
} }
} }

@ -25,7 +25,7 @@
"LICENSE" "LICENSE"
], ],
"devDependencies": { "devDependencies": {
"tslib": "^2.4.1" "tslib": "^2.5.0"
}, },
"dependencies": { "dependencies": {
"@tauri-apps/api": "^1.2.0" "@tauri-apps/api": "^1.2.0"

@ -10,9 +10,9 @@ use std::path::PathBuf;
#[non_exhaustive] #[non_exhaustive]
pub enum Error { pub enum Error {
#[error("Failed to serialize store. {0}")] #[error("Failed to serialize store. {0}")]
Serialize(Box<dyn std::error::Error>), Serialize(Box<dyn std::error::Error + Send + Sync>),
#[error("Failed to deserialize store. {0}")] #[error("Failed to deserialize store. {0}")]
Deserialize(Box<dyn std::error::Error>), Deserialize(Box<dyn std::error::Error + Send + Sync>),
/// JSON error. /// JSON error.
#[error(transparent)] #[error(transparent)]
Json(#[from] serde_json::Error), Json(#[from] serde_json::Error),
@ -22,6 +22,9 @@ pub enum Error {
/// Store not found /// Store not found
#[error("Store \"{0}\" not found")] #[error("Store \"{0}\" not found")]
NotFound(PathBuf), NotFound(PathBuf),
/// Some Tauri API failed
#[error(transparent)]
Tauri(#[from] tauri::Error),
} }
impl Serialize for Error { impl Serialize for Error {

@ -5,251 +5,201 @@
pub use error::Error; pub use error::Error;
use log::warn; use log::warn;
use serde::Serialize; use serde::Serialize;
use serde_json::Value as JsonValue; pub use serde_json::Value as JsonValue;
use std::{collections::HashMap, path::PathBuf, sync::Mutex}; use std::{
collections::HashMap,
path::{Path, PathBuf},
sync::Mutex,
};
pub use store::{Store, StoreBuilder}; pub use store::{Store, StoreBuilder};
use tauri::{ use tauri::{
plugin::{self, TauriPlugin}, plugin::{self, TauriPlugin},
AppHandle, Manager, RunEvent, Runtime, State, Window, AppHandle, Manager, RunEvent, Runtime, State,
}; };
mod error; mod error;
mod store; mod store;
#[derive(Serialize, Clone)] #[derive(Serialize, Clone)]
struct ChangePayload { struct ChangePayload<'a> {
path: PathBuf, path: &'a Path,
key: String, key: &'a str,
value: JsonValue, value: &'a JsonValue,
} }
#[derive(Default)] #[derive(Default)]
struct StoreCollection { pub struct StoreCollection<R: Runtime> {
stores: Mutex<HashMap<PathBuf, Store>>, stores: Mutex<HashMap<PathBuf, Store<R>>>,
frozen: bool, frozen: bool,
} }
fn with_store<R: Runtime, T, F: FnOnce(&mut Store) -> Result<T, Error>>( pub fn with_store<R: Runtime, T, F: FnOnce(&mut Store<R>) -> Result<T, Error>>(
app: &AppHandle<R>, app: AppHandle<R>,
collection: State<'_, StoreCollection>, collection: State<'_, StoreCollection<R>>,
path: PathBuf, path: impl AsRef<Path>,
f: F, f: F,
) -> Result<T, Error> { ) -> Result<T, Error> {
let mut stores = collection.stores.lock().expect("mutex poisoned"); let mut stores = collection.stores.lock().expect("mutex poisoned");
if !stores.contains_key(&path) { let path = path.as_ref();
if !stores.contains_key(path) {
if collection.frozen { if collection.frozen {
return Err(Error::NotFound(path)); return Err(Error::NotFound(path.to_path_buf()));
} }
let mut store = StoreBuilder::new(path.clone()).build(); let mut store = StoreBuilder::new(app, path.to_path_buf()).build();
// ignore loading errors, just use the default // ignore loading errors, just use the default
if let Err(err) = store.load(app) { if let Err(err) = store.load() {
warn!( warn!(
"Failed to load store {:?} from disk: {}. Falling back to default values.", "Failed to load store {:?} from disk: {}. Falling back to default values.",
path, err path, err
); );
} }
stores.insert(path.clone(), store); stores.insert(path.to_path_buf(), store);
} }
f(stores f(stores
.get_mut(&path) .get_mut(path)
.expect("failed to retrieve store. This is a bug!")) .expect("failed to retrieve store. This is a bug!"))
} }
#[tauri::command] #[tauri::command]
async fn set<R: Runtime>( async fn set<R: Runtime>(
app: AppHandle<R>, app: AppHandle<R>,
window: Window<R>, stores: State<'_, StoreCollection<R>>,
stores: State<'_, StoreCollection>,
path: PathBuf, path: PathBuf,
key: String, key: String,
value: JsonValue, value: JsonValue,
) -> Result<(), Error> { ) -> Result<(), Error> {
with_store(&app, stores, path.clone(), |store| { with_store(app, stores, path, |store| store.insert(key, value))
store.cache.insert(key.clone(), value.clone());
let _ = window.emit("store://change", ChangePayload { path, key, value });
Ok(())
})
} }
#[tauri::command] #[tauri::command]
async fn get<R: Runtime>( async fn get<R: Runtime>(
app: AppHandle<R>, app: AppHandle<R>,
stores: State<'_, StoreCollection>, stores: State<'_, StoreCollection<R>>,
path: PathBuf, path: PathBuf,
key: String, key: String,
) -> Result<Option<JsonValue>, Error> { ) -> Result<Option<JsonValue>, Error> {
with_store(&app, stores, path, |store| { with_store(app, stores, path, |store| Ok(store.get(key).cloned()))
Ok(store.cache.get(&key).cloned())
})
} }
#[tauri::command] #[tauri::command]
async fn has<R: Runtime>( async fn has<R: Runtime>(
app: AppHandle<R>, app: AppHandle<R>,
stores: State<'_, StoreCollection>, stores: State<'_, StoreCollection<R>>,
path: PathBuf, path: PathBuf,
key: String, key: String,
) -> Result<bool, Error> { ) -> Result<bool, Error> {
with_store(&app, stores, path, |store| { with_store(app, stores, path, |store| Ok(store.has(key)))
Ok(store.cache.contains_key(&key))
})
} }
#[tauri::command] #[tauri::command]
async fn delete<R: Runtime>( async fn delete<R: Runtime>(
app: AppHandle<R>, app: AppHandle<R>,
window: Window<R>, stores: State<'_, StoreCollection<R>>,
stores: State<'_, StoreCollection>,
path: PathBuf, path: PathBuf,
key: String, key: String,
) -> Result<bool, Error> { ) -> Result<bool, Error> {
with_store(&app, stores, path.clone(), |store| { with_store(app, stores, path, |store| store.delete(key))
let flag = store.cache.remove(&key).is_some();
if flag {
let _ = window.emit(
"store://change",
ChangePayload {
path,
key,
value: JsonValue::Null,
},
);
}
Ok(flag)
})
} }
#[tauri::command] #[tauri::command]
async fn clear<R: Runtime>( async fn clear<R: Runtime>(
app: AppHandle<R>, app: AppHandle<R>,
window: Window<R>, stores: State<'_, StoreCollection<R>>,
stores: State<'_, StoreCollection>,
path: PathBuf, path: PathBuf,
) -> Result<(), Error> { ) -> Result<(), Error> {
with_store(&app, stores, path.clone(), |store| { with_store(app, stores, path, |store| store.clear())
let keys = store.cache.keys().cloned().collect::<Vec<String>>();
store.cache.clear();
for key in keys {
let _ = window.emit(
"store://change",
ChangePayload {
path: path.clone(),
key,
value: JsonValue::Null,
},
);
}
Ok(())
})
} }
#[tauri::command] #[tauri::command]
async fn reset<R: Runtime>( async fn reset<R: Runtime>(
app: AppHandle<R>, app: AppHandle<R>,
window: Window<R>, collection: State<'_, StoreCollection<R>>,
collection: State<'_, StoreCollection>,
path: PathBuf, path: PathBuf,
) -> Result<(), Error> { ) -> Result<(), Error> {
let has_defaults = collection with_store(app, collection, path, |store| store.reset())
.stores
.lock()
.expect("mutex poisoned")
.get(&path)
.map(|store| store.defaults.is_some());
if Some(true) == has_defaults {
with_store(&app, collection, path.clone(), |store| {
if let Some(defaults) = &store.defaults {
for (key, value) in &store.cache {
if defaults.get(key) != Some(value) {
let _ = window.emit(
"store://change",
ChangePayload {
path: path.clone(),
key: key.clone(),
value: defaults.get(key).cloned().unwrap_or(JsonValue::Null),
},
);
}
}
store.cache = defaults.clone();
}
Ok(())
})
} else {
clear(app, window, collection, path).await
}
} }
#[tauri::command] #[tauri::command]
async fn keys<R: Runtime>( async fn keys<R: Runtime>(
app: AppHandle<R>, app: AppHandle<R>,
stores: State<'_, StoreCollection>, stores: State<'_, StoreCollection<R>>,
path: PathBuf, path: PathBuf,
) -> Result<Vec<String>, Error> { ) -> Result<Vec<String>, Error> {
with_store(&app, stores, path, |store| { with_store(app, stores, path, |store| {
Ok(store.cache.keys().cloned().collect()) Ok(store.keys().cloned().collect())
}) })
} }
#[tauri::command] #[tauri::command]
async fn values<R: Runtime>( async fn values<R: Runtime>(
app: AppHandle<R>, app: AppHandle<R>,
stores: State<'_, StoreCollection>, stores: State<'_, StoreCollection<R>>,
path: PathBuf, path: PathBuf,
) -> Result<Vec<JsonValue>, Error> { ) -> Result<Vec<JsonValue>, Error> {
with_store(&app, stores, path, |store| { with_store(app, stores, path, |store| {
Ok(store.cache.values().cloned().collect()) Ok(store.values().cloned().collect())
}) })
} }
#[tauri::command] #[tauri::command]
async fn entries<R: Runtime>( async fn entries<R: Runtime>(
app: AppHandle<R>, app: AppHandle<R>,
stores: State<'_, StoreCollection>, stores: State<'_, StoreCollection<R>>,
path: PathBuf, path: PathBuf,
) -> Result<Vec<(String, JsonValue)>, Error> { ) -> Result<Vec<(String, JsonValue)>, Error> {
with_store(&app, stores, path, |store| { with_store(app, stores, path, |store| {
Ok(store.cache.clone().into_iter().collect()) Ok(store
.entries()
.map(|(k, v)| (k.to_owned(), v.to_owned()))
.collect())
}) })
} }
#[tauri::command] #[tauri::command]
async fn length<R: Runtime>( async fn length<R: Runtime>(
app: AppHandle<R>, app: AppHandle<R>,
stores: State<'_, StoreCollection>, stores: State<'_, StoreCollection<R>>,
path: PathBuf, path: PathBuf,
) -> Result<usize, Error> { ) -> Result<usize, Error> {
with_store(&app, stores, path, |store| Ok(store.cache.len())) with_store(app, stores, path, |store| Ok(store.len()))
} }
#[tauri::command] #[tauri::command]
async fn load<R: Runtime>( async fn load<R: Runtime>(
app: AppHandle<R>, app: AppHandle<R>,
stores: State<'_, StoreCollection>, stores: State<'_, StoreCollection<R>>,
path: PathBuf, path: PathBuf,
) -> Result<(), Error> { ) -> Result<(), Error> {
with_store(&app, stores, path, |store| store.load(&app)) with_store(app, stores, path, |store| store.load())
} }
#[tauri::command] #[tauri::command]
async fn save<R: Runtime>( async fn save<R: Runtime>(
app: AppHandle<R>, app: AppHandle<R>,
stores: State<'_, StoreCollection>, stores: State<'_, StoreCollection<R>>,
path: PathBuf, path: PathBuf,
) -> Result<(), Error> { ) -> Result<(), Error> {
with_store(&app, stores, path, |store| store.save(&app)) with_store(app, stores, path, |store| store.save())
} }
#[derive(Default)] // #[derive(Default)]
pub struct Builder { pub struct Builder<R: Runtime> {
stores: HashMap<PathBuf, Store>, stores: HashMap<PathBuf, Store<R>>,
frozen: bool, frozen: bool,
} }
impl Builder { impl<R: Runtime> Default for Builder<R> {
fn default() -> Self {
Self {
stores: Default::default(),
frozen: false,
}
}
}
impl<R: Runtime> Builder<R> {
/// Registers a store with the plugin. /// Registers a store with the plugin.
/// ///
/// # Examples /// # Examples
@ -265,7 +215,7 @@ impl Builder {
/// # Ok(()) /// # Ok(())
/// # } /// # }
/// ``` /// ```
pub fn store(mut self, store: Store) -> Self { pub fn store(mut self, store: Store<R>) -> Self {
self.stores.insert(store.path.clone(), store); self.stores.insert(store.path.clone(), store);
self self
} }
@ -285,7 +235,7 @@ impl Builder {
/// # Ok(()) /// # Ok(())
/// # } /// # }
/// ``` /// ```
pub fn stores<T: IntoIterator<Item = Store>>(mut self, stores: T) -> Self { pub fn stores<T: IntoIterator<Item = Store<R>>>(mut self, stores: T) -> Self {
self.stores = stores self.stores = stores
.into_iter() .into_iter()
.map(|store| (store.path.clone(), store)) .map(|store| (store.path.clone(), store))
@ -331,7 +281,7 @@ impl Builder {
/// # Ok(()) /// # Ok(())
/// # } /// # }
/// ``` /// ```
pub fn build<R: Runtime>(mut self) -> TauriPlugin<R> { pub fn build(mut self) -> TauriPlugin<R> {
plugin::Builder::new("store") plugin::Builder::new("store")
.invoke_handler(tauri::generate_handler![ .invoke_handler(tauri::generate_handler![
set, get, has, delete, clear, reset, keys, values, length, entries, load, save set, get, has, delete, clear, reset, keys, values, length, entries, load, save
@ -339,7 +289,7 @@ impl Builder {
.setup(move |app_handle| { .setup(move |app_handle| {
for (path, store) in self.stores.iter_mut() { for (path, store) in self.stores.iter_mut() {
// ignore loading errors, just use the default // ignore loading errors, just use the default
if let Err(err) = store.load(app_handle) { if let Err(err) = store.load() {
warn!( warn!(
"Failed to load store {:?} from disk: {}. Falling back to default values.", "Failed to load store {:?} from disk: {}. Falling back to default values.",
path, err path, err
@ -356,10 +306,10 @@ impl Builder {
}) })
.on_event(|app_handle, event| { .on_event(|app_handle, event| {
if let RunEvent::Exit = event { if let RunEvent::Exit = event {
let collection = app_handle.state::<StoreCollection>(); let collection = app_handle.state::<StoreCollection<R>>();
for store in collection.stores.lock().expect("mutex poisoned").values() { for store in collection.stores.lock().expect("mutex poisoned").values() {
if let Err(err) = store.save(app_handle) { if let Err(err) = store.save() {
eprintln!("failed to save store {:?} with error {:?}", store.path, err); eprintln!("failed to save store {:?} with error {:?}", store.path, err);
} }
} }

@ -2,7 +2,7 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
use crate::Error; use crate::{ChangePayload, Error};
use serde_json::Value as JsonValue; use serde_json::Value as JsonValue;
use std::{ use std::{
collections::HashMap, collections::HashMap,
@ -10,25 +10,28 @@ use std::{
io::Write, io::Write,
path::PathBuf, path::PathBuf,
}; };
use tauri::{AppHandle, Runtime}; use tauri::{AppHandle, Manager, Runtime};
type SerializeFn = fn(&HashMap<String, JsonValue>) -> Result<Vec<u8>, Box<dyn std::error::Error>>; type SerializeFn =
type DeserializeFn = fn(&[u8]) -> Result<HashMap<String, JsonValue>, Box<dyn std::error::Error>>; fn(&HashMap<String, JsonValue>) -> Result<Vec<u8>, Box<dyn std::error::Error + Send + Sync>>;
type DeserializeFn =
fn(&[u8]) -> Result<HashMap<String, JsonValue>, Box<dyn std::error::Error + Send + Sync>>;
fn default_serialize( fn default_serialize(
cache: &HashMap<String, JsonValue>, cache: &HashMap<String, JsonValue>,
) -> Result<Vec<u8>, Box<dyn std::error::Error>> { ) -> Result<Vec<u8>, Box<dyn std::error::Error + Send + Sync>> {
Ok(serde_json::to_vec(&cache)?) Ok(serde_json::to_vec(&cache)?)
} }
fn default_deserialize( fn default_deserialize(
bytes: &[u8], bytes: &[u8],
) -> Result<HashMap<String, JsonValue>, Box<dyn std::error::Error>> { ) -> Result<HashMap<String, JsonValue>, Box<dyn std::error::Error + Send + Sync>> {
serde_json::from_slice(bytes).map_err(Into::into) serde_json::from_slice(bytes).map_err(Into::into)
} }
/// Builds a [`Store`] /// Builds a [`Store`]
pub struct StoreBuilder { pub struct StoreBuilder<R: Runtime> {
app: AppHandle<R>,
path: PathBuf, path: PathBuf,
defaults: Option<HashMap<String, JsonValue>>, defaults: Option<HashMap<String, JsonValue>>,
cache: HashMap<String, JsonValue>, cache: HashMap<String, JsonValue>,
@ -36,7 +39,7 @@ pub struct StoreBuilder {
deserialize: DeserializeFn, deserialize: DeserializeFn,
} }
impl StoreBuilder { impl<R: Runtime> StoreBuilder<R> {
/// Creates a new [`StoreBuilder`]. /// Creates a new [`StoreBuilder`].
/// ///
/// # Examples /// # Examples
@ -49,8 +52,9 @@ impl StoreBuilder {
/// # Ok(()) /// # Ok(())
/// # } /// # }
/// ``` /// ```
pub fn new(path: PathBuf) -> Self { pub fn new(app: AppHandle<R>, path: PathBuf) -> Self {
Self { Self {
app,
path, path,
defaults: None, defaults: None,
cache: Default::default(), cache: Default::default(),
@ -147,8 +151,9 @@ impl StoreBuilder {
/// ///
/// # Ok(()) /// # Ok(())
/// # } /// # }
pub fn build(self) -> Store { pub fn build(self) -> Store<R> {
Store { Store {
app: self.app,
path: self.path, path: self.path,
defaults: self.defaults, defaults: self.defaults,
cache: self.cache, cache: self.cache,
@ -159,18 +164,20 @@ impl StoreBuilder {
} }
#[derive(Clone)] #[derive(Clone)]
pub struct Store { pub struct Store<R: Runtime> {
app: AppHandle<R>,
pub(crate) path: PathBuf, pub(crate) path: PathBuf,
pub(crate) defaults: Option<HashMap<String, JsonValue>>, defaults: Option<HashMap<String, JsonValue>>,
pub(crate) cache: HashMap<String, JsonValue>, cache: HashMap<String, JsonValue>,
serialize: SerializeFn, serialize: SerializeFn,
deserialize: DeserializeFn, deserialize: DeserializeFn,
} }
impl Store { impl<R: Runtime> Store<R> {
/// Update the store from the on-disk state /// Update the store from the on-disk state
pub fn load<R: Runtime>(&mut self, app: &AppHandle<R>) -> Result<(), Error> { pub fn load(&mut self) -> Result<(), Error> {
let app_dir = app let app_dir = self
.app
.path_resolver() .path_resolver()
.app_data_dir() .app_data_dir()
.expect("failed to resolve app dir"); .expect("failed to resolve app dir");
@ -178,14 +185,16 @@ impl Store {
let bytes = read(store_path)?; let bytes = read(store_path)?;
self.cache = (self.deserialize)(&bytes).map_err(Error::Deserialize)?; self.cache
.extend((self.deserialize)(&bytes).map_err(Error::Deserialize)?);
Ok(()) Ok(())
} }
/// Saves the store to disk /// Saves the store to disk
pub fn save<R: Runtime>(&self, app: &AppHandle<R>) -> Result<(), Error> { pub fn save(&self) -> Result<(), Error> {
let app_dir = app let app_dir = self
.app
.path_resolver() .path_resolver()
.app_data_dir() .app_data_dir()
.expect("failed to resolve app dir"); .expect("failed to resolve app dir");
@ -199,9 +208,107 @@ impl Store {
Ok(()) Ok(())
} }
pub fn insert(&mut self, key: String, value: JsonValue) -> Result<(), Error> {
self.cache.insert(key.clone(), value.clone());
self.app.emit_all(
"store://change",
ChangePayload {
path: &self.path,
key: &key,
value: &value,
},
)?;
Ok(())
}
pub fn get(&self, key: impl AsRef<str>) -> Option<&JsonValue> {
self.cache.get(key.as_ref())
}
pub fn has(&self, key: impl AsRef<str>) -> bool {
self.cache.contains_key(key.as_ref())
}
pub fn delete(&mut self, key: impl AsRef<str>) -> Result<bool, Error> {
let flag = self.cache.remove(key.as_ref()).is_some();
if flag {
self.app.emit_all(
"store://change",
ChangePayload {
path: &self.path,
key: key.as_ref(),
value: &JsonValue::Null,
},
)?;
}
Ok(flag)
}
pub fn clear(&mut self) -> Result<(), Error> {
let keys: Vec<String> = self.cache.keys().cloned().collect();
self.cache.clear();
for key in keys {
self.app.emit_all(
"store://change",
ChangePayload {
path: &self.path,
key: &key,
value: &JsonValue::Null,
},
)?;
}
Ok(())
}
pub fn reset(&mut self) -> Result<(), Error> {
let has_defaults = self.defaults.is_some();
if has_defaults {
if let Some(defaults) = &self.defaults {
for (key, value) in &self.cache {
if defaults.get(key) != Some(value) {
let _ = self.app.emit_all(
"store://change",
ChangePayload {
path: &self.path,
key,
value: defaults.get(key).unwrap_or(&JsonValue::Null),
},
);
}
}
self.cache = defaults.clone();
}
Ok(())
} else {
self.clear()
}
}
pub fn keys(&self) -> impl Iterator<Item = &String> {
self.cache.keys()
}
pub fn values(&self) -> impl Iterator<Item = &JsonValue> {
self.cache.values()
}
pub fn entries(&self) -> impl Iterator<Item = (&String, &JsonValue)> {
self.cache.iter()
}
pub fn len(&self) -> usize {
self.cache.len()
}
pub fn is_empty(&self) -> bool {
self.cache.is_empty()
}
} }
impl std::fmt::Debug for Store { impl<R: Runtime> std::fmt::Debug for Store<R> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Store") f.debug_struct("Store")
.field("path", &self.path) .field("path", &self.path)

@ -1,6 +1,6 @@
[package] [package]
name = "tauri-plugin-stronghold" name = "tauri-plugin-stronghold"
version = "0.1.0" version = "0.0.0"
description = "Store secrets and keys using the IOTA Stronghold encrypted database." description = "Store secrets and keys using the IOTA Stronghold encrypted database."
authors.workspace = true authors.workspace = true
license.workspace = true license.workspace = true
@ -16,7 +16,7 @@ tauri.workspace = true
log.workspace = true log.workspace = true
thiserror.workspace = true thiserror.workspace = true
iota_stronghold = "1" iota_stronghold = "1"
iota-crypto = "0.15" iota-crypto = "0.19"
hex = "0.4" hex = "0.4"
zeroize = { version = "1", features = ["zeroize_derive"] } zeroize = { version = "1", features = ["zeroize_derive"] }

@ -4,6 +4,8 @@ Store secrets and keys using the [IOTA Stronghold](https://github.com/iotaledger
## Install ## Install
_This plugin requires a Rust version of at least **1.64**_
There are three general methods of installation that we can recommend. There are three general methods of installation that we can recommend.
1. Use crates.io and npm (easiest, and requires you to trust that our publishing pipeline worked) 1. Use crates.io and npm (easiest, and requires you to trust that our publishing pipeline worked)
@ -16,7 +18,7 @@ Install the Core plugin by adding the following to your `Cargo.toml` file:
```toml ```toml
[dependencies] [dependencies]
tauri-plugin-stronghold = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "dev" } tauri-plugin-stronghold = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" }
``` ```
You can install the JavaScript Guest bindings using your preferred JavaScript package manager: You can install the JavaScript Guest bindings using your preferred JavaScript package manager:

@ -25,7 +25,7 @@
"LICENSE" "LICENSE"
], ],
"devDependencies": { "devDependencies": {
"tslib": "^2.4.1" "tslib": "^2.5.0"
}, },
"dependencies": { "dependencies": {
"@tauri-apps/api": "^1.2.0" "@tauri-apps/api": "^1.2.0"

@ -1,6 +1,6 @@
[package] [package]
name = "tauri-plugin-upload" name = "tauri-plugin-upload"
version = "0.1.0" version = "0.0.0"
description = "Upload files from disk to a remote server over HTTP." description = "Upload files from disk to a remote server over HTTP."
authors.workspace = true authors.workspace = true
license.workspace = true license.workspace = true
@ -18,5 +18,5 @@ thiserror.workspace = true
tokio = { version = "1", features = [ "fs" ] } tokio = { version = "1", features = [ "fs" ] }
tokio-util = { version = "0.7", features = [ "codec" ] } tokio-util = { version = "0.7", features = [ "codec" ] }
reqwest = { version = "0.11", features = [ "json", "stream" ] } reqwest = { version = "0.11", features = [ "json", "stream" ] }
futures = "0.3" futures-util = "0.3"
read-progress-stream = "1.0.0" read-progress-stream = "1.0.0"

@ -1,9 +1,12 @@
![plugin-upload](banner.png) ![plugin-upload](banner.png)
Upload files from disk to a remote server over HTTP. Upload files from disk to a remote server over HTTP.
Download files from a remote HTTP server to disk.
## Install ## Install
_This plugin requires a Rust version of at least **1.64**_
There are three general methods of installation that we can recommend. There are three general methods of installation that we can recommend.
1. Use crates.io and npm (easiest, and requires you to trust that our publishing pipeline worked) 1. Use crates.io and npm (easiest, and requires you to trust that our publishing pipeline worked)
@ -16,7 +19,7 @@ Install the Core plugin by adding the following to your `Cargo.toml` file:
```toml ```toml
[dependencies] [dependencies]
tauri-plugin-upload = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "dev" } tauri-plugin-upload = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" }
``` ```
You can install the JavaScript Guest bindings using your preferred JavaScript package manager: You can install the JavaScript Guest bindings using your preferred JavaScript package manager:
@ -52,10 +55,21 @@ Afterwards all the plugin's APIs are available through the JavaScript guest bind
import { upload } from 'tauri-plugin-upload-api' import { upload } from 'tauri-plugin-upload-api'
upload( upload(
'https://example.com/file-upload' 'https://example.com/file-upload',
'./path/to/my/file.txt' './path/to/my/file.txt',
(progress, total) => console.log(`Downloaded ${progress} of ${total} bytes`) // a callback that will be called with the upload progress (progress, total) => console.log(`Uploaded ${progress} of ${total} bytes`), // a callback that will be called with the upload progress
{ 'ContentType': 'text/plain' } // optional headers to send with the request { 'Content-Type': 'text/plain' } // optional headers to send with the request
)
```
```javascript
import { download } from "tauri-plugin-upload-api";
download(
'https://example.com/file-download-link',
'./path/to/save/my/file.txt',
(progress, total) => console.log(`Downloaded ${progress} of ${total} bytes`), // a callback that will be called with the download progress
{ 'Content-Type': 'text/plain' } // optional headers to send with the request
) )
``` ```

@ -11,12 +11,12 @@ type ProgressHandler = (progress: number, total: number) => void;
const handlers: Map<number, ProgressHandler> = new Map(); const handlers: Map<number, ProgressHandler> = new Map();
let listening = false; let listening = false;
async function listenToUploadEventIfNeeded(): Promise<void> { async function listenToEventIfNeeded(event: string): Promise<void> {
if (listening) { if (listening) {
return await Promise.resolve(); return await Promise.resolve();
} }
return await appWindow return await appWindow
.listen<ProgressPayload>("upload://progress", ({ payload }) => { .listen<ProgressPayload>(event, ({ payload }) => {
const handler = handlers.get(payload.id); const handler = handlers.get(payload.id);
if (handler != null) { if (handler != null) {
handler(payload.progress, payload.total); handler(payload.progress, payload.total);
@ -27,7 +27,7 @@ async function listenToUploadEventIfNeeded(): Promise<void> {
}); });
} }
export default async function upload( async function upload(
url: string, url: string,
filePath: string, filePath: string,
progressHandler?: ProgressHandler, progressHandler?: ProgressHandler,
@ -41,7 +41,7 @@ export default async function upload(
handlers.set(id, progressHandler); handlers.set(id, progressHandler);
} }
await listenToUploadEventIfNeeded(); await listenToEventIfNeeded("upload://progress");
await invoke("plugin:upload|upload", { await invoke("plugin:upload|upload", {
id, id,
@ -50,3 +50,34 @@ export default async function upload(
headers: headers ?? {}, headers: headers ?? {},
}); });
} }
/// Download file from given url.
///
/// Note that `filePath` currently must include the file name.
/// Furthermore the progress events will report a total length of 0 if the server did not sent a `Content-Length` header or if the file is compressed.
async function download(
url: string,
filePath: string,
progressHandler?: ProgressHandler,
headers?: Map<string, string>
): Promise<void> {
const ids = new Uint32Array(1);
window.crypto.getRandomValues(ids);
const id = ids[0];
if (progressHandler != null) {
handlers.set(id, progressHandler);
}
await listenToEventIfNeeded("download://progress");
await invoke("plugin:upload|download", {
id,
url,
filePath,
headers: headers ?? {},
});
}
export default upload;
export { download, upload };

@ -25,7 +25,7 @@
"LICENSE" "LICENSE"
], ],
"devDependencies": { "devDependencies": {
"tslib": "^2.4.1" "tslib": "^2.5.0"
}, },
"dependencies": { "dependencies": {
"@tauri-apps/api": "^1.2.0" "@tauri-apps/api": "^1.2.0"

@ -2,14 +2,14 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
use futures::TryStreamExt; use futures_util::TryStreamExt;
use serde::{ser::Serializer, Serialize}; use serde::{ser::Serializer, Serialize};
use tauri::{ use tauri::{
command, command,
plugin::{Builder as PluginBuilder, TauriPlugin}, plugin::{Builder as PluginBuilder, TauriPlugin},
Runtime, Window, Runtime, Window,
}; };
use tokio::fs::File; use tokio::{fs::File, io::AsyncWriteExt};
use tokio_util::codec::{BytesCodec, FramedRead}; use tokio_util::codec::{BytesCodec, FramedRead};
use read_progress_stream::ReadProgressStream; use read_progress_stream::ReadProgressStream;
@ -24,6 +24,8 @@ pub enum Error {
Io(#[from] std::io::Error), Io(#[from] std::io::Error),
#[error(transparent)] #[error(transparent)]
Request(#[from] reqwest::Error), Request(#[from] reqwest::Error),
#[error("{0}")]
ContentLength(String),
} }
impl Serialize for Error { impl Serialize for Error {
@ -42,6 +44,44 @@ struct ProgressPayload {
total: u64, total: u64,
} }
#[command]
async fn download<R: Runtime>(
window: Window<R>,
id: u32,
url: &str,
file_path: &str,
headers: HashMap<String, String>,
) -> Result<u32> {
let client = reqwest::Client::new();
let mut request = client.get(url);
// Loop trought the headers keys and values
// and add them to the request object.
for (key, value) in headers {
request = request.header(&key, value);
}
let response = request.send().await?;
let total = response.content_length().unwrap_or(0);
let mut file = File::create(file_path).await?;
let mut stream = response.bytes_stream();
while let Some(chunk) = stream.try_next().await? {
file.write_all(&chunk).await?;
let _ = window.emit(
"download://progress",
ProgressPayload {
id,
progress: chunk.len() as u64,
total,
},
);
}
Ok(id)
}
#[command] #[command]
async fn upload<R: Runtime>( async fn upload<R: Runtime>(
window: Window<R>, window: Window<R>,
@ -88,6 +128,6 @@ fn file_to_body<R: Runtime>(id: u32, window: Window<R>, file: File) -> reqwest::
pub fn init<R: Runtime>() -> TauriPlugin<R> { pub fn init<R: Runtime>() -> TauriPlugin<R> {
PluginBuilder::new("upload") PluginBuilder::new("upload")
.invoke_handler(tauri::generate_handler![upload]) .invoke_handler(tauri::generate_handler![download, upload])
.build() .build()
} }

@ -1,6 +1,6 @@
[package] [package]
name = "tauri-plugin-websocket" name = "tauri-plugin-websocket"
version = "0.1.0" version = "0.0.0"
authors.workspace = true authors.workspace = true
license.workspace = true license.workspace = true
edition.workspace = true edition.workspace = true
@ -18,4 +18,4 @@ thiserror.workspace = true
rand = "0.8" rand = "0.8"
futures-util = "0.3" futures-util = "0.3"
tokio = { version = "1", features = ["net", "sync"] } tokio = { version = "1", features = ["net", "sync"] }
tokio-tungstenite = { version = "0.18", features = ["native-tls"] } tokio-tungstenite = { version = "0.19", features = ["native-tls"] }

@ -4,6 +4,8 @@
## Install ## Install
_This plugin requires a Rust version of at least **1.64**_
There are three general methods of installation that we can recommend. There are three general methods of installation that we can recommend.
1. Use crates.io and npm (easiest, and requires you to trust that our publishing pipeline worked) 1. Use crates.io and npm (easiest, and requires you to trust that our publishing pipeline worked)
@ -16,7 +18,7 @@ Install the Core plugin by adding the following to your `Cargo.toml` file:
```toml ```toml
[dependencies] [dependencies]
tauri-plugin-websocket = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "dev" } tauri-plugin-websocket = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" }
``` ```
You can install the JavaScript Guest bindings using your preferred JavaScript package manager: You can install the JavaScript Guest bindings using your preferred JavaScript package manager:

@ -11,16 +11,16 @@
"tauri": "tauri" "tauri": "tauri"
}, },
"devDependencies": { "devDependencies": {
"@sveltejs/adapter-auto": "^1.0.0", "@sveltejs/adapter-auto": "^2.0.0",
"@sveltejs/kit": "^1.0.0", "@sveltejs/kit": "^1.15.5",
"svelte": "^3.54.0", "@tauri-apps/cli": "^1.2.3",
"svelte-check": "^2.9.2", "svelte": "^3.58.0",
"tslib": "^2.4.1", "svelte-check": "^3.2.0",
"typescript": "^4.9.3", "tslib": "^2.5.0",
"vite": "^4.0.0" "typescript": "^5.0.4",
"vite": "^4.2.1"
}, },
"dependencies": { "dependencies": {
"@tauri-apps/cli": "^1.0.0",
"tauri-plugin-websocket-api": "link:../../" "tauri-plugin-websocket-api": "link:../../"
}, },
"type": "module" "type": "module"

@ -49,7 +49,7 @@ dependencies = [
"tauri-build", "tauri-build",
"tauri-plugin-websocket", "tauri-plugin-websocket",
"tokio", "tokio",
"tokio-tungstenite 0.15.0", "tokio-tungstenite",
] ]
[[package]] [[package]]
@ -78,9 +78,9 @@ dependencies = [
[[package]] [[package]]
name = "autocfg" name = "autocfg"
version = "1.0.1" version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]] [[package]]
name = "base64" name = "base64"
@ -100,15 +100,6 @@ version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a"
[[package]]
name = "block-buffer"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4"
dependencies = [
"generic-array",
]
[[package]] [[package]]
name = "block-buffer" name = "block-buffer"
version = "0.10.2" version = "0.10.2"
@ -331,15 +322,6 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "cpufeatures"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "66c99696f6c9dd7f35d486b9d04d7e6e202aa3e8c40d553f2fdf5e7e0c6a71ef"
dependencies = [
"libc",
]
[[package]] [[package]]
name = "cpufeatures" name = "cpufeatures"
version = "0.2.1" version = "0.2.1"
@ -402,7 +384,7 @@ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"smallvec", "smallvec",
"syn", "syn 1.0.107",
] ]
[[package]] [[package]]
@ -412,7 +394,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dfae75de57f2b2e85e8768c3ea840fd159c8f33e2b6522c7835b7abac81be16e" checksum = "dfae75de57f2b2e85e8768c3ea840fd159c8f33e2b6522c7835b7abac81be16e"
dependencies = [ dependencies = [
"quote", "quote",
"syn", "syn 1.0.107",
] ]
[[package]] [[package]]
@ -422,7 +404,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ccc0a48a9b826acdf4028595adc9db92caea352f7af011a3034acd172a52a0aa" checksum = "ccc0a48a9b826acdf4028595adc9db92caea352f7af011a3034acd172a52a0aa"
dependencies = [ dependencies = [
"quote", "quote",
"syn", "syn 1.0.107",
] ]
[[package]] [[package]]
@ -452,7 +434,7 @@ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"strsim", "strsim",
"syn", "syn 1.0.107",
] ]
[[package]] [[package]]
@ -463,9 +445,15 @@ checksum = "72c41b3b7352feb3211a0d743dc5700a4e3b60f51bd2b368892d1e0f9a95f44b"
dependencies = [ dependencies = [
"darling_core", "darling_core",
"quote", "quote",
"syn", "syn 1.0.107",
] ]
[[package]]
name = "data-encoding"
version = "2.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23d8666cb01533c39dde32bcbab8e227b4ed6679b2c925eba05feabea39508fb"
[[package]] [[package]]
name = "derivative" name = "derivative"
version = "2.2.0" version = "2.2.0"
@ -474,7 +462,7 @@ checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn", "syn 1.0.107",
] ]
[[package]] [[package]]
@ -487,16 +475,7 @@ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"rustc_version", "rustc_version",
"syn", "syn 1.0.107",
]
[[package]]
name = "digest"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066"
dependencies = [
"generic-array",
] ]
[[package]] [[package]]
@ -505,7 +484,7 @@ version = "0.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f"
dependencies = [ dependencies = [
"block-buffer 0.10.2", "block-buffer",
"crypto-common", "crypto-common",
] ]
@ -658,9 +637,9 @@ dependencies = [
[[package]] [[package]]
name = "futures-channel" name = "futures-channel"
version = "0.3.16" version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74ed2411805f6e4e3d9bc904c95d5d423b89b3b25dc0250aa74729de20629ff9" checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2"
dependencies = [ dependencies = [
"futures-core", "futures-core",
"futures-sink", "futures-sink",
@ -668,9 +647,9 @@ dependencies = [
[[package]] [[package]]
name = "futures-core" name = "futures-core"
version = "0.3.16" version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af51b1b4a7fdff033703db39de8802c673eb91855f2e0d47dcf3bf2c0ef01f99" checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c"
[[package]] [[package]]
name = "futures-executor" name = "futures-executor"
@ -685,9 +664,9 @@ dependencies = [
[[package]] [[package]]
name = "futures-io" name = "futures-io"
version = "0.3.16" version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b0e06c393068f3a6ef246c75cdca793d6a46347e75286933e5e75fd2fd11582" checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964"
[[package]] [[package]]
name = "futures-lite" name = "futures-lite"
@ -706,36 +685,33 @@ dependencies = [
[[package]] [[package]]
name = "futures-macro" name = "futures-macro"
version = "0.3.16" version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c54913bae956fb8df7f4dc6fc90362aa72e69148e3f39041fbe8742d21e0ac57" checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72"
dependencies = [ dependencies = [
"autocfg",
"proc-macro-hack",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn", "syn 2.0.16",
] ]
[[package]] [[package]]
name = "futures-sink" name = "futures-sink"
version = "0.3.16" version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0f30aaa67363d119812743aa5f33c201a7a66329f97d1a887022971feea4b53" checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e"
[[package]] [[package]]
name = "futures-task" name = "futures-task"
version = "0.3.16" version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbe54a98670017f3be909561f6ad13e810d9a51f3f061b902062ca3da80799f2" checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65"
[[package]] [[package]]
name = "futures-util" name = "futures-util"
version = "0.3.16" version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67eb846bfd58e44a8481a00049e82c43e0ccb5d61f8dc071057cb19249dd4d78" checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533"
dependencies = [ dependencies = [
"autocfg",
"futures-channel", "futures-channel",
"futures-core", "futures-core",
"futures-io", "futures-io",
@ -745,8 +721,6 @@ dependencies = [
"memchr", "memchr",
"pin-project-lite", "pin-project-lite",
"pin-utils", "pin-utils",
"proc-macro-hack",
"proc-macro-nested",
"slab", "slab",
] ]
@ -938,7 +912,7 @@ dependencies = [
"proc-macro-error", "proc-macro-error",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn", "syn 1.0.107",
] ]
[[package]] [[package]]
@ -1033,7 +1007,7 @@ dependencies = [
"proc-macro-error", "proc-macro-error",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn", "syn 1.0.107",
] ]
[[package]] [[package]]
@ -1077,7 +1051,7 @@ dependencies = [
"markup5ever", "markup5ever",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn", "syn 1.0.107",
] ]
[[package]] [[package]]
@ -1313,9 +1287,9 @@ dependencies = [
[[package]] [[package]]
name = "log" name = "log"
version = "0.4.14" version = "0.4.17"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
] ]
@ -1411,14 +1385,14 @@ dependencies = [
"libc", "libc",
"log", "log",
"wasi 0.11.0+wasi-snapshot-preview1", "wasi 0.11.0+wasi-snapshot-preview1",
"windows-sys", "windows-sys 0.42.0",
] ]
[[package]] [[package]]
name = "native-tls" name = "native-tls"
version = "0.2.7" version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8d96b2e1c8da3957d58100b09f102c6d9cfdfced01b7ec5a8974044bb09dbd4" checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e"
dependencies = [ dependencies = [
"lazy_static", "lazy_static",
"libc", "libc",
@ -1531,7 +1505,7 @@ dependencies = [
"proc-macro-crate", "proc-macro-crate",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn", "syn 1.0.107",
] ]
[[package]] [[package]]
@ -1568,12 +1542,6 @@ version = "1.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66" checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66"
[[package]]
name = "opaque-debug"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
[[package]] [[package]]
name = "openssl" name = "openssl"
version = "0.10.35" version = "0.10.35"
@ -1658,7 +1626,7 @@ dependencies = [
"libc", "libc",
"redox_syscall", "redox_syscall",
"smallvec", "smallvec",
"windows-sys", "windows-sys 0.42.0",
] ]
[[package]] [[package]]
@ -1745,7 +1713,7 @@ dependencies = [
"proc-macro-hack", "proc-macro-hack",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn", "syn 1.0.107",
] ]
[[package]] [[package]]
@ -1759,7 +1727,7 @@ dependencies = [
"proc-macro-hack", "proc-macro-hack",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn", "syn 1.0.107",
] ]
[[package]] [[package]]
@ -1780,26 +1748,6 @@ dependencies = [
"siphasher", "siphasher",
] ]
[[package]]
name = "pin-project"
version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "576bc800220cc65dac09e99e97b08b358cfab6e17078de8dc5fee223bd2d0c08"
dependencies = [
"pin-project-internal",
]
[[package]]
name = "pin-project-internal"
version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e8fe8163d14ce7f0cdac2e040116f22eac817edabff0be91e8aff7e9accf389"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]] [[package]]
name = "pin-project-lite" name = "pin-project-lite"
version = "0.2.7" version = "0.2.7"
@ -1875,7 +1823,7 @@ dependencies = [
"proc-macro-error-attr", "proc-macro-error-attr",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn", "syn 1.0.107",
"version_check", "version_check",
] ]
@ -1896,26 +1844,20 @@ version = "0.5.19"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5"
[[package]]
name = "proc-macro-nested"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc881b2c22681370c6a780e47af9840ef841837bc98118431d4e1868bd0c1086"
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.49" version = "1.0.57"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57a8eca9f9c4ffde41714334dee777596264c7825420f521abc92b5b5deb63a5" checksum = "c4ec6d5fe0b140acb27c9a0444118cf55bfbb4e0b259739429abb4521dd67c16"
dependencies = [ dependencies = [
"unicode-ident", "unicode-ident",
] ]
[[package]] [[package]]
name = "quote" name = "quote"
version = "1.0.9" version = "1.0.27"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" checksum = "8f4f29d145265ec1c483c7c654450edde0bfe043d3938d6972630663356d9500"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
] ]
@ -2210,7 +2152,7 @@ checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn", "syn 1.0.107",
] ]
[[package]] [[package]]
@ -2232,7 +2174,7 @@ checksum = "98d0516900518c29efa217c298fa1f4e6c6ffc85ae29fd7f4ee48f176e1a9ed5"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn", "syn 1.0.107",
] ]
[[package]] [[package]]
@ -2255,7 +2197,7 @@ dependencies = [
"darling", "darling",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn", "syn 1.0.107",
] ]
[[package]] [[package]]
@ -2277,7 +2219,7 @@ checksum = "74064874e9f6a15f04c1f3cb627902d0e6b410abbf36668afa873c61889f1763"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn", "syn 1.0.107",
] ]
[[package]] [[package]]
@ -2290,19 +2232,6 @@ dependencies = [
"stable_deref_trait", "stable_deref_trait",
] ]
[[package]]
name = "sha-1"
version = "0.9.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a0c8611594e2ab4ebbf06ec7cbbf0a99450b8570e96cbf5188b5d5f6ef18d81"
dependencies = [
"block-buffer 0.9.0",
"cfg-if",
"cpufeatures 0.1.5",
"digest 0.9.0",
"opaque-debug",
]
[[package]] [[package]]
name = "sha1" name = "sha1"
version = "0.10.5" version = "0.10.5"
@ -2310,8 +2239,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"cpufeatures 0.2.1", "cpufeatures",
"digest 0.10.6", "digest",
] ]
[[package]] [[package]]
@ -2321,8 +2250,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "99c3bd8169c58782adad9290a9af5939994036b76187f7b4f0e6de91dbbfc0ec" checksum = "99c3bd8169c58782adad9290a9af5939994036b76187f7b4f0e6de91dbbfc0ec"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"cpufeatures 0.2.1", "cpufeatures",
"digest 0.10.6", "digest",
] ]
[[package]] [[package]]
@ -2345,9 +2274,9 @@ checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e"
[[package]] [[package]]
name = "socket2" name = "socket2"
version = "0.4.7" version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd" checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662"
dependencies = [ dependencies = [
"libc", "libc",
"winapi", "winapi",
@ -2438,6 +2367,17 @@ dependencies = [
"unicode-ident", "unicode-ident",
] ]
[[package]]
name = "syn"
version = "2.0.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a6f671d4b5ffdb8eadec19c0ae67fe2639df8684bd7bc4b83d986b8db549cf01"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]] [[package]]
name = "system-deps" name = "system-deps"
version = "5.0.0" version = "5.0.0"
@ -2616,14 +2556,14 @@ dependencies = [
"heck 0.4.0", "heck 0.4.0",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn", "syn 1.0.107",
"tauri-codegen", "tauri-codegen",
"tauri-utils", "tauri-utils",
] ]
[[package]] [[package]]
name = "tauri-plugin-websocket" name = "tauri-plugin-websocket"
version = "0.1.0" version = "0.0.0"
dependencies = [ dependencies = [
"futures-util", "futures-util",
"log", "log",
@ -2633,7 +2573,7 @@ dependencies = [
"tauri", "tauri",
"thiserror", "thiserror",
"tokio", "tokio",
"tokio-tungstenite 0.18.0", "tokio-tungstenite",
] ]
[[package]] [[package]]
@ -2752,7 +2692,7 @@ checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn", "syn 1.0.107",
] ]
[[package]] [[package]]
@ -2808,26 +2748,25 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
[[package]] [[package]]
name = "tokio" name = "tokio"
version = "1.19.2" version = "1.27.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c51a52ed6686dd62c320f9b89299e9dfb46f730c7a48e635c19f21d116cb1439" checksum = "d0de47a4eecbe11f498978a9b29d792f0d2692d1dd003650c24c76510e3bc001"
dependencies = [ dependencies = [
"autocfg",
"bytes", "bytes",
"libc", "libc",
"memchr",
"mio", "mio",
"num_cpus", "num_cpus",
"once_cell",
"pin-project-lite", "pin-project-lite",
"socket2", "socket2",
"winapi", "windows-sys 0.45.0",
] ]
[[package]] [[package]]
name = "tokio-native-tls" name = "tokio-native-tls"
version = "0.3.0" version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7d995660bd2b7f8c1568414c1126076c13fbb725c40112dc0120b78eb9b717b" checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2"
dependencies = [ dependencies = [
"native-tls", "native-tls",
"tokio", "tokio",
@ -2835,29 +2774,16 @@ dependencies = [
[[package]] [[package]]
name = "tokio-tungstenite" name = "tokio-tungstenite"
version = "0.15.0" version = "0.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "511de3f85caf1c98983545490c3d09685fa8eb634e57eec22bb4db271f46cbd8"
dependencies = [
"futures-util",
"log",
"pin-project",
"tokio",
"tungstenite 0.14.0",
]
[[package]]
name = "tokio-tungstenite"
version = "0.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "54319c93411147bced34cb5609a80e0a8e44c5999c93903a81cd866630ec0bfd" checksum = "ec509ac96e9a0c43427c74f003127d953a265737636129424288d27cb5c4b12c"
dependencies = [ dependencies = [
"futures-util", "futures-util",
"log", "log",
"native-tls", "native-tls",
"tokio", "tokio",
"tokio-native-tls", "tokio-native-tls",
"tungstenite 0.18.0", "tungstenite",
] ]
[[package]] [[package]]
@ -2880,32 +2806,13 @@ dependencies = [
[[package]] [[package]]
name = "tungstenite" name = "tungstenite"
version = "0.14.0" version = "0.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a0b2d8558abd2e276b0a8df5c05a2ec762609344191e5fd23e292c910e9165b5"
dependencies = [
"base64",
"byteorder",
"bytes",
"http",
"httparse",
"log",
"rand 0.8.4",
"sha-1",
"thiserror",
"url",
"utf-8",
]
[[package]]
name = "tungstenite"
version = "0.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30ee6ab729cd4cf0fd55218530c4522ed30b7b6081752839b68fcec8d0960788" checksum = "15fba1a6d6bb030745759a9a2a588bfe8490fc8b4751a277db3a0be1c9ebbf67"
dependencies = [ dependencies = [
"base64",
"byteorder", "byteorder",
"bytes", "bytes",
"data-encoding",
"http", "http",
"httparse", "httparse",
"log", "log",
@ -3119,7 +3026,7 @@ checksum = "eaebe196c01691db62e9e4ca52c5ef1e4fd837dcae27dae3ada599b5a8fd05ac"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn", "syn 1.0.107",
] ]
[[package]] [[package]]
@ -3211,7 +3118,7 @@ version = "0.37.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67a1062e555f7d9d66fd1130ed4f7c6ec41a47529ee0850cd0e926d95b26bb14" checksum = "67a1062e555f7d9d66fd1130ed4f7c6ec41a47529ee0850cd0e926d95b26bb14"
dependencies = [ dependencies = [
"syn", "syn 1.0.107",
"windows-tokens 0.37.0", "windows-tokens 0.37.0",
] ]
@ -3221,7 +3128,7 @@ version = "0.39.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba01f98f509cb5dc05f4e5fc95e535f78260f15fea8fe1a8abdd08f774f1cee7" checksum = "ba01f98f509cb5dc05f4e5fc95e535f78260f15fea8fe1a8abdd08f774f1cee7"
dependencies = [ dependencies = [
"syn", "syn 1.0.107",
"windows-tokens 0.39.0", "windows-tokens 0.39.0",
] ]
@ -3238,12 +3145,36 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7"
dependencies = [ dependencies = [
"windows_aarch64_gnullvm", "windows_aarch64_gnullvm",
"windows_aarch64_msvc 0.42.0", "windows_aarch64_msvc 0.42.2",
"windows_i686_gnu 0.42.0", "windows_i686_gnu 0.42.2",
"windows_i686_msvc 0.42.0", "windows_i686_msvc 0.42.2",
"windows_x86_64_gnu 0.42.0", "windows_x86_64_gnu 0.42.2",
"windows_x86_64_gnullvm", "windows_x86_64_gnullvm",
"windows_x86_64_msvc 0.42.0", "windows_x86_64_msvc 0.42.2",
]
[[package]]
name = "windows-sys"
version = "0.45.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-targets"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc 0.42.2",
"windows_i686_gnu 0.42.2",
"windows_i686_msvc 0.42.2",
"windows_x86_64_gnu 0.42.2",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc 0.42.2",
] ]
[[package]] [[package]]
@ -3260,9 +3191,9 @@ checksum = "f838de2fe15fe6bac988e74b798f26499a8b21a9d97edec321e79b28d1d7f597"
[[package]] [[package]]
name = "windows_aarch64_gnullvm" name = "windows_aarch64_gnullvm"
version = "0.42.0" version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e" checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
[[package]] [[package]]
name = "windows_aarch64_msvc" name = "windows_aarch64_msvc"
@ -3278,9 +3209,9 @@ checksum = "ec7711666096bd4096ffa835238905bb33fb87267910e154b18b44eaabb340f2"
[[package]] [[package]]
name = "windows_aarch64_msvc" name = "windows_aarch64_msvc"
version = "0.42.0" version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4" checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
[[package]] [[package]]
name = "windows_i686_gnu" name = "windows_i686_gnu"
@ -3296,9 +3227,9 @@ checksum = "763fc57100a5f7042e3057e7e8d9bdd7860d330070251a73d003563a3bb49e1b"
[[package]] [[package]]
name = "windows_i686_gnu" name = "windows_i686_gnu"
version = "0.42.0" version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7" checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
[[package]] [[package]]
name = "windows_i686_msvc" name = "windows_i686_msvc"
@ -3314,9 +3245,9 @@ checksum = "7bc7cbfe58828921e10a9f446fcaaf649204dcfe6c1ddd712c5eebae6bda1106"
[[package]] [[package]]
name = "windows_i686_msvc" name = "windows_i686_msvc"
version = "0.42.0" version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246" checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
[[package]] [[package]]
name = "windows_x86_64_gnu" name = "windows_x86_64_gnu"
@ -3332,15 +3263,15 @@ checksum = "6868c165637d653ae1e8dc4d82c25d4f97dd6605eaa8d784b5c6e0ab2a252b65"
[[package]] [[package]]
name = "windows_x86_64_gnu" name = "windows_x86_64_gnu"
version = "0.42.0" version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed" checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
[[package]] [[package]]
name = "windows_x86_64_gnullvm" name = "windows_x86_64_gnullvm"
version = "0.42.0" version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028" checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
[[package]] [[package]]
name = "windows_x86_64_msvc" name = "windows_x86_64_msvc"
@ -3356,9 +3287,9 @@ checksum = "5e4d40883ae9cae962787ca76ba76390ffa29214667a111db9e0a1ad8377e809"
[[package]] [[package]]
name = "windows_x86_64_msvc" name = "windows_x86_64_msvc"
version = "0.42.0" version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5" checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
[[package]] [[package]]
name = "winres" name = "winres"

@ -10,10 +10,10 @@ edition = "2021"
serde = { version = "1", features = ["derive"] } serde = { version = "1", features = ["derive"] }
serde_json = "1" serde_json = "1"
tauri = { version = "1", features = [] } tauri = { version = "1", features = [] }
tokio = { version = "1.11", features = ["net"] } tokio = { version = "1", features = ["net"] }
futures-util = "0.3" futures-util = "0.3"
tauri-plugin-websocket = { path = "../../../" } tauri-plugin-websocket = { path = "../../../" }
tokio-tungstenite = "0.15" tokio-tungstenite = "0.19"
[build-dependencies] [build-dependencies]
tauri-build = { version = "1", features = [] } tauri-build = { version = "1", features = [] }

@ -24,7 +24,7 @@
"LICENSE" "LICENSE"
], ],
"devDependencies": { "devDependencies": {
"tslib": "^2.4.1" "tslib": "^2.5.0"
}, },
"dependencies": { "dependencies": {
"@tauri-apps/api": "^1.2.0" "@tauri-apps/api": "^1.2.0"

@ -79,15 +79,14 @@ enum WebSocketMessage {
} }
#[tauri::command] #[tauri::command]
fn connect<R: Runtime>( async fn connect<R: Runtime>(
window: Window<R>, window: Window<R>,
url: String, url: String,
callback_function: CallbackFn, callback_function: CallbackFn,
config: Option<ConnectionConfig>, config: Option<ConnectionConfig>,
) -> Result<Id> { ) -> Result<Id> {
let id = rand::random(); let id = rand::random();
let (ws_stream, _) = let (ws_stream, _) = connect_async_with_config(url, config.map(Into::into), false).await?;
tauri::async_runtime::block_on(connect_async_with_config(url, config.map(Into::into)))?;
tauri::async_runtime::spawn(async move { tauri::async_runtime::spawn(async move {
let (write, read) = ws_stream.split(); let (write, read) = ws_stream.split();

@ -0,0 +1 @@
node_modules

@ -1,7 +1,7 @@
[package] [package]
name = "tauri-plugin-window-state" name = "tauri-plugin-window-state"
version = "0.1.0" version = "0.1.0"
description = "Save window positions and sizse and restore them when the app is reopened." description = "Save window positions and sizes and restore them when the app is reopened."
authors.workspace = true authors.workspace = true
license.workspace = true license.workspace = true
edition.workspace = true edition.workspace = true
@ -16,4 +16,4 @@ tauri.workspace = true
log.workspace = true log.workspace = true
thiserror.workspace = true thiserror.workspace = true
bincode = "1.3" bincode = "1.3"
bitflags = "1" bitflags = "2"

@ -4,6 +4,8 @@ Save window positions and sizes and restore them when the app is reopened.
## Install ## Install
_This plugin requires a Rust version of at least **1.64**_
There are three general methods of installation that we can recommend. There are three general methods of installation that we can recommend.
1. Use crates.io and npm (easiest, and requires you to trust that our publishing pipeline worked) 1. Use crates.io and npm (easiest, and requires you to trust that our publishing pipeline worked)
@ -16,7 +18,19 @@ Install the Core plugin by adding the following to your `Cargo.toml` file:
```toml ```toml
[dependencies] [dependencies]
tauri-plugin-window-state = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "dev" } tauri-plugin-window-state = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" }
```
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 https://github.com/tauri-apps/tauri-plugin-window-state
# or
npm add https://github.com/tauri-apps/tauri-plugin-window-state
# or
yarn add https://github.com/tauri-apps/tauri-plugin-window-state
``` ```
## Usage ## Usage
@ -36,7 +50,7 @@ fn main() {
Afterwards all windows will remember their state when the app is being closed and will restore to their previous state on the next launch. Afterwards all windows will remember their state when the app is being closed and will restore to their previous state on the next launch.
Optionally you can also tell the plugin to save the state of all open window to disk my using the `save_window_state()` method exposed by the `AppHandleExt` trait: Optionally you can also tell the plugin to save the state of all open window to disk by using the `save_window_state()` method exposed by the `AppHandleExt` trait:
```rust ```rust
use tauri_plugin_window_state::{AppHandleExt, StateFlags}; use tauri_plugin_window_state::{AppHandleExt, StateFlags};
@ -45,6 +59,14 @@ use tauri_plugin_window_state::{AppHandleExt, StateFlags};
app.save_window_state(StateFlags::all()); // will save the state of all open windows to disk app.save_window_state(StateFlags::all()); // will save the state of all open windows to disk
``` ```
or through Javascript
```javascript
import { saveWindowState, StateFlags } from "tauri-plugin-window-state-api";
saveWindowState(StateFlags.ALL);
```
To manually restore a windows state from disk you can call the `restore_state()` method exposed by the `WindowExt` trait: To manually restore a windows state from disk you can call the `restore_state()` method exposed by the `WindowExt` trait:
```rust ```rust
@ -54,6 +76,14 @@ use tauri_plugin_window_state::{WindowExt, StateFlags};
window.restore_state(StateFlags::all()); // will restore the windows state from disk window.restore_state(StateFlags::all()); // will restore the windows state from disk
``` ```
or through Javascript
```javascript
import { restoreStateCurrent, StateFlags } from "tauri-plugin-window-state-api";
restoreStateCurrent(StateFlags.ALL);
```
## Contributing ## Contributing
PRs accepted. Please make sure to read the Contributing Guide before making a pull request. PRs accepted. Please make sure to read the Contributing Guide before making a pull request.

@ -0,0 +1,35 @@
import { invoke } from "@tauri-apps/api/tauri";
import { WindowLabel, getCurrent } from "@tauri-apps/api/window";
export enum StateFlags {
SIZE = 1 << 0,
POSITION = 1 << 1,
MAXIMIZED = 1 << 2,
VISIBLE = 1 << 3,
DECORATIONS = 1 << 4,
FULLSCREEN = 1 << 5,
ALL = SIZE | POSITION | MAXIMIZED | VISIBLE | DECORATIONS | FULLSCREEN,
}
/**
* Save the state of all open windows to disk.
*/
async function saveWindowState(flags: StateFlags) {
invoke("plugin:window-state|save_window_state", { flags });
}
/**
* Restore the state for the specified window from disk.
*/
async function restoreState(label: WindowLabel, flags: StateFlags) {
invoke("plugin:window-state|restore_state", { label, flags });
}
/**
* Restore the state for the current window from disk.
*/
async function restoreStateCurrent(flags: StateFlags) {
restoreState(getCurrent().label, flags);
}
export { restoreState, restoreStateCurrent, saveWindowState };

@ -0,0 +1,33 @@
{
"name": "tauri-plugin-window-state-api",
"version": "0.0.0",
"description": "Save window positions and sizes and restore them when the app is reopened.",
"license": "MIT or APACHE-2.0",
"authors": [
"Tauri Programme within The Commons Conservancy"
],
"type": "module",
"browser": "dist-js/index.min.js",
"module": "dist-js/index.mjs",
"types": "dist-js/index.d.ts",
"exports": {
"import": "./dist-js/index.mjs",
"types": "./dist-js/index.d.ts",
"browser": "./dist-js/index.min.js"
},
"scripts": {
"build": "rollup -c"
},
"files": [
"dist-js",
"!dist-js/**/*.map",
"README.md",
"LICENSE"
],
"devDependencies": {
"tslib": "^2.5.0"
},
"dependencies": {
"@tauri-apps/api": "^1.2.0"
}
}

@ -0,0 +1,11 @@
import { readFileSync } from "fs";
import { createConfig } from "../../shared/rollup.config.mjs";
export default createConfig({
input: "guest-js/index.ts",
pkg: JSON.parse(
readFileSync(new URL("./package.json", import.meta.url), "utf8")
),
external: [/^@tauri-apps\/api/],
});

@ -0,0 +1,28 @@
use crate::{AppHandleExt, StateFlags, WindowExt};
use tauri::{command, AppHandle, Manager, Runtime};
#[command]
pub async fn save_window_state<R: Runtime>(
app: AppHandle<R>,
flags: u32,
) -> std::result::Result<(), String> {
let flags = StateFlags::from_bits(flags)
.ok_or_else(|| format!("Invalid state flags bits: {}", flags))?;
app.save_window_state(flags).map_err(|e| e.to_string())?;
Ok(())
}
#[command]
pub async fn restore_state<R: Runtime>(
app: AppHandle<R>,
label: String,
flags: u32,
) -> std::result::Result<(), String> {
let flags = StateFlags::from_bits(flags)
.ok_or_else(|| format!("Invalid state flags bits: {}", flags))?;
app.get_window(&label)
.ok_or_else(|| format!("Couldn't find window with label: {}", label))?
.restore_state(flags)
.map_err(|e| e.to_string())?;
Ok(())
}

@ -17,6 +17,8 @@ use std::{
sync::{Arc, Mutex}, sync::{Arc, Mutex},
}; };
mod cmd;
pub const STATE_FILENAME: &str = ".window-state"; pub const STATE_FILENAME: &str = ".window-state";
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]
@ -34,6 +36,7 @@ pub enum Error {
pub type Result<T> = std::result::Result<T, Error>; pub type Result<T> = std::result::Result<T, Error>;
bitflags! { bitflags! {
#[derive(Clone, Copy, Debug)]
pub struct StateFlags: u32 { pub struct StateFlags: u32 {
const SIZE = 1 << 0; const SIZE = 1 << 0;
const POSITION = 1 << 1; const POSITION = 1 << 1;
@ -50,7 +53,7 @@ impl Default for StateFlags {
} }
} }
#[derive(Debug, Default, Deserialize, Serialize)] #[derive(Debug, Default, Deserialize, Serialize, PartialEq)]
struct WindowState { struct WindowState {
width: f64, width: f64,
height: f64, height: f64,
@ -64,6 +67,7 @@ struct WindowState {
struct WindowStateCache(Arc<Mutex<HashMap<String, WindowState>>>); struct WindowStateCache(Arc<Mutex<HashMap<String, WindowState>>>);
pub trait AppHandleExt { pub trait AppHandleExt {
/// Saves all open windows state to disk
fn save_window_state(&self, flags: StateFlags) -> Result<()>; fn save_window_state(&self, flags: StateFlags) -> Result<()>;
} }
@ -93,6 +97,7 @@ impl<R: Runtime> AppHandleExt for tauri::AppHandle<R> {
} }
pub trait WindowExt { pub trait WindowExt {
/// Restores this window state from disk
fn restore_state(&self, flags: StateFlags) -> tauri::Result<()>; fn restore_state(&self, flags: StateFlags) -> tauri::Result<()>;
} }
@ -100,9 +105,15 @@ impl<R: Runtime> WindowExt for Window<R> {
fn restore_state(&self, flags: StateFlags) -> tauri::Result<()> { fn restore_state(&self, flags: StateFlags) -> tauri::Result<()> {
let cache = self.state::<WindowStateCache>(); let cache = self.state::<WindowStateCache>();
let mut c = cache.0.lock().unwrap(); let mut c = cache.0.lock().unwrap();
let mut should_show = true; let mut should_show = true;
if let Some(state) = c.get(self.label()) { if let Some(state) = c.get(self.label()) {
// avoid restoring the default zeroed state
if *state == WindowState::default() {
return Ok(());
}
if flags.contains(StateFlags::DECORATIONS) { if flags.contains(StateFlags::DECORATIONS) {
self.set_decorations(state.decorated)?; self.set_decorations(state.decorated)?;
} }
@ -225,7 +236,7 @@ impl<R: Runtime> WindowExtInternal for Window<R> {
} }
if flags.contains(StateFlags::POSITION) { if flags.contains(StateFlags::POSITION) {
let position = self.inner_position()?; let position = self.outer_position()?;
if let Ok(Some(monitor)) = self.current_monitor() { if let Ok(Some(monitor)) = self.current_monitor() {
// save only window positions that are inside the current monitor // save only window positions that are inside the current monitor
if monitor.contains(position) && !is_maximized { if monitor.contains(position) && !is_maximized {
@ -269,6 +280,10 @@ impl Builder {
pub fn build<R: Runtime>(self) -> TauriPlugin<R> { pub fn build<R: Runtime>(self) -> TauriPlugin<R> {
let flags = self.state_flags; let flags = self.state_flags;
PluginBuilder::new("window-state") PluginBuilder::new("window-state")
.invoke_handler(tauri::generate_handler![
cmd::save_window_state,
cmd::restore_state
])
.setup(|app| { .setup(|app| {
let cache: Arc<Mutex<HashMap<String, WindowState>>> = if let Some(app_dir) = let cache: Arc<Mutex<HashMap<String, WindowState>>> = if let Some(app_dir) =
app.path_resolver().app_config_dir() app.path_resolver().app_config_dir()
@ -304,6 +319,17 @@ impl Builder {
let label = window.label().to_string(); let label = window.label().to_string();
let window_clone = window.clone(); let window_clone = window.clone();
let flags = self.state_flags; let flags = self.state_flags;
// insert a default state if this window should be tracked and
// the disk cache doesn't have a state for it
{
cache
.lock()
.unwrap()
.entry(label.clone())
.or_insert_with(WindowState::default);
}
window.on_window_event(move |e| { window.on_window_event(move |e| {
if let WindowEvent::CloseRequested { .. } = e { if let WindowEvent::CloseRequested { .. } = e {
let mut c = cache.lock().unwrap(); let mut c = cache.lock().unwrap();

@ -0,0 +1 @@
../../shared/tsconfig.json

File diff suppressed because it is too large Load Diff

@ -1,6 +1,16 @@
{ {
"extends": ["config:base"], "extends": ["config:base"],
"enabledManagers": ["cargo", "npm"], "enabledManagers": ["cargo", "npm"],
"semanticCommitType": "chore",
"ignorePaths": [
"**/node_modules/**",
"**/bower_components/**",
"**/vendor/**",
"**/__tests__/**",
"**/test/**",
"**/tests/**",
"**/__fixtures__/**"
],
"packageRules": [ "packageRules": [
{ {
"description": "Disable node/pnpm version updates", "description": "Disable node/pnpm version updates",

@ -1,6 +1,6 @@
[package] [package]
name = "tauri-plugin-{{name}}" name = "tauri-plugin-{{name}}"
version = "0.1.0" version = "0.0.0"
edition.workspace = true edition.workspace = true
authors.workspace = true authors.workspace = true
license.workspace = true license.workspace = true

@ -4,6 +4,8 @@
## Install ## Install
_This plugin requires a Rust version of at least **1.64**_
There are three general methods of installation that we can recommend. There are three general methods of installation that we can recommend.
1. Use crates.io and npm (easiest, and requires you to trust that our publishing pipeline worked) 1. Use crates.io and npm (easiest, and requires you to trust that our publishing pipeline worked)
@ -16,7 +18,7 @@ Install the Core plugin by adding the following to your `Cargo.toml` file:
```toml ```toml
[dependencies] [dependencies]
<!-- plugin here --> = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "dev" } <!-- plugin here --> = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" }
``` ```
You can install the JavaScript Guest bindings using your preferred JavaScript package manager: You can install the JavaScript Guest bindings using your preferred JavaScript package manager:

@ -14,7 +14,7 @@
"strict": true, "strict": true,
"target": "ES2019", "target": "ES2019",
"declaration": true, "declaration": true,
"declarationDir": "dist" "declarationDir": "./"
}, },
"exclude": ["dist-js", "node_modules", "test/types"] "exclude": ["dist-js", "node_modules", "test/types"]
} }

Loading…
Cancel
Save