]> Untitled Git - lemmy-ui.git/blob - src/shared/services/HttpService.ts
Merge branch 'main' into route-data-refactor
[lemmy-ui.git] / src / shared / services / HttpService.ts
1 import { LemmyHttp } from "lemmy-js-client";
2 import { getHttpBase } from "../../shared/env";
3 import { i18n } from "../../shared/i18next";
4 import { toast } from "../../shared/utils";
5
6 type EmptyRequestState = {
7   state: "empty";
8 };
9
10 type LoadingRequestState = {
11   state: "loading";
12 };
13
14 export type FailedRequestState = {
15   state: "failed";
16   msg: string;
17 };
18
19 type SuccessRequestState<T> = {
20   state: "success";
21   data: T;
22 };
23
24 /**
25  * Shows the state of an API request.
26  *
27  * Can be empty, loading, failed, or success
28  */
29 export type RequestState<T> =
30   | EmptyRequestState
31   | LoadingRequestState
32   | FailedRequestState
33   | SuccessRequestState<T>;
34
35 export type WrappedLemmyHttp = {
36   [K in keyof LemmyHttp]: LemmyHttp[K] extends (...args: any[]) => any
37     ? ReturnType<LemmyHttp[K]> extends Promise<infer U>
38       ? (...args: Parameters<LemmyHttp[K]>) => Promise<RequestState<U>>
39       : (
40           ...args: Parameters<LemmyHttp[K]>
41         ) => Promise<RequestState<LemmyHttp[K]>>
42     : LemmyHttp[K];
43 };
44
45 class WrappedLemmyHttpClient {
46   #client: LemmyHttp;
47
48   constructor(client: LemmyHttp) {
49     this.#client = client;
50
51     for (const key of Object.getOwnPropertyNames(
52       Object.getPrototypeOf(this.#client)
53     )) {
54       if (key !== "constructor") {
55         WrappedLemmyHttpClient.prototype[key] = async (...args) => {
56           try {
57             const res = await this.#client[key](...args);
58
59             return {
60               data: res,
61               state: !(res === undefined || res === null) ? "success" : "empty",
62             };
63           } catch (error) {
64             console.error(`API error: ${error}`);
65             toast(i18n.t(error), "danger");
66             return {
67               state: "failed",
68               msg: error,
69             };
70           }
71         };
72       }
73     }
74   }
75 }
76
77 export function wrapClient(client: LemmyHttp) {
78   return new WrappedLemmyHttpClient(client) as unknown as WrappedLemmyHttp; // unfortunately, this verbose cast is necessary
79 }
80
81 export class HttpService {
82   static #_instance: HttpService;
83   #client: WrappedLemmyHttp;
84
85   private constructor() {
86     this.#client = wrapClient(new LemmyHttp(getHttpBase()));
87   }
88
89   static get #Instance() {
90     return this.#_instance ?? (this.#_instance = new this());
91   }
92
93   public static get client() {
94     return this.#Instance.#client;
95   }
96 }