diff --git a/package.json b/package.json index 6cb748ac..5ebeab83 100644 --- a/package.json +++ b/package.json @@ -67,6 +67,7 @@ "react-router-dom": "^6.22.3", "typeorm": "^0.3.20", "user-agents": "^1.1.193", + "webtorrent-health": "^1.2.0", "winston": "^3.13.0", "yaml": "^2.4.1" }, diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index 7b54b889..19efb4d8 100644 --- a/src/locales/en/translation.json +++ b/src/locales/en/translation.json @@ -96,6 +96,8 @@ "copy_to_clipboard": "Copy", "copied_to_clipboard": "Copied", "got_it": "Got it", + "multi_language": "Multi Language", + "multiplayer": "Multi Player", "no_shop_details": "Could not retrieve shop details.", "download_options": "Download options", "download_path": "Download path", diff --git a/src/locales/es/translation.json b/src/locales/es/translation.json index d3f90cb6..eeb9e103 100644 --- a/src/locales/es/translation.json +++ b/src/locales/es/translation.json @@ -95,6 +95,8 @@ "dont_show_it_again": "No mostrar de nuevo", "copy_to_clipboard": "Copiar", "copied_to_clipboard": "Copiado", + "multi_language": "Multi Idioma", + "multiplayer": "Multijugador", "got_it": "Entendido", "no_shop_details": "No se pudieron obtener detalles de la tienda.", "download_options": "Opciones de descarga", diff --git a/src/locales/fr/translation.json b/src/locales/fr/translation.json index 2e17f492..42e14e1b 100644 --- a/src/locales/fr/translation.json +++ b/src/locales/fr/translation.json @@ -75,7 +75,9 @@ "close": "Fermer", "deleting": "Suppression du programme d'installation…", "playing_now": "Jeu en cours", - "last_time_played": "Dernièrement joué {{période}}" + "last_time_played": "Dernièrement joué {{période}}", + "multi_language": "Multilingue", + "multiplayer": "Multijoueur" }, "activation": { "title": "Activer Hydra", diff --git a/src/locales/hu/translation.json b/src/locales/hu/translation.json index 8a370fb2..038e6874 100644 --- a/src/locales/hu/translation.json +++ b/src/locales/hu/translation.json @@ -84,7 +84,9 @@ "repacks_modal_description": "Choose the repack you want to download", "downloads_path": "Letöltések helye", "select_folder_hint": "Ahhoz, hogy megváltoztasd a helyet, hozzákell férned a", - "download_now": "Töltsd le most" + "download_now": "Töltsd le most", + "multi_language": "Többnyelvű", + "multiplayer": "Többjátékos" }, "activation": { "title": "Hydra Aktiválása", diff --git a/src/locales/it/translation.json b/src/locales/it/translation.json index 57706dc9..554001d3 100644 --- a/src/locales/it/translation.json +++ b/src/locales/it/translation.json @@ -96,7 +96,9 @@ "dont_show_it_again": "Non mostrarlo più", "copy_to_clipboard": "Copia", "copied_to_clipboard": "Copiato", - "got_it": "Capito" + "got_it": "Capito", + "multi_language": "Multilingua", + "multiplayer": "Multigiocatore" }, "activation": { "title": "Attiva Hydra", diff --git a/src/locales/pl/translation.json b/src/locales/pl/translation.json index d629d1f3..6ec38502 100644 --- a/src/locales/pl/translation.json +++ b/src/locales/pl/translation.json @@ -87,6 +87,9 @@ "change": "Zmień", "repacks_modal_description": "Wybierz repack, który chcesz pobrać", "select_folder_hint": "Aby zmienić domyślny folder, przejdź do", + "settings": "Ustawienia Hydra", + "multi_language": "Wielojęzyczny", + "multiplayer": "Wieloosobowy", "download_now": "Pobierz teraz", "installation_instructions": "Instrukcja instalacji", "installation_instructions_description": "Do zainstalowania tej gry wymagane są dodatkowe kroki", diff --git a/src/locales/pt/translation.json b/src/locales/pt/translation.json index 57ec0470..d14e480e 100644 --- a/src/locales/pt/translation.json +++ b/src/locales/pt/translation.json @@ -92,6 +92,8 @@ "copy_to_clipboard": "Copiar", "copied_to_clipboard": "Copiado", "got_it": "Entendi", + "multi_language": "Multi Idioma", + "multiplayer": "Multijogador", "no_shop_details": "Não foi possível obter os detalhes da loja.", "download_options": "Opções de download", "download_path": "Diretório de download", diff --git a/src/locales/ru/translation.json b/src/locales/ru/translation.json index f7a80771..24eb5cb0 100644 --- a/src/locales/ru/translation.json +++ b/src/locales/ru/translation.json @@ -96,6 +96,8 @@ "copy_to_clipboard": "Копировать", "copied_to_clipboard": "Скопировано", "got_it": "Понятно", + "multi_language": "Мультиязычный", + "multiplayer": "Многопользовательский", "no_shop_details": "Не удалось получить описание", "download_options": "Вариантов загрузки", "download_path": "Путь для загрузок", diff --git a/src/locales/tr/translation.json b/src/locales/tr/translation.json index be40e013..c9d1479b 100644 --- a/src/locales/tr/translation.json +++ b/src/locales/tr/translation.json @@ -96,7 +96,9 @@ "dont_show_it_again": "Tekrar gösterme", "copy_to_clipboard": "Kopyala", "copied_to_clipboard": "Kopyalandı", - "got_it": "Tamam" + "got_it": "Tamam", + "multi_language": "Çoklu Dil", + "multiplayer": "Çok Oyunculu" }, "activation": { "title": "Hydra'yı aktif et", diff --git a/src/main/events/catalogue/repacks/get-magnet-health.ts b/src/main/events/catalogue/repacks/get-magnet-health.ts new file mode 100644 index 00000000..6c8b3ded --- /dev/null +++ b/src/main/events/catalogue/repacks/get-magnet-health.ts @@ -0,0 +1,11 @@ +import { webTorrentData } from "@main/services/web-torrent-data"; +import { registerEvent } from "../../register-event"; + +const getMagnetHealth = async ( + _event: Electron.IpcMainInvokeEvent, + magnet: string +) => { + return webTorrentData.getSeedersAndPeers(magnet); +}; + +registerEvent("getMagnetHealth", getMagnetHealth); diff --git a/src/main/events/index.ts b/src/main/events/index.ts index 5d721c62..0bdb184f 100644 --- a/src/main/events/index.ts +++ b/src/main/events/index.ts @@ -7,6 +7,7 @@ import "./catalogue/get-games"; import "./catalogue/get-how-long-to-beat"; import "./catalogue/get-random-game"; import "./catalogue/search-games"; +import "./catalogue/repacks/get-magnet-health"; import "./catalogue/search-game-repacks"; import "./hardware/get-disk-free-space"; import "./library/add-game-to-library"; diff --git a/src/main/services/web-torrent-data.ts b/src/main/services/web-torrent-data.ts new file mode 100644 index 00000000..72f29be6 --- /dev/null +++ b/src/main/services/web-torrent-data.ts @@ -0,0 +1,40 @@ +import webTorrentHealth from "webtorrent-health"; + +type WebTorrentHealthData = { + seeds: number; + peers: number; +}; + +const MILLISECONDS = 1000; +const SECONDS = 1.5; + +export const webTorrentData = { + async getSeedersAndPeers( + magnet: string + ): Promise<{ seeders: number; peers: number } | null> { + let peers = 0; + let seeds = 0; + let retry = 0; + let timeout = SECONDS * MILLISECONDS; + + while (retry < 3) { + try { + const data: WebTorrentHealthData = await webTorrentHealth(magnet, { + timeout, + }); + + peers = data.peers; + seeds = data.seeds; + + if (peers || seeds) break; + } catch (e) { + return null; + } + + timeout *= 2; + retry++; + } + + return { peers, seeders: seeds }; + }, +}; diff --git a/src/preload/index.d.ts b/src/preload/index.d.ts new file mode 100644 index 00000000..487ac3d1 --- /dev/null +++ b/src/preload/index.d.ts @@ -0,0 +1,107 @@ +// See the Electron documentation for details on how to use preload scripts: +// https://www.electronjs.org/docs/latest/tutorial/process-model#preload-scripts +import { contextBridge, ipcRenderer } from "electron"; + +import type { + CatalogueCategory, + GameShop, + TorrentProgress, + UserPreferences, +} from "@types"; + +contextBridge.exposeInMainWorld("electron", { + /* Torrenting */ + startGameDownload: ( + repackId: number, + objectID: string, + title: string, + shop: GameShop + ) => ipcRenderer.invoke("startGameDownload", repackId, objectID, title, shop), + cancelGameDownload: (gameId: number) => + ipcRenderer.invoke("cancelGameDownload", gameId), + pauseGameDownload: (gameId: number) => + ipcRenderer.invoke("pauseGameDownload", gameId), + resumeGameDownload: (gameId: number) => + ipcRenderer.invoke("resumeGameDownload", gameId), + onDownloadProgress: (cb: (value: TorrentProgress) => void) => { + const listener = ( + _event: Electron.IpcRendererEvent, + value: TorrentProgress + ) => cb(value); + ipcRenderer.on("on-download-progress", listener); + return () => ipcRenderer.removeListener("on-download-progress", listener); + }, + + /* Catalogue */ + searchGames: (query: string) => ipcRenderer.invoke("searchGames", query), + getCatalogue: (category: CatalogueCategory) => + ipcRenderer.invoke("getCatalogue", category), + getGameShopDetails: (objectID: string, shop: GameShop, language: string) => + ipcRenderer.invoke("getGameShopDetails", objectID, shop, language), + getRandomGame: () => ipcRenderer.invoke("getRandomGame"), + getHowLongToBeat: (objectID: string, shop: GameShop, title: string) => + ipcRenderer.invoke("getHowLongToBeat", objectID, shop, title), + getGames: (take?: number, prevCursor?: number) => + ipcRenderer.invoke("getGames", take, prevCursor), + + /* User preferences */ + getUserPreferences: () => ipcRenderer.invoke("getUserPreferences"), + updateUserPreferences: (preferences: UserPreferences) => + ipcRenderer.invoke("updateUserPreferences", preferences), + autoLaunch: (enabled: boolean) => ipcRenderer.invoke("autoLaunch", enabled), + + /* Library */ + addGameToLibrary: ( + objectID: string, + title: string, + shop: GameShop, + executablePath: string + ) => + ipcRenderer.invoke( + "addGameToLibrary", + objectID, + title, + shop, + executablePath + ), + getLibrary: () => ipcRenderer.invoke("getLibrary"), + getRepackersFriendlyNames: () => + ipcRenderer.invoke("getRepackersFriendlyNames"), + openGameInstaller: (gameId: number) => + ipcRenderer.invoke("openGameInstaller", gameId), + openGame: (gameId: number, executablePath: string) => + ipcRenderer.invoke("openGame", gameId, executablePath), + closeGame: (gameId: number) => ipcRenderer.invoke("closeGame", gameId), + removeGameFromLibrary: (gameId: number) => + ipcRenderer.invoke("removeGameFromLibrary", gameId), + deleteGameFolder: (gameId: number) => + ipcRenderer.invoke("deleteGameFolder", gameId), + getGameByObjectID: (objectID: string) => + ipcRenderer.invoke("getGameByObjectID", objectID), + onPlaytime: (cb: (gameId: number) => void) => { + const listener = (_event: Electron.IpcRendererEvent, gameId: number) => + cb(gameId); + ipcRenderer.on("on-playtime", listener); + return () => ipcRenderer.removeListener("on-playtime", listener); + }, + onGameClose: (cb: (gameId: number) => void) => { + const listener = (_event: Electron.IpcRendererEvent, gameId: number) => + cb(gameId); + ipcRenderer.on("on-game-close", listener); + return () => ipcRenderer.removeListener("on-game-close", listener); + }, + + /* Hardware */ + getDiskFreeSpace: () => ipcRenderer.invoke("getDiskFreeSpace"), + + /* Misc */ + ping: () => ipcRenderer.invoke("ping"), + getVersion: () => ipcRenderer.invoke("getVersion"), + getDefaultDownloadsPath: () => ipcRenderer.invoke("getDefaultDownloadsPath"), + openExternal: (src: string) => ipcRenderer.invoke("openExternal", src), + showOpenDialog: (options: Electron.OpenDialogOptions) => + ipcRenderer.invoke("showOpenDialog", options), + platform: process.platform, + getMagnetHealth: (magnet: string) => + ipcRenderer.invoke("getMagnetHealth", magnet), +}); diff --git a/src/preload/index.ts b/src/preload/index.ts index 6a209787..4176f1a3 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -112,4 +112,6 @@ contextBridge.exposeInMainWorld("electron", { showOpenDialog: (options: Electron.OpenDialogOptions) => ipcRenderer.invoke("showOpenDialog", options), platform: process.platform, + getMagnetHealth: (magnet: string) => + ipcRenderer.invoke("getMagnetHealth", magnet), }); diff --git a/src/renderer/src/assets/sprout-icon.svg b/src/renderer/src/assets/sprout-icon.svg new file mode 100644 index 00000000..fb35828a --- /dev/null +++ b/src/renderer/src/assets/sprout-icon.svg @@ -0,0 +1 @@ + diff --git a/src/renderer/src/assets/users-icon.svg b/src/renderer/src/assets/users-icon.svg new file mode 100644 index 00000000..63cb3f98 --- /dev/null +++ b/src/renderer/src/assets/users-icon.svg @@ -0,0 +1 @@ + diff --git a/src/renderer/src/components/index.ts b/src/renderer/src/components/index.ts index 70777fea..a5ad3a0f 100644 --- a/src/renderer/src/components/index.ts +++ b/src/renderer/src/components/index.ts @@ -8,3 +8,4 @@ export * from "./sidebar/sidebar"; export * from "./text-field/text-field"; export * from "./checkbox-field/checkbox-field"; export * from "./link/link"; +export * from "./tag/tag"; diff --git a/src/renderer/src/components/tag/tag.css.ts b/src/renderer/src/components/tag/tag.css.ts new file mode 100644 index 00000000..f0c0608f --- /dev/null +++ b/src/renderer/src/components/tag/tag.css.ts @@ -0,0 +1,12 @@ +import { SPACING_UNIT, vars } from "../../theme.css"; +import { style } from "@vanilla-extract/css"; + +export const tagStyle = style({ + borderRadius: "3px", + border: `1px solid ${vars.color.border}`, + padding: `${SPACING_UNIT / 4}px ${SPACING_UNIT}px`, +}); + +export const tagText = style({ + fontSize: "11px", +}); diff --git a/src/renderer/src/components/tag/tag.tsx b/src/renderer/src/components/tag/tag.tsx new file mode 100644 index 00000000..fa7d0182 --- /dev/null +++ b/src/renderer/src/components/tag/tag.tsx @@ -0,0 +1,9 @@ +import * as styles from "./tag.css"; + +export function Tag({ children }: Readonly<{ children: React.ReactNode }>) { + return ( +
+ {children} +
+ ); +} diff --git a/src/renderer/src/components/tooltip/tooltip.css.ts b/src/renderer/src/components/tooltip/tooltip.css.ts new file mode 100644 index 00000000..e478380b --- /dev/null +++ b/src/renderer/src/components/tooltip/tooltip.css.ts @@ -0,0 +1,36 @@ +import { style } from "@vanilla-extract/css"; + +export const tooltipStyle = style({ + position: "relative", + display: "flex", + cursor: "pointer", + alignItems: "center", +}); + +export const tooltipTextStyle = style({ + visibility: "hidden", + backgroundColor: "#555", + color: "#fff", + textAlign: "center", + borderRadius: "6px", + padding: "5px 5px", + position: "absolute", + zIndex: "1", + bottom: "125%", + left: "max(0%, min(100%, 50%))", + transform: "translateX(-50%)", + ":after": { + content: '""', + position: "absolute", + top: "100%", + left: "50%", + marginLeft: "-5px", + borderWidth: "5px", + borderStyle: "solid", + borderColor: "#555 transparent transparent transparent", + }, +}); + +export const tooltipVisible = style({ + visibility: "visible", +}); diff --git a/src/renderer/src/components/tooltip/tooltip.tsx b/src/renderer/src/components/tooltip/tooltip.tsx new file mode 100644 index 00000000..50d74f4c --- /dev/null +++ b/src/renderer/src/components/tooltip/tooltip.tsx @@ -0,0 +1,26 @@ +import { useState } from "react"; +import * as styles from "./tooltip.css"; + +interface TooltipProps { + children: React.ReactNode; + tooltipText: string; +} + +export function Tooltip({ children, tooltipText }: Readonly) { + const [isVisible, setIsVisible] = useState(false); + + return ( +
setIsVisible(true)} + onMouseLeave={() => setIsVisible(false)} + > + {children} + + {tooltipText} + +
+ ); +} diff --git a/src/renderer/src/declaration.d.ts b/src/renderer/src/declaration.d.ts index 045915bb..f03cc6cb 100644 --- a/src/renderer/src/declaration.d.ts +++ b/src/renderer/src/declaration.d.ts @@ -90,6 +90,9 @@ declare global { options: Electron.OpenDialogOptions ) => Promise; platform: NodeJS.Platform; + getMagnetHealth: ( + magnet: string + ) => Promise<{ seeders: number; peers: number }>; } interface Window { diff --git a/src/renderer/src/helpers/searcher.ts b/src/renderer/src/helpers/searcher.ts new file mode 100644 index 00000000..8f2f4890 --- /dev/null +++ b/src/renderer/src/helpers/searcher.ts @@ -0,0 +1,39 @@ +import { toCapitalize } from "./string"; + +export const isMultiplayerRepack = (title: string, repacker: string) => { + const titleToLower = title.toLowerCase(); + const repackerToLower = repacker.toLowerCase(); + + return ( + titleToLower.includes("multiplayer") || + titleToLower.includes("onlinefix") || + titleToLower.includes("online fix") || + repackerToLower.includes("onlinefix") || + repackerToLower.includes("online fix") + ); +}; + +export const supportMultiLanguage = (title: string) => { + const multiFollowedByDigitsRegex = /multi\d+/; + + return multiFollowedByDigitsRegex.test(title.toLowerCase()); +}; + +export const getRepackLanguageBasedOnRepacker = ( + repacker: string, + userLanguage: string +) => { + const languageCodes = { + xatab: "ru", + }; + + const languageCode = languageCodes[repacker.toLowerCase()] || "en"; + + const displayNames = new Intl.DisplayNames([userLanguage], { + type: "language", + }); + + const language = displayNames.of(languageCode); + + return language ? toCapitalize(language) : "English"; +}; diff --git a/src/renderer/src/helpers/string.ts b/src/renderer/src/helpers/string.ts new file mode 100644 index 00000000..eb18b8b2 --- /dev/null +++ b/src/renderer/src/helpers/string.ts @@ -0,0 +1,3 @@ +export function toCapitalize(str: string) { + return str.charAt(0).toUpperCase() + str.slice(1); +} diff --git a/src/renderer/src/pages/game-details/repacks-modal.css.ts b/src/renderer/src/pages/game-details/repacks-modal.css.ts index 4e15a63a..472ba934 100644 --- a/src/renderer/src/pages/game-details/repacks-modal.css.ts +++ b/src/renderer/src/pages/game-details/repacks-modal.css.ts @@ -16,3 +16,9 @@ export const repackButton = style({ color: vars.color.bodyText, padding: `${SPACING_UNIT * 2}px`, }); + +export const tagsContainer = style({ + display: "flex", + gap: `${SPACING_UNIT}px`, + flexWrap: "wrap", +}); diff --git a/src/renderer/src/pages/game-details/repacks-modal.tsx b/src/renderer/src/pages/game-details/repacks-modal.tsx index 4bb92408..b33d5bb8 100644 --- a/src/renderer/src/pages/game-details/repacks-modal.tsx +++ b/src/renderer/src/pages/game-details/repacks-modal.tsx @@ -9,6 +9,14 @@ import * as styles from "./repacks-modal.css"; import { SPACING_UNIT } from "../../theme.css"; import { format } from "date-fns"; import { SelectFolderModal } from "./select-folder-modal"; +import { SeedersAndPeers } from "./seeders-and-peers/seeders-and-peers"; +import { + isMultiplayerRepack, + supportMultiLanguage, +} from "@renderer/helpers/searcher"; +import { Tag } from "@renderer/components/tag/tag"; +import { getRepackLanguageBasedOnRepacker } from "../../helpers/searcher"; +import { useAppSelector } from "@renderer/hooks"; export interface RepacksModalProps { visible: boolean; @@ -26,6 +34,9 @@ export function RepacksModal({ const [filteredRepacks, setFilteredRepacks] = useState([]); const [repack, setRepack] = useState(null); const [showSelectFolderModal, setShowSelectFolderModal] = useState(false); + const { value: userPreferences } = useAppSelector( + (state) => state.userPreferences + ); const { t } = useTranslation("game_details"); @@ -83,12 +94,41 @@ export function RepacksModal({

{repack.title}

-

- {repack.fileSize} - {repack.repacker} -{" "} - {repack.uploadDate - ? format(repack.uploadDate, "dd/MM/yyyy") - : ""} -

+
+
+

+ {repack.fileSize} - {repack.repacker} -{" "} + {repack.uploadDate + ? format(repack.uploadDate, "dd/MM/yyyy") + : ""} + {userPreferences?.language && ( + <> + {" - " + + getRepackLanguageBasedOnRepacker( + repack.repacker, + userPreferences?.language + )} + + )} +

+
+ +
+
+ {supportMultiLanguage(repack.title) && ( + {t("multi_language")} + )} + {isMultiplayerRepack(repack.title, repack.repacker) && ( + {t("multiplayer")} + )} +
))} diff --git a/src/renderer/src/pages/game-details/seeders-and-peers/seeders-and-peers-skeleton.tsx b/src/renderer/src/pages/game-details/seeders-and-peers/seeders-and-peers-skeleton.tsx new file mode 100644 index 00000000..dc0f7381 --- /dev/null +++ b/src/renderer/src/pages/game-details/seeders-and-peers/seeders-and-peers-skeleton.tsx @@ -0,0 +1,20 @@ +import Skeleton from "react-loading-skeleton"; + +export function SeedersAndPeersSkeleton() { + return ( +
+ + +
+ ); +} diff --git a/src/renderer/src/pages/game-details/seeders-and-peers/seeders-and-peers.tsx b/src/renderer/src/pages/game-details/seeders-and-peers/seeders-and-peers.tsx new file mode 100644 index 00000000..ba33abfe --- /dev/null +++ b/src/renderer/src/pages/game-details/seeders-and-peers/seeders-and-peers.tsx @@ -0,0 +1,55 @@ +import { GameRepack } from "@types"; +import UsersIcon from "@renderer/assets/users-icon.svg?react"; +import SproutIcon from "@renderer/assets/sprout-icon.svg?react"; + +import { useMagnetHealth } from "./useMagnetHealth"; +import { Tooltip } from "@renderer/components/tooltip/tooltip"; +import { SeedersAndPeersSkeleton } from "./seeders-and-peers-skeleton"; +import { SPACING_UNIT, vars } from "@renderer/theme.css"; + +interface SeedersAndPeersProps { + repack: GameRepack; +} + +export function SeedersAndPeers({ repack }: Readonly) { + const { magnetData, isLoading, error } = useMagnetHealth(repack.magnet); + + if (isLoading) { + return ; + } + + if (error) { + return null; + } + + return ( +
+ + + + {magnetData?.seeders} + + + + + + {magnetData?.peers} + + +
+ ); +} diff --git a/src/renderer/src/pages/game-details/seeders-and-peers/types.ts b/src/renderer/src/pages/game-details/seeders-and-peers/types.ts new file mode 100644 index 00000000..23532807 --- /dev/null +++ b/src/renderer/src/pages/game-details/seeders-and-peers/types.ts @@ -0,0 +1,5 @@ +export type TorrentData = { + seeders: number; + peers: number; + lastTracked?: Date; +}; diff --git a/src/renderer/src/pages/game-details/seeders-and-peers/useMagnetHealth.tsx b/src/renderer/src/pages/game-details/seeders-and-peers/useMagnetHealth.tsx new file mode 100644 index 00000000..67e6f21e --- /dev/null +++ b/src/renderer/src/pages/game-details/seeders-and-peers/useMagnetHealth.tsx @@ -0,0 +1,80 @@ +import { useEffect, useState } from "react"; +import { TorrentData } from "./types"; + +const cache: Record = {}; + +export function useMagnetHealth(magnet: string) { + const [magnetData, setMagnetData] = useState( + cache[magnet] || null + ); + const [isLoading, setIsLoading] = useState(() => { + if (cache[magnet]) { + return false; + } + + return true; + }); + const [error, setError] = useState(null); + + useEffect(() => { + if (!magnet) { + return; + } + + if (cache[magnet]) { + setMagnetData(cache[magnet]); + setIsLoading(false); + return; + } + + window.electron + .getMagnetHealth(magnet) + .then( + (result) => { + if (result) { + setMagnetData(result); + setIsLoading(false); + + cache[magnet] = result; + cache[magnet].lastTracked = new Date(); + } + }, + (error) => { + setError(error); + setIsLoading(false); + } + ) + .catch(() => {}); + }, [magnet]); + + useEffect(() => { + function invalidateCache() { + const TWO_MINUTES = 2 * 60 * 1000; + const cacheExpiresIn = TWO_MINUTES; + + Object.keys(cache).forEach((key) => { + const lastTracked = cache[key].lastTracked; + + if (!lastTracked) { + return; + } + + if (Date.now() - lastTracked.getTime() > cacheExpiresIn) { + delete cache[key]; + } + }); + } + + invalidateCache(); + + return () => { + invalidateCache(); + }; + }, []); + + return { + magnetData, + isLoading, + error, + }; +} diff --git a/yarn.lock b/yarn.lock index 76e0d8ce..edd68514 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1609,6 +1609,11 @@ acorn@^8.11.3, acorn@^8.9.0: resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.11.3.tgz#71e0b14e13a4ec160724b38fb7b0f233b1b81d7a" integrity sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg== +addr-to-ip-port@^1.0.1: + version "1.5.4" + resolved "https://registry.yarnpkg.com/addr-to-ip-port/-/addr-to-ip-port-1.5.4.tgz#9542b1c6219fdb8c9ce6cc72c14ee880ab7ddd88" + integrity sha512-ByxmJgv8vjmDcl3IDToxL2yrWFrRtFpZAToY0f46XFXl8zS081t7El5MXIodwm7RC6DhHBRoOSMLFSPKCtHukg== + agent-base@6: version "6.0.2" resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" @@ -1928,6 +1933,11 @@ base64-js@^1.3.1, base64-js@^1.5.1: resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== +bencode@^2.0.1, bencode@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/bencode/-/bencode-2.0.3.tgz#89b9c80ea1b8573554915a7d0c15f62b0aa7fc52" + integrity sha512-D/vrAD4dLVX23NalHwb8dSvsUsxeRPO8Y7ToKA015JQYq69MLDOMkC0uGZYA/MPpltLO8rt8eqFC2j8DxjTZ/w== + bencode@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/bencode/-/bencode-4.0.0.tgz#36ca0bc366290dad002215fc52fc74edf4eb0625" @@ -1935,6 +1945,11 @@ bencode@^4.0.0: dependencies: uint8-util "^2.2.2" +bep53-range@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/bep53-range/-/bep53-range-1.1.1.tgz#20fd125b00a413254a77d42f63a43750ca7e64ac" + integrity sha512-ct6s33iiwRCUPp9KXnJ4QMWDgHIgaw36caK/5XEQ9L8dCzSQlJt1Vk6VmHh1VD4AlGCAI4C2zmtfItifBBPrhQ== + bep53-range@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/bep53-range/-/bep53-range-2.0.0.tgz#a1770475661b4b814c4359e4b66f7cbd88de2b10" @@ -1955,6 +1970,43 @@ bindings@^1.5.0: dependencies: file-uri-to-path "1.0.0" +bittorrent-peerid@^1.3.3: + version "1.3.6" + resolved "https://registry.yarnpkg.com/bittorrent-peerid/-/bittorrent-peerid-1.3.6.tgz#3688705a64937a8176ac2ded1178fc7bd91b61db" + integrity sha512-VyLcUjVMEOdSpHaCG/7odvCdLbAB1y3l9A2V6WIje24uV7FkJPrQrH/RrlFmKxP89pFVDEnE+YlHaFujlFIZsg== + +bittorrent-tracker@^9.19.0: + version "9.19.0" + resolved "https://registry.yarnpkg.com/bittorrent-tracker/-/bittorrent-tracker-9.19.0.tgz#2266bfa8a45a57b09f8d8b184710ba531712d8ef" + integrity sha512-09d0aD2b+MC+zWvWajkUAKkYMynYW4tMbTKiRSthKtJZbafzEoNQSUHyND24SoCe3ZOb2fKfa6fu2INAESL9wA== + dependencies: + bencode "^2.0.1" + bittorrent-peerid "^1.3.3" + bn.js "^5.2.0" + chrome-dgram "^3.0.6" + clone "^2.0.0" + compact2string "^1.4.1" + debug "^4.1.1" + ip "^1.1.5" + lru "^3.1.0" + minimist "^1.2.5" + once "^1.4.0" + queue-microtask "^1.2.3" + random-iterate "^1.0.1" + randombytes "^2.1.0" + run-parallel "^1.2.0" + run-series "^1.1.9" + simple-get "^4.0.0" + simple-peer "^9.11.0" + simple-websocket "^9.1.0" + socks "^2.0.0" + string2compact "^1.3.0" + unordered-array-remove "^1.0.2" + ws "^7.4.5" + optionalDependencies: + bufferutil "^4.0.3" + utf-8-validate "^5.0.5" + bl@^4.0.3: version "4.1.0" resolved "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a" @@ -1964,6 +2016,11 @@ bl@^4.0.3: inherits "^2.0.4" readable-stream "^3.4.0" +blob-to-buffer@^1.2.9: + version "1.2.9" + resolved "https://registry.yarnpkg.com/blob-to-buffer/-/blob-to-buffer-1.2.9.tgz#a17fd6c1c564011408f8971e451544245daaa84a" + integrity sha512-BF033y5fN6OCofD3vgHmNtwZWRcq9NLyyxyILx9hfMy1sXYy4ojFl765hJ2lP0YaN2fuxPaLO2Vzzoxy0FLFFA== + bluebird-lst@^1.0.9: version "1.0.9" resolved "https://registry.yarnpkg.com/bluebird-lst/-/bluebird-lst-1.0.9.tgz#a64a0e4365658b9ab5fe875eb9dfb694189bb41c" @@ -1976,6 +2033,11 @@ bluebird@^3.5.5: resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== +bn.js@^5.2.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.1.tgz#0bc527a6a0d18d0aa8d5b0538ce4a77dccfa7b70" + integrity sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ== + boolean@^3.0.1: version "3.2.0" resolved "https://registry.yarnpkg.com/boolean/-/boolean-3.2.0.tgz#9e5294af4e98314494cbb17979fa54ca159f116b" @@ -2044,6 +2106,13 @@ buffer@^6.0.3: base64-js "^1.3.1" ieee754 "^1.2.1" +bufferutil@^4.0.3: + version "4.0.8" + resolved "https://registry.yarnpkg.com/bufferutil/-/bufferutil-4.0.8.tgz#1de6a71092d65d7766c4d8a522b261a6e787e8ea" + integrity sha512-4T53u4PdgsXqKaIctwF8ifXlRTTmEPJ8iEPWFdGZvcf7sbwYo6FKFEX9eNNAnzFZ7EzJAQ3CJeOtCRA4rDp7Pw== + dependencies: + node-gyp-build "^4.3.0" + builder-util-runtime@9.2.3: version "9.2.3" resolved "https://registry.yarnpkg.com/builder-util-runtime/-/builder-util-runtime-9.2.3.tgz#0a82c7aca8eadef46d67b353c638f052c206b83c" @@ -2168,6 +2237,14 @@ chownr@^2.0.0: resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece" integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ== +chrome-dgram@^3.0.6: + version "3.0.6" + resolved "https://registry.yarnpkg.com/chrome-dgram/-/chrome-dgram-3.0.6.tgz#2288b5c7471f66f073691206d36319dda713cf55" + integrity sha512-bqBsUuaOiXiqxXt/zA/jukNJJ4oaOtc7ciwqJpZVEaaXwwxqgI2/ZdG02vXYWUhHGziDlvGMQWk0qObgJwVYKA== + dependencies: + inherits "^2.0.4" + run-series "^1.1.9" + chromium-pickle-js@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/chromium-pickle-js/-/chromium-pickle-js-0.2.0.tgz#04a106672c18b085ab774d983dfa3ea138f22205" @@ -2228,6 +2305,11 @@ clone-response@^1.0.2: dependencies: mimic-response "^1.0.0" +clone@^2.0.0: + version "2.1.2" + resolved "https://registry.yarnpkg.com/clone/-/clone-2.1.2.tgz#1b7f4b9f591f1e8f83670401600345a02887435f" + integrity sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w== + color-convert@^1.9.0, color-convert@^1.9.3: version "1.9.3" resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" @@ -2301,6 +2383,13 @@ commander@^5.0.0: resolved "https://registry.yarnpkg.com/commander/-/commander-5.1.0.tgz#46abbd1652f8e059bddaef99bbdcb2ad9cf179ae" integrity sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg== +compact2string@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/compact2string/-/compact2string-1.4.1.tgz#8d34929055f8300a13cfc030ad1832e2e53c2e25" + integrity sha512-3D+EY5nsRhqnOwDxveBv5T8wGo4DEvYxjDtPGmdOX+gfr5gE92c2RC0w2wa+xEefm07QuVqqcF3nZJUZ92l/og== + dependencies: + ipaddr.js ">= 0.1.5" + compare-func@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/compare-func/-/compare-func-2.0.0.tgz#fb65e75edbddfd2e568554e8b5b05fff7a51fcb3" @@ -2797,6 +2886,11 @@ err-code@^2.0.2: resolved "https://registry.yarnpkg.com/err-code/-/err-code-2.0.3.tgz#23c2f3b756ffdfc608d30e27c9a941024807e7f9" integrity sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA== +err-code@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/err-code/-/err-code-3.0.1.tgz#a444c7b992705f2b120ee320b09972eef331c920" + integrity sha512-GiaH0KJUewYok+eeY05IIgjtAe4Yltygk9Wqp1V5yVWLdhf0hYZchRjNIT9bb0mSwRcIusT3cx7PJUf3zEIfUA== + error-ex@^1.3.1: version "1.3.2" resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" @@ -3450,6 +3544,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-browser-rtc@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/get-browser-rtc/-/get-browser-rtc-1.1.0.tgz#d1494e299b00f33fc8e9d6d3343ba4ba99711a2c" + integrity sha512-MghbMJ61EJrRsDe7w1Bvqt3ZsBuqhce5nrn/XAwgwOXhcsz53/ltdxOse1h/8eKXj5slzxdsz56g5rzOFSGwfQ== + get-caller-file@^2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" @@ -3466,6 +3565,11 @@ get-intrinsic@^1.1.3, get-intrinsic@^1.2.1, get-intrinsic@^1.2.3, get-intrinsic@ has-symbols "^1.0.3" hasown "^2.0.0" +get-stdin@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-8.0.0.tgz#cbad6a73feb75f6eeb22ba9e01f89aa28aa97a53" + integrity sha512-sY22aA6xchAzprjyqmSEQv4UbAAzRN0L2dQB0NlN5acTTK9Don6nhoc3eAbUnpZiCANAMfd/+40kVdKfFygohg== + get-stdin@^9.0.0: version "9.0.0" resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-9.0.0.tgz#3983ff82e03d56f1b2ea0d3e60325f39d703a575" @@ -3850,6 +3954,24 @@ internal-slot@^1.0.7: hasown "^2.0.0" side-channel "^1.0.4" +ip-address@^9.0.5: + version "9.0.5" + resolved "https://registry.yarnpkg.com/ip-address/-/ip-address-9.0.5.tgz#117a960819b08780c3bd1f14ef3c1cc1d3f3ea5a" + integrity sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g== + dependencies: + jsbn "1.1.0" + sprintf-js "^1.1.3" + +ip@^1.1.5: + version "1.1.9" + resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.9.tgz#8dfbcc99a754d07f425310b86a99546b1151e396" + integrity sha512-cyRxvOEpNHNtchU3Ln9KC/auJgup87llfQpQ+t5ghoC/UhL16SWzbueiCsdTnWmqAWl7LadfuwhlqmtOaqMHdQ== + +"ipaddr.js@>= 0.1.5", ipaddr.js@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-2.2.0.tgz#d33fa7bac284f4de7af949638c9d68157c6b92e8" + integrity sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA== + is-array-buffer@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/is-array-buffer/-/is-array-buffer-3.0.4.tgz#7a1f92b3d61edd2bc65d24f130530ea93d7fae98" @@ -4141,6 +4263,11 @@ js-yaml@^4.1.0: dependencies: argparse "^2.0.1" +jsbn@1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-1.1.0.tgz#b01307cb29b618a1ed26ec79e911f803c4da0040" + integrity sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A== + jsdom@^24.0.0: version "24.0.0" resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-24.0.0.tgz#e2dc04e4c79da368481659818ee2b0cd7c39007c" @@ -4427,6 +4554,13 @@ lru-cache@^6.0.0: dependencies: yallist "^4.0.0" +lru@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/lru/-/lru-3.1.0.tgz#ea7fb8546d83733396a13091d76cfeb4c06837d5" + integrity sha512-5OUtoiVIGU4VXBOshidmtOsvBIvcQR6FD/RzWSvaeHyxCGB+PCUCu+52lqMfdc0h/2CLvHhZS4TwUmMQrrMbBQ== + dependencies: + inherits "^2.0.1" + magic-string@^0.30.5: version "0.30.10" resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.10.tgz#123d9c41a0cb5640c892b041d4cfb3bd0aa4b39e" @@ -4434,6 +4568,14 @@ magic-string@^0.30.5: dependencies: "@jridgewell/sourcemap-codec" "^1.4.15" +magnet-uri@^6.2.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/magnet-uri/-/magnet-uri-6.2.0.tgz#10f7be050bf23452df210838239b118463c3eeff" + integrity sha512-O9AgdDwT771fnUj0giPYu/rACpz8173y8UXCSOdLITjOVfBenZ9H9q3FqQmveK+ORUMuD+BkKNSZP8C3+IMAKQ== + dependencies: + bep53-range "^1.1.0" + thirty-two "^1.0.2" + magnet-uri@^7.0.5: version "7.0.5" resolved "https://registry.yarnpkg.com/magnet-uri/-/magnet-uri-7.0.5.tgz#7b5143fd5527f3f612959eeeae264d6f4aeff37b" @@ -4545,7 +4687,7 @@ minimist@1.2.6: resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44" integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q== -minimist@^1.2.0, minimist@^1.2.3, minimist@^1.2.6, minimist@^1.2.8: +minimist@^1.2.0, minimist@^1.2.3, minimist@^1.2.5, minimist@^1.2.6, minimist@^1.2.8: version "1.2.8" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== @@ -4701,6 +4843,11 @@ node-fetch@^3.3.0: fetch-blob "^3.1.4" formdata-polyfill "^4.0.10" +node-gyp-build@^4.3.0: + version "4.8.1" + resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.8.1.tgz#976d3ad905e71b76086f4f0b0d3637fe79b6cda5" + integrity sha512-OSs33Z9yWr148JZcbZd5WiAXhh/n9z8TxQcdMhIOlpN9AhWpLfvVFO73+m77bBABQMaY9XSvIa+qk0jlI7Gcaw== + node-releases@^2.0.14: version "2.0.14" resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.14.tgz#2ffb053bceb8b2be8495ece1ab6ce600c4461b0b" @@ -4914,6 +5061,19 @@ parse-torrent@^11.0.16: queue-microtask "^1.2.3" uint8-util "^2.2.4" +parse-torrent@^9.1.5: + version "9.1.5" + resolved "https://registry.yarnpkg.com/parse-torrent/-/parse-torrent-9.1.5.tgz#fcae5f360d9baf617d9a2de68e74d5de4c8099fd" + integrity sha512-K8FXRwTOaZMI0/xuv0dpng1MVHZRtMJ0jRWBJ3qZWVNTrC1MzWUxm9QwaXDz/2qPhV2XC4UIHI92IGHwseAwaA== + dependencies: + bencode "^2.0.2" + blob-to-buffer "^1.2.9" + get-stdin "^8.0.0" + magnet-uri "^6.2.0" + queue-microtask "^1.2.3" + simple-get "^4.0.1" + simple-sha1 "^3.1.0" + parse5-htmlparser2-tree-adapter@^6.0.0: version "6.0.1" resolved "https://registry.yarnpkg.com/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz#2cdf9ad823321140370d4dbf5d3e92c7c8ddc6e6" @@ -5133,6 +5293,18 @@ quick-lru@^5.1.1: resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-5.1.1.tgz#366493e6b3e42a3a6885e2e99d18f80fb7a8c932" integrity sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA== +random-iterate@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/random-iterate/-/random-iterate-1.0.1.tgz#f7d97d92dee6665ec5f6da08c7f963cad4b2ac99" + integrity sha512-Jdsdnezu913Ot8qgKgSgs63XkAjEsnMcS1z+cC6D6TNXsUXsMxy0RpclF2pzGZTEiTXL9BiArdGTEexcv4nqcA== + +randombytes@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" + integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== + dependencies: + safe-buffer "^5.1.0" + rc@^1.2.7: version "1.2.8" resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" @@ -5383,13 +5555,23 @@ rrweb-cssom@^0.6.0: resolved "https://registry.yarnpkg.com/rrweb-cssom/-/rrweb-cssom-0.6.0.tgz#ed298055b97cbddcdeb278f904857629dec5e0e1" integrity sha512-APM0Gt1KoXBz0iIkkdB/kfvGOwC4UuJFeG/c+yV7wSc7q96cG/kJ0HiYCnzivD9SB53cLV1MlHFNfOuPaadYSw== -run-parallel@^1.1.9: +run-parallel@^1.1.9, run-parallel@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== dependencies: queue-microtask "^1.2.2" +run-series@^1.1.9: + version "1.1.9" + resolved "https://registry.yarnpkg.com/run-series/-/run-series-1.1.9.tgz#15ba9cb90e6a6c054e67c98e1dc063df0ecc113a" + integrity sha512-Arc4hUN896vjkqCYrUXquBFtRZdv1PfLbTYP71efP6butxyQ0kWpiNJyAgsxscmQg1cqvHY32/UCBzXedTpU2g== + +rusha@^0.8.13: + version "0.8.14" + resolved "https://registry.yarnpkg.com/rusha/-/rusha-0.8.14.tgz#a977d0de9428406138b7bb90d3de5dcd024e2f68" + integrity sha512-cLgakCUf6PedEu15t8kbsjnwIFFR2D4RfL+W3iWFJ4iac7z4B0ZI8fxy4R3J956kAI68HclCFGL8MPoUVC3qVA== + safe-array-concat@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/safe-array-concat/-/safe-array-concat-1.1.2.tgz#81d77ee0c4e8b863635227c721278dd524c20edb" @@ -5400,7 +5582,7 @@ safe-array-concat@^1.1.2: has-symbols "^1.0.3" isarray "^2.0.5" -safe-buffer@^5.0.1, safe-buffer@~5.2.0: +safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@~5.2.0: version "5.2.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== @@ -5539,7 +5721,7 @@ simple-concat@^1.0.0: resolved "https://registry.yarnpkg.com/simple-concat/-/simple-concat-1.0.1.tgz#f46976082ba35c2263f1c8ab5edfe26c41c9552f" integrity sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q== -simple-get@^4.0.0: +simple-get@^4.0.0, simple-get@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-4.0.1.tgz#4a39db549287c979d352112fa03fd99fd6bc3543" integrity sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA== @@ -5548,6 +5730,27 @@ simple-get@^4.0.0: once "^1.3.1" simple-concat "^1.0.0" +simple-peer@^9.11.0: + version "9.11.1" + resolved "https://registry.yarnpkg.com/simple-peer/-/simple-peer-9.11.1.tgz#9814d5723f821b778b7fb011bdefcbd1e788e6cc" + integrity sha512-D1SaWpOW8afq1CZGWB8xTfrT3FekjQmPValrqncJMX7QFl8YwhrPTZvMCANLtgBwwdS+7zURyqxDDEmY558tTw== + dependencies: + buffer "^6.0.3" + debug "^4.3.2" + err-code "^3.0.1" + get-browser-rtc "^1.1.0" + queue-microtask "^1.2.3" + randombytes "^2.1.0" + readable-stream "^3.6.0" + +simple-sha1@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/simple-sha1/-/simple-sha1-3.1.0.tgz#40cac8436dfaf9924332fc46a5c7bca45f656131" + integrity sha512-ArTptMRC1v08H8ihPD6l0wesKvMfF9e8XL5rIHPanI7kGOsSsbY514MwVu6X1PITHCTB2F08zB7cyEbfc4wQjg== + dependencies: + queue-microtask "^1.2.2" + rusha "^0.8.13" + simple-swizzle@^0.2.2: version "0.2.2" resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz#a4da6b635ffcccca33f70d17cb92592de95e557a" @@ -5562,6 +5765,17 @@ simple-update-notifier@2.0.0: dependencies: semver "^7.5.3" +simple-websocket@^9.1.0: + version "9.1.0" + resolved "https://registry.yarnpkg.com/simple-websocket/-/simple-websocket-9.1.0.tgz#91cbb39eafefbe7e66979da6c639109352786a7f" + integrity sha512-8MJPnjRN6A8UCp1I+H/dSFyjwJhp6wta4hsVRhjf8w9qBHRzxYt14RaOcjvQnhD1N4yKOddEjflwMnQM4VtXjQ== + dependencies: + debug "^4.3.1" + queue-microtask "^1.2.2" + randombytes "^2.1.0" + readable-stream "^3.6.0" + ws "^7.4.2" + slash@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" @@ -5576,7 +5790,7 @@ slice-ansi@^3.0.0: astral-regex "^2.0.0" is-fullwidth-code-point "^3.0.0" -smart-buffer@^4.0.2: +smart-buffer@^4.0.2, smart-buffer@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-4.2.0.tgz#6e1d71fa4f18c05f7d0ff216dd16a481d0e8d9ae" integrity sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg== @@ -5589,6 +5803,14 @@ snake-case@^3.0.4: dot-case "^3.0.4" tslib "^2.0.3" +socks@^2.0.0: + version "2.8.3" + resolved "https://registry.yarnpkg.com/socks/-/socks-2.8.3.tgz#1ebd0f09c52ba95a09750afe3f3f9f724a800cb5" + integrity sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw== + dependencies: + ip-address "^9.0.5" + smart-buffer "^4.2.0" + source-map-js@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.0.tgz#16b809c162517b5b8c3e7dcd315a2a5c2612b2af" @@ -5612,7 +5834,7 @@ split2@^4.0.0: resolved "https://registry.yarnpkg.com/split2/-/split2-4.2.0.tgz#c9c5920904d148bab0b9f67145f245a86aadbfa4" integrity sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg== -sprintf-js@^1.1.2: +sprintf-js@^1.1.2, sprintf-js@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.1.3.tgz#4914b903a2f8b685d17fdf78a70e917e872e444a" integrity sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA== @@ -5700,6 +5922,14 @@ string.prototype.trimstart@^1.0.8: define-properties "^1.2.1" es-object-atoms "^1.0.0" +string2compact@^1.3.0: + version "1.3.2" + resolved "https://registry.yarnpkg.com/string2compact/-/string2compact-1.3.2.tgz#c9d11a13f368404b8025425cc53f9916de1d0b8b" + integrity sha512-3XUxUgwhj7Eqh2djae35QHZZT4mN3fsO7kagZhSGmhhlrQagVvWSFuuFIWnpxFS0CdTB2PlQcaL16RDi14I8uw== + dependencies: + addr-to-ip-port "^1.0.1" + ipaddr.js "^2.0.0" + string_decoder@^1.1.1: version "1.3.0" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" @@ -5857,6 +6087,11 @@ thenify-all@^1.0.0: dependencies: any-promise "^1.0.0" +thirty-two@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/thirty-two/-/thirty-two-1.0.2.tgz#4ca2fffc02a51290d2744b9e3f557693ca6b627a" + integrity sha512-OEI0IWCe+Dw46019YLl6V10Us5bi574EvlJEOcAkB29IzQ/mYD1A6RyNHLjZPiHCmuodxvgF6U+vZO1L15lxVA== + "through@>=2.2.7 <3": version "2.3.8" resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" @@ -6076,6 +6311,11 @@ universalify@^2.0.0: resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.1.tgz#168efc2180964e6386d061e094df61afe239b18d" integrity sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw== +unordered-array-remove@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/unordered-array-remove/-/unordered-array-remove-1.0.2.tgz#c546e8f88e317a0cf2644c97ecb57dba66d250ef" + integrity sha512-45YsfD6svkgaCBNyvD+dFHm4qFX9g3wRSIVgWVPtm2OCnphvPxzJoe20ATsiNpNJrmzHifnxm+BN5F7gFT/4gw== + untildify@^3.0.2: version "3.0.3" resolved "https://registry.yarnpkg.com/untildify/-/untildify-3.0.3.tgz#1e7b42b140bcfd922b22e70ca1265bfe3634c7c9" @@ -6116,6 +6356,13 @@ user-agents@^1.1.193: dependencies: lodash.clonedeep "^4.5.0" +utf-8-validate@^5.0.5: + version "5.0.10" + resolved "https://registry.yarnpkg.com/utf-8-validate/-/utf-8-validate-5.0.10.tgz#d7d10ea39318171ca982718b6b96a8d2442571a2" + integrity sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ== + dependencies: + node-gyp-build "^4.3.0" + utf8-byte-length@^1.0.1: version "1.0.5" resolved "https://registry.yarnpkg.com/utf8-byte-length/-/utf8-byte-length-1.0.5.tgz#f9f63910d15536ee2b2d5dd4665389715eac5c1e" @@ -6198,6 +6445,14 @@ webidl-conversions@^7.0.0: resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-7.0.0.tgz#256b4e1882be7debbf01d05f0aa2039778ea080a" integrity sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g== +webtorrent-health@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/webtorrent-health/-/webtorrent-health-1.2.0.tgz#f127fe4dba603e3bbed4224c1de8e18034ab02a2" + integrity sha512-id51LF+1IGvqSXhdO9rl9riDLGRPO7e/DrjcNJ/1PhTp5O3+1U50+ogDEJyxQvIISZV//xYX+kMM4Fn9cogfCg== + dependencies: + bittorrent-tracker "^9.19.0" + parse-torrent "^9.1.5" + whatwg-encoding@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz#d0f4ef769905d426e1688f3e34381a99b60b76e5" @@ -6356,6 +6611,11 @@ wrappy@1: resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== +ws@^7.4.2, ws@^7.4.5: + version "7.5.9" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.9.tgz#54fa7db29f4c7cec68b1ddd3a89de099942bb591" + integrity sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q== + ws@^8.16.0: version "8.17.0" resolved "https://registry.yarnpkg.com/ws/-/ws-8.17.0.tgz#d145d18eca2ed25aaf791a183903f7be5e295fea"