import { EncryptedAttachmentInfo, decryptAttachment, encryptAttachment, } from 'browser-encrypt-attachment'; import { MatrixClient, MatrixError, MatrixEvent, Room, UploadProgress, UploadResponse, } from 'matrix-js-sdk'; import { IImageInfo, IThumbnailContent, IVideoInfo } from '../../types/matrix/common'; export const matchMxId = (id: string): RegExpMatchArray | null => id.match(/^([@!$+#])(\S+):(\S+)$/); export const validMxId = (id: string): boolean => !!matchMxId(id); export const getMxIdServer = (userId: string): string | undefined => matchMxId(userId)?.[3]; export const getMxIdLocalPart = (userId: string): string | undefined => matchMxId(userId)?.[2]; export const isUserId = (id: string): boolean => validMxId(id) && id.startsWith('@'); export const isRoomId = (id: string): boolean => validMxId(id) && id.startsWith('!'); export const isRoomAlias = (id: string): boolean => validMxId(id) && id.startsWith('#'); export const parseMatrixToUrl = (url: string): [string | undefined, string | undefined] => { const href = decodeURIComponent(url); const match = href.match(/^https?:\/\/matrix.to\/#\/([@!$+#]\S+:[^\\?|^\s|^\\/]+)(\?(via=\S+))?/); if (!match) return [undefined, undefined]; const [, g1AsMxId, , g3AsVia] = match; return [g1AsMxId, g3AsVia]; }; export const getRoomWithCanonicalAlias = (mx: MatrixClient, alias: string): Room | undefined => mx.getRooms()?.find((room) => room.getCanonicalAlias() === alias); export const getImageInfo = (img: HTMLImageElement, fileOrBlob: File | Blob): IImageInfo => { const info: IImageInfo = {}; info.w = img.width; info.h = img.height; info.mimetype = fileOrBlob.type; info.size = fileOrBlob.size; return info; }; export const getVideoInfo = (video: HTMLVideoElement, fileOrBlob: File | Blob): IVideoInfo => { const info: IVideoInfo = {}; info.duration = Number.isNaN(video.duration) ? undefined : Math.floor(video.duration * 1000); info.w = video.videoWidth; info.h = video.videoHeight; info.mimetype = fileOrBlob.type; info.size = fileOrBlob.size; return info; }; export const getThumbnailContent = (thumbnailInfo: { thumbnail: File | Blob; encInfo: EncryptedAttachmentInfo | undefined; mxc: string; width: number; height: number; }): IThumbnailContent => { const { thumbnail, encInfo, mxc, width, height } = thumbnailInfo; const content: IThumbnailContent = { thumbnail_info: { mimetype: thumbnail.type, size: thumbnail.size, w: width, h: height, }, }; if (encInfo) { content.thumbnail_file = { ...encInfo, url: mxc, }; } else { content.thumbnail_url = mxc; } return content; }; export const encryptFile = async ( file: File | Blob ): Promise<{ encInfo: EncryptedAttachmentInfo; file: File; originalFile: File | Blob; }> => { const dataBuffer = await file.arrayBuffer(); const encryptedAttachment = await encryptAttachment(dataBuffer); const encFile = new File([encryptedAttachment.data], file.name, { type: file.type, }); return { encInfo: encryptedAttachment.info, file: encFile, originalFile: file, }; }; export const decryptFile = async ( dataBuffer: ArrayBuffer, type: string, encInfo: EncryptedAttachmentInfo ): Promise => { const dataArray = await decryptAttachment(dataBuffer, encInfo); const blob = new Blob([dataArray], { type }); return blob; }; export type TUploadContent = File | Blob; export type ContentUploadOptions = { name?: string; fileType?: string; hideFilename?: boolean; onPromise?: (promise: Promise) => void; onProgress?: (progress: UploadProgress) => void; onSuccess: (mxc: string) => void; onError: (error: MatrixError) => void; }; export const uploadContent = async ( mx: MatrixClient, file: TUploadContent, options: ContentUploadOptions ) => { const { name, fileType, hideFilename, onProgress, onPromise, onSuccess, onError } = options; const uploadPromise = mx.uploadContent(file, { name, type: fileType, includeFilename: !hideFilename, progressHandler: onProgress, }); onPromise?.(uploadPromise); try { const data = await uploadPromise; const mxc = data.content_uri; if (mxc) onSuccess(mxc); else onError(new MatrixError(data)); } catch (e: any) { const error = typeof e?.message === 'string' ? e.message : undefined; const errcode = typeof e?.name === 'string' ? e.message : undefined; onError(new MatrixError({ error, errcode })); } }; export const matrixEventByRecency = (m1: MatrixEvent, m2: MatrixEvent) => m2.getTs() - m1.getTs(); export const factoryEventSentBy = (senderId: string) => (ev: MatrixEvent) => ev.getSender() === senderId; export const eventWithShortcode = (ev: MatrixEvent) => typeof ev.getContent().shortcode === 'string'; export function hasDMWith(mx: MatrixClient, userId: string) { const dmLikeRooms = mx .getRooms() .filter((room) => mx.isRoomEncrypted(room.roomId) && room.getMembers().length <= 2); return dmLikeRooms.find((room) => room.getMember(userId)); }