122 lines
3.9 KiB
TypeScript
122 lines
3.9 KiB
TypeScript
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;
|
||
}
|