1/**
2 * Only the types a provider turn accepts. A picture the provider would reject is still a
3 * file, so widening this map would promote attachments the send path cannot carry.
4 * Mirrors `PROVIDER_SEND_TURN_SUPPORTED_IMAGE_MIME_TYPES`.
5 */
6const IMAGE_MIME_TYPE_BY_EXTENSION = new Map([
7 ["gif", "image/gif"],
8 ["jpeg", "image/jpeg"],
9 ["jpg", "image/jpeg"],
10 ["png", "image/png"],
11 ["webp", "image/webp"],
12]);
13
14const SUPPORTED_IMAGE_MIME_TYPES = new Set(IMAGE_MIME_TYPE_BY_EXTENSION.values());
15
16/** What a picker writes when it did not recognize the file; the name is better evidence. */
17export const GENERIC_MIME_TYPES = new Set([
18 "application/octet-stream",
19 "binary/octet-stream",
20 "application/unknown",
21]);
22
23/**
24 * Recognizes pictures even when the picker omitted their MIME type. A picture chosen through
25 * the document picker arrives typed as a plain file, so what it *is* has to come from its own
26 * name and type rather than from which picker produced it.
27 */
28export function imageMimeType(attachment: {
29 readonly name: string;
30 readonly mimeType: string;
31}): string | null {
32 const mimeType = attachment.mimeType.split(";", 1)[0]?.trim().toLowerCase() ?? "";
33 if (SUPPORTED_IMAGE_MIME_TYPES.has(mimeType)) return mimeType;
34 // A declared but unsupported image type stays a file: the send path cannot carry it.
35 if (mimeType.startsWith("image/")) return null;
36 // The name is only evidence when nothing recorded what this is. A definite type already
37 // answers the question, and a `.png` on a PDF must not override it.
38 if (mimeType !== "" && !GENERIC_MIME_TYPES.has(mimeType)) return null;
39 const dotIndex = attachment.name.lastIndexOf(".");
40 return dotIndex < 0
41 ? null
42 : (IMAGE_MIME_TYPE_BY_EXTENSION.get(
43 attachment.name
44 .slice(dotIndex + 1)
45 .trim()
46 .toLowerCase(),
47 ) ?? null);
48}