Skip to content

Commit

Permalink
feat: app/wallet clients (#2)
Browse files Browse the repository at this point in the history
  • Loading branch information
horsefacts authored Dec 19, 2023
1 parent 7d38059 commit 2e0fac4
Show file tree
Hide file tree
Showing 18 changed files with 579 additions and 4 deletions.
6 changes: 3 additions & 3 deletions apps/relay/src/handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,12 +75,12 @@ export async function authenticate(request: FastifyRequest<{ Body: AuthenticateR
signature,
});
if (update.isOk()) {
reply.send();
reply.send(update.value);
} else {
reply.code(500).send({ error: update.error.message });
}
} else {
if (channel.error.errCode === "not_found") reply.code(401).send();
if (channel.error.errCode === "not_found") reply.code(401).send({ error: "Unauthorized " });
reply.code(500).send({ error: channel.error.message });
}
}
Expand All @@ -97,7 +97,7 @@ export async function status(request: FastifyRequest, reply: FastifyReply) {
}
reply.send(res);
} else {
if (channel.error.errCode === "not_found") reply.code(401).send();
if (channel.error.errCode === "not_found") reply.code(401).send({ error: "Unauthorized" });
reply.code(500).send({ error: channel.error.message });
}
}
Expand Down
2 changes: 1 addition & 1 deletion apps/relay/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ export class RelayServer {
if (channelToken) {
request.channelToken = channelToken;
} else {
reply.code(401).send();
reply.code(401).send({ error: "Unauthorized " });
return;
}
});
Expand Down
68 changes: 68 additions & 0 deletions packages/connect/src/actions/authenticate.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { createWalletClient } from "../clients/createWalletClient";
import { jest } from "@jest/globals";

describe("authenticate", () => {
const client = createWalletClient({
relayURI: "https://connect.farcaster.xyz",
});

afterEach(() => {
jest.restoreAllMocks();
});

const message = "example.com wants you to sign in with your Ethereum account = [...]";
const signature = "0xabcd1234";
const fid = 1;
const username = "alice";
const bio = "I'm a little teapot who didn't fill out my bio";
const displayName = "Alice Teapot";
const pfpUrl = "https://example.com/alice.png";

const statusResponseDataStub = {
state: "completed",
nonce: "abcd1234",
connectURI: "farcaster://connect?nonce=abcd1234[...]",
message,
signature,
fid,
username,
bio,
displayName,
pfpUrl,
};

test("constructs API request", async () => {
const response = new Response(JSON.stringify(statusResponseDataStub));
const spy = jest.spyOn(global, "fetch").mockResolvedValue(response);

const res = await client.authenticate({
channelToken: "some-channel-token",
message,
signature,
fid,
username,
bio,
displayName,
pfpUrl,
});

expect(res.response).toEqual(response);
expect(spy).toHaveBeenCalledTimes(1);
expect(spy).toHaveBeenCalledWith("https://connect.farcaster.xyz/v1/connect/authenticate", {
method: "POST",
body: JSON.stringify({
message,
signature,
fid,
username,
bio,
displayName,
pfpUrl,
}),
headers: {
"Content-Type": "application/json",
Authorization: "Bearer some-channel-token",
},
});
});
});
28 changes: 28 additions & 0 deletions packages/connect/src/actions/authenticate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { StatusResponse } from "./status";
import { post, AsyncHttpResponse } from "../clients/transports/http";
import { Client } from "../clients/createClient";

export interface AuthenticateArgs extends AuthenticateRequest {
channelToken: string;
}

interface AuthenticateRequest {
message: string;
signature: `0x${string}`;
fid: number;
username: string;
bio: string;
displayName: string;
pfpUrl: string;
}

export type AuthenticateResponse = StatusResponse;

const path = "connect/authenticate";

export const authenticate = async (
client: Client,
{ channelToken, ...request }: AuthenticateArgs,
): AsyncHttpResponse<AuthenticateResponse> => {
return post<AuthenticateRequest, AuthenticateResponse>(client, path, request, { authToken: channelToken });
};
46 changes: 46 additions & 0 deletions packages/connect/src/actions/connect.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { createAppClient } from "../clients/createAppClient";
import { jest } from "@jest/globals";

describe("connect", () => {
const client = createAppClient({
relayURI: "https://connect.farcaster.xyz",
});

afterEach(() => {
jest.restoreAllMocks();
});

const siweUri = "https://example.com/login";
const domain = "example.com";
const nonce = "abcd1234";

const connectResponseDataStub = {
channelToken: "some-channel-token",
state: "completed",
};

test("constructs API request", async () => {
const response = new Response(JSON.stringify(connectResponseDataStub));
const spy = jest.spyOn(global, "fetch").mockResolvedValue(response);

const res = await client.connect({
siweUri,
domain,
nonce,
});

expect(res.response).toEqual(response);
expect(spy).toHaveBeenCalledTimes(1);
expect(spy).toHaveBeenCalledWith("https://connect.farcaster.xyz/v1/connect", {
method: "POST",
body: JSON.stringify({
siweUri,
domain,
nonce,
}),
headers: {
"Content-Type": "application/json",
},
});
});
});
24 changes: 24 additions & 0 deletions packages/connect/src/actions/connect.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { Client } from "../clients/createClient";
import { AsyncHttpResponse, post } from "../clients/transports/http";

export type ConnectArgs = ConnectRequest;

interface ConnectRequest {
siweUri: string;
domain: string;
nonce?: string;
notBefore?: string;
expirationTime?: string;
requestId?: string;
}

export interface ConnectResponse {
channelToken: string;
connectURI: string;
}

const path = "connect";

export const connect = async (client: Client, { ...request }: ConnectArgs): AsyncHttpResponse<ConnectResponse> => {
return post<ConnectRequest, ConnectResponse>(client, path, request);
};
36 changes: 36 additions & 0 deletions packages/connect/src/actions/status.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { createAppClient } from "../clients/createAppClient";
import { jest } from "@jest/globals";

describe("status", () => {
const client = createAppClient({
relayURI: "https://connect.farcaster.xyz",
});

afterEach(() => {
jest.restoreAllMocks();
});

const statusResponseDataStub = {
state: "pending",
nonce: "abcd1234",
connectURI: "farcaster://connect?nonce=abcd1234[...]",
};

test("constructs API request", async () => {
const response = new Response(JSON.stringify(statusResponseDataStub));
const spy = jest.spyOn(global, "fetch").mockResolvedValue(response);

const res = await client.status({
channelToken: "some-channel-token",
});

expect(res.response).toEqual(response);
expect(spy).toHaveBeenCalledTimes(1);
expect(spy).toHaveBeenCalledWith("https://connect.farcaster.xyz/v1/connect/status", {
headers: {
"Content-Type": "application/json",
Authorization: "Bearer some-channel-token",
},
});
});
});
25 changes: 25 additions & 0 deletions packages/connect/src/actions/status.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { Client } from "../clients/createClient";
import { get, AsyncHttpResponse } from "../clients/transports/http";

export interface StatusArgs {
channelToken: string;
}

export interface StatusResponse {
state: "pending" | "completed";
nonce: string;
connectURI: string;
message?: string;
signature?: `0x${string}`;
fid?: number;
username?: string;
bio?: string;
displayName?: string;
pfpUrl?: string;
}

const path = "connect/status";

export const status = async (client: Client, { channelToken }: StatusArgs): AsyncHttpResponse<StatusResponse> => {
return get(client, path, { authToken: channelToken });
};
37 changes: 37 additions & 0 deletions packages/connect/src/clients/createAppClient.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { createAppClient, AppClient } from "./createAppClient";

describe("createAppClient", () => {
const config = {
relayURI: "https://connect.farcaster.xyz",
};

let appClient: AppClient;

beforeEach(() => {
appClient = createAppClient(config);
});

test("adds version to config", () => {
expect(appClient.config).toEqual({
relayURI: "https://connect.farcaster.xyz",
version: "v1",
});
});

test("overrides version", () => {
appClient = createAppClient({
...config,
version: "v2",
});

expect(appClient.config).toEqual({
relayURI: "https://connect.farcaster.xyz",
version: "v2",
});
});

test("includes app actions", () => {
expect(appClient.connect).toBeDefined();
expect(appClient.status).toBeDefined();
});
});
18 changes: 18 additions & 0 deletions packages/connect/src/clients/createAppClient.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { connect, ConnectArgs, ConnectResponse } from "../actions/connect";
import { status, StatusArgs, StatusResponse } from "../actions/status";
import { Client, ClientConfig, createClient } from "./createClient";
import { AsyncHttpResponse } from "./transports/http";

export interface AppClient extends Client {
connect: (args: ConnectArgs) => AsyncHttpResponse<ConnectResponse>;
status: (args: StatusArgs) => AsyncHttpResponse<StatusResponse>;
}

export const createAppClient = (config: ClientConfig): AppClient => {
const client = createClient(config);
return {
...client,
connect: (args: ConnectArgs) => connect(client, args),
status: (args: StatusArgs) => status(client, args),
};
};
46 changes: 46 additions & 0 deletions packages/connect/src/clients/createClient.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { createClient, Client } from "./createClient";

describe("createClient", () => {
const config = {
relayURI: "https://connect.farcaster.xyz",
};

let client: Client;

beforeEach(() => {
client = createClient(config);
});

test("adds version to config", () => {
expect(client.config).toEqual({
relayURI: "https://connect.farcaster.xyz",
version: "v1",
});
});

test("overrides version", () => {
client = createClient({
...config,
version: "v2",
});

expect(client.config).toEqual({
relayURI: "https://connect.farcaster.xyz",
version: "v2",
});
});

test("includes no actions", () => {
client = createClient({
...config,
version: "v2",
});

expect(client).toEqual({
config: {
relayURI: "https://connect.farcaster.xyz",
version: "v2",
},
});
});
});
18 changes: 18 additions & 0 deletions packages/connect/src/clients/createClient.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
export interface ClientConfig {
relayURI: string;
version?: string;
}

export interface Client {
config: ClientConfig;
}

const configDefaults = {
version: "v1",
};

export const createClient = (config: ClientConfig) => {
return {
config: { ...configDefaults, ...config },
};
};
Loading

0 comments on commit 2e0fac4

Please sign in to comment.