mirror of
https://github.com/p-stream/p-stream.git
synced 2026-01-11 20:10:32 +00:00
222 lines
6.1 KiB
TypeScript
222 lines
6.1 KiB
TypeScript
import { useCallback } from "react";
|
|
|
|
import { SessionResponse } from "@/backend/accounts/auth";
|
|
import { bookmarkMediaToInput } from "@/backend/accounts/bookmarks";
|
|
import {
|
|
bytesToBase64,
|
|
bytesToBase64Url,
|
|
encryptData,
|
|
keysFromMnemonic,
|
|
signChallenge,
|
|
} from "@/backend/accounts/crypto";
|
|
import { getGroupOrder } from "@/backend/accounts/groupOrder";
|
|
import { importBookmarks, importProgress } from "@/backend/accounts/import";
|
|
import { getLoginChallengeToken, loginAccount } from "@/backend/accounts/login";
|
|
import { progressMediaItemToInputs } from "@/backend/accounts/progress";
|
|
import {
|
|
getRegisterChallengeToken,
|
|
registerAccount,
|
|
} from "@/backend/accounts/register";
|
|
import { removeSession } from "@/backend/accounts/sessions";
|
|
import { getSettings } from "@/backend/accounts/settings";
|
|
import {
|
|
UserResponse,
|
|
getBookmarks,
|
|
getProgress,
|
|
getUser,
|
|
} from "@/backend/accounts/user";
|
|
import { useAuthData } from "@/hooks/auth/useAuthData";
|
|
import { useBackendUrl } from "@/hooks/auth/useBackendUrl";
|
|
import { AccountWithToken, useAuthStore } from "@/stores/auth";
|
|
import { BookmarkMediaItem } from "@/stores/bookmarks";
|
|
import { ProgressMediaItem } from "@/stores/progress";
|
|
|
|
export interface RegistrationData {
|
|
recaptchaToken?: string;
|
|
mnemonic: string;
|
|
userData: {
|
|
device: string;
|
|
profile: {
|
|
colorA: string;
|
|
colorB: string;
|
|
icon: string;
|
|
};
|
|
};
|
|
}
|
|
|
|
export interface LoginData {
|
|
mnemonic: string;
|
|
userData: {
|
|
device: string;
|
|
};
|
|
}
|
|
|
|
export function useAuth() {
|
|
const currentAccount = useAuthStore((s) => s.account);
|
|
const profile = useAuthStore((s) => s.account?.profile);
|
|
const loggedIn = !!useAuthStore((s) => s.account);
|
|
const backendUrl = useBackendUrl();
|
|
const {
|
|
logout: userDataLogout,
|
|
login: userDataLogin,
|
|
syncData,
|
|
} = useAuthData();
|
|
|
|
const login = useCallback(
|
|
async (loginData: LoginData) => {
|
|
if (!backendUrl) return;
|
|
const keys = await keysFromMnemonic(loginData.mnemonic);
|
|
const publicKeyBase64Url = bytesToBase64Url(keys.publicKey);
|
|
const { challenge } = await getLoginChallengeToken(
|
|
backendUrl,
|
|
publicKeyBase64Url,
|
|
);
|
|
const signature = await signChallenge(keys, challenge);
|
|
const loginResult = await loginAccount(backendUrl, {
|
|
challenge: {
|
|
code: challenge,
|
|
signature,
|
|
},
|
|
publicKey: publicKeyBase64Url,
|
|
device: await encryptData(loginData.userData.device, keys.seed),
|
|
});
|
|
|
|
const user = await getUser(backendUrl, loginResult.token);
|
|
const seedBase64 = bytesToBase64(keys.seed);
|
|
return userDataLogin(loginResult, user.user, user.session, seedBase64);
|
|
},
|
|
[userDataLogin, backendUrl],
|
|
);
|
|
|
|
const logout = useCallback(async () => {
|
|
if (!currentAccount || !backendUrl) return;
|
|
try {
|
|
await removeSession(
|
|
backendUrl,
|
|
currentAccount.token,
|
|
currentAccount.sessionId,
|
|
);
|
|
} catch {
|
|
// we dont care about failing to delete session
|
|
}
|
|
await userDataLogout();
|
|
}, [userDataLogout, backendUrl, currentAccount]);
|
|
|
|
const register = useCallback(
|
|
async (registerData: RegistrationData) => {
|
|
if (!backendUrl) return;
|
|
const { challenge } = await getRegisterChallengeToken(
|
|
backendUrl,
|
|
registerData.recaptchaToken,
|
|
);
|
|
const keys = await keysFromMnemonic(registerData.mnemonic);
|
|
const signature = await signChallenge(keys, challenge);
|
|
const registerResult = await registerAccount(backendUrl, {
|
|
challenge: {
|
|
code: challenge,
|
|
signature,
|
|
},
|
|
publicKey: bytesToBase64Url(keys.publicKey),
|
|
device: await encryptData(registerData.userData.device, keys.seed),
|
|
profile: registerData.userData.profile,
|
|
});
|
|
|
|
return userDataLogin(
|
|
registerResult,
|
|
registerResult.user,
|
|
registerResult.session,
|
|
bytesToBase64(keys.seed),
|
|
);
|
|
},
|
|
[backendUrl, userDataLogin],
|
|
);
|
|
|
|
const importData = useCallback(
|
|
async (
|
|
account: AccountWithToken,
|
|
progressItems: Record<string, ProgressMediaItem>,
|
|
bookmarks: Record<string, BookmarkMediaItem>,
|
|
) => {
|
|
if (!backendUrl) return;
|
|
if (
|
|
Object.keys(progressItems).length === 0 &&
|
|
Object.keys(bookmarks).length === 0
|
|
) {
|
|
return;
|
|
}
|
|
|
|
const progressInputs = Object.entries(progressItems).flatMap(
|
|
([tmdbId, item]) => progressMediaItemToInputs(tmdbId, item),
|
|
);
|
|
|
|
const bookmarkInputs = Object.entries(bookmarks).map(([tmdbId, item]) =>
|
|
bookmarkMediaToInput(tmdbId, item),
|
|
);
|
|
|
|
await Promise.all([
|
|
importProgress(backendUrl, account, progressInputs),
|
|
importBookmarks(backendUrl, account, bookmarkInputs),
|
|
]);
|
|
},
|
|
[backendUrl],
|
|
);
|
|
|
|
const restore = useCallback(
|
|
async (account: AccountWithToken) => {
|
|
if (!backendUrl) return;
|
|
let user: { user: UserResponse; session: SessionResponse };
|
|
try {
|
|
user = await getUser(backendUrl, account.token);
|
|
} catch (err) {
|
|
const anyError: any = err;
|
|
if (
|
|
anyError?.response?.status === 401 ||
|
|
anyError?.response?.status === 403 ||
|
|
anyError?.response?.status === 400
|
|
) {
|
|
await logout();
|
|
return;
|
|
}
|
|
console.error(err);
|
|
throw err;
|
|
}
|
|
|
|
const [bookmarks, progress, settings, groupOrder] = await Promise.all([
|
|
getBookmarks(backendUrl, account),
|
|
getProgress(backendUrl, account),
|
|
getSettings(backendUrl, account),
|
|
getGroupOrder(backendUrl, account),
|
|
]);
|
|
|
|
// Update account store with fresh user data (including nickname)
|
|
const { setAccount } = useAuthStore.getState();
|
|
if (account) {
|
|
setAccount({
|
|
...account,
|
|
nickname: user.user.nickname,
|
|
profile: user.user.profile,
|
|
});
|
|
}
|
|
|
|
syncData(
|
|
user.user,
|
|
user.session,
|
|
progress,
|
|
bookmarks,
|
|
settings,
|
|
groupOrder,
|
|
);
|
|
},
|
|
[backendUrl, syncData, logout],
|
|
);
|
|
|
|
return {
|
|
loggedIn,
|
|
profile,
|
|
login,
|
|
logout,
|
|
register,
|
|
restore,
|
|
importData,
|
|
};
|
|
}
|