diff --git a/.gitignore b/.gitignore index 115cc540..66ef3a57 100644 --- a/.gitignore +++ b/.gitignore @@ -65,3 +65,12 @@ frontend/*.local *.tsbuildinfo **/.claude/settings.local.json + +# Rust build directories +target/ +**/target/ +plugins/*/target/ + +# iOS build artifacts +frontend/src-tauri/gen/apple/build/ +frontend/src-tauri/gen/apple/DerivedData/ \ No newline at end of file diff --git a/flake.nix b/flake.nix index 1e5555c7..b6399fb4 100644 --- a/flake.nix +++ b/flake.nix @@ -16,7 +16,7 @@ }; # Use specific rust version required by Tauri - rustToolchain = pkgs.rust-bin.stable."1.78.0".default.override { + rustToolchain = pkgs.rust-bin.stable."1.81.0".default.override { extensions = [ "rust-src" ]; }; in diff --git a/frontend/src-tauri/Cargo.lock b/frontend/src-tauri/Cargo.lock index 12445b00..c2c337b2 100644 --- a/frontend/src-tauri/Cargo.lock +++ b/frontend/src-tauri/Cargo.lock @@ -2236,6 +2236,7 @@ dependencies = [ "once_cell", "serde", "serde_json", + "store", "tauri", "tauri-build", "tauri-plugin", @@ -3971,6 +3972,16 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "store" +version = "0.1.0" +dependencies = [ + "serde", + "tauri", + "tauri-plugin", + "thiserror 1.0.69", +] + [[package]] name = "string_cache" version = "0.8.8" diff --git a/frontend/src-tauri/Cargo.toml b/frontend/src-tauri/Cargo.toml index 41a13e8e..49457c40 100644 --- a/frontend/src-tauri/Cargo.toml +++ b/frontend/src-tauri/Cargo.toml @@ -32,3 +32,8 @@ tauri-plugin-os = "2" tauri-plugin-sign-in-with-apple = "1.0.2" tokio = { version = "1.0", features = ["time"] } once_cell = "1.18.0" +store = { path = "../../plugins/store", features = [] } + +[target.'cfg(target_os = "ios")'.dependencies.store] +path = "../../plugins/store" +features = ["mobile"] diff --git a/frontend/src-tauri/capabilities/default.json b/frontend/src-tauri/capabilities/default.json index d8a7b18c..71e0597f 100644 --- a/frontend/src-tauri/capabilities/default.json +++ b/frontend/src-tauri/capabilities/default.json @@ -28,6 +28,13 @@ } ] }, - "sign-in-with-apple:default" + "sign-in-with-apple:default", + "store:allow-get-region", + "store:allow-get-products", + "store:allow-purchase", + "store:allow-verify-purchase", + "store:allow-get-transactions", + "store:allow-restore-purchases", + "store:allow-get-subscription-status" ] } diff --git a/frontend/src-tauri/capabilities/mobile-ios.json b/frontend/src-tauri/capabilities/mobile-ios.json index 6adf773b..11cce655 100644 --- a/frontend/src-tauri/capabilities/mobile-ios.json +++ b/frontend/src-tauri/capabilities/mobile-ios.json @@ -29,6 +29,13 @@ } ] }, - "sign-in-with-apple:default" + "sign-in-with-apple:default", + "store:allow-get-region", + "store:allow-get-products", + "store:allow-purchase", + "store:allow-verify-purchase", + "store:allow-get-transactions", + "store:allow-restore-purchases", + "store:allow-get-subscription-status" ] -} \ No newline at end of file +} diff --git a/frontend/src-tauri/src/lib.rs b/frontend/src-tauri/src/lib.rs index 52777924..60e74ea6 100644 --- a/frontend/src-tauri/src/lib.rs +++ b/frontend/src-tauri/src/lib.rs @@ -1,6 +1,11 @@ use tauri::Emitter; use tauri_plugin_deep_link::DeepLinkExt; use tauri_plugin_opener; + +#[cfg(all(not(desktop), target_os = "ios"))] +use store; + +#[cfg(all(not(desktop), target_os = "ios"))] use tauri_plugin_sign_in_with_apple; // This handles incoming deep links @@ -198,7 +203,9 @@ pub fn run() { // Only add the Apple Sign In plugin on iOS #[cfg(all(not(desktop), target_os = "ios"))] { - builder = builder.plugin(tauri_plugin_sign_in_with_apple::init()); + builder = builder + .plugin(tauri_plugin_sign_in_with_apple::init()) + .plugin(store::init()); } #[cfg(not(desktop))] diff --git a/frontend/src/billing/applePayService.ts b/frontend/src/billing/applePayService.ts new file mode 100644 index 00000000..f3c69003 --- /dev/null +++ b/frontend/src/billing/applePayService.ts @@ -0,0 +1,282 @@ +import { invoke } from "@tauri-apps/api/core"; +import { allowExternalBilling } from "../utils/region-gate"; + +// Types for StoreKit +export type Product = { + id: string; + title: string; + description: string; + price: string; + priceValue: number; + currencyCode: string; + type: "consumable" | "non_consumable" | "auto_renewable_subscription" | "non_renewable_subscription"; + subscriptionPeriod?: { + unit: "day" | "week" | "month" | "year"; + value: number; + }; + introductoryOffer?: SubscriptionOffer; + promotionalOffers?: SubscriptionOffer[]; +}; + +export type SubscriptionOffer = { + id: string; + displayPrice: string; + period: { + unit: "day" | "week" | "month" | "year"; + value: number; + }; + paymentMode: "pay_as_you_go" | "pay_up_front" | "free_trial"; + type: "introductory" | "promotional" | "prepaid" | "consumable"; + discountType?: "percentage" | "nominal"; + discountPrice?: string; +}; + +export type Transaction = { + id: number; + originalId?: number; + productId: string; + purchaseDate: number; + expirationDate?: number; + webOrderLineItemId: string; + quantity: number; + type: "consumable" | "non_consumable" | "auto_renewable_subscription" | "non_renewable_subscription"; + ownershipType: "purchased" | "familyShared"; + signedDate: number; +}; + +export type PurchaseResult = { + status: "success" | "pending"; + transactionId?: number; + originalTransactionId?: number; + productId?: string; + purchaseDate?: number; + expirationDate?: number; + webOrderLineItemId?: string; + quantity?: number; + type?: string; + ownershipType?: string; + signedDate?: number; + environment?: "sandbox" | "production"; + message?: string; +}; + +export type VerificationResult = { + isValid: boolean; + expirationDate?: number; + purchaseDate?: number; +}; + +export type RestorePurchasesResult = { + status: string; + transactions: Transaction[]; +}; + +export type SubscriptionStatus = { + productId: string; + status: "subscribed" | "expired" | "in_billing_retry_period" | "in_grace_period" | "revoked" | "not_subscribed"; + willAutoRenew: boolean; + expirationDate?: number; + gracePeriodExpirationDate?: number; +}; + +/** + * Maps between Stripe/server product IDs and Apple product IDs + * In a real app, this would be configured on the server and fetched during initialization + */ +const PRODUCT_ID_MAP: Record = { + // Example: Stripe product ID -> Apple product ID (monthly plans only for Apple Pay) + "price_pro_monthly": "com.opensecret.maple.pro.monthly", + "price_starter_monthly": "com.opensecret.maple.starter.monthly" +}; + +class ApplePayService { + private static instance: ApplePayService; + private cachedProducts: Map = new Map(); + + // A mapping between system product IDs (e.g., Stripe) and Apple Store product IDs + private productIdMap: Record = PRODUCT_ID_MAP; + + private constructor() {} + + public static getInstance(): ApplePayService { + if (!ApplePayService.instance) { + ApplePayService.instance = new ApplePayService(); + } + return ApplePayService.instance; + } + + /** + * Set the product ID mapping + */ + public setProductIdMap(map: Record): void { + this.productIdMap = { ...PRODUCT_ID_MAP, ...map }; + } + + /** + * Convert a system product ID to an Apple product ID + */ + public getAppleProductId(systemProductId: string): string { + return this.productIdMap[systemProductId] || systemProductId; + } + + /** + * Check if Apple Pay is available and permitted based on region + */ + public async isApplePayAvailable(): Promise { + try { + // First check if we're on iOS + const { type } = await import("@tauri-apps/plugin-os"); + const platform = await type(); + + if (platform !== "ios") { + console.log("[ApplePay] Not on iOS platform"); + return false; + } + + // For regions where external billing is not allowed, Apple Pay is required + // For US regions, we can show both options + return true; + } catch (error) { + console.error("[ApplePay] Error checking availability:", error); + return false; + } + } + + /** + * Check if Apple Pay should be the required payment method (non-US) + */ + public async isApplePayRequired(): Promise { + try { + // If not on iOS, it's never required + const { type } = await import("@tauri-apps/plugin-os"); + const platform = await type(); + + if (platform !== "ios") { + return false; + } + + // For non-US regions (where external billing is not allowed), Apple Pay is required + const isUSRegion = await allowExternalBilling(); + return !isUSRegion; + } catch (error) { + console.error("[ApplePay] Error checking if required:", error); + return false; + } + } + + /** + * Get products from the App Store + */ + public async getProducts(productIds: string[]): Promise { + try { + console.log("[ApplePay] Fetching products:", productIds); + const products = await invoke("plugin:store|get_products", { + productIds + }); + + // Cache products for later use + products.forEach(product => { + this.cachedProducts.set(product.id, product); + }); + + return products; + } catch (error) { + console.error("[ApplePay] Error fetching products:", error); + throw error; + } + } + + /** + * Purchase a product + */ + public async purchase(productId: string): Promise { + try { + console.log("[ApplePay] Purchasing product:", productId); + return await invoke("plugin:store|purchase", { + productId + }); + } catch (error) { + console.error("[ApplePay] Purchase error:", error); + throw error; + } + } + + /** + * Verify a purchase + */ + public async verifyPurchase(productId: string, transactionId: number): Promise { + try { + console.log("[ApplePay] Verifying purchase:", productId, transactionId); + return await invoke("plugin:store|verify_purchase", { + productId, + transactionId + }); + } catch (error) { + console.error("[ApplePay] Verification error:", error); + throw error; + } + } + + /** + * Get all transactions, optionally filtered by product ID + */ + public async getTransactions(productId?: string): Promise { + try { + console.log("[ApplePay] Getting transactions"); + return await invoke("plugin:store|get_transactions", { + productId + }); + } catch (error) { + console.error("[ApplePay] Error getting transactions:", error); + throw error; + } + } + + /** + * Restore purchases + */ + public async restorePurchases(): Promise { + try { + console.log("[ApplePay] Restoring purchases"); + return await invoke("plugin:store|restore_purchases"); + } catch (error) { + console.error("[ApplePay] Restore error:", error); + throw error; + } + } + + /** + * Get subscription status + */ + public async getSubscriptionStatus(productId: string): Promise { + try { + console.log("[ApplePay] Getting subscription status:", productId); + return await invoke("plugin:store|get_subscription_status", { + productId + }); + } catch (error) { + console.error("[ApplePay] Error getting subscription status:", error); + throw error; + } + } + + /** + * Format price to display currency with localization + */ + public formatPrice(product: Product): string { + return new Intl.NumberFormat(navigator.language, { + style: "currency", + currency: product.currencyCode + }).format(product.priceValue); + } + + /** + * Get a product from cache by ID + */ + public getCachedProduct(productId: string): Product | undefined { + return this.cachedProducts.get(productId); + } +} + +// Create singleton instance +export const applePayService = ApplePayService.getInstance(); \ No newline at end of file diff --git a/frontend/src/billing/billingApi.ts b/frontend/src/billing/billingApi.ts index ba9882b0..18f0d5bf 100644 --- a/frontend/src/billing/billingApi.ts +++ b/frontend/src/billing/billingApi.ts @@ -300,3 +300,47 @@ export async function fetchTeamPlanAvailable(thirdPartyToken: string): Promise { + try { + console.log("[BillingAPI] Syncing Apple transaction:", { transactionId, productId }); + const response = await fetch( + `${import.meta.env.VITE_MAPLE_BILLING_API_URL}/v1/maple/subscription/apple-sync`, + { + method: "POST", + headers: { + Authorization: `Bearer ${thirdPartyToken}`, + "Content-Type": "application/json" + }, + body: JSON.stringify({ + transaction_id: transactionId, + product_id: productId + }) + } + ); + + if (!response.ok) { + const errorText = await response.text(); + console.error("Apple transaction sync error response:", errorText); + if (response.status === 401) { + throw new Error("Unauthorized"); + } + throw new Error(`Failed to sync Apple transaction: ${errorText}`); + } + + console.log("[BillingAPI] Apple transaction sync successful"); + } catch (error) { + console.error("Error syncing Apple transaction:", error); + throw error; + } +} diff --git a/frontend/src/billing/billingService.ts b/frontend/src/billing/billingService.ts index c0c459a2..3d210b29 100644 --- a/frontend/src/billing/billingService.ts +++ b/frontend/src/billing/billingService.ts @@ -7,8 +7,10 @@ import { createZapriteCheckoutSession, BillingStatus, BillingProduct, - fetchTeamPlanAvailable + fetchTeamPlanAvailable, + syncAppleTransaction } from "./billingApi"; +import { allowExternalBilling } from "../utils/region-gate"; const TOKEN_STORAGE_KEY = "maple_billing_token"; @@ -19,7 +21,7 @@ class BillingService { this.os = os; } - private async getStoredToken(): Promise { + async getStoredToken(): Promise { return sessionStorage.getItem(TOKEN_STORAGE_KEY); } @@ -95,6 +97,53 @@ class BillingService { async getTeamPlanAvailable(): Promise { return this.executeWithToken((token) => fetchTeamPlanAvailable(token)); } + + /** + * Sync an Apple in-app purchase transaction with our backend + */ + async syncAppleTransaction( + transactionId: number, + productId: string + ): Promise { + return this.executeWithToken((token) => + syncAppleTransaction(token, transactionId, productId) + ); + } + + /** + * Check if Apple Pay should be shown as a payment option + * On iOS, this is true if external billing is NOT allowed + */ + async shouldShowApplePay(): Promise { + try { + // Check if we're on iOS + const { type } = await import("@tauri-apps/plugin-os"); + const platform = await type(); + + if (platform !== "ios") { + // Not on iOS, don't show Apple Pay + return false; + } + + // Check if external billing is allowed based on region + const isExternalBillingAllowed = await allowExternalBilling(); + + // We need to show Apple Pay if external billing is NOT allowed + return !isExternalBillingAllowed; + } catch (error) { + console.error("Error checking if Apple Pay should be shown:", error); + // Default to not showing Apple Pay on errors + return false; + } + } + + /** + * Check if Apple Pay should be the only payment option (non-US regions) + * In non-US App Store regions, Apple requires using their IAP system + */ + async isApplePayRequired(): Promise { + return this.shouldShowApplePay(); + } clearToken(): void { sessionStorage.removeItem(TOKEN_STORAGE_KEY); diff --git a/frontend/src/components/ApplePayButton.tsx b/frontend/src/components/ApplePayButton.tsx new file mode 100644 index 00000000..a045ff01 --- /dev/null +++ b/frontend/src/components/ApplePayButton.tsx @@ -0,0 +1,130 @@ +import React, { useState, useEffect } from "react"; +import { cn } from "@/utils/utils"; +import { applePayService } from "../billing/applePayService"; +import { getBillingService } from "../billing/billingService"; +import { useQueryClient } from "@tanstack/react-query"; + +type ApplePayButtonProps = { + productId: string; + className?: string; + text?: string; + disabled?: boolean; + onSuccess?: (transactionId: number) => void; + onError?: (error: Error) => void; + onCancel?: () => void; +}; + +export function ApplePayButton({ + productId, + className, + text = "Pay with Apple", + disabled = false, + onSuccess, + onError, + onCancel +}: ApplePayButtonProps) { + const [isLoading, setIsLoading] = useState(false); + const [buttonLabel, setButtonLabel] = useState(text); + const queryClient = useQueryClient(); + + // Load product info when available + useEffect(() => { + async function loadProductInfo() { + try { + const products = await applePayService.getProducts([productId]); + if (products && products.length > 0) { + const product = products[0]; + setButtonLabel(`${text} ${product.price}`); + } + } catch (error) { + console.error("Error loading product info:", error); + } + } + + loadProductInfo(); + }, [productId, text]); + + // Handle purchase + const handlePurchase = async () => { + if (disabled || isLoading) return; + + setIsLoading(true); + try { + // Make the purchase + const result = await applePayService.purchase(productId); + + if (result.status === "success" && result.transactionId) { + console.log("Purchase successful:", result); + + // Sync with backend + try { + // Update user's subscription with your backend + const billingService = getBillingService(); + await billingService.syncAppleTransaction(result.transactionId, productId); + + // Refresh billing status data + await queryClient.invalidateQueries({ queryKey: ["billingStatus"] }); + + // Call success callback + onSuccess?.(result.transactionId); + } catch (syncError) { + console.error("Error syncing purchase with backend:", syncError); + // Even if sync fails, the purchase was successful + onSuccess?.(result.transactionId); + } + } else if (result.status === "pending") { + console.log("Purchase pending:", result); + // Handle pending purchase (like "Ask to Buy" for children) + setButtonLabel("Purchase pending approval"); + } else { + // This shouldn't happen, but handle it anyway + throw new Error("Purchase failed with unknown status"); + } + } catch (error) { + console.error("Purchase error:", error); + + // If user canceled, call the onCancel callback + if (error instanceof Error && error.message.includes("cancelled")) { + onCancel?.(); + } else { + // Otherwise call the onError callback + onError?.(error instanceof Error ? error : new Error(String(error))); + } + } finally { + setIsLoading(false); + } + }; + + return ( + + ); +} \ No newline at end of file diff --git a/frontend/src/components/Marketing.tsx b/frontend/src/components/Marketing.tsx index ad3c8ab2..820c7fa4 100644 --- a/frontend/src/components/Marketing.tsx +++ b/frontend/src/components/Marketing.tsx @@ -88,7 +88,8 @@ function PricingTier({ ctaText, popular = false, productId = "", // Add productId parameter - isIOS = false // Add iOS detection parameter + isIOS = false, // Add iOS detection parameter + externalBillingAllowed = true // Add external billing parameter with default to true }: { name: string; price: string; @@ -98,6 +99,7 @@ function PricingTier({ popular?: boolean; productId?: string; // Add type for productId isIOS?: boolean; // Add type for iOS detection + externalBillingAllowed?: boolean; // Add type for external billing }) { const isTeamPlan = name.toLowerCase().includes("team"); const isFreeplan = name.toLowerCase().includes("free"); @@ -133,19 +135,49 @@ function PricingTier({ ))} - {/* For iOS devices, disable paid plans with "Coming Soon" text */} + {/* For iOS devices, check if external billing is allowed */} {isIOS && !isFreeplan ? ( - + externalBillingAllowed ? ( + // If external billing is allowed, handle product selection normally + productId ? ( + + ) : ( + + {ctaText} + + ) + ) : ( + // If external billing is not allowed, show "Coming Soon" text + + ) ) : isTeamPlan ? ( // For team plans, add "Contact Us" button that opens email + {/* Show Apple Pay button for paid plans on iOS in non-US regions */} + {isIOS && + !externalBillingAllowed && + !product.name.toLowerCase().includes("free") && + !product.name.toLowerCase().includes("team") ? ( +
+ {/* ApplePayButton will be dynamically imported below */} + +
+ In-app purchase required for your region +
+
+ ) : ( + + )} ); diff --git a/frontend/src/utils/region-gate.ts b/frontend/src/utils/region-gate.ts new file mode 100644 index 00000000..a9b14f90 --- /dev/null +++ b/frontend/src/utils/region-gate.ts @@ -0,0 +1,72 @@ +import { invoke } from "@tauri-apps/api/core"; + +const US_CODES = ["USA", "ASM", "GUM", "PRI", "VIR", "MNP", "UMI"]; // US and territories + +/** + * Gets the raw App Store region code. + * Returns the region code, or a short error message if there's an error invoking the plugin. + * Note: The Swift code returns "UNKNOWN" if it can't determine the region. + */ +export async function getStoreRegion(): Promise { + console.log("[Region Gate] Attempting to invoke plugin:store|get_region..."); + try { + const code = await invoke("plugin:store|get_region"); + console.log("[Region Gate] Success! Store region code:", code); + return code; + } catch (error) { + console.error("[Region Gate] Error invoking plugin:", error); + console.error("[Region Gate] Stack trace:", new Error().stack); + + // Return the error message for diagnosis + let errorMsg = "Error: "; + if (error instanceof Error) { + errorMsg += error.message; + } else if (typeof error === "string") { + errorMsg += error; + } else { + errorMsg += JSON.stringify(error); + } + + // Truncate if the error message is too long + if (errorMsg.length > 30) { + errorMsg = errorMsg.substring(0, 27) + "..."; + } + + return errorMsg; + } +} + +/** + * Checks if external billing is allowed based on the App Store region. + * Returns true for US regions, false for all others or on errors. + */ +export async function allowExternalBilling(): Promise { + try { + // Check if we're on iOS + const { platform } = await import("@tauri-apps/plugin-os"); + const currentPlatform = await platform(); + + // Only apply the region check on iOS + if (currentPlatform !== "ios") { + // For non-iOS platforms, always allow external billing + return true; + } + + // On iOS, check the App Store region + const regionCode = await getStoreRegion(); + const isAllowed = US_CODES.includes(regionCode); + console.log("[Region Gate] Is US region:", isAllowed, "Valid US codes:", US_CODES); + return isAllowed; + } catch (error) { + console.error("[Region Gate] Error checking external billing:", error); + // On error, assume non-US to be safe + return false; + } +} + +/** + * Checks if a region code is in the US. + */ +export function isUSRegion(regionCode: string): boolean { + return US_CODES.includes(regionCode); +} diff --git a/plugins/store/.tauri/tauri-api/.gitignore b/plugins/store/.tauri/tauri-api/.gitignore new file mode 100644 index 00000000..5922fdaa --- /dev/null +++ b/plugins/store/.tauri/tauri-api/.gitignore @@ -0,0 +1,10 @@ +.DS_Store +/.build +/Packages +/*.xcodeproj +xcuserdata/ +DerivedData/ +.swiftpm/config/registries.json +.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata +.netrc +Package.resolved diff --git a/plugins/store/.tauri/tauri-api/Package.swift b/plugins/store/.tauri/tauri-api/Package.swift new file mode 100644 index 00000000..c7a73323 --- /dev/null +++ b/plugins/store/.tauri/tauri-api/Package.swift @@ -0,0 +1,40 @@ +// swift-tools-version:5.3 +// Copyright 2019-2024 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +import PackageDescription + +let package = Package( + name: "Tauri", + platforms: [ + .macOS(.v10_13), + .iOS(.v11), + ], + products: [ + // Products define the executables and libraries a package produces, and make them visible to other packages. + .library( + name: "Tauri", + type: .static, + targets: ["Tauri"]) + ], + dependencies: [ + // Dependencies declare other packages that this package depends on. + .package(name: "SwiftRs", url: "https://github.com/Brendonovich/swift-rs", from: "1.0.0") + ], + 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", + dependencies: [ + .byName(name: "SwiftRs") + ], + path: "Sources" + ), + .testTarget( + name: "TauriTests", + dependencies: ["Tauri"] + ), + ] +) diff --git a/plugins/store/.tauri/tauri-api/README.md b/plugins/store/.tauri/tauri-api/README.md new file mode 100644 index 00000000..52c3f1c7 --- /dev/null +++ b/plugins/store/.tauri/tauri-api/README.md @@ -0,0 +1,3 @@ +# Tauri + +Tauri iOS API. diff --git a/plugins/store/.tauri/tauri-api/Sources/Tauri/Channel.swift b/plugins/store/.tauri/tauri-api/Sources/Tauri/Channel.swift new file mode 100644 index 00000000..add065c7 --- /dev/null +++ b/plugins/store/.tauri/tauri-api/Sources/Tauri/Channel.swift @@ -0,0 +1,65 @@ +// Copyright 2019-2024 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +import Foundation + +let CHANNEL_PREFIX = "__CHANNEL__:" +let channelDataKey = CodingUserInfoKey(rawValue: "sendChannelData")! + +public class Channel: Decodable { + public let id: UInt64 + let handler: (UInt64, String) -> Void + + public required init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + let channelDef = try container.decode(String.self) + + let components = channelDef.components(separatedBy: CHANNEL_PREFIX) + if components.count < 2 { + throw DecodingError.dataCorruptedError( + in: container, + debugDescription: "Invalid channel definition from \(channelDef)" + ) + + } + guard let channelId = UInt64(components[1]) else { + throw DecodingError.dataCorruptedError( + in: container, + debugDescription: "Invalid channel ID from \(channelDef)" + ) + } + + guard let handler = decoder.userInfo[channelDataKey] as? (UInt64, String) -> Void else { + throw DecodingError.dataCorruptedError( + in: container, + debugDescription: "missing userInfo for Channel handler. This is a Tauri issue" + ) + } + + self.id = channelId + self.handler = handler + } + + func serialize(_ data: JsonValue) -> String { + do { + return try data.jsonRepresentation() ?? "\"Failed to serialize payload\"" + } catch { + return "\"\(error)\"" + } + } + + public func send(_ data: JsonObject) { + send(.dictionary(data)) + } + + public func send(_ data: JsonValue) { + handler(id, serialize(data)) + } + + public func send(_ data: T) throws { + let json = try JSONEncoder().encode(data) + handler(id, String(decoding: json, as: UTF8.self)) + } + +} diff --git a/plugins/store/.tauri/tauri-api/Sources/Tauri/Invoke.swift b/plugins/store/.tauri/tauri-api/Sources/Tauri/Invoke.swift new file mode 100644 index 00000000..6f810868 --- /dev/null +++ b/plugins/store/.tauri/tauri-api/Sources/Tauri/Invoke.swift @@ -0,0 +1,118 @@ +// Copyright 2019-2024 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +import Foundation +import UIKit + +@objc public class Invoke: NSObject { + public let command: String + let callback: UInt64 + let error: UInt64 + let data: String + let sendResponse: (UInt64, String?) -> Void + let sendChannelData: (UInt64, String) -> Void + + public init( + command: String, callback: UInt64, error: UInt64, + sendResponse: @escaping (UInt64, String?) -> Void, + sendChannelData: @escaping (UInt64, String) -> Void, data: String + ) { + self.command = command + self.callback = callback + self.error = error + self.data = data + self.sendResponse = sendResponse + self.sendChannelData = sendChannelData + } + + public func getRawArgs() -> String { + return self.data + } + + public func getArgs() throws -> JSObject { + let jsonData = self.data.data(using: .utf8)! + let data = try JSONSerialization.jsonObject(with: jsonData, options: []) + return JSTypes.coerceDictionaryToJSObject( + (data as! NSDictionary), formattingDatesAsStrings: true)! + } + + public func parseArgs(_ type: T.Type) throws -> T { + let jsonData = self.data.data(using: .utf8)! + let decoder = JSONDecoder() + decoder.userInfo[channelDataKey] = sendChannelData + return try decoder.decode(type, from: jsonData) + } + + func serialize(_ data: JsonValue) -> String { + do { + return try data.jsonRepresentation() ?? "\"Failed to serialize payload\"" + } catch { + return "\"\(error)\"" + } + } + + public func resolve() { + sendResponse(callback, nil) + } + + public func resolve(_ data: JsonObject) { + resolve(.dictionary(data)) + } + + public func resolve(_ data: JsonValue) { + sendResponse(callback, serialize(data)) + } + + public func resolve(_ data: T) { + do { + let json = try JSONEncoder().encode(data) + sendResponse(callback, String(decoding: json, as: UTF8.self)) + } catch { + sendResponse(self.error, "\"\(error)\"") + } + } + + public func reject( + _ message: String, code: String? = nil, error: Error? = nil, data: JsonValue? = nil + ) { + let payload: NSMutableDictionary = [ + "message": message + ] + + if let code = code { + payload["code"] = code + } + + if let error = error { + payload["error"] = error + } + + if let data = data { + switch data { + case .dictionary(let dict): + for entry in dict { + payload[entry.key] = entry.value + } + } + } + + sendResponse(self.error, serialize(.dictionary(payload as! JsonObject))) + } + + public func unimplemented() { + unimplemented("not implemented") + } + + public func unimplemented(_ message: String) { + reject(message) + } + + public func unavailable() { + unavailable("not available") + } + + public func unavailable(_ message: String) { + reject(message) + } +} diff --git a/plugins/store/.tauri/tauri-api/Sources/Tauri/JSTypes.swift b/plugins/store/.tauri/tauri-api/Sources/Tauri/JSTypes.swift new file mode 100644 index 00000000..0ac9f2c7 --- /dev/null +++ b/plugins/store/.tauri/tauri-api/Sources/Tauri/JSTypes.swift @@ -0,0 +1,123 @@ +// Copyright 2019-2024 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +import Foundation + +// declare our empty protocol, and conformance, for typing +public protocol JSValue {} +extension String: JSValue {} +extension Bool: JSValue {} +extension Int: JSValue {} +extension Float: JSValue {} +extension Double: JSValue {} +extension NSNumber: JSValue {} +extension NSNull: JSValue {} +extension Array: JSValue {} +extension Date: JSValue {} +extension Dictionary: JSValue where Key == String, Value == JSValue {} + +// convenience aliases +public typealias JSObject = [String: JSValue] +public typealias JSArray = [JSValue] + +extension Dictionary where Key == String, Value == JSValue { + public func getValue(_ key: String) -> JSValue? { + return self[key] + } + + public func getString(_ key: String) -> String? { + return self[key] as? String + } + + public func getBool(_ key: String) -> Bool? { + return self[key] as? Bool + } + + public func getInt(_ key: String) -> Int? { + return self[key] as? Int + } + + public func getFloat(_ key: String) -> Float? { + if let floatValue = self[key] as? Float { + return floatValue + } else if let doubleValue = self[key] as? Double { + return Float(doubleValue) + } + return nil + } + + public func getDouble(_ key: String) -> Double? { + return self[key] as? Double + } + + public func getArray(_ key: String) -> JSArray? { + return self[key] as? JSArray + } + + public func getObject(_ key: String) -> JSObject? { + return self[key] as? JSObject + } +} + +/* + Simply casting objects from foundation class clusters (such as __NSArrayM) + doesn't work with the JSValue protocol and will always fail. So we need to + recursively and explicitly convert each value in the dictionary. + */ +public enum JSTypes {} +extension JSTypes { + public static func coerceDictionaryToJSObject( + _ dictionary: NSDictionary?, formattingDatesAsStrings: Bool = false + ) -> JSObject? { + return coerceToJSValue(dictionary, formattingDates: formattingDatesAsStrings) as? JSObject + } + + public static func coerceDictionaryToJSObject( + _ dictionary: [AnyHashable: Any]?, formattingDatesAsStrings: Bool = false + ) -> JSObject? { + return coerceToJSValue(dictionary, formattingDates: formattingDatesAsStrings) as? JSObject + } +} + +private let dateStringFormatter = ISO8601DateFormatter() + +// We need a large switch statement because we have a lot of types. +// swiftlint:disable:next cyclomatic_complexity +private func coerceToJSValue(_ value: Any?, formattingDates: Bool) -> JSValue? { + guard let value = value else { + return nil + } + switch value { + case let stringValue as String: + return stringValue + case let numberValue as NSNumber: + return numberValue + case let boolValue as Bool: + return boolValue + case let intValue as Int: + return intValue + case let floatValue as Float: + return floatValue + case let doubleValue as Double: + return doubleValue + case let dateValue as Date: + if formattingDates { + return dateStringFormatter.string(from: dateValue) + } + return dateValue + case let nullValue as NSNull: + return nullValue + case let arrayValue as NSArray: + return arrayValue.compactMap { coerceToJSValue($0, formattingDates: formattingDates) } + case let dictionaryValue as NSDictionary: + let keys = dictionaryValue.allKeys.compactMap { $0 as? String } + var result: JSObject = [:] + for key in keys { + result[key] = coerceToJSValue(dictionaryValue[key], formattingDates: formattingDates) + } + return result + default: + return nil + } +} diff --git a/plugins/store/.tauri/tauri-api/Sources/Tauri/JsonValue.swift b/plugins/store/.tauri/tauri-api/Sources/Tauri/JsonValue.swift new file mode 100644 index 00000000..ae6d96f7 --- /dev/null +++ b/plugins/store/.tauri/tauri-api/Sources/Tauri/JsonValue.swift @@ -0,0 +1,58 @@ +// Copyright 2019-2024 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +import Foundation + +public typealias JsonObject = [String: Any?] + +public enum JsonValue { + case dictionary(JsonObject) + + enum SerializationError: Error { + case invalidObject + } + + public func jsonRepresentation(includingFields: JsonObject? = nil) throws -> String? { + switch self { + case .dictionary(var dictionary): + if let fields = includingFields { + dictionary.merge(fields) { (current, _) in current } + } + dictionary = prepare(dictionary: dictionary) + guard JSONSerialization.isValidJSONObject(dictionary) else { + throw SerializationError.invalidObject + } + let data = try JSONSerialization.data(withJSONObject: dictionary, options: []) + return String(data: data, encoding: .utf8) + } + } + + private static let formatter = ISO8601DateFormatter() + + private func prepare(dictionary: JsonObject) -> JsonObject { + return dictionary.mapValues { (value) -> Any in + if let date = value as? Date { + return JsonValue.formatter.string(from: date) + } else if let aDictionary = value as? JsonObject { + return prepare(dictionary: aDictionary) + } else if let anArray = value as? [Any] { + return prepare(array: anArray) + } + return value + } + } + + private func prepare(array: [Any]) -> [Any] { + return array.map { (value) -> Any in + if let date = value as? Date { + return JsonValue.formatter.string(from: date) + } else if let aDictionary = value as? JsonObject { + return prepare(dictionary: aDictionary) + } else if let anArray = value as? [Any] { + return prepare(array: anArray) + } + return value + } + } +} diff --git a/plugins/store/.tauri/tauri-api/Sources/Tauri/Logger.swift b/plugins/store/.tauri/tauri-api/Sources/Tauri/Logger.swift new file mode 100644 index 00000000..9fa6e3fe --- /dev/null +++ b/plugins/store/.tauri/tauri-api/Sources/Tauri/Logger.swift @@ -0,0 +1,58 @@ +// Copyright 2019-2024 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +import os.log +import UIKit + +/// Wrapper class for os_log function +public class Logger { + private static var _enabled = false + public static var enabled: Bool { + get { + #if DEBUG + return true + #else + return _enabled + #endif + } + set { + Logger._enabled = newValue + } + } + + static func log(_ items: Any..., category: String, type: OSLogType) { + if Logger.enabled { + var message = "" + let last = items.count - 1 + for (index, item) in items.enumerated() { + message += "\(item)" + if index != last { + message += " " + } + } + let log = OSLog(subsystem: Bundle.main.bundleIdentifier ?? "-", category: category) + os_log("%{public}@", log: log, type: type, String(message.prefix(4068))) + } + } + + public static func debug(_ items: Any..., category: String = "app") { + #if DEBUG + Logger.log(items, category: category, type: OSLogType.default) + #else + Logger.log(items, category: category, type: OSLogType.debug) + #endif + } + + public static func info(_ items: Any..., category: String = "app") { + #if DEBUG + Logger.log(items, category: category, type: OSLogType.default) + #else + Logger.log(items, category: category, type: OSLogType.info) + #endif + } + + public static func error(_ items: Any..., category: String = "app") { + Logger.log(items, category: category, type: OSLogType.error) + } +} diff --git a/plugins/store/.tauri/tauri-api/Sources/Tauri/Plugin/Plugin.swift b/plugins/store/.tauri/tauri-api/Sources/Tauri/Plugin/Plugin.swift new file mode 100644 index 00000000..7ce67f9b --- /dev/null +++ b/plugins/store/.tauri/tauri-api/Sources/Tauri/Plugin/Plugin.swift @@ -0,0 +1,81 @@ +// Copyright 2019-2024 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +import WebKit +import os.log + +struct RegisterListenerArgs: Decodable { + let event: String + let handler: Channel +} + +struct RemoveListenerArgs: Decodable { + let event: String + let channelId: UInt64 +} + +open class Plugin: NSObject { + public let manager: PluginManager = PluginManager.shared + var config: String = "{}" + private var listeners = [String: [Channel]]() + + internal func setConfig(_ config: String) { + self.config = config + } + + public func parseConfig(_ type: T.Type) throws -> T { + let jsonData = self.config.data(using: .utf8)! + let decoder = JSONDecoder() + return try decoder.decode(type, from: jsonData) + } + + @objc open func load(webview: WKWebView) {} + + @objc open func checkPermissions(_ invoke: Invoke) { + invoke.resolve() + } + + @objc open func requestPermissions(_ invoke: Invoke) { + invoke.resolve() + } + + public func trigger(_ event: String, data: JSObject) { + if let eventListeners = listeners[event] { + for channel in eventListeners { + channel.send(data) + } + } + } + + public func trigger(_ event: String, data: T) throws { + if let eventListeners = listeners[event] { + for channel in eventListeners { + try channel.send(data) + } + } + } + + @objc func registerListener(_ invoke: Invoke) throws { + let args = try invoke.parseArgs(RegisterListenerArgs.self) + + if var eventListeners = listeners[args.event] { + eventListeners.append(args.handler) + } else { + listeners[args.event] = [args.handler] + } + + invoke.resolve() + } + + @objc func removeListener(_ invoke: Invoke) throws { + let args = try invoke.parseArgs(RemoveListenerArgs.self) + + if let eventListeners = listeners[args.event] { + + listeners[args.event] = eventListeners.filter { $0.id != args.channelId } + } + + invoke.resolve() + } +} diff --git a/plugins/store/.tauri/tauri-api/Sources/Tauri/Tauri.swift b/plugins/store/.tauri/tauri-api/Sources/Tauri/Tauri.swift new file mode 100644 index 00000000..9a9c40e3 --- /dev/null +++ b/plugins/store/.tauri/tauri-api/Sources/Tauri/Tauri.swift @@ -0,0 +1,134 @@ +// Copyright 2019-2024 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +import Foundation +import SwiftRs +import UIKit +import WebKit +import os.log + +class PluginHandle { + var instance: Plugin + var loaded = false + + init(plugin: Plugin) { + instance = plugin + } +} + +public class PluginManager { + static let shared: PluginManager = PluginManager() + public var viewController: UIViewController? + var plugins: [String: PluginHandle] = [:] + var ipcDispatchQueue = DispatchQueue(label: "ipc") + public var isSimEnvironment: Bool { + #if targetEnvironment(simulator) + return true + #else + return false + #endif + } + + public func assetUrl(fromLocalURL url: URL?) -> URL? { + guard let inputURL = url else { + return nil + } + + return URL(string: "asset://localhost")!.appendingPathComponent(inputURL.path) + } + + func onWebviewCreated(_ webview: WKWebView) { + for (_, handle) in plugins { + if !handle.loaded { + handle.instance.load(webview: webview) + } + } + } + + func load(name: String, plugin: P, config: String, webview: WKWebView?) { + plugin.setConfig(config) + let handle = PluginHandle(plugin: plugin) + if let webview = webview { + handle.instance.load(webview: webview) + handle.loaded = true + } + plugins[name] = handle + } + + func invoke(name: String, invoke: Invoke) { + if let plugin = plugins[name] { + ipcDispatchQueue.async { + let selectorWithThrows = Selector(("\(invoke.command):error:")) + if plugin.instance.responds(to: selectorWithThrows) { + var error: NSError? = nil + withUnsafeMutablePointer(to: &error) { + let methodIMP: IMP! = plugin.instance.method(for: selectorWithThrows) + unsafeBitCast( + methodIMP, to: (@convention(c) (Any?, Selector, Invoke, OpaquePointer) -> Void).self)( + plugin.instance, selectorWithThrows, invoke, OpaquePointer($0)) + } + if let error = error { + invoke.reject("\(error)") + // TODO: app crashes without this leak + let _ = Unmanaged.passRetained(error) + } + } else { + let selector = Selector(("\(invoke.command):")) + if plugin.instance.responds(to: selector) { + plugin.instance.perform(selector, with: invoke) + } else { + invoke.reject("No command \(invoke.command) found for plugin \(name)") + } + } + } + } else { + invoke.reject("Plugin \(name) not initialized") + } + } +} + +extension PluginManager: NSCopying { + public func copy(with zone: NSZone? = nil) -> Any { + return self + } +} + +@_cdecl("register_plugin") +func registerPlugin(name: SRString, plugin: NSObject, config: SRString, webview: WKWebView?) { + PluginManager.shared.load( + name: name.toString(), + plugin: plugin as! Plugin, + config: config.toString(), + webview: webview + ) +} + +@_cdecl("on_webview_created") +func onWebviewCreated(webview: WKWebView, viewController: UIViewController) { + PluginManager.shared.viewController = viewController + PluginManager.shared.onWebviewCreated(webview) +} + +@_cdecl("run_plugin_command") +func runCommand( + id: Int, + name: SRString, + command: SRString, + data: SRString, + callback: @escaping @convention(c) (Int, Bool, UnsafePointer) -> Void, + sendChannelData: @escaping @convention(c) (UInt64, UnsafePointer) -> Void +) { + let callbackId: UInt64 = 0 + let errorId: UInt64 = 1 + let invoke = Invoke( + command: command.toString(), callback: callbackId, error: errorId, + sendResponse: { (fn: UInt64, payload: String?) -> Void in + let success = fn == callbackId + callback(id, success, payload ?? "null") + }, + sendChannelData: { (id: UInt64, payload: String) -> Void in + sendChannelData(id, payload) + }, data: data.toString()) + PluginManager.shared.invoke(name: name.toString(), invoke: invoke) +} diff --git a/plugins/store/.tauri/tauri-api/Sources/Tauri/UiUtils.swift b/plugins/store/.tauri/tauri-api/Sources/Tauri/UiUtils.swift new file mode 100644 index 00000000..4097596e --- /dev/null +++ b/plugins/store/.tauri/tauri-api/Sources/Tauri/UiUtils.swift @@ -0,0 +1,15 @@ +// Copyright 2019-2024 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +import UIKit + +public class UIUtils { + public static func centerPopover(rootViewController: UIViewController?, popoverController: UIViewController) { + if let viewController = rootViewController { + popoverController.popoverPresentationController?.sourceRect = CGRect(x: viewController.view.center.x, y: viewController.view.center.y, width: 0, height: 0) + popoverController.popoverPresentationController?.sourceView = viewController.view + popoverController.popoverPresentationController?.permittedArrowDirections = UIPopoverArrowDirection.up + } + } +} diff --git a/plugins/store/Cargo.lock b/plugins/store/Cargo.lock new file mode 100644 index 00000000..6b00276f --- /dev/null +++ b/plugins/store/Cargo.lock @@ -0,0 +1,4636 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "addr2line" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "alloc-no-stdlib" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" + +[[package]] +name = "alloc-stdlib" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" +dependencies = [ + "alloc-no-stdlib", +] + +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anyhow" +version = "1.0.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" + +[[package]] +name = "atk" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "241b621213072e993be4f6f3a9e4b45f65b7e6faad43001be957184b7bb1824b" +dependencies = [ + "atk-sys", + "glib", + "libc", +] + +[[package]] +name = "atk-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5e48b684b0ca77d2bbadeef17424c2ea3c897d44d566a1617e7e8f30614d086" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "backtrace" +version = "0.3.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-targets 0.52.6", +] + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" +dependencies = [ + "serde", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block2" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c132eebf10f5cad5289222520a4a058514204aed6d791f1cf4fe8088b82d15f" +dependencies = [ + "objc2 0.5.2", +] + +[[package]] +name = "block2" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "340d2f0bdb2a43c1d3cd40513185b2bd7def0aa1052f956455114bc98f82dcf2" +dependencies = [ + "objc2 0.6.1", +] + +[[package]] +name = "brotli" +version = "7.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc97b8f16f944bba54f0433f07e30be199b6dc2bd25937444bbad560bcea29bd" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", + "brotli-decompressor", +] + +[[package]] +name = "brotli-decompressor" +version = "4.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a334ef7c9e23abf0ce748e8cd309037da93e606ad52eb372e4ce327a0dcfbdfd" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", +] + +[[package]] +name = "bumpalo" +version = "3.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" + +[[package]] +name = "bytemuck" +version = "1.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9134a6ef01ce4b366b50689c94f82c14bc72bc5d0386829828a2e2752ef7958c" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" +dependencies = [ + "serde", +] + +[[package]] +name = "cairo-rs" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ca26ef0159422fb77631dc9d17b102f253b876fe1586b03b803e63a309b4ee2" +dependencies = [ + "bitflags 2.9.0", + "cairo-sys-rs", + "glib", + "libc", + "once_cell", + "thiserror 1.0.69", +] + +[[package]] +name = "cairo-sys-rs" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "685c9fa8e590b8b3d678873528d83411db17242a73fccaed827770ea0fedda51" +dependencies = [ + "glib-sys", + "libc", + "system-deps", +] + +[[package]] +name = "camino" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b96ec4966b5813e2c0507c1f86115c8c5abaadc3980879c3424042a02fd1ad3" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo-platform" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e35af189006b9c0f00a064685c727031e3ed2d8020f7ba284d78cc2671bd36ea" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo_metadata" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd5eb614ed4c27c5d706420e4320fbe3216ab31fa1c33cd8246ac36dae4479ba" +dependencies = [ + "camino", + "cargo-platform", + "semver", + "serde", + "serde_json", + "thiserror 2.0.12", +] + +[[package]] +name = "cargo_toml" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02260d489095346e5cafd04dea8e8cb54d1d74fcd759022a9b72986ebe9a1257" +dependencies = [ + "serde", + "toml", +] + +[[package]] +name = "cc" +version = "1.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8691782945451c1c383942c4874dbe63814f61cb57ef773cda2972682b7bb3c0" +dependencies = [ + "shlex", +] + +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + +[[package]] +name = "cfb" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d38f2da7a0a2c4ccf0065be06397cc26a81f4e528be095826eee9d4adbb8c60f" +dependencies = [ + "byteorder", + "fnv", + "uuid", +] + +[[package]] +name = "cfg-expr" +version = "0.15.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02" +dependencies = [ + "smallvec", + "target-lexicon", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "chrono" +version = "0.4.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "num-traits", + "serde", + "windows-link", +] + +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + +[[package]] +name = "cookie" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747" +dependencies = [ + "time", + "version_check", +] + +[[package]] +name = "core-foundation" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b55271e5c8c478ad3f38ad24ef34923091e0548492a266d19b3c0b4d82574c63" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "core-graphics" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa95a34622365fa5bbf40b20b75dba8dfa8c94c734aea8ac9a5ca38af14316f1" +dependencies = [ + "bitflags 2.9.0", + "core-foundation", + "core-graphics-types", + "foreign-types", + "libc", +] + +[[package]] +name = "core-graphics-types" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" +dependencies = [ + "bitflags 2.9.0", + "core-foundation", + "libc", +] + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "cssparser" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "754b69d351cdc2d8ee09ae203db831e005560fc6030da058f86ad60c92a9cb0a" +dependencies = [ + "cssparser-macros", + "dtoa-short", + "itoa 0.4.8", + "matches", + "phf 0.8.0", + "proc-macro2", + "quote", + "smallvec", + "syn 1.0.109", +] + +[[package]] +name = "cssparser-macros" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" +dependencies = [ + "quote", + "syn 2.0.101", +] + +[[package]] +name = "ctor" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a2785755761f3ddc1492979ce1e48d2c00d09311c39e4466429188f3dd6501" +dependencies = [ + "quote", + "syn 2.0.101", +] + +[[package]] +name = "darling" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.101", +] + +[[package]] +name = "darling_macro" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "deranged" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e" +dependencies = [ + "powerfmt", + "serde", +] + +[[package]] +name = "derive_more" +version = "0.99.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6edb4b64a43d977b8e99788fe3a04d483834fba1215a7e02caa415b626497f7f" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version", + "syn 2.0.101", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "dirs" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys 0.59.0", +] + +[[package]] +name = "dispatch" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" + +[[package]] +name = "dispatch2" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec" +dependencies = [ + "bitflags 2.9.0", + "objc2 0.6.1", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "dlopen2" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1297103d2bbaea85724fcee6294c2d50b1081f9ad47d0f6f6f61eda65315a6" +dependencies = [ + "dlopen2_derive", + "libc", + "once_cell", + "winapi", +] + +[[package]] +name = "dlopen2_derive" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2b99bf03862d7f545ebc28ddd33a665b50865f4dfd84031a393823879bd4c54" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "dpi" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" +dependencies = [ + "serde", +] + +[[package]] +name = "dtoa" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6add3b8cff394282be81f3fc1a0605db594ed69890078ca6e2cab1c408bcf04" + +[[package]] +name = "dtoa-short" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd1511a7b6a56299bd043a9c167a6d2bfb37bf84a6dfceaba651168adfb43c87" +dependencies = [ + "dtoa", +] + +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + +[[package]] +name = "dyn-clone" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c7a8fb8a9fbf66c1f703fe16184d10ca0ee9d23be5b4436400408ba54a95005" + +[[package]] +name = "embed-resource" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fbc6e0d8e0c03a655b53ca813f0463d2c956bc4db8138dbc89f120b066551e3" +dependencies = [ + "cc", + "memchr", + "rustc_version", + "toml", + "vswhom", + "winreg", +] + +[[package]] +name = "embed_plist" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ef6b89e5b37196644d8796de5268852ff179b44e96276cf4290264843743bb7" + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "erased-serde" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e004d887f51fcb9fef17317a2f3525c887d8aa3f4f50fed920816a688284a5b7" +dependencies = [ + "serde", + "typeid", +] + +[[package]] +name = "fdeflate" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "field-offset" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38e2275cc4e4fc009b0669731a1e5ab7ebf11f469eaede2bab9309a5b4d6057f" +dependencies = [ + "memoffset", + "rustc_version", +] + +[[package]] +name = "flate2" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ced92e76e966ca2fd84c8f7aa01a4aea65b0eb6648d72f7c8f3e2764a67fece" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foreign-types" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" +dependencies = [ + "foreign-types-macros", + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-macros" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "foreign-types-shared" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futf" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df420e2e84819663797d1ec6544b13c5be84629e7bb00dc960d6917db2987843" +dependencies = [ + "mac", + "new_debug_unreachable", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", +] + +[[package]] +name = "gdk" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9f245958c627ac99d8e529166f9823fb3b838d1d41fd2b297af3075093c2691" +dependencies = [ + "cairo-rs", + "gdk-pixbuf", + "gdk-sys", + "gio", + "glib", + "libc", + "pango", +] + +[[package]] +name = "gdk-pixbuf" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50e1f5f1b0bfb830d6ccc8066d18db35c487b1b2b1e8589b5dfe9f07e8defaec" +dependencies = [ + "gdk-pixbuf-sys", + "gio", + "glib", + "libc", + "once_cell", +] + +[[package]] +name = "gdk-pixbuf-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9839ea644ed9c97a34d129ad56d38a25e6756f99f3a88e15cd39c20629caf7" +dependencies = [ + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "gdk-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c2d13f38594ac1e66619e188c6d5a1adb98d11b2fcf7894fc416ad76aa2f3f7" +dependencies = [ + "cairo-sys-rs", + "gdk-pixbuf-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "pango-sys", + "pkg-config", + "system-deps", +] + +[[package]] +name = "gdkwayland-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "140071d506d223f7572b9f09b5e155afbd77428cd5cc7af8f2694c41d98dfe69" +dependencies = [ + "gdk-sys", + "glib-sys", + "gobject-sys", + "libc", + "pkg-config", + "system-deps", +] + +[[package]] +name = "gdkx11" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3caa00e14351bebbc8183b3c36690327eb77c49abc2268dd4bd36b856db3fbfe" +dependencies = [ + "gdk", + "gdkx11-sys", + "gio", + "glib", + "libc", + "x11", +] + +[[package]] +name = "gdkx11-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e2e7445fe01ac26f11601db260dd8608fe172514eb63b3b5e261ea6b0f4428d" +dependencies = [ + "gdk-sys", + "glib-sys", + "libc", + "system-deps", + "x11", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.9.0+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasi 0.14.2+wasi-0.2.4", +] + +[[package]] +name = "gimli" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" + +[[package]] +name = "gio" +version = "0.18.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4fc8f532f87b79cbc51a79748f16a6828fb784be93145a322fa14d06d354c73" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "gio-sys", + "glib", + "libc", + "once_cell", + "pin-project-lite", + "smallvec", + "thiserror 1.0.69", +] + +[[package]] +name = "gio-sys" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37566df850baf5e4cb0dfb78af2e4b9898d817ed9263d1090a2df958c64737d2" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", + "winapi", +] + +[[package]] +name = "glib" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "233daaf6e83ae6a12a52055f568f9d7cf4671dabb78ff9560ab6da230ce00ee5" +dependencies = [ + "bitflags 2.9.0", + "futures-channel", + "futures-core", + "futures-executor", + "futures-task", + "futures-util", + "gio-sys", + "glib-macros", + "glib-sys", + "gobject-sys", + "libc", + "memchr", + "once_cell", + "smallvec", + "thiserror 1.0.69", +] + +[[package]] +name = "glib-macros" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bb0228f477c0900c880fd78c8759b95c7636dbd7842707f49e132378aa2acdc" +dependencies = [ + "heck 0.4.1", + "proc-macro-crate 2.0.0", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "glib-sys" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "063ce2eb6a8d0ea93d2bf8ba1957e78dbab6be1c2220dd3daca57d5a9d869898" +dependencies = [ + "libc", + "system-deps", +] + +[[package]] +name = "glob" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" + +[[package]] +name = "gobject-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0850127b514d1c4a4654ead6dedadb18198999985908e6ffe4436f53c785ce44" +dependencies = [ + "glib-sys", + "libc", + "system-deps", +] + +[[package]] +name = "gtk" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd56fb197bfc42bd5d2751f4f017d44ff59fbb58140c6b49f9b3b2bdab08506a" +dependencies = [ + "atk", + "cairo-rs", + "field-offset", + "futures-channel", + "gdk", + "gdk-pixbuf", + "gio", + "glib", + "gtk-sys", + "gtk3-macros", + "libc", + "pango", + "pkg-config", +] + +[[package]] +name = "gtk-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f29a1c21c59553eb7dd40e918be54dccd60c52b049b75119d5d96ce6b624414" +dependencies = [ + "atk-sys", + "cairo-sys-rs", + "gdk-pixbuf-sys", + "gdk-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "pango-sys", + "system-deps", +] + +[[package]] +name = "gtk3-macros" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ff3c5b21f14f0736fed6dcfc0bfb4225ebf5725f3c0209edeec181e4d73e9d" +dependencies = [ + "proc-macro-crate 1.3.1", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hashbrown" +version = "0.15.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84b26c544d002229e640969970a2e74021aadf6e2f96372b9c58eff97de08eb3" + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "html5ever" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bea68cab48b8459f17cf1c944c67ddc572d272d9f2b274140f223ecb1da4a3b7" +dependencies = [ + "log", + "mac", + "markup5ever", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "http" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" +dependencies = [ + "bytes", + "fnv", + "itoa 1.0.15", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "hyper" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "httparse", + "itoa 1.0.15", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "497bbc33a26fdd4af9ed9c70d63f61cf56a938375fbb32df34db9b1cd6d643f2" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "hyper", + "libc", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "ico" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc50b891e4acf8fe0e71ef88ec43ad82ee07b3810ad09de10f1d01f072ed4b98" +dependencies = [ + "byteorder", + "png", +] + +[[package]] +name = "icu_collections" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locid" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_locid_transform" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_locid_transform_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_locid_transform_data" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7515e6d781098bf9f7205ab3fc7e9709d34554ae0b21ddbcb5febfa4bc7df11d" + +[[package]] +name = "icu_normalizer" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "utf16_iter", + "utf8_iter", + "write16", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5e8338228bdc8ab83303f16b797e177953730f601a96c25d10cb3ab0daa0cb7" + +[[package]] +name = "icu_properties" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locid_transform", + "icu_properties_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85fb8799753b75aee8d2a21d7c14d9f38921b54b3dbda10f5a3c7a7b82dba5e2" + +[[package]] +name = "icu_provider" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_provider_macros", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_provider_macros" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "idna" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde", +] + +[[package]] +name = "indexmap" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" +dependencies = [ + "equivalent", + "hashbrown 0.15.3", + "serde", +] + +[[package]] +name = "infer" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a588916bfdfd92e71cacef98a63d9b1f0d74d6599980d11894290e7ddefffcf7" +dependencies = [ + "cfb", +] + +[[package]] +name = "ipnet" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" + +[[package]] +name = "itoa" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "javascriptcore-rs" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca5671e9ffce8ffba57afc24070e906da7fc4b1ba66f2cabebf61bf2ea257fcc" +dependencies = [ + "bitflags 1.3.2", + "glib", + "javascriptcore-rs-sys", +] + +[[package]] +name = "javascriptcore-rs-sys" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af1be78d14ffa4b75b66df31840478fef72b51f8c2465d4ca7c194da9f7a5124" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "jni" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +dependencies = [ + "cesu8", + "cfg-if", + "combine", + "jni-sys", + "log", + "thiserror 1.0.69", + "walkdir", + "windows-sys 0.45.0", +] + +[[package]] +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" + +[[package]] +name = "js-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "json-patch" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "863726d7afb6bc2590eeff7135d923545e5e964f004c2ccf8716c25e70a86f08" +dependencies = [ + "jsonptr", + "serde", + "serde_json", + "thiserror 1.0.69", +] + +[[package]] +name = "jsonptr" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dea2b27dd239b2556ed7a25ba842fe47fd602e7fc7433c2a8d6106d4d9edd70" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] +name = "keyboard-types" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b750dcadc39a09dbadd74e118f6dd6598df77fa01df0cfcdc52c28dece74528a" +dependencies = [ + "bitflags 2.9.0", + "serde", + "unicode-segmentation", +] + +[[package]] +name = "kuchikiki" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f29e4755b7b995046f510a7520c42b2fed58b77bd94d5a87a8eb43d2fd126da8" +dependencies = [ + "cssparser", + "html5ever", + "indexmap 1.9.3", + "matches", + "selectors", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libappindicator" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03589b9607c868cc7ae54c0b2a22c8dc03dd41692d48f2d7df73615c6a95dc0a" +dependencies = [ + "glib", + "gtk", + "gtk-sys", + "libappindicator-sys", + "log", +] + +[[package]] +name = "libappindicator-sys" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e9ec52138abedcc58dc17a7c6c0c00a2bdb4f3427c7f63fa97fd0d859155caf" +dependencies = [ + "gtk-sys", + "libloading", + "once_cell", +] + +[[package]] +name = "libc" +version = "0.2.172" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" + +[[package]] +name = "libloading" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" +dependencies = [ + "cfg-if", + "winapi", +] + +[[package]] +name = "libredox" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +dependencies = [ + "bitflags 2.9.0", + "libc", +] + +[[package]] +name = "litemap" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23fb14cb19457329c82206317a5663005a4d404783dc74f4252769b0d5f42856" + +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" + +[[package]] +name = "mac" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" + +[[package]] +name = "markup5ever" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2629bb1404f3d34c2e921f21fd34ba00b206124c81f65c50b43b6aaefeb016" +dependencies = [ + "log", + "phf 0.10.1", + "phf_codegen 0.10.0", + "string_cache", + "string_cache_codegen", + "tendril", +] + +[[package]] +name = "matches" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "miniz_oxide" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "mio" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" +dependencies = [ + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", + "windows-sys 0.52.0", +] + +[[package]] +name = "muda" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4de14a9b5d569ca68d7c891d613b390cf5ab4f851c77aaa2f9e435555d3d9492" +dependencies = [ + "crossbeam-channel", + "dpi", + "gtk", + "keyboard-types", + "objc2 0.6.1", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation 0.3.1", + "once_cell", + "png", + "serde", + "thiserror 2.0.12", + "windows-sys 0.59.0", +] + +[[package]] +name = "ndk" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" +dependencies = [ + "bitflags 2.9.0", + "jni-sys", + "log", + "ndk-sys", + "num_enum", + "raw-window-handle", + "thiserror 1.0.69", +] + +[[package]] +name = "ndk-context" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" + +[[package]] +name = "ndk-sys" +version = "0.6.0+11769913" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873" +dependencies = [ + "jni-sys", +] + +[[package]] +name = "new_debug_unreachable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" + +[[package]] +name = "nodrop" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" + +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_enum" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e613fc340b2220f734a8595782c551f1250e969d87d3be1ae0579e8d4065179" +dependencies = [ + "num_enum_derive", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56" +dependencies = [ + "proc-macro-crate 3.3.0", + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "objc-sys" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb91bdd390c7ce1a8607f35f3ca7151b65afc0ff5ff3b34fa350f7d7c7e4310" + +[[package]] +name = "objc2" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46a785d4eeff09c14c487497c162e92766fbb3e4059a71840cecc03d9a50b804" +dependencies = [ + "objc-sys", + "objc2-encode", +] + +[[package]] +name = "objc2" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88c6597e14493ab2e44ce58f2fdecf095a51f12ca57bec060a11c57332520551" +dependencies = [ + "objc2-encode", + "objc2-exception-helper", +] + +[[package]] +name = "objc2-app-kit" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6f29f568bec459b0ddff777cec4fe3fd8666d82d5a40ebd0ff7e66134f89bcc" +dependencies = [ + "bitflags 2.9.0", + "block2 0.6.1", + "libc", + "objc2 0.6.1", + "objc2-cloud-kit", + "objc2-core-data", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-core-image", + "objc2-foundation 0.3.1", + "objc2-quartz-core 0.3.1", +] + +[[package]] +name = "objc2-cloud-kit" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17614fdcd9b411e6ff1117dfb1d0150f908ba83a7df81b1f118005fe0a8ea15d" +dependencies = [ + "bitflags 2.9.0", + "objc2 0.6.1", + "objc2-foundation 0.3.1", +] + +[[package]] +name = "objc2-core-data" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291fbbf7d29287518e8686417cf7239c74700fd4b607623140a7d4a3c834329d" +dependencies = [ + "bitflags 2.9.0", + "objc2 0.6.1", + "objc2-foundation 0.3.1", +] + +[[package]] +name = "objc2-core-foundation" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c10c2894a6fed806ade6027bcd50662746363a9589d3ec9d9bef30a4e4bc166" +dependencies = [ + "bitflags 2.9.0", + "dispatch2", + "objc2 0.6.1", +] + +[[package]] +name = "objc2-core-graphics" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "989c6c68c13021b5c2d6b71456ebb0f9dc78d752e86a98da7c716f4f9470f5a4" +dependencies = [ + "bitflags 2.9.0", + "dispatch2", + "objc2 0.6.1", + "objc2-core-foundation", + "objc2-io-surface", +] + +[[package]] +name = "objc2-core-image" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79b3dc0cc4386b6ccf21c157591b34a7f44c8e75b064f85502901ab2188c007e" +dependencies = [ + "objc2 0.6.1", + "objc2-foundation 0.3.1", +] + +[[package]] +name = "objc2-encode" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" + +[[package]] +name = "objc2-exception-helper" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7a1c5fbb72d7735b076bb47b578523aedc40f3c439bea6dfd595c089d79d98a" +dependencies = [ + "cc", +] + +[[package]] +name = "objc2-foundation" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" +dependencies = [ + "bitflags 2.9.0", + "block2 0.5.1", + "libc", + "objc2 0.5.2", +] + +[[package]] +name = "objc2-foundation" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "900831247d2fe1a09a683278e5384cfb8c80c79fe6b166f9d14bfdde0ea1b03c" +dependencies = [ + "bitflags 2.9.0", + "block2 0.6.1", + "libc", + "objc2 0.6.1", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-io-surface" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7282e9ac92529fa3457ce90ebb15f4ecbc383e8338060960760fa2cf75420c3c" +dependencies = [ + "bitflags 2.9.0", + "objc2 0.6.1", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-metal" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" +dependencies = [ + "bitflags 2.9.0", + "block2 0.5.1", + "objc2 0.5.2", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-quartz-core" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" +dependencies = [ + "bitflags 2.9.0", + "block2 0.5.1", + "objc2 0.5.2", + "objc2-foundation 0.2.2", + "objc2-metal", +] + +[[package]] +name = "objc2-quartz-core" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90ffb6a0cd5f182dc964334388560b12a57f7b74b3e2dec5e2722aa2dfb2ccd5" +dependencies = [ + "bitflags 2.9.0", + "objc2 0.6.1", + "objc2-foundation 0.3.1", +] + +[[package]] +name = "objc2-ui-kit" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25b1312ad7bc8a0e92adae17aa10f90aae1fb618832f9b993b022b591027daed" +dependencies = [ + "bitflags 2.9.0", + "objc2 0.6.1", + "objc2-core-foundation", + "objc2-foundation 0.3.1", +] + +[[package]] +name = "objc2-web-kit" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91672909de8b1ce1c2252e95bbee8c1649c9ad9d14b9248b3d7b4c47903c47ad" +dependencies = [ + "bitflags 2.9.0", + "block2 0.6.1", + "objc2 0.6.1", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation 0.3.1", +] + +[[package]] +name = "object" +version = "0.36.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + +[[package]] +name = "pango" +version = "0.18.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ca27ec1eb0457ab26f3036ea52229edbdb74dee1edd29063f5b9b010e7ebee4" +dependencies = [ + "gio", + "glib", + "libc", + "once_cell", + "pango-sys", +] + +[[package]] +name = "pango-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "436737e391a843e5933d6d9aa102cb126d501e815b83601365a948a518555dc5" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "parking_lot" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets 0.52.6", +] + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "phf" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12" +dependencies = [ + "phf_macros 0.8.0", + "phf_shared 0.8.0", + "proc-macro-hack", +] + +[[package]] +name = "phf" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259" +dependencies = [ + "phf_shared 0.10.0", +] + +[[package]] +name = "phf" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" +dependencies = [ + "phf_macros 0.11.3", + "phf_shared 0.11.3", +] + +[[package]] +name = "phf_codegen" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbffee61585b0411840d3ece935cce9cb6321f01c45477d30066498cd5e1a815" +dependencies = [ + "phf_generator 0.8.0", + "phf_shared 0.8.0", +] + +[[package]] +name = "phf_codegen" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb1c3a8bc4dd4e5cfce29b44ffc14bedd2ee294559a294e2a4d4c9e9a6a13cd" +dependencies = [ + "phf_generator 0.10.0", + "phf_shared 0.10.0", +] + +[[package]] +name = "phf_generator" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17367f0cc86f2d25802b2c26ee58a7b23faeccf78a396094c13dced0d0182526" +dependencies = [ + "phf_shared 0.8.0", + "rand 0.7.3", +] + +[[package]] +name = "phf_generator" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6" +dependencies = [ + "phf_shared 0.10.0", + "rand 0.8.5", +] + +[[package]] +name = "phf_generator" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" +dependencies = [ + "phf_shared 0.11.3", + "rand 0.8.5", +] + +[[package]] +name = "phf_macros" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f6fde18ff429ffc8fe78e2bf7f8b7a5a5a6e2a8b58bc5a9ac69198bbda9189c" +dependencies = [ + "phf_generator 0.8.0", + "phf_shared 0.8.0", + "proc-macro-hack", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "phf_macros" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" +dependencies = [ + "phf_generator 0.11.3", + "phf_shared 0.11.3", + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "phf_shared" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7" +dependencies = [ + "siphasher 0.3.11", +] + +[[package]] +name = "phf_shared" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" +dependencies = [ + "siphasher 0.3.11", +] + +[[package]] +name = "phf_shared" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" +dependencies = [ + "siphasher 1.0.1", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + +[[package]] +name = "plist" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eac26e981c03a6e53e0aee43c113e3202f5581d5360dae7bd2c70e800dd0451d" +dependencies = [ + "base64 0.22.1", + "indexmap 2.9.0", + "quick-xml", + "serde", + "time", +] + +[[package]] +name = "png" +version = "0.17.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526" +dependencies = [ + "bitflags 1.3.2", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "precomputed-hash" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" + +[[package]] +name = "proc-macro-crate" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +dependencies = [ + "once_cell", + "toml_edit 0.19.15", +] + +[[package]] +name = "proc-macro-crate" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e8366a6159044a37876a2b9817124296703c586a5c92e2c53751fa06d8d43e8" +dependencies = [ + "toml_edit 0.20.7", +] + +[[package]] +name = "proc-macro-crate" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35" +dependencies = [ + "toml_edit 0.22.26", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro-hack" +version = "0.5.20+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" + +[[package]] +name = "proc-macro2" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quick-xml" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d3a6e5838b60e0e8fa7a43f22ade549a37d61f8bdbe636d0d7816191de969c2" +dependencies = [ + "memchr", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" + +[[package]] +name = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +dependencies = [ + "getrandom 0.1.16", + "libc", + "rand_chacha 0.2.2", + "rand_core 0.5.1", + "rand_hc", + "rand_pcg", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +dependencies = [ + "ppv-lite86", + "rand_core 0.5.1", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom 0.1.16", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.16", +] + +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +dependencies = [ + "rand_core 0.5.1", +] + +[[package]] +name = "rand_pcg" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429" +dependencies = [ + "rand_core 0.5.1", +] + +[[package]] +name = "raw-window-handle" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" + +[[package]] +name = "redox_syscall" +version = "0.5.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "928fca9cf2aa042393a8325b9ead81d2f0df4cb12e1e24cef072922ccd99c5af" +dependencies = [ + "bitflags 2.9.0", +] + +[[package]] +name = "redox_users" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd6f9d3d47bdd2ad6945c5015a226ec6155d0bcdfd8f7cd29f86b71f8de99d2b" +dependencies = [ + "getrandom 0.2.16", + "libredox", + "thiserror 2.0.12", +] + +[[package]] +name = "regex" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + +[[package]] +name = "reqwest" +version = "0.12.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d19c46a6fdd48bc4dab94b6103fccc55d34c67cc0ad04653aad4ea2a07cd7bbb" +dependencies = [ + "base64 0.22.1", + "bytes", + "futures-core", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-util", + "ipnet", + "js-sys", + "log", + "mime", + "once_cell", + "percent-encoding", + "pin-project-lite", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-util", + "tower", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams", + "web-sys", + "windows-registry", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustversion" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" + +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "schemars" +version = "0.8.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fbf2ae1b8bc8e02df939598064d22402220cd5bbcca1c76f7d6a310974d5615" +dependencies = [ + "dyn-clone", + "indexmap 1.9.3", + "schemars_derive", + "serde", + "serde_json", + "url", + "uuid", +] + +[[package]] +name = "schemars_derive" +version = "0.8.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32e265784ad618884abaea0600a9adf15393368d840e0222d101a072f3f7534d" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals", + "syn 2.0.101", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "selectors" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df320f1889ac4ba6bc0cdc9c9af7af4bd64bb927bccdf32d81140dc1f9be12fe" +dependencies = [ + "bitflags 1.3.2", + "cssparser", + "derive_more", + "fxhash", + "log", + "matches", + "phf 0.8.0", + "phf_codegen 0.8.0", + "precomputed-hash", + "servo_arc", + "smallvec", + "thin-slice", +] + +[[package]] +name = "semver" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" +dependencies = [ + "serde", +] + +[[package]] +name = "serde" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde-untagged" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "299d9c19d7d466db4ab10addd5703e4c615dec2a5a16dbbafe191045e87ee66e" +dependencies = [ + "erased-serde", + "serde", + "typeid", +] + +[[package]] +name = "serde_derive" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "serde_derive_internals" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "serde_json" +version = "1.0.140" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" +dependencies = [ + "itoa 1.0.15", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "serde_repr" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "serde_spanned" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa 1.0.15", + "ryu", + "serde", +] + +[[package]] +name = "serde_with" +version = "3.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6b6f7f2fcb69f747921f79f3926bd1e203fce4fef62c268dd3abfb6d86029aa" +dependencies = [ + "base64 0.22.1", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.9.0", + "serde", + "serde_derive", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d00caa5193a3c8362ac2b73be6b9e768aa5a4b2f721d8f4b339600c3cb51f8e" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "serialize-to-javascript" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9823f2d3b6a81d98228151fdeaf848206a7855a7a042bbf9bf870449a66cafb" +dependencies = [ + "serde", + "serde_json", + "serialize-to-javascript-impl", +] + +[[package]] +name = "serialize-to-javascript-impl" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74064874e9f6a15f04c1f3cb627902d0e6b410abbf36668afa873c61889f1763" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "servo_arc" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d98238b800e0d1576d8b6e3de32827c2d74bee68bb97748dcf5071fb53965432" +dependencies = [ + "nodrop", + "stable_deref_trait", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "simd-adler32" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + +[[package]] +name = "siphasher" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" + +[[package]] +name = "siphasher" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" + +[[package]] +name = "socket2" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f5fd57c80058a56cf5c777ab8a126398ece8e442983605d280a44ce79d0edef" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "softbuffer" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18051cdd562e792cad055119e0cdb2cfc137e44e3987532e0f9659a77931bb08" +dependencies = [ + "bytemuck", + "cfg_aliases", + "core-graphics", + "foreign-types", + "js-sys", + "log", + "objc2 0.5.2", + "objc2-foundation 0.2.2", + "objc2-quartz-core 0.2.2", + "raw-window-handle", + "redox_syscall", + "wasm-bindgen", + "web-sys", + "windows-sys 0.59.0", +] + +[[package]] +name = "soup3" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "471f924a40f31251afc77450e781cb26d55c0b650842efafc9c6cbd2f7cc4f9f" +dependencies = [ + "futures-channel", + "gio", + "glib", + "libc", + "soup3-sys", +] + +[[package]] +name = "soup3-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ebe8950a680a12f24f15ebe1bf70db7af98ad242d9db43596ad3108aab86c27" +dependencies = [ + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "store" +version = "0.1.0" +dependencies = [ + "serde", + "tauri", + "tauri-plugin", + "thiserror 1.0.69", +] + +[[package]] +name = "string_cache" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f" +dependencies = [ + "new_debug_unreachable", + "parking_lot", + "phf_shared 0.11.3", + "precomputed-hash", + "serde", +] + +[[package]] +name = "string_cache_codegen" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c711928715f1fe0fe509c53b43e993a9a557babc2d0a3567d0a3006f1ac931a0" +dependencies = [ + "phf_generator 0.11.3", + "phf_shared 0.11.3", + "proc-macro2", + "quote", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "swift-rs" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4057c98e2e852d51fdcfca832aac7b571f6b351ad159f9eda5db1655f8d0c4d7" +dependencies = [ + "base64 0.21.7", + "serde", + "serde_json", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "system-deps" +version = "6.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349" +dependencies = [ + "cfg-expr", + "heck 0.5.0", + "pkg-config", + "toml", + "version-compare", +] + +[[package]] +name = "tao" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e59c1f38e657351a2e822eadf40d6a2ad4627b9c25557bc1180ec1b3295ef82" +dependencies = [ + "bitflags 2.9.0", + "core-foundation", + "core-graphics", + "crossbeam-channel", + "dispatch", + "dlopen2", + "dpi", + "gdkwayland-sys", + "gdkx11-sys", + "gtk", + "jni", + "lazy_static", + "libc", + "log", + "ndk", + "ndk-context", + "ndk-sys", + "objc2 0.6.1", + "objc2-app-kit", + "objc2-foundation 0.3.1", + "once_cell", + "parking_lot", + "raw-window-handle", + "scopeguard", + "tao-macros", + "unicode-segmentation", + "url", + "windows", + "windows-core", + "windows-version", + "x11-dl", +] + +[[package]] +name = "tao-macros" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4e16beb8b2ac17db28eab8bca40e62dbfbb34c0fcdc6d9826b11b7b5d047dfd" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "target-lexicon" +version = "0.12.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" + +[[package]] +name = "tauri" +version = "2.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7b0bc1aec81bda6bc455ea98fcaed26b3c98c1648c627ad6ff1c704e8bf8cbc" +dependencies = [ + "anyhow", + "bytes", + "dirs", + "dunce", + "embed_plist", + "futures-util", + "getrandom 0.2.16", + "glob", + "gtk", + "heck 0.5.0", + "http", + "jni", + "libc", + "log", + "mime", + "muda", + "objc2 0.6.1", + "objc2-app-kit", + "objc2-foundation 0.3.1", + "objc2-ui-kit", + "percent-encoding", + "plist", + "raw-window-handle", + "reqwest", + "serde", + "serde_json", + "serde_repr", + "serialize-to-javascript", + "swift-rs", + "tauri-build", + "tauri-macros", + "tauri-runtime", + "tauri-runtime-wry", + "tauri-utils", + "thiserror 2.0.12", + "tokio", + "tray-icon", + "url", + "urlpattern", + "webkit2gtk", + "webview2-com", + "window-vibrancy", + "windows", +] + +[[package]] +name = "tauri-build" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7a0350f0df1db385ca5c02888a83e0e66655c245b7443db8b78a70da7d7f8fc" +dependencies = [ + "anyhow", + "cargo_toml", + "dirs", + "glob", + "heck 0.5.0", + "json-patch", + "schemars", + "semver", + "serde", + "serde_json", + "tauri-utils", + "tauri-winres", + "toml", + "walkdir", +] + +[[package]] +name = "tauri-codegen" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f93f035551bf7b11b3f51ad9bc231ebbe5e085565527991c16cf326aa38cdf47" +dependencies = [ + "base64 0.22.1", + "brotli", + "ico", + "json-patch", + "plist", + "png", + "proc-macro2", + "quote", + "semver", + "serde", + "serde_json", + "sha2", + "syn 2.0.101", + "tauri-utils", + "thiserror 2.0.12", + "time", + "url", + "uuid", + "walkdir", +] + +[[package]] +name = "tauri-macros" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8db4df25e2d9d45de0c4c910da61cd5500190da14ae4830749fee3466dddd112" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.101", + "tauri-codegen", + "tauri-utils", +] + +[[package]] +name = "tauri-plugin" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37a5ebe6a610d1b78a94650896e6f7c9796323f408800cef436e0fa0539de601" +dependencies = [ + "anyhow", + "glob", + "plist", + "schemars", + "serde", + "serde_json", + "tauri-utils", + "toml", + "walkdir", +] + +[[package]] +name = "tauri-runtime" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00f004905d549854069e6774533d742b03cacfd6f03deb08940a8677586cbe39" +dependencies = [ + "cookie", + "dpi", + "gtk", + "http", + "jni", + "objc2 0.6.1", + "objc2-ui-kit", + "raw-window-handle", + "serde", + "serde_json", + "tauri-utils", + "thiserror 2.0.12", + "url", + "windows", +] + +[[package]] +name = "tauri-runtime-wry" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f85d056f4d4b014fe874814034f3416d57114b617a493a4fe552580851a3f3a2" +dependencies = [ + "gtk", + "http", + "jni", + "log", + "objc2 0.6.1", + "objc2-app-kit", + "objc2-foundation 0.3.1", + "once_cell", + "percent-encoding", + "raw-window-handle", + "softbuffer", + "tao", + "tauri-runtime", + "tauri-utils", + "url", + "webkit2gtk", + "webview2-com", + "windows", + "wry", +] + +[[package]] +name = "tauri-utils" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2900399c239a471bcff7f15c4399eb1a8c4fe511ba2853e07c996d771a5e0a4" +dependencies = [ + "anyhow", + "brotli", + "cargo_metadata", + "ctor", + "dunce", + "glob", + "html5ever", + "http", + "infer", + "json-patch", + "kuchikiki", + "log", + "memchr", + "phf 0.11.3", + "proc-macro2", + "quote", + "regex", + "schemars", + "semver", + "serde", + "serde-untagged", + "serde_json", + "serde_with", + "swift-rs", + "thiserror 2.0.12", + "toml", + "url", + "urlpattern", + "uuid", + "walkdir", +] + +[[package]] +name = "tauri-winres" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8d321dbc6f998d825ab3f0d62673e810c861aac2d0de2cc2c395328f1d113b4" +dependencies = [ + "embed-resource", + "indexmap 2.9.0", + "toml", +] + +[[package]] +name = "tendril" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d24a120c5fc464a3458240ee02c299ebcb9d67b5249c8848b09d639dca8d7bb0" +dependencies = [ + "futf", + "mac", + "utf-8", +] + +[[package]] +name = "thin-slice" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaa81235c7058867fa8c0e7314f33dcce9c215f535d1913822a2b3f5e289f3c" + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +dependencies = [ + "thiserror-impl 2.0.12", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "time" +version = "0.3.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" +dependencies = [ + "deranged", + "itoa 1.0.15", + "num-conv", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" + +[[package]] +name = "time-macros" +version = "0.2.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tinystr" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tokio" +version = "1.44.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6b88822cbe49de4185e3a4cbf8321dd487cf5fe0c5c65695fef6346371e9c48" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "pin-project-lite", + "socket2", + "windows-sys 0.52.0", +] + +[[package]] +name = "tokio-util" +version = "0.7.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "toml" +version = "0.8.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05ae329d1f08c4d17a59bed7ff5b5a769d062e64a62d34a3261b219e62cd5aae" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit 0.22.26", +] + +[[package]] +name = "toml_datetime" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3da5db5a963e24bc68be8b17b6fa82814bb22ee8660f192bb182771d498f09a3" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.19.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +dependencies = [ + "indexmap 2.9.0", + "toml_datetime", + "winnow 0.5.40", +] + +[[package]] +name = "toml_edit" +version = "0.20.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70f427fce4d84c72b5b732388bf4a9f4531b53f74e2887e3ecb2481f68f66d81" +dependencies = [ + "indexmap 2.9.0", + "toml_datetime", + "winnow 0.5.40", +] + +[[package]] +name = "toml_edit" +version = "0.22.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "310068873db2c5b3e7659d2cc35d21855dbafa50d1ce336397c666e3cb08137e" +dependencies = [ + "indexmap 2.9.0", + "serde", + "serde_spanned", + "toml_datetime", + "toml_write", + "winnow 0.7.9", +] + +[[package]] +name = "toml_write" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfb942dfe1d8e29a7ee7fcbde5bd2b9a25fb89aa70caea2eba3bee836ff41076" + +[[package]] +name = "tower" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +dependencies = [ + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" +dependencies = [ + "once_cell", +] + +[[package]] +name = "tray-icon" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7eee98ec5c90daf179d55c20a49d8c0d043054ce7c26336c09a24d31f14fa0" +dependencies = [ + "crossbeam-channel", + "dirs", + "libappindicator", + "muda", + "objc2 0.6.1", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-foundation 0.3.1", + "once_cell", + "png", + "serde", + "thiserror 2.0.12", + "windows-sys 0.59.0", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "typeid" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" + +[[package]] +name = "typenum" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" + +[[package]] +name = "unic-char-property" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221" +dependencies = [ + "unic-char-range", +] + +[[package]] +name = "unic-char-range" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc" + +[[package]] +name = "unic-common" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc" + +[[package]] +name = "unic-ucd-ident" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e230a37c0381caa9219d67cf063aa3a375ffed5bf541a452db16e744bdab6987" +dependencies = [ + "unic-char-property", + "unic-char-range", + "unic-ucd-version", +] + +[[package]] +name = "unic-ucd-version" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4" +dependencies = [ + "unic-common", +] + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "url" +version = "2.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "urlpattern" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70acd30e3aa1450bc2eece896ce2ad0d178e9c079493819301573dae3c37ba6d" +dependencies = [ + "regex", + "serde", + "unic-ucd-ident", + "url", +] + +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + +[[package]] +name = "utf16_iter" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "uuid" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "458f7a779bf54acc9f347480ac654f68407d3aab21269a6e3c9f922acd9e2da9" +dependencies = [ + "getrandom 0.3.2", + "serde", +] + +[[package]] +name = "version-compare" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "852e951cb7832cb45cb1169900d19760cfa39b82bc0ea9c0e5a14ae88411c98b" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "vswhom" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be979b7f07507105799e854203b470ff7c78a1639e330a58f183b5fea574608b" +dependencies = [ + "libc", + "vswhom-sys", +] + +[[package]] +name = "vswhom-sys" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb067e4cbd1ff067d1df46c9194b5de0e98efd2810bbc95c5d5e5f25a3231150" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasi" +version = "0.14.2+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +dependencies = [ + "wit-bindgen-rt", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn 2.0.101", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" +dependencies = [ + "cfg-if", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-streams" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "web-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webkit2gtk" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76b1bc1e54c581da1e9f179d0b38512ba358fb1af2d634a1affe42e37172361a" +dependencies = [ + "bitflags 1.3.2", + "cairo-rs", + "gdk", + "gdk-sys", + "gio", + "gio-sys", + "glib", + "glib-sys", + "gobject-sys", + "gtk", + "gtk-sys", + "javascriptcore-rs", + "libc", + "once_cell", + "soup3", + "webkit2gtk-sys", +] + +[[package]] +name = "webkit2gtk-sys" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62daa38afc514d1f8f12b8693d30d5993ff77ced33ce30cd04deebc267a6d57c" +dependencies = [ + "bitflags 1.3.2", + "cairo-sys-rs", + "gdk-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "gtk-sys", + "javascriptcore-rs-sys", + "libc", + "pkg-config", + "soup3-sys", + "system-deps", +] + +[[package]] +name = "webview2-com" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b542b5cfbd9618c46c2784e4d41ba218c336ac70d44c55e47b251033e7d85601" +dependencies = [ + "webview2-com-macros", + "webview2-com-sys", + "windows", + "windows-core", + "windows-implement", + "windows-interface", +] + +[[package]] +name = "webview2-com-macros" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d228f15bba3b9d56dde8bddbee66fa24545bd17b48d5128ccf4a8742b18e431" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "webview2-com-sys" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae2d11c4a686e4409659d7891791254cf9286d3cfe0eef54df1523533d22295" +dependencies = [ + "thiserror 2.0.12", + "windows", + "windows-core", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "window-vibrancy" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9bec5a31f3f9362f2258fd0e9c9dd61a9ca432e7306cc78c444258f0dce9a9c" +dependencies = [ + "objc2 0.6.1", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation 0.3.1", + "raw-window-handle", + "windows-sys 0.59.0", + "windows-version", +] + +[[package]] +name = "windows" +version = "0.61.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5ee8f3d025738cb02bad7868bbb5f8a6327501e870bf51f1b455b0a2454a419" +dependencies = [ + "windows-collections", + "windows-core", + "windows-future", + "windows-link", + "windows-numerics", +] + +[[package]] +name = "windows-collections" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" +dependencies = [ + "windows-core", +] + +[[package]] +name = "windows-core" +version = "0.61.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4763c1de310c86d75a878046489e2e5ba02c649d185f21c67d4cf8a56d098980" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings 0.4.0", +] + +[[package]] +name = "windows-future" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a1d6bbefcb7b60acd19828e1bc965da6fcf18a7e39490c5f8be71e54a19ba32" +dependencies = [ + "windows-core", + "windows-link", +] + +[[package]] +name = "windows-implement" +version = "0.60.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "windows-interface" +version = "0.59.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "windows-link" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" + +[[package]] +name = "windows-numerics" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" +dependencies = [ + "windows-core", + "windows-link", +] + +[[package]] +name = "windows-registry" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4286ad90ddb45071efd1a66dfa43eb02dd0dfbae1545ad6cc3c51cf34d7e8ba3" +dependencies = [ + "windows-result", + "windows-strings 0.3.1", + "windows-targets 0.53.0", +] + +[[package]] +name = "windows-result" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c64fd11a4fd95df68efcfee5f44a294fe71b8bc6a91993e2791938abcc712252" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87fa48cc5d406560701792be122a10132491cff9d0aeb23583cc2dcafc847319" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2ba9642430ee452d5a7aa78d72907ebe8cfda358e8cb7918a2050581322f97" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1e4c7e8ceaaf9cb7d7507c974735728ab453b67ef8f18febdd7c11fe59dca8b" +dependencies = [ + "windows_aarch64_gnullvm 0.53.0", + "windows_aarch64_msvc 0.53.0", + "windows_i686_gnu 0.53.0", + "windows_i686_gnullvm 0.53.0", + "windows_i686_msvc 0.53.0", + "windows_x86_64_gnu 0.53.0", + "windows_x86_64_gnullvm 0.53.0", + "windows_x86_64_msvc 0.53.0", +] + +[[package]] +name = "windows-version" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e04a5c6627e310a23ad2358483286c7df260c964eb2d003d8efd6d0f4e79265c" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" + +[[package]] +name = "winnow" +version = "0.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +dependencies = [ + "memchr", +] + +[[package]] +name = "winnow" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9fb597c990f03753e08d3c29efbfcf2019a003b4bf4ba19225c158e1549f0f3" +dependencies = [ + "memchr", +] + +[[package]] +name = "winreg" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + +[[package]] +name = "wit-bindgen-rt" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" +dependencies = [ + "bitflags 2.9.0", +] + +[[package]] +name = "write16" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" + +[[package]] +name = "writeable" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" + +[[package]] +name = "wry" +version = "0.51.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c886a0a9d2a94fd90cfa1d929629b79cfefb1546e2c7430c63a47f0664c0e4e2" +dependencies = [ + "base64 0.22.1", + "block2 0.6.1", + "cookie", + "crossbeam-channel", + "dpi", + "dunce", + "gdkx11", + "gtk", + "html5ever", + "http", + "javascriptcore-rs", + "jni", + "kuchikiki", + "libc", + "ndk", + "objc2 0.6.1", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation 0.3.1", + "objc2-ui-kit", + "objc2-web-kit", + "once_cell", + "percent-encoding", + "raw-window-handle", + "sha2", + "soup3", + "tao-macros", + "thiserror 2.0.12", + "url", + "webkit2gtk", + "webkit2gtk-sys", + "webview2-com", + "windows", + "windows-core", + "windows-version", + "x11-dl", +] + +[[package]] +name = "x11" +version = "2.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "502da5464ccd04011667b11c435cb992822c2c0dbde1770c988480d312a0db2e" +dependencies = [ + "libc", + "pkg-config", +] + +[[package]] +name = "x11-dl" +version = "2.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f" +dependencies = [ + "libc", + "once_cell", + "pkg-config", +] + +[[package]] +name = "yoke" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.8.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "zerofrom" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", + "synstructure", +] + +[[package]] +name = "zerovec" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] diff --git a/plugins/store/Cargo.toml b/plugins/store/Cargo.toml new file mode 100644 index 00000000..92ffacd7 --- /dev/null +++ b/plugins/store/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "store" +version = "0.1.0" +edition = "2021" +description = "Check the App Store region for conditional UI elements" +links = "store" + +[dependencies] +tauri = { version = "2.1.0" } +thiserror = "1.0" +serde = { version = "1.0", features = ["derive"] } + +[features] +default = [] +mobile = [] + +[build-dependencies] +tauri-plugin = { version = "2.0.2", features = ["build"] } \ No newline at end of file diff --git a/plugins/store/build.rs b/plugins/store/build.rs new file mode 100644 index 00000000..ee8e65ac --- /dev/null +++ b/plugins/store/build.rs @@ -0,0 +1,15 @@ +const COMMANDS: &[&str] = &[ + "get_region", + "get_products", + "purchase", + "verify_purchase", + "get_transactions", + "restore_purchases", + "get_subscription_status" +]; + +fn main() { + tauri_plugin::Builder::new(COMMANDS) + .ios_path("ios") + .build(); +} \ No newline at end of file diff --git a/plugins/store/ios/Package.resolved b/plugins/store/ios/Package.resolved new file mode 100644 index 00000000..4f1994b1 --- /dev/null +++ b/plugins/store/ios/Package.resolved @@ -0,0 +1,16 @@ +{ + "object": { + "pins": [ + { + "package": "SwiftRs", + "repositoryURL": "https://github.com/Brendonovich/swift-rs", + "state": { + "branch": null, + "revision": "f64a4514de07f450ec5b6aa297624cd3479d9579", + "version": "1.0.7" + } + } + ] + }, + "version": 1 +} diff --git a/plugins/store/ios/Package.swift b/plugins/store/ios/Package.swift new file mode 100644 index 00000000..766b201c --- /dev/null +++ b/plugins/store/ios/Package.swift @@ -0,0 +1,26 @@ +// swift-tools-version:5.5 +// Requires iOS 15.0+ + +import PackageDescription + +let package = Package( + name: "store", + platforms: [ + .iOS(.v15) + ], + products: [ + .library( + name: "store", + type: .static, // Original static configuration + targets: ["StorePlugin"]), + ], + dependencies: [ + .package(name: "Tauri", path: "../.tauri/tauri-api") + ], + targets: [ + .target( + name: "StorePlugin", + dependencies: ["Tauri"], + path: "Sources") + ] +) \ No newline at end of file diff --git a/plugins/store/ios/Sources/StorePlugin.swift b/plugins/store/ios/Sources/StorePlugin.swift new file mode 100644 index 00000000..b4be548f --- /dev/null +++ b/plugins/store/ios/Sources/StorePlugin.swift @@ -0,0 +1,593 @@ +// +// StorePlugin.swift +// store +// + +import StoreKit +import Tauri + +// Main StoreKit plugin class +@available(iOS 15.0, *) +class StorePlugin: Plugin { + // MARK: - Properties + + // Dictionary to store pending transactions by identifier + private var pendingTransactions: [UInt32: Transaction] = [:] + + // Store product identifiers + private var availableProducts: [Product] = [] + + // Transaction listener task + private var transactionListenerTask: Task? = nil + + // MARK: - Initialization + + override init() { + super.init() + // Start listening for transactions + startTransactionListener() + } + + deinit { + // Cancel the transaction listener task when the plugin is destroyed + transactionListenerTask?.cancel() + } + + // MARK: - Transaction Listener + + private func startTransactionListener() { + transactionListenerTask = Task { + // Listen for transactions for the lifetime of the app + for await verificationResult in Transaction.updates { + // Handle transaction updates + do { + let transaction = try checkVerificationResult(verificationResult) + + // Process the transaction accordingly + await handleTransactionUpdate(transaction) + } catch { + print("[StorePlugin] Error handling transaction update: \(error.localizedDescription)") + } + } + } + } + + private func checkVerificationResult(_ result: VerificationResult) throws -> T { + switch result { + case .unverified(let unverifiedItem, let error): + print("[StorePlugin] Unverified transaction: \(error.localizedDescription)") + // Still return the unverified item, but log the error + return unverifiedItem + case .verified(let verifiedItem): + return verifiedItem + } + } + + private func handleTransactionUpdate(_ transaction: Transaction) async { + // Notify your server about the transaction + + // For consumables, manage them appropriately + + // Finish the transaction to inform Apple the transaction is complete + await transaction.finish() + + // Send a notification to the frontend if needed + emit("transactionUpdated", transaction.jsonRepresentation) + } + + // MARK: - Command: getRegion + + @objc public func getRegion(_ invoke: Invoke) { + // Using StoreKit 2 API for iOS 15+ + Task { + // Try to get the current storefront using StoreKit 2 + if let storefront = await StoreKit.Storefront.current { + print("[StorePlugin] StoreKit 2 returned storefront: \(storefront.countryCode)") + invoke.resolve(storefront.countryCode) + return + } else { + print("[StorePlugin] StoreKit 2 storefront is nil") + // StoreKit 2 failed, fall back to StoreKit 1 + fallbackToStoreKit1(invoke) + } + } + } + + // Helper method for StoreKit 1 fallback + private func fallbackToStoreKit1(_ invoke: Invoke) { + // Try StoreKit 1 API + if let code = SKPaymentQueue.default().storefront?.countryCode { + print("[StorePlugin] StoreKit 1 returned storefront: \(code)") + invoke.resolve(code) + return + } + print("[StorePlugin] StoreKit 1 storefront is nil") + + // Final fallback - try to use device locale + if let regionCode = Locale.current.regionCode { + print("[StorePlugin] Using device locale region: \(regionCode)") + invoke.resolve(regionCode) + return + } + + // Ultimate fallback + print("[StorePlugin] All methods failed, returning UNKNOWN") + invoke.resolve("UNKNOWN") + } + + // MARK: - Command: getProducts + + @objc public func getProducts(_ invoke: Invoke) { + let args = invoke.arguments + guard let productIds = args["productIds"] as? [String] else { + invoke.reject("Invalid arguments: expected an array of product IDs") + return + } + + Task { + do { + let storeProducts = try await Product.products(for: Set(productIds)) + availableProducts = storeProducts + + // Convert products to dictionaries for JSON serialization + let productDicts = storeProducts.map { product -> [String: Any] in + return product.jsonRepresentation + } + + invoke.resolve(productDicts) + } catch { + print("[StorePlugin] Error fetching products: \(error.localizedDescription)") + invoke.reject("Failed to fetch products: \(error.localizedDescription)") + } + } + } + + // MARK: - Command: purchase + + @objc public func purchase(_ invoke: Invoke) { + let args = invoke.arguments + guard let productId = args["productId"] as? String else { + invoke.reject("Invalid arguments: expected a product ID") + return + } + + // Find the product in the available products + guard let product = availableProducts.first(where: { $0.id == productId }) else { + invoke.reject("Product not found. Make sure to call getProducts first.") + return + } + + Task { + do { + // Create a purchase option + let options: Set = [] + + // Purchase the product + let result = try await product.purchase(options: options) + + // Handle the purchase result + switch result { + case .success(let verificationResult): + do { + let transaction = try checkVerificationResult(verificationResult) + + // Save the transaction with the invoke ID for later resolution + pendingTransactions[invoke.callbackId] = transaction + + // Resolve the purchase with the transaction details + invoke.resolve([ + "status": "success", + "transactionId": transaction.id, + "originalTransactionId": transaction.originalID, + "productId": transaction.productID, + "purchaseDate": Int(transaction.purchaseDate.timeIntervalSince1970 * 1000), + "expirationDate": transaction.expirationDate.map { Int($0.timeIntervalSince1970 * 1000) }, + "webOrderLineItemId": transaction.webOrderLineItemID ?? "", + "quantity": transaction.purchasedQuantity, + "type": transactionTypeToString(transaction), + "ownershipType": transaction.ownershipType == .purchased ? "purchased" : "familyShared", + "signedDate": Int(transaction.signedDate.timeIntervalSince1970 * 1000), + "environment": environmentToString() + ]) + + // Process and finish the transaction + await handleTransactionUpdate(transaction) + } catch { + invoke.reject("Transaction verification failed: \(error.localizedDescription)") + } + case .userCancelled: + invoke.reject("Purchase was cancelled by the user") + case .pending: + // For ask to buy or other pending states + invoke.resolve(["status": "pending", "message": "Purchase is pending approval"]) + @unknown default: + invoke.reject("Unknown purchase result") + } + } catch { + print("[StorePlugin] Purchase error: \(error.localizedDescription)") + invoke.reject("Purchase failed: \(error.localizedDescription)") + } + } + } + + // MARK: - Command: verifyPurchase + + @objc public func verifyPurchase(_ invoke: Invoke) { + let args = invoke.arguments + guard let productId = args["productId"] as? String, + let transactionId = args["transactionId"] as? UInt64 else { + invoke.reject("Invalid arguments: expected productId and transactionId") + return + } + + Task { + do { + // Get all transaction history for the specified product + var verifiedTransaction: Transaction? + + for await result in Transaction.currentEntitlements { + do { + let transaction = try checkVerificationResult(result) + + if transaction.productID == productId && transaction.id == transactionId { + verifiedTransaction = transaction + break + } + } catch { + continue // Skip unverified transactions + } + } + + if let transaction = verifiedTransaction { + // Transaction is valid + invoke.resolve([ + "isValid": true, + "expirationDate": transaction.expirationDate.map { Int($0.timeIntervalSince1970 * 1000) }, + "purchaseDate": Int(transaction.purchaseDate.timeIntervalSince1970 * 1000) + ]) + } else { + // Transaction not found or not valid + invoke.resolve(["isValid": false]) + } + } catch { + print("[StorePlugin] Verification error: \(error.localizedDescription)") + invoke.reject("Verification failed: \(error.localizedDescription)") + } + } + } + + // MARK: - Command: getTransactions + + @objc public func getTransactions(_ invoke: Invoke) { + let args = invoke.arguments + let productId = args["productId"] as? String // Optional filter by product ID + + Task { + do { + var transactions: [[String: Any]] = [] + + // Get all current entitlements (active subscriptions and non-consumables) + for await verificationResult in Transaction.currentEntitlements { + do { + let transaction = try checkVerificationResult(verificationResult) + + // Filter by product ID if specified + if let productId = productId, transaction.productID != productId { + continue + } + + transactions.append(transaction.jsonRepresentation) + } catch { + continue // Skip unverified transactions + } + } + + invoke.resolve(transactions) + } catch { + print("[StorePlugin] Error fetching transactions: \(error.localizedDescription)") + invoke.reject("Failed to fetch transactions: \(error.localizedDescription)") + } + } + } + + // MARK: - Command: restorePurchases + + @objc public func restorePurchases(_ invoke: Invoke) { + Task { + do { + // Request a refresh of App Store purchase history + try await AppStore.sync() + + var restoredTransactions: [[String: Any]] = [] + + // Get all current entitlements after sync + for await verificationResult in Transaction.currentEntitlements { + do { + let transaction = try checkVerificationResult(verificationResult) + restoredTransactions.append(transaction.jsonRepresentation) + } catch { + continue // Skip unverified transactions + } + } + + invoke.resolve(["status": "success", "transactions": restoredTransactions]) + } catch { + print("[StorePlugin] Restore error: \(error.localizedDescription)") + invoke.reject("Restore failed: \(error.localizedDescription)") + } + } + } + + // MARK: - Command: getSubscriptionStatus + + @objc public func getSubscriptionStatus(_ invoke: Invoke) { + let args = invoke.arguments + guard let productId = args["productId"] as? String else { + invoke.reject("Invalid arguments: expected a product ID") + return + } + + Task { + do { + var subscriptionGroupStatus: [String: Any] = [ + "productId": productId, + "status": "not_subscribed", + "willAutoRenew": false, + "expirationDate": nil, + "gracePeriodExpirationDate": nil + ] + + // Look for subscription transactions for this product + for await verificationResult in Transaction.currentEntitlements { + do { + let transaction = try checkVerificationResult(verificationResult) + + // Only check for subscriptions that match this product ID + if transaction.productID == productId && transaction.productType == .autoRenewable { + + // Check the subscription info + let status = try? await transaction.subscriptionStatus + + if let status = status { + // Update subscription status + subscriptionGroupStatus["status"] = subscriptionStateToString(status.state) + subscriptionGroupStatus["willAutoRenew"] = status.willAutoRenew + subscriptionGroupStatus["expirationDate"] = transaction.expirationDate.map { Int($0.timeIntervalSince1970 * 1000) } + + // Check for grace period + if status.state == .inGracePeriod, let renewalInfo = status.renewalInfo { + subscriptionGroupStatus["gracePeriodExpirationDate"] = Int(renewalInfo.expirationDate.timeIntervalSince1970 * 1000) + } + + // Return the first valid subscription we find + break + } + } + } catch { + continue // Skip unverified transactions + } + } + + invoke.resolve(subscriptionGroupStatus) + } catch { + print("[StorePlugin] Error getting subscription status: \(error.localizedDescription)") + invoke.reject("Failed to get subscription status: \(error.localizedDescription)") + } + } + } + + // MARK: - Helper Functions + + private func transactionTypeToString(_ transaction: Transaction) -> String { + switch transaction.productType { + case .consumable: + return "consumable" + case .nonConsumable: + return "non_consumable" + case .nonRenewable: + return "non_renewable_subscription" + case .autoRenewable: + return "auto_renewable_subscription" + @unknown default: + return "unknown" + } + } + + private func subscriptionStateToString(_ state: Product.SubscriptionInfo.RenewalState) -> String { + switch state { + case .subscribed: + return "subscribed" + case .expired: + return "expired" + case .inBillingRetryPeriod: + return "in_billing_retry_period" + case .inGracePeriod: + return "in_grace_period" + case .revoked: + return "revoked" + @unknown default: + return "unknown" + } + } + + private func environmentToString() -> String { + #if DEBUG + return "sandbox" + #else + return AppStore.currentStorefront != nil ? "production" : "sandbox" + #endif + } +} + +// MARK: - Extensions + +@available(iOS 15.0, *) +extension Product { + var jsonRepresentation: [String: Any] { + var dict: [String: Any] = [ + "id": id, + "title": displayName, + "description": description, + "price": displayPrice, + "priceValue": price, + "currencyCode": priceFormatStyle.currencyCode ?? "USD", + "type": productTypeToString() + ] + + // Add subscription-specific details if available + if let subscription = subscription { + dict["subscriptionPeriod"] = subscription.subscriptionPeriod.jsonRepresentation + + if let introductoryOffer = subscription.introductoryOffer { + dict["introductoryOffer"] = introductoryOffer.jsonRepresentation + } + + if let promotionalOffers = subscription.promotionalOffers, !promotionalOffers.isEmpty { + dict["promotionalOffers"] = promotionalOffers.map { $0.jsonRepresentation } + } + } + + return dict + } + + private func productTypeToString() -> String { + switch type { + case .consumable: + return "consumable" + case .nonConsumable: + return "non_consumable" + case .nonRenewable: + return "non_renewable_subscription" + case .autoRenewable: + return "auto_renewable_subscription" + @unknown default: + return "unknown" + } + } +} + +@available(iOS 15.0, *) +extension Product.SubscriptionPeriod { + var jsonRepresentation: [String: Any] { + return [ + "unit": unitToString(), + "value": value + ] + } + + private func unitToString() -> String { + switch unit { + case .day: + return "day" + case .week: + return "week" + case .month: + return "month" + case .year: + return "year" + @unknown default: + return "unknown" + } + } +} + +@available(iOS 15.0, *) +extension Product.SubscriptionOffer { + var jsonRepresentation: [String: Any] { + var dict: [String: Any] = [ + "id": id, + "displayPrice": displayPrice, + "period": subscriptionPeriod.jsonRepresentation, + "paymentMode": paymentModeToString(), + "type": offerTypeToString() + ] + + if let discount = discount { + dict["discountType"] = discountTypeToString(discount) + dict["discountPrice"] = discount.displayPrice + } + + return dict + } + + private func paymentModeToString() -> String { + switch paymentMode { + case .payAsYouGo: + return "pay_as_you_go" + case .payUpFront: + return "pay_up_front" + case .freeTrial: + return "free_trial" + @unknown default: + return "unknown" + } + } + + private func offerTypeToString() -> String { + switch type { + case .introductory: + return "introductory" + case .promotional: + return "promotional" + case .prepaid: + return "prepaid" + case .consumable: + return "consumable" + @unknown default: + return "unknown" + } + } + + private func discountTypeToString(_ discount: Product.SubscriptionOffer.Discount) -> String { + if let numericValue = discount.numericDiscount, numericValue > 0 { + // numericDiscount is a decimal percentage value (e.g., 0.5 for 50%) + return "percentage" + } else { + return "nominal" // Fixed amount discount + } + } +} + +@available(iOS 15.0, *) +extension Transaction { + var jsonRepresentation: [String: Any] { + return [ + "id": id, + "originalId": originalID, + "productId": productID, + "purchaseDate": Int(purchaseDate.timeIntervalSince1970 * 1000), + "expirationDate": expirationDate.map { Int($0.timeIntervalSince1970 * 1000) }, + "webOrderLineItemId": webOrderLineItemID ?? "", + "quantity": purchasedQuantity, + "type": productType.productTypeToString(), + "ownershipType": ownershipType == .purchased ? "purchased" : "familyShared", + "signedDate": Int(signedDate.timeIntervalSince1970 * 1000) + ] + } +} + +@available(iOS 15.0, *) +extension Product.ProductType { + func productTypeToString() -> String { + switch self { + case .consumable: + return "consumable" + case .nonConsumable: + return "non_consumable" + case .nonRenewable: + return "non_renewable_subscription" + case .autoRenewable: + return "auto_renewable_subscription" + @unknown default: + return "unknown" + } + } +} + +// MARK: - Init Plugin + +@available(iOS 15.0, *) +@_cdecl("init_plugin_store") +func initPlugin() -> Plugin { + return StorePlugin() +} \ No newline at end of file diff --git a/plugins/store/permissions/autogenerated/commands/get_products.toml b/plugins/store/permissions/autogenerated/commands/get_products.toml new file mode 100644 index 00000000..088f5cf4 --- /dev/null +++ b/plugins/store/permissions/autogenerated/commands/get_products.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-get-products" +description = "Enables the get_products command without any pre-configured scope." +commands.allow = ["get_products"] + +[[permission]] +identifier = "deny-get-products" +description = "Denies the get_products command without any pre-configured scope." +commands.deny = ["get_products"] diff --git a/plugins/store/permissions/autogenerated/commands/get_region.toml b/plugins/store/permissions/autogenerated/commands/get_region.toml new file mode 100644 index 00000000..3d205963 --- /dev/null +++ b/plugins/store/permissions/autogenerated/commands/get_region.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-get-region" +description = "Enables the get_region command without any pre-configured scope." +commands.allow = ["get_region"] + +[[permission]] +identifier = "deny-get-region" +description = "Denies the get_region command without any pre-configured scope." +commands.deny = ["get_region"] diff --git a/plugins/store/permissions/autogenerated/commands/get_subscription_status.toml b/plugins/store/permissions/autogenerated/commands/get_subscription_status.toml new file mode 100644 index 00000000..49680892 --- /dev/null +++ b/plugins/store/permissions/autogenerated/commands/get_subscription_status.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-get-subscription-status" +description = "Enables the get_subscription_status command without any pre-configured scope." +commands.allow = ["get_subscription_status"] + +[[permission]] +identifier = "deny-get-subscription-status" +description = "Denies the get_subscription_status command without any pre-configured scope." +commands.deny = ["get_subscription_status"] diff --git a/plugins/store/permissions/autogenerated/commands/get_transactions.toml b/plugins/store/permissions/autogenerated/commands/get_transactions.toml new file mode 100644 index 00000000..9ac36eb8 --- /dev/null +++ b/plugins/store/permissions/autogenerated/commands/get_transactions.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-get-transactions" +description = "Enables the get_transactions command without any pre-configured scope." +commands.allow = ["get_transactions"] + +[[permission]] +identifier = "deny-get-transactions" +description = "Denies the get_transactions command without any pre-configured scope." +commands.deny = ["get_transactions"] diff --git a/plugins/store/permissions/autogenerated/commands/purchase.toml b/plugins/store/permissions/autogenerated/commands/purchase.toml new file mode 100644 index 00000000..925ad9f7 --- /dev/null +++ b/plugins/store/permissions/autogenerated/commands/purchase.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-purchase" +description = "Enables the purchase command without any pre-configured scope." +commands.allow = ["purchase"] + +[[permission]] +identifier = "deny-purchase" +description = "Denies the purchase command without any pre-configured scope." +commands.deny = ["purchase"] diff --git a/plugins/store/permissions/autogenerated/commands/restore_purchases.toml b/plugins/store/permissions/autogenerated/commands/restore_purchases.toml new file mode 100644 index 00000000..151a44ed --- /dev/null +++ b/plugins/store/permissions/autogenerated/commands/restore_purchases.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-restore-purchases" +description = "Enables the restore_purchases command without any pre-configured scope." +commands.allow = ["restore_purchases"] + +[[permission]] +identifier = "deny-restore-purchases" +description = "Denies the restore_purchases command without any pre-configured scope." +commands.deny = ["restore_purchases"] diff --git a/plugins/store/permissions/autogenerated/commands/verify_purchase.toml b/plugins/store/permissions/autogenerated/commands/verify_purchase.toml new file mode 100644 index 00000000..f6eed3e1 --- /dev/null +++ b/plugins/store/permissions/autogenerated/commands/verify_purchase.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-verify-purchase" +description = "Enables the verify_purchase command without any pre-configured scope." +commands.allow = ["verify_purchase"] + +[[permission]] +identifier = "deny-verify-purchase" +description = "Denies the verify_purchase command without any pre-configured scope." +commands.deny = ["verify_purchase"] diff --git a/plugins/store/permissions/autogenerated/reference.md b/plugins/store/permissions/autogenerated/reference.md new file mode 100644 index 00000000..b5f2a5de --- /dev/null +++ b/plugins/store/permissions/autogenerated/reference.md @@ -0,0 +1,192 @@ + +## Permission Table + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
IdentifierDescription
+ +`store:allow-get-products` + + + +Enables the get_products command without any pre-configured scope. + +
+ +`store:deny-get-products` + + + +Denies the get_products command without any pre-configured scope. + +
+ +`store:allow-get-region` + + + +Enables the get_region command without any pre-configured scope. + +
+ +`store:deny-get-region` + + + +Denies the get_region command without any pre-configured scope. + +
+ +`store:allow-get-subscription-status` + + + +Enables the get_subscription_status command without any pre-configured scope. + +
+ +`store:deny-get-subscription-status` + + + +Denies the get_subscription_status command without any pre-configured scope. + +
+ +`store:allow-get-transactions` + + + +Enables the get_transactions command without any pre-configured scope. + +
+ +`store:deny-get-transactions` + + + +Denies the get_transactions command without any pre-configured scope. + +
+ +`store:allow-purchase` + + + +Enables the purchase command without any pre-configured scope. + +
+ +`store:deny-purchase` + + + +Denies the purchase command without any pre-configured scope. + +
+ +`store:allow-restore-purchases` + + + +Enables the restore_purchases command without any pre-configured scope. + +
+ +`store:deny-restore-purchases` + + + +Denies the restore_purchases command without any pre-configured scope. + +
+ +`store:allow-verify-purchase` + + + +Enables the verify_purchase command without any pre-configured scope. + +
+ +`store:deny-verify-purchase` + + + +Denies the verify_purchase command without any pre-configured scope. + +
diff --git a/plugins/store/permissions/schemas/schema.json b/plugins/store/permissions/schemas/schema.json new file mode 100644 index 00000000..0d1e1f9c --- /dev/null +++ b/plugins/store/permissions/schemas/schema.json @@ -0,0 +1,384 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PermissionFile", + "description": "Permission file that can define a default permission, a set of permissions or a list of inlined permissions.", + "type": "object", + "properties": { + "default": { + "description": "The default permission set for the plugin", + "anyOf": [ + { + "$ref": "#/definitions/DefaultPermission" + }, + { + "type": "null" + } + ] + }, + "set": { + "description": "A list of permissions sets defined", + "type": "array", + "items": { + "$ref": "#/definitions/PermissionSet" + } + }, + "permission": { + "description": "A list of inlined permissions", + "default": [], + "type": "array", + "items": { + "$ref": "#/definitions/Permission" + } + } + }, + "definitions": { + "DefaultPermission": { + "description": "The default permission set of the plugin.\n\nWorks similarly to a permission with the \"default\" identifier.", + "type": "object", + "required": [ + "permissions" + ], + "properties": { + "version": { + "description": "The version of the permission.", + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 1.0 + }, + "description": { + "description": "Human-readable description of what the permission does. Tauri convention is to use `

