feat: adding initial leveldb configuration

This commit is contained in:
Chubby Granny Chaser 2025-01-15 16:58:59 +00:00
parent d4be5b8c66
commit 2c5fb8a037
No known key found for this signature in database
18 changed files with 202 additions and 196 deletions

View File

@ -38,6 +38,13 @@ export default defineConfig(({ mode }) => {
build: { build: {
sourcemap: true, sourcemap: true,
}, },
css: {
preprocessorOptions: {
scss: {
api: "modern",
},
},
},
resolve: { resolve: {
alias: { alias: {
"@renderer": resolve("src/renderer/src"), "@renderer": resolve("src/renderer/src"),

View File

@ -62,6 +62,7 @@
"jsdom": "^24.0.0", "jsdom": "^24.0.0",
"jsonwebtoken": "^9.0.2", "jsonwebtoken": "^9.0.2",
"knex": "^3.1.0", "knex": "^3.1.0",
"level": "^9.0.0",
"lodash-es": "^4.17.21", "lodash-es": "^4.17.21",
"parse-torrent": "^11.0.17", "parse-torrent": "^11.0.17",
"piscina": "^4.7.0", "piscina": "^4.7.0",

View File

@ -7,13 +7,18 @@ export const defaultDownloadsPath = app.getPath("downloads");
export const isStaging = import.meta.env.MAIN_VITE_API_URL.includes("staging"); export const isStaging = import.meta.env.MAIN_VITE_API_URL.includes("staging");
export const levelDatabasePath = path.join(
app.getPath("userData"),
`hydra-db${isStaging ? "-staging" : ""}`
);
export const databaseDirectory = path.join(app.getPath("appData"), "hydra"); export const databaseDirectory = path.join(app.getPath("appData"), "hydra");
export const databasePath = path.join( export const databasePath = path.join(
databaseDirectory, databaseDirectory,
isStaging ? "hydra_test.db" : "hydra.db" isStaging ? "hydra_test.db" : "hydra.db"
); );
export const logsPath = path.join(app.getPath("appData"), "hydra", "logs"); export const logsPath = path.join(app.getPath("userData"), "hydra", "logs");
export const seedsPath = app.isPackaged export const seedsPath = app.isPackaged
? path.join(process.resourcesPath, "seeds") ? path.join(process.resourcesPath, "seeds")

View File

@ -4,9 +4,7 @@ import {
Game, Game,
GameShopCache, GameShopCache,
UserPreferences, UserPreferences,
UserAuth,
GameAchievement, GameAchievement,
UserSubscription,
} from "@main/entity"; } from "@main/entity";
import { databasePath } from "./constants"; import { databasePath } from "./constants";
@ -15,9 +13,7 @@ export const dataSource = new DataSource({
type: "better-sqlite3", type: "better-sqlite3",
entities: [ entities: [
Game, Game,
UserAuth,
UserPreferences, UserPreferences,
UserSubscription,
GameShopCache, GameShopCache,
DownloadQueue, DownloadQueue,
GameAchievement, GameAchievement,

View File

@ -1,7 +1,5 @@
export * from "./game.entity"; export * from "./game.entity";
export * from "./user-auth.entity";
export * from "./user-preferences.entity"; export * from "./user-preferences.entity";
export * from "./user-subscription.entity";
export * from "./game-shop-cache.entity"; export * from "./game-shop-cache.entity";
export * from "./game.entity"; export * from "./game.entity";
export * from "./game-achievements.entity"; export * from "./game-achievements.entity";

View File

@ -1,45 +0,0 @@
import {
Entity,
PrimaryGeneratedColumn,
Column,
CreateDateColumn,
UpdateDateColumn,
OneToOne,
} from "typeorm";
import { UserSubscription } from "./user-subscription.entity";
@Entity("user_auth")
export class UserAuth {
@PrimaryGeneratedColumn()
id: number;
@Column("text", { default: "" })
userId: string;
@Column("text", { default: "" })
displayName: string;
@Column("text", { nullable: true })
profileImageUrl: string | null;
@Column("text", { nullable: true })
backgroundImageUrl: string | null;
@Column("text", { default: "" })
accessToken: string;
@Column("text", { default: "" })
refreshToken: string;
@Column("int", { default: 0 })
tokenExpirationTimestamp: number;
@OneToOne("UserSubscription", "user")
subscription: UserSubscription | null;
@CreateDateColumn()
createdAt: Date;
@UpdateDateColumn()
updatedAt: Date;
}

View File

@ -1,42 +0,0 @@
import type { SubscriptionStatus } from "@types";
import {
Entity,
PrimaryGeneratedColumn,
Column,
CreateDateColumn,
UpdateDateColumn,
OneToOne,
JoinColumn,
} from "typeorm";
import { UserAuth } from "./user-auth.entity";
@Entity("user_subscription")
export class UserSubscription {
@PrimaryGeneratedColumn()
id: number;
@Column("text", { default: "" })
subscriptionId: string;
@OneToOne("UserAuth", "subscription")
@JoinColumn()
user: UserAuth;
@Column("text", { default: "" })
status: SubscriptionStatus;
@Column("text", { default: "" })
planId: string;
@Column("text", { default: "" })
planName: string;
@Column("datetime", { nullable: true })
expiresAt: Date | null;
@CreateDateColumn()
createdAt: Date;
@UpdateDateColumn()
updatedAt: Date;
}

View File

@ -1,13 +1,20 @@
import jwt from "jsonwebtoken"; import jwt from "jsonwebtoken";
import { userAuthRepository } from "@main/repository";
import { registerEvent } from "../register-event"; import { registerEvent } from "../register-event";
import { db } from "@main/level";
import type { Auth } from "@types";
import { levelKeys } from "@main/level/sublevels/keys";
import { Crypto } from "@main/services";
const getSessionHash = async (_event: Electron.IpcMainInvokeEvent) => { const getSessionHash = async (_event: Electron.IpcMainInvokeEvent) => {
const auth = await userAuthRepository.findOne({ where: { id: 1 } }); const auth = await db.get<string, Auth>(levelKeys.auth, {
valueEncoding: "json",
});
if (!auth) return null; if (!auth) return null;
const payload = jwt.decode(auth.accessToken) as jwt.JwtPayload; const payload = jwt.decode(
Crypto.decrypt(auth.accessToken)
) as jwt.JwtPayload;
if (!payload) return null; if (!payload) return null;

View File

@ -1,8 +1,10 @@
import { registerEvent } from "../register-event"; import { registerEvent } from "../register-event";
import { DownloadManager, HydraApi, gamesPlaytime } from "@main/services"; import { DownloadManager, HydraApi, gamesPlaytime } from "@main/services";
import { dataSource } from "@main/data-source"; import { dataSource } from "@main/data-source";
import { DownloadQueue, Game, UserAuth, UserSubscription } from "@main/entity"; import { DownloadQueue, Game } from "@main/entity";
import { PythonRPC } from "@main/services/python-rpc"; import { PythonRPC } from "@main/services/python-rpc";
import { db } from "@main/level";
import { levelKeys } from "@main/level/sublevels/keys";
const signOut = async (_event: Electron.IpcMainInvokeEvent) => { const signOut = async (_event: Electron.IpcMainInvokeEvent) => {
const databaseOperations = dataSource const databaseOperations = dataSource
@ -11,13 +13,16 @@ const signOut = async (_event: Electron.IpcMainInvokeEvent) => {
await transactionalEntityManager.getRepository(Game).delete({}); await transactionalEntityManager.getRepository(Game).delete({});
await transactionalEntityManager await db.batch([
.getRepository(UserAuth) {
.delete({ id: 1 }); type: "del",
key: levelKeys.auth,
await transactionalEntityManager },
.getRepository(UserSubscription) {
.delete({ id: 1 }); type: "del",
key: levelKeys.user,
},
]);
}) })
.then(() => { .then(() => {
/* Removes all games being played */ /* Removes all games being played */

View File

@ -1,17 +1,21 @@
import { shell } from "electron"; import { shell } from "electron";
import { registerEvent } from "../register-event"; import { registerEvent } from "../register-event";
import { userAuthRepository } from "@main/repository"; import { Crypto, HydraApi } from "@main/services";
import { HydraApi } from "@main/services"; import { db } from "@main/level";
import type { Auth } from "@types";
import { levelKeys } from "@main/level/sublevels/keys";
const openCheckout = async (_event: Electron.IpcMainInvokeEvent) => { const openCheckout = async (_event: Electron.IpcMainInvokeEvent) => {
const userAuth = await userAuthRepository.findOne({ where: { id: 1 } }); const auth = await db.get<string, Auth>(levelKeys.auth, {
valueEncoding: "json",
});
if (!userAuth) { if (!auth) {
return; return;
} }
const paymentToken = await HydraApi.post("/auth/payment", { const paymentToken = await HydraApi.post("/auth/payment", {
refreshToken: userAuth.refreshToken, refreshToken: Crypto.decrypt(auth.refreshToken),
}).then((response) => response.accessToken); }).then((response) => response.accessToken);
const params = new URLSearchParams({ const params = new URLSearchParams({

View File

@ -1,16 +1,19 @@
import { userAuthRepository } from "@main/repository"; import { db } from "@main/level";
import { registerEvent } from "../register-event"; import { registerEvent } from "../register-event";
import { HydraApi } from "@main/services"; import { HydraApi } from "@main/services";
import type { UserFriends } from "@types"; import type { User, UserFriends } from "@types";
import { levelKeys } from "@main/level/sublevels/keys";
export const getUserFriends = async ( export const getUserFriends = async (
userId: string, userId: string,
take: number, take: number,
skip: number skip: number
): Promise<UserFriends> => { ): Promise<UserFriends> => {
const loggedUser = await userAuthRepository.findOne({ where: { id: 1 } }); const user = await db.get<string, User>(levelKeys.user, {
valueEncoding: "json",
});
if (loggedUser?.userId === userId) { if (user?.id === userId) {
return HydraApi.get(`/profile/friends`, { take, skip }); return HydraApi.get(`/profile/friends`, { take, skip });
} }

View File

@ -4,9 +4,7 @@ import {
Game, Game,
GameShopCache, GameShopCache,
UserPreferences, UserPreferences,
UserAuth,
GameAchievement, GameAchievement,
UserSubscription,
} from "@main/entity"; } from "@main/entity";
export const gameRepository = dataSource.getRepository(Game); export const gameRepository = dataSource.getRepository(Game);
@ -18,10 +16,5 @@ export const gameShopCacheRepository = dataSource.getRepository(GameShopCache);
export const downloadQueueRepository = dataSource.getRepository(DownloadQueue); export const downloadQueueRepository = dataSource.getRepository(DownloadQueue);
export const userAuthRepository = dataSource.getRepository(UserAuth);
export const userSubscriptionRepository =
dataSource.getRepository(UserSubscription);
export const gameAchievementRepository = export const gameAchievementRepository =
dataSource.getRepository(GameAchievement); dataSource.getRepository(GameAchievement);

View File

@ -1,7 +1,3 @@
import {
userAuthRepository,
userSubscriptionRepository,
} from "@main/repository";
import axios, { AxiosError, AxiosInstance } from "axios"; import axios, { AxiosError, AxiosInstance } from "axios";
import { WindowManager } from "./window-manager"; import { WindowManager } from "./window-manager";
import url from "url"; import url from "url";
@ -13,6 +9,10 @@ import { omit } from "lodash-es";
import { appVersion } from "@main/constants"; import { appVersion } from "@main/constants";
import { getUserData } from "./user/get-user-data"; import { getUserData } from "./user/get-user-data";
import { isFuture, isToday } from "date-fns"; import { isFuture, isToday } from "date-fns";
import { db } from "@main/level";
import { levelKeys } from "@main/level/sublevels/keys";
import type { Auth, User } from "@types";
import { Crypto } from "./crypto";
interface HydraApiOptions { interface HydraApiOptions {
needsAuth?: boolean; needsAuth?: boolean;
@ -77,14 +77,14 @@ export class HydraApi {
tokenExpirationTimestamp tokenExpirationTimestamp
); );
await userAuthRepository.upsert( db.put<string, Auth>(
levelKeys.auth,
{ {
id: 1, accessToken: Crypto.encrypt(accessToken),
accessToken, refreshToken: Crypto.encrypt(refreshToken),
tokenExpirationTimestamp, tokenExpirationTimestamp,
refreshToken,
}, },
["id"] { valueEncoding: "json" }
); );
await getUserData().then((userDetails) => { await getUserData().then((userDetails) => {
@ -186,17 +186,23 @@ export class HydraApi {
); );
} }
const userAuth = await userAuthRepository.findOne({ const result = await db.getMany<string>([levelKeys.auth, levelKeys.user], {
where: { id: 1 }, valueEncoding: "json",
relations: { subscription: true },
}); });
const userAuth = result.at(0) as Auth | undefined;
const user = result.at(1) as User | undefined;
this.userAuth = { this.userAuth = {
authToken: userAuth?.accessToken ?? "", authToken: userAuth?.accessToken
refreshToken: userAuth?.refreshToken ?? "", ? Crypto.decrypt(userAuth.accessToken)
: "",
refreshToken: userAuth?.refreshToken
? Crypto.decrypt(userAuth.refreshToken)
: "",
expirationTimestamp: userAuth?.tokenExpirationTimestamp ?? 0, expirationTimestamp: userAuth?.tokenExpirationTimestamp ?? 0,
subscription: userAuth?.subscription subscription: user?.subscription
? { expiresAt: userAuth.subscription?.expiresAt } ? { expiresAt: user.subscription?.expiresAt }
: null, : null,
}; };
@ -239,14 +245,19 @@ export class HydraApi {
this.userAuth.expirationTimestamp this.userAuth.expirationTimestamp
); );
userAuthRepository.upsert( await db
.get<string, Auth>(levelKeys.auth, { valueEncoding: "json" })
.then((auth) => {
return db.put<string, Auth>(
levelKeys.auth,
{ {
id: 1, ...auth,
accessToken, accessToken: Crypto.encrypt(accessToken),
tokenExpirationTimestamp, tokenExpirationTimestamp,
}, },
["id"] { valueEncoding: "json" }
); );
});
} catch (err) { } catch (err) {
this.handleUnauthorizedError(err); this.handleUnauthorizedError(err);
} }
@ -276,8 +287,16 @@ export class HydraApi {
subscription: null, subscription: null,
}; };
userAuthRepository.delete({ id: 1 }); db.batch([
userSubscriptionRepository.delete({ id: 1 }); {
type: "del",
key: levelKeys.auth,
},
{
type: "del",
key: levelKeys.user,
},
]);
this.sendSignOutEvent(); this.sendSignOutEvent();
} }

View File

@ -1,3 +1,4 @@
export * from "./crypto";
export * from "./logger"; export * from "./logger";
export * from "./steam"; export * from "./steam";
export * from "./steam-250"; export * from "./steam-250";

View File

@ -1,42 +1,29 @@
import type { ProfileVisibility, UserDetails } from "@types"; import { User, type ProfileVisibility, type UserDetails } from "@types";
import { HydraApi } from "../hydra-api"; import { HydraApi } from "../hydra-api";
import {
userAuthRepository,
userSubscriptionRepository,
} from "@main/repository";
import { UserNotLoggedInError } from "@shared"; import { UserNotLoggedInError } from "@shared";
import { logger } from "../logger"; import { logger } from "../logger";
import { db } from "@main/level";
import { levelKeys } from "@main/level/sublevels/keys";
export const getUserData = () => { export const getUserData = async () => {
return HydraApi.get<UserDetails>(`/profile/me`) return HydraApi.get<UserDetails>(`/profile/me`)
.then(async (me) => { .then(async (me) => {
userAuthRepository.upsert( db.get<string, User>(levelKeys.user, { valueEncoding: "json" }).then(
(user) => {
return db.put<string, User>(
levelKeys.user,
{ {
id: 1, ...user,
id: me.id,
displayName: me.displayName, displayName: me.displayName,
profileImageUrl: me.profileImageUrl, profileImageUrl: me.profileImageUrl,
backgroundImageUrl: me.backgroundImageUrl, backgroundImageUrl: me.backgroundImageUrl,
userId: me.id, subscription: me.subscription,
}, },
["id"] { valueEncoding: "json" }
); );
if (me.subscription) {
await userSubscriptionRepository.upsert(
{
id: 1,
subscriptionId: me.subscription?.id || "",
status: me.subscription?.status || "",
planId: me.subscription?.plan.id || "",
planName: me.subscription?.plan.name || "",
expiresAt: me.subscription?.expiresAt || null,
user: { id: 1 },
},
["id"]
);
} else {
await userSubscriptionRepository.delete({ id: 1 });
} }
);
return me; return me;
}) })
@ -46,15 +33,14 @@ export const getUserData = () => {
return null; return null;
} }
logger.error("Failed to get logged user"); logger.error("Failed to get logged user");
const loggedUser = await userAuthRepository.findOne({
where: { id: 1 }, const loggedUser = await db.get<string, User>(levelKeys.user, {
relations: { subscription: true }, valueEncoding: "json",
}); });
if (loggedUser) { if (loggedUser) {
return { return {
...loggedUser, ...loggedUser,
id: loggedUser.userId,
username: "", username: "",
bio: "", bio: "",
email: null, email: null,
@ -64,11 +50,11 @@ export const getUserData = () => {
}, },
subscription: loggedUser.subscription subscription: loggedUser.subscription
? { ? {
id: loggedUser.subscription.subscriptionId, id: loggedUser.subscription.id,
status: loggedUser.subscription.status, status: loggedUser.subscription.status,
plan: { plan: {
id: loggedUser.subscription.planId, id: loggedUser.subscription.plan.id,
name: loggedUser.subscription.planName, name: loggedUser.subscription.plan.name,
}, },
expiresAt: loggedUser.subscription.expiresAt, expiresAt: loggedUser.subscription.expiresAt,
} }

View File

@ -52,6 +52,7 @@ export function Modal({
) )
) )
return false; return false;
const openModals = document.querySelectorAll("[role=dialog]"); const openModals = document.querySelectorAll("[role=dialog]");
return ( return (

View File

@ -248,16 +248,6 @@ export interface UserProfileCurrentGame extends Omit<GameRunning, "objectId"> {
export type ProfileVisibility = "PUBLIC" | "PRIVATE" | "FRIENDS"; export type ProfileVisibility = "PUBLIC" | "PRIVATE" | "FRIENDS";
export type SubscriptionStatus = "active" | "pending" | "cancelled";
export interface Subscription {
id: string;
status: SubscriptionStatus;
plan: { id: string; name: string };
expiresAt: string | null;
paymentMethod: "pix" | "paypal";
}
export interface UserDetails { export interface UserDetails {
id: string; id: string;
username: string; username: string;
@ -421,3 +411,4 @@ export * from "./real-debrid.types";
export * from "./ludusavi.types"; export * from "./ludusavi.types";
export * from "./how-long-to-beat.types"; export * from "./how-long-to-beat.types";
export * from "./torbox.types"; export * from "./torbox.types";
export * from "./level.types";

View File

@ -3699,6 +3699,18 @@ abort-controller@^3.0.0:
dependencies: dependencies:
event-target-shim "^5.0.0" event-target-shim "^5.0.0"
abstract-level@^2.0.0, abstract-level@^2.0.1:
version "2.0.2"
resolved "https://registry.yarnpkg.com/abstract-level/-/abstract-level-2.0.2.tgz#8d965e731afb42a72f163874410c1687fb2e4bdb"
integrity sha512-pPJixmXk/kTKLB2sSue7o4Uj6TlLD2XfaP2gWZomHVCC6cuUGX/VslQqKG1yZHfXwBb/3lS6oSTMPGzh1P1iig==
dependencies:
buffer "^6.0.3"
is-buffer "^2.0.5"
level-supports "^6.0.0"
level-transcoder "^1.0.1"
maybe-combine-errors "^1.0.0"
module-error "^1.0.1"
acorn-jsx@^5.3.2: acorn-jsx@^5.3.2:
version "5.3.2" version "5.3.2"
resolved "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz" resolved "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz"
@ -4169,6 +4181,13 @@ braces@^3.0.3, braces@~3.0.2:
dependencies: dependencies:
fill-range "^7.1.1" fill-range "^7.1.1"
browser-level@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/browser-level/-/browser-level-2.0.0.tgz#cc63eb1322e67c44489d7fbdda5c30a2db7b59da"
integrity sha512-RuYSCHG/jwFCrK+KWA3dLSUNLKHEgIYhO5ORPjJMjCt7T3e+RzpIDmYKWRHxq2pfKGXjlRuEff7y7RESAAgzew==
dependencies:
abstract-level "^2.0.1"
browserslist@^4.22.2, browserslist@^4.23.1: browserslist@^4.22.2, browserslist@^4.23.1:
version "4.24.0" version "4.24.0"
resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.24.0.tgz#a1325fe4bc80b64fda169629fc01b3d6cecd38d4" resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.24.0.tgz#a1325fe4bc80b64fda169629fc01b3d6cecd38d4"
@ -4421,6 +4440,16 @@ ci-info@^3.2.0:
resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.9.0.tgz#4279a62028a7b1f262f3473fc9605f5e218c59b4" resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.9.0.tgz#4279a62028a7b1f262f3473fc9605f5e218c59b4"
integrity sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ== integrity sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==
classic-level@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/classic-level/-/classic-level-2.0.0.tgz#6fd9ca686bbcd645e35caaf403c3f3a56495d11b"
integrity sha512-ftiMvKgCQK+OppXcvMieDoYlYLYWhScK6yZRFBrrlHQRbm4k6Gr+yDgu/wt3V0k1/jtNbuiXAsRmuAFcD0Tx5Q==
dependencies:
abstract-level "^2.0.0"
module-error "^1.0.1"
napi-macros "^2.2.2"
node-gyp-build "^4.3.0"
classnames@^2.2.1, classnames@^2.2.6, classnames@^2.5.1: classnames@^2.2.1, classnames@^2.2.6, classnames@^2.5.1:
version "2.5.1" version "2.5.1"
resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.5.1.tgz#ba774c614be0f016da105c858e7159eae8e7687b" resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.5.1.tgz#ba774c614be0f016da105c858e7159eae8e7687b"
@ -6459,6 +6488,11 @@ is-boolean-object@^1.2.0:
call-bound "^1.0.2" call-bound "^1.0.2"
has-tostringtag "^1.0.2" has-tostringtag "^1.0.2"
is-buffer@^2.0.5:
version "2.0.5"
resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.5.tgz#ebc252e400d22ff8d77fa09888821a24a658c191"
integrity sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==
is-callable@^1.1.3, is-callable@^1.1.4, is-callable@^1.2.7: is-callable@^1.1.3, is-callable@^1.1.4, is-callable@^1.2.7:
version "1.2.7" version "1.2.7"
resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055" resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055"
@ -6958,6 +6992,28 @@ lazy-val@^1.0.5:
resolved "https://registry.yarnpkg.com/lazy-val/-/lazy-val-1.0.5.tgz#6cf3b9f5bc31cee7ee3e369c0832b7583dcd923d" resolved "https://registry.yarnpkg.com/lazy-val/-/lazy-val-1.0.5.tgz#6cf3b9f5bc31cee7ee3e369c0832b7583dcd923d"
integrity sha512-0/BnGCCfyUMkBpeDgWihanIAF9JmZhHBgUhEqzvf+adhNGLoP6TaiI5oF8oyb3I45P+PcnrqihSf01M0l0G5+Q== integrity sha512-0/BnGCCfyUMkBpeDgWihanIAF9JmZhHBgUhEqzvf+adhNGLoP6TaiI5oF8oyb3I45P+PcnrqihSf01M0l0G5+Q==
level-supports@^6.0.0:
version "6.2.0"
resolved "https://registry.yarnpkg.com/level-supports/-/level-supports-6.2.0.tgz#e78b228973a24acdc5199c5f51e244e70f26c611"
integrity sha512-QNxVXP0IRnBmMsJIh+sb2kwNCYcKciQZJEt+L1hPCHrKNELllXhvrlClVHXBYZVT+a7aTSM6StgNXdAldoab3w==
level-transcoder@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/level-transcoder/-/level-transcoder-1.0.1.tgz#f8cef5990c4f1283d4c86d949e73631b0bc8ba9c"
integrity sha512-t7bFwFtsQeD8cl8NIoQ2iwxA0CL/9IFw7/9gAjOonH0PWTTiRfY7Hq+Ejbsxh86tXobDQ6IOiddjNYIfOBs06w==
dependencies:
buffer "^6.0.3"
module-error "^1.0.1"
level@^9.0.0:
version "9.0.0"
resolved "https://registry.yarnpkg.com/level/-/level-9.0.0.tgz#880aa9d341a5411e36bed77f4fa233f425b492a8"
integrity sha512-n+mVuf63mUEkd8NUx7gwxY+QF5vtkibv6fXTGUgtHWLPDaA5/XZjLcI/Q1nQ8k6OttHT6Ezt+7nSEXsRUfHtOQ==
dependencies:
abstract-level "^2.0.1"
browser-level "^2.0.0"
classic-level "^2.0.0"
levn@^0.4.1: levn@^0.4.1:
version "0.4.1" version "0.4.1"
resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade"
@ -7198,6 +7254,11 @@ math-intrinsics@^1.0.0:
resolved "https://registry.yarnpkg.com/math-intrinsics/-/math-intrinsics-1.0.0.tgz#4e04bf87c85aa51e90d078dac2252b4eb5260817" resolved "https://registry.yarnpkg.com/math-intrinsics/-/math-intrinsics-1.0.0.tgz#4e04bf87c85aa51e90d078dac2252b4eb5260817"
integrity sha512-4MqMiKP90ybymYvsut0CH2g4XWbfLtmlCkXmtmdcDCxNB+mQcu1w/1+L/VD7vi/PSv7X2JYV7SCcR+jiPXnQtA== integrity sha512-4MqMiKP90ybymYvsut0CH2g4XWbfLtmlCkXmtmdcDCxNB+mQcu1w/1+L/VD7vi/PSv7X2JYV7SCcR+jiPXnQtA==
maybe-combine-errors@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/maybe-combine-errors/-/maybe-combine-errors-1.0.0.tgz#e9592832e61fc47643a92cff3c1f33e27211e5be"
integrity sha512-eefp6IduNPT6fVdwPp+1NgD0PML1NU5P6j1Mj5nz1nidX8/sWY7119WL8vTAHgqfsY74TzW0w1XPgdYEKkGZ5A==
media-query-parser@^2.0.2: media-query-parser@^2.0.2:
version "2.0.2" version "2.0.2"
resolved "https://registry.yarnpkg.com/media-query-parser/-/media-query-parser-2.0.2.tgz#ff79e56cee92615a304a1c2fa4f2bd056c0a1d29" resolved "https://registry.yarnpkg.com/media-query-parser/-/media-query-parser-2.0.2.tgz#ff79e56cee92615a304a1c2fa4f2bd056c0a1d29"
@ -7414,6 +7475,11 @@ modern-ahocorasick@^1.0.0:
resolved "https://registry.yarnpkg.com/modern-ahocorasick/-/modern-ahocorasick-1.0.1.tgz#dec373444f51b5458ac05216a8ec376e126dd283" resolved "https://registry.yarnpkg.com/modern-ahocorasick/-/modern-ahocorasick-1.0.1.tgz#dec373444f51b5458ac05216a8ec376e126dd283"
integrity sha512-yoe+JbhTClckZ67b2itRtistFKf8yPYelHLc7e5xAwtNAXxM6wJTUx2C7QeVSJFDzKT7bCIFyBVybPMKvmB9AA== integrity sha512-yoe+JbhTClckZ67b2itRtistFKf8yPYelHLc7e5xAwtNAXxM6wJTUx2C7QeVSJFDzKT7bCIFyBVybPMKvmB9AA==
module-error@^1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/module-error/-/module-error-1.0.2.tgz#8d1a48897ca883f47a45816d4fb3e3c6ba404d86"
integrity sha512-0yuvsqSCv8LbaOKhnsQ/T5JhyFlCYLPXK3U2sgV10zoKQwzs/MyfuQUOZQ1V/6OCOJsK/TRgNVrPuPDqtdMFtA==
ms@2.1.2: ms@2.1.2:
version "2.1.2" version "2.1.2"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
@ -7448,6 +7514,11 @@ napi-build-utils@^1.0.1:
resolved "https://registry.yarnpkg.com/napi-build-utils/-/napi-build-utils-1.0.2.tgz#b1fddc0b2c46e380a0b7a76f984dd47c41a13806" resolved "https://registry.yarnpkg.com/napi-build-utils/-/napi-build-utils-1.0.2.tgz#b1fddc0b2c46e380a0b7a76f984dd47c41a13806"
integrity sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg== integrity sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==
napi-macros@^2.2.2:
version "2.2.2"
resolved "https://registry.yarnpkg.com/napi-macros/-/napi-macros-2.2.2.tgz#817fef20c3e0e40a963fbf7b37d1600bd0201044"
integrity sha512-hmEVtAGYzVQpCKdbQea4skABsdXW4RUh5t5mJ2zzqowJS2OyXZTU1KhDVFhx+NlWZ4ap9mqR9TcDO3LTTttd+g==
natural-compare@^1.4.0: natural-compare@^1.4.0:
version "1.4.0" version "1.4.0"
resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
@ -7506,6 +7577,11 @@ node-fetch@^3.3.0:
fetch-blob "^3.1.4" fetch-blob "^3.1.4"
formdata-polyfill "^4.0.10" formdata-polyfill "^4.0.10"
node-gyp-build@^4.3.0:
version "4.8.4"
resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.8.4.tgz#8a70ee85464ae52327772a90d66c6077a900cfc8"
integrity sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==
node-gyp@^9.0.0: node-gyp@^9.0.0:
version "9.4.1" version "9.4.1"
resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-9.4.1.tgz#8a1023e0d6766ecb52764cc3a734b36ff275e185" resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-9.4.1.tgz#8a1023e0d6766ecb52764cc3a734b36ff275e185"