Created
March 26, 2024 12:37
-
-
Save kuririn1/1bfb41567684bc3d1bbed4f40a5e9fd7 to your computer and use it in GitHub Desktop.
XMTP middleware for Frog
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { Context, Next } from "hono"; | |
import { validateFramesPost } from "@xmtp/frames-validator"; | |
export const xmtpSupport = async (c: Context, next: Next) => { | |
await next(); | |
const isFrame = c.res.headers.get("content-type")?.includes("html"); | |
if (!isFrame) return; | |
let html = await c.res.clone().text(); | |
const content = extractState(html); | |
if (content) { | |
const state = encodeState(decodeURIComponent(content)); | |
html = addStateToPostUrl(html, state); | |
html = addStatesToButtonTargets(html, state); | |
} | |
const metaTag = '<meta property="of:accepts:xmtp" content="2024-02-01" />'; | |
html = html.replace(/(<head>)/i, `$1${metaTag}`); | |
c.res = new Response(html, { | |
headers: { | |
"content-type": "text/html", | |
}, | |
}); | |
}; | |
export const validateXMTPUser = async (c: Context, next: Next) => { | |
if (c.req.method !== "POST") { | |
await next(); | |
return; | |
} | |
const requestBody = | |
(await c.req.raw | |
.clone() | |
.json() | |
.catch(() => {})) || {}; | |
if (requestBody?.clientProtocol?.includes("xmtp")) { | |
c.set("client", "xmtp"); | |
const { verifiedWalletAddress } = await validateFramesPost(requestBody); | |
c.set("verifiedWalletAddress", verifiedWalletAddress); | |
} else { | |
//add farcaster check | |
c.set("client", "farcaster"); | |
} | |
await next(); | |
}; | |
function addStatesToButtonTargets(html: string, query: string): string { | |
const metaTagRegex = | |
/<meta property="fc:frame:button:([1-9]):target" content="([^"]+)"/g; | |
return html.replace(metaTagRegex, (match, buttonNum, url) => { | |
const separator = url.includes("?") ? "&" : "?"; | |
const updatedUrl = `${url}${separator}${query}`; | |
return `<meta property="fc:frame:button:${buttonNum}:target" content="${updatedUrl}"`; | |
}); | |
} | |
function addStateToPostUrl(html: string, query: string): string { | |
const metaTagRegex = /<meta property="fc:frame:post_url" content="([^"]+)"/; | |
const match = html.match(metaTagRegex); | |
if (match && !match[1].includes("?")) { | |
const updatedUrl = `${match[1]}?${query}`; | |
html = html.replace( | |
metaTagRegex, | |
`<meta property="fc:frame:post_url" content="${updatedUrl}"`, | |
); | |
} | |
return html; | |
} | |
function extractState(htmlString: string): string | null { | |
const metaTagStart = htmlString.indexOf('<meta property="fc:frame:state"'); | |
if (metaTagStart === -1) return null; | |
const contentStart = htmlString.indexOf('content="', metaTagStart); | |
if (contentStart === -1) return null; | |
const contentEnd = htmlString.indexOf('"', contentStart + 9); | |
if (contentEnd === -1) return null; | |
return htmlString.substring(contentStart + 9, contentEnd); | |
} | |
function encodeState(content: string): string { | |
const data = JSON.parse(content); | |
const encodedData: Record<string, string> = {}; | |
for (const key in data) { | |
if (Array.isArray(data[key])) { | |
encodedData[key] = | |
"%2523A_" + data[key].map(encodeURIComponent).join("%252C"); | |
} else { | |
encodedData[key] = encodeURIComponent(data[key]).replace(/%2F/g, "%252F"); | |
} | |
} | |
return Object.entries(encodedData) | |
.map(([key, value]) => `${key}=${value}`) | |
.join("&"); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment