diff --git a/packages/react/src/runtimes/attachment/ComposedAttachmentAdapter.ts b/packages/react/src/runtimes/attachment/ComposedAttachmentAdapter.ts new file mode 100644 index 000000000..2ba186445 --- /dev/null +++ b/packages/react/src/runtimes/attachment/ComposedAttachmentAdapter.ts @@ -0,0 +1,98 @@ +import { + ComposerAttachment, + MessageAttachment, +} from "../../context/stores/Attachment"; +import { AttachmentAdapter } from "./AttachmentAdapter"; + +function fileMatchesAccept(file: File, acceptString: string) { + // Check if the accept string is "*", which allows any file + if (acceptString === "*") { + return true; + } + + // Split the accept string into an array of allowed types + const allowedTypes = acceptString + .split(",") + .map((type) => type.trim().toLowerCase()); + + // Get the file's extension and MIME type + const fileExtension = "." + file.name.split(".").pop()!.toLowerCase(); + const fileMimeType = file.type.toLowerCase(); + + for (const type of allowedTypes) { + // Check for file extension match + if (type.startsWith(".") && type === fileExtension) { + return true; + } + + // Check for exact MIME type match + if (type.includes("/") && type === fileMimeType) { + return true; + } + + if (type === "image/*" || type === "video/*" || type === "audio/*") { + // Check for wildcard MIME type match + if (type.endsWith("/*")) { + const generalType = type.split("/")[0]!; + if (fileMimeType.startsWith(generalType + "/")) { + return true; + } + } + } + } + + return false; +} + +export class ComposedAttachmentAdapter implements AttachmentAdapter { + private _adapters: AttachmentAdapter[]; + + public accept: string; + + constructor(adapters: AttachmentAdapter[]) { + this._adapters = adapters; + + const wildcardIdx = adapters.findIndex((a) => a.accept === "*"); + if (wildcardIdx !== -1) { + if (wildcardIdx !== adapters.length - 1) + throw new Error( + "A wildcard adapter (handling all files) can only be specified as the last adapter.", + ); + + this.accept = "*"; + } else { + this.accept = adapters.map((a) => a.accept).join(","); + } + } + + public async add(state: { file: File }): Promise { + for (const adapter of this._adapters) { + if (fileMatchesAccept(state.file, adapter.accept)) { + return adapter.add(state); + } + } + throw new Error("No matching adapter found for file"); + } + + public async send( + attachment: ComposerAttachment, + ): Promise { + const adapters = this._adapters.slice(); + for (const adapter of adapters) { + if (fileMatchesAccept(attachment.file, adapter.accept)) { + return adapter.send(attachment); + } + } + throw new Error("No matching adapter found for attachment"); + } + + public async remove(attachment: ComposerAttachment): Promise { + const adapters = this._adapters.slice(); + for (const adapter of adapters) { + if (fileMatchesAccept(attachment.file, adapter.accept)) { + return adapter.remove(attachment); + } + } + throw new Error("No matching adapter found for attachment"); + } +}