diff --git a/.gitignore b/.gitignore index 50d8e32..24a7fe1 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ .idea/ debug.log package-lock.json +!examples/tauri-app/package-lock.json .vscode/settings.json yarn.lock @@ -15,3 +16,10 @@ node_modules/ dist-js dist + +android/.gradle/ +android/**/build/ +examples/tauri-app/node_modules/ +examples/tauri-app/dist/ +examples/tauri-app/src-tauri/gen/ +examples/tauri-app/src-tauri/target/ diff --git a/Cargo.toml b/Cargo.toml index 7bd0131..1542d84 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tauri-plugin-vnidrop-share" -version = "0.2.2" +version = "1.0.0-rc" description = "A Tauri plugin for sharing content via the system's share dialog." license = "MIT" authors = [ "Abass Hammed", "Vnidrop" ] diff --git a/README.md b/README.md index 8815e81..1e10727 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,8 @@ The web's native [Web Share API](https://developer.mozilla.org/en-US/docs/Web/AP For file sharing, the plugin intelligently manages the lifecycle of temporary files. It creates secure temporary files from Base64 data, ensuring they persist for the duration of the sharing operation, and automatically cleans them up when the application exits. On mobile platforms like Android and iOS, the native sharing APIs are directly invoked, and temporary files are managed and cleaned up within the native code. Android file cleanup is delayed briefly after the app resumes so receiving apps have time to open granted file URIs. +For release safety, share payloads are bounded on both the JavaScript and native sides. URLs must be well-formed `http://` or `https://` URLs; other schemes should be shared as plain text instead. A single share request may include up to 16 files, each file may be up to 50 MiB, and the total file payload may be up to 100 MiB. Text is limited to 64 KiB, titles to 1 KiB, URLs to 4 KiB, and file names/MIME types to 255 bytes. + ## Installation ### Rust @@ -18,7 +20,7 @@ Add the plugin to your `Cargo.toml`: ```sh [dependencies] -tauri-plugin-vnidrop-share = "0.2.2" +tauri-plugin-vnidrop-share = "1.0.0-rc" ``` ### Frontend @@ -93,7 +95,7 @@ The frontend API is designed to closely resemble the Web Share API, making it in 3. Manual Cleanup - While the plugin automatically handles cleanup when the app exits, you can manually call `cleanup()` to remove temporary files after a share operation is complete to free up disk space. Avoid calling `cleanup()` while a share sheet is still open. + While the plugin automatically handles cleanup when the app exits, you can manually call `cleanup()` to remove temporary files after a share operation is complete to free up disk space. Avoid calling `cleanup()` while a share sheet is still open. The `cleanup` command is not included in the default permission set; enable `vnidrop-share:allow-cleanup` explicitly if your app calls it from the frontend. ```ts import { cleanup } from "@vnidrop/tauri-plugin-share"; @@ -115,9 +117,25 @@ The frontend API is designed to closely resemble the Web Share API, making it in .plugin(tauri_plugin_vnidrop_share::init()) .run(tauri::generate_context!()) .expect("error while running tauri application"); - } + } ``` +### Android troubleshooting + +If Android reports `vnidrop-share.can_share not allowed. Plugin not found`, the APK does not have the share plugin registered in Tauri's runtime/ACL. Verify that your app calls: + +```rs +.plugin(tauri_plugin_vnidrop_share::init()) +``` + +and that the active capability includes the plugin permission: + +```json +"vnidrop-share:default" +``` + +After changing plugin permissions or native Android code, fully rebuild and reinstall the Android app. A stale installed APK can keep showing this error even after the source code is corrected. + 2. **Using the `ShareExt` Trait** The `ShareExt` trait is provided for a more idiomatic way to access the plugin's functionalities directly from an `AppHandle` or `Window`. @@ -141,3 +159,18 @@ The frontend API is designed to closely resemble the Web Share API, making it in Ok(()) } ``` + +## Testing + +The repository includes Rust, JavaScript, Android JVM, iOS Swift, and example-app checks: + +```sh +bun run build +bun run test +bun run test:types +cargo test +cd android && gradle testDebugUnitTest +cd ios && VNIDROP_SHARE_USE_TAURI_STUB=1 swift test +cd examples/tauri-app && npm install && npm run build && npm audit +cd examples/tauri-app/src-tauri && cargo check +``` diff --git a/android/build.gradle.kts b/android/build.gradle.kts index df8282a..d13f981 100644 --- a/android/build.gradle.kts +++ b/android/build.gradle.kts @@ -24,16 +24,18 @@ android { } } compileOptions { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 } - kotlinOptions { - jvmTarget = "1.8" +} + +kotlin { + compilerOptions { + jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_11) } } 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") diff --git a/android/gradle.properties b/android/gradle.properties new file mode 100644 index 0000000..5bac8ac --- /dev/null +++ b/android/gradle.properties @@ -0,0 +1 @@ +android.useAndroidX=true diff --git a/android/settings.gradle b/android/settings.gradle index d7782a4..a9f43dc 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -8,10 +8,10 @@ pluginManagement { eachPlugin { switch (requested.id.id) { case "com.android.library": - useVersion("8.0.2") + useVersion("8.13.2") break case "org.jetbrains.kotlin.android": - useVersion("1.8.20") + useVersion("2.2.21") break } } @@ -28,4 +28,7 @@ dependencyResolutionManagement { } include ':tauri-android' -project(':tauri-android').projectDir = new File('./.tauri/tauri-api') +def generatedTauriApi = new File('./.tauri/tauri-api') +project(':tauri-android').projectDir = generatedTauriApi.exists() + ? generatedTauriApi + : new File('./test-support/tauri-api') diff --git a/android/src/main/java/SharePlugin.kt b/android/src/main/java/SharePlugin.kt index f45be38..2ea5d38 100644 --- a/android/src/main/java/SharePlugin.kt +++ b/android/src/main/java/SharePlugin.kt @@ -65,6 +65,7 @@ class SharePlugin(private val activity: Activity): Plugin(activity) { val filesForShare = ArrayList() try { val args = invoke.parseArgs(ShareOptions::class.java) + ShareValidation.validateShareOptions(args) val fileUris = ArrayList() var determinedMimeType = "text/plain" @@ -72,6 +73,7 @@ class SharePlugin(private val activity: Activity): Plugin(activity) { if (it.isNotEmpty()) { for (file in it) { val decodedBytes = Base64.decode(file.data, Base64.DEFAULT) + ShareValidation.validateDecodedFileSize(file, decodedBytes.size) val tempFile = createSafeFile(file.name) FileOutputStream(tempFile).use { outputStream -> outputStream.write(decodedBytes) @@ -256,12 +258,12 @@ class SharePlugin(private val activity: Activity): Plugin(activity) { // A robust approach is to allow only a whitelist of characters. // Here, we also add a UUID to prevent name collisions. val sanitizedBaseName = untrustedFileName.replace(Regex("[^a-zA-Z0-9._-]"), "") - val finalFileName = "${UUID.randomUUID()}-${sanitizedBaseName}" - - if (finalFileName.isEmpty()) { + if (sanitizedBaseName.isEmpty()) { throw SecurityException("Invalid filename: sanitized name is empty.") } + val finalFileName = "${UUID.randomUUID()}-${sanitizedBaseName}" + val intendedFile = File(safeDir, finalFileName) // CRITICAL: Path Traversal Check diff --git a/android/src/main/java/ShareValidation.kt b/android/src/main/java/ShareValidation.kt new file mode 100644 index 0000000..ae840a7 --- /dev/null +++ b/android/src/main/java/ShareValidation.kt @@ -0,0 +1,97 @@ +package plugin.vnidrop.share + +import java.net.URI + +object ShareValidation { + const val MAX_FILES = 16 + const val MAX_FILE_BYTES = 50 * 1024 * 1024 + const val MAX_TOTAL_FILE_BYTES = 100 * 1024 * 1024 + const val MAX_TEXT_BYTES = 64 * 1024 + const val MAX_TITLE_BYTES = 1024 + const val MAX_URL_BYTES = 4096 + const val MAX_FILE_NAME_BYTES = 255 + const val MAX_MIME_TYPE_BYTES = 255 + + @Throws(SecurityException::class) + fun validateShareOptions(args: ShareOptions) { + validateStringLength("text", args.text, MAX_TEXT_BYTES) + validateStringLength("title", args.title, MAX_TITLE_BYTES) + validateStringLength("url", args.url, MAX_URL_BYTES) + + args.url?.let { + if (it.isNotEmpty()) { + validateWebUrl(it) + } + } + + val files = args.files ?: return + if (files.size > MAX_FILES) { + throw SecurityException("Too many files provided. Maximum is $MAX_FILES.") + } + + var totalEstimatedBytes = 0L + for (file in files) { + validateStringLength("file name", file.name, MAX_FILE_NAME_BYTES) + validateStringLength("mime type", file.mimeType, MAX_MIME_TYPE_BYTES) + + val estimatedBytes = estimateBase64DecodedSize(file.data) + if (estimatedBytes > MAX_FILE_BYTES) { + throw SecurityException("File '${file.name}' exceeds the maximum size of $MAX_FILE_BYTES bytes.") + } + totalEstimatedBytes += estimatedBytes + if (totalEstimatedBytes > MAX_TOTAL_FILE_BYTES) { + throw SecurityException("Total shared file size exceeds the maximum of $MAX_TOTAL_FILE_BYTES bytes.") + } + } + } + + @Throws(SecurityException::class) + fun validateDecodedFileSize(file: SharedFile, byteCount: Int) { + if (byteCount > MAX_FILE_BYTES) { + throw SecurityException("File '${file.name}' exceeds the maximum size of $MAX_FILE_BYTES bytes.") + } + } + + @Throws(SecurityException::class) + fun estimateBase64DecodedSize(data: String): Long { + val normalized = data.filterNot { it.isWhitespace() } + if (normalized.isEmpty()) return 0 + if (normalized.length % 4 != 0) { + throw SecurityException("Invalid Base64 data.") + } + + val padding = normalized.takeLastWhile { it == '=' }.length + if (padding > 2) { + throw SecurityException("Invalid Base64 data.") + } + + return (normalized.length / 4L) * 3L - padding + } + + @Throws(SecurityException::class) + private fun validateStringLength(field: String, value: String?, maxBytes: Int) { + if (value != null && value.toByteArray(Charsets.UTF_8).size > maxBytes) { + throw SecurityException("$field exceeds the maximum length of $maxBytes bytes.") + } + } + + @Throws(SecurityException::class) + private fun validateWebUrl(url: String) { + if (url.trim() != url || url.any { it.isWhitespace() || Character.isISOControl(it) }) { + throw SecurityException("Only well-formed http:// and https:// URLs can be shared as URLs.") + } + + val uri = try { + URI(url) + } catch (_: Exception) { + null + } + val scheme = uri?.scheme?.lowercase() + if (scheme != "http" && scheme != "https") { + throw SecurityException("Only http:// and https:// URLs can be shared as URLs.") + } + if (uri.host.isNullOrEmpty()) { + throw SecurityException("Only http:// and https:// URLs can be shared as URLs.") + } + } +} diff --git a/android/src/test/java/ShareValidationUnitTest.kt b/android/src/test/java/ShareValidationUnitTest.kt new file mode 100644 index 0000000..da36ee3 --- /dev/null +++ b/android/src/test/java/ShareValidationUnitTest.kt @@ -0,0 +1,95 @@ +package plugin.vnidrop.share + +import org.junit.Assert.assertEquals +import org.junit.Test + +class ShareValidationUnitTest { + @Test + fun acceptsHttpAndHttpsUrls() { + ShareValidation.validateShareOptions(options(url = "https://example.com")) + ShareValidation.validateShareOptions(options(url = "http://example.com")) + } + + @Test + fun rejectsNonWebUrlSchemes() { + listOf( + "file:///data/user/0/app/secret.db", + "content://provider/item", + "custom://value", + "https:///missing-host", + " https://example.com", + "https://example.com\nhttps://evil.example", + ).forEach { + assertThrowsSecurity { + ShareValidation.validateShareOptions(options(url = it)) + } + } + } + + @Test + fun estimatesBase64DecodedSize() { + assertEquals(5, ShareValidation.estimateBase64DecodedSize("aGVsbG8=")) + assertEquals(5, ShareValidation.estimateBase64DecodedSize("aGVs\nbG8=")) + } + + @Test + fun rejectsInvalidBase64Shape() { + assertThrowsSecurity { + ShareValidation.estimateBase64DecodedSize("abc") + } + assertThrowsSecurity { + ShareValidation.estimateBase64DecodedSize("abcd===") + } + } + + @Test + fun rejectsTooManyFilesAndOversizedText() { + val files = (0..ShareValidation.MAX_FILES).map { + file("report-$it.txt", "aGVsbG8=") + } + assertThrowsSecurity { + ShareValidation.validateShareOptions(options(files = files)) + } + + assertThrowsSecurity { + ShareValidation.validateShareOptions(options(text = "a".repeat(ShareValidation.MAX_TEXT_BYTES + 1))) + } + + assertThrowsSecurity { + ShareValidation.validateShareOptions( + options(files = listOf(file("a".repeat(ShareValidation.MAX_FILE_NAME_BYTES + 1), "aGVsbG8="))) + ) + } + } + + private fun options( + text: String? = null, + title: String? = null, + url: String? = null, + files: List? = null, + ): ShareOptions { + return ShareOptions().apply { + this.text = text + this.title = title + this.url = url + this.files = files + } + } + + private fun file(name: String, data: String): SharedFile { + return SharedFile().apply { + this.name = name + this.data = data + this.mimeType = "text/plain" + } + } + + private fun assertThrowsSecurity(block: () -> Unit) { + try { + block() + } catch (_: SecurityException) { + return + } + throw AssertionError("Expected SecurityException") + } +} diff --git a/android/test-support/tauri-api/build.gradle.kts b/android/test-support/tauri-api/build.gradle.kts new file mode 100644 index 0000000..42811d8 --- /dev/null +++ b/android/test-support/tauri-api/build.gradle.kts @@ -0,0 +1,24 @@ +plugins { + id("com.android.library") + id("org.jetbrains.kotlin.android") +} + +android { + namespace = "app.tauri" + compileSdk = 36 + + defaultConfig { + minSdk = 21 + } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + } +} + +kotlin { + compilerOptions { + jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_11) + } +} diff --git a/android/test-support/tauri-api/src/main/AndroidManifest.xml b/android/test-support/tauri-api/src/main/AndroidManifest.xml new file mode 100644 index 0000000..94cbbcf --- /dev/null +++ b/android/test-support/tauri-api/src/main/AndroidManifest.xml @@ -0,0 +1 @@ + diff --git a/android/test-support/tauri-api/src/main/java/app/tauri/annotation/Command.kt b/android/test-support/tauri-api/src/main/java/app/tauri/annotation/Command.kt new file mode 100644 index 0000000..2b9b065 --- /dev/null +++ b/android/test-support/tauri-api/src/main/java/app/tauri/annotation/Command.kt @@ -0,0 +1,5 @@ +package app.tauri.annotation + +@Target(AnnotationTarget.FUNCTION) +@Retention(AnnotationRetention.RUNTIME) +annotation class Command diff --git a/android/test-support/tauri-api/src/main/java/app/tauri/annotation/InvokeArg.kt b/android/test-support/tauri-api/src/main/java/app/tauri/annotation/InvokeArg.kt new file mode 100644 index 0000000..8f5e0d1 --- /dev/null +++ b/android/test-support/tauri-api/src/main/java/app/tauri/annotation/InvokeArg.kt @@ -0,0 +1,5 @@ +package app.tauri.annotation + +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +annotation class InvokeArg diff --git a/android/test-support/tauri-api/src/main/java/app/tauri/annotation/TauriPlugin.kt b/android/test-support/tauri-api/src/main/java/app/tauri/annotation/TauriPlugin.kt new file mode 100644 index 0000000..309d49e --- /dev/null +++ b/android/test-support/tauri-api/src/main/java/app/tauri/annotation/TauriPlugin.kt @@ -0,0 +1,5 @@ +package app.tauri.annotation + +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +annotation class TauriPlugin diff --git a/android/test-support/tauri-api/src/main/java/app/tauri/plugin/Invoke.kt b/android/test-support/tauri-api/src/main/java/app/tauri/plugin/Invoke.kt new file mode 100644 index 0000000..b6e47ad --- /dev/null +++ b/android/test-support/tauri-api/src/main/java/app/tauri/plugin/Invoke.kt @@ -0,0 +1,26 @@ +package app.tauri.plugin + +class Invoke { + fun parseArgs(clazz: Class): T { + return clazz.getDeclaredConstructor().newInstance() + } + + fun resolve() {} + + fun resolve(value: Any?) { + @Suppress("UNUSED_VARIABLE") + val ignored = value + } + + fun reject(message: String) { + @Suppress("UNUSED_VARIABLE") + val ignored = message + } + + fun reject(message: String, throwable: Throwable) { + @Suppress("UNUSED_VARIABLE") + val ignoredMessage = message + @Suppress("UNUSED_VARIABLE") + val ignoredThrowable = throwable + } +} diff --git a/android/test-support/tauri-api/src/main/java/app/tauri/plugin/JSObject.kt b/android/test-support/tauri-api/src/main/java/app/tauri/plugin/JSObject.kt new file mode 100644 index 0000000..61494b2 --- /dev/null +++ b/android/test-support/tauri-api/src/main/java/app/tauri/plugin/JSObject.kt @@ -0,0 +1,3 @@ +package app.tauri.plugin + +class JSObject : HashMap() diff --git a/android/test-support/tauri-api/src/main/java/app/tauri/plugin/Plugin.kt b/android/test-support/tauri-api/src/main/java/app/tauri/plugin/Plugin.kt new file mode 100644 index 0000000..599e48c --- /dev/null +++ b/android/test-support/tauri-api/src/main/java/app/tauri/plugin/Plugin.kt @@ -0,0 +1,12 @@ +package app.tauri.plugin + +import android.app.Activity + +open class Plugin(activity: Activity) { + @Suppress("UNUSED_VARIABLE") + private val ignoredActivity = activity + + open fun onPause() {} + + open fun onResume() {} +} diff --git a/api-iife.js b/api-iife.js index 553af6f..01e58b7 100644 --- a/api-iife.js +++ b/api-iife.js @@ -1 +1 @@ -if("__TAURI__"in window){var __TAURI_PLUGIN_SHARE__=function(e){"use strict";async function n(e,n={},t){return window.__TAURI_INTERNALS__.invoke(e,n,t)}function t(e){return Boolean(e.text||e.url||e.files&&e.files.length>0)}async function r(e){return new Promise((n,t)=>{const r=new FileReader;r.readAsDataURL(e),r.onload=()=>{const e=r.result.split(",")[1];n(e)},r.onerror=e=>t(e)})}return"function"==typeof SuppressedError&&SuppressedError,e.canShare=async function(e){if(e&&!t(e))return!1;const r=await n("plugin:vnidrop-share|can_share");return!0===r.value||"true"===r.value},e.cleanup=async function(){await n("plugin:vnidrop-share|cleanup")},e.share=async function(e){if(!t(e))throw new TypeError("No content provided to share.");const a={text:e.text,title:e.title,url:e.url};e.files&&e.files.length>0&&(a.files=await Promise.all(e.files.map(async e=>({data:await r(e),name:e.name,mimeType:e.type||"application/octet-stream"})))),await n("plugin:vnidrop-share|share",{options:a})},e}({});Object.defineProperty(window.__TAURI__,"share",{value:__TAURI_PLUGIN_SHARE__})} +if("__TAURI__"in window){var __TAURI_PLUGIN_SHARE__=function(e){"use strict";async function t(e,t={},r){return window.__TAURI_INTERNALS__.invoke(e,t,r)}"function"==typeof SuppressedError&&SuppressedError;const r=52428800,n=104857600;function i(e){return Boolean(e.text||e.url||e.files&&e.files.length>0)}function o(e){return(new TextEncoder).encode(e).length}function s(e){if(e.text&&o(e.text)>65536)throw new TypeError("text exceeds the maximum length of 65536 bytes.");if(e.title&&o(e.title)>1024)throw new TypeError("title exceeds the maximum length of 1024 bytes.");if(void 0!==e.url){if(o(e.url)>4096)throw new TypeError("url exceeds the maximum length of 4096 bytes.");if(e.url.length>0&&!function(e){if(e.trim()!==e||/[\s\u0000-\u001f\u007f]/u.test(e))return!1;const t=e.indexOf("://");if(-1===t)return!1;const r=e.slice(t+3).split(/[/?#]/u,1)[0].split("@").pop()??"";if(!r||r.startsWith(":"))return!1;try{const t=new URL(e);return("http:"===t.protocol||"https:"===t.protocol)&&t.host.length>0}catch{return!1}}(e.url))throw new TypeError("Only http:// and https:// URLs can be shared as URLs.")}if(!e.files||0===e.files.length)return;if(e.files.length>16)throw new TypeError("Too many files provided. Maximum is 16.");if(e.files.reduce((e,t)=>e+t.size,0)>n)throw new TypeError("Total shared file size exceeds the maximum of 104857600 bytes.");const t=e.files.find(e=>e.size>r);if(t)throw new TypeError(`File '${t.name}' exceeds the maximum size of 52428800 bytes.`);if(e.files.find(e=>o(e.name)>255))throw new TypeError("File name exceeds the maximum length of 255 bytes.");if(e.files.find(e=>o(e.type||"application/octet-stream")>255))throw new TypeError("mime type exceeds the maximum length of 255 bytes.")}async function a(e){return new Promise((t,r)=>{const n=new FileReader;n.readAsDataURL(e),n.onload=()=>{const e=n.result.split(",")[1];t(e)},n.onerror=e=>r(e)})}return e.canShare=async function(e){if(e&&!i(e))return!1;if(e)try{s(e)}catch{return!1}const r=await t("plugin:vnidrop-share|can_share");return!0===r.value||"true"===r.value},e.cleanup=async function(){await t("plugin:vnidrop-share|cleanup")},e.share=async function(e){if(!i(e))throw new TypeError("No content provided to share.");s(e);const r={text:e.text,title:e.title,url:e.url};e.files&&e.files.length>0&&(r.files=await Promise.all(e.files.map(async e=>({data:await a(e),name:e.name,mimeType:e.type||"application/octet-stream"})))),await t("plugin:vnidrop-share|share",{options:r})},e}({});Object.defineProperty(window.__TAURI__,"share",{value:__TAURI_PLUGIN_SHARE__})} diff --git a/bun.lock b/bun.lock index ba41293..8ed6d13 100644 --- a/bun.lock +++ b/bun.lock @@ -4,93 +4,188 @@ "": { "name": "tauri-plugin-share-api", "dependencies": { - "@rollup/plugin-terser": "^0.4.4", - "@tauri-apps/api": ">=2.0.0-beta.6", + "@tauri-apps/api": "^2.11.0", }, "devDependencies": { - "@rollup/plugin-node-resolve": "^16.0.1", - "@rollup/plugin-typescript": "^12.0.0", - "rollup": "^4.9.6", - "tslib": "^2.6.2", - "typescript": "^5.3.3", + "@rollup/plugin-node-resolve": "^16.0.3", + "@rollup/plugin-terser": "^1.0.0", + "@rollup/plugin-typescript": "^12.3.0", + "@types/node": "^25.9.3", + "rollup": "^4.62.0", + "tslib": "^2.8.1", + "typescript": "^6.0.3", + "vitest": "^4.1.9", }, }, }, + "overrides": { + "picomatch": "4.0.4", + }, "packages": { + "@emnapi/core": ["@emnapi/core@1.10.0", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" } }, "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw=="], + + "@emnapi/runtime": ["@emnapi/runtime@1.10.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA=="], + + "@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w=="], + "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.12", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg=="], "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="], "@jridgewell/source-map": ["@jridgewell/source-map@0.3.10", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25" } }, "sha512-0pPkgz9dY+bijgistcTTJ5mR+ocqRXLuhXHYdzoMmmoJ2C9S46RCm2GMUbatPEUK9Yjy26IrAy8D/M00lLkv+Q=="], - "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.4", "", {}, "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw=="], + "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="], "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.29", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ=="], - "@rollup/plugin-node-resolve": ["@rollup/plugin-node-resolve@16.0.1", "", { "dependencies": { "@rollup/pluginutils": "^5.0.1", "@types/resolve": "1.20.2", "deepmerge": "^4.2.2", "is-module": "^1.0.0", "resolve": "^1.22.1" }, "peerDependencies": { "rollup": "^2.78.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-tk5YCxJWIG81umIvNkSod2qK5KyQW19qcBF/B78n1bjtOON6gzKoVeSzAE8yHCZEDmqkHKkxplExA8KzdJLJpA=="], + "@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.5", "", { "dependencies": { "@tybys/wasm-util": "^0.10.2" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" } }, "sha512-AWPoBRJ9tsnVhor4sjO7rkni+7p+2IAEFj6cx06UgP10jkQHqay/36uRV/bFkgrh18D9vb4cr8Q0Pthskgzy+Q=="], + + "@oxc-project/types": ["@oxc-project/types@0.133.0", "", {}, "sha512-KzkdCd6Uxqnf6l3HOw1xfatAlUURA0g14cvBYFyJ5SaNOQbOUvBr9PKArcPcrNIeRsBdgcUzOGrhKveVpvOIGA=="], + + "@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.0.3", "", { "os": "android", "cpu": "arm64" }, "sha512-454rs7jHngixp/NMxd5srYD57OnzSlZ/eFTETjORQHLwJG1lRtmNOJcBerZlfu4GjKqeq8aCCIQrMdHyhI51Hw=="], + + "@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.0.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-PcAhP+ynjURNyy8SKGl5DQP94aGuB/7JrXJb/t7P+hanXvQVMWzUvRRhBAcg/lNRadBhoUPqSoP4xw5tR/KBEA=="], + + "@rolldown/binding-darwin-x64": ["@rolldown/binding-darwin-x64@1.0.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-9YpfeUvSE2RS7wysJ81uOZkXJz7f7Q55H2Gvp3VEw/EsahqDtrphrZ0EwDLK5vvKOzaCrBsjF8JmnMLcUt78Gg=="], + + "@rolldown/binding-freebsd-x64": ["@rolldown/binding-freebsd-x64@1.0.3", "", { "os": "freebsd", "cpu": "x64" }, "sha512-yB1IlAsSNHncV6SCTL27/MVGR5htvQsoGxIv5KMGXALp+Ll1wYsn+x98M9MW7qa+NdSbvrrY7ANI4wLJ0n1e6g=="], + + "@rolldown/binding-linux-arm-gnueabihf": ["@rolldown/binding-linux-arm-gnueabihf@1.0.3", "", { "os": "linux", "cpu": "arm" }, "sha512-Yi30IVAAfLUCy2MseFjbB1jAMDl1VMCAas5StnYp8da9+CKvMd2H2cbEjWcw5NPaPqzvYkVIaF1nNUG+b7u/sw=="], + + "@rolldown/binding-linux-arm64-gnu": ["@rolldown/binding-linux-arm64-gnu@1.0.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-jsO7R8To+AdlYgUmN5sHSCZbfhtMBkO0WUx8iORQnPcMMdgr7qM2DQmMwgabs3GhNztdmoKkMKQFHD6DTMCIQw=="], + + "@rolldown/binding-linux-arm64-musl": ["@rolldown/binding-linux-arm64-musl@1.0.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-VWkUHwWriDciit80wleYwKILoR/KMvxh/IdwS/paX+ZgpuRpCrKLUdadJbc0NpBEiyhpYawsJ73j9aCvOH+f7Q=="], + + "@rolldown/binding-linux-ppc64-gnu": ["@rolldown/binding-linux-ppc64-gnu@1.0.3", "", { "os": "linux", "cpu": "ppc64" }, "sha512-5f1laC0SlIR0yDbFCd8acUhvJIag6N3zC5P7oUPN6wX0aOma+uKJ0wBDH5aq7I1PVI2ttTlhJwzwRIBnLiSGEg=="], + + "@rolldown/binding-linux-s390x-gnu": ["@rolldown/binding-linux-s390x-gnu@1.0.3", "", { "os": "linux", "cpu": "s390x" }, "sha512-Iq4ko0r4XsgbrF/LunNgHtAGLRRVE2kXonAXQ/MV0mC6jQpMOhW1SvtZja2EhC/kd05++bP78dsqBeIQyYJ6Yg=="], + + "@rolldown/binding-linux-x64-gnu": ["@rolldown/binding-linux-x64-gnu@1.0.3", "", { "os": "linux", "cpu": "x64" }, "sha512-B8m6tD5+/N5FeNQFbKlLA/2yVq9ycQP1SeedyEYYKWBNR3ZQbkvIUcNnDNM03lO1l5F2roiiFJGgvoLLyZXtSg=="], + + "@rolldown/binding-linux-x64-musl": ["@rolldown/binding-linux-x64-musl@1.0.3", "", { "os": "linux", "cpu": "x64" }, "sha512-pSdpdUJHkuCxun9LE7jvgUB9qsRgaiyNNCX7m/AvHTcq67AiT/Yhoxvw5zPfhrM8k/BfP8ce/hMOpthKDpEUow=="], + + "@rolldown/binding-openharmony-arm64": ["@rolldown/binding-openharmony-arm64@1.0.3", "", { "os": "none", "cpu": "arm64" }, "sha512-OXXS3RKJgX2uLwM+gYyuH5omcH8fL1LJs96pZGgtetVCahON57+d4SJHzTgZiOjxgGkSnpXpOsWuPDGAKAigEg=="], + + "@rolldown/binding-wasm32-wasi": ["@rolldown/binding-wasm32-wasi@1.0.3", "", { "dependencies": { "@emnapi/core": "1.10.0", "@emnapi/runtime": "1.10.0", "@napi-rs/wasm-runtime": "^1.1.4" }, "cpu": "none" }, "sha512-JTtb8BWFynicNSoPrehsCzBtOKjZ6jhMiPFEmOiuXg1Fl8dn2KHQob+GuPSGR0dryQa1PQJbzjF3dqO/whhjLg=="], + + "@rolldown/binding-win32-arm64-msvc": ["@rolldown/binding-win32-arm64-msvc@1.0.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-gEdFFEN70A/jxb2svrWsN3aDL7OUtmvlOy+6fa2jxG8K0wQ1ZbdeLGnidov6Yu5/733dI5ySfzFlQ/cb0bSz1g=="], + + "@rolldown/binding-win32-x64-msvc": ["@rolldown/binding-win32-x64-msvc@1.0.3", "", { "os": "win32", "cpu": "x64" }, "sha512-eXB7CHuaQdqmJcc3koCNtNPmT/bj2gc999kUFgBxG8Ac0NdgXc4rkCHhqrgrhN3zddvvvrgzj1e90SuSfmyIXA=="], - "@rollup/plugin-terser": ["@rollup/plugin-terser@0.4.4", "", { "dependencies": { "serialize-javascript": "^6.0.1", "smob": "^1.0.0", "terser": "^5.17.4" }, "peerDependencies": { "rollup": "^2.0.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-XHeJC5Bgvs8LfukDwWZp7yeqin6ns8RTl2B9avbejt6tZqsqvVoWI7ZTQrcNsfKEDWBTnTxM8nMDkO2IFFbd0A=="], + "@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.1", "", {}, "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw=="], - "@rollup/plugin-typescript": ["@rollup/plugin-typescript@12.1.4", "", { "dependencies": { "@rollup/pluginutils": "^5.1.0", "resolve": "^1.22.1" }, "peerDependencies": { "rollup": "^2.14.0||^3.0.0||^4.0.0", "tslib": "*", "typescript": ">=3.7.0" }, "optionalPeers": ["rollup", "tslib"] }, "sha512-s5Hx+EtN60LMlDBvl5f04bEiFZmAepk27Q+mr85L/00zPDn1jtzlTV6FWn81MaIwqfWzKxmOJrBWHU6vtQyedQ=="], + "@rollup/plugin-node-resolve": ["@rollup/plugin-node-resolve@16.0.3", "", { "dependencies": { "@rollup/pluginutils": "^5.0.1", "@types/resolve": "1.20.2", "deepmerge": "^4.2.2", "is-module": "^1.0.0", "resolve": "^1.22.1" }, "peerDependencies": { "rollup": "^2.78.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-lUYM3UBGuM93CnMPG1YocWu7X802BrNF3jW2zny5gQyLQgRFJhV1Sq0Zi74+dh/6NBx1DxFC4b4GXg9wUCG5Qg=="], + + "@rollup/plugin-terser": ["@rollup/plugin-terser@1.0.0", "", { "dependencies": { "serialize-javascript": "^7.0.3", "smob": "^1.0.0", "terser": "^5.17.4" }, "peerDependencies": { "rollup": "^2.0.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-FnCxhTBx6bMOYQrar6C8h3scPt8/JwIzw3+AJ2K++6guogH5fYaIFia+zZuhqv0eo1RN7W1Pz630SyvLbDjhtQ=="], + + "@rollup/plugin-typescript": ["@rollup/plugin-typescript@12.3.0", "", { "dependencies": { "@rollup/pluginutils": "^5.1.0", "resolve": "^1.22.1" }, "peerDependencies": { "rollup": "^2.14.0||^3.0.0||^4.0.0", "tslib": "*", "typescript": ">=3.7.0" }, "optionalPeers": ["rollup", "tslib"] }, "sha512-7DP0/p7y3t67+NabT9f8oTBFE6gGkto4SA6Np2oudYmZE/m1dt8RB0SjL1msMxFpLo631qjRCcBlAbq1ml/Big=="], "@rollup/pluginutils": ["@rollup/pluginutils@5.2.0", "", { "dependencies": { "@types/estree": "^1.0.0", "estree-walker": "^2.0.2", "picomatch": "^4.0.2" }, "peerDependencies": { "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-qWJ2ZTbmumwiLFomfzTyt5Kng4hwPi9rwCYN4SHb6eaRU1KNO4ccxINHr/VhH4GgPlt1XfSTLX2LBTme8ne4Zw=="], - "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.45.1", "", { "os": "android", "cpu": "arm" }, "sha512-NEySIFvMY0ZQO+utJkgoMiCAjMrGvnbDLHvcmlA33UXJpYBCvlBEbMMtV837uCkS+plG2umfhn0T5mMAxGrlRA=="], + "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.62.0", "", { "os": "android", "cpu": "arm" }, "sha512-IPIQ55ythEHkfEd9jMEi32OQ7SxURsGA43JI22lj01OLZNt2NUbJX8YUHxkVWyQ6daHPNn0truF5nSj3DQp6YQ=="], + + "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.62.0", "", { "os": "android", "cpu": "arm64" }, "sha512-M6s9cr10MibETyo8JsOkq+Lo1+lU6hcvb1MApnUql5qte/5hMEgzlN8/ReIKNfRV8rrqX50W1BX9zoUhC192RA=="], + + "@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.62.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-BqCoMoIbn0keKys+dEAdBa70EtOwV1bEsQCUgU9FdiZmmMge/Zk7LlkYGqbrdHR+Frnt0E1FOanly+rlwvvQzw=="], + + "@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.62.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-SIMzST3VFNXDAbeIWDWiFCNM5qncUBDWaEV7NfE7oZbDt2mgfW4MvbKdbYiGOLoM32gbTv608UMd0XktEYSD7w=="], + + "@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.62.0", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-ezjfSQMP7ArdUsbBwbQIfwAlhE84I2iVnzQNCFSveqV42q+BmKlzVpf7mxv5EchLcoWU4y6/heFzVg1F+hodUQ=="], + + "@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.62.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-9+qTWGW9AZRhnUgwtTwzNwcPlL87ngkeN0LA+q1bADvmY9aNvWaF2TFW8BZgnQPYxpDI7+rMVLivcd4V737TAQ=="], + + "@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.62.0", "", { "os": "linux", "cpu": "arm" }, "sha512-T1dMEQhXA/jkJ/jyMIw9IovK8bSUq7A8kLIlvZTb/6YIVsp2zLavr4F3oyllHWo7eIVJRyE5n3tUjQJEbE1IuQ=="], + + "@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.62.0", "", { "os": "linux", "cpu": "arm" }, "sha512-2as0LgT7qQpyceQq6VUJYnumUMUrgGQCWIiDIN9DE0/tglsk6o66uCB4f3djRawAltvfCNLyZZrsqbPA6inCsA=="], + + "@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.62.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-bVURMg+6eNN9C/yc0aVjooZcwTTtYF4YW3xta5pP0//r3o1V8gXEHXWCndj47w/HhwsFroZrFhR+6uQP5T0n0g=="], - "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.45.1", "", { "os": "android", "cpu": "arm64" }, "sha512-ujQ+sMXJkg4LRJaYreaVx7Z/VMgBBd89wGS4qMrdtfUFZ+TSY5Rs9asgjitLwzeIbhwdEhyj29zhst3L1lKsRQ=="], + "@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.62.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-Ful8pM/2yYI83PViWdFdpZhdI8HJ5qsXANe5atypbHDf+KIBBDsZsbyy8hbXnULVvW9NsTh5DHwbcBftyLTfiw=="], - "@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.45.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-FSncqHvqTm3lC6Y13xncsdOYfxGSLnP+73k815EfNmpewPs+EyM49haPS105Rh4aF5mJKywk9X0ogzLXZzN9lA=="], + "@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.62.0", "", { "os": "linux", "cpu": "none" }, "sha512-9Gp/DgrkzfUBmNPVTyPTvay+4xEP7M/clXpj3efXBcm6uTIVIgDg4rqUpqKXvLEuFRVuEpSAOkhgNeecvaZ4Cg=="], - "@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.45.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-2/vVn/husP5XI7Fsf/RlhDaQJ7x9zjvC81anIVbr4b/f0xtSmXQTFcGIQ/B1cXIYM6h2nAhJkdMHTnD7OtQ9Og=="], + "@rollup/rollup-linux-loong64-musl": ["@rollup/rollup-linux-loong64-musl@4.62.0", "", { "os": "linux", "cpu": "none" }, "sha512-m9tsJz54LUXkSYM8+8PG81B9IKK5r+2T0clMq4QrS16xFosufU7firBDAZEsDheDs7wTlP7h3++S7lMsU955HA=="], - "@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.45.1", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-4g1kaDxQItZsrkVTdYQ0bxu4ZIQ32cotoQbmsAnW1jAE4XCMbcBPDirX5fyUzdhVCKgPcrwWuucI8yrVRBw2+g=="], + "@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.62.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-3UvJ5PNVU16aJf6M3tFI24pWzAl2/ynfbyRN3ICyQajK1lSkrnVYNnLz3v04J32qKa0FczJc22zeToc0lr2A3w=="], - "@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.45.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-L/6JsfiL74i3uK1Ti2ZFSNsp5NMiM4/kbbGEcOCps99aZx3g8SJMO1/9Y0n/qKlWZfn6sScf98lEOUe2mBvW9A=="], + "@rollup/rollup-linux-ppc64-musl": ["@rollup/rollup-linux-ppc64-musl@4.62.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-vRWUAbYLGHBZS6Q8Msb2sfnf1fvJf+47t8l/TwOerM2qArzy+IeNMTHrYLHXh95h8MoatPHI5hhSZNs+mGXKPg=="], - "@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.45.1", "", { "os": "linux", "cpu": "arm" }, "sha512-RkdOTu2jK7brlu+ZwjMIZfdV2sSYHK2qR08FUWcIoqJC2eywHbXr0L8T/pONFwkGukQqERDheaGTeedG+rra6Q=="], + "@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.62.0", "", { "os": "linux", "cpu": "none" }, "sha512-c00T5SYENHAt86cfW47URaP3Us5vLC/4QO7GYud1G5VNRffCwwCuBspwqYrriuJB+5m0WFzClCn9wed0FBjKvg=="], - "@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.45.1", "", { "os": "linux", "cpu": "arm" }, "sha512-3kJ8pgfBt6CIIr1o+HQA7OZ9mp/zDk3ctekGl9qn/pRBgrRgfwiffaUmqioUGN9hv0OHv2gxmvdKOkARCtRb8Q=="], + "@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.62.0", "", { "os": "linux", "cpu": "none" }, "sha512-krrCDilhXOwFkSkO3Wm9I/f9H0L92XHHwy2fwxjukxIbh0dem8gZqOW5Y8BsHrpJv5qwlRBV+Wl4ZFyRWhUpwg=="], - "@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.45.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-k3dOKCfIVixWjG7OXTCOmDfJj3vbdhN0QYEqB+OuGArOChek22hn7Uy5A/gTDNAcCy5v2YcXRJ/Qcnm4/ma1xw=="], + "@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.62.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-7pfYFSTc4/rUC/FtAI0Qp6QthDBCIi6/AuP1xYqFk5vanI6KnL5dWKP60OM/05LOsbwTmIcvr6eXC4CJuJ75IA=="], - "@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.45.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-PmI1vxQetnM58ZmDFl9/Uk2lpBBby6B6rF4muJc65uZbxCs0EA7hhKCk2PKlmZKuyVSHAyIw3+/SiuMLxKxWog=="], + "@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.62.0", "", { "os": "linux", "cpu": "x64" }, "sha512-7SDIalKeIpG0Ifogbbdn58HmSotYMlf23K3dCJEmiVd9Fg36Vmni82iPQec27N3wY4Bvbxftkxz6vSx9OcouTg=="], - "@rollup/rollup-linux-loongarch64-gnu": ["@rollup/rollup-linux-loongarch64-gnu@4.45.1", "", { "os": "linux", "cpu": "none" }, "sha512-9UmI0VzGmNJ28ibHW2GpE2nF0PBQqsyiS4kcJ5vK+wuwGnV5RlqdczVocDSUfGX/Na7/XINRVoUgJyFIgipoRg=="], + "@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.62.0", "", { "os": "linux", "cpu": "x64" }, "sha512-eRZevouTH2i1HeAVLqJuLnt256krQkGY0TN6WsTmsIhuzbh457HuWDMakKwmi0Cjadux983CoSr8Lim2QhUIFw=="], - "@rollup/rollup-linux-powerpc64le-gnu": ["@rollup/rollup-linux-powerpc64le-gnu@4.45.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-7nR2KY8oEOUTD3pBAxIBBbZr0U7U+R9HDTPNy+5nVVHDXI4ikYniH1oxQz9VoB5PbBU1CZuDGHkLJkd3zLMWsg=="], + "@rollup/rollup-openbsd-x64": ["@rollup/rollup-openbsd-x64@4.62.0", "", { "os": "openbsd", "cpu": "x64" }, "sha512-3oVS7FLGa4U1qcvao9ylGxrjXZyUQqR8UwxEcnUEyPX53O/C/mKDZegNXTdHCP+h3e6ta/f1EN38Yif1mmZHYg=="], - "@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.45.1", "", { "os": "linux", "cpu": "none" }, "sha512-nlcl3jgUultKROfZijKjRQLUu9Ma0PeNv/VFHkZiKbXTBQXhpytS8CIj5/NfBeECZtY2FJQubm6ltIxm/ftxpw=="], + "@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.62.0", "", { "os": "none", "cpu": "arm64" }, "sha512-yTB9TgfWj5wHe5QgktAgXTLLot1gvEjl1NiPPAUiCs4oPrIWFl5V4nC3GrkNdj9LaAU4s94nVrGbGOCqUpyWsg=="], - "@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.45.1", "", { "os": "linux", "cpu": "none" }, "sha512-HJV65KLS51rW0VY6rvZkiieiBnurSzpzore1bMKAhunQiECPuxsROvyeaot/tcK3A3aGnI+qTHqisrpSgQrpgA=="], + "@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.62.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-5LOhoaesY3doG1c+ac/2JtgREpKoJr5bUHH8tKY0V8di7+uSV6BwLs2PlR0/yzefGOkR+wE7ZolZphHCsyG5Rw=="], - "@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.45.1", "", { "os": "linux", "cpu": "s390x" }, "sha512-NITBOCv3Qqc6hhwFt7jLV78VEO/il4YcBzoMGGNxznLgRQf43VQDae0aAzKiBeEPIxnDrACiMgbqjuihx08OOw=="], + "@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.62.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-yYkWHhmbhRTWTnWos5HC4GcPQfjlzzCNbM9e/+GXrLuaBXYA3qSDR9f0Vgufd5S8yX81U8jPKp7ZnAjZFMtRnw=="], - "@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.45.1", "", { "os": "linux", "cpu": "x64" }, "sha512-+E/lYl6qu1zqgPEnTrs4WysQtvc/Sh4fC2nByfFExqgYrqkKWp1tWIbe+ELhixnenSpBbLXNi6vbEEJ8M7fiHw=="], + "@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.62.0", "", { "os": "win32", "cpu": "x64" }, "sha512-SoTb6lPg25xZlA2ibwQ++ahCCnH+FP0qmEuafMJ4gznZKOlXioKEAeJLgCrqjM98ACziXM9V1amFjICVL4IFoA=="], - "@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.45.1", "", { "os": "linux", "cpu": "x64" }, "sha512-a6WIAp89p3kpNoYStITT9RbTbTnqarU7D8N8F2CV+4Cl9fwCOZraLVuVFvlpsW0SbIiYtEnhCZBPLoNdRkjQFw=="], + "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.62.0", "", { "os": "win32", "cpu": "x64" }, "sha512-5L+T1fMX4RIEBoZzT0+sQ0PhTS36NULFmMXtl1TZo44TMAROIMHbZufSOjVWt/Y622BtxgxtaNOokbTDvfsrZA=="], - "@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.45.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-T5Bi/NS3fQiJeYdGvRpTAP5P02kqSOpqiopwhj0uaXB6nzs5JVi2XMJb18JUSKhCOX8+UE1UKQufyD6Or48dJg=="], + "@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], - "@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.45.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-lxV2Pako3ujjuUe9jiU3/s7KSrDfH6IgTSQOnDWr9aJ92YsFd7EurmClK0ly/t8dzMkDtd04g60WX6yl0sGfdw=="], + "@tauri-apps/api": ["@tauri-apps/api@2.11.0", "", {}, "sha512-7CinYODhky9lmO23xHnUFv0Xt43fbtWMyxZcLcRBlFkcgXKuEirBvHpmtJ89YMhyeGcq20Wuc47Fa4XjyniywA=="], - "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.45.1", "", { "os": "win32", "cpu": "x64" }, "sha512-M/fKi4sasCdM8i0aWJjCSFm2qEnYRR8AMLG2kxp6wD13+tMGA4Z1tVAuHkNRjud5SW2EM3naLuK35w9twvf6aA=="], + "@tybys/wasm-util": ["@tybys/wasm-util@0.10.2", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg=="], - "@tauri-apps/api": ["@tauri-apps/api@2.7.0", "", {}, "sha512-v7fVE8jqBl8xJFOcBafDzXFc8FnicoH3j8o8DNNs0tHuEBmXUDqrCOAzMRX0UkfpwqZLqvrvK0GNQ45DfnoVDg=="], + "@types/chai": ["@types/chai@5.2.3", "", { "dependencies": { "@types/deep-eql": "*", "assertion-error": "^2.0.1" } }, "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA=="], - "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], + "@types/deep-eql": ["@types/deep-eql@4.0.2", "", {}, "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw=="], + + "@types/estree": ["@types/estree@1.0.9", "", {}, "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg=="], + + "@types/node": ["@types/node@25.9.3", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-603BddQMv3pUcr4U2dhujk83N2tTDVr/34wII2B6bJy6g+8WD6yUb11jszNs0gdi4PesVWl7ABt8nYMVpnLUcg=="], "@types/resolve": ["@types/resolve@1.20.2", "", {}, "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q=="], + "@vitest/expect": ["@vitest/expect@4.1.9", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "@types/chai": "^5.2.2", "@vitest/spy": "4.1.9", "@vitest/utils": "4.1.9", "chai": "^6.2.2", "tinyrainbow": "^3.1.0" } }, "sha512-vl/rYsUKcBr3SnQn166+XR5ZQcgMx3DQhFWdfli/cWpLnLUmbxZvyrJZotLFUryib+LtArYMSTJ5RbQ57ZqrlA=="], + + "@vitest/mocker": ["@vitest/mocker@4.1.9", "", { "dependencies": { "@vitest/spy": "4.1.9", "estree-walker": "^3.0.3", "magic-string": "^0.30.21" }, "peerDependencies": { "msw": "^2.4.9", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" }, "optionalPeers": ["msw", "vite"] }, "sha512-EVkXzBjrPGM+cK8/ANWgBrkUCfJfb38/EfTSO8h7pWvKkyPkpWxvR7BkD2MyItMF62C97zAEoqdpUixwR/e+Rw=="], + + "@vitest/pretty-format": ["@vitest/pretty-format@4.1.9", "", { "dependencies": { "tinyrainbow": "^3.1.0" } }, "sha512-s0iufns3iIFitdgm+YR7g1whCAaGtXz459VS9/PqyKDEEFgYIhsHOQmXgIgDuYCt7DeQmiZT0Qe2OA2p4ZPu5A=="], + + "@vitest/runner": ["@vitest/runner@4.1.9", "", { "dependencies": { "@vitest/utils": "4.1.9", "pathe": "^2.0.3" } }, "sha512-KXLMDtc7oe70+3mJfGrPUWPesswH+3sTxAMAMl8DG7I8IUQT4XW718dY5ID3vPUcmlu27CcKfY4P3h3I29SLJg=="], + + "@vitest/snapshot": ["@vitest/snapshot@4.1.9", "", { "dependencies": { "@vitest/pretty-format": "4.1.9", "@vitest/utils": "4.1.9", "magic-string": "^0.30.21", "pathe": "^2.0.3" } }, "sha512-Jc7RKGNBo8Z28WYIm0Niej4xdSPByRf6mU58VpHQkd6Zh05rlnA+twjbK5HyeIGHxrzsc3mJgS43uM0CZKzaIA=="], + + "@vitest/spy": ["@vitest/spy@4.1.9", "", {}, "sha512-fHpsS6mIi+PiEW+vcRVOMkX1oSaPKne3VOclSFICPcGOmfKgXPU5iAah+wcNcj2xPrCCmfq99IDGf+EojhhvhA=="], + + "@vitest/utils": ["@vitest/utils@4.1.9", "", { "dependencies": { "@vitest/pretty-format": "4.1.9", "convert-source-map": "^2.0.0", "tinyrainbow": "^3.1.0" } }, "sha512-A51o8ymO5PpqlWNnBP9ZHPXDIpuMtTLlGSjN7la4US+LJzoUMyhwjA5QXlm39JexgwHKW4Xjs8Z2d3dLCXOeuA=="], + "acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="], + "assertion-error": ["assertion-error@2.0.1", "", {}, "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA=="], + "buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="], + "chai": ["chai@6.2.2", "", {}, "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg=="], + "commander": ["commander@2.20.3", "", {}, "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="], + "convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="], + "deepmerge": ["deepmerge@4.3.1", "", {}, "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A=="], + "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], + + "es-module-lexer": ["es-module-lexer@2.1.0", "", {}, "sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ=="], + "estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="], + "expect-type": ["expect-type@1.3.0", "", {}, "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA=="], + + "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], + "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="], @@ -101,32 +196,98 @@ "is-module": ["is-module@1.0.0", "", {}, "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g=="], + "lightningcss": ["lightningcss@1.32.0", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.32.0", "lightningcss-darwin-arm64": "1.32.0", "lightningcss-darwin-x64": "1.32.0", "lightningcss-freebsd-x64": "1.32.0", "lightningcss-linux-arm-gnueabihf": "1.32.0", "lightningcss-linux-arm64-gnu": "1.32.0", "lightningcss-linux-arm64-musl": "1.32.0", "lightningcss-linux-x64-gnu": "1.32.0", "lightningcss-linux-x64-musl": "1.32.0", "lightningcss-win32-arm64-msvc": "1.32.0", "lightningcss-win32-x64-msvc": "1.32.0" } }, "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ=="], + + "lightningcss-android-arm64": ["lightningcss-android-arm64@1.32.0", "", { "os": "android", "cpu": "arm64" }, "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg=="], + + "lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.32.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ=="], + + "lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.32.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w=="], + + "lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.32.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig=="], + + "lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.32.0", "", { "os": "linux", "cpu": "arm" }, "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw=="], + + "lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.32.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ=="], + + "lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.32.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg=="], + + "lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.32.0", "", { "os": "linux", "cpu": "x64" }, "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA=="], + + "lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.32.0", "", { "os": "linux", "cpu": "x64" }, "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg=="], + + "lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.32.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw=="], + + "lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.32.0", "", { "os": "win32", "cpu": "x64" }, "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q=="], + + "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], + + "nanoid": ["nanoid@3.3.12", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ=="], + + "obug": ["obug@2.1.3", "", {}, "sha512-9miFgM2OFba7hB+pRgvtV84pYTBaoTHohvmIgiRt6dRIzbwEOIaNaP+dIlGs2fNFoB0SeISs0Jz5WFVRid6Xyg=="], + "path-parse": ["path-parse@1.0.7", "", {}, "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="], - "picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="], + "pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], + + "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], + + "picomatch": ["picomatch@4.0.4", "", {}, "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="], - "randombytes": ["randombytes@2.1.0", "", { "dependencies": { "safe-buffer": "^5.1.0" } }, "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ=="], + "postcss": ["postcss@8.5.15", "", { "dependencies": { "nanoid": "^3.3.12", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A=="], "resolve": ["resolve@1.22.10", "", { "dependencies": { "is-core-module": "^2.16.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w=="], - "rollup": ["rollup@4.45.1", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.45.1", "@rollup/rollup-android-arm64": "4.45.1", "@rollup/rollup-darwin-arm64": "4.45.1", "@rollup/rollup-darwin-x64": "4.45.1", "@rollup/rollup-freebsd-arm64": "4.45.1", "@rollup/rollup-freebsd-x64": "4.45.1", "@rollup/rollup-linux-arm-gnueabihf": "4.45.1", "@rollup/rollup-linux-arm-musleabihf": "4.45.1", "@rollup/rollup-linux-arm64-gnu": "4.45.1", "@rollup/rollup-linux-arm64-musl": "4.45.1", "@rollup/rollup-linux-loongarch64-gnu": "4.45.1", "@rollup/rollup-linux-powerpc64le-gnu": "4.45.1", "@rollup/rollup-linux-riscv64-gnu": "4.45.1", "@rollup/rollup-linux-riscv64-musl": "4.45.1", "@rollup/rollup-linux-s390x-gnu": "4.45.1", "@rollup/rollup-linux-x64-gnu": "4.45.1", "@rollup/rollup-linux-x64-musl": "4.45.1", "@rollup/rollup-win32-arm64-msvc": "4.45.1", "@rollup/rollup-win32-ia32-msvc": "4.45.1", "@rollup/rollup-win32-x64-msvc": "4.45.1", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-4iya7Jb76fVpQyLoiVpzUrsjQ12r3dM7fIVz+4NwoYvZOShknRmiv+iu9CClZml5ZLGb0XMcYLutK6w9tgxHDw=="], + "rolldown": ["rolldown@1.0.3", "", { "dependencies": { "@oxc-project/types": "=0.133.0", "@rolldown/pluginutils": "^1.0.0" }, "optionalDependencies": { "@rolldown/binding-android-arm64": "1.0.3", "@rolldown/binding-darwin-arm64": "1.0.3", "@rolldown/binding-darwin-x64": "1.0.3", "@rolldown/binding-freebsd-x64": "1.0.3", "@rolldown/binding-linux-arm-gnueabihf": "1.0.3", "@rolldown/binding-linux-arm64-gnu": "1.0.3", "@rolldown/binding-linux-arm64-musl": "1.0.3", "@rolldown/binding-linux-ppc64-gnu": "1.0.3", "@rolldown/binding-linux-s390x-gnu": "1.0.3", "@rolldown/binding-linux-x64-gnu": "1.0.3", "@rolldown/binding-linux-x64-musl": "1.0.3", "@rolldown/binding-openharmony-arm64": "1.0.3", "@rolldown/binding-wasm32-wasi": "1.0.3", "@rolldown/binding-win32-arm64-msvc": "1.0.3", "@rolldown/binding-win32-x64-msvc": "1.0.3" }, "bin": { "rolldown": "./bin/cli.mjs" } }, "sha512-i00lAJ2ks1BYr7rjNjKC7BcqAS7nVfiT3QX1SI5aY+AFHblCmaUf9OE9dbdzDvW6dJxbi2ZCZiy9v3CcwOiX3g=="], - "safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], + "rollup": ["rollup@4.62.0", "", { "dependencies": { "@types/estree": "1.0.9" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.62.0", "@rollup/rollup-android-arm64": "4.62.0", "@rollup/rollup-darwin-arm64": "4.62.0", "@rollup/rollup-darwin-x64": "4.62.0", "@rollup/rollup-freebsd-arm64": "4.62.0", "@rollup/rollup-freebsd-x64": "4.62.0", "@rollup/rollup-linux-arm-gnueabihf": "4.62.0", "@rollup/rollup-linux-arm-musleabihf": "4.62.0", "@rollup/rollup-linux-arm64-gnu": "4.62.0", "@rollup/rollup-linux-arm64-musl": "4.62.0", "@rollup/rollup-linux-loong64-gnu": "4.62.0", "@rollup/rollup-linux-loong64-musl": "4.62.0", "@rollup/rollup-linux-ppc64-gnu": "4.62.0", "@rollup/rollup-linux-ppc64-musl": "4.62.0", "@rollup/rollup-linux-riscv64-gnu": "4.62.0", "@rollup/rollup-linux-riscv64-musl": "4.62.0", "@rollup/rollup-linux-s390x-gnu": "4.62.0", "@rollup/rollup-linux-x64-gnu": "4.62.0", "@rollup/rollup-linux-x64-musl": "4.62.0", "@rollup/rollup-openbsd-x64": "4.62.0", "@rollup/rollup-openharmony-arm64": "4.62.0", "@rollup/rollup-win32-arm64-msvc": "4.62.0", "@rollup/rollup-win32-ia32-msvc": "4.62.0", "@rollup/rollup-win32-x64-gnu": "4.62.0", "@rollup/rollup-win32-x64-msvc": "4.62.0", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-nc72Wgq62I7rtDV4izT5/aaS0zxy3kttkinf9586ApknY3jZO9NYsmtc24fUckA0X7Q2v+ML4a15pdUlV5V/jA=="], - "serialize-javascript": ["serialize-javascript@6.0.2", "", { "dependencies": { "randombytes": "^2.1.0" } }, "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g=="], + "serialize-javascript": ["serialize-javascript@7.0.5", "", {}, "sha512-F4LcB0UqUl1zErq+1nYEEzSHJnIwb3AF2XWB94b+afhrekOUijwooAYqFyRbjYkm2PAKBabx6oYv/xDxNi8IBw=="], + + "siginfo": ["siginfo@2.0.0", "", {}, "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g=="], "smob": ["smob@1.5.0", "", {}, "sha512-g6T+p7QO8npa+/hNx9ohv1E5pVCmWrVCUzUXJyLdMmftX6ER0oiWY/w9knEonLpnOp6b6FenKnMfR8gqwWdwig=="], "source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], + "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], + "source-map-support": ["source-map-support@0.5.21", "", { "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" } }, "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w=="], + "stackback": ["stackback@0.0.2", "", {}, "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw=="], + + "std-env": ["std-env@4.1.0", "", {}, "sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ=="], + "supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="], "terser": ["terser@5.43.1", "", { "dependencies": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.14.0", "commander": "^2.20.0", "source-map-support": "~0.5.20" }, "bin": { "terser": "bin/terser" } }, "sha512-+6erLbBm0+LROX2sPXlUYx/ux5PyE9K/a92Wrt6oA+WDAoFTdpHE5tCYCI5PNzq2y8df4rA+QgHLJuR4jNymsg=="], + "tinybench": ["tinybench@2.9.0", "", {}, "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg=="], + + "tinyexec": ["tinyexec@1.2.4", "", {}, "sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg=="], + + "tinyglobby": ["tinyglobby@0.2.17", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" } }, "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g=="], + + "tinyrainbow": ["tinyrainbow@3.1.0", "", {}, "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw=="], + "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - "typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="], + "typescript": ["typescript@6.0.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw=="], + + "undici-types": ["undici-types@7.24.6", "", {}, "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg=="], + + "vite": ["vite@8.0.16", "", { "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.4", "postcss": "^8.5.15", "rolldown": "1.0.3", "tinyglobby": "^0.2.17" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.1.18", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "@vitejs/devtools", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-h9bXPmJichP5fLmVQo3PyaGSDE2n3aPuomeAlVRm0JLmt4rY6zmPKd59HYI4LNW8oTK7tlTsuC7l/m7awx9Jcw=="], + + "vitest": ["vitest@4.1.9", "", { "dependencies": { "@vitest/expect": "4.1.9", "@vitest/mocker": "4.1.9", "@vitest/pretty-format": "4.1.9", "@vitest/runner": "4.1.9", "@vitest/snapshot": "4.1.9", "@vitest/spy": "4.1.9", "@vitest/utils": "4.1.9", "es-module-lexer": "^2.0.0", "expect-type": "^1.3.0", "magic-string": "^0.30.21", "obug": "^2.1.1", "pathe": "^2.0.3", "picomatch": "^4.0.3", "std-env": "^4.0.0-rc.1", "tinybench": "^2.9.0", "tinyexec": "^1.0.2", "tinyglobby": "^0.2.15", "tinyrainbow": "^3.1.0", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", "why-is-node-running": "^2.3.0" }, "peerDependencies": { "@edge-runtime/vm": "*", "@opentelemetry/api": "^1.9.0", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", "@vitest/browser-playwright": "4.1.9", "@vitest/browser-preview": "4.1.9", "@vitest/browser-webdriverio": "4.1.9", "@vitest/coverage-istanbul": "4.1.9", "@vitest/coverage-v8": "4.1.9", "@vitest/ui": "4.1.9", "happy-dom": "*", "jsdom": "*" }, "optionalPeers": ["@edge-runtime/vm", "@opentelemetry/api", "@types/node", "@vitest/browser-playwright", "@vitest/browser-preview", "@vitest/browser-webdriverio", "@vitest/coverage-istanbul", "@vitest/coverage-v8", "@vitest/ui", "happy-dom", "jsdom"], "bin": { "vitest": "./vitest.mjs" } }, "sha512-nE3/LEyc0z87uHYLZebqCUOaJr2hdtuPp7BQ4BosVFnfltxgAvMG08NyrSGlPpOUWvR27c5flSmYFTNr78L9GQ=="], + + "why-is-node-running": ["why-is-node-running@2.3.0", "", { "dependencies": { "siginfo": "^2.0.0", "stackback": "0.0.2" }, "bin": { "why-is-node-running": "cli.js" } }, "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w=="], + + "@jridgewell/gen-mapping/@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.4", "", {}, "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw=="], + + "@jridgewell/trace-mapping/@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.4", "", {}, "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw=="], + + "@rollup/pluginutils/@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], + + "@vitest/mocker/estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="], } } diff --git a/examples/tauri-app/README.md b/examples/tauri-app/README.md new file mode 100644 index 0000000..1e22283 --- /dev/null +++ b/examples/tauri-app/README.md @@ -0,0 +1,10 @@ +# Vnidrop Share Example + +This app exercises `@vnidrop/tauri-plugin-share` from a Tauri 2 frontend. + +```sh +npm install +npm run tauri dev +``` + +The example includes `vnidrop-share:allow-cleanup` because cleanup is intentionally not part of the plugin's default permission set. diff --git a/examples/tauri-app/index.html b/examples/tauri-app/index.html new file mode 100644 index 0000000..52ab86e --- /dev/null +++ b/examples/tauri-app/index.html @@ -0,0 +1,12 @@ + + + + + + Vnidrop Share Example + + +
+ + + diff --git a/examples/tauri-app/jsconfig.json b/examples/tauri-app/jsconfig.json new file mode 100644 index 0000000..99a8f59 --- /dev/null +++ b/examples/tauri-app/jsconfig.json @@ -0,0 +1,9 @@ +{ + "compilerOptions": { + "checkJs": true, + "module": "esnext", + "moduleResolution": "bundler", + "target": "es2021" + }, + "include": ["src/**/*.js"] +} diff --git a/examples/tauri-app/package-lock.json b/examples/tauri-app/package-lock.json new file mode 100644 index 0000000..5dc5584 --- /dev/null +++ b/examples/tauri-app/package-lock.json @@ -0,0 +1,1637 @@ +{ + "name": "vnidrop-share-example", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "vnidrop-share-example", + "version": "0.1.0", + "dependencies": { + "@tauri-apps/api": "^2.11.0", + "@vnidrop/tauri-plugin-share": "file:../../" + }, + "devDependencies": { + "@tauri-apps/cli": "^2.8.4", + "vite": "^8.0.16" + } + }, + "../..": { + "name": "@vnidrop/tauri-plugin-share", + "version": "0.2.2", + "dependencies": { + "@tauri-apps/api": "^2.11.0" + }, + "devDependencies": { + "@rollup/plugin-node-resolve": "^16.0.3", + "@rollup/plugin-terser": "^1.0.0", + "@rollup/plugin-typescript": "^12.3.0", + "@types/node": "^25.9.3", + "rollup": "^4.62.0", + "tslib": "^2.8.1", + "typescript": "^6.0.3", + "vitest": "^4.1.9" + } + }, + "node_modules/@emnapi/core": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", + "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.1", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", + "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", + "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.1.tgz", + "integrity": "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.1.tgz", + "integrity": "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.1.tgz", + "integrity": "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.1.tgz", + "integrity": "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.1.tgz", + "integrity": "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.1.tgz", + "integrity": "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.1.tgz", + "integrity": "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.1.tgz", + "integrity": "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.1.tgz", + "integrity": "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.1.tgz", + "integrity": "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.1.tgz", + "integrity": "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.1.tgz", + "integrity": "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.1.tgz", + "integrity": "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.1.tgz", + "integrity": "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.1.tgz", + "integrity": "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.1.tgz", + "integrity": "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.1.tgz", + "integrity": "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.1.tgz", + "integrity": "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.1.tgz", + "integrity": "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.1.tgz", + "integrity": "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.1.tgz", + "integrity": "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.1.tgz", + "integrity": "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.1.tgz", + "integrity": "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.1.tgz", + "integrity": "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.1.tgz", + "integrity": "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.1.tgz", + "integrity": "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.5.tgz", + "integrity": "sha512-AWPoBRJ9tsnVhor4sjO7rkni+7p+2IAEFj6cx06UgP10jkQHqay/36uRV/bFkgrh18D9vb4cr8Q0Pthskgzy+Q==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@tybys/wasm-util": "^0.10.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.133.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.133.0.tgz", + "integrity": "sha512-KzkdCd6Uxqnf6l3HOw1xfatAlUURA0g14cvBYFyJ5SaNOQbOUvBr9PKArcPcrNIeRsBdgcUzOGrhKveVpvOIGA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.3.tgz", + "integrity": "sha512-454rs7jHngixp/NMxd5srYD57OnzSlZ/eFTETjORQHLwJG1lRtmNOJcBerZlfu4GjKqeq8aCCIQrMdHyhI51Hw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.3.tgz", + "integrity": "sha512-PcAhP+ynjURNyy8SKGl5DQP94aGuB/7JrXJb/t7P+hanXvQVMWzUvRRhBAcg/lNRadBhoUPqSoP4xw5tR/KBEA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.3.tgz", + "integrity": "sha512-9YpfeUvSE2RS7wysJ81uOZkXJz7f7Q55H2Gvp3VEw/EsahqDtrphrZ0EwDLK5vvKOzaCrBsjF8JmnMLcUt78Gg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.3.tgz", + "integrity": "sha512-yB1IlAsSNHncV6SCTL27/MVGR5htvQsoGxIv5KMGXALp+Ll1wYsn+x98M9MW7qa+NdSbvrrY7ANI4wLJ0n1e6g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.3.tgz", + "integrity": "sha512-Yi30IVAAfLUCy2MseFjbB1jAMDl1VMCAas5StnYp8da9+CKvMd2H2cbEjWcw5NPaPqzvYkVIaF1nNUG+b7u/sw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.3.tgz", + "integrity": "sha512-jsO7R8To+AdlYgUmN5sHSCZbfhtMBkO0WUx8iORQnPcMMdgr7qM2DQmMwgabs3GhNztdmoKkMKQFHD6DTMCIQw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.3.tgz", + "integrity": "sha512-VWkUHwWriDciit80wleYwKILoR/KMvxh/IdwS/paX+ZgpuRpCrKLUdadJbc0NpBEiyhpYawsJ73j9aCvOH+f7Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.3.tgz", + "integrity": "sha512-5f1laC0SlIR0yDbFCd8acUhvJIag6N3zC5P7oUPN6wX0aOma+uKJ0wBDH5aq7I1PVI2ttTlhJwzwRIBnLiSGEg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.3.tgz", + "integrity": "sha512-Iq4ko0r4XsgbrF/LunNgHtAGLRRVE2kXonAXQ/MV0mC6jQpMOhW1SvtZja2EhC/kd05++bP78dsqBeIQyYJ6Yg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.3.tgz", + "integrity": "sha512-B8m6tD5+/N5FeNQFbKlLA/2yVq9ycQP1SeedyEYYKWBNR3ZQbkvIUcNnDNM03lO1l5F2roiiFJGgvoLLyZXtSg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.3.tgz", + "integrity": "sha512-pSdpdUJHkuCxun9LE7jvgUB9qsRgaiyNNCX7m/AvHTcq67AiT/Yhoxvw5zPfhrM8k/BfP8ce/hMOpthKDpEUow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.3.tgz", + "integrity": "sha512-OXXS3RKJgX2uLwM+gYyuH5omcH8fL1LJs96pZGgtetVCahON57+d4SJHzTgZiOjxgGkSnpXpOsWuPDGAKAigEg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.3.tgz", + "integrity": "sha512-JTtb8BWFynicNSoPrehsCzBtOKjZ6jhMiPFEmOiuXg1Fl8dn2KHQob+GuPSGR0dryQa1PQJbzjF3dqO/whhjLg==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "1.10.0", + "@emnapi/runtime": "1.10.0", + "@napi-rs/wasm-runtime": "^1.1.4" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.3.tgz", + "integrity": "sha512-gEdFFEN70A/jxb2svrWsN3aDL7OUtmvlOy+6fa2jxG8K0wQ1ZbdeLGnidov6Yu5/733dI5ySfzFlQ/cb0bSz1g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.3.tgz", + "integrity": "sha512-eXB7CHuaQdqmJcc3koCNtNPmT/bj2gc999kUFgBxG8Ac0NdgXc4rkCHhqrgrhN3zddvvvrgzj1e90SuSfmyIXA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tauri-apps/api": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/api/-/api-2.11.0.tgz", + "integrity": "sha512-7CinYODhky9lmO23xHnUFv0Xt43fbtWMyxZcLcRBlFkcgXKuEirBvHpmtJ89YMhyeGcq20Wuc47Fa4XjyniywA==", + "license": "Apache-2.0 OR MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/tauri" + } + }, + "node_modules/@tauri-apps/cli": { + "version": "2.11.2", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli/-/cli-2.11.2.tgz", + "integrity": "sha512-bk3HemqvGRoy+5D/dVMUQHKMYLglD0jVnMm/0iGMH6ufZ+p8r14m6BpIixwij3PBvZdvORUp1YifTD8QxVZ1Nw==", + "dev": true, + "license": "Apache-2.0 OR MIT", + "bin": { + "tauri": "tauri.js" + }, + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/tauri" + }, + "optionalDependencies": { + "@tauri-apps/cli-darwin-arm64": "2.11.2", + "@tauri-apps/cli-darwin-x64": "2.11.2", + "@tauri-apps/cli-linux-arm-gnueabihf": "2.11.2", + "@tauri-apps/cli-linux-arm64-gnu": "2.11.2", + "@tauri-apps/cli-linux-arm64-musl": "2.11.2", + "@tauri-apps/cli-linux-riscv64-gnu": "2.11.2", + "@tauri-apps/cli-linux-x64-gnu": "2.11.2", + "@tauri-apps/cli-linux-x64-musl": "2.11.2", + "@tauri-apps/cli-win32-arm64-msvc": "2.11.2", + "@tauri-apps/cli-win32-ia32-msvc": "2.11.2", + "@tauri-apps/cli-win32-x64-msvc": "2.11.2" + } + }, + "node_modules/@tauri-apps/cli-darwin-arm64": { + "version": "2.11.2", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-arm64/-/cli-darwin-arm64-2.11.2.tgz", + "integrity": "sha512-+4UZzLt+eOAEQCwgd+TqKgyUJMrvx+BgdXLLaqJYmPqzP+nE6YZr/hY6CWLYGQb8jFn99jEkmC6uA3tNvamA1w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-darwin-x64": { + "version": "2.11.2", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-x64/-/cli-darwin-x64-2.11.2.tgz", + "integrity": "sha512-VjYYtZUPqDMLutSfJEyxFE3Bz+DPi7c8wC3imckgvciLDZLq4qwKJxBicg0BXGhXjJsl8vKWgWRFNMPELQ+Xyg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-arm-gnueabihf": { + "version": "2.11.2", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm-gnueabihf/-/cli-linux-arm-gnueabihf-2.11.2.tgz", + "integrity": "sha512-yMemD6f4i95AQriS8EazyOFzbE34yjnP16i3IOzpHGQvBoy2DjypFMFBq0NtPuITURv/cOGguRtHR5d79/9CSA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-arm64-gnu": { + "version": "2.11.2", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-gnu/-/cli-linux-arm64-gnu-2.11.2.tgz", + "integrity": "sha512-cgI91D2wL8GSgoWwZXDqt+DwnuZCP2/bz03QAE4TrhgAKIsrB4hX26W/H1EONPUUNkqrsgeCD0wU6pcNjV/5kw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-arm64-musl": { + "version": "2.11.2", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.11.2.tgz", + "integrity": "sha512-X1rm0BERqAAggtYTESSgXrS3sz4Sb/OiPiz54UqISlXW+GkR3vNIGnsy/lejNmoXGVqri3Q53BCfQiclOIyRPw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-riscv64-gnu": { + "version": "2.11.2", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-riscv64-gnu/-/cli-linux-riscv64-gnu-2.11.2.tgz", + "integrity": "sha512-usbMLJbT3KtkOrBMDVeGYNM35aTHXx38SJSzTMSqqjeUIOQ+iVPjb2yAGNAE+KqmBbAx4FOFIyMeKXx2M/JKGQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-x64-gnu": { + "version": "2.11.2", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-gnu/-/cli-linux-x64-gnu-2.11.2.tgz", + "integrity": "sha512-Ru4gwJKPG0ctVGchRGpRup4Y4lW2SSfFnrbQcyHhCliKy4g8Qz97TrUgCur4CbWyAgKxvGh3SjrkA0LDYzDGiw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-x64-musl": { + "version": "2.11.2", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-musl/-/cli-linux-x64-musl-2.11.2.tgz", + "integrity": "sha512-eUm7T6clN1MMmNSRQ9gaWsQdyehQx2Gmn5hht/QUlqZQI/qcP2OJK5dnaxqwFzCr2HdsEo9ydxaqcS1oJzMvUw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-win32-arm64-msvc": { + "version": "2.11.2", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-arm64-msvc/-/cli-win32-arm64-msvc-2.11.2.tgz", + "integrity": "sha512-HeeZW80jU+gVTOEX4X/hC6NVSAdDVXajwP5fxIZ/3z9WvUC7qrudX2GMTilYq6Dg0e0sk0XgsAJD1hZ5wPBXUA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-win32-ia32-msvc": { + "version": "2.11.2", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-ia32-msvc/-/cli-win32-ia32-msvc-2.11.2.tgz", + "integrity": "sha512-YhjQNZcXfbkCLyazSv1nPnJ9iRFE1wm6kc51FDbU10/Dk09io+6PAGMLjkxnX2GdM0qMnDmTjstY8mTDVvtKeA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-win32-x64-msvc": { + "version": "2.11.2", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-x64-msvc/-/cli-win32-x64-msvc-2.11.2.tgz", + "integrity": "sha512-d2JchlFIpZevZVReyqhQOekJmb1UH3rhZ5VX6sH3ty9ETE0TKQavpihvoScUXfKKpW6HZC0MrFGRU0ZtD+w3gA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz", + "integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@vnidrop/tauri-plugin-share": { + "resolved": "../..", + "link": true + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/esbuild": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.1.tgz", + "integrity": "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "peer": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.28.1", + "@esbuild/android-arm": "0.28.1", + "@esbuild/android-arm64": "0.28.1", + "@esbuild/android-x64": "0.28.1", + "@esbuild/darwin-arm64": "0.28.1", + "@esbuild/darwin-x64": "0.28.1", + "@esbuild/freebsd-arm64": "0.28.1", + "@esbuild/freebsd-x64": "0.28.1", + "@esbuild/linux-arm": "0.28.1", + "@esbuild/linux-arm64": "0.28.1", + "@esbuild/linux-ia32": "0.28.1", + "@esbuild/linux-loong64": "0.28.1", + "@esbuild/linux-mips64el": "0.28.1", + "@esbuild/linux-ppc64": "0.28.1", + "@esbuild/linux-riscv64": "0.28.1", + "@esbuild/linux-s390x": "0.28.1", + "@esbuild/linux-x64": "0.28.1", + "@esbuild/netbsd-arm64": "0.28.1", + "@esbuild/netbsd-x64": "0.28.1", + "@esbuild/openbsd-arm64": "0.28.1", + "@esbuild/openbsd-x64": "0.28.1", + "@esbuild/openharmony-arm64": "0.28.1", + "@esbuild/sunos-x64": "0.28.1", + "@esbuild/win32-arm64": "0.28.1", + "@esbuild/win32-ia32": "0.28.1", + "@esbuild/win32-x64": "0.28.1" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/nanoid": { + "version": "3.3.12", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", + "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.15", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", + "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.12", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/rolldown": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.3.tgz", + "integrity": "sha512-i00lAJ2ks1BYr7rjNjKC7BcqAS7nVfiT3QX1SI5aY+AFHblCmaUf9OE9dbdzDvW6dJxbi2ZCZiy9v3CcwOiX3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.133.0", + "@rolldown/pluginutils": "^1.0.0" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm64": "1.0.3", + "@rolldown/binding-darwin-arm64": "1.0.3", + "@rolldown/binding-darwin-x64": "1.0.3", + "@rolldown/binding-freebsd-x64": "1.0.3", + "@rolldown/binding-linux-arm-gnueabihf": "1.0.3", + "@rolldown/binding-linux-arm64-gnu": "1.0.3", + "@rolldown/binding-linux-arm64-musl": "1.0.3", + "@rolldown/binding-linux-ppc64-gnu": "1.0.3", + "@rolldown/binding-linux-s390x-gnu": "1.0.3", + "@rolldown/binding-linux-x64-gnu": "1.0.3", + "@rolldown/binding-linux-x64-musl": "1.0.3", + "@rolldown/binding-openharmony-arm64": "1.0.3", + "@rolldown/binding-wasm32-wasi": "1.0.3", + "@rolldown/binding-win32-arm64-msvc": "1.0.3", + "@rolldown/binding-win32-x64-msvc": "1.0.3" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD", + "optional": true + }, + "node_modules/vite": { + "version": "8.0.16", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.16.tgz", + "integrity": "sha512-h9bXPmJichP5fLmVQo3PyaGSDE2n3aPuomeAlVRm0JLmt4rY6zmPKd59HYI4LNW8oTK7tlTsuC7l/m7awx9Jcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "lightningcss": "^1.32.0", + "picomatch": "^4.0.4", + "postcss": "^8.5.15", + "rolldown": "1.0.3", + "tinyglobby": "^0.2.17" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.1.18", + "esbuild": "^0.27.0 || ^0.28.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + } + } +} diff --git a/examples/tauri-app/package.json b/examples/tauri-app/package.json new file mode 100644 index 0000000..435bf03 --- /dev/null +++ b/examples/tauri-app/package.json @@ -0,0 +1,23 @@ +{ + "name": "vnidrop-share-example", + "private": true, + "version": "0.1.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview", + "tauri": "tauri" + }, + "dependencies": { + "@tauri-apps/api": "^2.11.0", + "@vnidrop/tauri-plugin-share": "file:../../" + }, + "devDependencies": { + "@tauri-apps/cli": "^2.8.4", + "vite": "^8.0.16" + }, + "overrides": { + "esbuild": "0.28.1" + } +} diff --git a/examples/tauri-app/src-tauri/Cargo.toml b/examples/tauri-app/src-tauri/Cargo.toml new file mode 100644 index 0000000..acaf0ff --- /dev/null +++ b/examples/tauri-app/src-tauri/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "vnidrop-share-example" +version = "0.1.0" +description = "Vnidrop Share plugin example" +authors = ["Vnidrop"] +license = "" +repository = "" +edition = "2021" +rust-version = "1.77.2" + +[lib] +name = "tauri_app_lib" +crate-type = ["staticlib", "cdylib", "rlib"] + +[build-dependencies] +tauri-build = { version = "2.4.1", default-features = false, features = [] } + +[dependencies] +tauri = { version = "2.8.5", features = [] } +tauri-plugin-vnidrop-share = { path = "../../../" } diff --git a/examples/tauri-app/src-tauri/build.rs b/examples/tauri-app/src-tauri/build.rs new file mode 100644 index 0000000..d860e1e --- /dev/null +++ b/examples/tauri-app/src-tauri/build.rs @@ -0,0 +1,3 @@ +fn main() { + tauri_build::build() +} diff --git a/examples/tauri-app/src-tauri/capabilities/default.json b/examples/tauri-app/src-tauri/capabilities/default.json new file mode 100644 index 0000000..70f27a3 --- /dev/null +++ b/examples/tauri-app/src-tauri/capabilities/default.json @@ -0,0 +1,11 @@ +{ + "$schema": "../gen/schemas/desktop-schema.json", + "identifier": "default", + "description": "enables the share example permissions", + "windows": ["main"], + "permissions": [ + "core:default", + "vnidrop-share:default", + "vnidrop-share:allow-cleanup" + ] +} diff --git a/examples/tauri-app/src-tauri/icons/128x128.png b/examples/tauri-app/src-tauri/icons/128x128.png new file mode 100644 index 0000000..77e7d23 Binary files /dev/null and b/examples/tauri-app/src-tauri/icons/128x128.png differ diff --git a/examples/tauri-app/src-tauri/icons/128x128@2x.png b/examples/tauri-app/src-tauri/icons/128x128@2x.png new file mode 100644 index 0000000..0f7976f Binary files /dev/null and b/examples/tauri-app/src-tauri/icons/128x128@2x.png differ diff --git a/examples/tauri-app/src-tauri/icons/32x32.png b/examples/tauri-app/src-tauri/icons/32x32.png new file mode 100644 index 0000000..98fda06 Binary files /dev/null and b/examples/tauri-app/src-tauri/icons/32x32.png differ diff --git a/examples/tauri-app/src-tauri/icons/icon.icns b/examples/tauri-app/src-tauri/icons/icon.icns new file mode 100644 index 0000000..29d6685 Binary files /dev/null and b/examples/tauri-app/src-tauri/icons/icon.icns differ diff --git a/examples/tauri-app/src-tauri/icons/icon.ico b/examples/tauri-app/src-tauri/icons/icon.ico new file mode 100644 index 0000000..06c23c8 Binary files /dev/null and b/examples/tauri-app/src-tauri/icons/icon.ico differ diff --git a/examples/tauri-app/src-tauri/icons/icon.png b/examples/tauri-app/src-tauri/icons/icon.png new file mode 100644 index 0000000..d1756ce Binary files /dev/null and b/examples/tauri-app/src-tauri/icons/icon.png differ diff --git a/examples/tauri-app/src-tauri/src/lib.rs b/examples/tauri-app/src-tauri/src/lib.rs new file mode 100644 index 0000000..e1b3e84 --- /dev/null +++ b/examples/tauri-app/src-tauri/src/lib.rs @@ -0,0 +1,7 @@ +#[cfg_attr(mobile, tauri::mobile_entry_point)] +pub fn run() { + tauri::Builder::default() + .plugin(tauri_plugin_vnidrop_share::init()) + .run(tauri::generate_context!()) + .expect("error while running tauri application"); +} diff --git a/examples/tauri-app/src-tauri/src/main.rs b/examples/tauri-app/src-tauri/src/main.rs new file mode 100644 index 0000000..e5febf4 --- /dev/null +++ b/examples/tauri-app/src-tauri/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + tauri_app_lib::run() +} diff --git a/examples/tauri-app/src-tauri/tauri.conf.json b/examples/tauri-app/src-tauri/tauri.conf.json new file mode 100644 index 0000000..609dd19 --- /dev/null +++ b/examples/tauri-app/src-tauri/tauri.conf.json @@ -0,0 +1,28 @@ +{ + "$schema": "https://schema.tauri.app/config/2", + "productName": "Vnidrop Share Example", + "version": "0.1.0", + "identifier": "com.vnidrop.share-example", + "build": { + "beforeDevCommand": "npm run dev", + "beforeBuildCommand": "npm run build", + "devUrl": "http://localhost:1420", + "frontendDist": "../dist" + }, + "app": { + "windows": [ + { + "title": "Vnidrop Share Example", + "width": 860, + "height": 680 + } + ], + "security": { + "csp": null + } + }, + "bundle": { + "active": true, + "targets": "all" + } +} diff --git a/examples/tauri-app/src/main.js b/examples/tauri-app/src/main.js new file mode 100644 index 0000000..98ab1b9 --- /dev/null +++ b/examples/tauri-app/src/main.js @@ -0,0 +1,107 @@ +import { canShare, cleanup, share } from '@vnidrop/tauri-plugin-share' +import './style.css' + +const app = document.querySelector('#app') + +app.innerHTML = ` +
+
+
+

Vnidrop Share

+

Checking native share support...

+
+ +
+ +
+ + + + + + + + +
+ + +
+
+ + Ready +
+` + +const support = document.querySelector('#support') +const status = document.querySelector('#status') +const form = document.querySelector('#share-form') +const fileInput = document.querySelector('#files') + +function setStatus(message) { + status.textContent = message +} + +async function refreshSupport() { + const supported = await canShare() + support.textContent = supported ? 'Native sharing is available.' : 'Native sharing is unavailable on this platform.' +} + +form.addEventListener('submit', async event => { + event.preventDefault() + const data = new FormData(form) + + try { + await share({ + title: String(data.get('title') || ''), + text: String(data.get('text') || ''), + url: String(data.get('url') || ''), + }) + setStatus('Share sheet closed.') + } catch (error) { + setStatus(error instanceof Error ? error.message : String(error)) + } +}) + +document.querySelector('#share-file').addEventListener('click', async () => { + const files = Array.from(fileInput.files || []) + if (files.length === 0) { + setStatus('Select at least one local file first.') + return + } + + try { + await share({ + title: 'Selected local files', + text: 'Sharing local file content through temporary native files.', + files, + }) + setStatus(`Shared ${files.length} file${files.length === 1 ? '' : 's'}.`) + } catch (error) { + setStatus(error instanceof Error ? error.message : String(error)) + } +}) + +document.querySelector('#cleanup').addEventListener('click', async () => { + try { + await cleanup() + setStatus('Temporary files cleaned up.') + } catch (error) { + setStatus(error instanceof Error ? error.message : String(error)) + } +}) + +refreshSupport().catch(error => { + support.textContent = error instanceof Error ? error.message : String(error) +}) diff --git a/examples/tauri-app/src/style.css b/examples/tauri-app/src/style.css new file mode 100644 index 0000000..1486601 --- /dev/null +++ b/examples/tauri-app/src/style.css @@ -0,0 +1,124 @@ +:root { + color: #202124; + background: #f5f6f8; + font-family: + Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + font-size: 16px; + line-height: 1.5; +} + +* { + box-sizing: border-box; +} + +body { + min-width: 320px; + min-height: 100vh; + margin: 0; +} + +button, +input, +textarea { + font: inherit; +} + +button { + min-height: 40px; + border: 1px solid #1f6feb; + border-radius: 6px; + padding: 0 14px; + color: #ffffff; + background: #1f6feb; + cursor: pointer; +} + +button:hover { + background: #1158c7; +} + +.workspace { + width: min(920px, calc(100vw - 32px)); + margin: 0 auto; + padding: 28px 0; +} + +.toolbar { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: 16px; + margin-bottom: 20px; +} + +h1 { + margin: 0; + font-size: 28px; + line-height: 1.2; +} + +p { + margin: 6px 0 0; + color: #5f6368; +} + +.panel { + display: grid; + gap: 16px; + padding: 20px; + border: 1px solid #d9dde3; + border-radius: 8px; + background: #ffffff; +} + +label { + display: grid; + gap: 6px; + color: #3c4043; + font-weight: 600; +} + +input, +textarea { + width: 100%; + border: 1px solid #c5cad3; + border-radius: 6px; + padding: 10px 12px; + color: #202124; + background: #ffffff; +} + +textarea { + resize: vertical; +} + +.actions { + display: flex; + flex-wrap: wrap; + gap: 10px; +} + +.status { + display: block; + min-height: 44px; + margin-top: 16px; + border-left: 4px solid #188038; + padding: 10px 12px; + color: #1f1f1f; + background: #eef7ee; +} + +@media (max-width: 640px) { + .workspace { + width: min(100vw - 20px, 920px); + padding: 16px 0; + } + + .toolbar { + display: grid; + } + + button { + width: 100%; + } +} diff --git a/examples/tauri-app/vite.config.js b/examples/tauri-app/vite.config.js new file mode 100644 index 0000000..bb805be --- /dev/null +++ b/examples/tauri-app/vite.config.js @@ -0,0 +1,10 @@ +import { defineConfig } from 'vite' + +export default defineConfig({ + clearScreen: false, + server: { + port: 1420, + strictPort: true, + }, + envPrefix: ['VITE_', 'TAURI_'], +}) diff --git a/guest-js/index.ts b/guest-js/index.ts index abc5b4d..398afad 100644 --- a/guest-js/index.ts +++ b/guest-js/index.ts @@ -1,5 +1,14 @@ import { invoke } from "@tauri-apps/api/core"; +const MAX_FILES = 16; +const MAX_FILE_BYTES = 50 * 1024 * 1024; +const MAX_TOTAL_FILE_BYTES = 100 * 1024 * 1024; +const MAX_TEXT_BYTES = 64 * 1024; +const MAX_TITLE_BYTES = 1024; +const MAX_URL_BYTES = 4096; +const MAX_FILE_NAME_BYTES = 255; +const MAX_MIME_TYPE_BYTES = 255; + /** * Represents the content to be shared, similar to the Web Share API's ShareData dictionary. * @@ -28,6 +37,93 @@ function hasShareableContent(data: ShareData): boolean { return Boolean(data.text || data.url || (data.files && data.files.length > 0)); } +function byteLength(value: string): number { + return new TextEncoder().encode(value).length; +} + +function validateWebUrl(url: string): boolean { + if (url.trim() !== url || /[\s\u0000-\u001f\u007f]/u.test(url)) { + return false; + } + const schemeSeparator = url.indexOf("://"); + if (schemeSeparator === -1) { + return false; + } + const authority = url + .slice(schemeSeparator + 3) + .split(/[/?#]/u, 1)[0] + .split("@") + .pop() ?? ""; + if (!authority || authority.startsWith(":")) { + return false; + } + try { + const parsedUrl = new URL(url); + return ( + (parsedUrl.protocol === "http:" || parsedUrl.protocol === "https:") && + parsedUrl.host.length > 0 + ); + } catch { + return false; + } +} + +function validateShareData(data: ShareData): void { + if (data.text && byteLength(data.text) > MAX_TEXT_BYTES) { + throw new TypeError(`text exceeds the maximum length of ${MAX_TEXT_BYTES} bytes.`); + } + if (data.title && byteLength(data.title) > MAX_TITLE_BYTES) { + throw new TypeError(`title exceeds the maximum length of ${MAX_TITLE_BYTES} bytes.`); + } + if (data.url !== undefined) { + if (byteLength(data.url) > MAX_URL_BYTES) { + throw new TypeError(`url exceeds the maximum length of ${MAX_URL_BYTES} bytes.`); + } + if (data.url.length > 0 && !validateWebUrl(data.url)) { + throw new TypeError("Only http:// and https:// URLs can be shared as URLs."); + } + } + + if (!data.files || data.files.length === 0) { + return; + } + + if (data.files.length > MAX_FILES) { + throw new TypeError(`Too many files provided. Maximum is ${MAX_FILES}.`); + } + + const totalBytes = data.files.reduce((total, file) => total + file.size, 0); + if (totalBytes > MAX_TOTAL_FILE_BYTES) { + throw new TypeError( + `Total shared file size exceeds the maximum of ${MAX_TOTAL_FILE_BYTES} bytes.` + ); + } + + const oversizedFile = data.files.find((file) => file.size > MAX_FILE_BYTES); + if (oversizedFile) { + throw new TypeError( + `File '${oversizedFile.name}' exceeds the maximum size of ${MAX_FILE_BYTES} bytes.` + ); + } + + const oversizedName = data.files.find((file) => byteLength(file.name) > MAX_FILE_NAME_BYTES); + if (oversizedName) { + throw new TypeError( + `File name exceeds the maximum length of ${MAX_FILE_NAME_BYTES} bytes.` + ); + } + + const oversizedMimeType = data.files.find((file) => { + const type = file.type || "application/octet-stream"; + return byteLength(type) > MAX_MIME_TYPE_BYTES; + }); + if (oversizedMimeType) { + throw new TypeError( + `mime type exceeds the maximum length of ${MAX_MIME_TYPE_BYTES} bytes.` + ); + } +} + /** * Checks whether the native sharing capability is available for the given data. * @@ -50,6 +146,13 @@ export async function canShare(data?: ShareData): Promise { if (data && !hasShareableContent(data)) { return false; } + if (data) { + try { + validateShareData(data); + } catch { + return false; + } + } const result = (await invoke("plugin:vnidrop-share|can_share")) as { value: any; @@ -119,6 +222,7 @@ export async function share(data: ShareData): Promise { if (!hasShareableContent(data)) { throw new TypeError("No content provided to share."); } + validateShareData(data); const payload: any = { text: data.text, diff --git a/ios/Package.swift b/ios/Package.swift index dc7a3b0..1ba2a0a 100644 --- a/ios/Package.swift +++ b/ios/Package.swift @@ -1,8 +1,12 @@ // swift-tools-version:5.3 -// The swift-tools-version declares the minimum version of Swift required to build this package. - +import Foundation import PackageDescription +let useTauriStub = ProcessInfo.processInfo.environment["VNIDROP_SHARE_USE_TAURI_STUB"] == "1" +let tauriApiPath = !useTauriStub && FileManager.default.fileExists(atPath: "../.tauri/tauri-api/Package.swift") + ? "../.tauri/tauri-api" + : "test-support/tauri-api" + let package = Package( name: "tauri-plugin-vnidrop-share", platforms: [ @@ -17,16 +21,25 @@ let package = Package( targets: ["tauri-plugin-vnidrop-share"]), ], dependencies: [ - .package(name: "Tauri", path: "../.tauri/tauri-api") + .package(name: "Tauri", path: tauriApiPath) ], 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-vnidrop-share", dependencies: [ - .byName(name: "Tauri") + .byName(name: "Tauri"), + .byName(name: "ShareCore") + ], + path: "Sources", + exclude: ["ShareCore"]), + .target( + name: "ShareCore", + path: "Sources/ShareCore"), + .testTarget( + name: "SharePluginTests", + dependencies: [ + .byName(name: "ShareCore") ], - path: "Sources") + path: "Tests/SharePluginTests") ] ) diff --git a/ios/Sources/ShareCore/ShareModels.swift b/ios/Sources/ShareCore/ShareModels.swift new file mode 100644 index 0000000..588de8b --- /dev/null +++ b/ios/Sources/ShareCore/ShareModels.swift @@ -0,0 +1,25 @@ +public struct SharedFile: Decodable { + public let data: String + public let name: String + public let mimeType: String + + public init(data: String, name: String, mimeType: String) { + self.data = data + self.name = name + self.mimeType = mimeType + } +} + +public struct ShareOptions: Decodable { + public var text: String? + public var title: String? + public var url: String? + public var files: [SharedFile]? + + public init(text: String? = nil, title: String? = nil, url: String? = nil, files: [SharedFile]? = nil) { + self.text = text + self.title = title + self.url = url + self.files = files + } +} diff --git a/ios/Sources/ShareCore/ShareValidation.swift b/ios/Sources/ShareCore/ShareValidation.swift new file mode 100644 index 0000000..4f46957 --- /dev/null +++ b/ios/Sources/ShareCore/ShareValidation.swift @@ -0,0 +1,103 @@ +import Foundation + +public let maxFiles = 16 +public let maxFileBytes = 50 * 1024 * 1024 +public let maxTotalFileBytes = 100 * 1024 * 1024 +public let maxTextBytes = 64 * 1024 +public let maxTitleBytes = 1024 +public let maxURLBytes = 4096 +public let maxFileNameBytes = 255 +public let maxMimeTypeBytes = 255 + +public func validateShareOptions(_ args: ShareOptions) -> String? { + if let error = validateStringLength("text", args.text, maxBytes: maxTextBytes) { + return error + } + if let error = validateStringLength("title", args.title, maxBytes: maxTitleBytes) { + return error + } + if let error = validateStringLength("url", args.url, maxBytes: maxURLBytes) { + return error + } + + if let urlString = args.url, !urlString.isEmpty { + if let error = validateWebURL(urlString) { + return error + } + } + + guard let files = args.files else { + return nil + } + + if files.count > maxFiles { + return "Too many files provided. Maximum is \(maxFiles)." + } + + var totalEstimatedBytes = 0 + for file in files { + if let error = validateStringLength("file name", file.name, maxBytes: maxFileNameBytes) { + return error + } + if let error = validateStringLength("mime type", file.mimeType, maxBytes: maxMimeTypeBytes) { + return error + } + + guard let estimatedBytes = estimateBase64DecodedSize(file.data) else { + return "Invalid Base64 data." + } + if estimatedBytes > maxFileBytes { + return "File '\(file.name)' exceeds the maximum size of \(maxFileBytes) bytes." + } + totalEstimatedBytes += estimatedBytes + if totalEstimatedBytes > maxTotalFileBytes { + return "Total shared file size exceeds the maximum of \(maxTotalFileBytes) bytes." + } + } + + return nil +} + +public func estimateBase64DecodedSize(_ data: String) -> Int? { + let normalized = data.filter { !$0.isWhitespace } + if normalized.isEmpty { + return 0 + } + if normalized.count % 4 != 0 { + return nil + } + + let padding = normalized.reversed().prefix { $0 == "=" }.count + if padding > 2 { + return nil + } + + return (normalized.count / 4) * 3 - padding +} + +func validateWebURL(_ urlString: String) -> String? { + if urlString.trimmingCharacters(in: .whitespacesAndNewlines) != urlString || + urlString.unicodeScalars.contains(where: { CharacterSet.whitespacesAndNewlines.contains($0) || CharacterSet.controlCharacters.contains($0) }) { + return "Only well-formed http:// and https:// URLs can be shared as URLs." + } + + guard let components = URLComponents(string: urlString), + let scheme = components.scheme?.lowercased(), + scheme == "http" || scheme == "https", + let host = components.host, + !host.isEmpty else { + return "Only http:// and https:// URLs can be shared as URLs." + } + + return nil +} + +func validateStringLength(_ field: String, _ value: String?, maxBytes: Int) -> String? { + guard let value = value else { + return nil + } + if value.lengthOfBytes(using:.utf8) > maxBytes { + return "\(field) exceeds the maximum length of \(maxBytes) bytes." + } + return nil +} diff --git a/ios/Sources/SharePlugin.swift b/ios/Sources/SharePlugin.swift index a44d307..372130f 100644 --- a/ios/Sources/SharePlugin.swift +++ b/ios/Sources/SharePlugin.swift @@ -1,20 +1,9 @@ +#if canImport(UIKit) import Tauri +import ShareCore import UIKit import WebKit -struct SharedFile: Decodable { - let data: String - let name: String - let mimeType: String -} - -struct ShareOptions: Decodable { - var text: String? - var title: String? - var url: String? - var files: [SharedFile]? -} - @_cdecl("init_plugin_share") func initPlugin() -> Plugin { return SharePlugin() @@ -59,11 +48,16 @@ public class SharePlugin: Plugin { } let args = try invoke.parseArgs(ShareOptions.self) + if let validationError = validateShareOptions(args) { + invoke.reject(validationError) + return + } + var activityItems: [Any] = [] var createdFileURLs: [URL] = [] shareInProgress = true - if let urlString = args.url, let url = URL(string: urlString) { + if let urlString = args.url, !urlString.isEmpty, let url = URL(string: urlString) { activityItems.append(url) } if let text = args.text { @@ -78,6 +72,12 @@ public class SharePlugin: Plugin { invoke.reject("Invalid Base64 data for file: \(file.name)") return } + if decodedData.count > maxFileBytes { + cleanupTemporaryFiles(createdFileURLs) + _ = resetShareState() + invoke.reject("File '\(file.name)' exceeds the maximum size of \(maxFileBytes) bytes.") + return + } do { let tempFileURL = try createSafeTempFile(for: file.name) @@ -129,6 +129,9 @@ public class SharePlugin: Plugin { let safeDir = try getSafeShareDir() let sanitizedBaseName = URL(fileURLWithPath: untrustedFileName).lastPathComponent + if sanitizedBaseName.isEmpty { + throw NSError(domain: "SharePlugin", code: 1, userInfo: [NSLocalizedDescriptionKey: "Invalid filename: sanitized name is empty."]) + } let finalFileName = "\(UUID().uuidString)-\(sanitizedBaseName)" @@ -191,3 +194,4 @@ public class SharePlugin: Plugin { } } } +#endif diff --git a/ios/Tests/PluginTests/PluginTests.swift b/ios/Tests/PluginTests/PluginTests.swift deleted file mode 100644 index 4f8e9ac..0000000 --- a/ios/Tests/PluginTests/PluginTests.swift +++ /dev/null @@ -1,8 +0,0 @@ -import XCTest -@testable import ExamplePlugin - -final class ExamplePluginTests: XCTestCase { - func testExample() throws { - let plugin = ExamplePlugin() - } -} diff --git a/ios/Tests/SharePluginTests/ShareValidationTests.swift b/ios/Tests/SharePluginTests/ShareValidationTests.swift new file mode 100644 index 0000000..3d83a4c --- /dev/null +++ b/ios/Tests/SharePluginTests/ShareValidationTests.swift @@ -0,0 +1,53 @@ +import XCTest +@testable import ShareCore + +final class ShareValidationTests: XCTestCase { + func testAcceptsHttpAndHttpsURLs() { + XCTAssertNil(validateShareOptions(options(url: "https://example.com"))) + XCTAssertNil(validateShareOptions(options(url: "http://example.com"))) + } + + func testRejectsNonWebURLSchemes() { + XCTAssertNotNil(validateShareOptions(options(url: "file:///tmp/secret.db"))) + XCTAssertNotNil(validateShareOptions(options(url: "content://provider/item"))) + XCTAssertNotNil(validateShareOptions(options(url: "custom://value"))) + XCTAssertNotNil(validateShareOptions(options(url: "https:///missing-host"))) + XCTAssertNotNil(validateShareOptions(options(url: " https://example.com"))) + XCTAssertNotNil(validateShareOptions(options(url: "https://example.com\nhttps://evil.example"))) + } + + func testEstimatesBase64DecodedSize() { + XCTAssertEqual(estimateBase64DecodedSize("aGVsbG8="), 5) + XCTAssertEqual(estimateBase64DecodedSize("aGVs\nbG8="), 5) + } + + func testRejectsInvalidBase64Shape() { + XCTAssertNil(estimateBase64DecodedSize("abc")) + XCTAssertNil(estimateBase64DecodedSize("abcd===")) + } + + func testRejectsTooManyFilesAndOversizedText() { + let files = (0...maxFiles).map { + SharedFile(data: "aGVsbG8=", name: "report-\($0).txt", mimeType: "text/plain") + } + + XCTAssertNotNil(validateShareOptions(options(files: files))) + XCTAssertNotNil(validateShareOptions(options(text: String(repeating: "a", count: maxTextBytes + 1)))) + XCTAssertNotNil(validateShareOptions(options(files: [ + SharedFile( + data: "aGVsbG8=", + name: String(repeating: "a", count: maxFileNameBytes + 1), + mimeType: "text/plain" + ) + ]))) + } + + private func options( + text: String? = nil, + title: String? = nil, + url: String? = nil, + files: [SharedFile]? = nil + ) -> ShareOptions { + ShareOptions(text: text, title: title, url: url, files: files) + } +} diff --git a/ios/test-support/tauri-api/Package.swift b/ios/test-support/tauri-api/Package.swift new file mode 100644 index 0000000..b744f26 --- /dev/null +++ b/ios/test-support/tauri-api/Package.swift @@ -0,0 +1,12 @@ +// swift-tools-version:5.3 +import PackageDescription + +let package = Package( + name: "Tauri", + products: [ + .library(name: "Tauri", targets: ["Tauri"]) + ], + targets: [ + .target(name: "Tauri", path: "Sources/Tauri") + ] +) diff --git a/ios/test-support/tauri-api/Sources/Tauri/Tauri.swift b/ios/test-support/tauri-api/Sources/Tauri/Tauri.swift new file mode 100644 index 0000000..adf89ad --- /dev/null +++ b/ios/test-support/tauri-api/Sources/Tauri/Tauri.swift @@ -0,0 +1,29 @@ +import Foundation + +public typealias JsonObject = [String: Any?] + +open class Plugin: NSObject { + public let manager = PluginManager() + + public required override init() { + super.init() + } +} + +open class PluginManager: NSObject { + public var viewController: AnyObject? +} + +open class Invoke: NSObject { + open func parseArgs(_ type: T.Type) throws -> T { + throw NSError(domain: "TauriTestSupport", code: 1) + } + + open func resolve() {} + + open func resolve(_ value: JsonObject) {} + + open func resolve(_ value: T) {} + + open func reject(_ message: String) {} +} diff --git a/package.json b/package.json index 36efd20..e141218 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@vnidrop/tauri-plugin-share", - "version": "0.2.2", + "version": "1.0.0-rc", "author": "AbassHammed", "description": "A Tauri plugin for sharing content via the system's share dialog.", "type": "module", @@ -17,17 +17,30 @@ "README.md" ], "scripts": { - "build": "rollup -c" + "build": "rollup -c", + "test": "vitest run", + "test:android:jvm": "cd android && gradle testDebugUnitTest", + "test:ios": "cd ios && VNIDROP_SHARE_USE_TAURI_STUB=1 swift test", + "test:types": "tsc -p tsconfig.test.json --noEmit", + "example:install": "cd examples/tauri-app && npm install", + "example:build": "cd examples/tauri-app && npm run build", + "example:check": "npm run example:install && npm run example:build && cd examples/tauri-app/src-tauri && cargo check", + "check": "bun run build && bun run test && bun run test:types && cargo test" }, "dependencies": { - "@tauri-apps/api": ">=2.0.0-beta.6" + "@tauri-apps/api": "^2.11.0" }, "devDependencies": { - "@rollup/plugin-terser": "^0.4.4", - "@rollup/plugin-node-resolve": "^16.0.1", - "@rollup/plugin-typescript": "^12.0.0", - "rollup": "^4.9.6", - "typescript": "^5.3.3", - "tslib": "^2.6.2" + "@rollup/plugin-node-resolve": "^16.0.3", + "@rollup/plugin-terser": "^1.0.0", + "@rollup/plugin-typescript": "^12.3.0", + "@types/node": "^25.9.3", + "rollup": "^4.62.0", + "tslib": "^2.8.1", + "typescript": "^6.0.3", + "vitest": "^4.1.9" + }, + "overrides": { + "picomatch": "4.0.4" } } diff --git a/permissions/autogenerated/reference.md b/permissions/autogenerated/reference.md index 4d83dae..5a5a4a2 100644 --- a/permissions/autogenerated/reference.md +++ b/permissions/autogenerated/reference.md @@ -1,12 +1,11 @@ ## Default Permission -Default permissions for the Vnidrop Plugin, all commands can be invoked by default. +Default permissions for the Vnidrop Plugin, excluding maintenance cleanup by default. #### This default permission set includes the following: - `allow-share` - `allow-can-share` -- `allow-cleanup` ## Permission Table diff --git a/permissions/default.toml b/permissions/default.toml index ae64bb1..2b70fa6 100644 --- a/permissions/default.toml +++ b/permissions/default.toml @@ -1,3 +1,3 @@ [default] -description = "Default permissions for the Vnidrop Plugin, all commands can be invoked by default." -permissions = ["allow-share", "allow-can-share", "allow-cleanup"] +description = "Default permissions for the Vnidrop Plugin, excluding maintenance cleanup by default." +permissions = ["allow-share", "allow-can-share"] diff --git a/permissions/schemas/schema.json b/permissions/schemas/schema.json index 70f2ce4..e9c9933 100644 --- a/permissions/schemas/schema.json +++ b/permissions/schemas/schema.json @@ -331,10 +331,10 @@ "markdownDescription": "Denies the share command without any pre-configured scope." }, { - "description": "Default permissions for the Vnidrop Plugin, all commands can be invoked by default.\n#### This default permission set includes:\n\n- `allow-share`\n- `allow-can-share`\n- `allow-cleanup`", + "description": "Default permissions for the Vnidrop Plugin, excluding maintenance cleanup by default.\n#### This default permission set includes:\n\n- `allow-share`\n- `allow-can-share`", "type": "string", "const": "default", - "markdownDescription": "Default permissions for the Vnidrop Plugin, all commands can be invoked by default.\n#### This default permission set includes:\n\n- `allow-share`\n- `allow-can-share`\n- `allow-cleanup`" + "markdownDescription": "Default permissions for the Vnidrop Plugin, excluding maintenance cleanup by default.\n#### This default permission set includes:\n\n- `allow-share`\n- `allow-can-share`" } ] } diff --git a/src/commands.rs b/src/commands.rs index 736b1fb..32217f7 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -32,6 +32,7 @@ pub async fn share( "No content provided to share.".to_string(), )); } + options.validate()?; app.share().share(window, options, state) } diff --git a/src/desktop.rs b/src/desktop.rs index 2e6627c..07caaad 100644 --- a/src/desktop.rs +++ b/src/desktop.rs @@ -23,6 +23,7 @@ impl Share { "No content provided to share.".to_string(), )); } + options.validate()?; platform::share(window, options, state) } diff --git a/src/lib.rs b/src/lib.rs index 598d910..4a770b3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -19,7 +19,7 @@ //! //! You need to initialize the plugin in your `main.rs` or `lib.rs` to register the commands and set up state management. //! -//! ```rust +//! ```rust,ignore //! // src/main.rs //! fn main() { //! tauri::Builder::default() diff --git a/src/mobile.rs b/src/mobile.rs index b3a762f..9f0cb38 100644 --- a/src/mobile.rs +++ b/src/mobile.rs @@ -45,6 +45,7 @@ impl Share { "No content provided to share.".to_string(), )); } + payload.validate()?; self.0 .run_mobile_plugin("share", payload) diff --git a/src/models.rs b/src/models.rs index ab1c6e9..00b1034 100644 --- a/src/models.rs +++ b/src/models.rs @@ -1,5 +1,16 @@ use serde::{Deserialize, Serialize}; +use crate::Error; + +pub const MAX_FILES: usize = 16; +pub const MAX_FILE_BYTES: usize = 50 * 1024 * 1024; +pub const MAX_TOTAL_FILE_BYTES: usize = 100 * 1024 * 1024; +pub const MAX_TEXT_BYTES: usize = 64 * 1024; +pub const MAX_TITLE_BYTES: usize = 1024; +pub const MAX_URL_BYTES: usize = 4096; +pub const MAX_FILE_NAME_BYTES: usize = 255; +pub const MAX_MIME_TYPE_BYTES: usize = 255; + /// Represents a file to be shared, including its content, name, and MIME type. /// /// The `data` field holds the Base64 encoded content of the file. This approach @@ -36,7 +47,7 @@ pub struct SharedFile { /// { /// "files": [ /// { -/// "data": "data:image/png;base64,iVBORw0KGgo...", +/// "data": "iVBORw0KGgo...", /// "name": "my-image.png", /// "mimeType": "image/png" /// } @@ -75,6 +86,125 @@ impl ShareOptions { _ => None, } } + + /// Validates caller-provided payload size and URL scheme before native sharing. + pub fn validate(&self) -> Result<(), Error> { + validate_optional_string("text", self.text.as_deref(), MAX_TEXT_BYTES)?; + validate_optional_string("title", self.title.as_deref(), MAX_TITLE_BYTES)?; + + if let Some(url) = self.url.as_deref().filter(|value| !value.is_empty()) { + validate_optional_string("url", Some(url), MAX_URL_BYTES)?; + validate_web_url(url)?; + } + + if let Some(files) = self.files.as_ref() { + if files.len() > MAX_FILES { + return Err(Error::InvalidArgs(format!( + "Too many files provided. Maximum is {MAX_FILES}." + ))); + } + + let mut total_estimated_bytes = 0usize; + for file in files { + validate_optional_string("file name", Some(&file.name), MAX_FILE_NAME_BYTES)?; + validate_optional_string("mime type", Some(&file.mime_type), MAX_MIME_TYPE_BYTES)?; + + let estimated_bytes = estimate_base64_decoded_len(&file.data)?; + if estimated_bytes > MAX_FILE_BYTES { + return Err(Error::InvalidArgs(format!( + "File '{}' exceeds the maximum size of {} bytes.", + file.name, MAX_FILE_BYTES + ))); + } + total_estimated_bytes = total_estimated_bytes + .checked_add(estimated_bytes) + .ok_or_else(|| { + Error::InvalidArgs("Total shared file size is too large.".to_string()) + })?; + } + + if total_estimated_bytes > MAX_TOTAL_FILE_BYTES { + return Err(Error::InvalidArgs(format!( + "Total shared file size exceeds the maximum of {} bytes.", + MAX_TOTAL_FILE_BYTES + ))); + } + } + + Ok(()) + } +} + +fn validate_optional_string( + field: &str, + value: Option<&str>, + max_bytes: usize, +) -> Result<(), Error> { + if let Some(value) = value { + if value.len() > max_bytes { + return Err(Error::InvalidArgs(format!( + "{field} exceeds the maximum length of {max_bytes} bytes." + ))); + } + } + Ok(()) +} + +fn validate_web_url(url: &str) -> Result<(), Error> { + if url.trim() != url + || url + .chars() + .any(|char| char.is_control() || char.is_whitespace()) + { + return Err(Error::InvalidArgs( + "Only well-formed http:// and https:// URLs can be shared as URLs.".to_string(), + )); + } + + let Some((scheme, rest)) = url.split_once("://") else { + return Err(Error::InvalidArgs( + "Only http:// and https:// URLs can be shared as URLs.".to_string(), + )); + }; + + if scheme.eq_ignore_ascii_case("https") || scheme.eq_ignore_ascii_case("http") { + let authority = rest.split(['/', '?', '#']).next().unwrap_or_default(); + let host_port = authority.rsplit('@').next().unwrap_or(authority); + let host = if let Some(ipv6_end) = host_port.find(']') { + host_port.get(..=ipv6_end).unwrap_or_default() + } else { + host_port.split(':').next().unwrap_or_default() + }; + if !host.is_empty() && host != "[]" && !host.starts_with(':') { + return Ok(()); + } + } + + Err(Error::InvalidArgs( + "Only http:// and https:// URLs can be shared as URLs.".to_string(), + )) +} + +fn estimate_base64_decoded_len(data: &str) -> Result { + let len = data.len(); + if len == 0 { + return Ok(0); + } + if len % 4 != 0 { + return Err(Error::InvalidArgs("Invalid Base64 data.".to_string())); + } + + let padding = data + .as_bytes() + .iter() + .rev() + .take_while(|&&b| b == b'=') + .count(); + if padding > 2 { + return Err(Error::InvalidArgs("Invalid Base64 data.".to_string())); + } + + Ok((len / 4) * 3 - padding) } /// The result type for the `can_share` command. @@ -90,7 +220,7 @@ pub struct CanShareResult { #[cfg(test)] mod tests { - use super::{ShareOptions, SharedFile}; + use super::{ShareOptions, SharedFile, MAX_FILES, MAX_TEXT_BYTES}; fn options( text: Option<&str>, @@ -132,4 +262,56 @@ mod tests { Some("hello\nhttps://example.com") ); } + + #[test] + fn validation_accepts_http_and_https_urls() { + assert!(options(None, Some("https://example.com"), None) + .validate() + .is_ok()); + assert!(options(None, Some("http://example.com"), None) + .validate() + .is_ok()); + } + + #[test] + fn validation_rejects_non_web_url_schemes() { + assert!(options(None, Some("file:///etc/passwd"), None) + .validate() + .is_err()); + assert!(options(None, Some("custom-scheme://value"), None) + .validate() + .is_err()); + assert!(options(None, Some("https:///missing-host"), None) + .validate() + .is_err()); + assert!(options( + None, + Some("https://example.com\nhttps://evil.example"), + None + ) + .validate() + .is_err()); + } + + #[test] + fn validation_rejects_oversized_text_and_too_many_files() { + let oversized_text = "a".repeat(MAX_TEXT_BYTES + 1); + assert!(ShareOptions { + text: Some(oversized_text), + title: None, + url: None, + files: None, + } + .validate() + .is_err()); + + let files = (0..=MAX_FILES) + .map(|index| SharedFile { + data: "aGVsbG8=".to_string(), + name: format!("hello-{index}.txt"), + mime_type: "text/plain".to_string(), + }) + .collect(); + assert!(options(None, None, Some(files)).validate().is_err()); + } } diff --git a/src/platform/macos.rs b/src/platform/macos.rs index 206915a..4b2c197 100644 --- a/src/platform/macos.rs +++ b/src/platform/macos.rs @@ -1,6 +1,6 @@ use crate::models::CanShareResult; use crate::state::PluginTempFileManager; -use crate::{Error, ShareOptions, SharedFile}; +use crate::{Error, ShareOptions, SharedFile, MAX_FILE_BYTES}; use base64::{engine::general_purpose, Engine as _}; use objc2::runtime::{AnyObject, ProtocolObject}; use objc2::Message; @@ -257,6 +257,12 @@ fn create_temp_file_for_data(options: &SharedFile) -> Result MAX_FILE_BYTES { + return Err(Error::InvalidArgs(format!( + "File '{}' exceeds the maximum size of {} bytes.", + options.name, MAX_FILE_BYTES + ))); + } // Security: Sanitize the filename to prevent path traversal attacks. let sanitized_name = Path::new(&options.name) .file_name() diff --git a/src/platform/windows.rs b/src/platform/windows.rs index f91b48e..8451a7e 100644 --- a/src/platform/windows.rs +++ b/src/platform/windows.rs @@ -1,6 +1,6 @@ use super::focus; use crate::state::PluginTempFileManager; -use crate::{CanShareResult, Error, ShareOptions, SharedFile}; +use crate::{CanShareResult, Error, ShareOptions, SharedFile, MAX_FILE_BYTES}; use base64::{engine::general_purpose, Engine as _}; use raw_window_handle::{HasWindowHandle, RawWindowHandle}; use std::cell::RefCell; @@ -273,6 +273,12 @@ fn create_temp_file_for_data(file: &SharedFile) -> Result { let decoded_bytes = general_purpose::STANDARD .decode(&file.data) .map_err(|_| Error::InvalidArgs("Invalid Base64 data provided".to_string()))?; + if decoded_bytes.len() > MAX_FILE_BYTES { + return Err(Error::InvalidArgs(format!( + "File '{}' exceeds the maximum size of {} bytes.", + file.name, MAX_FILE_BYTES + ))); + } // Security: Sanitize the filename to prevent path traversal attacks. // We only use the filename part and ignore any directory structure. diff --git a/tests/js/index.test.ts b/tests/js/index.test.ts new file mode 100644 index 0000000..985019f --- /dev/null +++ b/tests/js/index.test.ts @@ -0,0 +1,118 @@ +import { Buffer } from 'node:buffer' +import { beforeEach, describe, expect, it, vi } from 'vitest' + +const tauriCoreMock = vi.hoisted(() => ({ + invoke: vi.fn(), +})) + +vi.mock('@tauri-apps/api/core', () => tauriCoreMock) + +class TestFileReader { + result: string | ArrayBuffer | null = null + onload: (() => void) | null = null + onerror: ((error: unknown) => void) | null = null + + readAsDataURL(file: Blob) { + file.arrayBuffer() + .then(buffer => { + const base64 = Buffer.from(buffer).toString('base64') + this.result = `data:${file.type};base64,${base64}` + this.onload?.() + }) + .catch(error => this.onerror?.(error)) + } +} + +describe('share guest API', () => { + beforeEach(() => { + tauriCoreMock.invoke.mockReset() + tauriCoreMock.invoke.mockResolvedValue(undefined) + vi.stubGlobal('FileReader', TestFileReader) + }) + + it('checks native share capability through the plugin command', async () => { + tauriCoreMock.invoke.mockResolvedValue({ value: true }) + const { canShare } = await import('../../guest-js/index') + + await expect(canShare()).resolves.toBe(true) + + expect(tauriCoreMock.invoke).toHaveBeenCalledWith('plugin:vnidrop-share|can_share') + }) + + it('returns false for invalid data without invoking native code', async () => { + const { canShare } = await import('../../guest-js/index') + + await expect(canShare({ url: 'file:///tmp/secret.txt' })).resolves.toBe(false) + + expect(tauriCoreMock.invoke).not.toHaveBeenCalled() + }) + + it('converts File payloads to base64 before invoking share', async () => { + const { share } = await import('../../guest-js/index') + const file = new File(['hello'], 'hello.txt', { type: 'text/plain' }) + + await share({ + title: 'Greeting', + text: 'Share this', + url: 'https://example.com', + files: [file], + }) + + expect(tauriCoreMock.invoke).toHaveBeenCalledWith('plugin:vnidrop-share|share', { + options: { + title: 'Greeting', + text: 'Share this', + url: 'https://example.com', + files: [ + { + data: 'aGVsbG8=', + name: 'hello.txt', + mimeType: 'text/plain', + }, + ], + }, + }) + }) + + it('rejects non-web url schemes in the url field', async () => { + const { share } = await import('../../guest-js/index') + + await expect(share({ url: 'file:///tmp/secret.txt' })).rejects.toThrow( + 'Only http:// and https:// URLs can be shared as URLs.' + ) + + expect(tauriCoreMock.invoke).not.toHaveBeenCalled() + }) + + it('rejects malformed web urls before invoking native code', async () => { + const { share } = await import('../../guest-js/index') + + await expect(share({ url: 'https:///missing-host' })).rejects.toThrow( + 'Only http:// and https:// URLs can be shared as URLs.' + ) + await expect(share({ url: 'https://example.com\nhttps://evil.example' })).rejects.toThrow( + 'Only http:// and https:// URLs can be shared as URLs.' + ) + + expect(tauriCoreMock.invoke).not.toHaveBeenCalled() + }) + + it('rejects oversized file metadata before invoking native code', async () => { + const { share } = await import('../../guest-js/index') + const file = new File(['hello'], 'a'.repeat(256), { type: 'text/plain' }) + + await expect(share({ files: [file] })).rejects.toThrow( + 'File name exceeds the maximum length of 255 bytes.' + ) + + expect(tauriCoreMock.invoke).not.toHaveBeenCalled() + }) + + it('keeps cleanup as an explicit command', async () => { + const { cleanup } = await import('../../guest-js/index') + + await cleanup() + + expect(tauriCoreMock.invoke).toHaveBeenCalledWith('plugin:vnidrop-share|cleanup') + }) +}) diff --git a/tests/model_contracts.rs b/tests/model_contracts.rs new file mode 100644 index 0000000..520080f --- /dev/null +++ b/tests/model_contracts.rs @@ -0,0 +1,92 @@ +use tauri_plugin_vnidrop_share::{ + ShareOptions, SharedFile, MAX_FILES, MAX_FILE_NAME_BYTES, MAX_TEXT_BYTES, MAX_URL_BYTES, +}; + +#[test] +fn public_model_validation_accepts_local_file_bytes_without_url() { + let options = ShareOptions { + text: None, + title: Some("Local file".to_string()), + url: None, + files: Some(vec![SharedFile { + data: "aGVsbG8=".to_string(), + name: "hello.txt".to_string(), + mime_type: "text/plain".to_string(), + }]), + }; + + assert!(options.has_shareable_content()); + assert!(options.validate().is_ok()); +} + +#[test] +fn public_model_validation_rejects_non_web_url_schemes() { + for url in [ + "file:///tmp/secret.txt", + "content://provider/item", + "https:///missing-host", + " https://example.com", + "https://example.com\nhttps://evil.example", + ] { + assert!(ShareOptions { + text: None, + title: None, + url: Some(url.to_string()), + files: None, + } + .validate() + .is_err()); + } +} + +#[test] +fn public_model_validation_enforces_limits() { + let oversized_text = "a".repeat(MAX_TEXT_BYTES + 1); + assert!(ShareOptions { + text: Some(oversized_text), + title: None, + url: None, + files: None, + } + .validate() + .is_err()); + + let oversized_url = format!("https://example.com/{}", "a".repeat(MAX_URL_BYTES)); + assert!(ShareOptions { + text: None, + title: None, + url: Some(oversized_url), + files: None, + } + .validate() + .is_err()); + + let files = (0..=MAX_FILES) + .map(|index| SharedFile { + data: "aGVsbG8=".to_string(), + name: format!("hello-{index}.txt"), + mime_type: "text/plain".to_string(), + }) + .collect(); + assert!(ShareOptions { + text: None, + title: None, + url: None, + files: Some(files), + } + .validate() + .is_err()); + + assert!(ShareOptions { + text: None, + title: None, + url: None, + files: Some(vec![SharedFile { + data: "aGVsbG8=".to_string(), + name: "a".repeat(MAX_FILE_NAME_BYTES + 1), + mime_type: "text/plain".to_string(), + }]), + } + .validate() + .is_err()); +} diff --git a/tests/permissions.rs b/tests/permissions.rs new file mode 100644 index 0000000..8acd6e0 --- /dev/null +++ b/tests/permissions.rs @@ -0,0 +1,20 @@ +use std::fs; + +#[test] +fn default_permission_excludes_cleanup() { + let contents = fs::read_to_string("permissions/default.toml") + .expect("default permission file should exist"); + + assert!(contents.contains(r#""allow-share""#)); + assert!(contents.contains(r#""allow-can-share""#)); + assert!(!contents.contains(r#""allow-cleanup""#)); +} + +#[test] +fn cleanup_permission_remains_explicitly_available() { + let contents = fs::read_to_string("permissions/autogenerated/commands/cleanup.toml") + .expect("cleanup permission file should exist"); + + assert!(contents.contains(r#"identifier = "allow-cleanup""#)); + assert!(contents.contains(r#"commands.allow = ["cleanup"]"#)); +} diff --git a/tests/types/package-imports.ts b/tests/types/package-imports.ts new file mode 100644 index 0000000..4f9cd8a --- /dev/null +++ b/tests/types/package-imports.ts @@ -0,0 +1,18 @@ +import { canShare, cleanup, share, type ShareData } from '@vnidrop/tauri-plugin-share' + +async function checkShareImports(file: File): Promise { + const data: ShareData = { + title: 'Report', + text: 'Share this report', + url: 'https://example.com/report', + files: [file], + } + + const supported: boolean = await canShare(data) + if (supported) { + await share(data) + } + await cleanup() +} + +void checkShareImports diff --git a/tsconfig.json b/tsconfig.json index 0591122..184ceb7 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,6 +3,7 @@ "target": "es2021", "module": "esnext", "moduleResolution": "bundler", + "rootDir": "guest-js", "skipLibCheck": true, "strict": true, "noUnusedLocals": true, diff --git a/tsconfig.test.json b/tsconfig.test.json new file mode 100644 index 0000000..c78b635 --- /dev/null +++ b/tsconfig.test.json @@ -0,0 +1,17 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "types": ["vitest/globals", "node"], + "rootDir": ".", + "noEmit": true + }, + "include": [ + "guest-js/*.ts", + "tests/js/**/*.ts", + "tests/types/**/*.ts" + ], + "exclude": [ + "dist-js", + "node_modules" + ] +} diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 0000000..fd12bb9 --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,10 @@ +import { defineConfig } from 'vitest/config' + +export default defineConfig({ + test: { + environment: 'node', + include: ['tests/js/**/*.test.ts'], + clearMocks: true, + restoreMocks: true, + }, +})