diff --git a/package.json b/package.json index a483e87f..2ecde1b8 100644 --- a/package.json +++ b/package.json @@ -53,6 +53,7 @@ "jsdom": "^24.0.0", "lodash-es": "^4.17.21", "lottie-react": "^2.4.0", + "node-7z-archive": "^1.1.7", "parse-torrent": "^11.0.16", "ps-list": "^8.1.1", "react-i18next": "^14.1.0", diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index 0b225ff5..1e441854 100644 --- a/src/locales/en/translation.json +++ b/src/locales/en/translation.json @@ -127,7 +127,9 @@ "remove_from_list": "Remove", "delete_modal_title": "Are you sure?", "delete_modal_description": "This will remove all the installation files from your computer", - "install": "Install" + "install": "Install", + "real_debrid": "Real Debrid", + "torrent": "Torrent" }, "settings": { "downloads_path": "Downloads path", @@ -138,9 +140,11 @@ "telemetry": "Telemetry", "telemetry_description": "Enable anonymous usage statistics", "real_debrid_api_token_description": "(Optional) Real Debrid API token", + "quit_app_instead_hiding": "Quit Hydra instead of minimizing to tray", + "launch_with_system": "Launch Hydra on system start-up", + "general": "General", "behavior": "Behavior", - "quit_app_instead_hiding": "Close app instead of minimizing to tray", - "launch_with_system": "Launch app on system start-up" + "real_debrid": "Real Debrid" }, "notifications": { "download_complete": "Download complete", diff --git a/src/main/events/torrenting/start-game-download.ts b/src/main/events/torrenting/start-game-download.ts index 8ce62f0b..42ad2e84 100644 --- a/src/main/events/torrenting/start-game-download.ts +++ b/src/main/events/torrenting/start-game-download.ts @@ -26,7 +26,7 @@ const startGameDownload = async ( }); const downloader = userPreferences?.realDebridApiToken - ? Downloader.Http + ? Downloader.RealDebrid : Downloader.Torrent; const [game, repack] = await Promise.all([ diff --git a/src/main/services/download-manager.ts b/src/main/services/download-manager.ts index ca98fe82..e345835a 100644 --- a/src/main/services/download-manager.ts +++ b/src/main/services/download-manager.ts @@ -4,7 +4,7 @@ import type { Game } from "@main/entity"; import { Downloader } from "@shared"; import { writePipe } from "./fifo"; -import { HTTPDownloader } from "./downloaders"; +import { RealDebridDownloader } from "./downloaders"; export class DownloadManager { private static gameDownloading: Game; @@ -25,7 +25,7 @@ export class DownloadManager { ) { writePipe.write({ action: "cancel" }); } else { - HTTPDownloader.destroy(); + RealDebridDownloader.destroy(); } } @@ -36,7 +36,7 @@ export class DownloadManager { ) { writePipe.write({ action: "pause" }); } else { - HTTPDownloader.destroy(); + RealDebridDownloader.destroy(); } } @@ -51,7 +51,7 @@ export class DownloadManager { save_path: game!.downloadPath, }); } else { - HTTPDownloader.startDownload(game!); + RealDebridDownloader.startDownload(game!); } this.gameDownloading = game!; @@ -68,7 +68,7 @@ export class DownloadManager { save_path: game!.downloadPath, }); } else { - HTTPDownloader.startDownload(game!); + RealDebridDownloader.startDownload(game!); } this.gameDownloading = game!; diff --git a/src/main/services/downloaders/index.ts b/src/main/services/downloaders/index.ts index 54026581..cd742107 100644 --- a/src/main/services/downloaders/index.ts +++ b/src/main/services/downloaders/index.ts @@ -1,2 +1,2 @@ -export * from "./http.downloader"; +export * from "./real-debrid.downloader"; export * from "./torrent.downloader"; diff --git a/src/main/services/downloaders/http.downloader.ts b/src/main/services/downloaders/real-debrid.downloader.ts similarity index 59% rename from src/main/services/downloaders/http.downloader.ts rename to src/main/services/downloaders/real-debrid.downloader.ts index 6633d56a..38b27931 100644 --- a/src/main/services/downloaders/http.downloader.ts +++ b/src/main/services/downloaders/real-debrid.downloader.ts @@ -4,11 +4,12 @@ import path from "node:path"; import fs from "node:fs"; import EasyDL from "easydl"; import { GameStatus } from "@shared"; +import { fullArchive } from "node-7z-archive"; import { Downloader } from "./downloader"; import { RealDebridClient } from "../real-debrid"; -export class HTTPDownloader extends Downloader { +export class RealDebridDownloader extends Downloader { private static download: EasyDL; private static downloadSize = 0; @@ -22,52 +23,48 @@ export class HTTPDownloader extends Downloader { return 1; } - static async getDownloadUrl(game: Game) { - const torrents = await RealDebridClient.getAllTorrentsFromUser(); - const hash = RealDebridClient.extractSHA1FromMagnet(game!.repack.magnet); - let torrent = torrents.find((t) => t.hash === hash); - - if (!torrent) { - const magnet = await RealDebridClient.addMagnet(game!.repack.magnet); - - if (magnet && magnet.id) { - await RealDebridClient.selectAllFiles(magnet.id); - torrent = await RealDebridClient.getInfo(magnet.id); - } - } - - if (torrent) { - const { links } = torrent; - const { download } = await RealDebridClient.unrestrictLink(links[0]); - - if (!download) { - throw new Error("Torrent not cached on Real Debrid"); - } - - return download; - } - - throw new Error(); - } - private static createFolderIfNotExists(path: string) { if (!fs.existsSync(path)) { fs.mkdirSync(path); } } + private static async startDecompression( + rarFile: string, + dest: string, + game: Game + ) { + await fullArchive(rarFile, dest); + + const updatePayload: QueryDeepPartialEntity = { + status: GameStatus.Finished, + progress: 1, + }; + + await this.updateGameProgress(game.id, updatePayload, { + timeRemaining: 0, + }); + } + + static destroy() { + if (this.download) { + this.download.destroy(); + } + } + static async startDownload(game: Game) { if (this.download) this.download.destroy(); - const downloadUrl = await this.getDownloadUrl(game); + const downloadUrl = decodeURIComponent( + await RealDebridClient.getDownloadUrl(game) + ); const filename = path.basename(downloadUrl); const folderName = path.basename(filename, path.extname(filename)); - const fullDownloadPath = path.join(game.downloadPath!, folderName); + const downloadPath = path.join(game.downloadPath!, folderName); + this.createFolderIfNotExists(downloadPath); - this.createFolderIfNotExists(fullDownloadPath); - - this.download = new EasyDL(downloadUrl, fullDownloadPath); + this.download = new EasyDL(downloadUrl, path.join(downloadPath, filename)); const metadata = await this.download.metadata(); @@ -76,7 +73,7 @@ export class HTTPDownloader extends Downloader { const updatePayload: QueryDeepPartialEntity = { status: GameStatus.Downloading, fileSize: metadata.size, - folderName: folderName, + folderName, }; const downloadStatus = { @@ -87,11 +84,8 @@ export class HTTPDownloader extends Downloader { this.download.on("progress", async ({ total }) => { const updatePayload: QueryDeepPartialEntity = { - status: - total.percentage === 100 - ? GameStatus.Finished - : GameStatus.Downloading, - progress: total.percentage / 100, + status: GameStatus.Downloading, + progress: Math.min(0.99, total.percentage / 100), bytesDownloaded: total.bytes, }; @@ -102,11 +96,22 @@ export class HTTPDownloader extends Downloader { await this.updateGameProgress(game.id, updatePayload, downloadStatus); }); - } - static destroy() { - if (this.download) { - this.download.destroy(); - } + this.download.on("end", async () => { + const updatePayload: QueryDeepPartialEntity = { + status: GameStatus.Decompressing, + progress: 0.99, + }; + + await this.updateGameProgress(game.id, updatePayload, { + timeRemaining: 0, + }); + + this.startDecompression( + path.join(downloadPath, filename), + downloadPath, + game + ); + }); } } diff --git a/src/main/services/real-debrid.ts b/src/main/services/real-debrid.ts index b035907c..7fa12e86 100644 --- a/src/main/services/real-debrid.ts +++ b/src/main/services/real-debrid.ts @@ -62,6 +62,34 @@ export class RealDebridClient { return magnet.match(/btih:([0-9a-fA-F]*)/)?.[1].toLowerCase(); } + static async getDownloadUrl(game: Game) { + const torrents = await RealDebridClient.getAllTorrentsFromUser(); + const hash = RealDebridClient.extractSHA1FromMagnet(game!.repack.magnet); + let torrent = torrents.find((t) => t.hash === hash); + + if (!torrent) { + const magnet = await RealDebridClient.addMagnet(game!.repack.magnet); + + if (magnet && magnet.id) { + await RealDebridClient.selectAllFiles(magnet.id); + torrent = await RealDebridClient.getInfo(magnet.id); + } + } + + if (torrent) { + const { links } = torrent; + const { download } = await RealDebridClient.unrestrictLink(links[0]); + + if (!download) { + throw new Error("Torrent not cached on Real Debrid"); + } + + return download; + } + + throw new Error(); + } + static async authorize(apiToken: string) { this.instance = axios.create({ baseURL: base, diff --git a/src/renderer/src/pages/downloads/downloads.tsx b/src/renderer/src/pages/downloads/downloads.tsx index 1033e821..a9e78385 100644 --- a/src/renderer/src/pages/downloads/downloads.tsx +++ b/src/renderer/src/pages/downloads/downloads.tsx @@ -66,7 +66,7 @@ export function Downloads() { }; const downloaderName = { - [Downloader.Http]: t("real_debrid"), + [Downloader.RealDebrid]: t("real_debrid"), [Downloader.Torrent]: t("torrent"), }; diff --git a/src/renderer/src/pages/game-details/gallery-slider.tsx b/src/renderer/src/pages/game-details/gallery-slider.tsx index d506103c..cb8ad052 100644 --- a/src/renderer/src/pages/game-details/gallery-slider.tsx +++ b/src/renderer/src/pages/game-details/gallery-slider.tsx @@ -1,7 +1,7 @@ import { useEffect, useRef, useState } from "react"; import { ShopDetails, SteamMovies, SteamScreenshot } from "@types"; import { ChevronRightIcon, ChevronLeftIcon } from "@primer/octicons-react"; -import * as styles from "./game-details.css"; +import * as styles from "./gallery-slider.css"; export interface GallerySliderProps { gameDetails: ShopDetails | null; @@ -22,6 +22,7 @@ export function GallerySlider({ gameDetails }: GallerySliderProps) { } return 0; }); + const [mediaIndex, setMediaIndex] = useState(0); const [arrowShow, setArrowShow] = useState(false); @@ -41,6 +42,10 @@ export function GallerySlider({ gameDetails }: GallerySliderProps) { }); }; + useEffect(() => { + setMediaIndex(0); + }, [gameDetails]); + useEffect(() => { if (scrollContainerRef.current) { const container = scrollContainerRef.current; @@ -49,10 +54,10 @@ export function GallerySlider({ gameDetails }: GallerySliderProps) { const scrollLeft = mediaIndex * itemWidth; container.scrollLeft = scrollLeft; } - }, [mediaIndex, mediaCount]); + }, [gameDetails, mediaIndex, mediaCount]); - const hasScreenshots = gameDetails && gameDetails.screenshots.length > 0; - const hasMovies = gameDetails && gameDetails.movies.length > 0; + const hasScreenshots = gameDetails && gameDetails.screenshots.length; + const hasMovies = gameDetails && gameDetails.movies?.length; return ( <> @@ -72,6 +77,7 @@ export function GallerySlider({ gameDetails }: GallerySliderProps) { poster={video.thumbnail} style={{ translate: `${-100 * mediaIndex}%` }} autoPlay + loop muted > @@ -112,7 +118,7 @@ export function GallerySlider({ gameDetails }: GallerySliderProps) {
{hasMovies && - gameDetails.movies.map((video: SteamMovies, i: number) => ( + gameDetails.movies?.map((video: SteamMovies, i: number) => ( setMediaIndex(i)} diff --git a/src/renderer/src/pages/game-details/game-details.css.ts b/src/renderer/src/pages/game-details/game-details.css.ts index 8b331480..72c5e4d3 100644 --- a/src/renderer/src/pages/game-details/game-details.css.ts +++ b/src/renderer/src/pages/game-details/game-details.css.ts @@ -79,95 +79,6 @@ export const descriptionContent = style({ height: "100%", }); -export const gallerySliderContainer = style({ - padding: `${SPACING_UNIT * 3}px ${SPACING_UNIT * 2}px`, - width: "100%", - display: "flex", - flexDirection: "column", - alignItems: "center", -}); - -export const gallerySliderMedia = style({ - width: "100%", - height: "100%", - display: "block", - flexShrink: 0, - flexGrow: "0", - transition: "translate 0.3s ease-in-out", - borderRadius: "4px", -}); - -export const gallerySliderAnimationContainer = style({ - width: "100%", - height: "100%", - display: "flex", - position: "relative", - overflow: "hidden", - "@media": { - "(min-width: 1280px)": { - width: "60%", - }, - }, -}); - -export const gallerySliderPreview = style({ - width: "100%", - paddingTop: "0.5rem", - height: "100%", - display: "flex", - position: "relative", - overflowX: "auto", - overflowY: "hidden", - "@media": { - "(min-width: 1280px)": { - width: "60%", - }, - }, - "::-webkit-scrollbar-thumb": { - width: "20%", - }, -}); - -export const gallerySliderMediaPreview = style({ - cursor: "pointer", - width: "20%", - height: "20%", - display: "block", - flexShrink: 0, - flexGrow: 0, - opacity: 0.3, - paddingRight: "5px", - transition: "translate 300ms ease-in-out", - borderRadius: "4px", - ":hover": { - opacity: 1, - }, -}); - -export const gallerySliderMediaPreviewActive = style({ - opacity: 1, -}); - -export const gallerySliderButton = style({ - all: "unset", - display: "block", - position: "absolute", - top: 0, - bottom: 0, - padding: "1rem", - cursor: "pointer", - transition: "background-color 100ms ease-in-out", - ":hover": { - backgroundColor: "rgb(0, 0, 0, 0.2)", - }, -}); - -export const gallerySliderIcons = style({ - fill: vars.color.muted, - width: "2rem", - height: "2rem", -}); - export const contentSidebar = style({ borderLeft: `solid 1px ${vars.color.border};`, width: "100%", diff --git a/src/renderer/src/pages/game-details/game-details.tsx b/src/renderer/src/pages/game-details/game-details.tsx index 02c880d8..119adcde 100644 --- a/src/renderer/src/pages/game-details/game-details.tsx +++ b/src/renderer/src/pages/game-details/game-details.tsx @@ -257,6 +257,13 @@ export function GameDetails() { }} className={styles.description} /> + + + All screenshots and movies displayed on this page are the + property of Steam and/or their respective owners. We do not + claim ownership of any content unless otherwise stated. All + content is used for informational and promotional purposes only. +
diff --git a/src/renderer/src/pages/settings/settings.css.ts b/src/renderer/src/pages/settings/settings.css.ts index 7b1dd60d..52f5ff1e 100644 --- a/src/renderer/src/pages/settings/settings.css.ts +++ b/src/renderer/src/pages/settings/settings.css.ts @@ -24,3 +24,8 @@ export const downloadsPathField = style({ display: "flex", gap: `${SPACING_UNIT}px`, }); + +export const settingsCategories = style({ + display: "flex", + gap: `${SPACING_UNIT}px`, +}); diff --git a/src/renderer/src/pages/settings/settings.tsx b/src/renderer/src/pages/settings/settings.tsx index fb465bb8..5a5a6ab2 100644 --- a/src/renderer/src/pages/settings/settings.tsx +++ b/src/renderer/src/pages/settings/settings.tsx @@ -5,7 +5,11 @@ import * as styles from "./settings.css"; import { useTranslation } from "react-i18next"; import { UserPreferences } from "@types"; +const categories = ["general", "behavior", "real_debrid"]; + export function Settings() { + const [currentCategory, setCurrentCategory] = useState(categories.at(0)!); + const [form, setForm] = useState({ downloadsPath: "", downloadNotificationsEnabled: false, @@ -61,62 +65,80 @@ export function Settings() { } }; - return ( -
-
-
- { + if (currentCategory === "general") { + return ( + <> +
+ + + +
+ +

{t("notifications")}

+ + + updateUserPreferences( + "downloadNotificationsEnabled", + !form.downloadNotificationsEnabled + ) + } /> - -
+ + updateUserPreferences( + "repackUpdatesNotificationsEnabled", + !form.repackUpdatesNotificationsEnabled + ) + } + /> -

{t("notifications")}

+

{t("telemetry")}

- - updateUserPreferences( - "downloadNotificationsEnabled", - !form.downloadNotificationsEnabled - ) - } + + updateUserPreferences("telemetryEnabled", !form.telemetryEnabled) + } + /> + + ); + } + + if (currentCategory === "real_debrid") { + return ( + { + updateUserPreferences("realDebridApiToken", event.target.value); + }} + placeholder="API Token" /> + ); + } - - updateUserPreferences( - "repackUpdatesNotificationsEnabled", - !form.repackUpdatesNotificationsEnabled - ) - } - /> - -

{t("telemetry")}

- - - updateUserPreferences("telemetryEnabled", !form.telemetryEnabled) - } - /> - -

{t("behavior")}

- + return ( + <> - { - updateUserPreferences("realDebridApiToken", event.target.value); - }} - /> - { @@ -145,6 +158,27 @@ export function Settings() { }} checked={form.runAtStartup} /> + + ); + }; + + return ( +
+
+
+ {categories.map((category) => ( + + ))} +
+ +

{t(currentCategory)}

+ {renderCategory()}
); diff --git a/src/shared/index.ts b/src/shared/index.ts index 7193ac09..bedf98fb 100644 --- a/src/shared/index.ts +++ b/src/shared/index.ts @@ -5,11 +5,12 @@ export enum GameStatus { CheckingFiles = "checking_files", DownloadingMetadata = "downloading_metadata", Cancelled = "cancelled", + Decompressing = "decompressing", Finished = "finished", } export enum Downloader { - Http, + RealDebrid, Torrent, } diff --git a/src/types/index.ts b/src/types/index.ts index 21029a2f..94361564 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -16,7 +16,7 @@ export interface SteamScreenshot { export interface SteamVideoSource { max: string; - '480': string; + "480": string; } export interface SteamMovies { @@ -35,7 +35,7 @@ export interface SteamAppDetails { short_description: string; publishers: string[]; genres: SteamGenre[]; - movies: SteamMovies[]; + movies?: SteamMovies[]; screenshots: SteamScreenshot[]; pc_requirements: { minimum: string; diff --git a/yarn.lock b/yarn.lock index cdb52ab7..f3016846 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1440,7 +1440,7 @@ dependencies: undici-types "~5.26.4" -"@types/node@^18.11.18": +"@types/node@^18.11.18", "@types/node@^18.7.13": version "18.19.31" resolved "https://registry.npmjs.org/@types/node/-/node-18.19.31.tgz" integrity sha512-ArgCD39YpyyrtFKIqMDvjz79jto5fcI/SVUs2HwB+f0dAzq68yqOdyaSivLiLugSziTpNXLQrVb7RZFmdZzbhA== @@ -1523,6 +1523,11 @@ resolved "https://registry.yarnpkg.com/@types/verror/-/verror-1.10.10.tgz#d5a4b56abac169bfbc8b23d291363a682e6fa087" integrity sha512-l4MM0Jppn18hb9xmM6wwD1uTdShpf9Pn80aXTStnK1C94gtPvJcV2FrDmbOQUAQfJ1cKZHktkQUDwEqaAKXMMg== +"@types/when@^2.4.34": + version "2.4.41" + resolved "https://registry.yarnpkg.com/@types/when/-/when-2.4.41.tgz#e16e685aa739c696a582b10afc5f1306964846a2" + integrity sha512-o/j5X9Bnv6mMG4ZcNJur8UaU17Rl0mLbTZvWcODVVy+Xdh8LEc7s6I0CvbEuTP786LTa0OyJby5P4hI7C+ZJNg== + "@types/yauzl@^2.9.1": version "2.10.3" resolved "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz" @@ -4375,7 +4380,12 @@ minimatch@^8.0.2: dependencies: brace-expansion "^2.0.1" -minimist@^1.2.0, minimist@^1.2.3, minimist@^1.2.6: +minimist@1.2.6: + version "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: version "1.2.8" resolved "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz" integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== @@ -4484,6 +4494,20 @@ no-case@^3.0.4: lower-case "^2.0.2" tslib "^2.0.3" +node-7z-archive@^1.1.7: + version "1.1.7" + resolved "https://registry.yarnpkg.com/node-7z-archive/-/node-7z-archive-1.1.7.tgz#0b037701e016a651d6040b63d8781b2e7102facd" + integrity sha512-gtpWpajFyzeObGiYI9RDq76x5ULnxInvZ1OfA0/MD+2VezcMmMQMK6ITqkvsGEqVy4w/psvmIyowVDoSURAJHg== + dependencies: + fs-extra "^10.1.0" + minimist "^1.2.8" + node-sys "^1.2.2" + node-unar "^1.0.8" + node-wget-fetch "^1.1.3" + when "^3.7.8" + optionalDependencies: + "@types/when" "^2.4.34" + node-abi@^3.3.0: version "3.62.0" resolved "https://registry.npmjs.org/node-abi/-/node-abi-3.62.0.tgz" @@ -4522,6 +4546,40 @@ node-releases@^2.0.14: resolved "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz" integrity sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw== +node-stream-zip@^1.12.0: + version "1.15.0" + resolved "https://registry.yarnpkg.com/node-stream-zip/-/node-stream-zip-1.15.0.tgz#158adb88ed8004c6c49a396b50a6a5de3bca33ea" + integrity sha512-LN4fydt9TqhZhThkZIVQnF9cwjU3qmUH9h78Mx/K7d3VvfRqqwthLwJEUOEL0QPZ0XQmNN7be5Ggit5+4dq3Bw== + +node-sys@^1.1.7, node-sys@^1.2.2: + version "1.2.4" + resolved "https://registry.yarnpkg.com/node-sys/-/node-sys-1.2.4.tgz#db9c50fd93c8fc62bc4eafe93eae0fd3696c8028" + integrity sha512-71sIz+zgaHfSmP1vHTHXUVb77PqncIB1MBij+Q43fQSz7ceSLrrO5RTTBlnYWDU/M0fEFTZw3Zui/lVeJvoeag== + dependencies: + minimist "1.2.6" + which "^2.0.2" + optionalDependencies: + "@types/node" "^18.7.13" + +node-unar@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/node-unar/-/node-unar-1.0.8.tgz#fbf5b05da2ac24278b6160f3b46231d56a73a673" + integrity sha512-AnEdWmV8/Dx1qMB5O2VcemoBmNzW1mhibYNl3YDUI7cVohVuobuIZwxrtRedItO05A6PiLp/HNw1ryg7M17H5g== + dependencies: + node-sys "^1.1.7" + when "^3.7.8" + optionalDependencies: + fs-extra "^9.0.1" + node-stream-zip "^1.12.0" + node-wget-fetch "^1.1.2" + +node-wget-fetch@^1.1.2, node-wget-fetch@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/node-wget-fetch/-/node-wget-fetch-1.1.3.tgz#1e4aea2d7093393a961bb9c07cf5c5e33913c437" + integrity sha512-TmjZeeL/zAcB4fpok2iJ6FLbjVzSsjKi7rdk0womqvUY2ouitsEN0kGekndshaB7ENnXocrcgUudpvB4Jo3+LA== + dependencies: + node-fetch "^2.6.7" + normalize-path@^3.0.0, normalize-path@~3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz" @@ -5964,6 +6022,11 @@ whatwg-url@^5.0.0: tr46 "~0.0.3" webidl-conversions "^3.0.0" +when@^3.7.8: + version "3.7.8" + resolved "https://registry.yarnpkg.com/when/-/when-3.7.8.tgz#c7130b6a7ea04693e842cdc9e7a1f2aa39a39f82" + integrity sha512-5cZ7mecD3eYcMiCH4wtRPA5iFJZ50BJYDfckI5RRpQiktMiYTcn0ccLTZOvcbBume+1304fQztxeNzNS9Gvrnw== + which-boxed-primitive@^1.0.2: version "1.0.2" resolved "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz"