Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

wip: debugging #148

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions framegear/app/api/postFrame/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { NextRequest } from 'next/server';
import { getDebugFrameRequest } from '@coinbase/onchainkit';

export async function POST(req: NextRequest) {
const data = await req.json();
const { frameData, options } = data;
const postUrl = frameData.url;
const debugPayload = getDebugFrameRequest({ untrustedData: frameData }, options);

const res = await fetch(postUrl, {
method: 'POST',
body: JSON.stringify(debugPayload),
});

const text = await res.text();

return Response.json({ text });
}
23 changes: 21 additions & 2 deletions framegear/components/Frame/Frame.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { postFrame } from '@/utils/postFrame';
import { frameResultsAtom } from '@/utils/store';
import { useAtom } from 'jotai';
import { PropsWithChildren, useMemo } from 'react';
Expand Down Expand Up @@ -26,8 +27,12 @@ function ValidFrame({ tags }: { tags: Record<string, string> }) {
// TODO: when debugger is live we will also need to extract actions, etc.
const buttons = [1, 2, 3, 4].map((index) => {
const key = `fc:frame:button:${index}`;
const actionKey = `${key}:action`;
const targetKey = `${key}:target`;
const value = tags[key];
return value ? { key, value } : undefined;
const action = tags[actionKey];
const target = tags[targetKey];
return value ? { key, value, action, target } : undefined;
});
return {
image,
Expand Down Expand Up @@ -82,7 +87,21 @@ function FrameButton({ children }: PropsWithChildren<{}>) {
<button
className="border-button w-[45%] grow rounded-lg border bg-white p-2 text-black"
type="button"
disabled
onClick={() =>
postFrame({
buttonIndex: 1,
castId: {
fid: 0,
hash: '0xthisisnotreal',
},
inputText: '',
fid: 0,
messageHash: '0xthisisnotreal',
network: 0,
timestamp: 0,
url: 'http://localhost:3000/api/frame',
})
}
>
<span>{children}</span>
</button>
Expand Down
1 change: 1 addition & 0 deletions framegear/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"test": "jest"
},
"dependencies": {
"@coinbase/onchainkit": "portal:./..",
"@radix-ui/react-icons": "^1.3.0",
"jotai": "^2.6.4",
"next": "14.1.0",
Expand Down
18 changes: 18 additions & 0 deletions framegear/utils/postFrame.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { FrameRequest, DebugFrameRequestOptions } from '@coinbase/onchainkit';

type FrameData = FrameRequest['untrustedData'];

export async function postFrame(frameData: FrameData, options?: DebugFrameRequestOptions) {
const res = await fetch('/api/postFrame', {
body: JSON.stringify({
frameData,
options,
}),
method: 'POST',
headers: {
contentType: 'application/json',
},
});
const json = await res.json();
console.log(json.text);
}
13 changes: 13 additions & 0 deletions framegear/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -438,6 +438,18 @@ __metadata:
languageName: node
linkType: hard

"@coinbase/onchainkit@portal:./..::locator=framegear%40workspace%3A.":
version: 0.0.0-use.local
resolution: "@coinbase/onchainkit@portal:./..::locator=framegear%40workspace%3A."
peerDependencies:
graphql: ^14
graphql-request: ^6
react: ^18
react-dom: ^18
viem: ^2.7.0
languageName: node
linkType: soft

"@cspotcode/source-map-support@npm:^0.8.0":
version: 0.8.1
resolution: "@cspotcode/source-map-support@npm:0.8.1"
Expand Down Expand Up @@ -3179,6 +3191,7 @@ __metadata:
version: 0.0.0-use.local
resolution: "framegear@workspace:."
dependencies:
"@coinbase/onchainkit": "portal:./.."
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inteeeeerestinggggggggg

What the heck which magic is this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Once we finalize probably want to specify the exact version, but this is great for dev

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Portal Protocol 🤯

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now you're thinking with portals

"@radix-ui/react-icons": "npm:^1.3.0"
"@testing-library/jest-dom": "npm:^6.4.2"
"@testing-library/react": "npm:^14.2.1"
Expand Down
50 changes: 50 additions & 0 deletions src/core/getDebugFrameRequest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { FrameRequest, FrameValidationData } from './types';

export type DebugFrameRequestOptions = {
following?: boolean; // Indicates if the viewer clicking the frame follows the cast author
interactor?: {
fid?: number; // Viewer Farcaster ID
custody_address?: string; // Viewer custody address
verified_accounts?: string[]; // Viewer account addresses
};
liked?: boolean; // Indicates if the viewer clicking the frame liked the cast
recasted?: boolean; // Indicates if the viewer clicking the frame recasted the cast
};

type DebugFrameRequest = FrameRequest & { onchainkitDebug: Required<FrameValidationData> };

/**
* Modify a standard frame request to include simulated values (e.g., indicate the viewer
* follows the cast author) for development/debugging purposes.
* @param request A standard frame request.
* @param options An object containing values we will pretend are real for the purposes of debugging.
* @returns
*/
function getDebugFrameRequest(
cnasc marked this conversation as resolved.
Show resolved Hide resolved
request: FrameRequest,
options?: DebugFrameRequestOptions,
): DebugFrameRequest {
return {
...request,
onchainkitDebug: {
button: request.untrustedData.buttonIndex,
input: request.untrustedData.inputText,
following: !!options?.following,
interactor: {
fid: options?.interactor?.fid || 0,
custody_address: options?.interactor?.custody_address || '0xnotarealaddress',
verified_accounts: options?.interactor?.verified_accounts || [],
},
liked: !!options?.liked,
recasted: !!options?.recasted,
valid: true,
raw: {
valid: true,
// TODO: unjank
action: {} as any,
},
},
};
}

export { getDebugFrameRequest, type DebugFrameRequest };
52 changes: 52 additions & 0 deletions src/core/getFrameMessage.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { getFrameMessage } from './getFrameMessage';
import { neynarBulkUserLookup } from '../utils/neynar/user/neynarUserFunctions';
import { FrameRequest } from './types';
import { neynarFrameValidation } from '../utils/neynar/frame/neynarFrameFunctions';
import { getDebugFrameRequest } from './getDebugFrameRequest';

jest.mock('../utils/neynar/user/neynarUserFunctions', () => {
return {
Expand All @@ -24,6 +25,57 @@ describe('getFrameValidatedMessage', () => {
expect(result?.isValid).toEqual(false);
});

it('should consider debug messages valid, if allowed', async () => {
const result = await getFrameMessage(
getDebugFrameRequest({
untrustedData: {
buttonIndex: 1,
castId: {
fid: 0,
hash: '0xthisisnotreal',
},
inputText: '',
fid: 0,
network: 0,
messageHash: '0xthisisnotreal',
timestamp: 0,
url: 'https://localhost:3000',
},
trustedData: {
messageBytes: '0xthisisnotreal',
},
}),
{ allowDebug: true },
);
expect(result?.isValid).toEqual(true);
expect(result.message?.button).toEqual(1);
});

it('should consider debug messages invalid, if not allowed (default)', async () => {
const result = await getFrameMessage(
getDebugFrameRequest({
untrustedData: {
buttonIndex: 1,
castId: {
fid: 0,
hash: '0xthisisnotreal',
},
inputText: '',
fid: 0,
network: 0,
messageHash: '0xthisisnotreal',
timestamp: 0,
url: 'https://localhost:3000',
},
trustedData: {
messageBytes: '0xthisisnotreal',
},
}),
);
expect(result?.isValid).toEqual(false);
expect(result.message).toBeUndefined();
});

it('should return the message if the message is valid', async () => {
const fid = 1234;
const addresses = ['0xaddr1'];
Expand Down
14 changes: 13 additions & 1 deletion src/core/getFrameMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@ import {
NEYNAR_DEFAULT_API_KEY,
neynarFrameValidation,
} from '../utils/neynar/frame/neynarFrameFunctions';
import { DebugFrameRequest } from './getDebugFrameRequest';

type FrameMessageOptions =
| {
neynarApiKey?: string;
castReactionContext?: boolean;
followContext?: boolean;
allowDebug?: boolean;
}
| undefined;

Expand All @@ -20,9 +22,19 @@ type FrameMessageOptions =
* @param body The JSON received by server on frame callback
*/
async function getFrameMessage(
body: FrameRequest,
body: FrameRequest | DebugFrameRequest,
messageOptions?: FrameMessageOptions,
): Promise<FrameValidationResponse> {
// Skip validation only when allowed and when receiving a debug request
if (messageOptions?.allowDebug) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if this is a boolean, we should either use has or is as prefix.

so maybe isMockRequest: boolean

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this case, not every request is necessarily a mock request. I think it's most clear as is: if set, it will allow mock requests to pass through, otherwise it will reject them.

if ((body as DebugFrameRequest).onchainkitDebug) {
return {
isValid: true,
message: (body as DebugFrameRequest).onchainkitDebug,
};
}
}

// Validate the message
const response = await neynarFrameValidation(
body?.trustedData?.messageBytes,
Expand Down
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// 🌲☀️🌲
export { version } from './version';
export { getDebugFrameRequest, type DebugFrameRequestOptions } from './core/getDebugFrameRequest';
cnasc marked this conversation as resolved.
Show resolved Hide resolved
export { getEASAttestations } from './core/getEASAttestations';
export { getFrameHtmlResponse } from './core/getFrameHtmlResponse';
export { getFrameMetadata } from './core/getFrameMetadata';
Expand Down
Loading