diff --git a/CHANGELOG.md b/CHANGELOG.md index 0b278b293..9edb8fea2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### Features +* [FEATURE][all] Cloud backup & restore via Google Drive. Wallet state (accounts, settings, miden-client DB, transaction DB) is encrypted with either a user-supplied password (PBKDF2) or a WebAuthn / native iOS passkey (PRF-derived key) and uploaded to the user's Google Drive `appDataFolder`. Auto-backup fires after account creation, settings changes, transaction completion, and when new consumable notes arrive; the encryption key is persisted in vault storage so subsequent backups run silently. Onboarding and Forgot Password flows gain an **Import from Cloud** path that downloads + decrypts the backup and restores the wallet end-to-end; auto-backup is re-enabled automatically after a cloud restore (skipping the redundant initial upload). Cloud Backup settings exposes a **Restore from Backup** button that pulls fresh state from Drive on-demand using the vault's stored encryption key — no password / passkey prompt required, no onboarding detour. iOS uses native Keychain-backed passkeys bridged to the JS layer via Capacitor; extension uses `chrome.identity` OAuth with PKCE and a tab-based bridge for passkey registration. * [FEATURE][all] Transaction-complete modal now surfaces a **View on Midenscan** action alongside **Done**. Desktop / extension opens the explorer in a new tab; mobile opens it as a native `InAppBrowser` overlay so dismissing the overlay returns the user to the completion screen with no state loss. URL resolved per-network via a new `MIDEN_EXPLORER_ENDPOINTS` map (testnet / devnet); localnet has no explorer → button hidden. The on-chain tx hash is plumbed through `SendManager.onSubmit` → `lastCompletedTxHash` in the Zustand store, cleared at the start of each send so the button never points at a stale hash. (#203) * [UX][all] Transaction-complete modal no longer auto-closes 3 s after success — user now dismisses explicitly, giving time to read the confirmation and tap **View on Midenscan**. (#203) diff --git a/android/.idea/deploymentTargetSelector.xml b/android/.idea/deploymentTargetSelector.xml index b268ef36c..ca16a995c 100644 --- a/android/.idea/deploymentTargetSelector.xml +++ b/android/.idea/deploymentTargetSelector.xml @@ -4,6 +4,7 @@ diff --git a/android/app/build.gradle b/android/app/build.gradle index 99c3b8e49..7838897c2 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -65,6 +65,8 @@ dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" // Biometric authentication for hardware security implementation 'androidx.biometric:biometric:1.1.0' + // Google Identity Services — AuthorizationClient for Drive scope OAuth (cloud backup) + implementation 'com.google.android.gms:play-services-auth:21.3.0' } apply from: 'capacitor.build.gradle' diff --git a/android/app/capacitor.build.gradle b/android/app/capacitor.build.gradle index cf03392f0..083915780 100644 --- a/android/app/capacitor.build.gradle +++ b/android/app/capacitor.build.gradle @@ -10,6 +10,7 @@ android { apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle" dependencies { implementation project(':capacitor-app') + implementation project(':capacitor-browser') implementation project(':capacitor-filesystem') implementation project(':capacitor-haptics') implementation project(':capacitor-keyboard') diff --git a/android/app/src/main/java/com/miden/wallet/GoogleAuthPlugin.kt b/android/app/src/main/java/com/miden/wallet/GoogleAuthPlugin.kt new file mode 100644 index 000000000..df737cca0 --- /dev/null +++ b/android/app/src/main/java/com/miden/wallet/GoogleAuthPlugin.kt @@ -0,0 +1,147 @@ +package com.miden.wallet + +import android.app.Activity +import android.content.Intent +import android.util.Log +import com.getcapacitor.JSArray +import com.getcapacitor.JSObject +import com.getcapacitor.Plugin +import com.getcapacitor.PluginCall +import com.getcapacitor.PluginMethod +import com.getcapacitor.annotation.CapacitorPlugin +import com.google.android.gms.auth.api.identity.AuthorizationRequest +import com.google.android.gms.auth.api.identity.AuthorizationResult +import com.google.android.gms.auth.api.identity.Identity +import com.google.android.gms.common.api.Scope + +/** + * Native Google OAuth bridge for cloud backup on Android. + * + * Uses the Google Identity Services `AuthorizationClient` (Google Play Services) + * to obtain an OAuth access token for the requested scopes — native UI, no + * browser redirect or custom URI scheme. The OAuth client is picked up + * implicitly from the app's package name + signing SHA-1 (registered as an + * Android-type OAuth client in Google Cloud Console), so no client ID needs + * to be passed in from JS. + * + * Access tokens returned by this API live ~1 hour; Google manages silent + * refresh internally on subsequent `signInSilently` calls — we don't persist + * a refresh token on device (matches chrome.identity behavior on the + * extension path). + */ +@CapacitorPlugin(name = "GoogleAuthAndroid") +class GoogleAuthPlugin : Plugin() { + + companion object { + private const val TAG = "GoogleAuthAndroid" + private const val REQUEST_AUTHORIZE = 9001 + // Google access tokens live ~1h; use a slightly conservative expiry so the + // JS layer eagerly re-requests via signInSilently (which returns cached). + private const val TOKEN_LIFETIME_SECONDS = 55 * 60 + } + + private var pendingCall: PluginCall? = null + + @PluginMethod + fun signIn(call: PluginCall) { + authorize(call, interactive = true) + } + + @PluginMethod + fun signInSilently(call: PluginCall) { + authorize(call, interactive = false) + } + + private fun authorize(call: PluginCall, interactive: Boolean) { + val scopesArray: JSArray = call.getArray("scopes") ?: run { + call.reject("scopes array is required") + return + } + + val scopes = try { + (0 until scopesArray.length()).map { Scope(scopesArray.getString(it)) } + } catch (e: Exception) { + call.reject("scopes must be an array of strings", e) + return + } + + val request = AuthorizationRequest.Builder() + .setRequestedScopes(scopes) + .build() + + Identity.getAuthorizationClient(activity) + .authorize(request) + .addOnSuccessListener { result -> + if (result.hasResolution()) { + // User consent required. + if (!interactive) { + // Silent mode — signal that interactive auth is needed + // without triggering any UI. + val ret = JSObject() + ret.put("needsConsent", true) + call.resolve(ret) + return@addOnSuccessListener + } + val pendingIntent = result.pendingIntent ?: run { + call.reject("Authorization requires consent but no pending intent was returned") + return@addOnSuccessListener + } + try { + pendingCall = call + activity.startIntentSenderForResult( + pendingIntent.intentSender, + REQUEST_AUTHORIZE, + null, + 0, + 0, + 0, + null + ) + } catch (e: Exception) { + pendingCall = null + call.reject("Failed to launch authorization consent: ${e.message}", e) + } + } else { + resolveWithResult(call, result) + } + } + .addOnFailureListener { e -> + Log.w(TAG, "authorize failed", e) + call.reject("Authorization failed: ${e.message}", e) + } + } + + override fun handleOnActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + super.handleOnActivityResult(requestCode, resultCode, data) + if (requestCode != REQUEST_AUTHORIZE) return + + val call = pendingCall ?: return + pendingCall = null + + if (resultCode != Activity.RESULT_OK) { + call.reject("User cancelled Google authorization") + return + } + + try { + val result = Identity.getAuthorizationClient(activity).getAuthorizationResultFromIntent(data) + resolveWithResult(call, result) + } catch (e: Exception) { + Log.w(TAG, "Failed to parse authorization result", e) + call.reject("Failed to parse authorization result: ${e.message}", e) + } + } + + private fun resolveWithResult(call: PluginCall, result: AuthorizationResult) { + val accessToken = result.accessToken + if (accessToken == null) { + call.reject("Authorization succeeded but no access token was returned") + return + } + val ret = JSObject() + ret.put("accessToken", accessToken) + ret.put("grantedScopes", JSArray(result.grantedScopes)) + ret.put("expiresIn", TOKEN_LIFETIME_SECONDS) + call.resolve(ret) + } +} diff --git a/android/app/src/main/java/com/miden/wallet/MainActivity.java b/android/app/src/main/java/com/miden/wallet/MainActivity.java index 7eff59079..e2092b33a 100644 --- a/android/app/src/main/java/com/miden/wallet/MainActivity.java +++ b/android/app/src/main/java/com/miden/wallet/MainActivity.java @@ -17,6 +17,7 @@ public class MainActivity extends BridgeActivity { protected void onCreate(Bundle savedInstanceState) { // Register custom plugins before super.onCreate registerPlugin(HardwareSecurityPlugin.class); + registerPlugin(GoogleAuthPlugin.class); super.onCreate(savedInstanceState); setupStatusBar(); diff --git a/android/capacitor.settings.gradle b/android/capacitor.settings.gradle index 544a7541b..2ba6607fd 100644 --- a/android/capacitor.settings.gradle +++ b/android/capacitor.settings.gradle @@ -5,6 +5,9 @@ project(':capacitor-android').projectDir = new File('../node_modules/@capacitor/ include ':capacitor-app' project(':capacitor-app').projectDir = new File('../node_modules/@capacitor/app/android') +include ':capacitor-browser' +project(':capacitor-browser').projectDir = new File('../node_modules/@capacitor/browser/android') + include ':capacitor-filesystem' project(':capacitor-filesystem').projectDir = new File('../node_modules/@capacitor/filesystem/android') diff --git a/ios/App/App.xcodeproj/project.pbxproj b/ios/App/App.xcodeproj/project.pbxproj index dc1259670..4cad8a09a 100644 --- a/ios/App/App.xcodeproj/project.pbxproj +++ b/ios/App/App.xcodeproj/project.pbxproj @@ -18,6 +18,7 @@ 83CCE39C02FD0BF8DD39551F /* AppViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A6B2865F76F213517CFCCD1 /* AppViewController.swift */; }; B54E83DC5BCCDB512256423A /* LocalBiometricPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21A34DAD709C71D553F88951 /* LocalBiometricPlugin.swift */; }; C7D4E92A3F8B1C5D00A2B9E1 /* BarcodeScannerPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7D4E92B3F8B1C5D00A2B9E2 /* BarcodeScannerPlugin.swift */; }; + D1A2B3C4E5F607890A1B2C3D /* PasskeyPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1A2B3C4E5F607890A1B2C3E /* PasskeyPlugin.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -35,6 +36,7 @@ 873F0344C8952CB5585102E0 /* App.entitlements */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.entitlements; path = App.entitlements; sourceTree = ""; }; 958DCC722DB07C7200EA8C5F /* debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = debug.xcconfig; path = ../debug.xcconfig; sourceTree = SOURCE_ROOT; }; C7D4E92B3F8B1C5D00A2B9E2 /* BarcodeScannerPlugin.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = BarcodeScannerPlugin.swift; sourceTree = ""; }; + D1A2B3C4E5F607890A1B2C3E /* PasskeyPlugin.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = PasskeyPlugin.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -80,6 +82,7 @@ 50B271D01FEDC1A000F3C39B /* public */, 21A34DAD709C71D553F88951 /* LocalBiometricPlugin.swift */, C7D4E92B3F8B1C5D00A2B9E2 /* BarcodeScannerPlugin.swift */, + D1A2B3C4E5F607890A1B2C3E /* PasskeyPlugin.swift */, 1A6B2865F76F213517CFCCD1 /* AppViewController.swift */, 873F0344C8952CB5585102E0 /* App.entitlements */, ); @@ -179,6 +182,7 @@ B54E83DC5BCCDB512256423A /* LocalBiometricPlugin.swift in Sources */, C7D4E92A3F8B1C5D00A2B9E1 /* BarcodeScannerPlugin.swift in Sources */, 83CCE39C02FD0BF8DD39551F /* AppViewController.swift in Sources */, + D1A2B3C4E5F607890A1B2C3D /* PasskeyPlugin.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/ios/App/App/App.entitlements b/ios/App/App/App.entitlements index 2a557a662..379accfac 100644 --- a/ios/App/App/App.entitlements +++ b/ios/App/App/App.entitlements @@ -6,5 +6,9 @@ $(AppIdentifierPrefix)com.miden.wallet + com.apple.developer.associated-domains + + webcredentials:api.midenbrowserwallet.com + diff --git a/ios/App/App/AppViewController.swift b/ios/App/App/AppViewController.swift index 7c12db854..4bbaec9b9 100644 --- a/ios/App/App/AppViewController.swift +++ b/ios/App/App/AppViewController.swift @@ -5,5 +5,6 @@ class AppViewController: CAPBridgeViewController { override open func capacitorDidLoad() { bridge?.registerPluginInstance(LocalBiometricPlugin()) bridge?.registerPluginInstance(BarcodeScannerPlugin()) + bridge?.registerPluginInstance(PasskeyPlugin()) } } diff --git a/ios/App/App/Info.plist b/ios/App/App/Info.plist index d28109257..8e892bdf9 100644 --- a/ios/App/App/Info.plist +++ b/ios/App/App/Info.plist @@ -51,6 +51,15 @@ UIViewControllerBasedStatusBarAppearance + CFBundleURLTypes + + + CFBundleURLSchemes + + com.googleusercontent.apps.849882985138-gbl44m5nmvuim6eiv4vmtg5rvoq4knqi + + + NSAppTransportSecurity NSAllowsArbitraryLoads diff --git a/ios/App/App/PasskeyPlugin.swift b/ios/App/App/PasskeyPlugin.swift new file mode 100644 index 000000000..7a964a7f4 --- /dev/null +++ b/ios/App/App/PasskeyPlugin.swift @@ -0,0 +1,278 @@ +import Foundation +import Capacitor +import AuthenticationServices +import CryptoKit +import os.log + +private let logger = OSLog(subsystem: "com.miden.wallet", category: "Passkey") + +/// Native Capacitor plugin for passkey operations using Apple's ASAuthorization API +/// with PRF (Pseudo-Random Function) extension support. +/// +/// WKWebView's JavaScript WebAuthn bridge does not pass through the PRF extension, +/// so we bypass it entirely and call the native API directly. +/// +/// Requires iOS 18.0+ for PRF support. +@objc(PasskeyPlugin) +public class PasskeyPlugin: CAPPlugin, CAPBridgedPlugin, ASAuthorizationControllerDelegate, ASAuthorizationControllerPresentationContextProviding { + public let identifier = "PasskeyPlugin" + public let jsName = "Passkey" + public let pluginMethods: [CAPPluginMethod] = [ + CAPPluginMethod(name: "isAvailable", returnType: CAPPluginReturnPromise), + CAPPluginMethod(name: "register", returnType: CAPPluginReturnPromise), + CAPPluginMethod(name: "authenticate", returnType: CAPPluginReturnPromise) + ] + + // Strong reference to prevent ASAuthorizationController deallocation mid-flow + private var authController: ASAuthorizationController? + private var currentCall: CAPPluginCall? + private var isRegistration = false + + // MARK: - ASAuthorizationControllerPresentationContextProviding + + public func presentationAnchor(for controller: ASAuthorizationController) -> ASPresentationAnchor { + return self.bridge?.viewController?.view.window ?? ASPresentationAnchor() + } + + // MARK: - Plugin Methods + + @objc func isAvailable(_ call: CAPPluginCall) { + os_log("[Passkey] isAvailable called", log: logger, type: .debug) + if #available(iOS 18.0, *) { + call.resolve(["available": true]) + } else { + os_log("[Passkey] iOS 18.0+ required for PRF support", log: logger, type: .info) + call.resolve(["available": false]) + } + } + + @objc func register(_ call: CAPPluginCall) { + os_log("[Passkey] register called", log: logger, type: .debug) + + guard #available(iOS 18.0, *) else { + call.reject("Passkey PRF requires iOS 18.0+") + return + } + + guard let rpId = call.getString("rpId"), + let userName = call.getString("userName"), + let _ = call.getString("userDisplayName"), + let userIdBase64 = call.getString("userId"), + let challengeBase64 = call.getString("challenge"), + let prfSaltBase64 = call.getString("prfSalt") else { + call.reject("Missing required parameters") + return + } + + guard let userId = Data(base64Encoded: userIdBase64), + let challenge = Data(base64Encoded: challengeBase64), + let prfSalt = Data(base64Encoded: prfSaltBase64) else { + call.reject("Invalid base64 encoding") + return + } + + self.currentCall = call + self.isRegistration = true + + let provider = ASAuthorizationPlatformPublicKeyCredentialProvider(relyingPartyIdentifier: rpId) + let request = provider.createCredentialRegistrationRequest( + challenge: challenge, + name: userName, + userID: userId + ) + + // Attach PRF with salt so registration returns the PRF output directly. + let saltValues = ASAuthorizationPublicKeyCredentialPRFAssertionInput.InputValues(saltInput1: prfSalt) + request.prf = .inputValues(saltValues) + + DispatchQueue.main.async { [weak self] in + guard let self = self else { return } + let controller = ASAuthorizationController(authorizationRequests: [request]) + controller.delegate = self + controller.presentationContextProvider = self + self.authController = controller + controller.performRequests() + } + } + + @objc func authenticate(_ call: CAPPluginCall) { + os_log("[Passkey] authenticate called", log: logger, type: .debug) + + guard #available(iOS 18.0, *) else { + call.reject("Passkey PRF requires iOS 18.0+") + return + } + + guard let rpId = call.getString("rpId"), + let credentialIdBase64 = call.getString("credentialId"), + let challengeBase64 = call.getString("challenge"), + let prfSaltBase64 = call.getString("prfSalt") else { + call.reject("Missing required parameters") + return + } + + guard let credentialId = Data(base64Encoded: credentialIdBase64), + let challenge = Data(base64Encoded: challengeBase64), + let prfSalt = Data(base64Encoded: prfSaltBase64) else { + call.reject("Invalid base64 encoding") + return + } + + self.currentCall = call + self.isRegistration = false + + let provider = ASAuthorizationPlatformPublicKeyCredentialProvider(relyingPartyIdentifier: rpId) + let request = provider.createCredentialAssertionRequest(challenge: challenge) + + request.allowedCredentials = [ + ASAuthorizationPlatformPublicKeyCredentialDescriptor(credentialID: credentialId) + ] + + let saltValues = ASAuthorizationPublicKeyCredentialPRFAssertionInput.InputValues(saltInput1: prfSalt) + request.prf = .inputValues(saltValues) + + DispatchQueue.main.async { [weak self] in + guard let self = self else { return } + let controller = ASAuthorizationController(authorizationRequests: [request]) + controller.delegate = self + controller.presentationContextProvider = self + self.authController = controller + controller.performRequests() + } + } + + // MARK: - ASAuthorizationControllerDelegate + + public func authorizationController( + controller: ASAuthorizationController, + didCompleteWithAuthorization authorization: ASAuthorization + ) { + os_log("[Passkey] Authorization completed", log: logger, type: .debug) + + guard let call = currentCall else { + os_log("[Passkey] No pending call", log: logger, type: .error) + return + } + + if #available(iOS 18.0, *) { + if let registration = authorization.credential as? ASAuthorizationPlatformPublicKeyCredentialRegistration { + handleRegistrationResult(registration, call: call) + } else if let assertion = authorization.credential as? ASAuthorizationPlatformPublicKeyCredentialAssertion { + handleAssertionResult(assertion, call: call) + } else { + call.reject("Unexpected credential type") + cleanup() + } + } else { + call.reject("iOS 18.0+ required") + cleanup() + } + } + + public func authorizationController( + controller: ASAuthorizationController, + didCompleteWithError error: Error + ) { + os_log("[Passkey] Authorization error: %{public}@", log: logger, type: .error, error.localizedDescription) + + guard let call = currentCall else { return } + + let nsError = error as NSError + if nsError.domain == ASAuthorizationError.errorDomain, + let code = ASAuthorizationError.Code(rawValue: nsError.code) { + switch code { + case .canceled: + call.reject("Passkey operation was cancelled", "CANCELLED") + case .failed: + call.reject("Passkey operation failed", "FAILED") + case .invalidResponse: + call.reject("Invalid response from authenticator", "INVALID_RESPONSE") + case .notHandled: + call.reject("Request not handled", "NOT_HANDLED") + case .notInteractive: + call.reject("Not interactive", "NOT_INTERACTIVE") + @unknown default: + call.reject("Authorization error: \(error.localizedDescription)") + } + } else { + call.reject("Passkey error: \(error.localizedDescription)") + } + + cleanup() + } + + // MARK: - Result Handlers + + @available(iOS 18.0, *) + private func handleRegistrationResult( + _ registration: ASAuthorizationPlatformPublicKeyCredentialRegistration, + call: CAPPluginCall + ) { + let credentialId = registration.credentialID + os_log("[Passkey] Registration succeeded, credentialId length: %d", log: logger, type: .debug, credentialId.count) + + guard let prfOutput = registration.prf else { + os_log("[Passkey] No PRF output from registration", log: logger, type: .error) + call.reject("PRF extension not supported by this authenticator") + cleanup() + return + } + + guard prfOutput.isSupported else { + os_log("[Passkey] PRF not supported by authenticator", log: logger, type: .error) + call.reject("PRF extension not supported by this authenticator") + cleanup() + return + } + + guard let prfKey = prfOutput.first else { + os_log("[Passkey] PRF output has no first key", log: logger, type: .error) + call.reject("PRF output not available from registration") + cleanup() + return + } + + let prfData = prfKey.withUnsafeBytes { Data(Array($0)) } + os_log("[Passkey] PRF output obtained from registration, length: %d", log: logger, type: .debug, prfData.count) + + call.resolve([ + "credentialId": credentialId.base64EncodedString(), + "prfOutput": prfData.base64EncodedString() + ]) + + cleanup() + } + + @available(iOS 18.0, *) + private func handleAssertionResult( + _ assertion: ASAuthorizationPlatformPublicKeyCredentialAssertion, + call: CAPPluginCall + ) { + os_log("[Passkey] Assertion completed", log: logger, type: .debug) + + guard let prfResult = assertion.prf else { + os_log("[Passkey] No PRF output in assertion result", log: logger, type: .error) + call.reject("PRF output not available") + cleanup() + return + } + + let prfData = prfResult.first.withUnsafeBytes { Data(Array($0)) } + os_log("[Passkey] PRF output obtained, length: %d", log: logger, type: .debug, prfData.count) + + call.resolve([ + "credentialId": assertion.credentialID.base64EncodedString(), + "prfOutput": prfData.base64EncodedString() + ]) + + cleanup() + } + + // MARK: - Cleanup + + private func cleanup() { + currentCall = nil + authController = nil + isRegistration = false + } +} diff --git a/ios/App/CapApp-SPM/Package.swift b/ios/App/CapApp-SPM/Package.swift index 810263a71..1ae49d7e2 100644 --- a/ios/App/CapApp-SPM/Package.swift +++ b/ios/App/CapApp-SPM/Package.swift @@ -13,6 +13,7 @@ let package = Package( dependencies: [ .package(url: "https://github.com/ionic-team/capacitor-swift-pm.git", exact: "8.0.1"), .package(name: "CapacitorApp", path: "../../../node_modules/@capacitor/app"), + .package(name: "CapacitorBrowser", path: "../../../node_modules/@capacitor/browser"), .package(name: "CapacitorFilesystem", path: "../../../node_modules/@capacitor/filesystem"), .package(name: "CapacitorHaptics", path: "../../../node_modules/@capacitor/haptics"), .package(name: "CapacitorKeyboard", path: "../../../node_modules/@capacitor/keyboard"), @@ -29,6 +30,7 @@ let package = Package( .product(name: "Capacitor", package: "capacitor-swift-pm"), .product(name: "Cordova", package: "capacitor-swift-pm"), .product(name: "CapacitorApp", package: "CapacitorApp"), + .product(name: "CapacitorBrowser", package: "CapacitorBrowser"), .product(name: "CapacitorFilesystem", package: "CapacitorFilesystem"), .product(name: "CapacitorHaptics", package: "CapacitorHaptics"), .product(name: "CapacitorKeyboard", package: "CapacitorKeyboard"), diff --git a/package.json b/package.json index 5ce4fb9f3..968f86f4a 100644 --- a/package.json +++ b/package.json @@ -8,9 +8,9 @@ "scripts": { "postinstall": "rimraf node_modules/@miden-sdk/react/node_modules && patch-package", "start": "yarn watch:src", - "dev": "rimraf ./dist && concurrently yarn:watch:*", + "dev": "rimraf ./dist && cross-env TARGET_BROWSER=chrome MODE_ENV=development NODE_ENV=development yarn build:bg && cross-env TARGET_BROWSER=chrome MODE_ENV=development NODE_ENV=development yarn build:cs && concurrently -n bg,ext -c blue,green \"cross-env TARGET_BROWSER=chrome MODE_ENV=development NODE_ENV=development vite build --config vite.background.config.ts --watch\" \"cross-env TARGET_BROWSER=chrome MANIFEST_VERSION=3 MODE_ENV=development NODE_ENV=development vite build --config vite.extension.config.ts --watch\"", "dev-clean": "rimraf node_modules && yarn cache clean && yarn install && yarn dev", - "watch:src": "echo 'Use vite dev instead'", + "watch:src": "vite run --config vite.extension.config.ts", "watch:dist": "mv3-hot-reload", "build": "yarn build:extension && yarn build:mobile", "build:extension": "cross-env TARGET_BROWSER=chrome yarn build:bg && cross-env TARGET_BROWSER=chrome yarn build:cs && cross-env TARGET_BROWSER=chrome MANIFEST_VERSION=3 yarn build:ext", @@ -89,13 +89,14 @@ "desktop:build": "yarn build:desktop && yarn tauri build", "desktop:reset": "rm -rf ~/Library/WebKit/com.miden.wallet ~/Library/Caches/com.miden.wallet ~/Library/Application\\ Support/com.miden.wallet", "build:devnet": "rimraf ./dist && yarn clear:webpack-cache && cross-env MIDEN_NETWORK=devnet DISABLE_TS_CHECKER=true NODE_ENV=development MODE_ENV=production MANIFEST_VERSION=3 webpack", - "dev:devnet": "cross-env MIDEN_NETWORK=devnet DISABLE_TS_CHECKER=true NODE_ENV=development MODE_ENV=development MANIFEST_VERSION=3 webpack --watch --progress", + "dev:devnet": "cross-env MIDEN_NETWORK=devnet yarn run dev", "build:desktop:devnet": "rimraf ./dist/desktop && cross-env MIDEN_NETWORK=devnet DISABLE_TS_CHECKER=true NODE_ENV=development MODE_ENV=production webpack --config webpack.desktop.config.js", "tauri": "tauri" }, "dependencies": { "@capacitor/android": "^8.0.1", "@capacitor/app": "^8.0.0", + "@capacitor/browser": "^8.0.3", "@capacitor/core": "^8.0.1", "@capacitor/filesystem": "^8.0.0", "@capacitor/haptics": "^8.0.0", diff --git a/public/_locales/de/messages.json b/public/_locales/de/messages.json index 911273879..436829758 100644 --- a/public/_locales/de/messages.json +++ b/public/_locales/de/messages.json @@ -1562,6 +1562,63 @@ "message": "Verschlüsselte Wallet-Datei", "englishSource": "Encrypted Wallet File" }, + "cloudBackup": { + "message": "Cloud-Backup", + "englishSource": "Cloud Backup" + }, + "importWithCloudBackup": { + "message": "Aus Cloud-Backup importieren", + "englishSource": "Import from Cloud Backup" + }, + "importWithCloudBackupDescription": { + "message": "Stellen Sie Ihre Wallet-Daten von Google Drive wieder her. Sie benötigen weiterhin Ihre seed phrase, um den Import abzuschließen.", + "englishSource": "Restore your wallet data from Google Drive. You will still need your seed phrase to complete the import." + }, + "importFromCloudBackup": { + "message": "Aus der Cloud importieren", + "englishSource": "Import from Cloud" + }, + "cloudSignIn": { + "message": "Melden Sie sich mit Google an", + "englishSource": "Sign in with Google" + }, + "cloudSignedIn": { + "message": "Angemeldet als $email$ ", + "englishSource": "Signed in as $email$", + "placeholders": { + "email": { + "content": "$1" + } + } + }, + "backupPassword": { + "message": "Backup-Passwort", + "englishSource": "Backup Password" + }, + "cloudRestoring": { + "message": "Backup wird wiederhergestellt...", + "englishSource": "Restoring backup..." + }, + "cloudProbing": { + "message": "Sicherung wird überprüft...", + "englishSource": "Checking backup..." + }, + "cloudNoBackupFound": { + "message": "Für dieses Konto wurde kein Backup gefunden.", + "englishSource": "No backup found on this account." + }, + "cloudPasskeyRestoreHint": { + "message": "Dieses Backup wurde mit einem Passkey verschlüsselt. Zum Wiederherstellen authentifizieren.", + "englishSource": "This backup was encrypted with a passkey. Authenticate to restore." + }, + "cloudRestoreWithPasskey": { + "message": "Mit Passkey wiederherstellen", + "englishSource": "Restore with Passkey" + }, + "signingIn": { + "message": "Anmelden...", + "englishSource": "Signing in..." + }, "encryptedWalletFileDescription": { "message": "Geben Sie Ihr Passwort ein, um auf Ihre verschlüsselte Wallet-Datei zuzugreifen.", "englishSource": "Enter your password to access your Encrypted Wallet File." diff --git a/public/_locales/en/en.json b/public/_locales/en/en.json index 94d061182..2df27f92d 100644 --- a/public/_locales/en/en.json +++ b/public/_locales/en/en.json @@ -357,6 +357,19 @@ "importWithEncryptedWalletFileDescription": "Upload your encrypted wallet file to securely import your account. Your data remains private, with only hashed information stored on-chain", "twitter": "X (Twitter)", "encryptedWalletFile": "Encrypted Wallet File", + "cloudBackup": "Cloud Backup", + "importWithCloudBackup": "Import from Cloud Backup", + "importWithCloudBackupDescription": "Restore your wallet data from Google Drive. You will still need your seed phrase to complete the import.", + "importFromCloudBackup": "Import from Cloud", + "cloudSignIn": "Sign in with Google", + "cloudSignedIn": "Signed in as $email$", + "backupPassword": "Backup Password", + "cloudRestoring": "Restoring backup...", + "cloudProbing": "Checking backup...", + "cloudNoBackupFound": "No backup found on this account.", + "cloudPasskeyRestoreHint": "This backup was encrypted with a passkey. Authenticate to restore.", + "cloudRestoreWithPasskey": "Restore with Passkey", + "signingIn": "Signing in...", "encryptedWalletFileDescription": "Enter your password to access your Encrypted Wallet File.", "encryptedWalletFileDescriptionHardware": "Unlock with your passcode to access your Encrypted Wallet File.", "encryptedWalletFileConfirmation": "I will not share my Encrypted Wallet File with anyone, including Miden Wallet.", diff --git a/public/_locales/en/messages.json b/public/_locales/en/messages.json index 0fa839265..3f29d8db1 100644 --- a/public/_locales/en/messages.json +++ b/public/_locales/en/messages.json @@ -1531,6 +1531,63 @@ "message": "Encrypted Wallet File", "englishSource": "Encrypted Wallet File" }, + "cloudBackup": { + "message": "Cloud Backup", + "englishSource": "Cloud Backup" + }, + "importWithCloudBackup": { + "message": "Import from Cloud Backup", + "englishSource": "Import from Cloud Backup" + }, + "importWithCloudBackupDescription": { + "message": "Restore your wallet data from Google Drive. You will still need your seed phrase to complete the import.", + "englishSource": "Restore your wallet data from Google Drive. You will still need your seed phrase to complete the import." + }, + "importFromCloudBackup": { + "message": "Import from Cloud", + "englishSource": "Import from Cloud" + }, + "cloudSignIn": { + "message": "Sign in with Google", + "englishSource": "Sign in with Google" + }, + "cloudSignedIn": { + "message": "Signed in as $email$", + "englishSource": "Signed in as $email$", + "placeholders": { + "email": { + "content": "$1" + } + } + }, + "backupPassword": { + "message": "Backup Password", + "englishSource": "Backup Password" + }, + "cloudRestoring": { + "message": "Restoring backup...", + "englishSource": "Restoring backup..." + }, + "cloudProbing": { + "message": "Checking backup...", + "englishSource": "Checking backup..." + }, + "cloudNoBackupFound": { + "message": "No backup found on this account.", + "englishSource": "No backup found on this account." + }, + "cloudPasskeyRestoreHint": { + "message": "This backup was encrypted with a passkey. Authenticate to restore.", + "englishSource": "This backup was encrypted with a passkey. Authenticate to restore." + }, + "cloudRestoreWithPasskey": { + "message": "Restore with Passkey", + "englishSource": "Restore with Passkey" + }, + "signingIn": { + "message": "Signing in...", + "englishSource": "Signing in..." + }, "encryptedWalletFileDescription": { "message": "Enter your password to access your Encrypted Wallet File.", "englishSource": "Enter your password to access your Encrypted Wallet File." diff --git a/public/_locales/en_GB/messages.json b/public/_locales/en_GB/messages.json index 04dad8d66..f67c2231e 100644 --- a/public/_locales/en_GB/messages.json +++ b/public/_locales/en_GB/messages.json @@ -1590,6 +1590,63 @@ "message": "Encrypted Wallet File", "englishSource": "Encrypted Wallet File" }, + "cloudBackup": { + "message": "Cloud Backup", + "englishSource": "Cloud Backup" + }, + "importWithCloudBackup": { + "message": "Import from Cloud Backup", + "englishSource": "Import from Cloud Backup" + }, + "importWithCloudBackupDescription": { + "message": "Restore your wallet data from Google Drive. You will still need your seed phrase to complete the import.", + "englishSource": "Restore your wallet data from Google Drive. You will still need your seed phrase to complete the import." + }, + "importFromCloudBackup": { + "message": "Import from Cloud", + "englishSource": "Import from Cloud" + }, + "cloudSignIn": { + "message": "Sign in with Google", + "englishSource": "Sign in with Google" + }, + "cloudSignedIn": { + "message": "Signed in as $email$ ", + "englishSource": "Signed in as $email$", + "placeholders": { + "email": { + "content": "$1" + } + } + }, + "backupPassword": { + "message": "Backup Password", + "englishSource": "Backup Password" + }, + "cloudRestoring": { + "message": "Restoring backup...", + "englishSource": "Restoring backup..." + }, + "cloudProbing": { + "message": "Checking backup...", + "englishSource": "Checking backup..." + }, + "cloudNoBackupFound": { + "message": "No backup found on this account.", + "englishSource": "No backup found on this account." + }, + "cloudPasskeyRestoreHint": { + "message": "This backup was encrypted with a passkey. Authenticate to restore.", + "englishSource": "This backup was encrypted with a passkey. Authenticate to restore." + }, + "cloudRestoreWithPasskey": { + "message": "Restore with Passkey", + "englishSource": "Restore with Passkey" + }, + "signingIn": { + "message": "Signing in...", + "englishSource": "Signing in..." + }, "encryptedWalletFileDescription": { "message": "Enter your password to access your Encrypted Wallet File.", "englishSource": "Enter your password to access your Encrypted Wallet File." diff --git a/public/_locales/es/messages.json b/public/_locales/es/messages.json index 8bd4be953..ad2afafea 100644 --- a/public/_locales/es/messages.json +++ b/public/_locales/es/messages.json @@ -1504,6 +1504,63 @@ "message": "Archivo de billetera cifrado", "englishSource": "Encrypted Wallet File" }, + "cloudBackup": { + "message": "Copia de seguridad en la nube", + "englishSource": "Cloud Backup" + }, + "importWithCloudBackup": { + "message": "Importar desde copia de seguridad en la nube", + "englishSource": "Import from Cloud Backup" + }, + "importWithCloudBackupDescription": { + "message": "Restaure los datos de su billetera desde Google Drive. Aún necesitarás tu seed phrase para completar la importación.", + "englishSource": "Restore your wallet data from Google Drive. You will still need your seed phrase to complete the import." + }, + "importFromCloudBackup": { + "message": "Importar desde la nube", + "englishSource": "Import from Cloud" + }, + "cloudSignIn": { + "message": "Iniciar sesión con Google", + "englishSource": "Sign in with Google" + }, + "cloudSignedIn": { + "message": "Iniciado sesión como $email$ ", + "englishSource": "Signed in as $email$", + "placeholders": { + "email": { + "content": "$1" + } + } + }, + "backupPassword": { + "message": "Contraseña de respaldo", + "englishSource": "Backup Password" + }, + "cloudRestoring": { + "message": "Restaurando copia de seguridad...", + "englishSource": "Restoring backup..." + }, + "cloudProbing": { + "message": "Comprobando copia de seguridad...", + "englishSource": "Checking backup..." + }, + "cloudNoBackupFound": { + "message": "No se encontró ninguna copia de seguridad en esta cuenta.", + "englishSource": "No backup found on this account." + }, + "cloudPasskeyRestoreHint": { + "message": "Esta copia de seguridad se cifró con una clave de acceso. Autenticar para restaurar.", + "englishSource": "This backup was encrypted with a passkey. Authenticate to restore." + }, + "cloudRestoreWithPasskey": { + "message": "Restaurar con clave de acceso", + "englishSource": "Restore with Passkey" + }, + "signingIn": { + "message": "Iniciando sesión...", + "englishSource": "Signing in..." + }, "encryptedWalletFileDescription": { "message": "Ingrese su contraseña para acceder a su archivo de billetera cifrado.", "englishSource": "Enter your password to access your Encrypted Wallet File." diff --git a/public/_locales/fr/messages.json b/public/_locales/fr/messages.json index e613403e6..775de20ba 100644 --- a/public/_locales/fr/messages.json +++ b/public/_locales/fr/messages.json @@ -1561,6 +1561,63 @@ "message": "Fichier de portefeuille crypté", "englishSource": "Encrypted Wallet File" }, + "cloudBackup": { + "message": "Sauvegarde dans le cloud", + "englishSource": "Cloud Backup" + }, + "importWithCloudBackup": { + "message": "Importer depuis la sauvegarde cloud", + "englishSource": "Import from Cloud Backup" + }, + "importWithCloudBackupDescription": { + "message": "Restaurez les données de votre portefeuille depuis Google Drive. Vous aurez toujours besoin de votre seed phrase pour terminer l'importation.", + "englishSource": "Restore your wallet data from Google Drive. You will still need your seed phrase to complete the import." + }, + "importFromCloudBackup": { + "message": "Importer depuis le cloud", + "englishSource": "Import from Cloud" + }, + "cloudSignIn": { + "message": "Connectez-vous avec Google", + "englishSource": "Sign in with Google" + }, + "cloudSignedIn": { + "message": "Connecté en tant que $email$ ", + "englishSource": "Signed in as $email$", + "placeholders": { + "email": { + "content": "$1" + } + } + }, + "backupPassword": { + "message": "Mot de passe de sauvegarde", + "englishSource": "Backup Password" + }, + "cloudRestoring": { + "message": "Restauration de la sauvegarde...", + "englishSource": "Restoring backup..." + }, + "cloudProbing": { + "message": "Vérification de la sauvegarde...", + "englishSource": "Checking backup..." + }, + "cloudNoBackupFound": { + "message": "Aucune sauvegarde trouvée sur ce compte.", + "englishSource": "No backup found on this account." + }, + "cloudPasskeyRestoreHint": { + "message": "Cette sauvegarde a été chiffrée avec un mot de passe. Authenticate to restore.", + "englishSource": "This backup was encrypted with a passkey. Authenticate to restore." + }, + "cloudRestoreWithPasskey": { + "message": "Restaurer avec la clé d'accès", + "englishSource": "Restore with Passkey" + }, + "signingIn": { + "message": "Connexion...", + "englishSource": "Signing in..." + }, "encryptedWalletFileDescription": { "message": "Entrez votre mot de passe pour accéder à votre fichier de portefeuille crypté.", "englishSource": "Enter your password to access your Encrypted Wallet File." diff --git a/public/_locales/ja/messages.json b/public/_locales/ja/messages.json index 677062b60..048c04830 100644 --- a/public/_locales/ja/messages.json +++ b/public/_locales/ja/messages.json @@ -1562,6 +1562,63 @@ "message": "暗号化されたウォレットファイル", "englishSource": "Encrypted Wallet File" }, + "cloudBackup": { + "message": "クラウドバックアップ", + "englishSource": "Cloud Backup" + }, + "importWithCloudBackup": { + "message": "クラウドバックアップからインポート", + "englishSource": "Import from Cloud Backup" + }, + "importWithCloudBackupDescription": { + "message": "Googleドライブからウォレットデータを復元します。インポートを完了するには、seed phrase が必要になります。", + "englishSource": "Restore your wallet data from Google Drive. You will still need your seed phrase to complete the import." + }, + "importFromCloudBackup": { + "message": "クラウドからインポート", + "englishSource": "Import from Cloud" + }, + "cloudSignIn": { + "message": "Googleでサインイン", + "englishSource": "Sign in with Google" + }, + "cloudSignedIn": { + "message": "としてサインインしました $email$ ", + "englishSource": "Signed in as $email$", + "placeholders": { + "email": { + "content": "$1" + } + } + }, + "backupPassword": { + "message": "バックアップパスワード", + "englishSource": "Backup Password" + }, + "cloudRestoring": { + "message": "バックアップを復元中...", + "englishSource": "Restoring backup..." + }, + "cloudProbing": { + "message": "バックアップを確認しています...", + "englishSource": "Checking backup..." + }, + "cloudNoBackupFound": { + "message": "このアカウントにはバックアップが見つかりませんでした。", + "englishSource": "No backup found on this account." + }, + "cloudPasskeyRestoreHint": { + "message": "このバックアップはパスキーで暗号化されていました。認証して復元します。", + "englishSource": "This backup was encrypted with a passkey. Authenticate to restore." + }, + "cloudRestoreWithPasskey": { + "message": "パスキーを使用して復元する", + "englishSource": "Restore with Passkey" + }, + "signingIn": { + "message": "サインイン中...", + "englishSource": "Signing in..." + }, "encryptedWalletFileDescription": { "message": "パスワードを入力して、暗号化されたウォレット ファイルにアクセスします。", "englishSource": "Enter your password to access your Encrypted Wallet File." diff --git a/public/_locales/ko/messages.json b/public/_locales/ko/messages.json index 470472b06..8ab78767d 100644 --- a/public/_locales/ko/messages.json +++ b/public/_locales/ko/messages.json @@ -1562,6 +1562,63 @@ "message": "암호화된 지갑 파일", "englishSource": "Encrypted Wallet File" }, + "cloudBackup": { + "message": "클라우드 백업", + "englishSource": "Cloud Backup" + }, + "importWithCloudBackup": { + "message": "클라우드 백업에서 가져오기", + "englishSource": "Import from Cloud Backup" + }, + "importWithCloudBackupDescription": { + "message": "Google 드라이브에서 지갑 데이터를 복원하세요. 가져오기를 완료하려면 seed phrase이 필요합니다.", + "englishSource": "Restore your wallet data from Google Drive. You will still need your seed phrase to complete the import." + }, + "importFromCloudBackup": { + "message": "클라우드에서 가져오기", + "englishSource": "Import from Cloud" + }, + "cloudSignIn": { + "message": "Google로 로그인", + "englishSource": "Sign in with Google" + }, + "cloudSignedIn": { + "message": "다음 계정으로 로그인됨 $email$ ", + "englishSource": "Signed in as $email$", + "placeholders": { + "email": { + "content": "$1" + } + } + }, + "backupPassword": { + "message": "백업 비밀번호", + "englishSource": "Backup Password" + }, + "cloudRestoring": { + "message": "백업 복원 중...", + "englishSource": "Restoring backup..." + }, + "cloudProbing": { + "message": "백업 확인 중...", + "englishSource": "Checking backup..." + }, + "cloudNoBackupFound": { + "message": "이 계정에는 백업이 없습니다.", + "englishSource": "No backup found on this account." + }, + "cloudPasskeyRestoreHint": { + "message": "이 백업은 암호키로 암호화되었습니다. 복원하려면 인증하세요.", + "englishSource": "This backup was encrypted with a passkey. Authenticate to restore." + }, + "cloudRestoreWithPasskey": { + "message": "패스키로 복원", + "englishSource": "Restore with Passkey" + }, + "signingIn": { + "message": "로그인 중...", + "englishSource": "Signing in..." + }, "encryptedWalletFileDescription": { "message": "암호화된 지갑 파일에 접근하려면 비밀번호를 입력하세요.", "englishSource": "Enter your password to access your Encrypted Wallet File." diff --git a/public/_locales/pl/messages.json b/public/_locales/pl/messages.json index 6e33744de..5901d65fa 100644 --- a/public/_locales/pl/messages.json +++ b/public/_locales/pl/messages.json @@ -1504,6 +1504,63 @@ "message": "Zaszyfrowany plik portfela", "englishSource": "Encrypted Wallet File" }, + "cloudBackup": { + "message": "Kopia zapasowa w chmurze", + "englishSource": "Cloud Backup" + }, + "importWithCloudBackup": { + "message": "Importuj z kopii zapasowej w chmurze", + "englishSource": "Import from Cloud Backup" + }, + "importWithCloudBackupDescription": { + "message": "Przywróć dane portfela z Dysku Google. Do ukończenia importu nadal będziesz potrzebować seed phrase.", + "englishSource": "Restore your wallet data from Google Drive. You will still need your seed phrase to complete the import." + }, + "importFromCloudBackup": { + "message": "Importuj z chmury", + "englishSource": "Import from Cloud" + }, + "cloudSignIn": { + "message": "Zaloguj się za pomocą Google", + "englishSource": "Sign in with Google" + }, + "cloudSignedIn": { + "message": "Zalogowałem się jako $email$ ", + "englishSource": "Signed in as $email$", + "placeholders": { + "email": { + "content": "$1" + } + } + }, + "backupPassword": { + "message": "Hasło zapasowe", + "englishSource": "Backup Password" + }, + "cloudRestoring": { + "message": "Przywracam kopię zapasową...", + "englishSource": "Restoring backup..." + }, + "cloudProbing": { + "message": "Sprawdzam kopię zapasową...", + "englishSource": "Checking backup..." + }, + "cloudNoBackupFound": { + "message": "Na tym koncie nie znaleziono kopii zapasowej.", + "englishSource": "No backup found on this account." + }, + "cloudPasskeyRestoreHint": { + "message": "Ta kopia zapasowa została zaszyfrowana za pomocą klucza. Uwierzytelnij, aby przywrócić.", + "englishSource": "This backup was encrypted with a passkey. Authenticate to restore." + }, + "cloudRestoreWithPasskey": { + "message": "Przywróć za pomocą klucza dostępu", + "englishSource": "Restore with Passkey" + }, + "signingIn": { + "message": "Logowanie...", + "englishSource": "Signing in..." + }, "encryptedWalletFileDescription": { "message": "Wprowadź hasło, aby uzyskać dostęp do zaszyfrowanego pliku portfela.", "englishSource": "Enter your password to access your Encrypted Wallet File." diff --git a/public/_locales/pt/messages.json b/public/_locales/pt/messages.json index 3f3ff9166..04cc9b7f2 100644 --- a/public/_locales/pt/messages.json +++ b/public/_locales/pt/messages.json @@ -1560,6 +1560,63 @@ "message": "Arquivo de carteira criptografado", "englishSource": "Encrypted Wallet File" }, + "cloudBackup": { + "message": "Backup na nuvem", + "englishSource": "Cloud Backup" + }, + "importWithCloudBackup": { + "message": "Importar do backup na nuvem", + "englishSource": "Import from Cloud Backup" + }, + "importWithCloudBackupDescription": { + "message": "Restaure os dados da sua carteira do Google Drive. Você ainda precisará do seu seed phrase para concluir a importação.", + "englishSource": "Restore your wallet data from Google Drive. You will still need your seed phrase to complete the import." + }, + "importFromCloudBackup": { + "message": "Importar da nuvem", + "englishSource": "Import from Cloud" + }, + "cloudSignIn": { + "message": "Faça login com o Google", + "englishSource": "Sign in with Google" + }, + "cloudSignedIn": { + "message": "Conectado como $email$ ", + "englishSource": "Signed in as $email$", + "placeholders": { + "email": { + "content": "$1" + } + } + }, + "backupPassword": { + "message": "Senha de backup", + "englishSource": "Backup Password" + }, + "cloudRestoring": { + "message": "Restaurando backup...", + "englishSource": "Restoring backup..." + }, + "cloudProbing": { + "message": "Verificando backup...", + "englishSource": "Checking backup..." + }, + "cloudNoBackupFound": { + "message": "Nenhum backup encontrado nesta conta.", + "englishSource": "No backup found on this account." + }, + "cloudPasskeyRestoreHint": { + "message": "Este backup foi criptografado com uma chave de acesso. Autentique para restaurar.", + "englishSource": "This backup was encrypted with a passkey. Authenticate to restore." + }, + "cloudRestoreWithPasskey": { + "message": "Restaurar com senha", + "englishSource": "Restore with Passkey" + }, + "signingIn": { + "message": "Fazendo login...", + "englishSource": "Signing in..." + }, "encryptedWalletFileDescription": { "message": "Digite sua senha para acessar seu arquivo criptografado da carteira.", "englishSource": "Enter your password to access your Encrypted Wallet File." diff --git a/public/_locales/ru/messages.json b/public/_locales/ru/messages.json index f31515e9d..c5affa7c7 100644 --- a/public/_locales/ru/messages.json +++ b/public/_locales/ru/messages.json @@ -1563,6 +1563,63 @@ "message": "Зашифрованный файл кошелька", "englishSource": "Encrypted Wallet File" }, + "cloudBackup": { + "message": "Облачное резервное копирование", + "englishSource": "Cloud Backup" + }, + "importWithCloudBackup": { + "message": "Импорт из облачного резервного копирования", + "englishSource": "Import from Cloud Backup" + }, + "importWithCloudBackupDescription": { + "message": "Восстановите данные своего кошелька с Google Диска. Для завершения импорта вам все равно понадобится ваш seed phrase.", + "englishSource": "Restore your wallet data from Google Drive. You will still need your seed phrase to complete the import." + }, + "importFromCloudBackup": { + "message": "Импорт из облака", + "englishSource": "Import from Cloud" + }, + "cloudSignIn": { + "message": "Войти через Google", + "englishSource": "Sign in with Google" + }, + "cloudSignedIn": { + "message": "Вошёл как $email$ ", + "englishSource": "Signed in as $email$", + "placeholders": { + "email": { + "content": "$1" + } + } + }, + "backupPassword": { + "message": "Резервный пароль", + "englishSource": "Backup Password" + }, + "cloudRestoring": { + "message": "Восстановление резервной копии...", + "englishSource": "Restoring backup..." + }, + "cloudProbing": { + "message": "Проверяем резервную копию...", + "englishSource": "Checking backup..." + }, + "cloudNoBackupFound": { + "message": "В этом аккаунте не найдено резервных копий.", + "englishSource": "No backup found on this account." + }, + "cloudPasskeyRestoreHint": { + "message": "Эта резервная копия была зашифрована с помощью пароля. Авторизуйтесь для восстановления.", + "englishSource": "This backup was encrypted with a passkey. Authenticate to restore." + }, + "cloudRestoreWithPasskey": { + "message": "Восстановить с помощью пароля", + "englishSource": "Restore with Passkey" + }, + "signingIn": { + "message": "Вход в систему...", + "englishSource": "Signing in..." + }, "encryptedWalletFileDescription": { "message": "Введите свой пароль для доступа к вашему зашифрованному файлу кошелька.", "englishSource": "Enter your password to access your Encrypted Wallet File." diff --git a/public/_locales/tr/messages.json b/public/_locales/tr/messages.json index a136b393b..164e8e613 100644 --- a/public/_locales/tr/messages.json +++ b/public/_locales/tr/messages.json @@ -1562,6 +1562,63 @@ "message": "Şifreli Cüzdan Dosyası", "englishSource": "Encrypted Wallet File" }, + "cloudBackup": { + "message": "Bulut Yedekleme", + "englishSource": "Cloud Backup" + }, + "importWithCloudBackup": { + "message": "Bulut Yedekleme'den içe aktar", + "englishSource": "Import from Cloud Backup" + }, + "importWithCloudBackupDescription": { + "message": "Cüzdan verilerinizi Google Drive'dan geri yükleyin. İçe aktarma işlemini tamamlamak için yine de seed phrase numaranıza ihtiyacınız olacak.", + "englishSource": "Restore your wallet data from Google Drive. You will still need your seed phrase to complete the import." + }, + "importFromCloudBackup": { + "message": "Buluttan İçe Aktar", + "englishSource": "Import from Cloud" + }, + "cloudSignIn": { + "message": "Google ile oturum açın", + "englishSource": "Sign in with Google" + }, + "cloudSignedIn": { + "message": "Şu şekilde oturum açıldı: $email$ ", + "englishSource": "Signed in as $email$", + "placeholders": { + "email": { + "content": "$1" + } + } + }, + "backupPassword": { + "message": "Yedekleme Şifresi", + "englishSource": "Backup Password" + }, + "cloudRestoring": { + "message": "Yedekleme geri yükleniyor...", + "englishSource": "Restoring backup..." + }, + "cloudProbing": { + "message": "Yedekleme kontrol ediliyor...", + "englishSource": "Checking backup..." + }, + "cloudNoBackupFound": { + "message": "Bu hesapta yedek bulunamadı.", + "englishSource": "No backup found on this account." + }, + "cloudPasskeyRestoreHint": { + "message": "Bu yedekleme bir geçiş anahtarıyla şifrelendi. Geri yüklemek için kimlik doğrulaması yapın.", + "englishSource": "This backup was encrypted with a passkey. Authenticate to restore." + }, + "cloudRestoreWithPasskey": { + "message": "Şifre Anahtarı ile Geri Yükle", + "englishSource": "Restore with Passkey" + }, + "signingIn": { + "message": "Oturum açılıyor...", + "englishSource": "Signing in..." + }, "encryptedWalletFileDescription": { "message": "Şifreli Cüzdan Dosyanıza erişmek için şifrenizi girin.", "englishSource": "Enter your password to access your Encrypted Wallet File." diff --git a/public/_locales/uk/messages.json b/public/_locales/uk/messages.json index a199eb2f4..cc72f0709 100644 --- a/public/_locales/uk/messages.json +++ b/public/_locales/uk/messages.json @@ -1563,6 +1563,63 @@ "message": "Зашифрований файл гаманця", "englishSource": "Encrypted Wallet File" }, + "cloudBackup": { + "message": "Хмарне резервне копіювання", + "englishSource": "Cloud Backup" + }, + "importWithCloudBackup": { + "message": "Імпорт із Cloud Backup", + "englishSource": "Import from Cloud Backup" + }, + "importWithCloudBackupDescription": { + "message": "Відновіть дані свого гаманця з Google Drive. Для завершення імпорту вам знадобиться ваш seed phrase.", + "englishSource": "Restore your wallet data from Google Drive. You will still need your seed phrase to complete the import." + }, + "importFromCloudBackup": { + "message": "Імпорт із хмари", + "englishSource": "Import from Cloud" + }, + "cloudSignIn": { + "message": "Увійдіть за допомогою Google", + "englishSource": "Sign in with Google" + }, + "cloudSignedIn": { + "message": "Ви ввійшли як $email$ ", + "englishSource": "Signed in as $email$", + "placeholders": { + "email": { + "content": "$1" + } + } + }, + "backupPassword": { + "message": "Резервний пароль", + "englishSource": "Backup Password" + }, + "cloudRestoring": { + "message": "Відновлення резервної копії...", + "englishSource": "Restoring backup..." + }, + "cloudProbing": { + "message": "Перевірка резервної копії...", + "englishSource": "Checking backup..." + }, + "cloudNoBackupFound": { + "message": "Для цього облікового запису не знайдено резервної копії.", + "englishSource": "No backup found on this account." + }, + "cloudPasskeyRestoreHint": { + "message": "Ця резервна копія була зашифрована за допомогою ключа доступу. Щоб відновити, пройдіть автентифікацію.", + "englishSource": "This backup was encrypted with a passkey. Authenticate to restore." + }, + "cloudRestoreWithPasskey": { + "message": "Відновити за допомогою пароля", + "englishSource": "Restore with Passkey" + }, + "signingIn": { + "message": "Вхід...", + "englishSource": "Signing in..." + }, "encryptedWalletFileDescription": { "message": "Введіть свій пароль, щоб отримати доступ до зашифрованого файлу гаманця.", "englishSource": "Enter your password to access your Encrypted Wallet File." diff --git a/public/_locales/zh_CN/messages.json b/public/_locales/zh_CN/messages.json index 32e97175b..2090f6157 100644 --- a/public/_locales/zh_CN/messages.json +++ b/public/_locales/zh_CN/messages.json @@ -1562,6 +1562,63 @@ "message": "加密钱包文件", "englishSource": "Encrypted Wallet File" }, + "cloudBackup": { + "message": "云备份", + "englishSource": "Cloud Backup" + }, + "importWithCloudBackup": { + "message": "从云备份导入", + "englishSource": "Import from Cloud Backup" + }, + "importWithCloudBackupDescription": { + "message": "从 Google 云端硬盘恢复您的钱包数据。您仍然需要 seed phrase 来完成导入。", + "englishSource": "Restore your wallet data from Google Drive. You will still need your seed phrase to complete the import." + }, + "importFromCloudBackup": { + "message": "从云端导入", + "englishSource": "Import from Cloud" + }, + "cloudSignIn": { + "message": "使用 Google 登录", + "englishSource": "Sign in with Google" + }, + "cloudSignedIn": { + "message": "登录身份 $email$ ", + "englishSource": "Signed in as $email$", + "placeholders": { + "email": { + "content": "$1" + } + } + }, + "backupPassword": { + "message": "备份密码", + "englishSource": "Backup Password" + }, + "cloudRestoring": { + "message": "正在恢复备份...", + "englishSource": "Restoring backup..." + }, + "cloudProbing": { + "message": "正在检查备份...", + "englishSource": "Checking backup..." + }, + "cloudNoBackupFound": { + "message": "在此帐户上找不到备份。", + "englishSource": "No backup found on this account." + }, + "cloudPasskeyRestoreHint": { + "message": "该备份已使用密钥加密。进行身份验证即可恢复。", + "englishSource": "This backup was encrypted with a passkey. Authenticate to restore." + }, + "cloudRestoreWithPasskey": { + "message": "使用密钥恢复", + "englishSource": "Restore with Passkey" + }, + "signingIn": { + "message": "正在登录...", + "englishSource": "Signing in..." + }, "encryptedWalletFileDescription": { "message": "输入您的密码以访问您的加密钱包文件。", "englishSource": "Enter your password to access your Encrypted Wallet File." diff --git a/public/_locales/zh_TW/messages.json b/public/_locales/zh_TW/messages.json index 61193d11e..b0bd5f9c1 100644 --- a/public/_locales/zh_TW/messages.json +++ b/public/_locales/zh_TW/messages.json @@ -1562,6 +1562,63 @@ "message": "加密钱包文件", "englishSource": "Encrypted Wallet File" }, + "cloudBackup": { + "message": "云备份", + "englishSource": "Cloud Backup" + }, + "importWithCloudBackup": { + "message": "从云备份导入", + "englishSource": "Import from Cloud Backup" + }, + "importWithCloudBackupDescription": { + "message": "从 Google 云端硬盘恢复您的钱包数据。您仍然需要 seed phrase 来完成导入。", + "englishSource": "Restore your wallet data from Google Drive. You will still need your seed phrase to complete the import." + }, + "importFromCloudBackup": { + "message": "从云端导入", + "englishSource": "Import from Cloud" + }, + "cloudSignIn": { + "message": "使用 Google 登录", + "englishSource": "Sign in with Google" + }, + "cloudSignedIn": { + "message": "登录身份 $email$ ", + "englishSource": "Signed in as $email$", + "placeholders": { + "email": { + "content": "$1" + } + } + }, + "backupPassword": { + "message": "备份密码", + "englishSource": "Backup Password" + }, + "cloudRestoring": { + "message": "正在恢复备份...", + "englishSource": "Restoring backup..." + }, + "cloudProbing": { + "message": "正在检查备份...", + "englishSource": "Checking backup..." + }, + "cloudNoBackupFound": { + "message": "在此帐户上找不到备份。", + "englishSource": "No backup found on this account." + }, + "cloudPasskeyRestoreHint": { + "message": "该备份已使用密钥加密。进行身份验证即可恢复。", + "englishSource": "This backup was encrypted with a passkey. Authenticate to restore." + }, + "cloudRestoreWithPasskey": { + "message": "使用密钥恢复", + "englishSource": "Restore with Passkey" + }, + "signingIn": { + "message": "正在登录...", + "englishSource": "Signing in..." + }, "encryptedWalletFileDescription": { "message": "输入您的密码以访问您的加密钱包文件。", "englishSource": "Enter your password to access your Encrypted Wallet File." diff --git a/public/manifest.json b/public/manifest.json index 2aef48d41..c73050b23 100644 --- a/public/manifest.json +++ b/public/manifest.json @@ -13,13 +13,32 @@ "__chrome|firefox|opera__homepage_url": "https://miden.fi", "short_name": "Miden Wallet", - "permissions": ["storage", "clipboardWrite", "unlimitedStorage", "activeTab", "tabs", "notifications", "alarms", "sidePanel"], + "permissions": [ + "storage", + "clipboardWrite", + "unlimitedStorage", + "activeTab", + "tabs", + "notifications", + "alarms", + "sidePanel", + "identity" + ], "host_permissions": [ "http://localhost/*", "https://*.miden.fi/*", - "https://*.miden.io/*" + "https://*.miden.io/*", + "https://accounts.google.com/*", + "https://www.googleapis.com/*" ], + "oauth2": { + "client_id": "849882985138-egh1ol1n5clvlvb2th2f6njdrg7v8de9.apps.googleusercontent.com", + "scopes": ["https://www.googleapis.com/auth/drive.appdata"] + }, + + "key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAygLPRoPk3J/x6c2JU9H2yIcshyuMsWuKep6BSblZlVXm5E8jPZ5eSVdNooTZi2gk4vaN9Zo70fR7ArTPIqdjEkC/yFcgcpGdJD7Dh9SNuFfx/5xzowmRbKDZVRx7kK7E9BJ4LKMWEew3P94uSwBXIFndUROwN9G3voFN2d5no2F6oNKOddnmeRx0tjoCIEyCdEcdf0fEmz5JjhpTHe2X6sBOpkEBf9XwqmUmlcytHmAHsbJv5pKQeyb430sJFKJiOkX+q1125iAKaYmL0bCMRftH0+5Ho02rvRAZtZPl5dJvsYzbsIGUQzse9N6lNYvryiIB1dE1K8RnVI4fjH8wtwIDAQAB", + "content_security_policy": { "extension_pages": "script-src 'self' 'wasm-unsafe-eval'; object-src 'self'" }, diff --git a/public/manifest.v2.json b/public/manifest.v2.json index 47afe5dae..2fa8343d9 100644 --- a/public/manifest.v2.json +++ b/public/manifest.v2.json @@ -19,10 +19,16 @@ "unlimitedStorage", "clipboardWrite", "activeTab", + "identity", "http://localhost:4180/", "http://localhost:3000/", - "https://*.miden.fi/*" + "https://*.miden.fi/*", + "https://accounts.google.com/*", + "https://oauth2.googleapis.com/*", + "https://www.googleapis.com/*" ], + "key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAygLPRoPk3J/x6c2JU9H2yIcshyuMsWuKep6BSblZlVXm5E8jPZ5eSVdNooTZi2gk4vaN9Zo70fR7ArTPIqdjEkC/yFcgcpGdJD7Dh9SNuFfx/5xzowmRbKDZVRx7kK7E9BJ4LKMWEew3P94uSwBXIFndUROwN9G3voFN2d5no2F6oNKOddnmeRx0tjoCIEyCdEcdf0fEmz5JjhpTHe2X6sBOpkEBf9XwqmUmlcytHmAHsbJv5pKQeyb430sJFKJiOkX+q1125iAKaYmL0bCMRftH0+5Ho02rvRAZtZPl5dJvsYzbsIGUQzse9N6lNYvryiIB1dE1K8RnVI4fjH8wtwIDAQAB", + "content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'", "__chrome|firefox__author": "Miden", diff --git a/src/app/pages/ForgotPassword/ForgotPassword.tsx b/src/app/pages/ForgotPassword/ForgotPassword.tsx index d2a8823bf..f24ec1e49 100644 --- a/src/app/pages/ForgotPassword/ForgotPassword.tsx +++ b/src/app/pages/ForgotPassword/ForgotPassword.tsx @@ -4,37 +4,55 @@ import { generateMnemonic } from 'bip39'; import wordsList from 'bip39/src/wordlists/english.json'; import { formatMnemonic } from 'app/defaults'; +import { persistGoogleRefreshToken } from 'lib/miden/backup/google-drive-auth'; import { useMidenContext } from 'lib/miden/front'; import { clearClientStorage } from 'lib/miden/reset'; import { useMobileBackHandler } from 'lib/mobile/useMobileBackHandler'; -import type { WalletAccount } from 'lib/shared/types'; +import type { CloudBackupCredentials, WalletAccount } from 'lib/shared/types'; import { navigate } from 'lib/woozie'; import { OnboardingFlow } from 'screens/onboarding/navigator'; -import { OnboardingAction, OnboardingStep, OnboardingType } from 'screens/onboarding/types'; +import { ImportType, OnboardingAction, OnboardingStep, OnboardingType } from 'screens/onboarding/types'; const ForgotPassword: FC = () => { const [step, setStep] = useState(OnboardingStep.Welcome); const [seedPhrase, setSeedPhrase] = useState([]); const [onboardingType, setOnboardingType] = useState(null); const [password, setPassword] = useState(null); + const [importType, setImportType] = useState(null); const [importedWithFile, setImportedWithFile] = useState(false); const [isLoading, setIsLoading] = useState(false); const [importedWalletAccounts, setImportedWalletAccounts] = useState([]); - const { registerWallet, importWalletFromClient } = useMidenContext(); + const { registerWallet, importWalletFromClient, registerFromCloudBackup, setAutoBackupEnabled } = useMidenContext(); + const [cloudBackupData, setCloudBackupData] = useState(null); const register = useCallback(async () => { if (password && seedPhrase) { clearClientStorage(); const seedPhraseFormatted = formatMnemonic(seedPhrase.join(' ')); - if (!importedWithFile) { + if (cloudBackupData) { try { - await registerWallet( + await registerFromCloudBackup( password, seedPhraseFormatted, - onboardingType === OnboardingType.Import // might be able to leverage ownMnemonic to determine whther to attempt imports in general + cloudBackupData.walletAccounts, + cloudBackupData.walletSettings ); + await persistGoogleRefreshToken(cloudBackupData.refreshToken); + await setAutoBackupEnabled( + true, + cloudBackupData.accessToken, + cloudBackupData.expiresAt, + cloudBackupData.encryption, + true + ); + } catch (e) { + console.error(e); + } + } else if (!importedWithFile) { + try { + await registerWallet(password, seedPhraseFormatted, onboardingType === OnboardingType.Import); } catch (e) { console.error(e); } @@ -50,7 +68,10 @@ const ForgotPassword: FC = () => { password, seedPhrase, importedWithFile, + cloudBackupData, registerWallet, + registerFromCloudBackup, + setAutoBackupEnabled, onboardingType, importWalletFromClient, importedWalletAccounts @@ -71,6 +92,14 @@ const ForgotPassword: FC = () => { case 'import-from-file': setStep(OnboardingStep.ImportFromFile); break; + case 'import-from-cloud': + setImportType(ImportType.CloudBackup); + setStep(OnboardingStep.ImportFromCloud); + break; + case 'import-from-cloud-submit': + setCloudBackupData(action.payload); + setStep(OnboardingStep.ImportFromSeed); + break; case 'import-wallet-file-submit': const seedPhrase = action.payload.split(' '); setSeedPhrase(seedPhrase); @@ -118,15 +147,21 @@ const ForgotPassword: FC = () => { } else { setStep(OnboardingStep.ImportFromSeed); } - } else if (step === OnboardingStep.ImportFromFile || step === OnboardingStep.ImportFromSeed) { + } else if (step === OnboardingStep.ImportFromCloud) { setStep(OnboardingStep.SelectImportType); + } else if (step === OnboardingStep.ImportFromFile || step === OnboardingStep.ImportFromSeed) { + if (importType === ImportType.CloudBackup) { + setStep(OnboardingStep.ImportFromCloud); + } else { + setStep(OnboardingStep.SelectImportType); + } } break; default: break; } }, - [register, step, onboardingType] + [register, step, onboardingType, importType] ); // Handle mobile back button/gesture in forgot password flow diff --git a/src/app/pages/Settings.tsx b/src/app/pages/Settings.tsx index 46ee0371c..8b52c6058 100644 --- a/src/app/pages/Settings.tsx +++ b/src/app/pages/Settings.tsx @@ -42,6 +42,7 @@ import { Drawer, DrawerContent, DrawerHeader, DrawerTitle } from 'lib/ui/drawer' import { goBack, navigate } from 'lib/woozie'; import { EncryptedFileFlow } from 'screens/encrypted-file-flow/EncryptedFileManager'; +import CloudBackupSettings from '../templates/CloudBackupSettings'; import AdvancedSettings from './AdvancedSettings'; import NetworksSettings from './Networks'; import { SettingsSelectors } from './Settings.selectors'; @@ -147,6 +148,13 @@ const TAB_GROUPS: TabGroup[] = [ Component: EncryptedFileFlow, testID: SettingsSelectors.EncryptedWalletFile, hasOwnLayout: true + }, + { + slug: 'cloud-backup', + titleI18nKey: 'cloudBackup', + Icon: EncryptedWalletIcon, + Component: CloudBackupSettings, + isDrawer: true } ] }, diff --git a/src/app/pages/Welcome.tsx b/src/app/pages/Welcome.tsx index 31a66a2b6..5ffacf236 100644 --- a/src/app/pages/Welcome.tsx +++ b/src/app/pages/Welcome.tsx @@ -5,10 +5,11 @@ import wordslist from 'bip39/src/wordlists/english.json'; import { formatMnemonic } from 'app/defaults'; import { AnalyticsEventCategory, useAnalytics } from 'lib/analytics'; +import { persistGoogleRefreshToken } from 'lib/miden/backup/google-drive-auth'; import { useMidenContext } from 'lib/miden/front'; import { useMobileBackHandler } from 'lib/mobile/useMobileBackHandler'; import { isDesktop, isMobile } from 'lib/platform'; -import { WalletStatus, WalletAccount } from 'lib/shared/types'; +import { CloudBackupCredentials, WalletAccount, WalletStatus } from 'lib/shared/types'; import { useWalletStore } from 'lib/store'; import { fetchStateFromBackend } from 'lib/store/hooks/useIntercomSync'; import { navigate, useLocation } from 'lib/woozie'; @@ -78,7 +79,8 @@ const Welcome: FC = () => { const [biometricAttempts, setBiometricAttempts] = useState(0); const [biometricError, setBiometricError] = useState(null); const [importedWalletAccounts, setImportedWalletAccounts] = useState([]); - const { registerWallet, importWalletFromClient } = useMidenContext(); + const { registerWallet, importWalletFromClient, registerFromCloudBackup, setAutoBackupEnabled } = useMidenContext(); + const [cloudBackupData, setCloudBackupData] = useState(null); const { trackEvent } = useAnalytics(); const syncFromBackend = useWalletStore(s => s.syncFromBackend); @@ -123,7 +125,23 @@ const Welcome: FC = () => { const seedPhraseFormatted = formatMnemonic(seedPhrase.join(' ')); // For hardware-only wallets, pass undefined as password const actualPassword = password === '__HARDWARE_ONLY__' ? undefined : password; - if (!importedWithFile) { + if (cloudBackupData) { + await registerFromCloudBackup( + actualPassword, + seedPhraseFormatted, + cloudBackupData.walletAccounts, + cloudBackupData.walletSettings + ); + // clearStorage wiped the refresh token — re-persist it, then enable auto-backup + await persistGoogleRefreshToken(cloudBackupData.refreshToken); + await setAutoBackupEnabled( + true, + cloudBackupData.accessToken, + cloudBackupData.expiresAt, + cloudBackupData.encryption, + true + ); + } else if (!importedWithFile) { await registerWallet(actualPassword, seedPhraseFormatted, onboardingType === OnboardingType.Import); } else { try { @@ -140,7 +158,10 @@ const Welcome: FC = () => { password, seedPhrase, importedWithFile, + cloudBackupData, registerWallet, + registerFromCloudBackup, + setAutoBackupEnabled, onboardingType, importWalletFromClient, importedWalletAccounts @@ -181,6 +202,14 @@ const Welcome: FC = () => { } } break; + case 'import-from-cloud': + setImportType(ImportType.CloudBackup); + navigate('/#import-from-cloud'); + break; + case 'import-from-cloud-submit': + setCloudBackupData(action.payload); + navigate('/#import-from-seed'); + break; case 'import-from-seed': setImportType(ImportType.SeedPhrase); navigate('/#import-from-seed'); @@ -275,8 +304,14 @@ const Welcome: FC = () => { navigate('/#import-from-seed'); } } - } else if (step === OnboardingStep.ImportFromFile || step === OnboardingStep.ImportFromSeed) { + } else if (step === OnboardingStep.ImportFromCloud) { navigate('/#select-import-type'); + } else if (step === OnboardingStep.ImportFromFile || step === OnboardingStep.ImportFromSeed) { + if (importType === ImportType.CloudBackup) { + navigate('/#import-from-cloud'); + } else { + navigate('/#select-import-type'); + } } break; default: @@ -305,6 +340,9 @@ const Welcome: FC = () => { case '#import-from-file': setStep(OnboardingStep.ImportFromFile); break; + case '#import-from-cloud': + setStep(OnboardingStep.ImportFromCloud); + break; case '#backup-seed-phrase': setOnboardingType(OnboardingType.Create); setStep(OnboardingStep.BackupSeedPhrase); diff --git a/src/app/templates/CloudBackupSettings.tsx b/src/app/templates/CloudBackupSettings.tsx new file mode 100644 index 000000000..f83e57bca --- /dev/null +++ b/src/app/templates/CloudBackupSettings.tsx @@ -0,0 +1,252 @@ +import React, { FC, useCallback, useEffect, useState } from 'react'; + +import { Button, ButtonVariant } from 'components/Button'; +import { + consumePendingExtensionAuth, + getGoogleAuthToken, + GoogleAuthResult, + trySilentGoogleAuth +} from 'lib/miden/backup/google-drive-auth'; +import { useMidenContext } from 'lib/miden/front'; +import { generateSalt } from 'lib/miden/passworder'; +import { getPasskeyProvider } from 'lib/passkey'; +import { u8ToB64 } from 'lib/shared/helpers'; +import { AutoBackupStatus } from 'lib/shared/types'; + +type EncryptionMethod = 'password' | 'passkey'; + +const CloudBackupSettings: FC<{ onClose?: () => void }> = () => { + const { setAutoBackupEnabled, fetchAutoBackupStatus, restoreFromAutoBackup } = useMidenContext(); + + const [auth, setAuth] = useState(null); + const [backupPassword, setBackupPassword] = useState(''); + const [encryptionMethod, setEncryptionMethod] = useState('password'); + const [status, setStatus] = useState(''); + const [loading, setLoading] = useState(false); + const [autoBackupStatus, setAutoBackupStatus] = useState(null); + + // Fetch auto-backup status and try silent Google auth on mount. + // If we just landed here in the side panel after the popup deferred OAuth + // (popup closes when OAuth window takes focus), resume the interactive flow. + useEffect(() => { + fetchAutoBackupStatus() + .then(setAutoBackupStatus) + .catch(() => {}); + consumePendingExtensionAuth() + .then(deferred => { + if (deferred) { + setAuth(deferred); + setStatus('Signed in with Google'); + return; + } + return trySilentGoogleAuth().then(silent => { + if (silent) setAuth(silent); + }); + }) + .catch(err => setStatus(`Sign-in failed: ${err instanceof Error ? err.message : String(err)}`)); + }, [fetchAutoBackupStatus]); + + const handleSignIn = useCallback(async () => { + setLoading(true); + setStatus('Signing in...'); + try { + const result = await getGoogleAuthToken(); + setAuth(result); + setStatus('Signed in with Google'); + } catch (err: unknown) { + setStatus(`Sign-in failed: ${err instanceof Error ? err.message : String(err)}`); + } finally { + setLoading(false); + } + }, []); + + const handleEnable = useCallback(async () => { + if (!auth) return; + setLoading(true); + setStatus('Enabling auto-backup...'); + try { + if (encryptionMethod === 'passkey') { + const provider = await getPasskeyProvider(); + if (!provider) throw new Error('Passkeys not available on this platform'); + + const salt = generateSalt(); + const { keyMaterial, credentialId, prfSalt } = await provider.register(salt); + + await setAutoBackupEnabled(true, auth.accessToken, auth.expiresAt, { + method: 'passkey', + keyMaterial: u8ToB64(keyMaterial), + credentialId: u8ToB64(credentialId), + prfSalt: u8ToB64(prfSalt) + }); + } else { + if (!backupPassword) return; + await setAutoBackupEnabled(true, auth.accessToken, auth.expiresAt, { + method: 'password', + backupPassword + }); + } + setBackupPassword(''); + setStatus('Auto-backup enabled'); + const updated = await fetchAutoBackupStatus(); + setAutoBackupStatus(updated); + } catch (err: unknown) { + setStatus(`Failed: ${err instanceof Error ? err.message : String(err)}`); + } finally { + setLoading(false); + } + }, [auth, backupPassword, encryptionMethod, setAutoBackupEnabled, fetchAutoBackupStatus]); + + const handleDisable = useCallback(async () => { + setLoading(true); + setStatus('Disabling auto-backup...'); + try { + await setAutoBackupEnabled(false); + setStatus('Auto-backup disabled'); + const updated = await fetchAutoBackupStatus(); + setAutoBackupStatus(updated); + } catch (err: unknown) { + setStatus(`Failed: ${err instanceof Error ? err.message : String(err)}`); + } finally { + setLoading(false); + } + }, [setAutoBackupEnabled, fetchAutoBackupStatus]); + + const handleRestore = useCallback(async () => { + setLoading(true); + setStatus('Restoring from backup...'); + try { + await restoreFromAutoBackup(); + setStatus('Restored from backup'); + const updated = await fetchAutoBackupStatus(); + setAutoBackupStatus(updated); + } catch (err: unknown) { + setStatus(`Restore failed: ${err instanceof Error ? err.message : String(err)}`); + } finally { + setLoading(false); + } + }, [restoreFromAutoBackup, fetchAutoBackupStatus]); + + const handleGoogleReauth = useCallback(async () => { + setLoading(true); + setStatus('Re-authenticating with Google...'); + try { + const result = await getGoogleAuthToken(); + setAuth(result); + // Re-enable with fresh token (encryption unchanged, just refreshing token) + setStatus('Re-authenticated with Google'); + } catch (err: unknown) { + setStatus(`Re-auth failed: ${err instanceof Error ? err.message : String(err)}`); + } finally { + setLoading(false); + } + }, []); + + const isEnabled = autoBackupStatus?.enabled ?? false; + const enableDisabled = loading || !auth || (encryptionMethod === 'password' && !backupPassword); + + return ( +
+

Cloud Backup

+ + {/* Status */} + {isEnabled && autoBackupStatus && ( +
+

Auto-backup enabled ({autoBackupStatus.method})

+ {autoBackupStatus.lastBackupAt && ( +

Last backup: {new Date(autoBackupStatus.lastBackupAt).toLocaleString()}

+ )} + {autoBackupStatus.lastError &&

Error: {autoBackupStatus.lastError}

} + {autoBackupStatus.needsGoogleReauth && ( +
+ )} + + {/* Auth */} + {!isEnabled && ( + <> + + +
+ + {/* Password input (only for password method) */} + {encryptionMethod === 'password' && ( + setBackupPassword(e.target.value)} + className="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm" + /> + )} + + {/* Enable */} + + {signInError &&

{signInError}

} + + ) : probing ? ( +

{t('cloudProbing')}

+ ) : noBackup ? ( +

{t('cloudNoBackupFound')}

+ ) : isPasskeyBackup ? ( +
+
{t('cloudSignedIn', { email: 'Google' })}
+

{t('cloudPasskeyRestoreHint')}

+ {restoreError &&

{restoreError}

} +
+ +
+
+ ) : isPasswordBackup ? ( +
+
{t('cloudSignedIn', { email: 'Google' })}
+ +
+ + {isSubmitting ? t('cloudRestoring') : t('import')} + +
+ + ) : null} + + ); +}; diff --git a/src/screens/onboarding/import-wallet-flow/SelectImportType.tsx b/src/screens/onboarding/import-wallet-flow/SelectImportType.tsx index 3f2d3c1ba..f4ee21a76 100644 --- a/src/screens/onboarding/import-wallet-flow/SelectImportType.tsx +++ b/src/screens/onboarding/import-wallet-flow/SelectImportType.tsx @@ -30,7 +30,12 @@ export const SelectImportTypeScreen = ({ onSubmit }: SelectImportTypeScreenProps { id: ImportType.WalletFile, title: t('importWithEncryptedWalletFile'), - description: t('importWithEncryptedWalletFileDescription'), + description: t('importWithEncryptedWalletFileDescription') + }, + { + id: ImportType.CloudBackup, + title: t('importWithCloudBackup'), + description: t('importWithCloudBackupDescription'), isLast: true } ], diff --git a/src/screens/onboarding/navigator.tsx b/src/screens/onboarding/navigator.tsx index 02306d4bb..c7c06921d 100644 --- a/src/screens/onboarding/navigator.tsx +++ b/src/screens/onboarding/navigator.tsx @@ -7,7 +7,7 @@ import { useTranslation } from 'react-i18next'; import { Button, ButtonVariant } from 'components/Button'; import { ProgressIndicator } from 'components/ProgressIndicator'; import { isMobile } from 'lib/platform'; -import type { WalletAccount } from 'lib/shared/types'; +import type { CloudBackupCredentials, WalletAccount } from 'lib/shared/types'; import { ConfirmationScreen } from './common/Confirmation'; import { CreatePasswordScreen } from './common/CreatePassword'; @@ -15,6 +15,7 @@ import { WelcomeScreen } from './common/Welcome'; import { BackUpSeedPhraseScreen } from './create-wallet-flow/BackUpSeedPhrase'; import { SelectTransactionTypeScreen } from './create-wallet-flow/SelectTransactionType'; import { VerifySeedPhraseScreen } from './create-wallet-flow/VerifySeedPhrase'; +import { ImportFromCloudScreen } from './import-wallet-flow/ImportFromCloud'; import { ImportSeedPhraseScreen } from './import-wallet-flow/ImportSeedPhrase'; import { ImportWalletFileScreen } from './import-wallet-flow/ImportWalletFile'; import { SelectImportTypeScreen } from './import-wallet-flow/SelectImportType'; @@ -50,7 +51,11 @@ const Header: React.FC<{ currentStep = 1; } else if (step === OnboardingStep.CreatePassword) { currentStep = 3; - } else if (step === OnboardingStep.ImportFromSeed || step === OnboardingStep.ImportFromFile) { + } else if ( + step === OnboardingStep.ImportFromSeed || + step === OnboardingStep.ImportFromFile || + step === OnboardingStep.ImportFromCloud + ) { currentStep = 2; } else if (step === OnboardingStep.Confirmation) { currentStep = 4; @@ -119,6 +124,11 @@ export const OnboardingFlow: FC = ({ id: 'import-from-file' }); break; + case ImportType.CloudBackup: + onForwardAction?.({ + id: 'import-from-cloud' + }); + break; default: break; } @@ -152,6 +162,10 @@ export const OnboardingFlow: FC = ({ onForwardAction?.({ id: 'import-wallet-file-submit', payload: seedPhrase, walletAccounts }); }; + const onImportFromCloudSubmit = (payload: CloudBackupCredentials) => { + onForwardAction?.({ id: 'import-from-cloud-submit', payload }); + }; + switch (step) { case OnboardingStep.Welcome: return ; @@ -173,6 +187,8 @@ export const OnboardingFlow: FC = ({ return ; case OnboardingStep.ImportFromFile: return ; + case OnboardingStep.ImportFromCloud: + return ; case OnboardingStep.CreatePassword: return ; case OnboardingStep.SelectTransactionType: diff --git a/src/screens/onboarding/types.ts b/src/screens/onboarding/types.ts index 4a7a26531..737e965d6 100644 --- a/src/screens/onboarding/types.ts +++ b/src/screens/onboarding/types.ts @@ -1,4 +1,4 @@ -import type { WalletAccount } from 'lib/shared/types'; +import type { CloudBackupCredentials, WalletAccount } from 'lib/shared/types'; export enum OnboardingType { Create = 'create', @@ -12,7 +12,8 @@ export enum WalletType { export enum ImportType { SeedPhrase = 'seed-phrase', - WalletFile = 'wallet-file' + WalletFile = 'wallet-file', + CloudBackup = 'cloud-backup' } export enum OnboardingStep { @@ -23,6 +24,7 @@ export enum OnboardingStep { SelectImportType = 'select-import-type', ImportFromSeed = 'import-from-seed', ImportFromFile = 'import-from-file', + ImportFromCloud = 'import-from-cloud', CreatePassword = 'create-password', BiometricSetup = 'biometric-setup', SelectTransactionType = 'select-transaction-type', @@ -41,7 +43,9 @@ export type OnboardingActionId = | 'select-transaction-type' | 'confirmation' | 'import-from-file' - | 'import-from-seed'; + | 'import-from-seed' + | 'import-from-cloud' + | 'import-from-cloud-submit'; export type CreateWalletAction = { id: 'create-wallet'; @@ -59,6 +63,15 @@ export type ImportFromSeedAction = { id: 'import-from-seed'; }; +export type ImportFromCloudAction = { + id: 'import-from-cloud'; +}; + +export type ImportFromCloudSubmitAction = { + id: 'import-from-cloud-submit'; + payload: CloudBackupCredentials; +}; + export type BackupSeedPhraseAction = { id: 'backup-seed-phrase'; }; @@ -124,6 +137,8 @@ export type OnboardingAction = | BackAction | ImportFromFileAction | ImportFromSeedAction + | ImportFromCloudAction + | ImportFromCloudSubmitAction | ImportWalletFileSubmitAction | SwitchToPasswordAction; diff --git a/yarn.lock b/yarn.lock index b1f672482..fd471835c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -13,12 +13,12 @@ integrity sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw== "@appium/base-driver@^10.0.0-rc.1": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@appium/base-driver/-/base-driver-10.3.0.tgz#135e3b01d621a701c95859dc9ccf91cfa04774fe" - integrity sha512-9r+1f9EtcJt9NXIlyHdFMoD7DsAZPzpRq4Kj1hGZf7+26q1SFEyyMAuWxvlWGY7EoyPtPRyMUkM4dHiSK3Q9+w== + version "10.4.0" + resolved "https://registry.yarnpkg.com/@appium/base-driver/-/base-driver-10.4.0.tgz#f24ca19a0fc7bb28582781244474889a348e80c7" + integrity sha512-vt+9zILQw2wECIqzLPbWupaMrNVC9ALttoiARyvL1V7zVwNLi1Zr9MQY0o9+vAXcxiVAf2/aUWeMjyMBYNVBfw== dependencies: - "@appium/support" "7.1.0" - "@appium/types" "1.3.0" + "@appium/support" "7.1.1" + "@appium/types" "1.3.1" "@colors/colors" "1.6.0" async-lock "1.4.1" asyncbox "6.1.0" @@ -29,23 +29,23 @@ fastest-levenshtein "1.0.16" http-status-codes "2.3.0" lodash "4.18.1" - lru-cache "11.3.3" + lru-cache "11.3.5" method-override "3.0.0" morgan "1.10.1" path-to-regexp "8.4.2" serve-favicon "2.5.1" - type-fest "5.5.0" + type-fest "5.6.0" optionalDependencies: spdy "4.0.2" -"@appium/logger@2.0.6", "@appium/logger@^2.0.0-rc.1": - version "2.0.6" - resolved "https://registry.yarnpkg.com/@appium/logger/-/logger-2.0.6.tgz#70645d141b72af8963e31c439cb4c6b3c3dc88fe" - integrity sha512-9e8n9CtINBwi1ASEU5OyswmR2F7OnbrGfmf9yTy9i+rx4GR9RJlEp0/arsxvuyWCep67tOmM4FiRyXxxHjOK5Q== +"@appium/logger@2.0.7", "@appium/logger@^2.0.0-rc.1": + version "2.0.7" + resolved "https://registry.yarnpkg.com/@appium/logger/-/logger-2.0.7.tgz#535288b92c5944fb4780eca752cf30b742e2cdc0" + integrity sha512-WqagwYDZlPsSkICrXL9wB1E7qgErnwmYc/Q6NLVAC2ckXkWioh3fZ49AK5zevbJCnnkQbU2y8497Mk4xWDetkg== dependencies: console-control-strings "1.1.0" lodash "4.18.1" - lru-cache "11.3.3" + lru-cache "11.3.5" set-blocking "2.0.0" "@appium/schema@1.1.0": @@ -56,21 +56,21 @@ json-schema "0.4.0" "@appium/strongbox@^1.0.0-rc.1": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@appium/strongbox/-/strongbox-1.1.0.tgz#9703e9ce6c6c61859bb7bef7db14875d85c5c61c" - integrity sha512-X+Ff/6sGiTXHx2W3s+Gg3XyZMbIZe5v3jFSrwUJ9kH7NuGzpzrzEoKx9EMOw7iXVLMV6/NmuDQPq80gzOcRwjA== + version "1.1.1" + resolved "https://registry.yarnpkg.com/@appium/strongbox/-/strongbox-1.1.1.tgz#bc326ae63fc471c8f995b0b63a0d79f5eb5fd2b7" + integrity sha512-cBpvgD3r8OyLUoZXamY3mVTe3V+Fy6bFQulpyRCLi7DTtt4rwkIHzvn3h+81t0XMVFLxlOiJNXN2SkOFpRQ3eg== dependencies: env-paths "4.0.0" slugify "1.6.9" -"@appium/support@7.1.0", "@appium/support@^7.0.0-rc.1": - version "7.1.0" - resolved "https://registry.yarnpkg.com/@appium/support/-/support-7.1.0.tgz#fed2a704727d4cddbebeede6f563ea76b1e60fd4" - integrity sha512-kY4Qv4TzLCYmZnN2eNptEa8RiRzpbimIQ6tKuDaqLC2Y3q5Al4NumL/xRQAvfXJq/hNezq2Jh8NwciEW8zX/0g== +"@appium/support@7.1.1", "@appium/support@^7.0.0-rc.1": + version "7.1.1" + resolved "https://registry.yarnpkg.com/@appium/support/-/support-7.1.1.tgz#c6064b2da6d00109b4a24f4630ef51fb94a4d1b3" + integrity sha512-WJewvoQxDOaVKjlvjVwifNDMQUVdCy0Edcxobl6I7pJzSE2O15Bi3BEq5AMzDpaXs7DtjZTLkHT29Tin9KHV3Q== dependencies: - "@appium/logger" "2.0.6" + "@appium/logger" "2.0.7" "@appium/tsconfig" "1.1.2" - "@appium/types" "1.3.0" + "@appium/types" "1.3.1" "@colors/colors" "1.6.0" archiver "7.0.1" asyncbox "6.1.0" @@ -97,9 +97,9 @@ semver "7.7.4" shell-quote "1.8.3" supports-color "10.2.2" - teen_process "4.1.0" - type-fest "5.5.0" - uuid "13.0.0" + teen_process "4.1.1" + type-fest "5.6.0" + uuid "14.0.0" which "6.0.1" yauzl "3.3.0" optionalDependencies: @@ -112,15 +112,15 @@ dependencies: "@tsconfig/node20" "20.1.9" -"@appium/types@1.3.0": - version "1.3.0" - resolved "https://registry.yarnpkg.com/@appium/types/-/types-1.3.0.tgz#8a9fd6e6fc4f6fa8df8b896e30bf7da9ccb5b016" - integrity sha512-Gv4ev/5K5N7TvAHqem2DmB50zipC951QlmCDpuxDNHQl2dtCr20vJgnN8if7upqLcBX/6yNp3udR+f1n99zgcQ== +"@appium/types@1.3.1": + version "1.3.1" + resolved "https://registry.yarnpkg.com/@appium/types/-/types-1.3.1.tgz#3e0242b858dd5b1cbdbfdd0d8e6e2a4498f60d78" + integrity sha512-SWTntQ8EAI1m6P2LWqcqASGkRLk5HiKv2V2x7DSfknS8AsiF2ymr6T6oPJ7hUrTkEUA/3ncTSZji5Oq4h5TjzA== dependencies: - "@appium/logger" "2.0.6" + "@appium/logger" "2.0.7" "@appium/schema" "1.1.0" "@appium/tsconfig" "1.1.2" - type-fest "5.5.0" + type-fest "5.6.0" "@asamuzakjp/css-color@^3.2.0": version "3.2.0" @@ -1137,6 +1137,11 @@ resolved "https://registry.yarnpkg.com/@capacitor/app/-/app-8.0.0.tgz#47c2a90302919d71cef8a7297fb1d6ee439d1250" integrity sha512-OwzIkUs4w433Bu9WWAEbEYngXEfJXZ9Wmdb8eoaqzYBgB0W9/3Ed/mh6sAYPNBAZlpyarmewgP7Nb+d3Vrh+xA== +"@capacitor/browser@^8.0.3": + version "8.0.3" + resolved "https://registry.yarnpkg.com/@capacitor/browser/-/browser-8.0.3.tgz#21c3543981c67196c08a74dc622eda96e548700b" + integrity sha512-WJWPHEPbweiFoHYmVlCbZf5yrqJ2Rchx2Xvbmd+3Lf+Zkpq3nXBThThY2CF69lYEg1NINGF9BcHThIOEU1gZlQ== + "@capacitor/cli@^8.0.1": version "8.0.1" resolved "https://registry.yarnpkg.com/@capacitor/cli/-/cli-8.0.1.tgz#34acb86b2920fa6d2ab1abb79e66c061863413fd" @@ -1426,10 +1431,10 @@ seedrandom "^3.0.5" svgson "^4.0.0" -"@emnapi/core@1.9.2", "@emnapi/core@^1.8.1": - version "1.9.2" - resolved "https://registry.yarnpkg.com/@emnapi/core/-/core-1.9.2.tgz#3870265ecffc7352d01ead62d8d83d8358a2d034" - integrity sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA== +"@emnapi/core@1.10.0", "@emnapi/core@^1.8.1": + version "1.10.0" + resolved "https://registry.yarnpkg.com/@emnapi/core/-/core-1.10.0.tgz#380ccc8f2412ea22d1d972df7f8ee23a3b9c7467" + integrity sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw== dependencies: "@emnapi/wasi-threads" "1.2.1" tslib "^2.4.0" @@ -1442,10 +1447,10 @@ "@emnapi/wasi-threads" "1.1.0" tslib "^2.4.0" -"@emnapi/runtime@1.9.2", "@emnapi/runtime@^1.8.1": - version "1.9.2" - resolved "https://registry.yarnpkg.com/@emnapi/runtime/-/runtime-1.9.2.tgz#8b469a3db160817cadb1de9050211a9d1ea84fa2" - integrity sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw== +"@emnapi/runtime@1.10.0", "@emnapi/runtime@^1.8.1": + version "1.10.0" + resolved "https://registry.yarnpkg.com/@emnapi/runtime/-/runtime-1.10.0.tgz#4b260c0d3534204e98c6110b8db1a987d26ec87c" + integrity sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA== dependencies: tslib "^2.4.0" @@ -2303,10 +2308,10 @@ "@emnapi/runtime" "^1.7.1" "@tybys/wasm-util" "^0.10.1" -"@napi-rs/wasm-runtime@^1.1.1", "@napi-rs/wasm-runtime@^1.1.3": - version "1.1.3" - resolved "https://registry.yarnpkg.com/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.3.tgz#1eeb8699770481306e5fcd84471f20fcb6177336" - integrity sha512-xK9sGVbJWYb08+mTJt3/YV24WxvxpXcXtP6B172paPZ+Ts69Re9dAr7lKwJoeIx8OoeuimEiRZ7umkiUVClmmQ== +"@napi-rs/wasm-runtime@^1.1.1", "@napi-rs/wasm-runtime@^1.1.4": + version "1.1.4" + resolved "https://registry.yarnpkg.com/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz#a46bbfedc29751b7170c5d23bc1d8ee8c7e3c1e1" + integrity sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow== dependencies: "@tybys/wasm-util" "^0.10.1" @@ -2349,10 +2354,10 @@ resolved "https://registry.yarnpkg.com/@nolyfill/is-core-module/-/is-core-module-1.0.39.tgz#3dc35ba0f1e66b403c00b39344f870298ebb1c8e" integrity sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA== -"@oxc-project/types@=0.124.0": - version "0.124.0" - resolved "https://registry.yarnpkg.com/@oxc-project/types/-/types-0.124.0.tgz#1dfd7b3fbb98febc2f91b505f48c940db73c8701" - integrity sha512-VBFWMTBvHxS11Z5Lvlr3IWgrwhMTXV+Md+EQF0Xf60+wAdsGFTBx7X7K/hP4pi8N7dcm1RvcHwDxZ16Qx8keUg== +"@oxc-project/types@=0.127.0": + version "0.127.0" + resolved "https://registry.yarnpkg.com/@oxc-project/types/-/types-0.127.0.tgz#8374fcdfb4a641861218daa5700c447c00b66663" + integrity sha512-aIYXQBo4lCbO4z0R3FHeucQHpF46l2LbMdxRvqvuRuW2OxdnSkcng5B8+K12spgLDj93rtN3+J2Vac/TIO+ciQ== "@peculiar/asn1-schema@^2.0.27", "@peculiar/asn1-schema@^2.3.13": version "2.6.0" @@ -2558,89 +2563,89 @@ "@resvg/resvg-js-win32-ia32-msvc" "2.6.2" "@resvg/resvg-js-win32-x64-msvc" "2.6.2" -"@rolldown/binding-android-arm64@1.0.0-rc.15": - version "1.0.0-rc.15" - resolved "https://registry.yarnpkg.com/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.15.tgz#ca20574c469ade7b941f90c9af5e83e7c67f06b7" - integrity sha512-YYe6aWruPZDtHNpwu7+qAHEMbQ/yRl6atqb/AhznLTnD3UY99Q1jE7ihLSahNWkF4EqRPVC4SiR4O0UkLK02tA== - -"@rolldown/binding-darwin-arm64@1.0.0-rc.15": - version "1.0.0-rc.15" - resolved "https://registry.yarnpkg.com/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.15.tgz#ce2c5c7fc4958dfc94783dc09b3d09f3c2e1d072" - integrity sha512-oArR/ig8wNTPYsXL+Mzhs0oxhxfuHRfG7Ikw7jXsw8mYOtk71W0OkF2VEVh699pdmzjPQsTjlD1JIOoHkLP1Fg== - -"@rolldown/binding-darwin-x64@1.0.0-rc.15": - version "1.0.0-rc.15" - resolved "https://registry.yarnpkg.com/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.15.tgz#251ecdf1fdb751031cb6486907c105daaf9dab21" - integrity sha512-YzeVqOqjPYvUbJSWJ4EDL8ahbmsIXQpgL3JVipmN+MX0XnXMeWomLN3Fb+nwCmP/jfyqte5I3XRSm7OfQrbyxw== - -"@rolldown/binding-freebsd-x64@1.0.0-rc.15": - version "1.0.0-rc.15" - resolved "https://registry.yarnpkg.com/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.15.tgz#dbcfe95f409bf671a77bd83bff0fdc877d217728" - integrity sha512-9Erhx956jeQ0nNTyif1+QWAXDRD38ZNjr//bSHrt6wDwB+QkAfl2q6Mn1k6OBPerznjRmbM10lgRb1Pli4xZPw== - -"@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.15": - version "1.0.0-rc.15" - resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.15.tgz#ea002b45445be6f9ed1883a834b335bc2ccd510f" - integrity sha512-cVwk0w8QbZJGTnP/AHQBs5yNwmpgGYStL88t4UIaqcvYJWBfS0s3oqVLZPwsPU6M0zlW4GqjP0Zq5MnAGwFeGA== - -"@rolldown/binding-linux-arm64-gnu@1.0.0-rc.15": - version "1.0.0-rc.15" - resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.15.tgz#12b96e7e7821a9dc2cd5c670ad56882987ed5c62" - integrity sha512-eBZ/u8iAK9SoHGanqe/jrPnY0JvBN6iXbVOsbO38mbz+ZJsaobExAm1Iu+rxa4S1l2FjG0qEZn4Rc6X8n+9M+w== - -"@rolldown/binding-linux-arm64-musl@1.0.0-rc.15": - version "1.0.0-rc.15" - resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.15.tgz#738b0f62f0b65bf676dfe48595017f1883859d1f" - integrity sha512-ZvRYMGrAklV9PEkgt4LQM6MjQX2P58HPAuecwYObY2DhS2t35R0I810bKi0wmaYORt6m/2Sm+Z+nFgb0WhXNcQ== - -"@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.15": - version "1.0.0-rc.15" - resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.15.tgz#3088b9fbc2783033985b558316f87f39281bc533" - integrity sha512-VDpgGBzgfg5hLg+uBpCLoFG5kVvEyafmfxGUV0UHLcL5irxAK7PKNeC2MwClgk6ZAiNhmo9FLhRYgvMmedLtnQ== - -"@rolldown/binding-linux-s390x-gnu@1.0.0-rc.15": - version "1.0.0-rc.15" - resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.15.tgz#ac0aa6f1b72e3151d56c43145a71c745cf862a9a" - integrity sha512-y1uXY3qQWCzcPgRJATPSOUP4tCemh4uBdY7e3EZbVwCJTY3gLJWnQABgeUetvED+bt1FQ01OeZwvhLS2bpNrAQ== - -"@rolldown/binding-linux-x64-gnu@1.0.0-rc.15": - version "1.0.0-rc.15" - resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.15.tgz#b8cf27aa5be6da641c22dad5665d0240551d2dec" - integrity sha512-023bTPBod7J3Y/4fzAN6QtpkSABR0rigtrwaP+qSEabUh5zf6ELr9Nc7GujaROuPY3uwdSIXWrvhn1KxOvurWA== - -"@rolldown/binding-linux-x64-musl@1.0.0-rc.15": - version "1.0.0-rc.15" - resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.15.tgz#4531f9eca77963935026634ba9b61c2535340534" - integrity sha512-witB2O0/hU4CgfOOKUoeFgQ4GktPi1eEbAhaLAIpgD6+ZnhcPkUtPsoKKHRzmOoWPZue46IThdSgdo4XneOLYw== - -"@rolldown/binding-openharmony-arm64@1.0.0-rc.15": - version "1.0.0-rc.15" - resolved "https://registry.yarnpkg.com/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.15.tgz#66ff691a65f9325171bced98e353b4cc4b0095c3" - integrity sha512-UCL68NJ0Ud5zRipXZE9dF5PmirzJE4E4BCIOOssEnM7wLDsxjc6Qb0sGDxTNRTP53I6MZpygyCpY8Aa8sPfKPg== - -"@rolldown/binding-wasm32-wasi@1.0.0-rc.15": - version "1.0.0-rc.15" - resolved "https://registry.yarnpkg.com/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.15.tgz#7db6c90aa510eef65d7d0f14e8ca23775e8e5eee" - integrity sha512-ApLruZq/ig+nhaE7OJm4lDjayUnOHVUa77zGeqnqZ9pn0ovdVbbNPerVibLXDmWeUZXjIYIT8V3xkT58Rm9u5Q== - dependencies: - "@emnapi/core" "1.9.2" - "@emnapi/runtime" "1.9.2" - "@napi-rs/wasm-runtime" "^1.1.3" - -"@rolldown/binding-win32-arm64-msvc@1.0.0-rc.15": - version "1.0.0-rc.15" - resolved "https://registry.yarnpkg.com/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.15.tgz#81f9097abbd4493cc13373b26f5a3da8461dbb47" - integrity sha512-KmoUoU7HnN+Si5YWJigfTws1jz1bKBYDQKdbLspz0UaqjjFkddHsqorgiW1mxcAj88lYUE6NC/zJNwT+SloqtA== - -"@rolldown/binding-win32-x64-msvc@1.0.0-rc.15": - version "1.0.0-rc.15" - resolved "https://registry.yarnpkg.com/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.15.tgz#cef11bc89149f3a77771727be75490fbb13ae193" - integrity sha512-3P2A8L+x75qavWLe/Dll3EYBJLQmtkJN8rfh+U/eR3MqMgL/h98PhYI+JFfXuDPgPeCB7iZAKiqii5vqOvnA0g== - -"@rolldown/pluginutils@1.0.0-rc.15": - version "1.0.0-rc.15" - resolved "https://registry.yarnpkg.com/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.15.tgz#e75d7731593e195d23710f9ff49bf5c745c96682" - integrity sha512-UromN0peaE53IaBRe9W7CjrZgXl90fqGpK+mIZbA3qSTeYqg3pqpROBdIPvOG3F5ereDHNwoHBI2e50n1BDr1g== +"@rolldown/binding-android-arm64@1.0.0-rc.17": + version "1.0.0-rc.17" + resolved "https://registry.yarnpkg.com/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.17.tgz#0a502a88c39d0ffa81aa30b561dade6f6217dcc5" + integrity sha512-s70pVGhw4zqGeFnXWvAzJDlvxhlRollagdCCKRgOsgUOH3N1l0LIxf83AtGzmb5SiVM4Hjl5HyarMRfdfj3DaQ== + +"@rolldown/binding-darwin-arm64@1.0.0-rc.17": + version "1.0.0-rc.17" + resolved "https://registry.yarnpkg.com/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.17.tgz#8b7f05ac9000ab19161a79a0346b1b64a1bc7ba3" + integrity sha512-4ksWc9n0mhlZpZ9PMZgTGjeOPRu8MB1Z3Tz0Mo02eWfWCHMW1zN82Qz/pL/rC+yQa+8ZnutMF0JjJe7PjwasYw== + +"@rolldown/binding-darwin-x64@1.0.0-rc.17": + version "1.0.0-rc.17" + resolved "https://registry.yarnpkg.com/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.17.tgz#f8b465b3a4e992053890b162f1ae19e4f1719a6a" + integrity sha512-SUSDOI6WwUVNcWxd02QEBjLdY1VPHvlEkw6T/8nYG322iYWCTxRb1vzk4E+mWWYehTp7ERibq54LSJGjmouOsw== + +"@rolldown/binding-freebsd-x64@1.0.0-rc.17": + version "1.0.0-rc.17" + resolved "https://registry.yarnpkg.com/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.17.tgz#a8281e14fa9c243fe22dc2d0e54900e66b31935e" + integrity sha512-hwnz3nw9dbJ05EDO/PvcjaaewqqDy7Y1rn1UO81l8iIK1GjenME75dl16ajbvSSMfv66WXSRCYKIqfgq2KCfxw== + +"@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.17": + version "1.0.0-rc.17" + resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.17.tgz#cd29cf869ddd4fac8d6929abf94b91ddb0494650" + integrity sha512-IS+W7epTcwANmFSQFrS1SivEXHtl1JtuQA9wlxrZTcNi6mx+FDOYrakGevvvTwgj2JvWiK8B29/qD9BELZPyXQ== + +"@rolldown/binding-linux-arm64-gnu@1.0.0-rc.17": + version "1.0.0-rc.17" + resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.17.tgz#91c331236ec3728366218d61a62f0bd226546c6c" + integrity sha512-e6usGaHKW5BMNZOymS1UcEYGowQMWcgZ71Z17Sl/h2+ZziNJ1a9n3Zvcz6LdRyIW5572wBCTH/Z+bKuZouGk9Q== + +"@rolldown/binding-linux-arm64-musl@1.0.0-rc.17": + version "1.0.0-rc.17" + resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.17.tgz#80108957db752e7826836e22240e56b8140e9684" + integrity sha512-b/CgbwAJpmrRLp02RPfhbudf5tZnN9nsPWK82znefso832etkem8H7FSZwxrOI9djcdTP7U6YfNhbRnh7djErg== + +"@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.17": + version "1.0.0-rc.17" + resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.17.tgz#1dce51148cbc6bab3c3f9157b5323d2a31aac924" + integrity sha512-4EII1iNGRUN5WwGbF/kOh/EIkoDN9HsupgLQoXfY+D1oyJm7/F4t5PYU5n8SWZgG0FEwakyM8pGgwcBYruGTlA== + +"@rolldown/binding-linux-s390x-gnu@1.0.0-rc.17": + version "1.0.0-rc.17" + resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.17.tgz#d4a0d2e01d8d441e4ac3af3fa68eec17a7d0e9cd" + integrity sha512-AH8oq3XqQo4IibpVXvPeLDI5pzkpYn0WiZAfT05kFzoJ6tQNzwRdDYQ45M8I/gslbodRZwW8uxLhbSBbkv96rA== + +"@rolldown/binding-linux-x64-gnu@1.0.0-rc.17": + version "1.0.0-rc.17" + resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.17.tgz#0ac8b3139cefeea798ad147f30ea70572b133af1" + integrity sha512-cLnjV3xfo7KslbU41Z7z8BH/E1y5mzUYzAqih1d1MDaIGZRCMqTijqLv76/P7fyHuvUcfGsIpqCdddbxLLK9rA== + +"@rolldown/binding-linux-x64-musl@1.0.0-rc.17": + version "1.0.0-rc.17" + resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.17.tgz#2af61bee087571728f58f1c47734bbbd41dd7050" + integrity sha512-0phclDw1spsL7dUB37sIARuis2tAgomCJXAHZlpt8PXZ4Ba0dRP1e+66lsRqrfhISeN9bEGNjQs+T/Fbd7oYGw== + +"@rolldown/binding-openharmony-arm64@1.0.0-rc.17": + version "1.0.0-rc.17" + resolved "https://registry.yarnpkg.com/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.17.tgz#56c1afbf6c592819abf47b4a983987dc288b30c1" + integrity sha512-0ag/hEgXOwgw4t8QyQvUCxvEg+V0KBcA6YuOx9g0r02MprutRF5dyljgm3EmR02O292UX7UeS6HzWHAl6KgyhA== + +"@rolldown/binding-wasm32-wasi@1.0.0-rc.17": + version "1.0.0-rc.17" + resolved "https://registry.yarnpkg.com/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.17.tgz#5d112ff4dd0d268a60fb4e0eb3077e3ea2531f0d" + integrity sha512-LEXei6vo0E5wTGwpkJ4KoT3OZJRnglwldt5ziLzOlc6qqb55z4tWNq2A+PFqCJuvWWdP53CVhG1Z9NtToDPJrA== + dependencies: + "@emnapi/core" "1.10.0" + "@emnapi/runtime" "1.10.0" + "@napi-rs/wasm-runtime" "^1.1.4" + +"@rolldown/binding-win32-arm64-msvc@1.0.0-rc.17": + version "1.0.0-rc.17" + resolved "https://registry.yarnpkg.com/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.17.tgz#5125a85222d64a543201d28e16a395cc45bf4d17" + integrity sha512-gUmyzBl3SPMa6hrqFUth9sVfcLBlYsbMzBx5PlexMroZStgzGqlZ26pYG89rBb45Mnia+oil6YAIFeEWGWhoZA== + +"@rolldown/binding-win32-x64-msvc@1.0.0-rc.17": + version "1.0.0-rc.17" + resolved "https://registry.yarnpkg.com/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.17.tgz#fc6b78e759a0bb2054b5c0a3489da12b2cae54b4" + integrity sha512-3hkiolcUAvPB9FLb3UZdfjVVNWherN1f/skkGWJP/fgSQhYUZpSIRr0/I8ZK9TkF3F7kxvJAk0+IcKvPHk9qQg== + +"@rolldown/pluginutils@1.0.0-rc.17": + version "1.0.0-rc.17" + resolved "https://registry.yarnpkg.com/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.17.tgz#a89b30833fb628bc834fe2e89fea93a2da9fa69a" + integrity sha512-n8iosDOt6Ig1UhJ2AYqoIhHWh/isz0xpicHTzpKBeotdVsTEcxsSA/i3EVM7gQAj0rU27OLAxCjzlj15IWY7bg== "@rolldown/pluginutils@1.0.0-rc.7": version "1.0.0-rc.7" @@ -3234,131 +3239,131 @@ resolved "https://registry.yarnpkg.com/@swc/core-darwin-arm64/-/core-darwin-arm64-1.15.10.tgz#fb76ac44fa1d30696299d45c84f35b62f9ea2bac" integrity sha512-U72pGqmJYbjrLhMndIemZ7u9Q9owcJczGxwtfJlz/WwMaGYAV/g4nkGiUVk/+QSX8sFCAjanovcU1IUsP2YulA== -"@swc/core-darwin-arm64@1.15.24": - version "1.15.24" - resolved "https://registry.yarnpkg.com/@swc/core-darwin-arm64/-/core-darwin-arm64-1.15.24.tgz#e812659bb23c5a078c05c8b18aad25e9d3a12e39" - integrity sha512-uM5ZGfFXjtvtJ+fe448PVBEbn/CSxS3UAyLj3O9xOqKIWy3S6hPTXSPbszxkSsGDYKi+YFhzAsR4r/eXLxEQ0g== +"@swc/core-darwin-arm64@1.15.30": + version "1.15.30" + resolved "https://registry.yarnpkg.com/@swc/core-darwin-arm64/-/core-darwin-arm64-1.15.30.tgz#23447f1c30c9155fe35602de4392b4ecfa0a54cc" + integrity sha512-VvpP+vq08HmGYewMWvrdsxh9s2lthz/808zXm8Yu5kaqeR8Yia2b0eYXleHQ3VAjoStUDk6LzTheBW9KXYQdMA== "@swc/core-darwin-x64@1.15.10": version "1.15.10" resolved "https://registry.yarnpkg.com/@swc/core-darwin-x64/-/core-darwin-x64-1.15.10.tgz#b00fdc59022152cc4998e8478a9250ca8382a7d7" integrity sha512-NZpDXtwHH083L40xdyj1sY31MIwLgOxKfZEAGCI8xHXdHa+GWvEiVdGiu4qhkJctoHFzAEc7ZX3GN5phuJcPuQ== -"@swc/core-darwin-x64@1.15.24": - version "1.15.24" - resolved "https://registry.yarnpkg.com/@swc/core-darwin-x64/-/core-darwin-x64-1.15.24.tgz#99e38bdb00a6975d54471f77c345b4ee9bbb4502" - integrity sha512-fMIb/Zfn929pw25VMBhV7Ji2Dl+lCWtUPNdYJQYOke+00E5fcQ9ynxtP8+qhUo/HZc+mYQb1gJxwHM9vty+lXg== +"@swc/core-darwin-x64@1.15.30": + version "1.15.30" + resolved "https://registry.yarnpkg.com/@swc/core-darwin-x64/-/core-darwin-x64-1.15.30.tgz#16e6e35fff5b07c712d8af44783da59ac64ad5cf" + integrity sha512-WiJA0hiZI3nwQAO6mu5RqigtWGDtth4Hiq6rbZxAaQyhIcqKIg5IoMRc1Y071lrNJn29eEDMC86Rq58xgUxlDg== "@swc/core-linux-arm-gnueabihf@1.15.10": version "1.15.10" resolved "https://registry.yarnpkg.com/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.15.10.tgz#d39405a2ddcf6604db684fcbbcc9c807810b5d41" integrity sha512-ioieF5iuRziUF1HkH1gg1r93e055dAdeBAPGAk40VjqpL5/igPJ/WxFHGvc6WMLhUubSJI4S0AiZAAhEAp1jDg== -"@swc/core-linux-arm-gnueabihf@1.15.24": - version "1.15.24" - resolved "https://registry.yarnpkg.com/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.15.24.tgz#3555ef64268825e4975409b7ed3e9f614e2f9759" - integrity sha512-vOkjsyjjxnoYx3hMEWcGxQrMgnNrRm6WAegBXrN8foHtDAR+zpdhpGF5a4lj1bNPgXAvmysjui8cM1ov/Clkaw== +"@swc/core-linux-arm-gnueabihf@1.15.30": + version "1.15.30" + resolved "https://registry.yarnpkg.com/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.15.30.tgz#abce7de734301109a7df23c22f6b6d233e3b9de9" + integrity sha512-YANuFUo48kIT6plJgCD0keae9HFXfjxsbvsgevqc0hr/07X/p7sAWTFOGYEc2SXcASaK7UvuQqzlbW8pr7R79g== "@swc/core-linux-arm64-gnu@1.15.10": version "1.15.10" resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.15.10.tgz#5f59553f7d1d3e0af02eb9544b6f88c05a7bb372" integrity sha512-tD6BClOrxSsNus9cJL7Gxdv7z7Y2hlyvZd9l0NQz+YXzmTWqnfzLpg16ovEI7gknH2AgDBB5ywOsqu8hUgSeEQ== -"@swc/core-linux-arm64-gnu@1.15.24": - version "1.15.24" - resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.15.24.tgz#15677103362e56826bb8bc4e15090228f81ef448" - integrity sha512-h/oNu+upkXJ6Cicnq7YGVj9PkdfarLCdQa8l/FlHYvfv8CEiMaeeTnpLU7gSBH/rGxosM6Qkfa/J9mThGF9CLA== +"@swc/core-linux-arm64-gnu@1.15.30": + version "1.15.30" + resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.15.30.tgz#9a4e418cdbbfe64506dd12469a553c07e1924fef" + integrity sha512-VndG8jaR4ugY6u+iVOT0Q+d2fZd7sLgjPgN8W/Le+3EbZKl+cRfFxV7Eoz4gfLqhmneZPdcIzf9T3LkgkmqNLg== "@swc/core-linux-arm64-musl@1.15.10": version "1.15.10" resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.15.10.tgz#eb833e49df2a706d2ccf8e4474c111e7a1b784a5" integrity sha512-4uAHO3nbfbrTcmO/9YcVweTQdx5fN3l7ewwl5AEK4yoC4wXmoBTEPHAVdKNe4r9+xrTgd4BgyPsy0409OjjlMw== -"@swc/core-linux-arm64-musl@1.15.24": - version "1.15.24" - resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.15.24.tgz#d1655edac4d0101b9c21193770fb8642ced7ef37" - integrity sha512-ZpF/pRe1guk6sKzQI9D1jAORtjTdNlyeXn9GDz8ophof/w2WhojRblvSDJaGe7rJjcPN8AaOkhwdRUh7q8oYIg== +"@swc/core-linux-arm64-musl@1.15.30": + version "1.15.30" + resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.15.30.tgz#4cd68ccb2af71c3ec539b15aa15c8fd304833d26" + integrity sha512-1SYGs2l0Yyyi0pR/P/NKz/x0kqxkoiw+BXeJjLUdecSk/KasncWlJrc6hOvFSgKHOBrzgM5jwuluKtlT8dnrcA== -"@swc/core-linux-ppc64-gnu@1.15.24": - version "1.15.24" - resolved "https://registry.yarnpkg.com/@swc/core-linux-ppc64-gnu/-/core-linux-ppc64-gnu-1.15.24.tgz#65dc9265686cc24a63d5d8a41a01efe432993181" - integrity sha512-QZEsZfisHTSJlmyChgDFNmKPb3W6Lhbfo/O76HhIngfEdnQNmukS38/VSe1feho+xkV5A5hETyCbx3sALBZKAQ== +"@swc/core-linux-ppc64-gnu@1.15.30": + version "1.15.30" + resolved "https://registry.yarnpkg.com/@swc/core-linux-ppc64-gnu/-/core-linux-ppc64-gnu-1.15.30.tgz#561997d3c5f392db7e3473cb4bbc43e6d6b1160c" + integrity sha512-TXREtiXeRhbfDFbmhnkIsXpKfzbfT73YkV2ZF6w0sfxgjC5zI2ZAbaCOq25qxvegofj2K93DtOpm9RLaBgqR2g== -"@swc/core-linux-s390x-gnu@1.15.24": - version "1.15.24" - resolved "https://registry.yarnpkg.com/@swc/core-linux-s390x-gnu/-/core-linux-s390x-gnu-1.15.24.tgz#89c575dbfa39fde1d83033bf4d13e1bf93c93f45" - integrity sha512-DLdJKVsJgglqQrJBuoUYNmzm3leI7kUZhLbZGHv42onfKsGf6JDS3+bzCUQfte/XOqDjh/tmmn1DR/CF/tCJFw== +"@swc/core-linux-s390x-gnu@1.15.30": + version "1.15.30" + resolved "https://registry.yarnpkg.com/@swc/core-linux-s390x-gnu/-/core-linux-s390x-gnu-1.15.30.tgz#d6f1d5dceca794909305584cb69f80dd91820410" + integrity sha512-DCR2YYeyd6DQE4OuDhImouuNcjXEiEdnn1Y0DyGteugPEDvVuvYk8Xddi+4o2SgWH6jiW8/I+3emZvbep1NC+g== "@swc/core-linux-x64-gnu@1.15.10": version "1.15.10" resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.15.10.tgz#5d0b6f36e9d9d49fc0a2170fec2b6de2c9c0e76a" integrity sha512-W0h9ONNw1pVIA0cN7wtboOSTl4Jk3tHq+w2cMPQudu9/+3xoCxpFb9ZdehwCAk29IsvdWzGzY6P7dDVTyFwoqg== -"@swc/core-linux-x64-gnu@1.15.24": - version "1.15.24" - resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.15.24.tgz#5275c0b24b01b26fdb7a1b5da6bd062d94f9581f" - integrity sha512-IpLYfposPA/XLxYOKpRfeccl1p5dDa3+okZDHHTchBkXEaVCnq5MADPmIWwIYj1tudt7hORsEHccG5no6IUQRw== +"@swc/core-linux-x64-gnu@1.15.30": + version "1.15.30" + resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.15.30.tgz#c3e91c60f265a62cec60145f0d2d931feb1cf41a" + integrity sha512-5Pizw3NgfOJ5BJOBK8TIRa59xFW2avESTOBDPTAYwZYa1JNDs+KMF9lUfjJiJLM5HiMs/wPheA9eiT0q9m2AoA== "@swc/core-linux-x64-musl@1.15.10": version "1.15.10" resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.15.10.tgz#0788cf24330f8efec0379cce594c727f3bc0a7c8" integrity sha512-XQNZlLZB62S8nAbw7pqoqwy91Ldy2RpaMRqdRN3T+tAg6Xg6FywXRKCsLh6IQOadr4p1+lGnqM/Wn35z5a/0Vw== -"@swc/core-linux-x64-musl@1.15.24": - version "1.15.24" - resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.15.24.tgz#e65e6f0d7215ded63c0711c2284fe13127772209" - integrity sha512-JHy3fMSc0t/EPWgo74+OK5TGr51aElnzqfUPaiRf2qJ/BfX5CUCfMiWVBuhI7qmVMBnk1jTRnL/xZnOSHDPLYg== +"@swc/core-linux-x64-musl@1.15.30": + version "1.15.30" + resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.15.30.tgz#3fd112e617a951438f73930b514adf19375067fb" + integrity sha512-qyqydP/wyH8alcIP4a2hnGSjHLJjm9H7yDFup+CPy9oTahFgLLwnNcv5UHXqO2Qs3AIND+cls5f/Bb6hqpxdgA== "@swc/core-win32-arm64-msvc@1.15.10": version "1.15.10" resolved "https://registry.yarnpkg.com/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.15.10.tgz#1555dc561606268ff84805568cd9a60b5de42543" integrity sha512-qnAGrRv5Nj/DATxAmCnJQRXXQqnJwR0trxLndhoHoxGci9MuguNIjWahS0gw8YZFjgTinbTxOwzatkoySihnmw== -"@swc/core-win32-arm64-msvc@1.15.24": - version "1.15.24" - resolved "https://registry.yarnpkg.com/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.15.24.tgz#12dff91f148bc4e2e48d7990f175f108199f6f0d" - integrity sha512-Txj+qUH1z2bUd1P3JvwByfjKFti3cptlAxhWgmunBUUxy/IW3CXLZ6l6Gk4liANadKkU71nIU1X30Z5vpMT3BA== +"@swc/core-win32-arm64-msvc@1.15.30": + version "1.15.30" + resolved "https://registry.yarnpkg.com/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.15.30.tgz#d005dce92e4ec1b0a7898667c9cf5e5215e4631c" + integrity sha512-CaQENgDHVGOg1mSF5sQVgvfFHG9kjMor2rkLMLeLOkfZYNj13ppnJ9+lfaBZLZUMMbnlGQnavCJb8PVBUOso7Q== "@swc/core-win32-ia32-msvc@1.15.10": version "1.15.10" resolved "https://registry.yarnpkg.com/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.15.10.tgz#e51f79e4e257f9b1a8c90437b20fc5a9b5b13119" integrity sha512-i4X/q8QSvzVlaRtv1xfnfl+hVKpCfiJ+9th484rh937fiEZKxZGf51C+uO0lfKDP1FfnT6C1yBYwHy7FLBVXFw== -"@swc/core-win32-ia32-msvc@1.15.24": - version "1.15.24" - resolved "https://registry.yarnpkg.com/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.15.24.tgz#eb3747533a3c078bfee5d857b086774647506a4a" - integrity sha512-15D/nl3XwrhFpMv+MADFOiVwv3FvH9j8c6Rf8EXBT3Q5LoMh8YnDnSgPYqw1JzPnksvsBX6QPXLiPqmcR/Z4qQ== +"@swc/core-win32-ia32-msvc@1.15.30": + version "1.15.30" + resolved "https://registry.yarnpkg.com/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.15.30.tgz#67ebfaa22266835a3d82776014c2f428346062bd" + integrity sha512-30VdLeGk6fugiUs/kUdJ/pAg7z/zpvVbR11RH60jZ0Z42WIeIniYx0rLEWN7h/pKJ3CopqsQ3RsogCAkRKiA2g== "@swc/core-win32-x64-msvc@1.15.10": version "1.15.10" resolved "https://registry.yarnpkg.com/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.15.10.tgz#9c2a11ac3321ebea8fc24940a1cd4b4cc9249fe2" integrity sha512-HvY8XUFuoTXn6lSccDLYFlXv1SU/PzYi4PyUqGT++WfTnbw/68N/7BdUZqglGRwiSqr0qhYt/EhmBpULj0J9rA== -"@swc/core-win32-x64-msvc@1.15.24": - version "1.15.24" - resolved "https://registry.yarnpkg.com/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.15.24.tgz#a0ad3bb9b8755093efe656299aa167138a589708" - integrity sha512-PR0PlTlPra2JbaDphrOAzm6s0v9rA0F17YzB+XbWD95B4g2cWcZY9LAeTa4xll70VLw9Jr7xBrlohqlQmelMFQ== +"@swc/core-win32-x64-msvc@1.15.30": + version "1.15.30" + resolved "https://registry.yarnpkg.com/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.15.30.tgz#cb602b53f9cdcdfb580cecdb02b536339d4b004b" + integrity sha512-4iObHPR+Q4oDY110EF5SF5eIaaVJNpMdG9C0q3Q92BsJ5y467uHz7sYQhP60WYlLFsLQ1el2YrIPUItUAQGOKg== "@swc/core@^1.15.11": - version "1.15.24" - resolved "https://registry.yarnpkg.com/@swc/core/-/core-1.15.24.tgz#258dd1f74c662d9a2535fcfa2aa8ff30fd23883d" - integrity sha512-5Hj8aNasue7yusUt8LGCUe/AjM7RMAce8ZoyDyiFwx7Al+GbYKL+yE7g4sJk8vEr1dKIkTRARkNIJENc4CjkBQ== + version "1.15.30" + resolved "https://registry.yarnpkg.com/@swc/core/-/core-1.15.30.tgz#2f77d5ed3b0df964aac8aaa251dc43ed822100cc" + integrity sha512-R8VQbQY1BZcbIF2p3gjlTCwAQzx1A194ugWfwld5y+WgVVWqVKm7eURGGOVbQVubgKWzidP2agomBbg96rZilQ== dependencies: "@swc/counter" "^0.1.3" "@swc/types" "^0.1.26" optionalDependencies: - "@swc/core-darwin-arm64" "1.15.24" - "@swc/core-darwin-x64" "1.15.24" - "@swc/core-linux-arm-gnueabihf" "1.15.24" - "@swc/core-linux-arm64-gnu" "1.15.24" - "@swc/core-linux-arm64-musl" "1.15.24" - "@swc/core-linux-ppc64-gnu" "1.15.24" - "@swc/core-linux-s390x-gnu" "1.15.24" - "@swc/core-linux-x64-gnu" "1.15.24" - "@swc/core-linux-x64-musl" "1.15.24" - "@swc/core-win32-arm64-msvc" "1.15.24" - "@swc/core-win32-ia32-msvc" "1.15.24" - "@swc/core-win32-x64-msvc" "1.15.24" + "@swc/core-darwin-arm64" "1.15.30" + "@swc/core-darwin-x64" "1.15.30" + "@swc/core-linux-arm-gnueabihf" "1.15.30" + "@swc/core-linux-arm64-gnu" "1.15.30" + "@swc/core-linux-arm64-musl" "1.15.30" + "@swc/core-linux-ppc64-gnu" "1.15.30" + "@swc/core-linux-s390x-gnu" "1.15.30" + "@swc/core-linux-x64-gnu" "1.15.30" + "@swc/core-linux-x64-musl" "1.15.30" + "@swc/core-win32-arm64-msvc" "1.15.30" + "@swc/core-win32-ia32-msvc" "1.15.30" + "@swc/core-win32-x64-msvc" "1.15.30" "@swc/core@^1.15.8", "@swc/core@^1.7.3": version "1.15.10" @@ -3420,10 +3425,10 @@ source-map-js "^1.2.1" tailwindcss "4.1.18" -"@tailwindcss/node@4.2.2": - version "4.2.2" - resolved "https://registry.yarnpkg.com/@tailwindcss/node/-/node-4.2.2.tgz#840e904226dc1b379609de8a72323fc211568993" - integrity sha512-pXS+wJ2gZpVXqFaUEjojq7jzMpTGf8rU6ipJz5ovJV6PUGmlJ+jvIwGrzdHdQ80Sg+wmQxUFuoW1UAAwHNEdFA== +"@tailwindcss/node@4.2.4": + version "4.2.4" + resolved "https://registry.yarnpkg.com/@tailwindcss/node/-/node-4.2.4.tgz#1f7fc0c1741037ded1fa92fbe62a786a197771ce" + integrity sha512-Ai7+yQPxz3ddrDQzFfBKdHEVBg0w3Zl83jnjuwxnZOsnH9pGn93QHQtpU0p/8rYWxvbFZHneni6p1BSLK4DkGA== dependencies: "@jridgewell/remapping" "^2.3.5" enhanced-resolve "^5.19.0" @@ -3431,97 +3436,97 @@ lightningcss "1.32.0" magic-string "^0.30.21" source-map-js "^1.2.1" - tailwindcss "4.2.2" + tailwindcss "4.2.4" "@tailwindcss/oxide-android-arm64@4.1.18": version "4.1.18" resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.18.tgz#79717f87e90135e5d3d23a3d3aecde4ca5595dd5" integrity sha512-dJHz7+Ugr9U/diKJA0W6N/6/cjI+ZTAoxPf9Iz9BFRF2GzEX8IvXxFIi/dZBloVJX/MZGvRuFA9rqwdiIEZQ0Q== -"@tailwindcss/oxide-android-arm64@4.2.2": - version "4.2.2" - resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.2.2.tgz#61d9ec5c18394fe7a972e99e19e6065e833da77c" - integrity sha512-dXGR1n+P3B6748jZO/SvHZq7qBOqqzQ+yFrXpoOWWALWndF9MoSKAT3Q0fYgAzYzGhxNYOoysRvYlpixRBBoDg== +"@tailwindcss/oxide-android-arm64@4.2.4": + version "4.2.4" + resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.2.4.tgz#d533e52ee98d58f55d1d4753774251513ba8a911" + integrity sha512-e7MOr1SAn9U8KlZzPi1ZXGZHeC5anY36qjNwmZv9pOJ8E4Q6jmD1vyEHkQFmNOIN7twGPEMXRHmitN4zCMN03g== "@tailwindcss/oxide-darwin-arm64@4.1.18": version "4.1.18" resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.18.tgz#7fa47608d62d60e9eb020682249d20159667fbb0" integrity sha512-Gc2q4Qhs660bhjyBSKgq6BYvwDz4G+BuyJ5H1xfhmDR3D8HnHCmT/BSkvSL0vQLy/nkMLY20PQ2OoYMO15Jd0A== -"@tailwindcss/oxide-darwin-arm64@4.2.2": - version "4.2.2" - resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.2.2.tgz#9ad7b141789dae235c85d2f7874592bf869f636e" - integrity sha512-iq9Qjr6knfMpZHj55/37ouZeykwbDqF21gPFtfnhCCKGDcPI/21FKC9XdMO/XyBM7qKORx6UIhGgg6jLl7BZlg== +"@tailwindcss/oxide-darwin-arm64@4.2.4": + version "4.2.4" + resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.2.4.tgz#2a6250aa7d8791fc1b5797e64e09e51da57514a6" + integrity sha512-tSC/Kbqpz/5/o/C2sG7QvOxAKqyd10bq+ypZNf+9Fi2TvbVbv1zNpcEptcsU7DPROaSbVgUXmrzKhurFvo5eDg== "@tailwindcss/oxide-darwin-x64@4.1.18": version "4.1.18" resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.18.tgz#c05991c85aa2af47bf9d1f8172fe9e4636591e79" integrity sha512-FL5oxr2xQsFrc3X9o1fjHKBYBMD1QZNyc1Xzw/h5Qu4XnEBi3dZn96HcHm41c/euGV+GRiXFfh2hUCyKi/e+yw== -"@tailwindcss/oxide-darwin-x64@4.2.2": - version "4.2.2" - resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.2.2.tgz#a5899f1fbe55c4eddcbc871b835d5183ba34658c" - integrity sha512-BlR+2c3nzc8f2G639LpL89YY4bdcIdUmiOOkv2GQv4/4M0vJlpXEa0JXNHhCHU7VWOKWT/CjqHdTP8aUuDJkuw== +"@tailwindcss/oxide-darwin-x64@4.2.4": + version "4.2.4" + resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.2.4.tgz#d647299812946b6ab5140c61a334c8ebc8d877de" + integrity sha512-yPyUXn3yO/ufR6+Kzv0t4fCg2qNr90jxXc5QqBpjlPNd0NqyDXcmQb/6weunH/MEDXW5dhyEi+agTDiqa3WsGg== "@tailwindcss/oxide-freebsd-x64@4.1.18": version "4.1.18" resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.18.tgz#3d48e8d79fd08ece0e02af8e72d5059646be34d0" integrity sha512-Fj+RHgu5bDodmV1dM9yAxlfJwkkWvLiRjbhuO2LEtwtlYlBgiAT4x/j5wQr1tC3SANAgD+0YcmWVrj8R9trVMA== -"@tailwindcss/oxide-freebsd-x64@4.2.2": - version "4.2.2" - resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.2.2.tgz#76185bb1bea9af915a5b9f465323861646587e21" - integrity sha512-YUqUgrGMSu2CDO82hzlQ5qSb5xmx3RUrke/QgnoEx7KvmRJHQuZHZmZTLSuuHwFf0DJPybFMXMYf+WJdxHy/nQ== +"@tailwindcss/oxide-freebsd-x64@4.2.4": + version "4.2.4" + resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.2.4.tgz#019b7fce37aaf5ddfed0f231c536108292e87ffb" + integrity sha512-BoMIB4vMQtZsXdGLVc2z+P9DbETkiopogfWZKbWwM8b/1Vinbs4YcUwo+kM/KeLkX3Ygrf4/PsRndKaYhS8Eiw== "@tailwindcss/oxide-linux-arm-gnueabihf@4.1.18": version "4.1.18" resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.18.tgz#982ecd1a65180807ccfde67dc17c6897f2e50aa8" integrity sha512-Fp+Wzk/Ws4dZn+LV2Nqx3IilnhH51YZoRaYHQsVq3RQvEl+71VGKFpkfHrLM/Li+kt5c0DJe/bHXK1eHgDmdiA== -"@tailwindcss/oxide-linux-arm-gnueabihf@4.2.2": - version "4.2.2" - resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.2.2.tgz#74c17c69b2015f7600d566ab0990aaac8701128e" - integrity sha512-FPdhvsW6g06T9BWT0qTwiVZYE2WIFo2dY5aCSpjG/S/u1tby+wXoslXS0kl3/KXnULlLr1E3NPRRw0g7t2kgaQ== +"@tailwindcss/oxide-linux-arm-gnueabihf@4.2.4": + version "4.2.4" + resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.2.4.tgz#c88a95d69095e84f811b302daa66f5287ad8ce0f" + integrity sha512-7pIHBLTHYRAlS7V22JNuTh33yLH4VElwKtB3bwchK/UaKUPpQ0lPQiOWcbm4V3WP2I6fNIJ23vABIvoy2izdwA== "@tailwindcss/oxide-linux-arm64-gnu@4.1.18": version "4.1.18" resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.18.tgz#df49357bc9737b2e9810ea950c1c0647ba6573c3" integrity sha512-S0n3jboLysNbh55Vrt7pk9wgpyTTPD0fdQeh7wQfMqLPM/Hrxi+dVsLsPrycQjGKEQk85Kgbx+6+QnYNiHalnw== -"@tailwindcss/oxide-linux-arm64-gnu@4.2.2": - version "4.2.2" - resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.2.2.tgz#38a846d9d5795bc3b57951172044d8dbb3c79aa6" - integrity sha512-4og1V+ftEPXGttOO7eCmW7VICmzzJWgMx+QXAJRAhjrSjumCwWqMfkDrNu1LXEQzNAwz28NCUpucgQPrR4S2yw== +"@tailwindcss/oxide-linux-arm64-gnu@4.2.4": + version "4.2.4" + resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.2.4.tgz#1292f1c222994bfe4a5e990ac0a701de6487dd02" + integrity sha512-+E4wxJ0ZGOzSH325reXTWB48l42i93kQqMvDyz5gqfRzRZ7faNhnmvlV4EPGJU3QJM/3Ab5jhJ5pCRUsKn6OQw== "@tailwindcss/oxide-linux-arm64-musl@4.1.18": version "4.1.18" resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.18.tgz#b266c12822bf87883cf152615f8fffb8519d689c" integrity sha512-1px92582HkPQlaaCkdRcio71p8bc8i/ap5807tPRDK/uw953cauQBT8c5tVGkOwrHMfc2Yh6UuxaH4vtTjGvHg== -"@tailwindcss/oxide-linux-arm64-musl@4.2.2": - version "4.2.2" - resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.2.2.tgz#f4cc4129c17d3f2bcb01efef4d7a2f381e5e3f53" - integrity sha512-oCfG/mS+/+XRlwNjnsNLVwnMWYH7tn/kYPsNPh+JSOMlnt93mYNCKHYzylRhI51X+TbR+ufNhhKKzm6QkqX8ag== +"@tailwindcss/oxide-linux-arm64-musl@4.2.4": + version "4.2.4" + resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.2.4.tgz#afb6492b22616f0d9d3346d39c1a6e285f994a08" + integrity sha512-bBADEGAbo4ASnppIziaQJelekCxdMaxisrk+fB7Thit72IBnALp9K6ffA2G4ruj90G9XRS2VQ6q2bCKbfFV82g== "@tailwindcss/oxide-linux-x64-gnu@4.1.18": version "4.1.18" resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.18.tgz#5c737f13dd9529b25b314e6000ff54e05b3811da" integrity sha512-v3gyT0ivkfBLoZGF9LyHmts0Isc8jHZyVcbzio6Wpzifg/+5ZJpDiRiUhDLkcr7f/r38SWNe7ucxmGW3j3Kb/g== -"@tailwindcss/oxide-linux-x64-gnu@4.2.2": - version "4.2.2" - resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.2.2.tgz#7c4a00b0829e12736bd72ec74e1c08205448cc2e" - integrity sha512-rTAGAkDgqbXHNp/xW0iugLVmX62wOp2PoE39BTCGKjv3Iocf6AFbRP/wZT/kuCxC9QBh9Pu8XPkv/zCZB2mcMg== +"@tailwindcss/oxide-linux-x64-gnu@4.2.4": + version "4.2.4" + resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.2.4.tgz#400b0ccfc53937c7804ed8e0e9652b42bd86f2eb" + integrity sha512-7Mx25E4WTfnht0TVRTyC00j3i0M+EeFe7wguMDTlX4mRxafznw0CA8WJkFjWYH5BlgELd1kSjuU2JiPnNZbJDA== "@tailwindcss/oxide-linux-x64-musl@4.1.18": version "4.1.18" resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.18.tgz#3380e17f7be391f1ef924be9f0afe1f304fe3478" integrity sha512-bhJ2y2OQNlcRwwgOAGMY0xTFStt4/wyU6pvI6LSuZpRgKQwxTec0/3Scu91O8ir7qCR3AuepQKLU/kX99FouqQ== -"@tailwindcss/oxide-linux-x64-musl@4.2.2": - version "4.2.2" - resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.2.2.tgz#711756d7bbe97e221fc041b63a4f385b85ba4321" - integrity sha512-XW3t3qwbIwiSyRCggeO2zxe3KWaEbM0/kW9e8+0XpBgyKU4ATYzcVSMKteZJ1iukJ3HgHBjbg9P5YPRCVUxlnQ== +"@tailwindcss/oxide-linux-x64-musl@4.2.4": + version "4.2.4" + resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.2.4.tgz#5c23c476e5de4ed9cd6ab39c2718b9a4be2bbb2b" + integrity sha512-2wwJRF7nyhOR0hhHoChc04xngV3iS+akccHTGtz965FwF0up4b2lOdo6kI1EbDaEXKgvcrFBYcYQQ/rrnWFVfA== "@tailwindcss/oxide-wasm32-wasi@4.1.18": version "4.1.18" @@ -3535,10 +3540,10 @@ "@tybys/wasm-util" "^0.10.1" tslib "^2.4.0" -"@tailwindcss/oxide-wasm32-wasi@4.2.2": - version "4.2.2" - resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.2.2.tgz#ed6d28567b7abb8505f824457c236d2cd07ee18e" - integrity sha512-eKSztKsmEsn1O5lJ4ZAfyn41NfG7vzCg496YiGtMDV86jz1q/irhms5O0VrY6ZwTUkFy/EKG3RfWgxSI3VbZ8Q== +"@tailwindcss/oxide-wasm32-wasi@4.2.4": + version "4.2.4" + resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.2.4.tgz#21b7f53ba7c6c03f26ccb8cef5d09f5c2973ae5e" + integrity sha512-FQsqApeor8Fo6gUEklzmaa9994orJZZDBAlQpK2Mq+DslRKFJeD6AjHpBQ0kZFQohVr8o85PPh8eOy86VlSCmw== dependencies: "@emnapi/core" "^1.8.1" "@emnapi/runtime" "^1.8.1" @@ -3552,20 +3557,20 @@ resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.18.tgz#bbcdd59c628811f6a0a4d5b09616967d8fb0c4d4" integrity sha512-HjSA7mr9HmC8fu6bdsZvZ+dhjyGCLdotjVOgLA2vEqxEBZaQo9YTX4kwgEvPCpRh8o4uWc4J/wEoFzhEmjvPbA== -"@tailwindcss/oxide-win32-arm64-msvc@4.2.2": - version "4.2.2" - resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.2.2.tgz#f2d0360e5bc06fe201537fb08193d3780e7dd24f" - integrity sha512-qPmaQM4iKu5mxpsrWZMOZRgZv1tOZpUm+zdhhQP0VhJfyGGO3aUKdbh3gDZc/dPLQwW4eSqWGrrcWNBZWUWaXQ== +"@tailwindcss/oxide-win32-arm64-msvc@4.2.4": + version "4.2.4" + resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.2.4.tgz#13bc1cf3818e3345a965d36b40c237817124d070" + integrity sha512-L9BXqxC4ToVgwMFqj3pmZRqyHEztulpUJzCxUtLjobMCzTPsGt1Fa9enKbOpY2iIyVtaHNeNvAK8ERP/64sqGQ== "@tailwindcss/oxide-win32-x64-msvc@4.1.18": version "4.1.18" resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.18.tgz#9c628d04623aa4c3536c508289f58d58ba4b3fb1" integrity sha512-bJWbyYpUlqamC8dpR7pfjA0I7vdF6t5VpUGMWRkXVE3AXgIZjYUYAK7II1GNaxR8J1SSrSrppRar8G++JekE3Q== -"@tailwindcss/oxide-win32-x64-msvc@4.2.2": - version "4.2.2" - resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.2.2.tgz#10fc71b73883f9c3999b5b8c338fd96a45240dcb" - integrity sha512-1T/37VvI7WyH66b+vqHj/cLwnCxt7Qt3WFu5Q8hk65aOvlwAhs7rAp1VkulBJw/N4tMirXjVnylTR72uI0HGcA== +"@tailwindcss/oxide-win32-x64-msvc@4.2.4": + version "4.2.4" + resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.2.4.tgz#5476dbbbf6b8934d58452340cec737fdaa5ec8c6" + integrity sha512-ESlKG0EpVJQwRjXDDa9rLvhEAh0mhP1sF7sap9dNZT0yyl9SAG6T7gdP09EH0vIv0UNTlo6jPWyujD6559fZvw== "@tailwindcss/oxide@4.1.18": version "4.1.18" @@ -3585,23 +3590,23 @@ "@tailwindcss/oxide-win32-arm64-msvc" "4.1.18" "@tailwindcss/oxide-win32-x64-msvc" "4.1.18" -"@tailwindcss/oxide@4.2.2": - version "4.2.2" - resolved "https://registry.yarnpkg.com/@tailwindcss/oxide/-/oxide-4.2.2.tgz#c6534cb4b22650df605a58258235523a6abd7de8" - integrity sha512-qEUA07+E5kehxYp9BVMpq9E8vnJuBHfJEC0vPC5e7iL/hw7HR61aDKoVoKzrG+QKp56vhNZe4qwkRmMC0zDLvg== +"@tailwindcss/oxide@4.2.4": + version "4.2.4" + resolved "https://registry.yarnpkg.com/@tailwindcss/oxide/-/oxide-4.2.4.tgz#e2ca51d04e8ad94d569222fa727de479b097db39" + integrity sha512-9El/iI069DKDSXwTvB9J4BwdO5JhRrOweGaK25taBAvBXyXqJAX+Jqdvs8r8gKpsI/1m0LeJLyQYTf/WLrBT1Q== optionalDependencies: - "@tailwindcss/oxide-android-arm64" "4.2.2" - "@tailwindcss/oxide-darwin-arm64" "4.2.2" - "@tailwindcss/oxide-darwin-x64" "4.2.2" - "@tailwindcss/oxide-freebsd-x64" "4.2.2" - "@tailwindcss/oxide-linux-arm-gnueabihf" "4.2.2" - "@tailwindcss/oxide-linux-arm64-gnu" "4.2.2" - "@tailwindcss/oxide-linux-arm64-musl" "4.2.2" - "@tailwindcss/oxide-linux-x64-gnu" "4.2.2" - "@tailwindcss/oxide-linux-x64-musl" "4.2.2" - "@tailwindcss/oxide-wasm32-wasi" "4.2.2" - "@tailwindcss/oxide-win32-arm64-msvc" "4.2.2" - "@tailwindcss/oxide-win32-x64-msvc" "4.2.2" + "@tailwindcss/oxide-android-arm64" "4.2.4" + "@tailwindcss/oxide-darwin-arm64" "4.2.4" + "@tailwindcss/oxide-darwin-x64" "4.2.4" + "@tailwindcss/oxide-freebsd-x64" "4.2.4" + "@tailwindcss/oxide-linux-arm-gnueabihf" "4.2.4" + "@tailwindcss/oxide-linux-arm64-gnu" "4.2.4" + "@tailwindcss/oxide-linux-arm64-musl" "4.2.4" + "@tailwindcss/oxide-linux-x64-gnu" "4.2.4" + "@tailwindcss/oxide-linux-x64-musl" "4.2.4" + "@tailwindcss/oxide-wasm32-wasi" "4.2.4" + "@tailwindcss/oxide-win32-arm64-msvc" "4.2.4" + "@tailwindcss/oxide-win32-x64-msvc" "4.2.4" "@tailwindcss/postcss@^4.1.18": version "4.1.18" @@ -3615,13 +3620,13 @@ tailwindcss "4.1.18" "@tailwindcss/vite@^4.2.2": - version "4.2.2" - resolved "https://registry.yarnpkg.com/@tailwindcss/vite/-/vite-4.2.2.tgz#49240a41691c34b78ed4a80d07a39301f1a5129f" - integrity sha512-mEiF5HO1QqCLXoNEfXVA1Tzo+cYsrqV7w9Juj2wdUFyW07JRenqMG225MvPwr3ZD9N1bFQj46X7r33iHxLUW0w== + version "4.2.4" + resolved "https://registry.yarnpkg.com/@tailwindcss/vite/-/vite-4.2.4.tgz#2c2586ed964de044778b294538481878ffb9ae5c" + integrity sha512-pCvohwOCspk3ZFn6eJzrrX3g4n2JY73H6MmYC87XfGPyTty4YsCjYTMArRZm/zOI8dIt3+EcrLHAFPe5A4bgtw== dependencies: - "@tailwindcss/node" "4.2.2" - "@tailwindcss/oxide" "4.2.2" - tailwindcss "4.2.2" + "@tailwindcss/node" "4.2.4" + "@tailwindcss/oxide" "4.2.4" + tailwindcss "4.2.4" "@tauri-apps/api@^2.0.0": version "2.9.1" @@ -4810,9 +4815,9 @@ anymatch@^3.1.3, anymatch@~3.1.2: picomatch "^2.0.4" appium-ios-device@^3.0.0: - version "3.1.10" - resolved "https://registry.yarnpkg.com/appium-ios-device/-/appium-ios-device-3.1.10.tgz#a78ad5bc52be764626ed15d78e3ffad9b2fe553d" - integrity sha512-2oE7yQtLSdrcZ9YArqgGguzDuiplHj0GXSMlTfwTXl0n22DEzkV0M1mXdaNaWNuzVBJ5VDc1EuYv38p1ruuk2g== + version "3.1.11" + resolved "https://registry.yarnpkg.com/appium-ios-device/-/appium-ios-device-3.1.11.tgz#f3848b84c6200ac272dd09382d825a2f52f10867" + integrity sha512-ccW8jAfZTtKc6mvFbbHCkVbB8/OxOdBolAB/sAHmwGl0jDrCrzMWOINkx1EdZx6QIrNPAw11Op1HibIRU66RWA== dependencies: "@appium/support" "^7.0.0-rc.1" asyncbox "^6.0.1" @@ -4823,10 +4828,10 @@ appium-ios-device@^3.0.0: lodash "^4.17.15" semver "^7.0.0" -appium-ios-remotexpc@^0.x: - version "0.44.2" - resolved "https://registry.yarnpkg.com/appium-ios-remotexpc/-/appium-ios-remotexpc-0.44.2.tgz#8861a1706fe336ebb55e5d33935ccdc4f3d417b1" - integrity sha512-KQSDBL3Tk+7ViqLj1Ixdl62BcKVkpg0Nfaae9f6pQzCv0ljK4uispqxyu+ue4aTGq1JETOlhviqDhnC5cZU/fA== +appium-ios-remotexpc@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/appium-ios-remotexpc/-/appium-ios-remotexpc-1.0.1.tgz#a92893ad8e1279f58146d6e9afbf2bcd6ea79e99" + integrity sha512-I9y7UHyl1JIwCODpUc+6LkWuCKWlw3OekVVDet3Ldt4z/fc2lLR/KHipGdphHcYqcBeJgmfIHWKUlMOY+xYoqw== dependencies: "@appium/strongbox" "^1.0.0-rc.1" "@appium/support" "^7.0.0-rc.1" @@ -4852,9 +4857,9 @@ appium-ios-tuntap@^0.x: typescript "^6.0.2" appium-remote-debugger@^15.6.0: - version "15.6.0" - resolved "https://registry.yarnpkg.com/appium-remote-debugger/-/appium-remote-debugger-15.6.0.tgz#80592475e044bfaee1df1b85bb94492c26fdadc4" - integrity sha512-vyN2PO/hQ+/DC9ZuY0r6xjg9zedltkSKuUJx3WfVZpgvv/al9uE/AYzk5C/arlwMjAR6Ci+LJFOHUtfDJweTSA== + version "15.7.3" + resolved "https://registry.yarnpkg.com/appium-remote-debugger/-/appium-remote-debugger-15.7.3.tgz#5e83e3db22a7653b5763bccf511224d14c700720" + integrity sha512-CC57qOATozVFpsrSj9lXRIU/J65SiubpRQS04tBdgwp4RF8giA9DHRkJ/eM3JBBGZwKhRGrwYu6E3DZmTXJElQ== dependencies: "@appium/base-driver" "^10.0.0-rc.1" "@appium/support" "^7.0.0-rc.1" @@ -4866,7 +4871,7 @@ appium-remote-debugger@^15.6.0: lodash "^4.17.11" teen_process "^4.0.4" optionalDependencies: - appium-ios-remotexpc "^0.x" + appium-ios-remotexpc "^1.0.0" archiver-utils@^5.0.0, archiver-utils@^5.0.2: version "5.0.2" @@ -5241,9 +5246,9 @@ bare-events@^2.5.4, bare-events@^2.7.0: integrity sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ== bare-fs@^4.5.5: - version "4.7.0" - resolved "https://registry.yarnpkg.com/bare-fs/-/bare-fs-4.7.0.tgz#01efd92b14a6c93825e8992ac1e42fdd6237de71" - integrity sha512-xzqKsCFxAek9aezYhjJuJRXBIaYlg/0OGDTZp+T8eYmYMlm66cs6cYko02drIyjN2CBbi+I6L7YfXyqpqtKRXA== + version "4.7.1" + resolved "https://registry.yarnpkg.com/bare-fs/-/bare-fs-4.7.1.tgz#6e81f784761102867c13f0823aa48c942d160f00" + integrity sha512-WDRsyVN52eAx/lBamKD6uyw8H4228h/x0sGGGegOamM2cd7Pag88GfMQalobXI+HaEUxpCkbKQUDOQqt9wawRw== dependencies: bare-events "^2.5.4" bare-path "^3.0.0" @@ -5252,9 +5257,9 @@ bare-fs@^4.5.5: fast-fifo "^1.3.2" bare-os@^3.0.1: - version "3.8.7" - resolved "https://registry.yarnpkg.com/bare-os/-/bare-os-3.8.7.tgz#09c7c4e8c817de750b0b69b65c929513f69ede65" - integrity sha512-G4Gr1UsGeEy2qtDTZwL7JFLo2wapUarz7iTMcYcMFdS89AIQuBoyjgXZz0Utv7uHs3xA9LckhVbeBi8lEQrC+w== + version "3.9.0" + resolved "https://registry.yarnpkg.com/bare-os/-/bare-os-3.9.0.tgz#59b1dd75c231067526f811c0e3d985257bd35747" + integrity sha512-JTjuZyNIDpw+GytMO4a6TK1VXdVKKJr6DRxEHasyuYyShV2deuiHJK/ahGZlebc+SG0/wJCB9XK8gprBGDFi/Q== bare-path@^3.0.0: version "3.0.0" @@ -5272,9 +5277,9 @@ bare-stream@^2.6.4: teex "^1.0.1" bare-url@^2.2.2: - version "2.4.0" - resolved "https://registry.yarnpkg.com/bare-url/-/bare-url-2.4.0.tgz#1546d63057917189cab9b24629e946e1e8f7af31" - integrity sha512-NSTU5WN+fy/L0DDenfE8SXQna4voXuW0FHM7wH8i3/q9khUSchfPbPezO4zSFMnDGIf9YE+mt/RWhZgNRKRIXA== + version "2.4.2" + resolved "https://registry.yarnpkg.com/bare-url/-/bare-url-2.4.2.tgz#1faab7d8f1780f6e51d5db03711dc390460874c0" + integrity sha512-/9a2j4ac6ckpmAHvod/ob7x439OAHst/drc2Clnq+reRYd/ovddwcF4LfoxHyNk5AuGBnPg+HqFjmE/Zpq6v0A== dependencies: bare-path "^3.0.0" @@ -6625,9 +6630,9 @@ dexie@^3.2.2: integrity sha512-2a+BXvVhY5op+smDRLxeBAivE7YcYaneXJ1la3HOkUfX9zKkE/AJ8CNgjiXbtXepFyFmJNGSbmjOwqbT749r/w== dexie@^4.0.1: - version "4.2.1" - resolved "https://registry.yarnpkg.com/dexie/-/dexie-4.2.1.tgz#70d111ae8d2dabf53f424fca79f6f918c407e6db" - integrity sha512-Ckej0NS6jxQ4Po3OrSQBFddayRhTCic2DoCAG5zacOfOVB9P2Q5Xc5uL/nVa7ZVs+HdMnvUPzLFCB/JwpB6Csg== + version "4.4.2" + resolved "https://registry.yarnpkg.com/dexie/-/dexie-4.4.2.tgz#447511328c982baaf6a88c736ddc08484916886c" + integrity sha512-zMtV8q79EFE5U8FKZvt0Y/77PCU/Hr/RDxv1EDeo228L+m/HTbeN2AjoQm674rhQCX8n3ljK87lajt7UQuZfvw== diff@^4.0.1: version "4.0.4" @@ -6919,12 +6924,12 @@ enhanced-resolve@^5.18.3: tapable "^2.3.0" enhanced-resolve@^5.19.0: - version "5.20.1" - resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.20.1.tgz#eeeb3966bea62c348c40a0cc9e7912e2557d0be0" - integrity sha512-Qohcme7V1inbAfvjItgw0EaxVX5q2rdVEZHRBrEQdRZTssLDGsL8Lwrznl8oQ/6kuTJONLaDcGjkNP247XEhcA== + version "5.21.0" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.21.0.tgz#bb8e6fabaf74930de70e61397798750429e5b1ae" + integrity sha512-otxSQPw4lkOZWkHpB3zaEQs6gWYEsmX4xQF68ElXC/TWvGxGMSGOvoNbaLXm6/cS/fSfHtsEdw90y20PCd+sCA== dependencies: graceful-fs "^4.2.4" - tapable "^2.3.0" + tapable "^2.3.3" enquirer@^2.3.6: version "2.4.1" @@ -9839,21 +9844,16 @@ lower-case@^2.0.2: dependencies: tslib "^2.0.3" -lru-cache@11.3.3: - version "11.3.3" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-11.3.3.tgz#d6c633c2a9657760fd30594d8d98da65330d9d78" - integrity sha512-JvNw9Y81y33E+BEYPr0U7omo+U9AySnsMsEiXgwT6yqd31VQWTLNQqmT4ou5eqPFUrTfIDFta2wKhB1hyohtAQ== +lru-cache@11.3.5, lru-cache@^11.1.0: + version "11.3.5" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-11.3.5.tgz#29047d348c0b2793e3112a01c739bb7c6d855637" + integrity sha512-NxVFwLAnrd9i7KUBxC4DrUhmgjzOs+1Qm50D3oF1/oL+r1NpZ4gA7xvG0/zJ8evR7zIKn4vLf7qTNduWFtCrRw== lru-cache@^10.2.0, lru-cache@^10.4.3: version "10.4.3" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.4.3.tgz#410fc8a17b70e598013df257c2446b7f3383f119" integrity sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ== -lru-cache@^11.1.0: - version "11.3.5" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-11.3.5.tgz#29047d348c0b2793e3112a01c739bb7c6d855637" - integrity sha512-NxVFwLAnrd9i7KUBxC4DrUhmgjzOs+1Qm50D3oF1/oL+r1NpZ4gA7xvG0/zJ8evR7zIKn4vLf7qTNduWFtCrRw== - lru-cache@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" @@ -11617,10 +11617,10 @@ postcss@^8.4.33, postcss@^8.4.41, postcss@^8.5.6: picocolors "^1.1.1" source-map-js "^1.2.1" -postcss@^8.5.8: - version "8.5.9" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.5.9.tgz#f6ee9e0b94f0f19c97d2f172bfbd7fc71fe1cca4" - integrity sha512-7a70Nsot+EMX9fFU3064K/kdHWZqGVY+BADLyXc8Dfv+mTLLVl6JzJpPaCZ2kQL9gIJvKXSLMHhqdRRjwQeFtw== +postcss@^8.5.10: + version "8.5.10" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.5.10.tgz#8992d8c30acf3f12169e7c09514a12fed7e48356" + integrity sha512-pMMHxBOZKFU6HgAZ4eyGnwXF/EvPGGqUr0MnZ5+99485wwW41kW91A4LOGxSHhgugZmSChL5AlElNdwlNgcnLQ== dependencies: nanoid "^3.3.11" picocolors "^1.1.1" @@ -12434,29 +12434,29 @@ ripemd160@^2.0.0, ripemd160@^2.0.1, ripemd160@^2.0.3: hash-base "^3.1.2" inherits "^2.0.4" -rolldown@1.0.0-rc.15: - version "1.0.0-rc.15" - resolved "https://registry.yarnpkg.com/rolldown/-/rolldown-1.0.0-rc.15.tgz#ea3526443b2dbe834e9f8f6c1fde6232ec687170" - integrity sha512-Ff31guA5zT6WjnGp0SXw76X6hzGRk/OQq2hE+1lcDe+lJdHSgnSX6nK3erbONHyCbpSj9a9E+uX/OvytZoWp2g== +rolldown@1.0.0-rc.17: + version "1.0.0-rc.17" + resolved "https://registry.yarnpkg.com/rolldown/-/rolldown-1.0.0-rc.17.tgz#c524fc22f6bb37b5588aec862ab1ee11382610f3" + integrity sha512-ZrT53oAKrtA4+YtBWPQbtPOxIbVDbxT0orcYERKd63VJTF13zPcgXTvD4843L8pcsI7M6MErt8QtON6lrB9tyA== dependencies: - "@oxc-project/types" "=0.124.0" - "@rolldown/pluginutils" "1.0.0-rc.15" + "@oxc-project/types" "=0.127.0" + "@rolldown/pluginutils" "1.0.0-rc.17" optionalDependencies: - "@rolldown/binding-android-arm64" "1.0.0-rc.15" - "@rolldown/binding-darwin-arm64" "1.0.0-rc.15" - "@rolldown/binding-darwin-x64" "1.0.0-rc.15" - "@rolldown/binding-freebsd-x64" "1.0.0-rc.15" - "@rolldown/binding-linux-arm-gnueabihf" "1.0.0-rc.15" - "@rolldown/binding-linux-arm64-gnu" "1.0.0-rc.15" - "@rolldown/binding-linux-arm64-musl" "1.0.0-rc.15" - "@rolldown/binding-linux-ppc64-gnu" "1.0.0-rc.15" - "@rolldown/binding-linux-s390x-gnu" "1.0.0-rc.15" - "@rolldown/binding-linux-x64-gnu" "1.0.0-rc.15" - "@rolldown/binding-linux-x64-musl" "1.0.0-rc.15" - "@rolldown/binding-openharmony-arm64" "1.0.0-rc.15" - "@rolldown/binding-wasm32-wasi" "1.0.0-rc.15" - "@rolldown/binding-win32-arm64-msvc" "1.0.0-rc.15" - "@rolldown/binding-win32-x64-msvc" "1.0.0-rc.15" + "@rolldown/binding-android-arm64" "1.0.0-rc.17" + "@rolldown/binding-darwin-arm64" "1.0.0-rc.17" + "@rolldown/binding-darwin-x64" "1.0.0-rc.17" + "@rolldown/binding-freebsd-x64" "1.0.0-rc.17" + "@rolldown/binding-linux-arm-gnueabihf" "1.0.0-rc.17" + "@rolldown/binding-linux-arm64-gnu" "1.0.0-rc.17" + "@rolldown/binding-linux-arm64-musl" "1.0.0-rc.17" + "@rolldown/binding-linux-ppc64-gnu" "1.0.0-rc.17" + "@rolldown/binding-linux-s390x-gnu" "1.0.0-rc.17" + "@rolldown/binding-linux-x64-gnu" "1.0.0-rc.17" + "@rolldown/binding-linux-x64-musl" "1.0.0-rc.17" + "@rolldown/binding-openharmony-arm64" "1.0.0-rc.17" + "@rolldown/binding-wasm32-wasi" "1.0.0-rc.17" + "@rolldown/binding-win32-arm64-msvc" "1.0.0-rc.17" + "@rolldown/binding-win32-x64-msvc" "1.0.0-rc.17" router@^2.2.0: version "2.2.0" @@ -13353,10 +13353,10 @@ tailwindcss@4.1.18, tailwindcss@^4.1.18: resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-4.1.18.tgz#f488ba47853abdb5354daf9679d3e7791fc4f4e3" integrity sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw== -tailwindcss@4.2.2: - version "4.2.2" - resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-4.2.2.tgz#688fb0751c8ca9044e890546510a2ee817308e87" - integrity sha512-KWBIxs1Xb6NoLdMVqhbhgwZf2PGBpPEiwOqgI4pFIYbNTfBXiKYyWoTsXgBQ9WFg/OlhnvHaY+AEpW7wSmFo2Q== +tailwindcss@4.2.4: + version "4.2.4" + resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-4.2.4.tgz#f7e3090edb22d56394db4d68e6464d2628dc2aa9" + integrity sha512-HhKppgO81FQof5m6TEnuBWCZGgfRAWbaeOaGT00KOy/Pf/j6oUihdvBpA7ltCeAvZpFhW3j0PTclkxsd4IXYDA== tapable@^1.0.0: version "1.1.3" @@ -13368,6 +13368,11 @@ tapable@^2.0.0, tapable@^2.2.0, tapable@^2.3.0: resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.3.0.tgz#7e3ea6d5ca31ba8e078b560f0d83ce9a14aa8be6" integrity sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg== +tapable@^2.3.3: + version "2.3.3" + resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.3.3.tgz#5da7c9992c46038221267985ab28421a8879f160" + integrity sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A== + tar-stream@^3.0.0: version "3.1.8" resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-3.1.8.tgz#a26f5b26c34dfd4936a4f8a9e694a8f5102af13d" @@ -13390,15 +13395,7 @@ tar@^6.1.11, tar@^6.2.1: mkdirp "^1.0.3" yallist "^4.0.0" -teen_process@4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/teen_process/-/teen_process-4.1.0.tgz#e91393daa1d733fd9994b72e515fc5bc89717d7c" - integrity sha512-AN8y3MYPExB3r2mkkX9r0wEF4xPfhKOj6YvcfeIqQai+GVhTIhjjdkPvwI5CFT4z8UQ5aZWldzbJ+jNejYAdGw== - dependencies: - lodash "^4.17.21" - shell-quote "^1.8.1" - -teen_process@^4.0.4, teen_process@^4.1.0: +teen_process@4.1.1, teen_process@^4.0.4, teen_process@^4.1.0: version "4.1.1" resolved "https://registry.yarnpkg.com/teen_process/-/teen_process-4.1.1.tgz#be29cc0d1db7952b1ccbc66d1bcfd409fb030258" integrity sha512-E9gaYuVaWrvbxzZDgZ/MjWkPKqiKETBWSRy06qz1GOyKU22mI76JrxzaGbeddcHcmW8ZFXPowPv1ad3a7S+Xvg== @@ -13503,7 +13500,7 @@ tiny-invariant@^1.3.1, tiny-invariant@^1.3.3: dependencies: esm "^3.2.25" -tinyglobby@^0.2.13, tinyglobby@^0.2.15: +tinyglobby@^0.2.13, tinyglobby@^0.2.16: version "0.2.16" resolved "https://registry.yarnpkg.com/tinyglobby/-/tinyglobby-0.2.16.tgz#1c3b7eb953fce42b226bc5a1ee06428281aff3d6" integrity sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg== @@ -13754,10 +13751,10 @@ type-detect@4.0.8: resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== -type-fest@5.5.0, type-fest@^5.4.4: - version "5.5.0" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-5.5.0.tgz#78fca72f3a1f9ec964e6ae260db492b070c56f3b" - integrity sha512-PlBfpQwiUvGViBNX84Yxwjsdhd1TUlXr6zjX7eoirtCPIr08NAmxwa+fcYBTeRQxHo9YC9wwF3m9i700sHma8g== +type-fest@5.6.0, type-fest@^5.4.4: + version "5.6.0" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-5.6.0.tgz#502f7a003b7309e96a7e17052cc2ab2c7e5c7a31" + integrity sha512-8ZiHFm91orbSAe2PSAiSVBVko18pbhbiB3U9GglSzF/zCGkR+rxpHx6sEMCUm4kxY4LjDIUGgCfUMtwfZfjfUA== dependencies: tagged-tag "^1.0.0" @@ -13846,9 +13843,9 @@ typescript@^5.4.5: integrity sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw== typescript@^6.0.2: - version "6.0.2" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-6.0.2.tgz#0b1bfb15f68c64b97032f3d78abbf98bdbba501f" - integrity sha512-bGdAIrZ0wiGDo5l8c++HWtbaNCWTS4UTv7RaTH/ThVIgjkveJt83m74bBHMJkuCbslY8ixgLBVZJIOiQlQTjfQ== + version "6.0.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-6.0.3.tgz#90251dc007916e972786cb94d74d15b185577d21" + integrity sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw== typeson-registry@^1.0.0-alpha.20: version "1.0.0-alpha.39" @@ -14117,10 +14114,10 @@ utila@~0.4: resolved "https://registry.yarnpkg.com/utila/-/utila-0.4.0.tgz#8a16a05d445657a3aea5eecc5b12a4fa5379772c" integrity sha512-Z0DbgELS9/L/75wZbro8xAnT50pBVFQZ+hUEueGDU5FN51YSCYM+jdxsfCiHjwNP/4LCDD0i/graKpeBnOXKRA== -uuid@13.0.0: - version "13.0.0" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-13.0.0.tgz#263dc341b19b4d755eb8fe36b78d95a6b65707e8" - integrity sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w== +uuid@14.0.0: + version "14.0.0" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-14.0.0.tgz#0af883220163d264ffe0c084f6b8a89b9666966d" + integrity sha512-Qo+uWgilfSmAhXCMav1uYFynlQO7fMFiMVZsQqZRMIXp0O7rR7qjkj+cPvBHLgBqi960QCoo/PH2/6ZtVqKvrg== uuid@^9.0.0: version "9.0.1" @@ -14213,15 +14210,15 @@ vite-plugin-wasm@^3.6.0: integrity sha512-mL/QPziiIA4RAA6DkaZZzOstdwbW5jO4Vz7Zenj0wieKWBlNvIvX5L5ljum9lcUX0ShNfBgCNLKTjNkRVVqcsw== vite@^8.0.8: - version "8.0.8" - resolved "https://registry.yarnpkg.com/vite/-/vite-8.0.8.tgz#4e26a9bba77c4b27a00b6b10100a7dab48d546a3" - integrity sha512-dbU7/iLVa8KZALJyLOBOQ88nOXtNG8vxKuOT4I2mD+Ya70KPceF4IAmDsmU0h1Qsn5bPrvsY9HJstCRh3hG6Uw== + version "8.0.10" + resolved "https://registry.yarnpkg.com/vite/-/vite-8.0.10.tgz#fb31868526ec874101fac084172a2cdc6776319b" + integrity sha512-rZuUu9j6J5uotLDs+cAA4O5H4K1SfPliUlQwqa6YEwSrWDZzP4rhm00oJR5snMewjxF5V/K3D4kctsUTsIU9Mw== dependencies: lightningcss "^1.32.0" picomatch "^4.0.4" - postcss "^8.5.8" - rolldown "1.0.0-rc.15" - tinyglobby "^0.2.15" + postcss "^8.5.10" + rolldown "1.0.0-rc.17" + tinyglobby "^0.2.16" optionalDependencies: fsevents "~2.3.3" @@ -14702,9 +14699,9 @@ zlib@^1.0.5: integrity sha512-40fpE2II+Cd3k8HWTWONfeKE2jL+P42iWJ1zzps5W51qcTsOUKM5Q5m2PFb0CLxlmFAaUuUdJGc3OfZy947v0w== zustand@^5.0.0: - version "5.0.12" - resolved "https://registry.yarnpkg.com/zustand/-/zustand-5.0.12.tgz#ed36f647aa89965c4019b671dfc23ef6c6e3af8c" - integrity sha512-i77ae3aZq4dhMlRhJVCYgMLKuSiZAaUPAct2AksxQ+gOtimhGMdXljRT21P5BNpeT4kXlLIckvkPM029OljD7g== + version "5.0.11" + resolved "https://registry.yarnpkg.com/zustand/-/zustand-5.0.11.tgz#99f912e590de1ca9ce6c6d1cab6cdb1f034ab494" + integrity sha512-fdZY+dk7zn/vbWNCYmzZULHRrss0jx5pPFiOuMZ/5HJN6Yv3u+1Wswy/4MpZEkEGhtNH+pwxZB8OKgUBPzYAGg== zustand@^5.0.9: version "5.0.10"