aboutsummaryrefslogtreecommitdiff
path: root/Timeline/ClientApp/src/app/services/user.ts
diff options
context:
space:
mode:
Diffstat (limited to 'Timeline/ClientApp/src/app/services/user.ts')
-rw-r--r--Timeline/ClientApp/src/app/services/user.ts393
1 files changed, 0 insertions, 393 deletions
diff --git a/Timeline/ClientApp/src/app/services/user.ts b/Timeline/ClientApp/src/app/services/user.ts
deleted file mode 100644
index f253fc19..00000000
--- a/Timeline/ClientApp/src/app/services/user.ts
+++ /dev/null
@@ -1,393 +0,0 @@
-import React, { useState, useEffect } from "react";
-import { BehaviorSubject, Observable, from } from "rxjs";
-import { map, filter } from "rxjs/operators";
-
-import { UiLogicError } from "@/common";
-import { convertError } from "@/utilities/rxjs";
-
-import { HttpNetworkError, BlobWithEtag, NotModified } from "@/http/common";
-import {
- getHttpTokenClient,
- HttpCreateTokenBadCredentialError,
-} from "@/http/token";
-import {
- getHttpUserClient,
- HttpUserNotExistError,
- HttpUser,
-} from "@/http/user";
-
-import { dataStorage, throwIfNotNetworkError } from "./common";
-import { DataHub } from "./DataHub";
-import { pushAlert } from "./alert";
-
-export type User = HttpUser;
-
-export interface UserAuthInfo {
- username: string;
- administrator: boolean;
-}
-
-export interface UserWithToken extends User {
- token: string;
-}
-
-export interface LoginCredentials {
- username: string;
- password: string;
-}
-
-export class BadCredentialError {
- message = "login.badCredential";
-}
-
-const USER_STORAGE_KEY = "currentuser";
-
-export class UserService {
- private userSubject = new BehaviorSubject<UserWithToken | null | undefined>(
- undefined
- );
-
- get user$(): Observable<UserWithToken | null | undefined> {
- return this.userSubject;
- }
-
- get currentUser(): UserWithToken | null | undefined {
- return this.userSubject.value;
- }
-
- async checkLoginState(): Promise<UserWithToken | null> {
- if (this.currentUser !== undefined) {
- console.warn("Already checked user. Can't check twice.");
- }
-
- const savedUser = await dataStorage.getItem<UserWithToken | null>(
- USER_STORAGE_KEY
- );
-
- if (savedUser == null) {
- this.userSubject.next(null);
- return null;
- }
-
- this.userSubject.next(savedUser);
-
- const savedToken = savedUser.token;
- try {
- const res = await getHttpTokenClient().verify({ token: savedToken });
- const user: UserWithToken = { ...res.user, token: savedToken };
- await dataStorage.setItem<UserWithToken>(USER_STORAGE_KEY, user);
- this.userSubject.next(user);
- pushAlert({
- type: "success",
- message: {
- type: "i18n",
- key: "user.welcomeBack",
- },
- });
- return user;
- } catch (error) {
- if (error instanceof HttpNetworkError) {
- pushAlert({
- type: "danger",
- message: { type: "i18n", key: "user.verifyTokenFailedNetwork" },
- });
- return savedUser;
- } else {
- await dataStorage.removeItem(USER_STORAGE_KEY);
- this.userSubject.next(null);
- pushAlert({
- type: "danger",
- message: { type: "i18n", key: "user.verifyTokenFailed" },
- });
- return null;
- }
- }
- }
-
- async login(
- credentials: LoginCredentials,
- rememberMe: boolean
- ): Promise<void> {
- if (this.currentUser) {
- throw new UiLogicError("Already login.");
- }
- try {
- const res = await getHttpTokenClient().create({
- ...credentials,
- expire: 30,
- });
- const user: UserWithToken = {
- ...res.user,
- token: res.token,
- };
- if (rememberMe) {
- await dataStorage.setItem<UserWithToken>(USER_STORAGE_KEY, user);
- }
- this.userSubject.next(user);
- } catch (e) {
- if (e instanceof HttpCreateTokenBadCredentialError) {
- throw new BadCredentialError();
- } else {
- throw e;
- }
- }
- }
-
- async logout(): Promise<void> {
- if (this.currentUser === undefined) {
- throw new UiLogicError("Please check user first.");
- }
- if (this.currentUser === null) {
- throw new UiLogicError("No login.");
- }
- await dataStorage.removeItem(USER_STORAGE_KEY);
- this.userSubject.next(null);
- }
-
- changePassword(
- oldPassword: string,
- newPassword: string
- ): Observable<unknown> {
- if (this.currentUser == undefined) {
- throw new UiLogicError("Not login or checked now, can't log out.");
- }
- const $ = from(
- getHttpUserClient().changePassword(
- {
- oldPassword,
- newPassword,
- },
- this.currentUser.token
- )
- );
- $.subscribe(() => {
- void this.logout();
- });
- return $;
- }
-}
-
-export const userService = new UserService();
-
-export function useRawUser(): UserWithToken | null | undefined {
- const [user, setUser] = useState<UserWithToken | null | undefined>(
- userService.currentUser
- );
- useEffect(() => {
- const subscription = userService.user$.subscribe((u) => setUser(u));
- return () => {
- subscription.unsubscribe();
- };
- });
- return user;
-}
-
-export function useUser(): UserWithToken | null {
- const [user, setUser] = useState<UserWithToken | null>(() => {
- const initUser = userService.currentUser;
- if (initUser === undefined) {
- throw new UiLogicError(
- "This is a logic error in user module. Current user can't be undefined in useUser."
- );
- }
- return initUser;
- });
- useEffect(() => {
- const sub = userService.user$.subscribe((u) => {
- if (u === undefined) {
- throw new UiLogicError(
- "This is a logic error in user module. User emitted can't be undefined later."
- );
- }
- setUser(u);
- });
- return () => {
- sub.unsubscribe();
- };
- });
- return user;
-}
-
-export function useUserLoggedIn(): UserWithToken {
- const user = useUser();
- if (user == null) {
- throw new UiLogicError("You assert user has logged in but actually not.");
- }
- return user;
-}
-
-export function checkLogin(): UserWithToken {
- const user = userService.currentUser;
- if (user == null) {
- throw new UiLogicError("You must login to perform the operation.");
- }
- return user;
-}
-
-export class UserNotExistError extends Error {}
-
-export class UserInfoService {
- saveUser(user: HttpUser): void {
- const key = user.username;
- void this._userHub.optionalInitLineWithSyncAction(key, async (line) => {
- await this.doSaveUser(user);
- line.next({ user, type: "synced" });
- });
- }
-
- saveUsers(users: HttpUser[]): void {
- return users.forEach((user) => this.saveUser(user));
- }
-
- private getCachedUser(username: string): Promise<User | null> {
- return dataStorage.getItem<HttpUser | null>(`user.${username}`);
- }
-
- private doSaveUser(user: HttpUser): Promise<void> {
- return dataStorage.setItem<HttpUser>(`user.${user.username}`, user).then();
- }
-
- syncUser(username: string): Promise<void> {
- return this._userHub.getLineOrCreate(username).sync();
- }
-
- private _userHub = new DataHub<
- string,
- | { user: User; type: "cache" | "synced" | "offline" }
- | { user?: undefined; type: "notexist" | "offline" }
- >({
- sync: async (key, line) => {
- if (line.value == undefined) {
- const cache = await this.getCachedUser(key);
- if (cache != null) {
- line.next({ user: cache, type: "cache" });
- }
- }
-
- try {
- const res = await getHttpUserClient().get(key);
- await this.doSaveUser(res);
- line.next({ user: res, type: "synced" });
- } catch (e) {
- if (e instanceof HttpUserNotExistError) {
- line.next({ type: "notexist" });
- } else {
- const cache = await this.getCachedUser(key);
- line.next({ user: cache ?? undefined, type: "offline" });
- throwIfNotNetworkError(e);
- }
- }
- },
- });
-
- getUser$(username: string): Observable<User> {
- return this._userHub.getObservable(username).pipe(
- map((state) => state?.user),
- filter((user): user is User => user != null)
- );
- }
-
- private getCachedAvatar(username: string): Promise<BlobWithEtag | null> {
- return dataStorage.getItem<BlobWithEtag | null>(`user.${username}.avatar`);
- }
-
- private saveAvatar(username: string, data: BlobWithEtag): Promise<void> {
- return dataStorage
- .setItem<BlobWithEtag>(`user.${username}.avatar`, data)
- .then();
- }
-
- syncAvatar(username: string): Promise<void> {
- return this._avatarHub.getLineOrCreate(username).sync();
- }
-
- private _avatarHub = new DataHub<
- string,
- | { data: Blob; type: "cache" | "synced" | "offline" }
- | { data?: undefined; type: "notexist" | "offline" }
- >({
- sync: async (key, line) => {
- const cache = await this.getCachedAvatar(key);
- if (line.value == null) {
- if (cache != null) {
- line.next({ data: cache.data, type: "cache" });
- }
- }
-
- if (cache == null) {
- try {
- const avatar = await getHttpUserClient().getAvatar(key);
- await this.saveAvatar(key, avatar);
- line.next({ data: avatar.data, type: "synced" });
- } catch (e) {
- line.next({ type: "offline" });
- throwIfNotNetworkError(e);
- }
- } else {
- try {
- const res = await getHttpUserClient().getAvatar(key, cache.etag);
- if (res instanceof NotModified) {
- line.next({ data: cache.data, type: "synced" });
- } else {
- const avatar = res;
- await this.saveAvatar(key, avatar);
- line.next({ data: avatar.data, type: "synced" });
- }
- } catch (e) {
- line.next({ data: cache.data, type: "offline" });
- throwIfNotNetworkError(e);
- }
- }
- },
- });
-
- getAvatar$(username: string): Observable<Blob> {
- return this._avatarHub.getObservable(username).pipe(
- map((state) => state.data),
- filter((blob): blob is Blob => blob != null)
- );
- }
-
- getUserInfo(username: string): Observable<User> {
- return from(getHttpUserClient().get(username)).pipe(
- convertError(HttpUserNotExistError, UserNotExistError)
- );
- }
-
- async setAvatar(username: string, blob: Blob): Promise<void> {
- const user = checkLogin();
- await getHttpUserClient().putAvatar(username, blob, user.token);
- this._avatarHub.getLine(username)?.next({ data: blob, type: "synced" });
- }
-
- async setNickname(username: string, nickname: string): Promise<void> {
- const user = checkLogin();
- return getHttpUserClient()
- .patch(username, { nickname }, user.token)
- .then((user) => {
- this.saveUser(user);
- });
- }
-}
-
-export const userInfoService = new UserInfoService();
-
-export function useAvatar(username?: string): Blob | undefined {
- const [state, setState] = React.useState<Blob | undefined>(undefined);
- React.useEffect(() => {
- if (username == null) {
- setState(undefined);
- return;
- }
-
- const subscription = userInfoService
- .getAvatar$(username)
- .subscribe((blob) => {
- setState(blob);
- });
- return () => {
- subscription.unsubscribe();
- };
- }, [username]);
- return state;
-}