From 97510a86198776f213f88a65b6aeb0f6cacb7a14 Mon Sep 17 00:00:00 2001 From: binarybaron <86064887+binarybaron@users.noreply.github.com> Date: Mon, 23 Sep 2024 03:31:24 +0600 Subject: [PATCH] refactor(gui): Swap state stepper to use Tauri events (#77) Previously we used the data we fetched via the rpc (`GetSwapInfo` call, saved in redux in `rpc.swapInfos`) to decide what to display in the state stepper to the user. The state stepper is displayed at the bottom of the `SwapDialog`. However, we are moving away from our depedence on periodic rpc calls and towards relying more and more on the events we receive from the Host (from Tauri). Our goal is to rely solely on the Tauri events for everything displayed about the currently running swap. This PR includes the following changes: - refactor the `SwapStateStepper` such that it relies only on the Tauri events - emit two new Tauri events (`EncryptedSignatureSent`, `CancelTimelockExpired`) in the state machine - correctly emit `BtcRefunded` Tauri event after Bitcoin refund transaction is published - differentiate between `"Waiting for them to redeem the Bitcoin"` and `"Revealing encrypted signature to the other party"` on the `SwapStatePage` (content displayed in the center of the `SwapDialog`) --- src-gui/index.html | 5 +- src-gui/package.json | 7 +- src-gui/src/models/storeModel.ts | 12 +- src-gui/src/models/tauriModelExt.ts | 4 +- .../components/modal/swap/SwapDialog.tsx | 5 +- .../modal/swap/SwapStateStepper.tsx | 270 ++++++------- .../modal/swap/pages/SwapStatePage.tsx | 10 +- .../swap/pages/exited/ProcessExitedPage.tsx | 3 +- .../in_progress/CancelTimelockExpiredPage.tsx | 5 + .../EncryptedSignatureSentPage.tsx | 7 + .../swap/pages/in_progress/XmrLockedPage.tsx | 2 +- .../other/ScrollablePaperTextBox.tsx | 18 +- src-gui/src/renderer/index.ejs | 26 -- src-gui/src/store/config.ts | 20 + src-gui/src/store/features/rpcSlice.ts | 6 - src-gui/yarn.lock | 382 +++++++++++++++++- src-tauri/src/lib.rs | 8 +- swap/src/asb/event_loop.rs | 1 - swap/src/cli/api/tauri_bindings.rs | 2 + swap/src/protocol/bob/state.rs | 8 +- swap/src/protocol/bob/swap.rs | 13 +- 21 files changed, 608 insertions(+), 206 deletions(-) create mode 100644 src-gui/src/renderer/components/modal/swap/pages/in_progress/CancelTimelockExpiredPage.tsx create mode 100644 src-gui/src/renderer/components/modal/swap/pages/in_progress/EncryptedSignatureSentPage.tsx delete mode 100644 src-gui/src/renderer/index.ejs diff --git a/src-gui/index.html b/src-gui/index.html index 4f8025605..524e9b817 100644 --- a/src-gui/index.html +++ b/src-gui/index.html @@ -4,7 +4,10 @@ - Tauri + React + Typescript + diff --git a/src-gui/package.json b/src-gui/package.json index 42e8247a4..32d0a1a3a 100644 --- a/src-gui/package.json +++ b/src-gui/package.json @@ -6,6 +6,8 @@ "scripts": { "check-bindings": "typeshare --lang=typescript --output-file __temp_bindings.ts ../swap/src && dprint fmt __temp_bindings.ts && diff -wbB __temp_bindings.ts ./src/models/tauriModel.ts && rm __temp_bindings.ts", "gen-bindings": "typeshare --lang=typescript --output-file ./src/models/tauriModel.ts ../swap/src && dprint fmt ./src/models/tauriModel.ts", + "test": "vitest", + "test:ui": "vitest --ui", "dev": "vite", "prebuild": "yarn run gen-bindings", "build": "vite build --mode dev", @@ -38,6 +40,8 @@ "devDependencies": { "@eslint/js": "^9.9.0", "@tauri-apps/cli": ">=2.0.0-beta.0", + "@testing-library/react": "^16.0.1", + "@testing-library/user-event": "^14.5.2", "@types/humanize-duration": "^3.27.4", "@types/lodash": "^4.17.6", "@types/node": "^20.14.10", @@ -53,6 +57,7 @@ "typescript-eslint": "^8.1.0", "vite": "^5.3.1", "vite-plugin-watch": "^0.3.1", - "vite-tsconfig-paths": "^4.3.2" + "vite-tsconfig-paths": "^4.3.2", + "vitest": "^2.1.1" } } diff --git a/src-gui/src/models/storeModel.ts b/src-gui/src/models/storeModel.ts index 72edf0b3b..56e14588b 100644 --- a/src-gui/src/models/storeModel.ts +++ b/src-gui/src/models/storeModel.ts @@ -1,12 +1,14 @@ import { CliLog, SwapSpawnType } from "./cliModel"; import { TauriSwapProgressEvent } from "./tauriModel"; +export type SwapState = { + curr: TauriSwapProgressEvent; + prev: TauriSwapProgressEvent | null; + swapId: string; +}; + export interface SwapSlice { - state: { - curr: TauriSwapProgressEvent; - prev: TauriSwapProgressEvent | null; - swapId: string; - } | null; + state: SwapState | null; logs: CliLog[]; spawnType: SwapSpawnType | null; } diff --git a/src-gui/src/models/tauriModelExt.ts b/src-gui/src/models/tauriModelExt.ts index fe24da11f..70cbbd366 100644 --- a/src-gui/src/models/tauriModelExt.ts +++ b/src-gui/src/models/tauriModelExt.ts @@ -4,8 +4,10 @@ import { TauriSwapProgressEvent, } from "./tauriModel"; +export type TauriSwapProgressEventType = TauriSwapProgressEvent["type"]; + export type TauriSwapProgressEventContent< - T extends TauriSwapProgressEvent["type"], + T extends TauriSwapProgressEventType, > = Extract["content"]; // See /swap/src/protocol/bob/state.rs#L57 diff --git a/src-gui/src/renderer/components/modal/swap/SwapDialog.tsx b/src-gui/src/renderer/components/modal/swap/SwapDialog.tsx index d6931403b..bf1a5f29a 100644 --- a/src-gui/src/renderer/components/modal/swap/SwapDialog.tsx +++ b/src-gui/src/renderer/components/modal/swap/SwapDialog.tsx @@ -33,6 +33,7 @@ export default function SwapDialog({ const classes = useStyles(); const swap = useAppSelector((state) => state.swap); const isSwapRunning = useIsSwapRunning(); + const [debug, setDebug] = useState(false); const [openSuspendAlert, setOpenSuspendAlert] = useState(false); const dispatch = useAppDispatch(); @@ -63,7 +64,7 @@ export default function SwapDialog({ ) : ( <> - + )} @@ -76,7 +77,7 @@ export default function SwapDialog({ color="primary" variant="contained" onClick={onCancel} - disabled={isSwapRunning} + disabled={isSwapRunning || swap.state === null} > Done diff --git a/src-gui/src/renderer/components/modal/swap/SwapStateStepper.tsx b/src-gui/src/renderer/components/modal/swap/SwapStateStepper.tsx index c9bcf6c97..873d6016a 100644 --- a/src-gui/src/renderer/components/modal/swap/SwapStateStepper.tsx +++ b/src-gui/src/renderer/components/modal/swap/SwapStateStepper.tsx @@ -1,175 +1,171 @@ import { Step, StepLabel, Stepper, Typography } from "@material-ui/core"; -import { SwapSpawnType } from "models/cliModel"; -import { BobStateName } from "models/tauriModelExt"; -import { - useActiveSwapInfo, - useAppSelector, - useIsSwapRunning, -} from "store/hooks"; -import { exhaustiveGuard } from "utils/typescriptUtils"; +import { SwapState } from "models/storeModel"; +import { useAppSelector } from "store/hooks"; export enum PathType { HAPPY_PATH = "happy path", UNHAPPY_PATH = "unhappy path", } -// TODO: Consider using a TauriProgressEvent here instead of BobStateName -// TauriProgressEvent is always up to date, BobStateName is not (needs to be periodically fetched) +type PathStep = [type: PathType, step: number, isError: boolean]; + +/** + * Determines the current step in the swap process based on the previous and latest state. + * @param prevState - The previous state of the swap process (null if it's the initial state) + * @param latestState - The latest state of the swap process + * @returns A tuple containing [PathType, activeStep, errorFlag] + */ function getActiveStep( - stateName: BobStateName | null, - processExited: boolean, -): [PathType, number, boolean] { - switch (stateName) { - /// // Happy Path - // Step: 0 (Waiting for Bitcoin lock tx to be published) - case null: - return [PathType.HAPPY_PATH, 0, false]; - case BobStateName.Started: - case BobStateName.SwapSetupCompleted: - return [PathType.HAPPY_PATH, 0, processExited]; - - // Step: 1 (Waiting for Bitcoin Lock confirmation and XMR Lock Publication) - // We have locked the Bitcoin and are waiting for the other party to lock their XMR - case BobStateName.BtcLocked: - return [PathType.HAPPY_PATH, 1, processExited]; - - // Step: 2 (Waiting for XMR Lock confirmation) - // We have locked the Bitcoin and the other party has locked their XMR - case BobStateName.XmrLockProofReceived: - return [PathType.HAPPY_PATH, 1, processExited]; - - // Step: 3 (Sending Encrypted Signature and waiting for Bitcoin Redemption) - // The XMR lock transaction has been confirmed - // We now need to send the encrypted signature to the other party and wait for them to redeem the Bitcoin - case BobStateName.XmrLocked: - case BobStateName.EncSigSent: - return [PathType.HAPPY_PATH, 2, processExited]; - - // Step: 4 (Waiting for XMR Redemption) - case BobStateName.BtcRedeemed: - return [PathType.HAPPY_PATH, 3, processExited]; - - // Step: 4 (Completed) (Swap completed, XMR redeemed) - case BobStateName.XmrRedeemed: + state: SwapState | null +): PathStep { + // In case we cannot infer a correct step from the state + function fallbackStep(reason: string) { + console.error(`Unable to choose correct stepper type (reason: ${reason}, state: ${JSON.stringify(state)}`); + return [PathType.HAPPY_PATH, 0, true] as PathStep; + } + + if (state === null) { + return [PathType.HAPPY_PATH, 0, false]; + } + + const prevState = state.prev; + const isReleased = state.curr.type === "Released"; + + // If the swap is released we use the previous state to display the correct step + const latestState = isReleased ? prevState : state.curr; + + // If the swap is released but we do not have a previous state we fallback + if (latestState === null) { + return fallbackStep("Swap has been released but we do not have a previous state saved to display"); + } + + // This should really never happen. For this statement to be true, the host has to submit a "Released" event twice + if(latestState.type === "Released") { + return fallbackStep("Both the current and previous states are both of type 'Released'."); + } + + switch (latestState.type) { + // Step 0: Initializing the swap + // These states represent the very beginning of the swap process + // No funds have been locked + case "Initiated": + case "ReceivedQuote": + case "WaitingForBtcDeposit": + case "Started": + return [PathType.HAPPY_PATH, 0, isReleased]; + + // Step 1: Waiting for Bitcoin lock confirmation + // Bitcoin has been locked, waiting for the counterparty to lock their XMR + case "BtcLockTxInMempool": + return [PathType.HAPPY_PATH, 1, isReleased]; + + // Still Step 1: Both Bitcoin and XMR have been locked, waiting for Monero lock to be confirmed + case "XmrLockTxInMempool": + return [PathType.HAPPY_PATH, 1, isReleased]; + + // Step 2: Waiting for encrypted signature to be sent to Alice + // and for Alice to redeem the Bitcoin + case "XmrLocked": + case "EncryptedSignatureSent": + return [PathType.HAPPY_PATH, 2, isReleased]; + + // Step 3: Waiting for XMR redemption + // Bitcoin has been redeemed by Alice, now waiting for us to redeem Monero + case "BtcRedeemed": + return [PathType.HAPPY_PATH, 3, isReleased]; + + // Step 4: Swap completed successfully + // XMR redemption transaction is in mempool, swap is essentially complete + case "XmrRedeemInMempool": return [PathType.HAPPY_PATH, 4, false]; // Edge Case of Happy Path where the swap is safely aborted. We "fail" at the first step. - case BobStateName.SafelyAborted: - return [PathType.HAPPY_PATH, 0, true]; + // TODO: There's no equivalent for this with the Tauri events + // case BobStateName.SafelyAborted: + // return [PathType.HAPPY_PATH, 0, true]; + + // Unhappy Path States - // // Unhappy Path - // Step: 1 (Cancelling swap, checking if cancel transaction has been published already by the other party) - case BobStateName.CancelTimelockExpired: - return [PathType.UNHAPPY_PATH, 0, processExited]; + // Step 1: Cancel timelock has expired. Waiting for cancel transaction to be published + case "CancelTimelockExpired": + return [PathType.UNHAPPY_PATH, 0, isReleased]; - // Step: 2 (Attempt to publish the Bitcoin refund transaction) - case BobStateName.BtcCancelled: - return [PathType.UNHAPPY_PATH, 1, processExited]; + // Step 2: Swap has been cancelled. Waiting for Bitcoin to be refunded + case "BtcCancelled": + return [PathType.UNHAPPY_PATH, 1, isReleased]; - // Step: 2 (Completed) (Bitcoin refunded) - case BobStateName.BtcRefunded: + // Step 2: Swap cancelled and Bitcoin refunded successfully + case "BtcRefunded": return [PathType.UNHAPPY_PATH, 2, false]; - // Step: 2 (We failed to publish the Bitcoin refund transaction) - // We failed to publish the Bitcoin refund transaction because the timelock has expired. - // We will be punished. Nothing we can do about it now. - case BobStateName.BtcPunished: + // Step 2 (Failed): Failed to refund Bitcoin + // The timelock expired before we could refund, resulting in punishment + case "BtcPunished": + return [PathType.UNHAPPY_PATH, 1, true]; + + // Attempting cooperative redemption after punishment + case "AttemptingCooperativeRedeem": + case "CooperativeRedeemAccepted": + return [PathType.UNHAPPY_PATH, 1, isReleased]; + case "CooperativeRedeemRejected": return [PathType.UNHAPPY_PATH, 1, true]; default: - return exhaustiveGuard(stateName); + return fallbackStep("No step is assigned to the current state"); + // TODO: Make this guard work. It should force the compiler to check if we have covered all possible cases. + // return exhaustiveGuard(latestState.type); } } -function HappyPathStepper({ +function SwapStepper({ + steps, activeStep, error, }: { + steps: Array<{ label: string; duration: string }>; activeStep: number; error: boolean; }) { return ( - - ~12min} - error={error && activeStep === 0} - > - Locking your BTC - - - - ~18min} - error={error && activeStep === 1} - > - They lock their XMR - - - - ~2min} - error={error && activeStep === 2} - > - They redeem the BTC - - - - ~2min} - error={error && activeStep === 3} - > - Redeeming your XMR - - + {steps.map((step, index) => ( + + {step.duration} + } + error={error && activeStep === index} + > + {step.label} + + + ))} ); } -function UnhappyPathStepper({ - activeStep, - error, -}: { - activeStep: number; - error: boolean; -}) { - return ( - - - ~20min} - error={error && activeStep === 0} - > - Cancelling swap - - - - ~20min} - error={error && activeStep === 1} - > - Refunding your BTC - - - - ); -} +const HAPPY_PATH_STEP_LABELS = [ + { label: "Locking your BTC", duration: "~12min" }, + { label: "They lock their XMR", duration: "~18min" }, + { label: "They redeem the BTC", duration: "~2min" }, + { label: "Redeeming your XMR", duration: "~2min" }, +]; -export default function SwapStateStepper() { - // TODO: There's no equivalent of this with Tauri yet. - const currentSwapSpawnType = useAppSelector((s) => s.swap.spawnType); +const UNHAPPY_PATH_STEP_LABELS = [ + { label: "Cancelling swap", duration: "~1min" }, + { label: "Attempting recovery", duration: "~5min" }, +]; - const stateName = useActiveSwapInfo()?.state_name ?? null; - const processExited = !useIsSwapRunning(); - const [pathType, activeStep, error] = getActiveStep(stateName, processExited); +export default function SwapStateStepper({ + state, +}: { + state: SwapState | null +}) { + const [pathType, activeStep, error] = getActiveStep(state); - // TODO: Fix this to work with Tauri - // If the current swap is being manually cancelled and refund, we want to show the unhappy path even though the current state is not a "unhappy" state - if (currentSwapSpawnType === SwapSpawnType.CANCEL_REFUND) { - return ; - } + const steps = + pathType === PathType.HAPPY_PATH + ? HAPPY_PATH_STEP_LABELS + : UNHAPPY_PATH_STEP_LABELS; - if (pathType === PathType.HAPPY_PATH) { - return ; - } - return ; + return ; } diff --git a/src-gui/src/renderer/components/modal/swap/pages/SwapStatePage.tsx b/src-gui/src/renderer/components/modal/swap/pages/SwapStatePage.tsx index f779d3d85..a666de653 100644 --- a/src-gui/src/renderer/components/modal/swap/pages/SwapStatePage.tsx +++ b/src-gui/src/renderer/components/modal/swap/pages/SwapStatePage.tsx @@ -1,5 +1,5 @@ import { Box } from "@material-ui/core"; -import { SwapSlice } from "models/storeModel"; +import { SwapSlice, SwapState } from "models/storeModel"; import CircularProgressWithSubtitle from "../CircularProgressWithSubtitle"; import BitcoinPunishedPage from "./done/BitcoinPunishedPage"; import BitcoinRefundedPage from "./done/BitcoinRefundedPage"; @@ -8,6 +8,8 @@ import ProcessExitedPage from "./exited/ProcessExitedPage"; import BitcoinCancelledPage from "./in_progress/BitcoinCancelledPage"; import BitcoinLockTxInMempoolPage from "./in_progress/BitcoinLockTxInMempoolPage"; import BitcoinRedeemedPage from "./in_progress/BitcoinRedeemedPage"; +import CancelTimelockExpiredPage from "./in_progress/CancelTimelockExpiredPage"; +import EncryptedSignatureSentPage from "./in_progress/EncryptedSignatureSentPage"; import ReceivedQuotePage from "./in_progress/ReceivedQuotePage"; import StartedPage from "./in_progress/StartedPage"; import XmrLockedPage from "./in_progress/XmrLockedPage"; @@ -19,7 +21,7 @@ import WaitingForBitcoinDepositPage from "./init/WaitingForBitcoinDepositPage"; export default function SwapStatePage({ state, }: { - state: SwapSlice["state"]; + state: SwapState | null }) { // TODO: Reimplement this using tauri events /* @@ -50,10 +52,14 @@ export default function SwapStatePage({ return ; case "XmrLocked": return ; + case "EncryptedSignatureSent": + return ; case "BtcRedeemed": return ; case "XmrRedeemInMempool": return ; + case "CancelTimelockExpired": + return ; case "BtcCancelled": return ; case "BtcRefunded": diff --git a/src-gui/src/renderer/components/modal/swap/pages/exited/ProcessExitedPage.tsx b/src-gui/src/renderer/components/modal/swap/pages/exited/ProcessExitedPage.tsx index 6835a6937..ca37986e1 100644 --- a/src-gui/src/renderer/components/modal/swap/pages/exited/ProcessExitedPage.tsx +++ b/src-gui/src/renderer/components/modal/swap/pages/exited/ProcessExitedPage.tsx @@ -14,7 +14,8 @@ export default function ProcessExitedPage({ prevState != null && (prevState.type === "XmrRedeemInMempool" || prevState.type === "BtcRefunded" || - prevState.type === "BtcPunished") + prevState.type === "BtcPunished" || + prevState.type === "CooperativeRedeemRejected") ) { return ( ; +} diff --git a/src-gui/src/renderer/components/modal/swap/pages/in_progress/EncryptedSignatureSentPage.tsx b/src-gui/src/renderer/components/modal/swap/pages/in_progress/EncryptedSignatureSentPage.tsx new file mode 100644 index 000000000..01b01f1b2 --- /dev/null +++ b/src-gui/src/renderer/components/modal/swap/pages/in_progress/EncryptedSignatureSentPage.tsx @@ -0,0 +1,7 @@ +import CircularProgressWithSubtitle from "../../CircularProgressWithSubtitle"; + +export default function EncryptedSignatureSentPage() { + return ( + + ); +} diff --git a/src-gui/src/renderer/components/modal/swap/pages/in_progress/XmrLockedPage.tsx b/src-gui/src/renderer/components/modal/swap/pages/in_progress/XmrLockedPage.tsx index 881d87173..086126ac5 100644 --- a/src-gui/src/renderer/components/modal/swap/pages/in_progress/XmrLockedPage.tsx +++ b/src-gui/src/renderer/components/modal/swap/pages/in_progress/XmrLockedPage.tsx @@ -2,6 +2,6 @@ import CircularProgressWithSubtitle from "../../CircularProgressWithSubtitle"; export default function XmrLockedPage() { return ( - + ); } diff --git a/src-gui/src/renderer/components/other/ScrollablePaperTextBox.tsx b/src-gui/src/renderer/components/other/ScrollablePaperTextBox.tsx index 731e2d295..b828b7c81 100644 --- a/src-gui/src/renderer/components/other/ScrollablePaperTextBox.tsx +++ b/src-gui/src/renderer/components/other/ScrollablePaperTextBox.tsx @@ -12,16 +12,16 @@ export default function ScrollablePaperTextBox({ rows, title, copyValue, - searchQuery, - setSearchQuery, - minHeight, + searchQuery = null, + setSearchQuery = null, + minHeight = MIN_HEIGHT, }: { rows: ReactNode[]; title: string; copyValue: string; - searchQuery?: string; - setSearchQuery?: (query: string) => void; - minHeight?: string; + searchQuery: string | null; + setSearchQuery?: ((query: string) => void) | null; + minHeight: string; }) { const virtuaEl = useRef(null); @@ -82,9 +82,3 @@ export default function ScrollablePaperTextBox({ ); } - -ScrollablePaperTextBox.defaultProps = { - searchQuery: undefined, - setSearchQuery: undefined, - minHeight: MIN_HEIGHT, -}; diff --git a/src-gui/src/renderer/index.ejs b/src-gui/src/renderer/index.ejs deleted file mode 100644 index 146f4f799..000000000 --- a/src-gui/src/renderer/index.ejs +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - -
- - diff --git a/src-gui/src/store/config.ts b/src-gui/src/store/config.ts index 03eeb6f3c..b0db02f45 100644 --- a/src-gui/src/store/config.ts +++ b/src-gui/src/store/config.ts @@ -1,9 +1,29 @@ import { ExtendedProviderStatus } from "models/apiModel"; +import { splitPeerIdFromMultiAddress } from "utils/parseUtils"; export const isTestnet = () => true; export const isDevelopment = true; export function getStubTestnetProvider(): ExtendedProviderStatus | null { + const stubProviderAddress = process.env.TESTNET_STUB_PROVIDER_ADDRESS; + + if(stubProviderAddress != null) { + try { + const [multiAddr, peerId] = splitPeerIdFromMultiAddress(stubProviderAddress); + + return { + multiAddr, + testnet: true, + peerId, + maxSwapAmount: 0, + minSwapAmount: 0, + price: 0, + }; + }catch { + return null; + } + } + return null; } diff --git a/src-gui/src/store/features/rpcSlice.ts b/src-gui/src/store/features/rpcSlice.ts index b3ea79a4e..4bebd3fec 100644 --- a/src-gui/src/store/features/rpcSlice.ts +++ b/src-gui/src/store/features/rpcSlice.ts @@ -18,9 +18,6 @@ interface State { swapId: string; keys: MoneroRecoveryResponse; } | null; - moneroWallet: { - isSyncing: boolean; - }; moneroWalletRpc: { // TODO: Reimplement this using Tauri updateState: false; @@ -41,9 +38,6 @@ const initialState: RPCSlice = { rendezvous_discovered_sellers: [], swapInfos: {}, moneroRecovery: null, - moneroWallet: { - isSyncing: false, - }, moneroWalletRpc: { updateState: false, }, diff --git a/src-gui/yarn.lock b/src-gui/yarn.lock index 2de964bf7..43825c5c1 100644 --- a/src-gui/yarn.lock +++ b/src-gui/yarn.lock @@ -178,6 +178,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.24.7" +"@babel/runtime@^7.12.5": + version "7.25.6" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.25.6.tgz#9afc3289f7184d8d7f98b099884c26317b9264d2" + integrity sha512-VBj9MYyDb9tuLq7yzqjgzt6Q+IBQLrGZfdjOekyEirZPHxXWoTSGUTMrpsfi58Up73d13NfYLv8HT9vmznjzhQ== + dependencies: + regenerator-runtime "^0.14.0" + "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.4", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.3", "@babel/runtime@^7.8.7": version "7.24.7" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.24.7.tgz#f4f0d5530e8dbdf59b3451b9b3e594b6ba082e12" @@ -419,6 +426,11 @@ resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32" integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg== +"@jridgewell/sourcemap-codec@^1.5.0": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz#3188bcb273a414b0d215fd22a58540b989b9409a" + integrity sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ== + "@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25": version "0.3.25" resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz#15f190e98895f3fc23276ee14bc76b675c2e50f0" @@ -550,81 +562,161 @@ resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.18.0.tgz#bbd0e616b2078cd2d68afc9824d1fadb2f2ffd27" integrity sha512-Tya6xypR10giZV1XzxmH5wr25VcZSncG0pZIjfePT0OVBvqNEurzValetGNarVrGiq66EBVAFn15iYX4w6FKgQ== +"@rollup/rollup-android-arm-eabi@4.22.2": + version "4.22.2" + resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.22.2.tgz#4e0c4c462692ecb7ae2b008f25af4cced05ac4f9" + integrity sha512-8Ao+EDmTPjZ1ZBABc1ohN7Ylx7UIYcjReZinigedTOnGFhIctyGPxY2II+hJ6gD2/vkDKZTyQ0e7++kwv6wDrw== + "@rollup/rollup-android-arm64@4.18.0": version "4.18.0" resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.18.0.tgz#97255ef6384c5f73f4800c0de91f5f6518e21203" integrity sha512-avCea0RAP03lTsDhEyfy+hpfr85KfyTctMADqHVhLAF3MlIkq83CP8UfAHUssgXTYd+6er6PaAhx/QGv4L1EiA== +"@rollup/rollup-android-arm64@4.22.2": + version "4.22.2" + resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.22.2.tgz#d97ed02a950061adc2056d6d2d6df8f05d877ae9" + integrity sha512-I+B1v0a4iqdS9DvYt1RJZ3W+Oh9EVWjbY6gp79aAYipIbxSLEoQtFQlZEnUuwhDXCqMxJ3hluxKAdPD+GiluFQ== + "@rollup/rollup-darwin-arm64@4.18.0": version "4.18.0" resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.18.0.tgz#b6dd74e117510dfe94541646067b0545b42ff096" integrity sha512-IWfdwU7KDSm07Ty0PuA/W2JYoZ4iTj3TUQjkVsO/6U+4I1jN5lcR71ZEvRh52sDOERdnNhhHU57UITXz5jC1/w== +"@rollup/rollup-darwin-arm64@4.22.2": + version "4.22.2" + resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.22.2.tgz#06dec35316de9fe433d66c849ecc056e221ba422" + integrity sha512-BTHO7rR+LC67OP7I8N8GvdvnQqzFujJYWo7qCQ8fGdQcb8Gn6EQY+K1P+daQLnDCuWKbZ+gHAQZuKiQkXkqIYg== + "@rollup/rollup-darwin-x64@4.18.0": version "4.18.0" resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.18.0.tgz#e07d76de1cec987673e7f3d48ccb8e106d42c05c" integrity sha512-n2LMsUz7Ynu7DoQrSQkBf8iNrjOGyPLrdSg802vk6XT3FtsgX6JbE8IHRvposskFm9SNxzkLYGSq9QdpLYpRNA== +"@rollup/rollup-darwin-x64@4.22.2": + version "4.22.2" + resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.22.2.tgz#22ee27a0ccfdc045c2a37f6980351329516ce119" + integrity sha512-1esGwDNFe2lov4I6GsEeYaAMHwkqk0IbuGH7gXGdBmd/EP9QddJJvTtTF/jv+7R8ZTYPqwcdLpMTxK8ytP6k6Q== + "@rollup/rollup-linux-arm-gnueabihf@4.18.0": version "4.18.0" resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.18.0.tgz#9f1a6d218b560c9d75185af4b8bb42f9f24736b8" integrity sha512-C/zbRYRXFjWvz9Z4haRxcTdnkPt1BtCkz+7RtBSuNmKzMzp3ZxdM28Mpccn6pt28/UWUCTXa+b0Mx1k3g6NOMA== +"@rollup/rollup-linux-arm-gnueabihf@4.22.2": + version "4.22.2" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.22.2.tgz#d86df2d8c600ebdd7251110a3357c53e0a583ace" + integrity sha512-GBHuY07x96OTEM3OQLNaUSUwrOhdMea/LDmlFHi/HMonrgF6jcFrrFFwJhhe84XtA1oK/Qh4yFS+VMREf6dobg== + "@rollup/rollup-linux-arm-musleabihf@4.18.0": version "4.18.0" resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.18.0.tgz#53618b92e6ffb642c7b620e6e528446511330549" integrity sha512-l3m9ewPgjQSXrUMHg93vt0hYCGnrMOcUpTz6FLtbwljo2HluS4zTXFy2571YQbisTnfTKPZ01u/ukJdQTLGh9A== +"@rollup/rollup-linux-arm-musleabihf@4.22.2": + version "4.22.2" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.22.2.tgz#a8b7b6a805356c8bd0409e4c5f56664d80a50aaa" + integrity sha512-Dbfa9Sc1G1lWxop0gNguXOfGhaXQWAGhZUcqA0Vs6CnJq8JW/YOw/KvyGtQFmz4yDr0H4v9X248SM7bizYj4yQ== + "@rollup/rollup-linux-arm64-gnu@4.18.0": version "4.18.0" resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.18.0.tgz#99a7ba5e719d4f053761a698f7b52291cefba577" integrity sha512-rJ5D47d8WD7J+7STKdCUAgmQk49xuFrRi9pZkWoRD1UeSMakbcepWXPF8ycChBoAqs1pb2wzvbY6Q33WmN2ftw== +"@rollup/rollup-linux-arm64-gnu@4.22.2": + version "4.22.2" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.22.2.tgz#766064021d2bfc42f13f4653f8870a9b8bbdc31d" + integrity sha512-Z1YpgBvFYhZIyBW5BoopwSg+t7yqEhs5HCei4JbsaXnhz/eZehT18DaXl957aaE9QK7TRGFryCAtStZywcQe1A== + "@rollup/rollup-linux-arm64-musl@4.18.0": version "4.18.0" resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.18.0.tgz#f53db99a45d9bc00ce94db8a35efa7c3c144a58c" integrity sha512-be6Yx37b24ZwxQ+wOQXXLZqpq4jTckJhtGlWGZs68TgdKXJgw54lUUoFYrg6Zs/kjzAQwEwYbp8JxZVzZLRepQ== +"@rollup/rollup-linux-arm64-musl@4.22.2": + version "4.22.2" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.22.2.tgz#490f49236102b97738d9406eaf5cd8d9dad35c15" + integrity sha512-66Zszr7i/JaQ0u/lefcfaAw16wh3oT72vSqubIMQqWzOg85bGCPhoeykG/cC5uvMzH80DQa2L539IqKht6twVA== + "@rollup/rollup-linux-powerpc64le-gnu@4.18.0": version "4.18.0" resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.18.0.tgz#cbb0837408fe081ce3435cf3730e090febafc9bf" integrity sha512-hNVMQK+qrA9Todu9+wqrXOHxFiD5YmdEi3paj6vP02Kx1hjd2LLYR2eaN7DsEshg09+9uzWi2W18MJDlG0cxJA== +"@rollup/rollup-linux-powerpc64le-gnu@4.22.2": + version "4.22.2" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.22.2.tgz#03a67f1476dd80f115ce35bc9b0d03c50c16679d" + integrity sha512-HpJCMnlMTfEhwo19bajvdraQMcAq3FX08QDx3OfQgb+414xZhKNf3jNvLFYKbbDSGBBrQh5yNwWZrdK0g0pokg== + "@rollup/rollup-linux-riscv64-gnu@4.18.0": version "4.18.0" resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.18.0.tgz#8ed09c1d1262ada4c38d791a28ae0fea28b80cc9" integrity sha512-ROCM7i+m1NfdrsmvwSzoxp9HFtmKGHEqu5NNDiZWQtXLA8S5HBCkVvKAxJ8U+CVctHwV2Gb5VUaK7UAkzhDjlg== +"@rollup/rollup-linux-riscv64-gnu@4.22.2": + version "4.22.2" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.22.2.tgz#d86e9b7b5b242652cd691c46d1939130c35cb68d" + integrity sha512-/egzQzbOSRef2vYCINKITGrlwkzP7uXRnL+xU2j75kDVp3iPdcF0TIlfwTRF8woBZllhk3QaxNOEj2Ogh3t9hg== + "@rollup/rollup-linux-s390x-gnu@4.18.0": version "4.18.0" resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.18.0.tgz#938138d3c8e0c96f022252a28441dcfb17afd7ec" integrity sha512-0UyyRHyDN42QL+NbqevXIIUnKA47A+45WyasO+y2bGJ1mhQrfrtXUpTxCOrfxCR4esV3/RLYyucGVPiUsO8xjg== +"@rollup/rollup-linux-s390x-gnu@4.22.2": + version "4.22.2" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.22.2.tgz#c8fca373bec6df8550b31b3dbb56e2b241bc8718" + integrity sha512-qgYbOEbrPfEkH/OnUJd1/q4s89FvNJQIUldx8X2F/UM5sEbtkqZpf2s0yly2jSCKr1zUUOY1hnTP2J1WOzMAdA== + "@rollup/rollup-linux-x64-gnu@4.18.0": version "4.18.0" resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.18.0.tgz#1a7481137a54740bee1ded4ae5752450f155d942" integrity sha512-xuglR2rBVHA5UsI8h8UbX4VJ470PtGCf5Vpswh7p2ukaqBGFTnsfzxUBetoWBWymHMxbIG0Cmx7Y9qDZzr648w== +"@rollup/rollup-linux-x64-gnu@4.22.2": + version "4.22.2" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.22.2.tgz#be182ef761c9b0147496e647ace44fd1b912344f" + integrity sha512-a0lkvNhFLhf+w7A95XeBqGQaG0KfS3hPFJnz1uraSdUe/XImkp/Psq0Ca0/UdD5IEAGoENVmnYrzSC9Y2a2uKQ== + "@rollup/rollup-linux-x64-musl@4.18.0": version "4.18.0" resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.18.0.tgz#f1186afc601ac4f4fc25fac4ca15ecbee3a1874d" integrity sha512-LKaqQL9osY/ir2geuLVvRRs+utWUNilzdE90TpyoX0eNqPzWjRm14oMEE+YLve4k/NAqCdPkGYDaDF5Sw+xBfg== +"@rollup/rollup-linux-x64-musl@4.22.2": + version "4.22.2" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.22.2.tgz#c280202d5b54d04f1e2b810359fe73c4973e8b72" + integrity sha512-sSWBVZgzwtsuG9Dxi9kjYOUu/wKW+jrbzj4Cclabqnfkot8Z3VEHcIgyenA3lLn/Fu11uDviWjhctulkhEO60g== + "@rollup/rollup-win32-arm64-msvc@4.18.0": version "4.18.0" resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.18.0.tgz#ed6603e93636a96203c6915be4117245c1bd2daf" integrity sha512-7J6TkZQFGo9qBKH0pk2cEVSRhJbL6MtfWxth7Y5YmZs57Pi+4x6c2dStAUvaQkHQLnEQv1jzBUW43GvZW8OFqA== +"@rollup/rollup-win32-arm64-msvc@4.22.2": + version "4.22.2" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.22.2.tgz#8ae561401b92acb8ca7a842ffadececb22a2247e" + integrity sha512-t/YgCbZ638R/r7IKb9yCM6nAek1RUvyNdfU0SHMDLOf6GFe/VG1wdiUAsxTWHKqjyzkRGg897ZfCpdo1bsCSsA== + "@rollup/rollup-win32-ia32-msvc@4.18.0": version "4.18.0" resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.18.0.tgz#14e0b404b1c25ebe6157a15edb9c46959ba74c54" integrity sha512-Txjh+IxBPbkUB9+SXZMpv+b/vnTEtFyfWZgJ6iyCmt2tdx0OF5WhFowLmnh8ENGNpfUlUZkdI//4IEmhwPieNg== +"@rollup/rollup-win32-ia32-msvc@4.22.2": + version "4.22.2" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.22.2.tgz#c3a8b081595026eab9fccfe581624cb31af0d6f8" + integrity sha512-kTmX5uGs3WYOA+gYDgI6ITkZng9SP71FEMoHNkn+cnmb9Zuyyay8pf0oO5twtTwSjNGy1jlaWooTIr+Dw4tIbw== + "@rollup/rollup-win32-x64-msvc@4.18.0": version "4.18.0" resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.18.0.tgz#5d694d345ce36b6ecf657349e03eb87297e68da4" integrity sha512-UOo5FdvOL0+eIVTgS4tIdbW+TtnBLWg1YBCcU2KWM7nuNwRz9bksDX1bekJJCpu25N1DVWaCwnT39dVQxzqS8g== +"@rollup/rollup-win32-x64-msvc@4.22.2": + version "4.22.2" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.22.2.tgz#c770006ccc780b2de7b2151fc7f37b49121a21c1" + integrity sha512-Yy8So+SoRz8I3NS4Bjh91BICPOSVgdompTIPYTByUqU66AXSIOgmW3Lv1ke3NORPqxdF+RdrZET+8vYai6f4aA== + "@tauri-apps/api@2.0.0-rc.1": version "2.0.0-rc.1" resolved "https://registry.yarnpkg.com/@tauri-apps/api/-/api-2.0.0-rc.1.tgz#ec858f239e34792625e311f687fcaca0581e0904" @@ -720,6 +812,18 @@ dependencies: "@tauri-apps/api" "^2.0.0-rc.4" +"@testing-library/react@^16.0.1": + version "16.0.1" + resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-16.0.1.tgz#29c0ee878d672703f5e7579f239005e4e0faa875" + integrity sha512-dSmwJVtJXmku+iocRhWOUFbrERC76TX2Mnf0ATODz8brzAZrMBbzLwQixlBSanZxR6LddK3eiwpSFZgDET1URg== + dependencies: + "@babel/runtime" "^7.12.5" + +"@testing-library/user-event@^14.5.2": + version "14.5.2" + resolved "https://registry.yarnpkg.com/@testing-library/user-event/-/user-event-14.5.2.tgz#db7257d727c891905947bd1c1a99da20e03c2ebd" + integrity sha512-YAh82Wh4TIrxYLmfGcixwD18oIjyC1pFQC2Y01F2lzV2HTMiYrI0nze0FD0ocB//CKS/7jIUgae+adPqxK5yCQ== + "@types/babel__core@^7.20.5": version "7.20.5" resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.5.tgz#3df15f27ba85319caa07ba08d0721889bb39c017" @@ -758,6 +862,11 @@ resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.5.tgz#a6ce3e556e00fd9895dd872dd172ad0d4bd687f4" integrity sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw== +"@types/estree@^1.0.0": + version "1.0.6" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.6.tgz#628effeeae2064a1b4e79f78e81d87b7e5fc7b50" + integrity sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw== + "@types/humanize-duration@^3.27.4": version "3.27.4" resolved "https://registry.yarnpkg.com/@types/humanize-duration/-/humanize-duration-3.27.4.tgz#51d6d278213374735440bc3749de920935e9127e" @@ -904,6 +1013,65 @@ "@types/babel__core" "^7.20.5" react-refresh "^0.14.2" +"@vitest/expect@2.1.1": + version "2.1.1" + resolved "https://registry.yarnpkg.com/@vitest/expect/-/expect-2.1.1.tgz#907137a86246c5328929d796d741c4e95d1ee19d" + integrity sha512-YeueunS0HiHiQxk+KEOnq/QMzlUuOzbU1Go+PgAsHvvv3tUkJPm9xWt+6ITNTlzsMXUjmgm5T+U7KBPK2qQV6w== + dependencies: + "@vitest/spy" "2.1.1" + "@vitest/utils" "2.1.1" + chai "^5.1.1" + tinyrainbow "^1.2.0" + +"@vitest/mocker@2.1.1": + version "2.1.1" + resolved "https://registry.yarnpkg.com/@vitest/mocker/-/mocker-2.1.1.tgz#3e37c80ac267318d4aa03c5073a017d148dc8e67" + integrity sha512-LNN5VwOEdJqCmJ/2XJBywB11DLlkbY0ooDJW3uRX5cZyYCrc4PI/ePX0iQhE3BiEGiQmK4GE7Q/PqCkkaiPnrA== + dependencies: + "@vitest/spy" "^2.1.0-beta.1" + estree-walker "^3.0.3" + magic-string "^0.30.11" + +"@vitest/pretty-format@2.1.1", "@vitest/pretty-format@^2.1.1": + version "2.1.1" + resolved "https://registry.yarnpkg.com/@vitest/pretty-format/-/pretty-format-2.1.1.tgz#fea25dd4e88c3c1329fbccd1d16b1d607eb40067" + integrity sha512-SjxPFOtuINDUW8/UkElJYQSFtnWX7tMksSGW0vfjxMneFqxVr8YJ979QpMbDW7g+BIiq88RAGDjf7en6rvLPPQ== + dependencies: + tinyrainbow "^1.2.0" + +"@vitest/runner@2.1.1": + version "2.1.1" + resolved "https://registry.yarnpkg.com/@vitest/runner/-/runner-2.1.1.tgz#f3b1fbc3c109fc44e2cceecc881344453f275559" + integrity sha512-uTPuY6PWOYitIkLPidaY5L3t0JJITdGTSwBtwMjKzo5O6RCOEncz9PUN+0pDidX8kTHYjO0EwUIvhlGpnGpxmA== + dependencies: + "@vitest/utils" "2.1.1" + pathe "^1.1.2" + +"@vitest/snapshot@2.1.1": + version "2.1.1" + resolved "https://registry.yarnpkg.com/@vitest/snapshot/-/snapshot-2.1.1.tgz#38ef23104e90231fea5540754a19d8468afbba66" + integrity sha512-BnSku1WFy7r4mm96ha2FzN99AZJgpZOWrAhtQfoxjUU5YMRpq1zmHRq7a5K9/NjqonebO7iVDla+VvZS8BOWMw== + dependencies: + "@vitest/pretty-format" "2.1.1" + magic-string "^0.30.11" + pathe "^1.1.2" + +"@vitest/spy@2.1.1", "@vitest/spy@^2.1.0-beta.1": + version "2.1.1" + resolved "https://registry.yarnpkg.com/@vitest/spy/-/spy-2.1.1.tgz#20891f7421a994256ea0d739ed72f05532c78488" + integrity sha512-ZM39BnZ9t/xZ/nF4UwRH5il0Sw93QnZXd9NAZGRpIgj0yvVwPpLd702s/Cx955rGaMlyBQkZJ2Ir7qyY48VZ+g== + dependencies: + tinyspy "^3.0.0" + +"@vitest/utils@2.1.1": + version "2.1.1" + resolved "https://registry.yarnpkg.com/@vitest/utils/-/utils-2.1.1.tgz#284d016449ecb4f8704d198d049fde8360cc136e" + integrity sha512-Y6Q9TsI+qJ2CC0ZKj6VBb+T8UPz593N113nnUykqwANqhgf3QkZeHFlusgKLTqrnVHbj/XDKZcDHol+dxVT+rQ== + dependencies: + "@vitest/pretty-format" "2.1.1" + loupe "^3.1.1" + tinyrainbow "^1.2.0" + abort-controller@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392" @@ -1037,6 +1205,11 @@ arraybuffer.prototype.slice@^1.0.3: is-array-buffer "^3.0.4" is-shared-array-buffer "^1.0.2" +assertion-error@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-2.0.1.tgz#f641a196b335690b1070bf00b6e7593fec190bf7" + integrity sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA== + atomic-sleep@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/atomic-sleep/-/atomic-sleep-1.0.0.tgz#eb85b77a601fc932cfe432c5acd364a9e2c9075b" @@ -1099,6 +1272,11 @@ buffer@^6.0.3: base64-js "^1.3.1" ieee754 "^1.2.1" +cac@^6.7.14: + version "6.7.14" + resolved "https://registry.yarnpkg.com/cac/-/cac-6.7.14.tgz#804e1e6f506ee363cb0e3ccbb09cad5dd9870959" + integrity sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ== + call-bind@^1.0.2, call-bind@^1.0.5, call-bind@^1.0.6, call-bind@^1.0.7: version "1.0.7" resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.7.tgz#06016599c40c56498c18769d2730be242b6fa3b9" @@ -1120,6 +1298,17 @@ caniuse-lite@^1.0.30001629: resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001640.tgz#32c467d4bf1f1a0faa63fc793c2ba81169e7652f" integrity sha512-lA4VMpW0PSUrFnkmVuEKBUovSWKhj7puyCg8StBChgu298N1AtuF1sKWEvfDuimSEDbhlb/KqPKC3fs1HbuQUA== +chai@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/chai/-/chai-5.1.1.tgz#f035d9792a22b481ead1c65908d14bb62ec1c82c" + integrity sha512-pT1ZgP8rPNqUgieVaEY+ryQr6Q4HXNg8Ei9UnLUrjN4IA7dvQC5JB+/kxVcPNDHyBcc/26CXPkbNzq3qwrOEKA== + dependencies: + assertion-error "^2.0.1" + check-error "^2.1.1" + deep-eql "^5.0.1" + loupe "^3.1.0" + pathval "^2.0.0" + chalk@^2.4.2: version "2.4.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" @@ -1137,6 +1326,11 @@ chalk@^4.0.0: ansi-styles "^4.1.0" supports-color "^7.1.0" +check-error@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/check-error/-/check-error-2.1.1.tgz#87eb876ae71ee388fa0471fe423f494be1d96ccc" + integrity sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw== + clsx@^1.0.4, clsx@^1.1.0: version "1.2.1" resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.2.1.tgz#0ddc4a20a549b59c93a4116bb26f5294ca17dc12" @@ -1254,6 +1448,18 @@ debug@^4.3.2, debug@^4.3.4: dependencies: ms "2.1.2" +debug@^4.3.6: + version "4.3.7" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.7.tgz#87945b4151a011d76d95a198d7111c865c360a52" + integrity sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ== + dependencies: + ms "^2.1.3" + +deep-eql@^5.0.1: + version "5.0.2" + resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-5.0.2.tgz#4b756d8d770a9257300825d52a2c2cff99c3a341" + integrity sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q== + deep-is@^0.1.3: version "0.1.4" resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" @@ -1602,6 +1808,13 @@ estraverse@^5.1.0, estraverse@^5.2.0, estraverse@^5.3.0: resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== +estree-walker@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-3.0.3.tgz#67c3e549ec402a487b4fc193d1953a524752340d" + integrity sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g== + dependencies: + "@types/estree" "^1.0.0" + esutils@^2.0.2: version "2.0.3" resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" @@ -1752,6 +1965,11 @@ gensync@^1.0.0-beta.2: resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== +get-func-name@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.2.tgz#0d7cf20cd13fda808669ffa88f4ffc7a3943fc41" + integrity sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ== + get-intrinsic@^1.1.3, get-intrinsic@^1.2.1, get-intrinsic@^1.2.3, get-intrinsic@^1.2.4: version "1.2.4" resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.4.tgz#e385f5a4b5227d449c3eabbad05494ef0abbeadd" @@ -2347,6 +2565,13 @@ loose-envify@^1.1.0, loose-envify@^1.4.0: dependencies: js-tokens "^3.0.0 || ^4.0.0" +loupe@^3.1.0, loupe@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/loupe/-/loupe-3.1.1.tgz#71d038d59007d890e3247c5db97c1ec5a92edc54" + integrity sha512-edNu/8D5MKVfGVFRhFf8aAxiTM6Wumfz5XsaatSxlD3w4R1d/WEKUTydCdPGbl9K7QG/Ca3GnDV2sIKIpXRQcw== + dependencies: + get-func-name "^2.0.1" + lru-cache@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" @@ -2354,6 +2579,13 @@ lru-cache@^5.1.1: dependencies: yallist "^3.0.2" +magic-string@^0.30.11: + version "0.30.11" + resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.11.tgz#301a6f93b3e8c2cb13ac1a7a673492c0dfd12954" + integrity sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A== + dependencies: + "@jridgewell/sourcemap-codec" "^1.5.0" + merge-stream@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" @@ -2408,7 +2640,7 @@ ms@2.1.2: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== -ms@^2.1.1: +ms@^2.1.1, ms@^2.1.3: version "2.1.3" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== @@ -2609,11 +2841,26 @@ path-type@^4.0.0: resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== +pathe@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/pathe/-/pathe-1.1.2.tgz#6c4cb47a945692e48a1ddd6e4094d170516437ec" + integrity sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ== + +pathval@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/pathval/-/pathval-2.0.0.tgz#7e2550b422601d4f6b8e26f1301bc8f15a741a25" + integrity sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA== + picocolors@^1.0.0, picocolors@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.1.tgz#a8ad579b571952f0e5d25892de5445bcfe25aaa1" integrity sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew== +picocolors@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.0.tgz#5358b76a78cde483ba5cef6a9dc9671440b27d59" + integrity sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw== + picomatch@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" @@ -2688,6 +2935,15 @@ postcss@^8.4.39: picocolors "^1.0.1" source-map-js "^1.2.0" +postcss@^8.4.43: + version "8.4.47" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.47.tgz#5bf6c9a010f3e724c503bf03ef7947dcb0fea365" + integrity sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ== + dependencies: + nanoid "^3.3.7" + picocolors "^1.1.0" + source-map-js "^1.2.1" + prelude-ls@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" @@ -2926,6 +3182,31 @@ rollup@^4.13.0: "@rollup/rollup-win32-x64-msvc" "4.18.0" fsevents "~2.3.2" +rollup@^4.20.0: + version "4.22.2" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.22.2.tgz#d762fa52c6ddb1307c1d6e8b463ba79432ffbb6b" + integrity sha512-JWWpTrZmqQGQWt16xvNn6KVIUz16VtZwl984TKw0dfqqRpFwtLJYYk1/4BTgplndMQKWUk/yB4uOShYmMzA2Vg== + dependencies: + "@types/estree" "1.0.5" + optionalDependencies: + "@rollup/rollup-android-arm-eabi" "4.22.2" + "@rollup/rollup-android-arm64" "4.22.2" + "@rollup/rollup-darwin-arm64" "4.22.2" + "@rollup/rollup-darwin-x64" "4.22.2" + "@rollup/rollup-linux-arm-gnueabihf" "4.22.2" + "@rollup/rollup-linux-arm-musleabihf" "4.22.2" + "@rollup/rollup-linux-arm64-gnu" "4.22.2" + "@rollup/rollup-linux-arm64-musl" "4.22.2" + "@rollup/rollup-linux-powerpc64le-gnu" "4.22.2" + "@rollup/rollup-linux-riscv64-gnu" "4.22.2" + "@rollup/rollup-linux-s390x-gnu" "4.22.2" + "@rollup/rollup-linux-x64-gnu" "4.22.2" + "@rollup/rollup-linux-x64-musl" "4.22.2" + "@rollup/rollup-win32-arm64-msvc" "4.22.2" + "@rollup/rollup-win32-ia32-msvc" "4.22.2" + "@rollup/rollup-win32-x64-msvc" "4.22.2" + fsevents "~2.3.2" + run-parallel@^1.1.9: version "1.2.0" resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" @@ -3033,6 +3314,11 @@ side-channel@^1.0.4, side-channel@^1.0.6: get-intrinsic "^1.2.4" object-inspect "^1.13.1" +siginfo@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/siginfo/-/siginfo-2.0.0.tgz#32e76c70b79724e3bb567cb9d543eb858ccfaf30" + integrity sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g== + signal-exit@^3.0.3: version "3.0.7" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" @@ -3055,11 +3341,26 @@ source-map-js@^1.2.0: resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.0.tgz#16b809c162517b5b8c3e7dcd315a2a5c2612b2af" integrity sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg== +source-map-js@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46" + integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA== + split2@^4.0.0: version "4.2.0" resolved "https://registry.yarnpkg.com/split2/-/split2-4.2.0.tgz#c9c5920904d148bab0b9f67145f245a86aadbfa4" integrity sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg== +stackback@0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/stackback/-/stackback-0.0.2.tgz#1ac8a0d9483848d1695e418b6d031a3c3ce68e3b" + integrity sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw== + +std-env@^3.7.0: + version "3.7.0" + resolved "https://registry.yarnpkg.com/std-env/-/std-env-3.7.0.tgz#c9f7386ced6ecf13360b6c6c55b8aaa4ef7481d2" + integrity sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg== + string.prototype.matchall@^4.0.11: version "4.0.11" resolved "https://registry.yarnpkg.com/string.prototype.matchall/-/string.prototype.matchall-4.0.11.tgz#1092a72c59268d2abaad76582dccc687c0297e0a" @@ -3174,6 +3475,31 @@ tiny-warning@^1.0.2: resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754" integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA== +tinybench@^2.9.0: + version "2.9.0" + resolved "https://registry.yarnpkg.com/tinybench/-/tinybench-2.9.0.tgz#103c9f8ba6d7237a47ab6dd1dcff77251863426b" + integrity sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg== + +tinyexec@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/tinyexec/-/tinyexec-0.3.0.tgz#ed60cfce19c17799d4a241e06b31b0ec2bee69e6" + integrity sha512-tVGE0mVJPGb0chKhqmsoosjsS+qUnJVGJpZgsHYQcGoPlG3B51R3PouqTgEGH2Dc9jjFyOqOpix6ZHNMXp1FZg== + +tinypool@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/tinypool/-/tinypool-1.0.1.tgz#c64233c4fac4304e109a64340178760116dbe1fe" + integrity sha512-URZYihUbRPcGv95En+sz6MfghfIc2OJ1sv/RmhWZLouPY0/8Vo80viwPvg3dlaS9fuq7fQMEfgRRK7BBZThBEA== + +tinyrainbow@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/tinyrainbow/-/tinyrainbow-1.2.0.tgz#5c57d2fc0fb3d1afd78465c33ca885d04f02abb5" + integrity sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ== + +tinyspy@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/tinyspy/-/tinyspy-3.0.2.tgz#86dd3cf3d737b15adcf17d7887c84a75201df20a" + integrity sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q== + to-fast-properties@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" @@ -3313,6 +3639,16 @@ virtua@^0.33.2: resolved "https://registry.yarnpkg.com/virtua/-/virtua-0.33.2.tgz#b9596387bc77664293359d438319e81180a0e051" integrity sha512-4NgtryQH/idQ3oKkwM6DRCoCsn+IrjrStGcDOARPdlY7zIg0AtTcUq24nysM8YyHoS6KhqcVe8A3+lHJidNQWA== +vite-node@2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/vite-node/-/vite-node-2.1.1.tgz#7d46f623c04dfed6df34e7127711508a3386fa1c" + integrity sha512-N/mGckI1suG/5wQI35XeR9rsMsPqKXzq1CdUndzVstBj/HvyxxGctwnK6WX43NGt5L3Z5tcRf83g4TITKJhPrA== + dependencies: + cac "^6.7.14" + debug "^4.3.6" + pathe "^1.1.2" + vite "^5.0.0" + vite-plugin-watch@^0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/vite-plugin-watch/-/vite-plugin-watch-0.3.1.tgz#5000f7ded6eb1c42e9483d6ea3d812061ab8188f" @@ -3329,6 +3665,17 @@ vite-tsconfig-paths@^4.3.2: globrex "^0.1.2" tsconfck "^3.0.3" +vite@^5.0.0: + version "5.4.7" + resolved "https://registry.yarnpkg.com/vite/-/vite-5.4.7.tgz#d226f57c08b61379e955f3836253ed3efb2dcf00" + integrity sha512-5l2zxqMEPVENgvzTuBpHer2awaetimj2BGkhBPdnwKbPNOlHsODU+oiazEZzLK7KhAnOrO+XGYJYn4ZlUhDtDQ== + dependencies: + esbuild "^0.21.3" + postcss "^8.4.43" + rollup "^4.20.0" + optionalDependencies: + fsevents "~2.3.3" + vite@^5.3.1: version "5.3.3" resolved "https://registry.yarnpkg.com/vite/-/vite-5.3.3.tgz#5265b1f0a825b3b6564c2d07524777c83e3c04c2" @@ -3340,6 +3687,31 @@ vite@^5.3.1: optionalDependencies: fsevents "~2.3.3" +vitest@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/vitest/-/vitest-2.1.1.tgz#24a6f6f5d894509f10685b82de008c507faacbb1" + integrity sha512-97We7/VC0e9X5zBVkvt7SGQMGrRtn3KtySFQG5fpaMlS+l62eeXRQO633AYhSTC3z7IMebnPPNjGXVGNRFlxBA== + dependencies: + "@vitest/expect" "2.1.1" + "@vitest/mocker" "2.1.1" + "@vitest/pretty-format" "^2.1.1" + "@vitest/runner" "2.1.1" + "@vitest/snapshot" "2.1.1" + "@vitest/spy" "2.1.1" + "@vitest/utils" "2.1.1" + chai "^5.1.1" + debug "^4.3.6" + magic-string "^0.30.11" + pathe "^1.1.2" + std-env "^3.7.0" + tinybench "^2.9.0" + tinyexec "^0.3.0" + tinypool "^1.0.0" + tinyrainbow "^1.2.0" + vite "^5.0.0" + vite-node "2.1.1" + why-is-node-running "^2.3.0" + which-boxed-primitive@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6" @@ -3397,6 +3769,14 @@ which@^2.0.1: dependencies: isexe "^2.0.0" +why-is-node-running@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/why-is-node-running/-/why-is-node-running-2.3.0.tgz#a3f69a97107f494b3cdc3bdddd883a7d65cebf04" + integrity sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w== + dependencies: + siginfo "^2.0.0" + stackback "0.0.2" + word-wrap@^1.2.5: version "1.2.5" resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34" diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 3866e2e05..141f28bc7 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -3,9 +3,9 @@ use std::sync::Arc; use swap::cli::{ api::{ request::{ - BalanceArgs, BuyXmrArgs, GetHistoryArgs, GetLogsArgs, GetSwapInfosAllArgs, - ListSellersArgs, MoneroRecoveryArgs, ResumeSwapArgs, SuspendCurrentSwapArgs, - WithdrawBtcArgs, + BalanceArgs, BuyXmrArgs, CancelAndRefundArgs, GetHistoryArgs, GetLogsArgs, + GetSwapInfosAllArgs, ListSellersArgs, MoneroRecoveryArgs, ResumeSwapArgs, + SuspendCurrentSwapArgs, WithdrawBtcArgs, }, tauri_bindings::{TauriContextStatusEvent, TauriEmitter, TauriHandle}, Context, ContextBuilder, @@ -172,6 +172,7 @@ pub fn run() { get_logs, list_sellers, suspend_current_swap, + cancel_and_refund, is_context_available, ]) .setup(setup) @@ -211,6 +212,7 @@ tauri_command!(withdraw_btc, WithdrawBtcArgs); tauri_command!(monero_recovery, MoneroRecoveryArgs); tauri_command!(get_logs, GetLogsArgs); tauri_command!(list_sellers, ListSellersArgs); +tauri_command!(cancel_and_refund, CancelAndRefundArgs); // These commands require no arguments tauri_command!(suspend_current_swap, SuspendCurrentSwapArgs, no_args); diff --git a/swap/src/asb/event_loop.rs b/swap/src/asb/event_loop.rs index 8e3ac4c34..c82b746b8 100644 --- a/swap/src/asb/event_loop.rs +++ b/swap/src/asb/event_loop.rs @@ -161,7 +161,6 @@ where swarm_event = self.swarm.select_next_some() => { match swarm_event { SwarmEvent::Behaviour(OutEvent::SwapSetupInitiated { mut send_wallet_snapshot }) => { - let (btc, responder) = match send_wallet_snapshot.recv().await { Ok((btc, responder)) => (btc, responder), Err(error) => { diff --git a/swap/src/cli/api/tauri_bindings.rs b/swap/src/cli/api/tauri_bindings.rs index 9172dc3c5..675dd3299 100644 --- a/swap/src/cli/api/tauri_bindings.rs +++ b/swap/src/cli/api/tauri_bindings.rs @@ -133,6 +133,7 @@ pub enum TauriSwapProgressEvent { xmr_lock_tx_confirmations: u64, }, XmrLocked, + EncryptedSignatureSent, BtcRedeemed, XmrRedeemInMempool { #[typeshare(serialized_as = "Vec")] @@ -140,6 +141,7 @@ pub enum TauriSwapProgressEvent { #[typeshare(serialized_as = "string")] xmr_redeem_address: monero::Address, }, + CancelTimelockExpired, BtcCancelled { #[typeshare(serialized_as = "string")] btc_cancel_txid: Txid, diff --git a/swap/src/protocol/bob/state.rs b/swap/src/protocol/bob/state.rs index 238683d5a..873be156a 100644 --- a/swap/src/protocol/bob/state.rs +++ b/swap/src/protocol/bob/state.rs @@ -740,11 +740,15 @@ impl State6 { Ok((tx_id, subscription)) } - pub async fn publish_refund_btc(&self, bitcoin_wallet: &bitcoin::Wallet) -> Result<()> { + pub async fn publish_refund_btc( + &self, + bitcoin_wallet: &bitcoin::Wallet, + ) -> Result { let signed_tx_refund = self.signed_refund_transaction()?; + let signed_tx_refund_txid = signed_tx_refund.txid(); bitcoin_wallet.broadcast(signed_tx_refund, "refund").await?; - Ok(()) + Ok(signed_tx_refund_txid) } pub fn signed_refund_transaction(&self) -> Result { diff --git a/swap/src/protocol/bob/swap.rs b/swap/src/protocol/bob/swap.rs index ef518ac76..165ffda17 100644 --- a/swap/src/protocol/bob/swap.rs +++ b/swap/src/protocol/bob/swap.rs @@ -328,6 +328,9 @@ async fn next_state( } } BobState::EncSigSent(state) => { + event_emitter + .emit_swap_progress_event(swap_id, TauriSwapProgressEvent::EncryptedSignatureSent); + // We need to make sure that Alice did not publish the redeem transaction while we were offline // Even if the cancel timelock expired, if Alice published the redeem transaction while we were away we cannot miss it // If we do we cannot refund and will never be able to leave the "CancelTimelockExpired" state @@ -375,6 +378,9 @@ async fn next_state( } } BobState::CancelTimelockExpired(state4) => { + event_emitter + .emit_swap_progress_event(swap_id, TauriSwapProgressEvent::CancelTimelockExpired); + if state4.check_for_tx_cancel(bitcoin_wallet).await.is_err() { state4.submit_tx_cancel(bitcoin_wallet).await?; } @@ -397,13 +403,11 @@ async fn next_state( ); } ExpiredTimelocks::Cancel { .. } => { - state.publish_refund_btc(bitcoin_wallet).await?; + let btc_refund_txid = state.publish_refund_btc(bitcoin_wallet).await?; event_emitter.emit_swap_progress_event( swap_id, - TauriSwapProgressEvent::BtcRefunded { - btc_refund_txid: state.signed_refund_transaction()?.txid(), - }, + TauriSwapProgressEvent::BtcRefunded { btc_refund_txid }, ); BobState::BtcRefunded(state) @@ -521,6 +525,7 @@ async fn next_state( } }; } + // TODO: Emit a Tauri event here BobState::SafelyAborted => BobState::SafelyAborted, BobState::XmrRedeemed { tx_lock_id } => { event_emitter.emit_swap_progress_event(