* fix(invoices): embed company logo as PNG so it renders on invoice PDFs (#772) @react-pdf/renderer's <Image> only decodes JPG/PNG, but the logo upload route and the `logos` bucket also accept SVG and WebP. For an SVG/WebP logo @react-pdf silently swallows the decode error (console.warn inside a try/catch in its fetchImage step), so the invoice renders with NO logo and nothing surfaces — "Logotyp kommer inte med på fakturor". Fix: prepareInvoicePdfRender now fetches the stored logo and re-encodes it to a PNG data URL via sharp (SVGs rasterized at higher density), handing the template a company whose logo_url is that data URL. Renders regardless of upload format and removes the render-time dependency on a remote fetch inside @react-pdf. Falls back to the original URL unchanged on any failure (network, unreadable image, sharp unavailable), so behaviour is never worse than before. Result is cached per logo URL (5-min TTL, bounded to 50) since the logo is re-rendered on every invoice — twice per send and once per invoice in recurring/batch loops. prepareInvoicePdfRender becomes async and returns the resolved { branding, company }; all 8 call sites updated (6 routes, recurring-schedule-service, pending-operations/commit) to await it and pass the resolved company. Layered cleanly on top of the Swish-QR feature already on main — both coexist at every call site. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(invoices): bound and dedupe the logo fetch (review hardening) Review (PR Agent security): resolveLogoDataUrl fetched logo_url with no timeout or size limit. Add a 5s AbortSignal.timeout and a 5 MB cap (checked on the declared content-length and the read body) so a slow/oversized logo host can't hang or balloon an invoice render. SSRF itself isn't reachable today — logo_url is only ever set to a Supabase logos-bucket URL by the upload route — so an origin allowlist is intentionally skipped (would break self-hosted storage). Also coalesce concurrent renders of the same logo (preflight+final on a send, and recurring/batch loops) onto one in-flight fetch+encode instead of N. New test covers the size-cap fallback; existing SVG test now asserts the timeout signal. 9/9 pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
739f18fd1c
commit
10a0b1d8dd
@@ -959,13 +959,13 @@ async function commitSendInvoice(
|
||||
const isFreshAllocation = !invoice.invoice_number
|
||||
if (isFreshAllocation) {
|
||||
try {
|
||||
const preflight = prepareInvoicePdfRender(company as CompanySettings)
|
||||
const preflight = await prepareInvoicePdfRender(company as CompanySettings)
|
||||
await renderToBuffer(
|
||||
InvoicePDF({
|
||||
invoice: { ...(invoice as Invoice), invoice_number: 'F-PREVIEW' },
|
||||
customer,
|
||||
items,
|
||||
company: company as CompanySettings,
|
||||
company: preflight.company,
|
||||
originalInvoiceNumber,
|
||||
branding: preflight.branding,
|
||||
})
|
||||
@@ -995,14 +995,16 @@ async function commitSendInvoice(
|
||||
// after email delivery (line ~625); rendering with the stale 'draft' status
|
||||
// would stamp the customer's PDF with "UTKAST – inte en giltig faktura".
|
||||
const renderableInvoice = { ...(invoice as Invoice), status: 'sent' as const }
|
||||
const { branding } = prepareInvoicePdfRender(company as CompanySettings)
|
||||
const { branding, company: renderCompany } = await prepareInvoicePdfRender(
|
||||
company as CompanySettings,
|
||||
)
|
||||
const swishQrDataUrl = await buildSwishQrDataUrl(company as CompanySettings, renderableInvoice)
|
||||
const pdfBuffer = await renderToBuffer(
|
||||
InvoicePDF({
|
||||
invoice: renderableInvoice,
|
||||
customer,
|
||||
items,
|
||||
company: company as CompanySettings,
|
||||
company: renderCompany,
|
||||
originalInvoiceNumber,
|
||||
branding,
|
||||
swishQrDataUrl,
|
||||
|
||||
Reference in New Issue
Block a user