Skip to content

Commit

Permalink
Merge pull request #24 from depatchedmode/v0.8.0
Browse files Browse the repository at this point in the history
v0.8.0 - migration to frames.js 
#23
  • Loading branch information
depatchedmode authored Feb 6, 2024
2 parents fab7ad6 + f18c5ff commit 818bb8d
Show file tree
Hide file tree
Showing 11 changed files with 87 additions and 99 deletions.
63 changes: 28 additions & 35 deletions api/index.js
Original file line number Diff line number Diff line change
@@ -1,72 +1,65 @@
import { getFrameMessage } from "frames.js"
import landingPage from '../src/landing-page';
import frames from '../src/frames';
import { parseRequest, objectToURLSearchParams } from '../modules/utils';
import buildButtons from '../modules/buildButtons';
import buildInputs from '../modules/buildInputs';
import getTargetFrame from '../modules/getTargetFrame';
import { validateMessage } from '../src/data/message';
import { isFrameStolen } from '../src/data/antitheft';

export default async (req, context) => {
try {
const requestURL = new URL(req.url);
const payload = await parseRequest(req);
let from = requestURL.searchParams.get('frame');
let buttonId = null;
let frameIsStolen = false;
const frameMessage = payload ? await getFrameMessage(payload, {
hubHttpUrl: process.env.FARCASTER_HUB
}) : {};

if (payload) {
payload.referringFrame = from;
payload.validData = await validateMessage(payload.trustedData.messageBytes);
}

if (payload?.validData) {
buttonId = payload.validData.data.frameActionBody.buttonIndex;
frameIsStolen = await isFrameStolen(payload);
}
// extending the frames.js frameMessage object with a few things
// we require
// TODO: see about refactoring this more towards the frames.js style
frameMessage.from = requestURL.searchParams.get('frame');
frameMessage.requestURL = payload?.untrustedData.url;

const { targetFrameSrc, targetFrameName, redirectUrl } = getTargetFrame(from, buttonId, frames);
const { targetFrame, redirectURL } = await getTargetFrame(frameMessage);

if (redirectUrl) {
return await respondWithRedirect(redirectUrl);
} else if (frameIsStolen) {
return await respondWithFrame('stolen', frames['stolen'], payload);
} else if (targetFrameSrc) {
return await respondWithFrame(targetFrameName, targetFrameSrc, payload);
if (redirectURL) {
return await respondWithRedirect(redirectURL);
} else if (targetFrame) {
return await respondWithFrame(targetFrame, frameMessage);
} else {
console.error(`Unknown frame requested: ${targetFrameName}`);
console.error(`Unknown frame requested: ${targetFrame.name}`);
}
} catch (error) {
console.error(`Error processing request: ${error}`);
}
};

const respondWithRedirect = (redirectUrl) => {
const internalRedirectUrl = new URL(`${process.env.URL}/redirect`)
internalRedirectUrl.searchParams.set('redirectUrl',redirectUrl);
const respondWithRedirect = (redirectURL) => {
const internalRedirectURL = new URL(`${process.env.URL}/redirect`)
internalRedirectURL.searchParams.set('redirectURL',redirectURL);
return new Response('<div>redirect</div>',
{
status: 302,
headers: {
'Location': internalRedirectUrl,
'Location': internalRedirectURL,
},
}
);
}

const respondWithFrame = async (targetFrameName, targetFrameSrc, payload) => {
const respondWithFrame = async (targetFrame, frameMessage) => {
const searchParams = {
targetFrameName,
payload
targetFrameName: targetFrame.name,
frameMessage
}

const host = process.env.URL;
const frameContent = {
image: targetFrameSrc.image ?
`${host}/${targetFrameSrc.image}` :
image: targetFrame.image ?
`${host}/${targetFrame.image}` :
`${host}/og-image?${objectToURLSearchParams(searchParams)}` || '',
buttons: targetFrameSrc.buttons ? buildButtons(targetFrameSrc.buttons) : '',
inputs: targetFrameSrc.inputs ? buildInputs(targetFrameSrc.inputs) : '',
postURL: `${host}/?frame=${targetFrameName}`
buttons: targetFrame.buttons ? buildButtons(targetFrame.buttons) : '',
inputs: targetFrame.inputs ? buildInputs(targetFrame.inputs) : '',
postURL: `${host}/?frame=${targetFrame.name}`
};

return new Response(await landingPage(frameContent),
Expand Down
4 changes: 2 additions & 2 deletions api/og-image.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import { URLSearchParamsToObject } from '../modules/utils';
export default async (req, context) => {
const url = new URL(req.url);
const params = URLSearchParamsToObject(url.searchParams);
const targetFrameSrc = frames[params.targetFrameName];
const markup = await targetFrameSrc.build(params.payload);
const targetFrame = frames[params.targetFrameName];
const markup = await targetFrame.build(params.frameMessage);

const svg = await satori(
html(markup),
Expand Down
6 changes: 3 additions & 3 deletions api/redirect.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@
// Redirects to an external URL based on buttonIndex parameter. Used to work
// around the same origin policy on frames, which is being removed soon.
export default async (req, context) => {
const requestUrl = new URL(req.url);
const redirectUrl = requestUrl.searchParams.get('redirectUrl');
const requestURL = new URL(req.url);
const redirectURL = requestURL.searchParams.get('redirectURL');

return new Response('<div>redirect</div>',
{
status: 302,
headers: {
'Location': redirectUrl,
'Location': redirectURL,
},
}
);
Expand Down
18 changes: 15 additions & 3 deletions src/data/antitheft.js → modules/antitheft.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { getStore } from '@netlify/blobs';
import frame from '../api/frame';

// Utility functions to abstract the fetching and setting operations
const fetchData = async (key) => {
Expand Down Expand Up @@ -50,14 +51,25 @@ const removeBoundCast = (castHash) => removeFromList(getBoundCasts, setBoundCast
// 1. The castAuthorID is in boundAccounts.
// 2. The castHash is in boundCasts.
// 3. Both boundCasts & boundAccounts are empty.
const isFrameStolen = async (payload) => {
const { fid: castAuthorID, hash: castHash } = payload.validData.data.frameActionBody.castId;
const isFrameStolen = async (frameMessage) => {
console.log('isFrameStolen', frameMessage);
const { castId, requestURL } = frameMessage;
if (!castId || !requestURL) {
console.log('isFrameStolen:quickExit', castId, requestURL);
return false;
}

const { fid: castAuthorID, hash: castHash } = castId;
const boundCasts = await getBoundCasts();
const boundAccounts = await getBoundAccounts();

const isAuthorAllowed = boundAccounts.includes(castAuthorID) || boundAccounts.length === 0;
const isCastAllowed = boundCasts.includes(castHash) || boundCasts.length === 0;
const isFirstParty = payload.untrustedData.url.indexOf(process.env.URL) > -1;
const isFirstParty = requestURL ? requestURL.indexOf(process.env.URL) > -1 : true;

console.log('isAuthorAllowed', isAuthorAllowed, castAuthorID, boundAccounts);
console.log('isCastAllowed', isCastAllowed, castHash, boundCasts);
console.log('isFirstParty', isFirstParty);

return !isFirstParty || !isAuthorAllowed || !isCastAllowed;
};
Expand Down
29 changes: 17 additions & 12 deletions modules/getTargetFrame.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,23 @@
const DEFAULT_FRAME = 'poster';
import frames from '../src/frames';
import { isFrameStolen } from './antitheft';

export default (name, buttonId, frames) => {
export default async (frameMessage) => {
const frameIsStolen = await isFrameStolen(frameMessage);
let targetFrameName = DEFAULT_FRAME;
let redirectUrl = null;
if (name && buttonId) {
const originFrame = frames[name];
const button = originFrame.buttons[buttonId-1];
let redirectURL = null;
console.log('getTargetFrame:frameIsStolen', frameIsStolen);
if (frameIsStolen) {
targetFrameName = 'stolen';
} else if (frameMessage.buttonIndex) {
const originFrame = frames[frameMessage.from];
const button = originFrame.buttons[frameMessage.buttonIndex-1];
targetFrameName = button.goTo;
redirectUrl = button.url;
}
const targetFrameSrc = frames[targetFrameName];
redirectURL = button.url;
}

return {
targetFrameSrc,
targetFrameName,
redirectUrl
};
targetFrame: frames[targetFrameName],
redirectURL
}
}
13 changes: 0 additions & 13 deletions modules/safeDecode.js

This file was deleted.

12 changes: 12 additions & 0 deletions modules/sanitize.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { JSDOM } from 'jsdom';
import DOMPurify from 'dompurify';

export default (text) => {
try {
const window = new JSDOM('').window;
const purify = DOMPurify(window);
return purify.sanitize(text);
} catch {
throw new Error(`That ain't no string mfr`)
}
}
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"dependencies": {
"@netlify/blobs": "^6.4.2",
"dompurify": "^3.0.8",
"frames.js": "^0.1.1",
"jsdom": "^24.0.0",
"satori": "^0.10.11",
"satori-html": "^0.3.2",
Expand Down
3 changes: 2 additions & 1 deletion src/data/framer.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { getStore } from '@netlify/blobs';
import { getUsername } from './username';
import sanitize from '../../modules/sanitize';

const getFramer = async() => {
const store = getStore('gameState');
Expand All @@ -15,7 +16,7 @@ const getFramer = async() => {
const setFramer = async(fid, taunt) => {
const store = getStore('gameState');
await store.set('framer', fid);
await store.set('taunt', taunt);
await store.set('taunt', sanitize(taunt));
}

export {
Expand Down
21 changes: 0 additions & 21 deletions src/data/message.js

This file was deleted.

16 changes: 7 additions & 9 deletions src/frames/count.js
Original file line number Diff line number Diff line change
@@ -1,24 +1,22 @@
import mainLayout from '../layouts/main';
import { getFramer, setFramer } from '../data/framer';
import { getCount, incrementCount } from '../data/count';
import safeDecode from '../../modules/safeDecode';

const build = async (payload) => {
const build = async (frameMessage) => {
let count = await getCount();
const validData = payload?.validData;

if (payload.validData && payload.referringFrame == 'count') {
if (frameMessage.from == 'count') {
count = await incrementCount(count);
const tauntInput = validData.data.frameActionBody.inputText;
await setFramer(validData.data.fid, tauntInput);
const tauntInput = frameMessage.inputText;
await setFramer(frameMessage.requesterFid, tauntInput);
}

const { username, taunt } = await getFramer() || '';

let tauntOutput;
tauntOutput = taunt ? `
<div style="font-size: 2em; line-height: 1.3; color: #cacaca; margin-top: 1em; padding: 0 2rem; text-align: center;">
"${safeDecode(taunt)}"
"${taunt}"
</div>
` : '';

Expand All @@ -35,7 +33,7 @@ const build = async (payload) => {
</fc-frame>
`;

return mainLayout(payload, frameHTML);
return mainLayout(frameMessage, frameHTML);
}

export const inputs = [
Expand All @@ -57,7 +55,7 @@ export const buttons = [
]

export default {
name: 'stolen',
name: 'count',
build,
buttons,
inputs
Expand Down

0 comments on commit 818bb8d

Please sign in to comment.