Skip to content

Commit

Permalink
feat: getFrameMessage can now handle mock frame messages. (#149)
Browse files Browse the repository at this point in the history
  • Loading branch information
cnasc authored Feb 20, 2024
1 parent 483a2d5 commit ee72476
Show file tree
Hide file tree
Showing 6 changed files with 144 additions and 2 deletions.
5 changes: 5 additions & 0 deletions .changeset/cuddly-frogs-flow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@coinbase/onchainkit': minor
---

- **feat**: `getFrameMessage` can now handle mock frame messages. When `allowFramegear` is passed as an option (defaults to `false`), it will skip validating which facilitates testing locally running apps with future releases of `framegear`.
63 changes: 63 additions & 0 deletions src/core/getFrameMessage.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { mockNeynarResponse } from './mock';
import { getFrameMessage } from './getFrameMessage';
import { getMockFrameRequest } from './getMockFrameRequest';
import { neynarBulkUserLookup } from '../utils/neynar/user/neynarUserFunctions';
import { FrameRequest } from './types';
import { neynarFrameValidation } from '../utils/neynar/frame/neynarFrameFunctions';
Expand All @@ -24,6 +25,68 @@ describe('getFrameValidatedMessage', () => {
expect(result?.isValid).toEqual(false);
});

it('should consider invalid non-mock requests as invalid, even if mock requests are allowed', async () => {
const result = await getFrameMessage(
{
trustedData: { messageBytes: 'invalid' },
} as FrameRequest,
{ allowFramegear: true },
);
expect(result?.isValid).toEqual(false);
expect(result.message).toBeUndefined();
});

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

it('should consider mock messages invalid, if not allowed (default)', async () => {
const result = await getFrameMessage(
getMockFrameRequest({
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
15 changes: 13 additions & 2 deletions src/core/getFrameMessage.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { FrameRequest, FrameValidationResponse } from './types';
import { FrameRequest, FrameValidationResponse, MockFrameRequest } from './types';
import {
NEYNAR_DEFAULT_API_KEY,
neynarFrameValidation,
Expand All @@ -9,6 +9,7 @@ type FrameMessageOptions =
neynarApiKey?: string;
castReactionContext?: boolean;
followContext?: boolean;
allowFramegear?: boolean;
}
| undefined;

Expand All @@ -20,9 +21,19 @@ type FrameMessageOptions =
* @param body The JSON received by server on frame callback
*/
async function getFrameMessage(
body: FrameRequest,
body: FrameRequest | MockFrameRequest,
messageOptions?: FrameMessageOptions,
): Promise<FrameValidationResponse> {
// Skip validation only when allowed and when receiving a request from framegear
if (messageOptions?.allowFramegear) {
if ((body as MockFrameRequest).mockFrameData) {
return {
isValid: true,
message: (body as MockFrameRequest).mockFrameData,
};
}
}

// Validate the message
const response = await neynarFrameValidation(
body?.trustedData?.messageBytes,
Expand Down
37 changes: 37 additions & 0 deletions src/core/getMockFrameRequest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { FrameRequest, MockFrameRequest, MockFrameRequestOptions } from './types';

/**
* 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 getMockFrameRequest(
request: FrameRequest,
options?: MockFrameRequestOptions,
): MockFrameRequest {
return {
...request,
mockFrameData: {
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 { getMockFrameRequest };
23 changes: 23 additions & 0 deletions src/core/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,3 +175,26 @@ export type EASChainDefinition = {
id: number; // blockchain source id
schemaUids: EASSchemaUid[]; // Array of EAS Schema UIDs
};

/**
* Settings to simulate statuses on mock frames.
*
* Note: exported as public Type
*/
export type MockFrameRequestOptions = {
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
};

/**
* A mock frame request payload
*
* Note: exported as public Type
*/
export type MockFrameRequest = FrameRequest & { mockFrameData: Required<FrameValidationData> };
3 changes: 3 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export { getEASAttestations } from './core/getEASAttestations';
export { getFrameHtmlResponse } from './core/getFrameHtmlResponse';
export { getFrameMetadata } from './core/getFrameMetadata';
export { getFrameMessage } from './core/getFrameMessage';
export { getMockFrameRequest } from './core/getMockFrameRequest';
export { FrameMetadata } from './components/FrameMetadata';
export { Avatar } from './components/Avatar';
export { Name } from './components/Name';
Expand All @@ -17,4 +18,6 @@ export type {
FrameMetadataType,
FrameRequest,
FrameValidationData,
MockFrameRequest,
MockFrameRequestOptions,
} from './core/types';

0 comments on commit ee72476

Please sign in to comment.