7982c243c0
- media-api: useUploadAttachment (presign → direct PUT to IIOS storage),
useDownloadUrl (short-lived signed URL), isImage, 25MB cap
- socket + REST send now carry attachment {contentRef, mimeType, sizeBytes}
- composer: 📎 attach button, staged chip, send with attachment (text optional)
- MessageBubble renders AttachmentView (inline image or file chip), aligned
to the message side; empty bubble suppressed for attachment-only messages
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
38 lines
1.8 KiB
TypeScript
38 lines
1.8 KiB
TypeScript
"use client";
|
|
|
|
// Media (attachment) helpers over the be-crm data door (crm.media.*). The browser transfers bytes
|
|
// DIRECTLY to IIOS storage via the signed URLs — be-crm only mints them. Used by Messenger + Mail.
|
|
|
|
import { useCallback } from "react";
|
|
import { useAppShell } from "@abe-kap/appshell-sdk/react";
|
|
|
|
export interface UploadedAttachment { contentRef: string; mimeType: string; sizeBytes: number; filename: string }
|
|
|
|
export const MAX_ATTACHMENT_BYTES = 26 * 1024 * 1024; // matches IIOS's cap
|
|
|
|
export function isImage(mime?: string | null): boolean {
|
|
return !!mime && mime.startsWith("image/");
|
|
}
|
|
|
|
/** Upload a File → { contentRef, mimeType, sizeBytes, filename }. Throws on oversize / failure. */
|
|
export function useUploadAttachment() {
|
|
const { sdk } = useAppShell();
|
|
return useCallback(async (file: File): Promise<UploadedAttachment> => {
|
|
if (file.size > MAX_ATTACHMENT_BYTES) throw new Error("File is too large (max 25 MB).");
|
|
const mime = file.type || "application/octet-stream";
|
|
const { objectKey, uploadUrl } = (await sdk.command("crm.media.presignUpload", { mime, sizeBytes: file.size })) as { objectKey: string; uploadUrl: string };
|
|
const res = await fetch(uploadUrl, { method: "PUT", body: file });
|
|
if (!res.ok) throw new Error(`Upload failed (${res.status}).`);
|
|
return { contentRef: objectKey, mimeType: mime, sizeBytes: file.size, filename: file.name };
|
|
}, [sdk]);
|
|
}
|
|
|
|
/** Mint a short-lived signed URL to display/download an attachment by its contentRef. */
|
|
export function useDownloadUrl() {
|
|
const { sdk } = useAppShell();
|
|
return useCallback(async (contentRef: string, mime?: string): Promise<string> => {
|
|
const { url } = (await sdk.command("crm.media.presignDownload", { contentRef, ...(mime ? { mime } : {}) })) as { url: string };
|
|
return url;
|
|
}, [sdk]);
|
|
}
|