Skip to content

Commit

Permalink
feat: add @farcaster/connect package
Browse files Browse the repository at this point in the history
  • Loading branch information
horsefacts committed Dec 7, 2023
1 parent 7178269 commit 3b16588
Show file tree
Hide file tree
Showing 16 changed files with 715 additions and 27 deletions.
3 changes: 2 additions & 1 deletion apps/relay/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@
"fastify": "^4.24.3",
"ioredis": "^5.3.2",
"neverthrow": "^6.1.0",
"siwe": "^2.1.4"
"siwe": "^2.1.4",
"@farcaster/connect": "*"
},
"devDependencies": {
"axios": "^1.6.2",
Expand Down
18 changes: 9 additions & 9 deletions apps/relay/src/channels.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Redis } from "ioredis";
import { ResultAsync, err, ok } from "neverthrow";
import { randomUUID } from "crypto";
import { RelayAsyncResult, RelayError } from "./errors";
import { ConnectAsyncResult, ConnectError } from "@farcaster/connect";

interface ChannelStoreOpts {
redisUrl: string;
Expand All @@ -17,36 +17,36 @@ export class ChannelStore<T> {
this.ttl = ttl ?? 3600;
}

async open(state?: T): RelayAsyncResult<string> {
async open(state?: T): ConnectAsyncResult<string> {
const channelToken = randomUUID();
return ResultAsync.fromPromise(
this.redis.set(channelToken, JSON.stringify(state ?? {}), "EX", this.ttl),
(err) => new RelayError("unavailable", err as Error),
(err) => new ConnectError("unavailable", err as Error),
).andThen(() => ok(channelToken));
}

async update(channelToken: string, state: T): RelayAsyncResult<T> {
async update(channelToken: string, state: T): ConnectAsyncResult<T> {
return ResultAsync.fromPromise(
this.redis.set(channelToken, JSON.stringify(state), "KEEPTTL"),
(err) => new RelayError("unavailable", err as Error),
(err) => new ConnectError("unavailable", err as Error),
).andThen(() => ok(state));
}

async read(channelToken: string): RelayAsyncResult<T> {
async read(channelToken: string): ConnectAsyncResult<T> {
return ResultAsync.fromPromise(
this.redis.get(channelToken),
(err) => new RelayError("unavailable", err as Error),
(err) => new ConnectError("unavailable", err as Error),
).andThen((channel) => {
if (channel) {
return ok(JSON.parse(channel));
} else {
return err(new RelayError("not_found", "Channel not found"));
return err(new ConnectError("not_found", "Channel not found"));
}
});
}

async close(channelToken: string) {
return ResultAsync.fromPromise(this.redis.del(channelToken), (err) => new RelayError("unknown", err as Error));
return ResultAsync.fromPromise(this.redis.del(channelToken), (err) => new ConnectError("unknown", err as Error));
}

async clear() {
Expand Down
6 changes: 3 additions & 3 deletions apps/relay/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
status,
} from "./handlers";
import { logger } from "./logger";
import { RelayError, RelayAsyncResult } from "./errors";
import { ConnectError, ConnectAsyncResult } from "@farcaster/connect";

const log = logger.child({ component: "RelayServer" });

Expand Down Expand Up @@ -82,12 +82,12 @@ export class RelayServer {
);
}

async start(ip = "0.0.0.0", port = 0): RelayAsyncResult<string> {
async start(ip = "0.0.0.0", port = 0): ConnectAsyncResult<string> {
return new Promise((resolve) => {
this.app.listen({ host: ip, port }, (e, address) => {
if (e) {
log.error({ err: e, errMsg: e.message }, "Failed to start http server");
resolve(err(new RelayError("unavailable", `Failed to start http server: ${e.message}`)));
resolve(err(new ConnectError("unavailable", `Failed to start http server: ${e.message}`)));
}

log.info({ address }, "Started relay server");
Expand Down
8 changes: 8 additions & 0 deletions packages/connect/.changeset/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Changesets

Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works
with multi-package repos, or single-package repos to help you version and publish your code. You can
find the full documentation for it [in our repository](https://github.com/changesets/changesets)

We have a quick list of common questions to get you started engaging with this project in
[our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md)
11 changes: 11 additions & 0 deletions packages/connect/.changeset/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"$schema": "https://unpkg.com/@changesets/[email protected]/schema.json",
"changelog": "@changesets/cli/changelog",
"commit": false,
"fixed": [],
"linked": [],
"access": "restricted",
"baseBranch": "main",
"updateInternalDependencies": "patch",
"ignore": []
}
21 changes: 21 additions & 0 deletions packages/connect/biome.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"$schema": "../../node_modules/@biomejs/biome/configuration_schema.json",
"organizeImports": {
"enabled": false
},
"formatter": {
"enabled": true,
"indentSize": 2,
"indentStyle": "space",
"lineWidth": 120
},
"linter": {
"enabled": true,
"rules": {
"recommended": true,
"complexity": {
"useLiteralKeys": "off"
}
}
}
}
21 changes: 21 additions & 0 deletions packages/connect/jest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import type { Config } from "jest";

const jestConfig: Config = {
testEnvironment: "node",
moduleNameMapper: {
"^~/(.*)$": "<rootDir>/src/$1",
"^(.+)_generated.js$": "$1_generated", // Support flatc generated files
},
coveragePathIgnorePatterns: ["<rootDir>/build/", "<rootDir>/node_modules/"],
testPathIgnorePatterns: ["<rootDir>/build", "<rootDir>/node_modules"],
extensionsToTreatAsEsm: [".ts"],
/**
* For high performance with minimal configuration transform with TS with swc.
* @see https://github.com/farcasterxyz/hubble/issues/314
*/
transform: {
"^.+\\.(t|j)sx?$": "@swc/jest",
},
};

export default jestConfig;
33 changes: 33 additions & 0 deletions packages/connect/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"name": "@farcaster/connect",
"version": "0.0.1",
"main": "./dist/index.js",
"module": "./dist/index.mjs",
"types": "./dist/index.d.ts",
"exports": {
".": {
"require": "./dist/index.js",
"import": "./dist/index.mjs",
"types": "./dist/index.d.ts"
}
},
"files": [
"dist"
],
"license": "MIT",
"scripts": {
"build": "tsup --config tsup.config.ts",
"clean": "rimraf ./dist",
"lint": "biome format src/ --write && biome check src/ --apply",
"lint:ci": "biome ci src/",
"test": "NODE_OPTIONS=--experimental-vm-modules jest",
"test:ci": "ENVIRONMENT=test NODE_OPTIONS=--experimental-vm-modules jest --ci --forceExit --coverage",
"prepublishOnly": "yarn run build"
},
"dependencies": {
"siwe": "^2.1.4"
},
"devDependencies": {
"viem": "^1.19.11"
}
}
27 changes: 14 additions & 13 deletions apps/relay/src/errors.ts → packages/connect/src/errors.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@
import { Result } from "neverthrow";

interface RelayErrorOpts {
interface ConnectErrorOpts {
message: string;
cause: Error | RelayError;
cause: Error | ConnectError;
presentable: boolean;
}

export class RelayError extends Error {
public readonly errCode: RelayErrorCode;
export class ConnectError extends Error {
public readonly errCode: ConnectErrorCode;

/* Indicates if error message can be presented to the user */
public readonly presentable: boolean = false;

/**
* @param errCode - the RelayError code for this message
* @param context - a message, another Error, or a RelayErrorOpts
* @param errCode - the ConnectError code for this message
* @param context - a message, another Error, or a ConnectErrorOpts
*/
constructor(errCode: RelayErrorCode, context: Partial<RelayErrorOpts> | string | Error) {
let parsedContext: string | Error | Partial<RelayErrorOpts>;
constructor(errCode: ConnectErrorCode, context: Partial<ConnectErrorOpts> | string | Error) {
let parsedContext: string | Error | Partial<ConnectErrorOpts>;

if (typeof context === "string") {
parsedContext = { message: context };
Expand All @@ -33,25 +33,26 @@ export class RelayError extends Error {

super(parsedContext.message, { cause: parsedContext.cause });

this.name = "RelayError";
this.name = "ConnectError";
this.errCode = errCode;
}
}

/**
* RelayErrorCode defines all the types of errors that can be raised.
* ConnectErrorCode defines all the types of errors that can be raised.
*
* A string union type is chosen over an enumeration since TS enums are unusual types that generate
* javascript code and may cause downstream issues. See:
* https://www.executeprogram.com/blog/typescript-features-to-avoid
*/
export type RelayErrorCode =
export type ConnectErrorCode =
/* The request did not have valid authentication credentials, retry with credentials */
| "unauthenticated"
/* The authenticated request did not have the authority to perform this action */
| "unauthorized"
/* The request cannot be completed as constructed, do not retry */
| "bad_request"
| "bad_request.validation_failure"
/* The requested resource could not be found */
| "not_found"
/* The request could not be completed because the operation is not executable */
Expand All @@ -62,5 +63,5 @@ export type RelayErrorCode =
| "unknown";

/** Type alias for shorthand when handling errors */
export type RelayResult<T> = Result<T, RelayError>;
export type RelayAsyncResult<T> = Promise<RelayResult<T>>;
export type ConnectResult<T> = Result<T, ConnectError>;
export type ConnectAsyncResult<T> = Promise<ConnectResult<T>>;
2 changes: 2 additions & 0 deletions packages/connect/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from "./errors";
export * from "./messages";
1 change: 1 addition & 0 deletions packages/connect/src/messages/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * as messages from "./messages";
Loading

0 comments on commit 3b16588

Please sign in to comment.