From 4355bad581bdad4457bdc0e75f29865e5eb3227b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Muhammed=20Tanr=C4=B1kulu?= Date: Sun, 28 Jul 2024 19:18:54 +0200 Subject: [PATCH 1/4] add browser preview template for both image & avatar endpoints --- package.json | 2 +- src/controller/avatarImage.ts | 10 ++ src/controller/ensImage.ts | 10 ++ src/index.ts | 1 + src/service/avatar.ts | 6 +- src/template-document.ts | 327 ++++++++++++++++++++++++++++++++++ yarn.lock | 8 +- 7 files changed, 356 insertions(+), 8 deletions(-) create mode 100644 src/template-document.ts diff --git a/package.json b/package.json index ab97db6..514dd51 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ }, "dependencies": { "@adraffy/ens-normalize": "^1.10.0", - "@ensdomains/ens-avatar": "^1.0.0-alpha.2.ethers.6", + "@ensdomains/ens-avatar": "^1.0.0-alpha.3.ethers.6", "@ensdomains/ensjs": "^3.7.0", "@types/lodash": "^4.14.170", "btoa": "^1.2.1", diff --git a/src/controller/avatarImage.ts b/src/controller/avatarImage.ts index 17d0c99..f4b5565 100644 --- a/src/controller/avatarImage.ts +++ b/src/controller/avatarImage.ts @@ -11,6 +11,7 @@ import { import { RESPONSE_TIMEOUT } from '../config'; import { getAvatarImage } from '../service/avatar'; import getNetwork, { NetworkName } from '../service/network'; +import createDocumentfromTemplate from '../template-document'; export async function avatarImage(req: Request, res: Response) { // #swagger.description = 'ENS avatar image' @@ -29,6 +30,15 @@ export async function avatarImage(req: Request, res: Response) { description: 'Image file' } */ if (!res.headersSent) { + if (req.header('sec-fetch-dest') === 'document') { + const documentTemplate = createDocumentfromTemplate({ buffer, metadata: { name, network: networkName }, mimeType }); + res + .writeHead(200, { + 'Content-Type': 'text/html', + }) + .end(documentTemplate); + return; + } res .writeHead(200, { 'Content-Type': mimeType, diff --git a/src/controller/ensImage.ts b/src/controller/ensImage.ts index 8de06b9..644f3fa 100644 --- a/src/controller/ensImage.ts +++ b/src/controller/ensImage.ts @@ -10,6 +10,7 @@ import { RESPONSE_TIMEOUT } from '../config'; import { checkContract } from '../service/contract'; import { getDomain } from '../service/domain'; import getNetwork, { NetworkName } from '../service/network'; +import createDocumentfromTemplate from '../template-document'; /* istanbul ignore next */ export async function ensImage(req: Request, res: Response) { @@ -39,6 +40,15 @@ export async function ensImage(req: Request, res: Response) { version ); if (result.image_url) { + if (req.header('sec-fetch-dest') === 'document') { + const documentTemplate = createDocumentfromTemplate({ metadata: {...result, network: networkName }}); + res + .writeHead(200, { + 'Content-Type': 'text/html', + }) + .end(documentTemplate); + return; + } const base64 = result.image_url.replace('data:image/svg+xml;base64,', ''); const buffer = Buffer.from(base64, 'base64'); if (!res.headersSent) { diff --git a/src/index.ts b/src/index.ts index f710719..56238a4 100644 --- a/src/index.ts +++ b/src/index.ts @@ -22,6 +22,7 @@ const setCacheHeader = function ( `public, max-age=${period}, s-maxage=${period}` ); } + res.append('Vary', 'Sec-Fetch-Dest'); next(); }; diff --git a/src/service/avatar.ts b/src/service/avatar.ts index db2e8f9..eb738c2 100644 --- a/src/service/avatar.ts +++ b/src/service/avatar.ts @@ -64,7 +64,7 @@ export class AvatarMetadata { this.uri = uri; } - async getImage() { + async getImage(): Promise<[Buffer, string]> { let avatarURI; try { avatarURI = await this.avtResolver.getAvatar(this.uri, { @@ -102,7 +102,7 @@ export class AvatarMetadata { assert(!!response, 'Response is empty'); - const mimeType = response?.headers.get('Content-Type'); + const mimeType = response?.headers.get('Content-Type') || ''; const data = await response?.buffer(); if (mimeType?.includes('svg') || isSvg(data.toString())) { @@ -189,7 +189,7 @@ export async function getAvatarMeta( export async function getAvatarImage( provider: JsonRpcProvider, name: string -): Promise { +): Promise<[Buffer, string]> { const avatar = new AvatarMetadata(provider, name); return await avatar.getImage(); } diff --git a/src/template-document.ts b/src/template-document.ts new file mode 100644 index 0000000..7170ff9 --- /dev/null +++ b/src/template-document.ts @@ -0,0 +1,327 @@ +import { CANVAS_FONT_PATH } from './config'; +import { importFont } from './utils/importFont'; + +const fontSatoshiBold = importFont(CANVAS_FONT_PATH, 'font/truetype'); + +interface DocumentMetadata { + name: string; + network: string; + image_url?: string; +} + +interface DocumentTemplateFields { + buffer?: Buffer; + metadata: DocumentMetadata; + mimeType?: string; +} + +export default function createDocumentfromTemplate({ + buffer, + metadata, + mimeType, +}: DocumentTemplateFields) { + if (!metadata && !buffer) { + throw 'Either image url, or image buffer needs to be provided for the document template'; + } + const image = + (metadata && metadata.image_url) || + (buffer && + `data:${mimeType};base64,${Buffer.from(buffer).toString('base64')}`); + return ` + + + + ${metadata.name} + + + + +
+
+ ${ + mimeType !== 'image/svg+xml' + ? `${metadata.name}` + : buffer + } +
+
+
+
+ +
+
path Parameters
+ + + + + + + ${ + buffer + ? "" + : ` + + + ` + } + ${ + buffer + ? ` + + + ` + : ` + + + ` + } + +
+ + networkName +
required
+
+
+
+ + string + (networkName) +
+
+ Enum: + "mainnet" + "sepolia" +
+
+
+

Name of the chain to query for.

+
+
+
+
+ + contractAddress +
required
+
+
+
+ + string + (contractAddress) +
+
+ Example: + 0x57f1887a8BF19b14fC0dF6Fd9B2acc9Af147eA85 +
+
+
+
+
+
+ + name +
required
+
+
+
+ + string + (ensName) +
+
+ Example: ${metadata.name} +
+
+
+

ENS Name

+
+
+
+
+ + tokenId +
required
+
+
+
+ + string + (tokenId / ENS name) +
+
+ Example: +
+ 4221908525551133525058944220830153... / ${metadata.name} +
+
+
+

TokenID = Labelhash(v1) /Namehash(v2) of your ENS name.

+

+ More: + https://docs.ens.domains/contract-api-reference/name-processing#hashing-names +

+
+
+
+
+
+
+

Responses

+
+ + + + +
+
+
+
+
+
+ + +`; +} diff --git a/yarn.lock b/yarn.lock index 8bdfe66..db7b7e8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -250,10 +250,10 @@ dns-packet "^5.6.1" typescript-logging "^1.0.1" -"@ensdomains/ens-avatar@^1.0.0-alpha.2.ethers.6": - version "1.0.0-alpha.2.ethers.6" - resolved "https://registry.yarnpkg.com/@ensdomains/ens-avatar/-/ens-avatar-1.0.0-alpha.2.ethers.6.tgz#b406c20fec6fa98e3427e92970686c8cdafbddeb" - integrity sha512-lx8Ggmp6uLPQHzgunMvVUG3MfMLhyhSoY03Fb5HyjAK39OjYl7pMB+nmWnh+l4foKztMqXBfXLJ9WOYls5AyAw== +"@ensdomains/ens-avatar@^1.0.0-alpha.3.ethers.6": + version "1.0.0-alpha.3.ethers.6" + resolved "https://registry.yarnpkg.com/@ensdomains/ens-avatar/-/ens-avatar-1.0.0-alpha.3.ethers.6.tgz#7a0707a4608044340427d21cb27ed317563abe89" + integrity sha512-CLO2xIace+9pZygKbw+6Kt1qSwhx86m/L9hpQSEjqbOqPX2Wph4LVX84D07bFzI7oTRKKWobojIFYqNRd0JQ3Q== dependencies: "@ethersproject/contracts" "^5.7.0" "@ethersproject/providers" "^5.7.0" From 7aee029bf21a420290f30b54410d75030301d9cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Muhammed=20Tanr=C4=B1kulu?= Date: Mon, 29 Jul 2024 11:20:48 +0200 Subject: [PATCH 2/4] update template style --- src/template-document.ts | 144 ++++++++++++++++++++++++++++++++------- 1 file changed, 118 insertions(+), 26 deletions(-) diff --git a/src/template-document.ts b/src/template-document.ts index 7170ff9..8b3d4ef 100644 --- a/src/template-document.ts +++ b/src/template-document.ts @@ -31,6 +31,7 @@ export default function createDocumentfromTemplate({ + ${metadata.name}