Skip to content

Commit

Permalink
serve icons w doctype header [SLT-547] (#3500)
Browse files Browse the repository at this point in the history
* serve icons w doctype header [SLT-547]

* optional headers

* prettier

---------

Co-authored-by: Trajan0x <[email protected]>
  • Loading branch information
trajan0x and trajan0x authored Jan 28, 2025
1 parent 8811b0d commit d60545c
Show file tree
Hide file tree
Showing 5 changed files with 958 additions and 23 deletions.
1 change: 1 addition & 0 deletions packages/rest-api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
"express-validator": "^7.2.0",
"http-proxy-middleware": "^3.0.3",
"jest": "^29.7.0",
"jsdom": "^26.0.0",
"lodash": "^4.17.21",
"supertest": "^6.3.3",
"typescript": "^5.3.3",
Expand Down
13 changes: 11 additions & 2 deletions packages/rest-api/src/routes/addressIconRoute.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,14 @@ import express from 'express'
import { BRIDGABLE_TOKENS, Token } from '@synapsecns/synapse-constants'
import fetch from 'cross-fetch'

import { addSvgHeaderIfMissing } from '../utils/svgUtils'

const router: express.Router = express.Router()

router.get('/:chainId/:address.svg', async (req, res) => {
const chainId = parseInt(req.params.chainId, 10)
const address = req.params.address.toLowerCase()
const addHeaders = req.query.headers === 'true'

// Find the token with matching address on the specified chain
const token = Object.values(BRIDGABLE_TOKENS[chainId] || []).find(
Expand Down Expand Up @@ -35,14 +38,20 @@ router.get('/:chainId/:address.svg', async (req, res) => {
}

const buffer = await response.arrayBuffer()
const contentType = response.headers.get('content-type') || 'image/svg+xml'

// Only process SVG files if headers are requested
const processedBuffer = contentType === 'image/svg+xml' && addHeaders

Check warning on line 44 in packages/rest-api/src/routes/addressIconRoute.ts

View workflow job for this annotation

GitHub Actions / lint

Insert `⏎·····`
? addSvgHeaderIfMissing(buffer)

Check warning on line 45 in packages/rest-api/src/routes/addressIconRoute.ts

View workflow job for this annotation

GitHub Actions / lint

Insert `··`
: Buffer.from(buffer)

Check warning on line 46 in packages/rest-api/src/routes/addressIconRoute.ts

View workflow job for this annotation

GitHub Actions / lint

Insert `··`

// Set cache headers (cache for 1 week)
res.set({
'Cache-Control': 'public, max-age=604800',
'Content-Type': response.headers.get('content-type') || 'image/svg+xml',
'Content-Type': contentType,
})

res.send(Buffer.from(buffer))
res.send(processedBuffer)
} catch (error) {
console.error('Error fetching token icon:', error)
res.status(500).json({ error: 'Failed to fetch token icon' })
Expand Down
14 changes: 12 additions & 2 deletions packages/rest-api/src/routes/chainIconRoute.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@ import express from 'express'
import { CHAINS, Chain } from '@synapsecns/synapse-constants'
import fetch from 'cross-fetch'

import { addSvgHeaderIfMissing } from '../utils/svgUtils'

const router: express.Router = express.Router()

router.get('/:chainId.svg', async (req, res) => {
const chainId = parseInt(req.params.chainId, 10)
const addHeaders = req.query.headers === 'true'

// Find the chain with matching ID
const chain = Object.values(CHAINS).find(
Expand All @@ -26,14 +29,21 @@ router.get('/:chainId.svg', async (req, res) => {
}

const buffer = await response.arrayBuffer()
const contentType = response.headers.get('content-type') || 'image/svg+xml'

// Only process SVG files if headers are requested
const processedBuffer =
contentType === 'image/svg+xml' && addHeaders
? addSvgHeaderIfMissing(buffer)
: Buffer.from(buffer)

// Set cache headers (cache for 1 week)
res.set({
'Cache-Control': 'public, max-age=604800',
'Content-Type': response.headers.get('content-type') || 'image/svg+xml',
'Content-Type': contentType,
})

res.send(Buffer.from(buffer))
res.send(processedBuffer)
} catch (error) {
console.error('Error fetching chain icon:', error)
res.status(500).json({ error: 'Failed to fetch chain icon' })
Expand Down
44 changes: 44 additions & 0 deletions packages/rest-api/src/utils/svgUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { Buffer } from 'buffer'

import { JSDOM } from 'jsdom'

const XML_HEADER = '<?xml version="1.0" encoding="UTF-8" standalone="no"?>'
const DOCTYPE =
'<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">'

export const addSvgHeaderIfMissing = (buffer: ArrayBuffer): Buffer => {
const content = Buffer.from(buffer).toString('utf-8')

// Create a DOM with the SVG content
const dom = new JSDOM(content, { contentType: 'image/svg+xml' })
const document = dom.window.document

// Get the serialized content to check if we need to add headers
const serialized = dom.serialize()
const needsXmlHeader = !serialized.includes('<?xml')
const needsDoctype = !serialized.includes('<!DOCTYPE')

if (!needsXmlHeader && !needsDoctype) {
return Buffer.from(content)
}

// Build the final SVG with required headers
let finalSvg = ''
if (needsXmlHeader) {
finalSvg += XML_HEADER + '\n'
}
if (needsDoctype) {
finalSvg += DOCTYPE + '\n'
}

// Add the SVG content
const svgElement = document.querySelector('svg')
if (svgElement) {
finalSvg += svgElement.outerHTML
} else {
// If no SVG element found, return original content
return Buffer.from(content)
}

return Buffer.from(finalSvg)
}
Loading

0 comments on commit d60545c

Please sign in to comment.