Skip to content
File

Blob: src/client/lib/uploads.ts

typescript57 lines
1import { api } from "@/client/lib/api";
2import { MAX_UPLOAD_SIZE, UPLOAD_MIME_SET } from "@/shared/constants";
3import type { PresignRequest } from "@/shared/types";
4 
5export async function uploadFile(
6 workspaceId: string,
7 file: File,
8 pageId?: string,
9 shareToken?: string,
10): Promise<string> {
11 if (!UPLOAD_MIME_SET.has(file.type)) throw new Error("File type not allowed");
12 if (file.size > MAX_UPLOAD_SIZE) throw new Error("File too large (max 10MB)");
13 
14 const upload = await api.uploads.presign(
15 workspaceId,
16 {
17 filename: file.name,
18 content_type: file.type as PresignRequest["content_type"],
19 size_bytes: file.size,
20 page_id: pageId ?? null,
21 },
22 shareToken,
23 );
24 await api.uploads.uploadData(upload.upload_url, file, shareToken);
25 return upload.url;
26}
27 
28/**
29 * Fetch an upload blob and encode it as a data URL. Needed by the canvas
30 * surface: Excalidraw won't render images from remote URLs — it requires
31 * dataURLs in its `BinaryFiles` map (upstream #9491).
32 *
33 * Auth: the GET /uploads/:id route authorises via the refresh cookie
34 * (same-origin, `credentials: "include"`) or `?share=<token>` query param
35 * for share viewers. Do not attach an `Authorization` bearer header — the
36 * worker ignores it on this route.
37 */
38export async function fetchUploadAsDataURL(
39 uploadId: string,
40 shareToken?: string,
41): Promise<{ dataURL: string; mime: string }> {
42 const qs = shareToken ? `?share=${encodeURIComponent(shareToken)}` : "";
43 const res = await fetch(`/uploads/${uploadId}${qs}`, {
44 credentials: "include",
45 });
46 if (!res.ok) throw new Error(`Upload fetch failed: ${res.status}`);
47 const mime = res.headers.get("content-type") ?? "application/octet-stream";
48 const blob = await res.blob();
49 const dataURL = await new Promise<string>((resolve, reject) => {
50 const reader = new FileReader();
51 reader.onload = () => resolve(reader.result as string);
52 reader.onerror = () => reject(reader.error ?? new Error("FileReader failed"));
53 reader.readAsDataURL(blob);
54 });
55 return { dataURL, mime };
56}