1/* Panggilan jaringan dari sisi klien. Dua alur (estafet dan bagikan) memakai
2 bentuk yang sama, dan bagian yang pernah disalin ke keduanya tinggal di
3 sini: pesan galat dari badan respons, dan PUT ke URL presigned R2.
4
5 Unggahan tidak lewat rute server (ARCHITECTURE §7): badannya lumayan besar
6 dan fungsi serverless punya batas ukuran isi permintaan. Klien mengunggah
7 langsung ke bucket lewat URL presigned (M10), lalu rute complete yang
8 memeriksa hasilnya. */
9
10export interface SignedUpload {
11 path: string
12 url: string
13}
14
15/** Pesan dari server sudah berbahasa Indonesia (DESIGN-SYSTEM §12); pesan
16 * cadangan dipakai bila badan responsnya bukan JSON yang diharapkan. */
17export async function messageFrom(
18 response: Response,
19 fallback: string
20): Promise<string> {
21 try {
22 const body = (await response.json()) as { error?: unknown }
23 if (typeof body.error === "string" && body.error !== "") {
24 return body.error
25 }
26 } catch {
27 /* Bukan JSON: pakai pesan cadangan. */
28 }
29 return fallback
30}
31
32/** PUT ke URL presigned. Jenis isi ikut ditandatangani di URL, jadi header
33 * `Content-Type`-nya wajib sama dengan yang dipakai server saat
34 * menandatangani. */
35export async function putToSignedUrl(
36 upload: SignedUpload,
37 blob: Blob,
38 contentType: string,
39 failedMessage: string
40): Promise<void> {
41 const response = await fetch(upload.url, {
42 method: "PUT",
43 headers: { "Content-Type": contentType },
44 body: blob,
45 })
46 if (!response.ok) {
47 throw new Error(failedMessage)
48 }
49}
50