Freika/dawarich · error · Error
translate(ERROR_KEYS[body.error] || "poster.order_errors.gen
Error message
translate(ERROR_KEYS[body.error] || "poster.order_errors.generic")
What it means
submitPrintOrder uploads the rendered poster PDF via XMLHttpRequest (XHR is used deliberately because fetch has no upload-progress events) and throws a translated, user-facing message when the response status is outside 200-299. The server is expected to return a JSON body with an error code from the known set - wrong_size, too_large, unknown_sku, payment_unavailable, not_pdf, unreadable - mapped through ERROR_KEYS to i18n keys; any other or missing code falls back to poster.order_errors.generic. If the error body is not JSON (e.g. an HTML 502), body.error access can itself throw.
Source
Thrown at app/javascript/poster_studio/ui/order_client.js:30
export async function submitPrintOrder({
url,
blob,
sku,
title,
themeBase,
layoutId,
onProgress,
}) {
const form = new FormData()
form.append("file", blob, "poster.pdf")
form.append("sku", sku)
form.append("title", title || "")
form.append("theme_base", themeBase || "")
form.append("layout_id", layoutId)
const { status, body } = await postForm(url, form, onProgress)
if (status < 200 || status >= 300) {
throw new Error(
translate(ERROR_KEYS[body.error] || "poster.order_errors.generic"),
)
}
return { token: body.token, checkoutUrl: body.checkout_url }
}
// XMLHttpRequest instead of fetch solely for upload progress events —
// print PDFs run tens of MB and fetch has no upload progress API.
function postForm(url, form, onProgress) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest()
xhr.open("POST", url)
xhr.responseType = "json"
if (onProgress) {
xhr.upload.addEventListener("progress", (event) => {
if (event.lengthComputable) onProgress(event.loaded / event.total)
})
}View on GitHub (pinned to 97fad417c5)
Solutions
- Check response body's error code first - it names the exact rejection reason; only trust generic when the code is absent
- For too_large: lower the poster DPI/size or raise the server's upload limit (nginx client_max_body_size / Rack config)
- For unknown_sku: verify the SKU list sent by the UI matches what the print controller accepts
- For not_pdf/unreadable: re-render the blob and confirm it starts with %PDF- before uploading
- Guard body being null/non-JSON so an HTML error page does not crash the handler with a TypeError
Example fix
// before
const { status, body } = await postForm(url, form, onProgress)
if (status < 200 || status >= 300) {
throw new Error(translate(ERROR_KEYS[body.error] || 'poster.order_errors.generic'))
}
// after
const { status, body } = await postForm(url, form, onProgress)
if (status < 200 || status >= 300) {
const code = body && typeof body.error === 'string' ? body.error : null
throw new Error(translate(ERROR_KEYS[code] || 'poster.order_errors.generic'))
} Defensive patterns
Strategy: validation
Validate before calling
function validateOrderInput({ blob, sku, maxBytes }) {
if (!(blob instanceof Blob)) throw new Error('A PDF blob is required')
if (blob.size > maxBytes) throw new Error(translate('poster.order_errors.too_large'))
if (blob.type && blob.type !== 'application/pdf') throw new Error(translate('poster.order_errors.not_pdf'))
if (!sku) throw new Error(translate('poster.order_errors.unknown_sku'))
} Type guard
/** Narrow a postForm result to a usable body before reading error codes. */
function hasErrorBody(result) {
return result.body !== null && typeof result.body === 'object' && 'error' in result.body
} Try / catch
try {
return await submitPrintOrder({ url, blob, sku, title, themeBase, layoutId, onProgress })
} catch (error) {
showOrderError(error.message) // already a translated, user-facing string
throw error
} Prevention
- Check blob.size against the server's upload cap before starting a multi-MB XHR upload
- Verify the blob starts with %PDF- (arrayBuffer peek) before submitting
- Validate sku/layout pairs against the same list the server accepts
- Keep ERROR_KEYS in sync when the print controller adds new error codes
When it happens
Trigger: POST of the print-order form where the server rejects the PDF: dimensions not matching the SKU (wrong_size), file above the server's size cap (too_large), a SKU that is not configured for printing (unknown_sku), payment provider unreachable (payment_unavailable), blob not a valid PDF (not_pdf), or a corrupt/unreadable PDF (unreadable). A 500 from an unknown cause yields the generic message.
Common situations: Large high-DPI posters exceeding an nginx client_max_body_size or app limit, SKU renamed on the print provider while the UI still offers the old one, checkout backend down or mis-credentialed, generating the blob from a canvas that produced an empty/invalid PDF, proxy timeouts on multi-MB uploads over slow links.
Related errors
AI-assisted analysis of Freika/dawarich@97fad417c5 (2026-08-21).
Data as JSON: /api/errors/693d18f42ea24c53.
Report an issue: GitHub.