` headings in markdown content for Tauri documentation generation purposes.", + "type": [ + "string", + "null" + ] + }, + "permissions": { + "description": "All permissions this set contains.", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "PermissionSet": { + "description": "A set of direct permissions grouped together under a new name.", + "type": "object", + "required": [ + "description", + "identifier", + "permissions" + ], + "properties": { + "identifier": { + "description": "A unique identifier for the permission.", + "type": "string" + }, + "description": { + "description": "Human-readable description of what the permission does.", + "type": "string" + }, + "permissions": { + "description": "All permissions this set contains.", + "type": "array", + "items": { + "$ref": "#/definitions/PermissionKind" + } + } + } + }, + "Permission": { + "description": "Descriptions of explicit privileges of commands.\n\nIt can enable commands to be accessible in the frontend of the application.\n\nIf the scope is defined it can be used to fine grain control the access of individual or multiple commands.", + "type": "object", + "required": [ + "identifier" + ], + "properties": { + "version": { + "description": "The version of the permission.", + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 1.0 + }, + "identifier": { + "description": "A unique identifier for the permission.", + "type": "string" + }, + "description": { + "description": "Human-readable description of what the permission does. Tauri internal convention is to use `

` headings in markdown content for Tauri documentation generation purposes.", + "type": [ + "string", + "null" + ] + }, + "commands": { + "description": "Allowed or denied commands when using this permission.", + "default": { + "allow": [], + "deny": [] + }, + "allOf": [ + { + "$ref": "#/definitions/Commands" + } + ] + }, + "scope": { + "description": "Allowed or denied scoped when using this permission.", + "allOf": [ + { + "$ref": "#/definitions/Scopes" + } + ] + }, + "platforms": { + "description": "Target platforms this permission applies. By default all platforms are affected by this permission.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Target" + } + } + } + }, + "Commands": { + "description": "Allowed and denied commands inside a permission.\n\nIf two commands clash inside of `allow` and `deny`, it should be denied by default.", + "type": "object", + "properties": { + "allow": { + "description": "Allowed command.", + "default": [], + "type": "array", + "items": { + "type": "string" + } + }, + "deny": { + "description": "Denied command, which takes priority.", + "default": [], + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "Scopes": { + "description": "An argument for fine grained behavior control of Tauri commands.\n\nIt can be of any serde serializable type and is used to allow or prevent certain actions inside a Tauri command. The configured scope is passed to the command and will be enforced by the command implementation.\n\n## Example\n\n```json { \"allow\": [{ \"path\": \"$HOME/**\" }], \"deny\": [{ \"path\": \"$HOME/secret.txt\" }] } ```", + "type": "object", + "properties": { + "allow": { + "description": "Data that defines what is allowed by the scope.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Value" + } + }, + "deny": { + "description": "Data that defines what is denied by the scope. This should be prioritized by validation logic.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Value" + } + } + } + }, + "Value": { + "description": "All supported ACL values.", + "anyOf": [ + { + "description": "Represents a null JSON value.", + "type": "null" + }, + { + "description": "Represents a [`bool`].", + "type": "boolean" + }, + { + "description": "Represents a valid ACL [`Number`].", + "allOf": [ + { + "$ref": "#/definitions/Number" + } + ] + }, + { + "description": "Represents a [`String`].", + "type": "string" + }, + { + "description": "Represents a list of other [`Value`]s.", + "type": "array", + "items": { + "$ref": "#/definitions/Value" + } + }, + { + "description": "Represents a map of [`String`] keys to [`Value`]s.", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/Value" + } + } + ] + }, + "Number": { + "description": "A valid ACL number.", + "anyOf": [ + { + "description": "Represents an [`i64`].", + "type": "integer", + "format": "int64" + }, + { + "description": "Represents a [`f64`].", + "type": "number", + "format": "double" + } + ] + }, + "Target": { + "description": "Platform target.", + "oneOf": [ + { + "description": "MacOS.", + "type": "string", + "enum": [ + "macOS" + ] + }, + { + "description": "Windows.", + "type": "string", + "enum": [ + "windows" + ] + }, + { + "description": "Linux.", + "type": "string", + "enum": [ + "linux" + ] + }, + { + "description": "Android.", + "type": "string", + "enum": [ + "android" + ] + }, + { + "description": "iOS.", + "type": "string", + "enum": [ + "iOS" + ] + } + ] + }, + "PermissionKind": { + "type": "string", + "oneOf": [ + { + "description": "Enables the get_products command without any pre-configured scope.", + "type": "string", + "const": "allow-get-products", + "markdownDescription": "Enables the get_products command without any pre-configured scope." + }, + { + "description": "Denies the get_products command without any pre-configured scope.", + "type": "string", + "const": "deny-get-products", + "markdownDescription": "Denies the get_products command without any pre-configured scope." + }, + { + "description": "Enables the get_region command without any pre-configured scope.", + "type": "string", + "const": "allow-get-region", + "markdownDescription": "Enables the get_region command without any pre-configured scope." + }, + { + "description": "Denies the get_region command without any pre-configured scope.", + "type": "string", + "const": "deny-get-region", + "markdownDescription": "Denies the get_region command without any pre-configured scope." + }, + { + "description": "Enables the get_subscription_status command without any pre-configured scope.", + "type": "string", + "const": "allow-get-subscription-status", + "markdownDescription": "Enables the get_subscription_status command without any pre-configured scope." + }, + { + "description": "Denies the get_subscription_status command without any pre-configured scope.", + "type": "string", + "const": "deny-get-subscription-status", + "markdownDescription": "Denies the get_subscription_status command without any pre-configured scope." + }, + { + "description": "Enables the get_transactions command without any pre-configured scope.", + "type": "string", + "const": "allow-get-transactions", + "markdownDescription": "Enables the get_transactions command without any pre-configured scope." + }, + { + "description": "Denies the get_transactions command without any pre-configured scope.", + "type": "string", + "const": "deny-get-transactions", + "markdownDescription": "Denies the get_transactions command without any pre-configured scope." + }, + { + "description": "Enables the purchase command without any pre-configured scope.", + "type": "string", + "const": "allow-purchase", + "markdownDescription": "Enables the purchase command without any pre-configured scope." + }, + { + "description": "Denies the purchase command without any pre-configured scope.", + "type": "string", + "const": "deny-purchase", + "markdownDescription": "Denies the purchase command without any pre-configured scope." + }, + { + "description": "Enables the restore_purchases command without any pre-configured scope.", + "type": "string", + "const": "allow-restore-purchases", + "markdownDescription": "Enables the restore_purchases command without any pre-configured scope." + }, + { + "description": "Denies the restore_purchases command without any pre-configured scope.", + "type": "string", + "const": "deny-restore-purchases", + "markdownDescription": "Denies the restore_purchases command without any pre-configured scope." + }, + { + "description": "Enables the verify_purchase command without any pre-configured scope.", + "type": "string", + "const": "allow-verify-purchase", + "markdownDescription": "Enables the verify_purchase command without any pre-configured scope." + }, + { + "description": "Denies the verify_purchase command without any pre-configured scope.", + "type": "string", + "const": "deny-verify-purchase", + "markdownDescription": "Denies the verify_purchase command without any pre-configured scope." + } + ] + } + } +} \ No newline at end of file diff --git a/plugins/store/src/commands.rs b/plugins/store/src/commands.rs new file mode 100644 index 00000000..637f4762 --- /dev/null +++ b/plugins/store/src/commands.rs @@ -0,0 +1,59 @@ +use tauri::{AppHandle, command, Runtime}; + +use crate::Result; +use crate::StoreExt; + +#[command] +pub(crate) async fn get_region( + app: AppHandle, +) -> Result { + app.store().get_region() +} + +#[command] +pub(crate) async fn get_products( + app: AppHandle, + product_ids: Vec, +) -> Result> { + app.store().get_products(product_ids) +} + +#[command] +pub(crate) async fn purchase( + app: AppHandle, + product_id: String, +) -> Result { + app.store().purchase(product_id) +} + +#[command] +pub(crate) async fn verify_purchase( + app: AppHandle, + product_id: String, + transaction_id: u64, +) -> Result { + app.store().verify_purchase(product_id, transaction_id) +} + +#[command] +pub(crate) async fn get_transactions( + app: AppHandle, + product_id: Option, +) -> Result> { + app.store().get_transactions(product_id) +} + +#[command] +pub(crate) async fn restore_purchases( + app: AppHandle, +) -> Result { + app.store().restore_purchases() +} + +#[command] +pub(crate) async fn get_subscription_status( + app: AppHandle, + product_id: String, +) -> Result { + app.store().get_subscription_status(product_id) +} \ No newline at end of file diff --git a/plugins/store/src/desktop.rs b/plugins/store/src/desktop.rs new file mode 100644 index 00000000..ea2d51a1 --- /dev/null +++ b/plugins/store/src/desktop.rs @@ -0,0 +1,19 @@ +use serde::de::DeserializeOwned; +use tauri::{plugin::PluginApi, AppHandle, Runtime}; + +// Initialize the plugin for desktop +pub fn init( + app: &AppHandle, + _api: PluginApi, +) -> crate::Result> { + Ok(Store(app.clone())) +} + +/// Desktop implementation of the Store plugin (does nothing) +pub struct Store(AppHandle); + +impl Store { + pub fn get_region(&self) -> crate::Result { + Ok(String::from("UNKNOWN")) + } +} \ No newline at end of file diff --git a/plugins/store/src/error.rs b/plugins/store/src/error.rs new file mode 100644 index 00000000..68f1d789 --- /dev/null +++ b/plugins/store/src/error.rs @@ -0,0 +1,24 @@ +use serde::{ser::Serializer, Serialize}; + +pub type Result = std::result::Result; + +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("Unsupported platform for store")] + UnsupportedPlatformError, + #[error(transparent)] + Io(#[from] std::io::Error), + #[cfg(feature = "mobile")] + #[error(transparent)] + PluginInvoke(#[from] tauri::plugin::mobile::PluginInvokeError), +} + +impl Serialize for Error { + fn serialize(&self, serializer: S) -> std::result::Result + where + S: Serializer, + { + serializer.serialize_str(self.to_string().as_ref()) + } +} + diff --git a/plugins/store/src/lib.rs b/plugins/store/src/lib.rs new file mode 100644 index 00000000..edf9b81d --- /dev/null +++ b/plugins/store/src/lib.rs @@ -0,0 +1,53 @@ +use tauri::{ + plugin::{Builder, TauriPlugin}, + Manager, Runtime, +}; + +#[cfg(not(feature = "mobile"))] +mod desktop; +#[cfg(feature = "mobile")] +mod mobile; + +mod commands; +mod error; + +pub use error::{Error, Result}; + +#[cfg(not(feature = "mobile"))] +use desktop::Store; +#[cfg(feature = "mobile")] +use mobile::Store; + +/// Extensions to [`tauri::App`], [`tauri::AppHandle`] and [`tauri::Window`] to access the store APIs. +pub trait StoreExt { + fn store(&self) -> &Store; +} + +impl> crate::StoreExt for T { + fn store(&self) -> &Store { + self.state::>().inner() + } +} + +/// Initializes the plugin. +pub fn init() -> TauriPlugin { + Builder::new("store") + .invoke_handler(tauri::generate_handler![ + commands::get_region, + commands::get_products, + commands::purchase, + commands::verify_purchase, + commands::get_transactions, + commands::restore_purchases, + commands::get_subscription_status + ]) + .setup(|app, api| { + #[cfg(feature = "mobile")] + let store = mobile::init(app, api)?; + #[cfg(not(feature = "mobile"))] + let store = desktop::init(app, api)?; + app.manage(store); + Ok(()) + }) + .build() +} \ No newline at end of file diff --git a/plugins/store/src/mobile.rs b/plugins/store/src/mobile.rs new file mode 100644 index 00000000..445dc413 --- /dev/null +++ b/plugins/store/src/mobile.rs @@ -0,0 +1,222 @@ +use serde::de::DeserializeOwned; +use serde::{Deserialize, Serialize}; +use tauri::{ + plugin::{PluginApi, PluginHandle}, + AppHandle, Runtime, +}; + +#[cfg(target_os = "ios")] +tauri::ios_plugin_binding!(init_plugin_store); + +// Initialize the plugin +pub fn init( + _app: &AppHandle, + api: PluginApi, +) -> crate::Result> { + #[cfg(target_os = "ios")] + let handle = api.register_ios_plugin(init_plugin_store)?; + + #[cfg(not(target_os = "ios"))] + let handle = { + // Dummy handle for non-iOS platforms + let handle: PluginHandle = unsafe { std::mem::zeroed() }; + handle + }; + + Ok(Store(handle)) +} + +/// Access to the store APIs. +pub struct Store(PluginHandle); + +// Transaction response type +#[derive(Debug, Serialize, Deserialize)] +pub struct Transaction { + pub id: u64, + #[serde(rename = "originalId")] + pub original_id: Option, + #[serde(rename = "productId")] + pub product_id: String, + #[serde(rename = "purchaseDate")] + pub purchase_date: i64, + #[serde(rename = "expirationDate")] + pub expiration_date: Option, + #[serde(rename = "webOrderLineItemId")] + pub web_order_line_item_id: String, + pub quantity: i32, + pub r#type: String, + #[serde(rename = "ownershipType")] + pub ownership_type: String, + #[serde(rename = "signedDate")] + pub signed_date: i64, +} + +// Product response type +#[derive(Debug, Serialize, Deserialize)] +pub struct Product { + pub id: String, + pub title: String, + pub description: String, + pub price: String, + #[serde(rename = "priceValue")] + pub price_value: f64, + #[serde(rename = "currencyCode")] + pub currency_code: String, + pub r#type: String, + #[serde(rename = "subscriptionPeriod", default, skip_serializing_if = "Option::is_none")] + pub subscription_period: Option, + #[serde(rename = "introductoryOffer", default, skip_serializing_if = "Option::is_none")] + pub introductory_offer: Option, + #[serde(rename = "promotionalOffers", default, skip_serializing_if = "Option::is_none")] + pub promotional_offers: Option>, +} + +// Subscription period type +#[derive(Debug, Serialize, Deserialize)] +pub struct SubscriptionPeriod { + pub unit: String, + pub value: i32, +} + +// Subscription offer type +#[derive(Debug, Serialize, Deserialize)] +pub struct SubscriptionOffer { + pub id: String, + #[serde(rename = "displayPrice")] + pub display_price: String, + pub period: SubscriptionPeriod, + #[serde(rename = "paymentMode")] + pub payment_mode: String, + pub r#type: String, + #[serde(rename = "discountType", default, skip_serializing_if = "Option::is_none")] + pub discount_type: Option, + #[serde(rename = "discountPrice", default, skip_serializing_if = "Option::is_none")] + pub discount_price: Option, +} + +// Purchase result type +#[derive(Debug, Serialize, Deserialize)] +pub struct PurchaseResult { + pub status: String, + #[serde(rename = "transactionId", default, skip_serializing_if = "Option::is_none")] + pub transaction_id: Option, + #[serde(rename = "originalTransactionId", default, skip_serializing_if = "Option::is_none")] + pub original_transaction_id: Option, + #[serde(rename = "productId", default, skip_serializing_if = "Option::is_none")] + pub product_id: Option, + #[serde(rename = "purchaseDate", default, skip_serializing_if = "Option::is_none")] + pub purchase_date: Option, + #[serde(rename = "expirationDate", default, skip_serializing_if = "Option::is_none")] + pub expiration_date: Option, + #[serde(rename = "webOrderLineItemId", default, skip_serializing_if = "Option::is_none")] + pub web_order_line_item_id: Option, + #[serde(rename = "quantity", default, skip_serializing_if = "Option::is_none")] + pub quantity: Option, + #[serde(rename = "type", default, skip_serializing_if = "Option::is_none")] + pub r#type: Option, + #[serde(rename = "ownershipType", default, skip_serializing_if = "Option::is_none")] + pub ownership_type: Option, + #[serde(rename = "signedDate", default, skip_serializing_if = "Option::is_none")] + pub signed_date: Option, + #[serde(rename = "environment", default, skip_serializing_if = "Option::is_none")] + pub environment: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub message: Option, +} + +// Verification result type +#[derive(Debug, Serialize, Deserialize)] +pub struct VerificationResult { + #[serde(rename = "isValid")] + pub is_valid: bool, + #[serde(rename = "expirationDate", default, skip_serializing_if = "Option::is_none")] + pub expiration_date: Option, + #[serde(rename = "purchaseDate", default, skip_serializing_if = "Option::is_none")] + pub purchase_date: Option, +} + +// Restore purchases result type +#[derive(Debug, Serialize, Deserialize)] +pub struct RestorePurchasesResult { + pub status: String, + pub transactions: Vec, +} + +// Subscription status type +#[derive(Debug, Serialize, Deserialize)] +pub struct SubscriptionStatus { + #[serde(rename = "productId")] + pub product_id: String, + pub status: String, + #[serde(rename = "willAutoRenew")] + pub will_auto_renew: bool, + #[serde(rename = "expirationDate", default, skip_serializing_if = "Option::is_none")] + pub expiration_date: Option, + #[serde(rename = "gracePeriodExpirationDate", default, skip_serializing_if = "Option::is_none")] + pub grace_period_expiration_date: Option, +} + +impl Store { + // Get region code from the App Store + pub fn get_region(&self) -> crate::Result { + self.0 + .run_mobile_plugin("getRegion", ()) + .map_err(Into::into) + } + + // Get products from the App Store + pub fn get_products(&self, product_ids: Vec) -> crate::Result> { + self.0 + .run_mobile_plugin("getProducts", serde_json::json!({ "productIds": product_ids })) + .map_err(Into::into) + } + + // Make a purchase + pub fn purchase(&self, product_id: String) -> crate::Result { + self.0 + .run_mobile_plugin("purchase", serde_json::json!({ "productId": product_id })) + .map_err(Into::into) + } + + // Verify a purchase + pub fn verify_purchase(&self, product_id: String, transaction_id: u64) -> crate::Result { + self.0 + .run_mobile_plugin( + "verifyPurchase", + serde_json::json!({ + "productId": product_id, + "transactionId": transaction_id + }), + ) + .map_err(Into::into) + } + + // Get all transactions, optionally filtered by product ID + pub fn get_transactions(&self, product_id: Option) -> crate::Result> { + let args = match product_id { + Some(id) => serde_json::json!({ "productId": id }), + None => serde_json::json!({}), + }; + + self.0 + .run_mobile_plugin("getTransactions", args) + .map_err(Into::into) + } + + // Restore purchases + pub fn restore_purchases(&self) -> crate::Result { + self.0 + .run_mobile_plugin("restorePurchases", ()) + .map_err(Into::into) + } + + // Get subscription status + pub fn get_subscription_status(&self, product_id: String) -> crate::Result { + self.0 + .run_mobile_plugin( + "getSubscriptionStatus", + serde_json::json!({ "productId": product_id }), + ) + .map_err(Into::into) + } +} \ No newline at end of file