Skip to content

Instantly share code, notes, and snippets.

@kuririn1
Created March 26, 2024 12:37
Show Gist options
  • Save kuririn1/1bfb41567684bc3d1bbed4f40a5e9fd7 to your computer and use it in GitHub Desktop.
Save kuririn1/1bfb41567684bc3d1bbed4f40a5e9fd7 to your computer and use it in GitHub Desktop.
XMTP middleware for Frog
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("&amp;");
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment