Files
IdP/apps/frontend/lib/chat-media-upload.ts
2026-06-26 17:49:22 +03:00

122 lines
3.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import {
apiFetch,
PresignedUploadResponse,
sendChatMessage,
uploadMediaObject,
type ChatMessage
} from '@/lib/api';
import { compressChatImage, isCompressibleImageFile, readImageDimensions } from '@/lib/chat-image-compress';
import { detectChatMessageType, resolveChatContentType } from '@/lib/chat-media';
import { e2eKindFromMessageType, encryptE2EBlob, type E2EMessagePayload } from '@/lib/e2e-chat';
export interface ChatMediaUploadItem {
file: File;
sendAsFile: boolean;
width?: number;
height?: number;
}
type EncryptOutgoing = (payload: E2EMessagePayload) => Promise<string>;
export async function uploadChatMediaBatch(params: {
roomId: string;
token: string;
items: ChatMediaUploadItem[];
caption?: string;
isE2E: boolean;
userId?: string;
peerE2eKey?: string | null;
encryptOutgoing?: EncryptOutgoing;
}): Promise<ChatMessage[]> {
const { roomId, token, items, caption, isE2E, userId, peerE2eKey, encryptOutgoing } = params;
if (!items.length) return [];
const albumId = crypto.randomUUID();
const created: ChatMessage[] = [];
for (let index = 0; index < items.length; index += 1) {
const item = items[index]!;
let file = item.file;
let width = item.width;
let height = item.height;
const shouldCompress = isCompressibleImageFile(file) && !item.sendAsFile;
if (shouldCompress) {
const compressed = await compressChatImage(file);
file = compressed.file;
width = compressed.width ?? width;
height = compressed.height ?? height;
} else if (isCompressibleImageFile(file) && (!width || !height)) {
const dimensions = await readImageDimensions(file);
width = dimensions.width;
height = dimensions.height;
}
const contentType = resolveChatContentType(file);
const messageType = item.sendAsFile && isCompressibleImageFile(item.file) ? 'FILE' : detectChatMessageType(file);
const metadata = {
fileName: item.file.name,
fileSize: file.size,
albumId,
albumIndex: index,
albumCount: items.length,
...(width ? { width } : {}),
...(height ? { height } : {}),
...(item.sendAsFile ? { sendAsFile: true } : {})
};
let uploadFile = file;
let uploadContentType = contentType;
let messageContent: string | undefined =
index === 0 && caption?.trim()
? caption.trim()
: messageType === 'FILE'
? item.file.name
: undefined;
let isEncrypted = false;
if (isE2E) {
if (!userId || !peerE2eKey || !encryptOutgoing) {
throw new Error('Секретный чат недоступен без ключей E2E у обоих участников');
}
const encryptedBuffer = await encryptE2EBlob(userId, peerE2eKey, await file.arrayBuffer());
uploadFile = new File([encryptedBuffer], `${item.file.name}.lendryenc`, { type: 'application/octet-stream' });
uploadContentType = 'application/octet-stream';
messageContent = await encryptOutgoing({
kind: e2eKindFromMessageType(messageType),
fileName: item.file.name,
mimeType: contentType,
fileSize: file.size,
text: index === 0 ? caption?.trim() : undefined
});
isEncrypted = true;
}
const presigned = await apiFetch<PresignedUploadResponse>(
`/media/chat/${roomId}/media/upload-url`,
{
method: 'POST',
body: JSON.stringify({ contentType: uploadContentType, fileName: uploadFile.name })
},
token
);
await uploadMediaObject(presigned, uploadFile, uploadContentType);
const message = await sendChatMessage(
roomId,
{
type: messageType,
storageKey: presigned.storageKey,
mimeType: uploadContentType,
content: messageContent,
isEncrypted,
metadataJson: JSON.stringify(isE2E ? { e2e: true, ...metadata } : metadata)
},
token
);
created.push(message);
}
return created;
}