The Coline App SDK: apps, tools, file types and surfaces that run inside Coline workspaces, and public websites with Coline Sites. TypeScript, with an in-memory test workspace. MIT.
| 5 |
| * as base64; the sandbox bridge sends an ArrayBuffer and the host encodes |
| 6 | * it. The server re-checks size and sniffs the type independently. |
| 7 | */ |
| 8 |
| 9 | export const MAX_INLINE_UPLOAD_BYTES = 25 * 1024 * 1024; |
| 10 |
| 11 | export interface EncodedUpload { |
| 12 | name: string; |
| 13 | mimeType: string | null; |
| 14 | base64: string; |
| 15 | size: number; |
| 16 | driveId?: string; |
| 17 | parentId?: string | null; |
| 18 | } |
| 19 |
| 20 | export async function uploadDataToBytes(data: ColineUploadData): Promise<Uint8Array> { |
| 21 | if (data instanceof Uint8Array) return data; |
| 22 | if (data instanceof ArrayBuffer) return new Uint8Array(data); |
| 23 | if (typeof Blob !== "undefined" && data instanceof Blob) { |
| 24 | return new Uint8Array(await data.arrayBuffer()); |
| 25 | } |
| 26 | if (typeof data === "object" && data !== null && "base64" in data) { |
| 27 | return base64ToBytes(data.base64); |
| 28 | } |
| 29 | throw new TypeError("files.upload expects Uint8Array, ArrayBuffer, Blob, or { base64 }."); |
| 30 | } |
| 31 |
| 32 | export function bytesToBase64(bytes: Uint8Array): string { |
| 33 | let binary = ""; |
| 34 | const chunk = 0x8000; |
| 35 | for (let index = 0; index < bytes.length; index += chunk) { |
| 36 | binary += String.fromCharCode(...bytes.subarray(index, index + chunk)); |
| 37 | } |
| 38 | return btoa(binary); |
| 39 | } |
| 40 |
| 41 | export function base64ToBytes(value: string): Uint8Array { |
| 42 | const binary = atob(value); |
| 43 | const bytes = new Uint8Array(binary.length); |
| 44 | for (let index = 0; index < binary.length; index += 1) bytes[index] = binary.charCodeAt(index); |
| 45 | return bytes; |
| 46 | } |
| 47 |
| 48 | export async function encodeUpload(input: ColineFileUploadInput): Promise<EncodedUpload> { |
| 49 | const bytes = await uploadDataToBytes(input.data); |
| 50 | if (bytes.byteLength > MAX_INLINE_UPLOAD_BYTES) { |
| 51 | throw Object.assign( |
| 52 | new Error("files.upload accepts up to 25 MB. Use files.createUpload for larger files."), |
| 53 | { code: "upload_too_large" }, |
| 54 | ); |
| 55 | } |
| 56 | const blobType = |
| 57 | typeof Blob !== "undefined" && input.data instanceof Blob ? input.data.type || null : null; |
| 58 | return { |
| 59 | name: input.name, |
| 60 | mimeType: input.mimeType ?? blobType, |
| 61 | base64: bytesToBase64(bytes), |
| 62 | size: bytes.byteLength, |
| 63 | ...(input.driveId ? { driveId: input.driveId } : {}), |
| 64 | ...(input.parentId !== undefined ? { parentId: input.parentId } : {}), |
| 65 | }; |
| 66 | } |
| 67 |