{"release_id":"01a1998d-9802-0000-b0f7-33a23307f4ed","chunk_id":"01a1998d-990e-7170-b21d-2268c73828cc","file":"CXjdkV_7.mjs","mappings":"qTAiCA,eAAsBA,EAAWC,EAA2B,CAC3D,GAAI,CACH,MAAMC,EAAQ,MAAMD,EAAK,YAAA,EACzB,OAAO,IAAI,KAAK,CAACC,CAAK,EAAGD,EAAK,KAAM,CAAE,KAAMA,EAAK,KAAM,aAAcA,EAAK,aAAc,CACzF,MAAQ,CAGP,OAAOA,CACR,CACD,CAMA,MAAME,EAAmB,GAAK,KAAO,KAWrC,eAAsBC,EAAcH,EAA2B,CAK9D,OADIA,EAAK,KAAK,WAAW,QAAQ,GAC7BA,EAAK,KAAOE,EAAyBF,EAClCD,EAAWC,CAAI,CACvB,CAKA,MAAMI,EACL,2EAUM,SAASC,EAAiBC,EAAuB,CACvD,GAAIA,aAAe,cAAgBA,EAAI,OAAS,mBAAoB,MAAO,GAC3E,MAAMC,EAAYD,GAA4C,cAC9D,GAAIC,aAAoB,cAAgBA,EAAS,OAAS,mBAAoB,MAAO,GACrF,MAAMC,EAAID,EACV,MAAO,CAAED,GAAqC,QAASE,GAAG,KAAMA,GAAG,OAAO,EAAE,KAC1EC,GAAM,OAAOA,GAAM,UAAYL,EAAmB,KAAKK,CAAC,CAAA,CAE3D,CAKO,MAAMC,EACZ,8EAWM,SAASC,EAAYL,EAAkE,CAC7F,MAAMM,EAAQP,EAAiBC,CAAG,EAClC,MAAO,CACN,QAASM,EAAQF,EAA0B,8CAC3C,MAAOJ,aAAe,MAAQ,GAAGA,EAAI,IAAI,KAAKA,EAAI,OAAO,GAAK,OAAOA,CAAG,EACxE,MAAAM,CAAA,CAEF","names":["detachFile","file","bytes","MAX_DETACH_BYTES","detachIfImage","STALE_FILE_MESSAGE","isStaleFileError","err","original","o","s","UNREADABLE_FILE_MESSAGE","pickFailure","stale"],"sources":["../../../../../../src/lib/utils/stale-file.ts"],"sourcesContent":["// Shared helpers for the stale-`File` upload bug (RAT: stale-file NotReadableError).\n//\n// A `<input type=\"file\">` (and a drag-drop `DataTransfer`) hands back a File that\n// is only a *reference* to a file on disk — the bytes are read lazily, when\n// something finally streams it. That reference can go dead while the user is off\n// somewhere else (the login/OAuth flow, minutes in the email/OTP path), and the\n// upload then fails with a `NotReadableError` that nothing downstream can\n// recover from: `heicToJpeg`, `compressImage` and `stripExifClient` all fall\n// back to resolving the original file on error, so re-running the pipeline just\n// hands the same dead handle back.\n//\n// This was first fixed on `/rate` (commit 80400962) with the two functions\n// below; every other picker (`/d`, `/a/submit`, `/b/start`, `/f`) still ran the\n// raw handle straight into `heicToJpeg`, so the failure kept stranding uploaders\n// there. Extracted here so every picker can share the exact same fix.\n\n/**\n * Read a picked File's bytes once, right inside the gesture that picked it —\n * when the OS handle is guaranteed live — and return an in-memory copy. A File\n * built from an ArrayBuffer has no OS handle behind it and cannot go stale,\n * however long the user then takes in a login flow. This *removes* the failure\n * rather than retrying it (a retry has nothing to re-read from anyway: the\n * `<input>` is usually already unmounted by the time the upload runs).\n *\n * CALL ORDER MATTERS. This costs the file's full size in memory, twice over\n * (the ArrayBuffer plus the File copy), on the main thread. It was written for\n * `/rate`, which takes images only and caps them at 4 MB. The video pickers cap\n * at 200–500 MB, so detaching one before its size and type have been checked\n * peaks around a gigabyte and kills the tab on iOS Safari — a failure no\n * try/catch here can intercept. Validate first, and only detach images:\n * `detachIfImage` below does both checks, or call this directly once you have\n * already established the file is a small image.\n */\nexport async function detachFile(file: File): Promise<File> {\n\ttry {\n\t\tconst bytes = await file.arrayBuffer();\n\t\treturn new File([bytes], file.name, { type: file.type, lastModified: file.lastModified });\n\t} catch {\n\t\t// Unreadable already, at pick time. Nothing to detach — let the normal\n\t\t// pipeline run and let the upload error path report it.\n\t\treturn file;\n\t}\n}\n\n/** Largest file worth pulling into memory to detach. Images that clear every\n *  picker's own cap sit well under this; it exists so an unvalidated or\n *  mislabelled giant can't reach `arrayBuffer()` even if a caller forgets to\n *  check. Videos are always skipped regardless of size. */\nconst MAX_DETACH_BYTES = 12 * 1024 * 1024;\n\n/**\n * Detach only when it's safe to: an image, small enough to hold in memory.\n * Anything else is returned untouched — a video streams straight to storage\n * from the picked handle, so the stale-handle window barely applies to it, and\n * reading it here is what crashes mobile tabs.\n *\n * Still call this *after* the caller's own type/size validation where possible;\n * this is the floor, not a substitute for a real error message.\n */\nexport async function detachIfImage(file: File): Promise<File> {\n\t// Excluded by what it is NOT, rather than by `image/*`: iOS hands back HEIC\n\t// pickings with an empty `type` often enough that an allowlist would quietly\n\t// drop the fix on exactly the browser it was written for.\n\tif (file.type.startsWith('video/')) return file;\n\tif (file.size > MAX_DETACH_BYTES) return file;\n\treturn detachFile(file);\n}\n\n// A File whose bytes are gone reports the same thing in every engine, just\n// worded differently: \"NotReadableError\" / \"The requested file could not be\n// read...\" (Chromium, Firefox) and \"WebKitBlobResource error 1\" (iOS Safari).\nconst STALE_FILE_MESSAGE =\n\t/notreadableerror|not be read|not readable|i\\/o error|webkitblobresource/i;\n\n/**\n * True when `err` is (or wraps) a dead-File-handle failure. Handles both shapes:\n * a directly-thrown `DOMException`, and Supabase storage-js's returned\n * `StorageUnknownError` — a non-Response fetch rejection is wrapped and RETURNED\n * as `{ data: null, error }` (the browser client never sets `throwOnError`), so\n * the original `DOMException` hangs off `.originalError` and a plain\n * `err instanceof DOMException` in a catch block can never match it.\n */\nexport function isStaleFileError(err: unknown): boolean {\n\tif (err instanceof DOMException && err.name === 'NotReadableError') return true;\n\tconst original = (err as { originalError?: unknown } | null)?.originalError;\n\tif (original instanceof DOMException && original.name === 'NotReadableError') return true;\n\tconst o = original as { name?: string; message?: string } | undefined;\n\treturn [(err as { message?: string } | null)?.message, o?.name, o?.message].some(\n\t\t(s) => typeof s === 'string' && STALE_FILE_MESSAGE.test(s)\n\t);\n}\n\n/** What a picker says when the picked file can't be read at all. On Android\n *  that is nearly always a photo that lives only in the cloud and never came\n *  down to the phone, so the sentence names the way out. */\nexport const UNREADABLE_FILE_MESSAGE =\n\t\"couldn't read that file — save it to your phone first, or pick another one.\";\n\n/**\n * For a picker's catch around the work it does on a file before it can look at\n * it: the detach above and the HEIC sniff, both of which read the bytes. When\n * the file can't be read those reads throw, and most pickers caught nothing —\n * the rejection went unhandled, the pick did nothing on screen, and people\n * tapped the upload button again (91 Chrome Android users in 30 days, all\n * `NotReadableError`). Gives the sentence to show and the `upload_fail`\n * properties that keep the miss measurable.\n */\nexport function pickFailure(err: unknown): { message: string; error: string; stale: boolean } {\n\tconst stale = isStaleFileError(err);\n\treturn {\n\t\tmessage: stale ? UNREADABLE_FILE_MESSAGE : \"couldn't open that file — pick another one.\",\n\t\terror: err instanceof Error ? `${err.name}: ${err.message}` : String(err),\n\t\tstale\n\t};\n}\n"],"version":3}