/ src / core / request-upload.ts
request-upload.ts
 1  import type { FormDataFile } from "@literate.ink/utilities";
 2  import { UploadFailedError, type SessionHandle } from "~/models";
 3  import { aesKeys } from "~/api/private/keys";
 4  import { AES } from "~/api/private/aes";
 5  import { apiProperties } from "~/api/private/api-properties";
 6  import { USER_AGENT } from "~/api/private/user-agent";
 7  
 8  export class RequestUpload {
 9    public order: string;
10    public id = `selecfile_1_${Date.now()}`;
11  
12    private url: string;
13    private form: FormData;
14    private headers: Record<string, string> = {
15      "User-Agent": USER_AGENT
16    };
17  
18    public constructor (
19      private session: SessionHandle,
20      public functionName: string,
21      file: FormDataFile,
22      public fileName: string
23    ) {
24      session.information.order++;
25  
26      const { iv, key } = aesKeys(session);
27      this.order = AES.encrypt(session.information.order.toString(), key, iv);
28  
29      const properties = apiProperties(this.session);
30  
31      const form = new FormData();
32      form.append(properties.fileUploadOrderNumber, this.order);
33      form.append(properties.fileUploadSession, session.information.id.toString());
34      form.append(properties.fileUploadRequestId, functionName);
35      form.append(properties.fileUploadFileId, this.id);
36      form.append(properties.fileUploadMd5, "");
37      // @ts-expect-error : trust me.
38      form.append("files[]", file, fileName);
39  
40      this.form = form;
41      this.url = session.information.url + `/uploadfilesession/${session.information.accountKind}/${session.information.id}`;
42      this.headers["Content-Disposition"] = `attachment; filename="${encodeURI(fileName)}"`;
43    }
44  
45    public async send (): Promise<void> {
46      // Please, see the following comment to understand.
47      // https://github.com/oven-sh/bun/issues/7917#issuecomment-1872454367
48      const textBody = await new Response(this.form).text();
49      this.headers["Content-Type"] = `multipart/form-data; boundary=${textBody
50        .split("\n")[0]
51        .slice(2)}`;
52  
53      let state = 3; // Set to UPLOADING by default.
54  
55      while (state === 3) { // UPLOADING
56        const response = await this.session.fetcher({
57          url: new URL(this.url),
58          method: "POST",
59          content: textBody,
60          headers: this.headers
61        });
62  
63        const json = JSON.parse(response.content);
64        state = json.etat;
65      }
66  
67      // Even if there's an error, it bumped.
68      this.session.information.order++;
69  
70      if (state === 0 || state === 2) { // UNKNOWN or ERROR
71        throw new UploadFailedError();
72      }
73    }
74  }