diff --git a/.changes/config.json b/.changes/config.json
index 399448f5..42de6fec 100644
--- a/.changes/config.json
+++ b/.changes/config.json
@@ -189,6 +189,15 @@
"manager": "javascript"
},
+ "nfc": {
+ "path": "./plugins/nfc",
+ "manager": "rust"
+ },
+ "nfc-js": {
+ "path": "./plugins/nfc",
+ "manager": "javascript"
+ },
+
"notification": {
"path": "./plugins/notification",
"manager": "rust"
diff --git a/.changes/nfc-initial-release.md b/.changes/nfc-initial-release.md
new file mode 100644
index 00000000..0a5ee02b
--- /dev/null
+++ b/.changes/nfc-initial-release.md
@@ -0,0 +1,6 @@
+---
+"nfc": major
+"nfc-js": major
+---
+
+Initial release.
diff --git a/Cargo.lock b/Cargo.lock
index 084d891f..eeb2dec2 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -245,6 +245,7 @@ dependencies = [
"tauri-plugin-global-shortcut",
"tauri-plugin-http",
"tauri-plugin-log",
+ "tauri-plugin-nfc",
"tauri-plugin-notification",
"tauri-plugin-os",
"tauri-plugin-process",
@@ -6023,6 +6024,19 @@ dependencies = [
"time 0.3.30",
]
+[[package]]
+name = "tauri-plugin-nfc"
+version = "1.0.0"
+dependencies = [
+ "log",
+ "serde",
+ "serde_json",
+ "serde_repr",
+ "tauri",
+ "tauri-build",
+ "thiserror",
+]
+
[[package]]
name = "tauri-plugin-notification"
version = "2.0.0-alpha.6"
diff --git a/examples/api/package.json b/examples/api/package.json
index 3d076b42..e1135b54 100644
--- a/examples/api/package.json
+++ b/examples/api/package.json
@@ -17,6 +17,7 @@
"@tauri-apps/plugin-fs": "2.0.0-alpha.4",
"@tauri-apps/plugin-global-shortcut": "2.0.0-alpha.4",
"@tauri-apps/plugin-http": "2.0.0-alpha.4",
+ "@tauri-apps/plugin-nfc": "1.0.0",
"@tauri-apps/plugin-notification": "2.0.0-alpha.4",
"@tauri-apps/plugin-os": "2.0.0-alpha.5",
"@tauri-apps/plugin-process": "2.0.0-alpha.4",
diff --git a/examples/api/src-tauri/Cargo.toml b/examples/api/src-tauri/Cargo.toml
index c7448e47..5ab61ed6 100644
--- a/examples/api/src-tauri/Cargo.toml
+++ b/examples/api/src-tauri/Cargo.toml
@@ -47,6 +47,7 @@ tauri-plugin-updater = { path = "../../../plugins/updater", version = "2.0.0-alp
[target."cfg(any(target_os = \"android\", target_os = \"ios\"))".dependencies]
tauri-plugin-barcode-scanner = { path = "../../../plugins/barcode-scanner/", version = "2.0.0-alpha.3" }
+tauri-plugin-nfc = { path = "../../../plugins/nfc", version = "1.0.0" }
[target."cfg(target_os = \"windows\")".dependencies]
window-shadows = "0.2"
diff --git a/examples/api/src-tauri/gen/android/.idea/.gitignore b/examples/api/src-tauri/gen/android/.idea/.gitignore
new file mode 100644
index 00000000..26d33521
--- /dev/null
+++ b/examples/api/src-tauri/gen/android/.idea/.gitignore
@@ -0,0 +1,3 @@
+# Default ignored files
+/shelf/
+/workspace.xml
diff --git a/examples/api/src-tauri/gen/android/.idea/compiler.xml b/examples/api/src-tauri/gen/android/.idea/compiler.xml
new file mode 100644
index 00000000..b589d56e
--- /dev/null
+++ b/examples/api/src-tauri/gen/android/.idea/compiler.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/examples/api/src-tauri/gen/android/.idea/gradle.xml b/examples/api/src-tauri/gen/android/.idea/gradle.xml
new file mode 100644
index 00000000..b9c0e8d6
--- /dev/null
+++ b/examples/api/src-tauri/gen/android/.idea/gradle.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/examples/api/src-tauri/gen/android/.idea/jarRepositories.xml b/examples/api/src-tauri/gen/android/.idea/jarRepositories.xml
new file mode 100644
index 00000000..d2ce72d1
--- /dev/null
+++ b/examples/api/src-tauri/gen/android/.idea/jarRepositories.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/examples/api/src-tauri/gen/android/.idea/kotlinc.xml b/examples/api/src-tauri/gen/android/.idea/kotlinc.xml
new file mode 100644
index 00000000..0fc31131
--- /dev/null
+++ b/examples/api/src-tauri/gen/android/.idea/kotlinc.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/examples/api/src-tauri/gen/android/.idea/misc.xml b/examples/api/src-tauri/gen/android/.idea/misc.xml
new file mode 100644
index 00000000..8978d23d
--- /dev/null
+++ b/examples/api/src-tauri/gen/android/.idea/misc.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/examples/api/src-tauri/gen/android/.idea/vcs.xml b/examples/api/src-tauri/gen/android/.idea/vcs.xml
new file mode 100644
index 00000000..bc599707
--- /dev/null
+++ b/examples/api/src-tauri/gen/android/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/examples/api/src-tauri/gen/android/app/src/main/AndroidManifest.xml b/examples/api/src-tauri/gen/android/app/src/main/AndroidManifest.xml
index c9ef9e7f..4679a745 100644
--- a/examples/api/src-tauri/gen/android/app/src/main/AndroidManifest.xml
+++ b/examples/api/src-tauri/gen/android/app/src/main/AndroidManifest.xml
@@ -17,6 +17,26 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ version = "1.3">
+ buildImplicitDependencies = "YES">
@@ -27,21 +26,16 @@
buildConfiguration = "debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
- shouldUseLaunchSchemeArgsEnv = "NO"
- onlyGenerateCoverageForSpecifiedTargets = "NO">
+ shouldUseLaunchSchemeArgsEnv = "NO">
-
-
-
-
+
+
-
-
-
-
+ NFCReaderUsageDescription
+ NFC Test
NSCameraUsageDescription
Request camera access for barcode scanner
CFBundleDevelopmentRegion
diff --git a/examples/api/src-tauri/gen/apple/api_iOS/api_iOS.entitlements b/examples/api/src-tauri/gen/apple/api_iOS/api_iOS.entitlements
index 0c67376e..9db395a2 100644
--- a/examples/api/src-tauri/gen/apple/api_iOS/api_iOS.entitlements
+++ b/examples/api/src-tauri/gen/apple/api_iOS/api_iOS.entitlements
@@ -1,5 +1,11 @@
-
+
+ com.apple.developer.nfc.readersession.formats
+
+ TAG
+ NDEF
+
+
diff --git a/examples/api/src-tauri/src/lib.rs b/examples/api/src-tauri/src/lib.rs
index ae23d314..8471daba 100644
--- a/examples/api/src-tauri/src/lib.rs
+++ b/examples/api/src-tauri/src/lib.rs
@@ -47,6 +47,7 @@ pub fn run() {
#[cfg(mobile)]
{
app.handle().plugin(tauri_plugin_barcode_scanner::init())?;
+ app.handle().plugin(tauri_plugin_nfc::init())?;
}
let mut window_builder = WindowBuilder::new(app, "main", WindowUrl::default());
diff --git a/examples/api/src/App.svelte b/examples/api/src/App.svelte
index 040d1939..f5ab054e 100644
--- a/examples/api/src/App.svelte
+++ b/examples/api/src/App.svelte
@@ -20,6 +20,7 @@
import { onMount } from "svelte";
import { ask } from "@tauri-apps/plugin-dialog";
+ import Nfc from "./views/Nfc.svelte";
const appWindow = getCurrent();
@@ -107,6 +108,11 @@
component: Scanner,
icon: "i-ph-scan",
},
+ isMobile && {
+ label: "NFC",
+ component: Nfc,
+ icon: "i-ph-nfc",
+ },
];
let selected = views[0];
@@ -221,7 +227,7 @@
let isWindows;
onMount(async () => {
- isWindows = (await os.platform()) === "win32";
+ isWindows = (await os.platform()) === "windows";
});
// mobile
diff --git a/examples/api/src/views/Nfc.svelte b/examples/api/src/views/Nfc.svelte
new file mode 100644
index 00000000..4d7ea2c3
--- /dev/null
+++ b/examples/api/src/views/Nfc.svelte
@@ -0,0 +1,126 @@
+
+
+
+
+
+
+ Write data
+
+
+
+ TAG
+ NDEF
+
+
+
+ {#if isAndroid}
+
+ {/if}
+
+
+
+
Scan
+
diff --git a/plugins/deep-link/examples/app/package.json b/plugins/deep-link/examples/app/package.json
index b9866e22..5bded619 100644
--- a/plugins/deep-link/examples/app/package.json
+++ b/plugins/deep-link/examples/app/package.json
@@ -17,6 +17,6 @@
"@tauri-apps/cli": "2.0.0-alpha.18",
"internal-ip": "^8.0.0",
"typescript": "^5.2.2",
- "vite": "^4.5.0"
+ "vite": "^5.0.6"
}
}
diff --git a/plugins/nfc/.gitignore b/plugins/nfc/.gitignore
new file mode 100644
index 00000000..1b0b469d
--- /dev/null
+++ b/plugins/nfc/.gitignore
@@ -0,0 +1 @@
+/.tauri
diff --git a/plugins/nfc/Cargo.toml b/plugins/nfc/Cargo.toml
new file mode 100644
index 00000000..88808be7
--- /dev/null
+++ b/plugins/nfc/Cargo.toml
@@ -0,0 +1,22 @@
+[package]
+name = "tauri-plugin-nfc"
+version = "1.0.0"
+edition = { workspace = true }
+authors = { workspace = true }
+license = { workspace = true }
+links = "tauri-plugin-nfc"
+
+[package.metadata.docs.rs]
+rustc-args = [ "--cfg", "docsrs" ]
+rustdoc-args = [ "--cfg", "docsrs" ]
+
+[build-dependencies]
+tauri-build = { workspace = true }
+
+[dependencies]
+serde = { workspace = true }
+serde_json = { workspace = true }
+tauri = { workspace = true }
+log = { workspace = true }
+thiserror = { workspace = true }
+serde_repr = "0.1"
diff --git a/plugins/nfc/LICENSE.spdx b/plugins/nfc/LICENSE.spdx
new file mode 100644
index 00000000..cdd0df5a
--- /dev/null
+++ b/plugins/nfc/LICENSE.spdx
@@ -0,0 +1,20 @@
+SPDXVersion: SPDX-2.1
+DataLicense: CC0-1.0
+PackageName: tauri
+DataFormat: SPDXRef-1
+PackageSupplier: Organization: The Tauri Programme in the Commons Conservancy
+PackageHomePage: https://tauri.app
+PackageLicenseDeclared: Apache-2.0
+PackageLicenseDeclared: MIT
+PackageCopyrightText: 2019-2022, The Tauri Programme in the Commons Conservancy
+PackageSummary: Tauri is a rust project that enables developers to make secure
+and small desktop applications using a web frontend.
+
+PackageComment: The package includes the following libraries; see
+Relationship information.
+
+Created: 2019-05-20T09:00:00Z
+PackageDownloadLocation: git://github.com/tauri-apps/tauri
+PackageDownloadLocation: git+https://github.com/tauri-apps/tauri.git
+PackageDownloadLocation: git+ssh://github.com/tauri-apps/tauri.git
+Creator: Person: Daniel Thompson-Yvetot
\ No newline at end of file
diff --git a/plugins/nfc/LICENSE_APACHE-2.0 b/plugins/nfc/LICENSE_APACHE-2.0
new file mode 100644
index 00000000..4947287f
--- /dev/null
+++ b/plugins/nfc/LICENSE_APACHE-2.0
@@ -0,0 +1,177 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
\ No newline at end of file
diff --git a/plugins/nfc/LICENSE_MIT b/plugins/nfc/LICENSE_MIT
new file mode 100644
index 00000000..4d754725
--- /dev/null
+++ b/plugins/nfc/LICENSE_MIT
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2017 - Present Tauri Apps Contributors
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
\ No newline at end of file
diff --git a/plugins/nfc/README.md b/plugins/nfc/README.md
new file mode 100644
index 00000000..ded44056
--- /dev/null
+++ b/plugins/nfc/README.md
@@ -0,0 +1,113 @@
+
+
+
+
+## Install
+
+_This plugin requires a Rust version of at least **1.65**_
+
+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)
+2. Pull sources directly from Github using git tags / revision hashes (most secure)
+3. Git submodule install this repo in your tauri project and then use file protocol to ingest the source (most secure, but inconvenient to use)
+
+Install the Core plugin by adding the following to your `Cargo.toml` file:
+
+`src-tauri/Cargo.toml`
+
+```toml
+[dependencies]
+tauri-plugin-nfc = "2.0.0-alpha"
+# alternatively with Git:
+tauri-plugin-nfc = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v2" }
+```
+
+You can install the JavaScript Guest bindings using your preferred JavaScript package manager:
+
+> Note: Since most JavaScript package managers are unable to install packages from git monorepos we provide read-only mirrors of each plugin. This makes installation option 2 more ergonomic to use.
+
+
+
+```sh
+pnpm add @tauri-apps/plugin-nfc
+# or
+npm add @tauri-apps/plugin-nfc
+# or
+yarn add @tauri-apps/plugin-nfc
+
+# alternatively with Git:
+pnpm add https://github.com/tauri-apps/tauri-plugin-nfc#v2
+# or
+npm add https://github.com/tauri-apps/tauri-plugin-nfc#v2
+# or
+yarn add https://github.com/tauri-apps/tauri-plugin-nfc#v2
+```
+
+## Usage
+
+First you need to register the core plugin with Tauri:
+
+`src-tauri/src/main.rs`
+
+```rust
+fn main() {
+ tauri::Builder::default()
+ .plugin(tauri_plugin_nfc::init())
+ .run(tauri::generate_context!())
+ .expect("error while running tauri application");
+}
+```
+
+Afterwards all the plugin's APIs are available through the JavaScript guest bindings:
+
+```javascript
+import { scan, textRecord, write } from "@tauri-apps/plugin-nfc";
+await scan({ type: "tag", keepSessionAlive: true });
+await write([textRecord("Tauri is awesome!")]);
+```
+
+## Contributing
+
+PRs accepted. Please make sure to read the Contributing Guide before making a pull request.
+
+## Contributed By
+
+
+
+## Partners
+
+
+
+
+
+
+
+
+
+
+
+
+
+For the complete list of sponsors please visit our [website](https://tauri.app#sponsors) and [Open Collective](https://opencollective.com/tauri).
+
+## License
+
+Code: (c) 2015 - Present - The Tauri Programme within The Commons Conservancy.
+
+MIT or MIT/Apache 2.0 where applicable.
diff --git a/plugins/nfc/android/.gitignore b/plugins/nfc/android/.gitignore
new file mode 100644
index 00000000..c0f21ec2
--- /dev/null
+++ b/plugins/nfc/android/.gitignore
@@ -0,0 +1,2 @@
+/build
+/.tauri
diff --git a/plugins/nfc/android/build.gradle.kts b/plugins/nfc/android/build.gradle.kts
new file mode 100644
index 00000000..66eb414f
--- /dev/null
+++ b/plugins/nfc/android/build.gradle.kts
@@ -0,0 +1,46 @@
+plugins {
+ id("com.android.library")
+ id("org.jetbrains.kotlin.android")
+}
+
+android {
+ namespace = "app.tauri.nfc"
+ compileSdk = 33
+
+ defaultConfig {
+ minSdk = 24
+ targetSdk = 33
+
+ testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
+ consumerProguardFiles("consumer-rules.pro")
+ }
+
+ buildTypes {
+ release {
+ isMinifyEnabled = false
+ proguardFiles(
+ getDefaultProguardFile("proguard-android-optimize.txt"),
+ "proguard-rules.pro"
+ )
+ }
+ }
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_1_8
+ targetCompatibility = JavaVersion.VERSION_1_8
+ }
+ kotlinOptions {
+ jvmTarget = "1.8"
+ }
+}
+
+dependencies {
+
+ implementation("androidx.core:core-ktx:1.9.0")
+ implementation("androidx.appcompat:appcompat:1.6.0")
+ implementation("com.google.android.material:material:1.7.0")
+ implementation("com.fasterxml.jackson.core:jackson-databind:2.15.3")
+ testImplementation("junit:junit:4.13.2")
+ androidTestImplementation("androidx.test.ext:junit:1.1.5")
+ androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
+ implementation(project(":tauri-android"))
+}
diff --git a/plugins/nfc/android/proguard-rules.pro b/plugins/nfc/android/proguard-rules.pro
new file mode 100644
index 00000000..481bb434
--- /dev/null
+++ b/plugins/nfc/android/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/plugins/nfc/android/settings.gradle b/plugins/nfc/android/settings.gradle
new file mode 100644
index 00000000..14a752e4
--- /dev/null
+++ b/plugins/nfc/android/settings.gradle
@@ -0,0 +1,2 @@
+include ':tauri-android'
+project(':tauri-android').projectDir = new File('./.tauri/tauri-api')
diff --git a/plugins/nfc/android/src/androidTest/java/ExampleInstrumentedTest.kt b/plugins/nfc/android/src/androidTest/java/ExampleInstrumentedTest.kt
new file mode 100644
index 00000000..f0f7b66e
--- /dev/null
+++ b/plugins/nfc/android/src/androidTest/java/ExampleInstrumentedTest.kt
@@ -0,0 +1,28 @@
+// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
+// SPDX-License-Identifier: Apache-2.0
+// SPDX-License-Identifier: MIT
+
+package app.tauri.nfc
+
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.ext.junit.runners.AndroidJUnit4
+
+import org.junit.Test
+import org.junit.runner.RunWith
+
+import org.junit.Assert.*
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+@RunWith(AndroidJUnit4::class)
+class ExampleInstrumentedTest {
+ @Test
+ fun useAppContext() {
+ // Context of the app under test.
+ val appContext = InstrumentationRegistry.getInstrumentation().targetContext
+ assertEquals("app.tauri.nfc", appContext.packageName)
+ }
+}
diff --git a/plugins/nfc/android/src/main/AndroidManifest.xml b/plugins/nfc/android/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..a2c208e2
--- /dev/null
+++ b/plugins/nfc/android/src/main/AndroidManifest.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/plugins/nfc/android/src/main/java/NfcPlugin.kt b/plugins/nfc/android/src/main/java/NfcPlugin.kt
new file mode 100644
index 00000000..5aa33732
--- /dev/null
+++ b/plugins/nfc/android/src/main/java/NfcPlugin.kt
@@ -0,0 +1,521 @@
+// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
+// SPDX-License-Identifier: Apache-2.0
+// SPDX-License-Identifier: MIT
+
+package app.tauri.nfc
+
+import android.app.Activity
+import android.app.PendingIntent
+import android.content.Intent
+import android.content.IntentFilter
+import android.nfc.NdefMessage
+import android.nfc.NdefRecord
+import android.nfc.NfcAdapter
+import android.nfc.Tag
+import android.nfc.tech.Ndef
+import android.nfc.tech.NdefFormatable
+import android.os.Build
+import android.os.Parcelable
+import android.os.PatternMatcher
+import android.webkit.WebView
+import app.tauri.Logger
+import app.tauri.annotation.Command
+import app.tauri.annotation.InvokeArg
+import app.tauri.annotation.TauriPlugin
+import app.tauri.plugin.Invoke
+import app.tauri.plugin.JSArray
+import app.tauri.plugin.JSObject
+import app.tauri.plugin.Plugin
+import com.fasterxml.jackson.annotation.JsonValue
+import com.fasterxml.jackson.core.JsonParser
+import com.fasterxml.jackson.databind.DeserializationContext
+import com.fasterxml.jackson.databind.JsonDeserializer
+import com.fasterxml.jackson.databind.JsonNode
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize
+import org.json.JSONArray
+import java.io.IOException
+import kotlin.concurrent.thread
+
+sealed class NfcAction {
+ object Read : NfcAction()
+ data class Write(val message: NdefMessage) : NfcAction()
+}
+
+@InvokeArg
+class UriFilter {
+ var scheme: String? = null
+ var host: String? = null
+ var pathPrefix: String? = null
+}
+
+@InvokeArg
+enum class TechKind(@JsonValue val value: String) {
+ IsoDep("IsoDep"),
+ MifareClassic("MifareClassic"),
+ MifareUltralight("MifareUltralight"),
+ Ndef("Ndef"),
+ NdefFormatable("NdefFormatable"),
+ NfcA("NfcA"),
+ NfcB("NfcB"),
+ NfcBarcode("NfcBarcode"),
+ NfcF("NfcF"),
+ NfcV("NfcV");
+
+ fun className(): String {
+ return when (this) {
+ IsoDep -> {
+ android.nfc.tech.IsoDep::class.java.name
+ }
+ MifareClassic -> {
+ android.nfc.tech.MifareClassic::class.java.name
+ }
+ MifareUltralight -> {
+ android.nfc.tech.MifareUltralight::class.java.name
+ }
+ Ndef -> {
+ android.nfc.tech.Ndef::class.java.name
+ }
+ NdefFormatable -> {
+ android.nfc.tech.NdefFormatable::class.java.name
+ }
+ NfcA -> {
+ android.nfc.tech.NfcA::class.java.name
+ }
+ NfcB -> {
+ android.nfc.tech.NfcB::class.java.name
+ }
+ NfcBarcode -> {
+ android.nfc.tech.NfcBarcode::class.java.name
+ }
+ NfcF -> {
+ android.nfc.tech.NfcF::class.java.name
+ }
+ NfcV -> {
+ android.nfc.tech.NfcV::class.java.name
+ }
+ }
+ }
+}
+
+private fun addDataFilters(intentFilter: IntentFilter, uri: UriFilter?, mimeType: String?) {
+ uri?.let { it -> {
+ it.scheme?.let {
+ intentFilter.addDataScheme(it)
+ }
+ it.host?.let {
+ intentFilter.addDataAuthority(it, null)
+ }
+ it.pathPrefix?.let {
+ intentFilter.addDataPath(it, PatternMatcher.PATTERN_PREFIX)
+ }
+ }}
+ mimeType?.let {
+ intentFilter.addDataType(it)
+ }
+}
+
+@InvokeArg
+@JsonDeserialize(using = ScanKindDeserializer::class)
+sealed class ScanKind {
+ @JsonDeserialize
+ class Tag: ScanKind() {
+ var mimeType: String? = null
+ var uri: UriFilter? = null
+ }
+ @JsonDeserialize
+ class Ndef: ScanKind() {
+ var mimeType: String? = null
+ var uri: UriFilter? = null
+ var techLists: Array>? = null
+ }
+
+ fun filters(): Array? {
+ return when (this) {
+ is Tag -> {
+ val intentFilter = IntentFilter(NfcAdapter.ACTION_TAG_DISCOVERED)
+ addDataFilters(intentFilter, uri, mimeType)
+ arrayOf(intentFilter)
+ }
+ is Ndef -> {
+ val intentFilter = IntentFilter(if (techLists == null) NfcAdapter.ACTION_NDEF_DISCOVERED else NfcAdapter.ACTION_TECH_DISCOVERED)
+ addDataFilters(intentFilter, uri, mimeType)
+ arrayOf(intentFilter)
+ }
+ else -> null
+ }
+ }
+
+ fun techLists(): Array>? {
+ return when (this) {
+ is Tag -> null
+ is Ndef -> {
+ techLists?.let {
+ val techs = mutableListOf>()
+ for (techList in it) {
+ val list = mutableListOf()
+ for (tech in techList) {
+ list.add(tech.className())
+ }
+ techs.add(list.toTypedArray())
+ }
+ techs.toTypedArray()
+ } ?: run {
+ null
+ }
+ }
+ else -> null
+ }
+ }
+}
+
+internal class ScanKindDeserializer: JsonDeserializer() {
+ override fun deserialize(
+ jsonParser: JsonParser,
+ deserializationContext: DeserializationContext
+ ): ScanKind {
+ val node: JsonNode = jsonParser.codec.readTree(jsonParser)
+ node.get("tag")?.let {
+ return jsonParser.codec.treeToValue(it, ScanKind.Tag::class.java)
+ } ?: node.get("ndef")?.let {
+ return jsonParser.codec.treeToValue(it, ScanKind.Ndef::class.java)
+ } ?: run {
+ throw Error("unknown scan kind $node")
+ }
+ }
+}
+
+@InvokeArg
+class ScanOptions {
+ lateinit var kind: ScanKind
+ var keepSessionAlive: Boolean = false
+}
+
+@InvokeArg
+class NDEFRecordData {
+ var format: Short = 0
+ var kind: ByteArray = ByteArray(0)
+ var id: ByteArray = ByteArray(0)
+ var payload: ByteArray = ByteArray(0)
+}
+
+@InvokeArg
+class WriteOptions {
+ var kind: ScanKind? = null
+ lateinit var records: Array
+}
+
+class Session(
+ val action: NfcAction,
+ val invoke: Invoke,
+ val keepAlive: Boolean,
+ var tag: Tag? = null,
+ val filters: Array? = null,
+ val techLists: Array>? = null
+)
+
+@TauriPlugin
+class NfcPlugin(private val activity: Activity) : Plugin(activity) {
+ private lateinit var webView: WebView
+
+ private var nfcAdapter: NfcAdapter? = null
+ private var session: Session? = null
+
+ override fun load(webView: WebView) {
+ super.load(webView)
+ this.webView = webView
+ this.nfcAdapter = NfcAdapter.getDefaultAdapter(activity.applicationContext)
+ }
+
+ override fun onNewIntent(intent: Intent) {
+ Logger.info("NFC", "onNewIntent")
+ super.onNewIntent(intent)
+
+ val extraTag = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ intent.getParcelableExtra(NfcAdapter.EXTRA_TAG, Tag::class.java)
+ } else {
+ @Suppress("DEPRECATION")
+ intent.getParcelableExtra(NfcAdapter.EXTRA_TAG)
+ }
+
+ extraTag?.let { tag ->
+ session?.let {
+ if (it.keepAlive) {
+ it.tag = tag
+ }
+ }
+
+ when (session?.action) {
+ is NfcAction.Read -> readTag(tag, intent)
+ is NfcAction.Write -> thread {
+ if (session?.action is NfcAction.Write) {
+ try {
+ writeTag(tag, (session?.action as NfcAction.Write).message)
+ session?.invoke?.resolve()
+ } catch (e: Exception) {
+ session?.invoke?.reject(e.toString())
+ } finally {
+ if (this.session?.keepAlive != true) {
+ this.session = null
+ disableNFCInForeground()
+ }
+ }
+ }
+ }
+
+ else -> {}
+ }
+ }
+
+ }
+
+ override fun onPause() {
+ disableNFCInForeground()
+ super.onPause()
+ Logger.info("NFC", "onPause")
+ }
+
+ override fun onResume() {
+ super.onResume()
+ Logger.info("NFC", "onResume")
+ session?.let {
+ enableNFCInForeground(it.filters, it.techLists)
+ }
+ }
+
+ private fun isAvailable(): Pair {
+ val available: Boolean
+ var errorReason: String? = null
+
+ if (this.nfcAdapter === null) {
+ available = false
+ errorReason = "Device does not have NFC capabilities"
+ } else if (this.nfcAdapter?.isEnabled == false) {
+ available = false
+ errorReason = "NFC is disabled in device settings"
+ } else {
+ available = true
+ }
+
+ return Pair(available, errorReason)
+ }
+
+ @Command
+ fun isAvailable(invoke: Invoke) {
+ val ret = JSObject()
+ ret.put("available", isAvailable().first)
+ invoke.resolve(ret)
+ }
+
+ @Command
+ fun scan(invoke: Invoke) {
+ val status = isAvailable()
+ if (!status.first) {
+ invoke.reject("NFC unavailable: " + status.second)
+ return
+ }
+
+ val args = invoke.parseArgs(ScanOptions::class.java)
+
+ val filters = args.kind.filters()
+ val techLists = args.kind.techLists()
+ enableNFCInForeground(filters, techLists)
+
+ session = Session(NfcAction.Read, invoke, args.keepSessionAlive, null, filters, techLists)
+ }
+
+ @Command
+ fun write(invoke: Invoke) {
+ val status = isAvailable()
+ if (!status.first) {
+ invoke.reject("NFC unavailable: " + status.second)
+ return
+ }
+
+ val args = invoke.parseArgs(WriteOptions::class.java)
+
+ val ndefRecords: MutableList = ArrayList()
+ for (record in args.records) {
+ ndefRecords.add(NdefRecord(record.format, record.kind, record.id, record.payload))
+ }
+
+ val message = NdefMessage(ndefRecords.toTypedArray())
+
+ session?.let { session ->
+ session.tag?.let {
+ try {
+ writeTag(it, message)
+ invoke.resolve()
+ } catch (e: Exception) {
+ invoke.reject(e.toString())
+ } finally {
+ if (this.session?.keepAlive != true) {
+ this.session = null
+ disableNFCInForeground()
+ }
+ }
+ } ?: run {
+ invoke.reject("connected tag not found, please wait for it to be available and then call write()")
+ }
+ } ?: run {
+ args.kind?.let { kind -> {
+ val filters = kind.filters()
+ val techLists = kind.techLists()
+ enableNFCInForeground(filters, techLists)
+ session = Session(NfcAction.Write(message), invoke, true, null, filters, techLists)
+ Logger.warn("NFC", "Write Mode Enabled")
+ }} ?: run {
+ invoke.reject("Missing `kind` for write")
+ }
+
+ }
+ }
+
+ private fun readTag(tag: Tag, intent: Intent) {
+ try {
+ val rawMessages = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES, Parcelable::class.java)
+ } else {
+ @Suppress("DEPRECATION")
+ intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES)
+ }
+
+ when (intent.action) {
+ NfcAdapter.ACTION_NDEF_DISCOVERED -> {
+ // For some reason this one never triggers.
+ Logger.info("NFC", "new NDEF intent")
+ readTagInner(tag, rawMessages)
+ }
+ NfcAdapter.ACTION_TECH_DISCOVERED -> {
+ // For some reason this always triggers instead of NDEF_DISCOVERED even though we set ndef filters right now
+ Logger.info("NFC", "new TECH intent")
+ // TODO: handle different techs. Don't assume ndef.
+ readTagInner(tag, rawMessages)
+ }
+ NfcAdapter.ACTION_TAG_DISCOVERED -> {
+ // This should never trigger when an app handles NDEF and TECH
+ // TODO: Don't assume ndef.
+ readTagInner(tag, rawMessages)
+ }
+ }
+ } catch (e: Exception) {
+ session?.invoke?.reject("failed to read tag", e)
+ } finally {
+ if (this.session?.keepAlive != true) {
+ this.session = null
+ }
+ // TODO this crashes? disableNFCInForeground()
+ }
+ }
+
+ private fun readTagInner(tag: Tag?, rawMessages: Array?) {
+ val ndefMessage = rawMessages?.get(0) as NdefMessage?
+
+ val records = ndefMessage?.records ?: arrayOf()
+
+ val jsonRecords = Array(records.size) { i -> recordToJson(records[i]) }
+
+ val ret = JSObject()
+ if (tag !== null) {
+ ret.put("id", fromU8Array(tag.id))
+ // TODO There's also ndef.type which returns the ndef spec type which may be interesting to know too?
+ ret.put("kind", JSArray.from(tag.techList))
+ }
+ ret.put("records", JSArray.from(jsonRecords))
+
+ session?.invoke?.resolve(ret)
+ }
+
+ private fun writeTag(tag: Tag, message: NdefMessage) {
+ // This should return tags that are already in ndef format
+ val ndefTag = Ndef.get(tag)
+ if (ndefTag !== null) {
+ // We have to connect first to check maxSize.
+ try {
+ ndefTag.connect()
+ } catch (e: IOException) {
+ throw Exception("Couldn't connect to NFC tag", e)
+ }
+
+ if (ndefTag.maxSize < message.toByteArray().size) {
+ throw Exception("The message is too large for the provided NFC tag")
+ } else if (!ndefTag.isWritable) {
+ throw Exception("NFC tag is read-only")
+ } else {
+ try {
+ ndefTag.writeNdefMessage(message)
+ } catch (e: Exception) {
+ throw Exception("Couldn't write message to NFC tag", e)
+ }
+ }
+
+ try {
+ ndefTag.close()
+ } catch (e: IOException) {
+ Logger.error("failed to close tag", e)
+ }
+
+ return
+ }
+
+ // This should cover tags that are not yet in ndef format but can be converted
+ val ndefFormatableTag = NdefFormatable.get(tag)
+ if (ndefFormatableTag !== null) {
+ try {
+ ndefFormatableTag.connect()
+ ndefFormatableTag.format(message)
+ } catch (e: Exception) {
+ throw Exception("Couldn't format tag as Ndef", e)
+ }
+
+ try {
+ ndefFormatableTag.close()
+ } catch (e: IOException) {
+ Logger.error("failed to close tag", e)
+ }
+
+ return
+ }
+
+ // if we get to this line, the tag was neither Ndef nor NdefFormatable compatible
+ throw Exception("Tag doesn't support Ndef format")
+ }
+
+ // TODO: Use ReaderMode instead of ForegroundDispatch
+ private fun enableNFCInForeground(filters: Array?, techLists: Array>?) {
+ val flag =
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE else PendingIntent.FLAG_UPDATE_CURRENT
+ val pendingIntent = PendingIntent.getActivity(
+ activity, 0,
+ Intent(
+ activity,
+ activity.javaClass
+ ).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP),
+ flag
+ )
+
+ nfcAdapter?.enableForegroundDispatch(activity, pendingIntent, filters, techLists)
+ }
+
+ private fun disableNFCInForeground() {
+ activity.runOnUiThread {
+ nfcAdapter?.disableForegroundDispatch(activity)
+ }
+ }
+}
+
+private fun fromU8Array(byteArray: ByteArray): JSONArray {
+ val json = JSONArray()
+ for (byte in byteArray) {
+ json.put(byte)
+ }
+ return json
+}
+
+private fun recordToJson(record: NdefRecord): JSObject {
+ val json = JSObject()
+ json.put("tnf", record.tnf)
+ json.put("kind", fromU8Array(record.type))
+ json.put("id", fromU8Array(record.id))
+ json.put("payload", fromU8Array(record.payload))
+ return json
+}
\ No newline at end of file
diff --git a/plugins/nfc/android/src/main/res/xml/nfc_tech_filter.xml b/plugins/nfc/android/src/main/res/xml/nfc_tech_filter.xml
new file mode 100644
index 00000000..994905a6
--- /dev/null
+++ b/plugins/nfc/android/src/main/res/xml/nfc_tech_filter.xml
@@ -0,0 +1,13 @@
+
+
+ android.nfc.tech.IsoDep
+ android.nfc.tech.NfcA
+ android.nfc.tech.NfcB
+ android.nfc.tech.NfcF
+ android.nfc.tech.NfcV
+ android.nfc.tech.Ndef
+ android.nfc.tech.NdefFormatable
+ android.nfc.tech.MifareClassic
+ android.nfc.tech.MifareUltralight
+
+
diff --git a/plugins/nfc/android/src/test/java/ExampleUnitTest.kt b/plugins/nfc/android/src/test/java/ExampleUnitTest.kt
new file mode 100644
index 00000000..2af426f8
--- /dev/null
+++ b/plugins/nfc/android/src/test/java/ExampleUnitTest.kt
@@ -0,0 +1,21 @@
+// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
+// SPDX-License-Identifier: Apache-2.0
+// SPDX-License-Identifier: MIT
+
+package app.tauri.nfc
+
+import org.junit.Test
+
+import org.junit.Assert.*
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+class ExampleUnitTest {
+ @Test
+ fun addition_isCorrect() {
+ assertEquals(4, 2 + 2)
+ }
+}
diff --git a/plugins/nfc/build.rs b/plugins/nfc/build.rs
new file mode 100644
index 00000000..2f932ecc
--- /dev/null
+++ b/plugins/nfc/build.rs
@@ -0,0 +1,43 @@
+// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
+// SPDX-License-Identifier: Apache-2.0
+// SPDX-License-Identifier: MIT
+
+use std::process::exit;
+
+fn main() {
+ if let Err(error) = tauri_build::mobile::PluginBuilder::new()
+ .android_path("android")
+ .ios_path("ios")
+ .run()
+ {
+ println!("{error:#}");
+ exit(1);
+ }
+
+ // TODO: triple check if this can reference the plugin's xml as it expects rn
+ // TODO: This has to be configurable if we want to support handling nfc tags when the app is not open.
+ tauri_build::mobile::update_android_manifest(
+ "NFC PLUGIN",
+ "activity",
+ r#"
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ "#
+ .to_string(),
+ )
+ .expect("failed to rewrite AndroidManifest.xml");
+}
diff --git a/plugins/nfc/contributors/crabnebula.svg b/plugins/nfc/contributors/crabnebula.svg
new file mode 100644
index 00000000..a9bb4609
--- /dev/null
+++ b/plugins/nfc/contributors/crabnebula.svg
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/plugins/nfc/contributors/impierce.svg b/plugins/nfc/contributors/impierce.svg
new file mode 100644
index 00000000..9d2a510b
--- /dev/null
+++ b/plugins/nfc/contributors/impierce.svg
@@ -0,0 +1,21 @@
+
+
+
+
\ No newline at end of file
diff --git a/plugins/nfc/guest-js/index.ts b/plugins/nfc/guest-js/index.ts
new file mode 100644
index 00000000..e1861ae4
--- /dev/null
+++ b/plugins/nfc/guest-js/index.ts
@@ -0,0 +1,272 @@
+// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
+// SPDX-License-Identifier: Apache-2.0
+// SPDX-License-Identifier: MIT
+
+import { invoke } from "@tauri-apps/api/primitives";
+
+export const RTD_TEXT = [0x54]; // "T"
+export const RTD_URI = [0x55]; // "U"
+
+export interface UriFilter {
+ scheme?: string;
+ host?: string;
+ pathPrefix?: string;
+}
+
+export enum TechKind {
+ IsoDep,
+ MifareClassic,
+ MifareUltralight,
+ Ndef,
+ NdefFormatable,
+ NfcA,
+ NfcB,
+ NfcBarcode,
+ NfcF,
+ NfcV,
+}
+
+export type ScanKind =
+ | {
+ type: "tag";
+ uri?: UriFilter;
+ mimeType?: string;
+ }
+ | {
+ type: "ndef";
+ uri?: UriFilter;
+ mimeType?: string;
+ /**
+ * Each of the tech-lists is considered independently and the activity is considered a match if
+ * any single tech-list matches the tag that was discovered.
+ * This provides AND and OR semantics for filtering desired techs.
+ *
+ * See for more information.
+ *
+ * Examples
+ *
+ * ```ts
+ * import type { TechKind } from "@tauri-apps/plugin-nfc"
+ *
+ * const techLists = [
+ * // capture anything using NfcF
+ * [TechKind.NfcF],
+ * // capture all MIFARE Classics with NDEF payloads
+ * [TechKind.NfcA, TechKind.MifareClassic, TechKind.Ndef]
+ * ]
+ * ```
+ */
+ techLists?: TechKind[][];
+ };
+
+export interface ScanOptions {
+ keepSessionAlive?: boolean;
+ /** Message displayed in the UI. iOS only. */
+ message?: string;
+ /** Message displayed in the UI when the message has been read. iOS only. */
+ successMessage?: string;
+}
+
+export interface WriteOptions {
+ kind?: ScanKind;
+ /** Message displayed in the UI when reading the tag. iOS only. */
+ message?: string;
+ /** Message displayed in the UI when the tag has been read. iOS only. */
+ successfulReadMessage?: string;
+ /** Message displayed in the UI when the message has been written. iOS only. */
+ successMessage?: string;
+}
+
+export enum NFCTypeNameFormat {
+ Empty = 0,
+ NfcWellKnown = 1,
+ Media = 2,
+ AbsoluteURI = 3,
+ NfcExternal = 4,
+ Unknown = 5,
+ Unchanged = 6,
+}
+
+export interface TagRecord {
+ tnf: NFCTypeNameFormat;
+ kind: number[];
+ id: number[];
+ payload: number[];
+}
+
+export interface Tag {
+ id: number[];
+ kind: string[];
+ records: TagRecord[];
+}
+
+export interface NFCRecord {
+ format: NFCTypeNameFormat;
+ kind: number[];
+ id: number[];
+ payload: number[];
+}
+
+export function record(
+ format: NFCTypeNameFormat,
+ kind: string | number[],
+ id: string | number[],
+ payload: string | number[],
+): NFCRecord {
+ return {
+ format,
+ kind:
+ typeof kind === "string"
+ ? Array.from(new TextEncoder().encode(kind))
+ : kind,
+ id: typeof id === "string" ? Array.from(new TextEncoder().encode(id)) : id,
+ payload:
+ typeof payload === "string"
+ ? Array.from(new TextEncoder().encode(payload))
+ : payload,
+ };
+}
+
+export function textRecord(
+ text: string,
+ id?: string | number[],
+ language: string = "en",
+): NFCRecord {
+ const payload = Array.from(new TextEncoder().encode(language + text));
+ payload.unshift(language.length);
+ return record(NFCTypeNameFormat.NfcWellKnown, RTD_TEXT, id || [], payload);
+}
+
+const protocols = [
+ "",
+ "http://www.",
+ "https://www.",
+ "http://",
+ "https://",
+ "tel:",
+ "mailto:",
+ "ftp://anonymous:anonymous@",
+ "ftp://ftp.",
+ "ftps://",
+ "sftp://",
+ "smb://",
+ "nfs://",
+ "ftp://",
+ "dav://",
+ "news:",
+ "telnet://",
+ "imap:",
+ "rtsp://",
+ "urn:",
+ "pop:",
+ "sip:",
+ "sips:",
+ "tftp:",
+ "btspp://",
+ "btl2cap://",
+ "btgoep://",
+ "tcpobex://",
+ "irdaobex://",
+ "file://",
+ "urn:epc:id:",
+ "urn:epc:tag:",
+ "urn:epc:pat:",
+ "urn:epc:raw:",
+ "urn:epc:",
+ "urn:nfc:",
+];
+
+function encodeURI(uri: string): number[] {
+ let prefix = "";
+
+ protocols.slice(1).forEach(function (protocol) {
+ if ((!prefix || prefix === "urn:") && uri.indexOf(protocol) === 0) {
+ prefix = protocol;
+ }
+ });
+
+ if (!prefix) {
+ prefix = "";
+ }
+
+ const encoded = Array.from(
+ new TextEncoder().encode(uri.slice(prefix.length)),
+ );
+ const protocolCode = protocols.indexOf(prefix);
+ // prepend protocol code
+ encoded.unshift(protocolCode);
+
+ return encoded;
+}
+
+export function uriRecord(uri: string, id?: string | number[]): NFCRecord {
+ return record(
+ NFCTypeNameFormat.NfcWellKnown,
+ RTD_URI,
+ id || [],
+ encodeURI(uri),
+ );
+}
+
+function mapScanKind(kind: ScanKind): Record {
+ const { type: scanKind, ...kindOptions } = kind;
+ return { [scanKind]: kindOptions };
+}
+
+/**
+ * Scans an NFC tag.
+ *
+ * ```javascript
+ * import { scan } from "@tauri-apps/plugin-nfc";
+ * await scan({ type: "tag" });
+ * ```
+ *
+ * See for more information.
+ *
+ * @param kind
+ * @param options
+ * @returns
+ */
+export async function scan(
+ kind: ScanKind,
+ options?: ScanOptions,
+): Promise {
+ return await invoke("plugin:nfc|scan", {
+ kind: mapScanKind(kind),
+ ...options,
+ });
+}
+
+/**
+ * Write to an NFC tag.
+ *
+ * ```javascript
+ * import { uriRecord, write } from "@tauri-apps/plugin-nfc";
+ * await write([uriRecord("https://tauri.app")], { kind: { type: "ndef" } });
+ * ```
+ *
+ * If you did not previously call {@link scan} with {@link ScanOptions.keepSessionAlive} set to true,
+ * it will first scan the tag then write to it.
+ *
+ * @param records
+ * @param options
+ * @returns
+ */
+export async function write(
+ records: NFCRecord[],
+ options?: WriteOptions,
+): Promise {
+ const { kind, ...opts } = options || {};
+ if (kind) {
+ // @ts-expect-error map the property
+ opts.kind = mapScanKind(kind);
+ }
+ return await invoke("plugin:nfc|write", {
+ records,
+ ...opts,
+ });
+}
+
+export async function isAvailable(): Promise {
+ return await invoke("plugin:nfc|isAvailable");
+}
diff --git a/plugins/nfc/ios/.gitignore b/plugins/nfc/ios/.gitignore
new file mode 100644
index 00000000..5922fdaa
--- /dev/null
+++ b/plugins/nfc/ios/.gitignore
@@ -0,0 +1,10 @@
+.DS_Store
+/.build
+/Packages
+/*.xcodeproj
+xcuserdata/
+DerivedData/
+.swiftpm/config/registries.json
+.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
+.netrc
+Package.resolved
diff --git a/plugins/nfc/ios/Package.swift b/plugins/nfc/ios/Package.swift
new file mode 100644
index 00000000..e8f1f19a
--- /dev/null
+++ b/plugins/nfc/ios/Package.swift
@@ -0,0 +1,33 @@
+// swift-tools-version:5.3
+// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
+// SPDX-License-Identifier: Apache-2.0
+// SPDX-License-Identifier: MIT
+
+import PackageDescription
+
+let package = Package(
+ name: "tauri-plugin-nfc",
+ platforms: [
+ .iOS(.v13)
+ ],
+ products: [
+ // Products define the executables and libraries a package produces, and make them visible to other packages.
+ .library(
+ name: "tauri-plugin-nfc",
+ type: .static,
+ targets: ["tauri-plugin-nfc"])
+ ],
+ dependencies: [
+ .package(name: "Tauri", path: "../.tauri/tauri-api")
+ ],
+ targets: [
+ // Targets are the basic building blocks of a package. A target can define a module or a test suite.
+ // Targets can depend on other targets in this package, and on products in packages this package depends on.
+ .target(
+ name: "tauri-plugin-nfc",
+ dependencies: [
+ .byName(name: "Tauri")
+ ],
+ path: "Sources")
+ ]
+)
diff --git a/plugins/nfc/ios/README.md b/plugins/nfc/ios/README.md
new file mode 100644
index 00000000..88a429b7
--- /dev/null
+++ b/plugins/nfc/ios/README.md
@@ -0,0 +1,3 @@
+# Tauri Plugin Nfc
+
+A description of this package.
diff --git a/plugins/nfc/ios/Sources/NfcPlugin.swift b/plugins/nfc/ios/Sources/NfcPlugin.swift
new file mode 100644
index 00000000..21bb2606
--- /dev/null
+++ b/plugins/nfc/ios/Sources/NfcPlugin.swift
@@ -0,0 +1,521 @@
+// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
+// SPDX-License-Identifier: Apache-2.0
+// SPDX-License-Identifier: MIT
+
+// https://developer.apple.com/documentation/corenfc/building_an_nfc_tag-reader_app
+
+import CoreNFC
+import SwiftRs
+import Tauri
+import UIKit
+import WebKit
+
+enum ScanKind: Decodable {
+ case ndef, tag
+}
+
+struct ScanOptions: Decodable {
+ let kind: ScanKind
+ let keepSessionAlive: Bool?
+ let message: String?
+ let successMessage: String?
+}
+
+struct NDEFRecord: Decodable {
+ let format: UInt8?
+ let kind: [UInt8]?
+ let identifier: [UInt8]?
+ let payload: [UInt8]?
+}
+
+struct WriteOptions: Decodable {
+ let kind: ScanKind?
+ let records: [NDEFRecord]
+ let message: String?
+ let successMessage: String?
+ let successfulReadMessage: String?
+}
+
+enum TagProcessMode {
+ case write(message: NFCNDEFMessage)
+ case read
+}
+
+class Session {
+ let nfcSession: NFCReaderSession?
+ let invoke: Invoke
+ var keepAlive: Bool
+ let tagProcessMode: TagProcessMode
+ var tagStatus: NFCNDEFStatus?
+ var tag: NFCNDEFTag?
+ let successfulReadMessage: String?
+ let successfulWriteAlertMessage: String?
+
+ init(
+ nfcSession: NFCReaderSession?,
+ invoke: Invoke,
+ keepAlive: Bool,
+ tagProcessMode: TagProcessMode,
+ successfulReadMessage: String?,
+ successfulWriteAlertMessage: String?
+ ) {
+ self.nfcSession = nfcSession
+ self.invoke = invoke
+ self.keepAlive = keepAlive
+ self.tagProcessMode = tagProcessMode
+ self.successfulReadMessage = successfulReadMessage
+ self.successfulWriteAlertMessage = successfulWriteAlertMessage
+ }
+}
+
+class NfcStatus {
+ let available: Bool
+ let errorReason: String?
+
+ init(available: Bool, errorReason: String?) {
+ self.available = available
+ self.errorReason = errorReason
+ }
+}
+
+class NfcPlugin: Plugin, NFCTagReaderSessionDelegate, NFCNDEFReaderSessionDelegate {
+ var session: Session?
+ var status: NfcStatus!
+
+ public override func load(webview: WKWebView) {
+ var available = false
+ var errorReason: String?
+
+ let entry = Bundle.main.infoDictionary?["NFCReaderUsageDescription"] as? String
+
+ if entry == nil || entry?.count == 0 {
+ errorReason = "missing NFCReaderUsageDescription configuration on the Info.plist file"
+ } else if !NFCNDEFReaderSession.readingAvailable {
+ errorReason =
+ "NFC tag reading unavailable, make sure the Near-Field Communication capability on Xcode is enabled and the device supports NFC tag reading"
+ } else {
+ available = true
+ }
+
+ if let error = errorReason {
+ Logger.error("\(error)")
+ }
+
+ self.status = NfcStatus(available: available, errorReason: errorReason)
+ }
+
+ func tagReaderSessionDidBecomeActive(
+ _ session: NFCTagReaderSession
+ ) {
+ Logger.info("tagReaderSessionDidBecomeActive")
+ }
+
+ func tagReaderSession(_ session: NFCTagReaderSession, didDetect tags: [NFCTag]) {
+ let tag = tags.first!
+
+ session.connect(
+ to: tag,
+ completionHandler: { [self] (error) in
+ if let error = error {
+ self.closeSession(session, error: "cannot connect to tag: \(error)")
+
+ } else {
+ let ndefTag: NFCNDEFTag
+ switch tag {
+ case let .feliCa(tag):
+ ndefTag = tag as NFCNDEFTag
+ break
+ case let .miFare(tag):
+ ndefTag = tag as NFCNDEFTag
+ break
+ case let .iso15693(tag):
+ ndefTag = tag as NFCNDEFTag
+ break
+ case let .iso7816(tag):
+ ndefTag = tag as NFCNDEFTag
+ break
+ default:
+ return
+ }
+
+ self.processTag(
+ session: session, tag: ndefTag, metadata: tagMetadata(tag),
+ mode: self.session!.tagProcessMode)
+ }
+ }
+ )
+ }
+
+ func tagReaderSession(_ session: NFCTagReaderSession, didInvalidateWithError error: Error) {
+ Logger.error("Tag reader session error \(error)")
+ self.session?.invoke.reject("session invalidated with error: \(error)")
+ }
+
+ func readerSession(_ session: NFCNDEFReaderSession, didDetectNDEFs messages: [NFCNDEFMessage]) {
+ let message = messages.first!
+ // TODO: do we really need this hook?
+ self.session?.invoke.resolve(["records": ndefMessageRecords(message)])
+ }
+
+ func readerSession(_ session: NFCNDEFReaderSession, didDetect tags: [NFCNDEFTag]) {
+ let tag = tags.first!
+
+ session.connect(
+ to: tag,
+ completionHandler: { [self] (error) in
+ if let error = error {
+ self.closeSession(session, error: "cannot connect to tag: \(error)")
+
+ } else {
+ var metadata: JsonObject = [:]
+ if tag.isKind(of: NFCFeliCaTag.self) {
+ metadata["kind"] = ["FeliCa"]
+ metadata["id"] = nil
+ } else if let t = tag as? NFCMiFareTag {
+ metadata["kind"] = ["MiFare"]
+ metadata["id"] = byteArrayFromData(t.identifier)
+ } else if let t = tag as? NFCISO15693Tag {
+ metadata["kind"] = ["ISO15693"]
+ metadata["id"] = byteArrayFromData(t.identifier)
+ } else if let t = tag as? NFCISO7816Tag {
+ metadata["kind"] = ["ISO7816Compatible"]
+ metadata["id"] = byteArrayFromData(t.identifier)
+ }
+
+ self.processTag(
+ session: session, tag: tag, metadata: metadata,
+ mode: self.session!.tagProcessMode)
+ }
+ }
+ )
+
+ }
+
+ func readerSession(_ session: NFCNDEFReaderSession, didInvalidateWithError error: Error) {
+ if (error as NSError).code
+ == NFCReaderError.Code.readerSessionInvalidationErrorFirstNDEFTagRead.rawValue
+ {
+ // not an error because we're using invalidateAfterFirstRead: true
+ Logger.debug("readerSessionInvalidationErrorFirstNDEFTagRead")
+ } else {
+ Logger.error("NDEF reader session error \(error)")
+ self.session?.invoke.reject("session invalidated with error: \(error)")
+ }
+ }
+
+ private func tagMetadata(_ tag: NFCTag) -> JsonObject {
+ var metadata: JsonObject = [:]
+
+ switch tag {
+ case .feliCa:
+ metadata["kind"] = ["FeliCa"]
+ metadata["id"] = []
+ break
+ case let .miFare(tag):
+ metadata["kind"] = ["MiFare"]
+ metadata["id"] = byteArrayFromData(tag.identifier)
+ break
+ case let .iso15693(tag):
+ metadata["kind"] = ["ISO15693"]
+ metadata["id"] = byteArrayFromData(tag.identifier)
+ break
+ case let .iso7816(tag):
+ metadata["kind"] = ["ISO7816Compatible"]
+ metadata["id"] = byteArrayFromData(tag.identifier)
+ break
+ default:
+ metadata["kind"] = ["Unknown"]
+ metadata["id"] = []
+ break
+ }
+
+ return metadata
+ }
+
+ private func closeSession(_ session: NFCReaderSession) {
+ session.invalidate()
+ self.session = nil
+ }
+
+ private func closeSession(_ session: NFCReaderSession, error: String) {
+ session.invalidate(errorMessage: error)
+ self.session = nil
+ }
+
+ private func processTag(
+ session: NFCReaderSession, tag: T, metadata: JsonObject, mode: TagProcessMode
+ ) {
+ tag.queryNDEFStatus(completionHandler: {
+ [self] (status, capacity, error) in
+ if let error = error {
+ self.closeSession(session, error: "cannot connect to tag: \(error)")
+ } else {
+ switch mode {
+ case .write(let message):
+ self.writeNDEFTag(
+ session: session, status: status, tag: tag, message: message,
+ alertMessage: self.session?.successfulWriteAlertMessage)
+ break
+ case .read:
+ if self.session?.keepAlive == true {
+ self.session!.tagStatus = status
+ self.session!.tag = tag
+ }
+ self.readNDEFTag(
+ session: session, status: status, tag: tag, metadata: metadata,
+ alertMessage: self.session?.successfulReadMessage)
+ break
+ }
+ }
+ })
+ }
+
+ private func writeNDEFTag(
+ session: NFCReaderSession, status: NFCNDEFStatus, tag: T, message: NFCNDEFMessage,
+ alertMessage: String?
+ ) {
+ switch status {
+ case .notSupported:
+ self.closeSession(session, error: "Tag is not an NDEF-formatted tag")
+ break
+ case .readOnly:
+ self.closeSession(session, error: "Read only tag")
+ break
+ case .readWrite:
+ if let currentSession = self.session {
+ tag.writeNDEF(
+ message,
+ completionHandler: { (error) in
+ if let error = error {
+ self.closeSession(session, error: "cannot write to tag: \(error)")
+ } else {
+ if let message = alertMessage {
+ session.alertMessage = message
+ }
+ currentSession.invoke.resolve()
+
+ self.closeSession(session)
+
+ }
+ })
+ }
+ break
+ default:
+ return
+ }
+ }
+
+ private func readNDEFTag(
+ session: NFCReaderSession, status: NFCNDEFStatus, tag: T, metadata m: JsonObject,
+ alertMessage: String?
+ ) {
+ var metadata: JsonObject = [:]
+ metadata.merge(m) { (_, new) in new }
+
+ switch status {
+ case .notSupported:
+ self.resolveInvoke(message: nil, metadata: metadata)
+ self.closeSession(session)
+ return
+ case .readOnly:
+ metadata["readOnly"] = true
+ break
+ case .readWrite:
+ metadata["readOnly"] = false
+ break
+ default:
+ break
+ }
+
+ tag.readNDEF(completionHandler: {
+ [self] (message, error) in
+ if let error = error {
+ let code = (error as NSError).code
+ if code != 403 {
+ self.closeSession(session, error: "Failed to read: \(error)")
+ return
+ }
+ }
+
+ if let message = alertMessage {
+ session.alertMessage = message
+ }
+ self.resolveInvoke(message: message, metadata: metadata)
+
+ if self.session?.keepAlive != true {
+ self.closeSession(session)
+ }
+ })
+ }
+
+ private func resolveInvoke(message: NFCNDEFMessage?, metadata: JsonObject) {
+ var data: JsonObject = [:]
+
+ data.merge(metadata) { (_, new) in new }
+
+ if let message = message {
+ data["records"] = ndefMessageRecords(message)
+ } else {
+ data["records"] = []
+ }
+
+ self.session?.invoke.resolve(data)
+ }
+
+ private func ndefMessageRecords(_ message: NFCNDEFMessage) -> [JsonObject] {
+ var records: [JsonObject] = []
+ for record in message.records {
+ var recordJson: JsonObject = [:]
+ recordJson["tnf"] = record.typeNameFormat.rawValue
+ recordJson["kind"] = byteArrayFromData(record.type)
+ recordJson["id"] = byteArrayFromData(record.identifier)
+ recordJson["payload"] = byteArrayFromData(record.payload)
+
+ records.append(recordJson)
+ }
+
+ return records
+ }
+
+ private func byteArrayFromData(_ data: Data) -> [UInt8] {
+ var arr: [UInt8] = []
+ for b in data {
+ arr.append(b)
+ }
+ return arr
+ }
+
+ private func dataFromByteArray(_ array: [UInt8]) -> Data {
+ var data = Data(capacity: array.count)
+
+ data.append(contentsOf: array)
+
+ return data
+ }
+
+ @objc func isAvailable(_ invoke: Invoke) {
+ invoke.resolve([
+ "available": self.status.available
+ ])
+ }
+
+ @objc public func write(_ invoke: Invoke) throws {
+ if !self.status.available {
+ invoke.reject("NFC reading unavailable: \(self.status.errorReason ?? "")")
+ return
+ }
+
+ let args = try invoke.parseArgs(WriteOptions.self)
+
+ var ndefPayloads = [NFCNDEFPayload]()
+
+ for record in args.records {
+ ndefPayloads.append(
+ NFCNDEFPayload(
+ format: NFCTypeNameFormat(rawValue: record.format ?? 0) ?? .unknown,
+ type: dataFromByteArray(record.kind ?? []),
+ identifier: dataFromByteArray(record.identifier ?? []),
+ payload: dataFromByteArray(record.payload ?? [])
+ )
+ )
+ }
+
+ if let session = self.session {
+ if let nfcSession = session.nfcSession, let tagStatus = session.tagStatus,
+ let tag = session.tag
+ {
+ session.keepAlive = false
+ self.writeNDEFTag(
+ session: nfcSession, status: tagStatus, tag: tag,
+ message: NFCNDEFMessage(records: ndefPayloads),
+ alertMessage: args.successMessage
+ )
+ } else {
+ invoke.reject(
+ "connected tag not found, please wait for it to be available and then call write()")
+ }
+ } else {
+ self.startScanSession(
+ invoke: invoke,
+ kind: args.kind ?? .ndef,
+ keepAlive: true,
+ invalidateAfterFirstRead: false,
+ tagProcessMode: .write(
+ message: NFCNDEFMessage(records: ndefPayloads)
+ ),
+ alertMessage: args.message,
+ successfulReadMessage: args.successfulReadMessage,
+ successfulWriteAlertMessage: args.successMessage
+ )
+ }
+ }
+
+ @objc public func scan(_ invoke: Invoke) throws {
+ if !self.status.available {
+ invoke.reject("NFC reading unavailable: \(self.status.errorReason ?? "")")
+ return
+ }
+
+ let args = try invoke.parseArgs(ScanOptions.self)
+
+ self.startScanSession(
+ invoke: invoke,
+ kind: args.kind,
+ keepAlive: args.keepSessionAlive ?? false,
+ invalidateAfterFirstRead: true,
+ tagProcessMode: .read,
+ alertMessage: args.message,
+ successfulReadMessage: args.successMessage,
+ successfulWriteAlertMessage: nil
+ )
+ }
+
+ private func startScanSession(
+ invoke: Invoke,
+ kind: ScanKind,
+ keepAlive: Bool,
+ invalidateAfterFirstRead: Bool,
+ tagProcessMode: TagProcessMode,
+ alertMessage: String?,
+ successfulReadMessage: String?,
+ successfulWriteAlertMessage: String?
+ ) {
+ let nfcSession: NFCReaderSession?
+
+ switch kind {
+ case .tag:
+ nfcSession = NFCTagReaderSession(
+ pollingOption: [.iso14443, .iso15693],
+ delegate: self,
+ queue: DispatchQueue.main
+ )
+ break
+ case .ndef:
+ nfcSession = NFCNDEFReaderSession(
+ delegate: self,
+ queue: DispatchQueue.main,
+ invalidateAfterFirstRead: invalidateAfterFirstRead
+ )
+ break
+ }
+
+ if let message = alertMessage {
+ nfcSession?.alertMessage = message
+ }
+ nfcSession?.begin()
+
+ self.session = Session(
+ nfcSession: nfcSession,
+ invoke: invoke,
+ keepAlive: keepAlive,
+ tagProcessMode: tagProcessMode,
+ successfulReadMessage: successfulReadMessage,
+ successfulWriteAlertMessage: successfulWriteAlertMessage
+ )
+ }
+}
+
+@_cdecl("init_plugin_nfc")
+func initPlugin() -> Plugin {
+ return NfcPlugin()
+}
diff --git a/plugins/nfc/ios/Tests/PluginTests/PluginTests.swift b/plugins/nfc/ios/Tests/PluginTests/PluginTests.swift
new file mode 100644
index 00000000..99992ce4
--- /dev/null
+++ b/plugins/nfc/ios/Tests/PluginTests/PluginTests.swift
@@ -0,0 +1,12 @@
+// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
+// SPDX-License-Identifier: Apache-2.0
+// SPDX-License-Identifier: MIT
+
+import XCTest
+@testable import ExamplePlugin
+
+final class ExamplePluginTests: XCTestCase {
+ func testExample() throws {
+ let plugin = ExamplePlugin()
+ }
+}
diff --git a/plugins/nfc/package.json b/plugins/nfc/package.json
new file mode 100644
index 00000000..4131ed01
--- /dev/null
+++ b/plugins/nfc/package.json
@@ -0,0 +1,32 @@
+{
+ "name": "@tauri-apps/plugin-nfc",
+ "version": "1.0.0",
+ "license": "MIT or APACHE-2.0",
+ "authors": [
+ "Tauri Programme within The Commons Conservancy"
+ ],
+ "type": "module",
+ "types": "./dist-js/index.d.ts",
+ "main": "./dist-js/index.cjs",
+ "module": "./dist-js/index.js",
+ "exports": {
+ "types": "./dist-js/index.d.ts",
+ "import": "./dist-js/index.js",
+ "require": "./dist-js/index.cjs"
+ },
+ "scripts": {
+ "build": "rollup -c"
+ },
+ "files": [
+ "dist-js",
+ "!dist-js/**/*.map",
+ "README.md",
+ "LICENSE"
+ ],
+ "devDependencies": {
+ "tslib": "2.6.0"
+ },
+ "dependencies": {
+ "@tauri-apps/api": "2.0.0-alpha.12"
+ }
+}
diff --git a/plugins/nfc/rollup.config.js b/plugins/nfc/rollup.config.js
new file mode 100644
index 00000000..977dfac8
--- /dev/null
+++ b/plugins/nfc/rollup.config.js
@@ -0,0 +1,7 @@
+// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
+// SPDX-License-Identifier: Apache-2.0
+// SPDX-License-Identifier: MIT
+
+import { createConfig } from "../../shared/rollup.config.js";
+
+export default createConfig();
diff --git a/plugins/nfc/src/api-iife.js b/plugins/nfc/src/api-iife.js
new file mode 100644
index 00000000..80156954
--- /dev/null
+++ b/plugins/nfc/src/api-iife.js
@@ -0,0 +1 @@
+if("__TAURI__"in window){var __TAURI_PLUGIN_NFC__=function(n){"use strict";async function e(n,e={},t){return window.__TAURI_INTERNALS__.invoke(n,e,t)}"function"==typeof SuppressedError&&SuppressedError;const t=[84],r=[85];var o,c;function i(n,e,t,r){return{format:n,kind:"string"==typeof e?Array.from((new TextEncoder).encode(e)):e,id:"string"==typeof t?Array.from((new TextEncoder).encode(t)):t,payload:"string"==typeof r?Array.from((new TextEncoder).encode(r)):r}}n.TechKind=void 0,(o=n.TechKind||(n.TechKind={}))[o.IsoDep=0]="IsoDep",o[o.MifareClassic=1]="MifareClassic",o[o.MifareUltralight=2]="MifareUltralight",o[o.Ndef=3]="Ndef",o[o.NdefFormatable=4]="NdefFormatable",o[o.NfcA=5]="NfcA",o[o.NfcB=6]="NfcB",o[o.NfcBarcode=7]="NfcBarcode",o[o.NfcF=8]="NfcF",o[o.NfcV=9]="NfcV",n.NFCTypeNameFormat=void 0,(c=n.NFCTypeNameFormat||(n.NFCTypeNameFormat={}))[c.Empty=0]="Empty",c[c.NfcWellKnown=1]="NfcWellKnown",c[c.Media=2]="Media",c[c.AbsoluteURI=3]="AbsoluteURI",c[c.NfcExternal=4]="NfcExternal",c[c.Unknown=5]="Unknown",c[c.Unchanged=6]="Unchanged";const a=["","http://www.","https://www.","http://","https://","tel:","mailto:","ftp://anonymous:anonymous@","ftp://ftp.","ftps://","sftp://","smb://","nfs://","ftp://","dav://","news:","telnet://","imap:","rtsp://","urn:","pop:","sip:","sips:","tftp:","btspp://","btl2cap://","btgoep://","tcpobex://","irdaobex://","file://","urn:epc:id:","urn:epc:tag:","urn:epc:pat:","urn:epc:raw:","urn:epc:","urn:nfc:"];function f(n){const{type:e,...t}=n;return{[e]:t}}return n.RTD_TEXT=t,n.RTD_URI=r,n.isAvailable=async function(){return await e("plugin:nfc|isAvailable")},n.record=i,n.scan=async function(n,t){return await e("plugin:nfc|scan",{kind:f(n),...t})},n.textRecord=function(e,r,o="en"){const c=Array.from((new TextEncoder).encode(o+e));return c.unshift(o.length),i(n.NFCTypeNameFormat.NfcWellKnown,t,r||[],c)},n.uriRecord=function(e,t){return i(n.NFCTypeNameFormat.NfcWellKnown,r,t||[],function(n){let e="";a.slice(1).forEach((function(t){e&&"urn:"!==e||0!==n.indexOf(t)||(e=t)})),e||(e="");const t=Array.from((new TextEncoder).encode(n.slice(e.length))),r=a.indexOf(e);return t.unshift(r),t}(e))},n.write=async function(n,t){const{kind:r,...o}=t||{};return r&&(o.kind=f(r)),await e("plugin:nfc|write",{records:n,...o})},n}({});Object.defineProperty(window.__TAURI__,"nfc",{value:__TAURI_PLUGIN_NFC__})}
diff --git a/plugins/nfc/src/error.rs b/plugins/nfc/src/error.rs
new file mode 100644
index 00000000..339e763b
--- /dev/null
+++ b/plugins/nfc/src/error.rs
@@ -0,0 +1,25 @@
+// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
+// SPDX-License-Identifier: Apache-2.0
+// SPDX-License-Identifier: MIT
+
+use serde::{ser::Serializer, Serialize};
+
+pub type Result = std::result::Result;
+
+#[derive(Debug, thiserror::Error)]
+pub enum Error {
+ #[error(transparent)]
+ Io(#[from] std::io::Error),
+ #[cfg(mobile)]
+ #[error(transparent)]
+ PluginInvoke(#[from] tauri::plugin::mobile::PluginInvokeError),
+}
+
+impl Serialize for Error {
+ fn serialize(&self, serializer: S) -> std::result::Result
+ where
+ S: Serializer,
+ {
+ serializer.serialize_str(self.to_string().as_ref())
+ }
+}
diff --git a/plugins/nfc/src/lib.rs b/plugins/nfc/src/lib.rs
new file mode 100644
index 00000000..28d5160d
--- /dev/null
+++ b/plugins/nfc/src/lib.rs
@@ -0,0 +1,84 @@
+// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
+// SPDX-License-Identifier: Apache-2.0
+// SPDX-License-Identifier: MIT
+
+#![cfg(mobile)]
+
+use serde::{Deserialize, Serialize};
+use tauri::{
+ plugin::{Builder, PluginHandle, TauriPlugin},
+ Manager, Runtime,
+};
+
+pub use models::*;
+
+mod error;
+mod models;
+
+pub use error::{Error, Result};
+
+#[cfg(target_os = "android")]
+const PLUGIN_IDENTIFIER: &str = "app.tauri.nfc";
+
+#[cfg(target_os = "ios")]
+tauri::ios_plugin_binding!(init_plugin_nfc);
+
+/// Access to the nfc APIs.
+pub struct Nfc(PluginHandle);
+
+#[derive(Deserialize)]
+struct IsAvailableResponse {
+ available: bool,
+}
+
+#[derive(Serialize)]
+struct WriteRequest {
+ records: Vec,
+}
+
+impl Nfc {
+ pub fn is_available(&self) -> crate::Result {
+ self.0
+ .run_mobile_plugin::("isAvailable", ())
+ .map(|r| r.available)
+ .map_err(Into::into)
+ }
+
+ pub fn scan(&self, payload: ScanRequest) -> crate::Result {
+ self.0
+ .run_mobile_plugin("scan", payload)
+ .map_err(Into::into)
+ }
+
+ pub fn write(&self, records: Vec) -> crate::Result<()> {
+ self.0
+ .run_mobile_plugin("write", WriteRequest { records })
+ .map_err(Into::into)
+ }
+}
+
+/// Extensions to [`tauri::App`], [`tauri::AppHandle`] and [`tauri::Window`] to access the nfc APIs.
+pub trait NfcExt {
+ fn nfc(&self) -> &Nfc;
+}
+
+impl> crate::NfcExt for T {
+ fn nfc(&self) -> &Nfc {
+ self.state::>().inner()
+ }
+}
+
+/// Initializes the plugin.
+pub fn init() -> TauriPlugin {
+ Builder::new("nfc")
+ .js_init_script(include_str!("api-iife.js").to_string())
+ .setup(|app, api| {
+ #[cfg(target_os = "android")]
+ let handle = api.register_android_plugin(PLUGIN_IDENTIFIER, "NfcPlugin")?;
+ #[cfg(target_os = "ios")]
+ let handle = api.register_ios_plugin(init_plugin_nfc)?;
+ app.manage(Nfc(handle));
+ Ok(())
+ })
+ .build()
+}
diff --git a/plugins/nfc/src/mobile.rs b/plugins/nfc/src/mobile.rs
new file mode 100644
index 00000000..cf34d45e
--- /dev/null
+++ b/plugins/nfc/src/mobile.rs
@@ -0,0 +1,11 @@
+// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
+// SPDX-License-Identifier: Apache-2.0
+// SPDX-License-Identifier: MIT
+
+use serde::de::DeserializeOwned;
+use tauri::{
+ plugin::{PluginApi, PluginHandle},
+ AppHandle, Runtime,
+};
+
+use crate::models::*;
diff --git a/plugins/nfc/src/models.rs b/plugins/nfc/src/models.rs
new file mode 100644
index 00000000..eb05cf7a
--- /dev/null
+++ b/plugins/nfc/src/models.rs
@@ -0,0 +1,119 @@
+// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
+// SPDX-License-Identifier: Apache-2.0
+// SPDX-License-Identifier: MIT
+
+use serde::{Deserialize, Serialize, Serializer};
+use std::fmt::Display;
+
+#[derive(Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct ScanRequest {
+ pub kind: ScanKind,
+ pub keep_session_alive: bool,
+}
+
+#[derive(Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct NfcRecord {
+ pub format: NFCTypeNameFormat,
+ pub kind: Vec,
+ pub id: Vec,
+ pub payload: Vec,
+}
+
+#[derive(serde_repr::Deserialize_repr, serde_repr::Serialize_repr)]
+#[repr(u8)]
+pub enum NFCTypeNameFormat {
+ Empty = 0,
+ NfcWellKnown = 1,
+ Media = 2,
+ AbsoluteURI = 3,
+ NfcExternal = 4,
+ Unknown = 5,
+ Unchanged = 6,
+}
+
+#[derive(Deserialize)]
+pub struct NfcTagRecord {
+ pub tnf: NFCTypeNameFormat,
+ pub kind: Vec,
+ pub id: Vec,
+ pub payload: Vec,
+}
+
+#[derive(Deserialize)]
+pub struct NfcTag {
+ pub id: String,
+ pub kind: String,
+ pub records: Vec,
+}
+
+#[derive(Deserialize)]
+pub struct ScanResponse {
+ pub tag: NfcTag,
+}
+
+#[derive(Debug, Default, Serialize)]
+pub struct UriFilter {
+ scheme: Option,
+ host: Option,
+ path_prefix: Option,
+}
+
+#[derive(Debug)]
+pub enum TechKind {
+ IsoDep,
+ MifareClassic,
+ MifareUltralight,
+ Ndef,
+ NdefFormatable,
+ NfcA,
+ NfcB,
+ NfcBarcode,
+ NfcF,
+ NfcV,
+}
+
+impl Display for TechKind {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ write!(
+ f,
+ "{}",
+ match self {
+ Self::IsoDep => "IsoDep",
+ Self::MifareClassic => "MifareClassic",
+ Self::MifareUltralight => "MifareUltralight",
+ Self::Ndef => "Ndef",
+ Self::NdefFormatable => "NdefFormatable",
+ Self::NfcA => "NfcA",
+ Self::NfcB => "NfcB",
+ Self::NfcBarcode => "NfcBarcode",
+ Self::NfcF => "NfcF",
+ Self::NfcV => "NfcV",
+ }
+ )
+ }
+}
+
+impl Serialize for TechKind {
+ fn serialize(&self, serializer: S) -> Result
+ where
+ S: Serializer,
+ {
+ serializer.serialize_str(&self.to_string())
+ }
+}
+
+#[derive(Debug, Serialize)]
+#[serde(rename_all = "camelCase")]
+pub enum ScanKind {
+ Ndef {
+ mime_type: Option,
+ uri: Option,
+ tech_list: Option>>,
+ },
+ Tag {
+ mime_type: Option,
+ uri: Option,
+ },
+}
diff --git a/plugins/nfc/tsconfig.json b/plugins/nfc/tsconfig.json
new file mode 100644
index 00000000..5098169a
--- /dev/null
+++ b/plugins/nfc/tsconfig.json
@@ -0,0 +1,4 @@
+{
+ "extends": "../../tsconfig.base.json",
+ "include": ["guest-js/*.ts"]
+}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index a19c1686..f67cd2c4 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -87,6 +87,9 @@ importers:
'@tauri-apps/plugin-http':
specifier: 2.0.0-alpha.4
version: link:../../plugins/http
+ '@tauri-apps/plugin-nfc':
+ specifier: 1.0.0
+ version: link:../../plugins/nfc
'@tauri-apps/plugin-notification':
specifier: 2.0.0-alpha.4
version: link:../../plugins/notification
@@ -129,7 +132,7 @@ importers:
version: 4.2.8
unocss:
specifier: ^0.58.0
- version: 0.58.0(postcss@8.4.32)(vite@5.0.6)
+ version: 0.58.0(postcss@8.4.32)(rollup@4.6.1)(vite@5.0.6)
vite:
specifier: ^5.0.6
version: 5.0.6
@@ -189,8 +192,8 @@ importers:
specifier: ^5.2.2
version: 5.3.2
vite:
- specifier: ^4.5.0
- version: 4.5.0
+ specifier: ^5.0.6
+ version: 5.0.6
plugins/dialog:
dependencies:
@@ -222,6 +225,16 @@ importers:
specifier: 2.0.0-alpha.12
version: 2.0.0-alpha.12
+ plugins/nfc:
+ dependencies:
+ '@tauri-apps/api':
+ specifier: 2.0.0-alpha.12
+ version: 2.0.0-alpha.12
+ devDependencies:
+ tslib:
+ specifier: 2.6.0
+ version: 2.6.0
+
plugins/notification:
dependencies:
'@tauri-apps/api':
@@ -381,7 +394,7 @@ packages:
'@babel/traverse': 7.23.5
'@babel/types': 7.23.5
convert-source-map: 2.0.0
- debug: 4.3.4
+ debug: 4.3.4(supports-color@8.1.1)
gensync: 1.0.0-beta.2
json5: 2.2.3
semver: 7.5.4
@@ -651,7 +664,7 @@ packages:
'@babel/helper-split-export-declaration': 7.22.6
'@babel/parser': 7.23.5
'@babel/types': 7.23.5
- debug: 4.3.4
+ debug: 4.3.4(supports-color@8.1.1)
globals: 11.12.0
transitivePeerDependencies:
- supports-color
@@ -785,7 +798,7 @@ packages:
peerDependencies:
mocha: ^10.0.0
dependencies:
- effection: 2.0.8
+ effection: 2.0.8(mocha@10.2.0)
mocha: 10.2.0
dev: true
@@ -814,15 +827,6 @@ packages:
'@effection/core': 2.2.3
dev: true
- /@esbuild/android-arm64@0.18.14:
- resolution: {integrity: sha512-rZ2v+Luba5/3D6l8kofWgTnqE+qsC/L5MleKIKFyllHTKHrNBMqeRCnZI1BtRx8B24xMYxeU32iIddRQqMsOsg==}
- engines: {node: '>=12'}
- cpu: [arm64]
- os: [android]
- requiresBuild: true
- dev: true
- optional: true
-
/@esbuild/android-arm64@0.19.8:
resolution: {integrity: sha512-B8JbS61bEunhfx8kasogFENgQfr/dIp+ggYXwTqdbMAgGDhRa3AaPpQMuQU0rNxDLECj6FhDzk1cF9WHMVwrtA==}
engines: {node: '>=12'}
@@ -832,15 +836,6 @@ packages:
dev: true
optional: true
- /@esbuild/android-arm@0.18.14:
- resolution: {integrity: sha512-blODaaL+lngG5bdK/t4qZcQvq2BBqrABmYwqPPcS5VRxrCSGHb9R/rA3fqxh7R18I7WU4KKv+NYkt22FDfalcg==}
- engines: {node: '>=12'}
- cpu: [arm]
- os: [android]
- requiresBuild: true
- dev: true
- optional: true
-
/@esbuild/android-arm@0.19.8:
resolution: {integrity: sha512-31E2lxlGM1KEfivQl8Yf5aYU/mflz9g06H6S15ITUFQueMFtFjESRMoDSkvMo8thYvLBax+VKTPlpnx+sPicOA==}
engines: {node: '>=12'}
@@ -850,15 +845,6 @@ packages:
dev: true
optional: true
- /@esbuild/android-x64@0.18.14:
- resolution: {integrity: sha512-qSwh8y38QKl+1Iqg+YhvCVYlSk3dVLk9N88VO71U4FUjtiSFylMWK3Ugr8GC6eTkkP4Tc83dVppt2n8vIdlSGg==}
- engines: {node: '>=12'}
- cpu: [x64]
- os: [android]
- requiresBuild: true
- dev: true
- optional: true
-
/@esbuild/android-x64@0.19.8:
resolution: {integrity: sha512-rdqqYfRIn4jWOp+lzQttYMa2Xar3OK9Yt2fhOhzFXqg0rVWEfSclJvZq5fZslnz6ypHvVf3CT7qyf0A5pM682A==}
engines: {node: '>=12'}
@@ -868,15 +854,6 @@ packages:
dev: true
optional: true
- /@esbuild/darwin-arm64@0.18.14:
- resolution: {integrity: sha512-9Hl2D2PBeDYZiNbnRKRWuxwHa9v5ssWBBjisXFkVcSP5cZqzZRFBUWEQuqBHO4+PKx4q4wgHoWtfQ1S7rUqJ2Q==}
- engines: {node: '>=12'}
- cpu: [arm64]
- os: [darwin]
- requiresBuild: true
- dev: true
- optional: true
-
/@esbuild/darwin-arm64@0.19.8:
resolution: {integrity: sha512-RQw9DemMbIq35Bprbboyf8SmOr4UXsRVxJ97LgB55VKKeJOOdvsIPy0nFyF2l8U+h4PtBx/1kRf0BelOYCiQcw==}
engines: {node: '>=12'}
@@ -886,15 +863,6 @@ packages:
dev: true
optional: true
- /@esbuild/darwin-x64@0.18.14:
- resolution: {integrity: sha512-ZnI3Dg4ElQ6tlv82qLc/UNHtFsgZSKZ7KjsUNAo1BF1SoYDjkGKHJyCrYyWjFecmXpvvG/KJ9A/oe0H12odPLQ==}
- engines: {node: '>=12'}
- cpu: [x64]
- os: [darwin]
- requiresBuild: true
- dev: true
- optional: true
-
/@esbuild/darwin-x64@0.19.8:
resolution: {integrity: sha512-3sur80OT9YdeZwIVgERAysAbwncom7b4bCI2XKLjMfPymTud7e/oY4y+ci1XVp5TfQp/bppn7xLw1n/oSQY3/Q==}
engines: {node: '>=12'}
@@ -904,15 +872,6 @@ packages:
dev: true
optional: true
- /@esbuild/freebsd-arm64@0.18.14:
- resolution: {integrity: sha512-h3OqR80Da4oQCIa37zl8tU5MwHQ7qgPV0oVScPfKJK21fSRZEhLE4IIVpmcOxfAVmqjU6NDxcxhYaM8aDIGRLw==}
- engines: {node: '>=12'}
- cpu: [arm64]
- os: [freebsd]
- requiresBuild: true
- dev: true
- optional: true
-
/@esbuild/freebsd-arm64@0.19.8:
resolution: {integrity: sha512-WAnPJSDattvS/XtPCTj1tPoTxERjcTpH6HsMr6ujTT+X6rylVe8ggxk8pVxzf5U1wh5sPODpawNicF5ta/9Tmw==}
engines: {node: '>=12'}
@@ -922,15 +881,6 @@ packages:
dev: true
optional: true
- /@esbuild/freebsd-x64@0.18.14:
- resolution: {integrity: sha512-ha4BX+S6CZG4BoH9tOZTrFIYC1DH13UTCRHzFc3GWX74nz3h/N6MPF3tuR3XlsNjMFUazGgm35MPW5tHkn2lzQ==}
- engines: {node: '>=12'}
- cpu: [x64]
- os: [freebsd]
- requiresBuild: true
- dev: true
- optional: true
-
/@esbuild/freebsd-x64@0.19.8:
resolution: {integrity: sha512-ICvZyOplIjmmhjd6mxi+zxSdpPTKFfyPPQMQTK/w+8eNK6WV01AjIztJALDtwNNfFhfZLux0tZLC+U9nSyA5Zg==}
engines: {node: '>=12'}
@@ -940,15 +890,6 @@ packages:
dev: true
optional: true
- /@esbuild/linux-arm64@0.18.14:
- resolution: {integrity: sha512-IXORRe22In7U65NZCzjwAUc03nn8SDIzWCnfzJ6t/8AvGx5zBkcLfknI+0P+hhuftufJBmIXxdSTbzWc8X/V4w==}
- engines: {node: '>=12'}
- cpu: [arm64]
- os: [linux]
- requiresBuild: true
- dev: true
- optional: true
-
/@esbuild/linux-arm64@0.19.8:
resolution: {integrity: sha512-z1zMZivxDLHWnyGOctT9JP70h0beY54xDDDJt4VpTX+iwA77IFsE1vCXWmprajJGa+ZYSqkSbRQ4eyLCpCmiCQ==}
engines: {node: '>=12'}
@@ -958,15 +899,6 @@ packages:
dev: true
optional: true
- /@esbuild/linux-arm@0.18.14:
- resolution: {integrity: sha512-5+7vehI1iqru5WRtJyU2XvTOvTGURw3OZxe3YTdE9muNNIdmKAVmSHpB3Vw2LazJk2ifEdIMt/wTWnVe5V98Kg==}
- engines: {node: '>=12'}
- cpu: [arm]
- os: [linux]
- requiresBuild: true
- dev: true
- optional: true
-
/@esbuild/linux-arm@0.19.8:
resolution: {integrity: sha512-H4vmI5PYqSvosPaTJuEppU9oz1dq2A7Mr2vyg5TF9Ga+3+MGgBdGzcyBP7qK9MrwFQZlvNyJrvz6GuCaj3OukQ==}
engines: {node: '>=12'}
@@ -976,15 +908,6 @@ packages:
dev: true
optional: true
- /@esbuild/linux-ia32@0.18.14:
- resolution: {integrity: sha512-BfHlMa0nibwpjG+VXbOoqJDmFde4UK2gnW351SQ2Zd4t1N3zNdmUEqRkw/srC1Sa1DRBE88Dbwg4JgWCbNz/FQ==}
- engines: {node: '>=12'}
- cpu: [ia32]
- os: [linux]
- requiresBuild: true
- dev: true
- optional: true
-
/@esbuild/linux-ia32@0.19.8:
resolution: {integrity: sha512-1a8suQiFJmZz1khm/rDglOc8lavtzEMRo0v6WhPgxkrjcU0LkHj+TwBrALwoz/OtMExvsqbbMI0ChyelKabSvQ==}
engines: {node: '>=12'}
@@ -994,15 +917,6 @@ packages:
dev: true
optional: true
- /@esbuild/linux-loong64@0.18.14:
- resolution: {integrity: sha512-j2/Ex++DRUWIAaUDprXd3JevzGtZ4/d7VKz+AYDoHZ3HjJzCyYBub9CU1wwIXN+viOP0b4VR3RhGClsvyt/xSw==}
- engines: {node: '>=12'}
- cpu: [loong64]
- os: [linux]
- requiresBuild: true
- dev: true
- optional: true
-
/@esbuild/linux-loong64@0.19.8:
resolution: {integrity: sha512-fHZWS2JJxnXt1uYJsDv9+b60WCc2RlvVAy1F76qOLtXRO+H4mjt3Tr6MJ5l7Q78X8KgCFudnTuiQRBhULUyBKQ==}
engines: {node: '>=12'}
@@ -1012,15 +926,6 @@ packages:
dev: true
optional: true
- /@esbuild/linux-mips64el@0.18.14:
- resolution: {integrity: sha512-qn2+nc+ZCrJmiicoAnJXJJkZWt8Nwswgu1crY7N+PBR8ChBHh89XRxj38UU6Dkthl2yCVO9jWuafZ24muzDC/A==}
- engines: {node: '>=12'}
- cpu: [mips64el]
- os: [linux]
- requiresBuild: true
- dev: true
- optional: true
-
/@esbuild/linux-mips64el@0.19.8:
resolution: {integrity: sha512-Wy/z0EL5qZYLX66dVnEg9riiwls5IYnziwuju2oUiuxVc+/edvqXa04qNtbrs0Ukatg5HEzqT94Zs7J207dN5Q==}
engines: {node: '>=12'}
@@ -1030,15 +935,6 @@ packages:
dev: true
optional: true
- /@esbuild/linux-ppc64@0.18.14:
- resolution: {integrity: sha512-aGzXzd+djqeEC5IRkDKt3kWzvXoXC6K6GyYKxd+wsFJ2VQYnOWE954qV2tvy5/aaNrmgPTb52cSCHFE+Z7Z0yg==}
- engines: {node: '>=12'}
- cpu: [ppc64]
- os: [linux]
- requiresBuild: true
- dev: true
- optional: true
-
/@esbuild/linux-ppc64@0.19.8:
resolution: {integrity: sha512-ETaW6245wK23YIEufhMQ3HSeHO7NgsLx8gygBVldRHKhOlD1oNeNy/P67mIh1zPn2Hr2HLieQrt6tWrVwuqrxg==}
engines: {node: '>=12'}
@@ -1048,15 +944,6 @@ packages:
dev: true
optional: true
- /@esbuild/linux-riscv64@0.18.14:
- resolution: {integrity: sha512-8C6vWbfr0ygbAiMFLS6OPz0BHvApkT2gCboOGV76YrYw+sD/MQJzyITNsjZWDXJwPu9tjrFQOVG7zijRzBCnLw==}
- engines: {node: '>=12'}
- cpu: [riscv64]
- os: [linux]
- requiresBuild: true
- dev: true
- optional: true
-
/@esbuild/linux-riscv64@0.19.8:
resolution: {integrity: sha512-T2DRQk55SgoleTP+DtPlMrxi/5r9AeFgkhkZ/B0ap99zmxtxdOixOMI570VjdRCs9pE4Wdkz7JYrsPvsl7eESg==}
engines: {node: '>=12'}
@@ -1066,15 +953,6 @@ packages:
dev: true
optional: true
- /@esbuild/linux-s390x@0.18.14:
- resolution: {integrity: sha512-G/Lf9iu8sRMM60OVGOh94ZW2nIStksEcITkXdkD09/T6QFD/o+g0+9WVyR/jajIb3A0LvBJ670tBnGe1GgXMgw==}
- engines: {node: '>=12'}
- cpu: [s390x]
- os: [linux]
- requiresBuild: true
- dev: true
- optional: true
-
/@esbuild/linux-s390x@0.19.8:
resolution: {integrity: sha512-NPxbdmmo3Bk7mbNeHmcCd7R7fptJaczPYBaELk6NcXxy7HLNyWwCyDJ/Xx+/YcNH7Im5dHdx9gZ5xIwyliQCbg==}
engines: {node: '>=12'}
@@ -1084,15 +962,6 @@ packages:
dev: true
optional: true
- /@esbuild/linux-x64@0.18.14:
- resolution: {integrity: sha512-TBgStYBQaa3EGhgqIDM+ECnkreb0wkcKqL7H6m+XPcGUoU4dO7dqewfbm0mWEQYH3kzFHrzjOFNpSAVzDZRSJw==}
- engines: {node: '>=12'}
- cpu: [x64]
- os: [linux]
- requiresBuild: true
- dev: true
- optional: true
-
/@esbuild/linux-x64@0.19.8:
resolution: {integrity: sha512-lytMAVOM3b1gPypL2TRmZ5rnXl7+6IIk8uB3eLsV1JwcizuolblXRrc5ShPrO9ls/b+RTp+E6gbsuLWHWi2zGg==}
engines: {node: '>=12'}
@@ -1102,15 +971,6 @@ packages:
dev: true
optional: true
- /@esbuild/netbsd-x64@0.18.14:
- resolution: {integrity: sha512-stvCcjyCQR2lMTroqNhAbvROqRjxPEq0oQ380YdXxA81TaRJEucH/PzJ/qsEtsHgXlWFW6Ryr/X15vxQiyRXVg==}
- engines: {node: '>=12'}
- cpu: [x64]
- os: [netbsd]
- requiresBuild: true
- dev: true
- optional: true
-
/@esbuild/netbsd-x64@0.19.8:
resolution: {integrity: sha512-hvWVo2VsXz/8NVt1UhLzxwAfo5sioj92uo0bCfLibB0xlOmimU/DeAEsQILlBQvkhrGjamP0/el5HU76HAitGw==}
engines: {node: '>=12'}
@@ -1120,15 +980,6 @@ packages:
dev: true
optional: true
- /@esbuild/openbsd-x64@0.18.14:
- resolution: {integrity: sha512-apAOJF14CIsN5ht1PA57PboEMsNV70j3FUdxLmA2liZ20gEQnfTG5QU0FhENo5nwbTqCB2O3WDsXAihfODjHYw==}
- engines: {node: '>=12'}
- cpu: [x64]
- os: [openbsd]
- requiresBuild: true
- dev: true
- optional: true
-
/@esbuild/openbsd-x64@0.19.8:
resolution: {integrity: sha512-/7Y7u77rdvmGTxR83PgaSvSBJCC2L3Kb1M/+dmSIvRvQPXXCuC97QAwMugBNG0yGcbEGfFBH7ojPzAOxfGNkwQ==}
engines: {node: '>=12'}
@@ -1138,15 +989,6 @@ packages:
dev: true
optional: true
- /@esbuild/sunos-x64@0.18.14:
- resolution: {integrity: sha512-fYRaaS8mDgZcGybPn2MQbn1ZNZx+UXFSUoS5Hd2oEnlsyUcr/l3c6RnXf1bLDRKKdLRSabTmyCy7VLQ7VhGdOQ==}
- engines: {node: '>=12'}
- cpu: [x64]
- os: [sunos]
- requiresBuild: true
- dev: true
- optional: true
-
/@esbuild/sunos-x64@0.19.8:
resolution: {integrity: sha512-9Lc4s7Oi98GqFA4HzA/W2JHIYfnXbUYgekUP/Sm4BG9sfLjyv6GKKHKKVs83SMicBF2JwAX6A1PuOLMqpD001w==}
engines: {node: '>=12'}
@@ -1156,15 +998,6 @@ packages:
dev: true
optional: true
- /@esbuild/win32-arm64@0.18.14:
- resolution: {integrity: sha512-1c44RcxKEJPrVj62XdmYhxXaU/V7auELCmnD+Ri+UCt+AGxTvzxl9uauQhrFso8gj6ZV1DaORV0sT9XSHOAk8Q==}
- engines: {node: '>=12'}
- cpu: [arm64]
- os: [win32]
- requiresBuild: true
- dev: true
- optional: true
-
/@esbuild/win32-arm64@0.19.8:
resolution: {integrity: sha512-rq6WzBGjSzihI9deW3fC2Gqiak68+b7qo5/3kmB6Gvbh/NYPA0sJhrnp7wgV4bNwjqM+R2AApXGxMO7ZoGhIJg==}
engines: {node: '>=12'}
@@ -1174,15 +1007,6 @@ packages:
dev: true
optional: true
- /@esbuild/win32-ia32@0.18.14:
- resolution: {integrity: sha512-EXAFttrdAxZkFQmpvcAQ2bywlWUsONp/9c2lcfvPUhu8vXBBenCXpoq9YkUvVP639ld3YGiYx0YUQ6/VQz3Maw==}
- engines: {node: '>=12'}
- cpu: [ia32]
- os: [win32]
- requiresBuild: true
- dev: true
- optional: true
-
/@esbuild/win32-ia32@0.19.8:
resolution: {integrity: sha512-AIAbverbg5jMvJznYiGhrd3sumfwWs8572mIJL5NQjJa06P8KfCPWZQ0NwZbPQnbQi9OWSZhFVSUWjjIrn4hSw==}
engines: {node: '>=12'}
@@ -1192,15 +1016,6 @@ packages:
dev: true
optional: true
- /@esbuild/win32-x64@0.18.14:
- resolution: {integrity: sha512-K0QjGbcskx+gY+qp3v4/940qg8JitpXbdxFhRDA1aYoNaPff88+aEwoq45aqJ+ogpxQxmU0ZTjgnrQD/w8iiUg==}
- engines: {node: '>=12'}
- cpu: [x64]
- os: [win32]
- requiresBuild: true
- dev: true
- optional: true
-
/@esbuild/win32-x64@0.19.8:
resolution: {integrity: sha512-bfZ0cQ1uZs2PqpulNL5j/3w+GDhP36k1K5c38QdQg+Swy51jFZWWeIkteNsufkQxp986wnqRRsb/bHbY1WQ7TA==}
engines: {node: '>=12'}
@@ -1230,7 +1045,7 @@ packages:
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
dependencies:
ajv: 6.12.6
- debug: 4.3.4
+ debug: 4.3.4(supports-color@8.1.1)
espree: 9.6.1
globals: 13.23.0
ignore: 5.3.0
@@ -1257,7 +1072,7 @@ packages:
engines: {node: '>=10.10.0'}
dependencies:
'@humanwhocodes/object-schema': 2.0.1
- debug: 4.3.4
+ debug: 4.3.4(supports-color@8.1.1)
minimatch: 3.1.2
transitivePeerDependencies:
- supports-color
@@ -1298,7 +1113,7 @@ packages:
'@antfu/install-pkg': 0.1.1
'@antfu/utils': 0.7.6
'@iconify/types': 2.0.0
- debug: 4.3.4
+ debug: 4.3.4(supports-color@8.1.1)
kolorist: 1.8.0
local-pkg: 0.4.3
transitivePeerDependencies:
@@ -1414,20 +1229,6 @@ packages:
typescript: 5.3.2
dev: true
- /@rollup/pluginutils@5.1.0:
- resolution: {integrity: sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g==}
- engines: {node: '>=14.0.0'}
- peerDependencies:
- rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0
- peerDependenciesMeta:
- rollup:
- optional: true
- dependencies:
- '@types/estree': 1.0.5
- estree-walker: 2.0.2
- picomatch: 2.3.1
- dev: true
-
/@rollup/pluginutils@5.1.0(rollup@4.6.1):
resolution: {integrity: sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g==}
engines: {node: '>=14.0.0'}
@@ -1585,7 +1386,7 @@ packages:
vite: ^4.0.0
dependencies:
'@sveltejs/vite-plugin-svelte': 2.5.3(svelte@4.2.8)(vite@5.0.6)
- debug: 4.3.4
+ debug: 4.3.4(supports-color@8.1.1)
svelte: 4.2.8
vite: 5.0.6
transitivePeerDependencies:
@@ -1601,7 +1402,7 @@ packages:
vite: ^5.0.0
dependencies:
'@sveltejs/vite-plugin-svelte': 3.0.1(svelte@4.2.8)(vite@5.0.6)
- debug: 4.3.4
+ debug: 4.3.4(supports-color@8.1.1)
svelte: 4.2.8
vite: 5.0.6
transitivePeerDependencies:
@@ -1616,7 +1417,7 @@ packages:
vite: ^4.0.0
dependencies:
'@sveltejs/vite-plugin-svelte-inspector': 1.0.4(@sveltejs/vite-plugin-svelte@2.5.3)(svelte@4.2.8)(vite@5.0.6)
- debug: 4.3.4
+ debug: 4.3.4(supports-color@8.1.1)
deepmerge: 4.3.1
kleur: 4.1.5
magic-string: 0.30.5
@@ -1636,7 +1437,7 @@ packages:
vite: ^5.0.0
dependencies:
'@sveltejs/vite-plugin-svelte-inspector': 2.0.0(@sveltejs/vite-plugin-svelte@3.0.1)(svelte@4.2.8)(vite@5.0.6)
- debug: 4.3.4
+ debug: 4.3.4(supports-color@8.1.1)
deepmerge: 4.3.1
kleur: 4.1.5
magic-string: 0.30.5
@@ -1818,7 +1619,7 @@ packages:
'@typescript-eslint/type-utils': 6.13.2(eslint@8.55.0)(typescript@5.3.2)
'@typescript-eslint/utils': 6.13.2(eslint@8.55.0)(typescript@5.3.2)
'@typescript-eslint/visitor-keys': 6.13.2
- debug: 4.3.4
+ debug: 4.3.4(supports-color@8.1.1)
eslint: 8.55.0
graphemer: 1.4.0
ignore: 5.3.0
@@ -1844,7 +1645,7 @@ packages:
'@typescript-eslint/types': 6.13.2
'@typescript-eslint/typescript-estree': 6.13.2(typescript@5.3.2)
'@typescript-eslint/visitor-keys': 6.13.2
- debug: 4.3.4
+ debug: 4.3.4(supports-color@8.1.1)
eslint: 8.55.0
typescript: 5.3.2
transitivePeerDependencies:
@@ -1871,7 +1672,7 @@ packages:
dependencies:
'@typescript-eslint/typescript-estree': 6.13.2(typescript@5.3.2)
'@typescript-eslint/utils': 6.13.2(eslint@8.55.0)(typescript@5.3.2)
- debug: 4.3.4
+ debug: 4.3.4(supports-color@8.1.1)
eslint: 8.55.0
ts-api-utils: 1.0.3(typescript@5.3.2)
typescript: 5.3.2
@@ -1895,7 +1696,7 @@ packages:
dependencies:
'@typescript-eslint/types': 6.13.2
'@typescript-eslint/visitor-keys': 6.13.2
- debug: 4.3.4
+ debug: 4.3.4(supports-color@8.1.1)
globby: 11.1.0
is-glob: 4.0.3
semver: 7.5.4
@@ -1936,7 +1737,7 @@ packages:
resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==}
dev: true
- /@unocss/astro@0.58.0(vite@5.0.6):
+ /@unocss/astro@0.58.0(rollup@4.6.1)(vite@5.0.6):
resolution: {integrity: sha512-df+tEFO5eKXjQOwSWQhS9IdjD0sfLHLtn8U09sEKR2Nmh5CvpwyBxmvLQgOCilPou7ehmyKfsyGRLZg7IMp+Ew==}
peerDependencies:
vite: ^2.9.0 || ^3.0.0-0 || ^4.0.0 || ^5.0.0-0
@@ -1946,19 +1747,19 @@ packages:
dependencies:
'@unocss/core': 0.58.0
'@unocss/reset': 0.58.0
- '@unocss/vite': 0.58.0(vite@5.0.6)
+ '@unocss/vite': 0.58.0(rollup@4.6.1)(vite@5.0.6)
vite: 5.0.6
transitivePeerDependencies:
- rollup
dev: true
- /@unocss/cli@0.58.0:
+ /@unocss/cli@0.58.0(rollup@4.6.1):
resolution: {integrity: sha512-rhsrDBxAVueygMcAbMkbuvsHbBL2rG6N96LllYwHn16FLgOE3Sf4JW1/LlNjQje3BtwMMtbSCCAeu2SryFhzbw==}
engines: {node: '>=14'}
hasBin: true
dependencies:
'@ampproject/remapping': 2.2.1
- '@rollup/pluginutils': 5.1.0
+ '@rollup/pluginutils': 5.1.0(rollup@4.6.1)
'@unocss/config': 0.58.0
'@unocss/core': 0.58.0
'@unocss/preset-uno': 0.58.0
@@ -2134,13 +1935,13 @@ packages:
'@unocss/core': 0.58.0
dev: true
- /@unocss/vite@0.58.0(vite@5.0.6):
+ /@unocss/vite@0.58.0(rollup@4.6.1)(vite@5.0.6):
resolution: {integrity: sha512-OCUOLMSOBEtXOEyBbAvMI3/xdR175BWRzmvV9Wc34ANZclEvCdVH8+WU725ibjY4VT0gVIuX68b13fhXdHV41A==}
peerDependencies:
vite: ^2.9.0 || ^3.0.0-0 || ^4.0.0 || ^5.0.0-0
dependencies:
'@ampproject/remapping': 2.2.1
- '@rollup/pluginutils': 5.1.0
+ '@rollup/pluginutils': 5.1.0(rollup@4.6.1)
'@unocss/config': 0.58.0
'@unocss/core': 0.58.0
'@unocss/inspector': 0.58.0
@@ -2477,12 +2278,12 @@ packages:
ip-regex: 5.0.0
dev: true
- /cidr-tools@6.4.1:
- resolution: {integrity: sha512-s8JNDwWgc2e0roEF6KDkQfHkZgEnehoap5hK7swPlEQMb9f8msrWqpgVCVKiDm3ARxpesOru9Tu49N8UpJjmDA==}
+ /cidr-tools@6.4.2:
+ resolution: {integrity: sha512-KZC8t2ipCqU2M+ISmTxRDGu9bku5MRU3V1cWyGEFJTZEzRhGvBJvVsbpZO5UAu12fExRFihtYGXAlgFFpmK9jw==}
engines: {node: '>=16'}
dependencies:
cidr-regex: 4.0.3
- ip-bigint: 7.2.1
+ ip-bigint: 7.3.0
ip-regex: 5.0.0
string-natural-compare: 3.0.1
dev: true
@@ -2652,18 +2453,6 @@ packages:
ms: 2.1.3
dev: true
- /debug@4.3.4:
- resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==}
- engines: {node: '>=6.0'}
- peerDependencies:
- supports-color: '*'
- peerDependenciesMeta:
- supports-color:
- optional: true
- dependencies:
- ms: 2.1.2
- dev: true
-
/debug@4.3.4(supports-color@8.1.1):
resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==}
engines: {node: '>=6.0'}
@@ -2695,7 +2484,7 @@ packages:
resolution: {integrity: sha512-AD7TrdNNPXRZIGw63dw+lnGmT4v7ggZC5NHNJgAYWm5njrwoze1q5JSAW9YuLy2tjnoLUG/r8FEB93MCh9QJPg==}
engines: {node: '>= 16'}
dependencies:
- execa: 7.1.1
+ execa: 7.2.0
dev: true
/defaults@1.0.4:
@@ -2773,18 +2562,6 @@ packages:
resolution: {integrity: sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==}
dev: true
- /effection@2.0.8:
- resolution: {integrity: sha512-/v7cbPIXGGylInQgHHjJutzqUn6VIfcP13hh2X0hXf04wwAlSI+lVjUBKpr5TX3+v9dXV/JLHO/pqQ9Cp1QAnQ==}
- dependencies:
- '@effection/channel': 2.0.6
- '@effection/core': 2.2.3
- '@effection/events': 2.0.6
- '@effection/fetch': 2.0.7(mocha@10.2.0)
- '@effection/main': 2.1.2
- '@effection/stream': 2.0.6
- '@effection/subscription': 2.0.6
- dev: true
-
/effection@2.0.8(mocha@10.2.0):
resolution: {integrity: sha512-/v7cbPIXGGylInQgHHjJutzqUn6VIfcP13hh2X0hXf04wwAlSI+lVjUBKpr5TX3+v9dXV/JLHO/pqQ9Cp1QAnQ==}
dependencies:
@@ -2881,36 +2658,6 @@ packages:
resolution: {integrity: sha512-SOp9Phqvqn7jtEUxPWdWfWoLmyt2VaJ6MpvP9Comy1MceMXqE6bxvaTu4iaxpYYPzhny28Lc+M87/c2cPK6lDg==}
dev: true
- /esbuild@0.18.14:
- resolution: {integrity: sha512-uNPj5oHPYmj+ZhSQeYQVFZ+hAlJZbAGOmmILWIqrGvPVlNLbyOvU5Bu6Woi8G8nskcx0vwY0iFoMPrzT86Ko+w==}
- engines: {node: '>=12'}
- hasBin: true
- requiresBuild: true
- optionalDependencies:
- '@esbuild/android-arm': 0.18.14
- '@esbuild/android-arm64': 0.18.14
- '@esbuild/android-x64': 0.18.14
- '@esbuild/darwin-arm64': 0.18.14
- '@esbuild/darwin-x64': 0.18.14
- '@esbuild/freebsd-arm64': 0.18.14
- '@esbuild/freebsd-x64': 0.18.14
- '@esbuild/linux-arm': 0.18.14
- '@esbuild/linux-arm64': 0.18.14
- '@esbuild/linux-ia32': 0.18.14
- '@esbuild/linux-loong64': 0.18.14
- '@esbuild/linux-mips64el': 0.18.14
- '@esbuild/linux-ppc64': 0.18.14
- '@esbuild/linux-riscv64': 0.18.14
- '@esbuild/linux-s390x': 0.18.14
- '@esbuild/linux-x64': 0.18.14
- '@esbuild/netbsd-x64': 0.18.14
- '@esbuild/openbsd-x64': 0.18.14
- '@esbuild/sunos-x64': 0.18.14
- '@esbuild/win32-arm64': 0.18.14
- '@esbuild/win32-ia32': 0.18.14
- '@esbuild/win32-x64': 0.18.14
- dev: true
-
/esbuild@0.19.8:
resolution: {integrity: sha512-l7iffQpT2OrZfH2rXIp7/FkmaeZM0vxbxN9KfiCwGYuZqzMg/JdvX26R31Zxn/Pxvsrg3Y9N6XTcnknqDyyv4w==}
engines: {node: '>=12'}
@@ -3160,7 +2907,7 @@ packages:
ajv: 6.12.6
chalk: 4.1.2
cross-spawn: 7.0.3
- debug: 4.3.4
+ debug: 4.3.4(supports-color@8.1.1)
doctrine: 3.0.0
escape-string-regexp: 4.0.0
eslint-scope: 7.2.2
@@ -3257,8 +3004,8 @@ packages:
strip-final-newline: 2.0.0
dev: true
- /execa@7.1.1:
- resolution: {integrity: sha512-wH0eMf/UXckdUYnO21+HDztteVv05rq2GXksxT4fCGeHkBhw1DROXh40wcjMcRqDOWE7iPJ4n3M7e2+YFP+76Q==}
+ /execa@7.2.0:
+ resolution: {integrity: sha512-UduyVP7TLB5IcAQl+OzLyLcS/l32W/GLg+AhHJ+ow40FOk2U3SAllPwR44v4vmdFwIWqpdwxxpQbF1n5ta9seA==}
engines: {node: ^14.18.0 || ^16.14.0 || >=18.0.0}
dependencies:
cross-spawn: 7.0.3
@@ -3675,7 +3422,7 @@ packages:
resolution: {integrity: sha512-e6c3zxr9COnnc29PIz9LffmALOt0XhIJdR7f83DyHcQksL3B40KGmU3Sr1lrHja3i7Zyqo+AbwKZ+nZiMvg/OA==}
engines: {node: '>=16'}
dependencies:
- cidr-tools: 6.4.1
+ cidr-tools: 6.4.2
default-gateway: 7.2.2
is-ip: 5.0.0
p-event: 5.0.1
@@ -3690,8 +3437,8 @@ packages:
side-channel: 1.0.4
dev: true
- /ip-bigint@7.2.1:
- resolution: {integrity: sha512-AftDIrlM5ZQM+qQ31IQ5MsL3tJWleeN3r0VqhmkB9oLvwcaDLeLNPtX4d9hahzExTFtz69eRv6LsGAoH20/8/g==}
+ /ip-bigint@7.3.0:
+ resolution: {integrity: sha512-2qVAe0Q9+Y+5nGvmogwK9y4kefD5Ks5l/IG0Jo1lhU9gIF34jifhqrwXwzkIl+LC594Q6SyAlngs4p890xsXVw==}
engines: {node: '>=16'}
dev: true
@@ -4102,7 +3849,7 @@ packages:
/micromark@2.11.4:
resolution: {integrity: sha512-+WoovN/ppKolQOFIAajxi7Lu9kInbPxFuTBVEavFcL8eAfVstoc5MocPmqBeAdBOJV00uaVjegzH4+MA0DN/uA==}
dependencies:
- debug: 4.3.4
+ debug: 4.3.4(supports-color@8.1.1)
parse-entities: 2.0.0
transitivePeerDependencies:
- supports-color
@@ -4621,14 +4368,6 @@ packages:
glob: 7.2.3
dev: true
- /rollup@3.29.4:
- resolution: {integrity: sha512-oWzmBZwvYrU0iJHtDmhsm662rC15FRXmcjCk1xD771dFDx5jJ02ufAQQTn0etB2emNk4J9EZg/yWKpsn9BWGRw==}
- engines: {node: '>=14.18.0', npm: '>=8.0.0'}
- hasBin: true
- optionalDependencies:
- fsevents: 2.3.3
- dev: true
-
/rollup@4.6.1:
resolution: {integrity: sha512-jZHaZotEHQaHLgKr8JnQiDT1rmatjgKlMekyksz+yk9jt/8z9quNjnKNRoaM0wd9DC2QKXjmWWuDYtM3jfF8pQ==}
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
@@ -4663,7 +4402,7 @@ packages:
/rxjs@7.8.1:
resolution: {integrity: sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==}
dependencies:
- tslib: 2.6.2
+ tslib: 2.6.0
dev: true
/sade@1.8.1:
@@ -5133,8 +4872,8 @@ packages:
strip-bom: 3.0.0
dev: true
- /tslib@2.6.2:
- resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==}
+ /tslib@2.6.0:
+ resolution: {integrity: sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==}
dev: true
/type-check@0.4.0:
@@ -5250,7 +4989,7 @@ packages:
'@types/unist': 2.0.10
dev: true
- /unocss@0.58.0(postcss@8.4.32)(vite@5.0.6):
+ /unocss@0.58.0(postcss@8.4.32)(rollup@4.6.1)(vite@5.0.6):
resolution: {integrity: sha512-MSPRHxBqWN+1AHGV+J5uUy4//e6ZBK6O+ISzD0qrXcCD/GNtxk1+lYjOK2ltkUiKX539+/KF91vNxzhhwEf+xA==}
engines: {node: '>=14'}
peerDependencies:
@@ -5262,8 +5001,8 @@ packages:
vite:
optional: true
dependencies:
- '@unocss/astro': 0.58.0(vite@5.0.6)
- '@unocss/cli': 0.58.0
+ '@unocss/astro': 0.58.0(rollup@4.6.1)(vite@5.0.6)
+ '@unocss/cli': 0.58.0(rollup@4.6.1)
'@unocss/core': 0.58.0
'@unocss/extractor-arbitrary-variants': 0.58.0
'@unocss/postcss': 0.58.0(postcss@8.4.32)
@@ -5281,7 +5020,7 @@ packages:
'@unocss/transformer-compile-class': 0.58.0
'@unocss/transformer-directives': 0.58.0
'@unocss/transformer-variant-group': 0.58.0
- '@unocss/vite': 0.58.0(vite@5.0.6)
+ '@unocss/vite': 0.58.0(rollup@4.6.1)(vite@5.0.6)
vite: 5.0.6
transitivePeerDependencies:
- postcss
@@ -5326,41 +5065,6 @@ packages:
vfile-message: 2.0.4
dev: true
- /vite@4.5.0:
- resolution: {integrity: sha512-ulr8rNLA6rkyFAlVWw2q5YJ91v098AFQ2R0PRFwPzREXOUJQPtFUG0t+/ZikhaOCDqFoDhN6/v8Sq0o4araFAw==}
- engines: {node: ^14.18.0 || >=16.0.0}
- hasBin: true
- peerDependencies:
- '@types/node': '>= 14'
- less: '*'
- lightningcss: ^1.21.0
- sass: '*'
- stylus: '*'
- sugarss: '*'
- terser: ^5.4.0
- peerDependenciesMeta:
- '@types/node':
- optional: true
- less:
- optional: true
- lightningcss:
- optional: true
- sass:
- optional: true
- stylus:
- optional: true
- sugarss:
- optional: true
- terser:
- optional: true
- dependencies:
- esbuild: 0.18.14
- postcss: 8.4.32
- rollup: 3.29.4
- optionalDependencies:
- fsevents: 2.3.3
- dev: true
-
/vite@5.0.6:
resolution: {integrity: sha512-MD3joyAEBtV7QZPl2JVVUai6zHms3YOmLR+BpMzLlX2Yzjfcc4gTgNi09d/Rua3F4EtC8zdwPU8eQYyib4vVMQ==}
engines: {node: ^18.0.0 || >=20.0.0}