mirror of
https://github.com/hydralauncher/hydra.git
synced 2025-02-03 00:33:49 +03:00
Merge pull request #752 from hydralauncher/hyd-228-investigate-why-users-are-being-logged-out-when-updating
feat: prevent api calls when user is not logged in or auth is not loaded yet
This commit is contained in:
commit
138f33e0c3
@ -28,7 +28,7 @@ const signOut = async (_event: Electron.IpcMainInvokeEvent) => {
|
|||||||
|
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
databaseOperations,
|
databaseOperations,
|
||||||
HydraApi.post("/auth/logout").catch(),
|
HydraApi.post("/auth/logout").catch(() => {}),
|
||||||
]);
|
]);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -22,7 +22,6 @@ import "./library/open-game-installer-path";
|
|||||||
import "./library/update-executable-path";
|
import "./library/update-executable-path";
|
||||||
import "./library/remove-game";
|
import "./library/remove-game";
|
||||||
import "./library/remove-game-from-library";
|
import "./library/remove-game-from-library";
|
||||||
import "./misc/is-user-logged-in";
|
|
||||||
import "./misc/open-external";
|
import "./misc/open-external";
|
||||||
import "./misc/show-open-dialog";
|
import "./misc/show-open-dialog";
|
||||||
import "./torrenting/cancel-game-download";
|
import "./torrenting/cancel-game-download";
|
||||||
|
@ -53,18 +53,7 @@ const addGameToLibrary = async (
|
|||||||
|
|
||||||
const game = await gameRepository.findOne({ where: { objectID } });
|
const game = await gameRepository.findOne({ where: { objectID } });
|
||||||
|
|
||||||
createGame(game!).then((response) => {
|
createGame(game!);
|
||||||
const {
|
|
||||||
id: remoteId,
|
|
||||||
playTimeInMilliseconds,
|
|
||||||
lastTimePlayed,
|
|
||||||
} = response.data;
|
|
||||||
|
|
||||||
gameRepository.update(
|
|
||||||
{ objectID },
|
|
||||||
{ remoteId, playTimeInMilliseconds, lastTimePlayed }
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -20,7 +20,7 @@ const removeRemoveGameFromLibrary = async (gameId: number) => {
|
|||||||
const game = await gameRepository.findOne({ where: { id: gameId } });
|
const game = await gameRepository.findOne({ where: { id: gameId } });
|
||||||
|
|
||||||
if (game?.remoteId) {
|
if (game?.remoteId) {
|
||||||
HydraApi.delete(`/games/${game.remoteId}`);
|
HydraApi.delete(`/games/${game.remoteId}`).catch(() => {});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,8 +0,0 @@
|
|||||||
import { registerEvent } from "../register-event";
|
|
||||||
import { HydraApi } from "@main/services";
|
|
||||||
|
|
||||||
const isUserLoggedIn = async (_event: Electron.IpcMainInvokeEvent) => {
|
|
||||||
return HydraApi.isLoggedIn();
|
|
||||||
};
|
|
||||||
|
|
||||||
registerEvent("isUserLoggedIn", isUserLoggedIn);
|
|
@ -3,7 +3,7 @@ import * as Sentry from "@sentry/electron/main";
|
|||||||
import { HydraApi } from "@main/services";
|
import { HydraApi } from "@main/services";
|
||||||
import { UserProfile } from "@types";
|
import { UserProfile } from "@types";
|
||||||
import { userAuthRepository } from "@main/repository";
|
import { userAuthRepository } from "@main/repository";
|
||||||
import { logger } from "@main/services";
|
import { UserNotLoggedInError } from "@shared";
|
||||||
|
|
||||||
const getMe = async (
|
const getMe = async (
|
||||||
_event: Electron.IpcMainInvokeEvent
|
_event: Electron.IpcMainInvokeEvent
|
||||||
@ -27,7 +27,10 @@ const getMe = async (
|
|||||||
return me;
|
return me;
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
logger.error("getMe", err.message);
|
if (err instanceof UserNotLoggedInError) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return userAuthRepository.findOne({ where: { id: 1 } });
|
return userAuthRepository.findOne({ where: { id: 1 } });
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -26,9 +26,11 @@ const updateProfile = async (
|
|||||||
_event: Electron.IpcMainInvokeEvent,
|
_event: Electron.IpcMainInvokeEvent,
|
||||||
displayName: string,
|
displayName: string,
|
||||||
newProfileImagePath: string | null
|
newProfileImagePath: string | null
|
||||||
): Promise<UserProfile> => {
|
) => {
|
||||||
if (!newProfileImagePath) {
|
if (!newProfileImagePath) {
|
||||||
return (await patchUserProfile(displayName)).data;
|
return patchUserProfile(displayName).then(
|
||||||
|
(response) => response.data as UserProfile
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const stats = fs.statSync(newProfileImagePath);
|
const stats = fs.statSync(newProfileImagePath);
|
||||||
@ -51,11 +53,11 @@ const updateProfile = async (
|
|||||||
});
|
});
|
||||||
return profileImageUrl;
|
return profileImageUrl;
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => undefined);
|
||||||
return undefined;
|
|
||||||
});
|
|
||||||
|
|
||||||
return (await patchUserProfile(displayName, profileImageUrl)).data;
|
return patchUserProfile(displayName, profileImageUrl).then(
|
||||||
|
(response) => response.data as UserProfile
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
registerEvent("updateProfile", updateProfile);
|
registerEvent("updateProfile", updateProfile);
|
||||||
|
@ -95,18 +95,7 @@ const startGameDownload = async (
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
createGame(updatedGame!).then((response) => {
|
createGame(updatedGame!);
|
||||||
const {
|
|
||||||
id: remoteId,
|
|
||||||
playTimeInMilliseconds,
|
|
||||||
lastTimePlayed,
|
|
||||||
} = response.data;
|
|
||||||
|
|
||||||
gameRepository.update(
|
|
||||||
{ objectID },
|
|
||||||
{ remoteId, playTimeInMilliseconds, lastTimePlayed }
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
await downloadQueueRepository.delete({ game: { id: updatedGame!.id } });
|
await downloadQueueRepository.delete({ game: { id: updatedGame!.id } });
|
||||||
await downloadQueueRepository.insert({ game: { id: updatedGame!.id } });
|
await downloadQueueRepository.insert({ game: { id: updatedGame!.id } });
|
||||||
|
@ -13,9 +13,9 @@ const autoLaunch = async (
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (enabled) {
|
if (enabled) {
|
||||||
appLauncher.enable().catch();
|
appLauncher.enable().catch(() => {});
|
||||||
} else {
|
} else {
|
||||||
appLauncher.disable().catch();
|
appLauncher.disable().catch(() => {});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -25,8 +25,8 @@ const loadState = async (userPreferences: UserPreferences | null) => {
|
|||||||
if (userPreferences?.realDebridApiToken)
|
if (userPreferences?.realDebridApiToken)
|
||||||
RealDebridClient.authorize(userPreferences?.realDebridApiToken);
|
RealDebridClient.authorize(userPreferences?.realDebridApiToken);
|
||||||
|
|
||||||
HydraApi.setupApi().then(async () => {
|
HydraApi.setupApi().then(() => {
|
||||||
if (HydraApi.isLoggedIn()) uploadGamesBatch();
|
uploadGamesBatch();
|
||||||
});
|
});
|
||||||
|
|
||||||
const [nextQueueItem] = await downloadQueueRepository.find({
|
const [nextQueueItem] = await downloadQueueRepository.find({
|
||||||
|
@ -5,6 +5,7 @@ import url from "url";
|
|||||||
import { uploadGamesBatch } from "./library-sync";
|
import { uploadGamesBatch } from "./library-sync";
|
||||||
import { clearGamesRemoteIds } from "./library-sync/clear-games-remote-id";
|
import { clearGamesRemoteIds } from "./library-sync/clear-games-remote-id";
|
||||||
import { logger } from "./logger";
|
import { logger } from "./logger";
|
||||||
|
import { UserNotLoggedInError } from "@shared";
|
||||||
|
|
||||||
export class HydraApi {
|
export class HydraApi {
|
||||||
private static instance: AxiosInstance;
|
private static instance: AxiosInstance;
|
||||||
@ -19,7 +20,7 @@ export class HydraApi {
|
|||||||
expirationTimestamp: 0,
|
expirationTimestamp: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
static isLoggedIn() {
|
private static isLoggedIn() {
|
||||||
return this.userAuth.authToken !== "";
|
return this.userAuth.authToken !== "";
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -127,14 +128,8 @@ export class HydraApi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static async revalidateAccessTokenIfExpired() {
|
private static async revalidateAccessTokenIfExpired() {
|
||||||
if (!this.userAuth.authToken) {
|
|
||||||
userAuthRepository.delete({ id: 1 });
|
|
||||||
logger.error("user is not logged in");
|
|
||||||
this.sendSignOutEvent();
|
|
||||||
throw new Error("user is not logged in");
|
|
||||||
}
|
|
||||||
|
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
|
|
||||||
if (this.userAuth.expirationTimestamp < now.getTime()) {
|
if (this.userAuth.expirationTimestamp < now.getTime()) {
|
||||||
try {
|
try {
|
||||||
const response = await this.instance.post(`/auth/refresh`, {
|
const response = await this.instance.post(`/auth/refresh`, {
|
||||||
@ -190,6 +185,8 @@ export class HydraApi {
|
|||||||
};
|
};
|
||||||
|
|
||||||
static async get(url: string) {
|
static async get(url: string) {
|
||||||
|
if (!this.isLoggedIn()) throw new UserNotLoggedInError();
|
||||||
|
|
||||||
await this.revalidateAccessTokenIfExpired();
|
await this.revalidateAccessTokenIfExpired();
|
||||||
return this.instance
|
return this.instance
|
||||||
.get(url, this.getAxiosConfig())
|
.get(url, this.getAxiosConfig())
|
||||||
@ -197,6 +194,8 @@ export class HydraApi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static async post(url: string, data?: any) {
|
static async post(url: string, data?: any) {
|
||||||
|
if (!this.isLoggedIn()) throw new UserNotLoggedInError();
|
||||||
|
|
||||||
await this.revalidateAccessTokenIfExpired();
|
await this.revalidateAccessTokenIfExpired();
|
||||||
return this.instance
|
return this.instance
|
||||||
.post(url, data, this.getAxiosConfig())
|
.post(url, data, this.getAxiosConfig())
|
||||||
@ -204,6 +203,8 @@ export class HydraApi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static async put(url: string, data?: any) {
|
static async put(url: string, data?: any) {
|
||||||
|
if (!this.isLoggedIn()) throw new UserNotLoggedInError();
|
||||||
|
|
||||||
await this.revalidateAccessTokenIfExpired();
|
await this.revalidateAccessTokenIfExpired();
|
||||||
return this.instance
|
return this.instance
|
||||||
.put(url, data, this.getAxiosConfig())
|
.put(url, data, this.getAxiosConfig())
|
||||||
@ -211,6 +212,8 @@ export class HydraApi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static async patch(url: string, data?: any) {
|
static async patch(url: string, data?: any) {
|
||||||
|
if (!this.isLoggedIn()) throw new UserNotLoggedInError();
|
||||||
|
|
||||||
await this.revalidateAccessTokenIfExpired();
|
await this.revalidateAccessTokenIfExpired();
|
||||||
return this.instance
|
return this.instance
|
||||||
.patch(url, data, this.getAxiosConfig())
|
.patch(url, data, this.getAxiosConfig())
|
||||||
@ -218,6 +221,8 @@ export class HydraApi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static async delete(url: string) {
|
static async delete(url: string) {
|
||||||
|
if (!this.isLoggedIn()) throw new UserNotLoggedInError();
|
||||||
|
|
||||||
await this.revalidateAccessTokenIfExpired();
|
await this.revalidateAccessTokenIfExpired();
|
||||||
return this.instance
|
return this.instance
|
||||||
.delete(url, this.getAxiosConfig())
|
.delete(url, this.getAxiosConfig())
|
||||||
|
@ -1,11 +1,25 @@
|
|||||||
import { Game } from "@main/entity";
|
import { Game } from "@main/entity";
|
||||||
import { HydraApi } from "../hydra-api";
|
import { HydraApi } from "../hydra-api";
|
||||||
|
import { gameRepository } from "@main/repository";
|
||||||
|
|
||||||
export const createGame = async (game: Game) => {
|
export const createGame = async (game: Game) => {
|
||||||
return HydraApi.post(`/games`, {
|
HydraApi.post(`/games`, {
|
||||||
objectId: game.objectID,
|
objectId: game.objectID,
|
||||||
playTimeInMilliseconds: Math.trunc(game.playTimeInMilliseconds),
|
playTimeInMilliseconds: Math.trunc(game.playTimeInMilliseconds),
|
||||||
shop: game.shop,
|
shop: game.shop,
|
||||||
lastTimePlayed: game.lastTimePlayed,
|
lastTimePlayed: game.lastTimePlayed,
|
||||||
});
|
})
|
||||||
|
.then((response) => {
|
||||||
|
const {
|
||||||
|
id: remoteId,
|
||||||
|
playTimeInMilliseconds,
|
||||||
|
lastTimePlayed,
|
||||||
|
} = response.data;
|
||||||
|
|
||||||
|
gameRepository.update(
|
||||||
|
{ objectID: game.objectID },
|
||||||
|
{ remoteId, playTimeInMilliseconds, lastTimePlayed }
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.catch(() => {});
|
||||||
};
|
};
|
||||||
|
@ -2,14 +2,11 @@ import { gameRepository } from "@main/repository";
|
|||||||
import { HydraApi } from "../hydra-api";
|
import { HydraApi } from "../hydra-api";
|
||||||
import { steamGamesWorker } from "@main/workers";
|
import { steamGamesWorker } from "@main/workers";
|
||||||
import { getSteamAppAsset } from "@main/helpers";
|
import { getSteamAppAsset } from "@main/helpers";
|
||||||
import { logger } from "../logger";
|
|
||||||
import { AxiosError } from "axios";
|
|
||||||
|
|
||||||
export const mergeWithRemoteGames = async () => {
|
export const mergeWithRemoteGames = async () => {
|
||||||
try {
|
return HydraApi.get("/games")
|
||||||
const games = await HydraApi.get("/games");
|
.then(async (response) => {
|
||||||
|
for (const game of response.data) {
|
||||||
for (const game of games.data) {
|
|
||||||
const localGame = await gameRepository.findOne({
|
const localGame = await gameRepository.findOne({
|
||||||
where: {
|
where: {
|
||||||
objectID: game.objectId,
|
objectID: game.objectId,
|
||||||
@ -62,11 +59,6 @@ export const mergeWithRemoteGames = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (err) {
|
})
|
||||||
if (err instanceof AxiosError) {
|
.catch(() => {});
|
||||||
logger.error("getRemoteGames", err.message);
|
|
||||||
} else {
|
|
||||||
logger.error("getRemoteGames", err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
@ -6,8 +6,8 @@ export const updateGamePlaytime = async (
|
|||||||
deltaInMillis: number,
|
deltaInMillis: number,
|
||||||
lastTimePlayed: Date
|
lastTimePlayed: Date
|
||||||
) => {
|
) => {
|
||||||
return HydraApi.put(`/games/${game.remoteId}`, {
|
HydraApi.put(`/games/${game.remoteId}`, {
|
||||||
playTimeDeltaInSeconds: Math.trunc(deltaInMillis / 1000),
|
playTimeDeltaInSeconds: Math.trunc(deltaInMillis / 1000),
|
||||||
lastTimePlayed,
|
lastTimePlayed,
|
||||||
});
|
}).catch(() => {});
|
||||||
};
|
};
|
||||||
|
@ -2,14 +2,10 @@ import { gameRepository } from "@main/repository";
|
|||||||
import { chunk } from "lodash-es";
|
import { chunk } from "lodash-es";
|
||||||
import { IsNull } from "typeorm";
|
import { IsNull } from "typeorm";
|
||||||
import { HydraApi } from "../hydra-api";
|
import { HydraApi } from "../hydra-api";
|
||||||
import { logger } from "../logger";
|
|
||||||
import { AxiosError } from "axios";
|
|
||||||
|
|
||||||
import { mergeWithRemoteGames } from "./merge-with-remote-games";
|
import { mergeWithRemoteGames } from "./merge-with-remote-games";
|
||||||
import { WindowManager } from "../window-manager";
|
import { WindowManager } from "../window-manager";
|
||||||
|
|
||||||
export const uploadGamesBatch = async () => {
|
export const uploadGamesBatch = async () => {
|
||||||
try {
|
|
||||||
const games = await gameRepository.find({
|
const games = await gameRepository.find({
|
||||||
where: { remoteId: IsNull(), isDeleted: false },
|
where: { remoteId: IsNull(), isDeleted: false },
|
||||||
});
|
});
|
||||||
@ -27,18 +23,11 @@ export const uploadGamesBatch = async () => {
|
|||||||
lastTimePlayed: game.lastTimePlayed,
|
lastTimePlayed: game.lastTimePlayed,
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
);
|
).catch(() => {});
|
||||||
}
|
}
|
||||||
|
|
||||||
await mergeWithRemoteGames();
|
await mergeWithRemoteGames();
|
||||||
|
|
||||||
if (WindowManager.mainWindow)
|
if (WindowManager.mainWindow)
|
||||||
WindowManager.mainWindow.webContents.send("on-library-batch-complete");
|
WindowManager.mainWindow.webContents.send("on-library-batch-complete");
|
||||||
} catch (err) {
|
|
||||||
if (err instanceof AxiosError) {
|
|
||||||
logger.error("uploadGamesBatch", err.response, err.message);
|
|
||||||
} else {
|
|
||||||
logger.error("uploadGamesBatch", err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
@ -48,12 +48,7 @@ export const watchProcesses = async () => {
|
|||||||
if (game.remoteId) {
|
if (game.remoteId) {
|
||||||
updateGamePlaytime(game, 0, new Date());
|
updateGamePlaytime(game, 0, new Date());
|
||||||
} else {
|
} else {
|
||||||
createGame({ ...game, lastTimePlayed: new Date() }).then(
|
createGame({ ...game, lastTimePlayed: new Date() });
|
||||||
(response) => {
|
|
||||||
const { id: remoteId } = response.data;
|
|
||||||
gameRepository.update({ objectID: game.objectID }, { remoteId });
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
gamesPlaytime.set(game.id, {
|
gamesPlaytime.set(game.id, {
|
||||||
@ -72,10 +67,7 @@ export const watchProcesses = async () => {
|
|||||||
game.lastTimePlayed!
|
game.lastTimePlayed!
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
createGame(game).then((response) => {
|
createGame(game);
|
||||||
const { id: remoteId } = response.data;
|
|
||||||
gameRepository.update({ objectID: game.objectID }, { remoteId });
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -112,7 +112,6 @@ contextBridge.exposeInMainWorld("electron", {
|
|||||||
getDefaultDownloadsPath: () => ipcRenderer.invoke("getDefaultDownloadsPath"),
|
getDefaultDownloadsPath: () => ipcRenderer.invoke("getDefaultDownloadsPath"),
|
||||||
isPortableVersion: () => ipcRenderer.invoke("isPortableVersion"),
|
isPortableVersion: () => ipcRenderer.invoke("isPortableVersion"),
|
||||||
openExternal: (src: string) => ipcRenderer.invoke("openExternal", src),
|
openExternal: (src: string) => ipcRenderer.invoke("openExternal", src),
|
||||||
isUserLoggedIn: () => ipcRenderer.invoke("isUserLoggedIn"),
|
|
||||||
showOpenDialog: (options: Electron.OpenDialogOptions) =>
|
showOpenDialog: (options: Electron.OpenDialogOptions) =>
|
||||||
ipcRenderer.invoke("showOpenDialog", options),
|
ipcRenderer.invoke("showOpenDialog", options),
|
||||||
platform: process.platform,
|
platform: process.platform,
|
||||||
|
@ -93,13 +93,9 @@ export function App() {
|
|||||||
dispatch(setProfileBackground(profileBackground));
|
dispatch(setProfileBackground(profileBackground));
|
||||||
}
|
}
|
||||||
|
|
||||||
window.electron.isUserLoggedIn().then((isLoggedIn) => {
|
|
||||||
if (isLoggedIn) {
|
|
||||||
fetchUserDetails().then((response) => {
|
fetchUserDetails().then((response) => {
|
||||||
if (response) updateUserDetails(response);
|
if (response) updateUserDetails(response);
|
||||||
});
|
});
|
||||||
}
|
|
||||||
});
|
|
||||||
}, [fetchUserDetails, updateUserDetails, dispatch]);
|
}, [fetchUserDetails, updateUserDetails, dispatch]);
|
||||||
|
|
||||||
const onSignIn = useCallback(() => {
|
const onSignIn = useCallback(() => {
|
||||||
|
1
src/renderer/src/declaration.d.ts
vendored
1
src/renderer/src/declaration.d.ts
vendored
@ -100,7 +100,6 @@ declare global {
|
|||||||
|
|
||||||
/* Misc */
|
/* Misc */
|
||||||
openExternal: (src: string) => Promise<void>;
|
openExternal: (src: string) => Promise<void>;
|
||||||
isUserLoggedIn: () => Promise<boolean>;
|
|
||||||
getVersion: () => Promise<string>;
|
getVersion: () => Promise<string>;
|
||||||
ping: () => string;
|
ping: () => string;
|
||||||
getDefaultDownloadsPath: () => Promise<string>;
|
getDefaultDownloadsPath: () => Promise<string>;
|
||||||
|
@ -57,8 +57,14 @@ export function useUserDetails() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const fetchUserDetails = useCallback(async () => {
|
const fetchUserDetails = useCallback(async () => {
|
||||||
return window.electron.getMe();
|
return window.electron.getMe().then((userDetails) => {
|
||||||
}, []);
|
if (userDetails == null) {
|
||||||
|
clearUserDetails();
|
||||||
|
}
|
||||||
|
|
||||||
|
return userDetails;
|
||||||
|
});
|
||||||
|
}, [clearUserDetails]);
|
||||||
|
|
||||||
const patchUser = useCallback(
|
const patchUser = useCallback(
|
||||||
async (displayName: string, imageProfileUrl: string | null) => {
|
async (displayName: string, imageProfileUrl: string | null) => {
|
||||||
|
@ -8,6 +8,13 @@ export enum DownloadSourceStatus {
|
|||||||
Errored,
|
Errored,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class UserNotLoggedInError extends Error {
|
||||||
|
constructor() {
|
||||||
|
super("user not logged in");
|
||||||
|
this.name = "UserNotLoggedInError";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const FORMAT = ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
|
const FORMAT = ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
|
||||||
|
|
||||||
export const formatBytes = (bytes: number): string => {
|
export const formatBytes = (bytes: number): string => {
|
||||||
|
Loading…
Reference in New Issue
Block a user