]> Untitled Git - lemmy-ui.git/blob - src/shared/services/HttpService.ts
Darkly tweak (#1811)
[lemmy-ui.git] / src / shared / services / HttpService.ts
1 import { getHttpBase } from "@utils/env";
2 import { LemmyHttp } from "lemmy-js-client";
3 import { toast } from "../toast";
4 import { I18NextService } from "./I18NextService";
5
6 export 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, silent = false) {
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             if (!silent) {
65               console.error(`API error: ${error}`);
66               toast(I18NextService.i18n.t(error), "danger");
67             }
68             return {
69               state: "failed",
70               msg: error,
71             };
72           }
73         };
74       }
75     }
76   }
77 }
78
79 export function wrapClient(client: LemmyHttp, silent = false) {
80   // unfortunately, this verbose cast is necessary
81   return new WrappedLemmyHttpClient(
82     client,
83     silent
84   ) as unknown as WrappedLemmyHttp;
85 }
86
87 export class HttpService {
88   static #_instance: HttpService;
89   #silent_client: WrappedLemmyHttp;
90   #client: WrappedLemmyHttp;
91
92   private constructor() {
93     const lemmyHttp = new LemmyHttp(getHttpBase());
94     this.#client = wrapClient(lemmyHttp);
95     this.#silent_client = wrapClient(lemmyHttp, true);
96   }
97
98   static get #Instance() {
99     return this.#_instance ?? (this.#_instance = new this());
100   }
101
102   public static get client() {
103     return this.#Instance.#client;
104   }
105
106   public static get silent_client() {
107     return this.#Instance.#silent_client;
108   }
109 }